Matthias Mullie has uploaded a new change for review.
https://gerrit.wikimedia.org/r/291351
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, 191 insertions(+), 13 deletions(-)
git pull ssh://gerrit.wikimedia.org:29418/mediawiki/extensions/Echo
refs/changes/51/291351/1
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..68bcd0b 100644
--- a/i18n/en.json
+++ b/i18n/en.json
@@ -259,5 +259,6 @@
"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-example-1": "List pages with
(their amount of) unread notifications"
}
diff --git a/i18n/qqq.json b/i18n/qqq.json
index 51c540e..e82ed71 100644
--- a/i18n/qqq.json
+++ b/i18n/qqq.json
@@ -250,5 +250,6 @@
"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-example-1":
"{{doc-apihelp-example|query+unreadnotificationpages}}"
}
diff --git a/includes/api/ApiCrossWikiBase.php
b/includes/api/ApiCrossWikiBase.php
index d1f96c3..fd39005 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,44 @@
}
/**
- * @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();
+ $wikis = $params['wikis'];
+
+ 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 +162,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 +190,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..a884c48
--- /dev/null
+++ b/includes/api/ApiEchoUnreadNotificationPages.php
@@ -0,0 +1,142 @@
+<?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',
+ // don't need this condition, but it'll let us
use index
+ // echo_notification_user_base_read_timestamp
+ 'noticication_bundle_base' => array( 0, 1 ),
+ ),
+ __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 reliable
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: newchange
Gerrit-Change-Id: I271ea7f7a6e010284739bfce02c4ec8a077148fc
Gerrit-PatchSet: 1
Gerrit-Project: mediawiki/extensions/Echo
Gerrit-Branch: master
Gerrit-Owner: Matthias Mullie <[email protected]>
_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits