Skip to content

Commit

Permalink
Merge branch 'main' into js-6-frontend-validations
Browse files Browse the repository at this point in the history
  • Loading branch information
jasalisbury authored Feb 22, 2021
2 parents 69af857 + 15ee16d commit 3dfd8a8
Show file tree
Hide file tree
Showing 15 changed files with 166 additions and 65 deletions.
4 changes: 2 additions & 2 deletions cucumber/features/steps/activityReportSteps.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,12 @@ Given('I am on the landing page', async () => {
page.waitForNavigation(),
page.click(selector),
]);

await scope.context.currentPage.waitForSelector('h1');

const selectorNewReport = 'a[href$="activity-reports/new"]';
await Promise.all([
page.waitForNavigation(),
page.click(selectorNewReport),
await page.goto(`${scope.uri}/activity-reports/new`),
]);
await scope.context.currentPage.waitForSelector('h1');
});
Expand Down
2 changes: 1 addition & 1 deletion docker-compose.test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ services:
build:
context: .
container_name: test-frontend
command: yarn start
command: bash
user: ${CURRENT_USER:-root}
stdin_open: true
volumes:
Expand Down
7 changes: 5 additions & 2 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@
"react-router-dom": "^5.2.0",
"react-router-hash-link": "^2.3.1",
"react-router-prop-types": "^1.0.5",
"react-scripts": "^3.4.4",
"react-select": "^3.1.0",
"react-stickynode": "^3.0.4",
"react-with-direction": "^1.3.1",
Expand Down Expand Up @@ -136,12 +135,16 @@
"jest-junit": "^11.1.0",
"mutationobserver-shim": "^0.3.7",
"node-fetch": "^2.6.1",
"react-scripts": "^3.4.4",
"react-select-event": "^5.1.0"
},
"jest": {
"coverageThreshold": {
"global": {
"branches": 90
"statements": 90,
"functions": 85,
"branches": 90,
"lines": 90
}
}
}
Expand Down
7 changes: 4 additions & 3 deletions frontend/run-yarn-audit.sh
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
set -u

set +e
output=$(yarn audit --level low --json)
cmd="yarn audit --level low --json"
output=$($cmd)
result=$?
set -e

Expand All @@ -30,10 +31,10 @@ echo fixes and they do not apply to production, you may ignore them
echo
echo To ignore these vulnerabilities, run:
echo
echo "yarn audit --json | grep auditAdvisory > yarn-audit-known-issues"
echo "$cmd | grep auditAdvisory > yarn-audit-known-issues"
echo
echo and commit the yarn-audit-known-issues file
echo
echo "$output" | grep auditAdvisory | python -mjson.tool

exit "$result"
exit "$result"
28 changes: 27 additions & 1 deletion frontend/src/__tests__/permissions.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import isAdmin from '../permissions';
import isAdmin, { hasReadWrite } from '../permissions';

describe('permissions', () => {
describe('isAdmin', () => {
Expand All @@ -20,4 +20,30 @@ describe('permissions', () => {
expect(isAdmin(user)).toBeFalsy();
});
});

describe('hasReadWrite', () => {
it('returns true if the user has read/write to a region', () => {
const user = {
permissions: [
{
scopeId: 3,
regionId: 1,
},
],
};
expect(hasReadWrite(user)).toBeTruthy();
});

it('returns false if the user does not have read/write to a region', () => {
const user = {
permissions: [
{
scopeId: 2,
regionId: 1,
},
],
};
expect(hasReadWrite(user)).toBeFalsy();
});
});
});
2 changes: 1 addition & 1 deletion frontend/src/pages/ActivityReport/__tests__/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ describe('ActivityReport', () => {
const data = formData();
fetchMock.get('/api/activity-reports/1', data);
renderActivityReport('1', 'activity-summary', true);
await screen.findByRole('group', { name: 'Who was the activity for?' });
await screen.findByRole('group', { name: 'Who was the activity for?' }, { timeout: 4000 });
expect(await screen.findByTestId('alert')).toBeVisible();
});

