@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,352 @@
1
+ import {
2
+ Badge,
3
+ KpiGrid,
4
+ SectionHeading,
5
+ StatusBadge,
6
+ useToolQuery,
7
+ type KpiCell,
8
+ } from "@miragon/mcp-toolkit-ui"
9
+ import { Info } from "lucide-react"
10
+ import {
11
+ HandOffButton,
12
+ Icon,
13
+ ListTable,
14
+ NamedId,
15
+ TableEmptyState,
16
+ TableSkeleton,
17
+ Td,
18
+ ViewMeta,
19
+ WidgetShell,
20
+ formatNumber,
21
+ type ListTableColumn,
22
+ type ListTableGroup,
23
+ } from "@miragon/pulse-widget-shell/widgets"
24
+ import type { EngineLandscapeEngine, EngineLandscapeResult } from "@miragon/pulse-analytics-client"
25
+ import { ANALYTICS_ENGINE_LANDSCAPE_DATA } from "../tool-names.js"
26
+ import { QueryGate } from "./query-gate.js"
27
+ import { useT, type T } from "../messages/use-t.js"
28
+ import { useHandOff, type HandOff } from "./hand-off.js"
29
+ import { processNameOf, type Named } from "../bpmn-names.js"
30
+ import { Figure, FigureLegend, Legend, StateCount } from "./figure.js"
31
+
32
+ /** The landscape payload: the figures plus the process names behind the matrix's keys. */
33
+ export type EngineLandscapeView = Named<EngineLandscapeResult>
34
+ export type EngineLandscapeData = EngineLandscapeView | null
35
+
36
+ /** Engine ids from the layout cell props (`engine` may be a string or a list). */
37
+ function engineIdsFromProps(engine: string | string[] | undefined): string[] {
38
+ if (typeof engine === "string") return engine.length > 0 ? [engine] : []
39
+ if (Array.isArray(engine)) return engine.filter((id) => typeof id === "string" && id.length > 0)
40
+ return []
41
+ }
42
+
43
+ /**
44
+ * The cross-engine overview: what runs where, how much of it, and which engines
45
+ * carry a backlog.
46
+ *
47
+ * Deliberately NOT an engine scoreboard. Two engines run different processes,
48
+ * so a per-engine failure rate or average duration describes the process mix,
49
+ * not the engine — the widget therefore shows absolute counts plus the
50
+ * process-independent job backlog, and offers the KPI comparison only for the
51
+ * definitions that actually run on more than one engine.
52
+ *
53
+ * "—" means one thing here: not measured (an engine that sends no metrics).
54
+ * A process that is not deployed on an engine is an empty matrix cell.
55
+ */
56
+ export function EngineLandscapeWidget({
57
+ data: initialData,
58
+ engine,
59
+ }: {
60
+ data: EngineLandscapeData
61
+ /** Engine ids to include; pass all configured ones to surface silent engines. */
62
+ engine?: string | string[]
63
+ }) {
64
+ const t = useT()
65
+ const engineIds = engineIdsFromProps(engine)
66
+ const query = useToolQuery<EngineLandscapeView>(
67
+ // The engine scope belongs in the key: a subset view must not read the
68
+ // full-fleet payload out of the cache.
69
+ ["analytics:engine-landscape", engineIds.join(",")],
70
+ ANALYTICS_ENGINE_LANDSCAPE_DATA,
71
+ engineIds.length > 0 ? { engine: engineIds } : {},
72
+ { enabled: !initialData },
73
+ )
74
+
75
+ return (
76
+ <QueryGate initialData={initialData} query={query} skeleton={<TableSkeleton />}>
77
+ {(data) => (
78
+ <WidgetShell>
79
+ <LandscapeSummary data={data} t={t} />
80
+ <EngineTable data={data} t={t} />
81
+ <ProcessMatrix data={data} t={t} />
82
+ </WidgetShell>
83
+ )}
84
+ </QueryGate>
85
+ )
86
+ }
87
+
88
+ function LandscapeSummary({ data, t }: { data: EngineLandscapeView; t: T }) {
89
+ const { totals } = data
90
+ const silent = totals.engineCount - totals.reportingEngineCount
91
+ const cells: KpiCell[] = [
92
+ {
93
+ label: t("aLandscape.kpiEngines"),
94
+ value: formatNumber(totals.engineCount),
95
+ // An engine without metrics may be down: worth a look, not an error.
96
+ trend:
97
+ silent > 0 ? t("aLandscape.kpiEnginesSilent", { count: formatNumber(silent) }) : undefined,
98
+ trendTone: silent > 0 ? "warning" : undefined,
99
+ },
100
+ {
101
+ label: t("aLandscape.kpiProcesses"),
102
+ value: formatNumber(totals.processKeyCount),
103
+ trend: t("aLandscape.kpiProcessesShared", {
104
+ count: formatNumber(totals.sharedProcessKeyCount),
105
+ }),
106
+ },
107
+ { label: t("aLandscape.kpiRunning"), value: formatNumber(totals.runningInstances) },
108
+ {
109
+ label: t("aLandscape.kpiIncidents"),
110
+ value: formatNumber(totals.openIncidents),
111
+ tone: (totals.openIncidents ?? 0) > 0 ? "danger" : undefined,
112
+ },
113
+ ]
114
+ return (
115
+ <>
116
+ <SectionHeading title={t("aLandscape.heading")} hint={t("aLandscape.headingHint")} />
117
+ {/* A live snapshot: no period, the engines (and how many send no metrics) and when. */}
118
+ <ViewMeta
119
+ className="-mt-2 mb-3"
120
+ engines={{ count: totals.engineCount, silent }}
121
+ asOf={data.asOf}
122
+ />
123
+ <KpiGrid cells={cells} />
124
+ </>
125
+ )
126
+ }
127
+
128
+ /**
129
+ * The per-engine table in its two column groups, headed as such (U17): the
130
+ * load counts are absolute (comparable only as "where the work sits"), while
131
+ * the backlog gauges carry no process label and therefore genuinely compare
132
+ * across engines. The backlog is shown in FULL — it is the only
133
+ * mix-independent evidence this view offers, and the fleet prompt tells the
134
+ * model to judge engines on exactly it — so it never hides; only the
135
+ * inventory column gives way below 768 px.
136
+ */
137
+ function engineColumns(t: T): ListTableColumn[] {
138
+ return [
139
+ { label: t("aLandscape.colEngine") },
140
+ { label: t("aLandscape.colRunning"), align: "right" },
141
+ { label: t("aLandscape.colIncidents"), align: "right" },
142
+ { label: t("aLandscape.colFailedJobs"), align: "right" },
143
+ { label: t("aLandscape.colExecutable"), align: "right" },
144
+ { label: t("aLandscape.colSuspended"), align: "right" },
145
+ { label: t("aLandscape.colDueFuture"), align: "right" },
146
+ { label: t("aLandscape.colExternalTasks"), align: "right" },
147
+ { label: t("aLandscape.colProcesses"), align: "right", hideBelow: "md" },
148
+ ]
149
+ }
150
+
151
+ function engineGroups(t: T): ListTableGroup[] {
152
+ return [
153
+ {},
154
+ { label: t("aLandscape.groupLoad"), span: 3 },
155
+ { label: t("aLandscape.groupBacklog"), span: 4 },
156
+ {},
157
+ ]
158
+ }
159
+
160
+ /** One count of an engine: the number, or "—" with its meaning for an engine without metrics. */
161
+ function CountCell({ value, t }: { value: number | null; t: T }) {
162
+ return (
163
+ <Td align="right" className="tabular-nums">
164
+ <Figure value={value} why={t("aLandscape.unknown")} />
165
+ </Td>
166
+ )
167
+ }
168
+
169
+ /**
170
+ * An engine's open incidents or jobs without retries: the states among its
171
+ * counts (decision f), a danger edge while there are any ({@link StateCount},
172
+ * the kit's one ladder), as camunda7's engine tiles show the same counts of
173
+ * the same engines in the cross-engine overview this table sits in.
174
+ */
175
+ function StateCountCell({ value, t }: { value: number | null; t: T }) {
176
+ return (
177
+ <Td align="right" className="tabular-nums">
178
+ <StateCount value={value} why={t("aLandscape.unknown")} />
179
+ </Td>
180
+ )
181
+ }
182
+
183
+ function EngineRow({ engine: e, t }: { engine: EngineLandscapeEngine; t: T }) {
184
+ return (
185
+ <tr>
186
+ <Td>
187
+ <span className="flex flex-wrap items-center gap-2">
188
+ {/* An engine id is short and one word ("prod-a"): never wrapped. */}
189
+ <span className="font-mono font-medium whitespace-nowrap">{e.engineId}</span>
190
+ {/* The badge may go under the id in a narrow column, but never
191
+ breaks inside itself ("no / metrics"): a pill is one line. */}
192
+ {!e.reporting && (
193
+ <StatusBadge tone="warning" className="whitespace-nowrap">
194
+ {t("aLandscape.noMetrics")}
195
+ </StatusBadge>
196
+ )}
197
+ </span>
198
+ </Td>
199
+ <CountCell value={e.runningInstances} t={t} />
200
+ <StateCountCell value={e.openIncidents} t={t} />
201
+ <StateCountCell value={e.failedJobs} t={t} />
202
+ <CountCell value={e.executableJobs} t={t} />
203
+ <CountCell value={e.suspendedJobs} t={t} />
204
+ <CountCell value={e.jobsDueFuture} t={t} />
205
+ <CountCell value={e.openExternalTasks} t={t} />
206
+ <Td align="right" className="tabular-nums">
207
+ <Figure value={e.deployedDefinitionKeys} why={t("aLandscape.unknown")} />
208
+ {(e.exclusiveDefinitionKeys ?? 0) > 0 && (
209
+ <span className="text-muted-foreground text-small ml-1 whitespace-nowrap">
210
+ {t("aLandscape.exclusiveSuffix", {
211
+ count: formatNumber(e.exclusiveDefinitionKeys),
212
+ })}
213
+ </span>
214
+ )}
215
+ </Td>
216
+ </tr>
217
+ )
218
+ }
219
+
220
+ function EngineTable({ data, t }: { data: EngineLandscapeView; t: T }) {
221
+ if (data.engines.length === 0) {
222
+ return <TableEmptyState>{t("aLandscape.emptyEngines")}</TableEmptyState>
223
+ }
224
+ return (
225
+ <section>
226
+ <SectionHeading title={t("aLandscape.engines.title")} hint={t("aLandscape.engines.hint")} />
227
+ <ListTable
228
+ ariaLabel={t("aLandscape.engines.tableLabel")}
229
+ columns={engineColumns(t)}
230
+ groups={engineGroups(t)}
231
+ // Eight figures side by side: the compact cell padding keeps every
232
+ // backlog column in a 760 px inline view, in German too.
233
+ className="[&_td]:px-3 [&_th]:px-3"
234
+ >
235
+ {data.engines.map((e) => (
236
+ <EngineRow key={e.engineId} engine={e} t={t} />
237
+ ))}
238
+ </ListTable>
239
+ <FigureLegend reasons={[data.engines.some((e) => !e.reporting) && t("aLandscape.unknown")]} />
240
+ </section>
241
+ )
242
+ }
243
+
244
+ /**
245
+ * The process × engine inventory. Shared definitions lead the table and are the
246
+ * only rows offering the KPI comparison — everywhere else the two engines have
247
+ * no common workload to compare. Its columns are the engines that send
248
+ * metrics: a silent engine has nothing to show here, the meta line counts it
249
+ * and a note names it. The comparison is pinned at the right edge, so it
250
+ * stays in reach however many engines there are.
251
+ */
252
+ function ProcessMatrix({ data, t }: { data: EngineLandscapeView; t: T }) {
253
+ if (data.processes.length === 0) {
254
+ return <TableEmptyState>{t("aLandscape.emptyProcesses")}</TableEmptyState>
255
+ }
256
+ const engineIds = data.engines.filter((e) => e.reporting).map((e) => e.engineId)
257
+ const silent = data.engines.filter((e) => !e.reporting).map((e) => e.engineId)
258
+ const columns: ListTableColumn[] = [
259
+ { label: t("aLandscape.colProcess") },
260
+ ...engineIds.map((id): ListTableColumn => ({
261
+ label: <span className="font-mono">{id}</span>,
262
+ align: "right",
263
+ })),
264
+ { label: t("aLandscape.colIncidents"), align: "right" },
265
+ {
266
+ label: <span className="sr-only">{t("aLandscape.colCompare")}</span>,
267
+ plain: true,
268
+ sticky: true,
269
+ },
270
+ ]
271
+ return (
272
+ <section>
273
+ <SectionHeading title={t("aLandscape.matrix.title")} hint={t("aLandscape.matrix.hint")} />
274
+ <p className="text-muted-foreground text-small mb-3 flex items-start gap-2">
275
+ <Icon icon={Info} dense className="mt-0.5" />
276
+ <span>{t("aLandscape.mixNote")}</span>
277
+ </p>
278
+ <ListTable ariaLabel={t("aLandscape.matrix.tableLabel")} columns={columns}>
279
+ {data.processes.map((p) => (
280
+ <tr key={p.processDefinitionKey}>
281
+ <Td>
282
+ <span className="flex flex-wrap items-center gap-2">
283
+ <NamedId
284
+ name={processNameOf(data.names, p.processDefinitionKey)}
285
+ id={p.processDefinitionKey}
286
+ />
287
+ {p.shared && (
288
+ <Badge variant="outline">
289
+ {t("aLandscape.sharedBadge", { count: formatNumber(p.engineIds.length) })}
290
+ </Badge>
291
+ )}
292
+ </span>
293
+ </Td>
294
+ {engineIds.map((id) => (
295
+ <Td key={id} align="right" className="tabular-nums">
296
+ {p.engineIds.includes(id) ? (
297
+ formatNumber(p.runningByEngine[id] ?? 0)
298
+ ) : (
299
+ // Not deployed: an empty cell (the legend says so), never "—".
300
+ <span className="sr-only">{t("aLandscape.notDeployed")}</span>
301
+ )}
302
+ </Td>
303
+ ))}
304
+ <Td align="right" className="tabular-nums">
305
+ <StateCount value={p.openIncidentsTotal} />
306
+ </Td>
307
+ <Td align="right">
308
+ {p.shared && (
309
+ <CompareAction processKey={p.processDefinitionKey} on={p.engineIds} t={t} />
310
+ )}
311
+ </Td>
312
+ </tr>
313
+ ))}
314
+ </ListTable>
315
+ <Legend>{t("aLandscape.legendNotDeployed")}</Legend>
316
+ {silent.length > 0 && (
317
+ <Legend>
318
+ {t("aLandscape.silentNote", {
319
+ count: formatNumber(silent.length),
320
+ engines: silent.join(", "),
321
+ })}
322
+ </Legend>
323
+ )}
324
+ </section>
325
+ )
326
+ }
327
+
328
+ /**
329
+ * The one valid engine-vs-engine hand-off: same process, two engines (the
330
+ * further engines it runs on are named so the model can pair them next).
331
+ */
332
+ export function landscapeCompareHandOff(processKey: string, on: readonly string[]): HandOff {
333
+ const [engineA, engineB, ...more] = on
334
+ return {
335
+ intent: "askAi.landscapeCompare",
336
+ ids: { processDefinitionKey: processKey, engineA, engineB, windowDays: 14 },
337
+ facts: { alsoRunsOn: more.length > 0 ? more : undefined },
338
+ tools: ["analytics_show_engine_compare"],
339
+ }
340
+ }
341
+
342
+ function CompareAction({ processKey, on, t }: { processKey: string; on: string[]; t: T }) {
343
+ const { ask } = useHandOff()
344
+ return (
345
+ <HandOffButton
346
+ action="compareEngines"
347
+ variant="icon"
348
+ title={t("aLandscape.compareLabel", { key: processKey })}
349
+ prompt={ask(landscapeCompareHandOff(processKey, on))}
350
+ />
351
+ )
352
+ }
@@ -0,0 +1,122 @@
1
+ import {
2
+ HandOffButton,
3
+ ListTable,
4
+ NamedId,
5
+ Section,
6
+ TableEmptyState,
7
+ TableSkeleton,
8
+ Td,
9
+ WidgetShell,
10
+ type ListTableColumn,
11
+ } from "@miragon/pulse-widget-shell/widgets"
12
+ import type { ErrorPatternItem, FailureDashboardData } from "@miragon/pulse-analytics-client"
13
+ import { useFailureDashboardSelfFetch, type FailureScopeProps, type FailureView } from "./lib.js"
14
+ import { QueryGate } from "../query-gate.js"
15
+ import { useT, type T } from "../../messages/use-t.js"
16
+ import { engineIdsOf, useHandOff, type HandOff } from "../hand-off.js"
17
+ import { processNameOf } from "../../bpmn-names.js"
18
+ import { StateCount } from "../figure.js"
19
+
20
+ /**
21
+ * Root cause of ONE group of open incidents. The open-incident gauge carries
22
+ * only the incident type and the process (#336) — the message and the
23
+ * failing activity come from the live incidents, so the hand-off passes no
24
+ * field the data cannot fill. A custom incident type is any string: unless
25
+ * id-shaped it is quoted as untrusted data, never inlined.
26
+ */
27
+ export function errorPatternHandOff(
28
+ pattern: ErrorPatternItem,
29
+ data: Pick<FailureDashboardData, "engines">,
30
+ ): HandOff {
31
+ return {
32
+ intent: "askAi.errorPattern",
33
+ ids: {
34
+ engine: engineIdsOf(data.engines),
35
+ processDefinitionKey: pattern.processDefinitionKey,
36
+ incidentType: pattern.incidentType,
37
+ },
38
+ facts: { openIncidentsNow: pattern.incidentCount },
39
+ tools: [
40
+ "analytics_find_failed_instances",
41
+ "camunda7_list_incidents",
42
+ "camunda7_query_historic_incidents",
43
+ "camunda7_query_historic_activity_instances",
44
+ ],
45
+ }
46
+ }
47
+
48
+ function columns(t: T): ListTableColumn[] {
49
+ return [
50
+ { label: t("aErrorPatterns.columnIncidentType") },
51
+ { label: t("aErrorPatterns.columnProcess") },
52
+ { label: t("aErrorPatterns.columnCount"), align: "right" },
53
+ {
54
+ label: <span className="sr-only">{t("aErrorPatterns.columnAi")}</span>,
55
+ plain: true,
56
+ sticky: true,
57
+ },
58
+ ]
59
+ }
60
+
61
+ export function ErrorPatternsTable({
62
+ data: initialData,
63
+ engine,
64
+ }: { data: FailureView | null } & FailureScopeProps) {
65
+ const t = useT()
66
+ const { ask } = useHandOff()
67
+ const fallbackQuery = useFailureDashboardSelfFetch(initialData, { engine })
68
+ return (
69
+ <QueryGate initialData={initialData} query={fallbackQuery} skeleton={<TableSkeleton />}>
70
+ {(data) => {
71
+ if (data.errorPatterns.length === 0) {
72
+ // Nothing open is "all clear", not missing data.
73
+ return (
74
+ <WidgetShell>
75
+ {data.processBreakdown.length === 0 ? (
76
+ <TableEmptyState variant="done" description={t("aErrorPatterns.emptyDescription")}>
77
+ {t("aErrorPatterns.emptyTitle")}
78
+ </TableEmptyState>
79
+ ) : (
80
+ <TableEmptyState>{t("aErrorPatterns.emptyNoPatterns")}</TableEmptyState>
81
+ )}
82
+ </WidgetShell>
83
+ )
84
+ }
85
+
86
+ return (
87
+ <WidgetShell>
88
+ <Section
89
+ title={t("aErrorPatterns.heading")}
90
+ count={data.errorPatterns.length}
91
+ defaultOpen
92
+ >
93
+ <ListTable ariaLabel={t("aErrorPatterns.tableAriaLabel")} columns={columns(t)}>
94
+ {data.errorPatterns.map((pattern) => (
95
+ <tr key={`${pattern.processDefinitionKey}:${pattern.incidentType}`}>
96
+ <Td className="font-mono break-all">{pattern.incidentType}</Td>
97
+ <Td>
98
+ <NamedId
99
+ name={processNameOf(data.names, pattern.processDefinitionKey)}
100
+ id={pattern.processDefinitionKey}
101
+ />
102
+ </Td>
103
+ <Td align="right" className="tabular-nums">
104
+ <StateCount value={pattern.incidentCount} />
105
+ </Td>
106
+ <Td align="right">
107
+ <HandOffButton
108
+ action="findCause"
109
+ variant="icon"
110
+ prompt={ask(errorPatternHandOff(pattern, data))}
111
+ />
112
+ </Td>
113
+ </tr>
114
+ ))}
115
+ </ListTable>
116
+ </Section>
117
+ </WidgetShell>
118
+ )
119
+ }}
120
+ </QueryGate>
121
+ )
122
+ }
@@ -0,0 +1,80 @@
1
+ import { describe, expect, it } from "vitest"
2
+ import { failureRateHandOff } from "./failure-rate-table.js"
3
+ import { errorPatternHandOff } from "./error-patterns-table.js"
4
+ import { ANALYTICS_ONLY_SURFACE, analyticsSurface, bindHandOff } from "../hand-off.js"
5
+
6
+ const ask = bindHandOff("en", ANALYTICS_ONLY_SURFACE).ask
7
+
8
+ describe("failureRateHandOff", () => {
9
+ const prompt = ask(
10
+ failureRateHandOff(
11
+ {
12
+ processDefinitionKey: "order",
13
+ runningNow: 50,
14
+ deadJobs: 3,
15
+ openIncidents: 6,
16
+ incidentRatePct: 12,
17
+ },
18
+ { engines: ["prod-a"] },
19
+ ),
20
+ )!
21
+
22
+ it("states the live figures it asks about — open incidents, not failed instances (N85)", () => {
23
+ expect(prompt).toContain('Ids: engine="prod-a", processDefinitionKey="order"')
24
+ expect(prompt).toContain(
25
+ "On screen: openIncidentsNow=6, runningNow=50, incidentRatePct=12, deadJobs=3",
26
+ )
27
+ expect(prompt).not.toMatch(/failed|failure/i)
28
+ })
29
+
30
+ it("leaves an unmeasured rate out instead of sending it as 0", () => {
31
+ const idle = ask(
32
+ failureRateHandOff(
33
+ {
34
+ processDefinitionKey: "order",
35
+ runningNow: 0,
36
+ deadJobs: 0,
37
+ openIncidents: 2,
38
+ incidentRatePct: null,
39
+ },
40
+ { engines: ["prod-a"] },
41
+ ),
42
+ )!
43
+ expect(idle).not.toContain("incidentRatePct")
44
+ })
45
+
46
+ it("routes the regression check to tools that measure incident rates (#327)", () => {
47
+ expect(prompt).toContain("analytics_compare_execution_periods")
48
+ expect(prompt).toContain("analytics_cluster_compare")
49
+ // Its incident rates are null per version — a wasted call for this question.
50
+ expect(prompt).not.toContain("analytics_version_compare")
51
+ })
52
+ })
53
+
54
+ describe("errorPatternHandOff (N117)", () => {
55
+ const pattern = { incidentType: "failedJob", processDefinitionKey: "order", incidentCount: 4 }
56
+
57
+ it("names only what the open-incident metric carries", () => {
58
+ const prompt = ask(errorPatternHandOff(pattern, { engines: ["prod-a"] }))!
59
+ expect(prompt).toContain(
60
+ 'Ids: engine="prod-a", processDefinitionKey="order", incidentType="failedJob"',
61
+ )
62
+ expect(prompt).toContain("On screen: openIncidentsNow=4")
63
+ expect(prompt).not.toMatch(/first seen|last seen|sample instance|activityId/i)
64
+ })
65
+
66
+ it("names the aggregate's engines, never a placeholder", () => {
67
+ const prompt = ask(errorPatternHandOff(pattern, { engines: ["prod-a", "prod-b"] }))!
68
+ expect(prompt).toContain('engine=["prod-a","prod-b"]')
69
+ })
70
+
71
+ it("names the camunda7 reads only where camunda7 registers them", () => {
72
+ const withCamunda7 = bindHandOff("en", analyticsSurface(["camunda7_list_incidents"])).ask(
73
+ errorPatternHandOff(pattern, { engines: ["prod-a"] }),
74
+ )!
75
+ expect(withCamunda7).toContain(
76
+ "Tools: analytics_find_failed_instances, camunda7_list_incidents",
77
+ )
78
+ expect(ask(errorPatternHandOff(pattern, { engines: ["prod-a"] }))).not.toContain("camunda7_")
79
+ })
80
+ })