@miragon/pulse-analytics-connector 0.0.0-bootstrap.0 → 0.21.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.
Files changed (357) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +86 -3
  3. package/dist/bpmn-name-lookup.d.ts +72 -0
  4. package/dist/bpmn-name-lookup.d.ts.map +1 -0
  5. package/dist/bpmn-name-lookup.js +123 -0
  6. package/dist/bpmn-name-lookup.js.map +1 -0
  7. package/dist/bpmn-name-lookup.test.d.ts +2 -0
  8. package/dist/bpmn-name-lookup.test.d.ts.map +1 -0
  9. package/dist/bpmn-name-lookup.test.js +287 -0
  10. package/dist/bpmn-name-lookup.test.js.map +1 -0
  11. package/dist/bpmn-names.d.ts +38 -0
  12. package/dist/bpmn-names.d.ts.map +1 -0
  13. package/dist/bpmn-names.js +22 -0
  14. package/dist/bpmn-names.js.map +1 -0
  15. package/dist/brand-gates.test.d.ts +2 -0
  16. package/dist/brand-gates.test.d.ts.map +1 -0
  17. package/dist/brand-gates.test.js +58 -0
  18. package/dist/brand-gates.test.js.map +1 -0
  19. package/dist/builder-texts.d.ts +4 -0
  20. package/dist/builder-texts.d.ts.map +1 -0
  21. package/dist/builder-texts.js +14 -0
  22. package/dist/builder-texts.js.map +1 -0
  23. package/dist/builder-texts.test.d.ts +2 -0
  24. package/dist/builder-texts.test.d.ts.map +1 -0
  25. package/dist/builder-texts.test.js +32 -0
  26. package/dist/builder-texts.test.js.map +1 -0
  27. package/dist/definition.d.ts +3 -0
  28. package/dist/definition.d.ts.map +1 -0
  29. package/dist/definition.js +151 -0
  30. package/dist/definition.js.map +1 -0
  31. package/dist/engine-ids.d.ts +42 -0
  32. package/dist/engine-ids.d.ts.map +1 -0
  33. package/dist/engine-ids.js +50 -0
  34. package/dist/engine-ids.js.map +1 -0
  35. package/dist/engine-scope.test.d.ts +2 -0
  36. package/dist/engine-scope.test.d.ts.map +1 -0
  37. package/dist/engine-scope.test.js +260 -0
  38. package/dist/engine-scope.test.js.map +1 -0
  39. package/dist/heatmap-data.d.ts +15 -0
  40. package/dist/heatmap-data.d.ts.map +1 -0
  41. package/dist/heatmap-data.js +2 -0
  42. package/dist/heatmap-data.js.map +1 -0
  43. package/dist/index.d.ts +5 -0
  44. package/dist/index.d.ts.map +1 -0
  45. package/dist/index.js +3 -0
  46. package/dist/index.js.map +1 -0
  47. package/dist/instructions.d.ts +8 -0
  48. package/dist/instructions.d.ts.map +1 -0
  49. package/dist/instructions.js +33 -0
  50. package/dist/instructions.js.map +1 -0
  51. package/dist/messages/de.ask-ai.d.ts +9 -0
  52. package/dist/messages/de.ask-ai.d.ts.map +1 -0
  53. package/dist/messages/de.ask-ai.js +18 -0
  54. package/dist/messages/de.ask-ai.js.map +1 -0
  55. package/dist/messages/de.builder.d.ts +12 -0
  56. package/dist/messages/de.builder.d.ts.map +1 -0
  57. package/dist/messages/de.builder.js +53 -0
  58. package/dist/messages/de.builder.js.map +1 -0
  59. package/dist/messages/de.server.d.ts +8 -0
  60. package/dist/messages/de.server.d.ts.map +1 -0
  61. package/dist/messages/de.server.js +26 -0
  62. package/dist/messages/de.server.js.map +1 -0
  63. package/dist/messages/de.sweep.d.ts +10 -0
  64. package/dist/messages/de.sweep.d.ts.map +1 -0
  65. package/dist/messages/de.sweep.js +203 -0
  66. package/dist/messages/de.sweep.js.map +1 -0
  67. package/dist/messages/en.ask-ai.d.ts +25 -0
  68. package/dist/messages/en.ask-ai.d.ts.map +1 -0
  69. package/dist/messages/en.ask-ai.js +24 -0
  70. package/dist/messages/en.ask-ai.js.map +1 -0
  71. package/dist/messages/en.builder.d.ts +12 -0
  72. package/dist/messages/en.builder.d.ts.map +1 -0
  73. package/dist/messages/en.builder.js +53 -0
  74. package/dist/messages/en.builder.js.map +1 -0
  75. package/dist/messages/en.server.d.ts +8 -0
  76. package/dist/messages/en.server.d.ts.map +1 -0
  77. package/dist/messages/en.server.js +26 -0
  78. package/dist/messages/en.server.js.map +1 -0
  79. package/dist/messages/en.sweep.d.ts +11 -0
  80. package/dist/messages/en.sweep.d.ts.map +1 -0
  81. package/dist/messages/en.sweep.js +204 -0
  82. package/dist/messages/en.sweep.js.map +1 -0
  83. package/dist/messages/glossary.d.ts +26 -0
  84. package/dist/messages/glossary.d.ts.map +1 -0
  85. package/dist/messages/glossary.js +37 -0
  86. package/dist/messages/glossary.js.map +1 -0
  87. package/dist/messages/glossary.test.d.ts +2 -0
  88. package/dist/messages/glossary.test.d.ts.map +1 -0
  89. package/dist/messages/glossary.test.js +109 -0
  90. package/dist/messages/glossary.test.js.map +1 -0
  91. package/dist/messages/index.d.ts +12 -0
  92. package/dist/messages/index.d.ts.map +1 -0
  93. package/dist/messages/index.js +23 -0
  94. package/dist/messages/index.js.map +1 -0
  95. package/dist/messages/plural.d.ts +10 -0
  96. package/dist/messages/plural.d.ts.map +1 -0
  97. package/dist/messages/plural.js +17 -0
  98. package/dist/messages/plural.js.map +1 -0
  99. package/dist/messages/use-t.d.ts +10 -0
  100. package/dist/messages/use-t.d.ts.map +1 -0
  101. package/dist/messages/use-t.js +10 -0
  102. package/dist/messages/use-t.js.map +1 -0
  103. package/dist/module.d.ts +48 -0
  104. package/dist/module.d.ts.map +1 -0
  105. package/dist/module.js +174 -0
  106. package/dist/module.js.map +1 -0
  107. package/dist/module.test.d.ts +2 -0
  108. package/dist/module.test.d.ts.map +1 -0
  109. package/dist/module.test.js +230 -0
  110. package/dist/module.test.js.map +1 -0
  111. package/dist/named-views.d.ts +27 -0
  112. package/dist/named-views.d.ts.map +1 -0
  113. package/dist/named-views.js +73 -0
  114. package/dist/named-views.js.map +1 -0
  115. package/dist/named-views.test.d.ts +2 -0
  116. package/dist/named-views.test.d.ts.map +1 -0
  117. package/dist/named-views.test.js +110 -0
  118. package/dist/named-views.test.js.map +1 -0
  119. package/dist/plugin.d.ts +44 -0
  120. package/dist/plugin.d.ts.map +1 -0
  121. package/dist/plugin.js +52 -0
  122. package/dist/plugin.js.map +1 -0
  123. package/dist/prometheus-wiring.test.d.ts +2 -0
  124. package/dist/prometheus-wiring.test.d.ts.map +1 -0
  125. package/dist/prometheus-wiring.test.js +142 -0
  126. package/dist/prometheus-wiring.test.js.map +1 -0
  127. package/dist/server-locale.d.ts +48 -0
  128. package/dist/server-locale.d.ts.map +1 -0
  129. package/dist/server-locale.js +33 -0
  130. package/dist/server-locale.js.map +1 -0
  131. package/dist/settings-tools.d.ts +23 -0
  132. package/dist/settings-tools.d.ts.map +1 -0
  133. package/dist/settings-tools.js +100 -0
  134. package/dist/settings-tools.js.map +1 -0
  135. package/dist/settings.d.ts +77 -0
  136. package/dist/settings.d.ts.map +1 -0
  137. package/dist/settings.js +99 -0
  138. package/dist/settings.js.map +1 -0
  139. package/dist/settings.test.d.ts +2 -0
  140. package/dist/settings.test.d.ts.map +1 -0
  141. package/dist/settings.test.js +299 -0
  142. package/dist/settings.test.js.map +1 -0
  143. package/dist/steps/app-config.d.ts +30 -0
  144. package/dist/steps/app-config.d.ts.map +1 -0
  145. package/dist/steps/app-config.js +46 -0
  146. package/dist/steps/app-config.js.map +1 -0
  147. package/dist/steps/dashboard.d.ts +12 -0
  148. package/dist/steps/dashboard.d.ts.map +1 -0
  149. package/dist/steps/dashboard.js +58 -0
  150. package/dist/steps/dashboard.js.map +1 -0
  151. package/dist/steps/failure-dashboard.d.ts +10 -0
  152. package/dist/steps/failure-dashboard.d.ts.map +1 -0
  153. package/dist/steps/failure-dashboard.js +32 -0
  154. package/dist/steps/failure-dashboard.js.map +1 -0
  155. package/dist/steps/index.d.ts +3 -0
  156. package/dist/steps/index.d.ts.map +1 -0
  157. package/dist/steps/index.js +3 -0
  158. package/dist/steps/index.js.map +1 -0
  159. package/dist/steps/steps.test.d.ts +2 -0
  160. package/dist/steps/steps.test.d.ts.map +1 -0
  161. package/dist/steps/steps.test.js +153 -0
  162. package/dist/steps/steps.test.js.map +1 -0
  163. package/dist/tool-names.d.ts +13 -0
  164. package/dist/tool-names.d.ts.map +1 -0
  165. package/dist/tool-names.js +27 -0
  166. package/dist/tool-names.js.map +1 -0
  167. package/dist/tools/cluster-compare.d.ts +8 -0
  168. package/dist/tools/cluster-compare.d.ts.map +1 -0
  169. package/dist/tools/cluster-compare.js +17 -0
  170. package/dist/tools/cluster-compare.js.map +1 -0
  171. package/dist/tools/element.d.ts +8 -0
  172. package/dist/tools/element.d.ts.map +1 -0
  173. package/dist/tools/element.js +25 -0
  174. package/dist/tools/element.js.map +1 -0
  175. package/dist/tools/engine-compare.d.ts +8 -0
  176. package/dist/tools/engine-compare.d.ts.map +1 -0
  177. package/dist/tools/engine-compare.js +18 -0
  178. package/dist/tools/engine-compare.js.map +1 -0
  179. package/dist/tools/engine-compare.test.d.ts +2 -0
  180. package/dist/tools/engine-compare.test.d.ts.map +1 -0
  181. package/dist/tools/engine-compare.test.js +166 -0
  182. package/dist/tools/engine-compare.test.js.map +1 -0
  183. package/dist/tools/engine-landscape.d.ts +7 -0
  184. package/dist/tools/engine-landscape.d.ts.map +1 -0
  185. package/dist/tools/engine-landscape.js +13 -0
  186. package/dist/tools/engine-landscape.js.map +1 -0
  187. package/dist/tools/engine-landscape.test.d.ts +2 -0
  188. package/dist/tools/engine-landscape.test.d.ts.map +1 -0
  189. package/dist/tools/engine-landscape.test.js +110 -0
  190. package/dist/tools/engine-landscape.test.js.map +1 -0
  191. package/dist/tools/failures.d.ts +7 -0
  192. package/dist/tools/failures.d.ts.map +1 -0
  193. package/dist/tools/failures.js +13 -0
  194. package/dist/tools/failures.js.map +1 -0
  195. package/dist/tools/health.d.ts +7 -0
  196. package/dist/tools/health.d.ts.map +1 -0
  197. package/dist/tools/health.js +23 -0
  198. package/dist/tools/health.js.map +1 -0
  199. package/dist/tools/health.test.d.ts +2 -0
  200. package/dist/tools/health.test.d.ts.map +1 -0
  201. package/dist/tools/health.test.js +119 -0
  202. package/dist/tools/health.test.js.map +1 -0
  203. package/dist/tools/index.d.ts +18 -0
  204. package/dist/tools/index.d.ts.map +1 -0
  205. package/dist/tools/index.js +35 -0
  206. package/dist/tools/index.js.map +1 -0
  207. package/dist/tools/performance.d.ts +8 -0
  208. package/dist/tools/performance.d.ts.map +1 -0
  209. package/dist/tools/performance.js +25 -0
  210. package/dist/tools/performance.js.map +1 -0
  211. package/dist/tools/performance.test.d.ts +2 -0
  212. package/dist/tools/performance.test.d.ts.map +1 -0
  213. package/dist/tools/performance.test.js +176 -0
  214. package/dist/tools/performance.test.js.map +1 -0
  215. package/dist/tools/version-compare.d.ts +8 -0
  216. package/dist/tools/version-compare.d.ts.map +1 -0
  217. package/dist/tools/version-compare.js +17 -0
  218. package/dist/tools/version-compare.js.map +1 -0
  219. package/dist/tools/version-compare.test.d.ts +2 -0
  220. package/dist/tools/version-compare.test.d.ts.map +1 -0
  221. package/dist/tools/version-compare.test.js +82 -0
  222. package/dist/tools/version-compare.test.js.map +1 -0
  223. package/dist/toolsets.d.ts +17 -0
  224. package/dist/toolsets.d.ts.map +1 -0
  225. package/dist/toolsets.js +47 -0
  226. package/dist/toolsets.js.map +1 -0
  227. package/dist/toolsets.test.d.ts +2 -0
  228. package/dist/toolsets.test.d.ts.map +1 -0
  229. package/dist/toolsets.test.js +91 -0
  230. package/dist/toolsets.test.js.map +1 -0
  231. package/dist/view-title.test.d.ts +2 -0
  232. package/dist/view-title.test.d.ts.map +1 -0
  233. package/dist/view-title.test.js +91 -0
  234. package/dist/view-title.test.js.map +1 -0
  235. package/dist/widget-tools/comparisons.d.ts +13 -0
  236. package/dist/widget-tools/comparisons.d.ts.map +1 -0
  237. package/dist/widget-tools/comparisons.js +177 -0
  238. package/dist/widget-tools/comparisons.js.map +1 -0
  239. package/dist/widget-tools/comparisons.test.d.ts +2 -0
  240. package/dist/widget-tools/comparisons.test.d.ts.map +1 -0
  241. package/dist/widget-tools/comparisons.test.js +123 -0
  242. package/dist/widget-tools/comparisons.test.js.map +1 -0
  243. package/dist/widget-tools/shared.d.ts +36 -0
  244. package/dist/widget-tools/shared.d.ts.map +1 -0
  245. package/dist/widget-tools/shared.js +39 -0
  246. package/dist/widget-tools/shared.js.map +1 -0
  247. package/dist/widget-tools.d.ts +25 -0
  248. package/dist/widget-tools.d.ts.map +1 -0
  249. package/dist/widget-tools.js +269 -0
  250. package/dist/widget-tools.js.map +1 -0
  251. package/dist/widget-tools.test.d.ts +2 -0
  252. package/dist/widget-tools.test.d.ts.map +1 -0
  253. package/dist/widget-tools.test.js +272 -0
  254. package/dist/widget-tools.test.js.map +1 -0
  255. package/package.json +91 -5
  256. package/src/bpmn-name-lookup.test.ts +333 -0
  257. package/src/bpmn-name-lookup.ts +197 -0
  258. package/src/bpmn-names.ts +51 -0
  259. package/src/brand-gates.test.ts +69 -0
  260. package/src/builder-texts.test.ts +40 -0
  261. package/src/builder-texts.ts +15 -0
  262. package/src/definition.ts +181 -0
  263. package/src/engine-ids.ts +85 -0
  264. package/src/engine-scope.test.ts +324 -0
  265. package/src/heatmap-data.ts +14 -0
  266. package/src/index.ts +4 -0
  267. package/src/instructions.ts +33 -0
  268. package/src/messages/GLOSSARY.md +88 -0
  269. package/src/messages/de.ask-ai.ts +28 -0
  270. package/src/messages/de.builder.ts +77 -0
  271. package/src/messages/de.server.ts +39 -0
  272. package/src/messages/de.sweep.ts +244 -0
  273. package/src/messages/en.ask-ai.ts +34 -0
  274. package/src/messages/en.builder.ts +76 -0
  275. package/src/messages/en.server.ts +39 -0
  276. package/src/messages/en.sweep.ts +240 -0
  277. package/src/messages/glossary.test.ts +122 -0
  278. package/src/messages/glossary.ts +62 -0
  279. package/src/messages/index.ts +24 -0
  280. package/src/messages/plural.ts +18 -0
  281. package/src/messages/use-t.ts +12 -0
  282. package/src/module.test.ts +319 -0
  283. package/src/module.ts +211 -0
  284. package/src/named-views.test.ts +141 -0
  285. package/src/named-views.ts +97 -0
  286. package/src/plugin.ts +94 -0
  287. package/src/prometheus-wiring.test.ts +174 -0
  288. package/src/server-locale.ts +59 -0
  289. package/src/settings-tools.ts +170 -0
  290. package/src/settings.test.ts +402 -0
  291. package/src/settings.ts +114 -0
  292. package/src/steps/app-config.ts +75 -0
  293. package/src/steps/dashboard.ts +73 -0
  294. package/src/steps/failure-dashboard.ts +38 -0
  295. package/src/steps/index.ts +2 -0
  296. package/src/steps/steps.test.ts +216 -0
  297. package/src/tool-names.ts +26 -0
  298. package/src/tools/cluster-compare.ts +28 -0
  299. package/src/tools/element.ts +35 -0
  300. package/src/tools/engine-compare.test.ts +210 -0
  301. package/src/tools/engine-compare.ts +30 -0
  302. package/src/tools/engine-landscape.test.ts +138 -0
  303. package/src/tools/engine-landscape.ts +21 -0
  304. package/src/tools/failures.ts +19 -0
  305. package/src/tools/health.test.ts +156 -0
  306. package/src/tools/health.ts +29 -0
  307. package/src/tools/index.ts +44 -0
  308. package/src/tools/performance.test.ts +210 -0
  309. package/src/tools/performance.ts +38 -0
  310. package/src/tools/version-compare.test.ts +97 -0
  311. package/src/tools/version-compare.ts +28 -0
  312. package/src/toolsets.test.ts +107 -0
  313. package/src/toolsets.ts +56 -0
  314. package/src/view-title.test.ts +123 -0
  315. package/src/widget-tools/comparisons.test.ts +170 -0
  316. package/src/widget-tools/comparisons.ts +229 -0
  317. package/src/widget-tools/shared.ts +67 -0
  318. package/src/widget-tools.test.ts +306 -0
  319. package/src/widget-tools.ts +375 -0
  320. package/src/widgets/analytics-dashboard/activity-bottleneck-table.tsx +179 -0
  321. package/src/widgets/analytics-dashboard/definition-breakdown.tsx +108 -0
  322. package/src/widgets/analytics-dashboard/execution-performance-kpi.tsx +94 -0
  323. package/src/widgets/analytics-dashboard/execution-summary-kpi.tsx +122 -0
  324. package/src/widgets/analytics-dashboard/lib.ts +43 -0
  325. package/src/widgets/bpmn-heatmap.test.tsx +75 -0
  326. package/src/widgets/bpmn-heatmap.tsx +127 -0
  327. package/src/widgets/catalogue-sync.test.ts +16 -0
  328. package/src/widgets/cluster-compare.tsx +148 -0
  329. package/src/widgets/comparison-shared.test.ts +237 -0
  330. package/src/widgets/comparison-shared.tsx +371 -0
  331. package/src/widgets/comparison-widgets.test.tsx +307 -0
  332. package/src/widgets/dashboard-widgets.test.tsx +397 -0
  333. package/src/widgets/engine-compare.tsx +87 -0
  334. package/src/widgets/engine-landscape.tsx +352 -0
  335. package/src/widgets/failure-dashboard/error-patterns-table.tsx +122 -0
  336. package/src/widgets/failure-dashboard/failure-rate-table.test.ts +80 -0
  337. package/src/widgets/failure-dashboard/failure-rate-table.tsx +170 -0
  338. package/src/widgets/failure-dashboard/lib.ts +32 -0
  339. package/src/widgets/failure-dashboard/summary-kpi.tsx +122 -0
  340. package/src/widgets/failure-landscape-widgets.test.tsx +393 -0
  341. package/src/widgets/figure.tsx +79 -0
  342. package/src/widgets/hand-off.test.ts +126 -0
  343. package/src/widgets/hand-off.ts +102 -0
  344. package/src/widgets/index.ts +124 -0
  345. package/src/widgets/model-descriptions.dashboard.test.ts +280 -0
  346. package/src/widgets/model-descriptions.test.ts +251 -0
  347. package/src/widgets/model-descriptions.ts +428 -0
  348. package/src/widgets/names.test.ts +32 -0
  349. package/src/widgets/names.ts +39 -0
  350. package/src/widgets/query-gate.test.tsx +57 -0
  351. package/src/widgets/query-gate.tsx +50 -0
  352. package/src/widgets/settings-section.test.tsx +174 -0
  353. package/src/widgets/settings-section.tsx +244 -0
  354. package/src/widgets/version-compare.test.ts +68 -0
  355. package/src/widgets/version-compare.tsx +96 -0
  356. package/src/widgets/view-meta.ts +18 -0
  357. package/src/widgets/widget-render.test-support.tsx +103 -0