Expand Down
9 changes: 5 additions & 4 deletions frontend/src/pages/Landing/MyAlerts.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ function renderReports(reports) {

const collaboratorsWithTags = collaborators.map((collaborator) => (
<Tag
key={collaborator.id}
key={collaborator.fullName.slice(1, 3) + collaborator.id}
className="smart-hub--table-collection"
>
{collaborator.fullName}
Expand Down Expand Up @@ -89,15 +89,15 @@ function renderReports(reports) {
});
}

function MyAlerts({ reports }) {
function MyAlerts({ reports, newBtn }) {
return (
<>
{ reports && reports.length === 0 && (
<Container className="landing" padding={0}>
<div id="caughtUp">
<div><h2>You&apos;re all caught up!</h2></div>
<p id="beginNew">Would you like to begin a new activity report?</p>
<NewReport />
{ newBtn && <p id="beginNew">Would you like to begin a new activity report?</p> }
{ newBtn && <NewReport /> }
</div>
</Container>
) }
Expand Down Expand Up @@ -141,6 +141,7 @@ function MyAlerts({ reports }) {

MyAlerts.propTypes = {
reports: PropTypes.arrayOf(PropTypes.object),
newBtn: PropTypes.bool.isRequired,
};

MyAlerts.defaultProps = {
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/pages/Landing/NewReport.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import './index.css';
function NewReport() {
return (
<Link
to="/activity-reports/new"
to="/activity-reports/new/activity-summary"
referrerPolicy="same-origin"
className="usa-button smart-hub--new-report-btn"
variant="unstyled"
Expand Down
3 changes: 2 additions & 1 deletion frontend/src/pages/Landing/__tests__/MyAlerts.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,10 @@ import activityReports from '../mocks';

describe('My Alerts', () => {
beforeEach(() => {
const newBtn = true;
render(
<MemoryRouter>
<MyAlerts reports={activityReports} />
<MyAlerts reports={activityReports} newBtn={newBtn} />
</MemoryRouter>,
);
});
Expand Down
62 changes: 53 additions & 9 deletions frontend/src/pages/Landing/__tests__/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,21 +10,31 @@ import UserContext from '../../../UserContext';
import Landing from '../index';
import activityReports from '../mocks';

const renderLanding = (user) => {
render(
<MemoryRouter>
<UserContext.Provider value={{ user }}>
<Landing authenticated />
</UserContext.Provider>
</MemoryRouter>,
);
};

describe('Landing Page', () => {
beforeEach(() => {
fetchMock.get('/api/activity-reports', activityReports);
fetchMock.get('/api/activity-reports/alerts', []);
const user = {
name: '[email protected]',
permissions: [
{
scopeId: 3,
regionId: 1,
},
],
};

render(
<MemoryRouter>
<UserContext.Provider value={{ user }}>
<Landing authenticated />
</UserContext.Provider>
</MemoryRouter>,
);
renderLanding(user);
});
afterEach(() => fetchMock.restore());

Expand Down Expand Up @@ -156,18 +166,52 @@ describe('Landing Page error', () => {

it('handles errors by displaying an error message', async () => {
fetchMock.get('/api/activity-reports', 500);
render(<MemoryRouter><Landing authenticated /></MemoryRouter>);
const user = {
name: '[email protected]',
permissions: [
{
scopeId: 3,
regionId: 1,
},
],
};
renderLanding(user);
const alert = await screen.findByRole('alert');
expect(alert).toBeVisible();
expect(alert).toHaveTextContent('Unable to fetch reports');
});

it('displays an empty row if there are no reports', async () => {
fetchMock.get('/api/activity-reports', []);
render(<MemoryRouter><Landing authenticated /></MemoryRouter>);
const user = {
name: '[email protected]',
permissions: [
{
scopeId: 3,
regionId: 1,
},
],
};
renderLanding(user);
const rowCells = await screen.findAllByRole('cell');
expect(rowCells.length).toBe(8);
const grantee = rowCells[0];
expect(grantee).toHaveTextContent('');
});

it('does not displays new activity report button without permission', async () => {
fetchMock.get('/api/activity-reports', activityReports);
const user = {
name: '[email protected]',
permissions: [
{
scopeId: 2,
regionId: 1,
},
],
};
renderLanding(user);

await expect(screen.findAllByText(/New Activity Report/)).rejects.toThrow();
});
});
80 changes: 44 additions & 36 deletions frontend/src/pages/Landing/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,15 @@ import { Link } from 'react-router-dom';
import SimpleBar from 'simplebar-react';
import 'simplebar/dist/simplebar.min.css';

import UserContext from '../../UserContext';
import Container from '../../components/Container';
import { getReports, getReportAlerts } from '../../fetchers/activityReports';
import NewReport from './NewReport';
import 'uswds/dist/css/uswds.css';
import '@trussworks/react-uswds/lib/index.css';
import './index.css';
import MyAlerts from './MyAlerts';
import { hasReadWrite } from '../../permissions';

function renderReports(reports) {
const emptyReport = {
Expand Down Expand Up @@ -167,43 +169,49 @@ function Landing() {
<Helmet>
<title>Landing</title>
</Helmet>
<Grid row gap>
<Grid>
<h1 className="landing">Activity Reports</h1>
</Grid>
<Grid className="smart-hub--create-new-report">
{ reportAlerts && reportAlerts.length > 0 && <NewReport /> }
</Grid>
</Grid>
<Grid row>
{error && (
<Alert type="error" role="alert">
{error}
</Alert>
<UserContext.Consumer>
{({ user }) => (
<>
<Grid row gap>
<Grid>
<h1 className="landing">Activity Reports</h1>
</Grid>
<Grid className="smart-hub--create-new-report">
{reportAlerts && reportAlerts.length > 0 && hasReadWrite(user) && <NewReport />}
</Grid>
</Grid>
<Grid row>
{error && (
<Alert type="error" role="alert">
{error}
</Alert>
)}
</Grid>
<MyAlerts reports={reportAlerts} newBtn={hasReadWrite(user)} />
<SimpleBar>
<Container className="landing inline-size" padding={0}>
<Table className="usa-table usa-table--borderless usa-table--striped">
<caption>Activity reports</caption>
<thead>
<tr>
<th scope="col">Report ID</th>
<th scope="col">Grantee</th>
<th scope="col">Start date</th>
<th scope="col">Creator</th>
<th scope="col">Topic(s)</th>
<th scope="col">Collaborator(s)</th>
<th scope="col">Last saved</th>
<th scope="col">Status</th>
<th scope="col" aria-label="..." />
</tr>
</thead>
<tbody>{renderReports(reports)}</tbody>
</Table>
</Container>
</SimpleBar>
</>
)}
</Grid>
<MyAlerts reports={reportAlerts} />
<SimpleBar>
<Container className="landing inline-size" padding={0}>
<Table className="usa-table usa-table--borderless usa-table--striped">
<caption>Activity reports</caption>
<thead>
<tr>
<th scope="col">Report ID</th>
<th scope="col">Grantee</th>
<th scope="col">Start date</th>
<th scope="col">Creator</th>
<th scope="col">Topic(s)</th>
<th scope="col">Collaborator(s)</th>
<th scope="col">Last saved</th>
<th scope="col">Status</th>
<th scope="col" aria-label="..." />
</tr>
</thead>
<tbody>{renderReports(reports)}</tbody>
</Table>
</Container>
</SimpleBar>
</UserContext.Consumer>
</>
);
}
Expand Down
12 changes: 12 additions & 0 deletions frontend/src/permissions.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,16 @@ const isAdmin = (user) => {
) !== undefined;
};

/**
* Search the user's permissions for a read/write permisions for a region
* @param {*} user - user object
* @returns {boolean} - True if the user has re/write access for a region, false otherwise
*/
export const hasReadWrite = (user) => {
const { permissions } = user;
return permissions && permissions.find(
(p) => p.scopeId === SCOPE_IDS.READ_WRITE_ACTIVITY_REPORTS,
) !== undefined;
};

export default isAdmin;
1 change: 1 addition & 0 deletions frontend/yarn-audit-known-issues
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"type":"auditAdvisory","data":{"resolution":{"id":1603,"path":"react-scripts>react-dev-utils>immer","dev":false,"optional":false,"bundled":false},"advisory":{"findings":[{"version":"1.10.0","paths":["react-scripts>react-dev-utils>immer"]}],"id":1603,"created":"2021-02-19T18:18:20.058Z","updated":"2021-02-19T18:18:32.751Z","deleted":null,"title":"Prototype Pollution","found_by":{"link":"","name":"Anonymous","email":""},"reported_by":{"link":"","name":"Anonymous","email":""},"module_name":"immer","cves":["CVE-2020-28477"],"vulnerable_versions":"<8.0.1","patched_versions":">=8.0.1","overview":"## Overview\n\nAffected versions of `immer` are vulnerable to Prototype Pollution.\n\n## Proof of exploit\n\n```\nconst {applyPatches, enablePatches} = require(\"immer\");\nenablePatches();\nlet obj = {};\nconsole.log(\"Before : \" + obj.polluted);\napplyPatches({}, [ { op: 'add', path: [ \"__proto__\", \"polluted\" ], value: \"yes\" } ]);\n// applyPatches({}, [ { op: 'replace', path: [ \"__proto__\", \"polluted\" ], value: \"yes\" } ]);\nconsole.log(\"After : \" + obj.polluted);\n```\n\n## Remediation\n\nVersion 8.0.1 contains a [fix](https://github.com/immerjs/immer/commit/da2bd4fa0edc9335543089fe7d290d6a346c40c5) for this vulnerability, updating is recommended.","recommendation":"Upgrade to version 8.0.1 or later","references":"- [GitHub Advisory](https://github.com/advisories/GHSA-9qmh-276g-x5pj)\n","access":"public","severity":"high","cwe":"CWE-1321","metadata":{"module_type":"","exploitability":8,"affected_components":""},"url":"https://npmjs.com/advisories/1603"}}}
Loading

0 comments on commit 3dfd8a8

Please sign in to comment.