From 9ed5ab37b5da85fa1c97189b681d71e8be3583b1 Mon Sep 17 00:00:00 2001 From: muhammad-ammar Date: Tue, 19 Sep 2023 18:06:58 +0500 Subject: [PATCH] feat: add support for optional salesforce ids in csv upload --- src/components/InviteLearnersModal/index.jsx | 6 +- src/data/validation/email.js | 75 ++++++++++++-- src/data/validation/email.test.js | 102 +++++++++++++++++++ 3 files changed, 173 insertions(+), 10 deletions(-) diff --git a/src/components/InviteLearnersModal/index.jsx b/src/components/InviteLearnersModal/index.jsx index 89ee1e62d6..29c0f75e89 100644 --- a/src/components/InviteLearnersModal/index.jsx +++ b/src/components/InviteLearnersModal/index.jsx @@ -10,7 +10,7 @@ import { camelCaseObject } from '@edx/frontend-platform/utils'; import emailTemplate from './emailTemplate'; import TextAreaAutoSize from '../TextAreaAutoSize'; import FileInput from '../FileInput'; -import { returnValidatedEmails, validateEmailAddrTemplateForm } from '../../data/validation/email'; +import { extractSalesforceIds, returnValidatedEmails, validateEmailAddrTemplateForm } from '../../data/validation/email'; import { normalizeFileUpload } from '../../utils'; class InviteLearnersModal extends React.Component { @@ -74,6 +74,10 @@ class InviteLearnersModal extends React.Component { }; options.user_emails = returnValidatedEmails(formData); + const SFIDs = extractSalesforceIds(formData, options.user_emails); + if (SFIDs) { + options.user_sfids = SFIDs; + } /* eslint-disable no-underscore-dangle */ return addLicensesForUsers(options, subscriptionUUID) diff --git a/src/data/validation/email.js b/src/data/validation/email.js index c39edc6585..a7f757d8a7 100644 --- a/src/data/validation/email.js +++ b/src/data/validation/email.js @@ -82,6 +82,33 @@ const validateEmailAddresses = (emails) => { return result; }; +// Each row in textarea or csv can contain email plus an optional salesforce id +// Email and salesforce id will be separated by comma. This function will read +// each row, split it by comma and then return an object with three properties: +// textEmails: All emails extracted from textarea +// csvEmails: All emails extracted from CSV +// allEmails: Concatenation of `textEmails` and `csvEmails` +const extractEmails = (formData) => { + let textEmails = []; + let csvEmails = []; + let allEmails = []; + + if (formData[EMAIL_ADDRESS_TEXT_FORM_DATA] && formData[EMAIL_ADDRESS_TEXT_FORM_DATA].length) { + textEmails = formData[EMAIL_ADDRESS_TEXT_FORM_DATA].split(/\r\n|\n/).map(item => item.split(',')[0]); + } + if (formData[EMAIL_ADDRESS_CSV_FORM_DATA] && formData[EMAIL_ADDRESS_CSV_FORM_DATA].length) { + csvEmails = formData[EMAIL_ADDRESS_CSV_FORM_DATA].map(item => item.split(',')[0]); + } + + allEmails = [...textEmails, ...csvEmails]; + + return { + textEmails, + csvEmails, + allEmails, + }; +}; + const validateEmailAddressesFields = (formData) => { // Validate that email address fields contain valid-looking emails. // Expects Redux form data @@ -90,11 +117,11 @@ const validateEmailAddressesFields = (formData) => { _error: [], }; - const textAreaEmails = formData[EMAIL_ADDRESS_TEXT_FORM_DATA] && formData[EMAIL_ADDRESS_TEXT_FORM_DATA].split(/\r\n|\n/); - const csvEmails = formData[EMAIL_ADDRESS_CSV_FORM_DATA]; + const { csvEmails, textEmails } = extractEmails(formData); + const emails = textEmails.length ? textEmails : csvEmails; let { invalidEmailIndices, - } = validateEmailAddresses(textAreaEmails || csvEmails); + } = validateEmailAddresses(emails); // 1 is added to every index to fix off-by-one error in messages shown to the user. invalidEmailIndices = invalidEmailIndices.map(i => i + 1); @@ -105,7 +132,7 @@ const validateEmailAddressesFields = (formData) => { ${invalidEmailIndices.length !== 0 ? `and ${lastEmail}` : `${lastEmail}`} \ is invalid. Please try again.`; - errorsDict[textAreaEmails ? EMAIL_ADDRESS_TEXT_FORM_DATA : EMAIL_ADDRESS_CSV_FORM_DATA] = message; + errorsDict[textEmails.length ? EMAIL_ADDRESS_TEXT_FORM_DATA : EMAIL_ADDRESS_CSV_FORM_DATA] = message; errorsDict._error.push(message); } @@ -155,19 +182,49 @@ const returnValidatedEmails = (formData) => { if (errorsDict._error.length > 0) { throw new SubmissionError(errorsDict); } - let emails = []; + + const emails = _.union(extractEmails(formData).allEmails); // Dedup emails + return validateEmailAddresses(emails).validEmails; +}; + +// Combine all the rows from textarea and CSV and then make a map of email to salesforce id +const getSalesforceIdsByEmail = (formData) => { + const rows = []; + const allRecords = {}; + if (formData[EMAIL_ADDRESS_TEXT_FORM_DATA] && formData[EMAIL_ADDRESS_TEXT_FORM_DATA].length) { - emails.push(...formData[EMAIL_ADDRESS_TEXT_FORM_DATA].split(/\r\n|\n/)); + rows.push(...formData[EMAIL_ADDRESS_TEXT_FORM_DATA].split(/\r\n|\n/)); } + if (formData[EMAIL_ADDRESS_CSV_FORM_DATA] && formData[EMAIL_ADDRESS_CSV_FORM_DATA].length) { - emails.push(...formData[EMAIL_ADDRESS_CSV_FORM_DATA]); + rows.push(...formData[EMAIL_ADDRESS_CSV_FORM_DATA]); } - emails = _.union(emails); // Dedup emails - return validateEmailAddresses(emails).validEmails; + + rows.forEach((row) => { + const [email, salesforceId] = row.split(',').map(item => item.trim()); + allRecords[email] = salesforceId; + }); + + return allRecords; +}; + +// Extract salesforce ids for all validated emails +const extractSalesforceIds = (formData, userEmails) => { + const salesforceIdsByEmail = getSalesforceIdsByEmail(formData); + const ids = []; + + userEmails.forEach((email) => { + ids.push(salesforceIdsByEmail[email]); + }); + + // check if `ids` array contain non-empty, not-null values + const noIdsPresent = ids.every(item => !item); + return noIdsPresent ? undefined : ids; }; /* eslint-enable no-underscore-dangle */ export { + extractSalesforceIds, validateEmailAddresses, validateEmailAddressesFields, validateEmailTemplateForm, diff --git a/src/data/validation/email.test.js b/src/data/validation/email.test.js index bb32bec662..4cc46ac214 100644 --- a/src/data/validation/email.test.js +++ b/src/data/validation/email.test.js @@ -1,5 +1,6 @@ import _ from 'lodash'; import { + extractSalesforceIds, validateEmailAddresses, validateEmailAddressesFields, validateEmailTemplateFields, @@ -233,4 +234,105 @@ describe('email validation', () => { ); }); }); + + describe('validate emails and ids extraction', () => { + it('extracted correct emails and ids from textarea and csv', () => { + const formData = new FormData(); + formData[EMAIL_ADDRESS_TEXT_FORM_DATA] = [ + 'abc@example.com,000000000000ABCABC', + 'asdf@example.com,', + 'zzz@example.com,000000000000XYZXYZ', + ].join('\n'); + formData[EMAIL_ADDRESS_CSV_FORM_DATA] = [ + 'one@example.com,000000000000YYYYYY', + 'two@example.com,000000000000ZZZZZZ', + 'three@example.com,000000000000ABCDDD', + 'wow@example.com,', + 'abc@example.com,000000000000ABCABC', + 'ama@example.com', + ]; + const userEmails = [ + 'abc@example.com', + 'asdf@example.com', + 'zzz@example.com', + 'one@example.com', + 'two@example.com', + 'three@example.com', + 'wow@example.com', + 'ama@example.com', + ]; + + const salesforceIds = extractSalesforceIds(formData, userEmails); + expect(salesforceIds).toEqual([ + '000000000000ABCABC', + '', + '000000000000XYZXYZ', + '000000000000YYYYYY', + '000000000000ZZZZZZ', + '000000000000ABCDDD', + '', + undefined, + ]); + expect(userEmails.length).toEqual(salesforceIds.length); + }); + + it('extracted correct emails and ids from textarea only', () => { + const formData = new FormData(); + formData[EMAIL_ADDRESS_TEXT_FORM_DATA] = [ + 'aaa@example.com,000000000000ABCABC', + 'bbb@example.com,', + 'ccc@example.com,000000000000XYZXYZ', + 'ddd@example.com', + ].join('\n'); + + const userEmails = returnValidatedEmails(formData); + expect(userEmails).toEqual(['aaa@example.com', 'bbb@example.com', 'ccc@example.com', 'ddd@example.com']); + const salesforceIds = extractSalesforceIds(formData, userEmails); + expect(salesforceIds).toEqual([ + '000000000000ABCABC', + '', + '000000000000XYZXYZ', + undefined, + ]); + expect(userEmails.length).toEqual(salesforceIds.length); + }); + + it('extracted correct emails and ids from csv only', () => { + const formData = new FormData(); + formData[EMAIL_ADDRESS_CSV_FORM_DATA] = [ + 'eee@example.com,000000000000YYYYYY', + 'fff@example.com,', + 'ggg@example.com,000000000000ZZZZZZ', + 'hhh@example.com', + ]; + + const userEmails = returnValidatedEmails(formData); + expect(userEmails).toEqual(['eee@example.com', 'fff@example.com', 'ggg@example.com', 'hhh@example.com']); + const salesforceIds = extractSalesforceIds(formData, userEmails); + expect(salesforceIds).toEqual([ + '000000000000YYYYYY', + '', + '000000000000ZZZZZZ', + undefined, + ]); + expect(userEmails.length).toEqual(salesforceIds.length); + }); + + it('returns no salesforce ids for emails only', () => { + const formData = new FormData(); + formData[EMAIL_ADDRESS_TEXT_FORM_DATA] = [ + 'abc@example.com', + 'asdf@example.com,', + ].join('\n'); + formData[EMAIL_ADDRESS_CSV_FORM_DATA] = [ + 'one@example.com,', + 'two@example.com', + ]; + + const userEmails = returnValidatedEmails(formData); + expect(userEmails).toEqual(['abc@example.com', 'asdf@example.com', 'one@example.com', 'two@example.com']); + const salesforceIds = extractSalesforceIds(formData, userEmails); + expect(salesforceIds).toEqual(undefined); + }); + }); });