diff --git a/dev-packages/browser-integration-tests/suites/integrations/graphqlClient/fetch/test.ts b/dev-packages/browser-integration-tests/suites/integrations/graphqlClient/fetch/test.ts index d923b1a41970..c200e891f674 100644 --- a/dev-packages/browser-integration-tests/suites/integrations/graphqlClient/fetch/test.ts +++ b/dev-packages/browser-integration-tests/suites/integrations/graphqlClient/fetch/test.ts @@ -2,7 +2,7 @@ import { expect } from '@playwright/test'; import type { Event } from '@sentry/core'; import { sentryTest } from '../../../../utils/fixtures'; -import { getFirstSentryEnvelopeRequest } from '../../../../utils/helpers'; +import { getFirstSentryEnvelopeRequest, shouldSkipTracingTest } from '../../../../utils/helpers'; // Duplicate from subject.js const query = `query Test{ @@ -13,7 +13,11 @@ const query = `query Test{ }`; const queryPayload = JSON.stringify({ query }); -sentryTest('should update spans for GraphQL Fetch requests', async ({ getLocalTestUrl, page }) => { +sentryTest('should update spans for GraphQL fetch requests', async ({ getLocalTestUrl, page }) => { + if (shouldSkipTracingTest()) { + return; + } + const url = await getLocalTestUrl({ testDir: __dirname }); await page.route('**/foo', route => { @@ -57,7 +61,11 @@ sentryTest('should update spans for GraphQL Fetch requests', async ({ getLocalTe }); }); -sentryTest('should update breadcrumbs for GraphQL Fetch requests', async ({ getLocalTestUrl, page }) => { +sentryTest('should update breadcrumbs for GraphQL fetch requests', async ({ getLocalTestUrl, page }) => { + if (shouldSkipTracingTest()) { + return; + } + const url = await getLocalTestUrl({ testDir: __dirname }); await page.route('**/foo', route => { diff --git a/dev-packages/browser-integration-tests/suites/integrations/graphqlClient/init.js b/dev-packages/browser-integration-tests/suites/integrations/graphqlClient/init.js index 7ca9df70b6c3..ec5f5b76cd44 100644 --- a/dev-packages/browser-integration-tests/suites/integrations/graphqlClient/init.js +++ b/dev-packages/browser-integration-tests/suites/integrations/graphqlClient/init.js @@ -1,4 +1,6 @@ import * as Sentry from '@sentry/browser'; +// Must import this like this to ensure the test transformation for CDN bundles works +import { graphqlClientIntegration } from '@sentry/browser'; window.Sentry = Sentry; @@ -6,7 +8,7 @@ Sentry.init({ dsn: 'https://public@dsn.ingest.sentry.io/1337', integrations: [ Sentry.browserTracingIntegration(), - Sentry.graphqlClientIntegration({ + graphqlClientIntegration({ endpoints: ['http://sentry-test.io/foo'], }), ], diff --git a/dev-packages/browser-integration-tests/suites/integrations/graphqlClient/xhr/test.ts b/dev-packages/browser-integration-tests/suites/integrations/graphqlClient/xhr/test.ts index 1be028abea46..1beaf001d5a2 100644 --- a/dev-packages/browser-integration-tests/suites/integrations/graphqlClient/xhr/test.ts +++ b/dev-packages/browser-integration-tests/suites/integrations/graphqlClient/xhr/test.ts @@ -2,7 +2,7 @@ import { expect } from '@playwright/test'; import type { Event } from '@sentry/core'; import { sentryTest } from '../../../../utils/fixtures'; -import { getFirstSentryEnvelopeRequest } from '../../../../utils/helpers'; +import { getFirstSentryEnvelopeRequest, shouldSkipTracingTest } from '../../../../utils/helpers'; // Duplicate from subject.js const query = `query Test{ @@ -14,6 +14,10 @@ const query = `query Test{ const queryPayload = JSON.stringify({ query }); sentryTest('should update spans for GraphQL XHR requests', async ({ getLocalTestUrl, page }) => { + if (shouldSkipTracingTest()) { + return; + } + const url = await getLocalTestUrl({ testDir: __dirname }); await page.route('**/foo', route => { @@ -58,6 +62,10 @@ sentryTest('should update spans for GraphQL XHR requests', async ({ getLocalTest }); sentryTest('should update breadcrumbs for GraphQL XHR requests', async ({ getLocalTestUrl, page }) => { + if (shouldSkipTracingTest()) { + return; + } + const url = await getLocalTestUrl({ testDir: __dirname }); await page.route('**/foo', route => { diff --git a/dev-packages/browser-integration-tests/utils/generatePlugin.ts b/dev-packages/browser-integration-tests/utils/generatePlugin.ts index 77792d02b19c..1cb3fea77705 100644 --- a/dev-packages/browser-integration-tests/utils/generatePlugin.ts +++ b/dev-packages/browser-integration-tests/utils/generatePlugin.ts @@ -36,6 +36,7 @@ const IMPORTED_INTEGRATION_CDN_BUNDLE_PATHS: Record = { reportingObserverIntegration: 'reportingobserver', feedbackIntegration: 'feedback', moduleMetadataIntegration: 'modulemetadata', + graphqlClientIntegration: 'graphqlclient', // technically, this is not an integration, but let's add it anyway for simplicity makeMultiplexedTransport: 'multiplexedtransport', }; diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-2-svelte-5/tests/performance.client.test.ts b/dev-packages/e2e-tests/test-applications/sveltekit-2-svelte-5/tests/performance.client.test.ts index c31e51bf9e99..a2131287ec2e 100644 --- a/dev-packages/e2e-tests/test-applications/sveltekit-2-svelte-5/tests/performance.client.test.ts +++ b/dev-packages/e2e-tests/test-applications/sveltekit-2-svelte-5/tests/performance.client.test.ts @@ -109,30 +109,6 @@ test.describe('client-specific performance events', () => { op: 'ui.svelte.init', origin: 'auto.ui.svelte', }), - expect.objectContaining({ - data: { 'sentry.op': 'ui.svelte.update', 'sentry.origin': 'auto.ui.svelte' }, - description: '', - op: 'ui.svelte.update', - origin: 'auto.ui.svelte', - }), - expect.objectContaining({ - data: { 'sentry.op': 'ui.svelte.update', 'sentry.origin': 'auto.ui.svelte' }, - description: '', - op: 'ui.svelte.update', - origin: 'auto.ui.svelte', - }), - expect.objectContaining({ - data: { 'sentry.op': 'ui.svelte.update', 'sentry.origin': 'auto.ui.svelte' }, - description: '', - op: 'ui.svelte.update', - origin: 'auto.ui.svelte', - }), - expect.objectContaining({ - data: { 'sentry.op': 'ui.svelte.update', 'sentry.origin': 'auto.ui.svelte' }, - description: '', - op: 'ui.svelte.update', - origin: 'auto.ui.svelte', - }), ]), ); }); diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-2/src/routes/components/+page.svelte b/dev-packages/e2e-tests/test-applications/sveltekit-2/src/routes/components/+page.svelte index eff3fa3f2e8d..3c1052bbbe9c 100644 --- a/dev-packages/e2e-tests/test-applications/sveltekit-2/src/routes/components/+page.svelte +++ b/dev-packages/e2e-tests/test-applications/sveltekit-2/src/routes/components/+page.svelte @@ -5,7 +5,7 @@ import Component2 from "./Component2.svelte"; import Component3 from "./Component3.svelte"; - Sentry.trackComponent({componentName: 'components/+page'}) + Sentry.trackComponent({componentName: 'components/+page', trackUpdates: true})

Demonstrating Component Tracking

diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-2/src/routes/components/Component1.svelte b/dev-packages/e2e-tests/test-applications/sveltekit-2/src/routes/components/Component1.svelte index a675711e4b68..dfcf01de0b07 100644 --- a/dev-packages/e2e-tests/test-applications/sveltekit-2/src/routes/components/Component1.svelte +++ b/dev-packages/e2e-tests/test-applications/sveltekit-2/src/routes/components/Component1.svelte @@ -2,7 +2,7 @@ import Component2 from "./Component2.svelte"; import {trackComponent} from '@sentry/sveltekit'; - trackComponent({componentName: 'Component1'}); + trackComponent({componentName: 'Component1', trackUpdates: true});

Howdy, I'm component 1

diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-2/src/routes/components/Component2.svelte b/dev-packages/e2e-tests/test-applications/sveltekit-2/src/routes/components/Component2.svelte index 2b2f38308077..1b3ad103b3b7 100644 --- a/dev-packages/e2e-tests/test-applications/sveltekit-2/src/routes/components/Component2.svelte +++ b/dev-packages/e2e-tests/test-applications/sveltekit-2/src/routes/components/Component2.svelte @@ -2,7 +2,7 @@ import Component3 from "./Component3.svelte"; import {trackComponent} from '@sentry/sveltekit'; - trackComponent({componentName: 'Component2'}); + trackComponent({componentName: 'Component2', trackUpdates: true});

