From 7a646a012944ef1722d56c28eb0169aed7bc4ec6 Mon Sep 17 00:00:00 2001
From: "deb.monish" <monish.deb@jmaconsulting.biz>
Date: Mon, 9 Oct 2017 19:50:15 +0530
Subject: [PATCH] Rebuild recipient list and calculate count on demand, store
 result in

---
 ang/crmMailing.css                  | 10 +++++
 ang/crmMailing/BlockRecipients.html | 17 +++++---
 ang/crmMailing/EditRecipCtrl.js     | 66 +++++++++++++++++++----------
 ang/crmMailing/services.js          | 60 ++++++++++++++++++--------
 4 files changed, 107 insertions(+), 46 deletions(-)

diff --git a/ang/crmMailing.css b/ang/crmMailing.css
index b853cb4466e3..3c39b693128a 100644
--- a/ang/crmMailing.css
+++ b/ang/crmMailing.css
@@ -111,3 +111,13 @@ input[name=preview_test_email]:-ms-input-placeholder {
   margin: 0.5em;
   color: red;
 }
+
+.crmMailing-outdated-count {
+  color: red;
+  font-weight: bold;
+}
+
+.crmMailing-latest-count {
+  color: green;
+  font-weight: bold;
+}
diff --git a/ang/crmMailing/BlockRecipients.html b/ang/crmMailing/BlockRecipients.html
index 83af7a574198..d205f478f22c 100644
--- a/ang/crmMailing/BlockRecipients.html
+++ b/ang/crmMailing/BlockRecipients.html
@@ -1,9 +1,4 @@
 <div ng-controller="EditRecipCtrl" class="crm-mailing-recipients-row">
-  <div style="float: right;">
-    <div class="crmMailing-recip-est">
-      <a href="" ng-click="previewRecipients()" title="{{ts('Preview a List of Recipients')}}">{{getRecipientsEstimate()}}</a>
-    </div>
-  </div>
   <input
     type="hidden"
     crm-mailing-recipients
@@ -11,6 +6,14 @@
     crm-mandatory-groups="crmMailingConst.groupNames | filter:{is_hidden:1}"
     crm-ui-id="{{crmMailingBlockRecipients.id}}"
     name="{{crmMailingBlockRecipients.name}}"
-    ng-required="true"/>
-  <a crm-icon="fa-wrench" ng-click="editOptions(mailing)" class="crm-hover-button" title="{{ts('Edit Recipient Options')}}"></a>
+    ng-required="true" />
+    <a crm-icon="fa-wrench" ng-click="editOptions(mailing)" class="crm-hover-button" title="{{ts('Edit Recipient Options')}}"></a>
+    <div>
+      <button ng-click="rebuildRecipients()" class="ng-binding crm-button" title="{{ts('Preview a List of Recipients')}}">{{getRecipientsEstimate()}}</button>
+      <a ng-click="previewRecipients()" class="crm-hover-button" title="{{ts('Click to refresh recipient count')}}">
+        <span class="{{((outdated || recipient == 0) ? 'crmMailing-outdated-count' : 'crmMailing-latest-count')}}">
+          {{getRecipientCount()}}
+        </span>
+      </a>
+    </div>
 </div>
diff --git a/ang/crmMailing/EditRecipCtrl.js b/ang/crmMailing/EditRecipCtrl.js
index 16c125f8cfca..3064384065eb 100644
--- a/ang/crmMailing/EditRecipCtrl.js
+++ b/ang/crmMailing/EditRecipCtrl.js
@@ -5,7 +5,7 @@
   // Scope members:
   //  - [input] mailing: object
   //  - [output] recipients: array of recipient records
