Skip to content

Commit

Permalink
Merge pull request #181 from adhocteam/js-285-prevent-updating-of-rep…
Browse files Browse the repository at this point in the history
…orts

Prevent updating of reports
  • Loading branch information
jasalisbury authored Mar 1, 2021
2 parents ad27298 + 8096d10 commit 5e1e303
Show file tree
Hide file tree
Showing 23 changed files with 506 additions and 162 deletions.
21 changes: 21 additions & 0 deletions docs/openapi/paths/activity-reports/reset.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
put:
tags:
- activity-reports
summary: Reset activity report to draft
description: >
Activity reports are not editable when submitted for approval. This
endpoint allows a user to reset a report back to draft mode so that
the report can be edited.
parameters:
- in: path
name: activityReportId
required: true
schema:
type: number
responses:
200:
description: The report that now has a status of "draft"
content:
application/json:
schema:
$ref: '../../index.yaml#/components/schemas/activityReport'
2 changes: 2 additions & 0 deletions docs/openapi/paths/index.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -26,5 +26,7 @@
$ref: './activity-reports/submit.yaml'
'/activity-reports/{activityReportId}/review':
$ref: './activity-reports/review.yaml'
'/activity-reports/{activityReportId}/reset':
$ref: './activity-reports/reset.yaml'
'/files':
$ref: './files.yaml'
1 change: 1 addition & 0 deletions frontend/src/components/Navigator/__tests__/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ describe('Navigator', () => {
const renderNavigator = (currentPage = 'first', onSubmit = () => {}, onSave = () => {}, updatePage = () => {}, updateForm = () => {}) => {
render(
<Navigator
editable
reportId={1}
submitted={false}
formData={initialData}
Expand Down
15 changes: 14 additions & 1 deletion frontend/src/components/Navigator/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,14 @@ import SideNav from './components/SideNav';
import NavigatorHeader from './components/NavigatorHeader';

function Navigator({
editable,
formData,
updateFormData,
initialLastUpdated,
pages,
onFormSubmit,
onReview,
onResetToDraft,
currentPage,
additionalData,
onSave,
Expand Down Expand Up @@ -77,6 +79,9 @@ function Navigator({
};

const onSaveForm = async (completed) => {
if (!editable) {
return;
}
const { status, ...values } = getValues();
const data = { ...formData, ...values, pageState: newNavigatorState(completed) };
try {
Expand Down Expand Up @@ -117,7 +122,12 @@ function Navigator({

const navigatorPages = pages.map((p) => {
const current = p.position === page.position;
const stateOfPage = current ? IN_PROGRESS : pageState[p.position];

let stateOfPage = pageState[p.position];
if (stateOfPage !== COMPLETE) {
stateOfPage = current ? IN_PROGRESS : pageState[p.position];
}

const state = p.review ? formData.status : stateOfPage;
return {
label: p.label,
Expand Down Expand Up @@ -149,6 +159,7 @@ function Navigator({
additionalData,
onReview,
approvingManager,
onResetToDraft,
onSaveForm,
navigatorPages,
reportCreator,
Expand Down Expand Up @@ -186,6 +197,8 @@ function Navigator({
}

Navigator.propTypes = {
onResetToDraft: PropTypes.func.isRequired,
editable: PropTypes.bool.isRequired,
formData: PropTypes.shape({
status: PropTypes.string,
pageState: PropTypes.shape({}),
Expand Down
6 changes: 6 additions & 0 deletions frontend/src/fetchers/activityReports.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,3 +64,9 @@ export const reviewReport = async (reportId, data) => {
const report = await put(url, data);
return report.json();
};

export const resetToDraft = async (reportId) => {
const url = join(activityReportUrl, reportId.toString(DECIMAL_BASE), 'reset');
const response = await put(url);
return response.json();
};
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { REPORT_STATUSES } from '../../../../../../Constants';

const RenderApprover = ({
// eslint-disable-next-line react/prop-types
onFormReview, reviewed, valid, formData,
onFormReview, reviewed, formData,
}) => {
const hookForm = useForm({
mode: 'onChange',
Expand All @@ -21,16 +21,16 @@ const RenderApprover = ({
<Approver
onFormReview={onFormReview}
reviewed={reviewed}
valid={valid}
formData={formData}
/>
</FormProvider>
);
};

const renderReview = (status, onFormReview, reviewed, valid, notes = '') => {
const renderReview = (status, onFormReview, reviewed, notes = '') => {
const formData = {
approvingManager: { name: 'name' },
author: { name: 'user' },
managerNotes: notes,
additionalNotes: notes,
approvingManagerId: '1',
Expand All @@ -41,7 +41,6 @@ const renderReview = (status, onFormReview, reviewed, valid, notes = '') => {
status={status}
onFormReview={onFormReview}
reviewed={reviewed}
valid={valid}
formData={formData}
/>,
);
Expand All @@ -50,32 +49,33 @@ const renderReview = (status, onFormReview, reviewed, valid, notes = '') => {
describe('Approver review page', () => {
describe('when the report is submitted', () => {
it('displays the submit review component', async () => {
renderReview(REPORT_STATUSES.SUBMITTED, () => {}, false, true);
renderReview(REPORT_STATUSES.SUBMITTED, () => {}, false);
expect(await screen.findByText('Review and approve report')).toBeVisible();
});

it('allows the approver to submit a review', async () => {
const mockSubmit = jest.fn();
renderReview(REPORT_STATUSES.SUBMITTED, mockSubmit, true, true);
renderReview(REPORT_STATUSES.SUBMITTED, mockSubmit, true);
const dropdown = await screen.findByTestId('dropdown');
userEvent.selectOptions(dropdown, 'approved');
const button = await screen.findByRole('button');
userEvent.click(button);
const alert = await screen.findByTestId('alert');
expect(alert.textContent).toContain('Success');
const alerts = await screen.findAllByTestId('alert');
const success = alerts.find((alert) => alert.textContent.includes('Success'));
expect(success).toBeVisible();
expect(mockSubmit).toHaveBeenCalled();
});

it('handles empty notes', async () => {
renderReview(REPORT_STATUSES.SUBMITTED, () => {}, true, true);
renderReview(REPORT_STATUSES.SUBMITTED, () => {}, true);
const notes = await screen.findByLabelText('additionalNotes');
expect(notes.textContent).toContain('No creator notes');
});
});

describe('when the report is approved', () => {
it('displays the approved component', async () => {
renderReview(REPORT_STATUSES.APPROVED, () => {}, false, true);
renderReview(REPORT_STATUSES.APPROVED, () => {}, false);
expect(await screen.findByText('Report approved')).toBeVisible();
});
});
Expand Down
76 changes: 61 additions & 15 deletions frontend/src/pages/ActivityReport/Pages/Review/Approver/index.js
Original file line number Diff line number Diff line change
@@ -1,51 +1,97 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Alert } from '@trussworks/react-uswds';

import Review from './Review';
import Approved from './Approved';
import { REPORT_STATUSES } from '../../../../../Constants';
import Container from '../../../../../components/Container';

const Approver = ({
onFormReview,
reviewed,
formData,
children,
error,
}) => {
const { managerNotes, additionalNotes, status } = formData;
const review = status === REPORT_STATUSES.SUBMITTED || status === REPORT_STATUSES.NEEDS_ACTION;
const approved = status === REPORT_STATUSES.APPROVED;
const { author } = formData;

return (
<>
{review
&& (
<Review
reviewed={reviewed}
additionalNotes={additionalNotes}
onFormReview={onFormReview}
/>
const renderTopAlert = () => (
<Alert type="info" noIcon slim className="margin-bottom-1 no-print">
{review && (
<>
<span className="text-bold">
{ author.name }
{' '}
has requested approval for this activity report.
</span>
<br />
Please review all information in each section before submitting for approval.
</>
)}
{approved
&& (
<Approved
additionalNotes={additionalNotes}
managerNotes={managerNotes}
/>
{approved && (
<>
This report has been approved and is no longer editable
</>
)}
</Alert>
);

return (
<>
{renderTopAlert()}
{children}
<Container skipTopPadding className="margin-top-2 padding-top-2">
{error && (
<Alert noIcon className="margin-y-4" type="error">
<b>Error</b>
<br />
{error}
</Alert>
)}
{review
&& (
<Review
reviewed={reviewed}
additionalNotes={additionalNotes}
onFormReview={onFormReview}
/>
)}
{approved
&& (
<Approved
additionalNotes={additionalNotes}
managerNotes={managerNotes}
/>
)}
</Container>
</>
);
};

Approver.propTypes = {
onFormReview: PropTypes.func.isRequired,
reviewed: PropTypes.bool.isRequired,
children: PropTypes.node.isRequired,
error: PropTypes.string,
formData: PropTypes.shape({
approvingManager: PropTypes.shape({
name: PropTypes.string,
}),
managerNotes: PropTypes.string,
additionalNotes: PropTypes.string,
status: PropTypes.string,
author: PropTypes.shape({
name: PropTypes.string,
}),
}).isRequired,
};

Approver.defaultProps = {
error: '',
};

export default Approver;
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,14 @@ import React from 'react';
import PropTypes from 'prop-types';
import { useFormContext } from 'react-hook-form';
import {
Dropdown, Form, Fieldset, Textarea, Alert, Button,
Dropdown, Form, Fieldset, Textarea, Button,
} from '@trussworks/react-uswds';

import IncompletePages from './IncompletePages';
import { DECIMAL_BASE } from '../../../../../Constants';
import FormItem from '../../../../../components/FormItem';

const Draft = ({
submitted,
approvers,
onFormSubmit,
onSaveForm,
Expand All @@ -36,14 +35,6 @@ const Draft = ({

return (
<>
{submitted
&& (
<Alert noIcon className="margin-y-4" type="success">
<b>Success</b>
<br />
This report was successfully submitted for approval
</Alert>
)}
<h2>Submit Report</h2>
<Form className="smart-hub--form-large" onSubmit={handleSubmit(onSubmit)}>
<Fieldset className="smart-hub--report-legend smart-hub--form-section" legend="Additional Notes">
Expand Down Expand Up @@ -82,7 +73,6 @@ const Draft = ({
};

Draft.propTypes = {
submitted: PropTypes.bool.isRequired,
onSaveForm: PropTypes.func.isRequired,
approvers: PropTypes.arrayOf(PropTypes.shape({
id: PropTypes.number,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Alert, Button } from '@trussworks/react-uswds';
import { Button } from '@trussworks/react-uswds';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faInfoCircle } from '@fortawesome/free-solid-svg-icons';

Expand All @@ -23,15 +23,6 @@ const NeedsAction = ({

return (
<>
<Alert noIcon className="margin-y-4" type="error">
<b>
{ approvingManager.name }
{' '}
has requested updates to this activity report
</b>
<br />
Please review the manager notes below and re-submit for approval.
</Alert>
<h2>Review and re-submit report</h2>
<div className="smart-hub--creator-notes">
<p>
Expand All @@ -46,7 +37,7 @@ const NeedsAction = ({
<span className="text-bold">Manager notes</span>
<br />
<br />
{ managerNotes }
{ managerNotes || 'No manager notes' }
</p>
</div>
<div>
Expand All @@ -59,7 +50,7 @@ const NeedsAction = ({
{' '}
from
{' '}
{ approvingManager.name || 'No manager notes' }
{ approvingManager.name }
</div>
</div>
{hasIncompletePages && <IncompletePages incompletePages={incompletePages} />}
Expand Down
Loading

0 comments on commit 5e1e303

Please sign in to comment.