From 415080aee37daacb5ab3cca52f4dd734d13f8515 Mon Sep 17 00:00:00 2001 From: Paul Clue <67766160+Paul-Clue@users.noreply.github.com> Date: Thu, 17 Aug 2023 15:47:01 -0500 Subject: [PATCH 01/59] Add required reports dropdown --- client/components/ManageTestQueue/index.jsx | 98 +++++++++++++++++++-- 1 file changed, 93 insertions(+), 5 deletions(-) diff --git a/client/components/ManageTestQueue/index.jsx b/client/components/ManageTestQueue/index.jsx index 63382bc0f..203979d3f 100644 --- a/client/components/ManageTestQueue/index.jsx +++ b/client/components/ManageTestQueue/index.jsx @@ -76,11 +76,12 @@ const DisclosureContainer = styled.div` } } - .disclosure-row-test-plans { + .disclosure-row-controls { display: grid; grid-auto-flow: column; grid-template-columns: 1fr 1fr 1fr 1fr; grid-gap: 1rem; + align-items: end; } .disclosure-form-label { @@ -106,6 +107,7 @@ const ManageTestQueue = ({ const [showManageATs, setShowManageATs] = useState(false); const [showAddTestPlans, setShowAddTestPlans] = useState(false); + const [showManageReqReports, setShowManageReqReports] = useState(false); const [selectedManageAtId, setSelectedManageAtId] = useState('1'); const [selectedManageAtVersions, setSelectedManageAtVersions] = useState( [] @@ -149,6 +151,8 @@ const ManageTestQueue = ({ const onManageAtsClick = () => setShowManageATs(!showManageATs); const onAddTestPlansClick = () => setShowAddTestPlans(!showAddTestPlans); + const onManageReqReportsClick = () => + setShowManageReqReports(!showManageReqReports); useEffect(() => { const allTestPlanVersions = testPlanVersions @@ -504,7 +508,8 @@ const ManageTestQueue = ({ componentId="manage-test-queue" title={[ 'Manage Assistive Technology Versions', - 'Add Test Plans to the Test Queue' + 'Add Test Plans to the Test Queue', + 'Manage Required Reports' ]} disclosureContainerView={[ @@ -586,7 +591,7 @@ const ManageTestQueue = ({ Select a Test Plan and version and an Assistive Technology and Browser to add it to the Test Queue -
+
Test Plan @@ -691,10 +696,93 @@ const ManageTestQueue = ({ > Add Test Plan to Test Queue + , + + New Section +
+ + + New Label + + { + // const { value } = e.target; + // updateMatchingTestPlanVersions( + // value, + // allTestPlanVersions + // ); + }} + > + + + + + + New Label + + { + // const { value } = e.target; + // updateMatchingTestPlanVersions( + // value, + // allTestPlanVersions + // ); + }} + > + + + + + + New Label + + { + // const { value } = e.target; + // updateMatchingTestPlanVersions( + // value, + // allTestPlanVersions + // ); + }} + > + + + + + + +
]} - onClick={[onManageAtsClick, onAddTestPlansClick]} - expanded={[showManageATs, showAddTestPlans]} + onClick={[ + onManageAtsClick, + onAddTestPlansClick, + onManageReqReportsClick + ]} + expanded={[ + showManageATs, + showAddTestPlans, + showManageReqReports + ]} stacked /> From d2599d5ac7d74d2bc38807f4657d27f1686dc8e2 Mon Sep 17 00:00:00 2001 From: Howard Edwards Date: Mon, 21 Aug 2023 16:32:41 -0500 Subject: [PATCH 02/59] Fix bug that would allow testPlanVersion to be updated to RECOMMENDED before the associated reports were all marked as final (but the tests were 100% done in the Test Queue) --- .../DataManagementRow/index.jsx | 4 +- .../updatePhaseResolver.js | 19 ++++---- server/tests/integration/test-queue.test.js | 44 +++++++++++++++++++ 3 files changed, 57 insertions(+), 10 deletions(-) diff --git a/client/components/DataManagement/DataManagementRow/index.jsx b/client/components/DataManagement/DataManagementRow/index.jsx index c19ffbb5c..27078d1b4 100644 --- a/client/components/DataManagement/DataManagementRow/index.jsx +++ b/client/components/DataManagement/DataManagementRow/index.jsx @@ -665,7 +665,6 @@ const DataManagementRow = ({ const markedFinalAt = testPlanReport.markedFinalAt; const atName = testPlanReport.at.name; const browserName = testPlanReport.browser.name; - const value = `${atName}_${browserName}`; if (markedFinalAt && !coveredReports.includes(value)) { @@ -869,11 +868,12 @@ const DataManagementRow = ({ let coveredReports = []; latestVersion.testPlanReports.forEach(testPlanReport => { + const markedFinalAt = testPlanReport.markedFinalAt; const atName = testPlanReport.at.name; const browserName = testPlanReport.browser.name; const value = `${atName}_${browserName}`; - if (!coveredReports.includes(value)) + if (markedFinalAt && !coveredReports.includes(value)) coveredReports.push(value); }); diff --git a/server/resolvers/TestPlanVersionOperations/updatePhaseResolver.js b/server/resolvers/TestPlanVersionOperations/updatePhaseResolver.js index 0ce3b99b9..8798314eb 100644 --- a/server/resolvers/TestPlanVersionOperations/updatePhaseResolver.js +++ b/server/resolvers/TestPlanVersionOperations/updatePhaseResolver.js @@ -264,14 +264,17 @@ const updatePhaseResolver = async ( if (phase === 'CANDIDATE' || phase === 'RECOMMENDED') { const reportsByAtAndBrowser = {}; - testPlanReports.forEach(testPlanReport => { - const { at, browser } = testPlanReport; - if (!reportsByAtAndBrowser[at.id]) { - reportsByAtAndBrowser[at.id] = {}; - } + testPlanReports + // Only check for reports which have been marked as final + .filter(testPlanReport => !!testPlanReport.markedFinalAt) + .forEach(testPlanReport => { + const { at, browser } = testPlanReport; + if (!reportsByAtAndBrowser[at.id]) { + reportsByAtAndBrowser[at.id] = {}; + } - reportsByAtAndBrowser[at.id][browser.id] = testPlanReport; - }); + reportsByAtAndBrowser[at.id][browser.id] = testPlanReport; + }); const ats = await context.atLoader.getAll(); @@ -294,7 +297,7 @@ const updatePhaseResolver = async ( if (missingAtBrowserCombinations.length) { throw new Error( `Cannot set phase to ${phase.toLowerCase()} because the following` + - ` required reports have not been collected:` + + ` required reports have not been collected or finalized:` + ` ${missingAtBrowserCombinations.join(', ')}.` ); } diff --git a/server/tests/integration/test-queue.test.js b/server/tests/integration/test-queue.test.js index b4ba64a38..115274ebd 100644 --- a/server/tests/integration/test-queue.test.js +++ b/server/tests/integration/test-queue.test.js @@ -247,6 +247,50 @@ describe('test queue', () => { } `); + // Check to see that the testPlanVersion cannot be updated until the reports have been + // finalized + await expect(() => { + return mutate(gql` + mutation { + testPlanVersion(id: ${testPlanVersionId}) { + updatePhase(phase: CANDIDATE) { + testPlanVersion { + phase + } + } + } + } + `); + }).rejects.toThrow( + 'Cannot set phase to candidate because the following required reports have' + + ' not been collected or finalized: JAWS and Chrome, NVDA and Chrome,' + + ' VoiceOver for macOS and Safari.' + ); + + const testPlanReportsToMarkAsFinalResult = await query(gql` + query { + testPlanReports(testPlanVersionId: ${testPlanVersionId}) { + id + } + } + `); + + for (const testPlanReport of testPlanReportsToMarkAsFinalResult.testPlanReports) { + await mutate(gql` + mutation { + testPlanReport(id: ${testPlanReport.id}) { + markAsFinal { + testPlanReport { + id + markedFinalAt + } + } + } + } + + `); + } + const candidateResult = await mutate(gql` mutation { testPlanVersion(id: ${testPlanVersionId}) { From b062bacf3136dcd57b60474375fd743f99e8ce84 Mon Sep 17 00:00:00 2001 From: Paul Clue <67766160+Paul-Clue@users.noreply.github.com> Date: Wed, 23 Aug 2023 15:27:24 -0500 Subject: [PATCH 03/59] Start interface in UI for required reports --- client/components/DataManagement/queries.js | 12 +++ client/components/ManageTestQueue/index.jsx | 85 +++++++++++++++++++-- client/components/TestQueue/queries.js | 12 +++ 3 files changed, 104 insertions(+), 5 deletions(-) diff --git a/client/components/DataManagement/queries.js b/client/components/DataManagement/queries.js index db2d32bb0..47f897a7b 100644 --- a/client/components/DataManagement/queries.js +++ b/client/components/DataManagement/queries.js @@ -15,6 +15,18 @@ export const DATA_MANAGEMENT_PAGE_QUERY = gql` name releasedAt } + browsers { + id + name + } + candidateBrowsers { + id + name + } + recommendedBrowsers { + id + name + } } browsers { id diff --git a/client/components/ManageTestQueue/index.jsx b/client/components/ManageTestQueue/index.jsx index 209da4c13..22346548f 100644 --- a/client/components/ManageTestQueue/index.jsx +++ b/client/components/ManageTestQueue/index.jsx @@ -18,6 +18,13 @@ import { convertStringToDate } from '../../utils/formatter'; import { LoadingStatus, useTriggerLoad } from '../common/LoadingStatus'; import DisclosureComponent from '../common/DisclosureComponent'; import AddTestToQueueWithConfirmation from '../AddTestToQueueWithConfirmation'; +import { ThemeTable, ThemeTableHeader } from '../common/ThemeTable'; +import PhasePill from '../common/PhasePill'; + +const TransparentButton = styled.button` + border: none; + background-color: transparent; +`; const DisclosureContainer = styled.div` // Following directives are related to the ManageTestQueue component @@ -143,6 +150,23 @@ const ManageTestQueue = ({ const [selectedAtId, setSelectedAtId] = useState(''); const [selectedBrowserId, setSelectedBrowserId] = useState(''); + const atBrowserCombinations = [ + ...ats.flatMap(at => + at.candidateBrowsers.map(browser => ({ + at, + browser, + phase: 'CANDIDATE' + })) + ), + ...ats.flatMap(at => + at.recommendedBrowsers.map(browser => ({ + at, + browser, + phase: 'RECOMMENDED' + })) + ) + ]; + const [addAtVersion] = useMutation(ADD_AT_VERSION_MUTATION); const [editAtVersion] = useMutation(EDIT_AT_VERSION_MUTATION); const [deleteAtVersion] = useMutation(DELETE_AT_VERSION_MUTATION); @@ -667,11 +691,14 @@ const ManageTestQueue = ({ - New Section + + Add required reports for a specific AT and Browser + pair +
- New Label + Phase { @@ -689,7 +716,7 @@ const ManageTestQueue = ({ - New Label + Assistive Technology { @@ -707,7 +734,7 @@ const ManageTestQueue = ({ - New Label + Browser { @@ -734,10 +761,58 @@ const ManageTestQueue = ({ // } // onClick={handleAddTestPlanToTestQueue} > - New Button Label + Add Required Reports
+ Required Reports + + + + Phase + AT + Browser + Edit + + + + {atBrowserCombinations.map( + ({ at, browser, phase }) => { + return ( + + + + {phase} + {' '} + + {at.name} + {browser.name} + + + + Edit + + + + Remove + + + + ); + } + )} + +
]} onClick={[ diff --git a/client/components/TestQueue/queries.js b/client/components/TestQueue/queries.js index c1f565c80..3605233ca 100644 --- a/client/components/TestQueue/queries.js +++ b/client/components/TestQueue/queries.js @@ -20,6 +20,18 @@ export const TEST_QUEUE_PAGE_QUERY = gql` name releasedAt } + browsers { + id + name + } + candidateBrowsers { + id + name + } + recommendedBrowsers { + id + name + } } browsers { id From cc1ec2d279c2cb43b2e943919b7dd3d63894a795 Mon Sep 17 00:00:00 2001 From: Alexander Flenniken Date: Thu, 24 Aug 2023 16:28:00 -0400 Subject: [PATCH 04/59] Refine raise an issue behavior (#753) * Refine raise an issue behavior * Address feedback * Fixed squished dot icon * Address last feedback * Hide closed issues on datamgmt page --- .../CandidateTestPlanRun/index.jsx | 198 +++++++----------- .../CandidateTestPlanRun/queries.js | 1 + .../CandidateReview/TestPlans/index.jsx | 7 +- client/components/CandidateReview/queries.js | 1 + .../DataManagementRow/index.jsx | 56 ++--- client/components/DataManagement/index.jsx | 4 +- .../Reports/SummarizeTestPlanReport.jsx | 27 ++- client/components/Reports/queries.js | 6 + .../TestPlanReportStatusDialog/index.jsx | 2 +- .../components/TestPlanVersionsPage/index.jsx | 104 +++++++-- .../TestPlanVersionsPage/queries.js | 4 + client/components/TestRun/index.jsx | 91 ++------ client/components/TestRun/queries.js | 1 + client/components/common/ThemeTable/index.jsx | 3 +- client/package.json | 8 +- client/utils/createIssueLink.js | 140 +++++++++++++ server/graphql-schema.js | 21 +- .../TestPlanReport/issuesResolver.js | 70 ++++--- server/services/GithubService.js | 141 +++++-------- server/util/convertDateToString.js | 8 + yarn.lock | 24 --- 21 files changed, 527 insertions(+), 390 deletions(-) create mode 100644 client/utils/createIssueLink.js create mode 100644 server/util/convertDateToString.js diff --git a/client/components/CandidateReview/CandidateTestPlanRun/index.jsx b/client/components/CandidateReview/CandidateTestPlanRun/index.jsx index 296ab393e..f3f3099de 100644 --- a/client/components/CandidateReview/CandidateTestPlanRun/index.jsx +++ b/client/components/CandidateReview/CandidateTestPlanRun/index.jsx @@ -20,7 +20,6 @@ import { Helmet } from 'react-helmet'; import './CandidateTestPlanRun.css'; import '../../TestRun/TestRun.css'; import '../../App/App.css'; -import useResizeObserver from '@react-hook/resize-observer'; import { useMediaQuery } from 'react-responsive'; import { convertDateToString } from '../../../utils/formatter'; import TestPlanResultsTable from '../../Reports/TestPlanResultsTable'; @@ -29,19 +28,9 @@ import ThankYouModal from '../CandidateModals/ThankYouModal'; import getMetrics from '../../Reports/getMetrics'; import FeedbackListItem from '../FeedbackListItem'; import DisclosureComponent from '../../common/DisclosureComponent'; - -// https://codesandbox.io/s/react-hookresize-observer-example-ft88x -function useSize(target) { - const [size, setSize] = React.useState(); - - React.useLayoutEffect(() => { - target && setSize(target.getBoundingClientRect()); - }, [target]); - - // Where the magic happens - useResizeObserver(target, entry => setSize(entry.contentRect)); - return size; -} +import createIssueLink, { + getIssueSearchLink +} from '../../../utils/createIssueLink'; const CandidateTestPlanRun = () => { const { atId, testPlanVersionId } = useParams(); @@ -80,10 +69,6 @@ const CandidateTestPlanRun = () => { const [showBrowserBools, setShowBrowserBools] = useState([]); const [showBrowserClicks, setShowBrowserClicks] = useState([]); - const [issuesHeading, setIssuesHeading] = React.useState(); - const issuesHeadingSize = useSize(issuesHeading); - const [issuesList, setIssuesList] = React.useState(); - const issuesListSize = useSize(issuesList); const isLaptopOrLarger = useMediaQuery({ query: '(min-width: 792px)' }); @@ -296,6 +281,11 @@ const CandidateTestPlanRun = () => { const { testPlanVersion, vendorReviewStatus } = testPlanReport; const { recommendedPhaseTargetDate } = testPlanVersion; + const versionString = `V${convertDateToString( + testPlanVersion.updatedAt, + 'YY.MM.DD' + )}`; + const vendorReviewStatusMap = { READY: 'Ready', IN_PROGRESS: 'In Progress', @@ -322,54 +312,66 @@ const CandidateTestPlanRun = () => { issue.testNumberFilteredByAt === currentTest.seq ); + const issue = { + isCandidateReview: true, + isCandidateReviewChangesRequested: true, + testPlanTitle: testPlanVersion.title, + versionString, + testTitle: currentTest.title, + testRowNumber: currentTest.rowNumber, + testRenderedUrl: currentTest.renderedUrl, + atName: testPlanReport.at.name + }; + + const requestChangesUrl = createIssueLink(issue); + + const feedbackUrl = createIssueLink({ + ...issue, + isCandidateReviewChangesRequested: false + }); + + const generalFeedbackUrl = createIssueLink({ + ...issue, + isCandidateReviewChangesRequested: false, + testTitle: undefined, + testRowNumber: undefined, + testRenderedUrl: undefined + }); + + const issueQuery = { + isCandidateReview: true, + isCandidateReviewChangesRequested: false, + testPlanTitle: testPlanVersion.title, + versionString, + testRowNumber: currentTest.rowNumber, + username: data.me.username, + atName: testPlanReport.at.name + }; + + const feedbackGithubUrl = getIssueSearchLink(issueQuery); + + const changesRequestedGithubUrl = getIssueSearchLink({ + ...issueQuery, + isCandidateReviewChangesRequested: true + }); + + let fileBugUrl; + const githubAtLabelMap = { 'VoiceOver for macOS': 'vo', JAWS: 'jaws', NVDA: 'nvda' }; - const generateGithubUrl = ( - test = false, - type = '', - titleAddition = '', - search = false, - author = '' - ) => { - const testPlanVersionDate = convertDateToString( - new Date(testPlanVersion.updatedAt), - 'DD-MM-YYYY' - ); - - const generateGithubTitle = () => { - return `${at} Feedback: "${currentTest.title}" (${ - testPlanVersion.title - }${ - test ? `, Test ${currentTest.seq}` : '' - }, ${testPlanVersionDate})${ - titleAddition ? ` - ${titleAddition}` : '' - }`; - }; - - const githubIssueUrlTitle = generateGithubTitle(); - const defaultGithubLabels = 'app,candidate-review'; - let githubUrl; - - if (!search) { - githubUrl = `https://github.com/w3c/aria-at/issues/new?title=${encodeURI( - githubIssueUrlTitle - )}&labels=${defaultGithubLabels},${githubAtLabelMap[at]}`; - return `${githubUrl},${type}`; - } else { - let title = generateGithubTitle(); - let query = encodeURI( - `label:app label:candidate-review label:${type} label:${ - githubAtLabelMap[at] - } ${author ? `author:${author}` : ''} ${title}` - ); - githubUrl = `https://github.com/w3c/aria-at/issues?q=${query}`; - return githubUrl; - } - }; + if (githubAtLabelMap[at] == 'vo') { + fileBugUrl = + 'https://bugs.webkit.org/buglist.cgi?quicksearch=voiceover'; + } else if (githubAtLabelMap[at] == 'nvda') { + fileBugUrl = 'https://github.com/nvaccess/nvda/issues'; + } else { + fileBugUrl = + 'https://github.com/FreedomScientific/VFO-standards-support/issues'; + } const heading = (
@@ -429,24 +431,11 @@ const CandidateTestPlanRun = () => { issue => issue.testNumberFilteredByAt == currentTest.seq ).length > 0 && (
-

+

Feedback from{' '} {at} Representative

-
    +
      {[changesRequestedIssues, feedbackIssues].map((list, index) => { if (list.length > 0) { const uniqueAuthors = [ @@ -467,15 +456,15 @@ const CandidateTestPlanRun = () => { } issues={list} individualTest={true} - githubUrl={generateGithubUrl( - true, - index === 0 - ? 'changes-requested' - : 'feedback', - null, - true, - !differentAuthors ? data.me.username : null - )} + githubUrl={getIssueSearchLink({ + isCandidateReview: true, + isCandidateReviewChangesRequested: + index === 0, + atName: testPlanReport.at.name, + testPlanTitle: testPlanVersion.title, + versionString, + testRowNumber: currentTest.rowNumber + })} /> ); } @@ -540,20 +529,6 @@ const CandidateTestPlanRun = () => {
); - const requestChangesUrl = generateGithubUrl(true, 'changes-requested'); - const feedbackUrl = generateGithubUrl(true, 'feedback'); - let fileBugUrl; - - if (githubAtLabelMap[at] == 'vo') { - fileBugUrl = - 'https://bugs.webkit.org/buglist.cgi?quicksearch=voiceover'; - } else if (githubAtLabelMap[at] == 'nvda') { - fileBugUrl = 'https://github.com/nvaccess/nvda/issues'; - } else { - fileBugUrl = - 'https://github.com/FreedomScientific/VFO-standards-support/issues'; - } - return ( @@ -580,11 +555,10 @@ const CandidateTestPlanRun = () => { {heading} {testInfo} - + {feedback} @@ -691,28 +665,18 @@ const CandidateTestPlanRun = () => { testPlan={testPlanVersion.title} feedbackIssues={testPlanReport.issues?.filter( issue => + issue.isCandidateReview === true && issue.feedbackType === 'FEEDBACK' && issue.author == data.me.username )} - feedbackGithubUrl={generateGithubUrl( - false, - 'feedback', - null, - true, - data.me.username - )} + feedbackGithubUrl={feedbackGithubUrl} changesRequestedIssues={testPlanReport.issues?.filter( issue => + issue.isCandidateReview === true && issue.feedbackType === 'CHANGES_REQUESTED' && issue.author == data.me.username )} - changesRequestedGithubUrl={generateGithubUrl( - false, - 'changes-requested', - null, - true, - data.me.username - )} + changesRequestedGithubUrl={changesRequestedGithubUrl} handleAction={submitApproval} handleHide={() => setFeedbackModalShowing(false)} /> @@ -726,11 +690,7 @@ const CandidateTestPlanRun = () => { setThankYouModalShowing(false); navigate('/candidate-review'); }} - githubUrl={generateGithubUrl( - false, - 'feedback', - 'General Feedback' - )} + githubUrl={generalFeedbackUrl} /> ) : ( <> diff --git a/client/components/CandidateReview/CandidateTestPlanRun/queries.js b/client/components/CandidateReview/CandidateTestPlanRun/queries.js index 3320723f4..c61e1e3d7 100644 --- a/client/components/CandidateReview/CandidateTestPlanRun/queries.js +++ b/client/components/CandidateReview/CandidateTestPlanRun/queries.js @@ -79,6 +79,7 @@ export const CANDIDATE_REPORTS_QUERY = gql` runnableTests { id title + rowNumber renderedUrl renderableContent viewers { diff --git a/client/components/CandidateReview/TestPlans/index.jsx b/client/components/CandidateReview/TestPlans/index.jsx index 980638704..9b02c1432 100644 --- a/client/components/CandidateReview/TestPlans/index.jsx +++ b/client/components/CandidateReview/TestPlans/index.jsx @@ -451,7 +451,9 @@ const TestPlans = ({ testPlanVersions }) => { .sort((a, b) => (a.title < b.title ? -1 : 1)) .map(testPlanVersion => { const testPlanReports = - testPlanVersion.testPlanReports; + testPlanVersion.testPlanReports.filter( + ({ at }) => at.id === atId + ); const candidatePhaseReachedAt = testPlanVersion.candidatePhaseReachedAt; const recommendedPhaseTargetDate = @@ -535,6 +537,9 @@ const TestPlans = ({ testPlanVersions }) => { ...testPlanReport.issues ]) .flat() + .filter( + t => t.isCandidateReview === true + ) .filter(t => uniqueFilter(t, uniqueLinks, 'link') ); diff --git a/client/components/CandidateReview/queries.js b/client/components/CandidateReview/queries.js index cb167bcc9..02c82e7b2 100644 --- a/client/components/CandidateReview/queries.js +++ b/client/components/CandidateReview/queries.js @@ -44,6 +44,7 @@ export const CANDIDATE_REVIEW_PAGE_QUERY = gql` issues { link isOpen + isCandidateReview feedbackType } } diff --git a/client/components/DataManagement/DataManagementRow/index.jsx b/client/components/DataManagement/DataManagementRow/index.jsx index 27078d1b4..41d5c43f1 100644 --- a/client/components/DataManagement/DataManagementRow/index.jsx +++ b/client/components/DataManagement/DataManagementRow/index.jsx @@ -21,6 +21,7 @@ import ReportStatusDot from '../../common/ReportStatusDot'; import UpdateTargetDateModal from '@components/common/UpdateTargetDateModal'; import VersionString from '../../common/VersionString'; import PhasePill from '../../common/PhasePill'; +import { uniq as unique, uniqBy as uniqueBy } from 'lodash'; const StatusCell = styled.div` display: flex; @@ -90,7 +91,7 @@ const PhaseCell = styled.div` justify-content: center; align-items: center; - padding: 4px; + padding: 0.5rem; font-size: 14px; margin-top: 6px; @@ -99,8 +100,12 @@ const PhaseCell = styled.div` background: #f6f8fa; > span.more-issues-container { - display: flex; - flex-direction: row; + width: 100%; + text-align: center; + + .issues { + margin-right: 4px; + } align-items: center; } @@ -227,17 +232,6 @@ const DataManagementRow = ({ }; }; - const getUniqueAtObjects = testPlanReports => { - const uniqueAtObjects = {}; - testPlanReports.forEach(testPlanReport => { - const atId = testPlanReport.at.id; - if (!uniqueAtObjects[atId]) { - uniqueAtObjects[atId] = testPlanReport.at; - } - }); - return uniqueAtObjects; - }; - const handleClickUpdateTestPlanVersionPhase = async ( testPlanVersionId, phase, @@ -807,17 +801,29 @@ const DataManagementRow = ({ // sunset that version. This will also sunset any reports completed using that // version. if (testPlanVersions.length) { - const filteredTestPlanReports = - latestVersion.testPlanReports; - const uniqueAtObjects = getUniqueAtObjects( - filteredTestPlanReports - ); - const uniqueAtsCount = Object.keys(uniqueAtObjects).length; - - const issuesCount = filteredTestPlanReports.reduce( - (acc, obj) => acc + obj.issues.length, - 0 - ); + const uniqueAtsCount = unique( + testPlanVersions + .flatMap( + testPlanVersion => + testPlanVersion.testPlanReports + ) + .filter( + testPlanReport => testPlanReport.issues.length + ) + .map(testPlanReport => testPlanReport.at.id) + ).length; + + const issuesCount = uniqueBy( + testPlanVersions.flatMap(testPlanVersion => + testPlanVersion.testPlanReports.flatMap( + testPlanReport => + testPlanReport.issues.filter( + issue => issue.isOpen + ) + ) + ), + item => item.link + ).length; // If there is an earlier version that is recommended and that version has some // test plan runs in the test queue, this button will run the process for diff --git a/client/components/DataManagement/index.jsx b/client/components/DataManagement/index.jsx index b65b268b4..a020051aa 100644 --- a/client/components/DataManagement/index.jsx +++ b/client/components/DataManagement/index.jsx @@ -12,9 +12,7 @@ import { evaluateAuth } from '@client/utils/evaluateAuth'; const DataManagement = () => { const { loading, data, error, refetch } = useQuery( DATA_MANAGEMENT_PAGE_QUERY, - { - fetchPolicy: 'cache-and-network' - } + { fetchPolicy: 'cache-and-network' } ); const [pageReady, setPageReady] = useState(false); diff --git a/client/components/Reports/SummarizeTestPlanReport.jsx b/client/components/Reports/SummarizeTestPlanReport.jsx index 4a04bcc87..0663e0994 100644 --- a/client/components/Reports/SummarizeTestPlanReport.jsx +++ b/client/components/Reports/SummarizeTestPlanReport.jsx @@ -1,7 +1,6 @@ import React, { Fragment } from 'react'; import PropTypes from 'prop-types'; import { Helmet } from 'react-helmet'; -import { createGitHubIssueWithTitleAndBody } from '../TestRun'; import { getTestPlanTargetTitle, getTestPlanVersionTitle } from './getTitles'; import { Breadcrumb, Button, Container } from 'react-bootstrap'; import { LinkContainer } from 'react-router-bootstrap'; @@ -17,6 +16,7 @@ import DisclaimerInfo from '../DisclaimerInfo'; import TestPlanResultsTable from './TestPlanResultsTable'; import DisclosureComponent from '../common/DisclosureComponent'; import { Navigate, useLocation, useParams } from 'react-router-dom'; +import createIssueLink from '../../utils/createIssueLink'; const getTestersRunHistory = ( testPlanReport, @@ -141,13 +141,22 @@ const SummarizeTestPlanReport = ({ testPlanVersion, testPlanReports }) => { {testPlanReport.finalizedTestResults.map(testResult => { const test = testResult.test; - const fromReportPageLink = `https://aria-at.w3.org${location.pathname}#result-${testResult.id}`; - const gitHubIssueLinkWithTitleAndBody = - createGitHubIssueWithTitleAndBody({ - test, - testPlanReport, - fromReportPageLink - }); + const reportLink = `https://aria-at.w3.org${location.pathname}#result-${testResult.id}`; + const issueLink = createIssueLink({ + testPlanTitle: testPlanVersion.title, + versionString: `V${convertDateToString( + testPlanVersion.updatedAt, + 'YY.MM.DD' + )}`, + testTitle: test.title, + testRowNumber: test.rowNumber, + testRenderedUrl: test.renderedUrl, + atName: testPlanReport.at.name, + atVersionName: testResult.atVersion.name, + browserName: testPlanReport.browser.name, + browserVersionName: testResult.browserVersion.name, + reportLink + }); // TODO: fix renderedUrl let modifiedRenderedUrl = test.renderedUrl.replace( @@ -169,7 +178,7 @@ const SummarizeTestPlanReport = ({ testPlanVersion, testPlanReports }) => { ) : ( <> + Target  {Math.abs(timeToTargetDate)}{' '} Days +   + {timeToTargetDate < 0 + ? 'Past' + : 'Away'} - )}{' '} - {timeToTargetDate < 0 ? 'Past' : 'Away'} + )} @@ -972,15 +1002,16 @@ const DataManagementRow = ({ // Phase is "active" insertActivePhaseForTestPlan(latestVersion); return ( - + - - Approved{' '} + + Approved  {convertDateToString( completionDate, @@ -1025,8 +1056,8 @@ const DataManagementRow = ({ title={`Advancing test plan, ${testPlan.title}, V${advanceModalData.version}`} content={ <> - This version will be updated to{' '} - {advanceModalData.phase}.{' '} + This version will be updated to  + {advanceModalData.phase}.  {advanceModalData.coveredReports?.length ? ( <>
@@ -1043,7 +1074,7 @@ const DataManagementRow = ({ key={`${testPlan.id}${atName}${browserName}`} > - {atName} and{' '} + {atName} and  {browserName} diff --git a/client/components/common/VersionString/index.js b/client/components/common/VersionString/index.js index 761ab8342..45a4f9601 100644 --- a/client/components/common/VersionString/index.js +++ b/client/components/common/VersionString/index.js @@ -39,13 +39,16 @@ const VersionString = ({ autoWidth = true, iconColor = '#818F98', linkRef, - linkHref + linkHref, + ...restProps }) => { + const dateString = convertDateToString(date, 'YY.MM.DD'); + const body = ( - <> + - V{convertDateToString(date, 'YY.MM.DD')} - + {'V' + dateString} + ); let possibleLink; @@ -75,7 +78,11 @@ const VersionString = ({ let classes = fullWidth ? 'full-width' : ''; classes = autoWidth ? `${classes} auto-width` : classes; - return {possibleLink}; + return ( + + {possibleLink} + + ); }; VersionString.propTypes = { From 452f9bb19df6448770292eac48fa3822afd88741 Mon Sep 17 00:00:00 2001 From: Howard Edwards Date: Tue, 29 Aug 2023 17:06:58 -0500 Subject: [PATCH 12/59] Adjust BasicModal to support AtAndBrowserDetailsModal closing --- .../components/common/AtAndBrowserDetailsModal/index.jsx | 1 + client/components/common/BasicModal/index.jsx | 9 ++++++--- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/client/components/common/AtAndBrowserDetailsModal/index.jsx b/client/components/common/AtAndBrowserDetailsModal/index.jsx index 59fce4708..b5785637d 100644 --- a/client/components/common/AtAndBrowserDetailsModal/index.jsx +++ b/client/components/common/AtAndBrowserDetailsModal/index.jsx @@ -601,6 +601,7 @@ const AtAndBrowserDetailsModal = ({ handleClose={!isFirstLoad ? handleClose : null} handleHide={handleHide} staticBackdrop={true} + useOnHide={true} /> )} diff --git a/client/components/common/BasicModal/index.jsx b/client/components/common/BasicModal/index.jsx index 97b179c9b..8331d6b82 100644 --- a/client/components/common/BasicModal/index.jsx +++ b/client/components/common/BasicModal/index.jsx @@ -26,7 +26,8 @@ const BasicModal = ({ handleClose = null, handleAction = null, handleHide = null, - staticBackdrop = false + staticBackdrop = false, + useOnHide = false }) => { const headerRef = useRef(); @@ -41,7 +42,8 @@ const BasicModal = ({ show={show} centered={centered} animation={animation} - onExit={handleHide || handleClose} + onHide={useOnHide ? handleHide || handleClose : null} + onExit={!useOnHide ? handleHide || handleClose : null} /* Disabled due to buggy implementation which jumps the page */ autoFocus={false} aria-labelledby="basic-modal" @@ -101,7 +103,8 @@ BasicModal.propTypes = { handleClose: PropTypes.func, handleAction: PropTypes.func, handleHide: PropTypes.func, - staticBackdrop: PropTypes.bool + staticBackdrop: PropTypes.bool, + useOnHide: PropTypes.bool }; export default BasicModal; From a3b8dbe68ae058905ab7f978db2c5e1d79ae8d07 Mon Sep 17 00:00:00 2001 From: Howard Edwards Date: Tue, 29 Aug 2023 17:13:31 -0500 Subject: [PATCH 13/59] Stop assign menu dropdown from creating unintended bottom space with the parent container --- client/components/TestQueue/index.jsx | 1 - client/components/TestQueueRow/TestQueueRow.css | 5 +++++ client/components/TestQueueRow/index.jsx | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/client/components/TestQueue/index.jsx b/client/components/TestQueue/index.jsx index 2d0995f2a..2fb8ef344 100644 --- a/client/components/TestQueue/index.jsx +++ b/client/components/TestQueue/index.jsx @@ -101,7 +101,6 @@ const TestQueue = () => { className="test-queue" aria-labelledby={tableId} bordered - responsive > diff --git a/client/components/TestQueueRow/TestQueueRow.css b/client/components/TestQueueRow/TestQueueRow.css index 4035df1db..0cb3850d5 100644 --- a/client/components/TestQueueRow/TestQueueRow.css +++ b/client/components/TestQueueRow/TestQueueRow.css @@ -110,3 +110,8 @@ button.more-actions:active { /* tr.test-queue-run-row td:first-child { padding: 0.75rem; } */ + +[role="menu"].assign-menu { + height: 200px; + overflow-y: scroll; +} diff --git a/client/components/TestQueueRow/index.jsx b/client/components/TestQueueRow/index.jsx index 3e28a8f37..52bed7b8d 100644 --- a/client/components/TestQueueRow/index.jsx +++ b/client/components/TestQueueRow/index.jsx @@ -240,7 +240,7 @@ const TestQueueRow = ({ > - + {testers.length ? ( testers.map(({ username }) => { const isTesterAssigned = From 112d9487ef5fb0a19b58e5c4e4335b5ae8fbcbf0 Mon Sep 17 00:00:00 2001 From: Howard Edwards Date: Tue, 29 Aug 2023 17:17:39 -0500 Subject: [PATCH 14/59] Formatting --- client/components/TestQueueRow/TestQueueRow.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/components/TestQueueRow/TestQueueRow.css b/client/components/TestQueueRow/TestQueueRow.css index 0cb3850d5..97fbfdeab 100644 --- a/client/components/TestQueueRow/TestQueueRow.css +++ b/client/components/TestQueueRow/TestQueueRow.css @@ -111,7 +111,7 @@ button.more-actions:active { padding: 0.75rem; } */ -[role="menu"].assign-menu { +[role='menu'].assign-menu { height: 200px; overflow-y: scroll; } From fffdb62c696bebfe48d3913cf8f03f30dedd9970 Mon Sep 17 00:00:00 2001 From: Howard Edwards Date: Tue, 29 Aug 2023 20:54:00 -0500 Subject: [PATCH 15/59] Close #755 --- client/components/TestQueueRow/index.jsx | 41 ++++++++++++------------ 1 file changed, 21 insertions(+), 20 deletions(-) diff --git a/client/components/TestQueueRow/index.jsx b/client/components/TestQueueRow/index.jsx index 52bed7b8d..3acca1c9d 100644 --- a/client/components/TestQueueRow/index.jsx +++ b/client/components/TestQueueRow/index.jsx @@ -401,7 +401,6 @@ const TestQueueRow = ({ const evaluateLabelStatus = () => { const { conflictsLength } = testPlanReport; - const { phase } = testPlanVersion; let labelStatus; @@ -418,7 +417,7 @@ const TestQueueRow = ({ {pluralizedStatus}
); - } else if (phase === 'DRAFT' || !phase) { + } else { labelStatus = ( Draft ); @@ -427,8 +426,6 @@ const TestQueueRow = ({ return labelStatus; }; - const labelStatus = evaluateLabelStatus(); - const getRowId = tester => [ 'plan', @@ -510,24 +507,28 @@ const TestQueueRow = ({
-
{labelStatus}
+
+ {evaluateLabelStatus()} +
{isSignedIn && isTester && (
- {isAdmin && !isLoading && ( - <> - - - )} + {isAdmin && + !isLoading && + !testPlanReport.conflictsLength && ( + <> + + + )}
)} From b3366b3acbcd32772c487f1d3086f3f61ce76d9c Mon Sep 17 00:00:00 2001 From: Howard Edwards Date: Tue, 29 Aug 2023 20:54:24 -0500 Subject: [PATCH 16/59] Close #754 --- .../CandidatePhaseSelectModal.jsx | 104 ------------------ client/components/TestQueueRow/index.jsx | 51 +++------ 2 files changed, 17 insertions(+), 138 deletions(-) delete mode 100644 client/components/TestQueueRow/CandidatePhaseSelectModal.jsx diff --git a/client/components/TestQueueRow/CandidatePhaseSelectModal.jsx b/client/components/TestQueueRow/CandidatePhaseSelectModal.jsx deleted file mode 100644 index f716bce50..000000000 --- a/client/components/TestQueueRow/CandidatePhaseSelectModal.jsx +++ /dev/null @@ -1,104 +0,0 @@ -import React, { useState } from 'react'; -import PropTypes from 'prop-types'; -import { Form } from 'react-bootstrap'; -import styled from '@emotion/styled'; -import BasicModal from '../common/BasicModal'; -import { convertDateToString } from '../../utils/formatter'; -import FormCheck from 'react-bootstrap/FormCheck'; - -const ModalInnerSectionContainer = styled.div` - display: flex; - flex-direction: column; -`; - -const CandidatePhaseSelectModal = ({ - show = false, - title = null, - dates = [], - handleAction = () => {}, - handleClose = () => {} -}) => { - const [selectedDateIndex, setSelectedDateIndex] = useState('0'); - - const handleChange = e => { - const { value } = e.target; - setSelectedDateIndex(value); - }; - - const onSubmit = () => { - if (selectedDateIndex == -1) handleAction(null); - const date = dates[selectedDateIndex]; - handleAction(date); - }; - - return ( - - - {dates.map((d, index) => { - return ( - - - - Candidate Phase Start Date on{' '} - - {convertDateToString( - d.candidatePhaseReachedAt, - 'MMMM D, YYYY' - )} - {' '} - and Recommended Phase Target Completion - Date on{' '} - - {convertDateToString( - d.recommendedPhaseTargetDate, - 'MMMM D, YYYY' - )} - - - - ); - })} - - - - Create new Candidate Phase starting today - - - - - } - actionLabel={'Select Candidate Phase'} - handleAction={onSubmit} - handleClose={handleClose} - /> - ); -}; - -CandidatePhaseSelectModal.propTypes = { - show: PropTypes.bool, - title: PropTypes.node.isRequired, - dates: PropTypes.array, - handleAction: PropTypes.func, - handleClose: PropTypes.func -}; - -export default CandidatePhaseSelectModal; diff --git a/client/components/TestQueueRow/index.jsx b/client/components/TestQueueRow/index.jsx index 3acca1c9d..ed88d6906 100644 --- a/client/components/TestQueueRow/index.jsx +++ b/client/components/TestQueueRow/index.jsx @@ -19,17 +19,16 @@ import { REMOVE_TESTER_MUTATION, REMOVE_TESTER_RESULTS_MUTATION } from '../TestQueue/queries'; -import { gitUpdatedDateToString } from '../../utils/gitUtils'; import TestPlanUpdaterModal from '../TestPlanUpdater/TestPlanUpdaterModal'; import BasicThemedModal from '../common/BasicThemedModal'; import { LoadingStatus, useTriggerLoad } from '../common/LoadingStatus'; +import { convertDateToString } from '../../utils/formatter'; import './TestQueueRow.css'; const TestQueueRow = ({ user = {}, testers = [], testPlanReportData = {}, - latestTestPlanVersions = [], triggerDeleteTestPlanReportModal = () => {}, triggerDeleteResultsModal = () => {}, triggerPageUpdate = () => {} @@ -171,26 +170,22 @@ const TestQueueRow = ({ }; const renderAssignedUserToTestPlan = () => { - const gitUpdatedDateString = ( -

- Published {gitUpdatedDateToString(testPlanVersion.updatedAt)} -

+ const dateString = convertDateToString( + testPlanVersion.updatedAt, + 'YY.MM.DD' ); - const latestTestPlanVersion = latestTestPlanVersions.filter( - version => version.latestTestPlanVersion?.id === testPlanVersion.id + const titleElement = ( + <> + {testPlanVersion.title} {'V' + dateString} +  ({runnableTestsLength} Test + {runnableTestsLength === 0 || runnableTestsLength > 1 + ? `s` + : ''} + ) + ); - const updateTestPlanVersionButton = isAdmin && - latestTestPlanVersion.length === 0 && ( - - ); + // Determine if current user is assigned to testPlan if (currentUserAssigned) return ( @@ -199,11 +194,8 @@ const TestQueueRow = ({ className="test-plan" to={`/run/${currentUserTestPlanRun.id}`} > - {testPlanVersion.title || - `"${testPlanVersion.testPlan.directory}"`} + {titleElement} - {gitUpdatedDateString} - {updateTestPlanVersionButton} ); @@ -211,21 +203,12 @@ const TestQueueRow = ({ return ( <> - {testPlanVersion.title || - `"${testPlanVersion.testPlan.directory}"`} + {titleElement} - {gitUpdatedDateString} ); - return ( -
- {testPlanVersion.title || - `"${testPlanVersion.testPlan.directory}"`} - {gitUpdatedDateString} - {updateTestPlanVersionButton} -
- ); + return
{titleElement}
; }; const renderAssignMenu = () => { From 66267cc5d0004fb9bb2c135e6a58975d779438a5 Mon Sep 17 00:00:00 2001 From: Stalgia Grigg Date: Wed, 30 Aug 2023 17:00:32 -0400 Subject: [PATCH 17/59] Remove superfluous header from TestPlanReportStatusDialog (#766) --- client/components/TestPlanReportStatusDialog/index.jsx | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/client/components/TestPlanReportStatusDialog/index.jsx b/client/components/TestPlanReportStatusDialog/index.jsx index 60483fe6d..b48d27fd9 100644 --- a/client/components/TestPlanReportStatusDialog/index.jsx +++ b/client/components/TestPlanReportStatusDialog/index.jsx @@ -10,7 +10,7 @@ import { evaluateAuth } from '../../utils/evaluateAuth'; import getMetrics from '../Reports/getMetrics'; import { calculateTestPlanReportCompletionPercentage } from './calculateTestPlanReportCompletionPercentage'; import { convertDateToString } from '../../utils/formatter'; -import { ThemeTable, ThemeTableHeader } from '../common/ThemeTable'; +import { ThemeTable } from '../common/ThemeTable'; const TestPlanReportStatusModal = styled(Modal)` .modal-dialog { @@ -207,9 +207,6 @@ const TestPlanReportStatusDialog = ({

)} - - Reports for Draft Alert Test Plan - From 15f22da8ada14a6af7673d5bf4eca6ec5b154fc8 Mon Sep 17 00:00:00 2001 From: Howard Edwards Date: Wed, 30 Aug 2023 16:02:47 -0500 Subject: [PATCH 18/59] Revise required reports conditions (#764) * Revise required reports approach * Update tests * Revert dev.env --- .../DataManagementRow/index.jsx | 5 + .../TestPlanReportStatusDialog/WithButton.jsx | 6 +- .../TestPlanReportStatusDialog/isRequired.js | 14 + .../DataManagementPagePopulatedMock.js | 351 ++++++++++++++++++ .../updatePhaseResolver.js | 2 +- 5 files changed, 375 insertions(+), 3 deletions(-) diff --git a/client/components/DataManagement/DataManagementRow/index.jsx b/client/components/DataManagement/DataManagementRow/index.jsx index eafbe1213..2c081754d 100644 --- a/client/components/DataManagement/DataManagementRow/index.jsx +++ b/client/components/DataManagement/DataManagementRow/index.jsx @@ -1010,6 +1010,11 @@ const DataManagementRow = ({ linkRef={recommendedVersionStringRef} linkHref={`/test-review/${latestVersion.gitSha}/${latestVersion.testPlan.directory}`} /> + + + Approved  diff --git a/client/components/TestPlanReportStatusDialog/WithButton.jsx b/client/components/TestPlanReportStatusDialog/WithButton.jsx index 7873e5417..b7ee923ad 100644 --- a/client/components/TestPlanReportStatusDialog/WithButton.jsx +++ b/client/components/TestPlanReportStatusDialog/WithButton.jsx @@ -41,6 +41,7 @@ const TestPlanReportStatusDialogWithButton = ({ testPlanVersionId }) => { const [showDialog, setShowDialog] = useState(false); const { testPlanReports } = testPlanVersion ?? {}; + // TODO: Use the DB provided AtBrowsers combinations when doing the edit UI task const requiredReports = useMemo( () => getRequiredReports(testPlanVersion?.phase), [testPlanVersion?.phase] @@ -58,7 +59,7 @@ const TestPlanReportStatusDialogWithButton = ({ testPlanVersionId }) => { if (matchingReport) { const percentComplete = calculateTestPlanReportCompletionPercentage(matchingReport); - if (percentComplete === 100) { + if (percentComplete === 100 && matchingReport.markedFinalAt) { acc.completed++; } else { acc.inProgress++; @@ -126,7 +127,8 @@ const TestPlanReportStatusDialogWithButton = ({ testPlanVersionId }) => { !testPlanVersion || !testPlanVersion.phase || (testPlanVersion.phase !== 'DRAFT' && - testPlanVersion.phase !== 'CANDIDATE') + testPlanVersion.phase !== 'CANDIDATE' && + testPlanVersion.phase !== 'RECOMMENDED') ) { return; } diff --git a/client/components/TestPlanReportStatusDialog/isRequired.js b/client/components/TestPlanReportStatusDialog/isRequired.js index 855d410e5..bf2281e46 100644 --- a/client/components/TestPlanReportStatusDialog/isRequired.js +++ b/client/components/TestPlanReportStatusDialog/isRequired.js @@ -14,6 +14,20 @@ const requiredReports = { } ], CANDIDATE: [ + { + browser: { name: 'Chrome', id: '2' }, + at: { name: 'JAWS', id: '1' } + }, + { + browser: { name: 'Chrome', id: '2' }, + at: { name: 'NVDA', id: '2' } + }, + { + browser: { name: 'Safari', id: '3' }, + at: { name: 'VoiceOver for macOS', id: '3' } + } + ], + RECOMMENDED: [ { browser: { name: 'Chrome', id: '2' }, at: { name: 'JAWS', id: '1' } diff --git a/client/tests/__mocks__/GraphQLMocks/DataManagementPagePopulatedMock.js b/client/tests/__mocks__/GraphQLMocks/DataManagementPagePopulatedMock.js index 70d37a3c8..1715e7526 100644 --- a/client/tests/__mocks__/GraphQLMocks/DataManagementPagePopulatedMock.js +++ b/client/tests/__mocks__/GraphQLMocks/DataManagementPagePopulatedMock.js @@ -2427,6 +2427,357 @@ export default ( } } }, + { + request: { + query: testPlanReportStatusDialogQuery, + variables: { testPlanVersionId: '5' } + }, + result: { + data: { + testPlanVersion: { + id: '5', + title: 'Checkbox Example (Mixed-State)', + phase: 'RECOMMENDED', + gitSha: '836fb2a997f5b2844035b8c934f8fda9833cd5b2', + gitMessage: 'Validation for test csv formats (#980)', + updatedAt: '2023-08-23T20:30:34.000Z', + draftPhaseReachedAt: null, + candidatePhaseReachedAt: '2022-07-06T00:00:00.000Z', + recommendedPhaseTargetDate: '2023-01-02T00:00:00.000Z', + recommendedPhaseReachedAt: '2023-01-03T00:00:00.000Z', + testPlan: { + directory: 'checkbox-tri-state' + }, + testPlanReports: [ + { + id: '6', + metrics: { + testsCount: 7, + supportLevel: 'FAILING', + conflictsCount: 0, + supportPercent: 96, + testsFailedCount: 3, + testsPassedCount: 4, + optionalFormatted: '4 of 4 passed', + requiredFormatted: '44 of 46 passed', + optionalAssertionsCount: 4, + requiredAssertionsCount: 46, + unexpectedBehaviorCount: 0, + unexpectedBehaviorsFormatted: false, + optionalAssertionsFailedCount: 0, + optionalAssertionsPassedCount: 4, + requiredAssertionsFailedCount: 2, + requiredAssertionsPassedCount: 44 + }, + markedFinalAt: '2022-07-06T00:00:00.000Z', + at: { + id: '3', + name: 'VoiceOver for macOS' + }, + browser: { + id: '3', + name: 'Safari' + }, + issues: [], + draftTestPlanRuns: [ + { + tester: { + username: 'tom-proudfeet' + }, + testPlanReport: { + id: '6' + }, + testResults: [ + { + test: { + id: 'YTE3NeyIyIjoiNSJ9WJlMj' + }, + atVersion: { + id: '3', + name: '11.6 (20G165)' + }, + browserVersion: { + id: '3', + name: '14.1.2' + }, + completedAt: + '2023-08-30T13:23:57.070Z' + }, + { + test: { + id: 'YWJiOeyIyIjoiNSJ9GQ5Zm' + }, + atVersion: { + id: '3', + name: '11.6 (20G165)' + }, + browserVersion: { + id: '3', + name: '14.1.2' + }, + completedAt: + '2023-08-30T13:23:57.142Z' + }, + { + test: { + id: 'ZGFlYeyIyIjoiNSJ9TJlMW' + }, + atVersion: { + id: '3', + name: '11.6 (20G165)' + }, + browserVersion: { + id: '3', + name: '14.1.2' + }, + completedAt: + '2023-08-30T13:23:57.204Z' + }, + { + test: { + id: 'YjI2MeyIyIjoiNSJ9WE1OT' + }, + atVersion: { + id: '3', + name: '11.6 (20G165)' + }, + browserVersion: { + id: '3', + name: '14.1.2' + }, + completedAt: + '2023-08-30T13:23:57.275Z' + }, + { + test: { + id: 'ZjAwZeyIyIjoiNSJ9TZmZj' + }, + atVersion: { + id: '3', + name: '11.6 (20G165)' + }, + browserVersion: { + id: '3', + name: '14.1.2' + }, + completedAt: + '2023-08-30T13:23:57.330Z' + }, + { + test: { + id: 'MGRjZeyIyIjoiNSJ9WNiZD' + }, + atVersion: { + id: '3', + name: '11.6 (20G165)' + }, + browserVersion: { + id: '3', + name: '14.1.2' + }, + completedAt: + '2023-08-30T13:23:57.382Z' + }, + { + test: { + id: 'OTZmYeyIyIjoiNSJ9TU5Ym' + }, + atVersion: { + id: '3', + name: '11.6 (20G165)' + }, + browserVersion: { + id: '3', + name: '14.1.2' + }, + completedAt: + '2023-08-30T13:23:57.439Z' + } + ] + } + ] + }, + { + id: '12', + metrics: { + testsCount: 14, + supportLevel: 'FULL', + conflictsCount: 0, + supportPercent: 100, + testsFailedCount: 12, + testsPassedCount: 2, + optionalFormatted: false, + requiredFormatted: '25 of 25 passed', + optionalAssertionsCount: 0, + requiredAssertionsCount: 25, + unexpectedBehaviorCount: 0, + unexpectedBehaviorsFormatted: false, + optionalAssertionsFailedCount: 0, + optionalAssertionsPassedCount: 0, + requiredAssertionsFailedCount: 0, + requiredAssertionsPassedCount: 25 + }, + markedFinalAt: '2022-07-06T00:00:00.000Z', + at: { + id: '1', + name: 'JAWS' + }, + browser: { + id: '2', + name: 'Chrome' + }, + issues: [], + draftTestPlanRuns: [ + { + tester: { + username: 'esmeralda-baggins' + }, + testPlanReport: { + id: '12' + }, + testResults: [ + { + test: { + id: 'MTVlZeyIyIjoiNSJ9DUzMz' + }, + atVersion: { + id: '1', + name: '2021.2111.13' + }, + browserVersion: { + id: '2', + name: '99.0.4844.84' + }, + completedAt: + '2023-08-30T13:23:58.343Z' + }, + { + test: { + id: 'OThhMeyIyIjoiNSJ9WMxM2' + }, + atVersion: { + id: '1', + name: '2021.2111.13' + }, + browserVersion: { + id: '2', + name: '99.0.4844.84' + }, + completedAt: + '2023-08-30T13:23:58.404Z' + }, + { + test: { + id: 'YWNhNeyIyIjoiNSJ9TliN2' + }, + atVersion: { + id: '1', + name: '2021.2111.13' + }, + browserVersion: { + id: '2', + name: '99.0.4844.84' + }, + completedAt: + '2023-08-30T13:23:58.472Z' + } + ] + } + ] + }, + { + id: '13', + metrics: { + testsCount: 14, + supportLevel: 'FULL', + conflictsCount: 0, + supportPercent: 100, + testsFailedCount: 12, + testsPassedCount: 2, + optionalFormatted: false, + requiredFormatted: '25 of 25 passed', + optionalAssertionsCount: 0, + requiredAssertionsCount: 25, + unexpectedBehaviorCount: 0, + unexpectedBehaviorsFormatted: false, + optionalAssertionsFailedCount: 0, + optionalAssertionsPassedCount: 0, + requiredAssertionsFailedCount: 0, + requiredAssertionsPassedCount: 25 + }, + markedFinalAt: '2022-07-07T00:00:00.000Z', + at: { + id: '2', + name: 'NVDA' + }, + browser: { + id: '2', + name: 'Chrome' + }, + issues: [], + draftTestPlanRuns: [ + { + tester: { + username: 'esmeralda-baggins' + }, + testPlanReport: { + id: '13' + }, + testResults: [ + { + test: { + id: 'MTVlZeyIyIjoiNSJ9DUzMz' + }, + atVersion: { + id: '2', + name: '2020.4' + }, + browserVersion: { + id: '2', + name: '99.0.4844.84' + }, + completedAt: + '2023-08-30T13:23:58.531Z' + }, + { + test: { + id: 'OThhMeyIyIjoiNSJ9WMxM2' + }, + atVersion: { + id: '2', + name: '2020.4' + }, + browserVersion: { + id: '2', + name: '99.0.4844.84' + }, + completedAt: + '2023-08-30T13:23:58.593Z' + }, + { + test: { + id: 'YWNhNeyIyIjoiNSJ9TliN2' + }, + atVersion: { + id: '2', + name: '2020.4' + }, + browserVersion: { + id: '2', + name: '99.0.4844.84' + }, + completedAt: + '2023-08-30T13:23:58.655Z' + } + ] + } + ] + } + ] + } + } + } + }, { request: { query: testPlanReportStatusDialogQuery, diff --git a/server/resolvers/TestPlanVersionOperations/updatePhaseResolver.js b/server/resolvers/TestPlanVersionOperations/updatePhaseResolver.js index 0b3d5b934..ce6248ba2 100644 --- a/server/resolvers/TestPlanVersionOperations/updatePhaseResolver.js +++ b/server/resolvers/TestPlanVersionOperations/updatePhaseResolver.js @@ -270,7 +270,7 @@ const updatePhaseResolver = async ( throw new Error('No reports have been marked as final.'); } - if (phase === 'CANDIDATE' || phase === 'RECOMMENDED') { + if (phase === 'CANDIDATE') { const reportsByAtAndBrowser = {}; testPlanReports From 0828988388d461aa6f6011964979d76e0c5fdc47 Mon Sep 17 00:00:00 2001 From: Paul Clue <67766160+Paul-Clue@users.noreply.github.com> Date: Tue, 5 Sep 2023 14:32:32 -0500 Subject: [PATCH 19/59] Add UI models and resolvers for phase requirements --- client/components/ManageTestQueue/index.jsx | 411 ++++++++++++++++-- client/components/ManageTestQueue/queries.js | 35 ++ client/components/common/BasicModal/index.jsx | 1 + client/components/common/PhasePill/index.jsx | 9 +- server/graphql-schema.js | 45 ++ server/models/services/AtBrowserService | 27 ++ server/models/services/helpers.js | 2 + .../createRequiredReportResolver.js | 27 ++ .../deleteRequiredReportResolver.js | 27 ++ .../RequiredReportOperations/index.js | 9 + .../updateRequiredReportResolver.js | 38 ++ server/resolvers/index.js | 4 + .../resolvers/mutateRequiredReportResolver.js | 5 + 13 files changed, 606 insertions(+), 34 deletions(-) create mode 100644 client/components/ManageTestQueue/queries.js create mode 100644 server/models/services/AtBrowserService create mode 100644 server/resolvers/RequiredReportOperations/createRequiredReportResolver.js create mode 100644 server/resolvers/RequiredReportOperations/deleteRequiredReportResolver.js create mode 100644 server/resolvers/RequiredReportOperations/index.js create mode 100644 server/resolvers/RequiredReportOperations/updateRequiredReportResolver.js create mode 100644 server/resolvers/mutateRequiredReportResolver.js diff --git a/client/components/ManageTestQueue/index.jsx b/client/components/ManageTestQueue/index.jsx index 22346548f..cb7f89810 100644 --- a/client/components/ManageTestQueue/index.jsx +++ b/client/components/ManageTestQueue/index.jsx @@ -21,6 +21,31 @@ import AddTestToQueueWithConfirmation from '../AddTestToQueueWithConfirmation'; import { ThemeTable, ThemeTableHeader } from '../common/ThemeTable'; import PhasePill from '../common/PhasePill'; +const ModalInnerSectionContainer = styled.div` + display: flex; + flex-direction: column; +`; + +const Row = styled.div` + display: grid; + grid-template-columns: 1fr 1fr; + grid-gap: 1rem; +`; + +const ModalSubtitleStyle = styled.h2` + font-size: 0.8em; + margin: 0; + padding: 0; +`; + +const Required = styled.span` + color: #ce1b4c; + + :after { + content: '*'; + } +`; + const TransparentButton = styled.button` border: none; background-color: transparent; @@ -111,6 +136,19 @@ const ManageTestQueue = ({ const editAtVersionButtonRef = useRef(); const deleteAtVersionButtonRef = useRef(); + // Find Manage Required Reports Modal + const [showEditAtBrowserModal, setShowEditAtBrowserModal] = useState(true); + const [requiredReportsModalAt, setRequiredReportsModalAt] = useState(''); + const [requiredReportsModalTitle, setRequiredReportsModalTitle] = + useState(''); + + const [updateAtSelection, setUpdateAtSelection] = useState('Select an At'); + const [updateListAtSelection, setUpdateListAtSelection] = + useState('Select an At'); + const [updateBrowserSelection, setUpdateBrowserSelection] = + useState('Select a Browser'); + const [updateListBrowserSelection, setUpdateListBrowserSelection] = + useState('Select a Browser'); const [showManageATs, setShowManageATs] = useState(false); const [showAddTestPlans, setShowAddTestPlans] = useState(false); const [showManageReqReports, setShowManageReqReports] = useState(false); @@ -490,6 +528,50 @@ const ManageTestQueue = ({ setShowThemedModal(true); }; + // Find Manage Required Reports Modal + const onOpenShowEditAtBrowserModal = (type = 'edit', phase) => { + if (type === 'edit') { + setRequiredReportsModalTitle( +

+ Edit the following AT/Browser pair for{' '} + + {phase} + {' '} + requied reports +

+ ); + } + + if (type === 'delete') { + setRequiredReportsModalTitle(

delete this

); + } + setShowEditAtBrowserModal(false); + }; + + const handleShowEditAtBrowserModal = () => { + setShowEditAtBrowserModal(false); + }; + + const handleAtChange = e => { + const value = e.target.value; + setUpdateAtSelection(value); + }; + + const handleBrowserChange = e => { + const value = e.target.value; + setUpdateBrowserSelection(value); + }; + + const handleListAtChange = e => { + const value = e.target.value; + setUpdateListAtSelection(value); + }; + + const handleListBrowserChange = e => { + const value = e.target.value; + setUpdateListBrowserSelection(value); + }; + return ( Phase
- { - // const { value } = e.target; - // updateMatchingTestPlanVersions( - // value, - // allTestPlanVersions - // ); - }} - > - - + {/* {updateListAtSelection === 'Select an At' ? ( */} + {updateListAtSelection === 'Select an At' ? ( + + + + + + + ) : ( + + {Object.entries(ats).map( + ([key, value]) => { + return ( + + ); + }, + {} + )} + + )}
Assistive Technology - { - // const { value } = e.target; - // updateMatchingTestPlanVersions( - // value, - // allTestPlanVersions - // ); - }} - > - - + {updateListAtSelection === 'Select an At' ? ( + + + + + + + ) : ( + + + + )} @@ -759,7 +926,7 @@ const ManageTestQueue = ({ // !selectedAtId || // !selectedBrowserId // } - // onClick={handleAddTestPlanToTestQueue} + // onClick={handleShowEditAtBrowserModal} > Add Required Reports @@ -796,15 +963,36 @@ const ManageTestQueue = ({ { + setRequiredReportsModalAt( + phase + ); + onOpenShowEditAtBrowserModal( + 'edit', + phase + ); + }} /> - Edit + + Edit + { + onOpenShowEditAtBrowserModal( + 'delete' + ); + }} /> - Remove + + Remove + @@ -827,7 +1015,6 @@ const ManageTestQueue = ({ ]} stacked /> - {showAtVersionModal && ( )} - {showThemedModal && ( )} - {showFeedbackModal && ( )} + {/* {!showEditAtBrowserModal && */} + {!showEditAtBrowserModal && ( + + + + + Assistive Technology + + + {updateAtSelection === 'Select an At' ? ( + + + + + + + ) : ( + + {Object.entries(ats).map( + ([key, value]) => { + return ( + + ); + }, + {} + )} + + )} + + + + Browser + {/* */} + + + {updateAtSelection === 'Select an At' ? ( + + ) : updateAtSelection === 'JAWS' ? ( + + {' '} + {Object.entries( + ats[0].browsers + ).map(([key, value]) => { + return ( + + ); + })}{' '} + + ) : updateAtSelection === 'NVDA' ? ( + + {Object.entries( + ats[1].browsers + ).map(([key, value]) => { + return ( + + ); + })}{' '} + + ) : updateAtSelection === + 'VoiceOver for macOS' ? ( + + {Object.entries( + ats[2].browsers + ).map(([key, value]) => { + return ( + + ); + })}{' '} + + ) : null} + + + + } + actionLabel={'Save Changes'} + handleAction={ + // updatedAtVersion !== atVersion || + // updatedBrowserVersion !== browserVersion + // ? onSubmit + // : handleClose + () => {} + } + handleClose={() => { + setUpdateAtSelection('Select an At'); + setShowEditAtBrowserModal(true); + }} + handleHide={() => { + setUpdateAtSelection('Select an At'); + setShowEditAtBrowserModal(true); + }} + staticBackdrop={true} + /> + )} ); }; diff --git a/client/components/ManageTestQueue/queries.js b/client/components/ManageTestQueue/queries.js new file mode 100644 index 000000000..4fed4df53 --- /dev/null +++ b/client/components/ManageTestQueue/queries.js @@ -0,0 +1,35 @@ +import { gql } from '@apollo/client'; + +export const ADD_TEST_QUEUE_MUTATION = gql` + mutation AddTestPlanReport( + $testPlanVersionId: ID! + $atId: ID! + $browserId: ID! + ) { + findOrCreateTestPlanReport( + input: { + testPlanVersionId: $testPlanVersionId + atId: $atId + browserId: $browserId + } + ) { + populatedData { + testPlanReport { + id + at { + id + } + browser { + id + } + } + testPlanVersion { + id + } + } + created { + locationOfData + } + } + } +`; diff --git a/client/components/common/BasicModal/index.jsx b/client/components/common/BasicModal/index.jsx index 97b179c9b..f08857a2d 100644 --- a/client/components/common/BasicModal/index.jsx +++ b/client/components/common/BasicModal/index.jsx @@ -42,6 +42,7 @@ const BasicModal = ({ centered={centered} animation={animation} onExit={handleHide || handleClose} + onHide={handleHide} /* Disabled due to buggy implementation which jumps the page */ autoFocus={false} aria-labelledby="basic-modal" diff --git a/client/components/common/PhasePill/index.jsx b/client/components/common/PhasePill/index.jsx index 78e29be05..66ba0db2b 100644 --- a/client/components/common/PhasePill/index.jsx +++ b/client/components/common/PhasePill/index.jsx @@ -24,6 +24,9 @@ const PhaseText = styled.span` top: -1px; margin-right: 5px; } + &.for-header { + border-radius: 20px; + } &.rd { background: #4177de; @@ -46,10 +49,12 @@ const PhaseText = styled.span` } `; -const PhasePill = ({ fullWidth = true, children: phase }) => { +const PhasePill = ({ fullWidth = true, forHeader = false, children: phase }) => { + let classes = fullWidth ? 'full-width' : ''; + classes = forHeader ? `${classes} for-header` : classes; return ( str) .join(' ')} > diff --git a/server/graphql-schema.js b/server/graphql-schema.js index d5dcf1779..fcbf383a8 100644 --- a/server/graphql-schema.js +++ b/server/graphql-schema.js @@ -204,6 +204,21 @@ const graphqlSchema = gql` releasedAt: Timestamp } + # """ + # The fields on the RequiredReportOperations type which can be used or update the + # RequiredReports. + # """ + # input RequiredReportOperationsInput { + # """ + # See AtVersion type for more information. + # """ + # inputAtId: ID! + # """ + # See AtVersion type for more information. + # """ + # inputBrowserId: ID! + # } + """ A suite of tests which keeps its identity as it evolves over time. """ @@ -1039,6 +1054,29 @@ const graphqlSchema = gql` findOrCreateAtVersion(input: AtVersionInput!): AtVersion! } + """ + """ + enum RequiredReportPhase { + IS_CANDIDATE + IS_RECOMMENDED + } + + # type RequiredReportOperations { + # """ + # """ + # createRequiredReport: Boolean! + # updateRequiredReport(atId: ID!, browserId: ID!): Boolean! + # deleteRequiredReport: Boolean! + # } + + type RequiredReportOperations { + """ + """ + createRequiredReport: Boolean! + updateRequiredReport(atId: ID!, browserId: ID!): Boolean! + deleteRequiredReport: Boolean! + } + """ Mutations scoped to an existing AtVersion. """ @@ -1226,6 +1264,13 @@ const graphqlSchema = gql` """ browser(id: ID!): BrowserOperations! """ + """ + requiredReport( + atId: ID! + browserId: ID! + phase: RequiredReportPhase! + ): RequiredReportOperations! + """ Adds a report with the given TestPlanVersion, AT and Browser, and a state of "DRAFT", resulting in the report appearing in the Test Queue. In the case an identical report already exists, it will be returned diff --git a/server/models/services/AtBrowserService b/server/models/services/AtBrowserService new file mode 100644 index 000000000..6a3a75216 --- /dev/null +++ b/server/models/services/AtBrowserService @@ -0,0 +1,27 @@ +const ModelService = require('./ModelService'); +const { AtBrowsers } = require('../'); +const { AT_BROWSERS_ATTRIBUTES } = require('./helpers'); + +const updateAtBrowser = async ( + { atId, browserId }, + updateParams = {}, + atBrowsersAttributes = AT_BROWSERS_ATTRIBUTES, + options = {} +) => { + await ModelService.update( + AtBrowsers, + { atId, browserId }, + updateParams, + options + ); + + return await ModelService.getByQuery( + AtBrowsers, + { atId, browserId }, + atBrowsersAttributes, + null, + options + ); +}; + +module.exports = { updateAtBrowser }; diff --git a/server/models/services/helpers.js b/server/models/services/helpers.js index 577e76120..8115844cf 100644 --- a/server/models/services/helpers.js +++ b/server/models/services/helpers.js @@ -1,5 +1,6 @@ const { At, + AtBrowsers, AtMode, AtVersion, Browser, @@ -28,6 +29,7 @@ const getSequelizeModelAttributes = model => { module.exports = { getSequelizeModelAttributes, AT_ATTRIBUTES: getSequelizeModelAttributes(At), + AT_BROWSERS_ATTRIBUTES: getSequelizeModelAttributes(AtBrowsers), AT_MODE_ATTRIBUTES: getSequelizeModelAttributes(AtMode), AT_VERSION_ATTRIBUTES: getSequelizeModelAttributes(AtVersion), BROWSER_ATTRIBUTES: getSequelizeModelAttributes(Browser), diff --git a/server/resolvers/RequiredReportOperations/createRequiredReportResolver.js b/server/resolvers/RequiredReportOperations/createRequiredReportResolver.js new file mode 100644 index 000000000..435e57d23 --- /dev/null +++ b/server/resolvers/RequiredReportOperations/createRequiredReportResolver.js @@ -0,0 +1,27 @@ +const { AuthenticationError } = require('apollo-server'); +const { updateAtBrowser } = require('../../models/services/AtBrowserService'); + +const createRequiredReportResolver = async ( + { parentContext: { atId, browserId, phase } }, + _, + { user } +) => { + if (!user?.roles.find(role => role.name === 'ADMIN')) { + throw new AuthenticationError(); + } + + let updateParams = {}; + + if (phase === 'IS_CANDIDATE') { + updateParams = { isCandidate: true }; + } + if (phase === 'IS_RECOMMENDED') { + updateParams = { isRecommended: true }; + } + + await updateAtBrowser({ atId, browserId }, updateParams); + + return true; +}; + +module.exports = createRequiredReportResolver; diff --git a/server/resolvers/RequiredReportOperations/deleteRequiredReportResolver.js b/server/resolvers/RequiredReportOperations/deleteRequiredReportResolver.js new file mode 100644 index 000000000..6d4cf39a2 --- /dev/null +++ b/server/resolvers/RequiredReportOperations/deleteRequiredReportResolver.js @@ -0,0 +1,27 @@ +const { AuthenticationError } = require('apollo-server'); +const { updateAtBrowser } = require('../../models/services/AtBrowserService'); + +const deleteRequiredReportResolver = async ( + { parentContext: { atId, browserId, phase } }, + _, + { user } +) => { + if (!user?.roles.find(role => role.name === 'ADMIN')) { + throw new AuthenticationError(); + } + + let updateParams = {}; + + if (phase === 'IS_CANDIDATE') { + updateParams = { isCandidate: false }; + } + if (phase === 'IS_RECOMMENDED') { + updateParams = { isRecommended: false }; + } + + await updateAtBrowser({ atId, browserId }, updateParams); + + return true; +}; + +module.exports = deleteRequiredReportResolver; diff --git a/server/resolvers/RequiredReportOperations/index.js b/server/resolvers/RequiredReportOperations/index.js new file mode 100644 index 000000000..e17558d01 --- /dev/null +++ b/server/resolvers/RequiredReportOperations/index.js @@ -0,0 +1,9 @@ +const createRequiredReport = require('./createRequiredReportResolver'); +const updateRequiredReport = require('./updateRequiredReportResolver'); +const deleteRequiredReport = require('./deleteRequiredReportResolver'); + +module.exports = { + createRequiredReport, + updateRequiredReport, + deleteRequiredReport +}; diff --git a/server/resolvers/RequiredReportOperations/updateRequiredReportResolver.js b/server/resolvers/RequiredReportOperations/updateRequiredReportResolver.js new file mode 100644 index 000000000..b5a9968e6 --- /dev/null +++ b/server/resolvers/RequiredReportOperations/updateRequiredReportResolver.js @@ -0,0 +1,38 @@ +const { AuthenticationError } = require('apollo-server'); +const { updateAtBrowser } = require('../../models/services/AtBrowserService'); + +const updateRequiredReportResolver = async ( + { parentContext: { atId, browserId, phase } }, + { atId: inputAtId, browserId: inputBrowserId }, + // input, + { user } +) => { + if (!user?.roles.find(role => role.name === 'ADMIN')) { + throw new AuthenticationError(); + } + + let updateParams = {}; + + if (phase === 'IS_CANDIDATE') { + updateParams = { isCandidate: false }; + await updateAtBrowser({ atId, browserId }, updateParams); + updateParams = { isCandidate: true }; + await updateAtBrowser( + { atId: inputAtId, browserId: inputBrowserId }, + updateParams + ); + } + if (phase === 'IS_RECOMMENDED') { + updateParams = { isRecommended: false }; + await updateAtBrowser({ atId, browserId }, updateParams); + updateParams = { isRecommended: true }; + await updateAtBrowser( + { atId: inputAtId, browserId: inputBrowserId }, + updateParams + ); + } + + return true; +}; + +module.exports = updateRequiredReportResolver; diff --git a/server/resolvers/index.js b/server/resolvers/index.js index 09644a406..711893c32 100644 --- a/server/resolvers/index.js +++ b/server/resolvers/index.js @@ -14,6 +14,7 @@ const addViewer = require('./addViewerResolver'); const mutateAt = require('./mutateAtResolver'); const mutateAtVersion = require('./mutateAtVersionResolver'); const mutateBrowser = require('./mutateBrowserResolver'); +const mutateRequiredReport = require('./mutateRequiredReportResolver'); const mutateTestPlanReport = require('./mutateTestPlanReportResolver'); const mutateTestPlanRun = require('./mutateTestPlanRunResolver'); const mutateTestResult = require('./mutateTestResultResolver'); @@ -24,6 +25,7 @@ const User = require('./User'); const AtOperations = require('./AtOperations'); const AtVersionOperations = require('./AtVersionOperations'); const BrowserOperations = require('./BrowserOperations'); +const RequiredReportOperations = require('./RequiredReportOperations'); const TestPlanVersion = require('./TestPlanVersion'); const TestPlanReport = require('./TestPlanReport'); const TestPlanReportOperations = require('./TestPlanReportOperations'); @@ -53,6 +55,7 @@ const resolvers = { at: mutateAt, atVersion: mutateAtVersion, browser: mutateBrowser, + requiredReport: mutateRequiredReport, testPlanReport: mutateTestPlanReport, testPlanRun: mutateTestPlanRun, testResult: mutateTestResult, @@ -64,6 +67,7 @@ const resolvers = { AtOperations, AtVersionOperations, BrowserOperations, + RequiredReportOperations, User, TestPlanVersion, TestPlanReport, diff --git a/server/resolvers/mutateRequiredReportResolver.js b/server/resolvers/mutateRequiredReportResolver.js new file mode 100644 index 000000000..83ce4c83e --- /dev/null +++ b/server/resolvers/mutateRequiredReportResolver.js @@ -0,0 +1,5 @@ +const mutateRequiredReportResolver = (_, { atId, browserId, phase }) => { + return { parentContext: { atId, browserId, phase } }; +}; + +module.exports = mutateRequiredReportResolver; From 58920b04d6773fe134a0be5163416b5b669cf5db Mon Sep 17 00:00:00 2001 From: Howard Edwards Date: Wed, 6 Sep 2023 18:27:24 -0500 Subject: [PATCH 20/59] Update Version History Page (#767) * Apply correct sort order for Timeline for All Versions section * Update headings used on Versions page * Add aria-labelledby's for tables * Add   for applicable spaces so the text is properly announced by NVDA * Add migration to add missing deprecatedAt dates and to also properly set the candidatePhaseReachedAt dates to more practical dates after the migrations (if candidatePhaseReachedAt < draftPhaseReachedAt, set it candidatePhaseReachedAt to draftPhaseReachedAt + 1day) * Test Plan Versions Page: Use standard testPlanVersions descending sort and show all phases being included in Version Summary * Switch issues search support for checking against hidden body content instead * Fix TestPlanReportStatusDialog using d (day of the week) and y (era) * Explicit check for older date when deprecating existing RD test plan versions during import * Update hidden message content in github issue * Update query for anon user on TestRun page * Keep overall status pill from drifting to center of cell * Fix R&D only TestPlanVersion's table not being shown * Use aria-label for heading to prevent space being announced --- .../DataManagementRow/index.jsx | 1 - .../TestPlanReportStatusDialog/index.jsx | 2 +- .../components/TestPlanVersionsPage/index.jsx | 95 +++++++---- client/components/TestRun/queries.js | 1 + client/components/common/ThemeTable/index.jsx | 12 +- client/utils/createIssueLink.js | 15 +- ...gDeprecatedAtAndReorderPhaseChangeDates.js | 161 ++++++++++++++++++ .../TestPlanReport/issuesResolver.js | 10 +- server/scripts/import-tests/index.js | 19 ++- server/services/GithubService.js | 8 +- 10 files changed, 273 insertions(+), 51 deletions(-) create mode 100644 server/migrations/20230830225248-addMissingDeprecatedAtAndReorderPhaseChangeDates.js diff --git a/client/components/DataManagement/DataManagementRow/index.jsx b/client/components/DataManagement/DataManagementRow/index.jsx index 2c081754d..914b179b7 100644 --- a/client/components/DataManagement/DataManagementRow/index.jsx +++ b/client/components/DataManagement/DataManagementRow/index.jsx @@ -27,7 +27,6 @@ import { getVersionData } from '../utils'; const StatusCell = styled.div` display: flex; flex-direction: column; - justify-content: center; height: 100%; .review-text { diff --git a/client/components/TestPlanReportStatusDialog/index.jsx b/client/components/TestPlanReportStatusDialog/index.jsx index b48d27fd9..1d01b5775 100644 --- a/client/components/TestPlanReportStatusDialog/index.jsx +++ b/client/components/TestPlanReportStatusDialog/index.jsx @@ -98,7 +98,7 @@ const TestPlanReportStatusDialog = ({ const renderCompleteReportStatus = testPlanReport => { const formattedDate = convertDateToString( testPlanReport.markedFinalAt, - 'MMM d, yyyy' + 'MMM D, YYYY' ); return ( { } }; - const getDerivedDeprecatedDuringPhase = testPlanVersion => { + const deriveDeprecatedDuringPhase = testPlanVersion => { let derivedPhaseDeprecatedDuring = 'RD'; if (testPlanVersion.recommendedPhaseReachedAt) derivedPhaseDeprecatedDuring = 'RECOMMENDED'; @@ -186,14 +188,6 @@ const TestPlanVersionsPage = () => { return new Date(b.updatedAt) - new Date(a.updatedAt); }); - const testPlanVersionsDesc = data.testPlan.testPlanVersions - .slice() - .sort((a, b) => { - return new Date(a.updatedAt) - new Date(b.updatedAt); - }); - - const nonRDVersions = testPlanVersions.filter(each => each.phase !== 'RD'); - const issues = uniqueBy( testPlanVersions.flatMap(testPlanVersion => testPlanVersion.testPlanReports.flatMap(testPlanReport => @@ -226,10 +220,16 @@ const TestPlanVersionsPage = () => { Commit History for aria-at/tests/{testPlanDirectory} - {!nonRDVersions.length ? null : ( + {!testPlanVersions.length ? null : ( <> - Version Summary - + + Version Summary + + Version @@ -238,7 +238,7 @@ const TestPlanVersionsPage = () => { - {nonRDVersions.map(testPlanVersion => ( + {testPlanVersions.map(testPlanVersion => ( { {(() => { // Gets the derived phase even if deprecated by checking // the known dates on the testPlanVersion object - const derivedPhase = - getDerivedDeprecatedDuringPhase( + const derivedDeprecatedAtPhase = + deriveDeprecatedDuringPhase( testPlanVersion ); const phasePill = ( - {derivedPhase} + {derivedDeprecatedAtPhase} ); @@ -294,11 +294,15 @@ const TestPlanVersionsPage = () => { )} - GitHub Issues + + GitHub Issues + {!issues.length ? ( - No GitHub Issues + + No GitHub Issues + ) : ( - + Author @@ -357,8 +361,14 @@ const TestPlanVersionsPage = () => { )} - Timeline for All Versions - + + Timeline for All Versions + + Date @@ -366,7 +376,7 @@ const TestPlanVersionsPage = () => { - {testPlanVersionsDesc.map(testPlanVersion => { + {testPlanVersions.map(testPlanVersion => { const versionString = ( { {getEventDate(testPlanVersion)} - {versionString} {eventBody} + {versionString} {eventBody} ); @@ -390,7 +400,7 @@ const TestPlanVersionsPage = () => { - {nonRDVersions.map(testPlanVersion => { + {testPlanVersions.map(testPlanVersion => { const vString = `V${convertDateToString( testPlanVersion.updatedAt, 'YY.MM.DD' @@ -398,29 +408,38 @@ const TestPlanVersionsPage = () => { // Gets the derived phase even if deprecated by checking // the known dates on the testPlanVersion object - const derivedPhase = - getDerivedDeprecatedDuringPhase(testPlanVersion); + const derivedDeprecatedAtPhase = + deriveDeprecatedDuringPhase(testPlanVersion); const hasFinalReports = - (derivedPhase === 'CANDIDATE' || - derivedPhase === 'RECOMMENDED') && + (derivedDeprecatedAtPhase === 'CANDIDATE' || + derivedDeprecatedAtPhase === 'RECOMMENDED') && !!testPlanVersion.testPlanReports.filter( report => report.isFinal ).length; + return (
-

+

+   {testPlanVersion.phase} - {' '} - on {getEventDate(testPlanVersion)} -

+ +  on {getEventDate(testPlanVersion)} +
  • { - + Timeline for {vString} - + Date diff --git a/client/components/TestRun/queries.js b/client/components/TestRun/queries.js index f1a835f63..fcb09ec9e 100644 --- a/client/components/TestRun/queries.js +++ b/client/components/TestRun/queries.js @@ -209,6 +209,7 @@ export const TEST_RUN_PAGE_ANON_QUERY = gql` id title phase + updatedAt gitSha testPageUrl testPlan { diff --git a/client/components/common/ThemeTable/index.jsx b/client/components/common/ThemeTable/index.jsx index 943bd390e..9907130b9 100644 --- a/client/components/common/ThemeTable/index.jsx +++ b/client/components/common/ThemeTable/index.jsx @@ -1,7 +1,17 @@ import { Table } from 'react-bootstrap'; import styled from '@emotion/styled'; -export const ThemeTableHeader = styled.h2` +export const ThemeTableHeaderH2 = styled.h2` + background-color: var(--bs-table-bg) !important; + font-size: 1.5rem; + font-weight: 600; + border: solid 1px #d2d5d9; + border-bottom: none; + padding: 0.5rem 1rem; + margin: 0.5rem 0 0 0; +`; + +export const ThemeTableHeaderH3 = styled.h3` background-color: var(--bs-table-bg) !important; font-size: 1.25rem; font-weight: 600; diff --git a/client/utils/createIssueLink.js b/client/utils/createIssueLink.js index 9fb9fcc5d..a196925a6 100644 --- a/client/utils/createIssueLink.js +++ b/client/utils/createIssueLink.js @@ -1,3 +1,8 @@ +const GITHUB_ISSUES_URL = + process.env.ENVIRONMENT === 'production' + ? 'https://github.com/w3c/aria-at' + : 'https://github.com/bocoup/aria-at'; + const atLabelMap = { 'VoiceOver for macOS': 'vo', JAWS: 'jaws', @@ -89,15 +94,17 @@ const createIssueLink = ({ let body = `## Description of Behavior\n\n` + - `\n\n` + - testSetupFormatted; + `\n\n` + + testSetupFormatted + + `\n\n\n\n` + + ``; if (conflictMarkdown) { body += `\n${conflictMarkdown}`; } return ( - `https://github.com/w3c/aria-at/issues/new?title=${encodeURI(title)}&` + + `${GITHUB_ISSUES_URL}/issues/new?title=${encodeURI(title)}&` + `labels=${labels}&body=${encodeURIComponent(body)}` ); }; @@ -134,7 +141,7 @@ export const getIssueSearchLink = ({ .filter(str => str) .join(' '); - return `https://github.com/w3c/aria-at/issues?q=${encodeURI(query)}`; + return `${GITHUB_ISSUES_URL}/issues?q=${encodeURI(query)}`; }; export default createIssueLink; diff --git a/server/migrations/20230830225248-addMissingDeprecatedAtAndReorderPhaseChangeDates.js b/server/migrations/20230830225248-addMissingDeprecatedAtAndReorderPhaseChangeDates.js new file mode 100644 index 000000000..c05f49607 --- /dev/null +++ b/server/migrations/20230830225248-addMissingDeprecatedAtAndReorderPhaseChangeDates.js @@ -0,0 +1,161 @@ +'use strict'; + +/** @type {import('sequelize-cli').Migration} */ +module.exports = { + async up(queryInterface, Sequelize) { + return queryInterface.sequelize.transaction(async transaction => { + // Check for all instances of TestPlanVersions that have "markedAsFinal" reports + // and in the DRAFT phase, and set those to CANDIDATE (so they can be shown as having + // reports been generated for them even though they are now deprecated) + // + // Eg. 281 (Disclosure Navigation) and + // 1178 (Radio Group Example Using aria-activedescendant) TestPlanVersions + const testPlanVersionsToSetToCandidate = + await queryInterface.sequelize.query( + `select "TestPlanVersion".id, phase, "markedFinalAt" + from "TestPlanVersion" + join "TestPlanReport" on "TestPlanVersion".id = "TestPlanReport"."testPlanVersionId" + where "markedFinalAt" is not null + and phase = 'DRAFT';`, + { + type: Sequelize.QueryTypes.SELECT, + transaction + } + ); + + const candidatePhaseReachedAt = new Date(); + const recommendedPhaseTargetDate = new Date( + candidatePhaseReachedAt + ); + recommendedPhaseTargetDate.setDate( + candidatePhaseReachedAt.getDate() + 180 + ); + + for (const testPlanVersion of testPlanVersionsToSetToCandidate) { + await queryInterface.sequelize.query( + `update "TestPlanVersion" + set "candidatePhaseReachedAt" = ?, + "recommendedPhaseTargetDate" = ?, + phase = 'CANDIDATE' + where id = ?`, + { + replacements: [ + candidatePhaseReachedAt, + recommendedPhaseTargetDate, + testPlanVersion.id + ], + transaction + } + ); + } + + // Check for instances of all older TestPlanVersions and deprecate them + const testPlanVersions = await queryInterface.sequelize.query( + `select id, directory, "updatedAt", "draftPhaseReachedAt", "candidatePhaseReachedAt", "recommendedPhaseReachedAt", "deprecatedAt" + from "TestPlanVersion" + order by directory, "updatedAt";`, + { + type: Sequelize.QueryTypes.SELECT, + transaction + } + ); + + // Group objects by directory + const groupedData = {}; + for (const testPlanVersion of testPlanVersions) { + if (!groupedData[testPlanVersion.directory]) { + groupedData[testPlanVersion.directory] = []; + } + groupedData[testPlanVersion.directory].push(testPlanVersion); + } + + // Update "deprecatedAt" based on next object's "updatedAt" + for (const directory in groupedData) { + const objects = groupedData[directory]; + for (let i = 0; i < objects.length - 1; i++) { + objects[i].deprecatedAt = objects[i + 1].updatedAt; + } + } + + // Flatten the grouped data back into a single array + const flattenedTestPlanVersions = Object.values(groupedData).reduce( + (acc, objects) => acc.concat(objects), + [] + ); + + for (let testPlanVersion of flattenedTestPlanVersions) { + // Check for the instances where candidatePhaseReachedAt is shown as happening + // before draftPhaseReachedAt + if ( + testPlanVersion.draftPhaseReachedAt && + testPlanVersion.candidatePhaseReachedAt + ) { + let draftPhaseReachedAt = new Date( + testPlanVersion.draftPhaseReachedAt + ); + let candidatePhaseReachedAt = new Date( + testPlanVersion.candidatePhaseReachedAt + ); + + // Update candidatePhaseReachedAt to be the draftPhaseReachedAt date (+1) + // (because that phase happening before shouldn't be possible) + if (candidatePhaseReachedAt < draftPhaseReachedAt) { + const newCandidatePhaseReachedAt = new Date( + draftPhaseReachedAt + ); + newCandidatePhaseReachedAt.setDate( + newCandidatePhaseReachedAt.getDate() + 1 + ); + + testPlanVersion.candidatePhaseReachedAt = + newCandidatePhaseReachedAt; + + await queryInterface.sequelize.query( + `update "TestPlanVersion" + set "candidatePhaseReachedAt" = ? + where id = ?`, + { + replacements: [ + testPlanVersion.candidatePhaseReachedAt, + testPlanVersion.id + ], + transaction + } + ); + } + } + + if (testPlanVersion.deprecatedAt) { + // Add deprecatedAt for applicable testPlanVersions + await queryInterface.sequelize.query( + `update "TestPlanVersion" + set "deprecatedAt" = ?, + phase = 'DEPRECATED' + where id = ?`, + { + replacements: [ + testPlanVersion.recommendedPhaseReachedAt + ? testPlanVersion.recommendedPhaseReachedAt + : testPlanVersion.candidatePhaseReachedAt + ? testPlanVersion.candidatePhaseReachedAt + : testPlanVersion.deprecatedAt, + testPlanVersion.id + ], + transaction + } + ); + } + } + }); + }, + + async down(queryInterface) { + return queryInterface.sequelize.transaction(async transaction => { + await queryInterface.sequelize.query( + `update "TestPlanVersion" + set "deprecatedAt" = null;`, + { transaction } + ); + }); + } +}; diff --git a/server/resolvers/TestPlanReport/issuesResolver.js b/server/resolvers/TestPlanReport/issuesResolver.js index d554c67ba..7f0ca22bf 100644 --- a/server/resolvers/TestPlanReport/issuesResolver.js +++ b/server/resolvers/TestPlanReport/issuesResolver.js @@ -2,9 +2,7 @@ const { GithubService } = require('../../services'); const convertDateToString = require('../../util/convertDateToString'); const issuesResolver = async testPlanReport => { - const issues = await GithubService.getAllIssues({ - atName: testPlanReport.at.name - }); + const issues = await GithubService.getAllIssues(); const { at, testPlanVersion } = testPlanReport; @@ -23,11 +21,11 @@ const issuesResolver = async testPlanReport => { } return issues - .filter(({ title, labels }) => { + .filter(({ labels, body }) => { return ( labels.find(({ name }) => name === searchAtName) && - title.includes(searchTestPlanTitle) && - title.includes(searchVersionString) + body?.includes(searchTestPlanTitle) && + body?.includes(searchVersionString) ); }) .map(issue => { diff --git a/server/scripts/import-tests/index.js b/server/scripts/import-tests/index.js index 8368c1a63..d34b55818 100644 --- a/server/scripts/import-tests/index.js +++ b/server/scripts/import-tests/index.js @@ -7,7 +7,8 @@ const spawn = require('cross-spawn'); const { At } = require('../../models'); const { createTestPlanVersion, - getTestPlanVersions + getTestPlanVersions, + updateTestPlanVersion } = require('../../models/services/TestPlanVersionService'); const { getTestPlans, @@ -148,6 +149,22 @@ const importTestPlanVersions = async () => { testPlanId = newTestPlan.dataValues.id; } + // Check if any TestPlanVersions exist for the directory and is currently in RD, and set it + // to DEPRECATED + const testPlanVersionsToDeprecate = await getTestPlanVersions('', { + phase: 'RD', + directory + }); + if (testPlanVersionsToDeprecate.length) { + for (const testPlanVersionToDeprecate of testPlanVersionsToDeprecate) { + if (new Date(testPlanVersionToDeprecate.updatedAt) < updatedAt) + await updateTestPlanVersion(testPlanVersionToDeprecate.id, { + phase: 'DEPRECATED', + deprecatedAt: updatedAt + }); + } + } + await createTestPlanVersion({ id: testPlanVersionId, title, diff --git a/server/services/GithubService.js b/server/services/GithubService.js index aebcc0bb7..c69ca0807 100644 --- a/server/services/GithubService.js +++ b/server/services/GithubService.js @@ -2,6 +2,7 @@ const axios = require('axios'); const NodeCache = require('node-cache'); const { + ENVIRONMENT, GITHUB_GRAPHQL_SERVER, GITHUB_OAUTH_SERVER, GITHUB_CLIENT_ID, @@ -11,6 +12,11 @@ const { GITHUB_TEAM_QUERY } = process.env; +const GITHUB_ISSUES_API_URL = + ENVIRONMENT === 'production' + ? 'https://api.github.com/repos/w3c/aria-at' + : 'https://api.github.com/repos/bocoup/aria-at'; + const permissionScopes = [ // Not currently used, but this permissions scope will allow us to query for // the user's private email address via the REST API in the future (Note @@ -32,7 +38,7 @@ const getAllIssuesFromGitHub = async () => { // eslint-disable-next-line no-constant-condition while (true) { const issuesEndpoint = - `https://api.github.com/repos/w3c/aria-at/issues` + + `${GITHUB_ISSUES_API_URL}/issues` + `?labels=app&state=all&per_page=100`; const url = `${issuesEndpoint}&page=${page}`; const auth = { From 04c15f650985367e8cb017b3522d9bad42a34a8d Mon Sep 17 00:00:00 2001 From: Howard Edwards Date: Thu, 7 Sep 2023 09:25:24 -0500 Subject: [PATCH 21/59] Update date format used in aria-label --- client/components/TestPlanVersionsPage/index.jsx | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/client/components/TestPlanVersionsPage/index.jsx b/client/components/TestPlanVersionsPage/index.jsx index f5fe4e5ec..405ac9d0f 100644 --- a/client/components/TestPlanVersionsPage/index.jsx +++ b/client/components/TestPlanVersionsPage/index.jsx @@ -421,10 +421,13 @@ const TestPlanVersionsPage = () => { return (

    From 4ca4fd23cde4156e1d19625d32f977db8f1796f4 Mon Sep 17 00:00:00 2001 From: Paul Clue <67766160+Paul-Clue@users.noreply.github.com> Date: Tue, 12 Sep 2023 16:46:52 -0500 Subject: [PATCH 22/59] Update required report table --- client/components/ManageTestQueue/index.jsx | 598 +++++++++++++++--- client/components/ManageTestQueue/queries.js | 34 + client/components/TestQueue/TestQueue.css | 13 + server/graphql-schema.js | 13 +- .../createRequiredReportResolver.js | 2 +- 5 files changed, 557 insertions(+), 103 deletions(-) diff --git a/client/components/ManageTestQueue/index.jsx b/client/components/ManageTestQueue/index.jsx index cb7f89810..d6d9b439b 100644 --- a/client/components/ManageTestQueue/index.jsx +++ b/client/components/ManageTestQueue/index.jsx @@ -1,9 +1,15 @@ import React, { useEffect, useState, useRef } from 'react'; import { useMutation } from '@apollo/client'; -import { Button, Form } from 'react-bootstrap'; +import { Button, Form, Dropdown } from 'react-bootstrap'; import styled from '@emotion/styled'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { faEdit, faTrashAlt } from '@fortawesome/free-solid-svg-icons'; +import { + faEdit, + faTrashAlt, + faUserPlus, + faCheck, + faChevronDown +} from '@fortawesome/free-solid-svg-icons'; import PropTypes from 'prop-types'; import BasicModal from '../common/BasicModal'; import UpdateVersionModal from '../common/UpdateVersionModal'; @@ -13,6 +19,11 @@ import { EDIT_AT_VERSION_MUTATION, DELETE_AT_VERSION_MUTATION } from '../TestQueue/queries'; +import { + CREATE_MANAGE_TEST_QUEUE_MUTATION, + UPDATE_MANAGE_TEST_QUEUE_MUTATION + // DELET_MANAGE_TEST_QUEUE_MUTATION, +} from './queries'; import { gitUpdatedDateToString } from '../../utils/gitUtils'; import { convertStringToDate } from '../../utils/formatter'; import { LoadingStatus, useTriggerLoad } from '../common/LoadingStatus'; @@ -20,6 +31,7 @@ import DisclosureComponent from '../common/DisclosureComponent'; import AddTestToQueueWithConfirmation from '../AddTestToQueueWithConfirmation'; import { ThemeTable, ThemeTableHeader } from '../common/ThemeTable'; import PhasePill from '../common/PhasePill'; +import { at } from 'lodash'; const ModalInnerSectionContainer = styled.div` display: flex; @@ -143,12 +155,18 @@ const ManageTestQueue = ({ useState(''); const [updateAtSelection, setUpdateAtSelection] = useState('Select an At'); + const [updateAtForButton, setUpdateAtForButton] = useState(''); const [updateListAtSelection, setUpdateListAtSelection] = useState('Select an At'); const [updateBrowserSelection, setUpdateBrowserSelection] = useState('Select a Browser'); + const [updateBrowserForButton, setUpdateBrowserForButton] = + useState('Select a Browser'); const [updateListBrowserSelection, setUpdateListBrowserSelection] = useState('Select a Browser'); + const [updatePhaseSelection, setUpdatePhaseSelection] = + useState('Phase Selection'); + const [updatePhaseForButton, setUpdatePhaseForButton] = useState(''); const [showManageATs, setShowManageATs] = useState(false); const [showAddTestPlans, setShowAddTestPlans] = useState(false); const [showManageReqReports, setShowManageReqReports] = useState(false); @@ -188,7 +206,7 @@ const ManageTestQueue = ({ const [selectedAtId, setSelectedAtId] = useState(''); const [selectedBrowserId, setSelectedBrowserId] = useState(''); - const atBrowserCombinations = [ + const [atBrowserCombinations, setAtBrowserCombinations] = useState([ ...ats.flatMap(at => at.candidateBrowsers.map(browser => ({ at, @@ -203,11 +221,284 @@ const ManageTestQueue = ({ phase: 'RECOMMENDED' })) ) - ]; + ]); + + const toggleDivStyle = { + backgroundColor: 'transparent', + width: '100%', + height: '38px', + textAlign: 'center' + }; + + const togglePStyle = { + border: '1px solid #ced4da', + borderRadius: '0.375rem', + backgroundColor: '#fff', + padding: '2px', + width: '100%', + height: '38px', + cursor: 'default', + display: 'inline-block' + }; + + const toggleSpanStyle = { + float: 'left', + marginTop: '2px', + marginLeft: '20px', + backgroundColor: + updatePhaseSelection === 'Phase Selection' + ? '#fff' + : updatePhaseSelection === 'Candidate' + ? '#ff6c00' + : updatePhaseSelection === 'Recommended' + ? '#8441de' + : 'black', + borderRadius: '14px', + padding: '2px 15px', + fontSize: '1rem', + fontWeight: '400', + color: updatePhaseSelection === 'Phase Selection' ? 'black' : '#fff' + }; + + const CustomToggle = React.forwardRef(({ children, onClick }, ref) => ( +
    { + e.preventDefault(); + onClick(e); + }} + > +

    { + e.preventDefault(); + onClick(e); + }} + > + {children} + + + +

    +
    + )); + + const setPhase = phase => { + setUpdatePhaseSelection(phase); + if (phase === 'Candidate') { + setUpdatePhaseForButton('CANDIDATE'); + } + if (phase === 'Recommended') { + setUpdatePhaseForButton('RECOMMENDED'); + } + }; + const CustomMenu = React.forwardRef( + ( + { + children, + style, + onClick, + className, + 'aria-labelledby': labeledBy + }, + ref + ) => { + const [value, setValue] = useState(''); + + return ( +
    +
      + {React.Children.toArray(children).filter( + child => + !value || + child.props.children + .toLowerCase() + .startsWith(value) + )} +
    +
    + ); + } + ); + + // let atBrowserCombinations = [ + // ...ats.flatMap(at => + // at.candidateBrowsers.map(browser => ({ + // at, + // browser, + // phase: 'CANDIDATE' + // })) + // ), + // ...ats.flatMap(at => + // at.recommendedBrowsers.map(browser => ({ + // at, + // browser, + // phase: 'RECOMMENDED' + // })) + // ) + // ]; + + // Section: + const runMutationForRequiredReportTable = async mutation => { + let atId = ''; + let browserId = ''; + console.log(atBrowserCombinations) + + atBrowserCombinations.forEach(({ at, browser, phase }) => { + // console.log('AtID', updateAtForButton) + // console.log('at.Id', at.id) + // console.log('BrowserID', updateBrowserForButton) + // console.log('browser.Id', browser.id) + if ( + updateAtForButton === at.id && + updateBrowserForButton === browser.id + // updatePhaseForButton === phase + ) { + atId = at.id; + browserId = browser.id; + // console.log('IT CHECKED OUT'); + } + }); + mutation === 'createRequiredReport' + ? await triggerLoad(async () => { + const { data } = await createRequiredReport({ + variables: { + atId: atId, + browserId: browserId, + phase: `IS_${updatePhaseForButton}` + } + }); + console.log('data', data); + + const createdRequiredReport = + data.requiredReport.createRequiredReport; + console.log('atBrowserCombinations', atBrowserCombinations); + console.log('createdRequiredReport', createdRequiredReport); + + setAtBrowserCombinations([ + ...atBrowserCombinations, + createdRequiredReport + ]); + }, 'Adding Phase requirement to the required reports table') + : mutation === 'updateRequiredReport' + ? await triggerLoad(async () => { + await updateRequiredReport({ + variables: { + atId: atId, + browserId: browserId, + phase: `IS_${updatePhaseForButton}` + } + }); + }, 'Adding Phase requirement to the required reports table') + : // : mutation === 'deleteRequiredReport' + // ? await triggerLoad(async () => { + // await deleteRequiredReport({ + // variables: { + // atId: atId, + // browserId: browserId, + // phase: `IS_${updatePhaseForButton}` + // } + // }); + // }, 'Adding Phase requirement to the required reports table') + // : + null; + }; + + const addRequiredReport = async () => { + // console.log('The at', updateAtForButton); + // console.log('The browser', updateBrowserForButton); + // console.log('The phase', updatePhaseForButton); + // let atId = ''; + // let browserId = ''; + + // atBrowserCombinations.forEach(({ at, browser, phase }) => { + // // console.log('The at', at.id); + // // console.log('The at Update', updateAtForButton); + // // console.log('The browser', browser.id); + // // console.log('The browser Update', updateBrowserForButton); + // // console.log('The phase', phase); + // // console.log('The phase Update', updatePhaseForButton); + // if ( + // updateAtForButton === at.id && + // updateBrowserForButton === browser.id + // // updatePhaseForButton === phase + // ) { + // atId = at.id; + // browserId = browser.id; + // // console.log('IT CHECKED OUT'); + // } + // }); + await triggerLoad(async () => { + await createRequiredReport({ + variables: { + atId: atId, + browserId: browserId, + phase: `IS_${updatePhaseForButton}` + } + }); + }, 'Adding Phase requirement to the required reports table'); + // setShowConfirmation(true); + }; + + // const changeRequiredReport = async () => { + // atBrowserCombinations.forEach(({ at, browser, phase }) => { + // // console.log('The at', at.id); + // // console.log('The at Update', updateAtForButton); + // // console.log('The browser', browser.id); + // // console.log('The browser Update', updateBrowserForButton); + // // console.log('The phase', phase); + // // console.log('The phase Update', updatePhaseForButton); + // if ( + // updateAtForButton === at.id && + // updateBrowserForButton === browser.id + // // updatePhaseForButton === phase + // ) { + // atId = at.id; + // browserId = browser.id; + // // console.log('IT CHECKED OUT'); + // } + // }); + // await triggerLoad(async () => { + // await createRequiredReport({ + // variables: { + // atId: atId, + // browserId: browserId, + // phase: `IS_${updatePhaseForButton}` + // } + // }); + // }, 'Updating the required reports table'); + // }; + // addRequiredReport(); const [addAtVersion] = useMutation(ADD_AT_VERSION_MUTATION); const [editAtVersion] = useMutation(EDIT_AT_VERSION_MUTATION); const [deleteAtVersion] = useMutation(DELETE_AT_VERSION_MUTATION); + const [createRequiredReport] = useMutation( + CREATE_MANAGE_TEST_QUEUE_MUTATION + ); + const [updateRequiredReport] = useMutation( + UPDATE_MANAGE_TEST_QUEUE_MUTATION + ); + // const [deleteRequiredReport] = useMutation( + // DELETE_MANAGE_TEST_QUEUE_MUTATION + // ); const onManageAtsClick = () => setShowManageATs(!showManageATs); const onAddTestPlansClick = () => setShowAddTestPlans(!showAddTestPlans); @@ -515,6 +806,23 @@ const ManageTestQueue = ({ } }; + // IMPORTED PHASE FUNCTION DEFINITIONS + // const addPhaseRequirementToTable = async () => { + // await triggerLoad(async () => { + // await createRequiredReport({ + // variables: { + // atId: at.id, + // browserId: browser.id, + // phase: at.phase + // } + // }); + // }, 'Adding Phase requirement to the required reports table'); + // // setShowConfirmation(true); + // }; + + // DELET_MANAGE_TEST_QUEUE_MUTATION, + // UPDATE_MANAGE_TEST_QUEUE_MUTATION + const showFeedbackMessage = (title, content) => { setFeedbackModalTitle(title); setFeedbackModalContent(content); @@ -548,8 +856,9 @@ const ManageTestQueue = ({ setShowEditAtBrowserModal(false); }; - const handleShowEditAtBrowserModal = () => { - setShowEditAtBrowserModal(false); + const handlePhaseChange = e => { + const value = e.target.value; + setUpdatePhaseSelection(value); }; const handleAtChange = e => { @@ -565,11 +874,17 @@ const ManageTestQueue = ({ const handleListAtChange = e => { const value = e.target.value; setUpdateListAtSelection(value); + setUpdateAtForButton(value); + // console.log('value for at change', value); + // console.log('updateAtForButton', updateAtForButton); }; const handleListBrowserChange = e => { const value = e.target.value; setUpdateListBrowserSelection(value); + setUpdateBrowserForButton(value); + // console.log('value for browser change', value); + // console.log('updateBrowserForButton', updateBrowserForButton); }; return ( @@ -777,57 +1092,86 @@ const ManageTestQueue = ({ Add required reports for a specific AT and Browser pair + {/* section: */}
    Phase - {/* {updateListAtSelection === 'Select an At' ? ( */} + + + {updatePhaseSelection} + + + + {/* */} + + setPhase('Candidate') + } + > + Candidate + + + setPhase('Recommended') + } + > + Recommended + + + + + + + Assistive Technology + {updateListAtSelection === 'Select an At' ? ( - - + {ats.map(item => { + return ( + + ); + })} + {/* + + VoiceOver for macOs + */} ) : ( - {Object.entries(ats).map( + {/* {Object.entries(ats).map( ([key, value]) => { return ( + ); + })} )} - Assistive Technology + Browser {updateListAtSelection === 'Select an At' ? ( + ) : updateListAtSelection === '1' ? ( + - - - - + {' '} + + {/* {Object.entries(ats[0].browsers).map( + ([key, value]) => { + return ( + + ); + } + )}{' '} */} + {ats[0].browsers.map(item => { + return ( + + ); + })} - ) : ( + ) : updateListAtSelection === '2' ? ( - + + {/* {Object.entries(ats[1].browsers).map( + ([key, value]) => { + return ( + + ); + } + )}{' '} */} + {ats[1].browsers.map(item => { + return ( + + ); + })} - )} - - - - Browser - - { - // const { value } = e.target; - // updateMatchingTestPlanVersions( - // value, - // allTestPlanVersions - // ); - }} - > - - + ) : updateListAtSelection === '3' ? ( + + + {/* {Object.entries(ats[2].browsers).map( + ([key, value]) => { + return ( + + ); + } + )}{' '} */} + {ats[0].browsers.map(item => { + return ( + + ); + })} + + ) : null} @@ -963,9 +1359,6 @@ const ManageTestQueue = ({ { setRequiredReportsModalAt( phase @@ -1205,12 +1598,15 @@ const ManageTestQueue = ({ } actionLabel={'Save Changes'} + //section: handleAction={ // updatedAtVersion !== atVersion || // updatedBrowserVersion !== browserVersion // ? onSubmit // : handleClose - () => {} + () => { + console.log('IT RAN'); + } } handleClose={() => { setUpdateAtSelection('Select an At'); diff --git a/client/components/ManageTestQueue/queries.js b/client/components/ManageTestQueue/queries.js index 4fed4df53..896e504c9 100644 --- a/client/components/ManageTestQueue/queries.js +++ b/client/components/ManageTestQueue/queries.js @@ -1,5 +1,39 @@ import { gql } from '@apollo/client'; +export const CREATE_MANAGE_TEST_QUEUE_MUTATION = gql` + mutation CreateRequiredReport( + $atId: ID! + $browserId: ID! + $phase: RequiredReportPhase! + ) { + requiredReport(atId: $atId, browserId: $browserId, phase: $phase) { + createRequiredReport { + atId + browserId + phase + } + } + } +`; + +export const UPDATE_MANAGE_TEST_QUEUE_MUTATION = gql` + mutation UpdateRequiredReport( + $atId: ID! + $browserId: ID! + $phase: RequiredReportPhase! + ) { + requiredReport(atId: $atId, browserId: $browserId, phase: $phase) { + updateRequiredReport { + atId + browserId + phase + } + } + } +`; + +// DELET_MANAGE_TEST_QUEUE_MUTATION + export const ADD_TEST_QUEUE_MUTATION = gql` mutation AddTestPlanReport( $testPlanVersionId: ID! diff --git a/client/components/TestQueue/TestQueue.css b/client/components/TestQueue/TestQueue.css index 03d9ca34d..39c00b93d 100644 --- a/client/components/TestQueue/TestQueue.css +++ b/client/components/TestQueue/TestQueue.css @@ -95,3 +95,16 @@ table button { .add-test-plan-queue-modal-normalize-row { margin-top: auto; } + +.phase-option:hover { + text-decoration: none; + cursor: default; +} + +.drop-down-div { + height: fit-content; +} + +.drop-down-div > ul { + margin-bottom: 0; +} diff --git a/server/graphql-schema.js b/server/graphql-schema.js index fcbf383a8..bed7d575c 100644 --- a/server/graphql-schema.js +++ b/server/graphql-schema.js @@ -171,6 +171,17 @@ const graphqlSchema = gql` recommendedBrowsers: [Browser]! } + """ + The return type for createRequiredReport. + """ + type RequiredReport { + """ + """ + atId: ID! + browserId: ID! + phase: RequiredReportPhase! + } + """ The version for a given assistive technology. """ @@ -1072,7 +1083,7 @@ const graphqlSchema = gql` type RequiredReportOperations { """ """ - createRequiredReport: Boolean! + createRequiredReport: RequiredReport! updateRequiredReport(atId: ID!, browserId: ID!): Boolean! deleteRequiredReport: Boolean! } diff --git a/server/resolvers/RequiredReportOperations/createRequiredReportResolver.js b/server/resolvers/RequiredReportOperations/createRequiredReportResolver.js index 435e57d23..0039cf017 100644 --- a/server/resolvers/RequiredReportOperations/createRequiredReportResolver.js +++ b/server/resolvers/RequiredReportOperations/createRequiredReportResolver.js @@ -21,7 +21,7 @@ const createRequiredReportResolver = async ( await updateAtBrowser({ atId, browserId }, updateParams); - return true; + return { atId, browserId, phase }; }; module.exports = createRequiredReportResolver; From e4e78fa0d1c2866bd0ff6167947a59bb9578501c Mon Sep 17 00:00:00 2001 From: Howard Edwards Date: Tue, 12 Sep 2023 17:15:01 -0500 Subject: [PATCH 23/59] Fix createRequiredReport not being called during edge case condition --- client/components/ManageTestQueue/index.jsx | 76 ++++++++++++++------- 1 file changed, 52 insertions(+), 24 deletions(-) diff --git a/client/components/ManageTestQueue/index.jsx b/client/components/ManageTestQueue/index.jsx index d6d9b439b..6e3dbc545 100644 --- a/client/components/ManageTestQueue/index.jsx +++ b/client/components/ManageTestQueue/index.jsx @@ -357,25 +357,33 @@ const ManageTestQueue = ({ // Section: const runMutationForRequiredReportTable = async mutation => { - let atId = ''; - let browserId = ''; - console.log(atBrowserCombinations) - - atBrowserCombinations.forEach(({ at, browser, phase }) => { - // console.log('AtID', updateAtForButton) - // console.log('at.Id', at.id) - // console.log('BrowserID', updateBrowserForButton) - // console.log('browser.Id', browser.id) - if ( - updateAtForButton === at.id && - updateBrowserForButton === browser.id - // updatePhaseForButton === phase - ) { - atId = at.id; - browserId = browser.id; - // console.log('IT CHECKED OUT'); - } - }); + let atId = updateAtForButton; + let browserId = updateBrowserForButton; + // console.log(atBrowserCombinations); + + // console.log( + // 'ids', + // atId, + // browserId, + // updateAtForButton, + // updateBrowserForButton + // ); + + // atBrowserCombinations.forEach(({ at, browser, phase }) => { + // // console.log('AtID', updateAtForButton) + // // console.log('at.Id', at.id) + // // console.log('BrowserID', updateBrowserForButton) + // // console.log('browser.Id', browser.id) + // if ( + // updateAtForButton === at.id && + // updateBrowserForButton === browser.id + // // updatePhaseForButton === phase + // ) { + // atId = at.id; + // browserId = browser.id; + // // console.log('IT CHECKED OUT'); + // } + // }); mutation === 'createRequiredReport' ? await triggerLoad(async () => { const { data } = await createRequiredReport({ @@ -390,12 +398,32 @@ const ManageTestQueue = ({ const createdRequiredReport = data.requiredReport.createRequiredReport; console.log('atBrowserCombinations', atBrowserCombinations); - console.log('createdRequiredReport', createdRequiredReport); + console.log('createdRequiredReport', createdRequiredReport, { + at: ats.find(at => at.id === atId), + browser: browsers.find(browser => browser.id === atId), + phase: updatePhaseForButton + }); - setAtBrowserCombinations([ - ...atBrowserCombinations, - createdRequiredReport - ]); + // Verify that the created required report was actually created before updating + // the dataset + if (createdRequiredReport) { + // TODO: Sort this so it doesn't pop in at the bottom if it isn't intended + // for there + setAtBrowserCombinations([ + ...atBrowserCombinations, + { + at: ats.find( + at => at.id === createdRequiredReport.atId + ), + browser: browsers.find( + browser => + browser.id === + createdRequiredReport.browserId + ), + phase: updatePhaseForButton + } + ]); + } }, 'Adding Phase requirement to the required reports table') : mutation === 'updateRequiredReport' ? await triggerLoad(async () => { From 5aa85a3d5a43cf0cde2635bb2696fff6baef1f11 Mon Sep 17 00:00:00 2001 From: Paul Clue <67766160+Paul-Clue@users.noreply.github.com> Date: Thu, 14 Sep 2023 16:38:39 -0500 Subject: [PATCH 24/59] update modals and functions for phase requirement --- client/components/ManageTestQueue/index.jsx | 680 ++++++++++++------ client/components/ManageTestQueue/queries.js | 23 +- server/graphql-schema.js | 4 +- .../deleteRequiredReportResolver.js | 2 +- .../updateRequiredReportResolver.js | 2 +- 5 files changed, 481 insertions(+), 230 deletions(-) diff --git a/client/components/ManageTestQueue/index.jsx b/client/components/ManageTestQueue/index.jsx index 6e3dbc545..051d09291 100644 --- a/client/components/ManageTestQueue/index.jsx +++ b/client/components/ManageTestQueue/index.jsx @@ -21,8 +21,8 @@ import { } from '../TestQueue/queries'; import { CREATE_MANAGE_TEST_QUEUE_MUTATION, - UPDATE_MANAGE_TEST_QUEUE_MUTATION - // DELET_MANAGE_TEST_QUEUE_MUTATION, + UPDATE_MANAGE_TEST_QUEUE_MUTATION, + DELETE_MANAGE_TEST_QUEUE_MUTATION } from './queries'; import { gitUpdatedDateToString } from '../../utils/gitUtils'; import { convertStringToDate } from '../../utils/formatter'; @@ -154,6 +154,12 @@ const ManageTestQueue = ({ const [requiredReportsModalTitle, setRequiredReportsModalTitle] = useState(''); + const [isDelete, setIsDelete] = useState(false); + const [actionButtonLabel, setActionButtonLabel] = useState('Save Changes'); + const [updateAtIdForUpdate, setUpdateAtIdForUpdate] = useState(''); + const [updatePhaseForUpdate, setUpdatePhaseForUpdate] = useState(''); + const [updateBrowserIdForUpdate, setUpdateBrowserIdForUpdate] = + useState(''); const [updateAtSelection, setUpdateAtSelection] = useState('Select an At'); const [updateAtForButton, setUpdateAtForButton] = useState(''); const [updateListAtSelection, setUpdateListAtSelection] = @@ -355,98 +361,256 @@ const ManageTestQueue = ({ // ) // ]; + // Section: + + const onOpenShowEditAtBrowserModal = ( + type = 'edit', + phase, + at = '', + browser = '' + ) => { + if (type === 'edit') { + setRequiredReportsModalTitle( +

    + Edit the following AT/Browser pair for{' '} + + {phase} + {' '} + required reports +

    + ); + } + + if (type === 'delete') { + setRequiredReportsModalTitle( +

    + Delete {at} and {browser} pair for{' '} + + {phase} + {' '} + required reports +

    + ); + } + setShowEditAtBrowserModal(false); + }; + // Section: const runMutationForRequiredReportTable = async mutation => { let atId = updateAtForButton; let browserId = updateBrowserForButton; // console.log(atBrowserCombinations); - // console.log( - // 'ids', - // atId, - // browserId, - // updateAtForButton, - // updateBrowserForButton - // ); - - // atBrowserCombinations.forEach(({ at, browser, phase }) => { - // // console.log('AtID', updateAtForButton) - // // console.log('at.Id', at.id) - // // console.log('BrowserID', updateBrowserForButton) - // // console.log('browser.Id', browser.id) - // if ( - // updateAtForButton === at.id && - // updateBrowserForButton === browser.id - // // updatePhaseForButton === phase - // ) { - // atId = at.id; - // browserId = browser.id; - // // console.log('IT CHECKED OUT'); - // } - // }); mutation === 'createRequiredReport' ? await triggerLoad(async () => { - const { data } = await createRequiredReport({ - variables: { - atId: atId, - browserId: browserId, - phase: `IS_${updatePhaseForButton}` - } - }); - console.log('data', data); - - const createdRequiredReport = - data.requiredReport.createRequiredReport; - console.log('atBrowserCombinations', atBrowserCombinations); - console.log('createdRequiredReport', createdRequiredReport, { - at: ats.find(at => at.id === atId), - browser: browsers.find(browser => browser.id === atId), - phase: updatePhaseForButton - }); - - // Verify that the created required report was actually created before updating - // the dataset - if (createdRequiredReport) { - // TODO: Sort this so it doesn't pop in at the bottom if it isn't intended - // for there - setAtBrowserCombinations([ - ...atBrowserCombinations, - { - at: ats.find( - at => at.id === createdRequiredReport.atId - ), - browser: browsers.find( - browser => - browser.id === - createdRequiredReport.browserId - ), - phase: updatePhaseForButton + try { + atBrowserCombinations.forEach( + ({ at, browser, phase }) => { + // console.log('AtID', updateAtForButton) + // console.log('at.Id', at.id) + // console.log('BrowserID', updateBrowserForButton) + // console.log('browser.Id', browser.id) + if ( + updateAtForButton === at.id && + updateBrowserForButton === browser.id && + updatePhaseForButton === phase + ) { + // atId = at.id; + // browserId = browser.id; + // console.log('IT CHECKED OUT'); + throw new Error( + 'A duplicate Entry was detected in the table' + ); + } + } + ); + const { data } = await createRequiredReport({ + variables: { + atId: atId, + browserId: browserId, + phase: `IS_${updatePhaseForButton}` } - ]); + }); + // console.log('data', data); + + const createdRequiredReport = + data.requiredReport.createRequiredReport; + + // Verify that the created required report was actually created before updating + // the dataset + if (createdRequiredReport) { + setAtBrowserCombinations( + [ + ...atBrowserCombinations, + { + at: ats.find( + at => + at.id === + createdRequiredReport.atId + ), + browser: browsers.find( + browser => + browser.id === + createdRequiredReport.browserId + ), + phase: updatePhaseForButton + } + ].sort((a, b) => { + if (a.phase < b.phase) return -1; + if (a.phase > b.phase) return 1; + return a.at.name.localeCompare(b.at.name); + }) + ); + } + } catch (error) { + console.error(error); } }, 'Adding Phase requirement to the required reports table') : mutation === 'updateRequiredReport' ? await triggerLoad(async () => { - await updateRequiredReport({ + // console.log( + // 'Update IDS', + // updateBrowserSelection, + // updateAtSelection + // ); + // console.log( + // 'phase in title', + // requiredReportsModalTitle.props.children[2].props.children + // ); + try { + atBrowserCombinations.forEach( + ({ at, browser, phase }) => { + // console.log('AtID', updateAtForButton) + // console.log('at.Id', at.id) + // console.log('BrowserID', updateBrowserForButton) + // console.log('browser.Id', browser.id) + if ( + updateAtSelection === at.id && + updateBrowserSelection === browser.id && + updatePhaseForUpdate === phase + ) { + // atId = at.id; + // browserId = browser.id; + // console.log('IT CHECKED OUT'); + throw new Error( + 'A duplicate Entry was detected in the table' + ); + } + } + ); + // console.log('updateAt', updateAtIdForUpdate); + // console.log('updateBrowser', updateBrowserIdForUpdate); + + const { data } = await updateRequiredReport({ + variables: { + atId: updateAtIdForUpdate, + browserId: updateBrowserIdForUpdate, + // phase: `IS_${updatePhaseForButton}`, + phase: `IS_${updatePhaseForUpdate}`, + updateAtId: updateAtSelection, + updateBrowserId: updateBrowserSelection + } + }); + // console.log('data', data); + + const updatedRequiredReport = + data.requiredReport.updateRequiredReport; + + // Verify that the created required report was actually created before updating + // the dataset + if (updatedRequiredReport) { + setAtBrowserCombinations( + [ + ...atBrowserCombinations, + { + at: ats.find( + at => + at.id === + updatedRequiredReport.atId + ), + browser: browsers.find( + browser => + browser.id === + updatedRequiredReport.browserId + ), + phase: updatePhaseForUpdate + } + ] + .filter(row => { + if ( + row.at.id === updateAtIdForUpdate && + row.browser.id === + updateBrowserIdForUpdate && + row.phase == updatePhaseForUpdate + ) { + // foundOne80 = true; // Set the flag to true to filter out only the first object with score 80 + return false; // Exclude this object from the filtered result + } + return true; // Keep all other objects + }) + .sort((a, b) => { + if (a.phase < b.phase) return -1; + if (a.phase > b.phase) return 1; + return a.at.name.localeCompare(b.at.name); + }) + ); + } + } catch (error) { + console.error(error); + } + }, 'Adding Phase requirement to the required reports table') + : mutation === 'deleteRequiredReport' + ? await triggerLoad(async () => { + console.log('atId', updateAtIdForUpdate) + console.log('browserId', updateBrowserIdForUpdate) + const { data } = await deleteRequiredReport({ variables: { - atId: atId, - browserId: browserId, - phase: `IS_${updatePhaseForButton}` + atId: updateAtIdForUpdate, + browserId: updateBrowserIdForUpdate, + phase: `IS_${updatePhaseForUpdate}` } }); + // console.log('data', data); + + const deletedRequiredReport = + data.requiredReport.deleteRequiredReport; + + if (deletedRequiredReport) { + setAtBrowserCombinations( + [ + ...atBrowserCombinations + // { + // at: ats.find( + // at => at.id === deletedRequiredReport.atId + // ), + // browser: browsers.find( + // browser => + // browser.id === + // deletedRequiredReport.browserId + // ), + // phase: updatePhaseForUpdate + // } + ] + .filter(row => { + if ( + row.at.id === updateAtIdForUpdate && + row.browser.id === + updateBrowserIdForUpdate && + row.phase == updatePhaseForUpdate + ) { + return false; + } + return true; + }) + .sort((a, b) => { + if (a.phase < b.phase) return -1; + if (a.phase > b.phase) return 1; + return a.at.name.localeCompare(b.at.name); + }) + ); + } }, 'Adding Phase requirement to the required reports table') - : // : mutation === 'deleteRequiredReport' - // ? await triggerLoad(async () => { - // await deleteRequiredReport({ - // variables: { - // atId: atId, - // browserId: browserId, - // phase: `IS_${updatePhaseForButton}` - // } - // }); - // }, 'Adding Phase requirement to the required reports table') - // : - null; + : null; }; const addRequiredReport = async () => { @@ -524,9 +688,9 @@ const ManageTestQueue = ({ const [updateRequiredReport] = useMutation( UPDATE_MANAGE_TEST_QUEUE_MUTATION ); - // const [deleteRequiredReport] = useMutation( - // DELETE_MANAGE_TEST_QUEUE_MUTATION - // ); + const [deleteRequiredReport] = useMutation( + DELETE_MANAGE_TEST_QUEUE_MUTATION + ); const onManageAtsClick = () => setShowManageATs(!showManageATs); const onAddTestPlansClick = () => setShowAddTestPlans(!showAddTestPlans); @@ -865,24 +1029,24 @@ const ManageTestQueue = ({ }; // Find Manage Required Reports Modal - const onOpenShowEditAtBrowserModal = (type = 'edit', phase) => { - if (type === 'edit') { - setRequiredReportsModalTitle( -

    - Edit the following AT/Browser pair for{' '} - - {phase} - {' '} - requied reports -

    - ); - } - - if (type === 'delete') { - setRequiredReportsModalTitle(

    delete this

    ); - } - setShowEditAtBrowserModal(false); - }; + // const onOpenShowEditAtBrowserModal = (type = 'edit', phase) => { + // if (type === 'edit') { + // setRequiredReportsModalTitle( + //

    + // Edit the following AT/Browser pair for{' '} + // + // {phase} + // {' '} + // requied reports + //

    + // ); + // } + + // if (type === 'delete') { + // setRequiredReportsModalTitle(

    delete this

    ); + // } + // setShowEditAtBrowserModal(false); + // }; const handlePhaseChange = e => { const value = e.target.value; @@ -1323,7 +1487,7 @@ const ManageTestQueue = ({ ); } )}{' '} */} - {ats[0].browsers.map(item => { + {ats[2].browsers.map(item => { return ( */} {updateListAtSelection === 'Select an At' ? ( @@ -1348,38 +1160,13 @@ const ManageTestQueue = ({ ); })} - {/* - - */} ) : ( - {/* {Object.entries(ats).map( - ([key, value]) => { - return ( - - ); - } - )} */} - {ats.map(item => { return (
    ]} - onClick={[onManageAtsClick, onAddTestPlansClick]} - expanded={[showManageATs, showAddTestPlans]} + onClick={[ + onManageAtsClick, + onAddTestPlansClick, + onManageReqReportsClick + ]} + expanded={[ + showManageATs, + showAddTestPlans, + showManageReqReports + ]} stacked /> From 9e8a7031bc9695a121488227a9fbe87dac6dc6b3 Mon Sep 17 00:00:00 2001 From: Paul Clue <67766160+Paul-Clue@users.noreply.github.com> Date: Wed, 23 Aug 2023 15:27:24 -0500 Subject: [PATCH 28/59] Start interface in UI for required reports --- client/components/DataManagement/queries.js | 12 +++ client/components/ManageTestQueue/index.jsx | 85 +++++++++++++++++++-- client/components/TestQueue/queries.js | 12 +++ 3 files changed, 104 insertions(+), 5 deletions(-) diff --git a/client/components/DataManagement/queries.js b/client/components/DataManagement/queries.js index b7f4233d7..de2cee1ab 100644 --- a/client/components/DataManagement/queries.js +++ b/client/components/DataManagement/queries.js @@ -15,6 +15,18 @@ export const DATA_MANAGEMENT_PAGE_QUERY = gql` name releasedAt } + browsers { + id + name + } + candidateBrowsers { + id + name + } + recommendedBrowsers { + id + name + } } browsers { id diff --git a/client/components/ManageTestQueue/index.jsx b/client/components/ManageTestQueue/index.jsx index 6770648c4..a5a065b14 100644 --- a/client/components/ManageTestQueue/index.jsx +++ b/client/components/ManageTestQueue/index.jsx @@ -18,6 +18,13 @@ import { convertStringToDate } from '../../utils/formatter'; import { LoadingStatus, useTriggerLoad } from '../common/LoadingStatus'; import DisclosureComponent from '../common/DisclosureComponent'; import AddTestToQueueWithConfirmation from '../AddTestToQueueWithConfirmation'; +import { ThemeTable, ThemeTableHeader } from '../common/ThemeTable'; +import PhasePill from '../common/PhasePill'; + +const TransparentButton = styled.button` + border: none; + background-color: transparent; +`; const DisclosureContainer = styled.div` // Following directives are related to the ManageTestQueue component @@ -143,6 +150,23 @@ const ManageTestQueue = ({ const [selectedAtId, setSelectedAtId] = useState(''); const [selectedBrowserId, setSelectedBrowserId] = useState(''); + const atBrowserCombinations = [ + ...ats.flatMap(at => + at.candidateBrowsers.map(browser => ({ + at, + browser, + phase: 'CANDIDATE' + })) + ), + ...ats.flatMap(at => + at.recommendedBrowsers.map(browser => ({ + at, + browser, + phase: 'RECOMMENDED' + })) + ) + ]; + const [addAtVersion] = useMutation(ADD_AT_VERSION_MUTATION); const [editAtVersion] = useMutation(EDIT_AT_VERSION_MUTATION); const [deleteAtVersion] = useMutation(DELETE_AT_VERSION_MUTATION); @@ -684,11 +708,14 @@ const ManageTestQueue = ({ - New Section + + Add required reports for a specific AT and Browser + pair +
    - New Label + Phase { @@ -706,7 +733,7 @@ const ManageTestQueue = ({ - New Label + Assistive Technology { @@ -724,7 +751,7 @@ const ManageTestQueue = ({ - New Label + Browser { @@ -751,10 +778,58 @@ const ManageTestQueue = ({ // } // onClick={handleAddTestPlanToTestQueue} > - New Button Label + Add Required Reports
    + Required Reports + + + + Phase + AT + Browser + Edit + + + + {atBrowserCombinations.map( + ({ at, browser, phase }) => { + return ( + + + + {phase} + {' '} + + {at.name} + {browser.name} + + + + Edit + + + + Remove + + + + ); + } + )} + +
    ]} onClick={[ diff --git a/client/components/TestQueue/queries.js b/client/components/TestQueue/queries.js index 065c5e929..6aa9cbf75 100644 --- a/client/components/TestQueue/queries.js +++ b/client/components/TestQueue/queries.js @@ -20,6 +20,18 @@ export const TEST_QUEUE_PAGE_QUERY = gql` name releasedAt } + browsers { + id + name + } + candidateBrowsers { + id + name + } + recommendedBrowsers { + id + name + } } browsers { id From c24bb53438145f636ff4e72d6b6871053975c969 Mon Sep 17 00:00:00 2001 From: Paul Clue <67766160+Paul-Clue@users.noreply.github.com> Date: Tue, 5 Sep 2023 14:32:32 -0500 Subject: [PATCH 29/59] Add UI models and resolvers for phase requirements --- client/components/ManageTestQueue/index.jsx | 413 ++++++++++++++++-- client/components/ManageTestQueue/queries.js | 35 ++ client/components/common/PhasePill/index.jsx | 9 +- server/graphql-schema.js | 45 ++ server/models/services/AtBrowserService | 27 ++ server/models/services/helpers.js | 2 + .../createRequiredReportResolver.js | 27 ++ .../deleteRequiredReportResolver.js | 27 ++ .../RequiredReportOperations/index.js | 9 + .../updateRequiredReportResolver.js | 38 ++ server/resolvers/index.js | 4 + .../resolvers/mutateRequiredReportResolver.js | 5 + 12 files changed, 606 insertions(+), 35 deletions(-) create mode 100644 client/components/ManageTestQueue/queries.js create mode 100644 server/models/services/AtBrowserService create mode 100644 server/resolvers/RequiredReportOperations/createRequiredReportResolver.js create mode 100644 server/resolvers/RequiredReportOperations/deleteRequiredReportResolver.js create mode 100644 server/resolvers/RequiredReportOperations/index.js create mode 100644 server/resolvers/RequiredReportOperations/updateRequiredReportResolver.js create mode 100644 server/resolvers/mutateRequiredReportResolver.js diff --git a/client/components/ManageTestQueue/index.jsx b/client/components/ManageTestQueue/index.jsx index a5a065b14..9342e6829 100644 --- a/client/components/ManageTestQueue/index.jsx +++ b/client/components/ManageTestQueue/index.jsx @@ -1,6 +1,6 @@ import React, { useEffect, useState, useRef } from 'react'; import { useMutation } from '@apollo/client'; -import { Form } from 'react-bootstrap'; +import { Button, Form } from 'react-bootstrap'; import styled from '@emotion/styled'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { faEdit, faTrashAlt } from '@fortawesome/free-solid-svg-icons'; @@ -21,6 +21,31 @@ import AddTestToQueueWithConfirmation from '../AddTestToQueueWithConfirmation'; import { ThemeTable, ThemeTableHeader } from '../common/ThemeTable'; import PhasePill from '../common/PhasePill'; +const ModalInnerSectionContainer = styled.div` + display: flex; + flex-direction: column; +`; + +const Row = styled.div` + display: grid; + grid-template-columns: 1fr 1fr; + grid-gap: 1rem; +`; + +const ModalSubtitleStyle = styled.h2` + font-size: 0.8em; + margin: 0; + padding: 0; +`; + +const Required = styled.span` + color: #ce1b4c; + + :after { + content: '*'; + } +`; + const TransparentButton = styled.button` border: none; background-color: transparent; @@ -111,6 +136,19 @@ const ManageTestQueue = ({ const editAtVersionButtonRef = useRef(); const deleteAtVersionButtonRef = useRef(); + // Find Manage Required Reports Modal + const [showEditAtBrowserModal, setShowEditAtBrowserModal] = useState(true); + const [requiredReportsModalAt, setRequiredReportsModalAt] = useState(''); + const [requiredReportsModalTitle, setRequiredReportsModalTitle] = + useState(''); + + const [updateAtSelection, setUpdateAtSelection] = useState('Select an At'); + const [updateListAtSelection, setUpdateListAtSelection] = + useState('Select an At'); + const [updateBrowserSelection, setUpdateBrowserSelection] = + useState('Select a Browser'); + const [updateListBrowserSelection, setUpdateListBrowserSelection] = + useState('Select a Browser'); const [showManageATs, setShowManageATs] = useState(false); const [showAddTestPlans, setShowAddTestPlans] = useState(false); const [showManageReqReports, setShowManageReqReports] = useState(false); @@ -495,6 +533,50 @@ const ManageTestQueue = ({ setShowThemedModal(true); }; + // Find Manage Required Reports Modal + const onOpenShowEditAtBrowserModal = (type = 'edit', phase) => { + if (type === 'edit') { + setRequiredReportsModalTitle( +

    + Edit the following AT/Browser pair for{' '} + + {phase} + {' '} + requied reports +

    + ); + } + + if (type === 'delete') { + setRequiredReportsModalTitle(

    delete this

    ); + } + setShowEditAtBrowserModal(false); + }; + + const handleShowEditAtBrowserModal = () => { + setShowEditAtBrowserModal(false); + }; + + const handleAtChange = e => { + const value = e.target.value; + setUpdateAtSelection(value); + }; + + const handleBrowserChange = e => { + const value = e.target.value; + setUpdateBrowserSelection(value); + }; + + const handleListAtChange = e => { + const value = e.target.value; + setUpdateListAtSelection(value); + }; + + const handleListBrowserChange = e => { + const value = e.target.value; + setUpdateListBrowserSelection(value); + }; + return ( Phase - { - // const { value } = e.target; - // updateMatchingTestPlanVersions( - // value, - // allTestPlanVersions - // ); - }} - > - - + {/* {updateListAtSelection === 'Select an At' ? ( */} + {updateListAtSelection === 'Select an At' ? ( + + + + + + + ) : ( + + {Object.entries(ats).map( + ([key, value]) => { + return ( + + ); + }, + {} + )} + + )} Assistive Technology - { - // const { value } = e.target; - // updateMatchingTestPlanVersions( - // value, - // allTestPlanVersions - // ); - }} - > - - + {updateListAtSelection === 'Select an At' ? ( + + + + + + + ) : ( + + + + )} @@ -776,7 +943,7 @@ const ManageTestQueue = ({ // !selectedAtId || // !selectedBrowserId // } - // onClick={handleAddTestPlanToTestQueue} + // onClick={handleShowEditAtBrowserModal} > Add Required Reports @@ -813,15 +980,36 @@ const ManageTestQueue = ({ { + setRequiredReportsModalAt( + phase + ); + onOpenShowEditAtBrowserModal( + 'edit', + phase + ); + }} /> - Edit + + Edit + { + onOpenShowEditAtBrowserModal( + 'delete' + ); + }} /> - Remove + + Remove + @@ -844,7 +1032,6 @@ const ManageTestQueue = ({ ]} stacked /> - {showAtVersionModal && ( )} - {showThemedModal && ( )} - {showFeedbackModal && ( )} + {/* {!showEditAtBrowserModal && */} + {!showEditAtBrowserModal && ( + + + + + Assistive Technology + + + {updateAtSelection === 'Select an At' ? ( + + + + + + + ) : ( + + {Object.entries(ats).map( + ([key, value]) => { + return ( + + ); + }, + {} + )} + + )} + + + + Browser + {/* */} + + + {updateAtSelection === 'Select an At' ? ( + + ) : updateAtSelection === 'JAWS' ? ( + + {' '} + {Object.entries( + ats[0].browsers + ).map(([key, value]) => { + return ( + + ); + })}{' '} + + ) : updateAtSelection === 'NVDA' ? ( + + {Object.entries( + ats[1].browsers + ).map(([key, value]) => { + return ( + + ); + })}{' '} + + ) : updateAtSelection === + 'VoiceOver for macOS' ? ( + + {Object.entries( + ats[2].browsers + ).map(([key, value]) => { + return ( + + ); + })}{' '} + + ) : null} + + + + } + actionLabel={'Save Changes'} + handleAction={ + // updatedAtVersion !== atVersion || + // updatedBrowserVersion !== browserVersion + // ? onSubmit + // : handleClose + () => {} + } + handleClose={() => { + setUpdateAtSelection('Select an At'); + setShowEditAtBrowserModal(true); + }} + handleHide={() => { + setUpdateAtSelection('Select an At'); + setShowEditAtBrowserModal(true); + }} + staticBackdrop={true} + /> + )} ); }; diff --git a/client/components/ManageTestQueue/queries.js b/client/components/ManageTestQueue/queries.js new file mode 100644 index 000000000..4fed4df53 --- /dev/null +++ b/client/components/ManageTestQueue/queries.js @@ -0,0 +1,35 @@ +import { gql } from '@apollo/client'; + +export const ADD_TEST_QUEUE_MUTATION = gql` + mutation AddTestPlanReport( + $testPlanVersionId: ID! + $atId: ID! + $browserId: ID! + ) { + findOrCreateTestPlanReport( + input: { + testPlanVersionId: $testPlanVersionId + atId: $atId + browserId: $browserId + } + ) { + populatedData { + testPlanReport { + id + at { + id + } + browser { + id + } + } + testPlanVersion { + id + } + } + created { + locationOfData + } + } + } +`; diff --git a/client/components/common/PhasePill/index.jsx b/client/components/common/PhasePill/index.jsx index 78e29be05..66ba0db2b 100644 --- a/client/components/common/PhasePill/index.jsx +++ b/client/components/common/PhasePill/index.jsx @@ -24,6 +24,9 @@ const PhaseText = styled.span` top: -1px; margin-right: 5px; } + &.for-header { + border-radius: 20px; + } &.rd { background: #4177de; @@ -46,10 +49,12 @@ const PhaseText = styled.span` } `; -const PhasePill = ({ fullWidth = true, children: phase }) => { +const PhasePill = ({ fullWidth = true, forHeader = false, children: phase }) => { + let classes = fullWidth ? 'full-width' : ''; + classes = forHeader ? `${classes} for-header` : classes; return ( str) .join(' ')} > diff --git a/server/graphql-schema.js b/server/graphql-schema.js index e750cc2e8..f0dced1a0 100644 --- a/server/graphql-schema.js +++ b/server/graphql-schema.js @@ -204,6 +204,21 @@ const graphqlSchema = gql` releasedAt: Timestamp } + # """ + # The fields on the RequiredReportOperations type which can be used or update the + # RequiredReports. + # """ + # input RequiredReportOperationsInput { + # """ + # See AtVersion type for more information. + # """ + # inputAtId: ID! + # """ + # See AtVersion type for more information. + # """ + # inputBrowserId: ID! + # } + """ A suite of tests which keeps its identity as it evolves over time. """ @@ -1062,6 +1077,29 @@ const graphqlSchema = gql` findOrCreateAtVersion(input: AtVersionInput!): AtVersion! } + """ + """ + enum RequiredReportPhase { + IS_CANDIDATE + IS_RECOMMENDED + } + + # type RequiredReportOperations { + # """ + # """ + # createRequiredReport: Boolean! + # updateRequiredReport(atId: ID!, browserId: ID!): Boolean! + # deleteRequiredReport: Boolean! + # } + + type RequiredReportOperations { + """ + """ + createRequiredReport: Boolean! + updateRequiredReport(atId: ID!, browserId: ID!): Boolean! + deleteRequiredReport: Boolean! + } + """ Mutations scoped to an existing AtVersion. """ @@ -1249,6 +1287,13 @@ const graphqlSchema = gql` """ browser(id: ID!): BrowserOperations! """ + """ + requiredReport( + atId: ID! + browserId: ID! + phase: RequiredReportPhase! + ): RequiredReportOperations! + """ Adds a report with the given TestPlanVersion, AT and Browser, and a state of "DRAFT", resulting in the report appearing in the Test Queue. In the case an identical report already exists, it will be returned diff --git a/server/models/services/AtBrowserService b/server/models/services/AtBrowserService new file mode 100644 index 000000000..6a3a75216 --- /dev/null +++ b/server/models/services/AtBrowserService @@ -0,0 +1,27 @@ +const ModelService = require('./ModelService'); +const { AtBrowsers } = require('../'); +const { AT_BROWSERS_ATTRIBUTES } = require('./helpers'); + +const updateAtBrowser = async ( + { atId, browserId }, + updateParams = {}, + atBrowsersAttributes = AT_BROWSERS_ATTRIBUTES, + options = {} +) => { + await ModelService.update( + AtBrowsers, + { atId, browserId }, + updateParams, + options + ); + + return await ModelService.getByQuery( + AtBrowsers, + { atId, browserId }, + atBrowsersAttributes, + null, + options + ); +}; + +module.exports = { updateAtBrowser }; diff --git a/server/models/services/helpers.js b/server/models/services/helpers.js index 577e76120..8115844cf 100644 --- a/server/models/services/helpers.js +++ b/server/models/services/helpers.js @@ -1,5 +1,6 @@ const { At, + AtBrowsers, AtMode, AtVersion, Browser, @@ -28,6 +29,7 @@ const getSequelizeModelAttributes = model => { module.exports = { getSequelizeModelAttributes, AT_ATTRIBUTES: getSequelizeModelAttributes(At), + AT_BROWSERS_ATTRIBUTES: getSequelizeModelAttributes(AtBrowsers), AT_MODE_ATTRIBUTES: getSequelizeModelAttributes(AtMode), AT_VERSION_ATTRIBUTES: getSequelizeModelAttributes(AtVersion), BROWSER_ATTRIBUTES: getSequelizeModelAttributes(Browser), diff --git a/server/resolvers/RequiredReportOperations/createRequiredReportResolver.js b/server/resolvers/RequiredReportOperations/createRequiredReportResolver.js new file mode 100644 index 000000000..435e57d23 --- /dev/null +++ b/server/resolvers/RequiredReportOperations/createRequiredReportResolver.js @@ -0,0 +1,27 @@ +const { AuthenticationError } = require('apollo-server'); +const { updateAtBrowser } = require('../../models/services/AtBrowserService'); + +const createRequiredReportResolver = async ( + { parentContext: { atId, browserId, phase } }, + _, + { user } +) => { + if (!user?.roles.find(role => role.name === 'ADMIN')) { + throw new AuthenticationError(); + } + + let updateParams = {}; + + if (phase === 'IS_CANDIDATE') { + updateParams = { isCandidate: true }; + } + if (phase === 'IS_RECOMMENDED') { + updateParams = { isRecommended: true }; + } + + await updateAtBrowser({ atId, browserId }, updateParams); + + return true; +}; + +module.exports = createRequiredReportResolver; diff --git a/server/resolvers/RequiredReportOperations/deleteRequiredReportResolver.js b/server/resolvers/RequiredReportOperations/deleteRequiredReportResolver.js new file mode 100644 index 000000000..6d4cf39a2 --- /dev/null +++ b/server/resolvers/RequiredReportOperations/deleteRequiredReportResolver.js @@ -0,0 +1,27 @@ +const { AuthenticationError } = require('apollo-server'); +const { updateAtBrowser } = require('../../models/services/AtBrowserService'); + +const deleteRequiredReportResolver = async ( + { parentContext: { atId, browserId, phase } }, + _, + { user } +) => { + if (!user?.roles.find(role => role.name === 'ADMIN')) { + throw new AuthenticationError(); + } + + let updateParams = {}; + + if (phase === 'IS_CANDIDATE') { + updateParams = { isCandidate: false }; + } + if (phase === 'IS_RECOMMENDED') { + updateParams = { isRecommended: false }; + } + + await updateAtBrowser({ atId, browserId }, updateParams); + + return true; +}; + +module.exports = deleteRequiredReportResolver; diff --git a/server/resolvers/RequiredReportOperations/index.js b/server/resolvers/RequiredReportOperations/index.js new file mode 100644 index 000000000..e17558d01 --- /dev/null +++ b/server/resolvers/RequiredReportOperations/index.js @@ -0,0 +1,9 @@ +const createRequiredReport = require('./createRequiredReportResolver'); +const updateRequiredReport = require('./updateRequiredReportResolver'); +const deleteRequiredReport = require('./deleteRequiredReportResolver'); + +module.exports = { + createRequiredReport, + updateRequiredReport, + deleteRequiredReport +}; diff --git a/server/resolvers/RequiredReportOperations/updateRequiredReportResolver.js b/server/resolvers/RequiredReportOperations/updateRequiredReportResolver.js new file mode 100644 index 000000000..b5a9968e6 --- /dev/null +++ b/server/resolvers/RequiredReportOperations/updateRequiredReportResolver.js @@ -0,0 +1,38 @@ +const { AuthenticationError } = require('apollo-server'); +const { updateAtBrowser } = require('../../models/services/AtBrowserService'); + +const updateRequiredReportResolver = async ( + { parentContext: { atId, browserId, phase } }, + { atId: inputAtId, browserId: inputBrowserId }, + // input, + { user } +) => { + if (!user?.roles.find(role => role.name === 'ADMIN')) { + throw new AuthenticationError(); + } + + let updateParams = {}; + + if (phase === 'IS_CANDIDATE') { + updateParams = { isCandidate: false }; + await updateAtBrowser({ atId, browserId }, updateParams); + updateParams = { isCandidate: true }; + await updateAtBrowser( + { atId: inputAtId, browserId: inputBrowserId }, + updateParams + ); + } + if (phase === 'IS_RECOMMENDED') { + updateParams = { isRecommended: false }; + await updateAtBrowser({ atId, browserId }, updateParams); + updateParams = { isRecommended: true }; + await updateAtBrowser( + { atId: inputAtId, browserId: inputBrowserId }, + updateParams + ); + } + + return true; +}; + +module.exports = updateRequiredReportResolver; diff --git a/server/resolvers/index.js b/server/resolvers/index.js index 09644a406..711893c32 100644 --- a/server/resolvers/index.js +++ b/server/resolvers/index.js @@ -14,6 +14,7 @@ const addViewer = require('./addViewerResolver'); const mutateAt = require('./mutateAtResolver'); const mutateAtVersion = require('./mutateAtVersionResolver'); const mutateBrowser = require('./mutateBrowserResolver'); +const mutateRequiredReport = require('./mutateRequiredReportResolver'); const mutateTestPlanReport = require('./mutateTestPlanReportResolver'); const mutateTestPlanRun = require('./mutateTestPlanRunResolver'); const mutateTestResult = require('./mutateTestResultResolver'); @@ -24,6 +25,7 @@ const User = require('./User'); const AtOperations = require('./AtOperations'); const AtVersionOperations = require('./AtVersionOperations'); const BrowserOperations = require('./BrowserOperations'); +const RequiredReportOperations = require('./RequiredReportOperations'); const TestPlanVersion = require('./TestPlanVersion'); const TestPlanReport = require('./TestPlanReport'); const TestPlanReportOperations = require('./TestPlanReportOperations'); @@ -53,6 +55,7 @@ const resolvers = { at: mutateAt, atVersion: mutateAtVersion, browser: mutateBrowser, + requiredReport: mutateRequiredReport, testPlanReport: mutateTestPlanReport, testPlanRun: mutateTestPlanRun, testResult: mutateTestResult, @@ -64,6 +67,7 @@ const resolvers = { AtOperations, AtVersionOperations, BrowserOperations, + RequiredReportOperations, User, TestPlanVersion, TestPlanReport, diff --git a/server/resolvers/mutateRequiredReportResolver.js b/server/resolvers/mutateRequiredReportResolver.js new file mode 100644 index 000000000..83ce4c83e --- /dev/null +++ b/server/resolvers/mutateRequiredReportResolver.js @@ -0,0 +1,5 @@ +const mutateRequiredReportResolver = (_, { atId, browserId, phase }) => { + return { parentContext: { atId, browserId, phase } }; +}; + +module.exports = mutateRequiredReportResolver; From f03e87d9caa080c68a6423e80f0c90745c07ec1f Mon Sep 17 00:00:00 2001 From: Paul Clue <67766160+Paul-Clue@users.noreply.github.com> Date: Mon, 18 Sep 2023 15:30:40 -0500 Subject: [PATCH 30/59] Delete commented code --- client/components/ManageTestQueue/index.jsx | 1 - server/graphql-schema.js | 23 ------------------- .../updateRequiredReportResolver.js | 1 - server/tests/integration/graphql.test.js | 3 --- 4 files changed, 28 deletions(-) diff --git a/client/components/ManageTestQueue/index.jsx b/client/components/ManageTestQueue/index.jsx index ce78b4b3c..7dc892144 100644 --- a/client/components/ManageTestQueue/index.jsx +++ b/client/components/ManageTestQueue/index.jsx @@ -133,7 +133,6 @@ const ManageTestQueue = ({ // Find Manage Required Reports Modal const [showEditAtBrowserModal, setShowEditAtBrowserModal] = useState(true); - // const [requiredReportsModalAt, setRequiredReportsModalAt] = useState(''); const [requiredReportsModalTitle, setRequiredReportsModalTitle] = useState(''); diff --git a/server/graphql-schema.js b/server/graphql-schema.js index 24d301798..d2cb77443 100644 --- a/server/graphql-schema.js +++ b/server/graphql-schema.js @@ -215,21 +215,6 @@ const graphqlSchema = gql` releasedAt: Timestamp } - # """ - # The fields on the RequiredReportOperations type which can be used or update the - # RequiredReports. - # """ - # input RequiredReportOperationsInput { - # """ - # See AtVersion type for more information. - # """ - # inputAtId: ID! - # """ - # See AtVersion type for more information. - # """ - # inputBrowserId: ID! - # } - """ A suite of tests which keeps its identity as it evolves over time. """ @@ -1072,14 +1057,6 @@ const graphqlSchema = gql` IS_RECOMMENDED } - # type RequiredReportOperations { - # """ - # """ - # createRequiredReport: Boolean! - # updateRequiredReport(atId: ID!, browserId: ID!): Boolean! - # deleteRequiredReport: Boolean! - # } - type RequiredReportOperations { """ """ diff --git a/server/resolvers/RequiredReportOperations/updateRequiredReportResolver.js b/server/resolvers/RequiredReportOperations/updateRequiredReportResolver.js index c68a49973..0a0f07229 100644 --- a/server/resolvers/RequiredReportOperations/updateRequiredReportResolver.js +++ b/server/resolvers/RequiredReportOperations/updateRequiredReportResolver.js @@ -4,7 +4,6 @@ const { updateAtBrowser } = require('../../models/services/AtBrowserService'); const updateRequiredReportResolver = async ( { parentContext: { atId, browserId, phase } }, { atId: inputAtId, browserId: inputBrowserId }, - // input, { user } ) => { if (!user?.roles.find(role => role.name === 'ADMIN')) { diff --git a/server/tests/integration/graphql.test.js b/server/tests/integration/graphql.test.js index 0e80ffe73..d711e7389 100644 --- a/server/tests/integration/graphql.test.js +++ b/server/tests/integration/graphql.test.js @@ -137,8 +137,6 @@ describe('graphql', () => { const excludedTypeNames = [ // Items formatted like this: // 'TestResult' - // 'RequiredReport', - // 'RequiredReportOperations', 'Issue', 'Vendor' ]; @@ -498,7 +496,6 @@ describe('graphql', () => { } ` ); - // console.info(queryResult); await dbCleaner(async () => { const { From 0c4b432610eaa3f0a89c058703c74dbe93a8f5c1 Mon Sep 17 00:00:00 2001 From: Paul Clue <67766160+Paul-Clue@users.noreply.github.com> Date: Tue, 12 Sep 2023 16:46:52 -0500 Subject: [PATCH 31/59] Update required report table --- client/components/ManageTestQueue/index.jsx | 586 +++++++++++++++--- client/components/ManageTestQueue/queries.js | 34 + client/components/TestQueue/TestQueue.css | 13 + server/graphql-schema.js | 13 +- .../createRequiredReportResolver.js | 2 +- 5 files changed, 551 insertions(+), 97 deletions(-) diff --git a/client/components/ManageTestQueue/index.jsx b/client/components/ManageTestQueue/index.jsx index 9342e6829..bf2dda841 100644 --- a/client/components/ManageTestQueue/index.jsx +++ b/client/components/ManageTestQueue/index.jsx @@ -1,9 +1,15 @@ import React, { useEffect, useState, useRef } from 'react'; import { useMutation } from '@apollo/client'; -import { Button, Form } from 'react-bootstrap'; +import { Button, Form, Dropdown } from 'react-bootstrap'; import styled from '@emotion/styled'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { faEdit, faTrashAlt } from '@fortawesome/free-solid-svg-icons'; +import { + faEdit, + faTrashAlt, + faUserPlus, + faCheck, + faChevronDown +} from '@fortawesome/free-solid-svg-icons'; import PropTypes from 'prop-types'; import BasicModal from '../common/BasicModal'; import UpdateVersionModal from '../common/UpdateVersionModal'; @@ -13,6 +19,11 @@ import { EDIT_AT_VERSION_MUTATION, DELETE_AT_VERSION_MUTATION } from '../TestQueue/queries'; +import { + CREATE_MANAGE_TEST_QUEUE_MUTATION, + UPDATE_MANAGE_TEST_QUEUE_MUTATION + // DELET_MANAGE_TEST_QUEUE_MUTATION, +} from './queries'; import { gitUpdatedDateToString } from '../../utils/gitUtils'; import { convertStringToDate } from '../../utils/formatter'; import { LoadingStatus, useTriggerLoad } from '../common/LoadingStatus'; @@ -20,6 +31,7 @@ import DisclosureComponent from '../common/DisclosureComponent'; import AddTestToQueueWithConfirmation from '../AddTestToQueueWithConfirmation'; import { ThemeTable, ThemeTableHeader } from '../common/ThemeTable'; import PhasePill from '../common/PhasePill'; +import { at } from 'lodash'; const ModalInnerSectionContainer = styled.div` display: flex; @@ -143,12 +155,18 @@ const ManageTestQueue = ({ useState(''); const [updateAtSelection, setUpdateAtSelection] = useState('Select an At'); + const [updateAtForButton, setUpdateAtForButton] = useState(''); const [updateListAtSelection, setUpdateListAtSelection] = useState('Select an At'); const [updateBrowserSelection, setUpdateBrowserSelection] = useState('Select a Browser'); + const [updateBrowserForButton, setUpdateBrowserForButton] = + useState('Select a Browser'); const [updateListBrowserSelection, setUpdateListBrowserSelection] = useState('Select a Browser'); + const [updatePhaseSelection, setUpdatePhaseSelection] = + useState('Phase Selection'); + const [updatePhaseForButton, setUpdatePhaseForButton] = useState(''); const [showManageATs, setShowManageATs] = useState(false); const [showAddTestPlans, setShowAddTestPlans] = useState(false); const [showManageReqReports, setShowManageReqReports] = useState(false); @@ -188,7 +206,7 @@ const ManageTestQueue = ({ const [selectedAtId, setSelectedAtId] = useState(''); const [selectedBrowserId, setSelectedBrowserId] = useState(''); - const atBrowserCombinations = [ + const [atBrowserCombinations, setAtBrowserCombinations] = useState([ ...ats.flatMap(at => at.candidateBrowsers.map(browser => ({ at, @@ -203,11 +221,284 @@ const ManageTestQueue = ({ phase: 'RECOMMENDED' })) ) - ]; + ]); + + const toggleDivStyle = { + backgroundColor: 'transparent', + width: '100%', + height: '38px', + textAlign: 'center' + }; + + const togglePStyle = { + border: '1px solid #ced4da', + borderRadius: '0.375rem', + backgroundColor: '#fff', + padding: '2px', + width: '100%', + height: '38px', + cursor: 'default', + display: 'inline-block' + }; + + const toggleSpanStyle = { + float: 'left', + marginTop: '2px', + marginLeft: '20px', + backgroundColor: + updatePhaseSelection === 'Phase Selection' + ? '#fff' + : updatePhaseSelection === 'Candidate' + ? '#ff6c00' + : updatePhaseSelection === 'Recommended' + ? '#8441de' + : 'black', + borderRadius: '14px', + padding: '2px 15px', + fontSize: '1rem', + fontWeight: '400', + color: updatePhaseSelection === 'Phase Selection' ? 'black' : '#fff' + }; + + const CustomToggle = React.forwardRef(({ children, onClick }, ref) => ( +
    { + e.preventDefault(); + onClick(e); + }} + > +

    { + e.preventDefault(); + onClick(e); + }} + > + {children} + + + +

    +
    + )); + + const setPhase = phase => { + setUpdatePhaseSelection(phase); + if (phase === 'Candidate') { + setUpdatePhaseForButton('CANDIDATE'); + } + if (phase === 'Recommended') { + setUpdatePhaseForButton('RECOMMENDED'); + } + }; + const CustomMenu = React.forwardRef( + ( + { + children, + style, + onClick, + className, + 'aria-labelledby': labeledBy + }, + ref + ) => { + const [value, setValue] = useState(''); + + return ( +
    +
      + {React.Children.toArray(children).filter( + child => + !value || + child.props.children + .toLowerCase() + .startsWith(value) + )} +
    +
    + ); + } + ); + + // let atBrowserCombinations = [ + // ...ats.flatMap(at => + // at.candidateBrowsers.map(browser => ({ + // at, + // browser, + // phase: 'CANDIDATE' + // })) + // ), + // ...ats.flatMap(at => + // at.recommendedBrowsers.map(browser => ({ + // at, + // browser, + // phase: 'RECOMMENDED' + // })) + // ) + // ]; + + // Section: + const runMutationForRequiredReportTable = async mutation => { + let atId = ''; + let browserId = ''; + console.log(atBrowserCombinations) + + atBrowserCombinations.forEach(({ at, browser, phase }) => { + // console.log('AtID', updateAtForButton) + // console.log('at.Id', at.id) + // console.log('BrowserID', updateBrowserForButton) + // console.log('browser.Id', browser.id) + if ( + updateAtForButton === at.id && + updateBrowserForButton === browser.id + // updatePhaseForButton === phase + ) { + atId = at.id; + browserId = browser.id; + // console.log('IT CHECKED OUT'); + } + }); + mutation === 'createRequiredReport' + ? await triggerLoad(async () => { + const { data } = await createRequiredReport({ + variables: { + atId: atId, + browserId: browserId, + phase: `IS_${updatePhaseForButton}` + } + }); + console.log('data', data); + + const createdRequiredReport = + data.requiredReport.createRequiredReport; + console.log('atBrowserCombinations', atBrowserCombinations); + console.log('createdRequiredReport', createdRequiredReport); + + setAtBrowserCombinations([ + ...atBrowserCombinations, + createdRequiredReport + ]); + }, 'Adding Phase requirement to the required reports table') + : mutation === 'updateRequiredReport' + ? await triggerLoad(async () => { + await updateRequiredReport({ + variables: { + atId: atId, + browserId: browserId, + phase: `IS_${updatePhaseForButton}` + } + }); + }, 'Adding Phase requirement to the required reports table') + : // : mutation === 'deleteRequiredReport' + // ? await triggerLoad(async () => { + // await deleteRequiredReport({ + // variables: { + // atId: atId, + // browserId: browserId, + // phase: `IS_${updatePhaseForButton}` + // } + // }); + // }, 'Adding Phase requirement to the required reports table') + // : + null; + }; + + const addRequiredReport = async () => { + // console.log('The at', updateAtForButton); + // console.log('The browser', updateBrowserForButton); + // console.log('The phase', updatePhaseForButton); + // let atId = ''; + // let browserId = ''; + + // atBrowserCombinations.forEach(({ at, browser, phase }) => { + // // console.log('The at', at.id); + // // console.log('The at Update', updateAtForButton); + // // console.log('The browser', browser.id); + // // console.log('The browser Update', updateBrowserForButton); + // // console.log('The phase', phase); + // // console.log('The phase Update', updatePhaseForButton); + // if ( + // updateAtForButton === at.id && + // updateBrowserForButton === browser.id + // // updatePhaseForButton === phase + // ) { + // atId = at.id; + // browserId = browser.id; + // // console.log('IT CHECKED OUT'); + // } + // }); + await triggerLoad(async () => { + await createRequiredReport({ + variables: { + atId: atId, + browserId: browserId, + phase: `IS_${updatePhaseForButton}` + } + }); + }, 'Adding Phase requirement to the required reports table'); + // setShowConfirmation(true); + }; + + // const changeRequiredReport = async () => { + // atBrowserCombinations.forEach(({ at, browser, phase }) => { + // // console.log('The at', at.id); + // // console.log('The at Update', updateAtForButton); + // // console.log('The browser', browser.id); + // // console.log('The browser Update', updateBrowserForButton); + // // console.log('The phase', phase); + // // console.log('The phase Update', updatePhaseForButton); + // if ( + // updateAtForButton === at.id && + // updateBrowserForButton === browser.id + // // updatePhaseForButton === phase + // ) { + // atId = at.id; + // browserId = browser.id; + // // console.log('IT CHECKED OUT'); + // } + // }); + // await triggerLoad(async () => { + // await createRequiredReport({ + // variables: { + // atId: atId, + // browserId: browserId, + // phase: `IS_${updatePhaseForButton}` + // } + // }); + // }, 'Updating the required reports table'); + // }; + // addRequiredReport(); const [addAtVersion] = useMutation(ADD_AT_VERSION_MUTATION); const [editAtVersion] = useMutation(EDIT_AT_VERSION_MUTATION); const [deleteAtVersion] = useMutation(DELETE_AT_VERSION_MUTATION); + const [createRequiredReport] = useMutation( + CREATE_MANAGE_TEST_QUEUE_MUTATION + ); + const [updateRequiredReport] = useMutation( + UPDATE_MANAGE_TEST_QUEUE_MUTATION + ); + // const [deleteRequiredReport] = useMutation( + // DELETE_MANAGE_TEST_QUEUE_MUTATION + // ); const onManageAtsClick = () => setShowManageATs(!showManageATs); const onAddTestPlansClick = () => setShowAddTestPlans(!showAddTestPlans); @@ -520,6 +811,23 @@ const ManageTestQueue = ({ } }; + // IMPORTED PHASE FUNCTION DEFINITIONS + // const addPhaseRequirementToTable = async () => { + // await triggerLoad(async () => { + // await createRequiredReport({ + // variables: { + // atId: at.id, + // browserId: browser.id, + // phase: at.phase + // } + // }); + // }, 'Adding Phase requirement to the required reports table'); + // // setShowConfirmation(true); + // }; + + // DELET_MANAGE_TEST_QUEUE_MUTATION, + // UPDATE_MANAGE_TEST_QUEUE_MUTATION + const showFeedbackMessage = (title, content) => { setFeedbackModalTitle(title); setFeedbackModalContent(content); @@ -553,8 +861,9 @@ const ManageTestQueue = ({ setShowEditAtBrowserModal(false); }; - const handleShowEditAtBrowserModal = () => { - setShowEditAtBrowserModal(false); + const handlePhaseChange = e => { + const value = e.target.value; + setUpdatePhaseSelection(value); }; const handleAtChange = e => { @@ -570,11 +879,17 @@ const ManageTestQueue = ({ const handleListAtChange = e => { const value = e.target.value; setUpdateListAtSelection(value); + setUpdateAtForButton(value); + // console.log('value for at change', value); + // console.log('updateAtForButton', updateAtForButton); }; const handleListBrowserChange = e => { const value = e.target.value; setUpdateListBrowserSelection(value); + setUpdateBrowserForButton(value); + // console.log('value for browser change', value); + // console.log('updateBrowserForButton', updateBrowserForButton); }; return ( @@ -794,57 +1109,86 @@ const ManageTestQueue = ({ Add required reports for a specific AT and Browser pair + {/* section: */}
    Phase - {/* {updateListAtSelection === 'Select an At' ? ( */} + + + {updatePhaseSelection} + + + + {/* */} + + setPhase('Candidate') + } + > + Candidate + + + setPhase('Recommended') + } + > + Recommended + + + + + + + Assistive Technology + {updateListAtSelection === 'Select an At' ? ( - - + {ats.map(item => { + return ( + + ); + })} + {/* + + VoiceOver for macOs + */} ) : ( - {Object.entries(ats).map( + {/* {Object.entries(ats).map( ([key, value]) => { return ( + ); + })} )} - Assistive Technology + Browser {updateListAtSelection === 'Select an At' ? ( + ) : updateListAtSelection === '1' ? ( + - - - - + {' '} + + {/* {Object.entries(ats[0].browsers).map( + ([key, value]) => { + return ( + + ); + } + )}{' '} */} + {ats[0].browsers.map(item => { + return ( + + ); + })} - ) : ( + ) : updateListAtSelection === '2' ? ( - + + {/* {Object.entries(ats[1].browsers).map( + ([key, value]) => { + return ( + + ); + } + )}{' '} */} + {ats[1].browsers.map(item => { + return ( + + ); + })} - )} - - - - Browser - - { - // const { value } = e.target; - // updateMatchingTestPlanVersions( - // value, - // allTestPlanVersions - // ); - }} - > - - + ) : updateListAtSelection === '3' ? ( + + + {/* {Object.entries(ats[2].browsers).map( + ([key, value]) => { + return ( + + ); + } + )}{' '} */} + {ats[0].browsers.map(item => { + return ( + + ); + })} + + ) : null} @@ -980,9 +1376,6 @@ const ManageTestQueue = ({ { setRequiredReportsModalAt( phase @@ -1222,12 +1615,15 @@ const ManageTestQueue = ({ } actionLabel={'Save Changes'} + //section: handleAction={ // updatedAtVersion !== atVersion || // updatedBrowserVersion !== browserVersion // ? onSubmit // : handleClose - () => {} + () => { + console.log('IT RAN'); + } } handleClose={() => { setUpdateAtSelection('Select an At'); diff --git a/client/components/ManageTestQueue/queries.js b/client/components/ManageTestQueue/queries.js index 4fed4df53..896e504c9 100644 --- a/client/components/ManageTestQueue/queries.js +++ b/client/components/ManageTestQueue/queries.js @@ -1,5 +1,39 @@ import { gql } from '@apollo/client'; +export const CREATE_MANAGE_TEST_QUEUE_MUTATION = gql` + mutation CreateRequiredReport( + $atId: ID! + $browserId: ID! + $phase: RequiredReportPhase! + ) { + requiredReport(atId: $atId, browserId: $browserId, phase: $phase) { + createRequiredReport { + atId + browserId + phase + } + } + } +`; + +export const UPDATE_MANAGE_TEST_QUEUE_MUTATION = gql` + mutation UpdateRequiredReport( + $atId: ID! + $browserId: ID! + $phase: RequiredReportPhase! + ) { + requiredReport(atId: $atId, browserId: $browserId, phase: $phase) { + updateRequiredReport { + atId + browserId + phase + } + } + } +`; + +// DELET_MANAGE_TEST_QUEUE_MUTATION + export const ADD_TEST_QUEUE_MUTATION = gql` mutation AddTestPlanReport( $testPlanVersionId: ID! diff --git a/client/components/TestQueue/TestQueue.css b/client/components/TestQueue/TestQueue.css index 03d9ca34d..39c00b93d 100644 --- a/client/components/TestQueue/TestQueue.css +++ b/client/components/TestQueue/TestQueue.css @@ -95,3 +95,16 @@ table button { .add-test-plan-queue-modal-normalize-row { margin-top: auto; } + +.phase-option:hover { + text-decoration: none; + cursor: default; +} + +.drop-down-div { + height: fit-content; +} + +.drop-down-div > ul { + margin-bottom: 0; +} diff --git a/server/graphql-schema.js b/server/graphql-schema.js index f0dced1a0..b16d69b30 100644 --- a/server/graphql-schema.js +++ b/server/graphql-schema.js @@ -171,6 +171,17 @@ const graphqlSchema = gql` recommendedBrowsers: [Browser]! } + """ + The return type for createRequiredReport. + """ + type RequiredReport { + """ + """ + atId: ID! + browserId: ID! + phase: RequiredReportPhase! + } + """ The version for a given assistive technology. """ @@ -1095,7 +1106,7 @@ const graphqlSchema = gql` type RequiredReportOperations { """ """ - createRequiredReport: Boolean! + createRequiredReport: RequiredReport! updateRequiredReport(atId: ID!, browserId: ID!): Boolean! deleteRequiredReport: Boolean! } diff --git a/server/resolvers/RequiredReportOperations/createRequiredReportResolver.js b/server/resolvers/RequiredReportOperations/createRequiredReportResolver.js index 435e57d23..0039cf017 100644 --- a/server/resolvers/RequiredReportOperations/createRequiredReportResolver.js +++ b/server/resolvers/RequiredReportOperations/createRequiredReportResolver.js @@ -21,7 +21,7 @@ const createRequiredReportResolver = async ( await updateAtBrowser({ atId, browserId }, updateParams); - return true; + return { atId, browserId, phase }; }; module.exports = createRequiredReportResolver; From f6b5e7ea1336a04c34308a1c85347eba2688c34e Mon Sep 17 00:00:00 2001 From: Howard Edwards Date: Tue, 12 Sep 2023 17:15:01 -0500 Subject: [PATCH 32/59] Fix createRequiredReport not being called during edge case condition --- client/components/ManageTestQueue/index.jsx | 76 ++++++++++++++------- 1 file changed, 52 insertions(+), 24 deletions(-) diff --git a/client/components/ManageTestQueue/index.jsx b/client/components/ManageTestQueue/index.jsx index bf2dda841..9ac5512c3 100644 --- a/client/components/ManageTestQueue/index.jsx +++ b/client/components/ManageTestQueue/index.jsx @@ -357,25 +357,33 @@ const ManageTestQueue = ({ // Section: const runMutationForRequiredReportTable = async mutation => { - let atId = ''; - let browserId = ''; - console.log(atBrowserCombinations) - - atBrowserCombinations.forEach(({ at, browser, phase }) => { - // console.log('AtID', updateAtForButton) - // console.log('at.Id', at.id) - // console.log('BrowserID', updateBrowserForButton) - // console.log('browser.Id', browser.id) - if ( - updateAtForButton === at.id && - updateBrowserForButton === browser.id - // updatePhaseForButton === phase - ) { - atId = at.id; - browserId = browser.id; - // console.log('IT CHECKED OUT'); - } - }); + let atId = updateAtForButton; + let browserId = updateBrowserForButton; + // console.log(atBrowserCombinations); + + // console.log( + // 'ids', + // atId, + // browserId, + // updateAtForButton, + // updateBrowserForButton + // ); + + // atBrowserCombinations.forEach(({ at, browser, phase }) => { + // // console.log('AtID', updateAtForButton) + // // console.log('at.Id', at.id) + // // console.log('BrowserID', updateBrowserForButton) + // // console.log('browser.Id', browser.id) + // if ( + // updateAtForButton === at.id && + // updateBrowserForButton === browser.id + // // updatePhaseForButton === phase + // ) { + // atId = at.id; + // browserId = browser.id; + // // console.log('IT CHECKED OUT'); + // } + // }); mutation === 'createRequiredReport' ? await triggerLoad(async () => { const { data } = await createRequiredReport({ @@ -390,12 +398,32 @@ const ManageTestQueue = ({ const createdRequiredReport = data.requiredReport.createRequiredReport; console.log('atBrowserCombinations', atBrowserCombinations); - console.log('createdRequiredReport', createdRequiredReport); + console.log('createdRequiredReport', createdRequiredReport, { + at: ats.find(at => at.id === atId), + browser: browsers.find(browser => browser.id === atId), + phase: updatePhaseForButton + }); - setAtBrowserCombinations([ - ...atBrowserCombinations, - createdRequiredReport - ]); + // Verify that the created required report was actually created before updating + // the dataset + if (createdRequiredReport) { + // TODO: Sort this so it doesn't pop in at the bottom if it isn't intended + // for there + setAtBrowserCombinations([ + ...atBrowserCombinations, + { + at: ats.find( + at => at.id === createdRequiredReport.atId + ), + browser: browsers.find( + browser => + browser.id === + createdRequiredReport.browserId + ), + phase: updatePhaseForButton + } + ]); + } }, 'Adding Phase requirement to the required reports table') : mutation === 'updateRequiredReport' ? await triggerLoad(async () => { From 13549832465baf23007854b26275e4dbf662a6bc Mon Sep 17 00:00:00 2001 From: Paul Clue <67766160+Paul-Clue@users.noreply.github.com> Date: Thu, 14 Sep 2023 16:38:39 -0500 Subject: [PATCH 33/59] update modals and functions for phase requirement --- client/components/ManageTestQueue/index.jsx | 680 ++++++++++++------ client/components/ManageTestQueue/queries.js | 23 +- server/graphql-schema.js | 4 +- .../deleteRequiredReportResolver.js | 2 +- .../updateRequiredReportResolver.js | 2 +- 5 files changed, 481 insertions(+), 230 deletions(-) diff --git a/client/components/ManageTestQueue/index.jsx b/client/components/ManageTestQueue/index.jsx index 9ac5512c3..d398a8564 100644 --- a/client/components/ManageTestQueue/index.jsx +++ b/client/components/ManageTestQueue/index.jsx @@ -21,8 +21,8 @@ import { } from '../TestQueue/queries'; import { CREATE_MANAGE_TEST_QUEUE_MUTATION, - UPDATE_MANAGE_TEST_QUEUE_MUTATION - // DELET_MANAGE_TEST_QUEUE_MUTATION, + UPDATE_MANAGE_TEST_QUEUE_MUTATION, + DELETE_MANAGE_TEST_QUEUE_MUTATION } from './queries'; import { gitUpdatedDateToString } from '../../utils/gitUtils'; import { convertStringToDate } from '../../utils/formatter'; @@ -154,6 +154,12 @@ const ManageTestQueue = ({ const [requiredReportsModalTitle, setRequiredReportsModalTitle] = useState(''); + const [isDelete, setIsDelete] = useState(false); + const [actionButtonLabel, setActionButtonLabel] = useState('Save Changes'); + const [updateAtIdForUpdate, setUpdateAtIdForUpdate] = useState(''); + const [updatePhaseForUpdate, setUpdatePhaseForUpdate] = useState(''); + const [updateBrowserIdForUpdate, setUpdateBrowserIdForUpdate] = + useState(''); const [updateAtSelection, setUpdateAtSelection] = useState('Select an At'); const [updateAtForButton, setUpdateAtForButton] = useState(''); const [updateListAtSelection, setUpdateListAtSelection] = @@ -355,98 +361,256 @@ const ManageTestQueue = ({ // ) // ]; + // Section: + + const onOpenShowEditAtBrowserModal = ( + type = 'edit', + phase, + at = '', + browser = '' + ) => { + if (type === 'edit') { + setRequiredReportsModalTitle( +

    + Edit the following AT/Browser pair for{' '} + + {phase} + {' '} + required reports +

    + ); + } + + if (type === 'delete') { + setRequiredReportsModalTitle( +

    + Delete {at} and {browser} pair for{' '} + + {phase} + {' '} + required reports +

    + ); + } + setShowEditAtBrowserModal(false); + }; + // Section: const runMutationForRequiredReportTable = async mutation => { let atId = updateAtForButton; let browserId = updateBrowserForButton; // console.log(atBrowserCombinations); - // console.log( - // 'ids', - // atId, - // browserId, - // updateAtForButton, - // updateBrowserForButton - // ); - - // atBrowserCombinations.forEach(({ at, browser, phase }) => { - // // console.log('AtID', updateAtForButton) - // // console.log('at.Id', at.id) - // // console.log('BrowserID', updateBrowserForButton) - // // console.log('browser.Id', browser.id) - // if ( - // updateAtForButton === at.id && - // updateBrowserForButton === browser.id - // // updatePhaseForButton === phase - // ) { - // atId = at.id; - // browserId = browser.id; - // // console.log('IT CHECKED OUT'); - // } - // }); mutation === 'createRequiredReport' ? await triggerLoad(async () => { - const { data } = await createRequiredReport({ - variables: { - atId: atId, - browserId: browserId, - phase: `IS_${updatePhaseForButton}` - } - }); - console.log('data', data); - - const createdRequiredReport = - data.requiredReport.createRequiredReport; - console.log('atBrowserCombinations', atBrowserCombinations); - console.log('createdRequiredReport', createdRequiredReport, { - at: ats.find(at => at.id === atId), - browser: browsers.find(browser => browser.id === atId), - phase: updatePhaseForButton - }); - - // Verify that the created required report was actually created before updating - // the dataset - if (createdRequiredReport) { - // TODO: Sort this so it doesn't pop in at the bottom if it isn't intended - // for there - setAtBrowserCombinations([ - ...atBrowserCombinations, - { - at: ats.find( - at => at.id === createdRequiredReport.atId - ), - browser: browsers.find( - browser => - browser.id === - createdRequiredReport.browserId - ), - phase: updatePhaseForButton + try { + atBrowserCombinations.forEach( + ({ at, browser, phase }) => { + // console.log('AtID', updateAtForButton) + // console.log('at.Id', at.id) + // console.log('BrowserID', updateBrowserForButton) + // console.log('browser.Id', browser.id) + if ( + updateAtForButton === at.id && + updateBrowserForButton === browser.id && + updatePhaseForButton === phase + ) { + // atId = at.id; + // browserId = browser.id; + // console.log('IT CHECKED OUT'); + throw new Error( + 'A duplicate Entry was detected in the table' + ); + } + } + ); + const { data } = await createRequiredReport({ + variables: { + atId: atId, + browserId: browserId, + phase: `IS_${updatePhaseForButton}` } - ]); + }); + // console.log('data', data); + + const createdRequiredReport = + data.requiredReport.createRequiredReport; + + // Verify that the created required report was actually created before updating + // the dataset + if (createdRequiredReport) { + setAtBrowserCombinations( + [ + ...atBrowserCombinations, + { + at: ats.find( + at => + at.id === + createdRequiredReport.atId + ), + browser: browsers.find( + browser => + browser.id === + createdRequiredReport.browserId + ), + phase: updatePhaseForButton + } + ].sort((a, b) => { + if (a.phase < b.phase) return -1; + if (a.phase > b.phase) return 1; + return a.at.name.localeCompare(b.at.name); + }) + ); + } + } catch (error) { + console.error(error); } }, 'Adding Phase requirement to the required reports table') : mutation === 'updateRequiredReport' ? await triggerLoad(async () => { - await updateRequiredReport({ + // console.log( + // 'Update IDS', + // updateBrowserSelection, + // updateAtSelection + // ); + // console.log( + // 'phase in title', + // requiredReportsModalTitle.props.children[2].props.children + // ); + try { + atBrowserCombinations.forEach( + ({ at, browser, phase }) => { + // console.log('AtID', updateAtForButton) + // console.log('at.Id', at.id) + // console.log('BrowserID', updateBrowserForButton) + // console.log('browser.Id', browser.id) + if ( + updateAtSelection === at.id && + updateBrowserSelection === browser.id && + updatePhaseForUpdate === phase + ) { + // atId = at.id; + // browserId = browser.id; + // console.log('IT CHECKED OUT'); + throw new Error( + 'A duplicate Entry was detected in the table' + ); + } + } + ); + // console.log('updateAt', updateAtIdForUpdate); + // console.log('updateBrowser', updateBrowserIdForUpdate); + + const { data } = await updateRequiredReport({ + variables: { + atId: updateAtIdForUpdate, + browserId: updateBrowserIdForUpdate, + // phase: `IS_${updatePhaseForButton}`, + phase: `IS_${updatePhaseForUpdate}`, + updateAtId: updateAtSelection, + updateBrowserId: updateBrowserSelection + } + }); + // console.log('data', data); + + const updatedRequiredReport = + data.requiredReport.updateRequiredReport; + + // Verify that the created required report was actually created before updating + // the dataset + if (updatedRequiredReport) { + setAtBrowserCombinations( + [ + ...atBrowserCombinations, + { + at: ats.find( + at => + at.id === + updatedRequiredReport.atId + ), + browser: browsers.find( + browser => + browser.id === + updatedRequiredReport.browserId + ), + phase: updatePhaseForUpdate + } + ] + .filter(row => { + if ( + row.at.id === updateAtIdForUpdate && + row.browser.id === + updateBrowserIdForUpdate && + row.phase == updatePhaseForUpdate + ) { + // foundOne80 = true; // Set the flag to true to filter out only the first object with score 80 + return false; // Exclude this object from the filtered result + } + return true; // Keep all other objects + }) + .sort((a, b) => { + if (a.phase < b.phase) return -1; + if (a.phase > b.phase) return 1; + return a.at.name.localeCompare(b.at.name); + }) + ); + } + } catch (error) { + console.error(error); + } + }, 'Adding Phase requirement to the required reports table') + : mutation === 'deleteRequiredReport' + ? await triggerLoad(async () => { + console.log('atId', updateAtIdForUpdate) + console.log('browserId', updateBrowserIdForUpdate) + const { data } = await deleteRequiredReport({ variables: { - atId: atId, - browserId: browserId, - phase: `IS_${updatePhaseForButton}` + atId: updateAtIdForUpdate, + browserId: updateBrowserIdForUpdate, + phase: `IS_${updatePhaseForUpdate}` } }); + // console.log('data', data); + + const deletedRequiredReport = + data.requiredReport.deleteRequiredReport; + + if (deletedRequiredReport) { + setAtBrowserCombinations( + [ + ...atBrowserCombinations + // { + // at: ats.find( + // at => at.id === deletedRequiredReport.atId + // ), + // browser: browsers.find( + // browser => + // browser.id === + // deletedRequiredReport.browserId + // ), + // phase: updatePhaseForUpdate + // } + ] + .filter(row => { + if ( + row.at.id === updateAtIdForUpdate && + row.browser.id === + updateBrowserIdForUpdate && + row.phase == updatePhaseForUpdate + ) { + return false; + } + return true; + }) + .sort((a, b) => { + if (a.phase < b.phase) return -1; + if (a.phase > b.phase) return 1; + return a.at.name.localeCompare(b.at.name); + }) + ); + } }, 'Adding Phase requirement to the required reports table') - : // : mutation === 'deleteRequiredReport' - // ? await triggerLoad(async () => { - // await deleteRequiredReport({ - // variables: { - // atId: atId, - // browserId: browserId, - // phase: `IS_${updatePhaseForButton}` - // } - // }); - // }, 'Adding Phase requirement to the required reports table') - // : - null; + : null; }; const addRequiredReport = async () => { @@ -524,9 +688,9 @@ const ManageTestQueue = ({ const [updateRequiredReport] = useMutation( UPDATE_MANAGE_TEST_QUEUE_MUTATION ); - // const [deleteRequiredReport] = useMutation( - // DELETE_MANAGE_TEST_QUEUE_MUTATION - // ); + const [deleteRequiredReport] = useMutation( + DELETE_MANAGE_TEST_QUEUE_MUTATION + ); const onManageAtsClick = () => setShowManageATs(!showManageATs); const onAddTestPlansClick = () => setShowAddTestPlans(!showAddTestPlans); @@ -870,24 +1034,24 @@ const ManageTestQueue = ({ }; // Find Manage Required Reports Modal - const onOpenShowEditAtBrowserModal = (type = 'edit', phase) => { - if (type === 'edit') { - setRequiredReportsModalTitle( -

    - Edit the following AT/Browser pair for{' '} - - {phase} - {' '} - requied reports -

    - ); - } - - if (type === 'delete') { - setRequiredReportsModalTitle(

    delete this

    ); - } - setShowEditAtBrowserModal(false); - }; + // const onOpenShowEditAtBrowserModal = (type = 'edit', phase) => { + // if (type === 'edit') { + // setRequiredReportsModalTitle( + //

    + // Edit the following AT/Browser pair for{' '} + // + // {phase} + // {' '} + // requied reports + //

    + // ); + // } + + // if (type === 'delete') { + // setRequiredReportsModalTitle(

    delete this

    ); + // } + // setShowEditAtBrowserModal(false); + // }; const handlePhaseChange = e => { const value = e.target.value; @@ -1340,7 +1504,7 @@ const ManageTestQueue = ({ ); } )}{' '} */} - {ats[0].browsers.map(item => { + {ats[2].browsers.map(item => { return ( */} {updateListAtSelection === 'Select an At' ? ( @@ -1365,38 +1177,13 @@ const ManageTestQueue = ({ ); })} - {/* - - */} ) : ( - {/* {Object.entries(ats).map( - ([key, value]) => { - return ( - - ); - } - )} */} - {ats.map(item => { return (
    - Required Reports + + Required Reports + From bccaab0c8d821272d5e4b2ca7b721aacd881864c Mon Sep 17 00:00:00 2001 From: Paul Clue <67766160+Paul-Clue@users.noreply.github.com> Date: Tue, 19 Sep 2023 12:08:55 -0500 Subject: [PATCH 37/59] Remove comments and fix test --- client/components/ManageTestQueue/index.jsx | 4 - .../TestQueuePageTesterPopulatedMock.js | 90 +++++++++++++++++++ 2 files changed, 90 insertions(+), 4 deletions(-) diff --git a/client/components/ManageTestQueue/index.jsx b/client/components/ManageTestQueue/index.jsx index 7dc892144..8c4b6cb45 100644 --- a/client/components/ManageTestQueue/index.jsx +++ b/client/components/ManageTestQueue/index.jsx @@ -309,8 +309,6 @@ const ManageTestQueue = ({ ); }); - // Section: - const onOpenShowEditAtBrowserModal = ( type = 'edit', phase, @@ -343,7 +341,6 @@ const ManageTestQueue = ({ setShowEditAtBrowserModal(false); }; - // Section: const runMutationForRequiredReportTable = async mutation => { let atId = updateAtForButton; let browserId = updateBrowserForButton; @@ -1548,7 +1545,6 @@ const ManageTestQueue = ({ } actionLabel={actionButtonLabel} - //section: save button handleAction={() => { if (actionButtonLabel === 'Save Changes') { runMutationForRequiredReportTable( diff --git a/client/tests/__mocks__/GraphQLMocks/TestQueuePageTesterPopulatedMock.js b/client/tests/__mocks__/GraphQLMocks/TestQueuePageTesterPopulatedMock.js index 14fd3e6dc..a47dd330d 100644 --- a/client/tests/__mocks__/GraphQLMocks/TestQueuePageTesterPopulatedMock.js +++ b/client/tests/__mocks__/GraphQLMocks/TestQueuePageTesterPopulatedMock.js @@ -21,6 +21,36 @@ export default testQueuePageQuery => [ name: '2021.2103.174', releasedAt: '2022-08-02T14:36:02.659Z' } + ], + browsers: [ + { + id: '3', + name: 'Safari' + }, + { + id: '1', + name: 'Firefox' + }, + { + id: '2', + name: 'Chrome' + } + ], + candidateBrowsers: [ + { + id: '2', + name: 'Chrome' + } + ], + recommendedBrowsers: [ + { + id: '1', + name: 'Firefox' + }, + { + id: '2', + name: 'Chrome' + } ] }, { @@ -52,6 +82,36 @@ export default testQueuePageQuery => [ name: '2019.3', releasedAt: '2022-01-01T12:00:00.000Z' } + ], + browsers: [ + { + id: '3', + name: 'Safari' + }, + { + id: '1', + name: 'Firefox' + }, + { + id: '2', + name: 'Chrome' + } + ], + candidateBrowsers: [ + { + id: '2', + name: 'Chrome' + } + ], + recommendedBrowsers: [ + { + id: '1', + name: 'Firefox' + }, + { + id: '2', + name: 'Chrome' + } ] }, { @@ -63,6 +123,36 @@ export default testQueuePageQuery => [ name: '11.5.2', releasedAt: '2022-01-01T12:00:00.000Z' } + ], + browsers: [ + { + id: '3', + name: 'Safari' + }, + { + id: '1', + name: 'Firefox' + }, + { + id: '2', + name: 'Chrome' + } + ], + candidateBrowsers: [ + { + id: '3', + name: 'Safari' + } + ], + recommendedBrowsers: [ + { + id: '3', + name: 'Safari' + }, + { + id: '2', + name: 'Chrome' + } ] } ], From 08954663821b7dcbc786e10b7b481abc3e5bd5ae Mon Sep 17 00:00:00 2001 From: Paul Clue <67766160+Paul-Clue@users.noreply.github.com> Date: Wed, 20 Sep 2023 10:01:26 -0500 Subject: [PATCH 38/59] Add prop for ManageRequiredReports dropdown --- client/components/ManageTestQueue/index.jsx | 20 +++++++++++++++----- client/components/TestQueue/index.jsx | 1 + 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/client/components/ManageTestQueue/index.jsx b/client/components/ManageTestQueue/index.jsx index 6d966bae3..1204fa683 100644 --- a/client/components/ManageTestQueue/index.jsx +++ b/client/components/ManageTestQueue/index.jsx @@ -118,6 +118,7 @@ const DisclosureContainer = styled.div` `; const ManageTestQueue = ({ + enableManageRequiredReports = false, ats = [], browsers = [], testPlanVersions = [], @@ -538,6 +539,18 @@ const ManageTestQueue = ({ const onManageReqReportsClick = () => setShowManageReqReports(!showManageReqReports); + const disclosureTitle = enableManageRequiredReports + ? [ + 'Manage Assistive Technology Versions', + 'Add Test Plans to the Test Queue' + // 'Manage Required Reports' + ] + : [ + 'Manage Assistive Technology Versions', + 'Add Test Plans to the Test Queue', + 'Manage Required Reports' + ]; + useEffect(() => { const allTestPlanVersions = testPlanVersions .map(version => ({ ...version })) @@ -885,11 +898,7 @@ const ManageTestQueue = ({ @@ -1601,6 +1610,7 @@ ManageTestQueue.propTypes = { className: PropTypes.string, onClick: PropTypes.func, testPlanVersions: PropTypes.array, + enableManageRequiredReports: PropTypes.bool, triggerUpdate: PropTypes.func }; diff --git a/client/components/TestQueue/index.jsx b/client/components/TestQueue/index.jsx index 2fb8ef344..b7012c078 100644 --- a/client/components/TestQueue/index.jsx +++ b/client/components/TestQueue/index.jsx @@ -253,6 +253,7 @@ const TestQueue = () => { {isAdmin && ( Date: Wed, 20 Sep 2023 10:13:20 -0500 Subject: [PATCH 39/59] Remove comment --- client/components/ManageTestQueue/index.jsx | 1 - 1 file changed, 1 deletion(-) diff --git a/client/components/ManageTestQueue/index.jsx b/client/components/ManageTestQueue/index.jsx index 1204fa683..558eb04bb 100644 --- a/client/components/ManageTestQueue/index.jsx +++ b/client/components/ManageTestQueue/index.jsx @@ -543,7 +543,6 @@ const ManageTestQueue = ({ ? [ 'Manage Assistive Technology Versions', 'Add Test Plans to the Test Queue' - // 'Manage Required Reports' ] : [ 'Manage Assistive Technology Versions', From 87abb145d2779d545cef46e31219709975b6a99c Mon Sep 17 00:00:00 2001 From: Howard Edwards Date: Thu, 21 Sep 2023 15:06:28 -0500 Subject: [PATCH 40/59] Update `Timeline for All Versions` table on Test Plan Versions Page (#773) * Show all versions in 'Timeline for All Versions' table * Update timestamp migrations to use more real world change phase change times (avoid exact time being set for when one phase ends and another begins) * Update import script to have a 'delayed' time on the report being deprecated * Add td -> th for first column cell for Version Summary and Timeline related tables * Match sort order described in #719 * Correct deprecation date (#780) * Compare most recent test plan version to deprecate against * Correct deprecation date without using the more recent test plan version since it's being set already --------- Co-authored-by: Erika Miguel --- .../components/TestPlanVersionsPage/index.jsx | 85 +++++++++++++++++-- ...PhaseReachedAtColumnsForTestPlanVersion.js | 12 ++- ...gDeprecatedAtAndReorderPhaseChangeDates.js | 40 +++++---- server/scripts/import-tests/index.js | 12 ++- 4 files changed, 118 insertions(+), 31 deletions(-) diff --git a/client/components/TestPlanVersionsPage/index.jsx b/client/components/TestPlanVersionsPage/index.jsx index 405ac9d0f..24c3d5d37 100644 --- a/client/components/TestPlanVersionsPage/index.jsx +++ b/client/components/TestPlanVersionsPage/index.jsx @@ -188,6 +188,69 @@ const TestPlanVersionsPage = () => { return new Date(b.updatedAt) - new Date(a.updatedAt); }); + const timelineForAllVersions = []; + + testPlanVersions.forEach(testPlanVersion => { + const event = { + id: testPlanVersion.id, + updatedAt: testPlanVersion.updatedAt + }; + timelineForAllVersions.push({ ...event, phase: 'RD' }); + + if (testPlanVersion.draftPhaseReachedAt) + timelineForAllVersions.push({ + ...event, + phase: 'DRAFT', + draftPhaseReachedAt: testPlanVersion.draftPhaseReachedAt + }); + if (testPlanVersion.candidatePhaseReachedAt) + timelineForAllVersions.push({ + ...event, + phase: 'CANDIDATE', + candidatePhaseReachedAt: testPlanVersion.candidatePhaseReachedAt + }); + if (testPlanVersion.recommendedPhaseReachedAt) + timelineForAllVersions.push({ + ...event, + phase: 'RECOMMENDED', + recommendedPhaseReachedAt: + testPlanVersion.recommendedPhaseReachedAt + }); + if (testPlanVersion.deprecatedAt) + timelineForAllVersions.push({ + ...event, + phase: 'DEPRECATED', + deprecatedAt: testPlanVersion.deprecatedAt + }); + }); + + const phaseOrder = { + RD: 0, + DRAFT: 1, + CANDIDATE: 2, + RECOMMENDED: 3, + DEPRECATED: 4 + }; + + timelineForAllVersions.sort((a, b) => { + const dateA = + a.recommendedPhaseReachedAt || + a.candidatePhaseReachedAt || + a.draftPhaseReachedAt || + a.deprecatedAt || + a.updatedAt; + const dateB = + b.recommendedPhaseReachedAt || + b.candidatePhaseReachedAt || + b.draftPhaseReachedAt || + b.deprecatedAt || + b.updatedAt; + + // If dates are the same, compare phases + if (dateA === dateB) return phaseOrder[a.phase] - phaseOrder[b.phase]; + return new Date(dateA) - new Date(dateB); + }); + const issues = uniqueBy( testPlanVersions.flatMap(testPlanVersion => testPlanVersion.testPlanReports.flatMap(testPlanReport => @@ -198,7 +261,11 @@ const TestPlanVersionsPage = () => { ) ), item => item.link - ); + ).sort((a, b) => { + const aCreatedAt = new Date(a.createdAt); + const bCreatedAt = new Date(b.createdAt); + return bCreatedAt - aCreatedAt; + }); return ( @@ -240,7 +307,7 @@ const TestPlanVersionsPage = () => { {testPlanVersions.map(testPlanVersion => ( - + { )} autoWidth={false} /> - + {(() => { // Gets the derived phase even if deprecated by checking @@ -376,7 +443,7 @@ const TestPlanVersionsPage = () => { - {testPlanVersions.map(testPlanVersion => { + {timelineForAllVersions.map(testPlanVersion => { const versionString = ( { const eventBody = getEventBody(testPlanVersion.phase); return ( - - {getEventDate(testPlanVersion)} + + {getEventDate(testPlanVersion)} {versionString} {eventBody} @@ -541,12 +610,12 @@ const TestPlanVersionsPage = () => { return events.map(([phase, date]) => ( - + {convertDateToString( date, 'MMM D, YYYY' )} - + {getEventBody(phase)} )); diff --git a/server/migrations/20230626203205-updatePhaseAndDraftPhaseReachedAtColumnsForTestPlanVersion.js b/server/migrations/20230626203205-updatePhaseAndDraftPhaseReachedAtColumnsForTestPlanVersion.js index 8ed875cc0..1de8cbf57 100644 --- a/server/migrations/20230626203205-updatePhaseAndDraftPhaseReachedAtColumnsForTestPlanVersion.js +++ b/server/migrations/20230626203205-updatePhaseAndDraftPhaseReachedAtColumnsForTestPlanVersion.js @@ -37,15 +37,21 @@ module.exports = { for (const testPlanVersion of testPlanVersions) { const { id, updatedAt, hasTestPlanReport } = testPlanVersion; - if (hasTestPlanReport) + if (hasTestPlanReport) { + const draftPhaseReachedAt = new Date(updatedAt); + draftPhaseReachedAt.setSeconds( + // Set draftPhaseReachedAt to happen 60 seconds after updatedAt for general + // 'correctness' and to help with any app sorts + draftPhaseReachedAt.getSeconds() + 60 + ); await queryInterface.sequelize.query( `UPDATE "TestPlanVersion" SET "draftPhaseReachedAt" = ? WHERE id = ?`, { - replacements: [updatedAt, id], + replacements: [draftPhaseReachedAt, id], transaction } ); - else + } else await queryInterface.sequelize.query( `UPDATE "TestPlanVersion" SET phase = ? WHERE id = ?`, { diff --git a/server/migrations/20230830225248-addMissingDeprecatedAtAndReorderPhaseChangeDates.js b/server/migrations/20230830225248-addMissingDeprecatedAtAndReorderPhaseChangeDates.js index c05f49607..77d36372a 100644 --- a/server/migrations/20230830225248-addMissingDeprecatedAtAndReorderPhaseChangeDates.js +++ b/server/migrations/20230830225248-addMissingDeprecatedAtAndReorderPhaseChangeDates.js @@ -12,7 +12,7 @@ module.exports = { // 1178 (Radio Group Example Using aria-activedescendant) TestPlanVersions const testPlanVersionsToSetToCandidate = await queryInterface.sequelize.query( - `select "TestPlanVersion".id, phase, "markedFinalAt" + `select "TestPlanVersion".id, phase, "draftPhaseReachedAt", "markedFinalAt" from "TestPlanVersion" join "TestPlanReport" on "TestPlanVersion".id = "TestPlanReport"."testPlanVersionId" where "markedFinalAt" is not null @@ -23,15 +23,23 @@ module.exports = { } ); - const candidatePhaseReachedAt = new Date(); - const recommendedPhaseTargetDate = new Date( - candidatePhaseReachedAt - ); - recommendedPhaseTargetDate.setDate( - candidatePhaseReachedAt.getDate() + 180 - ); - for (const testPlanVersion of testPlanVersionsToSetToCandidate) { + const candidatePhaseReachedAt = new Date( + testPlanVersion.draftPhaseReachedAt + ); + + // Set candidatePhaseReachedAt to draftPhaseReachedAt date (+1 day) + candidatePhaseReachedAt.setDate( + candidatePhaseReachedAt.getDate() + 1 + ); + + const recommendedPhaseTargetDate = new Date( + candidatePhaseReachedAt + ); + recommendedPhaseTargetDate.setDate( + candidatePhaseReachedAt.getDate() + 180 + ); + await queryInterface.sequelize.query( `update "TestPlanVersion" set "candidatePhaseReachedAt" = ?, @@ -97,7 +105,7 @@ module.exports = { testPlanVersion.candidatePhaseReachedAt ); - // Update candidatePhaseReachedAt to be the draftPhaseReachedAt date (+1) + // Update candidatePhaseReachedAt to be the draftPhaseReachedAt date (+1 day) // (because that phase happening before shouldn't be possible) if (candidatePhaseReachedAt < draftPhaseReachedAt) { const newCandidatePhaseReachedAt = new Date( @@ -126,6 +134,9 @@ module.exports = { } if (testPlanVersion.deprecatedAt) { + const deprecatedAt = new Date(testPlanVersion.deprecatedAt); + deprecatedAt.setSeconds(deprecatedAt.getSeconds() - 1); + // Add deprecatedAt for applicable testPlanVersions await queryInterface.sequelize.query( `update "TestPlanVersion" @@ -133,14 +144,7 @@ module.exports = { phase = 'DEPRECATED' where id = ?`, { - replacements: [ - testPlanVersion.recommendedPhaseReachedAt - ? testPlanVersion.recommendedPhaseReachedAt - : testPlanVersion.candidatePhaseReachedAt - ? testPlanVersion.candidatePhaseReachedAt - : testPlanVersion.deprecatedAt, - testPlanVersion.id - ], + replacements: [deprecatedAt, testPlanVersion.id], transaction } ); diff --git a/server/scripts/import-tests/index.js b/server/scripts/import-tests/index.js index d34b55818..b36f9e7b7 100644 --- a/server/scripts/import-tests/index.js +++ b/server/scripts/import-tests/index.js @@ -157,11 +157,19 @@ const importTestPlanVersions = async () => { }); if (testPlanVersionsToDeprecate.length) { for (const testPlanVersionToDeprecate of testPlanVersionsToDeprecate) { - if (new Date(testPlanVersionToDeprecate.updatedAt) < updatedAt) + if ( + new Date(testPlanVersionToDeprecate.updatedAt) < updatedAt + ) { + // Set the deprecatedAt time to a couple seconds less than the updatedAt date. + // Deprecations happen slightly before update during normal app operations. + // This is to maintain correctness and any app sorts issues + const deprecatedAt = new Date(updatedAt); + deprecatedAt.setSeconds(deprecatedAt.getSeconds() - 60); await updateTestPlanVersion(testPlanVersionToDeprecate.id, { phase: 'DEPRECATED', - deprecatedAt: updatedAt + deprecatedAt }); + } } } From 1338f815ed5d7117d126cfe91a84ec5b2f8d4955 Mon Sep 17 00:00:00 2001 From: Alexander Flenniken Date: Thu, 21 Sep 2023 16:15:23 -0400 Subject: [PATCH 41/59] Deprecated phase copy (#781) * Update deprecated phase copy * Remove unneeded change * Fix spacing in copy --- .../components/TestPlanVersionsPage/index.jsx | 41 ++++++++++++++++++- client/components/common/PhasePill/index.jsx | 4 +- client/components/common/ThemeTable/index.jsx | 1 + 3 files changed, 42 insertions(+), 4 deletions(-) diff --git a/client/components/TestPlanVersionsPage/index.jsx b/client/components/TestPlanVersionsPage/index.jsx index 24c3d5d37..8c1db6033 100644 --- a/client/components/TestPlanVersionsPage/index.jsx +++ b/client/components/TestPlanVersionsPage/index.jsx @@ -340,10 +340,47 @@ const TestPlanVersionsPage = () => { ); + const draftPill = ( + + DRAFT + + ); + + if ( + derivedDeprecatedAtPhase === + 'RD' + ) { + return ( + <> + {deprecatedPill} + {` before `} + {draftPill} + {` review `} + + ); + } + + if ( + derivedDeprecatedAtPhase === + 'RECOMMENDED' + ) { + return ( + <> + {deprecatedPill} + {` after being approved as `} + {phasePill} + + ); + } + return ( <> - {deprecatedPill} during{' '} - {phasePill} review + {deprecatedPill} + {` during `} + {phasePill} + {` review `} ); } diff --git a/client/components/common/PhasePill/index.jsx b/client/components/common/PhasePill/index.jsx index 78e29be05..992dce3b8 100644 --- a/client/components/common/PhasePill/index.jsx +++ b/client/components/common/PhasePill/index.jsx @@ -21,8 +21,8 @@ const PhaseText = styled.span` padding: 2px 15px; vertical-align: middle; position: relative; - top: -1px; - margin-right: 5px; + top: -4px; + margin-top: 4px; /* Improve appearance when text wraps */ } &.rd { diff --git a/client/components/common/ThemeTable/index.jsx b/client/components/common/ThemeTable/index.jsx index 9907130b9..12d2e739c 100644 --- a/client/components/common/ThemeTable/index.jsx +++ b/client/components/common/ThemeTable/index.jsx @@ -28,6 +28,7 @@ export const ThemeTable = styled(Table)` th { padding-left: 1rem; min-width: 165px; + vertical-align: middle; } `; From 77fd3051c961ef04a8371c882b859e028055f541 Mon Sep 17 00:00:00 2001 From: Alexander Flenniken Date: Tue, 26 Sep 2023 15:52:45 -0400 Subject: [PATCH 42/59] Add hidden JSON metadata to issues created by the app (#775) * Implement hidden metadata comment for issues * Address PR feedback * Add test plan issues graphql field * Make issue loading more resilient * Fix error that occurs when there are multiple issues to sort --- .../CandidateTestPlanRun/index.jsx | 11 +- .../CandidateTestPlanRun/queries.js | 1 + .../Reports/SummarizeTestPlanReport.jsx | 1 + .../components/TestPlanVersionsPage/index.jsx | 28 ++--- .../TestPlanVersionsPage/queries.js | 21 ++-- client/components/TestRun/TestNavigator.jsx | 4 +- client/components/TestRun/index.jsx | 1 + client/utils/createIssueLink.js | 23 +++- server/graphql-schema.js | 22 +++- server/resolvers/TestPlan/index.js | 5 + server/resolvers/TestPlan/issuesResolver.js | 6 + .../TestPlanReport/issuesResolver.js | 106 +++++++++++------- server/resolvers/index.js | 2 + server/services/GithubService.js | 51 +++------ server/tests/integration/graphql.test.js | 19 +++- 15 files changed, 182 insertions(+), 119 deletions(-) create mode 100644 server/resolvers/TestPlan/index.js create mode 100644 server/resolvers/TestPlan/issuesResolver.js diff --git a/client/components/CandidateReview/CandidateTestPlanRun/index.jsx b/client/components/CandidateReview/CandidateTestPlanRun/index.jsx index f3f3099de..39c861a20 100644 --- a/client/components/CandidateReview/CandidateTestPlanRun/index.jsx +++ b/client/components/CandidateReview/CandidateTestPlanRun/index.jsx @@ -302,12 +302,14 @@ const CandidateTestPlanRun = () => { // Assumes that the issues are across the entire AT/Browser combination const changesRequestedIssues = testPlanReport.issues?.filter( issue => + issue.isCandidateReview && issue.feedbackType === 'CHANGES_REQUESTED' && issue.testNumberFilteredByAt === currentTest.seq ); const feedbackIssues = testPlanReport.issues?.filter( issue => + issue.isCandidateReview && issue.feedbackType === 'FEEDBACK' && issue.testNumberFilteredByAt === currentTest.seq ); @@ -316,6 +318,7 @@ const CandidateTestPlanRun = () => { isCandidateReview: true, isCandidateReviewChangesRequested: true, testPlanTitle: testPlanVersion.title, + testPlanDirectory: testPlanVersion.testPlan.directory, versionString, testTitle: currentTest.title, testRowNumber: currentTest.rowNumber, @@ -428,7 +431,9 @@ const CandidateTestPlanRun = () => { ); const feedback = testPlanReport.issues.filter( - issue => issue.testNumberFilteredByAt == currentTest.seq + issue => + issue.isCandidateReview && + issue.testNumberFilteredByAt == currentTest.seq ).length > 0 && (

    @@ -665,14 +670,14 @@ const CandidateTestPlanRun = () => { testPlan={testPlanVersion.title} feedbackIssues={testPlanReport.issues?.filter( issue => - issue.isCandidateReview === true && + issue.isCandidateReview && issue.feedbackType === 'FEEDBACK' && issue.author == data.me.username )} feedbackGithubUrl={feedbackGithubUrl} changesRequestedIssues={testPlanReport.issues?.filter( issue => - issue.isCandidateReview === true && + issue.isCandidateReview && issue.feedbackType === 'CHANGES_REQUESTED' && issue.author == data.me.username )} diff --git a/client/components/CandidateReview/CandidateTestPlanRun/queries.js b/client/components/CandidateReview/CandidateTestPlanRun/queries.js index c61e1e3d7..9b2f6d3e3 100644 --- a/client/components/CandidateReview/CandidateTestPlanRun/queries.js +++ b/client/components/CandidateReview/CandidateTestPlanRun/queries.js @@ -45,6 +45,7 @@ export const CANDIDATE_REPORTS_QUERY = gql` vendorReviewStatus issues { author + isCandidateReview feedbackType testNumberFilteredByAt link diff --git a/client/components/Reports/SummarizeTestPlanReport.jsx b/client/components/Reports/SummarizeTestPlanReport.jsx index 0663e0994..2daaf2323 100644 --- a/client/components/Reports/SummarizeTestPlanReport.jsx +++ b/client/components/Reports/SummarizeTestPlanReport.jsx @@ -144,6 +144,7 @@ const SummarizeTestPlanReport = ({ testPlanVersion, testPlanReports }) => { const reportLink = `https://aria-at.w3.org${location.pathname}#result-${testResult.id}`; const issueLink = createIssueLink({ testPlanTitle: testPlanVersion.title, + testPlanDirectory: testPlanVersion.testPlan.directory, versionString: `V${convertDateToString( testPlanVersion.updatedAt, 'YY.MM.DD' diff --git a/client/components/TestPlanVersionsPage/index.jsx b/client/components/TestPlanVersionsPage/index.jsx index 8c1db6033..2f40e768e 100644 --- a/client/components/TestPlanVersionsPage/index.jsx +++ b/client/components/TestPlanVersionsPage/index.jsx @@ -20,7 +20,6 @@ import { faCodeCommit } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { uniqBy as uniqueBy } from 'lodash'; const H2 = styled.h2` font-size: 1.25em; @@ -180,6 +179,13 @@ const TestPlanVersionsPage = () => { const testPlan = data.testPlan; + // GraphQL results are read only so they need to be cloned before sorting + const issues = [...testPlan.issues].sort((a, b) => { + const aCreatedAt = new Date(a.createdAt); + const bCreatedAt = new Date(b.createdAt); + return bCreatedAt - aCreatedAt; + }); + const ats = data.ats; const testPlanVersions = data.testPlan.testPlanVersions @@ -251,22 +257,6 @@ const TestPlanVersionsPage = () => { return new Date(dateA) - new Date(dateB); }); - const issues = uniqueBy( - testPlanVersions.flatMap(testPlanVersion => - testPlanVersion.testPlanReports.flatMap(testPlanReport => - testPlanReport.issues.map(issue => ({ - ...issue, - at: testPlanReport.at - })) - ) - ), - item => item.link - ).sort((a, b) => { - const aCreatedAt = new Date(a.createdAt); - const bCreatedAt = new Date(b.createdAt); - return bCreatedAt - aCreatedAt; - }); - return ( @@ -440,7 +430,9 @@ const TestPlanVersionsPage = () => { {issue.isOpen ? 'Open' : 'Closed'} - {issue.at.name} + + {issue.at?.name ?? 'AT not specified'} + {convertDateToString( issue.createdAt, diff --git a/client/components/TestPlanVersionsPage/queries.js b/client/components/TestPlanVersionsPage/queries.js index d630d92d0..b8557ffcb 100644 --- a/client/components/TestPlanVersionsPage/queries.js +++ b/client/components/TestPlanVersionsPage/queries.js @@ -8,6 +8,18 @@ export const TEST_PLAN_VERSIONS_PAGE_QUERY = gql` } testPlan(id: $testPlanDirectory) { title + issues { + author + title + link + feedbackType + isOpen + createdAt + closedAt + at { + name + } + } testPlanVersions { id testPlan { @@ -27,15 +39,6 @@ export const TEST_PLAN_VERSIONS_PAGE_QUERY = gql` at { name } - issues { - author - title - link - feedbackType - isOpen - createdAt - closedAt - } } } } diff --git a/client/components/TestRun/TestNavigator.jsx b/client/components/TestRun/TestNavigator.jsx index 999ec3c4a..f3bc07756 100644 --- a/client/components/TestRun/TestNavigator.jsx +++ b/client/components/TestRun/TestNavigator.jsx @@ -52,7 +52,9 @@ const TestNavigator = ({ let resultClassName = 'not-started'; let resultStatus = 'Not Started'; const issuesExist = testPlanReport.issues?.filter( - issue => issue.testNumberFilteredByAt == test.seq + issue => + issue.isCandidateReview && + issue.testNumberFilteredByAt == test.seq ).length; if (test) { diff --git a/client/components/TestRun/index.jsx b/client/components/TestRun/index.jsx index 63d975aef..d86cbd9e1 100644 --- a/client/components/TestRun/index.jsx +++ b/client/components/TestRun/index.jsx @@ -337,6 +337,7 @@ const TestRun = () => { if (hasLoadingCompleted) { issueLink = createIssueLink({ testPlanTitle: testPlanVersion.title, + testPlanDirectory: testPlanVersion.testPlan.directory, versionString: `V${convertDateToString( testPlanVersion.updatedAt, 'YY.MM.DD' diff --git a/client/utils/createIssueLink.js b/client/utils/createIssueLink.js index a196925a6..253d17517 100644 --- a/client/utils/createIssueLink.js +++ b/client/utils/createIssueLink.js @@ -12,6 +12,7 @@ const atLabelMap = { const createIssueLink = ({ isCandidateReview = false, isCandidateReviewChangesRequested = false, + testPlanDirectory, testPlanTitle, versionString, testTitle = null, @@ -24,7 +25,7 @@ const createIssueLink = ({ conflictMarkdown = null, reportLink = null }) => { - if (!(testPlanTitle || versionString || atName)) { + if (!(testPlanDirectory || testPlanTitle || versionString || atName)) { throw new Error('Cannot create issue link due to missing parameters'); } @@ -49,7 +50,6 @@ const createIssueLink = ({ } const labels = - 'app,' + (isCandidateReview ? 'candidate-review,' : '') + `${atLabelMap[atName]},` + (isCandidateReviewChangesRequested ? 'changes-requested' : 'feedback'); @@ -89,15 +89,27 @@ const createIssueLink = ({ `[${shortenedUrl}](${modifiedRenderedUrl})\n` + reportLinkFormatted + atFormatted + - browserFormatted; + browserFormatted + + '\n'; } + const hiddenIssueMetadata = JSON.stringify({ + testPlanDirectory, + versionString, + atName, + browserName, + testRowNumber, + isCandidateReview, + isCandidateReviewChangesRequested + }); + let body = `## Description of Behavior\n\n` + `\n\n` + testSetupFormatted + - `\n\n\n\n` + - ``; + `\n` + + ``; if (conflictMarkdown) { body += `\n${conflictMarkdown}`; @@ -126,7 +138,6 @@ export const getIssueSearchLink = ({ } const query = [ - `label:app`, isCandidateReview ? `label:candidate-review` : '', isCandidateReviewChangesRequested ? `label:changes-requested` diff --git a/server/graphql-schema.js b/server/graphql-schema.js index e750cc2e8..528b93829 100644 --- a/server/graphql-schema.js +++ b/server/graphql-schema.js @@ -230,11 +230,15 @@ const graphqlSchema = gql` Gets the most recent version imported from the test plan's directory. """ latestTestPlanVersion: TestPlanVersion - """ Gets all historic versions of the test plan. """ testPlanVersions: [TestPlanVersion]! + """ + A list of all issues which have filed through "Raise an Issue" buttons + in the app. Note that results will be cached for at least ten seconds. + """ + issues: [Issue]! } """ @@ -825,6 +829,17 @@ const graphqlSchema = gql` The time the issue was closed, if it was closed. """ closedAt: Timestamp + """ + The AT associated with the issue. Although there are not currently any + cases where we generate GitHub issues without an associated AT, that + may not remain true forever and we do support this field being + undefined. + """ + at: At + """ + The browser associated with the issue, which may not be present. + """ + browser: Browser } """ @@ -889,9 +904,8 @@ const graphqlSchema = gql` """ finalizedTestResults: [TestResult] """ - These are the different feedback and requested change items created for - the TestPlanReport and retrieved from GitHub. Note that results will be - cached for one minute. + A list of all issues which have filed through "Raise an Issue" buttons + in the app. Note that results will be cached for at least ten seconds. """ issues: [Issue]! """ diff --git a/server/resolvers/TestPlan/index.js b/server/resolvers/TestPlan/index.js new file mode 100644 index 000000000..60c147e0f --- /dev/null +++ b/server/resolvers/TestPlan/index.js @@ -0,0 +1,5 @@ +const issues = require('./issuesResolver'); + +module.exports = { + issues +}; diff --git a/server/resolvers/TestPlan/issuesResolver.js b/server/resolvers/TestPlan/issuesResolver.js new file mode 100644 index 000000000..be59aa8f8 --- /dev/null +++ b/server/resolvers/TestPlan/issuesResolver.js @@ -0,0 +1,6 @@ +const { getIssues } = require('../TestPlanReport/issuesResolver'); + +const issuesResolver = (testPlan, _, context) => + getIssues({ testPlan, context }); + +module.exports = issuesResolver; diff --git a/server/resolvers/TestPlanReport/issuesResolver.js b/server/resolvers/TestPlanReport/issuesResolver.js index 7f0ca22bf..8194fc250 100644 --- a/server/resolvers/TestPlanReport/issuesResolver.js +++ b/server/resolvers/TestPlanReport/issuesResolver.js @@ -1,55 +1,76 @@ const { GithubService } = require('../../services'); const convertDateToString = require('../../util/convertDateToString'); -const issuesResolver = async testPlanReport => { +const issuesResolver = (testPlanReport, _, context) => + getIssues({ testPlanReport, context }); + +const getIssues = async ({ testPlanReport, testPlan, context }) => { + const [ats, browsers] = await Promise.all([ + context.atLoader.getAll(), + context.browserLoader.getAll() + ]); + const issues = await GithubService.getAllIssues(); - const { at, testPlanVersion } = testPlanReport; + const getHiddenIssueMetadata = issue => { + return JSON.parse( + issue.body.match( + // Since this is human editable it should be okay for the + // JSON part to be multiline, and for additional comments + // to follow this metadata comment + // + )?.[1] ?? 'null' + ); + }; - const searchTestPlanTitle = testPlanVersion.title; + return issues + .filter(issue => { + const hiddenIssueMetadata = getHiddenIssueMetadata(issue); - const searchVersionString = `V${convertDateToString( - testPlanVersion.updatedAt, - 'YY.MM.DD' - )}`; + if (testPlanReport) { + const { at, browser, testPlanVersion } = testPlanReport; - let searchAtName; - if (at.name === 'JAWS' || at.name === 'NVDA') { - searchAtName = at.name.toLowerCase(); - } else { - searchAtName = 'vo'; - } + const versionString = `V${convertDateToString( + testPlanVersion.updatedAt, + 'YY.MM.DD' + )}`; - return issues - .filter(({ labels, body }) => { - return ( - labels.find(({ name }) => name === searchAtName) && - body?.includes(searchTestPlanTitle) && - body?.includes(searchVersionString) - ); + return ( + hiddenIssueMetadata && + hiddenIssueMetadata.testPlanDirectory === + testPlanVersion.directory && + hiddenIssueMetadata.versionString === versionString && + hiddenIssueMetadata.atName === at.name && + (!hiddenIssueMetadata.browserName || + hiddenIssueMetadata.browserName === browser.name) + ); + } else if (testPlan) { + return ( + hiddenIssueMetadata && + hiddenIssueMetadata.testPlanDirectory === testPlan.directory + ); + } }) .map(issue => { - const { - title, - user, - labels, - state, - html_url, - id: topCommentId - } = issue; - const testNumberMatch = title.match(/\sTest \d+,/g); - const testNumberSubstring = testNumberMatch - ? testNumberMatch[0] - : ''; - const testNumberFilteredByAt = testNumberSubstring - ? testNumberSubstring.match(/\d+/g)[0] - : null; + const hiddenIssueMetadata = getHiddenIssueMetadata(issue); + + const { title, user, state, html_url, id: topCommentId } = issue; - const labelNames = labels.map(label => label.name); + const feedbackType = + hiddenIssueMetadata.isCandidateReviewChangesRequested + ? 'CHANGES_REQUESTED' + : 'FEEDBACK'; - const feedbackType = labelNames.includes('changes-requested') - ? 'CHANGES_REQUESTED' - : 'FEEDBACK'; + const at = ats.find( + at => + at.name.toLowerCase() === + hiddenIssueMetadata.atName?.toLowerCase() + ); + const browser = browsers.find( + browser => + browser.name.toLowerCase() === + hiddenIssueMetadata.browserName?.toLowerCase() + ); return { author: user.login, @@ -57,12 +78,15 @@ const issuesResolver = async testPlanReport => { createdAt: issue.created_at, closedAt: issue.closed_at, link: `${html_url}#issue-${topCommentId}`, - isCandidateReview: labelNames.includes('candidate-review'), + isCandidateReview: hiddenIssueMetadata.isCandidateReview, feedbackType, isOpen: state === 'open', - testNumberFilteredByAt + testNumberFilteredByAt: hiddenIssueMetadata.testRowNumber, + at, + browser }; }); }; module.exports = issuesResolver; +module.exports.getIssues = getIssues; diff --git a/server/resolvers/index.js b/server/resolvers/index.js index 09644a406..a8fad8916 100644 --- a/server/resolvers/index.js +++ b/server/resolvers/index.js @@ -24,6 +24,7 @@ const User = require('./User'); const AtOperations = require('./AtOperations'); const AtVersionOperations = require('./AtVersionOperations'); const BrowserOperations = require('./BrowserOperations'); +const TestPlan = require('./TestPlan'); const TestPlanVersion = require('./TestPlanVersion'); const TestPlanReport = require('./TestPlanReport'); const TestPlanReportOperations = require('./TestPlanReportOperations'); @@ -65,6 +66,7 @@ const resolvers = { AtVersionOperations, BrowserOperations, User, + TestPlan, TestPlanVersion, TestPlanReport, TestPlanRun, diff --git a/server/services/GithubService.js b/server/services/GithubService.js index c69ca0807..84b04b351 100644 --- a/server/services/GithubService.js +++ b/server/services/GithubService.js @@ -1,5 +1,5 @@ const axios = require('axios'); -const NodeCache = require('node-cache'); +const staleWhileRevalidate = require('../util/staleWhileRevalidate'); const { ENVIRONMENT, @@ -29,17 +29,14 @@ const permissionScopes = [ ]; const permissionScopesURI = encodeURI(permissionScopes.join(' ')); const graphQLEndpoint = `${GITHUB_GRAPHQL_SERVER}/graphql`; -const nodeCache = new NodeCache(); -const getAllIssuesFromGitHub = async () => { +const getAllIssues = async () => { let currentResults = []; let page = 1; // eslint-disable-next-line no-constant-condition while (true) { - const issuesEndpoint = - `${GITHUB_ISSUES_API_URL}/issues` + - `?labels=app&state=all&per_page=100`; + const issuesEndpoint = `${GITHUB_ISSUES_API_URL}/issues?state=all&per_page=100`; const url = `${issuesEndpoint}&page=${page}`; const auth = { username: GITHUB_CLIENT_ID, @@ -47,10 +44,15 @@ const getAllIssuesFromGitHub = async () => { }; const response = await axios.get(url, { auth }); - // https://docs.github.com/en/rest/issues/issues#list-repository-issues - // Filter out Pull Requests. GitHub's REST API v3 also considers every - // pull request an issue. - const issues = response.data.filter(data => !data.pull_request); + const issues = response.data + // https://docs.github.com/en/rest/issues/issues#list-repository-issues + // Filter out Pull Requests. GitHub's REST API v3 also considers every + // pull request an issue. + .filter(data => !data.pull_request) + // Our issue API should only return issues that were originally + // created by the app, indicated by the presence of metadata + // hidden in a comment + .filter(data => data.body.includes('ARIA_AT_APP_ISSUE_DATA')); currentResults = [...currentResults, ...issues]; @@ -67,31 +69,6 @@ const getAllIssuesFromGitHub = async () => { return currentResults; }; -let activeIssuePromise; - -const getAllIssues = async () => { - const cacheResult = nodeCache.get('allIssues'); - - if (cacheResult) return cacheResult; - - if (!activeIssuePromise) { - // eslint-disable-next-line no-async-promise-executor - activeIssuePromise = new Promise(async resolve => { - const result = await getAllIssuesFromGitHub(); - - nodeCache.set('allIssues', result, 60 /* 1 min */); - - activeIssuePromise = null; - - resolve(); - }); - } - - await activeIssuePromise; - - return nodeCache.get('allIssues'); -}; - module.exports = { graphQLEndpoint, getOauthUrl: ({ state = '' }) => { @@ -164,5 +141,7 @@ module.exports = { return isMember; }, - getAllIssues + getAllIssues: staleWhileRevalidate(getAllIssues, { + millisecondsUntilStale: 10000 /* 10 seconds */ + }) }; diff --git a/server/tests/integration/graphql.test.js b/server/tests/integration/graphql.test.js index 4e213a540..9dee97ae5 100644 --- a/server/tests/integration/graphql.test.js +++ b/server/tests/integration/graphql.test.js @@ -137,7 +137,6 @@ describe('graphql', () => { const excludedTypeNames = [ // Items formatted like this: // 'TestResult' - 'Issue', 'Vendor' ]; const excludedTypeNameAndField = [ @@ -291,6 +290,24 @@ describe('graphql', () => { testPlanVersions { id } + issues { + __typename + author + title + link + isCandidateReview + feedbackType + isOpen + testNumberFilteredByAt + createdAt + closedAt + at { + name + } + browser { + name + } + } } testPlans { directory From 373a1420b12f0c3ea9c75668ad599ea9820fa72c Mon Sep 17 00:00:00 2001 From: Paul Clue <67766160+Paul-Clue@users.noreply.github.com> Date: Wed, 27 Sep 2023 08:40:35 -0500 Subject: [PATCH 43/59] Relocate customToggle components and fix browser selection value --- client/components/ManageTestQueue/index.jsx | 539 ++++++++++--------- client/components/common/PhasePill/index.jsx | 6 +- server/graphql-schema.js | 9 +- 3 files changed, 278 insertions(+), 276 deletions(-) diff --git a/client/components/ManageTestQueue/index.jsx b/client/components/ManageTestQueue/index.jsx index 558eb04bb..40c523fb6 100644 --- a/client/components/ManageTestQueue/index.jsx +++ b/client/components/ManageTestQueue/index.jsx @@ -117,6 +117,97 @@ const DisclosureContainer = styled.div` } `; +const CustomToggleDiv = styled.div` + background-color: transparent; + width: 100%; + height: 38px; + text-align: center; + + .icon-container { + float: right; + margin-top: 2px; + margin-right: 3px; + } + .icon-chevron { + font-size: 0.8rem; + } +`; + +const CustomToggleP = styled.p` + border: 1px solid #ced4da; + border-radius: 0.375rem; + background-color: #fff; + padding: 2px; + width: 100%; + height: 38px; + cursor: default; + display: inline-block; +`; + +const CustomToggleSpan = styled.span` + float: left; + margin-top: 2px; + margin-left: 20px; + background-color: ${props => + props.phaseLabel === 'Phase Selection' + ? '#fff' + : props.phaseLabel === 'Candidate' + ? '#ff6c00' + : props.phaseLabel === 'Recommended' + ? '#8441de' + : 'black'}; + border-radius: 14px; + padding: 2px 15px; + font-size: 1rem; + font-weight: 400; + color: ${props => + props.phaseLabel === 'Phase Selection' ? 'black' : '#fff'}; +`; + +const CustomToggle = React.forwardRef(({ children, onClick }, ref) => ( + { + e.preventDefault(); + onClick(e); + }} + > + { + e.preventDefault(); + onClick(e); + }} + > + + {children} + + + + + + +)); + +const CustomMenu = React.forwardRef(({ children, className }, ref) => { + const value = ''; + + return ( +
    +
      + {React.Children.toArray(children).filter( + child => + !value || + child.props.children.toLowerCase().startsWith(value) + )} +
    +
    + ); +}); + const ManageTestQueue = ({ enableManageRequiredReports = false, ats = [], @@ -212,79 +303,6 @@ const ManageTestQueue = ({ ) ]); - const toggleDivStyle = { - backgroundColor: 'transparent', - width: '100%', - height: '38px', - textAlign: 'center' - }; - - const togglePStyle = { - border: '1px solid #ced4da', - borderRadius: '0.375rem', - backgroundColor: '#fff', - padding: '2px', - width: '100%', - height: '38px', - cursor: 'default', - display: 'inline-block' - }; - - const toggleSpanStyle = { - float: 'left', - marginTop: '2px', - marginLeft: '20px', - backgroundColor: - updatePhaseSelection === 'Phase Selection' - ? '#fff' - : updatePhaseSelection === 'Candidate' - ? '#ff6c00' - : updatePhaseSelection === 'Recommended' - ? '#8441de' - : 'black', - borderRadius: '14px', - padding: '2px 15px', - fontSize: '1rem', - fontWeight: '400', - color: updatePhaseSelection === 'Phase Selection' ? 'black' : '#fff' - }; - - const CustomToggle = React.forwardRef(({ children, onClick }, ref) => ( -
    { - e.preventDefault(); - onClick(e); - }} - > -

    { - e.preventDefault(); - onClick(e); - }} - > - {children} - - - -

    -
    - )); - const setPhase = phase => { setUpdatePhaseSelection(phase); if (phase === 'Candidate') { @@ -294,21 +312,6 @@ const ManageTestQueue = ({ setUpdatePhaseForButton('RECOMMENDED'); } }; - const CustomMenu = React.forwardRef(({ children, className }, ref) => { - const value = ''; - - return ( -
    -
      - {React.Children.toArray(children).filter( - child => - !value || - child.props.children.toLowerCase().startsWith(value) - )} -
    -
    - ); - }); const onOpenShowEditAtBrowserModal = ( type = 'edit', @@ -346,179 +349,175 @@ const ManageTestQueue = ({ let atId = updateAtForButton; let browserId = updateBrowserForButton; - mutation === 'createRequiredReport' - ? await triggerLoad(async () => { - try { - atBrowserCombinations.forEach( - ({ at, browser, phase }) => { - if ( - updateAtForButton === at.id && - updateBrowserForButton === browser.id && - updatePhaseForButton === phase - ) { - throw new Error( - 'A duplicate Entry was detected in the table' - ); - } - } - ); - const { data } = await createRequiredReport({ - variables: { - atId: atId, - browserId: browserId, - phase: `IS_${updatePhaseForButton}` - } - }); - - const createdRequiredReport = - data.requiredReport.createRequiredReport; - - // Verify that the created required report was actually created before updating - // the dataset - if (createdRequiredReport) { - setAtBrowserCombinations( - [ - ...atBrowserCombinations, - { - at: ats.find( - at => - at.id === - createdRequiredReport.atId - ), - browser: browsers.find( - browser => - browser.id === - createdRequiredReport.browserId - ), - phase: updatePhaseForButton - } - ].sort((a, b) => { - if (a.phase < b.phase) return -1; - if (a.phase > b.phase) return 1; - return a.at.name.localeCompare(b.at.name); - }) - ); - } - } catch (error) { - setShowThemedModal(true); - setThemedModalTitle( - 'Error Updating Required Reports Table' - ); - setThemedModalContent(<>{error.message}); - } - }, 'Adding Phase requirement to the required reports table') - : mutation === 'updateRequiredReport' - ? await triggerLoad(async () => { - try { - atBrowserCombinations.forEach( - ({ at, browser, phase }) => { - if ( - updateAtSelection === at.id && - updateBrowserSelection === browser.id && - updatePhaseForUpdate === phase - ) { - throw new Error( - 'Cannnot update to a duplicate entry' - ); - } - } - ); - - const { data } = await updateRequiredReport({ - variables: { - atId: updateAtIdForUpdate, - browserId: updateBrowserIdForUpdate, - phase: `IS_${updatePhaseForUpdate}`, - updateAtId: updateAtSelection, - updateBrowserId: updateBrowserSelection - } - }); - - const updatedRequiredReport = - data.requiredReport.updateRequiredReport; - - // Verify that the created required report was actually created before updating - // the dataset - if (updatedRequiredReport) { - setAtBrowserCombinations( - [ - ...atBrowserCombinations, - { - at: ats.find( - at => - at.id === - updatedRequiredReport.atId - ), - browser: browsers.find( - browser => - browser.id === - updatedRequiredReport.browserId - ), - phase: updatePhaseForUpdate - } - ] - .filter(row => { - if ( - row.at.id === updateAtIdForUpdate && - row.browser.id === - updateBrowserIdForUpdate && - row.phase == updatePhaseForUpdate - ) { - return false; - } - return true; - }) - .sort((a, b) => { - if (a.phase < b.phase) return -1; - if (a.phase > b.phase) return 1; - return a.at.name.localeCompare(b.at.name); - }) - ); - } - } catch (error) { - setShowThemedModal(true); - setThemedModalTitle( - 'Error Updating Required Reports Table' - ); - setThemedModalContent(<>{error.message}); - } - }, 'Adding Phase requirement to the required reports table') - : mutation === 'deleteRequiredReport' - ? await triggerLoad(async () => { - const { data } = await deleteRequiredReport({ - variables: { - atId: updateAtIdForUpdate, - browserId: updateBrowserIdForUpdate, - phase: `IS_${updatePhaseForUpdate}` - } - }); - - const deletedRequiredReport = - data.requiredReport.deleteRequiredReport; - - if (deletedRequiredReport) { - setAtBrowserCombinations( - [...atBrowserCombinations] - .filter(row => { - if ( - row.at.id === updateAtIdForUpdate && - row.browser.id === - updateBrowserIdForUpdate && - row.phase == updatePhaseForUpdate - ) { - return false; - } - return true; - }) - .sort((a, b) => { - if (a.phase < b.phase) return -1; - if (a.phase > b.phase) return 1; - return a.at.name.localeCompare(b.at.name); - }) - ); - } - }, 'Adding Phase requirement to the required reports table') - : null; + if (mutation === 'createRequiredReport') { + await triggerLoad(async () => { + try { + atBrowserCombinations.forEach(({ at, browser, phase }) => { + if ( + updateAtForButton === at.id && + updateBrowserForButton === browser.id && + updatePhaseForButton === phase + ) { + throw new Error( + 'A duplicate Entry was detected in the table' + ); + } + }); + const { data } = await createRequiredReport({ + variables: { + atId: atId, + browserId: browserId, + phase: `IS_${updatePhaseForButton}` + } + }); + + const createdRequiredReport = + data.requiredReport.createRequiredReport; + + // Verify that the created required report was actually created before updating + // the dataset + if (createdRequiredReport) { + setAtBrowserCombinations( + [ + ...atBrowserCombinations, + { + at: ats.find( + at => + at.id === createdRequiredReport.atId + ), + browser: browsers.find( + browser => + browser.id === + createdRequiredReport.browserId + ), + phase: updatePhaseForButton + } + ].sort((a, b) => { + if (a.phase < b.phase) return -1; + if (a.phase > b.phase) return 1; + return a.at.name.localeCompare(b.at.name); + }) + ); + } + } catch (error) { + setShowThemedModal(true); + setThemedModalTitle( + 'Error Updating Required Reports Table' + ); + setThemedModalContent(<>{error.message}); + } + }, 'Adding Phase requirement to the required reports table'); + } + if (mutation === 'updateRequiredReport') { + await triggerLoad(async () => { + try { + atBrowserCombinations.forEach(({ at, browser, phase }) => { + if ( + updateAtSelection === at.id && + updateBrowserSelection === browser.id && + updatePhaseForUpdate === phase + ) { + throw new Error( + 'Cannnot update to a duplicate entry' + ); + } + }); + + const { data } = await updateRequiredReport({ + variables: { + atId: updateAtIdForUpdate, + browserId: updateBrowserIdForUpdate, + phase: `IS_${updatePhaseForUpdate}`, + updateAtId: updateAtSelection, + updateBrowserId: updateBrowserSelection + } + }); + + const updatedRequiredReport = + data.requiredReport.updateRequiredReport; + + // Verify that the created required report was actually created before updating + // the dataset + if (updatedRequiredReport) { + setAtBrowserCombinations( + [ + ...atBrowserCombinations, + { + at: ats.find( + at => + at.id === updatedRequiredReport.atId + ), + browser: browsers.find( + browser => + browser.id === + updatedRequiredReport.browserId + ), + phase: updatePhaseForUpdate + } + ] + .filter(row => { + if ( + row.at.id === updateAtIdForUpdate && + row.browser.id === + updateBrowserIdForUpdate && + row.phase == updatePhaseForUpdate + ) { + return false; + } + return true; + }) + .sort((a, b) => { + if (a.phase < b.phase) return -1; + if (a.phase > b.phase) return 1; + return a.at.name.localeCompare(b.at.name); + }) + ); + } + } catch (error) { + setShowThemedModal(true); + setThemedModalTitle( + 'Error Updating Required Reports Table' + ); + setThemedModalContent(<>{error.message}); + } + }, 'Adding Phase requirement to the required reports table'); + } + if (mutation === 'deleteRequiredReport') { + await triggerLoad(async () => { + const { data } = await deleteRequiredReport({ + variables: { + atId: updateAtIdForUpdate, + browserId: updateBrowserIdForUpdate, + phase: `IS_${updatePhaseForUpdate}` + } + }); + + const deletedRequiredReport = + data.requiredReport.deleteRequiredReport; + + if (deletedRequiredReport) { + setAtBrowserCombinations( + [...atBrowserCombinations] + .filter(row => { + if ( + row.at.id === updateAtIdForUpdate && + row.browser.id === + updateBrowserIdForUpdate && + row.phase == updatePhaseForUpdate + ) { + return false; + } + return true; + }) + .sort((a, b) => { + if (a.phase < b.phase) return -1; + if (a.phase > b.phase) return 1; + return a.at.name.localeCompare(b.at.name); + }) + ); + } + }, 'Adding Phase requirement to the required reports table'); + } }; const [addAtVersion] = useMutation(ADD_AT_VERSION_MUTATION); @@ -1106,7 +1105,6 @@ const ManageTestQueue = ({ Add required reports for a specific AT and Browser pair - {/* section: */}
    @@ -1263,6 +1261,9 @@ const ManageTestQueue = ({ setUpdateListAtSelection( 'Select an At' ); + setUpdateListBrowserSelection( + 'Select a browser' + ); runMutationForRequiredReportTable( 'createRequiredReport' ); @@ -1602,12 +1603,20 @@ const ManageTestQueue = ({ ); }; +CustomToggle.propTypes = { + children: PropTypes.array, + className: PropTypes.string, + onClick: PropTypes.func +}; + +CustomMenu.propTypes = { + children: PropTypes.array, + className: PropTypes.string +}; + ManageTestQueue.propTypes = { ats: PropTypes.array, browsers: PropTypes.array, - children: PropTypes.array, - className: PropTypes.string, - onClick: PropTypes.func, testPlanVersions: PropTypes.array, enableManageRequiredReports: PropTypes.bool, triggerUpdate: PropTypes.func diff --git a/client/components/common/PhasePill/index.jsx b/client/components/common/PhasePill/index.jsx index 66c3ed022..9661db212 100644 --- a/client/components/common/PhasePill/index.jsx +++ b/client/components/common/PhasePill/index.jsx @@ -54,11 +54,11 @@ const PhasePill = ({ forHeader = false, children: phase }) => { - let classes = fullWidth ? 'full-width' : ''; - classes = forHeader ? `${classes} for-header` : classes; + let className = fullWidth ? 'full-width' : ''; + className = forHeader ? `${className} for-header` : className; return ( str) .join(' ')} > diff --git a/server/graphql-schema.js b/server/graphql-schema.js index a99e57b81..292541a66 100644 --- a/server/graphql-schema.js +++ b/server/graphql-schema.js @@ -175,8 +175,6 @@ const graphqlSchema = gql` The return type for createRequiredReport. """ type RequiredReport { - """ - """ atId: ID! browserId: ID! phase: RequiredReportPhase! @@ -1073,16 +1071,12 @@ const graphqlSchema = gql` findOrCreateAtVersion(input: AtVersionInput!): AtVersion! } - """ - """ enum RequiredReportPhase { IS_CANDIDATE IS_RECOMMENDED } type RequiredReportOperations { - """ - """ createRequiredReport: RequiredReport! updateRequiredReport(atId: ID!, browserId: ID!): RequiredReport! deleteRequiredReport: RequiredReport! @@ -1274,8 +1268,7 @@ const graphqlSchema = gql` Get the available mutations for the given browser. """ browser(id: ID!): BrowserOperations! - """ - """ + requiredReport( atId: ID! browserId: ID! From 0897edb218baf97c3a586e29b8c83973317215d3 Mon Sep 17 00:00:00 2001 From: Paul Clue <67766160+Paul-Clue@users.noreply.github.com> Date: Wed, 27 Sep 2023 10:17:09 -0500 Subject: [PATCH 44/59] Add comment --- client/components/ManageTestQueue/index.jsx | 1 + 1 file changed, 1 insertion(+) diff --git a/client/components/ManageTestQueue/index.jsx b/client/components/ManageTestQueue/index.jsx index 40c523fb6..d6f6827a2 100644 --- a/client/components/ManageTestQueue/index.jsx +++ b/client/components/ManageTestQueue/index.jsx @@ -164,6 +164,7 @@ const CustomToggleSpan = styled.span` props.phaseLabel === 'Phase Selection' ? 'black' : '#fff'}; `; +// You can learn everything about this component here: https://react-bootstrap.netlify.app/docs/components/dropdowns#custom-dropdown-components const CustomToggle = React.forwardRef(({ children, onClick }, ref) => ( Date: Wed, 27 Sep 2023 14:45:47 -0500 Subject: [PATCH 45/59] Fix tab issue on Phase select element --- client/components/ManageTestQueue/index.jsx | 11 ++++++++--- server/tests/integration/graphql.test.js | 2 -- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/client/components/ManageTestQueue/index.jsx b/client/components/ManageTestQueue/index.jsx index d6f6827a2..2460dccfd 100644 --- a/client/components/ManageTestQueue/index.jsx +++ b/client/components/ManageTestQueue/index.jsx @@ -117,12 +117,17 @@ const DisclosureContainer = styled.div` } `; -const CustomToggleDiv = styled.div` +const CustomToggleButton = styled.button` background-color: transparent; width: 100%; height: 38px; text-align: center; + border: none; + margin: 0; + padding: 0; + display: block; + .icon-container { float: right; margin-top: 2px; @@ -166,7 +171,7 @@ const CustomToggleSpan = styled.span` // You can learn everything about this component here: https://react-bootstrap.netlify.app/docs/components/dropdowns#custom-dropdown-components const CustomToggle = React.forwardRef(({ children, onClick }, ref) => ( - { e.preventDefault(); @@ -190,7 +195,7 @@ const CustomToggle = React.forwardRef(({ children, onClick }, ref) => ( /> - + )); const CustomMenu = React.forwardRef(({ children, className }, ref) => { diff --git a/server/tests/integration/graphql.test.js b/server/tests/integration/graphql.test.js index 2392af8cd..d711e7389 100644 --- a/server/tests/integration/graphql.test.js +++ b/server/tests/integration/graphql.test.js @@ -137,8 +137,6 @@ describe('graphql', () => { const excludedTypeNames = [ // Items formatted like this: // 'TestResult' - // 'RequiredReport', - // 'RequiredReportOperations', 'Issue', 'Vendor' ]; From 80b51b14fb8144337022bbfd65e31a42d187dc1f Mon Sep 17 00:00:00 2001 From: Stalgia Grigg Date: Thu, 28 Sep 2023 10:39:42 -0400 Subject: [PATCH 46/59] Change default data management sort status direction (#790) --- .../DataManagement/filterSortHooks.js | 21 +++++++++++-------- client/components/DataManagement/index.jsx | 12 +++++++++-- .../common/SortableTableHeader/index.js | 18 +++++++++++----- client/tests/DataManagement.test.jsx | 7 ++++++- 4 files changed, 41 insertions(+), 17 deletions(-) diff --git a/client/components/DataManagement/filterSortHooks.js b/client/components/DataManagement/filterSortHooks.js index 71bff7989..7123346fd 100644 --- a/client/components/DataManagement/filterSortHooks.js +++ b/client/components/DataManagement/filterSortHooks.js @@ -165,23 +165,26 @@ export const useDataManagementTableFiltering = ( export const useDataManagementTableSorting = ( testPlans, testPlanVersions, - ats + ats, + initialSortDirection = TABLE_SORT_ORDERS.ASC ) => { const [activeSort, setActiveSort] = useState({ key: DATA_MANAGEMENT_TABLE_SORT_OPTIONS.PHASE, - direction: TABLE_SORT_ORDERS.ASC + direction: initialSortDirection }); const { derivedOverallPhaseByTestPlanId } = useDerivedOverallPhaseByTestPlanId(testPlans, testPlanVersions); const sortedTestPlans = useMemo(() => { + // Ascending and descending interpreted differently for statuses + // (ascending = earlier phase first, descending = later phase first) const phaseOrder = { - NOT_STARTED: -1, - RD: 0, - DRAFT: 1, - CANDIDATE: 2, - RECOMMENDED: 3 + NOT_STARTED: 4, + RD: 3, + DRAFT: 2, + CANDIDATE: 1, + RECOMMENDED: 0 }; const directionMod = activeSort.direction === TABLE_SORT_ORDERS.ASC ? -1 : 1; @@ -192,7 +195,7 @@ export const useDataManagementTableSorting = ( const sortByAts = (a, b) => { const countA = ats.length; // Stubs based on current rendering in DataManagementRow const countB = ats.length; - if (countA === countB) return sortByName(a, b); + if (countA === countB) return sortByName(a, b, -1); return directionMod * (countA - countB); }; @@ -202,7 +205,7 @@ export const useDataManagementTableSorting = ( const testPlanVersionOverallB = derivedOverallPhaseByTestPlanId[b.id] ?? 'NOT_STARTED'; if (testPlanVersionOverallA === testPlanVersionOverallB) { - return sortByName(a, b, directionMod); + return sortByName(a, b, -1); } return ( directionMod * diff --git a/client/components/DataManagement/index.jsx b/client/components/DataManagement/index.jsx index 2f3fb8c60..3c68d48d7 100644 --- a/client/components/DataManagement/index.jsx +++ b/client/components/DataManagement/index.jsx @@ -8,7 +8,9 @@ import ManageTestQueue from '../ManageTestQueue'; import DataManagementRow from '@components/DataManagement/DataManagementRow'; import './DataManagement.css'; import { evaluateAuth } from '@client/utils/evaluateAuth'; -import SortableTableHeader from '../common/SortableTableHeader'; +import SortableTableHeader, { + TABLE_SORT_ORDERS +} from '../common/SortableTableHeader'; import FilterButtons from '../common/FilterButtons'; import { useDataManagementTableFiltering, @@ -61,7 +63,12 @@ const DataManagement = () => { ); const { sortedTestPlans, updateSort, activeSort } = - useDataManagementTableSorting(filteredTestPlans, testPlanVersions, ats); + useDataManagementTableSorting( + filteredTestPlans, + testPlanVersions, + ats, + TABLE_SORT_ORDERS.DESC + ); if (error) { return ( @@ -198,6 +205,7 @@ const DataManagement = () => { direction }) } + initialSortDirection={TABLE_SORT_ORDERS.DESC} /> R&D Version Draft Review diff --git a/client/components/common/SortableTableHeader/index.js b/client/components/common/SortableTableHeader/index.js index f3ccb1086..8dc1a24c2 100644 --- a/client/components/common/SortableTableHeader/index.js +++ b/client/components/common/SortableTableHeader/index.js @@ -55,10 +55,14 @@ export const TABLE_SORT_ORDERS = { DESC: 'DESCENDING' }; -const SortableTableHeader = ({ title, active, onSort = () => {} }) => { - const [currentSortOrder, setCurrentSortOrder] = useState( - TABLE_SORT_ORDERS.ASC - ); +const SortableTableHeader = ({ + title, + active, + onSort = () => {}, + initialSortDirection = TABLE_SORT_ORDERS.ASC +}) => { + const [currentSortOrder, setCurrentSortOrder] = + useState(initialSortDirection); const { setMessage } = useAriaLiveRegion(); @@ -129,7 +133,11 @@ const SortableTableHeader = ({ title, active, onSort = () => {} }) => { SortableTableHeader.propTypes = { title: PropTypes.string.isRequired, active: PropTypes.bool.isRequired, - onSort: PropTypes.func.isRequired + onSort: PropTypes.func.isRequired, + initialSortDirection: PropTypes.oneOf([ + TABLE_SORT_ORDERS.ASC, + TABLE_SORT_ORDERS.DESC + ]) }; export default SortableTableHeader; diff --git a/client/tests/DataManagement.test.jsx b/client/tests/DataManagement.test.jsx index fa5629717..bec23e584 100644 --- a/client/tests/DataManagement.test.jsx +++ b/client/tests/DataManagement.test.jsx @@ -127,7 +127,12 @@ const ats = []; // ATs are stubbed until this model is defined describe('useDataManagementTableSorting hook', () => { it('sorts by phase by default', () => { const { result } = renderHook(() => - useDataManagementTableSorting(testPlans, testPlanVersions, ats) + useDataManagementTableSorting( + testPlans, + testPlanVersions, + ats, + TABLE_SORT_ORDERS.DESC + ) ); expect(result.current.sortedTestPlans).toEqual([ testPlans[3], // RECOMMENDED From fae9cab497c4ad1d0b024ce845e90635a70937a7 Mon Sep 17 00:00:00 2001 From: Howard Edwards Date: Thu, 28 Sep 2023 13:31:32 -0500 Subject: [PATCH 47/59] Address edge case scenarios during the updating of TestPlanVersion phase with old TestPlanVersion results (#771) * Revise deprecation process when copying results from earlier version to avoid unexpected errors * Additional edge case management for updatePhaseResolver during copying of old test results * Address edge cases during phase update process when using old results * Update tests to include updatePhase process * Extend default timeout * Make db docs consistent between test and dev * Update database.md formatting --------- Co-authored-by: alflennik --- .github/workflows/runtest.yml | 1 + .../DataManagementRow/index.jsx | 55 +- config/test.env | 3 +- docs/database.md | 18 +- .../updatePhaseResolver.js | 175 ++++- server/scripts/populate-test-data/index.js | 23 + .../tests/integration/dataManagement.test.js | 709 ++++++++++++++++++ .../{test-queue.test.js => testQueue.test.js} | 137 ---- 8 files changed, 923 insertions(+), 198 deletions(-) create mode 100644 server/tests/integration/dataManagement.test.js rename server/tests/integration/{test-queue.test.js => testQueue.test.js} (83%) diff --git a/.github/workflows/runtest.yml b/.github/workflows/runtest.yml index 4d0d725aa..0a8684497 100644 --- a/.github/workflows/runtest.yml +++ b/.github/workflows/runtest.yml @@ -37,6 +37,7 @@ jobs: yarn sequelize:test db:migrate yarn sequelize:test db:seed:all yarn workspace server db-import-tests:test -c ${IMPORT_ARIA_AT_TESTS_COMMIT_1} + yarn workspace server db-import-tests:test -c ${IMPORT_ARIA_AT_TESTS_COMMIT_2} yarn workspace server db-import-tests:test yarn workspace server db-populate-sample-data:test - name: test diff --git a/client/components/DataManagement/DataManagementRow/index.jsx b/client/components/DataManagement/DataManagementRow/index.jsx index 914b179b7..eabe4e9c6 100644 --- a/client/components/DataManagement/DataManagementRow/index.jsx +++ b/client/components/DataManagement/DataManagementRow/index.jsx @@ -636,18 +636,14 @@ const DataManagementRow = ({ } let coveredReports = []; - let finalReportFound = false; - latestVersion.testPlanReports.forEach(testPlanReport => { const markedFinalAt = testPlanReport.markedFinalAt; const atName = testPlanReport.at.name; const browserName = testPlanReport.browser.name; const value = `${atName}_${browserName}`; - if (markedFinalAt && !coveredReports.includes(value)) { - finalReportFound = true; + if (markedFinalAt && !coveredReports.includes(value)) coveredReports.push(value); - } }); // Phase is "active" @@ -667,38 +663,23 @@ const DataManagementRow = ({ className="advance-button" variant="secondary" onClick={async () => { - if (finalReportFound) { - setShowAdvanceModal(true); - setAdvanceModalData({ - phase: derivePhaseName( - 'CANDIDATE' - ), - version: convertDateToString( - latestVersionDate, - 'YY.MM.DD' - ), - advanceFunc: () => { - setShowAdvanceModal(false); - handleClickUpdateTestPlanVersionPhase( - latestVersion.id, - 'CANDIDATE', - testPlanVersionDataToInclude - ); - }, - coveredReports - }); - } else { - setShowThemedModal(true); - setThemedModalTitle( - 'Error Updating Test Plan Version Phase' - ); - setThemedModalContent( - <> - No reports have been marked - as final. - - ); - } + setShowAdvanceModal(true); + setAdvanceModalData({ + phase: derivePhaseName('CANDIDATE'), + version: convertDateToString( + latestVersionDate, + 'YY.MM.DD' + ), + advanceFunc: () => { + setShowAdvanceModal(false); + handleClickUpdateTestPlanVersionPhase( + latestVersion.id, + 'CANDIDATE', + testPlanVersionDataToInclude + ); + }, + coveredReports + }); }} > Advance to Candidate diff --git a/config/test.env b/config/test.env index 22c5a305f..5556e6327 100644 --- a/config/test.env +++ b/config/test.env @@ -10,7 +10,8 @@ ENVIRONMENT=test ALLOW_FAKE_ROLE=true IMPORT_CONFIG=../config/test.env -IMPORT_ARIA_AT_TESTS_COMMIT_1=1aa3b74d24d340362e9f511eae33788d55487d12 +IMPORT_ARIA_AT_TESTS_COMMIT_1=5fe7afd82fe51c185b8661276105190a59d47322 +IMPORT_ARIA_AT_TESTS_COMMIT_2=1aa3b74d24d340362e9f511eae33788d55487d12 GITHUB_OAUTH_SERVER=http://localhost:4466 GITHUB_GRAPHQL_SERVER=http://localhost:4466 diff --git a/docs/database.md b/docs/database.md index 0be2c999b..b90318157 100644 --- a/docs/database.md +++ b/docs/database.md @@ -3,8 +3,9 @@ The database migrations are managed by [Sequelize](https://sequelize.org/). To read and understand the schema, see the Sequelize models that represent the data in `server/models`. Each model represents a table in the database. ## Setting up a local database for development + 0. Install PostgreSQL - - Mac + - Mac ``` brew install postgresql@14 brew services start postgresql@14 @@ -30,7 +31,9 @@ The database migrations are managed by [Sequelize](https://sequelize.org/). To r ``` 4. Import the most recent tests from the [aria-at repository](https://github.com/w3c/aria-at): ``` - yarn db-import-tests:dev + yarn db-import-tests:dev -c 5fe7afd82fe51c185b8661276105190a59d47322; + yarn db-import-tests:dev -c 1aa3b74d24d340362e9f511eae33788d55487d12; + yarn db-import-tests:dev; ``` All at once: @@ -45,6 +48,8 @@ fi; yarn sequelize db:migrate; yarn sequelize db:seed:all; +yarn db-import-tests:dev -c 5fe7afd82fe51c185b8661276105190a59d47322; +yarn db-import-tests:dev -c 1aa3b74d24d340362e9f511eae33788d55487d12; yarn db-import-tests:dev; ``` @@ -87,6 +92,8 @@ The instructions are similar for the test database, with one extra step: yarn db-init:test; yarn sequelize:test db:migrate; yarn sequelize:test db:seed:all; +yarn workspace server db-import-tests:test -c 5fe7afd82fe51c185b8661276105190a59d47322; +yarn workspace server db-import-tests:test -c 1aa3b74d24d340362e9f511eae33788d55487d12; yarn workspace server db-import-tests:test; yarn workspace server db-populate-sample-data:test; ``` @@ -94,9 +101,10 @@ yarn workspace server db-populate-sample-data:test; ### Inspecting the database To connect to the Postgres table locally: - ``` - yarn run dotenv -e config/dev.env psql - ``` + +``` +yarn run dotenv -e config/dev.env psql +``` ## Application development: modifications to the schema diff --git a/server/resolvers/TestPlanVersionOperations/updatePhaseResolver.js b/server/resolvers/TestPlanVersionOperations/updatePhaseResolver.js index ce6248ba2..564850198 100644 --- a/server/resolvers/TestPlanVersionOperations/updatePhaseResolver.js +++ b/server/resolvers/TestPlanVersionOperations/updatePhaseResolver.js @@ -2,7 +2,8 @@ const { AuthenticationError } = require('apollo-server'); const { updateTestPlanReport, getTestPlanReports, - getOrCreateTestPlanReport + getOrCreateTestPlanReport, + removeTestPlanReport } = require('../../models/services/TestPlanReportService'); const conflictsResolver = require('../TestPlanReport/conflictsResolver'); const finalizedTestResultsResolver = require('../TestPlanReport/finalizedTestResultsResolver'); @@ -51,6 +52,7 @@ const updatePhaseResolver = async ( let testPlanVersionDataToInclude; let testPlanReportsDataToIncludeId = []; + let createdTestPlanReportIdsFromOldResults = []; // The testPlanVersion being updated const testPlanVersion = await getTestPlanVersionById(testPlanVersionId); @@ -117,14 +119,23 @@ const updatePhaseResolver = async ( // between versions let keptTestIds = {}; for (const testPlanVersionTest of testPlanVersion.tests) { - const testHash = hashTest(testPlanVersionTest); + // Found odd instances of rowNumber being an int instead of being how it + // currently is; imported as a string + // Ensuring proper hashes are done here + const testHash = hashTest({ + ...testPlanVersionTest, + rowNumber: String(testPlanVersionTest.rowNumber) + }); if (keptTestIds[testHash]) continue; for (const testPlanVersionDataToIncludeTest of testPlanVersionDataToInclude.tests) { - const testDataToIncludeHash = hashTest( - testPlanVersionDataToIncludeTest - ); + const testDataToIncludeHash = hashTest({ + ...testPlanVersionDataToIncludeTest, + rowNumber: String( + testPlanVersionDataToIncludeTest.rowNumber + ) + }); if (testHash === testDataToIncludeHash) { if (!keptTestIds[testHash]) @@ -150,7 +161,7 @@ const updatePhaseResolver = async ( // Then this data should be preserved testResultsToSave[testId] = testResult; } else { - // TODO: Track which tests cannot be preserved + // TODO: Return information on which tests cannot be preserved } }); } @@ -163,6 +174,10 @@ const updatePhaseResolver = async ( browserId: testPlanReportDataToInclude.browserId }); + createdTestPlanReportIdsFromOldResults.push( + createdTestPlanReport.id + ); + const createdTestPlanRun = await createTestPlanRun({ testerUserId: testPlanRun.testerUserId, testPlanReportId: createdTestPlanReport.id @@ -231,9 +246,88 @@ const updatePhaseResolver = async ( testResults.push(testResultToSave); } + // Update TestPlanRun test results to be used in metrics evaluation + // afterward await updateTestPlanRun(createdTestPlanRun.id, { testResults }); + + // Update metrics for TestPlanReport + const { testPlanReport: populatedTestPlanReport } = + await populateData( + { testPlanReportId: createdTestPlanReport.id }, + { context } + ); + + const runnableTests = runnableTestsResolver( + populatedTestPlanReport + ); + let updateParams = {}; + + // Mark the report as final if previously was on the TestPlanVersion being + // deprecated + if (testPlanReportDataToInclude.markedFinalAt) + updateParams = { markedFinalAt: new Date() }; + + // Calculate the metrics (happens if updating to DRAFT) + const conflicts = await conflictsResolver( + populatedTestPlanReport, + null, + context + ); + + if (conflicts.length > 0) { + // Then no chance to have finalized reports, and means it hasn't been + // marked as final yet + updateParams = { + ...updateParams, + metrics: { + ...populatedTestPlanReport.metrics, + conflictsCount: conflicts.length + } + }; + } else { + const finalizedTestResults = + await finalizedTestResultsResolver( + populatedTestPlanReport, + null, + context + ); + + if ( + !finalizedTestResults || + !finalizedTestResults.length + ) { + // Just update with current { markedFinalAt } if available + updateParams = { + ...updateParams, + metrics: { + ...populatedTestPlanReport.metrics + } + }; + } else { + const metrics = getMetrics({ + testPlanReport: { + ...populatedTestPlanReport, + finalizedTestResults, + runnableTests + } + }); + + updateParams = { + ...updateParams, + metrics: { + ...populatedTestPlanReport.metrics, + ...metrics + } + }; + } + } + + await updateTestPlanReport( + populatedTestPlanReport.id, + updateParams + ); } } } @@ -262,12 +356,26 @@ const updatePhaseResolver = async ( throw new Error('No test plan reports found.'); } + // If there is at least one report that wasn't created by the old reports then do the exception + // check if ( - !testPlanReports.some(({ markedFinalAt }) => markedFinalAt) && - (phase === 'CANDIDATE' || phase === 'RECOMMENDED') + testPlanReports.some( + ({ id }) => !createdTestPlanReportIdsFromOldResults.includes(id) + ) ) { - // Do not update phase if no reports marked as final were found - throw new Error('No reports have been marked as final.'); + if ( + !testPlanReports.some(({ markedFinalAt }) => markedFinalAt) && + (phase === 'CANDIDATE' || phase === 'RECOMMENDED') + ) { + // Throw away newly created test plan reports if exception was hit + if (createdTestPlanReportIdsFromOldResults.length) + for (const createdTestPlanReportId of createdTestPlanReportIdsFromOldResults) { + await removeTestPlanReport(createdTestPlanReportId); + } + + // Do not update phase if no reports marked as final were found + throw new Error('No reports have been marked as final.'); + } } if (phase === 'CANDIDATE') { @@ -304,6 +412,12 @@ const updatePhaseResolver = async ( }); if (missingAtBrowserCombinations.length) { + // Throw away newly created test plan reports if exception was hit + if (createdTestPlanReportIdsFromOldResults.length) + for (const createdTestPlanReportId of createdTestPlanReportIdsFromOldResults) { + await removeTestPlanReport(createdTestPlanReportId); + } + throw new Error( `Cannot set phase to ${phase.toLowerCase()} because the following` + ` required reports have not been collected or finalized:` + @@ -316,42 +430,67 @@ const updatePhaseResolver = async ( const runnableTests = runnableTestsResolver(testPlanReport); let updateParams = {}; + const isReportCreatedFromOldResults = + createdTestPlanReportIdsFromOldResults.includes(testPlanReport.id); + if (phase === 'DRAFT') { const conflicts = await conflictsResolver( testPlanReport, null, context ); - await updateTestPlanReport(testPlanReport.id, { + + updateParams = { metrics: { ...testPlanReport.metrics, conflictsCount: conflicts.length - }, - markedFinalAt: null - }); + } + }; + + // Nullify markedFinalAt if not using old result + if (!isReportCreatedFromOldResults) + updateParams = { ...updateParams, markedFinalAt: null }; + + await updateTestPlanReport(testPlanReport.id, updateParams); } - if (phase === 'CANDIDATE' || phase === 'RECOMMENDED') { + const shouldThrowErrorIfFound = + (phase === 'CANDIDATE' || phase === 'RECOMMENDED') && + isReportCreatedFromOldResults + ? false + : testPlanReport.markedFinalAt; + + if (shouldThrowErrorIfFound) { const conflicts = await conflictsResolver( testPlanReport, null, context ); if (conflicts.length > 0) { + // Throw away newly created test plan reports if exception was hit + if (createdTestPlanReportIdsFromOldResults.length) + for (const createdTestPlanReportId of createdTestPlanReportIdsFromOldResults) { + await removeTestPlanReport(createdTestPlanReportId); + } + throw new Error( 'Cannot update test plan report due to conflicts' ); } const finalizedTestResults = await finalizedTestResultsResolver( - { - ...testPlanReport - }, + testPlanReport, null, context ); if (!finalizedTestResults || !finalizedTestResults.length) { + // Throw away newly created test plan reports if exception was hit + if (createdTestPlanReportIdsFromOldResults.length) + for (const createdTestPlanReportId of createdTestPlanReportIdsFromOldResults) { + await removeTestPlanReport(createdTestPlanReportId); + } + throw new Error( 'Cannot update test plan report because there are no ' + 'completed test results' diff --git a/server/scripts/populate-test-data/index.js b/server/scripts/populate-test-data/index.js index 7d368a667..9c285042e 100644 --- a/server/scripts/populate-test-data/index.js +++ b/server/scripts/populate-test-data/index.js @@ -68,6 +68,11 @@ const populateTestDatabase = async () => { 'completeAndFailingDueToIncorrectAssertions', 'completeAndFailingDueToNoOutputAssertions', 'completeAndFailingDueToUnexpectedBehaviors', + 'completeAndPassing', + 'completeAndPassing', + 'completeAndPassing', + 'completeAndPassing', + 'completeAndPassing', 'completeAndPassing' ]); @@ -103,24 +108,42 @@ const populateTestDatabase = async () => { ]); await populateFakeTestResults(8, [ + 'completeAndPassing', + 'completeAndPassing', + 'completeAndPassing', + 'completeAndPassing', + 'completeAndPassing', 'completeAndPassing', 'completeAndPassing', 'completeAndPassing' ]); await populateFakeTestResults(9, [ + 'completeAndPassing', + 'completeAndPassing', + 'completeAndPassing', + 'completeAndPassing', + 'completeAndPassing', 'completeAndPassing', 'completeAndPassing', 'completeAndPassing' ]); await populateFakeTestResults(10, [ + 'completeAndPassing', + 'completeAndPassing', + 'completeAndPassing', + 'completeAndPassing', 'completeAndPassing', 'completeAndPassing', 'completeAndPassing' ]); await populateFakeTestResults(11, [ + 'completeAndPassing', + 'completeAndPassing', + 'completeAndPassing', + 'completeAndPassing', 'completeAndPassing', 'completeAndPassing', 'completeAndPassing' diff --git a/server/tests/integration/dataManagement.test.js b/server/tests/integration/dataManagement.test.js new file mode 100644 index 000000000..7893a9cda --- /dev/null +++ b/server/tests/integration/dataManagement.test.js @@ -0,0 +1,709 @@ +const { gql } = require('apollo-server'); +const dbCleaner = require('../util/db-cleaner'); +const { query, mutate } = require('../util/graphql-test-utilities'); +const db = require('../../models'); + +beforeAll(() => { + jest.setTimeout(20000); +}); + +afterAll(async () => { + // Closing the DB connection allows Jest to exit successfully. + await db.sequelize.close(); +}); + +describe('data management', () => { + const testPlanVersionsQuery = () => { + return query(gql` + query { + testPlanVersions(phases: [RD, CANDIDATE]) { + id + phase + gitSha + testPlan { + directory + } + testPlanReports { + id + at { + id + } + browser { + id + } + draftTestPlanRuns { + testResults { + id + completedAt + test { + id + rowNumber + title + ats { + id + name + } + atMode + scenarios { + id + commands { + id + text + } + } + assertions { + id + priority + text + } + } + scenarioResults { + output + assertionResults { + id + assertion { + text + } + passed + } + scenario { + id + commands { + id + text + } + } + } + } + } + } + } + } + `); + }; + + const updateVersionToPhaseQuery = ( + testPlanVersionId, + testPlanVersionDataToIncludeId, + phase + ) => { + return mutate(gql` + mutation { + testPlanVersion(id: ${testPlanVersionId}) { + updatePhase( + phase: ${phase} + testPlanVersionDataToIncludeId: ${testPlanVersionDataToIncludeId} + ) { + testPlanVersion { + phase + testPlanReports { + id + at { + id + } + browser { + id + } + draftTestPlanRuns { + testResults { + id + completedAt + test { + id + rowNumber + title + ats { + id + name + } + atMode + scenarios { + id + commands { + id + text + } + } + assertions { + id + priority + text + } + } + scenarioResults { + output + assertionResults { + id + assertion { + text + } + passed + } + scenario { + id + commands { + id + text + } + } + } + } + } + } + } + } + } + } + `); + }; + + const countCompletedTests = testPlanReports => { + return testPlanReports.reduce((acc, testPlanReport) => { + return ( + acc + + testPlanReport.draftTestPlanRuns[0]?.testResults.reduce( + (acc, testResult) => + testResult.completedAt ? acc + 1 : acc, + 0 + ) || 0 + ); + }, 0); + }; + + it('can set test plan version to candidate and recommended', async () => { + await dbCleaner(async () => { + const candidateTestPlanVersions = await query(gql` + query { + testPlanVersions(phases: [CANDIDATE]) { + id + phase + } + } + `); + const candidateTestPlanVersion = + candidateTestPlanVersions.testPlanVersions[0]; + + let testPlanVersionId = candidateTestPlanVersion.id; + // This version is in 'CANDIDATE' phase. Let's set it to DRAFT + // This will also remove the associated TestPlanReports markedFinalAt values + await mutate(gql` + mutation { + testPlanVersion(id: ${testPlanVersionId}) { + updatePhase(phase: DRAFT) { + testPlanVersion { + phase + } + } + } + } + `); + + const previous = await query(gql` + query { + testPlanVersion(id: ${testPlanVersionId}) { + phase + testPlanReports { + id + } + } + } + `); + const previousPhase = previous.testPlanVersion.phase; + const previousPhaseTestPlanReportId = + previous.testPlanVersion.testPlanReports[0].id; + + // Need to approve at least one of the associated reports + await mutate(gql` + mutation { + testPlanReport(id: ${previousPhaseTestPlanReportId}) { + markAsFinal { + testPlanReport { + id + markedFinalAt + } + } + } + } + `); + + // Check to see that the testPlanVersion cannot be updated until the reports have been + // finalized + await expect(() => { + return mutate(gql` + mutation { + testPlanVersion(id: ${testPlanVersionId}) { + updatePhase(phase: CANDIDATE) { + testPlanVersion { + phase + } + } + } + } + `); + }).rejects.toThrow( + /Cannot set phase to candidate because the following required reports have not been collected or finalized:/i + ); + + const testPlanReportsToMarkAsFinalResult = await query(gql` + query { + testPlanReports(testPlanVersionId: ${testPlanVersionId}) { + id + } + } + `); + + for (const testPlanReport of testPlanReportsToMarkAsFinalResult.testPlanReports) { + await mutate(gql` + mutation { + testPlanReport(id: ${testPlanReport.id}) { + markAsFinal { + testPlanReport { + id + markedFinalAt + } + } + } + } + + `); + } + + const candidateResult = await mutate(gql` + mutation { + testPlanVersion(id: ${testPlanVersionId}) { + updatePhase(phase: CANDIDATE) { + testPlanVersion { + phase + } + } + } + } + `); + const candidateResultPhase = + candidateResult.testPlanVersion.updatePhase.testPlanVersion + .phase; + + const recommendedResult = await mutate(gql` + mutation { + testPlanVersion(id: ${testPlanVersionId}) { + updatePhase(phase: RECOMMENDED) { + testPlanVersion { + phase + } + } + } + } + `); + const recommendedResultPhase = + recommendedResult.testPlanVersion.updatePhase.testPlanVersion + .phase; + + expect(candidateTestPlanVersion.phase).toBe('CANDIDATE'); + expect(previousPhase).not.toBe('CANDIDATE'); + expect(candidateResultPhase).toBe('CANDIDATE'); + expect(recommendedResultPhase).toBe('RECOMMENDED'); + }); + }); + + it('updates test plan version and copies results from previous version reports', async () => { + await dbCleaner(async () => { + const testPlanVersions = await testPlanVersionsQuery(); + + // This has reports for JAWS + Chrome, NVDA + Chrome, VO + Safari + additional + // non-required reports + const [oldModalDialogVersion] = + testPlanVersions.testPlanVersions.filter( + e => + e.testPlan.directory === 'modal-dialog' && + e.phase === 'CANDIDATE' + ); + const oldModalDialogVersionTestPlanReports = + oldModalDialogVersion.testPlanReports; + const oldModalDialogVersionTestPlanReportsCount = + oldModalDialogVersionTestPlanReports.length; + + // Get JAWS-specific tests count for old version + const oldModalDialogVersionJAWSCompletedTestsCount = + countCompletedTests( + oldModalDialogVersionTestPlanReports.filter( + el => el.at.id == 1 + ) + ); + + // Get NVDA-specific tests count for old version + const oldModalDialogVersionNVDACompletedTestsCount = + countCompletedTests( + oldModalDialogVersionTestPlanReports.filter( + el => el.at.id == 2 + ) + ); + + // Get VO-specific tests count for old version + const oldModalDialogVersionVOCompletedTestsCount = + countCompletedTests( + oldModalDialogVersionTestPlanReports.filter( + el => el.at.id == 3 + ) + ); + + const [newModalDialogVersion] = + testPlanVersions.testPlanVersions.filter( + e => + e.testPlan.directory === 'modal-dialog' && + e.phase === 'RD' + ); + const newModalDialogVersionTestPlanReportsInRDCount = + newModalDialogVersion.testPlanReports.length; + + const { testPlanVersion: newModalDialogVersionInDraft } = + await mutate(gql` + mutation { + testPlanVersion(id: ${newModalDialogVersion.id}) { + updatePhase(phase: DRAFT) { + testPlanVersion { + phase + testPlanReports { + id + } + } + } + } + } + `); + const newModalDialogVersionTestPlanReportsInDraftCount = + newModalDialogVersionInDraft.updatePhase.testPlanVersion + .testPlanReports.length; + + const { testPlanVersion: newModalDialogVersionInCandidate } = + await updateVersionToPhaseQuery( + newModalDialogVersion.id, + oldModalDialogVersion.id, + 'CANDIDATE' + ); + const newModalDialogVersionTestPlanReportsInCandidate = + newModalDialogVersionInCandidate.updatePhase.testPlanVersion + .testPlanReports; + const newModalDialogVersionTestPlanReportsInCandidateCount = + newModalDialogVersionTestPlanReportsInCandidate.length; + + // Get JAWS-specific tests count for new version + const newModalDialogVersionJAWSCompletedTestsInCandidateCount = + countCompletedTests( + newModalDialogVersionTestPlanReportsInCandidate.filter( + el => el.at.id == 1 + ) + ); + + // Get NVDA-specific tests count for new version + const newModalDialogVersionNVDACompletedTestsInCandidateCount = + countCompletedTests( + newModalDialogVersionTestPlanReportsInCandidate.filter( + el => el.at.id == 2 + ) + ); + + // Get VO-specific tests count for new version + const newModalDialogVersionVOCompletedTestsInCandidateCount = + countCompletedTests( + newModalDialogVersionTestPlanReportsInCandidate.filter( + el => el.at.id == 3 + ) + ); + + // https://github.com/w3c/aria-at/compare/5fe7afd82fe51c185b8661276105190a59d47322..d0e16b42179de6f6c070da2310e99de837c71215 + // Modal Dialog was updated to show have differences between several NVDA and JAWS tests + // There are no changes for the VO tests + expect(oldModalDialogVersion.gitSha).toBe( + '5fe7afd82fe51c185b8661276105190a59d47322' + ); + expect(newModalDialogVersion.gitSha).toBe( + 'd0e16b42179de6f6c070da2310e99de837c71215' + ); + + expect(oldModalDialogVersionTestPlanReportsCount).toBeGreaterThan( + 0 + ); + expect(newModalDialogVersionTestPlanReportsInRDCount).toBe(0); + expect(newModalDialogVersionTestPlanReportsInDraftCount).toEqual(0); + expect( + newModalDialogVersionTestPlanReportsInCandidateCount + ).toEqual(oldModalDialogVersionTestPlanReportsCount); + + expect( + oldModalDialogVersionJAWSCompletedTestsCount + ).toBeGreaterThan( + newModalDialogVersionJAWSCompletedTestsInCandidateCount + ); + expect( + oldModalDialogVersionNVDACompletedTestsCount + ).toBeGreaterThan( + newModalDialogVersionNVDACompletedTestsInCandidateCount + ); + expect(oldModalDialogVersionVOCompletedTestsCount).toEqual( + newModalDialogVersionVOCompletedTestsInCandidateCount + ); + }); + }); + + it('updates test plan version and copies all but one report from previous version', async () => { + await dbCleaner(async () => { + const testPlanVersions = await testPlanVersionsQuery(); + + // This has reports for JAWS + Chrome, NVDA + Chrome, VO + Safari + additional + // non-required reports + const [oldModalDialogVersion] = + testPlanVersions.testPlanVersions.filter( + e => + e.testPlan.directory === 'modal-dialog' && + e.phase === 'CANDIDATE' + ); + const oldModalDialogVersionTestPlanReports = + oldModalDialogVersion.testPlanReports; + const oldModalDialogVersionTestPlanReportsCount = + oldModalDialogVersionTestPlanReports.length; + + // Get VO+Firefox-specific tests count for old version + const oldModalDialogVersionVOFirefoxCompletedTestsCount = + countCompletedTests( + oldModalDialogVersionTestPlanReports.filter( + el => el.at.id == 3 && el.browser.id == 1 + ) + ); + + const [newModalDialogVersion] = + testPlanVersions.testPlanVersions.filter( + e => + e.testPlan.directory === 'modal-dialog' && + e.phase === 'RD' + ); + const newModalDialogVersionTestPlanReportsInRDCount = + newModalDialogVersion.testPlanReports.length; + + await mutate(gql` + mutation { + testPlanVersion(id: ${newModalDialogVersion.id}) { + updatePhase(phase: DRAFT) { + testPlanVersion { + phase + testPlanReports { + id + } + } + } + } + } + `); + + const { + findOrCreateTestPlanReport: { + populatedData: { + testPlanVersion: newModalDialogVersionInDraft + } + } + } = await mutate(gql` + mutation { + findOrCreateTestPlanReport(input: { + testPlanVersionId: ${newModalDialogVersion.id} + atId: 3 + browserId: 1 + }) { + populatedData { + testPlanReport { + id + at { + id + } + browser { + id + } + } + testPlanVersion { + id + phase + testPlanReports { + id + } + } + } + created { + locationOfData + } + } + } + `); + + const newModalDialogVersionTestPlanReportsInDraftCount = + newModalDialogVersionInDraft.testPlanReports.length; + + const { testPlanVersion: newModalDialogVersionInCandidate } = + await updateVersionToPhaseQuery( + newModalDialogVersion.id, + oldModalDialogVersion.id, + 'CANDIDATE' + ); + const newModalDialogVersionTestPlanReportsInCandidate = + newModalDialogVersionInCandidate.updatePhase.testPlanVersion + .testPlanReports; + const newModalDialogVersionTestPlanReportsInCandidateCount = + newModalDialogVersionTestPlanReportsInCandidate.length; + + // Get VO+Firefox-specific tests count for new version + const newModalDialogVersionVOFirefoxCompletedTestsInCandidateCount = + countCompletedTests( + newModalDialogVersionTestPlanReportsInCandidate.filter( + el => el.at.id == 3 && el.browser.id == 1 + ) + ); + + // https://github.com/w3c/aria-at/compare/5fe7afd82fe51c185b8661276105190a59d47322..d0e16b42179de6f6c070da2310e99de837c71215 + // Modal Dialog was updated to show have differences between several NVDA and JAWS tests + // There are no changes for the VO tests + expect(oldModalDialogVersion.gitSha).toBe( + '5fe7afd82fe51c185b8661276105190a59d47322' + ); + expect(newModalDialogVersion.gitSha).toBe( + 'd0e16b42179de6f6c070da2310e99de837c71215' + ); + + expect(oldModalDialogVersionTestPlanReportsCount).toBeGreaterThan( + 0 + ); + expect(newModalDialogVersionTestPlanReportsInRDCount).toBe(0); + expect(newModalDialogVersionTestPlanReportsInDraftCount).toEqual(1); + expect( + newModalDialogVersionTestPlanReportsInCandidateCount + ).toEqual(oldModalDialogVersionTestPlanReportsCount); + + expect( + oldModalDialogVersionVOFirefoxCompletedTestsCount + ).toBeGreaterThan( + newModalDialogVersionVOFirefoxCompletedTestsInCandidateCount + ); + expect( + newModalDialogVersionVOFirefoxCompletedTestsInCandidateCount + ).toEqual(0); + }); + }); + + it('updates test plan version but has new reports that are required and not yet marked as final', async () => { + await dbCleaner(async () => { + const testPlanVersions = await testPlanVersionsQuery(); + + // This has reports for JAWS + Chrome, NVDA + Chrome, VO + Safari + additional + // non-required reports + const [oldModalDialogVersion] = + testPlanVersions.testPlanVersions.filter( + e => + e.testPlan.directory === 'modal-dialog' && + e.phase === 'CANDIDATE' + ); + const oldModalDialogVersionTestPlanReports = + oldModalDialogVersion.testPlanReports; + const oldModalDialogVersionTestPlanReportsCount = + oldModalDialogVersionTestPlanReports.length; + + // Get VO+Safari-specific tests count for old version + const oldModalDialogVersionVOSafariCompletedTestsCount = + countCompletedTests( + oldModalDialogVersionTestPlanReports.filter( + el => el.at.id == 3 && el.browser.id == 3 + ) + ); + + const [newModalDialogVersion] = + testPlanVersions.testPlanVersions.filter( + e => + e.testPlan.directory === 'modal-dialog' && + e.phase === 'RD' + ); + const newModalDialogVersionTestPlanReportsInRDCount = + newModalDialogVersion.testPlanReports.length; + + await mutate(gql` + mutation { + testPlanVersion(id: ${newModalDialogVersion.id}) { + updatePhase(phase: DRAFT) { + testPlanVersion { + phase + testPlanReports { + id + } + } + } + } + } + `); + + const { + findOrCreateTestPlanReport: { + populatedData: { + testPlanVersion: newModalDialogVersionInDraft + } + } + } = await mutate(gql` + mutation { + findOrCreateTestPlanReport(input: { + testPlanVersionId: ${newModalDialogVersion.id} + atId: 3 + browserId: 3 + }) { + populatedData { + testPlanReport { + id + at { + id + } + browser { + id + } + } + testPlanVersion { + id + phase + testPlanReports { + id + } + } + } + created { + locationOfData + } + } + } + `); + + const newModalDialogVersionTestPlanReportsInDraftCount = + newModalDialogVersionInDraft.testPlanReports.length; + + // A required report isn't marked as final, VO + Safari + await expect(() => { + return updateVersionToPhaseQuery( + newModalDialogVersion.id, + oldModalDialogVersion.id, + 'CANDIDATE' + ); + }).rejects.toThrow( + /Cannot set phase to candidate because the following required reports have not been collected or finalized:/i + ); + + // https://github.com/w3c/aria-at/compare/5fe7afd82fe51c185b8661276105190a59d47322..d0e16b42179de6f6c070da2310e99de837c71215 + // Modal Dialog was updated to show have differences between several NVDA and JAWS tests + // There are no changes for the VO tests + expect(oldModalDialogVersion.gitSha).toBe( + '5fe7afd82fe51c185b8661276105190a59d47322' + ); + expect(newModalDialogVersion.gitSha).toBe( + 'd0e16b42179de6f6c070da2310e99de837c71215' + ); + + expect(oldModalDialogVersionTestPlanReportsCount).toBeGreaterThan( + 0 + ); + expect( + oldModalDialogVersionVOSafariCompletedTestsCount + ).toBeGreaterThan(0); + expect(newModalDialogVersionTestPlanReportsInRDCount).toBe(0); + expect(newModalDialogVersionTestPlanReportsInDraftCount).toEqual(1); + }); + }); +}); diff --git a/server/tests/integration/test-queue.test.js b/server/tests/integration/testQueue.test.js similarity index 83% rename from server/tests/integration/test-queue.test.js rename to server/tests/integration/testQueue.test.js index 115274ebd..ac577d459 100644 --- a/server/tests/integration/test-queue.test.js +++ b/server/tests/integration/testQueue.test.js @@ -191,143 +191,6 @@ describe('test queue', () => { }); }); - it('can set test plan version to candidate and recommended', async () => { - await dbCleaner(async () => { - const candidateTestPlanVersions = await query(gql` - query { - testPlanVersions(phases: [CANDIDATE]) { - id - phase - } - } - `); - const candidateTestPlanVersion = - candidateTestPlanVersions.testPlanVersions[0]; - - let testPlanVersionId = candidateTestPlanVersion.id; - // This version is in 'CANDIDATE' phase. Let's set it to DRAFT - // This will also remove the associated TestPlanReports markedFinalAt values - await mutate(gql` - mutation { - testPlanVersion(id: ${testPlanVersionId}) { - updatePhase(phase: DRAFT) { - testPlanVersion { - phase - } - } - } - } - `); - - const previous = await query(gql` - query { - testPlanVersion(id: ${testPlanVersionId}) { - phase - testPlanReports { - id - } - } - } - `); - const previousPhase = previous.testPlanVersion.phase; - const previousPhaseTestPlanReportId = - previous.testPlanVersion.testPlanReports[0].id; - - // Need to approve at least one of the associated reports - await mutate(gql` - mutation { - testPlanReport(id: ${previousPhaseTestPlanReportId}) { - markAsFinal { - testPlanReport { - id - markedFinalAt - } - } - } - } - `); - - // Check to see that the testPlanVersion cannot be updated until the reports have been - // finalized - await expect(() => { - return mutate(gql` - mutation { - testPlanVersion(id: ${testPlanVersionId}) { - updatePhase(phase: CANDIDATE) { - testPlanVersion { - phase - } - } - } - } - `); - }).rejects.toThrow( - 'Cannot set phase to candidate because the following required reports have' + - ' not been collected or finalized: JAWS and Chrome, NVDA and Chrome,' + - ' VoiceOver for macOS and Safari.' - ); - - const testPlanReportsToMarkAsFinalResult = await query(gql` - query { - testPlanReports(testPlanVersionId: ${testPlanVersionId}) { - id - } - } - `); - - for (const testPlanReport of testPlanReportsToMarkAsFinalResult.testPlanReports) { - await mutate(gql` - mutation { - testPlanReport(id: ${testPlanReport.id}) { - markAsFinal { - testPlanReport { - id - markedFinalAt - } - } - } - } - - `); - } - - const candidateResult = await mutate(gql` - mutation { - testPlanVersion(id: ${testPlanVersionId}) { - updatePhase(phase: CANDIDATE) { - testPlanVersion { - phase - } - } - } - } - `); - const candidateResultPhase = - candidateResult.testPlanVersion.updatePhase.testPlanVersion - .phase; - - const recommendedResult = await mutate(gql` - mutation { - testPlanVersion(id: ${testPlanVersionId}) { - updatePhase(phase: RECOMMENDED) { - testPlanVersion { - phase - } - } - } - } - `); - const recommendedResultPhase = - recommendedResult.testPlanVersion.updatePhase.testPlanVersion - .phase; - - expect(candidateTestPlanVersion.phase).toBe('CANDIDATE'); - expect(previousPhase).not.toBe('CANDIDATE'); - expect(candidateResultPhase).toBe('CANDIDATE'); - expect(recommendedResultPhase).toBe('RECOMMENDED'); - }); - }); - it('queries for information needed to add reports', async () => { const result = await query(gql` query { From 2644b1912735b085183a876073b27cb9bd59fc8c Mon Sep 17 00:00:00 2001 From: Paul Clue <67766160+Paul-Clue@users.noreply.github.com> Date: Thu, 28 Sep 2023 14:52:13 -0500 Subject: [PATCH 48/59] Fix merge conflicts --- server/resolvers/index.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/server/resolvers/index.js b/server/resolvers/index.js index 2d5c1a8ab..00704ad3e 100644 --- a/server/resolvers/index.js +++ b/server/resolvers/index.js @@ -25,11 +25,8 @@ const User = require('./User'); const AtOperations = require('./AtOperations'); const AtVersionOperations = require('./AtVersionOperations'); const BrowserOperations = require('./BrowserOperations'); -<<<<<<< HEAD const RequiredReportOperations = require('./RequiredReportOperations'); -======= const TestPlan = require('./TestPlan'); ->>>>>>> update-database-impl const TestPlanVersion = require('./TestPlanVersion'); const TestPlanReport = require('./TestPlanReport'); const TestPlanReportOperations = require('./TestPlanReportOperations'); From 176b1a03022aa54cc7d24cd2a2d5337c1282d3e6 Mon Sep 17 00:00:00 2001 From: Paul Clue <67766160+Paul-Clue@users.noreply.github.com> Date: Mon, 2 Oct 2023 08:53:01 -0500 Subject: [PATCH 49/59] Pull changes from rebase --- server/tests/models/services/AtService.test.js | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/server/tests/models/services/AtService.test.js b/server/tests/models/services/AtService.test.js index 2bae5eba0..5abdba1d0 100644 --- a/server/tests/models/services/AtService.test.js +++ b/server/tests/models/services/AtService.test.js @@ -212,9 +212,17 @@ describe('AtModel Data Checks', () => { it('should return collection of ats with paginated structure', async () => { await dbCleaner(async () => { // A1 - const result = await AtService.getAts('', {}, ['name'], [], [], [], { - enablePagination: true - }); + const result = await AtService.getAts( + '', + {}, + ['name'], + [], + [], + [], + { + enablePagination: true + } + ); // A3 expect(result.data.length).toBeGreaterThanOrEqual(1); From 590b5503b9960b3f07582d6afbf88d9d12167b84 Mon Sep 17 00:00:00 2001 From: Paul Clue <67766160+Paul-Clue@users.noreply.github.com> Date: Thu, 5 Oct 2023 03:12:45 -0500 Subject: [PATCH 50/59] Fix check for browser selection --- client/components/ManageTestQueue/index.jsx | 41 ++++++++++++++++++--- 1 file changed, 36 insertions(+), 5 deletions(-) diff --git a/client/components/ManageTestQueue/index.jsx b/client/components/ManageTestQueue/index.jsx index 2460dccfd..8a9c6ba0c 100644 --- a/client/components/ManageTestQueue/index.jsx +++ b/client/components/ManageTestQueue/index.jsx @@ -891,11 +891,12 @@ const ManageTestQueue = ({ setUpdateListAtSelection(value); setUpdateAtForButton(value); }; - + //section: const handleListBrowserChange = e => { const value = e.target.value; setUpdateListBrowserSelection(value); setUpdateBrowserForButton(value); + console.log(value); }; return ( @@ -1194,7 +1195,38 @@ const ManageTestQueue = ({ Browser - {updateListAtSelection === 'Select an At' ? ( + {/* section: */} + {/* */} + + + {ats + .find( + at => + at.id === updateListAtSelection + ) + ?.browsers.map(item => ( + + ))} + + {/* {updateListAtSelection === 'Select an At' ? ( - ) : null} + ) : null} */}