From 6bff386445e5ab6f6c1cede8a9cdcd6c85918d7f Mon Sep 17 00:00:00 2001 From: Coleman Watts Date: Fri, 2 Jul 2021 22:20:33 -0400 Subject: [PATCH] SearchKit - Auto-apply Afform filters. Ensures that any scalar value passed as a filter via Afform markup will be auto-applied. Adds unit test for a search display embedded in an afform to lock in filter behavior. --- .../ang/testContactEmailSearchForm.aff.html | 8 + .../ang/testContactEmailSearchForm.aff.json | 6 + .../Civi/Api4/Action/SearchDisplay/Run.php | 38 ++--- .../api/v4/SearchDisplay/SearchAfformTest.php | 154 ++++++++++++++++++ 4 files changed, 187 insertions(+), 19 deletions(-) create mode 100644 ext/afform/mock/ang/testContactEmailSearchForm.aff.html create mode 100644 ext/afform/mock/ang/testContactEmailSearchForm.aff.json create mode 100644 ext/search_kit/tests/phpunit/api/v4/SearchDisplay/SearchAfformTest.php diff --git a/ext/afform/mock/ang/testContactEmailSearchForm.aff.html b/ext/afform/mock/ang/testContactEmailSearchForm.aff.html new file mode 100644 index 0000000000..2f3b1f8814 --- /dev/null +++ b/ext/afform/mock/ang/testContactEmailSearchForm.aff.html @@ -0,0 +1,8 @@ +
+ +
+ + +
+ +
diff --git a/ext/afform/mock/ang/testContactEmailSearchForm.aff.json b/ext/afform/mock/ang/testContactEmailSearchForm.aff.json new file mode 100644 index 0000000000..cefad3d1fd --- /dev/null +++ b/ext/afform/mock/ang/testContactEmailSearchForm.aff.json @@ -0,0 +1,6 @@ +{ + "type": "search", + "title": "TestContactEmailForm", + "server_route": "", + "permission": "access CiviCRM" +} diff --git a/ext/search_kit/Civi/Api4/Action/SearchDisplay/Run.php b/ext/search_kit/Civi/Api4/Action/SearchDisplay/Run.php index e27b6f1101..008da3d4e8 100644 --- a/ext/search_kit/Civi/Api4/Action/SearchDisplay/Run.php +++ b/ext/search_kit/Civi/Api4/Action/SearchDisplay/Run.php @@ -160,24 +160,13 @@ class Run extends \Civi\Api4\Generic\AbstractAction { return; } - // Process all filters that are included in SELECT clause. These filters are implicitly allowed. - foreach ($this->getSelectAliases() as $fieldName) { - if (isset($filters[$fieldName])) { - $value = $filters[$fieldName]; - unset($filters[$fieldName]); + // Process all filters that are included in SELECT clause or are allowed by the Afform. + $allowedFilters = array_merge($this->getSelectAliases(), $this->getAfformFilters()); + foreach ($filters as $fieldName => $value) { + if (in_array($fieldName, $allowedFilters, TRUE)) { $this->applyFilter($fieldName, $value); } } - - // Other filters may be allowed if display is embedded in an afform. - if ($filters) { - foreach ($this->getAfformFilters() as $fieldName) { - if (isset($filters[$fieldName])) { - $value = $filters[$fieldName]; - $this->applyFilter($fieldName, $value); - } - } - } } /** @@ -315,6 +304,10 @@ class Run extends \Civi\Api4\Generic\AbstractAction { } /** + * Returns a list of filter fields and directive filters + * + * Automatically applies directive filters + * * @return array */ private function getAfformFilters() { @@ -323,16 +316,23 @@ class Run extends \Civi\Api4\Generic\AbstractAction { return []; } // Get afform field filters - $filters = array_column(\CRM_Utils_Array::findAll( + $filterKeys = array_column(\CRM_Utils_Array::findAll( $afform['layout'] ?? [], ['#tag' => 'af-field'] ), 'name'); - // Get filters passed into search display directive + // Get filters passed into search display directive from Afform markup $filterAttr = $afform['searchDisplay']['filters'] ?? NULL; if ($filterAttr && is_string($filterAttr) && $filterAttr[0] === '{') { - $filters = array_unique(array_merge($filters, array_keys(\CRM_Utils_JS::getRawProps($filterAttr)))); + foreach (\CRM_Utils_JS::decode($filterAttr) as $filterKey => $filterVal) { + $filterKeys[] = $filterKey; + // Automatically apply filters from the markup if they have a value + // (if it's a javascript variable it will have come back from decode() as NULL and we'll ignore it). + if ($this->hasValue($filterVal)) { + $this->applyFilter($filterKey, $filterVal); + } + } } - return $filters; + return $filterKeys; } /** diff --git a/ext/search_kit/tests/phpunit/api/v4/SearchDisplay/SearchAfformTest.php b/ext/search_kit/tests/phpunit/api/v4/SearchDisplay/SearchAfformTest.php new file mode 100644 index 0000000000..c790462eac --- /dev/null +++ b/ext/search_kit/tests/phpunit/api/v4/SearchDisplay/SearchAfformTest.php @@ -0,0 +1,154 @@ +install(['org.civicrm.search_kit', 'org.civicrm.afform', 'org.civicrm.afform-mock']) + ->apply(); + } + + /** + * Test running a searchDisplay within an afform. + */ + public function testRunWithAfform() { + $search = SavedSearch::create(FALSE) + ->setValues([ + 'name' => 'TestContactEmailSearch', + 'label' => 'TestContactEmailSearch', + 'api_entity' => 'Contact', + 'api_params' => [ + 'version' => 4, + 'select' => [ + 'id', + 'display_name', + 'GROUP_CONCAT(DISTINCT Contact_Email_contact_id_01.email) AS GROUP_CONCAT_DISTINCT_Contact_Email_contact_id_01_email', + ], + 'orderBy' => [], + 'where' => [ + ['contact_type:name', '=', 'Individual'], + ], + 'groupBy' => ['id'], + 'join' => [ + [ + 'Email AS Contact_Email_contact_id_01', + 'LEFT', + ['id', '=', 'Contact_Email_contact_id_01.contact_id'], + ], + ], + 'having' => [], + ], + ]) + ->execute()->first(); + + $display = SearchDisplay::create(FALSE) + ->setValues([ + 'name' => 'TestContactEmailDisplay', + 'label' => 'TestContactEmailDisplay', + 'saved_search_id.name' => 'TestContactEmailSearch', + 'type' => 'table', + 'settings' => [ + 'limit' => 50, + 'pager' => TRUE, + 'columns' => [ + [ + 'key' => 'id', + 'label' => 'Contact ID', + 'dataType' => 'Integer', + 'type' => 'field', + ], + [ + 'key' => 'display_name', + 'label' => 'Display Name', + 'dataType' => 'String', + 'type' => 'field', + ], + [ + 'key' => 'GROUP_CONCAT_DISTINCT_Contact_Email_contact_id_01_email', + 'label' => 'Emails', + 'dataType' => 'String', + 'type' => 'field', + ], + ], + ], + 'acl_bypass' => FALSE, + ]) + ->execute()->first(); + + $email = uniqid('tester@'); + + Contact::create(FALSE) + ->addValue('first_name', 'tester') + ->addValue('last_name', 'AfformTest') + ->addValue('source', 'afform_test') + ->addChain('emails', Email::save() + ->addDefault('contact_id', '$id') + ->addRecord(['email' => $email, 'location_type_id:name' => 'Home']) + ->addRecord(['email' => $email, 'location_type_id:name' => 'Work']) + ) + ->execute(); + + Contact::create(FALSE) + ->addValue('first_name', 'tester2') + ->addValue('last_name', 'AfformTest') + ->addValue('source', 'afform_test2') + ->addChain('emails', Email::save() + ->addDefault('contact_id', '$id') + ->addRecord(['email' => 'other@test.com', 'location_type_id:name' => 'Other']) + ) + ->execute(); + + Contact::create(FALSE) + ->addValue('first_name', 'tester3') + ->addValue('last_name', 'excluded from test') + ->addValue('source', 'afform_test3') + ->execute(); + + $params = [ + 'return' => 'page:1', + 'savedSearch' => $search['name'], + 'display' => $display['name'], + 'afform' => 'testContactEmailSearchForm', + ]; + + // Try a filter that is on the afform but not the search + $params['filters'] = ['source' => 'afform_test2']; + $result = civicrm_api4('SearchDisplay', 'run', $params); + $this->assertCount(1, $result); + + // That same param will not work without the afform + $params['filters'] = ['source' => 'afform_test2']; + $result = civicrm_api4('SearchDisplay', 'run', ['afform' => NULL] + $params); + $this->assertGreaterThan(1, $result->count()); + + // Try a filter that is in neither afform nor search - it should not work + $params['filters'] = ['first_name' => 'tester2']; + $result = civicrm_api4('SearchDisplay', 'run', $params); + $this->assertGreaterThan(1, $result->count()); + + // Note that filters add a wildcard so the value `afform_test` matches all 3 sample contacts; + // But the Afform markup contains `filters="{last_name: 'AfformTest'}"` which only matches 2. + $params['filters'] = ['source' => 'afform_test']; + $result = civicrm_api4('SearchDisplay', 'run', $params); + $this->assertCount(2, $result); + + // Filter by email address + $params['filters'] = ['Contact_Email_contact_id_01.email' => $email]; + $result = civicrm_api4('SearchDisplay', 'run', $params); + $this->assertCount(1, $result); + } + +} -- 2.25.1