Skip to content

Commit

Permalink
Merge pull request #304 from bcgov/feat/multiple-project-managers
Browse files Browse the repository at this point in the history
Feat: multiple project managers
  • Loading branch information
matthieu-foucault committed Mar 9, 2022
2 parents d72ef66 + 60f8e08 commit e9dd666
Show file tree
Hide file tree
Showing 21 changed files with 770 additions and 191 deletions.
242 changes: 223 additions & 19 deletions app/components/Form/ProjectManagerForm.tsx
Original file line number Diff line number Diff line change
@@ -1,32 +1,53 @@
import type { JSONSchema7 } from "json-schema";
import React, { forwardRef, useMemo } from "react";
import { JSONSchema7, JSONSchema7Definition } from "json-schema";
import { useMemo, MutableRefObject } from "react";
import { graphql, useFragment } from "react-relay";
import { ProjectManagerForm_allUsers$key } from "__generated__/ProjectManagerForm_allUsers.graphql";
import { ProjectManagerForm_managerFormChange$key } from "__generated__/ProjectManagerForm_managerFormChange.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";
import Grid from "@button-inc/bcgov-theme/Grid";
import { Button } from "@button-inc/bcgov-theme";
import useAddManagerToRevisionMutation from "mutations/Manager/addManagerToRevision";
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";
import FieldLabel from "lib/theme/widgets/FieldLabel";

interface Props extends FormComponentProps {
allUsers: ProjectManagerForm_allUsers$key;
managerFormChange: ProjectManagerForm_managerFormChange$key;
query: ProjectManagerForm_query$key;
projectId: number;
projectRevisionId: string;
projectRevisionRowId: number;
formRefs: MutableRefObject<{}>;
}

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<any, Props> = (
props,
ref
) => {
const ProjectManagerForm: React.FC<Props> = (props) => {
const {
query,
projectId,
projectRevisionId,
projectRevisionRowId,
formRefs,
} = props;

const { allCifUsers } = useFragment(
graphql`
fragment ProjectManagerForm_allUsers on Query {
fragment ProjectManagerForm_query on Query {
allCifUsers {
edges {
node {
Expand All @@ -38,27 +59,210 @@ const ProjecManagerForm: React.ForwardRefRenderFunction<any, Props> = (
}
}
`,
props.allUsers
query
);

const schema: JSONSchema7 = useMemo(() => {
const initialSchema = projectManagerSchema;
const change = useFragment(
graphql`
fragment ProjectManagerForm_managerFormChange on ManagerFormChangesByLabelCompositeReturn {
projectManagerLabel {
id
rowId
label
}
formChange {
id
operation
newFormData
}
}
`,
props.managerFormChange
);

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 <FormBase {...props} ref={ref} schema={schema} uiSchema={uiSchema} />;
// Add a manager to the project revision
const [applyAddManagerToRevision] = useAddManagerToRevisionMutation();
const addManager = (data: {
cifUserId: number;
projectManagerLabelId: number;
projectId: number;
}) => {
applyAddManagerToRevision({
variables: {
projectRevision: projectRevisionId,
projectRevisionId: projectRevisionRowId,
newFormData: data,
},
optimisticResponse: {
createFormChange: {
query: {
projectRevision: {
managerFormChanges: {
edges: {
change: {
projectManagerLabel: {},
formChange: {
id: "new",
newFormData: data,
},
},
},
},
},
},
},
},
});
};

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,
formChange: { cifUserId: number }
) => {
const data = {
...formChange,
projectManagerLabelId: labelId,
projectId: projectId,
};

// If a form_change already exists, and the payload contains a cifUserId update it
if (formChangeId && formChange?.cifUserId) {
applyUpdateFormChangeMutation({
variables: {
input: {
id: formChangeId,
formChangePatch: {
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 (formChange?.cifUserId) {
addManager(data);
}
// If a form_change exists, and the payload does not contain a cifUserId delete it
else if (formChangeId && !formChange.cifUserId && !discardInFlight) {
deleteManager(formChangeId);
}
};

return (
<>
<Grid.Row>
<FieldLabel
label={change.projectManagerLabel.label}
required={false}
htmlFor={change.projectManagerLabel.id}
uiSchema={uiSchema}
/>
</Grid.Row>
<Grid.Row>
<Grid.Col span={6}>
<FormBase
id={`form-manager-${change.projectManagerLabel.label}`}
idPrefix={`form-${change.projectManagerLabel.id}`}
ref={(el) => (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}
/>
</Grid.Col>
<Grid.Col span={4}>
<Button
disabled={discardInFlight || !change.formChange?.id}
variant="secondary"
size="small"
onClick={() => deleteManager(change.formChange?.id)}
>
Clear
</Button>
</Grid.Col>
</Grid.Row>
</>
);
};

export default forwardRef(ProjecManagerForm);
export default ProjectManagerForm;
96 changes: 96 additions & 0 deletions app/components/Form/ProjectManagerFormGroup.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
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";
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";

interface Props extends ValidatingFormProps {
query: ProjectManagerFormGroup_query$key;
revision: ProjectManagerFormGroup_revision$key;
projectManagerFormRef: MutableRefObject<{}>;
}

const ProjectManagerFormGroup: React.FC<Props> = (props) => {
const formRefs = useRef({});
const query = useFragment(
graphql`
fragment ProjectManagerFormGroup_query on Query {
...ProjectManagerForm_query
}
`,
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
);

props.setValidatingForm({
selfValidate: () => {
return Object.keys(formRefs.current).reduce((agg, formId) => {
const formObject = formRefs.current[formId];
return [...agg, ...validateFormWithErrors(formObject)];
}, []);
},
});

return (
<div>
<Grid cols={10} align="center">
<Grid.Row>
<Grid.Col span={10}>
<FormBorder title="Project Managers">
{projectRevision.managerFormChanges.edges.map(({ node }) => (
<ProjectManagerForm
key={node.projectManagerLabel.id}
managerFormChange={node}
query={query}
projectId={projectRevision.projectFormChange.formDataRecordId}
projectRevisionId={projectRevision.id}
projectRevisionRowId={projectRevision.rowId}
formRefs={formRefs}
/>
))}
</FormBorder>
</Grid.Col>
</Grid.Row>
</Grid>
<style jsx>{`
div :global(button.pg-button) {
margin-left: 0.4em;
margin-right: 0em;
}
div :global(.right-aligned-column) {
display: flex;
justify-content: flex-end;
align-items: flex-start;
}
`}</style>
</div>
);
};

export default ProjectManagerFormGroup;
2 changes: 1 addition & 1 deletion app/cypress/integration/cif/project-revision/index.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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", 7);
// Renders a custom error message for a custom format validation error
cy.get(".error-detail")
.first()
Expand Down
Loading

0 comments on commit e9dd666

Please sign in to comment.