@nnkogift/dhis2-form-utils-devtools 0.1.0-alpha.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/index.cjs +1663 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.css +2 -0
- package/dist/index.d.cts +63 -0
- package/dist/index.d.ts +63 -0
- package/dist/index.js +1648 -0
- package/dist/index.js.map +1 -0
- package/package.json +76 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,1663 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var ui = require('@dhis2/ui');
|
|
4
|
+
var dhis2FormUtilsHooks = require('@nnkogift/dhis2-form-utils-hooks');
|
|
5
|
+
var react = require('react');
|
|
6
|
+
var dhis2FormUtilsMetadata = require('@nnkogift/dhis2-form-utils-metadata');
|
|
7
|
+
var i18n = require('@dhis2/d2-i18n');
|
|
8
|
+
var jsxRuntime = require('react/jsx-runtime');
|
|
9
|
+
var react$1 = require('@xyflow/react');
|
|
10
|
+
|
|
11
|
+
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
12
|
+
|
|
13
|
+
var i18n__default = /*#__PURE__*/_interopDefault(i18n);
|
|
14
|
+
|
|
15
|
+
// src/RulesPanel.tsx
|
|
16
|
+
|
|
17
|
+
// src/buildGraph.ts
|
|
18
|
+
var nodeKey = (kind, id) => `${kind}:${id}`;
|
|
19
|
+
var edgeKey = (source, ruleId, target, effectType) => `${source}|${ruleId}|${target}|${effectType ?? ""}`;
|
|
20
|
+
function accumulateGraph(previous, entry, labelLookup) {
|
|
21
|
+
const nodes = new Map(previous.nodes.map((node) => [node.id, node]));
|
|
22
|
+
const edges = new Map(previous.edges.map((edge) => [edge.id, edge]));
|
|
23
|
+
const ensureNode = (kind, id, label) => {
|
|
24
|
+
const key = nodeKey(kind, id);
|
|
25
|
+
if (!nodes.has(key)) {
|
|
26
|
+
nodes.set(key, { id: key, kind, label });
|
|
27
|
+
}
|
|
28
|
+
return key;
|
|
29
|
+
};
|
|
30
|
+
for (const fieldId of entry.changedFields) {
|
|
31
|
+
ensureNode("field", fieldId, labelLookup?.resolveFieldName(fieldId) ?? fieldId);
|
|
32
|
+
}
|
|
33
|
+
for (const ruleResult of entry.ruleResults) {
|
|
34
|
+
const ruleLabel = labelLookup?.resolveRuleName(ruleResult.ruleId) ?? ruleResult.ruleId;
|
|
35
|
+
const ruleNodeId = ensureNode("rule", ruleResult.ruleId, ruleLabel);
|
|
36
|
+
for (const fieldId of entry.changedFields) {
|
|
37
|
+
const fieldNodeId = ensureNode(
|
|
38
|
+
"field",
|
|
39
|
+
fieldId,
|
|
40
|
+
labelLookup?.resolveFieldName(fieldId) ?? fieldId
|
|
41
|
+
);
|
|
42
|
+
const id = edgeKey(fieldNodeId, ruleResult.ruleId, ruleNodeId, "read");
|
|
43
|
+
const existing = edges.get(id);
|
|
44
|
+
edges.set(id, {
|
|
45
|
+
id,
|
|
46
|
+
source: fieldNodeId,
|
|
47
|
+
target: ruleNodeId,
|
|
48
|
+
effectType: "read",
|
|
49
|
+
fireCount: (existing?.fireCount ?? 0) + 1
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
for (const effect of ruleResult.effects) {
|
|
53
|
+
const targetKind = effect.type === "HIDESECTION" ? "section" : effect.type === "DISPLAYTEXT" || effect.type === "DISPLAYKEYVALUEPAIR" ? "feedback" : "field";
|
|
54
|
+
const targetNodeId = ensureNode(
|
|
55
|
+
targetKind,
|
|
56
|
+
effect.targetId,
|
|
57
|
+
targetKind === "section" ? labelLookup?.resolveSectionName(effect.targetId) ?? effect.targetId : targetKind === "field" ? labelLookup?.resolveFieldName(effect.targetId) ?? effect.targetId : effect.targetId
|
|
58
|
+
);
|
|
59
|
+
const id = edgeKey(ruleNodeId, ruleResult.ruleId, targetNodeId, effect.type);
|
|
60
|
+
const existing = edges.get(id);
|
|
61
|
+
edges.set(id, {
|
|
62
|
+
id,
|
|
63
|
+
source: ruleNodeId,
|
|
64
|
+
target: targetNodeId,
|
|
65
|
+
effectType: effect.type,
|
|
66
|
+
fireCount: (existing?.fireCount ?? 0) + 1
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return {
|
|
71
|
+
nodes: [...nodes.values()],
|
|
72
|
+
edges: [...edges.values()]
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
function buildGraphFromTrace(entries, labelLookup) {
|
|
76
|
+
return entries.reduce(
|
|
77
|
+
(graph, entry) => accumulateGraph(graph, entry, labelLookup),
|
|
78
|
+
{ nodes: [], edges: [] }
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
var createLookup = (rules, fields, sections, stages) => ({
|
|
82
|
+
resolveRuleName: (id) => rules.get(id) ?? id,
|
|
83
|
+
resolveFieldName: (id) => fields.get(id) ?? id,
|
|
84
|
+
resolveSectionName: (id) => sections.get(id) ?? id,
|
|
85
|
+
resolveStageName: (id) => stages.get(id) ?? id
|
|
86
|
+
});
|
|
87
|
+
function buildEventLookup(source) {
|
|
88
|
+
const rules = /* @__PURE__ */ new Map();
|
|
89
|
+
const fields = /* @__PURE__ */ new Map();
|
|
90
|
+
const sections = /* @__PURE__ */ new Map();
|
|
91
|
+
const stages = /* @__PURE__ */ new Map();
|
|
92
|
+
for (const rule of source.metadata.programRules) {
|
|
93
|
+
if (!rule.id) {
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
rules.set(rule.id, rule.displayName ?? rule.id);
|
|
97
|
+
}
|
|
98
|
+
const stage = dhis2FormUtilsMetadata.selectProgramStage(
|
|
99
|
+
source.metadata,
|
|
100
|
+
source.programStageId
|
|
101
|
+
) ?? {
|
|
102
|
+
id: source.programStageId,
|
|
103
|
+
programStageDataElements: [],
|
|
104
|
+
programStageSections: []
|
|
105
|
+
};
|
|
106
|
+
for (const psde of stage.programStageDataElements ?? []) {
|
|
107
|
+
const de = psde.dataElement;
|
|
108
|
+
if (!de?.id) {
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
fields.set(de.id, de.displayFormName ?? de.displayName ?? de.id);
|
|
112
|
+
}
|
|
113
|
+
for (const section of stage.programStageSections ?? []) {
|
|
114
|
+
sections.set(section.id, section.displayName ?? section.id);
|
|
115
|
+
}
|
|
116
|
+
for (const programStage of source.metadata.programStages) {
|
|
117
|
+
if (!programStage.id) {
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
stages.set(programStage.id, programStage.displayName ?? programStage.id);
|
|
121
|
+
}
|
|
122
|
+
return createLookup(rules, fields, sections, stages);
|
|
123
|
+
}
|
|
124
|
+
function buildTrackerLookup(source) {
|
|
125
|
+
const rules = /* @__PURE__ */ new Map();
|
|
126
|
+
const fields = /* @__PURE__ */ new Map();
|
|
127
|
+
const sections = /* @__PURE__ */ new Map();
|
|
128
|
+
const stages = /* @__PURE__ */ new Map();
|
|
129
|
+
for (const rule of source.metadata.programRules) {
|
|
130
|
+
if (!rule.id) {
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
rules.set(rule.id, rule.name ?? rule.id);
|
|
134
|
+
}
|
|
135
|
+
for (const ptea of source.metadata.programTrackedEntityAttributes) {
|
|
136
|
+
const tea = ptea.trackedEntityAttribute;
|
|
137
|
+
if (!tea.id) {
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
fields.set(tea.id, tea.formName ?? tea.displayName ?? tea.id);
|
|
141
|
+
}
|
|
142
|
+
for (const section of source.metadata.programSections ?? []) {
|
|
143
|
+
sections.set(section.id, section.displayName ?? section.id);
|
|
144
|
+
}
|
|
145
|
+
for (const programStage of source.programStages ?? []) {
|
|
146
|
+
if (!programStage.id) {
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
stages.set(programStage.id, programStage.displayName ?? programStage.id);
|
|
150
|
+
}
|
|
151
|
+
return createLookup(rules, fields, sections, stages);
|
|
152
|
+
}
|
|
153
|
+
function createLabelLookup(source) {
|
|
154
|
+
return source.formKind === "event" ? buildEventLookup(source) : buildTrackerLookup(source);
|
|
155
|
+
}
|
|
156
|
+
var EFFECT_VARIANT = {
|
|
157
|
+
HIDEFIELD: "hide",
|
|
158
|
+
HIDEOPTION: "hide",
|
|
159
|
+
HIDEOPTIONGROUP: "hide",
|
|
160
|
+
HIDESECTION: "hide",
|
|
161
|
+
HIDEPROGRAMSTAGE: "hide",
|
|
162
|
+
SHOWFIELD: "show",
|
|
163
|
+
SHOWOPTION: "show",
|
|
164
|
+
SHOWOPTIONGROUP: "show",
|
|
165
|
+
ASSIGN: "assign",
|
|
166
|
+
SETMANDATORYFIELD: "mandatory",
|
|
167
|
+
UNSETMANDATORYFIELD: "mandatory",
|
|
168
|
+
SHOWWARNING: "warning",
|
|
169
|
+
WARNINGONCOMPLETE: "warning",
|
|
170
|
+
SHOWERROR: "error",
|
|
171
|
+
ERRORONCOMPLETE: "error",
|
|
172
|
+
DISPLAYTEXT: "feedback",
|
|
173
|
+
DISPLAYKEYVALUEPAIR: "feedback",
|
|
174
|
+
SENDMESSAGE: "message",
|
|
175
|
+
SCHEDULEMESSAGE: "message",
|
|
176
|
+
read: "read"
|
|
177
|
+
};
|
|
178
|
+
var EFFECT_VISUALS = {
|
|
179
|
+
hide: {
|
|
180
|
+
tagClassName: "bg-dhis2-grey-200 text-dhis2-grey-900",
|
|
181
|
+
edgeStroke: "#494949",
|
|
182
|
+
shortLabel: "hide"
|
|
183
|
+
},
|
|
184
|
+
show: {
|
|
185
|
+
tagClassName: "bg-dhis2-green-100 text-dhis2-green-900",
|
|
186
|
+
edgeStroke: "#388e3c",
|
|
187
|
+
shortLabel: "show"
|
|
188
|
+
},
|
|
189
|
+
assign: {
|
|
190
|
+
tagClassName: "bg-dhis2-blue-100 text-dhis2-blue-900",
|
|
191
|
+
edgeStroke: "#1565c0",
|
|
192
|
+
shortLabel: "assign"
|
|
193
|
+
},
|
|
194
|
+
mandatory: {
|
|
195
|
+
tagClassName: "bg-dhis2-yellow-100 text-dhis2-yellow-900",
|
|
196
|
+
edgeStroke: "#e56408",
|
|
197
|
+
shortLabel: "required"
|
|
198
|
+
},
|
|
199
|
+
warning: {
|
|
200
|
+
tagClassName: "bg-dhis2-yellow-100 text-dhis2-yellow-900",
|
|
201
|
+
edgeStroke: "#ff8302",
|
|
202
|
+
shortLabel: "warn"
|
|
203
|
+
},
|
|
204
|
+
error: {
|
|
205
|
+
tagClassName: "bg-dhis2-red-100 text-dhis2-red-900",
|
|
206
|
+
edgeStroke: "#c62828",
|
|
207
|
+
shortLabel: "error"
|
|
208
|
+
},
|
|
209
|
+
feedback: {
|
|
210
|
+
tagClassName: "bg-dhis2-purple-100 text-dhis2-purple-900",
|
|
211
|
+
edgeStroke: "#9c27b0",
|
|
212
|
+
shortLabel: "feedback"
|
|
213
|
+
},
|
|
214
|
+
message: {
|
|
215
|
+
tagClassName: "bg-dhis2-teal-100 text-dhis2-teal-900",
|
|
216
|
+
edgeStroke: "#00796b",
|
|
217
|
+
shortLabel: "message"
|
|
218
|
+
},
|
|
219
|
+
read: {
|
|
220
|
+
tagClassName: "bg-dhis2-teal-100 text-dhis2-teal-900",
|
|
221
|
+
edgeStroke: "#00796b",
|
|
222
|
+
shortLabel: "read"
|
|
223
|
+
},
|
|
224
|
+
default: {
|
|
225
|
+
tagClassName: "bg-dhis2-grey-100 text-dhis2-grey-800",
|
|
226
|
+
edgeStroke: "#494949",
|
|
227
|
+
shortLabel: "effect"
|
|
228
|
+
}
|
|
229
|
+
};
|
|
230
|
+
var EFFECT_ICONS = {
|
|
231
|
+
hide: ui.IconViewOff16,
|
|
232
|
+
show: ui.IconView16,
|
|
233
|
+
assign: ui.IconEdit16,
|
|
234
|
+
mandatory: ui.IconStarFilled16,
|
|
235
|
+
warning: ui.IconWarningFilled16,
|
|
236
|
+
error: ui.IconErrorFilled16,
|
|
237
|
+
feedback: ui.IconMessages16,
|
|
238
|
+
message: ui.IconMail16,
|
|
239
|
+
read: ui.IconLink16,
|
|
240
|
+
default: ui.IconInfo16
|
|
241
|
+
};
|
|
242
|
+
function getEffectVariant(type) {
|
|
243
|
+
return EFFECT_VARIANT[type] ?? "default";
|
|
244
|
+
}
|
|
245
|
+
function getEffectVisual(type) {
|
|
246
|
+
const variant = getEffectVariant(type);
|
|
247
|
+
return { variant, ...EFFECT_VISUALS[variant] };
|
|
248
|
+
}
|
|
249
|
+
var TAG_LAYOUT_CLASS = "max-w-full break-words";
|
|
250
|
+
function tagPropsForVariant(variant) {
|
|
251
|
+
switch (variant) {
|
|
252
|
+
case "show":
|
|
253
|
+
return { positive: true, className: TAG_LAYOUT_CLASS };
|
|
254
|
+
case "error":
|
|
255
|
+
return { negative: true, className: TAG_LAYOUT_CLASS };
|
|
256
|
+
case "assign":
|
|
257
|
+
return { neutral: true, className: TAG_LAYOUT_CLASS };
|
|
258
|
+
case "hide":
|
|
259
|
+
case "mandatory":
|
|
260
|
+
case "warning":
|
|
261
|
+
case "feedback":
|
|
262
|
+
case "message":
|
|
263
|
+
case "read":
|
|
264
|
+
return {
|
|
265
|
+
className: `${TAG_LAYOUT_CLASS} ${EFFECT_VISUALS[variant].tagClassName}`
|
|
266
|
+
};
|
|
267
|
+
default:
|
|
268
|
+
return {
|
|
269
|
+
className: `${TAG_LAYOUT_CLASS} ${EFFECT_VISUALS.default.tagClassName}`
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
function getEffectTagRenderProps(type) {
|
|
274
|
+
return tagPropsForVariant(getEffectVariant(type));
|
|
275
|
+
}
|
|
276
|
+
function getEffectTagRenderPropsForVariant(variant) {
|
|
277
|
+
return tagPropsForVariant(variant);
|
|
278
|
+
}
|
|
279
|
+
function getEffectEdgeStroke(type, highlighted = true) {
|
|
280
|
+
const { edgeStroke } = getEffectVisual(type);
|
|
281
|
+
if (highlighted) {
|
|
282
|
+
return edgeStroke;
|
|
283
|
+
}
|
|
284
|
+
return "#bdbdbd";
|
|
285
|
+
}
|
|
286
|
+
function getEffectShortLabel(type) {
|
|
287
|
+
return getEffectVisual(type).shortLabel;
|
|
288
|
+
}
|
|
289
|
+
function translate(message, variables) {
|
|
290
|
+
return String(i18n__default.default.t(message, variables));
|
|
291
|
+
}
|
|
292
|
+
function EffectBadge({ type, children, className = "" }) {
|
|
293
|
+
const variant = getEffectVariant(type);
|
|
294
|
+
const Icon = EFFECT_ICONS[variant];
|
|
295
|
+
const tagProps = getEffectTagRenderProps(type);
|
|
296
|
+
const label = children ?? type;
|
|
297
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
298
|
+
ui.Tag,
|
|
299
|
+
{
|
|
300
|
+
...tagProps,
|
|
301
|
+
icon: /* @__PURE__ */ jsxRuntime.jsx(Icon, { "aria-hidden": "true" }),
|
|
302
|
+
className: [tagProps.className, className].filter(Boolean).join(" "),
|
|
303
|
+
maxWidth: "100%",
|
|
304
|
+
children: label
|
|
305
|
+
}
|
|
306
|
+
);
|
|
307
|
+
}
|
|
308
|
+
var LEGEND_VARIANTS = [
|
|
309
|
+
{ variant: "read", label: translate("Read") },
|
|
310
|
+
{ variant: "hide", label: translate("Hide") },
|
|
311
|
+
{ variant: "show", label: translate("Show") },
|
|
312
|
+
{ variant: "assign", label: translate("Assign") },
|
|
313
|
+
{ variant: "mandatory", label: translate("Required") },
|
|
314
|
+
{ variant: "warning", label: translate("Warning") },
|
|
315
|
+
{ variant: "error", label: translate("Error") },
|
|
316
|
+
{ variant: "feedback", label: translate("Feedback") }
|
|
317
|
+
];
|
|
318
|
+
function EffectLegend({ activeVariants }) {
|
|
319
|
+
const items = LEGEND_VARIANTS.filter((item) => activeVariants.has(item.variant));
|
|
320
|
+
if (!items.length) {
|
|
321
|
+
return null;
|
|
322
|
+
}
|
|
323
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex min-w-0 flex-wrap items-center gap-dp8 border-t border-dhis2-grey-200 px-dp12 py-dp8", children: items.map((item) => {
|
|
324
|
+
const Icon = EFFECT_ICONS[item.variant];
|
|
325
|
+
const tagProps = getEffectTagRenderPropsForVariant(item.variant);
|
|
326
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
327
|
+
ui.Tag,
|
|
328
|
+
{
|
|
329
|
+
...tagProps,
|
|
330
|
+
icon: /* @__PURE__ */ jsxRuntime.jsx(Icon, { "aria-hidden": "true" }),
|
|
331
|
+
maxWidth: "100%",
|
|
332
|
+
children: item.label
|
|
333
|
+
},
|
|
334
|
+
item.variant
|
|
335
|
+
);
|
|
336
|
+
}) });
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
// src/formatAgo.ts
|
|
340
|
+
function formatAgo(timestamp, now = Date.now()) {
|
|
341
|
+
const diffMs = Math.max(0, now - timestamp);
|
|
342
|
+
if (diffMs < 5e3) {
|
|
343
|
+
return translate("just now");
|
|
344
|
+
}
|
|
345
|
+
const seconds = Math.floor(diffMs / 1e3);
|
|
346
|
+
if (seconds < 60) {
|
|
347
|
+
return translate("{{seconds}}s ago", { seconds });
|
|
348
|
+
}
|
|
349
|
+
const minutes = Math.floor(seconds / 60);
|
|
350
|
+
if (minutes < 60) {
|
|
351
|
+
return translate("{{minutes}}m ago", { minutes });
|
|
352
|
+
}
|
|
353
|
+
const hours = Math.floor(minutes / 60);
|
|
354
|
+
if (hours < 24) {
|
|
355
|
+
return translate("{{hours}}h ago", { hours });
|
|
356
|
+
}
|
|
357
|
+
return new Date(timestamp).toLocaleString();
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
// src/formatRuleActionSummary.ts
|
|
361
|
+
function resolveFieldLabel(id, displayName, labelLookup) {
|
|
362
|
+
if (labelLookup) {
|
|
363
|
+
const resolved = labelLookup.resolveFieldName(id);
|
|
364
|
+
if (resolved !== id) {
|
|
365
|
+
return resolved;
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
return displayName ?? id;
|
|
369
|
+
}
|
|
370
|
+
function resolveSectionLabel(id, displayName, labelLookup) {
|
|
371
|
+
if (labelLookup) {
|
|
372
|
+
const resolved = labelLookup.resolveSectionName(id);
|
|
373
|
+
if (resolved !== id) {
|
|
374
|
+
return resolved;
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
return displayName ?? id;
|
|
378
|
+
}
|
|
379
|
+
function formatRuleActionSummary(action, labelLookup) {
|
|
380
|
+
const type = action.programRuleActionType ?? "UNKNOWN";
|
|
381
|
+
let targetLabel = "";
|
|
382
|
+
if (action.dataElement?.id) {
|
|
383
|
+
targetLabel = resolveFieldLabel(
|
|
384
|
+
action.dataElement.id,
|
|
385
|
+
action.dataElement.displayName,
|
|
386
|
+
labelLookup
|
|
387
|
+
);
|
|
388
|
+
} else if (action.trackedEntityAttribute?.id) {
|
|
389
|
+
targetLabel = resolveFieldLabel(
|
|
390
|
+
action.trackedEntityAttribute.id,
|
|
391
|
+
action.trackedEntityAttribute.displayName,
|
|
392
|
+
labelLookup
|
|
393
|
+
);
|
|
394
|
+
} else if (action.programStageSection?.id) {
|
|
395
|
+
targetLabel = resolveSectionLabel(
|
|
396
|
+
action.programStageSection.id,
|
|
397
|
+
action.programStageSection.displayName,
|
|
398
|
+
labelLookup
|
|
399
|
+
);
|
|
400
|
+
} else if (action.programSection?.id) {
|
|
401
|
+
targetLabel = resolveSectionLabel(
|
|
402
|
+
action.programSection.id,
|
|
403
|
+
action.programSection.displayName,
|
|
404
|
+
labelLookup
|
|
405
|
+
);
|
|
406
|
+
} else if (type === "DISPLAYTEXT" || type === "DISPLAYKEYVALUEPAIR") {
|
|
407
|
+
targetLabel = action.location ?? "feedback";
|
|
408
|
+
}
|
|
409
|
+
let detail;
|
|
410
|
+
if (action.data) {
|
|
411
|
+
detail = action.data;
|
|
412
|
+
}
|
|
413
|
+
if (action.content && (type === "DISPLAYTEXT" || type === "DISPLAYKEYVALUEPAIR")) {
|
|
414
|
+
detail = action.content;
|
|
415
|
+
}
|
|
416
|
+
return { type, targetLabel, detail };
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
// src/resolveProgramRulesList.ts
|
|
420
|
+
function sortByPriority(rules) {
|
|
421
|
+
return [...rules].sort((left, right) => (left.priority ?? 0) - (right.priority ?? 0));
|
|
422
|
+
}
|
|
423
|
+
function toCatalogRule(rule) {
|
|
424
|
+
return {
|
|
425
|
+
id: rule.id,
|
|
426
|
+
name: rule.name,
|
|
427
|
+
condition: rule.condition,
|
|
428
|
+
priority: rule.priority,
|
|
429
|
+
programRuleActions: [...rule.programRuleActions ?? []],
|
|
430
|
+
programStageId: rule.programStageId
|
|
431
|
+
};
|
|
432
|
+
}
|
|
433
|
+
function resolveProgramRulesList(metadata) {
|
|
434
|
+
if (metadata.formKind === "event") {
|
|
435
|
+
return sortByPriority(
|
|
436
|
+
metadata.metadata.programRules.filter((rule) => Boolean(rule.id)).map(
|
|
437
|
+
(rule) => toCatalogRule({
|
|
438
|
+
id: rule.id,
|
|
439
|
+
name: rule.displayName ?? rule.id,
|
|
440
|
+
condition: rule.condition,
|
|
441
|
+
priority: rule.priority,
|
|
442
|
+
programRuleActions: rule.programRuleActions,
|
|
443
|
+
programStageId: rule.programStage?.id ?? null
|
|
444
|
+
})
|
|
445
|
+
)
|
|
446
|
+
);
|
|
447
|
+
}
|
|
448
|
+
return sortByPriority(
|
|
449
|
+
metadata.metadata.programRules.filter((rule) => Boolean(rule.id)).map(
|
|
450
|
+
(rule) => toCatalogRule({
|
|
451
|
+
id: rule.id,
|
|
452
|
+
name: rule.name ?? rule.id,
|
|
453
|
+
condition: rule.condition,
|
|
454
|
+
priority: rule.priority,
|
|
455
|
+
programRuleActions: rule.programRuleActions,
|
|
456
|
+
programStageId: rule.programStage?.id ?? null
|
|
457
|
+
})
|
|
458
|
+
)
|
|
459
|
+
);
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
// src/constants.ts
|
|
463
|
+
var DEFAULT_TRACE_MAX_ENTRIES = 200;
|
|
464
|
+
|
|
465
|
+
// src/traceStore.ts
|
|
466
|
+
function createRuleTraceStore(maxEntries = DEFAULT_TRACE_MAX_ENTRIES) {
|
|
467
|
+
let entries = [];
|
|
468
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
469
|
+
let disposeTraceSubscription = null;
|
|
470
|
+
const notify = () => {
|
|
471
|
+
for (const listener of listeners) {
|
|
472
|
+
listener();
|
|
473
|
+
}
|
|
474
|
+
};
|
|
475
|
+
return {
|
|
476
|
+
record(entry) {
|
|
477
|
+
entries = [...entries, entry].slice(-maxEntries);
|
|
478
|
+
notify();
|
|
479
|
+
},
|
|
480
|
+
getSnapshot() {
|
|
481
|
+
return entries;
|
|
482
|
+
},
|
|
483
|
+
subscribe(listener) {
|
|
484
|
+
listeners.add(listener);
|
|
485
|
+
return () => {
|
|
486
|
+
listeners.delete(listener);
|
|
487
|
+
};
|
|
488
|
+
},
|
|
489
|
+
dispose() {
|
|
490
|
+
disposeTraceSubscription?.();
|
|
491
|
+
disposeTraceSubscription = null;
|
|
492
|
+
listeners.clear();
|
|
493
|
+
},
|
|
494
|
+
bindTraceSubscription(unsubscribe) {
|
|
495
|
+
disposeTraceSubscription = unsubscribe;
|
|
496
|
+
}
|
|
497
|
+
};
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
// src/attach.ts
|
|
501
|
+
function attachRuleDevtools(formStore, options) {
|
|
502
|
+
const store = createRuleTraceStore(
|
|
503
|
+
DEFAULT_TRACE_MAX_ENTRIES
|
|
504
|
+
);
|
|
505
|
+
store.bindTraceSubscription(formStore.subscribeTrace(store.record));
|
|
506
|
+
return store;
|
|
507
|
+
}
|
|
508
|
+
var RuleTraceContext = react.createContext(null);
|
|
509
|
+
function RuleDevtoolsScope({ formStore, children }) {
|
|
510
|
+
const traceStore = react.useMemo(() => attachRuleDevtools(formStore), [formStore]);
|
|
511
|
+
react.useEffect(() => {
|
|
512
|
+
return () => {
|
|
513
|
+
traceStore.dispose();
|
|
514
|
+
};
|
|
515
|
+
}, [traceStore]);
|
|
516
|
+
return /* @__PURE__ */ jsxRuntime.jsx(RuleTraceContext.Provider, { value: traceStore, children });
|
|
517
|
+
}
|
|
518
|
+
function useRuleTraceStore() {
|
|
519
|
+
const store = react.useContext(RuleTraceContext);
|
|
520
|
+
if (!store) {
|
|
521
|
+
throw new Error("useRuleTraceStore must be used within RuleDevtoolsScope");
|
|
522
|
+
}
|
|
523
|
+
return store;
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
// src/graphLayout.ts
|
|
527
|
+
var ROLE_COLUMNS = {
|
|
528
|
+
source: 0,
|
|
529
|
+
rule: 280,
|
|
530
|
+
target: 560
|
|
531
|
+
};
|
|
532
|
+
var FEEDBACK_COLUMN = 720;
|
|
533
|
+
var LANE_HEIGHT = 104;
|
|
534
|
+
var STAGGER = 24;
|
|
535
|
+
function median(values) {
|
|
536
|
+
const sorted = [...values].sort((a, b) => a - b);
|
|
537
|
+
const mid = Math.floor(sorted.length / 2);
|
|
538
|
+
return sorted.length % 2 === 0 ? (sorted[mid - 1] + sorted[mid]) / 2 : sorted[mid];
|
|
539
|
+
}
|
|
540
|
+
function staggerOffset(indexInBucket) {
|
|
541
|
+
if (indexInBucket === 0) {
|
|
542
|
+
return 0;
|
|
543
|
+
}
|
|
544
|
+
const magnitude = Math.ceil(indexInBucket / 2) * STAGGER;
|
|
545
|
+
return indexInBucket % 2 === 1 ? magnitude : -magnitude;
|
|
546
|
+
}
|
|
547
|
+
function columnFor(node) {
|
|
548
|
+
if (node.role === "target" && node.kind === "feedback") {
|
|
549
|
+
return FEEDBACK_COLUMN;
|
|
550
|
+
}
|
|
551
|
+
return ROLE_COLUMNS[node.role];
|
|
552
|
+
}
|
|
553
|
+
function prepareFlowGraph(graph) {
|
|
554
|
+
const readSources = /* @__PURE__ */ new Set();
|
|
555
|
+
const effectTargets = /* @__PURE__ */ new Set();
|
|
556
|
+
for (const edge of graph.edges) {
|
|
557
|
+
if (edge.effectType === "read") {
|
|
558
|
+
readSources.add(edge.source);
|
|
559
|
+
} else {
|
|
560
|
+
effectTargets.add(edge.target);
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
const flowNodes = [];
|
|
564
|
+
const endpointByGraphId = /* @__PURE__ */ new Map();
|
|
565
|
+
for (const node of graph.nodes) {
|
|
566
|
+
if (node.kind === "rule") {
|
|
567
|
+
flowNodes.push({
|
|
568
|
+
id: node.id,
|
|
569
|
+
graphNodeId: node.id,
|
|
570
|
+
kind: node.kind,
|
|
571
|
+
label: node.label,
|
|
572
|
+
role: "rule"
|
|
573
|
+
});
|
|
574
|
+
continue;
|
|
575
|
+
}
|
|
576
|
+
const isSource = readSources.has(node.id);
|
|
577
|
+
const isTarget = effectTargets.has(node.id);
|
|
578
|
+
const endpoints = {};
|
|
579
|
+
if (isSource) {
|
|
580
|
+
const flowId = isTarget ? `${node.id}@src` : node.id;
|
|
581
|
+
endpoints.source = flowId;
|
|
582
|
+
flowNodes.push({
|
|
583
|
+
id: flowId,
|
|
584
|
+
graphNodeId: node.id,
|
|
585
|
+
kind: node.kind,
|
|
586
|
+
label: node.label,
|
|
587
|
+
role: "source"
|
|
588
|
+
});
|
|
589
|
+
}
|
|
590
|
+
if (isTarget) {
|
|
591
|
+
const flowId = isSource ? `${node.id}@tgt` : node.id;
|
|
592
|
+
endpoints.target = flowId;
|
|
593
|
+
flowNodes.push({
|
|
594
|
+
id: flowId,
|
|
595
|
+
graphNodeId: node.id,
|
|
596
|
+
kind: node.kind,
|
|
597
|
+
label: node.label,
|
|
598
|
+
role: "target"
|
|
599
|
+
});
|
|
600
|
+
}
|
|
601
|
+
endpointByGraphId.set(node.id, endpoints);
|
|
602
|
+
}
|
|
603
|
+
const flowEdges = graph.edges.map((edge) => {
|
|
604
|
+
if (edge.effectType === "read") {
|
|
605
|
+
const endpoints2 = endpointByGraphId.get(edge.source);
|
|
606
|
+
return { ...edge, source: endpoints2?.source ?? edge.source };
|
|
607
|
+
}
|
|
608
|
+
const endpoints = endpointByGraphId.get(edge.target);
|
|
609
|
+
return { ...edge, target: endpoints?.target ?? edge.target };
|
|
610
|
+
});
|
|
611
|
+
return { nodes: flowNodes, edges: flowEdges };
|
|
612
|
+
}
|
|
613
|
+
function computeGraphLayout(flowNodes, flowEdges) {
|
|
614
|
+
const positions = /* @__PURE__ */ new Map();
|
|
615
|
+
const ruleLane = /* @__PURE__ */ new Map();
|
|
616
|
+
flowNodes.filter((node) => node.role === "rule").forEach((node, index) => {
|
|
617
|
+
ruleLane.set(node.id, index);
|
|
618
|
+
positions.set(node.id, { x: ROLE_COLUMNS.rule, y: index * LANE_HEIGHT });
|
|
619
|
+
});
|
|
620
|
+
const connectedRuleLanes = /* @__PURE__ */ new Map();
|
|
621
|
+
const addLane = (nodeId, lane) => {
|
|
622
|
+
const lanes = connectedRuleLanes.get(nodeId) ?? /* @__PURE__ */ new Set();
|
|
623
|
+
lanes.add(lane);
|
|
624
|
+
connectedRuleLanes.set(nodeId, lanes);
|
|
625
|
+
};
|
|
626
|
+
for (const edge of flowEdges) {
|
|
627
|
+
const sourceLane = ruleLane.get(edge.source);
|
|
628
|
+
const targetLane = ruleLane.get(edge.target);
|
|
629
|
+
if (targetLane !== void 0 && sourceLane === void 0) {
|
|
630
|
+
addLane(edge.source, targetLane);
|
|
631
|
+
}
|
|
632
|
+
if (sourceLane !== void 0 && targetLane === void 0) {
|
|
633
|
+
addLane(edge.target, sourceLane);
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
const bucketCounts = /* @__PURE__ */ new Map();
|
|
637
|
+
for (const node of flowNodes) {
|
|
638
|
+
if (node.role === "rule") {
|
|
639
|
+
continue;
|
|
640
|
+
}
|
|
641
|
+
const lanes = connectedRuleLanes.get(node.id);
|
|
642
|
+
const lane = lanes && lanes.size > 0 ? median([...lanes]) : 0;
|
|
643
|
+
const column = columnFor(node);
|
|
644
|
+
const bucketKey = `${String(column)}:${String(lane)}`;
|
|
645
|
+
const indexInBucket = bucketCounts.get(bucketKey) ?? 0;
|
|
646
|
+
bucketCounts.set(bucketKey, indexInBucket + 1);
|
|
647
|
+
positions.set(node.id, {
|
|
648
|
+
x: column,
|
|
649
|
+
y: lane * LANE_HEIGHT + staggerOffset(indexInBucket)
|
|
650
|
+
});
|
|
651
|
+
}
|
|
652
|
+
return positions;
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
// src/graphNodeStyles.ts
|
|
656
|
+
var GRAPH_NODE_CLASSES = {
|
|
657
|
+
field: "border-dhis2-blue-500",
|
|
658
|
+
rule: "border-dhis2-teal-600 bg-dhis2-teal-050",
|
|
659
|
+
section: "border-dhis2-grey-600",
|
|
660
|
+
feedback: "border-dhis2-purple-500"
|
|
661
|
+
};
|
|
662
|
+
var LEGEND_SWATCH_CLASSES = {
|
|
663
|
+
field: "bg-dhis2-blue-500",
|
|
664
|
+
rule: "bg-dhis2-teal-600",
|
|
665
|
+
section: "bg-dhis2-grey-600",
|
|
666
|
+
feedback: "bg-dhis2-purple-500"
|
|
667
|
+
};
|
|
668
|
+
function getGraphNodeClassName(kind, highlighted) {
|
|
669
|
+
return `p-dp8 rounded border bg-white min-w-28 max-w-[180px] text-xs shadow-[0_1px_2px_rgb(0_0_0/6%)] transition-opacity duration-150 ease-out cursor-grab active:cursor-grabbing ${GRAPH_NODE_CLASSES[kind]} ${highlighted ? "" : "opacity-35"}`;
|
|
670
|
+
}
|
|
671
|
+
function getLegendSwatchClassName(kind) {
|
|
672
|
+
return `size-2.5 shrink-0 rounded-sm ${LEGEND_SWATCH_CLASSES[kind]}`;
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
// src/traceEntry.ts
|
|
676
|
+
function resolveGraphTraceEntry(entries, selectedEntryId) {
|
|
677
|
+
if (!entries.length) {
|
|
678
|
+
return null;
|
|
679
|
+
}
|
|
680
|
+
if (selectedEntryId) {
|
|
681
|
+
return entries.find((entry) => entry.id === selectedEntryId) ?? entries[entries.length - 1];
|
|
682
|
+
}
|
|
683
|
+
return entries[entries.length - 1];
|
|
684
|
+
}
|
|
685
|
+
function getActiveRuleIds(entries) {
|
|
686
|
+
const latest = resolveGraphTraceEntry(entries);
|
|
687
|
+
if (!latest) {
|
|
688
|
+
return /* @__PURE__ */ new Set();
|
|
689
|
+
}
|
|
690
|
+
return new Set(latest.ruleResults.map((result) => result.ruleId));
|
|
691
|
+
}
|
|
692
|
+
var KIND_LABELS = {
|
|
693
|
+
field: "Field",
|
|
694
|
+
rule: "Rule",
|
|
695
|
+
section: "Section",
|
|
696
|
+
feedback: "Feedback"
|
|
697
|
+
};
|
|
698
|
+
var READ_LABEL_EDGE_THRESHOLD = 4;
|
|
699
|
+
var DEFAULT_EDGE_OPTIONS = {
|
|
700
|
+
type: "ruleGraphEdge",
|
|
701
|
+
style: { stroke: getEffectEdgeStroke("default") },
|
|
702
|
+
markerEnd: {
|
|
703
|
+
type: react$1.MarkerType.ArrowClosed,
|
|
704
|
+
color: getEffectEdgeStroke("default")
|
|
705
|
+
}
|
|
706
|
+
};
|
|
707
|
+
function RuleGraphNode({ data }) {
|
|
708
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: getGraphNodeClassName(data.kind, data.highlighted), children: [
|
|
709
|
+
/* @__PURE__ */ jsxRuntime.jsx(react$1.Handle, { type: "target", position: react$1.Position.Left }),
|
|
710
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "mb-dp4 block text-[0.625rem] font-semibold uppercase leading-none tracking-wide text-dhis2-grey-600", children: KIND_LABELS[data.kind] }),
|
|
711
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "break-words font-semibold leading-[1.3]", children: data.label }),
|
|
712
|
+
data.value !== void 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-dp4 border-t border-dhis2-grey-200 pt-dp4 font-mono text-[0.6875rem] break-all text-dhis2-grey-700", children: data.value }) : null,
|
|
713
|
+
/* @__PURE__ */ jsxRuntime.jsx(react$1.Handle, { type: "source", position: react$1.Position.Right })
|
|
714
|
+
] });
|
|
715
|
+
}
|
|
716
|
+
var nodeTypes = {
|
|
717
|
+
ruleGraphNode: react.memo(RuleGraphNode)
|
|
718
|
+
};
|
|
719
|
+
function RuleGraphEdge({
|
|
720
|
+
id,
|
|
721
|
+
sourceX,
|
|
722
|
+
sourceY,
|
|
723
|
+
sourcePosition,
|
|
724
|
+
targetX,
|
|
725
|
+
targetY,
|
|
726
|
+
targetPosition,
|
|
727
|
+
markerEnd,
|
|
728
|
+
style,
|
|
729
|
+
data
|
|
730
|
+
}) {
|
|
731
|
+
const [hovered, setHovered] = react.useState(false);
|
|
732
|
+
const [edgePath, labelX, labelY] = react$1.getSmoothStepPath({
|
|
733
|
+
sourceX,
|
|
734
|
+
sourceY,
|
|
735
|
+
sourcePosition,
|
|
736
|
+
targetX,
|
|
737
|
+
targetY,
|
|
738
|
+
targetPosition,
|
|
739
|
+
offset: data?.offset ?? 20
|
|
740
|
+
});
|
|
741
|
+
const showLabel = data ? data.showLabel || hovered : false;
|
|
742
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
743
|
+
/* @__PURE__ */ jsxRuntime.jsx(react$1.BaseEdge, { id, path: edgePath, markerEnd, style }),
|
|
744
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
745
|
+
"path",
|
|
746
|
+
{
|
|
747
|
+
d: edgePath,
|
|
748
|
+
fill: "none",
|
|
749
|
+
stroke: "transparent",
|
|
750
|
+
strokeWidth: 16,
|
|
751
|
+
onMouseEnter: () => {
|
|
752
|
+
setHovered(true);
|
|
753
|
+
},
|
|
754
|
+
onMouseLeave: () => {
|
|
755
|
+
setHovered(false);
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
),
|
|
759
|
+
showLabel && data ? /* @__PURE__ */ jsxRuntime.jsx(react$1.EdgeLabelRenderer, { children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
760
|
+
"div",
|
|
761
|
+
{
|
|
762
|
+
className: "nodrag nopan absolute rounded-sm px-dp4 text-[10px] font-semibold",
|
|
763
|
+
style: {
|
|
764
|
+
transform: `translate(-50%, -50%) translate(${String(labelX)}px, ${String(labelY)}px)`,
|
|
765
|
+
background: "rgba(255,255,255,0.92)",
|
|
766
|
+
color: data.stroke,
|
|
767
|
+
pointerEvents: "all"
|
|
768
|
+
},
|
|
769
|
+
onMouseEnter: () => {
|
|
770
|
+
setHovered(true);
|
|
771
|
+
},
|
|
772
|
+
onMouseLeave: () => {
|
|
773
|
+
setHovered(false);
|
|
774
|
+
},
|
|
775
|
+
children: data.label
|
|
776
|
+
}
|
|
777
|
+
) }) : null
|
|
778
|
+
] });
|
|
779
|
+
}
|
|
780
|
+
var edgeTypes = {
|
|
781
|
+
ruleGraphEdge: react.memo(RuleGraphEdge)
|
|
782
|
+
};
|
|
783
|
+
function effectNodeKey(effect) {
|
|
784
|
+
const kind = effect.type === "HIDESECTION" ? "section" : effect.type === "DISPLAYTEXT" || effect.type === "DISPLAYKEYVALUEPAIR" ? "feedback" : "field";
|
|
785
|
+
return `${kind}:${effect.targetId}`;
|
|
786
|
+
}
|
|
787
|
+
function entryGraphKeys(entry) {
|
|
788
|
+
const keys = /* @__PURE__ */ new Set();
|
|
789
|
+
for (const fieldId of entry.changedFields) {
|
|
790
|
+
keys.add(`field:${fieldId}`);
|
|
791
|
+
}
|
|
792
|
+
for (const result of entry.ruleResults) {
|
|
793
|
+
keys.add(`rule:${result.ruleId}`);
|
|
794
|
+
for (const effect of result.effects) {
|
|
795
|
+
keys.add(effectNodeKey(effect));
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
return keys;
|
|
799
|
+
}
|
|
800
|
+
function ruleGraphKeys(entry, ruleId) {
|
|
801
|
+
const keys = /* @__PURE__ */ new Set([`rule:${ruleId}`]);
|
|
802
|
+
for (const result of entry.ruleResults) {
|
|
803
|
+
if (result.ruleId !== ruleId) {
|
|
804
|
+
continue;
|
|
805
|
+
}
|
|
806
|
+
for (const effect of result.effects) {
|
|
807
|
+
keys.add(effectNodeKey(effect));
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
return keys;
|
|
811
|
+
}
|
|
812
|
+
var formatDisplayValue = (value) => {
|
|
813
|
+
if (value === void 0 || value === null || value === "") {
|
|
814
|
+
return void 0;
|
|
815
|
+
}
|
|
816
|
+
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
|
|
817
|
+
return String(value);
|
|
818
|
+
}
|
|
819
|
+
return JSON.stringify(value);
|
|
820
|
+
};
|
|
821
|
+
function toFlowGraph(graph, fieldState, formValues, highlightedKeys) {
|
|
822
|
+
const { nodes: flowNodes, edges: flowEdges } = prepareFlowGraph(graph);
|
|
823
|
+
const positions = computeGraphLayout(flowNodes, flowEdges);
|
|
824
|
+
const graphNodeIdByFlowId = new Map(flowNodes.map((node) => [node.id, node.graphNodeId]));
|
|
825
|
+
const nodes = flowNodes.map((node) => {
|
|
826
|
+
const position = positions.get(node.id) ?? { x: 0, y: 0 };
|
|
827
|
+
const rawFieldId = node.kind === "field" ? node.graphNodeId.slice("field:".length) : void 0;
|
|
828
|
+
const assignedValue = rawFieldId && rawFieldId in fieldState ? fieldState[rawFieldId].assignedValue : void 0;
|
|
829
|
+
const formValue = rawFieldId ? formValues[rawFieldId] : void 0;
|
|
830
|
+
const displayValue = formatDisplayValue(assignedValue) ?? formatDisplayValue(formValue);
|
|
831
|
+
return {
|
|
832
|
+
id: node.id,
|
|
833
|
+
type: "ruleGraphNode",
|
|
834
|
+
position,
|
|
835
|
+
data: {
|
|
836
|
+
label: node.label,
|
|
837
|
+
kind: node.kind,
|
|
838
|
+
graphNodeId: node.graphNodeId,
|
|
839
|
+
value: displayValue,
|
|
840
|
+
highlighted: highlightedKeys ? highlightedKeys.has(node.graphNodeId) : true
|
|
841
|
+
}
|
|
842
|
+
};
|
|
843
|
+
});
|
|
844
|
+
const dense = flowEdges.length > READ_LABEL_EDGE_THRESHOLD;
|
|
845
|
+
const sourceFanOut = /* @__PURE__ */ new Map();
|
|
846
|
+
const edges = flowEdges.map((edge) => {
|
|
847
|
+
const sourceGraphId = graphNodeIdByFlowId.get(edge.source) ?? edge.source;
|
|
848
|
+
const targetGraphId = graphNodeIdByFlowId.get(edge.target) ?? edge.target;
|
|
849
|
+
const isHighlighted = highlightedKeys ? highlightedKeys.has(sourceGraphId) && highlightedKeys.has(targetGraphId) : true;
|
|
850
|
+
const effectType = edge.effectType ?? "default";
|
|
851
|
+
const isRead = effectType === "read";
|
|
852
|
+
const stroke = getEffectEdgeStroke(effectType, isHighlighted);
|
|
853
|
+
const label = getEffectShortLabel(effectType);
|
|
854
|
+
const showLabel = !(isRead && dense);
|
|
855
|
+
const fanIndex = sourceFanOut.get(edge.source) ?? 0;
|
|
856
|
+
sourceFanOut.set(edge.source, fanIndex + 1);
|
|
857
|
+
const offset = 20 + fanIndex * 14;
|
|
858
|
+
return {
|
|
859
|
+
id: edge.id,
|
|
860
|
+
source: edge.source,
|
|
861
|
+
target: edge.target,
|
|
862
|
+
type: "ruleGraphEdge",
|
|
863
|
+
animated: isHighlighted && !isRead,
|
|
864
|
+
zIndex: isHighlighted ? 1 : 0,
|
|
865
|
+
style: {
|
|
866
|
+
stroke,
|
|
867
|
+
strokeWidth: isRead ? 1 : Math.min(1 + edge.fireCount, 4),
|
|
868
|
+
opacity: isHighlighted ? isRead ? 0.5 : 1 : 0.35
|
|
869
|
+
},
|
|
870
|
+
markerEnd: {
|
|
871
|
+
type: react$1.MarkerType.ArrowClosed,
|
|
872
|
+
color: stroke
|
|
873
|
+
},
|
|
874
|
+
data: {
|
|
875
|
+
label,
|
|
876
|
+
stroke,
|
|
877
|
+
showLabel,
|
|
878
|
+
offset
|
|
879
|
+
}
|
|
880
|
+
};
|
|
881
|
+
});
|
|
882
|
+
return { nodes, edges };
|
|
883
|
+
}
|
|
884
|
+
function GraphToolbar({
|
|
885
|
+
nodeCount,
|
|
886
|
+
edgeCount,
|
|
887
|
+
headerActions,
|
|
888
|
+
activeEffectVariants
|
|
889
|
+
}) {
|
|
890
|
+
const items = [
|
|
891
|
+
{ kind: "field", label: translate("Field") },
|
|
892
|
+
{ kind: "rule", label: translate("Rule") },
|
|
893
|
+
{ kind: "section", label: translate("Section") },
|
|
894
|
+
{ kind: "feedback", label: translate("Feedback") }
|
|
895
|
+
];
|
|
896
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "shrink-0 border-b border-dhis2-grey-200 bg-white", children: [
|
|
897
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-wrap items-center justify-between gap-dp8 px-dp12 py-dp8", children: [
|
|
898
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
899
|
+
"div",
|
|
900
|
+
{
|
|
901
|
+
className: "flex min-w-0 flex-wrap items-center gap-x-dp12 gap-y-dp8",
|
|
902
|
+
"aria-hidden": "true",
|
|
903
|
+
children: items.map((item) => /* @__PURE__ */ jsxRuntime.jsxs(
|
|
904
|
+
"span",
|
|
905
|
+
{
|
|
906
|
+
className: "inline-flex items-center gap-dp8 text-xs text-dhis2-grey-800",
|
|
907
|
+
children: [
|
|
908
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: getLegendSwatchClassName(item.kind) }),
|
|
909
|
+
item.label
|
|
910
|
+
]
|
|
911
|
+
},
|
|
912
|
+
item.kind
|
|
913
|
+
))
|
|
914
|
+
}
|
|
915
|
+
),
|
|
916
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex shrink-0 items-center gap-dp8", children: [
|
|
917
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs tabular-nums text-dhis2-grey-600", children: translate("{{nodes}} nodes \xB7 {{edges}} edges", {
|
|
918
|
+
nodes: nodeCount,
|
|
919
|
+
edges: edgeCount
|
|
920
|
+
}) }),
|
|
921
|
+
headerActions
|
|
922
|
+
] })
|
|
923
|
+
] }),
|
|
924
|
+
/* @__PURE__ */ jsxRuntime.jsx(EffectLegend, { activeVariants: activeEffectVariants })
|
|
925
|
+
] });
|
|
926
|
+
}
|
|
927
|
+
function FitViewOnChange({
|
|
928
|
+
layoutKey,
|
|
929
|
+
nodeCount,
|
|
930
|
+
edgeCount
|
|
931
|
+
}) {
|
|
932
|
+
const { fitView } = react$1.useReactFlow();
|
|
933
|
+
react.useEffect(() => {
|
|
934
|
+
const frameId = requestAnimationFrame(() => {
|
|
935
|
+
void fitView({ padding: 0.2, duration: 150 });
|
|
936
|
+
});
|
|
937
|
+
return () => {
|
|
938
|
+
cancelAnimationFrame(frameId);
|
|
939
|
+
};
|
|
940
|
+
}, [edgeCount, fitView, layoutKey, nodeCount]);
|
|
941
|
+
return null;
|
|
942
|
+
}
|
|
943
|
+
function mergeNodePositions(next, current) {
|
|
944
|
+
const currentById = new Map(current.map((node) => [node.id, node]));
|
|
945
|
+
return next.map((node) => {
|
|
946
|
+
const existing = currentById.get(node.id);
|
|
947
|
+
if (existing) {
|
|
948
|
+
return { ...node, position: existing.position };
|
|
949
|
+
}
|
|
950
|
+
return node;
|
|
951
|
+
});
|
|
952
|
+
}
|
|
953
|
+
function RuleGraphCanvas({
|
|
954
|
+
nodes: layoutNodes,
|
|
955
|
+
edges: layoutEdges,
|
|
956
|
+
layoutKey
|
|
957
|
+
}) {
|
|
958
|
+
const [nodes, setNodes, onNodesChange] = react$1.useNodesState(layoutNodes);
|
|
959
|
+
const [edges, setEdges, onEdgesChange] = react$1.useEdgesState(layoutEdges);
|
|
960
|
+
react.useEffect(() => {
|
|
961
|
+
setNodes((current) => mergeNodePositions(layoutNodes, current));
|
|
962
|
+
}, [layoutNodes, setNodes]);
|
|
963
|
+
react.useEffect(() => {
|
|
964
|
+
setEdges(layoutEdges);
|
|
965
|
+
}, [layoutEdges, setEdges]);
|
|
966
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
967
|
+
react$1.ReactFlow,
|
|
968
|
+
{
|
|
969
|
+
nodes,
|
|
970
|
+
edges,
|
|
971
|
+
nodeTypes,
|
|
972
|
+
edgeTypes,
|
|
973
|
+
onNodesChange,
|
|
974
|
+
onEdgesChange,
|
|
975
|
+
nodesDraggable: true,
|
|
976
|
+
defaultEdgeOptions: DEFAULT_EDGE_OPTIONS,
|
|
977
|
+
elevateEdgesOnSelect: true,
|
|
978
|
+
fitView: true,
|
|
979
|
+
proOptions: { hideAttribution: true },
|
|
980
|
+
className: "h-full w-full",
|
|
981
|
+
children: [
|
|
982
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
983
|
+
FitViewOnChange,
|
|
984
|
+
{
|
|
985
|
+
layoutKey,
|
|
986
|
+
nodeCount: nodes.length,
|
|
987
|
+
edgeCount: edges.length
|
|
988
|
+
}
|
|
989
|
+
),
|
|
990
|
+
/* @__PURE__ */ jsxRuntime.jsx(react$1.Background, { gap: 16, size: 1 }),
|
|
991
|
+
/* @__PURE__ */ jsxRuntime.jsx(react$1.Controls, { showInteractive: false })
|
|
992
|
+
]
|
|
993
|
+
}
|
|
994
|
+
);
|
|
995
|
+
}
|
|
996
|
+
function RuleGraphView({
|
|
997
|
+
entries,
|
|
998
|
+
fieldState,
|
|
999
|
+
formValues,
|
|
1000
|
+
selectedEntryId,
|
|
1001
|
+
highlightRuleId,
|
|
1002
|
+
labelLookup,
|
|
1003
|
+
className,
|
|
1004
|
+
layoutKey,
|
|
1005
|
+
headerActions,
|
|
1006
|
+
minHeightClassName = "min-h-[360px]"
|
|
1007
|
+
}) {
|
|
1008
|
+
const graphEntry = react.useMemo(
|
|
1009
|
+
() => resolveGraphTraceEntry(entries, selectedEntryId),
|
|
1010
|
+
[entries, selectedEntryId]
|
|
1011
|
+
);
|
|
1012
|
+
const graph = react.useMemo(
|
|
1013
|
+
() => graphEntry ? buildGraphFromTrace([graphEntry], labelLookup) : { nodes: [], edges: [] },
|
|
1014
|
+
[graphEntry, labelLookup]
|
|
1015
|
+
);
|
|
1016
|
+
const highlightedKeys = react.useMemo(() => {
|
|
1017
|
+
if (!graphEntry || !selectedEntryId && !highlightRuleId) {
|
|
1018
|
+
return null;
|
|
1019
|
+
}
|
|
1020
|
+
const keys = /* @__PURE__ */ new Set();
|
|
1021
|
+
if (selectedEntryId) {
|
|
1022
|
+
for (const key of entryGraphKeys(graphEntry)) {
|
|
1023
|
+
keys.add(key);
|
|
1024
|
+
}
|
|
1025
|
+
}
|
|
1026
|
+
if (highlightRuleId) {
|
|
1027
|
+
for (const key of ruleGraphKeys(graphEntry, highlightRuleId)) {
|
|
1028
|
+
keys.add(key);
|
|
1029
|
+
}
|
|
1030
|
+
}
|
|
1031
|
+
return keys;
|
|
1032
|
+
}, [graphEntry, highlightRuleId, selectedEntryId]);
|
|
1033
|
+
const { nodes, edges } = react.useMemo(
|
|
1034
|
+
() => toFlowGraph(graph, fieldState, formValues, highlightedKeys),
|
|
1035
|
+
[graph, fieldState, formValues, highlightedKeys]
|
|
1036
|
+
);
|
|
1037
|
+
const activeEffectVariants = react.useMemo(() => {
|
|
1038
|
+
const variants = /* @__PURE__ */ new Set();
|
|
1039
|
+
for (const edge of graph.edges) {
|
|
1040
|
+
if (edge.effectType) {
|
|
1041
|
+
variants.add(getEffectVariant(edge.effectType));
|
|
1042
|
+
}
|
|
1043
|
+
}
|
|
1044
|
+
return variants;
|
|
1045
|
+
}, [graph.edges]);
|
|
1046
|
+
if (!graph.nodes.length) {
|
|
1047
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "p-dp8", children: /* @__PURE__ */ jsxRuntime.jsx(ui.NoticeBox, { title: translate("No rule relationships yet"), children: translate(
|
|
1048
|
+
"Interact with the form to build the dependency graph. Only rules active in the latest evaluation are shown. Select a trace entry to inspect a past evaluation. Connections flow Field \u2192 Rule \u2192 Target (read / effect)."
|
|
1049
|
+
) }) });
|
|
1050
|
+
}
|
|
1051
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1052
|
+
"div",
|
|
1053
|
+
{
|
|
1054
|
+
className: `flex h-full min-h-[480px] flex-1 flex-col overflow-hidden rounded-md border border-dhis2-grey-200 bg-white ${className ?? ""}`,
|
|
1055
|
+
children: [
|
|
1056
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1057
|
+
GraphToolbar,
|
|
1058
|
+
{
|
|
1059
|
+
nodeCount: graph.nodes.length,
|
|
1060
|
+
edgeCount: graph.edges.length,
|
|
1061
|
+
headerActions,
|
|
1062
|
+
activeEffectVariants
|
|
1063
|
+
}
|
|
1064
|
+
),
|
|
1065
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1066
|
+
"div",
|
|
1067
|
+
{
|
|
1068
|
+
className: `${minHeightClassName} flex-1 bg-white`,
|
|
1069
|
+
style: { width: "100%", height: "100%" },
|
|
1070
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(react$1.ReactFlowProvider, { children: /* @__PURE__ */ jsxRuntime.jsx(RuleGraphCanvas, { nodes, edges, layoutKey }) })
|
|
1071
|
+
}
|
|
1072
|
+
)
|
|
1073
|
+
]
|
|
1074
|
+
}
|
|
1075
|
+
);
|
|
1076
|
+
}
|
|
1077
|
+
function RuleGraphModal({
|
|
1078
|
+
open,
|
|
1079
|
+
onClose,
|
|
1080
|
+
subtitle,
|
|
1081
|
+
layoutKey,
|
|
1082
|
+
...graphProps
|
|
1083
|
+
}) {
|
|
1084
|
+
if (!open) {
|
|
1085
|
+
return null;
|
|
1086
|
+
}
|
|
1087
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(ui.Modal, { large: true, fluid: true, onClose, children: [
|
|
1088
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ui.ModalTitle, { children: [
|
|
1089
|
+
translate("Rule dependency graph"),
|
|
1090
|
+
subtitle ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "mt-dp4 block text-sm font-normal text-dhis2-grey-700", children: subtitle }) : null
|
|
1091
|
+
] }),
|
|
1092
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.ModalContent, { className: "p-0", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-[min(85vh,900px)] w-full min-w-[min(1200px,92vw)]", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
1093
|
+
RuleGraphView,
|
|
1094
|
+
{
|
|
1095
|
+
...graphProps,
|
|
1096
|
+
layoutKey: layoutKey ?? "modal",
|
|
1097
|
+
className: "h-full min-h-0 rounded-none border-0",
|
|
1098
|
+
minHeightClassName: "min-h-0 h-full"
|
|
1099
|
+
}
|
|
1100
|
+
) }) })
|
|
1101
|
+
] });
|
|
1102
|
+
}
|
|
1103
|
+
function resolveLabel(id, resolver) {
|
|
1104
|
+
const label = resolver?.(id) ?? id;
|
|
1105
|
+
return { label, showId: label !== id };
|
|
1106
|
+
}
|
|
1107
|
+
function resolveEffectTargetLabel(effect, labelLookup) {
|
|
1108
|
+
if (effect.type === "HIDESECTION") {
|
|
1109
|
+
return resolveLabel(
|
|
1110
|
+
effect.targetId,
|
|
1111
|
+
labelLookup ? (id) => labelLookup.resolveSectionName(id) : void 0
|
|
1112
|
+
);
|
|
1113
|
+
}
|
|
1114
|
+
if (effect.type === "DISPLAYTEXT" || effect.type === "DISPLAYKEYVALUEPAIR") {
|
|
1115
|
+
return { label: effect.targetId, showId: false };
|
|
1116
|
+
}
|
|
1117
|
+
return resolveLabel(
|
|
1118
|
+
effect.targetId,
|
|
1119
|
+
labelLookup ? (id) => labelLookup.resolveFieldName(id) : void 0
|
|
1120
|
+
);
|
|
1121
|
+
}
|
|
1122
|
+
function TraceTimeline({
|
|
1123
|
+
entries,
|
|
1124
|
+
selectedEntryId,
|
|
1125
|
+
highlightRuleId,
|
|
1126
|
+
onSelectEntry,
|
|
1127
|
+
onHighlightRule,
|
|
1128
|
+
labelLookup
|
|
1129
|
+
}) {
|
|
1130
|
+
const [expandedIds, setExpandedIds] = react.useState(() => /* @__PURE__ */ new Set());
|
|
1131
|
+
react.useEffect(() => {
|
|
1132
|
+
if (!entries.length) {
|
|
1133
|
+
setExpandedIds(/* @__PURE__ */ new Set());
|
|
1134
|
+
return;
|
|
1135
|
+
}
|
|
1136
|
+
const newestId = entries[entries.length - 1]?.id;
|
|
1137
|
+
if (!newestId) {
|
|
1138
|
+
return;
|
|
1139
|
+
}
|
|
1140
|
+
setExpandedIds((current) => {
|
|
1141
|
+
if (current.size > 0) {
|
|
1142
|
+
return current;
|
|
1143
|
+
}
|
|
1144
|
+
return /* @__PURE__ */ new Set([newestId]);
|
|
1145
|
+
});
|
|
1146
|
+
}, [entries]);
|
|
1147
|
+
if (!entries.length) {
|
|
1148
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "p-dp8", children: /* @__PURE__ */ jsxRuntime.jsx(ui.NoticeBox, { title: translate("No rules observed yet"), children: translate(
|
|
1149
|
+
"Interact with the form to record rule evaluations. Only rules that have fired at least once appear here."
|
|
1150
|
+
) }) });
|
|
1151
|
+
}
|
|
1152
|
+
const reversed = [...entries].reverse();
|
|
1153
|
+
const toggleExpanded = (entryId) => {
|
|
1154
|
+
setExpandedIds((current) => {
|
|
1155
|
+
const next = new Set(current);
|
|
1156
|
+
if (next.has(entryId)) {
|
|
1157
|
+
next.delete(entryId);
|
|
1158
|
+
} else {
|
|
1159
|
+
next.add(entryId);
|
|
1160
|
+
}
|
|
1161
|
+
return next;
|
|
1162
|
+
});
|
|
1163
|
+
};
|
|
1164
|
+
return /* @__PURE__ */ jsxRuntime.jsx("ul", { className: "m-0 flex list-none flex-col gap-dp12 p-0", children: reversed.map((entry) => {
|
|
1165
|
+
const isInitial = entry.changedFields.length === 0;
|
|
1166
|
+
const ruleCount = entry.ruleResults.length;
|
|
1167
|
+
const effectCount = entry.ruleResults.reduce(
|
|
1168
|
+
(total, result) => total + result.effects.length,
|
|
1169
|
+
0
|
|
1170
|
+
);
|
|
1171
|
+
const isExpanded = expandedIds.has(entry.id);
|
|
1172
|
+
const isSelected = selectedEntryId === entry.id;
|
|
1173
|
+
return /* @__PURE__ */ jsxRuntime.jsx("li", { className: "m-0", children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1174
|
+
ui.Card,
|
|
1175
|
+
{
|
|
1176
|
+
className: `overflow-hidden rounded-md border border-dhis2-grey-300 bg-white shadow-[0_1px_2px_rgb(0_0_0/4%)] transition-[border-color,box-shadow] duration-150 ease-out ${isSelected ? "border-dhis2-teal-600 shadow-[0_0_0_1px_var(--color-dhis2-teal-600),0_2px_8px_rgb(0_137_123/12%)]" : ""}`,
|
|
1177
|
+
children: [
|
|
1178
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-stretch gap-dp4 bg-dhis2-grey-050 py-dp4 pe-dp8 ps-dp4", children: [
|
|
1179
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1180
|
+
"button",
|
|
1181
|
+
{
|
|
1182
|
+
type: "button",
|
|
1183
|
+
className: "inline-flex size-10 min-h-10 min-w-10 shrink-0 cursor-pointer items-center justify-center self-center rounded border-none bg-transparent text-dhis2-grey-700 hover:bg-dhis2-grey-200 focus-visible:outline-2 focus-visible:outline-dhis2-teal-600 focus-visible:outline-offset-1",
|
|
1184
|
+
"aria-expanded": isExpanded,
|
|
1185
|
+
"aria-label": isExpanded ? translate("Collapse evaluation details") : translate("Expand evaluation details"),
|
|
1186
|
+
onClick: () => {
|
|
1187
|
+
toggleExpanded(entry.id);
|
|
1188
|
+
},
|
|
1189
|
+
children: isExpanded ? /* @__PURE__ */ jsxRuntime.jsx(ui.IconChevronDown16, {}) : /* @__PURE__ */ jsxRuntime.jsx(ui.IconChevronRight16, {})
|
|
1190
|
+
}
|
|
1191
|
+
),
|
|
1192
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
1193
|
+
"button",
|
|
1194
|
+
{
|
|
1195
|
+
type: "button",
|
|
1196
|
+
className: "flex min-h-11 min-w-0 flex-1 cursor-pointer items-center justify-between gap-dp12 rounded border-none bg-transparent py-dp8 pe-dp8 ps-dp4 text-start font-[inherit] text-inherit hover:bg-white/80 focus-visible:outline-2 focus-visible:outline-dhis2-teal-600 focus-visible:outline-offset-1",
|
|
1197
|
+
"aria-pressed": isSelected,
|
|
1198
|
+
onClick: () => {
|
|
1199
|
+
onSelectEntry(entry.id);
|
|
1200
|
+
},
|
|
1201
|
+
children: [
|
|
1202
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "flex min-w-0 flex-col items-start gap-0.5", children: [
|
|
1203
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm font-semibold leading-[1.3] text-dhis2-grey-900 tabular-nums", children: formatAgo(entry.timestamp) }),
|
|
1204
|
+
!isExpanded && effectCount > 0 ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs leading-[1.3] text-dhis2-grey-600", children: translate("{{count}} effects", {
|
|
1205
|
+
count: effectCount
|
|
1206
|
+
}) }) : null
|
|
1207
|
+
] }),
|
|
1208
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1209
|
+
ui.Tag,
|
|
1210
|
+
{
|
|
1211
|
+
className: isInitial ? "shrink-0" : void 0,
|
|
1212
|
+
neutral: !isInitial,
|
|
1213
|
+
children: isInitial ? translate("Initial") : translate("{{count}} rules", { count: ruleCount })
|
|
1214
|
+
}
|
|
1215
|
+
)
|
|
1216
|
+
]
|
|
1217
|
+
}
|
|
1218
|
+
)
|
|
1219
|
+
] }),
|
|
1220
|
+
isExpanded ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-dp16 border-t border-dhis2-grey-200 p-dp16", children: [
|
|
1221
|
+
isInitial ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "m-0 text-sm leading-normal text-dhis2-grey-600", children: ruleCount ? translate("{{count}} effects on load", {
|
|
1222
|
+
count: effectCount
|
|
1223
|
+
}) : translate("No rules fired") }) : /* @__PURE__ */ jsxRuntime.jsxs("section", { className: "flex flex-col gap-dp8", children: [
|
|
1224
|
+
/* @__PURE__ */ jsxRuntime.jsx("h3", { className: "m-0 text-[0.6875rem] font-bold uppercase leading-[1.3] tracking-wider text-dhis2-grey-600", children: translate("Changed fields") }),
|
|
1225
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-wrap gap-dp8", children: entry.changedFields.map((fieldId) => {
|
|
1226
|
+
const { label, showId } = resolveLabel(
|
|
1227
|
+
fieldId,
|
|
1228
|
+
labelLookup ? (id) => labelLookup.resolveFieldName(id) : void 0
|
|
1229
|
+
);
|
|
1230
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
1231
|
+
"div",
|
|
1232
|
+
{
|
|
1233
|
+
className: "flex max-w-full flex-col gap-0.5",
|
|
1234
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
1235
|
+
ui.Tag,
|
|
1236
|
+
{
|
|
1237
|
+
neutral: true,
|
|
1238
|
+
className: `max-w-full break-words text-xs ${showId ? "" : "font-mono break-all"}`,
|
|
1239
|
+
children: label
|
|
1240
|
+
}
|
|
1241
|
+
)
|
|
1242
|
+
},
|
|
1243
|
+
fieldId
|
|
1244
|
+
);
|
|
1245
|
+
}) })
|
|
1246
|
+
] }),
|
|
1247
|
+
entry.ruleResults.length === 0 ? !isInitial ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "m-0 text-sm leading-normal text-dhis2-grey-600", children: translate("No rules fired") }) : null : /* @__PURE__ */ jsxRuntime.jsxs("section", { className: "flex flex-col gap-dp8", children: [
|
|
1248
|
+
!isInitial ? /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "m-0 text-[0.6875rem] font-bold uppercase leading-[1.3] tracking-wider text-dhis2-grey-600", children: translate("Rules fired") }) : null,
|
|
1249
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-col gap-dp12", children: entry.ruleResults.map((result, index) => {
|
|
1250
|
+
const { label: displayName } = resolveLabel(
|
|
1251
|
+
result.ruleId,
|
|
1252
|
+
labelLookup ? (id) => labelLookup.resolveRuleName(id) : void 0
|
|
1253
|
+
);
|
|
1254
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1255
|
+
"div",
|
|
1256
|
+
{
|
|
1257
|
+
className: "rounded border border-dhis2-grey-200 bg-dhis2-grey-050 p-dp12",
|
|
1258
|
+
children: [
|
|
1259
|
+
index > 0 ? /* @__PURE__ */ jsxRuntime.jsx(ui.Divider, { margin: "12px 0" }) : null,
|
|
1260
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "mb-dp12 flex flex-col items-start gap-dp4", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
1261
|
+
ui.Chip,
|
|
1262
|
+
{
|
|
1263
|
+
selected: highlightRuleId === result.ruleId,
|
|
1264
|
+
onClick: (_, event) => {
|
|
1265
|
+
event.stopPropagation();
|
|
1266
|
+
onHighlightRule(
|
|
1267
|
+
highlightRuleId === result.ruleId ? null : result.ruleId
|
|
1268
|
+
);
|
|
1269
|
+
},
|
|
1270
|
+
children: displayName
|
|
1271
|
+
}
|
|
1272
|
+
) }),
|
|
1273
|
+
/* @__PURE__ */ jsxRuntime.jsx("ul", { className: "m-0 flex list-none flex-col gap-dp12 p-0", children: result.effects.map((effect) => {
|
|
1274
|
+
const {
|
|
1275
|
+
label: targetLabel,
|
|
1276
|
+
showId: showTargetId
|
|
1277
|
+
} = resolveEffectTargetLabel(
|
|
1278
|
+
effect,
|
|
1279
|
+
labelLookup
|
|
1280
|
+
);
|
|
1281
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1282
|
+
"li",
|
|
1283
|
+
{
|
|
1284
|
+
className: "flex flex-col gap-dp4",
|
|
1285
|
+
children: [
|
|
1286
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-wrap items-baseline gap-dp8", children: [
|
|
1287
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1288
|
+
EffectBadge,
|
|
1289
|
+
{
|
|
1290
|
+
type: effect.type
|
|
1291
|
+
}
|
|
1292
|
+
),
|
|
1293
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1294
|
+
"span",
|
|
1295
|
+
{
|
|
1296
|
+
className: `break-words text-[0.8125rem] leading-[1.45] text-dhis2-grey-800 ${showTargetId ? "" : "break-all font-mono"}`,
|
|
1297
|
+
children: targetLabel
|
|
1298
|
+
}
|
|
1299
|
+
)
|
|
1300
|
+
] }),
|
|
1301
|
+
effect.data ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "m-0 ps-dp4 text-[0.8125rem] leading-[1.45] break-words text-dhis2-grey-600", children: effect.data }) : null
|
|
1302
|
+
]
|
|
1303
|
+
},
|
|
1304
|
+
`${effect.type}-${effect.targetId}-${effect.data ?? ""}`
|
|
1305
|
+
);
|
|
1306
|
+
}) })
|
|
1307
|
+
]
|
|
1308
|
+
},
|
|
1309
|
+
result.ruleId
|
|
1310
|
+
);
|
|
1311
|
+
}) })
|
|
1312
|
+
] })
|
|
1313
|
+
] }) : null
|
|
1314
|
+
]
|
|
1315
|
+
}
|
|
1316
|
+
) }, entry.id);
|
|
1317
|
+
}) });
|
|
1318
|
+
}
|
|
1319
|
+
function resolveScopeStageId(metadata) {
|
|
1320
|
+
return metadata.formKind === "event" ? metadata.programStageId : null;
|
|
1321
|
+
}
|
|
1322
|
+
function isRuleInScope(rule, scopeStageId) {
|
|
1323
|
+
return rule.programStageId === null || rule.programStageId === scopeStageId;
|
|
1324
|
+
}
|
|
1325
|
+
function resolveAccentClassName(inScope, firing, selected) {
|
|
1326
|
+
if (selected) {
|
|
1327
|
+
return "bg-dhis2-blue-600";
|
|
1328
|
+
}
|
|
1329
|
+
if (!inScope) {
|
|
1330
|
+
return "bg-transparent";
|
|
1331
|
+
}
|
|
1332
|
+
return firing ? "bg-dhis2-teal-600" : "bg-dhis2-grey-400";
|
|
1333
|
+
}
|
|
1334
|
+
function resolveCardStatus(inScope, firing) {
|
|
1335
|
+
if (!inScope) {
|
|
1336
|
+
return { label: translate("Out of scope"), className: "text-dhis2-grey-500" };
|
|
1337
|
+
}
|
|
1338
|
+
return firing ? { label: translate("Firing"), className: "text-dhis2-teal-700" } : { label: translate("Idle"), className: "text-dhis2-grey-600" };
|
|
1339
|
+
}
|
|
1340
|
+
function formatActionLabel(action) {
|
|
1341
|
+
if (action.targetLabel && action.detail) {
|
|
1342
|
+
return `${action.type} \xB7 ${action.targetLabel} = ${action.detail}`;
|
|
1343
|
+
}
|
|
1344
|
+
if (action.targetLabel) {
|
|
1345
|
+
return `${action.type} \xB7 ${action.targetLabel}`;
|
|
1346
|
+
}
|
|
1347
|
+
return action.type;
|
|
1348
|
+
}
|
|
1349
|
+
function countObservedRules(entries) {
|
|
1350
|
+
const ruleIds = /* @__PURE__ */ new Set();
|
|
1351
|
+
for (const entry of entries) {
|
|
1352
|
+
for (const result of entry.ruleResults) {
|
|
1353
|
+
ruleIds.add(result.ruleId);
|
|
1354
|
+
}
|
|
1355
|
+
}
|
|
1356
|
+
return ruleIds.size;
|
|
1357
|
+
}
|
|
1358
|
+
var PANEL_TAB_KEYS = ["rules", "trace", "graph"];
|
|
1359
|
+
function resolveTabLabel(key) {
|
|
1360
|
+
switch (key) {
|
|
1361
|
+
case "rules":
|
|
1362
|
+
return translate("Rules");
|
|
1363
|
+
case "trace":
|
|
1364
|
+
return translate("Trace");
|
|
1365
|
+
case "graph":
|
|
1366
|
+
return translate("Graph");
|
|
1367
|
+
}
|
|
1368
|
+
}
|
|
1369
|
+
function RulesPanel({ metadata, showConditions = true }) {
|
|
1370
|
+
const formStore = dhis2FormUtilsHooks.useFormStore();
|
|
1371
|
+
const { form } = dhis2FormUtilsHooks.useFormStateContext();
|
|
1372
|
+
const traceStore = useRuleTraceStore();
|
|
1373
|
+
const [tab, setTab] = react.useState("rules");
|
|
1374
|
+
const [selectedEntryId, setSelectedEntryId] = react.useState(null);
|
|
1375
|
+
const [highlightRuleId, setHighlightRuleId] = react.useState(null);
|
|
1376
|
+
const [graphModalOpen, setGraphModalOpen] = react.useState(false);
|
|
1377
|
+
const labelLookup = react.useMemo(() => createLabelLookup(metadata), [metadata]);
|
|
1378
|
+
const catalog = react.useMemo(() => resolveProgramRulesList(metadata), [metadata]);
|
|
1379
|
+
const scopeStageId = react.useMemo(() => resolveScopeStageId(metadata), [metadata]);
|
|
1380
|
+
const entries = react.useSyncExternalStore(
|
|
1381
|
+
react.useCallback((listener) => traceStore.subscribe(listener), [traceStore]),
|
|
1382
|
+
react.useCallback(() => traceStore.getSnapshot(), [traceStore]),
|
|
1383
|
+
react.useCallback(() => traceStore.getSnapshot(), [traceStore])
|
|
1384
|
+
);
|
|
1385
|
+
const fieldState = react.useSyncExternalStore(
|
|
1386
|
+
react.useCallback((listener) => formStore.fieldStore.subscribeAll(listener), [formStore]),
|
|
1387
|
+
react.useCallback(() => formStore.fieldStore.getSnapshot(), [formStore]),
|
|
1388
|
+
react.useCallback(() => formStore.fieldStore.getSnapshot(), [formStore])
|
|
1389
|
+
);
|
|
1390
|
+
const activeRuleIds = react.useMemo(() => getActiveRuleIds(entries), [entries]);
|
|
1391
|
+
const scopedRules = react.useMemo(
|
|
1392
|
+
() => catalog.filter((rule) => isRuleInScope(rule, scopeStageId)),
|
|
1393
|
+
[catalog, scopeStageId]
|
|
1394
|
+
);
|
|
1395
|
+
const firingCount = react.useMemo(
|
|
1396
|
+
() => scopedRules.filter((rule) => activeRuleIds.has(rule.id)).length,
|
|
1397
|
+
[scopedRules, activeRuleIds]
|
|
1398
|
+
);
|
|
1399
|
+
const selectedEntry = react.useMemo(
|
|
1400
|
+
() => entries.find((entry) => entry.id === selectedEntryId) ?? null,
|
|
1401
|
+
[entries, selectedEntryId]
|
|
1402
|
+
);
|
|
1403
|
+
const observedRuleCount = react.useMemo(() => countObservedRules(entries), [entries]);
|
|
1404
|
+
const highlightedRuleName = react.useMemo(() => {
|
|
1405
|
+
if (!highlightRuleId) {
|
|
1406
|
+
return null;
|
|
1407
|
+
}
|
|
1408
|
+
return labelLookup.resolveRuleName(highlightRuleId);
|
|
1409
|
+
}, [highlightRuleId, labelLookup]);
|
|
1410
|
+
const graphHasNodes = react.useMemo(() => {
|
|
1411
|
+
const entry = resolveGraphTraceEntry(entries, selectedEntryId);
|
|
1412
|
+
return entry ? buildGraphFromTrace([entry], labelLookup).nodes.length > 0 : false;
|
|
1413
|
+
}, [entries, labelLookup, selectedEntryId]);
|
|
1414
|
+
const graphSubtitle = react.useMemo(() => {
|
|
1415
|
+
if (selectedEntry && highlightedRuleName) {
|
|
1416
|
+
return translate("Highlighting evaluation {{time}} \xB7 Rule: {{name}}", {
|
|
1417
|
+
time: formatAgo(selectedEntry.timestamp),
|
|
1418
|
+
name: highlightedRuleName
|
|
1419
|
+
});
|
|
1420
|
+
}
|
|
1421
|
+
if (selectedEntry) {
|
|
1422
|
+
return translate("Highlighting evaluation {{time}}", {
|
|
1423
|
+
time: formatAgo(selectedEntry.timestamp)
|
|
1424
|
+
});
|
|
1425
|
+
}
|
|
1426
|
+
if (highlightedRuleName) {
|
|
1427
|
+
return translate("Rule: {{name}}", { name: highlightedRuleName });
|
|
1428
|
+
}
|
|
1429
|
+
return null;
|
|
1430
|
+
}, [highlightedRuleName, selectedEntry]);
|
|
1431
|
+
const graphProps = {
|
|
1432
|
+
entries,
|
|
1433
|
+
fieldState,
|
|
1434
|
+
formValues: form.getValues(),
|
|
1435
|
+
selectedEntryId,
|
|
1436
|
+
highlightRuleId,
|
|
1437
|
+
labelLookup
|
|
1438
|
+
};
|
|
1439
|
+
const expandGraphButton = /* @__PURE__ */ jsxRuntime.jsx(
|
|
1440
|
+
"button",
|
|
1441
|
+
{
|
|
1442
|
+
type: "button",
|
|
1443
|
+
className: "inline-flex size-11 min-h-11 min-w-11 shrink-0 cursor-pointer items-center justify-center rounded border border-dhis2-grey-300 bg-white text-dhis2-grey-800 hover:bg-dhis2-grey-100 focus-visible:outline-2 focus-visible:outline-dhis2-teal-600 focus-visible:outline-offset-1",
|
|
1444
|
+
"aria-label": translate("Open graph in full screen"),
|
|
1445
|
+
onClick: () => {
|
|
1446
|
+
setGraphModalOpen(true);
|
|
1447
|
+
},
|
|
1448
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(ui.IconFullscreen16, {})
|
|
1449
|
+
}
|
|
1450
|
+
);
|
|
1451
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1452
|
+
"aside",
|
|
1453
|
+
{
|
|
1454
|
+
className: "flex h-full w-[404px] min-w-[320px] max-w-[min(92vw,480px)] flex-col border-s border-dhis2-grey-400 bg-dhis2-grey-100",
|
|
1455
|
+
"aria-label": translate("Rules"),
|
|
1456
|
+
children: [
|
|
1457
|
+
/* @__PURE__ */ jsxRuntime.jsxs("header", { className: "shrink-0 border-b border-dhis2-grey-200 bg-white px-dp16 pt-dp12", children: [
|
|
1458
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between gap-dp12 pb-[10px]", children: [
|
|
1459
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-dp8", children: [
|
|
1460
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1461
|
+
"span",
|
|
1462
|
+
{
|
|
1463
|
+
className: "size-2 shrink-0 rounded-full bg-dhis2-teal-600",
|
|
1464
|
+
"aria-hidden": "true"
|
|
1465
|
+
}
|
|
1466
|
+
),
|
|
1467
|
+
/* @__PURE__ */ jsxRuntime.jsx("h2", { className: "m-0 text-base font-bold leading-[1.35] text-dhis2-grey-900", children: translate("Rules") })
|
|
1468
|
+
] }),
|
|
1469
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "shrink-0 text-xs text-dhis2-grey-600", children: translate("{{scoped}} in scope \xB7 {{firing}} firing", {
|
|
1470
|
+
scoped: scopedRules.length,
|
|
1471
|
+
firing: firingCount
|
|
1472
|
+
}) })
|
|
1473
|
+
] }),
|
|
1474
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex", children: PANEL_TAB_KEYS.map((key) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
1475
|
+
"button",
|
|
1476
|
+
{
|
|
1477
|
+
type: "button",
|
|
1478
|
+
className: `cursor-pointer border-0 border-b-[3px] bg-transparent px-dp12 py-dp8 text-sm font-medium ${tab === key ? "border-b-dhis2-blue-600 text-dhis2-blue-600" : "border-b-transparent text-dhis2-grey-700"}`,
|
|
1479
|
+
onClick: () => {
|
|
1480
|
+
setTab(key);
|
|
1481
|
+
},
|
|
1482
|
+
children: resolveTabLabel(key)
|
|
1483
|
+
},
|
|
1484
|
+
key
|
|
1485
|
+
)) })
|
|
1486
|
+
] }),
|
|
1487
|
+
tab !== "rules" && (selectedEntry || highlightedRuleName) ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mx-dp16 mt-dp12 flex shrink-0 items-center justify-between gap-dp12 rounded border border-dhis2-teal-400 bg-dhis2-teal-050 p-dp12 text-[0.8125rem] leading-[1.45] text-dhis2-grey-900", children: [
|
|
1488
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "min-w-0", children: [
|
|
1489
|
+
selectedEntry ? translate("Highlighting evaluation {{time}}", {
|
|
1490
|
+
time: formatAgo(selectedEntry.timestamp)
|
|
1491
|
+
}) : null,
|
|
1492
|
+
selectedEntry && highlightedRuleName ? " \xB7 " : null,
|
|
1493
|
+
highlightedRuleName ? translate("Rule: {{name}}", { name: highlightedRuleName }) : null
|
|
1494
|
+
] }),
|
|
1495
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1496
|
+
ui.Button,
|
|
1497
|
+
{
|
|
1498
|
+
small: true,
|
|
1499
|
+
onClick: () => {
|
|
1500
|
+
setSelectedEntryId(null);
|
|
1501
|
+
setHighlightRuleId(null);
|
|
1502
|
+
},
|
|
1503
|
+
children: translate("Clear")
|
|
1504
|
+
}
|
|
1505
|
+
)
|
|
1506
|
+
] }) : null,
|
|
1507
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "min-h-0 flex-1 overflow-auto p-dp16", children: tab === "rules" ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
1508
|
+
RulesTab,
|
|
1509
|
+
{
|
|
1510
|
+
catalog,
|
|
1511
|
+
scopeStageId,
|
|
1512
|
+
activeRuleIds,
|
|
1513
|
+
selectedRuleId: highlightRuleId,
|
|
1514
|
+
showConditions,
|
|
1515
|
+
labelLookup,
|
|
1516
|
+
onSelectRule: (ruleId) => {
|
|
1517
|
+
setHighlightRuleId(ruleId);
|
|
1518
|
+
setTab("graph");
|
|
1519
|
+
}
|
|
1520
|
+
}
|
|
1521
|
+
) : tab === "trace" ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
1522
|
+
TraceTimeline,
|
|
1523
|
+
{
|
|
1524
|
+
entries,
|
|
1525
|
+
selectedEntryId,
|
|
1526
|
+
highlightRuleId,
|
|
1527
|
+
onSelectEntry: (entryId) => {
|
|
1528
|
+
setSelectedEntryId((current) => current === entryId ? null : entryId);
|
|
1529
|
+
},
|
|
1530
|
+
onHighlightRule: (ruleId) => {
|
|
1531
|
+
setHighlightRuleId(ruleId);
|
|
1532
|
+
if (ruleId) {
|
|
1533
|
+
setTab("graph");
|
|
1534
|
+
}
|
|
1535
|
+
},
|
|
1536
|
+
labelLookup
|
|
1537
|
+
}
|
|
1538
|
+
) : /* @__PURE__ */ jsxRuntime.jsx(
|
|
1539
|
+
RuleGraphView,
|
|
1540
|
+
{
|
|
1541
|
+
...graphProps,
|
|
1542
|
+
headerActions: graphHasNodes ? expandGraphButton : void 0
|
|
1543
|
+
}
|
|
1544
|
+
) }),
|
|
1545
|
+
entries.length > 0 && tab !== "rules" ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "sr-only", "aria-live": "polite", children: translate("{{evaluations}} evaluations \xB7 {{rules}} rules observed", {
|
|
1546
|
+
evaluations: entries.length,
|
|
1547
|
+
rules: observedRuleCount
|
|
1548
|
+
}) }) : null,
|
|
1549
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1550
|
+
RuleGraphModal,
|
|
1551
|
+
{
|
|
1552
|
+
...graphProps,
|
|
1553
|
+
open: graphModalOpen,
|
|
1554
|
+
onClose: () => {
|
|
1555
|
+
setGraphModalOpen(false);
|
|
1556
|
+
},
|
|
1557
|
+
subtitle: graphSubtitle,
|
|
1558
|
+
layoutKey: graphModalOpen ? "open" : "closed"
|
|
1559
|
+
}
|
|
1560
|
+
)
|
|
1561
|
+
]
|
|
1562
|
+
}
|
|
1563
|
+
);
|
|
1564
|
+
}
|
|
1565
|
+
function RulesTab({
|
|
1566
|
+
catalog,
|
|
1567
|
+
scopeStageId,
|
|
1568
|
+
activeRuleIds,
|
|
1569
|
+
selectedRuleId,
|
|
1570
|
+
showConditions,
|
|
1571
|
+
labelLookup,
|
|
1572
|
+
onSelectRule
|
|
1573
|
+
}) {
|
|
1574
|
+
if (!catalog.length) {
|
|
1575
|
+
return /* @__PURE__ */ jsxRuntime.jsx("p", { className: "m-0 text-sm leading-normal text-dhis2-grey-600", children: translate("This program has no rules.") });
|
|
1576
|
+
}
|
|
1577
|
+
return /* @__PURE__ */ jsxRuntime.jsx("ul", { className: "m-0 flex list-none flex-col gap-[10px] p-0", children: catalog.map((rule) => {
|
|
1578
|
+
const inScope = isRuleInScope(rule, scopeStageId);
|
|
1579
|
+
const firing = activeRuleIds.has(rule.id);
|
|
1580
|
+
const isSelected = selectedRuleId === rule.id;
|
|
1581
|
+
const status = resolveCardStatus(inScope, firing);
|
|
1582
|
+
return /* @__PURE__ */ jsxRuntime.jsx("li", { className: "m-0 shrink-0", children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1583
|
+
"article",
|
|
1584
|
+
{
|
|
1585
|
+
role: "button",
|
|
1586
|
+
tabIndex: 0,
|
|
1587
|
+
onClick: () => {
|
|
1588
|
+
onSelectRule(rule.id);
|
|
1589
|
+
},
|
|
1590
|
+
onKeyDown: (event) => {
|
|
1591
|
+
if (event.key === "Enter" || event.key === " ") {
|
|
1592
|
+
event.preventDefault();
|
|
1593
|
+
onSelectRule(rule.id);
|
|
1594
|
+
}
|
|
1595
|
+
},
|
|
1596
|
+
className: "relative flex cursor-pointer flex-col gap-dp8 rounded-[5px] border border-dhis2-grey-300 bg-white py-[11px] pe-[12px] ps-[14px] shadow-[0_1px_2px_rgb(0_0_0/4%)]",
|
|
1597
|
+
children: [
|
|
1598
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1599
|
+
"span",
|
|
1600
|
+
{
|
|
1601
|
+
className: `absolute inset-y-0 start-0 w-[3px] rounded-s-[5px] ${resolveAccentClassName(inScope, firing, isSelected)}`,
|
|
1602
|
+
"aria-hidden": "true"
|
|
1603
|
+
}
|
|
1604
|
+
),
|
|
1605
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-start justify-between gap-dp8", children: [
|
|
1606
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1607
|
+
"h3",
|
|
1608
|
+
{
|
|
1609
|
+
className: `m-0 min-w-0 flex-1 text-sm font-semibold leading-[1.4] ${inScope ? "text-dhis2-grey-900" : "text-dhis2-grey-600"}`,
|
|
1610
|
+
children: rule.name
|
|
1611
|
+
}
|
|
1612
|
+
),
|
|
1613
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1614
|
+
"span",
|
|
1615
|
+
{
|
|
1616
|
+
className: `shrink-0 text-[11px] font-semibold ${status.className}`,
|
|
1617
|
+
children: status.label
|
|
1618
|
+
}
|
|
1619
|
+
)
|
|
1620
|
+
] }),
|
|
1621
|
+
rule.programRuleActions.length ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-wrap gap-[6px]", children: rule.programRuleActions.map((action, index) => {
|
|
1622
|
+
const summary = formatRuleActionSummary(
|
|
1623
|
+
action,
|
|
1624
|
+
labelLookup
|
|
1625
|
+
);
|
|
1626
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
1627
|
+
EffectBadge,
|
|
1628
|
+
{
|
|
1629
|
+
type: summary.type,
|
|
1630
|
+
children: formatActionLabel(summary)
|
|
1631
|
+
},
|
|
1632
|
+
`${rule.id}-action-${String(index)}`
|
|
1633
|
+
);
|
|
1634
|
+
}) }) : null,
|
|
1635
|
+
showConditions && rule.condition ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
1636
|
+
"p",
|
|
1637
|
+
{
|
|
1638
|
+
className: "m-0 break-words font-mono text-[11px] leading-[1.5] text-dhis2-grey-700",
|
|
1639
|
+
title: rule.condition,
|
|
1640
|
+
children: rule.condition
|
|
1641
|
+
}
|
|
1642
|
+
) : null,
|
|
1643
|
+
!inScope && rule.programStageId ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[11px] text-dhis2-grey-600", children: translate("Applies to {{stage}}", {
|
|
1644
|
+
stage: labelLookup.resolveStageName(rule.programStageId)
|
|
1645
|
+
}) }) : null
|
|
1646
|
+
]
|
|
1647
|
+
}
|
|
1648
|
+
) }, rule.id);
|
|
1649
|
+
}) });
|
|
1650
|
+
}
|
|
1651
|
+
|
|
1652
|
+
exports.EFFECT_ICONS = EFFECT_ICONS;
|
|
1653
|
+
exports.RuleDevtoolsScope = RuleDevtoolsScope;
|
|
1654
|
+
exports.RulesPanel = RulesPanel;
|
|
1655
|
+
exports.createLabelLookup = createLabelLookup;
|
|
1656
|
+
exports.getEffectEdgeStroke = getEffectEdgeStroke;
|
|
1657
|
+
exports.getEffectShortLabel = getEffectShortLabel;
|
|
1658
|
+
exports.getEffectTagRenderProps = getEffectTagRenderProps;
|
|
1659
|
+
exports.getEffectTagRenderPropsForVariant = getEffectTagRenderPropsForVariant;
|
|
1660
|
+
exports.getEffectVariant = getEffectVariant;
|
|
1661
|
+
exports.getEffectVisual = getEffectVisual;
|
|
1662
|
+
//# sourceMappingURL=index.cjs.map
|
|
1663
|
+
//# sourceMappingURL=index.cjs.map
|