Skip to content

Commit 2732497

Browse files
committed
fix(vue): Ensure root component render span always ends
1 parent 6d61be0 commit 2732497

File tree

2 files changed

+30
-21
lines changed

2 files changed

+30
-21
lines changed

packages/vue/src/tracing.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ const HOOKS: { [key in Operation]: Hook[] } = {
3232
};
3333

3434
/** Finish top-level component span and activity with a debounce configured using `timeout` option */
35-
function finishRootComponentSpan(vm: VueSentry, timestamp: number, timeout: number): void {
35+
function maybeEndRootComponentSpan(vm: VueSentry, timestamp: number, timeout: number): void {
3636
if (vm.$_sentryRootComponentSpanTimer) {
3737
clearTimeout(vm.$_sentryRootComponentSpanTimer);
3838
}
@@ -66,6 +66,8 @@ export const createTracingMixins = (options: Partial<TracingOptions> = {}): Mixi
6666

6767
const mixins: Mixins = {};
6868

69+
const rootComponentSpanFinalTimeout = options.timeout || 2000;
70+
6971
for (const operation of hooks) {
7072
// Retrieve corresponding hooks from Vue lifecycle.
7173
// eg. mount => ['beforeMount', 'mounted']
@@ -91,6 +93,9 @@ export const createTracingMixins = (options: Partial<TracingOptions> = {}): Mixi
9193
},
9294
onlyIfParent: true,
9395
});
96+
97+
// call debounced end function once directly, just in case no child components call it
98+
maybeEndRootComponentSpan(this, timestampInSeconds(), rootComponentSpanFinalTimeout);
9499
}
95100

96101
// 2. Component tracking filter
@@ -102,7 +107,10 @@ export const createTracingMixins = (options: Partial<TracingOptions> = {}): Mixi
102107
? findTrackComponent(options.trackComponents, componentName)
103108
: options.trackComponents);
104109

110+
// We always want to track root component
105111
if (!shouldTrack) {
112+
// even if we don't track `this` component, we still want to end the root span eventually
113+
maybeEndRootComponentSpan(this, timestampInSeconds(), rootComponentSpanFinalTimeout);
106114
return;
107115
}
108116

@@ -143,7 +151,7 @@ export const createTracingMixins = (options: Partial<TracingOptions> = {}): Mixi
143151
span.end();
144152

145153
// For any "after" hook, also schedule the root component span to finish
146-
finishRootComponentSpan(this, timestampInSeconds(), options.timeout || 2000);
154+
maybeEndRootComponentSpan(this, timestampInSeconds(), options.timeout || 2000);
147155
}
148156
};
149157
}

packages/vue/test/tracing/tracingMixin.test.ts

Lines changed: 20 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,10 @@ vi.mock('../../src/vendor/components', () => {
2727
};
2828
});
2929

30-
const mockSpanFactory = (): { name?: string; op?: string; end: Mock; startChild: Mock } => ({
30+
const mockSpanFactory = (): { name?: string; op?: string; end: Mock } => ({
3131
name: undefined,
3232
op: undefined,
3333
end: vi.fn(),
34-
startChild: vi.fn(),
3534
});
3635

3736
vi.useFakeTimers();
@@ -127,23 +126,25 @@ describe('Vue Tracing Mixins', () => {
127126
);
128127
});
129128

130-
it('should finish root component span on timer after component spans end', () => {
131-
// todo/fixme: This root component span is only finished if trackComponents is true --> it should probably be always finished
132-
const mixins = createTracingMixins({ trackComponents: true, timeout: 1000 });
133-
const rootMockSpan = mockSpanFactory();
134-
mockRootInstance.$_sentryRootComponentSpan = rootMockSpan;
135-
136-
// Create and finish a component span
137-
mixins.beforeMount.call(mockVueInstance);
138-
mixins.mounted.call(mockVueInstance);
139-
140-
// Root component span should not end immediately
141-
expect(rootMockSpan.end).not.toHaveBeenCalled();
142-
143-
// After timeout, root component span should end
144-
vi.advanceTimersByTime(1001);
145-
expect(rootMockSpan.end).toHaveBeenCalled();
146-
});
129+
it.each([true, false])(
130+
'should finish root component span on timer after component spans end, if trackComponents is %s',
131+
() => {
132+
const mixins = createTracingMixins({ trackComponents: false, timeout: 1000 });
133+
const rootMockSpan = mockSpanFactory();
134+
mockRootInstance.$_sentryRootComponentSpan = rootMockSpan;
135+
136+
// Create and finish a component span
137+
mixins.beforeMount.call(mockVueInstance);
138+
mixins.mounted.call(mockVueInstance);
139+
140+
// Root component span should not end immediately
141+
expect(rootMockSpan.end).not.toHaveBeenCalled();
142+
143+
// After timeout, root component span should end
144+
vi.advanceTimersByTime(1001);
145+
expect(rootMockSpan.end).toHaveBeenCalled();
146+
},
147+
);
147148
});
148149

149150
describe('Component Span Lifecycle', () => {

0 commit comments

Comments
 (0)