From 11b731891f3dac0d96640cca10d1b07a073415e2 Mon Sep 17 00:00:00 2001 From: Evan Morikawa Date: Wed, 23 Dec 2015 17:25:30 -0800 Subject: [PATCH] feat(extension): async extensions Summary: WIP: This is a quick patch for Drew to make extensions async We'll need to think through the upgrade/deprecation plan to roll out async extensions across all of our APIs. Test Plan: TODO Reviewers: drew, evan, bengotow Reviewed By: bengotow Differential Revision: https://phab.nylas.com/D2392 --- docs/ComposerExtensions.md | 3 ++- ... => availability-composer-extension.coffee} | 18 +++++++++++++++--- .../lib/spellcheck-composer-extension.coffee | 2 +- .../spellcheck-composer-extension-spec.coffee | 7 ++++--- .../composer/spec/composer-view-spec.cjsx | 2 +- src/extensions/composer-extension.coffee | 10 ++++++---- src/flux/stores/draft-store.coffee | 16 +++++++--------- 7 files changed, 36 insertions(+), 22 deletions(-) rename examples/N1-Quick-Schedule/lib/{availability-composer-extension.cjsx => availability-composer-extension.coffee} (54%) diff --git a/docs/ComposerExtensions.md b/docs/ComposerExtensions.md index 528c0abf2..27885cce1 100644 --- a/docs/ComposerExtensions.md +++ b/docs/ComposerExtensions.md @@ -41,5 +41,6 @@ class ProductsExtension extends ComposerExtension bodyWithWarning = draft.body += "
This email \ contains competitor's product names \ or trademarks used in context." - session.changes.add(body: bodyWithWarning) + return session.changes.add(body: bodyWithWarning) + else return Promise.resolve() ``` diff --git a/examples/N1-Quick-Schedule/lib/availability-composer-extension.cjsx b/examples/N1-Quick-Schedule/lib/availability-composer-extension.coffee similarity index 54% rename from examples/N1-Quick-Schedule/lib/availability-composer-extension.cjsx rename to examples/N1-Quick-Schedule/lib/availability-composer-extension.coffee index 78d7da2f7..c26355295 100644 --- a/examples/N1-Quick-Schedule/lib/availability-composer-extension.cjsx +++ b/examples/N1-Quick-Schedule/lib/availability-composer-extension.coffee @@ -1,12 +1,13 @@ {ComposerExtension} = require 'nylas-exports' request = require 'request' +post = Promise.promisify(request.post, multiArgs: true) class AvailabilityComposerExtension extends ComposerExtension # When subclassing the ComposerExtension, you can add your own custom logic # to execute before a draft is sent in the @finalizeSessionBeforeSending # method. Here, we're registering the events before we send the draft. - @finalizeSessionBeforeSending: ({session}) -> + @finalizeSessionBeforeSending: (session) -> body = session.draft().body participants = session.draft().participants() sender = session.draft().from @@ -18,8 +19,19 @@ class AvailabilityComposerExtension extends ComposerExtension data.attendees = participants.map (p) -> name: p.name, email: p.email, isSender: p.isMe() serverUrl = "https://quickschedule.herokuapp.com/register-events" - request.post {url: serverUrl, body: JSON.stringify(data)}, (error, resp, data) => - console.log(error,resp,data) + post({url: serverUrl, body: JSON.stringify(data)}) + .then (args) => + data = args[1] + return data + .catch (error) -> + dialog = require('remote').require('dialog') + dialog.showErrorBox('Error creating QuickSchedule event', + "There was a problem connecting to the QuickSchedule server. Make sure you're connected to the internet and "+ + "try sending again. If problems persist, contact the N1 team (using the blue question icon at the bottom right "+ + "of your inbox) and we'll get right on it!") + Promise.reject(error) + else + Promise.resolve() module.exports = AvailabilityComposerExtension diff --git a/internal_packages/composer-spellcheck/lib/spellcheck-composer-extension.coffee b/internal_packages/composer-spellcheck/lib/spellcheck-composer-extension.coffee index b8fa9c66a..5af140d17 100644 --- a/internal_packages/composer-spellcheck/lib/spellcheck-composer-extension.coffee +++ b/internal_packages/composer-spellcheck/lib/spellcheck-composer-extension.coffee @@ -123,7 +123,7 @@ class SpellcheckComposerExtension extends ComposerExtension body = session.draft().body clean = body.replace(/<\/?spelling[^>]*>/g, '') if body != clean - session.changes.add(body: clean) + return session.changes.add(body: clean) SpellcheckComposerExtension.SpellcheckCache = SpellcheckCache diff --git a/internal_packages/composer-spellcheck/spec/spellcheck-composer-extension-spec.coffee b/internal_packages/composer-spellcheck/spec/spellcheck-composer-extension-spec.coffee index e63709758..422fc2f70 100644 --- a/internal_packages/composer-spellcheck/spec/spellcheck-composer-extension-spec.coffee +++ b/internal_packages/composer-spellcheck/spec/spellcheck-composer-extension-spec.coffee @@ -25,9 +25,10 @@ describe "SpellcheckComposerExtension", -> draft: -> body: expectedHTML changes: - add: jasmine.createSpy('add') + add: jasmine.createSpy('add').andReturn Promise.resolve() - SpellcheckComposerExtension.finalizeSessionBeforeSending({session}) - expect(session.changes.add).toHaveBeenCalledWith(body: initialHTML) + waitsForPromise -> + SpellcheckComposerExtension.finalizeSessionBeforeSending(session).then -> + expect(session.changes.add).toHaveBeenCalledWith(body: initialHTML) module.exports = SpellcheckComposerExtension diff --git a/internal_packages/composer/spec/composer-view-spec.cjsx b/internal_packages/composer/spec/composer-view-spec.cjsx index 8a26915af..bf6766d07 100644 --- a/internal_packages/composer/spec/composer-view-spec.cjsx +++ b/internal_packages/composer/spec/composer-view-spec.cjsx @@ -124,7 +124,7 @@ describe "ComposerView", -> # `componentWillMount`, we manually call sessionForClientId to make this # part of the test synchronous. We need to make the `then` block of the # sessionForClientId do nothing so `_setupSession` is not called twice! - spyOn(DraftStore, "sessionForClientId").andCallFake -> then: -> + spyOn(DraftStore, "sessionForClientId").andReturn then: -> then: -> useFullDraft = -> useDraft.call @, diff --git a/src/extensions/composer-extension.coffee b/src/extensions/composer-extension.coffee index 183bb5668..7a7c8b30c 100644 --- a/src/extensions/composer-extension.coffee +++ b/src/extensions/composer-extension.coffee @@ -28,8 +28,8 @@ session you receive in {::finalizeSessionBeforeSending} is for the same draft you previously received in {::warningsForSending}, etc. The ComposerExtension API does not currently expose any asynchronous or -{Promise}-based APIs. This will likely change in the future. If you have -a use-case for a ComposerExtension that is not possible with the current +{Promise}-based APIs, except for finalizeSessionBeforeSending. This will likely +change in the future. If you havea use-case for a ComposerExtension that is not possible with the current API, please let us know. Section: Extensions @@ -96,6 +96,8 @@ class ComposerExtension extends ContenteditableExtension the draft is sent. This method gives you an opportunity to make any final substitutions or changes after any {::warningsForSending} have been displayed. + If you want to perform asynchronous work, you this method can return a promise, + however, returning a Promise is not required. - `session`: A {DraftStoreProxy} for the draft. @@ -110,7 +112,7 @@ class ComposerExtension extends ContenteditableExtension session.changes.add(body: clean) ``` ### - @finalizeSessionBeforeSending: ({session}) -> - return + @finalizeSessionBeforeSending: (session) -> + return Promise.resolve(session) module.exports = ComposerExtension diff --git a/src/flux/stores/draft-store.coffee b/src/flux/stores/draft-store.coffee index 3d1a80ed9..19e13636c 100644 --- a/src/flux/stores/draft-store.coffee +++ b/src/flux/stores/draft-store.coffee @@ -496,10 +496,9 @@ class DraftStore # point there are still unpersisted changes in the DraftStoreProxy. If # we `trigger`, we'll briefly display the wrong version of the draft # as if it was sending. - - @sessionForClientId(draftClientId).then (session) => - @_runExtensionsBeforeSend(session) - + @sessionForClientId(draftClientId) + .then(@_runExtensionsBeforeSend) + .then (session) => # Immediately save any pending changes so we don't save after # sending # @@ -511,7 +510,6 @@ class DraftStore # committed to the Database since we'll look them up again just # before send. session.changes.commit(force: true, noSyncback: true).then => - # We unfortunately can't give the SendDraftTask the raw draft JSON # data because there may still be pending tasks (like a # {FileUploadTask}) that will continue to update the draft data. @@ -532,10 +530,10 @@ class DraftStore NylasEnv.getWindowType() is "composer" # Give third-party plugins an opportunity to sanitize draft data - _runExtensionsBeforeSend: (session) -> - for extension in @extensions() - continue unless extension.finalizeSessionBeforeSending - extension.finalizeSessionBeforeSending({session}) + _runExtensionsBeforeSend: (session) => + Promise.each @extensions(), (ext) -> + ext.finalizeSessionBeforeSending(session) + .return(session) _onRemoveFile: ({file, messageClientId}) => @sessionForClientId(messageClientId).then (session) ->