'js' => array('js/angular-crmMailing2.js', 'js/angular-crmMailing2-services.js', 'js/angular-crmMailing2-directives.js'),
'css' => array('css/angular-crmMailing2.css'),
);
+ $result['crmMailingAB2'] = array(
+ 'ext' => 'civicrm',
+ 'js' => array('js/angular-crmMailingAB2.js', 'js/angular-crmMailingAB2-services.js'),
+ 'css' => array('css/angular-crmMailingAB2.css'),
+ );
$result['crmMailingAB'] = array(
'ext' => 'civicrm',
'js' => array(
// ex: crmMailingMgr.mergeInto(newMailing, mailingTemplate, ['subject']);
mergeInto: function mergeInto(mailingTgt, mailingFrom, excludes) {
var MAILING_FIELDS = [
+ // always exclude: 'id'
'name',
'campaign_id',
'from_name',
--- /dev/null
+(function (angular, $, _) {
+
+ angular.module('crmMailingAB2').factory('crmMailingABCriteria', function () {
+ // TODO Get data from server
+ var values = {
+ '1': {value: '1', name: 'Subject lines', label: ts('Different "Subject" lines')},
+ '2': {value: '2', name: 'From names', label: ts('Different "From" names')},
+ '3': {value: '3', name: 'Two different emails', label: ts('Entirely different emails')}
+ };
+ return {
+ get: function get(value) {
+ var r = _.where(values, {value: '' + value});
+ return r.length > 0 ? r[0] : null;
+ },
+ getAll: function getAll() {
+ return values;
+ }
+ };
+ });
+
+ // CrmMailingAB is a data-model which combines an AB test (APIv3 "MailingAB") and three mailings (APIv3 "Mailing").
+ angular.module('crmMailingAB2').factory('CrmMailingAB', function (crmApi, crmMailingMgr, $q) {
+ function CrmMailingAB(id) {
+ this.id = id;
+ this.mailings = {};
+ }
+
+ angular.extend(CrmMailingAB.prototype, {
+ // @return Promise CrmMailingAB
+ load: function load() {
+ var crmMailingAB = this;
+ if (!crmMailingAB.id) {
+ crmMailingAB.ab = {
+ name: '',
+ mailing_id_a: null,
+ mailing_id_b: null,
+ mailing_id_c: null,
+ domain_id: null,
+ testing_criteria_id: null,
+ winner_criteria_id: null,
+ specific_url: '',
+ declare_winning_time: null,
+ group_percentage: 10
+ };
+ crmMailingAB.mailings.a = crmMailingMgr.create();
+ crmMailingAB.mailings.b = crmMailingMgr.create();
+ crmMailingAB.mailings.c = crmMailingMgr.create();
+
+ var dfr = $q.defer();
+ dfr.resolve(crmMailingAB);
+ return dfr.promise;
+ }
+ else {
+ return crmApi('MailingAB', 'get', {id: crmMailingAB.id})
+ .then(function (abResult) {
+ crmMailingAB.ab = abResult.values[abResult.id];
+ return crmMailingAB._loadMailings();
+ });
+ }
+ },
+ // @return Promise CrmMailingAB
+ save: function save() {
+ var crmMailingAB = this;
+
+ return crmMailingAB._saveMailings()
+ .then(function () {
+ return crmApi('MailingAB', 'create', crmMailingAB.ab)
+ .then(function (abResult) {
+ crmMailingAB.ab.id = abResult.id;
+ });
+ })
+ .then(function () {
+ return crmMailingAB;
+ });
+ },
+ // Load mailings A, B, and C (if available)
+ // @return Promise CrmMailingAB
+ _loadMailings: function _loadMailings() {
+ var crmMailingAB = this;
+ var todos = {};
+ _.each(['a', 'b', 'c'], function (mkey) {
+ if (crmMailingAB.ab['mailing_id_' + mkey]) {
+ todos[mkey] = crmMailingMgr.get(crmMailingAB.ab['mailing_id_' + mkey])
+ .then(function (mailing) {
+ crmMailingAB.mailings[mkey] = mailing;
+ });
+ }
+ else {
+ crmMailingAB.mailings[mkey] = crmMailingMgr.create();
+ }
+ });
+ return $q.all(todos).then(function () {
+ return crmMailingAB;
+ });
+ },
+ // Save mailings A, B, and C (if available)
+ // @return Promise CrmMailingAB
+ _saveMailings: function _saveMailings() {
+ var crmMailingAB = this;
+ var todos = {};
+ _.each(['a', 'b', 'c'], function (mkey) {
+ if (!crmMailingAB.mailings[mkey]) {
+ return;
+ }
+ if (crmMailingAB.ab['mailing_id_' + mkey]) {
+ // paranoia: in case caller forgot to manage id on mailing
+ crmMailingAB.mailings[mkey].id = crmMailingAB.ab['mailing_id_' + mkey];
+ }
+ todos[mkey] = crmMailingMgr.save(crmMailingAB.mailings[mkey]).then(function(){
+ crmMailingAB.ab['mailing_id_' + mkey] = crmMailingAB.mailings[mkey].id;
+ });
+ });
+ return $q.all(todos).then(function () {
+ return crmMailingAB;
+ });
+ }
+
+ });
+ return CrmMailingAB;
+ });
+
+})(angular, CRM.$, CRM._);
--- /dev/null
+(function (angular, $, _) {
+
+ var partialUrl = function (relPath, module) {
+ if (!module) {
+ module = 'crmMailingAB2';
+ }
+ return CRM.resourceUrls['civicrm'] + '/partials/' + module + '/' + relPath;
+ };
+
+ angular.module('crmMailingAB2', ['ngRoute', 'ui.utils', 'ngSanitize', 'crmUi', 'crmMailing2']);
+ angular.module('crmMailingAB2').config([
+ '$routeProvider',
+ function ($routeProvider) {
+ $routeProvider.when('/abtest2', {
+ templateUrl: partialUrl('list.html'),
+ controller: 'CrmMailingAB2ListCtrl',
+ resolve: {
+ mailingABList: function ($route, crmApi) {
+ return crmApi('MailingAB', 'get', {rowCount: 0});
+ }
+ }
+ });
+ $routeProvider.when('/abtest2/:id', {
+ templateUrl: partialUrl('edit.html'),
+ controller: 'CrmMailingAB2EditCtrl',
+ resolve: {
+ abtest: function ($route, CrmMailingAB) {
+ var abtest = new CrmMailingAB($route.current.params.id == 'new' ? null : $route.current.params.id);
+ return abtest.load();
+ }
+ }
+ });
+ }
+ ]);
+
+ angular.module('crmMailingAB2').controller('CrmMailingAB2ListCtrl', function ($scope, mailingABList, crmMailingABCriteria) {
+ $scope.mailingABList = mailingABList.values;
+ $scope.testing_criteria = crmMailingABCriteria.getAll();
+ })
+
+
+ angular.module('crmMailingAB2').controller('CrmMailingAB2EditCtrl', function ($scope, abtest, crmMailingABCriteria, crmMailingMgr) {
+ $scope.abtest = abtest;
+ $scope.ts = CRM.ts('CiviMail');
+ $scope.sync = function sync() {
+ abtest.mailings.a.name = ts('Test A (%1)', {1: abtest.ab.name});
+ abtest.mailings.b.name = ts('Test B (%1)', {1: abtest.ab.name});
+ abtest.mailings.c.name = ts('Winner (%1)', {1: abtest.ab.name});
+
+ var criteria = crmMailingABCriteria.get(abtest.ab.testing_criteria_id);
+ if (criteria) {
+ switch (criteria.name) {
+ case 'Subject lines':
+ crmMailingMgr.mergeInto(abtest.mailings.b, abtest.mailings.a, ['name', 'subject']);
+ break;
+ case 'From names':
+ crmMailingMgr.mergeInto(abtest.mailings.b, abtest.mailings.a, ['name', 'from_name', 'from_email']);
+ break;
+ case 'Two different emails':
+ crmMailingMgr.mergeInto(abtest.mailings.b, abtest.mailings.a, [
+ 'name',
+ 'subject',
+ 'from_name',
+ 'from_email',
+ 'body_html',
+ 'body_text'
+ ]);
+ break;
+ default:
+ throw "Unrecognized testing_criteria";
+ }
+ }
+ crmMailingMgr.mergeInto(abtest.mailings.c, abtest.mailings.a, ['name']);
+ };
+ $scope.save = function save() {
+ $scope.sync();
+ return abtest.save();
+ };
+ $scope.delete = function () {
+ throw "Not implemented: EditCtrl.delete"
+ };
+
+ $scope.sync();
+ });
+
+})(angular, CRM.$, CRM._);
--- /dev/null
+<div crm-ui-accordion crm-title="ts('Debug')" crm-collapsed="true">
+ <pre>{{abtest|json}}</pre>
+</div>
+
+<form name="crmMailingAB2">
+ <div crm-ui-wizard>
+ <div crm-ui-wizard-step crm-title="ts('Setup')">
+ Name: <input ng-model="abtest.ab.name"/>
+ </div>
+ <div crm-ui-wizard-step crm-title="ts('Content')">
+ <div>
+ Subject A: <input ng-model="abtest.mailings.a.subject"/>
+ </div>
+ <div>
+ Subject B: <input ng-model="abtest.mailings.b.subject"/>
+ </div>
+ </div>
+ <span crm-ui-wizard-buttons style="float:right;">
+ <button
+ crm-confirm="{title:ts('Delete Draft?'), message:ts('Are you sure you want to delete the draft mailing?')}"
+ on-yes="delete()">{{ts('Delete Draft')}}
+ </button>
+ <button ng-click="save()">{{ts('Save Draft')}}</button>
+ </span>
+ </div>
+</form>
--- /dev/null
+<!--
+Controller: ABListingCtrl
+Required vars: mailingABList
+-->
+<div id="help">
+ A/B Testing list
+</div>
+
+<div ng-show="!$.isEmptyObject(mailingABList)">
+ <table class="display">
+ <thead>
+ <tr>
+ <th>Title</th>
+ <th>Id</th>
+ <th>Test Type</th>
+ <th></th>
+ <th></th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr ng-repeat="mailingAB in mailingABList">
+ <td>{{mailingAB.name}}</td>
+ <td>{{mailingAB.id}}</td>
+ <td>{{testing_criteria[mailingAB.testing_criteria_id].label}}</td>
+ <td>
+ <a class="action-item crm-hover-button" ng-href="#/abtest2/{{mailingAB.id}}">Edit</a> 
+ <a class="action-item crm-hover-button" ng-href="#/abtest2/report/{{mailingAB.id}}">Results</a>
+ </td>
+ </tr>
+ </tbody>
+ </table>
+</div>
+
+<div ng-show="$.isEmptyObject(mailingABList)" class="messages status no-popup">
+ <div class="icon inform-icon"></div>
+ You have no A/B mailings
+</div>
+
+
+<div class="crm-submit-buttons">
+ <br>
+ <a ng-href="#/abtest2/new" class="button"><span><div class="icon add-icon"></div>New A/B Test</span></a>
+</div>