@oneblink/apps-react 13.0.0-beta.10 → 13.0.0-beta.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/OneBlinkFormBase.js +3 -2
- package/dist/OneBlinkFormBase.js.map +1 -1
- package/dist/apps/approvals-service.d.ts +1 -60
- package/dist/apps/approvals-service.js +1 -181
- package/dist/apps/approvals-service.js.map +1 -1
- package/dist/hooks/useFormIsDirty.d.ts +18 -0
- package/dist/hooks/useFormIsDirty.js +44 -1
- package/dist/hooks/useFormIsDirty.js.map +1 -1
- package/dist/hooks/useFormValidation.d.ts +1 -1
- package/dist/hooks/useFormValidation.js +5 -2
- package/dist/hooks/useFormValidation.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/services/form-validation/extensions.d.ts +2 -1
- package/dist/services/form-validation/extensions.js +2 -1
- package/dist/services/form-validation/extensions.js.map +1 -1
- package/dist/services/form-validation/getApproverEditableFormElementIds.d.ts +2 -0
- package/dist/services/form-validation/getApproverEditableFormElementIds.js +14 -0
- package/dist/services/form-validation/getApproverEditableFormElementIds.js.map +1 -0
- package/dist/services/form-validation/validateSubmission.d.ts +2 -1
- package/dist/services/form-validation/validateSubmission.js +13 -1
- package/dist/services/form-validation/validateSubmission.js.map +1 -1
- package/dist/services/getFlowInstanceNodesWithMeta.d.ts +70 -0
- package/dist/services/getFlowInstanceNodesWithMeta.js +247 -0
- package/dist/services/getFlowInstanceNodesWithMeta.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { ApprovalTypes } from '@oneblink/types';
|
|
2
|
+
/**
|
|
3
|
+
* Inputs a form approval flow instance and set of form submission approvals and
|
|
4
|
+
* returns a flattened array of nodes/steps with inlcuded metadata.
|
|
5
|
+
*
|
|
6
|
+
* #### Example
|
|
7
|
+
*
|
|
8
|
+
* ```js
|
|
9
|
+
* const formApprovalFlowInstance = {
|
|
10
|
+
* steps: [
|
|
11
|
+
* {
|
|
12
|
+
* type: 'STANDARD',
|
|
13
|
+
* group: 'oneblink:administrator',
|
|
14
|
+
* isSkipped: false,
|
|
15
|
+
* label: 'Manager Approval',
|
|
16
|
+
* approvalFormId: '1234',
|
|
17
|
+
* },
|
|
18
|
+
* {
|
|
19
|
+
* type: 'CONCURRENT',
|
|
20
|
+
* nodes: [
|
|
21
|
+
* {
|
|
22
|
+
* label: 'HR Review',
|
|
23
|
+
* group: 'oneblink:administrator',
|
|
24
|
+
* isSkipped: false,
|
|
25
|
+
* approvalFormId: '1234',
|
|
26
|
+
* },
|
|
27
|
+
* {
|
|
28
|
+
* label: 'Finance Review',
|
|
29
|
+
* group: 'oneblink:administrator',
|
|
30
|
+
* isSkipped: false,
|
|
31
|
+
* approvalFormId: '1234',
|
|
32
|
+
* },
|
|
33
|
+
* ],
|
|
34
|
+
* },
|
|
35
|
+
* ],
|
|
36
|
+
* }
|
|
37
|
+
*
|
|
38
|
+
* const formSubmissionApprovals = [
|
|
39
|
+
* {
|
|
40
|
+
* id: 'approval1',
|
|
41
|
+
* stepLabel: 'Manager Approval',
|
|
42
|
+
* status: 'APPROVED',
|
|
43
|
+
* createdAt: '2024-03-20T10:00:00Z',
|
|
44
|
+
* updatedAt: '2024-03-20T10:00:00Z',
|
|
45
|
+
* updatedBy: 'fake@fake.com',
|
|
46
|
+
* group: 'oneblink:administrator',
|
|
47
|
+
* approvalFormId: '1234',
|
|
48
|
+
* },
|
|
49
|
+
* ]
|
|
50
|
+
*
|
|
51
|
+
* const nodesWithMeta = getFlowInstanceNodesWithMeta(
|
|
52
|
+
* formApprovalFlowInstance,
|
|
53
|
+
* formSubmissionApprovals,
|
|
54
|
+
* )
|
|
55
|
+
* ```
|
|
56
|
+
*
|
|
57
|
+
* Concurrent nodes are ordered as actioned (by the time they were actioned),
|
|
58
|
+
* then the currently viewed pending approval if
|
|
59
|
+
* `currentlyViewingFormSubmissionApprovalId` is provided, then remaining
|
|
60
|
+
* unactioned nodes in definition order. Sequential steps around a concurrent
|
|
61
|
+
* group are not reordered.
|
|
62
|
+
*
|
|
63
|
+
* @param formApprovalFlowInstance
|
|
64
|
+
* @param formSubmissionApprovals
|
|
65
|
+
* @param currentlyViewingFormSubmissionApprovalId The id of the approval
|
|
66
|
+
* currently being viewed, used to keep that pending concurrent step next to
|
|
67
|
+
* already actioned siblings.
|
|
68
|
+
* @returns ApprovalTypes.FlowInstanceNodeWithMeta[]
|
|
69
|
+
*/
|
|
70
|
+
export declare function getFlowInstanceNodesWithMeta(formApprovalFlowInstance: ApprovalTypes.FormApprovalFlowInstance, formSubmissionApprovals: ApprovalTypes.FormSubmissionApproval[], currentlyViewingFormSubmissionApprovalId?: string): ApprovalTypes.FlowInstanceNodeWithMeta[];
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
const possibleStatuses = [
|
|
2
|
+
{
|
|
3
|
+
label: 'Awaiting Approval',
|
|
4
|
+
value: 'PENDING',
|
|
5
|
+
},
|
|
6
|
+
{
|
|
7
|
+
label: 'Approved',
|
|
8
|
+
value: 'APPROVED',
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
label: 'Clarification Requested',
|
|
12
|
+
value: 'CLARIFICATION_REQUIRED',
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
label: 'Clarification Received',
|
|
16
|
+
value: 'CLARIFICATION_RECEIVED',
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
label: 'Denied',
|
|
20
|
+
value: 'CLOSED',
|
|
21
|
+
},
|
|
22
|
+
];
|
|
23
|
+
const ACTIONED_STATUSES = new Set(['APPROVED', 'CLOSED', 'CLARIFICATION_REQUIRED']);
|
|
24
|
+
function findLatestFormSubmissionApproval(flowInstanceNode, formSubmissionApprovals) {
|
|
25
|
+
return formSubmissionApprovals.reduce((lastUpdatedFormSubmissionApproval, formSubmissionApproval) => {
|
|
26
|
+
if (formSubmissionApproval.stepLabel === flowInstanceNode.label &&
|
|
27
|
+
(!lastUpdatedFormSubmissionApproval ||
|
|
28
|
+
lastUpdatedFormSubmissionApproval.updatedAt <
|
|
29
|
+
formSubmissionApproval.updatedAt)) {
|
|
30
|
+
return formSubmissionApproval;
|
|
31
|
+
}
|
|
32
|
+
return lastUpdatedFormSubmissionApproval;
|
|
33
|
+
}, undefined);
|
|
34
|
+
}
|
|
35
|
+
function getConcurrentSortRank(node, formSubmissionApprovals, currentlyViewingFormSubmissionApprovalId) {
|
|
36
|
+
const approval = findLatestFormSubmissionApproval(node, formSubmissionApprovals);
|
|
37
|
+
if (approval && ACTIONED_STATUSES.has(approval.status)) {
|
|
38
|
+
return 0;
|
|
39
|
+
}
|
|
40
|
+
if (currentlyViewingFormSubmissionApprovalId &&
|
|
41
|
+
(approval === null || approval === void 0 ? void 0 : approval.id) === currentlyViewingFormSubmissionApprovalId) {
|
|
42
|
+
return 1;
|
|
43
|
+
}
|
|
44
|
+
return 2;
|
|
45
|
+
}
|
|
46
|
+
function withUpdatedConcurrentFlags(nodes) {
|
|
47
|
+
return nodes.map((node, index) => ({
|
|
48
|
+
...node,
|
|
49
|
+
isConcurrentWithPrevious: index !== 0,
|
|
50
|
+
isConcurrentWithNext: index < nodes.length - 1,
|
|
51
|
+
}));
|
|
52
|
+
}
|
|
53
|
+
function sortConcurrentGroup(nodes, formSubmissionApprovals, currentlyViewingFormSubmissionApprovalId) {
|
|
54
|
+
const sorted = [...nodes].sort((left, right) => {
|
|
55
|
+
var _a, _b, _c, _d;
|
|
56
|
+
const leftRank = getConcurrentSortRank(left, formSubmissionApprovals, currentlyViewingFormSubmissionApprovalId);
|
|
57
|
+
const rightRank = getConcurrentSortRank(right, formSubmissionApprovals, currentlyViewingFormSubmissionApprovalId);
|
|
58
|
+
if (leftRank !== rightRank) {
|
|
59
|
+
return leftRank - rightRank;
|
|
60
|
+
}
|
|
61
|
+
if (leftRank === 0) {
|
|
62
|
+
const leftUpdatedAt = (_b = (_a = findLatestFormSubmissionApproval(left, formSubmissionApprovals)) === null || _a === void 0 ? void 0 : _a.updatedAt) !== null && _b !== void 0 ? _b : '';
|
|
63
|
+
const rightUpdatedAt = (_d = (_c = findLatestFormSubmissionApproval(right, formSubmissionApprovals)) === null || _c === void 0 ? void 0 : _c.updatedAt) !== null && _d !== void 0 ? _d : '';
|
|
64
|
+
return leftUpdatedAt.localeCompare(rightUpdatedAt);
|
|
65
|
+
}
|
|
66
|
+
return 0;
|
|
67
|
+
});
|
|
68
|
+
return withUpdatedConcurrentFlags(sorted);
|
|
69
|
+
}
|
|
70
|
+
function sortConcurrentFlowInstanceNodes(nodes, formSubmissionApprovals, currentlyViewingFormSubmissionApprovalId) {
|
|
71
|
+
const groups = [];
|
|
72
|
+
for (const node of nodes) {
|
|
73
|
+
const currentGroup = groups[groups.length - 1];
|
|
74
|
+
if (currentGroup &&
|
|
75
|
+
currentGroup[0].parentStepLabelsId === node.parentStepLabelsId) {
|
|
76
|
+
currentGroup.push(node);
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
groups.push([node]);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return groups.flatMap((group) => {
|
|
83
|
+
if (group.length < 2) {
|
|
84
|
+
return group;
|
|
85
|
+
}
|
|
86
|
+
return sortConcurrentGroup(group, formSubmissionApprovals, currentlyViewingFormSubmissionApprovalId);
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
function getFormApprovalFlowInstanceNodes(steps) {
|
|
90
|
+
return steps.reduce((memo, step) => {
|
|
91
|
+
switch (step.type) {
|
|
92
|
+
case 'CONCURRENT': {
|
|
93
|
+
const parentStepLabelsId = step.nodes
|
|
94
|
+
.map(({ label }) => label)
|
|
95
|
+
.join('_');
|
|
96
|
+
for (const node of step.nodes) {
|
|
97
|
+
const index = step.nodes.indexOf(node);
|
|
98
|
+
memo.push({
|
|
99
|
+
...node,
|
|
100
|
+
parentStepLabelsId,
|
|
101
|
+
isConcurrentWithPrevious: index !== 0,
|
|
102
|
+
isConcurrentWithNext: index < step.nodes.length - 1,
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
break;
|
|
106
|
+
}
|
|
107
|
+
case 'STANDARD':
|
|
108
|
+
default: {
|
|
109
|
+
memo.push({
|
|
110
|
+
...step,
|
|
111
|
+
isConcurrentWithPrevious: false,
|
|
112
|
+
isConcurrentWithNext: false,
|
|
113
|
+
parentStepLabelsId: step.label,
|
|
114
|
+
});
|
|
115
|
+
break;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return memo;
|
|
119
|
+
}, []);
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Inputs a form approval flow instance and set of form submission approvals and
|
|
123
|
+
* returns a flattened array of nodes/steps with inlcuded metadata.
|
|
124
|
+
*
|
|
125
|
+
* #### Example
|
|
126
|
+
*
|
|
127
|
+
* ```js
|
|
128
|
+
* const formApprovalFlowInstance = {
|
|
129
|
+
* steps: [
|
|
130
|
+
* {
|
|
131
|
+
* type: 'STANDARD',
|
|
132
|
+
* group: 'oneblink:administrator',
|
|
133
|
+
* isSkipped: false,
|
|
134
|
+
* label: 'Manager Approval',
|
|
135
|
+
* approvalFormId: '1234',
|
|
136
|
+
* },
|
|
137
|
+
* {
|
|
138
|
+
* type: 'CONCURRENT',
|
|
139
|
+
* nodes: [
|
|
140
|
+
* {
|
|
141
|
+
* label: 'HR Review',
|
|
142
|
+
* group: 'oneblink:administrator',
|
|
143
|
+
* isSkipped: false,
|
|
144
|
+
* approvalFormId: '1234',
|
|
145
|
+
* },
|
|
146
|
+
* {
|
|
147
|
+
* label: 'Finance Review',
|
|
148
|
+
* group: 'oneblink:administrator',
|
|
149
|
+
* isSkipped: false,
|
|
150
|
+
* approvalFormId: '1234',
|
|
151
|
+
* },
|
|
152
|
+
* ],
|
|
153
|
+
* },
|
|
154
|
+
* ],
|
|
155
|
+
* }
|
|
156
|
+
*
|
|
157
|
+
* const formSubmissionApprovals = [
|
|
158
|
+
* {
|
|
159
|
+
* id: 'approval1',
|
|
160
|
+
* stepLabel: 'Manager Approval',
|
|
161
|
+
* status: 'APPROVED',
|
|
162
|
+
* createdAt: '2024-03-20T10:00:00Z',
|
|
163
|
+
* updatedAt: '2024-03-20T10:00:00Z',
|
|
164
|
+
* updatedBy: 'fake@fake.com',
|
|
165
|
+
* group: 'oneblink:administrator',
|
|
166
|
+
* approvalFormId: '1234',
|
|
167
|
+
* },
|
|
168
|
+
* ]
|
|
169
|
+
*
|
|
170
|
+
* const nodesWithMeta = getFlowInstanceNodesWithMeta(
|
|
171
|
+
* formApprovalFlowInstance,
|
|
172
|
+
* formSubmissionApprovals,
|
|
173
|
+
* )
|
|
174
|
+
* ```
|
|
175
|
+
*
|
|
176
|
+
* Concurrent nodes are ordered as actioned (by the time they were actioned),
|
|
177
|
+
* then the currently viewed pending approval if
|
|
178
|
+
* `currentlyViewingFormSubmissionApprovalId` is provided, then remaining
|
|
179
|
+
* unactioned nodes in definition order. Sequential steps around a concurrent
|
|
180
|
+
* group are not reordered.
|
|
181
|
+
*
|
|
182
|
+
* @param formApprovalFlowInstance
|
|
183
|
+
* @param formSubmissionApprovals
|
|
184
|
+
* @param currentlyViewingFormSubmissionApprovalId The id of the approval
|
|
185
|
+
* currently being viewed, used to keep that pending concurrent step next to
|
|
186
|
+
* already actioned siblings.
|
|
187
|
+
* @returns ApprovalTypes.FlowInstanceNodeWithMeta[]
|
|
188
|
+
*/
|
|
189
|
+
export function getFlowInstanceNodesWithMeta(formApprovalFlowInstance, formSubmissionApprovals, currentlyViewingFormSubmissionApprovalId) {
|
|
190
|
+
const formApprovalFlowInstanceNodes = getFormApprovalFlowInstanceNodes(formApprovalFlowInstance.steps);
|
|
191
|
+
let isDenied = false;
|
|
192
|
+
let isClarificationRequired = false;
|
|
193
|
+
const nodesWithMeta = formApprovalFlowInstanceNodes.map((step) => {
|
|
194
|
+
const associatedApproval = findLatestFormSubmissionApproval(step, formSubmissionApprovals);
|
|
195
|
+
const status = possibleStatuses.find(({ value }) => value === (associatedApproval === null || associatedApproval === void 0 ? void 0 : associatedApproval.status));
|
|
196
|
+
let isDeniedInConcurrentStep = false;
|
|
197
|
+
let isClarificationRequiredInConcurrentStep = false;
|
|
198
|
+
if ((status === null || status === void 0 ? void 0 : status.value) === 'PENDING') {
|
|
199
|
+
const siblingNodes = formApprovalFlowInstanceNodes.filter(({ parentStepLabelsId, label }) => parentStepLabelsId === step.parentStepLabelsId &&
|
|
200
|
+
label !== step.label);
|
|
201
|
+
for (const siblingNode of siblingNodes) {
|
|
202
|
+
const formSubmissionApproval = findLatestFormSubmissionApproval(siblingNode, formSubmissionApprovals);
|
|
203
|
+
if ((formSubmissionApproval === null || formSubmissionApproval === void 0 ? void 0 : formSubmissionApproval.status) === 'CLOSED') {
|
|
204
|
+
isDeniedInConcurrentStep = true;
|
|
205
|
+
}
|
|
206
|
+
if ((formSubmissionApproval === null || formSubmissionApproval === void 0 ? void 0 : formSubmissionApproval.status) === 'CLARIFICATION_REQUIRED') {
|
|
207
|
+
isClarificationRequiredInConcurrentStep = true;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
const isAfterDeniedStep = isDenied;
|
|
212
|
+
if ((status === null || status === void 0 ? void 0 : status.value) === 'CLOSED') {
|
|
213
|
+
isDenied = true;
|
|
214
|
+
}
|
|
215
|
+
const isAfterClarificationRequiredStep = isClarificationRequired;
|
|
216
|
+
if ((status === null || status === void 0 ? void 0 : status.value) === 'CLARIFICATION_REQUIRED') {
|
|
217
|
+
isClarificationRequired = true;
|
|
218
|
+
}
|
|
219
|
+
let description = 'Awaiting previous approvals';
|
|
220
|
+
if (step.isSkipped) {
|
|
221
|
+
description = 'Skipped based on form submission';
|
|
222
|
+
}
|
|
223
|
+
else if (status) {
|
|
224
|
+
const updatedByText = (associatedApproval === null || associatedApproval === void 0 ? void 0 : associatedApproval.updatedBy)
|
|
225
|
+
? ` by ${associatedApproval.updatedBy}`
|
|
226
|
+
: '';
|
|
227
|
+
description = `${status.label}${updatedByText}`;
|
|
228
|
+
}
|
|
229
|
+
else if (isAfterDeniedStep) {
|
|
230
|
+
description = 'Denied in a previous step';
|
|
231
|
+
}
|
|
232
|
+
else if (isAfterClarificationRequiredStep) {
|
|
233
|
+
description = 'Sent for clarification in a previous step';
|
|
234
|
+
}
|
|
235
|
+
return {
|
|
236
|
+
...step,
|
|
237
|
+
status,
|
|
238
|
+
isDeniedInConcurrentStep,
|
|
239
|
+
isAfterDeniedStep,
|
|
240
|
+
isClarificationRequiredInConcurrentStep,
|
|
241
|
+
isAfterClarificationRequiredStep,
|
|
242
|
+
description,
|
|
243
|
+
};
|
|
244
|
+
});
|
|
245
|
+
return sortConcurrentFlowInstanceNodes(nodesWithMeta, formSubmissionApprovals, currentlyViewingFormSubmissionApprovalId);
|
|
246
|
+
}
|
|
247
|
+
//# sourceMappingURL=getFlowInstanceNodesWithMeta.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"getFlowInstanceNodesWithMeta.js","sourceRoot":"","sources":["../../src/services/getFlowInstanceNodesWithMeta.ts"],"names":[],"mappings":"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","sourcesContent":["import { ApprovalTypes } from '@oneblink/types'\n\nconst possibleStatuses = [\n {\n label: 'Awaiting Approval',\n value: 'PENDING',\n },\n {\n label: 'Approved',\n value: 'APPROVED',\n },\n {\n label: 'Clarification Requested',\n value: 'CLARIFICATION_REQUIRED',\n },\n {\n label: 'Clarification Received',\n value: 'CLARIFICATION_RECEIVED',\n },\n {\n label: 'Denied',\n value: 'CLOSED',\n },\n]\n\nconst ACTIONED_STATUSES: ReadonlySet<\n ApprovalTypes.FormSubmissionApproval['status']\n> = new Set(['APPROVED', 'CLOSED', 'CLARIFICATION_REQUIRED'])\n\nfunction findLatestFormSubmissionApproval(\n flowInstanceNode: { label: string },\n formSubmissionApprovals: ApprovalTypes.FormSubmissionApproval[],\n) {\n return formSubmissionApprovals.reduce<\n ApprovalTypes.FormSubmissionApproval | undefined\n >((lastUpdatedFormSubmissionApproval, formSubmissionApproval) => {\n if (\n formSubmissionApproval.stepLabel === flowInstanceNode.label &&\n (!lastUpdatedFormSubmissionApproval ||\n lastUpdatedFormSubmissionApproval.updatedAt <\n formSubmissionApproval.updatedAt)\n ) {\n return formSubmissionApproval\n }\n return lastUpdatedFormSubmissionApproval\n }, undefined)\n}\n\nfunction getConcurrentSortRank(\n node: ApprovalTypes.FlowInstanceNodeWithMeta,\n formSubmissionApprovals: ApprovalTypes.FormSubmissionApproval[],\n currentlyViewingFormSubmissionApprovalId: string | undefined,\n) {\n const approval = findLatestFormSubmissionApproval(\n node,\n formSubmissionApprovals,\n )\n if (approval && ACTIONED_STATUSES.has(approval.status)) {\n return 0\n }\n if (\n currentlyViewingFormSubmissionApprovalId &&\n approval?.id === currentlyViewingFormSubmissionApprovalId\n ) {\n return 1\n }\n return 2\n}\n\nfunction withUpdatedConcurrentFlags(\n nodes: ApprovalTypes.FlowInstanceNodeWithMeta[],\n) {\n return nodes.map((node, index) => ({\n ...node,\n isConcurrentWithPrevious: index !== 0,\n isConcurrentWithNext: index < nodes.length - 1,\n }))\n}\n\nfunction sortConcurrentGroup(\n nodes: ApprovalTypes.FlowInstanceNodeWithMeta[],\n formSubmissionApprovals: ApprovalTypes.FormSubmissionApproval[],\n currentlyViewingFormSubmissionApprovalId: string | undefined,\n) {\n const sorted = [...nodes].sort((left, right) => {\n const leftRank = getConcurrentSortRank(\n left,\n formSubmissionApprovals,\n currentlyViewingFormSubmissionApprovalId,\n )\n const rightRank = getConcurrentSortRank(\n right,\n formSubmissionApprovals,\n currentlyViewingFormSubmissionApprovalId,\n )\n if (leftRank !== rightRank) {\n return leftRank - rightRank\n }\n if (leftRank === 0) {\n const leftUpdatedAt =\n findLatestFormSubmissionApproval(left, formSubmissionApprovals)\n ?.updatedAt ?? ''\n const rightUpdatedAt =\n findLatestFormSubmissionApproval(right, formSubmissionApprovals)\n ?.updatedAt ?? ''\n return leftUpdatedAt.localeCompare(rightUpdatedAt)\n }\n return 0\n })\n return withUpdatedConcurrentFlags(sorted)\n}\n\nfunction sortConcurrentFlowInstanceNodes(\n nodes: ApprovalTypes.FlowInstanceNodeWithMeta[],\n formSubmissionApprovals: ApprovalTypes.FormSubmissionApproval[],\n currentlyViewingFormSubmissionApprovalId: string | undefined,\n) {\n const groups: ApprovalTypes.FlowInstanceNodeWithMeta[][] = []\n for (const node of nodes) {\n const currentGroup = groups[groups.length - 1]\n if (\n currentGroup &&\n currentGroup[0].parentStepLabelsId === node.parentStepLabelsId\n ) {\n currentGroup.push(node)\n } else {\n groups.push([node])\n }\n }\n\n return groups.flatMap((group) => {\n if (group.length < 2) {\n return group\n }\n return sortConcurrentGroup(\n group,\n formSubmissionApprovals,\n currentlyViewingFormSubmissionApprovalId,\n )\n })\n}\n\nfunction getFormApprovalFlowInstanceNodes(\n steps: ApprovalTypes.FormApprovalFlowInstanceStep[],\n) {\n return steps.reduce<\n Array<\n ApprovalTypes.FormApprovalFlowInstanceNode &\n Pick<\n ApprovalTypes.FlowInstanceNodeMeta,\n | 'parentStepLabelsId'\n | 'isConcurrentWithPrevious'\n | 'isConcurrentWithNext'\n >\n >\n >((memo, step) => {\n switch (step.type) {\n case 'CONCURRENT': {\n const parentStepLabelsId = step.nodes\n .map(({ label }) => label)\n .join('_')\n for (const node of step.nodes) {\n const index = step.nodes.indexOf(node)\n memo.push({\n ...node,\n parentStepLabelsId,\n isConcurrentWithPrevious: index !== 0,\n isConcurrentWithNext: index < step.nodes.length - 1,\n })\n }\n break\n }\n case 'STANDARD':\n default: {\n memo.push({\n ...step,\n isConcurrentWithPrevious: false,\n isConcurrentWithNext: false,\n parentStepLabelsId: step.label,\n })\n break\n }\n }\n return memo\n }, [])\n}\n\n/**\n * Inputs a form approval flow instance and set of form submission approvals and\n * returns a flattened array of nodes/steps with inlcuded metadata.\n *\n * #### Example\n *\n * ```js\n * const formApprovalFlowInstance = {\n * steps: [\n * {\n * type: 'STANDARD',\n * group: 'oneblink:administrator',\n * isSkipped: false,\n * label: 'Manager Approval',\n * approvalFormId: '1234',\n * },\n * {\n * type: 'CONCURRENT',\n * nodes: [\n * {\n * label: 'HR Review',\n * group: 'oneblink:administrator',\n * isSkipped: false,\n * approvalFormId: '1234',\n * },\n * {\n * label: 'Finance Review',\n * group: 'oneblink:administrator',\n * isSkipped: false,\n * approvalFormId: '1234',\n * },\n * ],\n * },\n * ],\n * }\n *\n * const formSubmissionApprovals = [\n * {\n * id: 'approval1',\n * stepLabel: 'Manager Approval',\n * status: 'APPROVED',\n * createdAt: '2024-03-20T10:00:00Z',\n * updatedAt: '2024-03-20T10:00:00Z',\n * updatedBy: 'fake@fake.com',\n * group: 'oneblink:administrator',\n * approvalFormId: '1234',\n * },\n * ]\n *\n * const nodesWithMeta = getFlowInstanceNodesWithMeta(\n * formApprovalFlowInstance,\n * formSubmissionApprovals,\n * )\n * ```\n *\n * Concurrent nodes are ordered as actioned (by the time they were actioned),\n * then the currently viewed pending approval if\n * `currentlyViewingFormSubmissionApprovalId` is provided, then remaining\n * unactioned nodes in definition order. Sequential steps around a concurrent\n * group are not reordered.\n *\n * @param formApprovalFlowInstance\n * @param formSubmissionApprovals\n * @param currentlyViewingFormSubmissionApprovalId The id of the approval\n * currently being viewed, used to keep that pending concurrent step next to\n * already actioned siblings.\n * @returns ApprovalTypes.FlowInstanceNodeWithMeta[]\n */\nexport function getFlowInstanceNodesWithMeta(\n formApprovalFlowInstance: ApprovalTypes.FormApprovalFlowInstance,\n formSubmissionApprovals: ApprovalTypes.FormSubmissionApproval[],\n currentlyViewingFormSubmissionApprovalId?: string,\n): ApprovalTypes.FlowInstanceNodeWithMeta[] {\n const formApprovalFlowInstanceNodes = getFormApprovalFlowInstanceNodes(\n formApprovalFlowInstance.steps,\n )\n let isDenied = false\n let isClarificationRequired = false\n const nodesWithMeta = formApprovalFlowInstanceNodes.map((step) => {\n const associatedApproval = findLatestFormSubmissionApproval(\n step,\n formSubmissionApprovals,\n )\n const status = possibleStatuses.find(\n ({ value }) => value === associatedApproval?.status,\n )\n\n let isDeniedInConcurrentStep = false\n let isClarificationRequiredInConcurrentStep = false\n if (status?.value === 'PENDING') {\n const siblingNodes = formApprovalFlowInstanceNodes.filter(\n ({ parentStepLabelsId, label }) =>\n parentStepLabelsId === step.parentStepLabelsId &&\n label !== step.label,\n )\n for (const siblingNode of siblingNodes) {\n const formSubmissionApproval = findLatestFormSubmissionApproval(\n siblingNode,\n formSubmissionApprovals,\n )\n if (formSubmissionApproval?.status === 'CLOSED') {\n isDeniedInConcurrentStep = true\n }\n if (formSubmissionApproval?.status === 'CLARIFICATION_REQUIRED') {\n isClarificationRequiredInConcurrentStep = true\n }\n }\n }\n\n const isAfterDeniedStep = isDenied\n if (status?.value === 'CLOSED') {\n isDenied = true\n }\n\n const isAfterClarificationRequiredStep = isClarificationRequired\n if (status?.value === 'CLARIFICATION_REQUIRED') {\n isClarificationRequired = true\n }\n\n let description = 'Awaiting previous approvals'\n if (step.isSkipped) {\n description = 'Skipped based on form submission'\n } else if (status) {\n const updatedByText = associatedApproval?.updatedBy\n ? ` by ${associatedApproval.updatedBy}`\n : ''\n description = `${status.label}${updatedByText}`\n } else if (isAfterDeniedStep) {\n description = 'Denied in a previous step'\n } else if (isAfterClarificationRequiredStep) {\n description = 'Sent for clarification in a previous step'\n }\n\n return {\n ...step,\n status,\n isDeniedInConcurrentStep,\n isAfterDeniedStep,\n isClarificationRequiredInConcurrentStep,\n isAfterClarificationRequiredStep,\n description,\n }\n })\n\n return sortConcurrentFlowInstanceNodes(\n nodesWithMeta,\n formSubmissionApprovals,\n currentlyViewingFormSubmissionApprovalId,\n )\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oneblink/apps-react",
|
|
3
3
|
"description": "Helper functions for OneBlink apps in ReactJS.",
|
|
4
|
-
"version": "13.0.0-beta.
|
|
4
|
+
"version": "13.0.0-beta.12",
|
|
5
5
|
"author": "OneBlink <developers@oneblink.io> (https://oneblink.io)",
|
|
6
6
|
"bugs": {
|
|
7
7
|
"url": "https://github.com/oneblink/apps-react/issues"
|