Sending Progress

- improved progress bar styles (with completed status)
- add pause/resume buttons
- fixed method case in settings.mta for MailPoet & SMTP Providers
- fixed parsley dependency
- added validation on from name & address on step 3
This commit is contained in:
Jonathan Labreuille
2015-12-03 18:26:36 +01:00
committed by MrCasual
parent 9b011c0281
commit 4a2bbe3f88
15 changed files with 245 additions and 65 deletions

View File

@ -5,17 +5,25 @@
width: 100% width: 100%
margin: 0 margin: 0
border-radius: 5px border-radius: 5px
position: relative
.mailpoet_progress span .mailpoet_progress_label
position: absolute
width: 100%
text-align: center
display: inline-block
margin: 2px 0 0 0
.mailpoet_progress_bar
position: absolute
display: inline-block display: inline-block
height: 100% height: 100%
border-radius: 3px border-radius: 3px
box-shadow: 0 1px 0 rgba(255, 255, 255, .5) inset box-shadow: 0 1px 0 rgba(255, 255, 255, .5) inset
.blue span
background-color: #34c2e3 background-color: #34c2e3
background-image: linear-gradient(top, #34c2e3, darken(#34c2e3, 20%)) background-image: linear-gradient(top, #34c2e3, darken(#34c2e3, 20%))
.orange span .mailpoet_progress_complete
background-color: #fecf23 .mailpoet_progress_bar
background-image: linear-gradient(top, #fecf23, #fd9215) background-color: #fecf23
background-image: linear-gradient(top, #fecf23, #fd9215)

View File

@ -20,7 +20,9 @@ function(
value={ this.props.item[this.props.field.name] } value={ this.props.item[this.props.field.name] }
placeholder={ this.props.field.placeholder } placeholder={ this.props.field.placeholder }
defaultValue={ this.props.field.defaultValue } defaultValue={ this.props.field.defaultValue }
onChange={ this.props.onValueChange } /> onChange={ this.props.onValueChange }
{...this.props.field.validation}
/>
); );
} }
}); });

View File

