Skip to content

Commit 87a90a3

Browse files
authored
Merge pull request #14425 from getsentry/prepare-release/8.40.0
meta(changelog): Update changelog for 8.40.0
2 parents ec701fd + 5841854 commit 87a90a3

File tree

11 files changed

+157
-46
lines changed

11 files changed

+157
-46
lines changed

.github/workflows/build.yml

+6-2
Original file line numberDiff line numberDiff line change
@@ -795,7 +795,8 @@ jobs:
795795
# - The build job was successful, not skipped
796796
# - AND if the profiling node bindings were either successful or skipped
797797
if: |
798-
always() && needs.job_build.result == 'success' &&
798+
always() &&
799+
needs.job_build.result == 'success' &&
799800
(needs.job_compile_bindings_profiling_node.result == 'success' || needs.job_compile_bindings_profiling_node.result == 'skipped')
800801
needs: [job_get_metadata, job_build, job_compile_bindings_profiling_node]
801802
runs-on: ubuntu-20.04-large-js
@@ -981,13 +982,16 @@ jobs:
981982
directory: dev-packages/e2e-tests
982983
token: ${{ secrets.CODECOV_TOKEN }}
983984

985+
# - We skip optional tests on release branches
984986
job_optional_e2e_tests:
985987
name: E2E ${{ matrix.label || matrix.test-application }} Test
986988
# We only run E2E tests for non-fork PRs because the E2E tests require secrets to work and they can't be accessed from forks
987989
# We need to add the `always()` check here because the previous step has this as well :(
988990
# See: https://github.com/actions/runner/issues/2205
989991
if:
990-
always() && needs.job_e2e_prepare.result == 'success' &&
992+
always() &&
993+
needs.job_get_metadata.outputs.is_release != 'true' &&
994+
needs.job_e2e_prepare.result == 'success' &&
991995
needs.job_e2e_prepare.outputs.matrix-optional != '{"include":[]}' &&
992996
(github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository) &&
993997
github.actor != 'dependabot[bot]'

CHANGELOG.md

+2
Original file line numberDiff line numberDiff line change
@@ -81,10 +81,12 @@
8181
### Other Changes
8282

