Skip to content

Commit

Permalink
[website] Add event tracking (#119)
Browse files Browse the repository at this point in the history
* Add event tracking to website demo app
  • Loading branch information
benshope authored and heshan0131 committed Jun 21, 2018
1 parent f73be69 commit 0822a5e
Show file tree
Hide file tree
Showing 12 changed files with 162 additions and 31 deletions.
File renamed without changes.
8 changes: 7 additions & 1 deletion src/actions/ui-state-actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ const {
START_EXPORTING_IMAGE,
TOGGLE_LEGEND,
TOGGLE_MODAL,
SHOW_EXPORT_DROPDOWN,
HIDE_EXPORT_DROPDOWN,
TOGGLE_SIDE_PANEL,
TOGGLE_MAP_CONTROL
} = ActionTypes;
Expand All @@ -43,6 +45,8 @@ const {
const [
toggleSidePanel,
toggleModal,
showExportDropdown,
hideExportDropdown,
toggleMapControl,
openDeleteModal,
// export image
Expand All @@ -61,6 +65,8 @@ const [
] = [
TOGGLE_SIDE_PANEL,
TOGGLE_MODAL,
SHOW_EXPORT_DROPDOWN,
HIDE_EXPORT_DROPDOWN,
TOGGLE_MAP_CONTROL,
OPEN_DELETE_MODAL,
SET_RATIO,
Expand All @@ -77,7 +83,7 @@ const [
].map(a => createAction(a));

export {
toggleSidePanel, toggleModal, toggleMapControl, openDeleteModal, setExportConfig, setExportData,
toggleSidePanel, toggleModal, showExportDropdown, hideExportDropdown, toggleMapControl, openDeleteModal, setExportConfig, setExportData,
setRatio, setResolution, toggleLegend, startExportingImage, setExportImageDataUri, cleanupExportImage,
setExportSelectedDataset, setExportDataType, setExportFiltered
};
13 changes: 6 additions & 7 deletions src/components/common/logo.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,10 @@
// THE SOFTWARE.

import React from 'react';
import PropTypes from 'prop-types';
import styled from 'styled-components';
import {KEPLER_GL_NAME, KEPLER_GL_VERSION} from 'constants/default-settings';

const defaultProps = {
appName: KEPLER_GL_NAME,
version: KEPLER_GL_VERSION
};

const LogoTitle = styled.div`
display: inline-block;
margin-left: 6px;
Expand Down Expand Up @@ -68,7 +64,7 @@ const LogoSvg = () => (
</svg>
);

const KeplerGlLogo = ({appName, version}) => (
const KeplerGlLogo = ({appName=KEPLER_GL_NAME, version=KEPLER_GL_VERSION}) => (
<LogoWrapper className="side-panel-logo">
<LogoSvgWrapper>
<LogoSvg />
Expand All @@ -80,6 +76,9 @@ const KeplerGlLogo = ({appName, version}) => (
</LogoWrapper>
);

KeplerGlLogo.defaultProps = defaultProps;
KeplerGlLogo.defaultProps = {
appName: PropTypes.string,
version: PropTypes.string
};

export default KeplerGlLogo;
3 changes: 3 additions & 0 deletions src/components/side-panel.js
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,9 @@ export default function SidePanelFactory(PanelHeader) {
version={version}
onExportImage={this._onExportImage}
onExportData={this._onExportData}
visibleDropdown={uiState.visibleDropdown}
showExportDropdown={uiStateActions.showExportDropdown}
hideExportDropdown={uiStateActions.hideExportDropdown}
onExportConfig={this._onExportConfig}
onSaveMap={this.props.onSaveMap}
/>
Expand Down
31 changes: 13 additions & 18 deletions src/components/side-panel/panel-header.js
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,10 @@ const defaultActionItems = [
function PanelHeaderFactory() {
return class PanelHeader extends Component {
static propTypes = {
appName: PropTypes.string,
version: PropTypes.string,
uiState: PropTypes.object,
uiStateActions: PropTypes.object,
logoComponent: PropTypes.oneOfType([PropTypes.element, PropTypes.func]),
actionItems: PropTypes.arrayOf(PropTypes.any)
};
Expand All @@ -215,18 +219,6 @@ function PanelHeaderFactory() {
actionItems: defaultActionItems
};

state = {
dropdown: null
};

showDropdown = id => {
this.setState({dropdown: id});
};

hideDropdown = () => {
this.setState({dropdown: null});
};

render() {
const {
appName,
Expand All @@ -235,30 +227,33 @@ function PanelHeaderFactory() {
onSaveMap,
onExportImage,
onExportData,
onExportConfig
onExportConfig,
visibleDropdown,
showExportDropdown,
hideExportDropdown
} = this.props;

return (
<StyledPanelHeader className="side-panel__panel-header">
<StyledPanelHeaderTop className="side-panel__panel-header__top">
<this.props.logoComponent appName={appName} version={version}/>
<StyledPanelTopActions>
{actionItems.map(item => (
<div className="side-panel__panel-header__right"
key={item.id} style={{position: 'relative'}}>
key={item.id} style={{position: 'relative'}}>
<PanelAction
item={item}
onClick={() => {
if (item.dropdownComponent) {
this.showDropdown(item.id);
showExportDropdown(item.id);
}

item.onClick();
}}
/>
{item.dropdownComponent ? (
<item.dropdownComponent
onClose={this.hideDropdown}
show={this.state.dropdown === item.id}
onClose={hideExportDropdown}
show={visibleDropdown === item.id}
onSaveMap={onSaveMap}
onExportData={onExportData}
onExportImage={onExportImage}
Expand Down
2 changes: 2 additions & 0 deletions src/constants/action-types.js
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,8 @@ const ActionTypes = keyMirror({
// uiState
TOGGLE_SIDE_PANEL: null,
TOGGLE_MODAL: null,
SHOW_EXPORT_DROPDOWN: null,
HIDE_EXPORT_DROPDOWN: null,
OPEN_DELETE_MODAL: null,
TOGGLE_MAP_CONTROL: null,

Expand Down
1 change: 1 addition & 0 deletions src/constants/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export {
DIMENSIONS,
ALL_FIELD_TYPES,
FIELD_OPTS,
FILTER_TYPES,
GEOJSON_FIELDS,
ICON_FIELDS,
TRIP_POINT_FIELDS,
Expand Down
10 changes: 10 additions & 0 deletions src/reducers/ui-state-updaters.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,16 @@ export const toggleModalUpdater = (state, {payload: id}) => ({
currentModal: id
});

export const showExportDropdownUpdater = (state, {payload: id}) => ({
...state,
visibleDropdown: id
});

export const hideExportDropdownUpdater = (state, {payload}) => ({
...state,
visibleDropdown: null
});

export const toggleMapControlUpdater = (state, {payload: panelId}) => ({
...state,
mapControls: {
Expand Down
6 changes: 5 additions & 1 deletion src/reducers/ui-state.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ import {
import {
openDeleteModalUpdater,
toggleModalUpdater,
showExportDropdownUpdater,
hideExportDropdownUpdater,
toggleSidePanelUpdater,
toggleMapControlUpdater,

Expand Down Expand Up @@ -90,7 +92,7 @@ export const INITIAL_UI_STATE = {
activeSidePanel: DEFAULT_ACTIVE_SIDE_PANEL,
currentModal: DEFAULT_MODAL,
datasetKeyToRemove: null,

visibleDropdown: null,
// export image modal ui
exportImage: DEFAULT_EXPORT_IMAGE,
// export data modal ui
Expand All @@ -102,6 +104,8 @@ export const INITIAL_UI_STATE = {
const actionHandler = {
[ActionTypes.TOGGLE_SIDE_PANEL]: toggleSidePanelUpdater,
[ActionTypes.TOGGLE_MODAL]: toggleModalUpdater,
[ActionTypes.SHOW_EXPORT_DROPDOWN]: showExportDropdownUpdater,
[ActionTypes.HIDE_EXPORT_DROPDOWN]: hideExportDropdownUpdater,
[ActionTypes.OPEN_DELETE_MODAL]: openDeleteModalUpdater,
[ActionTypes.TOGGLE_MAP_CONTROL]: toggleMapControlUpdater,

Expand Down
7 changes: 4 additions & 3 deletions test/browser/components/injector-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

import React, {Component} from 'react';
import React from 'react';
import test from 'tape';
import {mount, shallow} from 'enzyme';
import {mount} from 'enzyme';
import configureStore from 'redux-mock-store';
import {Provider} from 'react-redux';
import sinon from 'sinon';
Expand Down Expand Up @@ -108,7 +108,8 @@ test('Components -> injector -> missing deps', t => {

test('Components -> injector -> wrong factory type', t => {
const spy = sinon.spy(Console, 'error');
const myCustomHeaderFactory = () => () => (
// const spy = sinon.spy(Console, 'error');
const myCustomHeaderFactory = Name => () => (
<div className="my-test-header-2">
<Name />smoothie
</div>
Expand Down
108 changes: 108 additions & 0 deletions website/src/reducers/analytics.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
// Copyright (c) 2018 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

// This file sends actions on the demo app to Google analytics

import {ActionTypes} from 'kepler.gl/actions';
import {LOCATION_CHANGE} from 'react-router-redux';
import window from 'global/window';
import {ALL_FIELD_TYPES} from 'kepler.gl/constants';

const getPayload = ({payload}) => payload;

// Hack, because we don't have a way to access next state
const getFilterType = (store, idx, value) => {
const {visState} = store.getState().demo.keplerGl.map;
const filter = visState.filters[idx];
const {dataId} = filter;
if (!dataId) {
return {};
}
const {fields} = visState.datasets[dataId];
const field = fields.find(f => f.name === value);

// Hack
switch (field.type) {
case ALL_FIELD_TYPES.real:
case ALL_FIELD_TYPES.integer:
return 'range';

case ALL_FIELD_TYPES.boolean:
return 'select';

case ALL_FIELD_TYPES.string:
case ALL_FIELD_TYPES.date:
return 'multiSelect';

case ALL_FIELD_TYPES.timestamp:
return 'timeRange';

default:
return null;
}
};

const trackingInformation = {
[ActionTypes.LOAD_FILES]: ({files}) =>
files.map(({size, type}) => ({size, type})),
[ActionTypes.LAYER_TYPE_CHANGE]: ({newType}) => ({
newType
}),
[ActionTypes.MAP_STYLE_CHANGE]: getPayload,
[ActionTypes.TOGGLE_MODAL]: getPayload,
[ActionTypes.ADD_LAYER]: (payload, store) => ({
total: store.getState().demo.keplerGl.map.visState.layers.length + 1
}),
[ActionTypes.ADD_FILTER]: (payload, store) => ({
total: store.getState().demo.keplerGl.map.visState.filters.length + 1
}),
[ActionTypes.SET_FILTER]: ({prop, idx, value}, store) => {
if (prop !== 'name') {
return {};
}
return {
filterType: getFilterType(store, idx, value)
};
},
[ActionTypes.INTERACTION_CONFIG_CHANGE]: ({config: {id, enabled}}) => ({
[id]: enabled
}),
[LOCATION_CHANGE]: x => x
};

const EXCLUDED_ACTIONS = [ActionTypes.LAYER_HOVER, ActionTypes.UPDATE_MAP];

const analyticsMiddleware = store => next => action => {
if (window.gtag && !EXCLUDED_ACTIONS.includes(action.type)) {
// eslint-disable-next-line no-undef
window.gtag('event', 'action', {
event_category: action.type,
event_label: trackingInformation[action.type]
? JSON.stringify(
trackingInformation[action.type](action.payload, store)
)
: undefined
});
}

return next(action);
};

export default analyticsMiddleware;
4 changes: 3 additions & 1 deletion website/src/reducers/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import {routerMiddleware} from 'react-router-redux';
import {hashHistory} from 'react-router';
import appReducer from './app';
import demoReducer from '../../../examples/demo-app/src/reducers';
import analyticsMiddleware from './analytics';

const initialState = {};
const reducers = {
Expand All @@ -39,7 +40,8 @@ const combinedReducers = combineReducers(reducers);
export const middlewares = [
taskMiddleware,
thunk,
routerMiddleware(hashHistory)
routerMiddleware(hashHistory),
analyticsMiddleware
];

export const enhancers = [applyMiddleware(...middlewares)];
Expand Down

0 comments on commit 0822a5e

Please sign in to comment.