add setNotificationTimestampsForUser to WatchedItemStore
authoraddshore <addshorewiki@gmail.com>
Wed, 4 May 2016 15:49:27 +0000 (16:49 +0100)
committerAddshore <addshorewiki@gmail.com>
Wed, 18 May 2016 08:50:46 +0000 (08:50 +0000)
Bug: T134387
Change-Id: Ia6abe7687b51aabe67e8461375075692db28c9a2

includes/WatchedItemStore.php
includes/api/ApiSetNotificationTimestamp.php
tests/phpunit/includes/WatchedItemStoreIntegrationTest.php
tests/phpunit/includes/WatchedItemStoreUnitTest.php

index eb652ce..f0619d6 100644 (file)
@@ -145,16 +145,28 @@ class WatchedItemStore implements StatsdAwareInterface {
        }
 
        private function uncacheLinkTarget( LinkTarget $target ) {
+               $this->stats->increment( 'WatchedItemStore.uncacheLinkTarget' );
                if ( !isset( $this->cacheIndex[$target->getNamespace()][$target->getDBkey()] ) ) {
                        return;
                }
-               $this->stats->increment( 'WatchedItemStore.uncacheLinkTarget' );
                foreach ( $this->cacheIndex[$target->getNamespace()][$target->getDBkey()] as $key ) {
                        $this->stats->increment( 'WatchedItemStore.uncacheLinkTarget.items' );
                        $this->cache->delete( $key );
                }
        }
 
+       private function uncacheUser( User $user ) {
+               $this->stats->increment( 'WatchedItemStore.uncacheUser' );
+               foreach ( $this->cacheIndex as $ns => $dbKeyArray ) {
+                       foreach ( $dbKeyArray as $dbKey => $userArray ) {
+                               if ( isset( $userArray[$user->getId()] ) ) {
+                                       $this->stats->increment( 'WatchedItemStore.uncacheUser.items' );
+                                       $this->cache->delete( $userArray[$user->getId()] );
+                               }
+                       }
+               }
+       }
+
        /**
         * @param User $user
         * @param LinkTarget $target
@@ -667,6 +679,41 @@ class WatchedItemStore implements StatsdAwareInterface {
                return $success;
        }
 
+       /**
+        * @param User $user The user to set the timestamp for
+        * @param string $timestamp Set the update timestamp to this value
+        * @param LinkTarget[] $targets List of targets to update. Default to all targets
+        *
+        * @return bool success
+        */
+       public function setNotificationTimestampsForUser( User $user, $timestamp, array $targets = [] ) {
+               // Only loggedin user can have a watchlist
+               if ( $user->isAnon() ) {
+                       return false;
+               }
+
+               $dbw = $this->getConnection( DB_MASTER );
+
+               $conds = [ 'wl_user' => $user->getId() ];
+               if ( $targets ) {
+                       $batch = new LinkBatch( $targets );
+                       $conds[] = $batch->constructSet( 'wl', $dbw );
+               }
+
+               $success = $dbw->update(
+                       'watchlist',
+                       [ 'wl_notificationtimestamp' => $dbw->timestamp( $timestamp ) ],
+                       $conds,
+                       __METHOD__
+               );
+
+               $this->reuseConnection( $dbw );
+
+               $this->uncacheUser( $user );
+
+               return $success;
+       }
+
        /**
         * @param User $editor The editor that triggered the update. Their notification
         *  timestamp will not be updated(they have already seen it)
index ea52e14..a299e87 100644 (file)
@@ -24,6 +24,7 @@
  *
  * @file
  */
