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 %>