jenkins-bot has submitted this change and it was merged.
Change subject: Suggestions: Personalized recommendations
......................................................................
Suggestions: Personalized recommendations
Bug: T111028
Change-Id: I8370ae8d0d6ab39f3c46a1358acc2e63c46bf9c9
---
M extension.json
A modules/dashboard/ext.cx.recommendtool.client.js
M modules/dashboard/ext.cx.suggestionlist.js
3 files changed, 197 insertions(+), 31 deletions(-)
Approvals:
Nikerabbit: Looks good to me, approved
jenkins-bot: Verified
diff --git a/extension.json b/extension.json
index ceaf029..a14d8d1 100644
--- a/extension.json
+++ b/extension.json
@@ -826,6 +826,7 @@
],
"dependencies": [
"ext.cx.util",
+ "ext.cx.recommendtool.client",
"jquery.uls.data"
],
"messages": [
@@ -840,6 +841,11 @@
"cx-suggestionlist-title"
]
},
+ "ext.cx.recommendtool.client": {
+ "scripts": [
+ "dashboard/ext.cx.recommendtool.client.js"
+ ]
+ },
"ext.cx.translation.conflict": {
"scripts": [
"translation/ext.cx.translation.conflict.js"
diff --git a/modules/dashboard/ext.cx.recommendtool.client.js
b/modules/dashboard/ext.cx.recommendtool.client.js
new file mode 100644
index 0000000..460b918
--- /dev/null
+++ b/modules/dashboard/ext.cx.recommendtool.client.js
@@ -0,0 +1,125 @@
+/**
+ * Module to interface with article recommendation tool AND to get seeds for
the tool.
+ *
+ * The recommendation tool is also known as trex in this code.
+ *
+ * See http://recommend.wmflabs.org/ and
https://github.com/ewulczyn/translation-recs-app
+ *
+ * @file
+ * @ingroup Extensions
+ * @copyright See AUTHORS.txt
+ * @license GPL-2.0+
+ */
+( function ( $, mw ) {
+ 'use strict';
+
+ /**
+ * RecommendTool
+ *
+ * @class
+ */
+ function RecommendTool( from, to ) {
+ this.sourceLanguage = from;
+ this.targetLanguage = to;
+ this.algorithms = [ 'wiki', 'morelike' ];
+ this.seeds = null;
+ }
+
+ /**
+ * Get a seed page for the current language pair.
+ * The seed page is one of the previous translations (of any status) by
the user.
+ *
+ * @return {jQuery.Promise}
+ */
+ RecommendTool.prototype.getSeedPages = function () {
+ var params,
+ self = this,
+ api = new mw.Api();
+
+ if ( this.seeds ) {
+ // We have seeds. So provide first 3.
+ // If we ran out of seeds, we will return empty seeds
and that is ok.
+ return $.Deferred().resolve( this.seeds.splice( 0, 3 )
);
+ }
+
+ // Collect a bunch of seed pages from previous translations.
+ // This API is also used for fetching the translations of user
from translationlist module.
+ // Effectively, the following request is duplicate. But it does
not look like a good idea
+ // to have dependency on translation list for this module.
There are some complications as
+ // well with sharing data - we are not sure which module gets
initated first.
+ params = {
+ list: 'contenttranslation',
+ from: this.sourceLanguage,
+ to: this.targetLanguage,
+ limit: 50
+ };
+
+ return api.get( params ).then( function ( response ) {
+ self.seeds = $.map(
response.query.contenttranslation.translations, function ( value ) {
+ return value.translation.sourceTitle;
+ } );
+
+ return self.seeds.splice( 0, 3 );
+ } );
+ };
+
+ /**
+ * @return {jQuery.Promise}
+ */
+ RecommendTool.prototype.getSuggestionList = function () {
+ var self = this,
+ algorithm;
+
+ // Choose a random algorithm
+ algorithm = this.algorithms[ Math.floor( Math.random() *
this.algorithms.length ) ];
+ return this.getSeedPages().then( function ( seedPages ) {
+ return $.get( 'https://recommend.wmflabs.org/api', {
+ s: self.sourceLanguage,
+ t: self.targetLanguage,
+ article: seedPages.join( '|' ),
+ search: algorithm
+ } ).then( function ( response ) {
+ return self.adapt( response.articles, algorithm
);
+ } );
+ } );
+ };
+
+ /**
+ * Adapt the API response of Recommendation tool to the CX suggestion
list format.
+ *
+ * @param {Object} articles
+ * @param {string} algorithm The algorithm used.
+ * @return {Object}
+ */
+ RecommendTool.prototype.adapt = function ( articles, algorithm ) {
+ var i, title, suggestions = [];
+
+ for ( i = 0; i < articles.length; i++ ) {
+ title = mw.Title.newFromText( articles[ i ].title );
+ if ( !title ) {
+ continue;
+ }
+
+ suggestions.push( {
+ title: title.getPrefixedText(),
+ sourceLanguage: this.sourceLanguage,
+ targetLanguage: this.targetLanguage,
+ pageviews: articles[ i ].pageviews,
+ listId: 'trex'
+ } );
+ }
+
+ return {
+ lists: {
+ trex: {
+ name: 'cx-suggestionlist-trex',
+ type: 5,
+ algorithm: algorithm,
+ suggestions: suggestions
+ }
+ }
+ };
+ };
+
+ mw.cx.recommendtool = RecommendTool;
+}( jQuery, mediaWiki ) );
diff --git a/modules/dashboard/ext.cx.suggestionlist.js
b/modules/dashboard/ext.cx.suggestionlist.js
index c55db89..b66184f 100644
--- a/modules/dashboard/ext.cx.suggestionlist.js
+++ b/modules/dashboard/ext.cx.suggestionlist.js
@@ -17,7 +17,8 @@
TYPE_FEATURED: 1,
TYPE_DISCARDED: 2,
TYPE_FAVORITE: 3,
- TYPE_CATEGORY: 4
+ TYPE_CATEGORY: 4,
+ TYPE_PERSONALIZED: 5
};
/**
@@ -178,6 +179,14 @@
var params,
api = new mw.Api();
+ if ( list.id === 'trex' ) {
+ this.recommendtool = this.recommendtool || new
mw.cx.recommendtool(
+ this.filters.sourceLanguage,
+ this.filters.targetLanguage
+ );
+ return this.recommendtool.getSuggestionList();
+ }
+
if ( list.hasMore === false ) {
// This method is supposed to be called only if we
there are items to fetch
return $.Deferred().reject();
@@ -212,6 +221,9 @@
} );
if ( !Object.keys( this.lists ).length ) {
this.loadItems();
+ this.loadItems( {
+ id: 'trex'
+ } );
}
};
@@ -240,7 +252,11 @@
}
} );
this.lists = {};
+ this.recommendtool = null;
this.loadItems();
+ this.loadItems( {
+ id: 'trex'
+ } );
};
/**
@@ -336,7 +352,7 @@
.attr( 'data-listid', listId )
.addClass( 'cx-suggestionlist ' + list.name );
- if ( list.type !== listTypes.TYPE_FEATURED ) {
+ if ( list.type !== listTypes.TYPE_FEATURED && list.type
!== listTypes.TYPE_PERSONALIZED ) {
// No need to show heading for misc fallback
suggestions shown at the end.
$listHeading = $( '<h2>' ).text(
list.displayName );
list.$list.append( $listHeading );
@@ -366,8 +382,8 @@
if ( list.type === listTypes.TYPE_CATEGORY ) {
this.makeExpandableList( listId );
- } else if ( list.type === listTypes.TYPE_FEATURED ) {
- this.addRefreshTrigger( listId );
+ } else if ( list.type === listTypes.TYPE_FEATURED || list.type
=== listTypes.TYPE_PERSONALIZED ) {
+ this.addRefreshTrigger();
}
};
@@ -739,9 +755,10 @@
order = {};
order[ listTypes.TYPE_FAVORITE ] = 0;
order[ listTypes.TYPE_DISCARDED ] = 1;
- order[ listTypes.TYPE_CATEGORY ] = 2;
- order[ listTypes.TYPE_FEATURED ] = 3;
- order[ listTypes.TYPE_DEFAULT ] = 4;
+ order[ listTypes.TYPE_PERSONALIZED ] = 2;
+ order[ listTypes.TYPE_CATEGORY ] = 3;
+ order[ listTypes.TYPE_FEATURED ] = 4;
+ order[ listTypes.TYPE_DEFAULT ] = 5;
ids = Object.keys( lists ).sort( function ( a, b ) {
return order[ lists[ a ].type ] - order[ lists[ b
].type ];
@@ -787,10 +804,10 @@
$trigger = list.$list.find( '.cx-suggestionlist__expand,
.cx-suggestionlist__collapse' );
- if ( $trigger.is( '.cx-suggestionlist--collapsed' ) ) {
- $trigger.text( mw.msg( 'cx-suggestionlist-collapse' ) );
+ if ( list.$list.is( '.cx-suggestionlist--collapsed' ) ) {
// Collapse all expended lists.
this.$container.find( '.cx-suggestionlist__collapse'
).trigger( 'click' );
+ $trigger.text( mw.msg( 'cx-suggestionlist-collapse' ) );
} else {
$trigger.text( mw.msg( 'cx-suggestionlist-expand' ) );
}
@@ -801,40 +818,58 @@
/**
* Make the list refreshable
- *
- * @param {string} listId
*/
- CXSuggestionList.prototype.addRefreshTrigger = function ( listId ) {
- var i, suggestion,
- self = this,
- list = this.lists[ listId ];
+ CXSuggestionList.prototype.addRefreshTrigger = function () {
+ var self = this;
- if ( list.$list.find( '.cx-suggestionlist__refresh' ).length ) {
+ if ( this.$container.find( '.cx-suggestionlist__refresh'
).length ) {
return;
}
- list.$list.append( $( '<div>' )
+ this.$container.append( $( '<div>' )
.addClass( 'cx-suggestionlist__refresh' )
.text( mw.msg( 'cx-suggestionlist-refresh' ) )
.on( 'click', function () {
- if ( list.suggestions ) {
- for ( i = 0; i <
list.suggestions.length; i++ ) {
- suggestion = list.suggestions[
i ];
- suggestion.$element.hide();
+ // Hide all other lists, if any.
+ $.each( self.lists, function ( index, list ) {
+ if ( list.$list &&
+ ( list.type ===
listTypes.TYPE_FEATURED ||
+ list.type ===
listTypes.TYPE_PERSONALIZED
+ )
+ ) {
+ self.refreshList( list.id );
}
- }
- list.suggestions = [];
- // Do not run out of suggestions
- list.seed = parseInt( Math.random() * 10000, 10
);
- list.queryContinue = undefined;
- list.hasMore = true;
- // FIXME: Till the new items arrive, this list
will become empty.
- // May be we need to keep the height of
container and show a loading
- // indicator?
- self.loadItems( list );
+ } );
} )
);
};
+ /**
+ * @param {string} listId
+ */
+ CXSuggestionList.prototype.refreshList = function ( listId ) {
+ var i, suggestion, list = this.lists[ listId ];
+
+ if ( !list ) {
+ return;
+ }
+
+ if ( list.suggestions ) {
+ for ( i = 0; i < list.suggestions.length; i++ ) {
+ suggestion = list.suggestions[ i ];
+ suggestion.$element.hide();
+ }
+ }
+ list.suggestions = [];
+ // Do not run out of suggestions
+ list.seed = parseInt( Math.random() * 10000, 10 );
+ list.queryContinue = undefined;
+ list.hasMore = true;
+ // FIXME: Till the new items arrive, this list will become
empty.
+ // May be we need to keep the height of container and show a
loading
+ // indicator?
+ this.loadItems( list );
+ };
+
mw.cx.CXSuggestionList = CXSuggestionList;
}( jQuery, mediaWiki ) );
--
To view, visit https://gerrit.wikimedia.org/r/250884
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings
Gerrit-MessageType: merged
Gerrit-Change-Id: I8370ae8d0d6ab39f3c46a1358acc2e63c46bf9c9
Gerrit-PatchSet: 10
Gerrit-Project: mediawiki/extensions/ContentTranslation
Gerrit-Branch: master
Gerrit-Owner: Santhosh <[email protected]>
Gerrit-Reviewer: Nikerabbit <[email protected]>
Gerrit-Reviewer: Santhosh <[email protected]>
Gerrit-Reviewer: jenkins-bot <>
_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits