Skip to content

Commit a1f9d7b

Browse files
committed
feat: New setters for scope data (#1934)
* feat: New setter for scope * fix: Comments
1 parent 27d0cee commit a1f9d7b

File tree

4 files changed

+216
-47
lines changed

4 files changed

+216
-47
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ since we removed some methods from the public API and removed some classes from
3535
- **breaking** [core] ref: Move `extraErrorData` integration to `@sentry/integrations` package
3636
- [core] feat: Add `maxValueLength` option to adjust max string length for values, default is 250.
3737
- **breaking** [all] ref: Expose `module` in `package.json` as entry point for esm builds.
38+
- [hub] feat: Introduce `setExtra`, `setTags`, `clearBreadcrumbs` additionally some `set` on the Scope now accept no
39+
argument which makes it possible to unset the value.
3840

3941
## 4.6.4
4042

packages/hub/src/scope.ts

Lines changed: 79 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { Breadcrumb, Event, EventHint, EventProcessor, Scope as ScopeInterface, Severity, User } from '@sentry/types';
2-
import { isThenable } from '@sentry/utils/is';
2+
import { isPlainObject, isThenable } from '@sentry/utils/is';
33
import { getGlobalObject } from '@sentry/utils/misc';
44
import { normalize } from '@sentry/utils/object';
55
import { SyncPromise } from '@sentry/utils/syncpromise';
@@ -36,19 +36,37 @@ export class Scope implements ScopeInterface {
3636
/** Severity */
3737
protected level?: Severity;
3838

39-
/** Add internal on change listener. */
39+
/**
40+
* Add internal on change listener. Used for sub SDKs that need to store the scope.
41+
* @hidden
42+
*/
4043
public addScopeListener(callback: (scope: Scope) => void): void {
4144
this.scopeListeners.push(callback);
4245
}
4346

4447
/**
4548
* @inheritdoc
4649
*/
47-
public addEventProcessor(callback: EventProcessor): Scope {
50+
public addEventProcessor(callback: EventProcessor): this {
4851
this.eventProcessors.push(callback);
4952
return this;
5053
}
5154

55+
/**
56+
* This will be called on every set call.
57+
*/
58+
protected notifyScopeListeners(): void {
59+
if (!this.notifyingListeners) {
60+
this.notifyingListeners = true;
61+
setTimeout(() => {
62+
this.scopeListeners.forEach(callback => {
63+
callback(this);
64+
});
65+
this.notifyingListeners = false;
66+
});
67+
}
68+
}
69+
5270
/**
5371
* This will be called after {@link applyToEvent} is finished.
5472
*/
@@ -81,40 +99,75 @@ export class Scope implements ScopeInterface {
8199
/**
82100
* @inheritdoc
83101
*/
84-
public setUser(user: User): Scope {
85-
this.user = normalize(user);
102+
public setUser(user?: User): this {
103+
this.user = user ? normalize(user) : {};
104+
this.notifyScopeListeners();
86105
return this;
87106
}
88107

89108
/**
90109
* @inheritdoc
91110
*/
92-
public setTag(key: string, value: string): Scope {
111+
public setTags(tags?: { [key: string]: string }): this {
112+
this.tags =
113+
tags && isPlainObject(tags)
114+
? {
115+
...this.tags,
116+
...normalize(tags),
117+
}
118+
: {};
119+
this.notifyScopeListeners();
120+
return this;
121+
}
122+
123+
/**
124+
* @inheritdoc
125+
*/
126+
public setTag(key: string, value: string): this {
93127
this.tags = { ...this.tags, [key]: normalize(value) };
128+
this.notifyScopeListeners();
94129
return this;
95130
}
96131

97132
/**
98133
* @inheritdoc
99134
*/
100-
public setExtra(key: string, extra: any): Scope {
135+
public setExtras(extra?: { [key: string]: any }): this {
136+
this.extra =
137+
extra && isPlainObject(extra)
138+
? {
139+
...this.extra,
140+
...normalize(extra),
141+
}
142+
: {};
143+
this.notifyScopeListeners();
144+
return this;
145+
}
146+
147+
/**
148+
* @inheritdoc
149+
*/
150+
public setExtra(key: string, extra: any): this {
101151
this.extra = { ...this.extra, [key]: normalize(extra) };
152+
this.notifyScopeListeners();
102153
return this;
103154
}
104155

105156
/**
106157
* @inheritdoc
107158
*/
108-
public setFingerprint(fingerprint: string[]): Scope {
109-
this.fingerprint = normalize(fingerprint);
159+
public setFingerprint(fingerprint?: string[]): this {
160+
this.fingerprint = fingerprint ? normalize(fingerprint) : undefined;
161+
this.notifyScopeListeners();
110162
return this;
111163
}
112164

113165
/**
114166
* @inheritdoc
115167
*/
116-
public setLevel(level: Severity): Scope {
117-
this.level = normalize(level);
168+
public setLevel(level?: Severity): this {
169+
this.level = level ? normalize(level) : undefined;
170+
this.notifyScopeListeners();
118171
return this;
119172
}
120173

@@ -139,23 +192,36 @@ export class Scope implements ScopeInterface {
139192
/**
140193
* @inheritdoc
141194
*/
142-
public clear(): void {
195+
public clear(): this {
143196
this.breadcrumbs = [];
144197
this.tags = {};
145198
this.extra = {};
146199
this.user = {};
147200
this.level = undefined;
148201
this.fingerprint = undefined;
202+
this.notifyScopeListeners();
203+
return this;
149204
}
150205

151206
/**
152207
* @inheritdoc
153208
*/
154-
public addBreadcrumb(breadcrumb: Breadcrumb, maxBreadcrumbs?: number): void {
209+
public addBreadcrumb(breadcrumb: Breadcrumb, maxBreadcrumbs?: number): this {
155210
this.breadcrumbs =
156211
maxBreadcrumbs !== undefined && maxBreadcrumbs >= 0
157212
? [...this.breadcrumbs, normalize(breadcrumb)].slice(-maxBreadcrumbs)
158213
: [...this.breadcrumbs, normalize(breadcrumb)];
214+
this.notifyScopeListeners();
215+
return this;
216+
}
217+
218+
/**
219+
* @inheritdoc
220+
*/
221+
public clearBreadcrumbs(): this {
222+
this.breadcrumbs = [];
223+
this.notifyScopeListeners();
224+
return this;
159225
}
160226

161227
/**

packages/hub/test/scope.test.ts

Lines changed: 100 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -7,40 +7,105 @@ describe('Scope', () => {
77
jest.useRealTimers();
88
});
99

10-
test('fingerprint', () => {
11-
const scope = new Scope();
12-
scope.setFingerprint(['abcd']);
13-
expect((scope as any).fingerprint).toEqual(['abcd']);
10+
describe('fingerprint', () => {
11+
test('set', () => {
12+
const scope = new Scope();
13+
scope.setFingerprint(['abcd']);
14+
expect((scope as any).fingerprint).toEqual(['abcd']);
15+
});
16+
17+
test('unset', () => {
18+
const scope = new Scope();
19+
scope.setFingerprint(['abcd']);
20+
scope.setFingerprint();
21+
expect((scope as any).fingerprint).toEqual(undefined);
22+
});
1423
});
1524

16-
test('extra', () => {
17-
const scope = new Scope();
18-
scope.setExtra('a', 1);
19-
expect((scope as any).extra).toEqual({ a: 1 });
25+
describe('extra', () => {
26+
test('set key value', () => {
27+
const scope = new Scope();
28+
scope.setExtra('a', 1);
29+
expect((scope as any).extra).toEqual({ a: 1 });
30+
});
31+
32+
test('set object', () => {
33+
const scope = new Scope();
34+
scope.setExtras({ a: 1 });
35+
expect((scope as any).extra).toEqual({ a: 1 });
36+
});
37+
38+
test('set undefined', () => {
39+
const scope = new Scope();
40+
scope.setExtra('a', 1);
41+
scope.setExtras();
42+
expect((scope as any).extra).toEqual({});
43+
});
2044
});
2145

22-
test('tags', () => {
23-
const scope = new Scope();
24-
scope.setTag('a', 'b');
25-
expect((scope as any).tags).toEqual({ a: 'b' });
46+
describe('tags', () => {
47+
test('set key value', () => {
48+
const scope = new Scope();
49+
scope.setTag('a', 'b');
50+
expect((scope as any).tags).toEqual({ a: 'b' });
51+
});
52+
53+
test('set object', () => {
54+
const scope = new Scope();
55+
scope.setTags({ a: 'b' });
56+
expect((scope as any).tags).toEqual({ a: 'b' });
57+
});
58+
59+
test('set undefined', () => {
60+
const scope = new Scope();
61+
scope.setTags({ a: 'b' });
62+
scope.setTags();
63+
expect((scope as any).tags).toEqual({});
64+
});
2665
});
2766

28-
test('user', () => {
29-
const scope = new Scope();
30-
scope.setUser({ id: '1' });
31-
expect((scope as any).user).toEqual({ id: '1' });
67+
describe('user', () => {
68+
test('set', () => {
69+
const scope = new Scope();
70+
scope.setUser({ id: '1' });
71+
expect((scope as any).user).toEqual({ id: '1' });
72+
});
73+
74+
test('unset', () => {
75+
const scope = new Scope();
76+
scope.setUser({ id: '1' });
77+
scope.setUser();
78+
expect((scope as any).user).toEqual({});
79+
});
3280
});
3381

34-
test('breadcrumbs', () => {
35-
const scope = new Scope();
36-
scope.addBreadcrumb({ message: 'test' }, 100);
37-
expect((scope as any).breadcrumbs).toEqual([{ message: 'test' }]);
82+
describe('level', () => {
83+
test('add', () => {
84+
const scope = new Scope();
85+
scope.addBreadcrumb({ message: 'test' }, 100);
86+
expect((scope as any).breadcrumbs).toEqual([{ message: 'test' }]);
87+
});
88+
89+
test('clear', () => {
90+
const scope = new Scope();
91+
scope.addBreadcrumb({ message: 'test' }, 100);
92+
scope.clearBreadcrumbs();
93+
expect((scope as any).breadcrumbs).toEqual([]);
94+
});
3895
});
3996

40-
test('level', () => {
41-
const scope = new Scope();
42-
scope.setLevel(Severity.Critical);
43-
expect((scope as any).level).toEqual(Severity.Critical);
97+
describe('level', () => {
98+
test('set', () => {
99+
const scope = new Scope();
100+
scope.setLevel(Severity.Critical);
101+
expect((scope as any).level).toEqual(Severity.Critical);
102+
});
103+
test('unset', () => {
104+
const scope = new Scope();
105+
scope.setLevel(Severity.Critical);
106+
scope.setLevel();
107+
expect((scope as any).level).toEqual(undefined);
108+
});
44109
});
45110

46111
test('chaining', () => {
@@ -281,4 +346,15 @@ describe('Scope', () => {
281346
expect(processedEvent).toEqual(event);
282347
});
283348
});
349+
350+
test('listeners', () => {
351+
jest.useFakeTimers();
352+
const scope = new Scope();
353+
const listener = jest.fn();
354+
scope.addScopeListener(listener);
355+
scope.setExtra('a', 2);
356+
jest.runAllTimers();
357+
expect(listener).toHaveBeenCalled();
358+
expect(listener.mock.calls[0][0].extra).toEqual({ a: 2 });
359+
});
284360
});

0 commit comments

Comments
 (0)