Skip to content

feat(replay): Merge packages together & ensure bundles are built #11552

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 10 commits into from
Apr 11, 2024
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
10 changes: 1 addition & 9 deletions .craft.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,15 +32,7 @@ targets:
- name: npm
id: '@sentry-internal/feedback'
includeNames: /^sentry-internal-feedback-\d.*\.tgz$/
## 1.8 Feedback Modal package (browser only)
- name: npm
id: '@sentry-internal/feedback-modal'
includeNames: /^sentry-internal-feedback-modal-\d.*\.tgz$/
## 1.9 Feedback Screenshot package (browser only)
- name: npm
id: '@sentry-internal/feedback-screenshot'
includeNames: /^sentry-internal-feedback-screenshot-\d.*\.tgz$/
## 1.10 ReplayCanvas package (browser only)
## 1.8 ReplayCanvas package (browser only)
- name: npm
id: '@sentry-internal/replay-canvas'
includeNames: /^sentry-internal-replay-canvas-\d.*\.tgz$/
Expand Down
14 changes: 6 additions & 8 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -105,8 +105,6 @@ jobs:
- 'packages/replay/**'
- 'packages/replay-canvas/**'
- 'packages/feedback/**'
- 'packages/feedback-modal/**'
- 'packages/feedback-screenshot/**'
- 'packages/wasm/**'
browser_integration:
- *shared
Expand Down Expand Up @@ -413,6 +411,7 @@ jobs:
${{ github.workspace }}/packages/browser/build/bundles/**
${{ github.workspace }}/packages/replay/build/bundles/**
${{ github.workspace }}/packages/replay-canvas/build/bundles/**
${{ github.workspace }}/packages/feedback/build/bundles/**
${{ github.workspace }}/packages/**/*.tgz
${{ github.workspace }}/packages/aws-serverless/build/aws/dist-serverless/*.zip

Expand Down Expand Up @@ -623,21 +622,20 @@ jobs:
- bundle
- bundle_min
- bundle_replay
- bundle_replay_min
- bundle_tracing
- bundle_tracing_min
- bundle_tracing_replay
- bundle_tracing_replay_min
- bundle_tracing_replay_feedback
- bundle_tracing_replay_feedback_min
project:
- chromium
include:
# Only check all projects for esm & full bundle
# We also shard the tests as they take the longest
- bundle: bundle_tracing_replay_min
- bundle: bundle_tracing_replay_feedback_min
project: ''
shard: 1
shards: 2
- bundle: bundle_tracing_replay_min
- bundle: bundle_tracing_replay_feedback_min
project: ''
shard: 2
shards: 2
Expand All @@ -654,7 +652,7 @@ jobs:
shards: 3
exclude:
# Do not run the default chromium-only tests
- bundle: bundle_tracing_replay_min
- bundle: bundle_tracing_replay_feedback_min
project: 'chromium'
- bundle: esm
project: 'chromium'
Expand Down
4 changes: 2 additions & 2 deletions .size-limit.js
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ module.exports = [
path: 'packages/browser/build/npm/esm/index.js',
import: createImport('init', 'feedbackIntegration', 'feedbackModalIntegration', 'feedbackScreenshotIntegration'),
gzip: true,
limit: '37 KB',
limit: '40 KB',
},
{
name: '@sentry/browser (incl. sendFeedback)',
Expand Down Expand Up @@ -143,7 +143,7 @@ module.exports = [
name: 'CDN Bundle (incl. Tracing, Replay, Feedback)',
path: createCDNPath('bundle.tracing.replay.feedback.min.js'),
gzip: true,
limit: '83 KB',
limit: '86 KB',
},
// browser CDN bundles (non-gzipped)
{
Expand Down
2 changes: 0 additions & 2 deletions CODEOWNERS
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,4 @@ packages/replay @getsentry/replay-sdk-web
packages/replay-worker @getsentry/replay-sdk-web
packages/replay-canvas @getsentry/replay-sdk-web
packages/feedback @getsentry/replay-sdk-web
packages/feedback-modal @getsentry/replay-sdk-web
packages/feedback-screenshot @getsentry/replay-sdk-web
dev-packages/browser-integration-tests/suites/replay @getsentry/replay-sdk-web
2 changes: 2 additions & 0 deletions dev-packages/browser-integration-tests/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@
"test:bundle:replay:min": "PW_BUNDLE=bundle_replay_min yarn test",
"test:bundle:tracing": "PW_BUNDLE=bundle_tracing yarn test",
"test:bundle:tracing:min": "PW_BUNDLE=bundle_tracing_min yarn test",
"test:bundle:full": "PW_BUNDLE=bundle_tracing_replay_feedback yarn test",
"test:bundle:full:min": "PW_BUNDLE=bundle_tracing_replay_feedback_min yarn test",
"test:cjs": "PW_BUNDLE=cjs yarn test",
"test:esm": "PW_BUNDLE=esm yarn test",
"test:loader": "npx playwright test -c playwright.loader.config.ts --project='chromium'",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { expect } from '@playwright/test';

import { sentryTest } from '../../../utils/fixtures';
import { envelopeRequestParser, getEnvelopeType } from '../../../utils/helpers';
import { envelopeRequestParser, getEnvelopeType, shouldSkipFeedbackTest } from '../../../utils/helpers';

sentryTest('should capture feedback (@sentry-internal/feedback import)', async ({ getLocalTestPath, page }) => {
if (process.env.PW_BUNDLE) {
sentryTest('should capture feedback', async ({ getLocalTestPath, page }) => {
if (shouldSkipFeedbackTest()) {
sentryTest.skip();
}

Expand Down Expand Up @@ -39,7 +39,7 @@ sentryTest('should capture feedback (@sentry-internal/feedback import)', async (
await page.locator('[name="name"]').fill('Jane Doe');
await page.locator('[name="email"]').fill('[email protected]');
await page.locator('[name="message"]').fill('my example feedback');
await page.getByLabel('Send Bug Report').click();
await page.locator('[data-sentry-feedback] .btn--primary').click();

const feedbackEvent = envelopeRequestParser((await feedbackRequestPromise).request());
expect(feedbackEvent).toEqual({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ window.Sentry = Sentry;
Sentry.init({
dsn: 'https://[email protected]/1337',
replaysOnErrorSampleRate: 1.0,
replaysSessionSampleRate: 0,
replaysSessionSampleRate: 1.0,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this was intentional to make sure that replay is captured when in buffering mode

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it is not actually doing this today, I believe? At least the test failed 🤔 I merged this in now so we are unblocked, we can revert/tweak this a bit in a follow up!

integrations: [
Sentry.replayIntegration({
flushMinDelay: 200,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,107 +1,111 @@
import { expect } from '@playwright/test';

import { sentryTest } from '../../../../utils/fixtures';
import { envelopeRequestParser, getEnvelopeType } from '../../../../utils/helpers';
import { getCustomRecordingEvents, getReplayEvent, waitForReplayRequest } from '../../../../utils/replayHelpers';

sentryTest(
'should capture feedback (@sentry-internal/feedback import)',
async ({ forceFlushReplay, getLocalTestPath, page }) => {
if (process.env.PW_BUNDLE) {
sentryTest.skip();
import { envelopeRequestParser, getEnvelopeType, shouldSkipFeedbackTest } from '../../../../utils/helpers';
import {
collectReplayRequests,
getReplayBreadcrumbs,
shouldSkipReplayTest,
waitForReplayRequest,
} from '../../../../utils/replayHelpers';

sentryTest('should capture feedback', async ({ forceFlushReplay, getLocalTestPath, page }) => {
if (shouldSkipFeedbackTest() || shouldSkipReplayTest()) {
sentryTest.skip();
}

const reqPromise0 = waitForReplayRequest(page, 0);

const feedbackRequestPromise = page.waitForResponse(res => {
const req = res.request();

const postData = req.postData();
if (!postData) {
return false;
}

const reqPromise0 = waitForReplayRequest(page, 0);
const reqPromise1 = waitForReplayRequest(page, 1);
const reqPromise2 = waitForReplayRequest(page, 2);
const feedbackRequestPromise = page.waitForResponse(res => {
const req = res.request();

const postData = req.postData();
if (!postData) {
return false;
}

try {
return getEnvelopeType(req) === 'feedback';
} catch (err) {
return false;
}
});
try {
return getEnvelopeType(req) === 'feedback';
} catch (err) {
return false;
}
});

await page.route('https://dsn.ingest.sentry.io/**/*', route => {
return route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({ id: 'test-id' }),
});
await page.route('https://dsn.ingest.sentry.io/**/*', route => {
return route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({ id: 'test-id' }),
});
});

