diff --git a/packages/vue/src/index.ts b/packages/vue/src/index.ts index 0b9626ee185d..110b80d270a9 100644 --- a/packages/vue/src/index.ts +++ b/packages/vue/src/index.ts @@ -1,13 +1,7 @@ export * from '@sentry/browser'; export { init } from './sdk'; -// eslint-disable-next-line deprecation/deprecation -export { vueRouterInstrumentation } from './router'; export { browserTracingIntegration } from './browserTracingIntegration'; export { attachErrorHandler } from './errorhandler'; export { createTracingMixins } from './tracing'; -export { - vueIntegration, - // eslint-disable-next-line deprecation/deprecation - VueIntegration, -} from './integration'; +export { vueIntegration } from './integration'; diff --git a/packages/vue/src/integration.ts b/packages/vue/src/integration.ts index 8150ea6b95d6..4bd99e3d6c8c 100644 --- a/packages/vue/src/integration.ts +++ b/packages/vue/src/integration.ts @@ -1,8 +1,9 @@ -import { convertIntegrationFnToClass, defineIntegration, hasTracingEnabled } from '@sentry/core'; -import type { Client, Integration, IntegrationClass, IntegrationFn } from '@sentry/types'; +import { defineIntegration, hasTracingEnabled } from '@sentry/core'; +import type { Client, IntegrationFn } from '@sentry/types'; import { GLOBAL_OBJ, arrayify, consoleSandbox } from '@sentry/utils'; import { DEFAULT_HOOKS } from './constants'; +import { DEBUG_BUILD } from './debug-build'; import { attachErrorHandler } from './errorhandler'; import { createTracingMixins } from './tracing'; import type { Options, Vue, VueOptions } from './types'; @@ -33,17 +34,6 @@ const _vueIntegration = ((integrationOptions: Partial = {}) => { export const vueIntegration = defineIntegration(_vueIntegration); -/** - * Initialize Vue error & performance tracking. - * - * @deprecated Use `vueIntegration()` instead. - */ -// eslint-disable-next-line deprecation/deprecation -export const VueIntegration = convertIntegrationFnToClass( - INTEGRATION_NAME, - vueIntegration, -) as IntegrationClass; - function _setupIntegration(client: Client, integrationOptions: Partial): void { const options: Options = { ...DEFAULT_CONFIG, ...client.getOptions(), ...integrationOptions }; if (!options.Vue && !options.app) { @@ -67,23 +57,25 @@ Update your \`Sentry.init\` call with an appropriate config option: } const vueInit = (app: Vue, options: Options): void => { - // Check app is not mounted yet - should be mounted _after_ init()! - // This is _somewhat_ private, but in the case that this doesn't exist we simply ignore it - // See: https://github.com/vuejs/core/blob/eb2a83283caa9de0a45881d860a3cbd9d0bdd279/packages/runtime-core/src/component.ts#L394 - const appWithInstance = app as Vue & { - _instance?: { - isMounted?: boolean; + if (DEBUG_BUILD) { + // Check app is not mounted yet - should be mounted _after_ init()! + // This is _somewhat_ private, but in the case that this doesn't exist we simply ignore it + // See: https://github.com/vuejs/core/blob/eb2a83283caa9de0a45881d860a3cbd9d0bdd279/packages/runtime-core/src/component.ts#L394 + const appWithInstance = app as Vue & { + _instance?: { + isMounted?: boolean; + }; }; - }; - const isMounted = appWithInstance._instance && appWithInstance._instance.isMounted; - if (isMounted === true) { - consoleSandbox(() => { - // eslint-disable-next-line no-console - console.warn( - '[@sentry/vue]: Misconfigured SDK. Vue app is already mounted. Make sure to call `app.mount()` after `Sentry.init()`.', - ); - }); + const isMounted = appWithInstance._instance && appWithInstance._instance.isMounted; + if (isMounted === true) { + consoleSandbox(() => { + // eslint-disable-next-line no-console + console.warn( + '[@sentry/vue]: Misconfigured SDK. Vue app is already mounted. Make sure to call `app.mount()` after `Sentry.init()`.', + ); + }); + } } attachErrorHandler(app, options); diff --git a/packages/vue/src/router.ts b/packages/vue/src/router.ts index b7f3fd0466b0..b16541b1ea29 100644 --- a/packages/vue/src/router.ts +++ b/packages/vue/src/router.ts @@ -1,24 +1,12 @@ -import { WINDOW, captureException } from '@sentry/browser'; -import { SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, spanToJSON } from '@sentry/core'; -import type { SpanAttributes, Transaction, TransactionContext, TransactionSource } from '@sentry/types'; - -import { getActiveTransaction } from './tracing'; - -interface VueRouterInstrumationOptions { - /** - * What to use for route labels. - * By default, we use route.name (if set) and else the path. - * - * Default: 'name' - */ - routeLabel: 'name' | 'path'; -} - -export type VueRouterInstrumentation = ( - startTransaction: (context: TransactionContext) => T | undefined, - startTransactionOnPageLoad?: boolean, - startTransactionOnLocationChange?: boolean, -) => void; +import { captureException } from '@sentry/browser'; +import { + SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, + SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, + getActiveSpan, + getRootSpan, + spanToJSON, +} from '@sentry/core'; +import type { Span, SpanAttributes, TransactionContext, TransactionSource } from '@sentry/types'; // The following type is an intersection of the Route type from VueRouter v2, v3, and v4. // This is not great, but kinda necessary to make it work with all versions at the same time. @@ -43,57 +31,18 @@ interface VueRouter { beforeEach: (fn: (to: Route, from: Route, next?: () => void) => void) => void; } -/** - * Creates routing instrumentation for Vue Router v2, v3 and v4 - * - * You can optionally pass in an options object with the available option: - * * `routeLabel`: Set this to `route` to opt-out of using `route.name` for transaction names. - * - * @param router The Vue Router instance that is used - * - * @deprecated Use `browserTracingIntegration()` from `@sentry/vue` instead - this includes the vue router instrumentation. - */ -export function vueRouterInstrumentation( - router: VueRouter, - options: Partial = {}, -): VueRouterInstrumentation { - return ( - startTransaction: (context: TransactionContext) => Transaction | undefined, - startTransactionOnPageLoad: boolean = true, - startTransactionOnLocationChange: boolean = true, - ) => { - // We have to start the pageload transaction as early as possible (before the router's `beforeEach` hook - // is called) to not miss child spans of the pageload. - // We check that window & window.location exists in order to not run this code in SSR environments. - if (startTransactionOnPageLoad && WINDOW && WINDOW.location) { - startTransaction({ - name: WINDOW.location.pathname, - op: 'pageload', - attributes: { - [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.pageload.vue', - [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'url', - }, - }); - } - - instrumentVueRouter( - router, - { - routeLabel: options.routeLabel || 'name', - instrumentNavigation: startTransactionOnLocationChange, - instrumentPageLoad: startTransactionOnPageLoad, - }, - startTransaction, - ); - }; -} - /** * Instrument the Vue router to create navigation spans. */ export function instrumentVueRouter( router: VueRouter, options: { + /** + * What to use for route labels. + * By default, we use route.name (if set) and else the path. + * + * Default: 'name' + */ routeLabel: 'name' | 'path'; instrumentPageLoad: boolean; instrumentNavigation: boolean; @@ -139,17 +88,16 @@ export function instrumentVueRouter( } if (options.instrumentPageLoad && isPageLoadNavigation) { - // eslint-disable-next-line deprecation/deprecation - const pageloadTransaction = getActiveTransaction(); - if (pageloadTransaction) { - const existingAttributes = spanToJSON(pageloadTransaction).data || {}; + const activeRootSpan = getActiveRootSpan(); + if (activeRootSpan) { + const existingAttributes = spanToJSON(activeRootSpan).data || {}; if (existingAttributes[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE] !== 'custom') { - pageloadTransaction.updateName(transactionName); - pageloadTransaction.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, transactionSource); + activeRootSpan.updateName(transactionName); + activeRootSpan.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, transactionSource); } // Set router attributes on the existing pageload transaction - // This will the origin, and add params & query attributes - pageloadTransaction.setAttributes({ + // This will override the origin, and add params & query attributes + activeRootSpan.setAttributes({ ...attributes, [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.pageload.vue', }); @@ -158,6 +106,7 @@ export function instrumentVueRouter( if (options.instrumentNavigation && !isPageLoadNavigation) { attributes[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE] = transactionSource; + attributes[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN] = 'auto.navigation.vue'; startNavigationSpanFn({ name: transactionName, op: 'navigation', @@ -173,3 +122,17 @@ export function instrumentVueRouter( } }); } + +function getActiveRootSpan(): Span | undefined { + const span = getActiveSpan(); + const rootSpan = span ? getRootSpan(span) : undefined; + + if (!rootSpan) { + return undefined; + } + + const op = spanToJSON(rootSpan).op; + + // Only use this root span if it is a pageload or navigation span + return op === 'navigation' || op === 'pageload' ? rootSpan : undefined; +} diff --git a/packages/vue/src/tracing.ts b/packages/vue/src/tracing.ts index 8e2c743db064..277acc959c3f 100644 --- a/packages/vue/src/tracing.ts +++ b/packages/vue/src/tracing.ts @@ -1,5 +1,5 @@ -import { getActiveSpan, getCurrentScope, startInactiveSpan } from '@sentry/browser'; -import type { Span, Transaction } from '@sentry/types'; +import { getActiveSpan, startInactiveSpan } from '@sentry/browser'; +import type { Span } from '@sentry/types'; import { logger, timestampInSeconds } from '@sentry/utils'; import { DEFAULT_HOOKS } from './constants'; @@ -32,16 +32,6 @@ const HOOKS: { [key in Operation]: Hook[] } = { update: ['beforeUpdate', 'updated'], }; -/** - * Grabs active transaction off scope. - * - * @deprecated You should not rely on the transaction, but just use `startSpan()` APIs instead. - */ -export function getActiveTransaction(): Transaction | undefined { - // eslint-disable-next-line deprecation/deprecation - return getCurrentScope().getTransaction(); -} - /** Finish top-level span and activity with a debounce configured using `timeout` option */ function finishRootSpan(vm: VueSentry, timestamp: number, timeout: number): void { if (vm.$_sentryRootSpanTimer) { diff --git a/packages/vue/test/router.test.ts b/packages/vue/test/router.test.ts index 7d45889be864..c3a786a8f887 100644 --- a/packages/vue/test/router.test.ts +++ b/packages/vue/test/router.test.ts @@ -1,12 +1,19 @@ import * as SentryBrowser from '@sentry/browser'; +import * as SentryCore from '@sentry/core'; import { SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, SEMANTIC_ATTRIBUTE_SENTRY_SOURCE } from '@sentry/core'; -import type { SpanAttributes, Transaction } from '@sentry/types'; +import type { Span, SpanAttributes } from '@sentry/types'; import type { Route } from '../src/router'; -import { instrumentVueRouter, vueRouterInstrumentation } from '../src/router'; -import * as vueTracing from '../src/tracing'; +import { instrumentVueRouter } from '../src/router'; const captureExceptionSpy = jest.spyOn(SentryBrowser, 'captureException'); +jest.mock('@sentry/core', () => { + const actual = jest.requireActual('@sentry/core'); + return { + ...actual, + getActiveSpan: jest.fn().mockReturnValue({}), + }; +}); const mockVueRouter = { onError: jest.fn void]>(), @@ -51,307 +58,6 @@ const testRoutes: Record = { }, }; -/* eslint-disable deprecation/deprecation */ -describe('vueRouterInstrumentation()', () => { - const mockStartTransaction = jest.fn(); - - afterEach(() => { - jest.clearAllMocks(); - }); - - it('should return instrumentation that instruments VueRouter.onError', () => { - // create instrumentation - const instrument = vueRouterInstrumentation(mockVueRouter); - - // instrument - instrument(mockStartTransaction); - - // check - expect(mockVueRouter.onError).toHaveBeenCalledTimes(1); - - const onErrorCallback = mockVueRouter.onError.mock.calls[0][0]; - - const testError = new Error(); - onErrorCallback(testError); - - expect(captureExceptionSpy).toHaveBeenCalledTimes(1); - expect(captureExceptionSpy).toHaveBeenCalledWith(testError, { mechanism: { handled: false } }); - }); - - it.each([ - ['normalRoute1', 'normalRoute2', '/accounts/:accountId', 'route'], - ['normalRoute2', 'namedRoute', 'login-screen', 'custom'], - ['normalRoute2', 'unmatchedRoute', '/e8733846-20ac-488c-9871-a5cbcb647294', 'url'], - ])( - 'should return instrumentation that instruments VueRouter.beforeEach(%s, %s) for navigations', - (fromKey, toKey, transactionName, transactionSource) => { - // create instrumentation - const instrument = vueRouterInstrumentation(mockVueRouter); - - // instrument - instrument(mockStartTransaction, true, true); - - // check - expect(mockVueRouter.beforeEach).toHaveBeenCalledTimes(1); - const beforeEachCallback = mockVueRouter.beforeEach.mock.calls[0][0]; - - const from = testRoutes[fromKey]; - const to = testRoutes[toKey]; - beforeEachCallback(to, from, mockNext); - - // first startTx call happens when the instrumentation is initialized (for pageloads) - expect(mockStartTransaction).toHaveBeenCalledTimes(2); - expect(mockStartTransaction).toHaveBeenCalledWith({ - name: transactionName, - attributes: { - [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.navigation.vue', - [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: transactionSource, - ...getAttributesForRoute(to), - }, - op: 'navigation', - }); - - expect(mockNext).toHaveBeenCalledTimes(1); - }, - ); - - it.each([ - ['initialPageloadRoute', 'normalRoute1', '/books/:bookId/chapter/:chapterId', 'route'], - ['initialPageloadRoute', 'namedRoute', 'login-screen', 'custom'], - ['initialPageloadRoute', 'unmatchedRoute', '/e8733846-20ac-488c-9871-a5cbcb647294', 'url'], - ])( - 'should return instrumentation that instruments VueRouter.beforeEach(%s, %s) for pageloads', - (fromKey, toKey, transactionName, transactionSource) => { - const mockedTxn = { - updateName: jest.fn(), - setData: jest.fn(), - setAttribute: jest.fn(), - setAttributes: jest.fn(), - metadata: {}, - }; - const customMockStartTxn = { ...mockStartTransaction }.mockImplementation(_ => { - return mockedTxn; - }); - jest.spyOn(vueTracing, 'getActiveTransaction').mockImplementation(() => mockedTxn as unknown as Transaction); - - // create instrumentation - const instrument = vueRouterInstrumentation(mockVueRouter); - - // instrument - instrument(customMockStartTxn, true, true); - - // check for transaction start - expect(customMockStartTxn).toHaveBeenCalledTimes(1); - expect(customMockStartTxn).toHaveBeenCalledWith({ - name: '/', - attributes: { - [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.pageload.vue', - [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'url', - }, - op: 'pageload', - }); - - const beforeEachCallback = mockVueRouter.beforeEach.mock.calls[0][0]; - - const from = testRoutes[fromKey]; - const to = testRoutes[toKey]; - - beforeEachCallback(to, from, mockNext); - expect(mockVueRouter.beforeEach).toHaveBeenCalledTimes(1); - - expect(mockedTxn.updateName).toHaveBeenCalledWith(transactionName); - expect(mockedTxn.setAttribute).toHaveBeenCalledWith(SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, transactionSource); - expect(mockedTxn.setAttributes).toHaveBeenCalledWith({ - ...getAttributesForRoute(to), - [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.pageload.vue', - }); - - expect(mockNext).toHaveBeenCalledTimes(1); - }, - ); - - it('allows to configure routeLabel=path', () => { - // create instrumentation - const instrument = vueRouterInstrumentation(mockVueRouter, { routeLabel: 'path' }); - - // instrument - instrument(mockStartTransaction, true, true); - - // check - const beforeEachCallback = mockVueRouter.beforeEach.mock.calls[0][0]; - - const from = testRoutes.normalRoute1; - const to = testRoutes.namedRoute; - beforeEachCallback(to, from, mockNext); - - // first startTx call happens when the instrumentation is initialized (for pageloads) - expect(mockStartTransaction).toHaveBeenLastCalledWith({ - name: '/login', - attributes: { - [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route', - [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.navigation.vue', - ...getAttributesForRoute(to), - }, - op: 'navigation', - }); - }); - - it('allows to configure routeLabel=name', () => { - // create instrumentation - const instrument = vueRouterInstrumentation(mockVueRouter, { routeLabel: 'name' }); - - // instrument - instrument(mockStartTransaction, true, true); - - // check - const beforeEachCallback = mockVueRouter.beforeEach.mock.calls[0][0]; - - const from = testRoutes.normalRoute1; - const to = testRoutes.namedRoute; - beforeEachCallback(to, from, mockNext); - - // first startTx call happens when the instrumentation is initialized (for pageloads) - expect(mockStartTransaction).toHaveBeenLastCalledWith({ - name: 'login-screen', - attributes: { - [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'custom', - [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.navigation.vue', - ...getAttributesForRoute(to), - }, - op: 'navigation', - }); - }); - - it("doesn't overwrite a pageload transaction name it was set to custom before the router resolved the route", () => { - const mockedTxn = { - updateName: jest.fn(), - setData: jest.fn(), - setAttribute: jest.fn(), - setAttributes: jest.fn(), - name: '', - toJSON: () => ({ - data: { - [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'url', - }, - }), - }; - const customMockStartTxn = { ...mockStartTransaction }.mockImplementation(_ => { - return mockedTxn; - }); - jest.spyOn(vueTracing, 'getActiveTransaction').mockImplementation(() => mockedTxn as unknown as Transaction); - - // create instrumentation - const instrument = vueRouterInstrumentation(mockVueRouter); - - // instrument - instrument(customMockStartTxn, true, true); - - // check for transaction start - expect(customMockStartTxn).toHaveBeenCalledTimes(1); - expect(customMockStartTxn).toHaveBeenCalledWith({ - name: '/', - attributes: { - [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'url', - [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.pageload.vue', - }, - op: 'pageload', - }); - - // now we give the transaction a custom name, thereby simulating what would - // happen when users use the `beforeNavigate` hook - mockedTxn.name = 'customTxnName'; - mockedTxn.toJSON = () => ({ - data: { - [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'custom', - }, - }); - - const to = testRoutes['normalRoute1']; - const from = testRoutes['initialPageloadRoute']; - - const beforeEachCallback = mockVueRouter.beforeEach.mock.calls[0][0]; - beforeEachCallback(to, from, mockNext); - - expect(mockVueRouter.beforeEach).toHaveBeenCalledTimes(1); - - expect(mockedTxn.updateName).not.toHaveBeenCalled(); - expect(mockedTxn.setAttribute).not.toHaveBeenCalled(); - expect(mockedTxn.setAttributes).toHaveBeenCalledWith({ - [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.pageload.vue', - ...getAttributesForRoute(to), - }); - expect(mockedTxn.name).toEqual('customTxnName'); - }); - - test.each([ - [undefined, 1], - [false, 0], - [true, 1], - ])( - 'should return instrumentation that considers the startTransactionOnPageLoad option = %p', - (startTransactionOnPageLoad, expectedCallsAmount) => { - // create instrumentation - const instrument = vueRouterInstrumentation(mockVueRouter); - - // instrument - instrument(mockStartTransaction, startTransactionOnPageLoad, true); - - // check - expect(mockVueRouter.beforeEach).toHaveBeenCalledTimes(1); - - const beforeEachCallback = mockVueRouter.beforeEach.mock.calls[0][0]; - beforeEachCallback(testRoutes['normalRoute1'], testRoutes['initialPageloadRoute'], mockNext); - - expect(mockStartTransaction).toHaveBeenCalledTimes(expectedCallsAmount); - }, - ); - - test.each([ - [undefined, 1], - [false, 0], - [true, 1], - ])( - 'should return instrumentation that considers the startTransactionOnLocationChange option = %p', - (startTransactionOnLocationChange, expectedCallsAmount) => { - // create instrumentation - const instrument = vueRouterInstrumentation(mockVueRouter); - - // instrument (this will call startTrransaction once for pageloads but we can ignore that) - instrument(mockStartTransaction, true, startTransactionOnLocationChange); - - // check - expect(mockVueRouter.beforeEach).toHaveBeenCalledTimes(1); - - const beforeEachCallback = mockVueRouter.beforeEach.mock.calls[0][0]; - beforeEachCallback(testRoutes['normalRoute2'], testRoutes['normalRoute1'], mockNext); - - expect(mockStartTransaction).toHaveBeenCalledTimes(expectedCallsAmount + 1); - }, - ); - - it("doesn't throw when `next` is not available in the beforeEach callback (Vue Router 4)", () => { - const instrument = vueRouterInstrumentation(mockVueRouter, { routeLabel: 'path' }); - instrument(mockStartTransaction, true, true); - const beforeEachCallback = mockVueRouter.beforeEach.mock.calls[0][0]; - - const from = testRoutes.normalRoute1; - const to = testRoutes.namedRoute; - beforeEachCallback(to, from, undefined); - - // first startTx call happens when the instrumentation is initialized (for pageloads) - expect(mockStartTransaction).toHaveBeenLastCalledWith({ - name: '/login', - attributes: { - [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route', - [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.navigation.vue', - ...getAttributesForRoute(to), - }, - op: 'navigation', - }); - }); -}); -/* eslint-enable deprecation/deprecation */ - describe('instrumentVueRouter()', () => { afterEach(() => { jest.clearAllMocks(); @@ -421,18 +127,17 @@ describe('instrumentVueRouter()', () => { ])( 'should return instrumentation that instruments VueRouter.beforeEach(%s, %s) for pageloads', (fromKey, toKey, transactionName, transactionSource) => { - const mockedTxn = { + const mockRootSpan = { + getSpanJSON: jest.fn().mockReturnValue({ op: 'pageload' }), updateName: jest.fn(), - setData: jest.fn(), setAttribute: jest.fn(), setAttributes: jest.fn(), - metadata: {}, }; - jest.spyOn(vueTracing, 'getActiveTransaction').mockImplementation(() => mockedTxn as unknown as Transaction); + jest.spyOn(SentryCore, 'getRootSpan').mockImplementation(() => mockRootSpan as unknown as Span); const mockStartSpan = jest.fn().mockImplementation(_ => { - return mockedTxn; + return mockRootSpan; }); instrumentVueRouter( mockVueRouter, @@ -451,9 +156,9 @@ describe('instrumentVueRouter()', () => { beforeEachCallback(to, from, mockNext); expect(mockVueRouter.beforeEach).toHaveBeenCalledTimes(1); - expect(mockedTxn.updateName).toHaveBeenCalledWith(transactionName); - expect(mockedTxn.setAttribute).toHaveBeenCalledWith(SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, transactionSource); - expect(mockedTxn.setAttributes).toHaveBeenCalledWith({ + expect(mockRootSpan.updateName).toHaveBeenCalledWith(transactionName); + expect(mockRootSpan.setAttribute).toHaveBeenCalledWith(SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, transactionSource); + expect(mockRootSpan.setAttributes).toHaveBeenCalledWith({ [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.pageload.vue', ...getAttributesForRoute(to), }); @@ -517,22 +222,22 @@ describe('instrumentVueRouter()', () => { }); it("doesn't overwrite a pageload transaction name it was set to custom before the router resolved the route", () => { - const mockedTxn = { + const mockRootSpan = { updateName: jest.fn(), - setData: jest.fn(), setAttribute: jest.fn(), setAttributes: jest.fn(), name: '', - toJSON: () => ({ + getSpanJSON: () => ({ + op: 'pageload', data: { [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'url', }, }), }; const mockStartSpan = jest.fn().mockImplementation(_ => { - return mockedTxn; + return mockRootSpan; }); - jest.spyOn(vueTracing, 'getActiveTransaction').mockImplementation(() => mockedTxn as unknown as Transaction); + jest.spyOn(SentryCore, 'getRootSpan').mockImplementation(() => mockRootSpan as unknown as Span); instrumentVueRouter( mockVueRouter, @@ -545,8 +250,9 @@ describe('instrumentVueRouter()', () => { // now we give the transaction a custom name, thereby simulating what would // happen when users use the `beforeNavigate` hook - mockedTxn.name = 'customTxnName'; - mockedTxn.toJSON = () => ({ + mockRootSpan.name = 'customTxnName'; + mockRootSpan.getSpanJSON = () => ({ + op: 'pageload', data: { [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'custom', }, @@ -561,13 +267,13 @@ describe('instrumentVueRouter()', () => { expect(mockVueRouter.beforeEach).toHaveBeenCalledTimes(1); - expect(mockedTxn.updateName).not.toHaveBeenCalled(); - expect(mockedTxn.setAttribute).not.toHaveBeenCalled(); - expect(mockedTxn.setAttributes).toHaveBeenCalledWith({ + expect(mockRootSpan.updateName).not.toHaveBeenCalled(); + expect(mockRootSpan.setAttribute).not.toHaveBeenCalled(); + expect(mockRootSpan.setAttributes).toHaveBeenCalledWith({ [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.pageload.vue', ...getAttributesForRoute(to), }); - expect(mockedTxn.name).toEqual('customTxnName'); + expect(mockRootSpan.name).toEqual('customTxnName'); }); test.each([ @@ -576,19 +282,20 @@ describe('instrumentVueRouter()', () => { ])( 'should return instrumentation that considers the instrumentPageLoad = %p', (instrumentPageLoad, expectedCallsAmount) => { - const mockedTxn = { + const mockRootSpan = { updateName: jest.fn(), setData: jest.fn(), setAttribute: jest.fn(), setAttributes: jest.fn(), name: '', toJSON: () => ({ + op: 'pageload', data: { [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'url', }, }), }; - jest.spyOn(vueTracing, 'getActiveTransaction').mockImplementation(() => mockedTxn as unknown as Transaction); + jest.spyOn(SentryCore, 'getRootSpan').mockImplementation(() => mockRootSpan as unknown as Span); const mockStartSpan = jest.fn(); instrumentVueRouter( @@ -603,7 +310,7 @@ describe('instrumentVueRouter()', () => { const beforeEachCallback = mockVueRouter.beforeEach.mock.calls[0][0]; beforeEachCallback(testRoutes['normalRoute1'], testRoutes['initialPageloadRoute'], mockNext); - expect(mockedTxn.updateName).toHaveBeenCalledTimes(expectedCallsAmount); + expect(mockRootSpan.updateName).toHaveBeenCalledTimes(expectedCallsAmount); expect(mockStartSpan).not.toHaveBeenCalled(); }, );