Skip to content

Commit

Permalink
REST: Make a generic EntityUpdater
Browse files Browse the repository at this point in the history
* Rename `MediaWikiEditEntityFactoryItemUpdater` to `EntityUpdater`
* Rename its related classes
* Make the new generic `EntityUpdater` entity type agnostic
* Create `EntityUpdaterItemUpdater` implementation which uses the new generic `EntityUpdater`

Bug: T342886
Change-Id: Id69cbdb77638b35afa5bf5c9ccea89dfe4004499
  • Loading branch information
MuhammadJaziraly authored and jakobw committed Aug 8, 2023
1 parent 33b354d commit e2d315b
Show file tree
Hide file tree
Showing 14 changed files with 547 additions and 420 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,5 @@
/**
* @license GPL-2.0-or-later
*/
class ItemUpdateFailed extends Exception {
class EntityUpdateFailed extends Exception {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php declare( strict_types=1 );

namespace Wikibase\Repo\RestApi\Domain\Services\Exceptions;

/**
* @license GPL-2.0-or-later
*/
class EntityUpdatePrevented extends EntityUpdateFailed {
// Don't consider prevented edits unexpected
//
// This patch should only be considered a temporary solution to stop the
// error log spam.
//
// TODO: think of a better way to handle it.
}

This file was deleted.

4 changes: 2 additions & 2 deletions repo/rest-api/src/Domain/Services/ItemUpdater.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,15 @@
use Wikibase\DataModel\Entity\Item;
use Wikibase\Repo\RestApi\Domain\Model\EditMetadata;
use Wikibase\Repo\RestApi\Domain\ReadModel\ItemRevision;
use Wikibase\Repo\RestApi\Domain\Services\Exceptions\ItemUpdateFailed;
use Wikibase\Repo\RestApi\Domain\Services\Exceptions\EntityUpdateFailed;

/**
* @license GPL-2.0-or-later
*/
interface ItemUpdater {

/**
* @throws ItemUpdateFailed
* @throws EntityUpdateFailed
*/
public function update( Item $item, EditMetadata $editMetadata ): ItemRevision;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,56 +7,46 @@
use Psr\Log\LoggerInterface;
use RuntimeException;
use User;
use Wikibase\DataModel\Entity\Item as DataModelItem;
use Wikibase\DataModel\Entity\EntityDocument;
use Wikibase\Lib\Store\EntityRevision;
use Wikibase\Repo\EditEntity\MediaWikiEditEntityFactory;
use Wikibase\Repo\RestApi\Domain\Model\EditMetadata;
use Wikibase\Repo\RestApi\Domain\ReadModel\Descriptions;
use Wikibase\Repo\RestApi\Domain\ReadModel\Item;
use Wikibase\Repo\RestApi\Domain\ReadModel\ItemRevision;
use Wikibase\Repo\RestApi\Domain\ReadModel\Labels;
use Wikibase\Repo\RestApi\Domain\ReadModel\StatementList;
use Wikibase\Repo\RestApi\Domain\Services\Exceptions\ItemUpdateFailed;
use Wikibase\Repo\RestApi\Domain\Services\Exceptions\ItemUpdatePrevented;
use Wikibase\Repo\RestApi\Domain\Services\ItemUpdater;
use Wikibase\Repo\RestApi\Domain\Services\StatementReadModelConverter;
use Wikibase\Repo\RestApi\Domain\Services\Exceptions\EntityUpdateFailed;
use Wikibase\Repo\RestApi\Domain\Services\Exceptions\EntityUpdatePrevented;
use Wikibase\Repo\RestApi\Infrastructure\EditSummaryFormatter;

/**
* @license GPL-2.0-or-later
*/
class MediaWikiEditEntityFactoryItemUpdater implements ItemUpdater {
class EntityUpdater {

private IContextSource $context;
private MediaWikiEditEntityFactory $editEntityFactory;
private LoggerInterface $logger;
private EditSummaryFormatter $summaryFormatter;
private PermissionManager $permissionManager;
private StatementReadModelConverter $statementReadModelConverter;

public function __construct(
IContextSource $context,
MediaWikiEditEntityFactory $editEntityFactory,
LoggerInterface $logger,
EditSummaryFormatter $summaryFormatter,
PermissionManager $permissionManager,
StatementReadModelConverter $statementReadModelConverter
PermissionManager $permissionManager
) {
$this->context = $context;
$this->editEntityFactory = $editEntityFactory;
$this->logger = $logger;
$this->summaryFormatter = $summaryFormatter;
$this->permissionManager = $permissionManager;
$this->statementReadModelConverter = $statementReadModelConverter;
}

public function update( DataModelItem $item, EditMetadata $editMetadata ): ItemRevision {
public function update( EntityDocument $entity, EditMetadata $editMetadata ): EntityRevision {
$this->checkBotRightIfProvided( $this->context->getUser(), $editMetadata->isBot() );

$editEntity = $this->editEntityFactory->newEditEntity( $this->context, $item->getId() );
$editEntity = $this->editEntityFactory->newEditEntity( $this->context, $entity->getId() );

$status = $editEntity->attemptSave(
$item,
$entity,
$this->summaryFormatter->format( $editMetadata->getSummary() ),
EDIT_UPDATE | ( $editMetadata->isBot() ? EDIT_FORCE_BOT : 0 ),
false,
Expand All @@ -66,25 +56,15 @@ public function update( DataModelItem $item, EditMetadata $editMetadata ): ItemR

if ( !$status->isOK() ) {
if ( $this->isPreventedEdit( $status ) ) {
throw new ItemUpdatePrevented( (string)$status );
throw new EntityUpdatePrevented( (string)$status );
}

throw new ItemUpdateFailed( (string)$status );
throw new EntityUpdateFailed( (string)$status );
} elseif ( !$status->isGood() ) {
$this->logger->warning( (string)$status );
}

/** @var EntityRevision $entityRevision */
$entityRevision = $status->getValue()['revision'];
/** @var DataModelItem $savedItem */
$savedItem = $entityRevision->getEntity();
'@phan-var DataModelItem $savedItem';

return new ItemRevision(
$this->convertDataModelItemToReadModel( $savedItem ),
$entityRevision->getTimestamp(),
$entityRevision->getRevisionId()
);
return $status->getValue()['revision'];
}

private function isPreventedEdit( \Status $status ): bool {
Expand All @@ -103,17 +83,4 @@ private function checkBotRightIfProvided( User $user, bool $isBot ): void {
}
}

private function convertDataModelItemToReadModel( DataModelItem $item ): Item {
return new Item(
Labels::fromTermList( $item->getLabels() ),
Descriptions::fromTermList( $item->getDescriptions() ),
new StatementList(
...array_map(
[ $this->statementReadModelConverter, 'convert' ],
iterator_to_array( $item->getStatements() )
)
)
);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
<?php declare( strict_types=1 );

namespace Wikibase\Repo\RestApi\Infrastructure\DataAccess;

use Wikibase\DataModel\Entity\Item as DataModelItem;
use Wikibase\Repo\RestApi\Domain\Model\EditMetadata;
use Wikibase\Repo\RestApi\Domain\ReadModel\Descriptions;
use Wikibase\Repo\RestApi\Domain\ReadModel\Item;
use Wikibase\Repo\RestApi\Domain\ReadModel\ItemRevision;
use Wikibase\Repo\RestApi\Domain\ReadModel\Labels;
use Wikibase\Repo\RestApi\Domain\ReadModel\StatementList;
use Wikibase\Repo\RestApi\Domain\Services\ItemUpdater;
use Wikibase\Repo\RestApi\Domain\Services\StatementReadModelConverter;

/**
* @license GPL-2.0-or-later
*/
class EntityUpdaterItemUpdater implements ItemUpdater {

private EntityUpdater $entityUpdater;
private StatementReadModelConverter $statementReadModelConverter;

public function __construct( EntityUpdater $entityUpdater, StatementReadModelConverter $statementReadModelConverter ) {
$this->entityUpdater = $entityUpdater;
$this->statementReadModelConverter = $statementReadModelConverter;
}

public function update( DataModelItem $item, EditMetadata $editMetadata ): ItemRevision {
$entityRevision = $this->entityUpdater->update( $item, $editMetadata );

/** @var DataModelItem $savedItem */
$savedItem = $entityRevision->getEntity();
'@phan-var DataModelItem $savedItem';

return new ItemRevision(
$this->convertDataModelItemToReadModel( $savedItem ),
$entityRevision->getTimestamp(),
$entityRevision->getRevisionId()
);
}

private function convertDataModelItemToReadModel( DataModelItem $item ): Item {
return new Item(
Labels::fromTermList( $item->getLabels() ),
Descriptions::fromTermList( $item->getDescriptions() ),
new StatementList(
...array_map(
[ $this->statementReadModelConverter, 'convert' ],
iterator_to_array( $item->getStatements() )
)
)
);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
use Psr\Log\LoggerInterface;
use Throwable;
use Wikibase\Repo\RestApi\Application\UseCases\UseCaseError;
use Wikibase\Repo\RestApi\Domain\Services\Exceptions\ItemUpdatePrevented;
use Wikibase\Repo\RestApi\Domain\Services\Exceptions\EntityUpdatePrevented;
use Wikibase\Repo\RestApi\RouteHandlers\ResponseFactory;

/**
Expand All @@ -33,7 +33,7 @@ public function __construct(
public function run( Handler $routeHandler, callable $runNext ): Response {
try {
return $runNext();
} catch ( ItemUpdatePrevented $exception ) { // temporary fix for T329233
} catch ( EntityUpdatePrevented $exception ) { // temporary fix for T329233
$this->logger->warning( $exception->getMessage(), [ 'exception' => $exception ] );
} catch ( Throwable $exception ) {
$this->errorReporter->reportError( $exception, $routeHandler, $routeHandler->getRequest() );
Expand Down
21 changes: 12 additions & 9 deletions repo/rest-api/src/WbRestApi.ServiceWiring.php
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,8 @@
use Wikibase\Repo\RestApi\Infrastructure\DataAccess\EntityRevisionLookupItemDataRetriever;
use Wikibase\Repo\RestApi\Infrastructure\DataAccess\EntityRevisionLookupPropertyDataRetriever;
use Wikibase\Repo\RestApi\Infrastructure\DataAccess\EntityRevisionLookupStatementRetriever;
use Wikibase\Repo\RestApi\Infrastructure\DataAccess\MediaWikiEditEntityFactoryItemUpdater;
use Wikibase\Repo\RestApi\Infrastructure\DataAccess\EntityUpdater;
use Wikibase\Repo\RestApi\Infrastructure\DataAccess\EntityUpdaterItemUpdater;
use Wikibase\Repo\RestApi\Infrastructure\DataAccess\PrefetchingTermLookupAliasesRetriever;
use Wikibase\Repo\RestApi\Infrastructure\DataAccess\StatementSubjectRetriever;
use Wikibase\Repo\RestApi\Infrastructure\DataAccess\TermLookupItemDataRetriever;
Expand Down Expand Up @@ -277,15 +278,17 @@
},

'WbRestApi.ItemUpdater' => function( MediaWikiServices $services ): ItemUpdater {
return new MediaWikiEditEntityFactoryItemUpdater(
RequestContext::getMain(),
WikibaseRepo::getEditEntityFactory( $services ),
WikibaseRepo::getLogger( $services ),
new EditSummaryFormatter(
WikibaseRepo::getSummaryFormatter( $services ),
new LabelsEditSummaryToFormattableSummaryConverter()
return new EntityUpdaterItemUpdater(
new EntityUpdater(
RequestContext::getMain(),
WikibaseRepo::getEditEntityFactory( $services ),
WikibaseRepo::getLogger( $services ),
new EditSummaryFormatter(
WikibaseRepo::getSummaryFormatter( $services ),
new LabelsEditSummaryToFormattableSummaryConverter()
),
$services->getPermissionManager(),
),
$services->getPermissionManager(),
new StatementReadModelConverter(
WikibaseRepo::getStatementGuidParser( $services ),
WikibaseRepo::getPropertyDataTypeLookup( $services )
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
<?php declare( strict_types=1 );

namespace Wikibase\Repo\Tests\RestApi\Infrastructure\DataAccess;

use Generator;
use MediaWiki\Permissions\PermissionManager;
use MediaWikiIntegrationTestCase;
use Psr\Log\NullLogger;
use RequestContext;
use Wikibase\DataModel\Entity\EntityDocument;
use Wikibase\DataModel\Entity\ItemId;
use Wikibase\DataModel\Entity\NumericPropertyId;
use Wikibase\DataModel\Entity\Property;
use Wikibase\DataModel\Entity\StatementListProvidingEntity;
use Wikibase\DataModel\Statement\StatementGuid;
use Wikibase\DataModel\Statement\StatementList;
use Wikibase\DataModel\Tests\NewItem;
use Wikibase\DataModel\Tests\NewStatement;
use Wikibase\Repo\RestApi\Domain\Model\EditMetadata;
use Wikibase\Repo\RestApi\Infrastructure\DataAccess\EntityUpdater;
use Wikibase\Repo\RestApi\Infrastructure\EditSummaryFormatter;
use Wikibase\Repo\WikibaseRepo;

/**
* @covers \Wikibase\Repo\RestApi\Infrastructure\DataAccess\EntityUpdater
*
* @group Wikibase
* @group Database
*
* @license GPL-2.0-or-later
*/
class EntityUpdaterIntegrationTest extends MediaWikiIntegrationTestCase {

/**
* @dataProvider provideStatementIdAndEntityWithStatement
*/
public function testUpdate_removeStatementFromEntity( StatementGuid $statementId, StatementListProvidingEntity $entityToUpdate ): void {
$this->saveNewEntity( $entityToUpdate );

$entityToUpdate->getStatements()->removeStatementsWithGuid( (string)$statementId );

$newRevision = $this->newEntityUpdater()->update( $entityToUpdate, $this->createStub( EditMetadata::class ) );

$this->assertCount( 0, $newRevision->getEntity()->getStatements() );
}

/**
* @dataProvider provideStatementIdAndEntityWithStatement
*/
public function testUpdate_replaceStatementOnEntity( StatementGuid $statementId, StatementListProvidingEntity $entityToUpdate ): void {
$newValue = 'new statement value';
$newStatement = NewStatement::forProperty( 'P321' )
->withGuid( $statementId )
->withValue( $newValue )
->build();

$this->saveNewEntity( $entityToUpdate );

$entityToUpdate->getStatements()->replaceStatement( $statementId, $newStatement );

$newRevision = $this->newEntityUpdater()->update( $entityToUpdate, $this->createStub( EditMetadata::class ) );

$statementList = $newRevision->getEntity()->getStatements();
$this->assertSame(
$newValue,
$statementList->getFirstStatementWithGuid( (string)$statementId )->getMainSnak()->getDataValue()->getValue()
);
}

public function provideStatementIdAndEntityWithStatement(): Generator {
$statementId = new StatementGuid( new ItemId( 'Q123' ), 'AAAAAAAA-BBBB-CCCC-DDDD-EEEEEEEEEEEE' );
$statement = NewStatement::forProperty( 'P321' )
->withGuid( $statementId )
->withValue( 'a statement value' )
->build();
yield 'item with statement' => [ $statementId, NewItem::withStatement( $statement )->build() ];

$statementId = new StatementGuid( new NumericPropertyId( 'P123' ), 'AAAAAAAA-BBBB-CCCC-DDDD-EEEEEEEEEEEE' );
$statement = NewStatement::forProperty( 'P321' )
->withGuid( $statementId )
->withValue( 'a statement value' )
->build();
$property = Property::newFromType( 'string' );
$property->setStatements( new StatementList( $statement ) );
yield 'property with statement' => [ $statementId, $property ];
}

private function saveNewEntity( EntityDocument $entity ): void {
WikibaseRepo::getEntityStore()->saveEntity(
$entity,
__METHOD__,
$this->getTestUser()->getUser(),
EDIT_NEW
);
}

private function newEntityUpdater(): EntityUpdater {
$permissionManager = $this->createStub( PermissionManager::class );
$permissionManager->method( $this->anything() )->willReturn( true );

return new EntityUpdater(
RequestContext::getMain(),
WikibaseRepo::getEditEntityFactory(),
new NullLogger(),
$this->createStub( EditSummaryFormatter::class ),
$permissionManager
);
}

}
Loading

0 comments on commit e2d315b

Please sign in to comment.