From 9d5b1fda8b33910e335185d6984dd1360956b927 Mon Sep 17 00:00:00 2001 From: papertager <2567587994@qq.com> Date: Tue, 9 Jun 2026 22:26:58 +0800 Subject: [PATCH 1/2] Add mx-exporter Grafana dashboard inventory --- tools/dashboard_inventory.py | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 tools/dashboard_inventory.py diff --git a/tools/dashboard_inventory.py b/tools/dashboard_inventory.py new file mode 100644 index 0000000..02f1306 --- /dev/null +++ b/tools/dashboard_inventory.py @@ -0,0 +1,35 @@ +#!/usr/bin/env python3 +"""Inventory Grafana dashboards shipped with mx-exporter.""" + +from __future__ import annotations + +import argparse +import json +from pathlib import Path + + +def inventory(root: Path) -> dict[str, object]: + dashboards = [] + for path in sorted((root / "deployment" / "grafana-dashboard").glob("*.json")): + data = json.loads(path.read_text(encoding="utf-8-sig")) + panels = data.get("panels", []) + dashboards.append({"path": path.relative_to(root).as_posix(), "title": data.get("title", ""), "uid": data.get("uid", ""), "panel_count": len(panels)}) + return {"dashboard_count": len(dashboards), "dashboards": dashboards} + + +def main() -> int: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--root", type=Path, default=Path.cwd()) + parser.add_argument("--output", type=Path) + args = parser.parse_args() + + text = json.dumps(inventory(args.root), indent=2, ensure_ascii=False) + if args.output: + args.output.write_text(text + "\n", encoding="utf-8") + else: + print(text) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) From 9dab00964d42c23c98ac055839214004d013b400 Mon Sep 17 00:00:00 2001 From: papertager <2567587994@qq.com> Date: Thu, 11 Jun 2026 00:14:17 +0800 Subject: [PATCH 2/2] Handle invalid dashboard JSON cleanly --- tests/test_dashboard_inventory.py | 24 ++++++++++++++++++++++++ tools/dashboard_inventory.py | 14 ++++++++++++-- 2 files changed, 36 insertions(+), 2 deletions(-) create mode 100644 tests/test_dashboard_inventory.py diff --git a/tests/test_dashboard_inventory.py b/tests/test_dashboard_inventory.py new file mode 100644 index 0000000..ab3346c --- /dev/null +++ b/tests/test_dashboard_inventory.py @@ -0,0 +1,24 @@ +import tempfile +import unittest +from pathlib import Path + +from tools.dashboard_inventory import inventory + + +class DashboardInventoryTest(unittest.TestCase): + def test_reports_invalid_json_with_path(self): + with tempfile.TemporaryDirectory() as tmpdir: + root = Path(tmpdir) + dashboard_dir = root / "deployment" / "grafana-dashboard" + dashboard_dir.mkdir(parents=True) + bad_path = dashboard_dir / "broken.json" + bad_path.write_text("{not-json", encoding="utf-8") + + with self.assertRaises(RuntimeError) as ctx: + inventory(root) + + self.assertIn(str(bad_path), str(ctx.exception)) + + +if __name__ == "__main__": + unittest.main() diff --git a/tools/dashboard_inventory.py b/tools/dashboard_inventory.py index 02f1306..eec712f 100644 --- a/tools/dashboard_inventory.py +++ b/tools/dashboard_inventory.py @@ -11,9 +11,19 @@ def inventory(root: Path) -> dict[str, object]: dashboards = [] for path in sorted((root / "deployment" / "grafana-dashboard").glob("*.json")): - data = json.loads(path.read_text(encoding="utf-8-sig")) + try: + data = json.loads(path.read_text(encoding="utf-8-sig")) + except json.JSONDecodeError as exc: + raise RuntimeError(f"Failed to parse JSON file {path}: {exc}") from exc panels = data.get("panels", []) - dashboards.append({"path": path.relative_to(root).as_posix(), "title": data.get("title", ""), "uid": data.get("uid", ""), "panel_count": len(panels)}) + dashboards.append( + { + "path": path.relative_to(root).as_posix(), + "title": data.get("title", ""), + "uid": data.get("uid", ""), + "panel_count": len(panels), + } + ) return {"dashboard_count": len(dashboards), "dashboards": dashboards}