SearchKit - Fix display of contact reference fields (single-value)
authorColeman Watts <coleman@civicrm.org>
Wed, 21 Apr 2021 19:27:52 +0000 (15:27 -0400)
committerColeman Watts <coleman@civicrm.org>
Wed, 21 Apr 2021 22:10:01 +0000 (18:10 -0400)
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
ext/search/Civi/Search/Admin.php
ext/search/ang/crmSearchAdmin.module.js
tests/phpunit/api/v4/Action/CustomJoinTest.php [new file with mode: 0644]
tests/phpunit/api/v4/AllTests.php

index bcaad8ee350c1d69e78195ac79255a72e618b2b5..77666cb4a78d1137f1fdef6d6b70990a2ebaf3c2 100644 (file)
@@ -91,6 +91,7 @@ class SchemaMapBuilder {
     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 @@ class SchemaMapBuilder {
     }
     $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 @@ class SchemaMapBuilder {
         $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) {
index 92b2f605656f0a8d13cca89d47f0d68a5c02f628..5a28f6d6f054caea5f90eddc86250112017ef9be 100644 (file)
@@ -136,11 +136,20 @@ class Admin {
       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]);
           }
index 88cdddbe699452fa7805b34b417f96f09ed387c2..dfa3910d792c260e3d19c691f955a913b872bccf 100644 (file)
             return new RegExp('^' + join.alias + '_\\d\\d').test(path);
           });
           if (!join) {
-            console.warn( 'Join ' + fullNameOrAlias + ' not found.');
             return;
           }
           path = path.replace(join.alias + '_', '');
         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 (file)
index 0000000..9605577
--- /dev/null
@@ -0,0 +1,73 @@
+<?php
+
+/*
+ +--------------------------------------------------------------------+
+ | Copyright CiviCRM LLC. All rights reserved.                        |
+ |                                                                    |
+ | This work is published under the GNU AGPLv3 license with some      |
+ | permitted exceptions and without any warranty. For full license    |
+ | and copyright information, see https://civicrm.org/licensing       |
+ +--------------------------------------------------------------------+
+ */
+
+/**
+ *
+ * @package CRM
+ * @copyright CiviCRM LLC https://civicrm.org/licensing
+ */
+
+
+namespace api\v4\Action;
+
+use Civi\Api4\Contact;
+use Civi\Api4\CustomField;
+use Civi\Api4\CustomGroup;
+
+/**
+ * @group headless
+ */
+class CustomJoinTest extends BaseCustomValueTest {
+
+  public function testGetWithJoin() {
+
+    $customGroup = CustomGroup::create(FALSE)
+      ->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']);
+  }
+
+}
index 67304b923a6642e47e4a3bc2815b625b4a72c0cf..0a4965ecc559895fa4082f2302585289fc9b1a46 100644 (file)
  * @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 <walt@dharmatech.org> (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
- *   <http://www.gnu.org/licenses/>.
- */
-
 /**
  *  Class containing the APIv4 test suite
  *
@@ -73,13 +42,3 @@ class api_v4_AllTests extends CiviTestSuite {
   }
 
 }
-// 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: