diff --git a/packages/gatsby/src/index.ts b/packages/gatsby/src/index.ts index 7c603d040693..6957f7337700 100644 --- a/packages/gatsby/src/index.ts +++ b/packages/gatsby/src/index.ts @@ -1,3 +1,4 @@ export * from '@sentry/react'; +export { Integrations } from '@sentry/tracing'; export { init } from './sdk'; diff --git a/packages/gatsby/src/utils/integrations.ts b/packages/gatsby/src/utils/integrations.ts index 2fb0c3b6815b..3d6f725e3666 100644 --- a/packages/gatsby/src/utils/integrations.ts +++ b/packages/gatsby/src/utils/integrations.ts @@ -3,19 +3,54 @@ import { Integration } from '@sentry/types'; import { GatsbyOptions } from './types'; +type UserFnIntegrations = (integrations: Integration[]) => Integration[]; +export type UserIntegrations = Integration[] | UserFnIntegrations; + /** * Returns the integrations to add to the SDK. * If tracing is enabled, `BrowserTracing` is always present. * * @param options The options users have defined. */ -export function getIntegrationsFromOptions(options: GatsbyOptions): Integration[] { - const integrations = [...(options.integrations || [])]; +export function getIntegrationsFromOptions(options: GatsbyOptions): UserIntegrations { + const isTracingEnabled = Tracing.hasTracingEnabled(options); + if (options.integrations === undefined) { + return getIntegrationsFromArray([], isTracingEnabled); + } else if (Array.isArray(options.integrations)) { + return getIntegrationsFromArray(options.integrations, isTracingEnabled); + } else { + return getIntegrationsFromFunction(options.integrations, isTracingEnabled); + } +} + +/** + * Returns the integrations to add to the SDK, from the given integrations array. + * + * @param userIntegrations Array of user's integrations. + * @param isTracingEnabled Whether the user has enabled tracing. + */ +function getIntegrationsFromArray(userIntegrations: Integration[], isTracingEnabled: boolean): Integration[] { if ( - Tracing.hasTracingEnabled(options) && - !integrations.some(integration => integration.name === Tracing.Integrations.BrowserTracing.name) + isTracingEnabled && + !userIntegrations.some(integration => integration.name === Tracing.Integrations.BrowserTracing.name) ) { - integrations.push(new Tracing.Integrations.BrowserTracing()); + userIntegrations.push(new Tracing.Integrations.BrowserTracing()); } - return integrations; + return userIntegrations; +} + +/** + * Returns the integrations to add to the SDK, from the given function. + * + * @param userIntegrations Function returning the user's integrations. + * @param isTracingEnabled Whether the user has enabled tracing. + */ +function getIntegrationsFromFunction( + userIntegrations: UserFnIntegrations, + isTracingEnabled: boolean, +): UserFnIntegrations { + const wrapper: UserFnIntegrations = (defaultIntegrations: Integration[]) => { + return getIntegrationsFromArray(userIntegrations(defaultIntegrations), isTracingEnabled); + }; + return wrapper; } diff --git a/packages/gatsby/test/sdk.test.ts b/packages/gatsby/test/sdk.test.ts index f06b946f7b55..df5ae6f263e7 100644 --- a/packages/gatsby/test/sdk.test.ts +++ b/packages/gatsby/test/sdk.test.ts @@ -3,6 +3,7 @@ import { Integrations } from '@sentry/tracing'; import { Integration, Package } from '@sentry/types'; import { defaultOptions, init as gatsbyInit } from '../src/sdk'; +import { UserIntegrations } from '../src/utils/integrations'; import { GatsbyOptions } from '../src/utils/types'; const reactInit = reactInitRaw as jest.Mock; @@ -65,28 +66,75 @@ describe('Integrations from options', () => { afterEach(() => reactInit.mockClear()); test.each([ - ['tracing disabled, no integrations', {}, []], - ['tracing enabled, no integrations', { tracesSampleRate: 1 }, ['BrowserTracing']], + ['tracing disabled, no integrations', [], {}, []], + ['tracing enabled, no integrations', [], { tracesSampleRate: 1 }, ['BrowserTracing']], [ - 'tracing disabled, with Integrations.BrowserTracing', + 'tracing disabled, with Integrations.BrowserTracing as an array', + [], { integrations: [new Integrations.BrowserTracing()] }, ['BrowserTracing'], ], [ - 'tracing enabled, with Integrations.BrowserTracing', + 'tracing disabled, with Integrations.BrowserTracing as a function', + [], + { + integrations: () => [new Integrations.BrowserTracing()], + }, + ['BrowserTracing'], + ], + [ + 'tracing enabled, with Integrations.BrowserTracing as an array', + [], { tracesSampleRate: 1, integrations: [new Integrations.BrowserTracing()] }, ['BrowserTracing'], ], [ - 'tracing enabled, with another integration', + 'tracing enabled, with Integrations.BrowserTracing as a function', + [], + { tracesSampleRate: 1, integrations: () => [new Integrations.BrowserTracing()] }, + ['BrowserTracing'], + ], + [ + 'tracing enabled, with another integration as an array', + [], { tracesSampleRate: 1, integrations: [new Integrations.Express()] }, ['Express', 'BrowserTracing'], ], - ['tracing disabled, with another integration', { integrations: [new Integrations.Express()] }, ['Express']], - ])('%s', (_testName, options: GatsbyOptions, expectedIntNames: string[]) => { + [ + 'tracing enabled, with another integration as a function', + [], + { tracesSampleRate: 1, integrations: () => [new Integrations.Express()] }, + ['Express', 'BrowserTracing'], + ], + [ + 'tracing disabled, with another integration as an array', + [], + { integrations: [new Integrations.Express()] }, + ['Express'], + ], + [ + 'tracing disabled, with another integration as a function', + [], + { integrations: () => [new Integrations.Express()] }, + ['Express'], + ], + [ + 'merges integrations with user integrations as a function', + [new Integrations.Mongo()], + { + tracesSampleRate: 1, + integrations: (defaultIntegrations: Integration[]): Integration[] => [ + ...defaultIntegrations, + new Integrations.Express(), + ], + }, + ['Mongo', 'Express', 'BrowserTracing'], + ], + ])('%s', (_testName, defaultIntegrations: Integration[], options: GatsbyOptions, expectedIntNames: string[]) => { gatsbyInit(options); - const integrations: Integration[] = reactInit.mock.calls[0][0].integrations; - expect(integrations).toHaveLength(expectedIntNames.length); - integrations.map((integration, idx) => expect(integration.name).toStrictEqual(expectedIntNames[idx])); + const integrations: UserIntegrations = reactInit.mock.calls[0][0].integrations; + const arrIntegrations = Array.isArray(integrations) ? integrations : integrations(defaultIntegrations); + expect(arrIntegrations).toHaveLength(expectedIntNames.length); + arrIntegrations.map((integration, idx) => expect(integration.name).toStrictEqual(expectedIntNames[idx])); }); });