From ace754e77145f355dce9b9f7889ec1acaca99e7c Mon Sep 17 00:00:00 2001 From: colemanw Date: Sat, 15 Jul 2023 08:57:25 -0400 Subject: [PATCH] Api4 Autocomplete - Search by exact id match more effieciently Fixes dev/core#4423 --- Civi/Api4/Generic/AutocompleteAction.php | 18 +++++---- .../api/v4/Action/AutocompleteTest.php | 39 +++++++++++++------ 2 files changed, 39 insertions(+), 18 deletions(-) diff --git a/Civi/Api4/Generic/AutocompleteAction.php b/Civi/Api4/Generic/AutocompleteAction.php index 8842355c9d..7a70d4d2fe 100644 --- a/Civi/Api4/Generic/AutocompleteAction.php +++ b/Civi/Api4/Generic/AutocompleteAction.php @@ -156,13 +156,13 @@ class AutocompleteAction extends AbstractAction { // Always search on the first line of the display $searchFields = [$labelField]; // If input is an integer, search by id - if (\CRM_Utils_Rule::positiveInteger($this->input)) { - $searchFields[] = $idField; - // Add a sort clause to place exact ID match at the top - array_unshift($this->display['settings']['sort'], [ - "($idField = $this->input)", - 'DESC', - ]); + $numericInput = $this->page == 1 && \CRM_Utils_Rule::positiveInteger($this->input); + if ($numericInput) { + $searchFields = [$idField]; + } + // For subsequent pages when searching numeric input + elseif ($this->page > 1 && \CRM_Utils_Rule::positiveInteger($this->input)) { + $this->page -= 1; } // If first line uses a rewrite, search on those fields too if (!empty($this->display['settings']['columns'][0]['rewrite'])) { @@ -198,6 +198,10 @@ class AutocompleteAction extends AbstractAction { $result[] = $item; } $result->setCountMatched($apiResult->count()); + if (!empty($numericInput)) { + // Trigger "more results" after searching by exact id + $result->setCountMatched($apiResult->count() + 1); + } } /** diff --git a/tests/phpunit/api/v4/Action/AutocompleteTest.php b/tests/phpunit/api/v4/Action/AutocompleteTest.php index ede5038372..048ded6956 100644 --- a/tests/phpunit/api/v4/Action/AutocompleteTest.php +++ b/tests/phpunit/api/v4/Action/AutocompleteTest.php @@ -63,6 +63,11 @@ class AutocompleteTest extends Api4TestBase implements HookInterface, Transactio parent::setUp(); } + public function tearDown(): void { + \Civi::settings()->revert('search_autocomplete_count'); + parent::tearDown(); + } + public function testMockEntityAutocomplete(): void { $sampleData = [ ['foo' => 'White', 'color' => 'ffffff'], @@ -253,7 +258,7 @@ class AutocompleteTest extends Api4TestBase implements HookInterface, Transactio ->setInput((string) $sample['b']['id']) ->setKey('name') ->execute(); - $this->assertCount(1, $result1); + $this->assertEquals(1, $result1->countFetched()); $this->assertEquals('b', $result1[0]['id']); // This key won't be used since api_entity is not a unique index @@ -268,6 +273,7 @@ class AutocompleteTest extends Api4TestBase implements HookInterface, Transactio } public function testContactAutocompleteById(): void { + \Civi::settings()->set('search_autocomplete_count', 3); $firstName = \CRM_Utils_String::createRandom(10, implode('', range('a', 'z'))); $contacts = $this->saveTestRecords('Contact', [ @@ -280,25 +286,36 @@ class AutocompleteTest extends Api4TestBase implements HookInterface, Transactio ->addRecord(['id' => $contacts[0]['id'], 'last_name' => "Aaaac$cid"]) ->addRecord(['id' => $contacts[14]['id'], 'last_name' => "Aaaab$cid"]) ->addRecord(['id' => $contacts[6]['id'], 'last_name' => "Aaaaa$cid"]) + ->addRecord(['id' => $contacts[1]['id'], 'last_name' => "Aaaad$cid"]) ->execute(); - $result = Contact::autocomplete() - ->setInput((string) $cid) - ->execute(); + $page = 1; + $all = []; + do { + $result = Contact::autocomplete() + ->setPage($page++) + ->setInput((string) $cid) + ->execute(); + $all = array_merge($all, (array) $result); + } while ($result->countFetched() < $result->countMatched()); + + // Should have iterated at least 3 times + $this->assertGreaterThanOrEqual(3, $page); // Exact match should be at beginning of the list - $this->assertContains($cid, $result->column('id')); - $this->assertEquals($cid, $result[0]['id']); + $this->assertEquals($cid, \CRM_Utils_Array::first($all)['id']); + $this->assertEquals($cid, $all[0]['id']); // If by chance there are other matching contacts in the db, skip over them - foreach ($result as $i => $row) { + foreach ($all as $i => $row) { if ($row['id'] == $contacts[6]['id']) { break; } } - // The other 3 matches should be in order - $this->assertEquals($contacts[6]['id'], $result[$i]['id']); - $this->assertEquals($contacts[14]['id'], $result[$i + 1]['id']); - $this->assertEquals($contacts[0]['id'], $result[$i + 2]['id']); + // The other 4 matches should be in order + $this->assertEquals($contacts[6]['id'], $all[$i]['id']); + $this->assertEquals($contacts[14]['id'], $all[$i + 1]['id']); + $this->assertEquals($contacts[0]['id'], $all[$i + 2]['id']); + $this->assertEquals($contacts[1]['id'], $all[$i + 3]['id']); // Ensure partial match doesn't work (end of id) $result = Contact::autocomplete() -- 2.25.1