-  angular.module('crmMailing').controller('EditRecipCtrl', function EditRecipCtrl($scope, dialogService, crmApi, crmMailingMgr, $q, crmMetadata, crmStatus) {
+  angular.module('crmMailing').controller('EditRecipCtrl', function EditRecipCtrl($scope, dialogService, crmApi, crmMailingMgr, $q, crmMetadata, crmStatus, crmMailingCache) {
     // Time to wait before triggering AJAX update to recipients list
     var RECIPIENTS_DEBOUNCE_MS = 100;
     var RECIPIENTS_PREVIEW_LIMIT = 50;
@@ -18,29 +18,36 @@
     };
 
     $scope.recipients = null;
+    $scope.outdated = null;
+
     $scope.getRecipientsEstimate = function() {
       var ts = $scope.ts;
       if ($scope.recipients === null) {
         return ts('(Estimating)');
       }
       if ($scope.recipients === 0) {
-        return ts('No recipients');
+        return ts('Estimate recipient count');
       }
-      if ($scope.recipients === 1) {
-        return ts('~1 recipient');
+      return ts('Refresh recipient count');
+    };
+
+    $scope.getRecipientCount = function() {
+      var ts = $scope.ts;
+      if ($scope.recipients === 0) {
+        return ts('(unknown)');
       }
-      return ts('~%1 recipients', {1: $scope.recipients});
+      return ($scope.recipients === 1) ? ts('~1 recipient') : ts('~%1 recipients', {1 : $scope.recipients});
     };
 
     // We monitor four fields -- use debounce so that changes across the
     // four fields can settle-down before AJAX.
     var refreshRecipients = _.debounce(function() {
       $scope.$apply(function() {
-        $scope.recipients = null;
         if (!$scope.mailing) {
           return;
         }
-        crmMailingMgr.previewRecipientCount($scope.mailing).then(function(recipients) {
+        crmMailingMgr.previewRecipientCount($scope.mailing, crmMailingCache, false).then(function(recipients) {
+          $scope.outdated = (_.difference($scope.mailing.recipients, crmMailingCache.get('mailing-' + $scope.mailing.id + '-recipient-params')) !== 0);
           $scope.recipients = recipients;
         });
       });
@@ -54,21 +61,36 @@
     $scope.$watchCollection("mailing.recipients.mailings.exclude", refreshRecipients);
 
     $scope.previewRecipients = function previewRecipients() {
-      return crmStatus({start: ts('Previewing...'), success: ''}, crmMailingMgr.previewRecipients($scope.mailing, RECIPIENTS_PREVIEW_LIMIT).then(function(recipients) {
-        var model = {
-          count: $scope.recipients,
-          sample: recipients,
-          sampleLimit: RECIPIENTS_PREVIEW_LIMIT
-        };
-        var options = CRM.utils.adjustDialogDefaults({
-          width: '40%',
-          autoOpen: false,
-          title: ts('Preview (%1)', {
-            1: $scope.getRecipientsEstimate()
-          })
-        });
-        dialogService.open('recipDialog', '~/crmMailing/PreviewRecipCtrl.html', model, options);
-      }));
+      var model = {
+        count: $scope.recipients,
+        sample: crmMailingCache.get('mailing-' + $scope.mailing.id + '-recipient-list'),
+        sampleLimit: RECIPIENTS_PREVIEW_LIMIT
+      };
+      var options = CRM.utils.adjustDialogDefaults({
+        width: '40%',
+        autoOpen: false,
+        title: ts('Preview (%1)', {1: $scope.getRecipientCount()})
+      });
+
+      // don't open preview dialog if there is no recipient to show.
+      if ($scope.recipients !== 0) {
+        if (!_.isEmpty(model.sample)) {
+          dialogService.open('recipDialog', '~/crmMailing/PreviewRecipCtrl.html', model, options);
+        }
+        else {
+          return crmStatus({start: ts('Previewing...'), success: ''}, crmMailingMgr.previewRecipients($scope.mailing, RECIPIENTS_PREVIEW_LIMIT).then(function(recipients) {
+            model.sample = recipients;
+            dialogService.open('recipDialog', '~/crmMailing/PreviewRecipCtrl.html', model, options);
+          }));
+        }
+      }
+    };
+
+    $scope.rebuildRecipients = function rebuildRecipients() {
+      return crmMailingMgr.previewRecipientCount($scope.mailing, crmMailingCache, true).then(function(recipients) {
+        $scope.outdated = (recipients === 0) ? true : false;
+        $scope.recipients = recipients;
+      });
     };
 
     // Open a dialog for editing the advanced recipient options.
diff --git a/ang/crmMailing/services.js b/ang/crmMailing/services.js
index 96d134070884..db9d2d9df816 100644
--- a/ang/crmMailing/services.js
+++ b/ang/crmMailing/services.js
@@ -310,24 +310,46 @@
         });
       },
 
-      previewRecipientCount: function previewRecipientCount(mailing) {
-        // To get list of recipients, we tentatively save the mailing and
-        // get the resulting recipients -- then rollback any changes.
-        var params = angular.extend({}, mailing, mailing.recipients, {
-          name: 'placeholder', // for previewing recipients on new, incomplete mailing
-          subject: 'placeholder', // for previewing recipients on new, incomplete mailing
-          options: {force_rollback: 1},
-          'api.mailing_job.create': 1, // note: exact match to API default
-          'api.MailingRecipients.getcount': {
-            mailing_id: '$value.id'
+      previewRecipientCount: function previewRecipientCount(mailing, crmMailingCache, rebuild) {
+        var cachekey = 'mailing-' + mailing.id + '-recipient-count';
+        var recipientCount = crmMailingCache.get(cachekey);
+        if (rebuild || _.isEmpty(recipientCount)) {
+          // To get list of recipients, we tentatively save the mailing and
+          // get the resulting recipients -- then rollback any changes.
+          var params = angular.extend({}, mailing, mailing.recipients, {
+            name: 'placeholder', // for previewing recipients on new, incomplete mailing
+            subject: 'placeholder', // for previewing recipients on new, incomplete mailing
+            options: {force_rollback: 1},
+            'api.mailing_job.create': 1, // note: exact match to API default
+            'api.MailingRecipients.getcount': {
+              mailing_id: '$value.id'
+            }
+          });
+          // if this service is executed on rebuild then also fetch the recipients list
+          if (rebuild) {
+            params = angular.extend(params, {
+              'api.MailingRecipients.get': {
+                mailing_id: '$value.id',
+                options: {limit: 50},
+                'api.contact.getvalue': {'return': 'display_name'},
+                'api.email.getvalue': {'return': 'email'}
+              }
+            });
+            crmMailingCache.put('mailing-' + mailing.id + '-recipient-params', params.recipients);
           }
-        });
-        delete params.recipients; // the content was merged in
-        return qApi('Mailing', 'create', params).then(function (recipResult) {
-          // changes rolled back, so we don't care about updating mailing
-          mailing.modified_date = recipResult.values[recipResult.id].modified_date;
-          return recipResult.values[recipResult.id]['api.MailingRecipients.getcount'];
-        });
+          delete params.recipients; // the content was merged in
+          recipientCount = qApi('Mailing', 'create', params).then(function (recipResult) {
+            // changes rolled back, so we don't care about updating mailing
+            mailing.modified_date = recipResult.values[recipResult.id].modified_date;
+            if (rebuild) {
+              crmMailingCache.put('mailing-' + mailing.id + '-recipient-list', recipResult.values[recipResult.id]['api.MailingRecipients.get'].values);
+            }
+            return recipResult.values[recipResult.id]['api.MailingRecipients.getcount'];
+          });
+          crmMailingCache.put(cachekey, recipientCount);
+        }
+
+        return recipientCount;
       },
 
       // Save a (draft) mailing
@@ -555,4 +577,8 @@
     };
   });
 
+  angular.module('crmMailing').factory('crmMailingCache', ['$cacheFactory', function($cacheFactory) {
+    return $cacheFactory('crmMailingCache');
+  }]);
+
 })(angular, CRM.$, CRM._);