Skip to content

Commit a7d3a9d

Browse files
authored
fix(vue): Ensure Vue trackComponents list matches components with or without <> (#13543)
Ensure that the component names listed in the `trackComponent` option match regardless of if they were specified as `<Name>` or `Name`. Add unit and e2e tests for component tracking. --------- Signed-off-by: Kaung Zin Hein <[email protected]>
1 parent facaae4 commit a7d3a9d

File tree

8 files changed

+178
-2
lines changed

8 files changed

+178
-2
lines changed

dev-packages/e2e-tests/test-applications/vue-3/src/main.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ Sentry.init({
1919
}),
2020
],
2121
tunnel: `http://localhost:3031/`, // proxy server
22+
trackComponents: ['ComponentMainView', '<ComponentOneView>'],
2223
});
2324

2425
app.use(router);

dev-packages/e2e-tests/test-applications/vue-3/src/router/index.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,10 @@ const router = createRouter({
3030
},
3131
],
3232
},
33+
{
34+
path: '/components',
35+
component: () => import('../views/ComponentMainView.vue'),
36+
},
3337
],
3438
});
3539

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<script setup lang="ts">
2+
import ComponentOneView from './ComponentOneView.vue';
3+
import ComponentTwoView from './ComponentTwoView.vue';
4+
</script>
5+
6+
<template>
7+
<h1>Demonstrating Component Tracking</h1>
8+
<ComponentOneView />
9+
<ComponentTwoView />
10+
</template>
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<script setup lang="ts">
2+
function log() {
3+
console.log('Component One!');
4+
}
5+
</script>
6+
7+
<template>
8+
<h1>Component One</h1>
9+
<button id="componentOneBtn" @click="log">Click to Log</button>
10+
</template>
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<script setup lang="ts">
2+
function log() {
3+
console.log('Component Two!');
4+
}
5+
</script>
6+
7+
<template>
8+
<h1>Component One</h1>
9+
<button id="componentTwoBtn" @click="log">Click to Log</button>
10+
</template>

dev-packages/e2e-tests/test-applications/vue-3/tests/performance.test.ts

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,3 +122,86 @@ test('sends a pageload transaction with a route name as transaction name if avai
122122
},
123123
});
124124
});
125+
126+
test('sends a lifecycle span for each tracked components', async ({ page }) => {
127+
const transactionPromise = waitForTransaction('vue-3', async transactionEvent => {
128+
return !!transactionEvent?.transaction && transactionEvent.contexts?.trace?.op === 'pageload';
129+
});
130+
131+
await page.goto(`/components`);
132+
133+
const rootSpan = await transactionPromise;
134+
135+
expect(rootSpan).toMatchObject({
136+
contexts: {
137+
trace: {
138+
data: {
139+
'sentry.source': 'route',
140+
'sentry.origin': 'auto.pageload.vue',
141+
'sentry.op': 'pageload',
142+
},
143+
op: 'pageload',
144+
origin: 'auto.pageload.vue',
145+
},
146+
},
147+
spans: expect.arrayContaining([
148+
// enabled by default
149+
expect.objectContaining({
150+
data: {
151+
'sentry.op': 'ui.vue.render',
152+
'sentry.origin': 'auto.ui.vue',
153+
},
154+
description: 'Application Render',
155+
op: 'ui.vue.render',
156+
origin: 'auto.ui.vue',
157+
}),
158+
// enabled by default
159+
expect.objectContaining({
160+
data: {
161+
'sentry.op': 'ui.vue.mount',
162+
'sentry.origin': 'auto.ui.vue',
163+
},
164+
description: 'Vue <Root>',
165+
op: 'ui.vue.mount',
166+
origin: 'auto.ui.vue',
167+
}),
168+
169+
// without `<>`
170+
expect.objectContaining({
171+
data: {
172+
'sentry.op': 'ui.vue.mount',
173+
'sentry.origin': 'auto.ui.vue',
174+
},
175+
description: 'Vue <ComponentMainView>',
176+
op: 'ui.vue.mount',
177+
origin: 'auto.ui.vue',
178+
}),
179+
180+
// with `<>`
181+
expect.objectContaining({
182+
data: {
183+
'sentry.op': 'ui.vue.mount',
184+
'sentry.origin': 'auto.ui.vue',
185+
},
186+
description: 'Vue <ComponentOneView>',
187+
op: 'ui.vue.mount',
188+
origin: 'auto.ui.vue',
189+
}),
190+
191+
// not tracked
192+
expect.not.objectContaining({
193+
data: {
194+
'sentry.op': 'ui.vue.mount',
195+
'sentry.origin': 'auto.ui.vue',
196+
},
197+
description: 'Vue <ComponentTwoView>',
198+
op: 'ui.vue.mount',
199+
origin: 'auto.ui.vue',
200+
}),
201+
]),
202+
transaction: '/components',
203+
transaction_info: {
204+
source: 'route',
205+
},
206+
});
207+
});

