Track analytics for start time, channel and team switching (#1192)

* Track analytics for start time, channel and team switching

* Update analytics to include currentUserId and ip 0.0.0.0

* Include diagnostic id with every segment request

* feedback review
This commit is contained in:
enahum 2017-11-28 10:33:46 -03:00 committed by GitHub
parent e24988ca51
commit ac2b38cb3b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 133 additions and 36 deletions

View file

@ -9,6 +9,8 @@ import {getClientConfig, getDataRetentionPolicy, getLicenseConfig} from 'matterm
import {getPosts} from 'mattermost-redux/actions/posts';
import {getMyTeams, getMyTeamMembers, selectTeam} from 'mattermost-redux/actions/teams';
import {recordTime} from 'app/utils/segment';
import {
handleSelectChannel,
setChannelDisplayName,
@ -113,6 +115,14 @@ export function createPost(post) {
};
}
export function recordLoadTime(screenName, category) {
return async (dispatch, getState) => {
const {currentUserId} = getState().entities.users;
recordTime(screenName, category, currentUserId);
};
}
export default {
loadConfigAndLicense,
loadFromPushNotification,

View file

@ -15,6 +15,7 @@ import SafeAreaView from 'app/components/safe_area_view';
import Drawer from 'app/components/drawer';
import {alertErrorWithFallback} from 'app/utils/general';
import tracker from 'app/utils/time_tracker';
import ChannelsList from './channels_list';
import DrawerSwiper from './drawer_swipper';
@ -207,6 +208,7 @@ export default class ChannelDrawer extends Component {
markChannelAsViewed
} = actions;
tracker.channelSwitch = Date.now();
setChannelLoading(channel.id !== currentChannelId);
setChannelDisplayName(channel.display_name);

View file

@ -16,6 +16,7 @@ import MaterialIcon from 'react-native-vector-icons/MaterialIcons';
import FormattedText from 'app/components/formatted_text';
import {wrapWithPreventDoubleTap} from 'app/utils/tap';
import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
import tracker from 'app/utils/time_tracker';
import TeamsListItem from './teams_list_item';
@ -46,6 +47,7 @@ class TeamsList extends PureComponent {
requestAnimationFrame(() => {
const {actions, closeChannelDrawer, currentTeamId} = this.props;
if (teamId !== currentTeamId) {
tracker.teamSwitch = Date.now();
actions.handleTeamChange(teamId);
}

View file

@ -2,7 +2,6 @@
// See License.txt for license information.
import 'babel-polyfill';
import Analytics from 'analytics-react-native';
import Orientation from 'react-native-orientation';
import {Provider} from 'react-redux';
import {Navigation, NativeEventsReceiver} from 'react-native-navigation';
@ -10,7 +9,6 @@ import {IntlProvider} from 'react-intl';
import {
Alert,
AppState,
Dimensions,
InteractionManager,
NativeModules,
Platform
@ -52,6 +50,7 @@ import {registerScreens} from 'app/screens';
import configureStore from 'app/store';
import mattermostManaged from 'app/mattermost_managed';
import {deleteFileCache} from 'app/utils/file';
import {init as initAnalytics} from 'app/utils/segment';
import {
captureException,
initializeSentry,
@ -151,38 +150,7 @@ export default class Mattermost {
configureAnalytics = (config) => {
if (config && config.DiagnosticsEnabled === 'true' && config.DiagnosticId && Config.SegmentApiKey) {
if (!global.analytics) {
const {height, width} = Dimensions.get('window');
global.analytics = new Analytics(Config.SegmentApiKey);
global.analytics_context = {
app: {
version: DeviceInfo.getVersion(),
build: DeviceInfo.getBuildNumber()
},
device: {
dimensions: {
height,
width
},
isTablet: DeviceInfo.isTablet(),
os: DeviceInfo.getSystemVersion()
},
server: config.Version
};
global.analytics.identify({
userId: config.DiagnosticId,
context: global.analytics_context,
page: {
path: '',
referrer: '',
search: '',
title: '',
url: ''
},
anonymousId: '00000000000000000000000000'
});
}
initAnalytics(config);
} else {
global.analytics = null;
}
@ -418,6 +386,16 @@ export default class Mattermost {
this.orientationDidChange(orientation);
}
const {config} = state.entities.general;
if (config) {
this.configureAnalytics(config);
}
const {currentUserId} = state.entities.users;
if (currentUserId) {
Client4.setUserId(currentUserId);
}
const isNotActive = AppState.currentState !== 'active';
const notification = PushNotifications.getNotification();
if (notification) {

View file

@ -23,7 +23,7 @@ import {wrapWithPreventDoubleTap} from 'app/utils/tap';
import {makeStyleSheetFromTheme} from 'app/utils/theme';
import PostTextbox from 'app/components/post_textbox';
import networkConnectionListener from 'app/utils/network';
import tracker from 'app/utils/time_tracker';
import LocalConfig from 'assets/config';
import ChannelNavBar from './channel_nav_bar';
@ -39,6 +39,7 @@ class Channel extends PureComponent {
selectInitialChannel: PropTypes.func.isRequired,
initWebSocket: PropTypes.func.isRequired,
closeWebSocket: PropTypes.func.isRequired,
recordLoadTime: PropTypes.func.isRequired,
startPeriodicStatusUpdates: PropTypes.func.isRequired,
stopPeriodicStatusUpdates: PropTypes.func.isRequired
}).isRequired,
@ -60,12 +61,24 @@ class Channel extends PureComponent {
}
}
componentDidMount() {
if (tracker.initialLoad) {
this.props.actions.recordLoadTime('Start time', 'initialLoad');
}
}
componentWillReceiveProps(nextProps) {
if (nextProps.currentTeamId && this.props.currentTeamId !== nextProps.currentTeamId) {
this.loadChannels(nextProps.currentTeamId);
}
}
componentDidUpdate() {
if (tracker.teamSwitch) {
this.props.actions.recordLoadTime('Switch Team', 'teamSwitch');
}
}
componentWillUnmount() {
const {closeWebSocket, stopPeriodicStatusUpdates} = this.props.actions;

View file

@ -13,6 +13,7 @@ import {
import PostList from 'app/components/post_list';
import PostListRetry from 'app/components/post_list_retry';
import RetryBarIndicator from 'app/components/retry_bar_indicator';
import tracker from 'app/utils/time_tracker';
class ChannelPostList extends PureComponent {
static propTypes = {
@ -21,6 +22,7 @@ class ChannelPostList extends PureComponent {
loadThreadIfNecessary: PropTypes.func.isRequired,
increasePostVisibility: PropTypes.func.isRequired,
selectPost: PropTypes.func.isRequired,
recordLoadTime: PropTypes.func.isRequired,
refreshChannelWithRetry: PropTypes.func.isRequired
}).isRequired,
channelId: PropTypes.string.isRequired,
@ -64,6 +66,12 @@ class ChannelPostList extends PureComponent {
});
}
componentDidUpdate(prevProps) {
if (prevProps.channelId !== this.props.channelId && tracker.channelSwitch) {
this.props.actions.recordLoadTime('Switch Channel', 'channelSwitch');
}
}
getVisiblePostIds = (props) => {
return props.postIds.slice(0, props.postVisibility);
};

View file

@ -8,9 +8,11 @@ import {selectPost} from 'mattermost-redux/actions/posts';
import {getPostIdsInCurrentChannel} from 'mattermost-redux/selectors/entities/posts';
import {getCurrentChannelId, getMyCurrentChannelMembership, makeGetChannel} from 'mattermost-redux/selectors/entities/channels';
import {getCurrentUserId} from 'mattermost-redux/selectors/entities/users';
import {loadPostsIfNecessaryWithRetry, loadThreadIfNecessary, increasePostVisibility, refreshChannelWithRetry} from 'app/actions/views/channel';
import {getTheme} from 'mattermost-redux/selectors/entities/preferences';
import {loadPostsIfNecessaryWithRetry, loadThreadIfNecessary, increasePostVisibility, refreshChannelWithRetry} from 'app/actions/views/channel';
import {recordLoadTime} from 'app/actions/views/root';
import ChannelPostList from './channel_post_list';
function makeMapStateToProps() {
@ -41,6 +43,7 @@ function mapDispatchToProps(dispatch) {
loadThreadIfNecessary,
increasePostVisibility,
selectPost,
recordLoadTime,
refreshChannelWithRetry
}, dispatch)
};

View file

@ -16,6 +16,7 @@ import {
selectInitialChannel
} from 'app/actions/views/channel';
import {connection} from 'app/actions/device';
import {recordLoadTime} from 'app/actions/views/root';
import {selectFirstAvailableTeam} from 'app/actions/views/select_team';
import {getTheme} from 'mattermost-redux/selectors/entities/preferences';
@ -42,6 +43,7 @@ function mapDispatchToProps(dispatch) {
selectInitialChannel,
initWebSocket,
closeWebSocket,
recordLoadTime,
startPeriodicStatusUpdates,
stopPeriodicStatusUpdates
}, dispatch)

View file

@ -25,6 +25,7 @@ import StatusBar from 'app/components/status_bar';
import PushNotifications from 'app/push_notifications';
import {GlobalStyles} from 'app/styles';
import {wrapWithPreventDoubleTap} from 'app/utils/tap';
import tracker from 'app/utils/time_tracker';
import logo from 'assets/images/logo.png';
@ -77,6 +78,7 @@ class Login extends PureComponent {
goToLoadTeam = (expiresAt) => {
const {intl, navigator, theme} = this.props;
tracker.initialLoad = Date.now();
if (expiresAt) {
PushNotifications.localNotificationSchedule({

View file

@ -8,6 +8,7 @@ import {Client, Client4} from 'mattermost-redux/client';
import Loading from 'app/components/loading';
import {stripTrailingSlashes} from 'app/utils/url';
import tracker from 'app/utils/time_tracker';
export default class Root extends Component {
static propTypes = {
@ -37,6 +38,7 @@ export default class Root extends Component {
goToLoadTeam = () => {
const {navigator, theme} = this.props;
tracker.initialLoad = Date.now();
navigator.resetTo({
screen: 'LoadTeam',
title: '',

View file

@ -20,6 +20,7 @@ import Loading from 'app/components/loading';
import StatusBar from 'app/components/status_bar';
import PushNotifications from 'app/push_notifications';
import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme';
import tracker from 'app/utils/time_tracker';
const postMessageJS = "setTimeout(function() { postMessage(document.body.innerText, '*')});";
@ -81,6 +82,7 @@ class SSO extends PureComponent {
goToLoadTeam = (expiresAt) => {
const {intl, navigator, theme} = this.props;
tracker.initialLoad = Date.now();
if (expiresAt) {
PushNotifications.localNotificationSchedule({

65
app/utils/segment.js Normal file
View file

@ -0,0 +1,65 @@
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import Analytics from 'analytics-react-native';
import DeviceInfo from 'react-native-device-info';
import {Dimensions} from 'react-native';
import Config from 'assets/config';
import tracker from './time_tracker';
let diagnosticId;
export function init(config) {
if (!global.analytics) {
diagnosticId = config.DiagnosticId;
const {height, width} = Dimensions.get('window');
global.analytics = new Analytics(Config.SegmentApiKey, {flushAt: 1});
global.analytics_context = {
app: {
version: DeviceInfo.getVersion(),
build: DeviceInfo.getBuildNumber()
},
device: {
dimensions: {
height,
width
},
isTablet: DeviceInfo.isTablet(),
os: DeviceInfo.getSystemVersion()
},
ip: '0.0.0.0',
server: config.Version
};
global.analytics.identify({
userId: diagnosticId,
context: global.analytics_context,
page: {
path: '',
referrer: '',
search: '',
title: '',
url: ''
},
anonymousId: '00000000000000000000000000'
});
}
}
export function recordTime(screenName, category, userId) {
if (global.analytics) {
const startTime = tracker[category];
tracker[category] = 0;
global.analytics.screen({
userId: diagnosticId,
name: screenName,
context: global.analytics_context,
properties: {
actual_user_id: userId,
time: Date.now() - startTime
}
});
}
}

View file

@ -0,0 +1,8 @@
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
export default {
initialLoad: 0,
channelSwitch: 0,
teamSwitch: 0
};