From a3da16dc892806af852f15ed5215281ab9b3f30d Mon Sep 17 00:00:00 2001 From: Kyrylo Hudym-Levkovych Date: Wed, 3 Jul 2024 22:54:39 +0300 Subject: [PATCH] feat: same titles for sent email groups --- .../bulk-email-tool/BulkEmailTool.jsx | 5 +- .../bulk-email-form/BulkEmailForm.jsx | 5 +- .../BulkEmailRecipient.jsx | 7 +-- .../test/BulkEmailForm.test.jsx | 13 ++--- .../BulkEmailContentHistory.jsx | 3 +- .../BulkEmailTaskManager.jsx | 10 +++- .../ViewEmailModal.jsx | 2 +- .../BulkEmailScheduledEmailsTable.jsx | 22 +++++--- .../BulkEmailScheduledEmailsTable.test.jsx | 3 +- src/components/bulk-email-tool/utils.js | 52 +++++++++++++++++++ 10 files changed, 99 insertions(+), 23 deletions(-) create mode 100644 src/components/bulk-email-tool/utils.js diff --git a/src/components/bulk-email-tool/BulkEmailTool.jsx b/src/components/bulk-email-tool/BulkEmailTool.jsx index 1ebec152..9962d1e5 100644 --- a/src/components/bulk-email-tool/BulkEmailTool.jsx +++ b/src/components/bulk-email-tool/BulkEmailTool.jsx @@ -40,7 +40,10 @@ export default function BulkEmailTool() { />
- +
diff --git a/src/components/bulk-email-tool/bulk-email-form/BulkEmailForm.jsx b/src/components/bulk-email-tool/bulk-email-form/BulkEmailForm.jsx index 29de06d7..4aea1e64 100644 --- a/src/components/bulk-email-tool/bulk-email-form/BulkEmailForm.jsx +++ b/src/components/bulk-email-tool/bulk-email-form/BulkEmailForm.jsx @@ -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'; @@ -219,7 +220,7 @@ function BulkEmailForm(props) {

{intl.formatMessage(messages.bulkEmailTaskAlertRecipients, { subject: editor.emailSubject })}

{!isScheduled && ( @@ -246,7 +247,7 @@ function BulkEmailForm(props) {

{intl.formatMessage(messages.bulkEmailTaskAlertEditingTo)}

{intl.formatMessage(messages.bulkEmailTaskAlertEditingWarning)}

diff --git a/src/components/bulk-email-tool/bulk-email-form/bulk-email-recipient/BulkEmailRecipient.jsx b/src/components/bulk-email-tool/bulk-email-form/bulk-email-recipient/BulkEmailRecipient.jsx index d60b316b..48445990 100644 --- a/src/components/bulk-email-tool/bulk-email-form/bulk-email-recipient/BulkEmailRecipient.jsx +++ b/src/components/bulk-email-tool/bulk-email-form/bulk-email-recipient/BulkEmailRecipient.jsx @@ -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'; @@ -41,7 +42,7 @@ export default function BulkEmailRecipient(props) { @@ -52,7 +53,7 @@ export default function BulkEmailRecipient(props) { > @@ -99,7 +100,7 @@ export default function BulkEmailRecipient(props) { > diff --git a/src/components/bulk-email-tool/bulk-email-form/test/BulkEmailForm.test.jsx b/src/components/bulk-email-tool/bulk-email-form/test/BulkEmailForm.test.jsx index 2d0dd1c3..92d9de91 100644 --- a/src/components/bulk-email-tool/bulk-email-form/test/BulkEmailForm.test.jsx +++ b/src/components/bulk-email-tool/bulk-email-form/test/BulkEmailForm.test.jsx @@ -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'); @@ -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')); @@ -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' } }); @@ -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(); @@ -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'); diff --git a/src/components/bulk-email-tool/bulk-email-task-manager/BulkEmailContentHistory.jsx b/src/components/bulk-email-tool/bulk-email-task-manager/BulkEmailContentHistory.jsx index ef0e2446..2bd1c576 100644 --- a/src/components/bulk-email-tool/bulk-email-task-manager/BulkEmailContentHistory.jsx +++ b/src/components/bulk-email-tool/bulk-email-task-manager/BulkEmailContentHistory.jsx @@ -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(); @@ -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 || []; diff --git a/src/components/bulk-email-tool/bulk-email-task-manager/BulkEmailTaskManager.jsx b/src/components/bulk-email-tool/bulk-email-task-manager/BulkEmailTaskManager.jsx index 43a34cdb..2bc5267f 100644 --- a/src/components/bulk-email-tool/bulk-email-task-manager/BulkEmailTaskManager.jsx +++ b/src/components/bulk-email-tool/bulk-email-task-manager/BulkEmailTaskManager.jsx @@ -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 (
{getConfig().SCHEDULE_EMAIL_SECTION && (

{intl.formatMessage(messages.scheduledEmailsTableHeader)}

- +
)}
@@ -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); diff --git a/src/components/bulk-email-tool/bulk-email-task-manager/ViewEmailModal.jsx b/src/components/bulk-email-tool/bulk-email-task-manager/ViewEmailModal.jsx index 03b9f547..03d39a0c 100644 --- a/src/components/bulk-email-tool/bulk-email-task-manager/ViewEmailModal.jsx +++ b/src/components/bulk-email-tool/bulk-email-task-manager/ViewEmailModal.jsx @@ -33,7 +33,7 @@ function ViewEmailModal({

{messageContent.created}

-

{intl.formatMessage(messages.modalMessageSentTo)}

+

{intl.formatMessage(messages.modalMessageSentTo)}

{messageContent.sent_to}


diff --git a/src/components/bulk-email-tool/bulk-email-task-manager/bulk-email-scheduled-emails-table/BulkEmailScheduledEmailsTable.jsx b/src/components/bulk-email-tool/bulk-email-task-manager/bulk-email-scheduled-emails-table/BulkEmailScheduledEmailsTable.jsx index 0e5a5487..7f0d74f0 100644 --- a/src/components/bulk-email-tool/bulk-email-task-manager/bulk-email-scheduled-emails-table/BulkEmailScheduledEmailsTable.jsx +++ b/src/components/bulk-email-tool/bulk-email-task-manager/bulk-email-scheduled-emails-table/BulkEmailScheduledEmailsTable.jsx @@ -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, @@ -19,8 +20,9 @@ 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, @@ -28,11 +30,12 @@ function flattenScheduledEmailsArray(emails) { 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([]); @@ -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)); @@ -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( @@ -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); diff --git a/src/components/bulk-email-tool/bulk-email-task-manager/bulk-email-scheduled-emails-table/test/BulkEmailScheduledEmailsTable.test.jsx b/src/components/bulk-email-tool/bulk-email-task-manager/bulk-email-scheduled-emails-table/test/BulkEmailScheduledEmailsTable.test.jsx index 3495bef2..f461b2d5 100644 --- a/src/components/bulk-email-tool/bulk-email-task-manager/bulk-email-scheduled-emails-table/test/BulkEmailScheduledEmailsTable.test.jsx +++ b/src/components/bulk-email-tool/bulk-email-task-manager/bulk-email-scheduled-emails-table/test/BulkEmailScheduledEmailsTable.test.jsx @@ -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'), @@ -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(); diff --git a/src/components/bulk-email-tool/utils.js b/src/components/bulk-email-tool/utils.js new file mode 100644 index 00000000..2e454128 --- /dev/null +++ b/src/components/bulk-email-tool/utils.js @@ -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); + if (courseMode) { + return `Learners in the ${courseMode.name} Track`; + } + } + + if (normalizedRecipient.startsWith('cohort')) { + const cohort = normalizedRecipient.replace(/:/, ': '); + return cohort.charAt(0).toUpperCase() + cohort.slice(1); + } + + 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); + if (courseMode) { + return `track:${courseMode.slug}`; + } + } + + const cohortMatch = displayText.match(/^Cohort: (.+)$/); + if (cohortMatch) { + return `cohort:${cohortMatch[1].replace(' ', '-')}`; + } + + return REVERSE_RECIPIENTS_DISPLAY_NAMES[displayText] || displayText; +};