@@ -2,42 +2,150 @@ import { IStep, IStepConfig } from '@plumber/types'
22
33import get from 'lodash.get'
44
5- export function validateApprovalConfig ( config : IStepConfig , prevStep : IStep ) {
5+ import { stepApprovalConfigSchema } from '@/apps/formsg/common/types'
6+ import Step from '@/models/step'
7+
8+ import logger from './logger'
9+
10+ /**
11+ * TODO: write unit test for this function
12+ */
13+ export async function validateApprovalConfig (
14+ config : IStepConfig ,
15+ prevStep : IStep ,
16+ ) : Promise <
17+ | {
18+ isApprovalConfigValid : true
19+ newStepPosition : number
20+ }
21+ | {
22+ isApprovalConfigValid : false
23+ }
24+ > {
625 /**
7- * if approval config is not present, or is a trigger, return true
26+ * Case 1: new step is a trigger step , return early
827 */
9- if ( ! config ?. approval || ! prevStep ) {
10- return true
28+ if ( ! prevStep ) {
29+ return {
30+ isApprovalConfigValid : true ,
31+ newStepPosition : 1 ,
32+ }
1133 }
1234
35+ const isPreviousStepMrfApprovalStep =
36+ prevStep . appKey === 'formsg' &&
37+ prevStep . key === 'mrfSubmission' &&
38+ // if no approval field, it's just a normal mrf step
39+ ! ! get ( prevStep . parameters , 'mrf.approvalField' )
40+
1341 /**
14- * Both approval branch and approval step id must come together
42+ * Case 2: Prev step has no approval config and is not an mrf approval step,
43+ * current step should have no approval config either
1544 */
16- if ( ! ( config ?. approval ?. branch && config ?. approval ?. stepId ) ) {
17- return false
45+ if ( ! prevStep . config . approval && ! isPreviousStepMrfApprovalStep ) {
46+ if ( ! config ?. approval ) {
47+ return {
48+ isApprovalConfigValid : true ,
49+ newStepPosition : prevStep . position + 1 ,
50+ }
51+ }
52+ logger . error (
53+ 'Invalid approval config: previous step has no approval config and is not an mrf approval step' ,
54+ )
55+ return {
56+ isApprovalConfigValid : false ,
57+ }
1858 }
1959
2060 /**
21- * If previous step an approval step return true
61+ * Case 3: Prev step is part of an approval branch, check that
62+ * the new step has the same approval config
2263 */
23- if (
24- prevStep . id === config . approval ?. stepId &&
25- prevStep . appKey === 'formsg' &&
26- prevStep . key === 'mrfSubmission' &&
27- ! ! get ( prevStep . parameters , 'mrf.approvalField' )
28- ) {
29- return true
64+ if ( prevStep . config . approval ) {
65+ const isSameApprovalConfig =
66+ prevStep . config . approval ?. branch === config ?. approval ?. branch &&
67+ prevStep . config . approval ?. stepId === config ?. approval ?. stepId
68+ if ( isSameApprovalConfig ) {
69+ return {
70+ isApprovalConfigValid : true ,
71+ newStepPosition : prevStep . position + 1 ,
72+ }
73+ }
74+ logger . error (
75+ 'Invalid approval config: new step approval config is not the same as previous step' ,
76+ )
77+ return {
78+ isApprovalConfigValid : false ,
79+ }
3080 }
3181
3282 /**
33- * If previous step has the same approval branch and step id, return true
83+ * Case 4: Previous step is an approval step
3484 */
35- if (
36- prevStep . config . approval ?. branch === config . approval ?. branch &&
37- prevStep . config . approval ?. stepId === config . approval ?. stepId
38- ) {
39- return true
85+ if ( isPreviousStepMrfApprovalStep ) {
86+ // validate approval config
87+ const { success } = stepApprovalConfigSchema . safeParse ( config ?. approval )
88+ // validate approval config step id is the same as prev step id
89+ if ( ! success || config . approval . stepId !== prevStep . id ) {
90+ logger . error (
91+ 'Invalid approval config (after approval step): invalid approval config or new step id is not the same as approval step id' ,
92+ {
93+ approvalConfig : config ?. approval ,
94+ } ,
95+ )
96+ return {
97+ isApprovalConfigValid : false ,
98+ }
99+ }
100+ /**
101+ * If approval branch, simply add 1 to the prev step position
102+ */
103+ if ( config . approval . branch === 'approve' ) {
104+ return {
105+ isApprovalConfigValid : true ,
106+ newStepPosition : prevStep . position + 1 ,
107+ }
108+ }
109+ // If reject branch, find the end of the approval branch and add one to that step
110+ // first find the next mrf step (if any)
111+ const nextMrfStep = await Step . query ( )
112+ . where ( 'flow_id' , prevStep . flowId )
113+ . andWhere ( 'type' , 'action' )
114+ . andWhere ( 'key' , 'mrfSubmission' )
115+ . andWhere ( 'position' , '>' , prevStep . position )
116+ . orderBy ( 'position' , 'asc' )
117+ . first ( )
118+ // then find the last approval step in the curr branch (if any)
119+ const lastApprovalStepQuery = Step . query ( )
120+ . where ( 'flow_id' , prevStep . flowId )
121+ . andWhere ( 'position' , '>' , prevStep . position )
122+ . andWhereRaw ( `config->'approval'->>'branch' = ?` , [ 'approve' ] )
123+ . orderBy ( 'position' , 'desc' )
124+ . first ( )
125+
126+ if ( nextMrfStep ) {
127+ lastApprovalStepQuery . andWhere ( 'position' , '<' , nextMrfStep . position )
128+ }
129+ const lastApprovalStep = await lastApprovalStepQuery . first ( )
130+ // If last approval step exists, return the position after that
131+ if ( lastApprovalStep ) {
132+ return {
133+ isApprovalConfigValid : true ,
134+ newStepPosition : lastApprovalStep . position + 1 ,
135+ }
136+ } else {
137+ // If no last approval step, return the position after the previous step
138+ return {
139+ isApprovalConfigValid : true ,
140+ newStepPosition : prevStep . position + 1 ,
141+ }
142+ }
40143 }
41144
42- return false
145+ /**
146+ * Catch all branch, something seems fishy
147+ */
148+ return {
149+ isApprovalConfigValid : false ,
150+ }
43151}
0 commit comments