From 97cc0a0d73dcacfefcdff785c802762e2a0a60d6 Mon Sep 17 00:00:00 2001 From: George Goldberg Date: Fri, 24 Feb 2017 17:34:21 +0000 Subject: PLT-5071: Client side component of Telemetry. (#5516) --- webapp/actions/analytics_actions.jsx | 12 -- webapp/actions/channel_actions.jsx | 4 + webapp/actions/diagnostics_actions.jsx | 8 + webapp/actions/global_actions.jsx | 9 +- webapp/actions/post_actions.jsx | 3 + webapp/client/browser_web_client.jsx | 35 +++-- webapp/client/client.jsx | 167 +++++++++++++-------- webapp/components/create_post.jsx | 1 + .../create_team/components/display_name.jsx | 8 +- .../components/create_team/components/team_url.jsx | 10 +- webapp/components/logged_in.jsx | 16 +- webapp/components/root.jsx | 21 ++- webapp/components/sidebar.jsx | 9 ++ webapp/components/sidebar_right.jsx | 5 + .../components/signup/components/signup_email.jsx | 11 +- .../components/signup/components/signup_ldap.jsx | 8 +- .../team_sidebar/components/team_button.jsx | 2 + .../components/tutorial/tutorial_intro_screens.jsx | 26 ++++ webapp/components/tutorial/tutorial_tip.jsx | 31 +++- .../user_settings/user_settings_general.jsx | 12 ++ webapp/components/webrtc/webrtc_controller.jsx | 5 + webapp/i18n/en.json | 3 - webapp/utils/constants.jsx | 3 +- 23 files changed, 280 insertions(+), 129 deletions(-) delete mode 100644 webapp/actions/analytics_actions.jsx create mode 100644 webapp/actions/diagnostics_actions.jsx (limited to 'webapp') diff --git a/webapp/actions/analytics_actions.jsx b/webapp/actions/analytics_actions.jsx deleted file mode 100644 index 924afdaed..000000000 --- a/webapp/actions/analytics_actions.jsx +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -import Client from 'client/web_client.jsx'; - -export function track(category, action, label, property, value) { - Client.track(category, action, label, property, value); -} - -export function trackPage() { - Client.trackPage(); -} diff --git a/webapp/actions/channel_actions.jsx b/webapp/actions/channel_actions.jsx index 08d8f1486..3528b4480 100644 --- a/webapp/actions/channel_actions.jsx +++ b/webapp/actions/channel_actions.jsx @@ -10,6 +10,7 @@ import * as ChannelUtils from 'utils/channel_utils.jsx'; import PreferenceStore from 'stores/preference_store.jsx'; import {loadProfilesAndTeamMembersForDMSidebar} from 'actions/user_actions.jsx'; +import {trackEvent} from 'actions/diagnostics_actions.jsx'; import Client from 'client/web_client.jsx'; import * as AsyncClient from 'utils/async_client.jsx'; @@ -171,6 +172,7 @@ export function openDirectChannelToUser(user, success, error) { const channel = ChannelStore.getByName(channelName); if (channel) { + trackEvent('api', 'api_channels_join_direct'); PreferenceStore.setPreference(Preferences.CATEGORY_DIRECT_CHANNEL_SHOW, user.id, 'true'); loadProfilesAndTeamMembersForDMSidebar(); @@ -224,10 +226,12 @@ export function openDirectChannelToUser(user, success, error) { } export function markFavorite(channelId) { + trackEvent('api', 'api_channels_favorited'); AsyncClient.savePreference(Preferences.CATEGORY_FAVORITE_CHANNEL, channelId, 'true'); } export function unmarkFavorite(channelId) { + trackEvent('api', 'api_channels_unfavorited'); const pref = { user_id: UserStore.getCurrentId(), category: Preferences.CATEGORY_FAVORITE_CHANNEL, diff --git a/webapp/actions/diagnostics_actions.jsx b/webapp/actions/diagnostics_actions.jsx new file mode 100644 index 000000000..b093d8e8b --- /dev/null +++ b/webapp/actions/diagnostics_actions.jsx @@ -0,0 +1,8 @@ +// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +import Client from 'client/web_client.jsx'; + +export function trackEvent(category, event, properties) { + Client.trackEvent(category, event, properties); +} diff --git a/webapp/actions/global_actions.jsx b/webapp/actions/global_actions.jsx index 5def48858..efff5076a 100644 --- a/webapp/actions/global_actions.jsx +++ b/webapp/actions/global_actions.jsx @@ -17,6 +17,7 @@ import {loadProfilesAndTeamMembersForDMSidebar} from 'actions/user_actions.jsx'; import {loadChannelsForCurrentUser} from 'actions/channel_actions.jsx'; import {stopPeriodicStatusUpdates} from 'actions/status_actions.jsx'; import * as WebsocketActions from 'actions/websocket_actions.jsx'; +import {trackEvent} from 'actions/diagnostics_actions.jsx'; import Constants from 'utils/constants.jsx'; const ActionTypes = Constants.ActionTypes; @@ -28,7 +29,6 @@ import * as Utils from 'utils/utils.jsx'; import en from 'i18n/en.json'; import * as I18n from 'i18n/i18n.jsx'; -import {trackPage} from 'actions/analytics_actions.jsx'; import {browserHistory} from 'react-router/es6'; export function emitChannelClickEvent(channel) { @@ -53,7 +53,6 @@ export function emitChannelClickEvent(channel) { AsyncClient.getChannelStats(chan.id, true); AsyncClient.viewChannel(chan.id, oldChannelId); loadPosts(chan.id); - trackPage(); }); // Mark previous and next channel as read @@ -94,6 +93,10 @@ export function emitInitialLoad(callback) { global.window.mm_config = data.client_cfg; global.window.mm_license = data.license_cfg; + if (global.window && global.window.analytics) { + global.window.analytics.identify(global.window.mm_config.DiagnosticId); + } + UserStore.setNoAccounts(data.no_accounts); if (data.user && data.user.id) { @@ -515,6 +518,8 @@ export function emitSearchMentionsEvent(user) { terms = termKeys.join(' '); } + trackEvent('api', 'api_posts_search_mention'); + AppDispatcher.handleServerAction({ type: ActionTypes.RECEIVED_SEARCH_TERM, term: terms, diff --git a/webapp/actions/post_actions.jsx b/webapp/actions/post_actions.jsx index 81ef73fc5..ad05a69db 100644 --- a/webapp/actions/post_actions.jsx +++ b/webapp/actions/post_actions.jsx @@ -9,6 +9,7 @@ import UserStore from 'stores/user_store.jsx'; import {loadStatusesForChannel} from 'actions/status_actions.jsx'; import {loadNewDMIfNeeded} from 'actions/user_actions.jsx'; +import {trackEvent} from 'actions/diagnostics_actions.jsx'; import Client from 'client/web_client.jsx'; import * as AsyncClient from 'utils/async_client.jsx'; @@ -64,10 +65,12 @@ export function handleNewPost(post, msg) { } export function flagPost(postId) { + trackEvent('api', 'api_posts_flagged'); AsyncClient.savePreference(Preferences.CATEGORY_FLAGGED_POST, postId, 'true'); } export function unflagPost(postId, success) { + trackEvent('api', 'api_posts_unflagged'); const pref = { user_id: UserStore.getCurrentId(), category: Preferences.CATEGORY_FLAGGED_POST, diff --git a/webapp/client/browser_web_client.jsx b/webapp/client/browser_web_client.jsx index f5d7f6c80..ef543ca34 100644 --- a/webapp/client/browser_web_client.jsx +++ b/webapp/client/browser_web_client.jsx @@ -4,6 +4,7 @@ import Client from './client.jsx'; import TeamStore from 'stores/team_store.jsx'; +import UserStore from 'stores/user_store.jsx'; import BrowserStore from 'stores/browser_store.jsx'; import * as GlobalActions from 'actions/global_actions.jsx'; @@ -29,16 +30,22 @@ class WebClientClass extends Client { onTeamStoreChanged() { this.setTeamId(TeamStore.getCurrentId()); } - - track(category, action, label, property, value) { - if (global.window && global.window.analytics) { - global.window.analytics.track(action, {category, label, property, value}); - } - } - - trackPage() { + trackEvent(category, event, props) { if (global.window && global.window.analytics) { - global.window.analytics.page(); + const properties = Object.assign({category, type: event, user_id: UserStore.getCurrentId()}, props); + const options = { + context: { + ip: '0.0.0.0' + }, + page: { + path: '', + referrer: '', + search: '', + title: '', + url: '' + } + }; + global.window.analytics.track('event', properties, options); } } @@ -74,7 +81,7 @@ class WebClientClass extends Client { password, token, (data) => { - this.track('api', 'api_users_login_success', '', 'login_id', loginId); + this.trackEvent('api', 'api_users_login_success'); BrowserStore.signalLogin(); if (success) { @@ -82,7 +89,7 @@ class WebClientClass extends Client { } }, (err) => { - this.track('api', 'api_users_login_fail', '', 'login_id', loginId); + this.trackEvent('api', 'api_users_login_fail'); if (error) { error(err); } @@ -96,7 +103,8 @@ class WebClientClass extends Client { password, token, (data) => { - this.track('api', 'api_users_login_success', '', 'login_id', loginId); + this.trackEvent('api', 'api_users_login_success'); + this.trackEvent('api', 'api_users_login_ldap_success'); BrowserStore.signalLogin(); if (success) { @@ -104,7 +112,8 @@ class WebClientClass extends Client { } }, (err) => { - this.track('api', 'api_users_login_fail', '', 'login_id', loginId); + this.trackEvent('api', 'api_users_login_fail'); + this.trackEvent('api', 'api_users_login_ldap_fail'); if (error) { error(err); } diff --git a/webapp/client/client.jsx b/webapp/client/client.jsx index 478abdcde..24eb7eabb 100644 --- a/webapp/client/client.jsx +++ b/webapp/client/client.jsx @@ -144,11 +144,7 @@ export default class Client { } } - track(category, action, label, property, value) { // eslint-disable-line no-unused-vars - // NO-OP for inherited classes to override - } - - trackPage() { + trackEvent(category, event, properties) { // eslint-disable-line no-unused-vars // NO-OP for inherited classes to override } @@ -197,8 +193,6 @@ export default class Client { console.error(e); // eslint-disable-line no-console } - this.track('api', 'api_weberror', methodName, 'message', msg); - this.handleError(err, res); if (errorCallback) { @@ -425,7 +419,7 @@ export default class Client { attach('license', license, license.name). end(this.handleResponse.bind(this, 'uploadLicenseFile', success, error)); - this.track('api', 'api_license_upload'); + this.trackEvent('api', 'api_license_upload'); } importSlack(fileData, success, error) { @@ -472,7 +466,7 @@ export default class Client { send(data). end(this.handleResponse.bind(this, 'adminResetPassword', success, error)); - this.track('api', 'api_admin_reset_password'); + this.trackEvent('api', 'api_admin_reset_password'); } ldapSyncNow(success, error) { @@ -523,7 +517,7 @@ export default class Client { send(team). end(this.handleResponse.bind(this, 'createTeam', success, error)); - this.track('api', 'api_users_create', '', 'email', team.name); + this.trackEvent('api', 'api_teams_create'); } updateTeam(team, success, error) { @@ -535,7 +529,7 @@ export default class Client { send(team). end(this.handleResponse.bind(this, 'updateTeam', success, error)); - this.track('api', 'api_teams_update_name'); + this.trackEvent('api', 'api_teams_update_name', {team_id: this.getTeamId()}); } getAllTeams(success, error) { @@ -635,7 +629,7 @@ export default class Client { send(data). end(this.handleResponse.bind(this, 'inviteMembers', success, error)); - this.track('api', 'api_teams_invite_members'); + this.trackEvent('api', 'api_teams_invite_members', {team_id: this.getTeamId()}); } addUserToTeam(teamId, userId, success, error) { @@ -652,7 +646,7 @@ export default class Client { send({user_id: userId}). end(this.handleResponse.bind(this, 'addUserToTeam', success, error)); - this.track('api', 'api_teams_invite_members'); + this.trackEvent('api', 'api_teams_invite_members', {team_id: nonEmptyTeamId}); } addUserToTeamFromInvite(data, hash, inviteId, success, error) { @@ -664,7 +658,7 @@ export default class Client { send({hash, data, invite_id: inviteId}). end(this.handleResponse.bind(this, 'addUserToTeam', success, error)); - this.track('api', 'api_teams_invite_members'); + this.trackEvent('api', 'api_teams_invite_members'); } removeUserFromTeam(teamId, userId, success, error) { @@ -681,7 +675,7 @@ export default class Client { send({user_id: userId}). end(this.handleResponse.bind(this, 'removeUserFromTeam', success, error)); - this.track('api', 'api_teams_remove_members'); + this.trackEvent('api', 'api_teams_remove_members', {team_id: nonEmptyTeamId}); } getInviteInfo(inviteId, success, error) { @@ -713,6 +707,14 @@ export default class Client { url += '&iid=' + encodeURIComponent(inviteId); } + if (emailHash) { + this.trackEvent('api', 'api_users_create_email'); + } else if (inviteId) { + this.trackEvent('api', 'api_users_create_link'); + } else { + this.trackEvent('api', 'api_users_create_spontaneous'); + } + request. post(url). set(this.defaultHeaders). @@ -721,7 +723,7 @@ export default class Client { send(user). end(this.handleResponse.bind(this, 'createUser', success, error)); - this.track('api', 'api_users_create', '', 'email', user.email); + this.trackEvent('api', 'api_users_create'); } updateUser(user, type, success, error) { @@ -734,9 +736,9 @@ export default class Client { end(this.handleResponse.bind(this, 'updateUser', success, error)); if (type) { - this.track('api', 'api_users_update_' + type); + this.trackEvent('api', 'api_users_update_' + type); } else { - this.track('api', 'api_users_update'); + this.trackEvent('api', 'api_users_update'); } } @@ -754,7 +756,7 @@ export default class Client { send(data). end(this.handleResponse.bind(this, 'updatePassword', success, error)); - this.track('api', 'api_users_newpassword'); + this.trackEvent('api', 'api_users_newpassword'); } updateUserNotifyProps(notifyProps, success, error) { @@ -766,7 +768,7 @@ export default class Client { send(notifyProps). end(this.handleResponse.bind(this, 'updateUserNotifyProps', success, error)); - this.track('api', 'api_users_update_notification_settings'); + this.trackEvent('api', 'api_users_update_notification_settings'); } updateUserRoles(userId, newRoles, success, error) { @@ -782,7 +784,7 @@ export default class Client { send(data). end(this.handleResponse.bind(this, 'updateUserRoles', success, error)); - this.track('api', 'api_users_update_user_roles'); + this.trackEvent('api', 'api_users_update_roles'); } updateTeamMemberRoles(teamId, userId, newRoles, success, error) { @@ -799,7 +801,7 @@ export default class Client { send(data). end(this.handleResponse.bind(this, 'updateTeamMemberRoles', success, error)); - this.track('api', 'api_teams_update_member_roles'); + this.trackEvent('api', 'api_teams_update_member_roles', {team_id: teamId}); } updateActive(userId, active, success, error) { @@ -815,7 +817,7 @@ export default class Client { send(data). end(this.handleResponse.bind(this, 'updateActive', success, error)); - this.track('api', 'api_users_update_active'); + this.trackEvent('api', 'api_users_update_active'); } sendPasswordReset(email, success, error) { @@ -830,7 +832,7 @@ export default class Client { send(data). end(this.handleResponse.bind(this, 'sendPasswordReset', success, error)); - this.track('api', 'api_users_send_password_reset'); + this.trackEvent('api', 'api_users_send_password_reset'); } resetPassword(code, newPassword, success, error) { @@ -846,7 +848,7 @@ export default class Client { send(data). end(this.handleResponse.bind(this, 'resetPassword', success, error)); - this.track('api', 'api_users_reset_password'); + this.trackEvent('api', 'api_users_reset_password'); } emailToOAuth(email, password, token, service, success, error) { @@ -858,7 +860,7 @@ export default class Client { send({password, email, token, service}). end(this.handleResponse.bind(this, 'emailToOAuth', success, error)); - this.track('api', 'api_users_email_to_oauth'); + this.trackEvent('api', 'api_users_email_to_oauth'); } oauthToEmail(email, password, success, error) { @@ -874,7 +876,7 @@ export default class Client { send(data). end(this.handleResponse.bind(this, 'oauthToEmail', success, error)); - this.track('api', 'api_users_oauth_to_email'); + this.trackEvent('api', 'api_users_oauth_to_email'); } emailToLdap(email, password, token, ldapId, ldapPassword, success, error) { @@ -893,7 +895,7 @@ export default class Client { send(data). end(this.handleResponse.bind(this, 'emailToLdap', success, error)); - this.track('api', 'api_users_email_to_ldap'); + this.trackEvent('api', 'api_users_email_to_ldap'); } ldapToEmail(email, emailPassword, token, ldapPassword, success, error) { @@ -911,7 +913,7 @@ export default class Client { send(data). end(this.handleResponse.bind(this, 'ldapToEmail', success, error)); - this.track('api', 'api_users_oauth_to_email'); + this.trackEvent('api', 'api_users_ldap_to_email'); } getInitialLoad(success, error) { @@ -962,19 +964,20 @@ export default class Client { login(loginId, password, mfaToken, success, error) { this.doLogin({login_id: loginId, password, token: mfaToken}, success, error); - this.track('api', 'api_users_login', '', 'login_id', loginId); + this.trackEvent('api', 'api_users_login'); } loginById(id, password, mfaToken, success, error) { this.doLogin({id, password, token: mfaToken}, success, error); - this.track('api', 'api_users_login', '', 'id', id); + this.trackEvent('api', 'api_users_login'); } loginByLdap(loginId, password, mfaToken, success, error) { this.doLogin({login_id: loginId, password, token: mfaToken, ldap_only: 'true'}, success, error); - this.track('api', 'api_users_login', '', 'login_id', loginId); + this.trackEvent('api', 'api_users_login'); + this.trackEvent('api', 'api_users_login_ldap'); } doLogin(outgoingData, success, error) { @@ -1014,7 +1017,7 @@ export default class Client { accept('application/json'). end(this.handleResponse.bind(this, 'logout', success, error)); - this.track('api', 'api_users_logout'); + this.trackEvent('api', 'api_users_logout'); } checkMfa(loginId, success, error) { @@ -1030,7 +1033,7 @@ export default class Client { send(data). end(this.handleResponse.bind(this, 'checkMfa', success, error)); - this.track('api', 'api_users_oauth_to_email'); + this.trackEvent('api', 'api_users_oauth_to_email'); } generateMfaSecret(success, error) { @@ -1086,6 +1089,8 @@ export default class Client { type('application/json'). accept('application/json'). end(this.handleResponse.bind(this, 'getProfiles', success, error)); + + this.trackEvent('api', 'api_profiles_get'); } getProfilesInTeam(teamId, offset, limit, success, error) { @@ -1095,6 +1100,8 @@ export default class Client { type('application/json'). accept('application/json'). end(this.handleResponse.bind(this, 'getProfilesInTeam', success, error)); + + this.trackEvent('api', 'api_profiles_get_in_team', {team_id: teamId}); } getProfilesInChannel(channelId, offset, limit, success, error) { @@ -1104,6 +1111,8 @@ export default class Client { type('application/json'). accept('application/json'). end(this.handleResponse.bind(this, 'getProfilesInChannel', success, error)); + + this.trackEvent('api', 'api_profiles_get_in_channel', {team_id: this.getTeamId(), channel_id: channelId}); } getProfilesNotInChannel(channelId, offset, limit, success, error) { @@ -1113,6 +1122,8 @@ export default class Client { type('application/json'). accept('application/json'). end(this.handleResponse.bind(this, 'getProfilesNotInChannel', success, error)); + + this.trackEvent('api', 'api_profiles_get_not_in_channel', {team_id: this.getTeamId(), channel_id: channelId}); } getProfilesByIds(userIds, success, error) { @@ -1123,6 +1134,8 @@ export default class Client { accept('application/json'). send(userIds). end(this.handleResponse.bind(this, 'getProfilesByIds', success, error)); + + this.trackEvent('api', 'api_profiles_get_by_ids'); } searchUsers(term, teamId, options, success, error) { @@ -1190,6 +1203,8 @@ export default class Client { accept('application/json'). send({channel_id: id}). end(this.handleResponse.bind(this, 'setActiveChannel', success, error)); + + this.trackEvent('api', 'api_channels_set_active', {channel_id: id}); } verifyEmail(uid, hid, success, error) { @@ -1234,7 +1249,7 @@ export default class Client { accept('application/json'). end(this.handleResponse.bind(this, 'uploadProfileImage', success, error)); - this.track('api', 'api_users_update_profile_picture'); + this.trackEvent('api', 'api_users_update_profile_picture'); } // Channel Routes Section @@ -1248,7 +1263,7 @@ export default class Client { send(channel). end(this.handleResponse.bind(this, 'createChannel', success, error)); - this.track('api', 'api_channels_create', channel.type, 'name', channel.name); + this.trackEvent('api', 'api_channels_create', {team_id: this.getTeamId()}); } createDirectChannel(userId, success, error) { @@ -1259,6 +1274,8 @@ export default class Client { accept('application/json'). send({user_id: userId}). end(this.handleResponse.bind(this, 'createDirectChannel', success, error)); + + this.trackEvent('api', 'api_channels_create_direct', {team_id: this.getTeamId()}); } updateChannel(channel, success, error) { @@ -1270,7 +1287,7 @@ export default class Client { send(channel). end(this.handleResponse.bind(this, 'updateChannel', success, error)); - this.track('api', 'api_channels_update'); + this.trackEvent('api', 'api_channels_update', {team_id: this.getTeamId(), channel_id: channel.id}); } updateChannelHeader(channelId, header, success, error) { @@ -1287,7 +1304,7 @@ export default class Client { send(data). end(this.handleResponse.bind(this, 'updateChannel', success, error)); - this.track('api', 'api_channels_header'); + this.trackEvent('api', 'api_channels_header', {team_id: this.getTeamId(), channel_id: channelId}); } updateChannelPurpose(channelId, purpose, success, error) { @@ -1304,7 +1321,7 @@ export default class Client { send(data). end(this.handleResponse.bind(this, 'updateChannelPurpose', success, error)); - this.track('api', 'api_channels_purpose'); + this.trackEvent('api', 'api_channels_purpose', {team_id: this.getTeamId(), channel_id: channelId}); } updateChannelNotifyProps(data, success, error) { @@ -1325,7 +1342,7 @@ export default class Client { accept('application/json'). end(this.handleResponse.bind(this, 'leaveChannel', success, error)); - this.track('api', 'api_channels_leave'); + this.trackEvent('api', 'api_channels_leave', {team_id: this.getTeamId(), channel_id: channelId}); } joinChannel(channelId, success, error) { @@ -1336,7 +1353,7 @@ export default class Client { accept('application/json'). end(this.handleResponse.bind(this, 'joinChannel', success, error)); - this.track('api', 'api_channels_join'); + this.trackEvent('api', 'api_channels_join', {team_id: this.getTeamId(), channel_id: channelId}); } joinChannelByName(name, success, error) { @@ -1347,7 +1364,7 @@ export default class Client { accept('application/json'). end(this.handleResponse.bind(this, 'joinChannelByName', success, error)); - this.track('api', 'api_channels_join_name'); + this.trackEvent('api', 'api_channels_join_name', {team_id: this.getTeamId()}); } deleteChannel(channelId, success, error) { @@ -1358,7 +1375,7 @@ export default class Client { accept('application/json'). end(this.handleResponse.bind(this, 'deleteChannel', success, error)); - this.track('api', 'api_channels_delete'); + this.trackEvent('api', 'api_channels_delete', {team_id: this.getTeamId(), channel_id: channelId}); } viewChannel(channelId, prevChannelId = '', time = 0, success, error) { @@ -1410,7 +1427,7 @@ export default class Client { accept('application/json'). end(this.handleResponse.bind(this, 'getChannel', success, error)); - this.track('api', 'api_channel_get'); + this.trackEvent('api', 'api_channel_get', {team_id: this.getTeamId(), channel_id: channelId}); } // SCHEDULED FOR DEPRECATION IN 3.7 - use getMoreChannelsPage instead @@ -1421,6 +1438,8 @@ export default class Client { type('application/json'). accept('application/json'). end(this.handleResponse.bind(this, 'getMoreChannels', success, error)); + + this.trackEvent('api', 'api_channels_more', {team_id: this.getTeamId()}); } getMoreChannelsPage(offset, limit, success, error) { @@ -1430,6 +1449,8 @@ export default class Client { type('application/json'). accept('application/json'). end(this.handleResponse.bind(this, 'getMoreChannelsPage', success, error)); + + this.trackEvent('api', 'api_channels_more_page', {team_id: this.getTeamId()}); } searchMoreChannels(term, success, error) { @@ -1524,7 +1545,7 @@ export default class Client { send({user_id: userId}). end(this.handleResponse.bind(this, 'addChannelMember', success, error)); - this.track('api', 'api_channels_add_member'); + this.trackEvent('api', 'api_channels_add_member', {team_id: this.getTeamId(), channel_id: channelId}); } removeChannelMember(channelId, userId, success, error) { @@ -1536,7 +1557,7 @@ export default class Client { send({user_id: userId}). end(this.handleResponse.bind(this, 'removeChannelMember', success, error)); - this.track('api', 'api_channels_remove_member'); + this.trackEvent('api', 'api_channels_remove_member', {team_id: this.getTeamId(), channel_id: channelId}); } updateChannelMemberRoles(channelId, userId, newRoles, success, error) { @@ -1574,7 +1595,7 @@ export default class Client { send({command, ...commandArgs}). end(this.handleResponse.bind(this, 'executeCommand', success, error)); - this.track('api', 'api_integrations_used'); + this.trackEvent('api', 'api_integrations_used'); } addCommand(command, success, error) { @@ -1586,7 +1607,7 @@ export default class Client { send(command). end(this.handleResponse.bind(this, 'addCommand', success, error)); - this.track('api', 'api_integrations_created'); + this.trackEvent('api', 'api_integrations_created'); } editCommand(command, success, error) { @@ -1598,7 +1619,7 @@ export default class Client { send(command). end(this.handleResponse.bind(this, 'editCommand', success, error)); - this.track('api', 'api_integrations_created'); + this.trackEvent('api', 'api_integrations_created'); } deleteCommand(commandId, success, error) { @@ -1610,7 +1631,7 @@ export default class Client { send({id: commandId}). end(this.handleResponse.bind(this, 'deleteCommand', success, error)); - this.track('api', 'api_integrations_deleted'); + this.trackEvent('api', 'api_integrations_deleted'); } listTeamCommands(success, error) { @@ -1643,14 +1664,10 @@ export default class Client { send({...post, create_at: 0}). end(this.handleResponse.bind(this, 'createPost', success, error)); - this.track('api', 'api_posts_create', post.channel_id, 'length', post.message.length); - - if (post.message.match(/\s#./)) { - this.track('api', 'api_posts_hashtag'); - } + this.trackEvent('api', 'api_posts_create', {team_id: this.getTeamId(), channel_id: post.channel_id}); - if (post.message.match(/\s@./)) { - this.track('api', 'api_posts_mentions'); + if (post.parent_id != null && post.parent_id !== '') { + this.trackEvent('api', 'api_posts_replied', {team_id: this.getTeamId(), channel_id: post.channel_id}); } } @@ -1663,6 +1680,8 @@ export default class Client { type('application/json'). accept('application/json'). end(this.handleResponse.bind(this, 'getPermalinkTmp', success, error)); + + this.trackEvent('api', 'api_channels_permalink', {team_id: this.getTeamId()}); } getPostById(postId, success, error) { @@ -1692,7 +1711,7 @@ export default class Client { send(post). end(this.handleResponse.bind(this, 'updatePost', success, error)); - this.track('api', 'api_posts_update'); + this.trackEvent('api', 'api_posts_update', {team_id: this.getTeamId(), channel_id: post.channel_id}); } deletePost(channelId, postId, success, error) { @@ -1703,7 +1722,7 @@ export default class Client { accept('application/json'). end(this.handleResponse.bind(this, 'deletePost', success, error)); - this.track('api', 'api_posts_delete'); + this.trackEvent('api', 'api_posts_delete', {team_id: this.getTeamId(), channel_id: channelId}); } search(terms, isOrSearch, success, error) { @@ -1719,7 +1738,7 @@ export default class Client { send(data). end(this.handleResponse.bind(this, 'search', success, error)); - this.track('api', 'api_posts_search'); + this.trackEvent('api', 'api_posts_search', {team_id: this.getTeamId()}); } getPostsPage(channelId, offset, limit, success, error) { @@ -1747,6 +1766,8 @@ export default class Client { type('application/json'). accept('application/json'). end(this.handleResponse.bind(this, 'getPostsBefore', success, error)); + + this.trackEvent('api', 'api_posts_get_before', {team_id: this.getTeamId(), channel_id: channelId}); } getPostsAfter(channelId, postId, offset, numPost, success, error) { @@ -1756,6 +1777,8 @@ export default class Client { type('application/json'). accept('application/json'). end(this.handleResponse.bind(this, 'getPostsAfter', success, error)); + + this.trackEvent('api', 'api_posts_get_after', {team_id: this.getTeamId(), channel_id: channelId}); } getFlaggedPosts(offset, limit, success, error) { @@ -1765,6 +1788,8 @@ export default class Client { type('application/json'). accept('application/json'). end(this.handleResponse.bind(this, 'getFlaggedPosts', success, error)); + + this.trackEvent('api', 'api_posts_get_flagged', {team_id: this.getTeamId()}); } getFileInfosForPost(channelId, postId, success, error) { @@ -1789,6 +1814,8 @@ export default class Client { // Routes for Files uploadFile(file, filename, channelId, clientId, success, error) { + this.trackEvent('api', 'api_files_upload', {team_id: this.getTeamId(), channel_id: channelId}); + return request. post(`${this.getTeamFilesRoute()}/upload`). set(this.defaultHeaders). @@ -1855,7 +1882,7 @@ export default class Client { send(app). end(this.handleResponse.bind(this, 'registerOAuthApp', success, error)); - this.track('api', 'api_apps_register'); + this.trackEvent('api', 'api_apps_register'); } allowOAuth2(responseType, clientId, redirectUri, state, scope, success, error) { @@ -1890,6 +1917,8 @@ export default class Client { accept('application/json'). send({id}). end(this.handleResponse.bind(this, 'deleteOAuthApp', success, error)); + + this.trackEvent('api', 'api_apps_delete'); } getOAuthAppInfo(id, success, error) { @@ -1943,7 +1972,7 @@ export default class Client { send(hook). end(this.handleResponse.bind(this, 'addIncomingHook', success, error)); - this.track('api', 'api_integrations_created'); + this.trackEvent('api', 'api_integrations_created', {team_id: this.getTeamId()}); } deleteIncomingHook(hookId, success, error) { @@ -1955,7 +1984,7 @@ export default class Client { send({id: hookId}). end(this.handleResponse.bind(this, 'deleteIncomingHook', success, error)); - this.track('api', 'api_integrations_deleted'); + this.trackEvent('api', 'api_integrations_deleted', {team_id: this.getTeamId()}); } listIncomingHooks(success, error) { @@ -1976,7 +2005,7 @@ export default class Client { send(hook). end(this.handleResponse.bind(this, 'addOutgoingHook', success, error)); - this.track('api', 'api_integrations_created'); + this.trackEvent('api', 'api_integrations_created', {team_id: this.getTeamId()}); } deleteOutgoingHook(hookId, success, error) { @@ -1988,7 +2017,7 @@ export default class Client { send({id: hookId}). end(this.handleResponse.bind(this, 'deleteOutgoingHook', success, error)); - this.track('api', 'api_integrations_deleted'); + this.trackEvent('api', 'api_integrations_deleted', {team_id: this.getTeamId()}); } listOutgoingHooks(success, error) { @@ -2069,6 +2098,8 @@ export default class Client { attach('image', image, image.name). field('emoji', JSON.stringify(emoji)). end(this.handleResponse.bind(this, 'addEmoji', success, error)); + + this.trackEvent('api', 'api_emoji_custom_add'); } deleteEmoji(id, success, error) { @@ -2078,6 +2109,8 @@ export default class Client { accept('application/json'). send({id}). end(this.handleResponse.bind(this, 'deleteEmoji', success, error)); + + this.trackEvent('api', 'api_emoji_custom_delete'); } getCustomEmojiImageUrl(id) { @@ -2127,6 +2160,8 @@ export default class Client { accept('application/json'). send(reaction). end(this.handleResponse.bind(this, 'saveReaction', success, error)); + + this.trackEvent('api', 'api_reactions_save', {team_id: this.getTeamId(), channel_id: channelId, post_id: reaction.post_id}); } deleteReaction(channelId, reaction, success, error) { @@ -2136,6 +2171,8 @@ export default class Client { accept('application/json'). send(reaction). end(this.handleResponse.bind(this, 'deleteReaction', success, error)); + + this.trackEvent('api', 'api_reactions_delete', {team_id: this.getTeamId(), channel_id: channelId, post_id: reaction.post_id}); } listReactions(channelId, postId, success, error) { diff --git a/webapp/components/create_post.jsx b/webapp/components/create_post.jsx index 581ed16a0..7bdcf6888 100644 --- a/webapp/components/create_post.jsx +++ b/webapp/components/create_post.jsx @@ -497,6 +497,7 @@ export default class CreatePost extends React.Component { placement='top' screens={screens} overlayClass='tip-overlay--chat' + diagnosticsTag='tutorial_tip_1_sending_messages' /> ); } diff --git a/webapp/components/create_team/components/display_name.jsx b/webapp/components/create_team/components/display_name.jsx index 29077bd24..aeb8afbb9 100644 --- a/webapp/components/create_team/components/display_name.jsx +++ b/webapp/components/create_team/components/display_name.jsx @@ -1,7 +1,7 @@ // Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. // See License.txt for license information. -import {track} from 'actions/analytics_actions.jsx'; +import {trackEvent} from 'actions/diagnostics_actions.jsx'; import Constants from 'utils/constants.jsx'; import {cleanUpUrlable} from 'utils/url.jsx'; @@ -22,6 +22,10 @@ export default class TeamSignupDisplayNamePage extends React.Component { this.state = {}; } + componentDidMount() { + trackEvent('signup', 'signup_team_01_name'); + } + submitNext(e) { e.preventDefault(); @@ -60,8 +64,6 @@ export default class TeamSignupDisplayNamePage extends React.Component { } render() { - track('signup', 'signup_team_02_name'); - var nameError = null; var nameDivClass = 'form-group'; if (this.state.nameError) { diff --git a/webapp/components/create_team/components/team_url.jsx b/webapp/components/create_team/components/team_url.jsx index 2ab143d7f..c8a60cdf9 100644 --- a/webapp/components/create_team/components/team_url.jsx +++ b/webapp/components/create_team/components/team_url.jsx @@ -2,7 +2,7 @@ // See License.txt for license information. import {checkIfTeamExists, createTeam} from 'actions/team_actions.jsx'; -import {track} from 'actions/analytics_actions.jsx'; +import {trackEvent} from 'actions/diagnostics_actions.jsx'; import Constants from 'utils/constants.jsx'; import * as URL from 'utils/url.jsx'; @@ -27,6 +27,10 @@ export default class TeamUrl extends React.Component { }; } + componentDidMount() { + trackEvent('signup', 'signup_team_02_url'); + } + submitBack(e) { e.preventDefault(); this.props.state.wizard = 'display_name'; @@ -106,7 +110,7 @@ export default class TeamUrl extends React.Component { createTeam(teamSignup.team, () => { - track('signup', 'signup_team_08_complete'); + trackEvent('signup', 'signup_team_03_complete'); }, (err) => { this.setState({nameError: err.message}); @@ -126,8 +130,6 @@ export default class TeamUrl extends React.Component { } render() { - track('signup', 'signup_team_03_url'); - let nameError = null; let nameDivClass = 'form-group'; if (this.state.nameError) { diff --git a/webapp/components/logged_in.jsx b/webapp/components/logged_in.jsx index 9282e74ca..8d7a00653 100644 --- a/webapp/components/logged_in.jsx +++ b/webapp/components/logged_in.jsx @@ -26,7 +26,6 @@ export default class LoggedIn extends React.Component { super(params); this.onUserChanged = this.onUserChanged.bind(this); - this.setupUser = this.setupUser.bind(this); // Because current CSS requires the root tag to have specific stuff $('#root').attr('class', 'channel-view'); @@ -45,9 +44,7 @@ export default class LoggedIn extends React.Component { user: UserStore.getCurrentUser() }; - if (this.state.user) { - this.setupUser(this.state.user); - } else { + if (!this.state.user) { GlobalActions.emitUserLoggedOutEvent('/login'); } } @@ -56,21 +53,10 @@ export default class LoggedIn extends React.Component { return this.state.user != null; } - setupUser(user) { - // Update segment indentify - if (global.window.mm_config.SegmentDeveloperKey != null && global.window.mm_config.SegmentDeveloperKey !== '') { - global.window.analytics.identify(user.id, { - createdAt: user.create_at, - id: user.id - }); - } - } - onUserChanged() { // Grab the current user const user = UserStore.getCurrentUser(); if (!Utils.areObjectsEqual(this.state.user, user)) { - this.setupUser(user); this.setState({ user }); diff --git a/webapp/components/root.jsx b/webapp/components/root.jsx index 465df5d79..4e7c19452 100644 --- a/webapp/components/root.jsx +++ b/webapp/components/root.jsx @@ -14,6 +14,7 @@ import $ from 'jquery'; import {browserHistory} from 'react-router/es6'; import UserStore from 'stores/user_store.jsx'; import BrowserStore from 'stores/browser_store.jsx'; +import Constants from 'utils/constants.jsx'; export default class Root extends React.Component { constructor(props) { @@ -26,12 +27,26 @@ export default class Root extends React.Component { this.localizationChanged = this.localizationChanged.bind(this); this.redirectIfNecessary = this.redirectIfNecessary.bind(this); + const segmentKey = Constants.DIAGNOSTICS_SEGMENT_KEY; + // Ya.... /*eslint-disable */ - if (window.mm_config.SegmentDeveloperKey != null && window.mm_config.SegmentDeveloperKey !== "") { + if (segmentKey != null && segmentKey !== '' && window.mm_config.DiagnosticsEnabled) { !function(){var analytics=global.window.analytics=global.window.analytics||[];if(!analytics.initialize)if(analytics.invoked)window.console&&console.error&&console.error("Segment snippet included twice.");else{analytics.invoked=!0;analytics.methods=["trackSubmit","trackClick","trackLink","trackForm","pageview","identify","group","track","ready","alias","page","once","off","on"];analytics.factory=function(t){return function(){var e=Array.prototype.slice.call(arguments);e.unshift(t);analytics.push(e);return analytics}};for(var t=0;t ); } @@ -566,6 +570,7 @@ export default class Sidebar extends React.Component { {icon} {channel.display_name} @@ -577,6 +582,10 @@ export default class Sidebar extends React.Component { ); } + trackChannelSelectedEvent() { + trackEvent('ui', 'ui_channel_selected'); + } + render() { // Check if we have all info needed to render if (this.state.currentTeam == null || this.state.currentUser == null) { diff --git a/webapp/components/sidebar_right.jsx b/webapp/components/sidebar_right.jsx index da7ff818d..fb120337a 100644 --- a/webapp/components/sidebar_right.jsx +++ b/webapp/components/sidebar_right.jsx @@ -12,6 +12,7 @@ import PreferenceStore from 'stores/preference_store.jsx'; import WebrtcStore from 'stores/webrtc_store.jsx'; import {getFlaggedPosts} from 'actions/post_actions.jsx'; +import {trackEvent} from 'actions/diagnostics_actions.jsx'; import * as Utils from 'utils/utils.jsx'; import Constants from 'utils/constants.jsx'; @@ -71,6 +72,10 @@ export default class SidebarRight extends React.Component { const isOpen = this.state.searchVisible || this.state.postRightVisible; const willOpen = nextState.searchVisible || nextState.postRightVisible; + if (!isOpen && willOpen) { + trackEvent('ui', 'ui_rhs_opened'); + } + if (isOpen !== willOpen) { PostStore.jumpPostsViewSidebarOpen(); } diff --git a/webapp/components/signup/components/signup_email.jsx b/webapp/components/signup/components/signup_email.jsx index 8325c9f56..cf4ff0a95 100644 --- a/webapp/components/signup/components/signup_email.jsx +++ b/webapp/components/signup/components/signup_email.jsx @@ -4,8 +4,9 @@ import LoadingScreen from 'components/loading_screen.jsx'; import * as GlobalActions from 'actions/global_actions.jsx'; +import {trackEvent} from 'actions/diagnostics_actions.jsx'; + import BrowserStore from 'stores/browser_store.jsx'; -import {track} from 'actions/analytics_actions.jsx'; import {getInviteInfo} from 'actions/team_actions.jsx'; import {loginById, createUserWithInvite} from 'actions/user_actions.jsx'; @@ -37,6 +38,10 @@ export default class SignupEmail extends React.Component { this.state = this.getInviteInfo(); } + componentDidMount() { + trackEvent('signup', 'signup_user_01_welcome'); + } + getInviteInfo() { let data = this.props.location.query.d; let hash = this.props.location.query.h; @@ -117,7 +122,7 @@ export default class SignupEmail extends React.Component { } handleSignupSuccess(user, data) { - track('signup', 'signup_user_02_complete'); + trackEvent('signup', 'signup_user_02_complete'); loginById( data.id, user.password, @@ -401,8 +406,6 @@ export default class SignupEmail extends React.Component { } render() { - track('signup', 'signup_user_01_welcome'); - let serverError = null; if (this.state.serverError) { serverError = ( diff --git a/webapp/components/signup/components/signup_ldap.jsx b/webapp/components/signup/components/signup_ldap.jsx index bf98d0461..0e02d5db7 100644 --- a/webapp/components/signup/components/signup_ldap.jsx +++ b/webapp/components/signup/components/signup_ldap.jsx @@ -4,9 +4,9 @@ import FormError from 'components/form_error.jsx'; import * as GlobalActions from 'actions/global_actions.jsx'; -import {track} from 'actions/analytics_actions.jsx'; import {addUserToTeamFromInvite} from 'actions/team_actions.jsx'; import {webLoginByLdap} from 'actions/user_actions.jsx'; +import {trackEvent} from 'actions/diagnostics_actions.jsx'; import * as Utils from 'utils/utils.jsx'; @@ -39,6 +39,10 @@ export default class SignupLdap extends React.Component { }); } + componentDidMount() { + trackEvent('signup', 'signup_user_01_welcome'); + } + handleLdapIdChange(e) { this.setState({ ldapId: e.target.value @@ -107,8 +111,6 @@ export default class SignupLdap extends React.Component { } render() { - track('signup', 'signup_user_01_welcome'); - let ldapIdPlaceholder; if (global.window.mm_config.LdapLoginFieldName) { ldapIdPlaceholder = global.window.mm_config.LdapLoginFieldName; diff --git a/webapp/components/team_sidebar/components/team_button.jsx b/webapp/components/team_sidebar/components/team_button.jsx index 6fbf8aef9..894567538 100644 --- a/webapp/components/team_sidebar/components/team_button.jsx +++ b/webapp/components/team_sidebar/components/team_button.jsx @@ -3,6 +3,7 @@ import Constants from 'utils/constants.jsx'; +import {trackEvent} from 'actions/diagnostics_actions.jsx'; import {switchTeams} from 'actions/team_actions.jsx'; import React from 'react'; @@ -19,6 +20,7 @@ export default class TeamButton extends React.Component { handleSwitch(e) { e.preventDefault(); + trackEvent('ui', 'ui_team_sidebar_switch_team'); switchTeams(this.props.url); } diff --git a/webapp/components/tutorial/tutorial_intro_screens.jsx b/webapp/components/tutorial/tutorial_intro_screens.jsx index a0b6118d3..c266191b8 100644 --- a/webapp/components/tutorial/tutorial_intro_screens.jsx +++ b/webapp/components/tutorial/tutorial_intro_screens.jsx @@ -6,6 +6,7 @@ import TeamStore from 'stores/team_store.jsx'; import PreferenceStore from 'stores/preference_store.jsx'; import * as AsyncClient from 'utils/async_client.jsx'; import * as GlobalActions from 'actions/global_actions.jsx'; +import {trackEvent} from 'actions/diagnostics_actions.jsx'; import {Constants, Preferences} from 'utils/constants.jsx'; @@ -31,10 +32,23 @@ export default class TutorialIntroScreens extends React.Component { this.handleNext = this.handleNext.bind(this); this.createScreen = this.createScreen.bind(this); this.createCircles = this.createCircles.bind(this); + this.skipTutorial = this.skipTutorial.bind(this); this.state = {currentScreen: 0}; } handleNext() { + switch (this.state.currentScreen) { + case 0: + trackEvent('tutorial', 'tutorial_screen_1_welcome_to_mattermost_next'); + break; + case 1: + trackEvent('tutorial', 'tutorial_screen_2_how_mattermost_works_next'); + break; + case 2: + trackEvent('tutorial', 'tutorial_screen_3_youre_all_set_next'); + break; + } + if (this.state.currentScreen < 2) { this.setState({currentScreen: this.state.currentScreen + 1}); return; @@ -53,6 +67,18 @@ export default class TutorialIntroScreens extends React.Component { skipTutorial(e) { e.preventDefault(); + switch (this.state.currentScreen) { + case 0: + trackEvent('tutorial', 'tutorial_screen_1_welcome_to_mattermost_skip'); + break; + case 1: + trackEvent('tutorial', 'tutorial_screen_2_how_mattermost_works_skip'); + break; + case 2: + trackEvent('tutorial', 'tutorial_screen_3_youre_all_set_skip'); + break; + } + AsyncClient.savePreference( Preferences.TUTORIAL_STEP, UserStore.getCurrentId(), diff --git a/webapp/components/tutorial/tutorial_tip.jsx b/webapp/components/tutorial/tutorial_tip.jsx index 7b613fe51..e78668b10 100644 --- a/webapp/components/tutorial/tutorial_tip.jsx +++ b/webapp/components/tutorial/tutorial_tip.jsx @@ -4,6 +4,7 @@ import UserStore from 'stores/user_store.jsx'; import PreferenceStore from 'stores/preference_store.jsx'; import * as AsyncClient from 'utils/async_client.jsx'; +import {trackEvent} from 'actions/diagnostics_actions.jsx'; import Constants from 'utils/constants.jsx'; @@ -25,6 +26,7 @@ export default class TutorialTip extends React.Component { this.handleNext = this.handleNext.bind(this); this.toggle = this.toggle.bind(this); + this.skipTutorial = this.skipTutorial.bind(this); this.state = {currentScreen: 0, show: false}; } @@ -48,6 +50,22 @@ export default class TutorialTip extends React.Component { return; } + if (this.props.diagnosticsTag) { + let tag = this.props.diagnosticsTag; + + if (this.props.screens.length > 1) { + tag += '_' + (this.state.currentScreen + 1).toString(); + } + + if (this.state.currentScreen === this.props.screens.length - 1) { + tag += '_okay'; + } else { + tag += '_next'; + } + + trackEvent('tutorial', tag); + } + this.closeRightSidebar(); this.toggle(); } @@ -62,6 +80,15 @@ export default class TutorialTip extends React.Component { skipTutorial(e) { e.preventDefault(); + if (this.props.diagnosticsTag) { + let tag = this.props.diagnosticsTag; + if (this.props.screens.length > 1) { + tag += '_' + this.state.currentScreen; + } + tag += '_skip'; + trackEvent('tutorial', tag); + } + AsyncClient.savePreference( Preferences.TUTORIAL_STEP, UserStore.getCurrentId(), @@ -174,7 +201,8 @@ TutorialTip.defaultProps = { TutorialTip.propTypes = { screens: React.PropTypes.array.isRequired, placement: React.PropTypes.string.isRequired, - overlayClass: React.PropTypes.string + overlayClass: React.PropTypes.string, + diagnosticsTag: React.PropTypes.string }; export function createMenuTip(toggleFunc, onBottom) { @@ -207,6 +235,7 @@ export function createMenuTip(toggleFunc, onBottom) { placement={placement} screens={screens} overlayClass={'tip-overlay--header--' + arrow} + diagnosticsTag='tutorial_tip_3_main_menu' /> ); diff --git a/webapp/components/user_settings/user_settings_general.jsx b/webapp/components/user_settings/user_settings_general.jsx index d79507511..f9c624aa0 100644 --- a/webapp/components/user_settings/user_settings_general.jsx +++ b/webapp/components/user_settings/user_settings_general.jsx @@ -16,6 +16,7 @@ import * as Utils from 'utils/utils.jsx'; import {intlShape, injectIntl, defineMessages, FormattedMessage, FormattedHTMLMessage, FormattedDate} from 'react-intl'; import {updateUser, uploadProfileImage} from 'actions/user_actions.jsx'; +import {trackEvent} from 'actions/diagnostics_actions.jsx'; const holders = defineMessages({ usernameReserved: { @@ -127,6 +128,8 @@ class UserSettingsGeneralTab extends React.Component { user.username = username; + trackEvent('settings', 'user_settings_update', {field: 'username'}); + this.submitUser(user, Constants.UserUpdateEvents.USERNAME, false); } @@ -143,6 +146,8 @@ class UserSettingsGeneralTab extends React.Component { user.nickname = nickname; + trackEvent('settings', 'user_settings_update', {field: 'username'}); + this.submitUser(user, Constants.UserUpdateEvents.NICKNAME, false); } @@ -161,6 +166,8 @@ class UserSettingsGeneralTab extends React.Component { user.first_name = firstName; user.last_name = lastName; + trackEvent('settings', 'user_settings_update', {field: 'fullname'}); + this.submitUser(user, Constants.UserUpdateEvents.FULLNAME, false); } @@ -189,6 +196,7 @@ class UserSettingsGeneralTab extends React.Component { } user.email = email; + trackEvent('settings', 'user_settings_update', {field: 'email'}); this.submitUser(user, Constants.UserUpdateEvents.EMAIL, true); } @@ -228,6 +236,8 @@ class UserSettingsGeneralTab extends React.Component { return; } + trackEvent('settings', 'user_settings_update', {field: 'picture'}); + const {formatMessage} = this.props.intl; const picture = this.state.picture; @@ -268,6 +278,8 @@ class UserSettingsGeneralTab extends React.Component { user.position = position; + trackEvent('settings', 'user_settings_update', {field: 'position'}); + this.submitUser(user, Constants.UserUpdateEvents.Position, false); } diff --git a/webapp/components/webrtc/webrtc_controller.jsx b/webapp/components/webrtc/webrtc_controller.jsx index b8d3d4db6..0fab0d2e7 100644 --- a/webapp/components/webrtc/webrtc_controller.jsx +++ b/webapp/components/webrtc/webrtc_controller.jsx @@ -13,6 +13,7 @@ import SearchBox from '../search_bar.jsx'; import WebrtcHeader from './components/webrtc_header.jsx'; import ConnectingScreen from 'components/loading_screen.jsx'; +import {trackEvent} from 'actions/diagnostics_actions.jsx'; import * as WebrtcActions from 'actions/webrtc_actions.jsx'; import * as Utils from 'utils/utils.jsx'; @@ -600,6 +601,8 @@ export default class WebrtcController extends React.Component { } onFailed() { + trackEvent('api', 'api_users_webrtc_failed'); + this.setState({ isCalling: false, isAnswering: false, @@ -733,6 +736,7 @@ export default class WebrtcController extends React.Component { } doAnswer(jsep) { + trackEvent('api', 'api_users_webrtc_start'); this.videocall.createAnswer({ jsep, stream: this.localMedia, @@ -747,6 +751,7 @@ export default class WebrtcController extends React.Component { } doHangup(error, manual) { + trackEvent('api', 'api_users_webrtc_end'); if (this.videocall && this.state.callInProgress) { this.videocall.send({message: {request: 'hangup'}}); this.videocall.hangup(); diff --git a/webapp/i18n/en.json b/webapp/i18n/en.json index eeb29cc39..46b9c113f 100644 --- a/webapp/i18n/en.json +++ b/webapp/i18n/en.json @@ -718,9 +718,6 @@ "admin.service.readTimeoutDescription": "Maximum time allowed from when the connection is accepted to when the request body is fully read.", "admin.service.securityDesc": "When true, System Administrators are notified by email if a relevant security fix alert has been announced in the last 12 hours. Requires email to be enabled.", "admin.service.securityTitle": "Enable Security Alerts: ", - "admin.service.segmentDescription": "Segment.com is an online service that can be optionally used to track detailed system statistics. You can obtain a key by signing-up for a free account at Segment.com.", - "admin.service.segmentExample": "E.g.: \"g3fgGOXJAQ43QV7rAh6iwQCkV4cA1Gs\"", - "admin.service.segmentTitle": "Segment Write Key:", "admin.service.sessionCache": "Session Cache (minutes):", "admin.service.sessionCacheDesc": "The number of minutes to cache a session in memory.", "admin.service.sessionDaysEx": "E.g.: \"30\"", diff --git a/webapp/utils/constants.jsx b/webapp/utils/constants.jsx index ff26a0c7f..130e116a9 100644 --- a/webapp/utils/constants.jsx +++ b/webapp/utils/constants.jsx @@ -896,7 +896,8 @@ export const Constants = { STATUS_INTERVAL: 60000, AUTOCOMPLETE_TIMEOUT: 100, ANIMATION_TIMEOUT: 1000, - SEARCH_TIMEOUT_MILLISECONDS: 100 + SEARCH_TIMEOUT_MILLISECONDS: 100, + DIAGNOSTICS_SEGMENT_KEY: 'fwb7VPbFeQ7SKp3wHm1RzFUuXZudqVok' }; export default Constants; -- cgit v1.2.3-1-g7c22