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
5 changes: 5 additions & 0 deletions backend/src/ref_backend/api/routes/executions.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,8 @@ async def list_recent_execution_groups( # noqa: PLR0913, PLR0917
offset: int = Query(0, ge=0),
diagnostic_name_contains: str | None = None,
provider_name_contains: str | None = None,
provider_slug: str | None = Query(None, description="Keep only the groups of this provider"),
diagnostic_slug: str | None = Query(None, description="Keep only the groups of this diagnostic"),
dirty: bool | None = None,
successful: bool | None = None,
failure_reason: ExecutionFailureReason | None = None,
Expand All @@ -107,6 +109,7 @@ async def list_recent_execution_groups( # noqa: PLR0913, PLR0917
Supports filtering by:
- diagnostic_name_contains (case-insensitive substring of the diagnostic slug)
- provider_name_contains (case-insensitive substring of the provider slug)
- provider_slug and diagnostic_slug (exact slugs)
- dirty
- successful (filters by latest execution success)
- failure_reason (filters by why the latest execution failed, e.g. `system_error`)
Expand All @@ -119,6 +122,8 @@ async def list_recent_execution_groups( # noqa: PLR0913, PLR0917
filters = ExecutionGroupFilter(
diagnostic_contains=diagnostic_name_contains or None,
provider_contains=provider_name_contains or None,
provider_slug=provider_slug,
diagnostic_slug=diagnostic_slug,
include_superseded=False,
include_retracted=False,
dirty=dirty,
Expand Down
18 changes: 18 additions & 0 deletions backend/tests/test_api/test_routes/test_executions.py
Original file line number Diff line number Diff line change
Expand Up @@ -436,6 +436,24 @@ def test_execution_statistics_match_list(client: TestClient, settings) -> None:
assert unsuccessful["total_count"] == listed["total_count"] - successful["total_count"]


def test_execution_list_filters_by_exact_slugs(client: TestClient, settings) -> None:
"""The slug filters match whole slugs, unlike the `_contains` filters."""
group = client.get(f"{settings.API_V1_STR}/executions?limit=1").json()["data"][0]
provider, diagnostic = group["provider_slug"], group["diagnostic_slug"]

matched = client.get(
f"{settings.API_V1_STR}/executions",
params={"provider_slug": provider, "diagnostic_slug": diagnostic, "limit": 1000},
).json()
assert matched["total_count"] > 0
assert {(g["provider_slug"], g["diagnostic_slug"]) for g in matched["data"]} == {(provider, diagnostic)}

partial = client.get(
f"{settings.API_V1_STR}/executions", params={"diagnostic_slug": diagnostic[:-1], "limit": 1}
).json()
assert all(g["diagnostic_slug"] == diagnostic[:-1] for g in partial["data"])


def test_execution_list_pagination(client: TestClient, settings) -> None:
page1 = client.get(f"{settings.API_V1_STR}/executions?limit=2&offset=0").json()
page2 = client.get(f"{settings.API_V1_STR}/executions?limit=2&offset=2").json()
Expand Down
1 change: 1 addition & 0 deletions changelog/159.feature.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
The failures page can now show passing execution groups, and lists models without failures with an empty state saying how many of their groups passed.
6 changes: 4 additions & 2 deletions frontend/src/client/@tanstack/react-query.gen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -482,6 +482,7 @@ export const executionsListRecentExecutionGroupsQueryKey = (options?: Options<Ex
* Supports filtering by:
* - diagnostic_name_contains (case-insensitive substring of the diagnostic slug)
* - provider_name_contains (case-insensitive substring of the provider slug)
* - provider_slug and diagnostic_slug (exact slugs)
* - dirty
* - successful (filters by latest execution success)
* - failure_reason (filters by why the latest execution failed, e.g. `system_error`)
Expand Down Expand Up @@ -517,6 +518,7 @@ export const executionsListRecentExecutionGroupsInfiniteQueryKey = (options?: Op
* Supports filtering by:
* - diagnostic_name_contains (case-insensitive substring of the diagnostic slug)
* - provider_name_contains (case-insensitive substring of the provider slug)
* - provider_slug and diagnostic_slug (exact slugs)
* - dirty
* - successful (filters by latest execution success)
* - failure_reason (filters by why the latest execution failed, e.g. `system_error`)
Expand Down Expand Up @@ -684,7 +686,7 @@ export const executionsListMetricValuesQueryKey = (options: Options<ExecutionsLi
*
* Fetch metric values for a specific execution (both scalar and series)
*
* - `value_type`: Type of metric values - 'scalar', 'series', or 'all' (required)
* - `value_type`: Type of metric values - 'scalar' or 'series' (required)
* - `format`: Return format - 'json' (default) or 'csv'
* - `offset`: Number of items to skip (default 0)
* - `limit`: Maximum number of items to return (default 50, max 500)
Expand All @@ -711,7 +713,7 @@ export const executionsListMetricValuesInfiniteQueryKey = (options: Options<Exec
*
* Fetch metric values for a specific execution (both scalar and series)
*
* - `value_type`: Type of metric values - 'scalar', 'series', or 'all' (required)
* - `value_type`: Type of metric values - 'scalar' or 'series' (required)
* - `format`: Return format - 'json' (default) or 'csv'
* - `offset`: Number of items to skip (default 0)
* - `limit`: Maximum number of items to return (default 50, max 500)
Expand Down
3 changes: 2 additions & 1 deletion frontend/src/client/sdk.gen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,7 @@ export const executionsGetExecutionStatistics = <ThrowOnError extends boolean =
* Supports filtering by:
* - diagnostic_name_contains (case-insensitive substring of the diagnostic slug)
* - provider_name_contains (case-insensitive substring of the provider slug)
* - provider_slug and diagnostic_slug (exact slugs)
* - dirty
* - successful (filters by latest execution success)
* - failure_reason (filters by why the latest execution failed, e.g. `system_error`)
Expand Down Expand Up @@ -215,7 +216,7 @@ export const executionsListMetricValueFacets = <ThrowOnError extends boolean = f
*
* Fetch metric values for a specific execution (both scalar and series)
*
* - `value_type`: Type of metric values - 'scalar', 'series', or 'all' (required)
* - `value_type`: Type of metric values - 'scalar' or 'series' (required)
* - `format`: Return format - 'json' (default) or 'csv'
* - `offset`: Number of items to skip (default 0)
* - `limit`: Maximum number of items to return (default 50, max 500)
Expand Down
12 changes: 12 additions & 0 deletions frontend/src/client/types.gen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2538,6 +2538,18 @@ export type ExecutionsListRecentExecutionGroupsData = {
* Provider Name Contains
*/
provider_name_contains?: string | null;
/**
* Provider Slug
*
* Keep only the groups of this provider
*/
provider_slug?: string | null;
/**
* Diagnostic Slug
*
* Keep only the groups of this diagnostic
*/
diagnostic_slug?: string | null;
/**
* Dirty
*/
Expand Down
31 changes: 20 additions & 11 deletions frontend/src/components/failures/diagnosticFailuresCard.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Link } from "@tanstack/react-router";
import { format } from "date-fns";
import { ChevronRight, SquareArrowOutUpRight } from "lucide-react";
import { ChevronRight } from "lucide-react";
import { useState } from "react";
import type { ExecutionGroup } from "@/client";
import { Badge } from "@/components/ui/badge";
Expand Down Expand Up @@ -29,9 +29,12 @@ import {
lastSucceededAt,
reasonBreakdown,
} from "@/lib/failures";
import type { MipEra } from "@/lib/mipEras";
import { FailureReasonBadge } from "./failureReasonBadge";
import { HealthDot } from "./health";
import { LogExcerpt } from "./logExcerpt";
import { OpenGroupLink } from "./openGroupLink";
import { PassingGroups } from "./passingGroups";

const day = (date: string) => format(new Date(date), "d MMM yyyy");

Expand Down Expand Up @@ -91,15 +94,7 @@ function FailedGroupItem({ group }: { group: ExecutionGroup }) {
<span className="hidden shrink-0 text-xs text-muted-foreground sm:inline">
{format(new Date(execution.updated_at), "yyyy-MM-dd HH:mm")}
</span>
<Link
to="/executions/$groupId/logs"
params={{ groupId: group.id.toString() }}
aria-label="Open execution group"
title="Open execution group"
className="shrink-0"
>
<SquareArrowOutUpRight className="size-4 text-blue-500 hover:text-blue-300 dark:text-blue-400 dark:hover:text-blue-300" />
</Link>
<OpenGroupLink groupId={group.id} />
</div>
<CollapsibleContent className="px-2 pb-2">
<LogExcerpt groupId={group.id} executionId={execution.id} />
Expand All @@ -109,14 +104,20 @@ function FailedGroupItem({ group }: { group: ExecutionGroup }) {
);
}

/** A diagnostic with failing execution groups, listing each with what its log says about the failure. */
/**
* A diagnostic's failing execution groups, listing each with what its log says about the failure
*
* Given `passingEra`, it also lists the diagnostic's passing groups of that era.
*/
export function DiagnosticFailuresCard({
failures,
filters = {},
passingEra,
}: {
failures: DiagnosticFailures;
/** What the failures were narrowed to, named in the summary */
filters?: FailureFilters;
passingEra?: MipEra;
}) {
const [showAll, setShowAll] = useState(false);
const { diagnostic } = failures;
Expand Down Expand Up @@ -179,6 +180,14 @@ export function DiagnosticFailuresCard({
: `Show all ${groups.length} failed execution groups`}
</Button>
)}
{passingEra && failures.successful > 0 && (
<PassingGroups
diagnostic={diagnostic}
count={failures.successful}
mipEra={passingEra}
model={filters.model}
/>
)}
</CardContent>
</Card>
);
Expand Down
21 changes: 20 additions & 1 deletion frontend/src/components/failures/failureFilters.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Check, ChevronsUpDown, X } from "lucide-react";
import { useState } from "react";
import { Button } from "@/components/ui/button";
import { Checkbox } from "@/components/ui/checkbox";
import {
Command,
CommandEmpty,
Expand All @@ -9,6 +10,7 @@ import {
CommandItem,
CommandList,
} from "@/components/ui/command";
import { Label } from "@/components/ui/label";
import {
Popover,
PopoverContent,
Expand All @@ -21,7 +23,9 @@ import { cn } from "@/lib/utils";
import { type DayRange, DayRangePicker } from "./dayRangePicker";

const countText = ({ groups, diagnostics }: FailureCount) =>
`${groups} failed in ${diagnostics} ${diagnostics === 1 ? "diagnostic" : "diagnostics"}`;
groups === 0
? "no failures"
: `${groups} failed in ${diagnostics} ${diagnostics === 1 ? "diagnostic" : "diagnostics"}`;

/**
* A searchable dropdown picking one value to filter failures by, or all of them
Expand Down Expand Up @@ -144,6 +148,8 @@ export function FailureFilterBar({
allDays,
failureDays,
onDays,
passing,
onPassing,
onClear,
summary,
disabled,
Expand All @@ -165,6 +171,8 @@ export function FailureFilterBar({
/** Days with failures matching the other filters, marked in the calendar */
failureDays?: Set<string>;
onDays: (days: DayRange) => void;
passing: boolean;
onPassing: (passing: boolean) => void;
onClear?: () => void;
summary?: string;
disabled?: boolean;
Expand Down Expand Up @@ -206,6 +214,17 @@ export function FailureFilterBar({
onChange={onDays}
disabled={disabled || !allDays}
/>
<div className="flex items-center gap-2 text-sm">
<Checkbox
id="failures-show-passing"
checked={passing}
onCheckedChange={(checked) => onPassing(checked === true)}
disabled={disabled}
/>
<Label htmlFor="failures-show-passing" className="font-normal">
Show passing groups
</Label>
</div>
{onClear && (
<Button variant="ghost" size="sm" onClick={onClear}>
<X className="size-4" />
Expand Down
35 changes: 35 additions & 0 deletions frontend/src/components/failures/failuresEmptyState.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { CircleCheck } from "lucide-react";
import { Button } from "@/components/ui/button";

/** Shown when nothing matching the filters is failing, with how many groups passed */
export function FailuresEmptyState({
heading,
totals,
onShowPassing,
}: {
heading: string;
/** The groups the filters cover, left out when a reason or time range narrows the failures */
totals?: { total: number; successful: number };
/** Offered when passing groups are hidden and some exist */
onShowPassing?: () => void;
}) {
return (
<div className="flex flex-col items-center gap-3 rounded-lg border border-dashed py-8 text-center">
<CircleCheck className="size-8 text-green-600 dark:text-green-500" />
<div className="space-y-1">
<p className="font-medium">{heading}</p>
{totals && totals.total > 0 && (
<p className="text-sm text-muted-foreground">
{totals.successful} of {totals.total} execution groups passed at the
current version of each diagnostic.
</p>
)}
</div>
{onShowPassing && (
<Button variant="outline" size="sm" onClick={onShowPassing}>
Show passing groups
</Button>
)}
</div>
);
}
17 changes: 17 additions & 0 deletions frontend/src/components/failures/openGroupLink.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { Link } from "@tanstack/react-router";
import { SquareArrowOutUpRight } from "lucide-react";

/** Opens an execution group's logs */
export function OpenGroupLink({ groupId }: { groupId: number }) {
return (
<Link
to="/executions/$groupId/logs"
params={{ groupId: groupId.toString() }}
aria-label="Open execution group"
title="Open execution group"
className="shrink-0"
>
<SquareArrowOutUpRight className="size-4 text-blue-500 hover:text-blue-300 dark:text-blue-400 dark:hover:text-blue-300" />
</Link>
);
}
Loading
Loading