mirror of
https://gerrit.wikimedia.org/r/mediawiki/extensions/Echo
synced 2024-09-23 18:30:06 +00:00
Unread pages API
The query shouldn't be too expensive: it'll use an index to narrow down the resultset for 1 user. After that, it'll be sorted based on a grouped by value, but that should fit in memory: it'll never be on more than 2000 entries, which is the max amount of notifications per user. Change-Id: I271ea7f7a6e010284739bfce02c4ec8a077148fc
This commit is contained in:
parent
6b0081fd78
commit
f751e96839
1
Echo.php
1
Echo.php
|
@ -57,6 +57,7 @@ $wgJobClasses['EchoNotificationDeleteJob'] = 'EchoNotificationDeleteJob';
|
|||
|
||||
// API
|
||||
$wgAPIMetaModules['notifications'] = 'ApiEchoNotifications';
|
||||
$wgAPIMetaModules['unreadnotificationpages'] = 'ApiEchoUnreadNotificationPages';
|
||||
$wgAPIModules['echomarkread'] = 'ApiEchoMarkRead';
|
||||
$wgAPIModules['echomarkseen'] = 'ApiEchoMarkSeen';
|
||||
|
||||
|
|
|
@ -10,6 +10,7 @@ $wgAutoloadClasses += [
|
|||
'ApiEchoMarkSeen' => __DIR__ . '/includes/api/ApiEchoMarkSeen.php',
|
||||
'ApiEchoNotifications' => __DIR__ . '/includes/api/ApiEchoNotifications.php',
|
||||
'ApiEchoNotificationsTest' => __DIR__ . '/tests/phpunit/api/ApiEchoNotificationsTest.php',
|
||||
'ApiEchoUnreadNotificationPages' => __DIR__ . '/includes/api/ApiEchoUnreadNotificationPages.php',
|
||||
'ContainmentSetTest' => __DIR__ . '/tests/phpunit/ContainmentSetTest.php',
|
||||
'EchoAbstractEntity' => __DIR__ . '/includes/model/AbstractEntity.php',
|
||||
'EchoAbstractMapper' => __DIR__ . '/includes/mapper/AbstractMapper.php',
|
||||
|
|
|
@ -259,5 +259,9 @@
|
|||
"apihelp-query+notifications-param-messagecontinue": "When more message results are available, use this to continue.",
|
||||
"apihelp-query+notifications-param-messageunreadfirst": "Whether to show unread alert notifications first (only used if groupbysection is set).",
|
||||
"apihelp-query+notifications-example-1": "List notifications",
|
||||
"apihelp-query+notifications-example-2": "List notifications, grouped by section, with counts"
|
||||
"apihelp-query+notifications-example-2": "List notifications, grouped by section, with counts",
|
||||
"apihelp-query+unreadnotificationpages-description": "Get pages for which there are unread notifications for the current user.",
|
||||
"apihelp-query+unreadnotificationpages-param-limit": "The maximum number of pages to return.",
|
||||
"apihelp-query+unreadnotificationpages-param-wikis": "List of wikis to fetch pages with unread notifications from (defaults to only current wiki).",
|
||||
"apihelp-query+unreadnotificationpages-example-1": "List pages with (their amount of) unread notifications"
|
||||
}
|
||||
|
|
|
@ -250,5 +250,9 @@
|
|||
"apihelp-query+notifications-param-messagecontinue": "{{doc-apihelp-param|query+notifications|messagecontinue}}",
|
||||
"apihelp-query+notifications-param-messageunreadfirst": "{{doc-apihelp-param|query+notifications|messageunreadfirst}}",
|
||||
"apihelp-query+notifications-example-1": "{{doc-apihelp-example|query+notifications}}",
|
||||
"apihelp-query+notifications-example-2": "{{doc-apihelp-example|query+notifications}}"
|
||||
"apihelp-query+notifications-example-2": "{{doc-apihelp-example|query+notifications}}",
|
||||
"apihelp-query+unreadnotificationpages-description": "{{doc-apihelp-description|query+unreadnotificationpages}}",
|
||||
"apihelp-query+unreadnotificationpages-param-limit": "{{doc-apihelp-param|query+unreadnotificationpages|limit}}",
|
||||
"apihelp-query+unreadnotificationpages-param-wikis": "{{doc-apihelp-param|query+unreadnotificationpages|wikis}}",
|
||||
"apihelp-query+unreadnotificationpages-example-1": "{{doc-apihelp-example|query+unreadnotificationpages}}"
|
||||
}
|
||||
|
|
|
@ -27,7 +27,7 @@ abstract class ApiCrossWikiBase extends ApiQueryBase {
|
|||
* @throws Exception
|
||||
*/
|
||||
protected function getFromForeign() {
|
||||
$reqs = $this->getForeignRequestParams( $this->getForeignWikis() );
|
||||
$reqs = $this->getForeignRequestParams( $this->getRequestedForeignWikis() );
|
||||
|
||||
return $this->foreignRequests( $reqs );
|
||||
}
|
||||
|
@ -41,15 +41,47 @@ abstract class ApiCrossWikiBase extends ApiQueryBase {
|
|||
}
|
||||
|
||||
/**
|
||||
* @return array Wiki names
|
||||
* This is basically equivalent to $params['wikis'], but some added checks:
|
||||
* - `*` will expand to "all wikis with unread notifications"
|
||||
* - if `$wgEchoCrossWikiNotifications` is off, foreign wikis will be excluded
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function getForeignWikis() {
|
||||
if ( !$this->allowCrossWikiNotifications() ) {
|
||||
return array();
|
||||
protected function getRequestedWikis() {
|
||||
$params = $this->extractRequestParams();
|
||||
|
||||
// if wiki is omitted from params, that's because crosswiki is/was not
|
||||
// available, and it'll default to current wiki
|
||||
$wikis = isset( $params['wikis'] ) ? $params['wikis'] : array( wfWikiID() );
|
||||
|
||||
if ( array_search( '*', $wikis ) !== false ) {
|
||||
// expand `*` to all foreign wikis with unread notifications + local
|
||||
$wikis = array_merge(
|
||||
array( wfWikiID() ),
|
||||
$this->getForeignWikisWithUnreadNotifications()
|
||||
);
|
||||
}
|
||||
|
||||
$params = $this->extractRequestParams();
|
||||
return array_diff( $params['wikis'], array( wfWikiId() ) );
|
||||
if ( !$this->allowCrossWikiNotifications() ) {
|
||||
// exclude foreign wikis if x-wiki is not enabled
|
||||
$wikis = array_intersect_key( array( wfWikiID() ), $wikis );
|
||||
}
|
||||
|
||||
return $wikis;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array Wiki names
|
||||
*/
|
||||
protected function getRequestedForeignWikis() {
|
||||
return array_diff( $this->getRequestedWikis(), array( wfWikiId() ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array Wiki names
|
||||
*/
|
||||
protected function getForeignWikisWithUnreadNotifications() {
|
||||
return $this->foreignNotifications->getWikis();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -133,8 +165,9 @@ abstract class ApiCrossWikiBase extends ApiQueryBase {
|
|||
|
||||
if ( !isset( $results[$wiki] ) ) {
|
||||
LoggerFactory::getInstance( 'Echo' )->warning(
|
||||
'Failed to fetch notifications from {wiki}. Response: {code} {response}',
|
||||
'Failed to fetch {module} from {wiki}. Response: {code} {response}',
|
||||
array(
|
||||
'module' => $this->getModuleName(),
|
||||
'wiki' => $wiki,
|
||||
'code' => $response['response']['code'],
|
||||
'response' => $response['response']['body'],
|
||||
|
@ -160,7 +193,9 @@ abstract class ApiCrossWikiBase extends ApiQueryBase {
|
|||
'wikis' => array(
|
||||
ApiBase::PARAM_ISMULTI => true,
|
||||
ApiBase::PARAM_DFLT => wfWikiId(),
|
||||
ApiBase::PARAM_TYPE => array_unique( array_merge( $wgConf->wikis, array( wfWikiId() ) ) ),
|
||||
// `*` will let you immediately fetch from all wikis that have
|
||||
// unread notifications, without having to look them up first
|
||||
ApiBase::PARAM_TYPE => array_unique( array_merge( $wgConf->wikis, array( wfWikiId(), '*' ) ) ),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -39,11 +39,11 @@ class ApiEchoNotifications extends ApiCrossWikiBase {
|
|||
}
|
||||
|
||||
$results = array();
|
||||
if ( !$this->allowCrossWikiNotifications() || in_array( wfWikiId(), $params['wikis'] ) ) {
|
||||
if ( in_array( wfWikiId(), $this->getRequestedWikis() ) ) {
|
||||
$results[wfWikiId()] = $this->getLocalNotifications( $params );
|
||||
}
|
||||
|
||||
if ( $this->getForeignWikis() ) {
|
||||
if ( $this->getRequestedForeignWikis() ) {
|
||||
$foreignResults = $this->getFromForeign();
|
||||
foreach ( $foreignResults as $wiki => $result ) {
|
||||
if ( isset( $result['query']['notifications'] ) ) {
|
||||
|
|
139
includes/api/ApiEchoUnreadNotificationPages.php
Normal file
139
includes/api/ApiEchoUnreadNotificationPages.php
Normal file
|
@ -0,0 +1,139 @@
|
|||
<?php
|
||||
|
||||
class ApiEchoUnreadNotificationPages extends ApiCrossWikiBase {
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
protected $crossWikiSummary = false;
|
||||
|
||||
/**
|
||||
* @param ApiQuery $query
|
||||
* @param string $moduleName
|
||||
*/
|
||||
public function __construct( $query, $moduleName ) {
|
||||
parent::__construct( $query, $moduleName, 'unp' );
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws UsageException
|
||||
*/
|
||||
public function execute() {
|
||||
// To avoid API warning, register the parameter used to bust browser cache
|
||||
$this->getMain()->getVal( '_' );
|
||||
|
||||
if ( $this->getUser()->isAnon() ) {
|
||||
$this->dieUsage( 'Login is required', 'login-required' );
|
||||
}
|
||||
|
||||
$params = $this->extractRequestParams();
|
||||
|
||||
$result = array();
|
||||
if ( in_array( wfWikiId(), $this->getRequestedWikis() ) ) {
|
||||
$result[wfWikiID()] = $this->getFromLocal( $params['limit'] );
|
||||
}
|
||||
|
||||
if ( $this->getRequestedForeignWikis() ) {
|
||||
$result += $this->getFromForeign();
|
||||
}
|
||||
|
||||
$apis = $this->foreignNotifications->getApiEndpoints( $this->getRequestedWikis() );
|
||||
foreach ( $result as $wiki => $data ) {
|
||||
$result[$wiki]['source'] = $apis[$wiki];
|
||||
// StdClass to ensure empty data is json_encoded to `{}` instead of `[]`
|
||||
$result[$wiki]['pages'] = $data['pages'] ?: new StdClass;
|
||||
}
|
||||
|
||||
$this->getResult()->addValue( 'query', $this->getModuleName(), $result );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $limit
|
||||
* @return array
|
||||
*/
|
||||
protected function getFromLocal( $limit ) {
|
||||
$dbr = MWEchoDbFactory::newFromDefault()->getEchoDb( DB_SLAVE );
|
||||
$rows = $dbr->select(
|
||||
array( 'echo_event', 'echo_notification' ),
|
||||
array( 'event_page_id', 'count' => 'COUNT(*)' ),
|
||||
array(
|
||||
'notification_user' => $this->getUser()->getId(),
|
||||
'notification_read_timestamp' => null,
|
||||
'event_page_id IS NOT NULL',
|
||||
),
|
||||
__METHOD__,
|
||||
array(
|
||||
'GROUP BY' => 'event_page_id',
|
||||
'ORDER BY' => 'count DESC',
|
||||
'LIMIT' => $limit,
|
||||
),
|
||||
array( 'echo_notification' => array( 'INNER JOIN', 'notification_event = event_id' ) )
|
||||
);
|
||||
|
||||
if ( $rows === false ) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$pages = array();
|
||||
foreach ( $rows as $row ) {
|
||||
$pages[$row->event_page_id] = $row->count;
|
||||
}
|
||||
|
||||
$result = array();
|
||||
$titles = Title::newFromIDs( array_keys( $pages ) );
|
||||
foreach ( $titles as $title ) {
|
||||
$result[$title->getArticleID()] = array(
|
||||
'title' => $title->getPrefixedText(),
|
||||
'count' => $pages[$title->getArticleID()],
|
||||
);
|
||||
}
|
||||
|
||||
return array( 'pages' => $result );
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
protected function getFromForeign() {
|
||||
$result = array();
|
||||
foreach ( parent::getFromForeign() as $wiki => $data ) {
|
||||
$result[$wiki] = $data['query'][$this->getModuleName()][$wiki];
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getAllowedParams() {
|
||||
global $wgEchoMaxUpdateCount;
|
||||
|
||||
return parent::getAllowedParams() + array(
|
||||
'limit' => array(
|
||||
ApiBase::PARAM_TYPE => 'limit',
|
||||
ApiBase::PARAM_DFLT => 20,
|
||||
ApiBase::PARAM_MIN => 1,
|
||||
ApiBase::PARAM_MAX => $wgEchoMaxUpdateCount,
|
||||
ApiBase::PARAM_MAX2 => $wgEchoMaxUpdateCount,
|
||||
),
|
||||
// there is no `offset` or `continue` value: the set of possible
|
||||
// notifications is small enough to allow fetching all of them at
|
||||
// once, and any sort of fetching would be unreliable because
|
||||
// they're sorted based on count of notifications, which could
|
||||
// change in between requests
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see ApiBase::getExamplesMessages()
|
||||
*/
|
||||
protected function getExamplesMessages() {
|
||||
return array(
|
||||
'action=query&meta=unreadnotificationpages' => 'apihelp-query+unreadnotificationpages-example-1',
|
||||
);
|
||||
}
|
||||
|
||||
public function getHelpUrls() {
|
||||
return 'https://www.mediawiki.org/wiki/Echo_(Notifications)/API';
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue