From b874a2d0637c12603214a0c2c7ccb0eedf60588d Mon Sep 17 00:00:00 2001 From: Dylan Leard Date: Fri, 25 Feb 2022 14:33:08 -0800 Subject: [PATCH 01/25] chore: create_project() does not create a project_manager form change --- schema/deploy/mutations/create_project.sql | 11 ----------- .../unit/mutations/create_project_test.sql | 19 ++----------------- .../commit_project_revision_test.sql | 18 +++--------------- 3 files changed, 5 insertions(+), 43 deletions(-) diff --git a/schema/deploy/mutations/create_project.sql b/schema/deploy/mutations/create_project.sql index 88729a6b5b..ed395b61d4 100644 --- a/schema/deploy/mutations/create_project.sql +++ b/schema/deploy/mutations/create_project.sql @@ -42,16 +42,6 @@ begin 'pending', 'Creating new project: project record', 'project' - ), ( - format('{ "projectId": %s, "projectManagerLabelId": 1 }', next_project_id)::jsonb, - 'create', - 'cif', - 'project_manager', - nextval(pg_get_serial_sequence('cif.project_manager', 'id')), - revision_row.id, - 'pending', - 'Creating new project: project_manager record', - 'project_manager' ), ( format('{ "projectId": %s, "contactIndex": 1 }', next_project_id)::jsonb, 'create', @@ -70,7 +60,6 @@ $function$ language plpgsql strict volatile; grant execute on function cif.create_project to cif_internal, cif_external, cif_admin; grant usage, select on sequence cif.project_id_seq to cif_internal, cif_external, cif_admin; -grant usage, select on sequence cif.project_manager_id_seq to cif_internal, cif_external, cif_admin; grant usage, select on sequence cif.project_contact_id_seq to cif_internal, cif_external, cif_admin; commit; diff --git a/schema/test/unit/mutations/create_project_test.sql b/schema/test/unit/mutations/create_project_test.sql index 8b0d114ccf..ad2b8879b9 100644 --- a/schema/test/unit/mutations/create_project_test.sql +++ b/schema/test/unit/mutations/create_project_test.sql @@ -2,10 +2,9 @@ begin; -select plan(6); +select plan(4); truncate table cif.project restart identity cascade; -truncate table cif.project_manager restart identity cascade; truncate table cif.project_revision restart identity cascade; truncate table cif.form_change restart identity cascade; @@ -36,10 +35,9 @@ select results_eq( $$ values ('project'::varchar, 'cif'::varchar, 1::integer), - ('project_manager'::varchar, 'cif'::varchar, 1::integer), ('project_contact'::varchar, 'cif'::varchar, 1::integer) $$, - 'Creates 3 form_change records for the project, project_manager and project_contact tables' + 'Creates 2 form_change records for the project and project_contact tables' ); -- creating a second project to test the sequences @@ -51,18 +49,5 @@ select is( 'Reserves the next id in the sequence for the the project table' ); -select is( - (select form_data_record_id from cif.form_change where form_data_table_name='project_manager' and project_revision_id=2), - (select currval(pg_get_serial_sequence('cif.project_manager', 'id'))::integer), - 'Reserves the next id in the sequence for the the project_manager table' -); - --- prepopulates the project manager form with the project id -select is( - (select new_form_data from cif.form_change where form_data_table_name='project_manager' and project_revision_id=2), - '{ "projectId": 1235, "projectManagerLabelId": 1 }'::jsonb, - 'Populates the project_manager form with the project id' -); - select finish(); rollback; diff --git a/schema/test/unit/trigger_functions/commit_project_revision_test.sql b/schema/test/unit/trigger_functions/commit_project_revision_test.sql index f557fa71c0..80b221d1a7 100644 --- a/schema/test/unit/trigger_functions/commit_project_revision_test.sql +++ b/schema/test/unit/trigger_functions/commit_project_revision_test.sql @@ -33,18 +33,6 @@ update cif.form_change set new_form_data=format('{ where project_revision_id=(select id from cif.project_revision order by id desc limit 1) and form_data_table_name='project'; -update cif.form_change set new_form_data=format('{ - "projectId": %s, - "cifUserId": %s, - "projectManagerLabelId": 1 - }', - (select form_data_record_id from cif.form_change - where form_data_table_name='project' - and project_revision_id=(select id from cif.project_revision order by id desc limit 1)), - (select id from cif.cif_user order by id desc limit 1) - )::jsonb - where project_revision_id=(select id from cif.project_revision order by id desc limit 1) and form_data_table_name='project_manager'; - update cif.form_change set new_form_data=format('{ "projectId": %s, "contactIndex": %s, @@ -68,7 +56,7 @@ select results_eq( select change_status from cif.form_change where project_revision_id=(select id from cif.project_revision order by id desc limit 1); $$, $$ - values ('pending'::varchar), ('pending'::varchar), ('pending'::varchar); + values ('pending'::varchar), ('pending'::varchar); $$, 'Three form changes should be initialized with the pending status' ); @@ -93,7 +81,7 @@ select results_eq( select change_status from cif.form_change where project_revision_id = (select id from cif.project_revision order by id desc limit 1); $$, $$ - values ('test_pending'::varchar), ('test_pending'::varchar), ('test_pending'::varchar); + values ('test_pending'::varchar), ('test_pending'::varchar); $$, 'the form_change rows should be have the test_pending status' ); @@ -113,7 +101,7 @@ select results_eq( select change_status from cif.form_change where project_revision_id=(select id from cif.project_revision order by id desc limit 1); $$, $$ - values ('committed'::varchar), ('committed'::varchar), ('committed'::varchar); + values ('committed'::varchar), ('committed'::varchar); $$, 'the form_change rows should have the committed status' ); From a9b55bfcd9f890abfbc7f53d08dc3a9d277f142b Mon Sep 17 00:00:00 2001 From: Dylan Leard Date: Fri, 25 Feb 2022 15:39:05 -0800 Subject: [PATCH 02/25] chore: start updating front-end --- app/components/Form/ProjectContactForm.tsx | 1 + app/components/Form/ProjectManagerForm.tsx | 217 ++++++++++++++++-- app/mutations/Manager/addManagerToRevision.ts | 38 +++ app/next-env.d.ts | 1 + .../[projectRevision]/index.tsx | 19 +- 5 files changed, 239 insertions(+), 37 deletions(-) create mode 100644 app/mutations/Manager/addManagerToRevision.ts diff --git a/app/components/Form/ProjectContactForm.tsx b/app/components/Form/ProjectContactForm.tsx index 0ac7629fde..ce06c065f3 100644 --- a/app/components/Form/ProjectContactForm.tsx +++ b/app/components/Form/ProjectContactForm.tsx @@ -51,6 +51,7 @@ const ProjectContactForm: React.FC = (props) => { newFormData } } + cursor } } allContacts { diff --git a/app/components/Form/ProjectManagerForm.tsx b/app/components/Form/ProjectManagerForm.tsx index a5495d4e30..adf1713ee2 100644 --- a/app/components/Form/ProjectManagerForm.tsx +++ b/app/components/Form/ProjectManagerForm.tsx @@ -1,32 +1,66 @@ -import type { JSONSchema7 } from "json-schema"; -import React, { forwardRef, useMemo } from "react"; -import { graphql, useFragment } from "react-relay"; -import { ProjectManagerForm_allUsers$key } from "__generated__/ProjectManagerForm_allUsers.graphql"; +import { JSONSchema7, JSONSchema7Definition } from "json-schema"; +import React, { useRef, useMemo } from "react"; +import { graphql, useFragment, useMutation } from "react-relay"; +import { ProjectManagerForm_query$key } from "__generated__/ProjectManagerForm_query.graphql"; import FormBase from "./FormBase"; import projectManagerSchema from "data/jsonSchemaForm/projectManagerSchema"; -import FormComponentProps from "./Interfaces/FormComponentProps"; +import { ValidatingFormProps } from "./Interfaces/FormValidationTypes"; +import Grid from "@button-inc/bcgov-theme/Grid"; +import FormBorder from "lib/theme/components/FormBorder"; +import { Button } from "@button-inc/bcgov-theme"; +import validateFormWithErrors from "lib/helpers/validateFormWithErrors"; +import useAddManagerToRevisionMutation from "mutations/Manager/addManagerToRevision"; +import { mutation as deleteFormChangeMutation } from "mutations/FormChange/deleteFormChange"; +import { mutation as updateFormChangeMutation } from "mutations/FormChange/updateFormChange"; +import useDebouncedMutation from "mutations/useDebouncedMutation"; +import EmptyObjectFieldTemplate from "lib/theme/EmptyObjectFieldTemplate"; -interface Props extends FormComponentProps { - allUsers: ProjectManagerForm_allUsers$key; + +interface Props extends ValidatingFormProps { + query: ProjectManagerForm_query$key; } const uiSchema = { - "ui:title": "Project Manager", cifUserId: { "ui:placeholder": "Select a Project Manager", "ui:col-md": 12, "bcgov:size": "small", "ui:widget": "SearchWidget", - }, + "ui:options": { + label: false, + }, + } }; -const ProjecManagerForm: React.ForwardRefRenderFunction = ( - props, - ref -) => { - const { allCifUsers } = useFragment( +const ProjectManagerForm: React.FC = (props) => { + const formRefs = useRef({}); + const { allCifUsers, projectRevision } = useFragment( graphql` - fragment ProjectManagerForm_allUsers on Query { + fragment ProjectManagerForm_query on Query { + projectRevision(id: $projectRevision) { + id + rowId + projectId + managerFormChanges: projectManagerFormChangesByLabel { + edges { + cursor + node { + projectManagerLabel { + id + rowId + label + } + formChange { + id + newFormData + } + } + } + } + projectFormChange { + formDataRecordId + } + } allCifUsers { edges { node { @@ -38,27 +72,162 @@ const ProjecManagerForm: React.ForwardRefRenderFunction = ( } } `, - props.allUsers + props.query ); - const schema: JSONSchema7 = useMemo(() => { - const initialSchema = projectManagerSchema; + console.log(projectRevision.managerFormChanges) - initialSchema.properties.cifUserId = { - ...initialSchema.properties.cifUserId, + // Dynamically build the schema from the list of cif_users + const managerSchema = useMemo(() => { + const schema = projectManagerSchema; + schema.properties.cifUserId = { + ...schema.properties.cifUserId, anyOf: allCifUsers.edges.map(({ node }) => { return { type: "number", title: `${node.firstName} ${node.lastName}`, enum: [node.rowId], value: node.rowId, - }; + } as JSONSchema7Definition; }), }; - return initialSchema as JSONSchema7; + + return schema as JSONSchema7; }, [allCifUsers]); - return ; + // Add a manager to the project revision + const [applyAddManagerToRevision] = + useAddManagerToRevisionMutation(); + const addManager = (data: any) => { + applyAddManagerToRevision({ + variables: { + connections: [projectRevision.managerFormChanges.__id], + projectRevisionId: projectRevision.rowId, newFormData: data, + } + }); + }; + + // Delete a manager from the project revision + const [discardFormChange] = useMutation(deleteFormChangeMutation); + const deleteManager = (id: string) => { + console.log(id) + // discardFormChange({ + // variables: { + // input: { + // id: formChange.node.id, + // }, + // connections: [projectRevision.managerFormChanges.__id], + // }, + // onCompleted: () => { + // // delete formRefs.current[formChangeId]; + // console.log('deleted') + // }, + // }); + }; + + const [applyUpdateFormChangeMutation] = useDebouncedMutation( + updateFormChangeMutation + ); + // Update and existing project_manager form change if it exists, otherwise create one + const createOrUpdateFormChange = (formChangeId: string, labelId: number, change: any) => { + console.log(formChangeId, change); + const data = {...change, projectManagerLabelId: labelId, projectId: projectRevision.projectFormChange.formDataRecordId}; + console.log(data) + + if (formChangeId && change.cifUserId) { + applyUpdateFormChangeMutation({ + variables: { + input: { + id: formChangeId, + formChangePatch: { + newFormData: data, + }, + }, + }, + optimisticResponse: { + updateFormChange: { + formChange: { + id: formChangeId, + newFormData: change, + }, + }, + }, + debounceKey: formChangeId, + }); + } else if (change.cifUserId) { + console.log('CREATE: ', data); + addManager(data); + } + else if (formChangeId && !change.cifUserId) { + console.log('DELETE: ', change) + // deleteManager(formChange.id) + } + }; + + props.setValidatingForm({ + selfValidate: () => { + return Object.keys(formRefs.current).reduce((agg, formId) => { + const formObject = formRefs.current[formId]; + return [...agg, ...validateFormWithErrors(formObject)]; + }, []); + }, + }); + + return ( + <> + + + + + {projectRevision.managerFormChanges.edges.map(({node}) => ( + <> + + + + + + (formRefs.current[node.projectManagerLabel.id] = el)} + formData={node.formChange?.newFormData} + onChange={(change) => { + createOrUpdateFormChange(node.formChange?.id, node.projectManagerLabel.rowId, change.formData); + }} + schema={managerSchema} + uiSchema={uiSchema} + ObjectFieldTemplate={EmptyObjectFieldTemplate} + /> + + + + + + + ))} + + + + + + + ); }; -export default forwardRef(ProjecManagerForm); +export default ProjectManagerForm; diff --git a/app/mutations/Manager/addManagerToRevision.ts b/app/mutations/Manager/addManagerToRevision.ts new file mode 100644 index 0000000000..21b063c050 --- /dev/null +++ b/app/mutations/Manager/addManagerToRevision.ts @@ -0,0 +1,38 @@ +import { useMutation, graphql, Disposable, Environment } from "react-relay"; +import { MutationConfig } from "relay-runtime"; +import { addManagerToRevisionMutation } from "__generated__/addManagerToRevisionMutation.graphql"; + +export const mutation = graphql` + mutation addManagerToRevisionMutation($projectRevisionId: Int! $newFormData: JSON!) { + createFormChange( + input: { + formDataSchemaName: "cif" + formDataTableName: "project_manager" + jsonSchemaName: "project_manager" + operation: CREATE + changeReason: "Add manager to project revision from project manager form" + projectRevisionId: $projectRevisionId + newFormData: $newFormData + } + ) { + formChange { + id + newFormData + } + } + } +`; + +export const useAddManagerToRevisionMutation = ( + commitMutationFn?: ( + environment: Environment, + config: MutationConfig + ) => Disposable +) => { + return useMutation( + mutation, + commitMutationFn + ); +}; + +export default useAddManagerToRevisionMutation; diff --git a/app/next-env.d.ts b/app/next-env.d.ts index 4f11a03dc6..9bc3dd46b9 100644 --- a/app/next-env.d.ts +++ b/app/next-env.d.ts @@ -1,4 +1,5 @@ /// +/// /// // NOTE: This file should not be edited diff --git a/app/pages/cif/project-revision/[projectRevision]/index.tsx b/app/pages/cif/project-revision/[projectRevision]/index.tsx index d1ee9d7724..4fc3cbe5a0 100644 --- a/app/pages/cif/project-revision/[projectRevision]/index.tsx +++ b/app/pages/cif/project-revision/[projectRevision]/index.tsx @@ -39,7 +39,7 @@ const pageQuery = graphql` } } ...ProjectForm_query - ...ProjectManagerForm_allUsers + ...ProjectManagerForm_query ...ProjectContactForm_query } } @@ -49,7 +49,7 @@ export function ProjectRevision({ preloadedQuery, }: RelayProps<{}, ProjectRevisionQuery>) { const projectFormRef = useRef(null); - const projectManagerFormRef = useRef(null); + const projectManagerFormRef = useRef(null); const projectContactFormRef = useRef(null); const router = useRouter(); @@ -102,7 +102,7 @@ export function ProjectRevision({ const commitProject = async () => { const errors = [ ...validateFormWithErrors(projectFormRef.current), - ...validateFormWithErrors(projectManagerFormRef.current), + ...projectManagerFormRef.current.selfValidate(), ...projectContactFormRef.current.selfValidate(), ]; @@ -172,17 +172,10 @@ export function ProjectRevision({ - handleChange( - query.projectRevision.projectManagerFormChange, - change.formData - ) + query={query} + setValidatingForm={(validator) => + (projectContactFormRef.current = validator) } - allUsers={query} /> Date: Fri, 25 Feb 2022 16:15:56 -0800 Subject: [PATCH 03/25] feat: multiple project managers can be added to a revision --- app/components/Form/ProjectContactForm.tsx | 1 - app/components/Form/ProjectManagerForm.tsx | 5 ++-- app/mutations/Manager/addManagerToRevision.ts | 25 ++++++++++++++++--- 3 files changed, 24 insertions(+), 7 deletions(-) diff --git a/app/components/Form/ProjectContactForm.tsx b/app/components/Form/ProjectContactForm.tsx index ce06c065f3..0ac7629fde 100644 --- a/app/components/Form/ProjectContactForm.tsx +++ b/app/components/Form/ProjectContactForm.tsx @@ -51,7 +51,6 @@ const ProjectContactForm: React.FC = (props) => { newFormData } } - cursor } } allContacts { diff --git a/app/components/Form/ProjectManagerForm.tsx b/app/components/Form/ProjectManagerForm.tsx index adf1713ee2..b7037dccbe 100644 --- a/app/components/Form/ProjectManagerForm.tsx +++ b/app/components/Form/ProjectManagerForm.tsx @@ -101,8 +101,9 @@ const ProjectManagerForm: React.FC = (props) => { const addManager = (data: any) => { applyAddManagerToRevision({ variables: { - connections: [projectRevision.managerFormChanges.__id], - projectRevisionId: projectRevision.rowId, newFormData: data, + projectRevision: projectRevision.id, + projectRevisionId: projectRevision.rowId, + newFormData: data, } }); }; diff --git a/app/mutations/Manager/addManagerToRevision.ts b/app/mutations/Manager/addManagerToRevision.ts index 21b063c050..34d6665003 100644 --- a/app/mutations/Manager/addManagerToRevision.ts +++ b/app/mutations/Manager/addManagerToRevision.ts @@ -3,7 +3,7 @@ import { MutationConfig } from "relay-runtime"; import { addManagerToRevisionMutation } from "__generated__/addManagerToRevisionMutation.graphql"; export const mutation = graphql` - mutation addManagerToRevisionMutation($projectRevisionId: Int! $newFormData: JSON!) { + mutation addManagerToRevisionMutation($projectRevision: ID! $projectRevisionId: Int! $newFormData: JSON!) { createFormChange( input: { formDataSchemaName: "cif" @@ -15,9 +15,25 @@ export const mutation = graphql` newFormData: $newFormData } ) { - formChange { - id - newFormData + query { + projectRevision(id: $projectRevision) { + managerFormChanges: projectManagerFormChangesByLabel { + edges { + cursor + node { + projectManagerLabel { + id + rowId + label + } + formChange { + id + newFormData + } + } + } + } + } } } } @@ -36,3 +52,4 @@ export const useAddManagerToRevisionMutation = ( }; export default useAddManagerToRevisionMutation; + From 34cfe981b10308c71c7cd83fb512d2f665bb5445 Mon Sep 17 00:00:00 2001 From: Dylan Leard Date: Mon, 28 Feb 2022 11:16:55 -0800 Subject: [PATCH 04/25] chore: add a custom delete mutation --- app/components/Form/ProjectManagerForm.tsx | 50 ++++++++----------- app/mutations/Manager/addManagerToRevision.ts | 1 - .../Manager/deleteManagerFromRevision.ts | 47 +++++++++++++++++ 3 files changed, 67 insertions(+), 31 deletions(-) create mode 100644 app/mutations/Manager/deleteManagerFromRevision.ts diff --git a/app/components/Form/ProjectManagerForm.tsx b/app/components/Form/ProjectManagerForm.tsx index b7037dccbe..c2e866d2a6 100644 --- a/app/components/Form/ProjectManagerForm.tsx +++ b/app/components/Form/ProjectManagerForm.tsx @@ -1,6 +1,6 @@ import { JSONSchema7, JSONSchema7Definition } from "json-schema"; import React, { useRef, useMemo } from "react"; -import { graphql, useFragment, useMutation } from "react-relay"; +import { graphql, useFragment } from "react-relay"; import { ProjectManagerForm_query$key } from "__generated__/ProjectManagerForm_query.graphql"; import FormBase from "./FormBase"; import projectManagerSchema from "data/jsonSchemaForm/projectManagerSchema"; @@ -10,7 +10,7 @@ import FormBorder from "lib/theme/components/FormBorder"; import { Button } from "@button-inc/bcgov-theme"; import validateFormWithErrors from "lib/helpers/validateFormWithErrors"; import useAddManagerToRevisionMutation from "mutations/Manager/addManagerToRevision"; -import { mutation as deleteFormChangeMutation } from "mutations/FormChange/deleteFormChange"; +import useDeleteManagerFromRevisionMutation from "mutations/Manager/deleteManagerFromRevision"; import { mutation as updateFormChangeMutation } from "mutations/FormChange/updateFormChange"; import useDebouncedMutation from "mutations/useDebouncedMutation"; import EmptyObjectFieldTemplate from "lib/theme/EmptyObjectFieldTemplate"; @@ -40,10 +40,8 @@ const ProjectManagerForm: React.FC = (props) => { projectRevision(id: $projectRevision) { id rowId - projectId managerFormChanges: projectManagerFormChangesByLabel { edges { - cursor node { projectManagerLabel { id @@ -75,8 +73,6 @@ const ProjectManagerForm: React.FC = (props) => { props.query ); - console.log(projectRevision.managerFormChanges) - // Dynamically build the schema from the list of cif_users const managerSchema = useMemo(() => { const schema = projectManagerSchema; @@ -109,32 +105,26 @@ const ProjectManagerForm: React.FC = (props) => { }; // Delete a manager from the project revision - const [discardFormChange] = useMutation(deleteFormChangeMutation); + const [discardFormChange] = useDeleteManagerFromRevisionMutation(); const deleteManager = (id: string) => { - console.log(id) - // discardFormChange({ - // variables: { - // input: { - // id: formChange.node.id, - // }, - // connections: [projectRevision.managerFormChanges.__id], - // }, - // onCompleted: () => { - // // delete formRefs.current[formChangeId]; - // console.log('deleted') - // }, - // }); + discardFormChange({ + variables: { + input: { + id: id, + }, + projectRevision: projectRevision.id + } + }); }; const [applyUpdateFormChangeMutation] = useDebouncedMutation( updateFormChangeMutation ); - // Update and existing project_manager form change if it exists, otherwise create one + // Update an existing project_manager form change if it exists, otherwise create one const createOrUpdateFormChange = (formChangeId: string, labelId: number, change: any) => { - console.log(formChangeId, change); const data = {...change, projectManagerLabelId: labelId, projectId: projectRevision.projectFormChange.formDataRecordId}; - console.log(data) + // If a form_change already exists, and the payload contains a cifUserId update it if (formChangeId && change.cifUserId) { applyUpdateFormChangeMutation({ variables: { @@ -149,19 +139,19 @@ const ProjectManagerForm: React.FC = (props) => { updateFormChange: { formChange: { id: formChangeId, - newFormData: change, + newFormData: data, }, }, }, debounceKey: formChangeId, }); + // If a form_change does not exist, and the payload contains a cifUserId create a form_change record } else if (change.cifUserId) { - console.log('CREATE: ', data); addManager(data); } + // If a form_change exists, and the payload does not contain a cifUserId delete it else if (formChangeId && !change.cifUserId) { - console.log('DELETE: ', change) - // deleteManager(formChange.id) + deleteManager(formChangeId); } }; @@ -181,11 +171,11 @@ const ProjectManagerForm: React.FC = (props) => { {projectRevision.managerFormChanges.edges.map(({node}) => ( - <> + - + = (props) => { - + ))} diff --git a/app/mutations/Manager/addManagerToRevision.ts b/app/mutations/Manager/addManagerToRevision.ts index 34d6665003..e95cba8bff 100644 --- a/app/mutations/Manager/addManagerToRevision.ts +++ b/app/mutations/Manager/addManagerToRevision.ts @@ -19,7 +19,6 @@ export const mutation = graphql` projectRevision(id: $projectRevision) { managerFormChanges: projectManagerFormChangesByLabel { edges { - cursor node { projectManagerLabel { id diff --git a/app/mutations/Manager/deleteManagerFromRevision.ts b/app/mutations/Manager/deleteManagerFromRevision.ts new file mode 100644 index 0000000000..108f963f91 --- /dev/null +++ b/app/mutations/Manager/deleteManagerFromRevision.ts @@ -0,0 +1,47 @@ +import { useMutation, graphql, Disposable, Environment } from "react-relay"; +import { MutationConfig } from "relay-runtime"; +import { deleteManagerFromRevisionMutation } from "__generated__/deleteManagerFromRevisionMutation.graphql"; + +export const mutation = graphql` + mutation deleteManagerFromRevisionMutation($input: DeleteFormChangeInput! $projectRevision: ID!) { + deleteFormChange( + input: $input + ) { + deletedFormChangeId + query { + projectRevision(id: $projectRevision) { + managerFormChanges: projectManagerFormChangesByLabel { + edges { + node { + projectManagerLabel { + id + rowId + label + } + formChange { + id + newFormData + } + } + } + } + } + } + } + } +`; + +export const useDeleteManagerFromRevisionMutation = ( + commitMutationFn?: ( + environment: Environment, + config: MutationConfig + ) => Disposable +) => { + return useMutation( + mutation, + commitMutationFn + ); +}; + +export default useDeleteManagerFromRevisionMutation; + From 64bb2adf127ea91705323addd2f64aebaae87b7e Mon Sep 17 00:00:00 2001 From: Dylan Leard Date: Mon, 28 Feb 2022 11:32:45 -0800 Subject: [PATCH 05/25] chore: remove old project_manager_form_change computed column --- .../ProjectRevision/updateProjectRevision.ts | 4 -- .../[projectRevision]/index.tsx | 4 -- app/schema/schema.graphql | 3 +- app/schema/schema.json | 14 +------ ...t_revision_project_manager_form_change.sql | 23 ----------- ...t_revision_project_manager_form_change.sql | 7 ---- schema/sqitch.plan | 1 - ...ision_project_manager_form_change_test.sql | 39 ------------------- ...t_revision_project_manager_form_change.sql | 7 ---- 9 files changed, 2 insertions(+), 100 deletions(-) delete mode 100644 schema/deploy/computed_columns/project_revision_project_manager_form_change.sql delete mode 100644 schema/revert/computed_columns/project_revision_project_manager_form_change.sql delete mode 100644 schema/test/unit/computed_columns/project_revision_project_manager_form_change_test.sql delete mode 100644 schema/verify/computed_columns/project_revision_project_manager_form_change.sql diff --git a/app/mutations/ProjectRevision/updateProjectRevision.ts b/app/mutations/ProjectRevision/updateProjectRevision.ts index 3c2da947d3..c79d92f39f 100644 --- a/app/mutations/ProjectRevision/updateProjectRevision.ts +++ b/app/mutations/ProjectRevision/updateProjectRevision.ts @@ -5,10 +5,6 @@ const mutation = graphql` updateProjectRevision(input: $input) { projectRevision { id - projectManagerFormChange { - id - newFormData - } projectFormChange { id newFormData diff --git a/app/pages/cif/project-revision/[projectRevision]/index.tsx b/app/pages/cif/project-revision/[projectRevision]/index.tsx index 4fc3cbe5a0..1992e858c4 100644 --- a/app/pages/cif/project-revision/[projectRevision]/index.tsx +++ b/app/pages/cif/project-revision/[projectRevision]/index.tsx @@ -29,10 +29,6 @@ const pageQuery = graphql` projectRevision(id: $projectRevision) { id updatedAt - projectManagerFormChange { - id - newFormData - } projectFormChange { id newFormData diff --git a/app/schema/schema.graphql b/app/schema/schema.graphql index a4ee596899..5166398923 100644 --- a/app/schema/schema.graphql +++ b/app/schema/schema.graphql @@ -28313,7 +28313,7 @@ type ProjectRevision implements Node { projectByProjectId: Project """ - Computed column for graphql to retrieve the change related to the project manager association, within a project revision + Computed column for graphql to retrieve the change related to the project record, within a project revision """ projectFormChange: FormChange @@ -28321,7 +28321,6 @@ type ProjectRevision implements Node { Foreign key to the associated project row. Will be null if the project hasn't been committed yet. """ projectId: Int - projectManagerFormChange: FormChange """ Computed column returns a composite value for each record in project_manager_label and the last related form_change if it exists diff --git a/app/schema/schema.json b/app/schema/schema.json index b75a793a0f..caf7707d44 100644 --- a/app/schema/schema.json +++ b/app/schema/schema.json @@ -97391,7 +97391,7 @@ }, { "name": "projectFormChange", - "description": "Computed column for graphql to retrieve the change related to the project manager association, within a project revision", + "description": "Computed column for graphql to retrieve the change related to the project record, within a project revision", "args": [], "type": { "kind": "OBJECT", @@ -97413,18 +97413,6 @@ "isDeprecated": false, "deprecationReason": null }, - { - "name": "projectManagerFormChange", - "description": null, - "args": [], - "type": { - "kind": "OBJECT", - "name": "FormChange", - "ofType": null - }, - "isDeprecated": false, - "deprecationReason": null - }, { "name": "projectManagerFormChangesByLabel", "description": "Computed column returns a composite value for each record in project_manager_label and the last related form_change if it exists", diff --git a/schema/deploy/computed_columns/project_revision_project_manager_form_change.sql b/schema/deploy/computed_columns/project_revision_project_manager_form_change.sql deleted file mode 100644 index 6d1d32166e..0000000000 --- a/schema/deploy/computed_columns/project_revision_project_manager_form_change.sql +++ /dev/null @@ -1,23 +0,0 @@ --- Deploy cif:computed_columns/project_revision_project_manager_form_change to pg --- requires: tables/project_revision - -begin; - -create or replace function cif.project_revision_project_manager_form_change(project_revision cif.project_revision) -returns cif.form_change -as -$computed_column$ - - select * - from cif.form_change - where project_revision_id = project_revision.id - and form_data_schema_name='cif' - and form_data_table_name='project_manager'; - -$computed_column$ language sql stable; - -grant execute on function cif.project_revision_project_form_change to cif_internal, cif_external, cif_admin; - -comment on function cif.project_revision_project_form_change is 'Computed column for graphql to retrieve the change related to the project manager association, within a project revision'; - -commit; diff --git a/schema/revert/computed_columns/project_revision_project_manager_form_change.sql b/schema/revert/computed_columns/project_revision_project_manager_form_change.sql deleted file mode 100644 index b2bd2bc100..0000000000 --- a/schema/revert/computed_columns/project_revision_project_manager_form_change.sql +++ /dev/null @@ -1,7 +0,0 @@ --- Revert cif:computed_columns/project_revision_project_manager_form_change from pg - -begin; - -drop function cif.project_revision_project_manager_form_change; - -commit; diff --git a/schema/sqitch.plan b/schema/sqitch.plan index b1abbc8295..a6a2be7524 100644 --- a/schema/sqitch.plan +++ b/schema/sqitch.plan @@ -55,7 +55,6 @@ util_functions/import_swrs_operators [tables/operator schemas/private] 2022-02-0 tables/project_manager_label [schemas/main] 2022-02-17T23:44:42Z Dylan Leard # Table contains the possible labels that a cif_user can be assigned to as a manager of a project tables/project_manager 2021-11-30T22:00:42Z Pierre Bastianelli # A table to track project manager assignments to projects mutations/create_project [tables/project] 2021-11-09T21:34:55Z Dylan Leard # Custom mutation to create a project -computed_columns/project_revision_project_manager_form_change [tables/project_revision] 2021-12-09T19:32:45Z Pierre Bastianelli # Computed column to retrieve the form change related to the project manager association, within a project revision types/manager_form_changes_by_label_composite_return [schemas/main] 2022-02-18T20:58:37Z Dylan Leard # Add custom composite return type for computed column project_revision_project_manager_form_changes_by_label computed_columns/project_revision_project_manager_form_changes_by_label [tables/project_revision tables/project_manager] 2022-02-18T21:32:45Z Dylan Leard # Computed column to retrieve the set of form changes related to the project manager association by project_manager_label, within a project revision computed_columns/project_pending_project_revision 2022-02-16T17:14:03Z Matthieu Foucault # add a computed column to return a pending revision for a project diff --git a/schema/test/unit/computed_columns/project_revision_project_manager_form_change_test.sql b/schema/test/unit/computed_columns/project_revision_project_manager_form_change_test.sql deleted file mode 100644 index c5dd3182f4..0000000000 --- a/schema/test/unit/computed_columns/project_revision_project_manager_form_change_test.sql +++ /dev/null @@ -1,39 +0,0 @@ -begin; - -select plan(2); - -insert into cif.project_revision(id, change_status) - overriding system value - values (1, 'pending'), (2, 'pending'); -insert into cif.form_change(id, operation, form_data_schema_name, form_data_table_name, project_revision_id, change_reason, json_schema_name) - overriding system value - values - (1, 'create', 'cif', 'project_manager', 1, 'test reason', 'project_manager'), - (2, 'create', 'cif', 'project', 1, 'test reason', 'project'), - (3, 'create', 'test_schema_name', 'project', 1, 'test reason', 'project'); - -select is( - ( - with record as ( - select row(project_revision.*)::cif.project_revision - from cif.project_revision where id=1 - ) select form_data_table_name from cif.project_revision_project_manager_form_change((select * from record)) - ), - 'project_manager', - 'Only returns the form change record for the project_manager table' -); - -select is( - ( - with record as ( - select row(project_revision.*)::cif.project_revision - from cif.project_revision where id=2 - ) select form_data_table_name from cif.project_revision_project_manager_form_change((select * from record)) - ), - null, - 'Returns null if there is no form change for the project_manager table' -); - -select finish(); - -rollback; diff --git a/schema/verify/computed_columns/project_revision_project_manager_form_change.sql b/schema/verify/computed_columns/project_revision_project_manager_form_change.sql deleted file mode 100644 index cfcd469f6d..0000000000 --- a/schema/verify/computed_columns/project_revision_project_manager_form_change.sql +++ /dev/null @@ -1,7 +0,0 @@ --- Verify cif:computed_columns/project_revision_project_manager_form_change on pg - -begin; - -select pg_get_functiondef('cif.project_revision_project_manager_form_change(cif.project_revision)'::regprocedure); - -rollback; From 7774164444c6d8100adc4d9d7b5cc310dd12f5fd Mon Sep 17 00:00:00 2001 From: Dylan Leard Date: Mon, 28 Feb 2022 11:37:35 -0800 Subject: [PATCH 06/25] chore: fix copy-paste error --- app/pages/cif/project-revision/[projectRevision]/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/pages/cif/project-revision/[projectRevision]/index.tsx b/app/pages/cif/project-revision/[projectRevision]/index.tsx index 1992e858c4..c513e34bf1 100644 --- a/app/pages/cif/project-revision/[projectRevision]/index.tsx +++ b/app/pages/cif/project-revision/[projectRevision]/index.tsx @@ -170,7 +170,7 @@ export function ProjectRevision({ - (projectContactFormRef.current = validator) + (projectManagerFormRef.current = validator) } /> Date: Mon, 28 Feb 2022 11:41:26 -0800 Subject: [PATCH 07/25] test: update projectRevision jest test --- .../[projectRevision]/index.test.tsx | 29 ++++++++++++------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/app/tests/unit/pages/project-revision/[projectRevision]/index.test.tsx b/app/tests/unit/pages/project-revision/[projectRevision]/index.test.tsx index a0c0be4510..39780c92cf 100644 --- a/app/tests/unit/pages/project-revision/[projectRevision]/index.test.tsx +++ b/app/tests/unit/pages/project-revision/[projectRevision]/index.test.tsx @@ -31,12 +31,6 @@ environment.mock.queueOperationResolver((operation) => { ProjectRevision() { return { id: "mock-proj-rev-id", - projectManagerFormChange: { - id: "mock-projectmanager-form-id", - newFormData: { - someQueryData: "test", - }, - }, projectFormChange: { id: "mock-project-form-id", newFormData: { @@ -72,11 +66,16 @@ const generateMockForm = (errors: any[] = []) => { describe("The Create Project page", () => { beforeEach(() => { mocked(ProjectForm).render.mockReset(); - mocked(ProjectManagerForm).render.mockReset(); + mocked(ProjectManagerForm).mockReset(); mocked(ProjectContactForm).mockReset(); mocked(ProjectForm).render.mockImplementation(() => null); - mocked(ProjectManagerForm).render.mockImplementation(() => null); + mocked(ProjectManagerForm).mockImplementation((props) => { + props.setValidatingForm({ + selfValidate: jest.fn().mockImplementation(() => []), + }); + return null; + }); mocked(ProjectContactForm).mockImplementation((props) => { props.setValidatingForm({ selfValidate: jest.fn().mockImplementation(() => []), @@ -153,7 +152,12 @@ describe("The Create Project page", () => { it("calls the updateProjectRevision mutation when the Submit Button is clicked & input values are valid", async () => { mocked(ProjectForm).render.mockImplementation(generateMockForm()); - mocked(ProjectManagerForm).render.mockImplementation(generateMockForm()); + mocked(ProjectManagerForm).mockImplementation((props) => { + props.setValidatingForm({ + selfValidate: jest.fn().mockImplementation(() => []), + }); + return null; + }); const spy = jest.fn(); jest @@ -192,7 +196,12 @@ describe("The Create Project page", () => { mocked(ProjectForm).render.mockImplementation( generateMockForm([{ thereIsAnError: true }]) ); - mocked(ProjectManagerForm).render.mockImplementation(generateMockForm()); + mocked(ProjectManagerForm).mockImplementation((props) => { + props.setValidatingForm({ + selfValidate: jest.fn().mockImplementation(() => []), + }); + return null; + }); const spy = jest.fn(); jest From d5fb237b75c4861d8444bde320d7b41d2fd2f0bb Mon Sep 17 00:00:00 2001 From: Dylan Leard Date: Mon, 28 Feb 2022 14:37:51 -0800 Subject: [PATCH 08/25] test: add unit test for ProjectManagerForm --- .../Form/ProjectManagerForm.test.tsx | 183 ++++++++++++++++++ 1 file changed, 183 insertions(+) create mode 100644 app/tests/unit/components/Form/ProjectManagerForm.test.tsx diff --git a/app/tests/unit/components/Form/ProjectManagerForm.test.tsx b/app/tests/unit/components/Form/ProjectManagerForm.test.tsx new file mode 100644 index 0000000000..6248af3fbc --- /dev/null +++ b/app/tests/unit/components/Form/ProjectManagerForm.test.tsx @@ -0,0 +1,183 @@ +import { render, screen, fireEvent } from "@testing-library/react"; +import userEvent from '@testing-library/user-event'; +import { ValidatingFormProps } from "components/Form/Interfaces/FormValidationTypes"; +import ProjectManagerForm from "components/Form/ProjectManagerForm"; +import { + graphql, + RelayEnvironmentProvider, + useLazyLoadQuery, +} from "react-relay"; +import { createMockEnvironment, MockPayloadGenerator } from "relay-test-utils"; +import compiledProjectManagerFormQuery, { + ProjectManagerFormQuery, +} from "__generated__/ProjectManagerFormQuery.graphql"; +import validateFormWithErrors from "lib/helpers/validateFormWithErrors"; +import { mocked } from "jest-mock"; + +jest.mock("lib/helpers/validateFormWithErrors"); + +const loadedQuery = graphql` + query ProjectManagerFormQuery($projectRevision: ID!) @relay_test_operation { + query { + # Spread the fragment you want to test here + ...ProjectManagerForm_query + } + } +`; + +const props: ValidatingFormProps = { + setValidatingForm: jest.fn(), +}; + +let environment; +const TestRenderer = () => { + const data = useLazyLoadQuery(loadedQuery, { + projectRevision: "test-project-revision", + }); + return ; +}; +const renderProjectForm = () => { + return render( + + + + ); +}; + +const getMockQueryPayload = () => ({ + Query() { + return { + projectRevision: { + id: 'Test Revision ID', + rowId: 1, + managerFormChanges:{ + edges: [ + { + node: { + projectManagerLabel: { + id: 'Test Label 1 ID', + rowId: 1, + label: 'Test Label 1' + }, + formChange: null + } + }, + { + node: { + projectManagerLabel: { + id: 'Test Label 2 ID', + rowId: 2, + label: 'Test Label 2' + }, + formChange: {id: 'Change 2 ID', newFormData: {cifUserId: 2, projectId: 1, projectManagerLabelId: 2}} + } + }, + ] + }, + projectFormChange: { + formDataRecordId: 1 + } + }, + allCifUsers: { + edges: [ + { + node: { + rowId: 1, + firstName: 'Test First Name 1', + lastName: 'Test Last Name 1' + } + }, + { + node: { + rowId: 2, + firstName: 'Test First Name 2', + lastName: 'Test Last Name 2' + } + }, + ] + } + }; + }, +}); + +describe("The ProjectManagerForm", () => { + beforeEach(() => { + jest.restoreAllMocks(); + + environment = createMockEnvironment(); + + environment.mock.queueOperationResolver((operation) => + MockPayloadGenerator.generate(operation, getMockQueryPayload()) + ); + + environment.mock.queuePendingOperation(compiledProjectManagerFormQuery, {}); + }); + + it("Renders a form for each Project Manager Label", () => { + renderProjectForm(); + expect(screen.getAllByRole("textbox")).toHaveLength(2); + }); + + it("Renders any data contained in a formChange", () => { + renderProjectForm(); + expect(screen.getAllByPlaceholderText("Select a Project Manager")[1]).toHaveValue('Test First Name 2 Test Last Name 2'); + }); + + it("Calls the addManagerToRevision mutation when a new selection is made in the Manager dropdown", () => { + const mutationSpy = jest.fn(); + jest + .spyOn(require("react-relay"), "useMutation") + .mockImplementation(() => [mutationSpy, jest.fn()]); + + renderProjectForm(); + + fireEvent.click(screen.getAllByTitle('Open')[0]); + fireEvent.click(screen.getByText('Test First Name 1 Test Last Name 1')); + + expect(mutationSpy).toHaveBeenCalledWith({ + variables: { + projectRevision: 'Test Revision ID', + projectRevisionId: 1, + newFormData: {cifUserId: 1, projectId: 1, projectManagerLabelId: 1}, + } + }); + }); + + it("Calls the deleteFormChange mutation when the remove button is clicked", () => { + const mutationSpy = jest.fn(); + jest + .spyOn(require("react-relay"), "useMutation") + .mockImplementation(() => [mutationSpy, jest.fn()]); + + renderProjectForm(); + const clearButton = screen.getAllByText("Clear")[1]; + clearButton.click(); + + expect(mutationSpy).toHaveBeenCalledWith({ + variables: { + input: { + id: "Change 2 ID", + }, + projectRevision: "Test Revision ID" + } + }); + }); + + it("Validates all Manager forms when validator is called", () => { + mocked(validateFormWithErrors).mockImplementation(() => []); + jest + .spyOn(require("react-relay"), "useMutation") + .mockImplementation(() => [jest.fn(), jest.fn()]); + + renderProjectForm(); + + expect(props.setValidatingForm).toHaveBeenCalledWith({ + selfValidate: expect.any(Function), + }); + + props.setValidatingForm.mock.calls[0][0].selfValidate(); + + // Once per form + expect(mocked(validateFormWithErrors)).toHaveBeenCalledTimes(2); + }); +}); From 68af6c636857c7818668109299a030af5587f79e Mon Sep 17 00:00:00 2001 From: Dylan Leard Date: Mon, 28 Feb 2022 14:42:57 -0800 Subject: [PATCH 09/25] chore: linting fixes --- app/components/Form/ProjectManagerForm.tsx | 122 ++++++++++-------- app/mutations/Manager/addManagerToRevision.ts | 12 +- .../Manager/deleteManagerFromRevision.ts | 10 +- .../Form/ProjectManagerForm.test.tsx | 108 +++++++++------- 4 files changed, 136 insertions(+), 116 deletions(-) diff --git a/app/components/Form/ProjectManagerForm.tsx b/app/components/Form/ProjectManagerForm.tsx index c2e866d2a6..098aa8d770 100644 --- a/app/components/Form/ProjectManagerForm.tsx +++ b/app/components/Form/ProjectManagerForm.tsx @@ -15,7 +15,6 @@ import { mutation as updateFormChangeMutation } from "mutations/FormChange/updat import useDebouncedMutation from "mutations/useDebouncedMutation"; import EmptyObjectFieldTemplate from "lib/theme/EmptyObjectFieldTemplate"; - interface Props extends ValidatingFormProps { query: ProjectManagerForm_query$key; } @@ -29,7 +28,7 @@ const uiSchema = { "ui:options": { label: false, }, - } + }, }; const ProjectManagerForm: React.FC = (props) => { @@ -92,15 +91,14 @@ const ProjectManagerForm: React.FC = (props) => { }, [allCifUsers]); // Add a manager to the project revision - const [applyAddManagerToRevision] = - useAddManagerToRevisionMutation(); + const [applyAddManagerToRevision] = useAddManagerToRevisionMutation(); const addManager = (data: any) => { applyAddManagerToRevision({ variables: { projectRevision: projectRevision.id, projectRevisionId: projectRevision.rowId, newFormData: data, - } + }, }); }; @@ -112,8 +110,8 @@ const ProjectManagerForm: React.FC = (props) => { input: { id: id, }, - projectRevision: projectRevision.id - } + projectRevision: projectRevision.id, + }, }); }; @@ -121,32 +119,40 @@ const ProjectManagerForm: React.FC = (props) => { updateFormChangeMutation ); // Update an existing project_manager form change if it exists, otherwise create one - const createOrUpdateFormChange = (formChangeId: string, labelId: number, change: any) => { - const data = {...change, projectManagerLabelId: labelId, projectId: projectRevision.projectFormChange.formDataRecordId}; + const createOrUpdateFormChange = ( + formChangeId: string, + labelId: number, + change: any + ) => { + const data = { + ...change, + projectManagerLabelId: labelId, + projectId: projectRevision.projectFormChange.formDataRecordId, + }; // If a form_change already exists, and the payload contains a cifUserId update it if (formChangeId && change.cifUserId) { - applyUpdateFormChangeMutation({ - variables: { - input: { - id: formChangeId, - formChangePatch: { - newFormData: data, + applyUpdateFormChangeMutation({ + variables: { + input: { + id: formChangeId, + formChangePatch: { + newFormData: data, + }, }, }, - }, - optimisticResponse: { - updateFormChange: { - formChange: { - id: formChangeId, - newFormData: data, + optimisticResponse: { + updateFormChange: { + formChange: { + id: formChangeId, + newFormData: data, + }, }, }, - }, - debounceKey: formChangeId, - }); - // If a form_change does not exist, and the payload contains a cifUserId create a form_change record - } else if (change.cifUserId) { + debounceKey: formChangeId, + }); + // If a form_change does not exist, and the payload contains a cifUserId create a form_change record + } else if (change.cifUserId) { addManager(data); } // If a form_change exists, and the payload does not contain a cifUserId delete it @@ -170,36 +176,42 @@ const ProjectManagerForm: React.FC = (props) => { - {projectRevision.managerFormChanges.edges.map(({node}) => ( + {projectRevision.managerFormChanges.edges.map(({ node }) => ( - - - - - - (formRefs.current[node.projectManagerLabel.id] = el)} - formData={node.formChange?.newFormData} - onChange={(change) => { - createOrUpdateFormChange(node.formChange?.id, node.projectManagerLabel.rowId, change.formData); - }} - schema={managerSchema} - uiSchema={uiSchema} - ObjectFieldTemplate={EmptyObjectFieldTemplate} - /> - - - - - + + + + + + + (formRefs.current[node.projectManagerLabel.id] = el) + } + formData={node.formChange?.newFormData} + onChange={(change) => { + createOrUpdateFormChange( + node.formChange?.id, + node.projectManagerLabel.rowId, + change.formData + ); + }} + schema={managerSchema} + uiSchema={uiSchema} + ObjectFieldTemplate={EmptyObjectFieldTemplate} + /> + + + + + ))} diff --git a/app/mutations/Manager/addManagerToRevision.ts b/app/mutations/Manager/addManagerToRevision.ts index e95cba8bff..663a5ffc60 100644 --- a/app/mutations/Manager/addManagerToRevision.ts +++ b/app/mutations/Manager/addManagerToRevision.ts @@ -3,7 +3,11 @@ import { MutationConfig } from "relay-runtime"; import { addManagerToRevisionMutation } from "__generated__/addManagerToRevisionMutation.graphql"; export const mutation = graphql` - mutation addManagerToRevisionMutation($projectRevision: ID! $projectRevisionId: Int! $newFormData: JSON!) { + mutation addManagerToRevisionMutation( + $projectRevision: ID! + $projectRevisionId: Int! + $newFormData: JSON! + ) { createFormChange( input: { formDataSchemaName: "cif" @@ -44,11 +48,7 @@ export const useAddManagerToRevisionMutation = ( config: MutationConfig ) => Disposable ) => { - return useMutation( - mutation, - commitMutationFn - ); + return useMutation(mutation, commitMutationFn); }; export default useAddManagerToRevisionMutation; - diff --git a/app/mutations/Manager/deleteManagerFromRevision.ts b/app/mutations/Manager/deleteManagerFromRevision.ts index 108f963f91..151f4769e8 100644 --- a/app/mutations/Manager/deleteManagerFromRevision.ts +++ b/app/mutations/Manager/deleteManagerFromRevision.ts @@ -3,10 +3,11 @@ import { MutationConfig } from "relay-runtime"; import { deleteManagerFromRevisionMutation } from "__generated__/deleteManagerFromRevisionMutation.graphql"; export const mutation = graphql` - mutation deleteManagerFromRevisionMutation($input: DeleteFormChangeInput! $projectRevision: ID!) { - deleteFormChange( - input: $input - ) { + mutation deleteManagerFromRevisionMutation( + $input: DeleteFormChangeInput! + $projectRevision: ID! + ) { + deleteFormChange(input: $input) { deletedFormChangeId query { projectRevision(id: $projectRevision) { @@ -44,4 +45,3 @@ export const useDeleteManagerFromRevisionMutation = ( }; export default useDeleteManagerFromRevisionMutation; - diff --git a/app/tests/unit/components/Form/ProjectManagerForm.test.tsx b/app/tests/unit/components/Form/ProjectManagerForm.test.tsx index 6248af3fbc..dd6e5acf26 100644 --- a/app/tests/unit/components/Form/ProjectManagerForm.test.tsx +++ b/app/tests/unit/components/Form/ProjectManagerForm.test.tsx @@ -1,5 +1,4 @@ import { render, screen, fireEvent } from "@testing-library/react"; -import userEvent from '@testing-library/user-event'; import { ValidatingFormProps } from "components/Form/Interfaces/FormValidationTypes"; import ProjectManagerForm from "components/Form/ProjectManagerForm"; import { @@ -48,54 +47,61 @@ const getMockQueryPayload = () => ({ Query() { return { projectRevision: { - id: 'Test Revision ID', - rowId: 1, - managerFormChanges:{ - edges: [ - { - node: { - projectManagerLabel: { - id: 'Test Label 1 ID', - rowId: 1, - label: 'Test Label 1' - }, - formChange: null - } - }, - { - node: { - projectManagerLabel: { - id: 'Test Label 2 ID', - rowId: 2, - label: 'Test Label 2' - }, - formChange: {id: 'Change 2 ID', newFormData: {cifUserId: 2, projectId: 1, projectManagerLabelId: 2}} - } - }, - ] - }, - projectFormChange: { - formDataRecordId: 1 - } - }, - allCifUsers: { + id: "Test Revision ID", + rowId: 1, + managerFormChanges: { edges: [ { node: { - rowId: 1, - firstName: 'Test First Name 1', - lastName: 'Test Last Name 1' - } + projectManagerLabel: { + id: "Test Label 1 ID", + rowId: 1, + label: "Test Label 1", + }, + formChange: null, + }, }, { node: { - rowId: 2, - firstName: 'Test First Name 2', - lastName: 'Test Last Name 2' - } + projectManagerLabel: { + id: "Test Label 2 ID", + rowId: 2, + label: "Test Label 2", + }, + formChange: { + id: "Change 2 ID", + newFormData: { + cifUserId: 2, + projectId: 1, + projectManagerLabelId: 2, + }, + }, + }, + }, + ], + }, + projectFormChange: { + formDataRecordId: 1, + }, + }, + allCifUsers: { + edges: [ + { + node: { + rowId: 1, + firstName: "Test First Name 1", + lastName: "Test Last Name 1", }, - ] - } + }, + { + node: { + rowId: 2, + firstName: "Test First Name 2", + lastName: "Test Last Name 2", + }, + }, + ], + }, }; }, }); @@ -120,7 +126,9 @@ describe("The ProjectManagerForm", () => { it("Renders any data contained in a formChange", () => { renderProjectForm(); - expect(screen.getAllByPlaceholderText("Select a Project Manager")[1]).toHaveValue('Test First Name 2 Test Last Name 2'); + expect( + screen.getAllByPlaceholderText("Select a Project Manager")[1] + ).toHaveValue("Test First Name 2 Test Last Name 2"); }); it("Calls the addManagerToRevision mutation when a new selection is made in the Manager dropdown", () => { @@ -131,15 +139,15 @@ describe("The ProjectManagerForm", () => { renderProjectForm(); - fireEvent.click(screen.getAllByTitle('Open')[0]); - fireEvent.click(screen.getByText('Test First Name 1 Test Last Name 1')); + fireEvent.click(screen.getAllByTitle("Open")[0]); + fireEvent.click(screen.getByText("Test First Name 1 Test Last Name 1")); expect(mutationSpy).toHaveBeenCalledWith({ variables: { - projectRevision: 'Test Revision ID', + projectRevision: "Test Revision ID", projectRevisionId: 1, - newFormData: {cifUserId: 1, projectId: 1, projectManagerLabelId: 1}, - } + newFormData: { cifUserId: 1, projectId: 1, projectManagerLabelId: 1 }, + }, }); }); @@ -158,8 +166,8 @@ describe("The ProjectManagerForm", () => { input: { id: "Change 2 ID", }, - projectRevision: "Test Revision ID" - } + projectRevision: "Test Revision ID", + }, }); }); From ce8e52758d13315cf0d3fa243e648c7b78e9c814 Mon Sep 17 00:00:00 2001 From: Dylan Leard Date: Fri, 4 Mar 2022 14:34:37 -0800 Subject: [PATCH 10/25] chore: computed column should ignore changes with an archive operation --- .../project_revision_project_manager_form_changes_by_label.sql | 1 + 1 file changed, 1 insertion(+) diff --git a/schema/deploy/computed_columns/project_revision_project_manager_form_changes_by_label.sql b/schema/deploy/computed_columns/project_revision_project_manager_form_changes_by_label.sql index 243c10304c..33d477a0cd 100644 --- a/schema/deploy/computed_columns/project_revision_project_manager_form_changes_by_label.sql +++ b/schema/deploy/computed_columns/project_revision_project_manager_form_changes_by_label.sql @@ -12,6 +12,7 @@ $computed_column$ with latest_changes as ( select * from cif.form_change where project_revision_id = $1.id + and form_change.operation != 'archive' order by new_form_data->'projectManagerLabelId', updated_at desc, id desc ) select row(pml.*), row(latest_changes.*) as form_change from latest_changes From 6a48b3ed87d4ec14215a0474bbe6056136593200 Mon Sep 17 00:00:00 2001 From: Dylan Leard Date: Fri, 4 Mar 2022 14:38:29 -0800 Subject: [PATCH 11/25] refactor: move each individual form into a single fragment component --- app/components/Form/ProjectManagerForm.tsx | 231 ++++++++++-------- .../Form/ProjectManagerFormGroup.tsx | 94 +++++++ app/mutations/Manager/addManagerToRevision.ts | 16 +- app/next-env.d.ts | 1 - .../[projectRevision]/index.tsx | 9 +- 5 files changed, 226 insertions(+), 125 deletions(-) create mode 100644 app/components/Form/ProjectManagerFormGroup.tsx diff --git a/app/components/Form/ProjectManagerForm.tsx b/app/components/Form/ProjectManagerForm.tsx index 098aa8d770..8f1f1069ea 100644 --- a/app/components/Form/ProjectManagerForm.tsx +++ b/app/components/Form/ProjectManagerForm.tsx @@ -1,12 +1,11 @@ import { JSONSchema7, JSONSchema7Definition } from "json-schema"; import React, { useRef, useMemo } from "react"; import { graphql, useFragment } from "react-relay"; -import { ProjectManagerForm_query$key } from "__generated__/ProjectManagerForm_query.graphql"; +import { ProjectManagerForm_managerFormChange$key } from "__generated__/ProjectManagerForm_managerFormChange.graphql"; import FormBase from "./FormBase"; import projectManagerSchema from "data/jsonSchemaForm/projectManagerSchema"; import { ValidatingFormProps } from "./Interfaces/FormValidationTypes"; import Grid from "@button-inc/bcgov-theme/Grid"; -import FormBorder from "lib/theme/components/FormBorder"; import { Button } from "@button-inc/bcgov-theme"; import validateFormWithErrors from "lib/helpers/validateFormWithErrors"; import useAddManagerToRevisionMutation from "mutations/Manager/addManagerToRevision"; @@ -14,9 +13,14 @@ import useDeleteManagerFromRevisionMutation from "mutations/Manager/deleteManage import { mutation as updateFormChangeMutation } from "mutations/FormChange/updateFormChange"; import useDebouncedMutation from "mutations/useDebouncedMutation"; import EmptyObjectFieldTemplate from "lib/theme/EmptyObjectFieldTemplate"; +import FieldLabel from "lib/theme/widgets/FieldLabel"; interface Props extends ValidatingFormProps { - query: ProjectManagerForm_query$key; + managerFormChange: ProjectManagerForm_managerFormChange$key; + allCifUsers: any; + projectId: number; + projectRevisionId: string; + projectRevisionRowId: number; } const uiSchema = { @@ -33,43 +37,25 @@ const uiSchema = { const ProjectManagerForm: React.FC = (props) => { const formRefs = useRef({}); - const { allCifUsers, projectRevision } = useFragment( + const { allCifUsers, projectId, projectRevisionId, projectRevisionRowId } = + props; + + const change = useFragment( graphql` - fragment ProjectManagerForm_query on Query { - projectRevision(id: $projectRevision) { + fragment ProjectManagerForm_managerFormChange on ManagerFormChangesByLabelCompositeReturn { + projectManagerLabel { id rowId - managerFormChanges: projectManagerFormChangesByLabel { - edges { - node { - projectManagerLabel { - id - rowId - label - } - formChange { - id - newFormData - } - } - } - } - projectFormChange { - formDataRecordId - } + label } - allCifUsers { - edges { - node { - rowId - firstName - lastName - } - } + formChange { + id + operation + newFormData } } `, - props.query + props.managerFormChange ); // Dynamically build the schema from the list of cif_users @@ -95,22 +81,28 @@ const ProjectManagerForm: React.FC = (props) => { const addManager = (data: any) => { applyAddManagerToRevision({ variables: { - projectRevision: projectRevision.id, - projectRevisionId: projectRevision.rowId, + projectRevision: projectRevisionId, + projectRevisionId: projectRevisionRowId, newFormData: data, }, - }); - }; - - // Delete a manager from the project revision - const [discardFormChange] = useDeleteManagerFromRevisionMutation(); - const deleteManager = (id: string) => { - discardFormChange({ - variables: { - input: { - id: id, + optimisticResponse: { + createFormChange: { + query: { + projectRevision: { + managerFormChanges: { + edges: { + change: { + projectManagerLabel: {}, + formChange: { + id: "new", + newFormData: data, + }, + }, + }, + }, + }, + }, }, - projectRevision: projectRevision.id, }, }); }; @@ -118,20 +110,62 @@ const ProjectManagerForm: React.FC = (props) => { const [applyUpdateFormChangeMutation] = useDebouncedMutation( updateFormChangeMutation ); + + // Delete a manager from the project revision + const [discardFormChange, discardInFlight] = + useDeleteManagerFromRevisionMutation(); + const deleteManager = (id: string) => { + if (change.formChange.operation === "CREATE") + discardFormChange({ + variables: { + input: { + id: id, + }, + projectRevision: projectRevisionId, + }, + onError: (error) => { + console.log(error); + }, + }); + else + applyUpdateFormChangeMutation({ + variables: { + input: { + id: id, + formChangePatch: { + operation: "ARCHIVE", + }, + }, + }, + optimisticResponse: { + updateFormChange: { + formChange: { + id: id, + newFormData: {}, + }, + }, + }, + onError: (error) => { + console.log(error); + }, + debounceKey: id, + }); + }; + // Update an existing project_manager form change if it exists, otherwise create one const createOrUpdateFormChange = ( formChangeId: string, labelId: number, - change: any + formChange: any ) => { const data = { - ...change, + ...formChange, projectManagerLabelId: labelId, - projectId: projectRevision.projectFormChange.formDataRecordId, + projectId: projectId, }; // If a form_change already exists, and the payload contains a cifUserId update it - if (formChangeId && change.cifUserId) { + if (formChangeId && formChange?.cifUserId) { applyUpdateFormChangeMutation({ variables: { input: { @@ -152,11 +186,11 @@ const ProjectManagerForm: React.FC = (props) => { debounceKey: formChangeId, }); // If a form_change does not exist, and the payload contains a cifUserId create a form_change record - } else if (change.cifUserId) { + } else if (formChange?.cifUserId) { addManager(data); } // If a form_change exists, and the payload does not contain a cifUserId delete it - else if (formChangeId && !change.cifUserId) { + else if (formChangeId && !change.cifUserId && !discardInFlight) { deleteManager(formChangeId); } }; @@ -172,63 +206,48 @@ const ProjectManagerForm: React.FC = (props) => { return ( <> - + + + + - - - {projectRevision.managerFormChanges.edges.map(({ node }) => ( - - - - - - - - (formRefs.current[node.projectManagerLabel.id] = el) - } - formData={node.formChange?.newFormData} - onChange={(change) => { - createOrUpdateFormChange( - node.formChange?.id, - node.projectManagerLabel.rowId, - change.formData - ); - }} - schema={managerSchema} - uiSchema={uiSchema} - ObjectFieldTemplate={EmptyObjectFieldTemplate} - /> - - - - - - - ))} - + + + (formRefs.current[change.projectManagerLabel.id] = el) + } + formData={change.formChange?.newFormData} + onChange={(data) => { + createOrUpdateFormChange( + change.formChange?.id, + change.projectManagerLabel.rowId, + data.formData + ); + }} + schema={managerSchema} + uiSchema={uiSchema} + ObjectFieldTemplate={EmptyObjectFieldTemplate} + /> + + + - - + ); }; diff --git a/app/components/Form/ProjectManagerFormGroup.tsx b/app/components/Form/ProjectManagerFormGroup.tsx new file mode 100644 index 0000000000..93a185824b --- /dev/null +++ b/app/components/Form/ProjectManagerFormGroup.tsx @@ -0,0 +1,94 @@ +import { graphql, useFragment } from "react-relay"; +import { ProjectManagerFormGroup_query$key } from "__generated__/ProjectManagerFormGroup_query.graphql"; +import { ProjectManagerFormGroup_revision$key } from "__generated__/ProjectManagerFormGroup_revision.graphql"; +import { ValidatingFormProps } from "./Interfaces/FormValidationTypes"; +import Grid from "@button-inc/bcgov-theme/Grid"; +import FormBorder from "lib/theme/components/FormBorder"; +import ProjectManagerForm from "./ProjectManagerForm"; + +interface Props extends ValidatingFormProps { + query: ProjectManagerFormGroup_query$key; + revision: ProjectManagerFormGroup_revision$key; + projectManagerFormRef: any; +} + +const ProjectManagerFormGroup: React.FC = (props) => { + const { allCifUsers } = useFragment( + graphql` + fragment ProjectManagerFormGroup_query on Query { + allCifUsers { + edges { + node { + rowId + firstName + lastName + } + } + } + } + `, + props.query + ); + + const projectRevision = useFragment( + graphql` + fragment ProjectManagerFormGroup_revision on ProjectRevision { + id + rowId + managerFormChanges: projectManagerFormChangesByLabel { + edges { + node { + projectManagerLabel { + id + } + ...ProjectManagerForm_managerFormChange + } + } + } + projectFormChange { + formDataRecordId + } + } + `, + props.revision + ); + + return ( + <> + + + + + {projectRevision.managerFormChanges.edges.map(({ node }) => ( + + (props.projectManagerFormRef.current = validator) + } + /> + ))} + + + + + + + ); +}; + +export default ProjectManagerFormGroup; diff --git a/app/mutations/Manager/addManagerToRevision.ts b/app/mutations/Manager/addManagerToRevision.ts index 663a5ffc60..3ce71d9256 100644 --- a/app/mutations/Manager/addManagerToRevision.ts +++ b/app/mutations/Manager/addManagerToRevision.ts @@ -21,21 +21,7 @@ export const mutation = graphql` ) { query { projectRevision(id: $projectRevision) { - managerFormChanges: projectManagerFormChangesByLabel { - edges { - node { - projectManagerLabel { - id - rowId - label - } - formChange { - id - newFormData - } - } - } - } + ...ProjectManagerFormGroup_revision } } } diff --git a/app/next-env.d.ts b/app/next-env.d.ts index 9bc3dd46b9..4f11a03dc6 100644 --- a/app/next-env.d.ts +++ b/app/next-env.d.ts @@ -1,5 +1,4 @@ /// -/// /// // NOTE: This file should not be edited diff --git a/app/pages/cif/project-revision/[projectRevision]/index.tsx b/app/pages/cif/project-revision/[projectRevision]/index.tsx index c513e34bf1..bca1913b12 100644 --- a/app/pages/cif/project-revision/[projectRevision]/index.tsx +++ b/app/pages/cif/project-revision/[projectRevision]/index.tsx @@ -10,7 +10,7 @@ import Grid from "@button-inc/bcgov-theme/Grid"; import { useMemo, useRef } from "react"; import useDebouncedMutation from "mutations/useDebouncedMutation"; import SavingIndicator from "components/Form/SavingIndicator"; -import ProjecManagerForm from "components/Form/ProjectManagerForm"; +import ProjecManagerFormGroup from "components/Form/ProjectManagerFormGroup"; import ProjectForm from "components/Form/ProjectForm"; import { mutation as updateProjectRevisionMutation } from "mutations/ProjectRevision/updateProjectRevision"; import { useDeleteProjectRevisionMutation } from "mutations/ProjectRevision/deleteProjectRevision"; @@ -33,9 +33,10 @@ const pageQuery = graphql` id newFormData } + ...ProjectManagerFormGroup_revision } ...ProjectForm_query - ...ProjectManagerForm_query + ...ProjectManagerFormGroup_query ...ProjectContactForm_query } } @@ -167,8 +168,10 @@ export function ProjectRevision({ /> - (projectManagerFormRef.current = validator) } From 604b619a82123329740d9bc9b33aea8740c8808d Mon Sep 17 00:00:00 2001 From: Dylan Leard Date: Fri, 4 Mar 2022 14:50:22 -0800 Subject: [PATCH 12/25] chore: fix validation refs --- app/components/Form/ProjectManagerForm.tsx | 29 ++++++++----------- .../Form/ProjectManagerFormGroup.tsx | 16 ++++++++-- 2 files changed, 25 insertions(+), 20 deletions(-) diff --git a/app/components/Form/ProjectManagerForm.tsx b/app/components/Form/ProjectManagerForm.tsx index 8f1f1069ea..90039fc68b 100644 --- a/app/components/Form/ProjectManagerForm.tsx +++ b/app/components/Form/ProjectManagerForm.tsx @@ -1,13 +1,12 @@ import { JSONSchema7, JSONSchema7Definition } from "json-schema"; -import React, { useRef, useMemo } from "react"; +import React, { useMemo } from "react"; import { graphql, useFragment } from "react-relay"; import { ProjectManagerForm_managerFormChange$key } from "__generated__/ProjectManagerForm_managerFormChange.graphql"; import FormBase from "./FormBase"; import projectManagerSchema from "data/jsonSchemaForm/projectManagerSchema"; -import { ValidatingFormProps } from "./Interfaces/FormValidationTypes"; +import FormComponentProps from "./Interfaces/FormComponentProps"; import Grid from "@button-inc/bcgov-theme/Grid"; import { Button } from "@button-inc/bcgov-theme"; -import validateFormWithErrors from "lib/helpers/validateFormWithErrors"; import useAddManagerToRevisionMutation from "mutations/Manager/addManagerToRevision"; import useDeleteManagerFromRevisionMutation from "mutations/Manager/deleteManagerFromRevision"; import { mutation as updateFormChangeMutation } from "mutations/FormChange/updateFormChange"; @@ -15,12 +14,13 @@ import useDebouncedMutation from "mutations/useDebouncedMutation"; import EmptyObjectFieldTemplate from "lib/theme/EmptyObjectFieldTemplate"; import FieldLabel from "lib/theme/widgets/FieldLabel"; -interface Props extends ValidatingFormProps { +interface Props extends FormComponentProps { managerFormChange: ProjectManagerForm_managerFormChange$key; allCifUsers: any; projectId: number; projectRevisionId: string; projectRevisionRowId: number; + formRefs: any; } const uiSchema = { @@ -36,9 +36,13 @@ const uiSchema = { }; const ProjectManagerForm: React.FC = (props) => { - const formRefs = useRef({}); - const { allCifUsers, projectId, projectRevisionId, projectRevisionRowId } = - props; + const { + allCifUsers, + projectId, + projectRevisionId, + projectRevisionRowId, + formRefs, + } = props; const change = useFragment( graphql` @@ -190,20 +194,11 @@ const ProjectManagerForm: React.FC = (props) => { addManager(data); } // If a form_change exists, and the payload does not contain a cifUserId delete it - else if (formChangeId && !change.cifUserId && !discardInFlight) { + else if (formChangeId && !formChange.cifUserId && !discardInFlight) { deleteManager(formChangeId); } }; - props.setValidatingForm({ - selfValidate: () => { - return Object.keys(formRefs.current).reduce((agg, formId) => { - const formObject = formRefs.current[formId]; - return [...agg, ...validateFormWithErrors(formObject)]; - }, []); - }, - }); - return ( <> diff --git a/app/components/Form/ProjectManagerFormGroup.tsx b/app/components/Form/ProjectManagerFormGroup.tsx index 93a185824b..9a28284fae 100644 --- a/app/components/Form/ProjectManagerFormGroup.tsx +++ b/app/components/Form/ProjectManagerFormGroup.tsx @@ -1,7 +1,9 @@ +import { useRef } from "react"; import { graphql, useFragment } from "react-relay"; import { ProjectManagerFormGroup_query$key } from "__generated__/ProjectManagerFormGroup_query.graphql"; import { ProjectManagerFormGroup_revision$key } from "__generated__/ProjectManagerFormGroup_revision.graphql"; import { ValidatingFormProps } from "./Interfaces/FormValidationTypes"; +import validateFormWithErrors from "lib/helpers/validateFormWithErrors"; import Grid from "@button-inc/bcgov-theme/Grid"; import FormBorder from "lib/theme/components/FormBorder"; import ProjectManagerForm from "./ProjectManagerForm"; @@ -13,6 +15,7 @@ interface Props extends ValidatingFormProps { } const ProjectManagerFormGroup: React.FC = (props) => { + const formRefs = useRef({}); const { allCifUsers } = useFragment( graphql` fragment ProjectManagerFormGroup_query on Query { @@ -53,6 +56,15 @@ const ProjectManagerFormGroup: React.FC = (props) => { props.revision ); + props.setValidatingForm({ + selfValidate: () => { + return Object.keys(formRefs.current).reduce((agg, formId) => { + const formObject = formRefs.current[formId]; + return [...agg, ...validateFormWithErrors(formObject)]; + }, []); + }, + }); + return ( <> @@ -67,9 +79,7 @@ const ProjectManagerFormGroup: React.FC = (props) => { projectId={projectRevision.projectFormChange.formDataRecordId} projectRevisionId={projectRevision.id} projectRevisionRowId={projectRevision.rowId} - setValidatingForm={(validator) => - (props.projectManagerFormRef.current = validator) - } + formRefs={formRefs} /> ))} From 38ecd717f1c72858b2b296c8bb748392be35a964 Mon Sep 17 00:00:00 2001 From: Dylan Leard Date: Mon, 7 Mar 2022 10:27:12 -0800 Subject: [PATCH 13/25] chore: properly type typescript 'any' types --- app/components/Form/ProjectManagerForm.tsx | 18 +++++++++++++----- .../Form/ProjectManagerFormGroup.tsx | 3 ++- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/app/components/Form/ProjectManagerForm.tsx b/app/components/Form/ProjectManagerForm.tsx index 90039fc68b..8fc5e2571d 100644 --- a/app/components/Form/ProjectManagerForm.tsx +++ b/app/components/Form/ProjectManagerForm.tsx @@ -1,5 +1,5 @@ import { JSONSchema7, JSONSchema7Definition } from "json-schema"; -import React, { useMemo } from "react"; +import React, { useMemo, MutableRefObject } from "react"; import { graphql, useFragment } from "react-relay"; import { ProjectManagerForm_managerFormChange$key } from "__generated__/ProjectManagerForm_managerFormChange.graphql"; import FormBase from "./FormBase"; @@ -16,11 +16,19 @@ import FieldLabel from "lib/theme/widgets/FieldLabel"; interface Props extends FormComponentProps { managerFormChange: ProjectManagerForm_managerFormChange$key; - allCifUsers: any; + allCifUsers: { + edges: ReadonlyArray<{ + readonly node: { + readonly rowId: number; + readonly firstName: string | null; + readonly lastName: string | null; + }; + }>; + }; projectId: number; projectRevisionId: string; projectRevisionRowId: number; - formRefs: any; + formRefs: MutableRefObject<{}>; } const uiSchema = { @@ -82,7 +90,7 @@ const ProjectManagerForm: React.FC = (props) => { // Add a manager to the project revision const [applyAddManagerToRevision] = useAddManagerToRevisionMutation(); - const addManager = (data: any) => { + const addManager = (data: JSON) => { applyAddManagerToRevision({ variables: { projectRevision: projectRevisionId, @@ -160,7 +168,7 @@ const ProjectManagerForm: React.FC = (props) => { const createOrUpdateFormChange = ( formChangeId: string, labelId: number, - formChange: any + formChange: { cifUserId: number } ) => { const data = { ...formChange, diff --git a/app/components/Form/ProjectManagerFormGroup.tsx b/app/components/Form/ProjectManagerFormGroup.tsx index 9a28284fae..8fe5f1392f 100644 --- a/app/components/Form/ProjectManagerFormGroup.tsx +++ b/app/components/Form/ProjectManagerFormGroup.tsx @@ -7,11 +7,12 @@ import validateFormWithErrors from "lib/helpers/validateFormWithErrors"; import Grid from "@button-inc/bcgov-theme/Grid"; import FormBorder from "lib/theme/components/FormBorder"; import ProjectManagerForm from "./ProjectManagerForm"; +import { ISupportExternalValidation } from "components/Form/Interfaces/FormValidationTypes"; interface Props extends ValidatingFormProps { query: ProjectManagerFormGroup_query$key; revision: ProjectManagerFormGroup_revision$key; - projectManagerFormRef: any; + projectManagerFormRef: { selfValidate: () => ISupportExternalValidation }; } const ProjectManagerFormGroup: React.FC = (props) => { From d84b2ff2ce6c93f2a0cc1fd67afbec4ad4703639 Mon Sep 17 00:00:00 2001 From: Dylan Leard Date: Mon, 7 Mar 2022 16:30:25 -0800 Subject: [PATCH 14/25] test: update revision page unit test --- .../[projectRevision]/index.test.tsx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/app/tests/unit/pages/project-revision/[projectRevision]/index.test.tsx b/app/tests/unit/pages/project-revision/[projectRevision]/index.test.tsx index 39780c92cf..d1c79c2c68 100644 --- a/app/tests/unit/pages/project-revision/[projectRevision]/index.test.tsx +++ b/app/tests/unit/pages/project-revision/[projectRevision]/index.test.tsx @@ -9,12 +9,12 @@ import compiledProjectRevisionQuery, { ProjectRevisionQuery, } from "__generated__/ProjectRevisionQuery.graphql"; import ProjectForm from "components/Form/ProjectForm"; -import ProjectManagerForm from "components/Form/ProjectManagerForm"; +import ProjectManagerFormGroup from "components/Form/ProjectManagerFormGroup"; import ProjectContactForm from "components/Form/ProjectContactForm"; import { mocked } from "jest-mock"; jest.mock("components/Form/ProjectForm"); -jest.mock("components/Form/ProjectManagerForm"); +jest.mock("components/Form/ProjectManagerFormGroup"); jest.mock("components/Form/ProjectContactForm"); const environment = createMockEnvironment(); @@ -66,11 +66,11 @@ const generateMockForm = (errors: any[] = []) => { describe("The Create Project page", () => { beforeEach(() => { mocked(ProjectForm).render.mockReset(); - mocked(ProjectManagerForm).mockReset(); + mocked(ProjectManagerFormGroup).mockReset(); mocked(ProjectContactForm).mockReset(); mocked(ProjectForm).render.mockImplementation(() => null); - mocked(ProjectManagerForm).mockImplementation((props) => { + mocked(ProjectManagerFormGroup).mockImplementation((props) => { props.setValidatingForm({ selfValidate: jest.fn().mockImplementation(() => []), }); @@ -152,7 +152,7 @@ describe("The Create Project page", () => { it("calls the updateProjectRevision mutation when the Submit Button is clicked & input values are valid", async () => { mocked(ProjectForm).render.mockImplementation(generateMockForm()); - mocked(ProjectManagerForm).mockImplementation((props) => { + mocked(ProjectManagerFormGroup).mockImplementation((props) => { props.setValidatingForm({ selfValidate: jest.fn().mockImplementation(() => []), }); @@ -196,7 +196,7 @@ describe("The Create Project page", () => { mocked(ProjectForm).render.mockImplementation( generateMockForm([{ thereIsAnError: true }]) ); - mocked(ProjectManagerForm).mockImplementation((props) => { + mocked(ProjectManagerFormGroup).mockImplementation((props) => { props.setValidatingForm({ selfValidate: jest.fn().mockImplementation(() => []), }); From a9f45798cfcdb0053c0a03a7bb1df9ebbc38efab Mon Sep 17 00:00:00 2001 From: Dylan Leard Date: Tue, 8 Mar 2022 13:21:57 -0800 Subject: [PATCH 15/25] test: add ProjectManager unit tests --- .../Form/ProjectManagerForm.test.tsx | 105 ++++++++++++++++-- 1 file changed, 96 insertions(+), 9 deletions(-) diff --git a/app/tests/unit/components/Form/ProjectManagerForm.test.tsx b/app/tests/unit/components/Form/ProjectManagerForm.test.tsx index dd6e5acf26..79442a14e9 100644 --- a/app/tests/unit/components/Form/ProjectManagerForm.test.tsx +++ b/app/tests/unit/components/Form/ProjectManagerForm.test.tsx @@ -1,6 +1,6 @@ import { render, screen, fireEvent } from "@testing-library/react"; import { ValidatingFormProps } from "components/Form/Interfaces/FormValidationTypes"; -import ProjectManagerForm from "components/Form/ProjectManagerForm"; +import ProjectManagerFormGroup from "components/Form/ProjectManagerFormGroup"; import { graphql, RelayEnvironmentProvider, @@ -19,7 +19,10 @@ const loadedQuery = graphql` query ProjectManagerFormQuery($projectRevision: ID!) @relay_test_operation { query { # Spread the fragment you want to test here - ...ProjectManagerForm_query + ...ProjectManagerFormGroup_query + projectRevision(id: $projectRevision) { + ...ProjectManagerFormGroup_revision + } } } `; @@ -33,7 +36,14 @@ const TestRenderer = () => { const data = useLazyLoadQuery(loadedQuery, { projectRevision: "test-project-revision", }); - return ; + return ( + + ); }; const renderProjectForm = () => { return render( @@ -70,6 +80,7 @@ const getMockQueryPayload = () => ({ }, formChange: { id: "Change 2 ID", + operation: "CREATE", newFormData: { cifUserId: 2, projectId: 1, @@ -78,6 +89,24 @@ const getMockQueryPayload = () => ({ }, }, }, + { + node: { + projectManagerLabel: { + id: "Test Label 3 ID", + rowId: 3, + label: "Test Label 3", + }, + formChange: { + id: "Change 3 ID", + operation: "UPDATE", + newFormData: { + cifUserId: 2, + projectId: 1, + projectManagerLabelId: 3, + }, + }, + }, + }, ], }, projectFormChange: { @@ -121,7 +150,7 @@ describe("The ProjectManagerForm", () => { it("Renders a form for each Project Manager Label", () => { renderProjectForm(); - expect(screen.getAllByRole("textbox")).toHaveLength(2); + expect(screen.getAllByRole("textbox")).toHaveLength(3); }); it("Renders any data contained in a formChange", () => { @@ -143,6 +172,29 @@ describe("The ProjectManagerForm", () => { fireEvent.click(screen.getByText("Test First Name 1 Test Last Name 1")); expect(mutationSpy).toHaveBeenCalledWith({ + optimisticResponse: { + createFormChange: { + query: { + projectRevision: { + managerFormChanges: { + edges: { + change: { + projectManagerLabel: {}, + formChange: { + id: "new", + newFormData: { + cifUserId: 1, + projectId: 1, + projectManagerLabelId: 1, + }, + }, + }, + }, + }, + }, + }, + }, + }, variables: { projectRevision: "Test Revision ID", projectRevisionId: 1, @@ -151,17 +203,19 @@ describe("The ProjectManagerForm", () => { }); }); - it("Calls the deleteFormChange mutation when the remove button is clicked", () => { - const mutationSpy = jest.fn(); + it("Deletes the formChange record when the remove button is clicked and the formChange operation is 'CREATE'", () => { + const deleteMutationSpy = jest.fn(); + const inFlight = false; jest .spyOn(require("react-relay"), "useMutation") - .mockImplementation(() => [mutationSpy, jest.fn()]); + .mockImplementation(() => [deleteMutationSpy, inFlight]); renderProjectForm(); const clearButton = screen.getAllByText("Clear")[1]; clearButton.click(); - expect(mutationSpy).toHaveBeenCalledWith({ + expect(deleteMutationSpy).toHaveBeenCalledWith({ + onError: expect.any(Function), variables: { input: { id: "Change 2 ID", @@ -171,6 +225,39 @@ describe("The ProjectManagerForm", () => { }); }); + it("Updates the formChange record with operation = 'ARCHIVE' when the remove button is clicked and the formChange operation is 'UPDATE'", () => { + const deleteMutationSpy = jest.fn(); + const inFlight = false; + jest + .spyOn(require("react-relay"), "useMutation") + .mockImplementation(() => [deleteMutationSpy, inFlight]); + + renderProjectForm(); + const clearButton = screen.getAllByText("Clear")[2]; + clearButton.click(); + + expect(deleteMutationSpy).toHaveBeenCalledWith({ + onError: expect.any(Function), + variables: { + input: { + formChangePatch: { + operation: "ARCHIVE", + }, + id: "Change 3 ID", + }, + }, + optimisticResponse: { + updateFormChange: { + formChange: { + id: "Change 3 ID", + newFormData: {}, + }, + }, + }, + debounceKey: "Change 3 ID", + }); + }); + it("Validates all Manager forms when validator is called", () => { mocked(validateFormWithErrors).mockImplementation(() => []); jest @@ -186,6 +273,6 @@ describe("The ProjectManagerForm", () => { props.setValidatingForm.mock.calls[0][0].selfValidate(); // Once per form - expect(mocked(validateFormWithErrors)).toHaveBeenCalledTimes(2); + expect(mocked(validateFormWithErrors)).toHaveBeenCalledTimes(3); }); }); From 9127dabf22daaea1713a7258ca59cc77d9f59e10 Mon Sep 17 00:00:00 2001 From: Dylan Leard Date: Tue, 8 Mar 2022 13:29:08 -0800 Subject: [PATCH 16/25] test: add test for update mutation --- .../Form/ProjectManagerForm.test.tsx | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/app/tests/unit/components/Form/ProjectManagerForm.test.tsx b/app/tests/unit/components/Form/ProjectManagerForm.test.tsx index 79442a14e9..21e2c7e05d 100644 --- a/app/tests/unit/components/Form/ProjectManagerForm.test.tsx +++ b/app/tests/unit/components/Form/ProjectManagerForm.test.tsx @@ -203,6 +203,46 @@ describe("The ProjectManagerForm", () => { }); }); + it("Calls the update mutation when change is made in the Manager dropdown", () => { + const mutationSpy = jest.fn(); + jest + .spyOn(require("react-relay"), "useMutation") + .mockImplementation(() => [mutationSpy, jest.fn()]); + + renderProjectForm(); + + fireEvent.click(screen.getAllByTitle("Open")[1]); + fireEvent.click(screen.getByText("Test First Name 1 Test Last Name 1")); + + expect(mutationSpy).toHaveBeenCalledWith({ + debounceKey: "Change 2 ID", + optimisticResponse: { + updateFormChange: { + formChange: { + id: "Change 2 ID", + newFormData: { + cifUserId: 1, + projectId: 1, + projectManagerLabelId: 2, + }, + }, + }, + }, + variables: { + input: { + formChangePatch: { + newFormData: { + cifUserId: 1, + projectId: 1, + projectManagerLabelId: 2, + }, + }, + id: "Change 2 ID", + }, + }, + }); + }); + it("Deletes the formChange record when the remove button is clicked and the formChange operation is 'CREATE'", () => { const deleteMutationSpy = jest.fn(); const inFlight = false; From 4f70271d872b93b5fec7994896d54f775a31e718 Mon Sep 17 00:00:00 2001 From: Dylan Leard Date: Tue, 8 Mar 2022 13:45:50 -0800 Subject: [PATCH 17/25] chore: fix incorrect interface types --- app/components/Form/ProjectManagerForm.tsx | 6 +++++- app/components/Form/ProjectManagerFormGroup.tsx | 5 ++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/app/components/Form/ProjectManagerForm.tsx b/app/components/Form/ProjectManagerForm.tsx index 8fc5e2571d..9d218d0e53 100644 --- a/app/components/Form/ProjectManagerForm.tsx +++ b/app/components/Form/ProjectManagerForm.tsx @@ -90,7 +90,11 @@ const ProjectManagerForm: React.FC = (props) => { // Add a manager to the project revision const [applyAddManagerToRevision] = useAddManagerToRevisionMutation(); - const addManager = (data: JSON) => { + const addManager = (data: { + cifUserId: number; + projectManagerLabelId: number; + projectId: number; + }) => { applyAddManagerToRevision({ variables: { projectRevision: projectRevisionId, diff --git a/app/components/Form/ProjectManagerFormGroup.tsx b/app/components/Form/ProjectManagerFormGroup.tsx index 8fe5f1392f..1a1cafa918 100644 --- a/app/components/Form/ProjectManagerFormGroup.tsx +++ b/app/components/Form/ProjectManagerFormGroup.tsx @@ -1,4 +1,4 @@ -import { useRef } from "react"; +import { useRef, MutableRefObject } from "react"; import { graphql, useFragment } from "react-relay"; import { ProjectManagerFormGroup_query$key } from "__generated__/ProjectManagerFormGroup_query.graphql"; import { ProjectManagerFormGroup_revision$key } from "__generated__/ProjectManagerFormGroup_revision.graphql"; @@ -7,12 +7,11 @@ import validateFormWithErrors from "lib/helpers/validateFormWithErrors"; import Grid from "@button-inc/bcgov-theme/Grid"; import FormBorder from "lib/theme/components/FormBorder"; import ProjectManagerForm from "./ProjectManagerForm"; -import { ISupportExternalValidation } from "components/Form/Interfaces/FormValidationTypes"; interface Props extends ValidatingFormProps { query: ProjectManagerFormGroup_query$key; revision: ProjectManagerFormGroup_revision$key; - projectManagerFormRef: { selfValidate: () => ISupportExternalValidation }; + projectManagerFormRef: MutableRefObject<{}>; } const ProjectManagerFormGroup: React.FC = (props) => { From 6e681253e2af6239f4588d235638d0d2f9aa0830 Mon Sep 17 00:00:00 2001 From: Dylan Leard Date: Tue, 8 Mar 2022 13:59:20 -0800 Subject: [PATCH 18/25] test: update cypress test --- app/cypress/integration/cif/project-revision/index.spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/cypress/integration/cif/project-revision/index.spec.js b/app/cypress/integration/cif/project-revision/index.spec.js index ad8cd72465..053d42f506 100644 --- a/app/cypress/integration/cif/project-revision/index.spec.js +++ b/app/cypress/integration/cif/project-revision/index.spec.js @@ -53,7 +53,7 @@ describe("the new project page", () => { component: "Project Page with errors", variant: "empty", }); - cy.get(".error-detail").should("have.length", 8); + cy.get(".error-detail").should("have.length", 11); // Renders a custom error message for a custom format validation error cy.get(".error-detail") .first() From 295fa62b8c7c4b2d9dda66d23a426a6311f714c2 Mon Sep 17 00:00:00 2001 From: Dylan Leard Date: Tue, 8 Mar 2022 16:43:42 -0800 Subject: [PATCH 19/25] chore: project manager fields are optional --- app/data/jsonSchemaForm/projectManagerSchema.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/app/data/jsonSchemaForm/projectManagerSchema.ts b/app/data/jsonSchemaForm/projectManagerSchema.ts index e4fc5d515a..f1a92927c3 100644 --- a/app/data/jsonSchemaForm/projectManagerSchema.ts +++ b/app/data/jsonSchemaForm/projectManagerSchema.ts @@ -2,7 +2,6 @@ const projectManagerSchema = { $schema: "http://json-schema.org/draft-07/schema", type: "object", title: "Project Manager", - required: ["cifUserId"], properties: { cifUserId: { type: "number", From c82b5c087b60983c4d515e7e0d80b21a21057a6a Mon Sep 17 00:00:00 2001 From: Dylan Leard Date: Tue, 8 Mar 2022 16:44:25 -0800 Subject: [PATCH 20/25] test: update cypress test --- app/cypress/integration/cif/project-revision/index.spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/cypress/integration/cif/project-revision/index.spec.js b/app/cypress/integration/cif/project-revision/index.spec.js index 053d42f506..895ea32f92 100644 --- a/app/cypress/integration/cif/project-revision/index.spec.js +++ b/app/cypress/integration/cif/project-revision/index.spec.js @@ -53,7 +53,7 @@ describe("the new project page", () => { component: "Project Page with errors", variant: "empty", }); - cy.get(".error-detail").should("have.length", 11); + cy.get(".error-detail").should("have.length", 7); // Renders a custom error message for a custom format validation error cy.get(".error-detail") .first() From a2607ae7970698e392131a8c411d26d6a9e459de Mon Sep 17 00:00:00 2001 From: Dylan Leard Date: Wed, 9 Mar 2022 09:12:08 -0800 Subject: [PATCH 21/25] refactor: move allCifUsers fragment into component that uses it --- app/components/Form/ProjectManagerForm.tsx | 30 ++++++++++++------- .../Form/ProjectManagerFormGroup.tsx | 22 +++++--------- .../jsonSchemaForm/projectManagerSchema.ts | 5 ++++ 3 files changed, 32 insertions(+), 25 deletions(-) diff --git a/app/components/Form/ProjectManagerForm.tsx b/app/components/Form/ProjectManagerForm.tsx index 9d218d0e53..c6ce1d9646 100644 --- a/app/components/Form/ProjectManagerForm.tsx +++ b/app/components/Form/ProjectManagerForm.tsx @@ -2,6 +2,7 @@ import { JSONSchema7, JSONSchema7Definition } from "json-schema"; import React, { useMemo, MutableRefObject } from "react"; import { graphql, useFragment } from "react-relay"; import { ProjectManagerForm_managerFormChange$key } from "__generated__/ProjectManagerForm_managerFormChange.graphql"; +import { ProjectManagerForm_query$key } from "__generated__/ProjectManagerFormG_query.graphql"; import FormBase from "./FormBase"; import projectManagerSchema from "data/jsonSchemaForm/projectManagerSchema"; import FormComponentProps from "./Interfaces/FormComponentProps"; @@ -16,15 +17,7 @@ import FieldLabel from "lib/theme/widgets/FieldLabel"; interface Props extends FormComponentProps { managerFormChange: ProjectManagerForm_managerFormChange$key; - allCifUsers: { - edges: ReadonlyArray<{ - readonly node: { - readonly rowId: number; - readonly firstName: string | null; - readonly lastName: string | null; - }; - }>; - }; + query: ProjectManagerForm_query$key; projectId: number; projectRevisionId: string; projectRevisionRowId: number; @@ -45,13 +38,30 @@ const uiSchema = { const ProjectManagerForm: React.FC = (props) => { const { - allCifUsers, + query, projectId, projectRevisionId, projectRevisionRowId, formRefs, } = props; + const { allCifUsers } = useFragment( + graphql` + fragment ProjectManagerForm_query on Query { + allCifUsers { + edges { + node { + rowId + firstName + lastName + } + } + } + } + `, + query + ); + const change = useFragment( graphql` fragment ProjectManagerForm_managerFormChange on ManagerFormChangesByLabelCompositeReturn { diff --git a/app/components/Form/ProjectManagerFormGroup.tsx b/app/components/Form/ProjectManagerFormGroup.tsx index 1a1cafa918..6f428258f2 100644 --- a/app/components/Form/ProjectManagerFormGroup.tsx +++ b/app/components/Form/ProjectManagerFormGroup.tsx @@ -16,18 +16,10 @@ interface Props extends ValidatingFormProps { const ProjectManagerFormGroup: React.FC = (props) => { const formRefs = useRef({}); - const { allCifUsers } = useFragment( + const query = useFragment( graphql` fragment ProjectManagerFormGroup_query on Query { - allCifUsers { - edges { - node { - rowId - firstName - lastName - } - } - } + ...ProjectManagerForm_query } `, props.query @@ -66,7 +58,7 @@ const ProjectManagerFormGroup: React.FC = (props) => { }); return ( - <> +
@@ -75,7 +67,7 @@ const ProjectManagerFormGroup: React.FC = (props) => { = (props) => { - +
); }; diff --git a/app/data/jsonSchemaForm/projectManagerSchema.ts b/app/data/jsonSchemaForm/projectManagerSchema.ts index f1a92927c3..6edda03b3f 100644 --- a/app/data/jsonSchemaForm/projectManagerSchema.ts +++ b/app/data/jsonSchemaForm/projectManagerSchema.ts @@ -2,6 +2,11 @@ const projectManagerSchema = { $schema: "http://json-schema.org/draft-07/schema", type: "object", title: "Project Manager", + /* + Despite the cifUserId being required in the database for the project_manager table, we are not requiring it in this schema. + This is because we create an empty dropdown select form for each project_manager_label record, + where a selection is not required to create a project. So we don't want to validate the cifUserId as required here. + */ properties: { cifUserId: { type: "number", From db173cfe508313d1b6d3c9b211d8d037adda9945 Mon Sep 17 00:00:00 2001 From: Dylan Leard Date: Wed, 9 Mar 2022 09:21:12 -0800 Subject: [PATCH 22/25] chore: fix typo --- app/components/Form/ProjectManagerForm.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/components/Form/ProjectManagerForm.tsx b/app/components/Form/ProjectManagerForm.tsx index c6ce1d9646..aff894a1c8 100644 --- a/app/components/Form/ProjectManagerForm.tsx +++ b/app/components/Form/ProjectManagerForm.tsx @@ -2,7 +2,7 @@ import { JSONSchema7, JSONSchema7Definition } from "json-schema"; import React, { useMemo, MutableRefObject } from "react"; import { graphql, useFragment } from "react-relay"; import { ProjectManagerForm_managerFormChange$key } from "__generated__/ProjectManagerForm_managerFormChange.graphql"; -import { ProjectManagerForm_query$key } from "__generated__/ProjectManagerFormG_query.graphql"; +import { ProjectManagerForm_query$key } from "__generated__/ProjectManagerForm_query.graphql"; import FormBase from "./FormBase"; import projectManagerSchema from "data/jsonSchemaForm/projectManagerSchema"; import FormComponentProps from "./Interfaces/FormComponentProps"; From 43cbcccf5583380da18249315bb7fec6e659a805 Mon Sep 17 00:00:00 2001 From: Dylan Leard Date: Wed, 9 Mar 2022 09:48:44 -0800 Subject: [PATCH 23/25] chore: remove unnecessary fragment --- app/components/Form/ProjectManagerForm.tsx | 80 ++++++++++------------ 1 file changed, 38 insertions(+), 42 deletions(-) diff --git a/app/components/Form/ProjectManagerForm.tsx b/app/components/Form/ProjectManagerForm.tsx index aff894a1c8..01fd8e69d3 100644 --- a/app/components/Form/ProjectManagerForm.tsx +++ b/app/components/Form/ProjectManagerForm.tsx @@ -222,50 +222,46 @@ const ProjectManagerForm: React.FC = (props) => { }; return ( - <> - - - + + + + + + (formRefs.current[change.projectManagerLabel.id] = el)} + formData={change.formChange?.newFormData} + onChange={(data) => { + createOrUpdateFormChange( + change.formChange?.id, + change.projectManagerLabel.rowId, + data.formData + ); + }} + schema={managerSchema} uiSchema={uiSchema} + ObjectFieldTemplate={EmptyObjectFieldTemplate} /> - - - - - (formRefs.current[change.projectManagerLabel.id] = el) - } - formData={change.formChange?.newFormData} - onChange={(data) => { - createOrUpdateFormChange( - change.formChange?.id, - change.projectManagerLabel.rowId, - data.formData - ); - }} - schema={managerSchema} - uiSchema={uiSchema} - ObjectFieldTemplate={EmptyObjectFieldTemplate} - /> - - - - - - - +
+ + + +
+ ); }; From 6c0b00ffa75585e40c2187fb6d1d381bf810e6b9 Mon Sep 17 00:00:00 2001 From: Dylan Leard Date: Wed, 9 Mar 2022 09:54:13 -0800 Subject: [PATCH 24/25] chore: remove unnecessary React.Fragment --- app/components/Form/ProjectManagerForm.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/components/Form/ProjectManagerForm.tsx b/app/components/Form/ProjectManagerForm.tsx index 01fd8e69d3..148e8d1e62 100644 --- a/app/components/Form/ProjectManagerForm.tsx +++ b/app/components/Form/ProjectManagerForm.tsx @@ -222,7 +222,7 @@ const ProjectManagerForm: React.FC = (props) => { }; return ( - + <> = (props) => {
- + ); }; From 60f8e08fed128e4e9cc91f23fd9a87e67ecf67fe Mon Sep 17 00:00:00 2001 From: Dylan Leard Date: Wed, 9 Mar 2022 09:54:57 -0800 Subject: [PATCH 25/25] chore: remove unnecessary React import --- app/components/Form/ProjectManagerForm.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/components/Form/ProjectManagerForm.tsx b/app/components/Form/ProjectManagerForm.tsx index 148e8d1e62..f279ed68e6 100644 --- a/app/components/Form/ProjectManagerForm.tsx +++ b/app/components/Form/ProjectManagerForm.tsx @@ -1,5 +1,5 @@ import { JSONSchema7, JSONSchema7Definition } from "json-schema"; -import React, { useMemo, MutableRefObject } from "react"; +import { useMemo, MutableRefObject } from "react"; import { graphql, useFragment } from "react-relay"; import { ProjectManagerForm_managerFormChange$key } from "__generated__/ProjectManagerForm_managerFormChange.graphql"; import { ProjectManagerForm_query$key } from "__generated__/ProjectManagerForm_query.graphql";