fix(ui): keep recorded order when an untimed guardrail shares a phase

This commit is contained in:
Yuneng Jiang 2026-09-15 22:25:30 -07:00
parent 8bb496154a
commit d67c7894dd
No known key found for this signature in database
2 changed files with 30 additions and 3 deletions

View file

@ -33,6 +33,15 @@ const untimedPreCall: Partial<GuardrailInformation> = {
duration: null,
};
const timedPreCall: Partial<GuardrailInformation> = {
guardrail_name: "timed-pre-rail",
guardrail_status: "success",
guardrail_mode: "pre_call",
start_time: 1_700_000_000,
end_time: 1_700_000_000.1,
duration: 0.1,
};
const ranPostCall: Partial<GuardrailInformation> = {
guardrail_name: "ran-rail",
guardrail_status: "success",
@ -117,6 +126,21 @@ describe("GuardrailViewer", () => {
expect(screen.queryByText(/^T\+/)).not.toBeInTheDocument();
});
it("keeps an untimed guardrail ahead of a timed one recorded after it in the same phase", () => {
const untimed = makeGuardrailInformation(untimedPreCall);
const timedPre = makeGuardrailInformation(timedPreCall);
renderWithProviders(<GuardrailViewer data={[untimed, timedPre]} />);
const rows = screen.getAllByTestId("lifecycle-row");
const rowIndex = (label: RegExp): number => rows.findIndex((r) => within(r).queryByText(label) !== null);
const untimedIndex = rowIndex(/Pre-call guardrail: conduct/);
const timedIndex = rowIndex(/Pre-call guardrail: timed-pre-rail/);
expect(untimedIndex).toBeGreaterThanOrEqual(0);
expect(timedIndex).toBeGreaterThanOrEqual(0);
expect(untimedIndex).toBeLessThan(timedIndex);
});
it("anchors offsets on the timed entries and gives the untimed one no fabricated offset", () => {
const untimed = makeGuardrailInformation(untimedPreCall);
const ran = makeGuardrailInformation(ranPostCall);

View file

@ -375,15 +375,18 @@ const belongsOnLifecycle = (e: GuardrailInformation): boolean => isTimed(e) || g
const RequestLifecycle = ({ entries }: { entries: GuardrailInformation[] }) => {
const sorted = useMemo(() => {
const onLifecycle = entries.filter(belongsOnLifecycle);
const timed = onLifecycle.filter(isTimed).sort((a, b) => a.start_time - b.start_time);
return [...timed, ...onLifecycle.filter((e) => !isTimed(e))];
const byStart = onLifecycle.filter(isTimed).sort((a, b) => a.start_time - b.start_time);
const timedSlots = new Map(
onLifecycle.flatMap((e, i) => (isTimed(e) ? [i] : [])).map((slot, k) => [slot, byStart[k]]),
);
return onLifecycle.map((e, i) => timedSlots.get(i) ?? e);
}, [entries]);
const timeline = useMemo(() => {
if (sorted.length === 0) return [];
const timed = sorted.filter(isTimed);
const baseTime = timed.length > 0 ? timed[0].start_time : null;
const baseTime = timed.length > 0 ? Math.min(...timed.map((e) => e.start_time)) : null;
const offsetOf = (e: GuardrailInformation): number | null =>
baseTime === null || !isTimed(e) ? null : Math.round((e.end_time - baseTime) * 1000);
const items: TimelineEntry[] = [];