Skip to content

Commit e06a166

Browse files
jakobwjenkins-bot
authored andcommitted
GQL: Enable fetching labels of item values
Bug: T404837 Change-Id: Ia47f554deb6c54e8e85d83b0c97bd1af0ebbf269
1 parent dc83958 commit e06a166

File tree

7 files changed

+248
-7
lines changed

7 files changed

+248
-7
lines changed

repo/WikibaseRepo.datatypes.php

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,12 +42,14 @@
4242
use ValueParsers\ValueParser;
4343
use Wikibase\DataModel\Entity\EntityIdParsingException;
4444
use Wikibase\DataModel\Entity\EntityIdValue;
45+
use Wikibase\DataModel\Entity\ItemId;
4546
use Wikibase\Lib\Formatters\EntityIdValueFormatter;
4647
use Wikibase\Lib\Formatters\SnakFormat;
4748
use Wikibase\Lib\Formatters\SnakFormatter;
4849
use Wikibase\Lib\Store\FieldPropertyInfoProvider;
4950
use Wikibase\Lib\Store\PropertyInfoStore;
5051
use Wikibase\Repo\Domains\Reuse\Domain\Model\Value;
52+
use Wikibase\Repo\Domains\Reuse\WbReuse;
5153
use Wikibase\Repo\Parsers\EntityIdValueParser;
5254
use Wikibase\Repo\Parsers\MediaWikiNumberUnlocalizer;
5355
use Wikibase\Repo\Parsers\MonolingualTextParser;
@@ -468,6 +470,9 @@
468470
return PropertySpecificComponentsRdfBuilder::OBJECT_PROPERTY;
469471
},
470472
'graphql-value-type' => static function() {
473+
$itemLabelsResolver = WbReuse::getItemLabelsResolver();
474+
$languageCodeType = WbReuse::getLanguageCodeType();
475+
471476
return new ObjectType( [
472477
'name' => 'ItemValue',
473478
'fields' => [
@@ -479,6 +484,19 @@
479484
'type' => Type::nonNull( Type::string() ),
480485
'resolve' => fn( EntityIdValue $content ) => $content->getEntityId()->getSerialization(),
481486
],
487+
'label' => [
488+
'type' => Type::string(),
489+
'args' => [
490+
'languageCode' => Type::nonNull( $languageCodeType ),
491+
],
492+
'resolve' => function( EntityIdValue $value, array $args ) use( $itemLabelsResolver ) {
493+
/** @var ItemId $itemId */
494+
$itemId = $value->getEntityId();
495+
'@phan-var ItemId $itemId';
496+
497+
return $itemLabelsResolver->resolve( $itemId, $args['languageCode'] );
498+
},
499+
],
482500
],
483501
] ) ),
484502
'resolve' => fn( Value $v ) => $v->content,
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\Domains\Reuse\Infrastructure\GraphQL\Resolvers;
4+
5+
use GraphQL\Deferred;
6+
use Wikibase\DataModel\Entity\ItemId;
7+
use Wikibase\Repo\Domains\Reuse\Application\UseCases\BatchGetItemLabels\BatchGetItemLabels;
8+
use Wikibase\Repo\Domains\Reuse\Application\UseCases\BatchGetItemLabels\BatchGetItemLabelsRequest;
9+
use Wikibase\Repo\Domains\Reuse\Domain\Model\ItemLabelsBatch;
10+
11+
/**
12+
* @license GPL-2.0-or-later
13+
*/
14+
class ItemLabelsResolver {
15+
private array $itemsToFetch = [];
16+
private array $languagesToFetch = [];
17+
private ?ItemLabelsBatch $labelsBatch = null;
18+
19+
public function __construct(
20+
private readonly BatchGetItemLabels $batchGetItemLabels
21+
) {
22+
}
23+
24+
public function resolve( ItemId $itemId, string $languageCode ): Deferred {
25+
$this->itemsToFetch[] = $itemId->getSerialization();
26+
$this->languagesToFetch[] = $languageCode;
27+
28+
return new Deferred( function() use ( $itemId, $languageCode ) {
29+
if ( !$this->labelsBatch ) {
30+
$this->labelsBatch = $this->batchGetItemLabels
31+
->execute( new BatchGetItemLabelsRequest(
32+
array_values( array_unique( $this->itemsToFetch ) ),
33+
array_values( array_unique( $this->languagesToFetch ) ),
34+
) )
35+
->batch;
36+
}
37+
38+
return $this->labelsBatch
39+
->getItemLabels( $itemId )
40+
->getLabelInLanguage( $languageCode )
41+
?->text;
42+
} );
43+
}
44+
}

