Merge pull request #1436 from mailpoet/cron-status-info

Cron status info in help section [MAILPOET-1457]
This commit is contained in:
Michelle Shull
2018-07-25 10:17:22 -04:00
committed by GitHub
15 changed files with 220 additions and 35 deletions

View File

@ -0,0 +1,26 @@
import React from 'react';
import MailPoet from 'mailpoet';
const PrintBoolean = props => (
<span>
{(props.children === true && props.truthy) ||
(props.children === false && props.falsy) ||
(props.unknown)}
</span>
);
PrintBoolean.propTypes = {
truthy: React.PropTypes.string,
falsy: React.PropTypes.string,
unknown: React.PropTypes.string,
children: React.PropTypes.bool,
};
PrintBoolean.defaultProps = {
truthy: MailPoet.I18n.t('yes'),
falsy: MailPoet.I18n.t('no'),
unknown: MailPoet.I18n.t('unknown'),
children: null,
};
module.exports = PrintBoolean;

View File

@ -0,0 +1,71 @@
import MailPoet from 'mailpoet';
import React from 'react';
import PrintBoolean from 'common/print_boolean.jsx';
function renderStatusTableRow(title, value) {
return (
<tr>
<td className={'row-title'}>{ title }</td><td>{ value }</td>
</tr>
);
}
const CronStatus = (props) => {
const status = props.status_data;
const activeStatusMapping = {
active: MailPoet.I18n.t('cronRunning'),
inactive: MailPoet.I18n.t('cronWaiting'),
};
return (
<div>
<h2>{MailPoet.I18n.t('systemStatusCronStatusTitle')}</h2>
<table className={'widefat fixed'} style={{ maxWidth: '400px' }}>
<tbody>
{renderStatusTableRow(
MailPoet.I18n.t('accessible'),
<PrintBoolean>{status.accessible}</PrintBoolean>)
}
{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 || '-')}
</tbody>
</table>
</div>
);
};
CronStatus.propTypes = {
status_data: React.PropTypes.shape({
accessible: React.PropTypes.bool,
status: React.PropTypes.string,
updated_at: React.PropTypes.number,
run_accessed_at: React.PropTypes.number,
run_completed_at: React.PropTypes.number,
}).isRequired,
};
CronStatus.defaultProps = {
status_data: {
accessible: null,
status: null,
updated_at: null,
run_accessed_at: null,
run_completed_at: null,
},
};
module.exports = CronStatus;

View File

@ -21,11 +21,11 @@ if (container) {
ReactDOM.render(( ReactDOM.render((
<Router history={history}> <Router history={history}>
<Route path="/" component={App}> <Route path="/" component={App}>
<IndexRedirect to="systemStatus" /> <IndexRedirect to="knowledgeBase" />
{/* Pages */} {/* Pages */}
<Route path="knowledgeBase(/)**" params={{ tab: 'knowledgeBase' }} component={KnowledgeBase} />
<Route path="systemStatus(/)**" params={{ tab: 'systemStatus' }} component={SystemStatus} /> <Route path="systemStatus(/)**" params={{ tab: 'systemStatus' }} component={SystemStatus} />
<Route path="systemInfo(/)**" params={{ tab: 'systemInfo' }} component={SystemInfo} /> <Route path="systemInfo(/)**" params={{ tab: 'systemInfo' }} component={SystemInfo} />
<Route path="knowledgeBase(/)**" params={{ tab: 'knowledgeBase' }} component={KnowledgeBase} />
</Route> </Route>
</Router> </Router>
), container); ), container);

View File

@ -1,6 +1,7 @@
import MailPoet from 'mailpoet'; import MailPoet from 'mailpoet';
import React from 'react'; import React from 'react';
import ReactStringReplace from 'react-string-replace'; import ReactStringReplace from 'react-string-replace';
import CronStatus from './cron_status.jsx';
import Tabs from './tabs.jsx'; import Tabs from './tabs.jsx';
function renderStatusMessage(status, error, link) { function renderStatusMessage(status, error, link) {
@ -65,6 +66,7 @@ function SystemStatus() {
</div> </div>
{renderCronSection(systemStatusData)} {renderCronSection(systemStatusData)}
{renderMSSSection(systemStatusData)} {renderMSSSection(systemStatusData)}
<CronStatus status_data={systemStatusData.cronStatus} />
</div> </div>
); );
} }

