From 441cf2d9e8f348d998862485ffa16b005919d3ae Mon Sep 17 00:00:00 2001 From: Daniele Rossetti <drossetti@gitlab.com> Date: Fri, 22 Mar 2024 18:49:34 +0000 Subject: [PATCH] Improve support for incomplete traces --- .../tracing/details/tracing_header.vue | 23 +++++++++-- .../tracing/list/tracing_table.vue | 10 ++++- .../assets/javascripts/tracing/trace_utils.js | 4 +- .../tracing/details/tracing_header_spec.js | 38 +++++++++++++++---- .../tracing/list/tracing_table_spec.js | 34 +++++++++++++++-- ee/spec/frontend/tracing/trace_utils_spec.js | 37 ++++++++++++++++++ locale/gitlab.pot | 3 ++ 7 files changed, 133 insertions(+), 16 deletions(-) diff --git a/ee/app/assets/javascripts/tracing/details/tracing_header.vue b/ee/app/assets/javascripts/tracing/details/tracing_header.vue index 4a442637bb8ea..bb03c1924ba3a 100644 --- a/ee/app/assets/javascripts/tracing/details/tracing_header.vue +++ b/ee/app/assets/javascripts/tracing/details/tracing_header.vue @@ -1,7 +1,8 @@ <script> -import { GlCard } from '@gitlab/ui'; +import { GlCard, GlBadge } from '@gitlab/ui'; import { formatDate } from '~/lib/utils/datetime/date_format_utility'; -import { formatTraceDuration } from '../trace_utils'; +import { s__ } from '~/locale'; +import { formatTraceDuration, findRootSpan } from '../trace_utils'; const CARD_CLASS = 'gl-mr-7 gl-w-15p gl-min-w-fit-content'; const HEADER_CLASS = 'gl-p-2 gl-font-weight-bold gl--flex-center'; @@ -14,6 +15,10 @@ export default { BODY_CLASS, components: { GlCard, + GlBadge, + }, + i18n: { + inProgress: s__('Tracing|In progress'), }, props: { trace: { @@ -34,13 +39,25 @@ export default { traceDuration() { return formatTraceDuration(this.trace.duration_nano); }, + isTraceInProgress() { + return !findRootSpan(this.trace); + }, }, }; </script> <template> <div class="gl-mb-6"> - <h1>{{ title }}</h1> + <h1> + {{ title }} + <gl-badge + v-if="isTraceInProgress" + variant="warning" + size="md" + class="gl-ml-3 gl-vertical-align-middle" + >{{ $options.i18n.inProgress }}</gl-badge + > + </h1> <div class="gl-display-flex gl-flex-wrap gl-justify-content-center gl-my-7 gl-row-gap-6"> <gl-card diff --git a/ee/app/assets/javascripts/tracing/list/tracing_table.vue b/ee/app/assets/javascripts/tracing/list/tracing_table.vue index 3b0045e229bbe..bc72ee6d00a24 100644 --- a/ee/app/assets/javascripts/tracing/list/tracing_table.vue +++ b/ee/app/assets/javascripts/tracing/list/tracing_table.vue @@ -9,6 +9,7 @@ export default { i18n: { title: s__('Tracing|Traces'), emptyText: __('No results found'), + inProgress: s__('Tracing|In progress'), }, fields: [ { @@ -71,7 +72,11 @@ export default { }, matchesBadgeContent(item) { const spans = n__('Tracing|%d span', 'Tracing|%d spans', item.total_spans); - if (item.total_spans === item.matched_span_count) { + if ( + item.total_spans === item.matched_span_count || + !Number.isInteger(item.matched_span_count) || + item.in_progress + ) { return spans; } const matches = n__('Tracing|%d match', 'Tracing|%d matches', item.matched_span_count); @@ -108,6 +113,9 @@ export default { {{ item.timestamp }} <div class="gl-mt-4 gl-display-flex"> <gl-badge variant="info" size="md">{{ matchesBadgeContent(item) }}</gl-badge> + <gl-badge v-if="item.in_progress" variant="warning" size="md" class="gl-ml-3">{{ + $options.i18n.inProgress + }}</gl-badge> <gl-badge v-if="hasError(item)" variant="danger" size="md" class="gl-ml-2"> <gl-icon name="status-alert" class="gl-mr-2 gl-text-red-500" /> {{ errorBadgeContent(item) }} diff --git a/ee/app/assets/javascripts/tracing/trace_utils.js b/ee/app/assets/javascripts/tracing/trace_utils.js index cd7a8c579a5f2..38fa19a9c9e5f 100644 --- a/ee/app/assets/javascripts/tracing/trace_utils.js +++ b/ee/app/assets/javascripts/tracing/trace_utils.js @@ -69,10 +69,12 @@ export function assignColorToServices(trace) { const timestampToMs = (ts) => new Date(ts).getTime(); +export const findRootSpan = (trace) => trace.spans.find((s) => s.parent_span_id === ''); + export function mapTraceToTreeRoot(trace) { const nodes = {}; - const rootSpan = trace.spans.find((s) => s.parent_span_id === ''); + const rootSpan = findRootSpan(trace); if (!rootSpan) return undefined; const spanToNode = (span) => ({ diff --git a/ee/spec/frontend/tracing/details/tracing_header_spec.js b/ee/spec/frontend/tracing/details/tracing_header_spec.js index a0607a49f77ef..570b9009a0dad 100644 --- a/ee/spec/frontend/tracing/details/tracing_header_spec.js +++ b/ee/spec/frontend/tracing/details/tracing_header_spec.js @@ -4,24 +4,46 @@ import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; describe('TracingHeader', () => { let wrapper; - beforeEach(() => { + const defaultTrace = { + service_name: 'Service', + operation: 'Operation', + timestamp: 1692021937219, + duration_nano: 1000000000, + total_spans: 10, + spans: [ + { span_id: 'span-1', parent_span_id: '' }, + { span_id: 'span-2', parent_span_id: 'span-1' }, + ], + }; + + const createComponent = (trace = defaultTrace) => { wrapper = shallowMountExtended(TracingHeader, { propsData: { - trace: { - service_name: 'Service', - operation: 'Operation', - timestamp: 1692021937219, - duration_nano: 1000000000, - total_spans: 10, - }, + trace, }, }); + }; + beforeEach(() => { + createComponent(); }); it('renders the correct title', () => { expect(wrapper.find('h1').text()).toBe('Service : Operation'); }); + it('does not show the in progress label when the root span is not missing', () => { + expect(wrapper.find('h1').text()).not.toContain('In progress'); + }); + + it('shows the in progress label when the root span is missing', () => { + createComponent({ + ...defaultTrace, + spans: [{ span_id: 'span-2', parent_span_id: 'span-1' }], + }); + + expect(wrapper.find('h1').text()).toContain('In progress'); + }); + it('renders the correct trace date', () => { expect(wrapper.findByTestId('trace-date-card').text()).toMatchInterpolatedText( 'Trace start Aug 14, 2023 14:05:37.219 UTC', diff --git a/ee/spec/frontend/tracing/list/tracing_table_spec.js b/ee/spec/frontend/tracing/list/tracing_table_spec.js index 733536cf74876..f089dd04ba473 100644 --- a/ee/spec/frontend/tracing/list/tracing_table_spec.js +++ b/ee/spec/frontend/tracing/list/tracing_table_spec.js @@ -25,6 +25,17 @@ describe('TracingTable', () => { matched_span_count: 2, error_span_count: 1, }, + { + timestamp: '2023-08-11T16:03:50.577538Z', + service_name: 'tracegen-3', + operation: 'lets-go-3', + duration_nano: 2000000, + trace_id: 'trace-3', + total_spans: 3, + matched_span_count: 2, + error_span_count: 1, + in_progress: true, + }, ]; const expectedTraces = [ @@ -46,6 +57,16 @@ describe('TracingTable', () => { duration: '2ms', trace_id: 'trace-2', }, + { + timestamp: 'Aug 11, 2023 4:03pm UTC', + badge: '3 spans', + errorBadge: '1 error', + service_name: 'tracegen-3', + operation: 'lets-go-3', + duration: '2ms', + trace_id: 'trace-3', + inProgressBadge: true, + }, ]; const mountComponent = ({ traces = mockTraces, highlightedTraceId } = {}) => { @@ -74,15 +95,22 @@ describe('TracingTable', () => { const row = getRows().at(i); const expected = expectedTraces[i]; expect(row.find(`[data-testid="trace-timestamp"]`).text()).toContain(expected.timestamp); + expect(row.find(`[data-testid="trace-service"]`).text()).toBe(expected.service_name); + expect(row.find(`[data-testid="trace-operation"]`).text()).toBe(expected.operation); + expect(row.find(`[data-testid="trace-duration"]`).text()).toBe(expected.duration); expect(row.find(`[data-testid="trace-timestamp"]`).text()).toContain(expected.badge); + if (expected.errorBadge) { expect(row.find(`[data-testid="trace-timestamp"]`).text()).toContain(expected.errorBadge); } else { expect(row.find(`[data-testid="trace-timestamp"]`).text()).not.toContain('error'); } - expect(row.find(`[data-testid="trace-service"]`).text()).toBe(expected.service_name); - expect(row.find(`[data-testid="trace-operation"]`).text()).toBe(expected.operation); - expect(row.find(`[data-testid="trace-duration"]`).text()).toBe(expected.duration); + + if (expected.inProgressBadge) { + expect(row.find(`[data-testid="trace-timestamp"]`).text()).toContain('In progress'); + } else { + expect(row.find(`[data-testid="trace-timestamp"]`).text()).not.toContain('In progress'); + } }); }); diff --git a/ee/spec/frontend/tracing/trace_utils_spec.js b/ee/spec/frontend/tracing/trace_utils_spec.js index 00ae7683de973..df7e4873f8849 100644 --- a/ee/spec/frontend/tracing/trace_utils_spec.js +++ b/ee/spec/frontend/tracing/trace_utils_spec.js @@ -6,6 +6,7 @@ import { formatTraceDuration, assignColorToServices, periodFilterToDate, + findRootSpan, } from 'ee/tracing/trace_utils'; describe('trace_utils', () => { @@ -96,6 +97,42 @@ describe('trace_utils', () => { }); }); + describe('findRootSpan', () => { + const rootSpan = { + timestamp: '2023-08-07T15:03:53.199871Z', + span_id: 'SPAN-1', + trace_id: 'TRACE-1', + service_name: 'SERVICE-1', + operation: 'OP-1', + duration_nano: 123456789, + parent_span_id: '', + }; + const nonRootSpan = { + timestamp: '2023-08-07T15:03:53.199871Z', + span_id: 'SPAN-2', + trace_id: 'TRACE-2', + service_name: 'SERVICE-2', + operation: 'OP-2', + duration_nano: 123456789, + parent_span_id: 'SPAN-1', + }; + it('returns the root span', () => { + expect( + findRootSpan({ + spans: [nonRootSpan, rootSpan], + }), + ).toBe(rootSpan); + }); + + it('returns undefined if the root span is missing', () => { + expect( + findRootSpan({ + spans: [nonRootSpan], + }), + ).toBeUndefined(); + }); + }); + describe('mapTraceToTreeRoot', () => { it('should map a trace data to tree data and return the root node', () => { const trace = { diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 352ba946d2b05..5182c7321edf6 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -53281,6 +53281,9 @@ msgstr "" msgid "Tracing|Filter traces" msgstr "" +msgid "Tracing|In progress" +msgstr "" + msgid "Tracing|Metadata" msgstr "" -- GitLab