From b73e0c539f495be9343992a4ebe7ca962a78831b Mon Sep 17 00:00:00 2001 From: Tim Otten Date: Thu, 20 Nov 2014 17:53:46 -0800 Subject: [PATCH] CRM-15578 - Mailing API - Replace "preview_recipients" action with chained calls The special-purpose API "Mailing.preview_recipients" is no longer necessary. Instead, one can chain simpler APIs ("Mailing.create" + "MailingRecipients.get" + "force_rollback") --- api/v3/Mailing.php | 52 ---------------------------- js/angular-crmMailing2.js | 19 ++++++++-- tests/phpunit/api/v3/MailingTest.php | 21 +++++++++-- 3 files changed, 35 insertions(+), 57 deletions(-) diff --git a/api/v3/Mailing.php b/api/v3/Mailing.php index 013a327483..af48f786ea 100755 --- a/api/v3/Mailing.php +++ b/api/v3/Mailing.php @@ -310,58 +310,6 @@ function civicrm_api3_mailing_event_open($params) { return civicrm_api3_create_success($params); } -/** - * Generate a list of likely recipients for a (hypothetical) mailing. - * - * "Mailing.preview_recipients" == "Mailing.create" + "MailingRecipients.get" + "rollback" - * - * Ideally, we could add a "force_rollback" option to the API; then downstream code could - * combine the actions without needing this function. - * - * @param array $params - * @return array list of recipients - * @throws API_Exception - */ -function civicrm_api3_mailing_preview_recipients($params) { - static $nextId = 0; - $savePoint = "civimail_preview_" . ++$nextId; - $tx = new CRM_Core_Transaction(); - CRM_Core_DAO::executeQuery("SAVEPOINT $savePoint"); - - // We manually apply defaults (rather than using API defaults) because - // (a) these are temporary/non-sense values and (b) we want to - // apply defaults regardless of whether mailing has NULL/''/real-value. - $params['name'] = 'Placeholder (not saved)'; - $params['subject'] = 'Placeholder (not saved)'; - $params['scheduled_date'] = date('YmdHis', time() + 24*60*60); - - // "Mailing.preview_recipients" == "Mailing.create"+"MailingRecipients.get" - $params['debug'] = 1; - $params['version'] = 3; - $params['options']['force_rollback'] = 1; - $params['api.MailingRecipients.get'] = array( - 'options' => array( - 'limit' => isset($params['options']['limit']) ? $params['options']['limit'] : 1000, - ), - 'api.contact.getvalue' => array( - 'return' => 'display_name', - ), - 'api.email.getvalue' => array( - 'return' => 'email', - ), - ); - - try { - $mailing = civicrm_api3('Mailing', 'create', $params); - } catch (Exception $ex) { - CRM_Core_DAO::executeQuery("ROLLBACK TO SAVEPOINT $savePoint"); - throw $ex; - } - - CRM_Core_DAO::executeQuery("ROLLBACK TO SAVEPOINT $savePoint"); - return civicrm_api3_create_success($mailing['values'][$mailing['id']]['api.MailingRecipients.get']['values']); -} - function civicrm_api3_mailing_preview($params) { civicrm_api3_verify_mandatory($params, 'CRM_Mailing_DAO_Mailing', diff --git a/js/angular-crmMailing2.js b/js/angular-crmMailing2.js index 99d5d6dda2..542f3b9d01 100644 --- a/js/angular-crmMailing2.js +++ b/js/angular-crmMailing2.js @@ -7,6 +7,7 @@ // Time to wait before triggering AJAX update to recipients list var RECIPIENTS_DEBOUNCE_MS = 100; + var RECIPIENTS_PREVIEW_LIMIT = 10000; /** * Initialize a new mailing @@ -138,6 +139,8 @@ return ts('No recipients'); if ($scope.recipients.length == 1) return ts('~1 recipient'); + if (RECIPIENTS_PREVIEW_LIMIT > 0 && $scope.recipients.length >= RECIPIENTS_PREVIEW_LIMIT) + return ts('>%1 recipients', {1: RECIPIENTS_PREVIEW_LIMIT}); return ts('~%1 recipients', {1: $scope.recipients.length}); }; // We monitor four fields -- use debounce so that changes across the @@ -145,10 +148,22 @@ var refreshRecipients = _.debounce(function () { $scope.$apply(function () { $scope.recipients = null; - crmApi('Mailing', 'preview_recipients', $scope.mailing) + // To get list of recipients, we tentatively save the mailing and + // get the resulting recipients -- then rollback any changes. + var params = _.extend({}, $scope.mailing, { + options: {force_rollback: 1}, + 'api.MailingRecipients.get': { + mailing_id: '$value.id', + options: {limit: RECIPIENTS_PREVIEW_LIMIT}, + 'api.contact.getvalue': {'return': 'display_name'}, + 'api.email.getvalue': {'return': 'email'} + } + }); + + crmApi('Mailing', 'create', params) .then(function (recipResult) { $scope.$apply(function () { - $scope.recipients = recipResult.values; + $scope.recipients = recipResult.values[recipResult.id]['api.MailingRecipients.get'].values; }); }); }); diff --git a/tests/phpunit/api/v3/MailingTest.php b/tests/phpunit/api/v3/MailingTest.php index aa282b20bb..2ed1eb732e 100755 --- a/tests/phpunit/api/v3/MailingTest.php +++ b/tests/phpunit/api/v3/MailingTest.php @@ -108,8 +108,8 @@ class api_v3_MailingTest extends CiviUnitTestCase { // BEGIN SAMPLE DATA $this->groupIDs['inc'] = $this->groupCreate(array('name' => 'Example include group', 'title' => 'Example include group')); $this->groupIDs['exc'] = $this->groupCreate(array('name' => 'Example exclude group', 'title' => 'Example exclude group')); - $this->contactIDs['includeme'] = $this->individualCreate(array('include.me@example.org')); - $this->contactIDs['excludeme'] = $this->individualCreate(array('exclude.me@example.org')); + $this->contactIDs['includeme'] = $this->individualCreate(array('email' => 'include.me@example.org', 'first_name' => 'Includer', 'last_name' => 'Person')); + $this->contactIDs['excludeme'] = $this->individualCreate(array('email' => 'exclude.me@example.org', 'last_name' => 'Excluder', 'last_name' => 'Excluder')); $this->callAPISuccess('GroupContact', 'create', array('group_id' => $this->groupIDs['inc'], 'contact_id' => $this->contactIDs['includeme'])); $this->callAPISuccess('GroupContact', 'create', array('group_id' => $this->groupIDs['inc'], 'contact_id' => $this->contactIDs['excludeme'])); $this->callAPISuccess('GroupContact', 'create', array('group_id' => $this->groupIDs['exc'], 'contact_id' => $this->contactIDs['excludeme'])); @@ -119,6 +119,16 @@ class api_v3_MailingTest extends CiviUnitTestCase { $params['groups']['exclude'] = array($this->groupIDs['exc']); $params['mailings']['include'] = array(); $params['mailings']['exclude'] = array(); + $params['options']['force_rollback'] = 1; + $params['api.MailingRecipients.get'] = array( + 'mailing_id' => '$value.id', + 'api.contact.getvalue' => array( + 'return' => 'display_name', + ), + 'api.email.getvalue' => array( + 'return' => 'email', + ), + ); // END SAMPLE DATA $maxIDs = array( @@ -126,13 +136,18 @@ class api_v3_MailingTest extends CiviUnitTestCase { 'job' => CRM_Core_DAO::singleValueQuery('SELECT MAX(id) FROM civicrm_mailing_job'), 'group' => CRM_Core_DAO::singleValueQuery('SELECT MAX(id) FROM civicrm_mailing_group'), ); - $preview = $this->callAPIAndDocument('Mailing', 'preview_recipients', $params, __FUNCTION__, __FILE__); + $create = $this->callAPIAndDocument('Mailing', 'create', $params, __FUNCTION__, __FILE__); $this->assertDBQuery($maxIDs['mailing'], 'SELECT MAX(id) FROM civicrm_mailing'); // 'Preview should not create any mailing records' $this->assertDBQuery($maxIDs['job'], 'SELECT MAX(id) FROM civicrm_mailing_job'); // 'Preview should not create any mailing_job record' $this->assertDBQuery($maxIDs['group'], 'SELECT MAX(id) FROM civicrm_mailing_group'); // 'Preview should not create any mailing_group records' + $preview = $create['values'][$create['id']]['api.MailingRecipients.get']; $previewIds = array_values(CRM_Utils_Array::collect('contact_id', $preview['values'])); $this->assertEquals(array((string)$this->contactIDs['includeme']), $previewIds); + $previewEmails = array_values(CRM_Utils_Array::collect('api.email.getvalue', $preview['values'])); + $this->assertEquals(array('include.me@example.org'), $previewEmails); + $previewNames = array_values(CRM_Utils_Array::collect('api.contact.getvalue', $preview['values'])); + $this->assertTrue((bool)preg_match('/Includer Person/', $previewNames[0]), "Name 'Includer Person' should appear in '" . $previewNames[0] . '"'); } public function testMailerSendTestMail() { -- 2.25.1