Skip to content

ref(nextjs): Make integration utils generic #3584

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
May 21, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 20 additions & 7 deletions packages/nextjs/src/index.server.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { RewriteFrames } from '@sentry/integrations';
import { configureScope, init as nodeInit } from '@sentry/node';

import { instrumentServer } from './utils/instrumentServer';
import { MetadataBuilder } from './utils/metadataBuilder';
import { NextjsOptions } from './utils/nextjsOptions';
import { defaultRewriteFrames, getFinalServerIntegrations } from './utils/serverIntegrations';
import { addIntegration } from './utils/userIntegrations';

export * from '@sentry/node';

Expand All @@ -16,12 +17,7 @@ export function init(options: NextjsOptions): void {
const metadataBuilder = new MetadataBuilder(options, ['nextjs', 'node']);
metadataBuilder.addSdkMetadata();
options.environment = options.environment || process.env.NODE_ENV;
if (options.integrations) {
options.integrations = getFinalServerIntegrations(options.integrations);
} else {
options.integrations = [defaultRewriteFrames];
}

addServerIntegrations(options);
// Right now we only capture frontend sessions for Next.js
options.autoSessionTracking = false;

Expand All @@ -31,6 +27,23 @@ export function init(options: NextjsOptions): void {
});
}

const SOURCEMAP_FILENAME_REGEX = /^.*\/\.next\//;

const defaultRewriteFramesIntegration = new RewriteFrames({
iteratee: frame => {
frame.filename = frame.filename?.replace(SOURCEMAP_FILENAME_REGEX, 'app:///_next/');
return frame;
},
});

function addServerIntegrations(options: NextjsOptions): void {
if (options.integrations) {
options.integrations = addIntegration(defaultRewriteFramesIntegration, options.integrations);
} else {
options.integrations = [defaultRewriteFramesIntegration];
}
}

export { withSentryConfig } from './utils/config';
export { withSentry } from './utils/handlers';

Expand Down
55 changes: 0 additions & 55 deletions packages/nextjs/src/utils/serverIntegrations.ts

This file was deleted.

41 changes: 41 additions & 0 deletions packages/nextjs/src/utils/userIntegrations.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { Integration } from '@sentry/types';

export type UserFunctionIntegrations = (integrations: Integration[]) => Integration[];
type UserIntegrations = Integration[] | UserFunctionIntegrations;

/**
* Retrieves the patched integrations with the provided integration.
*
* The integration must be present in the final user integrations, and they are compared
* by integration name. If the user has defined one, there's nothing to patch; if not,
* the provided integration is added.
*
* @param integration The integration to patch, if necessary.
* @param userIntegrations Integrations defined by the user.
* @returns Final integrations, patched if necessary.
*/
export function addIntegration(integration: Integration, userIntegrations: UserIntegrations): UserIntegrations {
if (Array.isArray(userIntegrations)) {
return addIntegrationToArray(integration, userIntegrations);
} else {
return addIntegrationToFunction(integration, userIntegrations);
}
}

function addIntegrationToArray(integration: Integration, userIntegrations: Integration[]): Integration[] {
if (userIntegrations.map(int => int.name).includes(integration.name)) {
return userIntegrations;
}
return [...userIntegrations, integration];
}

function addIntegrationToFunction(
integration: Integration,
userIntegrationsFunc: UserFunctionIntegrations,
): UserFunctionIntegrations {
const wrapper: UserFunctionIntegrations = defaultIntegrations => {
const userFinalIntegrations = userIntegrationsFunc(defaultIntegrations);
return addIntegrationToArray(integration, userFinalIntegrations);
};
return wrapper;
}
50 changes: 0 additions & 50 deletions packages/nextjs/test/serverIntegrations.test.ts

This file was deleted.

50 changes: 50 additions & 0 deletions packages/nextjs/test/userIntegrations.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { RewriteFrames } from '@sentry/integrations';
import { Integration } from '@sentry/types';

import { addIntegration, UserFunctionIntegrations } from '../src/utils/userIntegrations';

const testIntegration = new RewriteFrames();

describe('user integrations without any integrations', () => {
test('as an array', () => {
const userIntegrations: Integration[] = [];
// Should get a single integration
let finalIntegrations = addIntegration(testIntegration, userIntegrations);
expect(finalIntegrations).toBeInstanceOf(Array);
finalIntegrations = finalIntegrations as Integration[];
expect(finalIntegrations).toHaveLength(1);
expect(finalIntegrations[0]).toMatchObject(testIntegration);
});

test('as a function', () => {
const userIntegrationFnc: UserFunctionIntegrations = (): Integration[] => [];
// Should get a single integration
const integrationWrapper = addIntegration(testIntegration, userIntegrationFnc);
expect(integrationWrapper).toBeInstanceOf(Function);
const finalIntegrations = (integrationWrapper as UserFunctionIntegrations)([]);
expect(finalIntegrations).toHaveLength(1);
expect(finalIntegrations[0]).toMatchObject(testIntegration);
});
});

describe('user integrations with integrations', () => {
test('as an array', () => {
const userIntegrations = [new RewriteFrames()];
// Should get the same array (with no patches)
const finalIntegrations = addIntegration(testIntegration, userIntegrations);
expect(finalIntegrations).toMatchObject(userIntegrations);
});

test('as a function', () => {
const userIntegrations = [new RewriteFrames()];
const integrationsFnc: UserFunctionIntegrations = (_integrations: Integration[]): Integration[] => {
return userIntegrations;
};
// Should get a function that returns the test integration
let finalIntegrations = addIntegration(testIntegration, integrationsFnc);
expect(typeof finalIntegrations === 'function').toBe(true);
expect(finalIntegrations).toBeInstanceOf(Function);
finalIntegrations = finalIntegrations as UserFunctionIntegrations;
expect(finalIntegrations([])).toMatchObject(userIntegrations);
});
});