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

Change subject: Support wikitabular graph protocol
......................................................................


Support wikitabular graph protocol

Bug: T149713
Depends-On: I6b5f189690b52fc3b523a4087ba8d1e48755a879
Change-Id: I6eb35ea739f5827a14f85758748c313701734880
---
M lib/graph2.compiled.js
M modules/graph2.js
2 files changed, 239 insertions(+), 180 deletions(-)

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



diff --git a/lib/graph2.compiled.js b/lib/graph2.compiled.js
index e7abd5e..bd95538 100644
--- a/lib/graph2.compiled.js
+++ b/lib/graph2.compiled.js
@@ -1,107 +1,4 @@
 (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof 
require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var 
f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var 
l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return 
s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof 
require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return 
s})({1:[function(require,module,exports){
-( function ( $, mw, vg ) {
-
-       'use strict';
-       /* global require */
-
-       var wrapper,
-               VegaWrapper = require( 'graph-shared' );
-
-       wrapper = new VegaWrapper(
-               vg.util, true,
-               mw.config.get( 'wgGraphIsTrusted' ),
-               mw.config.get( 'wgGraphAllowedDomains' ),
-               false,
-               function ( warning ) {
-                       mw.log.warn( warning );
-               }, function ( opt ) {
-                       // Parse URL
-                       var uri = new mw.Uri( opt.url );
-                       // reduce confusion, only keep expected values
-                       if ( uri.port ) {
-                               uri.host += ':' + uri.port;
-                               delete uri.port;
-                       }
-                       // If url begins with   protocol:///...  mark it as 
having relative host
-                       if ( /^[a-z]+:\/\/\//.test( opt.url ) ) {
-                               uri.isRelativeHost = true;
-                       }
-                       if ( uri.protocol ) {
-                               // All other libs use trailing colon in the 
protocol field
-                               uri.protocol += ':';
-                       }
-                       // Node's path includes the query, whereas pathname is 
without the query
-                       // Standardizing on pathname
-                       uri.pathname = uri.path;
-                       delete uri.path;
-                       return uri;
-               }, function ( uri, opt ) {
-                       // Format URL back into a string
-                       // Revert path into pathname
-                       uri.path = uri.pathname;
-                       delete uri.pathname;
-
-                       if ( location.host.toLowerCase() === 
uri.host.toLowerCase() ) {
-                               if ( !mw.config.get( 'wgGraphIsTrusted' ) ) {
-                                       // Only send this header when hostname 
is the same.
-                                       // This is broader than the same-origin 
policy,
-                                       // but playing on the safer side.
-                                       opt.headers = { 'Treat-as-Untrusted': 1 
};
-                               }
-                       } else if ( opt.addCorsOrigin ) {
-                               // All CORS api calls require origin parameter.
-                               // It would be better to use location.origin,
-                               // but apparently it's not universal yet.
-                               uri.query.origin = location.protocol + '//' + 
location.host;
-                       }
-
-                       if ( uri.protocol[ uri.protocol.length - 1 ] === ':' ) {
-                               uri.protocol = uri.protocol.substring( 0, 
uri.protocol.length - 1 );
-                       }
-
-                       return uri.toString();
-               } );
-
-       /**
-        * Set up drawing canvas inside the given element and draw graph data
-        *
-        * @param {HTMLElement} element
-        * @param {Object|string} data graph spec
-        * @param {Function} [callback] function(error) called when drawing is 
done
-        */
-       mw.drawVegaGraph = function ( element, data, callback ) {
-               vg.parse.spec( data, function ( error, chart ) {
-                       if ( !error ) {
-                               chart( { el: element } ).update();
-                       }
-                       if ( callback ) {
-                               callback( error );
-                       }
-               } );
-       };
-
-       mw.hook( 'wikipage.content' ).add( function ( $content ) {
-               var specs = mw.config.get( 'wgGraphSpecs' );
-               if ( !specs ) {
-                       return;
-               }
-               $content.find( '.mw-graph.mw-graph-always' ).each( function () {
-                       var graphId = $( this ).data( 'graph-id' );
-                       if ( !specs.hasOwnProperty( graphId ) ) {
-                               mw.log.warn( graphId );
-                       } else {
-                               mw.drawVegaGraph( this, specs[ graphId ], 
function ( error ) {
-                                       if ( error ) {
-                                               mw.log.warn( error );
-                                       }
-                               } );
-                       }
-               } );
-       } );
-
-}( jQuery, mediaWiki, vg ) );
-
-},{"graph-shared":4}],2:[function(require,module,exports){
 'use strict';
 
 /**
@@ -128,7 +25,7 @@
             .join('|') + ')$', 'i');
 };
 
-},{}],3:[function(require,module,exports){
+},{}],2:[function(require,module,exports){
 'use strict';
 /* global module */
 
@@ -195,7 +92,7 @@
 }
 
 
-},{}],4:[function(require,module,exports){
+},{}],3:[function(require,module,exports){
 'use strict';
 /* global module */
 
@@ -203,34 +100,43 @@
     parseWikidataValue = require('wd-type-parser');
 
 module.exports = VegaWrapper;
+module.exports.removeColon = removeColon;
+
+/**
+ * Utility function to remove trailing colon from a protocol
+ * @param {string} protocol
+ * @return {string}
+ */
+function removeColon(protocol) {
+    return protocol && protocol.length && protocol[protocol.length - 1] === ':'
+        ? protocol.substring(0, protocol.length - 1) : protocol;
+}
 
 /**
  * Shared library to wrap around vega code
- * @param {Object} datalib Vega's datalib object
- * @param {Object} datalib.load Vega's data loader
- * @param {Function} datalib.load.loader Vega's data loader function
- * @param {Function} datalib.extend similar to jquery's extend()
- * @param {boolean} useXhr true if we should use XHR, false for node.js http 
loading
- * @param {boolean} isTrusted true if the graph spec can be trusted
- * @param {Object} domains allowed protocols and a list of their domains
- * @param {Object} domainMap domain remapping
- * @param {Function} logger
- * @param {Function} parseUrl
- * @param {Function} formatUrl
+ * @param {Object} wrapperOpts Configuration options
+ * @param {Object} wrapperOpts.datalib Vega's datalib object
+ * @param {Object} wrapperOpts.datalib.load Vega's data loader
+ * @param {Function} wrapperOpts.datalib.load.loader Vega's data loader 
function
+ * @param {Function} wrapperOpts.datalib.extend similar to jquery's extend()
+ * @param {boolean} wrapperOpts.useXhr true if we should use XHR, false for 
node.js http loading
+ * @param {boolean} wrapperOpts.isTrusted true if the graph spec can be trusted
+ * @param {Object} wrapperOpts.domains allowed protocols and a list of their 
domains
+ * @param {Object} wrapperOpts.domainMap domain remapping
+ * @param {Function} wrapperOpts.logger
+ * @param {Function} wrapperOpts.parseUrl
+ * @param {Function} wrapperOpts.formatUrl
+ * @param {string} [wrapperOpts.languageCode]
  * @constructor
  */
-function VegaWrapper(datalib, useXhr, isTrusted, domains, domainMap, logger, 
parseUrl, formatUrl) {
+function VegaWrapper(wrapperOpts) {
     var self = this;
-    self.isTrusted = isTrusted;
-    self.domains = domains;
-    self.domainMap = domainMap;
-    self.logger = logger;
-    self.objExtender = datalib.extend;
-    self.parseUrl = parseUrl;
-    self.formatUrl = formatUrl;
+    // Copy all options into this object
+    self.objExtender = wrapperOpts.datalib.extend;
+    self.objExtender(self, wrapperOpts);
     self.validators = {};
 
-    datalib.load.loader = function (opt, callback) {
+    self.datalib.load.loader = function (opt, callback) {
         var error = callback || function (e) { throw e; }, url;
 
         try {
@@ -245,21 +151,21 @@
             return self.dataParser(error, data, opt, callback);
         };
 
-        if (useXhr) {
-            return datalib.load.xhr(url, opt, cb);
+        if (self.useXhr) {
+            return self.datalib.load.xhr(url, opt, cb);
         } else {
-            return datalib.load.http(url, opt, cb);
+            return self.datalib.load.http(url, opt, cb);
         }
     };
 
-    datalib.load.sanitizeUrl = self.sanitizeUrl.bind(self);
+    self.datalib.load.sanitizeUrl = self.sanitizeUrl.bind(self);
 
     // Prevent accidental use
-    datalib.load.file = alwaysFail;
-    if (useXhr) {
-        datalib.load.http = alwaysFail;
+    self.datalib.load.file = alwaysFail;
+    if (self.useXhr) {
+        self.datalib.load.http = alwaysFail;
     } else {
-        datalib.load.xhr = alwaysFail;
+        self.datalib.load.xhr = alwaysFail;
     }
 }
 
@@ -306,15 +212,10 @@
  * @private
  */
 VegaWrapper.prototype._getProtocolDomains = function 
_getProtocolDomains(protocol) {
-    return this.domains[protocol] || this.domains[this.removeColon(protocol)];
+    return this.domains[protocol] || this.domains[removeColon(protocol)];
 };
 
-VegaWrapper.prototype.removeColon = function removeColon(protocol) {
-    return protocol && protocol.length && protocol[protocol.length - 1] === ':'
-    ? protocol.substring(0, protocol.length - 1) : protocol;
-}
-
-/**this
+/**
  * Validate and update urlObj to be safe for client-side and server-side usage
  * @param {Object} opt passed by the vega loader, and will add 'graphProtocol' 
param
  * @returns {boolean} true on success
@@ -414,24 +315,39 @@
                 break;
 
             case 'wikiraw:':
+            case 'tabular:':
+            case 'tabularinfo:':
                 // wikiraw:///MyPage/data
-                // Get raw content of a wiki page, where the path is the title
+                // Get content of a wiki page, where the path is the title
                 // of the page with an additional leading '/' which gets 
removed.
                 // Uses mediawiki api, and extract the content after the 
request
                 // Query value must be a valid MediaWiki title string, but we 
only ensure
-                // there is no pipe symbol, the rest is handlered by the api.
+                // there is no pipe symbol, the rest is handled by the api.
                 decodedPathname = decodeURIComponent(urlParts.pathname);
                 if (!/^\/[^|]+$/.test(decodedPathname)) {
-                    throw new Error('wikiraw: invalid title');
+                    throw new Error(urlParts.protocol + ' invalid title');
                 }
-                urlParts.query = {
-                    format: 'json',
-                    formatversion: '2',
-                    action: 'query',
-                    prop: 'revisions',
-                    rvprop: 'content',
-                    titles: decodedPathname.substring(1)
-                };
+                if (urlParts.protocol === 'wikiraw:') {
+                    urlParts.query = {
+                        format: 'json',
+                        formatversion: '2',
+                        action: 'query',
+                        prop: 'revisions',
+                        rvprop: 'content',
+                        titles: decodedPathname.substring(1)
+                    };
+                } else {
+                    urlParts.query = {
+                        format: 'json',
+                        formatversion: '2',
+                        action: 'jsondata',
+                        title: decodedPathname.substring(1)
+                    };
+                    if (this.languageCode) {
+                        urlParts.query.uselang = this.languageCode;
+                    }
+                }
+
                 urlParts.pathname = '/w/api.php';
                 urlParts.protocol = sanitizedHost.protocol;
                 opt.addCorsOrigin = true;
@@ -479,7 +395,7 @@
                     throw new Error(opt.graphProtocol + ' missing ids or query 
parameter in: ' + opt.url);
                 }
                 // the query object is not modified
-                urlParts.pathname = '/' + this.removeColon(opt.graphProtocol);
+                urlParts.pathname = '/' + removeColon(opt.graphProtocol);
                 break;
 
             case 'mapsnapshot:':
@@ -562,29 +478,63 @@
 };
 
 /**
+ * Parses the response from MW Api, throwing an error or logging warnings
+ */
+VegaWrapper.prototype.parseMWApiResponse = function parseMWApiResponse(data) {
+    data = JSON.parse(data);
+    if (data.error) {
+        throw new Error('API error: ' + JSON.stringify(data.error));
+    }
+    if (data.warnings) {
+        this.logger('API warnings: ' + JSON.stringify(data.warnings));
+    }
+    return data;
+};
+
+/**
  * Performs post-processing of the data requested by the graph's spec, and 
throw on error
  */
 VegaWrapper.prototype.parseDataOrThrow = function parseDataOrThrow(data, opt) {
+    var result;
     switch (opt.graphProtocol) {
         case 'wikiapi:':
+            data = this.parseMWApiResponse(data);
+            break;
         case 'wikiraw:':
-            // This was an API call - check for errors
-            data = JSON.parse(data);
-            if (data.error) {
-                throw new Error('API error: ' + JSON.stringify(data.error));
-            }
-            if (data.warnings) {
-                this.logger('API warnings: ' + JSON.stringify(data.warnings));
-            }
-            if (opt.graphProtocol === 'wikiraw:') {
-                try {
-                    data = data.query.pages[0].revisions[0].content;
-                } catch (e) {
-                    throw new Error('Page content not available ' + opt.url);
-                }
+            data = this.parseMWApiResponse(data);
+            try {
+                data = data.query.pages[0].revisions[0].content;
+            } catch (e) {
+                throw new Error('Page content not available ' + opt.url);
             }
             break;
-
+        case 'tabular:':
+            data = this.parseMWApiResponse(data).jsondata;
+            result = [];
+            data.rows.forEach(function(v) {
+                var row = {};
+                for (var i = 0; i < data.headers.length; i++) {
+                    row[data.headers[i]] = v[i];
+                }
+                result.push(row);
+            });
+            data = result;
+            break;
+        case 'tabularinfo:':
+            data = this.parseMWApiResponse(data).jsondata;
+            result = {
+                license: data.license,
+                info: data.info,
+                types: {},
+                titles: {},
+                count: data.rows ? data.rows.length : 0
+            };
+            for (var i = 0; i < data.headers.length; i++) {
+                result.types[data.headers[i]] = data.types[i];
+                result.titles[data.headers[i]] = data.titles[i];
+            }
+            data = result;
+            break;
         case 'wikidatasparql:':
             data = JSON.parse(data);
             if (!data.results || !Array.isArray(data.results.bindings)) {
@@ -612,4 +562,110 @@
     throw new Error('Disabled');
 }
 
-},{"domain-validator":2,"wd-type-parser":3}]},{},[1]);
+},{"domain-validator":1,"wd-type-parser":2}],4:[function(require,module,exports){
+( function ( $, mw, vg ) {
+
+       'use strict';
+       /* global require */
+
+       var wrapper,
+               VegaWrapper = require( 'graph-shared' );
+
+       wrapper = new VegaWrapper( {
+               datalib: vg.util,
+               useXhr: true,
+               isTrusted: mw.config.get( 'wgGraphIsTrusted' ),
+               domains: mw.config.get( 'wgGraphAllowedDomains' ),
+               domainMap: false,
+               logger: function ( warning ) {
+                       mw.log.warn( warning );
+               },
+               parseUrl: function ( opt ) {
+                       // Parse URL
+                       var uri = new mw.Uri( opt.url );
+                       // reduce confusion, only keep expected values
+                       if ( uri.port ) {
+                               uri.host += ':' + uri.port;
+                               delete uri.port;
+                       }
+                       // If url begins with   protocol:///...  mark it as 
having relative host
+                       if ( /^[a-z]+:\/\/\//.test( opt.url ) ) {
+                               uri.isRelativeHost = true;
+                       }
+                       if ( uri.protocol ) {
+                               // All other libs use trailing colon in the 
protocol field
+                               uri.protocol += ':';
+                       }
+                       // Node's path includes the query, whereas pathname is 
without the query
+                       // Standardizing on pathname
+                       uri.pathname = uri.path;
+                       delete uri.path;
+                       return uri;
+               },
+               formatUrl: function ( uri, opt ) {
+                       // Format URL back into a string
+                       // Revert path into pathname
+                       uri.path = uri.pathname;
+                       delete uri.pathname;
+
+                       if ( location.host.toLowerCase() === 
uri.host.toLowerCase() ) {
+                               if ( !mw.config.get( 'wgGraphIsTrusted' ) ) {
+                                       // Only send this header when hostname 
is the same.
+                                       // This is broader than the same-origin 
policy,
+                                       // but playing on the safer side.
+                                       opt.headers = { 'Treat-as-Untrusted': 1 
};
+                               }
+                       } else if ( opt.addCorsOrigin ) {
+                               // All CORS api calls require origin parameter.
+                               // It would be better to use location.origin,
+                               // but apparently it's not universal yet.
+                               uri.query.origin = location.protocol + '//' + 
location.host;
+                       }
+
+                       uri.protocol = VegaWrapper.removeColon( uri.protocol );
+
+                       return uri.toString();
+               },
+               languageCode: mw.config.get( 'wgUserLanguage' )
+       } );
+
+       /**
+        * Set up drawing canvas inside the given element and draw graph data
+        *
+        * @param {HTMLElement} element
+        * @param {Object|string} data graph spec
+        * @param {Function} [callback] function(error) called when drawing is 
done
+        */
+       mw.drawVegaGraph = function ( element, data, callback ) {
+               vg.parse.spec( data, function ( error, chart ) {
+                       if ( !error ) {
+                               chart( { el: element } ).update();
+                       }
+                       if ( callback ) {
+                               callback( error );
+                       }
+               } );
+       };
+
+       mw.hook( 'wikipage.content' ).add( function ( $content ) {
+               var specs = mw.config.get( 'wgGraphSpecs' );
+               if ( !specs ) {
+                       return;
+               }
+               $content.find( '.mw-graph.mw-graph-always' ).each( function () {
+                       var graphId = $( this ).data( 'graph-id' );
+                       if ( !specs.hasOwnProperty( graphId ) ) {
+                               mw.log.warn( graphId );
+                       } else {
+                               mw.drawVegaGraph( this, specs[ graphId ], 
function ( error ) {
+                                       if ( error ) {
+                                               mw.log.warn( error );
+                                       }
+                               } );
+                       }
+               } );
+       } );
+
+}( jQuery, mediaWiki, vg ) );
+
+},{"graph-shared":3}]},{},[4]);
diff --git a/modules/graph2.js b/modules/graph2.js
index 3a81d11..ea72eb5 100644
--- a/modules/graph2.js
+++ b/modules/graph2.js
@@ -6,14 +6,16 @@
        var wrapper,
                VegaWrapper = require( 'graph-shared' );
 
-       wrapper = new VegaWrapper(
-               vg.util, true,
-               mw.config.get( 'wgGraphIsTrusted' ),
-               mw.config.get( 'wgGraphAllowedDomains' ),
-               false,
-               function ( warning ) {
+       wrapper = new VegaWrapper( {
+               datalib: vg.util,
+               useXhr: true,
+               isTrusted: mw.config.get( 'wgGraphIsTrusted' ),
+               domains: mw.config.get( 'wgGraphAllowedDomains' ),
+               domainMap: false,
+               logger: function ( warning ) {
                        mw.log.warn( warning );
-               }, function ( opt ) {
+               },
+               parseUrl: function ( opt ) {
                        // Parse URL
                        var uri = new mw.Uri( opt.url );
                        // reduce confusion, only keep expected values
@@ -34,7 +36,8 @@
                        uri.pathname = uri.path;
                        delete uri.path;
                        return uri;
-               }, function ( uri, opt ) {
+               },
+               formatUrl: function ( uri, opt ) {
                        // Format URL back into a string
                        // Revert path into pathname
                        uri.path = uri.pathname;
@@ -54,12 +57,12 @@
                                uri.query.origin = location.protocol + '//' + 
location.host;
                        }
 
-                       if ( uri.protocol[ uri.protocol.length - 1 ] === ':' ) {
-                               uri.protocol = uri.protocol.substring( 0, 
uri.protocol.length - 1 );
-                       }
+                       uri.protocol = VegaWrapper.removeColon( uri.protocol );
 
                        return uri.toString();
-               } );
+               },
+               languageCode: mw.config.get( 'wgUserLanguage' )
+       } );
 
        /**
         * Set up drawing canvas inside the given element and draw graph data

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

Gerrit-MessageType: merged
Gerrit-Change-Id: I6eb35ea739f5827a14f85758748c313701734880
Gerrit-PatchSet: 15
Gerrit-Project: mediawiki/extensions/Graph
Gerrit-Branch: master
Gerrit-Owner: Yurik <yu...@wikimedia.org>
Gerrit-Reviewer: MaxSem <maxsem.w...@gmail.com>
Gerrit-Reviewer: jenkins-bot <>

_______________________________________________
MediaWiki-commits mailing list
MediaWiki-commits@lists.wikimedia.org
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to