diff --git a/packages/gatsby/gatsby-browser.js b/packages/gatsby/gatsby-browser.js index b4c02fb7be20..e0ab9d5199da 100644 --- a/packages/gatsby/gatsby-browser.js +++ b/packages/gatsby/gatsby-browser.js @@ -1,40 +1,16 @@ -const Sentry = require('@sentry/react'); -const Tracing = require('@sentry/tracing'); +const Sentry = require('@sentry/gatsby'); exports.onClientEntry = function(_, pluginParams) { if (pluginParams === undefined) { return; } - pluginParams._metadata = pluginParams._metadata || {}; - pluginParams._metadata.sdk = { - name: 'sentry.javascript.gatsby', - packages: [ - { - name: 'npm:@sentry/gatsby', - version: Sentry.SDK_VERSION, - }, - ], - version: Sentry.SDK_VERSION, - }; - - const integrations = [...(pluginParams.integrations || [])]; - - if (Tracing.hasTracingEnabled(pluginParams) && !integrations.some(ele => ele.name === 'BrowserTracing')) { - integrations.push(new Tracing.Integrations.BrowserTracing(pluginParams.browserTracingOptions)); - } - - Tracing.addExtensionMethods(); - Sentry.init({ - autoSessionTracking: true, - environment: process.env.NODE_ENV || 'development', // eslint-disable-next-line no-undef release: __SENTRY_RELEASE__, // eslint-disable-next-line no-undef dsn: __SENTRY_DSN__, ...pluginParams, - integrations, }); window.Sentry = Sentry; diff --git a/packages/gatsby/package.json b/packages/gatsby/package.json index 5ae9aaad128d..f30e41771303 100644 --- a/packages/gatsby/package.json +++ b/packages/gatsby/package.json @@ -33,8 +33,10 @@ "gatsby": "^2.0.0 || ^3.0.0" }, "devDependencies": { - "@sentry-internal/eslint-config-sdk": "6.13.3", + "@sentry-internal/eslint-config-sdk": "6.13.2", + "@sentry/types": "6.13.2", "@testing-library/react": "^10.4.9", + "@types/node": "^16.10.3", "jest": "^24.7.1", "npm-run-all": "^4.1.2", "prettier": "1.19.0", diff --git a/packages/gatsby/src/index.ts b/packages/gatsby/src/index.ts index c45aad673ad0..7c603d040693 100644 --- a/packages/gatsby/src/index.ts +++ b/packages/gatsby/src/index.ts @@ -1 +1,3 @@ export * from '@sentry/react'; + +export { init } from './sdk'; diff --git a/packages/gatsby/src/sdk.ts b/packages/gatsby/src/sdk.ts new file mode 100644 index 000000000000..afbc06c1143b --- /dev/null +++ b/packages/gatsby/src/sdk.ts @@ -0,0 +1,33 @@ +import { init as reactInit, SDK_VERSION } from '@sentry/react'; + +import { getIntegrationsFromOptions } from './utils/integrations'; +import { GatsbyOptions } from './utils/types'; + +export const defaultOptions = { + autoSessionTracking: true, + environment: process.env.NODE_ENV || 'development', +}; + +/** + * Inits the Sentry Gatsby SDK. + */ +export function init(options: GatsbyOptions): void { + options._metadata = options._metadata || {}; + options._metadata.sdk = options._metadata.sdk || { + name: 'sentry.javascript.gatsby', + packages: [ + { + name: 'npm:@sentry/gatsby', + version: SDK_VERSION, + }, + ], + version: SDK_VERSION, + }; + + const integrations = getIntegrationsFromOptions(options); + reactInit({ + ...defaultOptions, + ...options, + integrations, + }); +} diff --git a/packages/gatsby/src/utils/integrations.ts b/packages/gatsby/src/utils/integrations.ts new file mode 100644 index 000000000000..2fb0c3b6815b --- /dev/null +++ b/packages/gatsby/src/utils/integrations.ts @@ -0,0 +1,21 @@ +import * as Tracing from '@sentry/tracing'; +import { Integration } from '@sentry/types'; + +import { GatsbyOptions } from './types'; + +/** + * 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 || [])]; + if ( + Tracing.hasTracingEnabled(options) && + !integrations.some(integration => integration.name === Tracing.Integrations.BrowserTracing.name) + ) { + integrations.push(new Tracing.Integrations.BrowserTracing()); + } + return integrations; +} diff --git a/packages/gatsby/src/utils/types.ts b/packages/gatsby/src/utils/types.ts new file mode 100644 index 000000000000..936a5b3c6ae6 --- /dev/null +++ b/packages/gatsby/src/utils/types.ts @@ -0,0 +1,3 @@ +import { BrowserOptions } from '@sentry/react'; + +export type GatsbyOptions = BrowserOptions; diff --git a/packages/gatsby/test/gatsby-browser.test.ts b/packages/gatsby/test/gatsby-browser.test.ts index 3dc764ea5c50..e39926e77b37 100644 --- a/packages/gatsby/test/gatsby-browser.test.ts +++ b/packages/gatsby/test/gatsby-browser.test.ts @@ -7,8 +7,8 @@ const { onClientEntry } = require('../gatsby-browser'); (global as any).__SENTRY_DSN__ = 'https://examplePublicKey@o0.ingest.sentry.io/0'; let sentryInit = jest.fn(); -jest.mock('@sentry/react', () => { - const original = jest.requireActual('@sentry/react'); +jest.mock('@sentry/gatsby', () => { + const original = jest.requireActual('@sentry/gatsby'); return { ...original, init: (...args: any[]) => { @@ -38,28 +38,16 @@ describe('onClientEntry', () => { (window as any).Sentry = undefined; }); - it('inits Sentry by default', () => { - onClientEntry(undefined, {}); + it.each([ + [{}, ['dsn', 'release']], + [{ key: 'value' }, ['dsn', 'release', 'key']], + ])('inits Sentry by default', (pluginParams, expectedKeys) => { + onClientEntry(undefined, pluginParams); expect(sentryInit).toHaveBeenCalledTimes(1); - expect(sentryInit).toHaveBeenLastCalledWith({ - dsn: (global as any).__SENTRY_DSN__, - environment: process.env.NODE_ENV, - integrations: [], - release: (global as any).__SENTRY_RELEASE__, - autoSessionTracking: true, - _metadata: { - sdk: { - name: 'sentry.javascript.gatsby', - packages: [ - { - name: 'npm:@sentry/gatsby', - version: expect.any(String), - }, - ], - version: expect.any(String), - }, - }, - }); + const calledWith = sentryInit.mock.calls[0][0]; + for (const key of expectedKeys) { + expect(calledWith[key]).toBeDefined(); + } }); it('sets window.Sentry', () => { @@ -67,13 +55,6 @@ describe('onClientEntry', () => { expect((window as any).Sentry).not.toBeUndefined(); }); - it('adds Tracing extension methods', () => { - onClientEntry(undefined, {}); - - expect(tracingAddExtensionMethods).toHaveBeenCalledTimes(1); - expect(tracingAddExtensionMethods).toHaveBeenLastCalledWith(); - }); - it('sets a tracesSampleRate if defined as option', () => { onClientEntry(undefined, { tracesSampleRate: 0.5 }); expect(sentryInit).toHaveBeenLastCalledWith( @@ -93,25 +74,6 @@ describe('onClientEntry', () => { ); }); - it('adds `BrowserTracing` integration if tracesSampleRate is defined', () => { - onClientEntry(undefined, { tracesSampleRate: 0.5 }); - expect(sentryInit).toHaveBeenLastCalledWith( - expect.objectContaining({ - integrations: [expect.objectContaining({ name: 'BrowserTracing' })], - }), - ); - }); - - it('adds `BrowserTracing` integration if tracesSampler is defined', () => { - const tracesSampler = jest.fn(); - onClientEntry(undefined, { tracesSampler }); - expect(sentryInit).toHaveBeenLastCalledWith( - expect.objectContaining({ - integrations: [expect.objectContaining({ name: 'BrowserTracing' })], - }), - ); - }); - it('only defines a single `BrowserTracing` integration', () => { const Tracing = jest.requireActual('@sentry/tracing'); const integrations = [new Tracing.Integrations.BrowserTracing()]; diff --git a/packages/gatsby/test/index.test.ts b/packages/gatsby/test/index.test.ts index ff5898974446..2ebc76e04a7f 100644 --- a/packages/gatsby/test/index.test.ts +++ b/packages/gatsby/test/index.test.ts @@ -1,4 +1,4 @@ -import * as GatsbyIntegration from '../src'; +import * as GatsbyIntegration from '../src/index'; describe('package', () => { it('exports init', () => { diff --git a/packages/gatsby/test/sdk.test.ts b/packages/gatsby/test/sdk.test.ts new file mode 100644 index 000000000000..f06b946f7b55 --- /dev/null +++ b/packages/gatsby/test/sdk.test.ts @@ -0,0 +1,92 @@ +import { init as reactInitRaw, SDK_VERSION } from '@sentry/react'; +import { Integrations } from '@sentry/tracing'; +import { Integration, Package } from '@sentry/types'; + +import { defaultOptions, init as gatsbyInit } from '../src/sdk'; +import { GatsbyOptions } from '../src/utils/types'; + +const reactInit = reactInitRaw as jest.Mock; +jest.mock('@sentry/react'); + +describe('Initialize React SDK', () => { + afterEach(() => reactInit.mockReset()); + + test('Default init props', () => { + gatsbyInit({}); + expect(reactInit).toHaveBeenCalledTimes(1); + const calledWith = reactInit.mock.calls[0][0]; + expect(calledWith).toMatchObject(defaultOptions); + }); + + test('Has correct SDK metadata', () => { + gatsbyInit({}); + const calledWith = reactInit.mock.calls[0][0]; + const sdkMetadata = calledWith._metadata.sdk; + expect(sdkMetadata.name).toStrictEqual('sentry.javascript.gatsby'); + expect(sdkMetadata.version).toBe(SDK_VERSION); + expect(sdkMetadata.packages).toMatchInlineSnapshot(` + Array [ + Object { + "name": "npm:@sentry/gatsby", + "version": "6.13.3", + }, + ] + `); + }); + + describe('Environment', () => { + test('process.env', () => { + gatsbyInit({}); + expect(reactInit).toHaveBeenCalledTimes(1); + const callingObject = reactInit.mock.calls[0][0]; + expect(callingObject.environment).toStrictEqual('test'); + }); + + test('defined in the options', () => { + gatsbyInit({ + environment: 'custom env!', + }); + expect(reactInit).toHaveBeenCalledTimes(1); + const callingObject = reactInit.mock.calls[0][0]; + expect(callingObject.environment).toStrictEqual('custom env!'); + }); + }); + + test('Has BrowserTracing if tracing enabled', () => { + gatsbyInit({ tracesSampleRate: 1 }); + expect(reactInit).toHaveBeenCalledTimes(1); + const calledWith = reactInit.mock.calls[0][0]; + const integrationNames: string[] = calledWith.integrations.map((integration: Integration) => integration.name); + expect(integrationNames.some(name => name === 'BrowserTracing')).toBe(true); + }); +}); + +describe('Integrations from options', () => { + afterEach(() => reactInit.mockClear()); + + test.each([ + ['tracing disabled, no integrations', {}, []], + ['tracing enabled, no integrations', { tracesSampleRate: 1 }, ['BrowserTracing']], + [ + 'tracing disabled, with Integrations.BrowserTracing', + { integrations: [new Integrations.BrowserTracing()] }, + ['BrowserTracing'], + ], + [ + 'tracing enabled, with Integrations.BrowserTracing', + { tracesSampleRate: 1, integrations: [new Integrations.BrowserTracing()] }, + ['BrowserTracing'], + ], + [ + 'tracing enabled, with another integration', + { tracesSampleRate: 1, integrations: [new Integrations.Express()] }, + ['Express', 'BrowserTracing'], + ], + ['tracing disabled, with another integration', { integrations: [new Integrations.Express()] }, ['Express']], + ])('%s', (_testName, 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])); + }); +}); diff --git a/yarn.lock b/yarn.lock index b9cdf63452dc..a93560b3ca41 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2820,6 +2820,36 @@ resolved "https://registry.yarnpkg.com/@protobufjs/utf8/-/utf8-1.1.0.tgz#a777360b5b39a1a2e5106f8e858f2fd2d060c570" integrity sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA= +"@sentry-internal/eslint-config-sdk@6.13.2": + version "6.13.2" + resolved "https://registry.yarnpkg.com/@sentry-internal/eslint-config-sdk/-/eslint-config-sdk-6.13.2.tgz#9ab680d6ea6faaf1915c4f51d5cae7832df96084" + integrity sha512-FuYLQwbVok+sC2kvJZyPrhllchgx6KixwNMQMk/w/WfqxNmhf0jt3W2sHFg5gIJyFHvPABdVqwKeAdFUi327BA== + dependencies: + "@sentry-internal/eslint-plugin-sdk" "6.13.2" + "@sentry-internal/typescript" "6.13.2" + "@typescript-eslint/eslint-plugin" "^3.9.0" + "@typescript-eslint/parser" "^3.9.0" + eslint-config-prettier "^6.11.0" + eslint-plugin-deprecation "^1.1.0" + eslint-plugin-import "^2.22.0" + eslint-plugin-jsdoc "^30.0.3" + eslint-plugin-simple-import-sort "^5.0.3" + +"@sentry-internal/eslint-plugin-sdk@6.13.2": + version "6.13.2" + resolved "https://registry.yarnpkg.com/@sentry-internal/eslint-plugin-sdk/-/eslint-plugin-sdk-6.13.2.tgz#0549e916de80aee717fe1bd8b606ba61ba065913" + integrity sha512-hKwvZ83sLqb7JRmJl8FjjmiVm3QVtsYDQDr3PZioLvcSaw/TS/Fr/p955gK+H2m4p6wSML3iNRZ5xMXUd5wuvw== + dependencies: + requireindex "~1.1.0" + +"@sentry-internal/typescript@6.13.2": + version "6.13.2" + resolved "https://registry.yarnpkg.com/@sentry-internal/typescript/-/typescript-6.13.2.tgz#9e84e5df44a8919d7a9920ed30751b29450c820c" + integrity sha512-Hd+OZHMsxKQdz3MQejdEdGqQ4Ey1Qg4WBFcBgmwRocAq6Ao6NKMeUbNhW+YZ0GzYryvd8Hy/ebzBRq4MdzxxKg== + dependencies: + tslint-config-prettier "^1.18.0" + tslint-consistent-codestyle "^1.15.1" + "@sentry/cli@^1.68.0": version "1.68.0" resolved "https://registry.yarnpkg.com/@sentry/cli/-/cli-1.68.0.tgz#2ced8fac67ee01e746a45e8ee45a518d4526937e" @@ -3472,6 +3502,11 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-13.13.48.tgz#46a3df718aed5217277f2395a682e055a487e341" integrity sha512-z8wvSsgWQzkr4sVuMEEOvwMdOQjiRY2Y/ZW4fDfjfe3+TfQrZqFKOthBgk2RnVEmtOKrkwdZ7uTvsxTBLjKGDQ== +"@types/node@^16.10.3": + version "16.10.3" + resolved "https://registry.yarnpkg.com/@types/node/-/node-16.10.3.tgz#7a8f2838603ea314d1d22bb3171d899e15c57bd5" + integrity sha512-ho3Ruq+fFnBrZhUYI46n/bV2GjwzSkwuT4dTf0GkuNFmnb8nq4ny2z9JEVemFi6bdEJanHLlYfy9c6FN9B9McQ== + "@types/node@~10.17.0": version "10.17.56" resolved "https://registry.yarnpkg.com/@types/node/-/node-10.17.56.tgz#010c9e047c3ff09ddcd11cbb6cf5912725cdc2b3" @@ -4277,11 +4312,32 @@ after@0.8.2: resolved "https://registry.yarnpkg.com/after/-/after-0.8.2.tgz#fedb394f9f0e02aa9768e702bda23b505fae7e1f" integrity sha1-/ts5T58OAqqXaOcCvaI7UF+ufh8= -agent-base@4, agent-base@5, agent-base@6, agent-base@^4.3.0, agent-base@~4.2.1: +agent-base@4, agent-base@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-4.3.0.tgz#8165f01c436009bccad0b1d122f05ed770efc6ee" + integrity sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg== + dependencies: + es6-promisify "^5.0.0" + +agent-base@5: version "5.1.1" resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-5.1.1.tgz#e8fb3f242959db44d63be665db7a8e739537a32c" integrity sha512-TMeqbNl2fMW0nMjTEPOwe3J/PRFP4vqeoNuQMG0HlMrtm5QxKqdvAkZ1pRBQ/ulIyDD5Yq0nJ7YbdD8ey0TO3g== +agent-base@6: + version "6.0.2" + resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77" + integrity sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ== + dependencies: + debug "4" + +agent-base@~4.2.1: + version "4.2.1" + resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-4.2.1.tgz#d89e5999f797875674c07d87f260fc41e83e8ca9" + integrity sha512-JVwXMr9nHYTUXsBFKUqhJwvlcYU/blreOEUkhNR2eXZIvwd+c+o5V4MgDPKWnMS/56awN3TRzIP+KoPn+roQtg== + dependencies: + es6-promisify "^5.0.0" + agentkeepalive@^3.4.1: version "3.5.2" resolved "https://registry.yarnpkg.com/agentkeepalive/-/agentkeepalive-3.5.2.tgz#a113924dd3fa24a0bc3b78108c450c2abee00f67" @@ -9559,6 +9615,18 @@ es6-object-assign@^1.1.0: resolved "https://registry.yarnpkg.com/es6-object-assign/-/es6-object-assign-1.1.0.tgz#c2c3582656247c39ea107cb1e6652b6f9f24523c" integrity sha1-wsNYJlYkfDnqEHyx5mUrb58kUjw= +es6-promise@^4.0.3: + version "4.2.8" + resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.2.8.tgz#4eb21594c972bc40553d276e510539143db53e0a" + integrity sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w== + +es6-promisify@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/es6-promisify/-/es6-promisify-5.0.0.tgz#5109d62f3e56ea967c4b63505aef08291c8a5203" + integrity sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM= + dependencies: + es6-promise "^4.0.3" + escalade@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" @@ -12984,7 +13052,7 @@ jest-environment-jsdom@^24.9.0: jest-util "^24.9.0" jsdom "^11.5.1" -jest-environment-node@24, "jest-environment-node@>=24 <=26", jest-environment-node@^24.9.0: +"jest-environment-node@>=24 <=26", jest-environment-node@^24.9.0: version "24.9.0" resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-24.9.0.tgz#333d2d2796f9687f2aeebf0742b519f33c1cbfd3" integrity sha512-6d4V2f4nxzIzwendo27Tr0aFm+IXWa0XEUnaH6nU0FMaozxovt+sfRvh4J47wL1OvF83I3SSTu0XK+i4Bqe7uA==