Merge remote-tracking branch 'origin/master' into layergroup-token-param
This commit is contained in:
commit
78404b1308
82
NEWS.md
82
NEWS.md
@ -1,8 +1,88 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
## 3.10.2
|
## 3.12.11
|
||||||
Released 2017-mm-dd
|
Released 2017-mm-dd
|
||||||
|
|
||||||
|
## 3.12.10
|
||||||
|
Released 2017-09-18
|
||||||
|
- Upgrades windshaft to [3.3.2](https://github.com/CartoDB/windshaft/releases/tag/3.3.2).
|
||||||
|
|
||||||
|
## 3.12.9
|
||||||
|
Released 2017-09-07
|
||||||
|
|
||||||
|
Bug fixes:
|
||||||
|
- Do not use distinct when calculating quantiles. #743
|
||||||
|
|
||||||
|
## 3.12.8
|
||||||
|
Released 2017-09-07
|
||||||
|
|
||||||
|
Bug fixes:
|
||||||
|
- Integer out of range in date histograms. (https://github.com/CartoDB/support/issues/962)
|
||||||
|
|
||||||
|
## 3.12.7
|
||||||
|
Released 2017-09-01
|
||||||
|
|
||||||
|
- Upgrades camshaft to [0.58.1](https://github.com/CartoDB/camshaft/releases/tag/0.58.1).
|
||||||
|
|
||||||
|
|
||||||
|
## 3.12.6
|
||||||
|
Released 2017-08-31
|
||||||
|
|
||||||
|
- Upgrades camshaft to [0.58.0](https://github.com/CartoDB/camshaft/releases/tag/0.58.0).
|
||||||
|
|
||||||
|
|
||||||
|
## 3.12.5
|
||||||
|
Released 2017-08-24
|
||||||
|
|
||||||
|
- Upgrades camshaft to [0.57.0](https://github.com/CartoDB/camshaft/releases/tag/0.57.0).
|
||||||
|
|
||||||
|
|
||||||
|
## 3.12.4
|
||||||
|
Released 2017-08-23
|
||||||
|
|
||||||
|
Announcements:
|
||||||
|
- Upgrades camshaft to [0.56.0](https://github.com/CartoDB/camshaft/releases/tag/0.56.0).
|
||||||
|
|
||||||
|
## 3.12.3
|
||||||
|
Released 2017-08-22
|
||||||
|
|
||||||
|
Announcements:
|
||||||
|
- Upgrades camshaft to [0.55.8](https://github.com/CartoDB/camshaft/releases/tag/0.55.8).
|
||||||
|
|
||||||
|
## 3.12.2
|
||||||
|
Released 2017-08-16
|
||||||
|
|
||||||
|
Bug fixes:
|
||||||
|
- Polygon count problems #725.
|
||||||
|
|
||||||
|
|
||||||
|
## 3.12.1
|
||||||
|
Released 2017-08-13
|
||||||
|
- Upgrades cartodb-psql to [0.10.1](https://github.com/CartoDB/node-cartodb-psql/releases/tag/0.10.1).
|
||||||
|
- Upgrades windshaft to [3.3.1](https://github.com/CartoDB/windshaft/releases/tag/3.3.1).
|
||||||
|
- Upgrades camshaft to [0.55.7](https://github.com/CartoDB/camshaft/releases/tag/0.55.7).
|
||||||
|
|
||||||
|
|
||||||
|
## 3.12.0
|
||||||
|
Released 2017-08-10
|
||||||
|
|
||||||
|
Announcements:
|
||||||
|
- Apply max tile response time for requests to layergoup, tiles, static maps, attributes and dataviews services #717.
|
||||||
|
- Upgrades windshaft to [3.3.0](https://github.com/CartoDB/windshaft/releases/tag/3.3.0).
|
||||||
|
- Upgrades cartodb-redis to [0.14.0](https://github.com/CartoDB/node-cartodb-redis/releases/tag/0.14.0).
|
||||||
|
|
||||||
|
|
||||||
|
## 3.11.0
|
||||||
|
Released 2017-08-08
|
||||||
|
|
||||||
|
Announcements:
|
||||||
|
- Allow to override with any aggregation for histograms instantiated w/o aggregation.
|
||||||
|
|
||||||
|
Bug fixes:
|
||||||
|
- Apply timezone after truncating the minimun date for each bin to calculate timestamps in time-series.
|
||||||
|
- Support timestamp with timezones to calculate the number of bins in time-series.
|
||||||
|
- Fixed issue related to name collision while building time-series query.
|
||||||
|
|
||||||
|
|
||||||
## 3.10.1
|
## 3.10.1
|
||||||
Released 2017-08-04
|
Released 2017-08-04
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
var step = require('step');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param metadataBackend
|
* @param metadataBackend
|
||||||
@ -13,16 +15,65 @@ function UserLimitsApi(metadataBackend, options) {
|
|||||||
|
|
||||||
module.exports = UserLimitsApi;
|
module.exports = UserLimitsApi;
|
||||||
|
|
||||||
UserLimitsApi.prototype.getRenderLimits = function (username, callback) {
|
UserLimitsApi.prototype.getRenderLimits = function (username, apiKey, callback) {
|
||||||
var self = this;
|
var self = this;
|
||||||
this.metadataBackend.getTilerRenderLimit(username, function handleTilerLimits(err, renderLimit) {
|
|
||||||
|
var limits = {
|
||||||
|
cacheOnTimeout: self.options.limits.cacheOnTimeout || false,
|
||||||
|
render: self.options.limits.render || 0
|
||||||
|
};
|
||||||
|
|
||||||
|
self.getTimeoutRenderLimit(username, apiKey, function (err, timeoutRenderLimit) {
|
||||||
if (err) {
|
if (err) {
|
||||||
return callback(err);
|
return callback(err);
|
||||||
}
|
}
|
||||||
|
|
||||||
return callback(null, {
|
if (timeoutRenderLimit && timeoutRenderLimit.render) {
|
||||||
cacheOnTimeout: self.options.limits.cacheOnTimeout || false,
|
if (Number.isFinite(timeoutRenderLimit.render)) {
|
||||||
render: renderLimit || self.options.limits.render || 0
|
limits.render = timeoutRenderLimit.render;
|
||||||
});
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return callback(null, limits);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
UserLimitsApi.prototype.getTimeoutRenderLimit = function (username, apiKey, callback) {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
step(
|
||||||
|
function isAuthorized() {
|
||||||
|
var next = this;
|
||||||
|
|
||||||
|
if (!apiKey) {
|
||||||
|
return next(null, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.metadataBackend.getUserMapKey(username, function (err, userApiKey) {
|
||||||
|
if (err) {
|
||||||
|
return next(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
return next(null, userApiKey === apiKey);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
function getUserTimeoutRenderLimits(err, authorized) {
|
||||||
|
var next = this;
|
||||||
|
|
||||||
|
if (err) {
|
||||||
|
return next(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.metadataBackend.getUserTimeoutRenderLimits(username, function (err, timeoutRenderLimit) {
|
||||||
|
if (err) {
|
||||||
|
return next(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
next(null, {
|
||||||
|
render: authorized ? timeoutRenderLimit.render : timeoutRenderLimit.renderPublic
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
callback
|
||||||
|
);
|
||||||
|
};
|
||||||
|
@ -21,10 +21,10 @@ function createTemplate(method) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var methods = {
|
var methods = {
|
||||||
quantiles: 'CDB_QuantileBins(array_agg(distinct({{=it._column}}::numeric)), {{=it._buckets}}) as quantiles',
|
quantiles: 'CDB_QuantileBins(array_agg({{=it._column}}::numeric), {{=it._buckets}}) as quantiles',
|
||||||
equal: 'CDB_EqualIntervalBins(array_agg({{=it._column}}::numeric), {{=it._buckets}}) as equal',
|
equal: 'CDB_EqualIntervalBins(array_agg({{=it._column}}::numeric), {{=it._buckets}}) as equal',
|
||||||
jenks: 'CDB_JenksBins(array_agg(distinct({{=it._column}}::numeric)), {{=it._buckets}}) as jenks',
|
jenks: 'CDB_JenksBins(array_agg({{=it._column}}::numeric), {{=it._buckets}}) as jenks',
|
||||||
headtails: 'CDB_HeadsTailsBins(array_agg(distinct({{=it._column}}::numeric)), {{=it._buckets}}) as headtails'
|
headtails: 'CDB_HeadsTailsBins(array_agg({{=it._column}}::numeric), {{=it._buckets}}) as headtails'
|
||||||
};
|
};
|
||||||
|
|
||||||
var methodTemplates = Object.keys(methods).reduce(function(methodTemplates, methodName) {
|
var methodTemplates = Object.keys(methods).reduce(function(methodTemplates, methodName) {
|
||||||
|
@ -187,6 +187,9 @@ BaseController.prototype.send = function(req, res, body, status, headers) {
|
|||||||
|
|
||||||
BaseController.prototype.sendError = function(req, res, err, label) {
|
BaseController.prototype.sendError = function(req, res, err, label) {
|
||||||
var allErrors = Array.isArray(err) ? err : [err];
|
var allErrors = Array.isArray(err) ? err : [err];
|
||||||
|
|
||||||
|
allErrors = populateTimeoutErrors(allErrors);
|
||||||
|
|
||||||
label = label || 'UNKNOWN';
|
label = label || 'UNKNOWN';
|
||||||
err = allErrors[0] || new Error(label);
|
err = allErrors[0] || new Error(label);
|
||||||
allErrors[0] = err;
|
allErrors[0] = err;
|
||||||
@ -221,6 +224,18 @@ function stripConnectionInfo(message) {
|
|||||||
.replace(/is the server.*encountered/im, 'encountered');
|
.replace(/is the server.*encountered/im, 'encountered');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var ERROR_INFO_TO_EXPOSE = {
|
||||||
|
message: true,
|
||||||
|
layer: true,
|
||||||
|
type: true,
|
||||||
|
analysis: true,
|
||||||
|
subtype: true
|
||||||
|
};
|
||||||
|
|
||||||
|
function shouldBeExposed (prop) {
|
||||||
|
return !!ERROR_INFO_TO_EXPOSE[prop];
|
||||||
|
}
|
||||||
|
|
||||||
function errorMessage(err) {
|
function errorMessage(err) {
|
||||||
// See https://github.com/Vizzuality/Windshaft-cartodb/issues/68
|
// See https://github.com/Vizzuality/Windshaft-cartodb/issues/68
|
||||||
var message = (_.isString(err) ? err : err.message) || 'Unknown error';
|
var message = (_.isString(err) ? err : err.message) || 'Unknown error';
|
||||||
@ -239,7 +254,7 @@ function errorMessageWithContext(err) {
|
|||||||
|
|
||||||
for (var prop in err) {
|
for (var prop in err) {
|
||||||
// type & message are properties from Error's prototype and will be skipped
|
// type & message are properties from Error's prototype and will be skipped
|
||||||
if (err.hasOwnProperty(prop)) {
|
if (err.hasOwnProperty(prop) && shouldBeExposed(prop)) {
|
||||||
error[prop] = err[prop];
|
error[prop] = err[prop];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -281,5 +296,38 @@ function statusFromErrorMessage(errMsg) {
|
|||||||
statusCode = 404;
|
statusCode = 404;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return statusCode;
|
return statusCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isRenderTimeoutError (err) {
|
||||||
|
return err.message === 'Render timed out';
|
||||||
|
}
|
||||||
|
|
||||||
|
function isDatasourceTimeoutError (err) {
|
||||||
|
return err.message && err.message.match(/canceling statement due to statement timeout/i);
|
||||||
|
}
|
||||||
|
|
||||||
|
function isTimeoutError (err) {
|
||||||
|
return isRenderTimeoutError(err) || isDatasourceTimeoutError(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
function populateTimeoutErrors (errors) {
|
||||||
|
return errors.map(function (error) {
|
||||||
|
if (isRenderTimeoutError(error)) {
|
||||||
|
error.subtype = 'render';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isDatasourceTimeoutError(error)) {
|
||||||
|
error.subtype = 'datasource';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isTimeoutError(error)) {
|
||||||
|
error.message = 'You are over platform\'s limits. Please contact us to know more details';
|
||||||
|
error.type = 'limit';
|
||||||
|
error.http_status = 429;
|
||||||
|
}
|
||||||
|
|
||||||
|
return error;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
@ -13,7 +13,6 @@ BaseDataview.prototype.getResult = function(psql, override, callback) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
psql.query(query, function(err, result) {
|
psql.query(query, function(err, result) {
|
||||||
|
|
||||||
if (err) {
|
if (err) {
|
||||||
return callback(err, result);
|
return callback(err, result);
|
||||||
}
|
}
|
||||||
|
@ -9,42 +9,42 @@ var columnCastTpl = dot.template("date_part('epoch', {{=it.column}})");
|
|||||||
|
|
||||||
var dateIntervalQueryTpl = dot.template([
|
var dateIntervalQueryTpl = dot.template([
|
||||||
'WITH',
|
'WITH',
|
||||||
'dates AS (',
|
'__cdb_dates AS (',
|
||||||
' SELECT',
|
' SELECT',
|
||||||
' MAX({{=it.column}}::timestamp) AS _end,',
|
' MAX({{=it.column}}::timestamp) AS __cdb_end,',
|
||||||
' MIN({{=it.column}}::timestamp) AS _start',
|
' MIN({{=it.column}}::timestamp) AS __cdb_start',
|
||||||
' FROM ({{=it.query}}) _cdb_source',
|
' FROM ({{=it.query}}) __cdb_source',
|
||||||
'),',
|
'),',
|
||||||
'interval_in_days AS (',
|
'__cdb_interval_in_days AS (',
|
||||||
' SELECT' ,
|
' SELECT' ,
|
||||||
' DATE_PART(\'day\', _end - _start) AS days',
|
' DATE_PART(\'day\', __cdb_end - __cdb_start) AS __cdb_days',
|
||||||
' FROM dates',
|
' FROM __cdb_dates',
|
||||||
'),',
|
'),',
|
||||||
'interval_in_hours AS (',
|
'__cdb_interval_in_hours AS (',
|
||||||
' SELECT',
|
' SELECT',
|
||||||
' days * 24 + DATE_PART(\'hour\', _end - _start) AS hours',
|
' __cdb_days * 24 + DATE_PART(\'hour\', __cdb_end - __cdb_start) AS __cdb_hours',
|
||||||
' FROM interval_in_days, dates',
|
' FROM __cdb_interval_in_days, __cdb_dates',
|
||||||
'),',
|
'),',
|
||||||
'interval_in_minutes AS (',
|
'__cdb_interval_in_minutes AS (',
|
||||||
' SELECT',
|
' SELECT',
|
||||||
' hours * 60 + DATE_PART(\'minute\', _end - _start) AS minutes',
|
' __cdb_hours * 60 + DATE_PART(\'minute\', __cdb_end - __cdb_start) AS __cdb_minutes',
|
||||||
' FROM interval_in_hours, dates',
|
' FROM __cdb_interval_in_hours, __cdb_dates',
|
||||||
'),',
|
'),',
|
||||||
'interval_in_seconds AS (',
|
'__cdb_interval_in_seconds AS (',
|
||||||
' SELECT',
|
' SELECT',
|
||||||
' minutes * 60 + DATE_PART(\'second\', _end - _start) AS seconds',
|
' __cdb_minutes * 60 + DATE_PART(\'second\', __cdb_end - __cdb_start) AS __cdb_seconds',
|
||||||
' FROM interval_in_minutes, dates',
|
' FROM __cdb_interval_in_minutes, __cdb_dates',
|
||||||
')',
|
')',
|
||||||
'SELECT',
|
'SELECT',
|
||||||
' ROUND(days / 365) AS year,',
|
' ROUND(__cdb_days / 365) AS year,',
|
||||||
' ROUND(days / 90) AS quarter,',
|
' ROUND(__cdb_days / 90) AS quarter,',
|
||||||
' ROUND(days / 30) AS month,',
|
' ROUND(__cdb_days / 30) AS month,',
|
||||||
' ROUND(days / 7) AS week,',
|
' ROUND(__cdb_days / 7) AS week,',
|
||||||
' days AS day,',
|
' __cdb_days AS day,',
|
||||||
' hours AS hour,',
|
' __cdb_hours AS hour,',
|
||||||
' minutes AS minute,',
|
' __cdb_minutes AS minute,',
|
||||||
' seconds AS second',
|
' __cdb_seconds AS second',
|
||||||
'FROM interval_in_days, interval_in_hours, interval_in_minutes, interval_in_seconds'
|
'FROM __cdb_interval_in_days, __cdb_interval_in_hours, __cdb_interval_in_minutes, __cdb_interval_in_seconds'
|
||||||
].join('\n'));
|
].join('\n'));
|
||||||
|
|
||||||
var MAX_INTERVAL_VALUE = 366;
|
var MAX_INTERVAL_VALUE = 366;
|
||||||
@ -52,9 +52,9 @@ var BIN_MIN_NUMBER = 6;
|
|||||||
var BIN_MAX_NUMBER = 48;
|
var BIN_MAX_NUMBER = 48;
|
||||||
|
|
||||||
var filteredQueryTpl = dot.template([
|
var filteredQueryTpl = dot.template([
|
||||||
'filtered_source AS (',
|
'__cdb_filtered_source AS (',
|
||||||
' SELECT *',
|
' SELECT *',
|
||||||
' FROM ({{=it._query}}) _cdb_filtered_source',
|
' FROM ({{=it._query}}) __cdb_filtered_source_query',
|
||||||
' WHERE',
|
' WHERE',
|
||||||
' {{=it._column}} IS NOT NULL',
|
' {{=it._column}} IS NOT NULL',
|
||||||
' {{?it._isFloatColumn}}AND',
|
' {{?it._isFloatColumn}}AND',
|
||||||
@ -67,74 +67,74 @@ var filteredQueryTpl = dot.template([
|
|||||||
].join(' \n'));
|
].join(' \n'));
|
||||||
|
|
||||||
var basicsQueryTpl = dot.template([
|
var basicsQueryTpl = dot.template([
|
||||||
'basics AS (',
|
'__cdb_basics AS (',
|
||||||
' SELECT',
|
' SELECT',
|
||||||
' max({{=it._column}}) AS max_val, min({{=it._column}}) AS min_val,',
|
' max({{=it._column}}) AS __cdb_max_val, min({{=it._column}}) AS __cdb_min_val,',
|
||||||
' avg({{=it._column}}) AS avg_val, count(1) AS total_rows',
|
' avg({{=it._column}}) AS __cdb_avg_val, count(1) AS __cdb_total_rows',
|
||||||
' FROM filtered_source',
|
' FROM __cdb_filtered_source',
|
||||||
')'
|
')'
|
||||||
].join(' \n'));
|
].join(' \n'));
|
||||||
|
|
||||||
var overrideBasicsQueryTpl = dot.template([
|
var overrideBasicsQueryTpl = dot.template([
|
||||||
'basics AS (',
|
'__cdb_basics AS (',
|
||||||
' SELECT',
|
' SELECT',
|
||||||
' max({{=it._end}}) AS max_val, min({{=it._start}}) AS min_val,',
|
' max({{=it._end}}) AS __cdb_max_val, min({{=it._start}}) AS __cdb_min_val,',
|
||||||
' avg({{=it._column}}) AS avg_val, count(1) AS total_rows',
|
' avg({{=it._column}}) AS __cdb_avg_val, count(1) AS __cdb_total_rows',
|
||||||
' FROM filtered_source',
|
' FROM __cdb_filtered_source',
|
||||||
')'
|
')'
|
||||||
].join('\n'));
|
].join('\n'));
|
||||||
|
|
||||||
var iqrQueryTpl = dot.template([
|
var iqrQueryTpl = dot.template([
|
||||||
'iqrange AS (',
|
'__cdb_iqrange AS (',
|
||||||
' SELECT max(quartile_max) - min(quartile_max) AS iqr',
|
' SELECT max(quartile_max) - min(quartile_max) AS __cdb_iqr',
|
||||||
' FROM (',
|
' FROM (',
|
||||||
' SELECT quartile, max(_cdb_iqr_column) AS quartile_max 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}}',
|
' SELECT {{=it._column}} AS _cdb_iqr_column, ntile(4) over (order by {{=it._column}}',
|
||||||
' ) AS quartile',
|
' ) AS quartile',
|
||||||
' FROM filtered_source) _cdb_quartiles',
|
' FROM __cdb_filtered_source) _cdb_quartiles',
|
||||||
' WHERE quartile = 1 or quartile = 3',
|
' WHERE quartile = 1 or quartile = 3',
|
||||||
' GROUP BY quartile',
|
' GROUP BY quartile',
|
||||||
' ) _cdb_iqr',
|
' ) __cdb_iqr',
|
||||||
')'
|
')'
|
||||||
].join('\n'));
|
].join('\n'));
|
||||||
|
|
||||||
var binsQueryTpl = dot.template([
|
var binsQueryTpl = dot.template([
|
||||||
'bins AS (',
|
'__cdb_bins AS (',
|
||||||
' SELECT CASE WHEN total_rows = 0 OR iqr = 0',
|
' SELECT CASE WHEN __cdb_total_rows = 0 OR __cdb_iqr = 0',
|
||||||
' THEN 1',
|
' THEN 1',
|
||||||
' ELSE GREATEST(',
|
' ELSE GREATEST(',
|
||||||
' LEAST({{=it._minBins}}, CAST(total_rows AS INT)),',
|
' LEAST({{=it._minBins}}, CAST(__cdb_total_rows AS INT)),',
|
||||||
' LEAST(',
|
' LEAST(',
|
||||||
' CAST(((max_val - min_val) / (2 * iqr * power(total_rows, 1/3))) AS INT),',
|
' CAST(((__cdb_max_val - __cdb_min_val) / (2 * __cdb_iqr * power(__cdb_total_rows, 1/3))) AS INT),',
|
||||||
' {{=it._maxBins}}',
|
' {{=it._maxBins}}',
|
||||||
' )',
|
' )',
|
||||||
' )',
|
' )',
|
||||||
' END AS bins_number',
|
' END AS __cdb_bins_number',
|
||||||
' FROM basics, iqrange, filtered_source',
|
' FROM __cdb_basics, __cdb_iqrange, __cdb_filtered_source',
|
||||||
' LIMIT 1',
|
' LIMIT 1',
|
||||||
')'
|
')'
|
||||||
].join('\n'));
|
].join('\n'));
|
||||||
|
|
||||||
var overrideBinsQueryTpl = dot.template([
|
var overrideBinsQueryTpl = dot.template([
|
||||||
'bins AS (',
|
'__cdb_bins AS (',
|
||||||
' SELECT {{=it._bins}} AS bins_number',
|
' SELECT {{=it._bins}} AS __cdb_bins_number',
|
||||||
')'
|
')'
|
||||||
].join('\n'));
|
].join('\n'));
|
||||||
|
|
||||||
var nullsQueryTpl = dot.template([
|
var nullsQueryTpl = dot.template([
|
||||||
'nulls AS (',
|
'__cdb_nulls AS (',
|
||||||
' SELECT',
|
' SELECT',
|
||||||
' count(*) AS nulls_count',
|
' count(*) AS __cdb_nulls_count',
|
||||||
' FROM ({{=it._query}}) _cdb_histogram_nulls',
|
' FROM ({{=it._query}}) __cdb_histogram_nulls',
|
||||||
' WHERE {{=it._column}} IS NULL',
|
' WHERE {{=it._column}} IS NULL',
|
||||||
')'
|
')'
|
||||||
].join('\n'));
|
].join('\n'));
|
||||||
|
|
||||||
var infinitiesQueryTpl = dot.template([
|
var infinitiesQueryTpl = dot.template([
|
||||||
'infinities AS (',
|
'__cdb_infinities AS (',
|
||||||
' SELECT',
|
' SELECT',
|
||||||
' count(*) AS infinities_count',
|
' count(*) AS __cdb_infinities_count',
|
||||||
' FROM ({{=it._query}}) _cdb_histogram_infinities',
|
' FROM ({{=it._query}}) __cdb_infinities_query',
|
||||||
' WHERE',
|
' WHERE',
|
||||||
' {{=it._column}} = \'infinity\'::float',
|
' {{=it._column}} = \'infinity\'::float',
|
||||||
' OR',
|
' OR',
|
||||||
@ -143,121 +143,128 @@ var infinitiesQueryTpl = dot.template([
|
|||||||
].join('\n'));
|
].join('\n'));
|
||||||
|
|
||||||
var nansQueryTpl = dot.template([
|
var nansQueryTpl = dot.template([
|
||||||
'nans AS (',
|
'__cdb_nans AS (',
|
||||||
' SELECT',
|
' SELECT',
|
||||||
' count(*) AS nans_count',
|
' count(*) AS __cdb_nans_count',
|
||||||
' FROM ({{=it._query}}) _cdb_histogram_infinities',
|
' FROM ({{=it._query}}) __cdb_nans_query',
|
||||||
' WHERE {{=it._column}} = \'NaN\'::float',
|
' WHERE {{=it._column}} = \'NaN\'::float',
|
||||||
')'
|
')'
|
||||||
].join('\n'));
|
].join('\n'));
|
||||||
|
|
||||||
var histogramQueryTpl = dot.template([
|
var histogramQueryTpl = dot.template([
|
||||||
'SELECT',
|
'SELECT',
|
||||||
' (max_val - min_val) / cast(bins_number as float) AS bin_width,',
|
' (__cdb_max_val - __cdb_min_val) / cast(__cdb_bins_number as float) AS bin_width,',
|
||||||
' bins_number,',
|
' __cdb_bins_number AS bins_number,',
|
||||||
' nulls_count,',
|
' __cdb_nulls_count AS nulls_count,',
|
||||||
' {{?it._isFloatColumn}}infinities_count,',
|
' {{?it._isFloatColumn}}__cdb_infinities_count AS infinities_count,',
|
||||||
' nans_count,{{?}}',
|
' __cdb_nans_count AS nans_count,{{?}}',
|
||||||
' avg_val,',
|
' __cdb_avg_val AS avg_val,',
|
||||||
' CASE WHEN min_val = max_val',
|
' CASE WHEN __cdb_min_val = __cdb_max_val',
|
||||||
' THEN 0',
|
' THEN 0',
|
||||||
' ELSE GREATEST(1, LEAST(WIDTH_BUCKET({{=it._column}}, min_val, max_val, bins_number), bins_number)) - 1',
|
' ELSE GREATEST(',
|
||||||
|
' 1,',
|
||||||
|
' LEAST(',
|
||||||
|
' WIDTH_BUCKET({{=it._column}}, __cdb_min_val, __cdb_max_val, __cdb_bins_number),',
|
||||||
|
' __cdb_bins_number',
|
||||||
|
' )',
|
||||||
|
' ) - 1',
|
||||||
' END AS bin,',
|
' END AS bin,',
|
||||||
' min({{=it._column}})::numeric AS min,',
|
' min({{=it._column}})::numeric AS min,',
|
||||||
' max({{=it._column}})::numeric AS max,',
|
' max({{=it._column}})::numeric AS max,',
|
||||||
' avg({{=it._column}})::numeric AS avg,',
|
' avg({{=it._column}})::numeric AS avg,',
|
||||||
' count(*) AS freq',
|
' count(*) AS freq',
|
||||||
'FROM filtered_source, basics, nulls, bins{{?it._isFloatColumn}}, infinities, nans{{?}}',
|
'FROM __cdb_filtered_source, __cdb_basics, __cdb_nulls,',
|
||||||
|
' __cdb_bins{{?it._isFloatColumn}}, __cdb_infinities, __cdb_nans{{?}}',
|
||||||
'GROUP BY bin, bins_number, bin_width, nulls_count,',
|
'GROUP BY bin, bins_number, bin_width, nulls_count,',
|
||||||
' avg_val{{?it._isFloatColumn}}, infinities_count, nans_count{{?}}',
|
' avg_val{{?it._isFloatColumn}}, infinities_count, nans_count{{?}}',
|
||||||
'ORDER BY bin'
|
'ORDER BY bin'
|
||||||
].join('\n'));
|
].join('\n'));
|
||||||
|
|
||||||
var dateBasicsQueryTpl = dot.template([
|
var dateBasicsQueryTpl = dot.template([
|
||||||
'basics AS (',
|
'__cdb_basics AS (',
|
||||||
' SELECT',
|
' SELECT',
|
||||||
' max(date_part(\'epoch\', {{=it._column}})) AS max_val,',
|
' max(date_part(\'epoch\', {{=it._column}})) AS __cdb_max_val,',
|
||||||
' min(date_part(\'epoch\', {{=it._column}})) AS min_val,',
|
' min(date_part(\'epoch\', {{=it._column}})) AS __cdb_min_val,',
|
||||||
' avg(date_part(\'epoch\', {{=it._column}})) AS avg_val,',
|
' avg(date_part(\'epoch\', {{=it._column}})) AS __cdb_avg_val,',
|
||||||
' min(date_trunc(',
|
' min(date_trunc(',
|
||||||
' \'{{=it._aggregation}}\', {{=it._column}} AT TIME ZONE \'{{=it._offset}}\'',
|
' \'{{=it._aggregation}}\', {{=it._column}}::timestamp AT TIME ZONE \'{{=it._offset}}\'',
|
||||||
' )) AS start_date,',
|
' )) AS __cdb_start_date,',
|
||||||
' max({{=it._column}} AT TIME ZONE \'{{=it._offset}}\') AS end_date,',
|
' max({{=it._column}}::timestamp AT TIME ZONE \'{{=it._offset}}\') AS __cdb_end_date,',
|
||||||
' count(1) AS total_rows',
|
' count(1) AS __cdb_total_rows',
|
||||||
' FROM ({{=it._query}}) _cdb_basics',
|
' FROM ({{=it._query}}) __cdb_basics_query',
|
||||||
')'
|
')'
|
||||||
].join(' \n'));
|
].join(' \n'));
|
||||||
|
|
||||||
var dateOverrideBasicsQueryTpl = dot.template([
|
var dateOverrideBasicsQueryTpl = dot.template([
|
||||||
'basics AS (',
|
'__cdb_basics AS (',
|
||||||
' SELECT',
|
' SELECT',
|
||||||
' max({{=it._end}}) AS max_val,',
|
' max({{=it._end}})::float AS __cdb_max_val,',
|
||||||
' min({{=it._start}}) AS min_val,',
|
' min({{=it._start}})::float AS __cdb_min_val,',
|
||||||
' avg(date_part(\'epoch\', {{=it._column}})) AS avg_val,',
|
' avg(date_part(\'epoch\', {{=it._column}})) AS __cdb_avg_val,',
|
||||||
' min(',
|
' min(',
|
||||||
' date_trunc(',
|
' date_trunc(',
|
||||||
' \'{{=it._aggregation}}\',',
|
' \'{{=it._aggregation}}\',',
|
||||||
' TO_TIMESTAMP({{=it._start}})::timestamp AT TIME ZONE \'{{=it._offset}}\'',
|
' TO_TIMESTAMP({{=it._start}})::timestamp AT TIME ZONE \'{{=it._offset}}\'',
|
||||||
' )',
|
' )',
|
||||||
' ) AS start_date,',
|
' ) AS __cdb_start_date,',
|
||||||
' max(',
|
' max(',
|
||||||
' TO_TIMESTAMP({{=it._end}})::timestamp AT TIME ZONE \'{{=it._offset}}\'',
|
' TO_TIMESTAMP({{=it._end}})::timestamp AT TIME ZONE \'{{=it._offset}}\'',
|
||||||
' ) AS end_date,',
|
' ) AS __cdb_end_date,',
|
||||||
' count(1) AS total_rows',
|
' count(1) AS __cdb_total_rows',
|
||||||
' FROM ({{=it._query}}) _cdb_basics',
|
' FROM ({{=it._query}}) __cdb_basics_query',
|
||||||
')'
|
')'
|
||||||
].join(' \n'));
|
].join(' \n'));
|
||||||
|
|
||||||
var dateBinsQueryTpl = dot.template([
|
var dateBinsQueryTpl = dot.template([
|
||||||
'bins AS (',
|
'__cdb_bins AS (',
|
||||||
' SELECT',
|
' SELECT',
|
||||||
' bins_array,',
|
' __cdb_bins_array,',
|
||||||
' ARRAY_LENGTH(bins_array, 1) AS bins_number',
|
' ARRAY_LENGTH(__cdb_bins_array, 1) AS __cdb_bins_number',
|
||||||
' FROM (',
|
' FROM (',
|
||||||
' SELECT',
|
' SELECT',
|
||||||
' ARRAY(',
|
' ARRAY(',
|
||||||
' SELECT GENERATE_SERIES(',
|
' SELECT GENERATE_SERIES(',
|
||||||
' start_date::timestamptz,',
|
' __cdb_start_date::timestamptz,',
|
||||||
' end_date::timestamptz,',
|
' __cdb_end_date::timestamptz,',
|
||||||
' {{?it._aggregation==="quarter"}}\'3 month\'{{??}}\'1 {{=it._aggregation}}\'{{?}}::interval',
|
' {{?it._aggregation==="quarter"}}\'3 month\'{{??}}\'1 {{=it._aggregation}}\'{{?}}::interval',
|
||||||
' )',
|
' )',
|
||||||
' ) AS bins_array',
|
' ) AS __cdb_bins_array',
|
||||||
' FROM basics',
|
' FROM __cdb_basics',
|
||||||
' ) _cdb_bins_array',
|
' ) __cdb_bins_array_query',
|
||||||
')'
|
')'
|
||||||
].join('\n'));
|
].join('\n'));
|
||||||
|
|
||||||
var dateHistogramQueryTpl = dot.template([
|
var dateHistogramQueryTpl = dot.template([
|
||||||
'SELECT',
|
'SELECT',
|
||||||
' (max_val - min_val) / cast(bins_number as float) AS bin_width,',
|
' (__cdb_max_val - __cdb_min_val) / cast(__cdb_bins_number as float) AS bin_width,',
|
||||||
' bins_number,',
|
' __cdb_bins_number AS bins_number,',
|
||||||
' nulls_count,',
|
' __cdb_nulls_count AS nulls_count,',
|
||||||
' CASE WHEN min_val = max_val',
|
' CASE WHEN __cdb_min_val = __cdb_max_val',
|
||||||
' THEN 0',
|
' THEN 0',
|
||||||
' ELSE GREATEST(1, LEAST(',
|
' ELSE GREATEST(1, LEAST(',
|
||||||
' WIDTH_BUCKET(',
|
' WIDTH_BUCKET(',
|
||||||
' {{=it._column}}::timestamp AT TIME ZONE \'{{=it._offset}}\',',
|
' {{=it._column}}::timestamp AT TIME ZONE \'{{=it._offset}}\',',
|
||||||
' bins_array',
|
' __cdb_bins_array',
|
||||||
' ),',
|
' ),',
|
||||||
' bins_number',
|
' __cdb_bins_number',
|
||||||
' )) - 1',
|
' )) - 1',
|
||||||
' END AS bin,',
|
' END AS bin,',
|
||||||
' min(',
|
' min(',
|
||||||
' date_part(',
|
' date_part(',
|
||||||
' \'epoch\', ',
|
' \'epoch\', ',
|
||||||
' date_trunc(',
|
' date_trunc(',
|
||||||
' \'{{=it._aggregation}}\', {{=it._column}}::timestamptz',
|
' \'{{=it._aggregation}}\', {{=it._column}}::timestamp AT TIME ZONE \'{{=it._offset}}\'',
|
||||||
' ) AT TIME ZONE \'{{=it._offset}}\'',
|
' ) AT TIME ZONE \'{{=it._offset}}\'',
|
||||||
' )',
|
' )',
|
||||||
' )::numeric AS timestamp,',
|
' )::numeric AS timestamp,',
|
||||||
' date_part(\'epoch\', start_date)::numeric AS timestamp_start,',
|
' date_part(\'epoch\', __cdb_start_date)::numeric AS timestamp_start,',
|
||||||
' min(date_part(\'epoch\', {{=it._column}}))::numeric AS min,',
|
' min(date_part(\'epoch\', {{=it._column}}))::numeric AS min,',
|
||||||
' max(date_part(\'epoch\', {{=it._column}}))::numeric AS max,',
|
' max(date_part(\'epoch\', {{=it._column}}))::numeric AS max,',
|
||||||
' avg(date_part(\'epoch\', {{=it._column}}))::numeric AS avg,',
|
' avg(date_part(\'epoch\', {{=it._column}}))::numeric AS avg,',
|
||||||
' count(*) AS freq',
|
' count(*) AS freq',
|
||||||
'FROM ({{=it._query}}) _cdb_histogram, basics, bins, nulls',
|
'FROM ({{=it._query}}) __cdb_histogram, __cdb_basics, __cdb_bins, __cdb_nulls',
|
||||||
'WHERE date_part(\'epoch\', {{=it._column}}) IS NOT NULL',
|
'WHERE date_part(\'epoch\', {{=it._column}}) IS NOT NULL',
|
||||||
'GROUP BY bin, bins_number, bin_width, nulls_count, avg_val, start_date',
|
'GROUP BY bin, bins_number, bin_width, nulls_count, timestamp_start',
|
||||||
'ORDER BY bin'
|
'ORDER BY bin'
|
||||||
].join('\n'));
|
].join('\n'));
|
||||||
|
|
||||||
@ -328,12 +335,16 @@ Histogram.prototype.sql = function(psql, override, callback) {
|
|||||||
this._buildQuery(psql, override, callback);
|
this._buildQuery(psql, override, callback);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Histogram.prototype.isDateHistogram = function (override) {
|
||||||
|
return this._columnType === 'date' && (this.aggregation !== undefined || override.aggregation !== undefined);
|
||||||
|
};
|
||||||
|
|
||||||
Histogram.prototype._buildQuery = function (psql, override, callback) {
|
Histogram.prototype._buildQuery = function (psql, override, callback) {
|
||||||
var filteredQuery, basicsQuery, binsQuery;
|
var filteredQuery, basicsQuery, binsQuery;
|
||||||
var _column = this.column;
|
var _column = this.column;
|
||||||
var _query = this.query;
|
var _query = this.query;
|
||||||
|
|
||||||
if (this._columnType === 'date' && this.aggregation !== undefined) {
|
if (this.isDateHistogram(override)) {
|
||||||
return this._buildDateHistogramQuery(psql, override, callback);
|
return this._buildDateHistogramQuery(psql, override, callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -8,7 +8,7 @@ var filterQueryTpl = dot.template([
|
|||||||
].join('\n'));
|
].join('\n'));
|
||||||
|
|
||||||
var bboxFilterTpl = dot.template(
|
var bboxFilterTpl = dot.template(
|
||||||
'{{=it._column}} && ST_Transform(ST_MakeEnvelope({{=it._bbox}}, 4326), {{=it._srid}})'
|
'ST_Intersects({{=it._column}}, ST_Transform(ST_MakeEnvelope({{=it._bbox}}, 4326), {{=it._srid}}))'
|
||||||
);
|
);
|
||||||
|
|
||||||
var LATITUDE_MAX_VALUE = 85.0511287798066;
|
var LATITUDE_MAX_VALUE = 85.0511287798066;
|
||||||
|
@ -26,7 +26,7 @@ CreateLayergroupMapConfigProvider.prototype.getMapConfig = function(callback) {
|
|||||||
var context = {};
|
var context = {};
|
||||||
step(
|
step(
|
||||||
function prepareContextLimits() {
|
function prepareContextLimits() {
|
||||||
self.userLimitsApi.getRenderLimits(self.user, this);
|
self.userLimitsApi.getRenderLimits(self.user, self.params.api_key, this);
|
||||||
},
|
},
|
||||||
function handleRenderLimits(err, renderLimits) {
|
function handleRenderLimits(err, renderLimits) {
|
||||||
assert.ifError(err);
|
assert.ifError(err);
|
||||||
|
@ -27,7 +27,7 @@ MapStoreMapConfigProvider.prototype.getMapConfig = function(callback) {
|
|||||||
var context = {};
|
var context = {};
|
||||||
step(
|
step(
|
||||||
function prepareContextLimits() {
|
function prepareContextLimits() {
|
||||||
self.userLimitsApi.getRenderLimits(self.user, this);
|
self.userLimitsApi.getRenderLimits(self.user, self.params.api_key, this);
|
||||||
},
|
},
|
||||||
function handleRenderLimits(err, renderLimits) {
|
function handleRenderLimits(err, renderLimits) {
|
||||||
assert.ifError(err);
|
assert.ifError(err);
|
||||||
|
@ -114,7 +114,7 @@ NamedMapMapConfigProvider.prototype.getMapConfig = function(callback) {
|
|||||||
function prepareContextLimits(err, _mapConfig) {
|
function prepareContextLimits(err, _mapConfig) {
|
||||||
assert.ifError(err);
|
assert.ifError(err);
|
||||||
mapConfig = _mapConfig;
|
mapConfig = _mapConfig;
|
||||||
self.userLimitsApi.getRenderLimits(self.owner, this);
|
self.userLimitsApi.getRenderLimits(self.owner, self.params.api_key, this);
|
||||||
},
|
},
|
||||||
function cacheAndReturnMapConfig(err, renderLimits) {
|
function cacheAndReturnMapConfig(err, renderLimits) {
|
||||||
self.err = err;
|
self.err = err;
|
||||||
|
@ -118,8 +118,27 @@ module.exports = function(serverOptions) {
|
|||||||
var onTileErrorStrategy;
|
var onTileErrorStrategy;
|
||||||
if (global.environment.enabledFeatures.onTileErrorStrategy !== false) {
|
if (global.environment.enabledFeatures.onTileErrorStrategy !== false) {
|
||||||
onTileErrorStrategy = function onTileErrorStrategy$TimeoutTile(err, tile, headers, stats, format, callback) {
|
onTileErrorStrategy = function onTileErrorStrategy$TimeoutTile(err, tile, headers, stats, format, callback) {
|
||||||
if (err && err.message === 'Render timed out' && format === 'png') {
|
|
||||||
return callback(null, timeoutErrorTile, { 'Content-Type': 'image/png' }, {});
|
function isRenderTimeoutError (err) {
|
||||||
|
return err.message === 'Render timed out';
|
||||||
|
}
|
||||||
|
|
||||||
|
function isDatasourceTimeoutError (err) {
|
||||||
|
return err.message && err.message.match(/canceling statement due to statement timeout/i);
|
||||||
|
}
|
||||||
|
|
||||||
|
function isTimeoutError (err) {
|
||||||
|
return isRenderTimeoutError(err) || isDatasourceTimeoutError(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
function isRasterFormat (format) {
|
||||||
|
return format === 'png' || format === 'jpg';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isTimeoutError(err) && isRasterFormat(format)) {
|
||||||
|
return callback(null, timeoutErrorTile, {
|
||||||
|
'Content-Type': 'image/png',
|
||||||
|
}, {});
|
||||||
} else {
|
} else {
|
||||||
return callback(err, tile, headers, stats);
|
return callback(err, tile, headers, stats);
|
||||||
}
|
}
|
||||||
|
14
package.json
14
package.json
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"private": true,
|
"private": true,
|
||||||
"name": "windshaft-cartodb",
|
"name": "windshaft-cartodb",
|
||||||
"version": "3.10.2",
|
"version": "3.12.11",
|
||||||
"description": "A map tile server for CartoDB",
|
"description": "A map tile server for CartoDB",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"cartodb"
|
"cartodb"
|
||||||
@ -17,14 +17,16 @@
|
|||||||
"Simon Tokumine <simon@vizzuality.com>",
|
"Simon Tokumine <simon@vizzuality.com>",
|
||||||
"Javi Santana <jsantana@vizzuality.com>",
|
"Javi Santana <jsantana@vizzuality.com>",
|
||||||
"Sandro Santilli <strk@vizzuality.com>",
|
"Sandro Santilli <strk@vizzuality.com>",
|
||||||
"Carlos Matallín <matallo@carto.com>"
|
"Carlos Matallín <matallo@carto.com>",
|
||||||
|
"Daniel Garcia Aubert <dgaubert@carto.com>",
|
||||||
|
"Mario de Frutos <mario.defrutos@carto.com>"
|
||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"body-parser": "~1.14.0",
|
"body-parser": "~1.14.0",
|
||||||
"camshaft": "0.55.6",
|
"camshaft": "0.58.1",
|
||||||
"cartodb-psql": "0.8.0",
|
"cartodb-psql": "0.10.1",
|
||||||
"cartodb-query-tables": "0.2.0",
|
"cartodb-query-tables": "0.2.0",
|
||||||
"cartodb-redis": "0.13.2",
|
"cartodb-redis": "0.14.0",
|
||||||
"debug": "~2.2.0",
|
"debug": "~2.2.0",
|
||||||
"dot": "~1.0.2",
|
"dot": "~1.0.2",
|
||||||
"express": "~4.13.3",
|
"express": "~4.13.3",
|
||||||
@ -40,7 +42,7 @@
|
|||||||
"step-profiler": "~0.3.0",
|
"step-profiler": "~0.3.0",
|
||||||
"turbo-carto": "0.19.2",
|
"turbo-carto": "0.19.2",
|
||||||
"underscore": "~1.6.0",
|
"underscore": "~1.6.0",
|
||||||
"windshaft": "3.2.2",
|
"windshaft": "3.3.2",
|
||||||
"yargs": "~5.0.0"
|
"yargs": "~5.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
@ -332,6 +332,21 @@ describe('histogram-dataview for date column type', function() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should cast overridden start and end to float to avoid out of range errors ' + test.desc, function (done) {
|
||||||
|
var params = {
|
||||||
|
start: -2145916800,
|
||||||
|
end: 1009843199
|
||||||
|
};
|
||||||
|
|
||||||
|
this.testClient = new TestClient(mapConfig, 1234);
|
||||||
|
this.testClient.getDataview(test.dataviewId, 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);
|
||||||
|
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('should return same histogram ' + test.desc, function (done) {
|
it('should return same histogram ' + test.desc, function (done) {
|
||||||
var params = {
|
var params = {
|
||||||
@ -888,7 +903,6 @@ describe('histogram-dates: aggregation input value', function() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
describe('histogram-dates: timestamp starts at epoch', function() {
|
describe('histogram-dates: timestamp starts at epoch', function() {
|
||||||
|
|
||||||
afterEach(function(done) {
|
afterEach(function(done) {
|
||||||
@ -958,3 +972,221 @@ describe('histogram-dates: timestamp starts at epoch', function() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('histogram-dates: trunc timestamp for each bin respecting user\'s timezone', function() {
|
||||||
|
|
||||||
|
afterEach(function(done) {
|
||||||
|
if (this.testClient) {
|
||||||
|
this.testClient.drain(done);
|
||||||
|
} else {
|
||||||
|
done();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
var mapConfig = createMapConfig(
|
||||||
|
[
|
||||||
|
{
|
||||||
|
type: "cartodb",
|
||||||
|
options: {
|
||||||
|
source: {
|
||||||
|
id: "a0"
|
||||||
|
},
|
||||||
|
cartocss: "#points { marker-width: 10; marker-fill: red; }",
|
||||||
|
cartocss_version: "2.3.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
{
|
||||||
|
timezone_epoch_histogram: {
|
||||||
|
source: {
|
||||||
|
id: 'a0'
|
||||||
|
},
|
||||||
|
type: 'histogram',
|
||||||
|
options: {
|
||||||
|
column: 'd',
|
||||||
|
aggregation: 'auto'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
timezone_epoch_histogram_tz: {
|
||||||
|
source: {
|
||||||
|
id: 'a1'
|
||||||
|
},
|
||||||
|
type: 'histogram',
|
||||||
|
options: {
|
||||||
|
column: 'd',
|
||||||
|
aggregation: 'auto'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[
|
||||||
|
{
|
||||||
|
id: 'a0',
|
||||||
|
type: 'source',
|
||||||
|
params: {
|
||||||
|
query: [
|
||||||
|
'select null::geometry the_geom_webmercator, date AS d',
|
||||||
|
'from generate_series(',
|
||||||
|
'\'1970-01-01 00:00:00\'::timestamp,',
|
||||||
|
'\'1970-01-01 01:59:00\'::timestamp,',
|
||||||
|
' \'1 minute\'::interval',
|
||||||
|
') date'
|
||||||
|
].join(' ')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'a1',
|
||||||
|
type: 'source',
|
||||||
|
params: {
|
||||||
|
query: [
|
||||||
|
'select null::geometry the_geom_webmercator, date AS d',
|
||||||
|
'from generate_series(',
|
||||||
|
'\'1970-01-01 00:00:00\'::timestamptz,',
|
||||||
|
'\'1970-01-01 01:59:00\'::timestamptz,',
|
||||||
|
' \'1 minute\'::interval',
|
||||||
|
') date'
|
||||||
|
].join(' ')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
var dateHistogramsUseCases = [{
|
||||||
|
desc: 'supporting timestamp with offset',
|
||||||
|
dataviewId: 'timezone_epoch_histogram_tz'
|
||||||
|
}, {
|
||||||
|
desc: 'supporting timestamp without offset',
|
||||||
|
dataviewId: 'timezone_epoch_histogram'
|
||||||
|
}];
|
||||||
|
|
||||||
|
dateHistogramsUseCases.forEach(function (test) {
|
||||||
|
it('should return histogram with two buckets ' + test.desc , function(done) {
|
||||||
|
this.testClient = new TestClient(mapConfig, 1234);
|
||||||
|
|
||||||
|
const override = {
|
||||||
|
aggregation: 'day',
|
||||||
|
offset: '-3600'
|
||||||
|
};
|
||||||
|
|
||||||
|
this.testClient.getDataview(test.dataviewId, override, function(err, dataview) {
|
||||||
|
assert.ifError(err);
|
||||||
|
|
||||||
|
var OFFSET_IN_MINUTES = -1 * 60; // GMT-01
|
||||||
|
var initialTimestamp = '1969-12-31T00:00:00-01:00';
|
||||||
|
var binsStartInMilliseconds = dataview.bins_start * 1000;
|
||||||
|
var binsStartFormatted = moment.utc(binsStartInMilliseconds)
|
||||||
|
.utcOffset(OFFSET_IN_MINUTES)
|
||||||
|
.format();
|
||||||
|
assert.equal(binsStartFormatted, initialTimestamp);
|
||||||
|
|
||||||
|
dataview.bins.forEach(function (bin, index) {
|
||||||
|
var binTimestampExpected = moment.utc(initialTimestamp)
|
||||||
|
.utcOffset(OFFSET_IN_MINUTES)
|
||||||
|
.add(index, override.aggregation)
|
||||||
|
.format();
|
||||||
|
var binsTimestampInMilliseconds = bin.timestamp * 1000;
|
||||||
|
var binTimestampFormatted = moment.utc(binsTimestampInMilliseconds)
|
||||||
|
.utcOffset(OFFSET_IN_MINUTES)
|
||||||
|
.format();
|
||||||
|
|
||||||
|
assert.equal(binTimestampFormatted, binTimestampExpected);
|
||||||
|
assert.ok(bin.timestamp <= bin.min, 'bin timestamp < bin min: ' + JSON.stringify(bin));
|
||||||
|
assert.ok(bin.min <= bin.max, 'bin min < bin max: ' + JSON.stringify(bin));
|
||||||
|
});
|
||||||
|
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
describe('histogram: be able to override with aggregation for histograms instantiated w/o aggregation', function() {
|
||||||
|
|
||||||
|
afterEach(function(done) {
|
||||||
|
if (this.testClient) {
|
||||||
|
this.testClient.drain(done);
|
||||||
|
} else {
|
||||||
|
done();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
var mapConfig = createMapConfig(
|
||||||
|
[
|
||||||
|
{
|
||||||
|
type: "cartodb",
|
||||||
|
options: {
|
||||||
|
source: {
|
||||||
|
id: "a0"
|
||||||
|
},
|
||||||
|
cartocss: "#points { marker-width: 10; marker-fill: red; }",
|
||||||
|
cartocss_version: "2.3.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
{
|
||||||
|
timezone_epoch_histogram: {
|
||||||
|
source: {
|
||||||
|
id: 'a0'
|
||||||
|
},
|
||||||
|
type: 'histogram',
|
||||||
|
options: {
|
||||||
|
column: 'd',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[
|
||||||
|
{
|
||||||
|
id: 'a0',
|
||||||
|
type: 'source',
|
||||||
|
params: {
|
||||||
|
query: [
|
||||||
|
'select null::geometry the_geom_webmercator, date AS d',
|
||||||
|
'from generate_series(',
|
||||||
|
'\'1970-01-01 00:00:00\'::timestamp,',
|
||||||
|
'\'1970-01-01 01:59:00\'::timestamp,',
|
||||||
|
' \'1 minute\'::interval',
|
||||||
|
') date'
|
||||||
|
].join(' ')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
it('should apply aggregation to the histogram', function(done) {
|
||||||
|
this.testClient = new TestClient(mapConfig, 1234);
|
||||||
|
|
||||||
|
const override = {
|
||||||
|
aggregation: 'day',
|
||||||
|
offset: '-3600'
|
||||||
|
};
|
||||||
|
|
||||||
|
this.testClient.getDataview('timezone_epoch_histogram', override, function(err, dataview) {
|
||||||
|
assert.ifError(err);
|
||||||
|
|
||||||
|
var OFFSET_IN_MINUTES = -1 * 60; // GMT-01
|
||||||
|
var initialTimestamp = '1969-12-31T00:00:00-01:00';
|
||||||
|
var binsStartInMilliseconds = dataview.bins_start * 1000;
|
||||||
|
var binsStartFormatted = moment.utc(binsStartInMilliseconds)
|
||||||
|
.utcOffset(OFFSET_IN_MINUTES)
|
||||||
|
.format();
|
||||||
|
assert.equal(binsStartFormatted, initialTimestamp);
|
||||||
|
|
||||||
|
dataview.bins.forEach(function (bin, index) {
|
||||||
|
var binTimestampExpected = moment.utc(initialTimestamp)
|
||||||
|
.utcOffset(OFFSET_IN_MINUTES)
|
||||||
|
.add(index, override.aggregation)
|
||||||
|
.format();
|
||||||
|
var binsTimestampInMilliseconds = bin.timestamp * 1000;
|
||||||
|
var binTimestampFormatted = moment.utc(binsTimestampInMilliseconds)
|
||||||
|
.utcOffset(OFFSET_IN_MINUTES)
|
||||||
|
.format();
|
||||||
|
|
||||||
|
assert.equal(binTimestampFormatted, binTimestampExpected);
|
||||||
|
assert.ok(bin.timestamp <= bin.min, 'bin timestamp < bin min: ' + JSON.stringify(bin));
|
||||||
|
assert.ok(bin.min <= bin.max, 'bin min < bin max: ' + JSON.stringify(bin));
|
||||||
|
});
|
||||||
|
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
@ -1,311 +0,0 @@
|
|||||||
var testHelper = require('../support/test_helper');
|
|
||||||
|
|
||||||
var assert = require('../support/assert');
|
|
||||||
var _ = require('underscore');
|
|
||||||
var redis = require('redis');
|
|
||||||
|
|
||||||
var CartodbWindshaft = require('../../lib/cartodb/server');
|
|
||||||
var serverOptions = require('../../lib/cartodb/server_options');
|
|
||||||
|
|
||||||
var LayergroupToken = require('../../lib/cartodb/models/layergroup-token');
|
|
||||||
|
|
||||||
describe('render limits', function() {
|
|
||||||
|
|
||||||
var layergroupUrl = '/api/v1/map';
|
|
||||||
|
|
||||||
var redisClient = redis.createClient(global.environment.redis.port);
|
|
||||||
|
|
||||||
var server;
|
|
||||||
var keysToDelete;
|
|
||||||
beforeEach(function() {
|
|
||||||
keysToDelete = {};
|
|
||||||
server = new CartodbWindshaft(serverOptions);
|
|
||||||
server.setMaxListeners(0);
|
|
||||||
});
|
|
||||||
|
|
||||||
afterEach(function(done) {
|
|
||||||
testHelper.deleteRedisKeys(keysToDelete, done);
|
|
||||||
});
|
|
||||||
|
|
||||||
var user = 'localhost';
|
|
||||||
|
|
||||||
var pointSleepSql = "SELECT pg_sleep(0.5)," +
|
|
||||||
" 'SRID=3857;POINT(0 0)'::geometry the_geom_webmercator, 1 cartodb_id";
|
|
||||||
var pointCartoCss = '#layer { marker-fill:red; }';
|
|
||||||
var polygonSleepSql = "SELECT pg_sleep(0.5)," +
|
|
||||||
" ST_Buffer('SRID=3857;POINT(0 0)'::geometry, 100000000) the_geom_webmercator, 1 cartodb_id";
|
|
||||||
var polygonCartoCss = '#layer { polygon-fill:red; }';
|
|
||||||
|
|
||||||
function singleLayergroupConfig(sql, cartocss) {
|
|
||||||
return {
|
|
||||||
version: '1.0.0',
|
|
||||||
layers: [
|
|
||||||
{
|
|
||||||
type: 'mapnik',
|
|
||||||
options: {
|
|
||||||
sql: sql,
|
|
||||||
cartocss: cartocss,
|
|
||||||
cartocss_version: '2.0.1'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function createRequest(layergroup, userHost) {
|
|
||||||
return {
|
|
||||||
url: layergroupUrl,
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
host: userHost,
|
|
||||||
'Content-Type': 'application/json'
|
|
||||||
},
|
|
||||||
data: JSON.stringify(layergroup)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function withRenderLimit(user, renderLimit, callback) {
|
|
||||||
redisClient.SELECT(5, function(err) {
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
var userLimitsKey = 'limits:tiler:' + user;
|
|
||||||
redisClient.HSET(userLimitsKey, 'render', renderLimit, function(err) {
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
keysToDelete[userLimitsKey] = 5;
|
|
||||||
return callback();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
describe('with onTileErrorStrategy DISABLED', function() {
|
|
||||||
var onTileErrorStrategyEnabled;
|
|
||||||
before(function() {
|
|
||||||
onTileErrorStrategyEnabled = global.environment.enabledFeatures.onTileErrorStrategy;
|
|
||||||
global.environment.enabledFeatures.onTileErrorStrategy = false;
|
|
||||||
});
|
|
||||||
|
|
||||||
after(function() {
|
|
||||||
global.environment.enabledFeatures.onTileErrorStrategy = onTileErrorStrategyEnabled;
|
|
||||||
});
|
|
||||||
|
|
||||||
it("layergroup creation fails if test tile is slow", function(done) {
|
|
||||||
withRenderLimit(user, 50, function(err) {
|
|
||||||
if (err) {
|
|
||||||
return done(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
var layergroup = singleLayergroupConfig(polygonSleepSql, polygonCartoCss);
|
|
||||||
assert.response(server,
|
|
||||||
createRequest(layergroup, user),
|
|
||||||
{
|
|
||||||
status: 400
|
|
||||||
},
|
|
||||||
function(res) {
|
|
||||||
var parsed = JSON.parse(res.body);
|
|
||||||
assert.deepEqual(parsed.errors, [ 'Render timed out' ]);
|
|
||||||
done();
|
|
||||||
}
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("layergroup creation does not fail if user limit is high enough even if test tile is slow", function(done) {
|
|
||||||
withRenderLimit(user, 5000, function(err) {
|
|
||||||
if (err) {
|
|
||||||
return done(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
var layergroup = singleLayergroupConfig(polygonSleepSql, polygonCartoCss);
|
|
||||||
assert.response(server,
|
|
||||||
createRequest(layergroup, user),
|
|
||||||
{
|
|
||||||
status: 200
|
|
||||||
},
|
|
||||||
function(res) {
|
|
||||||
var parsed = JSON.parse(res.body);
|
|
||||||
assert.ok(parsed.layergroupid);
|
|
||||||
keysToDelete['map_cfg|' + LayergroupToken.parse(parsed.layergroupid).token] = 0;
|
|
||||||
keysToDelete['user:localhost:mapviews:global'] = 5;
|
|
||||||
done();
|
|
||||||
}
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
it("layergroup creation works if test tile is fast but tile request fails if they are slow", function(done) {
|
|
||||||
withRenderLimit(user, 50, function(err) {
|
|
||||||
if (err) {
|
|
||||||
return done(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
var layergroup = singleLayergroupConfig(pointSleepSql, pointCartoCss);
|
|
||||||
assert.response(server,
|
|
||||||
createRequest(layergroup, user),
|
|
||||||
{
|
|
||||||
status: 200
|
|
||||||
},
|
|
||||||
function(res) {
|
|
||||||
keysToDelete['map_cfg|' + LayergroupToken.parse(JSON.parse(res.body).layergroupid).token] = 0;
|
|
||||||
keysToDelete['user:localhost:mapviews:global'] = 5;
|
|
||||||
assert.response(server,
|
|
||||||
{
|
|
||||||
url: layergroupUrl + _.template('/<%= layergroupId %>/<%= z %>/<%= x %>/<%= y %>.png', {
|
|
||||||
layergroupId: JSON.parse(res.body).layergroupid,
|
|
||||||
z: 0,
|
|
||||||
x: 0,
|
|
||||||
y: 0
|
|
||||||
}),
|
|
||||||
method: 'GET',
|
|
||||||
headers: {
|
|
||||||
host: 'localhost'
|
|
||||||
},
|
|
||||||
encoding: 'binary'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
status: 400
|
|
||||||
},
|
|
||||||
function(res) {
|
|
||||||
var parsed = JSON.parse(res.body);
|
|
||||||
assert.deepEqual(parsed.errors, ['Render timed out']);
|
|
||||||
done();
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
}
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("tile request does not fail if user limit is high enough", function(done) {
|
|
||||||
withRenderLimit(user, 5000, function(err) {
|
|
||||||
if (err) {
|
|
||||||
return done(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
var layergroup = singleLayergroupConfig(pointSleepSql, pointCartoCss);
|
|
||||||
assert.response(server,
|
|
||||||
createRequest(layergroup, user),
|
|
||||||
{
|
|
||||||
status: 200
|
|
||||||
},
|
|
||||||
function(res) {
|
|
||||||
keysToDelete['map_cfg|' + LayergroupToken.parse(JSON.parse(res.body).layergroupid).token] = 0;
|
|
||||||
keysToDelete['user:localhost:mapviews:global'] = 5;
|
|
||||||
assert.response(server,
|
|
||||||
{
|
|
||||||
url: layergroupUrl + _.template('/<%= layergroupId %>/<%= z %>/<%= x %>/<%= y %>.png', {
|
|
||||||
layergroupId: JSON.parse(res.body).layergroupid,
|
|
||||||
z: 0,
|
|
||||||
x: 0,
|
|
||||||
y: 0
|
|
||||||
}),
|
|
||||||
method: 'GET',
|
|
||||||
headers: {
|
|
||||||
host: 'localhost'
|
|
||||||
},
|
|
||||||
encoding: 'binary'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
status: 200,
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'image/png'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
function(res, err) {
|
|
||||||
done(err);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
}
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('with onTileErrorStrategy', function() {
|
|
||||||
|
|
||||||
it("layergroup creation works even if test tile is slow", function(done) {
|
|
||||||
withRenderLimit(user, 50, function(err) {
|
|
||||||
if (err) {
|
|
||||||
return done(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
var layergroup = singleLayergroupConfig(polygonSleepSql, polygonCartoCss);
|
|
||||||
assert.response(server,
|
|
||||||
createRequest(layergroup, user),
|
|
||||||
{
|
|
||||||
status: 200
|
|
||||||
},
|
|
||||||
function(res) {
|
|
||||||
var parsed = JSON.parse(res.body);
|
|
||||||
assert.ok(parsed.layergroupid);
|
|
||||||
keysToDelete['map_cfg|' + LayergroupToken.parse(parsed.layergroupid).token] = 0;
|
|
||||||
keysToDelete['user:localhost:mapviews:global'] = 5;
|
|
||||||
done();
|
|
||||||
}
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("layergroup creation and tile requests works even if they are slow but returns fallback", function(done) {
|
|
||||||
withRenderLimit(user, 50, function(err) {
|
|
||||||
if (err) {
|
|
||||||
return done(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
var layergroup = singleLayergroupConfig(pointSleepSql, pointCartoCss);
|
|
||||||
assert.response(server,
|
|
||||||
createRequest(layergroup, user),
|
|
||||||
{
|
|
||||||
status: 200
|
|
||||||
},
|
|
||||||
function(res) {
|
|
||||||
keysToDelete['map_cfg|' + LayergroupToken.parse(JSON.parse(res.body).layergroupid).token] = 0;
|
|
||||||
keysToDelete['user:localhost:mapviews:global'] = 5;
|
|
||||||
assert.response(server,
|
|
||||||
{
|
|
||||||
url: layergroupUrl + _.template('/<%= layergroupId %>/<%= z %>/<%= x %>/<%= y %>.png', {
|
|
||||||
layergroupId: JSON.parse(res.body).layergroupid,
|
|
||||||
z: 0,
|
|
||||||
x: 0,
|
|
||||||
y: 0
|
|
||||||
}),
|
|
||||||
method: 'GET',
|
|
||||||
headers: {
|
|
||||||
host: 'localhost'
|
|
||||||
},
|
|
||||||
encoding: 'binary'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
status: 200,
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'image/png'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
function(res, err) {
|
|
||||||
if (err) {
|
|
||||||
done(err);
|
|
||||||
}
|
|
||||||
var referenceImagePath = './test/fixtures/render-timeout-fallback.png';
|
|
||||||
assert.imageBufferIsSimilarToFile(res.body, referenceImagePath, 25,
|
|
||||||
function(imgErr/*, similarity*/) {
|
|
||||||
done(imgErr);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
}
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
@ -24,14 +24,24 @@ describe('mvt', function () {
|
|||||||
desc: 'should get empty mvt with code 204 (no content)',
|
desc: 'should get empty mvt with code 204 (no content)',
|
||||||
coords: { z: 0, x: 0, y: 0 },
|
coords: { z: 0, x: 0, y: 0 },
|
||||||
format: 'mvt',
|
format: 'mvt',
|
||||||
|
response: {
|
||||||
status: 204,
|
status: 204,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': undefined
|
||||||
|
}
|
||||||
|
},
|
||||||
mapConfig: createMapConfig(TestClient.SQL.EMPTY)
|
mapConfig: createMapConfig(TestClient.SQL.EMPTY)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: 'should get mvt tile with code 200 (ok)',
|
desc: 'should get mvt tile with code 200 (ok)',
|
||||||
coords: { z: 0, x: 0, y: 0 },
|
coords: { z: 0, x: 0, y: 0 },
|
||||||
format: 'mvt',
|
format: 'mvt',
|
||||||
|
response: {
|
||||||
status: 200,
|
status: 200,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/x-protobuf'
|
||||||
|
}
|
||||||
|
},
|
||||||
mapConfig: createMapConfig()
|
mapConfig: createMapConfig()
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
@ -40,12 +50,12 @@ describe('mvt', function () {
|
|||||||
it(test.desc, done => {
|
it(test.desc, done => {
|
||||||
const testClient = new TestClient(test.mapConfig, 1234);
|
const testClient = new TestClient(test.mapConfig, 1234);
|
||||||
const { z, x, y } = test.coords;
|
const { z, x, y } = test.coords;
|
||||||
const { format, status } = test;
|
const { format, response } = test;
|
||||||
|
|
||||||
testClient.getTile(z, x, y, { format, status }, (err, res) => {
|
testClient.getTile(z, x, y, { format, response }, (err, res) => {
|
||||||
assert.ifError(err);
|
assert.ifError(err);
|
||||||
|
|
||||||
assert.equal(res.statusCode, test.status);
|
assert.equal(res.statusCode, test.response.status);
|
||||||
testClient.drain(done);
|
testClient.drain(done);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
786
test/acceptance/user-database-timeout-limit.js
Normal file
786
test/acceptance/user-database-timeout-limit.js
Normal file
@ -0,0 +1,786 @@
|
|||||||
|
require('../support/test_helper');
|
||||||
|
|
||||||
|
const assert = require('../support/assert');
|
||||||
|
const TestClient = require('../support/test-client');
|
||||||
|
|
||||||
|
const timeoutErrorTilePath = `${process.cwd()}/assets/render-timeout-fallback.png`;
|
||||||
|
|
||||||
|
const pointSleepSql = `
|
||||||
|
SELECT
|
||||||
|
pg_sleep(0.3),
|
||||||
|
'SRID=3857;POINT(0 0)'::geometry the_geom_webmercator,
|
||||||
|
1 cartodb_id,
|
||||||
|
2 val
|
||||||
|
`;
|
||||||
|
|
||||||
|
const validationPointSleepSql = `
|
||||||
|
SELECT
|
||||||
|
pg_sleep(0.3),
|
||||||
|
ST_Transform('SRID=4326;POINT(-180 85.05112877)'::geometry, 3857) the_geom_webmercator,
|
||||||
|
1 cartodb_id,
|
||||||
|
2 val
|
||||||
|
`;
|
||||||
|
|
||||||
|
const createMapConfig = ({
|
||||||
|
version = '1.6.0',
|
||||||
|
type = 'cartodb',
|
||||||
|
sql = pointSleepSql,
|
||||||
|
cartocss = TestClient.CARTOCSS.POINTS,
|
||||||
|
cartocss_version = '2.3.0',
|
||||||
|
interactivity = 'cartodb_id',
|
||||||
|
countBy = 'cartodb_id',
|
||||||
|
attributes
|
||||||
|
} = {}) => ({
|
||||||
|
version,
|
||||||
|
layers: [{
|
||||||
|
type,
|
||||||
|
options: {
|
||||||
|
source: {
|
||||||
|
id: 'a0'
|
||||||
|
},
|
||||||
|
cartocss,
|
||||||
|
cartocss_version,
|
||||||
|
attributes,
|
||||||
|
interactivity
|
||||||
|
}
|
||||||
|
}],
|
||||||
|
analyses: [
|
||||||
|
{
|
||||||
|
id: 'a0',
|
||||||
|
type: 'source',
|
||||||
|
params: {
|
||||||
|
query: sql
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
dataviews: {
|
||||||
|
count: {
|
||||||
|
source: {
|
||||||
|
id: 'a0'
|
||||||
|
},
|
||||||
|
type: 'formula',
|
||||||
|
options: {
|
||||||
|
column: countBy,
|
||||||
|
operation: 'count'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const DATASOURCE_TIMEOUT_ERROR = {
|
||||||
|
errors: ['You are over platform\'s limits. Please contact us to know more details'],
|
||||||
|
errors_with_context: [{
|
||||||
|
type: 'limit',
|
||||||
|
subtype: 'datasource',
|
||||||
|
message: 'You are over platform\'s limits. Please contact us to know more details'
|
||||||
|
}]
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('user database timeout limit', function () {
|
||||||
|
describe('dataview', function () {
|
||||||
|
beforeEach(function (done) {
|
||||||
|
const mapconfig = createMapConfig();
|
||||||
|
this.testClient = new TestClient(mapconfig, 1234);
|
||||||
|
this.testClient.setUserDatabaseTimeoutLimit(200, done);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(function (done) {
|
||||||
|
this.testClient.setUserDatabaseTimeoutLimit(0, (err) => {
|
||||||
|
if (err) {
|
||||||
|
return done(err);
|
||||||
|
}
|
||||||
|
this.testClient.drain(done);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('layergroup creation works but dataview request fails due to statement timeout', function (done) {
|
||||||
|
const params = {
|
||||||
|
response: {
|
||||||
|
status: 429,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json; charset=utf-8'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
this.testClient.getDataview('count', params, (err, timeoutError) => {
|
||||||
|
assert.ifError(err);
|
||||||
|
|
||||||
|
assert.deepEqual(timeoutError, DATASOURCE_TIMEOUT_ERROR);
|
||||||
|
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('raster', function () {
|
||||||
|
describe('while validating in layergroup creation', function () {
|
||||||
|
beforeEach(function (done) {
|
||||||
|
const mapconfig = createMapConfig({ sql: validationPointSleepSql });
|
||||||
|
this.testClient = new TestClient(mapconfig, 1234);
|
||||||
|
this.testClient.setUserDatabaseTimeoutLimit(200, done);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(function (done) {
|
||||||
|
this.testClient.setUserDatabaseTimeoutLimit(0, (err) => {
|
||||||
|
if (err) {
|
||||||
|
return done(err);
|
||||||
|
}
|
||||||
|
this.testClient.drain(done);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('fails due to statement timeout', function (done) {
|
||||||
|
const expectedResponse = {
|
||||||
|
status: 429,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json; charset=utf-8'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
this.testClient.getLayergroup(expectedResponse, (err, timeoutError) => {
|
||||||
|
assert.deepEqual(timeoutError, {
|
||||||
|
errors: [ 'You are over platform\'s limits. Please contact us to know more details' ],
|
||||||
|
errors_with_context: [{
|
||||||
|
type: 'limit',
|
||||||
|
subtype: 'datasource',
|
||||||
|
message: 'You are over platform\'s limits. Please contact us to know more details',
|
||||||
|
layer: { id: 'layer0', index: 0, type: 'mapnik' }
|
||||||
|
}]
|
||||||
|
});
|
||||||
|
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('fetching raster tiles', function () {
|
||||||
|
describe('with user\'s timeout of 200 ms', function () {
|
||||||
|
describe('with onTileErrorStrategy ENABLED', function () {
|
||||||
|
let onTileErrorStrategy;
|
||||||
|
|
||||||
|
beforeEach(function (done) {
|
||||||
|
onTileErrorStrategy = global.environment.enabledFeatures.onTileErrorStrategy;
|
||||||
|
global.environment.enabledFeatures.onTileErrorStrategy = true;
|
||||||
|
|
||||||
|
const mapconfig = createMapConfig();
|
||||||
|
this.testClient = new TestClient(mapconfig, 1234);
|
||||||
|
const expectedResponse = {
|
||||||
|
status: 200,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json; charset=utf-8'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
this.testClient.setUserDatabaseTimeoutLimit(200, (err) => {
|
||||||
|
if (err) {
|
||||||
|
return done(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.testClient.getLayergroup(expectedResponse, (err, res) => {
|
||||||
|
if (err) {
|
||||||
|
return done(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.layergroupid = res.layergroupid;
|
||||||
|
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(function (done) {
|
||||||
|
global.environment.enabledFeatures.onTileErrorStrategy = onTileErrorStrategy;
|
||||||
|
this.testClient.setUserDatabaseTimeoutLimit(0, (err) => {
|
||||||
|
if (err) {
|
||||||
|
return done(err);
|
||||||
|
}
|
||||||
|
this.testClient.drain(done);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('"png" fails due to statement timeout', function (done) {
|
||||||
|
const params = {
|
||||||
|
layergroupid: this.layergroupid,
|
||||||
|
format: 'png',
|
||||||
|
layers: [ 0 ]
|
||||||
|
};
|
||||||
|
|
||||||
|
this.testClient.getTile(0, 0, 0, params, (err, res, tile) => {
|
||||||
|
assert.ifError(err);
|
||||||
|
|
||||||
|
assert.imageIsSimilarToFile(tile, timeoutErrorTilePath, 0.05, (err) => {
|
||||||
|
assert.ifError(err);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('"static png" fails due to statement timeout', function (done) {
|
||||||
|
const params = {
|
||||||
|
layergroupid: this.layergroupid,
|
||||||
|
zoom: 0,
|
||||||
|
lat: 0,
|
||||||
|
lng: 0,
|
||||||
|
width: 256,
|
||||||
|
height: 256,
|
||||||
|
format: 'png'
|
||||||
|
};
|
||||||
|
|
||||||
|
this.testClient.getStaticCenter(params, function (err, res, tile) {
|
||||||
|
assert.ifError(err);
|
||||||
|
|
||||||
|
assert.imageIsSimilarToFile(tile, timeoutErrorTilePath, 0.05, (err) => {
|
||||||
|
assert.ifError(err);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('with onTileErrorStrategy DISABLED', function () {
|
||||||
|
let onTileErrorStrategy;
|
||||||
|
|
||||||
|
beforeEach(function (done) {
|
||||||
|
onTileErrorStrategy = global.environment.enabledFeatures.onTileErrorStrategy;
|
||||||
|
global.environment.enabledFeatures.onTileErrorStrategy = false;
|
||||||
|
|
||||||
|
const mapconfig = createMapConfig();
|
||||||
|
this.testClient = new TestClient(mapconfig, 1234);
|
||||||
|
const expectedResponse = {
|
||||||
|
status: 200,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json; charset=utf-8'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
this.testClient.setUserDatabaseTimeoutLimit(200, (err) => {
|
||||||
|
if (err) {
|
||||||
|
return done(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.testClient.getLayergroup(expectedResponse, (err, res) => {
|
||||||
|
if (err) {
|
||||||
|
return done(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.layergroupid = res.layergroupid;
|
||||||
|
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(function (done) {
|
||||||
|
global.environment.enabledFeatures.onTileErrorStrategy = onTileErrorStrategy;
|
||||||
|
|
||||||
|
this.testClient.setUserDatabaseTimeoutLimit(0, (err) => {
|
||||||
|
if (err) {
|
||||||
|
return done(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.testClient.drain(done);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('"png" fails due to statement timeout', function (done) {
|
||||||
|
const params = {
|
||||||
|
layergroupid: this.layergroupid,
|
||||||
|
format: 'png',
|
||||||
|
layers: [ 0 ],
|
||||||
|
response: {
|
||||||
|
status: 429,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json; charset=utf-8'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
this.testClient.getTile(0, 0, 0, params, (err, res, timeoutError) => {
|
||||||
|
assert.ifError(err);
|
||||||
|
|
||||||
|
assert.deepEqual(timeoutError, DATASOURCE_TIMEOUT_ERROR);
|
||||||
|
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('"static png" fails due to statement timeout', function (done) {
|
||||||
|
const params = {
|
||||||
|
layergroupid: this.layergroupid,
|
||||||
|
zoom: 0,
|
||||||
|
lat: 0,
|
||||||
|
lng: 0,
|
||||||
|
width: 256,
|
||||||
|
height: 256,
|
||||||
|
format: 'png',
|
||||||
|
response: {
|
||||||
|
status: 429,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json; charset=utf-8'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
this.testClient.getStaticCenter(params, (err, res, timeoutError) => {
|
||||||
|
assert.ifError(err);
|
||||||
|
|
||||||
|
assert.deepEqual(timeoutError, DATASOURCE_TIMEOUT_ERROR);
|
||||||
|
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('vector', function () {
|
||||||
|
describe('while validating in layergroup creation', function () {
|
||||||
|
beforeEach(function (done) {
|
||||||
|
const mapconfig = createMapConfig({ sql: validationPointSleepSql });
|
||||||
|
this.testClient = new TestClient(mapconfig, 1234);
|
||||||
|
this.testClient.setUserDatabaseTimeoutLimit(200, done);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(function (done) {
|
||||||
|
this.testClient.setUserDatabaseTimeoutLimit(0, (err) => {
|
||||||
|
if (err) {
|
||||||
|
return done(err);
|
||||||
|
}
|
||||||
|
this.testClient.drain(done);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('fails due to statement timeout', function (done) {
|
||||||
|
const expectedResponse = {
|
||||||
|
status: 429,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json; charset=utf-8'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
this.testClient.getLayergroup(expectedResponse, (err, timeoutError) => {
|
||||||
|
assert.deepEqual(timeoutError, {
|
||||||
|
errors: [ 'You are over platform\'s limits. Please contact us to know more details' ],
|
||||||
|
errors_with_context: [{
|
||||||
|
type: 'limit',
|
||||||
|
subtype: 'datasource',
|
||||||
|
message: 'You are over platform\'s limits. Please contact us to know more details',
|
||||||
|
layer: { id: 'layer0', index: 0, type: 'mapnik' }
|
||||||
|
}]
|
||||||
|
});
|
||||||
|
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('fetching vector tiles', function () {
|
||||||
|
beforeEach(function (done) {
|
||||||
|
const mapconfig = createMapConfig();
|
||||||
|
this.testClient = new TestClient(mapconfig, 1234);
|
||||||
|
const expectedResponse = {
|
||||||
|
status: 200,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json; charset=utf-8'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
this.testClient.getLayergroup(expectedResponse, (err, res) => {
|
||||||
|
if (err) {
|
||||||
|
return done(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.layergroupid = res.layergroupid;
|
||||||
|
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(function (done) {
|
||||||
|
this.testClient.drain(done);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('with user\'s timeout of 200 ms', function () {
|
||||||
|
beforeEach(function (done) {
|
||||||
|
this.testClient.setUserDatabaseTimeoutLimit(200, done);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(function (done) {
|
||||||
|
this.testClient.setUserDatabaseTimeoutLimit(0, done);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('"mvt" fails due to statement timeout', function (done) {
|
||||||
|
const params = {
|
||||||
|
layergroupid: this.layergroupid,
|
||||||
|
format: 'mvt',
|
||||||
|
layers: [ 0 ],
|
||||||
|
response: {
|
||||||
|
status: 429,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json; charset=utf-8'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
this.testClient.getTile(0, 0, 0, params, (err, res, timeoutError) => {
|
||||||
|
assert.ifError(err);
|
||||||
|
|
||||||
|
assert.deepEqual(timeoutError, DATASOURCE_TIMEOUT_ERROR);
|
||||||
|
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
describe('interactivity', function () {
|
||||||
|
describe('while validating in layergroup creation', function () {
|
||||||
|
beforeEach(function (done) {
|
||||||
|
const mapconfig = createMapConfig({ sql: validationPointSleepSql, interactivity: 'val' });
|
||||||
|
this.testClient = new TestClient(mapconfig, 1234);
|
||||||
|
this.testClient.setUserDatabaseTimeoutLimit(200, done);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(function (done) {
|
||||||
|
this.testClient.setUserDatabaseTimeoutLimit(0, (err) => {
|
||||||
|
if (err) {
|
||||||
|
return done(err);
|
||||||
|
}
|
||||||
|
this.testClient.drain(done);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('fails due to statement timeout', function (done) {
|
||||||
|
const expectedResponse = {
|
||||||
|
status: 429,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json; charset=utf-8'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
this.testClient.getLayergroup(expectedResponse, (err, timeoutError) => {
|
||||||
|
assert.deepEqual(timeoutError, {
|
||||||
|
errors: [ 'You are over platform\'s limits. Please contact us to know more details' ],
|
||||||
|
errors_with_context: [{
|
||||||
|
type: 'limit',
|
||||||
|
subtype: 'datasource',
|
||||||
|
message: 'You are over platform\'s limits. Please contact us to know more details',
|
||||||
|
layer: { id: 'layer0', index: 0, type: 'mapnik' }
|
||||||
|
}]
|
||||||
|
});
|
||||||
|
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('fetching interactivity tiles', function () {
|
||||||
|
beforeEach(function (done) {
|
||||||
|
const mapconfig = createMapConfig({ interactivity: 'val' });
|
||||||
|
this.testClient = new TestClient(mapconfig, 1234);
|
||||||
|
const expectedResponse = {
|
||||||
|
status: 200,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json; charset=utf-8'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
this.testClient.getLayergroup(expectedResponse, (err, res) => {
|
||||||
|
if (err) {
|
||||||
|
return done(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.layergroupid = res.layergroupid;
|
||||||
|
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(function (done) {
|
||||||
|
this.testClient.drain(done);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('with user\'s timeout of 200 ms', function () {
|
||||||
|
beforeEach(function (done) {
|
||||||
|
this.testClient.setUserDatabaseTimeoutLimit(200, done);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(function (done) {
|
||||||
|
this.testClient.setUserDatabaseTimeoutLimit(0, done);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('"grid.json" fails due to statement timeout', function (done) {
|
||||||
|
const params = {
|
||||||
|
layergroupid: this.layergroupid,
|
||||||
|
format: 'grid.json',
|
||||||
|
layers: 'mapnik',
|
||||||
|
response: {
|
||||||
|
status: 429,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json; charset=utf-8'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
this.testClient.getTile(0, 0, 0, params, (err, res, timeoutError) => {
|
||||||
|
assert.ifError(err);
|
||||||
|
|
||||||
|
assert.deepEqual(timeoutError, DATASOURCE_TIMEOUT_ERROR);
|
||||||
|
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('torque', function () {
|
||||||
|
describe('while validating in layergroup creation', function () {
|
||||||
|
beforeEach(function (done) {
|
||||||
|
const mapconfig = createMapConfig({
|
||||||
|
type: 'torque',
|
||||||
|
cartocss: TestClient.CARTOCSS.TORQUE
|
||||||
|
});
|
||||||
|
this.testClient = new TestClient(mapconfig, 1234);
|
||||||
|
this.testClient.setUserDatabaseTimeoutLimit(200, done);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(function (done) {
|
||||||
|
this.testClient.setUserDatabaseTimeoutLimit(0, (err) => {
|
||||||
|
if (err) {
|
||||||
|
return done(err);
|
||||||
|
}
|
||||||
|
this.testClient.drain(done);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('fails due to statement timeout', function (done) {
|
||||||
|
const expectedResponse = {
|
||||||
|
status: 429,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json; charset=utf-8'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
this.testClient.getLayergroup(expectedResponse, (err, timeoutError) => {
|
||||||
|
assert.deepEqual(timeoutError, {
|
||||||
|
errors: [ 'You are over platform\'s limits. Please contact us to know more details' ],
|
||||||
|
errors_with_context: [{
|
||||||
|
type: 'limit',
|
||||||
|
subtype: 'datasource',
|
||||||
|
message: 'You are over platform\'s limits. Please contact us to know more details',
|
||||||
|
layer: { id: 'torque-layer0', index: 0, type: 'torque' }
|
||||||
|
}]
|
||||||
|
});
|
||||||
|
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('fetching torque tiles', function () {
|
||||||
|
beforeEach(function (done) {
|
||||||
|
const mapconfig = createMapConfig({
|
||||||
|
type: 'torque',
|
||||||
|
cartocss: TestClient.CARTOCSS.TORQUE
|
||||||
|
});
|
||||||
|
this.testClient = new TestClient(mapconfig, 1234);
|
||||||
|
const expectedResponse = {
|
||||||
|
status: 200,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json; charset=utf-8'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
this.testClient.getLayergroup(expectedResponse, (err, res) => {
|
||||||
|
if (err) {
|
||||||
|
return done(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.layergroupid = res.layergroupid;
|
||||||
|
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(function (done) {
|
||||||
|
this.testClient.drain(done);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('with user\'s timeout of 200 ms', function () {
|
||||||
|
beforeEach(function (done) {
|
||||||
|
this.testClient.setUserDatabaseTimeoutLimit(200, done);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(function (done) {
|
||||||
|
this.testClient.setUserDatabaseTimeoutLimit(0, done);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('"torque.json" fails due to statement timeout', function (done) {
|
||||||
|
const params = {
|
||||||
|
layergroupid: this.layergroupid,
|
||||||
|
format: 'torque.json',
|
||||||
|
layers: [ 0 ],
|
||||||
|
response: {
|
||||||
|
status: 429,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json; charset=utf-8'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
this.testClient.getTile(0, 0, 0, params, (err, res, timeoutError) => {
|
||||||
|
assert.ifError(err);
|
||||||
|
|
||||||
|
assert.deepEqual(timeoutError, DATASOURCE_TIMEOUT_ERROR);
|
||||||
|
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('".png" fails due to statement timeout', function (done) {
|
||||||
|
const params = {
|
||||||
|
layergroupid: this.layergroupid,
|
||||||
|
format: 'torque.png',
|
||||||
|
layers: [ 0 ],
|
||||||
|
response: {
|
||||||
|
status: 429,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json; charset=utf-8'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
this.testClient.getTile(0, 0, 0, params, (err, res, attributes) => {
|
||||||
|
assert.ifError(err);
|
||||||
|
|
||||||
|
assert.deepEqual(attributes, DATASOURCE_TIMEOUT_ERROR);
|
||||||
|
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('attributes:', function () {
|
||||||
|
describe('while validating in map instatiation', function () {
|
||||||
|
beforeEach(function (done) {
|
||||||
|
const mapconfig = createMapConfig({
|
||||||
|
attributes: {
|
||||||
|
id: 'cartodb_id',
|
||||||
|
columns: [ 'val' ]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.testClient = new TestClient(mapconfig, 1234);
|
||||||
|
this.testClient.setUserDatabaseTimeoutLimit(200, done);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(function (done) {
|
||||||
|
this.testClient.setUserDatabaseTimeoutLimit(0, (err) => {
|
||||||
|
if (err) {
|
||||||
|
return done(err);
|
||||||
|
}
|
||||||
|
this.testClient.drain(done);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('layergroup creation fails due to statement timeout', function (done) {
|
||||||
|
const expectedResponse = {
|
||||||
|
status: 429,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json; charset=utf-8'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
this.testClient.getLayergroup(expectedResponse, (err, timeoutError) => {
|
||||||
|
assert.deepEqual(timeoutError, {
|
||||||
|
errors: [ 'You are over platform\'s limits. Please contact us to know more details' ],
|
||||||
|
errors_with_context: [{
|
||||||
|
type: 'limit',
|
||||||
|
subtype: 'datasource',
|
||||||
|
message: 'You are over platform\'s limits. Please contact us to know more details',
|
||||||
|
layer: {
|
||||||
|
id: 'layer0',
|
||||||
|
index: 0,
|
||||||
|
type: 'mapnik'
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
});
|
||||||
|
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('fetching by feature id', function () {
|
||||||
|
beforeEach(function (done) {
|
||||||
|
const mapconfig = createMapConfig({
|
||||||
|
attributes: {
|
||||||
|
id: 'cartodb_id',
|
||||||
|
columns: [ 'val' ]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.testClient = new TestClient(mapconfig, 1234);
|
||||||
|
|
||||||
|
const expectedResponse = {
|
||||||
|
status: 200,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json; charset=utf-8'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
this.testClient.getLayergroup(expectedResponse, (err, res) => {
|
||||||
|
if (err) {
|
||||||
|
return done(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.layergroupid = res.layergroupid;
|
||||||
|
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(function (done) {
|
||||||
|
this.testClient.drain(done);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('with user\'s timeout of 200 ms', function () {
|
||||||
|
beforeEach(function (done) {
|
||||||
|
this.testClient.setUserDatabaseTimeoutLimit(200, done);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(function (done) {
|
||||||
|
this.testClient.setUserDatabaseTimeoutLimit(0, done);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('fails due to statement timeout', function (done) {
|
||||||
|
const params = {
|
||||||
|
layergroupid: this.layergroupid,
|
||||||
|
featureId: 1,
|
||||||
|
layer: 0,
|
||||||
|
response: {
|
||||||
|
status: 429,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json; charset=utf-8'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
this.testClient.getAttributes(params, (err, res, timeoutError) => {
|
||||||
|
assert.ifError(err);
|
||||||
|
|
||||||
|
assert.deepEqual(timeoutError, DATASOURCE_TIMEOUT_ERROR);
|
||||||
|
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
394
test/acceptance/user-render-timeout-limit.js
Normal file
394
test/acceptance/user-render-timeout-limit.js
Normal file
@ -0,0 +1,394 @@
|
|||||||
|
require('../support/test_helper');
|
||||||
|
|
||||||
|
const assert = require('../support/assert');
|
||||||
|
const TestClient = require('../support/test-client');
|
||||||
|
|
||||||
|
const timeoutErrorTilePath = `${process.cwd()}/assets/render-timeout-fallback.png`;
|
||||||
|
|
||||||
|
const pointSleepSql = `
|
||||||
|
SELECT
|
||||||
|
pg_sleep(0.5),
|
||||||
|
'SRID=3857;POINT(0 0)'::geometry the_geom_webmercator,
|
||||||
|
1 cartodb_id,
|
||||||
|
2 val
|
||||||
|
`;
|
||||||
|
|
||||||
|
// during instatiation we validate tile 30/0/0, creating a point in that tile `pg_sleep` will throw a timeout
|
||||||
|
const validationPointSleepSql = `
|
||||||
|
SELECT
|
||||||
|
pg_sleep(0.5),
|
||||||
|
ST_Transform('SRID=4326;POINT(-180 85.05112877)'::geometry, 3857) the_geom_webmercator,
|
||||||
|
1 cartodb_id,
|
||||||
|
2 val
|
||||||
|
`;
|
||||||
|
|
||||||
|
const createMapConfig = ({
|
||||||
|
version = '1.6.0',
|
||||||
|
type = 'cartodb',
|
||||||
|
sql = pointSleepSql,
|
||||||
|
cartocss = TestClient.CARTOCSS.POINTS,
|
||||||
|
cartocss_version = '2.3.0',
|
||||||
|
interactivity = 'cartodb_id',
|
||||||
|
countBy = 'cartodb_id'
|
||||||
|
} = {}) => ({
|
||||||
|
version,
|
||||||
|
layers: [{
|
||||||
|
type,
|
||||||
|
options: {
|
||||||
|
source: {
|
||||||
|
id: 'a0'
|
||||||
|
},
|
||||||
|
cartocss,
|
||||||
|
cartocss_version,
|
||||||
|
interactivity
|
||||||
|
}
|
||||||
|
}],
|
||||||
|
analyses: [
|
||||||
|
{
|
||||||
|
id: 'a0',
|
||||||
|
type: 'source',
|
||||||
|
params: {
|
||||||
|
query: sql
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
dataviews: {
|
||||||
|
count: {
|
||||||
|
source: {
|
||||||
|
id: 'a0'
|
||||||
|
},
|
||||||
|
type: 'formula',
|
||||||
|
options: {
|
||||||
|
column: countBy,
|
||||||
|
operation: 'count'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('user render timeout limit', function () {
|
||||||
|
describe('map instantiation => validation', function () {
|
||||||
|
beforeEach(function (done) {
|
||||||
|
const mapconfig = createMapConfig({ sql: validationPointSleepSql });
|
||||||
|
this.testClient = new TestClient(mapconfig, 1234);
|
||||||
|
this.testClient.setUserRenderTimeoutLimit('localhost', 50, done);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(function (done) {
|
||||||
|
this.testClient.setUserRenderTimeoutLimit('localhost', 0, (err) => {
|
||||||
|
if (err) {
|
||||||
|
return done(err);
|
||||||
|
}
|
||||||
|
this.testClient.drain(done);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('layergroup creation fails due to statement timeout', function (done) {
|
||||||
|
const expectedResponse = {
|
||||||
|
status: 429,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json; charset=utf-8'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
this.testClient.getLayergroup(expectedResponse, (err, timeoutError) => {
|
||||||
|
assert.ifError(err);
|
||||||
|
|
||||||
|
assert.deepEqual(timeoutError, {
|
||||||
|
errors: ["You are over platform\'s limits. Please contact us to know more details"],
|
||||||
|
errors_with_context: [{
|
||||||
|
type: 'limit',
|
||||||
|
subtype: 'render',
|
||||||
|
message: "You are over platform\'s limits. Please contact us to know more details",
|
||||||
|
layer: {
|
||||||
|
id: "layer0",
|
||||||
|
index: 0,
|
||||||
|
type: "mapnik"
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
});
|
||||||
|
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('raster', function () {
|
||||||
|
describe('with onTileErrorStrategy ENABLED', function () {
|
||||||
|
let onTileErrorStrategy;
|
||||||
|
|
||||||
|
beforeEach(function (done) {
|
||||||
|
onTileErrorStrategy = global.environment.enabledFeatures.onTileErrorStrategy;
|
||||||
|
global.environment.enabledFeatures.onTileErrorStrategy = true;
|
||||||
|
|
||||||
|
const mapconfig = createMapConfig();
|
||||||
|
this.testClient = new TestClient(mapconfig, 1234);
|
||||||
|
this.testClient.setUserRenderTimeoutLimit('localhost', 50, done);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(function (done) {
|
||||||
|
global.environment.enabledFeatures.onTileErrorStrategy = onTileErrorStrategy;
|
||||||
|
|
||||||
|
this.testClient.setUserRenderTimeoutLimit('localhost', 0, (err) => {
|
||||||
|
if (err) {
|
||||||
|
return done(err);
|
||||||
|
}
|
||||||
|
this.testClient.drain(done);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('layergroup creation works but tile request fails due to render timeout', function (done) {
|
||||||
|
this.testClient.getTile(0, 0, 0, {}, (err, res, tile) => {
|
||||||
|
assert.ifError(err);
|
||||||
|
|
||||||
|
assert.imageIsSimilarToFile(tile, timeoutErrorTilePath, 0.05, (err) => {
|
||||||
|
assert.ifError(err);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('with onTileErrorStrategy DISABLED', function() {
|
||||||
|
var onTileErrorStrategy;
|
||||||
|
|
||||||
|
beforeEach(function (done) {
|
||||||
|
onTileErrorStrategy = global.environment.enabledFeatures.onTileErrorStrategy;
|
||||||
|
global.environment.enabledFeatures.onTileErrorStrategy = false;
|
||||||
|
|
||||||
|
const mapconfig = createMapConfig();
|
||||||
|
this.testClient = new TestClient(mapconfig, 1234);
|
||||||
|
this.testClient.setUserRenderTimeoutLimit('localhost', 50, done);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(function (done) {
|
||||||
|
global.environment.enabledFeatures.onTileErrorStrategy = onTileErrorStrategy;
|
||||||
|
|
||||||
|
this.testClient.setUserRenderTimeoutLimit('localhost', 0, (err) => {
|
||||||
|
if (err) {
|
||||||
|
return done(err);
|
||||||
|
}
|
||||||
|
this.testClient.drain(done);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('layergroup creation works and render tile fails', function (done) {
|
||||||
|
var params = {
|
||||||
|
response: {
|
||||||
|
status: 429,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json; charset=utf-8'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
this.testClient.getTile(0, 0, 0, params, (err, res, timeoutError) => {
|
||||||
|
assert.ifError(err);
|
||||||
|
|
||||||
|
assert.deepEqual(timeoutError, {
|
||||||
|
errors: ["You are over platform\'s limits. Please contact us to know more details"],
|
||||||
|
errors_with_context: [{
|
||||||
|
type: 'limit',
|
||||||
|
subtype: 'render',
|
||||||
|
message: "You are over platform\'s limits. Please contact us to know more details"
|
||||||
|
}]
|
||||||
|
});
|
||||||
|
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('vector', function () {
|
||||||
|
beforeEach(function (done) {
|
||||||
|
const mapconfig = createMapConfig();
|
||||||
|
this.testClient = new TestClient(mapconfig, 1234);
|
||||||
|
this.testClient.setUserRenderTimeoutLimit('localhost', 50, done);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(function (done) {
|
||||||
|
this.testClient.setUserRenderTimeoutLimit('localhost', 0, (err) => {
|
||||||
|
if (err) {
|
||||||
|
return done(err);
|
||||||
|
}
|
||||||
|
this.testClient.drain(done);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('layergroup creation works but vector tile request fails due to render timeout', function (done) {
|
||||||
|
const params = {
|
||||||
|
format: 'mvt',
|
||||||
|
response: {
|
||||||
|
status: 429,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json; charset=utf-8'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
this.testClient.getTile(0, 0, 0, params, (err, res, tile) => {
|
||||||
|
assert.ifError(err);
|
||||||
|
|
||||||
|
assert.deepEqual(tile, {
|
||||||
|
errors: ['You are over platform\'s limits. Please contact us to know more details'],
|
||||||
|
errors_with_context: [{
|
||||||
|
type: 'limit',
|
||||||
|
subtype: 'render',
|
||||||
|
message: 'You are over platform\'s limits. Please contact us to know more details'
|
||||||
|
}]
|
||||||
|
});
|
||||||
|
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('interativity', function () {
|
||||||
|
beforeEach(function (done) {
|
||||||
|
const mapconfig = createMapConfig();
|
||||||
|
this.testClient = new TestClient(mapconfig, 1234);
|
||||||
|
this.testClient.setUserRenderTimeoutLimit('localhost', 50, done);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(function (done) {
|
||||||
|
this.testClient.setUserRenderTimeoutLimit('localhost', 0, (err) => {
|
||||||
|
if (err) {
|
||||||
|
return done(err);
|
||||||
|
}
|
||||||
|
this.testClient.drain(done);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('layergroup creation works but "grid.json" tile request fails due to render timeout', function (done) {
|
||||||
|
const params = {
|
||||||
|
layers: 'mapnik',
|
||||||
|
format: 'grid.json',
|
||||||
|
response: {
|
||||||
|
status: 429,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json; charset=utf-8'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
this.testClient.getTile(0, 0, 0, params, (err, res, tile) => {
|
||||||
|
assert.ifError(err);
|
||||||
|
|
||||||
|
assert.deepEqual(tile, {
|
||||||
|
errors: ['You are over platform\'s limits. Please contact us to know more details'],
|
||||||
|
errors_with_context: [{
|
||||||
|
type: 'limit',
|
||||||
|
subtype: 'render',
|
||||||
|
message: 'You are over platform\'s limits. Please contact us to know more details'
|
||||||
|
}]
|
||||||
|
});
|
||||||
|
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('static images', function () {
|
||||||
|
describe('with onTileErrorStrategy ENABLED', function () {
|
||||||
|
let onTileErrorStrategy;
|
||||||
|
|
||||||
|
beforeEach(function (done) {
|
||||||
|
onTileErrorStrategy = global.environment.enabledFeatures.onTileErrorStrategy;
|
||||||
|
global.environment.enabledFeatures.onTileErrorStrategy = true;
|
||||||
|
|
||||||
|
const mapconfig = createMapConfig();
|
||||||
|
this.testClient = new TestClient(mapconfig, 1234);
|
||||||
|
this.testClient.setUserRenderTimeoutLimit('localhost', 50, done);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(function (done) {
|
||||||
|
global.environment.enabledFeatures.onTileErrorStrategy = onTileErrorStrategy;
|
||||||
|
|
||||||
|
this.testClient.setUserRenderTimeoutLimit('localhost', 0, (err) => {
|
||||||
|
if (err) {
|
||||||
|
return done(err);
|
||||||
|
}
|
||||||
|
this.testClient.drain(done);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('layergroup creation works but static image fails due to render timeout', function (done) {
|
||||||
|
const params = {
|
||||||
|
zoom: 0,
|
||||||
|
lat: 0,
|
||||||
|
lng: 0,
|
||||||
|
width: 256,
|
||||||
|
height: 256,
|
||||||
|
format: 'png'
|
||||||
|
};
|
||||||
|
|
||||||
|
this.testClient.getStaticCenter(params, function (err, res, tile) {
|
||||||
|
assert.ifError(err);
|
||||||
|
|
||||||
|
assert.imageIsSimilarToFile(tile, timeoutErrorTilePath, 0.05, (err) => {
|
||||||
|
assert.ifError(err);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('with onTileErrorStrategy DISABLED', function() {
|
||||||
|
var onTileErrorStrategy;
|
||||||
|
|
||||||
|
beforeEach(function (done) {
|
||||||
|
onTileErrorStrategy = global.environment.enabledFeatures.onTileErrorStrategy;
|
||||||
|
global.environment.enabledFeatures.onTileErrorStrategy = false;
|
||||||
|
|
||||||
|
const mapconfig = createMapConfig();
|
||||||
|
this.testClient = new TestClient(mapconfig, 1234);
|
||||||
|
this.testClient.setUserRenderTimeoutLimit('localhost', 50, done);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(function (done) {
|
||||||
|
global.environment.enabledFeatures.onTileErrorStrategy = onTileErrorStrategy;
|
||||||
|
|
||||||
|
this.testClient.setUserRenderTimeoutLimit('localhost', 0, (err) => {
|
||||||
|
if (err) {
|
||||||
|
return done(err);
|
||||||
|
}
|
||||||
|
this.testClient.drain(done);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('layergroup creation works and render tile fails', function (done) {
|
||||||
|
const params = {
|
||||||
|
zoom: 0,
|
||||||
|
lat: 0,
|
||||||
|
lng: 0,
|
||||||
|
width: 256,
|
||||||
|
height: 256,
|
||||||
|
format: 'png',
|
||||||
|
response: {
|
||||||
|
status: 429,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json; charset=utf-8'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
this.testClient.getStaticCenter(params, function (err, res, timeoutError) {
|
||||||
|
assert.ifError(err);
|
||||||
|
|
||||||
|
assert.deepEqual(timeoutError, {
|
||||||
|
errors: ["You are over platform\'s limits. Please contact us to know more details"],
|
||||||
|
errors_with_context: [{
|
||||||
|
type: 'limit',
|
||||||
|
subtype: 'render',
|
||||||
|
message: "You are over platform\'s limits. Please contact us to know more details"
|
||||||
|
}]
|
||||||
|
});
|
||||||
|
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -218,6 +218,114 @@ describe('widgets-regressions', function() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
it('should not count the polygons outside the bounding box', function(done) {
|
||||||
|
|
||||||
|
// $ % $ = not intersecting left triangle
|
||||||
|
// $$ **VVVVV** %% % = not intersecting right triangle
|
||||||
|
// $$$ *VVVVV* %%% * = intersecting triangle
|
||||||
|
// $$$$ ***** %%%% V = bounding box
|
||||||
|
// $$$$$ *** %%%%%
|
||||||
|
// $$$$$$ * %%%%%%
|
||||||
|
// $$$$$$$ %%%%%%%
|
||||||
|
// $$$$$$$$ %%%%%%%%
|
||||||
|
|
||||||
|
const notIntersectingLeftTriangle = {
|
||||||
|
type: "Polygon",
|
||||||
|
coordinates:[[
|
||||||
|
[-161.015625,69.28725695167886],
|
||||||
|
[-162.7734375,-7.710991655433217],
|
||||||
|
[-40.78125,-8.059229627200192],
|
||||||
|
[-161.015625,69.28725695167886]
|
||||||
|
]]
|
||||||
|
};
|
||||||
|
|
||||||
|
const notIntersectingRightTriangle = {
|
||||||
|
type: "Polygon",
|
||||||
|
coordinates: [[
|
||||||
|
[-29.179687499999996,-7.01366792756663],
|
||||||
|
[103.71093749999999,-6.664607562172573],
|
||||||
|
[105.46875,69.16255790810501],
|
||||||
|
[-29.179687499999996,-7.01366792756663]
|
||||||
|
]]
|
||||||
|
};
|
||||||
|
|
||||||
|
const intersectingTriangle = {
|
||||||
|
type: "Polygon",
|
||||||
|
coordinates:[[
|
||||||
|
[-117.42187500000001,68.13885164925573],
|
||||||
|
[-35.859375,20.96143961409684],
|
||||||
|
[59.4140625,68.52823492039876],
|
||||||
|
[-117.42187500000001,68.13885164925573]
|
||||||
|
]]
|
||||||
|
};
|
||||||
|
|
||||||
|
const query = `
|
||||||
|
SELECT
|
||||||
|
ST_TRANSFORM(ST_SETSRID(ST_GeomFromGeoJSON(
|
||||||
|
'${JSON.stringify(notIntersectingLeftTriangle)}'
|
||||||
|
), 4326), 3857) AS the_geom_webmercator, 1 AS cartodb_id, 'notIntersectingLeftTriangle' AS name
|
||||||
|
UNION
|
||||||
|
SELECT
|
||||||
|
ST_TRANSFORM(ST_SETSRID(ST_GeomFromGeoJSON(
|
||||||
|
'${JSON.stringify(notIntersectingRightTriangle)}'
|
||||||
|
), 4326), 3857), 2, 'notIntersectingRightTriangle'
|
||||||
|
UNION
|
||||||
|
SELECT
|
||||||
|
ST_TRANSFORM(ST_SETSRID(ST_GeomFromGeoJSON(
|
||||||
|
'${JSON.stringify(intersectingTriangle)}'
|
||||||
|
), 4326), 3857), 3, 'intersectingTriangle'
|
||||||
|
`;
|
||||||
|
|
||||||
|
const mapConfig = {
|
||||||
|
version: '1.5.0',
|
||||||
|
layers: [
|
||||||
|
{
|
||||||
|
"type": "cartodb",
|
||||||
|
"options": {
|
||||||
|
"source": {
|
||||||
|
"id": "a0"
|
||||||
|
},
|
||||||
|
"cartocss": "#points { marker-width: 10; marker-fill: red; }",
|
||||||
|
"cartocss_version": "2.3.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
dataviews: {
|
||||||
|
val_formula: {
|
||||||
|
source: {
|
||||||
|
id: 'a0'
|
||||||
|
},
|
||||||
|
type: 'aggregation',
|
||||||
|
options: {
|
||||||
|
column: "name",
|
||||||
|
aggregation: "count",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
analyses: [
|
||||||
|
{
|
||||||
|
"id": "a0",
|
||||||
|
"type": "source",
|
||||||
|
"params": {
|
||||||
|
"query": query
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
this.testClient = new TestClient(mapConfig, 1234);
|
||||||
|
const params = {
|
||||||
|
bbox: '-77.34374999999999,45.82879925192134,17.578125,55.97379820507658'
|
||||||
|
};
|
||||||
|
this.testClient.getDataview('val_formula', params, function(err, dataview) {
|
||||||
|
assert.ifError(err);
|
||||||
|
assert.equal(dataview.categories.length, 1);
|
||||||
|
assert.equal(dataview.categories[0].category, 'intersectingTriangle');
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
@ -3,7 +3,8 @@
|
|||||||
var qs = require('querystring');
|
var qs = require('querystring');
|
||||||
var step = require('step');
|
var step = require('step');
|
||||||
var urlParser = require('url');
|
var urlParser = require('url');
|
||||||
|
var PSQL = require('cartodb-psql');
|
||||||
|
var _ = require('underscore');
|
||||||
var mapnik = require('windshaft').mapnik;
|
var mapnik = require('windshaft').mapnik;
|
||||||
|
|
||||||
var LayergroupToken = require('../../lib/cartodb/models/layergroup-token');
|
var LayergroupToken = require('../../lib/cartodb/models/layergroup-token');
|
||||||
@ -14,13 +15,21 @@ var helper = require('./test_helper');
|
|||||||
var CartodbWindshaft = require('../../lib/cartodb/server');
|
var CartodbWindshaft = require('../../lib/cartodb/server');
|
||||||
var serverOptions = require('../../lib/cartodb/server_options');
|
var serverOptions = require('../../lib/cartodb/server_options');
|
||||||
serverOptions.analysis.batch.inlineExecution = true;
|
serverOptions.analysis.batch.inlineExecution = true;
|
||||||
var server = new CartodbWindshaft(serverOptions);
|
|
||||||
|
const MAPNIK_SUPPORTED_FORMATS = {
|
||||||
|
'png': true,
|
||||||
|
'png32': true,
|
||||||
|
'grid.json': true,
|
||||||
|
'geojson': true,
|
||||||
|
'mvt': true
|
||||||
|
}
|
||||||
|
|
||||||
function TestClient(config, apiKey) {
|
function TestClient(config, apiKey) {
|
||||||
this.mapConfig = isMapConfig(config) ? config : null;
|
this.mapConfig = isMapConfig(config) ? config : null;
|
||||||
this.template = isTemplate(config) ? config : null;
|
this.template = isTemplate(config) ? config : null;
|
||||||
this.apiKey = apiKey;
|
this.apiKey = apiKey;
|
||||||
this.keysToDelete = {};
|
this.keysToDelete = {};
|
||||||
|
this.server = new CartodbWindshaft(serverOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = TestClient;
|
module.exports = TestClient;
|
||||||
@ -72,6 +81,34 @@ module.exports.CARTOCSS = {
|
|||||||
' line-width: 0.5;',
|
' line-width: 0.5;',
|
||||||
' line-opacity: 1;',
|
' line-opacity: 1;',
|
||||||
'}'
|
'}'
|
||||||
|
].join('\n'),
|
||||||
|
|
||||||
|
TORQUE: [
|
||||||
|
'Map {',
|
||||||
|
' -torque-frame-count: 256;',
|
||||||
|
' -torque-animation-duration: 30;',
|
||||||
|
' -torque-time-attribute: "cartodb_id";',
|
||||||
|
' -torque-aggregation-function: "count(1)";',
|
||||||
|
' -torque-resolution: 4;',
|
||||||
|
' -torque-data-aggregation: linear;',
|
||||||
|
'}',
|
||||||
|
'#layer {',
|
||||||
|
' marker-width: 7;',
|
||||||
|
' marker-fill: #FFB927;',
|
||||||
|
' marker-fill-opacity: 0.9;',
|
||||||
|
' marker-line-width: 1;',
|
||||||
|
' marker-line-color: #FFF;',
|
||||||
|
' marker-line-opacity: 1;',
|
||||||
|
' comp-op: lighter;',
|
||||||
|
'}',
|
||||||
|
'#layer[frame-offset=1] {',
|
||||||
|
' marker-width: 9;',
|
||||||
|
' marker-fill-opacity: 0.45;',
|
||||||
|
'}',
|
||||||
|
'#layer[frame-offset=2] {',
|
||||||
|
' marker-width: 11;',
|
||||||
|
' marker-fill-opacity: 0.225;',
|
||||||
|
'}'
|
||||||
].join('\n')
|
].join('\n')
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -97,7 +134,7 @@ TestClient.prototype.getWidget = function(widgetName, params, callback) {
|
|||||||
step(
|
step(
|
||||||
function createLayergroup() {
|
function createLayergroup() {
|
||||||
var next = this;
|
var next = this;
|
||||||
assert.response(server,
|
assert.response(self.server,
|
||||||
{
|
{
|
||||||
url: url,
|
url: url,
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
@ -156,7 +193,7 @@ TestClient.prototype.getWidget = function(widgetName, params, callback) {
|
|||||||
|
|
||||||
url = '/api/v1/map/' + layergroupId + '/0/widget/' + widgetName + '?' + qs.stringify(urlParams);
|
url = '/api/v1/map/' + layergroupId + '/0/widget/' + widgetName + '?' + qs.stringify(urlParams);
|
||||||
|
|
||||||
assert.response(server,
|
assert.response(self.server,
|
||||||
{
|
{
|
||||||
url: url,
|
url: url,
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
@ -208,7 +245,7 @@ TestClient.prototype.widgetSearch = function(widgetName, userQuery, params, call
|
|||||||
step(
|
step(
|
||||||
function createLayergroup() {
|
function createLayergroup() {
|
||||||
var next = this;
|
var next = this;
|
||||||
assert.response(server,
|
assert.response(self.server,
|
||||||
{
|
{
|
||||||
url: url,
|
url: url,
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
@ -265,7 +302,7 @@ TestClient.prototype.widgetSearch = function(widgetName, userQuery, params, call
|
|||||||
}
|
}
|
||||||
url = '/api/v1/map/' + layergroupId + '/0/widget/' + widgetName + '/search?' + qs.stringify(urlParams);
|
url = '/api/v1/map/' + layergroupId + '/0/widget/' + widgetName + '/search?' + qs.stringify(urlParams);
|
||||||
|
|
||||||
assert.response(server,
|
assert.response(self.server,
|
||||||
{
|
{
|
||||||
url: url,
|
url: url,
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
@ -332,7 +369,7 @@ TestClient.prototype.getDataview = function(dataviewName, params, callback) {
|
|||||||
step(
|
step(
|
||||||
function createLayergroup() {
|
function createLayergroup() {
|
||||||
var next = this;
|
var next = this;
|
||||||
assert.response(server,
|
assert.response(self.server,
|
||||||
{
|
{
|
||||||
url: url,
|
url: url,
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
@ -385,7 +422,7 @@ TestClient.prototype.getDataview = function(dataviewName, params, callback) {
|
|||||||
}
|
}
|
||||||
url = '/api/v1/map/' + layergroupId + '/dataview/' + dataviewName + '?' + qs.stringify(urlParams);
|
url = '/api/v1/map/' + layergroupId + '/dataview/' + dataviewName + '?' + qs.stringify(urlParams);
|
||||||
|
|
||||||
assert.response(server,
|
assert.response(self.server,
|
||||||
{
|
{
|
||||||
url: url,
|
url: url,
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
@ -404,12 +441,16 @@ TestClient.prototype.getDataview = function(dataviewName, params, callback) {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
function finish(err, dataview) {
|
function finish(err, dataview) {
|
||||||
|
if (err) {
|
||||||
|
return callback(err);
|
||||||
|
}
|
||||||
|
|
||||||
if (layergroupId) {
|
if (layergroupId) {
|
||||||
self.keysToDelete['map_cfg|' + LayergroupToken.parse(layergroupId).token] = 0;
|
self.keysToDelete['map_cfg|' + LayergroupToken.parse(layergroupId).token] = 0;
|
||||||
self.keysToDelete['user:localhost:mapviews:global'] = 5;
|
self.keysToDelete['user:localhost:mapviews:global'] = 5;
|
||||||
}
|
}
|
||||||
|
|
||||||
return callback(err, dataview);
|
return callback(null, dataview);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@ -446,7 +487,7 @@ TestClient.prototype.getFeatureAttributes = function(featureId, layerId, params,
|
|||||||
step(
|
step(
|
||||||
function createLayergroup() {
|
function createLayergroup() {
|
||||||
var next = this;
|
var next = this;
|
||||||
assert.response(server,
|
assert.response(self.server,
|
||||||
{
|
{
|
||||||
url: url,
|
url: url,
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
@ -485,7 +526,7 @@ TestClient.prototype.getFeatureAttributes = function(featureId, layerId, params,
|
|||||||
|
|
||||||
url = '/api/v1/map/' + layergroupId + '/' + layerId + '/attributes/' + featureId;
|
url = '/api/v1/map/' + layergroupId + '/' + layerId + '/attributes/' + featureId;
|
||||||
|
|
||||||
assert.response(server,
|
assert.response(self.server,
|
||||||
{
|
{
|
||||||
url: url,
|
url: url,
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
@ -529,6 +570,11 @@ TestClient.prototype.getTile = function(z, x, y, params, callback) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var layergroupId;
|
var layergroupId;
|
||||||
|
|
||||||
|
if (params.layergroupid) {
|
||||||
|
layergroupId = params.layergroupid
|
||||||
|
}
|
||||||
|
|
||||||
step(
|
step(
|
||||||
function createTemplate () {
|
function createTemplate () {
|
||||||
var next = this;
|
var next = this;
|
||||||
@ -543,7 +589,7 @@ TestClient.prototype.getTile = function(z, x, y, params, callback) {
|
|||||||
|
|
||||||
params.placeholders = params.placeholders || {};
|
params.placeholders = params.placeholders || {};
|
||||||
|
|
||||||
assert.response(server,
|
assert.response(self.server,
|
||||||
{
|
{
|
||||||
url: urlNamed + '?' + qs.stringify({ api_key: self.apiKey }),
|
url: urlNamed + '?' + qs.stringify({ api_key: self.apiKey }),
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
@ -570,12 +616,16 @@ TestClient.prototype.getTile = function(z, x, y, params, callback) {
|
|||||||
function createLayergroup(err, templateId) {
|
function createLayergroup(err, templateId) {
|
||||||
var next = this;
|
var next = this;
|
||||||
|
|
||||||
|
if (layergroupId) {
|
||||||
|
return next(null, layergroupId);
|
||||||
|
}
|
||||||
|
|
||||||
var data = templateId ? params.placeholders : self.mapConfig
|
var data = templateId ? params.placeholders : self.mapConfig
|
||||||
var path = templateId ?
|
var path = templateId ?
|
||||||
urlNamed + '/' + templateId + '?' + qs.stringify({api_key: self.apiKey}) :
|
urlNamed + '/' + templateId + '?' + qs.stringify({api_key: self.apiKey}) :
|
||||||
url;
|
url;
|
||||||
|
|
||||||
assert.response(server,
|
assert.response(self.server,
|
||||||
{
|
{
|
||||||
url: path,
|
url: path,
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
@ -616,6 +666,10 @@ TestClient.prototype.getTile = function(z, x, y, params, callback) {
|
|||||||
|
|
||||||
var format = params.format || 'png';
|
var format = params.format || 'png';
|
||||||
|
|
||||||
|
if (layers === undefined && !MAPNIK_SUPPORTED_FORMATS[format]) {
|
||||||
|
throw new Error(`Missing layer filter while fetching ${format} tile, review params argument`);
|
||||||
|
}
|
||||||
|
|
||||||
url += [z,x,y].join('/');
|
url += [z,x,y].join('/');
|
||||||
url += '.' + format;
|
url += '.' + format;
|
||||||
|
|
||||||
@ -631,32 +685,30 @@ TestClient.prototype.getTile = function(z, x, y, params, callback) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var expectedResponse = {
|
var expectedResponse = Object.assign({}, {
|
||||||
status: params.status || 200,
|
status: 200,
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json; charset=utf-8'
|
'Content-Type': 'image/png'
|
||||||
}
|
}
|
||||||
};
|
}, params.response);
|
||||||
|
|
||||||
|
|
||||||
var isPng = format.match(/png$/);
|
var isPng = format.match(/png$/);
|
||||||
|
|
||||||
if (isPng) {
|
if (isPng) {
|
||||||
request.encoding = 'binary';
|
request.encoding = 'binary';
|
||||||
expectedResponse.headers['Content-Type'] = 'image/png';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var isMvt = format.match(/mvt$/);
|
var isMvt = format.match(/mvt$/);
|
||||||
|
|
||||||
if (isMvt) {
|
if (isMvt) {
|
||||||
request.encoding = 'binary';
|
request.encoding = 'binary';
|
||||||
|
|
||||||
if (expectedResponse.status === 200) {
|
if (expectedResponse.status === 200) {
|
||||||
expectedResponse.headers['Content-Type'] = 'application/x-protobuf';
|
expectedResponse.headers['Content-Type'] = 'application/x-protobuf';
|
||||||
} else if (expectedResponse.status === 204) {
|
|
||||||
expectedResponse.headers['Content-Type'] = undefined;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
var isGeojson = format.match(/geojson$/);
|
var isGeojson = format.match(/geojson$/);
|
||||||
|
|
||||||
if (isGeojson) {
|
if (isGeojson) {
|
||||||
@ -671,29 +723,38 @@ TestClient.prototype.getTile = function(z, x, y, params, callback) {
|
|||||||
expectedResponse.headers['Content-Type'] = 'application/json; charset=utf-8';
|
expectedResponse.headers['Content-Type'] = 'application/json; charset=utf-8';
|
||||||
}
|
}
|
||||||
|
|
||||||
assert.response(server, request, expectedResponse, function(res, err) {
|
if (params.contentType) {
|
||||||
|
expectedResponse.headers['Content-Type'] = 'application/json; charset=utf-8';
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.response(self.server, request, expectedResponse, function(res, err) {
|
||||||
assert.ifError(err);
|
assert.ifError(err);
|
||||||
var obj;
|
|
||||||
|
|
||||||
if (isPng) {
|
var body;
|
||||||
obj = mapnik.Image.fromBytes(new Buffer(res.body, 'binary'));
|
switch (res.headers['content-type']) {
|
||||||
}
|
case 'image/png':
|
||||||
else if (isMvt) {
|
body = mapnik.Image.fromBytes(new Buffer(res.body, 'binary'));
|
||||||
if (res.body) {
|
break;
|
||||||
obj = new mapnik.VectorTile(z, x, y);
|
case 'application/x-protobuf':
|
||||||
obj.setDataSync(new Buffer(res.body, 'binary'));
|
body = new mapnik.VectorTile(z, x, y);
|
||||||
}
|
body.setDataSync(new Buffer(res.body, 'binary'));
|
||||||
}
|
break;
|
||||||
else {
|
case 'application/json; charset=utf-8':
|
||||||
obj = JSON.parse(res.body);
|
body = JSON.parse(res.body);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
body = res.body
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
next(null, res, obj);
|
next(null, res, body);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
function finish(err, res, image) {
|
function finish(err, res, image) {
|
||||||
|
if (layergroupId) {
|
||||||
self.keysToDelete['map_cfg|' + LayergroupToken.parse(layergroupId).token] = 0;
|
self.keysToDelete['map_cfg|' + LayergroupToken.parse(layergroupId).token] = 0;
|
||||||
self.keysToDelete['user:localhost:mapviews:global'] = 5;
|
self.keysToDelete['user:localhost:mapviews:global'] = 5;
|
||||||
|
}
|
||||||
return callback(err, res, image);
|
return callback(err, res, image);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
@ -718,7 +779,7 @@ TestClient.prototype.getLayergroup = function(expectedResponse, callback) {
|
|||||||
url += '?' + qs.stringify({api_key: this.apiKey});
|
url += '?' + qs.stringify({api_key: this.apiKey});
|
||||||
}
|
}
|
||||||
|
|
||||||
assert.response(server,
|
assert.response(self.server,
|
||||||
{
|
{
|
||||||
url: url,
|
url: url,
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
@ -743,7 +804,111 @@ TestClient.prototype.getLayergroup = function(expectedResponse, callback) {
|
|||||||
return callback(err);
|
return callback(err);
|
||||||
}
|
}
|
||||||
|
|
||||||
return callback(null, JSON.parse(res.body));
|
return callback(null, parsedBody);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
TestClient.prototype.getStaticCenter = function (params, callback) {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
let { layergroupid, z, lat, lng, width, height, format } = params
|
||||||
|
|
||||||
|
var url = `/api/v1/map/`;
|
||||||
|
|
||||||
|
if (this.apiKey) {
|
||||||
|
url += '?' + qs.stringify({api_key: this.apiKey});
|
||||||
|
}
|
||||||
|
|
||||||
|
step(
|
||||||
|
function createLayergroup() {
|
||||||
|
var next = this;
|
||||||
|
|
||||||
|
if (layergroupid) {
|
||||||
|
return next(null, layergroupid);
|
||||||
|
}
|
||||||
|
|
||||||
|
var data = self.mapConfig
|
||||||
|
var path = url;
|
||||||
|
|
||||||
|
assert.response(self.server,
|
||||||
|
{
|
||||||
|
url: path,
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
host: 'localhost',
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
data: JSON.stringify(data)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
status: 200,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json; charset=utf-8'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
function(res, err) {
|
||||||
|
if (err) {
|
||||||
|
return next(err);
|
||||||
|
}
|
||||||
|
return next(null, JSON.parse(res.body).layergroupid);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
},
|
||||||
|
function getStaticResult(err, _layergroupid) {
|
||||||
|
assert.ifError(err);
|
||||||
|
|
||||||
|
var next = this;
|
||||||
|
|
||||||
|
layergroupid = _layergroupid;
|
||||||
|
|
||||||
|
url = `/api/v1/map/static/center/${layergroupid}/${z}/${lat}/${lng}/${width}/${height}.${format}`
|
||||||
|
|
||||||
|
if (self.apiKey) {
|
||||||
|
url += '?' + qs.stringify({api_key: self.apiKey});
|
||||||
|
}
|
||||||
|
|
||||||
|
var request = {
|
||||||
|
url: url,
|
||||||
|
encoding: 'binary',
|
||||||
|
method: 'GET',
|
||||||
|
headers: {
|
||||||
|
host: 'localhost'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var expectedResponse = Object.assign({}, {
|
||||||
|
status: 200,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'image/png'
|
||||||
|
}
|
||||||
|
}, params.response);
|
||||||
|
|
||||||
|
assert.response(self.server, request, expectedResponse, function(res, err) {
|
||||||
|
assert.ifError(err);
|
||||||
|
|
||||||
|
var body;
|
||||||
|
switch (res.headers['content-type']) {
|
||||||
|
case 'image/png':
|
||||||
|
body = mapnik.Image.fromBytes(new Buffer(res.body, 'binary'));
|
||||||
|
break;
|
||||||
|
case 'application/json; charset=utf-8':
|
||||||
|
body = JSON.parse(res.body);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
body = res.body
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
next(null, res, body);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
function finish(err, res, image) {
|
||||||
|
if (layergroupid) {
|
||||||
|
self.keysToDelete['map_cfg|' + LayergroupToken.parse(layergroupid).token] = 0;
|
||||||
|
self.keysToDelete['user:localhost:mapviews:global'] = 5;
|
||||||
|
}
|
||||||
|
return callback(err, res, image);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@ -762,7 +927,7 @@ TestClient.prototype.getNodeStatus = function(nodeName, callback) {
|
|||||||
step(
|
step(
|
||||||
function createLayergroup() {
|
function createLayergroup() {
|
||||||
var next = this;
|
var next = this;
|
||||||
assert.response(server,
|
assert.response(self.server,
|
||||||
{
|
{
|
||||||
url: url,
|
url: url,
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
@ -823,7 +988,7 @@ TestClient.prototype.getNodeStatus = function(nodeName, callback) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
assert.response(server, request, expectedResponse, function(res, err) {
|
assert.response(self.server, request, expectedResponse, function(res, err) {
|
||||||
assert.ifError(err);
|
assert.ifError(err);
|
||||||
next(null, res, JSON.parse(res.body));
|
next(null, res, JSON.parse(res.body));
|
||||||
});
|
});
|
||||||
@ -836,11 +1001,119 @@ TestClient.prototype.getNodeStatus = function(nodeName, callback) {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
TestClient.prototype.getAttributes = function(params, callback) {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
if (!Number.isFinite(params.featureId)) {
|
||||||
|
throw new Error('featureId param must be a number')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Number.isFinite(params.layer)) {
|
||||||
|
throw new Error('layer param must be a number')
|
||||||
|
}
|
||||||
|
|
||||||
|
var url = '/api/v1/map';
|
||||||
|
|
||||||
|
if (this.apiKey) {
|
||||||
|
url += '?' + qs.stringify({ api_key: this.apiKey });
|
||||||
|
}
|
||||||
|
|
||||||
|
var layergroupid;
|
||||||
|
|
||||||
|
if (params.layergroupid) {
|
||||||
|
layergroupid = params.layergroupid
|
||||||
|
}
|
||||||
|
|
||||||
|
step(
|
||||||
|
function createLayergroup() {
|
||||||
|
var next = this;
|
||||||
|
|
||||||
|
if (layergroupid) {
|
||||||
|
return next(null, layergroupid);
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.response(self.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);
|
||||||
|
|
||||||
|
return next(null, parsedBody.layergroupid);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
},
|
||||||
|
function getAttributes(err, _layergroupid) {
|
||||||
|
assert.ifError(err);
|
||||||
|
|
||||||
|
var next = this;
|
||||||
|
|
||||||
|
layergroupid = _layergroupid;
|
||||||
|
|
||||||
|
url = `/api/v1/map/${layergroupid}/${params.layer}/attributes/${params.featureId}`;
|
||||||
|
|
||||||
|
if (self.apiKey) {
|
||||||
|
url += '?' + qs.stringify({api_key: self.apiKey});
|
||||||
|
}
|
||||||
|
|
||||||
|
var request = {
|
||||||
|
url: url,
|
||||||
|
method: 'GET',
|
||||||
|
headers: {
|
||||||
|
host: 'localhost'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var expectedResponse = params.response || {
|
||||||
|
status: 200,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json; charset=utf-8'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
assert.response(self.server, request, expectedResponse, function (res, err) {
|
||||||
|
assert.ifError(err);
|
||||||
|
|
||||||
|
var attributes = JSON.parse(res.body);
|
||||||
|
|
||||||
|
next(null, res, attributes);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
function finish(err, res, attributes) {
|
||||||
|
if (layergroupid) {
|
||||||
|
self.keysToDelete['map_cfg|' + LayergroupToken.parse(layergroupid).token] = 0;
|
||||||
|
self.keysToDelete['user:localhost:mapviews:global'] = 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
return callback(err, res, attributes);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
TestClient.prototype.drain = function(callback) {
|
TestClient.prototype.drain = function(callback) {
|
||||||
helper.deleteRedisKeys(this.keysToDelete, callback);
|
helper.deleteRedisKeys(this.keysToDelete, callback);
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports.getStaticMap = function getStaticMap(templateName, params, callback) {
|
module.exports.getStaticMap = function getStaticMap(templateName, params, callback) {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
self.server = new CartodbWindshaft(serverOptions);
|
||||||
|
|
||||||
if (!callback) {
|
if (!callback) {
|
||||||
callback = params;
|
callback = params;
|
||||||
params = null;
|
params = null;
|
||||||
@ -871,9 +1144,56 @@ module.exports.getStaticMap = function getStaticMap(templateName, params, callba
|
|||||||
// this could be removed once named maps are invalidated, otherwise you hits the cache
|
// this could be removed once named maps are invalidated, otherwise you hits the cache
|
||||||
var server = new CartodbWindshaft(serverOptions);
|
var server = new CartodbWindshaft(serverOptions);
|
||||||
|
|
||||||
assert.response(server, requestOptions, expectedResponse, function (res, err) {
|
assert.response(self.server, requestOptions, expectedResponse, function (res, err) {
|
||||||
helper.deleteRedisKeys({'user:localhost:mapviews:global': 5}, function() {
|
helper.deleteRedisKeys({'user:localhost:mapviews:global': 5}, function() {
|
||||||
return callback(err, mapnik.Image.fromBytes(new Buffer(res.body, 'binary')));
|
return callback(err, mapnik.Image.fromBytes(new Buffer(res.body, 'binary')));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
TestClient.prototype.setUserRenderTimeoutLimit = function (user, userTimeoutLimit, callback) {
|
||||||
|
const userTimeoutLimitsKey = `limits:timeout:${user}`;
|
||||||
|
const params = [
|
||||||
|
userTimeoutLimitsKey,
|
||||||
|
'render', userTimeoutLimit,
|
||||||
|
'render_public', userTimeoutLimit
|
||||||
|
];
|
||||||
|
|
||||||
|
this.keysToDelete[userTimeoutLimitsKey] = 5;
|
||||||
|
|
||||||
|
helper.configureMetadata('hmset', params, callback);
|
||||||
|
};
|
||||||
|
|
||||||
|
TestClient.prototype.setUserDatabaseTimeoutLimit = function (timeoutLimit, callback) {
|
||||||
|
const dbname = _.template(global.environment.postgres_auth_user, { user_id: 1 }) + '_db';
|
||||||
|
const dbuser = _.template(global.environment.postgres_auth_user, { user_id: 1 })
|
||||||
|
const pass = _.template(global.environment.postgres_auth_pass, { user_id: 1 })
|
||||||
|
const publicuser = global.environment.postgres.user;
|
||||||
|
|
||||||
|
// we need to guarantee all new connections have the new settings
|
||||||
|
helper.cleanPGPoolConnections()
|
||||||
|
|
||||||
|
const psql = new PSQL({
|
||||||
|
user: 'postgres',
|
||||||
|
dbname: dbname,
|
||||||
|
host: global.environment.postgres.host,
|
||||||
|
port: global.environment.postgres.port
|
||||||
|
});
|
||||||
|
|
||||||
|
step(
|
||||||
|
function configureTimeouts () {
|
||||||
|
const timeoutSQLs = [
|
||||||
|
`ALTER ROLE "${publicuser}" SET STATEMENT_TIMEOUT TO ${timeoutLimit}`,
|
||||||
|
`ALTER ROLE "${dbuser}" SET STATEMENT_TIMEOUT TO ${timeoutLimit}`,
|
||||||
|
`ALTER DATABASE "${dbname}" SET STATEMENT_TIMEOUT TO ${timeoutLimit}`
|
||||||
|
];
|
||||||
|
|
||||||
|
const group = this.group();
|
||||||
|
|
||||||
|
timeoutSQLs.forEach(sql => psql.query(sql, group()));
|
||||||
|
},
|
||||||
|
callback
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
@ -14,6 +14,7 @@ var lzmaWorker = new LZMA();
|
|||||||
var redis = require('redis');
|
var redis = require('redis');
|
||||||
var nock = require('nock');
|
var nock = require('nock');
|
||||||
var log4js = require('log4js');
|
var log4js = require('log4js');
|
||||||
|
var pg = require('pg');
|
||||||
|
|
||||||
// set environment specific variables
|
// set environment specific variables
|
||||||
global.environment = require(__dirname + '/../../config/environments/test');
|
global.environment = require(__dirname + '/../../config/environments/test');
|
||||||
@ -127,6 +128,11 @@ afterEach(function(done) {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function cleanPGPoolConnections () {
|
||||||
|
// TODO: this method will be replaced by psql.end
|
||||||
|
pg.end();
|
||||||
|
}
|
||||||
|
|
||||||
function deleteRedisKeys(keysToDelete, callback) {
|
function deleteRedisKeys(keysToDelete, callback) {
|
||||||
|
|
||||||
if (Object.keys(keysToDelete).length === 0) {
|
if (Object.keys(keysToDelete).length === 0) {
|
||||||
@ -166,12 +172,30 @@ function rmdirRecursiveSync(dirname) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function configureMetadata(action, params, callback) {
|
||||||
|
redisClient.SELECT(5, function (err) {
|
||||||
|
if (err) {
|
||||||
|
return callback(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
redisClient[action](params, function (err) {
|
||||||
|
if (err) {
|
||||||
|
return callback(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
return callback();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
deleteRedisKeys: deleteRedisKeys,
|
deleteRedisKeys: deleteRedisKeys,
|
||||||
lzma_compress_to_base64: lzma_compress_to_base64,
|
lzma_compress_to_base64: lzma_compress_to_base64,
|
||||||
checkNoCache: checkNoCache,
|
checkNoCache: checkNoCache,
|
||||||
checkSurrogateKey: checkSurrogateKey,
|
checkSurrogateKey: checkSurrogateKey,
|
||||||
checkCache: checkCache,
|
checkCache: checkCache,
|
||||||
rmdirRecursiveSync: rmdirRecursiveSync
|
rmdirRecursiveSync: rmdirRecursiveSync,
|
||||||
|
configureMetadata,
|
||||||
|
cleanPGPoolConnections
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -6,10 +6,13 @@ var LayergroupController = require('../../../../lib/cartodb/controllers/layergro
|
|||||||
|
|
||||||
describe('tile stats', function() {
|
describe('tile stats', function() {
|
||||||
|
|
||||||
after(function() {
|
beforeEach(function () {
|
||||||
global.statsClient = null;
|
this.statsClient = global.statsClient;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
afterEach(function() {
|
||||||
|
global.statsClient = this.statsClient;
|
||||||
|
});
|
||||||
|
|
||||||
it('finalizeGetTileOrGrid does not call statsClient when format is not supported', function() {
|
it('finalizeGetTileOrGrid does not call statsClient when format is not supported', function() {
|
||||||
var expectedCalls = 2, // it will call increment once for the general error
|
var expectedCalls = 2, // it will call increment once for the general error
|
||||||
|
Loading…
Reference in New Issue
Block a user