Browse Source
* Add Redux example * Update .npmignore and bower.json * Add Examples README and fix copymaster
Dustin Larimer
7 years ago
committed by
GitHub
6 changed files with 313 additions and 1 deletions
@ -0,0 +1,206 @@ |
|||
# React Redux Middleware |
|||
|
|||
This example demonstrates how to instrument a Redux Store, based on the official [Redux Real World Example app](https://github.com/reactjs/redux/tree/master/examples/real-world). |
|||
|
|||
### Installation |
|||
|
|||
Install this package from npm: |
|||
|
|||
```ssh |
|||
$ npm install keen-tracking --save |
|||
``` |
|||
|
|||
### Middleware |
|||
|
|||
Create a new file where instrumentation and data modeling logic will live: |
|||
|
|||
```ssh |
|||
redux/examples/real-world/src/store/keen-middleware.js |
|||
``` |
|||
|
|||
This file has a lot going on, so here's a quick overview: |
|||
|
|||
1. Import the `keen-tracking` package and disable event logging when not in `production` mode. |
|||
2. `EVENT_STREAM_NAME` can be customized to your liking. We recommend recording events to a single stream when instrumenting apps like this, and filtering queries on the `action.type` (or similar) property defined within your actions. |
|||
3. `OMITTED_ACTIONS` allows you to omit noisy or trivial actions from being recorded. |
|||
4. Define a `client` instance and enable logging when not in `production` mode. Events won't be recorded, since `Keen.disabled` is `true`, but you will still be able to see captured events logged out in the console. |
|||
5. Use `client.extendEvents()` to define a baseline data model for every action/event that is recorded. |
|||
6. Define and export the actual middleware function where `action` and `state` data are captured. |
|||
|
|||
```javascript |
|||
import Keen from 'keen-tracking'; |
|||
if (process.env.NODE_ENV !== 'production') { |
|||
Keen.disabled = true; |
|||
} |
|||
|
|||
const EVENT_STREAM_NAME = 'app-action'; |
|||
const OMITTED_ACTIONS = [ |
|||
'@@router/LOCATION_CHANGE' |
|||
]; |
|||
|
|||
const client = new Keen({ |
|||
projectId: 'PROJECT_ID', |
|||
writeKey: 'WRITE_KEY' |
|||
}); |
|||
if (process.env.NODE_ENV !== 'production') { |
|||
client.on('recordEvent', KeenTracking.log); |
|||
} |
|||
|
|||
const helpers = Keen.helpers; |
|||
const timer = Keen.utils.timer(); |
|||
timer.start(); |
|||
|
|||
client.extendEvents(() => { |
|||
return { |
|||
geo: { |
|||
info: { /* Enriched */ }, |
|||
ip_address: '${keen.ip}', |
|||
}, |
|||
page: { |
|||
info: { /* Enriched */ }, |
|||
title: document.title, |
|||
url: document.location.href |
|||
}, |
|||
referrer: { |
|||
info: { /* Enriched */ }, |
|||
url: document.referrer |
|||
}, |
|||
tech: { |
|||
browser: helers.getBrowserProfile(), |
|||
info: { /* Enriched */ }, |
|||
user_agent: '${keen.user_agent}' |
|||
}, |
|||
time: helpers.getDatetimeIndex(), |
|||
visitor: { |
|||
time_on_page: timer.value() |
|||
/* Include additional visitor info here */ |
|||
}, |
|||
keen: { |
|||
addons: [ |
|||
{ |
|||
name: 'keen:ip_to_geo', |
|||
input: { |
|||
ip: 'geo.ip_address' |
|||
}, |
|||
output : 'geo.info' |
|||
}, |
|||
{ |
|||
name: 'keen:ua_parser', |
|||
input: { |
|||
ua_string: 'tech.user_agent' |
|||
}, |
|||
output: 'tech.info' |
|||
}, |
|||
{ |
|||
name: 'keen:url_parser', |
|||
input: { |
|||
url: 'page.url' |
|||
}, |
|||
output: 'page.info' |
|||
}, |
|||
{ |
|||
name: 'keen:referrer_parser', |
|||
input: { |
|||
referrer_url: 'referrer.url', |
|||
page_url: 'page.url' |
|||
}, |
|||
output: 'referrer.info' |
|||
} |
|||
] |
|||
} |
|||
}; |
|||
}); |
|||
|
|||
const reduxMiddleware = function({ getState }) { |
|||
return (next) => (action) => { |
|||
const returnValue = next(action); |
|||
const eventBody = { |
|||
'action': action, |
|||
'state': getState() |
|||
}; |
|||
if (OMITTED_ACTIONS.indexOf(action.type) < 0) { |
|||
client.recordEvent(EVENT_STREAM_NAME, eventBody); |
|||
} |
|||
return returnValue; |
|||
}; |
|||
} |
|||
|
|||
export default reduxMiddleware; |
|||
``` |
|||
|
|||
|
|||
### Instrument the Store |
|||
|
|||
Next, let's insert our middleware where the store is first instantiated. The demo app contains distinct files for both `dev` and `prod` environments. |
|||
|
|||
**configureStore.dev.js** |
|||
|
|||
```ssh |
|||
redux/examples/real-world/src/store/configureStore.dev.js |
|||
``` |
|||
|
|||
Import the module defined previously and include it in the middleware composition [here](https://github.com/reactjs/redux/blob/master/examples/real-world/src/store/configureStore.dev.js#L13): |
|||
|
|||
```javascript |
|||
import { createStore, applyMiddleware, compose } from 'redux'; |
|||
import thunk from 'redux-thunk'; |
|||
import { createLogger } from 'redux-logger'; |
|||
import api from '../middleware/api'; |
|||
import rootReducer from '../reducers'; |
|||
import DevTools from '../containers/DevTools'; |
|||
|
|||
// 1. Import middleware module |
|||
import KeenMiddleware from './keen-middleware'; |
|||
|
|||
const configureStore = preloadedState => { |
|||
const store = createStore( |
|||
rootReducer, |
|||
preloadedState, |
|||
compose( |
|||
// 2. Append to middleware composition |
|||
applyMiddleware(thunk, api, createLogger(), KeenMiddleware), |
|||
DevTools.instrument() |
|||
) |
|||
); |
|||
|
|||
if (module.hot) { |
|||
module.hot.accept('../reducers', () => { |
|||
const nextRootReducer = require('../reducers').default |
|||
store.replaceReducer(nextRootReducer) |
|||
}) |
|||
} |
|||
|
|||
return store; |
|||
} |
|||
|
|||
export default configureStore; |
|||
``` |
|||
|
|||
**configureStore.prod.js** |
|||
|
|||
```ssh |
|||
redux/examples/real-world/src/store/configureStore.prod.js |
|||
``` |
|||
|
|||
Import the module and include it in the middleware composition [here](https://github.com/reactjs/redux/blob/master/examples/real-world/src/store/configureStore.prod.js#L9): |
|||
|
|||
```javascript |
|||
import { createStore, applyMiddleware } from 'redux'; |
|||
import thunk from 'redux-thunk'; |
|||
import api from '../middleware/api'; |
|||
import rootReducer from '../reducers'; |
|||
|
|||
// 1. Import middleware module |
|||
import KeenMiddleware from './keen-middleware'; |
|||
|
|||
const configureStore = preloadedState => createStore( |
|||
rootReducer, |
|||
preloadedState, |
|||
// 2. Append to middleware composition |
|||
applyMiddleware(thunk, api, KeenMiddleware) |
|||
) |
|||
|
|||
export default configureStore |
|||
``` |
|||
|
|||
Once the app reloads, events should begin appearing in the browser console. |
@ -0,0 +1,98 @@ |
|||
import Keen from 'keen-tracking'; |
|||
if (process.env.NODE_ENV !== 'production') { |
|||
Keen.disabled = true; |
|||
} |
|||
|
|||
const EVENT_STREAM_NAME = 'app-action'; |
|||
const OMITTED_ACTIONS = [ |
|||
'@@router/LOCATION_CHANGE' |
|||
]; |
|||
|
|||
const client = new Keen({ |
|||
projectId: 'PROJECT_ID', |
|||
writeKey: 'WRITE_KEY' |
|||
}); |
|||
if (process.env.NODE_ENV !== 'production') { |
|||
client.on('recordEvent', KeenTracking.log); |
|||
} |
|||
|
|||
const helpers = Keen.helpers; |
|||
const timer = Keen.utils.timer(); |
|||
timer.start(); |
|||
|
|||
client.extendEvents(() => { |
|||
return { |
|||
geo: { |
|||
info: { /* Enriched */ }, |
|||
ip_address: '${keen.ip}', |
|||
}, |
|||
page: { |
|||
info: { /* Enriched */ }, |
|||
title: document.title, |
|||
url: document.location.href |
|||
}, |
|||
referrer: { |
|||
info: { /* Enriched */ }, |
|||
url: document.referrer |
|||
}, |
|||
tech: { |
|||
browser: helers.getBrowserProfile(), |
|||
info: { /* Enriched */ }, |
|||
user_agent: '${keen.user_agent}' |
|||
}, |
|||
time: helpers.getDatetimeIndex(), |
|||
visitor: { |
|||
time_on_page: timer.value() |
|||
/* Include additional visitor info here */ |
|||
}, |
|||
keen: { |
|||
addons: [ |
|||
{ |
|||
name: 'keen:ip_to_geo', |
|||
input: { |
|||
ip: 'geo.ip_address' |
|||
}, |
|||
output : 'geo.info' |
|||
}, |
|||
{ |
|||
name: 'keen:ua_parser', |
|||
input: { |
|||
ua_string: 'tech.user_agent' |
|||
}, |
|||
output: 'tech.info' |
|||
}, |
|||
{ |
|||
name: 'keen:url_parser', |
|||
input: { |
|||
url: 'page.url' |
|||
}, |
|||
output: 'page.info' |
|||
}, |
|||
{ |
|||
name: 'keen:referrer_parser', |
|||
input: { |
|||
referrer_url: 'referrer.url', |
|||
page_url: 'page.url' |
|||
}, |
|||
output: 'referrer.info' |
|||
} |
|||
] |
|||
} |
|||
}; |
|||
}); |
|||
|
|||
const reduxMiddleware = function({ getState }) { |
|||
return (next) => (action) => { |
|||
const returnValue = next(action); |
|||
const eventBody = { |
|||
'action': action, |
|||
'state': getState() |
|||
}; |
|||
if (OMITTED_ACTIONS.indexOf(action.type) < 0) { |
|||
client.recordEvent(EVENT_STREAM_NAME, eventBody); |
|||
} |
|||
return returnValue; |
|||
}; |
|||
} |
|||
|
|||
export default reduxMiddleware; |
Loading…
Reference in new issue