SearchKit - Consolidate getJoins, add tests
authorcolemanw <coleman@civicrm.org>
Wed, 20 Dec 2023 03:22:06 +0000 (22:22 -0500)
committercolemanw <coleman@civicrm.org>
Wed, 20 Dec 2023 13:44:37 +0000 (08:44 -0500)
ext/search_kit/Civi/Search/Admin.php
ext/search_kit/tests/phpunit/Civi/Search/AdminTest.php

index 44fd79ba402e272a0dda4c954af9408425d291e5..ccad5f42ac4f557568c6b7fe37b39b8569562535 100644 (file)
@@ -282,56 +282,10 @@ class Admin {
   public static function getJoins(array $allowedEntities):array {
     $joins = [];
     foreach ($allowedEntities as $entity) {
-      // Multi-record custom field groups (to-date only the contact entity supports these)
-      if (in_array('CustomValue', $entity['type'])) {
-        // TODO: Lookup target entity from custom group if someday other entities support multi-record custom data
-        $targetEntity = $allowedEntities['Contact'];
-        // Join from Custom group to Contact (n-1)
-        $alias = "{$entity['name']}_{$targetEntity['name']}_entity_id";
-        $joins[$entity['name']][] = [
-          'label' => $entity['title'] . ' ' . $targetEntity['title'],
-          'description' => '',
-          'entity' => $targetEntity['name'],
-          'conditions' => self::getJoinConditions('entity_id', $alias . '.id'),
-          'defaults' => self::getJoinDefaults($alias, $targetEntity),
-          'alias' => $alias,
-          'multi' => FALSE,
-        ];
-        // Join from Contact to Custom group (n-n)
-        $alias = "{$targetEntity['name']}_{$entity['name']}_entity_id";
-        $joins[$targetEntity['name']][] = [
-          'label' => $entity['title_plural'],
-          'description' => '',
-          'entity' => $entity['name'],
-          'conditions' => self::getJoinConditions('id', $alias . '.entity_id'),
-          'defaults' => self::getJoinDefaults($alias, $entity),
-          'alias' => $alias,
-          'multi' => TRUE,
-        ];
-
-        // Allow joins via EntityRef fields in multi-value custom data
-        foreach ($entity['fields'] as $field) {
-          // Note: we skip entity_id field since it is handled below.
-          if ($field['input_type'] === 'EntityRef' && $field['name'] !== 'entity_id') {
-
-            // Join from the custom data entity to the referenced entity
-            $joins[$entity['name']][] = self::getEntityRefJoins($entity, $field);
+      $isCustomEntity = in_array('CustomValue', $entity['type'], TRUE);
 
-            // Join from referenced entity to the custom data entity
-            $joins[$field['fk_entity']][] = [
-              'label' => $entity["title_plural"],
-              'description' => $entity["description"],
-              'entity' => $entity["name"],
-              'conditions' => [],
-              'defaults' => [],
-              'alias' => $entity['name'],
-              'multi' => TRUE,
-            ];
-          }
-        }
-      }
       // Non-custom DAO entities
-      elseif (!empty($entity['dao'])) {
+      if (!$isCustomEntity && !empty($entity['dao'])) {
         /** @var \CRM_Core_DAO $daoClass */
         $daoClass = $entity['dao'];
         $references = $daoClass::getReferenceColumns();
@@ -443,10 +397,14 @@ class Admin {
             }
           }
         }
-        // Custom EntityRef joins
-        foreach ($fields as $field) {
-          if ($field['type'] === 'Custom' && $field['input_type'] === 'EntityRef') {
-            $joins[$entity['name']][] = self::getEntityRefJoins($entity, $field);
+      }
+
+      // Custom EntityRef joins
+      foreach ($entity['fields'] as $field) {
+        if (($field['type'] === 'Custom' || $isCustomEntity) && $field['fk_entity'] && $field['input_type'] === 'EntityRef') {
+          $entityRefJoins = self::getEntityRefJoins($entity, $field);
+          foreach ($entityRefJoins as $joinEntity => $joinInfo) {
+            $joins[$joinEntity][] = $joinInfo;
           }
         }
       }
@@ -455,16 +413,16 @@ class Admin {
   }
 
   /**
-   * Get a join for an entity reference
+   * Get joins for entity reference custom fields, and the entity_id fields in multi-record custom groups.
    *
-   * @return void
+   * @return array[]
    */
-  public static function getEntityRefJoins($entity, $field) {
+  public static function getEntityRefJoins(array $entity, array $field): array {
     $exploded = explode('.', $field['name']);
     $bareFieldName = array_reverse($exploded)[0];
-    $alias = $entity['name'] . '_' . $field['fk_entity'] . '_' . $bareFieldName;
-    $join = [
-      'label' => $entity['title'] . ' ' . $field['title'],
+    $alias = "{$entity['name']}_{$field['fk_entity']}_$bareFieldName";
+    $joins[$entity['name']] = [
+      'label' => $entity['title'] . ' ' . $field['label'],
       'description' => $field['description'],
       'entity' => $field['fk_entity'],
       'conditions' => self::getJoinConditions($field['name'], $alias . '.id'),
@@ -472,7 +430,20 @@ class Admin {
       'alias' => $alias,
       'multi' => FALSE,
     ];
-    return $join;
+    // Do reverse join if not the same entity
+    if ($entity['name'] !== $field['fk_entity']) {
+      $alias = "{$field['fk_entity']}_{$entity['name']}_$bareFieldName";
+      $joins[$field['fk_entity']] = [
+        'label' => $entity['title_plural'],
+        'description' => $entity['description'],
+        'entity' => $entity['name'],
+        'conditions' => self::getJoinConditions('id', "$alias.{$field['name']}"),
+        'defaults' => [],
+        'alias' => $alias,
+        'multi' => TRUE,
+      ];
+    }
+    return $joins;
   }
 
   /**
index 7093a165ec594e70ffb787b0e5d6b8f0e2e6bd59..2902241644513915e82c4be2225546935a904023 100644 (file)
@@ -1,15 +1,17 @@
 <?php
 namespace Civi\Search;
 
-use Civi\Test\HeadlessInterface;
-use Civi\Test\TransactionalInterface;
+use api\v4\Api4TestBase;
+use Civi\Test\CiviEnvBuilder;
+
+require_once __DIR__ . '/../../../../../../tests/phpunit/api/v4/Api4TestBase.php';
 
 /**
  * @group headless
  */
-class AdminTest extends \PHPUnit\Framework\TestCase implements HeadlessInterface, TransactionalInterface {
+class AdminTest extends Api4TestBase {
 
-  public function setUpHeadless() {
+  public function setUpHeadless(): CiviEnvBuilder {
     return \Civi\Test::headless()->installMe(__DIR__)->apply();
   }
 
@@ -127,21 +129,62 @@ class AdminTest extends \PHPUnit\Framework\TestCase implements HeadlessInterface
   }
 
   public function testEntityRefGetJoins(): void {
-    \Civi\Api4\CustomGroup::create()->setValues([
+    $this->createTestRecord('CustomGroup', [
       'title' => 'EntityRefFields',
       'extends' => 'Individual',
-    ])->execute();
-    \Civi\Api4\CustomField::create()->setValues([
+    ]);
+    $this->createTestRecord('CustomField', [
       'label' => 'Favorite Nephew',
       'name' => 'favorite_nephew',
       'custom_group_id.name' => 'EntityRefFields',
       'html_type' => 'Autocomplete-Select',
       'data_type' => 'EntityReference',
       'fk_entity' => 'Contact',
-    ])->execute();
+    ]);
+    $allowedEntities = Admin::getSchema();
+    $joins = Admin::getJoins($allowedEntities);
+
+    $entityRefJoin = \CRM_Utils_Array::findAll($joins['Contact'], ['alias' => 'Contact_Contact_favorite_nephew']);
+    $this->assertCount(1, $entityRefJoin);
+    $this->assertEquals([['EntityRefFields.favorite_nephew', '=', 'Contact_Contact_favorite_nephew.id']], $entityRefJoin[0]['conditions']);
+    $this->assertStringContainsString('Favorite Nephew', $entityRefJoin[0]['label']);
+  }
+
+  public function testMultiRecordCustomGetJoins(): void {
+    $this->createTestRecord('CustomGroup', [
+      'title' => 'Multiple Things',
+      'name' => 'MultiRecordActivity',
+      'extends' => 'Activity',
+      'is_multiple' => TRUE,
+    ]);
+    $this->createTestRecord('CustomField', [
+      'label' => 'Ref Group',
+      'name' => 'ref_group',
+      'custom_group_id.name' => 'MultiRecordActivity',
+      'html_type' => 'Autocomplete-Select',
+      'data_type' => 'EntityReference',
+      'fk_entity' => 'Group',
+    ]);
     $allowedEntities = Admin::getSchema();
     $joins = Admin::getJoins($allowedEntities);
-    $this->assertContains('Contact Favorite Nephew', array_column($joins['Contact'], 'label'));
+
+    $entityRefJoin = \CRM_Utils_Array::findAll($joins['Custom_MultiRecordActivity'], ['alias' => 'Custom_MultiRecordActivity_Group_ref_group']);
+    $this->assertCount(1, $entityRefJoin);
+    $this->assertEquals([['ref_group', '=', 'Custom_MultiRecordActivity_Group_ref_group.id']], $entityRefJoin[0]['conditions']);
+
+    $reverseJoin = \CRM_Utils_Array::findAll($joins['Group'], ['alias' => 'Group_Custom_MultiRecordActivity_ref_group']);
+    $this->assertCount(1, $reverseJoin);
+    $this->assertEquals([['id', '=', 'Group_Custom_MultiRecordActivity_ref_group.ref_group']], $reverseJoin[0]['conditions']);
+
+    $activityToCustomJoin = \CRM_Utils_Array::findAll($joins['Activity'], ['alias' => 'Activity_Custom_MultiRecordActivity_entity_id']);
+    $this->assertCount(1, $activityToCustomJoin);
+    $this->assertEquals([['id', '=', 'Activity_Custom_MultiRecordActivity_entity_id.entity_id']], $activityToCustomJoin[0]['conditions']);
+    $this->assertEquals('Multiple Things', $activityToCustomJoin[0]['label']);
+
+    $customToActivityJoin = \CRM_Utils_Array::findAll($joins['Custom_MultiRecordActivity'], ['alias' => 'Custom_MultiRecordActivity_Activity_entity_id']);
+    $this->assertCount(1, $customToActivityJoin);
+    $this->assertEquals([['entity_id', '=', 'Custom_MultiRecordActivity_Activity_entity_id.id']], $customToActivityJoin[0]['conditions']);
+    $this->assertEquals('Multiple Things Activity', $customToActivityJoin[0]['label']);
   }
 
 }