Mixpanel analytics [MAILPOET-686] (#940)

* Send analytics data to mixpanel

[MAILPOET-686]
This commit is contained in:
pavel-mailpoet 2017-06-20 10:20:50 +01:00 committed by stoletniy
parent 823cd3cd07
commit 812c6634ba
18 changed files with 413 additions and 88 deletions

View File

@ -1,8 +1,15 @@
(function(e,b){if(!b.__SV){var a,f,i,g;window.mixpanel=b;b._i=[];b.init=function(a,e,d){function f(b,h){var a=h.split(".");2==a.length&&(b=b[a[0]],h=a[1]);b[h]=function(){b.push([h].concat(Array.prototype.slice.call(arguments,0)))}}var c=b;"undefined"!==typeof d?c=b[d]=[]:d="mixpanel";c.people=c.people||[];c.toString=function(b){var a="mixpanel";"mixpanel"!==d&&(a+="."+d);b||(a+=" (stub)");return a};c.people.toString=function(){return c.toString(1)+".people (stub)"};i="disable time_event track track_pageview track_links track_forms register register_once alias unregister identify name_tag set_config people.set people.set_once people.increment people.append people.union people.track_charge people.clear_charges people.delete_user".split(" ");
for(g=0;g<i.length;g++)f(c,i[g]);b._i.push([a,e,d])};b.__SV=1.2;a=e.createElement("script");a.type="text/javascript";a.async=!0;a.src="undefined"!==typeof MIXPANEL_CUSTOM_LIB_URL?MIXPANEL_CUSTOM_LIB_URL:"file:"===e.location.protocol&&"//cdn.mxpnl.com/libs/mixpanel-2-latest.min.js".match(/^\/\//)?"https://cdn.mxpnl.com/libs/mixpanel-2-latest.min.js":"//cdn.mxpnl.com/libs/mixpanel-2-latest.min.js";f=e.getElementsByTagName("script")[0];f.parentNode.insertBefore(a,f)}})(document,window.mixpanel||[]);
(function(e,a){if(!a.__SV){var b=window;try{var c,l,i,j=b.location,g=j.hash;c=function(a,b){return(l=a.match(RegExp(b+"=([^&]*)")))?l[1]:null};g&&c(g,"state")&&(i=JSON.parse(decodeURIComponent(c(g,"state"))),"mpeditor"===i.action&&(b.sessionStorage.setItem("_mpcehash",g),history.replaceState(i.desiredHash||"",e.title,j.pathname+j.search)))}catch(m){}var k,h;window.mixpanel=a;a._i=[];a.init=function(b,c,f){function e(b,a){var c=a.split(".");2==c.length&&(b=b[c[0]],a=c[1]);b[a]=function(){b.push([a].concat(Array.prototype.slice.call(arguments,
0)))}}var d=a;"undefined"!==typeof f?d=a[f]=[]:f="mixpanel";d.people=d.people||[];d.toString=function(b){var a="mixpanel";"mixpanel"!==f&&(a+="."+f);b||(a+=" (stub)");return a};d.people.toString=function(){return d.toString(1)+".people (stub)"};k="disable time_event track track_pageview track_links track_forms register register_once alias unregister identify name_tag set_config reset people.set people.set_once people.increment people.append people.union people.track_charge people.clear_charges people.delete_user".split(" ");
for(h=0;h<k.length;h++)e(d,k[h]);a._i.push([b,c,f])};a.__SV=1.2;b=e.createElement("script");b.type="text/javascript";b.async=!0;b.src="undefined"!==typeof MIXPANEL_CUSTOM_LIB_URL?MIXPANEL_CUSTOM_LIB_URL:"file:"===e.location.protocol&&"//cdn.mxpnl.com/libs/mixpanel-2-latest.min.js".match(/^\/\//)?"https://cdn.mxpnl.com/libs/mixpanel-2-latest.min.js":"//cdn.mxpnl.com/libs/mixpanel-2-latest.min.js";c=e.getElementsByTagName("script")[0];c.parentNode.insertBefore(b,c)}})(document,window.mixpanel||[]);
mixpanel.init("f683d388fb25fcf331f1b2b5c4449798");
window.mixpanelTrackingId = "8cce373b255e5a76fb22d57b85db0c92";
if (mailpoet_analytics_enabled) {
mixpanel.init(window.mixpanelTrackingId);
if (mailpoet_analytics_data != null) {
mixpanel.track('MailPoet 3', mailpoet_analytics_data);
}
if (typeof mailpoet_analytics_data === 'object') {
mixpanel.track('Wysija Usage', mailpoet_analytics_data || {});
}

View File

@ -0,0 +1,71 @@
/*
* This creates two functions and adds them to MailPoet object
* - `trackEvent` which should be used in normal circumstances.
* This function tracks an event and sends it to mixpanel.
* This function does nothing if analytics is disabled.
* - `forceTrackEvent` which sends given event to analytics
* even if it has been disabled.
*
*/
/**
* This is to cache events which are triggered before the mixpanel
* library is loaded. This might happen if an event is tracked
* on page load and the mixpanel library takes a long time to load.
* After it is loaded all events are posted.
* @type {Array.Object}
*/
var eventsCache = [];
function track(name, data){
if (typeof window.mixpanel.track !== "function") {
window.mixpanel.init(window.mixpanelTrackingId);
}
window.mixpanel.track(name, data);
}
function exportMixpanel(MailPoet) {
MailPoet.forceTrackEvent = track;
if (window.mailpoet_analytics_enabled) {
MailPoet.trackEvent = track;
} else {
MailPoet.trackEvent = function () {};
}
}
function trackCachedEvents() {
eventsCache.map(function (event) {
if (window.mailpoet_analytics_enabled || event.forced) {
window.mixpanel.track(event.name, event.data)
}
});
}
function initializeMixpanelWhenLoaded() {
if (typeof window.mixpanel === "object") {
exportMixpanel(MailPoet);
trackCachedEvents();
} else {
setTimeout(initializeMixpanelWhenLoaded, 100);
}
}
function cacheEvent(forced, name, data) {
eventsCache.push({
name: name,
data: data,
forced: forced,
});
}
define(
['mailpoet', 'underscore'],
function(MailPoet, _) {
MailPoet.trackEvent = _.partial(cacheEvent, false);
MailPoet.forceTrackEvent = _.partial(cacheEvent, true);
initializeMixpanelWhenLoaded();
}
);

View File

@ -0,0 +1,43 @@
define(
[
'mailpoet'
],
function(
MailPoet
) {
function eventHandler() {
if (confirm(MailPoet.I18n.t('reinstallConfirmation'))) {
MailPoet.trackEvent(
'User has reinstalled MailPoet via Settings',
{'MailPoet Free version': window.mailpoet_version}
);
MailPoet.Modal.loading(true);
MailPoet.Ajax.post({
'api_version': window.mailpoet_api_version,
'endpoint': 'setup',
'action': 'reset'
}).always(function () {
MailPoet.Modal.loading(false);
}).done(function () {
window.location = 'admin.php?page=mailpoet-newsletters';
}).fail(function (response) {
if (response.errors.length > 0) {
MailPoet.Notice.error(
response.errors.map(function (error) {
return error.message;
}),
{scroll: true}
);
}
});
}
return false;
}
var element = document.getElementById('mailpoet_reinstall');
if (element) {
element.addEventListener('click', eventHandler, false);
}
});

View File

@ -0,0 +1,51 @@
<?php
namespace MailPoet\Analytics;
use Carbon\Carbon;
use MailPoet\Models\Setting;
if(!defined('ABSPATH')) exit;
class Analytics {
const SETTINGS_LAST_SENT_KEY = "analytics_last_sent";
const SEND_AFTER_DAYS = 7;
/** @var Reporter */
private $reporter;
public function __construct(Reporter $reporter) {
$this->reporter = $reporter;
}
/** @return array */
function generateAnalytics() {
if($this->shouldSend()) {
$data = $this->reporter->getData();
$this->recordDataSent();
return $data;
}
}
/** @return boolean */
function isEnabled() {
$analytics_settings = Setting::getValue('analytics', array());
return ($analytics_settings["enabled"] === "1") || ($analytics_settings["enabled"] === "true");
}
private function shouldSend() {
if(!$this->isEnabled()) {
return false;
}
$lastSent = Setting::getValue(Analytics::SETTINGS_LAST_SENT_KEY);
if(!$lastSent) {
return true;
}
$lastSentCarbon = Carbon::createFromTimestamp(strtotime($lastSent))->addDays(Analytics::SEND_AFTER_DAYS);
return $lastSentCarbon->isPast();
}
private function recordDataSent() {
Setting::setValue(Analytics::SETTINGS_LAST_SENT_KEY, Carbon::now());
}
}

View File

@ -1,23 +1,33 @@
<?php
namespace MailPoet\Analytics;
use MailPoet\Config\Installer;
use MailPoet\Models\Newsletter;
use MailPoet\Models\Setting;
use MailPoet\Models\Subscriber;
class Reporter {
private $fields = array(
'Plugin Version' => 'pluginVersion',
);
function getData() {
$_this = $this;
$analytics_data = array_map(function($func) use ($_this) {
return $_this->$func();
}, $this->fields);
$mta = Setting::getValue('mta', array());
$premium_status = Installer::getPremiumStatus();
$newsletters = Newsletter::getAnalytics();
return $analytics_data;
return array(
'MailPoet Free version' => MAILPOET_VERSION,
'MailPoet Premium version' => (defined('MAILPOET_PREMIUM_VERSION')) ? MAILPOET_PREMIUM_VERSION : 'N/A',
'Premium Plugin Installed' => $premium_status['premium_plugin_installed'],
'Premium Plugin Active' => $premium_status['premium_plugin_active'],
'Total number of subscribers' => Subscriber::getTotalSubscribers(),
'Sending Method' => $mta['method'],
'Date of plugin installation' => Setting::getValue('installed_at'),
'Number of standard newsletters sent in last 3 months' => $newsletters['sent_newsletters'],
'Number of active post notifications' => $newsletters['notifications_count'],
'Number of active welcome emails' => $newsletters['welcome_newsletters_count'],
'Is WooCommerce plugin installed' => is_plugin_active("woocommerce/woocommerce.php"),
);
}
private function pluginVersion() {
return MAILPOET_VERSION;
}
}

View File

@ -1,32 +0,0 @@
<?php
namespace MailPoet\Config;
use MailPoet\Analytics\Reporter;
use MailPoet\Models\Setting;
if(!defined('ABSPATH')) exit;
class Analytics {
function __construct() {
}
function init() {
add_action('admin_enqueue_scripts', array($this, 'setupAdminDependencies'));
}
function setupAdminDependencies() {
if(Setting::getValue('send_analytics_now', false)) {
$analytics = new Reporter();
wp_enqueue_script(
'analytics',
Env::$assets_url . '/js/lib/analytics.js',
array(),
Env::$version
);
wp_localize_script(
'analytics',
'mailpoet_analytics_data',
$analytics->getData()
);
}
}
}

View File

@ -88,7 +88,6 @@ class Initializer {
$this->setupUpdater();
$this->setupLocalizer();
$this->setupMenu();
$this->setupAnalytics();
$this->setupChangelog();
$this->setupShortcodes();
$this->setupImages();
@ -178,11 +177,6 @@ class Initializer {
$menu->init();
}
function setupAnalytics() {
$analytics = new Analytics();
$analytics->init();
}
function setupChangelog() {
$changelog = new Changelog();
$changelog->init();

View File

@ -34,6 +34,7 @@ class Renderer {
$this->setupFilters();
$this->setupHandlebars();
$this->setupHelpscout();
$this->setupAnalytics();
$this->setupGlobalVariables();
$this->setupSyntax();
}
@ -58,6 +59,10 @@ class Renderer {
$this->renderer->addExtension(new Twig\Helpscout());
}
function setupAnalytics() {
$this->renderer->addExtension(new Twig\Analytics());
}
function setupGlobalVariables() {
$this->renderer->addExtension(new Twig\Assets(array(
'version' => Env::$version,

View File

@ -1,5 +1,6 @@
<?php
namespace MailPoet\Models;
use Carbon\Carbon;
use MailPoet\Newsletter\Renderer\Renderer;
use MailPoet\Util\Helpers;
use MailPoet\Util\Security;
@ -492,6 +493,37 @@ class Newsletter extends Model {
);
}
static function getAnalytics() {
$welcome_newsletters_count = Newsletter::getPublished()
->filter('filterType', self::TYPE_WELCOME)
->filter('filterStatus', self::STATUS_ACTIVE)
->count();
$notifications_count = Newsletter::getPublished()
->filter('filterType', self::TYPE_NOTIFICATION)
->filter('filterStatus', self::STATUS_ACTIVE)
->count();
$sent_newsletters = static::table_alias('newsletters')
->where('newsletters.type', self::TYPE_STANDARD)
->where('newsletters.status', self::STATUS_SENT)
->join(
MP_SENDING_QUEUES_TABLE,
'queues.newsletter_id = newsletters.id',
'queues'
)
->where('queues.status', SendingQueue::STATUS_COMPLETED)
->whereGte('queues.processed_at', Carbon::now()->subMonths(3))
->count();
return array(
'welcome_newsletters_count' => $welcome_newsletters_count,
'notifications_count' => $notifications_count,
'sent_newsletters' => $sent_newsletters,
);
}
static function search($orm, $search = '') {
if(strlen(trim($search)) > 0) {
$orm->whereLike('subject', '%' . $search . '%');

30
lib/Twig/Analytics.php Normal file
View File

@ -0,0 +1,30 @@
<?php
namespace MailPoet\Twig;
use MailPoet\Analytics\Reporter;
use MailPoet\Analytics\Analytics as AnalyticsGenerator;
if(!defined('ABSPATH')) exit;
class Analytics extends \Twig_Extension {
public function getName() {
return 'analytics';
}
public function getFunctions() {
$analytics = new AnalyticsGenerator(new Reporter());
return array(
new \Twig_SimpleFunction(
'get_analytics_data',
array($analytics, 'generateAnalytics'),
array('is_safe' => array('all'))
),
new \Twig_SimpleFunction(
'is_analytics_enabled',
array($analytics, 'isEnabled'),
array('is_safe' => array('all'))
),
);
}
}

View File

@ -56,6 +56,11 @@ class Functions extends \Twig_Extension {
array($this, 'getWPDateFormat'),
array('is_safe' => array('all'))
),
new \Twig_SimpleFunction(
'mailpoet_version',
array($this, 'getMailPoetVersion'),
array('is_safe' => array('all'))
),
new \Twig_SimpleFunction(
'wp_time_format',
array($this, 'getWPTimeFormat'),
@ -116,6 +121,10 @@ class Functions extends \Twig_Extension {
'F j, Y';
}
function getMailPoetVersion() {
return MAILPOET_VERSION;
}
function getWPTimeFormat() {
return (get_option('time_format')) ?
get_option('time_format') :

View File

@ -0,0 +1,104 @@
<?php
namespace MailPoet\Analytics;
use Carbon\Carbon;
use Codeception\Util\Stub;
use MailPoet\Models\Setting;
class AnalyticsTest extends \MailPoetTest {
protected $backupGlobals = false;
function testIsEnabledReturnsTrueIfSettingEnabled() {
Setting::setValue('analytics', array('enabled' => '1'));
$analytics = new Analytics(new Reporter());
expect($analytics->isEnabled())->true();
}
function testIsEnabledReturnsFalseIfEmptySettings() {
Setting::setValue('analytics', array());
$analytics = new Analytics(new Reporter());
expect($analytics->isEnabled())->false();
}
function testIsEnabledReturnsFalseIfNotEnabled() {
Setting::setValue('analytics', array('enabled' => ''));
$analytics = new Analytics(new Reporter());
expect($analytics->isEnabled())->false();
}
function testGetDataIfSettingsIsDisabled() {
$reporter = Stub::makeEmpty(
'MailPoet\Analytics\Reporter',
array(
'getData' => Stub::never(),
),
$this
);
Setting::setValue('analytics', array('enabled' => ''));
$analytics = new Analytics($reporter);
expect($analytics->generateAnalytics())->null();
}
function testGetDataIfSentRecently() {
$reporter = Stub::makeEmpty(
'MailPoet\Analytics\Reporter',
array(
'getData' => Stub::never(),
),
$this
);
Setting::setValue('analytics', array('enabled' => '1'));
Setting::setValue('analytics_last_sent', Carbon::now()->subHours(1));
$analytics = new Analytics($reporter);
expect($analytics->generateAnalytics())->null();
}
function testGetDataIfEnabledButNeverSent() {
$data = array();
$reporter = Stub::makeEmpty(
'MailPoet\Analytics\Reporter',
array(
'getData' => Stub::once(function() use ($data){
return $data;
}),
),
$this
);
Setting::setValue('analytics', array('enabled' => '1'));
Setting::setValue('analytics_last_sent', null);
$analytics = new Analytics($reporter);
expect($analytics->generateAnalytics())->equals($data);
}
function testGetDataIfEnabledAndSentLongTimeAgo() {
$data = array();
$reporter = Stub::makeEmpty(
'MailPoet\Analytics\Reporter',
array(
'getData' => Stub::once(function() use ($data){
return $data;
}),
),
$this
);
Setting::setValue('analytics', array('enabled' => '1'));
Setting::setValue('analytics_last_sent', Carbon::now()->subYear());
$analytics = new Analytics($reporter);
expect($analytics->generateAnalytics())->equals($data);
}
}

View File

@ -44,6 +44,9 @@ jQuery('.toplevel_page_mailpoet-newsletters.menu-top-last')
<script type="text/javascript">
var mailpoet_date_format = "<%= wp_datetime_format()|escape('js') %>";
var mailpoet_time_format = "<%= wp_time_format()|escape('js') %>";
var mailpoet_version = "<%= mailpoet_version() %>";
var mailpoet_analytics_enabled = <%= is_analytics_enabled() | json_encode %>;
var mailpoet_analytics_data = <%= json_encode(get_analytics_data()) %>;
</script>
<!-- javascripts -->
@ -66,6 +69,8 @@ jQuery('.toplevel_page_mailpoet-newsletters.menu-top-last')
'admin.js'
)%>
<%= javascript('lib/analytics.js') %>
<script type="text/javascript">
if(window['HS'] !== undefined) {
// HelpScout Beacon: Configuration

View File

@ -135,3 +135,8 @@
});
</script>
<% endblock %>
<% block translations %>
<%= localize({
'reinstallConfirmation': __('Are you sure? All of your MailPoet data will be permanently erased (newsletters, statistics, subscribers, etc.).')
}) %>
<% endblock %>

View File

@ -165,34 +165,3 @@
</tr>
</tbody>
</table>
<script type="text/javascript">
jQuery(function($) {
$(function() {
$('#mailpoet_reinstall').on('click', function() {
if(confirm(
"<%= __('Are you sure? All of your MailPoet data will be permanently erased (newsletters, statistics, subscribers, etc.).') %>"
)) {
MailPoet.Modal.loading(true);
MailPoet.Ajax.post({
'api_version': window.mailpoet_api_version,
'endpoint': 'setup',
'action': 'reset'
}).always(function() {
MailPoet.Modal.loading(false);
}).done(function(response) {
window.location = "<%= admin_url('admin.php?page=mailpoet-newsletters') %>";
}).fail(function(response) {
if (response.errors.length > 0) {
MailPoet.Notice.error(
response.errors.map(function(error) { return error.message; }),
{ scroll: true }
);
}
});
}
return false;
});
});
});
</script>

View File

@ -47,4 +47,16 @@
</div>
<script type="text/javascript">
jQuery(function($) {
$(function() {
MailPoet.trackEvent(
'User has updated MailPoet',
{'MailPoet Free version': window.mailpoet_version}
);
});
});
</script>
<% endblock %>

View File

@ -124,7 +124,15 @@ jQuery(function($) {
data: {
analytics: { enabled: (is_enabled)}
}
})
});
if (is_enabled) {
MailPoet.forceTrackEvent(
'User has installed MailPoet',
{'MailPoet Free version': window.mailpoet_version}
);
}
})
});
});

View File

@ -138,7 +138,8 @@ config.push(_.extend({}, baseConfig, {
'notice',
'num',
'jquery.serialize_object',
'parsleyjs'
'parsleyjs',
'analytics_event',
],
admin_vendor: [
'react',
@ -156,6 +157,7 @@ config.push(_.extend({}, baseConfig, {
'segments/segments.jsx',
'forms/forms.jsx',
'settings/tabs.js',
'settings/reinstall_from_scratch.js',
'subscribers/importExport/import.js',
'subscribers/importExport/export.js',
'helpscout'