Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 41 additions & 1 deletion apps/ai-dial-admin/src/components/Runs/Compare/CompareView.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
'use client';

import { IconAdjustmentsHorizontal } from '@tabler/icons-react';
import { ColDef } from 'ag-grid-community';
import { useRouter } from 'next/navigation';
import { FC, useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { createPortal } from 'react-dom';
Expand Down Expand Up @@ -76,6 +77,9 @@ const CompareView: FC<Props> = ({ runId, comparedRunId: comparedRunIdProp }) =>
const [availableMetricGroups, setAvailableMetricGroups] = useState<string[]>([]);
const [selectedMetricGroups, setSelectedMetricGroups] = useState<Set<string>>(new Set());
const areMetricGroupsInitializedRef = useRef(false);
const displayTreeRef = useRef<ColDef[]>([]);
const viewDifferencesOnlyRef = useRef(false);
const hideHighlightsRef = useRef(false);

const {
state: tabState,
Expand Down Expand Up @@ -215,6 +219,16 @@ const CompareView: FC<Props> = ({ runId, comparedRunId: comparedRunIdProp }) =>
setSelectedMetricGroups(groups);
}, []);

const persistDisplayTree = useCallback((tree: ColDef[]) => {
displayTreeRef.current = tree;
}, []);
const persistViewDifferencesOnly = useCallback((value: boolean) => {
viewDifferencesOnlyRef.current = value;
}, []);
const persistHideHighlights = useCallback((value: boolean) => {
hideHighlightsRef.current = value;
}, []);

const closeRowDetail = useCallback(() => {
setSelectedRow(null);
setFocusFieldKey(null);
Expand Down Expand Up @@ -250,6 +264,12 @@ const CompareView: FC<Props> = ({ runId, comparedRunId: comparedRunIdProp }) =>
focusFieldKey={fieldKey}
fieldSchema={fieldSchema}
metricGroupOrder={metricGroupOrder}
initialDisplayTree={displayTreeRef.current}
onDisplayTreeChange={persistDisplayTree}
initialViewDifferencesOnly={viewDifferencesOnlyRef.current}
onViewDifferencesOnlyChange={persistViewDifferencesOnly}
initialHideHighlights={hideHighlightsRef.current}
onHideHighlightsChange={persistHideHighlights}
/>
) : (
<CompareRowDetailPanel
Expand All @@ -262,12 +282,29 @@ const CompareView: FC<Props> = ({ runId, comparedRunId: comparedRunIdProp }) =>
focusFieldKey={fieldKey}
fieldSchema={fieldSchema}
metricGroupOrder={metricGroupOrder}
initialDisplayTree={displayTreeRef.current}
onDisplayTreeChange={persistDisplayTree}
initialViewDifferencesOnly={viewDifferencesOnlyRef.current}
onViewDifferencesOnlyChange={persistViewDifferencesOnly}
initialHideHighlights={hideHighlightsRef.current}
onHideHighlightsChange={persistHideHighlights}
/>
);
const className = isBottom ? ROW_DETAIL_BOTTOM_CLASS : ROW_DETAIL_SIDEBAR_CLASS;
sidebarRef.current.showSidebar(content, className, position);
},
[primaryRunName, comparedRunName, closeRowDetail, switchToBottom, switchToSidebar, fieldSchema, metricGroupOrder],
[
primaryRunName,
comparedRunName,
closeRowDetail,
switchToBottom,
switchToSidebar,
fieldSchema,
metricGroupOrder,
persistDisplayTree,
persistViewDifferencesOnly,
persistHideHighlights,
],
);

showDetailPanelRef.current = showDetailPanel;
Expand All @@ -292,6 +329,9 @@ const CompareView: FC<Props> = ({ runId, comparedRunId: comparedRunIdProp }) =>

