Skip to content

Commit ecbbb4e

Browse files
authored
[Agent Traces] improve UX and use discover data table (opensearch-project#11513)
Signed-off-by: Joshua Li <joshuali925@gmail.com>
1 parent 018291f commit ecbbb4e

90 files changed

Lines changed: 3684 additions & 1947 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

cypress/integration/core_opensearch_dashboards/opensearch_dashboards/apps/explore/04/agent_traces.spec.js

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ const selectDatasetAndWaitForData = () => {
3333

3434
cy.osd.setTopNavDate(AGENT_TRACES_START, AGENT_TRACES_END);
3535

36-
cy.get('.agentTracesTable__container .euiBasicTable tbody tr', { timeout: 15000 }).should(
36+
cy.get('.agentTracesTable__container .agentTraces-table tbody tr', { timeout: 15000 }).should(
3737
'have.length.greaterThan',
3838
0
3939
);
@@ -79,7 +79,9 @@ const agentTracesTestSuite = () => {
7979
});
8080

8181
cy.get('.agentTracesTable__container').should('be.visible');
82-
cy.contains('Showing').should('be.visible');
82+
cy.get('.agentTracesTable__container')
83+
.contains(/\d+ of \d+/)
84+
.should('be.visible');
8385

8486
cy.get('.agentTraces__categoryBadge').contains('Agent').should('exist');
8587
});
@@ -97,7 +99,10 @@ const agentTracesTestSuite = () => {
9799

98100
cy.get('.agentTracesTabs__panel--active', { timeout: 15000 }).within(() => {
99101
cy.get('.agentTracesTable__container').should('exist');
100-
cy.get('.euiBasicTable tbody tr', { timeout: 15000 }).should('have.length.greaterThan', 0);
102+
cy.get('.agentTraces-table tbody tr', { timeout: 15000 }).should(
103+
'have.length.greaterThan',
104+
0
105+
);
101106
});
102107
});
103108
it('should expand a trace row to show child spans', () => {
@@ -121,7 +126,7 @@ const agentTracesTestSuite = () => {
121126
it('should open flyout when clicking a trace row', () => {
122127
selectDatasetAndWaitForData();
123128

124-
cy.get('.agentTracesTable__clickableRow').first().click();
129+
cy.get('[data-test-subj="agentTracesTimeLink"]').first().click();
125130
cy.get('.agentTracesFlyout', { timeout: 15000 }).should('be.visible');
126131
cy.get('.agentTracesFlyout').within(() => {
127132
cy.get('#trace-details-flyout').should('contain.text', 'POST /plan');
@@ -139,9 +144,9 @@ const agentTracesTestSuite = () => {
139144

140145
cy.get('.agentTracesFlyout__detailPanel').should('be.visible');
141146
cy.contains('Metadata').should('be.visible');
142-
cy.contains('OPERATION').should('be.visible');
143-
cy.contains('DURATION').should('be.visible');
144-
cy.contains('SPAN ID').should('be.visible');
147+
cy.contains('Operation:').should('be.visible');
148+
cy.contains('Duration:').should('be.visible');
149+
cy.contains('Span ID:').should('be.visible');
145150

146151
cy.contains('Input / Output').should('be.visible');
147152
});
@@ -153,16 +158,16 @@ const agentTracesTestSuite = () => {
153158
it('should support column sorting on traces table', () => {
154159
selectDatasetAndWaitForData();
155160

156-
cy.get('.euiBasicTable thead').contains('th', 'Name').click();
161+
cy.getElementByTestId('docTableHeaderFieldSort_name').click();
157162

158-
cy.get('.agentTracesTable__container .euiBasicTable tbody tr').should(
163+
cy.get('.agentTracesTable__container .agentTraces-table tbody tr').should(
159164
'have.length.greaterThan',
160165
0
161166
);
162167

163-
cy.get('.euiBasicTable thead').contains('th', 'Name').click();
168+
cy.getElementByTestId('docTableHeaderFieldSort_name').click();
164169

165-
cy.get('.agentTracesTable__container .euiBasicTable tbody tr').should(
170+
cy.get('.agentTracesTable__container .agentTraces-table tbody tr').should(
166171
'have.length.greaterThan',
167172
0
168173
);

src/plugins/agent_traces/common/index.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,38 @@ export const AGENT_TRACES_SPANS_TAB_ID = 'spans';
1919
export enum AgentTracesFlavor {
2020
Traces = 'traces',
2121
}
22+
23+
/** Hardcoded default columns for agent traces / spans tables. These are always
24+
* present and cannot be removed by the user via the fields sidebar. */
25+
export const AGENT_TRACES_DEFAULT_COLUMNS: readonly string[] = [
26+
'kind',
27+
'name',
28+
'status',
29+
'latency',
30+
'totalTokens',
31+
'input',
32+
'output',
33+
];
34+
35+
/** Map from virtual column key to its user-facing display name. */
36+
export const AGENT_TRACES_COLUMN_DISPLAY_NAMES: Record<string, string> = {
37+
kind: 'Kind',
38+
name: 'Name',
39+
status: 'Status',
40+
latency: 'Latency',
41+
totalTokens: 'Tokens',
42+
input: 'Input',
43+
output: 'Output',
44+
};
45+
46+
/** Virtual columns that support sorting in the DataTable. */
47+
export const AGENT_TRACES_SORTABLE_COLUMNS = new Set<string>(['kind', 'name', 'status', 'latency']);
48+
49+
/** Map from virtual column key to the underlying source field(s) used for details/filtering. */
50+
export const AGENT_TRACES_VIRTUAL_COLUMN_SOURCE_FIELDS: Record<string, string> = {
51+
kind: 'attributes.gen_ai.operation.name',
52+
status: 'status.code',
53+
latency: 'durationInNanos',
54+
input: 'attributes.gen_ai.input.messages',
55+
output: 'attributes.gen_ai.output.messages',
56+
};

src/plugins/agent_traces/public/application/hooks/editor_hooks/use_change_query_editor/use_change_query_editor.ts

Lines changed: 30 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,13 @@ import {
1313
selectQuery,
1414
selectIsQueryEditorDirty,
1515
} from '../../../utils/state_management/selectors';
16-
import { DataViewField, IndexPatternField } from '../../../../../../data/common';
16+
import {
17+
DataViewField,
18+
Filter,
19+
IndexPatternField,
20+
IFieldType,
21+
IIndexPattern,
22+
} from '../../../../../../data/common';
1723
import { opensearchFilters } from '../../../../../../data/public';
1824
import { useDatasetContext } from '../../../context';
1925
import { EditorMode } from '../../../utils/state_management/types';
@@ -38,18 +44,33 @@ export const useChangeQueryEditor = () => {
3844
const isQueryEditorDirty = useSelector(selectIsQueryEditorDirty);
3945

4046
const onAddFilter = useCallback(
41-
(field: string | IndexPatternField | DataViewField, values: string, operation: '+' | '-') => {
47+
(
48+
field: string | IndexPatternField | DataViewField,
49+
values: string | string[],
50+
operation: '+' | '-'
51+
) => {
4252
if (!dataset) return;
4353
const languageConfig = queryString.getLanguageService().getLanguage(query.language);
4454
if (!languageConfig) return;
4555

46-
const newFilters = opensearchFilters.generateFilters(
47-
filterManager,
48-
field,
49-
values,
50-
operation,
51-
dataset.id ?? ''
52-
);
56+
let newFilters: Filter[];
57+
// Array values → build a single "phrases" filter (OR condition)
58+
if (Array.isArray(values) && values.length > 1) {
59+
const fieldObj = (typeof field === 'string' ? { name: field } : field) as IFieldType;
60+
const indexPattern = { id: dataset.id ?? '' } as IIndexPattern;
61+
const filter = opensearchFilters.buildPhrasesFilter(fieldObj, values, indexPattern);
62+
filter.meta.negate = operation === '-';
63+
newFilters = [filter];
64+
} else {
65+
const singleValue = Array.isArray(values) ? values[0] : values;
66+
newFilters = opensearchFilters.generateFilters(
67+
filterManager,
68+
field,
69+
singleValue,
70+
operation,
71+
dataset.id ?? ''
72+
);
73+
}
5374
setEditorText((text) => {
5475
const newText =
5576
editorMode === EditorMode.Prompt

src/plugins/agent_traces/public/application/pages/traces/flyout/flyout_detail_panel.test.tsx

Lines changed: 8 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,10 @@ describe('FlyoutDetailPanel', () => {
4141
startTime: '01/01/2025, 12:00:00 AM',
4242
endTime: '01/01/2025, 12:00:01 AM',
4343
latency: '150ms',
44+
durationNanos: 150000000,
4445
totalTokens: 200,
46+
inputTokens: 100,
47+
outputTokens: 100,
4548
totalCost: '—',
4649
};
4750

@@ -73,9 +76,9 @@ describe('FlyoutDetailPanel', () => {
7376
/>
7477
);
7578

76-
expect(screen.getByText('OPERATION')).toBeInTheDocument();
79+
expect(screen.getByText(/Operation:/)).toBeInTheDocument();
7780
expect(screen.getByText('chat')).toBeInTheDocument();
78-
expect(screen.getByText('DURATION')).toBeInTheDocument();
81+
expect(screen.getByText(/Duration:/)).toBeInTheDocument();
7982
expect(screen.getByText('150ms')).toBeInTheDocument();
8083
});
8184

@@ -92,7 +95,7 @@ describe('FlyoutDetailPanel', () => {
9295
expect(dashes.length).toBeGreaterThan(0);
9396
});
9497

95-
it('renders status as OK for success', () => {
98+
it('renders span ID in a badge', () => {
9699
render(
97100
<FlyoutDetailPanel
98101
selectedNode={mockTreeNode}
@@ -101,21 +104,7 @@ describe('FlyoutDetailPanel', () => {
101104
/>
102105
);
103106

104-
expect(screen.getByText('OK')).toBeInTheDocument();
105-
});
106-
107-
it('renders status as ERROR for error trace rows', () => {
108-
const errorRow: TraceRow = { ...mockTraceRow, status: 'error' };
109-
const errorNode: TreeNode = { ...mockTreeNode, traceRow: errorRow };
110-
111-
render(
112-
<FlyoutDetailPanel
113-
selectedNode={errorNode}
114-
selectedTraceRow={errorRow}
115-
onSelectNode={jest.fn()}
116-
/>
117-
);
118-
119-
expect(screen.getByText('ERROR')).toBeInTheDocument();
107+
expect(screen.getByText(/Span ID:/)).toBeInTheDocument();
108+
expect(screen.getByText('span-1-full-id-abcdef')).toBeInTheDocument();
120109
});
121110
});

0 commit comments

Comments
 (0)