Skip to content

Commit

Permalink
feat: Include Settings page for admin and UI to manually import lates…
Browse files Browse the repository at this point in the history
…t test plan versions (#1290)

* Allows navigation to the `/settings` page for the admin
* Admin Settings page now includes a "Import Latest Test Plan Versions" button.
* Admin Settings page will also display the date of the latest imported test plan version
  • Loading branch information
howard-e authored Jan 28, 2025
1 parent 67b7148 commit 7e22455
Show file tree
Hide file tree
Showing 30 changed files with 535 additions and 221 deletions.
4 changes: 2 additions & 2 deletions client/components/App/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ const App = () => {
const [isNavbarExpanded, setIsNavbarExpanded] = useState(false);

const auth = evaluateAuth(data && data.me ? data.me : {});
const { username, isSignedIn, isAdmin, isVendor } = auth;
const { username, isSignedIn, isAdmin, isVendor, isTester } = auth;

const signOut = async () => {
await fetch('/api/auth/signout', { method: 'POST' });
Expand Down Expand Up @@ -104,7 +104,7 @@ const App = () => {
)}
{isSignedIn && (
<>
{!isVendor && (
{(isAdmin || isTester) && (
<li>
<Nav.Link
as={Link}
Expand Down
82 changes: 82 additions & 0 deletions client/components/UserSettings/AdminSettings.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Button } from 'react-bootstrap';
import { LoadingStatus, useTriggerLoad } from '../common/LoadingStatus';
import { useThemedModal, THEMES } from '../../hooks/useThemedModal';
import { dates } from 'shared';

const AdminSettings = ({ latestTestPlanVersion, refetch }) => {
const { triggerLoad, loadingMessage, loadingNote } = useTriggerLoad();
const {
themedModal,
showThemedModal,
setShowThemedModal,
setThemedModalTitle,
setThemedModalContent,
setThemedModalType
} = useThemedModal({
type: THEMES.SUCCESS,
title: 'Success'
});

const handleImportTests = async () => {
await triggerLoad(
async () => {
try {
const response = await fetch('/api/test/import', { method: 'POST' });
if (!response.ok) {
throw new Error(
`Failed to import the latest Test Plan Versions: ${response.status}`
);
}

// Success
setThemedModalType(THEMES.SUCCESS);
setThemedModalTitle('Success');
setThemedModalContent(
<>The latest Test Plan Versions have been imported.</>
);
setShowThemedModal(true);
await refetch();
} catch (e) {
// Failed, show themed message
setThemedModalType(THEMES.DANGER);
setThemedModalTitle('Error');
setThemedModalContent(<>{e.message}</>);
setShowThemedModal(true);
}
},
'Importing latest Test Plan Versions',
'This may take a few minutes ...'
);
};

return (
<LoadingStatus message={loadingMessage} note={loadingNote}>
<section>
<h2>Admin Actions</h2>
<Button variant="primary" onClick={handleImportTests}>
Import Latest Test Plan Versions
</Button>
<p>
Date of latest test plan version:{' '}
{dates.convertDateToString(
latestTestPlanVersion?.updatedAt,
'MMMM D, YYYY HH:mm z'
)}
</p>
</section>
{showThemedModal && themedModal}
</LoadingStatus>
);
};

AdminSettings.propTypes = {
latestTestPlanVersion: PropTypes.shape({
id: PropTypes.string.isRequired,
updatedAt: PropTypes.string.isRequired
}),
refetch: PropTypes.func
};

export default AdminSettings;
106 changes: 106 additions & 0 deletions client/components/UserSettings/TesterSettings.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import React, { useCallback, useEffect, useState } from 'react';
import PropTypes from 'prop-types';
import { Button, Form } from 'react-bootstrap';
import { useMutation } from '@apollo/client';
import { TESTER_SETTINGS_QUERY, UPDATE_ME_MUTATION } from './queries';
import { AtPropType } from '../common/proptypes';

const TesterSettings = ({ ats, meAts }) => {
const [updateMe] = useMutation(UPDATE_ME_MUTATION, {
refetchQueries: [{ query: TESTER_SETTINGS_QUERY }]
});

const [checkedAts, setCheckedAts] = useState([]);

if (!ats || !meAts || !checkedAts) return null;

useEffect(() => {
if (!meAts) return;
setCheckedAts(meAts.map(at => at.id));
}, [meAts]);

const handleCheckedAt = useCallback(
event => {
const atId = event.target.id;
const isChecked = checkedAts.includes(atId);
if (isChecked) {
setCheckedAts(checkedAts.filter(item => item !== atId));
} else {
setCheckedAts([...checkedAts, atId]);
}
},
[checkedAts]
);

const handleSave = useCallback(
event => {
event.preventDefault();
updateMe({ variables: { input: { atIds: checkedAts } } });
},
[checkedAts]
);

const savedAts = meAts.map(at => at.id);

return (
<section>
<h2>Assistive Technology Settings</h2>
<div aria-atomic="true" aria-live="polite">
{savedAts.length > 0 ? (
<div>
<p data-testid="testable-ats-status">
You can currently test the following assistive technologies:
</p>
<ul>
{ats
.filter(({ id: atId }) => savedAts.includes(atId))
.map(at => (
<li style={{ listStyle: 'disc' }} key={at.id}>
{at.name}
</li>
))}
</ul>
</div>
) : (
<p data-testid="testable-ats-status">
You have not yet selected any assistive technologies.
</p>
)}
</div>
<p>
Update the assistive technologies you can test by selecting from the
options below:
</p>
<Form>
<h3 id="at-group-label">Assistive Technologies</h3>
<Form.Group
controlId="formBasicCheckbox"
role="group"
aria-labelledby="at-group-label"
>
{ats?.map(at => {
return (
<Form.Check
id={at.id}
key={at.id}
label={at.name}
onChange={handleCheckedAt}
checked={!!checkedAts.find(atId => atId === at.id)}
/>
);
})}
</Form.Group>
<Button variant="primary" type="submit" onClick={handleSave}>
Save
</Button>
</Form>
</section>
);
};

TesterSettings.propTypes = {
ats: PropTypes.arrayOf(AtPropType),
meAts: PropTypes.arrayOf(AtPropType)
};

export default TesterSettings;
143 changes: 0 additions & 143 deletions client/components/UserSettings/UserSettings.jsx

This file was deleted.

Loading

0 comments on commit 7e22455

Please sign in to comment.