Skip to content

Commit fd1d0cd

Browse files
committed
feat(cloud-agent-next): filter health dashboard by platform
1 parent 4638a54 commit fd1d0cd

4 files changed

Lines changed: 254 additions & 31 deletions

File tree

apps/web/src/app/admin/api/cloud-agent-next/hooks.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ export type CloudAgentNextFilters = {
1212

1313
export type CloudAgentNextHealthFilters = CloudAgentNextFilters & {
1414
bucket: 'hour' | 'day';
15+
createdOnPlatform?: string | null;
1516
};
1617

1718
export type CloudAgentNextHealthError = {
@@ -36,6 +37,11 @@ function enabledForInterval(params: CloudAgentNextFilters) {
3637
return Boolean(params.startDate && params.endDate);
3738
}
3839

40+
export function useCloudAgentNextHealthPlatforms() {
41+
const trpc = useTRPC();
42+
return useQuery(trpc.admin.cloudAgentNext.listHealthPlatforms.queryOptions());
43+
}
44+
3945
export function useCloudAgentNextHealthOverview(
4046
params: CloudAgentNextHealthFilters,
4147
enabled = true
@@ -59,6 +65,7 @@ export function useCloudAgentNextHealthErrorSessions(
5965
source: error?.source ?? 'run',
6066
stage: error?.stage ?? 'not-selected',
6167
code: error?.code ?? 'not-selected',
68+
createdOnPlatform: params.createdOnPlatform,
6269
}),
6370
enabled: enabledForInterval(params) && Boolean(error),
6471
});

apps/web/src/app/admin/components/CloudAgentNextTelemetry/CloudAgentNextOutcomesPage.tsx

Lines changed: 78 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import AdminPage from '@/app/admin/components/AdminPage';
1616
import {
1717
useCloudAgentNextHealthErrorSessions,
1818
useCloudAgentNextHealthOverview,
19+
useCloudAgentNextHealthPlatforms,
1920
type CloudAgentNextHealthFilters,
2021
} from '@/app/admin/api/cloud-agent-next/hooks';
2122
import { CopyButton } from '@/components/admin/CopyButton';
@@ -88,6 +89,23 @@ type TopError = HealthData['topErrors'][number];
8889
type TooltipPayload = { payload: SeriesPoint };
8990

