mirror of
https://github.com/Foundry376/Mailspring.git
synced 2024-09-21 07:46:06 +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
e8f7f76080
commit
8941eb791e
|
@ -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…
Reference in a new issue