mirror of
https://github.com/Foundry376/Mailspring.git
synced 2025-10-01 08:54:52 +08:00
Revert "[client-app] Measure and report archiving times"
This reverts commit a8fbcb0c93
.
# Conflicts:
# packages/client-app/internal_packages/thread-list/lib/thread-list-quick-actions.cjsx
# packages/client-app/internal_packages/thread-list/lib/thread-list.cjsx
# packages/client-app/src/flux/actions.es6
# packages/client-app/src/flux/stores/thread-list-actions-store.es6
# packages/client-app/src/global/nylas-exports.es6
# packages/isomorphic-core/src/metrics-reporter.es6
This commit is contained in:
parent
0f6fdb4256
commit
ef9a1b1164
12 changed files with 59 additions and 313 deletions
|
@ -26,10 +26,10 @@ class ThreadArchiveButton extends React.Component
|
|||
|
||||
_onArchive: (e) =>
|
||||
return unless DOMUtils.nodeIsVisible(e.currentTarget)
|
||||
Actions.archiveThreads({
|
||||
threads: [@props.thread],
|
||||
source: 'Toolbar Button: Message List',
|
||||
})
|
||||
tasks = TaskFactory.tasksForArchiving
|
||||
threads: [@props.thread]
|
||||
source: "Toolbar Button: Message List"
|
||||
Actions.queueTasks(tasks)
|
||||
Actions.popSheet()
|
||||
e.stopPropagation()
|
||||
|
||||
|
|
|
@ -20,10 +20,11 @@ export function sendActions() {
|
|||
Actions.queueTask(new SendDraftTask(draft.id))
|
||||
return DatabaseStore.modelify(Thread, [draft.threadId])
|
||||
.then((threads) => {
|
||||
Actions.archiveThreads({
|
||||
const tasks = TaskFactory.tasksForArchiving({
|
||||
source: "Send and Archive",
|
||||
threads: threads,
|
||||
})
|
||||
Actions.queueTasks(tasks)
|
||||
})
|
||||
},
|
||||
}]
|
||||
|
|
|
@ -103,10 +103,11 @@ export default class ThreadListContextMenu {
|
|||
return {
|
||||
label: "Archive",
|
||||
click: () => {
|
||||
Actions.archiveThreads({
|
||||
const tasks = TaskFactory.tasksForArchiving({
|
||||
source: "Context Menu: Thread List",
|
||||
threads: this.threads,
|
||||
})
|
||||
Actions.queueTasks(tasks)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,12 +25,10 @@ class ThreadArchiveQuickAction extends React.Component
|
|||
newProps.thread.id isnt @props?.thread.id
|
||||
|
||||
_onArchive: (event) =>
|
||||
# Don't trigger the thread row click
|
||||
event.stopPropagation()
|
||||
Actions.archiveThreads({
|
||||
source: "Quick Actions: Thread List",
|
||||
threads: [@props.thread],
|
||||
})
|
||||
tasks = TaskFactory.tasksForArchiving
|
||||
source: "Quick Actions: Thread List"
|
||||
threads: [@props.thread]
|
||||
Actions.queueTasks(tasks)
|
||||
|
||||
class ThreadTrashQuickAction extends React.Component
|
||||
@displayName: 'ThreadTrashQuickAction'
|
||||
|
|
|
@ -342,9 +342,11 @@ class ThreadList extends React.Component
|
|||
|
||||
_onArchiveItem: =>
|
||||
threads = @_threadsForKeyboardAction()
|
||||
if not threads
|
||||
return
|
||||
Actions.archiveThreads({threads, source: "Keyboard Shortcut"})
|
||||
if threads
|
||||
tasks = TaskFactory.tasksForArchiving
|
||||
source: "Keyboard Shortcut"
|
||||
threads: threads
|
||||
Actions.queueTasks(tasks)
|
||||
Actions.popSheet()
|
||||
|
||||
_onDeleteItem: =>
|
||||
|
|
|
@ -22,10 +22,11 @@ export class ArchiveButton extends React.Component {
|
|||
}
|
||||
|
||||
_onArchive = (event) => {
|
||||
Actions.archiveThreads({
|
||||
const tasks = TaskFactory.tasksForArchiving({
|
||||
threads: this.props.items,
|
||||
source: "Toolbar Button: Thread List",
|
||||
})
|
||||
Actions.queueTasks(tasks);
|
||||
Actions.popSheet();
|
||||
event.stopPropagation();
|
||||
return;
|
||||
|
|
|
@ -11,9 +11,9 @@ export default class GlobalTimer {
|
|||
this._pendingRuns = {}
|
||||
}
|
||||
|
||||
start(key, now = Date.now()) {
|
||||
start(key) {
|
||||
if (!this._pendingRuns[key]) {
|
||||
this._pendingRuns[key] = [now]
|
||||
this._pendingRuns[key] = [Date.now()]
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -30,20 +30,15 @@ export default class GlobalTimer {
|
|||
}
|
||||
}
|
||||
|
||||
stop(key, now = Date.now()) {
|
||||
stop(key) {
|
||||
if (!this._pendingRuns[key]) { return 0 }
|
||||
|
||||
if (!this._doneRuns[key]) {
|
||||
this._doneRuns[key] = []
|
||||
}
|
||||
this._pendingRuns[key].push(now);
|
||||
|
||||
if (!this._doneRuns[key]) { this._doneRuns[key] = [] }
|
||||
this._pendingRuns[key].push(Date.now());
|
||||
const total = this.calcTotal(this._pendingRuns[key])
|
||||
this._doneRuns[key].push(this._pendingRuns[key])
|
||||
if (this._doneRuns[key].length > BUFFER_SIZE) {
|
||||
this._doneRuns[key].shift()
|
||||
}
|
||||
|
||||
delete this._pendingRuns[key]
|
||||
return total
|
||||
}
|
||||
|
|
|
@ -556,19 +556,6 @@ class Actions {
|
|||
static resetEmailCache = ActionScopeGlobal;
|
||||
|
||||
static debugSync = ActionScopeGlobal;
|
||||
|
||||
// Thread list actions
|
||||
static archiveThreads = ActionScopeWindow;
|
||||
static trashThreads = ActionScopeWindow;
|
||||
static markAsSpamThreads = ActionScopeWindow;
|
||||
static toggleStarredThreads = ActionScopeWindow;
|
||||
static toggleUnreadThreads = ActionScopeWindow;
|
||||
static setUnreadThreads = ActionScopeWindow;
|
||||
static removeThreadsFromView = ActionScopeWindow;
|
||||
static moveThreadsToPerspective = ActionScopeWindow;
|
||||
static applyCategoryToThreads = ActionScopeWindow;
|
||||
static removeCategoryFromThreads = ActionScopeWindow;
|
||||
static threadListDidUpdate = ActionScopeWindow;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -86,11 +86,6 @@ export default class ObservableListDataSource extends ListTabular.DataSource {
|
|||
return this._resultSet.offsetOfId(id);
|
||||
}
|
||||
|
||||
itemsCurrentlyInView() {
|
||||
if (!this._resultSet) { return [] }
|
||||
return this._resultSet.models()
|
||||
}
|
||||
|
||||
itemsCurrentlyInViewMatching(matchFn) {
|
||||
if (!this._resultSet) {
|
||||
return [];
|
||||
|
|
|
@ -1,214 +0,0 @@
|
|||
import NylasStore from 'nylas-store'
|
||||
import Actions from '../actions'
|
||||
import Utils from '../models/utils'
|
||||
import TaskFactory from '../tasks/task-factory'
|
||||
import AccountStore from '../stores/account-store'
|
||||
import FocusedPerspectiveStore from '../stores/focused-perspective-store'
|
||||
|
||||
|
||||
class ThreadListActionsStore extends NylasStore {
|
||||
|
||||
constructor() {
|
||||
super()
|
||||
this._timers = new Map()
|
||||
}
|
||||
|
||||
activate() {
|
||||
if (!NylasEnv.isMainWindow()) { return }
|
||||
this.listenTo(Actions.archiveThreads, this._onArchiveThreads)
|
||||
this.listenTo(Actions.trashThreads, this._onTrashThreads)
|
||||
this.listenTo(Actions.markAsSpamThreads, this._onMarkAsSpamThreads)
|
||||
this.listenTo(Actions.toggleStarredThreads, this._onToggleStarredThreads)
|
||||
this.listenTo(Actions.toggleUnreadThreads, this._onToggleUnreadThreads)
|
||||
this.listenTo(Actions.setUnreadThreads, this._onSetUnreadThreads)
|
||||
this.listenTo(Actions.removeThreadsFromView, this._onRemoveThreadsFromView)
|
||||
this.listenTo(Actions.moveThreadsToPerspective, this._onMoveThreadsToPerspective)
|
||||
this.listenTo(Actions.removeCategoryFromThreads, this._onRemoveCategoryFromThreads)
|
||||
this.listenTo(Actions.applyCategoryToThreads, this._onApplyCategoryToThreads)
|
||||
this.listenTo(Actions.threadListDidUpdate, this._onThreadListDidUpdate)
|
||||
}
|
||||
|
||||
deactivate() {
|
||||
this.stopListeningToAll()
|
||||
}
|
||||
|
||||
_onThreadListDidUpdate = (threads) => {
|
||||
const updatedAt = Date.now()
|
||||
const threadIdsInList = new Set(threads.map(t => t.id))
|
||||
|
||||
for (const [timerId, timerData] of this._timers.entries()) {
|
||||
const {threadIds, provider, source, action, targetCategory} = timerData
|
||||
const threadsHaveBeenRemoved = threadIds.every(id => !threadIdsInList.has(id))
|
||||
if (threadsHaveBeenRemoved) {
|
||||
const actionTimeMs = NylasEnv.timer.stop(timerId, updatedAt)
|
||||
Actions.recordPerfMetric({
|
||||
action,
|
||||
source,
|
||||
provider,
|
||||
actionTimeMs,
|
||||
targetCategory,
|
||||
threadCount: threadIds.length,
|
||||
sample: 0.9,
|
||||
})
|
||||
this._timers.delete(timerId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_setNewTimer({threads, threadIds, accountIds, source, action, targetCategory = 'unknown'} = {}) {
|
||||
if (!threads && !threadIds) {
|
||||
return
|
||||
}
|
||||
if (threadIds && !accountIds) {
|
||||
throw new Error('ThreadListActionStore._setNewTimer: Must pass accountIds along with threadIds')
|
||||
}
|
||||
const tIds = threadIds || threads.map(t => t.id);
|
||||
const timerId = Utils.generateTempId()
|
||||
let accounts
|
||||
if (!threads) {
|
||||
accounts = accountIds
|
||||
.map(id => AccountStore.accountForId(id))
|
||||
.filter(Boolean)
|
||||
} else {
|
||||
accounts = AccountStore.accountsForItems(threads)
|
||||
}
|
||||
const firstProvider = accounts[0].provider
|
||||
const haveSameProvider = accounts
|
||||
.reduce((provider, acct) => (acct.provider === provider ? provider : false), firstProvider)
|
||||
const provider = haveSameProvider ? firstProvider : 'mixed'
|
||||
const timerData = {
|
||||
source,
|
||||
action,
|
||||
provider,
|
||||
targetCategory,
|
||||
threadIds: tIds,
|
||||
}
|
||||
this._timers.set(timerId, timerData)
|
||||
NylasEnv.timer.start(timerId)
|
||||
}
|
||||
|
||||
_onArchiveThreads = ({threads, source} = {}) => {
|
||||
if (!threads) { return }
|
||||
if (threads.length === 0) { return }
|
||||
this._setNewTimer({threads, source, action: 'remove-threads-from-list', targetCategory: 'archive'})
|
||||
const tasks = TaskFactory.tasksForArchiving({threads, source})
|
||||
Actions.queueTasks(tasks)
|
||||
}
|
||||
|
||||
_onTrashThreads = ({threads, source} = {}) => {
|
||||
if (!threads) { return }
|
||||
if (threads.length === 0) { return }
|
||||
this._setNewTimer({threads, source, action: 'remove-threads-from-list', targetCategory: 'trash'})
|
||||
const tasks = TaskFactory.tasksForMovingToTrash({threads, source})
|
||||
Actions.queueTasks(tasks)
|
||||
}
|
||||
|
||||
_onMarkAsSpamThreads = ({threads, source} = {}) => {
|
||||
if (!threads) { return }
|
||||
if (threads.length === 0) { return }
|
||||
this._setNewTimer({threads, source, action: 'remove-threads-from-list', targetCategory: 'spam'})
|
||||
const tasks = TaskFactory.tasksForMarkingAsSpam({threads, source})
|
||||
Actions.queueTasks(tasks)
|
||||
}
|
||||
|
||||
_onToggleStarredThreads = ({threads, source} = {}) => {
|
||||
if (!threads) { return }
|
||||
if (threads.length === 0) { return }
|
||||
const task = TaskFactory.taskForInvertingStarred({threads, source})
|
||||
Actions.queueTask(task)
|
||||
}
|
||||
|
||||
_onToggleUnreadThreads = ({threads, canBeUndone, source} = {}) => {
|
||||
if (!threads) { return }
|
||||
if (threads.length === 0) { return }
|
||||
const task = TaskFactory.taskForInvertingUnread({threads, source, canBeUndone})
|
||||
Actions.queueTask(task)
|
||||
}
|
||||
|
||||
_onSetUnreadThreads = ({threads, unread, canBeUndone, source} = {}) => {
|
||||
if (!threads) { return }
|
||||
if (threads.length === 0) { return }
|
||||
const task = TaskFactory.taskForSettingUnread({threads, unread, source, canBeUndone})
|
||||
Actions.queueTask(task)
|
||||
}
|
||||
|
||||
_onRemoveThreadsFromView = ({threads, ruleset, source} = {}) => {
|
||||
if (!threads) { return }
|
||||
if (threads.length === 0) { return }
|
||||
const currentPerspective = FocusedPerspectiveStore.current()
|
||||
const tasks = currentPerspective.tasksForRemovingItems(threads, ruleset, source)
|
||||
|
||||
// This action can encompass many different actions, e.g.:
|
||||
// - unstarring in starred view
|
||||
// - changing unread in unread view
|
||||
// - Moving to inbox from trash
|
||||
// - archiving a search result (which won't actually remove it from the thread-list)
|
||||
// For now, we are only interested in timing actions that remove threads
|
||||
// from the inbox
|
||||
if (currentPerspective.isInbox()) {
|
||||
// TODO figure out the `targetCategory`
|
||||
this._setNewTimer({threads, source, action: 'remove-threads-from-list'})
|
||||
}
|
||||
Actions.queueTasks(tasks)
|
||||
}
|
||||
|
||||
_onMoveThreadsToPerspective = ({targetPerspective, threadIds, accountIds} = {}) => {
|
||||
if (!threadIds) { return }
|
||||
if (threadIds.length === 0) { return }
|
||||
const currentPerspective = FocusedPerspectiveStore.current()
|
||||
|
||||
// For now, we are only interested in timing actions that remove threads
|
||||
// from the inbox
|
||||
const targetCategories = targetPerspective.categories()
|
||||
const targetCategoryIsFolder = (
|
||||
targetCategories && targetCategories.length > 0 &&
|
||||
targetCategories.every(c => c.object === 'folder')
|
||||
)
|
||||
const isRemovingFromInbox = currentPerspective.isInbox() && targetCategoryIsFolder
|
||||
if (isRemovingFromInbox) {
|
||||
const targetCategory = targetPerspective.isArchive() ? 'archive' : targetPerspective.categoriesSharedName();
|
||||
this._setNewTimer({
|
||||
threadIds,
|
||||
accountIds,
|
||||
targetCategory,
|
||||
source: "Dragged to Sidebar",
|
||||
action: 'remove-threads-from-list',
|
||||
})
|
||||
}
|
||||
targetPerspective.receiveThreads(threadIds)
|
||||
}
|
||||
|
||||
_onApplyCategoryToThreads = ({threads, source, categoryToApply} = {}) => {
|
||||
if (!threads) { return }
|
||||
if (threads.length === 0) { return }
|
||||
const task = TaskFactory.taskForApplyingCategory({
|
||||
threads,
|
||||
source,
|
||||
category: categoryToApply,
|
||||
})
|
||||
Actions.queueTask(task)
|
||||
}
|
||||
|
||||
_onRemoveCategoryFromThreads = ({threads, source, categoryToRemove} = {}) => {
|
||||
if (!threads) { return }
|
||||
if (threads.length === 0) { return }
|
||||
// For now, we are only interested in timing actions that remove threads
|
||||
// from the inbox
|
||||
if (categoryToRemove.isInbox()) {
|
||||
this._setNewTimer({
|
||||
source,
|
||||
threads,
|
||||
targetCategory: 'archive',
|
||||
action: 'remove-threads-from-list',
|
||||
})
|
||||
}
|
||||
const task = TaskFactory.taskForRemovingCategory({
|
||||
threads,
|
||||
source,
|
||||
category: categoryToRemove,
|
||||
})
|
||||
Actions.queueTask(task)
|
||||
}
|
||||
}
|
||||
|
||||
export default new ThreadListActionsStore()
|
|
@ -164,7 +164,7 @@ lazyLoadAndRegisterStore(`MessageBodyProcessor`, 'message-body-processor');
|
|||
lazyLoadAndRegisterStore(`FocusedContactsStore`, 'focused-contacts-store');
|
||||
lazyLoadAndRegisterStore(`DeltaConnectionStore`, 'delta-connection-store');
|
||||
lazyLoadAndRegisterStore(`FolderSyncProgressStore`, 'folder-sync-progress-store');
|
||||
lazyLoadAndRegisterStore(`ThreadListActionsStore`, 'thread-list-actions-store');
|
||||
lazyLoadAndRegisterStore(`TaskQueueStatusStore`, 'task-queue-status-store');
|
||||
lazyLoadAndRegisterStore(`FocusedPerspectiveStore`, 'focused-perspective-store');
|
||||
lazyLoadAndRegisterStore(`SearchableComponentStore`, 'searchable-component-store');
|
||||
lazyLoad(`CustomContenteditableComponents`, 'components/overlaid-components/custom-contenteditable-components');
|
||||
|
|
|
@ -1,31 +1,13 @@
|
|||
import os from 'os'
|
||||
import {isClientEnv, isCloudEnv} from './env-helpers'
|
||||
|
||||
/**
|
||||
* NOTE: This is the Honeycomb performance metrics reporting for the Nylas
|
||||
* Mail Client. It is NOT the logging data for cloud plugins. This is
|
||||
* accessed via the /ingest-metrics endpoint of the cloud api. this can
|
||||
* also be used from the cloud environment to report metrics to honeycomb,
|
||||
* which is different from sending the logs to honeycomb
|
||||
*
|
||||
* Each AWS box automatically sends all log data to Honeycomb via
|
||||
* honeytail. You can find the config by ssh-ing to a production cloud box
|
||||
* and looking at /etc/sv/honeytail/run
|
||||
*/
|
||||
class MetricsReporter {
|
||||
|
||||
constructor() {
|
||||
this._honey = null
|
||||
this._baseReportingData = {
|
||||
hostname: os.hostname(),
|
||||
cpus: os.cpus().length,
|
||||
arch: os.arch(),
|
||||
platform: process.platform,
|
||||
version: isClientEnv() ? NylasEnv.getVersion() : undefined,
|
||||
}
|
||||
|
||||
if (isCloudEnv()) {
|
||||
const LibHoney = require('libhoney').default // eslint-disable-line
|
||||
const LibHoney = require('libhoney') // eslint-disable-line
|
||||
|
||||
this._honey = new LibHoney({
|
||||
writeKey: process.env.HONEY_WRITE_KEY,
|
||||
|
@ -46,55 +28,53 @@ class MetricsReporter {
|
|||
});
|
||||
}
|
||||
|
||||
sendToHoneycomb(data) {
|
||||
sendToHoneycomb(info) {
|
||||
if (!this._honey) {
|
||||
throw new Error('Metrics Reporter: Honeycomb is not available in this environment')
|
||||
}
|
||||
this._honey.sendNow(data);
|
||||
this._honey.sendNow(info);
|
||||
}
|
||||
|
||||
async reportEvent(data) {
|
||||
if (!data.nylasId) {
|
||||
async reportEvent(info) {
|
||||
if (!info.nylasId) {
|
||||
throw new Error("Metrics Reporter: You must include an nylasId");
|
||||
}
|
||||
const {accountId: id, emailAddress} = data
|
||||
const logger = global.Logger ? global.Logger.forAccount({id, emailAddress}) : console;
|
||||
const logger = global.Logger.child({accountEmail: info.emailAddress})
|
||||
const {workingSetSize, privateBytes, sharedBytes} = process.getProcessMemoryInfo();
|
||||
|
||||
const dataToReport = Object.assign({}, this._baseReportingData, data, {
|
||||
processWorkingSetSize: workingSetSize,
|
||||
processPrivateBytes: privateBytes,
|
||||
processSharedBytes: sharedBytes,
|
||||
})
|
||||
info.hostname = os.hostname();
|
||||
info.cpus = os.cpus().length;
|
||||
info.arch = os.arch();
|
||||
info.platform = process.platform;
|
||||
info.version = NylasEnv.getVersion();
|
||||
info.processWorkingSetSize = workingSetSize;
|
||||
info.processPrivateBytes = privateBytes;
|
||||
info.processSharedBytes = sharedBytes;
|
||||
|
||||
try {
|
||||
if (!isClientEnv()) {
|
||||
this.sendToHoneycomb(dataToReport)
|
||||
return
|
||||
}
|
||||
if (NylasEnv.inDevMode()) { return }
|
||||
if (isClientEnv()) {
|
||||
if (NylasEnv.inDevMode()) { return }
|
||||
if (!info.accountId) {
|
||||
throw new Error("Metrics Reporter: You must include an accountId");
|
||||
}
|
||||
|
||||
const {IdentityStore, N1CloudAPI, NylasAPIRequest} = require('nylas-exports') // eslint-disable-line
|
||||
if (!IdentityStore.identity()) {
|
||||
throw new Error("Metrics Reporter: Identity must be available");
|
||||
const {N1CloudAPI, NylasAPIRequest} = require('nylas-exports') // eslint-disable-line
|
||||
const req = new NylasAPIRequest({
|
||||
api: N1CloudAPI,
|
||||
options: {
|
||||
path: `/ingest-metrics`,
|
||||
method: 'POST',
|
||||
body: info,
|
||||
accountId: info.accountId,
|
||||
},
|
||||
});
|
||||
await req.run()
|
||||
} else {
|
||||
this.sendToHoneycomb(info)
|
||||
}
|
||||
if (!dataToReport.accountId) {
|
||||
throw new Error("Metrics Reporter: You must include an accountId");
|
||||
}
|
||||
|
||||
const req = new NylasAPIRequest({
|
||||
api: N1CloudAPI,
|
||||
options: {
|
||||
path: `/ingest-metrics`,
|
||||
method: 'POST',
|
||||
body: dataToReport,
|
||||
accountId: dataToReport.accountId,
|
||||
},
|
||||
});
|
||||
await req.run()
|
||||
logger.log("Metrics Reporter: Submitted.", dataToReport);
|
||||
logger.log(info, "Metrics Reporter: Submitted.", info);
|
||||
} catch (err) {
|
||||
logger.warn("Metrics Reporter: Submission Failed.", {error: err, ...dataToReport});
|
||||
logger.warn("Metrics Reporter: Submission Failed.", {error: err, ...info});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue