Status update of newsletters completed

- duplicate newsletter now includes options as well
- fixed NaN issue in statistics when newsletter is being sent
- use constant for scheduled (and put it as the sendingQueue Model level)
This commit is contained in:
Jonathan Labreuille
2016-06-16 20:01:53 +02:00
parent 744455f0df
commit aa3a46b941
7 changed files with 122 additions and 69 deletions

View File

@ -94,12 +94,12 @@ const bulk_actions = [
} }
]; ];
const item_actions = [ const newsletter_actions = [
{ {
name: 'view', name: 'view',
link: function(item) { link: function(newsletter) {
return ( return (
<a href={ item.preview_url } target="_blank"> <a href={ newsletter.preview_url } target="_blank">
{MailPoet.I18n.t('preview')} {MailPoet.I18n.t('preview')}
</a> </a>
); );
@ -107,9 +107,9 @@ const item_actions = [
}, },
{ {
name: 'edit', name: 'edit',
link: function(item) { link: function(newsletter) {
return ( return (
<a href={ `?page=mailpoet-newsletter-editor&id=${ item.id }` }> <a href={ `?page=mailpoet-newsletter-editor&id=${ newsletter.id }` }>
{MailPoet.I18n.t('edit')} {MailPoet.I18n.t('edit')}
</a> </a>
); );
@ -118,11 +118,11 @@ const item_actions = [
{ {
name: 'duplicate', name: 'duplicate',
label: MailPoet.I18n.t('duplicate'), label: MailPoet.I18n.t('duplicate'),
onClick: function(item, refresh) { onClick: function(newsletter, refresh) {
return MailPoet.Ajax.post({ return MailPoet.Ajax.post({
endpoint: 'newsletters', endpoint: 'newsletters',
action: 'duplicate', action: 'duplicate',
data: item.id data: newsletter.id
}).done(function(response) { }).done(function(response) {
if (response !== false && response.subject !== undefined) { if (response !== false && response.subject !== undefined) {
MailPoet.Notice.success( MailPoet.Notice.success(
@ -141,77 +141,77 @@ const item_actions = [
]; ];
const NewsletterListStandard = React.createClass({ const NewsletterListStandard = React.createClass({
pauseSending: function(item) { pauseSending: function(newsletter) {
MailPoet.Ajax.post({ MailPoet.Ajax.post({
endpoint: 'sendingQueue', endpoint: 'sendingQueue',
action: 'pause', action: 'pause',
data: item.id data: newsletter.id
}).done(function() { }).done(function() {
jQuery('#resume_'+item.id).show(); jQuery('#resume_'+newsletter.id).show();
jQuery('#pause_'+item.id).hide(); jQuery('#pause_'+newsletter.id).hide();
}); });
}, },
resumeSending: function(item) { resumeSending: function(newsletter) {
MailPoet.Ajax.post({ MailPoet.Ajax.post({
endpoint: 'sendingQueue', endpoint: 'sendingQueue',
action: 'resume', action: 'resume',
data: item.id data: newsletter.id
}).done(function() { }).done(function() {
jQuery('#pause_'+item.id).show(); jQuery('#pause_'+newsletter.id).show();
jQuery('#resume_'+item.id).hide(); jQuery('#resume_'+newsletter.id).hide();
}); });
}, },
renderStatus: function(item) { renderStatus: function(newsletter) {
if(!item.queue) { if (!newsletter.queue) {
return ( return (
<span>{MailPoet.I18n.t('notSentYet')}</span> <span>{MailPoet.I18n.t('notSentYet')}</span>
); );
} else { } else {
if (item.queue.status === 'scheduled') { if (newsletter.queue.status === 'scheduled') {
return ( return (
<span>{MailPoet.I18n.t('scheduledFor')} { MailPoet.Date.format(item.queue.scheduled_at) } </span> <span>{MailPoet.I18n.t('scheduledFor')} { MailPoet.Date.format(newsletter.queue.scheduled_at) } </span>
) )
} }
const progressClasses = classNames( const progressClasses = classNames(
'mailpoet_progress', 'mailpoet_progress',
{ 'mailpoet_progress_complete': item.queue.status === 'completed'} { 'mailpoet_progress_complete': newsletter.queue.status === 'completed'}
); );
// calculate percentage done // calculate percentage done
const percentage = Math.round( const percentage = Math.round(
(item.queue.count_processed * 100) / (item.queue.count_total) (newsletter.queue.count_processed * 100) / (newsletter.queue.count_total)
); );
let label; let label;
if(item.queue.status === 'completed') { if (newsletter.queue.status === 'completed') {
label = ( label = (
<span> <span>
{ {
MailPoet.I18n.t('newsletterQueueCompleted') MailPoet.I18n.t('newsletterQueueCompleted')
.replace("%$1d", item.queue.count_processed - item.queue.count_failed) .replace("%$1d", newsletter.queue.count_processed - newsletter.queue.count_failed)
.replace("%$2d", item.queue.count_total) .replace("%$2d", newsletter.queue.count_total)
} }
</span> </span>
); );
} else { } else {
label = ( label = (
<span> <span>
{ item.queue.count_processed } / { item.queue.count_total } { newsletter.queue.count_processed } / { newsletter.queue.count_total }
&nbsp;&nbsp; &nbsp;&nbsp;
<a <a
id={ 'resume_'+item.id } id={ 'resume_'+newsletter.id }
className="button" className="button"
style={{ display: (item.queue.status === 'paused') ? 'inline-block': 'none' }} style={{ display: (newsletter.queue.status === 'paused') ? 'inline-block': 'none' }}
href="javascript:;" href="javascript:;"
onClick={ this.resumeSending.bind(null, item) } onClick={ this.resumeSending.bind(null, newsletter) }
>{MailPoet.I18n.t('resume')}</a> >{MailPoet.I18n.t('resume')}</a>
<a <a
id={ 'pause_'+item.id } id={ 'pause_'+newsletter.id }
className="button mailpoet_pause" className="button mailpoet_pause"
style={{ display: (item.queue.status === null) ? 'inline-block': 'none' }} style={{ display: (newsletter.queue.status === null) ? 'inline-block': 'none' }}
href="javascript:;" href="javascript:;"
onClick={ this.pauseSending.bind(null, item) } onClick={ this.pauseSending.bind(null, newsletter) }
>{MailPoet.I18n.t('pause')}</a> >{MailPoet.I18n.t('pause')}</a>
</span> </span>
); );
@ -242,15 +242,22 @@ const NewsletterListStandard = React.createClass({
if (newsletter.statistics && newsletter.queue && newsletter.queue.status !== 'scheduled') { if (newsletter.statistics && newsletter.queue && newsletter.queue.status !== 'scheduled') {
const total_sent = ~~(newsletter.queue.count_processed); const total_sent = ~~(newsletter.queue.count_processed);
const percentage_clicked = Math.round(
let percentage_clicked = 0;
let percentage_opened = 0;
let percentage_unsubscribed = 0;
if (total_sent > 0) {
percentage_clicked = Math.round(
(~~(newsletter.statistics.clicked) * 100) / total_sent (~~(newsletter.statistics.clicked) * 100) / total_sent
); );
const percentage_opened = Math.round( percentage_opened = Math.round(
(~~(newsletter.statistics.opened) * 100) / total_sent (~~(newsletter.statistics.opened) * 100) / total_sent
); );
const percentage_unsubscribed = Math.round( percentage_unsubscribed = Math.round(
(~~(newsletter.statistics.unsubscribed) * 100) / total_sent (~~(newsletter.statistics.unsubscribed) * 100) / total_sent
); );
}
return ( return (
<span> <span>
@ -318,7 +325,7 @@ const NewsletterListStandard = React.createClass({
onRenderItem={this.renderItem} onRenderItem={this.renderItem}
columns={columns} columns={columns}
bulk_actions={ bulk_actions } bulk_actions={ bulk_actions }
item_actions={ item_actions } item_actions={ newsletter_actions }
messages={ messages } messages={ messages }
auto_refresh={ true } auto_refresh={ true }
/> />

View File

@ -105,8 +105,9 @@ define(
} }
}).done((response) => { }).done((response) => {
this.setState({ loading: false }); this.setState({ loading: false });
if(response.result === true) { if(response.result === true) {
this.context.router.push('/'); this.context.router.push(`/${ this.state.item.type || '' }`);
MailPoet.Notice.success(response.data.message); MailPoet.Notice.success(response.data.message);
} else { } else {
if(response.errors) { if(response.errors) {
@ -133,7 +134,7 @@ define(
this.setState({ loading: false }); this.setState({ loading: false });
if(response.result === true) { if(response.result === true) {
this.context.router.push('/'); this.context.router.push(`/${ this.state.item.type || '' }`);
MailPoet.Notice.success( MailPoet.Notice.success(
MailPoet.I18n.t('newsletterUpdated') MailPoet.I18n.t('newsletterUpdated')
); );

View File

@ -322,6 +322,10 @@ class SendingQueue {
if(!$queue->count_to_process) { if(!$queue->count_to_process) {
$queue->processed_at = current_time('mysql'); $queue->processed_at = current_time('mysql');
$queue->status = SendingQueueModel::STATUS_COMPLETED; $queue->status = SendingQueueModel::STATUS_COMPLETED;
// set newsletter status to sent
$newsletter = Newsletter::findOne($queue->newsletter_id);
$newsletter->setStatus(Newsletter::STATUS_SENT);
} }
$queue->subscribers = serialize((array) $queue->subscribers); $queue->subscribers = serialize((array) $queue->subscribers);
$queue->save(); $queue->save();

View File

@ -86,12 +86,24 @@ class Newsletter extends Model {
foreach($segments as $segment) { foreach($segments as $segment) {
$relation = NewsletterSegment::create(); $relation = NewsletterSegment::create();
$relation->segment_id = $segment['id']; $relation->segment_id = $segment['id'];
$relation->newsletter_id = $duplicate->id(); $relation->newsletter_id = $duplicate->id;
$result = $relation->save(); $relation->save();
} }
} }
// TODO: duplicate options (if need be) // duplicate options
$options = NewsletterOption::where('newsletter_id', $this->id)
->findArray();
if(!empty($options)) {
foreach($options as $option) {
$relation = NewsletterOption::create();
$relation->newsletter_id = $duplicate->id;
$relation->option_field_id = $option['option_field_id'];
$relation->value = $option['value'];
$relation->save();
}
}
} }
return $duplicate; return $duplicate;

View File

@ -7,6 +7,8 @@ class SendingQueue extends Model {
public static $_table = MP_SENDING_QUEUES_TABLE; public static $_table = MP_SENDING_QUEUES_TABLE;
const STATUS_COMPLETED = 'completed'; const STATUS_COMPLETED = 'completed';
const STATUS_SCHEDULED = 'scheduled';
const STATUS_PAUSED = 'paused';
function __construct() { function __construct() {
parent::__construct(); parent::__construct();
@ -16,7 +18,7 @@ class SendingQueue extends Model {
if($this->count_processed === $this->count_total) { if($this->count_processed === $this->count_total) {
return false; return false;
} else { } else {
$this->set('status', 'paused'); $this->set('status', STATUS_PAUSED);
$this->save(); $this->save();
return ($this->getErrors() === false && $this->id() > 0); return ($this->getErrors() === false && $this->id() > 0);
} }
@ -26,14 +28,14 @@ class SendingQueue extends Model {
if($this->count_processed === $this->count_total) { if($this->count_processed === $this->count_total) {
return $this->complete(); return $this->complete();
} else { } else {
$this->set_expr('status', 'NULL'); $this->setExpr('status', 'NULL');
$this->save(); $this->save();
return ($this->getErrors() === false && $this->id() > 0); return ($this->getErrors() === false && $this->id() > 0);
} }
} }
function complete() { function complete() {
$this->set('status', 'completed'); $this->set('status', self::STATUS_COMPLETED);
$this->save(); $this->save();
return ($this->getErrors() === false && $this->id() > 0); return ($this->getErrors() === false && $this->id() > 0);
} }

View File

@ -182,7 +182,7 @@ class Scheduler {
if($existing_queue) return; if($existing_queue) return;
$queue = SendingQueue::create(); $queue = SendingQueue::create();
$queue->newsletter_id = $newsletter->id; $queue->newsletter_id = $newsletter->id;
$queue->status = 'scheduled'; $queue->status = self::STATUS_SCHEDULED;
$queue->scheduled_at = $next_run_date; $queue->scheduled_at = $next_run_date;
$queue->save(); $queue->save();
return $queue; return $queue;

View File

@ -9,12 +9,14 @@ use MailPoet\Models\Setting;
use MailPoet\Models\Subscriber; use MailPoet\Models\Subscriber;
use MailPoet\Models\SubscriberSegment; use MailPoet\Models\SubscriberSegment;
use MailPoet\Newsletter\Scheduler\Scheduler; use MailPoet\Newsletter\Scheduler\Scheduler;
use MailPoet\Models\SendingQueue as SendingQueueModel;
use MailPoet\Util\Helpers; use MailPoet\Util\Helpers;
if(!defined('ABSPATH')) exit; if(!defined('ABSPATH')) exit;
class SendingQueue { class SendingQueue {
function add($data) { function add($data) {
// check that the sending method has been configured properly
try { try {
new Mailer(false); new Mailer(false);
} catch(\Exception $e) { } catch(\Exception $e) {
@ -24,59 +26,81 @@ class SendingQueue {
); );
} }
// check that the newsletter exists
$newsletter = Newsletter::filter('filterWithOptions') $newsletter = Newsletter::filter('filterWithOptions')
->findOne($data['newsletter_id']); ->findOne($data['newsletter_id']);
if(!$newsletter) {
if($newsletter === false) {
return array( return array(
'result' => false, 'result' => false,
'errors' => array(__('Newsletter does not exist.')) 'errors' => array(__('This newsletter does not exist.'))
); );
} }
if($newsletter->type === Newsletter::TYPE_WELCOME) { if($newsletter->type === Newsletter::TYPE_WELCOME) {
// set welcome email active
$result = $newsletter->setStatus(Newsletter::STATUS_ACTIVE);
$errors = $result->getErrors();
if(!empty($errors)) {
return array(
'result' => false,
'errors' => $errors
);
} else {
return array( return array(
'result' => true, 'result' => true,
'data' => array( 'data' => array(
'message' => __('Your welcome notification is activated.') 'message' => __('Your welcome email has been activated.')
) )
); );
}
} else if($newsletter->type === Newsletter::TYPE_NOTIFICATION) { } else if($newsletter->type === Newsletter::TYPE_NOTIFICATION) {
// Post Notifications
$newsletter = Scheduler::processPostNotificationSchedule($newsletter->id); $newsletter = Scheduler::processPostNotificationSchedule($newsletter->id);
Scheduler::createPostNotificationQueue($newsletter); Scheduler::createPostNotificationQueue($newsletter);
// set post notification active
$newsletter->setStatus(Newsletter::STATUS_ACTIVE);
} }
$queue = \MailPoet\Models\SendingQueue::whereNull('status') $queue = SendingQueueModel::whereNull('status')
->where('newsletter_id', $newsletter->id) ->where('newsletter_id', $newsletter->id)
->findOne(); ->findOne();
if(!empty($queue)) { if(!empty($queue)) {
return array( return array(
'result' => false, 'result' => false,
'errors' => array(__('Send operation is already in progress.')) 'errors' => array(__('This newsletter is already being sent.'))
); );
} }
$queue = \MailPoet\Models\SendingQueue::where('status', 'scheduled') $queue = SendingQueueModel::where('newsletter_id', $newsletter->id)
->where('newsletter_id', $newsletter->id) ->where('status', SendingQueueModel::STATUS_SCHEDULED)
->findOne(); ->findOne();
if(!$queue) { if(!$queue) {
$queue = \MailPoet\Models\SendingQueue::create(); $queue = SendingQueueModel::create();
$queue->newsletter_id = $newsletter->id; $queue->newsletter_id = $newsletter->id;
} }
if($newsletter->type === 'notification') { if($newsletter->type === Newsletter::TYPE_NOTIFICATION) {
$queue->scheduled_at = Scheduler::getNextRunDate($newsletter->schedule); $queue->scheduled_at = Scheduler::getNextRunDate($newsletter->schedule);
$queue->status = 'scheduled'; $queue->status = SendingQueueModel::STATUS_SCHEDULED;
$queue->save(); $queue->save();
return array( return array(
'result' => true, 'result' => true,
'data' => array( 'data' => array(
'message' => __('Your post notifications are activated.') 'message' => __('Your post notification has been activated.')
) )
); );
} }
if((bool)$newsletter->isScheduled) { if((bool)$newsletter->isScheduled) {
$queue->status = 'scheduled'; // set newsletter status
$newsletter->setStatus(Newsletter::STATUS_SCHEDULED);
// set queue status
$queue->status = SendingQueueModel::STATUS_SCHEDULED;
$queue->scheduled_at = Scheduler::scheduleFromTimestamp( $queue->scheduled_at = Scheduler::scheduleFromTimestamp(
$newsletter->scheduledAt $newsletter->scheduledAt
); );
@ -104,6 +128,9 @@ class SendingQueue {
); );
$queue->count_total = $queue->count_to_process = count($subscribers); $queue->count_total = $queue->count_to_process = count($subscribers);
// set newsletter status
$newsletter->setStatus(Newsletter::STATUS_SENDING);
$message = __('The newsletter is being sent...'); $message = __('The newsletter is being sent...');
} }
$queue->save(); $queue->save();