nucleus-core-ts 0.10.129 → 0.10.131
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/.build-ok +1 -1
- package/dist/fe/components/VerificationFlowPage/components/PendingTab.js +12 -0
- package/dist/fe/components/VerificationFlowPage/components/VerificationEntityList.d.ts +14 -2
- package/dist/fe/components/VerificationFlowPage/components/VerificationEntityList.js +64 -1
- package/dist/fe/components/VerificationFlowPage/components/VerificationFlowPage.d.ts +1 -1
- package/dist/fe/components/VerificationFlowPage/components/VerificationFlowPage.js +254 -4
- package/dist/fe/components/VerificationFlowPage/index.d.ts +7 -0
- package/dist/fe/components/VerificationFlowPage/index.js +6 -0
- package/dist/fe/components/VerificationFlowPage/labels.d.ts +158 -0
- package/dist/fe/components/VerificationFlowPage/labels.js +134 -1
- package/dist/fe/components/VerificationFlowPage/model2/ActionRows.d.ts +37 -0
- package/dist/fe/components/VerificationFlowPage/model2/ActionRows.js +721 -0
- package/dist/fe/components/VerificationFlowPage/model2/AssigneeRows.d.ts +61 -0
- package/dist/fe/components/VerificationFlowPage/model2/AssigneeRows.js +324 -0
- package/dist/fe/components/VerificationFlowPage/model2/FlowDiagram.d.ts +13 -0
- package/dist/fe/components/VerificationFlowPage/model2/FlowDiagram.js +637 -0
- package/dist/fe/components/VerificationFlowPage/model2/OutcomeRows.d.ts +13 -0
- package/dist/fe/components/VerificationFlowPage/model2/OutcomeRows.js +261 -0
- package/dist/fe/components/VerificationFlowPage/model2/ReadinessCard.d.ts +30 -0
- package/dist/fe/components/VerificationFlowPage/model2/ReadinessCard.js +310 -0
- package/dist/fe/components/VerificationFlowPage/model2/StepCard.d.ts +37 -0
- package/dist/fe/components/VerificationFlowPage/model2/StepCard.js +273 -0
- package/dist/fe/components/VerificationFlowPage/model2/StepListEditor.d.ts +44 -0
- package/dist/fe/components/VerificationFlowPage/model2/StepListEditor.js +344 -0
- package/dist/fe/components/VerificationFlowPage/model2/editorGraph.d.ts +76 -0
- package/dist/fe/components/VerificationFlowPage/model2/editorGraph.js +385 -0
- package/dist/fe/components/VerificationFlowPage/model2/index.d.ts +9 -0
- package/dist/fe/components/VerificationFlowPage/model2/index.js +9 -0
- package/dist/fe/components/VerificationFlowPage/model2/theme.d.ts +156 -0
- package/dist/fe/components/VerificationFlowPage/model2/theme.js +312 -0
- package/dist/fe/components/VerificationFlowPage/store/index.d.ts +18 -0
- package/dist/fe/components/VerificationFlowPage/store/index.js +54 -0
- package/dist/fe/components/VerificationFlowPage/theme/index.d.ts +11 -0
- package/dist/fe/components/VerificationFlowPage/theme/index.js +6 -2
- package/dist/fe/components/VerificationFlowPage/types/index.d.ts +62 -1
- package/dist/fe/index.d.ts +2 -2
- package/dist/fe/index.js +1 -1
- package/dist/index.js +3 -3
- package/dist/server.js +3 -3
- package/dist/src/Client/ApiCaller/types.d.ts +23 -0
- package/dist/src/Services/Verification/model2Validate.d.ts +1 -1
- package/dist/src/Services/Verification/types.d.ts +50 -1
- package/package.json +1 -1
package/dist/.build-ok
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.10.
|
|
1
|
+
0.10.131
|
|
@@ -281,6 +281,18 @@ export function PendingTab({ pendingAction, decideAction, uploadSignatureAction,
|
|
|
281
281
|
store.decidingId === item.instance_id ? /*#__PURE__*/ _jsxs("div", {
|
|
282
282
|
className: theme.pending.card.actions,
|
|
283
283
|
children: [
|
|
284
|
+
typeof item.pass_no === 'number' && typeof item.max_passes === 'number' ? /*#__PURE__*/ _jsxs("p", {
|
|
285
|
+
className: theme.pending.card.decisionMeta,
|
|
286
|
+
children: [
|
|
287
|
+
item.max_passes > 1 ? say.pendingRound(item.pass_no, item.max_passes) : null,
|
|
288
|
+
item.max_passes > 1 ? ' · ' : null,
|
|
289
|
+
item.rejection_sends_back_to ? say.pendingRejectSendsBack(say.stepOption(item.rejection_sends_back_to.step_order, item.rejection_sends_back_to.step_name)) : say.pendingRejectEndsReview
|
|
290
|
+
]
|
|
291
|
+
}) : null,
|
|
292
|
+
item.is_last_pass === true && (item.max_passes ?? 1) > 1 ? /*#__PURE__*/ _jsx("p", {
|
|
293
|
+
className: theme.pending.card.lastRoundWarning,
|
|
294
|
+
children: say.pendingLastRoundWarning
|
|
295
|
+
}) : null,
|
|
284
296
|
item.require_signature ? /*#__PURE__*/ _jsxs("label", {
|
|
285
297
|
className: "flex flex-col gap-1 text-xs",
|
|
286
298
|
children: [
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { VerificationFlowLabels } from '../labels';
|
|
2
|
-
import type { VerificationBulkStatusItem, VerificationCancelAction, VerificationEntityStatusesAction } from '../types';
|
|
2
|
+
import type { VerificationBulkStatusItem, VerificationCancelAction, VerificationEntityStatusesAction, VerificationStartForEntityAction } from '../types';
|
|
3
3
|
export type VerificationEntityListTheme = {
|
|
4
4
|
container: string;
|
|
5
5
|
header: string;
|
|
@@ -52,5 +52,17 @@ export type VerificationEntityListProps = {
|
|
|
52
52
|
* now the API had one and the product offered none.
|
|
53
53
|
*/
|
|
54
54
|
cancelAction?: VerificationCancelAction;
|
|
55
|
+
/**
|
|
56
|
+
* Puts a record back into review.
|
|
57
|
+
*
|
|
58
|
+
* It was declared on the page's props, passed in by the panel, and never
|
|
59
|
+
* destructured anywhere — so on a flow with `trigger_on: 'manual'` there was
|
|
60
|
+
* no way from the product to start a review at all. This list is the one
|
|
61
|
+
* screen that knows which chains have FINISHED, so a rejected record that has
|
|
62
|
+
* since been corrected gets its next review here. A record that has never
|
|
63
|
+
* been reviewed does not appear in this list (it is built from instances), so
|
|
64
|
+
* its first review still starts from the record's own screen.
|
|
65
|
+
*/
|
|
66
|
+
startForEntityAction?: VerificationStartForEntityAction;
|
|
55
67
|
};
|
|
56
|
-
export declare function VerificationEntityList({ entityName, entityStatusesAction, theme: themeOverrides, themeMode, onEntityClick, pageSize, labels, cancelAction, }: VerificationEntityListProps): import("react/jsx-runtime").JSX.Element;
|
|
68
|
+
export declare function VerificationEntityList({ entityName, entityStatusesAction, theme: themeOverrides, themeMode, onEntityClick, pageSize, labels, cancelAction, startForEntityAction, }: VerificationEntityListProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -97,7 +97,7 @@ const lightTheme = {
|
|
|
97
97
|
};
|
|
98
98
|
return map[status] ?? status;
|
|
99
99
|
}
|
|
100
|
-
export function VerificationEntityList({ entityName, entityStatusesAction, theme: themeOverrides, themeMode = 'dark', onEntityClick, pageSize = 20, labels, cancelAction }) {
|
|
100
|
+
export function VerificationEntityList({ entityName, entityStatusesAction, theme: themeOverrides, themeMode = 'dark', onEntityClick, pageSize = 20, labels, cancelAction, startForEntityAction }) {
|
|
101
101
|
const baseTheme = themeMode === 'light' ? lightTheme : darkTheme;
|
|
102
102
|
const t = {
|
|
103
103
|
...baseTheme,
|
|
@@ -113,6 +113,50 @@ export function VerificationEntityList({ entityName, entityStatusesAction, theme
|
|
|
113
113
|
const [cancellingId, setCancellingId] = useState(null);
|
|
114
114
|
const [cancelReason, setCancelReason] = useState('');
|
|
115
115
|
const [cancelNote, setCancelNote] = useState(null);
|
|
116
|
+
// Which row is being sent back into review, and what it was told.
|
|
117
|
+
const [startingId, setStartingId] = useState(null);
|
|
118
|
+
const [startNote, setStartNote] = useState(null);
|
|
119
|
+
const startAgain = useEffectEvent((item)=>{
|
|
120
|
+
if (!startForEntityAction) return;
|
|
121
|
+
setStartingId(item.instance_id);
|
|
122
|
+
setStartNote(null);
|
|
123
|
+
startForEntityAction.start({
|
|
124
|
+
payload: {
|
|
125
|
+
entity_name: item.entity_name,
|
|
126
|
+
entity_id: item.entity_id
|
|
127
|
+
},
|
|
128
|
+
onAfterHandle: (res)=>{
|
|
129
|
+
setStartingId(null);
|
|
130
|
+
const r = res;
|
|
131
|
+
/*
|
|
132
|
+
* The engine has plenty of legitimate reasons to refuse — no live flow
|
|
133
|
+
* for this entity, the first step resolves to nobody, a chain is
|
|
134
|
+
* already running — and each one is a sentence. Shown on the row rather
|
|
135
|
+
* than swallowed: a button that does nothing and says nothing is how
|
|
136
|
+
* "send to review does not work" became a support conversation.
|
|
137
|
+
*/ if (r.success === false) {
|
|
138
|
+
setStartNote({
|
|
139
|
+
id: item.instance_id,
|
|
140
|
+
text: r.message || say.startReviewFailed
|
|
141
|
+
});
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
setStartNote({
|
|
145
|
+
id: item.instance_id,
|
|
146
|
+
text: r.message || say.reviewStartedNote
|
|
147
|
+
});
|
|
148
|
+
loadStatuses(activeTab === 'all' ? undefined : activeTab, currentPage);
|
|
149
|
+
},
|
|
150
|
+
onErrorHandle: (err)=>{
|
|
151
|
+
setStartingId(null);
|
|
152
|
+
const e = err;
|
|
153
|
+
setStartNote({
|
|
154
|
+
id: item.instance_id,
|
|
155
|
+
text: e?.message || say.startReviewFailed
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
});
|
|
116
160
|
const loadStatuses = useEffectEvent((status, page)=>{
|
|
117
161
|
entityStatusesAction.start({
|
|
118
162
|
payload: {
|
|
@@ -332,6 +376,25 @@ export function VerificationEntityList({ entityName, entityStatusesAction, theme
|
|
|
332
376
|
className: `${t.statusBadge} ${getStatusBadgeClass(item.status)}`,
|
|
333
377
|
children: statusLabel(say, item.status)
|
|
334
378
|
}),
|
|
379
|
+
startForEntityAction && item.status !== 'active' ? /*#__PURE__*/ _jsxs("div", {
|
|
380
|
+
className: "shrink-0 flex flex-col items-end gap-1",
|
|
381
|
+
children: [
|
|
382
|
+
/*#__PURE__*/ _jsx("button", {
|
|
383
|
+
type: "button",
|
|
384
|
+
disabled: startingId === item.instance_id,
|
|
385
|
+
onClick: (e)=>{
|
|
386
|
+
e.stopPropagation();
|
|
387
|
+
startAgain(item);
|
|
388
|
+
},
|
|
389
|
+
className: themeMode === 'light' ? 'rounded-lg border border-gray-200 bg-white px-2.5 py-1 text-[11px] font-medium text-gray-600 hover:bg-gray-100 disabled:opacity-50 disabled:cursor-not-allowed' : 'rounded-lg border border-white/[0.08] bg-white/[0.04] px-2.5 py-1 text-[11px] font-medium text-white/60 hover:bg-white/[0.1] disabled:opacity-40 disabled:cursor-not-allowed',
|
|
390
|
+
children: startingId === item.instance_id ? say.startingReview : say.startNewReview
|
|
391
|
+
}),
|
|
392
|
+
startNote?.id === item.instance_id ? /*#__PURE__*/ _jsx("p", {
|
|
393
|
+
className: themeMode === 'light' ? 'max-w-[18rem] text-right text-[10px] leading-relaxed text-gray-500' : 'max-w-[18rem] text-right text-[10px] leading-relaxed text-white/45',
|
|
394
|
+
children: startNote.text
|
|
395
|
+
}) : null
|
|
396
|
+
]
|
|
397
|
+
}) : null,
|
|
335
398
|
cancelAction && item.status === 'active' ? cancellingId === item.instance_id ? // No handlers on this wrapper: the row above is a button
|
|
336
399
|
// when `onEntityClick` is set, so each INTERACTIVE child
|
|
337
400
|
// stops the bubble itself rather than a bare div doing it
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import '@xyflow/react/dist/style.css';
|
|
2
2
|
import type { ReactElement } from 'react';
|
|
3
3
|
import type { VerificationFlowPageProps } from '../types';
|
|
4
|
-
export declare function VerificationFlowPage({ entityName, title, subtitle, flowListAction, flowGetAction, flowSaveAction, flowPublishAction, flowUnpublishAction, flowDeleteAction, pendingAction, decideAction, uploadSignatureAction, listUsersAction, listRolesAction, entityStatusesAction, onFlowSelected, onDecisionMade, onEntityClick, onBack, showPending, showEntityStatuses, className, themeMode, labels, describeEntity, entityHref, availableChannels, initialTab, cancelAction, }: VerificationFlowPageProps): ReactElement;
|
|
4
|
+
export declare function VerificationFlowPage({ entityName, title, subtitle, flowListAction, flowGetAction, flowSaveAction, flowPublishAction, flowUnpublishAction, flowDeleteAction, pendingAction, decideAction, uploadSignatureAction, listUsersAction, listRolesAction, entityStatusesAction, onFlowSelected, onDecisionMade, onEntityClick, onBack, showPending, showEntityStatuses, className, themeMode, labels, describeEntity, entityHref, availableChannels, initialTab, cancelAction, startForEntityAction, authoringModel, flowFeasibilityAction, manageRolesHref, }: VerificationFlowPageProps): ReactElement;
|
|
@@ -5,7 +5,10 @@ import '@xyflow/react/dist/style.css';
|
|
|
5
5
|
import { useEffect, useEffectEvent, useRef, useState } from 'react';
|
|
6
6
|
import { cn } from '../../../utils/cn';
|
|
7
7
|
import { newId } from '../../../utils/newId';
|
|
8
|
-
import { DEFAULT_VERIFICATION_FLOW_LABELS, setActiveLabels } from '../labels';
|
|
8
|
+
import { DEFAULT_VERIFICATION_FLOW_LABELS, notificationChannelLabels, notificationTriggerLabels, recipientTypeLabels, setActiveLabels } from '../labels';
|
|
9
|
+
import { emptyModel2Graph } from '../model2/editorGraph';
|
|
10
|
+
import { StepListEditor } from '../model2/StepListEditor';
|
|
11
|
+
import { getModel2Theme } from '../model2/theme';
|
|
9
12
|
import { assignStepOrders } from '../stepOrder';
|
|
10
13
|
import { useVerificationFlowStore } from '../store';
|
|
11
14
|
import { getVerificationFlowTheme } from '../theme';
|
|
@@ -281,7 +284,7 @@ function buildSavePayload(flowId, entityName, flowName, flowDescription, trigger
|
|
|
281
284
|
};
|
|
282
285
|
}
|
|
283
286
|
// ─── Main Component ───────────────────────────────────────────────
|
|
284
|
-
export function VerificationFlowPage({ entityName, title, subtitle, flowListAction, flowGetAction, flowSaveAction, flowPublishAction, flowUnpublishAction, flowDeleteAction, pendingAction, decideAction, uploadSignatureAction, listUsersAction, listRolesAction, entityStatusesAction, onFlowSelected, onDecisionMade, onEntityClick, onBack, showPending = true, showEntityStatuses = false, className, themeMode = 'dark', labels, describeEntity, entityHref, availableChannels, initialTab, cancelAction }) {
|
|
287
|
+
export function VerificationFlowPage({ entityName, title, subtitle, flowListAction, flowGetAction, flowSaveAction, flowPublishAction, flowUnpublishAction, flowDeleteAction, pendingAction, decideAction, uploadSignatureAction, listUsersAction, listRolesAction, entityStatusesAction, onFlowSelected, onDecisionMade, onEntityClick, onBack, showPending = true, showEntityStatuses = false, className, themeMode = 'dark', labels, describeEntity, entityHref, availableChannels, initialTab, cancelAction, startForEntityAction, authoringModel = 1, flowFeasibilityAction, manageRolesHref }) {
|
|
285
288
|
const theme = getVerificationFlowTheme(themeMode);
|
|
286
289
|
setActiveTheme(theme);
|
|
287
290
|
const say = {
|
|
@@ -304,6 +307,15 @@ export function VerificationFlowPage({ entityName, title, subtitle, flowListActi
|
|
|
304
307
|
const [showCreateForm, setShowCreateForm] = useState(false);
|
|
305
308
|
const reactFlowWrapper = useRef(null);
|
|
306
309
|
const reactFlowInstance = useRef(null);
|
|
310
|
+
/*
|
|
311
|
+
* Which editor is on screen — NOT which one the host prefers.
|
|
312
|
+
*
|
|
313
|
+
* An existing flow always opens in the editor it was written in, decided by
|
|
314
|
+
* the `model_version` the server sends back, because opening a drawn flow in
|
|
315
|
+
* the list editor would show an empty chain for a flow that has three steps.
|
|
316
|
+
* `authoringModel` only decides what the CREATE button makes.
|
|
317
|
+
*/ const [isModel2, setIsModel2] = useState(false);
|
|
318
|
+
const m2Theme = getModel2Theme(themeMode);
|
|
307
319
|
const resolvedTitle = title || say.pageTitle(entityName);
|
|
308
320
|
const resolvedSubtitle = subtitle || entityName;
|
|
309
321
|
// ─── Node/Edge Change Handlers ────────────────────────────────
|
|
@@ -400,6 +412,54 @@ export function VerificationFlowPage({ entityName, title, subtitle, flowListActi
|
|
|
400
412
|
}
|
|
401
413
|
const name = flowName || say.defaultFlowName(entityName);
|
|
402
414
|
store.setIsSaving(true);
|
|
415
|
+
/*
|
|
416
|
+
* An authored chain sends a DIFFERENT body, and `graph` goes out empty
|
|
417
|
+
* beside it. The server reads `model2` only when `model_version` is 2, and
|
|
418
|
+
* `graph` only when it is not — sending both filled would leave the
|
|
419
|
+
* question of which one is the flow to whichever reader ran last.
|
|
420
|
+
*/ if (isModel2) {
|
|
421
|
+
flowSaveAction.start({
|
|
422
|
+
payload: {
|
|
423
|
+
flow_id: flowId,
|
|
424
|
+
entity_name: entityName,
|
|
425
|
+
name,
|
|
426
|
+
description: flowDescription || undefined,
|
|
427
|
+
trigger_on: flowTriggerOn,
|
|
428
|
+
is_draft: true,
|
|
429
|
+
model_version: 2,
|
|
430
|
+
max_passes: store.maxPasses,
|
|
431
|
+
model2: store.model2Graph,
|
|
432
|
+
graph: {
|
|
433
|
+
steps: [],
|
|
434
|
+
edges: [],
|
|
435
|
+
verifier_configs: [],
|
|
436
|
+
notification_rules: [],
|
|
437
|
+
notification_recipients: [],
|
|
438
|
+
notification_channels: []
|
|
439
|
+
}
|
|
440
|
+
},
|
|
441
|
+
onAfterHandle: (res)=>{
|
|
442
|
+
const resObj = res;
|
|
443
|
+
store.setIsSaving(false);
|
|
444
|
+
if (resObj.success === false) {
|
|
445
|
+
// Refused. The chain on screen was NOT written, so the editor stays
|
|
446
|
+
// dirty — clearing it here would tell the author their work is saved.
|
|
447
|
+
store.setError(resObj.message || say.saveRefused);
|
|
448
|
+
return;
|
|
449
|
+
}
|
|
450
|
+
store.setError(null);
|
|
451
|
+
if (resObj.flow_id) store.setSelectedFlowId(resObj.flow_id);
|
|
452
|
+
store.setIsDirty(false);
|
|
453
|
+
loadFlows();
|
|
454
|
+
},
|
|
455
|
+
onErrorHandle: (err)=>{
|
|
456
|
+
store.setIsSaving(false);
|
|
457
|
+
const e = err;
|
|
458
|
+
store.setError(e?.message || say.saveRefused);
|
|
459
|
+
}
|
|
460
|
+
});
|
|
461
|
+
return;
|
|
462
|
+
}
|
|
403
463
|
const viewport = reactFlowInstance.current ? reactFlowInstance.current.getViewport() : undefined;
|
|
404
464
|
const payload = buildSavePayload(flowId, entityName, name, flowDescription, flowTriggerOn, true, viewport, nodes, edges);
|
|
405
465
|
flowSaveAction.start({
|
|
@@ -492,6 +552,56 @@ export function VerificationFlowPage({ entityName, title, subtitle, flowListActi
|
|
|
492
552
|
}
|
|
493
553
|
});
|
|
494
554
|
});
|
|
555
|
+
// ─── The authored chain ───────────────────────────────────────
|
|
556
|
+
/*
|
|
557
|
+
* Asked of the graph on screen, not of the stored flow.
|
|
558
|
+
*
|
|
559
|
+
* The moment the answer is worth anything is while the author is still
|
|
560
|
+
* editing. An endpoint that only answered for saved flows would mean saving a
|
|
561
|
+
* flow you already suspect is broken in order to find out whether it is.
|
|
562
|
+
*/ const checkFeasibility = useEffectEvent(()=>{
|
|
563
|
+
if (!flowFeasibilityAction) return;
|
|
564
|
+
if ((store.model2Graph.steps ?? []).length === 0) {
|
|
565
|
+
store.setFeasibility(null);
|
|
566
|
+
return;
|
|
567
|
+
}
|
|
568
|
+
store.setFeasibilityLoading(true);
|
|
569
|
+
store.setFeasibilityError(null);
|
|
570
|
+
flowFeasibilityAction.start({
|
|
571
|
+
payload: {
|
|
572
|
+
entity_name: entityName,
|
|
573
|
+
max_passes: store.maxPasses,
|
|
574
|
+
model2: store.model2Graph
|
|
575
|
+
},
|
|
576
|
+
onAfterHandle: (res)=>{
|
|
577
|
+
store.setFeasibilityLoading(false);
|
|
578
|
+
const data = res?.data;
|
|
579
|
+
store.setFeasibility(data ?? null);
|
|
580
|
+
},
|
|
581
|
+
onErrorHandle: (err)=>{
|
|
582
|
+
store.setFeasibilityLoading(false);
|
|
583
|
+
const e = err;
|
|
584
|
+
/*
|
|
585
|
+
* The check failing is not the same as the flow being fine. Reported as
|
|
586
|
+
* its own line rather than left as a stale green card — a readiness
|
|
587
|
+
* panel that keeps showing the last good answer after it stopped being
|
|
588
|
+
* able to ask is worse than one that says it could not ask.
|
|
589
|
+
*/ store.setFeasibilityError(e?.message || say.readinessSaveRefusal);
|
|
590
|
+
store.setFeasibility(null);
|
|
591
|
+
}
|
|
592
|
+
});
|
|
593
|
+
});
|
|
594
|
+
const handleModel2Change = useEffectEvent((next, notes)=>{
|
|
595
|
+
store.setModel2Graph(next);
|
|
596
|
+
if (notes.length > 0) store.setModel2Notes(notes);
|
|
597
|
+
store.setIsDirty(true);
|
|
598
|
+
/*
|
|
599
|
+
* The readiness answer is about a graph that no longer exists the moment an
|
|
600
|
+
* edit lands, so it is dropped rather than left on screen. Re-asking on
|
|
601
|
+
* every keystroke would be a request per character; the author asks again
|
|
602
|
+
* with the button, and a save always re-asks on the server anyway.
|
|
603
|
+
*/ store.setFeasibility(null);
|
|
604
|
+
});
|
|
495
605
|
// ─── View Mode Handlers ──────────────────────────────────────
|
|
496
606
|
const openDesigner = useEffectEvent((flowId)=>{
|
|
497
607
|
store.setSelectedFlowId(flowId);
|
|
@@ -508,6 +618,11 @@ export function VerificationFlowPage({ entityName, title, subtitle, flowListActi
|
|
|
508
618
|
store.setSelectedFlowId(null);
|
|
509
619
|
setNodes([]);
|
|
510
620
|
setEdges([]);
|
|
621
|
+
setIsModel2(authoringModel === 2);
|
|
622
|
+
store.setModel2Graph(emptyModel2Graph());
|
|
623
|
+
store.clearModel2Notes();
|
|
624
|
+
store.setFeasibility(null);
|
|
625
|
+
store.setMaxPasses(3);
|
|
511
626
|
store.setIsDirty(true);
|
|
512
627
|
setShowCreateForm(false);
|
|
513
628
|
setViewMode('designer');
|
|
@@ -554,6 +669,24 @@ export function VerificationFlowPage({ entityName, title, subtitle, flowListActi
|
|
|
554
669
|
setFlowDescription(flow.description || '');
|
|
555
670
|
setFlowTriggerOn(flow.trigger_on || 'create');
|
|
556
671
|
}
|
|
672
|
+
/*
|
|
673
|
+
* The STORED model decides which editor opens, not the host's
|
|
674
|
+
* preference. A model-2 flow read into the drawn editor would render an
|
|
675
|
+
* empty canvas for a chain that has three steps, and the author's next
|
|
676
|
+
* save would write that emptiness back.
|
|
677
|
+
*/ const model2 = resObj.data?.model2;
|
|
678
|
+
if (model2) {
|
|
679
|
+
setIsModel2(true);
|
|
680
|
+
store.setModel2Graph(model2);
|
|
681
|
+
store.setMaxPasses(Number(flow?.max_passes ?? 3));
|
|
682
|
+
store.setFeasibility(null);
|
|
683
|
+
store.clearModel2Notes();
|
|
684
|
+
setNodes([]);
|
|
685
|
+
setEdges([]);
|
|
686
|
+
store.setLoadingGraph(false);
|
|
687
|
+
return;
|
|
688
|
+
}
|
|
689
|
+
setIsModel2(false);
|
|
557
690
|
const graph = resObj.data?.graph;
|
|
558
691
|
if (graph?.steps && graph.steps.length > 0) {
|
|
559
692
|
store.setGraphData(graph);
|
|
@@ -643,6 +776,19 @@ export function VerificationFlowPage({ entityName, title, subtitle, flowListActi
|
|
|
643
776
|
}, [
|
|
644
777
|
store.selectedFlowId
|
|
645
778
|
]);
|
|
779
|
+
/*
|
|
780
|
+
* Roles and people are loaded when the AUTHORED editor opens, not on mount.
|
|
781
|
+
*
|
|
782
|
+
* On mount would mean two directory requests on every visit to a screen most
|
|
783
|
+
* people open to look at the pending queue, and the pickers those lists feed
|
|
784
|
+
* do not exist unless the list editor is on screen. Loaded once per opening
|
|
785
|
+
* rather than per step card — see `loadDirectories`.
|
|
786
|
+
*/ useEffect(()=>{
|
|
787
|
+
if (viewMode === 'designer' && isModel2) loadDirectories();
|
|
788
|
+
}, [
|
|
789
|
+
viewMode,
|
|
790
|
+
isModel2
|
|
791
|
+
]);
|
|
646
792
|
// ─── Derived State ────────────────────────────────────────────
|
|
647
793
|
const selectedFlow = store.flows.find((f)=>f.id === store.selectedFlowId);
|
|
648
794
|
const selectedNode = store.selectedNodeId ? nodes.find((n)=>n.id === store.selectedNodeId) || null : null;
|
|
@@ -675,6 +821,73 @@ export function VerificationFlowPage({ entityName, title, subtitle, flowListActi
|
|
|
675
821
|
delete: say.triggerOnDelete,
|
|
676
822
|
manual: say.triggerOnManual
|
|
677
823
|
};
|
|
824
|
+
/*
|
|
825
|
+
* The enum → label maps the authored editor needs, built from the same
|
|
826
|
+
* functions the drawn editor's panel uses. Two maps would be two spellings of
|
|
827
|
+
* `on_step_reached` on two screens of one product.
|
|
828
|
+
*/ const notifTriggerLabels = notificationTriggerLabels(say);
|
|
829
|
+
const recipientLabels = recipientTypeLabels(say);
|
|
830
|
+
const channelLabelMap = notificationChannelLabels(say);
|
|
831
|
+
/*
|
|
832
|
+
* Roles and people, once, for the whole authored editor.
|
|
833
|
+
*
|
|
834
|
+
* The drawn editor's properties panel fetches its own and caches them in
|
|
835
|
+
* module scope, because it is mounted and unmounted as nodes are selected.
|
|
836
|
+
* The list editor shows every step at once, so a fetch per step card would be
|
|
837
|
+
* one request per step — and every assignee picker would be a separately
|
|
838
|
+
* paginated list of the same twenty people.
|
|
839
|
+
*
|
|
840
|
+
* A high limit rather than pagination: these pickers name reviewers, and a
|
|
841
|
+
* role that is on page two of the list is a role an author cannot choose.
|
|
842
|
+
* When an install outgrows this the readiness card is what catches it — a
|
|
843
|
+
* slot pointing at nothing is blocking there.
|
|
844
|
+
*/ const [roleOptions, setRoleOptions] = useState([]);
|
|
845
|
+
const [userOptions, setUserOptions] = useState([]);
|
|
846
|
+
const loadDirectories = useEffectEvent(()=>{
|
|
847
|
+
if (listRolesAction) {
|
|
848
|
+
listRolesAction.start({
|
|
849
|
+
payload: {
|
|
850
|
+
page: 1,
|
|
851
|
+
limit: 200
|
|
852
|
+
},
|
|
853
|
+
onAfterHandle: (res)=>{
|
|
854
|
+
const dataObj = res?.data;
|
|
855
|
+
const items = dataObj?.items || dataObj?.data || [];
|
|
856
|
+
setRoleOptions(items.map((r)=>({
|
|
857
|
+
id: r.id,
|
|
858
|
+
name: r.name
|
|
859
|
+
})));
|
|
860
|
+
},
|
|
861
|
+
onErrorHandle: ()=>{
|
|
862
|
+
setRoleOptions([]);
|
|
863
|
+
}
|
|
864
|
+
});
|
|
865
|
+
}
|
|
866
|
+
if (listUsersAction) {
|
|
867
|
+
listUsersAction.start({
|
|
868
|
+
payload: {
|
|
869
|
+
page: 1,
|
|
870
|
+
limit: 200
|
|
871
|
+
},
|
|
872
|
+
onAfterHandle: (res)=>{
|
|
873
|
+
const dataObj = res?.data;
|
|
874
|
+
const items = dataObj?.items || dataObj?.data || [];
|
|
875
|
+
/*
|
|
876
|
+
* A person with no display name still has to be pickable, and by
|
|
877
|
+
* something a human recognises. The e-mail is that; the id is not —
|
|
878
|
+
* a reviewer chosen by uuid is a reviewer nobody can verify was the
|
|
879
|
+
* right one.
|
|
880
|
+
*/ setUserOptions(items.map((u)=>({
|
|
881
|
+
id: u.id,
|
|
882
|
+
name: u.name || u.email || u.id
|
|
883
|
+
})));
|
|
884
|
+
},
|
|
885
|
+
onErrorHandle: ()=>{
|
|
886
|
+
setUserOptions([]);
|
|
887
|
+
}
|
|
888
|
+
});
|
|
889
|
+
}
|
|
890
|
+
});
|
|
678
891
|
// ─── Render ───────────────────────────────────────────────────
|
|
679
892
|
return /*#__PURE__*/ _jsxs("div", {
|
|
680
893
|
className: cn(theme.layout.root, className),
|
|
@@ -1111,7 +1324,37 @@ export function VerificationFlowPage({ entityName, title, subtitle, flowListActi
|
|
|
1111
1324
|
})
|
|
1112
1325
|
]
|
|
1113
1326
|
}),
|
|
1114
|
-
viewMode === 'designer' && store.activeTab === 'flow' && /*#__PURE__*/
|
|
1327
|
+
viewMode === 'designer' && store.activeTab === 'flow' && isModel2 && /*#__PURE__*/ _jsx(StepListEditor, {
|
|
1328
|
+
graph: store.model2Graph,
|
|
1329
|
+
maxPasses: store.maxPasses,
|
|
1330
|
+
notes: store.model2Notes,
|
|
1331
|
+
roles: roleOptions,
|
|
1332
|
+
users: userOptions,
|
|
1333
|
+
triggerLabels: notifTriggerLabels,
|
|
1334
|
+
recipientTypeLabels: recipientLabels,
|
|
1335
|
+
channelLabels: channelLabelMap,
|
|
1336
|
+
availableChannels: availableChannels,
|
|
1337
|
+
feasibility: store.feasibility,
|
|
1338
|
+
isCheckingFeasibility: store.isCheckingFeasibility,
|
|
1339
|
+
feasibilityError: store.feasibilityError,
|
|
1340
|
+
manageRolesHref: manageRolesHref,
|
|
1341
|
+
diagramOpen: store.diagramOpen,
|
|
1342
|
+
say: say,
|
|
1343
|
+
theme: m2Theme,
|
|
1344
|
+
themeMode: themeMode,
|
|
1345
|
+
disabled: false,
|
|
1346
|
+
newStepId: newId,
|
|
1347
|
+
onGraphChange: handleModel2Change,
|
|
1348
|
+
onMaxPassesChange: (v)=>{
|
|
1349
|
+
store.setMaxPasses(v);
|
|
1350
|
+
store.setIsDirty(true);
|
|
1351
|
+
},
|
|
1352
|
+
onDismissNotes: store.clearModel2Notes,
|
|
1353
|
+
onToggleDiagram: ()=>store.setDiagramOpen(!store.diagramOpen),
|
|
1354
|
+
onRecheckFeasibility: checkFeasibility,
|
|
1355
|
+
confirmRemoveStep: (step)=>typeof window === 'undefined' ? true : window.confirm(say.removeStepConfirm(step.name || say.stepNumber(step.step_order)))
|
|
1356
|
+
}),
|
|
1357
|
+
viewMode === 'designer' && store.activeTab === 'flow' && !isModel2 && /*#__PURE__*/ _jsxs("div", {
|
|
1115
1358
|
className: theme.layout.main,
|
|
1116
1359
|
children: [
|
|
1117
1360
|
/*#__PURE__*/ _jsx("aside", {
|
|
@@ -1308,7 +1551,14 @@ export function VerificationFlowPage({ entityName, title, subtitle, flowListActi
|
|
|
1308
1551
|
onEntityClick: onEntityClick,
|
|
1309
1552
|
themeMode: themeMode,
|
|
1310
1553
|
labels: say,
|
|
1311
|
-
cancelAction: cancelAction
|
|
1554
|
+
cancelAction: cancelAction,
|
|
1555
|
+
/*
|
|
1556
|
+
* Declared on these props, passed in by the panel, and destructured
|
|
1557
|
+
* NOWHERE until now — so on a flow with `trigger_on: 'manual'` the
|
|
1558
|
+
* product had no way to put a record into review at all. The list
|
|
1559
|
+
* knows which chains have finished, so a corrected record gets its
|
|
1560
|
+
* next review from the row it is already on.
|
|
1561
|
+
*/ startForEntityAction: startForEntityAction
|
|
1312
1562
|
})
|
|
1313
1563
|
})
|
|
1314
1564
|
]
|
|
@@ -6,6 +6,13 @@ export { VerificationEntityList, type VerificationEntityListProps, type Verifica
|
|
|
6
6
|
export { VerificationFlowPage } from './components/VerificationFlowPage';
|
|
7
7
|
export { VerificationStatusCard, type VerificationStatusCardProps, type VerificationStatusCardTheme, } from './components/VerificationStatusCard';
|
|
8
8
|
export { DEFAULT_VERIFICATION_FLOW_LABELS, type VerificationFlowLabels, } from './labels';
|
|
9
|
+
/**
|
|
10
|
+
* The authored-chain editor. Exported whole, because a host that wants a
|
|
11
|
+
* different arrangement of the same blocks — the readiness card beside the
|
|
12
|
+
* list, say, or the diagram on its own screen — can only have it if the pieces
|
|
13
|
+
* are reachable.
|
|
14
|
+
*/
|
|
15
|
+
export * from './model2';
|
|
9
16
|
export { useVerificationFlowStore } from './store';
|
|
10
17
|
export type { VerificationFlowPageTheme } from './theme';
|
|
11
18
|
export { extendVerificationFlowPageTheme, getVerificationFlowTheme, verificationFlowPageLightTheme, verificationFlowPageTheme, } from './theme';
|
|
@@ -6,5 +6,11 @@ export { VerificationEntityList } from './components/VerificationEntityList';
|
|
|
6
6
|
export { VerificationFlowPage } from './components/VerificationFlowPage';
|
|
7
7
|
export { VerificationStatusCard } from './components/VerificationStatusCard';
|
|
8
8
|
export { DEFAULT_VERIFICATION_FLOW_LABELS } from './labels';
|
|
9
|
+
/**
|
|
10
|
+
* The authored-chain editor. Exported whole, because a host that wants a
|
|
11
|
+
* different arrangement of the same blocks — the readiness card beside the
|
|
12
|
+
* list, say, or the diagram on its own screen — can only have it if the pieces
|
|
13
|
+
* are reachable.
|
|
14
|
+
*/ export * from './model2';
|
|
9
15
|
export { useVerificationFlowStore } from './store';
|
|
10
16
|
export { extendVerificationFlowPageTheme, getVerificationFlowTheme, verificationFlowPageLightTheme, verificationFlowPageTheme } from './theme';
|