From bc2e41180d9969f92e6d39123b79279551b12db8 Mon Sep 17 00:00:00 2001 From: colemanw Date: Mon, 21 Aug 2023 09:14:58 -0400 Subject: [PATCH] SearchKit - Add @searchFields metadata to specify default search display fields per-entity - Use Contact.sort_name instead of display_name for searches and Autocompletes - Adds more useful fields by default to a new SearchKit display - Makes use of new metadata to improve generated default Autocomplete display --- CRM/Pledge/DAO/Pledge.php | 3 +- Civi/Api4/Contact.php | 1 + Civi/Api4/Entity.php | 5 ++ Civi/Api4/Generic/AbstractEntity.php | 5 +- Civi/Api4/Relationship.php | 1 + Civi/Api4/RelationshipCache.php | 1 + .../ActivityAutocompleteProvider.php | 6 +- .../Autocomplete/CaseAutocompleteProvider.php | 6 +- .../ContactAutocompleteProvider.php | 2 +- .../ContributionAutocompleteProvider.php | 6 +- .../ContributionRecurAutocompleteProvider.php | 6 +- .../ParticipantAutocompleteProvider.php | 57 ------------- .../PledgeAutocompleteProvider.php | 6 +- .../RelationshipAutocompleteProvider.php | 2 +- Civi/Api4/Utils/CoreUtil.php | 9 ++ .../api/v4/AfformAutocompleteUsageTest.php | 12 +-- .../Civi/Api4/Contribution.php | 1 + ext/civi_event/Civi/Api4/Participant.php | 1 + ext/civi_member/Civi/Api4/Membership.php | 1 + ext/civi_pledge/Civi/Api4/Pledge.php | 1 + ext/civigrant/Civi/Api4/Grant.php | 1 + .../GrantAutocompleteProvider.php | 83 ------------------- .../Api4/Action/SearchDisplay/GetDefault.php | 2 +- .../Subscriber/DefaultDisplaySubscriber.php | 30 +++++-- ext/search_kit/Civi/Search/Admin.php | 55 ++++++++---- .../crmSearchAdmin.component.js | 23 +++-- ...earchAdminDisplayAutocomplete.component.js | 5 +- .../api/v4/Action/AutocompleteTest.php | 6 +- tests/phpunit/api/v4/Entity/EntityTest.php | 11 ++- xml/schema/Pledge/Pledge.xml | 1 + 30 files changed, 138 insertions(+), 211 deletions(-) delete mode 100644 Civi/Api4/Service/Autocomplete/ParticipantAutocompleteProvider.php delete mode 100644 ext/civigrant/Civi/Api4/Service/Autocomplete/GrantAutocompleteProvider.php diff --git a/CRM/Pledge/DAO/Pledge.php b/CRM/Pledge/DAO/Pledge.php index f383046651..e8d5012217 100644 --- a/CRM/Pledge/DAO/Pledge.php +++ b/CRM/Pledge/DAO/Pledge.php @@ -6,7 +6,7 @@ * * Generated from xml/schema/CRM/Pledge/Pledge.xml * DO NOT EDIT. Generated by CRM_Core_CodeGen - * (GenCodeChecksum:de226da85d80eda7f8ffef798215ad2a) + * (GenCodeChecksum:91fbbe8481e26df491af63fd07594f20) */ /** @@ -819,6 +819,7 @@ class CRM_Pledge_DAO_Pledge extends CRM_Core_DAO { 'localizable' => 0, 'html' => [ 'type' => 'Select', + 'label' => ts("Status"), ], 'pseudoconstant' => [ 'optionGroupName' => 'pledge_status', diff --git a/Civi/Api4/Contact.php b/Civi/Api4/Contact.php index b8ddca6aa0..eeb05eb9a1 100644 --- a/Civi/Api4/Contact.php +++ b/Civi/Api4/Contact.php @@ -21,6 +21,7 @@ namespace Civi\Api4; * @see https://docs.civicrm.org/user/en/latest/organising-your-data/contacts/ * @searchable primary * @orderBy sort_name + * @searchFields sort_name * @iconField contact_sub_type:icon,contact_type:icon * @since 5.19 * @package Civi\Api4 diff --git a/Civi/Api4/Entity.php b/Civi/Api4/Entity.php index 5f36793aa7..fb2b4780e9 100644 --- a/Civi/Api4/Entity.php +++ b/Civi/Api4/Entity.php @@ -74,6 +74,11 @@ class Entity extends Generic\AbstractEntity { 'name' => 'label_field', 'description' => 'Field to show when displaying a record', ], + [ + 'name' => 'search_fields', + 'data_type' => 'Array', + 'description' => 'Fields to show in search context', + ], [ 'name' => 'icon_field', 'data_type' => 'Array', diff --git a/Civi/Api4/Generic/AbstractEntity.php b/Civi/Api4/Generic/AbstractEntity.php index 083e3359e2..6e5a8e0cce 100644 --- a/Civi/Api4/Generic/AbstractEntity.php +++ b/Civi/Api4/Generic/AbstractEntity.php @@ -177,7 +177,10 @@ abstract class AbstractEntity { $info[$field['name']] = $val; } } - + // search_fields defaults to label_field + if (empty($info['search_fields']) && !empty($info['label_field'])) { + $info['search_fields'] = [$info['label_field']]; + } if ($dao) { $info['description'] = $dao::getEntityDescription() ?? $info['description'] ?? NULL; } diff --git a/Civi/Api4/Relationship.php b/Civi/Api4/Relationship.php index bed75391c4..2532d3a65c 100644 --- a/Civi/Api4/Relationship.php +++ b/Civi/Api4/Relationship.php @@ -15,6 +15,7 @@ namespace Civi\Api4; * * @see https://docs.civicrm.org/user/en/latest/organising-your-data/relationships/ * @searchable none + * @searchFields contact_id_a.sort_name,relationship_type_id.label_a_b,contact_id_b.sort_name * @since 5.19 * @package Civi\Api4 */ diff --git a/Civi/Api4/RelationshipCache.php b/Civi/Api4/RelationshipCache.php index 3beee1ab43..1b3e350fcf 100644 --- a/Civi/Api4/RelationshipCache.php +++ b/Civi/Api4/RelationshipCache.php @@ -14,6 +14,7 @@ namespace Civi\Api4; * RelationshipCache - readonly table to facilitate joining and finding contacts by relationship. * * @searchable secondary + * @searchFields near_contact_id.sort_name,near_relation:label,far_contact_id.sort_name * @see \Civi\Api4\Relationship * @ui_join_filters near_relation * @since 5.29 diff --git a/Civi/Api4/Service/Autocomplete/ActivityAutocompleteProvider.php b/Civi/Api4/Service/Autocomplete/ActivityAutocompleteProvider.php index 144035c90a..843ef5fea3 100644 --- a/Civi/Api4/Service/Autocomplete/ActivityAutocompleteProvider.php +++ b/Civi/Api4/Service/Autocomplete/ActivityAutocompleteProvider.php @@ -36,7 +36,7 @@ class ActivityAutocompleteProvider extends \Civi\Core\Service\AutoService implem 'id', 'subject', 'activity_date_time', - 'Activity_ActivityContact_Contact_01.display_name', + 'Activity_ActivityContact_Contact_01.sort_name', 'activity_type_id:label', ], 'orderBy' => [], @@ -97,8 +97,8 @@ class ActivityAutocompleteProvider extends \Civi\Core\Service\AutoService implem [$entity, $contactAlias] = explode(' AS ', $join[0]); if ($entity === 'Contact') { array_unshift($e->display['settings']['sort'], ["$contactAlias.sort_name", 'ASC']); - $e->display['settings']['columns'][0]['rewrite'] = "[$contactAlias.display_name] - [subject]"; - $e->display['settings']['columns'][0]['empty_value'] = "[$contactAlias.display_name] (" . ts('no subject') . ')'; + $e->display['settings']['columns'][0]['rewrite'] = "[$contactAlias.sort_name] - [subject]"; + $e->display['settings']['columns'][0]['empty_value'] = "[$contactAlias.sort_name] (" . ts('no subject') . ')'; break; } } diff --git a/Civi/Api4/Service/Autocomplete/CaseAutocompleteProvider.php b/Civi/Api4/Service/Autocomplete/CaseAutocompleteProvider.php index ed13b5c951..e28e6fbaf9 100644 --- a/Civi/Api4/Service/Autocomplete/CaseAutocompleteProvider.php +++ b/Civi/Api4/Service/Autocomplete/CaseAutocompleteProvider.php @@ -35,7 +35,7 @@ class CaseAutocompleteProvider extends \Civi\Core\Service\AutoService implements 'select' => [ 'id', 'subject', - 'Case_CaseContact_Contact_01.display_name', + 'Case_CaseContact_Contact_01.sort_name', 'case_type_id:label', 'status_id:label', 'start_date', @@ -94,8 +94,8 @@ class CaseAutocompleteProvider extends \Civi\Core\Service\AutoService implements [$entity, $contactAlias] = explode(' AS ', $join[0]); if ($entity === 'Contact') { array_unshift($e->display['settings']['sort'], ["$contactAlias.sort_name", 'ASC']); - $e->display['settings']['columns'][0]['rewrite'] = "[$contactAlias.display_name] - [subject]"; - $e->display['settings']['columns'][0]['empty_value'] = "[$contactAlias.display_name] (" . ts('no subject') . ')'; + $e->display['settings']['columns'][0]['rewrite'] = "[$contactAlias.sort_name] - [subject]"; + $e->display['settings']['columns'][0]['empty_value'] = "[$contactAlias.sort_name] (" . ts('no subject') . ')'; break; } } diff --git a/Civi/Api4/Service/Autocomplete/ContactAutocompleteProvider.php b/Civi/Api4/Service/Autocomplete/ContactAutocompleteProvider.php index 1aadc64464..0d292aa0db 100644 --- a/Civi/Api4/Service/Autocomplete/ContactAutocompleteProvider.php +++ b/Civi/Api4/Service/Autocomplete/ContactAutocompleteProvider.php @@ -37,7 +37,7 @@ class ContactAutocompleteProvider extends \Civi\Core\Service\AutoService impleme 'columns' => [ [ 'type' => 'field', - 'key' => 'display_name', + 'key' => 'sort_name', 'icons' => [ ['field' => 'contact_sub_type:icon'], ['field' => 'contact_type:icon'], diff --git a/Civi/Api4/Service/Autocomplete/ContributionAutocompleteProvider.php b/Civi/Api4/Service/Autocomplete/ContributionAutocompleteProvider.php index 916a6c56d6..114b3f4b29 100644 --- a/Civi/Api4/Service/Autocomplete/ContributionAutocompleteProvider.php +++ b/Civi/Api4/Service/Autocomplete/ContributionAutocompleteProvider.php @@ -34,7 +34,7 @@ class ContributionAutocompleteProvider extends \Civi\Core\Service\AutoService im 'version' => 4, 'select' => [ 'id', - 'contact_id.display_name', + 'contact_id.sort_name', 'total_amount', 'receive_date', 'financial_type_id:label', @@ -66,8 +66,8 @@ class ContributionAutocompleteProvider extends \Civi\Core\Service\AutoService im 'columns' => [ [ 'type' => 'field', - 'key' => 'contact_id.display_name', - 'rewrite' => '[contact_id.display_name] - [total_amount]', + 'key' => 'contact_id.sort_name', + 'rewrite' => '[contact_id.sort_name] - [total_amount]', ], [ 'type' => 'field', diff --git a/Civi/Api4/Service/Autocomplete/ContributionRecurAutocompleteProvider.php b/Civi/Api4/Service/Autocomplete/ContributionRecurAutocompleteProvider.php index 4f845248cb..7d3ddae797 100644 --- a/Civi/Api4/Service/Autocomplete/ContributionRecurAutocompleteProvider.php +++ b/Civi/Api4/Service/Autocomplete/ContributionRecurAutocompleteProvider.php @@ -34,7 +34,7 @@ class ContributionRecurAutocompleteProvider extends \Civi\Core\Service\AutoServi 'version' => 4, 'select' => [ 'id', - 'contact_id.display_name', + 'contact_id.sort_name', 'frequency_unit:label', 'frequency_interval', 'amount', @@ -68,8 +68,8 @@ class ContributionRecurAutocompleteProvider extends \Civi\Core\Service\AutoServi 'columns' => [ [ 'type' => 'field', - 'key' => 'contact_id.display_name', - 'rewrite' => '[contact_id.display_name] - [amount]', + 'key' => 'contact_id.sort_name', + 'rewrite' => '[contact_id.sort_name] - [amount]', ], [ 'type' => 'field', diff --git a/Civi/Api4/Service/Autocomplete/ParticipantAutocompleteProvider.php b/Civi/Api4/Service/Autocomplete/ParticipantAutocompleteProvider.php deleted file mode 100644 index 2131e3cd8d..0000000000 --- a/Civi/Api4/Service/Autocomplete/ParticipantAutocompleteProvider.php +++ /dev/null @@ -1,57 +0,0 @@ -display['settings'] || $e->display['type'] !== 'autocomplete' || $e->savedSearch['api_entity'] !== 'Participant') { - return; - } - $e->display['settings'] = [ - 'sort' => [ - ['contact_id.sort_name', 'ASC'], - ['event_id.title', 'ASC'], - ], - 'columns' => [ - [ - 'type' => 'field', - 'key' => 'contact_id.display_name', - 'rewrite' => '[contact_id.display_name] - [event_id.title]', - ], - [ - 'type' => 'field', - 'key' => 'role_id:label', - 'rewrite' => '#[id] [role_id:label]', - ], - [ - 'type' => 'field', - 'key' => 'status_id:label', - ], - ], - ]; - } - -} diff --git a/Civi/Api4/Service/Autocomplete/PledgeAutocompleteProvider.php b/Civi/Api4/Service/Autocomplete/PledgeAutocompleteProvider.php index 7e7e22964b..3a38d53956 100644 --- a/Civi/Api4/Service/Autocomplete/PledgeAutocompleteProvider.php +++ b/Civi/Api4/Service/Autocomplete/PledgeAutocompleteProvider.php @@ -34,7 +34,7 @@ class PledgeAutocompleteProvider extends \Civi\Core\Service\AutoService implemen 'version' => 4, 'select' => [ 'id', - 'contact_id.display_name', + 'contact_id.sort_name', 'amount', 'start_date', 'end_date', @@ -66,8 +66,8 @@ class PledgeAutocompleteProvider extends \Civi\Core\Service\AutoService implemen 'columns' => [ [ 'type' => 'field', - 'key' => 'contact_id.display_name', - 'rewrite' => '[contact_id.display_name] - [amount]', + 'key' => 'contact_id.sort_name', + 'rewrite' => '[contact_id.sort_name] - [amount]', ], [ 'type' => 'field', diff --git a/Civi/Api4/Service/Autocomplete/RelationshipAutocompleteProvider.php b/Civi/Api4/Service/Autocomplete/RelationshipAutocompleteProvider.php index f86bcbaae6..bbae3255cf 100644 --- a/Civi/Api4/Service/Autocomplete/RelationshipAutocompleteProvider.php +++ b/Civi/Api4/Service/Autocomplete/RelationshipAutocompleteProvider.php @@ -38,7 +38,7 @@ class RelationshipAutocompleteProvider extends \Civi\Core\Service\AutoService im [ 'type' => 'field', 'key' => 'relationship_type_id.label_a_b', - 'rewrite' => '[contact_id_a.display_name] [relationship_type_id.label_a_b] [contact_id_b.display_name]', + 'rewrite' => '[contact_id_a.sort_name] [relationship_type_id.label_a_b] [contact_id_b.sort_name]', ], [ 'type' => 'field', diff --git a/Civi/Api4/Utils/CoreUtil.php b/Civi/Api4/Utils/CoreUtil.php index 865aa7be14..76db7022f7 100644 --- a/Civi/Api4/Utils/CoreUtil.php +++ b/Civi/Api4/Utils/CoreUtil.php @@ -81,6 +81,15 @@ class CoreUtil { return self::getInfoItem($entityName, 'primary_key')[0] ?? 'id'; } + /** + * Get name of field(s) to display in search context + * @param string $entityName + * @return array + */ + public static function getSearchFields(string $entityName): array { + return self::getInfoItem($entityName, 'search_fields') ?: []; + } + /** * Get table name of given entity * diff --git a/ext/afform/mock/tests/phpunit/api/v4/AfformAutocompleteUsageTest.php b/ext/afform/mock/tests/phpunit/api/v4/AfformAutocompleteUsageTest.php index 43b88fcf5a..ade9f0c09c 100644 --- a/ext/afform/mock/tests/phpunit/api/v4/AfformAutocompleteUsageTest.php +++ b/ext/afform/mock/tests/phpunit/api/v4/AfformAutocompleteUsageTest.php @@ -68,8 +68,8 @@ EOHTML; ->execute(); $this->assertCount(2, $result); - $this->assertEquals('A ' . $lastName, $result[0]['label']); - $this->assertEquals('B ' . $lastName, $result[1]['label']); + $this->assertEquals($lastName . ', A', $result[0]['label']); + $this->assertEquals($lastName . ', B', $result[1]['label']); // Ensure form validates submission, restricting it to contacts A & B $values = [ @@ -168,8 +168,8 @@ EOHTML; ->execute(); $this->assertCount(2, $result); - $this->assertEquals('A ' . $lastName, $result[0]['label']); - $this->assertEquals('B ' . $lastName, $result[1]['label']); + $this->assertEquals($lastName . ', A', $result[0]['label']); + $this->assertEquals($lastName . ', B', $result[1]['label']); // Ensure form validates submission, restricting it to contacts A & B $values = [ @@ -262,8 +262,8 @@ EOHTML; ->execute(); $this->assertCount(2, $result); - $this->assertEquals('A ' . $lastName, $result[0]['label']); - $this->assertEquals('C ' . $lastName, $result[1]['label']); + $this->assertEquals($lastName . ', A', $result[0]['label']); + $this->assertEquals($lastName . ', C', $result[1]['label']); // Ensure form validates submission, restricting it to contacts A & C $values = [ diff --git a/ext/civi_contribute/Civi/Api4/Contribution.php b/ext/civi_contribute/Civi/Api4/Contribution.php index 67d61be1f7..b2a275d353 100644 --- a/ext/civi_contribute/Civi/Api4/Contribution.php +++ b/ext/civi_contribute/Civi/Api4/Contribution.php @@ -14,6 +14,7 @@ namespace Civi\Api4; * Contribution entity. * * @searchable primary + * @searchFields contact_id.sort_name,total_amount * @since 5.19 * @package Civi\Api4 */ diff --git a/ext/civi_event/Civi/Api4/Participant.php b/ext/civi_event/Civi/Api4/Participant.php index 425020067e..3224d94bff 100644 --- a/ext/civi_event/Civi/Api4/Participant.php +++ b/ext/civi_event/Civi/Api4/Participant.php @@ -14,6 +14,7 @@ namespace Civi\Api4; * Participant entity, stores the participation record of a contact in an event. * * @searchable primary + * @searchFields contact_id.sort_name,event_id.title * @since 5.19 * @package Civi\Api4 */ diff --git a/ext/civi_member/Civi/Api4/Membership.php b/ext/civi_member/Civi/Api4/Membership.php index 751cd65e32..1a8eda1d21 100644 --- a/ext/civi_member/Civi/Api4/Membership.php +++ b/ext/civi_member/Civi/Api4/Membership.php @@ -14,6 +14,7 @@ namespace Civi\Api4; * Membership entity. * * @searchable primary + * @searchFields contact_id.sort_name * @since 5.42 * @package Civi\Api4 */ diff --git a/ext/civi_pledge/Civi/Api4/Pledge.php b/ext/civi_pledge/Civi/Api4/Pledge.php index c571f9d464..df39ede1ef 100644 --- a/ext/civi_pledge/Civi/Api4/Pledge.php +++ b/ext/civi_pledge/Civi/Api4/Pledge.php @@ -15,6 +15,7 @@ namespace Civi\Api4; * * @see https://docs.civicrm.org/user/en/latest/pledges/what-is-civipledge/ * @searchable primary + * @searchFields contact_id.display_name,amount * @since 5.35 * @package Civi\Api4 */ diff --git a/ext/civigrant/Civi/Api4/Grant.php b/ext/civigrant/Civi/Api4/Grant.php index 96caf2375e..83ad0f87d5 100644 --- a/ext/civigrant/Civi/Api4/Grant.php +++ b/ext/civigrant/Civi/Api4/Grant.php @@ -18,6 +18,7 @@ namespace Civi\Api4; * @see https://docs.civicrm.org/user/en/latest/grants/what-is-civigrant/ * * @searchable primary + * @searchFields contact_id.sort_name,grant_type_id:label * @since 5.33 * @package Civi\Api4 */ diff --git a/ext/civigrant/Civi/Api4/Service/Autocomplete/GrantAutocompleteProvider.php b/ext/civigrant/Civi/Api4/Service/Autocomplete/GrantAutocompleteProvider.php deleted file mode 100644 index e5c0388126..0000000000 --- a/ext/civigrant/Civi/Api4/Service/Autocomplete/GrantAutocompleteProvider.php +++ /dev/null @@ -1,83 +0,0 @@ -savedSearch) || $e->savedSearch['api_entity'] !== 'Grant') { - return; - } - $e->savedSearch['api_params'] = [ - 'version' => 4, - 'select' => [ - 'id', - 'contact_id.display_name', - 'grant_type_id:label', - 'financial_type_id:label', - 'status_id:label', - ], - 'orderBy' => [], - 'where' => [], - 'groupBy' => [], - 'join' => [], - 'having' => [], - ]; - } - - /** - * Provide default SearchDisplay for Grant autocompletes - * - * @param \Civi\Core\Event\GenericHookEvent $e - */ - public static function on_civi_search_defaultDisplay(GenericHookEvent $e) { - if ($e->display['settings'] || $e->display['type'] !== 'autocomplete' || $e->savedSearch['api_entity'] !== 'Grant') { - return; - } - $e->display['settings'] = [ - 'sort' => [ - ['contact_id.sort_name', 'ASC'], - ['application_received_date', 'DESC'], - ], - 'columns' => [ - [ - 'type' => 'field', - 'key' => 'contact_id.display_name', - 'rewrite' => '[contact_id.display_name] - [grant_type_id:label]', - ], - [ - 'type' => 'field', - 'key' => 'financial_type_id:label', - 'rewrite' => '#[id] [status_id:label]', - ], - [ - 'type' => 'field', - 'key' => 'financial_type_id:label', - ], - ], - ]; - } - -} diff --git a/ext/search_kit/Civi/Api4/Action/SearchDisplay/GetDefault.php b/ext/search_kit/Civi/Api4/Action/SearchDisplay/GetDefault.php index 994e2018ec..407b6ba1c1 100644 --- a/ext/search_kit/Civi/Api4/Action/SearchDisplay/GetDefault.php +++ b/ext/search_kit/Civi/Api4/Action/SearchDisplay/GetDefault.php @@ -195,7 +195,7 @@ class GetDefault extends \Civi\Api4\Generic\AbstractAction { if ($clause['expr'] instanceof SqlField || $clause['expr'] instanceof SqlFunctionGROUP_CONCAT) { $field = \CRM_Utils_Array::first($clause['fields'] ?? []); if ($field && - CoreUtil::getInfoItem($field['entity'], 'label_field') === $field['name'] && + in_array($field['name'], array_merge(CoreUtil::getSearchFields($field['entity']), [CoreUtil::getInfoItem($field['entity'], 'label_field')]), TRUE) && !empty(CoreUtil::getInfoItem($field['entity'], 'paths')['view']) ) { $col['link'] = [ diff --git a/ext/search_kit/Civi/Api4/Event/Subscriber/DefaultDisplaySubscriber.php b/ext/search_kit/Civi/Api4/Event/Subscriber/DefaultDisplaySubscriber.php index 20fa233a4b..03649969ba 100644 --- a/ext/search_kit/Civi/Api4/Event/Subscriber/DefaultDisplaySubscriber.php +++ b/ext/search_kit/Civi/Api4/Event/Subscriber/DefaultDisplaySubscriber.php @@ -59,8 +59,8 @@ class DefaultDisplaySubscriber extends \Civi\Core\Service\AutoService implements throw new \CRM_Core_Exception("Entity name is required to get autocomplete default display."); } $idField = CoreUtil::getIdFieldName($entityName); - $labelField = CoreUtil::getInfoItem($entityName, 'label_field'); - if (!$labelField) { + $searchFields = CoreUtil::getSearchFields($entityName); + if (!$searchFields) { throw new \CRM_Core_Exception("Entity $entityName has no default label field."); } @@ -69,11 +69,17 @@ class DefaultDisplaySubscriber extends \Civi\Core\Service\AutoService implements $apiGet = Request::create($entityName, 'get', ['version' => 4]); $fields = $apiGet->entityFields(); - $columns = [$labelField]; + $columns = array_slice($searchFields, 0, 1); // Add grouping fields like "event_type_id" in the description - $grouping = (array) (CoreUtil::getCustomGroupExtends($entityName)['grouping'] ?? []); + $grouping = (array) (CoreUtil::getCustomGroupExtends($entityName)['grouping'] ?? ['financial_type_id']); foreach ($grouping as $fieldName) { - $columns[] = "$fieldName:label"; + if (!empty($fields[$fieldName]['options']) && !in_array("$fieldName:label", $searchFields)) { + $columns[] = "$fieldName:label"; + } + } + $statusField = $fields['status_id'] ?? $fields[strtolower($entityName) . '_status_id'] ?? NULL; + if (!empty($statusField['options']) && !in_array("{$statusField['name']}:label", $searchFields)) { + $columns[] = "{$statusField['name']}:label"; } if (isset($fields['description'])) { $columns[] = 'description'; @@ -86,11 +92,15 @@ class DefaultDisplaySubscriber extends \Civi\Core\Service\AutoService implements 'key' => $columnField, ]; } + if (count($searchFields) > 1) { + $e->display['settings']['columns'][0]['rewrite'] = '[' . implode('] - [', $searchFields) . ']'; + } // Include entity id on the second line $e->display['settings']['columns'][1] = [ 'type' => 'field', - 'key' => $idField, + 'key' => $columns[1] ?? $idField, 'rewrite' => "#[$idField]" . (isset($columns[1]) ? " [$columns[1]]" : ''), + 'empty_value' => "#[$idField]", ]; // Default icons @@ -152,8 +162,12 @@ class DefaultDisplaySubscriber extends \Civi\Core\Service\AutoService implements * @return array */ protected static function getDefaultSort($entityName) { - $sortField = CoreUtil::getInfoItem($entityName, 'order_by') ?: CoreUtil::getInfoItem($entityName, 'label_field'); - return $sortField ? [[$sortField, 'ASC']] : []; + $result = []; + $sortFields = (array) (CoreUtil::getInfoItem($entityName, 'order_by') ?: CoreUtil::getSearchFields($entityName)); + foreach ($sortFields as $sortField) { + $result[] = [$sortField, 'ASC']; + } + return $result; } } diff --git a/ext/search_kit/Civi/Search/Admin.php b/ext/search_kit/Civi/Search/Admin.php index 1ebf866016..90f317a75a 100644 --- a/ext/search_kit/Civi/Search/Admin.php +++ b/ext/search_kit/Civi/Search/Admin.php @@ -131,7 +131,7 @@ class Admin { public static function getSchema(): array { $schema = []; $entities = Entity::get() - ->addSelect('name', 'title', 'title_plural', 'bridge_title', 'type', 'primary_key', 'description', 'label_field', 'icon', 'dao', 'bridge', 'ui_join_filters', 'searchable', 'order_by') + ->addSelect('name', 'title', 'title_plural', 'bridge_title', 'type', 'primary_key', 'description', 'label_field', 'search_fields', 'icon', 'dao', 'bridge', 'ui_join_filters', 'searchable', 'order_by') ->addWhere('searchable', '!=', 'none') ->addOrderBy('title_plural') ->setChain([ @@ -154,7 +154,7 @@ class Admin { 'select' => ['name', 'title', 'label', 'description', 'type', 'options', 'input_type', 'input_attrs', 'data_type', 'serialize', 'entity', 'fk_entity', 'readonly', 'operators', 'suffixes', 'nullable'], 'where' => [['deprecated', '=', FALSE], ['name', 'NOT IN', ['api_key', 'hash']]], 'orderBy' => ['label'], - ]); + ])->indexBy('name'); } catch (\CRM_Core_Exception $e) { \Civi::log()->warning('Entity could not be loaded', ['entity' => $entity['name']]); @@ -170,6 +170,22 @@ class Admin { } $entity['fields'][] = $field; } + $defaultColumns = CoreUtil::getSearchFields($entity['name']); + // Add grouping fields like "event_type_id" + status_id + description if available + $grouping = (array) (CoreUtil::getCustomGroupExtends($entity['name'])['grouping'] ?? ['financial_type_id']); + foreach ($grouping as $fieldName) { + if (!empty($getFields[$fieldName]['options'])) { + $defaultColumns[] = "$fieldName:label"; + } + } + $statusField = $getFields['status_id'] ?? $getFields[strtolower($entity['name']) . '_status_id'] ?? NULL; + if (!empty($statusField['options'])) { + $defaultColumns[] = $statusField['name'] . ':label'; + } + if (isset($getFields['description'])) { + $defaultColumns[] = 'description'; + } + $entity['default_columns'] = array_values(array_unique($defaultColumns)); $params = $entity['get'][0]; // Entity must support at least these params or it is too weird for search kit if (!array_diff(['select', 'where', 'orderBy', 'limit', 'offset'], array_keys($params))) { @@ -194,22 +210,27 @@ class Admin { foreach ($schema as &$entity) { if ($entity['searchable'] !== 'bridge') { foreach (array_reverse($entity['fields'] ?? [], TRUE) as $index => $field) { - if (!empty($field['fk_entity']) && !$field['options'] && !$field['suffixes'] && !empty($schema[$field['fk_entity']]['label_field'])) { - $isCustom = strpos($field['name'], '.'); - // Custom fields: append "Contact ID" etc. to original field label - if ($isCustom) { - $idField = array_column($schema[$field['fk_entity']]['fields'], NULL, 'name')['id']; - $entity['fields'][$index]['label'] .= ' ' . $idField['title']; - } - // DAO fields: use title instead of label since it represents the id (title usually ends in ID but label does not) - else { - $entity['fields'][$index]['label'] = $field['title']; + if (!empty($field['fk_entity']) && !$field['options'] && !$field['suffixes'] && !empty($schema[$field['fk_entity']]['search_fields'])) { + $labelFields = array_unique(array_merge($schema[$field['fk_entity']]['search_fields'], (array) ($schema[$field['fk_entity']]['label_field'] ?? []))); + foreach ($labelFields as $labelField) { + $isCustom = strpos($field['name'], '.'); + // Custom fields: append "Contact ID" etc. to original field label + if ($isCustom) { + $idField = array_column($schema[$field['fk_entity']]['fields'], NULL, 'name')['id']; + $entity['fields'][$index]['label'] .= ' ' . $idField['title']; + } + // DAO fields: use title instead of label since it represents the id (title usually ends in ID but label does not) + else { + $entity['fields'][$index]['label'] = $field['title']; + } + // Add the label field from the other entity to this entity's list of fields + $newField = \CRM_Utils_Array::findAll($schema[$field['fk_entity']]['fields'], ['name' => $labelField])[0] ?? NULL; + if ($newField) { + $newField['name'] = $field['name'] . '.' . $labelField; + $newField['label'] = $field['label'] . ' ' . $newField['label']; + array_splice($entity['fields'], $index + 1, 0, [$newField]); + } } - // Add the label field from the other entity to this entity's list of fields - $newField = \CRM_Utils_Array::findAll($schema[$field['fk_entity']]['fields'], ['name' => $schema[$field['fk_entity']]['label_field']])[0]; - $newField['name'] = $field['name'] . '.' . $schema[$field['fk_entity']]['label_field']; - $newField['label'] = $field['label'] . ' ' . $newField['label']; - array_splice($entity['fields'], $index, 0, [$newField]); } } // Useful address fields (see ContactSchemaMapSubscriber) diff --git a/ext/search_kit/ang/crmSearchAdmin/crmSearchAdmin.component.js b/ext/search_kit/ang/crmSearchAdmin/crmSearchAdmin.component.js index 34633bed32..970a83a86d 100644 --- a/ext/search_kit/ang/crmSearchAdmin/crmSearchAdmin.component.js +++ b/ext/search_kit/ang/crmSearchAdmin/crmSearchAdmin.component.js @@ -61,7 +61,7 @@ if (!this.savedSearch.id) { var defaults = { version: 4, - select: getDefaultSelect(), + select: searchMeta.getEntity(ctrl.savedSearch.api_entity).default_columns, orderBy: {}, where: [], }; @@ -329,8 +329,15 @@ params.push(condition); }); ctrl.savedSearch.api_params.join.push(params); - if (entity.label_field && $scope.controls.joinType !== 'EXCLUDE') { - ctrl.savedSearch.api_params.select.push(join.alias + '.' + entity.label_field); + if (entity.search_fields && $scope.controls.joinType !== 'EXCLUDE') { + // Add columns for newly-joined entity + entity.search_fields.forEach((fieldName) => { + // Try to avoid adding duplicate columns + const simpleName = _.last(fieldName.split('.')); + if (!ctrl.savedSearch.api_params.select.join(',').includes(simpleName)) { + ctrl.savedSearch.api_params.select.push(join.alias + '.' + fieldName); + } + }); } loadFieldOptions(); } @@ -535,16 +542,6 @@ return {results: ctrl.getSelectFields()}; }; - // Sets the default select clause based on commonly-named fields - function getDefaultSelect() { - var entity = searchMeta.getEntity(ctrl.savedSearch.api_entity); - return _.transform(entity.fields, function(defaultSelect, field) { - if (field.name === 'id' || field.name === entity.label_field) { - defaultSelect.push(field.name); - } - }); - } - this.getAllFields = function(suffix, allowedTypes, disabledIf, topJoin) { disabledIf = disabledIf || _.noop; allowedTypes = allowedTypes || ['Field', 'Custom', 'Extra', 'Filter']; diff --git a/ext/search_kit/ang/crmSearchAdmin/displays/searchAdminDisplayAutocomplete.component.js b/ext/search_kit/ang/crmSearchAdmin/displays/searchAdminDisplayAutocomplete.component.js index df55c2e731..c8803ef76d 100644 --- a/ext/search_kit/ang/crmSearchAdmin/displays/searchAdminDisplayAutocomplete.component.js +++ b/ext/search_kit/ang/crmSearchAdmin/displays/searchAdminDisplayAutocomplete.component.js @@ -26,8 +26,9 @@ sort: ctrl.parent.getDefaultSort(), columns: [] }; - var labelField = searchMeta.getEntity(ctrl.apiEntity).label_field; - _.each([labelField, 'description'], function(field) { + var searchFields = searchMeta.getEntity(ctrl.apiEntity).search_fields || []; + searchFields.push('description'); + searchFields.forEach((field) => { if (_.includes(ctrl.parent.savedSearch.api_params.select, field)) { ctrl.display.settings.columns.push(searchMeta.fieldToColumn(field, {})); } diff --git a/tests/phpunit/api/v4/Action/AutocompleteTest.php b/tests/phpunit/api/v4/Action/AutocompleteTest.php index 415b7919de..d4fef2a474 100644 --- a/tests/phpunit/api/v4/Action/AutocompleteTest.php +++ b/tests/phpunit/api/v4/Action/AutocompleteTest.php @@ -135,11 +135,11 @@ class AutocompleteTest extends Api4TestBase implements HookInterface, Transactio ->execute(); // Contacts will be returned in order by sort_name - $this->assertStringStartsWith('Both', $result[0]['label']); + $this->assertStringEndsWith('Both', $result[0]['label']); $this->assertEquals('fa-star', $result[0]['icon']); - $this->assertStringStartsWith('No icon', $result[1]['label']); + $this->assertStringEndsWith('No icon', $result[1]['label']); $this->assertEquals('fa-user', $result[1]['icon']); - $this->assertStringStartsWith('Starry', $result[2]['label']); + $this->assertStringEndsWith('Starry', $result[2]['label']); $this->assertEquals('fa-star', $result[2]['icon']); } diff --git a/tests/phpunit/api/v4/Entity/EntityTest.php b/tests/phpunit/api/v4/Entity/EntityTest.php index 271e94cfca..6a65f5fbb6 100644 --- a/tests/phpunit/api/v4/Entity/EntityTest.php +++ b/tests/phpunit/api/v4/Entity/EntityTest.php @@ -28,17 +28,24 @@ use api\v4\Api4TestBase; class EntityTest extends Api4TestBase { public function testEntityGet() { + \CRM_Core_BAO_ConfigSetting::enableAllComponents(); $result = Entity::get(FALSE) ->execute() ->indexBy('name'); - $this->assertArrayHasKey('Entity', $result, - "Entity::get missing itself"); + $this->assertArrayHasKey('Entity', $result, "Entity::get missing itself"); $this->assertEquals('CRM_Contact_DAO_Contact', $result['Contact']['dao']); $this->assertEquals(['DAOEntity'], $result['Contact']['type']); $this->assertEquals(['id'], $result['Contact']['primary_key']); // Contact icon fields $this->assertEquals(['contact_sub_type:icon', 'contact_type:icon'], $result['Contact']['icon_field']); + // Label fields + $this->assertEquals('display_name', $result['Contact']['label_field']); + $this->assertEquals('title', $result['Event']['label_field']); + // Search fields + $this->assertEquals(['sort_name'], $result['Contact']['search_fields']); + $this->assertEquals(['title'], $result['Event']['search_fields']); + $this->assertEquals(['contact_id.sort_name', 'event_id.title'], $result['Participant']['search_fields']); } public function testEntity() { diff --git a/xml/schema/Pledge/Pledge.xml b/xml/schema/Pledge/Pledge.xml index 32dfa1a2d7..df479d0e6c 100644 --- a/xml/schema/Pledge/Pledge.xml +++ b/xml/schema/Pledge/Pledge.xml @@ -310,6 +310,7 @@ int unsigned Select + pledge_status -- 2.25.1