const url = await getLocalTestPath({ testDir: __dirname });

const [, , replayReq0] = await Promise.all([page.goto(url), page.getByText('Report a Bug').click(), reqPromise0]);

// Inputs are slow, these need to be serial
await page.locator('[name="name"]').fill('Jane Doe');
await page.locator('[name="email"]').fill('[email protected]');
await page.locator('[name="message"]').fill('my example feedback');

// Force flush here, as inputs are slow and can cause click event to be in unpredictable segments
await Promise.all([forceFlushReplay(), reqPromise1]);

const [, feedbackResp, replayReq2] = await Promise.all([
page.getByLabel('Send Bug Report').click(),
feedbackRequestPromise,
reqPromise2,
]);

const feedbackEvent = envelopeRequestParser(feedbackResp.request());
const replayEvent = getReplayEvent(replayReq0);
// Feedback breadcrumb is on second segment because we flush when "Report a Bug" is clicked
// And then the breadcrumb is sent when feedback form is submitted
const { breadcrumbs } = getCustomRecordingEvents(replayReq2);

expect(breadcrumbs).toEqual(
expect.arrayContaining([
expect.objectContaining({
category: 'sentry.feedback',
data: { feedbackId: expect.any(String) },
timestamp: expect.any(Number),
type: 'default',
}),
]),
);

