Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SearchKit - Ensure filters work with multiple search displays on a form #23018

Merged
merged 1 commit into from
Apr 6, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions ext/afform/mock/ang/testMultipleSearchForm.aff.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<div>
<div af-fieldset="">
<af-field name="source" />
<af-field name="Contact_Email_contact_id_01.location_type_id" defn="{input_attrs: {multiple: true}}" />
<crm-search-display-table search-name="TestContactEmailSearch" display-name="TestContactEmailDisplay"></crm-search-display-table>
</div>
<div af-fieldset="">
<!-- Filter is in wrong fieldset to work with `TestSearchForEmail`. See SearchAfformTest::testRunMultipleSearchForm -->
<af-field name="email" />
<crm-search-display-table search-name="TestContactEmailSearch" display-name=""></crm-search-display-table>
</div>
</div>
<div af-fieldset="">
<af-field name="contact_id.display_name" />
<af-field name="location_type_id" />
<crm-search-display-table search-name="TestSearchForEmail" display-name=""></crm-search-display-table>
</div>
<!-- This display is not enclosed in an af-fieldset so this filter will not work. See SearchAfformTest::testRunMultipleSearchForm -->
<af-field name="location_type_id" />
<crm-search-display-table filters="{'contact_id.display_name': 'testRunMultipleSearchForm'}" search-name="TestSearchForPhone" display-name=""></crm-search-display-table>

6 changes: 6 additions & 0 deletions ext/afform/mock/ang/testMultipleSearchForm.aff.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"type": "search",
"title": "TestMultipleSearchForm",
"server_route": "",
"permission": "access CiviCRM"
}
31 changes: 21 additions & 10 deletions ext/search_kit/Civi/Api4/Action/SearchDisplay/AbstractRunAction.php
Original file line number Diff line number Diff line change
Expand Up @@ -913,7 +913,7 @@ private function getAfformFilters() {
}
// Get afform field filters
$filterKeys = array_column(\CRM_Utils_Array::findAll(
$afform['layout'] ?? [],
$afform['searchDisplay']['fieldset'],
['#tag' => 'af-field']
), 'name');
// Get filters passed into search display directive from Afform markup
Expand Down Expand Up @@ -942,24 +942,35 @@ private function getAfformFilters() {
*
* Verifies the searchDisplay is embedded in the afform and the user has permission to view it.
*
* @return array|false|null
* @return array|false
*/
private function loadAfform() {
// Only attempt to load afform once.
if ($this->afform && !isset($this->_afform)) {
$this->_afform = FALSE;
// Permission checks are enabled in this api call to ensure the user has permission to view the form
$afform = \Civi\Api4\Afform::get()
$afform = \Civi\Api4\Afform::get($this->getCheckPermissions())
->addWhere('name', '=', $this->afform)
->setLayoutFormat('shallow')
->setLayoutFormat('deep')
->execute()->first();
if (empty($afform['layout'])) {
return FALSE;
}
// Get all search display fieldsets (which will have an empty value for the af-fieldset attribute)
$fieldsets = \CRM_Utils_Array::findAll($afform['layout'], ['af-fieldset' => '']);
// As a fallback, search the entire afform in case the search display is not in a fieldset
$fieldsets['form'] = $afform['layout'];
// Validate that the afform contains this search display
$afform['searchDisplay'] = \CRM_Utils_Array::findAll(
$afform['layout'] ?? [],
['#tag' => "{$this->display['type:name']}", 'display-name' => $this->display['name']]
)[0] ?? NULL;
if ($afform['searchDisplay']) {
$this->_afform = $afform;
foreach ($fieldsets as $key => $fieldset) {
$afform['searchDisplay'] = \CRM_Utils_Array::findAll(
$fieldset,
['#tag' => $this->display['type:name'], 'search-name' => $this->savedSearch['name'], 'display-name' => $this->display['name']]
)[0] ?? NULL;
if ($afform['searchDisplay']) {
// Set the fieldset for this display (if it is in one and we haven't fallen back to the whole form)
$afform['searchDisplay']['fieldset'] = $key === 'form' ? [] : $fieldset;
return $this->_afform = $afform;
}
}
}
return $this->_afform;
Expand Down
170 changes: 170 additions & 0 deletions ext/search_kit/tests/phpunit/api/v4/SearchDisplay/SearchAfformTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
use Civi\Api4\Afform;
use Civi\Api4\Contact;
use Civi\Api4\Email;
use Civi\Api4\Phone;
use Civi\Api4\SavedSearch;
use Civi\Api4\SearchDisplay;
use Civi\Api4\Utils\CoreUtil;
Expand Down Expand Up @@ -159,6 +160,175 @@ public function testRunWithAfform() {
$this->assertCount(1, $result);
}

public function testRunMultipleSearchForm() {
$email = uniqid('tester@');

Contact::create(FALSE)
->addValue('first_name', 'tester')
->addValue('last_name', __FUNCTION__)
->addValue('source', 'afform_multi_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'])
)
->addChain('phones', Phone::save()
->addDefault('contact_id', '$id')
->addRecord(['phone' => '123-4567', 'location_type_id:name' => 'Home'])
->addRecord(['phone' => '234-5678', 'location_type_id:name' => 'Work'])
)
->execute();

Contact::create(FALSE)
->addValue('first_name', 'tester2')
->addValue('last_name', __FUNCTION__)
->addValue('source', 'afform_multi_test')
->addChain('emails', Email::save()
->addDefault('contact_id', '$id')
->addRecord(['email' => 'other@test.com', 'location_type_id:name' => 'Other'])
)
->addChain('phones', Phone::save()
->addDefault('contact_id', '$id')
->addRecord(['phone' => '123-4567', 'location_type_id:name' => 'Home'])
->addRecord(['phone' => '234-5678', 'location_type_id:name' => 'Work'])
)
->execute();

// Decoy contact just to make sure we don't get false-positives
Contact::create(FALSE)
->addValue('first_name', 'tester3')
->addValue('last_name', 'nobody')
->addValue('source', 'decoy')
->addChain('emails', Email::save()
->addDefault('contact_id', '$id')
->addRecord(['email' => $email, 'location_type_id:name' => 'Home'])
)
->addChain('phones', Phone::save()
->addDefault('contact_id', '$id')
->addRecord(['phone' => '123-4567', 'location_type_id:name' => 'Home'])
->addRecord(['phone' => '234-5678', 'location_type_id:name' => 'Work'])
)
->execute();

$contactEmailSearch = 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_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();

$contactEmailDisplay = 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_Contact_Email_contact_id_01_email',
'label' => 'Emails',
'dataType' => 'String',
'type' => 'field',
],
],
],
'acl_bypass' => FALSE,
])
->execute()->first();