9091
const DEFAULT_RANGE: RangeValue = DEFAULT_HEALTH_PERIOD;
92+
const ALL_PLATFORMS_VALUE = 'all-platforms';
93+
const UNKNOWN_PLATFORM_VALUE = 'unknown-platform';
94+
const EXACT_PLATFORM_PREFIX = 'platform:';
95+
96+
function platformSelectionValue(platform: string): string {
97+
return `${EXACT_PLATFORM_PREFIX}${platform}`;
98+
}
99+
100+
function createdOnPlatformForSelection(selection: string): string | null | undefined {
101+
if (selection === ALL_PLATFORMS_VALUE) return undefined;
102+
if (selection === UNKNOWN_PLATFORM_VALUE) return null;
103+
if (selection.startsWith(EXACT_PLATFORM_PREFIX)) {
104+
return selection.slice(EXACT_PLATFORM_PREFIX.length);
105+
}
106+
return undefined;
107+
}
108+
91109
const utcLongLabel = new Intl.DateTimeFormat('en-US', {
92110
timeZone: 'UTC',
93111
month: 'short',
@@ -107,15 +125,20 @@ const utcShortDay = new Intl.DateTimeFormat('en-US', {
107125
day: 'numeric',
108126
});
109127

110-
function intervalForRange(range: RangeValue): CloudAgentNextHealthFilters {
128+
function intervalForRange(
129+
range: RangeValue,
130+
platformSelection = ALL_PLATFORMS_VALUE
131+
): CloudAgentNextHealthFilters {
111132
const selectedRange = RANGE_OPTIONS.find(option => option.value === range) ?? RANGE_OPTIONS[3];
133+
const createdOnPlatform = createdOnPlatformForSelection(platformSelection);
112134
const end = new Date();
113135
if (selectedRange.bucket === 'day') end.setUTCHours(0, 0, 0, 0);
114136
else end.setUTCMinutes(0, 0, 0);
115137
return {
116138
startDate: new Date(end.getTime() - selectedRange.durationMs).toISOString(),
117139
endDate: end.toISOString(),
118140
bucket: selectedRange.bucket,
141+
...(createdOnPlatform === undefined ? {} : { createdOnPlatform }),
119142
};
120143
}
121144

@@ -565,8 +588,10 @@ function TopErrors({
565588

566589
export default function CloudAgentNextOutcomesPage() {
567590
const [range, setRange] = useState<RangeValue>(DEFAULT_RANGE);
591+
const [platformSelection, setPlatformSelection] = useState(ALL_PLATFORMS_VALUE);
568592
const [interval, setInterval] = useState(() => intervalForRange(DEFAULT_RANGE));
569593
const [hasLoadedPeriodPreference, setHasLoadedPeriodPreference] = useState(false);
594+
const healthPlatforms = useCloudAgentNextHealthPlatforms();
570595
const health = useCloudAgentNextHealthOverview(interval, hasLoadedPeriodPreference);
571596
const bucket = interval.bucket;
572597

@@ -583,15 +608,21 @@ export default function CloudAgentNextOutcomesPage() {
583608
if (!isHealthPeriod(value)) return;
584609
setStoredHealthPeriod(value);
585610
setRange(value);
586-
setInterval(intervalForRange(value));
611+
setInterval(intervalForRange(value, platformSelection));
612+
}
613+
614+
function updatePlatformSelection(value: string) {
615+
setPlatformSelection(value);
616+
setInterval(intervalForRange(range, value));
587617
}
588618

589619
function refresh() {
590-
const nextInterval = intervalForRange(range);
620+
const nextInterval = intervalForRange(range, platformSelection);
591621
if (
592622
nextInterval.startDate === interval.startDate &&
593623
nextInterval.endDate === interval.endDate &&
594-
nextInterval.bucket === interval.bucket
624+
nextInterval.bucket === interval.bucket &&
625+
nextInterval.createdOnPlatform === interval.createdOnPlatform
595626
) {
596627
void health.refetch();
597628
return;
@@ -620,20 +651,49 @@ export default function CloudAgentNextOutcomesPage() {
620651
Operational outcome trends from best-effort Cloud Agent reporting.
621652
</p>
622653
</div>
623-
<div className="flex min-w-60 flex-col gap-2">
624-
<Label htmlFor="cloud-agent-health-period">Period</Label>
625-
<Select value={range} onValueChange={updateRange}>
626-
<SelectTrigger id="cloud-agent-health-period" className="w-full">
627-
<SelectValue />
628-
</SelectTrigger>
629-
<SelectContent>
630-
{RANGE_OPTIONS.map(option => (
631-
<SelectItem key={option.value} value={option.value}>
632-
{option.label}
633-
</SelectItem>
634-
))}
635-
</SelectContent>
636-
</Select>
654+
<div className="grid w-full gap-3 sm:grid-cols-2 md:w-auto md:min-w-[32rem]">
655+
<div className="flex flex-col gap-2">
656+
<Label htmlFor="cloud-agent-health-period">Period</Label>
657+
<Select value={range} onValueChange={updateRange}>
658+
<SelectTrigger id="cloud-agent-health-period" className="w-full">
659+
<SelectValue />
660+
</SelectTrigger>
661+
<SelectContent>
662+
{RANGE_OPTIONS.map(option => (
663+
<SelectItem key={option.value} value={option.value}>
664+
{option.label}
665+
</SelectItem>
666+
))}
667+
</SelectContent>
668+
</Select>
669+
</div>
670+
<div className="flex flex-col gap-2">
671+
<Label htmlFor="cloud-agent-health-platform">Created on platform</Label>
672+
<Select value={platformSelection} onValueChange={updatePlatformSelection}>
673+
<SelectTrigger id="cloud-agent-health-platform" className="w-full">
674+
<SelectValue />
675+
</SelectTrigger>
676+
<SelectContent>
677+
<SelectItem value={ALL_PLATFORMS_VALUE}>All platforms</SelectItem>
678+
{healthPlatforms.isLoading && (
679+
<SelectItem value="loading-platforms" disabled>
680+
Loading platforms...
681+
</SelectItem>
682+
)}
683+
{healthPlatforms.error && (
684+
<SelectItem value="platforms-unavailable" disabled>
685+
Platforms unavailable
686+
</SelectItem>
687+
)}
688+
{healthPlatforms.data?.map(platform => (
689+
<SelectItem key={platform} value={platformSelectionValue(platform)}>
690+
<span className="font-mono text-xs">{platform}</span>
691+
</SelectItem>
692+
))}
693+
<SelectItem value={UNKNOWN_PLATFORM_VALUE}>Unknown / unlinked</SelectItem>
694+
</SelectContent>
695+
</Select>
696+
</div>
637697
</div>
638698
</div>
639699
<p className="text-muted-foreground text-xs">

apps/web/src/routers/admin-cloud-agent-next-router.test.ts

Lines changed: 87 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -87,12 +87,20 @@ describe('adminCloudAgentNextRouter', () => {
8787
created_at: '2025-01-10T00:20:00.000Z',
8888
},
8989
]);
90-
await db.insert(cli_sessions_v2).values({
91-
session_id: 'ses_admin_outcomes_mapped',
92-
kilo_user_id: adminUser.id,
93-
cloud_agent_session_id: ids.mapped,
94-
created_on_platform: 'vscode',
95-
});
90+
await db.insert(cli_sessions_v2).values([
91+
{
92+
session_id: 'ses_admin_outcomes_mapped',
93+
kilo_user_id: adminUser.id,
94+
cloud_agent_session_id: ids.mapped,
95+
created_on_platform: 'vscode',
96+
},
97+
{
98+
session_id: 'ses_admin_setup_failed_later',
99+
kilo_user_id: adminUser.id,
100+
cloud_agent_session_id: ids.setupFailedLater,
101+
created_on_platform: ' code-review ',
102+
},
103+
]);
96104
await db.insert(cloud_agent_session_runs).values([
97105
{
98106
cloud_agent_session_id: ids.mapped,
@@ -290,6 +298,79 @@ describe('adminCloudAgentNextRouter', () => {
290298
expect(JSON.stringify(health.topErrors)).not.toContain('wrapper_start_failed');
291299
});
292300

301+
it('filters health and error drilldowns by exact or unknown created platform', async () => {
302+
const caller = await createCallerForUser(adminUser.id);
303+
const platforms = await caller.admin.cloudAgentNext.listHealthPlatforms();
304+
const vscodeHealth = await caller.admin.cloudAgentNext.getHealthOverview({
305+
...interval(),
306+
bucket: 'hour',
307+
createdOnPlatform: 'vscode',
308+
});
309+
const legacyRawPlatformHealth = await caller.admin.cloudAgentNext.getHealthOverview({
310+
...interval(),
311+
bucket: 'hour',
312+
createdOnPlatform: ' code-review ',
313+
});
314+
await db
315+
.update(cli_sessions_v2)
316+
.set({ created_on_platform: 'unknown' })
317+
.where(eq(cli_sessions_v2.cloud_agent_session_id, ids.setupFailedLater));
318+
const platformsAfterStoredUnknown = await caller.admin.cloudAgentNext.listHealthPlatforms();
319+
const unknownHealth = await caller.admin.cloudAgentNext.getHealthOverview({
320+
...interval(),
321+
bucket: 'hour',
322+
createdOnPlatform: null,
323+
});
324+
const vscodeRunSessions = await caller.admin.cloudAgentNext.listHealthErrorSessions({
325+
...interval(),
326+
source: 'run',
327+
stage: 'pre_dispatch',
328+
code: 'workspace_setup_failed',
329+
createdOnPlatform: 'vscode',
330+
});
331+
const unknownSetupSessions = await caller.admin.cloudAgentNext.listHealthErrorSessions({
332+
...interval(),
333+
source: 'setup',
334+
stage: 'initial_admission',
335+
code: 'initial_admission_rejected',
336+
createdOnPlatform: null,
337+
});
338+
339+
expect(platforms).toEqual([' code-review ', 'vscode']);
340+
expect(platformsAfterStoredUnknown).toEqual(['vscode']);
341+
expect(vscodeHealth.summary).toEqual({
342+
completedRuns: 1,
343+
failedRuns: 1,
344+
interruptedRuns: 0,
345+
setupFailures: 0,
346+
});
347+
expect(vscodeHealth.topErrors).toEqual([
348+
{ source: 'run', stage: 'pre_dispatch', code: 'workspace_setup_failed', count: 1 },
349+
]);
350+
expect(legacyRawPlatformHealth.summary).toEqual({
351+
completedRuns: 0,
352+
failedRuns: 0,
353+
interruptedRuns: 0,
354+
setupFailures: 1,
355+
});
356+
expect(unknownHealth.summary).toEqual({
357+
completedRuns: 0,
358+
failedRuns: 1,
359+
interruptedRuns: 1,
360+
setupFailures: 2,
361+
});
362+
expect(vscodeRunSessions.rows).toEqual([
363+
expect.objectContaining({ cloudAgentSessionId: ids.mapped }),
364+
]);
365+
expect(unknownSetupSessions).toMatchObject({ totalSessions: 2 });
366+
expect(unknownSetupSessions.rows).toEqual(
367+
expect.arrayContaining([
368+
expect.objectContaining({ cloudAgentSessionId: ids.setupFailed }),
369+
expect.objectContaining({ cloudAgentSessionId: ids.setupFailedLater }),
370+
])
371+
);
372+
});
373+
293374
it('summarizes longer health ranges into daily UTC buckets', async () => {
294375
const caller = await createCallerForUser(adminUser.id);
295376
const health = await caller.admin.cloudAgentNext.getHealthOverview({

0 commit comments

Comments
 (0)