Skip to content

Commit

Permalink
Add helpers as quick hack (#4)
Browse files Browse the repository at this point in the history
  • Loading branch information
mikermcneil committed Apr 26, 2024
1 parent cb762e2 commit 550afe6
Show file tree
Hide file tree
Showing 4 changed files with 618 additions and 2 deletions.
190 changes: 190 additions & 0 deletions api/helpers/iq/get-enriched.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
module.exports = {


friendlyName: 'Get enriched',


description: 'Search for the contact indicated and return enriched data.',


inputs: {

emailAddress: { type: 'string', defaultsTo: '', },
linkedinUrl: { type: 'string', defaultsTo: '', },
firstName: { type: 'string', defaultsTo: '', },
lastName: { type: 'string', defaultsTo: '', },
organization: { type: 'string', defaultsTo: '', },

},


exits: {

success: {
outputFriendlyName: 'Report',
outputDescription: 'All available, enriched info about this person and their current employer.',
outputType: {
person: {
emailAddress: 'string',
linkedinUrl: 'string',
firstName: 'string',
lastName: 'string',
organization: 'string',
title: 'string',
phone: 'string',
},
employer: {
organization: 'string',
numberOfEmployees: 'number',
emailDomain: 'string',
linkedinCompanyPageUrl: 'string',
technologies: [{name: 'string', category: 'string'}]
}
}
},

},


fn: async function ({emailAddress,linkedinUrl,firstName,lastName,organization}) {
require('assert')(sails.config.custom.iqSecret);

let RX_TECHNOLOGY_CATEGORIES = /(device|security|endpoint|configuration management|data management platforms|mobility management|identity|information technology|IT$|employee experience|apple)/i;

// [?] https://developer.leadiq.com/#query-searchPeople
// [?] https://developer.leadiq.com/#definition-SearchPeopleInput
// [?] https://graphql.org/learn/serving-over-http/
let emailDomain = emailAddress.match(/@([^@]+)$/) && emailAddress.match(/@([^@]+)$/)[1] || '';

let searchExpr = `{
${emailAddress? 'email: '+ JSON.stringify(emailAddress) : ''}
${linkedinUrl? 'linkedinUrl: '+ JSON.stringify(linkedinUrl) : ''}
${firstName? 'firstName: '+ JSON.stringify(firstName) : ''}
${lastName? 'lastName: '+ JSON.stringify(lastName) : ''}
${organization || emailDomain ? (`company: {
${organization? 'name: '+ JSON.stringify(organization) : ''}
${emailDomain? 'domain: '+ JSON.stringify(emailDomain)+' '+'emailDomain: '+ JSON.stringify(emailDomain) : ''}
searchInPastCompanies: false
strict: false
}`) : ''}
}`; //sails.log('GraphQL query:',searchExpr);
let report = await sails.helpers.http.get('https://api.leadiq.com/graphql', {
query: `{ searchPeople(input: ${searchExpr}) {
totalResults
results {
_id
name { first last }
linkedin { linkedinId linkedinUrl status updatedAt }
profiles { network id username url status updatedAt }
location { country areaLevel1 city fullAddress type status updatedAt }
personalPhones { value type status verificationStatus }
currentPositions {
title
emails { value type status }
phones { value type status verificationStatus }
companyInfo {
name
domain
country
address
linkedinUrl
numberOfEmployees
technologies { name category parentCategory attributes categories }
}
}
}
}
}`,
}, {
Authorization: `Basic ${sails.config.custom.iqSecret}`,
'content-type': 'application/json'
});

if (report.errors) {
sails.log.warn('Errors returned from IQ API when attempting to search for a matching contact:',report.errors);
}

// sails.log('person search results:',require('util').inspect(report.data.searchPeople.results, {depth:null}));
let foundPerson = report.data.searchPeople.results[0]; //sails.log('Found person:',foundPerson);
let foundPosition = foundPerson && foundPerson.currentPositions && foundPerson.currentPositions.length >= 1 ? foundPerson.currentPositions[0] : undefined;

let person;
if (foundPerson) {
person = {
emailAddress: emailAddress? emailAddress : foundPosition && foundPosition.emails[0]? foundPosition.emails[0].value : '',
linkedinUrl: linkedinUrl? linkedinUrl : foundPerson.linkedin.linkedinUrl,
firstName: firstName? firstName : foundPerson.name.first,
lastName: lastName? lastName : foundPerson.name.last,
organization: organization? organization : foundPosition? foundPosition.companyInfo.name : '',
title: foundPosition? foundPosition.title : '',
phone: foundPerson.personalPhones[0] && foundPerson.personalPhones[0].status !== 'Suppressed' ? foundPerson.personalPhones[0].value : '',
};
}//fi





// If no person was found, then try and look up the organization by itself.
let employer;
if (foundPosition) {
employer = {
organization: organization? organization : foundPosition.companyInfo.name || '',
numberOfEmployees: foundPosition.companyInfo.numberOfEmployees || 0,
emailDomain:( foundPosition.companyInfo.domain? foundPosition.companyInfo.domain : emailDomain )|| '',
linkedinCompanyPageUrl: foundPosition.companyInfo.linkedinUrl || '',
technologies: foundPosition.companyInfo.technologies? foundPosition.companyInfo.technologies
.filter((tech) => tech.category.match(RX_TECHNOLOGY_CATEGORIES))
.map((tech) => ({ name: tech.name, category: tech.category })) : []
};
} else {
let report = await sails.helpers.http.get('https://api.leadiq.com/graphql', {
query: `{ searchCompany(input: {
${organization? 'name: '+ JSON.stringify(organization) : ''}
${emailDomain? 'domain: '+ JSON.stringify(emailDomain) : ''}
}) {
totalResults
results {
name
domain
country
address
numberOfEmployees
linkedinUrl
technologies { name category parentCategory attributes categories }
}
}
}`
}, {
Authorization: `Basic ${sails.config.custom.iqSecret}`,
'content-type': 'application/json'
});
// sails.log('company search report:',report);

if (report.errors) {
sails.log.warn('Errors returned from IQ API when attempting to search directly for a matching organization:',report.errors);
}
let foundEmployer = report.data.searchCompany.results[0]; //sails.log(foundEmployer);
if (foundEmployer) {
employer = {
organization: organization? organization : foundEmployer.name || '',
numberOfEmployees: foundEmployer.numberOfEmployees || 0,
emailDomain: emailDomain? emailDomain : foundEmployer.domain || '',
linkedinCompanyPageUrl: foundEmployer.linkedinUrl || '',
technologies: foundEmployer.technologies? foundEmployer.technologies
.filter((tech) => tech.category.match(RX_TECHNOLOGY_CATEGORIES))
.map((tech) => ({ name: tech.name, category: tech.category })) : []
};// process.stdout.write(JSON.stringify(employer.technologies,0,2));
}
}//fi

return {
person,
employer
};

}


};

205 changes: 205 additions & 0 deletions api/helpers/salesforce/update-or-create-contact-and-account.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,205 @@
module.exports = {


friendlyName: 'Update or create contact and account',


description: 'Upsert contact±account into Salesforce given fresh data about a particular person, and fresh IQ-enrichment data about the person and account.',


inputs: {

// Find by…
emailAddress: { type: 'string' },
linkedinUrl: { type: 'string' },

// Set…
firstName: { type: 'string', required: true },
lastName: { type: 'string', required: true },
organization: { type: 'string' },
primaryBuyingSituation: { type: 'string' },
psychologicalStage: {
type: 'string',
isIn: [
'1 - Unaware',
'2 - Aware',
'3 - Intrigued',
'4 - Has use case',
'5 - Personally confident',
'6 - Has team buy-in'
]
},
},


exits: {

success: {
outputType: {
salesforceAccountId: 'string',
salesforceContactId: 'string'
}
},

},


fn: async function ({emailAddress, linkedinUrl, firstName, lastName, organization, primaryBuyingSituation, psychologicalStage}) {
require('assert')(sails.config.custom.salesforceIntegrationUsername);
require('assert')(sails.config.custom.salesforceIntegrationPasskey);
require('assert')(sails.config.custom.iqSecret);


if(!emailAddress && !linkedinUrl){
throw new Error('UsageError: when updating or creating a contact and account in salesforce, either an email or linkedInUrl is required.');
}
// Send the information we have to the enrichment helper.
let enrichmentData = await sails.helpers.iq.getEnriched(emailAddress, linkedinUrl, firstName, lastName, organization);
// console.log(enrichmentData);

// Log in to Salesforce.
let jsforce = require('jsforce');
let salesforceConnection = new jsforce.Connection({
loginUrl : 'https://fleetdm.my.salesforce.com'
});

let salesforceAccountOwnerId;

await salesforceConnection.login(sails.config.custom.salesforceIntegrationUsername, sails.config.custom.salesforceIntegrationPasskey);

let salesforceAccountId;
if(!organization && !enrichmentData.employer){
// Special sacraficial meat cave where the contacts with no organization go.
// https://fleetdm.lightning.force.com/lightning/r/Account/0014x000025JC8DAAW/view
salesforceAccountId = '0014x000025JC8DAAW';
salesforceAccountOwnerId = '0054x00000735wDAAQ';
} else {
let existingAccountRecord = await salesforceConnection.sobject('Account')
.findOne({
'Website': enrichmentData.employer.emailDomain,
// 'LinkedIn_company_URL__c': enrichmentData.employer.linkedinCompanyPageUrl // TODO: if this information is not present on an existing account, nothing will be returned.
});
// console.log(existingAccountRecord);
if(existingAccountRecord) {
// Store the ID of the Account record we found.
salesforceAccountId = existingAccountRecord.Id;
salesforceAccountOwnerId = existingAccountRecord.OwnerId;
// console.log('exising account found!', salesforceAccountId);
} else {


let roundRobinUsers = await salesforceConnection.sobject('User')
.find({
AE_Round_robin__c: true,// eslint-disable-line camelcase
});
let userWithEarliestAssignTimeStamp = _.sortBy(roundRobinUsers, 'AE_Account_Assignment_round_robin__c')[0];

let today = new Date();
let nowOn = today.toISOString().replace('Z', '+0000');

salesforceAccountOwnerId = userWithEarliestAssignTimeStamp.Id;

// Update this user to putthem atthe bottom of the round robin list.
await salesforceConnection.sobject('User')
.update({
Id: salesforceAccountOwnerId,
// eslint-disable-next-line camelcase
AE_Account_Assignment_round_robin__c: nowOn
});
// If no existing account record was found, create a new one.
let newAccountRecord = await salesforceConnection.sobject('Account')
.create({
OwnerId: salesforceAccountOwnerId,
Account_Assigned_date__c: nowOn,// eslint-disable-line camelcase
// eslint-disable-next-line camelcase
Current_Assignment_Reason__c: 'Inbound Lead',// TODO verify that this matters. if not, do not set it.
Prospect_Status__c: 'Assigned',// eslint-disable-line camelcase

Name: enrichmentData.employer.organization,// IFWMIH: We know organization exists
Website: enrichmentData.employer.emailDomain,
LinkedIn_company_URL__c: enrichmentData.employer.linkedinCompanyPageUrl,// eslint-disable-line camelcase
NumberOfEmployees: enrichmentData.employer.numberOfEmployees,
});
salesforceAccountId = newAccountRecord.id;
// console.log('New account created!', salesforceAccountId);
}
}



// Now search for an existing Contact.
// FUTURE: expand this section to improve the searches.
let existingContactRecord;
if(emailAddress){
// console.log('searching for existing contact by emailAddress');
existingContactRecord = await salesforceConnection.sobject('Contact')
.findOne({
AccountId: salesforceAccountId,
Email: emailAddress,
});
} else if(linkedinUrl) {
// console.log('searching for existing contact by linkedInUrl');
existingContactRecord = await salesforceConnection.sobject('Contact')
.findOne({
AccountId: salesforceAccountId,
LinkedIn_profile__c: linkedinUrl // eslint-disable-line camelcase
});
} else {
existingContactRecord = undefined;
}

let salesforceContactId;
let valuesToSet = {};
if(emailAddress || enrichmentData.person){
valuesToSet.Email = emailAddress || enrichmentData.person.emailAddress;
}
if(linkedinUrl || enrichmentData.person){
valuesToSet.LinkedIn_profile__c = linkedinUrl || enrichmentData.person.linkedinUrl;// eslint-disable-line camelcase
}
if(enrichmentData.person){
valuesToSet.Title = enrichmentData.person.title;
}
if(primaryBuyingSituation) {
valuesToSet.Primary_buying_situation__c = primaryBuyingSituation;// eslint-disable-line camelcase
}
if(psychologicalStage) {
valuesToSet.Stage__c = psychologicalStage;// eslint-disable-line camelcase
}


if(existingContactRecord){
salesforceContactId = existingContactRecord.Id;
// console.log(`existing contact record found! ${salesforceContactId}`);
// Update the existing contact with the information provided.
await salesforceConnection.sobject('Contact')
.update({
Id: salesforceContactId,
...valuesToSet,
});
// console.log(`${salesforceContactId} updated!`);
} else {
// Otherwise create a new Contact record.
let newContactRecord = await salesforceConnection.sobject('Contact')
.create({
AccountId: salesforceAccountId,
OwnerId: salesforceAccountOwnerId,
FirstName: firstName,
LastName: lastName,
...valuesToSet,
});
// console.log(newContactRecord);
salesforceContactId = newContactRecord.id;
// console.log(`New contact record created! ${salesforceContactId}`);
}


return {
salesforceAccountId,
salesforceContactId
};

}


};

0 comments on commit 550afe6

Please sign in to comment.