feat(dashboardAPI): resolve Grafana folder by name (fixes stale folderUid 400s)
A pinned folderUid goes stale whenever Grafana is rebuilt — the same-named folder returns with a fresh uid and every dashboard upsert then 400s "folder not found", silently dropping all generated dashboards. Add a folderTitle config field: when set, resolveFolderUid() looks the folder up by name (GET /api/folders), creates it if absent (POST /api/folders), caches the uid for the process, and falls back to the configured folderUid on any failure (never worse than the pinned behavior). The emit handlers (registerChild/regenerateDashboard/emitDashboardsFor) are now async and await the resolution. folderUid retained as an explicit override/fallback. Locked by slice48-folder-resolve-by-name; existing emit tests made async. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -32,14 +32,14 @@ test('recordChild caches child source by id; subsequent ones replace by id', ()
|
||||
assert.equal(api.cachedChildSources().length, 2);
|
||||
});
|
||||
|
||||
test('regenerate-dashboard with no cached children is a no-op (no msgs emitted)', () => {
|
||||
test('regenerate-dashboard with no cached children is a no-op (no msgs emitted)', async () => {
|
||||
const api = new DashboardApi({});
|
||||
const sends = [];
|
||||
handlers.regenerateDashboard(api, { topic: 'regenerate-dashboard', payload: {} }, makeCtx(sends));
|
||||
await handlers.regenerateDashboard(api, { topic: 'regenerate-dashboard', payload: {} }, makeCtx(sends));
|
||||
assert.equal(sends.length, 0);
|
||||
});
|
||||
|
||||
test('regenerate-dashboard re-emits for each cached child, bypassing diff', () => {
|
||||
test('regenerate-dashboard re-emits for each cached child, bypassing diff', async () => {
|
||||
const api = new DashboardApi({});
|
||||
// Pre-populate cache as if two children had registered.
|
||||
api.recordChild(makeChildPayload('m-1'));
|
||||
@@ -50,18 +50,18 @@ test('regenerate-dashboard re-emits for each cached child, bypassing diff', () =
|
||||
api.lastFlowsStartedDiff = { added: [], changed: [], removed: [], rewired: [] };
|
||||
|
||||
const sends = [];
|
||||
handlers.regenerateDashboard(api, { topic: 'regenerate-dashboard', payload: {} }, makeCtx(sends));
|
||||
await handlers.regenerateDashboard(api, { topic: 'regenerate-dashboard', payload: {} }, makeCtx(sends));
|
||||
// Each child yields at least one dashboard message (the root for the child's view).
|
||||
assert.ok(sends.length >= 2, `expected ≥2 emitted msgs, got ${sends.length}`);
|
||||
// Every emitted msg carries trigger: 'manual' in meta.
|
||||
for (const m of sends) assert.equal(m.meta?.trigger, 'manual');
|
||||
});
|
||||
|
||||
test('child.register stamps trigger: child.register in emitted msg meta', () => {
|
||||
test('child.register stamps trigger: child.register in emitted msg meta', async () => {
|
||||
const api = new DashboardApi({});
|
||||
api.lastFlowsStartedDiff = null; // cold-start → always regen
|
||||
const sends = [];
|
||||
handlers.registerChild(api, { topic: 'child.register', payload: makeChildPayload('m-3') }, makeCtx(sends));
|
||||
await handlers.registerChild(api, { topic: 'child.register', payload: makeChildPayload('m-3') }, makeCtx(sends));
|
||||
assert.ok(sends.length >= 1);
|
||||
for (const m of sends) assert.equal(m.meta?.trigger, 'child.register');
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user