Files
dashboardAPI/src/commands/handlers.js

86 lines
2.7 KiB
JavaScript
Raw Normal View History

'use strict';
// Resolve a child's source object from a registration payload.
// Payload may be: a string (node id) | { source: {...} } | { config: {...} }.
function resolveChildSource(payload, ctx) {
if (payload?.source?.config) return payload.source;
if (payload?.config) return { config: payload.config };
if (typeof payload === 'string') {
const childNode = resolveChildNode(payload, ctx);
return childNode?.source || null;
}
return null;
}
function resolveChildNode(childId, ctx) {
const runtimeNode = ctx.RED?.nodes?.getNode?.(childId);
if (runtimeNode?.source?.config) return runtimeNode;
const flowNode = ctx.node?._flow?.getNode?.(childId);
if (flowNode?.source?.config) return flowNode;
return runtimeNode || flowNode || null;
}
// On child.register: build the dashboard graph (root + direct children) and
// emit one Grafana upsert HTTP request per dashboard on Port 0.
//
// Diff-skip behavior (PRD F-1, S1 spike #32): if the latest flows:started
// payload's `diff` indicates that NEITHER the dashboardAPI itself NOR this
// child NOR its grandchildren changed, skip composition and log no-diff. The
// first call after startup (no cached diff yet) regenerates unconditionally.
function registerChild(source, msg, ctx) {
const childSource = resolveChildSource(msg.payload, ctx);
if (!childSource?.config) {
throw new Error('Missing or invalid child node');
}
const subtreeIds = source.subtreeIdsFor(ctx.node?.id, childSource);
const changed = source.subtreeChanged(source.lastFlowsStartedDiff, subtreeIds);
if (!changed) {
if (source.logger?.info) {
source.logger.info({
event: 'regen-skipped',
outcome: 'no-diff',
trigger: 'child.register',
dashboardApiId: ctx.node?.id,
childId: childSource?.config?.general?.id,
subtreeSize: subtreeIds.size,
});
}
return;
}
const dashboards = source.generateDashboardsForGraph(childSource, {
includeChildren: Boolean(msg.includeChildren ?? true),
});
const url = source.grafanaUpsertUrl();
const headers = { Accept: 'application/json', 'Content-Type': 'application/json' };
const token = source.config?.grafanaConnector?.bearerToken;
if (token) headers.Authorization = `Bearer ${token}`;
for (const dash of dashboards) {
ctx.send({
...msg,
topic: 'create',
url,
method: 'POST',
headers,
payload: source.buildUpsertRequest({
dashboard: dash.dashboard,
folderUid: source.config?.grafanaConnector?.folderUid || undefined,
overwrite: true,
}),
meta: {
nodeId: dash.nodeId,
softwareType: dash.softwareType,
uid: dash.uid,
title: dash.title,
},
});
}
}
module.exports = { registerChild };