mirror of
https://github.com/Foundry376/Mailspring.git
synced 2025-02-25 00:25:03 +08:00
[client-app] Measure and report inline composer open times + consolidate timers
Summary: This commit adds a new perf metric for inline composer times. Additionally, it consolidates the timer logic for all other types of draft creation (mailto links, dropping a file in the app dock icon, etc). Test Plan: manual Reviewers: halla, evan, mark Reviewed By: evan, mark Differential Revision: https://phab.nylas.com/D4186
This commit is contained in:
parent
811c192125
commit
f467664bde
4 changed files with 106 additions and 52 deletions
|
@ -52,6 +52,7 @@ export default class ComposerView extends React.Component {
|
|||
}
|
||||
|
||||
componentDidMount() {
|
||||
this._recordComposerOpenTime()
|
||||
if (this.props.session) {
|
||||
this._setupForProps(this.props);
|
||||
}
|
||||
|
@ -83,6 +84,31 @@ export default class ComposerView extends React.Component {
|
|||
}
|
||||
}
|
||||
|
||||
_recordComposerOpenTime() {
|
||||
const {draft: {threadId, replyToMessageId}} = this.props
|
||||
if (NylasEnv.isComposerWindow()) { return }
|
||||
|
||||
// This method only records inline composer opening times. Composer window
|
||||
// opening times are recorded in ComposerWithWindowPros
|
||||
const replyTimerKey = `compose-reply-${replyToMessageId}`
|
||||
const forwardTimerKey = `compose-forward-${threadId}`
|
||||
let actionTimeMs;
|
||||
if (NylasEnv.timer.isPending(replyTimerKey)) {
|
||||
actionTimeMs = NylasEnv.timer.stop(replyTimerKey)
|
||||
}
|
||||
if (NylasEnv.timer.isPending(forwardTimerKey)) {
|
||||
actionTimeMs = NylasEnv.timer.stop(forwardTimerKey)
|
||||
}
|
||||
if (actionTimeMs != null) {
|
||||
Actions.recordPerfMetric({
|
||||
action: 'open-inline-composer',
|
||||
actionTimeMs,
|
||||
maxValue: 4000,
|
||||
sample: 0.9,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
_keymapHandlers() {
|
||||
return {
|
||||
'composer:send-message': () => this._onPrimarySend(),
|
||||
|
|
|
@ -48,20 +48,8 @@ class ComposerWithWindowProps extends React.Component {
|
|||
|
||||
_onDraftReady = () => {
|
||||
this.refs.composer.focus().then(() => {
|
||||
if (NylasEnv.timer.isPending('open-composer-window')) {
|
||||
const actionTimeMs = NylasEnv.timer.stop('open-composer-window');
|
||||
if (actionTimeMs && actionTimeMs <= 4000) {
|
||||
Actions.recordUserEvent("Composer Popout Timed", {timeInMs: actionTimeMs})
|
||||
}
|
||||
// TODO time when plugins actually get loaded in
|
||||
Actions.recordPerfMetric({
|
||||
action: 'open-composer-window',
|
||||
actionTimeMs,
|
||||
maxValue: 4000,
|
||||
sample: 0.9,
|
||||
})
|
||||
}
|
||||
NylasEnv.displayWindow();
|
||||
this._recordComposerOpenTime()
|
||||
|
||||
if (this.state.errorMessage) {
|
||||
this._showInitialErrorDialog(this.state.errorMessage, this.state.errorDetail);
|
||||
|
@ -82,6 +70,24 @@ class ComposerWithWindowProps extends React.Component {
|
|||
});
|
||||
}
|
||||
|
||||
_recordComposerOpenTime() {
|
||||
const {timerId} = NylasEnv.getWindowProps()
|
||||
const timerKey = `open-composer-window-${timerId}`
|
||||
if (NylasEnv.timer.isPending(timerKey)) {
|
||||
const actionTimeMs = NylasEnv.timer.stop(timerKey);
|
||||
if (actionTimeMs && actionTimeMs <= 4000) {
|
||||
// TODO do we still need to record this legacy event?
|
||||
Actions.recordUserEvent("Composer Popout Timed", {timeInMs: actionTimeMs})
|
||||
}
|
||||
Actions.recordPerfMetric({
|
||||
action: 'open-composer-window',
|
||||
actionTimeMs,
|
||||
maxValue: 4000,
|
||||
sample: 0.9,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<ComposerViewForDraftClientId
|
||||
|
|
|
@ -74,7 +74,8 @@ class MessageControls extends React.Component
|
|||
Actions.composeReply({thread, message, type: 'reply-all', behavior: 'prefer-existing-if-pristine'})
|
||||
|
||||
_onForward: =>
|
||||
Actions.composeForward(thread: @props.thread, message: @props.message)
|
||||
{thread, message} = @props
|
||||
Actions.composeForward({thread, message})
|
||||
|
||||
_onShowActionsMenu: =>
|
||||
SystemMenu = remote.Menu
|
||||
|
|
|
@ -14,6 +14,7 @@ import SyncbackDraftTask from '../tasks/syncback-draft-task';
|
|||
import DestroyDraftTask from '../tasks/destroy-draft-task';
|
||||
import Thread from '../models/thread';
|
||||
import Message from '../models/message';
|
||||
import Utils from '../models/utils';
|
||||
import Actions from '../actions';
|
||||
import SoundRegistry from '../../registries/sound-registry';
|
||||
import * as ExtensionRegistry from '../../registries/extension-registry';
|
||||
|
@ -49,7 +50,7 @@ class DraftStore extends NylasStore {
|
|||
if (NylasEnv.isMainWindow()) {
|
||||
ipcRenderer.on('new-message', () => {
|
||||
Actions.composeNewBlankDraft();
|
||||
}); // So Analytics can see it
|
||||
});
|
||||
}
|
||||
|
||||
// Remember that these two actions only fire in the current window and
|
||||
|
@ -187,6 +188,9 @@ class DraftStore extends NylasStore {
|
|||
this._modelifyContext({thread, threadId, message, messageId})
|
||||
)
|
||||
.then(({message: m, thread: t}) => {
|
||||
if (['reply', 'reply-all'].includes(type)) {
|
||||
NylasEnv.timer.start(`compose-reply-${m.id}`)
|
||||
}
|
||||
return DraftFactory.createOrUpdateDraftForReply({message: m, thread: t, type, behavior});
|
||||
})
|
||||
.then(draft => {
|
||||
|
@ -200,6 +204,7 @@ class DraftStore extends NylasStore {
|
|||
this._modelifyContext({thread, threadId, message, messageId})
|
||||
)
|
||||
.then(({thread: t, message: m}) => {
|
||||
NylasEnv.timer.start(`compose-forward-${t.id}`)
|
||||
return DraftFactory.createDraftForForward({thread: t, message: m})
|
||||
})
|
||||
.then((draft) => {
|
||||
|
@ -268,26 +273,74 @@ class DraftStore extends NylasStore {
|
|||
}
|
||||
|
||||
_onPopoutNewDraftToRecipient = (contact) => {
|
||||
Actions.recordUserEvent("Draft Created", {type: "new"});
|
||||
const timerId = Utils.generateTempId()
|
||||
NylasEnv.timer.start(`open-composer-window-${timerId}`);
|
||||
return DraftFactory.createDraft({to: [contact]}).then((draft) => {
|
||||
return this._finalizeAndPersistNewMessage(draft, {popout: true});
|
||||
return this._finalizeAndPersistNewMessage(draft).then(({draftClientId}) => {
|
||||
return this._onPopoutDraftClientId(draftClientId, {timerId, newDraft: true});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
_onPopoutBlankDraft = () => {
|
||||
Actions.recordUserEvent("Draft Created", {type: "new"});
|
||||
NylasEnv.timer.start('open-composer-window');
|
||||
const timerId = Utils.generateTempId()
|
||||
NylasEnv.timer.start(`open-composer-window-${timerId}`);
|
||||
return DraftFactory.createDraft().then((draft) => {
|
||||
return this._finalizeAndPersistNewMessage(draft).then(({draftClientId}) => {
|
||||
return this._onPopoutDraftClientId(draftClientId, {newDraft: true});
|
||||
return this._onPopoutDraftClientId(draftClientId, {timerId, newDraft: true});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
_onHandleMailtoLink = (event, urlString) => {
|
||||
Actions.recordUserEvent("Draft Created", {type: "mailto"});
|
||||
const timerId = Utils.generateTempId()
|
||||
NylasEnv.timer.start(`open-composer-window-${timerId}`);
|
||||
return DraftFactory.createDraftForMailto(urlString).then((draft) => {
|
||||
return this._finalizeAndPersistNewMessage(draft).then(({draftClientId}) => {
|
||||
return this._onPopoutDraftClientId(draftClientId, {timerId, newDraft: true});
|
||||
});
|
||||
}).catch((err) => {
|
||||
NylasEnv.showErrorDialog(err.toString())
|
||||
});
|
||||
}
|
||||
|
||||
_onHandleMailFiles = (event, paths) => {
|
||||
Actions.recordUserEvent("Draft Created", {type: "dropped-file-in-dock"});
|
||||
const timerId = Utils.generateTempId()
|
||||
NylasEnv.timer.start(`open-composer-window-${timerId}`);
|
||||
return DraftFactory.createDraft().then((draft) => {
|
||||
return this._finalizeAndPersistNewMessage(draft);
|
||||
})
|
||||
.then(({draftClientId}) => {
|
||||
let remaining = paths.length;
|
||||
const callback = () => {
|
||||
remaining -= 1;
|
||||
if (remaining === 0) {
|
||||
this._onPopoutDraftClientId(draftClientId, {timerId});
|
||||
}
|
||||
};
|
||||
|
||||
paths.forEach((path) => {
|
||||
Actions.addAttachment({
|
||||
filePath: path,
|
||||
messageClientId: draftClientId,
|
||||
onUploadCreated: callback,
|
||||
});
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
_onPopoutDraftClientId = (draftClientId, options = {}) => {
|
||||
if (draftClientId == null) {
|
||||
throw new Error("DraftStore::onPopoutDraftId - You must provide a draftClientId");
|
||||
}
|
||||
NylasEnv.timer.start('open-composer-window');
|
||||
const {timerId} = options
|
||||
if (!timerId) {
|
||||
NylasEnv.timer.start(`open-composer-window-${draftClientId}`);
|
||||
}
|
||||
|
||||
const title = options.newDraft ? "New Message" : "Message";
|
||||
return this.sessionForClientId(draftClientId).then((session) => {
|
||||
|
@ -299,6 +352,7 @@ class DraftStore extends NylasStore {
|
|||
NylasEnv.newWindow({
|
||||
title,
|
||||
hidden: true, // We manually show in ComposerWithWindowProps::onDraftReady
|
||||
timerId: timerId || draftClientId,
|
||||
windowKey: `composer-${draftClientId}`,
|
||||
windowType: 'composer-preload',
|
||||
windowProps: _.extend(options, {draftClientId, draftJSON}),
|
||||
|
@ -307,39 +361,6 @@ class DraftStore extends NylasStore {
|
|||
});
|
||||
}
|
||||
|
||||
_onHandleMailtoLink = (event, urlString) => {
|
||||
// return is just used for specs
|
||||
return DraftFactory.createDraftForMailto(urlString).then((draft) => {
|
||||
return this._finalizeAndPersistNewMessage(draft, {popout: true});
|
||||
}).catch((err) => {
|
||||
NylasEnv.showErrorDialog(err.toString())
|
||||
});
|
||||
}
|
||||
|
||||
_onHandleMailFiles = (event, paths) => {
|
||||
// return is just used for specs
|
||||
return DraftFactory.createDraft().then((draft) => {
|
||||
return this._finalizeAndPersistNewMessage(draft);
|
||||
})
|
||||
.then(({draftClientId}) => {
|
||||
let remaining = paths.length;
|
||||
const callback = () => {
|
||||
remaining -= 1;
|
||||
if (remaining === 0) {
|
||||
this._onPopoutDraftClientId(draftClientId);
|
||||
}
|
||||
};
|
||||
|
||||
paths.forEach((path) => {
|
||||
Actions.addAttachment({
|
||||
filePath: path,
|
||||
messageClientId: draftClientId,
|
||||
onUploadCreated: callback,
|
||||
});
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
_onDestroyDraft = (draftClientId) => {
|
||||
const session = this._draftSessions[draftClientId];
|
||||
|
||||
|
|
Loading…
Reference in a new issue