Compare commits

...

93 Commits

Author SHA1 Message Date
Raul Ochoa
72986d1946 Release 2.53.4 2016-06-28 19:47:59 +02:00
Raul Ochoa
2195c55518 Upgrades camshaft to 0.22.4 2016-06-28 19:44:55 +02:00
Raul Ochoa
c418ba1908 Stubs next version 2016-06-28 14:39:02 +02:00
Raul Ochoa
934356e5cc Release 2.53.3 2016-06-28 14:38:25 +02:00
Raul Ochoa
c40235a910 Upgrades camshaft to 0.22.3 2016-06-28 14:37:21 +02:00
Raul Ochoa
cd8338196e Stubs next version 2016-06-28 13:08:08 +02:00
Raul Ochoa
2143e87401 Release 2.53.2 2016-06-28 13:07:19 +02:00
Raul Ochoa
f0a536ee1e Upgrades camshaft to 0.22.2 2016-06-28 13:06:46 +02:00
Raul Ochoa
dde4b63c6b Stubs next version 2016-06-28 00:25:29 +02:00
Raul Ochoa
0e7bcc4b56 Release 2.53.1 2016-06-28 00:24:40 +02:00
Raul Ochoa
e4816b4322 Merge pull request #527 from CartoDB/upgrade-camshaft
Upgrades camshaft to 0.22.1
2016-06-28 00:21:33 +02:00
Raul Ochoa
af4f29c538 Upgrades camshaft to 0.22.1 2016-06-28 00:17:33 +02:00
Raul Ochoa
7cedccedcd Stubs next version 2016-06-24 14:59:23 +02:00
Raul Ochoa
a9c12d4534 Release 2.53.0 2016-06-24 14:58:44 +02:00
Raul Ochoa
77f71b1978 Merge pull request #523 from CartoDB/upgrade-camshaft
Upgrades camshaft to 0.22.0
2016-06-24 14:56:05 +02:00
Raul Ochoa
1c029fbc7b Upgrades camshaft to 0.22.0 2016-06-24 14:51:44 +02:00
Raul Ochoa
2bc0d8d145 Stubs next version 2016-06-23 19:09:16 +02:00
Raul Ochoa
4c2af88f92 Release 2.52.0 2016-06-23 19:08:36 +02:00
Raul Ochoa
ddd5d2a0b0 Bump version 2016-06-23 19:08:10 +02:00
Raul Ochoa
d5cb59dc84 Merge pull request #521 from CartoDB/upgrade-camshaft
Upgrades camshaft to 0.21.0
2016-06-23 19:06:25 +02:00
Raul Ochoa
f21581630a Upgrades camshaft to 0.21.0 2016-06-23 19:01:59 +02:00
Raul Ochoa
3fef37d06b Stubs next version 2016-06-22 17:23:04 +02:00
Raul Ochoa
a8b93896ed Release 2.51.0 2016-06-22 17:22:20 +02:00
Raul Ochoa
6c1e9bf0ca Bump version 2016-06-22 17:21:58 +02:00
Raul Ochoa
1d8947d404 Merge pull request #520 from CartoDB/upgrade-camshaft
Upgrades camshaft to 0.20.0
2016-06-22 15:13:17 +02:00
Raul Ochoa
834377b342 Upgrades camshaft to 0.20.0 2016-06-22 15:03:26 +02:00
Daniel García Aubert
c2e0eb05e5 Updated NEWS. #519 2016-06-21 18:43:27 +02:00
Daniel
256032ca4a Merge pull request #519 from CartoDB/fix-error-with-context
Now errors with context have the same schema.
2016-06-21 18:41:46 +02:00
Daniel García Aubert
d80f2b9566 Now errors with context have the same schema. 2016-06-21 18:26:10 +02:00
Raul Ochoa
9e2f0371ba Merge pull request #518 from CartoDB/better-analyses-errors
Improve error messages for missing analyses for layers and dataviews
2016-06-21 17:34:44 +02:00
Raul Ochoa
a2e74a3e1b Improve error messages for missing analyses for layers and dataviews 2016-06-21 17:25:28 +02:00
Raul Ochoa
f04a5a1ab9 Merge pull request #517 from CartoDB/turbo-carto-datasource-improvements
Split turbo-carto adapter substitutions tokens query
2016-06-21 15:48:07 +02:00
Raul Ochoa
7114311b75 Split turbo-carto adapter substitutions tokens query 2016-06-21 15:05:44 +02:00
Daniel García Aubert
1e9e092dc3 Stubs next version 2016-06-21 14:52:27 +02:00
Daniel García Aubert
98b3a5ba23 Release 2.50.0 2016-06-21 14:41:52 +02:00
Daniel García Aubert
200966e806 Merge branch 'upgrade-camshaft-0.19.0' 2016-06-21 14:34:28 +02:00
Daniel García Aubert
407430b81e Updated shrinkwrap 2016-06-21 14:20:54 +02:00
Raul Ochoa
c4b1fc039c Merge pull request #516 from CartoDB/turbo-carto-datasource-improvements
Pixel size query for turbo-carto adapter using radians and degrees
2016-06-21 14:12:41 +02:00
Raul Ochoa
d00379af6b Pixel size query for turbo-carto adapter using radians and degrees instead of meters 2016-06-21 14:01:43 +02:00
Raul Ochoa
7cee0f3ee3 Merge pull request #515 from CartoDB/turbo-carto-datasource-improvements
Use psql client instead of pg query runner to use proper params
2016-06-21 13:24:36 +02:00
Daniel García Aubert
2588346f1b Bumped camshaft version to 0.19.0 2016-06-21 13:19:01 +02:00
Raul Ochoa
863128013d Use psql client instead of pg query runner to use proper params 2016-06-21 12:08:40 +02:00
Raul Ochoa
51eb8eb67f Upgrades camshaft to 0.18.0 2016-06-20 17:09:55 +02:00
Raul Ochoa
28e01fd8ac Bump version 2016-06-20 16:47:38 +02:00
Raul Ochoa
726e153ad5 Merge pull request #514 from CartoDB/dataview-aggregation-operations
Add support for min, max, and avg operations in aggregation dataview
2016-06-20 16:40:55 +02:00
Raul Ochoa
e8df09c85b Add support for min, max, and avg operations in aggregation dataview 2016-06-20 16:26:24 +02:00
Raul Ochoa
2b4fb2971d Stubs next version 2016-06-20 13:44:24 +02:00
Raul Ochoa
933b36cca0 Release 2.49.1 2016-06-20 13:35:59 +02:00
Raul Ochoa
37a7cfb6ba Update news 2016-06-20 13:35:38 +02:00
Raul Ochoa
8a1cda159c Merge pull request #512 from CartoDB/turbo-carto-datasource-fixes
Use an empty array as default value for falsy ramps
2016-06-20 13:33:02 +02:00
Raul Ochoa
403dcbebcd Use an empty array as default value for falsy ramps
Fixes #507
2016-06-20 13:27:39 +02:00
Raul Ochoa
373ad69306 Merge branch 'master' into turbo-carto-datasource-fixes 2016-06-20 13:27:02 +02:00
Raul Ochoa
b2029e09f5 Add CDB_OverviewsSupport sql from extension to fix CDB_Overviews calls 2016-06-20 13:26:30 +02:00
Raul Ochoa
4f37d2d0c2 Empty results should keep working, going red 2016-06-20 13:09:01 +02:00
Raul Ochoa
a5b07bc2a8 Upgrades turbo-carto to 0.12.1 2016-06-20 13:07:51 +02:00
Raul Ochoa
1544a5622d Merge pull request #511 from CartoDB/dataview-the_geom-query
Dataview the geom query
2016-06-17 17:08:48 +02:00
Raul Ochoa
d49a877771 Fix reversed own filter option 2016-06-17 16:26:45 +02:00
Raul Ochoa
0f4747743c Merge branch 'master' into dataview-the_geom-query 2016-06-17 15:55:07 +02:00
Raul Ochoa
368e4522e7 Merge pull request #510 from CartoDB/updated-at-from-analyses-result
Pick last update time for layergroupid from analyses results
2016-06-17 15:44:11 +02:00
Raul Ochoa
cb1d1bb115 Pick last update time for layergroupid from analyses results 2016-06-17 14:46:22 +02:00
Raul Ochoa
612cc3dd41 Use the_geom for intermediate dataviews 2016-06-16 17:27:00 +02:00
Raul Ochoa
bff082e577 Stubs next version 2016-06-15 19:27:48 +02:00
Raul Ochoa
ea41750a14 Release 2.49.0 2016-06-15 19:26:25 +02:00
Raul Ochoa
458376a665 Merge pull request #506 from CartoDB/upgrade-camshaft
Upgrade camshaft to 0.17.1
2016-06-15 19:24:48 +02:00
Raul Ochoa
f0284907c4 Upgrade camshaft to 0.17.1 2016-06-15 19:13:37 +02:00
Raul Ochoa
ad0385ccf7 Merge pull request #505 from CartoDB/upgrade-camshaft
Upgrade camshaft to 0.17.0
2016-06-15 16:58:23 +02:00
Raul Ochoa
4350fc3c65 Upgrade camshaft to 0.17.0 2016-06-15 16:48:00 +02:00
Raul Ochoa
fc3422b9e5 Merge pull request #503 from CartoDB/upgrade-camshaft
Upgrades camshaft to 0.16.0
2016-06-15 11:13:50 +02:00
Raul Ochoa
9703c19fb4 Upgrades camshaft to 0.16.0 2016-06-15 11:01:11 +02:00
Daniel García Aubert
d36f2fb354 Stubs next version 2016-06-14 17:50:40 +02:00
Daniel García Aubert
c837785314 Release 2.48.0 2016-06-14 17:48:07 +02:00
Daniel
33014a9f45 Merge pull request #500 from CartoDB/478-error-context
Adds more error information when either analysis or turbo-carto is not well formed
2016-06-14 15:56:00 +02:00
Daniel García Aubert
c16d0b8605 Fixed broken tests 2016-06-14 10:50:50 +02:00
Daniel García Aubert
c88e4c5173 Merge branch 'master' into 478-error-context 2016-06-14 10:43:00 +02:00
Daniel García Aubert
0540696c3e Avoid to expose internal naming 2016-06-14 10:27:35 +02:00
Raul Ochoa
5f59a97a02 Merge pull request #502 from CartoDB/upgrade-camshaft
Upgrades camshaft to 0.15.1
2016-06-14 01:41:41 +02:00
Raul Ochoa
d3b815c3c7 Upgrades camshaft to 0.15.1 2016-06-14 01:36:15 +02:00
Raul Ochoa
d2a8dcbede Merge pull request #501 from CartoDB/upgrade-camshaft
Upgrades camshaft to 0.15.0
2016-06-13 22:14:47 +02:00
Daniel García Aubert
4854e879a6 Updated npm-shrinkwrap 2016-06-13 19:49:20 +02:00
Daniel García Aubert
ddc99cebff Upgrades turbo-carto to 0.12.0 2016-06-13 18:40:14 +02:00
Raul Ochoa
47470d4f2b Upgrades camshaft to 0.15.0 2016-06-13 18:04:48 +02:00
Daniel García Aubert
d9297d54de made error_with_context non optional and adapted test's assertion 2016-06-13 16:14:01 +02:00
Daniel García Aubert
2d821f957e Merge branch 'master' into 478-error-context 2016-06-13 12:29:00 +02:00
Daniel García Aubert
c0ce6e7a8a WIP fixes 478, adds more error information when either analysis or turbo-carto is not well formed. 2016-06-13 12:20:56 +02:00
Raul Ochoa
3f620c6cdd Stubs next version 2016-06-13 10:47:15 +02:00
Raul Ochoa
9160d8018d Release 2.47.1 2016-06-13 10:43:45 +02:00
Raul Ochoa
51307bcc69 Upgrades camshaft to 0.14.1 2016-06-10 18:38:49 +02:00
Raul Ochoa
d29651ee80 Stubs next version 2016-06-10 14:41:08 +02:00
Raul Ochoa
18640077aa Release 2.47.0 2016-06-10 14:40:28 +02:00
Raul Ochoa
59563c893b Merge pull request #499 from CartoDB/upgrade-camshaft
Upgrades camshaft to 0.14.0
2016-06-10 14:19:39 +02:00
Raul Ochoa
90fd1786e1 Upgrades camshaft to 0.14.0 2016-06-10 14:05:23 +02:00
Raul Ochoa
4a11115dd0 Improve errors for dataviews validation 2016-06-09 18:13:54 +02:00
Raul Ochoa
baf3e774c5 Stubs next version 2016-06-09 10:33:35 +02:00
34 changed files with 1208 additions and 410 deletions

