From fd62cdaab8fe686ae6bed74907a5c006221602f3 Mon Sep 17 00:00:00 2001 From: "andrii.dudar" Date: Fri, 24 Jan 2025 10:33:29 +0100 Subject: [PATCH] [OPIK-625] [FE] Add average values for duration, usage, and total cost estimation in project list table --- .../pages/CompareExperimentsPage/helpers.ts | 3 +- .../pages/HomePage/ObservabilitySection.tsx | 7 +++ .../pages/ProjectsPage/ProjectsPage.tsx | 61 +++++++++++++++++++ .../pages/TracesPage/TracesSpansTab.tsx | 16 ++++- .../DataTableHeaders/HeaderStatistic.tsx | 8 +-- apps/opik-frontend/src/lib/utils.ts | 4 ++ apps/opik-frontend/src/types/datasets.ts | 7 +-- apps/opik-frontend/src/types/projects.ts | 12 ++++ apps/opik-frontend/src/types/shared.ts | 13 +++- apps/opik-frontend/src/types/traces.ts | 8 +-- 10 files changed, 115 insertions(+), 24 deletions(-) diff --git a/apps/opik-frontend/src/components/pages/CompareExperimentsPage/helpers.ts b/apps/opik-frontend/src/components/pages/CompareExperimentsPage/helpers.ts index 54222cf951..e588b90825 100644 --- a/apps/opik-frontend/src/components/pages/CompareExperimentsPage/helpers.ts +++ b/apps/opik-frontend/src/components/pages/CompareExperimentsPage/helpers.ts @@ -1,6 +1,5 @@ import uniq from "lodash/uniq"; -import { AverageFeedbackScore } from "@/types/datasets"; -import { ROW_HEIGHT } from "@/types/shared"; +import { AverageFeedbackScore, ROW_HEIGHT } from "@/types/shared"; interface GetFeedbackScoreMapArguments { experiments: { diff --git a/apps/opik-frontend/src/components/pages/HomePage/ObservabilitySection.tsx b/apps/opik-frontend/src/components/pages/HomePage/ObservabilitySection.tsx index 90047045a0..419c00d3c3 100644 --- a/apps/opik-frontend/src/components/pages/HomePage/ObservabilitySection.tsx +++ b/apps/opik-frontend/src/components/pages/HomePage/ObservabilitySection.tsx @@ -7,6 +7,7 @@ import { ArrowRight } from "lucide-react"; import DataTable from "@/components/shared/DataTable/DataTable"; import DataTableNoData from "@/components/shared/DataTableNoData/DataTableNoData"; +import CostCell from "@/components/shared/DataTableCells/CostCell"; import ResourceCell from "@/components/shared/DataTableCells/ResourceCell"; import useProjectsList from "@/api/projects/useProjectsList"; import Loader from "@/components/shared/Loader/Loader"; @@ -35,6 +36,12 @@ export const COLUMNS = convertColumnDataToColumn( resource: RESOURCE_TYPE.project, }, }, + { + id: "total_estimated_cost", + label: "Total cost", + type: COLUMN_TYPE.cost, + cell: CostCell as never, + }, { id: "created_at", label: "Created", diff --git a/apps/opik-frontend/src/components/pages/ProjectsPage/ProjectsPage.tsx b/apps/opik-frontend/src/components/pages/ProjectsPage/ProjectsPage.tsx index 3cd1ef8f44..19b048f39e 100644 --- a/apps/opik-frontend/src/components/pages/ProjectsPage/ProjectsPage.tsx +++ b/apps/opik-frontend/src/components/pages/ProjectsPage/ProjectsPage.tsx @@ -1,9 +1,14 @@ import React, { useCallback, useMemo, useRef, useState } from "react"; import { keepPreviousData } from "@tanstack/react-query"; +import isNumber from "lodash/isNumber"; + +import { formatNumericData } from "@/lib/utils"; import DataTable from "@/components/shared/DataTable/DataTable"; import DataTableNoData from "@/components/shared/DataTableNoData/DataTableNoData"; import DataTablePagination from "@/components/shared/DataTablePagination/DataTablePagination"; import IdCell from "@/components/shared/DataTableCells/IdCell"; +import DurationCell from "@/components/shared/DataTableCells/DurationCell"; +import CostCell from "@/components/shared/DataTableCells/CostCell"; import ResourceCell from "@/components/shared/DataTableCells/ResourceCell"; import useProjectsList from "@/api/projects/useProjectsList"; import { Project } from "@/types/projects"; @@ -51,6 +56,60 @@ export const DEFAULT_COLUMNS: ColumnData[] = [ cell: IdCell as never, sortable: true, }, + { + id: "duration.p50", + label: "Duration (p50)", + type: COLUMN_TYPE.duration, + accessorFn: (row) => row.duration?.p50, + cell: DurationCell as never, + }, + { + id: "duration.p90", + label: "Duration (p90)", + type: COLUMN_TYPE.duration, + accessorFn: (row) => row.duration?.p90, + cell: DurationCell as never, + }, + { + id: "duration.p99", + label: "Duration (p99)", + type: COLUMN_TYPE.duration, + accessorFn: (row) => row.duration?.p99, + cell: DurationCell as never, + }, + { + id: "total_estimated_cost", + label: "Total cost", + type: COLUMN_TYPE.cost, + cell: CostCell as never, + }, + { + id: "usage.total_tokens", + label: "Total tokens (average)", + type: COLUMN_TYPE.number, + accessorFn: (row) => + row.usage && isNumber(row.usage.total_tokens) + ? formatNumericData(row.usage.total_tokens) + : "-", + }, + { + id: "usage.prompt_tokens", + label: "Input tokens (average)", + type: COLUMN_TYPE.number, + accessorFn: (row) => + row.usage && isNumber(row.usage.prompt_tokens) + ? formatNumericData(row.usage.prompt_tokens) + : "-", + }, + { + id: "usage.completion_tokens", + label: "Output tokens (average)", + type: COLUMN_TYPE.number, + accessorFn: (row) => + row.usage && isNumber(row.usage.completion_tokens) + ? formatNumericData(row.usage.completion_tokens) + : "-", + }, { id: "last_updated_at", label: "Last updated", @@ -84,6 +143,8 @@ export const DEFAULT_COLUMN_PINNING: ColumnPinningState = { }; export const DEFAULT_SELECTED_COLUMNS: string[] = [ + "total_estimated_cost", + "duration.p50", "last_updated_at", "created_at", "description", diff --git a/apps/opik-frontend/src/components/pages/TracesPage/TracesSpansTab.tsx b/apps/opik-frontend/src/components/pages/TracesPage/TracesSpansTab.tsx index f48416d758..6b565f9254 100644 --- a/apps/opik-frontend/src/components/pages/TracesPage/TracesSpansTab.tsx +++ b/apps/opik-frontend/src/components/pages/TracesPage/TracesSpansTab.tsx @@ -10,6 +10,7 @@ import { ColumnPinningState, RowSelectionState } from "@tanstack/react-table"; import { RotateCw } from "lucide-react"; import findIndex from "lodash/findIndex"; import isObject from "lodash/isObject"; +import isNumber from "lodash/isNumber"; import get from "lodash/get"; import useTracesOrSpansList, { @@ -123,19 +124,28 @@ const SHARED_COLUMNS: ColumnData[] = [ id: "usage.total_tokens", label: "Total tokens", type: COLUMN_TYPE.number, - accessorFn: (row) => (row.usage ? `${row.usage.total_tokens}` : ""), + accessorFn: (row) => + row.usage && isNumber(row.usage.total_tokens) + ? `${row.usage.total_tokens}` + : "-", }, { id: "usage.prompt_tokens", label: "Total input tokens", type: COLUMN_TYPE.number, - accessorFn: (row) => (row.usage ? `${row.usage.prompt_tokens}` : ""), + accessorFn: (row) => + row.usage && isNumber(row.usage.prompt_tokens) + ? `${row.usage.prompt_tokens}` + : "-", }, { id: "usage.completion_tokens", label: "Total output tokens", type: COLUMN_TYPE.number, - accessorFn: (row) => (row.usage ? `${row.usage.completion_tokens}` : ""), + accessorFn: (row) => + row.usage && isNumber(row.usage.completion_tokens) + ? `${row.usage.completion_tokens}` + : "-", }, { id: "total_estimated_cost", diff --git a/apps/opik-frontend/src/components/shared/DataTableHeaders/HeaderStatistic.tsx b/apps/opik-frontend/src/components/shared/DataTableHeaders/HeaderStatistic.tsx index 4ea8883243..6b22f73cdd 100644 --- a/apps/opik-frontend/src/components/shared/DataTableHeaders/HeaderStatistic.tsx +++ b/apps/opik-frontend/src/components/shared/DataTableHeaders/HeaderStatistic.tsx @@ -1,8 +1,8 @@ import React from "react"; import { ChevronDown } from "lucide-react"; -import round from "lodash/round"; import get from "lodash/get"; +import { formatNumericData } from "@/lib/utils"; import { ColumnStatistic, DropdownOption, @@ -15,8 +15,6 @@ import { DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; -const ROUND_PRECISION = 3; - const OPTIONS: DropdownOption[] = [ { label: "Percentile 50", @@ -32,8 +30,6 @@ const OPTIONS: DropdownOption[] = [ }, ]; -const formatData = (value: number) => String(round(value, ROUND_PRECISION)); - type HeaderStatisticProps = { statistic?: ColumnStatistic; dataFormater?: (value: number) => string; @@ -41,7 +37,7 @@ type HeaderStatisticProps = { const HeaderStatistic: React.FC = ({ statistic, - dataFormater = formatData, + dataFormater = formatNumericData, }) => { const [value, setValue] = React.useState("p50"); switch (statistic?.type) { diff --git a/apps/opik-frontend/src/lib/utils.ts b/apps/opik-frontend/src/lib/utils.ts index 5d52ebd48b..10c9adbb51 100644 --- a/apps/opik-frontend/src/lib/utils.ts +++ b/apps/opik-frontend/src/lib/utils.ts @@ -3,6 +3,7 @@ import isObject from "lodash/isObject"; import isArray from "lodash/isArray"; import last from "lodash/last"; import get from "lodash/get"; +import round from "lodash/round"; import isUndefined from "lodash/isUndefined"; import times from "lodash/times"; import sample from "lodash/sample"; @@ -115,3 +116,6 @@ export const calculateWorkspaceName = ( export const extractIdFromLocation = (location: string) => last(location?.split("/")); + +export const formatNumericData = (value: number, precision = 3) => + String(round(value, precision)); diff --git a/apps/opik-frontend/src/types/datasets.ts b/apps/opik-frontend/src/types/datasets.ts index d16f29e633..a30a477ea1 100644 --- a/apps/opik-frontend/src/types/datasets.ts +++ b/apps/opik-frontend/src/types/datasets.ts @@ -1,5 +1,5 @@ import { TraceFeedbackScore } from "@/types/traces"; -import { DYNAMIC_COLUMN_TYPE } from "@/types/shared"; +import { AverageFeedbackScore, DYNAMIC_COLUMN_TYPE } from "@/types/shared"; export interface Dataset { id: string; @@ -40,11 +40,6 @@ export interface ExperimentOutputColumn { types: DYNAMIC_COLUMN_TYPE[]; } -export interface AverageFeedbackScore { - name: string; - value: number; -} - export interface ExperimentPromptVersion { id: string; commit: string; diff --git a/apps/opik-frontend/src/types/projects.ts b/apps/opik-frontend/src/types/projects.ts index 86cd510f31..447efdfe8e 100644 --- a/apps/opik-frontend/src/types/projects.ts +++ b/apps/opik-frontend/src/types/projects.ts @@ -1,9 +1,21 @@ +import { AverageFeedbackScore, UsageData } from "@/types/shared"; + +export interface ProjectDuration { + p50: number; + p90: number; + p99: number; +} + export interface Project { id: string; name: string; description: string; created_at: string; created_by: string; + usage: UsageData; + feedback_scores: AverageFeedbackScore[]; + total_estimated_cost: number; + duration?: ProjectDuration; last_updated_at: string; last_updated_by: string; last_updated_trace_at?: string; diff --git a/apps/opik-frontend/src/types/shared.ts b/apps/opik-frontend/src/types/shared.ts index e77eddccf2..45334defa6 100644 --- a/apps/opik-frontend/src/types/shared.ts +++ b/apps/opik-frontend/src/types/shared.ts @@ -38,7 +38,7 @@ export type ColumnData = { id: string; label: string; disabled?: boolean; - accessorFn?: (row: T) => string | object; + accessorFn?: (row: T) => string | number | object | undefined; size?: number; type?: COLUMN_TYPE; customMeta?: object; @@ -117,3 +117,14 @@ export type JsonNode = export type UsageType = { [key: string]: number | UsageType; }; + +export interface UsageData { + prompt_tokens: number; + completion_tokens: number; + total_tokens: number; +} + +export interface AverageFeedbackScore { + name: string; + value: number; +} diff --git a/apps/opik-frontend/src/types/traces.ts b/apps/opik-frontend/src/types/traces.ts index f62731224f..e631f30aad 100644 --- a/apps/opik-frontend/src/types/traces.ts +++ b/apps/opik-frontend/src/types/traces.ts @@ -1,3 +1,5 @@ +import { UsageData } from "@/types/shared"; + export enum FEEDBACK_SCORE_TYPE { sdk = "sdk", ui = "ui", @@ -11,12 +13,6 @@ export interface TraceFeedbackScore { value: number; } -export interface UsageData { - prompt_tokens: number; - completion_tokens: number; - total_tokens: number; -} - export interface BaseTraceDataErrorInfo { exception_type: string; message?: string;