Skip to content
This repository has been archived by the owner on May 17, 2019. It is now read-only.

Add transformer to payloads (ActionEmitterTransformerToken) #115

Merged
merged 2 commits into from
Sep 5, 2018
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
56 changes: 54 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,14 @@ This is useful for when you want to collect data about redux actions, potentiall

- [Installation](#installation)
- [Usage](#usage)
- [Capturing action emits](#capturing-action-emits)
- [Capturing action emits](#capturing-action-emits)
- [Transforming action payloads on emission](#transforming-action-payloads-on-emission)
- [Setup](#setup)
- [API](#api)
- [Registration API](#registration-api)
- [`ReduxActionEmitterEnhancer`](#reduxactionemitterenhancer)
- [`EnhancerToken`](#enhancertoken)
- [`ActionEmitterTransformerToken`](#actionemittertransformertoken)
- [Dependencies](#dependencies)
- [Service API](#service-api)
- [Emit API](#emit-api)
Expand All @@ -36,7 +38,7 @@ yarn add fusion-redux-action-emitter-enhancer

### Usage

### Capturing action emits
#### Capturing action emits

We can register a simple callback to listen for the events emitted by this enhancer - in this case, `redux-action-emitter:action`. Normally we might want to log these to a backend service, but for simplicity, we'll log them to console.

Expand All @@ -52,6 +54,45 @@ export default createPlugin({
});
```

#### Transforming action payloads on emission

This plugin depends on [fusion-plugin-universal-events](https://github.com/fusionjs/fusion-plugin-universal-events) to emit the action payloads, which means the payloads are sent over-the-wire to the server-side, which **can consume much of the network bandwidth**, or even cause 413 Payload Too Large HTTP errors. The dependence is the reason why by default the plugin will only emit [certain properties](#default-transformer) from the raw action payload.

By default, `_trackingMeta` is an opinionated property to be picked and emitted from the raw payload for tracking(analytics) purposes. **For customizations, you should provide a transformer function to mainly filter/pick properties for emission.**

```js
// src/app.js
import {ActionEmitterTransformerToken} from 'fusion-redux-action-emitter-enhancer';

app.register(ActionEmitterTransformerToken, action => {
const base = {type: action.type};
switch (action.type) {
case 'ADD_TO_SHOPPING_CART':
case 'REMOVE_FROM_SHOPPING_CART':
return {
...base,
items: action.payload.items,
};
case 'ADD_COUPON': {
return {
...base,
couponId: action.payload.couponId,
};
}
case 'SUPER_BIG_PAYLOAD':
return null; // !!Omit the action type from emission entirely!!
default:
return base;
}
});
```

Or, if you are certain about emitting everything from the raw payload, maybe when bandwidth is actually not a concern for your application:

```js
app.register(ActionEmitterTransformerToken, action => action);
```

---

### Setup
Expand Down Expand Up @@ -106,6 +147,17 @@ import {EnhancerToken} from 'fusion-plugin-react-redux';

If you are using [`fusion-plugin-react-redux`](https://github.com/fusionjs/fusion-plugin-react-redux), we recommend registering this plugin to the `EnhancerToken`.

##### `ActionEmitterTransformerToken`

```js
import {ActionEmitterTransformerToken} from 'fusion-redux-action-emitter-enhancer';
```
###### Default transformer
`action => ({type: action.type, _trackingMeta: action._trackingMeta})`

Providing a transform function for the raw action payloads. See ["Transforming action payloads on emission"](#transforming-action-payloads-on-emission) for more information.


#### Dependencies

```js
Expand Down
1 change: 1 addition & 0 deletions docs/migrations/00115.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
If you were consuming properties on the raw `action` payload other than the default(`{type, _trackingMeta}`), add a transformer for you payloads. See README section ["Transforming action payloads on emission"](https://github.com/fusionjs/fusion-plugin-redux-action-emitter-enhancer#transforming-action-payloads-on-emission)
2 changes: 1 addition & 1 deletion src/__tests__/app.node.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ test('plugin - service resolved as expected', t => {
};
const mockReducer: Reducer<*, *> = s => s;
const enhanced = enhancer(createStore)(mockReducer);
enhanced.dispatch();
enhanced.dispatch({});
wasResolved = true;
},
})
Expand Down
64 changes: 58 additions & 6 deletions src/__tests__/index.node.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import {UniversalEventsToken} from 'fusion-plugin-universal-events';
import type {Context} from 'fusion-core';
import {getService} from 'fusion-test-utils';

import actionEmitterPlugin from '../index.js';
import actionEmitterPlugin, {ActionEmitterTransformerToken} from '../index.js';

type ExtractReturnType = <V>(() => V) => V;
type IEmitter = $Call<typeof UniversalEventsToken, ExtractReturnType>;
Expand Down Expand Up @@ -52,11 +52,16 @@ const sampleReducer = (state = [], action) => {
}
};

const appCreator = (emitter?: IEmitter) => {
const appCreator = (deps?: {emitter?: IEmitter, transformer?: Function}) => {
const {emitter, transformer} = deps || {};

const app = new App('test', el => el);
if (emitter) {
app.register(UniversalEventsToken, emitter);
}
if (transformer) {
app.register(ActionEmitterTransformerToken, transformer);
}
return () => app;
};

Expand All @@ -67,7 +72,8 @@ test('Instantiation', t => {
'requires the EventEmitter dependency'
);
t.doesNotThrow(
() => getService(appCreator(mockEventEmitter), actionEmitterPlugin),
() =>
getService(appCreator({emitter: mockEventEmitter}), actionEmitterPlugin),
'provide the EventEmitter dependency'
);
t.end();
Expand All @@ -77,7 +83,7 @@ test('Emits actions', t => {
// Setup
const mockEventEmitter = getMockEventEmitterFactory();
const enhancer = getService(
appCreator(mockEventEmitter),
appCreator({emitter: mockEventEmitter}),
actionEmitterPlugin
);
const mockCtx = {mock: true};
Expand Down Expand Up @@ -105,14 +111,60 @@ test('Emits actions', t => {
'SAMPLE_SET',
'payload type is SAMPLE_SET, as expected'
);
t.equal(payload.value, true, 'payload value is true, as expected');
t.ok(
typeof payload.foo === 'undefined',
'By default properties other than {type, _trackingMeta} is emitted'
);
t.equal(ctx, mockCtxTyped, 'ctx was provided');
});
store.dispatch({
type: 'SAMPLE_SET',
value: true,
foo: {bar: 1},
});

t.plan(3);
t.end();
});

test('transformers', t => {
// Setup
const mockEventEmitter = getMockEventEmitterFactory();

const enhancer = getService(
appCreator({
emitter: mockEventEmitter,
transformer: action => ({foo: action.foo}),
}),
actionEmitterPlugin
);
const mockCtx = {mock: true};
const mockCtxTyped = ((mockCtx: any): Context);
const store = createStore(
sampleReducer,
[],
compose(
enhancer,
createStore => (...args) => {
const store = createStore(...args);
// $FlowFixMe
store.ctx = mockCtx;
return store;
}
)
);

// Test Emits
mockEventEmitter
.from(mockCtxTyped)
.on('redux-action-emitter:action', (payload, ctx) => {
t.deepEqual(payload, {foo: 1}, 'payload is transformed');
t.equal(ctx, mockCtxTyped, 'ctx was provided');
});
store.dispatch({
type: 'SAMPLE_SET',
foo: 1,
});

t.plan(2);
t.end();
});
35 changes: 30 additions & 5 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,35 @@
/* eslint-env browser,node */

import type {StoreEnhancer, StoreCreator, Store} from 'redux';
import type {Token} from 'fusion-core';

import {createPlugin} from 'fusion-core';
import {createPlugin, createToken} from 'fusion-core';
import {UniversalEventsToken} from 'fusion-plugin-universal-events';

type ExtractReturnType = <V>(() => V) => V;
type IEmitter = $Call<typeof UniversalEventsToken, ExtractReturnType>;

export const ActionEmitterTransformerToken: Token<Function> = createToken(
'ActionEmitterTransformerToken'
);

const defaultTransformer = action => {
const {type, _trackingMeta} = action;
return {type, _trackingMeta};
};

const plugin = createPlugin({
deps: {
emitter: UniversalEventsToken,
transformer: ActionEmitterTransformerToken.optional,
},
provides({emitter}: {emitter: IEmitter}) {
provides({
emitter,
transformer,
}: {
emitter: IEmitter,
transformer?: Function,
}) {
if (__DEV__ && !emitter) {
throw new Error(`emitter is required, but was: ${emitter}`);
}
Expand All @@ -31,9 +48,17 @@ const plugin = createPlugin({
const store: Store<*, *, *> = createStore(...args);
return {
...store,
dispatch: action => {
// $FlowFixMe
emitter.from(store.ctx).emit('redux-action-emitter:action', action);
dispatch: (action: Object) => {
let payload: Object = !transformer
? defaultTransformer(action)
: transformer(action);

if (payload) {
emitter // $FlowFixMe
.from(store.ctx)
.emit('redux-action-emitter:action', payload);
}

return store.dispatch(action);
},
};
Expand Down