Santhosh has uploaded a new change for review. https://gerrit.wikimedia.org/r/270256
Change subject: Show basic user stats in the translation dashboard ...................................................................... Show basic user stats in the translation dashboard Bug: T111776 Change-Id: I4b4edee1d2031417e51a42077c206d889373d29d --- M api/ApiQueryContentTranslationLanguageTrend.php A api/ApiQueryTranslatorStats.php M extension.json M i18n/api/en.json M i18n/api/qqq.json M i18n/en.json M i18n/qqq.json M includes/Translation.php M modules/dashboard/ext.cx.dashboard.js A modules/widgets/translator/ext.cx.translator.js A modules/widgets/translator/ext.cx.translator.less A modules/widgets/translator/images/user-small.png A modules/widgets/translator/images/user-small.svg 13 files changed, 250 insertions(+), 10 deletions(-) git pull ssh://gerrit.wikimedia.org:29418/mediawiki/extensions/ContentTranslation refs/changes/56/270256/1 diff --git a/api/ApiQueryContentTranslationLanguageTrend.php b/api/ApiQueryContentTranslationLanguageTrend.php index 96572a3..1fdae20 100644 --- a/api/ApiQueryContentTranslationLanguageTrend.php +++ b/api/ApiQueryContentTranslationLanguageTrend.php @@ -33,8 +33,10 @@ $interval = $params['interval']; $data = array( - 'translations' => Translation::getTrendByStatus( $source, $target, 'published', $interval ), - 'drafts' => Translation::getTrendByStatus( $source, $target, 'draft', $interval ), + 'translations' => Translation::getTrendByStatus( + $source, $target, 'published', $interval, null + ), + 'drafts' => Translation::getTrendByStatus( $source, $target, 'draft', $interval, null ), ); if ( $target !== null ) { diff --git a/api/ApiQueryTranslatorStats.php b/api/ApiQueryTranslatorStats.php new file mode 100644 index 0000000..e9c3fc6 --- /dev/null +++ b/api/ApiQueryTranslatorStats.php @@ -0,0 +1,64 @@ +<?php +/** + * Api module for querying translation statistics for a translator. + * + * @file + * @copyright See AUTHORS.txt + * @license GPL-2.0+ + */ + +use ContentTranslation\Translation; +use ContentTranslation\Translator; + +/** + * @ingroup API ContentTranslationAPI + */ +class ApiQueryTranslatorStats extends ApiQueryBase { + + public function __construct( $query, $moduleName ) { + parent::__construct( $query, $moduleName ); + } + + public function execute() { + $result = $this->getResult(); + $params = $this->extractRequestParams(); + $user = $this->getUser(); + if ( isset( $params['translator'] ) ) { + $user = \User::newFromName( $params['translator'] ); + } + $translator = new Translator( $user ); + $translatorId = $translator->getGlobalUserId(); + if ( !$translatorId ) { + $this->dieUsage( 'Invalid translator name', 'invalidtranslator' ); + } + $publishedStats = Translation::getTrendByStatus( + null, null, 'published', 'month', $translatorId + ); + // TODO: The $publishedStats does not contain data for all months, + // if there is not translation in that month. ApiQueryContentTranslationLanguageTrend + // has utility methods to fill it. But it is not important for the graph we render + // from the output of this data. + $result = array( + 'translator' => $user->getName(), + 'translatorId' => $translatorId, + 'publishTrend' => $publishedStats, + ); + $this->getResult()->addValue( null, $this->getModuleName(), $result ); + } + + public function getAllowedParams() { + $allowedParams = array( + 'translator' => array( + ApiBase::PARAM_TYPE => 'string', + ) + ); + return $allowedParams; + } + + protected function getExamplesMessages() { + return array( + 'action=query&list=cxtranslatorstats&translator=TranslatorName' => + 'apihelp-query+cxtranslatorstats-example-1', + ); + } +} diff --git a/extension.json b/extension.json index 8a15cd1..c7804b0 100644 --- a/extension.json +++ b/extension.json @@ -47,7 +47,8 @@ "contenttranslationlangtrend": "ApiQueryContentTranslationLanguageTrend", "contenttranslationstats": "ApiQueryContentTranslationStats", "contenttranslationsuggestions": "ApiQueryContentTranslationSuggestions", - "cxpublishedtranslations": "ApiQueryPublishedTranslations" + "cxpublishedtranslations": "ApiQueryPublishedTranslations", + "cxtranslatorstats": "ApiQueryTranslatorStats" }, "MessagesDirs": { "ContentTranslation": "i18n", @@ -70,6 +71,7 @@ "ApiQueryContentTranslationLanguageTrend": "api/ApiQueryContentTranslationLanguageTrend.php", "ApiQueryContentTranslationStats": "api/ApiQueryContentTranslationStats.php", "ApiQueryPublishedTranslations": "api/ApiQueryPublishedTranslations.php", + "ApiQueryTranslatorStats": "api/ApiQueryTranslatorStats.php", "ContentTranslationHooks": "ContentTranslation.hooks.php", "ContentTranslation\\AbuseFilterCheck": "includes/AbuseFilterCheck.php", "ContentTranslation\\CorporaLookup": "includes/CorporaLookup.php", @@ -265,6 +267,7 @@ "ext.cx.source.selector", "ext.cx.suggestionlist", "ext.cx.translationlist", + "ext.cx.widgets.translator", "ext.uls.mediawiki", "mediawiki.Uri", "mediawiki.storage", @@ -1063,6 +1066,22 @@ "styles": [ "widgets/callout/ext.cx.callout.css" ] + }, + "ext.cx.widgets.translator": { + "scripts": [ + "widgets/translator/ext.cx.translator.js" + ], + "styles": [ + "widgets/translator/ext.cx.translator.less" + ], + "messages":[ + "cx-translator-month-stats-label", + "cx-translator-total-translations-label" + ], + "dependencies": [ + "chart.js", + "mediawiki.api" + ] } }, "ResourceFileModulePaths": { diff --git a/i18n/api/en.json b/i18n/api/en.json index c93edc6..6518875 100644 --- a/i18n/api/en.json +++ b/i18n/api/en.json @@ -82,5 +82,8 @@ "apihelp-cxsave-description": "This module allows to save draft translations by section to save bandwidth and to collect parallel corpora.", "apihelp-cxsave-example-1": "Save the source and translation sections. The content must be JSON-encoded string.", "apihelp-cxsave-param-translationid": "The translation ID", - "apihelp-cxsave-param-content": "JSON-encoded section data. Each section is an object and has the following keys: content, sectionId, sequenceId, sequenceId, origin" + "apihelp-cxsave-param-content": "JSON-encoded section data. Each section is an object and has the following keys: content, sectionId, sequenceId, sequenceId, origin", + "apihelp-cxtranslatorstats-description": "Fetch the translation statistics for the given user.", + "apihelp-cxtranslatorstats-param-translator": "The translators user name. This param is optional. If not possed, the current logged-in user will be used.", + "apihelp-cxtranslatorstats-example-1": "Fetch the translation statistics for the given user.", } diff --git a/i18n/api/qqq.json b/i18n/api/qqq.json index e47dfbf..684cf7e 100644 --- a/i18n/api/qqq.json +++ b/i18n/api/qqq.json @@ -78,5 +78,8 @@ "apihelp-cxsave-description": "{{doc-apihelp-description|cxsave}}\n\nFor an explanation of \"parallel corpora\" see [[:w:en:Parallel text|Parallel text]].", "apihelp-cxsave-example-1": "{{doc-apihelp-example|cxsave}}", "apihelp-cxsave-param-translationid": "{{doc-apihelp-param|cxsave|translationId}}", - "apihelp-cxsave-param-content": "{{doc-apihelp-param|cxsave|content}}" + "apihelp-cxsave-param-content": "{{doc-apihelp-param|cxsave|content}}", + "apihelp-cxtranslatorstats-description": "{{doc-apihelp-description|cxtranslatorstats}}", + "apihelp-cxtranslatorstats-param-translator": "{{doc-apihelp-param|cxtranslatorstats|translator}}", + "apihelp-cxtranslatorstats-example-1": "{{doc-apihelp-example|query+cxtranslatorstats}}" } diff --git a/i18n/en.json b/i18n/en.json index 222ec27..89d68c6 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -206,5 +206,7 @@ "cx-suggestionlist-collapse": "View less", "cx-suggestionlist-refresh": "Refresh suggestions", "cx-notification-suggestions-available": "Based on {{GENDER:$2|your}} previous translation of '''$1''', new suggestions are now available.", - "cx-notification-suggestions-available-email-subject": "Translation suggestions available" + "cx-notification-suggestions-available-email-subject": "Translation suggestions available", + "cx-translator-month-stats-label": "This month", + "cx-translator-total-translations-label": "Total" } diff --git a/i18n/qqq.json b/i18n/qqq.json index 0261b5f..2531e2f 100644 --- a/i18n/qqq.json +++ b/i18n/qqq.json @@ -212,5 +212,7 @@ "cx-suggestionlist-collapse": "Label for the suggestions list collapse trigger", "cx-suggestionlist-refresh": "Label for the suggestions list refresh trigger", "cx-notification-suggestions-available": "Echo notification to inform the translator about the availability of personalized suggestions. \n\nParameters:\n* $1 - Source title of last translated page. \n* $2 - User name of current user.", - "cx-notification-suggestions-available-email-subject": "Email subject to inform the translator about the availability of personalized suggestions" + "cx-notification-suggestions-available-email-subject": "Email subject to inform the translator about the availability of personalized suggestions", + "cx-translator-month-stats-label": "Label displayed in the translation statistics of current user.", + "cx-translator-total-translations-label": "Label displayed in the translation statistics of current user." } diff --git a/includes/Translation.php b/includes/Translation.php index 66db0cc..c1af385 100644 --- a/includes/Translation.php +++ b/includes/Translation.php @@ -292,7 +292,9 @@ * @param string $interval 'weekly' or 'monthly' trend * @return array */ - public static function getTrendByStatus( $source, $target, $status, $interval ) { + public static function getTrendByStatus( + $source, $target, $status, $interval, $translatorId + ) { $dbr = Database::getConnection( DB_SLAVE ); $conditions = array(); @@ -320,6 +322,9 @@ if ( $target !== null ) { $conditions['translation_target_language'] = $target; } + if ( $translatorId !== null ) { + $conditions['translation_last_update_by'] = $translatorId; + } $options = null; if ( $interval === 'week' ) { $options = array( diff --git a/modules/dashboard/ext.cx.dashboard.js b/modules/dashboard/ext.cx.dashboard.js index 2f83c7b..1ac0e2e 100644 --- a/modules/dashboard/ext.cx.dashboard.js +++ b/modules/dashboard/ext.cx.dashboard.js @@ -128,12 +128,13 @@ }; CXDashboard.prototype.buildSidebar = function () { - var $header, i, items, $links = []; + var $header, $translator, i, items, $links = []; $header = $( '<div>' ) .addClass( 'cx-sidebar__title' ) .text( mw.msg( 'cx-dashboard-sidebar-title' ) ); + $translator = mw.cx.widgets.CXTranslator(); items = this.getSidebarItems(); $links = $( '<ul>' ); for ( i = 0; i < items.length; i++ ) { @@ -152,7 +153,7 @@ return $( '<div>' ) .addClass( 'cx-sidebar' ) - .append( $header, $links ); + .append( $translator, $header, $links ); }; CXDashboard.prototype.render = function () { diff --git a/modules/widgets/translator/ext.cx.translator.js b/modules/widgets/translator/ext.cx.translator.js new file mode 100644 index 0000000..1366007 --- /dev/null +++ b/modules/widgets/translator/ext.cx.translator.js @@ -0,0 +1,91 @@ +/*! + * ContentTranslation extension - Translator Widget. + * + * @ingroup Extensions + * @copyright See AUTHORS.txt + * @license GPL-2.0+ + */ +( function ( $, mw ) { + 'use strict'; + var statsRequest; + + mw.cx.widgets = mw.cx.widgets || {}; + + function drawChart( ctx, stats ) { + var cxTrendChart, data; + data = { + labels: Object.keys( stats.cxtranslatorstats.publishTrend ), + datasets: [ + { + strokeColor: '#347BFF', + fillColor: '#347BFF', + data: $.map( stats.cxtranslatorstats.publishTrend, function ( data ) { + return data.delta; + } ) + } + ] + }; + /*global Chart:false */ + cxTrendChart = new Chart( ctx ).Bar( data, { + responsive: true, + barDatasetSpacing: 1, + showScale: false, + showTooltips: false + } ); + } + + mw.cx.widgets.CXTranslator = function ( translatorName ) { + var $widget, $header, $monthStats, $total, $trend, + api = new mw.Api(); + + $header = $( '<div>' ).addClass( 'cx-translator__header' ); + $monthStats = $( '<div>' ).addClass( 'cx-translator__month-stats' ).append( + $( '<div>' ).addClass( 'cx-translator__month-stats-count' ), + $( '<div>' ) + .addClass( 'cx-translator__month-stats-label' ) + .text( mw.msg( 'cx-translator-month-stats-label' ) ) + ); + $total = $( '<div>' ).addClass( 'cx-translator__total-translations' ).append( + $( '<div>' ).addClass( 'cx-translator__total-translations-count' ), + $( '<div>' ) + .addClass( 'cx-translator__total-translations-label' ) + .text( mw.msg( 'cx-translator-total-translations-label' ) ) + ); + $trend = $( '<canvas>' ).attr( { + id: 'cxtranslatorstats', + width: '1000%', // Tricks Chart.js to scale down the graph 10 times + height: '100px' + } ); + statsRequest = statsRequest || api.get( { + action: 'query', + list: 'cxtranslatorstats', + translator: translatorName + } ); + $widget = $( '<div>' ) + .addClass( 'cx-translator' ) + .append( $header, $monthStats, $total, $trend ); + statsRequest.then( function ( stats ) { + var total, monthCount, ctx, + thisMonthKey = new Date().toISOString().slice( 0, 7 ); + + if ( !stats.cxtranslatorstats.publishTrend[ thisMonthKey ] ) { + $widget.remove(); + return; + } + total = stats.cxtranslatorstats.publishTrend[ thisMonthKey ].count; + monthCount = stats.cxtranslatorstats.publishTrend[ thisMonthKey ].delta; + + $header.text( stats.cxtranslatorstats.translator ); + $total.find( '.cx-translator__total-translations-count' ).text( total ); + $monthStats.find( '.cx-translator__month-stats-count' ) + .text( stats.cxtranslatorstats.publishTrend[ thisMonthKey ].delta ); + + ctx = $trend[ 0 ].getContext( '2d' ); + drawChart( ctx, stats ); + } ).fail( function () { + $widget.remove(); + } ); + + return $widget; + }; +}( jQuery, mediaWiki ) ); diff --git a/modules/widgets/translator/ext.cx.translator.less b/modules/widgets/translator/ext.cx.translator.less new file mode 100644 index 0000000..4271ac3 --- /dev/null +++ b/modules/widgets/translator/ext.cx.translator.less @@ -0,0 +1,41 @@ +@import "../../widgets/common/ext.cx.common"; +@import "mediawiki.mixins"; + +.cx-translator { + .mw-ui-item; + .mw-ui-one-whole; + padding-left: 40px; + margin-bottom: 10px; + .background-image-svg('images/user-small.svg', 'images/user-small.png'); + background-size: 25px; + background-repeat: no-repeat; + background-position: top left; +} + +.cx-translator__header { + font-size: larger; + color: @gray-dark; + font-size: 1.5em; +} + +.cx-translator__month-stats, +.cx-translator__total-translations { + .mw-ui-one-half; + color: #565656; +} + +.cx-translator__total-translations-count, +.cx-translator__month-stats-count { + font-size: 2em; +} + +.cx-translator__total-translations-label, +.cx-translator__month-stats-label { + font-size: 1em; +} + +#cxtranslatorstats { + .mw-ui-one-whole; + max-height: 50px; + background-color: #f0f0f0; +} diff --git a/modules/widgets/translator/images/user-small.png b/modules/widgets/translator/images/user-small.png new file mode 100644 index 0000000..32e8914 --- /dev/null +++ b/modules/widgets/translator/images/user-small.png Binary files differ diff --git a/modules/widgets/translator/images/user-small.svg b/modules/widgets/translator/images/user-small.svg new file mode 100644 index 0000000..a6d332b --- /dev/null +++ b/modules/widgets/translator/images/user-small.svg @@ -0,0 +1,7 @@ +<?xml version="1.0" encoding="UTF-8"?> +<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="15px" height="15px" viewBox="0 0 15 15" enable-background="new 0 0 15 15" xml:space="preserve"> +<g> + <path fill="#777" d="M12.281,4.776c0,2.639-2.142,4.783-4.78,4.783c-2.64,0-4.779-2.144-4.779-4.783 C2.722,2.138,4.861,0,7.501,0C10.14,0,12.281,2.138,12.281,4.776z"/> + <path fill="#777" d="M13.36,8.609h-0.947c-1.031,1.625-2.844,2.707-4.912,2.707c-2.068,0-3.883-1.082-4.914-2.707H1.645 c-0.445,0-0.799,0.358-0.799,0.801v4.795C0.846,14.641,1.199,15,1.645,15H13.36c0.44,0,0.794-0.359,0.794-0.795V9.41 C14.154,8.968,13.801,8.609,13.36,8.609z"/> +</g> +</svg> -- To view, visit https://gerrit.wikimedia.org/r/270256 To unsubscribe, visit https://gerrit.wikimedia.org/r/settings Gerrit-MessageType: newchange Gerrit-Change-Id: I4b4edee1d2031417e51a42077c206d889373d29d Gerrit-PatchSet: 1 Gerrit-Project: mediawiki/extensions/ContentTranslation Gerrit-Branch: master Gerrit-Owner: Santhosh <santhosh.thottin...@gmail.com> _______________________________________________ MediaWiki-commits mailing list MediaWiki-commits@lists.wikimedia.org https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits