Comparing version 0.2.2 to 0.3.0
@@ -13,4 +13,4 @@ // Type definitions for redux-gtm | ||
* Use this property to specify the name of the event you want to emit | ||
* for the associated Redux action. If not provided, the event name | ||
* defaults to the Redux action type. | ||
* for the associated action. If not provided, the event name defaults | ||
* to the action type. | ||
* | ||
@@ -20,5 +20,6 @@ * eventFields | ||
* would like to emit with the event. Any function assigned to this | ||
* property will receive the state of the application, and the | ||
* associated action object. Any properties in the object returned by | ||
* the attached function will be emitted along with the event. | ||
* property will receive the state of the application (before the | ||
* action), and the associated action object. Any property named | ||
* "event" in the returned object will override any defaults or any | ||
* event names defined in `eventName`. | ||
* | ||
@@ -28,17 +29,14 @@ * eventSchema | ||
* validation functions for each property in the event that you want | ||
* to validate. If any of these validation functions return false, the | ||
* event will not be emitted. | ||
* to validate. If any of these validation functions return false, | ||
* ReduxGTM will not emit the event. | ||
*/ | ||
export interface EventDefinition { | ||
eventName?: string; | ||
eventFields? (prevState: any, action: any): any; | ||
eventSchema?: EventSchema; | ||
} | ||
export type EventDefinition = { eventName: string } | { eventFields(prevState: any, action: any): any } | { eventSchema: EventSchema }; | ||
/** | ||
* A map between your Redux actions and your analytics events. Each | ||
* key must be a Redux action type. | ||
* A map between your actions and your analytics events. Each key | ||
* must be an action type. Each property must be a valid | ||
* EventDefinition or an array of EventDefinitions. | ||
*/ | ||
export interface EventDefinitionsMap { | ||
[key: string]: EventDefinition; | ||
[key: string]: EventDefinition | Array<EventDefinition>; | ||
} | ||
@@ -58,8 +56,20 @@ | ||
interface GAeventProperties { | ||
eventAction?: string, | ||
eventCategory?: string, | ||
eventLabel?: string, | ||
eventValue?: string, | ||
eventAction?: string; | ||
eventCategory?: string; | ||
eventLabel?: string; | ||
eventValue?: string; | ||
} | ||
interface GApageviewProperties { | ||
title?: string; | ||
location?: string; | ||
} | ||
interface GAtimingProperties { | ||
timingCategory?: string; | ||
timingLabel?: string; | ||
timingVar?: string; | ||
timingValue?: number; | ||
} | ||
/** | ||
@@ -75,3 +85,16 @@ * Create an event compatible with Google Analtyics. | ||
*/ | ||
function createGApageview(page?: string): any; | ||
function createGApageview(page?: string, pageProps?: GApageviewProperties): any; | ||
/** | ||
* Create a timing event compatible with Google analytics. | ||
* Use with the ga-stater container | ||
*/ | ||
function createGAuserTiming(timingProps?: GAtimingProperties): any; | ||
} | ||
declare namespace Extensions { | ||
function logger(): any; | ||
type ConnectivitySelector = (state: any) => boolean; | ||
function offlineWeb(isConnected: ConnectivitySelector): any; | ||
function offlineReactNative(AsyncStorage: any, isConnected: ConnectivitySelector): any; | ||
} |
'use strict'; | ||
var createEvent = require('./create-event'); | ||
var validateEvent = require('./validate-event'); | ||
var createEvents = require('./create-events'); | ||
var getDataLayer = require('./get-data-layer'); | ||
var registerEvents = require('./register-events'); | ||
var createMetaReducer = function createMetaReducer(actionsToTrack, customDataLayer) { | ||
function createMetaReducer(eventDefinitionsMap) { | ||
var extensions = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; | ||
return function (reducer) { | ||
return function (state, action) { | ||
var dataLayer = getDataLayer(window, customDataLayer); | ||
return function (prevState, action) { | ||
if (!eventDefinitionsMap[action.type]) { | ||
return reducer(prevState, action); | ||
} | ||
var result = reducer(state, action); | ||
var dataLayer = getDataLayer(window, extensions.customDataLayer); | ||
var eventDefinition = actionsToTrack[action.type]; | ||
if (dataLayer === undefined || !eventDefinition) { | ||
return result; | ||
if (dataLayer === undefined) { | ||
return reducer(prevState, action); | ||
} | ||
var event = createEvent(eventDefinition, state, action); | ||
var isValidEvent = validateEvent(event, eventDefinition); | ||
var events = createEvents(eventDefinitionsMap[action.type], prevState, action); | ||
registerEvents(events, dataLayer, prevState, extensions, action); | ||
if (isValidEvent) { | ||
dataLayer.push(event); | ||
} | ||
return result; | ||
return reducer(prevState, action); | ||
}; | ||
}; | ||
}; | ||
} | ||
module.exports = createMetaReducer; |
'use strict'; | ||
var createEvent = require('./create-event'); | ||
var validateEvent = require('./validate-event'); | ||
var createEvents = require('./create-events'); | ||
var registerEvents = require('./register-events'); | ||
var getDataLayer = require('./get-data-layer'); | ||
var createMiddleware = function createMiddleware(actionsToTrack, customDataLayer) { | ||
var createMiddleware = function createMiddleware(eventDefinitionsMap) { | ||
var extensions = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; | ||
return function (store) { | ||
return function (next) { | ||
return function (action) { | ||
var dataLayer = getDataLayer(window, customDataLayer); | ||
if (!eventDefinitionsMap[action.type]) { | ||
return next(action); | ||
} | ||
var eventDefinition = actionsToTrack[action.type]; | ||
var dataLayer = getDataLayer(window, extensions.customDataLayer); | ||
if (dataLayer === undefined || !eventDefinition) { | ||
if (dataLayer === undefined) { | ||
return next(action); | ||
@@ -20,10 +23,6 @@ } | ||
var prevState = store.getState(); | ||
var event = createEvent(eventDefinition, prevState, action); | ||
var isValidEvent = validateEvent(event, eventDefinition); | ||
var events = createEvents(eventDefinitionsMap[action.type], prevState, action); | ||
registerEvents(events, dataLayer, prevState, extensions, action); | ||
if (isValidEvent) { | ||
dataLayer.push(event); | ||
} | ||
return next(action); | ||
@@ -30,0 +29,0 @@ }; |
'use strict'; | ||
function emitGApageview() { | ||
/** | ||
* If pageProps not specified, google analytics could still | ||
* pick up page's title and location. | ||
*/ | ||
function createGApageview() { | ||
var page = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 'unknown page'; | ||
var pageProps = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; | ||
return { | ||
var defaultPageView = { | ||
event: 'REDUX_GTM_GA_EVENT', | ||
@@ -11,4 +16,5 @@ hitType: 'pageview', | ||
}; | ||
return Object.assign(defaultPageView, pageProps); | ||
} | ||
module.exports = emitGApageview; | ||
module.exports = createGApageview; |
@@ -5,5 +5,10 @@ 'use strict'; | ||
var createMetaReducer = require('./create-meta-reducer'); | ||
// Extensions | ||
var logger = require('./extensions/logger'); | ||
var offlineWeb = require('./extensions/offline-web'); | ||
var offlineReactNative = require('./extensions/offline-react-native'); | ||
// Event Helpers | ||
var createGAevent = require('./event-helpers/create-ga-event'); | ||
var createGApageview = require('./event-helpers/create-ga-pageview'); | ||
var createGAuserTiming = require('./event-helpers/create-ga-user-timing'); | ||
@@ -13,6 +18,12 @@ module.exports = { | ||
createMetaReducer: createMetaReducer, | ||
Extensions: { | ||
logger: logger, | ||
offlineWeb: offlineWeb, | ||
offlineReactNative: offlineReactNative | ||
}, | ||
EventHelpers: { | ||
createGAevent: createGAevent, | ||
createGApageview: createGApageview | ||
createGApageview: createGApageview, | ||
createGAuserTiming: createGAuserTiming | ||
} | ||
}; |
{ | ||
"name": "redux-gtm", | ||
"version": "0.2.2", | ||
"version": "0.3.0", | ||
"description": "Google Tag Manager integration for Redux and ngrx/store", | ||
@@ -13,2 +13,6 @@ "main": "lib/index.js", | ||
"scripts": { | ||
"docs:prepare": "gitbook install", | ||
"docs:watch": "npm run docs:prepare && gitbook serve", | ||
"docs:build": "npm run docs:prepare && rm -rf _book && gitbook build", | ||
"docs:publish": "npm run docs:build && cd _book && git init && git commit --allow-empty -m 'Update docs' && git checkout -b gh-pages && git add . && git commit -am 'Update docs' && git push git@github.com:rangle/redux-gtm gh-pages --force", | ||
"test": "jest", | ||
@@ -46,2 +50,3 @@ "test:watch": "npm test -- --watch", | ||
"eslint-plugin-react": "^6.4.1", | ||
"gitbook-cli": "^2.3.0", | ||
"jest": "^16.0.1", | ||
@@ -48,0 +53,0 @@ "redux": "^3.6.0", |
108
README.md
@@ -5,27 +5,52 @@ # ReduxGTM | ||
[![license](https://img.shields.io/github/license/rangle/redux-gtm.svg?style=flat-square)](LICENSE) | ||
[![npm version](https://img.shields.io/npm/v/redux-gtm.svg?style=flat-square)](https://www.npmjs.com/package/redux-gtm) | ||
[![CircleCI](https://img.shields.io/circleci/project/github/rangle/redux-gtm.svg?style=flat-square)](https://circleci.com/gh/rangle/redux-gtm) | ||
[![license](https://img.shields.io/github/license/rangle/redux-gtm.svg)](LICENSE) | ||
[![npm version](https://img.shields.io/npm/v/redux-gtm.svg)](https://www.npmjs.com/package/redux-gtm) | ||
[![CircleCI](https://img.shields.io/circleci/project/github/rangle/redux-gtm.svg)](https://circleci.com/gh/rangle/redux-gtm) | ||
## Getting Started | ||
```bash | ||
npm install --save redux-gtm | ||
``` | ||
### Prerequisites | ||
![logger-ext](https://cloud.githubusercontent.com/assets/7446702/20887911/9739e4b4-baca-11e6-8d2d-08db48189d0c.gif) | ||
---- | ||
### Quick Start | ||
##### What You Need First | ||
- An app using [Redux](http://redux.js.org/) or [ngrx/store](https://github.com/ngrx/store) to manage state | ||
- A [Google Tag Manager](https://developers.google.com/tag-manager/) account | ||
- A [container snippet](https://developers.google.com/tag-manager/quickstart) in your app's html _(Web)_ | ||
- A [Google Tag Manager](https://developers.google.com/tag-manager/) (GTM) account | ||
- An installed GTM [container snippet](https://developers.google.com/tag-manager/quickstart) | ||
### Installation | ||
##### How it Works | ||
In a nutshell, ReduxGTM provides a way for mapping your redux actions to | ||
[custom Gooogle Tag Manager events](https://developers.google.com/tag-manager/devguide#events). | ||
The first step is to create an `EventDefinitionsMap` which maps your | ||
action types to an `EventDefinition`: | ||
With [npm](https://www.npmjs.com/): | ||
```bash | ||
npm install --save redux-gtm | ||
```js | ||
const eventDefinitionsMap = { | ||
'SOME_ACTION_TYPE': { | ||
eventName: 'some-custom-gtm-event', | ||
eventFields: (state, action) => ({ | ||
'someEventVariable': action.payload | ||
}), | ||
} | ||
}; | ||
``` | ||
With [yarn](https://yarnpkg.com/): | ||
```bash | ||
yarn add redux-gtm | ||
The object mapped to `SOME_ACTION_TYPE` is called an | ||
`EventDefinition`. ReduxGTM uses `EventDefinitions` to generate a | ||
custom GTM events. The `EventDefinition` above will produce an event | ||
with following shape: | ||
```js | ||
{ | ||
'event': 'some-custom-gtm-event', | ||
'someEventVariable': ... // the value stored in action.payload | ||
} | ||
``` | ||
### How it Works | ||
Once we've got an event definitions map, all we have to do is create | ||
the middleware, and apply it to our store. | ||
@@ -36,39 +61,40 @@ ```js | ||
// 1. Import ReduxGTM | ||
// Import ReduxGTM | ||
import { createMiddleware } from 'redux-gtm'; | ||
// 2. Create a mapping between you Redux actions and you Google Tag Manager events | ||
const eventDefinitions = { | ||
'SOME_REDUX_ACTION_TYPE': { eventName: 'some-gtm-custom-event' }, | ||
// The event definitions map prepared earlier | ||
const eventDefinitionsMap = { | ||
'SOME_ACTION_TYPE': { | ||
eventName: 'some-custom-gtm-event', | ||
eventFields: (state, action) => ({ | ||
'someEventVariable': action.payload | ||
}), | ||
} | ||
}; | ||
// 3. Create the middleware using createMiddleware from ReduxGTM | ||
const analyticsMiddleware = createMiddleware(eventDefinitions); | ||
// Create the ReduxGTM middleware | ||
const middleware = createMiddleware(eventDefinitionsMap); | ||
// 4. Apply the middleware when creating your Redux store | ||
// Apply the middleware when creating your Redux store | ||
const store = createStore(reducer, applyMiddleware(analyticsMiddleware)); | ||
``` | ||
Now, whenever your application dispatches `SOME_REDUX_ACTION_TYPE`, | ||
ReduxGTM will emit `some-gtm-custom-event` to Google Tag Manager. | ||
Now, whenever your application dispatches `SOME_ACTION_TYPE`, ReduxGTM | ||
will create the associated custom event and push it to the data layer. | ||
#### Notes | ||
- When mapping actions to events, each action type must be mapped to a | ||
valid [eventDefinition](docs/event-definition.md). | ||
##### What Else Can You Do? | ||
## Examples | ||
- [How do I track pageviews when using React Router?](docs/examples/example1.md) | ||
- [How do I track failure events?](docs/examples/example2.md) | ||
- [How to do mini surveys](docs/examples/example3.md) | ||
* Use ReduxGTM in React Native and Cordova apps | ||
* Track analytics events even if one of your users loses connection | ||
(offline events tracking) | ||
* Use one of our starter containers to get up and running in GTM with | ||
almost zero configuration | ||
* Provide multiple event definitions for a single Redux action | ||
## API | ||
- [createMiddleware](docs/create-middleware.md)([eventDefinitionsMap](docs/event-definitions-map.md), [[dataLayer]](docs/data-layer.md)) | ||
- [createMetaReducer](docs/create-meta-reducer.md)([eventDefinitionsMap](docs/event-definitions-map.md), [[dataLayer]](docs/data-layer.md)) | ||
- [EventHelpers](docs/event-helpers/event-helpers.md) | ||
- [createGAevent](docs/event-helpers/create-ga-event.md) | ||
- [createGApageview](docs/event-helpers/create-ga-pageview.md) | ||
### Documentation | ||
The [official docs](https://rangle.github.io/redux-gtm/) contain | ||
tutorials, examples, and a comprehensive API reference for the latest | ||
npm version. | ||
## License | ||
This project is licensed under the MIT License - see | ||
the [LICENSE](LICENSE) file for details | ||
### License | ||
This project is licensed under the MIT License. |
@@ -1,25 +0,26 @@ | ||
const createEvent = require('./create-event'); | ||
const validateEvent = require('./validate-event'); | ||
const createEvents = require('./create-events'); | ||
const getDataLayer = require('./get-data-layer'); | ||
const registerEvents = require('./register-events'); | ||
const createMetaReducer = (actionsToTrack, customDataLayer) => reducer => (state, action) => { | ||
const dataLayer = getDataLayer(window, customDataLayer); | ||
function createMetaReducer(eventDefinitionsMap, extensions = {}) { | ||
return function (reducer) { | ||
return function (prevState, action) { | ||
if (!eventDefinitionsMap[action.type]) { | ||
return reducer(prevState, action); | ||
} | ||
const result = reducer(state, action); | ||
const dataLayer = getDataLayer(window, extensions.customDataLayer); | ||
const eventDefinition = actionsToTrack[action.type]; | ||
if (dataLayer === undefined || !eventDefinition) { | ||
return result; | ||
} | ||
if (dataLayer === undefined) { | ||
return reducer(prevState, action); | ||
} | ||
const event = createEvent(eventDefinition, state, action); | ||
const isValidEvent = validateEvent(event, eventDefinition); | ||
const events = createEvents(eventDefinitionsMap[action.type], prevState, action); | ||
registerEvents(events, dataLayer, prevState, extensions, action); | ||
if (isValidEvent) { | ||
dataLayer.push(event); | ||
} | ||
return reducer(prevState, action); | ||
}; | ||
}; | ||
} | ||
return result; | ||
}; | ||
module.exports = createMetaReducer; |
@@ -1,11 +0,13 @@ | ||
const createEvent = require('./create-event'); | ||
const validateEvent = require('./validate-event'); | ||
const createEvents = require('./create-events'); | ||
const registerEvents = require('./register-events'); | ||
const getDataLayer = require('./get-data-layer'); | ||
const createMiddleware = (actionsToTrack, customDataLayer) => store => next => (action) => { | ||
const dataLayer = getDataLayer(window, customDataLayer); | ||
const createMiddleware = (eventDefinitionsMap, extensions = {}) => store => next => (action) => { | ||
if (!eventDefinitionsMap[action.type]) { | ||
return next(action); | ||
} | ||
const eventDefinition = actionsToTrack[action.type]; | ||
const dataLayer = getDataLayer(window, extensions.customDataLayer); | ||
if (dataLayer === undefined || !eventDefinition) { | ||
if (dataLayer === undefined) { | ||
return next(action); | ||
@@ -15,10 +17,6 @@ } | ||
const prevState = store.getState(); | ||
const event = createEvent(eventDefinition, prevState, action); | ||
const isValidEvent = validateEvent(event, eventDefinition); | ||
const events = createEvents(eventDefinitionsMap[action.type], prevState, action); | ||
registerEvents(events, dataLayer, prevState, extensions, action); | ||
if (isValidEvent) { | ||
dataLayer.push(event); | ||
} | ||
return next(action); | ||
@@ -25,0 +23,0 @@ }; |
@@ -1,3 +0,7 @@ | ||
function emitGApageview(page = 'unknown page') { | ||
return { | ||
/** | ||
* If pageProps not specified, google analytics could still | ||
* pick up page's title and location. | ||
*/ | ||
function createGApageview(page = 'unknown page', pageProps = {}) { | ||
const defaultPageView = { | ||
event: 'REDUX_GTM_GA_EVENT', | ||
@@ -7,4 +11,5 @@ hitType: 'pageview', | ||
}; | ||
return Object.assign(defaultPageView, pageProps); | ||
} | ||
module.exports = emitGApageview; | ||
module.exports = createGApageview; |
const createMiddleware = require('./create-middleware'); | ||
const createMetaReducer = require('./create-meta-reducer'); | ||
// Extensions | ||
const logger = require('./extensions/logger'); | ||
const offlineWeb = require('./extensions/offline-web'); | ||
const offlineReactNative = require('./extensions/offline-react-native'); | ||
// Event Helpers | ||
const createGAevent = require('./event-helpers/create-ga-event'); | ||
const createGApageview = require('./event-helpers/create-ga-pageview'); | ||
const createGAuserTiming = require('./event-helpers/create-ga-user-timing'); | ||
@@ -10,6 +15,12 @@ module.exports = { | ||
createMetaReducer, | ||
Extensions: { | ||
logger, | ||
offlineWeb, | ||
offlineReactNative, | ||
}, | ||
EventHelpers: { | ||
createGAevent, | ||
createGApageview, | ||
createGAuserTiming, | ||
}, | ||
}; |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
30220
30
701
99
12