Merge "ApiQueryInfo: Return watchlist expiry when applicable for inprop=watched"

This commit is contained in:
jenkins-bot 2021-01-08 07:52:13 +00:00 committed by Gerrit Code Review
commit 7e3c1e6629
6 changed files with 241 additions and 109 deletions

View file

@ -71,6 +71,12 @@ class ApiQueryInfo extends ApiQueryBase {
private $protections, $restrictionTypes, $watched, $watchers, $visitingwatchers,
$notificationtimestamps, $talkids, $subjectids, $displaytitles, $variantTitles;
/**
* Watchlist expiries that corresponds with the $watched property. Keyed by namespace and title.
* @var array<int,array<string,string>>
*/
private $watchlistExpiries;
/**
* @var array<int,string[]> Mapping of page id to list of 'extra link
* classes' for the given page
@ -495,8 +501,12 @@ class ApiQueryInfo extends ApiQueryBase {
ApiResult::setIndexedTagName( $pageInfo['restrictiontypes'], 'rt' );
}
if ( $this->fld_watched && $this->watched !== null ) {
if ( $this->fld_watched && $this->watched && $this->watched[$ns][$dbkey] ) {
$pageInfo['watched'] = $this->watched[$ns][$dbkey];
if ( isset( $this->watchlistExpiries[$ns][$dbkey] ) ) {
$pageInfo['watchlistexpiry'] = $this->watchlistExpiries[$ns][$dbkey];
}
}
if ( $this->fld_watchers ) {
@ -908,22 +918,28 @@ class ApiQueryInfo extends ApiQueryBase {
}
$this->watched = [];
$this->watchlistExpiries = [];
$this->notificationtimestamps = [];
$timestamps = $this->watchedItemStore->getNotificationTimestampsBatch( $user, $this->everything );
/** @var WatchedItem[] $items */
$items = $this->watchedItemStore->loadWatchedItemsBatch( $user, $this->everything );
if ( $this->fld_watched ) {
foreach ( $timestamps as $namespaceId => $dbKeys ) {
$this->watched[$namespaceId] = array_map(
function ( $x ) {
return $x !== false;
},
$dbKeys
);
foreach ( $items as $item ) {
$nsId = $item->getLinkTarget()->getNamespace();
$dbKey = $item->getLinkTarget()->getDBkey();
if ( $this->fld_watched ) {
$this->watched[$nsId][$dbKey] = true;
$expiry = $item->getExpiry( TS_ISO_8601 );
if ( $expiry ) {
$this->watchlistExpiries[$nsId][$dbKey] = $expiry;
}
}
if ( $this->fld_notificationtimestamp ) {
$this->notificationtimestamps[$nsId][$dbKey] = $item->getNotificationTimestamp();
}
}
if ( $this->fld_notificationtimestamp ) {
$this->notificationtimestamps = $timestamps;
}
}

View file

@ -81,6 +81,10 @@ class NoWriteWatchedItemStore implements WatchedItemStoreInterface {
return $this->actualStore->loadWatchedItem( $user, $target );
}
public function loadWatchedItemsBatch( UserIdentity $user, array $targets ) {
return $this->actualStore->loadWatchedItemsBatch( $user, $targets );
}
public function getWatchedItemsForUser( UserIdentity $user, array $options = [] ) {
return $this->actualStore->getWatchedItemsForUser( $user, $options );
}

View file

@ -721,6 +721,17 @@ class WatchedItemStore implements WatchedItemStoreInterface, StatsdAwareInterfac
* @return WatchedItem|false
*/
public function loadWatchedItem( UserIdentity $user, LinkTarget $target ) {
$item = $this->loadWatchedItemsBatch( $user, [ $target ] );
return $item ? $item[0] : false;
}
/**
* @since 1.36
* @param UserIdentity $user
* @param LinkTarget[] $targets
* @return WatchedItem[]|false
*/
public function loadWatchedItemsBatch( UserIdentity $user, array $targets ) {
// Only registered user can have a watchlist
if ( !$user->isRegistered() ) {
return false;
@ -728,22 +739,27 @@ class WatchedItemStore implements WatchedItemStoreInterface, StatsdAwareInterfac
$dbr = $this->getConnectionRef( DB_REPLICA );
$row = $this->fetchWatchedItems(
$rows = $this->fetchWatchedItems(
$dbr,
$user,
[ 'wl_notificationtimestamp' ],
[ 'wl_namespace', 'wl_title', 'wl_notificationtimestamp' ],
[],
$target
$targets
);
if ( !$row ) {
if ( !$rows ) {
return false;
}
$item = $this->getWatchedItemFromRow( $user, $target, $row );
$this->cache( $item );
$items = [];
foreach ( $rows as $row ) {
$target = new TitleValue( (int)$row->wl_namespace, $row->wl_title );
$item = $this->getWatchedItemFromRow( $user, $target, $row );
$this->cache( $item );
$items[] = $item;
}
return $item;
return $items;
}
/**
@ -818,7 +834,7 @@ class WatchedItemStore implements WatchedItemStoreInterface, StatsdAwareInterfac
}
/**
* Fetches either a single or all watched items for the given user.
* Fetches either a single or all watched items for the given user, or a specific set of items.
* If a $target is given, IDatabase::selectRow() is called, otherwise select().
* If $wgWatchlistExpiry is enabled, expired items are not returned.
*
@ -826,7 +842,7 @@ class WatchedItemStore implements WatchedItemStoreInterface, StatsdAwareInterfac
* @param UserIdentity $user
* @param array $vars we_expiry is added when $wgWatchlistExpiry is enabled.
* @param array $options
* @param LinkTarget|null $target null if selecting all watched items.
* @param LinkTarget|LinkTarget[]|null $target null if selecting all watched items.
* @return IResultWrapper|stdClass|false
*/
private function fetchWatchedItems(
@ -834,17 +850,31 @@ class WatchedItemStore implements WatchedItemStoreInterface, StatsdAwareInterfac
UserIdentity $user,
array $vars,
array $options = [],
?LinkTarget $target = null
$target = null
) {
$dbMethod = 'select';
$conds = [ 'wl_user' => $user->getId() ];
if ( $target ) {
$dbMethod = 'selectRow';
$conds = array_merge( $conds, [
'wl_namespace' => $target->getNamespace(),
'wl_title' => $target->getDBkey(),
] );
if ( $target instanceof LinkTarget ) {
$dbMethod = 'selectRow';
$conds = array_merge( $conds, [
'wl_namespace' => $target->getNamespace(),
'wl_title' => $target->getDBkey(),
] );
} else {
$titleConds = [];
foreach ( $target as $linkTarget ) {
$titleConds[] = $db->makeList(
[
'wl_namespace' => $linkTarget->getNamespace(),
'wl_title' => $linkTarget->getDBkey(),
],
$db::LIST_AND
);
}
$conds[] = $db->makeList( $titleConds, $db::LIST_OR );
}
}
if ( $this->expiryEnabled ) {

View file

@ -136,6 +136,18 @@ interface WatchedItemStoreInterface {
*/
public function loadWatchedItem( UserIdentity $user, LinkTarget $target );
/**
* Loads a set of WatchedItems from the db.
*
* @since 1.36
*
* @param UserIdentity $user
* @param LinkTarget[] $targets
*
* @return WatchedItem[]|false
*/
public function loadWatchedItemsBatch( UserIdentity $user, array $targets );
/**
* @since 1.31 Method Added
* @since 1.35 Allows 'sortByExpiry' as a key in $options

View file

@ -12,6 +12,13 @@ use MediaWiki\MediaWikiServices;
*/
class ApiQueryInfoTest extends ApiTestCase {
protected function setUp(): void {
parent::setUp();
$this->setMwGlobals( [
'wgWatchlistExpiry' => true,
] );
}
/**
* @covers ::execute
* @covers ::extractPageInfo
@ -19,12 +26,22 @@ class ApiQueryInfoTest extends ApiTestCase {
public function testExecute() {
$page = $this->getExistingTestPage( 'Pluto' );
$title = $page->getTitle();
$user = $this->getTestUser()->getUser();
WatchAction::doWatch(
$title,
$user,
User::CHECK_USER_RIGHTS,
'30300101000000'
);
$watchItemStore = MediaWikiServices::getInstance()->getWatchedItemStore();
$expiry = $watchItemStore->getWatchedItem( $user, $title )->getExpiry( TS_ISO_8601 );
list( $data ) = $this->doApiRequest( [
'action' => 'query',
'prop' => 'info',
'inprop' => 'watched',
'titles' => $title->getText(),
] );
], null, false, $user );
$this->assertArrayHasKey( 'query', $data );
$this->assertArrayHasKey( 'pages', $data['query'] );
@ -42,6 +59,8 @@ class ApiQueryInfoTest extends ApiTestCase {
$this->assertSame( $title->getLatestRevID(), $info['lastrevid'] );
$this->assertSame( $title->getLength(), $info['length'] );
$this->assertSame( $title->isNewPage(), $info['new'] );
$this->assertTrue( $info['watched'] );
$this->assertSame( $expiry, $info['watchlistexpiry'] );
$this->assertArrayNotHasKey( 'actions', $info );
$this->assertArrayNotHasKey( 'linkclasses', $info );
}

View file

@ -1271,24 +1271,29 @@ class WatchedItemStoreUnitTest extends MediaWikiIntegrationTestCase {
$mockDb->expects( $this->once() )
->method( 'addQuotes' )
->willReturn( '20200101000000' );
$makeListSql = "wl_namespace = 0 AND wl_title = 'SomeDbKey'";
$mockDb->expects( $this->exactly( 2 ) )
->method( 'makeList' )
->willReturnOnConsecutiveCalls( $makeListSql, $makeListSql );
$mockDb->expects( $this->once() )
->method( 'selectRow' )
->method( 'select' )
->with(
[ 'watchlist', 'watchlist_expiry' ],
[ 'wl_notificationtimestamp', 'we_expiry' ],
[ 'wl_namespace', 'wl_title', 'wl_notificationtimestamp', 'we_expiry' ],
[
'wl_user' => 1,
'wl_namespace' => 0,
'wl_title' => 'SomeDbKey',
$makeListSql,
'we_expiry IS NULL OR we_expiry > 20200101000000'
]
)
->will( $this->returnValue(
->will( $this->returnValue( [
$this->getFakeRow( [
'wl_namespace' => 0,
'wl_title' => 'SomeDbKey',
'wl_notificationtimestamp' => '20151212010101',
'we_expiry' => '20300101000000'
] )
) );
] ) );
$mockCache = $this->getMockCache();
$mockCache->expects( $this->once() )
@ -1316,17 +1321,7 @@ class WatchedItemStoreUnitTest extends MediaWikiIntegrationTestCase {
->method( 'addQuotes' )
->willReturn( '20200101000000' );
$mockDb->expects( $this->once() )
->method( 'selectRow' )
->with(
[ 'watchlist', 'watchlist_expiry' ],
[ 'wl_notificationtimestamp', 'we_expiry' ],
[
'wl_user' => 1,
'wl_namespace' => 0,
'wl_title' => 'SomeDbKey',
'we_expiry IS NULL OR we_expiry > 20200101000000'
]
)
->method( 'select' )
->will( $this->returnValue( [] ) );
$mockCache = $this->getMockCache();
@ -1346,7 +1341,7 @@ class WatchedItemStoreUnitTest extends MediaWikiIntegrationTestCase {
public function testLoadWatchedItem_anonymousUser() {
$mockDb = $this->getMockDb();
$mockDb->expects( $this->never() )
->method( 'selectRow' );
->method( 'select' );
$mockCache = $this->getMockCache();
$mockCache->expects( $this->never() )->method( 'get' );
@ -1456,24 +1451,29 @@ class WatchedItemStoreUnitTest extends MediaWikiIntegrationTestCase {
$mockDb->expects( $this->once() )
->method( 'addQuotes' )
->willReturn( '20200101000000' );
$makeListSql = "wl_namespace = 0 AND wl_title = 'SomeDbKey'";
$mockDb->expects( $this->exactly( 2 ) )
->method( 'makeList' )
->willReturnOnConsecutiveCalls( $makeListSql, $makeListSql );
$mockDb->expects( $this->once() )
->method( 'selectRow' )
->method( 'select' )
->with(
[ 'watchlist', 'watchlist_expiry' ],
[ 'wl_notificationtimestamp', 'we_expiry' ],
[ 'wl_namespace', 'wl_title', 'wl_notificationtimestamp', 'we_expiry' ],
[
'wl_user' => 1,
'wl_namespace' => 0,
'wl_title' => 'SomeDbKey',
$makeListSql,
'we_expiry IS NULL OR we_expiry > 20200101000000'
]
)
->will( $this->returnValue(
->will( $this->returnValue( [
$this->getFakeRow( [
'wl_namespace' => 0,
'wl_title' => 'SomeDbKey',
'wl_notificationtimestamp' => '20151212010101',
'we_expiry' => '20300101000000'
] )
) );
] ) );
$mockCache = $this->getMockCache();
$mockCache->expects( $this->never() )->method( 'delete' );
@ -1537,15 +1537,18 @@ class WatchedItemStoreUnitTest extends MediaWikiIntegrationTestCase {
$mockDb->expects( $this->once() )
->method( 'addQuotes' )
->willReturn( '20200101000000' );
$makeListSql = "wl_namespace = 0 AND wl_title = 'SomeDbKey'";
$mockDb->expects( $this->exactly( 2 ) )
->method( 'makeList' )
->willReturnOnConsecutiveCalls( $makeListSql, $makeListSql );
$mockDb->expects( $this->once() )
->method( 'selectRow' )
->method( 'select' )
->with(
[ 'watchlist', 'watchlist_expiry' ],
[ 'wl_notificationtimestamp', 'we_expiry' ],
[ 'wl_namespace', 'wl_title', 'wl_notificationtimestamp', 'we_expiry' ],
[
'wl_user' => 1,
'wl_namespace' => 0,
'wl_title' => 'SomeDbKey',
$makeListSql,
'we_expiry IS NULL OR we_expiry > 20200101000000'
]
)
@ -1771,21 +1774,28 @@ class WatchedItemStoreUnitTest extends MediaWikiIntegrationTestCase {
$mockDb->expects( $this->once() )
->method( 'addQuotes' )
->willReturn( '20200101000000' );
$makeListSql = "wl_namespace = 0 AND wl_title = 'SomeDbKey'";
$mockDb->expects( $this->exactly( 2 ) )
->method( 'makeList' )
->willReturnOnConsecutiveCalls( $makeListSql, $makeListSql );
$mockDb->expects( $this->once() )
->method( 'selectRow' )
->method( 'select' )
->with(
[ 'watchlist', 'watchlist_expiry' ],
[ 'wl_notificationtimestamp', 'we_expiry' ],
[ 'wl_namespace', 'wl_title', 'wl_notificationtimestamp', 'we_expiry' ],
[
'wl_user' => 1,
'wl_namespace' => 0,
'wl_title' => 'SomeDbKey',
$makeListSql,
'we_expiry IS NULL OR we_expiry > 20200101000000'
]
)
->will( $this->returnValue(
$this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
) );
->will( $this->returnValue( [
$this->getFakeRow( [
'wl_namespace' => 0,
'wl_title' => 'SomeDbKey',
'wl_notificationtimestamp' => '20151212010101',
] )
] ) );
$mockCache = $this->getMockCache();
$mockCache->expects( $this->never() )->method( 'delete' );
@ -1814,15 +1824,18 @@ class WatchedItemStoreUnitTest extends MediaWikiIntegrationTestCase {
$mockDb->expects( $this->once() )
->method( 'addQuotes' )
->willReturn( '20200101000000' );
$makeListSql = "wl_namespace = 0 AND wl_title = 'SomeDbKey'";
$mockDb->expects( $this->exactly( 2 ) )
->method( 'makeList' )
->willReturnOnConsecutiveCalls( $makeListSql, $makeListSql );
$mockDb->expects( $this->once() )
->method( 'selectRow' )
->method( 'select' )
->with(
[ 'watchlist', 'watchlist_expiry' ],
[ 'wl_notificationtimestamp', 'we_expiry' ],
[ 'wl_namespace', 'wl_title', 'wl_notificationtimestamp', 'we_expiry' ],
[
'wl_user' => 1,
'wl_namespace' => 0,
'wl_title' => 'SomeDbKey',
$makeListSql,
'we_expiry IS NULL OR we_expiry > 20200101000000'
]
)
@ -2121,15 +2134,18 @@ class WatchedItemStoreUnitTest extends MediaWikiIntegrationTestCase {
$mockDb->expects( $this->once() )
->method( 'addQuotes' )
->willReturn( '20200101000000' );
$makeListSql = "wl_namespace = 0 AND wl_title = 'SomeDbKey'";
$mockDb->expects( $this->exactly( 2 ) )
->method( 'makeList' )
->willReturnOnConsecutiveCalls( $makeListSql, $makeListSql );
$mockDb->expects( $this->once() )
->method( 'selectRow' )
->method( 'select' )
->with(
[ 'watchlist', 'watchlist_expiry' ],
[ 'wl_notificationtimestamp', 'we_expiry' ],
[ 'wl_namespace', 'wl_title', 'wl_notificationtimestamp', 'we_expiry' ],
[
'wl_user' => 1,
'wl_namespace' => 0,
'wl_title' => 'SomeDbKey',
$makeListSql,
'we_expiry IS NULL OR we_expiry > 20200101000000'
]
)
@ -2158,21 +2174,28 @@ class WatchedItemStoreUnitTest extends MediaWikiIntegrationTestCase {
$mockDb->expects( $this->once() )
->method( 'addQuotes' )
->willReturn( '20200101000000' );
$makeListSql = "wl_namespace = 0 AND wl_title = 'SomeDbKey'";
$mockDb->expects( $this->exactly( 2 ) )
->method( 'makeList' )
->willReturnOnConsecutiveCalls( $makeListSql, $makeListSql );
$mockDb->expects( $this->once() )
->method( 'selectRow' )
->method( 'select' )
->with(
[ 'watchlist', 'watchlist_expiry' ],
[ 'wl_notificationtimestamp', 'we_expiry' ],
[ 'wl_namespace', 'wl_title', 'wl_notificationtimestamp', 'we_expiry' ],
[
'wl_user' => 1,
'wl_namespace' => 0,
'wl_title' => 'SomeDbKey',
$makeListSql,
'we_expiry IS NULL OR we_expiry > 20200101000000'
]
)
->will( $this->returnValue(
$this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
) );
->will( $this->returnValue( [
$this->getFakeRow( [
'wl_namespace' => 0,
'wl_title' => 'SomeDbKey',
'wl_notificationtimestamp' => '20151212010101',
] )
] ) );
$mockCache = $this->getMockCache();
$mockCache->expects( $this->never() )->method( 'get' );
@ -2367,21 +2390,28 @@ class WatchedItemStoreUnitTest extends MediaWikiIntegrationTestCase {
$mockDb->expects( $this->once() )
->method( 'addQuotes' )
->willReturn( '20200101000000' );
$makeListSql = "wl_namespace = 0 AND wl_title = 'SomeDbKey'";
$mockDb->expects( $this->exactly( 2 ) )
->method( 'makeList' )
->willReturnOnConsecutiveCalls( $makeListSql, $makeListSql );
$mockDb->expects( $this->once() )
->method( 'selectRow' )
->method( 'select' )
->with(
[ 'watchlist', 'watchlist_expiry' ],
[ 'wl_notificationtimestamp', 'we_expiry' ],
[ 'wl_namespace', 'wl_title', 'wl_notificationtimestamp', 'we_expiry' ],
[
'wl_user' => 1,
'wl_namespace' => 0,
'wl_title' => 'SomeDbKey',
$makeListSql,
'we_expiry IS NULL OR we_expiry > 20200101000000'
]
)
->will( $this->returnValue(
$this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
) );
->will( $this->returnValue( [
$this->getFakeRow( [
'wl_namespace' => 0,
'wl_title' => 'SomeDbKey',
'wl_notificationtimestamp' => '20151212010101',
] )
] ) );
$mockCache = $this->getMockCache();
$mockCache->expects( $this->never() )->method( 'get' );
@ -2456,15 +2486,18 @@ class WatchedItemStoreUnitTest extends MediaWikiIntegrationTestCase {
$mockDb->expects( $this->once() )
->method( 'addQuotes' )
->willReturn( '20200101000000' );
$makeListSql = "wl_namespace = 0 AND wl_title = 'SomeDbKey'";
$mockDb->expects( $this->exactly( 2 ) )
->method( 'makeList' )
->willReturnOnConsecutiveCalls( $makeListSql, $makeListSql );
$mockDb->expects( $this->once() )
->method( 'selectRow' )
->method( 'select' )
->with(
[ 'watchlist', 'watchlist_expiry' ],
[ 'wl_notificationtimestamp', 'we_expiry' ],
[ 'wl_namespace', 'wl_title', 'wl_notificationtimestamp', 'we_expiry' ],
[
'wl_user' => 1,
'wl_namespace' => 0,
'wl_title' => 'SomeDbKey',
$makeListSql,
'we_expiry IS NULL OR we_expiry > 20200101000000'
]
)
@ -2545,21 +2578,28 @@ class WatchedItemStoreUnitTest extends MediaWikiIntegrationTestCase {
$mockDb->expects( $this->once() )
->method( 'addQuotes' )
->willReturn( '20200101000000' );
$makeListSql = "wl_namespace = 0 AND wl_title = 'SomeDbKey'";
$mockDb->expects( $this->exactly( 2 ) )
->method( 'makeList' )
->willReturnOnConsecutiveCalls( $makeListSql, $makeListSql );
$mockDb->expects( $this->once() )
->method( 'selectRow' )
->method( 'select' )
->with(
[ 'watchlist', 'watchlist_expiry' ],
[ 'wl_notificationtimestamp', 'we_expiry' ],
[ 'wl_namespace', 'wl_title', 'wl_notificationtimestamp', 'we_expiry' ],
[
'wl_user' => 1,
'wl_namespace' => 0,
'wl_title' => 'SomeDbKey',
$makeListSql,
'we_expiry IS NULL OR we_expiry > 20200101000000'
]
)
->will( $this->returnValue(
$this->getFakeRow( [ 'wl_notificationtimestamp' => '30151212010101' ] )
) );
->will( $this->returnValue( [
$this->getFakeRow( [
'wl_namespace' => 0,
'wl_title' => 'SomeDbKey',
'wl_notificationtimestamp' => '30151212010101',
] )
] ) );
$mockCache = $this->getMockCache();
$mockCache->expects( $this->never() )->method( 'get' );
@ -2638,21 +2678,28 @@ class WatchedItemStoreUnitTest extends MediaWikiIntegrationTestCase {
$mockDb->expects( $this->once() )
->method( 'addQuotes' )
->willReturn( '20200101000000' );
$makeListSql = "wl_namespace = 0 AND wl_title = 'SomeDbKey'";
$mockDb->expects( $this->exactly( 2 ) )
->method( 'makeList' )
->willReturnOnConsecutiveCalls( $makeListSql, $makeListSql );
$mockDb->expects( $this->once() )
->method( 'selectRow' )
->method( 'select' )
->with(
[ 'watchlist', 'watchlist_expiry' ],
[ 'wl_notificationtimestamp', 'we_expiry' ],
[ 'wl_namespace', 'wl_title', 'wl_notificationtimestamp', 'we_expiry' ],
[
'wl_user' => 1,
'wl_namespace' => 0,
'wl_title' => 'SomeDbKey',
'we_expiry IS NULL OR we_expiry > 20200101000000'
$makeListSql,
'we_expiry IS NULL OR we_expiry > 20200101000000',
]
)
->will( $this->returnValue(
$this->getFakeRow( [ 'wl_notificationtimestamp' => '30151212010101' ] )
) );
->will( $this->returnValue( [
$this->getFakeRow( [
'wl_namespace' => 0,
'wl_title' => 'SomeDbKey',
'wl_notificationtimestamp' => '30151212010101',
] )
] ) );
$mockCache = $this->getMockCache();
$mockCache->expects( $this->never() )->method( 'get' );
@ -2892,10 +2939,14 @@ class WatchedItemStoreUnitTest extends MediaWikiIntegrationTestCase {
$mockDb = $this->getMockDb();
$mockDb->expects( $this->once() )
->method( 'selectRow' )
->will( $this->returnValue(
$this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
) );
->method( 'select' )
->will( $this->returnValue( [
$this->getFakeRow( [
'wl_namespace' => 0,
'wl_title' => 'SomeDbKey',
'wl_notificationtimestamp' => '20151212010101'
] )
] ) );
$mockDb->expects( $this->once() )
->method( 'selectFieldValues' )
->will(