foreach (['Email', 'Phone'] as $entity) {
SavedSearch::create(FALSE)
->setValues([
'name' => 'TestSearchFor' . $entity,
'label' => 'TestSearchFor' . $entity,
'api_entity' => $entity,
'api_params' => [
'version' => 4,
'select' => [
'id',
'contact_id.display_name',
],
'orderBy' => [],
'where' => [],
'groupBy' => [],
'join' => [],
'having' => [],
],
])
->execute();
}

$params = [
'return' => 'page:1',
'display' => NULL,
'afform' => 'testMultipleSearchForm',
];

// This filter will not work because the search display is not within an <af-field>
$params['savedSearch'] = 'TestSearchForPhone';
$params['filters'] = ['location_type_id' => 1];
$result = civicrm_api4('SearchDisplay', 'run', $params);
$this->assertCount(4, $result);

$params['savedSearch'] = 'TestSearchForEmail';
$params['filters'] = ['location_type_id' => 1, 'contact_id.display_name' => __FUNCTION__];
$result = civicrm_api4('SearchDisplay', 'run', $params);
$this->assertCount(1, $result);

// Email filter will not work because it's in the wrong fieldset on the form
$params['filters'] = ['email' => $email, 'contact_id.display_name' => __FUNCTION__];
$result = civicrm_api4('SearchDisplay', 'run', $params);
$this->assertCount(3, $result);

// No filters will work; they are in the fieldset belonging to the non-default display
$params['savedSearch'] = 'TestContactEmailSearch';
$params['filters'] = ['source' => 'afform_multi_test', 'Contact_Email_contact_id_01.location_type_id' => 1];
$result = civicrm_api4('SearchDisplay', 'run', $params);
$this->assertGreaterThanOrEqual(3, $result->count());

// Now the filters will work because they are in the fieldset for this display
$params['display'] = 'TestContactEmailDisplay';
$result = civicrm_api4('SearchDisplay', 'run', $params);
$this->assertCount(1, $result);
}

public function testSearchReferencesToAfform() {
$search = SavedSearch::create(FALSE)
->setValues([
Expand Down