expect(feedbackEvent).toEqual({
type: 'feedback',
breadcrumbs: expect.any(Array),
contexts: {
feedback: {
contact_email: '[email protected]',
message: 'my example feedback',
name: 'Jane Doe',
replay_id: replayEvent.event_id,
source: 'widget',
url: expect.stringContaining('/dist/index.html'),
},
},
level: 'info',
timestamp: expect.any(Number),
event_id: expect.stringMatching(/\w{32}/),
environment: 'production',
sdk: {
integrations: expect.arrayContaining(['Feedback']),
version: expect.any(String),
name: 'sentry.javascript.browser',
packages: expect.anything(),
},
request: {
const url = await getLocalTestPath({ testDir: __dirname });

await Promise.all([page.goto(url), page.getByText('Report a Bug').click(), reqPromise0]);

const replayRequestPromise = collectReplayRequests(page, recordingEvents => {
return getReplayBreadcrumbs(recordingEvents).some(breadcrumb => breadcrumb.category === 'sentry.feedback');
});

// Inputs are slow, these need to be serial
await page.locator('[name="name"]').fill('Jane Doe');
await page.locator('[name="email"]').fill('[email protected]');
await page.locator('[name="message"]').fill('my example feedback');

// Force flush here, as inputs are slow and can cause click event to be in unpredictable segments
await Promise.all([forceFlushReplay()]);

const [, feedbackResp] = await Promise.all([
page.locator('[data-sentry-feedback] .btn--primary').click(),
feedbackRequestPromise,
]);

const { replayEvents, replayRecordingSnapshots } = await replayRequestPromise;
const breadcrumbs = getReplayBreadcrumbs(replayRecordingSnapshots);

const replayEvent = replayEvents[0];
const feedbackEvent = envelopeRequestParser(feedbackResp.request());

expect(breadcrumbs).toEqual(
expect.arrayContaining([
expect.objectContaining({
category: 'sentry.feedback',
data: { feedbackId: expect.any(String) },
timestamp: expect.any(Number),
type: 'default',
}),
]),
);

expect(feedbackEvent).toEqual({
type: 'feedback',
breadcrumbs: expect.any(Array),
contexts: {
feedback: {
contact_email: '[email protected]',
message: 'my example feedback',
name: 'Jane Doe',
replay_id: replayEvent.event_id,
source: 'widget',
url: expect.stringContaining('/dist/index.html'),
headers: {
'User-Agent': expect.stringContaining(''),
},
},
platform: 'javascript',
});
},
);
},
level: 'info',
timestamp: expect.any(Number),
event_id: expect.stringMatching(/\w{32}/),
environment: 'production',
sdk: {
integrations: expect.arrayContaining(['Feedback']),
version: expect.any(String),
name: 'sentry.javascript.browser',
packages: expect.anything(),
},
request: {
url: expect.stringContaining('/dist/index.html'),
headers: {
'User-Agent': expect.stringContaining(''),
},
},
platform: 'javascript',
});
});
12 changes: 4 additions & 8 deletions dev-packages/browser-integration-tests/utils/generatePlugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,21 +26,14 @@ const useLoader = bundleKey.startsWith('loader');
// In this case, if we encounter this import, we want to add this CDN bundle file instead
const IMPORTED_INTEGRATION_CDN_BUNDLE_PATHS: Record<string, string> = {
httpClientIntegration: 'httpclient',
HttpClient: 'httpclient',
captureConsoleIntegration: 'captureconsole',
CaptureConsole: 'captureconsole',
debugIntegration: 'debug',
Debug: 'debug',
rewriteFramesIntegration: 'rewriteframes',
RewriteFrames: 'rewriteframes',
contextLinesIntegration: 'contextlines',
ContextLines: 'contextlines',
extraErrorDataIntegration: 'extraerrordata',
ExtraErrorData: 'extraerrordata',
reportingObserverIntegration: 'reportingobserver',
ReportingObserver: 'reportingobserver',
sessionTimingIntegration: 'sessiontiming',
SessionTiming: 'sessiontiming',
};

const BUNDLE_PATHS: Record<string, Record<string, string>> = {
Expand All @@ -55,6 +48,8 @@ const BUNDLE_PATHS: Record<string, Record<string, string>> = {
bundle_tracing_min: 'build/bundles/bundle.tracing.min.js',
bundle_tracing_replay: 'build/bundles/bundle.tracing.replay.js',
bundle_tracing_replay_min: 'build/bundles/bundle.tracing.replay.min.js',
bundle_tracing_replay_feedback: 'build/bundles/bundle.tracing.replay.feedback.js',
bundle_tracing_replay_feedback_min: 'build/bundles/bundle.tracing.replay.feedback.min.js',
loader_base: 'build/bundles/bundle.min.js',
loader_eager: 'build/bundles/bundle.min.js',
loader_debug: 'build/bundles/bundle.debug.min.js',
Expand Down Expand Up @@ -227,7 +222,8 @@ class SentryScenarioGenerationPlugin {
const integrationBundleKey = bundleKey
.replace('loader_', 'bundle_')
.replace('_replay', '')
.replace('_tracing', '');
.replace('_tracing', '')
.replace('_feedback', '');

this.requiredIntegrations.forEach(integration => {
const fileName = `${integration}.bundle.js`;
Expand Down
Loading