fix(salesforce): better message syncing

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
This commit is contained in:
Evan Morikawa 2015-07-21 12:33:40 -07:00
parent a8effbc201
commit a84722d38d
4 changed files with 42 additions and 22 deletions

View file

@ -70,6 +70,10 @@ class Contact extends Model
return "You" if @email is NamespaceStore.current()?.emailAddress
@_nameParts().join(' ')
# Full Name <email@address.com>
messageName: ->
if @name then "#{@name} &lt;#{@email}&gt;" else @email
displayFirstName: ->
return "You" if @email is NamespaceStore.current()?.emailAddress
@firstName()

View file

@ -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

View file

@ -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

View file

@ -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} &lt;#{c.email}&gt;" 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 = """
<br><br><blockquote class="gmail_quote"
style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex;">
#{attribution}
#{msg.replyAttributionLine()}
<br>
#{@_formatBodyForQuoting(msg.body)}
</blockquote>"""
@ -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