diff --git a/internal/metrics/exporter.go b/internal/metrics/exporter.go index 1eeda5b..d81677e 100644 --- a/internal/metrics/exporter.go +++ b/internal/metrics/exporter.go @@ -180,10 +180,12 @@ func appendQualityGateMetrics(dst *strings.Builder) { } raw, err := os.ReadFile(path) if err != nil { + appendDefaultQualityGateMetrics(dst) return } text := strings.TrimSpace(string(raw)) if text == "" { + appendDefaultQualityGateMetrics(dst) return } if dst.Len() > 0 { @@ -195,6 +197,20 @@ func appendQualityGateMetrics(dst *strings.Builder) { } } +// appendDefaultQualityGateMetrics runs one orchestration or CLI step. +// Signature: appendDefaultQualityGateMetrics(dst *strings.Builder). +// Why: Grafana should always have baseline Ananke quality-gate series so +// overview panels never show "no data" before the first host-side gate run. +func appendDefaultQualityGateMetrics(dst *strings.Builder) { + if dst.Len() > 0 { + dst.WriteString("\n") + } + dst.WriteString("# HELP ananke_quality_gate_runs_total Total Ananke quality gate runs by status.\n") + dst.WriteString("# TYPE ananke_quality_gate_runs_total counter\n") + dst.WriteString("ananke_quality_gate_runs_total{suite=\"ananke\",status=\"ok\"} 0\n") + dst.WriteString("ananke_quality_gate_runs_total{suite=\"ananke\",status=\"failed\"} 0\n") +} + // boolNum runs one orchestration or CLI step. // Signature: boolNum(v bool) int. // Why: keeps behavior explicit so startup/shutdown workflows remain maintainable as services evolve. diff --git a/internal/metrics/exporter_additional_test.go b/internal/metrics/exporter_additional_test.go index d5b5dd7..90b7774 100644 --- a/internal/metrics/exporter_additional_test.go +++ b/internal/metrics/exporter_additional_test.go @@ -84,3 +84,22 @@ func TestExporterAppendsQualityGateMetrics(t *testing.T) { t.Fatalf("expected quality gate metrics appended to exporter output, got:\n%s", body) } } + +// TestExporterDefaultsQualityGateMetrics runs one orchestration or CLI step. +// Signature: TestExporterDefaultsQualityGateMetrics(t *testing.T). +// Why: ensures Grafana panels see zero-value quality-gate series instead of +// "no data" before host-side quality-gate files exist. +func TestExporterDefaultsQualityGateMetrics(t *testing.T) { + t.Setenv("ANANKE_QUALITY_METRICS_FILE", filepath.Join(t.TempDir(), "missing.prom")) + e := New() + req := httptest.NewRequest(http.MethodGet, "/metrics", nil) + rr := httptest.NewRecorder() + e.Handler("/metrics").ServeHTTP(rr, req) + body := rr.Body.String() + if !strings.Contains(body, `ananke_quality_gate_runs_total{suite="ananke",status="ok"} 0`) { + t.Fatalf("expected default ok=0 quality gate metric, got:\n%s", body) + } + if !strings.Contains(body, `ananke_quality_gate_runs_total{suite="ananke",status="failed"} 0`) { + t.Fatalf("expected default failed=0 quality gate metric, got:\n%s", body) + } +}