diff --git a/NEWS.md b/NEWS.md index bff12396..ce678f4d 100644 --- a/NEWS.md +++ b/NEWS.md @@ -5,6 +5,8 @@ Released 2016-mm-dd Announcements: + - Upgrades windshaft to [2.0.1](https://github.com/CartoDB/camshaft/releases/tag/2.0.1 ) + - Upgrades camshaft to [0.12.1](https://github.com/CartoDB/camshaft/releases/tag/0.12.1) - Upgrades turbo-carto to [0.10.1](https://github.com/CartoDB/turbo-carto/releases/tag/0.10.1) 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/controllers/map.js b/lib/cartodb/controllers/map.js index 32459059..0357b765 100644 --- a/lib/cartodb/controllers/map.js +++ b/lib/cartodb/controllers/map.js @@ -161,15 +161,15 @@ MapController.prototype.create = function(req, res, prepareConfigFn) { }, function afterLayergroupCreate(err, layergroup) { assert.ifError(err); - var analysesResults = context.analysesResults || []; - self.afterLayergroupCreate(req, res, mapConfig, analysesResults, layergroup, this); + self.afterLayergroupCreate(req, res, mapConfig, layergroup, this); }, function finish(err, layergroup) { if (err) { self.sendError(req, res, err, 'ANONYMOUS LAYERGROUP'); } else { - addWidgetsUrl(req.context.user, layergroup); - + var analysesResults = context.analysesResults || []; + addDataviewsAndWidgetsUrls(req.context.user, layergroup, mapConfig.obj()); + addAnalysesMetadata(req.context.user, layergroup, analysesResults, true); res.set('X-Layergroup-Id', layergroup.layergroupid); self.send(req, res, layergroup, 200); } @@ -219,7 +219,7 @@ MapController.prototype.instantiateTemplate = function(req, res, prepareParamsFn }, function afterLayergroupCreate(err, layergroup) { assert.ifError(err); - self.afterLayergroupCreate(req, res, mapConfig, [], layergroup, this); + self.afterLayergroupCreate(req, res, mapConfig, layergroup, this); }, function finishTemplateInstantiation(err, layergroup) { if (err) { @@ -228,8 +228,7 @@ MapController.prototype.instantiateTemplate = function(req, res, prepareParamsFn var templateHash = self.templateMaps.fingerPrint(mapConfigProvider.template).substring(0, 8); layergroup.layergroupid = cdbuser + '@' + templateHash + '@' + layergroup.layergroupid; - addWidgetsUrl(cdbuser, layergroup); - addDataviewsUrls(cdbuser, layergroup, mapConfig.obj()); + addDataviewsAndWidgetsUrls(cdbuser, layergroup, mapConfig.obj()); addAnalysesMetadata(cdbuser, layergroup, mapConfigProvider.analysesResults); res.set('X-Layergroup-Id', layergroup.layergroupid); @@ -241,8 +240,7 @@ MapController.prototype.instantiateTemplate = function(req, res, prepareParamsFn ); }; - -MapController.prototype.afterLayergroupCreate = function(req, res, mapconfig, analysesResults, layergroup, callback) { +MapController.prototype.afterLayergroupCreate = function(req, res, mapconfig, layergroup, callback) { var self = this; var username = req.context.user; @@ -305,10 +303,6 @@ MapController.prototype.afterLayergroupCreate = function(req, res, mapconfig, an layergroup.layergroupid = layergroup.layergroupid + ':' + result.getLastUpdatedAt(); layergroup.last_updated = new Date(result.getLastUpdatedAt()).toISOString(); - // TODO this should take into account several URL patterns - addWidgetsUrl(username, layergroup); - addDataviewsUrls(username, layergroup, mapconfig.obj()); - addAnalysesMetadata(username, layergroup, analysesResults, true); if (req.method === 'GET') { var ttl = global.environment.varnish.layergroupTtl || 86400; res.set('Cache-Control', 'public,max-age='+ttl+',must-revalidate'); @@ -353,6 +347,12 @@ function addAnalysesMetadata(username, layergroup, analysesResults, includeQuery }); } +// TODO this should take into account several URL patterns +function addDataviewsAndWidgetsUrls(username, layergroup, mapConfig) { + addDataviewsUrls(username, layergroup, mapConfig); + addWidgetsUrl(username, layergroup, mapConfig); +} + function addDataviewsUrls(username, layergroup, mapConfig) { layergroup.metadata.dataviews = layergroup.metadata.dataviews || {}; var dataviews = mapConfig.dataviews || {}; @@ -365,20 +365,23 @@ function addDataviewsUrls(username, layergroup, mapConfig) { }); } -function addWidgetsUrl(username, layergroup) { - - if (layergroup.metadata && Array.isArray(layergroup.metadata.layers)) { +function addWidgetsUrl(username, layergroup, mapConfig) { + if (layergroup.metadata && Array.isArray(layergroup.metadata.layers) && Array.isArray(mapConfig.layers)) { layergroup.metadata.layers = layergroup.metadata.layers.map(function(layer, layerIndex) { - if (layer.widgets) { - Object.keys(layer.widgets).forEach(function(widgetName) { + var mapConfigLayer = mapConfig.layers[layerIndex]; + if (mapConfigLayer.options && mapConfigLayer.options.widgets) { + layer.widgets = layer.widgets || {}; + Object.keys(mapConfigLayer.options.widgets).forEach(function(widgetName) { var resource = layergroup.layergroupid + '/' + layerIndex + '/widget/' + widgetName; - layer.widgets[widgetName].url = getUrls(username, resource); + layer.widgets[widgetName] = { + type: mapConfigLayer.options.widgets[widgetName].type, + url: getUrls(username, resource) + }; }); } return layer; }); } - } function getUrls(username, resource) { diff --git a/lib/cartodb/models/dataview/aggregation.js b/lib/cartodb/models/dataview/aggregation.js index f7a49210..4b02f0ee 100644 --- a/lib/cartodb/models/dataview/aggregation.js +++ b/lib/cartodb/models/dataview/aggregation.js @@ -102,7 +102,7 @@ Aggregation.prototype.constructor = Aggregation; module.exports = Aggregation; -Aggregation.prototype.sql = function(psql, filters, override, callback) { +Aggregation.prototype.sql = function(psql, override, callback) { if (!callback) { callback = override; override = {}; diff --git a/lib/cartodb/models/dataview/formula.js b/lib/cartodb/models/dataview/formula.js index c3c176dd..156985c5 100644 --- a/lib/cartodb/models/dataview/formula.js +++ b/lib/cartodb/models/dataview/formula.js @@ -56,7 +56,7 @@ Formula.prototype.constructor = Formula; module.exports = Formula; -Formula.prototype.sql = function(psql, filters, override, callback) { +Formula.prototype.sql = function(psql, override, callback) { if (!callback) { callback = override; override = {}; diff --git a/lib/cartodb/models/dataview/list.js b/lib/cartodb/models/dataview/list.js index 4127103a..33993777 100644 --- a/lib/cartodb/models/dataview/list.js +++ b/lib/cartodb/models/dataview/list.js @@ -34,7 +34,7 @@ List.prototype.constructor = List; module.exports = List; -List.prototype.sql = function(psql, filters, override, callback) { +List.prototype.sql = function(psql, override, callback) { if (!callback) { callback = override; } diff --git a/lib/cartodb/models/mapconfig/adapter/dataviews-widgets-adapter.js b/lib/cartodb/models/mapconfig/adapter/dataviews-widgets-adapter.js new file mode 100644 index 00000000..46e1c378 --- /dev/null +++ b/lib/cartodb/models/mapconfig/adapter/dataviews-widgets-adapter.js @@ -0,0 +1,90 @@ +function DataviewsWidgetsMapConfigAdapter() { +} + +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/models/mapconfig/adapter/mapconfig-overviews-adapter.js b/lib/cartodb/models/mapconfig/adapter/mapconfig-overviews-adapter.js index b8a473b5..db2cb439 100644 --- a/lib/cartodb/models/mapconfig/adapter/mapconfig-overviews-adapter.js +++ b/lib/cartodb/models/mapconfig/adapter/mapconfig-overviews-adapter.js @@ -72,7 +72,7 @@ MapConfigOverviewsAdapter.prototype.getMapConfig = function(user, requestMapConf layer = _.extend({}, layer); layer.options = _.extend({}, layer.options, { query_rewrite_data: query_rewrite_data }); } - done(err, layer); + done(null, layer); } ); } diff --git a/lib/cartodb/server.js b/lib/cartodb/server.js index 825ae2b7..aa41ed27 100644 --- a/lib/cartodb/server.js +++ b/lib/cartodb/server.js @@ -41,6 +41,7 @@ var MapConfigNamedLayersAdapter = require('./models/mapconfig/adapter/mapconfig- var AnalysisMapConfigAdapter = require('./models/mapconfig/adapter/analysis-mapconfig-adapter'); var MapConfigOverviewsAdapter = require('./models/mapconfig/adapter/mapconfig-overviews-adapter'); var TurboCartoAdapter = require('./models/mapconfig/adapter/turbo-carto-adapter'); +var DataviewsWidgetsAdapter = require('./models/mapconfig/adapter/dataviews-widgets-adapter'); var MapConfigAdapter = require('./models/mapconfig/adapter'); module.exports = function(serverOptions) { @@ -159,6 +160,7 @@ module.exports = function(serverOptions) { var mapConfigAdapter = new MapConfigAdapter( new MapConfigNamedLayersAdapter(templateMaps, pgConnection), new SqlWrapMapConfigAdapter(), + new DataviewsWidgetsAdapter(), new AnalysisMapConfigAdapter(analysisBackend), new MapConfigOverviewsAdapter(overviewsMetadataApi, filterStatsApi), new TurboCartoAdapter(turboCartoParser) @@ -192,7 +194,6 @@ module.exports = function(serverOptions) { tileBackend, previewBackend, attributesBackend, - new windshaft.backend.Widget(), surrogateKeysCache, userLimitsApi, layergroupAffectedTablesCache, diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index cba946fd..ad676692 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -105,13 +105,13 @@ } }, "camshaft": { - "version": "0.11.0", - "from": "camshaft@0.11.0", - "resolved": "https://registry.npmjs.org/camshaft/-/camshaft-0.11.0.tgz", + "version": "0.12.1", + "from": "camshaft@0.12.1", + "resolved": "https://registry.npmjs.org/camshaft/-/camshaft-0.12.1.tgz", "dependencies": { "async": { "version": "1.5.2", - "from": "async@>=1.0.0 <2.0.0", + "from": "async@>=1.5.2 <2.0.0", "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz" }, "request": { @@ -270,7 +270,7 @@ }, "is-my-json-valid": { "version": "2.13.1", - "from": "is-my-json-valid@>=2.12.0 <3.0.0", + "from": "is-my-json-valid@>=2.12.4 <3.0.0", "resolved": "https://registry.npmjs.org/is-my-json-valid/-/is-my-json-valid-2.13.1.tgz", "dependencies": { "generate-function": { @@ -391,9 +391,9 @@ "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz" }, "dashdash": { - "version": "1.13.1", + "version": "1.14.0", "from": "dashdash@>=1.12.0 <2.0.0", - "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.13.1.tgz" + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.0.tgz" }, "getpass": { "version": "0.1.6", @@ -648,7 +648,7 @@ "dependencies": { "unpipe": { "version": "1.0.0", - "from": "unpipe@>=1.0.0 <1.1.0", + "from": "unpipe@1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz" } } @@ -804,7 +804,7 @@ }, "type-is": { "version": "1.6.13", - "from": "type-is@>=1.6.10 <1.7.0", + "from": "type-is@>=1.6.6 <1.7.0", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.13.tgz", "dependencies": { "media-typer": { @@ -1029,7 +1029,7 @@ }, "mime-types": { "version": "2.1.11", - "from": "mime-types@>=2.1.2 <2.2.0", + "from": "mime-types@>=2.1.11 <2.2.0", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.11.tgz", "dependencies": { "mime-db": { @@ -1056,7 +1056,7 @@ }, "tough-cookie": { "version": "2.2.2", - "from": "tough-cookie@>=2.2.0 <2.3.0", + "from": "tough-cookie@>=0.12.0", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.2.2.tgz" }, "http-signature": { @@ -1152,7 +1152,7 @@ }, "chalk": { "version": "1.1.3", - "from": "chalk@>=1.1.1 <2.0.0", + "from": "chalk@>=1.0.0 <2.0.0", "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", "dependencies": { "ansi-styles": { @@ -1318,14 +1318,14 @@ "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.6.0.tgz" }, "windshaft": { - "version": "1.19.0", - "from": "windshaft@1.19.0", - "resolved": "https://registry.npmjs.org/windshaft/-/windshaft-1.19.0.tgz", + "version": "2.0.1", + "from": "windshaft@2.0.1", + "resolved": "https://registry.npmjs.org/windshaft/-/windshaft-2.0.1.tgz", "dependencies": { "abaculus": { - "version": "1.1.0-cdb4", - "from": "https://github.com/CartoDB/abaculus/tarball/1.1.0-cdb4", - "resolved": "https://github.com/CartoDB/abaculus/tarball/1.1.0-cdb4" + "version": "1.1.0-cdb5", + "from": "https://github.com/CartoDB/abaculus/tarball/1.1.0-cdb5", + "resolved": "https://github.com/CartoDB/abaculus/tarball/1.1.0-cdb5" }, "canvas": { "version": "1.2.7-cdb1", @@ -2966,56 +2966,56 @@ "from": "node-pre-gyp@>=0.6.28 <0.7.0", "resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.6.28.tgz" }, - "ansi": { - "version": "0.3.1", - "from": "ansi@>=0.3.1 <0.4.0", - "resolved": "https://registry.npmjs.org/ansi/-/ansi-0.3.1.tgz" - }, "abbrev": { "version": "1.0.7", "from": "abbrev@>=1.0.0 <2.0.0", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.0.7.tgz" }, - "ansi-styles": { - "version": "2.2.1", - "from": "ansi-styles@>=2.2.1 <3.0.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz" + "ansi": { + "version": "0.3.1", + "from": "ansi@>=0.3.1 <0.4.0", + "resolved": "https://registry.npmjs.org/ansi/-/ansi-0.3.1.tgz" }, "ansi-regex": { "version": "2.0.0", "from": "ansi-regex@>=2.0.0 <3.0.0", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.0.0.tgz" }, + "ansi-styles": { + "version": "2.2.1", + "from": "ansi-styles@>=2.2.1 <3.0.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz" + }, "are-we-there-yet": { "version": "1.1.2", "from": "are-we-there-yet@>=1.1.2 <1.2.0", "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.2.tgz" }, - "assert-plus": { - "version": "0.2.0", - "from": "assert-plus@>=0.2.0 <0.3.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-0.2.0.tgz" - }, "asn1": { "version": "0.2.3", "from": "asn1@>=0.2.3 <0.3.0", "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz" }, + "assert-plus": { + "version": "0.2.0", + "from": "assert-plus@>=0.2.0 <0.3.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-0.2.0.tgz" + }, "async": { "version": "1.5.2", "from": "async@>=1.5.2 <2.0.0", "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz" }, - "aws4": { - "version": "1.4.1", - "from": "aws4@>=1.2.1 <2.0.0", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.4.1.tgz" - }, "aws-sign2": { "version": "0.6.0", "from": "aws-sign2@>=0.6.0 <0.7.0", "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.6.0.tgz" }, + "aws4": { + "version": "1.4.1", + "from": "aws4@>=1.2.1 <2.0.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.4.1.tgz" + }, "balanced-match": { "version": "0.4.1", "from": "balanced-match@>=0.4.1 <0.5.0", @@ -3031,6 +3031,11 @@ "from": "boom@>=2.0.0 <3.0.0", "resolved": "https://registry.npmjs.org/boom/-/boom-2.10.1.tgz" }, + "brace-expansion": { + "version": "1.1.4", + "from": "brace-expansion@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.4.tgz" + }, "caseless": { "version": "0.11.0", "from": "caseless@>=0.11.0 <0.12.0", @@ -3041,6 +3046,11 @@ "from": "chalk@>=1.1.1 <2.0.0", "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz" }, + "combined-stream": { + "version": "1.0.5", + "from": "combined-stream@>=1.0.5 <1.1.0", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.5.tgz" + }, "commander": { "version": "2.9.0", "from": "commander@>=2.9.0 <3.0.0", @@ -3061,16 +3071,16 @@ "from": "cryptiles@>=2.0.0 <3.0.0", "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-2.0.5.tgz" }, - "combined-stream": { - "version": "1.0.5", - "from": "combined-stream@>=1.0.5 <1.1.0", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.5.tgz" - }, "debug": { "version": "2.2.0", "from": "debug@>=2.2.0 <2.3.0", "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz" }, + "deep-extend": { + "version": "0.4.1", + "from": "deep-extend@>=0.4.0 <0.5.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.4.1.tgz" + }, "delayed-stream": { "version": "1.0.0", "from": "delayed-stream@>=1.0.0 <1.1.0", @@ -3081,16 +3091,6 @@ "from": "delegates@>=1.0.0 <2.0.0", "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz" }, - "deep-extend": { - "version": "0.4.1", - "from": "deep-extend@>=0.4.0 <0.5.0", - "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.4.1.tgz" - }, - "brace-expansion": { - "version": "1.1.4", - "from": "brace-expansion@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.4.tgz" - }, "ecc-jsbn": { "version": "0.1.1", "from": "ecc-jsbn@>=0.1.1 <0.2.0", @@ -3106,6 +3106,11 @@ "from": "extend@>=3.0.0 <3.1.0", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.0.tgz" }, + "extsprintf": { + "version": "1.0.2", + "from": "extsprintf@1.0.2", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.0.2.tgz" + }, "forever-agent": { "version": "0.6.1", "from": "forever-agent@>=0.6.1 <0.7.0", @@ -3116,11 +3121,6 @@ "from": "form-data@>=1.0.0-rc3 <1.1.0", "resolved": "https://registry.npmjs.org/form-data/-/form-data-1.0.0-rc4.tgz" }, - "extsprintf": { - "version": "1.0.2", - "from": "extsprintf@1.0.2", - "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.0.2.tgz" - }, "fstream": { "version": "1.0.9", "from": "fstream@>=1.0.2 <2.0.0", @@ -3136,55 +3136,55 @@ "from": "gauge@>=1.2.5 <1.3.0", "resolved": "https://registry.npmjs.org/gauge/-/gauge-1.2.7.tgz" }, - "generate-function": { - "version": "2.0.0", - "from": "generate-function@>=2.0.0 <3.0.0", - "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.0.0.tgz" - }, "generate-object-property": { "version": "1.2.0", "from": "generate-object-property@>=1.1.0 <2.0.0", "resolved": "https://registry.npmjs.org/generate-object-property/-/generate-object-property-1.2.0.tgz" }, + "generate-function": { + "version": "2.0.0", + "from": "generate-function@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.0.0.tgz" + }, "glob": { "version": "7.0.3", "from": "glob@>=7.0.0 <8.0.0", "resolved": "https://registry.npmjs.org/glob/-/glob-7.0.3.tgz" }, - "graceful-fs": { - "version": "4.1.4", - "from": "graceful-fs@>=4.1.2 <5.0.0", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.4.tgz" + "graceful-readlink": { + "version": "1.0.1", + "from": "graceful-readlink@>=1.0.0", + "resolved": "https://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz" }, "har-validator": { "version": "2.0.6", "from": "har-validator@>=2.0.6 <2.1.0", "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-2.0.6.tgz" }, + "graceful-fs": { + "version": "4.1.4", + "from": "graceful-fs@>=4.1.2 <5.0.0", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.4.tgz" + }, "has-ansi": { "version": "2.0.0", "from": "has-ansi@>=2.0.0 <3.0.0", "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz" }, - "graceful-readlink": { - "version": "1.0.1", - "from": "graceful-readlink@>=1.0.0", - "resolved": "https://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz" - }, - "hoek": { - "version": "2.16.3", - "from": "hoek@>=2.0.0 <3.0.0", - "resolved": "https://registry.npmjs.org/hoek/-/hoek-2.16.3.tgz" + "has-unicode": { + "version": "2.0.0", + "from": "has-unicode@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.0.tgz" }, "hawk": { "version": "3.1.3", "from": "hawk@>=3.1.3 <3.2.0", "resolved": "https://registry.npmjs.org/hawk/-/hawk-3.1.3.tgz" }, - "has-unicode": { - "version": "2.0.0", - "from": "has-unicode@>=2.0.0 <3.0.0", - "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.0.tgz" + "hoek": { + "version": "2.16.3", + "from": "hoek@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/hoek/-/hoek-2.16.3.tgz" }, "inflight": { "version": "1.0.4", @@ -3201,6 +3201,11 @@ "from": "inherits@>=2.0.1 <2.1.0", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz" }, + "ini": { + "version": "1.3.4", + "from": "ini@>=1.3.0 <1.4.0", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.4.tgz" + }, "is-my-json-valid": { "version": "2.13.1", "from": "is-my-json-valid@>=2.12.4 <3.0.0", @@ -3211,11 +3216,6 @@ "from": "is-property@>=1.0.0 <2.0.0", "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz" }, - "ini": { - "version": "1.3.4", - "from": "ini@>=1.3.0 <1.4.0", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.4.tgz" - }, "is-typedarray": { "version": "1.0.0", "from": "is-typedarray@>=1.0.0 <1.1.0", @@ -3236,16 +3236,16 @@ "from": "jodid25519@>=1.0.0 <2.0.0", "resolved": "https://registry.npmjs.org/jodid25519/-/jodid25519-1.0.2.tgz" }, - "json-schema": { - "version": "0.2.2", - "from": "json-schema@0.2.2", - "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.2.tgz" - }, "jsbn": { "version": "0.1.0", "from": "jsbn@>=0.1.0 <0.2.0", "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.0.tgz" }, + "json-schema": { + "version": "0.2.2", + "from": "json-schema@0.2.2", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.2.tgz" + }, "json-stringify-safe": { "version": "5.0.1", "from": "json-stringify-safe@>=5.0.1 <5.1.0", @@ -3271,31 +3271,31 @@ "from": "lodash._basetostring@>=4.12.0 <4.13.0", "resolved": "https://registry.npmjs.org/lodash._basetostring/-/lodash._basetostring-4.12.0.tgz" }, - "lodash.padend": { - "version": "4.5.0", - "from": "lodash.padend@>=4.1.0 <5.0.0", - "resolved": "https://registry.npmjs.org/lodash.padend/-/lodash.padend-4.5.0.tgz" - }, "lodash.pad": { "version": "4.4.0", "from": "lodash.pad@>=4.1.0 <5.0.0", "resolved": "https://registry.npmjs.org/lodash.pad/-/lodash.pad-4.4.0.tgz" }, + "lodash.padend": { + "version": "4.5.0", + "from": "lodash.padend@>=4.1.0 <5.0.0", + "resolved": "https://registry.npmjs.org/lodash.padend/-/lodash.padend-4.5.0.tgz" + }, "lodash.padstart": { "version": "4.5.0", "from": "lodash.padstart@>=4.1.0 <5.0.0", "resolved": "https://registry.npmjs.org/lodash.padstart/-/lodash.padstart-4.5.0.tgz" }, - "mime-db": { - "version": "1.23.0", - "from": "mime-db@>=1.23.0 <1.24.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.23.0.tgz" - }, "lodash.tostring": { "version": "4.1.3", "from": "lodash.tostring@>=4.0.0 <5.0.0", "resolved": "https://registry.npmjs.org/lodash.tostring/-/lodash.tostring-4.1.3.tgz" }, + "mime-db": { + "version": "1.23.0", + "from": "mime-db@>=1.23.0 <1.24.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.23.0.tgz" + }, "mime-types": { "version": "2.1.11", "from": "mime-types@>=2.1.7 <2.2.0", @@ -3346,31 +3346,31 @@ "from": "once@>=1.3.0 <2.0.0", "resolved": "https://registry.npmjs.org/once/-/once-1.3.3.tgz" }, - "pinkie": { - "version": "2.0.4", - "from": "pinkie@>=2.0.0 <3.0.0", - "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz" - }, "path-is-absolute": { "version": "1.0.0", "from": "path-is-absolute@>=1.0.0 <2.0.0", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.0.tgz" }, + "pinkie": { + "version": "2.0.4", + "from": "pinkie@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz" + }, "pinkie-promise": { "version": "2.0.1", "from": "pinkie-promise@>=2.0.0 <3.0.0", "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz" }, - "qs": { - "version": "6.1.0", - "from": "qs@>=6.1.0 <6.2.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.1.0.tgz" - }, "process-nextick-args": { "version": "1.0.7", "from": "process-nextick-args@>=1.0.6 <1.1.0", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz" }, + "qs": { + "version": "6.1.0", + "from": "qs@>=6.1.0 <6.2.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.1.0.tgz" + }, "readable-stream": { "version": "2.1.2", "from": "readable-stream@>=2.0.0 <3.0.0||>=1.1.13 <2.0.0", @@ -3386,16 +3386,16 @@ "from": "rimraf@>=2.5.0 <2.6.0", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.5.2.tgz" }, - "sntp": { - "version": "1.0.9", - "from": "sntp@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/sntp/-/sntp-1.0.9.tgz" - }, "semver": { "version": "5.1.0", "from": "semver@>=5.1.0 <5.2.0", "resolved": "https://registry.npmjs.org/semver/-/semver-5.1.0.tgz" }, + "sntp": { + "version": "1.0.9", + "from": "sntp@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/sntp/-/sntp-1.0.9.tgz" + }, "string_decoder": { "version": "0.10.31", "from": "string_decoder@>=0.10.0 <0.11.0", @@ -3421,16 +3421,16 @@ "from": "supports-color@>=2.0.0 <3.0.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz" }, - "tough-cookie": { - "version": "2.2.2", - "from": "tough-cookie@>=2.2.0 <2.3.0", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.2.2.tgz" - }, "tar": { "version": "2.2.1", "from": "tar@>=2.2.0 <2.3.0", "resolved": "https://registry.npmjs.org/tar/-/tar-2.2.1.tgz" }, + "tough-cookie": { + "version": "2.2.2", + "from": "tough-cookie@>=2.2.0 <2.3.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.2.2.tgz" + }, "tunnel-agent": { "version": "0.4.3", "from": "tunnel-agent@>=0.4.1 <0.5.0", @@ -3451,11 +3451,6 @@ "from": "util-deprecate@>=1.0.1 <1.1.0", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz" }, - "wrappy": { - "version": "1.0.1", - "from": "wrappy@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.1.tgz" - }, "verror": { "version": "1.3.6", "from": "verror@1.3.6", @@ -3466,6 +3461,11 @@ "from": "xtend@>=4.0.0 <5.0.0", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz" }, + "wrappy": { + "version": "1.0.1", + "from": "wrappy@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.1.tgz" + }, "bl": { "version": "1.1.2", "from": "bl@>=1.1.2 <1.2.0", @@ -3478,6 +3478,18 @@ } } }, + "getpass": { + "version": "0.1.6", + "from": "getpass@>=0.1.1 <0.2.0", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.6.tgz", + "dependencies": { + "assert-plus": { + "version": "1.0.0", + "from": "assert-plus@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz" + } + } + }, "dashdash": { "version": "1.13.1", "from": "dashdash@>=1.12.0 <2.0.0", @@ -3502,18 +3514,6 @@ } } }, - "getpass": { - "version": "0.1.6", - "from": "getpass@>=0.1.1 <0.2.0", - "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.6.tgz", - "dependencies": { - "assert-plus": { - "version": "1.0.0", - "from": "assert-plus@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz" - } - } - }, "sshpk": { "version": "1.8.3", "from": "sshpk@>=1.7.0 <2.0.0", @@ -3579,9 +3579,9 @@ } }, "mapnik": { - "version": "1.4.15-cdb6", - "from": "https://github.com/CartoDB/node-mapnik/tarball/1.4.15-cdb6", - "resolved": "https://github.com/CartoDB/node-mapnik/tarball/1.4.15-cdb6", + "version": "1.4.15-cdb8", + "from": "https://github.com/CartoDB/node-mapnik/tarball/1.4.15-cdb8", + "resolved": "https://github.com/CartoDB/node-mapnik/tarball/1.4.15-cdb8", "dependencies": { "nan": { "version": "1.2.0", @@ -3998,14 +3998,14 @@ } }, "tilelive-bridge": { - "version": "1.3.0-cdb4", - "from": "https://github.com/CartoDB/tilelive-bridge/tarball/1.3.0-cdb4", - "resolved": "https://github.com/CartoDB/tilelive-bridge/tarball/1.3.0-cdb4", + "version": "1.3.0-cdb5", + "from": "https://github.com/CartoDB/tilelive-bridge/tarball/1.3.0-cdb5", + "resolved": "https://github.com/CartoDB/tilelive-bridge/tarball/1.3.0-cdb5", "dependencies": { "mapnik-pool": { - "version": "0.1.1-cdb4", - "from": "https://github.com/CartoDB/mapnik-pool/tarball/0.1.1-cdb4", - "resolved": "https://github.com/CartoDB/mapnik-pool/tarball/0.1.1-cdb4", + "version": "0.1.1-cdb5", + "from": "https://github.com/CartoDB/mapnik-pool/tarball/0.1.1-cdb5", + "resolved": "https://github.com/CartoDB/mapnik-pool/tarball/0.1.1-cdb5", "dependencies": { "generic-pool": { "version": "2.1.1", @@ -4022,9 +4022,9 @@ } }, "tilelive-mapnik": { - "version": "0.6.15-cdb5", - "from": "https://github.com/CartoDB/tilelive-mapnik/tarball/0.6.15-cdb5", - "resolved": "https://github.com/CartoDB/tilelive-mapnik/tarball/0.6.15-cdb5", + "version": "0.6.15-cdb7", + "from": "https://github.com/CartoDB/tilelive-mapnik/tarball/0.6.15-cdb7", + "resolved": "https://github.com/CartoDB/tilelive-mapnik/tarball/0.6.15-cdb7", "dependencies": { "generic-pool": { "version": "2.1.1", diff --git a/package.json b/package.json index fb063f1e..8902330e 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,7 @@ ], "dependencies": { "body-parser": "~1.14.0", - "camshaft": "0.11.0", + "camshaft": "0.12.1", "cartodb-psql": "~0.6.1", "cartodb-query-tables": "~0.1.0", "cartodb-redis": "~0.13.0", @@ -39,7 +39,7 @@ "step-profiler": "~0.3.0", "turbo-carto": "0.10.1", "underscore": "~1.6.0", - "windshaft": "1.19.0" + "windshaft": "2.0.1" }, "devDependencies": { "istanbul": "~0.4.3", diff --git a/test/acceptance/geojson-renderer.js b/test/acceptance/geojson-renderer.js new file mode 100644 index 00000000..1c4e2449 --- /dev/null +++ b/test/acceptance/geojson-renderer.js @@ -0,0 +1,171 @@ +require('../support/test_helper'); + +var assert = require('../support/assert'); +var TestClient = require('../support/test-client'); + + +describe('use only needed columns', function() { + + function getFeatureByCartodbId(features, cartodbId) { + for (var i = 0, len = features.length; i < len; i++) { + if (features[i].properties.cartodb_id === cartodbId) { + return features[i]; + } + } + return {}; + } + + var options = { format: 'geojson', layer: 0 }; + + afterEach(function(done) { + if (this.testClient) { + this.testClient.drain(done); + } else { + done(); + } + }); + + it('with aggregation widget, interactivity and cartocss columns', function(done) { + var widgetMapConfig = { + version: '1.5.0', + layers: [{ + type: 'mapnik', + options: { + sql: 'select * from populated_places_simple_reduced', + cartocss: '#layer0 { marker-fill: red; marker-width: 10; [name="Madrid"] { marker-fill: green; } }', + cartocss_version: '2.0.1', + widgets: { + adm0name: { + type: 'aggregation', + options: { + column: 'adm0name', + aggregation: 'sum', + aggregationColumn: 'pop_max' + } + } + }, + interactivity: "cartodb_id,pop_min" + } + }] + }; + + this.testClient = new TestClient(widgetMapConfig); + this.testClient.getTile(0, 0, 0, options, function (err, res, geojsonTile) { + assert.ok(!err, err); + assert.deepEqual(getFeatureByCartodbId(geojsonTile.features, 1109).properties, { + cartodb_id: 1109, + name: 'Mardin', + adm0name: 'Turkey', + pop_max: 71373, + pop_min: 57586 + }); + done(); + }); + }); + + it('should not duplicate columns', function(done) { + var widgetMapConfig = { + version: '1.5.0', + layers: [{ + type: 'mapnik', + options: { + sql: 'select * from populated_places_simple_reduced', + cartocss: ['#layer0 {', + 'marker-fill: red;', + 'marker-width: 10;', + '[name="Madrid"] { marker-fill: green; } ', + '[pop_max>100000] { marker-fill: black; } ', + '}'].join('\n'), + cartocss_version: '2.3.0', + widgets: { + adm0name: { + type: 'aggregation', + options: { + column: 'adm0name', + aggregation: 'sum', + aggregationColumn: 'pop_max' + } + } + }, + interactivity: "cartodb_id,pop_max" + } + }] + }; + + this.testClient = new TestClient(widgetMapConfig); + this.testClient.getTile(0, 0, 0, options, function (err, res, geojsonTile) { + assert.ok(!err, err); + assert.deepEqual(getFeatureByCartodbId(geojsonTile.features, 1109).properties, { + cartodb_id: 1109, + name: 'Mardin', + adm0name: 'Turkey', + pop_max: 71373 + }); + done(); + }); + }); + + it('with formula widget, no interactivity and no cartocss columns', function(done) { + var formulaWidgetMapConfig = { + version: '1.5.0', + layers: [{ + type: 'mapnik', + options: { + sql: 'select * from populated_places_simple_reduced where pop_max > 0 and pop_max < 600000', + cartocss: '#layer0 { marker-fill: red; marker-width: 10; }', + cartocss_version: '2.0.1', + interactivity: 'cartodb_id', + widgets: { + pop_max_f: { + type: 'formula', + options: { + column: 'pop_max', + operation: 'count' + } + } + } + } + }] + }; + + this.testClient = new TestClient(formulaWidgetMapConfig); + this.testClient.getTile(0, 0, 0, options, function (err, res, geojsonTile) { + assert.ok(!err, err); + assert.deepEqual(getFeatureByCartodbId(geojsonTile.features, 1109).properties, { + cartodb_id: 1109, + pop_max: 71373 + }); + done(); + }); + }); + it('with cartocss with multiple expressions', function(done) { + var formulaWidgetMapConfig = { + version: '1.5.0', + layers: [{ + type: 'mapnik', + options: { + sql: 'select * from populated_places_simple_reduced where pop_max > 0 and pop_max < 600000', + cartocss: '#layer0 { marker-fill: red; marker-width: 10; }' + + '#layer0 { marker-width: 14; [name="Madrid"] { marker-width: 20; } }' + + '#layer0[pop_max>1000] { marker-width: 14; [name="Madrid"] { marker-width: 20; } }' + + '#layer0[adm0name=~".*Turkey*"] { marker-width: 14; [name="Madrid"] { marker-width: 20; } }', + cartocss_version: '2.0.1', + interactivity: 'cartodb_id' + } + }] + }; + + this.testClient = new TestClient(formulaWidgetMapConfig); + this.testClient.getTile(0, 0, 0, options, function (err, res, geojsonTile) { + assert.ok(!err, err); + assert.deepEqual(getFeatureByCartodbId(geojsonTile.features, 1109).properties, { + cartodb_id: 1109, + pop_max:71373, + name:"Mardin", + adm0name:"Turkey" + }); + done(); + }); + }); + +}); \ No newline at end of file diff --git a/test/acceptance/widgets/ported/aggregation.js b/test/acceptance/widgets/ported/aggregation.js new file mode 100644 index 00000000..9329f039 --- /dev/null +++ b/test/acceptance/widgets/ported/aggregation.js @@ -0,0 +1,329 @@ +require('../../../support/test_helper'); + +var assert = require('../../../support/assert'); +var TestClient = require('../../../support/test-client'); + +describe('widgets', function() { + + describe('aggregations', function() { + + afterEach(function(done) { + if (this.testClient) { + this.testClient.drain(done); + } else { + done(); + } + }); + + var aggregationMapConfig = { + version: '1.5.0', + layers: [ + { + type: 'mapnik', + options: { + sql: 'select * from populated_places_simple_reduced', + cartocss: '#layer0 { marker-fill: red; marker-width: 10; }', + cartocss_version: '2.0.1', + widgets: { + adm0name: { + type: 'aggregation', + options: { + column: 'adm0name', + aggregation: 'count' + } + } + } + } + } + ] + }; + + it('can be fetched from a valid aggregation', function(done) { + this.testClient = new TestClient(aggregationMapConfig); + this.testClient.getWidget('adm0name', { own_filter: 0 }, function (err, res, aggregation) { + assert.ok(!err, err); + assert.ok(aggregation); + assert.equal(aggregation.type, 'aggregation'); + + assert.equal(aggregation.categories.length, 6); + + assert.deepEqual( + aggregation.categories[0], + { category: 'United States of America', value: 769, agg: false } + ); + + assert.deepEqual( + aggregation.categories[aggregation.categories.length - 1], + { category: 'Other', value: 4914, agg: true } + ); + + done(); + }); + }); + + var filteredCategoriesScenarios = [ + { accept: ['Canada'], values: [256] }, + { accept: ['Canada', 'Spain', 'Chile', 'Thailand'], values: [256, 49, 83, 79] }, + { accept: ['Canada', 'Spain', 'Chile', 'Thailand', 'Japan'], values: [256, 49, 83, 79, 69] }, + { accept: ['Canada', 'Spain', 'Chile', 'Thailand', 'Japan', 'France'], values: [256, 49, 83, 79, 69, 71] }, + { + accept: ['United States of America', 'Canada', 'Spain', 'Chile', 'Thailand', 'Japan', 'France'], + values: [769, 256, 49, 83, 79, 69, 71] + } + ]; + + filteredCategoriesScenarios.forEach(function(scenario) { + it('can filter some categories: ' + scenario.accept.join(', '), function(done) { + this.testClient = new TestClient(aggregationMapConfig); + var adm0nameFilter = { + adm0name: { + accept: scenario.accept + } + }; + var params = { + own_filter: 1, + filters: { + layers: [ + adm0nameFilter + ] + } + }; + this.testClient.getWidget('adm0name', params, function (err, res, aggregation) { + assert.ok(!err, err); + assert.ok(aggregation); + assert.equal(aggregation.type, 'aggregation'); + + assert.equal(aggregation.categories.length, scenario.accept.length); + + var categoriesByCategory = aggregation.categories.reduce(function(byCategory, row) { + byCategory[row.category] = row; + return byCategory; + }, {}); + + var scenarioByCategory = scenario.accept.reduce(function(byCategory, category, index) { + byCategory[category] = { category: category, value: scenario.values[index], agg: false }; + return byCategory; + }, {}); + + Object.keys(categoriesByCategory).forEach(function(category) { + assert.deepEqual(categoriesByCategory[category], scenarioByCategory[category]); + }); + + done(); + }); + }); + }); + + var aggregationSumMapConfig = { + version: '1.5.0', + layers: [ + { + type: 'mapnik', + options: { + sql: 'select * from populated_places_simple_reduced', + cartocss: '#layer0 { marker-fill: red; marker-width: 10; }', + cartocss_version: '2.0.1', + widgets: { + adm0name: { + type: 'aggregation', + options: { + column: 'adm0name', + aggregation: 'sum', + aggregationColumn: 'pop_max' + } + } + } + } + } + ] + }; + + it('can sum other column for aggregation value', function(done) { + + this.testClient = new TestClient(aggregationSumMapConfig); + this.testClient.getWidget('adm0name', { own_filter: 0 }, function (err, res, aggregation) { + assert.ok(!err, err); + assert.ok(aggregation); + assert.equal(aggregation.type, 'aggregation'); + + assert.equal(aggregation.categories.length, 6); + + assert.deepEqual( + aggregation.categories[0], + { category: 'China', value: 374537585, agg: false } + ); + + assert.deepEqual( + aggregation.categories[aggregation.categories.length - 1], + { category: 'Other', value: 1412626289, agg: true } + ); + + done(); + }); + }); + + var filteredCategoriesSumScenarios = [ + { accept: [], values: [] }, + { accept: ['Canada'], values: [23955084] }, + { accept: ['Canada', 'Spain', 'Chile', 'Thailand'], values: [23955084, 22902774, 14356263, 17492483] }, + { + accept: ['United States of America', 'Canada', 'Spain', 'Chile', 'Thailand', 'Japan', 'France'], + values: [239098994, 23955084, 22902774, 14356263, 17492483, 93577001, 25473876] + } + ]; + + filteredCategoriesSumScenarios.forEach(function(scenario) { + it('can filter some categories with sum aggregation: ' + scenario.accept.join(', '), function(done) { + this.testClient = new TestClient(aggregationSumMapConfig); + var adm0nameFilter = { + adm0name: { + accept: scenario.accept + } + }; + var params = { + own_filter: 1, + filters: { + layers: [ + adm0nameFilter + ] + } + }; + this.testClient.getWidget('adm0name', params, function (err, res, aggregation) { + assert.ok(!err, err); + assert.ok(aggregation); + assert.equal(aggregation.type, 'aggregation'); + + assert.equal(aggregation.categories.length, scenario.accept.length); + + var categoriesByCategory = aggregation.categories.reduce(function(byCategory, row) { + byCategory[row.category] = row; + return byCategory; + }, {}); + + var scenarioByCategory = scenario.accept.reduce(function(byCategory, category, index) { + byCategory[category] = { category: category, value: scenario.values[index], agg: false }; + return byCategory; + }, {}); + + Object.keys(categoriesByCategory).forEach(function(category) { + assert.deepEqual(categoriesByCategory[category], scenarioByCategory[category]); + }); + + done(); + }); + }); + }); + + var numericAggregationMapConfig = { + version: '1.5.0', + layers: [ + { + type: 'mapnik', + options: { + sql: 'select * from populated_places_simple_reduced', + cartocss: '#layer0 { marker-fill: red; marker-width: 10; }', + cartocss_version: '2.3.0', + widgets: { + scalerank: { + type: 'aggregation', + options: { + column: 'scalerank', + aggregation: 'count' + } + } + } + } + } + ] + }; + + ['1', 1].forEach(function(filterValue) { + it('can filter numeric categories: ' + (typeof filterValue), function(done) { + this.testClient = new TestClient(numericAggregationMapConfig); + var scalerankFilter = { + scalerank: { + accept: [filterValue] + } + }; + var params = { + own_filter: 1, + filters: { + layers: [scalerankFilter] + } + }; + this.testClient.getWidget('scalerank', params, function (err, res, aggregation) { + assert.ok(!err, err); + assert.ok(aggregation); + assert.equal(aggregation.type, 'aggregation'); + + assert.equal(aggregation.categories.length, 1); + assert.deepEqual(aggregation.categories[0], { category: '1', value: 179, agg: false }); + + done(); + }); + }); + }); + + describe('search', function() { + afterEach(function(done) { + if (this.testClient) { + this.testClient.drain(done); + } else { + done(); + } + }); + + ['1', 1].forEach(function(userQuery) { + it('can search numeric categories: ' + (typeof userQuery), function(done) { + this.testClient = new TestClient(numericAggregationMapConfig); + var scalerankFilter = { + scalerank: { + accept: [userQuery] + } + }; + var params = { + own_filter: 0, + filters: { + layers: [scalerankFilter] + } + }; + this.testClient.widgetSearch('scalerank', userQuery, params, function (err, res, searchResult) { + assert.ok(!err, err); + assert.ok(searchResult); + assert.equal(searchResult.type, 'aggregation'); + + assert.equal(searchResult.categories.length, 2); + assert.deepEqual( + searchResult.categories, + [{ category: 10, value: 515 }, { category: 1, value: 179 }] + ); + + done(); + }); + }); + }); + + var adm0name = 'Argentina'; + [adm0name, adm0name.toLowerCase(), adm0name.toUpperCase()].forEach(function(userQuery) { + it('should search with case insensitive: ' + userQuery, function(done) { + this.testClient = new TestClient(aggregationMapConfig); + this.testClient.widgetSearch('adm0name', userQuery, function (err, res, searchResult) { + assert.ok(!err, err); + assert.ok(searchResult); + assert.equal(searchResult.type, 'aggregation'); + + assert.equal(searchResult.categories.length, 1); + assert.deepEqual( + searchResult.categories, + [{ category:"Argentina", value:159 }] + ); + + done(); + }); + }); + }); + }); + + }); + +}); diff --git a/test/acceptance/widgets/ported/formula.js b/test/acceptance/widgets/ported/formula.js new file mode 100644 index 00000000..17d5c287 --- /dev/null +++ b/test/acceptance/widgets/ported/formula.js @@ -0,0 +1,89 @@ +require('../../../support/test_helper'); + +var assert = require('../../../support/assert'); +var TestClient = require('../../../support/test-client'); + +describe('widgets', function() { + + describe('formula', function() { + + afterEach(function(done) { + if (this.testClient) { + this.testClient.drain(done); + } else { + done(); + } + }); + + function widgetsMapConfig(widgets) { + return { + version: '1.5.0', + layers: [ + { + type: 'mapnik', + options: { + sql: 'select * from populated_places_simple_reduced where pop_max > 0 and pop_max < 600000', + cartocss: '#layer0 { marker-fill: red; marker-width: 10; }', + cartocss_version: '2.0.1', + widgets: widgets + } + } + ] + }; + } + + var operations = { + min: [10, 0], + max: [599579, 0], + count: [5822, 0], + avg: [112246.00893163861, 0], + sum: [653496264, 0] + }; + + Object.keys(operations).forEach(function(operation) { + it('should do ' + operation + ' over column', function(done) { + var widgets = { + pop_max_f: { + type: 'formula', + options: { + column: 'pop_max', + operation: operation + } + } + }; + this.testClient = new TestClient(widgetsMapConfig(widgets)); + this.testClient.getWidget('pop_max_f', function (err, res, result) { + assert.ok(!err, err); + assert.equal(result.operation, operation); + assert.equal(result.result, operations[operation][0]); + assert.equal(result.nulls, operations[operation][1]); + + done(); + }); + }); + }); + + it('does not require column for count formula', function(done) { + var operation = 'count'; + var widgets = { + pop_max_count_f: { + type: 'formula', + options: { + operation: operation + } + } + }; + this.testClient = new TestClient(widgetsMapConfig(widgets)); + this.testClient.getWidget('pop_max_count_f', function (err, res, result) { + assert.ok(!err, err); + assert.equal(result.operation, operation); + assert.equal(result.result, operations[operation][0]); + assert.equal(result.nulls, operations[operation][1]); + + done(); + }); + }); + + }); + +}); diff --git a/test/acceptance/widgets/ported/histogram.js b/test/acceptance/widgets/ported/histogram.js new file mode 100644 index 00000000..0f5c9d8e --- /dev/null +++ b/test/acceptance/widgets/ported/histogram.js @@ -0,0 +1,373 @@ +require('../../../support/test_helper'); + +var assert = require('../../../support/assert'); +var TestClient = require('../../../support/test-client'); + +describe('widgets', function() { + + describe('histograms', function() { + + afterEach(function(done) { + if (this.testClient) { + this.testClient.drain(done); + } else { + done(); + } + }); + + function histogramsMapConfig(widgets) { + return { + version: '1.5.0', + layers: [ + { + type: 'mapnik', + options: { + sql: 'select * from populated_places_simple_reduced', + cartocss: '#layer0 { marker-fill: red; marker-width: 10; }', + cartocss_version: '2.0.1', + widgets: widgets || { + scalerank: { + type: 'histogram', + options: { + column: 'scalerank' + } + }, + pop_max: { + type: 'histogram', + options: { + column: 'pop_max' + } + } + } + } + } + ] + }; + } + + it('can be fetched from a valid histogram', function(done) { + this.testClient = new TestClient(histogramsMapConfig()); + this.testClient.getWidget('scalerank', function (err, res, histogram) { + assert.ok(!err, err); + assert.ok(histogram); + assert.equal(histogram.type, 'histogram'); + validateHistogramBins(histogram); + + assert.ok(histogram.bins.length); + + assert.deepEqual(histogram.bins[0], { bin: 0, freq: 179, min: 1, max: 1, avg: 1 }); + + done(); + }); + }); + + it('can be fetched from a valid histogram', function(done) { + this.testClient = new TestClient(histogramsMapConfig()); + this.testClient.getWidget('pop_max', function (err, res, histogram) { + assert.ok(!err, err); + assert.ok(histogram); + assert.equal(histogram.type, 'histogram'); + validateHistogramBins(histogram); + + assert.ok(histogram.bins.length); + + assert.deepEqual( + histogram.bins[histogram.bins.length - 1], + { bin: 47, freq: 1, min: 35676000, max: 35676000, avg: 35676000 } + ); + + done(); + }); + }); + + it('can be fetched from a valid filtered histogram', function(done) { + this.testClient = new TestClient(histogramsMapConfig()); + var popMaxFilter = { + pop_max: { + min: 1e5, + max: 1e7 + } + }; + var params = { + own_filter: 1, + filters: { + layers: [popMaxFilter] + } + }; + this.testClient.getWidget('pop_max', params, function (err, res, histogram) { + assert.ok(!err, err); + assert.ok(histogram); + assert.equal(histogram.type, 'histogram'); + validateHistogramBins(histogram); + + assert.ok(histogram.bins.length); + + assert.deepEqual( + histogram.bins[histogram.bins.length - 1], + { bin: 7, min: 8829000, max: 9904000, avg: 9340914.714285715, freq: 7 } + ); + + done(); + }); + }); + + it('returns array with freq=0 entries for empty bins', function(done) { + var histogram20binsMapConfig = { + version: '1.5.0', + layers: [ + { + type: 'mapnik', + options: { + sql: 'select * from populated_places_simple_reduced', + cartocss: '#layer0 { marker-fill: red; marker-width: 10; }', + cartocss_version: '2.0.1', + widgets: { + pop_max: { + type: 'histogram', + options: { + column: 'pop_max' + } + } + } + } + } + ] + }; + + this.testClient = new TestClient(histogram20binsMapConfig); + this.testClient.getWidget('pop_max', { start: 0, end: 35676000, bins: 20 }, function (err, res, histogram) { + assert.ok(!err, err); + assert.equal(histogram.type, 'histogram'); + validateHistogramBins(histogram); + assert.ok(histogram.bins.length); + assert.deepEqual( + histogram.bins[histogram.bins.length - 1], + { bin: 19, freq: 1, min: 35676000, max: 35676000, avg: 35676000 } + ); + + var emptyBin = histogram.bins[18]; + assert.ok(!emptyBin); + + done(); + }); + }); + + it('can use a fixed number of bins', function(done) { + var fixedBinsHistogramMapConfig = histogramsMapConfig({ + pop_max: { + type: 'histogram', + options: { + column: 'pop_max' + } + } + }); + + this.testClient = new TestClient(fixedBinsHistogramMapConfig); + this.testClient.getWidget('pop_max', { bins: 5 }, function (err, res, histogram) { + assert.ok(!err, err); + assert.equal(histogram.type, 'histogram'); + + assert.equal(histogram.bins_count, 5); + + validateHistogramBins(histogram); + + assert.ok(histogram.bins.length); + assert.deepEqual( + histogram.bins[0], + { bin: 0, min: 0, max: 7067423, avg: 280820.0057731959, freq: 7275 } + ); + assert.deepEqual( + histogram.bins[histogram.bins.length - 1], + { bin: 4, freq: 1, min: 35676000, max: 35676000, avg: 35676000 } + ); + + done(); + }); + }); + + function validateHistogramBins(histogram) { + var binWidth = histogram.bin_width; + var start = histogram.bins_start; + var end = start + (histogram.bins_count * binWidth); + + var firstBin = histogram.bins[0]; + assert.equal(firstBin.min, start, + 'First bin does not match min and start ' + JSON.stringify({ + min: firstBin.min, + start: start + }) + ); + + var lastBin = histogram.bins[histogram.bins.length - 1]; + assert.equal(lastBin.max, end, + 'Last bin does not match max and end ' + JSON.stringify({ + max: lastBin.max, + end: end + }) + ); + + function getBinStartEnd(binIndex) { + return { + start: start + (binIndex * binWidth), + end: start + ((binIndex + 1) * binWidth) + }; + } + + histogram.bins.forEach(function(bin) { + var binStartEnd = getBinStartEnd(bin.bin); + + assert.ok(binStartEnd.start <= bin.min, + 'Bin start bigger than bin min ' + JSON.stringify({ + bin: bin.bin, + min: bin.min, + start: binStartEnd.start + }) + ); + + assert.ok(binStartEnd.end >= bin.max, + 'Bin end smaller than bin max ' + JSON.stringify({ + bin: bin.bin, + max: bin.max, + end: binStartEnd.end + }) + ); + + assert.ok(bin.avg >= bin.min && bin.avg <= bin.max, + 'Bin avg not between min and max values' + JSON.stringify({ + bin: bin.bin, + avg: bin.avg, + min: bin.min, + max: bin.max + }) + ); + }); + } + + describe('datetime column', function() { + afterEach(function(done) { + if (this.testClient) { + this.testClient.drain(done); + } else { + done(); + } + }); + + var updatedAtFilter = { + updated_at: { + min: 0 + } + }; + + it('can use a datetime column', function(done) { + this.testClient = new TestClient(histogramsMapConfig({ + updated_at: { + type: 'histogram', + options: { + column: 'updated_at' + } + } + })); + this.testClient.getWidget('updated_at', function (err, res, histogram) { + assert.ok(!err, err); + assert.ok(histogram); + assert.equal(histogram.type, 'histogram'); + + assert.ok(histogram.bins.length); + + done(); + }); + }); + + it('can use a datetime filtered column', function(done) { + this.testClient = new TestClient(histogramsMapConfig({ + updated_at: { + type: 'histogram', + options: { + column: 'updated_at' + } + } + })); + var params = { + own_filter: 1, + filters: { + layers: [updatedAtFilter] + } + }; + this.testClient.getWidget('updated_at', params, function (err, res, histogram) { + assert.ok(!err, err); + assert.ok(histogram); + assert.equal(histogram.type, 'histogram'); + + assert.ok(histogram.bins.length); + + done(); + }); + }); + + it('can getTile with datetime filtered column', function(done) { + this.testClient = new TestClient(histogramsMapConfig({ + updated_at: { + type: 'histogram', + options: { + column: 'updated_at' + } + } + })); + var params = { + own_filter: 1, + filters: { + layers: [updatedAtFilter] + } + }; + this.testClient.getTile(0, 0, 0, params, function (err, res, tile) { + assert.ok(!err, err); + assert.ok(tile); + + done(); + }); + }); + + it('can use two columns with different types', function(done) { + this.testClient = new TestClient(histogramsMapConfig({ + updated_at: { + type: 'histogram', + options: { + column: 'updated_at' + } + }, + pop_max: { + type: 'histogram', + options: { + column: 'pop_max' + } + } + })); + + var popMaxFilter = { + pop_max: { + max: 1e7 + } + }; + + var params = { + own_filter: 1, + filters: { + layers: [popMaxFilter] + } + }; + + this.testClient.getWidget('updated_at', params, function (err, res, histogram) { + assert.ok(!err, err); + assert.ok(histogram); + assert.equal(histogram.type, 'histogram'); + + assert.ok(histogram.bins.length); + + done(); + }); + }); + }); + + }); + +}); diff --git a/test/acceptance/widgets/ported/list.js b/test/acceptance/widgets/ported/list.js new file mode 100644 index 00000000..e33156f7 --- /dev/null +++ b/test/acceptance/widgets/ported/list.js @@ -0,0 +1,106 @@ +require('../../../support/test_helper'); + +var assert = require('../../../support/assert'); +var TestClient = require('../../../support/test-client'); +var _ = require('underscore'); + +describe('widgets', function() { + + describe('lists', function() { + + afterEach(function(done) { + if (this.testClient) { + this.testClient.drain(done); + } else { + done(); + } + }); + + function listsMapConfig(columns) { + return { + version: '1.5.0', + layers: [ + { + type: 'mapnik', + options: { + sql: 'select * from test_table', + cartocss: '#layer0 { marker-fill: red; marker-width: 10; }', + cartocss_version: '2.0.1', + widgets: { + places: { + type: 'list', + options: { + columns: columns || ['name', 'address'] + } + } + } + } + } + ] + }; + } + + var EXPECTED_NAMES = ['Hawai', 'El Estocolmo', 'El Rey del Tallarín', 'El Lacón', 'El Pico']; + + it('can be fetched from a valid list', function(done) { + var columns = ['name', 'address']; + this.testClient = new TestClient(listsMapConfig(columns)); + this.testClient.getWidget('places', function (err, res, list) { + assert.ok(!err, err); + assert.ok(list); + assert.equal(list.type, 'list'); + assert.equal(list.rows.length, 5); + + assert.ok(onlyHasFields(list, columns)); + + var names = list.rows.map(function (item) { + return item.name; + }); + assert.deepEqual(names, EXPECTED_NAMES); + + var expectedAddresses = [ + 'Calle de Pérez Galdós 9, Madrid, Spain', + 'Calle de la Palma 72, Madrid, Spain', + 'Plaza Conde de Toreno 2, Madrid, Spain', + 'Manuel Fernández y González 8, Madrid, Spain', + 'Calle Divino Pastor 12, Madrid, Spain' + ]; + var addresses = list.rows.map(function (item) { + return item.address; + }); + assert.deepEqual(addresses, expectedAddresses); + + done(); + }); + }); + + it('should fetch just one column', function(done) { + var columns = ['name']; + this.testClient = new TestClient(listsMapConfig(columns)); + this.testClient.getWidget('places', function (err, res, list) { + assert.ok(!err, err); + assert.ok(list); + assert.equal(list.type, 'list'); + assert.equal(list.rows.length, 5); + + assert.ok(onlyHasFields(list, columns)); + + var names = list.rows.map(function (item) { + return item.name; + }); + assert.deepEqual(names, EXPECTED_NAMES); + + done(); + }); + }); + + function onlyHasFields(list, expectedFields) { + var fields = (!!list.rows[0]) ? Object.keys(list.rows[0]) : []; + + return _.difference(fields, expectedFields).length === 0 && + _.difference(expectedFields, fields).length === 0; + } + + }); + +}); diff --git a/test/support/test-client.js b/test/support/test-client.js index e0fadeee..6aec4405 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); } ); @@ -82,9 +92,12 @@ TestClient.prototype.getWidget = function(widgetName, params, callback) { var urlParams = { own_filter: params.hasOwnProperty('own_filter') ? params.own_filter : 1 }; - if (params && params.bbox) { - urlParams.bbox = params.bbox; - } + ['bbox', 'bins', 'start', 'end'].forEach(function(extraParam) { + if (params.hasOwnProperty(extraParam)) { + urlParams[extraParam] = params[extraParam]; + } + }); + url = '/api/v1/map/' + layergroupId + '/0/widget/' + widgetName + '?' + qs.stringify(urlParams); assert.response(server, @@ -113,7 +126,120 @@ TestClient.prototype.getWidget = function(widgetName, params, callback) { function finish(err, res) { self.keysToDelete['map_cfg|' + LayergroupToken.parse(layergroupId).token] = 0; self.keysToDelete['user:localhost:mapviews:global'] = 5; - return callback(err, res); + var widget; + if (!err && res.body) { + widget = JSON.parse(res.body); + } + return callback(err, res, widget); + } + ); +}; + +TestClient.prototype.widgetSearch = function(widgetName, userQuery, params, callback) { + var self = this; + + if (!callback) { + callback = params; + params = {}; + } + + var url = '/api/v1/map'; + if (params && params.filters) { + url += '?' + qs.stringify({ filters: JSON.stringify(params.filters) }); + } + + var layergroupId; + step( + function createLayergroup() { + var next = this; + assert.response(server, + { + url: url, + method: 'POST', + headers: { + host: 'localhost', + 'Content-Type': 'application/json' + }, + data: JSON.stringify(self.mapConfig) + }, + { + status: 200, + headers: { + 'Content-Type': 'application/json; charset=utf-8' + } + }, + function(res, err) { + if (err) { + return next(err); + } + var parsedBody = JSON.parse(res.body); + + var expectedWidgetURLS = { + http: "/api/v1/map/" + parsedBody.layergroupid + "/0/widget/" + widgetName + }; + assert.ok(parsedBody.metadata.layers[0].widgets[widgetName]); + 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); + } + ); + }, + function getWidgetSearchResult(err, _layergroupId) { + assert.ifError(err); + + var next = this; + layergroupId = _layergroupId; + + var urlParams = { + q: userQuery, + own_filter: params.hasOwnProperty('own_filter') ? params.own_filter : 1 + }; + if (params && params.bbox) { + urlParams.bbox = params.bbox; + } + url = '/api/v1/map/' + layergroupId + '/0/widget/' + widgetName + '/search?' + qs.stringify(urlParams); + + assert.response(server, + { + url: url, + method: 'GET', + headers: { + host: 'localhost' + } + }, + { + status: 200, + headers: { + 'Content-Type': 'application/json; charset=utf-8' + } + }, + function(res, err) { + if (err) { + return next(err); + } + + next(null, res); + } + ); + }, + function finish(err, res) { + self.keysToDelete['map_cfg|' + LayergroupToken.parse(layergroupId).token] = 0; + self.keysToDelete['user:localhost:mapviews:global'] = 5; + var searchResult; + if (!err && res.body) { + searchResult = JSON.parse(res.body); + } + return callback(err, res, searchResult); } ); }; 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(); + }); + }); + }); + +}); diff --git a/test/unit/cartodb/model/filter/bbox-filters.test.js b/test/unit/cartodb/model/filter/bbox-filters.test.js new file mode 100644 index 00000000..df894892 --- /dev/null +++ b/test/unit/cartodb/model/filter/bbox-filters.test.js @@ -0,0 +1,129 @@ +require('../../../../support/test_helper'); +var util = require('util'); + +var assert = require('../../../../support/assert'); +var BboxFilter = require('../../../../../lib/cartodb/models/filter/bbox'); + +var MAX_EXTENT_MERCATOR_REF = [ + -BboxFilter.LONGITUDE_MAX_VALUE, + -BboxFilter.LATITUDE_MAX_VALUE, + BboxFilter.LONGITUDE_MAX_VALUE, + BboxFilter.LATITUDE_MAX_VALUE +]; + +describe('Bounding box filter', function() { + + describe('wrap longitude', function() { + var longitudesScenarios = [ + [[0, 90], [0, 90]], + [[-90, 0], [-90, 0]], + [[-90, 90], [-90, 90]], + [[-990, -720], [90, 360]], + [[810, 1080], [90, 360]], + [[-180, 180], [-180, 180]] + ]; + + longitudesScenarios.forEach(function(scenario) { + it(util.format('should adjust from %j to %j', scenario[0], scenario[1]), function() { + var we = BboxFilter.adjustLongitudeRange(scenario[0]); + + assert.equal( + we[0], scenario[1][0], + util.format('west, got %d, expected %d, scenario: %s', + we[1], scenario[1][1], JSON.stringify(scenario) + ) + ); + assert.equal( + we[1], scenario[1][1], + util.format('east, got %d, expected %d, scenario: %s', + we[1], scenario[1][1], JSON.stringify(scenario) + ) + ); + }); + }); + }); + + function createFilter(bbox) { + return new BboxFilter({}, { bbox: bbox.join(',') }); + } + + function createRef(bbox) { + return bbox; +// return mercator.forward([bbox[0], bbox[1]]).concat(mercator.forward([bbox[2], bbox[3]])); + } + + + it('happy case', function() { + var bbox = [-90, -45, 90, 45]; + var bboxFilter = createFilter(bbox); + assert.equal(bboxFilter.bboxes.length, 1); + assert.deepEqual(bboxFilter.bboxes[0], createRef(bbox)); + }); + + describe('latitude', function() { + it('(hardcoded) clipping out of bounds', function() { + var bbox = [-180, -90, 180, 90]; + var bboxFilter = createFilter(bbox); + assert.equal(bboxFilter.bboxes.length, 1); + assert.deepEqual(bboxFilter.bboxes[0], MAX_EXTENT_MERCATOR_REF); + }); + + it('clipping out of bounds', function() { + var bbox = [-180, -90, 180, 90]; + var bboxFilter = createFilter(bbox); + assert.equal(bboxFilter.bboxes.length, 1); + assert.deepEqual( + bboxFilter.bboxes[0], + createRef([-180, -BboxFilter.LATITUDE_MAX_VALUE, 180, BboxFilter.LATITUDE_MAX_VALUE]) + ); + }); + }); + + describe('longitude', function() { + it('generating multiple bbox for east out of bounds', function() { + var bbox = [90, -45, 360, 45]; + var bboxFilter = createFilter(bbox); + + assert.equal(bboxFilter.bboxes.length, 2, JSON.stringify([bboxFilter.bboxes, bbox])); + + assert.deepEqual( + bboxFilter.bboxes[0], + createRef([90, -45, 180, 45]) + ); + assert.deepEqual( + bboxFilter.bboxes[1], + createRef([-180, -45, 0, 45]) + ); + }); + + it('generating multiple bbox for east out of bounds', function() { + var bbox = [-270, -45, 0, 45]; + var bboxFilter = createFilter(bbox); + + assert.equal(bboxFilter.bboxes.length, 2); + + assert.deepEqual( + bboxFilter.bboxes[0], + createRef([90, -45, 180, 45]) + ); + assert.deepEqual( + bboxFilter.bboxes[1], + createRef([-180, -45, 0, 45]) + ); + }); + }); + + describe('out of bounds', function() { + it('wraps longitude', function () { + var bbox = [-190, -45, 190, 45]; + var bboxFilter = createFilter(bbox); + + assert.equal(bboxFilter.bboxes.length, 1); + + assert.deepEqual( + bboxFilter.bboxes[0], + createRef([-180, -45, 180, 45]) + ); + }); + }); +});