mirror of
https://github.com/okieselbach/AutopilotMonitor.git
synced 2026-10-24 20:53:06 +00:00
changed charts for app health
This commit is contained in:
@@ -28,6 +28,10 @@ const AppBarChart = dynamic(() => import("../../../components/charts/AppBarChart
|
||||
ssr: false,
|
||||
loading: () => <div className="h-60 flex items-center justify-center text-gray-400 text-sm">Loading chart…</div>,
|
||||
});
|
||||
const AppStackedBarChart = dynamic(() => import("../../../components/charts/AppStackedBarChart"), {
|
||||
ssr: false,
|
||||
loading: () => <div className="h-64 flex items-center justify-center text-gray-400 text-sm">Loading chart…</div>,
|
||||
});
|
||||
|
||||
interface TimeSeriesPoint {
|
||||
bucketStart: string;
|
||||
@@ -543,18 +547,17 @@ export default function AppDetailPage() {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Charts row 1: failure rate + duration over time */}
|
||||
{/* Charts row 1: installs (success vs failure) + duration over time */}
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||
<Card title="Failure Rate over time">
|
||||
<Card title="Installs over time (success vs failure)">
|
||||
{analytics.timeSeries.length > 0 ? (
|
||||
<AppLineChart
|
||||
<AppStackedBarChart
|
||||
data={analytics.timeSeries as unknown as Array<Record<string, unknown>>}
|
||||
xKey="bucketStart"
|
||||
series={[
|
||||
{ dataKey: "failureRate", label: "Failure rate", color: chartColors.danger },
|
||||
stacks={[
|
||||
{ dataKey: "succeeded", label: "Succeeded", color: chartColors.success },
|
||||
{ dataKey: "failed", label: "Failed", color: chartColors.danger },
|
||||
]}
|
||||
yUnit="%"
|
||||
yDomain={[0, "auto"]}
|
||||
formatXTick={formatBucketTick}
|
||||
/>
|
||||
) : (
|
||||
@@ -579,38 +582,39 @@ export default function AppDetailPage() {
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
{/* Charts row 2: version breakdown + phase breakdown */}
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||
<Card title="Failure Rate by Version">
|
||||
{analytics.versionBreakdown.length > 0 ? (
|
||||
<AppBarChart
|
||||
data={analytics.versionBreakdown as unknown as Array<Record<string, unknown>>}
|
||||
categoryKey="appVersion"
|
||||
valueKey="failureRate"
|
||||
valueFormatter={(v) => `${v}%`}
|
||||
barColor={(row) => {
|
||||
const r = Number(row.failureRate);
|
||||
return r >= 20 ? chartColors.danger : r >= 5 ? chartColors.warning : chartColors.success;
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<EmptyState text="No version data captured yet" />
|
||||
{/* Charts row 2: version breakdown + phase breakdown.
|
||||
Each card is hidden when empty so the row collapses gracefully:
|
||||
- Version: will appear once new agents emit AppVersion data
|
||||
- Phase: deferred feature, only shows once we have failure-log samples */}
|
||||
{(analytics.versionBreakdown.length > 0 || analytics.installerPhaseBreakdown.length > 0) && (
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||
{analytics.versionBreakdown.length > 0 && (
|
||||
<Card title="Failure Rate by Version">
|
||||
<AppBarChart
|
||||
data={analytics.versionBreakdown as unknown as Array<Record<string, unknown>>}
|
||||
categoryKey="appVersion"
|
||||
valueKey="failureRate"
|
||||
valueFormatter={(v) => `${v}%`}
|
||||
barColor={(row) => {
|
||||
const r = Number(row.failureRate);
|
||||
return r >= 20 ? chartColors.danger : r >= 5 ? chartColors.warning : chartColors.success;
|
||||
}}
|
||||
/>
|
||||
</Card>
|
||||
)}
|
||||
</Card>
|
||||
<Card title="Installer Phase (failures only)">
|
||||
{analytics.installerPhaseBreakdown.length > 0 ? (
|
||||
<AppBarChart
|
||||
data={analytics.installerPhaseBreakdown as unknown as Array<Record<string, unknown>>}
|
||||
categoryKey="phase"
|
||||
valueKey="failed"
|
||||
horizontal
|
||||
barColor={chartColors.danger}
|
||||
/>
|
||||
) : (
|
||||
<EmptyState text="No failure phases recorded" />
|
||||
{analytics.installerPhaseBreakdown.length > 0 && (
|
||||
<Card title="Installer Phase (failures only)">
|
||||
<AppBarChart
|
||||
data={analytics.installerPhaseBreakdown as unknown as Array<Record<string, unknown>>}
|
||||
categoryKey="phase"
|
||||
valueKey="failed"
|
||||
horizontal
|
||||
barColor={chartColors.danger}
|
||||
/>
|
||||
</Card>
|
||||
)}
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Top failure codes table */}
|
||||
<Card title="Top Failure Codes">
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
"use client";
|
||||
|
||||
import {
|
||||
ResponsiveContainer,
|
||||
BarChart,
|
||||
Bar,
|
||||
XAxis,
|
||||
YAxis,
|
||||
CartesianGrid,
|
||||
Tooltip,
|
||||
Legend,
|
||||
} from "recharts";
|
||||
import { chartColors, tooltipStyle, axisTick, axisLine, legendStyle } from "./chartTheme";
|
||||
|
||||
export interface StackedSeries {
|
||||
dataKey: string;
|
||||
label: string;
|
||||
color: string;
|
||||
}
|
||||
|
||||
export interface AppStackedBarChartProps {
|
||||
data: Array<Record<string, unknown>>;
|
||||
xKey: string;
|
||||
stacks: StackedSeries[];
|
||||
height?: number;
|
||||
formatXTick?: (value: unknown) => string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stacked bar chart used by the App Health detail page for "Installs per
|
||||
* bucket (success vs failure)". Better than a failure-rate line when sample
|
||||
* size per bucket is small — the rate is binary (0 % / 100 %) which produces
|
||||
* useless spikes, while stacked counts immediately show both volume and
|
||||
* success/failure ratio.
|
||||
*/
|
||||
export default function AppStackedBarChart({
|
||||
data,
|
||||
xKey,
|
||||
stacks,
|
||||
height = 260,
|
||||
formatXTick,
|
||||
}: AppStackedBarChartProps) {
|
||||
return (
|
||||
<ResponsiveContainer width="100%" height={height}>
|
||||
<BarChart data={data} margin={{ top: 8, right: 16, left: 0, bottom: 4 }}>
|
||||
<CartesianGrid stroke={chartColors.gridLine} strokeDasharray="3 3" vertical={false} />
|
||||
<XAxis
|
||||
dataKey={xKey}
|
||||
tick={axisTick}
|
||||
axisLine={axisLine}
|
||||
tickLine={false}
|
||||
tickFormatter={formatXTick ? (v: unknown) => formatXTick(v) : undefined}
|
||||
minTickGap={24}
|
||||
/>
|
||||
<YAxis
|
||||
tick={axisTick}
|
||||
axisLine={axisLine}
|
||||
tickLine={false}
|
||||
allowDecimals={false}
|
||||
width={32}
|
||||
/>
|
||||
<Tooltip
|
||||
contentStyle={tooltipStyle}
|
||||
labelStyle={{ color: chartColors.tooltipText, marginBottom: 4 }}
|
||||
cursor={{ fill: chartColors.gridLine, opacity: 0.3 }}
|
||||
labelFormatter={formatXTick ? (l: unknown) => formatXTick(l) : undefined}
|
||||
/>
|
||||
<Legend wrapperStyle={legendStyle} iconType="square" />
|
||||
{stacks.map((s) => (
|
||||
<Bar
|
||||
key={s.dataKey}
|
||||
dataKey={s.dataKey}
|
||||
name={s.label}
|
||||
stackId="installs"
|
||||
fill={s.color}
|
||||
radius={[0, 0, 0, 0]}
|
||||
/>
|
||||
))}
|
||||
</BarChart>
|
||||
</ResponsiveContainer>
|
||||
);
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user