Skip to content

User context #49

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

Merged
merged 3 commits into from
Dec 27, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
20 changes: 20 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ This library makes, what I think are, a few improvements over
2. Adds your state and last action as context to _all_ errors, not just reducer
exceptions.
3. Allows filtering action breadcrumbs before sending to Sentry
4. Allows you to define a user context mapping from the state

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

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

This option was introduced in version 1.1.1.

#### `getUserContext` _(Optional Function)_

Signature: `state => userContext`

The [user context] is an important part of the error report. This function
allows you to define a mapping from the `state` to the user context. When
`getUserContext` is spesified, the result of `getUserContext`
will set the the user context before sending the error report.

Be careful not to mutate your `state` within this function.

If you have specified a [`dataCallback`] when you configured Raven, note that
`getUserContext` will be applied _before_ your specified `dataCallback`.
When a `getUserContext` function is given, it will override the user context
set elsewhere.

This option was introduced in version 1.X

## Changelog

### 1.1.1
Expand Down Expand Up @@ -168,6 +187,7 @@ This option was introduced in version 1.1.1.
[Raven]: https://docs.sentry.io/clients/javascript/
[Raven Breadcrumbs]: https://docs.sentry.io/clients/javascript/usage/#recording-breadcrumbs
[Breadcrumb documentation]: https://docs.sentry.io/learn/breadcrumbs/
[user context]: https://docs.sentry.io/learn/context/#capturing-the-user
[`dataCallback`]: https://docs.sentry.io/clients/javascript/config/
[#11]: https://github.com/captbaritone/raven-for-redux/pull/11
[#8]: https://github.com/captbaritone/raven-for-redux/pull/8
Expand Down
9 changes: 7 additions & 2 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,20 @@ function createRavenMiddleware(Raven, options = {}) {
actionTransformer = identity,
stateTransformer = identity,
breadcrumbCategory = "redux-action",
filterBreadcrumbActions = filter
filterBreadcrumbActions = filter,
getUserContext
} = options;

return store => {
let lastAction;

Raven.setDataCallback((data, original) => {
const state = store.getState();
data.extra.lastAction = actionTransformer(lastAction);
data.extra.state = stateTransformer(store.getState());
data.extra.state = stateTransformer(state);
if (getUserContext) {
data.user = getUserContext(state);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you think we should add a note that the original state, and not the transformed one?

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You mean, a note about it overwriting any existing user context?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, but I did indeed add a notice about that.

But, a note saying that the getUserContext is executed with the original redux-state, and not the state transformed via the stateTransformer.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the docs, as long as we refer to it as “Redux state” I think that’s clear. In the code, I think it’s self explanatory.

}
return original ? original(data) : data;
});

Expand Down
62 changes: 59 additions & 3 deletions index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -107,13 +107,25 @@ describe("raven-for-redux", () => {
expect(extra.lastAction).toEqual({ type: "DOUBLE" });
expect(extra.state).toEqual(4);
});
it("preserves user context", () => {
const userData = { userId: 1, username: "captbaritone" };
Raven.setUserContext(userData);
expect(() => {
context.store.dispatch({ type: "THROW", extra: "BAR" });
}).toThrow();

expect(context.mockTransport.mock.calls[0][0].data.user).toEqual(
userData
);
});
});
describe("with all the options enabled", () => {
beforeEach(() => {
context.stateTransformer = jest.fn(state => `transformed state ${state}`);
context.actionTransformer = jest.fn(
action => `transformed action ${action.type}`
);
context.getUserContext = jest.fn(state => `user context ${state}`);
context.breadcrumbDataFromAction = jest.fn(action => ({
extra: action.extra
}));
Expand All @@ -128,7 +140,8 @@ describe("raven-for-redux", () => {
stateTransformer: context.stateTransformer,
actionTransformer: context.actionTransformer,
breadcrumbDataFromAction: context.breadcrumbDataFromAction,
filterBreadcrumbActions: context.filterBreadcrumbActions
filterBreadcrumbActions: context.filterBreadcrumbActions,
getUserContext: context.getUserContext
})
)
);
Expand Down Expand Up @@ -170,19 +183,62 @@ describe("raven-for-redux", () => {
expect(breadcrumbs.values[0].data).toMatchObject({ extra: "FOO" });
expect(breadcrumbs.values[1].data).toMatchObject({ extra: "BAR" });
});
it("preserves user context", () => {
it("transforms the user context on data callback", () => {
context.store.dispatch({ type: "INCREMENT", extra: "FOO" });
const userData = { userId: 1, username: "captbaritone" };
Raven.setUserContext(userData);
expect(() => {
context.store.dispatch({ type: "THROW", extra: "BAR" });
}).toThrow();

expect(context.mockTransport.mock.calls[0][0].data.user).toEqual(
userData
"user context 1"
);
});
});
describe("with multiple data callbaks", () => {
beforeEach(() => {
context.firstOriginalDataCallback = jest.fn((data, original) => {
const newData = Object.assign({}, data, {
firstData: "first"
});
return original ? original(newData) : newData;
});
context.secondOriginalDataCallback = jest.fn((data, original) => {
const newData = Object.assign({}, data, {
secondData: "second"
});
return original ? original(newData) : newData;
});
Raven.setDataCallback(context.firstOriginalDataCallback);
Raven.setDataCallback(context.secondOriginalDataCallback);
context.stateTransformer = jest.fn(state => `transformed state ${state}`);

context.store = createStore(
reducer,
applyMiddleware(
createRavenMiddleware(Raven, {
stateTransformer: context.stateTransformer
})
)
);
});

it("runs all the data callbacks given", () => {
context.store.dispatch({ type: "INCREMENT" });
expect(() => {
context.store.dispatch({ type: "THROW" });
}).toThrow();
expect(context.firstOriginalDataCallback).toHaveBeenCalledTimes(1);
expect(context.secondOriginalDataCallback).toHaveBeenCalledTimes(1);

expect(context.mockTransport).toHaveBeenCalledTimes(1);
const data = context.mockTransport.mock.calls[0][0].data;
expect(data.extra.state).toEqual("transformed state 1");
expect(data.firstData).toEqual("first");
expect(data.secondData).toEqual("second");
});
});
describe("with filterBreadcrumbActions option enabled", () => {
beforeEach(() => {
context.filterBreadcrumbActions = action => {
Expand Down