Rewrite renderStatistics as a React component
This commit is contained in:
committed by
M. Shull
parent
d9857731ea
commit
b1d521f929
@ -1,247 +1,14 @@
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import ReactStringReplace from 'react-string-replace';
|
||||
import { Link } from 'react-router-dom';
|
||||
import MailPoet from 'mailpoet';
|
||||
import moment from 'moment';
|
||||
import jQuery from 'jquery';
|
||||
import Hooks from 'wp-js-hooks';
|
||||
import StatsBadge from 'newsletters/badges/stats.jsx';
|
||||
import HelpTooltip from 'help-tooltip.jsx';
|
||||
|
||||
function trackStatsCTAClicked() {
|
||||
export const trackStatsCTAClicked = () => {
|
||||
MailPoet.trackEvent(
|
||||
'User has clicked a CTA to view detailed stats',
|
||||
{ 'MailPoet Free version': window.mailpoet_version }
|
||||
);
|
||||
}
|
||||
|
||||
function wrapInLink(content, params, id, totalSent) {
|
||||
if (totalSent <= 0 || !params.link) {
|
||||
return content;
|
||||
}
|
||||
|
||||
if (params.externalLink) {
|
||||
return (
|
||||
<a
|
||||
key={`stats-${id}`}
|
||||
href={params.link}
|
||||
onClick={params.onClick || null}
|
||||
>
|
||||
{content}
|
||||
</a>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<Link
|
||||
key={`stats-${id}`}
|
||||
to={params.link}
|
||||
onClick={params.onClick || null}
|
||||
>
|
||||
{content}
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
|
||||
const addStatsCTALink = (params) => {
|
||||
if (window.mailpoet_premium_active) {
|
||||
return params;
|
||||
}
|
||||
const newParams = params;
|
||||
newParams.link = 'admin.php?page=mailpoet-premium';
|
||||
newParams.externalLink = true;
|
||||
newParams.onClick = trackStatsCTAClicked;
|
||||
return newParams;
|
||||
};
|
||||
|
||||
export const renderStatistics = (newsletter, isSent, currentTime) => {
|
||||
let sent = isSent;
|
||||
if (sent === undefined) {
|
||||
// condition for standard and post notification listings
|
||||
sent = newsletter.statistics
|
||||
&& newsletter.queue
|
||||
&& newsletter.queue.status !== 'scheduled';
|
||||
}
|
||||
if (!sent) {
|
||||
return (
|
||||
<span>{MailPoet.I18n.t('notSentYet')}</span>
|
||||
);
|
||||
}
|
||||
|
||||
let params = {};
|
||||
Hooks.addFilter('mailpoet_newsletters_listing_stats_before', 'mailpoet', addStatsCTALink);
|
||||
params = Hooks.applyFilters('mailpoet_newsletters_listing_stats_before', params, newsletter);
|
||||
|
||||
// welcome emails provide explicit total_sent value
|
||||
const totalSent = Number((newsletter.total_sent || newsletter.queue.count_processed));
|
||||
|
||||
let percentageClicked = 0;
|
||||
let percentageOpened = 0;
|
||||
let percentageUnsubscribed = 0;
|
||||
let revenue = null;
|
||||
|
||||
if (totalSent > 0) {
|
||||
percentageClicked = (newsletter.statistics.clicked * 100) / totalSent;
|
||||
percentageOpened = (newsletter.statistics.opened * 100) / totalSent;
|
||||
percentageUnsubscribed = (newsletter.statistics.unsubscribed * 100) / totalSent;
|
||||
revenue = newsletter.statistics.revenue;
|
||||
}
|
||||
|
||||
// format to 1 decimal place
|
||||
const percentageClickedDisplay = MailPoet.Num.toLocaleFixed(percentageClicked, 1);
|
||||
const percentageOpenedDisplay = MailPoet.Num.toLocaleFixed(percentageOpened, 1);
|
||||
const percentageUnsubscribedDisplay = MailPoet.Num.toLocaleFixed(percentageUnsubscribed, 1);
|
||||
|
||||
let showStatsTimeout;
|
||||
let newsletterDate;
|
||||
let sentHoursAgo;
|
||||
let tooEarlyForStats;
|
||||
let showKbLink;
|
||||
if (currentTime !== undefined) {
|
||||
// standard emails and post notifications:
|
||||
// display green box for newsletters that were just sent
|
||||
showStatsTimeout = 6; // in hours
|
||||
newsletterDate = newsletter.queue.scheduled_at || newsletter.queue.created_at;
|
||||
sentHoursAgo = moment(currentTime).diff(moment(newsletterDate), 'hours');
|
||||
tooEarlyForStats = sentHoursAgo < showStatsTimeout;
|
||||
showKbLink = true;
|
||||
} else {
|
||||
// welcome emails: no green box and KB link
|
||||
tooEarlyForStats = false;
|
||||
showKbLink = false;
|
||||
}
|
||||
|
||||
const improveStatsKBLink = 'http://beta.docs.mailpoet.com/article/191-how-to-improve-my-open-and-click-rates';
|
||||
|
||||
// thresholds to display badges
|
||||
const minNewslettersSent = 20;
|
||||
const minNewsletterOpens = 5;
|
||||
|
||||
let openedAndClickedStats;
|
||||
if (totalSent >= minNewslettersSent
|
||||
&& newsletter.statistics.opened >= minNewsletterOpens
|
||||
&& !tooEarlyForStats
|
||||
) {
|
||||
// display stats with badges
|
||||
openedAndClickedStats = (
|
||||
<div className="mailpoet_stats_text">
|
||||
<div>
|
||||
<span>
|
||||
{ percentageOpenedDisplay }
|
||||
%
|
||||
{' '}
|
||||
</span>
|
||||
<StatsBadge
|
||||
stat="opened"
|
||||
rate={percentageOpened}
|
||||
tooltipId={`opened-${newsletter.id}`}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<span>
|
||||
{ percentageClickedDisplay }
|
||||
%
|
||||
{' '}
|
||||
</span>
|
||||
<StatsBadge
|
||||
stat="clicked"
|
||||
rate={percentageClicked}
|
||||
tooltipId={`clicked-${newsletter.id}`}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<span className="mailpoet_stat_hidden">
|
||||
{ percentageUnsubscribedDisplay }
|
||||
%
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
// display simple stats
|
||||
openedAndClickedStats = (
|
||||
<div>
|
||||
<span className="mailpoet_stats_text">
|
||||
{ percentageOpenedDisplay }
|
||||
%,
|
||||
{ ' ' }
|
||||
{ percentageClickedDisplay }
|
||||
%
|
||||
<span className="mailpoet_stat_hidden">
|
||||
,
|
||||
{' '}
|
||||
{ percentageUnsubscribedDisplay }
|
||||
%
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const wrapContentInLink = (content, idPrefix) => wrapInLink(
|
||||
content,
|
||||
params,
|
||||
`${idPrefix}-${newsletter.id}`,
|
||||
totalSent
|
||||
);
|
||||
|
||||
const content = (
|
||||
<>
|
||||
{ wrapContentInLink(openedAndClickedStats, 'opened-and-clicked') }
|
||||
{ revenue !== null && revenue.value > 0 && (
|
||||
<div className="mailpoet_stats_text">
|
||||
{ wrapContentInLink(revenue.formatted, 'revenue') }
|
||||
{' '}
|
||||
<HelpTooltip
|
||||
tooltip={MailPoet.I18n.t('revenueStatsTooltip')}
|
||||
place="left"
|
||||
tooltipId="helpTooltipStatsRevenue"
|
||||
/>
|
||||
</div>
|
||||
) }
|
||||
{ tooEarlyForStats && wrapContentInLink(
|
||||
(
|
||||
<div className="mailpoet_badge mailpoet_badge_green">
|
||||
{MailPoet.I18n.t('checkBackInHours').replace('%$1d', showStatsTimeout - sentHoursAgo)}
|
||||
</div>
|
||||
),
|
||||
'check-back'
|
||||
) }
|
||||
</>
|
||||
);
|
||||
|
||||
// thresholds to display bad open rate help
|
||||
const maxPercentageOpened = 5;
|
||||
const minSentHoursAgo = 24;
|
||||
const minTotalSent = 10;
|
||||
|
||||
let afterContent;
|
||||
if (showKbLink
|
||||
&& percentageOpened < maxPercentageOpened
|
||||
&& sentHoursAgo >= minSentHoursAgo
|
||||
&& totalSent >= minTotalSent
|
||||
) {
|
||||
// help link for bad open rate
|
||||
afterContent = (
|
||||
<div>
|
||||
<a
|
||||
href={improveStatsKBLink}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="mailpoet_stat_link_small"
|
||||
>
|
||||
{MailPoet.I18n.t('improveThisLinkText')}
|
||||
</a>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
{content}
|
||||
{afterContent}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const addStatsCTAAction = (actions) => {
|
||||
|
@ -12,8 +12,8 @@ import ListingHeading from 'newsletters/listings/heading.jsx';
|
||||
import FeatureAnnouncement from 'announcements/feature_announcement.jsx';
|
||||
|
||||
import QueueStatus from 'newsletters/listings/queue_status.jsx';
|
||||
import Statistics from 'newsletters/listings/statistics.jsx';
|
||||
import {
|
||||
renderStatistics,
|
||||
addStatsCTAAction,
|
||||
checkCronStatus,
|
||||
checkMailerStatus,
|
||||
@ -102,7 +102,7 @@ const NewsletterListNotificationHistory = createReactClass({ // eslint-disable-l
|
||||
</td>
|
||||
{ (mailpoetTrackingEnabled === true) ? (
|
||||
<td className="column" data-colname={MailPoet.I18n.t('statistics')}>
|
||||
{ renderStatistics(newsletter, undefined, meta.current_time) }
|
||||
<Statistics newsletter={newsletter} currentTime={meta.current_time} />
|
||||
</td>
|
||||
) : null }
|
||||
<td className="column-date" data-colname={MailPoet.I18n.t('sentOn')}>
|
||||
|
@ -11,8 +11,8 @@ import ListingTabs from 'newsletters/listings/tabs.jsx';
|
||||
import ListingHeading from 'newsletters/listings/heading.jsx';
|
||||
import FeatureAnnouncement from 'announcements/feature_announcement.jsx';
|
||||
import QueueStatus from 'newsletters/listings/queue_status.jsx';
|
||||
import Statistics from 'newsletters/listings/statistics.jsx';
|
||||
import {
|
||||
renderStatistics,
|
||||
addStatsCTAAction,
|
||||
checkCronStatus,
|
||||
checkMailerStatus,
|
||||
@ -213,7 +213,7 @@ const NewsletterListStandard = createReactClass({ // eslint-disable-line react/p
|
||||
</td>
|
||||
{ (mailpoetTrackingEnabled === true) ? (
|
||||
<td className="column" data-colname={MailPoet.I18n.t('statistics')}>
|
||||
{ renderStatistics(newsletter, undefined, meta.current_time) }
|
||||
<Statistics newsletter={newsletter} currentTime={meta.current_time} />
|
||||
</td>
|
||||
) : null }
|
||||
<td className="column-date" data-colname={MailPoet.I18n.t('sentOn')}>
|
||||
|
264
assets/js/src/newsletters/listings/statistics.jsx
Normal file
264
assets/js/src/newsletters/listings/statistics.jsx
Normal file
@ -0,0 +1,264 @@
|
||||
import React from 'react';
|
||||
import moment from 'moment';
|
||||
import MailPoet from 'mailpoet';
|
||||
import Hooks from 'wp-js-hooks';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Link } from 'react-router-dom';
|
||||
import HelpTooltip from 'help-tooltip.jsx';
|
||||
import StatsBadge from 'newsletters/badges/stats.jsx';
|
||||
import { trackStatsCTAClicked } from 'newsletters/listings/mixins.jsx';
|
||||
|
||||
const wrapInLink = (content, params, id, totalSent) => {
|
||||
if (totalSent <= 0 || !params.link) {
|
||||
return content;
|
||||
}
|
||||
|
||||
if (params.externalLink) {
|
||||
return (
|
||||
<a
|
||||
key={`stats-${id}`}
|
||||
href={params.link}
|
||||
onClick={params.onClick || null}
|
||||
>
|
||||
{content}
|
||||
</a>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<Link
|
||||
key={`stats-${id}`}
|
||||
to={params.link}
|
||||
onClick={params.onClick || null}
|
||||
>
|
||||
{content}
|
||||
</Link>
|
||||
);
|
||||
};
|
||||
|
||||
const addStatsCTALink = (params) => {
|
||||
if (window.mailpoet_premium_active) {
|
||||
return params;
|
||||
}
|
||||
const newParams = params;
|
||||
newParams.link = 'admin.php?page=mailpoet-premium';
|
||||
newParams.externalLink = true;
|
||||
newParams.onClick = trackStatsCTAClicked;
|
||||
return newParams;
|
||||
};
|
||||
|
||||
const Statistics = ({ newsletter, isSent, currentTime }) => {
|
||||
let sent = isSent;
|
||||
if (sent === undefined) {
|
||||
// condition for standard and post notification listings
|
||||
sent = newsletter.statistics
|
||||
&& newsletter.queue
|
||||
&& newsletter.queue.status !== 'scheduled';
|
||||
}
|
||||
if (!sent) {
|
||||
return (
|
||||
<span>{MailPoet.I18n.t('notSentYet')}</span>
|
||||
);
|
||||
}
|
||||
|
||||
let params = {};
|
||||
Hooks.addFilter('mailpoet_newsletters_listing_stats_before', 'mailpoet', addStatsCTALink);
|
||||
params = Hooks.applyFilters('mailpoet_newsletters_listing_stats_before', params, newsletter);
|
||||
|
||||
// welcome emails provide explicit total_sent value
|
||||
const totalSent = Number((newsletter.total_sent || newsletter.queue.count_processed));
|
||||
|
||||
let percentageClicked = 0;
|
||||
let percentageOpened = 0;
|
||||
let percentageUnsubscribed = 0;
|
||||
let revenue = null;
|
||||
|
||||
if (totalSent > 0) {
|
||||
percentageClicked = (newsletter.statistics.clicked * 100) / totalSent;
|
||||
percentageOpened = (newsletter.statistics.opened * 100) / totalSent;
|
||||
percentageUnsubscribed = (newsletter.statistics.unsubscribed * 100) / totalSent;
|
||||
revenue = newsletter.statistics.revenue;
|
||||
}
|
||||
|
||||
// format to 1 decimal place
|
||||
const percentageClickedDisplay = MailPoet.Num.toLocaleFixed(percentageClicked, 1);
|
||||
const percentageOpenedDisplay = MailPoet.Num.toLocaleFixed(percentageOpened, 1);
|
||||
const percentageUnsubscribedDisplay = MailPoet.Num.toLocaleFixed(percentageUnsubscribed, 1);
|
||||
|
||||
let showStatsTimeout;
|
||||
let newsletterDate;
|
||||
let sentHoursAgo;
|
||||
let tooEarlyForStats;
|
||||
let showKbLink;
|
||||
if (currentTime !== undefined) {
|
||||
// standard emails and post notifications:
|
||||
// display green box for newsletters that were just sent
|
||||
showStatsTimeout = 6; // in hours
|
||||
newsletterDate = newsletter.queue.scheduled_at || newsletter.queue.created_at;
|
||||
sentHoursAgo = moment(currentTime).diff(moment(newsletterDate), 'hours');
|
||||
tooEarlyForStats = sentHoursAgo < showStatsTimeout;
|
||||
showKbLink = true;
|
||||
} else {
|
||||
// welcome emails: no green box and KB link
|
||||
tooEarlyForStats = false;
|
||||
showKbLink = false;
|
||||
}
|
||||
|
||||
const improveStatsKBLink = 'http://beta.docs.mailpoet.com/article/191-how-to-improve-my-open-and-click-rates';
|
||||
|
||||
// thresholds to display badges
|
||||
const minNewslettersSent = 20;
|
||||
const minNewsletterOpens = 5;
|
||||
|
||||
let openedAndClickedStats;
|
||||
if (totalSent >= minNewslettersSent
|
||||
&& newsletter.statistics.opened >= minNewsletterOpens
|
||||
&& !tooEarlyForStats
|
||||
) {
|
||||
// display stats with badges
|
||||
openedAndClickedStats = (
|
||||
<div className="mailpoet_stats_text">
|
||||
<div>
|
||||
<span>
|
||||
{percentageOpenedDisplay}
|
||||
%
|
||||
{' '}
|
||||
</span>
|
||||
<StatsBadge
|
||||
stat="opened"
|
||||
rate={percentageOpened}
|
||||
tooltipId={`opened-${newsletter.id}`}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<span>
|
||||
{percentageClickedDisplay}
|
||||
%
|
||||
{' '}
|
||||
</span>
|
||||
<StatsBadge
|
||||
stat="clicked"
|
||||
rate={percentageClicked}
|
||||
tooltipId={`clicked-${newsletter.id}`}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<span className="mailpoet_stat_hidden">
|
||||
{percentageUnsubscribedDisplay}
|
||||
%
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
// display simple stats
|
||||
openedAndClickedStats = (
|
||||
<div>
|
||||
<span className="mailpoet_stats_text">
|
||||
{percentageOpenedDisplay}
|
||||
%,
|
||||
{' '}
|
||||
{percentageClickedDisplay}
|
||||
%
|
||||
<span className="mailpoet_stat_hidden">
|
||||
,
|
||||
{' '}
|
||||
{percentageUnsubscribedDisplay}
|
||||
%
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const wrapContentInLink = (content, idPrefix) => wrapInLink(
|
||||
content,
|
||||
params,
|
||||
`${idPrefix}-${newsletter.id}`,
|
||||
totalSent
|
||||
);
|
||||
|
||||
const content = (
|
||||
<>
|
||||
{wrapContentInLink(openedAndClickedStats, 'opened-and-clicked')}
|
||||
{revenue !== null && revenue.value > 0 && (
|
||||
<div className="mailpoet_stats_text">
|
||||
{wrapContentInLink(revenue.formatted, 'revenue')}
|
||||
{' '}
|
||||
<HelpTooltip
|
||||
tooltip={MailPoet.I18n.t('revenueStatsTooltip')}
|
||||
place="left"
|
||||
tooltipId="helpTooltipStatsRevenue"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{tooEarlyForStats && wrapContentInLink(
|
||||
(
|
||||
<div className="mailpoet_badge mailpoet_badge_green">
|
||||
{MailPoet.I18n.t('checkBackInHours').replace('%$1d', showStatsTimeout - sentHoursAgo)}
|
||||
</div>
|
||||
),
|
||||
'check-back'
|
||||
)}
|
||||
</>
|
||||
);
|
||||
|
||||
// thresholds to display bad open rate help
|
||||
const maxPercentageOpened = 5;
|
||||
const minSentHoursAgo = 24;
|
||||
const minTotalSent = 10;
|
||||
|
||||
let afterContent;
|
||||
if (showKbLink
|
||||
&& percentageOpened < maxPercentageOpened
|
||||
&& sentHoursAgo >= minSentHoursAgo
|
||||
&& totalSent >= minTotalSent
|
||||
) {
|
||||
// help link for bad open rate
|
||||
afterContent = (
|
||||
<div>
|
||||
<a
|
||||
href={improveStatsKBLink}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="mailpoet_stat_link_small"
|
||||
>
|
||||
{MailPoet.I18n.t('improveThisLinkText')}
|
||||
</a>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
{content}
|
||||
{afterContent}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
Statistics.propTypes = {
|
||||
newsletter: PropTypes.shape({
|
||||
id: PropTypes.number.isRequired,
|
||||
queue: PropTypes.shape({
|
||||
status: PropTypes.string,
|
||||
count_processed: PropTypes.number.isRequired,
|
||||
count_total: PropTypes.number.isRequired,
|
||||
created_at: PropTypes.instanceOf(Date),
|
||||
scheduled_at: PropTypes.instanceOf(Date),
|
||||
}),
|
||||
total_sent: PropTypes.number,
|
||||
statistics: PropTypes.shape({
|
||||
clicked: PropTypes.number,
|
||||
opened: PropTypes.number,
|
||||
unsubscribed: PropTypes.number,
|
||||
revenue: PropTypes.number,
|
||||
}),
|
||||
}).isRequired,
|
||||
isSent: PropTypes.bool,
|
||||
currentTime: PropTypes.instanceOf(Date),
|
||||
};
|
||||
Statistics.defaultProps = {
|
||||
isSent: undefined,
|
||||
currentTime: undefined,
|
||||
};
|
||||
|
||||
export default Statistics;
|
@ -8,8 +8,8 @@ import ListingTabs from 'newsletters/listings/tabs.jsx';
|
||||
import ListingHeading from 'newsletters/listings/heading.jsx';
|
||||
import FeatureAnnouncement from 'announcements/feature_announcement.jsx';
|
||||
|
||||
import Statistics from 'newsletters/listings/statistics.jsx';
|
||||
import {
|
||||
renderStatistics,
|
||||
addStatsCTAAction,
|
||||
checkCronStatus,
|
||||
checkMailerStatus,
|
||||
@ -332,10 +332,10 @@ const NewsletterListWelcome = createReactClass({ // eslint-disable-line react/pr
|
||||
</td>
|
||||
{ (mailpoetTrackingEnabled === true) ? (
|
||||
<td className="column" data-colname={MailPoet.I18n.t('statistics')}>
|
||||
{ renderStatistics(
|
||||
newsletter,
|
||||
newsletter.total_sent > 0 && newsletter.statistics
|
||||
) }
|
||||
<Statistics
|
||||
newsletter={newsletter}
|
||||
isSent={newsletter.total_sent > 0 && newsletter.statistics}
|
||||
/>
|
||||
</td>
|
||||
) : null }
|
||||
<td className="column-date" data-colname={MailPoet.I18n.t('lastModifiedOn')}>
|
||||
|
Reference in New Issue
Block a user