@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.
- package/LICENSE +21 -0
- package/README.md +86 -3
- package/dist/bpmn-name-lookup.d.ts +72 -0
- package/dist/bpmn-name-lookup.d.ts.map +1 -0
- package/dist/bpmn-name-lookup.js +123 -0
- package/dist/bpmn-name-lookup.js.map +1 -0
- package/dist/bpmn-name-lookup.test.d.ts +2 -0
- package/dist/bpmn-name-lookup.test.d.ts.map +1 -0
- package/dist/bpmn-name-lookup.test.js +287 -0
- package/dist/bpmn-name-lookup.test.js.map +1 -0
- package/dist/bpmn-names.d.ts +38 -0
- package/dist/bpmn-names.d.ts.map +1 -0
- package/dist/bpmn-names.js +22 -0
- package/dist/bpmn-names.js.map +1 -0
- package/dist/brand-gates.test.d.ts +2 -0
- package/dist/brand-gates.test.d.ts.map +1 -0
- package/dist/brand-gates.test.js +58 -0
- package/dist/brand-gates.test.js.map +1 -0
- package/dist/builder-texts.d.ts +4 -0
- package/dist/builder-texts.d.ts.map +1 -0
- package/dist/builder-texts.js +14 -0
- package/dist/builder-texts.js.map +1 -0
- package/dist/builder-texts.test.d.ts +2 -0
- package/dist/builder-texts.test.d.ts.map +1 -0
- package/dist/builder-texts.test.js +32 -0
- package/dist/builder-texts.test.js.map +1 -0
- package/dist/definition.d.ts +3 -0
- package/dist/definition.d.ts.map +1 -0
- package/dist/definition.js +151 -0
- package/dist/definition.js.map +1 -0
- package/dist/engine-ids.d.ts +42 -0
- package/dist/engine-ids.d.ts.map +1 -0
- package/dist/engine-ids.js +50 -0
- package/dist/engine-ids.js.map +1 -0
- package/dist/engine-scope.test.d.ts +2 -0
- package/dist/engine-scope.test.d.ts.map +1 -0
- package/dist/engine-scope.test.js +260 -0
- package/dist/engine-scope.test.js.map +1 -0
- package/dist/heatmap-data.d.ts +15 -0
- package/dist/heatmap-data.d.ts.map +1 -0
- package/dist/heatmap-data.js +2 -0
- package/dist/heatmap-data.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -0
- package/dist/instructions.d.ts +8 -0
- package/dist/instructions.d.ts.map +1 -0
- package/dist/instructions.js +33 -0
- package/dist/instructions.js.map +1 -0
- package/dist/messages/de.ask-ai.d.ts +9 -0
- package/dist/messages/de.ask-ai.d.ts.map +1 -0
- package/dist/messages/de.ask-ai.js +18 -0
- package/dist/messages/de.ask-ai.js.map +1 -0
- package/dist/messages/de.builder.d.ts +12 -0
- package/dist/messages/de.builder.d.ts.map +1 -0
- package/dist/messages/de.builder.js +53 -0
- package/dist/messages/de.builder.js.map +1 -0
- package/dist/messages/de.server.d.ts +8 -0
- package/dist/messages/de.server.d.ts.map +1 -0
- package/dist/messages/de.server.js +26 -0
- package/dist/messages/de.server.js.map +1 -0
- package/dist/messages/de.sweep.d.ts +10 -0
- package/dist/messages/de.sweep.d.ts.map +1 -0
- package/dist/messages/de.sweep.js +203 -0
- package/dist/messages/de.sweep.js.map +1 -0
- package/dist/messages/en.ask-ai.d.ts +25 -0
- package/dist/messages/en.ask-ai.d.ts.map +1 -0
- package/dist/messages/en.ask-ai.js +24 -0
- package/dist/messages/en.ask-ai.js.map +1 -0
- package/dist/messages/en.builder.d.ts +12 -0
- package/dist/messages/en.builder.d.ts.map +1 -0
- package/dist/messages/en.builder.js +53 -0
- package/dist/messages/en.builder.js.map +1 -0
- package/dist/messages/en.server.d.ts +8 -0
- package/dist/messages/en.server.d.ts.map +1 -0
- package/dist/messages/en.server.js +26 -0
- package/dist/messages/en.server.js.map +1 -0
- package/dist/messages/en.sweep.d.ts +11 -0
- package/dist/messages/en.sweep.d.ts.map +1 -0
- package/dist/messages/en.sweep.js +204 -0
- package/dist/messages/en.sweep.js.map +1 -0
- package/dist/messages/glossary.d.ts +26 -0
- package/dist/messages/glossary.d.ts.map +1 -0
- package/dist/messages/glossary.js +37 -0
- package/dist/messages/glossary.js.map +1 -0
- package/dist/messages/glossary.test.d.ts +2 -0
- package/dist/messages/glossary.test.d.ts.map +1 -0
- package/dist/messages/glossary.test.js +109 -0
- package/dist/messages/glossary.test.js.map +1 -0
- package/dist/messages/index.d.ts +12 -0
- package/dist/messages/index.d.ts.map +1 -0
- package/dist/messages/index.js +23 -0
- package/dist/messages/index.js.map +1 -0
- package/dist/messages/plural.d.ts +10 -0
- package/dist/messages/plural.d.ts.map +1 -0
- package/dist/messages/plural.js +17 -0
- package/dist/messages/plural.js.map +1 -0
- package/dist/messages/use-t.d.ts +10 -0
- package/dist/messages/use-t.d.ts.map +1 -0
- package/dist/messages/use-t.js +10 -0
- package/dist/messages/use-t.js.map +1 -0
- package/dist/module.d.ts +48 -0
- package/dist/module.d.ts.map +1 -0
- package/dist/module.js +174 -0
- package/dist/module.js.map +1 -0
- package/dist/module.test.d.ts +2 -0
- package/dist/module.test.d.ts.map +1 -0
- package/dist/module.test.js +230 -0
- package/dist/module.test.js.map +1 -0
- package/dist/named-views.d.ts +27 -0
- package/dist/named-views.d.ts.map +1 -0
- package/dist/named-views.js +73 -0
- package/dist/named-views.js.map +1 -0
- package/dist/named-views.test.d.ts +2 -0
- package/dist/named-views.test.d.ts.map +1 -0
- package/dist/named-views.test.js +110 -0
- package/dist/named-views.test.js.map +1 -0
- package/dist/plugin.d.ts +44 -0
- package/dist/plugin.d.ts.map +1 -0
- package/dist/plugin.js +52 -0
- package/dist/plugin.js.map +1 -0
- package/dist/prometheus-wiring.test.d.ts +2 -0
- package/dist/prometheus-wiring.test.d.ts.map +1 -0
- package/dist/prometheus-wiring.test.js +142 -0
- package/dist/prometheus-wiring.test.js.map +1 -0
- package/dist/server-locale.d.ts +48 -0
- package/dist/server-locale.d.ts.map +1 -0
- package/dist/server-locale.js +33 -0
- package/dist/server-locale.js.map +1 -0
- package/dist/settings-tools.d.ts +23 -0
- package/dist/settings-tools.d.ts.map +1 -0
- package/dist/settings-tools.js +100 -0
- package/dist/settings-tools.js.map +1 -0
- package/dist/settings.d.ts +77 -0
- package/dist/settings.d.ts.map +1 -0
- package/dist/settings.js +99 -0
- package/dist/settings.js.map +1 -0
- package/dist/settings.test.d.ts +2 -0
- package/dist/settings.test.d.ts.map +1 -0
- package/dist/settings.test.js +299 -0
- package/dist/settings.test.js.map +1 -0
- package/dist/steps/app-config.d.ts +30 -0
- package/dist/steps/app-config.d.ts.map +1 -0
- package/dist/steps/app-config.js +46 -0
- package/dist/steps/app-config.js.map +1 -0
- package/dist/steps/dashboard.d.ts +12 -0
- package/dist/steps/dashboard.d.ts.map +1 -0
- package/dist/steps/dashboard.js +58 -0
- package/dist/steps/dashboard.js.map +1 -0
- package/dist/steps/failure-dashboard.d.ts +10 -0
- package/dist/steps/failure-dashboard.d.ts.map +1 -0
- package/dist/steps/failure-dashboard.js +32 -0
- package/dist/steps/failure-dashboard.js.map +1 -0
- package/dist/steps/index.d.ts +3 -0
- package/dist/steps/index.d.ts.map +1 -0
- package/dist/steps/index.js +3 -0
- package/dist/steps/index.js.map +1 -0
- package/dist/steps/steps.test.d.ts +2 -0
- package/dist/steps/steps.test.d.ts.map +1 -0
- package/dist/steps/steps.test.js +153 -0
- package/dist/steps/steps.test.js.map +1 -0
- package/dist/tool-names.d.ts +13 -0
- package/dist/tool-names.d.ts.map +1 -0
- package/dist/tool-names.js +27 -0
- package/dist/tool-names.js.map +1 -0
- package/dist/tools/cluster-compare.d.ts +8 -0
- package/dist/tools/cluster-compare.d.ts.map +1 -0
- package/dist/tools/cluster-compare.js +17 -0
- package/dist/tools/cluster-compare.js.map +1 -0
- package/dist/tools/element.d.ts +8 -0
- package/dist/tools/element.d.ts.map +1 -0
- package/dist/tools/element.js +25 -0
- package/dist/tools/element.js.map +1 -0
- package/dist/tools/engine-compare.d.ts +8 -0
- package/dist/tools/engine-compare.d.ts.map +1 -0
- package/dist/tools/engine-compare.js +18 -0
- package/dist/tools/engine-compare.js.map +1 -0
- package/dist/tools/engine-compare.test.d.ts +2 -0
- package/dist/tools/engine-compare.test.d.ts.map +1 -0
- package/dist/tools/engine-compare.test.js +166 -0
- package/dist/tools/engine-compare.test.js.map +1 -0
- package/dist/tools/engine-landscape.d.ts +7 -0
- package/dist/tools/engine-landscape.d.ts.map +1 -0
- package/dist/tools/engine-landscape.js +13 -0
- package/dist/tools/engine-landscape.js.map +1 -0
- package/dist/tools/engine-landscape.test.d.ts +2 -0
- package/dist/tools/engine-landscape.test.d.ts.map +1 -0
- package/dist/tools/engine-landscape.test.js +110 -0
- package/dist/tools/engine-landscape.test.js.map +1 -0
- package/dist/tools/failures.d.ts +7 -0
- package/dist/tools/failures.d.ts.map +1 -0
- package/dist/tools/failures.js +13 -0
- package/dist/tools/failures.js.map +1 -0
- package/dist/tools/health.d.ts +7 -0
- package/dist/tools/health.d.ts.map +1 -0
- package/dist/tools/health.js +23 -0
- package/dist/tools/health.js.map +1 -0
- package/dist/tools/health.test.d.ts +2 -0
- package/dist/tools/health.test.d.ts.map +1 -0
- package/dist/tools/health.test.js +119 -0
- package/dist/tools/health.test.js.map +1 -0
- package/dist/tools/index.d.ts +18 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +35 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/performance.d.ts +8 -0
- package/dist/tools/performance.d.ts.map +1 -0
- package/dist/tools/performance.js +25 -0
- package/dist/tools/performance.js.map +1 -0
- package/dist/tools/performance.test.d.ts +2 -0
- package/dist/tools/performance.test.d.ts.map +1 -0
- package/dist/tools/performance.test.js +176 -0
- package/dist/tools/performance.test.js.map +1 -0
- package/dist/tools/version-compare.d.ts +8 -0
- package/dist/tools/version-compare.d.ts.map +1 -0
- package/dist/tools/version-compare.js +17 -0
- package/dist/tools/version-compare.js.map +1 -0
- package/dist/tools/version-compare.test.d.ts +2 -0
- package/dist/tools/version-compare.test.d.ts.map +1 -0
- package/dist/tools/version-compare.test.js +82 -0
- package/dist/tools/version-compare.test.js.map +1 -0
- package/dist/toolsets.d.ts +17 -0
- package/dist/toolsets.d.ts.map +1 -0
- package/dist/toolsets.js +47 -0
- package/dist/toolsets.js.map +1 -0
- package/dist/toolsets.test.d.ts +2 -0
- package/dist/toolsets.test.d.ts.map +1 -0
- package/dist/toolsets.test.js +91 -0
- package/dist/toolsets.test.js.map +1 -0
- package/dist/view-title.test.d.ts +2 -0
- package/dist/view-title.test.d.ts.map +1 -0
- package/dist/view-title.test.js +91 -0
- package/dist/view-title.test.js.map +1 -0
- package/dist/widget-tools/comparisons.d.ts +13 -0
- package/dist/widget-tools/comparisons.d.ts.map +1 -0
- package/dist/widget-tools/comparisons.js +177 -0
- package/dist/widget-tools/comparisons.js.map +1 -0
- package/dist/widget-tools/comparisons.test.d.ts +2 -0
- package/dist/widget-tools/comparisons.test.d.ts.map +1 -0
- package/dist/widget-tools/comparisons.test.js +123 -0
- package/dist/widget-tools/comparisons.test.js.map +1 -0
- package/dist/widget-tools/shared.d.ts +36 -0
- package/dist/widget-tools/shared.d.ts.map +1 -0
- package/dist/widget-tools/shared.js +39 -0
- package/dist/widget-tools/shared.js.map +1 -0
- package/dist/widget-tools.d.ts +25 -0
- package/dist/widget-tools.d.ts.map +1 -0
- package/dist/widget-tools.js +269 -0
- package/dist/widget-tools.js.map +1 -0
- package/dist/widget-tools.test.d.ts +2 -0
- package/dist/widget-tools.test.d.ts.map +1 -0
- package/dist/widget-tools.test.js +272 -0
- package/dist/widget-tools.test.js.map +1 -0
- package/package.json +91 -5
- package/src/bpmn-name-lookup.test.ts +333 -0
- package/src/bpmn-name-lookup.ts +197 -0
- package/src/bpmn-names.ts +51 -0
- package/src/brand-gates.test.ts +69 -0
- package/src/builder-texts.test.ts +40 -0
- package/src/builder-texts.ts +15 -0
- package/src/definition.ts +181 -0
- package/src/engine-ids.ts +85 -0
- package/src/engine-scope.test.ts +324 -0
- package/src/heatmap-data.ts +14 -0
- package/src/index.ts +4 -0
- package/src/instructions.ts +33 -0
- package/src/messages/GLOSSARY.md +88 -0
- package/src/messages/de.ask-ai.ts +28 -0
- package/src/messages/de.builder.ts +77 -0
- package/src/messages/de.server.ts +39 -0
- package/src/messages/de.sweep.ts +244 -0
- package/src/messages/en.ask-ai.ts +34 -0
- package/src/messages/en.builder.ts +76 -0
- package/src/messages/en.server.ts +39 -0
- package/src/messages/en.sweep.ts +240 -0
- package/src/messages/glossary.test.ts +122 -0
- package/src/messages/glossary.ts +62 -0
- package/src/messages/index.ts +24 -0
- package/src/messages/plural.ts +18 -0
- package/src/messages/use-t.ts +12 -0
- package/src/module.test.ts +319 -0
- package/src/module.ts +211 -0
- package/src/named-views.test.ts +141 -0
- package/src/named-views.ts +97 -0
- package/src/plugin.ts +94 -0
- package/src/prometheus-wiring.test.ts +174 -0
- package/src/server-locale.ts +59 -0
- package/src/settings-tools.ts +170 -0
- package/src/settings.test.ts +402 -0
- package/src/settings.ts +114 -0
- package/src/steps/app-config.ts +75 -0
- package/src/steps/dashboard.ts +73 -0
- package/src/steps/failure-dashboard.ts +38 -0
- package/src/steps/index.ts +2 -0
- package/src/steps/steps.test.ts +216 -0
- package/src/tool-names.ts +26 -0
- package/src/tools/cluster-compare.ts +28 -0
- package/src/tools/element.ts +35 -0
- package/src/tools/engine-compare.test.ts +210 -0
- package/src/tools/engine-compare.ts +30 -0
- package/src/tools/engine-landscape.test.ts +138 -0
- package/src/tools/engine-landscape.ts +21 -0
- package/src/tools/failures.ts +19 -0
- package/src/tools/health.test.ts +156 -0
- package/src/tools/health.ts +29 -0
- package/src/tools/index.ts +44 -0
- package/src/tools/performance.test.ts +210 -0
- package/src/tools/performance.ts +38 -0
- package/src/tools/version-compare.test.ts +97 -0
- package/src/tools/version-compare.ts +28 -0
- package/src/toolsets.test.ts +107 -0
- package/src/toolsets.ts +56 -0
- package/src/view-title.test.ts +123 -0
- package/src/widget-tools/comparisons.test.ts +170 -0
- package/src/widget-tools/comparisons.ts +229 -0
- package/src/widget-tools/shared.ts +67 -0
- package/src/widget-tools.test.ts +306 -0
- package/src/widget-tools.ts +375 -0
- package/src/widgets/analytics-dashboard/activity-bottleneck-table.tsx +179 -0
- package/src/widgets/analytics-dashboard/definition-breakdown.tsx +108 -0
- package/src/widgets/analytics-dashboard/execution-performance-kpi.tsx +94 -0
- package/src/widgets/analytics-dashboard/execution-summary-kpi.tsx +122 -0
- package/src/widgets/analytics-dashboard/lib.ts +43 -0
- package/src/widgets/bpmn-heatmap.test.tsx +75 -0
- package/src/widgets/bpmn-heatmap.tsx +127 -0
- package/src/widgets/catalogue-sync.test.ts +16 -0
- package/src/widgets/cluster-compare.tsx +148 -0
- package/src/widgets/comparison-shared.test.ts +237 -0
- package/src/widgets/comparison-shared.tsx +371 -0
- package/src/widgets/comparison-widgets.test.tsx +307 -0
- package/src/widgets/dashboard-widgets.test.tsx +397 -0
- package/src/widgets/engine-compare.tsx +87 -0
- package/src/widgets/engine-landscape.tsx +352 -0
- package/src/widgets/failure-dashboard/error-patterns-table.tsx +122 -0
- package/src/widgets/failure-dashboard/failure-rate-table.test.ts +80 -0
- package/src/widgets/failure-dashboard/failure-rate-table.tsx +170 -0
- package/src/widgets/failure-dashboard/lib.ts +32 -0
- package/src/widgets/failure-dashboard/summary-kpi.tsx +122 -0
- package/src/widgets/failure-landscape-widgets.test.tsx +393 -0
- package/src/widgets/figure.tsx +79 -0
- package/src/widgets/hand-off.test.ts +126 -0
- package/src/widgets/hand-off.ts +102 -0
- package/src/widgets/index.ts +124 -0
- package/src/widgets/model-descriptions.dashboard.test.ts +280 -0
- package/src/widgets/model-descriptions.test.ts +251 -0
- package/src/widgets/model-descriptions.ts +428 -0
- package/src/widgets/names.test.ts +32 -0
- package/src/widgets/names.ts +39 -0
- package/src/widgets/query-gate.test.tsx +57 -0
- package/src/widgets/query-gate.tsx +50 -0
- package/src/widgets/settings-section.test.tsx +174 -0
- package/src/widgets/settings-section.tsx +244 -0
- package/src/widgets/version-compare.test.ts +68 -0
- package/src/widgets/version-compare.tsx +96 -0
- package/src/widgets/view-meta.ts +18 -0
- package/src/widgets/widget-render.test-support.tsx +103 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Miragon GmbH
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,5 +1,88 @@
|
|
|
1
1
|
# @miragon/pulse-analytics-connector
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
The **analytics module** for [Pulse](../../../../README.md): Prometheus-backed process analytics as
|
|
4
|
+
`analytics_*` MCP tools, plus dashboard and comparison widgets. Built on
|
|
5
|
+
[`@miragon/pulse-analytics-client`](../analytics-client) and the `@miragon/mcp-toolkit-*` packages.
|
|
6
|
+
|
|
7
|
+
The server loads this module as `analytics` via `analyticsModule` (`src/module.ts`), which conforms
|
|
8
|
+
structurally to the app's `ModuleDefinition` port. The package is published to npm in lockstep with
|
|
9
|
+
the other `@miragon/pulse-*` packages (pin them all to one version); the
|
|
10
|
+
[pulse-starter](https://github.com/Miragon/pulse-starter) template shows how to compose it
|
|
11
|
+
into your own MCP server.
|
|
12
|
+
|
|
13
|
+
The consumer-shared libraries are ranged **peer dependencies** so they dedupe against your app's
|
|
14
|
+
copy: `react`/`react-dom` `^19.2.0`, `zod` `^4.4.0`, `@miragon/mcp-toolkit-*` `~2.8.1`,
|
|
15
|
+
`lucide-react` `^0.562.0 || ^1.0.0` (the widgets' icons; add it to your `dedupe` list like the
|
|
16
|
+
widget-shell kit asks, and pin 1.47.0 or newer: the toolkit's chrome (2.7 and later) imports `LucideProvider`,
|
|
17
|
+
which 0.x lacks, so a deduped 0.x copy fails the bundle build). **`mcp-use` is exactly pinned**:
|
|
18
|
+
pin it to `mcp-use@2.7.3` in your app. A duplicate `mcp-use` instance breaks the React context and
|
|
19
|
+
hangs every in-widget query on "Loading…".
|
|
20
|
+
|
|
21
|
+
## What it provides
|
|
22
|
+
|
|
23
|
+
- **Analytics tools** (`src/tools/`): `analyze_process_performance`, `compare_execution_periods`,
|
|
24
|
+
`element_bottleneck`, `find_failed_instances`, `cluster_compare`, `version_compare`,
|
|
25
|
+
`engine_landscape`, `engine_compare`, and `engine_health` (a live WIP / incidents / backlog /
|
|
26
|
+
alerts snapshot). All carry the `analytics` category and run PromQL through
|
|
27
|
+
[`@miragon/pulse-analytics-client`](../analytics-client).
|
|
28
|
+
- **Widgets** (`src/widgets/`): `show_dashboard`, `show_failure_dashboard`, `show_cluster_compare`,
|
|
29
|
+
`show_version_compare`, `show_engine_landscape`, `show_engine_compare`, `show_bpmn_heatmap`.
|
|
30
|
+
- **Configured engines only:** analytics reads exactly the server's configured engine ids:
|
|
31
|
+
`createPlugin({ engineIds })` (a composed server injects camunda7's engines through
|
|
32
|
+
`analyticsModule`'s shared `engineIds`), else `ANALYTICS_ENGINE_IDS` for a standalone boot.
|
|
33
|
+
Without either, every analytics tool refuses (fail-closed) instead of reading every `engine_id`
|
|
34
|
+
a shared Prometheus holds. Every tool takes an optional `engine` filter (one configured id or a
|
|
35
|
+
list); omitted, it covers all configured engines and the result's `engines` names them; any
|
|
36
|
+
other id is refused. Analytics are session-independent.
|
|
37
|
+
- **Toolsets** (`src/toolsets.ts`, declared as the module's `toolsets` vocabulary): `read-only`
|
|
38
|
+
(every analytics tool and widget, no settings save) and `standard` (adds
|
|
39
|
+
`analytics_save_settings`). With no `analytics:<toolset>` suffix the module runs `read-only` on an
|
|
40
|
+
unauthenticated server and `standard` under OAuth; an empty or unknown suffix falls back to
|
|
41
|
+
`read-only`.
|
|
42
|
+
|
|
43
|
+
### Cross-engine: overview, not scoreboard
|
|
44
|
+
|
|
45
|
+
Engines host different process definitions, so a failure rate, incident rate or duration aggregated
|
|
46
|
+
per engine describes that engine's **process mix**, not the engine. The same aggregation can even
|
|
47
|
+
invert the per-process truth. Two consequences run through the module:
|
|
48
|
+
|
|
49
|
+
- `engine_landscape` is the cross-engine view. It reports the process × engine inventory, absolute
|
|
50
|
+
counts (running instances, open incidents, failed jobs) and the engine-owned job backlog
|
|
51
|
+
(`jobs_executable`, `jobs_suspended`, `jobs_due_future`, `external_tasks_open`), the one metric
|
|
52
|
+
family whose contract labels are `engine_id` only, so no process mix can confound it. It covers
|
|
53
|
+
every configured engine, so one that reports nothing still gets a row (`reporting: false`).
|
|
54
|
+
- `engine_compare` **requires** a `processDefinitionKey`. Holding the process fixed on both sides is
|
|
55
|
+
what makes a delta attributable to the engine; `engine_landscape.sharedProcessKeys` lists the
|
|
56
|
+
definitions that qualify.
|
|
57
|
+
|
|
58
|
+
These tools read the `camunda_*` series emitted by the
|
|
59
|
+
[engine metrics plugin](../../../../engine-plugins). Per-instance drill-down (search by variable) is **not**
|
|
60
|
+
metric-backed: use the `camunda7_query_historic_*` tools in the [camunda7 module](../../camunda/camunda7-connector).
|
|
61
|
+
|
|
62
|
+
The BPMN heatmap gets its diagram XML via an injected `fetchBpmnXml` shared resource (wired by the
|
|
63
|
+
host app from the camunda7 module), and the dashboards, the failure dashboard and the landscape read
|
|
64
|
+
the process and activity names behind their ids through the same lookup (`bpmn-name-lookup.ts`).
|
|
65
|
+
This package has **no** engine-SDK dependency: the heatmap
|
|
66
|
+
degrades gracefully without it and the views show ids. `fetchBpmnXml(key, engineId?)` reads on the
|
|
67
|
+
named engine (the default one without), resolves `null` for a key not deployed there and rejects a
|
|
68
|
+
failed read; the views ask the engines their rows come from, and the names are cached per caller
|
|
69
|
+
(under passthrough auth the engine answers with each caller's own permissions).
|
|
70
|
+
|
|
71
|
+
## Adding an analytics capability
|
|
72
|
+
|
|
73
|
+
Read the [`add-analytics-feature`](../../../../.claude/skills/add-analytics-feature) skill first. The house
|
|
74
|
+
pattern: add a PromQL query function in [`@miragon/pulse-analytics-client`](../analytics-client) (using
|
|
75
|
+
`METRIC_NAMES`, never raw metric strings), then a tool here, then optionally a widget. Metric names
|
|
76
|
+
and labels are governed by the
|
|
77
|
+
[metrics contract](../analytics-client/metrics-contract.json). Change it there first.
|
|
78
|
+
|
|
79
|
+
## Layout
|
|
80
|
+
|
|
81
|
+
| Path | Contents |
|
|
82
|
+
| --------------------- | ----------------------------------------------------------- |
|
|
83
|
+
| `src/tools/` | Analytics tools (`index.ts` wires them) |
|
|
84
|
+
| `src/widget-tools.ts` | `show_*` dashboard/comparison widget tools |
|
|
85
|
+
| `src/widgets/` | React dashboard + comparison widgets |
|
|
86
|
+
| `src/definition.ts` | Widget metadata |
|
|
87
|
+
| `src/module.ts` | `analyticsModule` (env mapping, `PROMETHEUS_URL` boot hint) |
|
|
88
|
+
| `src/steps/` | Pipeline steps contributed to the server |
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import type { NameIndex } from "./bpmn-names.js";
|
|
2
|
+
/**
|
|
3
|
+
* Engine-agnostic BPMN-XML lookup injected by the host app (which owns the
|
|
4
|
+
* engine clients) — this module never talks to an engine itself. Resolves the
|
|
5
|
+
* diagram XML of a process definition key's latest deployed version on the
|
|
6
|
+
* engine `engineId` names (omitted: the host's default engine). `null` is an
|
|
7
|
+
* answer: that engine has no such definition. A read that failed
|
|
8
|
+
* (unreachable, refused, timed out) REJECTS, so a consumer tells "not there"
|
|
9
|
+
* from "not now" and never remembers a failure as an answer.
|
|
10
|
+
*/
|
|
11
|
+
export type FetchBpmnXml = (processDefinitionKey: string, engineId?: string) => Promise<string | null>;
|
|
12
|
+
/** What one view asks the lookup for, besides its keys. */
|
|
13
|
+
export interface NameRequest {
|
|
14
|
+
/** Per key, the elements whose names the view shows; a key left out gets every named element. */
|
|
15
|
+
elementIds?: Readonly<Record<string, readonly string[]>>;
|
|
16
|
+
/**
|
|
17
|
+
* Per key, the engines it runs on, in the order to read them (the
|
|
18
|
+
* landscape knows them per process). Preferred over {@link NameRequest.engines}.
|
|
19
|
+
*/
|
|
20
|
+
enginesByKey?: Readonly<Record<string, readonly string[]>>;
|
|
21
|
+
/**
|
|
22
|
+
* The view's engines, in the order to read them, for a key without a list
|
|
23
|
+
* of its own: the first whose model has the key names it. None: the host's
|
|
24
|
+
* default engine.
|
|
25
|
+
*/
|
|
26
|
+
engines?: readonly string[] | null;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Names for the process keys of one view: a {@link NameIndex} holding the keys
|
|
30
|
+
* whose model could be read on one of their engines, each with the elements
|
|
31
|
+
* the request asks for.
|
|
32
|
+
*/
|
|
33
|
+
export type NameLookup = (keys: Iterable<string>, request?: NameRequest) => Promise<NameIndex>;
|
|
34
|
+
/** A lookup that knows no names: what a server without the camunda7 module has. */
|
|
35
|
+
export declare const NO_NAMES: NameLookup;
|
|
36
|
+
export interface NameLookupOptions {
|
|
37
|
+
/**
|
|
38
|
+
* How long one view waits for its names (ms), over every key and engine.
|
|
39
|
+
* Names are enrichment: a slow engine must not hold up the figures. A read
|
|
40
|
+
* still running then finishes for the next view.
|
|
41
|
+
*/
|
|
42
|
+
timeoutMs?: number;
|
|
43
|
+
/** How long an engine's answer is reused (ms): diagrams change with a deployment, not per call. */
|
|
44
|
+
ttlMs?: number;
|
|
45
|
+
/** The most keys one view looks up, in the order it names them; the rest show their ids. */
|
|
46
|
+
maxKeys?: number;
|
|
47
|
+
/** The most answers kept; the oldest go first. */
|
|
48
|
+
maxEntries?: number;
|
|
49
|
+
/**
|
|
50
|
+
* Whose engine permissions a read runs under: the partition of the cache,
|
|
51
|
+
* so one caller's answer never serves another. Default: the request's
|
|
52
|
+
* caller and credential (`callerCacheKey`, widget-shell). With passthrough
|
|
53
|
+
* auth every engine read carries the caller's own token and the engine
|
|
54
|
+
* answers with THAT caller's permissions: a model one caller may read must
|
|
55
|
+
* never reach another through the cache, and a caller the engine refused
|
|
56
|
+
* must not hide the names from everyone else.
|
|
57
|
+
*/
|
|
58
|
+
caller?: () => string;
|
|
59
|
+
/** Clock, for tests. */
|
|
60
|
+
now?: () => number;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* The view's name lookup over the injected BPMN lookup, or {@link NO_NAMES}
|
|
64
|
+
* without one. Reads a key's model on its engines in order until one has it.
|
|
65
|
+
* Each engine's answer is kept per caller for `ttlMs` (a "no such model"
|
|
66
|
+
* too: a key an engine does not know stays unknown there that long); a failed
|
|
67
|
+
* read is not kept, so the next view asks again. Never waits longer than
|
|
68
|
+
* `timeoutMs` and never fails: a key without an answer is left out, and the
|
|
69
|
+
* view shows its id.
|
|
70
|
+
*/
|
|
71
|
+
export declare function createNameLookup(fetchBpmnXml: FetchBpmnXml | undefined, options?: NameLookupOptions): NameLookup;
|
|
72
|
+
//# sourceMappingURL=bpmn-name-lookup.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bpmn-name-lookup.d.ts","sourceRoot":"","sources":["../src/bpmn-name-lookup.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAgB,MAAM,iBAAiB,CAAA;AAE9D;;;;;;;;GAQG;AACH,MAAM,MAAM,YAAY,GAAG,CACzB,oBAAoB,EAAE,MAAM,EAC5B,QAAQ,CAAC,EAAE,MAAM,KACd,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAA;AAE3B,2DAA2D;AAC3D,MAAM,WAAW,WAAW;IAC1B,iGAAiG;IACjG,UAAU,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC,CAAC,CAAA;IACxD;;;OAGG;IACH,YAAY,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC,CAAC,CAAA;IAC1D;;;;OAIG;IACH,OAAO,CAAC,EAAE,SAAS,MAAM,EAAE,GAAG,IAAI,CAAA;CACnC;AAED;;;;GAIG;AACH,MAAM,MAAM,UAAU,GAAG,CAAC,IAAI,EAAE,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC,EAAE,WAAW,KAAK,OAAO,CAAC,SAAS,CAAC,CAAA;AAE9F,mFAAmF;AACnF,eAAO,MAAM,QAAQ,EAAE,UAAsC,CAAA;AAE7D,MAAM,WAAW,iBAAiB;IAChC;;;;OAIG;IACH,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,mGAAmG;IACnG,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,4FAA4F;IAC5F,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,kDAAkD;IAClD,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB;;;;;;;;OAQG;IACH,MAAM,CAAC,EAAE,MAAM,MAAM,CAAA;IACrB,wBAAwB;IACxB,GAAG,CAAC,EAAE,MAAM,MAAM,CAAA;CACnB;AAUD;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,CAC9B,YAAY,EAAE,YAAY,GAAG,SAAS,EACtC,OAAO,GAAE,iBAAsB,GAC9B,UAAU,CAqEZ"}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { callerCacheKey, parseBpmnNames } from "@miragon/pulse-widget-shell/server";
|
|
2
|
+
/** A lookup that knows no names: what a server without the camunda7 module has. */
|
|
3
|
+
export const NO_NAMES = () => Promise.resolve({});
|
|
4
|
+
const DEFAULT_TIMEOUT_MS = 2_000;
|
|
5
|
+
const DEFAULT_TTL_MS = 5 * 60_000;
|
|
6
|
+
const DEFAULT_MAX_KEYS = 40;
|
|
7
|
+
const DEFAULT_MAX_ENTRIES = 2_000;
|
|
8
|
+
/**
|
|
9
|
+
* The view's name lookup over the injected BPMN lookup, or {@link NO_NAMES}
|
|
10
|
+
* without one. Reads a key's model on its engines in order until one has it.
|
|
11
|
+
* Each engine's answer is kept per caller for `ttlMs` (a "no such model"
|
|
12
|
+
* too: a key an engine does not know stays unknown there that long); a failed
|
|
13
|
+
* read is not kept, so the next view asks again. Never waits longer than
|
|
14
|
+
* `timeoutMs` and never fails: a key without an answer is left out, and the
|
|
15
|
+
* view shows its id.
|
|
16
|
+
*/
|
|
17
|
+
export function createNameLookup(fetchBpmnXml, options = {}) {
|
|
18
|
+
if (!fetchBpmnXml)
|
|
19
|
+
return NO_NAMES;
|
|
20
|
+
const fetchXml = fetchBpmnXml;
|
|
21
|
+
const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
22
|
+
const ttlMs = options.ttlMs ?? DEFAULT_TTL_MS;
|
|
23
|
+
const maxKeys = options.maxKeys ?? DEFAULT_MAX_KEYS;
|
|
24
|
+
const maxEntries = options.maxEntries ?? DEFAULT_MAX_ENTRIES;
|
|
25
|
+
const now = options.now ?? Date.now;
|
|
26
|
+
const callerOf = options.caller ?? callerCacheKey;
|
|
27
|
+
/** (caller, engine, key) → that engine's answer for that caller, oldest first. */
|
|
28
|
+
const cache = new Map();
|
|
29
|
+
function remember(id, names) {
|
|
30
|
+
// Re-inserted at the end, so the map stays oldest first.
|
|
31
|
+
cache.delete(id);
|
|
32
|
+
cache.set(id, { at: now(), names });
|
|
33
|
+
for (const oldest of cache.keys()) {
|
|
34
|
+
if (cache.size <= maxEntries)
|
|
35
|
+
break;
|
|
36
|
+
cache.delete(oldest);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
function readOn(caller, engine, key) {
|
|
40
|
+
const id = JSON.stringify([caller, engine ?? null, key]);
|
|
41
|
+
const hit = cache.get(id);
|
|
42
|
+
if (hit && now() - hit.at < ttlMs)
|
|
43
|
+
return Promise.resolve(hit.names);
|
|
44
|
+
return Promise.resolve()
|
|
45
|
+
.then(() => fetchXml(key, engine))
|
|
46
|
+
.then((xml) => {
|
|
47
|
+
const names = xml ? parseBpmnNames(xml, key) : null;
|
|
48
|
+
remember(id, names);
|
|
49
|
+
return names;
|
|
50
|
+
},
|
|
51
|
+
// A failed read is no answer: not kept, the next view asks again.
|
|
52
|
+
() => undefined);
|
|
53
|
+
}
|
|
54
|
+
return async (keys, request = {}) => {
|
|
55
|
+
const caller = callerOf();
|
|
56
|
+
const unique = [...new Set(keys)].filter((key) => key.length > 0).slice(0, maxKeys);
|
|
57
|
+
const deadline = startDeadline(timeoutMs);
|
|
58
|
+
async function namesOf(key) {
|
|
59
|
+
for (const engine of enginesFor(key, request)) {
|
|
60
|
+
const names = await Promise.race([readOn(caller, engine, key), deadline.passed]);
|
|
61
|
+
if (names)
|
|
62
|
+
return names;
|
|
63
|
+
if (deadline.expired())
|
|
64
|
+
break;
|
|
65
|
+
}
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
try {
|
|
69
|
+
const reads = await Promise.all(unique.map(async (key) => [key, await namesOf(key)]));
|
|
70
|
+
const index = {};
|
|
71
|
+
for (const [key, names] of reads) {
|
|
72
|
+
if (!names)
|
|
73
|
+
continue;
|
|
74
|
+
const wanted = own(request.elementIds, key);
|
|
75
|
+
index[key] = {
|
|
76
|
+
name: names.name,
|
|
77
|
+
elements: wanted ? pick(names.elements, wanted) : names.elements,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
return index;
|
|
81
|
+
}
|
|
82
|
+
finally {
|
|
83
|
+
deadline.clear();
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
/** `record[key]` when the record itself holds it: a key such as `constructor` is no inherited lookup. */
|
|
88
|
+
function own(record, key) {
|
|
89
|
+
return record && Object.hasOwn(record, key) ? record[key] : undefined;
|
|
90
|
+
}
|
|
91
|
+
/** The engines to read `key`'s model on, in order: its own, else the view's, else the default engine. */
|
|
92
|
+
function enginesFor(key, request) {
|
|
93
|
+
const mine = own(request.enginesByKey, key);
|
|
94
|
+
if (mine && mine.length > 0)
|
|
95
|
+
return mine;
|
|
96
|
+
if (request.engines && request.engines.length > 0)
|
|
97
|
+
return request.engines;
|
|
98
|
+
return [undefined];
|
|
99
|
+
}
|
|
100
|
+
/** The entries of `elements` named in `ids` (what the view shows, nothing else). */
|
|
101
|
+
function pick(elements, ids) {
|
|
102
|
+
const out = {};
|
|
103
|
+
for (const id of ids)
|
|
104
|
+
if (Object.hasOwn(elements, id))
|
|
105
|
+
out[id] = elements[id];
|
|
106
|
+
return out;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* One view's time budget: `passed` resolves `undefined` once `ms` passed, and
|
|
110
|
+
* `clear` stops the timer, so a fast view leaves nothing pending.
|
|
111
|
+
*/
|
|
112
|
+
function startDeadline(ms) {
|
|
113
|
+
let timer;
|
|
114
|
+
let expired = false;
|
|
115
|
+
const passed = new Promise((resolve) => {
|
|
116
|
+
timer = setTimeout(() => {
|
|
117
|
+
expired = true;
|
|
118
|
+
resolve(undefined);
|
|
119
|
+
}, ms);
|
|
120
|
+
});
|
|
121
|
+
return { passed, expired: () => expired, clear: () => clearTimeout(timer) };
|
|
122
|
+
}
|
|
123
|
+
//# sourceMappingURL=bpmn-name-lookup.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bpmn-name-lookup.js","sourceRoot":"","sources":["../src/bpmn-name-lookup.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bpmn-name-lookup.test.d.ts","sourceRoot":"","sources":["../src/bpmn-name-lookup.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
2
|
+
import { getMcpRequestInfo, runWithMcpRequestInfo } from "@miragon/pulse-widget-shell/server";
|
|
3
|
+
import { NO_NAMES, createNameLookup } from "./bpmn-name-lookup.js";
|
|
4
|
+
import { elementNameOf, processNameOf } from "./bpmn-names.js";
|
|
5
|
+
/**
|
|
6
|
+
* The names behind the ids (U13): read on the server from the diagram the
|
|
7
|
+
* injected BPMN lookup returns, never a guess, never a failure. The reader
|
|
8
|
+
* itself is the kit's (`parseBpmnNames`, widget-shell's `bpmn-names.test.ts`):
|
|
9
|
+
* the one rule camunda7 names the same process and activities by.
|
|
10
|
+
*/
|
|
11
|
+
const LEASING = `<?xml version="1.0" encoding="UTF-8"?>
|
|
12
|
+
<bpmn:definitions xmlns:bpmn="http://www.omg.org/spec/BPMN/20100524/MODEL" id="Defs_1">
|
|
13
|
+
<!-- <bpmn:userTask id="Task_Commented" name="Never shown" /> -->
|
|
14
|
+
<bpmn:process id="miraveloLeasing" name="Bike Leasing" isExecutable="true">
|
|
15
|
+
<bpmn:startEvent id="StartEvent_1" name="Application received" />
|
|
16
|
+
<bpmn:userTask id="Activity_Decide" name='Decide on "Leasing & Co"'>
|
|
17
|
+
<bpmn:documentation><![CDATA[<bpmn:task id="Task_InCdata" name="Nope"/>]]></bpmn:documentation>
|
|
18
|
+
</bpmn:userTask>
|
|
19
|
+
<bpmn:exclusiveGateway id="Gateway_Amount" name="Amount > 1000?" />
|
|
20
|
+
<bpmn:serviceTask id="Activity_Unnamed" />
|
|
21
|
+
<bpmn:serviceTask id="Activity_Blank" name=" " />
|
|
22
|
+
<bpmn:endEvent id="EndEvent_Done" name="Done ✓"/>
|
|
23
|
+
</bpmn:process>
|
|
24
|
+
</bpmn:definitions>`;
|
|
25
|
+
describe("createNameLookup", () => {
|
|
26
|
+
afterEach(() => {
|
|
27
|
+
vi.useRealTimers();
|
|
28
|
+
});
|
|
29
|
+
it("knows no names without a BPMN lookup (no camunda7 module)", async () => {
|
|
30
|
+
expect(createNameLookup(undefined)).toBe(NO_NAMES);
|
|
31
|
+
expect(await NO_NAMES(["a"])).toEqual({});
|
|
32
|
+
});
|
|
33
|
+
it("reads each key once and keeps only the elements a view asks for", async () => {
|
|
34
|
+
const fetchBpmnXml = vi.fn((key) => Promise.resolve(key === "miraveloLeasing" ? LEASING : null));
|
|
35
|
+
const namesFor = createNameLookup(fetchBpmnXml);
|
|
36
|
+
const index = await namesFor(["miraveloLeasing", "miraveloLeasing", "", "unknown"], {
|
|
37
|
+
elementIds: { miraveloLeasing: ["Activity_Decide", "Activity_Missing"], unknown: [] },
|
|
38
|
+
});
|
|
39
|
+
// Strictly: an element the model does not name is left out, never `undefined`.
|
|
40
|
+
expect(index).toStrictEqual({
|
|
41
|
+
miraveloLeasing: {
|
|
42
|
+
name: "Bike Leasing",
|
|
43
|
+
elements: { Activity_Decide: 'Decide on "Leasing & Co"' },
|
|
44
|
+
},
|
|
45
|
+
});
|
|
46
|
+
expect(fetchBpmnXml.mock.calls.map((c) => c[0])).toEqual(["miraveloLeasing", "unknown"]);
|
|
47
|
+
expect(processNameOf(index, "miraveloLeasing")).toBe("Bike Leasing");
|
|
48
|
+
expect(processNameOf(index, "unknown")).toBeNull();
|
|
49
|
+
expect(elementNameOf(index, "miraveloLeasing", "Activity_Decide")).toBe('Decide on "Leasing & Co"');
|
|
50
|
+
expect(elementNameOf(index, "miraveloLeasing", "constructor")).toBeNull();
|
|
51
|
+
expect(elementNameOf(index, "unknown", "x")).toBeNull();
|
|
52
|
+
expect(elementNameOf(undefined, "a", "b")).toBeNull();
|
|
53
|
+
// Without an element list a key comes with every named element.
|
|
54
|
+
const all = await namesFor(["miraveloLeasing"]);
|
|
55
|
+
expect(Object.keys(all.miraveloLeasing.elements)).toHaveLength(4);
|
|
56
|
+
});
|
|
57
|
+
it("reuses a read, an empty one too, until it is older than the ttl", async () => {
|
|
58
|
+
let now = 0;
|
|
59
|
+
const fetchBpmnXml = vi.fn((key) => Promise.resolve(key === "a" ? LEASING : null));
|
|
60
|
+
const namesFor = createNameLookup(fetchBpmnXml, { ttlMs: 1000, now: () => now });
|
|
61
|
+
await namesFor(["a", "b"]);
|
|
62
|
+
now = 999;
|
|
63
|
+
await namesFor(["a", "b"]);
|
|
64
|
+
expect(fetchBpmnXml).toHaveBeenCalledTimes(2);
|
|
65
|
+
now = 1000;
|
|
66
|
+
await namesFor(["a", "b"]);
|
|
67
|
+
expect(fetchBpmnXml).toHaveBeenCalledTimes(4);
|
|
68
|
+
});
|
|
69
|
+
it("goes on with the ids when a read fails or is slow, and asks again next time", async () => {
|
|
70
|
+
vi.useFakeTimers();
|
|
71
|
+
const fetchBpmnXml = vi.fn((key) => key === "slow"
|
|
72
|
+
? new Promise(() => { })
|
|
73
|
+
: Promise.reject(new Error("ECONNREFUSED")));
|
|
74
|
+
const namesFor = createNameLookup(fetchBpmnXml, { timeoutMs: 50 });
|
|
75
|
+
const pending = namesFor(["slow", "broken"]);
|
|
76
|
+
await vi.advanceTimersByTimeAsync(50);
|
|
77
|
+
expect(await pending).toEqual({});
|
|
78
|
+
const again = namesFor(["slow", "broken"]);
|
|
79
|
+
await vi.advanceTimersByTimeAsync(50);
|
|
80
|
+
await again;
|
|
81
|
+
expect(fetchBpmnXml).toHaveBeenCalledTimes(4);
|
|
82
|
+
expect(vi.getTimerCount()).toBe(0);
|
|
83
|
+
});
|
|
84
|
+
// The injected fetcher (camunda7's createBpmnXmlFetcher) REJECTS a failed
|
|
85
|
+
// read (503, refused, 401) and resolves null only for "no such model": the
|
|
86
|
+
// engine's failure is no answer to keep for five minutes.
|
|
87
|
+
it("keeps a 'no such model' answer but never a failure", async () => {
|
|
88
|
+
let engineUp = false;
|
|
89
|
+
const fetchBpmnXml = vi.fn((key) => engineUp
|
|
90
|
+
? Promise.resolve(key === "miraveloLeasing" ? LEASING : null)
|
|
91
|
+
: Promise.reject(new Error("503 Service Unavailable")));
|
|
92
|
+
const namesFor = createNameLookup(fetchBpmnXml);
|
|
93
|
+
expect(await namesFor(["miraveloLeasing", "unknown"])).toEqual({});
|
|
94
|
+
engineUp = true;
|
|
95
|
+
expect(processNameOf(await namesFor(["miraveloLeasing", "unknown"]), "miraveloLeasing")).toBe("Bike Leasing");
|
|
96
|
+
expect(fetchBpmnXml).toHaveBeenCalledTimes(4);
|
|
97
|
+
// Now both are answers: the names and "no such model", both kept.
|
|
98
|
+
await namesFor(["miraveloLeasing", "unknown"]);
|
|
99
|
+
expect(fetchBpmnXml).toHaveBeenCalledTimes(4);
|
|
100
|
+
});
|
|
101
|
+
it("leaves no timer pending after a fast view", async () => {
|
|
102
|
+
vi.useFakeTimers();
|
|
103
|
+
const fetchBpmnXml = vi.fn(() => Promise.resolve(LEASING));
|
|
104
|
+
const index = await createNameLookup(fetchBpmnXml)(["miraveloLeasing"]);
|
|
105
|
+
expect(processNameOf(index, "miraveloLeasing")).toBe("Bike Leasing");
|
|
106
|
+
expect(vi.getTimerCount()).toBe(0);
|
|
107
|
+
});
|
|
108
|
+
it("finishes a slow read for the next view", async () => {
|
|
109
|
+
vi.useFakeTimers();
|
|
110
|
+
let answer = () => { };
|
|
111
|
+
const fetchBpmnXml = vi.fn(() => new Promise((resolve) => {
|
|
112
|
+
answer = resolve;
|
|
113
|
+
}));
|
|
114
|
+
const namesFor = createNameLookup(fetchBpmnXml, { timeoutMs: 50 });
|
|
115
|
+
const first = namesFor(["miraveloLeasing"]);
|
|
116
|
+
await vi.advanceTimersByTimeAsync(50);
|
|
117
|
+
expect(await first).toEqual({});
|
|
118
|
+
answer(LEASING);
|
|
119
|
+
await vi.advanceTimersByTimeAsync(0);
|
|
120
|
+
expect(processNameOf(await namesFor(["miraveloLeasing"]), "miraveloLeasing")).toBe("Bike Leasing");
|
|
121
|
+
expect(fetchBpmnXml).toHaveBeenCalledTimes(1);
|
|
122
|
+
});
|
|
123
|
+
it("looks up at most maxKeys keys for one view, in the order it names them", async () => {
|
|
124
|
+
const fetchBpmnXml = vi.fn(() => Promise.resolve(null));
|
|
125
|
+
await createNameLookup(fetchBpmnXml, { maxKeys: 2 })(["a", "b", "c"]);
|
|
126
|
+
expect(fetchBpmnXml.mock.calls.map((c) => c[0])).toEqual(["a", "b"]);
|
|
127
|
+
});
|
|
128
|
+
it("keeps at most maxEntries answers, dropping the oldest first", async () => {
|
|
129
|
+
const fetchBpmnXml = vi.fn(() => Promise.resolve(null));
|
|
130
|
+
const namesFor = createNameLookup(fetchBpmnXml, { maxEntries: 2 });
|
|
131
|
+
await namesFor(["a", "b", "c"]);
|
|
132
|
+
expect(fetchBpmnXml).toHaveBeenCalledTimes(3);
|
|
133
|
+
// "b" and "c" are still kept; "a" went first and is read again.
|
|
134
|
+
await namesFor(["b", "c"]);
|
|
135
|
+
expect(fetchBpmnXml).toHaveBeenCalledTimes(3);
|
|
136
|
+
await namesFor(["a"]);
|
|
137
|
+
expect(fetchBpmnXml).toHaveBeenCalledTimes(4);
|
|
138
|
+
});
|
|
139
|
+
it("keeps a re-read answer as the newest, so the next to go is the oldest read", async () => {
|
|
140
|
+
let now = 0;
|
|
141
|
+
const fetchBpmnXml = vi.fn(() => Promise.resolve(null));
|
|
142
|
+
const namesFor = createNameLookup(fetchBpmnXml, { maxEntries: 2, ttlMs: 1000, now: () => now });
|
|
143
|
+
await namesFor(["a", "b"]);
|
|
144
|
+
now = 1000;
|
|
145
|
+
// "a" expired and is read again: now the newest, "b" the oldest.
|
|
146
|
+
await namesFor(["a"]);
|
|
147
|
+
await namesFor(["c"]);
|
|
148
|
+
expect(fetchBpmnXml).toHaveBeenCalledTimes(4);
|
|
149
|
+
// "b" went for "c"; "a" is still kept.
|
|
150
|
+
await namesFor(["a"]);
|
|
151
|
+
expect(fetchBpmnXml).toHaveBeenCalledTimes(4);
|
|
152
|
+
await namesFor(["b"]);
|
|
153
|
+
expect(fetchBpmnXml).toHaveBeenCalledTimes(5);
|
|
154
|
+
});
|
|
155
|
+
it("never reads an inherited entry for a key such as constructor", async () => {
|
|
156
|
+
const fetchBpmnXml = vi.fn(() => Promise.resolve('<process id="constructor" name="Odd"><task id="T" name="Step"/></process>'));
|
|
157
|
+
const index = await createNameLookup(fetchBpmnXml)(["constructor"], {
|
|
158
|
+
elementIds: {},
|
|
159
|
+
enginesByKey: {},
|
|
160
|
+
});
|
|
161
|
+
expect(index.constructor).toEqual({ name: "Odd", elements: { T: "Step" } });
|
|
162
|
+
expect(fetchBpmnXml.mock.calls).toEqual([["constructor", undefined]]);
|
|
163
|
+
});
|
|
164
|
+
});
|
|
165
|
+
/**
|
|
166
|
+
* A process may run on any configured engine, not only the default one
|
|
167
|
+
* (#322 review): each model is read on the engines the view's row comes
|
|
168
|
+
* from, in order, until one of them has it.
|
|
169
|
+
*/
|
|
170
|
+
describe("createNameLookup on several engines", () => {
|
|
171
|
+
afterEach(() => {
|
|
172
|
+
vi.useRealTimers();
|
|
173
|
+
});
|
|
174
|
+
/** An engine fleet: which engine holds which model, and which engine is down. */
|
|
175
|
+
function fleet(models, down = []) {
|
|
176
|
+
return vi.fn((key, engine) => {
|
|
177
|
+
if (engine && down.includes(engine))
|
|
178
|
+
return Promise.reject(new Error("503"));
|
|
179
|
+
return Promise.resolve(models[engine ?? "default"]?.[key] ?? null);
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
it("reads a key on the view's engines in order until one has its model", async () => {
|
|
183
|
+
const fetchBpmnXml = fleet({ "prod-c": { miraveloLeasing: LEASING } }, ["prod-a"]);
|
|
184
|
+
const index = await createNameLookup(fetchBpmnXml)(["miraveloLeasing"], {
|
|
185
|
+
engines: ["prod-a", "prod-b", "prod-c", "prod-d"],
|
|
186
|
+
});
|
|
187
|
+
expect(processNameOf(index, "miraveloLeasing")).toBe("Bike Leasing");
|
|
188
|
+
expect(fetchBpmnXml.mock.calls.map((c) => c[1])).toEqual(["prod-a", "prod-b", "prod-c"]);
|
|
189
|
+
});
|
|
190
|
+
it("prefers a key's own engines over the view's, and the default engine without either", async () => {
|
|
191
|
+
const fetchBpmnXml = fleet({
|
|
192
|
+
"prod-b": { miraveloLeasing: LEASING },
|
|
193
|
+
default: { other: '<process id="other" name="Other"/>' },
|
|
194
|
+
});
|
|
195
|
+
const namesFor = createNameLookup(fetchBpmnXml);
|
|
196
|
+
const index = await namesFor(["miraveloLeasing"], {
|
|
197
|
+
enginesByKey: { miraveloLeasing: ["prod-b"] },
|
|
198
|
+
engines: ["prod-a"],
|
|
199
|
+
});
|
|
200
|
+
expect(processNameOf(index, "miraveloLeasing")).toBe("Bike Leasing");
|
|
201
|
+
expect(processNameOf(await namesFor(["other"]), "other")).toBe("Other");
|
|
202
|
+
expect(fetchBpmnXml.mock.calls).toEqual([
|
|
203
|
+
["miraveloLeasing", "prod-b"],
|
|
204
|
+
["other", undefined],
|
|
205
|
+
]);
|
|
206
|
+
});
|
|
207
|
+
it("takes an empty engine list as none: the view's engines, else the default engine", async () => {
|
|
208
|
+
const fetchBpmnXml = fleet({ "prod-a": { miraveloLeasing: LEASING } });
|
|
209
|
+
const namesFor = createNameLookup(fetchBpmnXml);
|
|
210
|
+
const index = await namesFor(["miraveloLeasing"], {
|
|
211
|
+
enginesByKey: { miraveloLeasing: [] },
|
|
212
|
+
engines: ["prod-a"],
|
|
213
|
+
});
|
|
214
|
+
expect(processNameOf(index, "miraveloLeasing")).toBe("Bike Leasing");
|
|
215
|
+
expect(await namesFor(["other"], { engines: [] })).toEqual({});
|
|
216
|
+
expect(fetchBpmnXml.mock.calls).toEqual([
|
|
217
|
+
["miraveloLeasing", "prod-a"],
|
|
218
|
+
["other", undefined],
|
|
219
|
+
]);
|
|
220
|
+
});
|
|
221
|
+
it("keeps each engine's answer apart", async () => {
|
|
222
|
+
const fetchBpmnXml = fleet({ "prod-b": { miraveloLeasing: LEASING } });
|
|
223
|
+
const namesFor = createNameLookup(fetchBpmnXml);
|
|
224
|
+
// Not on prod-a: kept as "no such model" there, and prod-b still asked.
|
|
225
|
+
expect(await namesFor(["miraveloLeasing"], { engines: ["prod-a"] })).toEqual({});
|
|
226
|
+
const index = await namesFor(["miraveloLeasing"], { engines: ["prod-a", "prod-b"] });
|
|
227
|
+
expect(processNameOf(index, "miraveloLeasing")).toBe("Bike Leasing");
|
|
228
|
+
expect(fetchBpmnXml.mock.calls.map((c) => c[1])).toEqual(["prod-a", "prod-b"]);
|
|
229
|
+
});
|
|
230
|
+
it("stops at the view's time budget, not one budget per engine", async () => {
|
|
231
|
+
vi.useFakeTimers();
|
|
232
|
+
const fetchBpmnXml = vi.fn((_key, engine) => engine === "prod-a" ? new Promise(() => { }) : Promise.resolve(LEASING));
|
|
233
|
+
const pending = createNameLookup(fetchBpmnXml, { timeoutMs: 50 })(["miraveloLeasing"], {
|
|
234
|
+
engines: ["prod-a", "prod-b"],
|
|
235
|
+
});
|
|
236
|
+
await vi.advanceTimersByTimeAsync(50);
|
|
237
|
+
expect(await pending).toEqual({});
|
|
238
|
+
expect(fetchBpmnXml.mock.calls.map((c) => c[1])).toEqual(["prod-a"]);
|
|
239
|
+
expect(vi.getTimerCount()).toBe(0);
|
|
240
|
+
});
|
|
241
|
+
});
|
|
242
|
+
/**
|
|
243
|
+
* With passthrough auth the engine answers each caller with that caller's
|
|
244
|
+
* own permissions (#322 review): a model one caller may read never reaches
|
|
245
|
+
* another through the cache, and one caller's refusal never hides the names
|
|
246
|
+
* from the others.
|
|
247
|
+
*/
|
|
248
|
+
describe("createNameLookup per caller", () => {
|
|
249
|
+
const ALICE = { authUserId: "alice", authorization: "Bearer token-alice" };
|
|
250
|
+
const BOB = { authUserId: "bob", authorization: "Bearer token-bob" };
|
|
251
|
+
/** A passthrough engine: only Alice's token may read the model. */
|
|
252
|
+
const passthrough = () => vi.fn(() => Promise.resolve(getMcpRequestInfo()?.authorization === ALICE.authorization ? LEASING : null));
|
|
253
|
+
it("never hands one caller's names to another", async () => {
|
|
254
|
+
const fetchBpmnXml = passthrough();
|
|
255
|
+
const namesFor = createNameLookup(fetchBpmnXml);
|
|
256
|
+
const alice = await runWithMcpRequestInfo(ALICE, () => namesFor(["miraveloLeasing"]));
|
|
257
|
+
expect(processNameOf(alice, "miraveloLeasing")).toBe("Bike Leasing");
|
|
258
|
+
const bob = await runWithMcpRequestInfo(BOB, () => namesFor(["miraveloLeasing"]));
|
|
259
|
+
expect(bob).toEqual({});
|
|
260
|
+
expect(fetchBpmnXml).toHaveBeenCalledTimes(2);
|
|
261
|
+
});
|
|
262
|
+
it("never hides the names from a caller because another was refused", async () => {
|
|
263
|
+
const fetchBpmnXml = passthrough();
|
|
264
|
+
const namesFor = createNameLookup(fetchBpmnXml);
|
|
265
|
+
expect(await runWithMcpRequestInfo(BOB, () => namesFor(["miraveloLeasing"]))).toEqual({});
|
|
266
|
+
// A request without a token is a caller of its own too.
|
|
267
|
+
expect(await namesFor(["miraveloLeasing"])).toEqual({});
|
|
268
|
+
const alice = await runWithMcpRequestInfo(ALICE, () => namesFor(["miraveloLeasing"]));
|
|
269
|
+
expect(processNameOf(alice, "miraveloLeasing")).toBe("Bike Leasing");
|
|
270
|
+
// Each caller's own answer is still kept.
|
|
271
|
+
await runWithMcpRequestInfo(ALICE, () => namesFor(["miraveloLeasing"]));
|
|
272
|
+
await runWithMcpRequestInfo(BOB, () => namesFor(["miraveloLeasing"]));
|
|
273
|
+
expect(fetchBpmnXml).toHaveBeenCalledTimes(3);
|
|
274
|
+
});
|
|
275
|
+
it("takes the partition from the injected caller when one is given", async () => {
|
|
276
|
+
let caller = "a";
|
|
277
|
+
const fetchBpmnXml = vi.fn(() => Promise.resolve(LEASING));
|
|
278
|
+
const namesFor = createNameLookup(fetchBpmnXml, { caller: () => caller });
|
|
279
|
+
await namesFor(["miraveloLeasing"]);
|
|
280
|
+
await namesFor(["miraveloLeasing"]);
|
|
281
|
+
expect(fetchBpmnXml).toHaveBeenCalledTimes(1);
|
|
282
|
+
caller = "b";
|
|
283
|
+
await namesFor(["miraveloLeasing"]);
|
|
284
|
+
expect(fetchBpmnXml).toHaveBeenCalledTimes(2);
|
|
285
|
+
});
|
|
286
|
+
});
|
|
287
|
+
//# sourceMappingURL=bpmn-name-lookup.test.js.map
|