Merge pull request #12 from edx/jhynes/microba-1621_data-tables-continued
feat: Implement task history and pending task tables
This commit is contained in:
@@ -4,25 +4,35 @@ import { useParams } from 'react-router-dom';
|
||||
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
||||
|
||||
import {
|
||||
Alert, Button, DataTable, Modal,
|
||||
Button, Icon, Modal, StatefulButton,
|
||||
} from '@edx/paragon';
|
||||
import { SpinnerSimple } from '@edx/paragon/icons';
|
||||
import messages from './messages';
|
||||
import { getSentEmailHistory } from './api';
|
||||
import BulkEmailTaskManagerTable from './BulkEmailHistoryTable';
|
||||
|
||||
export function BulkEmailContentHistory({ intl }) {
|
||||
const { courseId } = useParams();
|
||||
const BUTTON_STATE = {
|
||||
DEFAULT: 'default',
|
||||
PENDING: 'pending',
|
||||
COMPLETE: 'complete',
|
||||
};
|
||||
|
||||
const [emailHistoryData, setEmailHistoryData] = useState();
|
||||
const [errorRetrievingData, setErrorRetrievingData] = useState(false);
|
||||
const [showHistoricalEmailContentTable, setShowHistoricalEmailContentTable] = useState(false);
|
||||
const [isMessageModalOpen, setIsMessageModalOpen] = useState(false);
|
||||
const [messageContent, setMessageContent] = useState();
|
||||
const [buttonState, setButtonState] = useState(BUTTON_STATE.DEFAULT);
|
||||
|
||||
/**
|
||||
* Async function that makes a REST API call to retrieve historical email message data sent by the bulk course email
|
||||
* tool from edx-platform.
|
||||
*/
|
||||
async function fetchSentEmailHistoryData() {
|
||||
setButtonState(BUTTON_STATE.PENDING);
|
||||
|
||||
let data = null;
|
||||
try {
|
||||
data = await getSentEmailHistory(courseId);
|
||||
@@ -35,6 +45,24 @@ export function BulkEmailContentHistory({ intl }) {
|
||||
setEmailHistoryData(emails);
|
||||
setShowHistoricalEmailContentTable(true);
|
||||
}
|
||||
|
||||
setButtonState(BUTTON_STATE.COMPLETE);
|
||||
}
|
||||
|
||||
/**
|
||||
* This utility function transforms the data stored in `emailHistoryData` to make it easier to display in the Paragon
|
||||
* DataTable component. Some of the information we want displayed is in an inner object so we extract it and move it
|
||||
* up a level (the `subject` field). We also convert the `sent_to` data to be a String rather than an array to fix a
|
||||
* display bug in the table.
|
||||
*/
|
||||
function transformDataForTable() {
|
||||
const tableData = emailHistoryData.map((item) => ({
|
||||
...item,
|
||||
subject: item.email.subject,
|
||||
sent_to: item.sent_to.join(', '),
|
||||
}));
|
||||
|
||||
return tableData;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -47,34 +75,6 @@ export function BulkEmailContentHistory({ intl }) {
|
||||
setIsMessageModalOpen(true);
|
||||
};
|
||||
|
||||
/**
|
||||
* Render function for the email content history table. If an error occurs while attempting to fetch data from
|
||||
* edx-platform we will render this error instead of the table.
|
||||
*/
|
||||
const renderError = () => (
|
||||
<div>
|
||||
<Alert variant="danger">
|
||||
<p className="font-weight-bold">
|
||||
{intl.formatMessage(messages.errorFetchingData)}
|
||||
</p>
|
||||
</Alert>
|
||||
</div>
|
||||
);
|
||||
|
||||
/**
|
||||
* Render function for the email content history table. If there is no data to display in our table we will render
|
||||
* this informative message instead.
|
||||
*/
|
||||
const renderEmpty = () => (
|
||||
<div className="pt-1">
|
||||
<Alert variant="warning">
|
||||
<p className="font-weight-bold">
|
||||
{intl.formatMessage(messages.noEmailData)}
|
||||
</p>
|
||||
</Alert>
|
||||
</div>
|
||||
);
|
||||
|
||||
/**
|
||||
* Renders a modal that will display the contents of a single historical email message sent via the bulk course email
|
||||
* tool to a user.
|
||||
@@ -132,83 +132,52 @@ export function BulkEmailContentHistory({ intl }) {
|
||||
</div>
|
||||
);
|
||||
|
||||
const tableColumns = [
|
||||
{
|
||||
Header: `${intl.formatMessage(messages.emailHistoryTableColumnHeaderSubject)}`,
|
||||
accessor: 'subject',
|
||||
},
|
||||
{
|
||||
Header: `${intl.formatMessage(messages.emailHistoryTableColumnHeaderAuthor)}`,
|
||||
accessor: 'requester',
|
||||
},
|
||||
{
|
||||
Header: `${intl.formatMessage(messages.emailHistoryTableColumnHeaderRecipients)}`,
|
||||
accessor: 'sent_to',
|
||||
},
|
||||
{
|
||||
Header: `${intl.formatMessage(messages.emailHistoryTableColumnHeaderTimeSent)}`,
|
||||
accessor: 'created',
|
||||
},
|
||||
{
|
||||
Header: `${intl.formatMessage(messages.emailHistoryTableColumnHeaderNumberSent)}`,
|
||||
accessor: 'number_sent',
|
||||
},
|
||||
];
|
||||
|
||||
/**
|
||||
* Render function for the email content history table. This function is responsible for displaying data inside of
|
||||
* the table when the `Show Sent Email History` button is pressed on the page.
|
||||
* Paragon's DataTable supports the ability to add extra columns that might not directly coincide with the data being
|
||||
* represented in the table. We are using an additional column to embed a button that will open a Modal to display the
|
||||
* contents of a previously sent message.
|
||||
*/
|
||||
const renderTable = () => {
|
||||
// Do a little data manipulation to make it easier to display what we want in the table. Pull the email subject out
|
||||
// of the email data. Transforms the `sent_to` array to a string for easier display in our table.
|
||||
const tableData = emailHistoryData.map((item) => ({
|
||||
...item,
|
||||
subject: item.email.subject,
|
||||
sent_to: item.sent_to.join(', '),
|
||||
}));
|
||||
const additionalColumns = () => {
|
||||
const tableData = transformDataForTable();
|
||||
|
||||
return (
|
||||
<div className="pb-3">
|
||||
<p className="font-italic">
|
||||
{intl.formatMessage(messages.emailHistoryTableViewMessageInstructions)}
|
||||
</p>
|
||||
<DataTable
|
||||
itemCount={emailHistoryData.length}
|
||||
columns={[
|
||||
{
|
||||
Header: `${intl.formatMessage(messages.emailHistoryTableColumnHeaderSubject)}`,
|
||||
accessor: 'subject',
|
||||
},
|
||||
{
|
||||
Header: `${intl.formatMessage(messages.emailHistoryTableColumnHeaderAuthor)}`,
|
||||
accessor: 'requester',
|
||||
},
|
||||
{
|
||||
Header: `${intl.formatMessage(messages.emailHistoryTableColumnHeaderRecipients)}`,
|
||||
accessor: 'sent_to',
|
||||
},
|
||||
{
|
||||
Header: `${intl.formatMessage(messages.emailHistoryTableColumnHeaderTimeSent)}`,
|
||||
accessor: 'created',
|
||||
},
|
||||
{
|
||||
Header: `${intl.formatMessage(messages.emailHistoryTableColumnHeaderNumberSent)}`,
|
||||
accessor: 'number_sent',
|
||||
},
|
||||
]}
|
||||
data={tableData}
|
||||
additionalColumns={[
|
||||
{
|
||||
id: 'view_message',
|
||||
Header: `${intl.formatMessage(messages.emailHistoryTableColumnHeaderViewMessage)}`,
|
||||
Cell: ({ row }) => (
|
||||
<Button variant="link" className="px-1" onClick={() => onViewMessageClick(tableData[row.index])}>
|
||||
{intl.formatMessage(messages.buttonViewMessage)}
|
||||
</Button>
|
||||
),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
[
|
||||
{
|
||||
id: 'view_message',
|
||||
Header: `${intl.formatMessage(messages.emailHistoryTableColumnHeaderViewMessage)}`,
|
||||
Cell: ({ row }) => (
|
||||
<Button variant="link" className="px-1" onClick={() => onViewMessageClick(tableData[row.index])}>
|
||||
{intl.formatMessage(messages.buttonViewMessage)}
|
||||
</Button>
|
||||
),
|
||||
},
|
||||
]
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Today there can be three states which the renderTableData function will handle:
|
||||
* 1. There was an error retrieving data from edx-platform and we can't display anything (for now).
|
||||
* 2. There is no email history for this course-run and we have nothing to display to the end user.
|
||||
* 3. We were able to receive historical email content and it will be presented in a table.
|
||||
*/
|
||||
const renderTableData = () => {
|
||||
if (errorRetrievingData) {
|
||||
return renderError();
|
||||
}
|
||||
|
||||
if (!emailHistoryData.length) {
|
||||
return renderEmpty();
|
||||
}
|
||||
|
||||
return renderTable();
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div>
|
||||
@@ -218,10 +187,35 @@ export function BulkEmailContentHistory({ intl }) {
|
||||
<p>
|
||||
{intl.formatMessage(messages.emailHistoryTableSectionButtonHeader)}
|
||||
</p>
|
||||
<Button variant="outline-primary" className="btn btn-outline-primary mb-2" onClick={async () => { await fetchSentEmailHistoryData(); }}>
|
||||
<StatefulButton
|
||||
className="btn btn-outline-primary mb-2"
|
||||
variant="outline-primary"
|
||||
type="submit"
|
||||
onClick={async () => { await fetchSentEmailHistoryData(); }}
|
||||
labels={{
|
||||
default: `${intl.formatMessage(messages.emailHistoryTableSectionButton)}`,
|
||||
pending: `${intl.formatMessage(messages.emailHistoryTableSectionButton)}`,
|
||||
complete: `${intl.formatMessage(messages.emailHistoryTableSectionButton)}`,
|
||||
}}
|
||||
icons={{
|
||||
pending: <Icon src={SpinnerSimple} className="icon-spin" />,
|
||||
}}
|
||||
disabledStates={['error']}
|
||||
state={buttonState}
|
||||
>
|
||||
{intl.formatMessage(messages.emailHistoryTableSectionButton)}
|
||||
</Button>
|
||||
{showHistoricalEmailContentTable && renderTableData()}
|
||||
</StatefulButton>
|
||||
{ showHistoricalEmailContentTable && (
|
||||
<BulkEmailTaskManagerTable
|
||||
error={errorRetrievingData}
|
||||
tableData={transformDataForTable()}
|
||||
tableDescription={intl.formatMessage(messages.emailHistoryTableViewMessageInstructions)}
|
||||
alertWarningMessage={intl.formatMessage(messages.noEmailData)}
|
||||
alertErrorMessage={intl.formatMessage(messages.errorFetchingEmailHistoryData)}
|
||||
columns={tableColumns}
|
||||
additionalColumns={additionalColumns()}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
import { Alert, DataTable } from '@edx/paragon';
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
|
||||
export default function BulkEmailTaskManagerTable(props) {
|
||||
const {
|
||||
errorRetrievingData,
|
||||
tableData,
|
||||
tableDescription,
|
||||
alertWarningMessage,
|
||||
alertErrorMessage,
|
||||
columns,
|
||||
additionalColumns,
|
||||
} = props;
|
||||
|
||||
/**
|
||||
* Sub-render function that creates an Alert component with a specific type and message for display to a user.
|
||||
*/
|
||||
const renderAlert = (alertType, alertMessage) => (
|
||||
<div className="pt-1">
|
||||
<Alert variant={`${alertType}`}>
|
||||
<p className="font-weight-bold">
|
||||
{`${alertMessage}`}
|
||||
</p>
|
||||
</Alert>
|
||||
</div>
|
||||
);
|
||||
|
||||
/**
|
||||
* Responsible for rendering the tables used by the BulkEmailContentHistory, BulkEmailTaskManager, and
|
||||
* BulkEmailTaskHistory components. Conditionally renders a table description as well.
|
||||
*/
|
||||
const renderTable = () => (
|
||||
<div className="pb-3">
|
||||
{tableDescription && (
|
||||
<p className="font-italic">
|
||||
{tableDescription}
|
||||
</p>
|
||||
)}
|
||||
<DataTable
|
||||
itemCount={tableData.length}
|
||||
columns={columns}
|
||||
data={tableData}
|
||||
additionalColumns={additionalColumns}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
/**
|
||||
* Sub-render function that determines if we can render the DataTable. If not, we will render an Alert component to
|
||||
* inform the user why the data/table cannot be displayed.
|
||||
*/
|
||||
const canRenderTable = () => {
|
||||
if (errorRetrievingData) {
|
||||
return renderAlert('danger', alertErrorMessage);
|
||||
}
|
||||
|
||||
if (!tableData.length) {
|
||||
return renderAlert('warning', alertWarningMessage);
|
||||
}
|
||||
|
||||
return renderTable();
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
{canRenderTable()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
BulkEmailTaskManagerTable.propTypes = {
|
||||
errorRetrievingData: PropTypes.bool.isRequired,
|
||||
tableData: PropTypes.arrayOf(PropTypes.object),
|
||||
tableDescription: PropTypes.string,
|
||||
alertWarningMessage: PropTypes.string.isRequired,
|
||||
alertErrorMessage: PropTypes.string.isRequired,
|
||||
columns: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
additionalColumns: PropTypes.arrayOf(PropTypes.object),
|
||||
};
|
||||
|
||||
BulkEmailTaskManagerTable.defaultProps = {
|
||||
tableData: [],
|
||||
tableDescription: '',
|
||||
additionalColumns: [],
|
||||
};
|
||||
@@ -1,32 +1,91 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import React, { useState } from 'react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
|
||||
import { FormattedMessage } from '@edx/frontend-platform/i18n';
|
||||
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
||||
import { getInstructorTasks } from './api';
|
||||
import messages from './messages';
|
||||
import useInterval from '../../../utils/useInterval';
|
||||
import BulkEmailTaskManagerTable from './BulkEmailHistoryTable';
|
||||
|
||||
export default function BulkEmailPendingTasks() {
|
||||
export function BulkEmailPendingTasks({ intl }) {
|
||||
const { courseId } = useParams();
|
||||
|
||||
const [instructorTaskData, setInstructorTaskData] = useState(); // eslint-disable-line no-unused-vars
|
||||
const [instructorTaskData, setInstructorTaskData] = useState();
|
||||
const [errorRetrievingData, setErrorRetrievingData] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
/**
|
||||
* We use a custom hook (`useInterval`) here to setup a timer that will refresh the pending instructor task data
|
||||
* displayed in the table of this component.
|
||||
*/
|
||||
useInterval(() => {
|
||||
async function fetchPendingInstructorTasksData() {
|
||||
const data = await getInstructorTasks(courseId);
|
||||
const { tasks } = data;
|
||||
setInstructorTaskData(tasks);
|
||||
}
|
||||
fetchPendingInstructorTasksData();
|
||||
}, []);
|
||||
|
||||
try {
|
||||
fetchPendingInstructorTasksData();
|
||||
} catch (error) {
|
||||
setErrorRetrievingData(true);
|
||||
}
|
||||
}, 30000);
|
||||
|
||||
const tableColumns = [
|
||||
{
|
||||
Header: `${intl.formatMessage(messages.taskHistoryTableColumnHeaderTaskType)}`,
|
||||
accessor: 'task_type',
|
||||
},
|
||||
{
|
||||
Header: `${intl.formatMessage(messages.taskHistoryTableColumnHeaderTaskInputs)}`,
|
||||
accessor: 'task_input',
|
||||
},
|
||||
{
|
||||
Header: `${intl.formatMessage(messages.taskHistoryTableColumnHeaderTaskId)}`,
|
||||
accessor: 'task_id',
|
||||
},
|
||||
{
|
||||
Header: `${intl.formatMessage(messages.taskHistoryTableColumnHeaderTaskRequester)}`,
|
||||
accessor: 'requester',
|
||||
},
|
||||
{
|
||||
Header: `${intl.formatMessage(messages.taskHistoryTableColumnHeaderTaskSubmittedDate)}`,
|
||||
accessor: 'created',
|
||||
},
|
||||
{
|
||||
Header: `${intl.formatMessage(messages.taskHistoryTableColumnHeaderTaskDuration)}`,
|
||||
accessor: 'duration_sec',
|
||||
},
|
||||
{
|
||||
Header: `${intl.formatMessage(messages.taskHistoryTableColumnHeaderTaskState)}`,
|
||||
accessor: 'task_state',
|
||||
},
|
||||
{
|
||||
Header: `${intl.formatMessage(messages.taskHistoryTableColumnHeaderTaskStatus)}`,
|
||||
accessor: 'status',
|
||||
},
|
||||
{
|
||||
Header: `${intl.formatMessage(messages.taskHistoryTableColumnHeaderTaskProgress)}`,
|
||||
accessor: 'task_message',
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<div>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="bulk.email.pending.tasks.section.info"
|
||||
defaultMessage="Email actions run in the background. The status for any active tasks - including email tasks - appears in the table below"
|
||||
description="A section to see pending and executing Instructor Tasks"
|
||||
/>
|
||||
</p>
|
||||
<div className="pb-4">
|
||||
<BulkEmailTaskManagerTable
|
||||
error={errorRetrievingData}
|
||||
tableData={instructorTaskData}
|
||||
tableDescription={intl.formatMessage(messages.pendingTaskSectionInfo)}
|
||||
alertWarningMessage={intl.formatMessage(messages.noPendingTaskData)}
|
||||
alertErrorMessage={intl.formatMessage(messages.errorFetchingPendingTaskData)}
|
||||
columns={tableColumns}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
BulkEmailPendingTasks.propTypes = {
|
||||
intl: intlShape.isRequired,
|
||||
};
|
||||
|
||||
export default injectIntl(BulkEmailPendingTasks);
|
||||
|
||||
@@ -1,41 +1,129 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import React, { useState } from 'react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
||||
|
||||
import { FormattedMessage } from '@edx/frontend-platform/i18n';
|
||||
import { Icon, StatefulButton } from '@edx/paragon';
|
||||
import { SpinnerSimple } from '@edx/paragon/icons';
|
||||
import { getEmailTaskHistory } from './api';
|
||||
import messages from './messages';
|
||||
|
||||
export default function BulkEmailTaskHistory() {
|
||||
import BulkEmailTaskManagerTable from './BulkEmailHistoryTable';
|
||||
|
||||
export function BulkEmailTaskHistory({ intl }) {
|
||||
const { courseId } = useParams();
|
||||
const BUTTON_STATE = {
|
||||
DEFAULT: 'default',
|
||||
PENDING: 'pending',
|
||||
COMPLETE: 'complete',
|
||||
};
|
||||
|
||||
const [emailTaskHistoryData, setEmailTaskHistoryData] = useState(); // eslint-disable-line no-unused-vars
|
||||
const [emailTaskHistoryData, setEmailTaskHistoryData] = useState();
|
||||
const [showHistoricalTaskContentTable, setShowHistoricalTaskContentTable] = useState(false);
|
||||
const [errorRetrievingData, setErrorRetrievingData] = useState(false);
|
||||
const [buttonState, setButtonState] = useState(BUTTON_STATE.DEFAULT);
|
||||
|
||||
useEffect(() => {
|
||||
async function fetchEmailTaskHistoryData() {
|
||||
const data = await getEmailTaskHistory(courseId);
|
||||
/**
|
||||
* Async function that makes a REST API call to retrieve historical bulk email (Instructor) task data for display
|
||||
* within this component.
|
||||
*/
|
||||
async function fetchEmailTaskHistoryData() {
|
||||
setButtonState(BUTTON_STATE.PENDING);
|
||||
|
||||
let data = null;
|
||||
try {
|
||||
data = await getEmailTaskHistory(courseId);
|
||||
} catch (error) {
|
||||
setErrorRetrievingData(true);
|
||||
}
|
||||
|
||||
if (data) {
|
||||
const { tasks } = data;
|
||||
setEmailTaskHistoryData(tasks);
|
||||
setShowHistoricalTaskContentTable(true);
|
||||
}
|
||||
fetchEmailTaskHistoryData();
|
||||
}, []);
|
||||
|
||||
setButtonState(BUTTON_STATE.COMPLETE);
|
||||
}
|
||||
|
||||
const tableColumns = [
|
||||
{
|
||||
Header: `${intl.formatMessage(messages.taskHistoryTableColumnHeaderTaskType)}`,
|
||||
accessor: 'task_type',
|
||||
},
|
||||
{
|
||||
Header: `${intl.formatMessage(messages.taskHistoryTableColumnHeaderTaskInputs)}`,
|
||||
accessor: 'task_input',
|
||||
},
|
||||
{
|
||||
Header: `${intl.formatMessage(messages.taskHistoryTableColumnHeaderTaskId)}`,
|
||||
accessor: 'task_id',
|
||||
},
|
||||
{
|
||||
Header: `${intl.formatMessage(messages.taskHistoryTableColumnHeaderTaskRequester)}`,
|
||||
accessor: 'requester',
|
||||
},
|
||||
{
|
||||
Header: `${intl.formatMessage(messages.taskHistoryTableColumnHeaderTaskSubmittedDate)}`,
|
||||
accessor: 'created',
|
||||
},
|
||||
{
|
||||
Header: `${intl.formatMessage(messages.taskHistoryTableColumnHeaderTaskDuration)}`,
|
||||
accessor: 'duration_sec',
|
||||
},
|
||||
{
|
||||
Header: `${intl.formatMessage(messages.taskHistoryTableColumnHeaderTaskState)}`,
|
||||
accessor: 'task_state',
|
||||
},
|
||||
{
|
||||
Header: `${intl.formatMessage(messages.taskHistoryTableColumnHeaderTaskStatus)}`,
|
||||
accessor: 'status',
|
||||
},
|
||||
{
|
||||
Header: `${intl.formatMessage(messages.taskHistoryTableColumnHeaderTaskProgress)}`,
|
||||
accessor: 'task_message',
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="bulk.email.task.history.section.heading"
|
||||
defaultMessage="To see the status for all email tasks submitted for this course, click this button:"
|
||||
description="Instructions for course staff and admins to view historical bulk course email task data"
|
||||
/>
|
||||
{intl.formatMessage(messages.emailTaskHistoryTableSectionButtonHeader)}
|
||||
</p>
|
||||
<button type="button" className="btn btn-outline-primary mb-2">
|
||||
<FormattedMessage
|
||||
id="bulk.email.view.task.history.button"
|
||||
defaultMessage="Show Task Email History"
|
||||
description="Button that displays a table with historical bulk email task data for a course-run"
|
||||
<StatefulButton
|
||||
className="btn btn-outline-primary mb-2"
|
||||
variant="outline-primary"
|
||||
type="submit"
|
||||
onClick={async () => { await fetchEmailTaskHistoryData(); }}
|
||||
labels={{
|
||||
default: `${intl.formatMessage(messages.emailTaskHistoryTableSectionButton)}`,
|
||||
pending: `${intl.formatMessage(messages.emailTaskHistoryTableSectionButton)}`,
|
||||
complete: `${intl.formatMessage(messages.emailTaskHistoryTableSectionButton)}`,
|
||||
}}
|
||||
icons={{
|
||||
pending: <Icon src={SpinnerSimple} className="icon-spin" />,
|
||||
}}
|
||||
disabledStates={['error']}
|
||||
state={buttonState}
|
||||
>
|
||||
{intl.formatMessage(messages.emailHistoryTableSectionButton)}
|
||||
</StatefulButton>
|
||||
{showHistoricalTaskContentTable && (
|
||||
<BulkEmailTaskManagerTable
|
||||
error={errorRetrievingData}
|
||||
tableData={emailTaskHistoryData}
|
||||
alertWarningMessage={intl.formatMessage(messages.noTaskHistoryData)}
|
||||
alertErrorMessage={intl.formatMessage(messages.errorFetchingTaskHistoryData)}
|
||||
columns={tableColumns}
|
||||
/>
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
BulkEmailTaskHistory.propTypes = {
|
||||
intl: intlShape.isRequired,
|
||||
};
|
||||
|
||||
export default injectIntl(BulkEmailTaskHistory);
|
||||
|
||||
@@ -1,30 +1,23 @@
|
||||
import React from 'react';
|
||||
|
||||
import { FormattedMessage } from '@edx/frontend-platform/i18n';
|
||||
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
||||
import BulkEmailContentHistory from './BulkEmailContentHistory';
|
||||
import BulkEmailPendingTasks from './BulkEmailPendingTasks';
|
||||
import BulkEmailTaskHistory from './BulkEmailTaskHistory';
|
||||
import messages from './messages';
|
||||
|
||||
export default function BulkEmailTaskManager() {
|
||||
export function BulkEmailTaskManager({ intl }) {
|
||||
return (
|
||||
<div className="px-5">
|
||||
<div>
|
||||
<h2 className="h3">
|
||||
<FormattedMessage
|
||||
id="bulk.email.pending.tasks.section.heading"
|
||||
defaultMessage="Pending Tasks"
|
||||
description="A section to see pending and executing Instructor Tasks"
|
||||
/>
|
||||
{intl.formatMessage(messages.pendingTasksHeader)}
|
||||
</h2>
|
||||
<BulkEmailPendingTasks />
|
||||
</div>
|
||||
<div>
|
||||
<h2 className="h3">
|
||||
<FormattedMessage
|
||||
id="bulk.email.task.manager.heading"
|
||||
defaultMessage="Email Task History"
|
||||
description="Title of the Email task History section of the Bulk Course Email tool"
|
||||
/>
|
||||
{intl.formatMessage(messages.emailTaskHistoryHeader)}
|
||||
</h2>
|
||||
<BulkEmailContentHistory />
|
||||
</div>
|
||||
@@ -34,3 +27,9 @@ export default function BulkEmailTaskManager() {
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
BulkEmailTaskManager.propTypes = {
|
||||
intl: intlShape.isRequired,
|
||||
};
|
||||
|
||||
export default injectIntl(BulkEmailTaskManager);
|
||||
|
||||
@@ -2,13 +2,13 @@ import { defineMessages } from '@edx/frontend-platform/i18n';
|
||||
|
||||
const messages = defineMessages({
|
||||
/* BulkEmailContentHistory.jsx Messages */
|
||||
errorFetchingData: {
|
||||
errorFetchingEmailHistoryData: {
|
||||
id: 'bulk.email.content.history.table.alert.errorFetchingData',
|
||||
defaultMessage: 'An error occurred retrieving email history data for this course. Please try again later.',
|
||||
},
|
||||
noEmailData: {
|
||||
id: 'bulk.email.content.history.table.alert.noEmailData',
|
||||
defaultMessage: 'There is no email history for this course',
|
||||
defaultMessage: 'There is no email history for this course.',
|
||||
},
|
||||
buttonViewMessage: {
|
||||
id: 'bulk.email.content.history.table.button.viewMessage',
|
||||
@@ -71,8 +71,81 @@ const messages = defineMessages({
|
||||
defaultMessage: 'Show Sent Email History',
|
||||
},
|
||||
/* BulkEmailTaskManager.jsx messages */
|
||||
pendingTasksHeader: {
|
||||
id: 'bulk.email.pending.tasks.header',
|
||||
defaultMessage: 'Pending Tasks',
|
||||
},
|
||||
emailTaskHistoryHeader: {
|
||||
id: 'bulk.email.email.task.history.header',
|
||||
defaultMessage: 'Email Task History',
|
||||
},
|
||||
/* BulkEmailPendingTasks.jsx messages */
|
||||
pendingTaskSectionInfo: {
|
||||
id: 'bulk.email.pending.tasks.section.info',
|
||||
defaultMessage: 'Email actions run in the background. The status for any active tasks - including email tasks - appears in the table below.',
|
||||
},
|
||||
errorFetchingPendingTaskData: {
|
||||
id: 'bulk.email.pending.tasks.table.alert.errorFetchingData',
|
||||
defaultMessage: 'Error fetching Instructor Task data. This request will be retried automatically.',
|
||||
},
|
||||
noPendingTaskData: {
|
||||
id: 'bulk.email.pending.tasks.table.alert.noTaskData',
|
||||
defaultMessage: 'No tasks currently running.',
|
||||
},
|
||||
/* BulkEmailTaskHistory.jsx messages */
|
||||
emailTaskHistoryTableSectionButtonHeader: {
|
||||
id: 'bulk.email.task.history.table.button.header',
|
||||
defaultMessage: 'To see the status for all email tasks submitted for this course, click this button:',
|
||||
},
|
||||
emailTaskHistoryTableSectionButton: {
|
||||
id: 'bulk.email.task.history.table.button',
|
||||
defaultMessage: 'Show Email Task History',
|
||||
},
|
||||
errorFetchingTaskHistoryData: {
|
||||
id: 'bulk.email.task.history.table.alert.errorFetchingData',
|
||||
defaultMessage: 'Error fetching email task history data for this course. Please try again later.',
|
||||
},
|
||||
noTaskHistoryData: {
|
||||
id: 'bulk.email.task.history.table.alert.noTaskData',
|
||||
defaultMessage: 'There is no email task history for this course.',
|
||||
},
|
||||
/* Common Messages */
|
||||
taskHistoryTableColumnHeaderTaskType: {
|
||||
id: 'bulk.email.task.history.table.column.header.taskType',
|
||||
defaultMessage: 'Task Type',
|
||||
},
|
||||
taskHistoryTableColumnHeaderTaskInputs: {
|
||||
id: 'bulk.email.task.history.table.column.header.taskInputs',
|
||||
defaultMessage: 'Task Inputs',
|
||||
},
|
||||
taskHistoryTableColumnHeaderTaskId: {
|
||||
id: 'bulk.email.task.history.table.column.header.taskId',
|
||||
defaultMessage: 'Task Id',
|
||||
},
|
||||
taskHistoryTableColumnHeaderTaskRequester: {
|
||||
id: 'bulk.email.task.history.table.column.header.taskRequester',
|
||||
defaultMessage: 'Requester',
|
||||
},
|
||||
taskHistoryTableColumnHeaderTaskSubmittedDate: {
|
||||
id: 'bulk.email.task.history.table.column.header.taskSubmittedDate',
|
||||
defaultMessage: 'Submitted',
|
||||
},
|
||||
taskHistoryTableColumnHeaderTaskDuration: {
|
||||
id: 'bulk.email.task.history.table.column.header.taskDuration',
|
||||
defaultMessage: 'Duration (seconds)',
|
||||
},
|
||||
taskHistoryTableColumnHeaderTaskState: {
|
||||
id: 'bulk.email.task.history.table.column.header.taskState',
|
||||
defaultMessage: 'State',
|
||||
},
|
||||
taskHistoryTableColumnHeaderTaskStatus: {
|
||||
id: 'bulk.email.task.history.table.column.header.taskStatus',
|
||||
defaultMessage: 'Status',
|
||||
},
|
||||
taskHistoryTableColumnHeaderTaskProgress: {
|
||||
id: 'bulk.email.task.history.table.column.header.taskProgress',
|
||||
defaultMessage: 'Task Progress',
|
||||
},
|
||||
});
|
||||
|
||||
export default messages;
|
||||
|
||||
28
src/utils/useInterval.js
Normal file
28
src/utils/useInterval.js
Normal file
@@ -0,0 +1,28 @@
|
||||
import { useEffect, useRef } from 'react';
|
||||
|
||||
/**
|
||||
* A custom hook used by the BulkEmailPendingTasks component to periodically make an API call on a regular interval.
|
||||
* This is lifted from: https://overreacted.io/making-setinterval-declarative-with-react-hooks/.
|
||||
*/
|
||||
export default function useInterval(callback, delay) {
|
||||
const savedCallback = useRef();
|
||||
|
||||
// Remember the latest callback
|
||||
useEffect(() => {
|
||||
savedCallback.current = callback;
|
||||
}, [callback]);
|
||||
|
||||
// Set up the interval
|
||||
useEffect(() => {
|
||||
function tick() {
|
||||
savedCallback.current();
|
||||
}
|
||||
|
||||
if (delay !== null) {
|
||||
const id = setInterval(tick, delay);
|
||||
return () => clearInterval(id);
|
||||
}
|
||||
|
||||
return null;
|
||||
}, [delay]);
|
||||
}
|
||||
Reference in New Issue
Block a user