diff --git a/assets/js/src/common/key_value_table.jsx b/assets/js/src/common/key_value_table.jsx
new file mode 100644
index 0000000000..bcda7fee7e
--- /dev/null
+++ b/assets/js/src/common/key_value_table.jsx
@@ -0,0 +1,31 @@
+import React from 'react';
+
+const KeyValueTable = props => (
+
+
+ {props.children.map(row => (
+
+ { row.key } | { row.value } |
+
+ ))}
+
+
+);
+
+KeyValueTable.propTypes = {
+ max_width: React.PropTypes.string,
+ children: React.PropTypes.arrayOf(React.PropTypes.shape({
+ key: React.PropTypes.string.isRequired,
+ value: React.PropTypes.oneOfType([
+ React.PropTypes.string,
+ React.PropTypes.number,
+ React.PropTypes.element,
+ ]).isRequired,
+ })).isRequired,
+};
+
+KeyValueTable.defaultProps = {
+ max_width: 'auto',
+};
+
+module.exports = KeyValueTable;
diff --git a/assets/js/src/help/cron_status.jsx b/assets/js/src/help/cron_status.jsx
index 80b268ab74..e399302560 100644
--- a/assets/js/src/help/cron_status.jsx
+++ b/assets/js/src/help/cron_status.jsx
@@ -1,49 +1,43 @@
import MailPoet from 'mailpoet';
import React from 'react';
+import KeyValueTable from 'common/key_value_table.jsx';
import PrintBoolean from 'common/print_boolean.jsx';
-function renderStatusTableRow(title, value) {
- return (
-
- { title } | { value } |
-
- );
-}
-
const CronStatus = (props) => {
const status = props.status_data;
const activeStatusMapping = {
- active: MailPoet.I18n.t('cronRunning'),
+ active: MailPoet.I18n.t('running'),
inactive: MailPoet.I18n.t('cronWaiting'),
};
return (
{MailPoet.I18n.t('systemStatusCronStatusTitle')}
-
-
- {renderStatusTableRow(
- MailPoet.I18n.t('accessible'),
- {status.accessible})
- }
- {renderStatusTableRow(
- MailPoet.I18n.t('status'),
- activeStatusMapping[status.status] ? activeStatusMapping[status.status] : MailPoet.I18n.t('unknown'))
- }
- {renderStatusTableRow(
- MailPoet.I18n.t('lastUpdated'),
- status.updated_at ? MailPoet.Date.full(status.updated_at * 1000) : MailPoet.I18n.t('unknown'))
- }
- {renderStatusTableRow(
- MailPoet.I18n.t('lastRunStarted'),
- status.run_accessed_at ? MailPoet.Date.full(status.run_started_at * 1000) : MailPoet.I18n.t('unknown'))
- }
- {renderStatusTableRow(
- MailPoet.I18n.t('lastRunCompleted'),
- status.run_completed_at ? MailPoet.Date.full(status.run_completed_at * 1000) : MailPoet.I18n.t('unknown'))
- }
- {renderStatusTableRow(MailPoet.I18n.t('lastSeenError'), status.last_error || '-')}
-
-
+
{[
+ {
+ key: MailPoet.I18n.t('accessible'),
+ value: {status.accessible},
+ },
+ {
+ key: MailPoet.I18n.t('status'),
+ value: activeStatusMapping[status.status] ? activeStatusMapping[status.status] : MailPoet.I18n.t('unknown'),
+ },
+ {
+ key: MailPoet.I18n.t('lastUpdated'),
+ value: status.updated_at ? MailPoet.Date.full(status.updated_at * 1000) : MailPoet.I18n.t('unknown'),
+ },
+ {
+ key: MailPoet.I18n.t('lastRunStarted'),
+ value: status.run_accessed_at ? MailPoet.Date.full(status.run_started_at * 1000) : MailPoet.I18n.t('unknown'),
+ },
+ {
+ key: MailPoet.I18n.t('lastRunCompleted'),
+ value: status.run_completed_at ? MailPoet.Date.full(status.run_completed_at * 1000) : MailPoet.I18n.t('unknown'),
+ },
+ {
+ key: MailPoet.I18n.t('lastSeenError'),
+ value: status.last_error || MailPoet.I18n.t('none'),
+ }]}
+
);
};
diff --git a/assets/js/src/help/queue_status.jsx b/assets/js/src/help/queue_status.jsx
new file mode 100644
index 0000000000..55caeb0e24
--- /dev/null
+++ b/assets/js/src/help/queue_status.jsx
@@ -0,0 +1,73 @@
+import MailPoet from 'mailpoet';
+import React from 'react';
+import KeyValueTable from 'common/key_value_table.jsx';
+
+const QueueStatus = (props) => {
+ const status = props.status_data;
+ return (
+
+
{MailPoet.I18n.t('systemStatusQueueTitle')}
+ {
+ [{
+ key: MailPoet.I18n.t('status'),
+ value: status.status === 'paused' ? MailPoet.I18n.t('paused') : MailPoet.I18n.t('running'),
+ }, {
+ key: MailPoet.I18n.t('startedAt'),
+ value: status.started ? MailPoet.Date.full(status.started * 1000) : MailPoet.I18n.t('unknown'),
+ }, {
+ key: MailPoet.I18n.t('sentEmails'),
+ value: status.sent || 0,
+ }, {
+ key: MailPoet.I18n.t('retryAttempt'),
+ value: status.retry_attempt || MailPoet.I18n.t('none'),
+ }, {
+ key: MailPoet.I18n.t('retryAt'),
+ value: status.retry_at ? MailPoet.Date.full(status.retry_at * 1000) : MailPoet.I18n.t('none'),
+ }, {
+ key: MailPoet.I18n.t('error'),
+ value: status.error || MailPoet.I18n.t('none'),
+ }, {
+ key: MailPoet.I18n.t('totalCompletedTasks'),
+ value: status.tasksStatusCounts.completed,
+ }, {
+ key: MailPoet.I18n.t('totalRunningTasks'),
+ value: status.tasksStatusCounts.running,
+ }, {
+ key: MailPoet.I18n.t('totalPausedTasks'),
+ value: status.tasksStatusCounts.paused,
+ }, {
+ key: MailPoet.I18n.t('totalScheduledTasks'),
+ value: status.tasksStatusCounts.scheduled,
+ }]}
+
+
+ );
+};
+
+QueueStatus.propTypes = {
+ status_data: React.PropTypes.shape({
+ status: React.PropTypes.string,
+ started: React.PropTypes.number,
+ sent: React.PropTypes.number,
+ retry_attempt: React.PropTypes.number,
+ retry_at: React.PropTypes.number,
+ tasksStatusCounts: React.PropTypes.shape({
+ completed: React.PropTypes.number.isRequired,
+ running: React.PropTypes.number.isRequired,
+ paused: React.PropTypes.number.isRequired,
+ scheduled: React.PropTypes.number.isRequired,
+ }).isRequired,
+ }).isRequired,
+};
+
+QueueStatus.defaultProps = {
+ status_data: {
+ status: null,
+ started: null,
+ sent: null,
+ retry_attempt: null,
+ retry_at: null,
+ },
+};
+
+module.exports = QueueStatus;
diff --git a/assets/js/src/help/system_status.jsx b/assets/js/src/help/system_status.jsx
index 7eb7f7b8cc..943c5009f1 100644
--- a/assets/js/src/help/system_status.jsx
+++ b/assets/js/src/help/system_status.jsx
@@ -2,6 +2,7 @@ import MailPoet from 'mailpoet';
import React from 'react';
import ReactStringReplace from 'react-string-replace';
import CronStatus from './cron_status.jsx';
+import QueueStatus from './queue_status.jsx';
import Tabs from './tabs.jsx';
function renderStatusMessage(status, error, link) {
@@ -67,6 +68,7 @@ function SystemStatus() {
{renderCronSection(systemStatusData)}
{renderMSSSection(systemStatusData)}
+
);
}
diff --git a/lib/Config/Menu.php b/lib/Config/Menu.php
index 97f168d3f7..6892703c2e 100644
--- a/lib/Config/Menu.php
+++ b/lib/Config/Menu.php
@@ -9,8 +9,10 @@ use MailPoet\Form\Block;
use MailPoet\Form\Renderer as FormRenderer;
use MailPoet\Helpscout\Beacon;
use MailPoet\Listing;
+use MailPoet\Mailer\MailerLog;
use MailPoet\Models\CustomField;
use MailPoet\Models\Form;
+use MailPoet\Models\ScheduledTask;
use MailPoet\Models\Segment;
use MailPoet\Models\Setting;
use MailPoet\Models\Subscriber;
@@ -20,6 +22,7 @@ use MailPoet\Services\Bridge;
use MailPoet\Settings\Hosts;
use MailPoet\Settings\Pages;
use MailPoet\Subscribers\ImportExport\ImportExportFactory;
+use MailPoet\Tasks\State;
use MailPoet\Util\License\Features\Subscribers as SubscribersFeature;
use MailPoet\Util\License\License;
use MailPoet\WP\DateTime;
@@ -447,6 +450,7 @@ class Menu {
function help() {
+ $tasks_state = new State();
$system_info_data = Beacon::getData();
$system_status_data = [
'cron' => [
@@ -459,8 +463,10 @@ class Menu {
false
],
'cronStatus' => CronHelper::getDaemon(),
+ 'queueStatus' => MailerLog::getMailerLog(),
];
$system_status_data['cronStatus']['accessible'] = CronHelper::isDaemonAccessible();
+ $system_status_data['queueStatus']['tasksStatusCounts'] = $tasks_state->getCountsPerStatus();
$this->displayPage(
'help.html',
array(
diff --git a/lib/Models/ScheduledTask.php b/lib/Models/ScheduledTask.php
index 525f896ce1..9dbc38558e 100644
--- a/lib/Models/ScheduledTask.php
+++ b/lib/Models/ScheduledTask.php
@@ -11,6 +11,7 @@ class ScheduledTask extends Model {
const STATUS_COMPLETED = 'completed';
const STATUS_SCHEDULED = 'scheduled';
const STATUS_PAUSED = 'paused';
+ const VIRTUAL_STATUS_RUNNING = 'running'; // For historical reasons this is stored as null in DB
const PRIORITY_HIGH = 1;
const PRIORITY_MEDIUM = 5;
const PRIORITY_LOW = 10;
diff --git a/lib/Tasks/State.php b/lib/Tasks/State.php
new file mode 100644
index 0000000000..fb0a2b14b2
--- /dev/null
+++ b/lib/Tasks/State.php
@@ -0,0 +1,35 @@
+ 0,
+ ScheduledTask::STATUS_PAUSED => 0,
+ ScheduledTask::STATUS_SCHEDULED => 0,
+ ScheduledTask::VIRTUAL_STATUS_RUNNING => 0,
+ ];
+ $counts = ScheduledTask::rawQuery(
+ "SELECT COUNT(*) as value, status
+ FROM `" . ScheduledTask::$_table . "`
+ WHERE deleted_at IS NULL AND `type` = 'sending'
+ GROUP BY status;"
+ )->findMany();
+ foreach($counts as $count) {
+ if($count->status === null) {
+ $stats[ScheduledTask::VIRTUAL_STATUS_RUNNING] = (int)$count->value;
+ continue;
+ }
+ $stats[$count->status] = (int)$count->value;
+ }
+ return $stats;
+ }
+}
diff --git a/views/help.html b/views/help.html
index 1d9c7b9f2f..899b1db1e0 100644
--- a/views/help.html
+++ b/views/help.html
@@ -34,17 +34,28 @@
'systemInfoIntro': __('The information below is useful when you need to get in touch with our support. Just copy all the text below and paste it into a message to us.'),
'systemInfoDataError': __('Sorry, there was an error, please try again later.'),
'systemStatusCronStatusTitle': __('Cron'),
- 'lastUpdated': __('Last updated'),
- 'lastRunStarted': __('Last run started'),
- 'lastRunCompleted': __('Last run completed'),
- 'lastSeenError': __('Last seen error'),
- 'unknown': __('unknown'),
- 'accessible': __('Accessible'),
+ 'systemStatusQueueTitle': __('Sending Queue'),
+ 'lastUpdated': _x('Last updated', 'A label in a status table e.g. Last updated: 2018-10-18 18:50'),
+ 'lastRunStarted': _x('Last run started', 'A label in a status table e.g. Last run started: 2018-10-18 18:50'),
+ 'lastRunCompleted': _x('Last run completed', 'A label in a status table e.g. Last run completed: 2018-10-18 18:50'),
+ 'lastSeenError': _x('Last seen error', 'A label in a status table e.g. Last seen error: Process timeout'),
+ 'unknown': _x('unknown', 'An unknown state is a status table e.g. Last run started: unknown'),
+ 'accessible': _x('Accessible', 'A label in a status table e.g. Accessible: yes'),
'status': __('Status'),
'yes': __('yes'),
'no': __('no'),
- 'cronRunning': __('running'),
- 'cronWaiting': __('waiting for the next run'),
+ 'none': _x('none', 'An empty state is a status table e.g. Error: none'),
+ 'running': _x('running', 'A state of a process.'),
+ 'cronWaiting': _x('waiting for the next run', 'A state of a process.'),
+ 'startedAt': _x('Started at', 'A label in a status table e.g. Started at: 2018-10-18 18:50'),
+ 'sentEmails': _x('Sent emails', 'A label in a status table e.g. Sent emails: 50'),
+ 'retryAttempt': _x('Retry attempt', 'A label in a status table e.g. Retry attempt: 2'),
+ 'retryAt': _x('Retry at', 'A label in a status table e.g. Retry at: 2018-10-18 18:50'),
+ 'error': _x('Error', 'A label in a status table e.g. Error: missing data'),
+ 'totalCompletedTasks': __('Total completed tasks'),
+ 'totalScheduledTasks': __('Total scheduled tasks'),
+ 'totalRunningTasks': __('Total running tasks'),
+ 'totalPausedTasks': __('Total paused tasks'),
}) %>
<% endblock %>