diff --git a/client/components/ManageBotRunDialog/index.jsx b/client/components/ManageBotRunDialog/index.jsx index 4706dcf22..b2cd54584 100644 --- a/client/components/ManageBotRunDialog/index.jsx +++ b/client/components/ManageBotRunDialog/index.jsx @@ -10,7 +10,6 @@ import { } from './queries'; import DeleteButton from '../common/DeleteButton'; import BotRunTestStatusList from '../BotRunTestStatusList'; -import { isBot } from '../../utils/automation'; import './ManageBotRunDialog.css'; import MarkBotRunFinishedButton from './MarkBotRunFinishedButton'; @@ -59,7 +58,7 @@ const ManageBotRunDialog = ({ () => testers.filter( t => - !isBot(t) && + !t.isBot && !testPlanReportAssignedTestersQuery?.testPlanReport.draftTestPlanRuns.some( d => d.tester.id === t.id ) diff --git a/client/components/TestQueue/AssignTesterDropdown/index.jsx b/client/components/TestQueue/AssignTesterDropdown/index.jsx index a21e5b226..98463d388 100644 --- a/client/components/TestQueue/AssignTesterDropdown/index.jsx +++ b/client/components/TestQueue/AssignTesterDropdown/index.jsx @@ -16,10 +16,7 @@ import { import { useMutation, useQuery } from '@apollo/client'; import { LoadingStatus, useTriggerLoad } from '../../common/LoadingStatus'; import { SCHEDULE_COLLECTION_JOB_MUTATION } from '../../AddTestToQueueWithConfirmation/queries'; -import { - isBot, - isSupportedByResponseCollector -} from '../../../utils/automation'; +import { isSupportedByResponseCollector } from '../../../utils/automation'; import './AssignTesterDropdown.css'; @@ -77,7 +74,7 @@ const AssignTesterDropdown = ({ }); }, `Updating Test Plan Assignees. Deleting Test Plan Run for ${tester.username}`); } else { - if (isBot(tester)) { + if (tester.isBot) { await triggerLoad(async () => { await scheduleCollection({ variables: { @@ -131,12 +128,12 @@ const AssignTesterDropdown = ({ const testerIsAssigned = isTesterAssigned(username); const classname = [ testerIsAssigned ? 'assigned' : 'not-assigned', - isBot(tester) ? 'bot' : 'human' + tester.isBot ? 'bot' : 'human' ].join(' '); let icon; if (testerIsAssigned) { icon = faCheck; - } else if (isBot(tester)) { + } else if (tester.isBot) { const supportedByBot = isSupportedByResponseCollector( testPlanReportAtBrowserQuery?.testPlanReport diff --git a/client/components/TestQueue/queries.js b/client/components/TestQueue/queries.js index 0f5ca2375..51d57a5f8 100644 --- a/client/components/TestQueue/queries.js +++ b/client/components/TestQueue/queries.js @@ -11,6 +11,7 @@ export const TEST_QUEUE_PAGE_QUERY = gql` id username roles + isBot } ats { id @@ -77,6 +78,7 @@ export const TEST_QUEUE_PAGE_QUERY = gql` tester { id username + isBot } testResultsLength } @@ -128,6 +130,7 @@ export const TEST_PLAN_REPORT_QUERY = gql` tester { id username + isBot } testResults { id @@ -267,6 +270,7 @@ export const ASSIGN_TESTER_MUTATION = gql` tester { id username + isBot } } } @@ -304,6 +308,7 @@ export const REMOVE_TESTER_MUTATION = gql` tester { id username + isBot } } } diff --git a/client/components/TestQueueCompletionStatusListItem/index.js b/client/components/TestQueueCompletionStatusListItem/index.js index ba3099eea..1ade96353 100644 --- a/client/components/TestQueueCompletionStatusListItem/index.js +++ b/client/components/TestQueueCompletionStatusListItem/index.js @@ -3,7 +3,6 @@ import PropTypes from 'prop-types'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { faRobot } from '@fortawesome/free-solid-svg-icons'; import BotTestCompletionStatus from './BotTestCompletionStatus'; -import { isBot } from '../../utils/automation'; import PreviouslyAutomatedTestCompletionStatus from './PreviouslyAutomatedTestCompletionStatus'; const TestQueueCompletionStatusListItem = ({ @@ -12,14 +11,13 @@ const TestQueueCompletionStatusListItem = ({ id }) => { const { testResultsLength, tester } = testPlanRun; - const testerIsBot = useMemo(() => isBot(tester), [tester]); const testPlanRunPreviouslyAutomated = useMemo( () => testPlanRun.initiatedByAutomation, [testPlanRun] ); const renderTesterInfo = () => { - if (testerIsBot) { + if (tester.isBot) { return ( @@ -44,7 +42,7 @@ const TestQueueCompletionStatusListItem = ({ }; const renderTestCompletionStatus = () => { - if (testerIsBot) { + if (tester.isBot) { return ( { - const botTestPlanRun = draftTestPlanRuns.find(({ tester }) => - isBot(tester) + const botTestPlanRun = draftTestPlanRuns.find( + ({ tester: { isBot } }) => isBot ); if (isAdmin && !isLoading) { diff --git a/client/components/TestRun/TestNavigator.jsx b/client/components/TestRun/TestNavigator.jsx index 68e99ba36..e1aba2b0c 100644 --- a/client/components/TestRun/TestNavigator.jsx +++ b/client/components/TestRun/TestNavigator.jsx @@ -8,7 +8,6 @@ import { import { Col } from 'react-bootstrap'; import React, { useMemo } from 'react'; import '@fortawesome/fontawesome-svg-core/styles.css'; -import { isBot } from '../../utils/automation'; import { COLLECTION_JOB_STATUS_BY_TEST_PLAN_RUN_ID_QUERY } from './queries'; import { useQuery } from '@apollo/client'; @@ -24,7 +23,7 @@ const TestNavigator = ({ handleTestClick = () => {}, testPlanRun = null }) => { - const isBotCompletedTest = isBot(testPlanRun?.tester); + const isBotCompletedTest = testPlanRun?.tester?.isBot; const { data: collectionJobQuery } = useQuery( COLLECTION_JOB_STATUS_BY_TEST_PLAN_RUN_ID_QUERY, diff --git a/client/components/TestRun/index.jsx b/client/components/TestRun/index.jsx index 957c65cda..0f44ed085 100644 --- a/client/components/TestRun/index.jsx +++ b/client/components/TestRun/index.jsx @@ -42,7 +42,6 @@ import './TestRun.css'; import ReviewConflicts from '../ReviewConflicts'; import createIssueLink from '../../utils/createIssueLink'; import { convertDateToString } from '../../utils/formatter'; -import { isBot } from '../../utils/automation'; const TestRun = () => { const params = useParams(); @@ -895,7 +894,7 @@ const TestRun = () => { const renderTestsCompletedInfoBox = () => { let isReviewingBot = false; if (openAsUserId) { - isReviewingBot = isBot(openAsUser); + isReviewingBot = openAsUser.isBot; } let content; @@ -1037,7 +1036,7 @@ const TestRun = () => { href={issueLink} /> - {isBot(openAsUser) && externalLogsUrl ? ( + {openAsUser?.isBot && externalLogsUrl ? (
  • { submitButtonRef={ testRendererSubmitButtonRef } - isReviewingBot={isBot(openAsUser)} + isReviewingBot={openAsUser?.isBot} isSubmitted={isTestSubmitClicked} isEdit={isTestEditClicked} setIsRendererReady={setIsRendererReady} @@ -1222,7 +1221,7 @@ const TestRun = () => { let openAsUserHeading = null; if (openAsUserId) { - if (isBot(openAsUser)) { + if (openAsUser.isBot) { openAsUserHeading = (
    Reviewing tests of{' '} diff --git a/client/components/TestRun/queries.js b/client/components/TestRun/queries.js index e8d485dbe..138b77abf 100644 --- a/client/components/TestRun/queries.js +++ b/client/components/TestRun/queries.js @@ -8,6 +8,7 @@ export const TEST_RUN_PAGE_QUERY = gql` tester { id username + isBot } testResults { id @@ -179,6 +180,7 @@ export const TEST_RUN_PAGE_QUERY = gql` users { id username + isBot } } `; @@ -211,6 +213,7 @@ export const TEST_RUN_PAGE_ANON_QUERY = gql` id tester { username + isBot } } scenarioResult { @@ -304,6 +307,7 @@ export const FIND_OR_CREATE_TEST_RESULT_MUTATION = gql` tester { id username + isBot } testResults { id @@ -498,6 +502,7 @@ export const FIND_OR_CREATE_TEST_RESULT_MUTATION = gql` testPlanRun { id tester { + isBot username } } @@ -596,6 +601,7 @@ export const SAVE_TEST_RESULT_MUTATION = gql` tester { id username + isBot } testResults { id diff --git a/client/tests/AssignTesterDropdown.test.jsx b/client/tests/AssignTesterDropdown.test.jsx index 8852eb6ad..bb8dce235 100644 --- a/client/tests/AssignTesterDropdown.test.jsx +++ b/client/tests/AssignTesterDropdown.test.jsx @@ -28,9 +28,9 @@ jest.mock('@apollo/client', () => { }); const mockPossibleTesters = [ - { id: '1', username: 'bee' }, - { id: '2', username: 'puppy' }, - { id: '3', username: 'NVDA Bot' } + { id: '1', username: 'bee', isBot: false }, + { id: '2', username: 'puppy', isBot: false }, + { id: '3', username: 'NVDA Bot', isBot: true } ]; const mockProps = { diff --git a/client/tests/__mocks__/GraphQLMocks/TestQueuePageAdminNotPopulatedMock.js b/client/tests/__mocks__/GraphQLMocks/TestQueuePageAdminNotPopulatedMock.js index f5243b0df..71e44308a 100644 --- a/client/tests/__mocks__/GraphQLMocks/TestQueuePageAdminNotPopulatedMock.js +++ b/client/tests/__mocks__/GraphQLMocks/TestQueuePageAdminNotPopulatedMock.js @@ -17,17 +17,20 @@ export default testQueuePageQuery => [ { id: '1', username: 'foo-bar', - roles: ['ADMIN', 'TESTER'] + roles: ['ADMIN', 'TESTER'], + isBot: false }, { id: '4', username: 'bar-foo', - roles: ['TESTER'] + roles: ['TESTER'], + isBot: false }, { id: '5', username: 'boo-far', - roles: ['TESTER'] + roles: ['TESTER'], + isBot: false } ], testPlanVersions: [], diff --git a/client/tests/__mocks__/GraphQLMocks/TestQueuePageAdminPopulatedMock.js b/client/tests/__mocks__/GraphQLMocks/TestQueuePageAdminPopulatedMock.js index fad26ed4d..53008087a 100644 --- a/client/tests/__mocks__/GraphQLMocks/TestQueuePageAdminPopulatedMock.js +++ b/client/tests/__mocks__/GraphQLMocks/TestQueuePageAdminPopulatedMock.js @@ -8,7 +8,8 @@ export default testQueuePageQuery => [ me: { id: '101', username: 'alflennik', - roles: ['ADMIN', 'TESTER'] + roles: ['ADMIN', 'TESTER'], + isBot: false }, ats: [ { @@ -173,13 +174,20 @@ export default testQueuePageQuery => [ { id: '1', username: 'esmeralda-baggins', - roles: ['TESTER', 'ADMIN'] + roles: ['TESTER', 'ADMIN'], + isBot: false + }, + { + id: '2', + username: 'tom-proudfeet', + roles: ['TESTER'], + isBot: false }, - { id: '2', username: 'tom-proudfeet', roles: ['TESTER'] }, { id: '101', username: 'alflennik', - roles: ['TESTER', 'ADMIN'] + roles: ['TESTER', 'ADMIN'], + isBot: false } ], testPlanVersions: [ @@ -243,7 +251,8 @@ export default testQueuePageQuery => [ id: '1', tester: { id: '1', - username: 'esmeralda-baggins' + username: 'esmeralda-baggins', + isBot: false }, testResultsLength: 0, initiatedByAutomation: false @@ -272,7 +281,8 @@ export default testQueuePageQuery => [ id: '1', tester: { id: '1', - username: 'esmeralda-baggins' + username: 'esmeralda-baggins', + isBot: false }, testResultsLength: 0, initiatedByAutomation: false @@ -299,13 +309,21 @@ export default testQueuePageQuery => [ draftTestPlanRuns: [ { id: '3', - tester: { id: '2', username: 'tom-proudfeet' }, + tester: { + id: '2', + username: 'tom-proudfeet', + isBot: false + }, testResultsLength: 3, initiatedByAutomation: false }, { id: '101', - tester: { id: '101', username: 'alflennik' }, + tester: { + id: '101', + username: 'alflennik', + isBot: false + }, testResultsLength: 1, initiatedByAutomation: false }, @@ -313,7 +331,8 @@ export default testQueuePageQuery => [ id: '2', tester: { id: '1', - username: 'esmeralda-baggins' + username: 'esmeralda-baggins', + isBot: false }, testResultsLength: 3, initiatedByAutomation: false diff --git a/client/tests/__mocks__/GraphQLMocks/TestQueuePageTesterNotPopulatedMock.js b/client/tests/__mocks__/GraphQLMocks/TestQueuePageTesterNotPopulatedMock.js index fe03d4e0e..266235ed0 100644 --- a/client/tests/__mocks__/GraphQLMocks/TestQueuePageTesterNotPopulatedMock.js +++ b/client/tests/__mocks__/GraphQLMocks/TestQueuePageTesterNotPopulatedMock.js @@ -18,18 +18,21 @@ export default testQueuePageQuery => [ id: '1', username: 'foo-bar', roles: ['ADMIN', 'TESTER'], + isBot: false, __typename: 'User' }, { id: '4', username: 'bar-foo', roles: ['TESTER'], + isBot: false, __typename: 'User' }, { id: '5', username: 'boo-far', roles: ['TESTER'], + isBot: false, __typename: 'User' } ], diff --git a/client/tests/__mocks__/GraphQLMocks/TestQueuePageTesterPopulatedMock.js b/client/tests/__mocks__/GraphQLMocks/TestQueuePageTesterPopulatedMock.js index 31f3a1808..7e621452c 100644 --- a/client/tests/__mocks__/GraphQLMocks/TestQueuePageTesterPopulatedMock.js +++ b/client/tests/__mocks__/GraphQLMocks/TestQueuePageTesterPopulatedMock.js @@ -174,17 +174,20 @@ export default testQueuePageQuery => [ { id: '1', username: 'foo-bar', - roles: ['ADMIN', 'TESTER'] + roles: ['ADMIN', 'TESTER'], + isBot: false }, { id: '4', username: 'bar-foo', - roles: ['TESTER'] + roles: ['TESTER'], + isBot: false }, { id: '5', username: 'boo-far', - roles: ['TESTER'] + roles: ['TESTER'], + isBot: false } ], testPlanVersions: [ @@ -256,7 +259,8 @@ export default testQueuePageQuery => [ id: '18', tester: { id: '1', - username: 'foo-bar' + username: 'foo-bar', + isBot: false }, testResultsLength: 0, initiatedByAutomation: false @@ -265,7 +269,8 @@ export default testQueuePageQuery => [ id: '19', tester: { id: '4', - username: 'bar-foo' + username: 'bar-foo', + isBot: false }, testResultsLength: 0, initiatedByAutomation: false @@ -302,7 +307,8 @@ export default testQueuePageQuery => [ id: '20', tester: { id: '5', - username: 'boo-far' + username: 'boo-far', + isBot: false }, testResultsLength: 0, initiatedByAutomation: false diff --git a/client/utils/automation.js b/client/utils/automation.js index 51cbf084f..4406116ed 100644 --- a/client/utils/automation.js +++ b/client/utils/automation.js @@ -8,8 +8,6 @@ export const isSupportedByResponseCollector = ctx => { ); }; -export const isBot = user => user?.username?.toLowerCase().slice(-3) === 'bot'; - // TODO: Stub, support for more bot users should be added export const getBotUsernameFromAtBrowser = (at, browser) => { if (at?.name === 'NVDA' && browser?.name === 'Chrome') { diff --git a/server/graphql-schema.js b/server/graphql-schema.js index 6c132b920..2085d08bd 100644 --- a/server/graphql-schema.js +++ b/server/graphql-schema.js @@ -50,6 +50,10 @@ const graphqlSchema = gql` List of types of actions the user can complete. """ roles: [Role]! + """ + Whether the user is an automation bot user. + """ + isBot: Boolean! # TODO: Either use the recorded data somewhere or eliminate the field. """ The ATs the user has indicated they are able to test. diff --git a/server/migrations/20240509102300-addIsBot.js b/server/migrations/20240509102300-addIsBot.js new file mode 100644 index 000000000..684e0cd89 --- /dev/null +++ b/server/migrations/20240509102300-addIsBot.js @@ -0,0 +1,23 @@ +module.exports = { + up: async (queryInterface, Sequelize) => { + return queryInterface.sequelize.transaction(async transaction => { + await queryInterface.addColumn( + 'User', + 'isBot', + { + type: Sequelize.BOOLEAN, + allowNull: false, + defaultValue: false + }, + { transaction } + ); + await queryInterface.sequelize.query( + `UPDATE "User" SET "isBot" = true WHERE "username" LIKE '% Bot'`, + { transaction } + ); + }); + }, + down: async queryInterface => { + await queryInterface.removeColumn('User', 'isBot'); + } +}; diff --git a/server/models/User.js b/server/models/User.js index cbd2ba3e9..a2459ace3 100644 --- a/server/models/User.js +++ b/server/models/User.js @@ -15,6 +15,11 @@ module.exports = function (sequelize, DataTypes) { allowNull: false, unique: true }, + isBot: { + type: DataTypes.BOOLEAN, + allowNull: false, + defaultValue: false + }, createdAt: { type: DataTypes.DATE }, updatedAt: { type: DataTypes.DATE } }, diff --git a/server/seeders/20231218191524-addNVDABot.js b/server/seeders/20231218191524-addNVDABot.js index 59e5fc10a..52f60747d 100644 --- a/server/seeders/20231218191524-addNVDABot.js +++ b/server/seeders/20231218191524-addNVDABot.js @@ -10,6 +10,7 @@ module.exports = { { id: responseCollectionUser.id, // Specified ID for NVDA Bot username: responseCollectionUser.username, + isBot: responseCollectionUser.isBot, createdAt: new Date(), updatedAt: new Date() } diff --git a/server/tests/integration/graphql.test.js b/server/tests/integration/graphql.test.js index 8152a5f68..cfc0ffbe6 100644 --- a/server/tests/integration/graphql.test.js +++ b/server/tests/integration/graphql.test.js @@ -246,6 +246,7 @@ describe('graphql', () => { __typename username roles + isBot } me { __typename diff --git a/server/tests/models/User.spec.js b/server/tests/models/User.spec.js index e704f96a7..efa71d252 100644 --- a/server/tests/models/User.spec.js +++ b/server/tests/models/User.spec.js @@ -19,7 +19,7 @@ describe('UserModel Schema Checks', () => { describe('properties', () => { // A3 - ['username', 'createdAt', 'updatedAt'].forEach( + ['username', 'isBot', 'createdAt', 'updatedAt'].forEach( checkPropertyExists(modelInstance) ); }); diff --git a/server/util/responseCollectionUser.js b/server/util/responseCollectionUser.js index 939d456bb..e1babb3f2 100644 --- a/server/util/responseCollectionUser.js +++ b/server/util/responseCollectionUser.js @@ -1,6 +1,7 @@ // TODO: Defaults to NVDA, offer AT-specific bot once support is added const responseCollectionUser = { username: 'NVDA Bot', + isBot: true, id: 9999 };