From a84722d38df3f2c162f0a1d26e3afee9e1c69d75 Mon Sep 17 00:00:00 2001 From: Evan Morikawa Date: Tue, 21 Jul 2015 12:33:40 -0700 Subject: [PATCH] fix(salesforce): better message syncing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Summary: Fixes: T2196 While new incoming messages were syncing properly, a new message you sent was not getting synced. Since we re-fetch all of the `Messages` immediately after a send, we had to be sure that the `sendDraftSuccess` Action fired after the new Message had been persisted to the DB. Unfortunately, `_handleModelResponse` was returning before the persist happened. This makes `_handleModelResponse`, and by extension `NylasAPI::makeRequest`, resolve only after the new models have been persisted to the DB. We also were just dumping all of the HTML into the synced SalesforceObject thread, making it entirely unreadable. We now grab the `innerText` instead, which works great for basic conversations. It usually includes the `On {date} {person} wrote:` information, except for the first message, which we manually include. The next version will likely create individual tasks for each individual message, however, doing this properly will require a much more complex Message <-> SFObject syncing task, as well as tying into the QuotedText enging so we only sync the correct pieces. We'll also likely need a more sophisticated plain text interpreter other then `innerText`. I wish we could get access to the raw TEXT MIME part… Test Plan: manual Reviewers: bengotow Reviewed By: bengotow Subscribers: gleb Differential Revision: https://phab.nylas.com/D1766 --- src/flux/models/contact.coffee | 4 ++++ src/flux/models/message.coffee | 13 ++++++++++++ src/flux/nylas-api.coffee | 33 ++++++++++++++++++++---------- src/flux/stores/draft-store.coffee | 14 +++---------- 4 files changed, 42 insertions(+), 22 deletions(-) diff --git a/src/flux/models/contact.coffee b/src/flux/models/contact.coffee index 4dbde8c85..69a97fc81 100644 --- a/src/flux/models/contact.coffee +++ b/src/flux/models/contact.coffee @@ -70,6 +70,10 @@ class Contact extends Model return "You" if @email is NamespaceStore.current()?.emailAddress @_nameParts().join(' ') + # Full Name + messageName: -> + if @name then "#{@name} <#{@email}>" else @email + displayFirstName: -> return "You" if @email is NamespaceStore.current()?.emailAddress @firstName() diff --git a/src/flux/models/message.coffee b/src/flux/models/message.coffee index 94db2ab0e..480de5808 100644 --- a/src/flux/models/message.coffee +++ b/src/flux/models/message.coffee @@ -1,4 +1,5 @@ _ = require 'underscore' +moment = require 'moment' File = require './file' Label = require './label' @@ -234,4 +235,16 @@ class Message extends Model fileIds: -> _.map @files, (file) -> file.id + plainTextBody: -> + if (@body ? "").trim().length is 0 then return "" + (new DOMParser()).parseFromString(@body, "text/html").body.innerText + + fromContact: -> + @from?[0] ? new Contact(name: 'Unknown', email: 'Unknown') + + replyAttributionLine: -> + "On #{@formattedDate()}, #{@fromContact().messageName()} wrote:" + + formattedDate: -> moment(@date).format("MMM D YYYY, [at] h:mm a") + module.exports = Message diff --git a/src/flux/nylas-api.coffee b/src/flux/nylas-api.coffee index 77c2ae426..5bee7880f 100644 --- a/src/flux/nylas-api.coffee +++ b/src/flux/nylas-api.coffee @@ -187,17 +187,20 @@ class NylasAPI success = (body) => if options.beforeProcessing body = options.beforeProcessing(body) + return Promise.resolve(body) if options.returnsModel - @_handleModelResponse(body) - Promise.resolve(body) + @_handleModelResponse(body).then (objects) -> + return Promise.resolve(body) error = (err) => if err.response + handlePromise = Promise.resolve() if err.response.statusCode is 404 and options.returnsModel - @_handleModel404(options.url) + handlePromise = @_handleModel404(options.url) if err.response.statusCode is 401 - @_handle401(options.url) - Promise.reject(err) + handlePromise = @_handle401(options.url) + handlePromise.then -> + Promise.reject(err) req = new NylasAPIRequest(@, options) req.run().then(success, error) @@ -221,7 +224,11 @@ class NylasAPI if klass and klassId and klassId.length > 0 console.warn("Deleting #{klass.name}:#{klassId} due to API 404") DatabaseStore.find(klass, klassId).then (model) -> - DatabaseStore.unpersistModel(model) if model + if model + return DatabaseStore.unpersistModel(model) + else return Promise.resolve() + else + return Promise.resolve() _handle401: (modelUrl) -> Actions.postNotification @@ -241,6 +248,8 @@ class NylasAPI atom.logout() @_notificationUnlisten = Actions.notificationActionTaken.listen(handler, @) + return Promise.resolve() + _handleDeltas: (deltas) -> Actions.longPollReceivedRawDeltas(deltas) console.log("Processing Deltas") @@ -289,6 +298,8 @@ class NylasAPI Promise.settle(destroyPromises) + # Returns a Promsie that resolves when any parsed out models (if any) + # have been created and persisted to the database. _handleModelResponse: (jsons) -> if not jsons return Promise.reject(new Error("handleModelResponse with no JSON provided")) @@ -297,11 +308,11 @@ class NylasAPI if uniquedJSONs.length < jsons.length console.warn("NylasAPI.handleModelResponse: called with non-unique object set. Maybe an API request returned the same object more than once?") - return Promise.filter(uniquedJSONs, @_shouldAcceptModel) - .map(modelFromJSON) - .then (objects) -> - DatabaseStore.persistModels(objects) - return Promise.resolve(objects) + Promise.filter(uniquedJSONs, @_shouldAcceptModel) + .map(modelFromJSON) + .then (objects) -> + DatabaseStore.persistModels(objects).then -> + return Promise.resolve(objects) _shouldAcceptModel: (model) => return Promise.resolve(false) unless model diff --git a/src/flux/stores/draft-store.coffee b/src/flux/stores/draft-store.coffee index 9ac062d06..65eaf3115 100644 --- a/src/flux/stores/draft-store.coffee +++ b/src/flux/stores/draft-store.coffee @@ -258,25 +258,17 @@ class DraftStore attributes.subject ?= subjectWithPrefix(thread.subject, 'Re:') attributes.body ?= "" - # A few helpers for formatting - contactString = (c) -> - if c.name then "#{c.name} <#{c.email}>" else c.email - contactStrings = (cs) -> - _.map(cs, contactString).join(", ") - messageDate = (d) -> - moment(d).format("MMM D YYYY, [at] h:mm a") + contactStrings = (cs) -> _.invoke(cs, "messageName").join(", ") if attributes.replyToMessage msg = attributes.replyToMessage - contact = msg.from[0] ? new Contact(name: 'Unknown', email:'Unknown') - attribution = "On #{messageDate(msg.date)}, #{contactString(contact)} wrote:" attributes.subject = subjectWithPrefix(msg.subject, 'Re:') attributes.replyToMessageId = msg.id attributes.body = """

- #{attribution} + #{msg.replyAttributionLine()}
#{@_formatBodyForQuoting(msg.body)}
""" @@ -287,7 +279,7 @@ class DraftStore fields = [] fields.push("From: #{contactStrings(msg.from)}") if msg.from.length > 0 fields.push("Subject: #{msg.subject}") - fields.push("Date: #{messageDate(msg.date)}") + fields.push("Date: #{msg.formattedDate()}") fields.push("To: #{contactStrings(msg.to)}") if msg.to.length > 0 fields.push("CC: #{contactStrings(msg.cc)}") if msg.cc.length > 0 fields.push("BCC: #{contactStrings(msg.bcc)}") if msg.bcc.length > 0