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:
parent
e24988ca51
commit
ac2b38cb3b
13 changed files with 133 additions and 36 deletions
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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({
|
||||
|
|
|
|||
|
|
@ -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: '',
|
||||
|
|
|
|||
|
|
@ -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
65
app/utils/segment.js
Normal 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
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
8
app/utils/time_tracker.js
Normal file
8
app/utils/time_tracker.js
Normal 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
|
||||
};
|
||||
Loading…
Reference in a new issue