Skip to content

Commit

Permalink
feat: same titles for sent email groups
Browse files Browse the repository at this point in the history
  • Loading branch information
Kyrylo Hudym-Levkovych authored and Kyrylo Hudym-Levkovych committed Jul 3, 2024
1 parent 10f906b commit a3da16d
Show file tree
Hide file tree
Showing 10 changed files with 99 additions and 23 deletions.
5 changes: 4 additions & 1 deletion src/components/bulk-email-tool/BulkEmailTool.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,10 @@ export default function BulkEmailTool() {
/>
</div>
<div className="row py-5">
<BulkEmailTaskManager courseId={courseId} />
<BulkEmailTaskManager
courseId={courseId}
courseModes={courseMetadata.courseModes}
/>
</div>
</Container>
</BulkEmailProvider>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import {
} from './data/actions';
import { editScheduledEmailThunk, postBulkEmailThunk } from './data/thunks';
import { getScheduledBulkEmailThunk } from '../bulk-email-task-manager/bulk-email-scheduled-emails-table/data/thunks';
import { getDisplayText } from '../utils';

import './bulkEmailForm.scss';

Expand Down Expand Up @@ -219,7 +220,7 @@ function BulkEmailForm(props) {
<p>{intl.formatMessage(messages.bulkEmailTaskAlertRecipients, { subject: editor.emailSubject })}</p>
<ul className="list-unstyled">
{editor.emailRecipients.map((group) => (
<li key={group}>{group}</li>
<li key={group}>{getDisplayText(group, courseModes)}</li>
))}
</ul>
{!isScheduled && (
Expand All @@ -246,7 +247,7 @@ function BulkEmailForm(props) {
<p>{intl.formatMessage(messages.bulkEmailTaskAlertEditingTo)}</p>
<ul className="list-unstyled">
{editor.emailRecipients.map((group) => (
<li key={group}>{group}</li>
<li key={group}>{getDisplayText(group, courseModes)}</li>
))}
</ul>
<p>{intl.formatMessage(messages.bulkEmailTaskAlertEditingWarning)}</p>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import React from 'react';
import PropTypes from 'prop-types';
import { Form } from '@openedx/paragon';
import { FormattedMessage } from '@edx/frontend-platform/i18n';
import { RECIPIENTS_DISPLAY_NAMES } from '../../utils';

import './bulkEmailRecepient.scss';

Expand Down Expand Up @@ -41,7 +42,7 @@ export default function BulkEmailRecipient(props) {
<Form.Checkbox key="myself" value="myself" className="mt-2.5 col col-lg-4 col-sm-6 col-12">
<FormattedMessage
id="bulk.email.form.recipients.myself"
defaultMessage="Myself"
defaultMessage={RECIPIENTS_DISPLAY_NAMES.myself}
description="A selectable choice from a list of potential email recipients"
/>
</Form.Checkbox>
Expand All @@ -52,7 +53,7 @@ export default function BulkEmailRecipient(props) {
>
<FormattedMessage
id="bulk.email.form.recipients.staff"
defaultMessage="Staff/Administrators"
defaultMessage={RECIPIENTS_DISPLAY_NAMES.staff}
description="A selectable choice from a list of potential email recipients"
/>
</Form.Checkbox>
Expand Down Expand Up @@ -99,7 +100,7 @@ export default function BulkEmailRecipient(props) {
>
<FormattedMessage
id="bulk.email.form.recipients.learners"
defaultMessage="All Learners"
defaultMessage={RECIPIENTS_DISPLAY_NAMES.learners}
description="A selectable choice from a list of potential email recipients"
/>
</Form.Checkbox>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { BulkEmailContext, BulkEmailProvider } from '../../bulk-email-context';
import { formatDate } from '../../../../utils/formatDateAndTime';
import cohortFactory from '../data/__factories__/bulkEmailFormCohort.factory';
import courseModeFactory from '../data/__factories__/bulkEmailFormCourseMode.factory';
import { RECIPIENTS_DISPLAY_NAMES } from '../../utils';

jest.mock('../../text-editor/TextEditor');

Expand Down Expand Up @@ -75,8 +76,8 @@ describe('bulk-email-form', () => {
success: true,
});
render(renderBulkEmailForm());
fireEvent.click(screen.getByRole('checkbox', { name: 'Myself' }));
expect(screen.getByRole('checkbox', { name: 'Myself' })).toBeChecked();
fireEvent.click(screen.getByRole('checkbox', { name: RECIPIENTS_DISPLAY_NAMES.myself }));
expect(screen.getByRole('checkbox', { name: RECIPIENTS_DISPLAY_NAMES.myself })).toBeChecked();
fireEvent.change(screen.getByRole('textbox', { name: 'Subject' }), { target: { value: 'test subject' } });
fireEvent.change(screen.getByTestId('textEditor'), { target: { value: 'test body' } });
fireEvent.click(screen.getByText('Send email'));
Expand All @@ -90,7 +91,7 @@ describe('bulk-email-form', () => {
axiosMock.onPost(`${getConfig().LMS_BASE_URL}/courses/test/instructor/api/send_email`).reply(500);
render(renderBulkEmailForm());
const subjectLine = screen.getByRole('textbox', { name: 'Subject' });
const recipient = screen.getByRole('checkbox', { name: 'Myself' });
const recipient = screen.getByRole('checkbox', { name: RECIPIENTS_DISPLAY_NAMES.myself });
fireEvent.click(recipient);
fireEvent.change(subjectLine, { target: { value: 'test subject' } });
fireEvent.change(screen.getByTestId('textEditor'), { target: { value: 'test body' } });
Expand All @@ -99,9 +100,9 @@ describe('bulk-email-form', () => {
fireEvent.click(await screen.findByRole('button', { name: /continue/i }));
expect(await screen.findByText('An error occured while attempting to send the email.')).toBeInTheDocument();
});
test('Checking "All Learners" disables each learner group', async () => {
test('Checking "All Students" disables each learner group', async () => {
render(renderBulkEmailForm());
fireEvent.click(screen.getByRole('checkbox', { name: 'All Learners' }));
fireEvent.click(screen.getByRole('checkbox', { name: RECIPIENTS_DISPLAY_NAMES.learners }));
const verifiedLearners = screen.getByRole('checkbox', { name: 'Learners in the Verified Certificate Track' });
const auditLearners = screen.getByRole('checkbox', { name: 'Learners in the Audit Track' });
const { cohorts } = cohortFactory.build();
Expand Down Expand Up @@ -130,7 +131,7 @@ describe('bulk-email-form', () => {
test('Adds scheduling data to POST requests when schedule is selected', async () => {
const postBulkEmailInstructorTask = jest.spyOn(bulkEmailFormApi, 'postBulkEmailInstructorTask');
render(renderBulkEmailForm());
fireEvent.click(screen.getByRole('checkbox', { name: 'Myself' }));
fireEvent.click(screen.getByRole('checkbox', { name: RECIPIENTS_DISPLAY_NAMES.myself }));
fireEvent.change(screen.getByRole('textbox', { name: 'Subject' }), { target: { value: 'test subject' } });
fireEvent.change(screen.getByTestId('textEditor'), { target: { value: 'test body' } });
const scheduleCheckbox = screen.getByText('Schedule this email for a future date');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import messages from './messages';
import { getSentEmailHistory } from './data/api';
import BulkEmailTaskManagerTable from './BulkEmailHistoryTable';
import ViewEmailModal from './ViewEmailModal';
import { HISTORY_RECIPIENTS_DISPLAY_NAMES } from '../utils';

function BulkEmailContentHistory({ intl }) {
const { courseId } = useParams();
Expand Down Expand Up @@ -55,7 +56,7 @@ function BulkEmailContentHistory({ intl }) {
const tableData = emailHistoryData?.map((item) => ({
...item,
subject: item.email.subject,
sent_to: item.sent_to.join(', '),
sent_to: item.sent_to.map((recipient) => HISTORY_RECIPIENTS_DISPLAY_NAMES[recipient] || recipient).join(', '),
created: new Date(item.created).toLocaleString(),
}));
return tableData || [];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,13 @@ import messages from './messages';
import BulkEmailScheduledEmailsTable from './bulk-email-scheduled-emails-table';
import BulkEmailPendingTasksAlert from './BulkEmailPendingTasksAlert';

function BulkEmailTaskManager({ intl, courseId }) {
function BulkEmailTaskManager({ intl, courseId, courseModes }) {
return (
<div className="w-100">
{getConfig().SCHEDULE_EMAIL_SECTION && (
<div>
<h2 className="h3 text-primary-500">{intl.formatMessage(messages.scheduledEmailsTableHeader)}</h2>
<BulkEmailScheduledEmailsTable />
<BulkEmailScheduledEmailsTable courseModes={courseModes} />
</div>
)}
<div>
Expand All @@ -36,6 +36,12 @@ function BulkEmailTaskManager({ intl, courseId }) {
BulkEmailTaskManager.propTypes = {
intl: intlShape.isRequired,
courseId: PropTypes.string.isRequired,
courseModes: PropTypes.arrayOf(
PropTypes.shape({
slug: PropTypes.string.isRequired,
name: PropTypes.string.isRequired,
}),
).isRequired,
};

export default injectIntl(BulkEmailTaskManager);
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ function ViewEmailModal({
<p className="pl-2">{messageContent.created}</p>
</div>
<div className="d-flex flex-row">
<p>{intl.formatMessage(messages.modalMessageSentTo)}</p>
<p className="flex-shrink-0">{intl.formatMessage(messages.modalMessageSentTo)}</p>
<p className="pl-2">{messageContent.sent_to}</p>
</div>
<hr className="py-2" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import React, {
useCallback, useContext, useState, useEffect,
} from 'react';
import PropTypes from 'prop-types';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import {
Alert, DataTable, Icon, IconButton, useToggle,
Expand All @@ -19,20 +20,22 @@ import ViewEmailModal from '../ViewEmailModal';
import { copyToEditor } from '../../bulk-email-form/data/actions';
import TaskAlertModal from '../../task-alert-modal';
import { formatDate, formatTime } from '../../../../utils/formatDateAndTime';
import { getDisplayText, getRecipientFromDisplayText } from '../../utils';

function flattenScheduledEmailsArray(emails) {
function flattenScheduledEmailsArray(emails, courseModes) {
return emails.map((email) => ({
schedulingId: email.id,
emailId: email.courseEmail.id,
task: email.task,
taskDue: new Date(email.taskDue).toLocaleString(),
taskDueUTC: email.taskDue,
...email.courseEmail,
targets: email.courseEmail.targets.join(', '),
targets: email.courseEmail.targets
.map((recipient) => getDisplayText(recipient, courseModes)).join(', '),
}));
}

function BulkEmailScheduledEmailsTable({ intl }) {
function BulkEmailScheduledEmailsTable({ intl, courseModes }) {
const { courseId } = useParams();
const [{ scheduledEmailsTable }, dispatch] = useContext(BulkEmailContext);
const [tableData, setTableData] = useState([]);
Expand All @@ -44,8 +47,8 @@ function BulkEmailScheduledEmailsTable({ intl }) {
const [currentTask, setCurrentTask] = useState({});

useEffect(() => {
setTableData(flattenScheduledEmailsArray(scheduledEmailsTable.results));
}, [scheduledEmailsTable.results]);
setTableData(flattenScheduledEmailsArray(scheduledEmailsTable.results, courseModes));
}, [scheduledEmailsTable.results, courseModes]);

const fetchTableData = useCallback((args) => {
dispatch(getScheduledBulkEmailThunk(courseId, args.pageIndex + 1));
Expand Down Expand Up @@ -96,7 +99,8 @@ function BulkEmailScheduledEmailsTable({ intl }) {
},
} = row;
const dateTime = new Date(taskDueUTC);
const emailRecipients = targets.replaceAll('-', ':').split(', ');
const emailRecipients = targets
.split(', ').map((recipient) => getRecipientFromDisplayText(recipient, courseModes));
const scheduleDate = formatDate(dateTime);
const scheduleTime = formatTime(dateTime);
dispatch(
Expand Down Expand Up @@ -198,6 +202,12 @@ function BulkEmailScheduledEmailsTable({ intl }) {

BulkEmailScheduledEmailsTable.propTypes = {
intl: intlShape.isRequired,
courseModes: PropTypes.arrayOf(
PropTypes.shape({
slug: PropTypes.string.isRequired,
name: PropTypes.string.isRequired,
}),
).isRequired,
};

export default injectIntl(BulkEmailScheduledEmailsTable);
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { BulkEmailProvider } from '../../../bulk-email-context';
import BulkEmailScheduledEmailsTable from '..';
import scheduledEmailsFactory from './__factories__/scheduledEmails.factory';
import * as actions from '../../../bulk-email-form/data/actions';
import { RECIPIENTS_DISPLAY_NAMES } from '../../../utils';

jest.mock('react-router-dom', () => ({
...jest.requireActual('react-router-dom'),
Expand Down Expand Up @@ -42,7 +43,7 @@ describe('BulkEmailScheduledEmailsTable', () => {
.onGet(`${getConfig().LMS_BASE_URL}/api/instructor_task/v1/schedules/test-id/bulk_email/?page=1`)
.reply(200, scheduledEmailsFactory.build(1));
render(renderBulkEmailScheduledEmailsTable());
expect(await screen.findByText('learners')).toBeTruthy();
expect(await screen.findByText(RECIPIENTS_DISPLAY_NAMES.learners)).toBeTruthy();
expect(await screen.findByText('subject')).toBeTruthy();
expect(await screen.findByText('edx')).toBeTruthy();
expect(await screen.findByLabelText('View')).toBeTruthy();
Expand Down
52 changes: 52 additions & 0 deletions src/components/bulk-email-tool/utils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
export const RECIPIENTS_DISPLAY_NAMES = {
myself: 'Myself',
staff: 'Staff/Administrators',
learners: 'All Learners',
};

export const HISTORY_RECIPIENTS_DISPLAY_NAMES = {
'Staff and instructors': 'Staff/Administrators',
'All students': 'All Learners',
};

// Output: { 'Myself': 'myself', 'Staff/Administrators': 'staff', 'All Learners': 'learners' }
export const REVERSE_RECIPIENTS_DISPLAY_NAMES = Object.fromEntries(
Object.entries(RECIPIENTS_DISPLAY_NAMES).map(([key, value]) => [value, key]),
);

export const getDisplayText = (recipient, courseModes) => {
const normalizedRecipient = recipient.replace(/-/, ':');

if (normalizedRecipient.startsWith('track') && courseModes) {
const courseModeSlug = normalizedRecipient.split(':')[1];
const courseMode = courseModes.find((mode) => mode.slug === courseModeSlug);

Check warning on line 22 in src/components/bulk-email-tool/utils.js

View check run for this annotation

Codecov / codecov/patch

src/components/bulk-email-tool/utils.js#L21-L22

Added lines #L21 - L22 were not covered by tests
if (courseMode) {
return `Learners in the ${courseMode.name} Track`;

Check warning on line 24 in src/components/bulk-email-tool/utils.js

View check run for this annotation

Codecov / codecov/patch

src/components/bulk-email-tool/utils.js#L24

Added line #L24 was not covered by tests
}
}

if (normalizedRecipient.startsWith('cohort')) {
const cohort = normalizedRecipient.replace(/:/, ': ');
return cohort.charAt(0).toUpperCase() + cohort.slice(1);

Check warning on line 30 in src/components/bulk-email-tool/utils.js

View check run for this annotation

Codecov / codecov/patch

src/components/bulk-email-tool/utils.js#L29-L30

Added lines #L29 - L30 were not covered by tests
}

return RECIPIENTS_DISPLAY_NAMES[recipient] || recipient;
};

export const getRecipientFromDisplayText = (displayText, courseModes) => {
const trackMatch = displayText.match(/^Learners in the (.+) Track$/);
if (trackMatch) {
const courseModeName = trackMatch[1];
const courseMode = courseModes.find(mode => mode.name === courseModeName);

Check warning on line 40 in src/components/bulk-email-tool/utils.js

View check run for this annotation

Codecov / codecov/patch

src/components/bulk-email-tool/utils.js#L39-L40

Added lines #L39 - L40 were not covered by tests
if (courseMode) {
return `track:${courseMode.slug}`;

Check warning on line 42 in src/components/bulk-email-tool/utils.js

View check run for this annotation

Codecov / codecov/patch

src/components/bulk-email-tool/utils.js#L42

Added line #L42 was not covered by tests
}
}

const cohortMatch = displayText.match(/^Cohort: (.+)$/);
if (cohortMatch) {
return `cohort:${cohortMatch[1].replace(' ', '-')}`;

Check warning on line 48 in src/components/bulk-email-tool/utils.js

View check run for this annotation

Codecov / codecov/patch

src/components/bulk-email-tool/utils.js#L48

Added line #L48 was not covered by tests
}

return REVERSE_RECIPIENTS_DISPLAY_NAMES[displayText] || displayText;
};

0 comments on commit a3da16d

Please sign in to comment.