@oneblink/apps-react 13.0.0-beta.9 → 13.0.0
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 +10 -10
- package/dist/OneBlinkFormBase.js.map +1 -1
- package/dist/OneBlinkReadOnlyForm.d.ts +2 -1
- package/dist/OneBlinkReadOnlyForm.js +2 -1
- package/dist/OneBlinkReadOnlyForm.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/apps/services/api/submissions.d.ts +2 -1
- package/dist/apps/services/api/submissions.js +3 -1
- package/dist/apps/services/api/submissions.js.map +1 -1
- package/dist/apps/submission-service.d.ts +4 -1
- package/dist/apps/submission-service.js +3 -1
- package/dist/apps/submission-service.js.map +1 -1
- package/dist/form-elements/FormElementForm.js +7 -1
- package/dist/form-elements/FormElementForm.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 +2 -2
- 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/dynamic-elements.d.ts +7 -0
- package/dist/services/dynamic-elements.js +16 -2
- package/dist/services/dynamic-elements.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 +17 -3
- 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/dist/utils/read-only-form-elements.d.ts +22 -0
- package/dist/utils/read-only-form-elements.js +70 -0
- package/dist/utils/read-only-form-elements.js.map +1 -1
- package/package.json +3 -3
|
@@ -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"]}
|
|
@@ -1,6 +1,28 @@
|
|
|
1
1
|
import { FormTypes } from '@oneblink/types';
|
|
2
2
|
type FormElementWithEditability = FormTypes.FormElement | FormTypes.LookupFormElement;
|
|
3
3
|
export declare function checkIsFormElementIdEditable(element: FormElementWithEditability, editableFormElementIds?: string[]): boolean;
|
|
4
|
+
/**
|
|
5
|
+
* Returns the editable ids to provide while rendering or validating a nested
|
|
6
|
+
* form. Nested forms get their own whitelist so an id reused by the parent form
|
|
7
|
+
* cannot unlock a nested element (or vice versa).
|
|
8
|
+
*
|
|
9
|
+
* Selecting the nested form itself makes all of its descendants editable.
|
|
10
|
+
*/
|
|
11
|
+
export declare function getNestedEditableFormElementIds(formElement: FormTypes.FormFormElement, editableFormElementIds: string[] | undefined): string[] | undefined;
|
|
12
|
+
/**
|
|
13
|
+
* Expands a whitelist to cover the elements a lookup has injected into the
|
|
14
|
+
* form. An element naming an editable id in `injectedByElementId` becomes
|
|
15
|
+
* editable, as do its descendants, so an approver can correct answers that only
|
|
16
|
+
* appeared after a lookup they are allowed to re-run. A dynamic element is
|
|
17
|
+
* included whenever the element it was generated from is editable, since it
|
|
18
|
+
* sits beside that element rather than within it.
|
|
19
|
+
*
|
|
20
|
+
* Only applies to the root whitelist; ids inside a nested form are scoped by
|
|
21
|
+
* `getNestedEditableFormElementIds`. Returns `undefined` when no whitelist was
|
|
22
|
+
* provided, matching the “everything is editable” convention used by the rest
|
|
23
|
+
* of these helpers.
|
|
24
|
+
*/
|
|
25
|
+
export declare function expandEditableFormElementIds(editableFormElementIds: string[] | undefined, formElements: FormTypes.FormElement[]): string[] | undefined;
|
|
4
26
|
/**
|
|
5
27
|
* Whether this element, or anything nested inside it, can be changed.
|
|
6
28
|
*
|
|
@@ -1,6 +1,76 @@
|
|
|
1
|
+
import { getGeneratedByFormElementId } from '../services/dynamic-elements';
|
|
1
2
|
export function checkIsFormElementIdEditable(element, editableFormElementIds) {
|
|
2
3
|
return (editableFormElementIds === null || editableFormElementIds === void 0 ? void 0 : editableFormElementIds.includes(element.id)) === true;
|
|
3
4
|
}
|
|
5
|
+
/**
|
|
6
|
+
* Returns the editable ids to provide while rendering or validating a nested
|
|
7
|
+
* form. Nested forms get their own whitelist so an id reused by the parent form
|
|
8
|
+
* cannot unlock a nested element (or vice versa).
|
|
9
|
+
*
|
|
10
|
+
* Selecting the nested form itself makes all of its descendants editable.
|
|
11
|
+
*/
|
|
12
|
+
export function getNestedEditableFormElementIds(formElement, editableFormElementIds) {
|
|
13
|
+
if (editableFormElementIds === undefined) {
|
|
14
|
+
return undefined;
|
|
15
|
+
}
|
|
16
|
+
if (!editableFormElementIds.includes(formElement.id)) {
|
|
17
|
+
return [];
|
|
18
|
+
}
|
|
19
|
+
const nestedIds = new Set();
|
|
20
|
+
const addIds = (elements) => {
|
|
21
|
+
for (const element of elements) {
|
|
22
|
+
nestedIds.add(element.id);
|
|
23
|
+
if ('elements' in element && Array.isArray(element.elements)) {
|
|
24
|
+
addIds(element.elements);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
addIds(formElement.elements || []);
|
|
29
|
+
return [...nestedIds];
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Expands a whitelist to cover the elements a lookup has injected into the
|
|
33
|
+
* form. An element naming an editable id in `injectedByElementId` becomes
|
|
34
|
+
* editable, as do its descendants, so an approver can correct answers that only
|
|
35
|
+
* appeared after a lookup they are allowed to re-run. A dynamic element is
|
|
36
|
+
* included whenever the element it was generated from is editable, since it
|
|
37
|
+
* sits beside that element rather than within it.
|
|
38
|
+
*
|
|
39
|
+
* Only applies to the root whitelist; ids inside a nested form are scoped by
|
|
40
|
+
* `getNestedEditableFormElementIds`. Returns `undefined` when no whitelist was
|
|
41
|
+
* provided, matching the “everything is editable” convention used by the rest
|
|
42
|
+
* of these helpers.
|
|
43
|
+
*/
|
|
44
|
+
export function expandEditableFormElementIds(editableFormElementIds, formElements) {
|
|
45
|
+
if (editableFormElementIds === undefined) {
|
|
46
|
+
return undefined;
|
|
47
|
+
}
|
|
48
|
+
const expanded = new Set(editableFormElementIds);
|
|
49
|
+
const addInjected = (elements, isWithinInjectedElement = false) => {
|
|
50
|
+
for (const element of elements) {
|
|
51
|
+
const injectedByElementId = getInjectedByElementId(element);
|
|
52
|
+
const generatedByFormElementId = getGeneratedByFormElementId(element);
|
|
53
|
+
const isInjected = isWithinInjectedElement ||
|
|
54
|
+
(injectedByElementId !== undefined && expanded.has(injectedByElementId));
|
|
55
|
+
if (isInjected ||
|
|
56
|
+
(generatedByFormElementId !== undefined &&
|
|
57
|
+
expanded.has(generatedByFormElementId))) {
|
|
58
|
+
expanded.add(element.id);
|
|
59
|
+
}
|
|
60
|
+
if ('elements' in element && Array.isArray(element.elements)) {
|
|
61
|
+
addInjected(element.elements, isInjected);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
addInjected(formElements);
|
|
66
|
+
return [...expanded];
|
|
67
|
+
}
|
|
68
|
+
function getInjectedByElementId(element) {
|
|
69
|
+
if ('injectedByElementId' in element &&
|
|
70
|
+
typeof element.injectedByElementId === 'string') {
|
|
71
|
+
return element.injectedByElementId;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
4
74
|
function checkHasEditableFormElement(elements, editableFormElementIds) {
|
|
5
75
|
return elements.some((element) => checkIsFormElementIdEditable(element, editableFormElementIds) ||
|
|
6
76
|
('elements' in element &&
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"read-only-form-elements.js","sourceRoot":"","sources":["../../src/utils/read-only-form-elements.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"read-only-form-elements.js","sourceRoot":"","sources":["../../src/utils/read-only-form-elements.ts"],"names":[],"mappings":"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","sourcesContent":["import { FormTypes } from '@oneblink/types'\nimport { getGeneratedByFormElementId } from '../services/dynamic-elements'\n\ntype FormElementWithEditability =\n | FormTypes.FormElement\n | FormTypes.LookupFormElement\n\nexport function checkIsFormElementIdEditable(\n element: FormElementWithEditability,\n editableFormElementIds?: string[],\n): boolean {\n return editableFormElementIds?.includes(element.id) === true\n}\n\n/**\n * Returns the editable ids to provide while rendering or validating a nested\n * form. Nested forms get their own whitelist so an id reused by the parent form\n * cannot unlock a nested element (or vice versa).\n *\n * Selecting the nested form itself makes all of its descendants editable.\n */\nexport function getNestedEditableFormElementIds(\n formElement: FormTypes.FormFormElement,\n editableFormElementIds: string[] | undefined,\n): string[] | undefined {\n if (editableFormElementIds === undefined) {\n return undefined\n }\n\n if (!editableFormElementIds.includes(formElement.id)) {\n return []\n }\n\n const nestedIds = new Set<string>()\n const addIds = (elements: FormTypes.FormElement[]) => {\n for (const element of elements) {\n nestedIds.add(element.id)\n if ('elements' in element && Array.isArray(element.elements)) {\n addIds(element.elements)\n }\n }\n }\n addIds(formElement.elements || [])\n return [...nestedIds]\n}\n\n/**\n * Expands a whitelist to cover the elements a lookup has injected into the\n * form. An element naming an editable id in `injectedByElementId` becomes\n * editable, as do its descendants, so an approver can correct answers that only\n * appeared after a lookup they are allowed to re-run. A dynamic element is\n * included whenever the element it was generated from is editable, since it\n * sits beside that element rather than within it.\n *\n * Only applies to the root whitelist; ids inside a nested form are scoped by\n * `getNestedEditableFormElementIds`. Returns `undefined` when no whitelist was\n * provided, matching the “everything is editable” convention used by the rest\n * of these helpers.\n */\nexport function expandEditableFormElementIds(\n editableFormElementIds: string[] | undefined,\n formElements: FormTypes.FormElement[],\n): string[] | undefined {\n if (editableFormElementIds === undefined) {\n return undefined\n }\n\n const expanded = new Set(editableFormElementIds)\n\n const addInjected = (\n elements: FormTypes.FormElement[],\n isWithinInjectedElement = false,\n ) => {\n for (const element of elements) {\n const injectedByElementId = getInjectedByElementId(element)\n const generatedByFormElementId = getGeneratedByFormElementId(element)\n const isInjected =\n isWithinInjectedElement ||\n (injectedByElementId !== undefined && expanded.has(injectedByElementId))\n\n if (\n isInjected ||\n (generatedByFormElementId !== undefined &&\n expanded.has(generatedByFormElementId))\n ) {\n expanded.add(element.id)\n }\n\n if ('elements' in element && Array.isArray(element.elements)) {\n addInjected(element.elements, isInjected)\n }\n }\n }\n addInjected(formElements)\n\n return [...expanded]\n}\n\nfunction getInjectedByElementId(\n element: FormTypes.FormElement,\n): string | undefined {\n if (\n 'injectedByElementId' in element &&\n typeof element.injectedByElementId === 'string'\n ) {\n return element.injectedByElementId\n }\n}\n\nfunction checkHasEditableFormElement(\n elements: FormTypes.FormElement[],\n editableFormElementIds?: string[],\n): boolean {\n return elements.some(\n (element) =>\n checkIsFormElementIdEditable(element, editableFormElementIds) ||\n ('elements' in element &&\n Array.isArray(element.elements) &&\n checkHasEditableFormElement(element.elements, editableFormElementIds)),\n )\n}\n\n/**\n * Whether this element, or anything nested inside it, can be changed.\n *\n * When `editableFormElementIds` is omitted, every element is treated as\n * editable. When it is provided, only listed ids and containers that wrap them\n * are editable. Used to skip validating what the user has no way to correct.\n */\nexport function checkIsFormElementEditable(\n element: FormElementWithEditability,\n editableFormElementIds?: string[],\n): boolean {\n if (editableFormElementIds === undefined) {\n return true\n }\n\n return (\n checkIsFormElementIdEditable(element, editableFormElementIds) ||\n ('elements' in element &&\n Array.isArray(element.elements) &&\n checkHasEditableFormElement(element.elements, editableFormElementIds))\n )\n}\n\n/**\n * Whether a form element’s own controls should be locked.\n *\n * An id in `editableFormElementIds` always wins. When that list is provided,\n * every other element is locked (whitelist). Otherwise the whole-form read-only\n * flag and the element’s own `readOnly` apply.\n */\nexport function checkIsFormElementReadOnly(\n element: FormElementWithEditability,\n {\n isFormReadOnly,\n isInheritedReadOnly,\n editableFormElementIds,\n }: {\n isFormReadOnly?: boolean\n isInheritedReadOnly?: boolean\n editableFormElementIds?: string[]\n } = {},\n): boolean {\n if (checkIsFormElementIdEditable(element, editableFormElementIds)) {\n return false\n }\n\n if (editableFormElementIds !== undefined) {\n return true\n }\n\n if (isFormReadOnly || isInheritedReadOnly) {\n return true\n }\n\n return 'readOnly' in element && element.readOnly === true\n}\n\n/**\n * Whether lookups (and reverse geocode) must not run for this element.\n *\n * Definition `readOnly` and inherited container locks still allow submitter\n * lookups from prefills, defaults, and chained lookup buttons. Lookups are\n * blocked on a read-only form, or when a whitelist is provided and this id is\n * not on it (an approver must not re-run a lookup that could overwrite\n * persisted answers). An id in the whitelist always wins.\n */\nexport function checkAreLookupsDisallowed(\n element: FormElementWithEditability,\n {\n isFormReadOnly,\n editableFormElementIds,\n }: {\n isFormReadOnly?: boolean\n editableFormElementIds?: string[]\n } = {},\n): boolean {\n if (checkIsFormElementIdEditable(element, editableFormElementIds)) {\n return false\n }\n\n if (editableFormElementIds !== undefined) {\n return true\n }\n\n return isFormReadOnly === true\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
|
|
4
|
+
"version": "13.0.0",
|
|
5
5
|
"author": "OneBlink <developers@oneblink.io> (https://oneblink.io)",
|
|
6
6
|
"bugs": {
|
|
7
7
|
"url": "https://github.com/oneblink/apps-react/issues"
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
"@emotion/styled": "^11.14.1",
|
|
14
14
|
"@nylas/react": "^3.1.2",
|
|
15
15
|
"@oneblink/sdk-core": "^10.1.0-beta.9",
|
|
16
|
-
"@oneblink/storage": "^9.0.0-beta.
|
|
16
|
+
"@oneblink/storage": "^9.0.0-beta.4",
|
|
17
17
|
"@react-google-maps/api": "^2.20.8",
|
|
18
18
|
"@react-input/mask": "^2.0.4",
|
|
19
19
|
"@sentry/browser": "^10.38.0",
|
|
@@ -141,6 +141,6 @@
|
|
|
141
141
|
"test": "vitest",
|
|
142
142
|
"types": "npm i -D github:oneblink/types",
|
|
143
143
|
"typescript": "tsc --noEmit",
|
|
144
|
-
"update-dependents": "oneblink-release update-dependents --force"
|
|
144
|
+
"update-dependents": "oneblink-release update-dependents --force-update-dependency"
|
|
145
145
|
}
|
|
146
146
|
}
|