useEffect(() => {
closeRowDetail();
displayTreeRef.current = [];
viewDifferencesOnlyRef.current = false;
hideHighlightsRef.current = false;
setAvailableMetricGroups([]);
setSelectedMetricGroups(new Set());
areMetricGroupsInitializedRef.current = false;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
'use client';

import { ColDef } from 'ag-grid-community';
import { FC } from 'react';

import CompareRowDetailPanel from '@/src/components/Runs/Compare/ExecutionResults/RowCompareDetails/CompareRowDetailPanel';
Expand All @@ -16,6 +17,12 @@ interface Props {
focusFieldKey?: string | null;
fieldSchema?: RowDetailFieldSchema;
metricGroupOrder?: readonly string[];
initialDisplayTree?: ColDef[];
onDisplayTreeChange?: (tree: ColDef[]) => void;
initialViewDifferencesOnly?: boolean;
onViewDifferencesOnlyChange?: (value: boolean) => void;
initialHideHighlights?: boolean;
onHideHighlightsChange?: (value: boolean) => void;
}

const CompareRowDetailBottomPanel: FC<Props> = ({
Expand All @@ -27,6 +34,12 @@ const CompareRowDetailBottomPanel: FC<Props> = ({
focusFieldKey,
fieldSchema,
metricGroupOrder,
initialDisplayTree,
onDisplayTreeChange,
initialViewDifferencesOnly,
onViewDifferencesOnlyChange,
initialHideHighlights,
onHideHighlightsChange,
}) => {
return (
<div className="flex flex-col size-full bg-layer-0 overflow-hidden">
Expand All @@ -40,6 +53,12 @@ const CompareRowDetailBottomPanel: FC<Props> = ({
focusFieldKey={focusFieldKey}
fieldSchema={fieldSchema}
metricGroupOrder={metricGroupOrder}
initialDisplayTree={initialDisplayTree}
onDisplayTreeChange={onDisplayTreeChange}
initialViewDifferencesOnly={initialViewDifferencesOnly}
onViewDifferencesOnlyChange={onViewDifferencesOnlyChange}
initialHideHighlights={initialHideHighlights}
onHideHighlightsChange={onHideHighlightsChange}
/>
</div>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,12 @@ interface Props {
focusFieldKey?: string | null;
fieldSchema?: RowDetailFieldSchema;
metricGroupOrder?: readonly string[];
initialDisplayTree?: ColDef[];
onDisplayTreeChange?: (tree: ColDef[]) => void;
initialViewDifferencesOnly?: boolean;
onViewDifferencesOnlyChange?: (value: boolean) => void;
initialHideHighlights?: boolean;
onHideHighlightsChange?: (value: boolean) => void;
}

const CompareRowDetailPanel: FC<Props> = ({
Expand All @@ -55,6 +61,12 @@ const CompareRowDetailPanel: FC<Props> = ({
focusFieldKey,
fieldSchema,
metricGroupOrder = [],
initialDisplayTree,
onDisplayTreeChange,
initialViewDifferencesOnly,
onViewDifferencesOnlyChange,
initialHideHighlights,
onHideHighlightsChange,
}) => {
const t = useI18n();
const [primaryDetail, setPrimaryDetail] = useState<AnalyticsResult | null>(null);
Expand All @@ -63,9 +75,9 @@ const CompareRowDetailPanel: FC<Props> = ({
const [hasError, setHasError] = useState(false);

const [showDisplayPanel, setShowDisplayPanel] = useState(false);
const [viewDifferencesOnly, setViewDifferencesOnly] = useState(false);
const [hideHighlights, setHideHighlights] = useState(false);
const [displayTree, setDisplayTree] = useState<ColDef[]>([]);
const [viewDifferencesOnly, setViewDifferencesOnly] = useState(initialViewDifferencesOnly ?? false);
const [hideHighlights, setHideHighlights] = useState(initialHideHighlights ?? false);
const [displayTree, setDisplayTree] = useState<ColDef[]>(() => initialDisplayTree ?? []);

const isPivotView = position === SidebarPosition.Bottom;
const comparedId = row._compared?.id ?? null;
Expand Down Expand Up @@ -140,10 +152,31 @@ const CompareRowDetailPanel: FC<Props> = ({
setDisplayTree((prev) => buildRowDetailDisplayTree(sections, prev, DEFAULT_HIDDEN_ROW_DETAIL_FIELDS));
}, [sections]);

useEffect(() => {
if (displayTree.length === 0) {
return;
}
onDisplayTreeChange?.(displayTree);
}, [displayTree, onDisplayTreeChange]);

const displaySections = useMemo(() => applyRowDetailDisplayTree(sections, displayTree), [sections, displayTree]);

const onToggleDisplayPanel = useCallback(() => setShowDisplayPanel((prev) => !prev), []);
const onCloseDisplayPanel = useCallback(() => setShowDisplayPanel(false), []);
const onViewDifferencesOnlyUpdate = useCallback(
(value: boolean) => {
setViewDifferencesOnly(value);
onViewDifferencesOnlyChange?.(value);
},
[onViewDifferencesOnlyChange],
);
const onHideHighlightsUpdate = useCallback(
(value: boolean) => {
setHideHighlights(value);
onHideHighlightsChange?.(value);
},
[onHideHighlightsChange],
);

return (
<div className={classNames('relative flex flex-col w-full h-full min-h-0 overflow-hidden bg-layer-0', className)}>
Expand Down Expand Up @@ -200,9 +233,9 @@ const CompareRowDetailPanel: FC<Props> = ({
onClose={onCloseDisplayPanel}
panelClassName={ROW_DETAIL_DISPLAY_PANEL_CLASS}
viewDifferencesOnly={viewDifferencesOnly}
onViewDifferencesOnlyChange={setViewDifferencesOnly}
onViewDifferencesOnlyChange={onViewDifferencesOnlyUpdate}
hideHighlights={hideHighlights}
onHideHighlightsChange={setHideHighlights}
onHideHighlightsChange={onHideHighlightsUpdate}
/>
</div>
)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,8 @@ interface Props {

const HEADER_CELL_BASE = 'h-10 px-3 flex items-center bg-layer-1 border-b border-secondary dial-small-semi-text';
const LEFT_CELL_STICKY = 'sticky left-0';
const VALUE_CELL_BASE = 'p-3 border-b border-r border-tertiary min-w-0 overflow-hidden h-10 flex items-center';
const VALUE_CELL_BASE =
'p-3 border-b border-r border-tertiary min-w-0 overflow-hidden h-full min-h-10 flex items-start self-stretch';

const CompareRowDetailPivotTable: FC<Props> = ({
sections,
Expand Down Expand Up @@ -73,6 +74,7 @@ const CompareRowDetailPivotTable: FC<Props> = ({

const gridTemplateColumns = useMemo(() => getPivotGridTemplateColumns(columns), [columns]);
const gridMinWidth = useMemo(() => getPivotGridMinWidth(columns), [columns]);
const gridTemplateRows = hasComparedMatch ? 'auto auto 1fr 1fr auto' : 'auto auto 1fr 1fr';

useEffect(() => {
scrollPivotToField(scrollContainerRef.current, focusFieldKey);
Expand All @@ -95,7 +97,7 @@ const CompareRowDetailPivotTable: FC<Props> = ({
field={field}
raw={raw}
isFailed={isFailed}
className={mergeClasses(rowBg, 'h-10 min-h-10 items-center self-auto', props.className)}
className={mergeClasses(rowBg, props.className)}
data-compare-diff={props['data-compare-diff']}
onOpenFullscreen={() => onOpenDiff(field)}
/>
Expand All @@ -108,7 +110,10 @@ const CompareRowDetailPivotTable: FC<Props> = ({
ref={scrollContainerRef}
className="flex-1 min-h-0 overflow-auto [scrollbar-width:none] [&::-webkit-scrollbar]:hidden"
>
<div className="dial-tiny-text grid w-full" style={{ gridTemplateColumns, minWidth: gridMinWidth }}>
<div
className="dial-tiny-text grid w-full h-full"
style={{ gridTemplateColumns, gridTemplateRows, minWidth: gridMinWidth }}
>
{/* Section header row */}
<div className={classNames(HEADER_CELL_BASE, LEFT_CELL_STICKY, 'z-30 border-r')} aria-hidden />
{columns.map((column) => {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import { fireEvent, render, screen, waitFor } from '@testing-library/react';
import { describe, expect, test, vi } from 'vitest';

import { SidebarPosition } from '@/src/components/Common/Sidebar/models';
import CompareRowDetailPanel from '@/src/components/Runs/Compare/ExecutionResults/RowCompareDetails/CompareRowDetailPanel';
import { CompareAnalyticsRow } from '@/src/components/Runs/View/models';

vi.mock('@/src/app/[lang]/runs/actions', () => ({
getTestCaseRunResultDetails: vi.fn((id: string) =>
Promise.resolve({
id,
executionStatus: 'SUCCESS',
execDurationMs: 100,
testCaseName: 'Case 1',
}),
),
}));

vi.mock('@/src/components/Runs/Compare/ExecutionResults/RowCompareDetails/CompareRowDetailPivotTable', () => ({
default: () => <div>pivot</div>,
}));

vi.mock('@/src/components/Runs/Compare/ExecutionResults/RowCompareDetails/CompareRowDetailTable', () => ({
default: () => <div>table</div>,
}));

vi.mock('@/src/components/Runs/Compare/ExecutionResults/DiffLegend', () => ({
default: () => null,
}));

vi.mock('@/src/components/Runs/Compare/ExecutionResults/RowCompareDetails/CompareRowDetailDisplayPanel', () => ({
default: ({ hideHighlights, viewDifferencesOnly }: { hideHighlights: boolean; viewDifferencesOnly: boolean }) => (
<div>
<span>{hideHighlights ? 'highlights-hidden' : 'highlights-shown'}</span>
<span>{viewDifferencesOnly ? 'diffs-only' : 'all-rows'}</span>
</div>
),
}));

const row = {
id: 'r1',
testCaseName: 'Case 1',
_compared: { id: 'r2', testCaseName: 'Case 1' },
} as CompareAnalyticsRow;

describe('CompareRowDetailPanel', () => {
test('persists Display tree after sections load', async () => {
const onDisplayTreeChange = vi.fn();

render(
<CompareRowDetailPanel
row={row}
primaryRunName="Run A"
comparedRunName="Run B"
onClose={vi.fn()}
position={SidebarPosition.Bottom}
onSwitchDisplayMode={vi.fn()}
onDisplayTreeChange={onDisplayTreeChange}
/>,
);

await waitFor(() => {
expect(onDisplayTreeChange).toHaveBeenCalled();
});
expect(onDisplayTreeChange.mock.calls.at(-1)?.[0]).toEqual(expect.arrayContaining([expect.any(Object)]));
});

test('restores Display diff toggles from initial values', () => {
render(
<CompareRowDetailPanel
row={row}
primaryRunName="Run A"
comparedRunName="Run B"
onClose={vi.fn()}
position={SidebarPosition.Bottom}
onSwitchDisplayMode={vi.fn()}
initialViewDifferencesOnly
initialHideHighlights
/>,
);

fireEvent.click(screen.getByRole('button', { name: 'Runs.RunCompareDisplay' }));

expect(screen.getByText('highlights-hidden')).toBeInTheDocument();
expect(screen.getByText('diffs-only')).toBeInTheDocument();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,10 @@ export const ROW_DETAIL_PIVOT_LEFT_COL_WIDTH = 212;
export const ROW_DETAIL_PIVOT_STATUS_COL_WIDTH = 135;
export const ROW_DETAIL_PIVOT_RUN_NUMBER_COL_WIDTH = 124;
export const ROW_DETAIL_PIVOT_HTTP_COL_WIDTH = 66;
export const ROW_DETAIL_PIVOT_DURATION_COL_WIDTH = 109;
export const ROW_DETAIL_PIVOT_DURATION_COL_WIDTH = 140;
export const ROW_DETAIL_PIVOT_SCORE_COL_WIDTH = 124;
export const ROW_DETAIL_PIVOT_DEFAULT_COL_WIDTH = 200;
export const ROW_DETAIL_PIVOT_TEXT_COL_WIDTH = 320;

export const ROW_DETAIL_EXECUTION_SECTION_KEY = 'execution';
export const ROW_DETAIL_RUN_NUMBER_FIELD_KEY = 'runNumber';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,5 @@ export enum PivotColumnWidthTier {
Duration = 'duration',
Score = 'score',
Default = 'default',
Text = 'text',
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
ROW_DETAIL_RUN_NUMBER_FIELD_KEY,
ROW_DETAIL_PIVOT_SCORE_COL_WIDTH,
ROW_DETAIL_PIVOT_STATUS_COL_WIDTH,
ROW_DETAIL_PIVOT_TEXT_COL_WIDTH,
} from '@/src/components/Runs/Details/RowDetails/constants';
import { RowDetailField, PivotColumnWidthTier } from '@/src/components/Runs/Details/RowDetails/models';
import { PivotColumn } from '@/src/components/Runs/Details/RowDetails/utils/flatten-pivot-fields';
Expand All @@ -21,6 +22,7 @@ const PIVOT_COLUMN_WIDTH_BY_TIER: Record<PivotColumnWidthTier, number> = {
[PivotColumnWidthTier.Duration]: ROW_DETAIL_PIVOT_DURATION_COL_WIDTH,
[PivotColumnWidthTier.Score]: ROW_DETAIL_PIVOT_SCORE_COL_WIDTH,
[PivotColumnWidthTier.Default]: ROW_DETAIL_PIVOT_DEFAULT_COL_WIDTH,
[PivotColumnWidthTier.Text]: ROW_DETAIL_PIVOT_TEXT_COL_WIDTH,
};

export const resolvePivotFieldWidthTier = (field: RowDetailField): PivotColumnWidthTier => {
Expand All @@ -41,6 +43,10 @@ export const resolvePivotFieldWidthTier = (field: RowDetailField): PivotColumnWi
return PivotColumnWidthTier.Score;
}

if (!field.isNumeric) {
return PivotColumnWidthTier.Text;
}

return PivotColumnWidthTier.Default;
};

Expand Down
Loading
Loading