@@ -0,0 +1,375 @@
1
+ import type { MCPServer } from "mcp-use"
2
+ import {
3
+ appOnly,
4
+ buildComposedView,
5
+ buildDataFeedResult,
6
+ buildSingleWidgetView,
7
+ showToolBinding,
8
+ withToolErrors,
9
+ strictToolInput,
10
+ } from "@miragon/pulse-widget-shell/server"
11
+ import {
12
+ queries,
13
+ schemas,
14
+ withCallerSignal,
15
+ type AnalyticsDashboardData,
16
+ type PrometheusClient,
17
+ } from "@miragon/pulse-analytics-client"
18
+ import {
19
+ ANALYTICS_BPMN_HEATMAP_DATA,
20
+ ANALYTICS_BPMN_XML_DATA,
21
+ ANALYTICS_DASHBOARD_DATA,
22
+ ANALYTICS_FAILURE_DASHBOARD_DATA,
23
+ } from "./tool-names.js"
24
+ import { localizeViewFor, type ProfileSource, type ServerT } from "./server-locale.js"
25
+ import { optionalPeriod, settingsFor } from "./settings.js"
26
+ import { registerComparisonWidgetTools } from "./widget-tools/comparisons.js"
27
+ import { engineScopeSummary } from "./widget-tools/shared.js"
28
+ import { isFleetRequest, withEngineScope, type AnalyticsEngineScope } from "./engine-ids.js"
29
+ import type { BpmnMissingReason } from "./heatmap-data.js"
30
+ import { NO_NAMES, type FetchBpmnXml, type NameLookup } from "./bpmn-name-lookup.js"
31
+ import { namedDashboard, namedFailureDashboard } from "./named-views.js"
32
+
33
+ export type { FetchBpmnXml } from "./bpmn-name-lookup.js"
34
+
35
+ export interface AnalyticsWidgetToolsOptions {
36
+ /** The server's configured engine ids — every query resolves `engine` through it. */
37
+ engineScope: AnalyticsEngineScope
38
+ /** Used by the BPMN heatmap to fetch the diagram XML. Absent → non-diagram fallback. */
39
+ fetchBpmnXml?: FetchBpmnXml
40
+ /**
41
+ * The process and element names behind the ids the dashboards list
42
+ * (`createNameLookup` over the same BPMN lookup). Absent → the ids.
43
+ */
44
+ namesFor?: NameLookup
45
+ /**
46
+ * Profile store: locale for model-facing summaries plus the caller's saved
47
+ * analytics defaults (`modules.analytics`) — the "explicit arg > saved
48
+ * setting > schema default" resolution for `period`/`minBucketSize`.
49
+ */
50
+ profileStore?: ProfileSource
51
+ }
52
+
53
+ /**
54
+ * Heatmap inputs shared by `analytics_show_bpmn_heatmap` and its
55
+ * `analytics_bpmn_heatmap_data` feed, composed from the exported client
56
+ * schemas so the describe() texts stay in one place.
57
+ */
58
+ const heatmapInputShape = {
59
+ processDefinitionKey: schemas.elementBottleneckInput.shape.processDefinitionKey,
60
+ period: optionalPeriod,
61
+ ...schemas.engineFilterShape,
62
+ }
63
+
64
+ /** Dashboard + its feed: one input shape, so the self-fetch can carry every scope the show tool takes. */
65
+ const dashboardInputShape = {
66
+ processDefinitionKey: schemas.clusterCompareInput.shape.processDefinitionKey,
67
+ period: optionalPeriod,
68
+ ...schemas.engineFilterShape,
69
+ }
70
+
71
+ /** "n/a" for a figure that was not measured — never a plausible 0. */
72
+ const orNa = (value: number | null) => value ?? "n/a"
73
+
74
+ /** The model-facing dashboard summary: window flows and the live state, never mixed. */
75
+ function dashboardSummary(t: ServerT, data: AnalyticsDashboardData, fleet: boolean): string {
76
+ return t("aSum.dashboard", {
77
+ scope: data.processDefinitionKey
78
+ ? t("aSum.scopeForProcess", { key: data.processDefinitionKey })
79
+ : "",
80
+ period: data.period,
81
+ engines: engineScopeSummary(t, data.engines, fleet),
82
+ totalCount: data.totalCount,
83
+ completedCount: data.completedCount,
84
+ incidentsCreated: data.incidentsCreated,
85
+ incidentRatePct: orNa(data.incidentRatePct),
86
+ runningNow: orNa(data.runningNow),
87
+ openIncidentsNow: orNa(data.openIncidentsNow),
88
+ })
89
+ }
90
+
91
+ export function registerWidgetTools(
92
+ server: MCPServer,
93
+ ch: PrometheusClient,
94
+ options: AnalyticsWidgetToolsOptions,
95
+ ) {
96
+ // Each show tool words its result via `await localizeViewFor(profileStore, ctx)`:
97
+ // `t` for the model-facing `summary` (→ "en" without a store or a caller
98
+ // identity), `title` for the view title, set only in a language the profile
99
+ // names (the kit's one rule for every module, #322 U3).
100
+ const { profileStore, engineScope } = options
101
+ const namesFor = options.namesFor ?? NO_NAMES
102
+
103
+ /**
104
+ * Fetches the latest deployed version's BPMN XML for the heatmap overlay via
105
+ * the injected lookup, from the engines whose heat it shows, in
106
+ * configuration order: the first that has the model draws it, so a process
107
+ * that runs only on a later engine still gets its diagram. Without a
108
+ * lookup, or when every engine fails or finds nothing, the XML is `null`
109
+ * and `bpmnMissing` says which of the two happened, so the widget's
110
+ * fallback names the known cause (`heatmap-data.ts`).
111
+ */
112
+ async function loadBpmn(
113
+ processDefinitionKey: string,
114
+ engines: readonly string[],
115
+ ): Promise<{ bpmnXml: string | null; bpmnMissing?: BpmnMissingReason }> {
116
+ const fetchBpmnXml = options.fetchBpmnXml
117
+ if (!fetchBpmnXml) return { bpmnXml: null, bpmnMissing: "no-camunda7" }
118
+ for (const engine of engines.length > 0 ? engines : [undefined]) {
119
+ const bpmnXml = await fetchBpmnXml(processDefinitionKey, engine).catch(() => null)
120
+ if (bpmnXml) return { bpmnXml }
121
+ }
122
+ return { bpmnXml: null, bpmnMissing: "not-loaded" }
123
+ }
124
+
125
+ // --- Process Analytics Dashboard ---
126
+ server.tool(
127
+ {
128
+ name: "analytics_show_dashboard",
129
+ title: "Process Analytics Dashboard",
130
+ description:
131
+ "Show process metrics from Prometheus: flows within the period (starts, completions, incidents created/resolved, durations of the instances that ended) and the live state right now (instances running, incidents open), per process definition and per (process, activity).",
132
+ annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: true },
133
+ inputSchema: strictToolInput(dashboardInputShape),
134
+ ...showToolBinding("analytics_show_dashboard", "Process Analytics Dashboard"),
135
+ },
136
+ withToolErrors(async (args, ctx) => {
137
+ const { t, title } = await localizeViewFor(profileStore, ctx)
138
+ const settings = await settingsFor(profileStore, ctx)
139
+ const period = args.period ?? settings.defaultPeriod
140
+ const data = await namedDashboard(
141
+ await queries.dashboardData(withCallerSignal(ch, ctx.signal), {
142
+ ...withEngineScope(engineScope, args),
143
+ period,
144
+ minBucketSize: settings.minBucketSize,
145
+ }),
146
+ namesFor,
147
+ )
148
+ // The RESOLVED period and the requested scope travel as cell props, so a
149
+ // widget that self-fetches (structuredContent stripped, a saved view)
150
+ // reads the same scope instead of the fleet default.
151
+ const cellProps = {
152
+ period,
153
+ ...(args.processDefinitionKey ? { processDefinitionKey: args.processDefinitionKey } : {}),
154
+ ...(isFleetRequest(args.engine) ? {} : { engine: args.engine }),
155
+ }
156
+ return buildComposedView({
157
+ app: "analytics",
158
+ // The view title in the language the profile names (the widget heading's
159
+ // own key); none with "system", where the heading names the view.
160
+ title: title("aExecSummary.title"),
161
+ layout: [
162
+ { row: [{ widget: "analytics:execution-summary-kpi", props: cellProps }] },
163
+ { row: [{ widget: "analytics:execution-performance-kpi", props: cellProps }] },
164
+ { row: [{ widget: "analytics:process-definition-breakdown", props: cellProps }] },
165
+ { row: [{ widget: "analytics:activity-bottleneck-table", props: cellProps }] },
166
+ ],
167
+ entries: [{ dataType: "analytics:dashboard", data }],
168
+ summary: dashboardSummary(t, data, isFleetRequest(args.engine)),
169
+ })
170
+ }),
171
+ )
172
+
173
+ // --- Failure Dashboard ---
174
+ server.tool(
175
+ {
176
+ name: "analytics_show_failure_dashboard",
177
+ title: "Failure Analysis Dashboard",
178
+ description:
179
+ "Show the incidents open right now from Prometheus, grouped by incident type and process definition (no activity, message or timestamps), with each affected process's running instances and dead jobs. Use to show the metric view to the user; for one engine's live incident clusters use camunda7_show_engine_health.",
180
+ annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: true },
181
+ inputSchema: strictToolInput({
182
+ ...schemas.engineFilterShape,
183
+ }),
184
+ ...showToolBinding("analytics_show_failure_dashboard", "Failure Analysis Dashboard"),
185
+ },
186
+ withToolErrors(async (args, ctx) => {
187
+ const { t, title } = await localizeViewFor(profileStore, ctx)
188
+ const data = await namedFailureDashboard(
189
+ await queries.failureDashboardData(
190
+ withCallerSignal(ch, ctx.signal),
191
+ withEngineScope(engineScope, args),
192
+ ),
193
+ namesFor,
194
+ )
195
+ // The requested scope travels as cell props so a self-fetch keeps it.
196
+ const cellProps = isFleetRequest(args.engine) ? undefined : { engine: args.engine }
197
+ return buildComposedView({
198
+ app: "analytics",
199
+ title: title("aFailureSummary.title"),
200
+ layout: [
201
+ { row: [{ widget: "analytics:failure-summary-kpi", props: cellProps }] },
202
+ { row: [{ widget: "analytics:error-patterns-table", props: cellProps }] },
203
+ { row: [{ widget: "analytics:failure-rate-table", props: cellProps }] },
204
+ ],
205
+ entries: [{ dataType: "analytics:failureDashboard", data }],
206
+ summary: t("aSum.failureDashboard", {
207
+ engines: engineScopeSummary(t, data.engines, isFleetRequest(args.engine)),
208
+ totalIncidents: data.totalIncidents,
209
+ uniqueErrorPatterns: data.uniqueErrorPatterns,
210
+ mostAffected: data.mostAffectedProcess
211
+ ? t("aSum.mostAffectedProcess", { key: data.mostAffectedProcess })
212
+ : "",
213
+ }),
214
+ })
215
+ }),
216
+ )
217
+
218
+ registerComparisonWidgetTools({ server, ch, engineScope, profileStore, namesFor })
219
+
220
+ // --- BPMN Heatmap (per-element frequency + duration on the diagram) ---
221
+ server.tool(
222
+ {
223
+ name: "analytics_show_bpmn_heatmap",
224
+ title: "BPMN Heatmap",
225
+ description:
226
+ "Render a process definition's BPMN diagram with a per-element heat overlay from metrics, with a Frequency↔Duration toggle (traversal count vs average duration per element). Node-level only — sequence-flow/edge heat is not available from metrics — and rendered on the latest deployed version's diagram (activity metrics carry no version label). Needs the camunda7 client to fetch the BPMN XML; otherwise the widget shows a fallback.",
227
+ annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: true },
228
+ inputSchema: strictToolInput(heatmapInputShape),
229
+ ...showToolBinding("analytics_show_bpmn_heatmap", "BPMN Heatmap"),
230
+ },
231
+ withToolErrors(async (args, ctx) => {
232
+ const { t, title } = await localizeViewFor(profileStore, ctx)
233
+ const period = args.period ?? (await settingsFor(profileStore, ctx)).defaultPeriod
234
+ const scoped = withEngineScope(engineScope, args)
235
+ const heat = await queries.elementHeat(withCallerSignal(ch, ctx.signal), {
236
+ ...scoped,
237
+ period,
238
+ })
239
+ // Read here only to know whether a diagram exists (and why not); the
240
+ // XML stays out of the payload — the widget reads it from
241
+ // `analytics_bpmn_xml_data` (cached by the camunda7 lookup).
242
+ const bpmn = await loadBpmn(args.processDefinitionKey, scoped.engine)
243
+ return buildSingleWidgetView({
244
+ widget: "analytics:bpmn-heatmap",
245
+ app: "analytics",
246
+ dataType: "analytics:bpmnHeatmap",
247
+ // `AnalyticsBpmnHeatmapData` (widgets/bpmn-heatmap.tsx): the engines
248
+ // travel with the heat, so the model description names them.
249
+ data: {
250
+ processDefinitionKey: args.processDefinitionKey,
251
+ period,
252
+ engines: scoped.engine,
253
+ ...(bpmn.bpmnMissing ? { bpmnMissing: bpmn.bpmnMissing } : {}),
254
+ frequency: heat.frequency,
255
+ durationSec: heat.durationSec,
256
+ asOf: heat.asOf,
257
+ },
258
+ title: title("aHeatmap.title"),
259
+ summary: t("aSum.bpmnHeatmap", {
260
+ key: args.processDefinitionKey,
261
+ period,
262
+ engines: engineScopeSummary(t, scoped.engine, isFleetRequest(args.engine)),
263
+ elementCount: Object.keys(heat.frequency).length,
264
+ fallbackNote: bpmn.bpmnXml ? "" : t("aSum.bpmnHeatmapNoXml"),
265
+ }),
266
+ })
267
+ }),
268
+ )
269
+
270
+ server.tool(
271
+ {
272
+ name: ANALYTICS_BPMN_HEATMAP_DATA,
273
+ title: "BPMN heatmap data (internal)",
274
+ description:
275
+ "Internal JSON feed (no UI) for the BPMN heatmap — per-element execution frequency + average duration over a window. Lets another widget (e.g. the CIB Seven cockpit) paint the heat on its own diagram. Prefer analytics_show_bpmn_heatmap for a standalone view.",
276
+ annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: true },
277
+ inputSchema: strictToolInput(heatmapInputShape),
278
+ ...appOnly,
279
+ },
280
+ withToolErrors(async (args, ctx) => {
281
+ const period = args.period ?? (await settingsFor(profileStore, ctx)).defaultPeriod
282
+ const scoped = withEngineScope(engineScope, args)
283
+ const heat = await queries.elementHeat(withCallerSignal(ch, ctx.signal), {
284
+ ...scoped,
285
+ period,
286
+ })
287
+ const data = {
288
+ processDefinitionKey: args.processDefinitionKey,
289
+ period,
290
+ engines: scoped.engine,
291
+ frequency: heat.frequency,
292
+ durationSec: heat.durationSec,
293
+ asOf: heat.asOf,
294
+ }
295
+ return buildDataFeedResult(data)
296
+ }),
297
+ )
298
+
299
+ server.tool(
300
+ {
301
+ name: ANALYTICS_BPMN_XML_DATA,
302
+ title: "BPMN heatmap diagram (internal)",
303
+ description:
304
+ "Internal JSON feed (no UI): the latest deployed BPMN XML of a process key on the engines in scope, for the heatmap view's diagram.",
305
+ annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: true },
306
+ inputSchema: strictToolInput({
307
+ processDefinitionKey: heatmapInputShape.processDefinitionKey,
308
+ ...schemas.engineFilterShape,
309
+ }),
310
+ ...appOnly,
311
+ },
312
+ withToolErrors(async (args) => {
313
+ const scoped = withEngineScope(engineScope, args)
314
+ return buildDataFeedResult({
315
+ processDefinitionKey: args.processDefinitionKey,
316
+ ...(await loadBpmn(args.processDefinitionKey, scoped.engine)),
317
+ })
318
+ }),
319
+ )
320
+
321
+ // ── Per-view data feeds (plain, no UI) ──────────────────────────────────
322
+ // Backing the dashboard widgets' self-fetch. Self-fetching a show_* tool
323
+ // instead would be host-defined behavior: hosts honoring
324
+ // `resultCanProduceWidget` may render a second widget per refresh, and the
325
+ // show tool's localized model summary is generated for a call the model
326
+ // never sees.
327
+
328
+ server.tool(
329
+ {
330
+ name: ANALYTICS_DASHBOARD_DATA,
331
+ title: "Analytics dashboard data (internal)",
332
+ description:
333
+ "Internal JSON feed (no UI) for the analytics dashboard widgets' self-fetch. Prefer analytics_show_dashboard.",
334
+ annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: true },
335
+ inputSchema: strictToolInput(dashboardInputShape),
336
+ ...appOnly,
337
+ },
338
+ withToolErrors(async (args, ctx) => {
339
+ const settings = await settingsFor(profileStore, ctx)
340
+ const data = await namedDashboard(
341
+ await queries.dashboardData(withCallerSignal(ch, ctx.signal), {
342
+ ...withEngineScope(engineScope, args),
343
+ period: args.period ?? settings.defaultPeriod,
344
+ minBucketSize: settings.minBucketSize,
345
+ }),
346
+ namesFor,
347
+ )
348
+ return buildDataFeedResult({ ...data })
349
+ }),
350
+ )
351
+
352
+ server.tool(
353
+ {
354
+ name: ANALYTICS_FAILURE_DASHBOARD_DATA,
355
+ title: "Failure dashboard data (internal)",
356
+ description:
357
+ "Internal JSON feed (no UI) for the failure dashboard widgets' self-fetch. Prefer analytics_show_failure_dashboard.",
358
+ annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: true },
359
+ inputSchema: strictToolInput({
360
+ ...schemas.engineFilterShape,
361
+ }),
362
+ ...appOnly,
363
+ },
364
+ withToolErrors(async (args, ctx) => {
365
+ const data = await namedFailureDashboard(
366
+ await queries.failureDashboardData(
367
+ withCallerSignal(ch, ctx.signal),
368
+ withEngineScope(engineScope, args),
369
+ ),
370
+ namesFor,
371
+ )
372
+ return buildDataFeedResult({ ...data })
373
+ }),
374
+ )
375
+ }
@@ -0,0 +1,179 @@
1
+ import {
2
+ HandOffButton,
3
+ ListTable,
4
+ NamedId,
5
+ Section,
6
+ TableEmptyState,
7
+ TableSkeleton,
8
+ Td,
9
+ WidgetShell,
10
+ formatDuration,
11
+ formatNumber,
12
+ type ListTableColumn,
13
+ } from "@miragon/pulse-widget-shell/widgets"
14
+ import type { AnalyticsDashboardData } from "@miragon/pulse-analytics-client"
15
+ import { useDashboardSelfFetch, type DashboardScopeProps, type DashboardView } from "./lib.js"
16
+ import { QueryGate } from "../query-gate.js"
17
+ import { useT, type T } from "../../messages/use-t.js"
18
+ import { engineIdsOf, useHandOff, type HandOff } from "../hand-off.js"
19
+ import { elementNameOf, processNameOf } from "../../bpmn-names.js"
20
+ import { activityTypeLabel } from "../names.js"
21
+ import { Figure, FigureLegend } from "../figure.js"
22
+
23
+ type ActivityRow = AnalyticsDashboardData["activityBreakdown"][number]
24
+
25
+ /**
26
+ * Why ONE activity of one process is a bottleneck — over the scope of the
27
+ * data on screen (its `engines` and `period` echo), so the follow-up reads
28
+ * exactly the numbers shown.
29
+ */
30
+ export function activityBottleneckHandOff(
31
+ act: ActivityRow,
32
+ data: Pick<AnalyticsDashboardData, "engines" | "period">,
33
+ ): HandOff {
34
+ return {
35
+ intent: "askAi.activityBottleneck",
36
+ ids: {
37
+ engine: engineIdsOf(data.engines),
38
+ processDefinitionKey: act.processDefinitionKey,
39
+ period: data.period,
40
+ },
41
+ // Durations are null when nothing ended in the window — left out, never 0.
42
+ facts: {
43
+ activityId: act.activityId,
44
+ activityType: act.activityType,
45
+ executionCount: act.executionCount,
46
+ avgDurationMs: act.avgDurationMs,
47
+ p95DurationMs: act.p95DurationMs,
48
+ totalTimeMs: act.totalTimeMs,
49
+ },
50
+ tools: ["analytics_element_bottleneck", "analytics_analyze_process_performance"],
51
+ }
52
+ }
53
+
54
+ /**
55
+ * The columns by priority (U2): the activity (named, with its id, type and
56
+ * process below), then the sort key right after it, the executions, and the
57
+ * two duration columns, which give way only below 640 px (the 760 px inline
58
+ * view shows every one). The chat hand-off is pinned at the right edge.
59
+ */
60
+ function columns(t: T): ListTableColumn[] {
61
+ return [
62
+ { label: t("aBottleneck.colActivity") },
63
+ { label: t("aBottleneck.colTotalTime"), align: "right", sort: "descending" },
64
+ { label: t("aBottleneck.colExecutions"), align: "right" },
65
+ { label: t("aBottleneck.colAvg"), align: "right", hideBelow: "sm" },
66
+ { label: t("aBottleneck.colP95"), align: "right", hideBelow: "sm" },
67
+ { plain: true, sticky: true },
68
+ ]
69
+ }
70
+
71
+ /**
72
+ * One row per (process, activity): BPMN ids are only unique within one model,
73
+ * so the same id in two processes is two rows — the second line says which
74
+ * process when the dashboard spans several. The list is the top of the
75
+ * breakdown by total time; its heading says so when it is cut.
76
+ */
77
+ export function ActivityBottleneckTable({
78
+ data: initialData,
79
+ processDefinitionKey,
80
+ period,
81
+ engine,
82
+ }: { data: DashboardView | null } & DashboardScopeProps) {
83
+ const t = useT()
84
+ const { ask } = useHandOff()
85
+ const fallbackQuery = useDashboardSelfFetch(initialData, { processDefinitionKey, period, engine })
86
+
87
+ return (
88
+ <QueryGate initialData={initialData} query={fallbackQuery} skeleton={<TableSkeleton />}>
89
+ {(data) => {
90
+ const rows = data.activityBreakdown
91
+ if (rows.length === 0) {
92
+ return (
93
+ <WidgetShell>
94
+ <TableEmptyState description={t("aBottleneck.emptyHint")}>
95
+ {t("aBottleneck.emptyState")}
96
+ </TableEmptyState>
97
+ </WidgetShell>
98
+ )
99
+ }
100
+ const cut = data.activityCount > rows.length
101
+ const fleet = data.processDefinitionKey === null
102
+ return (
103
+ <WidgetShell>
104
+ <Section
105
+ title={
106
+ cut
107
+ ? t("aBottleneck.headingTop", { count: formatNumber(rows.length) })
108
+ : t("aBottleneck.heading")
109
+ }
110
+ count={cut ? undefined : rows.length}
111
+ defaultOpen
112
+ >
113
+ {cut && (
114
+ <p className="text-muted-foreground text-small mb-2">
115
+ {t("aBottleneck.topNote", {
116
+ count: formatNumber(rows.length),
117
+ total: formatNumber(data.activityCount),
118
+ })}
119
+ </p>
120
+ )}
121
+ <ListTable ariaLabel={t("aBottleneck.tableLabel")} columns={columns(t)}>
122
+ {rows.map((act) => (
123
+ <tr key={`${act.processDefinitionKey}:${act.activityId}`}>
124
+ <Td>
125
+ <NamedId
126
+ name={elementNameOf(data.names, act.processDefinitionKey, act.activityId)}
127
+ id={act.activityId}
128
+ detail={[
129
+ activityTypeLabel(t, act.activityType),
130
+ fleet
131
+ ? (processNameOf(data.names, act.processDefinitionKey) ??
132
+ act.processDefinitionKey)
133
+ : null,
134
+ ]}
135
+ />
136
+ </Td>
137
+ <Td align="right" className="font-medium whitespace-nowrap tabular-nums">
138
+ {formatDuration(act.totalTimeMs)}
139
+ </Td>
140
+ <Td align="right" className="tabular-nums">
141
+ {formatNumber(act.executionCount)}
142
+ </Td>
143
+ <Td align="right" className="whitespace-nowrap tabular-nums">
144
+ <Figure
145
+ value={act.avgDurationMs}
146
+ format={formatDuration}
147
+ why={t("aFigure.nothingEnded")}
148
+ />
149
+ </Td>
150
+ <Td align="right" className="whitespace-nowrap tabular-nums">
151
+ <Figure
152
+ value={act.p95DurationMs}
153
+ format={formatDuration}
154
+ why={t("aFigure.nothingEnded")}
155
+ />
156
+ </Td>
157
+ <Td align="right">
158
+ <HandOffButton
159
+ action="explainBottleneck"
160
+ variant="icon"
161
+ prompt={ask(activityBottleneckHandOff(act, data))}
162
+ />
163
+ </Td>
164
+ </tr>
165
+ ))}
166
+ </ListTable>
167
+ <FigureLegend
168
+ reasons={[
169
+ rows.some((a) => a.avgDurationMs === null || a.p95DurationMs === null) &&
170
+ t("aFigure.nothingEnded"),
171
+ ]}
172
+ />
173
+ </Section>
174
+ </WidgetShell>
175
+ )
176
+ }}
177
+ </QueryGate>
178
+ )
179
+ }
@@ -0,0 +1,108 @@
1
+ import {
2
+ ListTable,
3
+ NamedId,
4
+ Section,
5
+ TableEmptyState,
6
+ TableSkeleton,
7
+ Td,
8
+ WidgetShell,
9
+ formatDuration,
10
+ formatNumber,
11
+ type ListTableColumn,
12
+ } from "@miragon/pulse-widget-shell/widgets"
13
+ import { useDashboardSelfFetch, type DashboardScopeProps, type DashboardView } from "./lib.js"
14
+ import { QueryGate } from "../query-gate.js"
15
+ import { useT, type T } from "../../messages/use-t.js"
16
+ import { processNameOf } from "../../bpmn-names.js"
17
+ import { Figure, FigureLegend } from "../figure.js"
18
+
19
+ /**
20
+ * The figures side by side (U17): one row per process, the counts in
21
+ * right-aligned tabular columns, so the processes compare at a glance. The
22
+ * average duration gives way below 640 px. Every count here is a volume of
23
+ * the period or a live count without a state, new incidents included, so
24
+ * none carries a tone (U14): the summary above shows them the same way.
25
+ */
26
+ function columns(t: T): ListTableColumn[] {
27
+ return [
28
+ { label: t("aDefBreakdown.colProcess") },
29
+ { label: t("aDefBreakdown.colStarted"), align: "right" },
30
+ { label: t("aDefBreakdown.colCompleted"), align: "right" },
31
+ { label: t("aDefBreakdown.colRunningNow"), align: "right" },
32
+ { label: t("aDefBreakdown.colIncidents"), align: "right" },
33
+ { label: t("aDefBreakdown.colAvgDuration"), align: "right", hideBelow: "sm" },
34
+ ]
35
+ }
36
+
37
+ export function ProcessDefinitionBreakdown({
38
+ data: initialData,
39
+ processDefinitionKey,
40
+ period,
41
+ engine,
42
+ }: { data: DashboardView | null } & DashboardScopeProps) {
43
+ const t = useT()
44
+ const fallbackQuery = useDashboardSelfFetch(initialData, { processDefinitionKey, period, engine })
45
+
46
+ return (
47
+ <QueryGate initialData={initialData} query={fallbackQuery} skeleton={<TableSkeleton />}>
48
+ {(data) =>
49
+ data.definitionBreakdown.length === 0 ? (
50
+ <WidgetShell>
51
+ <TableEmptyState description={t("aDefBreakdown.emptyHint")}>
52
+ {t("aDefBreakdown.emptyState")}
53
+ </TableEmptyState>
54
+ </WidgetShell>
55
+ ) : (
56
+ <WidgetShell>
57
+ <Section
58
+ title={t("aDefBreakdown.heading")}
59
+ count={data.definitionBreakdown.length}
60
+ defaultOpen
61
+ >
62
+ <ListTable ariaLabel={t("aDefBreakdown.tableLabel")} columns={columns(t)}>
63
+ {data.definitionBreakdown.map((def) => (
64
+ <tr key={def.processDefinitionKey}>
65
+ <Td>
66
+ <NamedId
67
+ name={processNameOf(data.names, def.processDefinitionKey)}
68
+ id={def.processDefinitionKey}
69
+ />
70
+ </Td>
71
+ <Td align="right" className="tabular-nums">
72
+ {formatNumber(def.totalInstances)}
73
+ </Td>
74
+ <Td align="right" className="tabular-nums">
75
+ {formatNumber(def.completed)}
76
+ </Td>
77
+ {/* Not reported (no engine in scope sends its gauges): "—", never 0. */}
78
+ <Td align="right" className="tabular-nums">
79
+ <Figure value={def.runningNow} why={t("aFigure.notReported")} />
80
+ </Td>
81
+ <Td align="right" className="tabular-nums">
82
+ {formatNumber(def.incidentsCreated)}
83
+ </Td>
84
+ <Td align="right" className="whitespace-nowrap tabular-nums">
85
+ <Figure
86
+ value={def.avgDurationMs}
87
+ format={formatDuration}
88
+ why={t("aFigure.nothingEnded")}
89
+ />
90
+ </Td>
91
+ </tr>
92
+ ))}
93
+ </ListTable>
94
+ <FigureLegend
95
+ reasons={[
96
+ data.definitionBreakdown.some((d) => d.runningNow === null) &&
97
+ t("aFigure.notReported"),
98
+ data.definitionBreakdown.some((d) => d.avgDurationMs === null) &&
99
+ t("aFigure.nothingEnded"),
100
+ ]}
101
+ />
102
+ </Section>
103
+ </WidgetShell>
104
+ )
105
+ }
106
+ </QueryGate>
107
+ )
108
+ }