Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

BHBC-914: Drafts now save WIP steps correctly. #230

Merged
merged 4 commits into from
Apr 14, 2021
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
119 changes: 95 additions & 24 deletions app/src/features/projects/CreateProjectPage.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ describe('CreateProjectPage', () => {

it('renders the initial default page correctly', async () => {
mockBiohubApi().codes.getAllCodeSets.mockResolvedValue({
code_set: []
coordinator_agency: [{ id: 1, name: 'A Rocha Canada' }]
});

const { getByText, asFragment } = renderContainer();
Expand Down Expand Up @@ -165,7 +165,7 @@ describe('CreateProjectPage', () => {

it('shows the page title', async () => {
mockBiohubApi().codes.getAllCodeSets.mockResolvedValue({
code_set: []
coordinator_agency: [{ id: 1, name: 'A Rocha Canada' }]
});
const { findByText } = renderContainer();
const PageTitle = await findByText('Create Project');
Expand All @@ -176,7 +176,7 @@ describe('CreateProjectPage', () => {
describe('Are you sure? Dialog', () => {
it('shows warning dialog if the user clicks the `Cancel and Exit` button', async () => {
mockBiohubApi().codes.getAllCodeSets.mockResolvedValue({
code_set: []
coordinator_agency: [{ id: 1, name: 'A Rocha Canada' }]
});
history.push('/home');
history.push('/projects/create');
Expand All @@ -195,7 +195,7 @@ describe('CreateProjectPage', () => {

it('it calls history.push() if the user clicks `Yes`', async () => {
mockBiohubApi().codes.getAllCodeSets.mockResolvedValue({
code_set: []
coordinator_agency: [{ id: 1, name: 'A Rocha Canada' }]
});
history.push('/home');
history.push('/projects/create');
Expand All @@ -212,7 +212,7 @@ describe('CreateProjectPage', () => {

it('it does nothing if the user clicks `No`', async () => {
mockBiohubApi().codes.getAllCodeSets.mockResolvedValue({
code_set: []
coordinator_agency: [{ id: 1, name: 'A Rocha Canada' }]
});
history.push('/home');
history.push('/projects/create');
Expand All @@ -231,12 +231,12 @@ describe('CreateProjectPage', () => {
describe('draft project', () => {
beforeEach(() => {
mockBiohubApi().codes.getAllCodeSets.mockResolvedValue({
code_set: []
coordinator_agency: [{ id: 1, name: 'A Rocha Canada' }]
});
});

it('opens the save as draft dialog', async () => {
const { getByText, findByText, asFragment } = renderContainer();
const { getByText, findByText } = renderContainer();

const saveAsDraftButton = await findByText('Save as Draft');

Expand All @@ -245,13 +245,10 @@ describe('CreateProjectPage', () => {
await waitFor(() => {
expect(getByText('Save Incomplete Project as a Draft')).toBeVisible();
});

// Expect dialog to be open
expect(asFragment()).toMatchSnapshot();
});

it('closes the dialog on cancel button click', async () => {
const { getByText, findByText, queryByText, getByRole, asFragment } = renderContainer();
const { getByText, findByText, queryByText, getByRole } = renderContainer();

const saveAsDraftButton = await findByText('Save as Draft');

Expand All @@ -268,9 +265,6 @@ describe('CreateProjectPage', () => {
await waitFor(() => {
expect(queryByText('Save Incomplete Project as a Draft')).not.toBeInTheDocument();
});

// Expect dialog to be closed
expect(asFragment()).toMatchSnapshot();
});

it('calls the createDraft/updateDraft functions and closes the dialog on save button click', async () => {
Expand All @@ -279,7 +273,7 @@ describe('CreateProjectPage', () => {
date: '2021-01-20'
});

const { getByText, findByText, queryByText, getByLabelText, asFragment } = renderContainer();
const { getByText, findByText, queryByText, getByLabelText } = renderContainer();

const saveAsDraftButton = await findByText('Save as Draft');

Expand All @@ -299,9 +293,6 @@ describe('CreateProjectPage', () => {
expect(queryByText('Save Incomplete Project as a Draft')).not.toBeInTheDocument();
});

// Expect dialog to be closed
expect(asFragment()).toMatchSnapshot();

fireEvent.click(getByText('Save as Draft'));

await waitFor(() => {
Expand All @@ -317,17 +308,100 @@ describe('CreateProjectPage', () => {

expect(queryByText('Save Incomplete Project as a Draft')).not.toBeInTheDocument();
});
});

// Expect dialog to be closed
expect(asFragment()).toMatchSnapshot();
it('calls the createDraft/updateDraft functions with WIP form data', async () => {
mockBiohubApi().draft.createDraft.mockResolvedValue({
id: 1,
date: '2021-01-20'
});

const { getByText, findByText, queryByText, getByLabelText } = renderContainer();

// wait for initial page to load
await waitFor(() => {
expect(getByText('Project Coordinator')).toBeVisible();
});

// update first name field
fireEvent.change(getByLabelText('First Name *'), { target: { value: 'draft first name' } });

const saveAsDraftButton = await findByText('Save as Draft');

fireEvent.click(saveAsDraftButton);

await waitFor(() => {
expect(getByText('Save Incomplete Project as a Draft')).toBeVisible();
});

fireEvent.change(getByLabelText('Draft Name *'), { target: { value: 'draft name' } });

fireEvent.click(getByText('Save'));

await waitFor(() => {
expect(mockBiohubApi().draft.createDraft).toHaveBeenCalledWith('draft name', {
coordinator: {
first_name: 'draft first name',
last_name: '',
email_address: '',
coordinator_agency: '',
share_contact_details: 'false'
},
permit: expect.any(Object),
project: expect.any(Object),
objectives: expect.any(Object),
location: expect.any(Object),
species: expect.any(Object),
iucn: expect.any(Object),
funding: expect.any(Object),
partnerships: expect.any(Object)
});

expect(queryByText('Save Incomplete Project as a Draft')).not.toBeInTheDocument();
});

// update last name field
fireEvent.change(getByLabelText('Last Name *'), { target: { value: 'draft last name' } });

fireEvent.click(getByText('Save as Draft'));

await waitFor(() => {
expect(getByText('Save Incomplete Project as a Draft')).toBeVisible();
});

fireEvent.change(getByLabelText('Draft Name *'), { target: { value: 'draft name' } });

fireEvent.click(getByText('Save'));

await waitFor(() => {
expect(mockBiohubApi().draft.updateDraft).toHaveBeenCalledWith(1, 'draft name', {
coordinator: {
first_name: 'draft first name',
last_name: 'draft last name',
email_address: '',
coordinator_agency: '',
share_contact_details: 'false'
},
permit: expect.any(Object),
project: expect.any(Object),
objectives: expect.any(Object),
location: expect.any(Object),
species: expect.any(Object),
iucn: expect.any(Object),
funding: expect.any(Object),
partnerships: expect.any(Object)
});

expect(queryByText('Save Incomplete Project as a Draft')).not.toBeInTheDocument();
});
});

it('renders an error dialog if the draft submit request fails', async () => {
mockBiohubApi().draft.createDraft.mockImplementation(() => {
throw new Error('Draft failed exception!');
});

const { getByText, findByText, queryByText, getByLabelText, asFragment } = renderContainer();
const { getByText, findByText, queryByText, getByLabelText } = renderContainer();

const saveAsDraftButton = await findByText('Save as Draft');

Expand All @@ -344,9 +418,6 @@ describe('CreateProjectPage', () => {
await waitFor(() => {
expect(queryByText('Save Incomplete Project as a Draft')).not.toBeInTheDocument();
});

// Expect dialog to be closed
expect(asFragment()).toMatchSnapshot();
});
});
});
91 changes: 47 additions & 44 deletions app/src/features/projects/CreateProjectPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,69 +18,58 @@ import { ArrowBack } from '@material-ui/icons';
import EditDialog from 'components/dialog/EditDialog';
import { ErrorDialog, IErrorDialogProps } from 'components/dialog/ErrorDialog';
import YesNoDialog from 'components/dialog/YesNoDialog';
import { DATE_FORMAT } from 'constants/dateFormats';
import { CreateProjectDraftI18N, CreateProjectI18N } from 'constants/i18n';
import {
IProjectCoordinatorForm,
ProjectCoordinatorInitialValues,
ProjectCoordinatorYupSchema
} from 'features/projects/components/ProjectCoordinatorForm';
import {
IProjectDetailsForm,
ProjectDetailsFormInitialValues,
ProjectDetailsFormYupSchema
} from 'features/projects/components/ProjectDetailsForm';
import {
IProjectFundingForm,
ProjectFundingFormInitialValues,
ProjectFundingFormYupSchema
} from 'features/projects/components/ProjectFundingForm';
import { ProjectIUCNFormInitialValues, ProjectIUCNFormYupSchema } from 'features/projects/components/ProjectIUCNForm';
import {
IProjectIUCNForm,
ProjectIUCNFormInitialValues,
ProjectIUCNFormYupSchema
} from 'features/projects/components/ProjectIUCNForm';
import {
IProjectLocationForm,
ProjectLocationFormInitialValues,
ProjectLocationFormYupSchema
} from 'features/projects/components/ProjectLocationForm';
import {
IProjectObjectivesForm,
ProjectObjectivesFormInitialValues,
ProjectObjectivesFormYupSchema
} from 'features/projects/components/ProjectObjectivesForm';
import {
ProjectPartnershipsFormInitialValues,
ProjectPartnershipsFormYupSchema
} from 'features/projects/components/ProjectPartnershipsForm';
import ProjectPermitForm, {
IProjectPermitForm,
ProjectPermitFormInitialValues,
ProjectPermitFormYupSchema
} from 'features/projects/components/ProjectPermitForm';
import {
IProjectSpeciesForm,
ProjectSpeciesFormInitialValues,
ProjectSpeciesFormYupSchema
} from 'features/projects/components/ProjectSpeciesForm';
import {
IProjectPartnershipsForm,
ProjectPartnershipsFormInitialValues,
ProjectPartnershipsFormYupSchema
} from 'features/projects/components/ProjectPartnershipsForm';
import { Formik } from 'formik';
import { Formik, FormikProps } from 'formik';
import * as History from 'history';
import { APIError } from 'hooks/api/useAxios';
import { useBiohubApi } from 'hooks/useBioHubApi';
import { ICreatePermitNoSamplingRequest, ICreateProjectRequest } from 'interfaces/useProjectApi.interface';
import { IGetAllCodeSetsResponse } from 'interfaces/useCodesApi.interface';
import React, { useEffect, useState } from 'react';
import { ICreatePermitNoSamplingRequest, ICreateProjectRequest } from 'interfaces/useProjectApi.interface';
import React, { useEffect, useRef, useState } from 'react';
import { useHistory } from 'react-router';
import * as History from 'history';
import { Prompt } from 'react-router-dom';
import ProjectStepComponents from 'utils/ProjectStepComponents';
import { getFormattedDate } from 'utils/Utils';
import ProjectDraftForm, {
IProjectDraftForm,
ProjectDraftFormInitialValues,
ProjectDraftFormYupSchema
} from './components/ProjectDraftForm';
import { getFormattedDate } from 'utils/Utils';
import { DATE_FORMAT } from 'constants/dateFormats';

export interface ICreateProjectStep {
stepTitle: string;
Expand Down Expand Up @@ -147,6 +136,10 @@ const CreateProjectPage: React.FC = () => {
// All possible step forms, and their current state
const [stepForms, setStepForms] = useState<ICreateProjectStep[]>([]);

// Reference to pass to the formik component in order to access its state at any time
// Used by the draft logic to fetch the values of a step form that has not been validated/completed
const [formikRef] = useState(useRef<FormikProps<any>>(null));

// Whether or not to show the 'Are you sure you want to cancel' dialog
const [openCancelDialog, setOpenCancelDialog] = useState(false);

Expand Down Expand Up @@ -333,28 +326,29 @@ const CreateProjectPage: React.FC = () => {
history.push('/projects');
};

const getProjectFormData = (): ICreateProjectRequest => {
return {
coordinator: stepForms[0].stepValues as IProjectCoordinatorForm,
permit: stepForms[1].stepValues as IProjectPermitForm,
project: stepForms[2].stepValues as IProjectDetailsForm,
objectives: stepForms[3].stepValues as IProjectObjectivesForm,
location: stepForms[4].stepValues as IProjectLocationForm,
species: stepForms[5].stepValues as IProjectSpeciesForm,
iucn: stepForms[6].stepValues as IProjectIUCNForm,
funding: stepForms[7].stepValues as IProjectFundingForm,
partnerships: stepForms[8].stepValues as IProjectPartnershipsForm
};
};

const handleSubmitDraft = async (values: IProjectDraftForm) => {
try {
let response;

// Get the form data for all steps
// Fetch the data from the formikRef for whichever step is the active step
// WHy? WIP changes to the active step will not yet be updated into its respective stepForms[n].stepValues
NickPhura marked this conversation as resolved.
Show resolved Hide resolved
const draftFormData = {
coordinator: (activeStep === 0 && formikRef?.current?.values) || stepForms[0].stepValues,
permit: (activeStep === 1 && formikRef?.current?.values) || stepForms[1].stepValues,
project: (activeStep === 2 && formikRef?.current?.values) || stepForms[2].stepValues,
objectives: (activeStep === 3 && formikRef?.current?.values) || stepForms[3].stepValues,
location: (activeStep === 4 && formikRef?.current?.values) || stepForms[4].stepValues,
species: (activeStep === 5 && formikRef?.current?.values) || stepForms[5].stepValues,
iucn: (activeStep === 6 && formikRef?.current?.values) || stepForms[6].stepValues,
funding: (activeStep === 7 && formikRef?.current?.values) || stepForms[7].stepValues,
partnerships: (activeStep === 8 && formikRef?.current?.values) || stepForms[8].stepValues
};

if (draft?.id) {
response = await biohubApi.draft.updateDraft(draft.id, values.draft_name, getProjectFormData());
response = await biohubApi.draft.updateDraft(draft.id, values.draft_name, draftFormData);
} else {
response = await biohubApi.draft.createDraft(values.draft_name, getProjectFormData());
response = await biohubApi.draft.createDraft(values.draft_name, draftFormData);
}

setOpenDraftDialog(false);
Expand Down Expand Up @@ -384,15 +378,23 @@ const CreateProjectPage: React.FC = () => {
*/
const handleSubmit = async () => {
try {
const formData = getProjectFormData();

if (!isSamplingConducted(formData.permit)) {
if (!isSamplingConducted(stepForms[1].stepValues)) {
await createPermitNoSampling({
coordinator: formData.coordinator,
permit: formData.permit
coordinator: stepForms[0].stepValues,
permit: stepForms[1].stepValues
});
} else {
await createProject(formData);
await createProject({
coordinator: stepForms[0].stepValues,
permit: stepForms[1].stepValues,
project: stepForms[2].stepValues,
objectives: stepForms[3].stepValues,
location: stepForms[4].stepValues,
species: stepForms[5].stepValues,
iucn: stepForms[6].stepValues,
funding: stepForms[7].stepValues,
partnerships: stepForms[8].stepValues
});
}
} catch (error) {
const apiError = error as APIError;
Expand Down Expand Up @@ -483,6 +485,7 @@ const CreateProjectPage: React.FC = () => {
<StepContent>
<Box my={3}>
<Formik
innerRef={formikRef}
initialValues={stepForms[index].stepValues}
validationSchema={stepForms[index].stepValidation}
validateOnBlur={true}
Expand Down
Loading