diff --git a/packages/backend/src/apps/formsg/common/get-data-out-metadata.ts b/packages/backend/src/apps/formsg/common/get-data-out-metadata.ts index ab73a1246..ac39d693e 100644 --- a/packages/backend/src/apps/formsg/common/get-data-out-metadata.ts +++ b/packages/backend/src/apps/formsg/common/get-data-out-metadata.ts @@ -335,10 +335,13 @@ async function getDataOutMetadata( let questionIdsToShowForMrf: Set | undefined + let approvalField: string | null = null if (parameters.mrf) { questionIdsToShowForMrf = new Set( (parameters.mrf as ParsedMrfWorkflowStep).fields, ) + approvalField = + (parameters.mrf as ParsedMrfWorkflowStep).approvalField ?? null } const fieldMetadata: IDataOutMetadata = Object.create(null) @@ -391,6 +394,11 @@ async function getDataOutMetadata( isVisible: { isHidden: true }, isHeader: { isHidden: true }, } + + if (approvalField && fieldId === approvalField) { + fieldMetadata[fieldId].answer.type = 'approval' + } + if (isAnswerArrayValid(fieldData)) { // table field will have a stringified table object in the answer field // so that it can be used in for-each diff --git a/packages/backend/src/graphql/mutations/create-flow.ts b/packages/backend/src/graphql/mutations/create-flow.ts index 51a1052e0..b35684e5a 100644 --- a/packages/backend/src/graphql/mutations/create-flow.ts +++ b/packages/backend/src/graphql/mutations/create-flow.ts @@ -14,15 +14,14 @@ const createFlow: MutationResolvers['createFlow'] = async ( name: trimmedFlowName, }) + /** + * We do not need to create an empty action since the AddStepButton already looks like an empty step + */ await flow.$relatedQuery('steps').insert([ { type: 'trigger', position: 1, }, - { - type: 'action', - position: 2, - }, ]) return flow diff --git a/packages/frontend/src/components/Editor/components/AddStepButton.tsx b/packages/frontend/src/components/Editor/components/AddStepButton.tsx index 7848dd45c..db3a3dc98 100644 --- a/packages/frontend/src/components/Editor/components/AddStepButton.tsx +++ b/packages/frontend/src/components/Editor/components/AddStepButton.tsx @@ -32,21 +32,21 @@ export function AddStepButton(props: AddStepButtonProps): JSX.Element { const { isOpen, onOpen, onClose } = useDisclosure() const { dividerColor, iconBgColor } = useMemo(() => { - if (approvalBranch === undefined) { - return { - dividerColor: 'base.divider.strong', - iconBgColor: undefined, - } - } if (approvalBranch === 'approve') { return { dividerColor: 'green.500', iconBgColor: 'green.50', } } + if (approvalBranch === 'reject') { + return { + dividerColor: 'red.500', + iconBgColor: 'red.50', + } + } return { - dividerColor: 'red.500', - iconBgColor: 'red.50', + dividerColor: 'base.divider.strong', + iconBgColor: undefined, } }, [approvalBranch]) @@ -93,7 +93,7 @@ export function AddStepButton(props: AddStepButtonProps): JSX.Element { )} {/* Top vertical line */} - + {/* Bottom vertical line */} {!isLastStep && ( - + )} diff --git a/packages/frontend/src/components/VariablesList/index.tsx b/packages/frontend/src/components/VariablesList/index.tsx index c46142437..bda567f5d 100644 --- a/packages/frontend/src/components/VariablesList/index.tsx +++ b/packages/frontend/src/components/VariablesList/index.tsx @@ -50,6 +50,11 @@ function VariableTag({ label: 'Tile Row ID', tooltip: `This variable can be used in Tile's Update Single Row action`, } + case 'approval': + return { + label: 'Approval', + tooltip: 'This variable determines the approval status of this step', + } default: return { label: null, diff --git a/packages/frontend/src/contexts/MrfContext.tsx b/packages/frontend/src/contexts/MrfContext.tsx index b21538732..121c27fcf 100644 --- a/packages/frontend/src/contexts/MrfContext.tsx +++ b/packages/frontend/src/contexts/MrfContext.tsx @@ -1,6 +1,7 @@ import { IStep, IStepApprovalBranch } from '@plumber/types' -import { createContext, useContext, useState } from 'react' +import { createContext, useContext, useEffect, useMemo, useState } from 'react' +import { isEqual } from 'lodash' import get from 'lodash/get' import { FORMSG_APP_KEY, MRF_ACTION_KEY } from '@/helpers/formsg' @@ -29,28 +30,55 @@ interface MrfContextProviderProps { children: React.ReactNode } +function generateDefaultApprovalBranches( + mrfApprovalSteps: IStep[], +): Record { + return mrfApprovalSteps.reduce((acc, step) => { + acc[step.id] = 'approve' + return acc + }, {} as Record) +} + export const MrfContextProvider = ({ children }: MrfContextProviderProps) => { const { flow } = useContext(EditorContext) const [disabledMrfStepToDisplay, setDisabledMrfStepToDisplay] = useState(null) - const mrfSteps = flow.steps.filter( - (step) => step.appKey === FORMSG_APP_KEY && step.key === MRF_ACTION_KEY, + const mrfSteps = useMemo( + () => + flow.steps.filter( + (step) => step.appKey === FORMSG_APP_KEY && step.key === MRF_ACTION_KEY, + ), + [flow.steps], ) - const mrfApprovalSteps = mrfSteps.filter( - (step) => !!get(step.parameters, 'mrf.approvalField', false), + const mrfApprovalSteps = useMemo( + () => + mrfSteps.filter( + (step) => !!get(step.parameters, 'mrf.approvalField', false), + ), + [mrfSteps], ) const [approvalBranches, setApprovalBranches] = useState< Record - >({ - ...mrfApprovalSteps.reduce((acc, step) => { - acc[step.id] = 'approve' - return acc - }, {} as Record), - }) + >(generateDefaultApprovalBranches(mrfApprovalSteps)) + + /** + * We need to reset the state when the mrf approval steps change + */ + useEffect(() => { + if ( + isEqual( + mrfApprovalSteps.map((step) => step.id), + Object.keys(approvalBranches), + ) + ) { + return + } + setApprovalBranches(generateDefaultApprovalBranches(mrfApprovalSteps)) + }, [approvalBranches, mrfApprovalSteps]) const setApprovalBranch = (stepId: string, branch: IStepApprovalBranch) => { setApprovalBranches((prev) => ({ diff --git a/packages/types/index.d.ts b/packages/types/index.d.ts index a96c4f40b..66c32e550 100644 --- a/packages/types/index.d.ts +++ b/packages/types/index.d.ts @@ -60,6 +60,7 @@ export type TDataOutMetadatumType = | 'array' | 'tile_row_id' | 'table' + | 'approval' /** * This should only be defined on _leaf_ nodes (i.e. **primitive array