Implement native BPMN workflows and guided modes
This commit is contained in:
@@ -23,8 +23,13 @@ import {
|
||||
FormField,
|
||||
IconButton,
|
||||
LoadingFrame,
|
||||
StageRail,
|
||||
StatusBadge,
|
||||
type ApiSettings
|
||||
dispatchWorkflowViewChanged,
|
||||
usePlatformUiCapability,
|
||||
type StageRailTone,
|
||||
type ApiSettings,
|
||||
type ViewsRuntimeUiCapability
|
||||
} from "@govoplan/core-webui";
|
||||
import {
|
||||
cancelWorkflowInstance,
|
||||
@@ -50,6 +55,7 @@ export default function WorkflowRunsDialog({
|
||||
open,
|
||||
settings,
|
||||
definition,
|
||||
initialInstanceId,
|
||||
canStart,
|
||||
canTransition,
|
||||
onClose
|
||||
@@ -57,6 +63,7 @@ export default function WorkflowRunsDialog({
|
||||
open: boolean;
|
||||
settings: ApiSettings;
|
||||
definition: WorkflowDefinition | null;
|
||||
initialInstanceId?: string | null;
|
||||
canStart: boolean;
|
||||
canTransition: boolean;
|
||||
onClose: () => void;
|
||||
@@ -69,6 +76,9 @@ export default function WorkflowRunsDialog({
|
||||
const [comment, setComment] = useState("");
|
||||
const [evidence, setEvidence] = useState("");
|
||||
const [cancelOpen, setCancelOpen] = useState(false);
|
||||
const viewsRuntime = usePlatformUiCapability<ViewsRuntimeUiCapability>(
|
||||
"views.runtime"
|
||||
);
|
||||
|
||||
const selected = useMemo(
|
||||
() => instances.find((item) => item.id === selectedId) ?? instances[0] ?? null,
|
||||
@@ -99,7 +109,10 @@ export default function WorkflowRunsDialog({
|
||||
const items = await listWorkflowInstances(settings, definition.id);
|
||||
setInstances(items);
|
||||
setSelectedId((current) => (
|
||||
items.some((item) => item.id === current)
|
||||
initialInstanceId
|
||||
&& items.some((item) => item.id === initialInstanceId)
|
||||
? initialInstanceId
|
||||
: items.some((item) => item.id === current)
|
||||
? current
|
||||
: items[0]?.id ?? null
|
||||
));
|
||||
@@ -108,7 +121,7 @@ export default function WorkflowRunsDialog({
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, [definition?.id, open, settings]);
|
||||
}, [definition?.id, initialInstanceId, open, settings]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!open) return;
|
||||
@@ -117,6 +130,39 @@ export default function WorkflowRunsDialog({
|
||||
void load();
|
||||
}, [load, open]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!open || !selected) return;
|
||||
const context = selected.view_context;
|
||||
if (!context || !viewsRuntime) {
|
||||
dispatchWorkflowViewChanged(null);
|
||||
return;
|
||||
}
|
||||
let cancelled = false;
|
||||
void viewsRuntime.resolveWorkflowView(settings, {
|
||||
viewId: context.view_id,
|
||||
revisionId: context.revision_id,
|
||||
visibleSurfaceIds: context.visible_surface_ids
|
||||
}).then((projection) => {
|
||||
if (!cancelled) {
|
||||
dispatchWorkflowViewChanged(projection, selected.id);
|
||||
}
|
||||
}).catch((viewError) => {
|
||||
if (!cancelled) {
|
||||
dispatchWorkflowViewChanged(null);
|
||||
setError(errorMessage(viewError));
|
||||
}
|
||||
});
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [
|
||||
open,
|
||||
selected?.id,
|
||||
selected?.updated_at,
|
||||
settings,
|
||||
viewsRuntime
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
!open
|
||||
@@ -236,6 +282,10 @@ export default function WorkflowRunsDialog({
|
||||
const actionUrl = typeof currentStep?.handoff.action_url === "string"
|
||||
? currentStep.handoff.action_url
|
||||
: "";
|
||||
const close = () => {
|
||||
dispatchWorkflowViewChanged(null);
|
||||
onClose();
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -244,8 +294,8 @@ export default function WorkflowRunsDialog({
|
||||
title={`Runs${definition ? ` · ${definition.name}` : ""}`}
|
||||
className="workflow-runs-dialog"
|
||||
bodyClassName="workflow-runs-dialog-body"
|
||||
onClose={onClose}
|
||||
footer={<Button onClick={onClose}>Close</Button>}
|
||||
onClose={close}
|
||||
footer={<Button onClick={close}>Close</Button>}
|
||||
>
|
||||
<div className="workflow-runs-toolbar">
|
||||
<span>
|
||||
@@ -419,40 +469,26 @@ export default function WorkflowRunsDialog({
|
||||
) : null}
|
||||
<section className="workflow-run-history">
|
||||
<h3>Progress</h3>
|
||||
<div>
|
||||
{selected.steps.map((step) => (
|
||||
<div
|
||||
key={step.id}
|
||||
className="workflow-run-stage"
|
||||
data-state={step.status}
|
||||
data-current={
|
||||
step.id === selected.current_step_id || undefined
|
||||
}
|
||||
>
|
||||
<span className="workflow-run-stage-marker">
|
||||
{step.status === "completed" ? (
|
||||
<Check size={15} aria-hidden="true" />
|
||||
) : step.status === "failed" ? (
|
||||
<AlertTriangle size={15} aria-hidden="true" />
|
||||
) : ["running", "waiting"].includes(step.status) ? (
|
||||
<Clock3 size={15} aria-hidden="true" />
|
||||
) : (
|
||||
<Circle size={13} aria-hidden="true" />
|
||||
)}
|
||||
</span>
|
||||
<span className="workflow-run-stage-copy">
|
||||
<strong>{step.node_id}</strong>
|
||||
<small>
|
||||
{step.node_type} · attempt {step.attempt}
|
||||
</small>
|
||||
<StatusBadge
|
||||
status={step.status}
|
||||
label={step.status}
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<StageRail
|
||||
ariaLabel="Workflow instance progress"
|
||||
items={selected.steps.map((step) => ({
|
||||
id: step.id,
|
||||
label: step.node_id,
|
||||
detail: `${step.node_type} · attempt ${step.attempt}`,
|
||||
statusLabel: step.status,
|
||||
current: step.id === selected.current_step_id,
|
||||
tone: stepTone(step.status),
|
||||
icon: step.status === "completed" ? (
|
||||
<Check size={15} aria-hidden="true" />
|
||||
) : step.status === "failed" ? (
|
||||
<AlertTriangle size={15} aria-hidden="true" />
|
||||
) : ["running", "waiting"].includes(step.status) ? (
|
||||
<Clock3 size={15} aria-hidden="true" />
|
||||
) : (
|
||||
<Circle size={13} aria-hidden="true" />
|
||||
)
|
||||
}))}
|
||||
/>
|
||||
</section>
|
||||
<section className="workflow-run-events">
|
||||
<h3>Evidence trail</h3>
|
||||
@@ -530,6 +566,15 @@ function actionLabel(action: WorkflowAction): string {
|
||||
}[action];
|
||||
}
|
||||
|
||||
function stepTone(
|
||||
status: WorkflowInstanceStep["status"]
|
||||
): StageRailTone {
|
||||
if (status === "completed") return "success";
|
||||
if (status === "running" || status === "waiting") return "active";
|
||||
if (status === "failed" || status === "cancelled") return "danger";
|
||||
return "neutral";
|
||||
}
|
||||
|
||||
function formatDateTime(value: string): string {
|
||||
return new Intl.DateTimeFormat(undefined, {
|
||||
dateStyle: "medium",
|
||||
|
||||
Reference in New Issue
Block a user