Skip to content

Commit 49f4d15

Browse files
odinugecaptbaritone
authored andcommitted
User context (#49)
* Add support for user data callback * Add documentation for user context callback * Add data callback tests
1 parent 1fd666b commit 49f4d15

File tree

3 files changed

+86
-5
lines changed

3 files changed

+86
-5
lines changed

README.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ This library makes, what I think are, a few improvements over
5353
2. Adds your state and last action as context to _all_ errors, not just reducer
5454
exceptions.
5555
3. Allows filtering action breadcrumbs before sending to Sentry
56+
4. Allows you to define a user context mapping from the state
5657

5758
## API: `createRavenMiddleware(Raven, [options])`
5859

@@ -141,6 +142,24 @@ Sentry as part of the extra data, if it was the last action before an error.
141142

142143
This option was introduced in version 1.1.1.
143144

145+
#### `getUserContext` _(Optional Function)_
146+
147+
Signature: `state => userContext`
148+
149+
The [user context] is an important part of the error report. This function
150+
allows you to define a mapping from the `state` to the user context. When
151+
`getUserContext` is spesified, the result of `getUserContext`
152+
will set the the user context before sending the error report.
153+
154+
Be careful not to mutate your `state` within this function.
155+
156+
If you have specified a [`dataCallback`] when you configured Raven, note that
157+
`getUserContext` will be applied _before_ your specified `dataCallback`.
158+
When a `getUserContext` function is given, it will override the user context
159+
set elsewhere.
160+
161+
This option was introduced in version 1.X
162+
144163
## Changelog
145164

146165
### 1.1.1
@@ -168,6 +187,7 @@ This option was introduced in version 1.1.1.
168187
[Raven]: https://docs.sentry.io/clients/javascript/
169188
[Raven Breadcrumbs]: https://docs.sentry.io/clients/javascript/usage/#recording-breadcrumbs
170189
[Breadcrumb documentation]: https://docs.sentry.io/learn/breadcrumbs/
190+
[user context]: https://docs.sentry.io/learn/context/#capturing-the-user
171191
[`dataCallback`]: https://docs.sentry.io/clients/javascript/config/
172192
[#11]: https://github.com/captbaritone/raven-for-redux/pull/11
173193
[#8]: https://github.com/captbaritone/raven-for-redux/pull/8

index.js

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,20 @@ function createRavenMiddleware(Raven, options = {}) {
88
actionTransformer = identity,
99
stateTransformer = identity,
1010
breadcrumbCategory = "redux-action",
11-
filterBreadcrumbActions = filter
11+
filterBreadcrumbActions = filter,
12+
getUserContext
1213
} = options;
1314

1415
return store => {
1516
let lastAction;
1617

1718
Raven.setDataCallback((data, original) => {
19+
const state = store.getState();
1820
data.extra.lastAction = actionTransformer(lastAction);
19-
data.extra.state = stateTransformer(store.getState());
21+
data.extra.state = stateTransformer(state);
22+
if (getUserContext) {
23+
data.user = getUserContext(state);
24+
}
2025
return original ? original(data) : data;
2126
});
2227

index.test.js

Lines changed: 59 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -107,13 +107,25 @@ describe("raven-for-redux", () => {
107107
expect(extra.lastAction).toEqual({ type: "DOUBLE" });
108108
expect(extra.state).toEqual(4);
109109
});
110+
it("preserves user context", () => {
111+
const userData = { userId: 1, username: "captbaritone" };
112+
Raven.setUserContext(userData);
113+
expect(() => {
114+
context.store.dispatch({ type: "THROW", extra: "BAR" });
115+
}).toThrow();
116+
117+
expect(context.mockTransport.mock.calls[0][0].data.user).toEqual(
118+
userData
119+
);
120+
});
110121
});
111122
describe("with all the options enabled", () => {
112123
beforeEach(() => {
113124
context.stateTransformer = jest.fn(state => `transformed state ${state}`);
114125
context.actionTransformer = jest.fn(
115126
action => `transformed action ${action.type}`
116127
);
128+
context.getUserContext = jest.fn(state => `user context ${state}`);
117129
context.breadcrumbDataFromAction = jest.fn(action => ({
118130
extra: action.extra
119131
}));
@@ -128,7 +140,8 @@ describe("raven-for-redux", () => {
128140
stateTransformer: context.stateTransformer,
129141
actionTransformer: context.actionTransformer,
130142
breadcrumbDataFromAction: context.breadcrumbDataFromAction,
131-
filterBreadcrumbActions: context.filterBreadcrumbActions
143+
filterBreadcrumbActions: context.filterBreadcrumbActions,
144+
getUserContext: context.getUserContext
132145
})
133146
)
134147
);
@@ -170,19 +183,62 @@ describe("raven-for-redux", () => {
170183
expect(breadcrumbs.values[0].data).toMatchObject({ extra: "FOO" });
171184
expect(breadcrumbs.values[1].data).toMatchObject({ extra: "BAR" });
172185
});
173-
it("preserves user context", () => {
186+
it("transforms the user context on data callback", () => {
187+
context.store.dispatch({ type: "INCREMENT", extra: "FOO" });
174188
const userData = { userId: 1, username: "captbaritone" };
175189
Raven.setUserContext(userData);
176190
expect(() => {
177191
context.store.dispatch({ type: "THROW", extra: "BAR" });
178192
}).toThrow();
179193

180194
expect(context.mockTransport.mock.calls[0][0].data.user).toEqual(
181-
userData
195+
"user context 1"
182196
);
183197
});
184198
});
199+
describe("with multiple data callbaks", () => {
200+
beforeEach(() => {
201+
context.firstOriginalDataCallback = jest.fn((data, original) => {
202+
const newData = Object.assign({}, data, {
203+
firstData: "first"
204+
});
205+
return original ? original(newData) : newData;
206+
});
207+
context.secondOriginalDataCallback = jest.fn((data, original) => {
208+
const newData = Object.assign({}, data, {
209+
secondData: "second"
210+
});
211+
return original ? original(newData) : newData;
212+
});
213+
Raven.setDataCallback(context.firstOriginalDataCallback);
214+
Raven.setDataCallback(context.secondOriginalDataCallback);
215+
context.stateTransformer = jest.fn(state => `transformed state ${state}`);
216+
217+
context.store = createStore(
218+
reducer,
219+
applyMiddleware(
220+
createRavenMiddleware(Raven, {
221+
stateTransformer: context.stateTransformer
222+
})
223+
)
224+
);
225+
});
185226

227+
it("runs all the data callbacks given", () => {
228+
context.store.dispatch({ type: "INCREMENT" });
229+
expect(() => {
230+
context.store.dispatch({ type: "THROW" });
231+
}).toThrow();
232+
expect(context.firstOriginalDataCallback).toHaveBeenCalledTimes(1);
233+
expect(context.secondOriginalDataCallback).toHaveBeenCalledTimes(1);
234+
235+
expect(context.mockTransport).toHaveBeenCalledTimes(1);
236+
const data = context.mockTransport.mock.calls[0][0].data;
237+
expect(data.extra.state).toEqual("transformed state 1");
238+
expect(data.firstData).toEqual("first");
239+
expect(data.secondData).toEqual("second");
240+
});
241+
});
186242
describe("with filterBreadcrumbActions option enabled", () => {
187243
beforeEach(() => {
188244
context.filterBreadcrumbActions = action => {

0 commit comments

Comments
 (0)