From 1252f35a231307a3a9e28d4dbb65ab5e03c64138 Mon Sep 17 00:00:00 2001 From: Rostislav Wolny Date: Mon, 23 Jul 2018 10:14:13 +0200 Subject: [PATCH 1/3] Add sending queue status to help page [MAILPOET-1459] --- assets/js/src/help/cron_status.jsx | 4 +- assets/js/src/help/queue_status.jsx | 91 ++++++++++++++++++++++++++++ assets/js/src/help/system_status.jsx | 2 + lib/Config/Menu.php | 6 ++ lib/Models/ScheduledTask.php | 1 + lib/Tasks/State.php | 35 +++++++++++ views/help.html | 13 +++- 7 files changed, 149 insertions(+), 3 deletions(-) create mode 100644 assets/js/src/help/queue_status.jsx create mode 100644 lib/Tasks/State.php diff --git a/assets/js/src/help/cron_status.jsx b/assets/js/src/help/cron_status.jsx index 80b268ab74..73c6a15e44 100644 --- a/assets/js/src/help/cron_status.jsx +++ b/assets/js/src/help/cron_status.jsx @@ -13,7 +13,7 @@ function renderStatusTableRow(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 ( @@ -41,7 +41,7 @@ const CronStatus = (props) => { 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 || '-')} + {renderStatusTableRow(MailPoet.I18n.t('lastSeenError'), 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..b0f0064f4d --- /dev/null +++ b/assets/js/src/help/queue_status.jsx @@ -0,0 +1,91 @@ +import MailPoet from 'mailpoet'; +import React from 'react'; + +function renderStatusTableRow(title, value) { + return ( + + { title }{ value } + + ); +} + +const QueueStatus = (props) => { + const status = props.status_data; + return ( +
+

{MailPoet.I18n.t('systemStatusQueueTitle')}

+ + + {renderStatusTableRow( + MailPoet.I18n.t('status'), + status.status === 'paused' ? MailPoet.I18n.t('paused') : MailPoet.I18n.t('running')) + } + {renderStatusTableRow( + MailPoet.I18n.t('startedAt'), + status.started ? MailPoet.Date.full(status.started * 1000) : MailPoet.I18n.t('unknown')) + } + {renderStatusTableRow( + MailPoet.I18n.t('sentEmails'), + status.sent || 0) + } + {renderStatusTableRow( + MailPoet.I18n.t('retryAttempts'), + status.retry_attempt || MailPoet.I18n.t('none')) + } + {renderStatusTableRow( + MailPoet.I18n.t('retryAt'), + status.retry_at ? MailPoet.Date.full(status.retry_at * 1000) : MailPoet.I18n.t('none')) + } + {renderStatusTableRow( + MailPoet.I18n.t('error'), + status.error || MailPoet.I18n.t('none')) + } + {renderStatusTableRow( + MailPoet.I18n.t('totalCompletedTasks'), + status.tasksStatusCounts.completed) + } + {renderStatusTableRow( + MailPoet.I18n.t('totalRunningTasks'), + status.tasksStatusCounts.running) + } + {renderStatusTableRow( + MailPoet.I18n.t('totalPausedTasks'), + status.tasksStatusCounts.paused) + } + {renderStatusTableRow( + MailPoet.I18n.t('totalScheduledTasks'), + 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..cfc222bf49 100644 --- a/views/help.html +++ b/views/help.html @@ -34,6 +34,7 @@ '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'), + 'systemStatusQueueTitle': __('Sending Queue'), 'lastUpdated': __('Last updated'), 'lastRunStarted': __('Last run started'), 'lastRunCompleted': __('Last run completed'), @@ -43,8 +44,18 @@ 'status': __('Status'), 'yes': __('yes'), 'no': __('no'), - 'cronRunning': __('running'), + 'none': __('none'), + 'running': __('running'), 'cronWaiting': __('waiting for the next run'), + 'startedAt': __('Started at'), + 'sentEmails': __('Sent emails'), + 'retryAttempt': __('Retry attempt'), + 'retryAt': __('Retry at'), + 'error': __('Error'), + 'totalCompletedTasks': __('Total completed tasks'), + 'totalScheduledTasks': __('Total scheduled tasks'), + 'totalRunningTasks': __('Total running tasks'), + 'totalPausedTasks': __('Total paused tasks'), }) %> <% endblock %> From 490ea67d181f42fd5938981ab4dcfb2fb2c0be3a Mon Sep 17 00:00:00 2001 From: Rostislav Wolny Date: Wed, 25 Jul 2018 11:10:20 +0200 Subject: [PATCH 2/3] Refactor help page status tables into component [MAILPOET-1459] --- assets/js/src/common/key_value_table.jsx | 31 +++++++++ assets/js/src/help/cron_status.jsx | 60 ++++++++--------- assets/js/src/help/queue_status.jsx | 86 ++++++++++-------------- 3 files changed, 92 insertions(+), 85 deletions(-) create mode 100644 assets/js/src/common/key_value_table.jsx 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 73c6a15e44..e399302560 100644 --- a/assets/js/src/help/cron_status.jsx +++ b/assets/js/src/help/cron_status.jsx @@ -1,15 +1,8 @@ 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 = { @@ -19,31 +12,32 @@ const CronStatus = (props) => { 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 || MailPoet.I18n.t('none'))} - -
+ {[ + { + 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 index b0f0064f4d..55caeb0e24 100644 --- a/assets/js/src/help/queue_status.jsx +++ b/assets/js/src/help/queue_status.jsx @@ -1,63 +1,45 @@ import MailPoet from 'mailpoet'; import React from 'react'; - -function renderStatusTableRow(title, value) { - return ( - - { title }{ value } - - ); -} +import KeyValueTable from 'common/key_value_table.jsx'; const QueueStatus = (props) => { const status = props.status_data; return (

{MailPoet.I18n.t('systemStatusQueueTitle')}

- - - {renderStatusTableRow( - MailPoet.I18n.t('status'), - status.status === 'paused' ? MailPoet.I18n.t('paused') : MailPoet.I18n.t('running')) - } - {renderStatusTableRow( - MailPoet.I18n.t('startedAt'), - status.started ? MailPoet.Date.full(status.started * 1000) : MailPoet.I18n.t('unknown')) - } - {renderStatusTableRow( - MailPoet.I18n.t('sentEmails'), - status.sent || 0) - } - {renderStatusTableRow( - MailPoet.I18n.t('retryAttempts'), - status.retry_attempt || MailPoet.I18n.t('none')) - } - {renderStatusTableRow( - MailPoet.I18n.t('retryAt'), - status.retry_at ? MailPoet.Date.full(status.retry_at * 1000) : MailPoet.I18n.t('none')) - } - {renderStatusTableRow( - MailPoet.I18n.t('error'), - status.error || MailPoet.I18n.t('none')) - } - {renderStatusTableRow( - MailPoet.I18n.t('totalCompletedTasks'), - status.tasksStatusCounts.completed) - } - {renderStatusTableRow( - MailPoet.I18n.t('totalRunningTasks'), - status.tasksStatusCounts.running) - } - {renderStatusTableRow( - MailPoet.I18n.t('totalPausedTasks'), - status.tasksStatusCounts.paused) - } - {renderStatusTableRow( - MailPoet.I18n.t('totalScheduledTasks'), - status.tasksStatusCounts.scheduled) - } - -
+ { + [{ + 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, + }]} +
); }; From d7f12b5973f30bc9f739e6e1c5cd3549e2e193dd Mon Sep 17 00:00:00 2001 From: Rostislav Wolny Date: Thu, 26 Jul 2018 09:21:29 +0200 Subject: [PATCH 3/3] Add context for translators on help page [MAILPOET-1459] --- views/help.html | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/views/help.html b/views/help.html index cfc222bf49..899b1db1e0 100644 --- a/views/help.html +++ b/views/help.html @@ -35,23 +35,23 @@ 'systemInfoDataError': __('Sorry, there was an error, please try again later.'), 'systemStatusCronStatusTitle': __('Cron'), 'systemStatusQueueTitle': __('Sending Queue'), - 'lastUpdated': __('Last updated'), - 'lastRunStarted': __('Last run started'), - 'lastRunCompleted': __('Last run completed'), - 'lastSeenError': __('Last seen error'), - 'unknown': __('unknown'), - 'accessible': __('Accessible'), + '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'), - 'none': __('none'), - 'running': __('running'), - 'cronWaiting': __('waiting for the next run'), - 'startedAt': __('Started at'), - 'sentEmails': __('Sent emails'), - 'retryAttempt': __('Retry attempt'), - 'retryAt': __('Retry at'), - 'error': __('Error'), + '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'),