diff --git a/lib/cartodb/controllers/layergroup.js b/lib/cartodb/controllers/layergroup.js index 8c426c7c..b4cc67cc 100644 --- a/lib/cartodb/controllers/layergroup.js +++ b/lib/cartodb/controllers/layergroup.js @@ -21,7 +21,6 @@ var QueryTables = require('cartodb-query-tables'); * @param {TileBackend} tileBackend * @param {PreviewBackend} previewBackend * @param {AttributesBackend} attributesBackend - * @param {WidgetBackend} widgetBackend * @param {SurrogateKeysCache} surrogateKeysCache * @param {UserLimitsApi} userLimitsApi * @param {LayergroupAffectedTables} layergroupAffectedTables @@ -29,7 +28,7 @@ var QueryTables = require('cartodb-query-tables'); * @constructor */ function LayergroupController(authApi, pgConnection, mapStore, tileBackend, previewBackend, attributesBackend, - widgetBackend, surrogateKeysCache, userLimitsApi, layergroupAffectedTables, analysisBackend) { + surrogateKeysCache, userLimitsApi, layergroupAffectedTables, analysisBackend) { BaseController.call(this, authApi, pgConnection); this.pgConnection = pgConnection; @@ -37,7 +36,6 @@ function LayergroupController(authApi, pgConnection, mapStore, tileBackend, prev this.tileBackend = tileBackend; this.previewBackend = previewBackend; this.attributesBackend = attributesBackend; - this.widgetBackend = widgetBackend; this.surrogateKeysCache = surrogateKeysCache; this.userLimitsApi = userLimitsApi; this.layergroupAffectedTables = layergroupAffectedTables; @@ -78,21 +76,19 @@ LayergroupController.prototype.register = function(app) { // Undocumented/non-supported API endpoint methods. // Use at your own peril. - app.get(app.base_url_mapconfig + - '/:token/:layer/widget/:widgetName', cors(), userMiddleware, - this.widget.bind(this)); - - app.get(app.base_url_mapconfig + - '/:token/:layer/widget/:widgetName/search', cors(), userMiddleware, - this.widgetSearch.bind(this)); - app.get(app.base_url_mapconfig + '/:token/dataview/:dataviewName', cors(), userMiddleware, this.dataview.bind(this)); + app.get(app.base_url_mapconfig + + '/:token/:layer/widget/:dataviewName', cors(), userMiddleware, + this.dataview.bind(this)); app.get(app.base_url_mapconfig + '/:token/dataview/:dataviewName/search', cors(), userMiddleware, this.dataviewSearch.bind(this)); + app.get(app.base_url_mapconfig + + '/:token/:layer/widget/:dataviewName/search', cors(), userMiddleware, + this.dataviewSearch.bind(this)); app.get(app.base_url_mapconfig + '/:token/analysis/node/:nodeId', cors(), userMiddleware, @@ -181,62 +177,6 @@ LayergroupController.prototype.dataviewSearch = function(req, res) { }; -LayergroupController.prototype.widget = function(req, res) { - var self = this; - - step( - function setupParams() { - self.req2params(req, this); - }, - function retrieveList(err) { - assert.ifError(err); - - var mapConfigProvider = new MapStoreMapConfigProvider( - self.mapStore, req.context.user, self.userLimitsApi, req.params - ); - self.widgetBackend.getWidget(mapConfigProvider, req.params, this); - }, - function finish(err, widget, stats) { - req.profiler.add(stats || {}); - - if (err) { - self.sendError(req, res, err, 'GET WIDGET'); - } else { - self.sendResponse(req, res, widget, 200); - } - } - ); - -}; - -LayergroupController.prototype.widgetSearch = function(req, res) { - var self = this; - - step( - function setupParams() { - self.req2params(req, this); - }, - function retrieveList(err) { - assert.ifError(err); - - var mapConfigProvider = new MapStoreMapConfigProvider( - self.mapStore, req.context.user, self.userLimitsApi, req.params - ); - self.widgetBackend.search(mapConfigProvider, req.params, this); - }, - function finish(err, searchResult, stats) { - req.profiler.add(stats || {}); - - if (err) { - self.sendError(req, res, err, 'GET WIDGET'); - } else { - self.sendResponse(req, res, searchResult, 200); - } - } - ); - -}; - LayergroupController.prototype.attributes = function(req, res) { var self = this; diff --git a/lib/cartodb/models/mapconfig/adapter/dataviews-widgets-adapter.js b/lib/cartodb/models/mapconfig/adapter/dataviews-widgets-adapter.js index 43bdd450..46e1c378 100644 --- a/lib/cartodb/models/mapconfig/adapter/dataviews-widgets-adapter.js +++ b/lib/cartodb/models/mapconfig/adapter/dataviews-widgets-adapter.js @@ -5,5 +5,86 @@ module.exports = DataviewsWidgetsMapConfigAdapter; DataviewsWidgetsMapConfigAdapter.prototype.getMapConfig = function(user, requestMapConfig, params, context, callback) { + if (!shouldAdapt(requestMapConfig)) { + return callback(null, requestMapConfig); + } + + // prepare placeholders for new dataviews created from widgets + requestMapConfig.analyses = requestMapConfig.analyses || []; + requestMapConfig.dataviews = requestMapConfig.dataviews || {}; + + requestMapConfig.layers.forEach(function(layer, index) { + var layerSourceId = getLayerSourceId(layer); + var dataviewSourceId = layerSourceId || 'cdb-layer-source-' + index; + // Append a new analysis if layer has no source id but sql. + if (!layerSourceId) { + requestMapConfig.analyses.push( + { + id: dataviewSourceId, + type: 'source', + params: { + query: layer.options.sql + } + } + ); + } + var source = { id: dataviewSourceId }; + var layerWidgets = layer.options.widgets; + Object.keys(layerWidgets).forEach(function(widgetId) { + var dataview = layerWidgets[widgetId]; + requestMapConfig.dataviews[widgetId] = { + source: source, + type: dataview.type, + options: dataview.options + }; + }); + + layer.options.source = source; + + delete layer.options.sql; + // don't delete widgets for now as it might be useful for old clients + //delete layer.options.widgets; + }); + + // filters have to be rewritten also + var filters = getFilters(params); + var layersFilters = filters.layers || []; + filters.dataviews = filters.dataviews || {}; + + layersFilters.forEach(function(layerFilters) { + Object.keys(layerFilters).forEach(function(filterName) { + if (!filters.dataviews.hasOwnProperty(filterName)) { + filters.dataviews[filterName] = layerFilters[filterName]; + } + }); + }); + + delete filters.layers; + + params.filters = JSON.stringify(filters); + return callback(null, requestMapConfig); }; + +function shouldAdapt(requestMapConfig) { +// return false; + return Array.isArray(requestMapConfig.layers) && requestMapConfig.layers.some(function hasWidgets(layer) { + return layer.options && layer.options.widgets && Object.keys(layer.options.widgets).length > 0; + }); +} + +function getLayerSourceId(layer) { + return layer.options.source && layer.options.source.id; +} + +function getFilters(params) { + var filters = {}; + if (params.filters) { + try { + filters = JSON.parse(params.filters); + } catch (e) { + // ignore + } + } + return filters; +} diff --git a/lib/cartodb/server.js b/lib/cartodb/server.js index d3ce98eb..aa41ed27 100644 --- a/lib/cartodb/server.js +++ b/lib/cartodb/server.js @@ -194,7 +194,6 @@ module.exports = function(serverOptions) { tileBackend, previewBackend, attributesBackend, - new windshaft.backend.Widget(), surrogateKeysCache, userLimitsApi, layergroupAffectedTablesCache, diff --git a/test/support/test-client.js b/test/support/test-client.js index e0fadeee..38fe434f 100644 --- a/test/support/test-client.js +++ b/test/support/test-client.js @@ -62,6 +62,7 @@ TestClient.prototype.getWidget = function(widgetName, params, callback) { return next(err); } var parsedBody = JSON.parse(res.body); + var expectedWidgetURLS = { http: "/api/v1/map/" + parsedBody.layergroupid + "/0/widget/" + widgetName }; @@ -69,6 +70,15 @@ TestClient.prototype.getWidget = function(widgetName, params, callback) { assert.ok( parsedBody.metadata.layers[0].widgets[widgetName].url.http.match(expectedWidgetURLS.http) ); + + var expectedDataviewsURLS = { + http: "/api/v1/map/" + parsedBody.layergroupid + "/dataview/" + widgetName + }; + assert.ok(parsedBody.metadata.dataviews[widgetName]); + assert.ok( + parsedBody.metadata.dataviews[widgetName].url.http.match(expectedDataviewsURLS.http) + ); + return next(null, parsedBody.layergroupid); } ); diff --git a/test/unit/cartodb/mapconfig/dataviews-widgets-adapter.test.js b/test/unit/cartodb/mapconfig/dataviews-widgets-adapter.test.js new file mode 100644 index 00000000..797ce06e --- /dev/null +++ b/test/unit/cartodb/mapconfig/dataviews-widgets-adapter.test.js @@ -0,0 +1,331 @@ +//require('../../../support/test_helper'); +var assert = require('assert'); + +var DataviewsMapConfigAdapter = require('../../../../lib/cartodb/models/mapconfig/adapter/dataviews-widgets-adapter'); + +describe('dataviews-widgets-adapter', function() { + + var widgetsMapConfigs = [ + { + "input": { + "version": "1.4.0", + "layers": [ + { + "type": "mapnik", + "options": { + "sql": "select * from populated_places_simple_reduced", + "cartocss": "#layer { marker-fill: red; marker-width: 32; marker-allow-overlap: true; }", + "cartocss_version": "2.3.0", + "widgets": { + "country_places_count": { + "type": "aggregation", + "options": { + "column": "adm0_a3", + "aggregation": "count" + } + } + } + } + } + ] + }, + "expected": { + "version": "1.4.0", + "layers": [ + { + "type": "mapnik", + "options": { + "source": { + "id": "cdb-layer-source-0" + }, + "cartocss": "#layer { marker-fill: red; marker-width: 32; marker-allow-overlap: true; }", + "cartocss_version": "2.3.0", + // keep them for now + "widgets": { + "country_places_count": { + "type": "aggregation", + "options": { + "column": "adm0_a3", + "aggregation": "count" + } + } + } + } + } + ], + "analyses": [ + { + "id": "cdb-layer-source-0", + "type": "source", + "params": { + "query": "select * from populated_places_simple_reduced" + } + } + ], + "dataviews": { + "country_places_count": { + "source": { + "id": "cdb-layer-source-0" + }, + "type": "aggregation", + "options": { + "column": "adm0_a3", + "aggregation": "count" + } + } + } + } + }, + { + "input": { + "version": "1.4.0", + "layers": [ + { + "type": "mapnik", + "options": { + "sql": "select * from populated_places_simple_reduced", + "cartocss": "#layer { marker-fill: red; marker-width: 32; marker-allow-overlap: true; }", + "cartocss_version": "2.3.0", + "widgets": { + "pop_max": { + "type": "histogram", + "options": { + "column": "pop_max" + } + } + } + } + } + ] + }, + "expected": { + "version": "1.4.0", + "layers": [ + { + "type": "mapnik", + "options": { + "source": { + "id": "cdb-layer-source-0" + }, + "cartocss": "#layer { marker-fill: red; marker-width: 32; marker-allow-overlap: true; }", + "cartocss_version": "2.3.0", + // keep them for now + "widgets": { + "pop_max": { + "type": "histogram", + "options": { + "column": "pop_max" + } + } + } + } + } + ], + "analyses": [ + { + "id": "cdb-layer-source-0", + "type": "source", + "params": { + "query": "select * from populated_places_simple_reduced" + } + } + ], + "dataviews": { + "pop_max": { + "source": { + "id": "cdb-layer-source-0" + }, + "type": "histogram", + "options": { + "column": "pop_max" + } + } + } + } + }, + { + "input": { + "version": "1.4.0", + "layers": [ + { + "type": "mapnik", + "options": { + "sql": "select * from test_table", + "cartocss": "#layer { marker-fill: red; marker-width: 32; marker-allow-overlap: true; }", + "cartocss_version": "2.3.0", + "widgets": { + "names": { + "type": "list", + "options": { + "columns": [ + "name" + ] + } + } + } + } + } + ] + }, + "expected": { + "version": "1.4.0", + "layers": [ + { + "type": "mapnik", + "options": { + "source": { + "id": "cdb-layer-source-0" + }, + "cartocss": "#layer { marker-fill: red; marker-width: 32; marker-allow-overlap: true; }", + "cartocss_version": "2.3.0", + // keep them for now + "widgets": { + "names": { + "type": "list", + "options": { + "columns": [ + "name" + ] + } + } + } + } + } + ], + "analyses": [ + { + "id": "cdb-layer-source-0", + "type": "source", + "params": { + "query": "select * from test_table" + } + } + ], + "dataviews": { + "names": { + "source": { + "id": "cdb-layer-source-0" + }, + "type": "list", + "options": { + "columns": [ + "name" + ] + } + } + } + } + }, + { + "input": { + "version": "1.4.0", + "layers": [ + { + "type": "mapnik", + "options": { + "sql": "select * from populated_places_simple_reduced", + "cartocss": "#layer { marker-fill: red; marker-width: 32; marker-allow-overlap: true; }", + "cartocss_version": "2.3.0", + "widgets": { + "country_places_count": { + "type": "aggregation", + "options": { + "column": "adm0_a3", + "aggregation": "count" + } + }, + "country_places_histogram": { + "type": "histogram", + "options": { + "column": "pop_max" + } + } + } + } + } + ] + }, + "expected": { + "version": "1.4.0", + "layers": [ + { + "type": "mapnik", + "options": { + "source": { + "id": "cdb-layer-source-0" + }, + "cartocss": "#layer { marker-fill: red; marker-width: 32; marker-allow-overlap: true; }", + "cartocss_version": "2.3.0", + // keep them for now + "widgets": { + "country_places_count": { + "type": "aggregation", + "options": { + "column": "adm0_a3", + "aggregation": "count" + } + }, + "country_places_histogram": { + "type": "histogram", + "options": { + "column": "pop_max" + } + } + } + } + } + ], + "analyses": [ + { + "id": "cdb-layer-source-0", + "type": "source", + "params": { + "query": "select * from populated_places_simple_reduced" + } + } + ], + "dataviews": { + "country_places_count": { + "source": { + "id": "cdb-layer-source-0" + }, + "type": "aggregation", + "options": { + "column": "adm0_a3", + "aggregation": "count" + } + }, + "country_places_histogram": { + "source": { + "id": "cdb-layer-source-0" + }, + "type": "histogram", + "options": { + "column": "pop_max" + } + } + } + } + } + ]; + + var user = 'wadus'; + function params() { + return {}; + } + function context() { + return {}; + } + + var dataviewsMapConfigAdapter = new DataviewsMapConfigAdapter(); + + widgetsMapConfigs.forEach(function(mapConfig, index) { + it('should adapt widgets ' + index, function(done) { + dataviewsMapConfigAdapter.getMapConfig(user, mapConfig.input, params(), context(), function(err, result) { + assert.deepEqual(result, mapConfig.expected); + done(); + }); + }); + }); + +});