-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathInstanceActiveActivitiesPanel.tsx
More file actions
140 lines (132 loc) · 5.22 KB
/
Copy pathInstanceActiveActivitiesPanel.tsx
File metadata and controls
140 lines (132 loc) · 5.22 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
// SPDX-License-Identifier: Apache-2.0
/**
* Active activities panel — currently-in-flight activities for a running
* process instance. Eighth panel-as-sibling consumer (after 10.4 / 11.3 /
* 12.4 / 13.1-runtime / 13.1-historic / 13.2-activities / 13.x-historic-
* variables). Project decision (Epic 12 retro R-2): never extract.
*
* The Flowable runtime DTO only carries a single lead `activityId` per
* process instance; a parallel-branch instance can have many active
* activities at once. The historic-activity-instances endpoint with
* `finished=false` is the supported recipe to enumerate them — the
* engine writes the historic-activity row at start time, before the
* activity completes. Calling the historic surface for "active right
* now" is the operator-feel shortcut Flowable supports (no separate
* `runtime/active-activities` endpoint exists).
*
* Mounted inside <InstanceRuntimePanel> after the properties table and
* before <InstanceVariablesPanel>. The historic timeline in
* <InstanceHistoricActivitiesPanel> keeps showing the full audit trail
* (active + completed) — that's the chronological view; this panel is
* the "what's running NOW" snapshot.
*
* Status-aware error-probe (Epic 11 retro §4.4): 404 → null → empty
* state. In practice the engine returns `{data:[]}` for an idle or
* completed instance.
*/
import { api, FlowableError, type FlowableHistoricActivity } from "../api";
import { fmtTime, Icon } from "../components";
import { EmptyState, getEmptyState } from "../lib/empty-states";
import { ErrorBox } from "../lib/error-box";
import { TableSkeleton } from "../lib/table-skeleton";
import { useApi } from "../lib/useApi";
// Engine-returned fields not on the typed FlowableHistoricActivity DTO.
// Mirrors the 12.1 / 13.2 Loose<T> cast pattern.
type ActiveActivityWide = FlowableHistoricActivity & {
assignee?: string;
executionId?: string;
};
const ACTIVE_PAGE_SIZE = 50;
export const fetchActiveActivitiesOrNull = async (
instanceId: string,
): Promise<FlowableHistoricActivity[] | null> => {
try {
const page = await api.listHistoricActivities({
processInstanceId: instanceId,
finished: false,
size: ACTIVE_PAGE_SIZE,
sort: "startTime",
});
return page.data ?? [];
} catch (err) {
if (err instanceof FlowableError && err.status === 404) return null;
throw err;
}
};
interface Props {
instanceId: string;
}
export function InstanceActiveActivitiesPanel({ instanceId }: Props) {
const activities = useApi<FlowableHistoricActivity[] | null>(
() => fetchActiveActivitiesOrNull(instanceId),
[instanceId],
);
const list = activities.data ?? [];
return (
<div className="panel" data-testid="instance-active-activities-panel" style={{ marginTop: 18 }}>
<div className="panel-hd">
<span className="panel-title">Active activities</span>
{activities.data && list.length > 0 && (
<span className="badge" data-tone="ok" style={{ marginLeft: 8 }}>
<span className="sr-only">Active activities: </span>
{list.length}
</span>
)}
<span
className="mono mute"
style={{ marginLeft: "auto", fontSize: 10, color: "var(--fg-mute)" }}
>
GET /history/historic-activity-instances?processInstanceId={instanceId}&finished=false
</span>
<button
type="button"
className="icon-btn"
data-testid="active-activities-refresh"
onClick={activities.reload}
disabled={activities.loading}
aria-label="Refresh active activities"
style={{ marginLeft: 8 }}
>
<Icon name="refresh" size={12} />
</button>
</div>
<div className="panel-body">
{activities.loading && <TableSkeleton columns={4} rows={2} />}
{activities.error && <ErrorBox error={activities.error} onRetry={activities.reload} />}
{!activities.loading && !activities.error && list.length === 0 && (
<EmptyState entry={getEmptyState("activeActivities")} />
)}
{!activities.loading && !activities.error && list.length > 0 && (
<table className="tbl">
<thead>
<tr>
<th scope="col">Activity</th>
<th scope="col">Type</th>
<th scope="col">Assignee</th>
<th scope="col">Started</th>
</tr>
</thead>
<tbody>
{list.map((raw) => {
const a = raw as ActiveActivityWide;
return (
<tr key={a.id} data-active-activity-id={a.id}>
<td>{a.activityName || a.activityId}</td>
<td className="mono">
<span className="badge" data-tone="mute">
<span className="sr-only">Activity type: </span>
{a.activityType}
</span>
</td>
<td className="mono mute">{a.assignee || <span className="mute">—</span>}</td>
<td className="mute mono">{fmtTime(a.startTime)}</td>
</tr>
);
})}
</tbody>
</table>
)}
</div>
</div>
);
}