Skip to content

Commit

Permalink
Added mixpanel tracking for some basic events (#2462)
Browse files Browse the repository at this point in the history
* Added mixpanel tracking for bunch of events.

* Changed hitEnter action to pinSearch.

* Moved all the event tracking out of app-actions.js

* Addressed @foot's comment.

* Added more keypress events tracking.

* Disable 'r' keyboard shortcut when Resource View is disabled
  • Loading branch information
fbarl authored Apr 27, 2017
1 parent 1849f0f commit 65b9b48
Show file tree
Hide file tree
Showing 16 changed files with 291 additions and 96 deletions.
73 changes: 35 additions & 38 deletions client/app/scripts/actions/app-actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,7 @@ import debug from 'debug';

import ActionTypes from '../constants/action-types';
import { saveGraph } from '../utils/file-utils';
import { modulo } from '../utils/math-utils';
import { updateRoute } from '../utils/router-utils';
import { parseQuery } from '../utils/search-utils';
import {
bufferDeltaUpdate,
resumeUpdate,
Expand All @@ -26,12 +24,14 @@ import { storageSet } from '../utils/storage-utils';
import { loadTheme } from '../utils/contrast-utils';
import {
availableMetricTypesSelector,
selectedMetricTypeSelector,
nextPinnedMetricTypeSelector,
previousPinnedMetricTypeSelector,
pinnedMetricSelector,
} from '../selectors/node-metric';
import {
activeTopologyOptionsSelector,
isResourceViewModeSelector,
resourceViewAvailableSelector,
} from '../selectors/topology';
import {
GRAPH_VIEW_MODE,
Expand Down Expand Up @@ -161,15 +161,27 @@ export function unpinMetric() {
};
}

export function pinNextMetric(delta) {
export function pinNextMetric() {
return (dispatch, getState) => {
const state = getState();
const metricTypes = availableMetricTypesSelector(state);
const currentIndex = metricTypes.indexOf(selectedMetricTypeSelector(state));
const nextIndex = modulo(currentIndex + delta, metricTypes.count());
const nextMetricType = metricTypes.get(nextIndex);
const nextPinnedMetricType = nextPinnedMetricTypeSelector(getState());
dispatch(pinMetric(nextPinnedMetricType));
};
}

dispatch(pinMetric(nextMetricType));
export function pinPreviousMetric() {
return (dispatch, getState) => {
const previousPinnedMetricType = previousPinnedMetricTypeSelector(getState());
dispatch(pinMetric(previousPinnedMetricType));
};
}

export function pinSearch() {
return (dispatch, getState) => {
dispatch({
type: ActionTypes.PIN_SEARCH,
query: getState().get('searchQuery'),
});
updateRoute(getState);
};
}

Expand Down Expand Up @@ -310,18 +322,20 @@ export function setTableView() {

export function setResourceView() {
return (dispatch, getState) => {
dispatch({
type: ActionTypes.SET_VIEW_MODE,
viewMode: RESOURCE_VIEW_MODE,
});
// Pin the first metric if none of the visible ones is pinned.
const state = getState();
if (!pinnedMetricSelector(state)) {
const firstAvailableMetricType = availableMetricTypesSelector(state).first();
dispatch(pinMetric(firstAvailableMetricType));
if (resourceViewAvailableSelector(getState())) {
dispatch({
type: ActionTypes.SET_VIEW_MODE,
viewMode: RESOURCE_VIEW_MODE,
});
// Pin the first metric if none of the visible ones is pinned.
const state = getState();
if (!pinnedMetricSelector(state)) {
const firstAvailableMetricType = availableMetricTypesSelector(state).first();
dispatch(pinMetric(firstAvailableMetricType));
}
getResourceViewNodesSnapshot(getState, dispatch);
updateRoute(getState);
}
getResourceViewNodesSnapshot(getState, dispatch);
updateRoute(getState);
};
}

Expand Down Expand Up @@ -504,23 +518,6 @@ export function hitBackspace() {
};
}

export function hitEnter() {
return (dispatch, getState) => {
const state = getState();
// pin query based on current search field
if (state.get('searchFocused')) {
const query = state.get('searchQuery');
if (query && parseQuery(query)) {
dispatch({
type: ActionTypes.PIN_SEARCH,
query
});
updateRoute(getState);
}
}
};
}

export function hitEsc() {
return (dispatch, getState) => {
const state = getState();
Expand Down
10 changes: 9 additions & 1 deletion client/app/scripts/charts/node.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import { clickNode, enterNode, leaveNode } from '../actions/app-actions';
import { getNodeColor } from '../utils/color-utils';
import MatchedText from '../components/matched-text';
import MatchedResults from '../components/matched-results';
import { trackMixpanelEvent } from '../utils/tracking-utils';
import { GRAPH_VIEW_MODE } from '../constants/naming';
import { NODE_BASE_SIZE } from '../constants/styles';

import NodeShapeStack from './node-shape-stack';
Expand Down Expand Up @@ -141,6 +143,11 @@ class Node extends React.Component {

handleMouseClick(ev) {
ev.stopPropagation();
trackMixpanelEvent('scope.node.click', {
layout: GRAPH_VIEW_MODE,
topologyId: this.props.currentTopology.get('id'),
parentTopologyId: this.props.currentTopology.get('parentId'),
});
this.props.clickNode(this.props.id, this.props.label, this.shapeRef.getBoundingClientRect());
}

Expand All @@ -159,7 +166,8 @@ function mapStateToProps(state) {
return {
exportingGraph: state.get('exportingGraph'),
showingNetworks: state.get('showingNetworks'),
contrastMode: state.get('contrastMode')
currentTopology: state.get('currentTopology'),
contrastMode: state.get('contrastMode'),
};
}

Expand Down
9 changes: 8 additions & 1 deletion client/app/scripts/charts/nodes-grid.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
/* eslint react/jsx-no-bind: "off", no-multi-comp: "off" */

import React from 'react';
import { connect } from 'react-redux';
import { List as makeList, Map as makeMap } from 'immutable';

import NodeDetailsTable from '../components/node-details/node-details-table';
import { clickNode, sortOrderChanged } from '../actions/app-actions';
import { shownNodesSelector } from '../selectors/node-filters';
import { trackMixpanelEvent } from '../utils/tracking-utils';
import { TABLE_VIEW_MODE } from '../constants/naming';

import { canvasMarginsSelector, canvasHeightSelector } from '../selectors/canvas';
import { searchNodeMatchesSelector } from '../selectors/search';
Expand Down Expand Up @@ -89,6 +91,11 @@ class NodesGrid extends React.Component {
if (ev.target.className === 'node-details-table-node-link') {
return;
}
trackMixpanelEvent('scope.node.click', {
layout: TABLE_VIEW_MODE,
topologyId: this.props.currentTopology.get('id'),
parentTopologyId: this.props.currentTopology.get('parentId'),
});
this.props.clickNode(node.id, node.label, ev.target.getBoundingClientRect());
}

Expand Down
44 changes: 34 additions & 10 deletions client/app/scripts/components/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ import { getApiDetails, getTopologies } from '../utils/web-api-utils';
import {
focusSearch,
pinNextMetric,
pinPreviousMetric,
hitBackspace,
hitEnter,
hitEsc,
unpinMetric,
toggleHelp,
Expand All @@ -31,24 +31,26 @@ import ViewModeSelector from './view-mode-selector';
import NetworkSelector from './networks-selector';
import DebugToolbar, { showingDebugToolbar, toggleDebugToolbar } from './debug-toolbar';
import { getRouter, getUrlState } from '../utils/router-utils';
import { trackMixpanelEvent } from '../utils/tracking-utils';
import { availableNetworksSelector } from '../selectors/node-networks';
import {
activeTopologyOptionsSelector,
isResourceViewModeSelector,
isTableViewModeSelector,
isGraphViewModeSelector,
} from '../selectors/topology';
import {
BACKSPACE_KEY_CODE,
ESC_KEY_CODE,
} from '../constants/key-codes';


const BACKSPACE_KEY_CODE = 8;
const ENTER_KEY_CODE = 13;
const ESC_KEY_CODE = 27;
const keyPressLog = debug('scope:app-key-press');

class App extends React.Component {

class App extends React.Component {
constructor(props, context) {
super(props, context);

this.onKeyPress = this.onKeyPress.bind(this);
this.onKeyUp = this.onKeyUp.bind(this);
}
Expand Down Expand Up @@ -79,8 +81,6 @@ class App extends React.Component {
// don't get esc in onKeyPress
if (ev.keyCode === ESC_KEY_CODE) {
this.props.dispatch(hitEsc());
} else if (ev.keyCode === ENTER_KEY_CODE) {
this.props.dispatch(hitEnter());
} else if (ev.keyCode === BACKSPACE_KEY_CODE) {
this.props.dispatch(hitBackspace());
} else if (ev.code === 'KeyD' && ev.ctrlKey && !showingTerminal) {
Expand All @@ -100,16 +100,28 @@ class App extends React.Component {
keyPressLog('onKeyPress', 'keyCode', ev.keyCode, ev);
const char = String.fromCharCode(ev.charCode);
if (char === '<') {
dispatch(pinNextMetric(-1));
dispatch(pinPreviousMetric());
this.trackEvent('scope.metric.selector.pin.previous.keypress', {
metricType: this.props.pinnedMetricType
});
} else if (char === '>') {
dispatch(pinNextMetric(1));
dispatch(pinNextMetric());
this.trackEvent('scope.metric.selector.pin.next.keypress', {
metricType: this.props.pinnedMetricType
});
} else if (char === 'g') {
dispatch(setGraphView());
this.trackEvent('scope.layout.selector.keypress');
} else if (char === 't') {
dispatch(setTableView());
this.trackEvent('scope.layout.selector.keypress');
} else if (char === 'r') {
dispatch(setResourceView());
this.trackEvent('scope.layout.selector.keypress');
} else if (char === 'q') {
this.trackEvent('scope.metric.selector.unpin.keypress', {
metricType: this.props.pinnedMetricType
});
dispatch(unpinMetric());
} else if (char === '/') {
ev.preventDefault();
Expand All @@ -120,6 +132,15 @@ class App extends React.Component {
}
}

trackEvent(eventName, additionalProps = {}) {
trackMixpanelEvent(eventName, {
layout: this.props.topologyViewMode,
topologyId: this.props.currentTopology.get('id'),
parentTopologyId: this.props.currentTopology.get('parentId'),
...additionalProps,
});
}

render() {
const { isTableViewMode, isGraphViewMode, isResourceViewMode, showingDetails, showingHelp,
showingNetworkSelector, showingTroubleshootingMenu } = this.props;
Expand Down Expand Up @@ -164,9 +185,11 @@ class App extends React.Component {
function mapStateToProps(state) {
return {
activeTopologyOptions: activeTopologyOptionsSelector(state),
currentTopology: state.get('currentTopology'),
isResourceViewMode: isResourceViewModeSelector(state),
isTableViewMode: isTableViewModeSelector(state),
isGraphViewMode: isGraphViewModeSelector(state),
pinnedMetricType: state.get('pinnedMetricType'),
routeSet: state.get('routeSet'),
searchFocused: state.get('searchFocused'),
searchQuery: state.get('searchQuery'),
Expand All @@ -175,6 +198,7 @@ function mapStateToProps(state) {
showingTroubleshootingMenu: state.get('showingTroubleshootingMenu'),
showingNetworkSelector: availableNetworksSelector(state).count() > 0,
showingTerminal: state.get('controlPipes').size > 0,
topologyViewMode: state.get('topologyViewMode'),
urlState: getUrlState(state)
};
}
Expand Down
33 changes: 27 additions & 6 deletions client/app/scripts/components/footer.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,39 @@ import moment from 'moment';

import Plugins from './plugins';
import { getUpdateBufferSize } from '../utils/update-buffer-utils';
import { clickDownloadGraph, clickForceRelayout, clickPauseUpdate,
clickResumeUpdate, toggleHelp, toggleTroubleshootingMenu, setContrastMode } from '../actions/app-actions';
import { trackMixpanelEvent } from '../utils/tracking-utils';
import {
clickDownloadGraph,
clickForceRelayout,
clickPauseUpdate,
clickResumeUpdate,
toggleHelp,
toggleTroubleshootingMenu,
setContrastMode
} from '../actions/app-actions';


class Footer extends React.Component {
constructor(props, context) {
super(props, context);

this.handleContrastClick = this.handleContrastClick.bind(this);
this.handleRelayoutClick = this.handleRelayoutClick.bind(this);
}
handleContrastClick(e) {
e.preventDefault();

handleContrastClick(ev) {
ev.preventDefault();
this.props.setContrastMode(!this.props.contrastMode);
}

handleRelayoutClick(ev) {
ev.preventDefault();
trackMixpanelEvent('scope.layout.refresh.click', {
layout: this.props.topologyViewMode,
});
this.props.clickForceRelayout();
}

render() {
const { hostname, updatePausedAt, version, versionUpdate, contrastMode } = this.props;

Expand Down Expand Up @@ -75,7 +95,7 @@ class Footer extends React.Component {
</a>
<a
className="footer-icon"
onClick={this.props.clickForceRelayout}
onClick={this.handleRelayoutClick}
title={forceRelayoutTitle}>
<span className="fa fa-refresh" />
</a>
Expand Down Expand Up @@ -103,9 +123,10 @@ function mapStateToProps(state) {
return {
hostname: state.get('hostname'),
updatePausedAt: state.get('updatePausedAt'),
topologyViewMode: state.get('topologyViewMode'),
version: state.get('version'),
versionUpdate: state.get('versionUpdate'),
contrastMode: state.get('contrastMode')
contrastMode: state.get('contrastMode'),
};
}

Expand Down
Loading

0 comments on commit 65b9b48

Please sign in to comment.