repo/domains/reuse/src/Infrastructure/GraphQL/schema.graphql

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ type ItemValue {
6161

6262
type ValueItem {
6363
id: String!
64+
label(languageCode: LanguageCode!): String
6465
}
6566

6667
enum ValueType {

repo/domains/reuse/src/WbReuse.ServiceWiring.php

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
<?php declare( strict_types=1 );
22

33
use MediaWiki\MediaWikiServices;
4+
use Wikibase\Repo\Domains\Reuse\Application\UseCases\BatchGetItemLabels\BatchGetItemLabels;
45
use Wikibase\Repo\Domains\Reuse\Application\UseCases\BatchGetItems\BatchGetItems;
56
use Wikibase\Repo\Domains\Reuse\Application\UseCases\BatchGetPropertyLabels\BatchGetPropertyLabels;
67
use Wikibase\Repo\Domains\Reuse\Domain\Services\StatementReadModelConverter;
78
use Wikibase\Repo\Domains\Reuse\Infrastructure\DataAccess\EntityLookupItemsBatchRetriever;
89
use Wikibase\Repo\Domains\Reuse\Infrastructure\DataAccess\PrefetchingTermLookupBatchLabelsRetriever;
910
use Wikibase\Repo\Domains\Reuse\Infrastructure\GraphQL\GraphQLService;
11+
use Wikibase\Repo\Domains\Reuse\Infrastructure\GraphQL\Resolvers\ItemLabelsResolver;
1012
use Wikibase\Repo\Domains\Reuse\Infrastructure\GraphQL\Resolvers\ItemResolver;
1113
use Wikibase\Repo\Domains\Reuse\Infrastructure\GraphQL\Resolvers\PropertyLabelsResolver;
1214
use Wikibase\Repo\Domains\Reuse\Infrastructure\GraphQL\Schema\ItemIdType;
@@ -23,7 +25,7 @@
2325
/** @phpcs-require-sorted-array */
2426
return [
2527
'WbReuse.GraphQLSchema' => function( MediaWikiServices $services ): Schema {
26-
$languageCodeType = new LanguageCodeType( WikibaseRepo::getTermsLanguages( $services )->getLanguages() );
28+
$languageCodeType = WbReuse::getLanguageCodeType( $services );
2729
$predicatePropertyType = new PredicatePropertyType(
2830
new PropertyLabelsResolver(
2931
new BatchGetPropertyLabels( new PrefetchingTermLookupBatchLabelsRetriever(
@@ -64,4 +66,14 @@
6466
$services->getMainConfig(),
6567
);
6668
},
69+
'WbReuse.ItemLabelsResolver' => function( MediaWikiServices $services ): ItemLabelsResolver {
70+
return new ItemLabelsResolver(
71+
new BatchGetItemLabels(
72+
new PrefetchingTermLookupBatchLabelsRetriever( WikibaseRepo::getPrefetchingTermLookup( $services ) )
73+
)
74+
);
75+
},
76+
'WbReuse.LanguageCodeType' => function( MediaWikiServices $services ): LanguageCodeType {
77+
return new LanguageCodeType( WikibaseRepo::getTermsLanguages( $services )->getLanguages() );
78+
},
6779
];

repo/domains/reuse/src/WbReuse.php

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
use MediaWiki\MediaWikiServices;
66
use Psr\Container\ContainerInterface;
77
use Wikibase\Repo\Domains\Reuse\Infrastructure\GraphQL\GraphQLService;
8+
use Wikibase\Repo\Domains\Reuse\Infrastructure\GraphQL\Resolvers\ItemLabelsResolver;
9+
use Wikibase\Repo\Domains\Reuse\Infrastructure\GraphQL\Schema\LanguageCodeType;
810
use Wikibase\Repo\Domains\Reuse\Infrastructure\GraphQL\Schema\Schema;
911

1012
/**
@@ -20,4 +22,14 @@ public static function getGraphQLSchema( ?ContainerInterface $services = null ):
2022
return ( $services ?: MediaWikiServices::getInstance() )
2123
->get( 'WbReuse.GraphQLSchema' );
2224
}
25+
26+
public static function getItemLabelsResolver( ?ContainerInterface $services = null ): ItemLabelsResolver {
27+
return ( $services ?: MediaWikiServices::getInstance() )
28+
->get( 'WbReuse.ItemLabelsResolver' );
29+
}
30+
31+
public static function getLanguageCodeType( ?ContainerInterface $services = null ): LanguageCodeType {
32+
return ( $services ?: MediaWikiServices::getInstance() )
33+
->get( 'WbReuse.LanguageCodeType' );
34+
}
2335
}

repo/domains/reuse/tests/phpunit/Infrastructure/GraphQL/GraphQLServiceTest.php

Lines changed: 68 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ class GraphQLServiceTest extends MediaWikiIntegrationTestCase {
3838

3939
private static Item $item1;
4040
private static Item $item2;
41+
private static Item $statementValueItem;
42+
private static Item $qualifierValueItem;
4143
private static Property $statementProperty;
4244
private static Property $qualifierProperty;
4345
private static MediaWikiSite $sitelinkSite;
@@ -61,7 +63,12 @@ public function testQuery( string $query, array $expectedResult ): void {
6163
$siteIdProvider->method( 'getList' )->willReturn( self::ALLOWED_SITELINK_SITES );
6264

6365
$termLookup = new InMemoryPrefetchingTermLookup();
64-
$termLookup->setData( [ self::$statementProperty, self::$qualifierProperty ] );
66+
$termLookup->setData( [
67+
self::$statementProperty,
68+
self::$qualifierProperty,
69+
self::$statementValueItem,
70+
self::$qualifierValueItem,
71+
] );
6572

6673
$this->assertEquals(
6774
$expectedResult,
@@ -75,8 +82,19 @@ public function testQuery( string $query, array $expectedResult ): void {
7582
}
7683

7784
public function queryProvider(): Generator {
78-
$itemId = 'Q123';
85+
$statementValueItemEnLabel = 'statement value item';
7986
$itemValueItemId = 'Q4';
87+
self::$statementValueItem = NewItem::withId( $itemValueItemId )
88+
->andLabel( 'en', $statementValueItemEnLabel )
89+
->build();
90+
91+
$qualifierValueItemEnLabel = 'statement value item';
92+
$qualifierValueItemId = 'Q5';
93+
self::$qualifierValueItem = NewItem::withId( $qualifierValueItemId )
94+
->andLabel( 'en', $qualifierValueItemEnLabel )
95+
->build();
96+
97+
$itemId = 'Q123';
8098
$enLabel = 'potato';
8199
$enDescription = 'root vegetable';
82100
$enAliases = [ 'spud', 'tater' ];
@@ -88,6 +106,7 @@ public function queryProvider(): Generator {
88106
$statementWithItemValuePropertyId = 'P3';
89107
$statementWithNoValuePropertyId = 'P4';
90108
$statementWithSomeValuePropertyId = 'P5';
109+
$statementWithItemValueQualifierPropertyId = $statementWithItemValuePropertyId; // also type wikibase-item so we can just reuse it.
91110
$unusedPropertyId = 'P9999';
92111
$qualifierStringValue = 'qualifierStringValue';
93112
$statementStringValue = 'statementStringValue';
@@ -100,6 +119,7 @@ public function queryProvider(): Generator {
100119
$statementWithItemValue = NewStatement::forProperty( ( $statementWithItemValuePropertyId ) )
101120
->withGuid( "$itemId\$bed933b7-4207-d679-7571-3630cfb49d8f" )
102121
->withValue( new ItemId( $itemValueItemId ) )
122+
->withQualifier( $statementWithItemValueQualifierPropertyId, self::$qualifierValueItem->getId() )
103123
->build();
104124

105125
$statementWithNoValue = NewStatement::noValueFor( ( $statementWithNoValuePropertyId ) )
@@ -211,7 +231,7 @@ public function queryProvider(): Generator {
211231
"{ item(id: \"$itemId\") {
212232
statements(propertyId: \"$statementWithStringValuePropertyId\") {
213233
$qualifierPropertyId: qualifiers(propertyId: \"$qualifierPropertyId\") {
214-
property { id dataType }
234+
property { id dataType }
215235
value { ...on StringValue { content } }
216236
valueType
217237
}
@@ -247,11 +267,11 @@ public function queryProvider(): Generator {
247267
yield 'statements with StringValue and ItemValue' => [
248268
"{ item(id: \"$itemId\") {
249269
$statementWithStringValuePropertyId: statements(propertyId: \"$statementWithStringValuePropertyId\") {
250-
value { ...on StringValue{ content } }
270+
value { ...on StringValue { content } }
251271
valueType
252272
}
253273
$statementWithItemValuePropertyId: statements(propertyId: \"$statementWithItemValuePropertyId\") {
254-
value { ...on ItemValue{ content { id } } }
274+
value { ...on ItemValue { content { id } } }
255275
valueType
256276
}
257277
} }",
@@ -281,7 +301,7 @@ public function queryProvider(): Generator {
281301
yield 'statements with novalue and somevalue' => [
282302
"{ item(id: \"$itemId\") {
283303
$statementWithSomeValuePropertyId: statements(propertyId: \"$statementWithSomeValuePropertyId\") {
284-
value { ...on StringValue{ content } }
304+
value { ...on StringValue { content } }
285305
valueType
286306
}
287307
$statementWithNoValuePropertyId: statements(propertyId: \"$statementWithNoValuePropertyId\") {
@@ -338,6 +358,48 @@ public function queryProvider(): Generator {
338358
],
339359
],
340360
];
361+
yield 'labels of item values' => [
362+
"{ item(id: \"$itemId\") {
363+
statements(propertyId: \"{$statementWithItemValuePropertyId}\") {
364+
value {
365+
... on ItemValue {
366+
content { label(languageCode: \"en\") }
367+
}
368+
}
369+
qualifiers(propertyId: \"{$statementWithItemValueQualifierPropertyId}\") {
370+
value {
371+
... on ItemValue {
372+
content { label(languageCode: \"en\") }
373+
}
374+
}
375+
}
376+
}
377+
} }",
378+
[
379+
'data' => [
380+
'item' => [
381+
'statements' => [
382+
[
383+
'value' => [
384+
'content' => [
385+
'label' => self::$statementValueItem->getLabels()->getByLanguage( 'en' )->getText(),
386+
],
387+
],
388+
'qualifiers' => [
389+
[
390+
'value' => [
391+
'content' => [
392+
'label' => self::$qualifierValueItem->getLabels()->getByLanguage( 'en' )->getText(),
393+
],
394+
],
395+
],
396+
],
397+
],
398+
],
399+
],
400+
],
401+
],
402+
];
341403
yield 'multiple items at once' => [
342404
"{
343405
item1: item(id: \"$itemId\") { label(languageCode: \"en\") }
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
<?php declare( strict_types=1 );
2+
3+
namespace Wikibase\Repo\Tests\Domains\Reuse\Infrastructure\GraphQL\Resolvers;
4+
5+
use GraphQL\Executor\Promise\Adapter\SyncPromise;
6+
use GraphQL\GraphQL;
7+
use PHPUnit\Framework\TestCase;
8+
use Wikibase\DataModel\Entity\ItemId;
9+
use Wikibase\Repo\Domains\Reuse\Application\UseCases\BatchGetItemLabels\BatchGetItemLabels;
10+
use Wikibase\Repo\Domains\Reuse\Application\UseCases\BatchGetItemLabels\BatchGetItemLabelsRequest;
11+
use Wikibase\Repo\Domains\Reuse\Application\UseCases\BatchGetItemLabels\BatchGetItemLabelsResponse;
12+
use Wikibase\Repo\Domains\Reuse\Domain\Model\ItemLabelsBatch;
13+
use Wikibase\Repo\Domains\Reuse\Domain\Model\Label;
14+
use Wikibase\Repo\Domains\Reuse\Domain\Model\Labels;
15+
use Wikibase\Repo\Domains\Reuse\Infrastructure\GraphQL\Resolvers\ItemLabelsResolver;
16+
17+
/**
18+
* @covers \Wikibase\Repo\Domains\Reuse\Infrastructure\GraphQL\Resolvers\ItemLabelsResolver
19+
*
20+
* @group Wikibase
21+
*
22+
* @license GPL-2.0-or-later
23+
*/
24+
class ItemLabelsResolverTest extends TestCase {
25+
public static function setUpBeforeClass(): void {
26+
if ( !class_exists( GraphQL::class ) ) {
27+
self::markTestSkipped( 'Needs webonyx/graphql-php to run' );
28+
}
29+
}
30+
31+
public function testResolve(): void {
32+
$requestedItems = [ new ItemId( 'Q123' ), new ItemId( 'Q321' ) ];
33+
$requestedItemIdSerializations = array_map( fn( $id ) => (string)$id, $requestedItems );
34+
$requestedLanguages = [ 'de', 'en' ];
35+
$itemLabelsBatch = $this->newLabelsBatch( $requestedItems, $requestedLanguages );
36+
37+
$batchGetItemLabels = $this->createMock( BatchGetItemLabels::class );
38+
// expecting the use case to only be called once demonstrates that the resolver aggregates multiple requests into one batch
39+
$batchGetItemLabels->expects( $this->once() )
40+
->method( 'execute' )
41+
->with( new BatchGetItemLabelsRequest( $requestedItemIdSerializations, $requestedLanguages ) )
42+
->willReturn( new BatchGetItemLabelsResponse( $itemLabelsBatch ) );
43+
44+
$resolver = new ItemLabelsResolver( $batchGetItemLabels );
45+
46+
$promise1 = $resolver->resolve( $requestedItems[0], $requestedLanguages[0] );
47+
$promise2 = $resolver->resolve( $requestedItems[0], $requestedLanguages[1] );
48+
$promise3 = $resolver->resolve( $requestedItems[1], $requestedLanguages[0] );
49+
$promise4 = $resolver->resolve( $requestedItems[1], $requestedLanguages[1] );
50+
51+
SyncPromise::runQueue(); // resolves the promises above
52+
53+
$this->assertSame(
54+
$itemLabelsBatch->getItemLabels( $requestedItems[0] )
55+
->getLabelInLanguage( $requestedLanguages[0] )
56+
->text,
57+
$promise1->result
58+
);
59+
$this->assertSame(
60+
$itemLabelsBatch->getItemLabels( $requestedItems[0] )
61+
->getLabelInLanguage( $requestedLanguages[1] )
62+
->text,
63+
$promise2->result
64+
);
65+
$this->assertSame(
66+
$itemLabelsBatch->getItemLabels( $requestedItems[1] )
67+
->getLabelInLanguage( $requestedLanguages[0] )
68+
->text,
69+
$promise3->result
70+
);
71+
$this->assertSame(
72+
$itemLabelsBatch->getItemLabels( $requestedItems[1] )
73+
->getLabelInLanguage( $requestedLanguages[1] )
74+
->text,
75+
$promise4->result
76+
);
77+
}
78+
79+
private function newLabelsBatch( array $itemIds, array $languageCodes ): ItemLabelsBatch {
80+
$batch = [];
81+
foreach ( $itemIds as $id ) {
82+
$labels = [];
83+
foreach ( $languageCodes as $languageCode ) {
84+
$labels[] = new Label( $languageCode, "$languageCode label " . rand() );
85+
}
86+
87+
$batch[$id->getSerialization()] = new Labels( ...$labels );
88+
}
89+
90+
return new ItemLabelsBatch( $batch );
91+
}
92+
}

0 commit comments

Comments
 (0)