Skip to content

Commit

Permalink
feat: add support for optional salesforce ids in csv upload
Browse files Browse the repository at this point in the history
  • Loading branch information
muhammad-ammar committed Sep 25, 2023
1 parent 5cb3eeb commit 9ed5ab3
Show file tree
Hide file tree
Showing 3 changed files with 173 additions and 10 deletions.
6 changes: 5 additions & 1 deletion src/components/InviteLearnersModal/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -74,6 +74,10 @@ class InviteLearnersModal extends React.Component {
};

options.user_emails = returnValidatedEmails(formData);
const SFIDs = extractSalesforceIds(formData, options.user_emails);

Check warning on line 77 in src/components/InviteLearnersModal/index.jsx

View check run for this annotation

Codecov / codecov/patch

src/components/InviteLearnersModal/index.jsx#L77

Added line #L77 was not covered by tests
if (SFIDs) {
options.user_sfids = SFIDs;

Check warning on line 79 in src/components/InviteLearnersModal/index.jsx

View check run for this annotation

Codecov / codecov/patch

src/components/InviteLearnersModal/index.jsx#L79

Added line #L79 was not covered by tests
}

/* eslint-disable no-underscore-dangle */
return addLicensesForUsers(options, subscriptionUUID)
Expand Down
75 changes: 66 additions & 9 deletions src/data/validation/email.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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);
Expand All @@ -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);
}

Expand Down Expand Up @@ -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,
Expand Down
102 changes: 102 additions & 0 deletions src/data/validation/email.test.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import _ from 'lodash';
import {
extractSalesforceIds,
validateEmailAddresses,
validateEmailAddressesFields,
validateEmailTemplateFields,
Expand Down Expand Up @@ -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] = [
'[email protected],000000000000ABCABC',
'[email protected],',
'[email protected],000000000000XYZXYZ',
].join('\n');
formData[EMAIL_ADDRESS_CSV_FORM_DATA] = [
'[email protected],000000000000YYYYYY',
'[email protected],000000000000ZZZZZZ',
'[email protected],000000000000ABCDDD',
'[email protected],',
'[email protected],000000000000ABCABC',
'[email protected]',
];
const userEmails = [
'[email protected]',
'[email protected]',
'[email protected]',
'[email protected]',
'[email protected]',
'[email protected]',
'[email protected]',
'[email protected]',
];

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] = [
'[email protected],000000000000ABCABC',
'[email protected],',
'[email protected],000000000000XYZXYZ',
'[email protected]',
].join('\n');

const userEmails = returnValidatedEmails(formData);
expect(userEmails).toEqual(['[email protected]', '[email protected]', '[email protected]', '[email protected]']);
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] = [
'[email protected],000000000000YYYYYY',
'[email protected],',
'[email protected],000000000000ZZZZZZ',
'[email protected]',
];

const userEmails = returnValidatedEmails(formData);
expect(userEmails).toEqual(['[email protected]', '[email protected]', '[email protected]', '[email protected]']);
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] = [
'[email protected]',
'[email protected],',
].join('\n');
formData[EMAIL_ADDRESS_CSV_FORM_DATA] = [
'[email protected],',
'[email protected]',
];

const userEmails = returnValidatedEmails(formData);
expect(userEmails).toEqual(['[email protected]', '[email protected]', '[email protected]', '[email protected]']);
const salesforceIds = extractSalesforceIds(formData, userEmails);
expect(salesforceIds).toEqual(undefined);
});
});
});

0 comments on commit 9ed5ab3

Please sign in to comment.