Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion dashboard/app/src/app/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export function App() {
const [route, setRoute] = useState<RouteId>(routeFromHash);
const [commandOpen, setCommandOpen] = useState(false);
const dashboard = useQuery({ queryKey: ['dashboard'], queryFn: ({ signal }) => api.dashboard(signal) });
const releaseHealth = useQuery({ queryKey: ['release-health'], queryFn: ({ signal }) => api.releaseHealth(signal) });

useEffect(() => {
const onHash = () => setRoute(routeFromHash());
Expand All @@ -39,7 +40,7 @@ export function App() {
let content = <LoadingState />;
if (dashboard.isError) content = <ErrorState message={dashboard.error.message} onRetry={() => void dashboard.refetch()} />;
if (dashboard.data) {
content = route === 'overview' ? <OverviewPage payload={dashboard.data} />
content = route === 'overview' ? <OverviewPage payload={dashboard.data} releaseHealth={releaseHealth.data} />
: route === 'forensics' ? <ForensicsPage payload={dashboard.data} />
: route === 'benchmarks' ? <BenchmarksPage payload={dashboard.data} />
: route === 'replay' ? <ReplayPage payload={dashboard.data} />
Expand Down
76 changes: 73 additions & 3 deletions dashboard/app/src/features/overview/OverviewPage.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,86 @@
import { Activity, AlertTriangle, Gauge, ShieldCheck } from 'lucide-react';
import { Activity, AlertTriangle, FileWarning, Gauge, ShieldCheck } from 'lucide-react';
import { BarChart } from '../../components/charts/BarChart';
import { compactNumber, ms, percent, ratio } from '../../lib/format';
import type { DashboardPayload, ServiceHealth } from '../../types/domain';
import { RELEASE_HEALTH_SUMMARY_PATH } from '../../lib/releaseHealth';
import type { ReleaseHealthState } from '../../lib/releaseHealth';
import type { DashboardPayload, ReleaseHealthCheck, ReleaseHealthStatus, ServiceHealth } from '../../types/domain';

function MetricCard({ title, value, description, icon: Icon, tone = 'nominal' }: { title: string; value: string; description: string; icon: typeof Activity; tone?: string }) {
return <article className="card"><div className="card-header"><div><h3>{title}</h3><p>{description}</p></div><span className={`badge ${tone}`}><Icon size={14} /></span></div><div className="metric">{value}</div></article>;
}


function healthTone(status?: ReleaseHealthStatus) {
const normalized = String(status ?? 'unknown').toLowerCase();
if (['green', 'pass', 'passed', 'present', 'nominal', 'ok'].includes(normalized)) return 'nominal';
if (['yellow', 'warning', 'degraded', 'missing_optional'].includes(normalized)) return 'warning';
if (['red', 'fail', 'failed', 'critical', 'missing', 'unavailable'].includes(normalized)) return 'critical';
return 'unknown';
}

function statusLabel(status?: ReleaseHealthStatus) {
return String(status ?? 'unknown').replaceAll('_', ' ');
}

function checkStatus(check?: ReleaseHealthCheck) {
if (!check) return 'unknown';
if (check.status) return check.status;
return check.present ? 'present' : 'missing';
}

function artifactList(artifacts?: string[]) {
return artifacts && artifacts.length ? artifacts : ['None reported'];
}

function ReleaseHealthSummaryCard({ state }: { state?: ReleaseHealthState }) {
const summary = state?.summary;
const missingRequired = summary?.missing_artifacts?.required_local ?? [];
const missingOptional = summary?.missing_artifacts?.optional_cross_repo ?? [];
const missingArtifacts = [...missingRequired, ...missingOptional];
const contractStatus = checkStatus(summary?.checks?.contract_validation_report);
const apiStatus = checkStatus(summary?.checks?.api_export_validation_report);
const projectStatus = checkStatus(summary?.checks?.project_health_report);
const overall = summary?.overall_status ?? 'unknown';

return (
<article className="card release-health-card">
<div className="card-header">
<div>
<h3>Release health summary</h3>
<p>Static release-readiness artifact for sanitized dashboard review.</p>
</div>
<span className={`badge ${healthTone(overall)}`}>{statusLabel(overall)}</span>
</div>
{state?.unavailable ? <div className="notice warning"><FileWarning size={16} /><span>{state.message ?? `Health summary unavailable. Expected ${RELEASE_HEALTH_SUMMARY_PATH}.`}</span></div> : null}
{!summary ? <div className="notice warning"><FileWarning size={16} /><span>Health summary unavailable. Expected {RELEASE_HEALTH_SUMMARY_PATH}.</span></div> : null}
Comment on lines +35 to +55

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The ReleaseHealthSummaryCard component currently displays a 'Health summary unavailable' warning while the data is still being fetched. This occurs because state is undefined during the loading phase, causing summary to be undefined and triggering the condition on line 55.

Furthermore, line 55 is redundant because the fetchReleaseHealthSummary helper is designed to always return a valid ReleaseHealthState object (using a fallback summary on error), so summary will only be falsy when the query is still loading.

I recommend adding a guard clause to return null if state is undefined to prevent the loading flash, and removing the redundant check on line 55.

function ReleaseHealthSummaryCard({ state }: { state?: ReleaseHealthState }) {
  if (!state) return null;
  const summary = state.summary;
  const missingRequired = summary?.missing_artifacts?.required_local ?? [];
  const missingOptional = summary?.missing_artifacts?.optional_cross_repo ?? [];
  const missingArtifacts = [...missingRequired, ...missingOptional];
  const contractStatus = checkStatus(summary?.checks?.contract_validation_report);
  const apiStatus = checkStatus(summary?.checks?.api_export_validation_report);
  const projectStatus = checkStatus(summary?.checks?.project_health_report);
  const overall = summary?.overall_status ?? 'unknown';

  return (
    <article className="card release-health-card">
      <div className="card-header">
        <div>
          <h3>Release health summary</h3>
          <p>Static release-readiness artifact for sanitized dashboard review.</p>
        </div>
        <span className={`badge ${healthTone(overall)}`}>{statusLabel(overall)}</span>
      </div>
      {state.unavailable ? <div className="notice warning"><FileWarning size={16} /><span>{state.message ?? `Health summary unavailable. Expected ${RELEASE_HEALTH_SUMMARY_PATH}.`}</span></div> : null}

<div className="release-health-checks" aria-label="Release health validation checks">
<div><span>Contract validation</span><strong className={`badge ${healthTone(contractStatus)}`}>{statusLabel(contractStatus)}</strong></div>
<div><span>API/export validation</span><strong className={`badge ${healthTone(apiStatus)}`}>{statusLabel(apiStatus)}</strong></div>
<div><span>Project health report</span><strong className={`badge ${healthTone(projectStatus)}`}>{statusLabel(projectStatus)}</strong></div>
</div>
<div className="release-health-columns">
<div>
<h4>Missing artifacts</h4>
<ul>{artifactList(missingArtifacts).map((artifact) => <li key={artifact}>{artifact}</li>)}</ul>
</div>
<div>
<h4>Next actions</h4>
<ul>{artifactList(summary?.next_recommended_actions).map((action) => <li key={action}>{action}</li>)}</ul>
</div>
<div>
<h4>Safety notes</h4>
<ul>{artifactList(summary?.safety_notes).map((note) => <li key={note}>{note}</li>)}</ul>
</div>
</div>
</article>
);
}

function ServiceList({ services }: { services: ServiceHealth[] }) {
return <div className="status-panel">{services.map((service) => <div key={service.id} className="service"><div><strong>{service.name}</strong><p>{service.owner} · {service.domain} · queue {service.queue_depth}</p></div><span className={`badge ${service.status}`}>{service.status}</span></div>)}</div>;
}

export function OverviewPage({ payload }: { payload: DashboardPayload }) {
export function OverviewPage({ payload, releaseHealth }: { payload: DashboardPayload; releaseHealth?: ReleaseHealthState }) {
const compressionData = payload.benchmarks.map((row) => ({ label: row.name, value: row.reduction_percent, auxiliary: row.top_family_coverage }));
return (
<div className="grid">
Expand All @@ -21,6 +90,7 @@ export function OverviewPage({ payload }: { payload: DashboardPayload }) {
<MetricCard title="Replay stable" value={payload.audit_summary.replay_determinism ? 'Yes' : 'No'} description={`${payload.replay.passes} deterministic replay passes`} icon={ShieldCheck} tone={payload.replay.stable ? 'nominal' : 'critical'} />
<MetricCard title="P95 compression" value={ms(payload.audit_summary.p95_compression_ms)} description="Gateway processing latency" icon={Activity} tone={payload.audit_summary.p95_compression_ms > 900 ? 'warning' : 'nominal'} />
</section>
<ReleaseHealthSummaryCard state={releaseHealth} />
<section className="grid cols-2">
<article className="card"><div className="card-header"><div><h3>Compression quality lanes</h3><p>Bars show token reduction; amber dots show top-family coverage.</p></div><span className="badge">live</span></div><BarChart data={compressionData} valueLabel={(value) => percent(value)} /></article>
<article className="card"><div className="card-header"><div><h3>Service dependency health</h3><p>Operational ownership, SLO posture, and queue pressure.</p></div><span className="badge degraded">{payload.audit_summary.degraded_services} degraded</span></div><ServiceList services={payload.services} /></article>
Expand Down
2 changes: 2 additions & 0 deletions dashboard/app/src/lib/api.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { fallbackPayload } from '../mocks/fallbackPayload';
import { fetchReleaseHealthSummary } from './releaseHealth';
import type { DashboardPayload } from '../types/domain';

const API_BASE = import.meta.env.VITE_COMP_TEXT_API_BASE ?? '';
Expand All @@ -20,6 +21,7 @@ export async function fetchDashboard(signal?: AbortSignal): Promise<DashboardPay

export const api = {
dashboard: fetchDashboard,
releaseHealth: (signal?: AbortSignal) => fetchReleaseHealthSummary(API_BASE, signal),
exportJsonUrl: `${API_BASE}/export.json`,
exportCsvUrl: `${API_BASE}/export.csv`,
};
78 changes: 78 additions & 0 deletions dashboard/app/src/lib/releaseHealth.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import { releaseHealthSummaryFallback } from '../mocks/releaseHealthSummary';
import type { ReleaseHealthCheck, ReleaseHealthSummary } from '../types/domain';

export const RELEASE_HEALTH_SUMMARY_PATH = 'docs/reports/dashboard-health-summary.json';

export interface ReleaseHealthState {
summary: ReleaseHealthSummary;
unavailable: boolean;
message?: string;
}

function isRecord(value: unknown): value is Record<string, unknown> {
return typeof value === 'object' && value !== null && !Array.isArray(value);
}

function stringArray(value: unknown): string[] {
return Array.isArray(value) ? value.filter((item): item is string => typeof item === 'string') : [];
}

function normalizeCheck(value: unknown, key: string): ReleaseHealthCheck {
if (!isRecord(value)) return { key, status: 'unknown', present: false };
return {
description: typeof value.description === 'string' ? value.description : undefined,
key: typeof value.key === 'string' ? value.key : key,
path: typeof value.path === 'string' ? value.path : undefined,
present: typeof value.present === 'boolean' ? value.present : undefined,
required: typeof value.required === 'boolean' ? value.required : undefined,
size_bytes: typeof value.size_bytes === 'number' ? value.size_bytes : undefined,
status: typeof value.status === 'string' ? value.status : undefined,
};
}

export function normalizeReleaseHealthSummary(value: unknown): ReleaseHealthSummary | null {
if (!isRecord(value)) return null;
const checks = isRecord(value.checks)
? Object.fromEntries(Object.entries(value.checks).map(([key, check]) => [key, normalizeCheck(check, key)]))
: undefined;
const missing = isRecord(value.missing_artifacts)
? Object.fromEntries(Object.entries(value.missing_artifacts).map(([key, artifacts]) => [key, stringArray(artifacts)]))
: undefined;

return {
overall_status: typeof value.overall_status === 'string' ? value.overall_status : 'unknown',
checks,
required_artifacts_present: stringArray(value.required_artifacts_present),
missing_artifacts: missing,
next_recommended_actions: stringArray(value.next_recommended_actions),
safety_notes: stringArray(value.safety_notes),
generated_at: typeof value.generated_at === 'string' ? value.generated_at : undefined,
synthetic: typeof value.synthetic === 'boolean' ? value.synthetic : undefined,
summary_type: typeof value.summary_type === 'string' ? value.summary_type : undefined,
};
}

function releaseHealthUrl(apiBase: string) {
return `${apiBase.replace(/\/$/, '')}/dashboard-health-summary.json`;
}

async function fetchSummary(path: string, signal?: AbortSignal): Promise<ReleaseHealthSummary> {
const response = await fetch(path, { signal, headers: { Accept: 'application/json' } });
if (!response.ok) throw new Error(`${response.status} ${response.statusText}`);
const summary = normalizeReleaseHealthSummary(await response.json());
if (!summary) throw new Error('Invalid release health summary payload');
return summary;
}

export async function fetchReleaseHealthSummary(apiBase = '', signal?: AbortSignal): Promise<ReleaseHealthState> {
try {
return { summary: await fetchSummary(releaseHealthUrl(apiBase), signal), unavailable: false };
} catch (error) {
const message = error instanceof Error ? error.message : 'Unknown summary load error';
return {
summary: releaseHealthSummaryFallback,
unavailable: true,
message: `Health summary unavailable. Expected ${RELEASE_HEALTH_SUMMARY_PATH}. ${message}`,
};
}
}
45 changes: 45 additions & 0 deletions dashboard/app/src/mocks/releaseHealthSummary.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import type { ReleaseHealthSummary } from '../types/domain';

export const releaseHealthSummaryFallback: ReleaseHealthSummary = {
overall_status: 'unknown',
checks: {
contract_validation_report: {
description: 'Contract schema validation report.',
key: 'contract_validation_report',
path: 'docs/reports/contract-validation-report.md',
present: false,
required: true,
status: 'unavailable',
},
api_export_validation_report: {
description: 'Synthetic API/export contract validation report.',
key: 'api_export_validation_report',
path: 'docs/reports/api-export-validation-report.md',
present: false,
required: true,
status: 'unavailable',
},
project_health_report: {
description: 'Generated project health and release status report.',
key: 'project_health_report',
path: 'docs/reports/project-health-report.md',
present: false,
required: true,
status: 'unavailable',
},
},
missing_artifacts: {
required_local: ['docs/reports/dashboard-health-summary.json'],
optional_cross_repo: [],
},
next_recommended_actions: [
'Generate docs/reports/dashboard-health-summary.json before release review.',
'Run the dashboard with a built static bundle or local backend route that can serve the summary artifact.',
],
safety_notes: [
'Health summary unavailable; showing synthetic fallback metadata only.',
'No real Daimler data, customer payloads, secrets, cookies, tokens, raw production logs, or proprietary documents are included.',
],
synthetic: true,
summary_type: 'dashboard_release_health_fallback',
};
12 changes: 12 additions & 0 deletions dashboard/app/src/styles/app.css

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

31 changes: 31 additions & 0 deletions dashboard/app/src/types/domain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,37 @@ export interface ServiceHealth {
dependencies: string[];
}


export type ReleaseHealthStatus = 'green' | 'yellow' | 'red' | 'unknown' | string;

export interface ReleaseHealthCheck {
description?: string;
key?: string;
path?: string;
present?: boolean;
required?: boolean;
size_bytes?: number;
status?: string;
}

export interface ReleaseHealthMissingArtifacts {
optional_cross_repo?: string[];
required_local?: string[];
[key: string]: string[] | undefined;
}

export interface ReleaseHealthSummary {
overall_status?: ReleaseHealthStatus;
checks?: Record<string, ReleaseHealthCheck>;
required_artifacts_present?: string[];
missing_artifacts?: ReleaseHealthMissingArtifacts;
next_recommended_actions?: string[];
safety_notes?: string[];
generated_at?: string;
synthetic?: boolean;
summary_type?: string;
}

export interface DashboardPayload {
audit_summary: AuditSummary;
benchmarks: BenchmarkResult[];
Expand Down
2 changes: 2 additions & 0 deletions dashboard/app/vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@ import react from '@vitejs/plugin-react';

export default defineConfig({
plugins: [react()],
publicDir: '../../docs/reports',
server: {
proxy: {
'/api': 'http://127.0.0.1:8765',
'/export.json': 'http://127.0.0.1:8765',
'/export.csv': 'http://127.0.0.1:8765',
'/dashboard-health-summary.json': 'http://127.0.0.1:8765',
},
},
build: {
Expand Down
7 changes: 7 additions & 0 deletions dashboard/industrial_dashboard.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
from src.validation.token_telemetry import drift_fingerprint, tokenizer_version

APP_DIST = Path(__file__).resolve().parent / "app" / "dist"
RELEASE_HEALTH_SUMMARY = ROOT / "docs" / "reports" / "dashboard-health-summary.json"


def _now() -> datetime:
Expand Down Expand Up @@ -249,6 +250,12 @@ def _serve_static(self, path: str) -> bool:

def do_GET(self) -> None:
parsed = urlparse(self.path)
if parsed.path == "/dashboard-health-summary.json":
if RELEASE_HEALTH_SUMMARY.exists():
self._send(200, RELEASE_HEALTH_SUMMARY.read_bytes(), "application/json")
return
self._send(404, b'{"error":"dashboard health summary not found"}', "application/json")
return
if parsed.path.startswith("/api/") or parsed.path in {"/export.json", "/export.csv", "/replay"}:
data = dashboard_data()
if parsed.path in {"/api/dashboard", "/export.json", "/replay"}:
Expand Down
11 changes: 7 additions & 4 deletions docs/API_SURFACE.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ surfaces:
| `GET /export.json` | JSON | Stable | Machine-readable export of the same dashboard evidence for CI or air-gapped review. |
| `GET /export.csv` | CSV | Stable | Spreadsheet-friendly review artifact with benchmark and forensic rows. |
| `GET /replay` | JSON | Stable | Replay-oriented evidence view using the dashboard payload shape. |
| `GET /dashboard-health-summary.json` | JSON | Stable | Static release health summary artifact loaded from `docs/reports/dashboard-health-summary.json` for dashboard release-readiness badges and fallback-safe UI rendering. |
| `GET /` | HTML or React app | Stable | Human dashboard entry point; falls back to a stdlib HTML view when the React build is unavailable. |

All stable routes should preserve no-store cache headers, avoid exposing raw
Expand All @@ -69,6 +70,7 @@ summaries include:
- Benchmark tables or charts for p50, p95, p99, RPS, error rate, and payload
size.
- Export links for JSON and CSV evidence handoff.
- Release health summary panel sourced from `docs/reports/dashboard-health-summary.json`, with fallback messaging when that artifact is unavailable.

## Export/report endpoints

Expand All @@ -78,10 +80,11 @@ PR artifacts, and explicit about any schema changes.

`docs/reports/dashboard-health-summary.json` is the dashboard-facing static
release-readiness artifact generated by
`python scripts/generate_dashboard_health_summary.py`. Future dashboard/UI work
can load that JSON directly from committed reports or CI artifacts to render
status badges, missing-artifact callouts, and next-action lists without starting
a live server or using real Daimler data. The companion Markdown report,
`python scripts/generate_dashboard_health_summary.py`. The React dashboard copies
`docs/reports/` into the Vite static bundle and also accepts the stdlib backend
route at `/dashboard-health-summary.json`, allowing the UI to render status
badges, missing-artifact callouts, and next-action lists without an external
service or real Daimler data. The companion Markdown report,
`docs/reports/dashboard-health-summary.md`, provides the human-readable review
summary.

Expand Down
Loading