packages/vue/src/tracing.ts

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,19 @@ function finishRootSpan(vm: VueSentry, timestamp: number, timeout: number): void
4646
}, timeout);
4747
}
4848

49+
/** Find if the current component exists in the provided `TracingOptions.trackComponents` array option. */
50+
export function findTrackComponent(trackComponents: string[], formattedName: string): boolean {
51+
function extractComponentName(name: string): string {
52+
return name.replace(/^<([^\s]*)>(?: at [^\s]*)?$/, '$1');
53+
}
54+
55+
const isMatched = trackComponents.some(compo => {
56+
return extractComponentName(formattedName) === extractComponentName(compo);
57+
});
58+
59+
return isMatched;
60+
}
61+
4962
export const createTracingMixins = (options: TracingOptions): Mixins => {
5063
const hooks = (options.hooks || [])
5164
.concat(DEFAULT_HOOKS)
@@ -84,8 +97,9 @@ export const createTracingMixins = (options: TracingOptions): Mixins => {
8497

8598
// Skip components that we don't want to track to minimize the noise and give a more granular control to the user
8699
const name = formatComponentName(this, false);
100+
87101
const shouldTrack = Array.isArray(options.trackComponents)
88-
? options.trackComponents.indexOf(name) > -1
102+
? findTrackComponent(options.trackComponents, name)
89103
: options.trackComponents;
90104

91105
// We always want to track root component
@@ -109,7 +123,7 @@ export const createTracingMixins = (options: TracingOptions): Mixins => {
109123
}
110124

111125
this.$_sentrySpans[operation] = startInactiveSpan({
112-
name: `Vue <${name}>`,
126+
name: `Vue ${name}`,
113127
op: `${VUE_OP}.${operation}`,
114128
attributes: {
115129
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.ui.vue',
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import { describe, expect, it } from 'vitest';
2+
import { findTrackComponent } from '../../src/tracing';
3+
4+
describe('findTrackComponent', () => {
5+
describe('when user-defined array contains `<Component>`', () => {
6+
it('returns true if a match is found', () => {
7+
// arrange
8+
const trackComponents = ['<ABC>', '<XYZ>'];
9+
const formattedComponentName = '<XYZ>';
10+
11+
// act
12+
const shouldTrack = findTrackComponent(trackComponents, formattedComponentName);
13+
14+
// assert
15+
expect(shouldTrack).toBe(true);
16+
});
17+
});
18+
describe('when user-defined array contains `Component` without the `<>`', () => {
19+
it('returns true if a match is found', () => {
20+
// arrange
21+
const trackComponents = ['ABC', 'XYZ'];
22+
const formattedComponentName = '<XYZ>';
23+
24+
// act
25+
const shouldTrack = findTrackComponent(trackComponents, formattedComponentName);
26+
27+
// assert
28+
expect(shouldTrack).toBe(true);
29+
});
30+
});
31+
describe('when the vue file name is include in the formatted component name', () => {
32+
it('returns true if a match is found', () => {
33+
// arrange
34+
const trackComponents = ['ABC', 'XYZ'];
35+
const formattedComponentName = '<XYZ> at XYZ.vue';
36+
37+
// act
38+
const shouldTrack = findTrackComponent(trackComponents, formattedComponentName);
39+
40+
// assert
41+
expect(shouldTrack).toBe(true);
42+
});
43+
});
44+
});

0 commit comments

Comments
 (0)