+use MediaWiki\MediaWikiServices;
 
 /**
  * API interface for setting the wl_notificationtimestamp field
@@ -98,13 +99,14 @@ class ApiSetNotificationTimestamp extends ApiBase {
                        }
                }
 
+               $watchedItemStore = MediaWikiServices::getInstance()->getWatchedItemStore();
                $apiResult = $this->getResult();
                $result = [];
                if ( $params['entirewatchlist'] ) {
                        // Entire watchlist mode: Just update the thing and return a success indicator
-                       $dbw->update( 'watchlist', [ 'wl_notificationtimestamp' => $timestamp ],
-                               [ 'wl_user' => $user->getId() ],
-                               __METHOD__
+                       $watchedItemStore->setNotificationTimestampsForUser(
+                               $user,
+                               $timestamp
                        );
 
                        $result['notificationtimestamp'] = is_null( $timestamp )
@@ -133,14 +135,15 @@ class ApiSetNotificationTimestamp extends ApiBase {
 
                        if ( $pageSet->getTitles() ) {
                                // Now process the valid titles
-                               $lb = new LinkBatch( $pageSet->getTitles() );
-                               $dbw->update( 'watchlist', [ 'wl_notificationtimestamp' => $timestamp ],
-                                       [ 'wl_user' => $user->getId(), $lb->constructSet( 'wl', $dbw ) ],
-                                       __METHOD__
+                               $watchedItemStore->setNotificationTimestampsForUser(
+                                       $user,
+                                       $timestamp,
+                                       $pageSet->getTitles()
                                );
 
                                // Query the results of our update
                                $timestamps = [];
+                               $lb = new LinkBatch( $pageSet->getTitles() );
                                $res = $dbw->select(
                                        'watchlist',
                                        [ 'wl_namespace', 'wl_title', 'wl_notificationtimestamp' ],
index f34af61..61b62aa 100644 (file)
@@ -106,7 +106,7 @@ class WatchedItemStoreIntegrationTest extends MediaWikiTestCase {
                );
        }
 
-       public function testUpdateAndResetNotificationTimestamp() {
+       public function testUpdateResetAndSetNotificationTimestamp() {
                $user = $this->getUser();
                $otherUser = ( new TestUser( 'WatchedItemStoreIntegrationTestUser_otherUser' ) )->getUser();
                $title = Title::newFromText( 'WatchedItemStoreIntegrationTestPage' );
@@ -172,6 +172,24 @@ class WatchedItemStoreIntegrationTest extends MediaWikiTestCase {
                                [ [ $title, '20150202020202' ] ], $initialVisitingWatchers + 1
                        )
                );
+
+               // setNotificationTimestampsForUser specifying a title
+               $this->assertTrue(
+                       $store->setNotificationTimestampsForUser( $user, '20200202020202', [ $title ] )
+               );
+               $this->assertEquals(
+                       '20200202020202',
+                       $store->getWatchedItem( $user, $title )->getNotificationTimestamp()
+               );
+
+               // setNotificationTimestampsForUser not specifying a title
+               $this->assertTrue(
+                       $store->setNotificationTimestampsForUser( $user, '20210202020202' )
+               );
+               $this->assertEquals(
+                       '20210202020202',
+                       $store->getWatchedItem( $user, $title )->getNotificationTimestamp()
+               );
        }
 
        public function testDuplicateAllAssociatedEntries() {
index 6c4a6f0..2d2e726 100644 (file)
@@ -2366,6 +2366,81 @@ class WatchedItemStoreUnitTest extends MediaWikiTestCase {
                ScopedCallback::consume( $scopedOverrideRevision );
        }
 
+       public function testSetNotificationTimestampsForUser_anonUser() {
+               $store = $this->newWatchedItemStore(
+                       $this->getMockLoadBalancer( $this->getMockDb() ),
+                       $this->getMockCache()
+               );
+               $this->assertFalse( $store->setNotificationTimestampsForUser( $this->getAnonUser(), '' ) );
+       }
+
+       public function testSetNotificationTimestampsForUser_allRows() {
+               $user = $this->getMockNonAnonUserWithId( 1 );
+               $timestamp = '20100101010101';
+
+               $mockDb = $this->getMockDb();
+               $mockDb->expects( $this->once() )
+                       ->method( 'update' )
+                       ->with(
+                               'watchlist',
+                               [ 'wl_notificationtimestamp' => 'TS' . $timestamp . 'TS' ],
+                               [ 'wl_user' => 1 ]
+                       )
+                       ->will( $this->returnValue( true ) );
+               $mockDb->expects( $this->exactly( 1 ) )
+                       ->method( 'timestamp' )
+                       ->will( $this->returnCallback( function( $value ) {
+                               return 'TS' . $value . 'TS';
+                       } ) );
+
+               $store = $this->newWatchedItemStore(
+                       $this->getMockLoadBalancer( $mockDb ),
+                       $this->getMockCache()
+               );
+
+               $this->assertTrue(
+                       $store->setNotificationTimestampsForUser( $user, $timestamp )
+               );
+       }
+
+       public function testSetNotificationTimestampsForUser_specificTargets() {
+               $user = $this->getMockNonAnonUserWithId( 1 );
+               $timestamp = '20100101010101';
+               $targets = [ new TitleValue( 0, 'Foo' ), new TitleValue( 0, 'Bar' ) ];
+
+               $mockDb = $this->getMockDb();
+               $mockDb->expects( $this->once() )
+                       ->method( 'update' )
+                       ->with(
+                               'watchlist',
+                               [ 'wl_notificationtimestamp' => 'TS' . $timestamp . 'TS' ],
+                               [ 'wl_user' => 1, 0 => 'makeWhereFrom2d return value' ]
+                       )
+                       ->will( $this->returnValue( true ) );
+               $mockDb->expects( $this->exactly( 1 ) )
+                       ->method( 'timestamp' )
+                       ->will( $this->returnCallback( function( $value ) {
+                               return 'TS' . $value . 'TS';
+                       } ) );
+               $mockDb->expects( $this->once() )
+                       ->method( 'makeWhereFrom2d' )
+                       ->with(
+                               [ [ 'Foo' => 1, 'Bar' => 1 ] ],
+                               $this->isType( 'string' ),
+                               $this->isType( 'string' )
+                       )
+                       ->will( $this->returnValue( 'makeWhereFrom2d return value' ) );
+
+               $store = $this->newWatchedItemStore(
+                       $this->getMockLoadBalancer( $mockDb ),
+                       $this->getMockCache()
+               );
+
+               $this->assertTrue(
+                       $store->setNotificationTimestampsForUser( $user, $timestamp, $targets )
+               );
+       }
+
        public function testUpdateNotificationTimestamp_watchersExist() {
                $mockDb = $this->getMockDb();
                $mockDb->expects( $this->once() )