feat(ui): add guardrail jump link in log detail view (#21437)

* feat(ui): add guardrail jump link at top of log detail

* fix(ui): align guardrail jump link to the left

* fix(ui): move guardrail jump link to trace sidebar

* fix(ui): move guardrail pill above event rows in sidebar
This commit is contained in:
Ishaan Jaff 2026-02-17 17:33:06 -08:00 • committed by GitHub
parent 93bbc27c53
commit 5a3a0210cb
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 45 additions and 9 deletions

View file

@ -66,9 +66,6 @@ export function LogDetailContent({ logEntry, onOpenSettings, isLoadingDetails =
const hasGuardrailData = guardrailEntries.length > 0;
const totalMaskedEntities = calculateTotalMaskedEntities(guardrailEntries);
const primaryGuardrailLabel = getGuardrailLabel(guardrailEntries);
const guardrailPolicyNames = Array.from(
new Set(guardrailEntries.map((e: any) => e?.policy_template).filter(Boolean))
) as string[];
// Vector store data
const hasVectorStoreData = checkHasVectorStoreData(metadata);
@ -127,7 +124,7 @@ export function LogDetailContent({ logEntry, onOpenSettings, isLoadingDetails =
)}
{hasGuardrailData && (
<Descriptions.Item label="Guardrail">
<GuardrailLabel label={primaryGuardrailLabel} maskedCount={totalMaskedEntities} policyNames={guardrailPolicyNames} />
<GuardrailLabel label={primaryGuardrailLabel} maskedCount={totalMaskedEntities} />
</Descriptions.Item>
)}
</Descriptions>
@ -225,7 +222,7 @@ function TagsSection({ tags }: { tags: Record<string, any> }) {
);
}
function GuardrailLabel({ label, maskedCount, policyNames }: { label: string; maskedCount: number; policyNames: string[] }) {
function GuardrailLabel({ label, maskedCount }: { label: string; maskedCount: number }) {
const handleClick = () => {
const el = document.getElementById("guardrail-section");
if (el) el.scrollIntoView({ behavior: "smooth" });
@ -239,9 +236,6 @@ function GuardrailLabel({ label, maskedCount, policyNames }: { label: string; ma
{maskedCount} masked
</Tag>
)}
{policyNames.map((name) => (
<Tag key={name} color="purple">{name}</Tag>
))}
</Space>
);
}
@ -424,6 +418,42 @@ function RequestResponseSection({
);
}
export function GuardrailJumpLink({ guardrailEntries }: { guardrailEntries: any[] }) {
const allPassed = guardrailEntries.every((e) => {
const status = e?.guardrail_status || e?.status;
return status === "pass" || status === "passed" || status === "success";
});
const handleClick = () => {
const el = document.getElementById("guardrail-section");
if (el) el.scrollIntoView({ behavior: "smooth" });
};
return (
<div style={{ textAlign: "left", marginBottom: 12 }}>
<div
onClick={handleClick}
style={{
display: "inline-flex",
alignItems: "center",
gap: 6,
padding: "4px 12px",
borderRadius: 16,
cursor: "pointer",
fontSize: 13,
fontWeight: 500,
backgroundColor: allPassed ? "#f0fdf4" : "#fef2f2",
color: allPassed ? "#15803d" : "#b91c1c",
border: `1px solid ${allPassed ? "#bbf7d0" : "#fecaca"}`,
}}
>
{allPassed ? "\u2713" : "\u2717"} {guardrailEntries.length} guardrail{guardrailEntries.length !== 1 ? "s" : ""} evaluated
<span style={{ fontSize: 11, opacity: 0.7 }}>{"\u2193"}</span>
</div>
</div>
);
}
function MetadataSection({ metadata }: { metadata: Record<string, any> }) {
return (
<div className="bg-white rounded-lg shadow w-full max-w-full overflow-hidden mb-6">

View file

@ -12,10 +12,11 @@ import { MCP_CALL_TYPES } from "../constants";
import { getEventDisplayName } from "../utils";
import { DrawerHeader } from "./DrawerHeader";
import { useKeyboardNavigation } from "./useKeyboardNavigation";
import { LogDetailContent } from "./LogDetailContent";
import { LogDetailContent, GuardrailJumpLink } from "./LogDetailContent";
import { sessionSpendLogsCall } from "../../networking";
import { useQuery } from "@tanstack/react-query";
import { getSpendString } from "@/utils/dataUtils";
import { normalizeGuardrailEntries } from "./utils";
import { DRAWER_WIDTH } from "./constants";
import { useLogDetails } from "@/app/(dashboard)/hooks/logDetails/useLogDetails";
@ -323,6 +324,11 @@ export function LogDetailsDrawer({
</div>
<div className="flex-1 overflow-y-auto">
{normalizeGuardrailEntries(metadata?.guardrail_information).length > 0 && (
<div className="px-3 pt-2">
<GuardrailJumpLink guardrailEntries={normalizeGuardrailEntries(metadata?.guardrail_information)} />
</div>
)}
{isSessionMode ? (
<div className="py-1">
{/* Child events — vertical tree line with horizontal connectors */}