Skip to content

Commit 9850413

Browse files
jessrlewopensearch-changeset-bot[bot]TackAdam
authored
Trace Details: Format duration in ms (#10719)
* format duration in ms Signed-off-by: Jessica Lew <[email protected]> * Changeset file for PR #10719 created/updated * include data prepper schema column Signed-off-by: Jessica Lew <[email protected]> --------- Signed-off-by: Jessica Lew <[email protected]> Co-authored-by: opensearch-changeset-bot[bot] <154024398+opensearch-changeset-bot[bot]@users.noreply.github.com> Co-authored-by: Adam Tackett <[email protected]>
1 parent 0141879 commit 9850413

File tree

7 files changed

+117
-6
lines changed

7 files changed

+117
-6
lines changed

changelogs/fragments/10719.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
fix:
2+
- Format duration in ms for discover traces table ([#10719](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/10719))

src/plugins/explore/public/components/data_table/table_cell/table_cell.test.tsx

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import { render, screen, fireEvent } from '@testing-library/react';
88
import { TableCell, ITableCellProps } from './table_cell';
99
import { useDatasetContext } from '../../../application/context';
1010
import { useTraceFlyoutContext } from '../../../application/pages/traces/trace_flyout/trace_flyout_context';
11-
import { isOnTracesPage } from './trace_utils/trace_utils';
1211

1312
jest.mock('../../../application/context', () => ({
1413
useDatasetContext: jest.fn(),
@@ -33,6 +32,7 @@ describe('TableCell', () => {
3332
onFilter: mockOnFilter,
3433
fieldMapping: { value: 'test' },
3534
isTimeField: false,
35+
isOnTracesPage: false,
3636
};
3737

3838
beforeEach(() => {
@@ -133,6 +133,37 @@ describe('TableCell', () => {
133133
expect(screen.getByTestId('osdDocTableCellDataField')).toBeInTheDocument();
134134
});
135135

136+
describe('Duration cell functionality', () => {
137+
it('renders duration in milliseconds on traces page', () => {
138+
render(
139+
<TableCell
140+
{...defaultProps}
141+
columnId="durationNano"
142+
sanitizedCellValue="<span>2,000,000</span>"
143+
isOnTracesPage={true}
144+
/>
145+
);
146+
147+
const cellContent = screen.getByTestId('osdDocTableCellDataField');
148+
expect(cellContent).toBeInTheDocument();
149+
expect(cellContent.innerHTML).toBe('<span>2 ms</span>');
150+
});
151+
152+
it('renders regular cell content when not on traces page', () => {
153+
render(
154+
<TableCell
155+
{...defaultProps}
156+
columnId="durationNano"
157+
sanitizedCellValue="<span>2,000,000</span>"
158+
/>
159+
);
160+
161+
const cellContent = screen.getByTestId('osdDocTableCellDataField');
162+
expect(cellContent).toBeInTheDocument();
163+
expect(cellContent.innerHTML).toBe('<span>2,000,000</span>');
164+
});
165+
});
166+
136167
// Tests for trace timeline functionality
137168
describe('Span ID link functionality', () => {
138169
const spanIdProps: ITableCellProps = {

src/plugins/explore/public/components/data_table/table_cell/table_cell.tsx

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,13 @@ import { EuiButtonIcon, EuiToolTip } from '@elastic/eui';
1010
import { i18n } from '@osd/i18n';
1111
import { DocViewFilterFn, OpenSearchSearchHit } from '../../../types/doc_views_types';
1212
import { useDatasetContext } from '../../../application/context';
13-
import { isSpanIdColumn, TraceFlyoutButton, SpanIdLink } from './trace_utils/trace_utils';
13+
import {
14+
isSpanIdColumn,
15+
TraceFlyoutButton,
16+
SpanIdLink,
17+
DurationTableCell,
18+
isDurationColumn,
19+
} from './trace_utils/trace_utils';
1420

1521
export interface ITableCellProps {
1622
columnId: string;
@@ -46,6 +52,8 @@ export const TableCellUI = ({
4652
dataset={dataset}
4753
setIsRowSelected={setIsRowSelected}
4854
/>
55+
) : isOnTracesPage && isDurationColumn(columnId) ? (
56+
<DurationTableCell sanitizedCellValue={sanitizedCellValue} />
4957
) : (
5058
<span
5159
className="exploreDocTableCell__dataField"

src/plugins/explore/public/components/data_table/table_cell/trace_utils/trace_utils.test.tsx

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { render, screen, fireEvent } from '@testing-library/react';
88
import {
99
isOnTracesPage,
1010
isSpanIdColumn,
11+
isDurationColumn,
1112
extractFieldFromRowData,
1213
buildTraceDetailsUrl,
1314
getTraceDetailsUrlParams,
@@ -16,6 +17,7 @@ import {
1617
TraceFlyoutButton,
1718
navigateToTraceDetailsWithSpan,
1819
getStatusCodeColor,
20+
DurationTableCell,
1921
} from './trace_utils';
2022

2123
const mockLocation = {
@@ -126,6 +128,32 @@ describe('trace_utils', () => {
126128
});
127129
});
128130

131+
describe('isDurationColumn', () => {
132+
it('should return true for duration columns', () => {
133+
expect(isDurationColumn('durationNano')).toBe(true);
134+
expect(isDurationColumn('durationInNanos')).toBe(true);
135+
});
136+
137+
it('should return false for other column names', () => {
138+
expect(isDurationColumn('duration')).toBe(false);
139+
expect(isDurationColumn('durationMs')).toBe(false);
140+
expect(isDurationColumn('spanId')).toBe(false);
141+
expect(isDurationColumn('traceId')).toBe(false);
142+
expect(isDurationColumn('')).toBe(false);
143+
});
144+
145+
it('should be case sensitive', () => {
146+
expect(isDurationColumn('durationnano')).toBe(false);
147+
expect(isDurationColumn('DURATIONNANO')).toBe(false);
148+
expect(isDurationColumn('DurationInNanos')).toBe(false);
149+
});
150+
151+
it('should handle null and undefined', () => {
152+
expect(isDurationColumn(null as any)).toBe(false);
153+
expect(isDurationColumn(undefined as any)).toBe(false);
154+
});
155+
});
156+
129157
describe('extractFieldFromRowData', () => {
130158
const TRACE_ID_FIELDS = [
131159
'traceId',
@@ -270,6 +298,18 @@ describe('trace_utils', () => {
270298
});
271299
});
272300

301+
describe('DurationTableCell', () => {
302+
it('should render duration in milliseconds', () => {
303+
render(<DurationTableCell sanitizedCellValue="<span>2,000,000</span>" />);
304+
expect(screen.getByText('2 ms')).toBeInTheDocument();
305+
});
306+
307+
it('should handle negative values', () => {
308+
render(<DurationTableCell sanitizedCellValue="-1000000" />);
309+
expect(screen.getByText('0 ms')).toBeInTheDocument();
310+
});
311+
});
312+
273313
describe('buildTraceDetailsUrl', () => {
274314
beforeEach(() => {
275315
mockLocation.pathname = '/app/explore/traces';

src/plugins/explore/public/components/data_table/table_cell/trace_utils/trace_utils.tsx

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@ import { OpenSearchSearchHit } from '../../../../types/doc_views_types';
1111
import { DataView as Dataset } from '../../../../../../data/common';
1212
import './trace_utils.scss';
1313
import { useTraceFlyoutContext } from '../../../../application/pages/traces/trace_flyout/trace_flyout_context';
14+
import {
15+
round,
16+
nanoToMilliSec,
17+
} from '../../../../application/pages/traces/trace_details/public/utils/helper_functions';
1418

1519
export const isOnTracesPage = (): boolean => {
1620
return (
@@ -23,6 +27,10 @@ export const isSpanIdColumn = (columnId: string): boolean => {
2327
return columnId === 'spanId' || columnId === 'span_id' || columnId === 'spanID';
2428
};
2529

30+
export const isDurationColumn = (columnId: string) => {
31+
return columnId === 'durationNano' || columnId === 'durationInNanos';
32+
};
33+
2634
export const extractFieldFromRowData = (
2735
rowData: OpenSearchSearchHit<Record<string, unknown>>,
2836
fields: readonly string[]
@@ -223,3 +231,22 @@ export const getStatusCodeColor = (statusCode: number | undefined): string => {
223231
if (statusCode >= 500 && statusCode < 600) return 'danger';
224232
return 'default';
225233
};
234+
235+
interface DurationTableCellProps {
236+
sanitizedCellValue: string;
237+
}
238+
239+
export const DurationTableCell: React.FC<DurationTableCellProps> = ({ sanitizedCellValue }) => {
240+
const duration = sanitizedCellValue
241+
.replace(/<[^>]*>/g, '')
242+
.replace(/,/g, '')
243+
.trim();
244+
245+
const durationLabel = `${round(nanoToMilliSec(Math.max(0, Number(duration))), 2)} ms`;
246+
247+
return (
248+
<span className="exploreDocTableCell__dataField" data-test-subj="osdDocTableCellDataField">
249+
<span>{durationLabel}</span>
250+
</span>
251+
);
252+
};

src/plugins/explore/public/helpers/data_table_helper.test.tsx

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,7 @@ describe('data_table_helper', () => {
198198
const columns = [
199199
'name',
200200
'durationNano',
201+
'durationInNanos',
201202
'resource.attributes.service.name',
202203
'attributes.http.status_code',
203204
'status.code',
@@ -208,10 +209,11 @@ describe('data_table_helper', () => {
208209

209210
expect(result[0].displayName).toBe('Service Identifier');
210211
expect(result[1].displayName).toBe('Duration');
211-
expect(result[2].displayName).toBe('Service');
212-
expect(result[3].displayName).toBe('Status Code');
213-
expect(result[4].displayName).toBe('Status');
214-
expect(result[5].displayName).toBe('SpanID');
212+
expect(result[2].displayName).toBe('Duration');
213+
expect(result[3].displayName).toBe('Service');
214+
expect(result[4].displayName).toBe('Status Code');
215+
expect(result[5].displayName).toBe('Status');
216+
expect(result[6].displayName).toBe('SpanID');
215217
});
216218
});
217219
});

src/plugins/explore/public/helpers/data_table_helper.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ function getColumnDisplayName(column: string): string {
8181
case 'name':
8282
return 'Service Identifier';
8383
case 'durationNano':
84+
case 'durationInNanos':
8485
return 'Duration';
8586
case 'resource.attributes.service.name':
8687
return 'Service';

0 commit comments

Comments
 (0)