jenkins-bot has submitted this change and it was merged.

Change subject: Unread pages API
......................................................................


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
---
M Echo.php
M autoload.php
M i18n/en.json
M i18n/qqq.json
M includes/api/ApiCrossWikiBase.php
M includes/api/ApiEchoNotifications.php
A includes/api/ApiEchoUnreadNotificationPages.php
7 files changed, 197 insertions(+), 13 deletions(-)

Approvals:
  Catrope: Looks good to me, approved
  jenkins-bot: Verified



diff --git a/Echo.php b/Echo.php
index a284adf..fdc8b22 100644
--- a/Echo.php
+++ b/Echo.php
@@ -57,6 +57,7 @@
 
 // API
 $wgAPIMetaModules['notifications'] = 'ApiEchoNotifications';
+$wgAPIMetaModules['unreadnotificationpages'] = 
'ApiEchoUnreadNotificationPages';
 $wgAPIModules['echomarkread'] = 'ApiEchoMarkRead';
 $wgAPIModules['echomarkseen'] = 'ApiEchoMarkSeen';
 
diff --git a/autoload.php b/autoload.php
index f778194..dc47330 100644
--- a/autoload.php
+++ b/autoload.php
@@ -10,6 +10,7 @@
        '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',
diff --git a/i18n/en.json b/i18n/en.json
index d74a4ae..8a98bc5 100644
--- a/i18n/en.json
+++ b/i18n/en.json
@@ -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"
 }
diff --git a/i18n/qqq.json b/i18n/qqq.json
index 51c540e..a26a3c5 100644
--- a/i18n/qqq.json
+++ b/i18n/qqq.json
@@ -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}}"
 }
diff --git a/includes/api/ApiCrossWikiBase.php 
b/includes/api/ApiCrossWikiBase.php
index d1f96c3..e1f7c10 100644
--- a/includes/api/ApiCrossWikiBase.php
+++ b/includes/api/ApiCrossWikiBase.php
@@ -27,7 +27,7 @@
         * @throws Exception
         */
        protected function getFromForeign() {
-               $reqs = $this->getForeignRequestParams( 
$this->getForeignWikis() );
+               $reqs = $this->getForeignRequestParams( 
$this->getRequestedForeignWikis() );
 
                return $this->foreignRequests( $reqs );
        }
@@ -41,15 +41,47 @@
        }
 
        /**
-        * @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 @@
 
                        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 @@
                                '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(), '*' ) ) ),
                                ),
                        );
                }
diff --git a/includes/api/ApiEchoNotifications.php 
b/includes/api/ApiEchoNotifications.php
index 7556a10..539830b 100644
--- a/includes/api/ApiEchoNotifications.php
+++ b/includes/api/ApiEchoNotifications.php
@@ -39,11 +39,11 @@
                }
 
                $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'] ) 
) {
diff --git a/includes/api/ApiEchoUnreadNotificationPages.php 
b/includes/api/ApiEchoUnreadNotificationPages.php
new file mode 100644
index 0000000..fd65b54
--- /dev/null
+++ b/includes/api/ApiEchoUnreadNotificationPages.php
@@ -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';
+       }
+}

-- 
To view, visit https://gerrit.wikimedia.org/r/291351
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings

Gerrit-MessageType: merged
Gerrit-Change-Id: I271ea7f7a6e010284739bfce02c4ec8a077148fc
Gerrit-PatchSet: 7
Gerrit-Project: mediawiki/extensions/Echo
Gerrit-Branch: master
Gerrit-Owner: Matthias Mullie <[email protected]>
Gerrit-Reviewer: Catrope <[email protected]>
Gerrit-Reviewer: Matthias Mullie <[email protected]>
Gerrit-Reviewer: Sbisson <[email protected]>
Gerrit-Reviewer: Siebrand <[email protected]>
Gerrit-Reviewer: jenkins-bot <>

_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to