From 61f227e123218ba76a7fdf7fc2ee89171c2bf16c Mon Sep 17 00:00:00 2001 From: Steve Klebanoff Date: Thu, 15 Nov 2018 10:43:42 -0800 Subject: feat(instant): Heap middleware and first tracking events --- .../src/components/zero_ex_instant_container.tsx | 5 +++ packages/instant/src/redux/analytics_middleware.ts | 38 ++++++++++++++++++++++ packages/instant/src/redux/store.ts | 7 ++-- packages/instant/src/util/analytics.ts | 35 ++++++++++++++++++++ packages/instant/src/util/heap.ts | 13 ++++---- 5 files changed, 88 insertions(+), 10 deletions(-) create mode 100644 packages/instant/src/redux/analytics_middleware.ts create mode 100644 packages/instant/src/util/analytics.ts (limited to 'packages') diff --git a/packages/instant/src/components/zero_ex_instant_container.tsx b/packages/instant/src/components/zero_ex_instant_container.tsx index 698bfef17..d977b0690 100644 --- a/packages/instant/src/components/zero_ex_instant_container.tsx +++ b/packages/instant/src/components/zero_ex_instant_container.tsx @@ -10,6 +10,7 @@ import { SelectedAssetInstantHeading } from '../containers/selected_asset_instan import { ColorOption } from '../style/theme'; import { zIndex } from '../style/z_index'; import { OrderProcessState, SlideAnimationState } from '../types'; +import { analytics } from '../util/analytics'; import { CSSReset } from './css_reset'; import { SlidingPanel } from './sliding_panel'; @@ -68,6 +69,10 @@ export class ZeroExInstantContainer extends React.Component<{}, ZeroExInstantCon ); } + // tslint:disable-next-line:prefer-function-over-method + public componentDidMount(): void { + analytics.track('Widget - Opened'); + } private readonly _handleSymbolClick = (): void => { this.setState({ tokenSelectionPanelAnimationState: 'slidIn', diff --git a/packages/instant/src/redux/analytics_middleware.ts b/packages/instant/src/redux/analytics_middleware.ts new file mode 100644 index 000000000..8dd674e94 --- /dev/null +++ b/packages/instant/src/redux/analytics_middleware.ts @@ -0,0 +1,38 @@ +import { ObjectMap } from '@0x/types'; +import * as _ from 'lodash'; +import { Middleware } from 'redux'; + +import { analytics } from '../util/analytics'; + +import { AccountState } from './../types'; +import { Action, ActionTypes } from './actions'; +import { State } from './reducer'; + +export const analyticsMiddleware: Middleware = store => next => middlewareAction => { + const prevState = store.getState() as State; + const nextAction = next(middlewareAction) as Action; + const nextState = store.getState() as State; + + const curAccount = nextState.providerState.account; + const prevAccount = prevState.providerState.account; + switch (nextAction.type) { + case ActionTypes.SET_ACCOUNT_STATE_READY: + if (curAccount.state === AccountState.Ready && !_.isEqual(curAccount, prevAccount)) { + const ethAddress = curAccount.address; + analytics.addUserProperties({ ethAddress }); + analytics.track('Wallet - Ready'); + } + break; + case ActionTypes.UPDATE_ACCOUNT_ETH_BALANCE: + if ( + curAccount.state === AccountState.Ready && + curAccount.ethBalanceInWei && + !_.isEqual(curAccount, prevAccount) + ) { + const ethBalanceInWei = curAccount.ethBalanceInWei.toString(); + analytics.addUserProperties({ ethBalanceInWei }); + } + } + + return nextAction; +}; diff --git a/packages/instant/src/redux/store.ts b/packages/instant/src/redux/store.ts index 20710765d..54dfe58c4 100644 --- a/packages/instant/src/redux/store.ts +++ b/packages/instant/src/redux/store.ts @@ -1,7 +1,8 @@ import * as _ from 'lodash'; -import { createStore, Store as ReduxStore } from 'redux'; -import { devToolsEnhancer } from 'redux-devtools-extension/developmentOnly'; +import { applyMiddleware, createStore, Store as ReduxStore } from 'redux'; +import { composeWithDevTools, devToolsEnhancer } from 'redux-devtools-extension/developmentOnly'; +import { analyticsMiddleware } from './analytics_middleware'; import { createReducer, State } from './reducer'; export type Store = ReduxStore; @@ -9,6 +10,6 @@ export type Store = ReduxStore; export const store = { create: (initialState: State): Store => { const reducer = createReducer(initialState); - return createStore(reducer, initialState, devToolsEnhancer({})); + return createStore(reducer, initialState, composeWithDevTools(applyMiddleware(analyticsMiddleware))); }, }; diff --git a/packages/instant/src/util/analytics.ts b/packages/instant/src/util/analytics.ts new file mode 100644 index 000000000..4de3e5eff --- /dev/null +++ b/packages/instant/src/util/analytics.ts @@ -0,0 +1,35 @@ +import { ObjectMap } from '@0x/types'; +import { logUtils } from '@0x/utils'; + +import { HeapAnalytics, heapUtil } from './heap'; + +export class Analytics { + public static init(): Analytics { + return new Analytics(); + } + public track(eventName: string, eventProperties?: ObjectMap): void { + console.log('HEAP: tracking', eventName, eventProperties); + this._evaluteHeapCall(heap => heap.track(eventName, eventProperties)); + } + public addUserProperties(properties: ObjectMap): void { + console.log('HEAP: adding user properties', properties); + this._evaluteHeapCall(heap => heap.addUserProperties(properties)); + } + public addEventProperties(properties: ObjectMap): void { + this._evaluteHeapCall(heap => heap.addEventProperties(properties)); + } + private _evaluteHeapCall(heapFunctionCall: (heap: HeapAnalytics) => void): void { + const curHeap = heapUtil.getHeap(); + if (curHeap) { + try { + heapFunctionCall(curHeap); + } catch (e) { + // We never want analytics to crash our React component + // TODO: error reporter here + logUtils.log('Analytics error', e); + } + } + } +} + +export const analytics = Analytics.init(); diff --git a/packages/instant/src/util/heap.ts b/packages/instant/src/util/heap.ts index 399ac3f6f..2f2c221b1 100644 --- a/packages/instant/src/util/heap.ts +++ b/packages/instant/src/util/heap.ts @@ -20,8 +20,13 @@ interface ModifiedWindow { const getWindow = (): ModifiedWindow => { return window as ModifiedWindow; }; -// Typescript-compatible version of https://docs.heapanalytics.com/docs/installation + const setupZeroExInstantHeap = () => { + const curWindow = getWindow(); + // Set property to specify that this is zeroEx's heap + curWindow.zeroExInstantLoadedHeap = true; + + // Typescript-compatible version of https://docs.heapanalytics.com/docs/installation /* tslint:disable */ ((window as any).heap = (window as any).heap || []), ((window as any).heap.load = function(e: any, t: any) { @@ -60,9 +65,6 @@ const setupZeroExInstantHeap = () => { (window as any).heap.load(HEAP_ANALYTICS_DEVELOPMENT_APP_ID); /* tslint:enable */ - const curWindow = getWindow(); - // Set property to specify that this is zeroEx's heap - curWindow.zeroExInstantLoadedHeap = true; return curWindow.heap as HeapAnalytics; }; @@ -71,17 +73,14 @@ export const heapUtil = { const curWindow = getWindow(); const hasOtherExistingHeapIntegration = curWindow.heap && !curWindow.zeroExInstantLoadedHeap; if (hasOtherExistingHeapIntegration) { - logUtils.log('Heap integration already exists'); return null; } const zeroExInstantHeapIntegration = curWindow.zeroExInstantLoadedHeap && curWindow.heap; if (zeroExInstantHeapIntegration) { - logUtils.log('Using existing 0x instant heap'); return zeroExInstantHeapIntegration; } - logUtils.log('Setting up heap'); return setupZeroExInstantHeap(); }, }; -- cgit v1.2.3