diff --git a/client/components/App/App.jsx b/client/components/App/App.jsx index 143a245a3..286460186 100644 --- a/client/components/App/App.jsx +++ b/client/components/App/App.jsx @@ -19,7 +19,7 @@ const App = () => { const [isNavbarExpanded, setIsNavbarExpanded] = useState(false); const auth = evaluateAuth(data && data.me ? data.me : {}); - const { username, isSignedIn, isAdmin, isVendor } = auth; + const { username, isSignedIn, isAdmin, isVendor, isTester } = auth; const signOut = async () => { await fetch('/api/auth/signout', { method: 'POST' }); @@ -104,7 +104,7 @@ const App = () => { )} {isSignedIn && ( <> - {!isVendor && ( + {(isAdmin || isTester) && (
  • { + const { triggerLoad, loadingMessage, loadingNote } = useTriggerLoad(); + const { + themedModal, + showThemedModal, + setShowThemedModal, + setThemedModalTitle, + setThemedModalContent, + setThemedModalType + } = useThemedModal({ + type: THEMES.SUCCESS, + title: 'Success' + }); + + const handleImportTests = async () => { + await triggerLoad( + async () => { + try { + const response = await fetch('/api/test/import', { method: 'POST' }); + if (!response.ok) { + throw new Error( + `Failed to import the latest Test Plan Versions: ${response.status}` + ); + } + + // Success + setThemedModalType(THEMES.SUCCESS); + setThemedModalTitle('Success'); + setThemedModalContent( + <>The latest Test Plan Versions have been imported. + ); + setShowThemedModal(true); + await refetch(); + } catch (e) { + // Failed, show themed message + setThemedModalType(THEMES.DANGER); + setThemedModalTitle('Error'); + setThemedModalContent(<>{e.message}); + setShowThemedModal(true); + } + }, + 'Importing latest Test Plan Versions', + 'This may take a few minutes ...' + ); + }; + + return ( + +
    +

    Admin Actions

    + +

    + Date of latest test plan version:{' '} + {dates.convertDateToString( + latestTestPlanVersion?.updatedAt, + 'MMMM D, YYYY HH:mm z' + )} +

    +
    + {showThemedModal && themedModal} +
    + ); +}; + +AdminSettings.propTypes = { + latestTestPlanVersion: PropTypes.shape({ + id: PropTypes.string.isRequired, + updatedAt: PropTypes.string.isRequired + }), + refetch: PropTypes.func +}; + +export default AdminSettings; diff --git a/client/components/UserSettings/TesterSettings.jsx b/client/components/UserSettings/TesterSettings.jsx new file mode 100644 index 000000000..c88695fe2 --- /dev/null +++ b/client/components/UserSettings/TesterSettings.jsx @@ -0,0 +1,106 @@ +import React, { useCallback, useEffect, useState } from 'react'; +import PropTypes from 'prop-types'; +import { Button, Form } from 'react-bootstrap'; +import { useMutation } from '@apollo/client'; +import { TESTER_SETTINGS_QUERY, UPDATE_ME_MUTATION } from './queries'; +import { AtPropType } from '../common/proptypes'; + +const TesterSettings = ({ ats, meAts }) => { + const [updateMe] = useMutation(UPDATE_ME_MUTATION, { + refetchQueries: [{ query: TESTER_SETTINGS_QUERY }] + }); + + const [checkedAts, setCheckedAts] = useState([]); + + if (!ats || !meAts || !checkedAts) return null; + + useEffect(() => { + if (!meAts) return; + setCheckedAts(meAts.map(at => at.id)); + }, [meAts]); + + const handleCheckedAt = useCallback( + event => { + const atId = event.target.id; + const isChecked = checkedAts.includes(atId); + if (isChecked) { + setCheckedAts(checkedAts.filter(item => item !== atId)); + } else { + setCheckedAts([...checkedAts, atId]); + } + }, + [checkedAts] + ); + + const handleSave = useCallback( + event => { + event.preventDefault(); + updateMe({ variables: { input: { atIds: checkedAts } } }); + }, + [checkedAts] + ); + + const savedAts = meAts.map(at => at.id); + + return ( +
    +

    Assistive Technology Settings

    +
    + {savedAts.length > 0 ? ( +
    +

    + You can currently test the following assistive technologies: +

    +
      + {ats + .filter(({ id: atId }) => savedAts.includes(atId)) + .map(at => ( +
    • + {at.name} +
    • + ))} +
    +
    + ) : ( +

    + You have not yet selected any assistive technologies. +

    + )} +
    +

    + Update the assistive technologies you can test by selecting from the + options below: +

    +
    +

    Assistive Technologies

    + + {ats?.map(at => { + return ( + atId === at.id)} + /> + ); + })} + + +
    +
    + ); +}; + +TesterSettings.propTypes = { + ats: PropTypes.arrayOf(AtPropType), + meAts: PropTypes.arrayOf(AtPropType) +}; + +export default TesterSettings; diff --git a/client/components/UserSettings/UserSettings.jsx b/client/components/UserSettings/UserSettings.jsx deleted file mode 100644 index 958d27f79..000000000 --- a/client/components/UserSettings/UserSettings.jsx +++ /dev/null @@ -1,143 +0,0 @@ -import React, { useState, useCallback, useEffect } from 'react'; -import { Helmet } from 'react-helmet'; -import { Button, Container, Form } from 'react-bootstrap'; -import { useMutation, useQuery } from '@apollo/client'; -import PageStatus from '../common/PageStatus'; -import { CURRENT_SETTINGS_QUERY, UPDATE_ME_MUTATION } from './queries'; - -const UserSettings = () => { - const { loading, data, error } = useQuery(CURRENT_SETTINGS_QUERY); - - const [updateMe] = useMutation(UPDATE_ME_MUTATION, { - refetchQueries: [{ query: CURRENT_SETTINGS_QUERY }] - }); - - const [checkedAts, setCheckedAts] = useState(undefined); - - const handleCheckedAt = useCallback( - event => { - const atId = event.target.id; - const isChecked = checkedAts.includes(atId); - if (isChecked) { - setCheckedAts(checkedAts.filter(item => item !== atId)); - } else { - setCheckedAts([...checkedAts, atId]); - } - }, - [checkedAts] - ); - - const handleSave = useCallback( - event => { - event.preventDefault(); - updateMe({ variables: { input: { atIds: checkedAts } } }); - }, - [checkedAts] - ); - - useEffect(() => { - if (!data) return; - setCheckedAts(data.me.ats.map(at => at.id)); - }, [data]); - - if (error) { - return ( - - ); - } - - if (loading) { - return ( - - ); - } - - if (!data || !checkedAts) return null; - - const { - ats, - me: { username, ats: userAtsData } - } = data; - const savedAts = userAtsData.map(at => at.id); - - return ( - - - - Settings | ARIA-AT - -

    Settings

    -
    -

    User Details

    -

    - - {username} - -

    -

    Assistive Technology Settings

    -
    - {savedAts.length > 0 ? ( -
    -

    - You can currently test the following assistive technologies: -

    -
      - {ats - .filter(({ id: atId }) => savedAts.includes(atId)) - .map(at => ( -
    • - {at.name} -
    • - ))} -
    -
    - ) : ( -

    - You have not yet selected any assistive technologies. -

    - )} -
    -

    - Update the assistive technologies you can test by selecting from the - options below: -

    -
    -

    Assistive Technologies

    - - {ats && - ats.map(at => { - return ( - atId === at.id)} - /> - ); - })} - - -
    -
    -
    -
    - ); -}; - -export default UserSettings; diff --git a/client/components/UserSettings/index.js b/client/components/UserSettings/index.js index 48df754fd..f040be42d 100644 --- a/client/components/UserSettings/index.js +++ b/client/components/UserSettings/index.js @@ -1,3 +1,88 @@ -import UserSettings from './UserSettings'; +import React from 'react'; +import { Helmet } from 'react-helmet'; +import { Container } from 'react-bootstrap'; +import { useQuery } from '@apollo/client'; +import PageStatus from '../common/PageStatus'; +import TesterSettings from './TesterSettings'; +import AdminSettings from './AdminSettings'; +import { ME_QUERY } from '../App/queries'; +import { TESTER_SETTINGS_QUERY, ADMIN_SETTINGS_QUERY } from './queries'; +import { evaluateAuth } from '../../utils/evaluateAuth'; + +const UserSettings = () => { + const { loading, data, error } = useQuery(ME_QUERY); + + const auth = evaluateAuth(data?.me ? data?.me : {}); + const { username, isAdmin, isVendor, isTester } = auth; + + const { + data: testerQueryData, + loading: testerQueryLoading, + error: testerQueryError + } = useQuery(TESTER_SETTINGS_QUERY, { + skip: isAdmin || isVendor // no need to query if admin or vendor + }); + + const { + data: adminQueryData, + loading: adminQueryLoading, + error: adminQueryError, + refetch: adminQueryRefetch + } = useQuery(ADMIN_SETTINGS_QUERY, { + skip: !isAdmin // no need to query if not admin + }); + + if (error || testerQueryError || adminQueryError) { + return ( + + ); + } + + if (loading || testerQueryLoading || adminQueryLoading) { + return ( + + ); + } + + return ( + + + + Settings | ARIA-AT + +

    Settings

    +
    +

    User Details

    +

    + + {username} + +

    +
    + {!isAdmin && isTester && !isVendor && ( + + )} + {isAdmin && ( + + )} +
    +
    + ); +}; export default UserSettings; diff --git a/client/components/UserSettings/queries.js b/client/components/UserSettings/queries.js index 016588602..7122f2b85 100644 --- a/client/components/UserSettings/queries.js +++ b/client/components/UserSettings/queries.js @@ -1,7 +1,7 @@ import { gql } from '@apollo/client'; import { AT_FIELDS, ME_FIELDS } from '@components/common/fragments'; -export const CURRENT_SETTINGS_QUERY = gql` +export const TESTER_SETTINGS_QUERY = gql` ${AT_FIELDS} ${ME_FIELDS} query CurrentSettings { @@ -17,6 +17,15 @@ export const CURRENT_SETTINGS_QUERY = gql` } `; +export const ADMIN_SETTINGS_QUERY = gql` + query AdminSettings { + latestTestPlanVersion { + id + updatedAt + } + } +`; + export const UPDATE_ME_MUTATION = gql` ${AT_FIELDS} mutation UpdateMe($input: UserInput) { diff --git a/client/components/common/BasicThemedModal/index.jsx b/client/components/common/BasicThemedModal/index.jsx index 5c53c5edb..4dbf8b1f2 100644 --- a/client/components/common/BasicThemedModal/index.jsx +++ b/client/components/common/BasicThemedModal/index.jsx @@ -2,9 +2,13 @@ import React, { useEffect, useMemo, useRef } from 'react'; import PropTypes from 'prop-types'; import { Button, Modal } from 'react-bootstrap'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { faExclamationTriangle } from '@fortawesome/free-solid-svg-icons'; +import { + faCheckCircle, + faExclamationTriangle +} from '@fortawesome/free-solid-svg-icons'; import styled from '@emotion/styled'; import { uniqueId } from 'lodash'; +import { THEMES, THEME_COLOR } from '@client/hooks/useThemedModal'; import { ActionButtonPropType } from '../proptypes'; const ModalTitleStyle = styled.h1` @@ -25,9 +29,7 @@ const ColorStrip = styled.div` width: 100%; height: 10px; ${props => props.hideHeadline && `display: none;`} - background-color: ${({ theme }) => - theme === 'danger' ? '#ce1b4c' : '#fab700'}; - + background-color: ${({ theme }) => THEME_COLOR(theme)}; border-top-left-radius: calc(0.3rem - 1px); border-top-right-radius: calc(0.3rem - 1px); `; @@ -36,7 +38,7 @@ const BasicThemedModal = ({ show = false, centered = false, animation = true, - theme = 'warning', // warning, danger + theme = THEMES.WARNING, // warning, danger, success dialogClassName = '', title = null, content = null, @@ -78,9 +80,13 @@ const BasicThemedModal = ({ > {title} @@ -103,7 +109,7 @@ const BasicThemedModal = ({ {actionButtons.map(({ action, text }) => (
  • +
  • + Settings +
  • Candidate Review
  • +
  • + Settings +
  • Candidate Review
  • +
  • + Settings +
  • User Details >joe-the-admin

    -

    Assistive Technology Settings

    -
    -

    - You have not yet selected any assistive technologies. -

    -
    + +
    +

    Admin Actions

    +

    - Update the assistive technologies you can test by selecting from - the options below: + Date of latest test plan version: January 23, 2025 20:56 UTC

    -
    -

    Assistive Technologies

    -
    -
    - -
    -
    - -
    -
    - -
    -
    - -
    diff --git a/client/tests/e2e/snapshots/saved/_candidate-review.html b/client/tests/e2e/snapshots/saved/_candidate-review.html index 068c908d6..57a4bb59f 100644 --- a/client/tests/e2e/snapshots/saved/_candidate-review.html +++ b/client/tests/e2e/snapshots/saved/_candidate-review.html @@ -47,6 +47,14 @@ >Candidate Review
  • +
  • + Settings +
  • Candidate Review
  • +
  • + Settings +
  • Candidate Review
  • +
  • + Settings +
  • Candidate Review
  • +
  • + Settings +
  • Candidate Review
  • +
  • + Settings +
  • Candidate Review
  • +
  • + Settings +
  • Candidate Review
  • +
  • + Settings +
  • Candidate Review
  • +
  • + Settings +
  • Candidate Review
  • +
  • + Settings +
  • Candidate Review
  • +
  • + Settings +
  • Candidate Review
  • +
  • + Settings +
  • Candidate Review
  • +
  • + Settings +
  • { + const { transaction } = context; + + const testPlanVersions = await getTestPlanVersions({ + testPlanReportAttributes: [], + atAttributes: [], + browserAttributes: [], + testPlanRunAttributes: [], + userAttribute: [], + pagination: { + limit: 1, + order: [['updatedAt', 'desc']] + }, + transaction + }); + + return testPlanVersions[0]; +}; + +module.exports = latestTestPlanVersionResolver; diff --git a/server/tests/integration/graphql.test.js b/server/tests/integration/graphql.test.js index 6c498662d..52a64d765 100644 --- a/server/tests/integration/graphql.test.js +++ b/server/tests/integration/graphql.test.js @@ -304,6 +304,9 @@ describe('graphql', () => { } } } + latestTestPlanVersion { + id + } testPlan(id: "checkbox") { __typename id