Merge "Also support continuation requests for $unreadFirst"

This commit is contained in:
jenkins-bot 2015-12-15 17:32:17 +00:00 committed by Gerrit Code Review
commit 95290e8a4e
5 changed files with 57 additions and 29 deletions

View file

@ -309,7 +309,7 @@ class MWEchoNotifUser {
$eventTypesToLoad = $attributeManager->getUserEnabledEventsbySections( $this->mUser, 'web', array( $section ) ); $eventTypesToLoad = $attributeManager->getUserEnabledEventsbySections( $this->mUser, 'web', array( $section ) );
} }
$notifications = $this->notifMapper->fetchUnreadByUser( $this->mUser, 1, $eventTypesToLoad, $dbSource ); $notifications = $this->notifMapper->fetchUnreadByUser( $this->mUser, 1, null, $eventTypesToLoad, $dbSource );
if ( $notifications ) { if ( $notifications ) {
$notification = reset( $notifications ); $notification = reset( $notifications );
$timestamp = $notification->getTimestamp(); $timestamp = $notification->getTimestamp();
@ -344,7 +344,7 @@ class MWEchoNotifUser {
// After this 'mark read', is there any unread edit-user-talk // After this 'mark read', is there any unread edit-user-talk
// remaining? If not, we should clear the newtalk flag. // remaining? If not, we should clear the newtalk flag.
if ( $this->mUser->getNewtalk() ) { if ( $this->mUser->getNewtalk() ) {
$unreadEditUserTalk = $this->notifMapper->fetchUnreadByUser( $this->mUser, 1, array( 'edit-user-talk' ), DB_MASTER ); $unreadEditUserTalk = $this->notifMapper->fetchUnreadByUser( $this->mUser, 1, null, array( 'edit-user-talk' ), DB_MASTER );
if ( count( $unreadEditUserTalk ) === 0 ) { if ( count( $unreadEditUserTalk ) === 0 ) {
$this->mUser->setNewtalk( false ); $this->mUser->setNewtalk( false );
} }
@ -379,7 +379,7 @@ class MWEchoNotifUser {
$attributeManager = EchoAttributeManager::newFromGlobalVars(); $attributeManager = EchoAttributeManager::newFromGlobalVars();
$eventTypes = $attributeManager->getUserEnabledEventsbySections( $this->mUser, 'web', $sections ); $eventTypes = $attributeManager->getUserEnabledEventsbySections( $this->mUser, 'web', $sections );
$notifs = $this->notifMapper->fetchUnreadByUser( $this->mUser, $wgEchoMaxUpdateCount, $eventTypes ); $notifs = $this->notifMapper->fetchUnreadByUser( $this->mUser, $wgEchoMaxUpdateCount, null, $eventTypes );
$eventIds = array_filter( $eventIds = array_filter(
array_map( function ( EchoNotification $notif ) { array_map( function ( EchoNotification $notif ) {

View file

@ -111,17 +111,46 @@ class ApiEchoNotifications extends ApiQueryBase {
// Prefer unread notifications. We don't care about next offset in this case // Prefer unread notifications. We don't care about next offset in this case
if ( $unreadFirst ) { if ( $unreadFirst ) {
$notifs = $notifMapper->fetchUnreadByUser( $user, $limit, $eventTypes ); // query for unread notifications past 'continue' (offset)
$notifs = $notifMapper->fetchUnreadByUser( $user, $limit + 1, $continue, $eventTypes );
/*
* 'continue' has a timestamp & id (to start with, in case there
* would be multiple events with that same timestamp)
* Unread notifications should always load first, but may be older
* than read ones, but we can work with current 'continue' format:
* * if there is no continue, first load unread notifications
* * if there is a continue, fetch unread notifications first:
* * if there are no unread ones, continue must've been about read:
* fetch 'em
* * if there are unread ones but first one doesn't match continue
* id, it must've been about read: discard unread & fetch read
*/
if ( $notifs && $continue ) {
/** @var EchoNotification $first */
$first = reset( $notifs );
$continueId = intval( trim( strrchr( $continue, '|' ), '|' ) );
if ( $first->getEvent()->getID() !== $continueId ) {
// notification doesn't match continue id, it must've been
// about read notifications: discard all unread ones
$notifs = array();
}
}
// If there are less unread notifications than we requested, // If there are less unread notifications than we requested,
// then fill the result with some read notifications // then fill the result with some read notifications
$count = count( $notifs ); $count = count( $notifs );
if ( $count < $limit ) { // we need 1 more than $limit, so we can respond 'continue'
if ( $count <= $limit ) {
// Query planner should be smart enough that passing a short list of ids to exclude // Query planner should be smart enough that passing a short list of ids to exclude
// will only visit at most that number of extra rows. // will only visit at most that number of extra rows.
$mixedNotifs = $notifMapper->fetchByUser( $mixedNotifs = $notifMapper->fetchByUser(
$user, $user,
$limit - $count, $limit - $count + 1,
null, // if there were unread notifications, 'continue' was for
// unread notifications and we should start fetching read
// notifications from start
$count > 0 ? null : $continue,
$eventTypes, $eventTypes,
array_keys( $notifs ) array_keys( $notifs )
); );
@ -141,11 +170,9 @@ class ApiEchoNotifications extends ApiQueryBase {
} }
// Generate offset if necessary // Generate offset if necessary
if ( !$unreadFirst ) { if ( count( $result['list'] ) > $limit ) {
if ( count( $result['list'] ) > $limit ) { $lastItem = array_pop( $result['list'] );
$lastItem = array_pop( $result['list'] ); $result['continue'] = $lastItem['timestamp']['utcunix'] . '|' . $lastItem['id'];
$result['continue'] = $lastItem['timestamp']['utcunix'] . '|' . $lastItem['id'];
}
} }
return $result; return $result;

View file

@ -1,5 +1,4 @@
<?php <?php
use EchoLinkNormalizer;
/** /**
* A formatter for the notification flyout popup * A formatter for the notification flyout popup

View file

@ -95,12 +95,13 @@ class EchoNotificationMapper extends EchoAbstractMapper {
* which is done via a deleteJob * which is done via a deleteJob
* @param User $user * @param User $user
* @param int $limit * @param int $limit
* @param string $continue Used for offset
* @param string[] $eventTypes * @param string[] $eventTypes
* @param int $dbSource Use master or slave database * @param int $dbSource Use master or slave database
* @return EchoNotification[] * @return EchoNotification[]
*/ */
public function fetchUnreadByUser( User $user, $limit, array $eventTypes = array(), $dbSource = DB_SLAVE ) { public function fetchUnreadByUser( User $user, $limit, $continue, array $eventTypes = array(), $dbSource = DB_SLAVE ) {
return $this->fetchByUserInternal( $user, $limit, $eventTypes, array( 'notification_read_timestamp' => null ), $dbSource ); return $this->fetchByUserInternal( $user, $limit, $continue, $eventTypes, array( 'notification_read_timestamp' => null ), $dbSource );
} }
/** /**
@ -121,27 +122,19 @@ class EchoNotificationMapper extends EchoAbstractMapper {
$conds[] = 'event_id NOT IN ( ' . $dbr->makeList( $excludeEventIds ) . ' ) '; $conds[] = 'event_id NOT IN ( ' . $dbr->makeList( $excludeEventIds ) . ' ) ';
} }
$offset = $this->extractQueryOffset( $continue ); return $this->fetchByUserInternal( $user, $limit, $continue, $eventTypes, $conds );
// Start points are specified
if ( $offset['timestamp'] && $offset['offset'] ) {
$ts = $dbr->addQuotes( $dbr->timestamp( $offset['timestamp'] ) );
// The offset and timestamp are those of the first notification we want to return
$conds[] = "notification_timestamp < $ts OR ( notification_timestamp = $ts AND notification_event <= " . $offset['offset'] . " )";
}
return $this->fetchByUserInternal( $user, $limit, $eventTypes, $conds );
} }
/** /**
* @param User $user the user to get notifications for * @param User $user the user to get notifications for
* @param int $limit The maximum number of notifications to return * @param int $limit The maximum number of notifications to return
* @param string $continue Used for offset
* @param array $eventTypes Event types to load * @param array $eventTypes Event types to load
* @param array $conds Additional query conditions. * @param array $conds Additional query conditions.
* @param int $dbSource Use master or slave database * @param int $dbSource Use master or slave database
* @return EchoNotification[] * @return EchoNotification[]
*/ */
protected function fetchByUserInternal( User $user, $limit, array $eventTypes = array(), array $conds = array(), $dbSource = DB_SLAVE ) { protected function fetchByUserInternal( User $user, $limit, $continue, array $eventTypes = array(), array $conds = array(), $dbSource = DB_SLAVE ) {
$dbr = $this->dbFactory->getEchoDb( $dbSource ); $dbr = $this->dbFactory->getEchoDb( $dbSource );
if ( !$eventTypes ) { if ( !$eventTypes ) {
@ -161,6 +154,15 @@ class EchoNotificationMapper extends EchoAbstractMapper {
'notification_bundle_base' => 1 'notification_bundle_base' => 1
) + $conds; ) + $conds;
$offset = $this->extractQueryOffset( $continue );
// Start points are specified
if ( $offset['timestamp'] && $offset['offset'] ) {
$ts = $dbr->addQuotes( $dbr->timestamp( $offset['timestamp'] ) );
// The offset and timestamp are those of the first notification we want to return
$conds[] = "notification_timestamp < $ts OR ( notification_timestamp = $ts AND notification_event <= " . $offset['offset'] . " )";
}
$res = $dbr->select( $res = $dbr->select(
array( 'echo_notification', 'echo_event', 'echo_target_page' ), array( 'echo_notification', 'echo_event', 'echo_target_page' ),
'*', '*',

View file

@ -12,7 +12,7 @@ class EchoNotificationMapperTest extends MediaWikiTestCase {
public function fetchUnreadByUser( User $user, $limit, array $eventTypes = array() ) { public function fetchUnreadByUser( User $user, $limit, array $eventTypes = array() ) {
// Unsuccessful select // Unsuccessful select
$notifMapper = new EchoNotificationMapper( $this->mockMWEchoDbFactory( array( 'select' => false ) ) ); $notifMapper = new EchoNotificationMapper( $this->mockMWEchoDbFactory( array( 'select' => false ) ) );
$res = $notifMapper->fetchUnreadByUser( $this->mockUser(), 10, '' ); $res = $notifMapper->fetchUnreadByUser( $this->mockUser(), 10, null, '' );
$this->assertEmpty( $res ); $this->assertEmpty( $res );
// Successful select // Successful select
@ -34,11 +34,11 @@ class EchoNotificationMapperTest extends MediaWikiTestCase {
) )
); );
$notifMapper = new EchoNotificationMapper( $this->mockMWEchoDbFactory( array( 'select' => $dbResult ) ) ); $notifMapper = new EchoNotificationMapper( $this->mockMWEchoDbFactory( array( 'select' => $dbResult ) ) );
$res = $notifMapper->fetchUnreadByUser( $this->mockUser(), 10, '', array() ); $res = $notifMapper->fetchUnreadByUser( $this->mockUser(), 10, null, '', array() );
$this->assertEmpty( $res ); $this->assertEmpty( $res );
$notifMapper = new EchoNotificationMapper( $this->mockMWEchoDbFactory( array( 'select' => $dbResult ) ) ); $notifMapper = new EchoNotificationMapper( $this->mockMWEchoDbFactory( array( 'select' => $dbResult ) ) );
$res = $notifMapper->fetchUnreadByUser( $this->mockUser(), 10, '', array( 'test_event' ) ); $res = $notifMapper->fetchUnreadByUser( $this->mockUser(), 10, null, '', array( 'test_event' ) );
$this->assertTrue( is_array( $res ) ); $this->assertTrue( is_array( $res ) );
$this->assertGreaterThan( 0, count( $res ) ); $this->assertGreaterThan( 0, count( $res ) );
foreach ( $res as $row ) { foreach ( $res as $row ) {