feat(prometheus-direct-charting): slice 1 — prom range query + chart rebrand
Add PrometheusWidgetSource._fetch_chart hitting /api/v1/query_range directly (SC-101..104). New shared helpers in widgets/prometheus_range.py: step_for_window (window preset -> step, ~200pts) and normalize_prometheus_matrix (extracted label/dedup rule, retargeted at Prom matrix, robust to malformed data). Chart widget kind moved grafana->prometheus in both registries; GrafanaChartWidget renamed -> PrometheusChartWidget (git mv, recharts body preserved). Grafana binding/service untouched (removed in slice 3). Backend: 298 pytest pass, ruff clean. Frontend: 128 vitest pass, build+lint green.
This commit is contained in:
@@ -45,61 +45,61 @@ This ordering ensures the chart capability is proven against Prometheus before t
|
||||
|
||||
**Satisfies:** SC-101, SC-102, SC-103, SC-104, SC-105, SC-106, SC-107, SC-108.
|
||||
|
||||
- [ ] **1.1 Create shared Prometheus range helpers module**
|
||||
- [x] **1.1 Create shared Prometheus range helpers module**
|
||||
- Files: `backend/src/media_library_viewer_api/widgets/prometheus_range.py` (new)
|
||||
- Lines: ~60
|
||||
- Dependencies: none
|
||||
- Details: Implement `WINDOW_PRESETS = {"1h": 3600, "6h": 21600, "24h": 86400, "7d": 604800}`, `step_for_window(window_seconds, target_points=200) -> int` returning `max(15, round(window_seconds / target_points))`, and `normalize_prometheus_matrix(result: list[dict]) -> list[dict]` that converts a Prometheus `/api/v1/query_range` `data.result` matrix into `{label, points:[{t:int, v:float|None}]}` series. Label rule: drop `__name__` from metric labels; join remaining as `k=v`; fall back to `"value"`; dedup collisions with `(n)` suffix. Null handling: `"NaN"`, `"+Inf"`, `"-Inf"`, `None` → `v: None`.
|
||||
|
||||
- [ ] **1.2 Add backend unit tests for range helpers**
|
||||
- [x] **1.2 Add backend unit tests for range helpers**
|
||||
- Files: `backend/tests/test_prometheus_range.py` (new)
|
||||
- Lines: ~70
|
||||
- Dependencies: 1.1
|
||||
- Details: Test `step_for_window` for all four presets asserts result yields 100–300 points. Test `normalize_prometheus_matrix`: feed a two-entry sample matrix (one with `__name__`, one colliding label) → assert `{series}` shape, label dedup `(1)` suffix, null handling for `"NaN"` string.
|
||||
|
||||
- [ ] **1.3 Add `_fetch_chart` + shared range-query plumbing to `PrometheusWidgetSource`**
|
||||
- [x] **1.3 Add `_fetch_chart` + shared range-query plumbing to `PrometheusWidgetSource`**
|
||||
- Files: `backend/src/media_library_viewer_api/widgets/sources.py` (modify)
|
||||
- Lines: ~55
|
||||
- Dependencies: 1.1
|
||||
- Details: Import `WINDOW_PRESETS`, `step_for_window`, `normalize_prometheus_matrix` from `prometheus_range`. Add a private `_range_query(base_url, timeout, promql, window) -> dict` returning `{"matrix": result}` or `{"error": ...}` (shared by chart now and mean in S2). Add `_fetch_chart(self, base_url, timeout, config)` calling `_range_query` and returning `{"series": normalize_prometheus_matrix(matrix)}`. Dispatch `widget_kind == "chart"` in `.fetch()`. Existing `metric` path stays byte-for-byte unchanged. Errors (timeout, `RequestException`) → `{"error": str}`, never raise.
|
||||
|
||||
- [ ] **1.4 Declare `chart` widget kind in Prometheus integration**
|
||||
- [x] **1.4 Declare `chart` widget kind in Prometheus integration**
|
||||
- Files: `backend/src/media_library_viewer_api/integrations/prometheus.py` (modify)
|
||||
- Lines: ~15
|
||||
- Dependencies: 1.3
|
||||
- Details: Add `PrometheusChartWidgetConfig(WidgetConfigBase)` with `promql: str` and `window: str = "1h"`. Add a `widget_kind(...)` entry for `chart` (refresh 60s, default config `{"promql": "", "window": "1h"}`). Leave `metric` kind untouched.
|
||||
|
||||
- [ ] **1.5 Rename `GrafanaChartWidget` → `PrometheusChartWidget`**
|
||||
- [x] **1.5 Rename `GrafanaChartWidget` → `PrometheusChartWidget`**
|
||||
- Files: `frontend/src/widgets/GrafanaChartWidget.tsx` → `frontend/src/widgets/PrometheusChartWidget.tsx` (git mv)
|
||||
- Lines: ~5 changed (rename export, fix empty-state copy)
|
||||
- Dependencies: none
|
||||
- Details: `git mv` to preserve history. Rename exported function `GrafanaChartWidget` → `PrometheusChartWidget`. The recharts body (`LineChart`, `Line`, `XAxis`, `YAxis`, `CartesianGrid`, `Tooltip`, `ResponsiveContainer`, `mergeSeries`, `CHART_COLORS`, `formatTime`) is preserved unchanged (SC-106). Fix empty-state copy: "Check your query and datasource_uid" → "Check your PromQL query and window."
|
||||
|
||||
- [ ] **1.6 Rename chart widget test**
|
||||
- [x] **1.6 Rename chart widget test**
|
||||
- Files: `frontend/src/widgets/__tests__/GrafanaChartWidget.test.tsx` → `frontend/src/widgets/__tests__/PrometheusChartWidget.test.tsx` (git mv)
|
||||
- Lines: ~10 changed (import path, component name, error-message assertion)
|
||||
- Dependencies: 1.5
|
||||
- Details: `git mv`. Update import to `PrometheusChartWidget`. Update error-state assertion: the old Grafana-specific error string (`"Grafana api_key is required"`) → a Prom error string (e.g. `"promql is required"`). Keep loading + rendered-data test cases.
|
||||
|
||||
- [ ] **1.7 Rebind `chart` from grafana to prometheus in frontend registry**
|
||||
- [x] **1.7 Rebind `chart` from grafana to prometheus in frontend registry**
|
||||
- Files: `frontend/src/integrations/registry.ts` (modify)
|
||||
- Lines: ~30
|
||||
- Dependencies: 1.5
|
||||
- Details: Import `PrometheusChartWidget`. Add a `chart` entry to the `prometheus` binding's `widgets` array (kind `chart`, refresh 60s, configSchema with `promql` + `window`). Remove the `chart` entry from the `grafana` binding's `widgets` array (leave `link` intact). Do NOT delete the `grafana` binding itself.
|
||||
|
||||
- [ ] **1.8 Update frontend widgets barrel export**
|
||||
- [x] **1.8 Update frontend widgets barrel export**
|
||||
- Files: `frontend/src/widgets/index.ts` (modify)
|
||||
- Lines: ~2
|
||||
- Dependencies: 1.5
|
||||
- Details: Rename the `GrafanaChartWidget` export to `PrometheusChartWidget`. Leave `GrafanaLinkWidget` export intact.
|
||||
|
||||
- [ ] **1.9 Update registry tests for chart rebind**
|
||||
- [x] **1.9 Update registry tests for chart rebind**
|
||||
- Files: `frontend/src/integrations/registry.test.ts` (modify)
|
||||
- Lines: ~15
|
||||
- Dependencies: 1.7
|
||||
- Details: Assert `prometheus` binding has `metric` + `chart` kinds. Assert `grafana` binding has only `link` (no `chart`).
|
||||
|
||||
- [ ] **1.10 Verify Slice 1 (build + lint + test)**
|
||||
- [x] **1.10 Verify Slice 1 (build + lint + test)**
|
||||
- Run: `cd backend && PYTHONPATH=src pytest tests/test_prometheus_range.py tests/test_widgets.py && cd ../frontend && npm run build && npm run lint`
|
||||
- Verify: helpers tests pass; existing widget tests pass (grafana link adapter still wired); frontend typechecks and lints; `prometheus/chart` widget resolves to `PrometheusChartWidget`.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user