jenkins-bot has submitted this change and it was merged.
Change subject: Use WatchedItemStore in ApiQueryInfo::getWatchedInfo
......................................................................
Use WatchedItemStore in ApiQueryInfo::getWatchedInfo
Adds a method for getting watchlist's notification timestamps
for a batch of LinkTargets.
Bug: T129482
Change-Id: I1f84212e7879a84b34bb3b53859069fcea282bba
---
M includes/WatchedItemStore.php
M includes/api/ApiQueryInfo.php
M tests/phpunit/includes/WatchedItemStoreIntegrationTest.php
M tests/phpunit/includes/WatchedItemStoreUnitTest.php
4 files changed, 332 insertions(+), 23 deletions(-)
Approvals:
Addshore: Looks good to me, approved
jenkins-bot: Verified
diff --git a/includes/WatchedItemStore.php b/includes/WatchedItemStore.php
index 4e2dfa5..8a3f205 100644
--- a/includes/WatchedItemStore.php
+++ b/includes/WatchedItemStore.php
@@ -503,6 +503,61 @@
}
/**
+ * @param User $user
+ * @param LinkTarget[] $targets
+ *
+ * @return array multi-dimensional like
$return[$namespaceId][$titleString] = $timestamp,
+ * where $timestamp is:
+ * - string|null value of wl_notificationtimestamp,
+ * - false if $target is not watched by $user.
+ */
+ public function getNotificationTimestampsBatch( User $user, array
$targets ) {
+ $timestamps = [];
+ foreach ( $targets as $target ) {
+
$timestamps[$target->getNamespace()][$target->getDBkey()] = false;
+ }
+
+ if ( $user->isAnon() ) {
+ return $timestamps;
+ }
+
+ $targetsToLoad = [];
+ foreach ( $targets as $target ) {
+ $cachedItem = $this->getCached( $user, $target );
+ if ( $cachedItem ) {
+
$timestamps[$target->getNamespace()][$target->getDBkey()] =
+ $cachedItem->getNotificationTimestamp();
+ } else {
+ $targetsToLoad[] = $target;
+ }
+ }
+
+ if ( !$targetsToLoad ) {
+ return $timestamps;
+ }
+
+ $dbr = $this->getConnection( DB_SLAVE );
+
+ $lb = new LinkBatch( $targetsToLoad );
+ $res = $dbr->select(
+ 'watchlist',
+ [ 'wl_namespace', 'wl_title',
'wl_notificationtimestamp' ],
+ [
+ $lb->constructSet( 'wl', $dbr ),
+ 'wl_user' => $user->getId(),
+ ],
+ __METHOD__
+ );
+ $this->reuseConnection( $dbr );
+
+ foreach ( $res as $row ) {
+ $timestamps[(int)$row->wl_namespace][$row->wl_title] =
$row->wl_notificationtimestamp;
+ }
+
+ return $timestamps;
+ }
+
+ /**
* Must be called separately for Subject & Talk namespaces
*
* @param User $user
diff --git a/includes/api/ApiQueryInfo.php b/includes/api/ApiQueryInfo.php
index 2d382dd..ea1b94e 100644
--- a/includes/api/ApiQueryInfo.php
+++ b/includes/api/ApiQueryInfo.php
@@ -448,8 +448,8 @@
ApiResult::setIndexedTagName(
$pageInfo['restrictiontypes'], 'rt' );
}
- if ( $this->fld_watched ) {
- $pageInfo['watched'] = isset(
$this->watched[$ns][$dbkey] );
+ if ( $this->fld_watched && $this->watched !== null ) {
+ $pageInfo['watched'] = $this->watched[$ns][$dbkey];
}
if ( $this->fld_watchers ) {
@@ -470,7 +470,7 @@
if ( $this->fld_notificationtimestamp ) {
$pageInfo['notificationtimestamp'] = '';
- if ( isset( $this->notificationtimestamps[$ns][$dbkey]
) ) {
+ if ( $this->notificationtimestamps[$ns][$dbkey] ) {
$pageInfo['notificationtimestamp'] =
wfTimestamp( TS_ISO_8601,
$this->notificationtimestamps[$ns][$dbkey] );
}
@@ -758,29 +758,22 @@
$this->watched = [];
$this->notificationtimestamps = [];
- $db = $this->getDB();
- $lb = new LinkBatch( $this->everything );
+ $store = WatchedItemStore::getDefaultInstance();
+ $timestamps = $store->getNotificationTimestampsBatch( $user,
$this->everything );
- $this->resetQueryParams();
- $this->addTables( [ 'watchlist' ] );
- $this->addFields( [ 'wl_title', 'wl_namespace' ] );
- $this->addFieldsIf( 'wl_notificationtimestamp',
$this->fld_notificationtimestamp );
- $this->addWhere( [
- $lb->constructSet( 'wl', $db ),
- 'wl_user' => $user->getId()
- ] );
-
- $res = $this->select( __METHOD__ );
-
- foreach ( $res as $row ) {
- if ( $this->fld_watched ) {
-
$this->watched[$row->wl_namespace][$row->wl_title] = true;
+ if ( $this->fld_watched ) {
+ foreach ( $timestamps as $namespaceId => $dbKeys ) {
+ $this->watched[$namespaceId] = array_map(
+ function( $x ) {
+ return $x !== false;
+ },
+ $dbKeys
+ );
}
- if ( $this->fld_notificationtimestamp ) {
-
$this->notificationtimestamps[$row->wl_namespace][$row->wl_title] =
- $row->wl_notificationtimestamp;
- }
+ }
+ if ( $this->fld_notificationtimestamp ) {
+ $this->notificationtimestamps = $timestamps;
}
}
diff --git a/tests/phpunit/includes/WatchedItemStoreIntegrationTest.php
b/tests/phpunit/includes/WatchedItemStoreIntegrationTest.php
index 0dea461..ad5041c 100644
--- a/tests/phpunit/includes/WatchedItemStoreIntegrationTest.php
+++ b/tests/phpunit/includes/WatchedItemStoreIntegrationTest.php
@@ -52,6 +52,10 @@
[ 0 => [ 'WatchedItemStoreIntegrationTestPage' => 0 ] ],
$store->countWatchersMultiple( [ $title ], [
'minimumWatchers' => $initialWatchers + 2 ] )
);
+ $this->assertEquals(
+ [ $title->getNamespace() => [ $title->getDBkey() =>
null ] ],
+ $store->getNotificationTimestampsBatch( $user, [ $title
] )
+ );
$store->removeWatch( $user, $title );
$this->assertFalse(
@@ -63,6 +67,10 @@
$this->assertEquals(
$initialWatchers,
$store->countWatchersMultiple( [ $title ]
)[$title->getNamespace()][$title->getDBkey()]
+ );
+ $this->assertEquals(
+ [ $title->getNamespace() => [ $title->getDBkey() =>
false ] ],
+ $store->getNotificationTimestampsBatch( $user, [ $title
] )
);
}
@@ -80,6 +88,10 @@
$this->assertEquals(
'20150202010101',
$store->loadWatchedItem( $user, $title
)->getNotificationTimestamp()
+ );
+ $this->assertEquals(
+ [ $title->getNamespace() => [ $title->getDBkey() =>
'20150202010101' ] ],
+ $store->getNotificationTimestampsBatch( $user, [ $title
] )
);
$this->assertEquals(
$initialVisitingWatchers - 1,
@@ -103,6 +115,10 @@
$this->assertTrue( $store->resetNotificationTimestamp( $user,
$title ) );
$this->assertNull( $store->getWatchedItem( $user, $title
)->getNotificationTimestamp() );
$this->assertEquals(
+ [ $title->getNamespace() => [ $title->getDBkey() =>
null ] ],
+ $store->getNotificationTimestampsBatch( $user, [ $title
] )
+ );
+ $this->assertEquals(
$initialVisitingWatchers,
$store->countVisitingWatchers( $title, '20150202020202'
)
);
diff --git a/tests/phpunit/includes/WatchedItemStoreUnitTest.php
b/tests/phpunit/includes/WatchedItemStoreUnitTest.php
index 869b0d2..be5172e 100644
--- a/tests/phpunit/includes/WatchedItemStoreUnitTest.php
+++ b/tests/phpunit/includes/WatchedItemStoreUnitTest.php
@@ -1481,6 +1481,251 @@
);
}
+ public function testGetNotificationTimestampsBatch() {
+ $targets = [
+ new TitleValue( 0, 'SomeDbKey' ),
+ new TitleValue( 1, 'AnotherDbKey' ),
+ ];
+
+ $mockDb = $this->getMockDb();
+ $dbResult = [
+ $this->getFakeRow( [
+ 'wl_namespace' => 0,
+ 'wl_title' => 'SomeDbKey',
+ 'wl_notificationtimestamp' => '20151212010101',
+ ] ),
+ $this->getFakeRow(
+ [
+ 'wl_namespace' => 1,
+ 'wl_title' => 'AnotherDbKey',
+ 'wl_notificationtimestamp' => null,
+ ]
+ ),
+ ];
+
+ $mockDb->expects( $this->once() )
+ ->method( 'makeWhereFrom2d' )
+ ->with(
+ [ [ 'SomeDbKey' => 1 ], [ 'AnotherDbKey' => 1 ]
],
+ $this->isType( 'string' ),
+ $this->isType( 'string' )
+ )
+ ->will( $this->returnValue( 'makeWhereFrom2d return
value' ) );
+ $mockDb->expects( $this->once() )
+ ->method( 'select' )
+ ->with(
+ 'watchlist',
+ [ 'wl_namespace', 'wl_title',
'wl_notificationtimestamp' ],
+ [
+ 'makeWhereFrom2d return value',
+ 'wl_user' => 1
+ ],
+ $this->isType( 'string' )
+ )
+ ->will( $this->returnValue( $dbResult ) );
+
+ $mockCache = $this->getMockCache();
+ $mockCache->expects( $this->exactly( 2 ) )
+ ->method( 'get' )
+ ->withConsecutive(
+ [ '0:SomeDbKey:1' ],
+ [ '1:AnotherDbKey:1' ]
+ )
+ ->will( $this->returnValue( null ) );
+ $mockCache->expects( $this->never() )->method( 'set' );
+ $mockCache->expects( $this->never() )->method( 'delete' );
+
+ $store = new WatchedItemStore(
+ $this->getMockLoadBalancer( $mockDb ),
+ $mockCache
+ );
+
+ $this->assertEquals(
+ [
+ 0 => [ 'SomeDbKey' => '20151212010101', ],
+ 1 => [ 'AnotherDbKey' => null, ],
+ ],
+ $store->getNotificationTimestampsBatch(
$this->getMockNonAnonUserWithId( 1 ), $targets )
+ );
+ }
+
+ public function testGetNotificationTimestampsBatch_notWatchedTarget() {
+ $targets = [
+ new TitleValue( 0, 'OtherDbKey' ),
+ ];
+
+ $mockDb = $this->getMockDb();
+
+ $mockDb->expects( $this->once() )
+ ->method( 'makeWhereFrom2d' )
+ ->with(
+ [ [ 'OtherDbKey' => 1 ] ],
+ $this->isType( 'string' ),
+ $this->isType( 'string' )
+ )
+ ->will( $this->returnValue( 'makeWhereFrom2d return
value' ) );
+ $mockDb->expects( $this->once() )
+ ->method( 'select' )
+ ->with(
+ 'watchlist',
+ [ 'wl_namespace', 'wl_title',
'wl_notificationtimestamp' ],
+ [
+ 'makeWhereFrom2d return value',
+ 'wl_user' => 1
+ ],
+ $this->isType( 'string' )
+ )
+ ->will( $this->returnValue( $this->getFakeRow( [] ) ) );
+
+ $mockCache = $this->getMockCache();
+ $mockCache->expects( $this->once() )
+ ->method( 'get' )
+ ->with( '0:OtherDbKey:1' )
+ ->will( $this->returnValue( null ) );
+ $mockCache->expects( $this->never() )->method( 'set' );
+ $mockCache->expects( $this->never() )->method( 'delete' );
+
+ $store = new WatchedItemStore(
+ $this->getMockLoadBalancer( $mockDb ),
+ $mockCache
+ );
+
+ $this->assertEquals(
+ [
+ 0 => [ 'OtherDbKey' => false, ],
+ ],
+ $store->getNotificationTimestampsBatch(
$this->getMockNonAnonUserWithId( 1 ), $targets )
+ );
+ }
+
+ public function testGetNotificationTimestampsBatch_cachedItem() {
+ $targets = [
+ new TitleValue( 0, 'SomeDbKey' ),
+ new TitleValue( 1, 'AnotherDbKey' ),
+ ];
+
+ $user = $this->getMockNonAnonUserWithId( 1 );
+ $cachedItem = new WatchedItem( $user, $targets[0],
'20151212010101' );
+
+ $mockDb = $this->getMockDb();
+
+ $mockDb->expects( $this->once() )
+ ->method( 'makeWhereFrom2d' )
+ ->with(
+ [ 1 => [ 'AnotherDbKey' => 1 ] ],
+ $this->isType( 'string' ),
+ $this->isType( 'string' )
+ )
+ ->will( $this->returnValue( 'makeWhereFrom2d return
value' ) );
+ $mockDb->expects( $this->once() )
+ ->method( 'select' )
+ ->with(
+ 'watchlist',
+ [ 'wl_namespace', 'wl_title',
'wl_notificationtimestamp' ],
+ [
+ 'makeWhereFrom2d return value',
+ 'wl_user' => 1
+ ],
+ $this->isType( 'string' )
+ )
+ ->will( $this->returnValue( [
+ $this->getFakeRow(
+ [ 'wl_namespace' => 1, 'wl_title' =>
'AnotherDbKey', 'wl_notificationtimestamp' => null, ]
+ )
+ ] ) );
+
+ $mockCache = $this->getMockCache();
+ $mockCache->expects( $this->at( 1 ) )
+ ->method( 'get' )
+ ->with( '0:SomeDbKey:1' )
+ ->will( $this->returnValue( $cachedItem ) );
+ $mockCache->expects( $this->at( 3 ) )
+ ->method( 'get' )
+ ->with( '1:AnotherDbKey:1' )
+ ->will( $this->returnValue( null ) );
+ $mockCache->expects( $this->never() )->method( 'set' );
+ $mockCache->expects( $this->never() )->method( 'delete' );
+
+ $store = new WatchedItemStore(
+ $this->getMockLoadBalancer( $mockDb ),
+ $mockCache
+ );
+
+ $this->assertEquals(
+ [
+ 0 => [ 'SomeDbKey' => '20151212010101', ],
+ 1 => [ 'AnotherDbKey' => null, ],
+ ],
+ $store->getNotificationTimestampsBatch( $user, $targets
)
+ );
+ }
+
+ public function testGetNotificationTimestampsBatch_allItemsCached() {
+ $targets = [
+ new TitleValue( 0, 'SomeDbKey' ),
+ new TitleValue( 1, 'AnotherDbKey' ),
+ ];
+
+ $user = $this->getMockNonAnonUserWithId( 1 );
+ $cachedItems = [
+ new WatchedItem( $user, $targets[0], '20151212010101' ),
+ new WatchedItem( $user, $targets[1], null ),
+ ];
+ $mockDb = $this->getMockDb();
+ $mockDb->expects( $this->never() )->method( $this->anything() );
+
+ $mockCache = $this->getMockCache();
+ $mockCache->expects( $this->at( 1 ) )
+ ->method( 'get' )
+ ->with( '0:SomeDbKey:1' )
+ ->will( $this->returnValue( $cachedItems[0] ) );
+ $mockCache->expects( $this->at( 3 ) )
+ ->method( 'get' )
+ ->with( '1:AnotherDbKey:1' )
+ ->will( $this->returnValue( $cachedItems[1] ) );
+ $mockCache->expects( $this->never() )->method( 'set' );
+ $mockCache->expects( $this->never() )->method( 'delete' );
+
+ $store = new WatchedItemStore(
+ $this->getMockLoadBalancer( $mockDb ),
+ $mockCache
+ );
+
+ $this->assertEquals(
+ [
+ 0 => [ 'SomeDbKey' => '20151212010101', ],
+ 1 => [ 'AnotherDbKey' => null, ],
+ ],
+ $store->getNotificationTimestampsBatch( $user, $targets
)
+ );
+ }
+
+ public function testGetNotificationTimestampsBatch_anonymousUser() {
+ $targets = [
+ new TitleValue( 0, 'SomeDbKey' ),
+ new TitleValue( 1, 'AnotherDbKey' ),
+ ];
+
+ $mockDb = $this->getMockDb();
+ $mockDb->expects( $this->never() )->method( $this->anything() );
+
+ $mockCache = $this->getMockCache();
+ $mockCache->expects( $this->never() )->method(
$this->anything() );
+
+ $store = new WatchedItemStore(
+ $this->getMockLoadBalancer( $mockDb ),
+ $mockCache
+ );
+
+ $this->assertEquals(
+ [
+ 0 => [ 'SomeDbKey' => false, ],
+ 1 => [ 'AnotherDbKey' => false, ],
+ ],
+ $store->getNotificationTimestampsBatch(
$this->getAnonUser(), $targets )
+ );
+ }
+
public function testResetNotificationTimestamp_anonymousUser() {
$mockDb = $this->getMockDb();
$mockDb->expects( $this->never() )
--
To view, visit https://gerrit.wikimedia.org/r/278283
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings
Gerrit-MessageType: merged
Gerrit-Change-Id: I1f84212e7879a84b34bb3b53859069fcea282bba
Gerrit-PatchSet: 4
Gerrit-Project: mediawiki/core
Gerrit-Branch: master
Gerrit-Owner: WMDE-leszek <[email protected]>
Gerrit-Reviewer: Addshore <[email protected]>
Gerrit-Reviewer: Anomie <[email protected]>
Gerrit-Reviewer: Daniel Kinzler <[email protected]>
Gerrit-Reviewer: Kaldari <[email protected]>
Gerrit-Reviewer: Tobias Gritschacher <[email protected]>
Gerrit-Reviewer: WMDE-Fisch <[email protected]>
Gerrit-Reviewer: WMDE-leszek <[email protected]>
Gerrit-Reviewer: jenkins-bot <>
_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits