From 812450eaaccc71c9aa416904349f9726243067d8 Mon Sep 17 00:00:00 2001 From: "Mx. Corey Frang" Date: Thu, 6 Jun 2024 13:28:10 -0400 Subject: [PATCH 1/9] Test Run / Navigator polling updates for collection jobs --- client/components/TestRun/TestNavigator.jsx | 39 ++++++-------- client/components/TestRun/TestRun.css | 21 ++++++++ client/components/TestRun/index.jsx | 56 ++++++++++++--------- client/components/TestRun/queries.js | 21 ++++---- 4 files changed, 79 insertions(+), 58 deletions(-) diff --git a/client/components/TestRun/TestNavigator.jsx b/client/components/TestRun/TestNavigator.jsx index e1aba2b0c..f6b86bfac 100644 --- a/client/components/TestRun/TestNavigator.jsx +++ b/client/components/TestRun/TestNavigator.jsx @@ -6,10 +6,8 @@ import { faArrowRight } from '@fortawesome/free-solid-svg-icons'; import { Col } from 'react-bootstrap'; -import React, { useMemo } from 'react'; +import React from 'react'; import '@fortawesome/fontawesome-svg-core/styles.css'; -import { COLLECTION_JOB_STATUS_BY_TEST_PLAN_RUN_ID_QUERY } from './queries'; -import { useQuery } from '@apollo/client'; const TestNavigator = ({ show = true, @@ -25,18 +23,7 @@ const TestNavigator = ({ }) => { const isBotCompletedTest = testPlanRun?.tester?.isBot; - const { data: collectionJobQuery } = useQuery( - COLLECTION_JOB_STATUS_BY_TEST_PLAN_RUN_ID_QUERY, - { - variables: { - testPlanRunId: testPlanRun?.id - } - } - ); - - const status = useMemo(() => { - return collectionJobQuery?.collectionJobByTestPlanRunId?.status; - }, [collectionJobQuery]); + const { status, testStatus } = testPlanRun?.collectionJob ?? {}; return ( @@ -78,17 +65,23 @@ const TestNavigator = ({ if (test) { if (isBotCompletedTest) { - if ( - test.testResult?.scenarioResults.some( - s => s.output - ) - ) { + const { status } = + testStatus.find( + ts => ts.test.id === test.id + ) ?? {}; + if (status === 'COMPLETED') { resultClassName = 'bot-complete'; resultStatus = 'Completed by Bot'; - } else if (status !== 'CANCELLED') { + } else if (status === 'QUEUED') { resultClassName = 'bot-queued'; - resultStatus = 'In Progress by Bot'; - } else { + resultStatus = 'Queued by Bot'; + } else if (status === 'RUNNING') { + resultClassName = 'bot-running'; + resultStatus = 'Queued by Bot'; + } else if (status === 'ERROR') { + resultClassName = 'bot-error'; + resultStatus = 'Error collecting by Bot'; + } else if (status === 'CANCELLED') { resultClassName = 'bot-cancelled'; resultStatus = 'Cancelled by Bot'; } diff --git a/client/components/TestRun/TestRun.css b/client/components/TestRun/TestRun.css index 25cc521fd..dd4eca036 100644 --- a/client/components/TestRun/TestRun.css +++ b/client/components/TestRun/TestRun.css @@ -158,6 +158,27 @@ button.test-navigator-toggle:focus { left: 4px; } +.test-name-wrapper.bot-running .progress-indicator { + background: #d2d5d9; + border: 2px solid #1e8f37; +} + +.test-name-wrapper.bot-error .progress-indicator { + background: #e3261f; +} +.test-name-wrapper.bot-error .progress-indicator:before { + display: inline-block; + text-rendering: auto; + -webkit-font-smoothing: antialiased; + font-family: 'Font Awesome 5 Free'; + font-weight: 900; + content: '\f071'; + color: white; + font-size: 10px; + position: relative; + top: -3px; + left: 4px; +} .test-name-wrapper.bot-queued .progress-indicator { background: #295fa6; } diff --git a/client/components/TestRun/index.jsx b/client/components/TestRun/index.jsx index db7320fbf..e972ca53d 100644 --- a/client/components/TestRun/index.jsx +++ b/client/components/TestRun/index.jsx @@ -28,7 +28,6 @@ import { useDetectUa } from '../../hooks/useDetectUa'; import DisplayNone from '../../utils/DisplayNone'; import { navigateTests } from '../../utils/navigateTests'; import { - COLLECTION_JOB_STATUS_BY_TEST_PLAN_RUN_ID_QUERY, DELETE_TEST_RESULT_MUTATION, FIND_OR_CREATE_BROWSER_VERSION_MUTATION, FIND_OR_CREATE_TEST_RESULT_MUTATION, @@ -42,6 +41,7 @@ import './TestRun.css'; import ReviewConflicts from '../ReviewConflicts'; import createIssueLink from '../../utils/createIssueLink'; import { convertDateToString } from '../../utils/formatter'; +const pollInterval = 2000; const TestRun = () => { const params = useParams(); @@ -68,21 +68,26 @@ const TestRun = () => { const { runId: testPlanRunId, testPlanReportId } = params; - const { loading, data, error } = useQuery( + const { loading, data, error, startPolling, stopPolling } = useQuery( testPlanRunId ? TEST_RUN_PAGE_QUERY : TEST_RUN_PAGE_ANON_QUERY, { fetchPolicy: 'cache-and-network', - variables: { testPlanRunId, testPlanReportId } + variables: { testPlanRunId, testPlanReportId }, + pollInterval } ); - const { data: collectionJobQuery } = useQuery( - COLLECTION_JOB_STATUS_BY_TEST_PLAN_RUN_ID_QUERY, - { - variables: { testPlanRunId }, - fetchPolicy: 'cache-and-network' + // control the data flow, turn on polling if this is a collection job report + // that still has possible updates. + useEffect(() => { + const status = data?.testPlanRun?.collectionJob?.status; + if (status === 'QUEUED' || status === 'RUNNING') { + startPolling(pollInterval); + } else { + stopPolling(); } - ); + if (data) setup(data); + }, [data]); const [createTestResult, { loading: createTestResultLoading }] = useMutation(FIND_OR_CREATE_TEST_RESULT_MUTATION); @@ -148,10 +153,6 @@ const TestRun = () => { const isAdminReviewer = !!(isAdmin && openAsUserId); const openAsUser = users?.find(user => user.id === openAsUserId); - useEffect(() => { - if (data) setup(data); - }, [data]); - useEffect(() => { reset(); @@ -903,16 +904,22 @@ const TestRun = () => { if (isReviewingBot) { content = ( <> - {`${testResults.reduce( - (acc, { scenarioResults }) => - acc + - (scenarioResults && - scenarioResults.every(({ output }) => !!output) - ? 1 - : 0), - 0 - )} of ${tests.length}`}{' '} - responses collected. +

+ {`${testResults.reduce( + (acc, { scenarioResults }) => + acc + + (scenarioResults && + scenarioResults.every(({ output }) => !!output) + ? 1 + : 0), + 0 + )} of ${tests.length}`}{' '} + responses collected. +

+

+ Collection Job Status:{' '} + {testPlanRun.collectionJob.status} +

); } else if (!isSignedIn) { @@ -1014,8 +1021,7 @@ const TestRun = () => { ]; } - const externalLogsUrl = - collectionJobQuery?.collectionJobByTestPlanRunId?.externalLogsUrl; + const externalLogsUrl = testPlanRun.collectionJob?.externalLogsUrl; const menuRightOfContent = (
diff --git a/client/components/TestRun/queries.js b/client/components/TestRun/queries.js index aed6ca3e1..866be0658 100644 --- a/client/components/TestRun/queries.js +++ b/client/components/TestRun/queries.js @@ -5,6 +5,17 @@ export const TEST_RUN_PAGE_QUERY = gql` testPlanRun(id: $testPlanRunId) { id initiatedByAutomation + collectionJob { + id + status + externalLogsUrl + testStatus { + test { + id + } + status + } + } tester { id username @@ -1257,13 +1268,3 @@ export const FIND_OR_CREATE_BROWSER_VERSION_MUTATION = gql` } } `; - -export const COLLECTION_JOB_STATUS_BY_TEST_PLAN_RUN_ID_QUERY = gql` - query CollectionJobIdByTestPlanRunId($testPlanRunId: ID!) { - collectionJobByTestPlanRunId(testPlanRunId: $testPlanRunId) { - id - status - externalLogsUrl - } - } -`; From 4aa0f4d6e61672e585cbbe4525311a081e458dd5 Mon Sep 17 00:00:00 2001 From: "Mx. Corey Frang" Date: Thu, 6 Jun 2024 13:41:59 -0400 Subject: [PATCH 2/9] fix test, add updates incoming warning --- client/components/TestRun/TestNavigator.jsx | 2 +- client/components/TestRun/index.jsx | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/client/components/TestRun/TestNavigator.jsx b/client/components/TestRun/TestNavigator.jsx index f6b86bfac..ebd9483db 100644 --- a/client/components/TestRun/TestNavigator.jsx +++ b/client/components/TestRun/TestNavigator.jsx @@ -23,7 +23,7 @@ const TestNavigator = ({ }) => { const isBotCompletedTest = testPlanRun?.tester?.isBot; - const { status, testStatus } = testPlanRun?.collectionJob ?? {}; + const testStatus = testPlanRun?.collectionJob?.testStatus; return ( diff --git a/client/components/TestRun/index.jsx b/client/components/TestRun/index.jsx index e972ca53d..6643d06c6 100644 --- a/client/components/TestRun/index.jsx +++ b/client/components/TestRun/index.jsx @@ -1229,11 +1229,17 @@ const TestRun = () => { if (openAsUserId) { if (openAsUser.isBot) { + const status = testPlanRun.collectionJob.status; openAsUserHeading = (
Reviewing tests of{' '} {' '} {`${openAsUser.username}`}. + {( status === 'QUEUED' || status === 'RUNNING') && ( + <> +
The collection bot is still updating information on this page. Changes may be lost when updates arrive. + + )}
); } else { From cff2313a4a1323ae6f2d4fd6c5366f830b1e8bfb Mon Sep 17 00:00:00 2001 From: "Mx. Corey Frang" Date: Thu, 6 Jun 2024 13:45:43 -0400 Subject: [PATCH 3/9] fix status messages --- client/components/TestRun/TestNavigator.jsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/components/TestRun/TestNavigator.jsx b/client/components/TestRun/TestNavigator.jsx index ebd9483db..c8590bd67 100644 --- a/client/components/TestRun/TestNavigator.jsx +++ b/client/components/TestRun/TestNavigator.jsx @@ -77,10 +77,10 @@ const TestNavigator = ({ resultStatus = 'Queued by Bot'; } else if (status === 'RUNNING') { resultClassName = 'bot-running'; - resultStatus = 'Queued by Bot'; + resultStatus = 'Running with Bot'; } else if (status === 'ERROR') { resultClassName = 'bot-error'; - resultStatus = 'Error collecting by Bot'; + resultStatus = 'Error collecting with Bot'; } else if (status === 'CANCELLED') { resultClassName = 'bot-cancelled'; resultStatus = 'Cancelled by Bot'; From 8ad9f5a67bfe220ad13434011e15f0ffcda7b0cc Mon Sep 17 00:00:00 2001 From: "Mx. Corey Frang" Date: Thu, 6 Jun 2024 13:47:32 -0400 Subject: [PATCH 4/9] minute adjustment for error --- client/components/TestRun/TestRun.css | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/components/TestRun/TestRun.css b/client/components/TestRun/TestRun.css index dd4eca036..ef6c24db4 100644 --- a/client/components/TestRun/TestRun.css +++ b/client/components/TestRun/TestRun.css @@ -176,8 +176,8 @@ button.test-navigator-toggle:focus { color: white; font-size: 10px; position: relative; - top: -3px; - left: 4px; + top: -4px; + left: 3px; } .test-name-wrapper.bot-queued .progress-indicator { background: #295fa6; From e5765edd67ff4932beb777ee10e0c8f282cb2b4b Mon Sep 17 00:00:00 2001 From: "Mx. Corey Frang" Date: Mon, 17 Jun 2024 11:03:24 -0400 Subject: [PATCH 5/9] isJobStatusFinal --- client/components/TestRun/index.jsx | 9 ++++++--- client/utils/collectionJobStatus.js | 16 ++++++++++++++++ 2 files changed, 22 insertions(+), 3 deletions(-) create mode 100644 client/utils/collectionJobStatus.js diff --git a/client/components/TestRun/index.jsx b/client/components/TestRun/index.jsx index 6643d06c6..1f970ddd1 100644 --- a/client/components/TestRun/index.jsx +++ b/client/components/TestRun/index.jsx @@ -41,6 +41,7 @@ import './TestRun.css'; import ReviewConflicts from '../ReviewConflicts'; import createIssueLink from '../../utils/createIssueLink'; import { convertDateToString } from '../../utils/formatter'; +import { isJobStatusFinal } from '../../utils/collectionJobStatus'; const pollInterval = 2000; const TestRun = () => { @@ -81,7 +82,7 @@ const TestRun = () => { // that still has possible updates. useEffect(() => { const status = data?.testPlanRun?.collectionJob?.status; - if (status === 'QUEUED' || status === 'RUNNING') { + if (!isJobStatusFinal(status)) { startPolling(pollInterval); } else { stopPolling(); @@ -1235,9 +1236,11 @@ const TestRun = () => { Reviewing tests of{' '} {' '} {`${openAsUser.username}`}. - {( status === 'QUEUED' || status === 'RUNNING') && ( + {!isJobStatusFinal(status) && ( <> -
The collection bot is still updating information on this page. Changes may be lost when updates arrive. +
+ The collection bot is still updating information on + this page. Changes may be lost when updates arrive. )}
diff --git a/client/utils/collectionJobStatus.js b/client/utils/collectionJobStatus.js new file mode 100644 index 000000000..1bba17474 --- /dev/null +++ b/client/utils/collectionJobStatus.js @@ -0,0 +1,16 @@ +// server side version of this enum. +// don't forget to also update the client side version in +// server/util/enums.js + +export const COLLECTION_JOB_STATUS = { + QUEUED: 'QUEUED', + RUNNING: 'RUNNING', + COMPLETED: 'COMPLETED', + ERROR: 'ERROR', + CANCELLED: 'CANCELLED' +}; + +export const isJobStatusFinal = status => + status === COLLECTION_JOB_STATUS.COMPLETED || + status === COLLECTION_JOB_STATUS.CANCELLED || + status === COLLECTION_JOB_STATUS.ERROR; From 3fded9584f68f1916d2dc515bd38d861f1bb1bcc Mon Sep 17 00:00:00 2001 From: "Mx. Corey Frang" Date: Wed, 19 Jun 2024 12:03:01 -0400 Subject: [PATCH 6/9] Shared update context for CollectionJob, let smaller portions of page rerender --- .../TestRun/CollectionJobContext.js | 64 +++ client/components/TestRun/Heading.js | 179 ++++++++ client/components/TestRun/TestNavigator.jsx | 11 +- client/components/TestRun/index.jsx | 398 +++++++----------- client/components/TestRun/queries.js | 16 + 5 files changed, 409 insertions(+), 259 deletions(-) create mode 100644 client/components/TestRun/CollectionJobContext.js create mode 100644 client/components/TestRun/Heading.js diff --git a/client/components/TestRun/CollectionJobContext.js b/client/components/TestRun/CollectionJobContext.js new file mode 100644 index 000000000..4eeb37a79 --- /dev/null +++ b/client/components/TestRun/CollectionJobContext.js @@ -0,0 +1,64 @@ +import PropTypes from 'prop-types'; +import React, { createContext, useState, useEffect } from 'react'; +import { useLazyQuery } from '@apollo/client'; +import { COLLECTION_JOB_UPDATES_QUERY } from './queries'; +import { isJobStatusFinal } from '../../utils/collectionJobStatus'; +const pollInterval = 5000; + +export const Context = createContext({ + state: { + collectionJob: null + }, + actions: {} +}); + +export const Provider = ({ + children, + testPlanRun: { id: testPlanRunId, collectionJob: initialCollectionJob } +}) => { + const [providerValue, setProviderValue] = useState({ + state: { collectionJob: initialCollectionJob }, + actions: {} + }); + + const [, { data: collectionJobUpdateData, startPolling, stopPolling }] = + testPlanRunId + ? useLazyQuery(COLLECTION_JOB_UPDATES_QUERY, { + fetchPolicy: 'cache-and-network', + variables: { collectionJobId: initialCollectionJob?.id }, + pollInterval + }) + : {}; + + // control the data flow, turn on polling if this is a collection job report + // that still has possible updates. + useEffect(() => { + // use the colllection job from the polling update first priority + // otherwise, default to the first data fetch from the API + const collectionJob = + collectionJobUpdateData?.collectionJob ?? initialCollectionJob; + const status = collectionJob?.status; + if (collectionJob && !isJobStatusFinal(status)) { + startPolling(pollInterval); + } else { + stopPolling(); + } + setProviderValue({ state: { collectionJob }, actions: {} }); + }, [collectionJobUpdateData]); + + return ( + {children} + ); +}; + +Provider.propTypes = { + children: PropTypes.node, + testPlanRun: PropTypes.shape({ + id: PropTypes.string, + collectionJob: PropTypes.shape({ + id: PropTypes.string.isRequired, + status: PropTypes.string.isRequired, + testStatus: PropTypes.arrayOf(PropTypes.object).isRequired + }) + }).isRequired +}; diff --git a/client/components/TestRun/Heading.js b/client/components/TestRun/Heading.js new file mode 100644 index 000000000..b2307dd44 --- /dev/null +++ b/client/components/TestRun/Heading.js @@ -0,0 +1,179 @@ +import React, { useContext } from 'react'; +import PropTypes from 'prop-types'; +import { Button } from 'react-bootstrap'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { + faEdit, + faCheck, + faExclamationCircle, + faRobot +} from '@fortawesome/free-solid-svg-icons'; +import { Context } from './CollectionJobContext'; +import { + COLLECTION_JOB_STATUS, + isJobStatusFinal +} from '../../utils/collectionJobStatus'; + +const TestRunHeading = ({ + at, + browser, + editAtBrowserDetailsButtonRef, + handleEditAtBrowserDetailsClick, + isSignedIn, + openAsUser, + showEditAtBrowser, + testPlanTitle, + testResults, + testCount +}) => { + const { + state: { collectionJob } + } = useContext(Context); + + const renderTestsCompletedInfoBox = () => { + let isReviewingBot = Boolean(openAsUser?.isBot); + let content; + + if (isReviewingBot) { + const countTestResults = testResults.reduce( + (acc, { scenarioResults }) => + acc + + (scenarioResults && + scenarioResults.every(({ output }) => !!output) + ? 1 + : 0), + 0 + ); + const countCompleteCollection = collectionJob.testStatus.reduce( + (acc, { status }) => + acc + (status === COLLECTION_JOB_STATUS.COMPLETED ? 1 : 0), + 0 + ); + + content = ( + <> +

+ {`${Math.max( + countTestResults, + countCompleteCollection + )} of ${testCount}`}{' '} + responses collected. +

+

+ Collection Job Status: {collectionJob.status} +

+ + ); + } else if (!isSignedIn) { + content = {testCount} tests to view; + } else if (testCount) { + content = ( + <> + {' '} + {`${testResults.reduce( + (acc, { completedAt }) => acc + (completedAt ? 1 : 0), + 0 + )} of ${testCount}`}{' '} + tests completed + + ); + } else { + content =
No tests for this AT and Browser combination
; + } + + return ( +
+
+ + {content} +
+
+ ); + }; + + let openAsUserHeading = null; + + if (openAsUser?.isBot) { + openAsUserHeading = ( +
+ Reviewing tests of{' '} + {' '} + {`${openAsUser.username}`}. + {!isJobStatusFinal(collectionJob.status) && ( + <> +
+ The collection bot is still updating information on this + page. Changes may be lost when updates arrive. + + )} +
+ ); + } else { + openAsUserHeading = ( +
+ Reviewing tests of {`${openAsUser.username}`}. +

{`All changes will be saved as performed by ${openAsUser.username}.`}

+
+ ); + } + + return ( + <> +
+
+
+ Test Plan: {testPlanTitle} +
+
+
+
+
+ AT: {at} +
+
+ Browser: {browser} +
+
+ {showEditAtBrowser && ( + + )} +
+ {renderTestsCompletedInfoBox()} +
+ {openAsUserHeading} + + ); +}; + +TestRunHeading.propTypes = { + testPlanTitle: PropTypes.string.isRequired, + at: PropTypes.string.isRequired, + browser: PropTypes.string.isRequired, + showEditAtBrowser: PropTypes.bool.isRequired, + editAtBrowserDetailsButtonRef: PropTypes.object.isRequired, + isSignedIn: PropTypes.bool.isRequired, + openAsUser: PropTypes.shape({ + isBot: PropTypes.bool.isRequired, + username: PropTypes.string.isRequired + }), + testResults: PropTypes.arrayOf(PropTypes.shape({})), + testCount: PropTypes.number.isRequired, + handleEditAtBrowserDetailsClick: PropTypes.func.isRequired +}; + +export default TestRunHeading; diff --git a/client/components/TestRun/TestNavigator.jsx b/client/components/TestRun/TestNavigator.jsx index c8590bd67..0d24c1da5 100644 --- a/client/components/TestRun/TestNavigator.jsx +++ b/client/components/TestRun/TestNavigator.jsx @@ -6,7 +6,8 @@ import { faArrowRight } from '@fortawesome/free-solid-svg-icons'; import { Col } from 'react-bootstrap'; -import React from 'react'; +import React, { useContext, useMemo } from 'react'; +import { Context as CollectionJobContext } from './CollectionJobContext'; import '@fortawesome/fontawesome-svg-core/styles.css'; const TestNavigator = ({ @@ -23,7 +24,13 @@ const TestNavigator = ({ }) => { const isBotCompletedTest = testPlanRun?.tester?.isBot; - const testStatus = testPlanRun?.collectionJob?.testStatus; + const { + state: { collectionJob } + } = useContext(CollectionJobContext); + const testStatus = useMemo( + () => collectionJob?.testStatus ?? [], + [collectionJob] + ); return ( diff --git a/client/components/TestRun/index.jsx b/client/components/TestRun/index.jsx index 1f970ddd1..d0a6f378e 100644 --- a/client/components/TestRun/index.jsx +++ b/client/components/TestRun/index.jsx @@ -6,12 +6,10 @@ import { useMutation, useQuery } from '@apollo/client'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { faPen, - faEdit, faRedo, faCheck, faCheckCircle, - faExclamationCircle, - faRobot + faExclamationCircle } from '@fortawesome/free-solid-svg-icons'; import nextId from 'react-id-generator'; import { Alert, Button, Col, Container, Row } from 'react-bootstrap'; @@ -20,6 +18,7 @@ import ReviewConflictsModal from './ReviewConflictsModal'; import StatusBar from './StatusBar'; import TestRenderer from '../TestRenderer'; import OptionButton from './OptionButton'; +import Heading from './Heading'; import PageStatus from '../common/PageStatus'; import BasicModal from '../common/BasicModal'; import BasicThemedModal from '../common/BasicThemedModal'; @@ -41,8 +40,7 @@ import './TestRun.css'; import ReviewConflicts from '../ReviewConflicts'; import createIssueLink from '../../utils/createIssueLink'; import { convertDateToString } from '../../utils/formatter'; -import { isJobStatusFinal } from '../../utils/collectionJobStatus'; -const pollInterval = 2000; +import { Provider as CollectionJobContextProvider } from './CollectionJobContext'; const TestRun = () => { const params = useParams(); @@ -69,24 +67,16 @@ const TestRun = () => { const { runId: testPlanRunId, testPlanReportId } = params; - const { loading, data, error, startPolling, stopPolling } = useQuery( + const { loading, data, error } = useQuery( testPlanRunId ? TEST_RUN_PAGE_QUERY : TEST_RUN_PAGE_ANON_QUERY, { fetchPolicy: 'cache-and-network', variables: { testPlanRunId, testPlanReportId }, - pollInterval + pollInterval: 0 } ); - // control the data flow, turn on polling if this is a collection job report - // that still has possible updates. useEffect(() => { - const status = data?.testPlanRun?.collectionJob?.status; - if (!isJobStatusFinal(status)) { - startPolling(pollInterval); - } else { - stopPolling(); - } if (data) setup(data); }, [data]); @@ -340,7 +330,7 @@ const TestRun = () => { }; // Check to see if there are tests to run - const hasTestsToRun = tests.length; + const testCount = tests.length; // Check if this test is being run as an admin if ( @@ -894,63 +884,6 @@ const TestRun = () => { editAtBrowserDetailsButtonRef.current.focus(); }; - const renderTestsCompletedInfoBox = () => { - let isReviewingBot = false; - if (openAsUserId) { - isReviewingBot = openAsUser.isBot; - } - - let content; - - if (isReviewingBot) { - content = ( - <> -

- {`${testResults.reduce( - (acc, { scenarioResults }) => - acc + - (scenarioResults && - scenarioResults.every(({ output }) => !!output) - ? 1 - : 0), - 0 - )} of ${tests.length}`}{' '} - responses collected. -

-

- Collection Job Status:{' '} - {testPlanRun.collectionJob.status} -

- - ); - } else if (!isSignedIn) { - content = {tests.length} tests to view; - } else if (hasTestsToRun) { - content = ( - <> - {' '} - {`${testResults.reduce( - (acc, { completedAt }) => acc + (completedAt ? 1 : 0), - 0 - )} of ${tests.length}`}{' '} - tests completed - - ); - } else { - content =
No tests for this AT and Browser combination
; - } - return ( -
-
- - {content} -
-
- ); - }; - const renderTestContent = (testPlanReport, currentTest, heading) => { const { index } = currentTest; const isComplete = currentTest.testResult @@ -1083,6 +1016,11 @@ const TestRun = () => { ); + // we are ready enough to show the page and all the buttons when the above code is + // pageReady and we have an anon view, bot test run, or a test result to display + const completeRender = + pageReady && (!isSignedIn || currentTest.testResult); + return ( <>

@@ -1097,7 +1035,7 @@ const TestRun = () => { handleReviewConflictsButtonClick } /> - {pageReady && (isSignedIn ? currentTest.testResult : true) && ( + {completeRender && ( @@ -1226,90 +1164,32 @@ const TestRun = () => { let heading; let content; - let openAsUserHeading = null; - - if (openAsUserId) { - if (openAsUser.isBot) { - const status = testPlanRun.collectionJob.status; - openAsUserHeading = ( -
- Reviewing tests of{' '} - {' '} - {`${openAsUser.username}`}. - {!isJobStatusFinal(status) && ( - <> -
- The collection bot is still updating information on - this page. Changes may be lost when updates arrive. - - )} -
- ); - } else { - openAsUserHeading = ( -
- Reviewing tests of {`${openAsUser.username}`}. -

{`All changes will be saved as performed by ${openAsUser.username}.`}

-
- ); - } - } heading = pageReady && ( - <> -
-
-
- Test Plan:{' '} - {`${ - testPlanVersion.title || - testPlanVersion.testPlan?.directory || - '' - }`} -
-
-
-
-
- AT:{' '} - {`${testPlanReport.at?.name}${ - isSignedIn ? ` ${currentAtVersion?.name}` : '' - }`} -
-
- Browser:{' '} - {`${testPlanReport.browser?.name}${ - isSignedIn - ? ` ${currentBrowserVersion?.name || ''}` - : '' - }`} -
-
- {isSignedIn && ( - - )} -
- {renderTestsCompletedInfoBox()} -
- {openAsUserHeading} - + ); if (!isSignedIn || !testPlanRun?.isComplete) { - content = hasTestsToRun ? ( + content = testCount ? ( renderTestContent(testPlanReport, currentTest, heading) ) : ( // No tests loaded @@ -1335,110 +1215,114 @@ const TestRun = () => { return ( pageReady && ( - - - - {hasTestsToRun - ? `${currentTest.title} for ${testPlanReport.at?.name} ${currentAtVersion?.name} and ${testPlanReport.browser?.name} ${currentBrowserVersion?.name} ` + - `| ARIA-AT` - : 'No tests for this AT and Browser | ARIA-AT'} - - - {updateMessageComponent && ( - - {updateMessageComponent} - - )} - - - - - {content} - - - - {showThemedModal && ( - - )} - {isSignedIn && isShowingAtBrowserModal && ( - { - // Only provide at version options that released - // at the same time or later than the minimum - // AT version - let earliestReleasedAt = null; - if (testPlanReport.minimumAtVersion) { - earliestReleasedAt = new Date( - testPlanReport.minimumAtVersion.releasedAt - ); - return ( - new Date(item.releasedAt) >= - earliestReleasedAt - ); - } - return item; - }) - .map(item => item.name)} - browserName={testPlanReport.browser.name} - browserVersion={ - currentTest.testResult?.browserVersion?.name - } - browserVersions={testPlanReport.browser.browserVersions.map( - item => item.name - )} - patternName={testPlanVersion.title} - testerName={tester.username} - exactAtVersion={testPlanReport.exactAtVersion} - handleAction={handleAtAndBrowserDetailsModalAction} - handleClose={handleAtAndBrowserDetailsModalCloseAction} - /> - )} - + + + + + {testCount + ? `${currentTest.title} for ${testPlanReport.at?.name} ${currentAtVersion?.name} and ${testPlanReport.browser?.name} ${currentBrowserVersion?.name} ` + + `| ARIA-AT` + : 'No tests for this AT and Browser | ARIA-AT'} + + + {updateMessageComponent && ( + + {updateMessageComponent} + + )} + + + + + {content} + + + + {showThemedModal && ( + + )} + {isSignedIn && isShowingAtBrowserModal && ( + { + // Only provide at version options that released + // at the same time or later than the minimum + // AT version + let earliestReleasedAt = null; + if (testPlanReport.minimumAtVersion) { + earliestReleasedAt = new Date( + testPlanReport.minimumAtVersion.releasedAt + ); + return ( + new Date(item.releasedAt) >= + earliestReleasedAt + ); + } + return item; + }) + .map(item => item.name)} + exactAtVersion={testPlanReport.exactAtVersion} + browserName={testPlanReport.browser.name} + browserVersion={ + currentTest.testResult?.browserVersion?.name + } + browserVersions={testPlanReport.browser.browserVersions.map( + item => item.name + )} + patternName={testPlanVersion.title} + testerName={tester.username} + handleAction={handleAtAndBrowserDetailsModalAction} + handleClose={ + handleAtAndBrowserDetailsModalCloseAction + } + /> + )} + + ) ); }; diff --git a/client/components/TestRun/queries.js b/client/components/TestRun/queries.js index 866be0658..e0e7ebd1a 100644 --- a/client/components/TestRun/queries.js +++ b/client/components/TestRun/queries.js @@ -206,6 +206,22 @@ export const TEST_RUN_PAGE_QUERY = gql` } `; +export const COLLECTION_JOB_UPDATES_QUERY = gql` + query CollectionJob($collectionJobId: ID!) { + collectionJob(id: $collectionJobId) { + id + status + externalLogsUrl + testStatus { + test { + id + } + status + } + } + } +`; + export const TEST_RUN_PAGE_ANON_QUERY = gql` query TestPlanRunAnonPage($testPlanReportId: ID!) { testPlanReport(id: $testPlanReportId) { From 89100a7ca0eedf8c2710f1f38091020b5ca303c8 Mon Sep 17 00:00:00 2001 From: "Mx. Corey Frang" Date: Thu, 20 Jun 2024 13:44:10 -0400 Subject: [PATCH 7/9] fix small render error for test run --- client/components/TestRun/Heading.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/components/TestRun/Heading.js b/client/components/TestRun/Heading.js index b2307dd44..66146b437 100644 --- a/client/components/TestRun/Heading.js +++ b/client/components/TestRun/Heading.js @@ -110,7 +110,7 @@ const TestRunHeading = ({ )} ); - } else { + } else if (openAsUser) { openAsUserHeading = (
Reviewing tests of {`${openAsUser.username}`}. From df31163f5794a9c699743560929e3c8d55062afd Mon Sep 17 00:00:00 2001 From: "Mx. Corey Frang" Date: Thu, 20 Jun 2024 16:50:28 -0400 Subject: [PATCH 8/9] Working for not logged in users again --- .../TestRun/CollectionJobContext.js | 19 ++++++++++++++----- client/components/TestRun/index.jsx | 2 +- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/client/components/TestRun/CollectionJobContext.js b/client/components/TestRun/CollectionJobContext.js index 4eeb37a79..bf7677fd8 100644 --- a/client/components/TestRun/CollectionJobContext.js +++ b/client/components/TestRun/CollectionJobContext.js @@ -12,10 +12,19 @@ export const Context = createContext({ actions: {} }); -export const Provider = ({ - children, - testPlanRun: { id: testPlanRunId, collectionJob: initialCollectionJob } -}) => { +export const Provider = ({ children, testPlanRun }) => { + if (!testPlanRun) { + // Anonymous page / not working, just viewing the tests, no need for the + // provider to provide any data or updates, but to be consistent we will + // still wrap with a provider with static data + return ( + + {children} + + ); + } + const { id: testPlanRunId, collectionJob: initialCollectionJob } = + testPlanRun; const [providerValue, setProviderValue] = useState({ state: { collectionJob: initialCollectionJob }, actions: {} @@ -60,5 +69,5 @@ Provider.propTypes = { status: PropTypes.string.isRequired, testStatus: PropTypes.arrayOf(PropTypes.object).isRequired }) - }).isRequired + }) }; diff --git a/client/components/TestRun/index.jsx b/client/components/TestRun/index.jsx index d0a6f378e..567603cc9 100644 --- a/client/components/TestRun/index.jsx +++ b/client/components/TestRun/index.jsx @@ -955,7 +955,7 @@ const TestRun = () => { ]; } - const externalLogsUrl = testPlanRun.collectionJob?.externalLogsUrl; + const externalLogsUrl = testPlanRun?.collectionJob?.externalLogsUrl; const menuRightOfContent = (
From 08d9e3cb02251e117df5e2ad4622633fb036424e Mon Sep 17 00:00:00 2001 From: "Mx. Corey Frang" Date: Thu, 20 Jun 2024 16:58:11 -0400 Subject: [PATCH 9/9] Add smoke tests for /test-plan-report/15 as anon and admin --- client/tests/smokeTest.test.js | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/client/tests/smokeTest.test.js b/client/tests/smokeTest.test.js index 3818b1d3b..fb1a92f22 100644 --- a/client/tests/smokeTest.test.js +++ b/client/tests/smokeTest.test.js @@ -111,7 +111,33 @@ describe('smoke test', () => { const h1Handle = await page.waitForSelector('h1'); const h1Text = await h1Handle.evaluate(h1 => h1.innerText); expect(h1Text).toBe('Data Management'); - }) + }), + getPage( + { role: false, url: '/test-plan-report/15' }, + async page => { + // Wait for an h2 because an h1 will show while the page is + // still loading + await page.waitForSelector('h2'); + const h1Handle = await page.waitForSelector('h1'); + const h1Text = await h1Handle.evaluate(h1 => h1.innerText); + expect(h1Text).toBe( + 'Test 1:\nNavigate forwards to a not pressed toggle button' + ); + } + ), + getPage( + { role: 'admin', url: '/test-plan-report/15' }, + async page => { + // Wait for an h2 because an h1 will show while the page is + // still loading + await page.waitForSelector('h2'); + const h1Handle = await page.waitForSelector('h1'); + const h1Text = await h1Handle.evaluate(h1 => h1.innerText); + expect(h1Text).toBe( + 'Test 1:\nNavigate forwards to a not pressed toggle button' + ); + } + ) ]); }); });