@ -397,6 +397,12 @@ define(
if(this.isMounted()) { if(this.isMounted()) {
const params = this.props.params || {} const params = this.props.params || {}
this.initWithParams(params) this.initWithParams(params)
if(this.props.auto_refresh) {
jQuery(document).on('heartbeat-tick.mailpoet', function(e, data) {
this.getItems();
}.bind(this));
}
} }
}, },
componentWillReceiveProps: function(nextProps) { componentWillReceiveProps: function(nextProps) {

View File

@ -123,23 +123,89 @@ define(
]; ];
var NewsletterList = React.createClass({ var NewsletterList = React.createClass({
pauseSending: function(item) {
MailPoet.Ajax.post({
endpoint: 'sendingQueue',
action: 'pause',
data: item.id
}).done(function() {
jQuery('#resume_'+item.id).show();
jQuery('#pause_'+item.id).hide();
});
},
resumeSending: function(item) {
MailPoet.Ajax.post({
endpoint: 'sendingQueue',
action: 'resume',
data: item.id
}).done(function() {
jQuery('#pause_'+item.id).show();
jQuery('#resume_'+item.id).hide();
});
},
renderStatus: function(item) { renderStatus: function(item) {
if(item.queue === null) { if(item.queue === null) {
return ( return (
<span>Not sent yet.</span> <span>Not sent yet.</span>
); );
} else { } else {
var progressClasses = classNames(
'mailpoet_progress',
{ 'mailpoet_progress_complete': item.queue.status === 'completed'}
);
// calculate percentage done // calculate percentage done
var percentage = Math.round( var percentage = Math.round(
(item.queue.count_processed * 100) / (item.queue.count_total) (item.queue.count_processed * 100) / (item.queue.count_total)
); );
var label = false;
if(item.queue.status === 'completed') {
label = (
<span>
Sent to {
item.queue.count_processed - item.queue.count_failed
} out of { item.queue.count_total }.
</span>
);
} else {
label = (
<span>
{ item.queue.count_processed } / { item.queue.count_total }
&nbsp;&nbsp;
<a
id={ 'resume_'+item.id }
className="button"
style={{ display: (item.queue.status === 'paused') ? 'inline-block': 'none' }}
href="javascript:;"
onClick={ this.resumeSending.bind(null, item) }
>Resume</a>
<a
id={ 'pause_'+item.id }
className="button mailpoet_pause"
style={{ display: (item.queue.status === null) ? 'inline-block': 'none' }}
href="javascript:;"
onClick={ this.pauseSending.bind(null, item) }
>Pause</a>
</span>
);
}
return ( return (
<div> <div>
<div className="mailpoet_progress blue"> <div className={ progressClasses }>
<span style={ { width: percentage + "%"} }></span> <span
className="mailpoet_progress_bar"
style={ { width: percentage + "%"} }
></span>
<span className="mailpoet_progress_label">
{ percentage + "%" }
</span>
</div> </div>
{ item.queue.count_processed } / { item.queue.count_total } <p style={{ textAlign:'center' }}>
{ label }
</p>
</div> </div>
); );
} }
@ -194,7 +260,8 @@ define(
columns={columns} columns={columns}
bulk_actions={ bulk_actions } bulk_actions={ bulk_actions }
item_actions={ item_actions } item_actions={ item_actions }
messages={ messages } /> messages={ messages }
auto_refresh={ true } />
</div> </div>
); );
} }

View File

@ -48,14 +48,21 @@ define(
name: 'from_name', name: 'from_name',
type: 'text', type: 'text',
placeholder: 'John Doe', placeholder: 'John Doe',
defaultValue: settings.from_name defaultValue: (settings.sender !== undefined) ? settings.sender.name : '',
validation: {
'data-parsley-required': true
}
}, },
{ {
name: 'from_email', name: 'from_email',
type: 'text', type: 'text',
placeholder: 'john.doe@email.com', placeholder: 'john.doe@email.com',
defaultValue: settings.from_address defaultValue: (settings.sender !== undefined) ? settings.sender.address : '',
}, validation: {
'data-parsley-required': true,
'data-parsley-type': 'email'
}
}
] ]
}, },
{ {
@ -93,33 +100,40 @@ define(
Router.History Router.History
], ],
handleSend: function() { handleSend: function() {
MailPoet.Ajax.post({ if(jQuery('#mailpoet_newsletter').parsley().validate() === true) {
endpoint: 'sendingQueue', MailPoet.Ajax.post({
action: 'addQueue', endpoint: 'sendingQueue',
data: { action: 'add',
newsletter_id: this.props.params.id, data: {
segments: jQuery('#mailpoet_segments').val() newsletter_id: this.props.params.id,
} segments: jQuery('#mailpoet_segments').val()
}).done(function(response) { }
if(response === true) { }).done(function(response) {
//this.history.pushState(null, '/'); if(response.result === true) {
this.history.pushState(null, '/');
MailPoet.Notice.success( MailPoet.Notice.success(
'The newsletter has been sent!' 'The newsletter is being sent...'
);
} else {
if(response.errors) {
MailPoet.Notice.error(
response.errors.join("<br />")
); );
} else { } else {
MailPoet.Notice.error( if(response.errors) {
'An error occurred while trying to send. '+ MailPoet.Notice.error(
'<a href="?page=mailpoet-settings">Check your settings.</a>' response.errors.join("<br />")
); );
} else {
MailPoet.Notice.error(
'An error occurred while trying to send. '+
'<a href="?page=mailpoet-settings">Check your settings.</a>'
);
}
} }
} }.bind(this));
}.bind(this)); }
},
componentDidMount: function() {
if(this.isMounted()) {
jQuery('#mailpoet_newsletter').parsley();
}
}, },
render: function() { render: function() {
return ( return (

View File

@ -36,7 +36,7 @@ define(
} else { } else {
// hide DKIM option when using MailPoet's API // hide DKIM option when using MailPoet's API
jQuery('#mailpoet_mta_dkim')[ jQuery('#mailpoet_mta_dkim')[
(method === 'mailpoet') (method === 'MailPoet')
? 'hide' ? 'hide'
: 'show' : 'show'
](); ]();

View File

@ -320,20 +320,27 @@ class Menu {
} }
function newsletters() { function newsletters() {
add_filter('heartbeat_received', array($this, 'getQueueStatus'), 10, 3);
global $wp_roles; global $wp_roles;
$data = array(); $data = array();
$data['segments'] = Segment::findArray(); $data['segments'] = Segment::findArray();
$settings = Setting::findArray(); $data['settings'] = Setting::getAll();
$data['settings'] = array();
foreach ($settings as $setting) {
$data['settings'][$setting['name']] = $setting['value'];
}
$data['roles'] = $wp_roles->get_names(); $data['roles'] = $wp_roles->get_names();
echo $this->renderer->render('newsletters.html', $data); echo $this->renderer->render('newsletters.html', $data);
} }
function getQueueStatus($response, $data, $screen_id) {
if(isset($data['mailpoet'])) {
$response['mailpoet'] = array(
'hello' => 'world'
);
}
return $response;
}
function newletterEditor() { function newletterEditor() {
$data = array(); $data = array();
wp_enqueue_media(); wp_enqueue_media();
@ -364,7 +371,7 @@ class Menu {
$data = array( $data = array(
'form' => $form, 'form' => $form,
'pages' => Pages::getAll(), 'pages' => Pages::getAll(),
'segments' => Segment::getPublished() 'segments' => Segment::getPublic()
->findArray(), ->findArray(),
'styles' => FormRenderer::getStyles($form), 'styles' => FormRenderer::getStyles($form),
'date_types' => Block\Date::getDateTypes(), 'date_types' => Block\Date::getDateTypes(),

View File

@ -43,6 +43,12 @@ class Newsletter extends Model {
)->select_expr(MP_NEWSLETTER_OPTION_TABLE.'.value'); )->select_expr(MP_NEWSLETTER_OPTION_TABLE.'.value');
} }
function getQueue() {
return SendingQueue::where('newsletter_id', $this->id)
->orderByDesc('updated_at')
->findOne();
}
static function search($orm, $search = '') { static function search($orm, $search = '') {
return $orm->where_like('subject', '%' . $search . '%'); return $orm->where_like('subject', '%' . $search . '%');
} }

View File

@ -9,4 +9,27 @@ class SendingQueue extends Model {
function __construct() { function __construct() {
parent::__construct(); parent::__construct();
} }
function pause() {
if($this->count_processed === $this->count_total) {
return false;
} else {
$this->set('status', 'paused');
return $this->save();
}
}
function resume() {
if($this->count_processed === $this->count_total) {
return $this->complete();
} else {
$this->set_expr('status', 'NULL');
return $this->save();
}
}
function complete() {
$this->set('status', 'completed');
return $this->save();
}
} }

View File

@ -92,6 +92,8 @@ class Queue {
); );
} }
function addQueues($data) { function addQueues($data) {
$result = array_map(function ($queueData) { $result = array_map(function ($queueData) {
$queue = SendingQueue::create(); $queue = SendingQueue::create();

View File

@ -1,13 +1,14 @@
<?php <?php
namespace MailPoet\Router; namespace MailPoet\Router;
use MailPoet\Models\Newsletter;
use MailPoet\Models\Segment; use MailPoet\Models\Segment;
use MailPoet\Util\Helpers; use MailPoet\Util\Helpers;
if(!defined('ABSPATH')) exit; if(!defined('ABSPATH')) exit;
class SendingQueue { class SendingQueue {
function addQueue($data) { function add($data) {
$queue = \MailPoet\Models\SendingQueue::where('newsletter_id', $data['newsletter_id']) $queue = \MailPoet\Models\SendingQueue::where('newsletter_id', $data['newsletter_id'])
->whereNull('status') ->whereNull('status')
->findArray(); ->findArray();
@ -18,6 +19,7 @@ class SendingQueue {
'errors' => array(__('Send operation is already in progress.')) 'errors' => array(__('Send operation is already in progress.'))
) )
); );
exit;
} }
$queue = \MailPoet\Models\SendingQueue::create(); $queue = \MailPoet\Models\SendingQueue::create();
$queue->newsletter_id = $data['newsletter_id']; $queue->newsletter_id = $data['newsletter_id'];
@ -39,18 +41,60 @@ class SendingQueue {
) )
); );
$queue->count_total = $queue->count_to_process = count($subscriber_ids); $queue->count_total = $queue->count_to_process = count($subscriber_ids);
$queue->save(); $result = $queue->save();
wp_send_json( if($result === false) {
!$queue->save() ? $errors = array(__('Queue could not be created.'));
if(!empty($queue->getValidationErrors())) {
$errors = array_merge($errors, $queue->getValidationErrors());
}
wp_send_json(
array( array(
'result' => false, 'result' => false,
'errors' => array(__('Queue could not be created.')) 'errors' => $errors
) : )
);
} else {
wp_send_json(
array( array(
'result' => true, 'result' => true,
'data' => array($queue->id) 'data' => array($queue->id)
) )
); );
}
}
function pause($newsletter_id) {
$newsletter = Newsletter::findOne($newsletter_id);
$result = false;
if($newsletter !== false) {
$queue = $newsletter->getQueue();
if($queue !== false && $queue->id() > 0) {
$result = $queue->pause();
}
}
wp_send_json(array(
'result' => $result
));
}
function resume($newsletter_id) {
$newsletter = Newsletter::findOne($newsletter_id);
$result = false;
if($newsletter !== false) {
$queue = $newsletter->getQueue();
if($queue !== false && $queue->id() > 0) {
$result = $queue->resume();
}
}
wp_send_json(array(
'result' => $result
));
} }
function addQueues($data) { function addQueues($data) {

View File

@ -3,25 +3,25 @@ namespace MailPoet\Settings;
class Hosts { class Hosts {
private static $_smtp = array( private static $_smtp = array(
'amazon' => array( 'AmazonSES' => array(
'name' => 'Amazon SES', 'name' => 'Amazon SES',
'api' => false, 'api' => false,
'emails' => 100, 'emails' => 100,
'interval' => 5 'interval' => 5
), ),
'elasticemail' => array( 'ElasticEmail' => array(
'name' => 'ElasticEmail', 'name' => 'ElasticEmail',
'api' => true, 'api' => true,
'emails' => 100, 'emails' => 100,
'interval' => 5 'interval' => 5
), ),
'mailgun' => array( 'MailGun' => array(
'name' => 'MailGun', 'name' => 'MailGun',
'api' => false, 'api' => false,
'emails' => 100, 'emails' => 100,
'interval' => 5 'interval' => 5
), ),
'sendgrid' => array( 'SendGrid' => array(
'name' => 'SendGrid', 'name' => 'SendGrid',
'api' => true, 'api' => true,
'emails' => 100, 'emails' => 100,

View File

@ -24,7 +24,7 @@
"moment": "^2.10.3", "moment": "^2.10.3",
"napa": "^1.2.0", "napa": "^1.2.0",
"papaparse": "4.1.1", "papaparse": "4.1.1",
"parsley": "^0.1.0", "parsleyjs": "^2.1.2",
"react": "^0.14.1", "react": "^0.14.1",
"react-checkbox-group": "0.2.2", "react-checkbox-group": "0.2.2",
"react-dom": "^0.14.1", "react-dom": "^0.14.1",

View File

@ -17,6 +17,7 @@
name="mta[method]" name="mta[method]"
value="<%= settings.mta.method %>" value="<%= settings.mta.method %>"
/> />
<!-- mta: sending frequency --> <!-- mta: sending frequency -->
<input <input
type="hidden" type="hidden"
@ -51,8 +52,8 @@
<!-- smtp: available sending methods --> <!-- smtp: available sending methods -->
<ul class="mailpoet_sending_methods clearfix"> <ul class="mailpoet_sending_methods clearfix">
<li <li
data-method="mailpoet" data-method="MailPoet"
<% if(settings.mta.method == 'mailpoet') %>class="mailpoet_active"<% endif %> <% if(settings.mta.method == 'MailPoet') %>class="mailpoet_active"<% endif %>
> >
<h3> <h3>
<img <img
@ -76,7 +77,7 @@
<div class="mailpoet_actions"> <div class="mailpoet_actions">
<a <a
class="button-secondary" class="button-secondary"
href="#mta/mailpoet"><%= __('Configure') %></a> href="#mta/MailPoet"><%= __('Configure') %></a>
</div> </div>
</li> </li>
<li <li
@ -125,7 +126,7 @@
<!-- Sending Method: MailPoet --> <!-- Sending Method: MailPoet -->
<div <div
class="mailpoet_sending_method" class="mailpoet_sending_method"
data-method="mailpoet" data-method="MailPoet"
style="display:none;" style="display:none;"
> >
<h3><%= __('Open a free account with MailPoet, and get:') %></h3> <h3><%= __('Open a free account with MailPoet, and get:') %></h3>
@ -165,8 +166,8 @@
type="text" type="text"
id="mailpoet_api_key" id="mailpoet_api_key"
size="40" size="40"
name="api_key" name="mta[api_key]"
value="<%= settings.api_key %>" value="<%= settings.mta.api_key %>"
/> />
</td> </td>
</tr> </tr>
@ -666,7 +667,6 @@
) )
? $('.mailpoet_sending_method:visible').data('method') ? $('.mailpoet_sending_method:visible').data('method')
: $('#mta_method').val(); : $('#mta_method').val();
alert( alert(
'Sending a test email to: '+recipient+ 'Sending a test email to: '+recipient+
' using sending method: '+mta_method ' using sending method: '+mta_method

View File

@ -77,7 +77,7 @@ baseConfig = {
{ {
include: /html2canvas.js$/, include: /html2canvas.js$/,
loader: 'expose-loader?html2canvas', loader: 'expose-loader?html2canvas',
}, }
] ]
} }
}; };
@ -92,7 +92,8 @@ config.push(_.extend({}, baseConfig, {
'ajax', 'ajax',
'modal', 'modal',
'notice', 'notice',
'jquery.serialize_object' 'jquery.serialize_object',
'parsleyjs'
], ],
admin: [ admin: [
'subscribers/subscribers.jsx', 'subscribers/subscribers.jsx',