mirror of
https://github.com/Foundry376/Mailspring.git
synced 2025-09-15 09:06:36 +08:00
feat(salesforce): add Lead and Contact syncing
Summary: associating salesforce contacts and leads with Nylas contacts adding fetcher form salesforce shows conencted leads and contacts auto associates acount fixing salesforce forms Salesforce composers are styled fix opportunity association with account on object creation fix specs Test Plan: edgehill --test Reviewers: bengotow Reviewed By: bengotow Differential Revision: https://review.inboxapp.com/D1507
This commit is contained in:
parent
214c8b2aaa
commit
96ead235ff
8 changed files with 87 additions and 32 deletions
|
@ -1,37 +1,41 @@
|
|||
_ = require 'underscore-plus'
|
||||
Reflux = require 'reflux'
|
||||
request = require 'request'
|
||||
{FocusedContactsStore} = require 'inbox-exports'
|
||||
{Contact, ContactStore, DatabaseStore, FocusedContactsStore} = require 'inbox-exports'
|
||||
|
||||
module.exports =
|
||||
FullContactStore = Reflux.createStore
|
||||
|
||||
init: ->
|
||||
@_fetchAPIData = _.debounce(_.bind(@__fetchAPIData, @), 50)
|
||||
@_cachedContactData = {}
|
||||
@listenTo FocusedContactsStore, @_onFocusedContacts
|
||||
@_loadContactDataFromAPI = _.debounce(_.bind(@__loadContactDataFromAPI, @), 50)
|
||||
# @_cachedContactData = {}
|
||||
@_resolvedFocusedContact = null
|
||||
@_loadFocusedContact = _.debounce(_.bind(@_loadFocusedContact, @), 20)
|
||||
@_loadFocusedContact()
|
||||
|
||||
sortedContacts: -> FocusedContactsStore.sortedContacts()
|
||||
focusedContact: -> FocusedContactsStore.focusedContact()
|
||||
@listenTo ContactStore, @_loadFocusedContact
|
||||
@listenTo FocusedContactsStore, @_loadFocusedContact
|
||||
|
||||
fullContactCache: ->
|
||||
emails = {}
|
||||
contacts = FocusedContactsStore.sortedContacts()
|
||||
emails[contact.email] = contact for contact in contacts
|
||||
fullContactCache = {}
|
||||
_.each @_cachedContactData, (fullContactData, email) ->
|
||||
if email of emails then fullContactCache[email] = fullContactData
|
||||
return fullContactCache
|
||||
focusedContact: -> @_resolvedFocusedContact
|
||||
|
||||
_onFocusedContacts: ->
|
||||
contact = FocusedContactsStore.focusedContact() ? {}
|
||||
if not @_cachedContactData[contact.email]
|
||||
@_fetchAPIData(contact.email)
|
||||
@trigger()
|
||||
# We need to pull fresh from the database so when we update data in the
|
||||
# for the contact, we get it anew.
|
||||
_loadFocusedContact: ->
|
||||
contact = FocusedContactsStore.focusedContact()
|
||||
if contact
|
||||
@_resolvedFocusedContact = contact
|
||||
DatabaseStore.findBy(Contact, email: contact.email).then (contact) =>
|
||||
@_resolvedFocusedContact = contact
|
||||
if contact and not contact.thirdPartyData?["FullContact"]?
|
||||
@_loadContactDataFromAPI(contact)
|
||||
@trigger()
|
||||
else
|
||||
@_resolvedFocusedContact = null
|
||||
@trigger()
|
||||
|
||||
__fetchAPIData: (email="") ->
|
||||
__loadContactDataFromAPI: (contact) ->
|
||||
# Swap the url's to see real data
|
||||
email = email.toLowerCase().trim()
|
||||
email = contact.email.toLowerCase().trim()
|
||||
return if email.length is 0
|
||||
url = "https://api.fullcontact.com/v2/person.json?email=#{email}&apiKey=eadcbaf0286562a"
|
||||
request url, (err, resp, data) =>
|
||||
|
@ -39,5 +43,12 @@ FullContactStore = Reflux.createStore
|
|||
return {} if resp.statusCode != 200
|
||||
try
|
||||
data = JSON.parse data
|
||||
@_cachedContactData[email] = data
|
||||
@trigger(@)
|
||||
contact = @_mergeDataIntoContact(contact, data)
|
||||
DatabaseStore.persistModel(contact).then => @trigger(@)
|
||||
|
||||
_mergeDataIntoContact: (contact, data) ->
|
||||
contact.title = data.organizations?[0]?["title"]
|
||||
contact.company = data.organizations?[0]?["name"]
|
||||
contact.thirdPartyData ?= {}
|
||||
contact.thirdPartyData["FullContact"] = data
|
||||
return contact
|
||||
|
|
|
@ -16,6 +16,7 @@ class SidebarFullContactDetails extends React.Component
|
|||
<div className="header">
|
||||
{@_profilePhoto()}
|
||||
<h1 className="name">{@_name()}</h1>
|
||||
<div className="email">{@_email()}</div>
|
||||
</div>
|
||||
<div className="subheader"
|
||||
style={display: if @_showSubheader() then "block" else "none"}>
|
||||
|
@ -81,7 +82,13 @@ class SidebarFullContactDetails extends React.Component
|
|||
@_title().length > 0 or @_company().length > 0
|
||||
|
||||
_name: =>
|
||||
(@props.fullContact.contactInfo?.fullName) ? @props.contact?.name
|
||||
(@props.fullContact.contactInfo?.fullName) ? @props.contact?.name ? ""
|
||||
|
||||
_email: =>
|
||||
email = @props.contact.email ? ""
|
||||
if @_name().toLowerCase().trim() isnt email.toLowerCase().trim()
|
||||
return email
|
||||
else return ""
|
||||
|
||||
_title: =>
|
||||
org = @_primaryOrg()
|
||||
|
|
|
@ -2,6 +2,8 @@ _ = require 'underscore-plus'
|
|||
React = require "react"
|
||||
FullContactStore = require "./fullcontact-store"
|
||||
|
||||
{InjectedComponentSet} = require 'ui-components'
|
||||
|
||||
SidebarFullContactDetails = require "./sidebar-fullcontact-details"
|
||||
|
||||
class SidebarFullContact extends React.Component
|
||||
|
@ -10,6 +12,7 @@ class SidebarFullContact extends React.Component
|
|||
order: 1
|
||||
maxWidth: 300
|
||||
minWidth: 200
|
||||
flexShrink: 0
|
||||
|
||||
constructor: (@props) ->
|
||||
@state = @_getStateFromStores()
|
||||
|
@ -24,11 +27,13 @@ class SidebarFullContact extends React.Component
|
|||
<div className="full-contact-sidebar">
|
||||
<SidebarFullContactDetails contact={@state.focusedContact ? {}}
|
||||
fullContact={@_fullContact()}/>
|
||||
<InjectedComponentSet matching={role: "sidebar:focusedContactInfo"}
|
||||
exposedProps={focusedContact: @state.focusedContact}/>
|
||||
</div>
|
||||
|
||||
_fullContact: =>
|
||||
if @state.focusedContact?.email
|
||||
return @state.fullContactCache[@state.focusedContact.email] ? {}
|
||||
if @state.focusedContact?.thirdPartyData
|
||||
return @state.focusedContact?.thirdPartyData["FullContact"] ? {}
|
||||
else
|
||||
return {}
|
||||
|
||||
|
@ -36,7 +41,6 @@ class SidebarFullContact extends React.Component
|
|||
@setState(@_getStateFromStores())
|
||||
|
||||
_getStateFromStores: =>
|
||||
fullContactCache: FullContactStore.fullContactCache()
|
||||
focusedContact: FullContactStore.focusedContact()
|
||||
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@ fixtureModule = 'internal_packages/salesforce'
|
|||
Adapter = require path.join('../../', fixtureModule, 'lib/salesforce-schema-adapter.coffee')
|
||||
fpath = path.join(fixtureModule, 'spec/fixtures/opportunity-layouts.json')
|
||||
rawData = JSON.parse(fs.readFileSync(fpath, 'utf-8'))
|
||||
testData = Adapter.convertLayout("opportunity", rawData)
|
||||
testData = Adapter.convertFullEditLayout("opportunity", rawData)
|
||||
|
||||
describe "Form Builder", ->
|
||||
beforeEach ->
|
||||
|
|
|
@ -232,7 +232,7 @@ class GeneratedFieldset extends React.Component
|
|||
|
||||
for item, i in items
|
||||
itemsWithSpacers.push(item)
|
||||
if i isnt items.length - 1 or items.length is 1
|
||||
if i isnt items.length - 1
|
||||
itemsWithSpacers.push(spacer: true)
|
||||
|
||||
<div className="row"
|
||||
|
|
|
@ -17,6 +17,14 @@ For more information about Contacts on the Nylas Platform, read the
|
|||
|
||||
`email`: {AttributeString} The email address of the contact. Queryable.
|
||||
|
||||
`thirdPartyData`: {AttributeObject} Extra data that we find out about a
|
||||
contact. The data is keyed by the service that dumped the data there e.g.
|
||||
"salesforce" or "fullcontact". The value is an object of raw data in the
|
||||
form that the service provides
|
||||
|
||||
We also have "normalized" optional data for each contact. This list may
|
||||
grow as the needs of a contact become more complex.
|
||||
|
||||
This class also inherits attributes from {Model}
|
||||
|
||||
Section: Models
|
||||
|
@ -32,6 +40,19 @@ class Contact extends Model
|
|||
queryable: true
|
||||
modelKey: 'email'
|
||||
|
||||
# Contains the raw thirdPartyData (keyed by the vendor name) about
|
||||
# this contact.
|
||||
'thirdPartyData': Attributes.Object
|
||||
modelKey: 'thirdPartyData'
|
||||
|
||||
# The following are "normalized" fields that we can use to consolidate
|
||||
# various thirdPartyData source. These list of attributes should
|
||||
# always be optional and may change as the needs of a Nylas contact
|
||||
# change over time.
|
||||
'title': Attributes.String(modelKey: 'title')
|
||||
'phone': Attributes.String(modelKey: 'phone')
|
||||
'company': Attributes.String(modelKey: 'company')
|
||||
|
||||
# Used to uniquely identify a contact
|
||||
nameEmail: ->
|
||||
"#{(@name ? "").toLowerCase().trim()} #{@email.toLowerCase().trim()}"
|
||||
|
|
|
@ -81,7 +81,7 @@ class ContactStore
|
|||
|
||||
matches
|
||||
|
||||
_refreshCache: =>
|
||||
__refreshCache: =>
|
||||
new Promise (resolve, reject) =>
|
||||
DatabaseStore.findAll(Contact)
|
||||
.then (contacts=[]) =>
|
||||
|
@ -89,6 +89,7 @@ class ContactStore
|
|||
@trigger()
|
||||
resolve()
|
||||
.catch(reject)
|
||||
_refreshCache: _.debounce(ContactStore::__refreshCache, 20)
|
||||
|
||||
_onDatabaseChanged: (change) =>
|
||||
return unless change?.objectClass is Contact.name
|
||||
|
|
|
@ -2,7 +2,18 @@
|
|||
@import "ui-mixins";
|
||||
|
||||
input[type="text"],
|
||||
input[type="email"] {
|
||||
input[type="email"],
|
||||
input[type="date"],
|
||||
input[type="datetime"],
|
||||
input[type="datetime-local"],
|
||||
input[type="month"],
|
||||
input[type="number"],
|
||||
input[type="password"],
|
||||
input[type="range"],
|
||||
input[type="search"],
|
||||
input[type="tel"],
|
||||
input[type="time"],
|
||||
input[type="url"] {
|
||||
width: 100%;
|
||||
padding-left: @padding-xs-horizontal;
|
||||
padding-right: @padding-xs-horizontal;
|
||||
|
@ -20,4 +31,4 @@ input[type="email"]:focus, {
|
|||
&.input-bordered {
|
||||
box-shadow: 0 0 3px @accent-primary;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue