From ede387bbf5478b96f53684383e19213920c9fef6 Mon Sep 17 00:00:00 2001 From: Coleman Watts Date: Wed, 21 Apr 2021 15:27:52 -0400 Subject: [PATCH] SearchKit - Fix display of contact reference fields (single-value) This fixes the display of contact reference fields, giving feature parity with other FK fields. Both ID and Display Name are shown as available columns. This does not address the more difficult question of how to join multi-valued contact reference fields with contact display names. --- Civi/Api4/Service/Schema/SchemaMapBuilder.php | 8 +- ext/search/Civi/Search/Admin.php | 15 +++- ext/search/ang/crmSearchAdmin.module.js | 29 +++----- .../phpunit/api/v4/Action/CustomJoinTest.php | 73 +++++++++++++++++++ tests/phpunit/api/v4/AllTests.php | 41 ----------- 5 files changed, 102 insertions(+), 64 deletions(-) create mode 100644 tests/phpunit/api/v4/Action/CustomJoinTest.php diff --git a/Civi/Api4/Service/Schema/SchemaMapBuilder.php b/Civi/Api4/Service/Schema/SchemaMapBuilder.php index bcaad8ee350c..77666cb4a78d 100644 --- a/Civi/Api4/Service/Schema/SchemaMapBuilder.php +++ b/Civi/Api4/Service/Schema/SchemaMapBuilder.php @@ -91,6 +91,7 @@ private function addJoins(Table $table, $field, array $data) { if ($fkClass) { $tableName = AllCoreTables::getTableForClass($fkClass); $fkKey = $data['FKKeyColumn'] ?? 'id'; + // Fixme: Clumsy string manipulation to transform e.g. "contact_id" to "contact" - we never should have done this $alias = str_replace('_id', '', $field); $joinable = new Joinable($tableName, $fkKey, $alias); $joinable->setJoinType($joinable::JOIN_TYPE_MANY_TO_ONE); @@ -154,7 +155,7 @@ private function addCustomFields(SchemaMap $map, Table $baseTable, string $entit } $fieldData = \CRM_Utils_SQL_Select::from('civicrm_custom_field f') ->join('custom_group', 'INNER JOIN civicrm_custom_group g ON g.id = f.custom_group_id') - ->select(['g.name as custom_group_name', 'g.table_name', 'g.is_multiple', 'f.name', 'label', 'column_name', 'option_group_id']) + ->select(['g.name as custom_group_name', 'g.table_name', 'g.is_multiple', 'f.name', 'f.data_type', 'label', 'column_name', 'option_group_id']) ->where('g.extends IN (@entity)', ['@entity' => $customInfo['extends']]) ->where('g.is_active') ->where('f.is_active') @@ -182,6 +183,11 @@ private function addCustomFields(SchemaMap $map, Table $baseTable, string $entit $joinable = new Joinable($baseTable->getName(), $customInfo['column'], AllCoreTables::convertEntityNameToLower($entityName)); $customTable->addTableLink('entity_id', $joinable); } + + if ($fieldData->data_type === 'ContactReference') { + $joinable = new Joinable('civicrm_contact', 'id', $fieldData->name); + $customTable->addTableLink($fieldData->column_name, $joinable); + } } foreach ($links as $alias => $link) { diff --git a/ext/search/Civi/Search/Admin.php b/ext/search/Civi/Search/Admin.php index 92b2f605656f..5a28f6d6f054 100644 --- a/ext/search/Civi/Search/Admin.php +++ b/ext/search/Civi/Search/Admin.php @@ -136,11 +136,20 @@ public static function getSchema() { if (in_array('DAOEntity', $entity['type'], TRUE) && !in_array('EntityBridge', $entity['type'], TRUE)) { foreach (array_reverse($entity['fields'], TRUE) as $index => $field) { if (!empty($field['fk_entity']) && !$field['options'] && !empty($schema[$field['fk_entity']]['label_field'])) { - // The original field will get title instead of label since it represents the id (title usually ends in ID but label does not) - $entity['fields'][$index]['label'] = $field['title']; + $isCustom = strpos($field['name'], '.'); + // Custom fields: append "ID" to original field label + if ($isCustom) { + $entity['fields'][$index]['label'] .= ' ' . E::ts('Contact ID'); + } + // 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' => $schema[$field['fk_entity']]['label_field']])[0]; - $newField['name'] = str_replace('_id', '', $field['name']) . '.' . $schema[$field['fk_entity']]['label_field']; + // Due to string manipulation in \Civi\Api4\Service\Schema\SchemaMapBuilder::addJoins() + $alias = $isCustom ? $field['name'] : str_replace('_id', '', $field['name']); + $newField['name'] = $alias . '.' . $schema[$field['fk_entity']]['label_field']; $newField['label'] = $field['label'] . ' ' . $newField['label']; array_splice($entity['fields'], $index, 0, [$newField]); } diff --git a/ext/search/ang/crmSearchAdmin.module.js b/ext/search/ang/crmSearchAdmin.module.js index 88cdddbe6994..dfa3910d792c 100644 --- a/ext/search/ang/crmSearchAdmin.module.js +++ b/ext/search/ang/crmSearchAdmin.module.js @@ -105,7 +105,6 @@ return new RegExp('^' + join.alias + '_\\d\\d').test(path); }); if (!join) { - console.warn( 'Join ' + fullNameOrAlias + ' not found.'); return; } path = path.replace(join.alias + '_', ''); @@ -138,28 +137,20 @@ return result; } function getFieldAndJoin(fieldName, entityName) { - var dotSplit = fieldName.split('.'), - joinEntity = dotSplit.length > 1 ? dotSplit[0] : null, - name = _.last(dotSplit).split(':')[0], + var fieldPath = fieldName.split(':')[0], + dotSplit = fieldPath.split('.'), + name, join, field; - // Custom fields contain a dot in their fieldname - // If 3 segments, the first is the joinEntity and the last 2 are the custom field - if (dotSplit.length === 3) { - name = dotSplit[1] + '.' + name; - } - // If 2 segments, it's ambiguous whether this is a custom field or joined field. Search the main entity first. - if (dotSplit.length === 2) { - field = _.find(getEntity(entityName).fields, {name: dotSplit[0] + '.' + name}); - if (field) { - field.baseEntity = entityName; - return {field: field}; + // If 2 or more segments, the first might be the name of a join + if (dotSplit.length > 1) { + join = getJoin(dotSplit[0]); + if (join) { + dotSplit.shift(); + entityName = join.entity; } } - if (joinEntity) { - join = getJoin(joinEntity); - entityName = getJoin(joinEntity).entity; - } + name = dotSplit.join('.'); field = _.find(getEntity(entityName).fields, {name: name}); if (!field && join && join.bridge) { field = _.find(getEntity(join.bridge).fields, {name: name}); diff --git a/tests/phpunit/api/v4/Action/CustomJoinTest.php b/tests/phpunit/api/v4/Action/CustomJoinTest.php new file mode 100644 index 000000000000..9605577ee3da --- /dev/null +++ b/tests/phpunit/api/v4/Action/CustomJoinTest.php @@ -0,0 +1,73 @@ +addValue('name', 'MyContactRef') + ->addValue('extends', 'Individual') + ->execute() + ->first(); + + CustomField::create(FALSE) + ->addValue('label', 'FavPerson') + ->addValue('custom_group_id', $customGroup['id']) + ->addValue('html_type', 'Autocomplete-Select') + ->addValue('data_type', 'ContactReference') + ->execute(); + + $favPersonId = Contact::create(FALSE) + ->addValue('first_name', 'Favorite') + ->addValue('last_name', 'Person') + ->addValue('contact_type', 'Individual') + ->execute() + ->first()['id']; + + $contactId = Contact::create(FALSE) + ->addValue('first_name', 'Mya') + ->addValue('last_name', 'Tester') + ->addValue('contact_type', 'Individual') + ->addValue('MyContactRef.FavPerson', $favPersonId) + ->execute() + ->first()['id']; + + $contact = Contact::get(FALSE) + ->addSelect('display_name') + ->addSelect('MyContactRef.FavPerson.first_name') + ->addSelect('MyContactRef.FavPerson.last_name') + ->addWhere('id', '=', $contactId) + ->execute() + ->first(); + + $this->assertEquals('Favorite', $contact['MyContactRef.FavPerson.first_name']); + $this->assertEquals('Person', $contact['MyContactRef.FavPerson.last_name']); + } + +} diff --git a/tests/phpunit/api/v4/AllTests.php b/tests/phpunit/api/v4/AllTests.php index 67304b923a66..0a4965ecc559 100644 --- a/tests/phpunit/api/v4/AllTests.php +++ b/tests/phpunit/api/v4/AllTests.php @@ -16,37 +16,6 @@ * @copyright CiviCRM LLC https://civicrm.org/licensing */ -// vim: set si ai expandtab tabstop=4 shiftwidth=4 softtabstop=4: - -/** - * File for the api_v4_AllTests class - * - * (PHP 5) - * - * @author Walt Haas (801) 534-1262 - * @copyright Copyright CiviCRM LLC (C) 2009 - * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html - * GNU Affero General Public License version 3 - * @version $Id: AllTests.php 40328 2012-05-11 23:06:13Z allen $ - * @package CiviCRM - * - * This file is part of CiviCRM - * - * CiviCRM is free software; you can redistribute it and/or - * modify it under the terms of the GNU Affero General Public License - * as published by the Free Software Foundation; either version 3 of - * the License, or (at your option) any later version. - * - * CiviCRM is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public - * License along with this program. If not, see - * . - */ - /** * Class containing the APIv4 test suite * @@ -73,13 +42,3 @@ public static function suite() { } } -// class AllTests - -// -- set Emacs parameters -- -// Local variables: -// mode: php; -// tab-width: 4 -// c-basic-offset: 4 -// c-hanging-comment-ender-p: nil -// indent-tabs-mode: nil -// End: