From 2af2c41572a56d429e39b4d514b9fb0bbc338d44 Mon Sep 17 00:00:00 2001 From: Eric Date: Mon, 27 May 2024 16:43:40 -0500 Subject: [PATCH] Website: Update round robin in salesforce helpers & move lead creation to save-questionnaire-progress. (#19209) Closes: #18932 Changes: - Updated `update-or-create-contact-and-account.js` to not round robin new account records created. - Updated create-lead to reassign accounts owned by the integrations admin user when a new lead is created. - Updated logged warnings in signup, deliver-contact-form-message and deliver-talk-to-us-form-submission. - Updated signup.js to not create salesforce leads (only account and contact records) - Updated save-questionnaire-progress to create Salesforce leads when we know the user has a use case for Fleet. --- .../deliver-contact-form-message.js | 2 +- .../deliver-talk-to-us-form-submission.js | 2 +- website/api/controllers/entrance/signup.js | 5 +- .../save-questionnaire-progress.js | 71 ++++++++++--------- website/api/helpers/salesforce/create-lead.js | 31 +++++++- .../update-or-create-contact-and-account.js | 61 +++++----------- 6 files changed, 89 insertions(+), 83 deletions(-) diff --git a/website/api/controllers/deliver-contact-form-message.js b/website/api/controllers/deliver-contact-form-message.js index 03696e07b2..78a47552d5 100644 --- a/website/api/controllers/deliver-contact-form-message.js +++ b/website/api/controllers/deliver-contact-form-message.js @@ -80,7 +80,7 @@ module.exports = { leadSource: 'Website - Contact forms', leadDescription: `Sent a contact form message: ${message}`, }).tolerate((err)=>{ - sails.log.warn(`Background task failed: When a user submitted a contact form message, a lead/contact could not be updated in the CRM for this email address: ${emailAddress}. Error:`, err.raw); + sails.log.warn(`Background task failed: When a user submitted a contact form message, a lead/contact could not be updated in the CRM for this email address: ${emailAddress}.`, err); }); });//_∏_ (Meanwhile...) diff --git a/website/api/controllers/deliver-talk-to-us-form-submission.js b/website/api/controllers/deliver-talk-to-us-form-submission.js index 9978b856f5..4d5d7c6cb7 100644 --- a/website/api/controllers/deliver-talk-to-us-form-submission.js +++ b/website/api/controllers/deliver-talk-to-us-form-submission.js @@ -86,7 +86,7 @@ module.exports = { leadSource: 'Website - Contact forms', leadDescription: `Submitted the "Talk to us" form.`, }).tolerate((err)=>{ - sails.log.warn(`Background task failed: When a user submitted the "Talk to us" form, a lead/contact could not be updated in the CRM for this email address: ${emailAddress}. Error:`, err.raw); + sails.log.warn(`Background task failed: When a user submitted the "Talk to us" form, a lead/contact could not be updated in the CRM for this email address: ${emailAddress}.`, err); }); });//_∏_ (Meanwhile...) diff --git a/website/api/controllers/entrance/signup.js b/website/api/controllers/entrance/signup.js index 2aecd88055..7ab841c007 100644 --- a/website/api/controllers/entrance/signup.js +++ b/website/api/controllers/entrance/signup.js @@ -140,14 +140,13 @@ the account verification message.)`, // Use timers.setImmediate() to update/create CRM records in the background. require('timers').setImmediate(async ()=>{ - await sails.helpers.salesforce.updateOrCreateContactAndAccountAndCreateLead.with({ + await sails.helpers.salesforce.updateOrCreateContactAndAccount.with({ emailAddress: newEmailAddress, firstName: firstName, lastName: lastName, organization: organization, - leadSource: 'Website - Sign up', }).tolerate((err)=>{ - sails.log.warn(`Background task failed: When a user (email: ${newEmailAddress} sign up for a fleetdm.com account, a Contact, Account, and Lead record could not be created/updated in the CRM. Error:`, err.raw); + sails.log.warn(`Background task failed: When a user (email: ${newEmailAddress} signed up for a fleetdm.com account, a Contact and Account record could not be created/updated in the CRM.`, err); }); });//_∏_ (Meanwhile...) diff --git a/website/api/controllers/save-questionnaire-progress.js b/website/api/controllers/save-questionnaire-progress.js index 3de73393a1..1d3bc90850 100644 --- a/website/api/controllers/save-questionnaire-progress.js +++ b/website/api/controllers/save-questionnaire-progress.js @@ -114,7 +114,8 @@ module.exports = { // - procurement-wants-some-stuff » No change (Stage ??) // - nothing » No change (Stage ??) - + // Set a variable to track whether we should create a Lead record in Salesforce. + let createLeadRecord = false; let psychologicalStage = userRecord.psychologicalStage; // Get the value of the submitted formData, we do this so we only need to check one variable, instead of (formData.attribute === 'foo'); let valueFromFormData = _.values(formData)[0]; @@ -125,8 +126,10 @@ module.exports = { } else if(currentStep === 'have-you-ever-used-fleet') { if(['yes-deployed'].includes(valueFromFormData)) { // If the user has Fleet deployed, set their stage to 6. + createLeadRecord = true; psychologicalStage = '6 - Has team buy-in'; } else if(valueFromFormData === 'yes-recently-deployed'){ + createLeadRecord = true; psychologicalStage = '5 - Personally confident'; } else if(valueFromFormData === 'yes-deployed-local'){ // If they've tried Fleet locally, set their stage to 3. @@ -151,6 +154,7 @@ module.exports = { psychologicalStage = '2 - Aware'; } } else {// Otherwise, they have a use case and will be set to stage 4. + createLeadRecord = true; psychologicalStage = '4 - Has use case'; } } else if(currentStep === 'is-it-any-good') { @@ -166,9 +170,11 @@ module.exports = { psychologicalStage = '2 - Aware'; } } else { + createLeadRecord = true; psychologicalStage = '4 - Has use case'; } } else {// For any other selected primary buying situation, since a use case will have been selected, set their psyStage to 4 + createLeadRecord = true; psychologicalStage = '4 - Has use case'; // FUTURE: check previous answers for other selected buying situations. } @@ -177,6 +183,7 @@ module.exports = { if(valueFromFormData === 'let-me-think-about-it') { psychologicalStage = '2 - Aware'; } else if (['deploy-fleet-in-environment','host-fleet-for-me'].includes(valueFromFormData)) { + createLeadRecord = true; psychologicalStage = '4 - Has use case'; } else { require('assert')(false,'This should never happen.'); } } else if(currentStep === 'how-was-your-deployment') { @@ -209,42 +216,40 @@ module.exports = { // Only update CRM records if the user's psychological stage changes. if(psychologicalStage !== userRecord.psychologicalStage) { + // If the createLeadRecord flag was set to true, create a lead record. + if(createLeadRecord){ // Use setImmediate to queue CRM updates. // [?]: https://nodejs.org/api/timers.html#setimmediatecallback-args - require('timers').setImmediate(async ()=>{ - await sails.helpers.salesforce.updateOrCreateContactAndAccount.with({ - emailAddress: this.req.me.emailAddress, - firstName: this.req.me.firstName, - lastName: this.req.me.lastName, - primaryBuyingSituation: primaryBuyingSituation === 'eo-security' ? 'Endpoint operations - Security' : primaryBuyingSituation === 'eo-it' ? 'Endpoint operations - IT' : primaryBuyingSituation === 'mdm' ? 'Device management (MDM)' : primaryBuyingSituation === 'vm' ? 'Vulnerability management' : undefined, - organization: this.req.me.organization, - psychologicalStage, - }).tolerate((err)=>{ - sails.log.warn(`Background task failed: When a user (email: ${this.req.me.emailAddress} submitted a step of the get started questionnaire, a Contact and Account record could not be created/updated in the CRM. Full error:`, err); - }); - });//_∏_ (Meanwhile...) + require('timers').setImmediate(async ()=>{ + await sails.helpers.salesforce.updateOrCreateContactAndAccountAndCreateLead.with({ + emailAddress: this.req.me.emailAddress, + firstName: this.req.me.firstName, + lastName: this.req.me.lastName, + primaryBuyingSituation: primaryBuyingSituation === 'eo-security' ? 'Endpoint operations - Security' : primaryBuyingSituation === 'eo-it' ? 'Endpoint operations - IT' : primaryBuyingSituation === 'mdm' ? 'Device management (MDM)' : primaryBuyingSituation === 'vm' ? 'Vulnerability management' : undefined, + organization: this.req.me.organization, + psychologicalStage, + leadSource: 'Website - Sign up', + }).tolerate((err)=>{ + sails.log.warn(`Background task failed: When a user (email: ${this.req.me.emailAddress} submitted a step of the get started questionnaire, a Contact and Account record could not be created/updated in the CRM.`, err); + }); + });//_∏_ (Meanwhile...) + } else { + // Otherwise, update or create a contact and account record. + require('timers').setImmediate(async ()=>{ + await sails.helpers.salesforce.updateOrCreateContactAndAccount.with({ + emailAddress: this.req.me.emailAddress, + firstName: this.req.me.firstName, + lastName: this.req.me.lastName, + primaryBuyingSituation: primaryBuyingSituation === 'eo-security' ? 'Endpoint operations - Security' : primaryBuyingSituation === 'eo-it' ? 'Endpoint operations - IT' : primaryBuyingSituation === 'mdm' ? 'Device management (MDM)' : primaryBuyingSituation === 'vm' ? 'Vulnerability management' : undefined, + organization: this.req.me.organization, + psychologicalStage, + }).tolerate((err)=>{ + sails.log.warn(`Background task failed: When a user (email: ${this.req.me.emailAddress} submitted a step of the get started questionnaire, a Contact and Account record could not be created/updated in the CRM.`, err); + }); + });//_∏_ (Meanwhile...) + } }//fi // TODO: send all other answers to Salesforce (when there are fields for them) - - // await sails.helpers.http.post.with({ - // url: 'https://hooks.zapier.com/hooks/catch/3627242/3nltwbg/', - // data: { - // emailAddress: this.req.me.emailAddress, - // firstName: this.req.me.firstName, - // lastName: this.req.me.lastName, - // primaryBuyingSituation: primaryBuyingSituation, - // organization: this.req.me.organization, - // psychologicalStage, - // currentStep, - // webhookSecret: sails.config.custom.zapierSandboxWebhookSecret, - // } - // }) - // .timeout(5000) - // .tolerate(['non200Response', 'requestFailed'], (err)=>{ - // // Note that Zapier responds with a 2xx status code even if something goes wrong, so just because this message is not logged doesn't mean everything is hunky dory. More info: https://github.com/fleetdm/fleet/pull/6380#issuecomment-1204395762 - // sails.log.warn(`When a user completed a questionnaire step, a lead/contact could not be updated in the CRM for this email address: ${this.req.me.emailAddress}. Raw error: ${err}`); - // return; - // }); // Set the user's answer to the current step. questionnaireProgress[currentStep] = formData; // Clone the questionnaireProgress to prevent any mutations from sending it through the updateOne Waterline method. diff --git a/website/api/helpers/salesforce/create-lead.js b/website/api/helpers/salesforce/create-lead.js index 7f7e28bd04..c936493e49 100644 --- a/website/api/helpers/salesforce/create-lead.js +++ b/website/api/helpers/salesforce/create-lead.js @@ -72,6 +72,35 @@ module.exports = { }).intercept((err)=>{ return new Error(`When attempting to create a Lead record using an exisitng Account record (ID: ${salesforceAccountId}), An error occured when retreiving the specified record. Error: ${err}`); }); + let salesforceAccountOwnerId = accountRecord.OwnerId; + // If the account record is owned by the integrations admin user and is not the account where unenriched contacts go, we'll round robin it and reassign it to an AE. + if(salesforceAccountOwnerId === '0054x00000735wDAAQ' && salesforceAccountId !== '0014x000025JC8DAAW') { + // Get all round robin users. + let roundRobinUsers = await salesforceConnection.sobject('User') + .find({ + AE_Round_robin__c: true,// eslint-disable-line camelcase + }); + // Get the user with the earliest round robin timestamp. + let userWithEarliestAssignTimeStamp = _.sortBy(roundRobinUsers, 'AE_Account_Assignment_round_robin__c')[0]; + + let today = new Date(); + let nowOn = today.toISOString().replace('Z', '+0000'); + // Update the salesforceAccountOwnerId value to be the ID of the next user in the round robin. + salesforceAccountOwnerId = userWithEarliestAssignTimeStamp.Id; + // Update this user to put them at the 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 + }); + // Reassign the account to the new owner. + await salesforceConnection.sobject('Account') + .update({ + Id: salesforceAccountId, + OwnerId: salesforceAccountOwnerId + }); + } let primaryBuyingSituationValuesByCodename = { 'vm': 'Vulnerability management', @@ -103,7 +132,7 @@ module.exports = { Primary_buying_scenario__c: primaryBuyingSituationToSet,// eslint-disable-line camelcase LinkedIn_profile__c: contactRecord.LinkedIn_profile__c,// eslint-disable-line camelcase // Information from the account record: - OwnerId: accountRecord.OwnerId + OwnerId: salesforceAccountOwnerId }); }).intercept((err)=>{ return new Error(`Could not create new Lead record. Error: ${err}`); diff --git a/website/api/helpers/salesforce/update-or-create-contact-and-account.js b/website/api/helpers/salesforce/update-or-create-contact-and-account.js index 6df60056d7..3388a161fb 100644 --- a/website/api/helpers/salesforce/update-or-create-contact-and-account.js +++ b/website/api/helpers/salesforce/update-or-create-contact-and-account.js @@ -94,60 +94,33 @@ module.exports = { // '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 we found an exisitng account and it is not owned by the integrations admin or a disabled user, we'll use assign the new contact to the account owner. - if(existingAccountRecord && !['0054x00000735wDAAQ', '0054x0000086wsQAAQ'].includes(existingAccountRecord.OwnerId)) { + // If we found an exisitng account, we'll use assign the new contact to the account owner. + if(existingAccountRecord) { // Store the ID of the Account record we found. salesforceAccountId = existingAccountRecord.Id; salesforceAccountOwnerId = existingAccountRecord.OwnerId; // console.log('exising account found!', salesforceAccountId); } else { - // If we didn't find an existing record, or found one onwned by the integrations admin or a disabled user, we'll round robin it between the AE's Salesforce users. - 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]; - + // If no existing account record was found, create a new one. + // Create a timestamp to use for the new account's assigned date. let today = new Date(); let nowOn = today.toISOString().replace('Z', '+0000'); - // Update the accountOwnerId value to be the ID of the next user in the round robin. - salesforceAccountOwnerId = userWithEarliestAssignTimeStamp.Id; - // Update this user to put them at the bottom of the round robin list. - await salesforceConnection.sobject('User') - .update({ - Id: salesforceAccountOwnerId, + + let newAccountRecord = await salesforceConnection.sobject('Account') + .create({ + Account_Assigned_date__c: nowOn,// eslint-disable-line camelcase // eslint-disable-next-line camelcase - AE_Account_Assignment_round_robin__c: nowOn + 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, }); - - - if(existingAccountRecord){ - // If we found an existing Account record owned by the integrations admin user account, reassign it to the new owner. - salesforceAccountId = existingAccountRecord.Id; - await salesforceConnection.sobject('Account') - .update({ - Id: salesforceAccountId, - OwnerId: salesforceAccountOwnerId - }); - } else { - // 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; - }//fi - // console.log('New account created!', salesforceAccountId); + salesforceAccountId = newAccountRecord.id; }//fi + // console.log('New account created!', salesforceAccountId); }//fi