View File

@ -4,6 +4,11 @@ import classNames from 'classnames';
import MailPoet from 'mailpoet'; import MailPoet from 'mailpoet';
const tabs = [ const tabs = [
{
name: 'knowledgeBase',
label: MailPoet.I18n.t('tabKnowledgeBaseTitle'),
link: '/knowledgeBase',
},
{ {
name: 'systemStatus', name: 'systemStatus',
label: MailPoet.I18n.t('tabSystemStatusTitle'), label: MailPoet.I18n.t('tabSystemStatusTitle'),
@ -14,11 +19,6 @@ const tabs = [
label: MailPoet.I18n.t('tabSystemInfoTitle'), label: MailPoet.I18n.t('tabSystemInfoTitle'),
link: '/systemInfo', link: '/systemInfo',
}, },
{
name: 'knowledgeBase',
label: MailPoet.I18n.t('tabKnowledgeBaseTitle'),
link: '/knowledgeBase',
},
]; ];
function Tabs(props) { function Tabs(props) {
@ -45,6 +45,6 @@ function Tabs(props) {
} }
Tabs.propTypes = { tab: React.PropTypes.string }; Tabs.propTypes = { tab: React.PropTypes.string };
Tabs.defaultProps = { tab: 'systemStatus' }; Tabs.defaultProps = { tab: 'knowledgeBase' };
module.exports = Tabs; module.exports = Tabs;

View File

@ -448,17 +448,19 @@ class Menu {
function help() { function help() {
$system_info_data = Beacon::getData(); $system_info_data = Beacon::getData();
$system_status_data = array( $system_status_data = [
'cron' => array( 'cron' => [
'url' => CronHelper::getCronUrl(CronDaemon::ACTION_PING), 'url' => CronHelper::getCronUrl(CronDaemon::ACTION_PING),
'isReachable' => CronHelper::pingDaemon(true) 'isReachable' => CronHelper::pingDaemon(true)
), ],
'mss' => array( 'mss' => [
'enabled' => (Bridge::isMPSendingServiceEnabled()) ? 'enabled' => (Bridge::isMPSendingServiceEnabled()) ?
array('isReachable' => Bridge::pingBridge()) : ['isReachable' => Bridge::pingBridge()] :
false false
) ],
); 'cronStatus' => CronHelper::getDaemon(),
];
$system_status_data['cronStatus']['accessible'] = CronHelper::isDaemonAccessible();
$this->displayPage( $this->displayPage(
'help.html', 'help.html',
array( array(

View File

@ -16,15 +16,18 @@ class CronHelper {
const DAEMON_EXECUTION_TIMEOUT = 35; // seconds const DAEMON_EXECUTION_TIMEOUT = 35; // seconds
const DAEMON_REQUEST_TIMEOUT = 5; // seconds const DAEMON_REQUEST_TIMEOUT = 5; // seconds
const DAEMON_SETTING = 'cron_daemon'; const DAEMON_SETTING = 'cron_daemon';
const DAEMON_STATUS_ACTIVE = 'active';
const DAEMON_STATUS_INACTIVE = 'inactive';
static function createDaemon($token) { static function createDaemon($token) {
$daemon = array( $daemon = [
'token' => $token, 'token' => $token,
'status' => self::DAEMON_STATUS_ACTIVE,
'run_accessed_at' => null, 'run_accessed_at' => null,
'run_started_at' => null, 'run_started_at' => null,
'run_completed_at' => null, 'run_completed_at' => null,
'last_error' => null, 'last_error' => null,
); ];
self::saveDaemon($daemon); self::saveDaemon($daemon);
return $daemon; return $daemon;
} }
@ -45,8 +48,12 @@ class CronHelper {
); );
} }
static function deleteDaemon() { static function deactivateDaemon($daemon) {
return Setting::deleteValue(self::DAEMON_SETTING); $daemon['status'] = self::DAEMON_STATUS_INACTIVE;
return Setting::setValue(
self::DAEMON_SETTING,
$daemon
);
} }
static function createToken() { static function createToken() {
@ -89,7 +96,7 @@ class CronHelper {
*/ */
static function isDaemonAccessible() { static function isDaemonAccessible() {
$daemon = self::getDaemon(); $daemon = self::getDaemon();
if(!$daemon || $daemon['run_accessed_at'] === null) { if(!$daemon || !isset($daemon['run_accessed_at']) || $daemon['run_accessed_at'] === null) {
return null; return null;
} }
if($daemon['run_accessed_at'] <= (int)$daemon['run_started_at']) { if($daemon['run_accessed_at'] <= (int)$daemon['run_started_at']) {

View File

@ -75,7 +75,7 @@ class Daemon {
} }
// after each execution, re-read daemon data in case it changed // after each execution, re-read daemon data in case it changed
$daemon = CronHelper::getDaemon(); $daemon = CronHelper::getDaemon();
if(!$daemon || $daemon['token'] !== $this->token) { if($this->shouldTerminateExecution($daemon)) {
return $this->terminateRequest(); return $this->terminateRequest();
} }
return $this->callSelf(); return $this->callSelf();
@ -128,4 +128,13 @@ class Daemon {
function terminateRequest($message = false) { function terminateRequest($message = false) {
die($message); die($message);
} }
}
/**
* @return boolean
*/
private function shouldTerminateExecution(array $daemon = null) {
return !$daemon ||
$daemon['token'] !== $this->token ||
(isset($daemon['status']) && $daemon['status'] !== CronHelper::DAEMON_STATUS_ACTIVE);
}
}

View File

@ -16,7 +16,9 @@ class Supervisor {
$daemon = $this->daemon; $daemon = $this->daemon;
$execution_timeout_exceeded = $execution_timeout_exceeded =
(time() - (int)$daemon['updated_at']) >= CronHelper::DAEMON_EXECUTION_TIMEOUT; (time() - (int)$daemon['updated_at']) >= CronHelper::DAEMON_EXECUTION_TIMEOUT;
if($execution_timeout_exceeded) { $daemon_is_inactive =
isset($daemon['status']) && $daemon['status'] === CronHelper::DAEMON_STATUS_INACTIVE;
if($execution_timeout_exceeded || $daemon_is_inactive) {
CronHelper::restartDaemon($this->token); CronHelper::restartDaemon($this->token);
return $this->runDaemon(); return $this->runDaemon();
} }

View File

@ -18,7 +18,7 @@ class WordPress {
static function run() { static function run() {
return (self::checkExecutionRequirements()) ? return (self::checkExecutionRequirements()) ?
MailPoet::run() : MailPoet::run() :
self::cleanup(); self::stop();
} }
static function checkExecutionRequirements() { static function checkExecutionRequirements() {
@ -60,10 +60,10 @@ class WordPress {
); );
} }
static function cleanup() { static function stop() {
$cron_daemon = CronHelper::getDaemon(); $cron_daemon = CronHelper::getDaemon();
if($cron_daemon) { if($cron_daemon) {
CronHelper::deleteDaemon(); CronHelper::deactivateDaemon($cron_daemon);
} }
} }
} }

View File

@ -33,6 +33,7 @@ class CronHelperTest extends \MailPoetTest {
expect($daemon)->equals( expect($daemon)->equals(
array( array(
'token' => $token, 'token' => $token,
'status' => CronHelper::DAEMON_STATUS_ACTIVE,
'updated_at' => $time, 'updated_at' => $time,
'run_accessed_at' => null, 'run_accessed_at' => null,
'run_started_at' => null, 'run_started_at' => null,
@ -50,6 +51,7 @@ class CronHelperTest extends \MailPoetTest {
expect($daemon)->equals( expect($daemon)->equals(
array( array(
'token' => $token, 'token' => $token,
'status' => CronHelper::DAEMON_STATUS_ACTIVE,
'updated_at' => $time, 'updated_at' => $time,
'run_accessed_at' => null, 'run_accessed_at' => null,
'run_started_at' => null, 'run_started_at' => null,
@ -62,6 +64,7 @@ class CronHelperTest extends \MailPoetTest {
function testItLoadsDaemon() { function testItLoadsDaemon() {
$daemon = array( $daemon = array(
'token' => 'some_token', 'token' => 'some_token',
'status' => CronHelper::DAEMON_STATUS_ACTIVE,
'updated_at' => '12345678', 'updated_at' => '12345678',
'run_accessed_at' => null, 'run_accessed_at' => null,
'run_started_at' => null, 'run_started_at' => null,
@ -79,6 +82,7 @@ class CronHelperTest extends \MailPoetTest {
// when saving daemon, 'updated_at' value should change // when saving daemon, 'updated_at' value should change
$daemon = array( $daemon = array(
'token' => 'some_token', 'token' => 'some_token',
'status' => CronHelper::DAEMON_STATUS_ACTIVE,
'updated_at' => '12345678', 'updated_at' => '12345678',
'run_accessed_at' => null, 'run_accessed_at' => null,
'run_started_at' => null, 'run_started_at' => null,
@ -98,6 +102,7 @@ class CronHelperTest extends \MailPoetTest {
function testItUpdatesDaemonAccessedAt() { function testItUpdatesDaemonAccessedAt() {
$daemon = [ $daemon = [
'token' => 'some_token', 'token' => 'some_token',
'status' => CronHelper::DAEMON_STATUS_ACTIVE,
'updated_at' => 12345678, 'updated_at' => 12345678,
'run_accessed_at' => null, 'run_accessed_at' => null,
'run_started_at' => null, 'run_started_at' => null,
@ -131,6 +136,7 @@ class CronHelperTest extends \MailPoetTest {
foreach($run_start_values as $run_start) { foreach($run_start_values as $run_start) {
$daemon = [ $daemon = [
'token' => 'some_token', 'token' => 'some_token',
'status' => CronHelper::DAEMON_STATUS_ACTIVE,
'updated_at' => 12345678, 'updated_at' => 12345678,
'run_accessed_at' => $time - 10, 'run_accessed_at' => $time - 10,
'run_started_at' => $run_start, 'run_started_at' => $run_start,
@ -149,6 +155,7 @@ class CronHelperTest extends \MailPoetTest {
$time = time(); $time = time();
$daemon = [ $daemon = [
'token' => 'some_token', 'token' => 'some_token',
'status' => CronHelper::DAEMON_STATUS_ACTIVE,
'updated_at' => 12345678, 'updated_at' => 12345678,
'run_accessed_at' => $time - 5, 'run_accessed_at' => $time - 5,
'run_started_at' => $time - 4, 'run_started_at' => $time - 4,
@ -182,6 +189,7 @@ class CronHelperTest extends \MailPoetTest {
foreach($test_inputs as $test_input) { foreach($test_inputs as $test_input) {
$daemon = [ $daemon = [
'token' => 'some_token', 'token' => 'some_token',
'status' => CronHelper::DAEMON_STATUS_ACTIVE,
'updated_at' => 12345678, 'updated_at' => 12345678,
'run_accessed_at' => $test_input['run_access'], 'run_accessed_at' => $test_input['run_access'],
'run_started_at' => $test_input['run_start'], 'run_started_at' => $test_input['run_start'],
@ -196,6 +204,26 @@ class CronHelperTest extends \MailPoetTest {
} }
} }
function testItDeactivatesDaemon() {
$daemon = [
'token' => 'some_token',
'status' => CronHelper::DAEMON_STATUS_ACTIVE,
'updated_at' => '12345678',
'run_accessed_at' => null,
'run_started_at' => null,
'run_completed_at' => null,
'last_error' => null,
];
Setting::setValue(
CronHelper::DAEMON_SETTING,
$daemon
);
CronHelper::deactivateDaemon($daemon);
$daemon = CronHelper::getDaemon();
expect($daemon['status'])->equals(CronHelper::DAEMON_STATUS_INACTIVE);
}
function testItCreatesRandomToken() { function testItCreatesRandomToken() {
// random token is a string of 5 characters // random token is a string of 5 characters
$token1 = CronHelper::createToken(); $token1 = CronHelper::createToken();

View File

@ -157,6 +157,22 @@ class DaemonTest extends \MailPoetTest {
$daemon->run(); $daemon->run();
} }
function testItTerminatesExecutionWhenDaemonIsDeactivated() {
$daemon = Stub::make(new Daemon(true), [
'executeScheduleWorker' => null,
'executeQueueWorker' => null,
'pauseExecution' => null,
'terminateRequest' => Expected::exactly(1)
], $this);
$data = [
'token' => 123,
'status' => CronHelper::DAEMON_STATUS_INACTIVE,
];
Setting::setValue(CronHelper::DAEMON_SETTING, $data);
$daemon->__construct($data);
$daemon->run();
}
function testItUpdatesDaemonTokenDuringExecution() { function testItUpdatesDaemonTokenDuringExecution() {
$daemon = Stub::make(new Daemon(true), array( $daemon = Stub::make(new Daemon(true), array(
'executeScheduleWorker' => null, 'executeScheduleWorker' => null,

View File

@ -50,6 +50,16 @@ class SupervisorTest extends \MailPoetTest {
$daemon = $supervisor->checkDaemon(); $daemon = $supervisor->checkDaemon();
expect(is_int($daemon['updated_at']))->true(); expect(is_int($daemon['updated_at']))->true();
expect($daemon['updated_at'])->notEquals($supervisor->daemon['updated_at']); expect($daemon['updated_at'])->notEquals($supervisor->daemon['updated_at']);
expect($daemon['status'])->equals(CronHelper::DAEMON_STATUS_ACTIVE);
}
function testRestartsDaemonWhenItIsInactive() {
if(getenv('WP_TEST_ENABLE_NETWORK_TESTS') !== 'true') return;
$supervisor = new Supervisor();
$supervisor->daemon['updated_at'] = time();
$supervisor->daemon['status'] = CronHelper::DAEMON_STATUS_INACTIVE;
$daemon = $supervisor->checkDaemon();
expect($daemon['status'])->equals(CronHelper::DAEMON_STATUS_ACTIVE);
} }
function _after() { function _after() {

View File

@ -77,11 +77,11 @@ class WordPressTest extends \MailPoetTest {
expect(WordPress::checkExecutionRequirements())->false(); expect(WordPress::checkExecutionRequirements())->false();
} }
function testItCanDeleteRunningDaemon() { function testItCanDeactivateRunningDaemon() {
Setting::setValue(CronHelper::DAEMON_SETTING, true); Setting::setValue(CronHelper::DAEMON_SETTING, ['status' => CronHelper::DAEMON_STATUS_ACTIVE]);
expect(Setting::getValue(CronHelper::DAEMON_SETTING))->notNull(); expect(Setting::getValue(CronHelper::DAEMON_SETTING)['status'])->equals(CronHelper::DAEMON_STATUS_ACTIVE);
WordPress::cleanup(); WordPress::stop();
expect(Setting::getValue(CronHelper::DAEMON_SETTING))->null(); expect(Setting::getValue(CronHelper::DAEMON_SETTING)['status'])->equals(CronHelper::DAEMON_STATUS_INACTIVE);
} }
function testItRunsWhenExecutionRequirementsAreMet() { function testItRunsWhenExecutionRequirementsAreMet() {
@ -93,11 +93,11 @@ class WordPressTest extends \MailPoetTest {
expect(Setting::getValue(CronHelper::DAEMON_SETTING))->notNull(); expect(Setting::getValue(CronHelper::DAEMON_SETTING))->notNull();
} }
function testItDeletesCronDaemonWhenExecutionRequirementsAreNotMet() { function testItDeactivatesCronDaemonWhenExecutionRequirementsAreNotMet() {
Setting::setValue(CronHelper::DAEMON_SETTING, true); Setting::setValue(CronHelper::DAEMON_SETTING, ['status' => CronHelper::DAEMON_STATUS_ACTIVE]);
expect(Setting::getValue(CronHelper::DAEMON_SETTING))->notNull(); expect(Setting::getValue(CronHelper::DAEMON_SETTING)['status'])->equals(CronHelper::DAEMON_STATUS_ACTIVE);
WordPress::run(); WordPress::run();
expect(Setting::getValue(CronHelper::DAEMON_SETTING))->null(); expect(Setting::getValue(CronHelper::DAEMON_SETTING)['status'])->equals(CronHelper::DAEMON_STATUS_INACTIVE);
} }
function _addMTAConfigAndLog($sent, $status = null) { function _addMTAConfigAndLog($sent, $status = null) {

View File

@ -33,6 +33,18 @@
'knowledgeBaseButton': __('Visit our Knowledge Base for more articles'), 'knowledgeBaseButton': __('Visit our Knowledge Base for more articles'),
'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.'), '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.'), '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'),
'status': __('Status'),
'yes': __('yes'),
'no': __('no'),
'cronRunning': __('running'),
'cronWaiting': __('waiting for the next run'),
}) %> }) %>
<% endblock %> <% endblock %>