CRM-15578 - Add crmMailingAB2 module (based on crmMailingAB). Add skeletal list/edit...
authorTim Otten <totten@civicrm.org>
Fri, 12 Dec 2014 00:26:01 +0000 (16:26 -0800)
committerTim Otten <totten@civicrm.org>
Sun, 14 Dec 2014 07:41:02 +0000 (23:41 -0800)
CRM/Mailing/Info.php
css/angular-crmMailingAB2.css [new file with mode: 0644]
js/angular-crmMailing2-services.js
js/angular-crmMailingAB2-services.js [new file with mode: 0644]
js/angular-crmMailingAB2.js [new file with mode: 0644]
partials/crmMailingAB2/edit.html [new file with mode: 0644]
partials/crmMailingAB2/list.html [new file with mode: 0644]

index 758c092bdbf576c7be08278e5b7dc38a4ccf4776..5c5b3ed1ccfae895c22bcffb9f4fb375b9070213 100644 (file)
@@ -70,6 +70,11 @@ class CRM_Mailing_Info extends CRM_Core_Component_Info {
       '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(
diff --git a/css/angular-crmMailingAB2.css b/css/angular-crmMailingAB2.css
new file mode 100644 (file)
index 0000000..e69de29
index b02b6539f056ca1998216e7373a5fd6a8ce5309c..340608a38ac731e0cca4304b02b1ac9b9fcb73a4 100644 (file)
       // ex: crmMailingMgr.mergeInto(newMailing, mailingTemplate, ['subject']);
       mergeInto: function mergeInto(mailingTgt, mailingFrom, excludes) {
         var MAILING_FIELDS = [
+          // always exclude: 'id'
           'name',
           'campaign_id',
           'from_name',
diff --git a/js/angular-crmMailingAB2-services.js b/js/angular-crmMailingAB2-services.js
new file mode 100644 (file)
index 0000000..b077d50
--- /dev/null
@@ -0,0 +1,122 @@
+(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._);
diff --git a/js/angular-crmMailingAB2.js b/js/angular-crmMailingAB2.js
new file mode 100644 (file)
index 0000000..0bd3f7b
--- /dev/null
@@ -0,0 +1,86 @@
+(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._);
diff --git a/partials/crmMailingAB2/edit.html b/partials/crmMailingAB2/edit.html
new file mode 100644 (file)
index 0000000..42b3d14
--- /dev/null
@@ -0,0 +1,26 @@
+<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>
diff --git a/partials/crmMailingAB2/list.html b/partials/crmMailingAB2/list.html
new file mode 100644 (file)
index 0000000..6556c52
--- /dev/null
@@ -0,0 +1,43 @@
+<!--
+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>&nbsp
+        <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>