Skip to content

Commit

Permalink
feat: New setters for scope data (#1934)
Browse files Browse the repository at this point in the history
* feat: New setter for scope
* fix: Comments
  • Loading branch information
HazAT authored and kamilogorek committed Feb 28, 2019
1 parent 583aa87 commit 3c10d9c
Show file tree
Hide file tree
Showing 4 changed files with 216 additions and 47 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Expand Up @@ -35,6 +35,8 @@ since we removed some methods from the public API and removed some classes from
- **breaking** [core] ref: Move `extraErrorData` integration to `@sentry/integrations` package
- [core] feat: Add `maxValueLength` option to adjust max string length for values, default is 250.
- **breaking** [all] ref: Expose `module` in `package.json` as entry point for esm builds.
- [hub] feat: Introduce `setExtra`, `setTags`, `clearBreadcrumbs` additionally some `set` on the Scope now accept no
argument which makes it possible to unset the value.

## 4.6.4

Expand Down
92 changes: 79 additions & 13 deletions packages/hub/src/scope.ts
@@ -1,5 +1,5 @@
import { Breadcrumb, Event, EventHint, EventProcessor, Scope as ScopeInterface, Severity, User } from '@sentry/types';
import { isThenable } from '@sentry/utils/is';
import { isPlainObject, isThenable } from '@sentry/utils/is';
import { getGlobalObject } from '@sentry/utils/misc';
import { normalize } from '@sentry/utils/object';
import { SyncPromise } from '@sentry/utils/syncpromise';
Expand Down Expand Up @@ -36,19 +36,37 @@ export class Scope implements ScopeInterface {
/** Severity */
protected level?: Severity;

/** Add internal on change listener. */
/**
* Add internal on change listener. Used for sub SDKs that need to store the scope.
* @hidden
*/
public addScopeListener(callback: (scope: Scope) => void): void {
this.scopeListeners.push(callback);
}

/**
* @inheritdoc
*/
public addEventProcessor(callback: EventProcessor): Scope {
public addEventProcessor(callback: EventProcessor): this {
this.eventProcessors.push(callback);
return this;
}

/**
* This will be called on every set call.
*/
protected notifyScopeListeners(): void {
if (!this.notifyingListeners) {
this.notifyingListeners = true;
setTimeout(() => {
this.scopeListeners.forEach(callback => {
callback(this);
});
this.notifyingListeners = false;
});
}
}

/**
* This will be called after {@link applyToEvent} is finished.
*/
Expand Down Expand Up @@ -81,40 +99,75 @@ export class Scope implements ScopeInterface {
/**
* @inheritdoc
*/
public setUser(user: User): Scope {
this.user = normalize(user);
public setUser(user?: User): this {
this.user = user ? normalize(user) : {};
this.notifyScopeListeners();
return this;
}

/**
* @inheritdoc
*/
public setTag(key: string, value: string): Scope {
public setTags(tags?: { [key: string]: string }): this {
this.tags =
tags && isPlainObject(tags)
? {
...this.tags,
...normalize(tags),
}
: {};
this.notifyScopeListeners();
return this;
}

/**
* @inheritdoc
*/
public setTag(key: string, value: string): this {
this.tags = { ...this.tags, [key]: normalize(value) };
this.notifyScopeListeners();
return this;
}

/**
* @inheritdoc
*/
public setExtra(key: string, extra: any): Scope {
public setExtras(extra?: { [key: string]: any }): this {
this.extra =
extra && isPlainObject(extra)
? {
...this.extra,
...normalize(extra),
}
: {};
this.notifyScopeListeners();
return this;
}

/**
* @inheritdoc
*/
public setExtra(key: string, extra: any): this {
this.extra = { ...this.extra, [key]: normalize(extra) };
this.notifyScopeListeners();
return this;
}

/**
* @inheritdoc
*/
public setFingerprint(fingerprint: string[]): Scope {
this.fingerprint = normalize(fingerprint);
public setFingerprint(fingerprint?: string[]): this {
this.fingerprint = fingerprint ? normalize(fingerprint) : undefined;
this.notifyScopeListeners();
return this;
}

/**
* @inheritdoc
*/
public setLevel(level: Severity): Scope {
this.level = normalize(level);
public setLevel(level?: Severity): this {
this.level = level ? normalize(level) : undefined;
this.notifyScopeListeners();
return this;
}

Expand All @@ -139,23 +192,36 @@ export class Scope implements ScopeInterface {
/**
* @inheritdoc
*/
public clear(): void {
public clear(): this {
this.breadcrumbs = [];
this.tags = {};
this.extra = {};
this.user = {};
this.level = undefined;
this.fingerprint = undefined;
this.notifyScopeListeners();
return this;
}

/**
* @inheritdoc
*/
public addBreadcrumb(breadcrumb: Breadcrumb, maxBreadcrumbs?: number): void {
public addBreadcrumb(breadcrumb: Breadcrumb, maxBreadcrumbs?: number): this {
this.breadcrumbs =
maxBreadcrumbs !== undefined && maxBreadcrumbs >= 0
? [...this.breadcrumbs, normalize(breadcrumb)].slice(-maxBreadcrumbs)
: [...this.breadcrumbs, normalize(breadcrumb)];
this.notifyScopeListeners();
return this;
}

/**
* @inheritdoc
*/
public clearBreadcrumbs(): this {
this.breadcrumbs = [];
this.notifyScopeListeners();
return this;
}

/**
Expand Down
124 changes: 100 additions & 24 deletions packages/hub/test/scope.test.ts
Expand Up @@ -7,40 +7,105 @@ describe('Scope', () => {
jest.useRealTimers();
});

test('fingerprint', () => {
const scope = new Scope();
scope.setFingerprint(['abcd']);
expect((scope as any).fingerprint).toEqual(['abcd']);
describe('fingerprint', () => {
test('set', () => {
const scope = new Scope();
scope.setFingerprint(['abcd']);
expect((scope as any).fingerprint).toEqual(['abcd']);
});

test('unset', () => {
const scope = new Scope();
scope.setFingerprint(['abcd']);
scope.setFingerprint();
expect((scope as any).fingerprint).toEqual(undefined);
});
});

test('extra', () => {
const scope = new Scope();
scope.setExtra('a', 1);
expect((scope as any).extra).toEqual({ a: 1 });
describe('extra', () => {
test('set key value', () => {
const scope = new Scope();
scope.setExtra('a', 1);
expect((scope as any).extra).toEqual({ a: 1 });
});

test('set object', () => {
const scope = new Scope();
scope.setExtras({ a: 1 });
expect((scope as any).extra).toEqual({ a: 1 });
});

test('set undefined', () => {
const scope = new Scope();
scope.setExtra('a', 1);
scope.setExtras();
expect((scope as any).extra).toEqual({});
});
});

test('tags', () => {
const scope = new Scope();
scope.setTag('a', 'b');
expect((scope as any).tags).toEqual({ a: 'b' });
describe('tags', () => {
test('set key value', () => {
const scope = new Scope();
scope.setTag('a', 'b');
expect((scope as any).tags).toEqual({ a: 'b' });
});

test('set object', () => {
const scope = new Scope();
scope.setTags({ a: 'b' });
expect((scope as any).tags).toEqual({ a: 'b' });
});

test('set undefined', () => {
const scope = new Scope();
scope.setTags({ a: 'b' });
scope.setTags();
expect((scope as any).tags).toEqual({});
});
});

test('user', () => {
const scope = new Scope();
scope.setUser({ id: '1' });
expect((scope as any).user).toEqual({ id: '1' });
describe('user', () => {
test('set', () => {
const scope = new Scope();
scope.setUser({ id: '1' });
expect((scope as any).user).toEqual({ id: '1' });
});

test('unset', () => {
const scope = new Scope();
scope.setUser({ id: '1' });
scope.setUser();
expect((scope as any).user).toEqual({});
});
});

test('breadcrumbs', () => {
const scope = new Scope();
scope.addBreadcrumb({ message: 'test' }, 100);
expect((scope as any).breadcrumbs).toEqual([{ message: 'test' }]);
describe('level', () => {
test('add', () => {
const scope = new Scope();
scope.addBreadcrumb({ message: 'test' }, 100);
expect((scope as any).breadcrumbs).toEqual([{ message: 'test' }]);
});

test('clear', () => {
const scope = new Scope();
scope.addBreadcrumb({ message: 'test' }, 100);
scope.clearBreadcrumbs();
expect((scope as any).breadcrumbs).toEqual([]);
});
});

test('level', () => {
const scope = new Scope();
scope.setLevel(Severity.Critical);
expect((scope as any).level).toEqual(Severity.Critical);
describe('level', () => {
test('set', () => {
const scope = new Scope();
scope.setLevel(Severity.Critical);
expect((scope as any).level).toEqual(Severity.Critical);
});
test('unset', () => {
const scope = new Scope();
scope.setLevel(Severity.Critical);
scope.setLevel();
expect((scope as any).level).toEqual(undefined);
});
});

test('chaining', () => {
Expand Down Expand Up @@ -281,4 +346,15 @@ describe('Scope', () => {
expect(processedEvent).toEqual(event);
});
});

test('listeners', () => {
jest.useFakeTimers();
const scope = new Scope();
const listener = jest.fn();
scope.addScopeListener(listener);
scope.setExtra('a', 2);
jest.runAllTimers();
expect(listener).toHaveBeenCalled();
expect(listener.mock.calls[0][0].extra).toEqual({ a: 2 });
});
});

0 comments on commit 3c10d9c

Please sign in to comment.