Skip to content

Commit 79a05c7

Browse files
jakobwjenkins-bot
authored andcommitted
Search: Add PropertyPrefixSearch
Bug: T399271 Change-Id: Ib23f62cc9c93a1a76ad9f27ec6de7fc52ba9c2ea
1 parent 04748ca commit 79a05c7

File tree

7 files changed

+295
-6
lines changed

7 files changed

+295
-6
lines changed
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<?php declare( strict_types=1 );
2+
3+
namespace Wikibase\Repo\Domains\Search\Application\UseCases\PropertyPrefixSearch;
4+
5+
use Wikibase\Repo\Domains\Search\Domain\Services\PropertyPrefixSearchEngine;
6+
7+
/**
8+
* @license GPL-2.0-or-later
9+
*/
10+
class PropertyPrefixSearch {
11+
12+
public function __construct(
13+
private PropertyPrefixSearchEngine $searchEngine
14+
) {
15+
}
16+
17+
public function execute( PropertyPrefixSearchRequest $searchRequest ): PropertyPrefixSearchResponse {
18+
return new PropertyPrefixSearchResponse( $this->searchEngine->suggestProperties(
19+
$searchRequest->query,
20+
$searchRequest->language,
21+
$searchRequest->limit,
22+
$searchRequest->offset
23+
) );
24+
}
25+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?php declare( strict_types=1 );
2+
3+
namespace Wikibase\Repo\Domains\Search\Application\UseCases\PropertyPrefixSearch;
4+
5+
/**
6+
* @license GPL-2.0-or-later
7+
*/
8+
class PropertyPrefixSearchRequest {
9+
10+
public const DEFAULT_LIMIT = 10;
11+
public const DEFAULT_OFFSET = 0;
12+
13+
public function __construct(
14+
public readonly string $query,
15+
public readonly string $language,
16+
public readonly int $limit = self::DEFAULT_LIMIT,
17+
public readonly int $offset = self::DEFAULT_OFFSET
18+
) {
19+
}
20+
21+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<?php declare( strict_types=1 );
2+
3+
namespace Wikibase\Repo\Domains\Search\Application\UseCases\PropertyPrefixSearch;
4+
5+
use Wikibase\Repo\Domains\Search\Domain\Model\PropertySearchResults;
6+
7+
/**
8+
* @license GPL-2.0-or-later
9+
*/
10+
class PropertyPrefixSearchResponse {
11+
12+
public function __construct( public readonly PropertySearchResults $results ) {
13+
}
14+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<?php declare( strict_types=1 );
2+
3+
namespace Wikibase\Repo\Domains\Search\Domain\Services;
4+
5+
use Wikibase\Repo\Domains\Search\Domain\Model\PropertySearchResults;
6+
7+
/**
8+
* @license GPL-2.0-or-later
9+
*/
10+
interface PropertyPrefixSearchEngine {
11+
public function suggestProperties(
12+
string $searchTerm,
13+
string $languageCode,
14+
int $limit,
15+
int $offset
16+
): PropertySearchResults;
17+
}

repo/domains/search/src/Infrastructure/DataAccess/EntitySearchHelperPrefixSearchEngine.php

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,20 +5,23 @@
55
use MediaWiki\Languages\LanguageFactory;
66
use MediaWiki\Request\WebRequest;
77
use Wikibase\DataModel\Entity\Item;
8-
use Wikibase\DataModel\Entity\ItemId;
8+
use Wikibase\DataModel\Entity\Property;
99
use Wikibase\Lib\Interactors\TermSearchResult;
1010
use Wikibase\Repo\Domains\Search\Domain\Model\Description;
1111
use Wikibase\Repo\Domains\Search\Domain\Model\ItemSearchResult;
1212
use Wikibase\Repo\Domains\Search\Domain\Model\ItemSearchResults;
1313
use Wikibase\Repo\Domains\Search\Domain\Model\Label;
1414
use Wikibase\Repo\Domains\Search\Domain\Model\MatchedData;
15+
use Wikibase\Repo\Domains\Search\Domain\Model\PropertySearchResult;
16+
use Wikibase\Repo\Domains\Search\Domain\Model\PropertySearchResults;
1517
use Wikibase\Repo\Domains\Search\Domain\Services\ItemPrefixSearchEngine;
18+
use Wikibase\Repo\Domains\Search\Domain\Services\PropertyPrefixSearchEngine;
1619
use Wikibase\Search\Elastic\EntitySearchHelperFactory;
1720

1821
/**
1922
* @license GPL-2.0-or-later
2023
*/
21-
class EntitySearchHelperPrefixSearchEngine implements ItemPrefixSearchEngine {
24+
class EntitySearchHelperPrefixSearchEngine implements ItemPrefixSearchEngine, PropertyPrefixSearchEngine {
2225
public function __construct(
2326
// @phan-suppress-next-line PhanUndeclaredTypeParameter, PhanUndeclaredTypeProperty WikibaseCirrusSearch is ok here
2427
private EntitySearchHelperFactory $searchHelperFactory,
@@ -45,12 +48,33 @@ public function suggestItems( string $searchTerm, string $languageCode, int $lim
4548
$limit
4649
);
4750

48-
return new ItemSearchResults( ...array_map( $this->convertResult( ... ), $results ) );
51+
return new ItemSearchResults( ...array_map( $this->convertResult( ItemSearchResult::class ), $results ) );
4952
}
5053

51-
private function convertResult( TermSearchResult $result ): ItemSearchResult {
52-
return new ItemSearchResult(
53-
new ItemId( $result->getEntityId()->getSerialization() ),
54+
public function suggestProperties( string $searchTerm, string $languageCode, int $limit, int $offset ): PropertySearchResults {
55+
$results = array_slice(
56+
// @phan-suppress-next-line PhanUndeclaredClassMethod
57+
$this->searchHelperFactory->newItemSearchForResultLanguage( // FIXME: This looks wrong but probably works fine. Fix in T399274.
58+
$this->request,
59+
$this->languageFactory->getLanguage( $languageCode )
60+
)->getRankedSearchResults(
61+
$searchTerm,
62+
$languageCode,
63+
Property::ENTITY_TYPE,
64+
$limit + $offset + 1,
65+
false,
66+
null
67+
),
68+
$offset,
69+
$limit
70+
);
71+
72+
return new PropertySearchResults( ...array_map( $this->convertResult( PropertySearchResult::class ), $results ) );
73+
}
74+
75+
private function convertResult( string $resultClass ): callable {
76+
return fn( TermSearchResult $result ) => new $resultClass(
77+
$result->getEntityId(),
5478
$result->getDisplayLabel()
5579
? new Label( $result->getDisplayLabel()->getLanguageCode(), $result->getDisplayLabel()->getText() )
5680
: null,
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
<?php declare( strict_types=1 );
2+
3+
namespace Wikibase\Repo\Tests\Domains\Search\Application\UseCases\PropertyPrefixSearch;
4+
5+
use PHPUnit\Framework\TestCase;
6+
use Wikibase\Repo\Domains\Search\Application\UseCases\PropertyPrefixSearch\PropertyPrefixSearch;
7+
use Wikibase\Repo\Domains\Search\Application\UseCases\PropertyPrefixSearch\PropertyPrefixSearchRequest;
8+
use Wikibase\Repo\Domains\Search\Domain\Model\PropertySearchResults;
9+
use Wikibase\Repo\Domains\Search\Domain\Services\PropertyPrefixSearchEngine;
10+
11+
/**
12+
* @covers \Wikibase\Repo\Domains\Search\Application\UseCases\PropertyPrefixSearch\PropertyPrefixSearch
13+
*
14+
* @group Wikibase
15+
*
16+
* @license GPL-2.0-or-later
17+
*/
18+
class PropertyPrefixSearchTest extends TestCase {
19+
20+
public function testCanExecute(): void {
21+
$query = 'subcla';
22+
$language = 'en';
23+
$limit = 10;
24+
$offset = 0;
25+
$expectedResults = $this->createStub( PropertySearchResults::class );
26+
27+
$searchEngine = $this->createMock( PropertyPrefixSearchEngine::class );
28+
$searchEngine->expects( $this->once() )
29+
->method( 'suggestProperties' )
30+
->with( $query, $language, $limit, $offset )
31+
->willReturn( $expectedResults );
32+
33+
$this->assertEquals(
34+
$expectedResults,
35+
$this->newUseCase( $searchEngine )
36+
->execute( new PropertyPrefixSearchRequest( $query, $language, $limit, $offset ) )
37+
->results
38+
);
39+
}
40+
41+
private function newUseCase( PropertyPrefixSearchEngine $searchEngine ): PropertyPrefixSearch {
42+
return new PropertyPrefixSearch( $searchEngine );
43+
}
44+
}

repo/domains/search/tests/phpunit/Infrastructure/DataAccess/EntitySearchHelperPrefixSearchEngineTest.php

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
use PHPUnit\Framework\TestCase;
1010
use Wikibase\DataModel\Entity\Item;
1111
use Wikibase\DataModel\Entity\ItemId;
12+
use Wikibase\DataModel\Entity\NumericPropertyId;
13+
use Wikibase\DataModel\Entity\Property;
1214
use Wikibase\DataModel\Term\Term;
1315
use Wikibase\Lib\Interactors\TermSearchResult;
1416
use Wikibase\Repo\Api\EntitySearchHelper;
@@ -17,6 +19,8 @@
1719
use Wikibase\Repo\Domains\Search\Domain\Model\ItemSearchResults;
1820
use Wikibase\Repo\Domains\Search\Domain\Model\Label;
1921
use Wikibase\Repo\Domains\Search\Domain\Model\MatchedData;
22+
use Wikibase\Repo\Domains\Search\Domain\Model\PropertySearchResult;
23+
use Wikibase\Repo\Domains\Search\Domain\Model\PropertySearchResults;
2024
use Wikibase\Repo\Domains\Search\Infrastructure\DataAccess\EntitySearchHelperPrefixSearchEngine;
2125
use Wikibase\Search\Elastic\EntitySearchHelperFactory;
2226

@@ -175,6 +179,146 @@ public static function itemSearchResultsProvider(): Generator {
175179
];
176180
}
177181

182+
/**
183+
* @dataProvider propertySearchResultsProvider
184+
*/
185+
public function testPropertySearch(
186+
array $results,
187+
PropertySearchResults $expected,
188+
int $limit = 10,
189+
int $offset = 0
190+
): void {
191+
$searchTerm = 'subcla';
192+
$language = 'en';
193+
194+
$entitySearchHelper = $this->createMock( EntitySearchHelper::class );
195+
$entitySearchHelper->expects( $this->once() )
196+
->method( 'getRankedSearchResults' )
197+
->with( $searchTerm, $language, Property::ENTITY_TYPE, $limit + $offset + 1, false, null )
198+
->willReturn( $results );
199+
200+
$this->assertEquals(
201+
$expected,
202+
$this->newSearchEngine( $entitySearchHelper )->suggestProperties( $searchTerm, $language, $limit, $offset )
203+
);
204+
}
205+
206+
public static function propertySearchResultsProvider(): Generator {
207+
yield 'no results' => [ [], new PropertySearchResults() ];
208+
yield 'some results' => [
209+
[
210+
new TermSearchResult(
211+
new Term( 'en', 'property label' ),
212+
'label',
213+
new NumericPropertyId( 'P123' ),
214+
new Term( 'en', 'property label' ),
215+
new Term( 'en', 'property description' )
216+
),
217+
new TermSearchResult(
218+
new Term( 'en', 'property 2 label' ),
219+
'label',
220+
new NumericPropertyId( 'P321' ),
221+
new Term( 'en', 'property 2 label' ),
222+
new Term( 'en', 'property 2 description' )
223+
),
224+
],
225+
new PropertySearchResults(
226+
new PropertySearchResult(
227+
new NumericPropertyId( 'P123' ),
228+
new Label( 'en', 'property label' ),
229+
new Description( 'en', 'property description' ),
230+
new MatchedData( 'label', 'en', 'property label' )
231+
),
232+
new PropertySearchResult(
233+
new NumericPropertyId( 'P321' ),
234+
new Label( 'en', 'property 2 label' ),
235+
new Description( 'en', 'property 2 description' ),
236+
new MatchedData( 'label', 'en', 'property 2 label' )
237+
)
238+
),
239+
];
240+
yield 'alias as display label' => [
241+
[
242+
new TermSearchResult(
243+
new Term( 'en', 'property alias' ),
244+
'alias',
245+
new NumericPropertyId( 'P123' ),
246+
new Term( 'en', 'property alias' ),
247+
new Term( 'en', 'property description' )
248+
),
249+
],
250+
new PropertySearchResults(
251+
new PropertySearchResult(
252+
new NumericPropertyId( 'P123' ),
253+
new Label( 'en', 'property alias' ),
254+
new Description( 'en', 'property description' ),
255+
new MatchedData( 'alias', 'en', 'property alias' )
256+
)
257+
),
258+
];
259+
260+
$propertyList = array_map(
261+
fn( int $i ) => [
262+
'id' => "P12$i",
263+
'label' => "property $i",
264+
'description' => "property description $i",
265+
],
266+
range( 1, 11 )
267+
);
268+
269+
yield 'pagination result with limit' => [
270+
array_map(
271+
fn( array $property ) => new TermSearchResult(
272+
new Term( 'en', $property['label'] ),
273+
'label',
274+
new NumericPropertyId( $property['id'] ),
275+
new Term( 'en', $property['label'] ),
276+
new Term( 'en', $property['description'] )
277+
),
278+
$propertyList
279+
),
280+
new PropertySearchResults(
281+
...array_map(
282+
fn( array $property ) => new PropertySearchResult(
283+
new NumericPropertyId( $property['id'] ),
284+
new Label( 'en', $property['label'] ),
285+
new Description( 'en', $property['description'] ),
286+
new MatchedData( 'label', 'en', $property['label'] )
287+
),
288+
array_slice( $propertyList, 0, 5 )
289+
)
290+
),
291+
5,
292+
0,
293+
];
294+
295+
yield 'pagination result with offset and limit' => [
296+
array_map(
297+
fn( array $property ) => new TermSearchResult(
298+
new Term( 'en', $property['label'] ),
299+
'label',
300+
new NumericPropertyId( $property['id'] ),
301+
new Term( 'en', $property['label'] ),
302+
new Term( 'en', $property['description'] )
303+
),
304+
$propertyList
305+
),
306+
new PropertySearchResults(
307+
...array_map(
308+
fn( array $property ) => new PropertySearchResult(
309+
new NumericPropertyId( $property['id'] ),
310+
new Label( 'en', $property['label'] ),
311+
new Description( 'en', $property['description'] ),
312+
new MatchedData( 'label', 'en', $property['label'] )
313+
),
314+
array_slice( $propertyList, 5, 5 )
315+
)
316+
),
317+
5,
318+
5,
319+
];
320+
}
321+
178322
public function newSearchEngine( EntitySearchHelper $entitySearchHelper ): EntitySearchHelperPrefixSearchEngine {
179323
$searchHelperFactory = $this->createMock( EntitySearchHelperFactory::class );
180324
$searchHelperFactory->method( 'newItemSearchForResultLanguage' )

0 commit comments

Comments
 (0)