Skip to content

Commit 96fdbf4

Browse files
authored
fix(replay): Ensure multi click has correct timestamps (#8591)
1 parent 768b025 commit 96fdbf4

File tree

6 files changed

+54
-7
lines changed

6 files changed

+54
-7
lines changed

packages/browser-integration-tests/suites/replay/slowClick/multiClick/test.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,43 @@ sentryTest('captures multi click when not detecting slow click', async ({ getLoc
6262
timestamp: expect.any(Number),
6363
},
6464
]);
65+
66+
// When this has been flushed, the timeout has exceeded - so add a new click now, which should trigger another multi click
67+
68+
const reqPromise2 = waitForReplayRequest(page, (event, res) => {
69+
const { breadcrumbs } = getCustomRecordingEvents(res);
70+
71+
return breadcrumbs.some(breadcrumb => breadcrumb.category === 'ui.multiClick');
72+
});
73+
74+
await page.click('#mutationButtonImmediately', { clickCount: 3 });
75+
76+
const { breadcrumbs: breadcrumbb2 } = getCustomRecordingEvents(await reqPromise2);
77+
78+
const slowClickBreadcrumbs2 = breadcrumbb2.filter(breadcrumb => breadcrumb.category === 'ui.multiClick');
79+
80+
expect(slowClickBreadcrumbs2).toEqual([
81+
{
82+
category: 'ui.multiClick',
83+
type: 'default',
84+
data: {
85+
clickCount: 3,
86+
metric: true,
87+
node: {
88+
attributes: {
89+
id: 'mutationButtonImmediately',
90+
},
91+
id: expect.any(Number),
92+
tagName: 'button',
93+
textContent: '******* ******** ***********',
94+
},
95+
nodeId: expect.any(Number),
96+
url: 'http://sentry-test.io/index.html',
97+
},
98+
message: 'body > button#mutationButtonImmediately',
99+
timestamp: expect.any(Number),
100+
},
101+
]);
65102
});
66103

67104
sentryTest('captures multiple multi clicks', async ({ getLocalTestUrl, page, forceFlushReplay, browserName }) => {

packages/replay/src/coreHandlers/handleClick.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import type { Breadcrumb } from '@sentry/types';
22

33
import { WINDOW } from '../constants';
44
import type { MultiClickFrame, ReplayClickDetector, ReplayContainer, SlowClickConfig, SlowClickFrame } from '../types';
5+
import { timestampToS } from '../util/timestamp';
56
import { addBreadcrumbEvent } from './util/addBreadcrumbEvent';
67
import { getClickTargetNode } from './util/domUtils';
78
import { onWindowOpen } from './util/onWindowOpen';
@@ -125,7 +126,7 @@ export class ClickDetector implements ReplayClickDetector {
125126
}
126127

127128
const newClick: Click = {
128-
timestamp: breadcrumb.timestamp,
129+
timestamp: timestampToS(breadcrumb.timestamp),
129130
clickBreadcrumb: breadcrumb,
130131
// Set this to 0 so we know it originates from the click breadcrumb
131132
clickCount: 0,
@@ -165,17 +166,18 @@ export class ClickDetector implements ReplayClickDetector {
165166
click.scrollAfter = click.timestamp <= this._lastScroll ? this._lastScroll - click.timestamp : undefined;
166167
}
167168

169+
// All of these are in seconds!
168170
if (click.timestamp + this._timeout <= now) {
169171
timedOutClicks.push(click);
170172
}
171173
});
172174

173175
// Remove "old" clicks
174176
for (const click of timedOutClicks) {
175-
this._generateBreadcrumbs(click);
176-
177177
const pos = this._clicks.indexOf(click);
178-
if (pos !== -1) {
178+
179+
if (pos > -1) {
180+
this._generateBreadcrumbs(click);
179181
this._clicks.splice(pos, 1);
180182
}
181183
}

packages/replay/src/eventBuffer/EventBufferArray.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { REPLAY_MAX_EVENT_BUFFER_SIZE } from '../constants';
22
import type { AddEventResult, EventBuffer, EventBufferType, RecordingEvent } from '../types';
3-
import { timestampToMs } from '../util/timestampToMs';
3+
import { timestampToMs } from '../util/timestamp';
44
import { EventBufferSizeExceededError } from './error';
55

66
/**

packages/replay/src/eventBuffer/EventBufferCompressionWorker.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import type { ReplayRecordingData } from '@sentry/types';
22

33
import { REPLAY_MAX_EVENT_BUFFER_SIZE } from '../constants';
44
import type { AddEventResult, EventBuffer, EventBufferType, RecordingEvent } from '../types';
5-
import { timestampToMs } from '../util/timestampToMs';
5+
import { timestampToMs } from '../util/timestamp';
66
import { EventBufferSizeExceededError } from './error';
77
import { WorkerHandler } from './WorkerHandler';
88

packages/replay/src/util/addEvent.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { logger } from '@sentry/utils';
44

55
import { EventBufferSizeExceededError } from '../eventBuffer/error';
66
import type { AddEventResult, RecordingEvent, ReplayContainer, ReplayFrameEvent, ReplayPluginOptions } from '../types';
7-
import { timestampToMs } from './timestampToMs';
7+
import { timestampToMs } from './timestamp';
88

99
function isCustomEvent(event: RecordingEvent): event is ReplayFrameEvent {
1010
return event.type === EventType.Custom;

packages/replay/src/util/timestampToMs.ts renamed to packages/replay/src/util/timestamp.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,11 @@ export function timestampToMs(timestamp: number): number {
55
const isMs = timestamp > 9999999999;
66
return isMs ? timestamp : timestamp * 1000;
77
}
8+
9+
/**
10+
* Converts a timestamp to s, if it was in ms, or keeps it as s.
11+
*/
12+
export function timestampToS(timestamp: number): number {
13+
const isMs = timestamp > 9999999999;
14+
return isMs ? timestamp / 1000 : timestamp;
15+
}

0 commit comments

Comments
 (0)