Compare commits

..

74 Commits

Author SHA1 Message Date
Raul Ochoa
ac296411d5 Release 2.46.0 2016-06-09 10:15:26 +02:00
Raul Ochoa
09cea4d6d4 Merge pull request #498 from CartoDB/upgrade-windshaft
Upgrades windshaft to 2.3.0
2016-06-08 19:45:53 +02:00
Raul Ochoa
382ff2416f Upgrades windshaft to 2.3.0 2016-06-08 19:14:48 +02:00
Raul Ochoa
27036379dd Merge pull request #494 from CartoDB/upgrade-windshaft
Upgrades windshaft to 2.2.0
2016-06-07 19:34:35 +02:00
Raul Ochoa
f4e6e140e0 Upgrades windshaft to 2.2.0 2016-06-07 19:26:41 +02:00
Raul Ochoa
b4e5cb88d9 Merge pull request #491 from CartoDB/issue-375
Sort start and end override params to correct bins width
2016-06-06 17:22:28 +02:00
Raul Ochoa
3269fef845 Sort start and end override params
Fixes #375
2016-06-06 17:10:52 +02:00
Raul Ochoa
e797719b41 Append url params for widgets 2016-06-06 17:10:36 +02:00
Raul Ochoa
284a8f2465 Deduplicate and skip falsy column names for geojson queries
Although Windshaft is already removing duplicates and skipping falsy
columns it's better to provide it with good input.

Closes #476
2016-06-06 15:58:16 +02:00
Raul Ochoa
54ea656da2 Merge pull request #490 from CartoDB/geojson-substitution-tokens
Upgrades windshaft to 2.1.0
2016-06-06 15:36:38 +02:00
Raul Ochoa
b4aaadf40b Upgrades windshaft to 2.1.0
Adds support for substitution tokens in geojson tiles

Fixes #484.
2016-06-06 15:29:58 +02:00
Raul Ochoa
74d2e3ef75 Merge pull request #489 from CartoDB/warn-about-deps
Warn on application start about non-matching dependencies
2016-06-06 12:11:38 +02:00
Raul Ochoa
b10e4c11d9 Warn on application start about non-matching dependencies 2016-06-06 12:07:43 +02:00
Raul Ochoa
075e141a9c Merge pull request #488 from CartoDB/upgrade-deps
Upgrades turbo-carto and camshaft deps
2016-06-06 12:04:56 +02:00
Raul Ochoa
04acf895f0 Upgrades turbo-carto and camshaft deps 2016-06-06 11:59:56 +02:00
Raul Ochoa
21608bf2e2 Merge pull request #487 from CartoDB/dataviews-adapter-fixes
Dataviews adapter working with non sql, non source layers
2016-06-06 11:56:31 +02:00
Raul Ochoa
653beb1952 Dataviews/widgets adapter working with non sql, non source, and non widgets layers
Ref #480
2016-06-06 11:46:27 +02:00
Raul Ochoa
050d33ff14 Use the_geom_webmercator; srid=3857 for the bounding box filter 2016-06-02 20:39:15 +02:00
Raul Ochoa
1ae86e039b Dataviews adapter: skip layers not containing SQL or widgets 2016-06-02 20:17:39 +02:00
Raul Ochoa
f75cadf6ba Dataviews adapter should work when there is a mix of layers with and without widgets 2016-06-02 19:51:16 +02:00
Raul Ochoa
93d0fe9176 Stubs next version 2016-06-02 16:21:40 +02:00
Raul Ochoa
6a15cd0566 Release 2.45.0 2016-06-02 16:19:39 +02:00
Raul Ochoa
614fe3f703 Update news and bump version 2016-06-02 14:45:00 +02:00
Raul Ochoa
82d4bb3046 Merge pull request #485 from CartoDB/mapconfig-dataviews-adapter
Mapconfig dataviews adapter
2016-06-02 14:42:22 +02:00
Raul Ochoa
f49c13b1b3 Do not apply twice metadata in anonymous maps 2016-06-02 14:28:32 +02:00
Raul Ochoa
828b817aca Append widgets metadata from mapconfig 2016-06-02 14:14:11 +02:00
Raul Ochoa
7f26f01743 Upgrade windshaft to 2.0.1 2016-06-02 13:38:05 +02:00
Raul Ochoa
50da63fc63 Upgrades windshaft to 2.0.0 2016-06-02 13:02:50 +02:00
Raul Ochoa
f8f6508449 Merge branch 'master' into mapconfig-dataviews-adapter
Conflicts:
	NEWS.md
	npm-shrinkwrap.json
2016-06-02 10:57:43 +02:00
Raul Ochoa
7256eb0935 Upgrade camshaft to 0.12.1 2016-06-02 10:54:23 +02:00
Raul Ochoa
cb08b42e54 Merge pull request #486 from CartoDB/upgrade-turbo-carto
Upgrades turbo-carto to 0.10.1
2016-06-01 19:43:58 +02:00
Raul Ochoa
9e7caeff94 Upgrades turbo-carto to 0.10.1 2016-06-01 19:32:17 +02:00
Raul Ochoa
e72a1d73be Geojson + column selection tests 2016-06-01 19:06:01 +02:00
Raul Ochoa
aaacad81e7 Add bbox unit tests 2016-06-01 19:05:46 +02:00
Raul Ochoa
55ee5b3b01 Ported histogram tests from windshaft 2016-06-01 15:03:18 +02:00
Raul Ochoa
94bf2748be Ignore errors coming from overviews adapter 2016-06-01 15:03:02 +02:00
Raul Ochoa
9a4aa7c1fa Add params to url if present in override option 2016-06-01 15:00:30 +02:00
Raul Ochoa
3e71365a95 Update camshaft to 0.12.0 2016-06-01 15:00:00 +02:00
Raul Ochoa
018ffcea7c List widget tests ported from windshaft 2016-06-01 11:51:31 +02:00
Raul Ochoa
e24ba9f495 Ported formula widget tests from windshaft 2016-06-01 11:48:37 +02:00
Raul Ochoa
0e2e069503 Remove empty line 2016-06-01 11:48:28 +02:00
Raul Ochoa
c4bbff3802 Tests for aggregation dataview ported from windshaft 2016-06-01 11:44:24 +02:00
Raul Ochoa
290054ef5d Add widget search support in test client 2016-06-01 11:43:19 +02:00
Raul Ochoa
4c25828540 Fix sql signature in agg, formula, and list dataviews 2016-06-01 11:42:24 +02:00
Javier Goizueta
5eda4888ed Stub next version 2016-06-01 10:51:36 +02:00
Javier Goizueta
7c322d9411 Release 2.44.1 2016-06-01 10:47:46 +02:00
Javier Goizueta
bd35d4e78a Merge pull request #468 from CartoDB/466-overviews-dataviews
Implement overviews support for all dataview types
2016-06-01 10:35:07 +02:00
Raul Ochoa
6eb711e70b Merge remote-tracking branch 'origin/master' into mapconfig-dataviews-adapter 2016-05-31 18:51:13 +02:00
Raul Ochoa
81ff0152c0 Merge pull request #481 from CartoDB/plpython-query-statements
Use plpython version of CDB_QueryStatements
2016-05-31 18:50:39 +02:00
Raul Ochoa
8a07f9f57e Create plpythonu extension 2016-05-31 18:45:43 +02:00
Raul Ochoa
ca367d0fe7 Use plpython version of CDB_QueryStatements 2016-05-31 18:39:03 +02:00
Raul Ochoa
cd7adbd792 Return a dataview/widget from response body 2016-05-31 18:20:16 +02:00
Raul Ochoa
5b76ec9f68 Merge remote-tracking branch 'origin/master' into mapconfig-dataviews-adapter 2016-05-31 17:14:28 +02:00
Raul Ochoa
bb21270aab Merge pull request #479 from CartoDB/improve-prepare_db
Improve prepare db
2016-05-31 17:14:11 +02:00
Raul Ochoa
22f3a54fbf Option to skip sql files download 2016-05-31 16:57:28 +02:00
Raul Ochoa
6644711969 Use a variable instead of a tmp file 2016-05-31 16:46:57 +02:00
Raul Ochoa
989df4a8a4 curl over all remote files at the same time 2016-05-31 16:42:42 +02:00
Javier Goizueta
d5423c88ea Replace use of the name widget by dataview for consistency 2016-05-31 15:30:38 +02:00
Javier Goizueta
5838b7a455 Remove debugging messages 2016-05-31 15:19:33 +02:00
Raul Ochoa
86e8cedfab All remote sql files together 2016-05-31 15:17:41 +02:00
Raul Ochoa
93c31c5433 Stubs next version 2016-05-31 10:35:25 +02:00
Raul Ochoa
b2d8f53a5c Merge branch 'master' into mapconfig-dataviews-adapter 2016-05-31 09:41:22 +02:00
Javier Goizueta
ef276bd51e Merge branch 'master' into 466-overviews-dataviews 2016-05-30 17:26:06 +02:00
Javier Goizueta
7ac3784f32 Increase the ratio used to select an overview level from a bounding box
This value would ideally be adjusted to prevent the grid size of the
overview used being greater that one pixel. So, this should be the
larger dimension of the map window in pixels.
2016-05-30 17:21:56 +02:00
Raul Ochoa
e12133e24b Merge remote-tracking branch 'origin/master' into mapconfig-dataviews-adapter 2016-05-27 15:35:29 +02:00
Raul Ochoa
f602ea88e2 Convert widgets from layers into dataviews
It also converts filters so full dataviews backend is reusable, that removes
widgets backend dependency.
2016-05-26 19:32:58 +02:00
Raul Ochoa
da6870cf1e Adds new adapter to transform widgets into dataviews 2016-05-26 11:57:55 +02:00
Javier Goizueta
c0a24108ba Implement overviews histogram dataviews 2016-05-23 18:11:10 +02:00
Javier Goizueta
ae9b8a0380 Remove comment 2016-05-23 18:10:46 +02:00
Javier Goizueta
f008c74419 Specific aggregation dataview implementation for overviews 2016-05-23 17:42:26 +02:00
Javier Goizueta
4a646d4700 Refactor overviews formula dataview 2016-05-23 17:20:04 +02:00
Javier Goizueta
657b262d92 Override all dataview types for overviews
All are using now the default behaviour defined in the base
class.
2016-05-23 17:06:52 +02:00
Javier Goizueta
988412fc07 Define default overviews dataview behaviour in base class 2016-05-23 16:53:28 +02:00
Javier Goizueta
5ba72b4894 Create base class for overviews dataviews 2016-05-23 14:18:45 +02:00
35 changed files with 3039 additions and 337 deletions