8383
- feat(browser): Send additional LCP timing info ([#14372](https://github.com/getsentry/sentry-javascript/pull/14372))
84+
- feat(replay): Clear event buffer when full and in buffer mode ([#14078](https://github.com/getsentry/sentry-javascript/pull/14078))
8485
- feat(core): Ensure `normalizedRequest` on `sdkProcessingMetadata` is merged ([#14315](https://github.com/getsentry/sentry-javascript/pull/14315))
8586
- feat(core): Hoist everything from `@sentry/utils` into `@sentry/core` ([#14382](https://github.com/getsentry/sentry-javascript/pull/14382))
8687
- fix(core): Do not throw when trying to fill readonly properties ([#14402](https://github.com/getsentry/sentry-javascript/pull/14402))
8788
- fix(feedback): Fix `__self` and `__source` attributes on feedback nodes ([#14356](https://github.com/getsentry/sentry-javascript/pull/14356))
89+
- fix(feedback): Fix non-wrapping form title ([#14355](https://github.com/getsentry/sentry-javascript/pull/14355))
8890
- fix(nextjs): Update check for not found navigation error ([#14378](https://github.com/getsentry/sentry-javascript/pull/14378))
8991

9092
## 8.39.0

packages/feedback/src/modal/components/Dialog.css.ts

+13-10
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ const DIALOG = `
6060
gap: 16px;
6161
padding: var(--dialog-padding, 24px);
6262
max-width: 100%;
63-
width: 100%;
63+
width: var(--form-width, 272px);
6464
max-height: 100%;
6565
overflow: auto;
6666
@@ -71,17 +71,26 @@ const DIALOG = `
7171
transform: translate(0, 0) scale(1);
7272
transition: transform 0.2s ease-in-out;
7373
}
74+
75+
@media (max-width: 600px) {
76+
.dialog__content {
77+
width: var(--form-width, 100%);
78+
}
79+
}
80+
7481
`;
7582

7683
const DIALOG_HEADER = `
7784
.dialog__header {
7885
display: flex;
79-
align-items: center;
86+
gap: 4px;
8087
justify-content: space-between;
8188
font-weight: var(--dialog-header-weight, 600);
8289
margin: 0;
8390
}
84-
91+
.dialog__title {
92+
align-self: center;
93+
}
8594
.brand-link {
8695
display: inline-flex;
8796
}
@@ -101,18 +110,12 @@ const FORM = `
101110
102111
.form__right {
103112
flex: 0 0 auto;
104-
width: var(--form-width, 272px);
105113
display: flex;
106114
overflow: auto;
107115
flex-direction: column;
108116
justify-content: space-between;
109117
gap: 20px;
110-
}
111-
112-
@media (max-width: 600px) {
113-
.form__right {
114-
width: var(--form-width, 100%);
115-
}
118+
width: 100%;
116119
}
117120
118121
.form__top {

packages/feedback/src/modal/components/DialogHeader.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ export function DialogHeader({ options }: Props): VNode {
1414

1515
return (
1616
<h2 class="dialog__header">
17-
{options.formTitle}
17+
<span class="dialog__title">{options.formTitle}</span>
1818
{options.showBranding ? (
1919
<a
2020
class="brand-link"

packages/replay-internal/src/eventBuffer/EventBufferArray.ts

+4
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,16 @@ export class EventBufferArray implements EventBuffer {
1414
/** @inheritdoc */
1515
public hasCheckout: boolean;
1616

17+
/** @inheritdoc */
18+
public waitForCheckout: boolean;
19+
1720
private _totalSize: number;
1821

1922
public constructor() {
2023
this.events = [];
2124
this._totalSize = 0;
2225
this.hasCheckout = false;
26+
this.waitForCheckout = false;
2327
}
2428

2529
/** @inheritdoc */

packages/replay-internal/src/eventBuffer/EventBufferCompressionWorker.ts

+4
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@ export class EventBufferCompressionWorker implements EventBuffer {
1616
/** @inheritdoc */
1717
public hasCheckout: boolean;
1818

19+
/** @inheritdoc */
20+
public waitForCheckout: boolean;
21+
1922
private _worker: WorkerHandler;
2023
private _earliestTimestamp: number | null;
2124
private _totalSize;
@@ -25,6 +28,7 @@ export class EventBufferCompressionWorker implements EventBuffer {
2528
this._earliestTimestamp = null;
2629
this._totalSize = 0;
2730
this.hasCheckout = false;
31+
this.waitForCheckout = false;
2832
}
2933

3034
/** @inheritdoc */

packages/replay-internal/src/eventBuffer/EventBufferProxy.ts

+13-1
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,11 @@ export class EventBufferProxy implements EventBuffer {
2525
this._ensureWorkerIsLoadedPromise = this._ensureWorkerIsLoaded();
2626
}
2727

28+
/** @inheritdoc */
29+
public get waitForCheckout(): boolean {
30+
return this._used.waitForCheckout;
31+
}
32+
2833
/** @inheritdoc */
2934
public get type(): EventBufferType {
3035
return this._used.type;
@@ -44,6 +49,12 @@ export class EventBufferProxy implements EventBuffer {
4449
this._used.hasCheckout = value;
4550
}
4651

52+
/** @inheritdoc */
53+
// eslint-disable-next-line @typescript-eslint/adjacent-overload-signatures
54+
public set waitForCheckout(value: boolean) {
55+
this._used.waitForCheckout = value;
56+
}
57+
4758
/** @inheritDoc */
4859
public destroy(): void {
4960
this._fallback.destroy();
@@ -99,14 +110,15 @@ export class EventBufferProxy implements EventBuffer {
99110

100111
/** Switch the used buffer to the compression worker. */
101112
private async _switchToCompressionWorker(): Promise<void> {
102-
const { events, hasCheckout } = this._fallback;
113+
const { events, hasCheckout, waitForCheckout } = this._fallback;
103114

104115
const addEventPromises: Promise<void>[] = [];
105116
for (const event of events) {
106117
addEventPromises.push(this._compression.addEvent(event));
107118
}
108119

109120
this._compression.hasCheckout = hasCheckout;
121+
this._compression.waitForCheckout = waitForCheckout;
110122

111123
// We switch over to the new buffer immediately - any further events will be added
112124
// after the previously buffered ones

packages/replay-internal/src/types/replay.ts

+6
Original file line numberDiff line numberDiff line change
@@ -400,6 +400,12 @@ export interface EventBuffer {
400400
*/
401401
hasCheckout: boolean;
402402

403+
/**
404+
* If the event buffer needs to wait for a checkout event before it
405+
* starts buffering events.
406+
*/
407+
waitForCheckout: boolean;
408+
403409
/**
404410
* Destroy the event buffer.
405411
*/

packages/replay-internal/src/util/addEvent.ts

+21-6
Original file line numberDiff line numberDiff line change
@@ -54,17 +54,22 @@ async function _addEvent(
5454
event: RecordingEvent,
5555
isCheckout?: boolean,
5656
): Promise<AddEventResult | null> {
57-
if (!replay.eventBuffer) {
57+
const { eventBuffer } = replay;
58+
59+
if (!eventBuffer || (eventBuffer.waitForCheckout && !isCheckout)) {
5860
return null;
5961
}
6062

63+
const isBufferMode = replay.recordingMode === 'buffer';
64+
6165
try {
62-
if (isCheckout && replay.recordingMode === 'buffer') {
63-
replay.eventBuffer.clear();
66+
if (isCheckout && isBufferMode) {
67+
eventBuffer.clear();
6468
}
6569

6670
if (isCheckout) {
67-
replay.eventBuffer.hasCheckout = true;
71+
eventBuffer.hasCheckout = true;
72+
eventBuffer.waitForCheckout = false;
6873
}
6974

7075
const replayOptions = replay.getOptions();
@@ -75,9 +80,19 @@ async function _addEvent(
7580
return;
7681
}
7782

78-
return await replay.eventBuffer.addEvent(eventAfterPossibleCallback);
83+
return await eventBuffer.addEvent(eventAfterPossibleCallback);
7984
} catch (error) {
80-
const reason = error && error instanceof EventBufferSizeExceededError ? 'addEventSizeExceeded' : 'addEvent';
85+
const isExceeded = error && error instanceof EventBufferSizeExceededError;
86+
const reason = isExceeded ? 'addEventSizeExceeded' : 'addEvent';
87+
88+
if (isExceeded && isBufferMode) {
89+
// Clear buffer and wait for next checkout
90+
eventBuffer.clear();
91+
eventBuffer.waitForCheckout = true;
92+
93+
return null;
94+
}
95+
8196
replay.handleException(error);
8297

8398
await replay.stop({ reason });
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
/**
2+
* @vitest-environment jsdom
3+
*/
4+
5+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
6+
7+
import { WINDOW } from '../../src/constants';
8+
import type { Replay } from '../../src/integration';
9+
import type { ReplayContainer } from '../../src/replay';
10+
import { addEvent } from '../../src/util/addEvent';
11+
12+
// mock functions need to be imported first
13+
import { BASE_TIMESTAMP, mockSdk } from '../index';
14+
import { getTestEventCheckout, getTestEventIncremental } from '../utils/getTestEvent';
15+
import { useFakeTimers } from '../utils/use-fake-timers';
16+
17+
useFakeTimers();
18+
19+
describe('Integration | eventBuffer | Event Buffer Max Size', () => {
20+
let replay: ReplayContainer;
21+
let integration: Replay;
22+
const prevLocation = WINDOW.location;
23+
24+
beforeEach(async () => {
25+
vi.setSystemTime(new Date(BASE_TIMESTAMP));
26+
27+
({ replay, integration } = await mockSdk());
28+
29+
await vi.runAllTimersAsync();
30+
vi.clearAllMocks();
31+
});
32+
33+
afterEach(async () => {
34+
vi.setSystemTime(new Date(BASE_TIMESTAMP));
35+
integration && (await integration.stop());
36+
Object.defineProperty(WINDOW, 'location', {
37+
value: prevLocation,
38+
writable: true,
39+
});
40+
vi.clearAllMocks();
41+
});
42+
43+
it('does not add replay breadcrumb when stopped due to event buffer limit', async () => {
44+
const TEST_EVENT = getTestEventIncremental({ timestamp: BASE_TIMESTAMP });
45+
46+
vi.mock('../../src/constants', async requireActual => ({
47+
...(await requireActual<any>()),
48+
REPLAY_MAX_EVENT_BUFFER_SIZE: 500,
49+
}));
50+
51+
await integration.stop();
52+
integration.startBuffering();
53+
54+
await addEvent(replay, TEST_EVENT);
55+
56+
expect(replay.eventBuffer?.hasEvents).toBe(true);
57+
expect(replay.eventBuffer?.['hasCheckout']).toBe(true);
58+
59+
// This should should go over max buffer size
60+
await addEvent(replay, TEST_EVENT);
61+
// buffer should be cleared and wait for next checkout
62+
expect(replay.eventBuffer?.hasEvents).toBe(false);
63+
expect(replay.eventBuffer?.['hasCheckout']).toBe(false);
64+
65+
await addEvent(replay, TEST_EVENT);
66+
expect(replay.eventBuffer?.hasEvents).toBe(false);
67+
expect(replay.eventBuffer?.['hasCheckout']).toBe(false);
68+
69+
await addEvent(replay, getTestEventCheckout({ timestamp: Date.now() }), true);
70+
expect(replay.eventBuffer?.hasEvents).toBe(true);
71+
expect(replay.eventBuffer?.['hasCheckout']).toBe(true);
72+
73+
vi.resetAllMocks();
74+
});
75+
});

0 commit comments

Comments
 (0)