Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

per-test status updates frontend and mock helpers #1058

Merged
merged 3 commits into from
May 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
152 changes: 60 additions & 92 deletions client/components/BotRunTestStatusList/index.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,9 @@
import React, { useEffect, useMemo, useRef, useState } from 'react';
import React, { useMemo } from 'react';
import PropTypes from 'prop-types';
import {
COLLECTION_JOB_STATUS_BY_TEST_PLAN_RUN_ID_QUERY,
TEST_PLAN_RUNS_TEST_RESULTS_QUERY
} from './queries';
import { useLazyQuery, useQuery } from '@apollo/client';
import { TEST_PLAN_RUNS_TEST_RESULTS_QUERY } from './queries';
import { useQuery } from '@apollo/client';
import styled from '@emotion/styled';
import ReportStatusDot from '../common/ReportStatusDot';
import { isBot } from '../../utils/automation';

const BotRunTestStatusUnorderedList = styled.ul`
list-style-type: none;
Expand Down Expand Up @@ -35,121 +31,93 @@ const BotRunTestStatusUnorderedList = styled.ul`
const testCountString = (count, status) =>
`${count} Test${count === 1 ? '' : 's'} ${status}`;

const BotRunTestStatusList = ({ testPlanReportId, runnableTestsLength }) => {
const pollInterval = 2000;

const BotRunTestStatusList = ({ testPlanReportId }) => {
const {
data: testPlanRunsQueryResult,
startPolling,
stopPolling
} = useQuery(TEST_PLAN_RUNS_TEST_RESULTS_QUERY, {
variables: { testPlanReportId },
fetchPolicy: 'cache-and-network',
pollInterval: 2000
pollInterval
});

const [getCollectionJobStatus, { data: collectionJobStatusQueryResult }] =
useLazyQuery(COLLECTION_JOB_STATUS_BY_TEST_PLAN_RUN_ID_QUERY, {
fetchPolicy: 'cache-and-network'
});

const [collectedData, setCollectedData] = useState([]);
const requestedTestRunIds = useRef(new Set());

const botTestPlanRuns = useMemo(() => {
if (!testPlanRunsQueryResult?.testPlanRuns) {
return [];
}
return testPlanRunsQueryResult.testPlanRuns.filter(testPlanRun =>
isBot(testPlanRun.tester)
);
}, [testPlanRunsQueryResult?.testPlanRuns]);

useEffect(() => {
const ids = botTestPlanRuns.map(run => run.id);
for (const id of ids) {
if (!requestedTestRunIds.current.has(id)) {
requestedTestRunIds.current.add(id);
getCollectionJobStatus({
variables: { testPlanRunId: id }
});
}
}
}, [botTestPlanRuns]);

useEffect(() => {
if (collectionJobStatusQueryResult?.collectionJobByTestPlanRunId) {
const { status } =
collectionJobStatusQueryResult.collectionJobByTestPlanRunId;
setCollectedData(prev => [...prev, status]);
}
}, [collectionJobStatusQueryResult?.collectionJobByTestPlanRunId]);

const [numTestsCompleted, numTestsQueued, numTestsCancelled] =
useMemo(() => {
const res = [0, 0, 0];
if (
botTestPlanRuns &&
botTestPlanRuns.length &&
collectedData.length === botTestPlanRuns.length
) {
for (let i = 0; i < botTestPlanRuns.length; i++) {
const status = collectedData[i];
res[0] += botTestPlanRuns[i].testResults.length;
switch (status) {
case 'COMPLETED':
case 'RUNNING':
case 'QUEUED':
res[1] +=
runnableTestsLength -
botTestPlanRuns[i].testResults.length;
break;
case 'CANCELLED':
res[2] +=
runnableTestsLength -
botTestPlanRuns[i].testResults.length;
break;
default:
break;
const { COMPLETED, ERROR, RUNNING, CANCELLED, QUEUED } = useMemo(() => {
const counter = {
COMPLETED: 0,
ERROR: 0,
RUNNING: 0,
CANCELLED: 0,
QUEUED: 0
};
let anyPossibleUpdates = false;
if (testPlanRunsQueryResult?.testPlanRuns) {
for (const {
collectionJob
} of testPlanRunsQueryResult.testPlanRuns) {
if (collectionJob?.testStatus) {
for (const { status } of collectionJob.testStatus) {
counter[status]++;
if (status === 'QUEUED' || status === 'RUNNING') {
anyPossibleUpdates = true;
}
}
}
if (
res[0] + res[2] ===
runnableTestsLength * botTestPlanRuns.length
) {
stopPolling();
}
}
return res;
}, [testPlanRunsQueryResult, collectedData, stopPolling, startPolling]);
// it's possible that we got incomplete data on first fetch and
// stopped the polling, so restart the polling if we detect any
// possible future updates, otherwise stop.
if (anyPossibleUpdates) {
startPolling(pollInterval);
} else {
stopPolling();
}
}
return counter;
}, [testPlanRunsQueryResult, stopPolling, startPolling]);

if (
!botTestPlanRuns ||
botTestPlanRuns.length === 0 ||
!collectedData ||
!(collectedData.length === botTestPlanRuns.length)
!testPlanRunsQueryResult ||
testPlanRunsQueryResult.testPlanRuns.length === 0
) {
return null;
}
return (
<BotRunTestStatusUnorderedList className="text-secondary fs-6">
{RUNNING > 0 && (
<li className="m-2">
<ReportStatusDot className="tests-running" />
{testCountString(RUNNING, 'Running')}
</li>
)}
{ERROR > 0 && (
<li className="m-2">
<ReportStatusDot className="tests-error" />
{testCountString(ERROR, 'Error')}
</li>
)}
<li className="m-2">
<ReportStatusDot className="tests-complete" />
{testCountString(numTestsCompleted, 'Completed')}
{testCountString(COMPLETED, 'Completed')}
</li>
<li className="m-2">
<ReportStatusDot className="tests-queued" />
{testCountString(numTestsQueued, 'Queued')}
</li>
<li className="m-2">
<ReportStatusDot className="tests-cancelled" />
{testCountString(numTestsCancelled, 'Cancelled')}
{testCountString(QUEUED, 'Queued')}
</li>
{CANCELLED > 0 && (
<li className="m-2">
<ReportStatusDot className="tests-cancelled" />
{testCountString(CANCELLED, 'Cancelled')}
</li>
)}
</BotRunTestStatusUnorderedList>
);
};

BotRunTestStatusList.propTypes = {
testPlanReportId: PropTypes.string.isRequired,
runnableTestsLength: PropTypes.number.isRequired
testPlanReportId: PropTypes.string.isRequired
};

export default BotRunTestStatusList;
15 changes: 6 additions & 9 deletions client/components/BotRunTestStatusList/queries.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,12 @@ export const TEST_PLAN_RUNS_TEST_RESULTS_QUERY = gql`
}
}
}
}
}
`;

export const COLLECTION_JOB_STATUS_BY_TEST_PLAN_RUN_ID_QUERY = gql`
query CollectionJobStatusByTestPlanRunId($testPlanRunId: ID!) {
collectionJobByTestPlanRunId(testPlanRunId: $testPlanRunId) {
id
status
collectionJob {
status
testStatus {
status
}
}
}
}
`;
13 changes: 12 additions & 1 deletion client/components/common/ReportStatusDot/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,15 @@ const ReportStatusDot = styled.span`
background: #7c7c7c;
}

&.tests-running {
border: 2px solid #1e8f37;
background: #d2d5d9;
}

&.tests-error {
background: #e3261f;
}

&.tests-queued,
&.reports-in-progress {
background: #3876e8;
Expand All @@ -27,7 +36,9 @@ const ReportStatusDot = styled.span`
background: #2ba51c;
}

&.tests-cancelled,
&.tests-cancelled {
background: #a231ff;
}
&.reports-missing {
background: #ce1b4c;
}
Expand Down
4 changes: 4 additions & 0 deletions server/graphql-schema.js
Original file line number Diff line number Diff line change
Expand Up @@ -846,6 +846,10 @@ const graphqlSchema = gql`
Whether the TestPlanRun was initiated by the Response Collection System
"""
initiatedByAutomation: Boolean!
"""
The CollectionJob related to this testPlanRun
"""
collectionJob: CollectionJob
}

"""
Expand Down
3 changes: 2 additions & 1 deletion server/resolvers/CollectionJob/index.js
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
const testStatus = require('./testStatusResolver');
module.exports = { testStatus };
const testPlanRun = require('./testPlanRunResolver');
module.exports = { testStatus, testPlanRun };
10 changes: 10 additions & 0 deletions server/resolvers/CollectionJob/testPlanRunResolver.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// eslint-disable-next-line no-unused-vars
const testPlanRunResolver = (collectionJob, _, context) => {
if (collectionJob.__testPlanRunChild) {
throw new Error(
'can not request TestPlanRun.collectionJob.testPlanRun'
);
}
return collectionJob.testPlanRun;
};
module.exports = testPlanRunResolver;
19 changes: 19 additions & 0 deletions server/resolvers/TestPlanRun/collectionJobResolver.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
const collectionJobByTestPlanRunIdResolver = require('../collectionJobByTestPlanRunIdResolver');

const collectionJobResolver = async (
testPlanRun,
args, // eslint-disable-line no-unused-vars
context // eslint-disable-line no-unused-vars
) => {
const collectionJob = await collectionJobByTestPlanRunIdResolver(
null,
{ testPlanRunId: testPlanRun.id },
context
);
if (collectionJob) {
return { ...collectionJob.dataValues, __testPlanRunChild: true };
}
return collectionJob;
};

module.exports = collectionJobResolver;
3 changes: 2 additions & 1 deletion server/resolvers/TestPlanRun/index.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
const testResults = require('./testResultsResolver');
const testResultsLength = require('./testResultsLengthResolver');

const collectionJob = require('./collectionJobResolver');
module.exports = {
collectionJob,
testResults,
testResultsLength
};
3 changes: 2 additions & 1 deletion server/tests/integration/graphql.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -506,10 +506,11 @@ describe('graphql', () => {
releasedAt
}
}
testPlanRun(id: 3) {
testPlanRun(id: 1) {
__typename
id
initiatedByAutomation
collectionJob { id }
testPlanReport {
id
}
Expand Down
Loading
Loading