119
NEWS.md
View File

@@ -1,5 +1,124 @@
# Changelog
## 2.53.4
Released 2016-06-28
Announcements:
- Upgrades camshaft to [0.22.4](https://github.com/CartoDB/camshaft/releases/tag/0.22.4)
## 2.53.3
Released 2016-06-28
Announcements:
- Upgrades camshaft to [0.22.3](https://github.com/CartoDB/camshaft/releases/tag/0.22.3)
## 2.53.2
Released 2016-06-28
Announcements:
- Upgrades camshaft to [0.22.2](https://github.com/CartoDB/camshaft/releases/tag/0.22.2)
## 2.53.1
Released 2016-06-28
Announcements:
- Upgrades camshaft to [0.22.1](https://github.com/CartoDB/camshaft/releases/tag/0.22.1)
## 2.53.0
Released 2016-06-24
Announcements:
- Upgrades camshaft to [0.22.0](https://github.com/CartoDB/camshaft/releases/tag/0.22.0)
## 2.52.0
Released 2016-06-23
Announcements:
- Upgrades camshaft to [0.21.0](https://github.com/CartoDB/camshaft/releases/tag/0.21.0)
## 2.51.0
Released 2016-06-21
Enhancements:
- Split turbo-carto adapter substitutions tokens query.
- Now errors with context have the same schema. #519
Announcements:
- Upgrades camshaft to [0.20.0](https://github.com/CartoDB/camshaft/releases/tag/0.20.0)
## 2.50.0
Released 2016-06-21
Bug fixes:
- Pixel size query for turbo-carto adapter using radians and degrees instead of meters.
New features:
- Add support for min, max, and avg operations in aggregation dataview #513.
Announcements:
- Upgrades camshaft to [0.19.0](https://github.com/CartoDB/camshaft/releases/tag/0.19.0)
## 2.49.1
Released 2016-06-20
Announcements:
- Upgrades turbo-carto to [0.12.1](https://github.com/CartoDB/turbo-carto/releases/tag/0.12.1).
Bug fixes:
- Use an empty array as default value for falsy ramps #512.
- Use the_geom for intermediate dataviews #511.
- Pick last update time for layergroupid from analyses results #510.
## 2.49.0
Released 2016-06-15
Announcements:
- Upgrades camshaft to [0.17.1](https://github.com/CartoDB/camshaft/releases/tag/0.17.1)
## 2.48.0
Released 2016-06-14
Announcements:
- Upgrades camshaft to [0.15.1](https://github.com/CartoDB/camshaft/releases/tag/0.15.1)
- Responses with more context info if analysis or turbo-carto fails during map creation.
## 2.47.1
Released 2016-06-13
Announcements:
- Upgrades camshaft to [0.14.1](https://github.com/CartoDB/camshaft/releases/tag/0.14.1)
## 2.47.0
Released 2016-06-10
Announcements:
- Upgrades camshaft to [0.14.0](https://github.com/CartoDB/camshaft/releases/tag/0.14.0)
## 2.46.0
Released 2016-06-09

View File

@@ -16,6 +16,9 @@ var overviewsQueryRewriter = new OverviewsQueryRewriter({
zoom_level: 'CDB_ZoomFromScale(!scale_denominator!)'
});
var dot = require('dot');
dot.templateSettings.strip = false;
function DataviewBackend(analysisBackend) {
this.analysisBackend = analysisBackend;
}
@@ -105,15 +108,7 @@ DataviewBackend.prototype.getDataview = function (mapConfigProvider, user, param
var ownFilter = +params.own_filter;
ownFilter = !!ownFilter;
var query;
if (ownFilter) {
query = node.getQuery();
} else {
var applyFilters = {};
applyFilters[dataviewName] = false;
query = node.getQuery(applyFilters);
}
var query = layerQuery(node, dataviewName, ownFilter);
var sourceId = dataviewDefinition.source.id; // node.id
var layer = _.find(
@@ -260,14 +255,7 @@ DataviewBackend.prototype.search = function (mapConfigProvider, user, params, ca
var ownFilter = +params.own_filter;
ownFilter = !!ownFilter;
var query;
if (ownFilter) {
query = node.getQuery();
} else {
var applyFilters = {};
applyFilters[dataviewName] = false;
query = node.getQuery(applyFilters);
}
var query = layerQuery(node, dataviewName, ownFilter);
if (params.bbox) {
var bboxFilter = new BBoxFilter({column: 'the_geom', srid: 4326}, {bbox: params.bbox});
@@ -321,3 +309,31 @@ function dbParamsFromReqParams(params) {
}
return dbParams;
}
var SKIP_COLUMNS = {
'the_geom': true,
'the_geom_webmercator': true
};
function skipColumns(columnNames) {
return columnNames
.filter(function(columnName) { return !SKIP_COLUMNS[columnName]; });
}
var layerQueryTemplate = dot.template([
'SELECT {{=it._columns}}',
'FROM ({{=it._query}}) _cdb_analysis_query'
].join('\n'));
function layerQuery(node, dataviewName, ownFilter) {
var applyFilters = {};
if (!ownFilter) {
applyFilters[dataviewName] = false;
}
if (node.type === 'source') {
return node.getQuery(applyFilters);
}
var _columns = ['ST_Transform(the_geom, 3857) the_geom_webmercator'].concat(skipColumns(node.getColumns()));
return layerQueryTemplate({ _query: node.getQuery(applyFilters), _columns: _columns.join(', ') });
}

View File

@@ -54,9 +54,8 @@ var method2strategy = {
category: STRATEGY.EXACT
};
function PostgresDatasource (pgQueryRunner, username, query) {
this.pgQueryRunner = pgQueryRunner;
this.username = username;
function PostgresDatasource (psql, query) {
this.psql = psql;
this.query = query;
}
@@ -75,13 +74,16 @@ PostgresDatasource.prototype.getRamp = function (column, buckets, method, callba
var query = template({ _column: column, _sql: this.query, _buckets: buckets });
this.pgQueryRunner.run(this.username, query, function (err, result) {
this.psql.query(query, function (err, resultSet) {
if (err) {
return callback(err);
}
resultSet = resultSet || {};
var result = resultSet.rows || [];
var strategy = method2strategy[methodName];
var ramp = result[0][methodName];
var ramp = result[0][methodName] || [];
if (strategy !== STRATEGY.EXACT) {
ramp = ramp.sort(function(a, b) {
return a - b;
@@ -89,7 +91,7 @@ PostgresDatasource.prototype.getRamp = function (column, buckets, method, callba
}
return callback(null, { ramp: ramp, strategy: strategy });
});
}, true); // use read-only transaction
};
module.exports = PostgresDatasource;

View File

@@ -214,15 +214,15 @@ BaseController.prototype.sendError = function(req, res, err, label) {
statusCode = 200;
}
var errorResponseBody = { errors: allErrors.map(errorMessage) };
var errorResponseBody = {
errors: allErrors.map(errorMessage),
errors_with_context: allErrors.map(errorMessageWithContext)
};
this.send(req, res, errorResponseBody, statusCode);
};
function errorMessage(err) {
// See https://github.com/Vizzuality/Windshaft-cartodb/issues/68
var message = (_.isString(err) ? err : err.message) || 'Unknown error';
function stripConnectionInfo(message) {
// Strip connection info, if any
return message
// See https://github.com/CartoDB/Windshaft/issues/173
@@ -230,6 +230,24 @@ function errorMessage(err) {
// See https://travis-ci.org/CartoDB/Windshaft/jobs/20703062#L1644
.replace(/is the server.*encountered/im, 'encountered');
}
function errorMessage(err) {
// See https://github.com/Vizzuality/Windshaft-cartodb/issues/68
var message = (_.isString(err) ? err : err.message) || 'Unknown error';
return stripConnectionInfo(message);
}
function errorMessageWithContext(err) {
// See https://github.com/Vizzuality/Windshaft-cartodb/issues/68
var message = (_.isString(err) ? err : err.message) || 'Unknown error';
return {
type: err.type || 'unknown',
message: stripConnectionInfo(message),
context: err.context || 'unknown'
};
}
module.exports.errorMessage = errorMessage;
function findStatusCode(err) {

View File

@@ -161,7 +161,7 @@ MapController.prototype.create = function(req, res, prepareConfigFn) {
},
function afterLayergroupCreate(err, layergroup) {
assert.ifError(err);
self.afterLayergroupCreate(req, res, mapConfig, layergroup, this);
self.afterLayergroupCreate(req, res, mapConfig, layergroup, context.analysesResults, this);
},
function finish(err, layergroup) {
if (err) {
@@ -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, mapConfigProvider.analysesResults, this);
},
function finishTemplateInstantiation(err, layergroup) {
if (err) {
@@ -240,7 +240,7 @@ MapController.prototype.instantiateTemplate = function(req, res, prepareParamsFn
);
};
MapController.prototype.afterLayergroupCreate = function(req, res, mapconfig, layergroup, callback) {
MapController.prototype.afterLayergroupCreate = function(req, res, mapconfig, layergroup, analysesResults, callback) {
var self = this;
var username = req.context.user;
@@ -299,9 +299,12 @@ MapController.prototype.afterLayergroupCreate = function(req, res, mapconfig, la
// feed affected tables cache so it can be reused from, for instance, layergroup controller
self.layergroupAffectedTables.set(dbName, layergroupId, result);
var lastUpdateTime = result.getLastUpdatedAt();
lastUpdateTime = getLastUpdatedTime(analysesResults, lastUpdateTime) || lastUpdateTime;
// last update for layergroup cache buster
layergroup.layergroupid = layergroup.layergroupid + ':' + result.getLastUpdatedAt();
layergroup.last_updated = new Date(result.getLastUpdatedAt()).toISOString();
layergroup.layergroupid = layergroup.layergroupid + ':' + lastUpdateTime;
layergroup.last_updated = new Date(lastUpdateTime).toISOString();
if (req.method === 'GET') {
var ttl = global.environment.varnish.layergroupTtl || 86400;
@@ -321,6 +324,19 @@ MapController.prototype.afterLayergroupCreate = function(req, res, mapconfig, la
);
};
function getLastUpdatedTime(analysesResults, lastUpdateTime) {
if (!Array.isArray(analysesResults)) {
return lastUpdateTime;
}
return analysesResults.reduce(function(lastUpdateTime, analysis) {
return analysis.getSortedNodes().reduce(function(lastNodeUpdatedAtTime, node) {
var nodeUpdatedAtDate = node.getUpdatedAt();
var nodeUpdatedTimeAt = (nodeUpdatedAtDate && nodeUpdatedAtDate.getTime()) || 0;
return nodeUpdatedTimeAt > lastNodeUpdatedAtTime ? nodeUpdatedTimeAt : lastNodeUpdatedAtTime;
}, lastUpdateTime);
}, lastUpdateTime);
}
function addAnalysesMetadata(username, layergroup, analysesResults, includeQuery) {
includeQuery = includeQuery || false;
analysesResults = analysesResults || [];

View File

@@ -54,7 +54,10 @@ var CATEGORIES_LIMIT = 6;
var VALID_OPERATIONS = {
count: [],
sum: ['aggregationColumn']
sum: ['aggregationColumn'],
avg: ['aggregationColumn'],
min: ['aggregationColumn'],
max: ['aggregationColumn']
};
var TYPE = 'aggregation';
@@ -198,6 +201,7 @@ Aggregation.prototype.format = function(result) {
}
return {
aggregation: this.aggregation,
count: count,
nulls: nulls,
min: minValue,

View File

@@ -58,13 +58,27 @@ AnalysisMapConfigAdapter.prototype.getMapConfig = function(user, requestMapConfi
requestMapConfig = appendFiltersToNodes(requestMapConfig, dataviewsFiltersBySourceId);
function createAnalysis(analysisDefinition, done) {
self.analysisBackend.create(analysisConfiguration, analysisDefinition, done);
function createAnalysis(analysisDefinition, index, done) {
self.analysisBackend.create(analysisConfiguration, analysisDefinition, function (err, analysis) {
if (err) {
var error = new Error(err.message);
error.type = 'analysis';
error.context = {};
error.context.layer = {
index: index,
id: analysisDefinition.id,
type: analysisDefinition.type
};
return done(error);
}
done(null, analysis);
});
}
var analysesQueue = queue(requestMapConfig.analyses.length);
requestMapConfig.analyses.forEach(function(analysis) {
analysesQueue.defer(createAnalysis, analysis);
requestMapConfig.analyses.forEach(function(analysis, index) {
analysesQueue.defer(createAnalysis, analysis, index);
});
analysesQueue.awaitAll(function(err, analysesResults) {
@@ -111,10 +125,12 @@ AnalysisMapConfigAdapter.prototype.getMapConfig = function(user, requestMapConfi
return layer;
});
debug('mapconfig output', JSON.stringify(requestMapConfig, null, 4));
if (missingNodesErrors.length > 0) {
return callback(missingNodesErrors);
var missingDataviewsNodesErrors = getMissingDataviewsSourceIds(dataviews, sourceId2Node);
if (missingNodesErrors.length > 0 || missingDataviewsNodesErrors.length > 0) {
return callback(missingNodesErrors.concat(missingDataviewsNodesErrors));
}
context.analysesResults = analysesResults;
@@ -163,8 +179,9 @@ function appendFiltersToNodes(requestMapConfig, dataviewsFiltersBySourceId) {
}
function shouldAdaptLayers(requestMapConfig) {
return Array.isArray(requestMapConfig.layers) &&
Array.isArray(requestMapConfig.analyses) && requestMapConfig.analyses.length > 0;
return Array.isArray(requestMapConfig.layers) && requestMapConfig.layers.some(getLayerSourceId) ||
(Array.isArray(requestMapConfig.analyses) && requestMapConfig.analyses.length > 0) ||
requestMapConfig.dataviews;
}
var DATAVIEW_TYPE_2_FILTER_TYPE = {
@@ -232,6 +249,15 @@ function getDataviewsList(dataviews) {
}
function getDataviewsErrors(dataviews) {
var dataviewType = typeof dataviews;
if (dataviewType !== 'object') {
return [new Error('"dataviews" must be a valid JSON object: "' + dataviewType + '" type found')];
}
if (Array.isArray(dataviews)) {
return [new Error('"dataviews" must be a valid JSON object: "array" type found')];
}
var errors = [];
Object.keys(dataviews).forEach(function(dataviewName) {
@@ -247,3 +273,26 @@ function getDataviewsErrors(dataviews) {
return errors;
}
function getMissingDataviewsSourceIds(dataviews, sourceId2Node) {
var missingDataviewsSourceIds = [];
Object.keys(dataviews).forEach(function(dataviewName) {
var dataview = dataviews[dataviewName];
var dataviewSourceId = getDataviewSourceId(dataview);
if (!sourceId2Node.hasOwnProperty(dataviewSourceId)) {
missingDataviewsSourceIds.push(new AnalysisError('Node with `source.id="' + dataviewSourceId +'"`' +
' not found in analyses for dataview "' + dataviewName + '"'));
}
});
return missingDataviewsSourceIds;
}
function AnalysisError(message) {
Error.captureStackTrace(this, this.constructor);
this.name = this.constructor.name;
this.type = 'analysis';
this.message = message;
}
require('util').inherits(AnalysisError, Error);

View File

@@ -3,10 +3,13 @@
var dot = require('dot');
dot.templateSettings.strip = false;
var queue = require('queue-async');
var SubstitutionTokens = require('../../../utils/substitution-tokens');
var PSQL = require('cartodb-psql');
var turboCarto = require('turbo-carto');
function TurboCartoAdapter(turboCartoParser) {
this.turboCartoParser = turboCartoParser;
var SubstitutionTokens = require('../../../utils/substitution-tokens');
var PostgresDatasource = require('../../../backends/turbo-carto-postgres-datasource');
function TurboCartoAdapter() {
}
module.exports = TurboCartoAdapter;
@@ -22,8 +25,8 @@ TurboCartoAdapter.prototype.getMapConfig = function (user, requestMapConfig, par
var parseCartoQueue = queue(layers.length);
layers.forEach(function(layer) {
parseCartoQueue.defer(self._parseCartoCss.bind(self), user, layer);
layers.forEach(function(layer, index) {
parseCartoQueue.defer(self._parseCartoCss.bind(self), user, params, layer, index);
});
parseCartoQueue.awaitAll(function (err, layers) {
@@ -37,25 +40,87 @@ TurboCartoAdapter.prototype.getMapConfig = function (user, requestMapConfig, par
});
};
var bboxTemplate = dot.template('(select ST_SetSRID(st_extent(the_geom_webmercator), 3857) from ({{=it._sql}}) __c)');
var zoomTemplate = dot.template([
'GREATEST(',
'ceil(log(40075017000 / 256 / GREATEST(',
' st_xmax({{=it._bbox}}) - st_xmin({{=it._bbox}}),',
' st_ymax({{=it._bbox}}) - st_ymin({{=it._bbox}})',
'))/log(2)),',
'0',
')'
var tokensQueryTpl = dot.template([
'WITH input_query AS (',
' {{=it._sql}}',
'),',
'bbox_query AS (',
' SELECT ST_SetSRID(ST_Extent(the_geom_webmercator), 3857) as bbox from input_query',
'),',
'zoom_query as (',
' SELECT GREATEST(',
' ceil(log(40075017000 / 256 / GREATEST(ST_XMax(bbox) - ST_XMin(bbox), ST_YMax(bbox) - ST_YMin(bbox)))/log(2)),',
' 0) as zoom',
' FROM bbox_query',
'),',
'pixel_size_query as (',
' SELECT 40075017 * cos(radians(ST_Y(ST_Transform(ST_Centroid(bbox), 4326)))) / 2 ^ ((zoom) + 8) as pixel_size',
' FROM bbox_query, zoom_query',
'),',
'scale_denominator_query as (',
' SELECT (pixel_size / 0.00028)::numeric as scale_denominator',
' FROM pixel_size_query',
')',
'select ST_AsText(bbox) bbox, pixel_size, scale_denominator, zoom',
'from bbox_query, pixel_size_query, scale_denominator_query, zoom_query'
].join('\n'));
var pixelSizeTemplate = dot.template('40075017 * cos(ST_Y(ST_Centroid({{=it._bbox}}))) / 2 ^ (({{=it._zoom}}) + 8)');
var scaleDenominatorTemplate = dot.template('({{=it._pixelSize}} / 0.00028)::numeric');
TurboCartoAdapter.prototype._parseCartoCss = function (username, layer, callback) {
TurboCartoAdapter.prototype._parseCartoCss = function (username, params, layer, index, callback) {
if (!shouldParseLayerCartocss(layer)) {
return callback(null, layer);
}
var pg = new PSQL(dbParamsFromReqParams(params));
function processCallback(err, cartocss) {
// Only return turbo-carto errors
if (err && err.name === 'TurboCartoError') {
var error = new Error('turbo-carto: ' + err.message);
error.http_status = 400;
error.type = 'turbo-carto';
error.context = err.context;
error.context.layer = {
index: index,
type: layer.type
};
return callback(error);
}
// Try to continue in the rest of the cases
if (cartocss) {
layer.options.cartocss = cartocss;
}
return callback(null, layer);
}
var layerSql = layer.options.sql;
var layerRawSql = layer.options.sql_raw;
if (SubstitutionTokens.hasTokens(layerSql) && layerRawSql) {
var self = this;
var tokensQuery = tokensQueryTpl({_sql: layerRawSql});
return pg.query(tokensQuery, function(err, resultSet) {
if (err) {
var error = new Error('turbo-carto: ' + err.message);
error.type = 'turbo-carto';
return callback(error);
}
resultSet = resultSet || {};
var rows = resultSet.rows || [];
var result = rows[0] || {};
var tokens = {
bbox: 'ST_SetSRID(ST_GeomFromText(\'' + result.bbox + '\'), 3857)',
scale_denominator: result.scale_denominator,
pixel_width: result.pixel_size,
pixel_height: result.pixel_size
};
var sql = SubstitutionTokens.replace(layerSql, tokens);
self.process(pg, layer.options.cartocss, sql, processCallback);
}, true); // use read-only transaction
}
var tokens = {
bbox: 'ST_MakeEnvelope(-20037508.34,-20037508.34,20037508.34,20037508.34,3857)',
scale_denominator: '500000001',
@@ -63,40 +128,35 @@ TurboCartoAdapter.prototype._parseCartoCss = function (username, layer, callback
pixel_height: '156412'
};
var layerSql = layer.options.sql;
var layerRawSql = layer.options.sql_raw;
if (SubstitutionTokens.hasTokens(layerSql) && layerRawSql) {
var bbox = bboxTemplate({ _sql: layerRawSql });
var zoom = zoomTemplate({ _bbox: bbox });
var pixelSize = pixelSizeTemplate({ _bbox: bbox, _zoom: zoom });
var scaleDenominator = scaleDenominatorTemplate({ _pixelSize: pixelSize });
tokens = {
bbox: bbox,
scale_denominator: scaleDenominator,
pixel_width: pixelSize,
pixel_height: pixelSize
};
}
var sql = SubstitutionTokens.replace(layerSql, tokens);
this.process(pg, layer.options.cartocss, sql, processCallback);
};
this.turboCartoParser.process(username, layer.options.cartocss, sql, function (err, cartocss) {
// Only return turbo-carto errors
if (err && err.name === 'TurboCartoError') {
err = new Error('turbo-carto: ' + err.message);
err.http_status = 400;
return callback(err);
}
// Try to continue in the rest of the cases
if (cartocss) {
layer.options.cartocss = cartocss;
}
return callback(null, layer);
});
TurboCartoAdapter.prototype.process = function (psql, cartocss, sql, callback) {
var datasource = new PostgresDatasource(psql, sql);
turboCarto(cartocss, datasource, callback);
};
function shouldParseLayerCartocss(layer) {
return layer && layer.options && layer.options.cartocss && layer.options.sql;
}
function dbParamsFromReqParams(params) {
var dbParams = {};
if ( params.dbuser ) {
dbParams.user = params.dbuser;
}
if ( params.dbpassword ) {
dbParams.pass = params.dbpassword;
}
if ( params.dbhost ) {
dbParams.host = params.dbhost;
}
if ( params.dbport ) {
dbParams.port = params.dbport;
}
if ( params.dbname ) {
dbParams.dbname = params.dbname;
}
return dbParams;
}

View File

@@ -33,8 +33,6 @@ var AnalysisBackend = require('./backends/analysis');
var timeoutErrorTilePath = __dirname + '/../../assets/render-timeout-fallback.png';
var timeoutErrorTile = require('fs').readFileSync(timeoutErrorTilePath, {encoding: null});
var TurboCartoParser = require('./utils/style/turbo-carto-parser');
var SqlWrapMapConfigAdapter = require('./models/mapconfig/adapter/sql-wrap-mapconfig-adapter');
var MapConfigNamedLayersAdapter = require('./models/mapconfig/adapter/mapconfig-named-layers-adapter');
var AnalysisMapConfigAdapter = require('./models/mapconfig/adapter/analysis-mapconfig-adapter');
@@ -154,15 +152,13 @@ module.exports = function(serverOptions) {
var layergroupAffectedTablesCache = new LayergroupAffectedTablesCache();
app.layergroupAffectedTablesCache = layergroupAffectedTablesCache;
var turboCartoParser = new TurboCartoParser(pgQueryRunner);
var mapConfigAdapter = new MapConfigAdapter(
new MapConfigNamedLayersAdapter(templateMaps, pgConnection),
new SqlWrapMapConfigAdapter(),
new DataviewsWidgetsAdapter(),
new AnalysisMapConfigAdapter(analysisBackend),
new MapConfigOverviewsAdapter(overviewsMetadataApi, filterStatsApi),
new TurboCartoAdapter(turboCartoParser)
new TurboCartoAdapter()
);
var namedMapProviderCache = new NamedMapProviderCache(

View File

@@ -1,15 +0,0 @@
'use strict';
var turboCarto = require('turbo-carto');
var PostgresDatasource = require('./postgres-datasource');
function TurboCartoParser (pgQueryRunner) {
this.pgQueryRunner = pgQueryRunner;
}
module.exports = TurboCartoParser;
TurboCartoParser.prototype.process = function (username, cartocss, sql, callback) {
var datasource = new PostgresDatasource(this.pgQueryRunner, username, sql);
turboCarto(cartocss, datasource, callback);
};

150
npm-shrinkwrap.json generated
View File

@@ -1,6 +1,6 @@
{
"name": "windshaft-cartodb",
"version": "2.46.0",
"version": "2.53.4",
"dependencies": {
"body-parser": {
"version": "1.14.2",
@@ -62,14 +62,14 @@
"resolved": "https://registry.npmjs.org/qs/-/qs-5.2.0.tgz"
},
"raw-body": {
"version": "2.1.6",
"version": "2.1.7",
"from": "raw-body@>=2.1.5 <2.2.0",
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.1.6.tgz",
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.1.7.tgz",
"dependencies": {
"bytes": {
"version": "2.3.0",
"from": "bytes@2.3.0",
"resolved": "https://registry.npmjs.org/bytes/-/bytes-2.3.0.tgz"
"version": "2.4.0",
"from": "bytes@2.4.0",
"resolved": "https://registry.npmjs.org/bytes/-/bytes-2.4.0.tgz"
},
"unpipe": {
"version": "1.0.0",
@@ -105,9 +105,9 @@
}
},
"camshaft": {
"version": "0.13.0",
"from": "camshaft@0.13.0",
"resolved": "https://registry.npmjs.org/camshaft/-/camshaft-0.13.0.tgz",
"version": "0.22.4",
"from": "camshaft@0.22.4",
"resolved": "https://registry.npmjs.org/camshaft/-/camshaft-0.22.4.tgz",
"dependencies": {
"async": {
"version": "1.5.2",
@@ -354,9 +354,9 @@
"resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-0.2.0.tgz"
},
"jsprim": {
"version": "1.2.2",
"version": "1.3.0",
"from": "jsprim@>=1.2.2 <2.0.0",
"resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.2.2.tgz",
"resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.3.0.tgz",
"dependencies": {
"extsprintf": {
"version": "1.0.2",
@@ -804,7 +804,7 @@
},
"type-is": {
"version": "1.6.13",
"from": "type-is@>=1.6.10 <1.7.0",
"from": "type-is@>=1.6.6 <1.7.0",
"resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.13.tgz",
"dependencies": {
"media-typer": {
@@ -1257,9 +1257,9 @@
"resolved": "https://registry.npmjs.org/step-profiler/-/step-profiler-0.3.0.tgz"
},
"turbo-carto": {
"version": "0.11.0",
"from": "turbo-carto@0.11.0",
"resolved": "https://registry.npmjs.org/turbo-carto/-/turbo-carto-0.11.0.tgz",
"version": "0.12.1",
"from": "turbo-carto@0.12.1",
"resolved": "https://registry.npmjs.org/turbo-carto/-/turbo-carto-0.12.1.tgz",
"dependencies": {
"colorbrewer": {
"version": "1.0.0",
@@ -2966,16 +2966,16 @@
"from": "node-pre-gyp@>=0.6.28 <0.7.0",
"resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.6.28.tgz"
},
"abbrev": {
"version": "1.0.7",
"from": "abbrev@>=1.0.0 <2.0.0",
"resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.0.7.tgz"
},
"ansi": {
"version": "0.3.1",
"from": "ansi@>=0.3.1 <0.4.0",
"resolved": "https://registry.npmjs.org/ansi/-/ansi-0.3.1.tgz"
},
"abbrev": {
"version": "1.0.7",
"from": "abbrev@>=1.0.0 <2.0.0",
"resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.0.7.tgz"
},
"ansi-regex": {
"version": "2.0.0",
"from": "ansi-regex@>=2.0.0 <3.0.0",
@@ -3031,16 +3031,16 @@
"from": "boom@>=2.0.0 <3.0.0",
"resolved": "https://registry.npmjs.org/boom/-/boom-2.10.1.tgz"
},
"brace-expansion": {
"version": "1.1.4",
"from": "brace-expansion@>=1.0.0 <2.0.0",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.4.tgz"
},
"caseless": {
"version": "0.11.0",
"from": "caseless@>=0.11.0 <0.12.0",
"resolved": "https://registry.npmjs.org/caseless/-/caseless-0.11.0.tgz"
},
"brace-expansion": {
"version": "1.1.4",
"from": "brace-expansion@>=1.0.0 <2.0.0",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.4.tgz"
},
"chalk": {
"version": "1.1.3",
"from": "chalk@>=1.1.1 <2.0.0",
@@ -3076,16 +3076,16 @@
"from": "debug@>=2.2.0 <2.3.0",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz"
},
"deep-extend": {
"version": "0.4.1",
"from": "deep-extend@>=0.4.0 <0.5.0",
"resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.4.1.tgz"
},
"delayed-stream": {
"version": "1.0.0",
"from": "delayed-stream@>=1.0.0 <1.1.0",
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz"
},
"deep-extend": {
"version": "0.4.1",
"from": "deep-extend@>=0.4.0 <0.5.0",
"resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.4.1.tgz"
},
"delegates": {
"version": "1.0.0",
"from": "delegates@>=1.0.0 <2.0.0",
@@ -3101,16 +3101,16 @@
"from": "escape-string-regexp@>=1.0.2 <2.0.0",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz"
},
"extend": {
"version": "3.0.0",
"from": "extend@>=3.0.0 <3.1.0",
"resolved": "https://registry.npmjs.org/extend/-/extend-3.0.0.tgz"
},
"extsprintf": {
"version": "1.0.2",
"from": "extsprintf@1.0.2",
"resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.0.2.tgz"
},
"extend": {
"version": "3.0.0",
"from": "extend@>=3.0.0 <3.1.0",
"resolved": "https://registry.npmjs.org/extend/-/extend-3.0.0.tgz"
},
"forever-agent": {
"version": "0.6.1",
"from": "forever-agent@>=0.6.1 <0.7.0",
@@ -3136,26 +3136,26 @@
"from": "gauge@>=1.2.5 <1.3.0",
"resolved": "https://registry.npmjs.org/gauge/-/gauge-1.2.7.tgz"
},
"generate-object-property": {
"version": "1.2.0",
"from": "generate-object-property@>=1.1.0 <2.0.0",
"resolved": "https://registry.npmjs.org/generate-object-property/-/generate-object-property-1.2.0.tgz"
},
"generate-function": {
"version": "2.0.0",
"from": "generate-function@>=2.0.0 <3.0.0",
"resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.0.0.tgz"
},
"glob": {
"version": "7.0.3",
"from": "glob@>=7.0.0 <8.0.0",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.0.3.tgz"
"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"
},
"graceful-fs": {
"version": "4.1.4",
"from": "graceful-fs@>=4.1.2 <5.0.0",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.4.tgz"
},
"glob": {
"version": "7.0.3",
"from": "glob@>=7.0.0 <8.0.0",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.0.3.tgz"
},
"graceful-readlink": {
"version": "1.0.1",
"from": "graceful-readlink@>=1.0.0",
@@ -3171,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"
},
"has-unicode": {
"version": "2.0.0",
"from": "has-unicode@>=2.0.0 <3.0.0",
"resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.0.tgz"
},
"hawk": {
"version": "3.1.3",
"from": "hawk@>=3.1.3 <3.2.0",
"resolved": "https://registry.npmjs.org/hawk/-/hawk-3.1.3.tgz"
},
"has-unicode": {
"version": "2.0.0",
"from": "has-unicode@>=2.0.0 <3.0.0",
"resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.0.tgz"
},
"hoek": {
"version": "2.16.3",
"from": "hoek@>=2.0.0 <3.0.0",
@@ -3236,16 +3236,16 @@
"from": "jodid25519@>=1.0.0 <2.0.0",
"resolved": "https://registry.npmjs.org/jodid25519/-/jodid25519-1.0.2.tgz"
},
"json-schema": {
"version": "0.2.2",
"from": "json-schema@0.2.2",
"resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.2.tgz"
},
"jsbn": {
"version": "0.1.0",
"from": "jsbn@>=0.1.0 <0.2.0",
"resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.0.tgz"
},
"json-schema": {
"version": "0.2.2",
"from": "json-schema@0.2.2",
"resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.2.tgz"
},
"json-stringify-safe": {
"version": "5.0.1",
"from": "json-stringify-safe@>=5.0.1 <5.1.0",
@@ -3416,15 +3416,20 @@
"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"
},
"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"
"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"
},
"tough-cookie": {
"version": "2.2.2",
@@ -3436,11 +3441,6 @@
"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",
@@ -3490,18 +3490,6 @@
}
}
},
"rc": {
"version": "1.1.6",
"from": "rc@>=1.1.0 <1.2.0",
"resolved": "https://registry.npmjs.org/rc/-/rc-1.1.6.tgz",
"dependencies": {
"minimist": {
"version": "1.2.0",
"from": "minimist@>=1.2.0 <2.0.0",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz"
}
}
},
"getpass": {
"version": "0.1.6",
"from": "getpass@>=0.1.1 <0.2.0",
@@ -3514,6 +3502,18 @@
}
}
},
"rc": {
"version": "1.1.6",
"from": "rc@>=1.1.0 <1.2.0",
"resolved": "https://registry.npmjs.org/rc/-/rc-1.1.6.tgz",
"dependencies": {
"minimist": {
"version": "1.2.0",
"from": "minimist@>=1.2.0 <2.0.0",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz"
}
}
},
"sshpk": {
"version": "1.8.3",
"from": "sshpk@>=1.7.0 <2.0.0",

View File

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

View File

@@ -20,6 +20,13 @@ describe('analysis-layers error cases', function() {
}
};
var AUTH_ERROR_RESPONSE = {
status: 403,
headers: {
'Content-Type': 'application/json; charset=utf-8'
}
};
it('should handle missing analysis nodes for layers', function(done) {
var mapConfig = createMapConfig(
[
@@ -64,4 +71,200 @@ describe('analysis-layers error cases', function() {
testClient.drain(done);
});
});
it('should handle missing analyses when layers point to nonexistent one', function(done) {
var mapConfig = createMapConfig(
[
{
"type": "http",
"options": {
"urlTemplate": "http://{s}.basemaps.cartocdn.com/light_only_labels/{z}/{x}/{y}.png",
"subdomains": "abcd"
}
},
{
"type": "cartodb",
"options": {
"source": {
"id": "ID-FOR-NONEXISTENT-ANALYSIS"
},
"cartocss": '#polygons { polygon-fill: red; }',
"cartocss_version": "2.3.0"
}
}
]
);
var testClient = new TestClient(mapConfig, 1234);
testClient.getLayergroup(ERROR_RESPONSE, function(err, layergroupResult) {
assert.ok(!err, err);
assert.equal(layergroupResult.errors.length, 1);
assert.equal(
layergroupResult.errors[0],
'Missing analysis node.id="ID-FOR-NONEXISTENT-ANALYSIS" for layer=1'
);
testClient.drain(done);
});
});
it('should handle missing analyses when dataviews point to nonexistent one', function(done) {
var mapConfig = createMapConfig(
[
{
"type": "http",
"options": {
"urlTemplate": "http://{s}.basemaps.cartocdn.com/light_only_labels/{z}/{x}/{y}.png",
"subdomains": "abcd"
}
},
{
"type": "cartodb",
"options": {
"sql": "select * from populated_places_simple_reduced",
"cartocss": '#polygons { polygon-fill: red; }',
"cartocss_version": "2.3.0"
}
}
],
{
pop_max_histogram: {
source: {
id: 'ID-FOR-NONEXISTENT-ANALYSIS'
},
type: 'histogram',
options: {
column: 'pop_max'
}
}
}
);
var testClient = new TestClient(mapConfig, 1234);
testClient.getLayergroup(ERROR_RESPONSE, function(err, layergroupResult) {
assert.ok(!err, err);
assert.equal(layergroupResult.errors.length, 1);
assert.equal(layergroupResult.errors[0], 'Node with `source.id="ID-FOR-NONEXISTENT-ANALYSIS"`' +
' not found in analyses for dataview "pop_max_histogram"');
testClient.drain(done);
});
});
it('camshaft: should return error missing analysis nodes for layers with some context', function(done) {
var mapConfig = createMapConfig(
[
{
"type": "cartodb",
"options": {
"source": {
"id": "HEAD"
},
"cartocss": '#polygons { polygon-fill: red; }',
"cartocss_version": "2.3.0"
}
}
],
{},
[
{
"id": "HEAD",
"type": "buffer",
"params": {
"source": {
"id": "HEAD",
"type": "source",
"params": {
"query": "select * from populated_places_simple_reduced"
}
},
"radius": 50000
}
}
]
);
var testClient = new TestClient(mapConfig, 11111);
testClient.getLayergroup(AUTH_ERROR_RESPONSE, function(err, layergroupResult) {
assert.ok(!err, err);
assert.equal(layergroupResult.errors.length, 1);
assert.equal(
layergroupResult.errors[0],
'Analysis requires authentication with API key: permission denied.'
);
assert.equal(layergroupResult.errors_with_context[0].type, 'analysis');
assert.equal(
layergroupResult.errors_with_context[0].message,
'Analysis requires authentication with API key: permission denied.'
);
assert.equal(layergroupResult.errors_with_context[0].context.layer.index, 0);
assert.equal(layergroupResult.errors_with_context[0].context.layer.id, 'HEAD');
assert.equal(layergroupResult.errors_with_context[0].context.layer.type, 'buffer');
testClient.drain(done);
});
});
it('camshaft: should return error: Missing required param "radius"; with context', function(done) {
var mapConfig = createMapConfig(
[
{
"type": "cartodb",
"options": {
"source": {
"id": "HEAD"
},
"cartocss": '#polygons { polygon-fill: red; }',
"cartocss_version": "2.3.0"
}
}
],
{},
[
{
"id": "HEAD",
"type": "buffer",
"params": {
"source": {
"id": "HEAD",
"type": "source",
"params": {
"query": "select * from populated_places_simple_reduced"
}
}
}
}
]
);
var testClient = new TestClient(mapConfig, 1234);
testClient.getLayergroup(ERROR_RESPONSE, function(err, layergroupResult) {
assert.ok(!err, err);
assert.equal(layergroupResult.errors.length, 1);
assert.equal(
layergroupResult.errors[0],
'Missing required param "radius"'
);
assert.equal(layergroupResult.errors_with_context[0].type, 'analysis');
assert.equal(layergroupResult.errors_with_context[0].message, 'Missing required param "radius"');
assert.equal(layergroupResult.errors_with_context[0].context.layer.index, 0);
assert.equal(layergroupResult.errors_with_context[0].context.layer.id, 'HEAD');
assert.equal(layergroupResult.errors_with_context[0].context.layer.type, 'buffer');
testClient.drain(done);
});
});
});

View File

@@ -0,0 +1,59 @@
require('../../support/test_helper');
var assert = require('../../support/assert');
var TestClient = require('../../support/test-client');
describe('aggregations', function() {
afterEach(function(done) {
if (this.testClient) {
this.testClient.drain(done);
} else {
done();
}
});
function aggregationOperationMapConfig(operation) {
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: {
adm0name: {
type: 'aggregation',
options: {
column: 'adm0name',
aggregation: operation,
aggregationColumn: 'pop_max'
}
}
}
}
}
]
};
}
var operations = ['count', 'sum', 'avg', 'max', 'min'];
operations.forEach(function(operation) {
it('should be able to use "' + operation + '" as aggregation operation', function(done) {
this.testClient = new TestClient(aggregationOperationMapConfig(operation));
this.testClient.getDataview('adm0name', { own_filter: 0 }, function (err, aggregation) {
assert.ok(!err, err);
assert.ok(aggregation);
assert.equal(aggregation.type, 'aggregation');
assert.equal(aggregation.aggregation, operation);
done();
});
});
});
});

View File

@@ -0,0 +1,87 @@
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();
}
});
var ERROR_RESPONSE = {
status: 400,
headers: {
'Content-Type': 'application/json; charset=utf-8'
}
};
function createMapConfig(dataviews) {
return {
version: '1.5.0',
layers: [
{
"type": "cartodb",
"options": {
"source": {
"id": "HEAD"
},
"cartocss": "#points { marker-width: 10; marker-fill: red; }",
"cartocss_version": "2.3.0"
}
}
],
dataviews: dataviews,
analyses: [
{
"id": "HEAD",
"type": "source",
"params": {
"query": "select null::geometry the_geom_webmercator, x from generate_series(0,1000) x"
}
}
]
};
}
it('should fail when invalid dataviews object is provided, string case', function(done) {
var mapConfig = createMapConfig("wadus-string");
this.testClient = new TestClient(mapConfig, 1234);
this.testClient.getLayergroup(ERROR_RESPONSE, function(err, errObj) {
assert.ok(!err, err);
assert.deepEqual(errObj.errors, [ '"dataviews" must be a valid JSON object: "string" type found' ]);
done();
});
});
it('should fail when invalid dataviews object is provided, array case', function(done) {
var mapConfig = createMapConfig([]);
this.testClient = new TestClient(mapConfig, 1234);
this.testClient.getLayergroup(ERROR_RESPONSE, function(err, errObj) {
assert.ok(!err, err);
assert.deepEqual(errObj.errors, [ '"dataviews" must be a valid JSON object: "array" type found' ]);
done();
});
});
it('should work with empty but valid objects', function(done) {
var mapConfig = createMapConfig({});
this.testClient = new TestClient(mapConfig, 1234);
this.testClient.getLayergroup(function(err, layergroup) {
assert.ok(!err, err);
assert.ok(layergroup);
assert.ok(layergroup.layergroupid);
done();
});
});
});

View File

@@ -106,7 +106,7 @@ describe('render limits', function() {
},
function(res) {
var parsed = JSON.parse(res.body);
assert.deepEqual(parsed, { errors: [ 'Render timed out' ] });
assert.deepEqual(parsed.errors, [ 'Render timed out' ]);
done();
}
);
@@ -171,7 +171,7 @@ describe('render limits', function() {
},
function(res) {
var parsed = JSON.parse(res.body);
assert.deepEqual(parsed, { errors: ['Render timed out'] });
assert.deepEqual(parsed.errors, ['Render timed out']);
done();
}
);

View File

@@ -228,7 +228,7 @@ describe('tests from old api translated to multilayer', function() {
},
function(res) {
var parsed = JSON.parse(res.body);
assert.deepEqual(parsed, { errors: [ 'Unexpected token W' ] });
assert.deepEqual(parsed.errors, [ 'Unexpected token W' ]);
done();
}
@@ -334,9 +334,7 @@ describe('tests from old api translated to multilayer', function() {
assert.ok(!res.headers.hasOwnProperty('x-cache-channel'));
var parsed = JSON.parse(res.body);
assert.deepEqual(parsed, {
errors: ["fake error message"]
});
assert.deepEqual(parsed.errors, ["fake error message"]);
done();
}

View File

@@ -181,7 +181,7 @@ describe('named_layers', function() {
}
var parsedBody = JSON.parse(response.body);
assert.deepEqual(parsedBody, { errors: ["Template 'nonexistent' of user 'localhost' not found"] });
assert.deepEqual(parsedBody.errors, ["Template 'nonexistent' of user 'localhost' not found"]);
return null;
},
@@ -234,10 +234,7 @@ describe('named_layers', function() {
}
var parsedBody = JSON.parse(response.body);
assert.deepEqual(
parsedBody,
{ errors: [ "Unauthorized 'auth_valid_template' template instantiation" ] }
);
assert.deepEqual(parsedBody.errors, [ "Unauthorized 'auth_valid_template' template instantiation" ]);
return null;
},
@@ -347,7 +344,7 @@ describe('named_layers', function() {
}
var parsedBody = JSON.parse(response.body);
assert.deepEqual(parsedBody, { errors: [ 'Nested named layers are not allowed' ] });
assert.deepEqual(parsedBody.errors, ['Nested named layers are not allowed' ]);
return null;
},

View File

@@ -169,8 +169,8 @@ describe('named maps authentication', function() {
getNamedTile(nonexistentName, 0, 0, 0, { status: 404 }, function(err, res) {
assert.ok(!err);
assert.deepEqual(
JSON.parse(res.body),
{ errors: ["Template '" + nonexistentName + "' of user '" + username + "' not found"] }
JSON.parse(res.body).errors,
["Template '" + nonexistentName + "' of user '" + username + "' not found"]
);
done();
});
@@ -179,7 +179,7 @@ describe('named maps authentication', function() {
it('should return 403 if not properly authorized', function(done) {
getNamedTile(tokenAuthTemplateName, 0, 0, 0, { status: 403 }, function(err, res) {
assert.ok(!err);
assert.deepEqual(JSON.parse(res.body), { errors: ['Unauthorized template instantiation'] });
assert.deepEqual(JSON.parse(res.body).errors, ['Unauthorized template instantiation']);
done();
});
});
@@ -238,8 +238,8 @@ describe('named maps authentication', function() {
getStaticMap(nonexistentName, { status: 404 }, function(err, res) {
assert.ok(!err);
assert.deepEqual(
JSON.parse(res.body),
{ errors: ["Template '" + nonexistentName + "' of user '" + username + "' not found"] }
JSON.parse(res.body).errors,
["Template '" + nonexistentName + "' of user '" + username + "' not found"]
);
done();
});
@@ -248,7 +248,7 @@ describe('named maps authentication', function() {
it('should return 403 if not properly authorized', function(done) {
getStaticMap(tokenAuthTemplateName, { status: 403 }, function(err, res) {
assert.ok(!err);
assert.deepEqual(JSON.parse(res.body), { errors: ['Unauthorized template instantiation'] });
assert.deepEqual(JSON.parse(res.body).errors, ['Unauthorized template instantiation']);
done();
});
});

View File

@@ -124,8 +124,8 @@ describe('named maps provider cache', function() {
getNamedTile({ statusCode: 404 }, function(err, res) {
assert.ok(!err);
assert.deepEqual(
JSON.parse(res.body),
{ errors: ["Template 'template_with_color' of user 'localhost' not found"] }
JSON.parse(res.body).errors,
["Template 'template_with_color' of user 'localhost' not found"]
);
// add template again so it's clean in afterEach

View File

@@ -231,7 +231,11 @@ describe('attributes', function() {
assert.equal(res.statusCode, 200, res.statusCode + ': ' + res.body);
assert.equal(
res.body,
'/**/ typeof test === \'function\' && test({"errors":["Layer 0 has no exposed attributes"]});'
'/**/ typeof test === \'function\' && ' +
'test({"errors":["Layer 0 has no exposed attributes"],' +
'"errors_with_context":[{' +
'"type":"unknown","message":"Layer 0 has no exposed attributes","context":"unknown"' +
'}]});'
);
return null;
},

View File

@@ -138,11 +138,9 @@ describe('blend http fallback', function() {
testClient.getTileLayer(mapConfig, tileRequest, expectedResponse, function(err, res) {
assert.ok(!err);
var parsedBody = JSON.parse(res.body);
assert.deepEqual(parsedBody, {
errors: [
"Unable to fetch http tile: http://127.0.0.1:8033/error404/1/0/0.png [404]"
]
});
assert.deepEqual(parsedBody.errors, [
"Unable to fetch http tile: http://127.0.0.1:8033/error404/1/0/0.png [404]"
]);
done();
});
});

View File

@@ -119,12 +119,11 @@ describe('external resources', function() {
var mapConfig = testClient.defaultTableMapConfig('test_table_3', style);
testClient.createLayergroup(mapConfig, { statusCode: 400 }, function(err, res) {
assert.deepEqual(JSON.parse(res.body), {
errors: ["Unable to download '" + url + "' for 'style0' (server returned 404)"]
});
assert.deepEqual(JSON.parse(res.body).errors, [
"Unable to download '" + url + "' for 'style0' (server returned 404)"]
);
done();
});
});
});

View File

@@ -31,7 +31,7 @@ describe('multilayer error cases', function() {
}, {}, function(res) {
assert.equal(res.statusCode, 400, res.body);
var parsedBody = JSON.parse(res.body);
assert.deepEqual(parsedBody, {"errors":["layergroup POST data must be of type application/json"]});
assert.deepEqual(parsedBody.errors, ["layergroup POST data must be of type application/json"]);
done();
});
});
@@ -44,7 +44,7 @@ describe('multilayer error cases', function() {
}, {}, function(res) {
assert.equal(res.statusCode, 400, res.body);
var parsedBody = JSON.parse(res.body);
assert.deepEqual(parsedBody, {"errors":["Missing layers array from layergroup config"]});
assert.deepEqual(parsedBody.errors, ["Missing layers array from layergroup config"]);
done();
});
});
@@ -58,7 +58,10 @@ describe('multilayer error cases', function() {
assert.equal(res.statusCode, 200);
assert.equal(
res.body,
'/**/ typeof test === \'function\' && test({"errors":["Missing layers array from layergroup config"]});'
'/**/ typeof test === \'function\' && ' +
'test({"errors":["Missing layers array from layergroup config"],' +
'"errors_with_context":[{"type":"unknown",' +
'"message":"Missing layers array from layergroup config","context":"unknown"}]});'
);
done();
});
@@ -83,7 +86,7 @@ describe('multilayer error cases', function() {
}, {}, function(res) {
assert.equal(res.statusCode, 400, res.body);
var parsedBody = JSON.parse(res.body);
assert.deepEqual(parsedBody, {errors:["Missing cartocss_version for layer 0 options"]});
assert.deepEqual(parsedBody.errors, ["Missing cartocss_version for layer 0 options"]);
done();
});
});
@@ -355,7 +358,7 @@ describe('multilayer error cases', function() {
var mapConfig = testClient.singleLayerMapConfig('select * from test_table', null, null, 'name');
testClient.getGrid(mapConfig, 1, 13, 4011, 3088, defaultErrorExpectedResponse, function(err, res) {
assert.deepEqual(JSON.parse(res.body), { errors: ["Layer '1' not found in layergroup"] });
assert.deepEqual(JSON.parse(res.body).errors, ["Layer '1' not found in layergroup"]);
done();
});
});
@@ -383,7 +386,7 @@ describe('multilayer error cases', function() {
// FIXME: should be 404
assert.equal(res.statusCode, 400, res.statusCode + ':' + res.body);
var parsed = JSON.parse(res.body);
assert.deepEqual(parsed, {"errors": ["Invalid or nonexistent map configuration token 'deadbeef'"]});
assert.deepEqual(parsed.errors, ["Invalid or nonexistent map configuration token 'deadbeef'"]);
return null;
},
function finish(err) {

View File

@@ -148,11 +148,10 @@ describe('raster', function() {
assert.ok(!err);
checkCORSHeaders(res);
var parsedBody = JSON.parse(res.body);
assert.deepEqual(parsedBody, { errors: [ 'Mapnik raster layers do not support interactivity' ] });
assert.deepEqual(parsedBody.errors, [ 'Mapnik raster layers do not support interactivity' ]);
done();
}
);
});
});

View File

@@ -29,7 +29,7 @@ describe('regressions', function() {
contentType: 'application/json; charset=utf-8'
};
requestTile('/0/0/0.png?testUnexpectedError=1', options, function(err, res) {
assert.deepEqual(JSON.parse(res.body), { "errors": ["test unexpected error"] });
assert.deepEqual(JSON.parse(res.body).errors, ["test unexpected error"]);
finish(done);
});
});

View File

@@ -129,7 +129,7 @@ describe('retina support', function() {
},
function(res, err) {
assert.ok(!err, 'Failed to request 0/0/0' + scaleFactor + '.png tile');
assert.deepEqual(JSON.parse(res.body), { errors: ["Tile with specified resolution not found"] } );
assert.deepEqual(JSON.parse(res.body).errors, ["Tile with specified resolution not found"]);
done();
}

View File

@@ -112,7 +112,7 @@ describe('server', function() {
}
};
testClient.getGrid(mapConfig, 0, 13, 4011, 3088, expectedResponse, function(err, res) {
assert.deepEqual(JSON.parse(res.body), {"errors":["Tileset has no interactivity"]});
assert.deepEqual(JSON.parse(res.body).errors, ["Tileset has no interactivity"]);
done();
});
});

View File

@@ -1927,9 +1927,8 @@ describe('template_api', function() {
if (err) {
return done(err);
}
assert.deepEqual(JSON.parse(res.body), {
errors: ["Invalid or nonexistent map configuration token '" + nonexistentToken + "'"]
});
assert.deepEqual(JSON.parse(res.body).errors,
["Invalid or nonexistent map configuration token '" + nonexistentToken + "'"]);
done();
};

View File

@@ -106,4 +106,34 @@ describe('turbo-carto error cases', function() {
done();
});
});
it('turbo-carto: should return error invalid column from datasource with some context', function(done) {
this.testClient = new TestClient(makeMapconfig(null, 'ramp([wadus_column], (red, green, blue))'));
this.testClient.getLayergroup(ERROR_RESPONSE, function(err, layergroup) {
assert.ok(!err, err);
assert.ok(layergroup.hasOwnProperty('errors'));
assert.equal(layergroup.errors_with_context.length, 1);
assert.equal(layergroup.errors_with_context[0].type, 'turbo-carto');
assert.ok(layergroup.errors_with_context[0].message.match(/^turbo-carto/));
assert.ok(layergroup.errors_with_context[0].message.match(/unable\sto\scompute\sramp/i));
assert.ok(layergroup.errors_with_context[0].message.match(/wadus_column/));
assert.equal(layergroup.errors_with_context[0].context.layer.index, 0);
assert.equal(layergroup.errors_with_context[0].context.layer.type, 'mapnik');
assert.equal(layergroup.errors_with_context[0].context.selector, '#populated_places_simple_reduced');
assert.deepEqual(layergroup.errors_with_context[0].context.source, {
start: {
line: 10,
column: 3
},
end: {
line: 10,
column: 56
}
});
done();
});
});
});

View File

@@ -22,215 +22,231 @@ describe('turbo-carto for named maps', function() {
var templateId = 'turbo-carto-template-1';
var template = {
version: '0.0.1',
name: templateId,
auth: { method: 'open' },
placeholders: {
color: {
type: "css_color",
default: "Reds"
}
},
layergroup: {
version: '1.0.0',
layers: [{
options: {
sql: [
'SELECT test_table.*, _prices.price FROM test_table JOIN (' +
' SELECT 1 AS cartodb_id, 10.00 AS price',
' UNION',
' SELECT 2, 10.50',
' UNION',
' SELECT 3, 11.00',
' UNION',
' SELECT 4, 12.00',
' UNION',
' SELECT 5, 21.00',
') _prices ON _prices.cartodb_id = test_table.cartodb_id'
].join('\n'),
cartocss: [
'#layer {',
' marker-fill: ramp([price], colorbrewer(<%= color %>));',
' marker-allow-overlap:true;',
'}'
].join('\n'),
cartocss_version: '2.0.2'
}
function template(table) {
return {
version: '0.0.1',
name: templateId,
auth: { method: 'open' },
placeholders: {
color: {
type: "css_color",
default: "Reds"
}
]
}
};
},
layergroup: {
version: '1.0.0',
layers: [
{
options: {
sql: [
'SELECT ' + table + '.*, _prices.price FROM ' + table + ' JOIN (' +
' SELECT 1 AS cartodb_id, 10.00 AS price',
' UNION',
' SELECT 2, 10.50',
' UNION',
' SELECT 3, 11.00',
' UNION',
' SELECT 4, 12.00',
' UNION',
' SELECT 5, 21.00',
') _prices ON _prices.cartodb_id = ' + table + '.cartodb_id'
].join('\n'),
cartocss: [
'#layer {',
' marker-fill: ramp([price], colorbrewer(<%= color %>));',
' marker-allow-overlap:true;',
'}'
].join('\n'),
cartocss_version: '2.0.2'
}
}
]
}
};
}
var templateParamsReds = { color: 'Reds' };
var templateParamsBlues = { color: 'Blues' };
it('should create a template with turbo-carto parsed properly', function (done) {
step(
function postTemplate() {
var next = this;
var scenarios = [
{
desc: 'with public tables',
table: 'test_table'
},
{
desc: 'with private tables',
table: 'test_table_private_1'
}
];
assert.response(server, {
url: '/api/v1/map/named?api_key=1234',
method: 'POST',
headers: { host: 'localhost', 'Content-Type': 'application/json' },
data: JSON.stringify(template)
}, {},
function (res, err) {
next(err, res);
});
},
function checkTemplate(err, res) {
assert.ifError(err);
assert.equal(res.statusCode, 200);
assert.deepEqual(JSON.parse(res.body), {
template_id: templateId
});
scenarios.forEach(function(scenario) {
it('should create a template with turbo-carto parsed properly: ' + scenario.desc, function (done) {
step(
function postTemplate() {
var next = this;
return null;
},
function instantiateTemplateWithReds(err) {
assert.ifError(err);
assert.response(server, {
url: '/api/v1/map/named?api_key=1234',
method: 'POST',
headers: { host: 'localhost', 'Content-Type': 'application/json' },
data: JSON.stringify(template(scenario.table))
}, {},
function (res, err) {
next(err, res);
});
},
function checkTemplate(err, res) {
assert.ifError(err);
assert.equal(res.statusCode, 200);
assert.deepEqual(JSON.parse(res.body), {
template_id: templateId
});
var next = this;
assert.response(server, {
url: '/api/v1/map/named/' + templateId,
method: 'POST',
headers: {
host: 'localhost',
'Content-Type': 'application/json'
},
data: JSON.stringify(templateParamsReds)
}, {},
function(res, err) {
return next(err, res);
});
},
function checkInstanciationWithReds(err, res) {
assert.ifError(err);
return null;
},
function instantiateTemplateWithReds(err) {
assert.ifError(err);
assert.equal(res.statusCode, 200);
var next = this;
assert.response(server, {
url: '/api/v1/map/named/' + templateId,
method: 'POST',
headers: {
host: 'localhost',
'Content-Type': 'application/json'
},
data: JSON.stringify(templateParamsReds)
}, {},
function(res, err) {
return next(err, res);
});
},
function checkInstanciationWithReds(err, res) {
assert.ifError(err);
var parsedBody = JSON.parse(res.body);
assert.equal(res.statusCode, 200);
keysToDelete['map_cfg|' + LayergroupToken.parse(parsedBody.layergroupid).token] = 0;
keysToDelete['user:localhost:mapviews:global'] = 5;
var parsedBody = JSON.parse(res.body);
assert.ok(parsedBody.layergroupid);
assert.ok(parsedBody.last_updated);
keysToDelete['map_cfg|' + LayergroupToken.parse(parsedBody.layergroupid).token] = 0;
keysToDelete['user:localhost:mapviews:global'] = 5;
return parsedBody.layergroupid;
},
function requestTileReds(err, layergroupId) {
assert.ifError(err);
assert.ok(parsedBody.layergroupid);
assert.ok(parsedBody.last_updated);
var next = this;
return parsedBody.layergroupid;
},
function requestTileReds(err, layergroupId) {
assert.ifError(err);
assert.response(server, {
url: '/api/v1/map/' + layergroupId + '/0/0/0.png',
method: 'GET',
headers: { host: 'localhost' },
encoding: 'binary'
}, {},
function(res, err) {
next(err, res);
});
},
function checkTileReds(err, res) {
assert.ifError(err);
var next = this;
var next = this;
assert.response(server, {
url: '/api/v1/map/' + layergroupId + '/0/0/0.png',
method: 'GET',
headers: { host: 'localhost' },
encoding: 'binary'
}, {},
function(res, err) {
next(err, res);
});
},
function checkTileReds(err, res) {
assert.ifError(err);
assert.equal(res.statusCode, 200);
assert.equal(res.headers['content-type'], 'image/png');
var next = this;
var fixturePath = './test/fixtures/turbo-carto-named-maps-reds.png';
var image = mapnik.Image.fromBytes(new Buffer(res.body, 'binary'));
assert.equal(res.statusCode, 200);
assert.equal(res.headers['content-type'], 'image/png');
assert.imageIsSimilarToFile(image, fixturePath, IMAGE_TOLERANCE_PER_MIL, next);
},
function instantiateTemplateWithBlues(err) {
assert.ifError(err);
var fixturePath = './test/fixtures/turbo-carto-named-maps-reds.png';
var image = mapnik.Image.fromBytes(new Buffer(res.body, 'binary'));
var next = this;
assert.response(server, {
url: '/api/v1/map/named/' + templateId,
method: 'POST',
headers: {
host: 'localhost',
'Content-Type': 'application/json'
},
data: JSON.stringify(templateParamsBlues)
}, {},
function(res, err) {
return next(err, res);
});
},
function checkInstanciationWithBlues(err, res) {
assert.ifError(err);
assert.equal(res.statusCode, 200);
assert.imageIsSimilarToFile(image, fixturePath, IMAGE_TOLERANCE_PER_MIL, next);
},
function instantiateTemplateWithBlues(err) {
assert.ifError(err);
var parsedBody = JSON.parse(res.body);
var next = this;
assert.response(server, {
url: '/api/v1/map/named/' + templateId,
method: 'POST',
headers: {
host: 'localhost',
'Content-Type': 'application/json'
},
data: JSON.stringify(templateParamsBlues)
}, {},
function(res, err) {
return next(err, res);
});
},
function checkInstanciationWithBlues(err, res) {
assert.ifError(err);
assert.equal(res.statusCode, 200);
keysToDelete['map_cfg|' + LayergroupToken.parse(parsedBody.layergroupid).token] = 0;
keysToDelete['user:localhost:mapviews:global'] = 5;
var parsedBody = JSON.parse(res.body);
assert.ok(parsedBody.layergroupid);
assert.ok(parsedBody.last_updated);
keysToDelete['map_cfg|' + LayergroupToken.parse(parsedBody.layergroupid).token] = 0;
keysToDelete['user:localhost:mapviews:global'] = 5;
return parsedBody.layergroupid;
},
function requestTileBlues(err, layergroupId) {
assert.ifError(err);
assert.ok(parsedBody.layergroupid);
assert.ok(parsedBody.last_updated);
var next = this;
return parsedBody.layergroupid;
},
function requestTileBlues(err, layergroupId) {
assert.ifError(err);
assert.response(server, {
url: '/api/v1/map/' + layergroupId + '/0/0/0.png',
method: 'GET',
headers: { host: 'localhost' },
encoding: 'binary'
}, {},
function(res, err) {
next(err, res);
});
},
function checkTileBlues(err, res) {
assert.ifError(err);
var next = this;
var next = this;
assert.response(server, {
url: '/api/v1/map/' + layergroupId + '/0/0/0.png',
method: 'GET',
headers: { host: 'localhost' },
encoding: 'binary'
}, {},
function(res, err) {
next(err, res);
});
},
function checkTileBlues(err, res) {
assert.ifError(err);
assert.equal(res.statusCode, 200);
assert.equal(res.headers['content-type'], 'image/png');
var next = this;
var fixturePath = './test/fixtures/turbo-carto-named-maps-blues.png';
var image = mapnik.Image.fromBytes(new Buffer(res.body, 'binary'));
assert.equal(res.statusCode, 200);
assert.equal(res.headers['content-type'], 'image/png');
assert.imageIsSimilarToFile(image, fixturePath, IMAGE_TOLERANCE_PER_MIL, next);
},
function deleteTemplate(err) {
assert.ifError(err);
var fixturePath = './test/fixtures/turbo-carto-named-maps-blues.png';
var image = mapnik.Image.fromBytes(new Buffer(res.body, 'binary'));
var next = this;
assert.imageIsSimilarToFile(image, fixturePath, IMAGE_TOLERANCE_PER_MIL, next);
},
function deleteTemplate(err) {
assert.ifError(err);
assert.response(server, {
url: '/api/v1/map/named/' + templateId + '?api_key=1234',
method: 'DELETE',
headers: { host: 'localhost' }
}, {}, function (res, err) {
next(err, res);
});
},
function checkDeleteTemplate(err, res) {
assert.ifError(err);
assert.equal(res.statusCode, 204);
assert.ok(!res.body);
var next = this;
return null;
},
function finish(err) {
done(err);
}
);
assert.response(server, {
url: '/api/v1/map/named/' + templateId + '?api_key=1234',
method: 'DELETE',
headers: { host: 'localhost' }
}, {}, function (res, err) {
next(err, res);
});
},
function checkDeleteTemplate(err, res) {
assert.ifError(err);
assert.equal(res.statusCode, 204);
assert.ok(!res.body);
return null;
},
function finish(err) {
done(err);
}
);
});
});
});

View File

@@ -24,6 +24,8 @@ describe('turbo-carto regressions', function() {
afterEach(function (done) {
if (this.testClient) {
this.testClient.drain(done);
} else {
done();
}
});
@@ -64,6 +66,57 @@ describe('turbo-carto regressions', function() {
});
});
it('should fail for private tables', function(done) {
var cartocss = [
"#private_table {",
" marker-placement: point;",
" marker-allow-overlap: true;",
" marker-line-width: 0;",
" marker-fill-opacity: 1.0;",
" marker-width: ramp([cartodb_id], 10, 20);",
" marker-fill: red;",
"}"
].join('\n');
this.testClient = new TestClient(makeMapconfig('SELECT * FROM test_table_private_1', cartocss));
this.testClient.getLayergroup(TestClient.RESPONSE.ERROR, function(err, layergroup) {
assert.ok(!err, err);
assert.ok(!layergroup.hasOwnProperty('layergroupid'));
assert.ok(layergroup.hasOwnProperty('errors'));
var turboCartoError = layergroup.errors_with_context[0];
assert.ok(turboCartoError);
assert.equal(turboCartoError.type, 'turbo-carto');
assert.ok(turboCartoError.message.match(/permission\sdenied\sfor\srelation\stest_table_private_1/));
done();
});
});
it('should work for private tables with api key', function(done) {
var cartocss = [
"#private_table {",
" marker-placement: point;",
" marker-allow-overlap: true;",
" marker-line-width: 0;",
" marker-fill-opacity: 1.0;",
" marker-width: ramp([cartodb_id], 10, 20);",
" marker-fill: red;",
"}"
].join('\n');
this.testClient = new TestClient(makeMapconfig('SELECT * FROM test_table_private_1', cartocss), 1234);
this.testClient.getLayergroup(function(err, layergroup) {
assert.ok(!err, err);
assert.ok(layergroup.hasOwnProperty('layergroupid'));
assert.ok(!layergroup.hasOwnProperty('errors'));
done();
});
});
it('should work with mapnik substitution tokens', function(done) {
var cartocss = [
"#layer {",
@@ -167,4 +220,83 @@ describe('turbo-carto regressions', function() {
done();
});
});
describe('empty datasource results', function() {
afterEach(function (done) {
if (this.testClient) {
this.testClient.drain(done);
} else {
done();
}
});
function emptyResultMapConfig(markerFillRule) {
var cartocss = [
"#county_points_with_population {",
" marker-placement: point;",
" marker-allow-overlap: true;",
" marker-fill-opacity: 1.0;",
" marker-fill: " + markerFillRule + ';',
" marker-line-width: 0;",
"}"
].join('\n');
return {
"version": "1.5.0",
"layers": [
{
"type": 'mapnik',
"options": {
"cartocss_version": '2.3.0',
"source": {
"id": "head"
},
"cartocss": cartocss
}
}
],
"analyses": [
{
"id": "head",
"type": "source",
"params": {
"query": "SELECT * FROM populated_places_simple_reduced limit 0"
}
}
]
};
}
it('should work for numeric ramps', function(done) {
var makerFillRule = 'ramp([pop_max], (#E5F5F9,#99D8C9,#2CA25F), jenks)';
this.testClient = new TestClient(emptyResultMapConfig(makerFillRule), 1234);
this.testClient.getLayergroup(function(err, layergroup) {
assert.ok(!err, err);
assert.ok(layergroup.hasOwnProperty('layergroupid'));
assert.ok(!layergroup.hasOwnProperty('errors'));
done();
});
});
it('should work for category ramps', function(done) {
var makerFillRule = 'ramp([adm0name], (#E5F5F9,#99D8C9,#2CA25F), category)';
this.testClient = new TestClient(emptyResultMapConfig(makerFillRule), 1234);
this.testClient.getLayergroup(function(err, layergroup) {
assert.ok(!err, err);
assert.ok(layergroup.hasOwnProperty('layergroupid'));
assert.ok(!layergroup.hasOwnProperty('errors'));
done();
});
});
});
});

View File

@@ -76,7 +76,7 @@ if test x"$PREPARE_PGSQL" = xyes; then
createdb -Ttemplate_postgis -EUTF8 "${TEST_DB}" || die "Could not create test database"
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'
REMOTE_SQL_SCRIPTS='CDB_QueryStatements CDB_QueryTables CDB_CartodbfyTable CDB_TableMetadata CDB_ForeignTable CDB_UserTables CDB_ColumnNames CDB_AnalysisCatalog CDB_ZoomFromScale CDB_OverviewsSupport CDB_Overviews CDB_QuantileBins CDB_JenksBins CDB_HeadsTailsBins CDB_EqualIntervalBins CDB_Hexagon CDB_XYZ'
CURL_ARGS=""
for i in ${REMOTE_SQL_SCRIPTS}

View File

@@ -24,6 +24,15 @@ function TestClient(mapConfig, apiKey) {
module.exports = TestClient;
module.exports.RESPONSE = {
ERROR: {
status: 400,
headers: {
'Content-Type': 'application/json; charset=utf-8'
}
}
};
TestClient.prototype.getWidget = function(widgetName, params, callback) {
var self = this;