View File

@@ -1,9 +1,10 @@
sudo: false
addons:
postgresql: "9.3"
postgresql: "9.4"
apt:
packages:
- postgresql-plpython-9.4
- pkg-config
- libcairo2-dev
- libjpeg8-dev

42
NEWS.md
View File

@@ -1,5 +1,47 @@
# Changelog
## 2.46.0
Released 2016-06-09
Improvements:
- Support for substitution tokens in geojson tiles
- Warn on application start about non-matching dependencies
Announcements:
- Upgrades windshaft to [2.3.0](https://github.com/CartoDB/camshaft/releases/tag/2.3.0)
- Upgrades camshaft to [0.13.0](https://github.com/CartoDB/camshaft/releases/tag/0.13.0)
- Upgrades turbo-carto to [0.11.0](https://github.com/CartoDB/turbo-carto/releases/tag/0.11.0)
Bug fixes:
- Column provided for geojson renderer should not be null #476
- Dataviews/widgets adapter working with non sql, non source, and non widgets layers
## 2.45.0
Released 2016-06-02
Improvements:
- Removes Windshaft's widgets dependency.
- Makes widgets/dataviews endpoint compatible, but all using dataviews backend instead of widgets from Windshaft.
- Keeps adding widgets metadata in map instantiations for old clients.
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)
## 2.44.1
Released 2016-06-01
Improvements:
- Extend overviews support to histogram and aggregation dataviews
- Test improvements
## 2.44.0
Released 2016-05-31

View File

@@ -137,14 +137,14 @@ DataviewBackend.prototype.getDataview = function (mapConfigProvider, user, param
}
if (params.bbox) {
var bboxFilter = new BBoxFilter({column: 'the_geom', srid: 4326}, {bbox: params.bbox});
var bboxFilter = new BBoxFilter({column: 'the_geom_webmercator', srid: 3857}, {bbox: params.bbox});
query = bboxFilter.sql(query);
if ( queryRewriteData ) {
var bbox_filter_definition = {
type: 'bbox',
options: {
column: 'the_geom',
srid: 4326,
column: 'the_geom_webmercator',
srid: 3857
},
params: {
bbox: params.bbox

View File

@@ -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;

View File

@@ -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) {

View File

@@ -1,21 +1,13 @@
var windshaft = require('windshaft');
var HealthCheck = require('../monitoring/health_check');
var WELCOME_MSG = "This is the CartoDB Maps API, " +
"see the documentation at http://docs.cartodb.com/cartodb-platform/maps-api.html";
var versions = {
windshaft: windshaft.version,
grainstore: windshaft.grainstore.version(),
node_mapnik: windshaft.mapnik.version,
mapnik: windshaft.mapnik.versions.mapnik,
windshaft_cartodb: require('../../../package.json').version
};
function ServerInfoController() {
function ServerInfoController(versions) {
this.healthConfig = global.environment.health || {};
this.healthCheck = new HealthCheck(global.environment.disabled_file);
this.versions = versions || {};
}
module.exports = ServerInfoController;
@@ -31,7 +23,7 @@ ServerInfoController.prototype.welcome = function(req, res) {
};
ServerInfoController.prototype.version = function(req, res) {
res.status(200).send(versions);
res.status(200).send(this.versions);
};
ServerInfoController.prototype.health = function(req, res) {

View File

@@ -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 = {};

View File

@@ -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 = {};

View File

@@ -174,8 +174,8 @@ Histogram.prototype.sql = function(psql, override, callback) {
basicsQuery = overrideBasicsQueryTpl({
_query: _query,
_column: _column,
_start: override.start,
_end: override.end
_start: getBinStart(override),
_end: getBinEnd(override)
});
binsQuery = [
@@ -248,7 +248,7 @@ Histogram.prototype.format = function(result, override) {
width = firstRow.bin_width || width;
avg = firstRow.avg_val;
nulls = firstRow.nulls_count;
binsStart = override.hasOwnProperty('start') ? override.start : firstRow.min;
binsStart = override.hasOwnProperty('start') ? getBinStart(override) : firstRow.min;
buckets = result.rows.map(function(row) {
return _.omit(row, 'bins_number', 'bin_width', 'nulls_count', 'avg_val');
@@ -266,9 +266,19 @@ Histogram.prototype.format = function(result, override) {
};
function getBinStart(override) {
if (override.hasOwnProperty('start') && override.hasOwnProperty('end')) {
return Math.min(override.start, override.end);
}
return override.start || 0;
}
function getBinEnd(override) {
if (override.hasOwnProperty('start') && override.hasOwnProperty('end')) {
return Math.max(override.start, override.end);
}
return override.end || 0;
}
function getBinsCount(override) {
return override.bins || 0;
}

View File

@@ -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;
}

View File

@@ -0,0 +1,141 @@
var BaseOverviewsDataview = require('./base');
var BaseDataview = require('../aggregation');
var dot = require('dot');
dot.templateSettings.strip = false;
var summaryQueryTpl = dot.template([
'summary AS (',
' SELECT',
' sum(_feature_count) AS count,',
' sum(CASE WHEN {{=it._column}} IS NULL THEN 1 ELSE 0 END) AS nulls_count',
' FROM ({{=it._query}}) _cdb_aggregation_nulls',
')'
].join('\n'));
var rankedCategoriesQueryTpl = dot.template([
'categories AS(',
' SELECT {{=it._column}} AS category, {{=it._aggregation}} AS value,',
' row_number() OVER (ORDER BY {{=it._aggregation}} desc) as rank',
' FROM ({{=it._query}}) _cdb_aggregation_all',
' GROUP BY {{=it._column}}',
' ORDER BY 2 DESC',
')'
].join('\n'));
var categoriesSummaryQueryTpl = dot.template([
'categories_summary AS(',
' SELECT count(1) categories_count, max(value) max_val, min(value) min_val',
' FROM categories',
')'
].join('\n'));
var rankedAggregationQueryTpl = dot.template([
'SELECT CAST(category AS text), value, false as agg, nulls_count, min_val, max_val, count, categories_count',
' FROM categories, summary, categories_summary',
' WHERE rank < {{=it._limit}}',
'UNION ALL',
'SELECT \'Other\' category, sum(value), true as agg, nulls_count, min_val, max_val, count, categories_count',
' FROM categories, summary, categories_summary',
' WHERE rank >= {{=it._limit}}',
'GROUP BY nulls_count, min_val, max_val, count, categories_count'
].join('\n'));
var aggregationQueryTpl = dot.template([
'SELECT CAST({{=it._column}} AS text) AS category, {{=it._aggregation}} AS value, false as agg,',
' nulls_count, min_val, max_val, count, categories_count',
'FROM ({{=it._query}}) _cdb_aggregation_all, summary, categories_summary',
'GROUP BY category, nulls_count, min_val, max_val, count, categories_count',
'ORDER BY value DESC'
].join('\n'));
var CATEGORIES_LIMIT = 6;
function Aggregation(query, options, queryRewriter, queryRewriteData, params) {
BaseOverviewsDataview.call(this, query, options, BaseDataview, queryRewriter, queryRewriteData, params);
this.query = query;
this.column = options.column;
this.aggregation = options.aggregation;
this.aggregationColumn = options.aggregationColumn;
}
Aggregation.prototype = Object.create(BaseOverviewsDataview.prototype);
Aggregation.prototype.constructor = Aggregation;
module.exports = Aggregation;
Aggregation.prototype.sql = function(psql, filters, override, callback) {
if (!callback) {
callback = override;
override = {};
}
var _query = this.rewrittenQuery(this.query);
var aggregationSql;
if (!!override.ownFilter) {
aggregationSql = [
"WITH",
[
summaryQueryTpl({
_query: _query,
_column: this.column
}),
rankedCategoriesQueryTpl({
_query: _query,
_column: this.column,
_aggregation: this.getAggregationSql()
}),
categoriesSummaryQueryTpl({
_query: _query,
_column: this.column
})
].join(',\n'),
aggregationQueryTpl({
_query: _query,
_column: this.column,
_aggregation: this.getAggregationSql(),
_limit: CATEGORIES_LIMIT
})
].join('\n');
} else {
aggregationSql = [
"WITH",
[
summaryQueryTpl({
_query: _query,
_column: this.column
}),
rankedCategoriesQueryTpl({
_query: _query,
_column: this.column,
_aggregation: this.getAggregationSql()
}),
categoriesSummaryQueryTpl({
_query: _query,
_column: this.column
})
].join(',\n'),
rankedAggregationQueryTpl({
_query: _query,
_column: this.column,
_limit: CATEGORIES_LIMIT
})
].join('\n');
}
return callback(null, aggregationSql);
};
var aggregationFnQueryTpl = {
count: dot.template('sum(_feature_count)'),
sum: dot.template('sum({{=it._aggregationColumn}}*_feature_count)')
};
Aggregation.prototype.getAggregationSql = function() {
return aggregationFnQueryTpl[this.aggregation]({
_aggregationFn: this.aggregation,
_aggregationColumn: this.aggregationColumn || 1
});
};

View File

@@ -0,0 +1,88 @@
var _ = require('underscore');
var BaseDataview = require('../base');
function BaseOverviewsDataview(query, queryOptions, BaseDataview, queryRewriter, queryRewriteData, options) {
this.BaseDataview = BaseDataview;
this.query = query;
this.queryOptions = queryOptions;
this.queryRewriter = queryRewriter;
this.queryRewriteData = queryRewriteData;
this.options = options;
this.baseDataview = new this.BaseDataview(this.query, this.queryOptions);
}
module.exports = BaseOverviewsDataview;
BaseOverviewsDataview.prototype = new BaseDataview();
BaseOverviewsDataview.prototype.constructor = BaseOverviewsDataview;
// TODO: parameterized these settings
var SETTINGS = {
// use overviews as a default fallback strategy
defaultOverviews: false,
// minimum ratio of bounding box size to grid size
// (this would ideally be based on the viewport size in pixels)
zoomLevelFactor: 1024.0
};
// Compute zoom level so that the the resolution grid size of the
// selected overview is smaller (zoomLevelFactor times smaller at least)
// than the bounding box size.
BaseOverviewsDataview.prototype.zoomLevelForBbox = function(bbox) {
var pxPerTile = 256.0;
var earthWidth = 360.0;
// TODO: now we assume overviews are computed for 1-pixel tolerance;
// should use extended overviews metadata to compute this properly.
if ( bbox ) {
var bboxValues = _.map(bbox.split(','), function(v) { return +v; });
var w = Math.abs(bboxValues[2]-bboxValues[0]);
var h = Math.abs(bboxValues[3]-bboxValues[1]);
var maxDim = Math.min(w, h);
// Find minimum suitable z
// note that the QueryRewirter will use the minimum level overview
// of level >= z if it exists, and otherwise the base table
var z = Math.ceil(-Math.log(maxDim*pxPerTile/earthWidth/SETTINGS.zoomLevelFactor)/Math.log(2.0));
return Math.max(z, 0);
}
return 0;
};
BaseOverviewsDataview.prototype.rewrittenQuery = function(query) {
var zoom_level = this.zoomLevelForBbox(this.options.bbox);
return this.queryRewriter.query(query, this.queryRewriteData, { zoom_level: zoom_level });
};
// Default behaviour
BaseOverviewsDataview.prototype.defaultSql = function(psql, filters, override, callback) {
var query = this.query;
var dataview = this.baseDataview;
if ( SETTINGS.defaultOverviews ) {
query = this.rewrittenQuery(query);
dataview = new this.BaseDataview(query, this.queryOptions);
}
return dataview.sql(psql, filters, override, callback);
};
// default implementation that can be override in derived classes:
BaseOverviewsDataview.prototype.sql = function(psql, filters, override, callback) {
return this.defaultSql(psql, filters, override, callback);
};
BaseOverviewsDataview.prototype.search = function(psql, userQuery, callback) {
return this.baseDataview.search(psql, userQuery, callback);
};
BaseOverviewsDataview.prototype.format = function(result) {
return this.baseDataview.format(result);
};
BaseOverviewsDataview.prototype.getType = function() {
return this.baseDataview.getType();
};
BaseOverviewsDataview.prototype.toString = function() {
return this.baseDataview.toString();
};

View File

@@ -1,9 +1,6 @@
var _ = require('underscore');
var BaseWidget = require('../base');
var BaseOverviewsDataview = require('./base');
var BaseDataview = require('../formula');
var debug = require('debug')('windshaft:widget:formula:overviews');
var dot = require('dot');
dot.templateSettings.strip = false;
@@ -29,76 +26,31 @@ var formulaQueryTpls = {
};
function Formula(query, options, queryRewriter, queryRewriteData, params) {
this.base_dataview = new BaseDataview(query, options);
this.query = query;
BaseOverviewsDataview.call(this, query, options, BaseDataview, queryRewriter, queryRewriteData, params);
this.column = options.column || '1';
this.operation = options.operation;
this.queryRewriter = queryRewriter;
this.queryRewriteData = queryRewriteData;
this.options = params;
}
Formula.prototype = new BaseWidget();
Formula.prototype = Object.create(BaseOverviewsDataview.prototype);
Formula.prototype.constructor = Formula;
module.exports = Formula;
var zoom_level_factor = 100.0;
// Compute zoom level so that the the resolution grid size of the
// selected overview is smaller (zoom_level_factor times smaller at least)
// than the bounding box size.
function zoom_level_for_bbox(bbox) {
var px_per_tile = 256.0;
var earth_width = 360.0;
// TODO: now we assume overviews are computed for 1-pixel tolerance;
// should use extended overviews metadata to compute this properly.
if ( bbox ) {
var bbox_values = _.map(bbox.split(','), function(v) { return +v; });
var w = Math.abs(bbox_values[2]-bbox_values[0]);
var h = Math.abs(bbox_values[3]-bbox_values[1]);
var max_dim = Math.min(w, h);
// Find minimum suitable z
// note that the QueryRewirter will use the minimum level overview
// of level >= z if it exists, and otherwise the base table
var z = Math.ceil(-Math.log(max_dim*px_per_tile/earth_width/zoom_level_factor)/Math.log(2.0));
return Math.max(z, 0);
}
return 0;
}
Formula.prototype.sql = function(psql, filters, override, callback) {
var _query = this.query;
var formulaQueryTpl = formulaQueryTpls[this.operation];
if ( formulaQueryTpl ) {
// supported formula for use with overviews
var zoom_level = zoom_level_for_bbox(this.options.bbox);
_query = this.queryRewriter.query(_query, this.queryRewriteData, { zoom_level: zoom_level });
var formulaSql = formulaQueryTpl({
_query: _query,
_query: this.rewrittenQuery(this.query),
_operation: this.operation,
_column: this.column
_column: this.column
});
debug(formulaSql);
callback = callback || override;
return callback(null, formulaSql);
}
// For non supported operations (min, max) we're not using overviews.
return this.base_dataview.sql(psql, filters, override, callback);
};
Formula.prototype.format = function(result) {
return this.base_dataview.format(result);
};
Formula.prototype.getType = function() {
return this.base_dataview.getType();
};
Formula.prototype.toString = function() {
return this.base_dataview.toString();
// default behaviour
return this.defaultSql(psql, filters, override, callback);
};

View File

@@ -0,0 +1,217 @@
var _ = require('underscore');
var BaseOverviewsDataview = require('./base');
var BaseDataview = require('../histogram');
var dot = require('dot');
dot.templateSettings.strip = false;
var columnTypeQueryTpl = dot.template(
'SELECT pg_typeof({{=it.column}})::oid FROM ({{=it.query}}) _cdb_histogram_column_type limit 1'
);
var columnCastTpl = dot.template("date_part('epoch', {{=it.column}})");
var BIN_MIN_NUMBER = 6;
var BIN_MAX_NUMBER = 48;
var basicsQueryTpl = dot.template([
'basics AS (',
' SELECT',
' max({{=it._column}}) AS max_val, min({{=it._column}}) AS min_val,',
' sum({{=it._column}}*_feature_count)/sum(_feature_count) AS avg_val, sum(_feature_count) AS total_rows',
' FROM ({{=it._query}}) _cdb_basics',
')'
].join(' \n'));
var overrideBasicsQueryTpl = dot.template([
'basics AS (',
' SELECT',
' max({{=it._end}}) AS max_val, min({{=it._start}}) AS min_val,',
' sum({{=it._column}}*_feature_count)/sum(_feature_count) AS avg_val, sum(_feature_count) AS total_rows',
' FROM ({{=it._query}}) _cdb_basics',
')'
].join('\n'));
var iqrQueryTpl = dot.template([
'iqrange AS (',
' SELECT max(quartile_max) - min(quartile_max) AS iqr',
' FROM (',
' SELECT quartile, max(_cdb_iqr_column) AS quartile_max from (',
' SELECT {{=it._column}} AS _cdb_iqr_column, ntile(4) over (order by {{=it._column}}',
' ) AS quartile',
' FROM ({{=it._query}}) _cdb_rank) _cdb_quartiles',
' WHERE quartile = 1 or quartile = 3',
' GROUP BY quartile',
' ) _cdb_iqr',
')'
].join('\n'));
var binsQueryTpl = dot.template([
'bins AS (',
' SELECT CASE WHEN total_rows = 0 OR iqr = 0',
' THEN 1',
' ELSE GREATEST(',
' LEAST({{=it._minBins}}, CAST(total_rows AS INT)),',
' LEAST(',
' CAST(((max_val - min_val) / (2 * iqr * power(total_rows, 1/3))) AS INT),',
' {{=it._maxBins}}',
' )',
' )',
' END AS bins_number',
' FROM basics, iqrange, ({{=it._query}}) _cdb_bins',
' LIMIT 1',
')'
].join('\n'));
var overrideBinsQueryTpl = dot.template([
'bins AS (',
' SELECT {{=it._bins}} AS bins_number',
')'
].join('\n'));
var nullsQueryTpl = dot.template([
'nulls AS (',
' SELECT',
' count(*) AS nulls_count',
' FROM ({{=it._query}}) _cdb_histogram_nulls',
' WHERE {{=it._column}} IS NULL',
')'
].join('\n'));
var histogramQueryTpl = dot.template([
'SELECT',
' (max_val - min_val) / cast(bins_number as float) AS bin_width,',
' bins_number,',
' nulls_count,',
' avg_val,',
' CASE WHEN min_val = max_val',
' THEN 0',
' ELSE GREATEST(1, LEAST(WIDTH_BUCKET({{=it._column}}, min_val, max_val, bins_number), bins_number)) - 1',
' END AS bin,',
' min({{=it._column}})::numeric AS min,',
' max({{=it._column}})::numeric AS max,',
' sum({{=it._column}}*_feature_count)/sum(_feature_count)::numeric AS avg,',
' sum(_feature_count) AS freq',
'FROM ({{=it._query}}) _cdb_histogram, basics, nulls, bins',
'WHERE {{=it._column}} IS NOT NULL',
'GROUP BY bin, bins_number, bin_width, nulls_count, avg_val',
'ORDER BY bin'
].join('\n'));
function Histogram(query, options, queryRewriter, queryRewriteData, params) {
BaseOverviewsDataview.call(this, query, options, BaseDataview, queryRewriter, queryRewriteData, params);
this.query = query;
this.column = options.column;
this.bins = options.bins;
this._columnType = null;
}
Histogram.prototype = Object.create(BaseOverviewsDataview.prototype);
Histogram.prototype.constructor = Histogram;
module.exports = Histogram;
var DATE_OIDS = {
1082: true,
1114: true,
1184: true
};
Histogram.prototype.sql = function(psql, override, callback) {
if (!callback) {
callback = override;
override = {};
}
var self = this;
var _column = this.column;
var columnTypeQuery = columnTypeQueryTpl({
column: _column, query: this.rewrittenQuery(this.query)
});
if (this._columnType === null) {
psql.query(columnTypeQuery, function(err, result) {
// assume numeric, will fail later
self._columnType = 'numeric';
if (!err && !!result.rows[0]) {
var pgType = result.rows[0].pg_typeof;
if (DATE_OIDS.hasOwnProperty(pgType)) {
self._columnType = 'date';
}
}
self.sql(psql, override, callback);
}, true); // use read-only transaction
return null;
}
if (this._columnType === 'date') {
_column = columnCastTpl({column: _column});
}
var _query = this.rewrittenQuery(this.query);
var basicsQuery, binsQuery;
if (override && _.has(override, 'start') && _.has(override, 'end') && _.has(override, 'bins')) {
basicsQuery = overrideBasicsQueryTpl({
_query: _query,
_column: _column,
_start: override.start,
_end: override.end
});
binsQuery = [
overrideBinsQueryTpl({
_bins: override.bins
})
].join(',\n');
} else {
basicsQuery = basicsQueryTpl({
_query: _query,
_column: _column
});
if (override && _.has(override, 'bins')) {
binsQuery = [
overrideBinsQueryTpl({
_bins: override.bins
})
].join(',\n');
} else {
binsQuery = [
iqrQueryTpl({
_query: _query,
_column: _column
}),
binsQueryTpl({
_query: _query,
_minBins: BIN_MIN_NUMBER,
_maxBins: BIN_MAX_NUMBER
})
].join(',\n');
}
}
var histogramSql = [
"WITH",
[
basicsQuery,
binsQuery,
nullsQueryTpl({
_query: _query,
_column: _column
})
].join(',\n'),
histogramQueryTpl({
_query: _query,
_column: _column
})
].join('\n');
return callback(null, histogramSql);
};

View File

@@ -1,3 +1,6 @@
module.exports = {
Formula: require('./formula')
Aggregation: require('./aggregation'),
Formula: require('./formula'),
Histogram: require('./histogram'),
List: require('./list')
};

View File

@@ -0,0 +1,11 @@
var BaseOverviewsDataview = require('./base');
var BaseDataview = require('../list');
function List(query, options, queryRewriter, queryRewriteData, params) {
BaseOverviewsDataview.call(this, query, options, BaseDataview, queryRewriter, queryRewriteData, params);
}
List.prototype = Object.create(BaseOverviewsDataview.prototype);
List.prototype.constructor = List;
module.exports = List;

View File

@@ -101,10 +101,7 @@ AnalysisMapConfigAdapter.prototype.getMapConfig = function(user, requestMapConfi
analysisSql = sqlQueryWrap.replace(/<%=\s*sql\s*%>/g, analysisSql);
}
layer.options.sql = analysisSql;
var layerDataviews = getLayerDataviews(layer, dataviews);
layer.options.columns = layerDataviews.reduce(function(columns, dataview) {
return columns.concat(getDataviewColumns(dataview));
}, []);
layer.options.columns = getDataviewsColumns(getLayerDataviews(layer, dataviews));
} else {
missingNodesErrors.push(
new Error('Missing analysis node.id="' + layerSourceId +'" for layer='+layerIndex)
@@ -208,11 +205,22 @@ function getLayerDataviews(layer, dataviews) {
return layerDataviews;
}
function getDataviewsColumns(dataviews) {
return Object.keys(dataviews.reduce(function(columnsDict, dataview) {
getDataviewColumns(dataview).forEach(function(columnName) {
if (!!columnName) {
columnsDict[columnName] = true;
}
});
return columnsDict;
}, {}));
}
function getDataviewColumns(dataview) {
var columns = [];
var options = dataview.options;
['column', 'aggregationColumn'].forEach(function(opt) {
if (options.hasOwnProperty(opt)) {
if (options.hasOwnProperty(opt) && !!options[opt]) {
columns.push(options[opt]);
}
});

View File

@@ -0,0 +1,98 @@
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);
if (!layer.options.widgets) {
return;
}
if (!layerSourceId && !layer.options.sql) {
return;
}
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 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;
}

View File

@@ -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);
}
);
}

View File

@@ -3,7 +3,6 @@ var bodyParser = require('body-parser');
var RedisPool = require('redis-mpool');
var cartodbRedis = require('cartodb-redis');
var _ = require('underscore');
var debug = require('debug')('windshaft:cartodb');
var controller = require('./controllers');
@@ -41,6 +40,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 +159,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)
@@ -181,6 +182,8 @@ module.exports = function(serverOptions) {
var TablesExtentApi = require('./api/tables_extent_api');
var tablesExtentApi = new TablesExtentApi(pgQueryRunner);
var versions = getAndValidateVersions(serverOptions);
/*******************************************************************************************************************
* Routing
******************************************************************************************************************/
@@ -192,7 +195,6 @@ module.exports = function(serverOptions) {
tileBackend,
previewBackend,
attributesBackend,
new windshaft.backend.Widget(),
surrogateKeysCache,
userLimitsApi,
layergroupAffectedTablesCache,
@@ -224,7 +226,7 @@ module.exports = function(serverOptions) {
new controller.NamedMapsAdmin(authApi, pgConnection, templateMaps).register(app);
new controller.ServerInfo().register(app);
new controller.ServerInfo(versions).register(app);
/*******************************************************************************************************************
* END Routing
@@ -237,12 +239,45 @@ function validateOptions(opts) {
if (!_.isString(opts.base_url) || !_.isString(opts.base_url_mapconfig) || !_.isString(opts.base_url_templated)) {
throw new Error("Must initialise server with: 'base_url'/'base_url_mapconfig'/'base_url_templated' URLs");
}
}
// Be nice and warn if configured mapnik version is != instaled mapnik version
if (mapnik.versions.mapnik !== opts.grainstore.mapnik_version) {
debug('WARNING: detected mapnik version (' + mapnik.versions.mapnik + ')' +
' != configured mapnik version (' + opts.grainstore.mapnik_version + ')');
function getAndValidateVersions(options) {
// jshint undef:false
var warn = console.warn.bind(console);
// jshint undef:true
var packageDefinition = require('../../package.json');
var declaredDependencies = packageDefinition.dependencies || {};
var installedDependenciesVersions = {
camshaft: require('camshaft').version,
grainstore: windshaft.grainstore.version(),
mapnik: windshaft.mapnik.versions.mapnik,
node_mapnik: windshaft.mapnik.version,
'turbo-carto': require('turbo-carto').version,
windshaft: windshaft.version,
windshaft_cartodb: packageDefinition.version
};
var dependenciesToValidate = ['camshaft', 'turbo-carto', 'windshaft'];
dependenciesToValidate.forEach(function(depName) {
var declaredDependencyVersion = declaredDependencies[depName];
var installedDependencyVersion = installedDependenciesVersions[depName];
if (declaredDependencyVersion !== installedDependencyVersion) {
warn(
'Dependency="%s" installed version="%s" does not match declared version="%s". Check your installation.',
depName, installedDependencyVersion, declaredDependencyVersion
);
}
});
// Be nice and warn if configured mapnik version is != installed mapnik version
if (mapnik.versions.mapnik !== options.grainstore.mapnik_version) {
warn('WARNING: detected mapnik version (' + mapnik.versions.mapnik + ')' +
' != configured mapnik version (' + options.grainstore.mapnik_version + ')');
}
return installedDependenciesVersions;
}
function bootstrapFonts(opts) {

243
npm-shrinkwrap.json generated
View File

@@ -1,6 +1,6 @@
{
"name": "windshaft-cartodb",
"version": "2.44.0",
"version": "2.46.0",
"dependencies": {
"body-parser": {
"version": "1.14.2",
@@ -73,14 +73,14 @@
},
"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"
}
}
},
"type-is": {
"version": "1.6.13",
"from": "type-is@>=1.6.6 <1.7.0",
"from": "type-is@>=1.6.10 <1.7.0",
"resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.13.tgz",
"dependencies": {
"media-typer": {
@@ -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.13.0",
"from": "camshaft@0.13.0",
"resolved": "https://registry.npmjs.org/camshaft/-/camshaft-0.13.0.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": {
@@ -146,7 +146,7 @@
},
"inherits": {
"version": "2.0.1",
"from": "inherits@>=2.0.1 <2.1.0",
"from": "inherits@>=2.0.0 <3.0.0",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz"
},
"isarray": {
@@ -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",
@@ -757,13 +757,54 @@
}
},
"serve-static": {
"version": "1.10.2",
"version": "1.10.3",
"from": "serve-static@>=1.10.2 <1.11.0",
"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.10.2.tgz"
"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.10.3.tgz",
"dependencies": {
"send": {
"version": "0.13.2",
"from": "send@0.13.2",
"resolved": "https://registry.npmjs.org/send/-/send-0.13.2.tgz",
"dependencies": {
"destroy": {
"version": "1.0.4",
"from": "destroy@>=1.0.4 <1.1.0",
"resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz"
},
"http-errors": {
"version": "1.3.1",
"from": "http-errors@>=1.3.1 <1.4.0",
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.3.1.tgz",
"dependencies": {
"inherits": {
"version": "2.0.1",
"from": "inherits@>=2.0.1 <2.1.0",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz"
}
}
},
"mime": {
"version": "1.3.4",
"from": "mime@1.3.4",
"resolved": "https://registry.npmjs.org/mime/-/mime-1.3.4.tgz"
},
"ms": {
"version": "0.7.1",
"from": "ms@0.7.1",
"resolved": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz"
},
"statuses": {
"version": "1.2.1",
"from": "statuses@>=1.2.1 <1.3.0",
"resolved": "https://registry.npmjs.org/statuses/-/statuses-1.2.1.tgz"
}
}
}
}
},
"type-is": {
"version": "1.6.13",
"from": "type-is@>=1.6.6 <1.7.0",
"from": "type-is@>=1.6.10 <1.7.0",
"resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.13.tgz",
"dependencies": {
"media-typer": {
@@ -976,7 +1017,7 @@
"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"
}
}
@@ -1216,9 +1257,9 @@
"resolved": "https://registry.npmjs.org/step-profiler/-/step-profiler-0.3.0.tgz"
},
"turbo-carto": {
"version": "0.10.0",
"from": "turbo-carto@0.10.0",
"resolved": "https://registry.npmjs.org/turbo-carto/-/turbo-carto-0.10.0.tgz",
"version": "0.11.0",
"from": "turbo-carto@0.11.0",
"resolved": "https://registry.npmjs.org/turbo-carto/-/turbo-carto-0.11.0.tgz",
"dependencies": {
"colorbrewer": {
"version": "1.0.0",
@@ -1277,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.3.0",
"from": "windshaft@2.3.0",
"resolved": "https://registry.npmjs.org/windshaft/-/windshaft-2.3.0.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",
@@ -1328,9 +1369,9 @@
}
},
"grainstore": {
"version": "1.1.1",
"from": "grainstore@1.1.1",
"resolved": "https://registry.npmjs.org/grainstore/-/grainstore-1.1.1.tgz",
"version": "1.2.0",
"from": "grainstore@1.2.0",
"resolved": "https://registry.npmjs.org/grainstore/-/grainstore-1.2.0.tgz",
"dependencies": {
"carto": {
"version": "0.9.5-cdb2",
@@ -2916,9 +2957,9 @@
"resolved": "https://registry.npmjs.org/sqlite3/-/sqlite3-3.1.4.tgz",
"dependencies": {
"nan": {
"version": "2.3.3",
"version": "2.3.5",
"from": "nan@>=2.3.3 <2.4.0",
"resolved": "https://registry.npmjs.org/nan/-/nan-2.3.3.tgz"
"resolved": "https://registry.npmjs.org/nan/-/nan-2.3.5.tgz"
},
"node-pre-gyp": {
"version": "0.6.28",
@@ -2955,16 +2996,16 @@
"from": "asn1@>=0.2.3 <0.3.0",
"resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.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"
},
"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"
},
"aws-sign2": {
"version": "0.6.0",
"from": "aws-sign2@>=0.6.0 <0.7.0",
@@ -2995,16 +3036,16 @@
"from": "brace-expansion@>=1.0.0 <2.0.0",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.4.tgz"
},
"chalk": {
"version": "1.1.3",
"from": "chalk@>=1.1.1 <2.0.0",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz"
},
"caseless": {
"version": "0.11.0",
"from": "caseless@>=0.11.0 <0.12.0",
"resolved": "https://registry.npmjs.org/caseless/-/caseless-0.11.0.tgz"
},
"chalk": {
"version": "1.1.3",
"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",
@@ -3070,16 +3111,16 @@
"from": "extsprintf@1.0.2",
"resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.0.2.tgz"
},
"form-data": {
"version": "1.0.0-rc4",
"from": "form-data@>=1.0.0-rc3 <1.1.0",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-1.0.0-rc4.tgz"
},
"forever-agent": {
"version": "0.6.1",
"from": "forever-agent@>=0.6.1 <0.7.0",
"resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz"
},
"form-data": {
"version": "1.0.0-rc4",
"from": "form-data@>=1.0.0-rc3 <1.1.0",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-1.0.0-rc4.tgz"
},
"fstream": {
"version": "1.0.9",
"from": "fstream@>=1.0.2 <2.0.0",
@@ -3095,16 +3136,16 @@
"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",
@@ -3130,16 +3171,16 @@
"from": "has-ansi@>=2.0.0 <3.0.0",
"resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-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"
},
"hawk": {
"version": "3.1.3",
"from": "hawk@>=3.1.3 <3.2.0",
"resolved": "https://registry.npmjs.org/hawk/-/hawk-3.1.3.tgz"
},
"hoek": {
"version": "2.16.3",
"from": "hoek@>=2.0.0 <3.0.0",
@@ -3195,16 +3236,16 @@
"from": "jodid25519@>=1.0.0 <2.0.0",
"resolved": "https://registry.npmjs.org/jodid25519/-/jodid25519-1.0.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"
},
"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-stringify-safe": {
"version": "5.0.1",
"from": "json-stringify-safe@>=5.0.1 <5.1.0",
@@ -3265,16 +3306,16 @@
"from": "minimatch@>=2.0.0 <3.0.0||>=3.0.0 <4.0.0",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.0.tgz"
},
"mkdirp": {
"version": "0.5.1",
"from": "mkdirp@>=0.5.0 <0.6.0",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz"
},
"minimist": {
"version": "0.0.8",
"from": "minimist@0.0.8",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz"
},
"mkdirp": {
"version": "0.5.1",
"from": "mkdirp@>=0.5.0 <0.6.0",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz"
},
"ms": {
"version": "0.7.1",
"from": "ms@0.7.1",
@@ -3330,16 +3371,16 @@
"from": "qs@>=6.1.0 <6.2.0",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.1.0.tgz"
},
"request": {
"version": "2.72.0",
"from": "request@>=2.0.0 <3.0.0",
"resolved": "https://registry.npmjs.org/request/-/request-2.72.0.tgz"
},
"readable-stream": {
"version": "2.1.2",
"from": "readable-stream@>=2.0.0 <3.0.0||>=1.1.13 <2.0.0",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.1.2.tgz"
},
"request": {
"version": "2.72.0",
"from": "request@>=2.0.0 <3.0.0",
"resolved": "https://registry.npmjs.org/request/-/request-2.72.0.tgz"
},
"rimraf": {
"version": "2.5.2",
"from": "rimraf@>=2.5.0 <2.6.0",
@@ -3375,20 +3416,15 @@
"from": "strip-json-comments@>=1.0.4 <1.1.0",
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-1.0.4.tgz"
},
"supports-color": {
"version": "2.0.0",
"from": "supports-color@>=2.0.0 <3.0.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.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"
},
"tunnel-agent": {
"version": "0.4.3",
"from": "tunnel-agent@>=0.4.1 <0.5.0",
"resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.4.3.tgz"
"supports-color": {
"version": "2.0.0",
"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",
@@ -3400,6 +3436,11 @@
"from": "tweetnacl@>=0.13.0 <0.14.0",
"resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.13.3.tgz"
},
"tunnel-agent": {
"version": "0.4.3",
"from": "tunnel-agent@>=0.4.1 <0.5.0",
"resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.4.3.tgz"
},
"uid-number": {
"version": "0.0.6",
"from": "uid-number@>=0.0.6 <0.1.0",
@@ -3449,18 +3490,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"
}
}
},
"rc": {
"version": "1.1.6",
"from": "rc@>=1.1.0 <1.2.0",
@@ -3473,6 +3502,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"
}
}
},
"sshpk": {
"version": "1.8.3",
"from": "sshpk@>=1.7.0 <2.0.0",
@@ -3538,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",
@@ -3957,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",
@@ -3981,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",

View File

@@ -1,7 +1,7 @@
{
"private": true,
"name": "windshaft-cartodb",
"version": "2.44.0",
"version": "2.46.0",
"description": "A map tile server for CartoDB",
"keywords": [
"cartodb"
@@ -20,7 +20,7 @@
],
"dependencies": {
"body-parser": "~1.14.0",
"camshaft": "0.11.0",
"camshaft": "0.13.0",
"cartodb-psql": "~0.6.1",
"cartodb-query-tables": "~0.1.0",
"cartodb-redis": "~0.13.0",
@@ -37,9 +37,9 @@
"request": "~2.62.0",
"step": "~0.0.6",
"step-profiler": "~0.3.0",
"turbo-carto": "0.10.0",
"turbo-carto": "0.11.0",
"underscore": "~1.6.0",
"windshaft": "1.19.0"
"windshaft": "2.3.0"
},
"devDependencies": {
"istanbul": "~0.4.3",

View File

@@ -5,6 +5,7 @@ OPT_CREATE_PGSQL=yes # create the PostgreSQL test environment
OPT_DROP_REDIS=yes # drop the redis test environment
OPT_DROP_PGSQL=yes # drop the PostgreSQL test environment
OPT_COVERAGE=no # run tests with coverage
OPT_DOWNLOAD_SQL=yes # download a fresh copy of sql files
export PGAPPNAME=cartodb_tiler_tester
@@ -73,6 +74,10 @@ while [ -n "$1" ]; do
OPT_CREATE_REDIS=no
shift
continue
elif test "$1" = "--no-sql-download"; then
OPT_DOWNLOAD_SQL=no
shift
continue
elif test "$1" = "--with-coverage"; then
OPT_COVERAGE=yes
shift
@@ -113,6 +118,9 @@ fi
if test x"$OPT_CREATE_REDIS" != xyes; then
PREPARE_DB_OPTS="$PREPARE_DB_OPTS --skip-redis"
fi
if test x"$OPT_DOWNLOAD_SQL" != xyes; then
PREPARE_DB_OPTS="$PREPARE_DB_OPTS --no-sql-download"
fi
echo "Preparing the environment"
cd ${BASEDIR}/test/support

View File

@@ -0,0 +1,80 @@
require('../../support/test_helper');
var assert = require('../../support/assert');
var TestClient = require('../../support/test-client');
describe('histogram-dataview', function() {
afterEach(function(done) {
if (this.testClient) {
this.testClient.drain(done);
} else {
done();
}
});
function createMapConfig(layers, dataviews, analysis) {
return {
version: '1.5.0',
layers: layers,
dataviews: dataviews || {},
analyses: analysis || []
};
}
var mapConfig = createMapConfig(
[
{
"type": "cartodb",
"options": {
"source": {
"id": "2570e105-7b37-40d2-bdf4-1af889598745"
},
"cartocss": "#points { marker-width: 10; marker-fill: red; }",
"cartocss_version": "2.3.0"
}
}
],
{
pop_max_histogram: {
source: {
id: '2570e105-7b37-40d2-bdf4-1af889598745'
},
type: 'histogram',
options: {
column: 'x'
}
}
},
[
{
"id": "2570e105-7b37-40d2-bdf4-1af889598745",
"type": "source",
"params": {
"query": "select null::geometry the_geom_webmercator, x from generate_series(0,1000) x"
}
}
]
);
it('should get bin_width right when max > min in filter', function(done) {
var params = {
bins: 10,
start: 1e3,
end: 0
};
this.testClient = new TestClient(mapConfig, 1234);
this.testClient.getDataview('pop_max_histogram', params, function(err, dataview) {
assert.ok(!err, err);
assert.equal(dataview.type, 'histogram');
assert.ok(dataview.bin_width > 0, 'Unexpected bin width: ' + dataview.bin_width);
dataview.bins.forEach(function(bin) {
assert.ok(bin.min <= bin.max, 'bin min < bin max: ' + JSON.stringify(bin));
});
done();
});
});
});

View File

@@ -0,0 +1,343 @@
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();
});
});
it('should work with mapnik substitution tokens', function(done) {
var cartocss = [
"#layer {",
" line-width: 2;",
" line-color: #3B3B58;",
" line-opacity: 1;",
" polygon-opacity: 0.7;",
" polygon-fill: ramp([points_count], (#E5F5F9,#99D8C9,#2CA25F))",
"}"
].join('\n');
var sql = [
'WITH hgrid AS (',
' SELECT CDB_HexagonGrid(',
' ST_Expand(!bbox!, greatest(!pixel_width!,!pixel_height!) * 100),',
' greatest(!pixel_width!,!pixel_height!) * 100',
' ) as cell',
')',
'SELECT',
' hgrid.cell as the_geom_webmercator,',
' count(1) as points_count,',
' count(1)/power(100 * CDB_XYZ_Resolution(CDB_ZoomFromScale(!scale_denominator!)), 2) as points_density,',
' 1 as cartodb_id',
'FROM hgrid, (SELECT * FROM populated_places_simple_reduced) i',
'where ST_Intersects(i.the_geom_webmercator, hgrid.cell)',
'GROUP BY hgrid.cell'
].join('\n');
var mapConfig = {
"version": "1.4.0",
"layers": [
{
"type": 'mapnik',
"options": {
"cartocss_version": '2.3.0',
"sql": sql,
"cartocss": cartocss
}
}
]
};
this.testClient = new TestClient(mapConfig);
this.testClient.getTile(0, 0, 0, { format: 'geojson', layer: 0 }, function(err, res, geojson) {
assert.ok(!err, err);
assert.ok(geojson);
assert.equal(geojson.features.length, 5);
done();
});
});
it('should skip empty and null columns for geojson tiles', function(done) {
var mapConfig = {
"analyses": [
{
"id": "a0",
"params": {
"query": "SELECT * FROM test_table"
},
"type": "source"
}
],
"dataviews": {
"4e7b0e07-6d21-4b83-9adb-6d7e17eea6ca": {
"options": {
"aggregationColumn": null,
"column": "cartodb_id",
"operation": "avg"
},
"source": {
"id": "a0"
},
"type": "formula"
},
"74f590f8-625c-4e95-922f-34ad3e9919c0": {
"options": {
"aggregation": "sum",
"aggregationColumn": "cartodb_id",
"column": "name"
},
"source": {
"id": "a0"
},
"type": "aggregation"
},
"98a75757-3006-400a-b028-fb613a6c0b69": {
"options": {
"aggregationColumn": null,
"column": "cartodb_id",
"operation": "sum"
},
"source": {
"id": "a0"
},
"type": "formula"
},
"ebbc97b2-87d2-4895-9e1f-2f012df3679d": {
"options": {
"aggregationColumn": null,
"bins": "12",
"column": "cartodb_id"
},
"source": {
"id": "a0"
},
"type": "histogram"
},
"ebc0653f-3581-469c-8b31-c969e440a865": {
"options": {
"aggregationColumn": null,
"column": "cartodb_id",
"operation": "avg"
},
"source": {
"id": "a0"
},
"type": "formula"
}
},
"layers": [
{
"options": {
"subdomains": "abcd",
"urlTemplate": "http://{s}.basemaps.cartocdn.com/light_nolabels/{z}/{x}/{y}.png"
},
"type": "http"
},
{
"options": {
"attributes": {
"columns": [
"name",
"address"
],
"id": "cartodb_id"
},
"cartocss": "#layer { marker-width: 10; marker-fill: red; }",
"cartocss_version": "2.3.0",
"interactivity": "cartodb_id",
"layer_name": "wadus",
"source": {
"id": "a0"
}
},
"type": "cartodb"
},
{
"options": {
"subdomains": "abcd",
"urlTemplate": "http://{s}.basemaps.cartocdn.com/light_only_labels/{z}/{x}/{y}.png"
},
"type": "http"
}
]
};
this.testClient = new TestClient(mapConfig);
this.testClient.getTile(0, 0, 0, { format: 'geojson', layer: 0 }, function(err, res, geojson) {
assert.ok(!err, err);
assert.ok(geojson);
assert.equal(geojson.features.length, 5);
assert.deepEqual(Object.keys(geojson.features[0].properties), ['cartodb_id', 'name']);
done();
});
});
});

View File

@@ -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();
});
});
});
});
});
});

View File

@@ -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();
});
});
});
});

View File

@@ -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();
});
});
});
});
});

View File

@@ -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;
}
});
});

View File

@@ -0,0 +1,223 @@
require('../../support/test_helper');
var assert = require('../../support/assert');
var TestClient = require('../../support/test-client');
describe('widgets-regressions', function() {
describe('aggregations', function() {
afterEach(function(done) {
if (this.testClient) {
this.testClient.drain(done);
} else {
done();
}
});
it('should work when there is a mix of layers with and without widgets', function(done) {
var layersWithNoWidgetsMapConfig = {
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'
}
}
}
}
},
{
type: 'mapnik',
options: {
sql: 'select * from populated_places_simple_reduced limit 100',
cartocss: '#layer0 { marker-fill: red; marker-width: 10; }',
cartocss_version: '2.0.1'
}
}
]
};
this.testClient = new TestClient(layersWithNoWidgetsMapConfig);
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();
});
});
it('should work when there is a mix of layers with and without widgets, source and sql', function(done) {
var mixOfLayersMapConfig = {
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_categories: {
type: 'aggregation',
options: {
column: 'adm0name',
aggregation: 'sum',
aggregationColumn: 'pop_max'
}
},
adm1name_categories: {
type: 'aggregation',
options: {
column: 'adm1name',
aggregation: 'sum',
aggregationColumn: 'pop_max'
}
}
}
}
},
{
type: 'mapnik',
options: {
source: {id: 'head-limited'},
cartocss: '#layer0 { marker-fill: red; marker-width: 10; }',
cartocss_version: '2.0.1',
widgets: {
pop_max_histogram: {
type: 'histogram',
options: {
column: 'pop_max'
}
}
}
}
},
{
"type": "http",
"options": {
"urlTemplate": "http://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}.png",
"subdomains": "abcd"
}
}
],
analyses: [
{
id: 'head-limited',
type: 'source',
params: {
query: 'select * from populated_places_simple_reduced limit 100'
}
}
],
dataviews: {
wadus: {
type: 'histogram',
source: {
id: 'head-limited'
},
options: {
column: 'population'
}
}
}
};
this.testClient = new TestClient(mixOfLayersMapConfig);
this.testClient.getLayergroup(function(err, layergroup) {
assert.ok(!err, err);
assert.ok(layergroup.metadata);
var metadata = layergroup.metadata;
assert.equal(metadata.layers.length, 3);
assert.equal(metadata.analyses.length, 2);
assert.equal(Object.keys(metadata.dataviews).length, 4);
assert.deepEqual(
Object.keys(metadata.dataviews),
['wadus', 'adm0name_categories', 'adm1name_categories', 'pop_max_histogram']
);
done();
});
});
it('should work with layers not containing sql', function(done) {
var nonSqlLayersMapConfig = {
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'
}
}
}
}
},
{
"type": "http",
"options": {
"urlTemplate": "http://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}.png",
"subdomains": "abcd"
}
}
]
};
this.testClient = new TestClient(nonSqlLayersMapConfig);
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();
});
});
});
});

View File

@@ -12,6 +12,7 @@
PREPARE_REDIS=yes
PREPARE_PGSQL=yes
DOWNLOAD_SQL_FILES=yes
while [ -n "$1" ]; do
if test "$1" = "--skip-pg"; then
@@ -20,6 +21,9 @@ while [ -n "$1" ]; do
elif test "$1" = "--skip-redis"; then
PREPARE_REDIS=no
shift; continue
elif test "$1" = "--no-sql-download"; then
DOWNLOAD_SQL_FILES=no
shift; continue
fi
done
@@ -71,26 +75,31 @@ if test x"$PREPARE_PGSQL" = xyes; then
dropdb "${TEST_DB}"
createdb -Ttemplate_postgis -EUTF8 "${TEST_DB}" || die "Could not create test database"
curl -L -s https://raw.githubusercontent.com/CartoDB/camshaft/master/test/fixtures/cdb_analysis_catalog.sql -o sql/cdb_analysis_catalog.sql
cat sql/cdb_analysis_catalog.sql | psql -v ON_ERROR_STOP=1 ${TEST_DB} || exit 1
LOCAL_SQL_SCRIPTS='windshaft.test gadm4 ported/populated_places_simple_reduced'
REMOTE_SQL_SCRIPTS='CDB_QueryStatements CDB_QueryTables CDB_CartodbfyTable CDB_TableMetadata CDB_ForeignTable CDB_UserTables CDB_ColumnNames CDB_AnalysisCatalog CDB_ZoomFromScale CDB_Overviews CDB_QuantileBins CDB_JenksBins CDB_HeadsTailsBins CDB_EqualIntervalBins CDB_Hexagon CDB_XYZ'
cat sql/windshaft.test.sql sql/gadm4.sql |
sed "s/:PUBLICUSER/${PUBLICUSER}/" |
sed "s/:PUBLICPASS/${PUBLICPASS}/" |
sed "s/:TESTUSER/${TESTUSER}/" |
sed "s/:TESTPASS/${TESTPASS}/" |
psql -v ON_ERROR_STOP=1 ${TEST_DB} || exit 1
cat sql/_CDB_QueryStatements.sql | psql -v ON_ERROR_STOP=1 ${TEST_DB} || exit 1
SQL_SCRIPTS='CDB_QueryTables CDB_CartodbfyTable CDB_TableMetadata CDB_ForeignTable CDB_UserTables CDB_ColumnNames CDB_ZoomFromScale CDB_Overviews CDB_QuantileBins CDB_JenksBins CDB_HeadsTailsBins CDB_EqualIntervalBins CDB_Hexagon CDB_XYZ'
for i in ${SQL_SCRIPTS}
CURL_ARGS=""
for i in ${REMOTE_SQL_SCRIPTS}
do
curl -L -s https://github.com/CartoDB/cartodb-postgresql/raw/master/scripts-available/$i.sql -o sql/$i.sql
cat sql/$i.sql | sed -e 's/cartodb\./public./g' -e "s/''cartodb''/''public''/g" \
| psql -v ON_ERROR_STOP=1 ${TEST_DB} || exit 1
CURL_ARGS="${CURL_ARGS}\"https://github.com/CartoDB/cartodb-postgresql/raw/master/scripts-available/$i.sql\" -o sql/$i.sql "
done
if test x"$DOWNLOAD_SQL_FILES" = xyes; then
echo "Downloading and updating: ${REMOTE_SQL_SCRIPTS}"
echo ${CURL_ARGS} | xargs curl -L -s
fi
psql -c "CREATE EXTENSION IF NOT EXISTS plpythonu;" ${TEST_DB}
ALL_SQL_SCRIPTS="${REMOTE_SQL_SCRIPTS} ${LOCAL_SQL_SCRIPTS}"
for i in ${ALL_SQL_SCRIPTS}
do
cat sql/${i}.sql |
sed -e 's/cartodb\./public./g' -e "s/''cartodb''/''public''/g" |
sed "s/:PUBLICUSER/${PUBLICUSER}/" |
sed "s/:PUBLICPASS/${PUBLICPASS}/" |
sed "s/:TESTUSER/${TESTUSER}/" |
sed "s/:TESTPASS/${TESTPASS}/" |
PGOPTIONS='--client-min-messages=WARNING' psql -q -v ON_ERROR_STOP=1 ${TEST_DB} > /dev/null || exit 1
done
fi
if test x"$PREPARE_REDIS" = xyes; then
@@ -122,17 +131,3 @@ EOF
fi
echo "Finished preparing data. Ready to run tests"
############################ WINDSHAFT TESTS ############################
echo "...Configuring Windshaft test database"
cat sql/ported/populated_places_simple_reduced.sql |
sed "s/:PUBLICUSER/${PUBLICUSER}/" |
sed "s/:PUBLICPASS/${PUBLICPASS}/" |
sed "s/:TESTUSER/${TESTUSER}/" |
sed "s/:TESTPASS/${TESTPASS}/" |
psql -v ON_ERROR_STOP=1 ${TEST_DB} || exit 1
echo "...Test database configuration complete"

View File

@@ -1,16 +0,0 @@
-- DUMMY IMPLEMENTATION
-- Ref: https://github.com/CartoDB/cartodb-postgresql/blob/master/scripts-available/CDB_QueryStatements.sql
-- Originally implemented in plpython for performance reasons
-- Return an array of statements found in the given query text
--
-- Regexp curtesy of Hubert Lubaczewski (depesz)
--
CREATE OR REPLACE FUNCTION CDB_QueryStatements(query text)
RETURNS SETOF TEXT AS $$
with matches as (
select regexp_matches($1, $regexp$((?:[^'"$;]+|"[^"]*"|'[^']*'|(\$[^$]*\$).*?\2)+)$regexp$, 'g') as m
)
select btrim(m[1]) from matches
$$
LANGUAGE SQL IMMUTABLE STRICT;

View File

@@ -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);
}
);
};
@@ -184,9 +310,13 @@ TestClient.prototype.getDataview = function(dataviewName, 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];
}
});
if (self.apiKey) {
urlParams.api_key = self.apiKey;
}
@@ -215,10 +345,10 @@ TestClient.prototype.getDataview = function(dataviewName, params, callback) {
}
);
},
function finish(err, res) {
function finish(err, dataview) {
self.keysToDelete['map_cfg|' + LayergroupToken.parse(layergroupId).token] = 0;
self.keysToDelete['user:localhost:mapviews:global'] = 5;
return callback(err, res);
return callback(err, dataview);
}
);
};

View File

@@ -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();
});
});
});
});

View File

@@ -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])
);
});
});
});