Howdy, I'm component 2

diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-2/src/routes/components/Component3.svelte b/dev-packages/e2e-tests/test-applications/sveltekit-2/src/routes/components/Component3.svelte index 9b4e028f78e7..9b813ff2c744 100644 --- a/dev-packages/e2e-tests/test-applications/sveltekit-2/src/routes/components/Component3.svelte +++ b/dev-packages/e2e-tests/test-applications/sveltekit-2/src/routes/components/Component3.svelte @@ -1,6 +1,6 @@

Howdy, I'm component 3

diff --git a/dev-packages/node-integration-tests/suites/tracing/sample-rate-propagation/tracesSampler/server.js b/dev-packages/node-integration-tests/suites/tracing/sample-rate-propagation/tracesSampler/server.js index 5dc1d17588e5..5f616438fe90 100644 --- a/dev-packages/node-integration-tests/suites/tracing/sample-rate-propagation/tracesSampler/server.js +++ b/dev-packages/node-integration-tests/suites/tracing/sample-rate-propagation/tracesSampler/server.js @@ -4,12 +4,8 @@ const Sentry = require('@sentry/node'); Sentry.init({ dsn: 'https://public@dsn.ingest.sentry.io/1337', transport: loggingTransport, - tracesSampler: ({ parentSampleRate }) => { - if (parentSampleRate) { - return parentSampleRate; - } - - return 0.69; + tracesSampler: ({ inheritOrSampleWith }) => { + return inheritOrSampleWith(0.69); }, }); diff --git a/dev-packages/node-integration-tests/suites/tracing/sample-rate-propagation/tracesSampler/test.ts b/dev-packages/node-integration-tests/suites/tracing/sample-rate-propagation/tracesSampler/test.ts index 304725268f03..f97773711941 100644 --- a/dev-packages/node-integration-tests/suites/tracing/sample-rate-propagation/tracesSampler/test.ts +++ b/dev-packages/node-integration-tests/suites/tracing/sample-rate-propagation/tracesSampler/test.ts @@ -11,7 +11,7 @@ describe('parentSampleRate propagation with tracesSampler', () => { expect((response as any).propagatedData.baggage).toMatch(/sentry-sample_rate=0\.69/); }); - test('should propagate sample_rate equivalent to sample rate returned by tracesSampler when there is no incoming sample rate', async () => { + test('should propagate sample_rate equivalent to sample rate returned by tracesSampler when there is no incoming sample rate (1 -> because there is a positive sampling decision and inheritOrSampleWith was used)', async () => { const runner = createRunner(__dirname, 'server.js').start(); const response = await runner.makeRequest('get', '/check', { headers: { @@ -20,6 +20,30 @@ describe('parentSampleRate propagation with tracesSampler', () => { }, }); + expect((response as any).propagatedData.baggage).toMatch(/sentry-sample_rate=1/); + }); + + test('should propagate sample_rate equivalent to sample rate returned by tracesSampler when there is no incoming sample rate (0 -> because there is a negative sampling decision and inheritOrSampleWith was used)', async () => { + const runner = createRunner(__dirname, 'server.js').start(); + const response = await runner.makeRequest('get', '/check', { + headers: { + 'sentry-trace': '530699e319cc067ce440315d74acb312-414dc2a08d5d1dac-0', + baggage: '', + }, + }); + + expect((response as any).propagatedData.baggage).toMatch(/sentry-sample_rate=0/); + }); + + test('should propagate sample_rate equivalent to sample rate returned by tracesSampler when there is no incoming sample rate (the fallback value -> because there is no sampling decision and inheritOrSampleWith was used)', async () => { + const runner = createRunner(__dirname, 'server.js').start(); + const response = await runner.makeRequest('get', '/check', { + headers: { + 'sentry-trace': '530699e319cc067ce440315d74acb312-414dc2a08d5d1dac', + baggage: '', + }, + }); + expect((response as any).propagatedData.baggage).toMatch(/sentry-sample_rate=0\.69/); }); diff --git a/packages/browser/rollup.bundle.config.mjs b/packages/browser/rollup.bundle.config.mjs index 4567be0b297c..f24cf2c311d3 100644 --- a/packages/browser/rollup.bundle.config.mjs +++ b/packages/browser/rollup.bundle.config.mjs @@ -12,6 +12,7 @@ const reexportedPluggableIntegrationFiles = [ 'rewriteframes', 'feedback', 'modulemetadata', + 'graphqlclient', ]; browserPluggableIntegrationFiles.forEach(integrationName => { diff --git a/packages/browser/src/integrations-bundle/index.graphqlclient.ts b/packages/browser/src/integrations-bundle/index.graphqlclient.ts new file mode 100644 index 000000000000..d1a1b1e792f4 --- /dev/null +++ b/packages/browser/src/integrations-bundle/index.graphqlclient.ts @@ -0,0 +1 @@ +export { graphqlClientIntegration } from '../integrations/graphqlClient'; diff --git a/packages/browser/src/integrations/graphqlClient.ts b/packages/browser/src/integrations/graphqlClient.ts index f7f44d77142a..2c9ce06b7794 100644 --- a/packages/browser/src/integrations/graphqlClient.ts +++ b/packages/browser/src/integrations/graphqlClient.ts @@ -193,6 +193,7 @@ export function getGraphQLRequestPayload(payload: string): GraphQLRequestPayload } /** - * GraphQL Client integration for the browser. + * This integration ensures that GraphQL requests made in the browser + * have their GraphQL-specific data captured and attached to spans and breadcrumbs. */ export const graphqlClientIntegration = defineIntegration(_graphqlClientIntegration); diff --git a/packages/browser/src/utils/lazyLoadIntegration.ts b/packages/browser/src/utils/lazyLoadIntegration.ts index e6fea13c4e2a..5ffbd31adff5 100644 --- a/packages/browser/src/utils/lazyLoadIntegration.ts +++ b/packages/browser/src/utils/lazyLoadIntegration.ts @@ -15,6 +15,7 @@ const LazyLoadableIntegrations = { linkedErrorsIntegration: 'linkederrors', dedupeIntegration: 'dedupe', extraErrorDataIntegration: 'extraerrordata', + graphqlClientIntegration: 'graphqlclient', httpClientIntegration: 'httpclient', reportingObserverIntegration: 'reportingobserver', rewriteFramesIntegration: 'rewriteframes', diff --git a/packages/core/src/tracing/sampling.ts b/packages/core/src/tracing/sampling.ts index 70c62cd20992..0820b7be2cf0 100644 --- a/packages/core/src/tracing/sampling.ts +++ b/packages/core/src/tracing/sampling.ts @@ -27,7 +27,24 @@ export function sampleSpan( // work; prefer the hook if so let sampleRate; if (typeof options.tracesSampler === 'function') { - sampleRate = options.tracesSampler(samplingContext); + sampleRate = options.tracesSampler({ + ...samplingContext, + inheritOrSampleWith: fallbackSampleRate => { + // If we have an incoming parent sample rate, we'll just use that one. + // The sampling decision will be inherited because of the sample_rand that was generated when the trace reached the incoming boundaries of the SDK. + if (typeof samplingContext.parentSampleRate === 'number') { + return samplingContext.parentSampleRate; + } + + // Fallback if parent sample rate is not on the incoming trace (e.g. if there is no baggage) + // This is to provide backwards compatibility if there are incoming traces from older SDKs that don't send a parent sample rate or a sample rand. In these cases we just want to force either a sampling decision on the downstream traces via the sample rate. + if (typeof samplingContext.parentSampled === 'boolean') { + return Number(samplingContext.parentSampled); + } + + return fallbackSampleRate; + }, + }); localSampleRateWasApplied = true; } else if (samplingContext.parentSampled !== undefined) { sampleRate = samplingContext.parentSampled; diff --git a/packages/core/src/types-hoist/options.ts b/packages/core/src/types-hoist/options.ts index 58634930c993..8e52b32eacf7 100644 --- a/packages/core/src/types-hoist/options.ts +++ b/packages/core/src/types-hoist/options.ts @@ -2,7 +2,7 @@ import type { CaptureContext } from '../scope'; import type { Breadcrumb, BreadcrumbHint } from './breadcrumb'; import type { ErrorEvent, EventHint, TransactionEvent } from './event'; import type { Integration } from './integration'; -import type { SamplingContext } from './samplingcontext'; +import type { TracesSamplerSamplingContext } from './samplingcontext'; import type { SdkMetadata } from './sdkmetadata'; import type { SpanJSON } from './span'; import type { StackLineParser, StackParser } from './stacktrace'; @@ -243,7 +243,7 @@ export interface ClientOptions number | boolean; + tracesSampler?: (samplingContext: TracesSamplerSamplingContext) => number | boolean; /** * An event-processing callback for error and message events, guaranteed to be invoked after all other event diff --git a/packages/core/src/types-hoist/samplingcontext.ts b/packages/core/src/types-hoist/samplingcontext.ts index 6f0d2a0800cf..b0a52862870c 100644 --- a/packages/core/src/types-hoist/samplingcontext.ts +++ b/packages/core/src/types-hoist/samplingcontext.ts @@ -10,9 +10,7 @@ export interface CustomSamplingContext { } /** - * Data passed to the `tracesSampler` function, which forms the basis for whatever decisions it might make. - * - * Adds default data to data provided by the user. + * Auxiliary data for various sampling mechanisms in the Sentry SDK. */ export interface SamplingContext extends CustomSamplingContext { /** @@ -42,3 +40,13 @@ export interface SamplingContext extends CustomSamplingContext { /** Initial attributes that have been passed to the span being sampled. */ attributes?: SpanAttributes; } + +/** + * Auxiliary data passed to the `tracesSampler` function. + */ +export interface TracesSamplerSamplingContext extends SamplingContext { + /** + * Returns a sample rate value that matches the sampling decision from the incoming trace, or falls back to the provided `fallbackSampleRate`. + */ + inheritOrSampleWith: (fallbackSampleRate: number) => number; +} diff --git a/packages/core/test/lib/tracing/trace.test.ts b/packages/core/test/lib/tracing/trace.test.ts index 0eee7338a93d..c33b50c01a85 100644 --- a/packages/core/test/lib/tracing/trace.test.ts +++ b/packages/core/test/lib/tracing/trace.test.ts @@ -608,6 +608,7 @@ describe('startSpan', () => { test2: 'aa', test3: 'bb', }, + inheritOrSampleWith: expect.any(Function), }); }); diff --git a/packages/feedback/src/screenshot/components/CropIcon.tsx b/packages/feedback/src/screenshot/components/CropIcon.tsx new file mode 100644 index 000000000000..091179d86004 --- /dev/null +++ b/packages/feedback/src/screenshot/components/CropIcon.tsx @@ -0,0 +1,23 @@ +import type { VNode, h as hType } from 'preact'; + +interface FactoryParams { + h: typeof hType; +} + +export default function CropIconFactory({ + h, // eslint-disable-line @typescript-eslint/no-unused-vars +}: FactoryParams) { + return function CropIcon(): VNode { + return ( + + + + ); + }; +} diff --git a/packages/feedback/src/screenshot/components/PenIcon.tsx b/packages/feedback/src/screenshot/components/PenIcon.tsx index ec50862c1dd4..75a0faedf480 100644 --- a/packages/feedback/src/screenshot/components/PenIcon.tsx +++ b/packages/feedback/src/screenshot/components/PenIcon.tsx @@ -9,7 +9,7 @@ export default function PenIconFactory({ }: FactoryParams) { return function PenIcon(): VNode { return ( - + ({ __html: createScreenshotInputStyles(options.styleNonce).innerText }), []); @@ -86,6 +88,7 @@ export function ScreenshotEditorFactory({ const [croppingRect, setCroppingRect] = hooks.useState({ startX: 0, startY: 0, endX: 0, endY: 0 }); const [confirmCrop, setConfirmCrop] = hooks.useState(false); const [isResizing, setIsResizing] = hooks.useState(false); + const [isCropping, setIsCropping] = hooks.useState(true); const [isAnnotating, setIsAnnotating] = hooks.useState(false); hooks.useEffect(() => { @@ -142,6 +145,10 @@ export function ScreenshotEditorFactory({ const croppingBox = constructRect(croppingRect); ctx.clearRect(0, 0, imageDimensions.width, imageDimensions.height); + if (!isCropping) { + return; + } + // draw gray overlay around the selection ctx.fillStyle = 'rgba(0, 0, 0, 0.5)'; ctx.fillRect(0, 0, imageDimensions.width, imageDimensions.height); @@ -154,7 +161,7 @@ export function ScreenshotEditorFactory({ ctx.strokeStyle = '#000000'; ctx.lineWidth = 1; ctx.strokeRect(croppingBox.x + 3, croppingBox.y + 3, croppingBox.width - 6, croppingBox.height - 6); - }, [croppingRect]); + }, [croppingRect, isCropping]); function onGrabButton(e: Event, corner: string): void { setIsAnnotating(false); @@ -398,102 +405,115 @@ export function ScreenshotEditorFactory({ return (
', @@ -248,7 +248,7 @@ describe('componentTrackingPreprocessor', () => { expect(processedCode.code).toEqual( '\n" + "

I'm a component with a script

\n" + '', @@ -267,7 +267,7 @@ describe('componentTrackingPreprocessor', () => { expect(processedCode.code).toEqual( '", ); }); diff --git a/packages/sveltekit/src/server/handle.ts b/packages/sveltekit/src/server/handle.ts index 9bb9de9ce394..3a26ee64fd2a 100644 --- a/packages/sveltekit/src/server/handle.ts +++ b/packages/sveltekit/src/server/handle.ts @@ -3,7 +3,6 @@ import { SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, continueTrace, - getActiveSpan, getCurrentScope, getDefaultIsolationScope, getIsolationScope, @@ -100,19 +99,13 @@ export function sentryHandle(handlerOptions?: SentryHandleOptions): Handle { }; const sentryRequestHandler: Handle = input => { - // event.isSubRequest was added in SvelteKit 1.21.0 and we can use it to check - // if we should create a new execution context or not. // In case of a same-origin `fetch` call within a server`load` function, // SvelteKit will actually just re-enter the `handle` function and set `isSubRequest` // to `true` so that no additional network call is made. // We want the `http.server` span of that nested call to be a child span of the // currently active span instead of a new root span to correctly reflect this // behavior. - // As a fallback for Kit < 1.21.0, we check if there is an active span only if there's none, - // we create a new execution context. - const isSubRequest = typeof input.event.isSubRequest === 'boolean' ? input.event.isSubRequest : !!getActiveSpan(); - - if (isSubRequest) { + if (input.event.isSubRequest) { return instrumentHandle(input, options); } diff --git a/packages/sveltekit/test/server/handle.test.ts b/packages/sveltekit/test/server/handle.test.ts index 150f59ae9bc8..cde6a78f1378 100644 --- a/packages/sveltekit/test/server/handle.test.ts +++ b/packages/sveltekit/test/server/handle.test.ts @@ -149,53 +149,6 @@ describe('sentryHandle', () => { expect(spans).toHaveLength(1); }); - it('[kit>=1.21.0] creates a child span for nested server calls (i.e. if there is an active span)', async () => { - let _span: Span | undefined = undefined; - let txnCount = 0; - client.on('spanEnd', span => { - if (span === getRootSpan(span)) { - _span = span; - ++txnCount; - } - }); - - try { - await sentryHandle()({ - event: mockEvent(), - resolve: async _ => { - // simulating a nested load call: - await sentryHandle()({ - event: mockEvent({ route: { id: 'api/users/details/[id]', isSubRequest: true } }), - resolve: resolve(type, isError), - }); - return mockResponse; - }, - }); - } catch (e) { - // - } - - expect(txnCount).toEqual(1); - expect(_span!).toBeDefined(); - - expect(spanToJSON(_span!).description).toEqual('GET /users/[id]'); - expect(spanToJSON(_span!).op).toEqual('http.server'); - expect(spanToJSON(_span!).status).toEqual(isError ? 'internal_error' : 'ok'); - expect(spanToJSON(_span!).data?.[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]).toEqual('route'); - - expect(spanToJSON(_span!).timestamp).toBeDefined(); - - const spans = getSpanDescendants(_span!).map(spanToJSON); - - expect(spans).toHaveLength(2); - expect(spans).toEqual( - expect.arrayContaining([ - expect.objectContaining({ op: 'http.server', description: 'GET /users/[id]' }), - expect.objectContaining({ op: 'http.server', description: 'GET api/users/details/[id]' }), - ]), - ); - }); - it('creates a child span for nested server calls (i.e. if there is an active span)', async () => { let _span: Span | undefined = undefined; let txnCount = 0; @@ -212,7 +165,7 @@ describe('sentryHandle', () => { resolve: async _ => { // simulating a nested load call: await sentryHandle()({ - event: mockEvent({ route: { id: 'api/users/details/[id]' } }), + event: mockEvent({ route: { id: 'api/users/details/[id]', isSubRequest: true } }), resolve: resolve(type, isError), }); return mockResponse;