Added callback afterGetItems on Listings

Updated MP Notice to allow reuse of notices (by id)
Updated newsletters endpoint's listing method to include mta_log & mta_method in response
Updated all newsletter listings in order to dynamically display mailer error
This commit is contained in:
Jonathan Labreuille
2016-11-25 11:24:04 +01:00
committed by Vlad
parent 8b96806b11
commit 80efc3c12d
10 changed files with 295 additions and 266 deletions

View File

@ -298,7 +298,8 @@ const Listing = React.createClass({
filters: {},
filter: {},
selected_ids: [],
selection: false
selection: false,
meta: {}
};
},
getParam: function(param) {
@ -463,15 +464,21 @@ const Listing = React.createClass({
items: response.data || [],
filters: response.meta.filters || {},
groups: response.meta.groups || [],
count: response.meta.count || 0
count: response.meta.count || 0,
meta: _.omit(response.meta, ['filters', 'groups', 'count'])
}, () => {
// if viewing an empty trash
if (this.state.group === 'trash' && response.meta.count === 0) {
// redirect to default group
this.handleGroup('all');
}
// trigger afterGetItems callback if specified
if (this.props.afterGetItems !== undefined) {
this.props.afterGetItems(this.state);
}
});
}).fail(function(response) {
}).fail((response) => {
if (response.errors.length > 0) {
MailPoet.Notice.error(
response.errors.map(function(error) { return error.message; }),
@ -711,7 +718,7 @@ const Listing = React.createClass({
}.bind(this));
},
handleRenderItem: function(item, actions) {
const render = this.props.onRenderItem(item, actions);
const render = this.props.onRenderItem(item, actions, this.state.meta);
return render.props.children;
},
handleRefreshItems: function() {

View File

@ -1,7 +1,9 @@
import React from 'react'
import ReactDOM from 'react-dom'
import MailPoet from 'mailpoet'
import classNames from 'classnames'
import jQuery from 'jquery'
import ListingNotices from 'newsletters/listings/notices.jsx'
const _QueueMixin = {
pauseSending: function(newsletter) {
@ -180,22 +182,23 @@ const _StatisticsMixin = {
}
const _MailerMixin = {
resumeSending: function() {
MailPoet.Ajax.post({
endpoint: 'mailer',
action: 'resumeSending'
}).done(function() {
jQuery('.mailpoet_sending_status.error').remove();
MailPoet.Notice.success(MailPoet.I18n.t('mailerSendingResumedNotice'));
// TODO: refresh listings to update the newsletter queue status
}).fail((response) => {
if (response.errors.length > 0) {
MailPoet.Notice.error(
response.errors.map(function(error) { return error.message; }),
{ scroll: true }
);
}
});
checkMailerStatus: function(state) {
if (state.meta.mta_log.error) {
MailPoet.Notice.error(
'',
{ static: true, id: 'mailpoet_mailer_error' }
);
ReactDOM.render((
<ListingNotices
mta_log={ state.meta.mta_log }
mta_method={ state.meta.mta_method }
/>
), jQuery('[data-id="mailpoet_mailer_error"]')[0]);
} else {
MailPoet.Notice.hide('mailpoet_mailer_error');
}
}
}

View File

@ -1,37 +1,47 @@
import React from "react";
import MailPoet from "mailpoet";
import { MailerMixin } from 'newsletters/listings/mixins.jsx'
const ListingNotices = React.createClass({
mixins: [MailerMixin],
render() {
// display sending error
if (this.props.mailer_log.error) {
let mailer_error_notice = null;
if (this.props.mailer_log.error.operation === 'send') {
mailer_error_notice =
MailPoet.I18n.t('mailerSendErrorNotice')
.replace('%$1s', this.props.mailer_config.method)
.replace('%$2s', this.props.mailer_log.error.error_message);
} else {
mailer_error_notice =
MailPoet.I18n.t('mailerConnectionErrorNotice')
.replace('%$1s', this.props.mailer_log.error.error_message);
resumeSending() {
MailPoet.Ajax.post({
endpoint: 'mailer',
action: 'resumeSending'
}).done(function() {
MailPoet.Notice.hide('mailpoet_mailer_error');
MailPoet.Notice.success(MailPoet.I18n.t('mailerSendingResumedNotice'));
}).fail((response) => {
if (response.errors.length > 0) {
MailPoet.Notice.error(
response.errors.map(function(error) { return error.message; }),
{ scroll: true }
);
}
return (
<div className="mailpoet_notice mailpoet_sending_status error">
<p>{ mailer_error_notice }</p>
<p>{ MailPoet.I18n.t('mailerResumeSendingNotice') }</p>
<p>
<a href="javascript:;"
className="button"
onClick={ this.resumeSending }>{ MailPoet.I18n.t('mailerResumeSendingButton') }</a>
</p>
</div>
)
});
},
render() {
let mailer_error_notice;
if (this.props.mta_log.error.operation === 'send') {
mailer_error_notice =
MailPoet.I18n.t('mailerSendErrorNotice')
.replace('%$1s', this.props.mta_method)
.replace('%$2s', this.props.mta_log.error.error_message);
} else {
mailer_error_notice =
MailPoet.I18n.t('mailerConnectionErrorNotice')
.replace('%$1s', this.props.mta_log.error.error_message);
}
return null;
return (
<div>
<p>{ mailer_error_notice }</p>
<p>{ MailPoet.I18n.t('mailerResumeSendingNotice') }</p>
<p>
<a href="javascript:;"
className="button"
onClick={ this.resumeSending }
>{ MailPoet.I18n.t('mailerResumeSendingButton') }</a>
</p>
</div>
);
}
});

View File

@ -4,7 +4,8 @@ import { createHashHistory } from 'history'
import Listing from 'listing/listing.jsx'
import ListingTabs from 'newsletters/listings/tabs.jsx'
import ListingNotices from 'newsletters/listings/notices.jsx'
import { MailerMixin } from 'newsletters/listings/mixins.jsx'
import classNames from 'classnames'
import jQuery from 'jquery'
@ -18,8 +19,6 @@ import {
} from 'newsletters/scheduling/common.jsx'
const mailpoet_settings = window.mailpoet_settings || {};
const mailpoet_mailer_log = mailpoet_settings.mta_log || {};
const mailpoet_mailer_config = mailpoet_settings.mta || {};
const messages = {
onTrash: (response) => {
@ -158,6 +157,7 @@ const newsletter_actions = [
];
const NewsletterListNotification = React.createClass({
mixins: [ MailerMixin ],
updateStatus: function(e) {
// make the event persist so that we can still override the selected value
// in the ajax callback
@ -316,8 +316,6 @@ const NewsletterListNotification = React.createClass({
{MailPoet.I18n.t('pageTitle')} <Link className="page-title-action" to="/new">{MailPoet.I18n.t('new')}</Link>
</h1>
<ListingNotices mailer_log={ mailpoet_mailer_log } mailer_config = { mailpoet_mailer_config } />
<ListingTabs tab="notification" />
<Listing
@ -335,6 +333,7 @@ const NewsletterListNotification = React.createClass({
auto_refresh={ true }
sort_by="updated_at"
sort_order="desc"
afterGetItems={ this.checkMailerStatus }
/>
</div>
);

View File

@ -6,14 +6,15 @@ import MailPoet from 'mailpoet'
import Listing from 'listing/listing.jsx'
import ListingTabs from 'newsletters/listings/tabs.jsx'
import ListingNotices from 'newsletters/listings/notices.jsx'
import { QueueMixin, StatisticsMixin } from 'newsletters/listings/mixins.jsx'
import {
QueueMixin,
StatisticsMixin,
MailerMixin
} from 'newsletters/listings/mixins.jsx'
const mailpoet_tracking_enabled = (!!(window['mailpoet_tracking_enabled']));
const mailpoet_settings = window.mailpoet_settings || {};
const mailpoet_mailer_log = mailpoet_settings.mta_log || {};
const mailpoet_mailer_config = mailpoet_settings.mta || {};
const columns = [
{
@ -53,7 +54,7 @@ const newsletter_actions = [
];
const NewsletterListNotificationHistory = React.createClass({
mixins: [QueueMixin, StatisticsMixin],
mixins: [ QueueMixin, StatisticsMixin, MailerMixin ],
renderItem: function(newsletter, actions) {
const rowClasses = classNames(
'manage-column',
@ -102,8 +103,6 @@ const NewsletterListNotificationHistory = React.createClass({
{MailPoet.I18n.t('pageTitle')} <Link className="page-title-action" to="/new">{MailPoet.I18n.t('new')}</Link>
</h1>
<ListingNotices mailer_log={ mailpoet_mailer_log } mailer_config = { mailpoet_mailer_config } />
<ListingTabs tab="notification" />
<Link
@ -124,6 +123,7 @@ const NewsletterListNotificationHistory = React.createClass({
auto_refresh={ true }
sort_by="updated_at"
sort_order="desc"
afterGetItems={ this.checkMailerStatus }
/>
</div>
);

View File

@ -6,14 +6,15 @@ import MailPoet from 'mailpoet'
import Listing from 'listing/listing.jsx'
import ListingTabs from 'newsletters/listings/tabs.jsx'
import ListingNotices from 'newsletters/listings/notices.jsx'
import { QueueMixin, StatisticsMixin } from 'newsletters/listings/mixins.jsx'
import {
QueueMixin,
StatisticsMixin,
MailerMixin
} from 'newsletters/listings/mixins.jsx'
const mailpoet_tracking_enabled = (!!(window['mailpoet_tracking_enabled']));
const mailpoet_settings = window.mailpoet_settings || {};
const mailpoet_mailer_log = mailpoet_settings.mta_log || {};
const mailpoet_mailer_config = mailpoet_settings.mta || {};
const messages = {
onTrash: (response) => {
@ -151,8 +152,8 @@ const newsletter_actions = [
];
const NewsletterListStandard = React.createClass({
mixins: [QueueMixin, StatisticsMixin],
renderItem: function(newsletter, actions) {
mixins: [ QueueMixin, StatisticsMixin, MailerMixin ],
renderItem: function(newsletter, actions, meta) {
const rowClasses = classNames(
'manage-column',
'column-primary',
@ -175,7 +176,7 @@ const NewsletterListStandard = React.createClass({
{ actions }
</td>
<td className="column" data-colname={ MailPoet.I18n.t('status') }>
{ this.renderQueueStatus(newsletter, mailpoet_mailer_log) }
{ this.renderQueueStatus(newsletter, meta.mta_log) }
</td>
<td className="column" data-colname={ MailPoet.I18n.t('lists') }>
{ segments }
@ -198,8 +199,6 @@ const NewsletterListStandard = React.createClass({
{MailPoet.I18n.t('pageTitle')} <Link className="page-title-action" to="/new">{MailPoet.I18n.t('new')}</Link>
</h1>
<ListingNotices mailer_log={ mailpoet_mailer_log } mailer_config = { mailpoet_mailer_config } />
<ListingTabs tab="standard" />
<Listing
@ -217,6 +216,7 @@ const NewsletterListStandard = React.createClass({
auto_refresh={ true }
sort_by="updated_at"
sort_order="desc"
afterGetItems={ this.checkMailerStatus }
/>
</div>
);

View File

@ -4,7 +4,8 @@ import { createHashHistory } from 'history'
import Listing from 'listing/listing.jsx'
import ListingTabs from 'newsletters/listings/tabs.jsx'
import ListingNotices from 'newsletters/listings/notices.jsx'
import { MailerMixin } from 'newsletters/listings/mixins.jsx'
import classNames from 'classnames'
import jQuery from 'jquery'
@ -15,8 +16,6 @@ const mailpoet_roles = window.mailpoet_roles || {};
const mailpoet_segments = window.mailpoet_segments || {};
const mailpoet_tracking_enabled = (!!(window['mailpoet_tracking_enabled']));
const mailpoet_settings = window.mailpoet_settings || {};
const mailpoet_mailer_log = mailpoet_settings.mta_log || {};
const mailpoet_mailer_config = mailpoet_settings.mta || {};
const messages = {
onTrash: (response) => {
@ -155,6 +154,7 @@ const newsletter_actions = [
];
const NewsletterListWelcome = React.createClass({
mixins: [ MailerMixin ],
updateStatus: function(e) {
// make the event persist so that we can still override the selected value
// in the ajax callback
@ -345,8 +345,6 @@ const NewsletterListWelcome = React.createClass({
{ MailPoet.I18n.t('pageTitle') } <Link className="page-title-action" to="/new">{ MailPoet.I18n.t('new') }</Link>
</h1>
<ListingNotices mailer_log={ mailpoet_mailer_log } mailer_config = { mailpoet_mailer_config } />
<ListingTabs tab="welcome" />
<Listing
@ -364,6 +362,7 @@ const NewsletterListWelcome = React.createClass({
auto_refresh={ true }
sort_by="updated_at"
sort_order="desc"
afterGetItems={ this.checkMailerStatus }
/>
</div>
);

View File

@ -27,7 +27,7 @@ const App = React.createClass({
const container = document.getElementById('newsletters_container');
if(container) {
ReactDOM.render((
const mailpoet_listing = ReactDOM.render((
<Router history={ history }>
<Route path="/" component={ App }>
<IndexRedirect to="standard" />
@ -49,4 +49,6 @@ if(container) {
</Route>
</Router>
), container);
window.mailpoet_listing = mailpoet_listing;
}

View File

@ -5,14 +5,14 @@ define('notice', ['mailpoet', 'jquery'], function(MailPoet, jQuery) {
MailPoet Notice:
description: Handles notices
version: 0.2
version: 1.0
author: Jonathan Labreuille
company: Wysija
dependencies: jQuery
Usage:
// success message (static: false)
// success message (static: false)
MailPoet.Notice.success('Yatta!');
// error message (static: false)
@ -21,199 +21,206 @@ define('notice', ['mailpoet', 'jquery'], function(MailPoet, jQuery) {
// system message (static: true)
MailPoet.Notice.system('You need to updated ASAP!');
Examples:
MailPoet.Notice.success('- success #1 -');
setTimeout(function() {
MailPoet.Notice.success('- success #2 -');
setTimeout(function() {
MailPoet.Notice.error('- error -');
setTimeout(function() {
MailPoet.Notice.system('- system -');
setTimeout(function() {
MailPoet.Notice.hide();
}, 2500);
}, 300);
}, 400);
}, 500);
==================================================================================================*/
MailPoet.Notice = {
version: 0.2,
// default options
defaults: {
type: 'success',
message: '',
static: false,
hideClose: false,
id: null,
positionAfter: false,
scroll: false,
timeout: 5000,
onOpen: null,
onClose: null
},
options: {},
init: function(options) {
// set options
this.options = jQuery.extend({}, this.defaults, options);
version: 1.0,
// default options
defaults: {
type: 'success',
message: '',
static: false,
hideClose: false,
id: null,
positionAfter: false,
scroll: false,
timeout: 5000,
onOpen: null,
onClose: null
},
options: {},
init: function(options) {
// set options
this.options = jQuery.extend({}, this.defaults, options);
// clone element
this.element = jQuery('#mailpoet_notice_'+this.options.type).clone();
return this;
},
createNotice: function() {
// clone element
this.element = jQuery('#mailpoet_notice_'+this.options.type).clone();
// add data-id to the element
if (this.options.id) this.element.attr('data-id', 'notice_' + this.options.id);
// remove id from clone
this.element.removeAttr('id');
// insert notice after its parent
var positionAfter;
if (typeof this.options.positionAfter === 'object') {
positionAfter = this.options.positionAfter;
} else if (typeof this.options.positionAfter === 'string') {
positionAfter = jQuery(this.options.positionAfter);
} else {
positionAfter = jQuery('#mailpoet_notice_'+this.options.type);
}
positionAfter.after(this.element);
// setup onClose callback
var onClose = null;
if(this.options.onClose !== null) {
onClose = this.options.onClose;
}
// listen to remove event
jQuery(this.element).on('close', function() {
jQuery(this).fadeOut(200, function() {
// on close callback
if(onClose !== null) {
onClose();
}
// remove notice
jQuery(this).remove();
});
}.bind(this.element));
// listen to message event
jQuery(this.element).on('message', function(e, message) {
MailPoet.Notice.setMessage(message);
}.bind(this.element));
return this;
},
isHTML: function(str) {
var a = document.createElement('div');
a.innerHTML = str;
for(var c = a.childNodes, i = c.length; i--;) {
if(c[i].nodeType == 1) return true;
}
return false;
},
setMessage: function(message) {
// if it's not an html message, let's sugar coat the message with a fancy <p>
if(this.isHTML(message) === false) {
message = '<p>'+message+'</p>';
}
// set message
return this.element.html(message);
},
show: function(options) {
// initialize
this.init(options);
// show notice
this.showNotice();
// return this;
},
showNotice: function() {
// set message
this.setMessage(this.options.message);
// position notice
this.element.insertAfter(jQuery('h2.title'));
// set class name
switch(this.options.type) {
case 'success':
this.element.addClass('updated');
break;
case 'system':
this.element.addClass('update-nag');
break;
case 'error':
this.element.addClass('error');
break;
}
// make the notice appear
this.element.fadeIn(200);
// if scroll option is enabled, scroll to the notice
if(this.options.scroll === true) {
this.element.get(0).scrollIntoView(false);
}
// if the notice is not static, it has to disappear after a timeout
if(this.options.static === false) {
this.element.delay(this.options.timeout).trigger('close');
} else if (this.options.hideClose === false) {
this.element.append('<a href="javascript:;" class="mailpoet_notice_close"><span class="dashicons dashicons-dismiss"></span></a>');
this.element.find('.mailpoet_notice_close').on('click', function() {
jQuery(this).trigger('close');
});
}
// call onOpen callback
if(this.options.onOpen !== null) {
this.options.onOpen(this.element);
}
},
hide: function(all) {
if(all !== undefined && all === true) {
jQuery('.mailpoet_notice:not([id])').trigger('close');
} else if (all !== undefined && jQuery.isArray(all)) {
for (var id in all) {
jQuery('[data-id="notice_' + all[id] + '"]')
.trigger('close');
}
} if (all !== undefined) {
jQuery('[data-id="notice_' + all + '"]')
.trigger('close');
} else {
jQuery('.mailpoet_notice.updated:not([id]), .mailpoet_notice.error:not([id])')
.trigger('close');
}
},
error: function(message, options) {
this.show(jQuery.extend({}, {
type: 'error',
message: '<p>'+this.formatMessage(message)+'</p>'
}, options));
},
success: function(message, options) {
this.show(jQuery.extend({}, {
type: 'success',
message: '<p>'+this.formatMessage(message)+'</p>'
}, options));
},
system: function(message, options) {
this.show(jQuery.extend({}, {
type: 'system',
static: true,
message: '<p>'+this.formatMessage(message)+'</p>'
}, options));
},
formatMessage: function(message) {
if(Array.isArray(message)) {
return message.join('<br />');
} else {
return message;
}
// add data-id to the element
if (this.options.id) {
this.element.attr(
'data-id',
this.options.id
);
}
// remove id from clone
this.element.removeAttr('id');
// insert notice after its parent
var positionAfter;
if (typeof this.options.positionAfter === 'object') {
positionAfter = this.options.positionAfter;
} else if (typeof this.options.positionAfter === 'string') {
positionAfter = jQuery(this.options.positionAfter);
} else {
positionAfter = jQuery('#mailpoet_notice_'+this.options.type);
}
positionAfter.after(this.element);
// setup onClose callback
var onClose = null;
if (this.options.onClose !== null) {
onClose = this.options.onClose;
}
// listen to remove event
jQuery(this.element).on('close', function() {
jQuery(this).fadeOut(200, function() {
// on close callback
if (onClose !== null) {
onClose();
}
// remove notice
jQuery(this).remove();
});
}.bind(this.element));
// listen to message event
jQuery(this.element).on('setMessage', function(e, message) {
MailPoet.Notice.setMessage(message);
}.bind(this.element));
return this;
},
updateNotice: function() {
// update notice's message
jQuery('[data-id="'+this.options.id+'"').first().trigger(
'setMessage', this.options.message
);
},
isHTML: function(str) {
var a = document.createElement('div');
a.innerHTML = str;
for (var c = a.childNodes, i = c.length; i--;) {
if (c[i].nodeType == 1) return true;
}
return false;
},
setMessage: function(message) {
message = this.formatMessage(message);
// if it's not an html message
// let's sugar coat the message with a fancy <p>
if (this.isHTML(message) === false) {
message = '<p>'+message+'</p>';
}
// set message
return this.element.html(message);
},
formatMessage: function(message) {
if (Array.isArray(message)) {
return message.join('<br />');
} else {
return message;
}
},
show: function(options) {
// initialize
this.init(options);
if (
this.options.id !== null
&&
jQuery('[data-id="'+this.options.id+'"').length > 0
) {
this.updateNotice();
} else {
this.createNotice();
}
this.showNotice();
},
showNotice: function() {
// set message
this.setMessage(this.options.message);
// position notice
this.element.insertAfter(jQuery('h2.title'));
// set class name
switch (this.options.type) {
case 'success':
this.element.addClass('updated');
break;
case 'system':
this.element.addClass('update-nag');
break;
case 'error':
this.element.addClass('error');
break;
}
// make the notice appear
this.element.fadeIn(200);
// if scroll option is enabled, scroll to the notice
if (this.options.scroll === true) {
this.element.get(0).scrollIntoView(false);
}
// if the notice is not static, it has to disappear after a timeout
if (this.options.static === false) {
this.element.delay(this.options.timeout).trigger('close');
} else if (this.options.hideClose === false) {
this.element.append('<a href="javascript:;" class="mailpoet_notice_close"><span class="dashicons dashicons-dismiss"></span></a>');
this.element.find('.mailpoet_notice_close').on('click', function() {
jQuery(this).trigger('close');
});
}
// call onOpen callback
if (this.options.onOpen !== null) {
this.options.onOpen(this.element);
}
},
hide: function(all) {
if (all !== undefined && all === true) {
// all notices
jQuery('.mailpoet_notice:not([id])').trigger('close');
} else if (all !== undefined && jQuery.isArray(all)) {
// array of ids
for (var id in all) {
jQuery('[data-id="' + all[id] + '"]').trigger('close');
}
} if (all !== undefined) {
// single id
jQuery('[data-id="' + all + '"]').trigger('close');
} else {
jQuery('.mailpoet_notice.updated:not([id]), .mailpoet_notice.error:not([id])')
.trigger('close');
}
},
error: function(message, options) {
this.show(jQuery.extend({}, {
type: 'error',
message: message
}, options));
},
success: function(message, options) {
this.show(jQuery.extend({}, {
type: 'success',
message: message
}, options));
},
system: function(message, options) {
this.show(jQuery.extend({}, {
type: 'system',
static: true,
message: message
}, options));
}
};
});

View File

@ -346,7 +346,9 @@ class Newsletters extends APIEndpoint {
return $this->successResponse($data, array(
'count' => $listing_data['count'],
'filters' => $listing_data['filters'],
'groups' => $listing_data['groups']
'groups' => $listing_data['groups'],
'mta_log' => Setting::getValue('mta_log'),
'mta_method' => Setting::getValue('mta.method')
));
}