Compare commits
62 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fe7a2451ef | ||
|
|
a696bdc723 | ||
|
|
a98c884e1a | ||
|
|
431ca9c56f | ||
|
|
b56d2ec30b | ||
|
|
90ded34af7 | ||
|
|
7fed91900d | ||
|
|
0a57e86cb8 | ||
|
|
9034508244 | ||
|
|
b2b68ffd5c | ||
|
|
52da3bfa55 | ||
|
|
35b9448e9a | ||
|
|
9959e009eb | ||
|
|
106b9a64b2 | ||
|
|
42d05f29ee | ||
|
|
fc3a959da1 | ||
|
|
6766b76545 | ||
|
|
e30b883906 | ||
|
|
70b4d5b7fd | ||
|
|
0fffafa1db | ||
|
|
21b8655f85 | ||
|
|
c8286233be | ||
|
|
b67f6053e8 | ||
|
|
967dca9578 | ||
|
|
468f641af8 | ||
|
|
6d2934b30b | ||
|
|
7018af18b6 | ||
|
|
01027b73da | ||
|
|
af42fba53b | ||
|
|
3e12bfe27a | ||
|
|
13764e18ce | ||
|
|
a6daca9628 | ||
|
|
6f7cb75256 | ||
|
|
6bfedef7eb | ||
|
|
77cb3dbbdc | ||
|
|
fe5c76d65b | ||
|
|
29a6658e3d | ||
|
|
2772fc62d2 | ||
|
|
0d4ac64f00 | ||
|
|
47af013157 | ||
|
|
35d4fb4d27 | ||
|
|
42e2f9e4b1 | ||
|
|
d3bcf6f80d | ||
|
|
eeea51e10d | ||
|
|
9337cd948c | ||
|
|
527e005952 | ||
|
|
1ff0954390 | ||
|
|
e82d688a18 | ||
|
|
95a6ad3b86 | ||
|
|
d01787842f | ||
|
|
c86f92f8eb | ||
|
|
003227fb29 | ||
|
|
869408b7b7 | ||
|
|
dc844f8131 | ||
|
|
71e9e62db0 | ||
|
|
6ff3b33cde | ||
|
|
32eeb57fce | ||
|
|
8bc38a375a | ||
|
|
c1fac13d6b | ||
|
|
6374d2e4b6 | ||
|
|
9c34428984 | ||
|
|
1d66e49910 |
14
NEWS.md
14
NEWS.md
@@ -1,5 +1,19 @@
|
||||
# Changelog
|
||||
|
||||
## 3.10.0
|
||||
Released 2017-08-03
|
||||
|
||||
Announcements:
|
||||
- Improve time-series dataview, now supports date aggregations (e.g: daily, weekly, monthly, etc.) and timezones (UTC by default) #698.
|
||||
- Support special numeric values (±Infinity, NaN) for json responses #706
|
||||
|
||||
|
||||
## 3.9.8
|
||||
Released 2017-07-21
|
||||
|
||||
- Upgrades windshaft to [3.2.2](https://github.com/CartoDB/windshaft/releases/tag/3.2.2).
|
||||
|
||||
|
||||
## 3.9.7
|
||||
Released 2017-07-20
|
||||
|
||||
|
||||
@@ -94,7 +94,7 @@ function getQueryRewriteData(mapConfig, dataviewDefinition, params) {
|
||||
}
|
||||
|
||||
function getOverrideParams(params, ownFilter) {
|
||||
return _.reduce(_.pick(params, 'start', 'end', 'bins'),
|
||||
var overrideParams = _.reduce(_.pick(params, 'start', 'end', 'bins', 'offset'),
|
||||
function castNumbers(overrides, val, k) {
|
||||
if (!Number.isFinite(+val)) {
|
||||
throw new Error('Invalid number format for parameter \'' + k + '\'');
|
||||
@@ -104,6 +104,13 @@ function getOverrideParams(params, ownFilter) {
|
||||
},
|
||||
{ownFilter: ownFilter}
|
||||
);
|
||||
|
||||
// validation will be delegated to the proper dataview
|
||||
if (params.aggregation !== undefined) {
|
||||
overrideParams.aggregation = params.aggregation;
|
||||
}
|
||||
|
||||
return overrideParams;
|
||||
}
|
||||
|
||||
DataviewBackend.prototype.search = function (mapConfigProvider, user, params, callback) {
|
||||
|
||||
@@ -17,16 +17,8 @@ var REQUEST_QUERY_PARAMS_WHITELIST = [
|
||||
'zoom',
|
||||
'lon',
|
||||
'lat',
|
||||
// widgets & filters
|
||||
'filters', // json
|
||||
'own_filter', // 0, 1
|
||||
'bbox', // w,s,e,n
|
||||
'bins', // number
|
||||
'start', // number
|
||||
'end', // number
|
||||
'column_type', // string
|
||||
// widgets search
|
||||
'q'
|
||||
// analysis
|
||||
'filters' // json
|
||||
];
|
||||
|
||||
function BaseController(authApi, pgConnection) {
|
||||
|
||||
@@ -79,19 +79,51 @@ LayergroupController.prototype.register = function(app) {
|
||||
|
||||
// Undocumented/non-supported API endpoint methods.
|
||||
// Use at your own peril.
|
||||
app.get(app.base_url_mapconfig +
|
||||
'/:token/dataview/:dataviewName', cors(), userMiddleware,
|
||||
this.dataview.bind(this));
|
||||
app.get(app.base_url_mapconfig +
|
||||
'/:token/:layer/widget/:dataviewName', cors(), userMiddleware,
|
||||
this.dataview.bind(this));
|
||||
|
||||
app.get(app.base_url_mapconfig +
|
||||
'/:token/dataview/:dataviewName/search', cors(), userMiddleware,
|
||||
this.dataviewSearch.bind(this));
|
||||
app.get(app.base_url_mapconfig +
|
||||
'/:token/:layer/widget/:dataviewName/search', cors(), userMiddleware,
|
||||
this.dataviewSearch.bind(this));
|
||||
var allowedDataviewQueryParams = [
|
||||
'filters', // json
|
||||
'own_filter', // 0, 1
|
||||
'bbox', // w,s,e,n
|
||||
'start', // number
|
||||
'end', // number
|
||||
'column_type', // string
|
||||
'bins', // number
|
||||
'aggregation', //string
|
||||
'offset', // number
|
||||
'q' // widgets search
|
||||
];
|
||||
|
||||
app.get(
|
||||
app.base_url_mapconfig + '/:token/dataview/:dataviewName',
|
||||
cors(),
|
||||
userMiddleware,
|
||||
allowQueryParams(allowedDataviewQueryParams),
|
||||
this.dataview.bind(this)
|
||||
);
|
||||
|
||||
app.get(
|
||||
app.base_url_mapconfig + '/:token/:layer/widget/:dataviewName',
|
||||
cors(),
|
||||
userMiddleware,
|
||||
allowQueryParams(allowedDataviewQueryParams),
|
||||
this.dataview.bind(this)
|
||||
);
|
||||
|
||||
app.get(
|
||||
app.base_url_mapconfig + '/:token/dataview/:dataviewName/search',
|
||||
cors(),
|
||||
userMiddleware,
|
||||
allowQueryParams(allowedDataviewQueryParams),
|
||||
this.dataviewSearch.bind(this)
|
||||
);
|
||||
|
||||
app.get(
|
||||
app.base_url_mapconfig + '/:token/:layer/widget/:dataviewName/search',
|
||||
cors(),
|
||||
userMiddleware,
|
||||
allowQueryParams(allowedDataviewQueryParams),
|
||||
this.dataviewSearch.bind(this)
|
||||
);
|
||||
|
||||
app.get(app.base_url_mapconfig +
|
||||
'/:token/analysis/node/:nodeId', cors(), userMiddleware,
|
||||
|
||||
@@ -8,6 +8,10 @@ module.exports = BaseDataview;
|
||||
BaseDataview.prototype.getResult = function(psql, override, callback) {
|
||||
var self = this;
|
||||
this.sql(psql, override, function(err, query) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
psql.query(query, function(err, result) {
|
||||
|
||||
if (err) {
|
||||
|
||||
@@ -7,6 +7,47 @@ dot.templateSettings.strip = false;
|
||||
|
||||
var columnCastTpl = dot.template("date_part('epoch', {{=it.column}})");
|
||||
|
||||
var dateIntervalQueryTpl = dot.template([
|
||||
'WITH',
|
||||
'dates AS (',
|
||||
' SELECT',
|
||||
' MAX({{=it.column}}::timestamp) AS _end,',
|
||||
' MIN({{=it.column}}::timestamp) AS _start',
|
||||
' FROM ({{=it.query}}) _cdb_source',
|
||||
'),',
|
||||
'interval_in_days AS (',
|
||||
' SELECT' ,
|
||||
' DATE_PART(\'day\', _end - _start) AS days',
|
||||
' FROM dates',
|
||||
'),',
|
||||
'interval_in_hours AS (',
|
||||
' SELECT',
|
||||
' days * 24 + DATE_PART(\'hour\', _end - _start) AS hours',
|
||||
' FROM interval_in_days, dates',
|
||||
'),',
|
||||
'interval_in_minutes AS (',
|
||||
' SELECT',
|
||||
' hours * 60 + DATE_PART(\'minute\', _end - _start) AS minutes',
|
||||
' FROM interval_in_hours, dates',
|
||||
'),',
|
||||
'interval_in_seconds AS (',
|
||||
' SELECT',
|
||||
' minutes * 60 + DATE_PART(\'second\', _end - _start) AS seconds',
|
||||
' FROM interval_in_minutes, dates',
|
||||
')',
|
||||
'SELECT',
|
||||
' ROUND(days / 365) AS year,',
|
||||
' ROUND(days / 90) AS quarter,',
|
||||
' ROUND(days / 30) AS month,',
|
||||
' ROUND(days / 7) AS week,',
|
||||
' days AS day,',
|
||||
' hours AS hour,',
|
||||
' minutes AS minute,',
|
||||
' seconds AS second',
|
||||
'FROM interval_in_days, interval_in_hours, interval_in_minutes, interval_in_seconds'
|
||||
].join('\n'));
|
||||
|
||||
var MAX_INTERVAL_VALUE = 366;
|
||||
var BIN_MIN_NUMBER = 6;
|
||||
var BIN_MAX_NUMBER = 48;
|
||||
|
||||
@@ -83,7 +124,7 @@ var overrideBinsQueryTpl = dot.template([
|
||||
var nullsQueryTpl = dot.template([
|
||||
'nulls AS (',
|
||||
' SELECT',
|
||||
' count(*) AS nulls_count',
|
||||
' count(*) AS nulls_count',
|
||||
' FROM ({{=it._query}}) _cdb_histogram_nulls',
|
||||
' WHERE {{=it._column}} IS NULL',
|
||||
')'
|
||||
@@ -132,16 +173,114 @@ var histogramQueryTpl = dot.template([
|
||||
'ORDER BY bin'
|
||||
].join('\n'));
|
||||
|
||||
var dateBasicsQueryTpl = dot.template([
|
||||
'basics AS (',
|
||||
' SELECT',
|
||||
' max(date_part(\'epoch\', {{=it._column}})) AS max_val,',
|
||||
' min(date_part(\'epoch\', {{=it._column}})) AS min_val,',
|
||||
' avg(date_part(\'epoch\', {{=it._column}})) AS avg_val,',
|
||||
' min(date_trunc(',
|
||||
' \'{{=it._aggregation}}\', {{=it._column}} AT TIME ZONE \'{{=it._offset}}\'',
|
||||
' )) AS start_date,',
|
||||
' max({{=it._column}} AT TIME ZONE \'{{=it._offset}}\') AS end_date,',
|
||||
' count(1) AS total_rows',
|
||||
' FROM ({{=it._query}}) _cdb_basics',
|
||||
')'
|
||||
].join(' \n'));
|
||||
|
||||
var dateOverrideBasicsQueryTpl = dot.template([
|
||||
'basics AS (',
|
||||
' SELECT',
|
||||
' max({{=it._end}}) AS max_val,',
|
||||
' min({{=it._start}}) AS min_val,',
|
||||
' avg(date_part(\'epoch\', {{=it._column}})) AS avg_val,',
|
||||
' min(',
|
||||
' date_trunc(',
|
||||
' \'{{=it._aggregation}}\',',
|
||||
' TO_TIMESTAMP({{=it._start}})::timestamp AT TIME ZONE \'{{=it._offset}}\'',
|
||||
' )',
|
||||
' ) AS start_date,',
|
||||
' max(',
|
||||
' TO_TIMESTAMP({{=it._end}})::timestamp AT TIME ZONE \'{{=it._offset}}\'',
|
||||
' ) AS end_date,',
|
||||
' count(1) AS total_rows',
|
||||
' FROM ({{=it._query}}) _cdb_basics',
|
||||
')'
|
||||
].join(' \n'));
|
||||
|
||||
var dateBinsQueryTpl = dot.template([
|
||||
'bins AS (',
|
||||
' SELECT',
|
||||
' bins_array,',
|
||||
' ARRAY_LENGTH(bins_array, 1) AS bins_number',
|
||||
' FROM (',
|
||||
' SELECT',
|
||||
' ARRAY(',
|
||||
' SELECT GENERATE_SERIES(',
|
||||
' start_date::timestamptz,',
|
||||
' end_date::timestamptz,',
|
||||
' {{?it._aggregation==="quarter"}}\'3 month\'{{??}}\'1 {{=it._aggregation}}\'{{?}}::interval',
|
||||
' )',
|
||||
' ) AS bins_array',
|
||||
' FROM basics',
|
||||
' ) _cdb_bins_array',
|
||||
')'
|
||||
].join('\n'));
|
||||
|
||||
var dateHistogramQueryTpl = dot.template([
|
||||
'SELECT',
|
||||
' (max_val - min_val) / cast(bins_number as float) AS bin_width,',
|
||||
' bins_number,',
|
||||
' nulls_count,',
|
||||
' CASE WHEN min_val = max_val',
|
||||
' THEN 0',
|
||||
' ELSE GREATEST(1, LEAST(',
|
||||
' WIDTH_BUCKET(',
|
||||
' {{=it._column}}::timestamp AT TIME ZONE \'{{=it._offset}}\',',
|
||||
' bins_array',
|
||||
' ),',
|
||||
' bins_number',
|
||||
' )) - 1',
|
||||
' END AS bin,',
|
||||
' min(',
|
||||
' date_part(',
|
||||
' \'epoch\', ',
|
||||
' date_trunc(',
|
||||
' \'{{=it._aggregation}}\', {{=it._column}}::timestamptz',
|
||||
' ) AT TIME ZONE \'{{=it._offset}}\'',
|
||||
' )',
|
||||
' )::numeric AS timestamp,',
|
||||
' date_part(\'epoch\', start_date)::numeric AS timestamp_start,',
|
||||
' min(date_part(\'epoch\', {{=it._column}}))::numeric AS min,',
|
||||
' max(date_part(\'epoch\', {{=it._column}}))::numeric AS max,',
|
||||
' avg(date_part(\'epoch\', {{=it._column}}))::numeric AS avg,',
|
||||
' count(*) AS freq',
|
||||
'FROM ({{=it._query}}) _cdb_histogram, basics, bins, nulls',
|
||||
'WHERE date_part(\'epoch\', {{=it._column}}) IS NOT NULL',
|
||||
'GROUP BY bin, bins_number, bin_width, nulls_count, avg_val, start_date',
|
||||
'ORDER BY bin'
|
||||
].join('\n'));
|
||||
|
||||
var TYPE = 'histogram';
|
||||
|
||||
/**
|
||||
{
|
||||
type: 'histogram',
|
||||
options: {
|
||||
column: 'name',
|
||||
bins: 10 // OPTIONAL
|
||||
}
|
||||
Numeric histogram:
|
||||
{
|
||||
type: 'histogram',
|
||||
options: {
|
||||
column: 'name', // column data type: numeric
|
||||
bins: 10 // OPTIONAL
|
||||
}
|
||||
}
|
||||
|
||||
Time series:
|
||||
{
|
||||
type: 'histogram',
|
||||
options: {
|
||||
column: 'date', // column data type: date
|
||||
aggregation: 'day' // OPTIONAL (if undefined then it'll be built as numeric)
|
||||
offset: -7200 // OPTIONAL (UTC offset in seconds)
|
||||
}
|
||||
}
|
||||
*/
|
||||
function Histogram(query, options, queries) {
|
||||
@@ -153,6 +292,8 @@ function Histogram(query, options, queries) {
|
||||
this.queries = queries;
|
||||
this.column = options.column;
|
||||
this.bins = options.bins;
|
||||
this.aggregation = options.aggregation;
|
||||
this.offset = options.offset;
|
||||
|
||||
this._columnType = null;
|
||||
}
|
||||
@@ -184,16 +325,18 @@ Histogram.prototype.sql = function(psql, override, callback) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var histogramSql = this._buildQuery(override);
|
||||
|
||||
return callback(null, histogramSql);
|
||||
this._buildQuery(psql, override, callback);
|
||||
};
|
||||
|
||||
Histogram.prototype._buildQuery = function (override) {
|
||||
Histogram.prototype._buildQuery = function (psql, override, callback) {
|
||||
var filteredQuery, basicsQuery, binsQuery;
|
||||
var _column = this.column;
|
||||
var _query = this.query;
|
||||
|
||||
if (this._columnType === 'date' && this.aggregation !== undefined) {
|
||||
return this._buildDateHistogramQuery(psql, override, callback);
|
||||
}
|
||||
|
||||
if (this._columnType === 'date') {
|
||||
_column = columnCastTpl({column: _column});
|
||||
}
|
||||
@@ -280,7 +423,7 @@ Histogram.prototype._buildQuery = function (override) {
|
||||
|
||||
debug(histogramSql);
|
||||
|
||||
return histogramSql;
|
||||
return callback(null, histogramSql);
|
||||
};
|
||||
|
||||
Histogram.prototype._shouldOverride = function (override) {
|
||||
@@ -291,6 +434,135 @@ Histogram.prototype._shouldOverrideBins = function (override) {
|
||||
return override && _.has(override, 'bins');
|
||||
};
|
||||
|
||||
var DATE_AGGREGATIONS = {
|
||||
'auto': true,
|
||||
'minute': true,
|
||||
'hour': true,
|
||||
'day': true,
|
||||
'week': true,
|
||||
'month': true,
|
||||
'quarter': true,
|
||||
'year': true
|
||||
};
|
||||
|
||||
Histogram.prototype._buildDateHistogramQuery = function (psql, override, callback) {
|
||||
var _column = this.column;
|
||||
var _query = this.query;
|
||||
var _aggregation = override && override.aggregation ? override.aggregation : this.aggregation;
|
||||
var _offset = override && Number.isFinite(override.offset) ? override.offset : this.offset;
|
||||
|
||||
if (!DATE_AGGREGATIONS.hasOwnProperty(_aggregation)) {
|
||||
return callback(new Error('Invalid aggregation value. Valid ones: ' +
|
||||
Object.keys(DATE_AGGREGATIONS).join(', ')
|
||||
));
|
||||
}
|
||||
|
||||
if (_aggregation === 'auto') {
|
||||
this.getAutomaticAggregation(psql, function (err, aggregation) {
|
||||
if (err || aggregation === 'none') {
|
||||
this.aggregation = 'day';
|
||||
} else {
|
||||
this.aggregation = aggregation;
|
||||
}
|
||||
override.aggregation = this.aggregation;
|
||||
this._buildDateHistogramQuery(psql, override, callback);
|
||||
}.bind(this));
|
||||
return null;
|
||||
}
|
||||
|
||||
var dateBasicsQuery;
|
||||
|
||||
if (override && _.has(override, 'start') && _.has(override, 'end')) {
|
||||
dateBasicsQuery = dateOverrideBasicsQueryTpl({
|
||||
_query: _query,
|
||||
_column: _column,
|
||||
_aggregation: _aggregation,
|
||||
_start: getBinStart(override),
|
||||
_end: getBinEnd(override),
|
||||
_offset: parseOffset(_offset, _aggregation)
|
||||
});
|
||||
} else {
|
||||
dateBasicsQuery = dateBasicsQueryTpl({
|
||||
_query: _query,
|
||||
_column: _column,
|
||||
_aggregation: _aggregation,
|
||||
_offset: parseOffset(_offset, _aggregation)
|
||||
});
|
||||
}
|
||||
|
||||
var dateBinsQuery = [
|
||||
dateBinsQueryTpl({
|
||||
_aggregation: _aggregation
|
||||
})
|
||||
].join(',\n');
|
||||
|
||||
var nullsQuery = nullsQueryTpl({
|
||||
_query: _query,
|
||||
_column: _column
|
||||
});
|
||||
|
||||
var dateHistogramQuery = dateHistogramQueryTpl({
|
||||
_query: _query,
|
||||
_column: _column,
|
||||
_aggregation: _aggregation,
|
||||
_offset: parseOffset(_offset, _aggregation)
|
||||
});
|
||||
|
||||
var histogramSql = [
|
||||
"WITH",
|
||||
[
|
||||
dateBasicsQuery,
|
||||
dateBinsQuery,
|
||||
nullsQuery
|
||||
].join(',\n'),
|
||||
dateHistogramQuery
|
||||
].join('\n');
|
||||
|
||||
debug(histogramSql);
|
||||
|
||||
return callback(null, histogramSql);
|
||||
};
|
||||
|
||||
Histogram.prototype.getAutomaticAggregation = function (psql, callback) {
|
||||
var dateIntervalQuery = dateIntervalQueryTpl({
|
||||
query: this.query,
|
||||
column: this.column
|
||||
});
|
||||
|
||||
debug(dateIntervalQuery);
|
||||
|
||||
psql.query(dateIntervalQuery, function (err, result) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
var aggegations = result.rows[0];
|
||||
var aggregation = Object.keys(aggegations)
|
||||
.map(function (key) {
|
||||
return {
|
||||
name: key,
|
||||
value: aggegations[key]
|
||||
};
|
||||
})
|
||||
.reduce(function (closer, current) {
|
||||
if (current.value > MAX_INTERVAL_VALUE) {
|
||||
return closer;
|
||||
}
|
||||
|
||||
var closerDiff = MAX_INTERVAL_VALUE - closer.value;
|
||||
var currentDiff = MAX_INTERVAL_VALUE - current.value;
|
||||
|
||||
if (Number.isFinite(current.value) && closerDiff > currentDiff) {
|
||||
return current;
|
||||
}
|
||||
|
||||
return closer;
|
||||
}, { name: 'none', value: -1 });
|
||||
|
||||
callback(null, aggregation.name);
|
||||
});
|
||||
};
|
||||
|
||||
Histogram.prototype.format = function(result, override) {
|
||||
override = override || {};
|
||||
var buckets = [];
|
||||
@@ -302,6 +574,9 @@ Histogram.prototype.format = function(result, override) {
|
||||
var infinities = 0;
|
||||
var nans = 0;
|
||||
var avg;
|
||||
var timestampStart;
|
||||
var aggregation;
|
||||
var offset;
|
||||
|
||||
if (result.rows.length) {
|
||||
var firstRow = result.rows[0];
|
||||
@@ -309,16 +584,34 @@ Histogram.prototype.format = function(result, override) {
|
||||
width = firstRow.bin_width || width;
|
||||
avg = firstRow.avg_val;
|
||||
nulls = firstRow.nulls_count;
|
||||
timestampStart = firstRow.timestamp_start;
|
||||
infinities = firstRow.infinities_count;
|
||||
nans = firstRow.nans_count;
|
||||
binsStart = override.hasOwnProperty('start') ? getBinStart(override) : firstRow.min;
|
||||
binsStart = populateBinStart(override, firstRow);
|
||||
|
||||
if (timestampStart) {
|
||||
aggregation = getAggregation(override, this.aggregation);
|
||||
offset = getOffset(override, this.offset);
|
||||
}
|
||||
|
||||
buckets = result.rows.map(function(row) {
|
||||
return _.omit(row, 'bins_number', 'bin_width', 'nulls_count', 'infinities_count', 'nans_count', 'avg_val');
|
||||
return _.omit(
|
||||
row,
|
||||
'bins_number',
|
||||
'bin_width',
|
||||
'nulls_count',
|
||||
'infinities_count',
|
||||
'nans_count',
|
||||
'avg_val',
|
||||
'timestamp_start'
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
aggregation: aggregation,
|
||||
offset: offset,
|
||||
timestamp_start: timestampStart,
|
||||
bin_width: width,
|
||||
bins_count: binsCount,
|
||||
bins_start: binsStart,
|
||||
@@ -330,6 +623,21 @@ Histogram.prototype.format = function(result, override) {
|
||||
};
|
||||
};
|
||||
|
||||
function getAggregation(override, aggregation) {
|
||||
return override && override.aggregation ? override.aggregation : aggregation;
|
||||
}
|
||||
|
||||
function getOffset(override, offset) {
|
||||
if (override && override.offset) {
|
||||
return override.offset;
|
||||
}
|
||||
if (offset) {
|
||||
return offset;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
function getBinStart(override) {
|
||||
if (override.hasOwnProperty('start') && override.hasOwnProperty('end')) {
|
||||
return Math.min(override.start, override.end);
|
||||
@@ -359,6 +667,32 @@ function getWidth(override) {
|
||||
return width;
|
||||
}
|
||||
|
||||
function parseOffset(offset, aggregation) {
|
||||
if (!offset) {
|
||||
return '0';
|
||||
}
|
||||
if (aggregation === 'hour' || aggregation === 'minute') {
|
||||
return '0';
|
||||
}
|
||||
|
||||
var offsetInHours = Math.ceil(offset / 3600);
|
||||
return '' + offsetInHours;
|
||||
}
|
||||
|
||||
function populateBinStart(override, firstRow) {
|
||||
var binStart;
|
||||
|
||||
if (firstRow.hasOwnProperty('timestamp')) {
|
||||
binStart = firstRow.timestamp;
|
||||
} else if (override.hasOwnProperty('start')) {
|
||||
binStart = getBinStart(override);
|
||||
} else {
|
||||
binStart = firstRow.min;
|
||||
}
|
||||
|
||||
return binStart;
|
||||
}
|
||||
|
||||
Histogram.prototype.getType = function() {
|
||||
return TYPE;
|
||||
};
|
||||
|
||||
@@ -310,6 +310,25 @@ function bootstrap(opts) {
|
||||
app.enable('jsonp callback');
|
||||
app.disable('x-powered-by');
|
||||
app.disable('etag');
|
||||
|
||||
// Fix: https://github.com/CartoDB/Windshaft-cartodb/issues/705
|
||||
// See: http://expressjs.com/en/4x/api.html#app.set
|
||||
app.set('json replacer', function (key, value) {
|
||||
if (value !== value) {
|
||||
return 'NaN';
|
||||
}
|
||||
|
||||
if (value === Infinity) {
|
||||
return 'Infinity';
|
||||
}
|
||||
|
||||
if (value === -Infinity) {
|
||||
return '-Infinity';
|
||||
}
|
||||
|
||||
return value;
|
||||
});
|
||||
|
||||
app.use(bodyParser.json());
|
||||
|
||||
app.use(function bootstrap$prepareRequestResponse(req, res, next) {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"private": true,
|
||||
"name": "windshaft-cartodb",
|
||||
"version": "3.9.7",
|
||||
"version": "3.10.0",
|
||||
"description": "A map tile server for CartoDB",
|
||||
"keywords": [
|
||||
"cartodb"
|
||||
@@ -40,13 +40,14 @@
|
||||
"step-profiler": "~0.3.0",
|
||||
"turbo-carto": "0.19.2",
|
||||
"underscore": "~1.6.0",
|
||||
"windshaft": "3.2.1",
|
||||
"windshaft": "3.2.2",
|
||||
"yargs": "~5.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"istanbul": "~0.4.3",
|
||||
"jshint": "~2.9.4",
|
||||
"mocha": "~3.4.1",
|
||||
"moment": "~2.18.1",
|
||||
"nock": "~2.11.0",
|
||||
"redis": "~0.12.1",
|
||||
"semver": "~1.1.4",
|
||||
|
||||
@@ -2,6 +2,16 @@ require('../../support/test_helper');
|
||||
|
||||
var assert = require('../../support/assert');
|
||||
var TestClient = require('../../support/test-client');
|
||||
var moment = require('moment');
|
||||
|
||||
function createMapConfig(layers, dataviews, analysis) {
|
||||
return {
|
||||
version: '1.5.0',
|
||||
layers: layers,
|
||||
dataviews: dataviews || {},
|
||||
analyses: analysis || []
|
||||
};
|
||||
}
|
||||
|
||||
function createMapConfig(layers, dataviews, analysis) {
|
||||
return {
|
||||
@@ -96,6 +106,598 @@ describe('histogram-dataview', function() {
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('histogram-dataview for date column type', function() {
|
||||
afterEach(function(done) {
|
||||
if (this.testClient) {
|
||||
this.testClient.drain(done);
|
||||
} else {
|
||||
done();
|
||||
}
|
||||
});
|
||||
|
||||
var mapConfig = createMapConfig(
|
||||
[
|
||||
{
|
||||
"type": "cartodb",
|
||||
"options": {
|
||||
"source": {
|
||||
"id": "datetime-histogram-source"
|
||||
},
|
||||
"cartocss": "#points { marker-width: 10; marker-fill: red; }",
|
||||
"cartocss_version": "2.3.0"
|
||||
}
|
||||
}
|
||||
],
|
||||
{
|
||||
datetime_histogram: {
|
||||
source: {
|
||||
id: 'datetime-histogram-source'
|
||||
},
|
||||
type: 'histogram',
|
||||
options: {
|
||||
column: 'd',
|
||||
aggregation: 'month',
|
||||
offset: -14400 // EDT Eastern Daylight Time (GMT-4) in seconds
|
||||
}
|
||||
},
|
||||
datetime_histogram_tz: {
|
||||
source: {
|
||||
id: 'datetime-histogram-source-tz'
|
||||
},
|
||||
type: 'histogram',
|
||||
options: {
|
||||
column: 'd',
|
||||
aggregation: 'month',
|
||||
offset: -14400 // EDT Eastern Daylight Time (GMT-4) in seconds
|
||||
}
|
||||
},
|
||||
datetime_histogram_automatic: {
|
||||
source: {
|
||||
id: 'datetime-histogram-source'
|
||||
},
|
||||
type: 'histogram',
|
||||
options: {
|
||||
column: 'd',
|
||||
aggregation: 'auto'
|
||||
}
|
||||
},
|
||||
date_histogram: {
|
||||
source: {
|
||||
id: 'date-histogram-source'
|
||||
},
|
||||
type: 'histogram',
|
||||
options: {
|
||||
column: 'd',
|
||||
aggregation: 'year'
|
||||
}
|
||||
},
|
||||
date_histogram_automatic: {
|
||||
source: {
|
||||
id: 'date-histogram-source'
|
||||
},
|
||||
type: 'histogram',
|
||||
options: {
|
||||
column: 'd',
|
||||
aggregation: 'auto'
|
||||
}
|
||||
},
|
||||
minute_histogram: {
|
||||
source: {
|
||||
id: 'minute-histogram-source'
|
||||
},
|
||||
type: 'histogram',
|
||||
options: {
|
||||
column: 'd',
|
||||
aggregation: 'minute'
|
||||
}
|
||||
}
|
||||
},
|
||||
[
|
||||
{
|
||||
"id": "datetime-histogram-source",
|
||||
"type": "source",
|
||||
"params": {
|
||||
"query": [
|
||||
"select null::geometry the_geom_webmercator, date AS d",
|
||||
"from generate_series(",
|
||||
"'2007-02-15 01:00:00'::timestamp, '2008-04-09 01:00:00'::timestamp, '1 day'::interval",
|
||||
") date"
|
||||
].join(' ')
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "datetime-histogram-source-tz",
|
||||
"type": "source",
|
||||
"params": {
|
||||
"query": [
|
||||
"select null::geometry the_geom_webmercator, date AS d",
|
||||
"from generate_series(",
|
||||
"'2007-02-15 01:00:00'::timestamptz, '2008-04-09 01:00:00'::timestamptz, '1 day'::interval",
|
||||
") date"
|
||||
].join(' ')
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "date-histogram-source",
|
||||
"type": "source",
|
||||
"params": {
|
||||
"query": [
|
||||
"select null::geometry the_geom_webmercator, date::date AS d",
|
||||
"from generate_series(",
|
||||
"'2007-02-15'::date, '2008-04-09'::date, '1 day'::interval",
|
||||
") date"
|
||||
].join(' ')
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "minute-histogram-source",
|
||||
"type": "source",
|
||||
"params": {
|
||||
"query": [
|
||||
"select null::geometry the_geom_webmercator, date AS d",
|
||||
"from generate_series(",
|
||||
"'2007-02-15 23:50:00'::timestamp, '2007-02-16 00:10:00'::timestamp, '1 minute'::interval",
|
||||
") date"
|
||||
].join(' ')
|
||||
}
|
||||
}
|
||||
]
|
||||
);
|
||||
|
||||
var dateHistogramsUseCases = [{
|
||||
desc: 'supporting timestamp with offset',
|
||||
dataviewId: 'datetime_histogram_tz'
|
||||
}, {
|
||||
desc: 'supporting timestamp without offset',
|
||||
dataviewId: 'datetime_histogram'
|
||||
}];
|
||||
|
||||
dateHistogramsUseCases.forEach(function (test) {
|
||||
it('should create a date histogram aggregated in months (EDT) ' + test.desc, function (done) {
|
||||
var OFFSET_EDT_IN_MINUTES = -4 * 60; // EDT Eastern Daylight Time (GMT-4) in minutes
|
||||
|
||||
this.testClient = new TestClient(mapConfig, 1234);
|
||||
|
||||
this.testClient.getDataview(test.dataviewId, {}, function(err, dataview) {
|
||||
assert.ok(!err, err);
|
||||
assert.equal(dataview.type, 'histogram');
|
||||
assert.ok(dataview.bin_width > 0, 'Unexpected bin width: ' + dataview.bin_width);
|
||||
assert.equal(dataview.bins.length, 15);
|
||||
|
||||
var initialTimestamp = '2007-02-01T00:00:00-04:00'; // EDT midnight
|
||||
var binsStartInMilliseconds = dataview.bins_start * 1000;
|
||||
var binsStartFormatted = moment.utc(binsStartInMilliseconds)
|
||||
.utcOffset(OFFSET_EDT_IN_MINUTES)
|
||||
.format();
|
||||
assert.equal(binsStartFormatted, initialTimestamp);
|
||||
|
||||
dataview.bins.forEach(function(bin, index) {
|
||||
var binTimestampExpected = moment.utc(initialTimestamp)
|
||||
.utcOffset(OFFSET_EDT_IN_MINUTES)
|
||||
.add(index, 'month')
|
||||
.format();
|
||||
var binsTimestampInMilliseconds = bin.timestamp * 1000;
|
||||
var binTimestampFormatted = moment.utc(binsTimestampInMilliseconds)
|
||||
.utcOffset(OFFSET_EDT_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();
|
||||
});
|
||||
});
|
||||
|
||||
it('should override aggregation in weeks ' + test.desc, function (done) {
|
||||
var params = {
|
||||
aggregation: 'week'
|
||||
};
|
||||
|
||||
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);
|
||||
assert.equal(dataview.bins.length, 61);
|
||||
dataview.bins.forEach(function (bin) {
|
||||
assert.ok(bin.min <= bin.max, 'bin min < bin max: ' + JSON.stringify(bin));
|
||||
});
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should override start and end ' + test.desc, function (done) {
|
||||
var params = {
|
||||
start: 1180659600, // 2007-06-01 01:00:00 UTC => '2007-05-31T21:00:00-04:00'
|
||||
end: 1193792400 // 2007-10-31 01:00:00 UTC
|
||||
};
|
||||
|
||||
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);
|
||||
assert.equal(dataview.bins.length, 6);
|
||||
dataview.bins.forEach(function (bin) {
|
||||
assert.ok(bin.min <= bin.max, 'bin min < bin max: ' + JSON.stringify(bin));
|
||||
});
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
it('should return same histogram ' + test.desc, function (done) {
|
||||
var params = {
|
||||
start: 1171501200, // 2007-02-15 01:00:00 = min(date_colum)
|
||||
end: 1207702800 // 2008-04-09 01:00:00 = max(date_colum)
|
||||
};
|
||||
|
||||
this.testClient = new TestClient(mapConfig, 1234);
|
||||
this.testClient.getDataview(test.dataviewId, {}, function (err, dataview) {
|
||||
assert.ok(!err, err);
|
||||
|
||||
this.testClient = new TestClient(mapConfig, 1234);
|
||||
this.testClient.getDataview(test.dataviewId, params, function (err, filteredDataview) {
|
||||
assert.ok(!err, err);
|
||||
|
||||
assert.deepEqual(dataview, filteredDataview);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
it('should aggregate histogram overriding default offset to CEST ' + test.desc, function (done) {
|
||||
var OFFSET_CEST_IN_SECONDS = 2 * 3600; // Central European Summer Time (Daylight Saving Time)
|
||||
var OFFSET_CEST_IN_MINUTES = 2 * 60; // Central European Summer Time (Daylight Saving Time)
|
||||
var params = {
|
||||
offset: OFFSET_CEST_IN_SECONDS
|
||||
};
|
||||
|
||||
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);
|
||||
assert.equal(dataview.bins.length, 15);
|
||||
|
||||
var initialTimestamp = '2007-02-01T00:00:00+02:00'; // CEST midnight
|
||||
var binsStartInMilliseconds = dataview.bins_start * 1000;
|
||||
var binsStartFormatted = moment.utc(binsStartInMilliseconds)
|
||||
.utcOffset(OFFSET_CEST_IN_MINUTES)
|
||||
.format();
|
||||
assert.equal(binsStartFormatted, initialTimestamp);
|
||||
|
||||
dataview.bins.forEach(function (bin, index) {
|
||||
var binTimestampExpected = moment.utc(initialTimestamp)
|
||||
.utcOffset(OFFSET_CEST_IN_MINUTES)
|
||||
.add(index, 'month')
|
||||
.format();
|
||||
var binsTimestampInMilliseconds = bin.timestamp * 1000;
|
||||
var binTimestampFormatted = moment.utc(binsTimestampInMilliseconds)
|
||||
.utcOffset(OFFSET_CEST_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();
|
||||
});
|
||||
});
|
||||
|
||||
it('should aggregate histogram overriding default offset to UTC/GMT ' + test.desc, function (done) {
|
||||
var OFFSET_UTC_IN_SECONDS = 0 * 3600; // UTC
|
||||
var OFFSET_UTC_IN_MINUTES = 0 * 60; // UTC
|
||||
var params = {
|
||||
offset: OFFSET_UTC_IN_SECONDS
|
||||
};
|
||||
|
||||
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);
|
||||
assert.equal(dataview.bins.length, 15);
|
||||
|
||||
var initialTimestamp = '2007-02-01T00:00:00Z'; // UTC midnight
|
||||
var binsStartInMilliseconds = dataview.bins_start * 1000;
|
||||
var binsStartFormatted = moment.utc(binsStartInMilliseconds)
|
||||
.utcOffset(OFFSET_UTC_IN_MINUTES)
|
||||
.format();
|
||||
assert.equal(binsStartFormatted, initialTimestamp);
|
||||
|
||||
dataview.bins.forEach(function (bin, index) {
|
||||
var binTimestampExpected = moment.utc(initialTimestamp)
|
||||
.utcOffset(OFFSET_UTC_IN_MINUTES)
|
||||
.add(index, 'month')
|
||||
.format();
|
||||
var binsTimestampInMilliseconds = bin.timestamp * 1000;
|
||||
var binTimestampFormatted = moment.utc(binsTimestampInMilliseconds)
|
||||
.utcOffset(OFFSET_UTC_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();
|
||||
});
|
||||
});
|
||||
|
||||
it('should aggregate histogram using "quarter" aggregation ' + test.desc, function (done) {
|
||||
var OFFSET_UTC_IN_SECONDS = 0 * 3600; // UTC
|
||||
var OFFSET_UTC_IN_MINUTES = 0 * 60; // UTC
|
||||
var params = {
|
||||
offset: OFFSET_UTC_IN_SECONDS,
|
||||
aggregation: 'quarter'
|
||||
};
|
||||
|
||||
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);
|
||||
assert.equal(dataview.bins.length, 6);
|
||||
|
||||
var initialTimestamp = '2007-01-01T00:00:00Z'; // UTC midnight
|
||||
var binsStartInMilliseconds = dataview.bins_start * 1000;
|
||||
var binsStartFormatted = moment.utc(binsStartInMilliseconds)
|
||||
.utcOffset(OFFSET_UTC_IN_MINUTES)
|
||||
.format();
|
||||
assert.equal(binsStartFormatted, initialTimestamp);
|
||||
|
||||
dataview.bins.forEach(function (bin, index) {
|
||||
var binTimestampExpected = moment.utc(initialTimestamp)
|
||||
.utcOffset(OFFSET_UTC_IN_MINUTES)
|
||||
.add(index * 3, 'month')
|
||||
.format();
|
||||
var binsTimestampInMilliseconds = bin.timestamp * 1000;
|
||||
var binTimestampFormatted = moment.utc(binsTimestampInMilliseconds)
|
||||
.utcOffset(OFFSET_UTC_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();
|
||||
});
|
||||
});
|
||||
|
||||
it('bins_count should be equal to bins length filtered by start and end ' + test.desc, function (done) {
|
||||
var OFFSET_UTC_IN_SECONDS = 0 * 3600; // UTC
|
||||
var params = {
|
||||
offset: OFFSET_UTC_IN_SECONDS,
|
||||
aggregation: 'quarter',
|
||||
start: 1167609600, // 2007-01-01T00:00:00Z, first bin start
|
||||
end: 1214870399 // 2008-06-30T23:59:59Z, last bin end
|
||||
};
|
||||
|
||||
this.testClient = new TestClient(mapConfig, 1234);
|
||||
this.testClient.getDataview(test.dataviewId, params, function (err, dataview) {
|
||||
assert.ifError(err);
|
||||
|
||||
assert.equal(dataview.type, 'histogram');
|
||||
assert.equal(dataview.bins.length, 6);
|
||||
assert.equal(dataview.bins_count, 6);
|
||||
assert.equal(dataview.bins_count, dataview.bins.length);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('bins_count should be greater than bins length filtered by start and end ' + test.desc, function (done) {
|
||||
var OFFSET_UTC_IN_SECONDS = 0 * 3600; // UTC
|
||||
var params = {
|
||||
offset: OFFSET_UTC_IN_SECONDS,
|
||||
aggregation: 'quarter',
|
||||
start: 1167609600, // 2007-01-01T00:00:00Z, first bin start
|
||||
end: 1214870400 // 2008-07-01T00:00:00Z, start the next bin to the last
|
||||
};
|
||||
|
||||
this.testClient = new TestClient(mapConfig, 1234);
|
||||
this.testClient.getDataview(test.dataviewId, params, function (err, dataview) {
|
||||
assert.ifError(err);
|
||||
|
||||
assert.equal(dataview.type, 'histogram');
|
||||
assert.equal(dataview.bins.length, 6);
|
||||
assert.equal(dataview.bins_count, 7);
|
||||
assert.ok(dataview.bins_count > dataview.bins.length);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should find the best aggregation (automatic mode) to build the histogram', function (done) {
|
||||
var params = {};
|
||||
this.testClient = new TestClient(mapConfig, 1234);
|
||||
this.testClient.getDataview('datetime_histogram_automatic', params, function (err, dataview) {
|
||||
assert.ifError(err);
|
||||
assert.equal(dataview.type, 'histogram');
|
||||
assert.equal(dataview.aggregation, 'week');
|
||||
assert.equal(dataview.bins.length, 61);
|
||||
assert.equal(dataview.bins_count, 61);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should work with dates', function (done) {
|
||||
var params = {};
|
||||
this.testClient = new TestClient(mapConfig, 1234);
|
||||
this.testClient.getDataview('date_histogram', params, function (err, dataview) {
|
||||
assert.ifError(err);
|
||||
assert.equal(dataview.type, 'histogram');
|
||||
assert.equal(dataview.aggregation, 'year');
|
||||
assert.equal(dataview.bins.length, 2);
|
||||
assert.equal(dataview.bins_count, 2);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
it('should find the best aggregation (automatic mode) to build the histogram with dates', function (done) {
|
||||
var params = {};
|
||||
this.testClient = new TestClient(mapConfig, 1234);
|
||||
this.testClient.getDataview('date_histogram_automatic', params, function (err, dataview) {
|
||||
assert.ifError(err);
|
||||
assert.equal(dataview.type, 'histogram');
|
||||
assert.equal(dataview.aggregation, 'week');
|
||||
assert.equal(dataview.bins.length, 61);
|
||||
assert.equal(dataview.bins_count, 61);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should not apply offset for a histogram aggregated by minutes', function (done) {
|
||||
var self = this;
|
||||
var params = {
|
||||
offset: '-3600'
|
||||
};
|
||||
|
||||
self.testClient = new TestClient(mapConfig, 1234);
|
||||
|
||||
self.testClient.getDataview('minute_histogram', {}, function (err, dataview) {
|
||||
assert.ifError(err);
|
||||
self.testClient.getDataview('minute_histogram', params, function (err, dataviewWithOffset) {
|
||||
assert.ifError(err);
|
||||
|
||||
assert.notEqual(dataview.offset, dataviewWithOffset.offset);
|
||||
dataview.offset = dataviewWithOffset.offset;
|
||||
assert.deepEqual(dataview, dataviewWithOffset);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should filter by "start" & "end" for a histogram aggregated by minutes', function (done) {
|
||||
var self = this;
|
||||
var paramsWithFilter = {
|
||||
start: 1171583400, // 2007-02-15 23:50:00 = min(date_colum)
|
||||
end: 1171584600 // 2007-02-16 00:10:00 = max(date_colum)
|
||||
};
|
||||
|
||||
var paramsWithOffset = {
|
||||
start: 1171583400, // 2007-02-15 23:50:00 = min(date_colum)
|
||||
end: 1171584600, // 2007-02-16 00:10:00 = max(date_colum)
|
||||
offset: '-3600'
|
||||
};
|
||||
|
||||
self.testClient = new TestClient(mapConfig, 1234);
|
||||
self.testClient.getDataview('minute_histogram', paramsWithFilter, function (err, dataview) {
|
||||
assert.ifError(err);
|
||||
|
||||
self.testClient.getDataview('minute_histogram', paramsWithFilter, function (err, filteredDataview) {
|
||||
assert.ifError(err);
|
||||
|
||||
assert.deepEqual(dataview, filteredDataview);
|
||||
|
||||
self.testClient.getDataview('minute_histogram', paramsWithOffset,
|
||||
function (err, filteredWithOffsetDataview) {
|
||||
assert.ifError(err);
|
||||
|
||||
assert.notEqual(filteredWithOffsetDataview.offset, filteredDataview.offset);
|
||||
filteredWithOffsetDataview.offset = filteredDataview.offset;
|
||||
assert.deepEqual(filteredWithOffsetDataview, filteredDataview);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
it('should return an histogram aggregated by days', function (done) {
|
||||
var self = this;
|
||||
var paramsWithDailyAgg = {
|
||||
aggregation: 'day',
|
||||
};
|
||||
|
||||
// data: from 2007-02-15 23:50:00 to 2007-02-16 00:10:00
|
||||
|
||||
var dataviewWithDailyAggFixture = {
|
||||
aggregation: 'day',
|
||||
bin_width: 600,
|
||||
bins_count: 2,
|
||||
bins_start: 1171497600,
|
||||
timestamp_start: 1171497600,
|
||||
offset: 0,
|
||||
nulls: 0,
|
||||
bins:
|
||||
[{
|
||||
bin: 0,
|
||||
timestamp: 1171497600,
|
||||
min: 1171583400,
|
||||
max: 1171583940,
|
||||
avg: 1171583670,
|
||||
freq: 10
|
||||
},
|
||||
{
|
||||
bin: 1,
|
||||
timestamp: 1171584000,
|
||||
min: 1171584000,
|
||||
max: 1171584600,
|
||||
avg: 1171584300,
|
||||
freq: 11
|
||||
}],
|
||||
type: 'histogram'
|
||||
};
|
||||
|
||||
self.testClient = new TestClient(mapConfig, 1234);
|
||||
self.testClient.getDataview('minute_histogram', paramsWithDailyAgg, function (err, dataview) {
|
||||
assert.ifError(err);
|
||||
|
||||
assert.deepEqual(dataview, dataviewWithDailyAggFixture);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should return a histogram aggregated by days with offset', function (done) {
|
||||
var self = this;
|
||||
|
||||
var paramsWithDailyAggAndOffset = {
|
||||
aggregation: 'day',
|
||||
offset: '-3600'
|
||||
};
|
||||
|
||||
// data (UTC): from 2007-02-15 23:50:00 to 2007-02-16 00:10:00
|
||||
|
||||
var dataviewWithDailyAggAndOffsetFixture = {
|
||||
aggregation: 'day',
|
||||
bin_width: 1200,
|
||||
bins_count: 1,
|
||||
bins_start: 1171501200,
|
||||
timestamp_start: 1171497600,
|
||||
nulls: 0,
|
||||
offset: -3600,
|
||||
bins:
|
||||
[{
|
||||
bin: 0,
|
||||
timestamp: 1171501200,
|
||||
min: 1171583400,
|
||||
max: 1171584600,
|
||||
avg: 1171584000,
|
||||
freq: 21
|
||||
}],
|
||||
type: 'histogram'
|
||||
};
|
||||
|
||||
self.testClient = new TestClient(mapConfig, 1234);
|
||||
self.testClient.getDataview('minute_histogram', paramsWithDailyAggAndOffset, function (err, dataview) {
|
||||
assert.ifError(err);
|
||||
|
||||
assert.deepEqual(dataview, dataviewWithDailyAggAndOffsetFixture);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -164,3 +766,124 @@ describe('histogram-dataview: special float valuer', function() {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('histogram-dates: aggregation input value', 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"
|
||||
}
|
||||
}
|
||||
],
|
||||
{
|
||||
agg_value_histogram: {
|
||||
source: {
|
||||
id: 'a0'
|
||||
},
|
||||
type: 'histogram',
|
||||
options: {
|
||||
column: 'd',
|
||||
aggregation: 'day'
|
||||
}
|
||||
},
|
||||
bad_agg_value_histogram: {
|
||||
source: {
|
||||
id: 'a0'
|
||||
},
|
||||
type: 'histogram',
|
||||
options: {
|
||||
column: 'd',
|
||||
aggregation: 'wadus'
|
||||
}
|
||||
}
|
||||
},
|
||||
[
|
||||
{
|
||||
id: 'a0',
|
||||
type: 'source',
|
||||
params: {
|
||||
query: [
|
||||
'select null::geometry the_geom_webmercator, date AS d',
|
||||
'from generate_series(',
|
||||
'\'2007-02-15 01:00:00\'::timestamp,',
|
||||
'\'2008-04-09 01:00:00\'::timestamp,',
|
||||
' \'1 day\'::interval',
|
||||
') date'
|
||||
].join(' ')
|
||||
}
|
||||
}
|
||||
]
|
||||
);
|
||||
|
||||
it('should fail when aggregation values is not valid while instantiating the map', function(done) {
|
||||
this.testClient = new TestClient(mapConfig, 1234);
|
||||
const override = {
|
||||
response: {
|
||||
status: 400
|
||||
}
|
||||
};
|
||||
|
||||
this.testClient.getDataview('bad_agg_value_histogram', override, function(err, dataviewError) {
|
||||
assert.ifError(err);
|
||||
|
||||
assert.deepEqual(dataviewError, {
|
||||
errors: [
|
||||
'Invalid aggregation value. Valid ones: auto, minute, hour, day, week, month, quarter, year'
|
||||
],
|
||||
errors_with_context: [{
|
||||
type: 'unknown',
|
||||
message: [
|
||||
'Invalid aggregation value. ',
|
||||
'Valid ones: auto, minute, hour, day, week, month, quarter, year'
|
||||
].join('')
|
||||
}]
|
||||
});
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should fail when aggregation values is not valid while fetching dataview result', function(done) {
|
||||
this.testClient = new TestClient(mapConfig, 1234);
|
||||
const override = {
|
||||
aggregation: 'wadus',
|
||||
response: {
|
||||
status: 400
|
||||
}
|
||||
};
|
||||
|
||||
this.testClient.getDataview('agg_value_histogram', override, function(err, dataviewError) {
|
||||
assert.ifError(err);
|
||||
|
||||
assert.deepEqual(dataviewError, {
|
||||
errors: [
|
||||
'Invalid aggregation value. Valid ones: auto, minute, hour, day, week, month, quarter, year'
|
||||
],
|
||||
errors_with_context: [{
|
||||
type: 'unknown',
|
||||
message: [
|
||||
'Invalid aggregation value. ',
|
||||
'Valid ones: auto, minute, hour, day, week, month, quarter, year'
|
||||
].join('')
|
||||
}]
|
||||
});
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
71
test/acceptance/special-numeric-values.js
Normal file
71
test/acceptance/special-numeric-values.js
Normal file
@@ -0,0 +1,71 @@
|
||||
require('../support/test_helper');
|
||||
|
||||
var assert = require('../support/assert');
|
||||
var TestClient = require('../support/test-client');
|
||||
|
||||
describe('special numeric values', function() {
|
||||
|
||||
afterEach(function(done) {
|
||||
if (this.testClient) {
|
||||
this.testClient.drain(done);
|
||||
} else {
|
||||
done();
|
||||
}
|
||||
});
|
||||
|
||||
var ATTRIBUTES_LAYER = 1;
|
||||
|
||||
function createMapConfig(sql, id, columns) {
|
||||
return {
|
||||
version: '1.6.0',
|
||||
layers: [
|
||||
{
|
||||
type: 'mapnik',
|
||||
options: {
|
||||
sql: "select 1 as id, 'SRID=4326;POINT(0 0)'::geometry as the_geom",
|
||||
cartocss: '#style { }',
|
||||
cartocss_version: '2.0.1'
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'mapnik',
|
||||
options: {
|
||||
sql: sql || "select 1 as i, 6 as n, 'SRID=4326;POINT(0 0)'::geometry as the_geom",
|
||||
attributes: {
|
||||
id: id || 'i',
|
||||
columns: columns || ['n']
|
||||
},
|
||||
cartocss: '#style { }',
|
||||
cartocss_version: '2.0.1'
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
|
||||
it('should retrieve special numeric values', function (done) {
|
||||
var featureId = 1;
|
||||
var sql = [
|
||||
'SELECT',
|
||||
' 1 as cartodb_id,',
|
||||
' null::geometry the_geom_webmercator,',
|
||||
' \'infinity\'::float as infinity,',
|
||||
' \'-infinity\'::float as _infinity,',
|
||||
' \'NaN\'::float as nan'
|
||||
].join('\n');
|
||||
var id = 'cartodb_id';
|
||||
var columns = ['infinity', '_infinity', 'nan'];
|
||||
|
||||
var mapConfig = createMapConfig(sql, id, columns);
|
||||
|
||||
this.testClient = new TestClient(mapConfig, 1234);
|
||||
this.testClient.getFeatureAttributes(featureId, ATTRIBUTES_LAYER, {}, function (err, attributes) {
|
||||
assert.ifError(err);
|
||||
assert.equal(attributes.infinity, 'Infinity');
|
||||
assert.equal(attributes._infinity, '-Infinity');
|
||||
assert.equal(attributes.nan, 'NaN');
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -374,7 +374,7 @@ TestClient.prototype.getDataview = function(dataviewName, params, callback) {
|
||||
own_filter: params.hasOwnProperty('own_filter') ? params.own_filter : 1
|
||||
};
|
||||
|
||||
['bbox', 'bins', 'start', 'end'].forEach(function(extraParam) {
|
||||
['bbox', 'bins', 'start', 'end', 'aggregation', 'offset'].forEach(function(extraParam) {
|
||||
if (params.hasOwnProperty(extraParam)) {
|
||||
urlParams[extraParam] = params[extraParam];
|
||||
}
|
||||
@@ -404,13 +404,115 @@ TestClient.prototype.getDataview = function(dataviewName, params, callback) {
|
||||
);
|
||||
},
|
||||
function finish(err, dataview) {
|
||||
self.keysToDelete['map_cfg|' + LayergroupToken.parse(layergroupId).token] = 0;
|
||||
self.keysToDelete['user:localhost:mapviews:global'] = 5;
|
||||
if (layergroupId) {
|
||||
self.keysToDelete['map_cfg|' + LayergroupToken.parse(layergroupId).token] = 0;
|
||||
self.keysToDelete['user:localhost:mapviews:global'] = 5;
|
||||
}
|
||||
|
||||
return callback(err, dataview);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
TestClient.prototype.getFeatureAttributes = function(featureId, layerId, params, callback) {
|
||||
var self = this;
|
||||
|
||||
if (!callback) {
|
||||
callback = params;
|
||||
params = {};
|
||||
}
|
||||
|
||||
var extraParams = {};
|
||||
if (this.apiKey) {
|
||||
extraParams.api_key = this.apiKey;
|
||||
}
|
||||
if (params && params.filters) {
|
||||
extraParams.filters = JSON.stringify(params.filters);
|
||||
}
|
||||
|
||||
var url = '/api/v1/map';
|
||||
if (Object.keys(extraParams).length > 0) {
|
||||
url += '?' + qs.stringify(extraParams);
|
||||
}
|
||||
|
||||
var expectedResponse = params.response || {
|
||||
status: 200,
|
||||
headers: {
|
||||
'Content-Type': 'application/json; charset=utf-8'
|
||||
}
|
||||
};
|
||||
|
||||
var layergroupId;
|
||||
step(
|
||||
function createLayergroup() {
|
||||
var next = this;
|
||||
assert.response(server,
|
||||
{
|
||||
url: url,
|
||||
method: 'POST',
|
||||
headers: {
|
||||
host: 'localhost',
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
data: JSON.stringify(self.mapConfig)
|
||||
},
|
||||
{
|
||||
status: 200,
|
||||
headers: {
|
||||
'Content-Type': 'application/json; charset=utf-8'
|
||||
}
|
||||
},
|
||||
function(res, err) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
var parsedBody = JSON.parse(res.body);
|
||||
|
||||
if (parsedBody.layergroupid) {
|
||||
self.keysToDelete['map_cfg|' + LayergroupToken.parse(parsedBody.layergroupid).token] = 0;
|
||||
self.keysToDelete['user:localhost:mapviews:global'] = 5;
|
||||
}
|
||||
|
||||
return next(null, parsedBody.layergroupid);
|
||||
}
|
||||
);
|
||||
},
|
||||
function getFeatureAttributes(err, layergroupId) {
|
||||
assert.ifError(err);
|
||||
|
||||
var next = this;
|
||||
|
||||
url = '/api/v1/map/' + layergroupId + '/' + layerId + '/attributes/' + featureId;
|
||||
|
||||
assert.response(server,
|
||||
{
|
||||
url: url,
|
||||
method: 'GET',
|
||||
headers: {
|
||||
host: 'localhost'
|
||||
}
|
||||
},
|
||||
expectedResponse,
|
||||
function(res, err) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
next(null, JSON.parse(res.body));
|
||||
}
|
||||
);
|
||||
},
|
||||
function finish(err, attributes) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
return callback(null, attributes);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
TestClient.prototype.getTile = function(z, x, y, params, callback) {
|
||||
var self = this;
|
||||
|
||||
|
||||
60
yarn.lock
60
yarn.lock
@@ -53,8 +53,8 @@ ap@~0.2.0:
|
||||
resolved "https://registry.yarnpkg.com/ap/-/ap-0.2.0.tgz#ae0942600b29912f0d2b14ec60c45e8f330b6110"
|
||||
|
||||
aproba@^1.0.3:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.1.1.tgz#95d3600f07710aa0e9298c726ad5ecf2eacbabab"
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.1.2.tgz#45c6629094de4e96f693ef7eab74ae079c240fc1"
|
||||
|
||||
are-we-there-yet@~1.1.2:
|
||||
version "1.1.4"
|
||||
@@ -161,10 +161,6 @@ browser-stdout@1.3.0:
|
||||
version "1.3.0"
|
||||
resolved "https://registry.yarnpkg.com/browser-stdout/-/browser-stdout-1.3.0.tgz#f351d32969d32fa5d7a5567154263d928ae3bd1f"
|
||||
|
||||
buffer-shims@~1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/buffer-shims/-/buffer-shims-1.0.0.tgz#9978ce317388c649ad8793028c3477ef044a8b51"
|
||||
|
||||
buffer-writer@1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/buffer-writer/-/buffer-writer-1.0.1.tgz#22a936901e3029afcd7547eb4487ceb697a3bf08"
|
||||
@@ -899,7 +895,7 @@ inflight@^1.0.4:
|
||||
once "^1.3.0"
|
||||
wrappy "1"
|
||||
|
||||
inherits@2, inherits@~2.0.0, inherits@~2.0.1:
|
||||
inherits@2, inherits@~2.0.0, inherits@~2.0.1, inherits@~2.0.3:
|
||||
version "2.0.3"
|
||||
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de"
|
||||
|
||||
@@ -1292,7 +1288,7 @@ mocha@~3.4.1:
|
||||
mkdirp "0.5.1"
|
||||
supports-color "3.1.2"
|
||||
|
||||
moment@^2.10.6:
|
||||
moment@^2.10.6, moment@~2.18.1:
|
||||
version "2.18.1"
|
||||
resolved "https://registry.yarnpkg.com/moment/-/moment-2.18.1.tgz#c36193dd3ce1c2eed2adb7c802dbbc77a81b1c0f"
|
||||
|
||||
@@ -1348,8 +1344,8 @@ nock@~2.11.0:
|
||||
propagate "0.3.x"
|
||||
|
||||
node-pre-gyp@~0.6.27, node-pre-gyp@~0.6.30, node-pre-gyp@~0.6.31:
|
||||
version "0.6.34"
|
||||
resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.6.34.tgz#94ad1c798a11d7fc67381b50d47f8cc18d9799f7"
|
||||
version "0.6.36"
|
||||
resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.6.36.tgz#db604112cb74e0d477554e9b505b17abddfab786"
|
||||
dependencies:
|
||||
mkdirp "^0.5.1"
|
||||
nopt "^4.0.1"
|
||||
@@ -1388,8 +1384,8 @@ normalize-package-data@^2.3.2:
|
||||
validate-npm-package-license "^3.0.1"
|
||||
|
||||
npmlog@^4.0.2:
|
||||
version "4.1.0"
|
||||
resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.1.0.tgz#dc59bee85f64f00ed424efb2af0783df25d1c0b5"
|
||||
version "4.1.2"
|
||||
resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.1.2.tgz#08a7f2a8bf734604779a9efa4ad5cc717abb954b"
|
||||
dependencies:
|
||||
are-we-there-yet "~1.1.2"
|
||||
console-control-strings "~1.1.0"
|
||||
@@ -1708,15 +1704,15 @@ readable-stream@1.1, readable-stream@~1.1.9:
|
||||
string_decoder "~0.10.x"
|
||||
|
||||
readable-stream@^2.0.6, readable-stream@^2.1.4:
|
||||
version "2.2.9"
|
||||
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.2.9.tgz#cf78ec6f4a6d1eb43d26488cac97f042e74b7fc8"
|
||||
version "2.3.3"
|
||||
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.3.tgz#368f2512d79f9d46fdfc71349ae7878bbc1eb95c"
|
||||
dependencies:
|
||||
buffer-shims "~1.0.0"
|
||||
core-util-is "~1.0.0"
|
||||
inherits "~2.0.1"
|
||||
inherits "~2.0.3"
|
||||
isarray "~1.0.0"
|
||||
process-nextick-args "~1.0.6"
|
||||
string_decoder "~1.0.0"
|
||||
safe-buffer "~5.1.1"
|
||||
string_decoder "~1.0.3"
|
||||
util-deprecate "~1.0.1"
|
||||
|
||||
readable-stream@~1.0.2:
|
||||
@@ -1827,9 +1823,9 @@ rimraf@~2.4.0:
|
||||
dependencies:
|
||||
glob "^6.0.1"
|
||||
|
||||
safe-buffer@^5.0.1:
|
||||
version "5.0.1"
|
||||
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.0.1.tgz#d263ca54696cd8a306b5ca6551e92de57918fbe7"
|
||||
safe-buffer@^5.0.1, safe-buffer@~5.1.0, safe-buffer@~5.1.1:
|
||||
version "5.1.1"
|
||||
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.1.tgz#893312af69b2123def71f57889001671eeb2c853"
|
||||
|
||||
safe-json-stringify@~1:
|
||||
version "1.0.4"
|
||||
@@ -2029,11 +2025,11 @@ string_decoder@~0.10.x:
|
||||
version "0.10.31"
|
||||
resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94"
|
||||
|
||||
string_decoder@~1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.0.0.tgz#f06f41157b664d86069f84bdbdc9b0d8ab281667"
|
||||
string_decoder@~1.0.3:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.0.3.tgz#0fc67d7c141825de94282dd536bec6b9bce860ab"
|
||||
dependencies:
|
||||
buffer-shims "~1.0.0"
|
||||
safe-buffer "~5.1.0"
|
||||
|
||||
stringstream@~0.0.4:
|
||||
version "0.0.5"
|
||||
@@ -2107,9 +2103,9 @@ through@2:
|
||||
version "2.3.8"
|
||||
resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5"
|
||||
|
||||
tilelive-bridge@cartodb/tilelive-bridge#2.3.1-cdb2:
|
||||
version "2.3.1-cdb2"
|
||||
resolved "https://codeload.github.com/cartodb/tilelive-bridge/tar.gz/0346c634875ac87dbf8316cb81ac46d2c30fe313"
|
||||
tilelive-bridge@cartodb/tilelive-bridge#2.3.1-cdb3:
|
||||
version "2.3.1-cdb3"
|
||||
resolved "https://codeload.github.com/cartodb/tilelive-bridge/tar.gz/bde83c8dcf4ada40c7c0eb1b477f212e75399d23"
|
||||
dependencies:
|
||||
mapnik "~3.5.0"
|
||||
mapnik-pool "~0.1.3"
|
||||
@@ -2156,7 +2152,7 @@ tunnel-agent@~0.4.1:
|
||||
version "0.4.3"
|
||||
resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.4.3.tgz#6373db76909fe570e08d73583365ed828a74eeeb"
|
||||
|
||||
turbo-carto@^0.19.2:
|
||||
turbo-carto@0.19.2:
|
||||
version "0.19.2"
|
||||
resolved "https://registry.yarnpkg.com/turbo-carto/-/turbo-carto-0.19.2.tgz#062d68e59f89377f0cfa69a2717c047fe95e32fd"
|
||||
dependencies:
|
||||
@@ -2278,9 +2274,9 @@ window-size@^0.2.0:
|
||||
version "0.2.0"
|
||||
resolved "https://registry.yarnpkg.com/window-size/-/window-size-0.2.0.tgz#b4315bb4214a3d7058ebeee892e13fa24d98b075"
|
||||
|
||||
windshaft@3.2.1:
|
||||
version "3.2.1"
|
||||
resolved "https://registry.yarnpkg.com/windshaft/-/windshaft-3.2.1.tgz#50a3afa6562315dd9e65e411660970e118f36c19"
|
||||
windshaft@3.2.2:
|
||||
version "3.2.2"
|
||||
resolved "https://registry.yarnpkg.com/windshaft/-/windshaft-3.2.2.tgz#7afb9d8fd8bba1bf02d39c06e8bbe5a451aad953"
|
||||
dependencies:
|
||||
abaculus cartodb/abaculus#2.0.3-cdb1
|
||||
canvas cartodb/node-canvas#1.6.2-cdb2
|
||||
@@ -2297,7 +2293,7 @@ windshaft@3.2.1:
|
||||
sphericalmercator "1.0.4"
|
||||
step "~0.0.6"
|
||||
tilelive "5.12.2"
|
||||
tilelive-bridge cartodb/tilelive-bridge#2.3.1-cdb2
|
||||
tilelive-bridge cartodb/tilelive-bridge#2.3.1-cdb3
|
||||
tilelive-mapnik cartodb/tilelive-mapnik#0.6.18-cdb2
|
||||
torque.js "~2.11.0"
|
||||
underscore "~1.6.0"
|
||||
|
||||
Reference in New Issue
Block a user