First commit
This commit is contained in:
commit
c6e2478c40
13918 changed files with 2303184 additions and 0 deletions
196
sites/all/modules/civicrm/ang/crmMailingAB/BlockMailing.html
Normal file
196
sites/all/modules/civicrm/ang/crmMailingAB/BlockMailing.html
Normal file
|
@ -0,0 +1,196 @@
|
|||
<!--
|
||||
Required vars: abtest, fields
|
||||
|
||||
Note: Much of this file is duplicated in crmMailing and crmMailingAB with variations on placement/title/binding.
|
||||
It could perhaps be thinned by 30-60% by making more directives.
|
||||
|
||||
This template follows a basic pattern. For each included field, there are three variants, as in this example:
|
||||
- fromAddress: The default From: address shared by both mailings (representatively mapped to mailing A)
|
||||
- fromAddressA: The From: address for mailing A
|
||||
- fromAddressB: The From: address for mailing B
|
||||
Each variant is guarded with "ng-if='fields.fieldName'"; if true, the field will be displayed and
|
||||
processed by Angular; if false, the field will be hidden and completely ignored by Angular.
|
||||
-->
|
||||
<div class="crm-block" ng-form="subform" crm-ui-id-scope>
|
||||
<div class="crm-group">
|
||||
|
||||
|
||||
<div crm-ui-field="{name: 'subform.msg_template_id', title: ts('Template')}" ng-if="fields.msg_template_id">
|
||||
<div ng-controller="MsgTemplateCtrl">
|
||||
<select
|
||||
crm-ui-id="subform.msg_template_id"
|
||||
name="msg_template_id"
|
||||
class="fa-clipboard"
|
||||
crm-ui-select="{dropdownAutoWidth : true, allowClear: true, placeholder: ts('Message Template')}"
|
||||
ng-model="abtest.mailings.a.msg_template_id"
|
||||
ng-change="loadTemplate(abtest.mailings.a, abtest.mailings.a.msg_template_id)"
|
||||
>
|
||||
<option value=""></option>
|
||||
<option ng-repeat="frm in crmMsgTemplates.getAll() | orderBy:'msg_title'" ng-value="frm.id">{{frm.msg_title}}</option>
|
||||
</select>
|
||||
<a crm-icon="fa-floppy-o" ng-click="saveTemplate(abtest.mailings.a)" class="crm-hover-button" title="{{ts('Save As')}}"></a>
|
||||
</div>
|
||||
</div>
|
||||
<div crm-ui-field="{name: 'subform.msg_template_idA', title: ts('Template (A)')}" ng-if="fields.msg_template_idA">
|
||||
<div ng-controller="MsgTemplateCtrl">
|
||||
<select
|
||||
crm-ui-id="subform.msg_template_idA"
|
||||
name="msg_template_idA"
|
||||
class="fa-clipboard"
|
||||
crm-ui-select="{dropdownAutoWidth : true, allowClear: true, placeholder: ts('Message Template')}"
|
||||
ng-model="abtest.mailings.a.msg_template_id"
|
||||
ng-change="loadTemplate(abtest.mailings.a, abtest.mailings.a.msg_template_id)"
|
||||
>
|
||||
<option value=""></option>
|
||||
<option ng-repeat="frm in crmMsgTemplates.getAll() | orderBy:'msg_title'" ng-value="frm.id">{{frm.msg_title}}</option>
|
||||
</select>
|
||||
<a crm-icon="fa-floppy-o" ng-click="saveTemplate(abtest.mailings.a)" class="crm-hover-button" title="{{ts('Save As')}}"></a>
|
||||
</div>
|
||||
</div>
|
||||
<div crm-ui-field="{name: 'subform.msg_template_idB', title: ts('Template (B)')}" ng-if="fields.msg_template_idB">
|
||||
<div ng-controller="MsgTemplateCtrl">
|
||||
<select
|
||||
crm-ui-id="subform.msg_template_idB"
|
||||
name="msg_template_idB"
|
||||
class="fa-clipboard"
|
||||
crm-ui-select="{dropdownAutoWidth : true, allowClear: true, placeholder: ts('Message Template')}"
|
||||
ng-model="abtest.mailings.b.msg_template_id"
|
||||
ng-change="loadTemplate(abtest.mailings.b, abtest.mailings.b.msg_template_id)"
|
||||
>
|
||||
<option value=""></option>
|
||||
<option ng-repeat="frm in crmMsgTemplates.getAll() | orderBy:'msg_title'" ng-value="frm.id">{{frm.msg_title}}</option>
|
||||
</select>
|
||||
<a crm-icon="fa-floppy-o" ng-click="saveTemplate(abtest.mailings.b)" class="crm-hover-button" title="{{ts('Save As')}}"></a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div crm-ui-field="{name: 'subform.fromAddress', title: ts('From'), help: hs('from_email')}" ng-if="fields.fromAddress">
|
||||
<span ng-controller="EmailAddrCtrl" crm-mailing-from-address="fromPlaceholder" crm-mailing="abtest.mailings.a">
|
||||
<select
|
||||
crm-ui-id="subform.fromAddress"
|
||||
crm-ui-select="{dropdownAutoWidth : true, allowClear: false, placeholder: ts('Email address')}"
|
||||
name="fromAddress"
|
||||
ng-model="fromPlaceholder.label"
|
||||
required>
|
||||
<option value=""></option>
|
||||
<option ng-repeat="frm in crmFromAddresses.getAll() | filter:{is_active:1} | orderBy:'weight'" value="{{frm.label}}">{{frm.label}}</option>
|
||||
</select>
|
||||
</span>
|
||||
</div>
|
||||
<div crm-ui-field="{name: 'subform.fromAddressA', title: ts('From (A)'), help: hs('from_email')}" ng-if="fields.fromAddressA">
|
||||
<span ng-controller="EmailAddrCtrl" crm-mailing-from-address="fromPlaceholder" crm-mailing="abtest.mailings.a">
|
||||
<select
|
||||
crm-ui-id="subform.fromAddressA"
|
||||
crm-ui-select="{dropdownAutoWidth : true, allowClear: false, placeholder: ts('Email address')}"
|
||||
name="fromAddressA"
|
||||
ng-model="fromPlaceholder.label"
|
||||
required>
|
||||
<option value=""></option>
|
||||
<option ng-repeat="frm in crmFromAddresses.getAll() | filter:{is_active:1} | orderBy:'weight'" value="{{frm.label}}">{{frm.label}}</option>
|
||||
</select>
|
||||
</span>
|
||||
</div>
|
||||
<div crm-ui-field="{name: 'subform.fromAddressB', title: ts('From (B)'), help: hs('from_email')}" ng-if="fields.fromAddressB">
|
||||
<span ng-controller="EmailAddrCtrl" crm-mailing-from-address="fromPlaceholder" crm-mailing="abtest.mailings.b">
|
||||
<select
|
||||
crm-ui-id="subform.fromAddressB"
|
||||
crm-ui-select="{dropdownAutoWidth : true, allowClear: false, placeholder: ts('Email address')}"
|
||||
name="fromAddressB"
|
||||
ng-model="fromPlaceholder.label"
|
||||
required>
|
||||
<option value=""></option>
|
||||
<option ng-repeat="frm in crmFromAddresses.getAll() | filter:{is_active:1} | orderBy:'weight'" value="{{frm.label}}">{{frm.label}}</option>
|
||||
</select>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div crm-ui-field="{name: 'subform.replyTo', title: ts('Reply-To')}" ng-show="crmMailingConst.enableReplyTo" ng-if="fields.replyTo">
|
||||
<span ng-controller="EmailAddrCtrl">
|
||||
<select
|
||||
crm-ui-id="subform.replyTo"
|
||||
crm-ui-select="{dropdownAutoWidth : true, allowClear: true, placeholder: ts('Email address')}"
|
||||
name="replyTo"
|
||||
ng-change="checkReplyToChange(abtest.mailings.a)"
|
||||
ng-model="abtest.mailings.a.replyto_email"
|
||||
>
|
||||
<option value=""></option>
|
||||
<option ng-repeat="frm in crmFromAddresses.getAll() | filter:{is_active:1} | orderBy:'weight'" value="{{frm.label}}">{{frm.label}}</option>
|
||||
</select>
|
||||
</span>
|
||||
</div>
|
||||
<div crm-ui-field="{name: 'subform.replyToA', title: ts('Reply-To (A)')}" ng-show="crmMailingConst.enableReplyTo" ng-if="fields.replyToA">
|
||||
<span ng-controller="EmailAddrCtrl">
|
||||
<select
|
||||
crm-ui-id="subform.replyToA"
|
||||
crm-ui-select="{dropdownAutoWidth : true, allowClear: true, placeholder: ts('Email address')}"
|
||||
name="replyToA"
|
||||
ng-change="checkReplyToChange(abtest.mailings.a)"
|
||||
ng-model="abtest.mailings.a.replyto_email"
|
||||
>
|
||||
<option value=""></option>
|
||||
<option ng-repeat="frm in crmFromAddresses.getAll() | filter:{is_active:1} | orderBy:'weight'" value="{{frm.label}}">{{frm.label}}</option>
|
||||
</select>
|
||||
</span>
|
||||
</div>
|
||||
<div crm-ui-field="{name: 'subform.replyToB', title: ts('Reply-To (B)')}" ng-show="crmMailingConst.enableReplyTo" ng-if="fields.replyToB">
|
||||
<span ng-controller="EmailAddrCtrl">
|
||||
<select
|
||||
crm-ui-id="subform.replyToB"
|
||||
crm-ui-select="{dropdownAutoWidth : true, allowClear: true, placeholder: ts('Email address')}"
|
||||
name="replyToB"
|
||||
ng-change="checkReplyToChange(abtest.mailings.b)"
|
||||
ng-model="abtest.mailings.b.replyto_email"
|
||||
>
|
||||
<option value=""></option>
|
||||
<option ng-repeat="frm in crmFromAddresses.getAll() | filter:{is_active:1} | orderBy:'weight'" value="{{frm.label}}">{{frm.label}}</option>
|
||||
</select>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div crm-ui-field="{name: 'subform.subject', title: ts('Subject')}" ng-if="fields.subject">
|
||||
<div style="float: right;">
|
||||
<input crm-mailing-token on-select="$broadcast('insert:subject', token.name)" tabindex="-1">
|
||||
</div>
|
||||
<input
|
||||
crm-ui-id="subform.subject"
|
||||
crm-ui-insert-rx="insert:subject"
|
||||
type="text"
|
||||
class="crm-form-text"
|
||||
ng-model="abtest.mailings.a.subject"
|
||||
required
|
||||
placeholder="Subject"
|
||||
name="subject" >
|
||||
</div>
|
||||
<div crm-ui-field="{name: 'subform.subjectA', title: ts('Subject (A)')}" ng-if="fields.subjectA">
|
||||
<div style="float: right;">
|
||||
<input crm-mailing-token on-select="$broadcast('insert:subjectA', token.name)" tabindex="-1">
|
||||
</div>
|
||||
<input
|
||||
crm-ui-id="subform.subjectA"
|
||||
crm-ui-insert-rx="insert:subjectA"
|
||||
type="text"
|
||||
class="crm-form-text"
|
||||
ng-model="abtest.mailings.a.subject"
|
||||
required
|
||||
placeholder="Subject"
|
||||
name="subjectA" >
|
||||
</div>
|
||||
<div crm-ui-field="{name: 'subform.subjectB', title: ts('Subject (B)')}" ng-if="fields.subjectB">
|
||||
<div style="float: right;">
|
||||
<input crm-mailing-token on-select="$broadcast('insert:subjectB', token.name)" tabindex="-1">
|
||||
</div>
|
||||
<input
|
||||
crm-ui-id="subform.subjectB"
|
||||
crm-ui-insert-rx="insert:subjectB"
|
||||
type="text"
|
||||
class="crm-form-text"
|
||||
ng-model="abtest.mailings.b.subject"
|
||||
required
|
||||
placeholder="Subject"
|
||||
name="subjectB" >
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
32
sites/all/modules/civicrm/ang/crmMailingAB/BlockMailing.js
Normal file
32
sites/all/modules/civicrm/ang/crmMailingAB/BlockMailing.js
Normal file
|
@ -0,0 +1,32 @@
|
|||
(function(angular, $, _) {
|
||||
|
||||
// example:
|
||||
// scope.myAbtest = new CrmMailingAB();
|
||||
// <crm-mailing-ab-block-mailing="{fromAddressA: 1, fromAddressB: 1}" crm-abtest="myAbtest" />
|
||||
var simpleDirectives = {
|
||||
crmMailingAbBlockMailing: '~/crmMailingAB/BlockMailing.html'
|
||||
};
|
||||
_.each(simpleDirectives, function(templateUrl, directiveName) {
|
||||
angular.module('crmMailingAB').directive(directiveName, function($parse, crmMailingABCriteria, crmUiHelp) {
|
||||
var scopeDesc = {crmAbtest: '@'};
|
||||
scopeDesc[directiveName] = '@';
|
||||
|
||||
return {
|
||||
scope: scopeDesc,
|
||||
templateUrl: templateUrl,
|
||||
link: function(scope, elm, attr) {
|
||||
var model = $parse(attr.crmAbtest);
|
||||
scope.abtest = model(scope.$parent);
|
||||
scope.crmMailingConst = CRM.crmMailing;
|
||||
scope.crmMailingABCriteria = crmMailingABCriteria;
|
||||
scope.ts = CRM.ts(null);
|
||||
scope.hs = crmUiHelp({file: 'CRM/Mailing/MailingUI'});
|
||||
|
||||
var fieldsModel = $parse(attr[directiveName]);
|
||||
scope.fields = fieldsModel(scope.$parent);
|
||||
}
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
})(angular, CRM.$, CRM._);
|
67
sites/all/modules/civicrm/ang/crmMailingAB/BlockSetup.html
Normal file
67
sites/all/modules/civicrm/ang/crmMailingAB/BlockSetup.html
Normal file
|
@ -0,0 +1,67 @@
|
|||
<div class="crm-block" ng-form="setupForm" crm-ui-id-scope>
|
||||
<div class="crm-group">
|
||||
<div class="help" ng-if="fields.help">
|
||||
{{ts('A/B testing allows you to send two test mailings to a random subset of your recipients. After collecting and comparing metrics, the more successful mailing will be sent to the remaining recipients.')}}
|
||||
</div>
|
||||
<div crm-ui-field="{name: 'setupForm.abName', title: ts('Name'), help: hs('name')}" ng-if="fields.abName">
|
||||
<input type="text"
|
||||
crm-ui-id="setupForm.abName"
|
||||
name="abName"
|
||||
ng-model="abtest.ab.name"
|
||||
class="crm-form-text"
|
||||
placeholder="A/B Test Name"
|
||||
required/>
|
||||
</div>
|
||||
<div crm-ui-field="{name: 'setupForm.campaign', title: ts('Campaign'), help: hs({id: 'id-campaign_id', file: 'CRM/Campaign/Form/addCampaignToComponent'})}" ng-show="crmMailingConst.campaignEnabled"
|
||||
ng-if="fields.campaign">
|
||||
<input
|
||||
crm-entityref="{entity: 'Campaign', select: {allowClear: true, placeholder: ts('Select Campaign')}}"
|
||||
crm-ui-id="setupForm.campaign"
|
||||
name="campaign"
|
||||
ng-model="abtest.mailings.a.campaign_id"
|
||||
ng-change="abtest.mailings.b.campaign_id=abtest.mailings.a.campaign_id"
|
||||
/>
|
||||
</div>
|
||||
<div crm-ui-field="{title: ts('Test Type')}" ng-if="fields.testing_criteria">
|
||||
<div ng-repeat="criteria in crmMailingABCriteria.getAll()">
|
||||
<label>
|
||||
<input name="testing_criteria" ng-model="abtest.ab.testing_criteria" type="radio"
|
||||
value="{{criteria.value}}" required/>
|
||||
{{criteria.label}}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div crm-ui-field="{name: 'setupForm.recipients', title: ts('Recipients')}" ng-if="fields.recipients">
|
||||
<div crm-mailing-block-recipients="{name: 'recipients', id: 'setupForm.recipients'}" crm-mailing="abtest.mailings.a"></div>
|
||||
</div>
|
||||
<div crm-ui-field="{title: ts('Distribution')}" ng-if="fields.group_percentage">
|
||||
<div crm-mailing-ab-slider ng-model="abtest.ab.group_percentage"></div>
|
||||
</div>
|
||||
<div crm-ui-field="{title: ts('Send')}" ng-if="fields.scheduled_date">
|
||||
<div crm-mailing-radio-date="schedule" ng-model="abtest.mailings.a.scheduled_date">
|
||||
<div>
|
||||
<input ng-model="schedule.mode" type="radio" name="send" value="now" id="schedule-send-now"/>
|
||||
<label for="schedule-send-now">{{ts('Send A/B test immediately')}}</label>
|
||||
</div>
|
||||
<div>
|
||||
<input ng-model="schedule.mode" type="radio" name="send" value="at" id="schedule-send-at"/>
|
||||
<label for="schedule-send-at">{{ts('Send A/B test at:')}}</label>
|
||||
<input crm-ui-datepicker ng-model="schedule.datetime"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div crm-ui-field="{title: ts('Assess')}" ng-if="fields.declare_winning_time">
|
||||
<div crm-mailing-radio-date="assessSched" ng-model="abtest.ab.declare_winning_time">
|
||||
<div>
|
||||
<input ng-model="assessSched.mode" type="radio" name="assess" value="now" id="schedule-assess-now"/>
|
||||
<label for="schedule-assess-now">{{ts('Assess A/B results on an on-going basis')}}</label>
|
||||
</div>
|
||||
<div>
|
||||
<input ng-model="assessSched.mode" type="radio" name="assess" value="at" id="schedule-assess-at"/>
|
||||
<label for="schedule-assess-at">{{ts('Assess A/B test at:')}}</label>
|
||||
<input crm-ui-datepicker ng-model="assessSched.datetime"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
32
sites/all/modules/civicrm/ang/crmMailingAB/BlockSetup.js
Normal file
32
sites/all/modules/civicrm/ang/crmMailingAB/BlockSetup.js
Normal file
|
@ -0,0 +1,32 @@
|
|||
(function(angular, $, _) {
|
||||
|
||||
// example:
|
||||
// scope.myAbtest = new CrmMailingAB();
|
||||
// <crm-mailing-ab-block-setup="{abName: 1, group_percentage: 1}" crm-abtest="myAbtest" />
|
||||
var simpleDirectives = {
|
||||
crmMailingAbBlockSetup: '~/crmMailingAB/BlockSetup.html'
|
||||
};
|
||||
_.each(simpleDirectives, function(templateUrl, directiveName) {
|
||||
angular.module('crmMailingAB').directive(directiveName, function($parse, crmMailingABCriteria, crmUiHelp) {
|
||||
var scopeDesc = {crmAbtest: '@'};
|
||||
scopeDesc[directiveName] = '@';
|
||||
|
||||
return {
|
||||
scope: scopeDesc,
|
||||
templateUrl: templateUrl,
|
||||
link: function(scope, elm, attr) {
|
||||
var model = $parse(attr.crmAbtest);
|
||||
scope.abtest = model(scope.$parent);
|
||||
scope.crmMailingConst = CRM.crmMailing;
|
||||
scope.crmMailingABCriteria = crmMailingABCriteria;
|
||||
scope.ts = CRM.ts(null);
|
||||
scope.hs = crmUiHelp({file: 'CRM/Mailing/MailingUI'});
|
||||
|
||||
var fieldsModel = $parse(attr[directiveName]);
|
||||
scope.fields = fieldsModel(scope.$parent);
|
||||
}
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
})(angular, CRM.$, CRM._);
|
149
sites/all/modules/civicrm/ang/crmMailingAB/EditCtrl.js
Normal file
149
sites/all/modules/civicrm/ang/crmMailingAB/EditCtrl.js
Normal file
|
@ -0,0 +1,149 @@
|
|||
(function(angular, $, _) {
|
||||
|
||||
angular.module('crmMailingAB').controller('CrmMailingABEditCtrl', function($scope, abtest, crmMailingABCriteria, crmMailingMgr, crmMailingPreviewMgr, crmStatus, $q, $location, crmBlocker, $interval, $timeout, CrmAutosaveCtrl, dialogService) {
|
||||
$scope.abtest = abtest;
|
||||
var ts = $scope.ts = CRM.ts(null);
|
||||
var block = $scope.block = crmBlocker();
|
||||
$scope.crmUrl = CRM.url;
|
||||
var myAutosave = null;
|
||||
$scope.crmMailingABCriteria = crmMailingABCriteria;
|
||||
$scope.crmMailingConst = CRM.crmMailing;
|
||||
$scope.checkPerm = CRM.checkPerm;
|
||||
|
||||
$scope.isSubmitted = function isSubmitted() {
|
||||
return _.size(abtest.mailings.a.jobs) > 0 || _.size(abtest.mailings.b.jobs) > 0;
|
||||
};
|
||||
|
||||
$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('Final (%1)', {1: abtest.ab.name});
|
||||
|
||||
if (abtest.ab.testing_criteria) {
|
||||
// TODO review fields exposed in UI and make sure the sync rules match
|
||||
switch (abtest.ab.testing_criteria) {
|
||||
case 'subject':
|
||||
var exclude_subject = [
|
||||
'name',
|
||||
'recipients',
|
||||
'subject'
|
||||
];
|
||||
crmMailingMgr.mergeInto(abtest.mailings.b, abtest.mailings.a, exclude_subject);
|
||||
crmMailingMgr.mergeInto(abtest.mailings.c, abtest.mailings.a, exclude_subject);
|
||||
break;
|
||||
case 'from':
|
||||
var exclude_from = [
|
||||
'name',
|
||||
'recipients',
|
||||
'from_name',
|
||||
'from_email'
|
||||
];
|
||||
crmMailingMgr.mergeInto(abtest.mailings.b, abtest.mailings.a, exclude_from);
|
||||
crmMailingMgr.mergeInto(abtest.mailings.c, abtest.mailings.a, exclude_from);
|
||||
break;
|
||||
case 'full_email':
|
||||
var exclude_full_email = [
|
||||
'name',
|
||||
'recipients',
|
||||
'subject',
|
||||
'from_name',
|
||||
'from_email',
|
||||
'replyto_email',
|
||||
'override_verp', // keep override_verp and replyto_Email linked
|
||||
'body_html',
|
||||
'body_text'
|
||||
];
|
||||
crmMailingMgr.mergeInto(abtest.mailings.b, abtest.mailings.a, exclude_full_email);
|
||||
crmMailingMgr.mergeInto(abtest.mailings.c, abtest.mailings.a, exclude_full_email);
|
||||
break;
|
||||
default:
|
||||
throw "Unrecognized testing_criteria";
|
||||
}
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
// @return Promise
|
||||
$scope.save = function save() {
|
||||
return block(crmStatus({start: ts('Saving...'), success: ts('Saved')}, abtest.save()));
|
||||
};
|
||||
|
||||
// @return Promise
|
||||
$scope.previewMailing = function previewMailing(mailingName, mode) {
|
||||
return crmMailingPreviewMgr.preview(abtest.mailings[mailingName], mode);
|
||||
};
|
||||
|
||||
// @return Promise
|
||||
$scope.sendTest = function sendTest(mailingName, recipient) {
|
||||
return block(crmStatus({start: ts('Saving...'), success: ''}, abtest.save())
|
||||
.then(function() {
|
||||
crmMailingPreviewMgr.sendTest(abtest.mailings[mailingName], recipient);
|
||||
}));
|
||||
};
|
||||
|
||||
// @return Promise
|
||||
$scope.delete = function() {
|
||||
return block(crmStatus({start: ts('Deleting...'), success: ts('Deleted')}, abtest.delete().then($scope.leave)));
|
||||
};
|
||||
|
||||
// @return Promise
|
||||
$scope.submit = function submit() {
|
||||
if (block.check() || $scope.crmMailingAB.$invalid) {
|
||||
return;
|
||||
}
|
||||
return block(crmStatus({start: ts('Saving...'), success: ''}, abtest.save())
|
||||
.then(function() {
|
||||
return crmStatus({
|
||||
start: ts('Submitting...'),
|
||||
success: ts('Submitted')
|
||||
}, myAutosave.suspend(abtest.submitTest()));
|
||||
// Note: We're going to leave, so we don't care that submit() modifies several server-side records.
|
||||
// If we stayed on this page, then we'd care about updating and call: abtest.submitTest().then(...abtest.load()...)
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
$scope.leave = function leave() {
|
||||
$location.path('abtest');
|
||||
$location.replace();
|
||||
};
|
||||
|
||||
$scope.selectWinner = function selectWinner(mailingName) {
|
||||
var model = {
|
||||
abtest: $scope.abtest,
|
||||
mailingName: mailingName
|
||||
};
|
||||
var options = CRM.utils.adjustDialogDefaults({
|
||||
autoOpen: false,
|
||||
height: 'auto',
|
||||
width: '40%',
|
||||
title: ts('Select Final Mailing (Test %1)', {
|
||||
1: mailingName.toUpperCase()
|
||||
})
|
||||
});
|
||||
return myAutosave.suspend(dialogService.open('selectWinnerDialog', '~/crmMailingAB/WinnerDialogCtrl.html', model, options));
|
||||
};
|
||||
|
||||
// initialize
|
||||
var syncJob = $interval($scope.sync, 333);
|
||||
$scope.$on('$destroy', function() {
|
||||
$interval.cancel(syncJob);
|
||||
});
|
||||
|
||||
myAutosave = new CrmAutosaveCtrl({
|
||||
save: $scope.save,
|
||||
saveIf: function() {
|
||||
return abtest.ab.status == 'Draft' && $scope.sync();
|
||||
},
|
||||
model: function() {
|
||||
return abtest.getAutosaveSignature();
|
||||
},
|
||||
form: function() {
|
||||
return $scope.crmMailingAB;
|
||||
}
|
||||
});
|
||||
$timeout(myAutosave.start);
|
||||
$scope.$on('$destroy', myAutosave.stop);
|
||||
});
|
||||
|
||||
})(angular, CRM.$, CRM._);
|
178
sites/all/modules/civicrm/ang/crmMailingAB/EditCtrl/edit.html
Normal file
178
sites/all/modules/civicrm/ang/crmMailingAB/EditCtrl/edit.html
Normal file
|
@ -0,0 +1,178 @@
|
|||
<!--
|
||||
Implicit Controller: CrmMailingABEditCtrl
|
||||
|
||||
An ABTest includes two mailings, but we don't require the user to enter two complete mailings. For
|
||||
simplicity, the email composition UI generally displays A (unless we specifically decided to expose an
|
||||
individual field from B). At the end of the composition process, the controller's "sync" operation will
|
||||
merge shared settings from "A" into "B".
|
||||
-->
|
||||
<div ng-form="crmMailingABEdit">
|
||||
<div class="crm-block crm-form-block crmMailing">
|
||||
<div crm-ui-wizard>
|
||||
<div crm-ui-wizard-step="10" crm-title="ts('Setup')" ng-form="setupForm">
|
||||
<div
|
||||
crm-mailing-ab-block-setup="{
|
||||
help: 1,
|
||||
abName: 1,
|
||||
campaign: 1,
|
||||
testing_criteria: 1
|
||||
}"
|
||||
crm-abtest="abtest"></div>
|
||||
</div>
|
||||
<div crm-ui-wizard-step="11" crm-title="ts('Target')" ng-form="targetForm">
|
||||
<div
|
||||
crm-mailing-ab-block-setup="{
|
||||
recipients: 1,
|
||||
group_percentage: 1
|
||||
}"
|
||||
crm-abtest="abtest"></div>
|
||||
</div>
|
||||
<div crm-ui-wizard-step="20" crm-title="ts('Compose')" ng-if="abtest.ab.testing_criteria != 'full_email'" ng-form="composeForm">
|
||||
<div crm-ui-tab-set>
|
||||
<div crm-ui-tab id="tab-mailing" crm-title="ts('Mailing')">
|
||||
<div
|
||||
ng-if="abtest.ab.testing_criteria == 'from'"
|
||||
crm-mailing-ab-block-mailing="{
|
||||
msg_template_id: 1,
|
||||
fromAddressA: 1,
|
||||
fromAddressB: 1,
|
||||
subject: 1
|
||||
}"
|
||||
crm-abtest="abtest"></div>
|
||||
<div
|
||||
ng-if="abtest.ab.testing_criteria == 'subject'"
|
||||
crm-mailing-ab-block-mailing="{
|
||||
msg_template_id: 1,
|
||||
fromAddress: 1,
|
||||
replyTo: 1,
|
||||
subjectA: 1,
|
||||
subjectB: 1
|
||||
}"
|
||||
crm-abtest="abtest"></div>
|
||||
<div crm-ui-accordion="{title: ts('HTML')}">
|
||||
<div crm-mailing-body-html crm-mailing="abtest.mailings.a"></div>
|
||||
</div>
|
||||
<div crm-ui-accordion="{title: ts('Plain Text'), collapsed: !abtest.mailings.a.body_text}">
|
||||
<div crm-mailing-body-text crm-mailing="abtest.mailings.a"></div>
|
||||
</div>
|
||||
</div>
|
||||
<!--
|
||||
FIXME: Attachment UI works, but we haven't implemented backend logic for copying/sharing
|
||||
of attachments among mailings A/B/C.
|
||||
<div crm-ui-tab id="tab-attachment" crm-title="ts('Attachments')">
|
||||
<div crm-attachments="abtest.attachments.a"></div>
|
||||
</div>
|
||||
-->
|
||||
<div crm-ui-tab id="tab-header" crm-title="ts('Header and Footer')">
|
||||
<div crm-mailing-block-header-footer crm-mailing="abtest.mailings.a"></div>
|
||||
</div>
|
||||
<div crm-ui-tab id="tab-pub" crm-title="ts('Publication')">
|
||||
<div crm-mailing-block-publication crm-mailing="abtest.mailings.a"></div>
|
||||
</div>
|
||||
<div crm-ui-tab id="tab-response" crm-title="ts('Responses')">
|
||||
<div crm-mailing-block-responses crm-mailing="abtest.mailings.a"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div crm-ui-accordion="{title: ts('Preview (A)')}">
|
||||
<div crm-mailing-block-preview crm-mailing="abtest.mailings.a" on-preview="previewMailing('a', preview.mode)" on-send="sendTest('a', preview.recipient)"></div>
|
||||
</div>
|
||||
<div crm-ui-accordion="{title: ts('Preview (B)')}">
|
||||
<div crm-mailing-block-preview crm-mailing="abtest.mailings.b" on-preview="previewMailing('b', preview.mode)" on-send="sendTest('b', preview.recipient)"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div crm-ui-wizard-step="21" crm-title="ts('Compose (A)')" ng-if="abtest.ab.testing_criteria == 'full_email'" ng-form="composeAForm">
|
||||
<div crm-ui-tab-set>
|
||||
<div crm-ui-tab id="tab-mailingA" crm-title="ts('Mailing')">
|
||||
<div
|
||||
crm-mailing-ab-block-mailing="{
|
||||
msg_template_idA: 1,
|
||||
fromAddressA: 1,
|
||||
replyToA: 1,
|
||||
subjectA: 1
|
||||
}"
|
||||
crm-abtest="abtest"></div>
|
||||
<div crm-ui-accordion="{title: ts('HTML')}">
|
||||
<div crm-mailing-body-html crm-mailing="abtest.mailings.a"></div>
|
||||
</div>
|
||||
<div crm-ui-accordion="{title: ts('Plain Text'), collapsed: !abtest.mailings.a.body_text}">
|
||||
<div crm-mailing-body-text crm-mailing="abtest.mailings.a"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div crm-ui-tab id="tab-attachmentA" crm-title="ts('Attachments')">
|
||||
<div crm-attachments="abtest.attachments.a"></div>
|
||||
</div>
|
||||
<div crm-ui-tab id="tab-headerA" crm-title="ts('Header and Footer')">
|
||||
<div crm-mailing-block-header-footer crm-mailing="abtest.mailings.a"></div>
|
||||
</div>
|
||||
<div crm-ui-tab id="tab-pubA" crm-title="ts('Publication')">
|
||||
<div crm-mailing-block-publication crm-mailing="abtest.mailings.a"></div>
|
||||
</div>
|
||||
<div crm-ui-tab id="tab-responseA" crm-title="ts('Responses')">
|
||||
<div crm-mailing-block-responses crm-mailing="abtest.mailings.a"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div crm-ui-accordion="{title: ts('Preview')}">
|
||||
<div crm-mailing-block-preview crm-mailing="abtest.mailings.a" on-preview="previewMailing('a', preview.mode)" on-send="sendTest('a', preview.recipient)"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div crm-ui-wizard-step="22" crm-title="ts('Compose (B)')" ng-if="abtest.ab.testing_criteria == 'full_email'" ng-form="composeBForm">
|
||||
<div crm-ui-tab-set>
|
||||
<div crm-ui-tab id="tab-mailingB" crm-title="ts('Mailing')">
|
||||
<div
|
||||
crm-mailing-ab-block-mailing="{
|
||||
msg_template_idB: 1,
|
||||
fromAddressB: 1,
|
||||
replyToB: 1,
|
||||
subjectB: 1
|
||||
}"
|
||||
crm-abtest="abtest"></div>
|
||||
<div crm-ui-accordion="{title: ts('HTML')}">
|
||||
<div crm-mailing-body-html crm-mailing="abtest.mailings.b"></div>
|
||||
</div>
|
||||
<div crm-ui-accordion="{title: ts('Plain Text'), collapsed: !abtest.mailings.b.body_text}">
|
||||
<div crm-mailing-body-text crm-mailing="abtest.mailings.b"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div crm-ui-tab id="tab-attachmentB" crm-title="ts('Attachments')">
|
||||
<div crm-attachments="abtest.attachments.b"></div>
|
||||
</div>
|
||||
<div crm-ui-tab id="tab-headerB" crm-title="ts('Header and Footer')">
|
||||
<div crm-mailing-block-header-footer crm-mailing="abtest.mailings.b"></div>
|
||||
</div>
|
||||
<div crm-ui-tab id="tab-pubB" crm-title="ts('Publication')">
|
||||
<div crm-mailing-block-publication crm-mailing="abtest.mailings.b"></div>
|
||||
</div>
|
||||
<div crm-ui-tab id="tab-responseB" crm-title="ts('Responses')">
|
||||
<div crm-mailing-block-responses crm-mailing="abtest.mailings.b"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div crm-ui-accordion="{title: ts('Preview')}">
|
||||
<div crm-mailing-block-preview crm-mailing="abtest.mailings.b" on-preview="previewMailing('b', preview.mode)" on-send="sendTest('b', preview.recipient)"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div crm-ui-wizard-step="30" crm-title="ts('Schedule')" ng-form="schedForm">
|
||||
<div
|
||||
crm-mailing-ab-block-setup="{
|
||||
scheduled_date: 1,
|
||||
declare_winning_time: 1
|
||||
}"
|
||||
crm-abtest="abtest"></div>
|
||||
<center>
|
||||
<a class="button crmMailing-submit-button" ng-click="submit()" ng-class="{blocking: block.check(), disabled: crmMailingAB.$invalid}">
|
||||
<div>{{ts('Submit Mailing')}}</div>
|
||||
</a>
|
||||
</center>
|
||||
</div>
|
||||
<span crm-ui-wizard-buttons style="float:right;">
|
||||
<button
|
||||
crm-icon="fa-trash"
|
||||
ng-show="checkPerm('delete in CiviMail')"
|
||||
ng-disabled="block.check()"
|
||||
crm-confirm="{title:ts('Delete Draft'), message:ts('Are you sure you want to permanently delete this mailing?')}"
|
||||
on-yes="delete()">{{ts('Delete Draft')}}
|
||||
</button>
|
||||
<button crm-icon="fa-floppy-o" ng-disabled="block.check()" ng-click="save().then(leave) ">{{ts('Save Draft')}}</button>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
|
@ -0,0 +1,10 @@
|
|||
<!--
|
||||
Implicit Controller: CrmMailingABEditCtrl
|
||||
-->
|
||||
<div crm-ui-debug="abtest.ab"></div>
|
||||
<div crm-ui-debug="abtest.mailings"></div>
|
||||
|
||||
<form name="crmMailingAB" novalidate>
|
||||
<div ng-include="'~/crmMailingAB/EditCtrl/edit.html'" ng-if="!isSubmitted()"></div>
|
||||
<div ng-include="'~/crmMailingAB/EditCtrl/report.html'" ng-if="isSubmitted()"></div>
|
||||
</form>
|
194
sites/all/modules/civicrm/ang/crmMailingAB/EditCtrl/report.html
Normal file
194
sites/all/modules/civicrm/ang/crmMailingAB/EditCtrl/report.html
Normal file
|
@ -0,0 +1,194 @@
|
|||
<!--
|
||||
Implicit Controller: CrmMailingABEditCtrl
|
||||
-->
|
||||
<div class="messages help">
|
||||
<div class="msg-title crm-title">{{ts('A/B Test Results')}}: {{abtest.ab.name}}</div>
|
||||
{{ts('This report displays the current results for your A/B test. You can return to this page to view the latest statistics by navigating to "Manage A/B Tests" and clicking "Results".')}}
|
||||
</div>
|
||||
<div ng-controller="CrmMailingABReportCtrl">
|
||||
<table class="crm-mailing-ab-table">
|
||||
<thead>
|
||||
<tr ng-show="abtest.ab.status == 'Testing'">
|
||||
<td></td>
|
||||
<td ng-repeat="am in getActiveMailings()">
|
||||
<button crm-icon="fa-trophy" ng-click="selectWinner(am.name)">{{ts('Select as Final')}}</button>
|
||||
</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{{ts('Delivery')}}</th>
|
||||
<th ng-repeat="am in getActiveMailings()" class="crm-mailing-ab-col">{{am.title}}</th>
|
||||
<th ng-show="abtest.ab.status == 'Testing'">{{ts('Final')}}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>{{ts('Status')}}</td>
|
||||
<td ng-repeat="am in getActiveMailings()">
|
||||
<span ng-repeat="job in am.mailing.jobs" ng-hide="job.is_test == 1 || job.parent_id != null">{{job.status}}</span>
|
||||
</td>
|
||||
<td ng-show="abtest.ab.status == 'Testing'">{{ts('Not selected')}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ts('Scheduled')}}</td>
|
||||
<td ng-repeat="am in getActiveMailings()">
|
||||
<div ng-repeat="job in am.mailing.jobs" ng-hide="job.is_test == 1 || job.parent_id != null">{{job.scheduled_date}}</div>
|
||||
</td>
|
||||
<td ng-show="abtest.ab.status == 'Testing'"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ts('Started at')}}</td>
|
||||
<td ng-repeat="am in getActiveMailings()">
|
||||
<div ng-repeat="job in am.mailing.jobs" ng-hide="job.is_test == 1 || job.parent_id != null">{{job.start_date || ts('Not started')}}</div>
|
||||
</td>
|
||||
<td ng-show="abtest.ab.status == 'Testing'"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ts('Completed at')}}</td>
|
||||
<td ng-repeat="am in getActiveMailings()">
|
||||
<div ng-repeat="job in am.mailing.jobs" ng-hide="job.is_test == 1 || job.parent_id != null">{{job.end_date || ts('Not completed')}}</div>
|
||||
</td>
|
||||
<td ng-show="abtest.ab.status == 'Testing'"></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{{ts('Performance')}}</th>
|
||||
<th ng-repeat="am in getActiveMailings()" class="crm-mailing-ab-col">{{am.title}}</th>
|
||||
<th ng-show="abtest.ab.status == 'Testing'">{{ts('Final')}}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr ng-repeat="statType in statTypes">
|
||||
<td>{{statType.title}}</td>
|
||||
<td ng-repeat="am in getActiveMailings()">
|
||||
<a
|
||||
class="crm-hover-button action-item"
|
||||
ng-href="{{statUrl(am.mailing, statType, 'search')}}"
|
||||
ng-if="checkPerm('view all contacts') || checkPerm('edit all contacts')"
|
||||
title="{{ts('Search for contacts using \'%1\'', {1: statType.title})}}"
|
||||
crm-icon="fa-search"
|
||||
></a>
|
||||
<a
|
||||
class="crm-hover-button action-item"
|
||||
ng-href="{{statUrl(am.mailing, statType, 'events')}}"
|
||||
title="{{ts('Browse events of type \'%1\'', {1: statType.title})}}"
|
||||
>{{stats[am.name][statType.name] || ts('n/a')}}</a>
|
||||
<a
|
||||
class="crm-hover-button action-item"
|
||||
ng-href="{{statUrl(am.mailing, statType, 'report')}}"
|
||||
title="{{ts('Reports for \'%1\'', {1: statType.title})}}"
|
||||
crm-icon="clipboard"
|
||||
></a>
|
||||
</td>
|
||||
<td ng-show="abtest.ab.status == 'Testing'"></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{{ts('Details')}}</th>
|
||||
<th ng-repeat="am in getActiveMailings()" class="crm-mailing-ab-col">{{am.title}}</th>
|
||||
<th ng-show="abtest.ab.status == 'Testing'">{{ts('Final')}}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>{{ts('Mailing Name')}}</td>
|
||||
<td ng-repeat="am in getActiveMailings()">
|
||||
{{am.mailing.name}}
|
||||
</td>
|
||||
<td ng-show="abtest.ab.status == 'Testing'"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ts('From')}}</td>
|
||||
<td ng-repeat="am in getActiveMailings()">
|
||||
"{{am.mailing.from_name}}" <{{am.mailing.from_email}}>
|
||||
</td>
|
||||
<td ng-show="abtest.ab.status == 'Testing'"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ts('Subject')}}</td>
|
||||
<td ng-repeat="am in getActiveMailings()">
|
||||
{{am.mailing.subject}}
|
||||
</td>
|
||||
<td ng-show="abtest.ab.status == 'Testing'"></td>
|
||||
</tr>
|
||||
<tr ng-controller="ViewRecipCtrl">
|
||||
<td>{{ts('Recipients')}}</td>
|
||||
<td ng-repeat="am in getActiveMailings()">
|
||||
<div ng-show="getIncludesAsString(am.mailing)">
|
||||
<strong>{{ts('Include:')}}</strong> {{getIncludesAsString(am.mailing)}}
|
||||
</div>
|
||||
<div ng-show="getExcludesAsString(am.mailing)">
|
||||
<strong>{{ts('Exclude:')}}</strong> <s>{{getExcludesAsString(am.mailing)}}</s>
|
||||
</div>
|
||||
</td>
|
||||
<td ng-show="abtest.ab.status == 'Testing'"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ts('Content')}}</td>
|
||||
<td ng-repeat="am in getActiveMailings()">
|
||||
<a crm-icon="fa-television" class="crm-hover-button action-item" ng-click="previewMailing(am.name,'html')" ng-show="am.mailing.body_html">{{ts('HTML')}}</a>
|
||||
<a crm-icon="fa-file-text-o" class="crm-hover-button action-item" ng-click="previewMailing(am.name,'text')" ng-show="am.mailing.body_text">{{ts('Text')}}</a>
|
||||
</td>
|
||||
<td ng-show="abtest.ab.status == 'Testing'"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ts('Attachments')}}</td>
|
||||
<td ng-repeat="am in getActiveMailings()">
|
||||
<div ng-repeat="file in am.attachments.files"><a ng-href="{{file.url}}" target="_blank">{{file.name}}</a></div>
|
||||
</td>
|
||||
<td ng-show="abtest.ab.status == 'Testing'"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ts('Tracking')}}</td>
|
||||
<td ng-repeat="am in getActiveMailings()">
|
||||
<div crm-mailing-review-bool crm-on="am.mailing.url_tracking=='1'" crm-title="ts('Click-Throughs')"></div>
|
||||
<div crm-mailing-review-bool crm-on="am.mailing.open_tracking=='1'" crm-title="ts('Opens')"></div>
|
||||
</td>
|
||||
<td ng-show="abtest.ab.status == 'Testing'"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ts('Responding')}}</td>
|
||||
<td ng-repeat="am in getActiveMailings()">
|
||||
<div crm-mailing-review-bool crm-on="am.mailing.override_verp=='0'" crm-title="ts('Track Replies')"></div>
|
||||
<div crm-mailing-review-bool crm-on="am.mailing.override_verp=='0' && mailing.forward_replies=='1'" crm-title="ts('Forward Replies')"></div>
|
||||
<div ng-controller="PreviewComponentCtrl">
|
||||
<div ng-show="am.mailing.override_verp == '0' && mailing.auto_responder"><a crm-icon="fa-envelope" class="crm-hover-button action-item" ng-click="previewComponent(ts('Auto-Respond'), am.mailing.reply_id)">{{ts('Auto-Respond')}}</a></div>
|
||||
<div><a crm-icon="fa-envelope" class="crm-hover-button action-item" ng-click="previewComponent(ts('Opt-out'), am.mailing.optout_id)">{{ts('Opt-out')}}</a></div>
|
||||
<div><a crm-icon="fa-envelope" class="crm-hover-button action-item" ng-click="previewComponent(ts('Resubscribe'), am.mailing.resubscribe_id)">{{ts('Resubscribe')}}</a></div>
|
||||
<div><a crm-icon="fa-envelope" class="crm-hover-button action-item" ng-click="previewComponent(ts('Unsubscribe'), am.mailing.unsubscribe_id)">{{ts('Unsubscribe')}}</a></div>
|
||||
</div>
|
||||
</td>
|
||||
<td ng-show="abtest.ab.status == 'Testing'"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ts('Publication')}}</td>
|
||||
<td ng-repeat="am in getActiveMailings()">
|
||||
{{am.mailing.visibility}}
|
||||
</td>
|
||||
<td ng-show="abtest.ab.status == 'Testing'"></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
|
||||
</table>
|
||||
|
||||
<!--
|
||||
<div crm-ui-tab-set>
|
||||
<div crm-ui-tab id="tab-opens" crm-title="ts('Opens (WIP)')">
|
||||
<div crm-mailing-ab-stats="{criteria: 'open', split_count: 5}" crm-abtest="abtest"></div>
|
||||
</div>
|
||||
<div crm-ui-tab id="tab-clicks" crm-title="ts('Total Clicks (WIP)')">
|
||||
<div crm-mailing-ab-stats="{criteria: 'total unique clicks', split_count: 5}" crm-abtest="abtest"></div>
|
||||
</div>
|
||||
</div>
|
||||
-->
|
||||
|
||||
</div>
|
63
sites/all/modules/civicrm/ang/crmMailingAB/ListCtrl.html
Normal file
63
sites/all/modules/civicrm/ang/crmMailingAB/ListCtrl.html
Normal file
|
@ -0,0 +1,63 @@
|
|||
<!--
|
||||
Controller: ABListingCtrl
|
||||
Required vars: mailingABList
|
||||
-->
|
||||
|
||||
<span crm-ui-order="{var: 'myOrder', defaults: ['-created_date']}"></span>
|
||||
|
||||
<div crm-ui-accordion="{title: ts('Filter'), collapsed: true}">
|
||||
<form name="filterForm">
|
||||
<span>
|
||||
<input class="big crm-form-text" ng-model="filter.name" placeholder="{{ts('Name')}}"/>
|
||||
</span>
|
||||
<span>
|
||||
<select crm-ui-select style="width: 10em;" ng-model="filter.status">
|
||||
<option value="">{{ts('- Status -')}}</option>
|
||||
<option ng-repeat="o in fields.status.options" ng-value="o.key">{{o.value}}</option>
|
||||
</select>
|
||||
</span>
|
||||
<span>
|
||||
<select crm-ui-select style="width: 20em;" ng-model="filter.testing_criteria">
|
||||
<option value="">{{ts('- Test Type -')}}</option>
|
||||
<option ng-repeat="o in fields.testing_criteria.options" ng-value="o.key">{{o.value}}</option>
|
||||
</select>
|
||||
</span>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div ng-show="mailingABList.length">
|
||||
<table class="display">
|
||||
<thead>
|
||||
<tr>
|
||||
<th><a crm-ui-order-by="[myOrder, 'name']">{{ts('Name')}}</a></th>
|
||||
<th><a crm-ui-order-by="[myOrder, 'status']">{{ts('Status')}}</a></th>
|
||||
<th><a crm-ui-order-by="[myOrder, 'testing_criteria']">{{ts('Test Type')}}</a></th>
|
||||
<th><a crm-ui-order-by="[myOrder, 'created_date']">{{ts('Created')}}</a></th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr ng-repeat="mailingAB in mailingABList | filter:filter | orderBy:myOrder.get()">
|
||||
<td>{{mailingAB.name}}</td>
|
||||
<td>{{crmMailingABStatus.getByName(mailingAB.status).label}}</td>
|
||||
<td>{{crmMailingABCriteria.get(mailingAB.testing_criteria).label}}</td>
|
||||
<td>{{mailingAB.created_date}}</td>
|
||||
<td>
|
||||
<a class="action-item crm-hover-button" ng-href="#/abtest/{{mailingAB.id}}" ng-show="mailingAB.status == 'Draft'">{{ts('Continue')}}</a>
|
||||
<a class="action-item crm-hover-button" ng-href="#/abtest/{{mailingAB.id}}" ng-show="mailingAB.status != 'Draft'">{{ts('Results')}}</a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div ng-show="mailingABList.length === 0" class="messages status no-popup">
|
||||
<i class="crm-i fa-info-circle"></i>
|
||||
{{ts('You have no A/B mailings')}}
|
||||
</div>
|
||||
|
||||
|
||||
<div class="crm-submit-buttons">
|
||||
<br>
|
||||
<a ng-href="#/abtest/new" class="button"><span><i class="crm-i fa-bar-chart"></i> {{ts('New A/B Test')}}</span></a>
|
||||
</div>
|
12
sites/all/modules/civicrm/ang/crmMailingAB/ListCtrl.js
Normal file
12
sites/all/modules/civicrm/ang/crmMailingAB/ListCtrl.js
Normal file
|
@ -0,0 +1,12 @@
|
|||
(function(angular, $, _) {
|
||||
|
||||
angular.module('crmMailingAB').controller('CrmMailingABListCtrl', function($scope, mailingABList, crmMailingABCriteria, crmMailingABStatus, fields) {
|
||||
var ts = $scope.ts = CRM.ts(null);
|
||||
$scope.mailingABList = _.values(mailingABList.values);
|
||||
$scope.crmMailingABCriteria = crmMailingABCriteria;
|
||||
$scope.crmMailingABStatus = crmMailingABStatus;
|
||||
$scope.fields = fields;
|
||||
$scope.filter = {};
|
||||
});
|
||||
|
||||
})(angular, CRM.$, CRM._);
|
11
sites/all/modules/civicrm/ang/crmMailingAB/NewCtrl.js
Normal file
11
sites/all/modules/civicrm/ang/crmMailingAB/NewCtrl.js
Normal file
|
@ -0,0 +1,11 @@
|
|||
(function(angular, $, _) {
|
||||
|
||||
angular.module('crmMailingAB').controller('CrmMailingABNewCtrl', function($scope, abtest, $location) {
|
||||
// Transition URL "/abtest/new/foo" => "/abtest/123/foo"
|
||||
var parts = $location.path().split('/'); // e.g. "/mailing/new" or "/mailing/123/wizard"
|
||||
parts[2] = abtest.id;
|
||||
$location.path(parts.join('/'));
|
||||
$location.replace();
|
||||
});
|
||||
|
||||
})(angular, CRM.$, CRM._);
|
52
sites/all/modules/civicrm/ang/crmMailingAB/ReportCtrl.js
Normal file
52
sites/all/modules/civicrm/ang/crmMailingAB/ReportCtrl.js
Normal file
|
@ -0,0 +1,52 @@
|
|||
(function(angular, $, _) {
|
||||
|
||||
angular.module('crmMailingAB').controller('CrmMailingABReportCtrl', function($scope, crmApi, crmMailingStats) {
|
||||
var ts = $scope.ts = CRM.ts(null);
|
||||
|
||||
var CrmMailingABReportCnt = 1, activeMailings = null;
|
||||
$scope.getActiveMailings = function() {
|
||||
if ($scope.abtest.$CrmMailingABReportCnt != CrmMailingABReportCnt) {
|
||||
$scope.abtest.$CrmMailingABReportCnt = ++CrmMailingABReportCnt;
|
||||
activeMailings = [
|
||||
{
|
||||
name: 'a',
|
||||
title: ts('Mailing A'),
|
||||
mailing: $scope.abtest.mailings.a,
|
||||
attachments: $scope.abtest.attachments.a
|
||||
},
|
||||
{
|
||||
name: 'b',
|
||||
title: ts('Mailing B'),
|
||||
mailing: $scope.abtest.mailings.b,
|
||||
attachments: $scope.abtest.attachments.b
|
||||
}
|
||||
];
|
||||
if ($scope.abtest.ab.status == 'Final') {
|
||||
activeMailings.push({
|
||||
name: 'c',
|
||||
title: ts('Final'),
|
||||
mailing: $scope.abtest.mailings.c,
|
||||
attachments: $scope.abtest.attachments.c
|
||||
});
|
||||
}
|
||||
}
|
||||
return activeMailings;
|
||||
};
|
||||
|
||||
crmMailingStats.getStats({
|
||||
a: $scope.abtest.ab.mailing_id_a,
|
||||
b: $scope.abtest.ab.mailing_id_b,
|
||||
c: $scope.abtest.ab.mailing_id_c
|
||||
}).then(function(stats) {
|
||||
$scope.stats = stats;
|
||||
});
|
||||
|
||||
$scope.statTypes = crmMailingStats.getStatTypes();
|
||||
$scope.statUrl = function statUrl(mailing, statType, view) {
|
||||
return crmMailingStats.getUrl(mailing, statType, view, 'abtest/' + $scope.abtest.ab.id);
|
||||
};
|
||||
|
||||
$scope.checkPerm = CRM.checkPerm;
|
||||
});
|
||||
|
||||
})(angular, CRM.$, CRM._);
|
25
sites/all/modules/civicrm/ang/crmMailingAB/Slider.html
Normal file
25
sites/all/modules/civicrm/ang/crmMailingAB/Slider.html
Normal file
|
@ -0,0 +1,25 @@
|
|||
<table class="crm-mailing-ab-slider">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="width: 10em;">{{ts('Test Mailing A')}}</td>
|
||||
<td>
|
||||
<div class="slider-test slider-a"></div>
|
||||
</td>
|
||||
<td style="width: 5em;">({{testValue}}%)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ts('Test Mailing B')}}</td>
|
||||
<td>
|
||||
<div class="slider-test slider-b"></div>
|
||||
</td>
|
||||
<td>({{testValue}}%)</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
<tr>
|
||||
<td>{{ts('Final Mailing')}}</td>
|
||||
<td>
|
||||
<div class="slider-win slider-b"></div>
|
||||
</td>
|
||||
<td>({{winValue}}%)</td>
|
||||
</tr>
|
||||
</table>
|
60
sites/all/modules/civicrm/ang/crmMailingAB/Slider.js
Normal file
60
sites/all/modules/civicrm/ang/crmMailingAB/Slider.js
Normal file
|
@ -0,0 +1,60 @@
|
|||
(function(angular, $, _) {
|
||||
|
||||
// example: <div crm-mailing-ab-slider ng-model="abtest.ab.group_percentage"></div>
|
||||
angular.module('crmMailingAB').directive('crmMailingAbSlider', function() {
|
||||
return {
|
||||
require: '?ngModel',
|
||||
scope: {},
|
||||
templateUrl: '~/crmMailingAB/Slider.html',
|
||||
link: function(scope, element, attrs, ngModel) {
|
||||
var TEST_MIN = 1, TEST_MAX = 50;
|
||||
var sliders = $('.slider-test,.slider-win', element);
|
||||
var sliderTests = $('.slider-test', element);
|
||||
var sliderWin = $('.slider-win', element);
|
||||
|
||||
scope.ts = CRM.ts(null);
|
||||
scope.testValue = 0;
|
||||
scope.winValue = 100;
|
||||
|
||||
// set the base value (following a GUI event)
|
||||
function setValue(value) {
|
||||
value = Math.min(TEST_MAX, Math.max(TEST_MIN, value));
|
||||
scope.$apply(function() {
|
||||
ngModel.$setViewValue(value);
|
||||
scope.testValue = value;
|
||||
scope.winValue = 100 - (2 * scope.testValue);
|
||||
sliderTests.slider('value', scope.testValue);
|
||||
sliderWin.slider('value', scope.winValue);
|
||||
});
|
||||
}
|
||||
|
||||
sliders.slider({
|
||||
min: 0,
|
||||
max: 100,
|
||||
range: 'min',
|
||||
step: 1
|
||||
});
|
||||
sliderTests.slider({
|
||||
slide: function slideTest(event, ui) {
|
||||
event.preventDefault();
|
||||
setValue(ui.value);
|
||||
}
|
||||
});
|
||||
sliderWin.slider({
|
||||
slide: function slideWinner(event, ui) {
|
||||
event.preventDefault();
|
||||
setValue(Math.round((100 - ui.value) / 2));
|
||||
}
|
||||
});
|
||||
|
||||
ngModel.$render = function() {
|
||||
scope.testValue = ngModel.$viewValue;
|
||||
scope.winValue = 100 - (2 * scope.testValue);
|
||||
sliderTests.slider('value', scope.testValue);
|
||||
sliderWin.slider('value', scope.winValue);
|
||||
};
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
})(angular, CRM.$, CRM._);
|
280
sites/all/modules/civicrm/ang/crmMailingAB/Stats.js
Normal file
280
sites/all/modules/civicrm/ang/crmMailingAB/Stats.js
Normal file
|
@ -0,0 +1,280 @@
|
|||
(function (angular, $, _) {
|
||||
|
||||
|
||||
// FIXME: This code is long and hasn't been fully working for me, but I've moved it into a spot
|
||||
// where it at least fits in a bit better.
|
||||
|
||||
// example: <div crm-mailing-ab-stats="{split_count: 6, criteria:'Open'}" crm-abtest="myabtest" />
|
||||
// options (see also: Mailing.graph_stats API)
|
||||
// - split_count: int
|
||||
// - criteria: string
|
||||
// - target_date: string, date
|
||||
// - target_url: string
|
||||
angular.module('crmMailingAB').directive('crmMailingAbStats', function (crmApi, $parse) {
|
||||
return {
|
||||
scope: {
|
||||
crmMailingAbStats: '@',
|
||||
crmAbtest: '@'
|
||||
},
|
||||
template: '<div class="crm-mailing-ab-stats"></div>',
|
||||
link: function (scope, element, attrs) {
|
||||
var abtestModel = $parse(attrs.crmAbtest);
|
||||
var optionModel = $parse(attrs.crmMailingAbStats);
|
||||
var options = angular.extend({}, optionModel(scope.$parent), {
|
||||
criteria: 'Open', // e.g. 'Open', 'Total Unique Clicks'
|
||||
split_count: 5
|
||||
});
|
||||
|
||||
scope.$watch(attrs.crmAbtest, refresh);
|
||||
function refresh() {
|
||||
var abtest = abtestModel(scope.$parent);
|
||||
if (!abtest) {
|
||||
console.log('failed to draw stats - missing abtest');
|
||||
return;
|
||||
}
|
||||
|
||||
scope.graph_data = [
|
||||
{},
|
||||
{},
|
||||
{},
|
||||
{},
|
||||
{}
|
||||
];
|
||||
var keep_cnt = 0;
|
||||
|
||||
for (var i = 1; i <= options.split_count; i++) {
|
||||
var result = crmApi('MailingAB', 'graph_stats', {
|
||||
id: abtest.ab.id,
|
||||
target_date: abtest.ab.declare_winning_time ? abtest.ab.declare_winning_time : 'now',
|
||||
target_url: null, // FIXME
|
||||
criteria: options.criteria,
|
||||
split_count: options.split_count,
|
||||
split_count_select: i
|
||||
});
|
||||
/*jshint -W083 */
|
||||
result.then(function (data) {
|
||||
var temp = 0;
|
||||
keep_cnt++;
|
||||
for (var key in data.values.A) {
|
||||
temp = key;
|
||||
}
|
||||
var t = data.values.A[temp].time.split(" ");
|
||||
var m = t[0];
|
||||
var year = t[2];
|
||||
var day = t[1].substr(0, t[1].length - 3);
|
||||
var t1, hur, hour, min;
|
||||
if (_.isEmpty(t[3])) {
|
||||
t1 = t[4].split(":");
|
||||
hur = t1[0];
|
||||
if (t[5] == "AM") {
|
||||
hour = hur;
|
||||
if (hour == 12) {
|
||||
hour = 0;
|
||||
}
|
||||
}
|
||||
if (t[5] == "PM") {
|
||||
hour = parseInt(hur) + 12;
|
||||
}
|
||||
min = t1[1];
|
||||
}
|
||||
else {
|
||||
t1 = t[3].split(":");
|
||||
hur = t1[0];
|
||||
if (t[4] == "AM") {
|
||||
hour = hur;
|
||||
if (hour == 12) {
|
||||
hour = 0;
|
||||
}
|
||||
}
|
||||
if (t[4] == "PM") {
|
||||
hour = parseInt(hur) + 12;
|
||||
}
|
||||
min = t1[1];
|
||||
}
|
||||
var month = 0;
|
||||
switch (m) {
|
||||
case "January":
|
||||
month = 0;
|
||||
break;
|
||||
case "February":
|
||||
month = 1;
|
||||
break;
|
||||
case "March":
|
||||
month = 2;
|
||||
break;
|
||||
case "April":
|
||||
month = 3;
|
||||
break;
|
||||
case "May":
|
||||
month = 4;
|
||||
break;
|
||||
case "June":
|
||||
month = 5;
|
||||
break;
|
||||
case "July":
|
||||
month = 6;
|
||||
break;
|
||||
case "August":
|
||||
month = 7;
|
||||
break;
|
||||
case "September":
|
||||
month = 8;
|
||||
break;
|
||||
case "October":
|
||||
month = 9;
|
||||
break;
|
||||
case "November":
|
||||
month = 10;
|
||||
break;
|
||||
case "December":
|
||||
month = 11;
|
||||
break;
|
||||
|
||||
}
|
||||
var tp = new Date(year, month, day, hour, min, 0, 0);
|
||||
scope.graph_data[temp - 1] = {
|
||||
time: tp,
|
||||
x: data.values.A[temp].count,
|
||||
y: data.values.B[temp].count
|
||||
};
|
||||
|
||||
if (keep_cnt == options.split_count) {
|
||||
scope.graphload = true;
|
||||
data = scope.graph_data;
|
||||
|
||||
// set up a colour variable
|
||||
var color = d3.scale.category10();
|
||||
|
||||
// map one colour each to x, y and z
|
||||
// keys grabs the key value or heading of each key value pair in the json
|
||||
// but not time
|
||||
color.domain(d3.keys(data[0]).filter(function (key) {
|
||||
return key !== "time";
|
||||
}));
|
||||
|
||||
// create a nested series for passing to the line generator
|
||||
// it's best understood by console logging the data
|
||||
var series = color.domain().map(function (name) {
|
||||
return {
|
||||
name: name,
|
||||
values: data.map(function (d) {
|
||||
return {
|
||||
time: d.time,
|
||||
score: +d[name]
|
||||
};
|
||||
})
|
||||
};
|
||||
});
|
||||
|
||||
// Set the dimensions of the canvas / graph
|
||||
var margin = {
|
||||
top: 30,
|
||||
right: 20,
|
||||
bottom: 40,
|
||||
left: 75
|
||||
},
|
||||
width = 550 - margin.left - margin.right,
|
||||
height = 350 - margin.top - margin.bottom;
|
||||
|
||||
// Set the ranges
|
||||
//var x = d3.time.scale().range([0, width]).domain([0,10]);
|
||||
var x = d3.time.scale().range([0, width]);
|
||||
var y = d3.scale.linear().range([height, 0]);
|
||||
|
||||
// Define the axes
|
||||
var xAxis = d3.svg.axis().scale(x)
|
||||
.orient("bottom").ticks(10);
|
||||
|
||||
var yAxis = d3.svg.axis().scale(y)
|
||||
.orient("left").ticks(5);
|
||||
|
||||
// Define the line
|
||||
// Note you plot the time / score pair from each key you created earlier
|
||||
var valueline = d3.svg.line()
|
||||
.x(function (d) {
|
||||
return x(d.time);
|
||||
})
|
||||
.y(function (d) {
|
||||
return y(d.score);
|
||||
});
|
||||
|
||||
// Adds the svg canvas
|
||||
var svg = d3.select($('.crm-mailing-ab-stats', element)[0])
|
||||
.append("svg")
|
||||
.attr("width", width + margin.left + margin.right)
|
||||
.attr("height", height + margin.top + margin.bottom)
|
||||
.append("g")
|
||||
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
|
||||
|
||||
// Scale the range of the data
|
||||
x.domain(d3.extent(data, function (d) {
|
||||
return d.time;
|
||||
}));
|
||||
|
||||
// note the nested nature of this you need to dig an additional level
|
||||
y.domain([
|
||||
d3.min(series, function (c) {
|
||||
return d3.min(c.values, function (v) {
|
||||
return v.score;
|
||||
});
|
||||
}),
|
||||
d3.max(series, function (c) {
|
||||
return d3.max(c.values, function (v) {
|
||||
return v.score;
|
||||
});
|
||||
})
|
||||
]);
|
||||
svg.append("text") // text label for the x axis
|
||||
.attr("x", width / 2)
|
||||
.attr("y", height + margin.bottom)
|
||||
.style("text-anchor", "middle")
|
||||
.text("Time");
|
||||
|
||||
svg.append("text") // text label for the x axis
|
||||
.style("text-anchor", "middle")
|
||||
.text(scope.winnercriteria).attr("transform",function (d) {
|
||||
return "rotate(-90)";
|
||||
}).attr("x", -height / 2)
|
||||
.attr("y", -30);
|
||||
|
||||
// create a variable called series and bind the date
|
||||
// for each series append a g element and class it as series for css styling
|
||||
series = svg.selectAll(".series")
|
||||
.data(series)
|
||||
.enter().append("g")
|
||||
.attr("class", "series");
|
||||
|
||||
// create the path for each series in the variable series i.e. x, y and z
|
||||
// pass each object called x, y nad z to the lne generator
|
||||
series.append("path")
|
||||
.attr("class", "line")
|
||||
.attr("d", function (d) {
|
||||
// console.log(d); // to see how d3 iterates through series
|
||||
return valueline(d.values);
|
||||
})
|
||||
.style("stroke", function (d) {
|
||||
return color(d.name);
|
||||
});
|
||||
|
||||
// Add the X Axis
|
||||
svg.append("g") // Add the X Axis
|
||||
.attr("class", "x axis")
|
||||
.attr("transform", "translate(0," + height + ")")
|
||||
.call(xAxis)
|
||||
.selectAll("text")
|
||||
.attr("transform", function (d) {
|
||||
return "rotate(-30)";
|
||||
});
|
||||
|
||||
// Add the Y Axis
|
||||
svg.append("g") // Add the Y Axis
|
||||
.attr("class", "y axis")
|
||||
.call(yAxis);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
} // link()
|
||||
};
|
||||
});
|
||||
})(angular, CRM.$, CRM._);
|
|
@ -0,0 +1,19 @@
|
|||
<div ng-controller="CrmMailingABWinnerDialogCtrl">
|
||||
<form novalidate name="winnerForm">
|
||||
<div class="help">
|
||||
{{ts('After selecting %1 as the winner, one must schedule the delivery for the final mailing.', {1: mailingTitle})}}
|
||||
</div>
|
||||
|
||||
<div crm-mailing-radio-date="schedule" ng-model="abtest.mailings.c.scheduled_date">
|
||||
<div>
|
||||
<input ng-model="schedule.mode" type="radio" name="send" value="now" id="schedule-send-now"/>
|
||||
<label for="schedule-send-now">{{ts('Send final mailing immediately')}}</label>
|
||||
</div>
|
||||
<div>
|
||||
<input ng-model="schedule.mode" type="radio" name="send" value="at" id="schedule-send-at"/>
|
||||
<label for="schedule-send-at">{{ts('Send final mailing at:')}}</label>
|
||||
<input crm-ui-datepicker ng-model="schedule.datetime"/>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
|
@ -0,0 +1,52 @@
|
|||
(function(angular, $, _) {
|
||||
|
||||
angular.module('crmMailingAB').controller('CrmMailingABWinnerDialogCtrl', function($scope, $timeout, dialogService, crmMailingMgr, crmStatus) {
|
||||
var ts = $scope.ts = CRM.ts(null);
|
||||
var abtest = $scope.abtest = $scope.model.abtest;
|
||||
var mailingName = $scope.model.mailingName;
|
||||
|
||||
var titles = {a: ts('Mailing A'), b: ts('Mailing B')};
|
||||
$scope.mailingTitle = titles[mailingName];
|
||||
|
||||
function init() {
|
||||
// When using dialogService with a button bar, the major button actions
|
||||
// need to be registered with the dialog widget (and not embedded in
|
||||
// the body of the dialog).
|
||||
var buttons = [
|
||||
{
|
||||
text: ts('Submit final mailing'),
|
||||
icons: {primary: 'fa-paper-plane'},
|
||||
click: function() {
|
||||
crmMailingMgr.mergeInto(abtest.mailings.c, abtest.mailings[mailingName], [
|
||||
'name',
|
||||
'recipients',
|
||||
'scheduled_date'
|
||||
]);
|
||||
crmStatus({start: ts('Saving...'), success: ''}, abtest.save())
|
||||
.then(function() {
|
||||
return crmStatus({start: ts('Submitting...'), success: ts('Submitted')},
|
||||
abtest.submitFinal().then(function(r) {
|
||||
delete abtest.$CrmMailingABReportCnt;
|
||||
return r;
|
||||
}));
|
||||
})
|
||||
.then(function() {
|
||||
dialogService.close('selectWinnerDialog', abtest);
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
text: ts('Cancel'),
|
||||
icons: {primary: 'fa-times'},
|
||||
click: function() {
|
||||
dialogService.cancel('selectWinnerDialog');
|
||||
}
|
||||
}
|
||||
];
|
||||
dialogService.setButtons('selectWinnerDialog', buttons);
|
||||
}
|
||||
|
||||
$timeout(init);
|
||||
});
|
||||
|
||||
})(angular, CRM.$, CRM._);
|
233
sites/all/modules/civicrm/ang/crmMailingAB/services.js
Normal file
233
sites/all/modules/civicrm/ang/crmMailingAB/services.js
Normal file
|
@ -0,0 +1,233 @@
|
|||
(function (angular, $, _) {
|
||||
|
||||
function OptionGroup(values) {
|
||||
this.get = function get(value) {
|
||||
var r = _.where(values, {value: '' + value});
|
||||
return r.length > 0 ? r[0] : null;
|
||||
};
|
||||
this.getByName = function get(name) {
|
||||
var r = _.where(values, {name: '' + name});
|
||||
return r.length > 0 ? r[0] : null;
|
||||
};
|
||||
this.getAll = function getAll() {
|
||||
return values;
|
||||
};
|
||||
}
|
||||
|
||||
angular.module('crmMailingAB').factory('crmMailingABCriteria', function () {
|
||||
// TODO Get data from server
|
||||
var values = {
|
||||
'1': {value: 'subject', name: 'subject', label: ts('Test different "Subject" lines')},
|
||||
'2': {value: 'from', name: 'from', label: ts('Test different "From" lines')},
|
||||
'3': {value: 'full_email', name: 'full_email', label: ts('Test entirely different emails')}
|
||||
};
|
||||
return new OptionGroup(values);
|
||||
});
|
||||
|
||||
angular.module('crmMailingAB').factory('crmMailingABStatus', function () {
|
||||
// TODO Get data from server
|
||||
var values = {
|
||||
'1': {value: '1', name: 'Draft', label: ts('Draft')},
|
||||
'2': {value: '2', name: 'Testing', label: ts('Testing')},
|
||||
'3': {value: '3', name: 'Final', label: ts('Final')}
|
||||
};
|
||||
return new OptionGroup(values);
|
||||
});
|
||||
|
||||
// CrmMailingAB is a data-model which combines an AB test (APIv3 "MailingAB"), three mailings (APIv3 "Mailing"),
|
||||
// and three sets of attachments (APIv3 "Attachment").
|
||||
//
|
||||
// example:
|
||||
// var abtest = new CrmMailingAB(123);
|
||||
// abtest.load().then(function(){
|
||||
// alert("Mailing A is named "+abtest.mailings.a.name);
|
||||
// });
|
||||
angular.module('crmMailingAB').factory('CrmMailingAB', function (crmApi, crmMailingMgr, $q, CrmAttachments) {
|
||||
function CrmMailingAB(id) {
|
||||
this.id = id;
|
||||
this.mailings = {};
|
||||
this.attachments = {};
|
||||
}
|
||||
|
||||
angular.extend(CrmMailingAB.prototype, {
|
||||
getAutosaveSignature: function() {
|
||||
return [
|
||||
this.ab,
|
||||
this.mailings,
|
||||
this.attachments.a.getAutosaveSignature(),
|
||||
this.attachments.b.getAutosaveSignature(),
|
||||
this.attachments.c.getAutosaveSignature()
|
||||
];
|
||||
},
|
||||
// @return Promise CrmMailingAB
|
||||
load: function load() {
|
||||
var crmMailingAB = this;
|
||||
if (!crmMailingAB.id) {
|
||||
crmMailingAB.ab = {
|
||||
name: '',
|
||||
status: 'Draft',
|
||||
mailing_id_a: null,
|
||||
mailing_id_b: null,
|
||||
mailing_id_c: null,
|
||||
domain_id: null,
|
||||
testing_criteria: 'subject',
|
||||
winner_criteria: null,
|
||||
specific_url: '',
|
||||
declare_winning_time: null,
|
||||
group_percentage: 10
|
||||
};
|
||||
var mailingDefaults = {
|
||||
// Most defaults provided by Mailing.create API, but we
|
||||
// want to force-enable tracking.
|
||||
open_tracking: "1",
|
||||
url_tracking: "1",
|
||||
mailing_type:"experiment"
|
||||
};
|
||||
crmMailingAB.mailings.a = crmMailingMgr.create(mailingDefaults);
|
||||
crmMailingAB.mailings.b = crmMailingMgr.create(mailingDefaults);
|
||||
mailingDefaults.mailing_type = 'winner';
|
||||
crmMailingAB.mailings.c = crmMailingMgr.create(mailingDefaults);
|
||||
crmMailingAB.attachments.a = new CrmAttachments(function () {
|
||||
return {entity_table: 'civicrm_mailing', entity_id: crmMailingAB.ab.mailing_id_a};
|
||||
});
|
||||
crmMailingAB.attachments.b = new CrmAttachments(function () {
|
||||
return {entity_table: 'civicrm_mailing', entity_id: crmMailingAB.ab.mailing_id_b};
|
||||
});
|
||||
crmMailingAB.attachments.c = new CrmAttachments(function () {
|
||||
return {entity_table: 'civicrm_mailing', entity_id: crmMailingAB.ab.mailing_id_c};
|
||||
});
|
||||
|
||||
var dfr = $q.defer();
|
||||
dfr.resolve(crmMailingAB);
|
||||
return dfr.promise;
|
||||
}
|
||||
else {
|
||||
return crmApi('MailingAB', 'get', {id: crmMailingAB.id})
|
||||
.then(function (abResult) {
|
||||
if (abResult.count != 1) {
|
||||
throw "Failed to load AB Test";
|
||||
}
|
||||
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) {
|
||||
if (!crmMailingAB.id) {
|
||||
crmMailingAB.id = crmMailingAB.ab.id = abResult.id;
|
||||
}
|
||||
});
|
||||
})
|
||||
.then(function () {
|
||||
return crmMailingAB;
|
||||
});
|
||||
},
|
||||
// Schedule the test
|
||||
// @return Promise CrmMailingAB
|
||||
// Note: Submission may cause the server state to change. Consider abtest.submit().then(...abtest.load()...)
|
||||
submitTest: function submitTest() {
|
||||
var crmMailingAB = this;
|
||||
var params = {
|
||||
id: this.ab.id,
|
||||
status: 'Testing',
|
||||
approval_date: 'now',
|
||||
scheduled_date: this.mailings.a.scheduled_date ? this.mailings.a.scheduled_date : 'now'
|
||||
};
|
||||
return crmApi('MailingAB', 'submit', params)
|
||||
.then(function () {
|
||||
return crmMailingAB.load();
|
||||
});
|
||||
},
|
||||
// Schedule the final mailing
|
||||
// @return Promise CrmMailingAB
|
||||
// Note: Submission may cause the server state to change. Consider abtest.submit().then(...abtest.load()...)
|
||||
submitFinal: function submitFinal() {
|
||||
var crmMailingAB = this;
|
||||
var params = {
|
||||
id: this.ab.id,
|
||||
status: 'Final',
|
||||
approval_date: 'now',
|
||||
scheduled_date: this.mailings.c.scheduled_date ? this.mailings.c.scheduled_date : 'now'
|
||||
};
|
||||
return crmApi('MailingAB', 'submit', params)
|
||||
.then(function () {
|
||||
return crmMailingAB.load();
|
||||
});
|
||||
},
|
||||
// @param mailing Object (per APIv3)
|
||||
// @return Promise
|
||||
'delete': function () {
|
||||
if (this.id) {
|
||||
return crmApi('MailingAB', 'delete', {id: this.id});
|
||||
}
|
||||
else {
|
||||
var d = $q.defer();
|
||||
d.resolve();
|
||||
return d.promise;
|
||||
}
|
||||
},
|
||||
// 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;
|
||||
crmMailingAB.attachments[mkey] = new CrmAttachments(function () {
|
||||
return {entity_table: 'civicrm_mailing', entity_id: crmMailingAB.ab['mailing_id_' + mkey]};
|
||||
});
|
||||
return crmMailingAB.attachments[mkey].load();
|
||||
});
|
||||
}
|
||||
else {
|
||||
crmMailingAB.mailings[mkey] = crmMailingMgr.create();
|
||||
crmMailingAB.attachments[mkey] = new CrmAttachments(function () {
|
||||
return {entity_table: 'civicrm_mailing', entity_id: crmMailingAB.ab['mailing_id_' + mkey]};
|
||||
});
|
||||
}
|
||||
});
|
||||
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 = {};
|
||||
var p = $q.when(true);
|
||||
_.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];
|
||||
}
|
||||
p = p.then(function(){
|
||||
return crmMailingMgr.save(crmMailingAB.mailings[mkey])
|
||||
.then(function () {
|
||||
crmMailingAB.ab['mailing_id_' + mkey] = crmMailingAB.mailings[mkey].id;
|
||||
return crmMailingAB.attachments[mkey].save();
|
||||
});
|
||||
});
|
||||
});
|
||||
return p.then(function () {
|
||||
return crmMailingAB;
|
||||
});
|
||||
}
|
||||
|
||||
});
|
||||
return CrmMailingAB;
|
||||
});
|
||||
|
||||
})(angular, CRM.$, CRM._);
|
Loading…
Add table
Add a link
Reference in a new issue