feat: [MICROBA-1688] Add cohorts to recipients (#16)
This commit is contained in:
@@ -6,7 +6,7 @@ import { Spinner } from '@edx/paragon';
|
||||
import { ErrorPage } from '@edx/frontend-platform/react';
|
||||
import BulkEmailTaskManager from './bulk-email-task-manager/BulkEmailTaskManager';
|
||||
import Navigationtabs from '../navigation-tabs/NavigationTabs';
|
||||
import { getCourseHomeCourseMetadata } from './data/api';
|
||||
import { getCohorts, getCourseHomeCourseMetadata } from './data/api';
|
||||
import useMobileResponsive from '../../utils/useMobileResponsive';
|
||||
import BulkEmailForm from './bulk-email-form';
|
||||
|
||||
@@ -18,20 +18,25 @@ export default function BulkEmailTool() {
|
||||
|
||||
useEffect(() => {
|
||||
async function fetchTabData() {
|
||||
let data;
|
||||
let metadataResponse;
|
||||
let cohortsResponse;
|
||||
try {
|
||||
data = await getCourseHomeCourseMetadata(courseId);
|
||||
metadataResponse = await getCourseHomeCourseMetadata(courseId);
|
||||
cohortsResponse = await getCohorts(courseId);
|
||||
} catch (e) {
|
||||
setCourseMetadata({
|
||||
isStaff: false,
|
||||
tabs: [],
|
||||
cohorts: [],
|
||||
});
|
||||
return;
|
||||
}
|
||||
const { tabs, is_staff: isStaff } = data;
|
||||
const { tabs, is_staff: isStaff } = metadataResponse;
|
||||
const { cohorts } = cohortsResponse;
|
||||
setCourseMetadata({
|
||||
isStaff,
|
||||
tabs: [...tabs],
|
||||
cohorts: cohorts.map(({ name }) => name),
|
||||
});
|
||||
}
|
||||
fetchTabData();
|
||||
@@ -43,7 +48,7 @@ export default function BulkEmailTool() {
|
||||
<Navigationtabs courseId={courseId} tabData={courseMetadata.tabs} />
|
||||
<div className={classnames({ 'border border-primary-200': !isMobile })}>
|
||||
<div className="row">
|
||||
<BulkEmailForm courseId={courseId} />
|
||||
<BulkEmailForm courseId={courseId} cohorts={courseMetadata.cohorts} />
|
||||
</div>
|
||||
<div className="row">
|
||||
<BulkEmailTaskManager courseId={courseId} />
|
||||
|
||||
@@ -20,7 +20,7 @@ export const FORM_SUBMIT_STATES = {
|
||||
};
|
||||
|
||||
export default function BulkEmailForm(props) {
|
||||
const { courseId } = props;
|
||||
const { courseId, cohorts } = props;
|
||||
const [subject, setSubject] = useState('');
|
||||
const [emailFormStatus, setEmailFormStatus] = useState(FORM_SUBMIT_STATES.DEFAULT);
|
||||
const [emailFormValidation, setEmailFormValidation] = useState({
|
||||
@@ -126,6 +126,7 @@ export default function BulkEmailForm(props) {
|
||||
<BulkEmailRecipient
|
||||
selectedGroups={selectedRecipients}
|
||||
handleCheckboxes={onRecipientChange}
|
||||
additionalCohorts={cohorts}
|
||||
isValid={emailFormValidation.recipients}
|
||||
/>
|
||||
<Form.Group controlId="emailSubject">
|
||||
@@ -220,6 +221,11 @@ export default function BulkEmailForm(props) {
|
||||
);
|
||||
}
|
||||
|
||||
BulkEmailForm.defaultProps = {
|
||||
cohorts: [],
|
||||
};
|
||||
|
||||
BulkEmailForm.propTypes = {
|
||||
courseId: PropTypes.string.isRequired,
|
||||
cohorts: PropTypes.arrayOf(PropTypes.string),
|
||||
};
|
||||
|
||||
@@ -12,7 +12,7 @@ const DEFAULT_GROUPS = {
|
||||
};
|
||||
|
||||
export default function BulkEmailRecipient(props) {
|
||||
const { handleCheckboxes, selectedGroups } = props;
|
||||
const { handleCheckboxes, selectedGroups, additionalCohorts } = props;
|
||||
return (
|
||||
<Form.Group>
|
||||
<Form.Label>
|
||||
@@ -22,8 +22,13 @@ export default function BulkEmailRecipient(props) {
|
||||
description="A label before the list of potential recipients"
|
||||
/>
|
||||
</Form.Label>
|
||||
<Form.CheckboxSet name="recipientGroups" onChange={handleCheckboxes} value={selectedGroups}>
|
||||
<Form.Checkbox key="myself" value="myself">
|
||||
<Form.CheckboxSet
|
||||
name="recipientGroups"
|
||||
className="w-75 flex-wrap flex-row justify-content-between"
|
||||
onChange={handleCheckboxes}
|
||||
value={selectedGroups}
|
||||
>
|
||||
<Form.Checkbox key="myself" value="myself" className="mt-2.5">
|
||||
<FormattedMessage
|
||||
id="bulk.email.form.recipients.myself"
|
||||
defaultMessage="Myself"
|
||||
@@ -70,6 +75,23 @@ export default function BulkEmailRecipient(props) {
|
||||
description="A selectable choice from a list of potential email recipients"
|
||||
/>
|
||||
</Form.Checkbox>
|
||||
{
|
||||
// additional cohorts
|
||||
additionalCohorts
|
||||
&& additionalCohorts.map((cohort) => (
|
||||
<Form.Checkbox
|
||||
key={cohort}
|
||||
value={`cohort:${cohort}`}
|
||||
disabled={selectedGroups.find((group) => group === (DEFAULT_GROUPS.AUDIT || DEFAULT_GROUPS.VERIFIED))}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="bulk.email.form.cohort.label"
|
||||
defaultMessage="Cohort: {cohort}"
|
||||
values={{ cohort }}
|
||||
/>
|
||||
</Form.Checkbox>
|
||||
))
|
||||
}
|
||||
</Form.CheckboxSet>
|
||||
{!props.isValid && (
|
||||
<Form.Control.Feedback className="px-3" hasIcon type="invalid">
|
||||
@@ -86,10 +108,12 @@ export default function BulkEmailRecipient(props) {
|
||||
|
||||
BulkEmailRecipient.defaultProps = {
|
||||
isValid: true,
|
||||
additionalCohorts: [],
|
||||
};
|
||||
|
||||
BulkEmailRecipient.propTypes = {
|
||||
selectedGroups: PropTypes.arrayOf(PropTypes.string).isRequired,
|
||||
handleCheckboxes: PropTypes.func.isRequired,
|
||||
isValid: PropTypes.bool,
|
||||
additionalCohorts: PropTypes.arrayOf(PropTypes.string),
|
||||
};
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
import { Factory } from 'rosie'; // eslint-disable-line import/no-extraneous-dependencies
|
||||
|
||||
export default Factory.define('cohort')
|
||||
.sequence('id', (i) => i)
|
||||
.sequence('name', i => `test cohort ${i}`)
|
||||
.attrs({
|
||||
assignment_type: 'test',
|
||||
user_count: 1,
|
||||
});
|
||||
@@ -14,3 +14,9 @@ export async function getCourseHomeCourseMetadata(courseId) {
|
||||
const { data } = await getAuthenticatedHttpClient().get(courseHomeMetadataUrl);
|
||||
return data;
|
||||
}
|
||||
|
||||
export async function getCohorts(courseId) {
|
||||
const url = `${getConfig().LMS_BASE_URL}/courses/${courseId}/cohorts/`;
|
||||
const { data } = await getAuthenticatedHttpClient().get(url);
|
||||
return data;
|
||||
}
|
||||
|
||||
@@ -3,10 +3,11 @@
|
||||
*/
|
||||
import React from 'react';
|
||||
import { Factory } from 'rosie';
|
||||
import { render, screen } from '../../../setupTest';
|
||||
import { render, screen, cleanup } from '../../../setupTest';
|
||||
import BulkEmailTool from '../BulkEmailTool';
|
||||
import { getCourseHomeCourseMetadata } from '../data/api';
|
||||
import { getCourseHomeCourseMetadata, getCohorts } from '../data/api';
|
||||
import '../data/__factories__/courseMetadata.factory';
|
||||
import '../data/__factories__/cohort.factory';
|
||||
|
||||
jest.mock('../text-editor/TextEditor');
|
||||
jest.mock('../bulk-email-task-manager/api', () => ({
|
||||
@@ -16,6 +17,7 @@ jest.mock('../bulk-email-task-manager/api', () => ({
|
||||
jest.mock('../data/api', () => ({
|
||||
__esModule: true,
|
||||
getCourseHomeCourseMetadata: jest.fn(() => {}),
|
||||
getCohorts: jest.fn(() => {}),
|
||||
}));
|
||||
jest.mock('react-router-dom', () => ({
|
||||
...jest.requireActual('react-router-dom'),
|
||||
@@ -25,15 +27,21 @@ jest.mock('react-router-dom', () => ({
|
||||
}));
|
||||
|
||||
describe('BulkEmailTool', () => {
|
||||
beforeEach(() => jest.resetModules());
|
||||
afterEach(cleanup);
|
||||
test('BulkEmailTool renders properly when given course metadata', async () => {
|
||||
const courseMetadata = Factory.build('courseMetadata');
|
||||
const cohorts = { cohorts: [Factory.build('cohort'), Factory.build('cohort')] };
|
||||
getCourseHomeCourseMetadata.mockImplementation(() => courseMetadata);
|
||||
getCohorts.mockImplementation(() => cohorts);
|
||||
render(<BulkEmailTool />);
|
||||
expect(await screen.findByText('Course')).toBeTruthy();
|
||||
});
|
||||
test('BulkEmailTool renders error page on no staff user', async () => {
|
||||
const courseMetadata = Factory.build('courseMetadata', { is_staff: false });
|
||||
const cohorts = { cohorts: [Factory.build('cohort'), Factory.build('cohort')] };
|
||||
getCourseHomeCourseMetadata.mockImplementation(() => courseMetadata);
|
||||
getCohorts.mockImplementation(() => cohorts);
|
||||
render(<BulkEmailTool />);
|
||||
expect(
|
||||
await screen.findByText('An unexpected error occurred. Please click the button below to refresh the page.'),
|
||||
|
||||
Reference in New Issue
Block a user