diff --git a/client/components/DataManagement/queries.js b/client/components/DataManagement/queries.js
index d5c40cc46..64398e0be 100644
--- a/client/components/DataManagement/queries.js
+++ b/client/components/DataManagement/queries.js
@@ -15,6 +15,10 @@ export const DATA_MANAGEMENT_PAGE_QUERY = gql`
name
releasedAt
}
+ browsers {
+ id
+ name
+ }
candidateBrowsers {
id
}
diff --git a/client/components/ManageTestQueue/index.jsx b/client/components/ManageTestQueue/index.jsx
index 1d884bf30..1339dc6d9 100644
--- a/client/components/ManageTestQueue/index.jsx
+++ b/client/components/ManageTestQueue/index.jsx
@@ -1,9 +1,13 @@
import React, { useEffect, useState, useRef } from 'react';
import { useMutation } from '@apollo/client';
-import { 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
+ // faChevronDown
+} from '@fortawesome/free-solid-svg-icons';
import PropTypes from 'prop-types';
import BasicModal from '../common/BasicModal';
import UpdateVersionModal from '../common/UpdateVersionModal';
@@ -13,11 +17,34 @@ import {
EDIT_AT_VERSION_MUTATION,
DELETE_AT_VERSION_MUTATION
} from '../TestQueue/queries';
+import {
+ CREATE_REQUIRED_REPORT_MUTATION,
+ UPDATE_REQUIRED_REPORT_MUTATION,
+ DELETE_REQUIRED_REPORT_MUTATION
+} from './queries';
import { gitUpdatedDateToString } from '../../utils/gitUtils';
import { convertStringToDate } from '../../utils/formatter';
import { LoadingStatus, useTriggerLoad } from '../common/LoadingStatus';
import DisclosureComponent from '../common/DisclosureComponent';
import AddTestToQueueWithConfirmation from '../AddTestToQueueWithConfirmation';
+import { ThemeTable, ThemeTableHeaderH2 } 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 TransparentButton = styled.button`
+ border: none;
+ background-color: transparent;
+`;
const DisclosureContainer = styled.div`
// Following directives are related to the ManageTestQueue component
@@ -76,11 +103,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 {
@@ -88,8 +116,119 @@ const DisclosureContainer = styled.div`
font-size: 1rem;
}
`;
+//section:
+const CustomToggleButton = styled.button`
+ background-color: transparent;
+ width: 100%;
+ height: 38px;
+ text-align: center;
+
+ border: none;
+ margin: 0;
+ padding: 0;
+ display: block;
+
+ .icon-container {
+ background-color: red;
+ /* position: relative; */
+ float: right;
+ margin-top: 2px;
+ margin-right: 3px;
+ }
+ .icon-chevron {
+ font-size: 0.8rem;
+ }
+`;
+
+const CustomToggleP = styled.p`
+ border: 1px solid #ced4da;
+ /* background-image: url('data:image/svg+xml,%3csvg xmlns=%27http://www.w3.org/2000/svg%27 viewBox=%270 0 16 16%27%3e%3cpath fill=%27none%27 stroke=%27%23343a40%27 stroke-linecap=%27round%27 stroke-linejoin=%27round%27 stroke-width=%272%27 d=%27m2 5 6 6 6-6%27/%3e%3c/svg%3e');
+ background-repeat: no-repeat;
+ background-position: right 0.75rem center;
+ background-size: 16px 12px; */
+ border-radius: 0.375rem;
+ background-color: #fff;
+ padding: 2px;
+ width: 100%;
+ height: 38px;
+ cursor: default;
+ display: inline-block;
+`;
+
+const CustomToggleSpan = styled.span`
+ background-image: url('data:image/svg+xml,%3csvg xmlns=%27http://www.w3.org/2000/svg%27 viewBox=%270 0 16 16%27%3e%3cpath fill=%27none%27 stroke=%27%23343a40%27 stroke-linecap=%27round%27 stroke-linejoin=%27round%27 stroke-width=%272%27 d=%27m2 5 6 6 6-6%27/%3e%3c/svg%3e');
+ background-repeat: no-repeat;
+ background-position: right 0.75rem center;
+ background-size: 16px 12px;
+ float: left;
+ margin-top: 2px;
+ /* margin-left: 20px; */
+ white-space: nowrap;
+ background-color: ${props =>
+ props.phaseLabel === 'Phase Selection'
+ ? '#fff'
+ : props.phaseLabel === 'Candidate'
+ ? '#ff6c00'
+ : props.phaseLabel === 'Recommended'
+ ? '#8441de'
+ : 'black'};
+ border-radius: 14px;
+ padding: 2px 32px 2px 14px;
+ text-align: left;
+ width: 100%;
+ font-size: 1rem;
+ font-weight: 400;
+ color: ${props =>
+ props.phaseLabel === 'Phase Selection' ? 'black' : '#fff'};
+`;
+//section:
+// 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();
+ 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 = [],
browsers = [],
testPlanVersions = [],
@@ -103,8 +242,33 @@ const ManageTestQueue = ({
const editAtVersionButtonRef = useRef();
const deleteAtVersionButtonRef = useRef();
+ // Find Manage Required Reports Modal
+ const [showEditAtBrowserModal, setShowEditAtBrowserModal] = useState(true);
+ 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 Assistive Technology'
+ );
+ const [updateAtForButton, setUpdateAtForButton] = useState('');
+ const [updateListAtSelection, setUpdateListAtSelection] = useState(
+ 'Select an Assistive Technology'
+ );
+ const [updateBrowserSelection, setUpdateBrowserSelection] = useState('');
+ const [updateListBrowserSelection, setUpdateListBrowserSelection] =
+ useState('');
+ 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);
const [selectedManageAtId, setSelectedManageAtId] = useState('1');
const [selectedManageAtVersions, setSelectedManageAtVersions] = useState(
[]
@@ -144,9 +308,256 @@ const ManageTestQueue = ({
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_REQUIRED_REPORT_MUTATION);
+ const [updateRequiredReport] = useMutation(UPDATE_REQUIRED_REPORT_MUTATION);
+ const [deleteRequiredReport] = useMutation(DELETE_REQUIRED_REPORT_MUTATION);
+
+ const [atBrowserCombinations, setAtBrowserCombinations] = useState([
+ ...ats.flatMap(at =>
+ at.candidateBrowsers?.map(browser => ({
+ at,
+ browser,
+ phase: 'CANDIDATE'
+ }))
+ ),
+ ...ats.flatMap(at =>
+ at.recommendedBrowsers?.map(browser => ({
+ at,
+ browser,
+ phase: 'RECOMMENDED'
+ }))
+ )
+ ]);
+
+ const setPhase = phase => {
+ setUpdatePhaseSelection(phase);
+ if (phase === 'Candidate' || phase === 'Recommended') {
+ setUpdatePhaseForButton(phase.toUpperCase());
+ }
+ };
+
+ 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);
+ };
+
+ const runMutationForRequiredReportTable = async mutation => {
+ let atId = updateAtForButton;
+ let browserId = updateListBrowserSelection;
+
+ if (mutation === 'createRequiredReport') {
+ await triggerLoad(async () => {
+ try {
+ atBrowserCombinations.forEach(({ at, browser, phase }) => {
+ if (
+ updateAtForButton === at.id &&
+ updateListBrowserSelection === 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 onManageAtsClick = () => setShowManageATs(!showManageATs);
const onAddTestPlansClick = () => setShowAddTestPlans(!showAddTestPlans);
+ const onManageReqReportsClick = () =>
+ setShowManageReqReports(!showManageReqReports);
+
+ const disclosureTitle = enableManageRequiredReports
+ ? [
+ 'Manage Assistive Technology Versions',
+ 'Add Test Plans to the Test Queue'
+ ]
+ : [
+ 'Manage Assistive Technology Versions',
+ 'Add Test Plans to the Test Queue',
+ 'Manage Required Reports'
+ ];
useEffect(() => {
const allTestPlanVersions = testPlanVersions
@@ -298,7 +709,7 @@ const ManageTestQueue = ({
const onThemedModalClose = () => {
setShowThemedModal(false);
- focusButtonRef.current.focus();
+ if (focusButtonRef.current) focusButtonRef.current.focus();
};
const getAtVersionFromId = id => {
@@ -467,14 +878,31 @@ const ManageTestQueue = ({
setShowThemedModal(true);
};
+ 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);
+ setUpdateAtForButton(value);
+ };
+ const handleListBrowserChange = e => {
+ const value = e.target.value;
+ setUpdateListBrowserSelection(value);
+ };
+
return (
@@ -555,7 +983,7 @@ const ManageTestQueue = ({
Select a Test Plan and version and an Assistive
Technology and Browser to add it to the Test Queue
-
+
Test Plan
@@ -675,13 +1103,239 @@ const ManageTestQueue = ({
!selectedBrowserId
}
/>
+ ,
+
+
+ Add required reports for a specific AT and Browser
+ pair
+
+
+
+
+ Phase
+
+
+
+ {updatePhaseSelection}
+
+
+
+
+ setPhase('Candidate')
+ }
+ >
+ Candidate
+
+
+ setPhase('Recommended')
+ }
+ >
+ Recommended
+
+
+
+
+
+
+ Assistive Technology
+
+
+
+ Select an Assistive Technology
+
+ {ats.map(item => {
+ return (
+
+ {item.name}
+
+ );
+ })}
+
+
+
+
+ Browser
+
+
+
+ Select a Browser
+
+ {ats
+ .find(
+ at =>
+ at.id === updateListAtSelection
+ )
+ ?.browsers.map(item => (
+
+ {item.name}
+
+ ))}
+
+
+
+ {
+ setUpdatePhaseSelection(
+ 'Phase Selection'
+ );
+ setUpdatePhaseForButton('');
+ setUpdateListAtSelection(
+ 'Select an Assistive Technology'
+ );
+ setUpdateAtForButton('');
+ setUpdateListBrowserSelection('');
+ runMutationForRequiredReportTable(
+ 'createRequiredReport'
+ );
+ }}
+ >
+ Add Required Reports
+
+
+
+
+ Required Reports
+
+
+
+
+ Phase
+ AT
+ Browser
+ Edit
+
+
+
+ {atBrowserCombinations?.map(
+ ({ at, browser, phase }) => {
+ return (
+
+
+
+ {phase}
+ {' '}
+
+ {at.name}
+ {browser.name}
+
+ {
+ setIsDelete(false);
+ setActionButtonLabel(
+ 'Save Changes'
+ );
+ setUpdateAtIdForUpdate(
+ at.id
+ );
+ setUpdateBrowserIdForUpdate(
+ browser.id
+ );
+ setUpdatePhaseForUpdate(
+ phase
+ );
+ onOpenShowEditAtBrowserModal(
+ 'edit',
+ phase
+ );
+ }}
+ >
+
+
+ Edit
+
+
+ {
+ setIsDelete(true);
+ setActionButtonLabel(
+ 'Confirm Delete'
+ );
+ setUpdateAtIdForUpdate(
+ at.id
+ );
+ setUpdateBrowserIdForUpdate(
+ browser.id
+ );
+ setUpdatePhaseForUpdate(
+ phase
+ );
+ onOpenShowEditAtBrowserModal(
+ 'delete',
+ phase,
+ at.name,
+ browser.name
+ );
+ }}
+ >
+
+
+ Remove
+
+
+
+
+ );
+ }
+ )}
+
+
]}
- onClick={[onManageAtsClick, onAddTestPlansClick]}
- expanded={[showManageATs, showAddTestPlans]}
+ onClick={[
+ onManageAtsClick,
+ onAddTestPlansClick,
+ onManageReqReportsClick
+ ]}
+ expanded={[
+ showManageATs,
+ showAddTestPlans,
+ showManageReqReports
+ ]}
stacked
/>
-
{showAtVersionModal && (
)}
-
{showThemedModal && (
)}
-
{showFeedbackModal && (
)}
+ {!showEditAtBrowserModal && (
+
+ {!isDelete ? (
+
+
+
+ Assistive Technology
+
+ {updateListAtSelection ===
+ 'Select an Assistive Technology' ? (
+
+
+ Select an Assistive
+ Technology
+
+ {ats.map(item => {
+ return (
+
+ {item.name}
+
+ );
+ })}
+
+ ) : (
+
+ {ats.map(item => {
+ return (
+
+ {item.name}
+
+ );
+ })}
+
+ )}
+
+
+ Browser
+
+
+ Select a Browser
+
+ {ats
+ .find(
+ at =>
+ at.id ===
+ updateAtSelection
+ )
+ ?.browsers.map(item => (
+
+ {item.name}
+
+ ))}
+
+
+
+ ) : null}
+
+ }
+ actionLabel={actionButtonLabel}
+ handleAction={() => {
+ if (actionButtonLabel === 'Save Changes') {
+ runMutationForRequiredReportTable(
+ 'updateRequiredReport'
+ );
+ }
+ if (actionButtonLabel === 'Confirm Delete') {
+ runMutationForRequiredReportTable(
+ 'deleteRequiredReport'
+ );
+ }
+ setUpdateAtSelection('Select an Assistive Technology');
+ setUpdateBrowserSelection('');
+ setShowEditAtBrowserModal(true);
+ }}
+ handleClose={() => {
+ setUpdateAtSelection('Select an Assistive Technology');
+ setShowEditAtBrowserModal(true);
+ }}
+ staticBackdrop={true}
+ />
+ )}
);
};
+CustomToggle.propTypes = {
+ children: PropTypes.string,
+ onClick: PropTypes.func
+};
+
+CustomMenu.propTypes = {
+ children: PropTypes.array,
+ className: PropTypes.string
+};
+
ManageTestQueue.propTypes = {
ats: PropTypes.array,
browsers: PropTypes.array,
testPlanVersions: PropTypes.array,
+ enableManageRequiredReports: PropTypes.bool,
triggerUpdate: PropTypes.func
};
diff --git a/client/components/ManageTestQueue/queries.js b/client/components/ManageTestQueue/queries.js
new file mode 100644
index 000000000..441c1594f
--- /dev/null
+++ b/client/components/ManageTestQueue/queries.js
@@ -0,0 +1,88 @@
+import { gql } from '@apollo/client';
+
+export const CREATE_REQUIRED_REPORT_MUTATION = gql`
+ mutation CreateRequiredReport(
+ $atId: ID!
+ $browserId: ID!
+ $phase: RequiredReportPhase!
+ ) {
+ requiredReport(atId: $atId, browserId: $browserId, phase: $phase) {
+ createRequiredReport {
+ atId
+ browserId
+ phase
+ }
+ }
+ }
+`;
+
+export const UPDATE_REQUIRED_REPORT_MUTATION = gql`
+ mutation UpdateRequiredReport(
+ $atId: ID!
+ $browserId: ID!
+ $phase: RequiredReportPhase!
+ $updateAtId: ID!
+ $updateBrowserId: ID!
+ ) {
+ requiredReport(atId: $atId, browserId: $browserId, phase: $phase) {
+ updateRequiredReport(
+ atId: $updateAtId
+ browserId: $updateBrowserId
+ ) {
+ atId
+ browserId
+ phase
+ }
+ }
+ }
+`;
+
+export const DELETE_REQUIRED_REPORT_MUTATION = gql`
+ mutation DeleteRequiredReport(
+ $atId: ID!
+ $browserId: ID!
+ $phase: RequiredReportPhase!
+ ) {
+ requiredReport(atId: $atId, browserId: $browserId, phase: $phase) {
+ deleteRequiredReport {
+ atId
+ browserId
+ phase
+ }
+ }
+ }
+`;
+
+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/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/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 && (
{
+const PhasePill = ({
+ fullWidth = true,
+ forHeader = false,
+ children: phase
+}) => {
+ let className = fullWidth ? 'full-width' : '';
+ className = forHeader ? `${className} for-header` : className;
return (
str)
.join(' ')}
>
@@ -59,6 +68,7 @@ const PhasePill = ({ fullWidth = true, children: phase }) => {
};
PhasePill.propTypes = {
+ forHeader: PropTypes.bool,
fullWidth: PropTypes.bool,
children: PropTypes.string.isRequired
};
diff --git a/client/tests/__mocks__/GraphQLMocks/TestQueuePageAdminPopulatedMock.js b/client/tests/__mocks__/GraphQLMocks/TestQueuePageAdminPopulatedMock.js
index 258b8119a..e5a8c8146 100644
--- a/client/tests/__mocks__/GraphQLMocks/TestQueuePageAdminPopulatedMock.js
+++ b/client/tests/__mocks__/GraphQLMocks/TestQueuePageAdminPopulatedMock.js
@@ -20,6 +20,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'
+ }
]
},
{
@@ -51,6 +81,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'
+ }
]
},
{
@@ -62,6 +122,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'
+ }
]
}
],
diff --git a/client/tests/__mocks__/GraphQLMocks/TestQueuePageTesterPopulatedMock.js b/client/tests/__mocks__/GraphQLMocks/TestQueuePageTesterPopulatedMock.js
index 3c1003e8c..b00937986 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'
+ }
]
}
],
diff --git a/server/graphql-schema.js b/server/graphql-schema.js
index 169d8f4d6..65e70d4f9 100644
--- a/server/graphql-schema.js
+++ b/server/graphql-schema.js
@@ -171,6 +171,15 @@ 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.
"""
@@ -1084,6 +1093,17 @@ 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!
+ }
+
"""
Mutations scoped to an existing AtVersion.
"""
@@ -1270,6 +1290,12 @@ const graphqlSchema = gql`
Get the available mutations for the given browser.
"""
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.
diff --git a/server/models/services/AtBrowserService b/server/models/services/AtBrowserService
new file mode 100644
index 000000000..07a292dae
--- /dev/null
+++ b/server/models/services/AtBrowserService
@@ -0,0 +1,27 @@
+const ModelService = require('./ModelService.js');
+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..0039cf017
--- /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 { atId, browserId, phase };
+};
+
+module.exports = createRequiredReportResolver;
diff --git a/server/resolvers/RequiredReportOperations/deleteRequiredReportResolver.js b/server/resolvers/RequiredReportOperations/deleteRequiredReportResolver.js
new file mode 100644
index 000000000..ae9f15fe9
--- /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 { atId, browserId, phase };
+};
+
+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..7f29cb18f
--- /dev/null
+++ b/server/resolvers/RequiredReportOperations/updateRequiredReportResolver.js
@@ -0,0 +1,41 @@
+const { AuthenticationError } = require('apollo-server');
+const { updateAtBrowser } = require('../../models/services/AtBrowserService');
+
+const updateRequiredReportResolver = async (
+ { parentContext: { atId, browserId, phase } },
+ { atId: inputAtId, browserId: inputBrowserId },
+ { user }
+) => {
+ if (!user?.roles.find(role => role.name === 'ADMIN')) {
+ throw new AuthenticationError();
+ }
+
+ let updateParams = {};
+
+ // These conditionals will change values in the At/Browsers table
+ // in the database. Each updateAtBrowser() call changes the boolean value
+ // for a particular row in the database. The booleans for two row need to be
+ // changed. So we call updateAtBrowser() twice.
+ 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 { atId: inputAtId, browserId: inputBrowserId, phase };
+};
+
+module.exports = updateRequiredReportResolver;
diff --git a/server/resolvers/index.js b/server/resolvers/index.js
index a8fad8916..00704ad3e 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 TestPlan = require('./TestPlan');
const TestPlanVersion = require('./TestPlanVersion');
const TestPlanReport = require('./TestPlanReport');
@@ -54,6 +56,7 @@ const resolvers = {
at: mutateAt,
atVersion: mutateAtVersion,
browser: mutateBrowser,
+ requiredReport: mutateRequiredReport,
testPlanReport: mutateTestPlanReport,
testPlanRun: mutateTestPlanRun,
testResult: mutateTestResult,
@@ -65,6 +68,7 @@ const resolvers = {
AtOperations,
AtVersionOperations,
BrowserOperations,
+ RequiredReportOperations,
User,
TestPlan,
TestPlanVersion,
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;
diff --git a/server/tests/integration/graphql.test.js b/server/tests/integration/graphql.test.js
index 3a7a3f8db..6db37911d 100644
--- a/server/tests/integration/graphql.test.js
+++ b/server/tests/integration/graphql.test.js
@@ -514,7 +514,6 @@ describe('graphql', () => {
}
`
);
- // console.info(queryResult);
await dbCleaner(async () => {
const {
@@ -699,6 +698,31 @@ describe('graphql', () => {
) {
username
}
+ requiredReport(
+ atId: 1
+ browserId: 1
+ phase: IS_CANDIDATE
+ ) {
+ __typename
+ createRequiredReport {
+ __typename
+ atId
+ browserId
+ phase
+ }
+ updateRequiredReport(atId: 1, browserId: 1) {
+ __typename
+ atId
+ browserId
+ phase
+ }
+ deleteRequiredReport {
+ __typename
+ atId
+ browserId
+ phase
+ }
+ }
}
`,
{