Add detailed stats page support in Free [PREMIUM-1]
This commit is contained in:
@ -209,8 +209,8 @@ const ListingItems = React.createClass({
|
|||||||
className="colspanchange">
|
className="colspanchange">
|
||||||
{
|
{
|
||||||
(this.props.loading === true)
|
(this.props.loading === true)
|
||||||
? MailPoet.I18n.t('loadingItems')
|
? (this.props.messages.onLoadingItems || MailPoet.I18n.t('loadingItems'))
|
||||||
: MailPoet.I18n.t('noItemsFound')
|
: (this.props.messages.onNoItemsFound || MailPoet.I18n.t('noItemsFound'))
|
||||||
}
|
}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@ -793,6 +793,12 @@ const Listing = React.createClass({
|
|||||||
groups = false;
|
groups = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// messages
|
||||||
|
let messages = {};
|
||||||
|
if (this.props.messages !== undefined) {
|
||||||
|
messages = this.props.messages;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
{ groups }
|
{ groups }
|
||||||
@ -846,6 +852,7 @@ const Listing = React.createClass({
|
|||||||
count={ this.state.count }
|
count={ this.state.count }
|
||||||
limit={ this.state.limit }
|
limit={ this.state.limit }
|
||||||
item_actions={ item_actions }
|
item_actions={ item_actions }
|
||||||
|
messages={ messages }
|
||||||
items={ items } />
|
items={ items } />
|
||||||
|
|
||||||
<tfoot>
|
<tfoot>
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import ReactDOM from 'react-dom'
|
import ReactDOM from 'react-dom'
|
||||||
import ReactStringReplace from 'react-string-replace'
|
import ReactStringReplace from 'react-string-replace'
|
||||||
|
import { Link } from 'react-router'
|
||||||
import MailPoet from 'mailpoet'
|
import MailPoet from 'mailpoet'
|
||||||
import classNames from 'classnames'
|
import classNames from 'classnames'
|
||||||
import jQuery from 'jquery'
|
import jQuery from 'jquery'
|
||||||
|
import Hooks from 'wp-js-hooks'
|
||||||
|
|
||||||
const _QueueMixin = {
|
const _QueueMixin = {
|
||||||
pauseSending: function(newsletter) {
|
pauseSending: function(newsletter) {
|
||||||
@ -146,6 +148,9 @@ const _StatisticsMixin = {
|
|||||||
&& newsletter.queue
|
&& newsletter.queue
|
||||||
&& newsletter.queue.status !== 'scheduled'
|
&& newsletter.queue.status !== 'scheduled'
|
||||||
) {
|
) {
|
||||||
|
let params = {};
|
||||||
|
params = Hooks.applyFilters('mailpoet_newsletters_listing_stats_before', params, newsletter);
|
||||||
|
|
||||||
const total_sent = ~~(newsletter.queue.count_processed);
|
const total_sent = ~~(newsletter.queue.count_processed);
|
||||||
|
|
||||||
let percentage_clicked = 0;
|
let percentage_clicked = 0;
|
||||||
@ -153,22 +158,32 @@ const _StatisticsMixin = {
|
|||||||
let percentage_unsubscribed = 0;
|
let percentage_unsubscribed = 0;
|
||||||
|
|
||||||
if (total_sent > 0) {
|
if (total_sent > 0) {
|
||||||
percentage_clicked = Math.round(
|
percentage_clicked = (newsletter.statistics.clicked * 100) / total_sent;
|
||||||
(~~(newsletter.statistics.clicked) * 100) / total_sent
|
percentage_opened = (newsletter.statistics.opened * 100) / total_sent;
|
||||||
);
|
percentage_unsubscribed = (newsletter.statistics.unsubscribed * 100) / total_sent;
|
||||||
percentage_opened = Math.round(
|
|
||||||
(~~(newsletter.statistics.opened) * 100) / total_sent
|
|
||||||
);
|
|
||||||
percentage_unsubscribed = Math.round(
|
|
||||||
(~~(newsletter.statistics.unsubscribed) * 100) / total_sent
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
// format to 1 decimal place
|
||||||
|
percentage_clicked = percentage_clicked.toFixed(1);
|
||||||
|
percentage_opened = percentage_opened.toFixed(1);
|
||||||
|
percentage_unsubscribed = percentage_unsubscribed.toFixed(1);
|
||||||
|
|
||||||
|
const content = (
|
||||||
<span>
|
<span>
|
||||||
{ percentage_opened }%, { percentage_clicked }%, { percentage_unsubscribed }%
|
{ percentage_opened }%, { percentage_clicked }%, { percentage_unsubscribed }%
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (total_sent > 0 && params.link) {
|
||||||
|
return (
|
||||||
|
<Link
|
||||||
|
key={ `stats-${newsletter.id}` }
|
||||||
|
to={ params.link }
|
||||||
|
>{ content }</Link>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return content;
|
||||||
} else {
|
} else {
|
||||||
return (
|
return (
|
||||||
<span>{MailPoet.I18n.t('notSentYet')}</span>
|
<span>{MailPoet.I18n.t('notSentYet')}</span>
|
||||||
|
@ -11,6 +11,7 @@ import classNames from 'classnames'
|
|||||||
import jQuery from 'jquery'
|
import jQuery from 'jquery'
|
||||||
import MailPoet from 'mailpoet'
|
import MailPoet from 'mailpoet'
|
||||||
import _ from 'underscore'
|
import _ from 'underscore'
|
||||||
|
import Hooks from 'wp-js-hooks'
|
||||||
|
|
||||||
const mailpoet_roles = window.mailpoet_roles || {};
|
const mailpoet_roles = window.mailpoet_roles || {};
|
||||||
const mailpoet_segments = window.mailpoet_segments || {};
|
const mailpoet_segments = window.mailpoet_segments || {};
|
||||||
@ -281,24 +282,37 @@ const NewsletterListWelcome = React.createClass({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let params = {};
|
||||||
|
params = Hooks.applyFilters('mailpoet_newsletters_listing_stats_before', params, newsletter);
|
||||||
|
|
||||||
if (newsletter.total_sent > 0 && newsletter.statistics) {
|
if (newsletter.total_sent > 0 && newsletter.statistics) {
|
||||||
const total_sent = ~~(newsletter.total_sent);
|
const total_sent = ~~(newsletter.total_sent);
|
||||||
|
|
||||||
const percentage_clicked = Math.round(
|
let percentage_clicked = (newsletter.statistics.clicked * 100) / total_sent;
|
||||||
(~~(newsletter.statistics.clicked) * 100) / total_sent
|
let percentage_opened = (newsletter.statistics.opened * 100) / total_sent;
|
||||||
);
|
let percentage_unsubscribed = (newsletter.statistics.unsubscribed * 100) / total_sent;
|
||||||
const percentage_opened = Math.round(
|
|
||||||
(~~(newsletter.statistics.opened) * 100) / total_sent
|
|
||||||
);
|
|
||||||
const percentage_unsubscribed = Math.round(
|
|
||||||
(~~(newsletter.statistics.unsubscribed) * 100) / total_sent
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
// format to 1 decimal place
|
||||||
|
percentage_clicked = percentage_clicked.toFixed(1);
|
||||||
|
percentage_opened = percentage_opened.toFixed(1);
|
||||||
|
percentage_unsubscribed = percentage_unsubscribed.toFixed(1);
|
||||||
|
|
||||||
|
const content = (
|
||||||
<span>
|
<span>
|
||||||
{ percentage_opened }%, { percentage_clicked }%, { percentage_unsubscribed }%
|
{ percentage_opened }%, { percentage_clicked }%, { percentage_unsubscribed }%
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (params.link) {
|
||||||
|
return (
|
||||||
|
<Link
|
||||||
|
key={ `stats-${newsletter.id}` }
|
||||||
|
to={ params.link }
|
||||||
|
>{ content }</Link>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return content;
|
||||||
} else {
|
} else {
|
||||||
return (
|
return (
|
||||||
<span>{MailPoet.I18n.t('notSentYet')}</span>
|
<span>{MailPoet.I18n.t('notSentYet')}</span>
|
||||||
|
@ -2,6 +2,7 @@ import React from 'react'
|
|||||||
import ReactDOM from 'react-dom'
|
import ReactDOM from 'react-dom'
|
||||||
import { Router, Route, IndexRedirect, Link, useRouterHistory } from 'react-router'
|
import { Router, Route, IndexRedirect, Link, useRouterHistory } from 'react-router'
|
||||||
import { createHashHistory } from 'history'
|
import { createHashHistory } from 'history'
|
||||||
|
import Hooks from 'wp-js-hooks'
|
||||||
|
|
||||||
import NewsletterTypes from 'newsletters/types.jsx'
|
import NewsletterTypes from 'newsletters/types.jsx'
|
||||||
import NewsletterTemplates from 'newsletters/templates.jsx'
|
import NewsletterTemplates from 'newsletters/templates.jsx'
|
||||||
@ -27,6 +28,9 @@ const App = React.createClass({
|
|||||||
const container = document.getElementById('newsletters_container');
|
const container = document.getElementById('newsletters_container');
|
||||||
|
|
||||||
if(container) {
|
if(container) {
|
||||||
|
let extra_routes = [];
|
||||||
|
extra_routes = Hooks.applyFilters('mailpoet_newsletters_before_router', extra_routes);
|
||||||
|
|
||||||
const mailpoet_listing = ReactDOM.render((
|
const mailpoet_listing = ReactDOM.render((
|
||||||
<Router history={ history }>
|
<Router history={ history }>
|
||||||
<Route path="/" component={ App }>
|
<Route path="/" component={ App }>
|
||||||
@ -46,6 +50,8 @@ if(container) {
|
|||||||
<Route name="template" path="template/:id" component={ NewsletterTemplates } />
|
<Route name="template" path="template/:id" component={ NewsletterTemplates } />
|
||||||
{/* Sending options */}
|
{/* Sending options */}
|
||||||
<Route path="send/:id" component={ NewsletterSend } />
|
<Route path="send/:id" component={ NewsletterSend } />
|
||||||
|
{/* Extra routes */}
|
||||||
|
{ extra_routes.map(rt => <Route key={rt.path} path={rt.path} component={rt.component} />) }
|
||||||
</Route>
|
</Route>
|
||||||
</Router>
|
</Router>
|
||||||
), container);
|
), container);
|
||||||
|
@ -300,7 +300,8 @@ class Migrator {
|
|||||||
'subscriber_id mediumint(9) NOT NULL,',
|
'subscriber_id mediumint(9) NOT NULL,',
|
||||||
'queue_id mediumint(9) NOT NULL,',
|
'queue_id mediumint(9) NOT NULL,',
|
||||||
'sent_at TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,',
|
'sent_at TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,',
|
||||||
'PRIMARY KEY (id)',
|
'PRIMARY KEY (id),',
|
||||||
|
'KEY newsletter_id (newsletter_id)',
|
||||||
);
|
);
|
||||||
return $this->sqlify(__FUNCTION__, $attributes);
|
return $this->sqlify(__FUNCTION__, $attributes);
|
||||||
}
|
}
|
||||||
@ -316,6 +317,7 @@ class Migrator {
|
|||||||
'created_at TIMESTAMP NULL,',
|
'created_at TIMESTAMP NULL,',
|
||||||
'updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,',
|
'updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,',
|
||||||
'PRIMARY KEY (id),',
|
'PRIMARY KEY (id),',
|
||||||
|
'KEY newsletter_id (newsletter_id),',
|
||||||
'KEY queue_id (queue_id)',
|
'KEY queue_id (queue_id)',
|
||||||
);
|
);
|
||||||
return $this->sqlify(__FUNCTION__, $attributes);
|
return $this->sqlify(__FUNCTION__, $attributes);
|
||||||
@ -329,6 +331,7 @@ class Migrator {
|
|||||||
'queue_id mediumint(9) NOT NULL,',
|
'queue_id mediumint(9) NOT NULL,',
|
||||||
'created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,',
|
'created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,',
|
||||||
'PRIMARY KEY (id),',
|
'PRIMARY KEY (id),',
|
||||||
|
'KEY newsletter_id (newsletter_id),',
|
||||||
'KEY queue_id (queue_id)',
|
'KEY queue_id (queue_id)',
|
||||||
);
|
);
|
||||||
return $this->sqlify(__FUNCTION__, $attributes);
|
return $this->sqlify(__FUNCTION__, $attributes);
|
||||||
@ -342,6 +345,7 @@ class Migrator {
|
|||||||
'queue_id mediumint(9) NOT NULL,',
|
'queue_id mediumint(9) NOT NULL,',
|
||||||
'created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,',
|
'created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,',
|
||||||
'PRIMARY KEY (id),',
|
'PRIMARY KEY (id),',
|
||||||
|
'KEY newsletter_id (newsletter_id),',
|
||||||
'KEY queue_id (queue_id)',
|
'KEY queue_id (queue_id)',
|
||||||
);
|
);
|
||||||
return $this->sqlify(__FUNCTION__, $attributes);
|
return $this->sqlify(__FUNCTION__, $attributes);
|
||||||
|
@ -264,7 +264,8 @@ class Newsletter extends Model {
|
|||||||
'status',
|
'status',
|
||||||
'count_processed',
|
'count_processed',
|
||||||
'count_total',
|
'count_total',
|
||||||
'scheduled_at'
|
'scheduled_at',
|
||||||
|
'created_at'
|
||||||
));
|
));
|
||||||
if($queue === false) {
|
if($queue === false) {
|
||||||
$this->queue = false;
|
$this->queue = false;
|
||||||
|
@ -3,7 +3,6 @@
|
|||||||
use Codeception\Util\Stub;
|
use Codeception\Util\Stub;
|
||||||
use MailPoet\Models\Newsletter;
|
use MailPoet\Models\Newsletter;
|
||||||
use MailPoet\Models\SendingQueue;
|
use MailPoet\Models\SendingQueue;
|
||||||
use MailPoet\Models\StatisticsClicks;
|
|
||||||
use MailPoet\Models\StatisticsOpens;
|
use MailPoet\Models\StatisticsOpens;
|
||||||
use MailPoet\Models\Subscriber;
|
use MailPoet\Models\Subscriber;
|
||||||
use MailPoet\Statistics\Track\Opens;
|
use MailPoet\Statistics\Track\Opens;
|
||||||
@ -90,6 +89,5 @@ class OpensTest extends MailPoetTest {
|
|||||||
ORM::raw_execute('TRUNCATE ' . Subscriber::$_table);
|
ORM::raw_execute('TRUNCATE ' . Subscriber::$_table);
|
||||||
ORM::raw_execute('TRUNCATE ' . SendingQueue::$_table);
|
ORM::raw_execute('TRUNCATE ' . SendingQueue::$_table);
|
||||||
ORM::raw_execute('TRUNCATE ' . StatisticsOpens::$_table);
|
ORM::raw_execute('TRUNCATE ' . StatisticsOpens::$_table);
|
||||||
ORM::raw_execute('TRUNCATE ' . StatisticsClicks::$_table);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -65,7 +65,11 @@ baseConfig = {
|
|||||||
include: require.resolve('react'),
|
include: require.resolve('react'),
|
||||||
loader: 'expose-loader?' + globalPrefix + '.React',
|
loader: 'expose-loader?' + globalPrefix + '.React',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
include: require.resolve('react-router'),
|
||||||
|
loader: 'expose-loader?' + globalPrefix + '.ReactRouter',
|
||||||
|
},
|
||||||
|
{
|
||||||
include: require.resolve('react-string-replace'),
|
include: require.resolve('react-string-replace'),
|
||||||
loader: 'expose-loader?' + globalPrefix + '.ReactStringReplace',
|
loader: 'expose-loader?' + globalPrefix + '.ReactStringReplace',
|
||||||
},
|
},
|
||||||
@ -73,6 +77,10 @@ baseConfig = {
|
|||||||
test: /wp-js-hooks/i,
|
test: /wp-js-hooks/i,
|
||||||
loader: 'expose-loader?' + globalPrefix + '.Hooks!exports-loader?wp.hooks',
|
loader: 'expose-loader?' + globalPrefix + '.Hooks!exports-loader?wp.hooks',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
test: /listing.jsx/i,
|
||||||
|
loader: 'expose-loader?' + globalPrefix + '.Listing!babel-loader',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
include: /Blob.js$/,
|
include: /Blob.js$/,
|
||||||
loader: 'exports-loader?window.Blob',
|
loader: 'exports-loader?window.Blob',
|
||||||
@ -124,7 +132,8 @@ config.push(_.extend({}, baseConfig, {
|
|||||||
'react',
|
'react',
|
||||||
'react-dom',
|
'react-dom',
|
||||||
'react-router',
|
'react-router',
|
||||||
'react-string-replace'
|
'react-string-replace',
|
||||||
|
'listing/listing.jsx'
|
||||||
],
|
],
|
||||||
admin: [
|
admin: [
|
||||||
'subscribers/subscribers.jsx',
|
'subscribers/subscribers.jsx',
|
||||||
|
Reference in New Issue
Block a user