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

Reply via email to