@sanity/workflow-studio-plugin 0.20.0 → 0.22.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/CHANGELOG.md +123 -0
- package/README.md +9 -1
- package/dist/_chunks-cjs/index.cjs +611 -290
- package/dist/_chunks-cjs/workflows-tool-root.cjs +504 -23
- package/dist/_chunks-es/index.js +607 -292
- package/dist/_chunks-es/workflows-tool-root.js +508 -25
- package/package.json +16 -14
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
|
-
var jsxRuntime = require("react/jsx-runtime"), ui = require("@sanity/ui"), react = require("react"), router = require("sanity/router"), index = require("./index.cjs"), ArrowLeft = require("@sanity/icons/ArrowLeft"), workflowDiagram = require("@sanity/workflow-diagram"), workflowEngine = require("@sanity/workflow-engine"), Add = require("@sanity/icons/Add"), Search = require("@sanity/icons/Search"), Checkmark = require("@sanity/icons/Checkmark"), Close = require("@sanity/icons/Close"), Filter = require("@sanity/icons/Filter"), workflowComponents = require("@sanity/workflow-components"), endOfDay = require("date-fns/endOfDay"), reactDom = require("react-dom"),
|
|
3
|
+
var jsxRuntime = require("react/jsx-runtime"), ui = require("@sanity/ui"), workflowReact = require("@sanity/workflow-react"), react = require("react"), router = require("sanity/router"), index = require("./index.cjs"), ArrowLeft = require("@sanity/icons/ArrowLeft"), workflowDiagram = require("@sanity/workflow-diagram"), workflowEngine = require("@sanity/workflow-engine"), Add = require("@sanity/icons/Add"), Search = require("@sanity/icons/Search"), sanity = require("sanity"), Checkmark = require("@sanity/icons/Checkmark"), Close = require("@sanity/icons/Close"), Filter = require("@sanity/icons/Filter"), workflowComponents = require("@sanity/workflow-components"), endOfDay = require("date-fns/endOfDay"), reactDom = require("react-dom"), Calendar = require("@sanity/icons/Calendar"), workflowStudio = require("@sanity/workflow-studio");
|
|
4
4
|
|
|
5
5
|
function documentRefsOf(instance) {
|
|
6
6
|
const seen = /* @__PURE__ */ new Map;
|
|
@@ -18,14 +18,19 @@ function InstanceTaskLists({entry: entry, onOpenActivity: onOpenActivity}) {
|
|
|
18
18
|
onOpenActivity: onOpenActivity
|
|
19
19
|
}),
|
|
20
20
|
/* @__PURE__ */ jsxRuntime.jsx(index.TodoItemsList, {
|
|
21
|
-
entry: entry
|
|
21
|
+
entry: entry,
|
|
22
|
+
surface: "tool-instance-detail"
|
|
22
23
|
}) ]
|
|
23
24
|
})
|
|
24
25
|
});
|
|
25
26
|
}
|
|
26
27
|
|
|
27
28
|
function WorkflowInstanceDetail({instanceId: instanceId, onBack: onBack}) {
|
|
28
|
-
const entry = index.useWorkflowInstanceEntry(instanceId)
|
|
29
|
+
const entry = index.useWorkflowInstanceEntry(instanceId);
|
|
30
|
+
index.useLogEventOnMount(index.WorkflowInstanceDetailViewed, {
|
|
31
|
+
instanceId: instanceId
|
|
32
|
+
});
|
|
33
|
+
const [graceOver, setGraceOver] = react.useState(!1);
|
|
29
34
|
return react.useEffect(() => {
|
|
30
35
|
const timer = setTimeout(() => setGraceOver(!0), 2500);
|
|
31
36
|
return () => clearTimeout(timer);
|
|
@@ -119,7 +124,8 @@ function InstanceDetailPanel({entry: entry, onBack: onBack}) {
|
|
|
119
124
|
breadcrumb: index.instanceBreadcrumb(instance, definition),
|
|
120
125
|
definition: definition,
|
|
121
126
|
entry: entry,
|
|
122
|
-
onClose: () => setOpenActivity(null)
|
|
127
|
+
onClose: () => setOpenActivity(null),
|
|
128
|
+
source: "tool-instance-detail"
|
|
123
129
|
}) : null ]
|
|
124
130
|
});
|
|
125
131
|
}
|
|
@@ -260,7 +266,8 @@ function NewWorkflowButton() {
|
|
|
260
266
|
onPick: definition => {
|
|
261
267
|
setOpen(!1), openStartDialog({
|
|
262
268
|
definition: definition.name,
|
|
263
|
-
label: definition.title
|
|
269
|
+
label: definition.title,
|
|
270
|
+
source: "tool"
|
|
264
271
|
});
|
|
265
272
|
}
|
|
266
273
|
}) : null,
|
|
@@ -367,7 +374,8 @@ function ToolActivityDialog({target: target, onClose: onClose}) {
|
|
|
367
374
|
definition: definition,
|
|
368
375
|
document: documentRefsOf(entry.instance)[0],
|
|
369
376
|
entry: entry,
|
|
370
|
-
onClose: onClose
|
|
377
|
+
onClose: onClose,
|
|
378
|
+
source: "tool-task-list"
|
|
371
379
|
}) : /* @__PURE__ */ jsxRuntime.jsx(ui.Dialog, {
|
|
372
380
|
header: "Loading activity…",
|
|
373
381
|
id: "workflow-activity-detail",
|
|
@@ -382,6 +390,147 @@ function ToolActivityDialog({target: target, onClose: onClose}) {
|
|
|
382
390
|
});
|
|
383
391
|
}
|
|
384
392
|
|
|
393
|
+
const GROUP_DEFAULT_OPEN = !0;
|
|
394
|
+
|
|
395
|
+
function instanceBandDefaultOpen(segment) {
|
|
396
|
+
return segment === "for-me";
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
const UNGROUPED_BAND_KEY = "section:no-document", PAYLOAD_VERSION = 1, MAX_ENTRIES = 200;
|
|
400
|
+
|
|
401
|
+
function bandStateStorageKey(scope) {
|
|
402
|
+
return `sanity.workflows.tool.bands:${scope}`;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
function isRecord(value) {
|
|
406
|
+
return typeof value == "object" && value !== null && !Array.isArray(value);
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
function bandEntryOf(value) {
|
|
410
|
+
if (!isRecord(value)) return;
|
|
411
|
+
const {open: open, touched: touched} = value;
|
|
412
|
+
if (!(typeof open != "boolean" || typeof touched != "number") && Number.isFinite(touched)) return {
|
|
413
|
+
open: open,
|
|
414
|
+
touched: touched
|
|
415
|
+
};
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
function parsePayload(raw) {
|
|
419
|
+
try {
|
|
420
|
+
const parsed = JSON.parse(raw);
|
|
421
|
+
if (!isRecord(parsed) || parsed.version !== PAYLOAD_VERSION || !isRecord(parsed.bands)) return;
|
|
422
|
+
const entries = [];
|
|
423
|
+
for (const [key, value] of Object.entries(parsed.bands)) {
|
|
424
|
+
const entry = bandEntryOf(value);
|
|
425
|
+
if (entry === void 0) return;
|
|
426
|
+
entries.push([ key, entry ]);
|
|
427
|
+
}
|
|
428
|
+
return Object.fromEntries(entries);
|
|
429
|
+
} catch {
|
|
430
|
+
return;
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
function readStored(storage, storageKey) {
|
|
435
|
+
if (!storage) return {
|
|
436
|
+
entries: void 0,
|
|
437
|
+
corrupt: !1
|
|
438
|
+
};
|
|
439
|
+
let raw;
|
|
440
|
+
try {
|
|
441
|
+
raw = storage.getItem(storageKey);
|
|
442
|
+
} catch {
|
|
443
|
+
return {
|
|
444
|
+
entries: void 0,
|
|
445
|
+
corrupt: !1
|
|
446
|
+
};
|
|
447
|
+
}
|
|
448
|
+
if (raw === null) return {
|
|
449
|
+
entries: {},
|
|
450
|
+
corrupt: !1
|
|
451
|
+
};
|
|
452
|
+
const entries = parsePayload(raw);
|
|
453
|
+
return {
|
|
454
|
+
entries: entries,
|
|
455
|
+
corrupt: entries === void 0
|
|
456
|
+
};
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
function writeStored(args) {
|
|
460
|
+
if (!args.storage) return !1;
|
|
461
|
+
try {
|
|
462
|
+
return args.storage.setItem(args.storageKey, JSON.stringify({
|
|
463
|
+
version: PAYLOAD_VERSION,
|
|
464
|
+
bands: args.entries
|
|
465
|
+
})), !0;
|
|
466
|
+
} catch {
|
|
467
|
+
return !1;
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
function baseEntries(args) {
|
|
472
|
+
return args.stored === void 0 ? args.held : args.unpersisted ? mergeByTouched(args.stored, args.held) : args.stored;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
function mergeByTouched(stored, held) {
|
|
476
|
+
const merged = {
|
|
477
|
+
...stored
|
|
478
|
+
};
|
|
479
|
+
for (const [key, entry] of Object.entries(held)) {
|
|
480
|
+
const persisted = merged[key];
|
|
481
|
+
(persisted === void 0 || entry.touched >= persisted.touched) && (merged[key] = entry);
|
|
482
|
+
}
|
|
483
|
+
return merged;
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
function prune(entries) {
|
|
487
|
+
const held = Object.entries(entries);
|
|
488
|
+
return held.length <= MAX_ENTRIES ? entries : Object.fromEntries(held.sort(([, a], [, b]) => b.touched - a.touched).slice(0, MAX_ENTRIES));
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
function togglesOf(entries) {
|
|
492
|
+
return new Map(Object.entries(entries).map(([key, entry]) => [ key, entry.open ]));
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
function defaultStorage() {
|
|
496
|
+
try {
|
|
497
|
+
return globalThis.localStorage;
|
|
498
|
+
} catch {
|
|
499
|
+
return;
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
function createBandStateStore(args) {
|
|
504
|
+
const storage = args.storage ?? defaultStorage(), initial = readStored(storage, args.storageKey);
|
|
505
|
+
initial.corrupt && writeStored({
|
|
506
|
+
storage: storage,
|
|
507
|
+
storageKey: args.storageKey,
|
|
508
|
+
entries: {}
|
|
509
|
+
});
|
|
510
|
+
let held = initial.entries ?? {}, unpersisted = !1;
|
|
511
|
+
return {
|
|
512
|
+
read: () => togglesOf(held),
|
|
513
|
+
setOpen(key, open) {
|
|
514
|
+
const stored = readStored(storage, args.storageKey).entries, base = baseEntries({
|
|
515
|
+
stored: stored,
|
|
516
|
+
held: held,
|
|
517
|
+
unpersisted: unpersisted
|
|
518
|
+
});
|
|
519
|
+
return held = prune({
|
|
520
|
+
...base,
|
|
521
|
+
[key]: {
|
|
522
|
+
open: open,
|
|
523
|
+
touched: Date.now()
|
|
524
|
+
}
|
|
525
|
+
}), unpersisted = !writeStored({
|
|
526
|
+
storage: storage,
|
|
527
|
+
storageKey: args.storageKey,
|
|
528
|
+
entries: held
|
|
529
|
+
}), togglesOf(held);
|
|
530
|
+
}
|
|
531
|
+
};
|
|
532
|
+
}
|
|
533
|
+
|
|
385
534
|
function datesOf(state) {
|
|
386
535
|
return state.filter(entry => entry._type === "date" || entry._type === "datetime").map(entry => entry.value).filter(value => typeof value == "string" && value !== "");
|
|
387
536
|
}
|
|
@@ -539,6 +688,223 @@ function taskRowRender(row, entry) {
|
|
|
539
688
|
};
|
|
540
689
|
}
|
|
541
690
|
|
|
691
|
+
const NO_ORPHANS = {
|
|
692
|
+
groupKeys: /* @__PURE__ */ new Set,
|
|
693
|
+
workflows: []
|
|
694
|
+
};
|
|
695
|
+
|
|
696
|
+
function localBareIds(group, contentResource) {
|
|
697
|
+
const ids = group.documents.map(doc => index.gdrLocality(doc.id, contentResource));
|
|
698
|
+
if (!ids.some(locality => !locality.parsed || !locality.local)) return ids.flatMap(locality => locality.parsed ? [ locality.parsed.documentId ] : []);
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
function orphanProbeIds(groups, contentResource) {
|
|
702
|
+
const ids = /* @__PURE__ */ new Set;
|
|
703
|
+
for (const group of groups.withDocuments) for (const id of localBareIds(group, contentResource) ?? []) ids.add(id);
|
|
704
|
+
return [ ...ids ].sort();
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
function orphanVerdict(args) {
|
|
708
|
+
const groupKeys = /* @__PURE__ */ new Set, workflows = /* @__PURE__ */ new Map;
|
|
709
|
+
for (const group of args.groups.withDocuments) {
|
|
710
|
+
const ids = localBareIds(group, args.contentResource);
|
|
711
|
+
if (!(ids === void 0 || !ids.every(id => args.missing.has(id)))) {
|
|
712
|
+
groupKeys.add(group.key);
|
|
713
|
+
for (const band of group.instances) workflows.set(band.instanceId, {
|
|
714
|
+
instanceId: band.instanceId,
|
|
715
|
+
title: band.title,
|
|
716
|
+
documentIds: ids
|
|
717
|
+
});
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
return groupKeys.size === 0 ? NO_ORPHANS : {
|
|
721
|
+
groupKeys: groupKeys,
|
|
722
|
+
workflows: [ ...workflows.values() ]
|
|
723
|
+
};
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
function hideOrphanedGroups(groups, orphaned) {
|
|
727
|
+
return orphaned.size === 0 ? groups : cutTaskGroups(groups, {
|
|
728
|
+
keepRow: () => !0,
|
|
729
|
+
keepGroup: group => !orphaned.has(group.key)
|
|
730
|
+
});
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
const DRAFT_PREFIX = "drafts.";
|
|
734
|
+
|
|
735
|
+
async function missingDocIds(client, ids) {
|
|
736
|
+
const present = await client.fetch("*[_id in $ids || _id in $drafts]._id", {
|
|
737
|
+
ids: ids,
|
|
738
|
+
drafts: ids.map(id => `${DRAFT_PREFIX}${id}`)
|
|
739
|
+
}, {
|
|
740
|
+
perspective: "raw"
|
|
741
|
+
}), found = new Set(present.map(id => id.startsWith(DRAFT_PREFIX) ? id.slice(DRAFT_PREFIX.length) : id)), candidates = ids.filter(id => !found.has(id)), versions = await Promise.all(candidates.map(id => client.fetch("*[_id in path($version)][0]._id", {
|
|
742
|
+
version: `versions.*.${id}`
|
|
743
|
+
}, {
|
|
744
|
+
perspective: "raw"
|
|
745
|
+
})));
|
|
746
|
+
return new Set(candidates.filter((_, index2) => versions[index2] === null));
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
async function settleOrphans(args) {
|
|
750
|
+
const ids = [ ...new Set(args.workflows.flatMap(workflow => workflow.documentIds)) ].sort(), missing = await args.probeMissing(ids), stillOrphaned = workflow => workflow.documentIds.every(id => missing.has(id));
|
|
751
|
+
let settled = 0;
|
|
752
|
+
const failures = [];
|
|
753
|
+
for (const workflow of args.workflows.filter(stillOrphaned)) try {
|
|
754
|
+
await args.abort({
|
|
755
|
+
instanceId: workflow.instanceId,
|
|
756
|
+
reason: "Subject document deleted"
|
|
757
|
+
}), settled += 1;
|
|
758
|
+
} catch (err) {
|
|
759
|
+
failures.push({
|
|
760
|
+
workflow: workflow,
|
|
761
|
+
message: index.describeError(err)
|
|
762
|
+
});
|
|
763
|
+
}
|
|
764
|
+
return {
|
|
765
|
+
settled: settled,
|
|
766
|
+
skipped: args.workflows.filter(workflow => !stillOrphaned(workflow)),
|
|
767
|
+
failures: failures
|
|
768
|
+
};
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
function workflowTally(workflows) {
|
|
772
|
+
const counts = /* @__PURE__ */ new Map;
|
|
773
|
+
for (const workflow of workflows) counts.set(workflow.title, (counts.get(workflow.title) ?? 0) + 1);
|
|
774
|
+
return [ ...counts ].map(([title, count]) => count === 1 ? title : `${title} ×${count}`);
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
function settleToast(outcome) {
|
|
778
|
+
const total = outcome.settled + outcome.skipped.length + outcome.failures.length;
|
|
779
|
+
if (outcome.skipped.length === 0 && outcome.failures.length === 0) return {
|
|
780
|
+
status: "success",
|
|
781
|
+
title: outcome.settled === 1 ? "Cleaned up 1 workflow" : `Cleaned up ${outcome.settled} workflows`
|
|
782
|
+
};
|
|
783
|
+
const lines = [ ...outcome.skipped.map(workflow => `${workflow.title}: its document exists again — left running`), ...outcome.failures.map(failure => `${failure.workflow.title}: ${failure.message}`) ];
|
|
784
|
+
return {
|
|
785
|
+
status: "warning",
|
|
786
|
+
title: `Cleaned up ${outcome.settled} of ${total} workflows`,
|
|
787
|
+
description: lines.join(`\n`)
|
|
788
|
+
};
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
function recheckFailedToast(err) {
|
|
792
|
+
return {
|
|
793
|
+
status: "error",
|
|
794
|
+
title: "Nothing was cleaned up — the documents could not be re-checked",
|
|
795
|
+
description: index.describeError(err)
|
|
796
|
+
};
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
function OrphanedWorkflowsNote({orphans: orphans}) {
|
|
800
|
+
const [confirming, setConfirming] = react.useState(!1), count = orphans.workflows.length;
|
|
801
|
+
if (count === 0) return null;
|
|
802
|
+
const label = count === 1 ? "1 workflow is attached to a deleted document — its tasks aren’t listed." : `${count} workflows are attached to deleted documents — their tasks aren’t listed.`;
|
|
803
|
+
/* @__PURE__ */
|
|
804
|
+
return jsxRuntime.jsxs(jsxRuntime.Fragment, {
|
|
805
|
+
children: [
|
|
806
|
+
/* @__PURE__ */ jsxRuntime.jsx(index.CautionNote, {
|
|
807
|
+
action: /* @__PURE__ */ jsxRuntime.jsx(ui.Button, {
|
|
808
|
+
fontSize: 1,
|
|
809
|
+
mode: "ghost",
|
|
810
|
+
onClick: () => setConfirming(!0),
|
|
811
|
+
padding: 2,
|
|
812
|
+
text: "Clean up…"
|
|
813
|
+
}),
|
|
814
|
+
label: label
|
|
815
|
+
}), confirming ? /* @__PURE__ */ jsxRuntime.jsx(SettleOrphansDialog, {
|
|
816
|
+
onClose: () => setConfirming(!1),
|
|
817
|
+
workflows: orphans.workflows
|
|
818
|
+
}) : null ]
|
|
819
|
+
});
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
function SettleOrphansDialog({workflows: workflows, onClose: onClose}) {
|
|
823
|
+
const {engine: engine} = index.useWorkflowContext(), client = sanity.useClient({
|
|
824
|
+
apiVersion: index.WORKFLOW_API_VERSION
|
|
825
|
+
}), toast = index.useClosableToast(), [pending, setPending] = react.useState(!1), confirm = async () => {
|
|
826
|
+
setPending(!0);
|
|
827
|
+
try {
|
|
828
|
+
const outcome = await settleOrphans({
|
|
829
|
+
workflows: workflows,
|
|
830
|
+
probeMissing: ids => missingDocIds(client, ids),
|
|
831
|
+
abort: args => engine.abortInstance(args)
|
|
832
|
+
});
|
|
833
|
+
toast.push(settleToast(outcome));
|
|
834
|
+
} catch (err) {
|
|
835
|
+
toast.push(recheckFailedToast(err));
|
|
836
|
+
}
|
|
837
|
+
onClose();
|
|
838
|
+
};
|
|
839
|
+
/* @__PURE__ */
|
|
840
|
+
return jsxRuntime.jsx(ui.Dialog, {
|
|
841
|
+
footer: /* @__PURE__ */ jsxRuntime.jsx(ui.Box, {
|
|
842
|
+
padding: 3,
|
|
843
|
+
children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Flex, {
|
|
844
|
+
gap: 2,
|
|
845
|
+
justify: "flex-end",
|
|
846
|
+
children: [
|
|
847
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Button, {
|
|
848
|
+
disabled: pending,
|
|
849
|
+
fontSize: 1,
|
|
850
|
+
mode: "bleed",
|
|
851
|
+
onClick: onClose,
|
|
852
|
+
padding: 2,
|
|
853
|
+
text: "Cancel"
|
|
854
|
+
}),
|
|
855
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Button, {
|
|
856
|
+
fontSize: 1,
|
|
857
|
+
loading: pending,
|
|
858
|
+
onClick: () => {
|
|
859
|
+
confirm();
|
|
860
|
+
},
|
|
861
|
+
padding: 2,
|
|
862
|
+
text: workflows.length === 1 ? "Clean up 1 workflow" : `Clean up ${workflows.length} workflows`
|
|
863
|
+
}) ]
|
|
864
|
+
})
|
|
865
|
+
}),
|
|
866
|
+
header: "Clean up workflows",
|
|
867
|
+
id: "settle-orphaned-workflows",
|
|
868
|
+
onClose: onClose,
|
|
869
|
+
width: 0,
|
|
870
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(ui.Box, {
|
|
871
|
+
padding: 4,
|
|
872
|
+
children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Stack, {
|
|
873
|
+
gap: 4,
|
|
874
|
+
children: [
|
|
875
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
|
|
876
|
+
muted: !0,
|
|
877
|
+
size: 1,
|
|
878
|
+
children: "The following workflows exist for documents which have since been deleted:"
|
|
879
|
+
}),
|
|
880
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Stack, {
|
|
881
|
+
as: "ul",
|
|
882
|
+
gap: 2,
|
|
883
|
+
style: {
|
|
884
|
+
listStyle: "disc",
|
|
885
|
+
margin: 0,
|
|
886
|
+
paddingLeft: "1.25em"
|
|
887
|
+
},
|
|
888
|
+
children: workflowTally(workflows).map(line => /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
|
|
889
|
+
as: "li",
|
|
890
|
+
size: 1,
|
|
891
|
+
style: {
|
|
892
|
+
display: "list-item"
|
|
893
|
+
},
|
|
894
|
+
weight: "medium",
|
|
895
|
+
children: line
|
|
896
|
+
}, line))
|
|
897
|
+
}),
|
|
898
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
|
|
899
|
+
muted: !0,
|
|
900
|
+
size: 1,
|
|
901
|
+
children: "Cleaning up these workflows will close them."
|
|
902
|
+
}) ]
|
|
903
|
+
})
|
|
904
|
+
})
|
|
905
|
+
});
|
|
906
|
+
}
|
|
907
|
+
|
|
542
908
|
function taskFilterOptions(groups) {
|
|
543
909
|
const workflows = /* @__PURE__ */ new Map, documents = /* @__PURE__ */ new Map;
|
|
544
910
|
for (const group of groups.withDocuments) {
|
|
@@ -555,8 +921,33 @@ function taskFilterOptions(groups) {
|
|
|
555
921
|
};
|
|
556
922
|
}
|
|
557
923
|
|
|
924
|
+
function activeEntries(filters) {
|
|
925
|
+
return Object.entries(filters).filter(([, value]) => value !== void 0 && (!Array.isArray(value) || value.length > 0));
|
|
926
|
+
}
|
|
927
|
+
|
|
558
928
|
function hasActiveFilters(filters) {
|
|
559
|
-
return
|
|
929
|
+
return activeEntries(filters).length > 0;
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
function sameFilters(a, b) {
|
|
933
|
+
return sameValueSet(a.statuses, b.statuses) && sameValueSet(a.workflows, b.workflows) && sameValueSet(a.documentIds, b.documentIds) && sameValueSet(a.assigneeIds, b.assigneeIds) && sameDate(a.date, b.date);
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
function sameValueSet(a, b) {
|
|
937
|
+
const av = a ?? [], bv = b ?? [];
|
|
938
|
+
return av.length === bv.length && av.every(value => bv.includes(value));
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
function sameDate(a, b) {
|
|
942
|
+
return typeof a == "object" && typeof b == "object" ? a.before === b.before : a === b;
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
function taskFilterTelemetry(filters) {
|
|
946
|
+
const entries = activeEntries(filters);
|
|
947
|
+
return {
|
|
948
|
+
filterKinds: entries.map(([key]) => key).sort(),
|
|
949
|
+
filterCount: entries.reduce((n, [, value]) => n + (Array.isArray(value) ? value.length : 1), 0)
|
|
950
|
+
};
|
|
560
951
|
}
|
|
561
952
|
|
|
562
953
|
function toggledValue(args) {
|
|
@@ -711,7 +1102,12 @@ function useDocPreviewTitles(documents) {
|
|
|
711
1102
|
const HITS_CAP = 6;
|
|
712
1103
|
|
|
713
1104
|
function TaskFilterMenu({filters: filters, options: options, onChange: onChange}) {
|
|
714
|
-
const [open, setOpen] = react.useState(!1),
|
|
1105
|
+
const [open, setOpen] = react.useState(!1), telemetry = workflowReact.useWorkflowTelemetry(), openSnapshot = react.useRef(void 0), openMenu = () => {
|
|
1106
|
+
openSnapshot.current = filters, setOpen(!0);
|
|
1107
|
+
}, close = () => {
|
|
1108
|
+
setOpen(!1), openSnapshot.current !== void 0 && !sameFilters(openSnapshot.current, filters) && telemetry.log(index.WorkflowTaskFiltersApplied, taskFilterTelemetry(filters)),
|
|
1109
|
+
openSnapshot.current = void 0;
|
|
1110
|
+
}, active = hasActiveFilters(filters);
|
|
715
1111
|
/* @__PURE__ */
|
|
716
1112
|
return jsxRuntime.jsx(index.DismissablePopover, {
|
|
717
1113
|
content: open ? /* @__PURE__ */ jsxRuntime.jsx(FilterPanel, {
|
|
@@ -719,7 +1115,7 @@ function TaskFilterMenu({filters: filters, options: options, onChange: onChange}
|
|
|
719
1115
|
onChange: onChange,
|
|
720
1116
|
options: options
|
|
721
1117
|
}) : null,
|
|
722
|
-
onDismiss:
|
|
1118
|
+
onDismiss: close,
|
|
723
1119
|
open: open,
|
|
724
1120
|
overflow: "visible",
|
|
725
1121
|
children: /* @__PURE__ */ jsxRuntime.jsx(index.HoverHint, {
|
|
@@ -730,7 +1126,7 @@ function TaskFilterMenu({filters: filters, options: options, onChange: onChange}
|
|
|
730
1126
|
fontSize: 1,
|
|
731
1127
|
icon: Filter.FilterIcon,
|
|
732
1128
|
mode: "bleed",
|
|
733
|
-
onClick: () =>
|
|
1129
|
+
onClick: () => open ? close() : openMenu(),
|
|
734
1130
|
padding: 2,
|
|
735
1131
|
selected: active || open
|
|
736
1132
|
})
|
|
@@ -1226,7 +1622,7 @@ function ActivityDateControl({instanceId: instanceId, state: state, dates: dates
|
|
|
1226
1622
|
});
|
|
1227
1623
|
const kind = index.dateControlKind(state), commit = (mode, value) => {
|
|
1228
1624
|
state.kind === "editable" && save(() => editFieldFor(instanceId, {
|
|
1229
|
-
target:
|
|
1625
|
+
target: workflowReact.editFieldTarget(state.field),
|
|
1230
1626
|
...mode === "set" ? {
|
|
1231
1627
|
mode: mode,
|
|
1232
1628
|
value: value
|
|
@@ -1327,13 +1723,15 @@ function RefChips({refs: refs, max: max = 2}) {
|
|
|
1327
1723
|
|
|
1328
1724
|
const EMPTY_NOTE = "No tasks match — adjust the filters, or start a workflow", UNGROUPED_TITLE = "Workflows without Studio documents";
|
|
1329
1725
|
|
|
1330
|
-
function TaskGroupList({groups: groups, onOpenTask: onOpenTask, onOpenWorkflow: onOpenWorkflow, showTerminalActions: showTerminalActions}) {
|
|
1726
|
+
function TaskGroupList({bands: bands, groups: groups, instanceDefaultOpen: instanceDefaultOpen, onOpenTask: onOpenTask, onOpenWorkflow: onOpenWorkflow, showTerminalActions: showTerminalActions}) {
|
|
1331
1727
|
return groups.withDocuments.length === 0 && groups.withoutDocuments.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx(MutedNote, {
|
|
1332
1728
|
text: EMPTY_NOTE
|
|
1333
1729
|
}) : /* @__PURE__ */ jsxRuntime.jsxs(ui.Stack, {
|
|
1334
1730
|
gap: 2,
|
|
1335
1731
|
children: [ groups.withDocuments.map(group => /* @__PURE__ */ jsxRuntime.jsx(index.CollapsibleBand, {
|
|
1336
1732
|
background: !0,
|
|
1733
|
+
onToggle: () => bands.toggle(group.key, GROUP_DEFAULT_OPEN),
|
|
1734
|
+
open: bands.isOpen(group.key, GROUP_DEFAULT_OPEN),
|
|
1337
1735
|
sticky: !0,
|
|
1338
1736
|
title: workflowEngine.toBareId(group.document.id),
|
|
1339
1737
|
header: /* @__PURE__ */ jsxRuntime.jsx(ui.Flex, {
|
|
@@ -1348,6 +1746,8 @@ function TaskGroupList({groups: groups, onOpenTask: onOpenTask, onOpenWorkflow:
|
|
|
1348
1746
|
})
|
|
1349
1747
|
}),
|
|
1350
1748
|
children: /* @__PURE__ */ jsxRuntime.jsx(InstanceBands, {
|
|
1749
|
+
bands: bands,
|
|
1750
|
+
instanceDefaultOpen: instanceDefaultOpen,
|
|
1351
1751
|
instances: group.instances,
|
|
1352
1752
|
onOpenTask: onOpenTask,
|
|
1353
1753
|
onOpenWorkflow: onOpenWorkflow,
|
|
@@ -1355,6 +1755,8 @@ function TaskGroupList({groups: groups, onOpenTask: onOpenTask, onOpenWorkflow:
|
|
|
1355
1755
|
})
|
|
1356
1756
|
}, group.key)), groups.withoutDocuments.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx(index.CollapsibleBand, {
|
|
1357
1757
|
background: !0,
|
|
1758
|
+
onToggle: () => bands.toggle(UNGROUPED_BAND_KEY, GROUP_DEFAULT_OPEN),
|
|
1759
|
+
open: bands.isOpen(UNGROUPED_BAND_KEY, GROUP_DEFAULT_OPEN),
|
|
1358
1760
|
sticky: !0,
|
|
1359
1761
|
title: UNGROUPED_TITLE,
|
|
1360
1762
|
header: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
|
|
@@ -1363,6 +1765,8 @@ function TaskGroupList({groups: groups, onOpenTask: onOpenTask, onOpenWorkflow:
|
|
|
1363
1765
|
children: UNGROUPED_TITLE
|
|
1364
1766
|
}),
|
|
1365
1767
|
children: /* @__PURE__ */ jsxRuntime.jsx(InstanceBands, {
|
|
1768
|
+
bands: bands,
|
|
1769
|
+
instanceDefaultOpen: instanceDefaultOpen,
|
|
1366
1770
|
instances: groups.withoutDocuments,
|
|
1367
1771
|
onOpenTask: onOpenTask,
|
|
1368
1772
|
onOpenWorkflow: onOpenWorkflow,
|
|
@@ -1380,12 +1784,14 @@ function targetOf(row) {
|
|
|
1380
1784
|
};
|
|
1381
1785
|
}
|
|
1382
1786
|
|
|
1383
|
-
function InstanceBands({instances: instances, onOpenTask: onOpenTask, onOpenWorkflow: onOpenWorkflow, showTerminalActions: showTerminalActions}) {
|
|
1787
|
+
function InstanceBands({bands: bands, instanceDefaultOpen: instanceDefaultOpen, instances: instances, onOpenTask: onOpenTask, onOpenWorkflow: onOpenWorkflow, showTerminalActions: showTerminalActions}) {
|
|
1384
1788
|
/* @__PURE__ */
|
|
1385
1789
|
return jsxRuntime.jsx(ui.Stack, {
|
|
1386
1790
|
gap: 1,
|
|
1387
1791
|
children: instances.map(band => /* @__PURE__ */ jsxRuntime.jsx(InstanceBand, {
|
|
1388
1792
|
band: band,
|
|
1793
|
+
bands: bands,
|
|
1794
|
+
instanceDefaultOpen: instanceDefaultOpen,
|
|
1389
1795
|
onOpenTask: onOpenTask,
|
|
1390
1796
|
onOpenWorkflow: onOpenWorkflow,
|
|
1391
1797
|
showTerminalActions: showTerminalActions
|
|
@@ -1393,10 +1799,12 @@ function InstanceBands({instances: instances, onOpenTask: onOpenTask, onOpenWork
|
|
|
1393
1799
|
});
|
|
1394
1800
|
}
|
|
1395
1801
|
|
|
1396
|
-
function InstanceBand({band: band, onOpenTask: onOpenTask, onOpenWorkflow: onOpenWorkflow, showTerminalActions: showTerminalActions}) {
|
|
1802
|
+
function InstanceBand({band: band, bands: bands, instanceDefaultOpen: instanceDefaultOpen, onOpenTask: onOpenTask, onOpenWorkflow: onOpenWorkflow, showTerminalActions: showTerminalActions}) {
|
|
1397
1803
|
const {resolvePathFromState: resolvePathFromState} = router.useRouter();
|
|
1398
1804
|
/* @__PURE__ */
|
|
1399
1805
|
return jsxRuntime.jsx(index.CollapsibleBand, {
|
|
1806
|
+
onToggle: () => bands.toggle(band.instanceId, instanceDefaultOpen),
|
|
1807
|
+
open: bands.isOpen(band.instanceId, instanceDefaultOpen),
|
|
1400
1808
|
title: band.title,
|
|
1401
1809
|
header: /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, {
|
|
1402
1810
|
children: [
|
|
@@ -1483,6 +1891,61 @@ function TaskActivityRow({row: row, onOpen: onOpen, showTerminalActions: showTer
|
|
|
1483
1891
|
});
|
|
1484
1892
|
}
|
|
1485
1893
|
|
|
1894
|
+
function useBandOpenState(args) {
|
|
1895
|
+
const {segment: segment, scope: scope} = args, store = react.useMemo(() => createBandStateStore({
|
|
1896
|
+
storageKey: bandStateStorageKey(scope)
|
|
1897
|
+
}), [ scope ]), [held, setHeld] = react.useState(() => ({
|
|
1898
|
+
store: store,
|
|
1899
|
+
toggles: store.read()
|
|
1900
|
+
}));
|
|
1901
|
+
held.store !== store && setHeld({
|
|
1902
|
+
store: store,
|
|
1903
|
+
toggles: store.read()
|
|
1904
|
+
});
|
|
1905
|
+
const qualify = key => `${segment}/${key}`, isOpen = (key, defaultOpen) => held.toggles.get(qualify(key)) ?? defaultOpen;
|
|
1906
|
+
return {
|
|
1907
|
+
isOpen: isOpen,
|
|
1908
|
+
toggle: (key, defaultOpen) => {
|
|
1909
|
+
setHeld({
|
|
1910
|
+
store: store,
|
|
1911
|
+
toggles: store.setOpen(qualify(key), !isOpen(key, defaultOpen))
|
|
1912
|
+
});
|
|
1913
|
+
}
|
|
1914
|
+
};
|
|
1915
|
+
}
|
|
1916
|
+
|
|
1917
|
+
const NO_MISSING = /* @__PURE__ */ new Set;
|
|
1918
|
+
|
|
1919
|
+
function sameIdSet(a, b) {
|
|
1920
|
+
if (a.size !== b.size) return !1;
|
|
1921
|
+
for (const id of a) if (!b.has(id)) return !1;
|
|
1922
|
+
return !0;
|
|
1923
|
+
}
|
|
1924
|
+
|
|
1925
|
+
function useOrphanedWorkflows(groups) {
|
|
1926
|
+
const {actualTypes: actualTypes, binding: binding} = index.useWorkflowContext(), contentResource = binding.contentResource, key = react.useMemo(() => orphanProbeIds(groups, contentResource), [ groups, contentResource ]).join("|"), [missing, setMissing] = react.useState(NO_MISSING);
|
|
1927
|
+
return react.useEffect(() => {
|
|
1928
|
+
if (key === "") {
|
|
1929
|
+
setMissing(NO_MISSING);
|
|
1930
|
+
return;
|
|
1931
|
+
}
|
|
1932
|
+
const tracked = key.split("|"), releases = tracked.map(id => actualTypes.track(id)), recompute = () => {
|
|
1933
|
+
const next = new Set(tracked.filter(id => actualTypes.read(id) === null));
|
|
1934
|
+
setMissing(current => sameIdSet(current, next) ? current : next);
|
|
1935
|
+
};
|
|
1936
|
+
recompute();
|
|
1937
|
+
const unsubscribe = actualTypes.subscribe(recompute);
|
|
1938
|
+
return () => {
|
|
1939
|
+
unsubscribe();
|
|
1940
|
+
for (const release of releases) release();
|
|
1941
|
+
};
|
|
1942
|
+
}, [ actualTypes, key ]), react.useMemo(() => missing.size === 0 ? NO_ORPHANS : orphanVerdict({
|
|
1943
|
+
groups: groups,
|
|
1944
|
+
contentResource: contentResource,
|
|
1945
|
+
missing: missing
|
|
1946
|
+
}), [ groups, contentResource, missing ]);
|
|
1947
|
+
}
|
|
1948
|
+
|
|
1486
1949
|
const INSTANCE_CAP = 200, NEWEST_INSTANCES = {
|
|
1487
1950
|
includeCompleted: !0,
|
|
1488
1951
|
limit: INSTANCE_CAP
|
|
@@ -1505,18 +1968,21 @@ function useToolInstances() {
|
|
|
1505
1968
|
}, [ instances, loading, unreadable ]);
|
|
1506
1969
|
}
|
|
1507
1970
|
|
|
1508
|
-
function TasksTab({filterSlot: filterSlot, instances: instances, loading: loading, identity: identity,
|
|
1509
|
-
const
|
|
1971
|
+
function TasksTab({filterSlot: filterSlot, instances: instances, loading: loading, identity: identity, onOpenTask: onOpenTask, onOpenWorkflow: onOpenWorkflow, segment: segment, truncated: truncated, unreadable: unreadable}) {
|
|
1972
|
+
const mineOnly = segment === "for-me", {binding: binding} = index.useWorkflowContext(), bands = useBandOpenState({
|
|
1973
|
+
segment: segment,
|
|
1974
|
+
scope: binding.engineResource.id
|
|
1975
|
+
}), [filters, setFilters] = react.useState({}), groups = react.useMemo(() => deriveTaskGroups({
|
|
1510
1976
|
instances: instances,
|
|
1511
1977
|
identity: identity
|
|
1512
|
-
}), [ instances, identity ]),
|
|
1978
|
+
}), [ instances, identity ]), orphans = useOrphanedWorkflows(groups), listed = react.useMemo(() => hideOrphanedGroups(groups, orphans.groupKeys), [ groups, orphans.groupKeys ]), options = react.useMemo(() => taskFilterOptions(listed), [ listed ]), visible = react.useMemo(() => {
|
|
1513
1979
|
const filtered = applyTaskFilters({
|
|
1514
|
-
groups:
|
|
1980
|
+
groups: listed,
|
|
1515
1981
|
filters: filters,
|
|
1516
1982
|
now: /* @__PURE__ */ new Date
|
|
1517
1983
|
});
|
|
1518
1984
|
return mineOnly ? onlyMyTasks(filtered) : filtered;
|
|
1519
|
-
}, [
|
|
1985
|
+
}, [ listed, filters, mineOnly ]);
|
|
1520
1986
|
return loading ?
|
|
1521
1987
|
/* @__PURE__ */ jsxRuntime.jsx(ui.Box, {
|
|
1522
1988
|
paddingLeft: 2,
|
|
@@ -1537,8 +2003,13 @@ function TasksTab({filterSlot: filterSlot, instances: instances, loading: loadin
|
|
|
1537
2003
|
/* @__PURE__ */ jsxRuntime.jsx(index.UnreadableDocsNote, {
|
|
1538
2004
|
unreadable: unreadable
|
|
1539
2005
|
}),
|
|
2006
|
+
/* @__PURE__ */ jsxRuntime.jsx(OrphanedWorkflowsNote, {
|
|
2007
|
+
orphans: orphans
|
|
2008
|
+
}),
|
|
1540
2009
|
/* @__PURE__ */ jsxRuntime.jsx(TaskGroupList, {
|
|
2010
|
+
bands: bands,
|
|
1541
2011
|
groups: visible,
|
|
2012
|
+
instanceDefaultOpen: instanceBandDefaultOpen(segment),
|
|
1542
2013
|
onOpenTask: onOpenTask,
|
|
1543
2014
|
onOpenWorkflow: onOpenWorkflow,
|
|
1544
2015
|
showTerminalActions: mineOnly
|
|
@@ -1579,7 +2050,12 @@ function carryingSearchParams(state, searchParams) {
|
|
|
1579
2050
|
}
|
|
1580
2051
|
|
|
1581
2052
|
function WorkflowsToolRoot() {
|
|
1582
|
-
const router$1 = router.useRouter(), {seedInstance: seedInstance} = index.useWorkflowContext(),
|
|
2053
|
+
const router$1 = router.useRouter(), {seedInstance: seedInstance} = index.useWorkflowContext(), {tab: tab} = useToolTab();
|
|
2054
|
+
index.useLogEventOnMount(index.WorkflowToolOpened, {
|
|
2055
|
+
tab: tab,
|
|
2056
|
+
via: "open"
|
|
2057
|
+
});
|
|
2058
|
+
const instanceId = typeof router$1.state.instanceId == "string" ? router$1.state.instanceId : void 0;
|
|
1583
2059
|
return instanceId ? /* @__PURE__ */ jsxRuntime.jsx(WorkflowInstanceDetail, {
|
|
1584
2060
|
instanceId: instanceId,
|
|
1585
2061
|
onBack: () => router$1.navigate(carryingSearchParams({}, router$1.state._searchParams))
|
|
@@ -1593,7 +2069,7 @@ function WorkflowsToolRoot() {
|
|
|
1593
2069
|
}
|
|
1594
2070
|
|
|
1595
2071
|
function WorkflowsHome({onOpenInstance: onOpenInstance}) {
|
|
1596
|
-
const {tab: tab, setTab: setTab} = useToolTab(), instances = useToolInstances(), identity = index.useAssignmentIdentity(), [openTask, setOpenTask] = react.useState(null), openInstance = target => {
|
|
2072
|
+
const {tab: tab, setTab: setTab} = useToolTab(), telemetry = workflowReact.useWorkflowTelemetry(), instances = useToolInstances(), identity = index.useAssignmentIdentity(), [openTask, setOpenTask] = react.useState(null), openInstance = target => {
|
|
1597
2073
|
if (typeof target != "string") return onOpenInstance(target);
|
|
1598
2074
|
const held = [ ...instances.inFlight, ...instances.settled ].find(i => i._id === target);
|
|
1599
2075
|
onOpenInstance(held ?? target);
|
|
@@ -1637,7 +2113,12 @@ function WorkflowsHome({onOpenInstance: onOpenInstance}) {
|
|
|
1637
2113
|
/* @__PURE__ */ jsxRuntime.jsx(index.TabSwitch, {
|
|
1638
2114
|
ariaControls: "workflows-tool-panel",
|
|
1639
2115
|
idPrefix: "workflows-tool-tab",
|
|
1640
|
-
onSelect:
|
|
2116
|
+
onSelect: next => {
|
|
2117
|
+
next !== tab && telemetry.log(index.WorkflowToolOpened, {
|
|
2118
|
+
tab: next,
|
|
2119
|
+
via: "tab-switch"
|
|
2120
|
+
}), setTab(next);
|
|
2121
|
+
},
|
|
1641
2122
|
options: TOOL_TABS,
|
|
1642
2123
|
selected: tab
|
|
1643
2124
|
}),
|
|
@@ -1668,9 +2149,9 @@ function WorkflowsHome({onOpenInstance: onOpenInstance}) {
|
|
|
1668
2149
|
identity: identity,
|
|
1669
2150
|
instances: [ ...instances.inFlight, ...instances.settled ],
|
|
1670
2151
|
loading: instances.loading,
|
|
1671
|
-
mineOnly: tab === "for-me",
|
|
1672
2152
|
onOpenTask: setOpenTask,
|
|
1673
2153
|
onOpenWorkflow: openInstance,
|
|
2154
|
+
segment: tab,
|
|
1674
2155
|
truncated: instances.truncated,
|
|
1675
2156
|
unreadable: instances.unreadable
|
|
1676
2157
|
})
|