From 1daeaff367073364568f8ab9ed7563b716c5c294 Mon Sep 17 00:00:00 2001 From: Ben Gotow Date: Thu, 3 Sep 2015 19:30:08 -0700 Subject: [PATCH] fix(mixpanel): Critical fix for mixpanel users all having the same distinct_id alias Summary: Mixpanel API misuse makes me sad. Test Plan: No tests, but easy to check with Mixpanel.com Reviewers: dillon, evan Reviewed By: dillon, evan Differential Revision: https://phab.nylas.com/D1978 --- package.json | 11 ++- src/flux/stores/analytics-store.coffee | 114 ++++++++++++------------- 2 files changed, 58 insertions(+), 67 deletions(-) diff --git a/package.json b/package.json index ad8a0ecb0..82bfaf84d 100644 --- a/package.json +++ b/package.json @@ -13,17 +13,17 @@ }, "electronVersion": "0.29.2", "dependencies": { - "asar": "^0.5.0", "6to5-core": "^3.5", + "asar": "^0.5.0", "async": "^0.9", "atom-keymap": "^5.1", "aws-sdk": "2.1.28", "bluebird": "^2.9", + "classnames": "1.2.1", "clear-cut": "0.4.0", "coffee-react": "^2.0.0", "coffee-script": "1.9.0", "coffeestack": "^1.1", - "classnames": "1.2.1", "color": "^0.7.3", "delegato": "^1", "emissary": "^1.3.1", @@ -41,8 +41,8 @@ "juice": "^1.4", "less-cache": "0.21", "marked": "^0.3", + "mixpanel": "0.0.20", "mkdirp": "^0.5", - "mixpanel": "^0.0.20", "moment": "^2.8", "moment-timezone": "^0.3", "nslog": "^2.0.0", @@ -52,8 +52,8 @@ "q": "^1.0.1", "raven": "0.7.2", "react": "^0.13.2", - "react-hot-api": "0.4.5", "react-atom-fork": "^0.11.5", + "react-hot-api": "0.4.5", "reactionary-atom-fork": "^1.0.0", "reflux": "0.1.13", "request": "^2.53", @@ -81,8 +81,7 @@ "proxyquire": "git+https://github.com/bengotow/proxyquire", "jasmine-react-helpers": "^0.2" }, - "packageDependencies": { - }, + "packageDependencies": {}, "private": true, "scripts": { "preinstall": "node -e 'process.exit(0)'" diff --git a/src/flux/stores/analytics-store.coffee b/src/flux/stores/analytics-store.coffee index 10acadb8b..22fab6429 100644 --- a/src/flux/stores/analytics-store.coffee +++ b/src/flux/stores/analytics-store.coffee @@ -7,87 +7,79 @@ AccountStore = require './account-store' printToConsole = false +# We white list actions to track. +# +# The Key is the action and the value is the callback function for that +# action. That callback function should return the data we pass along to +# our analytics service based on the sending data. +# +# IMPORTANT: Be VERY careful about what private data we send to our +# analytics service!! +# +# Only completely anonymous data essential to future metrics or +# debugging may be sent. +coreWindowActions = + showDeveloperConsole: -> {} + composeReply: -> ['Compose Draft', {'type': 'reply'}] + composeForward: -> ['Compose Draft', {'type': 'forward'}] + composeReplyAll: -> ['Compose Draft', {'type': 'reply-all'}] + composeNewBlankDraft: -> ['Compose Draft', {'type': 'blank'}] + composePopoutDraft: -> ['Popout Draft', {}] + sendDraft: -> ['Send Draft', {}] + destroyDraft: -> ['Delete Draft', {}] + searchQueryCommitted: (query) -> ['Commit Search Query', {}] + attachFile: -> ['Attach File', {}] + attachFilePath: -> ['Attach File Path', {}] + fetchAndOpenFile: -> ['Download and Open File', {}] + fetchAndSaveFile: -> ['Download and Save File', {}] + abortFetchFile: -> ['Cancel Download', {}] + fileDownloaded: -> ['Download Complete', {}] + module.exports = AnalyticsStore = Reflux.createStore - init: -> - @listenAndTrack = (dispatcher=Actions) => (callback, action) => - @listenTo dispatcher[action], (args...) => - @track(action, callback(args...)) - @analytics = Mixpanel.init("625e2300ef07cb4eb70a69b3638ca579") + init: -> + @analytics = Mixpanel.init("9a2137b80c098b3d594e39b776ebe085") @listenTo AccountStore, => @identify() @identify() - @_listenToCoreActions() + @trackActions(Actions, coreWindowActions) + @trackTasks() - @_setupGlobalPackageActions() + trackActions: (dispatcher, listeners) -> + _.each listeners, (mappingFunction, actionName) => + @listenTo dispatcher[actionName], (args...) => + [eventName, eventArgs] = mappingFunction(args...) + @track(eventName, eventArgs) - addPackageActions: (listeners, dispatcher=Actions) -> - _.each listeners, @listenAndTrack(dispatcher) + trackTasks: -> + @listenTo Actions.queueTask, (task) => + return unless task + eventName = task.constructor.name + eventArgs = {} + eventArgs['item_count'] = task.messages.length if task.messages? + eventArgs['item_count'] = task.threads.length if task.threads? + @track(eventName, eventArgs) - addGlobalPackageActions: (listeners) -> - @_globalPackageActions = _.extend @_globalPackageActions, listeners - - _setupGlobalPackageActions: -> - @_globalPackageActions = {} - @listenTo Actions.sendToAllWindows, (actionData={}) => - return unless atom.isMainWindow() - callback = @_globalPackageActions[actionData.action] - if callback? - @track(actionData.action, callback(actionData)) - - # We white list actions to track. - # - # The Key is the action and the value is the callback function for that - # action. That callback function should return the data we pass along to - # our analytics service based on the sending data. - # - # IMPORTANT: Be VERY careful about what private data we send to our - # analytics service!! - # - # Only completely anonymous data essential to future metrics or - # debugging may be sent. - coreWindowActions: -> - showDeveloperConsole: -> {} - composeReply: ({threadId, messageId}) -> {threadId, messageId} - composeForward: ({threadId, messageId}) -> {threadId, messageId} - composeReplyAll: ({threadId, messageId}) -> {threadId, messageId} - composePopoutDraft: (draftClientId) -> {draftClientId: draftClientId} - composeNewBlankDraft: -> {} - sendDraft: (draftClientId) -> {draftClientId} - destroyDraft: (draftClientId) -> {draftClientId} - searchQueryCommitted: (query) -> {} - fetchAndOpenFile: -> {} - fetchAndSaveFile: -> {} - abortFetchFile: -> {} - fileDownloaded: -> {} - - coreGlobalActions: -> - fileAborted: (uploadData={}) -> {fileSize: uploadData.fileSize} - fileUploaded: (uploadData={}) -> {fileSize: uploadData.fileSize} - sendDraftSuccess: ({draftClientId}) -> {draftClientId} - - track: (action, data={}) -> + track: (eventName, eventArgs={}) -> _.defer => # send to the analytics service - @analytics.track(action, _.extend(data, { - accountId: AccountStore.current()?.id + @analytics.track(eventName, _.extend(eventArgs, { + platform: process.platform + version: atom.getVersion() distinct_id: AccountStore.current()?.id + accountId: AccountStore.current()?.id })) # send to the logs that we ship to LogStash - console.debug(printToConsole, {action, data}) + console.debug(printToConsole, {eventName, eventArgs}) identify: -> account = AccountStore.current() if account - @analytics.alias("distinct_id", account.id) - @analytics.people.set account.id, + @analytics.people.set(account.id, { "$email": account.me().email "$first_name": account.me().firstName() "$last_name": account.me().lastName() "accountId": account.id - - _listenToCoreActions: -> - _.each @coreWindowActions(), @listenAndTrack() - _.each @coreGlobalActions(), @listenAndTrack() if atom.isMainWindow() + })