Files
dashboardAPI/test/dashboardapi.test.js
znetsixe 990a8c09ea feat(dashboardapi): recursive subtree discovery + measurement-name/template parity
Generate dashboards for an entire parent-child subtree from a single root
registration (pre-order, cycle/diamond-safe), so wiring only the subtree root
(e.g. pumpingStation) to dashboardAPI yields dashboards for every descendant.

Fix two contract drifts that left generated panels blank against live telemetry:
- _measurement var now mirrors outputUtils.formatMsg (general.name ||
  <softwareType>_<id>); previously it always used the fallback form, so any
  named node's dashboard queried a non-existent series.
- pumpingStation template field keys realigned to emitted telemetry
  (flow.*.{upstream,out,overflow}, netFlowRate.measured, inflowLevel/
  outflowLevel/overflowLevel, maxVolAtOverflow/minVolAt{Inflow,Outflow}).

Adds template alias resolution (softwareType -> shared template file) and
locks parity with slice44/45/46 tests + output manifest. 67/67 pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 09:45:37 +02:00

83 lines
2.8 KiB
JavaScript

const DashboardApi = require('../src/specificClass');
function makeNodeSource({ id, name, softwareType, positionVsParent, children = [] }) {
const registeredChildren = new Map();
for (const child of children) {
registeredChildren.set(child.config.general.id, {
child,
softwareType: child.config.functionality.softwareType,
position: child.positionVsParent || child.config.functionality.positionVsParent,
registeredAt: Date.now(),
});
}
return {
config: {
general: { id, name },
functionality: { softwareType, positionVsParent },
},
positionVsParent,
childRegistrationUtils: { registeredChildren },
};
}
describe('DashboardApi specificClass', () => {
it('buildDashboard sets id=null, stable uid, title, measurement and bucket vars', () => {
const api = new DashboardApi({
general: { name: 'dashboardapi-test', logging: { enabled: false, logLevel: 'error' } },
grafanaConnector: { protocol: 'http', host: 'localhost', port: 3000, bearerToken: '' },
});
const nodeSource = makeNodeSource({
id: 'm-1',
name: 'PT-1',
softwareType: 'measurement',
positionVsParent: 'downstream',
});
const dash = api.buildDashboard({ nodeConfig: nodeSource.config, positionVsParent: 'downstream' });
expect(dash.dashboard.id).toBeNull();
expect(dash.uid).toHaveLength(12);
expect(dash.dashboard.uid).toBe(dash.uid);
expect(dash.dashboard.title).toBe('PT-1');
const templ = dash.dashboard.templating.list;
const measurement = templ.find((v) => v.name === 'measurement');
const bucket = templ.find((v) => v.name === 'bucket');
// measurement var must mirror outputUtils: general.name when set.
expect(measurement.current.value).toBe('PT-1');
expect(bucket.current.value).toBe('lvl3');
});
it('generateDashboardsForGraph returns root + direct child dashboards and adds links', () => {
const api = new DashboardApi({
general: { name: 'dashboardapi-test', logging: { enabled: false, logLevel: 'error' } },
grafanaConnector: { protocol: 'http', host: 'localhost', port: 3000, bearerToken: '' },
});
const child = makeNodeSource({
id: 'c-1',
name: 'ChildSensor',
softwareType: 'measurement',
positionVsParent: 'upstream',
});
const root = makeNodeSource({
id: 'p-1',
name: 'ParentMachine',
softwareType: 'machine',
positionVsParent: 'atEquipment',
children: [child],
});
const results = api.generateDashboardsForGraph(root, { includeChildren: true });
expect(results).toHaveLength(2);
const rootDash = results[0];
expect(Array.isArray(rootDash.dashboard.links)).toBe(true);
expect(rootDash.dashboard.links.some((l) => l.url && l.url.includes('/d/'))).toBe(true);
});
});