Skip to content

Commit

Permalink
feat(trace): add a span aside with timing info and feedback (#4071)
Browse files Browse the repository at this point in the history
* feat(trace): add a span aside with timing info

fix import order

* Add annotations in the aside

* css

* PR comments
  • Loading branch information
mikeldking authored Jul 30, 2024
1 parent 4331fdd commit 275ad73
Show file tree
Hide file tree
Showing 12 changed files with 648 additions and 24 deletions.
2 changes: 1 addition & 1 deletion app/src/components/trace/SpanItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ import { css } from "@emotion/react";

import { Flex, Text, View } from "@arizeai/components";

import { TokenCount } from "@phoenix/components/trace/TokenCount";
import { SpanStatusCode } from "@phoenix/pages/project/__generated__/SpansTable_spans.graphql";
import { TokenCount } from "@phoenix/pages/project/TokenCount";

import { LatencyText } from "./LatencyText";
import { SpanKindLabel } from "./SpanKindLabel";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
Icon,
Icons,
Text,
TextProps,
Tooltip,
TooltipTrigger,
TriggerWrap,
Expand All @@ -24,6 +25,10 @@ type TokenCountProps = {
* The number of tokens in the completion
*/
tokenCountCompletion: number;
/**
* The size of the icon and text
*/
textSize?: TextProps["textSize"];
};

/**
Expand All @@ -33,7 +38,7 @@ export function TokenCount(props: TokenCountProps) {
return (
<TooltipTrigger>
<TriggerWrap>
<TokenItem>{props.tokenCountTotal}</TokenItem>
<TokenItem textSize={props.textSize}>{props.tokenCountTotal}</TokenItem>
</TriggerWrap>
<Tooltip>
<Flex direction="column" gap="size-50">
Expand All @@ -51,16 +56,22 @@ export function TokenCount(props: TokenCountProps) {
);
}

function TokenItem({ children }: { children: number }) {
function TokenItem({
children,
...textProps
}: {
children: number;
textSize?: TextProps["textSize"];
}) {
return (
<Flex direction="row" gap="size-25" alignItems="center">
<Flex direction="row" gap="size-50" alignItems="center">
<Icon
svg={<Icons.TokensOutline />}
css={css`
color: var(--ac-global-text-color-900);
`}
/>
<Text>{children}</Text>
<Text {...textProps}>{children}</Text>
</Flex>
);
}
2 changes: 1 addition & 1 deletion app/src/components/trace/TraceTree.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { css } from "@emotion/react";

import { Flex, Text, View } from "@arizeai/components";

import { TokenCount } from "@phoenix/pages/project/TokenCount";
import { TokenCount } from "@phoenix/components/trace/TokenCount";

import { LatencyText } from "./LatencyText";
import { SpanKindIcon } from "./SpanKindIcon";
Expand Down
2 changes: 1 addition & 1 deletion app/src/pages/project/SpansTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import { TimestampCell } from "@phoenix/components/table/TimestampCell";
import { LatencyText } from "@phoenix/components/trace/LatencyText";
import { SpanKindLabel } from "@phoenix/components/trace/SpanKindLabel";
import { SpanStatusCodeIcon } from "@phoenix/components/trace/SpanStatusCodeIcon";
import { TokenCount } from "@phoenix/components/trace/TokenCount";
import { useStreamState } from "@phoenix/contexts/StreamStateContext";
import { useTracingContext } from "@phoenix/contexts/TracingContext";

Expand All @@ -52,7 +53,6 @@ import {
EVALS_KEY_SEPARATOR,
getGqlSort,
} from "./tableUtils";
import { TokenCount } from "./TokenCount";
type SpansTableProps = {
project: SpansTable_spans$key;
};
Expand Down
2 changes: 1 addition & 1 deletion app/src/pages/project/TracesTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import { TimestampCell } from "@phoenix/components/table/TimestampCell";
import { LatencyText } from "@phoenix/components/trace/LatencyText";
import { SpanKindLabel } from "@phoenix/components/trace/SpanKindLabel";
import { SpanStatusCodeIcon } from "@phoenix/components/trace/SpanStatusCodeIcon";
import { TokenCount } from "@phoenix/components/trace/TokenCount";
import { ISpanItem } from "@phoenix/components/trace/types";
import { createSpanTree, SpanTreeNode } from "@phoenix/components/trace/utils";
import { useStreamState } from "@phoenix/contexts/StreamStateContext";
Expand All @@ -59,7 +60,6 @@ import {
EVALS_KEY_SEPARATOR,
getGqlSort,
} from "./tableUtils";
import { TokenCount } from "./TokenCount";
type TracesTableProps = {
project: TracesTable_spans$key;
};
Expand Down
145 changes: 145 additions & 0 deletions app/src/pages/trace/SpanAside.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
import React, { PropsWithChildren, useMemo } from "react";
import { graphql, useRefetchableFragment } from "react-relay";
import { css } from "@emotion/react";

import { Flex, Text, View } from "@arizeai/components";

import { AnnotationLabel } from "@phoenix/components/annotation";
import { LatencyText } from "@phoenix/components/trace/LatencyText";
import { SpanStatusCodeIcon } from "@phoenix/components/trace/SpanStatusCodeIcon";
import { TokenCount } from "@phoenix/components/trace/TokenCount";
import { useSpanStatusCodeColor } from "@phoenix/components/trace/useSpanStatusCodeColor";
import { fullTimeFormatter } from "@phoenix/utils/timeFormatUtils";

import { SpanAside_span$key } from "./__generated__/SpanAside_span.graphql";
import { SpanAsideSpanQuery } from "./__generated__/SpanAsideSpanQuery.graphql";

const annotationListCSS = css`
display: flex;
padding-top: var(--ac-global-dimension-size-50);
flex-direction: column;
gap: var(--ac-global-dimension-size-100);
align-items: flex-start;
`;

/**
* A component that shows the details of a span that is supplementary to the main span details
*/
export function SpanAside(props: { span: SpanAside_span$key }) {
const [data] = useRefetchableFragment<SpanAsideSpanQuery, SpanAside_span$key>(
graphql`
fragment SpanAside_span on Span
@refetchable(queryName: "SpanAsideSpanQuery") {
id
code: statusCode
startTime
endTime
tokenCountTotal
tokenCountPrompt
tokenCountCompletion
spanAnnotations {
id
name
label
annotatorKind
score
}
}
`,
props.span
);
const {
startTime,
endTime,
code,
tokenCountCompletion,
tokenCountPrompt,
tokenCountTotal,
} = data;
const startDate = useMemo(() => new Date(startTime), [startTime]);
const endDate = useMemo(
() => (endTime ? new Date(endTime) : null),
[endTime]
);
const latencyMs = useMemo(() => {
if (!endDate) return null;
return endDate.getTime() - startDate.getTime();
}, [endDate, startDate]);
const statusColor = useSpanStatusCodeColor(code);
const annotations = data.spanAnnotations;
const hasAnnotations = annotations.length > 0;
return (
<View
padding="size-200"
borderColor="dark"
backgroundColor="light"
borderLeftWidth="thin"
width="210px"
flex="none"
minHeight="100%"
>
<Flex direction="column" gap="size-200">
<LabeledValue label="Status">
<Flex direction="row" gap="size-50" alignItems="center">
<SpanStatusCodeIcon statusCode={code} />
<Text textSize="xlarge" color={statusColor}>
{code}
</Text>
</Flex>
</LabeledValue>
<LabeledValue label="Start Time">
<Text textSize="xlarge">{fullTimeFormatter(startDate)}</Text>
</LabeledValue>
{endDate && (
<LabeledValue label="End Time">
<Text textSize="xlarge">{fullTimeFormatter(endDate)}</Text>
</LabeledValue>
)}
<LabeledValue label="Latency">
<Text textSize="xlarge">
{typeof latencyMs === "number" ? (
<LatencyText latencyMs={latencyMs} textSize="xlarge" />
) : (
"--"
)}
</Text>
</LabeledValue>
{tokenCountTotal ? (
<LabeledValue label="Total Tokens" key="tokens">
<TokenCount
tokenCountTotal={tokenCountTotal}
tokenCountPrompt={tokenCountPrompt ?? 0}
tokenCountCompletion={tokenCountCompletion ?? 0}
textSize="xlarge"
/>
</LabeledValue>
) : null}
{hasAnnotations && (
<LabeledValue label="Feedback">
<ul css={annotationListCSS}>
{annotations.map((annotation) => (
<li key={annotation.id}>
<AnnotationLabel annotation={annotation} />
</li>
))}
</ul>
</LabeledValue>
)}
</Flex>
</View>
);
}

function LabeledValue({
label,
children,
}: PropsWithChildren<{ label: string }>) {
return (
<Flex direction="column">
<Text elementType="h3" textSize="medium" color="text-700">
{label}
</Text>
{children}
</Flex>
);
}
81 changes: 71 additions & 10 deletions app/src/pages/trace/SpanDetails.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ import {
TabPane,
Tabs,
Text,
Tooltip,
TooltipTrigger,
View,
ViewProps,
ViewStyleProps,
Expand All @@ -59,9 +61,10 @@ import {
MarkdownDisplayProvider,
} from "@phoenix/components/markdown";
import { SpanKindIcon } from "@phoenix/components/trace";
import { SpanItem } from "@phoenix/components/trace/SpanItem";
import { SpanKindLabel } from "@phoenix/components/trace/SpanKindLabel";
import { useNotifySuccess, useTheme } from "@phoenix/contexts";
import { useFeatureFlag } from "@phoenix/contexts/FeatureFlagsContext";
import { usePreferencesContext } from "@phoenix/contexts/PreferencesContext";
import {
AttributeDocument,
AttributeEmbedding,
Expand All @@ -85,6 +88,7 @@ import {
SpanDetailsQuery$data,
} from "./__generated__/SpanDetailsQuery.graphql";
import { EditSpanAnnotationsButton } from "./EditSpanAnnotationsButton";
import { SpanAside } from "./SpanAside";
import { SpanCodeDropdown } from "./SpanCodeDropdown";
import { SpanFeedback } from "./SpanFeedback";
import { SpanToDatasetExampleDialog } from "./SpanToDatasetExampleDialog";
Expand Down Expand Up @@ -195,6 +199,7 @@ export function SpanDetails({
name
}
...SpanFeedback_annotations
...SpanAside_span
}
}
}
Expand All @@ -214,6 +219,10 @@ export function SpanDetails({
return spanHasException(span);
}, [span]);
const showAnnotations = useFeatureFlag("annotations");
const showSpanAside = usePreferencesContext((store) => store.showSpanAside);
const setShowSpanAside = usePreferencesContext(
(store) => store.setShowSpanAside
);
return (
<Flex direction="column" flex="1 1 auto" height="100%">
<View
Expand All @@ -223,13 +232,17 @@ export function SpanDetails({
paddingEnd="size-200"
flex="none"
>
<Flex
direction="row"
gap="size-200"
justifyContent="space-between"
alignItems="center"
>
<SpanItem {...span} />
<Flex direction="row" gap="size-200" alignItems="center">
<Flex
direction="row"
gap="size-100"
width="100%"
height="100%"
alignItems="center"
>
<SpanKindLabel spanKind={span.spanKind} />
<Text>{span.name}</Text>
</Flex>
<Flex flex="none" direction="row" alignItems="center" gap="size-100">
<SpanCodeDropdown
traceId={span.context.traceId}
Expand All @@ -245,9 +258,35 @@ export function SpanDetails({
</Flex>
</Flex>
</View>
<Tabs>
<Tabs
extra={
<TooltipTrigger placement="start" offset={5}>
<Button
variant="default"
size="compact"
aria-label="Toggle showing span details"
onClick={() => {
setShowSpanAside(!showSpanAside);
}}
icon={
<Icon
svg={showSpanAside ? <Icons.SlideIn /> : <Icons.SlideOut />}
/>
}
/>
<Tooltip>
{showSpanAside ? "Hide Span Details" : "Show Span Details"}
</Tooltip>
</TooltipTrigger>
}
>
<TabPane name={"Info"}>
<SpanInfo span={span} />
<Flex direction="row" height="100%">
<SpanInfoWrap>
<SpanInfo span={span} />
</SpanInfoWrap>
{showSpanAside ? <SpanAside span={span} /> : null}
</Flex>
</TabPane>
<TabPane
name={"Feedback"}
Expand Down Expand Up @@ -287,6 +326,28 @@ export function SpanDetails({
);
}

const spanInfoWrapCSS = css`
flex: 1 1 auto;
overflow-y: auto;
// Overflow fails to take into account padding
& > *:after {
content: "";
display: block;
height: var(--ac-global-dimension-static-size-400);
}
`;

/**
* A wrapper for the span info to style it with the appropriate overflow
*/
function SpanInfoWrap({ children }: PropsWithChildren) {
return (
<div css={spanInfoWrapCSS} data-testid="span-info-wrap">
{children}
</div>
);
}

function AddSpanToDatasetButton({ span }: { span: Span }) {
const [dialog, setDialog] = useState<ReactNode>(null);
const notifySuccess = useNotifySuccess();
Expand Down
Loading

0 comments on commit 275ad73

Please sign in to comment.