Skip to content

Commit

Permalink
[OPIK-623] [FE] Add duration column to Traces/LLM Calls tables (#922)
Browse files Browse the repository at this point in the history
  • Loading branch information
andriidudar authored Dec 18, 2024
1 parent 363a7b8 commit cb7c326
Show file tree
Hide file tree
Showing 16 changed files with 120 additions and 35 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -45,11 +45,13 @@ import CodeCell from "@/components/shared/DataTableCells/CodeCell";
import ListCell from "@/components/shared/DataTableCells/ListCell";
import CostCell from "@/components/shared/DataTableCells/CostCell";
import ErrorCell from "@/components/shared/DataTableCells/ErrorCell";
import DurationCell from "@/components/shared/DataTableCells/DurationCell";
import FeedbackScoreCell from "@/components/shared/DataTableCells/FeedbackScoreCell";
import FeedbackScoreHeader from "@/components/shared/DataTableHeaders/FeedbackScoreHeader";
import TraceDetailsPanel from "@/components/shared/TraceDetailsPanel/TraceDetailsPanel";
import TooltipWrapper from "@/components/shared/TooltipWrapper/TooltipWrapper";
import { formatDate } from "@/lib/date";
import { millisecondsToSeconds } from "@/lib/utils";
import useTracesOrSpansStatistic from "@/hooks/useTracesOrSpansStatistic";
import { useDynamicColumnsCache } from "@/hooks/useDynamicColumnsCache";

Expand Down Expand Up @@ -95,6 +97,14 @@ const SHARED_COLUMNS: ColumnData<BaseTraceData>[] = [
isObject(row.output) ? JSON.stringify(row.output, null, 2) : row.output,
cell: CodeCell as never,
},
{
id: "duration",
label: "Duration",
type: COLUMN_TYPE.duration,
cell: DurationCell as never,
statisticDataFormater: (value: number) =>
isNaN(value) ? "NA" : `${millisecondsToSeconds(value)}s`,
},
{
id: "metadata",
label: "Metadata",
Expand Down Expand Up @@ -172,7 +182,12 @@ const DEFAULT_TRACES_COLUMN_PINNING: ColumnPinningState = {
right: [],
};

const DEFAULT_TRACES_PAGE_COLUMNS: string[] = ["name", "input", "output"];
const DEFAULT_TRACES_PAGE_COLUMNS: string[] = [
"name",
"input",
"output",
"duration",
];

const SELECTED_COLUMNS_KEY = "traces-selected-columns";
const COLUMNS_WIDTH_KEY = "traces-columns-width";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ declare module "@tanstack/react-table" {
verticalAlignment?: CELL_VERTICAL_ALIGNMENT;
overrideRowHeight?: ROW_HEIGHT;
statisticKey?: string;
statisticDataFormater?: (value: number) => string;
custom?: object;
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { CellContext } from "@tanstack/react-table";
import CellWrapper from "@/components/shared/DataTableCells/CellWrapper";
import { millisecondsToSeconds } from "@/lib/utils";

const DurationCell = <TData,>(context: CellContext<TData, number>) => {
const value = context.getValue();

return (
<CellWrapper
metadata={context.column.columnDef.meta}
tableMetadata={context.table.options.meta}
>
{isNaN(value) ? "NA" : `${millisecondsToSeconds(value)}s`}
</CellWrapper>
);
};

export default DurationCell;
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
import React from "react";
import { ChevronDown } from "lucide-react";
import round from "lodash/round";
import get from "lodash/get";

import {
ColumnStatistic,
DropdownOption,
STATISTIC_AGGREGATION_TYPE,
} from "@/types/shared";
import get from "lodash/get";
import {
DropdownMenu,
DropdownMenuCheckboxItem,
DropdownMenuContent,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { ChevronDown } from "lucide-react";
import round from "lodash/round";

const ROUND_PRECISION = 3;

Expand All @@ -31,20 +32,26 @@ const OPTIONS: DropdownOption<string>[] = [
},
];

const formatData = (value: number) => String(round(value, ROUND_PRECISION));

type HeaderStatisticProps = {
statistic?: ColumnStatistic;
dataFormater?: (value: number) => string;
};

const HeaderStatistic: React.FC<HeaderStatisticProps> = ({ statistic }) => {
const HeaderStatistic: React.FC<HeaderStatisticProps> = ({
statistic,
dataFormater = formatData,
}) => {
const [value, setValue] = React.useState<string>("p50");
switch (statistic?.type) {
case STATISTIC_AGGREGATION_TYPE.AVG:
case STATISTIC_AGGREGATION_TYPE.COUNT:
return (
<span className="comet-body-s truncate text-foreground ">
<span className="comet-body-s truncate text-foreground">
<span>{statistic.type.toLowerCase()}</span>
<span className="ml-1 font-semibold">
{String(round(statistic.value, ROUND_PRECISION))}
{dataFormater(statistic.value)}
</span>
</span>
);
Expand All @@ -53,12 +60,10 @@ const HeaderStatistic: React.FC<HeaderStatisticProps> = ({ statistic }) => {
<DropdownMenu>
<DropdownMenuTrigger asChild>
<div className="flex max-w-full">
<span className="comet-body-s truncate text-foreground ">
<span className="comet-body-s truncate text-foreground">
<span>{value}</span>
<span className="ml-1 font-semibold">
{String(
round(get(statistic.value, value, 0), ROUND_PRECISION),
)}
{dataFormater(get(statistic.value, value, 0))}
</span>
</span>
<ChevronDown className="ml-0.5 size-3.5 shrink-0"></ChevronDown>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import React from "react";
import find from "lodash/find";
import { ColumnMeta, TableMeta } from "@tanstack/react-table";
import { COLUMN_TYPE } from "@/types/shared";
import HeaderStatistic from "@/components/shared/DataTableHeaders/HeaderStatistic";
import { cn } from "@/lib/utils";
import { CELL_HORIZONTAL_ALIGNMENT_MAP } from "@/constants/shared";

type CellWrapperProps = {
type HeaderWrapperProps = {
children?: React.ReactNode;
metadata?: ColumnMeta<unknown, unknown>;
tableMetadata?: TableMeta<unknown>;
Expand All @@ -14,24 +14,27 @@ type CellWrapperProps = {
supportStatistic?: boolean;
};

const HeaderWrapper: React.FunctionComponent<CellWrapperProps> = ({
const HeaderWrapper: React.FunctionComponent<HeaderWrapperProps> = ({
children,
metadata,
tableMetadata,
className,
onClick,
supportStatistic = true,
}) => {
const { type, statisticKey } = metadata || {};
const { type, statisticKey, statisticDataFormater } = metadata || {};
const { columnsStatistic } = tableMetadata || {};

const horizontalAlignClass =
type === COLUMN_TYPE.number ? "justify-end" : "justify-start";
CELL_HORIZONTAL_ALIGNMENT_MAP[type!] ?? "justify-start";

const heightClass = columnsStatistic ? "h-14" : "h-11";

if (supportStatistic && columnsStatistic) {
const data = find(columnsStatistic, (s) => s.name === statisticKey);
const columnStatistic = find(
columnsStatistic,
(s) => s.name === statisticKey,
);

return (
<div
Expand All @@ -53,7 +56,10 @@ const HeaderWrapper: React.FunctionComponent<CellWrapperProps> = ({
horizontalAlignClass,
)}
>
<HeaderStatistic statistic={data} />
<HeaderStatistic
statistic={columnStatistic}
dataFormater={statisticDataFormater}
/>
</div>
</div>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import { cn } from "@/lib/utils";
import HeaderWrapper from "@/components/shared/DataTableHeaders/HeaderWrapper";

const COLUMN_TYPE_MAP: Record<
string,
COLUMN_TYPE,
ForwardRefExoticComponent<
Omit<LucideProps, "ref"> & RefAttributes<SVGSVGElement>
>
Expand All @@ -26,6 +26,7 @@ const COLUMN_TYPE_MAP: Record<
[COLUMN_TYPE.number]: Hash,
[COLUMN_TYPE.list]: List,
[COLUMN_TYPE.time]: Clock,
[COLUMN_TYPE.duration]: Clock,
[COLUMN_TYPE.dictionary]: Braces,
[COLUMN_TYPE.numberDictionary]: PenLine,
[COLUMN_TYPE.cost]: Coins,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ export const FilterRow = <TColumnData,>({
switch (filter.type) {
case COLUMN_TYPE.string:
return <StringRow filter={filter} onChange={onChange} />;
case COLUMN_TYPE.duration:
case COLUMN_TYPE.cost:
case COLUMN_TYPE.number:
return <NumberRow filter={filter} onChange={onChange} />;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import AgentGraphTab from "./AgentGraphTab";
import ErrorTab from "./ErrorTab";
import { Button } from "@/components/ui/button";
import { useToast } from "@/components/ui/use-toast";
import { calcDuration, millisecondsToSeconds } from "@/lib/utils";
import { millisecondsToSeconds } from "@/lib/utils";
import { isObjectSpan } from "@/lib/traces";
import isUndefined from "lodash/isUndefined";
import { formatCost } from "@/lib/money";
Expand Down Expand Up @@ -50,7 +50,6 @@ const TraceDataViewer: React.FunctionComponent<TraceDataViewerProps> = ({
const isSpan = isObjectSpan(data);
const type = get(data, "type", TRACE_TYPE_FOR_TREE);
const entity = isSpan ? "span" : "trace";
const duration = calcDuration(data.start_time, data.end_time);
const tokens = data.usage?.total_tokens;

const agentGraphData = get(
Expand Down Expand Up @@ -115,9 +114,9 @@ const TraceDataViewer: React.FunctionComponent<TraceDataViewerProps> = ({
className="flex items-center gap-2 px-1"
>
<Clock className="size-4 shrink-0" />
{isNaN(duration)
{isNaN(data.duration)
? "NA"
: `${millisecondsToSeconds(duration)} seconds`}
: `${millisecondsToSeconds(data.duration)} seconds`}
</div>
</TooltipWrapper>
{isNumber(tokens) && (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
} from "react-complex-tree";
import { BASE_TRACE_DATA_TYPE, Span, Trace } from "@/types/traces";
import { treeRenderers } from "./treeRenderers";
import { calcDuration, getTextWidth } from "@/lib/utils";
import { getTextWidth } from "@/lib/utils";
import { SPANS_COLORS_MAP, TRACE_TYPE_FOR_TREE } from "@/constants/traces";
import { Button } from "@/components/ui/button";
import useDeepMemo from "@/hooks/useDeepMemo";
Expand Down Expand Up @@ -96,7 +96,7 @@ const TraceTreeViewer: React.FunctionComponent<TraceTreeViewerProps> = ({
const sharedData = {
maxStartTime: new Date(trace.start_time).getTime(),
maxEndTime: new Date(trace.end_time).getTime(),
maxDuration: calcDuration(trace.start_time, trace.end_time),
maxDuration: trace.duration,
};

const acc: TreeData = {
Expand All @@ -119,7 +119,7 @@ const TraceTreeViewer: React.FunctionComponent<TraceTreeViewerProps> = ({
trace_id: trace.id,
type: TRACE_TYPE_FOR_TREE,
tokens: trace.usage?.total_tokens,
duration: calcDuration(trace.start_time, trace.end_time),
duration: trace.duration,
name: trace.name,
hasError: Boolean(trace.error_info),
},
Expand All @@ -140,7 +140,7 @@ const TraceTreeViewer: React.FunctionComponent<TraceTreeViewerProps> = ({
...sharedData,
spanColor,
tokens: span.usage?.total_tokens,
duration: calcDuration(span.start_time, span.end_time),
duration: span.duration,
startTimestamp: new Date(span.start_time).getTime(),
hasError: Boolean(span.error_info),
},
Expand Down
23 changes: 23 additions & 0 deletions apps/opik-frontend/src/constants/filters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export const DEFAULT_OPERATOR_MAP: Record<COLUMN_TYPE, FilterOperator> = {
[COLUMN_TYPE.dictionary]: "=",
[COLUMN_TYPE.numberDictionary]: "=",
[COLUMN_TYPE.cost]: "<=",
[COLUMN_TYPE.duration]: "<=",
};

export const OPERATORS_MAP: Record<
Expand Down Expand Up @@ -85,6 +86,28 @@ export const OPERATORS_MAP: Record<
value: "<=",
},
],
[COLUMN_TYPE.duration]: [
{
label: "=",
value: "=",
},
{
label: ">",
value: ">",
},
{
label: ">=",
value: ">=",
},
{
label: "<",
value: "<",
},
{
label: "<=",
value: "<=",
},
],
[COLUMN_TYPE.list]: [
{
label: "contains",
Expand Down
1 change: 1 addition & 0 deletions apps/opik-frontend/src/constants/shared.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export const CELL_VERTICAL_ALIGNMENT_MAP = {
export const CELL_HORIZONTAL_ALIGNMENT_MAP: Record<COLUMN_TYPE, string> = {
[COLUMN_TYPE.number]: "justify-end",
[COLUMN_TYPE.cost]: "justify-end",
[COLUMN_TYPE.duration]: "justify-end",
[COLUMN_TYPE.string]: "justify-start",
[COLUMN_TYPE.list]: "justify-start",
[COLUMN_TYPE.time]: "justify-start",
Expand Down
17 changes: 13 additions & 4 deletions apps/opik-frontend/src/lib/filters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import flatten from "lodash/flatten";
import { Filter } from "@/types/filters";
import { COLUMN_TYPE, DYNAMIC_COLUMN_TYPE } from "@/types/shared";
import { makeEndOfDay, makeStartOfDay } from "@/lib/date";
import { secondsToMilliseconds } from "@/lib/utils";

export const isFilterValid = (filter: Filter) => {
return (
Expand Down Expand Up @@ -75,14 +76,22 @@ const processTimeFilter: (filter: Filter) => Filter | Filter[] = (filter) => {
}
};

const processDurationFilter: (filter: Filter) => Filter = (filter) => ({
...filter,
value: secondsToMilliseconds(Number(filter.value)).toString(),
});

const processFiltersArray = (filters: Filter[]) => {
return flatten(
filters.map((filter) => {
if (filter.type === COLUMN_TYPE.time) {
return processTimeFilter(filter);
switch (filter.type) {
case COLUMN_TYPE.time:
return processTimeFilter(filter);
case COLUMN_TYPE.duration:
return processDurationFilter(filter);
default:
return filter;
}

return filter;
}),
);
};
Expand Down
3 changes: 3 additions & 0 deletions apps/opik-frontend/src/lib/table.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,9 @@ export const mapColumnDataFields = <TColumnData, TData>(
header: columnData.label,
iconType: columnData.iconType,
statisticKey: columnData.statisticKey || columnData.id,
...(columnData.statisticDataFormater && {
statisticDataFormater: columnData.statisticDataFormater,
}),
...(columnData.verticalAlignment && {
verticalAlignment: columnData.verticalAlignment,
}),
Expand Down
8 changes: 4 additions & 4 deletions apps/opik-frontend/src/lib/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,15 +39,15 @@ export const safelyParseJSON = (string: string) => {
}
};

export const calcDuration = (start: string, end: string) => {
return new Date(end).getTime() - new Date(start).getTime();
};

export const millisecondsToSeconds = (milliseconds: number) => {
// rounds with precision, one character after the point
return round(milliseconds / 1000, 1);
};

export const secondsToMilliseconds = (seconds: number) => {
return seconds * 1000;
};

export const getTextWidth = (
text: string[],
properties: {
Expand Down
Loading

0 comments on commit cb7c326

Please sign in to comment.