diff --git a/ee/app/assets/javascripts/tracing/components/tracing_scatter_chart.vue b/ee/app/assets/javascripts/tracing/components/tracing_scatter_chart.vue index 4c9c26d70619ce0600c05c61aceca76bfc367c9a..18b51925e910133f56a03c5abe7da94cf9672a2c 100644 --- a/ee/app/assets/javascripts/tracing/components/tracing_scatter_chart.vue +++ b/ee/app/assets/javascripts/tracing/components/tracing_scatter_chart.vue @@ -25,12 +25,12 @@ export default { rangeMin: { type: Date, required: false, - default: () => null, + default: null, }, rangeMax: { type: Date, required: false, - default: () => null, + default: null, }, }, diff --git a/ee/app/assets/javascripts/tracing/components/tracing_table_list.vue b/ee/app/assets/javascripts/tracing/components/tracing_table_list.vue index ef27292bfe8aeb696a21837cb37b1701773ef125..c92cb02dbef2a490733db43f04c632319be9c7a3 100644 --- a/ee/app/assets/javascripts/tracing/components/tracing_table_list.vue +++ b/ee/app/assets/javascripts/tracing/components/tracing_table_list.vue @@ -4,7 +4,6 @@ import { s__, __ } from '~/locale'; import { formatDate } from '~/lib/utils/datetime/date_format_utility'; import { formatTraceDuration } from './trace_utils'; -export const tableDataClass = 'gl-display-flex gl-md-display-table-cell gl-align-items-center'; export default { name: 'TracingTableList', i18n: { @@ -15,23 +14,23 @@ export default { { key: 'timestamp', label: s__('Tracing|Date'), - tdClass: tableDataClass, + tdAttr: { 'data-testid': 'trace-timestamp' }, }, { key: 'service_name', label: s__('Tracing|Service'), - tdClass: tableDataClass, + tdAttr: { 'data-testid': 'trace-service' }, }, { key: 'operation', label: s__('Tracing|Operation'), - tdClass: tableDataClass, + tdAttr: { 'data-testid': 'trace-operation' }, }, { key: 'duration', label: s__('Tracing|Duration'), thClass: 'gl-w-15p', - tdClass: tableDataClass, + tdAttr: { 'data-testid': 'trace-duration' }, }, ], components: { @@ -46,7 +45,7 @@ export default { highlightedTraceId: { required: false, type: String, - default: () => null, + default: null, }, }, computed: { @@ -85,6 +84,7 @@ export default { selectable select-mode="single" selected-variant="" + :tbody-tr-attr="{ 'data-testid': 'trace-row' }" @row-clicked="onRowClicked" > <template #cell(service_name)="{ item }"> diff --git a/ee/spec/frontend/tracing/components/tracing_table_list_spec.js b/ee/spec/frontend/tracing/components/tracing_table_list_spec.js index b2438e6342efa35719f33804989536d0e2248ad4..6bfc30418e10df7c91bf145b21b5834b9e5f5ae9 100644 --- a/ee/spec/frontend/tracing/components/tracing_table_list_spec.js +++ b/ee/spec/frontend/tracing/components/tracing_table_list_spec.js @@ -22,6 +22,23 @@ describe('TracingTableList', () => { }, ]; + const expectedTraces = [ + { + timestamp: 'Jul 10, 2023 3:02pm UTC', + service_name: 'tracegen', + operation: 'lets-go', + duration: '1.50 ms', + trace_id: 'trace-1', + }, + { + timestamp: 'Aug 11, 2023 4:03pm UTC', + service_name: 'tracegen-2', + operation: 'lets-go-2', + duration: '2.00 ms', + trace_id: 'trace-2', + }, + ]; + const mountComponent = ({ traces = mockTraces, highlightedTraceId } = {}) => { wrapper = mountExtended(TracingTableList, { propsData: { @@ -31,13 +48,8 @@ describe('TracingTableList', () => { }); }; - const getRows = () => wrapper.findComponent(GlTable).find('tbody').findAll('tr'); + const getRows = () => wrapper.findComponent(GlTable).findAll(`[data-testid="trace-row"]`); const getRow = (idx) => getRows().at(idx); - const getCells = (trIdx) => getRows().at(trIdx).findAll('td'); - - const getCell = (trIdx, tdIdx) => { - return getCells(trIdx).at(tdIdx); - }; const clickRow = async (idx) => { getRow(idx).trigger('click'); @@ -47,21 +59,16 @@ describe('TracingTableList', () => { it('renders traces as table', () => { mountComponent(); - const rows = wrapper.findAll('table tbody tr'); - + const rows = getRows(); expect(rows.length).toBe(mockTraces.length); - - expect(getCells(0).length).toBe(4); - expect(getCell(0, 0).text()).toBe('Jul 10, 2023 3:02pm UTC'); - expect(getCell(0, 1).text()).toBe('tracegen'); - expect(getCell(0, 2).text()).toBe('lets-go'); - expect(getCell(0, 3).text()).toBe(`1.50 ms`); - - expect(getCells(1).length).toBe(4); - expect(getCell(1, 0).text()).toBe('Aug 11, 2023 4:03pm UTC'); - expect(getCell(1, 1).text()).toBe('tracegen-2'); - expect(getCell(1, 2).text()).toBe('lets-go-2'); - expect(getCell(1, 3).text()).toBe(`2.00 ms`); + mockTraces.forEach((_, i) => { + const row = getRows().at(i); + const trace = expectedTraces[i]; + expect(row.find(`[data-testid="trace-timestamp"]`).text()).toBe(trace.timestamp); + expect(row.find(`[data-testid="trace-service"]`).text()).toBe(trace.service_name); + expect(row.find(`[data-testid="trace-operation"]`).text()).toBe(trace.operation); + expect(row.find(`[data-testid="trace-duration"]`).text()).toBe(trace.duration); + }); }); it('emits trace-clicked on row-clicked', async () => { @@ -76,7 +83,9 @@ describe('TracingTableList', () => { it('renders the empty state when no traces are provided', () => { mountComponent({ traces: [] }); - expect(getCell(0, 0).text()).toContain('No results found'); + expect(getRows().length).toBe(1); + + expect(getRows().at(0).text()).toContain('No results found'); }); it('sets the correct variant when a trace is highlighted', () => { diff --git a/ee/spec/frontend/tracing/filters_spec.js b/ee/spec/frontend/tracing/filters_spec.js index d197050f7594cdd245a3e156bcb0ee271d45c75a..148a16058289bc7e1fc706051d3c3c5a22fac80e 100644 --- a/ee/spec/frontend/tracing/filters_spec.js +++ b/ee/spec/frontend/tracing/filters_spec.js @@ -1,183 +1,122 @@ import { - filterToQueryObject, - urlQueryToFilter, - prepareTokens, - processFilters, -} from '~/vue_shared/components/filtered_search_bar/filtered_search_utils'; -import { FILTERED_SEARCH_TERM } from '~/vue_shared/components/filtered_search_bar/constants'; - -import { - PERIOD_FILTER_TOKEN_TYPE, - SERVICE_NAME_FILTER_TOKEN_TYPE, - OPERATION_FILTER_TOKEN_TYPE, - TRACE_ID_FILTER_TOKEN_TYPE, - DURATION_MS_FILTER_TOKEN_TYPE, - ATTRIBUTE_FILTER_TOKEN_TYPE, queryToFilterObj, filterObjToQuery, filterObjToFilterToken, filterTokensToFilterObj, } from 'ee/tracing/filters'; -jest.mock('~/vue_shared/components/filtered_search_bar/filtered_search_utils'); - describe('utils', () => { + const query = + 'sortBy=timestamp_desc' + + '&period[]=1h' + + '&service[]=accountingservice¬%5Bservice%5D[]=adservice' + + '&operation[]=orders%20receive¬%5Boperation%5D[]=orders%20receive' + + '>%5BdurationMs%5D[]=100<%5BdurationMs%5D[]=1000' + + '&trace_id[]=9609bf00-4b68-f86c-abe2-5e23d0089c83' + + '¬%5Btrace_id%5D[]=9609bf00-4b68-f86c-abe2-5e23d0089c83' + + '&attribute[]=foo%3Dbar&attribute[]=baz%3Dbar' + + '&search=searchquery'; + + const filterObj = { + period: [{ operator: '=', value: '1h' }], + service: [ + { operator: '=', value: 'accountingservice' }, + { operator: '!=', value: 'adservice' }, + ], + operation: [ + { operator: '=', value: 'orders receive' }, + { operator: '!=', value: 'orders receive' }, + ], + traceId: [ + { operator: '=', value: '9609bf00-4b68-f86c-abe2-5e23d0089c83' }, + { operator: '!=', value: '9609bf00-4b68-f86c-abe2-5e23d0089c83' }, + ], + durationMs: [ + { operator: '>', value: '100' }, + { operator: '<', value: '1000' }, + ], + attribute: [ + { operator: '=', value: 'foo=bar' }, + { operator: '=', value: 'baz=bar' }, + ], + search: [{ value: 'searchquery' }], + }; + + const queryObj = { + attribute: ['foo=bar', 'baz=bar'], + durationMs: null, + 'gt[durationMs]': ['100'], + 'lt[durationMs]': ['1000'], + 'not[attribute]': null, + 'not[durationMs]': null, + 'not[operation]': ['orders receive'], + 'not[period]': null, + 'not[service]': ['adservice'], + 'not[trace_id]': ['9609bf00-4b68-f86c-abe2-5e23d0089c83'], + operation: ['orders receive'], + period: ['1h'], + search: 'searchquery', + service: ['accountingservice'], + trace_id: ['9609bf00-4b68-f86c-abe2-5e23d0089c83'], + }; + + const filterTokens = [ + { type: 'period', value: { data: '1h', operator: '=' } }, + { type: 'service-name', value: { data: 'accountingservice', operator: '=' } }, + { type: 'service-name', value: { data: 'adservice', operator: '!=' } }, + { type: 'operation', value: { data: 'orders receive', operator: '=' } }, + { type: 'operation', value: { data: 'orders receive', operator: '!=' } }, + { + type: 'trace-id', + value: { data: '9609bf00-4b68-f86c-abe2-5e23d0089c83', operator: '=' }, + }, + { + type: 'trace-id', + value: { data: '9609bf00-4b68-f86c-abe2-5e23d0089c83', operator: '!=' }, + }, + { type: 'duration-ms', value: { data: '100', operator: '>' } }, + { type: 'duration-ms', value: { data: '1000', operator: '<' } }, + { type: 'attribute', value: { data: 'foo=bar', operator: '=' } }, + { type: 'attribute', value: { data: 'baz=bar', operator: '=' } }, + { type: 'filtered-search-term', value: { data: 'searchquery', operator: undefined } }, + ]; + describe('queryToFilterObj', () => { it('should build a filter obj', () => { - const query = { test: 'query' }; - urlQueryToFilter.mockReturnValue({ - period: '7d', - service: 'my_service', - operation: 'my_operation', - trace_id: 'my_trace_id', - durationMs: '500', - attribute: 'foo=bar', - [FILTERED_SEARCH_TERM]: 'test', - }); - - const filterObj = queryToFilterObj(query); - - expect(urlQueryToFilter).toHaveBeenCalledWith(query, { - customOperators: [ - { operator: '>', prefix: 'gt' }, - { operator: '<', prefix: 'lt' }, - ], - filteredSearchTermKey: 'search', - }); - expect(filterObj).toEqual({ - period: '7d', - service: 'my_service', - operation: 'my_operation', - traceId: 'my_trace_id', - durationMs: '500', - search: 'test', - attribute: 'foo=bar', - }); + expect(queryToFilterObj(query)).toEqual(filterObj); }); it('should add the default period filter if not specified', () => { - const query = { test: 'query' }; - urlQueryToFilter.mockReturnValue({}); - - const filterObj = queryToFilterObj(query); - - expect(filterObj).toEqual({ + expect(queryToFilterObj('service[]=accountingservice')).toEqual({ period: [{ operator: '=', value: '1h' }], - service: undefined, - operation: undefined, - traceId: undefined, - durationMs: undefined, - attribute: undefined, - search: undefined, + service: [{ operator: '=', value: 'accountingservice' }], }); }); }); describe('filterObjToQuery', () => { it('should convert filter object to URL query', () => { - filterToQueryObject.mockReturnValue('mockquery'); - - const query = filterObjToQuery({ - period: '7d', - service: 'my_service', - operation: 'my_operation', - traceId: 'my_trace_id', - durationMs: '500', - search: 'test', - attribute: 'foo=bar', - }); - - expect(filterToQueryObject).toHaveBeenCalledWith( - { - period: '7d', - service: 'my_service', - operation: 'my_operation', - trace_id: 'my_trace_id', - durationMs: '500', - 'filtered-search-term': 'test', - attribute: 'foo=bar', - }, - { - customOperators: [ - { applyOnlyToKey: 'durationMs', operator: '>', prefix: 'gt' }, - { applyOnlyToKey: 'durationMs', operator: '<', prefix: 'lt' }, - ], - filteredSearchTermKey: 'search', - }, - ); - expect(query).toBe('mockquery'); + expect(filterObjToQuery(filterObj)).toEqual(queryObj); }); }); describe('filterObjToFilterToken', () => { it('should convert filter object to filter tokens', () => { - const mockTokens = []; - prepareTokens.mockReturnValue(mockTokens); - - const tokens = filterObjToFilterToken({ - period: '7d', - service: 'my_service', - operation: 'my_operation', - traceId: 'my_trace_id', - durationMs: '500', - search: 'test', - attribute: 'foo=bar', - }); - - expect(prepareTokens).toHaveBeenCalledWith({ - [PERIOD_FILTER_TOKEN_TYPE]: '7d', - [SERVICE_NAME_FILTER_TOKEN_TYPE]: 'my_service', - [OPERATION_FILTER_TOKEN_TYPE]: 'my_operation', - [TRACE_ID_FILTER_TOKEN_TYPE]: 'my_trace_id', - [DURATION_MS_FILTER_TOKEN_TYPE]: '500', - [FILTERED_SEARCH_TERM]: 'test', - [ATTRIBUTE_FILTER_TOKEN_TYPE]: 'foo=bar', - }); - expect(tokens).toBe(mockTokens); + expect(filterObjToFilterToken(filterObj)).toEqual(filterTokens); }); }); describe('filterTokensToFilterObj', () => { it('should convert filter tokens to filter object', () => { - const mockTokens = []; - processFilters.mockReturnValue({ - [SERVICE_NAME_FILTER_TOKEN_TYPE]: 'my_service', - [PERIOD_FILTER_TOKEN_TYPE]: '7d', - [OPERATION_FILTER_TOKEN_TYPE]: 'my_operation', - [TRACE_ID_FILTER_TOKEN_TYPE]: 'my_trace_id', - [DURATION_MS_FILTER_TOKEN_TYPE]: '500', - [FILTERED_SEARCH_TERM]: 'test', - [ATTRIBUTE_FILTER_TOKEN_TYPE]: 'foo=bar', - }); - - const filterObj = filterTokensToFilterObj(mockTokens); - - expect(processFilters).toHaveBeenCalledWith(mockTokens); - expect(filterObj).toEqual({ - service: 'my_service', - period: '7d', - operation: 'my_operation', - traceId: 'my_trace_id', - durationMs: '500', - search: 'test', - attribute: 'foo=bar', - }); + expect(filterTokensToFilterObj(filterTokens)).toEqual(filterObj); }); it('should add the default period filter it not specified', () => { - const mockTokens = []; - processFilters.mockReturnValue({}); - - const filterObj = filterTokensToFilterObj(mockTokens); - - expect(filterObj).toEqual({ + expect( + filterTokensToFilterObj([{ type: 'duration-ms', value: { data: '100', operator: '>' } }]), + ).toEqual({ period: [{ operator: '=', value: '1h' }], - service: undefined, - operation: undefined, - traceId: undefined, - durationMs: undefined, - search: undefined, - attribute: undefined, + durationMs: [{ operator: '>', value: '100' }], }); }); });