Skip to content
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

Add debounce to capturing screenshots #2368

Merged
merged 33 commits into from
Nov 26, 2024
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
77cce7c
Add debounce to `ScreenshotWidget`
denrase Oct 22, 2024
f540ecb
add changelog entry
denrase Oct 22, 2024
e5f628a
provide clock in tests
denrase Oct 22, 2024
235de7b
remove unused import
denrase Oct 22, 2024
09c7f91
update clock at correct line
denrase Oct 22, 2024
7345987
Merge branch 'main' into feat/screenshot-debounce
denrase Oct 22, 2024
ba0b8e7
update cl
denrase Oct 22, 2024
3d489d9
use now from clock
denrase Oct 22, 2024
c088da8
Merge branch 'main' into feat/screenshot-debounce
denrase Nov 12, 2024
3a9d843
refactor debouncer to be similar to sentry-java impl
denrase Nov 12, 2024
e710098
use debouncer in screenshot_event_processor
denrase Nov 12, 2024
161f182
Change beforeScreenshot to beforeCapture callback
denrase Nov 12, 2024
d6b74f2
update changelog entry
denrase Nov 12, 2024
570b1aa
format
denrase Nov 12, 2024
fbea7d7
fix analyze issue
denrase Nov 12, 2024
ad5a9ce
feedback
denrase Nov 13, 2024
57087b1
recert timer impl for widgets observer
denrase Nov 13, 2024
1f5f85c
keep before screenshot callback
denrase Nov 13, 2024
f2950b9
Merge branch 'main' into feat/screenshot-debounce
denrase Nov 13, 2024
cfafff0
update cl
denrase Nov 13, 2024
eea29fc
update error msg
denrase Nov 13, 2024
006839e
refactor
denrase Nov 13, 2024
2e43ad3
add warning
denrase Nov 13, 2024
e96bb8d
add ignores for analyzer
denrase Nov 13, 2024
5a9bcac
Merge branch 'main' into feat/screenshot-debounce
denrase Nov 18, 2024
f4c724d
rename callback
denrase Nov 18, 2024
9fce5c0
Merge branch 'main' into feat/screenshot-debounce
denrase Nov 18, 2024
b157a35
rename to beforeScreenshotCapture
denrase Nov 18, 2024
28b0857
Merge branch 'main' into feat/screenshot-debounce
denrase Nov 25, 2024
7ce37e4
cleanup
denrase Nov 25, 2024
cd64cc1
fix cl
denrase Nov 25, 2024
d245569
move to features in cl
denrase Nov 25, 2024
4d5f535
add sample to changelog
denrase Nov 25, 2024
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
### Enhancements

- Cache parsed DSN ([#2365](https://github.com/getsentry/sentry-dart/pull/2365))
- Add debounce to `ScreenshotWidget` ([#2368](https://github.com/getsentry/sentry-dart/pull/2368))

## 8.10.0-beta.2

Expand Down
22 changes: 22 additions & 0 deletions flutter/lib/src/event_processor/screenshot_event_processor.dart
denrase marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,14 @@ class ScreenshotEventProcessor implements EventProcessor {
bool get _hasSentryScreenshotWidget =>
sentryScreenshotWidgetGlobalKey.currentContext != null;

final _debounceDuration = Duration(milliseconds: 100);

/// Only apply this event processor every [debounceDuration] duration.
DateTime? _lastApplyCall;

/// The debounce duration for applying this event processor.
Duration get debounceDuration => _debounceDuration;

@override
Future<SentryEvent?> apply(SentryEvent event, Hint hint) async {
if (event is SentryTransaction) {
Expand All @@ -30,6 +38,20 @@ class ScreenshotEventProcessor implements EventProcessor {
_hasSentryScreenshotWidget) {
return event;
}

// ignore: invalid_use_of_internal_member
final now = _options.clock();
final difference = _lastApplyCall?.difference(DateTime.now()).abs();
_lastApplyCall = now;

if (difference != null && difference < debounceDuration) {
_options.logger(
SentryLevel.warning,
'Skipping screenshot due to too many calls within a short time frame.',
);
return event; // Debounce
}

final beforeScreenshot = _options.beforeScreenshot;
if (beforeScreenshot != null) {
try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,71 @@ void main() {
expect(beforeScreenshotHint, hint);
});
});

group("debounce", () {
testWidgets("limits added screenshots within debounce timeframe",
(tester) async {
// Run with real async https://stackoverflow.com/a/54021863
await tester.runAsync(() async {
final sut = fixture.getSut(FlutterRenderer.canvasKit, false);

await tester.pumpWidget(SentryScreenshotWidget(
child: Text('Catching Pokémon is a snap!',
textDirection: TextDirection.ltr)));

final throwable = Exception();

final firstEvent = SentryEvent(throwable: throwable);
final firstHint = Hint();

final secondEvent = SentryEvent(throwable: throwable);
final secondHint = Hint();

// ignore: invalid_use_of_internal_member
fixture.options.clock = () => DateTime.fromMillisecondsSinceEpoch(0);
await sut.apply(firstEvent, firstHint);

// ignore: invalid_use_of_internal_member
fixture.options.clock = () => DateTime.fromMillisecondsSinceEpoch(
sut.debounceDuration.inMilliseconds - 1);
await sut.apply(secondEvent, secondHint);

expect(firstHint.screenshot, isNotNull);
expect(secondHint.screenshot, isNull);
});
});

testWidgets("adds screenshots after debounce timeframe", (tester) async {
// Run with real async https://stackoverflow.com/a/54021863
await tester.runAsync(() async {
final sut = fixture.getSut(FlutterRenderer.canvasKit, false);

await tester.pumpWidget(SentryScreenshotWidget(
child: Text('Catching Pokémon is a snap!',
textDirection: TextDirection.ltr)));

final throwable = Exception();

final firstEvent = SentryEvent(throwable: throwable);
final firstHint = Hint();

final secondEvent = SentryEvent(throwable: throwable);
final secondHint = Hint();

// ignore: invalid_use_of_internal_member
fixture.options.clock = () => DateTime.fromMillisecondsSinceEpoch(0);
await sut.apply(firstEvent, firstHint);

// ignore: invalid_use_of_internal_member
fixture.options.clock = () => DateTime.fromMillisecondsSinceEpoch(
sut.debounceDuration.inMilliseconds);
await sut.apply(secondEvent, secondHint);

expect(firstHint.screenshot, isNotNull);
expect(secondHint.screenshot, isNotNull);
});
});
});
}

class Fixture {
Expand Down
Loading