mirror of
https://github.com/Foundry376/Mailspring.git
synced 2025-09-21 20:15:52 +08:00
fix(analytics): improved analytics
This commit is contained in:
parent
b69f150e6a
commit
c4753197ee
16 changed files with 81 additions and 32 deletions
|
@ -267,6 +267,7 @@ class TemplateStore extends NylasStore {
|
|||
const signature = sigIndex > -1 ? draftContents.slice(sigIndex) : '';
|
||||
|
||||
const draftHtml = QuotedHTMLTransformer.appendQuotedHTML(templateBody + signature, session.draft().body);
|
||||
Actions.recordUserEvent("Email Template Inserted")
|
||||
session.changes.add({body: draftHtml});
|
||||
}
|
||||
});
|
||||
|
|
|
@ -70,6 +70,10 @@ class TranslateButton extends React.Component {
|
|||
const draftHtml = this.props.draft.body;
|
||||
const text = QuotedHTMLTransformer.removeQuotedHTML(draftHtml);
|
||||
|
||||
Actions.recordUserEvent("Email Translated", {
|
||||
language: YandexLanguages[lang],
|
||||
})
|
||||
|
||||
const query = {
|
||||
key: YandexTranslationKey,
|
||||
lang: YandexLanguages[lang],
|
||||
|
|
|
@ -46,9 +46,11 @@ class ComposerWithWindowProps extends React.Component {
|
|||
|
||||
_onDraftReady = () => {
|
||||
this.refs.composer.focus().then(() => {
|
||||
const totalTime = NylasEnv.perf.stop("Popout Draft");
|
||||
const timeInMs = NylasEnv.perf.stop("Popout Draft");
|
||||
if (!NylasEnv.inDevMode() && !NylasEnv.inSpecMode()) {
|
||||
Actions.recordUserEvent("Popout Composer Time", {totalTime})
|
||||
if (timeInMs && timeInMs <= 4000) {
|
||||
Actions.recordUserEvent("Composer Popout Timed", {timeInMs})
|
||||
}
|
||||
}
|
||||
NylasEnv.displayWindow();
|
||||
|
||||
|
|
|
@ -132,6 +132,7 @@ class ViewOnGithubButton extends React.Component
|
|||
# also queue a {Task} to eventually perform a mutating API POST or PUT
|
||||
# request.
|
||||
_openLink: =>
|
||||
Actions.recordUserEvent("Github Thread Opened", {pageUrl: @state.link})
|
||||
shell.openExternal(@state.link) if @state.link
|
||||
|
||||
module.exports = ViewOnGithubButton
|
||||
|
|
|
@ -9,6 +9,7 @@ export default class AccountErrorHeader extends React.Component {
|
|||
constructor() {
|
||||
super();
|
||||
this.state = this.getStateFromStores();
|
||||
this.upgradeLabel = ""
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
|
@ -62,7 +63,12 @@ export default class AccountErrorHeader extends React.Component {
|
|||
|
||||
_onUpgrade = () => {
|
||||
this.setState({buildingUpgradeURL: true});
|
||||
IdentityStore.fetchSingleSignOnURL('/payment').then((url) => {
|
||||
const isSubscription = this.state.subscriptionState === IdentityStore.State.Lapsed
|
||||
const utm = {
|
||||
source: "UpgradeBanner",
|
||||
campaign: isSubscription ? "SubscriptionExpired" : "TrialExpired",
|
||||
}
|
||||
IdentityStore.fetchSingleSignOnURL('/payment', utm).then((url) => {
|
||||
this.setState({buildingUpgradeURL: false});
|
||||
shell.openExternal(url);
|
||||
});
|
||||
|
|
|
@ -90,7 +90,7 @@ const CreatePageForForm = (FormComponent) => {
|
|||
OnboardingActions.accountJSONReceived(json)
|
||||
})
|
||||
.catch((err) => {
|
||||
Actions.recordUserEvent('Auth Failed', {
|
||||
Actions.recordUserEvent('Email Account Auth Failed', {
|
||||
errorMessage: err.message,
|
||||
provider: accountInfo.type,
|
||||
})
|
||||
|
|
|
@ -14,6 +14,16 @@ function accountTypeForProvider(provider) {
|
|||
return provider;
|
||||
}
|
||||
|
||||
function providerForAccountType(type) {
|
||||
if (type === 'exchange') {
|
||||
return 'eas';
|
||||
}
|
||||
if (type === 'imap') {
|
||||
return 'custom';
|
||||
}
|
||||
return type;
|
||||
}
|
||||
|
||||
class OnboardingStore extends NylasStore {
|
||||
constructor() {
|
||||
super();
|
||||
|
@ -99,7 +109,8 @@ class OnboardingStore extends NylasStore {
|
|||
} else if (type === 'exchange') {
|
||||
nextPage = "account-settings-exchange";
|
||||
}
|
||||
Actions.recordUserEvent('Auth Flow Started', {type});
|
||||
const provider = providerForAccountType(type)
|
||||
Actions.recordUserEvent('Email Account Auth Started', {provider});
|
||||
this._onSetAccountInfo(Object.assign({}, this._accountInfo, {type}));
|
||||
this._onMoveToPage(nextPage);
|
||||
}
|
||||
|
@ -126,7 +137,6 @@ class OnboardingStore extends NylasStore {
|
|||
if (!json.seen_welcome_page) {
|
||||
this._openWelcomePage();
|
||||
}
|
||||
Actions.recordUserEvent('Nylas Identity Set');
|
||||
|
||||
setTimeout(() => {
|
||||
if (isFirstAccount) {
|
||||
|
@ -148,7 +158,7 @@ class OnboardingStore extends NylasStore {
|
|||
AccountStore.addAccountFromJSON(json);
|
||||
this._accountFromAuth = AccountStore.accountForEmail(json.email_address);
|
||||
|
||||
Actions.recordUserEvent('Auth Successful', {
|
||||
Actions.recordUserEvent('Email Account Auth Succeeded', {
|
||||
provider: this._accountFromAuth.provider,
|
||||
});
|
||||
ipcRenderer.send('new-account-added');
|
||||
|
@ -156,7 +166,9 @@ class OnboardingStore extends NylasStore {
|
|||
|
||||
if (isFirstAccount) {
|
||||
this._onMoveToPage('initial-preferences');
|
||||
Actions.recordUserEvent('First Account Linked');
|
||||
Actions.recordUserEvent('First Account Linked', {
|
||||
provider: this._accountFromAuth.provider,
|
||||
});
|
||||
} else {
|
||||
this._onOnboardingComplete();
|
||||
}
|
||||
|
|
|
@ -80,7 +80,7 @@ export default class AuthenticatePage extends React.Component {
|
|||
|
||||
componentDidMount() {
|
||||
const webview = ReactDOM.findDOMNode(this.refs.webview);
|
||||
webview.src = `${IdentityStore.URLRoot}/onboarding`;
|
||||
webview.src = `${IdentityStore.URLRoot}/onboarding?utm_medium=N1&utm_source=OnboardingPage`;
|
||||
webview.addEventListener('did-start-loading', this.webviewDidStartLoading);
|
||||
webview.addEventListener('did-get-response-details', this.webviewDidGetResponseDetails);
|
||||
webview.addEventListener('did-fail-load', this.webviewDidFailLoad);
|
||||
|
|
|
@ -7,6 +7,8 @@ class OpenIdentityPageButton extends React.Component {
|
|||
static propTypes = {
|
||||
path: React.PropTypes.string,
|
||||
label: React.PropTypes.string,
|
||||
source: React.PropTypes.string,
|
||||
campaign: React.PropTypes.string,
|
||||
img: React.PropTypes.string,
|
||||
}
|
||||
|
||||
|
@ -18,7 +20,12 @@ class OpenIdentityPageButton extends React.Component {
|
|||
}
|
||||
|
||||
_onClick = () => {
|
||||
IdentityStore.fetchSingleSignOnURL(this.props.path).then((url) => {
|
||||
this.setState({loading: true});
|
||||
IdentityStore.fetchSingleSignOnURL(this.props.path, {
|
||||
source: this.props.source,
|
||||
campaign: this.props.campaign,
|
||||
content: this.props.label,
|
||||
}).then((url) => {
|
||||
this.setState({loading: false});
|
||||
shell.openExternal(url);
|
||||
});
|
||||
|
@ -83,7 +90,7 @@ class PreferencesIdentity extends React.Component {
|
|||
There {(trialDaysRemaining > 1) ? `are ${trialDaysRemaining} days ` : `is one day `}
|
||||
remaining in your 30-day trial of Nylas Pro.
|
||||
</div>
|
||||
<OpenIdentityPageButton img="ic-upgrade.png" label="Upgrade to Nylas Pro" path="/payment" />
|
||||
<OpenIdentityPageButton img="ic-upgrade.png" label="Upgrade to Nylas Pro" path="/payment" campaign="Upgrade" source="Preferences" />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
@ -95,7 +102,7 @@ class PreferencesIdentity extends React.Component {
|
|||
Your subscription has been cancelled or your billing information has expired.
|
||||
We've paused your mailboxes! Re-new your subscription to continue using N1.
|
||||
</div>
|
||||
<OpenIdentityPageButton img="ic-upgrade.png" label="Update Subscription" path="/dashboard#subscription" />
|
||||
<OpenIdentityPageButton img="ic-upgrade.png" label="Update Subscription" path="/dashboard#subscription" campaign="Renew" source="Preferences" />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
@ -128,7 +135,7 @@ class PreferencesIdentity extends React.Component {
|
|||
<div className="name">{firstname} {lastname}</div>
|
||||
<div className="email">{email}</div>
|
||||
<div className="identity-actions">
|
||||
<OpenIdentityPageButton label="Account Details" path="/dashboard" />
|
||||
<OpenIdentityPageButton label="Account Details" path="/dashboard" source="Preferences" campaign="Dashboard" />
|
||||
<div className="btn" onClick={() => Actions.logoutNylasIdentity()}>Sign Out</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -130,8 +130,8 @@ class SearchQuerySubscription extends MutableQuerySubscription {
|
|||
let timeToFirstThreadSelected = null;
|
||||
const searchQuery = this._searchQuery
|
||||
const timeInsideSearch = Math.round((Date.now() - this._searchStartedAt) / 1000)
|
||||
const selectedThreads = this._focusedThreadCount
|
||||
const didSelectAnyThreads = selectedThreads > 0
|
||||
const numItems = this._focusedThreadCount
|
||||
const didSelectAnyThreads = numItems > 0
|
||||
|
||||
if (this._firstThreadSelectedAt) {
|
||||
timeToFirstThreadSelected = Math.round((this._firstThreadSelectedAt - this._searchStartedAt) / 1000)
|
||||
|
@ -142,7 +142,7 @@ class SearchQuerySubscription extends MutableQuerySubscription {
|
|||
|
||||
const data = {
|
||||
searchQuery,
|
||||
selectedThreads,
|
||||
numItems,
|
||||
timeInsideSearch,
|
||||
didSelectAnyThreads,
|
||||
timeToFirstServerResults,
|
||||
|
|
|
@ -60,7 +60,6 @@ class SearchStore extends NylasStore
|
|||
current = FocusedPerspectiveStore.current()
|
||||
|
||||
if @queryPopulated()
|
||||
Actions.recordUserEvent("Commit Search Query", {})
|
||||
@_isSearching = true
|
||||
@_perspectiveBeforeSearch ?= current
|
||||
next = new SearchMailboxPerspective(current.accountIds, @_searchQuery.trim())
|
||||
|
|
|
@ -22,11 +22,12 @@ class SnoozeStore {
|
|||
|
||||
recordSnoozeEvent(threads, snoozeDate, label) {
|
||||
try {
|
||||
const min = Math.round(((new Date(snoozeDate)).valueOf() - Date.now()) / 1000 / 60);
|
||||
Actions.recordUserEvent("Snooze Threads", {
|
||||
numThreads: threads.length,
|
||||
snoozeTime: min,
|
||||
buttonType: label,
|
||||
const timeInSec = Math.round(((new Date(snoozeDate)).valueOf() - Date.now()) / 1000);
|
||||
Actions.recordUserEvent("Threads Snoozed", {
|
||||
timeInSec: timeInSec,
|
||||
timeInLog10Sec: Math.log10(timeInSec),
|
||||
label: label,
|
||||
numItems: threads.length,
|
||||
});
|
||||
} catch (e) {
|
||||
// Do nothing
|
||||
|
|
|
@ -139,11 +139,11 @@ class NylasSyncWorkerPool
|
|||
_handleAccountDeltas: (deltas) =>
|
||||
for delta in deltas
|
||||
Actions.updateAccount(delta.account_id, {syncState: delta.sync_state})
|
||||
Actions.recordUserEvent('Account State Delta', {
|
||||
accountId: delta.account_id
|
||||
accountEmail: delta.email_address
|
||||
syncState: delta.sync_state
|
||||
})
|
||||
if delta.sync_state isnt "running"
|
||||
Actions.recordUserEvent('Account Sync Errored', {
|
||||
accountId: delta.account_id
|
||||
syncState: delta.sync_state
|
||||
})
|
||||
|
||||
_handleDeltaDeletion: (delta) =>
|
||||
klass = NylasAPI._apiObjectToClassMap[delta.object]
|
||||
|
|
|
@ -163,7 +163,7 @@ class EventedIFrame extends React.Component
|
|||
# just following the link directly
|
||||
if rawHref.startsWith(IdentityStore.URLRoot)
|
||||
path = rawHref.split(IdentityStore.URLRoot).pop()
|
||||
IdentityStore.fetchSingleSignOnURL(IdentityStore.identity(), path).then (href) =>
|
||||
IdentityStore.fetchSingleSignOnURL(path, {source: "SingleSignOnEmail"}).then (href) =>
|
||||
NylasEnv.windowEventHandler.openLink(href: href, metaKey: e.metaKey)
|
||||
return
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@ import NylasStore from 'nylas-store';
|
|||
import keytar from 'keytar';
|
||||
import {ipcRenderer} from 'electron';
|
||||
import request from 'request';
|
||||
import url from 'url'
|
||||
|
||||
import Actions from '../actions';
|
||||
import AccountStore from './account-store';
|
||||
|
@ -125,12 +126,26 @@ class IdentityStore extends NylasStore {
|
|||
});
|
||||
}
|
||||
|
||||
fetchSingleSignOnURL(path) {
|
||||
/**
|
||||
* This passes utm_source, utm_campaign, and utm_content params to the
|
||||
* N1 billing site. Please reference:
|
||||
* https://paper.dropbox.com/doc/Analytics-ID-Unification-oVDTkakFsiBBbk9aeuiA3
|
||||
* for the full list of utm_ labels.
|
||||
*/
|
||||
fetchSingleSignOnURL(path, {source, campaign, content}) {
|
||||
if (!this._identity) {
|
||||
return Promise.reject(new Error("fetchSingleSignOnURL: no identity set."));
|
||||
}
|
||||
|
||||
if (!path.startsWith('/')) {
|
||||
const qs = {utm_medium: "N1"}
|
||||
if (source) { qs.utm_source = source }
|
||||
if (campaign) { qs.utm_campaign = campaign }
|
||||
if (content) { qs.utm_content = content }
|
||||
|
||||
const pathWithUtm = url.parse(path);
|
||||
pathWithUtm.query = Object.assign({}, qs, (pathWithUtm.query || {}))
|
||||
|
||||
if (!pathWithUtm.startsWith('/')) {
|
||||
return Promise.reject(new Error("fetchSingleSignOnURL: path must start with a leading slash."));
|
||||
}
|
||||
|
||||
|
@ -138,9 +153,10 @@ class IdentityStore extends NylasStore {
|
|||
request({
|
||||
method: 'POST',
|
||||
url: `${this.URLRoot}/n1/login-link`,
|
||||
qs: qs,
|
||||
json: true,
|
||||
body: {
|
||||
next_path: path,
|
||||
next_path: pathWithUtm.format(),
|
||||
account_token: this._identity.token,
|
||||
},
|
||||
}, (error, response = {}, body) => {
|
||||
|
|
2
src/pro
2
src/pro
|
@ -1 +1 @@
|
|||
Subproject commit 82d9667fe661ec332b86dd6b47babddd40e65d86
|
||||
Subproject commit 13d3ffdb2103110e2fe4e712dea817590c92309a
|
Loading…
Add table
Reference in a new issue