Compare commits
152 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3fa6750f9a | ||
|
|
1c842c1592 | ||
|
|
3c88634d09 | ||
|
|
c7f5f310f0 | ||
|
|
b32d056efe | ||
|
|
65308ea2eb | ||
|
|
8d16bf566d | ||
|
|
f2fa650661 | ||
|
|
aa57cdefb3 | ||
|
|
7c5b7641d8 | ||
|
|
eb4a49ec92 | ||
|
|
88f02458db | ||
|
|
1b405e42c2 | ||
|
|
bb5bfd10ee | ||
|
|
088a8b81a6 | ||
|
|
243e982bd6 | ||
|
|
dfe01c836c | ||
|
|
fcbf5ffcc5 | ||
|
|
90c9ad18e0 | ||
|
|
16a36a9d7a | ||
|
|
893b886a1e | ||
|
|
a60b335151 | ||
|
|
9ee0e2c3d0 | ||
|
|
565cfb7fbe | ||
|
|
0c8a31fad9 | ||
|
|
169b95809a | ||
|
|
23c0cb757d | ||
|
|
e18e86f565 | ||
|
|
496778c276 | ||
|
|
0ba4975360 | ||
|
|
e3d95fa654 | ||
|
|
cded5afdcb | ||
|
|
1b6de9961a | ||
|
|
a20e789302 | ||
|
|
f41af41bd4 | ||
|
|
c9e0f330c0 | ||
|
|
f9428682f9 | ||
|
|
330f8f3cb5 | ||
|
|
8270699b8e | ||
|
|
51c19c0b2e | ||
|
|
de376eef86 | ||
|
|
0a106cd038 | ||
|
|
1a78b8a75a | ||
|
|
e131df601c | ||
|
|
967d9b76e6 | ||
|
|
bee04e2553 | ||
|
|
37111f396d | ||
|
|
4df46fe5ea | ||
|
|
b1b2054f0a | ||
|
|
c1f2b96bfc | ||
|
|
804c6645fa | ||
|
|
5d6ccc07fd | ||
|
|
a585ba5480 | ||
|
|
448dcc7d82 | ||
|
|
0aaafa2068 | ||
|
|
1aa981d556 | ||
|
|
ccce598b04 | ||
|
|
667b2a9cb1 | ||
|
|
298882f410 | ||
|
|
6aaa5f99e2 | ||
|
|
22e3016cd3 | ||
|
|
d5c552a03a | ||
|
|
a5347c27e3 | ||
|
|
520e84e46b | ||
|
|
27521964c7 | ||
|
|
bdf4827300 | ||
|
|
172b3ece71 | ||
|
|
71146dbfaf | ||
|
|
38ca5db51b | ||
|
|
590233e3ee | ||
|
|
6f59c61c8b | ||
|
|
aff5fcda63 | ||
|
|
56d33b7f5b | ||
|
|
749b205944 | ||
|
|
ad0c035e2d | ||
|
|
d15ccd271e | ||
|
|
2aee357006 | ||
|
|
fc9dce0cca | ||
|
|
9149f72f42 | ||
|
|
743bb0723b | ||
|
|
0bf36fa058 | ||
|
|
970310bf7f | ||
|
|
4fc90db495 | ||
|
|
50ecdb5fee | ||
|
|
1ea4fc50c9 | ||
|
|
cda9a09b8e | ||
|
|
216c877f4b | ||
|
|
33fbff5011 | ||
|
|
c48e89826d | ||
|
|
52542e4a88 | ||
|
|
693a2e7bee | ||
|
|
f9ba3c41d3 | ||
|
|
ac153232d0 | ||
|
|
46289f27df | ||
|
|
05ccf20634 | ||
|
|
6acb873d95 | ||
|
|
65e8609fec | ||
|
|
677f6caab8 | ||
|
|
cb167313d2 | ||
|
|
2854d0252c | ||
|
|
717332d941 | ||
|
|
4607e4a12d | ||
|
|
3e7106002d | ||
|
|
08b91f935d | ||
|
|
1d08734721 | ||
|
|
b11b872b75 | ||
|
|
93bd2c9e50 | ||
|
|
658763da8c | ||
|
|
d2b5eaa8c3 | ||
|
|
eb5bf52bd9 | ||
|
|
c8000e5cf8 | ||
|
|
46c76d6a4c | ||
|
|
e6bec5ccb0 | ||
|
|
125587522f | ||
|
|
aeb9585708 | ||
|
|
8ed5df0072 | ||
|
|
6bbaeaa286 | ||
|
|
3d15551cb5 | ||
|
|
e0ffeb0adc | ||
|
|
e06f8fe25e | ||
|
|
da2228088e | ||
|
|
cdc39c8cae | ||
|
|
99fa66c026 | ||
|
|
d85a5d83b7 | ||
|
|
bb02494e02 | ||
|
|
39eb0f7bec | ||
|
|
5f7d5f6ec8 | ||
|
|
a4b2044e10 | ||
|
|
d1093686a3 | ||
|
|
12822c4341 | ||
|
|
fab87e2168 | ||
|
|
34e219353c | ||
|
|
3cf4a8f70b | ||
|
|
48172d4dc1 | ||
|
|
467bee4c91 | ||
|
|
3f2ef63976 | ||
|
|
235f5e4566 | ||
|
|
3f49743cd0 | ||
|
|
fb3afaa6ab | ||
|
|
b6c405bf68 | ||
|
|
da87a95dd9 | ||
|
|
cd7c604d10 | ||
|
|
b7227e0581 | ||
|
|
c564f5467a | ||
|
|
b04cc9c228 | ||
|
|
c0df0d12c6 | ||
|
|
5645cd16b3 | ||
|
|
eb6da1398e | ||
|
|
35c5cd34c2 | ||
|
|
6c51667ffb | ||
|
|
56bb083239 | ||
|
|
1207764c18 |
48
NEWS.md
48
NEWS.md
@@ -1,10 +1,58 @@
|
||||
# Changelog
|
||||
|
||||
## 4.4.0
|
||||
Released 2017-12-12
|
||||
|
||||
Announcements:
|
||||
- Upgrades camshaft to [0.60.0](https://github.com/CartoDB/camshaft/releases/tag/0.60.0).
|
||||
|
||||
|
||||
## 4.3.1
|
||||
Released 2017-12-12
|
||||
|
||||
Bug fix:
|
||||
- Fixed bug introduced in version 4.0.1 that brokes the static map generation using JPG as format #808
|
||||
|
||||
## 4.3.0
|
||||
Released 2017-12-11
|
||||
|
||||
Announcements:
|
||||
- Optimize Formula queries.
|
||||
- Optimize Formula queries in overviews.
|
||||
- Optimize Numeric Histogram queries.
|
||||
- Optimize Date Histogram queries.
|
||||
- Date Histograms: Now returns the same value for max/min/avg/timestamp per bin.
|
||||
- Date Histograms: Now it should return the same no matter the DB/Client time zone.
|
||||
|
||||
## 4.2.0
|
||||
Released 2017-12-04
|
||||
|
||||
Announcements:
|
||||
- Allow to request MVT tiles without CartoCSS
|
||||
- Upgrades windshaft to [4.1.0](https://github.com/CartoDB/windshaft/releases/tag/4.1.0).
|
||||
|
||||
|
||||
## 4.1.1
|
||||
Released 2017-11-29
|
||||
|
||||
Announcements:
|
||||
- Upgrades turbo-carto to [0.20.2](https://github.com/CartoDB/turbo-carto/releases/tag/0.20.2).
|
||||
|
||||
|
||||
## 4.1.0
|
||||
Released 2017-mm-dd
|
||||
|
||||
Announcements:
|
||||
- Upgrades windshaft to [4.0.1](https://github.com/CartoDB/windshaft/releases/tag/4.0.1).
|
||||
- Add `categories` query param to define the number of categories to be ranked for aggregation dataviews.
|
||||
|
||||
|
||||
## 4.0.1
|
||||
Released 2017-10-18
|
||||
|
||||
Announcements:
|
||||
- Upgrades camshaft to [0.59.4](https://github.com/CartoDB/camshaft/releases/tag/0.59.4).
|
||||
- Upgrades windshaft to [4.0.0](https://github.com/CartoDB/windshaft/releases/tag/4.0.0).
|
||||
- Split and move `req2params` method to multiple middlewares.
|
||||
- Use express error handler middleware to respond in case of something went wrong.
|
||||
- Use `res.locals` object to share info between middlewares and leave `req.params` as an object containing properties mapped to the named route params.
|
||||
|
||||
BIN
assets/render-timeout-fallback.mvt
Normal file
BIN
assets/render-timeout-fallback.mvt
Normal file
Binary file not shown.
@@ -17,3 +17,4 @@ You can create two types of maps with the Maps API:
|
||||
* [Anonymous Maps](anonymous_maps.md)
|
||||
* [Named Maps](named_maps.md)
|
||||
* [Static Maps API](static_maps_api.md)
|
||||
* [MapConfig File Format]([local file in the docs repo](https://github.com/CartoDB/docs/blob/master/_app/_mapsapi/06-mapconfig.md))
|
||||
|
||||
@@ -70,21 +70,203 @@ curl 'https://{username}.carto.com/api/v1/map' -H 'Content-Type: application/jso
|
||||
}
|
||||
```
|
||||
|
||||
### Retrieve resources from the layergroup
|
||||
## Map Tile Rendering
|
||||
|
||||
Map tiles create the graphical representation of your map in a web browser. The performance rendering of map tiles is dependent on the type of geospatial data model (raster or vector) that you are using.
|
||||
|
||||
- **Raster**: Generates map tiles based on a grid of pixels to represent your data. Each cell is a fixed size and contains values for particular map features. On the server-side, each request queries a dataset to retrieve data for each map tile. The grid size of map tiles can often lead to graphic quality issues.
|
||||
|
||||
- **Vector**: Generates map tiles based on pre-defined coordinates to represent your data, similar to how basemap image tiles are rendered. On the client-side, map tiles represent real-world geometries of a map. Depending on the coordinates, vertices are used to connect the data and display points, lines, or polygons for the map tiles.
|
||||
|
||||
## Retrieve resources from the layergroup
|
||||
|
||||
When you have a layergroup, there are several resources for retrieving layergoup details such as, accessing Mapnik tiles, getting individual layers, accessing defined Attributes, and blending and layer selection.
|
||||
|
||||
#### Mapnik tiles
|
||||
### Mapnik tiles
|
||||
|
||||
These tiles will get just the Mapnik layers. To get individual layers, see the following section.
|
||||
These raster tiles retrieve just the Mapnik layers. See [individual layers](#individual-layers) for details about how to retrieve other layers.
|
||||
|
||||
```bash
|
||||
https://{username}.carto.com/api/v1/map/{layergroupid}/{z}/{x}/{y}.png
|
||||
```
|
||||
|
||||
#### Individual layers
|
||||
### Mapbox Vector Tiles (MVT)
|
||||
|
||||
The MapConfig specification holds the layers definition in a 0-based index. Layers can be requested individually in different formats depending on the layer type.
|
||||
[Mapbox Vector Tiles (MVT)](https://www.mapbox.com/vector-tiles/specification/) are map tiles that store geographic vector data on the client-side. Browser performance is fast since you can pan and zoom without having to query the server.
|
||||
|
||||
CARTO uses a Web Graphics Library (WebGL) to process MVT files. This is useful since WebGL's are compatible with most web browsers, include support for multiple client-side mapping engines, and do not require additional information from the server; which makes it more efficient for rendering map tiles. However, you can use any implementation tool for processing MVT files.
|
||||
|
||||
The following examples describe how to fetch MVT tiles with a cURL request.
|
||||
|
||||
#### MVT and Windshaft
|
||||
|
||||
CARTO uses Windshaft as the map tiler library to render multilayer maps with the Maps API. You can use Windshaft to request MVT using the same layer type that is used for requesting raster tiles (Mapnik layer). Simply change the file format `.mvt` in the URL.
|
||||
|
||||
|
||||
```bash
|
||||
https://{username}.cartodb.com/api/v1/map/HASH/:layer/{z}/{x}/{y}.mvt
|
||||
```
|
||||
|
||||
The following example instantiates an anonymous map with layer options:
|
||||
|
||||
```bash
|
||||
{
|
||||
user_name: 'mycartodbuser',
|
||||
sublayers: [{
|
||||
sql: "SELECT * FROM table_name";
|
||||
cartocss: '#layer { marker-fill: #F0F0F0; }'
|
||||
}],
|
||||
maps_api_template: 'https://{user}.cartodb.com' // Optional
|
||||
}
|
||||
```
|
||||
|
||||
**Note**: If no layer type is specified, Mapnik tiles are used by default. To access MVT tiles, specify `https://{username}.cartodb.com/api/v1/map/HASH/{z}/{x}/{y}.mvt` as the `maps_api_template` variable.
|
||||
|
||||
**Tip:** If you are using [Named Maps](https://carto.com/docs/carto-engine/maps-api/named-maps/) to instantiate a layer, indicate the MVT file format and layer in the response:
|
||||
|
||||
```bash
|
||||
https://{username}.cartodb.com/api/v1/map/named/:templateId/:layer/{z}/{x}/{y}.mvt
|
||||
```
|
||||
|
||||
For all layers in a Named Map, you must indicate Mapnik as the layer filter:
|
||||
|
||||
```bash
|
||||
https://{username}.cartodb.com/api/v1/map/named/:templateId/mapnik/{z}/{x}/{y}.mvt
|
||||
```
|
||||
|
||||
#### Layergroup Filter for MVT Tiles
|
||||
|
||||
To filter layers using Windshaft, use the following request where layers are numbered:
|
||||
|
||||
```bash
|
||||
https://{username}.cartodb.com/api/v1/map/HASH/0,1,2/{z}/{x}/{y}.mvt
|
||||
```
|
||||
|
||||
To request all layers, remove the layergroup filter parameter:
|
||||
|
||||
```bash
|
||||
https://{username}.cartodb.com/api/v1/map/HASH/{z}/{x}/{y}.mvt
|
||||
```
|
||||
|
||||
To filter a specific layer:
|
||||
|
||||
```bash
|
||||
https://{username}.cartodb.com/api/v1/map/HASH/2/{z}/{x}/{y}.mvt
|
||||
```
|
||||
|
||||
#### Example 1: MVT Tiles with Windshaft, CARTO.js, and MapboxGL
|
||||
|
||||
1) Import the required libraries:
|
||||
|
||||
```bash
|
||||
<script src='https://api.tiles.mapbox.com/mapbox-gl-js/v0.9.0/mapbox-gl.js'></script>
|
||||
<link href='https://api.tiles.mapbox.com/mapbox-gl-js/v0.9.0/mapbox-gl.css' rel='stylesheet' />
|
||||
<script src="http://libs.cartocdn.com/cartodb.js/v3/3.15/cartodb.core.js"></script>
|
||||
```
|
||||
|
||||
2) Configure Map Client:
|
||||
|
||||
```bash
|
||||
mapboxgl.accessToken = '{yourMapboxToken}';
|
||||
```
|
||||
|
||||
3) Create Map Object (Mapbox):
|
||||
|
||||
```bash
|
||||
var map = new mapboxgl.Map({
|
||||
container: 'map',
|
||||
zoom: 1,
|
||||
minZoom: 0,
|
||||
maxZoom: 18,
|
||||
center: [30, 0]
|
||||
});
|
||||
```
|
||||
|
||||
4) Define Layer Options (CARTO):
|
||||
|
||||
```bash
|
||||
var layerOptions = {
|
||||
user_name: "{username}",
|
||||
sublayers: [{
|
||||
sql: "SELECT * FROM {table_name}",
|
||||
cartocss: "...",
|
||||
}]
|
||||
};
|
||||
```
|
||||
|
||||
5) Request Tiles (from CARTO) and Set to Map Object (Mapbox):
|
||||
|
||||
**Note:** By default, [CARTO core functions](https://carto.com/docs/carto-engine/carto-js/core-api/) retrieve URLs for fully rendered tiles. You must replace the default format (.png) with the MVT format (.mvt).
|
||||
|
||||
|
||||
```bash
|
||||
cartodb.Tiles.getTiles(layerOptions, function(result, err) {
|
||||
var tiles = result.tiles.map(function(tileUrl) {
|
||||
return tileUrl
|
||||
.replace('{s}', 'a')
|
||||
.replace(/\.png/, '.mvt');
|
||||
});
|
||||
map.setStyle(simpleStyle(tiles));
|
||||
});
|
||||
```
|
||||
|
||||
#### Example 2: MVT Libraries with Windshaft and MapboxGL
|
||||
|
||||
When you are not including CARTO.js to implement MVT tiles, you must use the `map.setStyle` parameter to specify vector map rendering.
|
||||
|
||||
1) Import the required libraries:
|
||||
|
||||
```bash
|
||||
<script src='https://api.tiles.mapbox.com/mapbox-gl-js/v0.9.0/mapbox-gl.js'></script>
|
||||
<link href='https://api.tiles.mapbox.com/mapbox-gl-js/v0.9.0/mapbox-gl.css' rel='stylesheet'/>
|
||||
```
|
||||
|
||||
2) Configure Map Client:
|
||||
|
||||
```bash
|
||||
mapboxgl.accessToken = '{yourMapboxToken}';
|
||||
```
|
||||
|
||||
3) Create Map Object (Mapbox):
|
||||
|
||||
```bash
|
||||
var map = new mapboxgl.Map({
|
||||
container: 'map',
|
||||
zoom: 1,
|
||||
minZoom: 0,
|
||||
maxZoom: 18,
|
||||
center: [30, 0]
|
||||
});
|
||||
```
|
||||
|
||||
4) Set the Style
|
||||
|
||||
```bash
|
||||
map.setStyle({
|
||||
"version": 7,
|
||||
"glyphs": "...",
|
||||
"constants": {...},
|
||||
"sources": {
|
||||
"cartodb": {
|
||||
"type": "vector",
|
||||
"tiles": [ "http://{username}.cartodb.com/api/v1/map/named/templateId/mapnik/{z}/{x}/{y}.mvt"
|
||||
],
|
||||
"maxzoom": 18
|
||||
}
|
||||
},
|
||||
"layers": [{...}]
|
||||
});
|
||||
```
|
||||
|
||||
**Tip:** If you are using MapboxGL, see the following resource for additional information.
|
||||
|
||||
- [MapboxGL API Reference](https://www.mapbox.com/mapbox-gl-js/api/)
|
||||
- [MapboxGL Style Specifications](https://www.mapbox.com/mapbox-gl-js/style-spec/)
|
||||
- [Example of MapboxGL Implementation](https://www.mapbox.com/mapbox-gl-js/examples/)
|
||||
|
||||
### Individual layers
|
||||
|
||||
The MapConfig specification holds the layers definition in a 0-based index. Layers can be requested individually, in different formats, depending on the layer type.
|
||||
|
||||
Individual layers can be accessed using that 0-based index. For UTF grid tiles:
|
||||
|
||||
@@ -100,19 +282,19 @@ If the MapConfig had a Torque layer at index 1 it could be possible to request i
|
||||
https://{username}.carto.com/api/v1/map/{layergroupid}/1/{z}/{x}/{y}.torque.json
|
||||
```
|
||||
|
||||
#### Attributes defined in `attributes` section
|
||||
### Attributes defined in `attributes` section
|
||||
|
||||
```bash
|
||||
https://{username}.carto.com/api/v1/map/{layergroupid}/{layer}/attributes/{feature_id}
|
||||
```
|
||||
|
||||
Which returns JSON with the attributes defined, like:
|
||||
Which returns JSON with the attributes defined, such as:
|
||||
|
||||
```javascript
|
||||
{ "c": 1, "d": 2 }
|
||||
```
|
||||
|
||||
#### Blending and layer selection
|
||||
### Blending and layer selection
|
||||
|
||||
```bash
|
||||
https://{username}.carto.com/api/v1/map/{layergroupid}/{layer_filter}/{z}/{x}/{y}.png
|
||||
@@ -141,10 +323,7 @@ https://{username}.carto.com/api/v1/map/{layergroupid}/0,3,4/{z}/{x}/{y}.png
|
||||
Some notes about filtering:
|
||||
|
||||
- Invalid index values or out of bounds indexes will end in `Invalid layer filtering` errors.
|
||||
- Ordering is not considered. So right now filtering layers 0,3,4 is the very same thing as filtering 3,4,0. As this
|
||||
may change in the future **it is recommended** to always select the layers in ascending order so you will get a
|
||||
consistent behavior in the future.
|
||||
|
||||
- Ordering is not considered. So right now filtering layers 0,3,4 is the very same thing as filtering 3,4,0. As this may change in the future, **it is recommended** to always select the layers in ascending order so that you will always get consistent behavior.
|
||||
|
||||
## Create JSONP
|
||||
|
||||
@@ -185,7 +364,6 @@ callback({
|
||||
})
|
||||
```
|
||||
|
||||
|
||||
## Remove
|
||||
|
||||
Anonymous Maps cannot be removed by an API call. They will expire after about five minutes, or sometimes longer. If an Anonymous Map expires and tiles are requested from it, an error will be raised. This could happen if a user leaves a map open and after time, returns to the map and attempts to interact with it in a way that requires new tiles (e.g. zoom). The client will need to go through the steps of creating the map again to fix the problem.
|
||||
|
||||
@@ -22,6 +22,6 @@ Errors are reported using standard HTTP codes and extended information encoded i
|
||||
|
||||
If you use JSONP, the 200 HTTP code is always returned so the JavaScript client can receive errors from the JSON object.
|
||||
|
||||
## CORS support
|
||||
## CORS Support
|
||||
|
||||
All the endpoints, which might be accessed using a web browser, add CORS headers and allow OPTIONS method.
|
||||
|
||||
@@ -152,7 +152,8 @@ It is important to note that generated images are cached from the live data refe
|
||||
* Timeout limits for generating static maps are the same across CARTO Builder and CARTO Engine. It is important to ensure timely processing of queries.
|
||||
* If you are publishing your map as a static image with the API, you must manually add [attributions](https://carto.com/attribution) for your static map image. For example, add the following attribution code:
|
||||
|
||||
{% highlight javascript %}attribution: '© <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors, © <a href="https://carto.com/attributions">CARTO</a>
|
||||
{% highlight javascript %}
|
||||
attribution: '© <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors, © <a href="https://carto.com/attributions">CARTO</a>
|
||||
{% endhighlight %}
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -24,7 +24,7 @@ module.exports = DataviewBackend;
|
||||
|
||||
DataviewBackend.prototype.getDataview = function (mapConfigProvider, user, params, callback) {
|
||||
|
||||
var dataviewName = params.dataviewName;
|
||||
var dataviewName = params.dataviewName;
|
||||
step(
|
||||
function getMapConfig() {
|
||||
mapConfigProvider.getMapConfig(this);
|
||||
@@ -94,7 +94,7 @@ function getQueryRewriteData(mapConfig, dataviewDefinition, params) {
|
||||
}
|
||||
|
||||
function getOverrideParams(params, ownFilter) {
|
||||
var overrideParams = _.reduce(_.pick(params, 'start', 'end', 'bins', 'offset'),
|
||||
var overrideParams = _.reduce(_.pick(params, 'start', 'end', 'bins', 'offset', 'categories'),
|
||||
function castNumbers(overrides, val, k) {
|
||||
if (!Number.isFinite(+val)) {
|
||||
throw new Error('Invalid number format for parameter \'' + k + '\'');
|
||||
|
||||
@@ -1,157 +1,150 @@
|
||||
var step = require('step');
|
||||
var assert = require('assert');
|
||||
var dot = require('dot');
|
||||
dot.templateSettings.strip = false;
|
||||
var PSQL = require('cartodb-psql');
|
||||
|
||||
var util = require('util');
|
||||
var BaseController = require('./base');
|
||||
|
||||
var cors = require('../middleware/cors');
|
||||
var userMiddleware = require('../middleware/user');
|
||||
|
||||
|
||||
function AnalysesController(prepareContext) {
|
||||
BaseController.call(this);
|
||||
this.prepareContext = prepareContext;
|
||||
}
|
||||
|
||||
util.inherits(AnalysesController, BaseController);
|
||||
|
||||
module.exports = AnalysesController;
|
||||
|
||||
AnalysesController.prototype.register = function(app) {
|
||||
AnalysesController.prototype.register = function (app) {
|
||||
app.get(
|
||||
app.base_url_mapconfig + '/analyses/catalog',
|
||||
`${app.base_url_mapconfig}/analyses/catalog`,
|
||||
cors(),
|
||||
userMiddleware,
|
||||
this.prepareContext,
|
||||
this.catalog.bind(this)
|
||||
this.createPGClient(),
|
||||
this.getDataFromQuery({ queryTemplate: catalogQueryTpl, key: 'catalog' }),
|
||||
this.getDataFromQuery({ queryTemplate: tablesQueryTpl, key: 'tables' }),
|
||||
this.prepareResponse(),
|
||||
this.setCacheControlHeader(),
|
||||
this.sendResponse(),
|
||||
this.unathorizedError()
|
||||
);
|
||||
};
|
||||
|
||||
AnalysesController.prototype.sendResponse = function(req, res, resource) {
|
||||
res.set('Cache-Control', 'public,max-age=10,must-revalidate');
|
||||
this.send(req, res, resource, 200);
|
||||
};
|
||||
|
||||
AnalysesController.prototype.catalog = function (req, res, next) {
|
||||
var self = this;
|
||||
var username = res.locals.user;
|
||||
|
||||
step(
|
||||
function catalogQuery() {
|
||||
var pg = new PSQL(dbParamsFromReqParams(res.locals));
|
||||
getMetadata(username, pg, this);
|
||||
},
|
||||
function prepareResponse(err, results) {
|
||||
assert.ifError(err);
|
||||
|
||||
var analysisIdToTable = results.tables.reduce(function(analysisIdToTable, table) {
|
||||
var analysisId = table.relname.split('_')[2];
|
||||
if (analysisId && analysisId.length === 40) {
|
||||
analysisIdToTable[analysisId] = table;
|
||||
}
|
||||
return analysisIdToTable;
|
||||
}, {});
|
||||
|
||||
var catalogWithTables = results.catalog.map(function(analysis) {
|
||||
if (analysisIdToTable.hasOwnProperty(analysis.node_id)) {
|
||||
analysis.table = analysisIdToTable[analysis.node_id];
|
||||
}
|
||||
return analysis;
|
||||
});
|
||||
|
||||
return catalogWithTables.sort(function(analysisA, analysisB) {
|
||||
if (!!analysisA.table && !!analysisB.table) {
|
||||
return analysisB.table.size - analysisA.table.size;
|
||||
}
|
||||
|
||||
if (!!analysisA.table) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!!analysisB.table) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
return -1;
|
||||
});
|
||||
},
|
||||
function sendResponse(err, catalogWithTables) {
|
||||
if (err) {
|
||||
if (err.message.match(/permission\sdenied/)) {
|
||||
err = new Error('Unauthorized');
|
||||
err.http_status = 401;
|
||||
}
|
||||
|
||||
next(req, res, err);
|
||||
} else {
|
||||
self.sendResponse(req, res, { catalog: catalogWithTables });
|
||||
}
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
var catalogQueryTpl = dot.template(
|
||||
'SELECT analysis_def->>\'type\' as type, * FROM cartodb.cdb_analysis_catalog WHERE username = \'{{=it._username}}\''
|
||||
);
|
||||
|
||||
var tablesQueryTpl = dot.template([
|
||||
"WITH analysis_tables AS (",
|
||||
" SELECT",
|
||||
" n.nspname AS nspname,",
|
||||
" c.relname AS relname,",
|
||||
" pg_total_relation_size(",
|
||||
" format('%s.%s', pg_catalog.quote_ident(n.nspname), pg_catalog.quote_ident(c.relname))",
|
||||
" ) AS size,",
|
||||
" format('%s.%s', pg_catalog.quote_ident(nspname), pg_catalog.quote_ident(relname)) AS fully_qualified_name",
|
||||
" FROM pg_catalog.pg_class c, pg_catalog.pg_namespace n",
|
||||
" WHERE c.relnamespace = n.oid",
|
||||
" AND pg_catalog.quote_ident(c.relname) ~ '^analysis_[a-z0-9]{10}_[a-z0-9]{40}$'",
|
||||
" AND n.nspname IN ('{{=it._username}}', 'public')",
|
||||
")",
|
||||
"SELECT *, pg_size_pretty(size) as size_pretty",
|
||||
"FROM analysis_tables",
|
||||
"ORDER BY size DESC"
|
||||
].join('\n'));
|
||||
|
||||
|
||||
function getMetadata(username, pg, callback) {
|
||||
var results = {
|
||||
catalog: [],
|
||||
tables: []
|
||||
AnalysesController.prototype.createPGClient = function () {
|
||||
return function createPGClientMiddleware (req, res, next) {
|
||||
res.locals.pg = new PSQL(dbParamsFromReqParams(res.locals));
|
||||
next();
|
||||
};
|
||||
step(
|
||||
function getCatalog() {
|
||||
pg.query(catalogQueryTpl({_username: username}), this, true); // use read-only transaction
|
||||
},
|
||||
function handleCatalog(err, resultSet) {
|
||||
assert.ifError(err);
|
||||
resultSet = resultSet || {};
|
||||
results.catalog = resultSet.rows || [];
|
||||
this();
|
||||
},
|
||||
function getTables(err) {
|
||||
assert.ifError(err);
|
||||
pg.query(tablesQueryTpl({_username: username}), this, true); // use read-only transaction
|
||||
},
|
||||
function handleTables(err, resultSet) {
|
||||
assert.ifError(err);
|
||||
resultSet = resultSet || {};
|
||||
results.tables = resultSet.rows || [];
|
||||
this();
|
||||
},
|
||||
function finish(err) {
|
||||
};
|
||||
|
||||
AnalysesController.prototype.getDataFromQuery = function ({ queryTemplate, key }) {
|
||||
const readOnlyTransactionOn = true;
|
||||
|
||||
return function getCatalogMiddleware(req, res, next) {
|
||||
const { pg, user } = res.locals;
|
||||
const sql = queryTemplate({ _username: user });
|
||||
|
||||
pg.query(sql, (err, resultSet = {}) => {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
return next(err);
|
||||
}
|
||||
|
||||
return callback(null, results);
|
||||
}
|
||||
);
|
||||
}
|
||||
res.locals[key] = resultSet.rows || [];
|
||||
|
||||
next();
|
||||
}, readOnlyTransactionOn);
|
||||
};
|
||||
};
|
||||
|
||||
AnalysesController.prototype.prepareResponse = function () {
|
||||
return function prepareResponseMiddleware (req, res, next) {
|
||||
const { catalog, tables } = res.locals;
|
||||
|
||||
const analysisIdToTable = tables.reduce((analysisIdToTable, table) => {
|
||||
const analysisId = table.relname.split('_')[2];
|
||||
|
||||
if (analysisId && analysisId.length === 40) {
|
||||
analysisIdToTable[analysisId] = table;
|
||||
}
|
||||
|
||||
return analysisIdToTable;
|
||||
}, {});
|
||||
|
||||
const analysisCatalog = catalog.map(analysis => {
|
||||
if (analysisIdToTable.hasOwnProperty(analysis.node_id)) {
|
||||
analysis.table = analysisIdToTable[analysis.node_id];
|
||||
}
|
||||
|
||||
return analysis;
|
||||
})
|
||||
.sort((analysisA, analysisB) => {
|
||||
if (!!analysisA.table && !!analysisB.table) {
|
||||
return analysisB.table.size - analysisA.table.size;
|
||||
}
|
||||
|
||||
if (!!analysisA.table) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!!analysisB.table) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
return -1;
|
||||
});
|
||||
|
||||
res.body = { catalog: analysisCatalog };
|
||||
|
||||
next();
|
||||
};
|
||||
};
|
||||
|
||||
AnalysesController.prototype.setCacheControlHeader = function () {
|
||||
return function setCacheControlHeaderMiddleware (req, res, next) {
|
||||
res.set('Cache-Control', 'public,max-age=10,must-revalidate');
|
||||
next();
|
||||
};
|
||||
};
|
||||
|
||||
AnalysesController.prototype.sendResponse = function() {
|
||||
return function sendResponseMiddleware (req, res) {
|
||||
res.status(200);
|
||||
|
||||
if (req.query && req.query.callback) {
|
||||
res.jsonp(res.body);
|
||||
} else {
|
||||
res.json(res.body);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
AnalysesController.prototype.unathorizedError = function () {
|
||||
return function unathorizedErrorMiddleware(err, req, res, next) {
|
||||
if (err.message.match(/permission\sdenied/)) {
|
||||
err = new Error('Unauthorized');
|
||||
err.http_status = 401;
|
||||
}
|
||||
|
||||
next(err);
|
||||
};
|
||||
};
|
||||
|
||||
const catalogQueryTpl = ctx => `
|
||||
SELECT analysis_def->>'type' as type, * FROM cdb_analysis_catalog WHERE username = '${ctx._username}'
|
||||
`;
|
||||
|
||||
var tablesQueryTpl = ctx => `
|
||||
WITH analysis_tables AS (
|
||||
SELECT
|
||||
n.nspname AS nspname,
|
||||
c.relname AS relname,
|
||||
pg_total_relation_size(
|
||||
format('%s.%s', pg_catalog.quote_ident(n.nspname), pg_catalog.quote_ident(c.relname))
|
||||
) AS size,
|
||||
format('%s.%s', pg_catalog.quote_ident(nspname), pg_catalog.quote_ident(relname)) AS fully_qualified_name
|
||||
FROM pg_catalog.pg_class c, pg_catalog.pg_namespace n
|
||||
WHERE c.relnamespace = n.oid
|
||||
AND pg_catalog.quote_ident(c.relname) ~ '^analysis_[a-z0-9]{10}_[a-z0-9]{40}$'
|
||||
AND n.nspname IN ('${ctx._username}', 'public')
|
||||
)
|
||||
SELECT *, pg_size_pretty(size) as size_pretty
|
||||
FROM analysis_tables
|
||||
ORDER BY size DESC
|
||||
`;
|
||||
|
||||
function dbParamsFromReqParams(params) {
|
||||
var dbParams = {};
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
function BaseController() {
|
||||
}
|
||||
|
||||
module.exports = BaseController;
|
||||
|
||||
BaseController.prototype.send = function(req, res, body, status, headers) {
|
||||
if (headers) {
|
||||
res.set(headers);
|
||||
}
|
||||
|
||||
res.status(status);
|
||||
|
||||
if (!Buffer.isBuffer(body) && typeof body === 'object') {
|
||||
if (req.query && req.query.callback) {
|
||||
res.jsonp(body);
|
||||
} else {
|
||||
res.json(body);
|
||||
}
|
||||
} else {
|
||||
res.send(body);
|
||||
}
|
||||
};
|
||||
@@ -1,12 +1,10 @@
|
||||
var assert = require('assert');
|
||||
var step = require('step');
|
||||
|
||||
var util = require('util');
|
||||
var BaseController = require('./base');
|
||||
|
||||
var cors = require('../middleware/cors');
|
||||
var userMiddleware = require('../middleware/user');
|
||||
var allowQueryParams = require('../middleware/allow-query-params');
|
||||
var vectorError = require('../middleware/vector-error');
|
||||
|
||||
var DataviewBackend = require('../backends/dataview');
|
||||
var AnalysisStatusBackend = require('../backends/analysis-status');
|
||||
@@ -30,8 +28,6 @@ var QueryTables = require('cartodb-query-tables');
|
||||
*/
|
||||
function LayergroupController(prepareContext, pgConnection, mapStore, tileBackend, previewBackend, attributesBackend,
|
||||
surrogateKeysCache, userLimitsApi, layergroupAffectedTables, analysisBackend) {
|
||||
BaseController.call(this);
|
||||
|
||||
this.pgConnection = pgConnection;
|
||||
this.mapStore = mapStore;
|
||||
this.tileBackend = tileBackend;
|
||||
@@ -47,8 +43,6 @@ function LayergroupController(prepareContext, pgConnection, mapStore, tileBacken
|
||||
this.prepareContext = prepareContext;
|
||||
}
|
||||
|
||||
util.inherits(LayergroupController, BaseController);
|
||||
|
||||
module.exports = LayergroupController;
|
||||
|
||||
LayergroupController.prototype.register = function(app) {
|
||||
@@ -57,7 +51,8 @@ LayergroupController.prototype.register = function(app) {
|
||||
cors(),
|
||||
userMiddleware,
|
||||
this.prepareContext,
|
||||
this.tile.bind(this)
|
||||
this.tile.bind(this),
|
||||
vectorError()
|
||||
);
|
||||
|
||||
app.get(
|
||||
@@ -65,7 +60,8 @@ LayergroupController.prototype.register = function(app) {
|
||||
cors(),
|
||||
userMiddleware,
|
||||
this.prepareContext,
|
||||
this.tile.bind(this)
|
||||
this.tile.bind(this),
|
||||
vectorError()
|
||||
);
|
||||
|
||||
app.get(
|
||||
@@ -74,7 +70,8 @@ LayergroupController.prototype.register = function(app) {
|
||||
userMiddleware,
|
||||
validateLayerRouteMiddleware,
|
||||
this.prepareContext,
|
||||
this.layer.bind(this)
|
||||
this.layer.bind(this),
|
||||
vectorError()
|
||||
);
|
||||
|
||||
app.get(
|
||||
@@ -116,7 +113,8 @@ LayergroupController.prototype.register = function(app) {
|
||||
'bins', // number
|
||||
'aggregation', //string
|
||||
'offset', // number
|
||||
'q' // widgets search
|
||||
'q', // widgets search
|
||||
'categories', // number
|
||||
];
|
||||
|
||||
app.get(
|
||||
@@ -359,7 +357,9 @@ LayergroupController.prototype.center = function(req, res, next) {
|
||||
|
||||
LayergroupController.prototype.staticMap = function(req, res, width, height, zoom /* bounds */, center, next) {
|
||||
var format = req.params.format === 'jpg' ? 'jpeg' : 'png';
|
||||
req.params.format = req.params.format || 'png';
|
||||
// We force always the tile to be generated using PNG because
|
||||
// is the only format we support by now
|
||||
res.locals.format = 'png';
|
||||
res.locals.layer = res.locals.layer || 'all';
|
||||
|
||||
var self = this;
|
||||
@@ -422,10 +422,24 @@ LayergroupController.prototype.sendResponse = function(req, res, body, status, h
|
||||
res.set('X-Cache-Channel', affectedTables.getCacheChannel());
|
||||
self.surrogateKeysCache.tag(res, affectedTables);
|
||||
}
|
||||
self.send(req, res, body, status, headers);
|
||||
|
||||
if (headers) {
|
||||
res.set(headers);
|
||||
}
|
||||
|
||||
res.status(status);
|
||||
|
||||
if (!Buffer.isBuffer(body) && typeof body === 'object') {
|
||||
if (req.query && req.query.callback) {
|
||||
res.jsonp(body);
|
||||
} else {
|
||||
res.json(body);
|
||||
}
|
||||
} else {
|
||||
res.send(body);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
};
|
||||
|
||||
LayergroupController.prototype.getAffectedTables = function(user, dbName, layergroupId, callback) {
|
||||
@@ -495,4 +509,4 @@ function validateLayerRouteMiddleware(req, res, next) {
|
||||
}
|
||||
|
||||
next();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,9 @@
|
||||
var _ = require('underscore');
|
||||
var assert = require('assert');
|
||||
var step = require('step');
|
||||
var windshaft = require('windshaft');
|
||||
var QueryTables = require('cartodb-query-tables');
|
||||
|
||||
var ResourceLocator = require('../models/resource-locator');
|
||||
|
||||
var util = require('util');
|
||||
var BaseController = require('./base');
|
||||
|
||||
var cors = require('../middleware/cors');
|
||||
var userMiddleware = require('../middleware/user');
|
||||
|
||||
@@ -36,9 +31,6 @@ var CreateLayergroupMapConfigProvider = require('../models/mapconfig/provider/cr
|
||||
function MapController(prepareContext, pgConnection, templateMaps, mapBackend, metadataBackend,
|
||||
surrogateKeysCache, userLimitsApi, layergroupAffectedTables, mapConfigAdapter,
|
||||
statsBackend) {
|
||||
|
||||
BaseController.call(this);
|
||||
|
||||
this.pgConnection = pgConnection;
|
||||
this.templateMaps = templateMaps;
|
||||
this.mapBackend = mapBackend;
|
||||
@@ -54,354 +46,342 @@ function MapController(prepareContext, pgConnection, templateMaps, mapBackend, m
|
||||
this.prepareContext = prepareContext;
|
||||
}
|
||||
|
||||
util.inherits(MapController, BaseController);
|
||||
|
||||
module.exports = MapController;
|
||||
|
||||
|
||||
MapController.prototype.register = function(app) {
|
||||
app.get(
|
||||
app.base_url_mapconfig,
|
||||
cors(),
|
||||
userMiddleware,
|
||||
this.prepareContext,
|
||||
this.createGet.bind(this)
|
||||
);
|
||||
app.post(
|
||||
app.base_url_mapconfig,
|
||||
cors(),
|
||||
userMiddleware,
|
||||
this.prepareContext,
|
||||
this.createPost.bind(this)
|
||||
);
|
||||
app.get(
|
||||
app.base_url_templated + '/:template_id/jsonp',
|
||||
cors(),
|
||||
userMiddleware,
|
||||
this.prepareContext,
|
||||
this.jsonp.bind(this)
|
||||
);
|
||||
app.post(
|
||||
app.base_url_templated + '/:template_id',
|
||||
cors(),
|
||||
userMiddleware,
|
||||
this.prepareContext,
|
||||
this.instantiate.bind(this)
|
||||
);
|
||||
const { base_url_mapconfig, base_url_templated } = app;
|
||||
const useTemplate = true;
|
||||
|
||||
app.get(base_url_mapconfig, this.composeCreateMapMiddleware());
|
||||
app.post(base_url_mapconfig, this.composeCreateMapMiddleware());
|
||||
app.get(`${base_url_templated}/:template_id/jsonp`, this.composeCreateMapMiddleware(useTemplate));
|
||||
app.post(`${base_url_templated}/:template_id`, this.composeCreateMapMiddleware(useTemplate));
|
||||
app.options(app.base_url_mapconfig, cors('Content-Type'));
|
||||
};
|
||||
|
||||
MapController.prototype.createGet = function(req, res, next){
|
||||
req.profiler.start('windshaft.createmap_get');
|
||||
MapController.prototype.composeCreateMapMiddleware = function (useTemplate = false) {
|
||||
const isTemplateInstantiation = useTemplate;
|
||||
const useTemplateHash = useTemplate;
|
||||
const includeQuery = !useTemplate;
|
||||
const label = useTemplate ? 'NAMED MAP LAYERGROUP' : 'ANONYMOUS LAYERGROUP';
|
||||
const addContext = !useTemplate;
|
||||
|
||||
this.create(req, res, function createGet$prepareConfig(req, config) {
|
||||
if ( ! config ) {
|
||||
throw new Error('layergroup GET needs a "config" parameter');
|
||||
}
|
||||
return JSON.parse(config);
|
||||
}, next);
|
||||
return [
|
||||
cors(),
|
||||
userMiddleware,
|
||||
this.prepareContext,
|
||||
this.initProfiler(isTemplateInstantiation),
|
||||
this.checkJsonContentType(),
|
||||
useTemplate ? this.checkInstantiteLayergroup() : this.checkCreateLayergroup(),
|
||||
useTemplate ? this.getTemplate() : this.prepareAdapterMapConfig(),
|
||||
useTemplate ? this.instantiateLayergroup() : this.createLayergroup(),
|
||||
this.incrementMapViewCount(),
|
||||
this.augmentLayergroupData(),
|
||||
this.getAffectedTables(),
|
||||
this.setCacheChannel(),
|
||||
this.setLastModified(),
|
||||
this.setLastUpdatedTimeToLayergroup(),
|
||||
this.setCacheControl(),
|
||||
this.setLayerStats(),
|
||||
this.setLayergroupIdHeader(useTemplateHash),
|
||||
this.setDataviewsAndWidgetsUrlsToLayergroupMetadata(),
|
||||
this.setAnalysesMetadataToLayergroup(includeQuery),
|
||||
this.setTurboCartoMetadataToLayergroup(),
|
||||
this.setSurrogateKeyHeader(),
|
||||
this.sendResponse(),
|
||||
this.augmentError({ label, addContext })
|
||||
];
|
||||
};
|
||||
|
||||
MapController.prototype.createPost = function(req, res, next) {
|
||||
req.profiler.start('windshaft.createmap_post');
|
||||
MapController.prototype.initProfiler = function (isTemplateInstantiation) {
|
||||
const operation = isTemplateInstantiation ? 'instance_template' : 'createmap';
|
||||
|
||||
this.create(req, res, function createPost$prepareConfig(req) {
|
||||
if (!req.is('application/json')) {
|
||||
throw new Error('layergroup POST data must be of type application/json');
|
||||
}
|
||||
return req.body;
|
||||
}, next);
|
||||
return function initProfilerMiddleware (req, res, next) {
|
||||
req.profiler.start(`windshaft-cartodb.${operation}_${req.method.toLowerCase()}`);
|
||||
req.profiler.done(`${operation}.initProfilerMiddleware`);
|
||||
next();
|
||||
};
|
||||
};
|
||||
|
||||
MapController.prototype.instantiate = function(req, res, next) {
|
||||
req.profiler.start('windshaft-cartodb.instance_template_post');
|
||||
|
||||
this.instantiateTemplate(req, res, function prepareTemplateParams(callback) {
|
||||
if (!req.is('application/json')) {
|
||||
return callback(new Error('Template POST data must be of type application/json'));
|
||||
MapController.prototype.checkJsonContentType = function () {
|
||||
return function checkJsonContentTypeMiddleware(req, res, next) {
|
||||
if (req.method === 'POST' && !req.is('application/json')) {
|
||||
return next(new Error('POST data must be of type application/json'));
|
||||
}
|
||||
return callback(null, req.body);
|
||||
}, next);
|
||||
|
||||
req.profiler.done('checkJsonContentTypeMiddleware');
|
||||
|
||||
next();
|
||||
};
|
||||
};
|
||||
|
||||
MapController.prototype.jsonp = function(req, res, next) {
|
||||
req.profiler.start('windshaft-cartodb.instance_template_get');
|
||||
MapController.prototype.checkInstantiteLayergroup = function () {
|
||||
return function checkInstantiteLayergroupMiddleware(req, res, next) {
|
||||
if (req.method === 'GET') {
|
||||
const { callback, config } = req.query;
|
||||
|
||||
this.instantiateTemplate(req, res, function prepareJsonTemplateParams(callback) {
|
||||
var err = null;
|
||||
if ( req.query.callback === undefined || req.query.callback.length === 0) {
|
||||
err = new Error('callback parameter should be present and be a function name');
|
||||
}
|
||||
if (callback === undefined || callback.length === 0) {
|
||||
return next(new Error('callback parameter should be present and be a function name'));
|
||||
}
|
||||
|
||||
var templateParams = {};
|
||||
if (req.query.config) {
|
||||
try {
|
||||
templateParams = JSON.parse(req.query.config);
|
||||
} catch(e) {
|
||||
err = new Error('Invalid config parameter, should be a valid JSON');
|
||||
if (config) {
|
||||
try {
|
||||
req.body = JSON.parse(config);
|
||||
} catch(e) {
|
||||
return next(new Error('Invalid config parameter, should be a valid JSON'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return callback(err, templateParams);
|
||||
}, next);
|
||||
req.profiler.done('checkInstantiteLayergroup');
|
||||
|
||||
return next();
|
||||
};
|
||||
};
|
||||
|
||||
MapController.prototype.create = function(req, res, prepareConfigFn, next) {
|
||||
var self = this;
|
||||
MapController.prototype.checkCreateLayergroup = function () {
|
||||
return function checkCreateLayergroupMiddleware (req, res, next) {
|
||||
if (req.method === 'GET') {
|
||||
const { config } = res.locals;
|
||||
|
||||
var mapConfig;
|
||||
if (!config) {
|
||||
return next(new Error('layergroup GET needs a "config" parameter'));
|
||||
}
|
||||
|
||||
var context = {};
|
||||
try {
|
||||
req.body = JSON.parse(config);
|
||||
} catch (err) {
|
||||
return next(err);
|
||||
}
|
||||
}
|
||||
|
||||
step(
|
||||
function prepareConfig () {
|
||||
const requestMapConfig = prepareConfigFn(req, res.locals.config);
|
||||
return requestMapConfig;
|
||||
},
|
||||
function prepareAdapterMapConfig(err, requestMapConfig) {
|
||||
assert.ifError(err);
|
||||
context.analysisConfiguration = {
|
||||
user: res.locals.user,
|
||||
req.profiler.done('checkCreateLayergroup');
|
||||
return next();
|
||||
};
|
||||
};
|
||||
|
||||
MapController.prototype.getTemplate = function () {
|
||||
return function getTemplateMiddleware (req, res, next) {
|
||||
const templateParams = req.body;
|
||||
const { user } = res.locals;
|
||||
|
||||
const mapconfigProvider = new NamedMapMapConfigProvider(
|
||||
this.templateMaps,
|
||||
this.pgConnection,
|
||||
this.metadataBackend,
|
||||
this.userLimitsApi,
|
||||
this.mapConfigAdapter,
|
||||
user,
|
||||
req.params.template_id,
|
||||
templateParams,
|
||||
res.locals.auth_token,
|
||||
res.locals
|
||||
);
|
||||
|
||||
mapconfigProvider.getMapConfig((err, mapconfig, rendererParams) => {
|
||||
req.profiler.done('named.getMapConfig');
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
res.locals.mapconfig = mapconfig;
|
||||
res.locals.rendererParams = rendererParams;
|
||||
res.locals.mapconfigProvider = mapconfigProvider;
|
||||
|
||||
next();
|
||||
});
|
||||
}.bind(this);
|
||||
};
|
||||
|
||||
MapController.prototype.prepareAdapterMapConfig = function () {
|
||||
return function prepareAdapterMapConfigMiddleware(req, res, next) {
|
||||
const requestMapConfig = req.body;
|
||||
const { user, dbhost, dbport, dbname, dbuser, dbpassword, api_key } = res.locals;
|
||||
|
||||
const context = {
|
||||
analysisConfiguration: {
|
||||
user,
|
||||
db: {
|
||||
host: res.locals.dbhost,
|
||||
port: res.locals.dbport,
|
||||
dbname: res.locals.dbname,
|
||||
user: res.locals.dbuser,
|
||||
pass: res.locals.dbpassword
|
||||
host: dbhost,
|
||||
port: dbport,
|
||||
dbname: dbname,
|
||||
user: dbuser,
|
||||
pass: dbpassword
|
||||
},
|
||||
batch: {
|
||||
username: res.locals.user,
|
||||
apiKey: res.locals.api_key
|
||||
username: user,
|
||||
apiKey: api_key
|
||||
}
|
||||
};
|
||||
self.mapConfigAdapter.getMapConfig(res.locals.user, requestMapConfig, res.locals, context, this);
|
||||
},
|
||||
function createLayergroup(err, requestMapConfig) {
|
||||
assert.ifError(err);
|
||||
var datasource = context.datasource || Datasource.EmptyDatasource();
|
||||
mapConfig = new MapConfig(requestMapConfig, datasource);
|
||||
self.mapBackend.createLayergroup(
|
||||
mapConfig,
|
||||
res.locals,
|
||||
new CreateLayergroupMapConfigProvider(mapConfig, res.locals.user, self.userLimitsApi, res.locals),
|
||||
this
|
||||
);
|
||||
},
|
||||
function afterLayergroupCreate(err, layergroup) {
|
||||
assert.ifError(err);
|
||||
self.afterLayergroupCreate(req, res, mapConfig, layergroup, context.analysesResults, this);
|
||||
},
|
||||
function finish(err, layergroup) {
|
||||
}
|
||||
};
|
||||
|
||||
this.mapConfigAdapter.getMapConfig(user, requestMapConfig, res.locals, context, (err, requestMapConfig) => {
|
||||
req.profiler.done('anonymous.getMapConfig');
|
||||
if (err) {
|
||||
if (Number.isFinite(err.layerIndex)) {
|
||||
var error = new Error(err.message);
|
||||
error.http_status = err.http_status;
|
||||
|
||||
if (!err.http_status && err.message.indexOf('column "the_geom_webmercator" does not exist') >= 0) {
|
||||
error.http_status = 400;
|
||||
}
|
||||
|
||||
error.type = 'layer';
|
||||
error.subtype = err.message.indexOf('Postgis Plugin') >= 0 ? 'query' : undefined;
|
||||
error.layer = {
|
||||
id: mapConfig.getLayerId(err.layerIndex),
|
||||
index: err.layerIndex,
|
||||
type: mapConfig.layerType(err.layerIndex)
|
||||
};
|
||||
|
||||
err = error;
|
||||
}
|
||||
err.label = 'ANONYMOUS LAYERGROUP';
|
||||
next(err);
|
||||
} else {
|
||||
var analysesResults = context.analysesResults || [];
|
||||
self.addDataviewsAndWidgetsUrls(res.locals.user, layergroup, mapConfig.obj());
|
||||
self.addAnalysesMetadata(res.locals.user, layergroup, analysesResults, true);
|
||||
addContextMetadata(layergroup, mapConfig.obj(), context);
|
||||
res.set('X-Layergroup-Id', layergroup.layergroupid);
|
||||
self.send(req, res, layergroup, 200);
|
||||
return next(err);
|
||||
}
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
function addContextMetadata(layergroup, mapConfig, context) {
|
||||
if (layergroup.metadata && Array.isArray(layergroup.metadata.layers) && Array.isArray(mapConfig.layers)) {
|
||||
layergroup.metadata.layers = layergroup.metadata.layers.map(function(layer, layerIndex) {
|
||||
if (context.turboCarto && Array.isArray(context.turboCarto.layers)) {
|
||||
layer.meta.cartocss_meta = context.turboCarto.layers[layerIndex];
|
||||
}
|
||||
return layer;
|
||||
req.body = requestMapConfig;
|
||||
res.locals.context = context;
|
||||
|
||||
next();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
MapController.prototype.instantiateTemplate = function(req, res, prepareParamsFn, next) {
|
||||
var self = this;
|
||||
|
||||
var cdbuser = res.locals.user;
|
||||
|
||||
var mapConfigProvider;
|
||||
var mapConfig;
|
||||
step(
|
||||
function getTemplateParams() {
|
||||
prepareParamsFn(this);
|
||||
},
|
||||
function getTemplate(err, templateParams) {
|
||||
assert.ifError(err);
|
||||
mapConfigProvider = new NamedMapMapConfigProvider(
|
||||
self.templateMaps,
|
||||
self.pgConnection,
|
||||
self.metadataBackend,
|
||||
self.userLimitsApi,
|
||||
self.mapConfigAdapter,
|
||||
cdbuser,
|
||||
req.params.template_id,
|
||||
templateParams,
|
||||
res.locals.auth_token,
|
||||
res.locals
|
||||
);
|
||||
mapConfigProvider.getMapConfig(this);
|
||||
},
|
||||
function createLayergroup(err, mapConfig_, rendererParams) {
|
||||
assert.ifError(err);
|
||||
mapConfig = mapConfig_;
|
||||
self.mapBackend.createLayergroup(
|
||||
mapConfig, rendererParams,
|
||||
new CreateLayergroupMapConfigProvider(mapConfig, cdbuser, self.userLimitsApi, rendererParams),
|
||||
this
|
||||
);
|
||||
},
|
||||
function afterLayergroupCreate(err, layergroup) {
|
||||
assert.ifError(err);
|
||||
self.afterLayergroupCreate(req, res, mapConfig, layergroup,
|
||||
mapConfigProvider.analysesResults,
|
||||
this);
|
||||
},
|
||||
function finishTemplateInstantiation(err, layergroup) {
|
||||
if (err) {
|
||||
err.label = 'NAMED MAP LAYERGROUP';
|
||||
next(err);
|
||||
} else {
|
||||
var templateHash = self.templateMaps.fingerPrint(mapConfigProvider.template).substring(0, 8);
|
||||
layergroup.layergroupid = cdbuser + '@' + templateHash + '@' + layergroup.layergroupid;
|
||||
|
||||
var _mapConfig = mapConfig.obj();
|
||||
self.addDataviewsAndWidgetsUrls(cdbuser, layergroup, _mapConfig);
|
||||
self.addAnalysesMetadata(cdbuser, layergroup, mapConfigProvider.analysesResults);
|
||||
addContextMetadata(layergroup, _mapConfig, mapConfigProvider.context);
|
||||
|
||||
res.set('X-Layergroup-Id', layergroup.layergroupid);
|
||||
self.surrogateKeysCache.tag(res, new NamedMapsCacheEntry(cdbuser, mapConfigProvider.getTemplateName()));
|
||||
|
||||
self.send(req, res, layergroup, 200);
|
||||
}
|
||||
}
|
||||
);
|
||||
}.bind(this);
|
||||
};
|
||||
|
||||
MapController.prototype.afterLayergroupCreate =
|
||||
function(req, res, mapconfig, layergroup, analysesResults, callback) {
|
||||
var self = this;
|
||||
MapController.prototype.createLayergroup = function () {
|
||||
return function createLayergroupMiddleware (req, res, next) {
|
||||
const requestMapConfig = req.body;
|
||||
const { context, user } = res.locals;
|
||||
const datasource = context.datasource || Datasource.EmptyDatasource();
|
||||
const mapconfig = new MapConfig(requestMapConfig, datasource);
|
||||
const mapconfigProvider =
|
||||
new CreateLayergroupMapConfigProvider(mapconfig, user, this.userLimitsApi, res.locals);
|
||||
|
||||
var username = res.locals.user;
|
||||
res.locals.mapconfig = mapconfig;
|
||||
res.locals.analysesResults = context.analysesResults;
|
||||
|
||||
var tasksleft = 2; // redis key and affectedTables
|
||||
var errors = [];
|
||||
|
||||
var done = function(err) {
|
||||
if ( err ) {
|
||||
errors.push('' + err);
|
||||
}
|
||||
if ( ! --tasksleft ) {
|
||||
err = errors.length ? new Error(errors.join('\n')) : null;
|
||||
callback(err, layergroup);
|
||||
}
|
||||
};
|
||||
|
||||
// include in layergroup response the variables in serverMedata
|
||||
// those variables are useful to send to the client information
|
||||
// about how to reach this server or information about it
|
||||
_.extend(layergroup, global.environment.serverMetadata);
|
||||
|
||||
// Don't wait for the mapview count increment to
|
||||
// take place before proceeding. Error will be logged
|
||||
// asynchronously
|
||||
this.metadataBackend.incMapviewCount(username, mapconfig.obj().stat_tag, function(err) {
|
||||
req.profiler.done('incMapviewCount');
|
||||
if ( err ) {
|
||||
global.logger.log("ERROR: failed to increment mapview count for user '" + username + "': " + err);
|
||||
}
|
||||
done();
|
||||
});
|
||||
|
||||
var sql = [];
|
||||
mapconfig.getLayers().forEach(function(layer) {
|
||||
sql.push(layer.options.sql);
|
||||
if (layer.options.affected_tables) {
|
||||
layer.options.affected_tables.map(function(table) {
|
||||
sql.push('SELECT * FROM ' + table + ' LIMIT 0');
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
var dbName = res.locals.dbname;
|
||||
var layergroupId = layergroup.layergroupid;
|
||||
var dbConnection;
|
||||
|
||||
step(
|
||||
function getPgConnection() {
|
||||
self.pgConnection.getConnection(username, this);
|
||||
},
|
||||
function getAffectedTablesAndLastUpdatedTime(err, connection) {
|
||||
assert.ifError(err);
|
||||
dbConnection = connection;
|
||||
QueryTables.getAffectedTablesFromQuery(dbConnection, sql.join(';'), this);
|
||||
},
|
||||
function handleAffectedTablesAndLastUpdatedTime(err, result) {
|
||||
req.profiler.done('queryTablesAndLastUpdated');
|
||||
assert.ifError(err);
|
||||
// feed affected tables cache so it can be reused from, for instance, layergroup controller
|
||||
self.layergroupAffectedTables.set(dbName, layergroupId, result);
|
||||
|
||||
var lastUpdateTime = result.getLastUpdatedAt();
|
||||
lastUpdateTime = getLastUpdatedTime(analysesResults, lastUpdateTime) || lastUpdateTime;
|
||||
|
||||
// last update for layergroup cache buster
|
||||
layergroup.layergroupid = layergroup.layergroupid + ':' + lastUpdateTime;
|
||||
layergroup.last_updated = new Date(lastUpdateTime).toISOString();
|
||||
|
||||
if (req.method === 'GET') {
|
||||
var ttl = global.environment.varnish.layergroupTtl || 86400;
|
||||
res.set('Cache-Control', 'public,max-age='+ttl+',must-revalidate');
|
||||
res.set('Last-Modified', (new Date()).toUTCString());
|
||||
res.set('X-Cache-Channel', result.getCacheChannel());
|
||||
if (result.tables && result.tables.length > 0) {
|
||||
self.surrogateKeysCache.tag(res, result);
|
||||
}
|
||||
this.mapBackend.createLayergroup(mapconfig, res.locals, mapconfigProvider, (err, layergroup) => {
|
||||
req.profiler.done('createLayergroup');
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
function fetchLayersStats(err) {
|
||||
assert.ifError(err);
|
||||
var next = this;
|
||||
self.statsBackend.getStats(mapconfig, dbConnection, function(err, layersStats) {
|
||||
res.locals.layergroup = layergroup;
|
||||
|
||||
next();
|
||||
});
|
||||
}.bind(this);
|
||||
};
|
||||
|
||||
MapController.prototype.instantiateLayergroup = function () {
|
||||
return function instantiateLayergroupMiddleware (req, res, next) {
|
||||
const { user, mapconfig, rendererParams } = res.locals;
|
||||
const mapconfigProvider =
|
||||
new CreateLayergroupMapConfigProvider(mapconfig, user, this.userLimitsApi, rendererParams);
|
||||
|
||||
this.mapBackend.createLayergroup(mapconfig, rendererParams, mapconfigProvider, (err, layergroup) => {
|
||||
req.profiler.done('createLayergroup');
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
res.locals.layergroup = layergroup;
|
||||
|
||||
const { mapconfigProvider } = res.locals;
|
||||
|
||||
res.locals.analysesResults = mapconfigProvider.analysesResults;
|
||||
res.locals.template = mapconfigProvider.template;
|
||||
res.locals.templateName = mapconfigProvider.getTemplateName();
|
||||
res.locals.context = mapconfigProvider.context;
|
||||
|
||||
next();
|
||||
});
|
||||
}.bind(this);
|
||||
};
|
||||
|
||||
MapController.prototype.incrementMapViewCount = function () {
|
||||
return function incrementMapViewCountMiddleware(req, res, next) {
|
||||
const { mapconfig, user } = res.locals;
|
||||
|
||||
// Error won't blow up, just be logged.
|
||||
this.metadataBackend.incMapviewCount(user, mapconfig.obj().stat_tag, (err) => {
|
||||
req.profiler.done('incMapviewCount');
|
||||
|
||||
if (err) {
|
||||
global.logger.log(`ERROR: failed to increment mapview count for user '${user}': ${err.message}`);
|
||||
}
|
||||
|
||||
next();
|
||||
});
|
||||
}.bind(this);
|
||||
};
|
||||
|
||||
MapController.prototype.augmentLayergroupData = function () {
|
||||
return function augmentLayergroupDataMiddleware (req, res, next) {
|
||||
const { layergroup } = res.locals;
|
||||
|
||||
// include in layergroup response the variables in serverMedata
|
||||
// those variables are useful to send to the client information
|
||||
// about how to reach this server or information about it
|
||||
_.extend(layergroup, global.environment.serverMetadata);
|
||||
|
||||
next();
|
||||
};
|
||||
};
|
||||
|
||||
MapController.prototype.getAffectedTables = function () {
|
||||
return function getAffectedTablesMiddleware (req, res, next) {
|
||||
const { dbname, layergroup, user, mapconfig } = res.locals;
|
||||
|
||||
this.pgConnection.getConnection(user, (err, connection) => {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
const sql = [];
|
||||
mapconfig.getLayers().forEach(function(layer) {
|
||||
sql.push(layer.options.sql);
|
||||
if (layer.options.affected_tables) {
|
||||
layer.options.affected_tables.map(function(table) {
|
||||
sql.push('SELECT * FROM ' + table + ' LIMIT 0');
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
QueryTables.getAffectedTablesFromQuery(connection, sql.join(';'), (err, affectedTables) => {
|
||||
req.profiler.done('getAffectedTablesFromQuery');
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
if (layersStats.length > 0) {
|
||||
layergroup.metadata.layers.forEach(function (layer, index) {
|
||||
layer.meta.stats = layersStats[index];
|
||||
});
|
||||
}
|
||||
return next();
|
||||
|
||||
// feed affected tables cache so it can be reused from, for instance, layergroup controller
|
||||
this.layergroupAffectedTables.set(dbname, layergroup.layergroupId, affectedTables);
|
||||
|
||||
res.locals.affectedTables = affectedTables;
|
||||
|
||||
next();
|
||||
});
|
||||
},
|
||||
function finish(err) {
|
||||
done(err);
|
||||
});
|
||||
}.bind(this);
|
||||
};
|
||||
|
||||
MapController.prototype.setCacheChannel = function () {
|
||||
return function setCacheChannelMiddleware (req, res, next) {
|
||||
const { affectedTables } = res.locals;
|
||||
|
||||
if (req.method === 'GET') {
|
||||
res.set('X-Cache-Channel', affectedTables.getCacheChannel());
|
||||
}
|
||||
);
|
||||
|
||||
next();
|
||||
};
|
||||
};
|
||||
|
||||
MapController.prototype.setLastModified = function () {
|
||||
return function setLastModifiedMiddleware (req, res, next) {
|
||||
if (req.method === 'GET') {
|
||||
res.set('Last-Modified', (new Date()).toUTCString());
|
||||
}
|
||||
|
||||
next();
|
||||
};
|
||||
};
|
||||
|
||||
MapController.prototype.setLastUpdatedTimeToLayergroup = function () {
|
||||
return function setLastUpdatedTimeToLayergroupMiddleware (req, res, next) {
|
||||
const { affectedTables, layergroup, analysesResults } = res.locals;
|
||||
|
||||
var lastUpdateTime = affectedTables.getLastUpdatedAt();
|
||||
|
||||
lastUpdateTime = getLastUpdatedTime(analysesResults, lastUpdateTime) || lastUpdateTime;
|
||||
|
||||
// last update for layergroup cache buster
|
||||
layergroup.layergroupid = layergroup.layergroupid + ':' + lastUpdateTime;
|
||||
layergroup.last_updated = new Date(lastUpdateTime).toISOString();
|
||||
|
||||
next();
|
||||
};
|
||||
};
|
||||
|
||||
function getLastUpdatedTime(analysesResults, lastUpdateTime) {
|
||||
@@ -417,34 +397,66 @@ function getLastUpdatedTime(analysesResults, lastUpdateTime) {
|
||||
}, lastUpdateTime);
|
||||
}
|
||||
|
||||
MapController.prototype.addAnalysesMetadata = function(username, layergroup, analysesResults, includeQuery) {
|
||||
includeQuery = includeQuery || false;
|
||||
analysesResults = analysesResults || [];
|
||||
layergroup.metadata.analyses = [];
|
||||
MapController.prototype.setCacheControl = function () {
|
||||
return function setCacheControlMiddleware (req, res, next) {
|
||||
if (req.method === 'GET') {
|
||||
var ttl = global.environment.varnish.layergroupTtl || 86400;
|
||||
res.set('Cache-Control', 'public,max-age='+ttl+',must-revalidate');
|
||||
}
|
||||
|
||||
analysesResults.forEach(function(analysis) {
|
||||
var nodes = analysis.getNodes();
|
||||
layergroup.metadata.analyses.push({
|
||||
nodes: nodes.reduce(function(nodesIdMap, node) {
|
||||
if (node.params.id) {
|
||||
var nodeResource = layergroup.layergroupid + '/analysis/node/' + node.id();
|
||||
var nodeRepr = {
|
||||
status: node.getStatus(),
|
||||
url: this.resourceLocator.getUrls(username, nodeResource)
|
||||
};
|
||||
if (includeQuery) {
|
||||
nodeRepr.query = node.getQuery();
|
||||
}
|
||||
if (node.getStatus() === 'failed') {
|
||||
nodeRepr.error_message = node.getErrorMessage();
|
||||
}
|
||||
nodesIdMap[node.params.id] = nodeRepr;
|
||||
next();
|
||||
};
|
||||
};
|
||||
|
||||
MapController.prototype.setLayerStats = function () {
|
||||
return function setLayerStatsMiddleware(req, res, next) {
|
||||
const { user, mapconfig, layergroup } = res.locals;
|
||||
|
||||
this.pgConnection.getConnection(user, (err, connection) => {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
this.statsBackend.getStats(mapconfig, connection, function(err, layersStats) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
return nodesIdMap;
|
||||
}.bind(this), {})
|
||||
if (layersStats.length > 0) {
|
||||
layergroup.metadata.layers.forEach(function (layer, index) {
|
||||
layer.meta.stats = layersStats[index];
|
||||
});
|
||||
}
|
||||
|
||||
next();
|
||||
});
|
||||
});
|
||||
}.bind(this));
|
||||
}.bind(this);
|
||||
};
|
||||
|
||||
MapController.prototype.setLayergroupIdHeader = function (useTemplateHash) {
|
||||
return function setLayergroupIdHeaderMiddleware (req, res, next) {
|
||||
const { layergroup, user, template } = res.locals;
|
||||
|
||||
if (useTemplateHash) {
|
||||
var templateHash = this.templateMaps.fingerPrint(template).substring(0, 8);
|
||||
layergroup.layergroupid = `${user}@${templateHash}@${layergroup.layergroupid}`;
|
||||
}
|
||||
|
||||
res.set('X-Layergroup-Id', layergroup.layergroupid);
|
||||
|
||||
next();
|
||||
}.bind(this);
|
||||
};
|
||||
|
||||
MapController.prototype.setDataviewsAndWidgetsUrlsToLayergroupMetadata = function () {
|
||||
return function setDataviewsAndWidgetsUrlsToLayergroupMetadataMiddleware (req, res, next) {
|
||||
const { layergroup, user, mapconfig } = res.locals;
|
||||
|
||||
this.addDataviewsAndWidgetsUrls(user, layergroup, mapconfig.obj());
|
||||
|
||||
next();
|
||||
}.bind(this);
|
||||
};
|
||||
|
||||
// TODO this should take into account several URL patterns
|
||||
@@ -483,3 +495,131 @@ MapController.prototype.addWidgetsUrl = function(username, layergroup, mapConfig
|
||||
}.bind(this));
|
||||
}
|
||||
};
|
||||
|
||||
MapController.prototype.setAnalysesMetadataToLayergroup = function (includeQuery) {
|
||||
return function setAnalysesMetadataToLayergroupMiddleware (req, res, next) {
|
||||
const { layergroup, user, analysesResults = [] } = res.locals;
|
||||
|
||||
this.addAnalysesMetadata(user, layergroup, analysesResults, includeQuery);
|
||||
|
||||
next();
|
||||
}.bind(this);
|
||||
};
|
||||
|
||||
MapController.prototype.addAnalysesMetadata = function(username, layergroup, analysesResults, includeQuery) {
|
||||
includeQuery = includeQuery || false;
|
||||
analysesResults = analysesResults || [];
|
||||
layergroup.metadata.analyses = [];
|
||||
|
||||
analysesResults.forEach(function(analysis) {
|
||||
var nodes = analysis.getNodes();
|
||||
layergroup.metadata.analyses.push({
|
||||
nodes: nodes.reduce(function(nodesIdMap, node) {
|
||||
if (node.params.id) {
|
||||
var nodeResource = layergroup.layergroupid + '/analysis/node/' + node.id();
|
||||
var nodeRepr = {
|
||||
status: node.getStatus(),
|
||||
url: this.resourceLocator.getUrls(username, nodeResource)
|
||||
};
|
||||
if (includeQuery) {
|
||||
nodeRepr.query = node.getQuery();
|
||||
}
|
||||
if (node.getStatus() === 'failed') {
|
||||
nodeRepr.error_message = node.getErrorMessage();
|
||||
}
|
||||
nodesIdMap[node.params.id] = nodeRepr;
|
||||
}
|
||||
|
||||
return nodesIdMap;
|
||||
}.bind(this), {})
|
||||
});
|
||||
}.bind(this));
|
||||
};
|
||||
|
||||
MapController.prototype.setTurboCartoMetadataToLayergroup = function () {
|
||||
return function setTurboCartoMetadataToLayergroupMiddleware (req, res, next) {
|
||||
const { layergroup, mapconfig, context } = res.locals;
|
||||
|
||||
addContextMetadata(layergroup, mapconfig.obj(), context);
|
||||
|
||||
next();
|
||||
};
|
||||
};
|
||||
|
||||
function addContextMetadata(layergroup, mapConfig, context) {
|
||||
if (layergroup.metadata && Array.isArray(layergroup.metadata.layers) && Array.isArray(mapConfig.layers)) {
|
||||
layergroup.metadata.layers = layergroup.metadata.layers.map(function(layer, layerIndex) {
|
||||
if (context.turboCarto && Array.isArray(context.turboCarto.layers)) {
|
||||
layer.meta.cartocss_meta = context.turboCarto.layers[layerIndex];
|
||||
}
|
||||
return layer;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
MapController.prototype.setSurrogateKeyHeader = function () {
|
||||
return function setSurrogateKeyHeaderMiddleware(req, res, next) {
|
||||
const { affectedTables, user, templateName } = res.locals;
|
||||
|
||||
if (req.method === 'GET' && affectedTables.tables && affectedTables.tables.length > 0) {
|
||||
this.surrogateKeysCache.tag(res, affectedTables);
|
||||
}
|
||||
|
||||
if (templateName) {
|
||||
this.surrogateKeysCache.tag(res, new NamedMapsCacheEntry(user, templateName));
|
||||
}
|
||||
|
||||
next();
|
||||
}.bind(this);
|
||||
};
|
||||
|
||||
MapController.prototype.sendResponse = function () {
|
||||
return function sendResponseMiddleware (req, res) {
|
||||
req.profiler.done('res');
|
||||
const { layergroup } = res.locals;
|
||||
|
||||
res.status(200);
|
||||
|
||||
if (req.query && req.query.callback) {
|
||||
res.jsonp(layergroup);
|
||||
} else {
|
||||
res.json(layergroup);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
MapController.prototype.augmentError = function (options) {
|
||||
const { addContext = false, label = 'MAPS CONTROLLER' } = options;
|
||||
|
||||
return function augmentErrorMiddleware (err, req, res, next) {
|
||||
req.profiler.done('error');
|
||||
const { mapconfig } = res.locals;
|
||||
|
||||
if (addContext) {
|
||||
err = Number.isFinite(err.layerIndex) ? populateError(err, mapconfig) : err;
|
||||
}
|
||||
|
||||
err.label = label;
|
||||
|
||||
next(err);
|
||||
};
|
||||
};
|
||||
|
||||
function populateError(err, mapConfig) {
|
||||
var error = new Error(err.message);
|
||||
error.http_status = err.http_status;
|
||||
|
||||
if (!err.http_status && err.message.indexOf('column "the_geom_webmercator" does not exist') >= 0) {
|
||||
error.http_status = 400;
|
||||
}
|
||||
|
||||
error.type = 'layer';
|
||||
error.subtype = err.message.indexOf('Postgis Plugin') >= 0 ? 'query' : undefined;
|
||||
error.layer = {
|
||||
id: mapConfig.getLayerId(err.layerIndex),
|
||||
index: err.layerIndex,
|
||||
type: mapConfig.layerType(err.layerIndex)
|
||||
};
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
@@ -3,17 +3,13 @@ var assert = require('assert');
|
||||
var _ = require('underscore');
|
||||
var NamedMapsCacheEntry = require('../cache/model/named_maps_entry');
|
||||
|
||||
var util = require('util');
|
||||
var BaseController = require('./base');
|
||||
|
||||
var cors = require('../middleware/cors');
|
||||
var userMiddleware = require('../middleware/user');
|
||||
var allowQueryParams = require('../middleware/allow-query-params');
|
||||
var vectorError = require('../middleware/vector-error');
|
||||
|
||||
function NamedMapsController(prepareContext, namedMapProviderCache, tileBackend, previewBackend,
|
||||
surrogateKeysCache, tablesExtentApi, metadataBackend) {
|
||||
BaseController.call(this);
|
||||
|
||||
this.namedMapProviderCache = namedMapProviderCache;
|
||||
this.tileBackend = tileBackend;
|
||||
this.previewBackend = previewBackend;
|
||||
@@ -23,8 +19,6 @@ function NamedMapsController(prepareContext, namedMapProviderCache, tileBackend,
|
||||
this.prepareContext = prepareContext;
|
||||
}
|
||||
|
||||
util.inherits(NamedMapsController, BaseController);
|
||||
|
||||
module.exports = NamedMapsController;
|
||||
|
||||
NamedMapsController.prototype.register = function(app) {
|
||||
@@ -33,7 +27,8 @@ NamedMapsController.prototype.register = function(app) {
|
||||
cors(),
|
||||
userMiddleware,
|
||||
this.prepareContext,
|
||||
this.tile.bind(this)
|
||||
this.tile.bind(this),
|
||||
vectorError()
|
||||
);
|
||||
|
||||
app.get(
|
||||
@@ -46,7 +41,7 @@ NamedMapsController.prototype.register = function(app) {
|
||||
);
|
||||
};
|
||||
|
||||
NamedMapsController.prototype.sendResponse = function(req, res, resource, headers, namedMapProvider) {
|
||||
NamedMapsController.prototype.sendResponse = function(req, res, body, headers, namedMapProvider) {
|
||||
this.surrogateKeysCache.tag(res, new NamedMapsCacheEntry(res.locals.user, namedMapProvider.getTemplateName()));
|
||||
res.set('Content-Type', headers['content-type'] || headers['Content-Type'] || 'image/png');
|
||||
res.set('Cache-Control', 'public,max-age=7200,must-revalidate');
|
||||
@@ -79,7 +74,8 @@ NamedMapsController.prototype.sendResponse = function(req, res, resource, header
|
||||
self.surrogateKeysCache.tag(res, result);
|
||||
}
|
||||
}
|
||||
self.send(req, res, resource, 200);
|
||||
res.status(200);
|
||||
res.send(body);
|
||||
}
|
||||
);
|
||||
};
|
||||
@@ -108,6 +104,7 @@ NamedMapsController.prototype.tile = function(req, res, next) {
|
||||
},
|
||||
function handleImage(err, tile, headers, stats) {
|
||||
req.profiler.add(stats);
|
||||
|
||||
if (err) {
|
||||
err.label = 'NAMED_MAP_TILE';
|
||||
next(err);
|
||||
@@ -124,7 +121,9 @@ NamedMapsController.prototype.staticMap = function(req, res, next) {
|
||||
var cdbUser = res.locals.user;
|
||||
|
||||
var format = req.params.format === 'jpg' ? 'jpeg' : 'png';
|
||||
res.locals.format = req.params.format || 'png';
|
||||
// We force always the tile to be generated using PNG because
|
||||
// is the only format we support by now
|
||||
res.locals.format = 'png';
|
||||
res.locals.layer = res.locals.layer || 'all';
|
||||
|
||||
var namedMapProvider;
|
||||
@@ -143,7 +142,7 @@ NamedMapsController.prototype.staticMap = function(req, res, next) {
|
||||
assert.ifError(err);
|
||||
|
||||
namedMapProvider = _namedMapProvider;
|
||||
|
||||
|
||||
self.prepareLayerFilterFromPreviewLayers(cdbUser, req, res.locals, namedMapProvider, this);
|
||||
},
|
||||
function prepareImageOptions(err) {
|
||||
@@ -192,10 +191,10 @@ NamedMapsController.prototype.staticMap = function(req, res, next) {
|
||||
};
|
||||
|
||||
NamedMapsController.prototype.prepareLayerFilterFromPreviewLayers = function (
|
||||
user,
|
||||
req,
|
||||
params,
|
||||
namedMapProvider,
|
||||
user,
|
||||
req,
|
||||
params,
|
||||
namedMapProvider,
|
||||
callback
|
||||
) {
|
||||
var self = this;
|
||||
|
||||
@@ -2,9 +2,6 @@ var step = require('step');
|
||||
var assert = require('assert');
|
||||
var templateName = require('../backends/template_maps').templateName;
|
||||
|
||||
var util = require('util');
|
||||
var BaseController = require('./base');
|
||||
|
||||
var cors = require('../middleware/cors');
|
||||
var userMiddleware = require('../middleware/user');
|
||||
|
||||
@@ -16,14 +13,10 @@ var userMiddleware = require('../middleware/user');
|
||||
* @constructor
|
||||
*/
|
||||
function NamedMapsAdminController(authApi, templateMaps) {
|
||||
BaseController.call(this);
|
||||
|
||||
this.authApi = authApi;
|
||||
this.templateMaps = templateMaps;
|
||||
}
|
||||
|
||||
util.inherits(NamedMapsAdminController, BaseController);
|
||||
|
||||
module.exports = NamedMapsAdminController;
|
||||
|
||||
NamedMapsAdminController.prototype.register = function (app) {
|
||||
@@ -206,12 +199,18 @@ NamedMapsAdminController.prototype.list = function(req, res, next) {
|
||||
};
|
||||
|
||||
function finishFn(controller, req, res, description, status, next) {
|
||||
return function finish(err, response){
|
||||
return function finish(err, body){
|
||||
if (err) {
|
||||
err.label = description;
|
||||
next(err);
|
||||
} else {
|
||||
controller.send(req, res, response, status || 200);
|
||||
res.status(status || 200);
|
||||
|
||||
if (req.query && req.query.callback) {
|
||||
res.jsonp(body);
|
||||
} else {
|
||||
res.json(body);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
const _ = require('underscore');
|
||||
|
||||
module.exports = function localsMiddleware(req, res, next) {
|
||||
_.extend(res.locals, req.params);
|
||||
|
||||
// save req.params in res.locals
|
||||
res.locals = Object.assign(req.params || {}, res.locals);
|
||||
|
||||
next();
|
||||
};
|
||||
|
||||
|
||||
@@ -4,11 +4,5 @@ var cdbRequest = new CdbRequest();
|
||||
module.exports = function userMiddleware(req, res, next) {
|
||||
res.locals.user = cdbRequest.userByReq(req);
|
||||
|
||||
// avoid a req.params.user equals to undefined
|
||||
// overwrites res.locals.user
|
||||
if(!req.params.user) {
|
||||
delete req.params.user;
|
||||
}
|
||||
|
||||
next();
|
||||
};
|
||||
|
||||
30
lib/cartodb/middleware/vector-error.js
Normal file
30
lib/cartodb/middleware/vector-error.js
Normal file
@@ -0,0 +1,30 @@
|
||||
const fs = require('fs');
|
||||
|
||||
const timeoutErrorVectorTile = fs.readFileSync(__dirname + '/../../../assets/render-timeout-fallback.mvt');
|
||||
|
||||
module.exports = function vectorError() {
|
||||
return function vectorErrorMiddleware(err, req, res, next) {
|
||||
if(req.params.format === 'mvt') {
|
||||
|
||||
if (isTimeoutError(err)) {
|
||||
res.set('Content-Type', 'application/x-protobuf');
|
||||
return res.status(429).send(timeoutErrorVectorTile);
|
||||
}
|
||||
}
|
||||
|
||||
next(err);
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
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);
|
||||
}
|
||||
@@ -245,6 +245,10 @@ module.exports = class Aggregation extends BaseDataview {
|
||||
return null;
|
||||
}
|
||||
|
||||
const limit = Number.isFinite(override.categories) && override.categories > 0 ?
|
||||
override.categories :
|
||||
CATEGORIES_LIMIT;
|
||||
|
||||
const aggregationSql = aggregationDataviewQueryTpl({
|
||||
override: override,
|
||||
query: this.query,
|
||||
@@ -256,7 +260,7 @@ module.exports = class Aggregation extends BaseDataview {
|
||||
aggregationColumn: this.aggregationColumn || 1
|
||||
}),
|
||||
isFloatColumn: this._isFloatColumn,
|
||||
limit: CATEGORIES_LIMIT
|
||||
limit
|
||||
});
|
||||
|
||||
debug(aggregationSql);
|
||||
|
||||
@@ -1,34 +1,14 @@
|
||||
const BaseDataview = require('./base');
|
||||
const debug = require('debug')('windshaft:dataview:formula');
|
||||
const utils = require('../../utils/query-utils');
|
||||
|
||||
const countInfinitiesQueryTpl = ctx => `
|
||||
SELECT count(1) FROM (${ctx.query}) __cdb_formula_infinities
|
||||
WHERE ${ctx.column} = 'infinity'::float OR ${ctx.column} = '-infinity'::float
|
||||
`;
|
||||
|
||||
const countNansQueryTpl = ctx => `
|
||||
SELECT count(1) FROM (${ctx.query}) __cdb_formula_nans
|
||||
WHERE ${ctx.column} = 'NaN'::float
|
||||
`;
|
||||
|
||||
const filterOutSpecialNumericValuesTpl = ctx => `
|
||||
WHERE
|
||||
${ctx.column} != 'infinity'::float
|
||||
AND
|
||||
${ctx.column} != '-infinity'::float
|
||||
AND
|
||||
${ctx.column} != 'NaN'::float
|
||||
`;
|
||||
|
||||
const formulaQueryTpl = ctx => `
|
||||
SELECT
|
||||
${ctx.operation}(${ctx.column}) AS result,
|
||||
(SELECT count(1) FROM (${ctx.query}) _cdb_formula_nulls WHERE ${ctx.column} IS NULL) AS nulls_count
|
||||
${ctx.isFloatColumn ? `,(${countInfinitiesQueryTpl(ctx)}) AS infinities_count` : ''}
|
||||
${ctx.isFloatColumn ? `,(${countNansQueryTpl(ctx)}) AS nans_count` : ''}
|
||||
FROM (${ctx.query}) __cdb_formula
|
||||
${ctx.isFloatColumn && ctx.operation !== 'count' ? `${filterOutSpecialNumericValuesTpl(ctx)}` : ''}
|
||||
`;
|
||||
const formulaQueryTpl = ctx =>
|
||||
`SELECT
|
||||
${ctx.operation}(${utils.handleFloatColumn(ctx)}) AS result,
|
||||
${utils.countNULLs(ctx)} AS nulls_count
|
||||
${ctx.isFloatColumn ? `,${utils.countInfinites(ctx)} AS infinities_count,` : ``}
|
||||
${ctx.isFloatColumn ? `${utils.countNaNs(ctx)} AS nans_count` : ``}
|
||||
FROM (${ctx.query}) __cdb_formula`;
|
||||
|
||||
const VALID_OPERATIONS = {
|
||||
count: true,
|
||||
|
||||
@@ -1,5 +1,102 @@
|
||||
const BaseHistogram = require('./base-histogram');
|
||||
const debug = require('debug')('windshaft:dataview:date-histogram');
|
||||
const utils = require('../../../utils/query-utils');
|
||||
|
||||
/**
|
||||
* Gets the name of a timezone with the same offset as the required
|
||||
* using the pg_timezone_names table. We do this because it's simpler to pass
|
||||
* the name than to pass the offset itself as PostgreSQL uses different
|
||||
* sign convention. For example: TIME ZONE 'CET' is equal to TIME ZONE 'UTC-1',
|
||||
* not 'UTC+1' which would be expected.
|
||||
* Gives priority to Etc/GMT±N timezones but still support odd offsets like 8.5
|
||||
* hours for Asia/Pyongyang.
|
||||
* It also makes it easier to, in the future, support the input of expected timezone
|
||||
* instead of the offset; that is using 'Europe/Madrid' instead of
|
||||
* '+3600' or '+7200'. The daylight saving status can be handled by postgres.
|
||||
*/
|
||||
const offsetNameQueryTpl = ctx => `
|
||||
WITH __wd_tz AS
|
||||
(
|
||||
SELECT name
|
||||
FROM pg_timezone_names
|
||||
WHERE utc_offset = interval '${ctx.offset} hours'
|
||||
ORDER BY CASE WHEN name LIKE 'Etc/GMT%' THEN 0 ELSE 1 END
|
||||
LIMIT 1
|
||||
),`;
|
||||
|
||||
/**
|
||||
* Function to get the subquery that places each row in its bin depending on
|
||||
* the aggregation. Since the data stored is in epoch we need to adapt it to
|
||||
* our timezone so when calling date_trunc it falls into the correct bin
|
||||
*/
|
||||
function dataBucketsQuery(ctx) {
|
||||
var condition_str = '';
|
||||
|
||||
if (ctx.start !== 0) {
|
||||
condition_str = `WHERE ${ctx.column} >= to_timestamp(${ctx.start})`;
|
||||
}
|
||||
if (ctx.end !== 0) {
|
||||
if (condition_str === '') {
|
||||
condition_str = `WHERE ${ctx.column} <= to_timestamp(${ctx.end})`;
|
||||
}
|
||||
else {
|
||||
condition_str += ` and ${ctx.column} <= to_timestamp(${ctx.end})`;
|
||||
}
|
||||
}
|
||||
|
||||
return `
|
||||
__wd_buckets AS
|
||||
(
|
||||
SELECT
|
||||
date_trunc('${ctx.aggregation}', timezone(__wd_tz.name, ${ctx.column}::timestamptz)) as timestamp,
|
||||
count(*) as freq,
|
||||
${utils.countNULLs(ctx)} as nulls_count
|
||||
FROM
|
||||
(
|
||||
${ctx.query}
|
||||
) __source, __wd_tz
|
||||
${condition_str}
|
||||
GROUP BY timestamp, __wd_tz.name
|
||||
),`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Function that generates an array with all the possible bins between the
|
||||
* start and end date. If not provided we use the min and max generated from
|
||||
* the dataBucketsQuery
|
||||
*/
|
||||
function allBucketsArrayQuery(ctx) {
|
||||
var extra_from = ``;
|
||||
var series_start = ``;
|
||||
var series_end = ``;
|
||||
|
||||
if (ctx.start === 0) {
|
||||
extra_from = `, __wd_buckets GROUP BY __wd_tz.name`;
|
||||
series_start = `min(__wd_buckets.timestamp)`;
|
||||
} else {
|
||||
series_start = `date_trunc('${ctx.aggregation}', timezone(__wd_tz.name, to_timestamp(${ctx.start})))`;
|
||||
}
|
||||
|
||||
if (ctx.end === 0) {
|
||||
extra_from = `, __wd_buckets GROUP BY __wd_tz.name`;
|
||||
series_end = `max(__wd_buckets.timestamp)`;
|
||||
} else {
|
||||
series_end = `date_trunc('${ctx.aggregation}', timezone(__wd_tz.name, to_timestamp(${ctx.end})))`;
|
||||
}
|
||||
|
||||
return `
|
||||
__wd_all_buckets AS
|
||||
(
|
||||
SELECT ARRAY(
|
||||
SELECT
|
||||
generate_series(
|
||||
${series_start},
|
||||
${series_end},
|
||||
interval '${ctx.interval}') as bin_start
|
||||
FROM __wd_tz${extra_from}
|
||||
) as bins
|
||||
)`;
|
||||
}
|
||||
|
||||
const dateIntervalQueryTpl = ctx => `
|
||||
WITH
|
||||
@@ -41,107 +138,6 @@ const dateIntervalQueryTpl = ctx => `
|
||||
FROM __cdb_interval_in_days, __cdb_interval_in_hours, __cdb_interval_in_minutes, __cdb_interval_in_seconds
|
||||
`;
|
||||
|
||||
const nullsQueryTpl = ctx => `
|
||||
__cdb_nulls AS (
|
||||
SELECT
|
||||
count(*) AS __cdb_nulls_count
|
||||
FROM (${ctx.query}) __cdb_histogram_nulls
|
||||
WHERE ${ctx.column} IS NULL
|
||||
)
|
||||
`;
|
||||
|
||||
const dateBasicsQueryTpl = ctx => `
|
||||
__cdb_basics AS (
|
||||
SELECT
|
||||
max(date_part('epoch', ${ctx.column})) AS __cdb_max_val,
|
||||
min(date_part('epoch', ${ctx.column})) AS __cdb_min_val,
|
||||
avg(date_part('epoch', ${ctx.column})) AS __cdb_avg_val,
|
||||
min(
|
||||
date_trunc(
|
||||
'${ctx.aggregation}', ${ctx.column}::timestamp AT TIME ZONE '${ctx.offset}'
|
||||
)
|
||||
) AS __cdb_start_date,
|
||||
max(${ctx.column}::timestamp AT TIME ZONE '${ctx.offset}') AS __cdb_end_date,
|
||||
count(1) AS __cdb_total_rows
|
||||
FROM (${ctx.query}) __cdb_basics_query
|
||||
)
|
||||
`;
|
||||
|
||||
const dateOverrideBasicsQueryTpl = ctx => `
|
||||
__cdb_basics AS (
|
||||
SELECT
|
||||
max(${ctx.end})::float AS __cdb_max_val,
|
||||
min(${ctx.start})::float AS __cdb_min_val,
|
||||
avg(date_part('epoch', ${ctx.column})) AS __cdb_avg_val,
|
||||
min(
|
||||
date_trunc(
|
||||
'${ctx.aggregation}',
|
||||
TO_TIMESTAMP(${ctx.start})::timestamp AT TIME ZONE '${ctx.offset}'
|
||||
)
|
||||
) AS __cdb_start_date,
|
||||
max(
|
||||
TO_TIMESTAMP(${ctx.end})::timestamp AT TIME ZONE '${ctx.offset}'
|
||||
) AS __cdb_end_date,
|
||||
count(1) AS __cdb_total_rows
|
||||
FROM (${ctx.query}) __cdb_basics_query
|
||||
)
|
||||
`;
|
||||
|
||||
const dateBinsQueryTpl = ctx => `
|
||||
__cdb_bins AS (
|
||||
SELECT
|
||||
__cdb_bins_array,
|
||||
ARRAY_LENGTH(__cdb_bins_array, 1) AS __cdb_bins_number
|
||||
FROM (
|
||||
SELECT
|
||||
ARRAY(
|
||||
SELECT GENERATE_SERIES(
|
||||
__cdb_start_date::timestamptz,
|
||||
__cdb_end_date::timestamptz,
|
||||
${ctx.aggregation === 'quarter' ? `'3 month'::interval` : `'1 ${ctx.aggregation}'::interval`}
|
||||
)
|
||||
) AS __cdb_bins_array
|
||||
FROM __cdb_basics
|
||||
) __cdb_bins_array_query
|
||||
)
|
||||
`;
|
||||
|
||||
const dateHistogramQueryTpl = ctx => `
|
||||
SELECT
|
||||
(__cdb_max_val - __cdb_min_val) / cast(__cdb_bins_number as float) AS bin_width,
|
||||
__cdb_bins_number AS bins_number,
|
||||
__cdb_nulls_count AS nulls_count,
|
||||
CASE WHEN __cdb_min_val = __cdb_max_val
|
||||
THEN 0
|
||||
ELSE GREATEST(
|
||||
1,
|
||||
LEAST(
|
||||
WIDTH_BUCKET(
|
||||
${ctx.column}::timestamp AT TIME ZONE '${ctx.offset}',
|
||||
__cdb_bins_array
|
||||
),
|
||||
__cdb_bins_number
|
||||
)
|
||||
) - 1
|
||||
END AS bin,
|
||||
min(
|
||||
date_part(
|
||||
'epoch',
|
||||
date_trunc(
|
||||
'${ctx.aggregation}', ${ctx.column}::timestamp AT TIME ZONE '${ctx.offset}'
|
||||
) AT TIME ZONE '${ctx.offset}'
|
||||
)
|
||||
)::numeric AS timestamp,
|
||||
date_part('epoch', __cdb_start_date)::numeric AS timestamp_start,
|
||||
min(date_part('epoch', ${ctx.column}))::numeric AS min,
|
||||
max(date_part('epoch', ${ctx.column}))::numeric AS max,
|
||||
avg(date_part('epoch', ${ctx.column}))::numeric AS avg,
|
||||
count(*) AS freq
|
||||
FROM (${ctx.query}) __cdb_histogram, __cdb_basics, __cdb_bins, __cdb_nulls
|
||||
WHERE date_part('epoch', ${ctx.column}) IS NOT NULL
|
||||
GROUP BY bin, bins_number, bin_width, nulls_count, timestamp_start
|
||||
ORDER BY bin
|
||||
`;
|
||||
|
||||
const MAX_INTERVAL_VALUE = 366;
|
||||
|
||||
@@ -176,12 +172,21 @@ module.exports = class DateHistogram extends BaseHistogram {
|
||||
|
||||
_buildQueryTpl (ctx) {
|
||||
return `
|
||||
WITH
|
||||
${this._hasOverridenRange(ctx.override) ? dateOverrideBasicsQueryTpl(ctx) : dateBasicsQueryTpl(ctx)},
|
||||
${dateBinsQueryTpl(ctx)},
|
||||
${nullsQueryTpl(ctx)}
|
||||
${dateHistogramQueryTpl(ctx)}
|
||||
`;
|
||||
${offsetNameQueryTpl(ctx)}
|
||||
${dataBucketsQuery(ctx)}
|
||||
${allBucketsArrayQuery(ctx)}
|
||||
SELECT
|
||||
array_position(__wd_all_buckets.bins, __wd_buckets.timestamp) - 1 as bin,
|
||||
date_part('epoch', timezone(__wd_tz.name, __wd_buckets.timestamp)) AS timestamp,
|
||||
__wd_buckets.freq as freq,
|
||||
date_part('epoch', timezone(__wd_tz.name, (__wd_all_buckets.bins)[1])) as timestamp_start,
|
||||
array_length(__wd_all_buckets.bins, 1) as bins_number,
|
||||
date_part('epoch', interval '${ctx.interval}') as bin_width,
|
||||
__wd_buckets.nulls_count as nulls_count
|
||||
FROM __wd_buckets, __wd_all_buckets, __wd_tz
|
||||
GROUP BY __wd_tz.name, __wd_all_buckets.bins, __wd_buckets.timestamp, __wd_buckets.nulls_count, __wd_buckets.freq
|
||||
ORDER BY bin ASC;
|
||||
`;
|
||||
}
|
||||
|
||||
_buildQuery (psql, override, callback) {
|
||||
@@ -204,6 +209,9 @@ module.exports = class DateHistogram extends BaseHistogram {
|
||||
return null;
|
||||
}
|
||||
|
||||
var interval = this._getAggregation(override) === 'quarter' ?
|
||||
'3 months' : '1 ' + this._getAggregation(override);
|
||||
|
||||
const histogramSql = this._buildQueryTpl({
|
||||
override: override,
|
||||
query: this.query,
|
||||
@@ -211,7 +219,8 @@ module.exports = class DateHistogram extends BaseHistogram {
|
||||
aggregation: this._getAggregation(override),
|
||||
start: this._getBinStart(override),
|
||||
end: this._getBinEnd(override),
|
||||
offset: this._parseOffset(override)
|
||||
offset: this._parseOffset(override),
|
||||
interval: interval
|
||||
});
|
||||
|
||||
debug(histogramSql);
|
||||
@@ -264,8 +273,8 @@ module.exports = class DateHistogram extends BaseHistogram {
|
||||
offset: this._getOffset(override),
|
||||
timestamp_start: firstRow.timestamp_start,
|
||||
|
||||
bin_width: firstRow.bin_width,
|
||||
bins_count: firstRow.bins_number,
|
||||
bin_width: firstRow.bin_width || 0,
|
||||
bins_count: firstRow.bins_number || 0,
|
||||
bins_start: firstRow.timestamp,
|
||||
nulls: firstRow.nulls_count,
|
||||
infinities: firstRow.infinities_count,
|
||||
@@ -275,6 +284,10 @@ module.exports = class DateHistogram extends BaseHistogram {
|
||||
}
|
||||
|
||||
_getBuckets (result) {
|
||||
result.rows.forEach(function(row) {
|
||||
row.min = row.max = row.avg = row.timestamp;
|
||||
});
|
||||
|
||||
return result.rows.map(({ bin, min, max, avg, freq, timestamp }) => ({ bin, min, max, avg, freq, timestamp }));
|
||||
}
|
||||
|
||||
|
||||
@@ -1,44 +1,25 @@
|
||||
const BaseHistogram = require('./base-histogram');
|
||||
const debug = require('debug')('windshaft:dataview:numeric-histogram');
|
||||
const utils = require('../../../utils/query-utils');
|
||||
|
||||
const columnCastTpl = ctx => `date_part('epoch', ${ctx.column})`;
|
||||
|
||||
const filterOutSpecialNumericValues = ctx => `
|
||||
${ctx.column} != 'infinity'::float
|
||||
AND
|
||||
${ctx.column} != '-infinity'::float
|
||||
AND
|
||||
${ctx.column} != 'NaN'::float
|
||||
`;
|
||||
|
||||
const filteredQueryTpl = ctx => `
|
||||
/** Query to get min and max values from the query */
|
||||
const irqQueryTpl = ctx => `
|
||||
__cdb_filtered_source AS (
|
||||
SELECT *
|
||||
FROM (${ctx.query}) __cdb_filtered_source_query
|
||||
WHERE ${ctx.column} IS NOT NULL
|
||||
${ctx.isFloatColumn ? `AND ${filterOutSpecialNumericValues(ctx)}` : ''}
|
||||
)
|
||||
`;
|
||||
|
||||
const basicsQueryTpl = ctx => `
|
||||
WHERE ${utils.handleFloatColumn(ctx)} IS NOT NULL
|
||||
),
|
||||
__cdb_basics AS (
|
||||
SELECT
|
||||
max(${ctx.column}) AS __cdb_max_val, min(${ctx.column}) AS __cdb_min_val,
|
||||
avg(${ctx.column}) AS __cdb_avg_val, count(1) AS __cdb_total_rows
|
||||
max(${ctx.column}) AS __cdb_max_val,
|
||||
min(${ctx.column}) AS __cdb_min_val,
|
||||
count(1) AS __cdb_total_rows
|
||||
FROM __cdb_filtered_source
|
||||
)
|
||||
`;
|
||||
|
||||
const overrideBasicsQueryTpl = ctx => `
|
||||
__cdb_basics AS (
|
||||
SELECT
|
||||
max(${ctx.end}) AS __cdb_max_val, min(${ctx.start}) AS __cdb_min_val,
|
||||
avg(${ctx.column}) AS __cdb_avg_val, count(1) AS __cdb_total_rows
|
||||
FROM __cdb_filtered_source
|
||||
)
|
||||
`;
|
||||
|
||||
const iqrQueryTpl = ctx => `
|
||||
/* Query to calculate the number of bins (needs irqQueryTpl before it*/
|
||||
const binsQueryTpl = ctx => `
|
||||
__cdb_iqrange AS (
|
||||
SELECT max(quartile_max) - min(quartile_max) AS __cdb_iqr
|
||||
FROM (
|
||||
@@ -49,10 +30,7 @@ const iqrQueryTpl = ctx => `
|
||||
WHERE quartile = 1 or quartile = 3
|
||||
GROUP BY quartile
|
||||
) __cdb_iqr
|
||||
)
|
||||
`;
|
||||
|
||||
const binsQueryTpl = ctx => `
|
||||
),
|
||||
__cdb_bins AS (
|
||||
SELECT
|
||||
CASE WHEN __cdb_total_rows = 0 OR __cdb_iqr = 0
|
||||
@@ -70,83 +48,6 @@ const binsQueryTpl = ctx => `
|
||||
)
|
||||
`;
|
||||
|
||||
const overrideBinsQueryTpl = ctx => `
|
||||
__cdb_bins AS (
|
||||
SELECT ${ctx.override.bins} AS __cdb_bins_number
|
||||
)
|
||||
`;
|
||||
|
||||
const nullsQueryTpl = ctx => `
|
||||
__cdb_nulls AS (
|
||||
SELECT
|
||||
count(*) AS __cdb_nulls_count
|
||||
FROM (${ctx.query}) __cdb_histogram_nulls
|
||||
WHERE ${ctx.column} IS NULL
|
||||
)
|
||||
`;
|
||||
|
||||
const infinitiesQueryTpl = ctx => `
|
||||
__cdb_infinities AS (
|
||||
SELECT
|
||||
count(*) AS __cdb_infinities_count
|
||||
FROM (${ctx.query}) __cdb_infinities_query
|
||||
WHERE
|
||||
${ctx.column} = 'infinity'::float
|
||||
OR
|
||||
${ctx.column} = '-infinity'::float
|
||||
)
|
||||
`;
|
||||
|
||||
const nansQueryTpl = ctx => `
|
||||
__cdb_nans AS (
|
||||
SELECT
|
||||
count(*) AS __cdb_nans_count
|
||||
FROM (${ctx.query}) __cdb_nans_query
|
||||
WHERE ${ctx.column} = 'NaN'::float
|
||||
)
|
||||
`;
|
||||
|
||||
const specialNumericValuesColumnDefinitionTpl = () => `
|
||||
__cdb_infinities_count AS infinities_count,
|
||||
__cdb_nans_count AS nans_count
|
||||
`;
|
||||
|
||||
const specialNumericValuesCTETpl = () => `
|
||||
__cdb_infinities, __cdb_nans
|
||||
`;
|
||||
|
||||
const specialNumericValuesColumnTpl = () => `
|
||||
infinities_count, nans_count
|
||||
`;
|
||||
|
||||
const histogramQueryTpl = ctx => `
|
||||
SELECT
|
||||
(__cdb_max_val - __cdb_min_val) / cast(__cdb_bins_number as float) AS bin_width,
|
||||
__cdb_bins_number AS bins_number,
|
||||
__cdb_nulls_count AS nulls_count,
|
||||
${ctx.isFloatColumn ? `${specialNumericValuesColumnDefinitionTpl()},` : ''}
|
||||
__cdb_avg_val AS avg_val,
|
||||
CASE WHEN __cdb_min_val = __cdb_max_val
|
||||
THEN 0
|
||||
ELSE GREATEST(
|
||||
1,
|
||||
LEAST(
|
||||
WIDTH_BUCKET(${ctx.column}, __cdb_min_val, __cdb_max_val, __cdb_bins_number),
|
||||
__cdb_bins_number
|
||||
)
|
||||
) - 1
|
||||
END AS bin,
|
||||
min(${ctx.column})::numeric AS min,
|
||||
max(${ctx.column})::numeric AS max,
|
||||
avg(${ctx.column})::numeric AS avg,
|
||||
count(*) AS freq
|
||||
FROM __cdb_filtered_source, __cdb_basics, __cdb_nulls, __cdb_bins
|
||||
${ctx.isFloatColumn ? `, ${specialNumericValuesCTETpl()}` : ''}
|
||||
GROUP BY bin, bins_number, bin_width, nulls_count, avg_val
|
||||
${ctx.isFloatColumn ? `, ${specialNumericValuesColumnTpl()}` : ''}
|
||||
ORDER BY bin
|
||||
`;
|
||||
|
||||
const BIN_MIN_NUMBER = 6;
|
||||
const BIN_MAX_NUMBER = 48;
|
||||
|
||||
@@ -167,14 +68,14 @@ module.exports = class NumericHistogram extends BaseHistogram {
|
||||
|
||||
_buildQuery (psql, override, callback) {
|
||||
const histogramSql = this._buildQueryTpl({
|
||||
override: override,
|
||||
column: this._columnType === 'date' ? columnCastTpl({ column: this.column }) : this.column,
|
||||
column: this._columnType === 'date' ? utils.columnCastTpl({ column: this.column }) : this.column,
|
||||
isFloatColumn: this._columnType === 'float',
|
||||
query: this.query,
|
||||
start: this._getBinStart(override),
|
||||
end: this._getBinEnd(override),
|
||||
bins: this._getBinsCount(override),
|
||||
minBins: BIN_MIN_NUMBER,
|
||||
maxBins: BIN_MAX_NUMBER,
|
||||
maxBins: BIN_MAX_NUMBER
|
||||
});
|
||||
|
||||
debug(histogramSql);
|
||||
@@ -182,19 +83,62 @@ module.exports = class NumericHistogram extends BaseHistogram {
|
||||
return callback(null, histogramSql);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* ctx: Object with the following values
|
||||
* ctx.column -- Column for the histogram
|
||||
* ctx.isFloatColumn - Whether the column is float or not
|
||||
* ctx.query -- Subquery to extract data
|
||||
* ctx.start -- Start value for the bins. [>= end to force calculation]
|
||||
* ctx.end -- End value for the bins.
|
||||
* ctx.bins -- Numbers of bins to generate [<0 to force calculation]
|
||||
* ctx.minBins - If !full min bins to calculate [Optional]
|
||||
* ctx.maxBins - If !full max bins to calculate [Optional]
|
||||
*/
|
||||
_buildQueryTpl (ctx) {
|
||||
var extra_tables = ``;
|
||||
var extra_queries = ``;
|
||||
var extra_groupby = ``;
|
||||
|
||||
if (ctx.start >= ctx.end) {
|
||||
ctx.end = `__cdb_basics.__cdb_max_val`;
|
||||
ctx.start = `__cdb_basics.__cdb_min_val`;
|
||||
extra_groupby = `, __cdb_basics.__cdb_max_val, __cdb_basics.__cdb_min_val`;
|
||||
extra_tables = `, __cdb_basics`;
|
||||
extra_queries = `WITH ${irqQueryTpl(ctx)}`;
|
||||
}
|
||||
|
||||
if (ctx.bins <= 0) {
|
||||
ctx.bins = `__cdb_bins.__cdb_bins_number`;
|
||||
extra_groupby += `, __cdb_bins.__cdb_bins_number`;
|
||||
extra_tables += `, __cdb_bins`;
|
||||
extra_queries = `WITH ${irqQueryTpl(ctx)}, ${binsQueryTpl(ctx)}`;
|
||||
}
|
||||
|
||||
return `
|
||||
WITH
|
||||
${filteredQueryTpl(ctx)},
|
||||
${this._hasOverridenRange(ctx.override) ? overrideBasicsQueryTpl(ctx) : basicsQueryTpl(ctx)},
|
||||
${this._hasOverridenBins(ctx.override) ?
|
||||
overrideBinsQueryTpl(ctx) :
|
||||
`${iqrQueryTpl(ctx)}, ${binsQueryTpl(ctx)}`
|
||||
},
|
||||
${nullsQueryTpl(ctx)}
|
||||
${ctx.isFloatColumn ? `,${infinitiesQueryTpl(ctx)}, ${nansQueryTpl(ctx)}` : ''}
|
||||
${histogramQueryTpl(ctx)}
|
||||
`;
|
||||
${extra_queries}
|
||||
SELECT
|
||||
(${ctx.end} - ${ctx.start}) / ${ctx.bins}::float AS bin_width,
|
||||
${ctx.bins} as bins_number,
|
||||
${utils.countNULLs(ctx)} AS nulls_count,
|
||||
${utils.countInfinites(ctx)} AS infinities_count,
|
||||
${utils.countNaNs(ctx)} AS nans_count,
|
||||
min(${utils.handleFloatColumn(ctx)}) AS min,
|
||||
max(${utils.handleFloatColumn(ctx)}) AS max,
|
||||
avg(${utils.handleFloatColumn(ctx)}) AS avg,
|
||||
sum(CASE WHEN (${utils.handleFloatColumn(ctx)} is not NULL) THEN 1 ELSE 0 END) as freq,
|
||||
CASE WHEN ${ctx.start} = ${ctx.end}
|
||||
THEN 0
|
||||
ELSE GREATEST(1, LEAST(
|
||||
${ctx.bins},
|
||||
WIDTH_BUCKET(${utils.handleFloatColumn(ctx)}, ${ctx.start}, ${ctx.end}, ${ctx.bins}))) - 1
|
||||
END AS bin
|
||||
FROM
|
||||
(
|
||||
${ctx.query}
|
||||
) __cdb_filtered_source_query${extra_tables}
|
||||
GROUP BY bin${extra_groupby}
|
||||
ORDER BY bin;`;
|
||||
}
|
||||
|
||||
_hasOverridenBins (override) {
|
||||
@@ -204,14 +148,31 @@ module.exports = class NumericHistogram extends BaseHistogram {
|
||||
_getSummary (result, override) {
|
||||
const firstRow = result.rows[0] || {};
|
||||
|
||||
var total_nulls = 0;
|
||||
var total_infinities = 0;
|
||||
var total_nans = 0;
|
||||
var total_avg = 0;
|
||||
var total_count = 0;
|
||||
|
||||
result.rows.forEach(function(row) {
|
||||
total_nulls += row.nulls_count;
|
||||
total_infinities += row.infinities_count;
|
||||
total_nans += row.nans_count;
|
||||
total_avg += row.avg * row.freq;
|
||||
total_count += row.freq;
|
||||
});
|
||||
if (total_count !== 0) {
|
||||
total_avg /= total_count;
|
||||
}
|
||||
|
||||
return {
|
||||
bin_width: firstRow.bin_width,
|
||||
bins_count: firstRow.bins_number,
|
||||
bins_start: this._populateBinStart(firstRow, override),
|
||||
nulls: firstRow.nulls_count,
|
||||
infinities: firstRow.infinities_count,
|
||||
nans: firstRow.nans_count,
|
||||
avg: firstRow.avg_val,
|
||||
nulls: total_nulls,
|
||||
infinities: total_infinities,
|
||||
nans: total_nans,
|
||||
avg: total_avg
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,55 +1,38 @@
|
||||
var BaseOverviewsDataview = require('./base');
|
||||
var BaseDataview = require('../formula');
|
||||
var debug = require('debug')('windshaft:widget:formula:overview');
|
||||
const utils = require('../../../utils/query-utils');
|
||||
|
||||
var dot = require('dot');
|
||||
dot.templateSettings.strip = false;
|
||||
|
||||
var formulaQueryTpls = {
|
||||
'count': dot.template([
|
||||
'SELECT',
|
||||
'sum(_feature_count) AS result,',
|
||||
'(SELECT count(1) FROM ({{=it._query}}) _cdb_formula_nulls WHERE {{=it._column}} IS NULL) AS nulls_count',
|
||||
'{{?it._isFloatColumn}},(SELECT count(1) FROM ({{=it._query}}) _cdb_formula_infinities',
|
||||
' WHERE {{=it._column}} = \'infinity\'::float OR {{=it._column}} = \'-infinity\'::float) AS infinities_count,',
|
||||
'(SELECT count(1) FROM ({{=it._query}}) _cdb_formula_nans',
|
||||
' WHERE {{=it._column}} = \'NaN\'::float) AS nans_count{{?}}',
|
||||
'FROM ({{=it._query}}) _cdb_formula'
|
||||
].join('\n')),
|
||||
'sum': dot.template([
|
||||
'SELECT',
|
||||
'sum({{=it._column}}*_feature_count) AS result,',
|
||||
'(SELECT count(1) FROM ({{=it._query}}) _cdb_formula_nulls WHERE {{=it._column}} IS NULL) AS nulls_count',
|
||||
'{{?it._isFloatColumn}},(SELECT count(1) FROM ({{=it._query}}) _cdb_formula_infinities',
|
||||
' WHERE {{=it._column}} = \'infinity\'::float OR {{=it._column}} = \'-infinity\'::float) AS infinities_count',
|
||||
',(SELECT count(1) FROM ({{=it._query}}) _cdb_formula_nans',
|
||||
' WHERE {{=it._column}} = \'NaN\'::float) AS nans_count{{?}}',
|
||||
'FROM ({{=it._query}}) _cdb_formula',
|
||||
'{{?it._isFloatColumn}}WHERE',
|
||||
' {{=it._column}} != \'infinity\'::float',
|
||||
'AND',
|
||||
' {{=it._column}} != \'-infinity\'::float',
|
||||
'AND',
|
||||
' {{=it._column}} != \'NaN\'::float{{?}}'
|
||||
].join('\n')),
|
||||
'avg': dot.template([
|
||||
'SELECT',
|
||||
'sum({{=it._column}}*_feature_count)/sum(_feature_count) AS result,',
|
||||
'(SELECT count(1) FROM ({{=it._query}}) _cdb_formula_nulls WHERE {{=it._column}} IS NULL) AS nulls_count',
|
||||
'{{?it._isFloatColumn}},(SELECT count(1) FROM ({{=it._query}}) _cdb_formula_infinities',
|
||||
' WHERE {{=it._column}} = \'infinity\'::float OR {{=it._column}} = \'-infinity\'::float) AS infinities_count',
|
||||
',(SELECT count(1) FROM ({{=it._query}}) _cdb_formula_nans',
|
||||
' WHERE {{=it._column}} = \'NaN\'::float) AS nans_count{{?}}',
|
||||
'FROM ({{=it._query}}) _cdb_formula',
|
||||
'{{?it._isFloatColumn}}WHERE',
|
||||
' {{=it._column}} != \'infinity\'::float',
|
||||
'AND',
|
||||
' {{=it._column}} != \'-infinity\'::float',
|
||||
'AND',
|
||||
' {{=it._column}} != \'NaN\'::float{{?}}'
|
||||
].join('\n')),
|
||||
const VALID_OPERATIONS = {
|
||||
count: true,
|
||||
sum: true,
|
||||
avg: true
|
||||
};
|
||||
|
||||
/** Formulae to calculate the end result using _feature_count from the overview table*/
|
||||
function dataviewResult(ctx) {
|
||||
switch (ctx.operation) {
|
||||
case 'count':
|
||||
return `sum(_feature_count)`;
|
||||
case 'sum':
|
||||
return `sum(${utils.handleFloatColumn(ctx)}*_feature_count)`;
|
||||
case 'avg':
|
||||
return `sum(${utils.handleFloatColumn(ctx)}*_feature_count)/sum(_feature_count) `;
|
||||
}
|
||||
return `${ctx.operation}(${utils.handleFloatColumn(ctx)})`;
|
||||
}
|
||||
|
||||
const formulaQueryTpl = ctx =>
|
||||
`SELECT
|
||||
${dataviewResult(ctx)} AS result,
|
||||
${utils.countNULLs(ctx)} AS nulls_count
|
||||
${ctx.isFloatColumn ? `,${utils.countInfinites(ctx)} AS infinities_count,` : ``}
|
||||
${ctx.isFloatColumn ? `${utils.countNaNs(ctx)} AS nans_count` : ``}
|
||||
FROM (${ctx.query}) __cdb_formula`;
|
||||
|
||||
function Formula(query, options, queryRewriter, queryRewriteData, params, queries) {
|
||||
BaseOverviewsDataview.call(this, query, options, BaseDataview, queryRewriter, queryRewriteData, params, queries);
|
||||
this.column = options.column || '1';
|
||||
@@ -65,36 +48,31 @@ module.exports = Formula;
|
||||
|
||||
Formula.prototype.sql = function (psql, override, callback) {
|
||||
var self = this;
|
||||
var formulaQueryTpl = formulaQueryTpls[this.operation];
|
||||
|
||||
if (formulaQueryTpl) {
|
||||
// supported formula for use with overviews
|
||||
if (this._isFloatColumn === null) {
|
||||
this._isFloatColumn = false;
|
||||
this.getColumnType(psql, this.column, this.queries.no_filters, function (err, type) {
|
||||
if (!err && !!type) {
|
||||
self._isFloatColumn = type.float;
|
||||
}
|
||||
self.sql(psql, override, callback);
|
||||
});
|
||||
return null;
|
||||
}
|
||||
|
||||
var formulaSql = formulaQueryTpl({
|
||||
_isFloatColumn: this._isFloatColumn,
|
||||
_query: this.rewrittenQuery(this.query),
|
||||
_operation: this.operation,
|
||||
_column: this.column
|
||||
});
|
||||
|
||||
callback = callback || override;
|
||||
|
||||
debug(formulaSql);
|
||||
|
||||
return callback(null, formulaSql);
|
||||
if (!VALID_OPERATIONS[this.operation]) {
|
||||
return this.defaultSql(psql, override, callback);
|
||||
}
|
||||
|
||||
if (this._isFloatColumn === null) {
|
||||
this._isFloatColumn = false;
|
||||
this.getColumnType(psql, this.column, this.queries.no_filters, function (err, type) {
|
||||
if (!err && !!type) {
|
||||
self._isFloatColumn = type.float;
|
||||
}
|
||||
self.sql(psql, override, callback);
|
||||
});
|
||||
return null;
|
||||
}
|
||||
|
||||
// default behaviour
|
||||
return this.defaultSql(psql, override, callback);
|
||||
var formulaSql = formulaQueryTpl({
|
||||
isFloatColumn: this._isFloatColumn,
|
||||
query: this.rewrittenQuery(this.query),
|
||||
operation: this.operation,
|
||||
column: this.column
|
||||
});
|
||||
|
||||
callback = callback || override;
|
||||
|
||||
debug(formulaSql);
|
||||
|
||||
return callback(null, formulaSql);
|
||||
};
|
||||
|
||||
@@ -22,5 +22,36 @@ module.exports.extractTableNames = function extractTableNames(query) {
|
||||
};
|
||||
|
||||
module.exports.getQueryRowCount = function getQueryRowEstimation(query) {
|
||||
return 'select CDB_EstimateRowCount(\'' + query + '\') as rows';
|
||||
return 'select CDB_EstimateRowCount($$' + query + '$$) as rows';
|
||||
};
|
||||
|
||||
/** Cast the column to epoch */
|
||||
module.exports.columnCastTpl = function columnCastTpl(ctx) {
|
||||
return `date_part('epoch', ${ctx.column})`;
|
||||
};
|
||||
|
||||
/** If the column type is float, ignore any non numeric result (infinity / NaN) */
|
||||
module.exports.handleFloatColumn = function handleFloatColumn(ctx) {
|
||||
return `${!ctx.isFloatColumn ? `${ctx.column}` :
|
||||
`nullif(nullif(nullif(${ctx.column}, 'infinity'::float), '-infinity'::float), 'NaN'::float)`
|
||||
}`;
|
||||
};
|
||||
|
||||
/** Count NULL appearances */
|
||||
module.exports.countNULLs= function countNULLs(ctx) {
|
||||
return `sum(CASE WHEN (${ctx.column} IS NULL) THEN 1 ELSE 0 END)`;
|
||||
};
|
||||
|
||||
/** Count only infinity (positive and negative) appearances */
|
||||
module.exports.countInfinites = function countInfinites(ctx) {
|
||||
return `${!ctx.isFloatColumn ? `0` :
|
||||
`sum(CASE WHEN (${ctx.column} = 'infinity'::float OR ${ctx.column} = '-infinity'::float) THEN 1 ELSE 0 END)`
|
||||
}`;
|
||||
};
|
||||
|
||||
/** Count only NaNs appearances*/
|
||||
module.exports.countNaNs = function countNaNs(ctx) {
|
||||
return `${!ctx.isFloatColumn ? `0` :
|
||||
`sum(CASE WHEN (${ctx.column} = 'NaN'::float) THEN 1 ELSE 0 END)`
|
||||
}`;
|
||||
};
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"private": true,
|
||||
"name": "windshaft-cartodb",
|
||||
"version": "4.0.1",
|
||||
"version": "4.4.0",
|
||||
"description": "A map tile server for CartoDB",
|
||||
"keywords": [
|
||||
"cartodb"
|
||||
@@ -23,7 +23,7 @@
|
||||
],
|
||||
"dependencies": {
|
||||
"body-parser": "^1.18.2",
|
||||
"camshaft": "0.59.4",
|
||||
"camshaft": "0.60.0",
|
||||
"cartodb-psql": "0.10.2",
|
||||
"cartodb-query-tables": "0.3.0",
|
||||
"cartodb-redis": "0.14.0",
|
||||
@@ -42,9 +42,9 @@
|
||||
"semver": "~5.3.0",
|
||||
"step": "~0.0.6",
|
||||
"step-profiler": "~0.3.0",
|
||||
"turbo-carto": "0.20.1",
|
||||
"turbo-carto": "0.20.2",
|
||||
"underscore": "~1.6.0",
|
||||
"windshaft": "~4.0.0",
|
||||
"windshaft": "4.1.0",
|
||||
"yargs": "~5.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
37
scripts/mvt-timeout-error.py
Executable file
37
scripts/mvt-timeout-error.py
Executable file
@@ -0,0 +1,37 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
import mapbox_vector_tile
|
||||
|
||||
lines_list = []
|
||||
|
||||
# main diagonal line
|
||||
lines_list.append({ "geometry":"LINESTRING (0 0, 4096 4096)"})
|
||||
|
||||
# diagonal lines
|
||||
for i in range(4096/32, 4096, 4096/32):
|
||||
start = i
|
||||
end = 4096 - i
|
||||
|
||||
lines_list.append({ "geometry":"LINESTRING (0 " + str(start) + ", " + str(end) + " 4096)" })
|
||||
lines_list.append({ "geometry":"LINESTRING (" + str(start) + " 0, 4096 " + str(end) + ")" })
|
||||
|
||||
# box lines
|
||||
lines_list.append({ "geometry":"LINESTRING (0 0, 0 4096)"})
|
||||
lines_list.append({ "geometry":"LINESTRING (0 4096, 4096 4096)"})
|
||||
lines_list.append({ "geometry":"LINESTRING (4096 4096, 4096 0)"})
|
||||
lines_list.append({ "geometry":"LINESTRING (4096 0, 0 0)"})
|
||||
|
||||
|
||||
tile = mapbox_vector_tile.encode([
|
||||
{
|
||||
"name": "errorTileSquareLayer",
|
||||
"features": [{ "geometry":"POLYGON ((0 0, 0 4096, 4096 4096, 4096 0, 0 0))" }]
|
||||
},
|
||||
{
|
||||
"name": "errorTileStripesLayer",
|
||||
"features": lines_list
|
||||
}
|
||||
])
|
||||
|
||||
with open('./assets/render-timeout-fallback.mvt', 'w+') as f:
|
||||
f.write(tile)
|
||||
114
test/acceptance/analysis/analyses-controller.js
Normal file
114
test/acceptance/analysis/analyses-controller.js
Normal file
@@ -0,0 +1,114 @@
|
||||
require('../../support/test_helper');
|
||||
|
||||
var assert = require('../../support/assert');
|
||||
var TestClient = require('../../support/test-client');
|
||||
|
||||
|
||||
describe('analyses controller', function () {
|
||||
const mapConfig = {
|
||||
version: '1.5.0',
|
||||
layers:
|
||||
[{
|
||||
type: 'cartodb',
|
||||
options:
|
||||
{
|
||||
source: { id: 'a1' },
|
||||
cartocss: TestClient.CARTOCSS.POLYGONS,
|
||||
cartocss_version: '2.3.0'
|
||||
}
|
||||
}],
|
||||
dataviews: {},
|
||||
analyses:
|
||||
[{
|
||||
id: 'a1',
|
||||
type: 'buffer',
|
||||
params: {
|
||||
source: {
|
||||
type: 'source',
|
||||
params: {
|
||||
query: 'select * from analysis_banks limit 1'
|
||||
}
|
||||
},
|
||||
radius: 250
|
||||
}
|
||||
}]
|
||||
};
|
||||
|
||||
beforeEach(function () {
|
||||
this.testClient = new TestClient(mapConfig, 1234);
|
||||
});
|
||||
|
||||
it('should get an array of analyses from catalog', function (done) {
|
||||
this.testClient.getAnalysesCatalog({}, (err, result) => {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
|
||||
assert.ok(Array.isArray(result.catalog));
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should support jsonp responses', function (done) {
|
||||
this.testClient.getAnalysesCatalog({ jsonp: 'jsonp_test' }, (err, result) => {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
|
||||
assert.ok(result);
|
||||
|
||||
let didRunJsonCallback = false;
|
||||
// jshint ignore:start
|
||||
function jsonp_test(body) {
|
||||
assert.ok(Array.isArray(body.catalog));
|
||||
didRunJsonCallback = true;
|
||||
}
|
||||
|
||||
eval(result);
|
||||
// jshint ignore:end
|
||||
|
||||
assert.ok(didRunJsonCallback);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should respond "unauthorized" when missing api_key', function (done) {
|
||||
const apiKey = this.testClient.apiKey;
|
||||
this.testClient.apiKey = null;
|
||||
|
||||
this.testClient.getAnalysesCatalog({ status: 401 }, (err, result) => {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
|
||||
assert.deepEqual(result.errors[0], 'Unauthorized');
|
||||
this.testClient.apiKey = apiKey;
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should get an array of analyses from catalog', function (done) {
|
||||
this.testClient.getTile(0, 0, 0, (err) => {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
|
||||
this.testClient.getAnalysesCatalog({}, (err, result) => {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
|
||||
assert.ok(Array.isArray(result.catalog));
|
||||
assert.ok(result.catalog.length >= 2); // buffer & source at least
|
||||
|
||||
result.catalog
|
||||
.filter(analysis => analysis.node_id === '0a215e1f3405381cf0ea6b3b0deb6fdcfdc2fcaa')
|
||||
.forEach(analysis => assert.equal(analysis.type, 'buffer'));
|
||||
|
||||
this.testClient.drain(done);
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -324,3 +324,104 @@ describe('aggregation-dataview: special float values', function() {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('aggregation dataview tuned by categories query param', function () {
|
||||
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: {
|
||||
categories: {
|
||||
source: {
|
||||
id: 'a0'
|
||||
},
|
||||
type: 'aggregation',
|
||||
options: {
|
||||
column: 'cat',
|
||||
aggregation: 'sum',
|
||||
aggregationColumn: 'val'
|
||||
}
|
||||
}
|
||||
},
|
||||
analyses: [
|
||||
{
|
||||
id: "a0",
|
||||
type: "source",
|
||||
params: {
|
||||
query: `
|
||||
SELECT
|
||||
null::geometry the_geom_webmercator,
|
||||
CASE
|
||||
WHEN x % 4 = 0 THEN 1
|
||||
WHEN x % 4 = 1 THEN 2
|
||||
WHEN x % 4 = 2 THEN 3
|
||||
ELSE 4
|
||||
END AS val,
|
||||
CASE
|
||||
WHEN x % 4 = 0 THEN 'category_1'
|
||||
WHEN x % 4 = 1 THEN 'category_2'
|
||||
WHEN x % 4 = 2 THEN 'category_3'
|
||||
ELSE 'category_4'
|
||||
END AS cat
|
||||
FROM generate_series(1, 1000) x
|
||||
`
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
beforeEach(function () {
|
||||
this.testClient = new TestClient(mapConfig, 1234);
|
||||
});
|
||||
|
||||
afterEach(function (done) {
|
||||
this.testClient.drain(done);
|
||||
});
|
||||
|
||||
var scenarios = [
|
||||
{
|
||||
params: { own_filter: 0, categories: -1 },
|
||||
categoriesExpected: 4
|
||||
},
|
||||
{
|
||||
params: { own_filter: 0, categories: 0 },
|
||||
categoriesExpected: 4
|
||||
},
|
||||
{
|
||||
params: { own_filter: 0, categories: 1 },
|
||||
categoriesExpected: 1
|
||||
},
|
||||
{
|
||||
params: { own_filter: 0, categories: 2 },
|
||||
categoriesExpected: 2
|
||||
},
|
||||
{
|
||||
params: { own_filter: 0, categories: 4 },
|
||||
categoriesExpected: 4
|
||||
},
|
||||
{
|
||||
params: { own_filter: 0, categories: 5 },
|
||||
categoriesExpected: 4
|
||||
}
|
||||
];
|
||||
|
||||
scenarios.forEach(function (scenario) {
|
||||
it(`should handle cartegories to customize aggregations: ${JSON.stringify(scenario.params)}`, function (done) {
|
||||
this.testClient.getDataview('categories', scenario.params, (err, dataview) => {
|
||||
assert.ifError(err);
|
||||
assert.equal(dataview.categories.length, scenario.categoriesExpected);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -186,7 +186,7 @@ describe('histogram-dataview for date column type', function() {
|
||||
},
|
||||
minute_histogram: {
|
||||
source: {
|
||||
id: 'minute-histogram-source'
|
||||
id: 'minute-histogram-source-tz'
|
||||
},
|
||||
type: 'histogram',
|
||||
options: {
|
||||
@@ -214,8 +214,8 @@ describe('histogram-dataview for date column type', function() {
|
||||
"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",
|
||||
"from generate_series('2007-02-15 01:00:00+00'::timestamptz,",
|
||||
"'2008-04-09 01:00:00+00'::timestamptz, '1 day'::interval",
|
||||
") date"
|
||||
].join(' ')
|
||||
}
|
||||
@@ -233,13 +233,13 @@ describe('histogram-dataview for date column type', function() {
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "minute-histogram-source",
|
||||
"id": "minute-histogram-source-tz",
|
||||
"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",
|
||||
"from generate_series('2007-02-15 23:50:00+00'::timestamptz,",
|
||||
"'2007-02-16 00:10:00+00'::timestamptz, '1 minute'::interval",
|
||||
") date"
|
||||
].join(' ')
|
||||
}
|
||||
@@ -256,6 +256,7 @@ describe('histogram-dataview for date column type', function() {
|
||||
}];
|
||||
|
||||
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
|
||||
|
||||
@@ -323,7 +324,7 @@ describe('histogram-dataview for date column type', function() {
|
||||
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);
|
||||
assert.equal(dataview.bins_count, 6);
|
||||
dataview.bins.forEach(function (bin) {
|
||||
assert.ok(bin.min <= bin.max, 'bin min < bin max: ' + JSON.stringify(bin));
|
||||
});
|
||||
@@ -335,7 +336,7 @@ 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
|
||||
end: 1193792400
|
||||
};
|
||||
|
||||
this.testClient = new TestClient(mapConfig, 1234);
|
||||
@@ -348,27 +349,6 @@ describe('histogram-dataview for date column type', function() {
|
||||
});
|
||||
});
|
||||
|
||||
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)
|
||||
@@ -533,6 +513,26 @@ describe('histogram-dataview for date column type', function() {
|
||||
});
|
||||
});
|
||||
|
||||
it('should return same histogram ', 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('datetime_histogram_tz', {}, function (err, dataview) {
|
||||
assert.ok(!err, err);
|
||||
|
||||
this.testClient = new TestClient(mapConfig, 1234);
|
||||
this.testClient.getDataview('datetime_histogram_tz', params, function (err, filteredDataview) {
|
||||
assert.ok(!err, err);
|
||||
|
||||
assert.deepEqual(dataview, filteredDataview);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should find the best aggregation (automatic mode) to build the histogram', function (done) {
|
||||
var params = {};
|
||||
this.testClient = new TestClient(mapConfig, 1234);
|
||||
@@ -640,7 +640,7 @@ describe('histogram-dataview for date column type', function() {
|
||||
|
||||
var dataviewWithDailyAggFixture = {
|
||||
aggregation: 'day',
|
||||
bin_width: 600,
|
||||
bin_width: 86400,
|
||||
bins_count: 2,
|
||||
bins_start: 1171497600,
|
||||
timestamp_start: 1171497600,
|
||||
@@ -650,17 +650,17 @@ describe('histogram-dataview for date column type', function() {
|
||||
[{
|
||||
bin: 0,
|
||||
timestamp: 1171497600,
|
||||
min: 1171583400,
|
||||
max: 1171583940,
|
||||
avg: 1171583670,
|
||||
min: 1171497600,
|
||||
max: 1171497600,
|
||||
avg: 1171497600,
|
||||
freq: 10
|
||||
},
|
||||
{
|
||||
bin: 1,
|
||||
timestamp: 1171584000,
|
||||
min: 1171584000,
|
||||
max: 1171584600,
|
||||
avg: 1171584300,
|
||||
max: 1171584000,
|
||||
avg: 1171584000,
|
||||
freq: 11
|
||||
}],
|
||||
type: 'histogram'
|
||||
@@ -687,19 +687,19 @@ describe('histogram-dataview for date column type', function() {
|
||||
|
||||
var dataviewWithDailyAggAndOffsetFixture = {
|
||||
aggregation: 'day',
|
||||
bin_width: 1200,
|
||||
bin_width: 86400,
|
||||
bins_count: 1,
|
||||
bins_start: 1171501200,
|
||||
timestamp_start: 1171497600,
|
||||
timestamp_start: 1171501200,
|
||||
nulls: 0,
|
||||
offset: -3600,
|
||||
bins:
|
||||
[{
|
||||
bin: 0,
|
||||
timestamp: 1171501200,
|
||||
min: 1171583400,
|
||||
max: 1171584600,
|
||||
avg: 1171584000,
|
||||
min: 1171501200,
|
||||
max: 1171501200,
|
||||
avg: 1171501200,
|
||||
freq: 21
|
||||
}],
|
||||
type: 'histogram'
|
||||
|
||||
@@ -1163,8 +1163,12 @@ describe(suiteName, function() {
|
||||
);
|
||||
});
|
||||
|
||||
// WARN: MapConfig with mapnik layer and no cartocss it's valid since
|
||||
// vector & raster aggregation project, now we can request MVT format w/o defining styles
|
||||
// for the layer.
|
||||
|
||||
// See https://github.com/CartoDB/Windshaft-cartodb/issues/133
|
||||
it("MapConfig with mapnik layer and no cartocss", function(done) {
|
||||
it.skip("MapConfig with mapnik layer and no cartocss", function(done) {
|
||||
|
||||
var layergroup = {
|
||||
version: '1.0.0',
|
||||
|
||||
@@ -504,4 +504,4 @@ return function () {
|
||||
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,8 @@ describe('named maps static view', function() {
|
||||
var username = 'localhost';
|
||||
var templateName = 'template_with_view';
|
||||
|
||||
var IMAGE_TOLERANCE = 20;
|
||||
var PNG_IMAGE_TOLERANCE = 20;
|
||||
var JPG_IMAGE_TOLERANCE = 100;
|
||||
|
||||
function createTemplate(view, layers) {
|
||||
return {
|
||||
@@ -92,8 +93,8 @@ describe('named maps static view', function() {
|
||||
});
|
||||
}
|
||||
|
||||
function previewFixture(version) {
|
||||
return './test/fixtures/previews/populated_places_simple_reduced-' + version + '.png';
|
||||
function previewFixture(version, format='png') {
|
||||
return './test/fixtures/previews/populated_places_simple_reduced-' + version + '.' + format;
|
||||
}
|
||||
|
||||
it('should return an image estimating its bounds based on dataset', function (done) {
|
||||
@@ -103,7 +104,7 @@ describe('named maps static view', function() {
|
||||
}
|
||||
getStaticMap(function(err, img) {
|
||||
assert.ok(!err);
|
||||
assert.imageIsSimilarToFile(img, previewFixture('estimated'), IMAGE_TOLERANCE, done);
|
||||
assert.imageIsSimilarToFile(img, previewFixture('estimated'), PNG_IMAGE_TOLERANCE, done);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -122,7 +123,7 @@ describe('named maps static view', function() {
|
||||
}
|
||||
getStaticMap(function(err, img) {
|
||||
assert.ok(!err);
|
||||
assert.imageIsSimilarToFile(img, previewFixture('zoom-center'), IMAGE_TOLERANCE, done);
|
||||
assert.imageIsSimilarToFile(img, previewFixture('zoom-center'), PNG_IMAGE_TOLERANCE, done);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -142,7 +143,7 @@ describe('named maps static view', function() {
|
||||
}
|
||||
getStaticMap(function(err, img) {
|
||||
assert.ok(!err);
|
||||
assert.imageIsSimilarToFile(img, previewFixture('bounds'), IMAGE_TOLERANCE, done);
|
||||
assert.imageIsSimilarToFile(img, previewFixture('bounds'), PNG_IMAGE_TOLERANCE, done);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -167,7 +168,7 @@ describe('named maps static view', function() {
|
||||
}
|
||||
getStaticMap(function(err, img) {
|
||||
assert.ok(!err);
|
||||
assert.imageIsSimilarToFile(img, previewFixture('zoom-center'), IMAGE_TOLERANCE, done);
|
||||
assert.imageIsSimilarToFile(img, previewFixture('zoom-center'), PNG_IMAGE_TOLERANCE, done);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -192,7 +193,7 @@ describe('named maps static view', function() {
|
||||
}
|
||||
getStaticMap({ zoom: 3 }, function(err, img) {
|
||||
assert.ok(!err);
|
||||
assert.imageIsSimilarToFile(img, previewFixture('override-zoom'), IMAGE_TOLERANCE, done);
|
||||
assert.imageIsSimilarToFile(img, previewFixture('override-zoom'), PNG_IMAGE_TOLERANCE, done);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -217,7 +218,7 @@ describe('named maps static view', function() {
|
||||
}
|
||||
getStaticMap({ bbox: '0,45,90,45' }, function(err, img) {
|
||||
assert.ok(!err);
|
||||
assert.imageIsSimilarToFile(img, previewFixture('override-bbox'), IMAGE_TOLERANCE, done);
|
||||
assert.imageIsSimilarToFile(img, previewFixture('override-bbox'), PNG_IMAGE_TOLERANCE, done);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -256,7 +257,27 @@ describe('named maps static view', function() {
|
||||
}
|
||||
getStaticMap({ layer: 0 }, function(err, img) {
|
||||
assert.ok(!err);
|
||||
assert.imageIsSimilarToFile(img, previewFixture('bounds'), IMAGE_TOLERANCE, done);
|
||||
assert.imageIsSimilarToFile(img, previewFixture('bounds'), PNG_IMAGE_TOLERANCE, done);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should return jpg static map', function (done) {
|
||||
var view = {
|
||||
zoom: 4,
|
||||
center: {
|
||||
lng: 40,
|
||||
lat: 20
|
||||
}
|
||||
};
|
||||
templateMaps.addTemplate(username, createTemplate(view), function (err) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
getStaticMap({ format: 'jpeg' }, function(err, img) {
|
||||
assert.ok(!err);
|
||||
assert.imageIsSimilarToFile(img, previewFixture('zoom-center', 'jpeg'),
|
||||
JPG_IMAGE_TOLERANCE, done, 'jpeg');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -4,7 +4,6 @@ var assert = require('../support/assert');
|
||||
var cartodbServer = require('../../lib/cartodb/server');
|
||||
var ServerOptions = require('./ported/support/ported_server_options');
|
||||
var testClient = require('./ported/support/test_client');
|
||||
var BaseController = require('../../lib/cartodb/controllers/base');
|
||||
|
||||
describe('overviews_queries', function() {
|
||||
|
||||
@@ -13,15 +12,7 @@ describe('overviews_queries', function() {
|
||||
|
||||
var IMAGE_EQUALS_TOLERANCE_PER_MIL = 2;
|
||||
|
||||
var req2paramsFn;
|
||||
before(function() {
|
||||
req2paramsFn = BaseController.prototype.req2params;
|
||||
BaseController.prototype.req2params = ServerOptions.req2params;
|
||||
});
|
||||
|
||||
after(function() {
|
||||
BaseController.prototype.req2params = req2paramsFn;
|
||||
|
||||
testHelper.rmdirRecursiveSync(global.environment.millstone.cache_basedir);
|
||||
});
|
||||
|
||||
|
||||
@@ -4,13 +4,10 @@ var assert = require('../../support/assert');
|
||||
var step = require('step');
|
||||
var cartodbServer = require('../../../lib/cartodb/server');
|
||||
var PortedServerOptions = require('./support/ported_server_options');
|
||||
var BaseController = require('../../../lib/cartodb/controllers/base');
|
||||
|
||||
var LayergroupToken = require('../../../lib/cartodb/models/layergroup-token');
|
||||
|
||||
|
||||
describe('attributes', function() {
|
||||
|
||||
var server = cartodbServer(PortedServerOptions);
|
||||
server.setMaxListeners(0);
|
||||
|
||||
@@ -49,16 +46,6 @@ describe('attributes', function() {
|
||||
testHelper.deleteRedisKeys(keysToDelete, done);
|
||||
});
|
||||
|
||||
var req2paramsFn;
|
||||
before(function() {
|
||||
req2paramsFn = BaseController.prototype.req2params;
|
||||
BaseController.prototype.req2params = PortedServerOptions.req2params;
|
||||
});
|
||||
|
||||
after(function() {
|
||||
BaseController.prototype.req2params = req2paramsFn;
|
||||
});
|
||||
|
||||
it("can only be fetched from layer having an attributes spec", function(done) {
|
||||
|
||||
var expected_token;
|
||||
|
||||
@@ -3,21 +3,7 @@ require('../../support/test_helper');
|
||||
var assert = require('../../support/assert');
|
||||
var testClient = require('./support/test_client');
|
||||
|
||||
var PortedServerOptions = require('./support/ported_server_options');
|
||||
var BaseController = require('../../../lib/cartodb/controllers/base');
|
||||
|
||||
describe('blend png renderer', function() {
|
||||
|
||||
var req2paramsFn;
|
||||
before(function() {
|
||||
req2paramsFn = BaseController.prototype.req2params;
|
||||
BaseController.prototype.req2params = PortedServerOptions.req2params;
|
||||
});
|
||||
|
||||
after(function() {
|
||||
BaseController.prototype.req2params = req2paramsFn;
|
||||
});
|
||||
|
||||
var IMAGE_TOLERANCE_PER_MIL = 20;
|
||||
|
||||
function plainTorqueMapConfig(plainColor) {
|
||||
|
||||
@@ -5,20 +5,13 @@ var testClient = require('./support/test_client');
|
||||
var fs = require('fs');
|
||||
var http = require('http');
|
||||
|
||||
var PortedServerOptions = require('./support/ported_server_options');
|
||||
var BaseController = require('../../../lib/cartodb/controllers/base');
|
||||
|
||||
describe('blend layer filtering', function() {
|
||||
|
||||
var IMG_TOLERANCE_PER_MIL = 20;
|
||||
|
||||
var httpRendererResourcesServer;
|
||||
|
||||
var req2paramsFn;
|
||||
before(function(done) {
|
||||
req2paramsFn = BaseController.prototype.req2params;
|
||||
BaseController.prototype.req2params = PortedServerOptions.req2params;
|
||||
|
||||
// Start a server to test external resources
|
||||
httpRendererResourcesServer = http.createServer( function(request, response) {
|
||||
var filename = __dirname + '/../../fixtures/http/light_nolabels-1-0-0.png';
|
||||
@@ -32,7 +25,6 @@ describe('blend layer filtering', function() {
|
||||
});
|
||||
|
||||
after(function(done) {
|
||||
BaseController.prototype.req2params = req2paramsFn;
|
||||
httpRendererResourcesServer.close(done);
|
||||
});
|
||||
|
||||
|
||||
@@ -5,19 +5,13 @@ var testClient = require('./support/test_client');
|
||||
var fs = require('fs');
|
||||
var http = require('http');
|
||||
|
||||
var PortedServerOptions = require('./support/ported_server_options');
|
||||
var BaseController = require('../../../lib/cartodb/controllers/base');
|
||||
|
||||
describe('blend http fallback', function() {
|
||||
|
||||
var IMG_TOLERANCE_PER_MIL = 20;
|
||||
|
||||
var httpRendererResourcesServer;
|
||||
|
||||
var req2paramsFn;
|
||||
before(function(done) {
|
||||
req2paramsFn = BaseController.prototype.req2params;
|
||||
BaseController.prototype.req2params = PortedServerOptions.req2params;
|
||||
// Start a server to test external resources
|
||||
httpRendererResourcesServer = http.createServer( function(request, response) {
|
||||
if (request.url.match(/^\/error404\//)) {
|
||||
@@ -39,7 +33,6 @@ describe('blend http fallback', function() {
|
||||
});
|
||||
|
||||
after(function(done) {
|
||||
BaseController.prototype.req2params = req2paramsFn;
|
||||
httpRendererResourcesServer.close(done);
|
||||
});
|
||||
|
||||
|
||||
@@ -6,21 +6,7 @@ var serverOptions = require('./support/ported_server_options');
|
||||
var fs = require('fs');
|
||||
var http = require('http');
|
||||
|
||||
var PortedServerOptions = require('./support/ported_server_options');
|
||||
var BaseController = require('../../../lib/cartodb/controllers/base');
|
||||
|
||||
describe.skip('blend http client timeout', function() {
|
||||
|
||||
var req2paramsFn;
|
||||
before(function() {
|
||||
req2paramsFn = BaseController.prototype.req2params;
|
||||
BaseController.prototype.req2params = PortedServerOptions.req2params;
|
||||
});
|
||||
|
||||
after(function() {
|
||||
BaseController.prototype.req2params = req2paramsFn;
|
||||
});
|
||||
|
||||
var mapConfig = {
|
||||
version: '1.3.0',
|
||||
layers: [
|
||||
|
||||
@@ -1,16 +1,12 @@
|
||||
var testHelper = require('../../support/test_helper');
|
||||
|
||||
|
||||
var assert = require('../../support/assert');
|
||||
var fs = require('fs');
|
||||
var PortedServerOptions = require('./support/ported_server_options');
|
||||
var http = require('http');
|
||||
var testClient = require('./support/test_client');
|
||||
|
||||
var nock = require('nock');
|
||||
|
||||
var BaseController = require('../../../lib/cartodb/controllers/base');
|
||||
|
||||
describe('external resources', function() {
|
||||
|
||||
var res_serv; // resources server
|
||||
@@ -19,12 +15,8 @@ describe('external resources', function() {
|
||||
|
||||
var IMAGE_EQUALS_TOLERANCE_PER_MIL = 25;
|
||||
|
||||
var req2paramsFn;
|
||||
before(function(done) {
|
||||
nock.enableNetConnect('127.0.0.1');
|
||||
|
||||
req2paramsFn = BaseController.prototype.req2params;
|
||||
BaseController.prototype.req2params = PortedServerOptions.req2params;
|
||||
// Start a server to test external resources
|
||||
res_serv = http.createServer( function(request, response) {
|
||||
++res_serv_status.numrequests;
|
||||
@@ -44,8 +36,6 @@ describe('external resources', function() {
|
||||
});
|
||||
|
||||
after(function(done) {
|
||||
BaseController.prototype.req2params = req2paramsFn;
|
||||
|
||||
testHelper.rmdirRecursiveSync(global.environment.millstone.cache_basedir);
|
||||
|
||||
// Close the resources server
|
||||
|
||||
@@ -6,19 +6,14 @@ var assert = require('../../support/assert');
|
||||
var testClient = require('./support/test_client');
|
||||
var serverOptions = require('./support/ported_server_options');
|
||||
|
||||
var PortedServerOptions = require('./support/ported_server_options');
|
||||
var BaseController = require('../../../lib/cartodb/controllers/base');
|
||||
|
||||
describe.skip('render limits', function() {
|
||||
|
||||
var IMAGE_EQUALS_TOLERANCE_PER_MIL = 25;
|
||||
|
||||
var limitsConfig;
|
||||
var onTileErrorStrategy;
|
||||
var req2paramsFn;
|
||||
|
||||
before(function() {
|
||||
req2paramsFn = BaseController.prototype.req2params;
|
||||
BaseController.prototype.req2params = PortedServerOptions.req2params;
|
||||
limitsConfig = serverOptions.renderer.mapnik.limits;
|
||||
serverOptions.renderer.mapnik.limits = {
|
||||
render: 50,
|
||||
@@ -31,7 +26,6 @@ describe.skip('render limits', function() {
|
||||
});
|
||||
|
||||
after(function() {
|
||||
BaseController.prototype.req2params = req2paramsFn;
|
||||
serverOptions.renderer.mapnik.limits = limitsConfig;
|
||||
serverOptions.renderer.onTileErrorStrategy = onTileErrorStrategy;
|
||||
});
|
||||
|
||||
@@ -8,7 +8,6 @@ var mapnik = require('windshaft').mapnik;
|
||||
var cartodbServer = require('../../../lib/cartodb/server');
|
||||
var ServerOptions = require('./support/ported_server_options');
|
||||
var LayergroupToken = require('../../../lib/cartodb/models/layergroup-token');
|
||||
var BaseController = require('../../../lib/cartodb/controllers/base');
|
||||
|
||||
describe('multilayer', function() {
|
||||
|
||||
@@ -24,16 +23,6 @@ describe('multilayer', function() {
|
||||
assert.equal(res.headers['access-control-allow-origin'], '*');
|
||||
}
|
||||
|
||||
var req2paramsFn;
|
||||
before(function() {
|
||||
req2paramsFn = BaseController.prototype.req2params;
|
||||
BaseController.prototype.req2params = ServerOptions.req2params;
|
||||
});
|
||||
|
||||
after(function() {
|
||||
BaseController.prototype.req2params = req2paramsFn;
|
||||
});
|
||||
|
||||
// See https://github.com/Vizzuality/Windshaft/issues/70
|
||||
it("post layergroup with encoding in content-type", function(done) {
|
||||
var layergroup = {
|
||||
|
||||
@@ -7,23 +7,11 @@ var ServerOptions = require('./support/ported_server_options');
|
||||
var testClient = require('./support/test_client');
|
||||
var TestClient = require('../../support/test-client');
|
||||
|
||||
var BaseController = require('../../../lib/cartodb/controllers/base');
|
||||
|
||||
describe('multilayer error cases', function() {
|
||||
|
||||
var server = cartodbServer(ServerOptions);
|
||||
server.setMaxListeners(0);
|
||||
|
||||
var req2paramsFn;
|
||||
before(function() {
|
||||
req2paramsFn = BaseController.prototype.req2params;
|
||||
BaseController.prototype.req2params = ServerOptions.req2params;
|
||||
});
|
||||
|
||||
after(function() {
|
||||
BaseController.prototype.req2params = req2paramsFn;
|
||||
});
|
||||
|
||||
// var client = null;
|
||||
afterEach(function(done) {
|
||||
if (this.client) {
|
||||
@@ -40,7 +28,7 @@ describe('multilayer error cases', function() {
|
||||
}, {}, function(res) {
|
||||
assert.equal(res.statusCode, 400, res.body);
|
||||
var parsedBody = JSON.parse(res.body);
|
||||
assert.deepEqual(parsedBody.errors, ["layergroup POST data must be of type application/json"]);
|
||||
assert.deepEqual(parsedBody.errors, ["POST data must be of type application/json"]);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -7,23 +7,11 @@ var getLayerTypeFn = require('windshaft').model.MapConfig.prototype.getType;
|
||||
var PortedServerOptions = require('./support/ported_server_options');
|
||||
var LayergroupToken = require('../../../lib/cartodb/models/layergroup-token');
|
||||
|
||||
var BaseController = require('../../../lib/cartodb/controllers/base');
|
||||
|
||||
describe('multilayer interactivity and layers order', function() {
|
||||
|
||||
var server = cartodbServer(PortedServerOptions);
|
||||
server.setMaxListeners(0);
|
||||
|
||||
var req2paramsFn;
|
||||
before(function() {
|
||||
req2paramsFn = BaseController.prototype.req2params;
|
||||
BaseController.prototype.req2params = PortedServerOptions.req2params;
|
||||
});
|
||||
|
||||
after(function() {
|
||||
BaseController.prototype.req2params = req2paramsFn;
|
||||
});
|
||||
|
||||
function layerType(layer) {
|
||||
return layer.type || 'undefined';
|
||||
}
|
||||
|
||||
@@ -4,8 +4,6 @@ var assert = require('../../support/assert');
|
||||
var step = require('step');
|
||||
var cartodbServer = require('../../../lib/cartodb/server');
|
||||
var ServerOptions = require('./support/ported_server_options');
|
||||
|
||||
var BaseController = require('../../../lib/cartodb/controllers/base');
|
||||
var LayergroupToken = require('../../../lib/cartodb/models/layergroup-token');
|
||||
|
||||
describe('raster', function() {
|
||||
@@ -20,18 +18,7 @@ describe('raster', function() {
|
||||
|
||||
var IMAGE_EQUALS_TOLERANCE_PER_MIL = 2;
|
||||
|
||||
var req2paramsFn;
|
||||
before(function() {
|
||||
req2paramsFn = BaseController.prototype.req2params;
|
||||
BaseController.prototype.req2params = ServerOptions.req2params;
|
||||
});
|
||||
|
||||
after(function() {
|
||||
BaseController.prototype.req2params = req2paramsFn;
|
||||
});
|
||||
|
||||
it("can render raster for valid mapconfig", function(done) {
|
||||
|
||||
var mapconfig = {
|
||||
version: '1.2.0',
|
||||
layers: [
|
||||
|
||||
@@ -1,22 +1,10 @@
|
||||
var testHelper = require('../../support/test_helper');
|
||||
|
||||
var assert = require('../../support/assert');
|
||||
var ServerOptions = require('./support/ported_server_options');
|
||||
var testClient = require('./support/test_client');
|
||||
|
||||
var BaseController = require('../../../lib/cartodb/controllers/base');
|
||||
|
||||
describe('regressions', function() {
|
||||
|
||||
var req2paramsFn;
|
||||
before(function() {
|
||||
req2paramsFn = BaseController.prototype.req2params;
|
||||
BaseController.prototype.req2params = ServerOptions.req2params;
|
||||
});
|
||||
|
||||
|
||||
after(function() {
|
||||
BaseController.prototype.req2params = req2paramsFn;
|
||||
testHelper.rmdirRecursiveSync(global.environment.millstone.cache_basedir);
|
||||
});
|
||||
|
||||
|
||||
@@ -5,7 +5,6 @@ var mapnik = require('windshaft').mapnik;
|
||||
var cartodbServer = require('../../../lib/cartodb/server');
|
||||
var ServerOptions = require('./support/ported_server_options');
|
||||
|
||||
var BaseController = require('../../../lib/cartodb/controllers/base');
|
||||
var LayergroupToken = require('../../../lib/cartodb/models/layergroup-token');
|
||||
|
||||
describe('retina support', function() {
|
||||
@@ -15,15 +14,6 @@ describe('retina support', function() {
|
||||
var server = cartodbServer(ServerOptions);
|
||||
server.setMaxListeners(0);
|
||||
|
||||
var req2paramsFn;
|
||||
before(function() {
|
||||
req2paramsFn = BaseController.prototype.req2params;
|
||||
BaseController.prototype.req2params = ServerOptions.req2params;
|
||||
});
|
||||
|
||||
after(function() {
|
||||
BaseController.prototype.req2params = req2paramsFn;
|
||||
});
|
||||
|
||||
var keysToDelete;
|
||||
beforeEach(function(done) {
|
||||
|
||||
@@ -5,22 +5,12 @@ var cartodbServer = require('../../../lib/cartodb/server');
|
||||
var ServerOptions = require('./support/ported_server_options');
|
||||
var testClient = require('./support/test_client');
|
||||
|
||||
var BaseController = require('../../../lib/cartodb/controllers/base');
|
||||
|
||||
describe('server', function() {
|
||||
|
||||
var server = cartodbServer(ServerOptions);
|
||||
server.setMaxListeners(0);
|
||||
|
||||
var req2paramsFn;
|
||||
before(function() {
|
||||
req2paramsFn = BaseController.prototype.req2params;
|
||||
BaseController.prototype.req2params = ServerOptions.req2params;
|
||||
});
|
||||
|
||||
after(function() {
|
||||
BaseController.prototype.req2params = req2paramsFn;
|
||||
|
||||
testHelper.rmdirRecursiveSync(global.environment.millstone.cache_basedir);
|
||||
});
|
||||
|
||||
|
||||
@@ -7,8 +7,6 @@ var cartodbServer = require('../../../lib/cartodb/server');
|
||||
var ServerOptions = require('./support/ported_server_options');
|
||||
var testClient = require('./support/test_client');
|
||||
|
||||
var BaseController = require('../../../lib/cartodb/controllers/base');
|
||||
|
||||
describe('server_gettile', function() {
|
||||
|
||||
var server = cartodbServer(ServerOptions);
|
||||
@@ -16,16 +14,7 @@ describe('server_gettile', function() {
|
||||
|
||||
var IMAGE_EQUALS_TOLERANCE_PER_MIL = 25;
|
||||
|
||||
var req2paramsFn;
|
||||
before(function() {
|
||||
req2paramsFn = BaseController.prototype.req2params;
|
||||
BaseController.prototype.req2params = ServerOptions.req2params;
|
||||
});
|
||||
|
||||
|
||||
after(function() {
|
||||
BaseController.prototype.req2params = req2paramsFn;
|
||||
|
||||
testHelper.rmdirRecursiveSync(global.environment.millstone.cache_basedir);
|
||||
});
|
||||
|
||||
|
||||
@@ -6,13 +6,11 @@ var fs = require('fs');
|
||||
var cartodbServer = require('../../../lib/cartodb/server');
|
||||
var ServerOptions = require('./support/ported_server_options');
|
||||
|
||||
var BaseController = require('../../../lib/cartodb/controllers/base');
|
||||
var LayergroupToken = require('../../../lib/cartodb/models/layergroup-token');
|
||||
|
||||
var IMAGE_EQUALS_TOLERANCE_PER_MIL = 85;
|
||||
|
||||
describe('server_png8_format', function() {
|
||||
|
||||
var serverOptionsPng32 = ServerOptions;
|
||||
serverOptionsPng32.grainstore = _.clone(ServerOptions.grainstore);
|
||||
serverOptionsPng32.grainstore.mapnik_tile_format = 'png32';
|
||||
@@ -25,13 +23,9 @@ describe('server_png8_format', function() {
|
||||
var serverPng8 = cartodbServer(serverOptionsPng8);
|
||||
serverPng8.setMaxListeners(0);
|
||||
|
||||
|
||||
var layergroupId;
|
||||
|
||||
var req2paramsFn;
|
||||
before(function(done) {
|
||||
req2paramsFn = BaseController.prototype.req2params;
|
||||
BaseController.prototype.req2params = ServerOptions.req2params;
|
||||
var testPngFilesDir = __dirname + '/../../results/png';
|
||||
fs.readdirSync(testPngFilesDir)
|
||||
.filter(function(fileName) {
|
||||
@@ -45,10 +39,6 @@ describe('server_png8_format', function() {
|
||||
done();
|
||||
});
|
||||
|
||||
after(function() {
|
||||
BaseController.prototype.req2params = req2paramsFn;
|
||||
});
|
||||
|
||||
var keysToDelete;
|
||||
beforeEach(function() {
|
||||
keysToDelete = {
|
||||
|
||||
@@ -5,9 +5,6 @@ var testClient = require('./support/test_client');
|
||||
var http = require('http');
|
||||
var fs = require('fs');
|
||||
|
||||
var PortedServerOptions = require('./support/ported_server_options');
|
||||
var BaseController = require('../../../lib/cartodb/controllers/base');
|
||||
|
||||
describe('static_maps', function() {
|
||||
|
||||
var validUrlTemplate = 'http://127.0.0.1:8033/{s}/{z}/{x}/{y}.png';
|
||||
@@ -15,11 +12,7 @@ describe('static_maps', function() {
|
||||
|
||||
var httpRendererResourcesServer;
|
||||
|
||||
var req2paramsFn;
|
||||
before(function(done) {
|
||||
req2paramsFn = BaseController.prototype.req2params;
|
||||
BaseController.prototype.req2params = PortedServerOptions.req2params;
|
||||
|
||||
// Start a server to test external resources
|
||||
httpRendererResourcesServer = http.createServer( function(request, response) {
|
||||
var filename = __dirname + '/../../fixtures/http/basemap.png';
|
||||
@@ -33,7 +26,6 @@ describe('static_maps', function() {
|
||||
});
|
||||
|
||||
after(function(done) {
|
||||
BaseController.prototype.req2params = req2paramsFn;
|
||||
httpRendererResourcesServer.close(done);
|
||||
});
|
||||
|
||||
|
||||
@@ -6,7 +6,6 @@ var step = require('step');
|
||||
var cartodbServer = require('../../../lib/cartodb/server');
|
||||
var ServerOptions = require('./support/ported_server_options');
|
||||
|
||||
var BaseController = require('../../../lib/cartodb/controllers/base');
|
||||
var LayergroupToken = require('../../../lib/cartodb/models/layergroup-token');
|
||||
|
||||
describe('torque', function() {
|
||||
@@ -14,16 +13,6 @@ describe('torque', function() {
|
||||
var server = cartodbServer(ServerOptions);
|
||||
server.setMaxListeners(0);
|
||||
|
||||
var req2paramsFn;
|
||||
before(function() {
|
||||
req2paramsFn = BaseController.prototype.req2params;
|
||||
BaseController.prototype.req2params = ServerOptions.req2params;
|
||||
});
|
||||
|
||||
after(function() {
|
||||
BaseController.prototype.req2params = req2paramsFn;
|
||||
});
|
||||
|
||||
var keysToDelete;
|
||||
beforeEach(function() {
|
||||
keysToDelete = {};
|
||||
|
||||
@@ -4,21 +4,9 @@ var assert = require('../../support/assert');
|
||||
var cartodbServer = require('../../../lib/cartodb/server');
|
||||
var ServerOptions = require('./support/ported_server_options');
|
||||
|
||||
var BaseController = require('../../../lib/cartodb/controllers/base');
|
||||
var LayergroupToken = require('../../../lib/cartodb/models/layergroup-token');
|
||||
|
||||
describe('torque boundary points', function() {
|
||||
|
||||
var req2paramsFn;
|
||||
before(function() {
|
||||
req2paramsFn = BaseController.prototype.req2params;
|
||||
BaseController.prototype.req2params = ServerOptions.req2params;
|
||||
});
|
||||
|
||||
after(function() {
|
||||
BaseController.prototype.req2params = req2paramsFn;
|
||||
});
|
||||
|
||||
var layergroupIdToDelete = null;
|
||||
|
||||
beforeEach(function() {
|
||||
|
||||
@@ -3,21 +3,7 @@ require('../../support/test_helper');
|
||||
var assert = require('../../support/assert');
|
||||
var testClient = require('./support/test_client');
|
||||
|
||||
var PortedServerOptions = require('./support/ported_server_options');
|
||||
var BaseController = require('../../../lib/cartodb/controllers/base');
|
||||
|
||||
describe('torque png renderer', function() {
|
||||
|
||||
var req2paramsFn;
|
||||
before(function() {
|
||||
req2paramsFn = BaseController.prototype.req2params;
|
||||
BaseController.prototype.req2params = PortedServerOptions.req2params;
|
||||
});
|
||||
|
||||
after(function() {
|
||||
BaseController.prototype.req2params = req2paramsFn;
|
||||
});
|
||||
|
||||
var IMAGE_TOLERANCE_PER_MIL = 20;
|
||||
|
||||
var torquePngPointsMapConfig = {
|
||||
|
||||
@@ -3,21 +3,7 @@ require('../../support/test_helper');
|
||||
var assert = require('../../support/assert');
|
||||
var testClient = require('./support/test_client');
|
||||
|
||||
var PortedServerOptions = require('./support/ported_server_options');
|
||||
var BaseController = require('../../../lib/cartodb/controllers/base');
|
||||
|
||||
describe('torque tiles at 0,0 point', function() {
|
||||
|
||||
var req2paramsFn;
|
||||
before(function() {
|
||||
req2paramsFn = BaseController.prototype.req2params;
|
||||
BaseController.prototype.req2params = PortedServerOptions.req2params;
|
||||
});
|
||||
|
||||
after(function() {
|
||||
BaseController.prototype.req2params = req2paramsFn;
|
||||
});
|
||||
|
||||
/*
|
||||
Tiles are represented as in:
|
||||
|
||||
|
||||
@@ -3,21 +3,7 @@ require('../../support/test_helper');
|
||||
var assert = require('../../support/assert');
|
||||
var testClient = require('./support/test_client');
|
||||
|
||||
var PortedServerOptions = require('./support/ported_server_options');
|
||||
var BaseController = require('../../../lib/cartodb/controllers/base');
|
||||
|
||||
describe('wrap x coordinate', function() {
|
||||
|
||||
var req2paramsFn;
|
||||
before(function() {
|
||||
req2paramsFn = BaseController.prototype.req2params;
|
||||
BaseController.prototype.req2params = PortedServerOptions.req2params;
|
||||
});
|
||||
|
||||
after(function() {
|
||||
BaseController.prototype.req2params = req2paramsFn;
|
||||
});
|
||||
|
||||
describe('renders correct tile', function() {
|
||||
var IMG_TOLERANCE_PER_MIL = 20;
|
||||
|
||||
|
||||
@@ -419,18 +419,23 @@ describe('user database timeout limit', function () {
|
||||
response: {
|
||||
status: 429,
|
||||
headers: {
|
||||
'Content-Type': 'application/json; charset=utf-8'
|
||||
'Content-Type': 'application/x-protobuf'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
this.testClient.getTile(0, 0, 0, params, (err, res, timeoutError) => {
|
||||
this.testClient.getTile(0, 0, 0, params, (err, res, tile) => {
|
||||
assert.ifError(err);
|
||||
|
||||
assert.deepEqual(timeoutError, DATASOURCE_TIMEOUT_ERROR);
|
||||
|
||||
|
||||
var tileJSON = tile.toJSON();
|
||||
assert.equal(Array.isArray(tileJSON), true);
|
||||
assert.equal(tileJSON.length, 2);
|
||||
assert.equal(tileJSON[0].name, 'errorTileSquareLayer');
|
||||
assert.equal(tileJSON[1].name, 'errorTileStripesLayer');
|
||||
|
||||
done();
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -232,7 +232,7 @@ describe('user render timeout limit', function () {
|
||||
response: {
|
||||
status: 429,
|
||||
headers: {
|
||||
'Content-Type': 'application/json; charset=utf-8'
|
||||
'Content-Type': 'application/x-protobuf'
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -240,15 +240,12 @@ describe('user render timeout limit', function () {
|
||||
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: 'datasource',
|
||||
message: 'You are over platform\'s limits. Please contact us to know more details'
|
||||
}]
|
||||
});
|
||||
|
||||
var tileJSON = tile.toJSON();
|
||||
assert.equal(Array.isArray(tileJSON), true);
|
||||
assert.equal(tileJSON.length, 2);
|
||||
assert.equal(tileJSON[0].name, 'errorTileSquareLayer');
|
||||
assert.equal(tileJSON[1].name, 'errorTileStripesLayer');
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
221
test/acceptance/vector-layergroup.js
Normal file
221
test/acceptance/vector-layergroup.js
Normal file
@@ -0,0 +1,221 @@
|
||||
require('../support/test_helper');
|
||||
|
||||
const assert = require('../support/assert');
|
||||
const TestClient = require('../support/test-client');
|
||||
const serverOptions = require('../../lib/cartodb/server_options');
|
||||
|
||||
|
||||
const POINTS_SQL_1 = `
|
||||
select
|
||||
st_setsrid(st_makepoint(x*10, x*10), 4326) as the_geom,
|
||||
st_transform(st_setsrid(st_makepoint(x*10, x*10), 4326), 3857) as the_geom_webmercator,
|
||||
x as value
|
||||
from generate_series(-3, 3) x
|
||||
`;
|
||||
|
||||
const POINTS_SQL_2 = `
|
||||
select
|
||||
st_setsrid(st_makepoint(x*10, x*10*(-1)), 4326) as the_geom,
|
||||
st_transform(st_setsrid(st_makepoint(x*10, x*10*(-1)), 4326), 3857) as the_geom_webmercator,
|
||||
x as value
|
||||
from generate_series(-3, 3) x
|
||||
`;
|
||||
|
||||
function createVectorLayergroup () {
|
||||
return {
|
||||
version: '1.6.0',
|
||||
layers: [
|
||||
{
|
||||
type: 'cartodb',
|
||||
options: {
|
||||
sql: POINTS_SQL_1
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'cartodb',
|
||||
options: {
|
||||
sql: POINTS_SQL_2
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
|
||||
const INCOMPATIBLE_LAYERS_ERROR = {
|
||||
"errors": [
|
||||
"The `mapnik` or `cartodb` layers must be consistent:" +
|
||||
" `cartocss` option is either present or voided in all layers. Mixing is not allowed."
|
||||
],
|
||||
"errors_with_context":[
|
||||
{
|
||||
"type":"mapconfig",
|
||||
"message": "The `mapnik` or `cartodb` layers must be consistent:" +
|
||||
" `cartocss` option is either present or voided in all layers. Mixing is not allowed."
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
const INVALID_FORMAT_ERROR = {
|
||||
"errors": [
|
||||
"Unsupported format: 'cartocss' option is missing for png"
|
||||
],
|
||||
"errors_with_context":[
|
||||
{
|
||||
"type": "tile",
|
||||
"message": "Unsupported format: 'cartocss' option is missing for png"
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
const suites = [{
|
||||
desc: 'mvt (mapnik)',
|
||||
usePostGIS: false
|
||||
}];
|
||||
|
||||
if (process.env.POSTGIS_VERSION === '2.4') {
|
||||
suites.push({
|
||||
desc: 'mvt (postgis)',
|
||||
usePostGIS: true
|
||||
});
|
||||
}
|
||||
|
||||
suites.forEach((suite) => {
|
||||
const { desc, usePostGIS } = suite;
|
||||
|
||||
describe(desc, function () {
|
||||
const originalUsePostGIS = serverOptions.renderer.mvt.usePostGIS;
|
||||
|
||||
before(function () {
|
||||
serverOptions.renderer.mvt.usePostGIS = usePostGIS;
|
||||
});
|
||||
|
||||
after(function (){
|
||||
serverOptions.renderer.mvt.usePostGIS = originalUsePostGIS;
|
||||
});
|
||||
|
||||
describe('vector-layergroup', function () {
|
||||
beforeEach(function () {
|
||||
this.mapConfig = createVectorLayergroup();
|
||||
this.testClient = new TestClient(this.mapConfig);
|
||||
});
|
||||
|
||||
afterEach(function (done) {
|
||||
this.testClient.drain(done);
|
||||
});
|
||||
|
||||
it('should get vector tiles from layergroup with layers w/o cartocss', function (done) {
|
||||
this.testClient.getTile(0, 0, 0, { format: 'mvt' }, (err, res, tile) => {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
|
||||
assert.equal(tile.tileSize, 4096);
|
||||
assert.equal(tile.z, 0);
|
||||
assert.equal(tile.x, 0);
|
||||
assert.equal(tile.y, 0);
|
||||
|
||||
const layer0 = JSON.parse(tile.toGeoJSONSync(0));
|
||||
|
||||
assert.equal(layer0.name, 'layer0');
|
||||
assert.equal(layer0.features[0].type, 'Feature');
|
||||
assert.equal(layer0.features[0].geometry.type, 'Point');
|
||||
|
||||
const layer1 = JSON.parse(tile.toGeoJSONSync(1));
|
||||
|
||||
assert.equal(layer1.name, 'layer1');
|
||||
assert.equal(layer1.features[0].type, 'Feature');
|
||||
assert.equal(layer1.features[0].geometry.type, 'Point');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should get vector tiles from specific layer (layer0)', function (done) {
|
||||
this.testClient.getTile(0, 0, 0, { format: 'mvt', layers: 0 }, (err, res, tile) => {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
|
||||
assert.equal(tile.tileSize, 4096);
|
||||
assert.equal(tile.z, 0);
|
||||
assert.equal(tile.x, 0);
|
||||
assert.equal(tile.y, 0);
|
||||
|
||||
const layer = JSON.parse(tile.toGeoJSONSync(0));
|
||||
|
||||
assert.equal(layer.name, 'layer0');
|
||||
assert.equal(layer.features[0].type, 'Feature');
|
||||
assert.equal(layer.features[0].geometry.type, 'Point');
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should get vector tiles from specific layer (layer1)', function (done) {
|
||||
this.testClient.getTile(0, 0, 0, { format: 'mvt', layers: 1 }, (err, res, tile) => {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
|
||||
assert.equal(tile.tileSize, 4096);
|
||||
assert.equal(tile.z, 0);
|
||||
assert.equal(tile.x, 0);
|
||||
assert.equal(tile.y, 0);
|
||||
|
||||
const layer = JSON.parse(tile.toGeoJSONSync(0));
|
||||
|
||||
assert.equal(layer.name, 'layer1');
|
||||
assert.equal(layer.features[0].type, 'Feature');
|
||||
assert.equal(layer.features[0].geometry.type, 'Point');
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should fail when the format requested is not mvt', function (done) {
|
||||
const options = {
|
||||
format: 'png',
|
||||
response: {
|
||||
status: 400,
|
||||
headers: {
|
||||
'Content-Type': 'application/json; charset=utf-8'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
this.testClient.getTile(0, 0, 0, options, (err, res, body) => {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
|
||||
assert.deepEqual(body, INVALID_FORMAT_ERROR);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should fail when the map-config mix layers with and without cartocss', function (done) {
|
||||
const response = {
|
||||
status: 400,
|
||||
headers: {
|
||||
'Content-Type': 'application/json; charset=utf-8'
|
||||
}
|
||||
};
|
||||
|
||||
const cartocss = `#layer0 { marker-fill: red; marker-width: 10; }`;
|
||||
const cartocssVersion = '2.3.0';
|
||||
|
||||
this.testClient.mapConfig.layers[0].options.cartocss = cartocss;
|
||||
this.testClient.mapConfig.layers[0].options.cartocss_version = cartocssVersion;
|
||||
|
||||
this.testClient.getLayergroup(response, (err, body) => {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
|
||||
assert.deepEqual(body, INCOMPATIBLE_LAYERS_ERROR);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
BIN
test/fixtures/previews/populated_places_simple_reduced-zoom-center.jpeg
vendored
Normal file
BIN
test/fixtures/previews/populated_places_simple_reduced-zoom-center.jpeg
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 49 KiB |
3
test/results/jpeg/.gitignore
vendored
Normal file
3
test/results/jpeg/.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
*.jpeg
|
||||
*.jpg
|
||||
*.js
|
||||
@@ -37,7 +37,7 @@ assert.imageBuffersAreSimilar = function(bufferA, bufferB, tolerance, callback)
|
||||
imagesAreSimilar(testImage, referenceImage, tolerance, callback);
|
||||
};
|
||||
|
||||
assert.imageIsSimilarToFile = function(testImage, referenceImageRelativeFilePath, tolerance, callback) {
|
||||
assert.imageIsSimilarToFile = function(testImage, referenceImageRelativeFilePath, tolerance, callback, format='png') {
|
||||
callback = callback || function(err) { assert.ifError(err); };
|
||||
|
||||
var referenceImageFilePath = path.resolve(referenceImageRelativeFilePath);
|
||||
@@ -46,19 +46,23 @@ assert.imageIsSimilarToFile = function(testImage, referenceImageRelativeFilePath
|
||||
|
||||
imagesAreSimilar(testImage, referenceImage, tolerance, function(err) {
|
||||
if (err) {
|
||||
var testImageFilePath = randomImagePath();
|
||||
testImage.save(testImageFilePath);
|
||||
var testImageFilePath = randomImagePath(format);
|
||||
testImage.save(testImageFilePath, format);
|
||||
}
|
||||
callback(err);
|
||||
});
|
||||
}, format);
|
||||
};
|
||||
|
||||
function imagesAreSimilar(testImage, referenceImage, tolerance, callback) {
|
||||
function imagesAreSimilar(testImage, referenceImage, tolerance, callback, format='png') {
|
||||
if (testImage.width() !== referenceImage.width() || testImage.height() !== referenceImage.height()) {
|
||||
return callback(new Error('Images are not the same size'));
|
||||
}
|
||||
|
||||
var pixelsDifference = referenceImage.compare(testImage);
|
||||
var options = {};
|
||||
if (format === 'jpeg') {
|
||||
options.alpha = false;
|
||||
}
|
||||
var pixelsDifference = referenceImage.compare(testImage, options);
|
||||
var similarity = pixelsDifference / (referenceImage.width() * referenceImage.height());
|
||||
var tolerancePerMil = (tolerance / 1000);
|
||||
|
||||
@@ -73,8 +77,8 @@ function imagesAreSimilar(testImage, referenceImage, tolerance, callback) {
|
||||
}
|
||||
}
|
||||
|
||||
function randomImagePath() {
|
||||
return path.resolve('test/results/png/image-test-' + Date.now() + '.png');
|
||||
function randomImagePath(format='png') {
|
||||
return path.resolve('test/results/' + format + '/image-test-' + Date.now() + '.' + format);
|
||||
}
|
||||
|
||||
assert.response = function(server, req, res, callback) {
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
PREPARE_REDIS=yes
|
||||
PREPARE_PGSQL=yes
|
||||
DOWNLOAD_SQL_FILES=yes
|
||||
PG_PARALLEL=$(pg_config --version | (awk '{$2*=1000; if ($2 >= 9600) print 1; else print 0;}' 2> /dev/null || echo 0))
|
||||
|
||||
while [ -n "$1" ]; do
|
||||
if test "$1" = "--skip-pg"; then
|
||||
@@ -92,6 +93,13 @@ if test x"$PREPARE_PGSQL" = xyes; then
|
||||
ALL_SQL_SCRIPTS="${REMOTE_SQL_SCRIPTS} ${LOCAL_SQL_SCRIPTS}"
|
||||
for i in ${ALL_SQL_SCRIPTS}
|
||||
do
|
||||
# Strip PARALLEL labels for PostgreSQL releases before 9.6
|
||||
if [ $PG_PARALLEL -eq 0 ]; then
|
||||
TMPFILE=$(mktemp /tmp/$(basename $0).XXXXXXXX)
|
||||
sed -e 's/PARALLEL \= [A-Z]*,/''/g' \
|
||||
-e 's/PARALLEL [A-Z]*/''/g' sql/$i.sql > $TMPFILE
|
||||
mv $TMPFILE sql/$i.sql
|
||||
fi
|
||||
cat sql/${i}.sql |
|
||||
sed -e 's/cartodb\./public./g' -e "s/''cartodb''/''public''/g" |
|
||||
sed "s/:PUBLICUSER/${PUBLICUSER}/" |
|
||||
|
||||
@@ -415,7 +415,7 @@ TestClient.prototype.getDataview = function(dataviewName, params, callback) {
|
||||
own_filter: params.hasOwnProperty('own_filter') ? params.own_filter : 1
|
||||
};
|
||||
|
||||
['bbox', 'bins', 'start', 'end', 'aggregation', 'offset'].forEach(function(extraParam) {
|
||||
['bbox', 'bins', 'start', 'end', 'aggregation', 'offset', 'categories'].forEach(function(extraParam) {
|
||||
if (params.hasOwnProperty(extraParam)) {
|
||||
urlParams[extraParam] = params[extraParam];
|
||||
}
|
||||
@@ -1174,4 +1174,42 @@ TestClient.prototype.setUserDatabaseTimeoutLimit = function (timeoutLimit, callb
|
||||
);
|
||||
};
|
||||
|
||||
TestClient.prototype.getAnalysesCatalog = function (params, callback) {
|
||||
var url = '/api/v1/map/analyses/catalog';
|
||||
|
||||
if (this.apiKey) {
|
||||
url += '?' + qs.stringify({api_key: this.apiKey});
|
||||
}
|
||||
|
||||
if (params.jsonp) {
|
||||
url += '&' + qs.stringify({callback: params.jsonp});
|
||||
}
|
||||
|
||||
assert.response(this.server,
|
||||
{
|
||||
url: url,
|
||||
method: 'GET',
|
||||
headers: {
|
||||
host: 'localhost',
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
},
|
||||
{
|
||||
status: params.status || 200,
|
||||
headers: {
|
||||
'Content-Type': params.jsonp ?
|
||||
'text/javascript; charset=utf-8' :
|
||||
'application/json; charset=utf-8'
|
||||
}
|
||||
},
|
||||
function(res, err) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
var parsedBody = params.jsonp ? res.body : JSON.parse(res.body);
|
||||
|
||||
return callback(null, parsedBody);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
186
yarn.lock
186
yarn.lock
@@ -2,7 +2,7 @@
|
||||
# yarn lockfile v1
|
||||
|
||||
|
||||
abaculus@cartodb/abaculus#2.0.3-cdb1:
|
||||
"abaculus@github:cartodb/abaculus#2.0.3-cdb1":
|
||||
version "2.0.3-cdb1"
|
||||
resolved "https://codeload.github.com/cartodb/abaculus/tar.gz/f5f34e1c80cdd8d49edd1d6fe3b2220ab2e23aaf"
|
||||
dependencies:
|
||||
@@ -10,7 +10,11 @@ abaculus@cartodb/abaculus#2.0.3-cdb1:
|
||||
mapnik "~3.5.0"
|
||||
sphericalmercator "1.0.x"
|
||||
|
||||
abbrev@1, abbrev@1.0.x:
|
||||
abbrev@1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8"
|
||||
|
||||
abbrev@1.0.x:
|
||||
version "1.0.9"
|
||||
resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.0.9.tgz#91b4792588a7738c25f35dd6f63752a2f8776135"
|
||||
|
||||
@@ -29,13 +33,13 @@ ajv@^4.9.1:
|
||||
json-stable-stringify "^1.0.1"
|
||||
|
||||
ajv@^5.1.0:
|
||||
version "5.2.3"
|
||||
resolved "https://registry.yarnpkg.com/ajv/-/ajv-5.2.3.tgz#c06f598778c44c6b161abafe3466b81ad1814ed2"
|
||||
version "5.5.1"
|
||||
resolved "https://registry.yarnpkg.com/ajv/-/ajv-5.5.1.tgz#b38bb8876d9e86bee994956a04e721e88b248eb2"
|
||||
dependencies:
|
||||
co "^4.6.0"
|
||||
fast-deep-equal "^1.0.0"
|
||||
fast-json-stable-stringify "^2.0.0"
|
||||
json-schema-traverse "^0.3.0"
|
||||
json-stable-stringify "^1.0.1"
|
||||
|
||||
align-text@^0.1.1, align-text@^0.1.3:
|
||||
version "0.1.4"
|
||||
@@ -211,9 +215,9 @@ camelcase@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-3.0.0.tgz#32fc4b9fcdaf845fcdf7e73bb97cac2261f0ab0a"
|
||||
|
||||
camshaft@0.59.4:
|
||||
version "0.59.4"
|
||||
resolved "https://registry.yarnpkg.com/camshaft/-/camshaft-0.59.4.tgz#7d4d0b8585fa180b5e750206aa84940870ebabfc"
|
||||
camshaft@0.60.0:
|
||||
version "0.60.0"
|
||||
resolved "https://registry.yarnpkg.com/camshaft/-/camshaft-0.60.0.tgz#0433b5a576e08cabbc9bae1e1b22305274b8b7b6"
|
||||
dependencies:
|
||||
async "^1.5.2"
|
||||
bunyan "1.8.1"
|
||||
@@ -222,7 +226,7 @@ camshaft@0.59.4:
|
||||
dot "^1.0.3"
|
||||
request "^2.69.0"
|
||||
|
||||
canvas@cartodb/node-canvas#1.6.2-cdb2:
|
||||
"canvas@github:cartodb/node-canvas#1.6.2-cdb2":
|
||||
version "1.6.2-cdb2"
|
||||
resolved "https://codeload.github.com/cartodb/node-canvas/tar.gz/8acf04557005c633f9e68524488a2657c04f3766"
|
||||
dependencies:
|
||||
@@ -240,15 +244,15 @@ carto@0.16.3:
|
||||
semver "^5.1.0"
|
||||
yargs "^4.2.0"
|
||||
|
||||
carto@CartoDB/carto#0.15.1-cdb1:
|
||||
"carto@github:cartodb/carto#0.15.1-cdb1":
|
||||
version "0.15.1-cdb1"
|
||||
resolved "https://codeload.github.com/CartoDB/carto/tar.gz/8050ec843f1f32a6469e5d1cf49602773015d398"
|
||||
resolved "https://codeload.github.com/cartodb/carto/tar.gz/8050ec843f1f32a6469e5d1cf49602773015d398"
|
||||
dependencies:
|
||||
mapnik-reference "~6.0.2"
|
||||
optimist "~0.6.0"
|
||||
underscore "~1.6.0"
|
||||
|
||||
carto@cartodb/carto#0.15.1-cdb3:
|
||||
"carto@github:cartodb/carto#0.15.1-cdb3":
|
||||
version "0.15.1-cdb3"
|
||||
resolved "https://codeload.github.com/cartodb/carto/tar.gz/945f5efb74fd1af1f5e1f69f409f9567f94fb5a7"
|
||||
dependencies:
|
||||
@@ -484,6 +488,10 @@ destroy@~1.0.4:
|
||||
version "1.0.4"
|
||||
resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80"
|
||||
|
||||
detect-libc@^1.0.2:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b"
|
||||
|
||||
diff@3.2.0:
|
||||
version "3.2.0"
|
||||
resolved "https://registry.yarnpkg.com/diff/-/diff-3.2.0.tgz#c9ce393a4b7cbd0b058a725c93df299027868ff9"
|
||||
@@ -516,7 +524,11 @@ domutils@1.5:
|
||||
dom-serializer "0"
|
||||
domelementtype "1"
|
||||
|
||||
dot@^1.0.3, dot@~1.0.2:
|
||||
dot@^1.0.3:
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/dot/-/dot-1.1.2.tgz#c7377019fc4e550798928b2b9afeb66abfa1f2f9"
|
||||
|
||||
dot@~1.0.2:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/dot/-/dot-1.0.3.tgz#f8750bfb6b03c7664eb0e6cb1eb4c66419af9427"
|
||||
|
||||
@@ -640,14 +652,22 @@ extend@~3.0.0, extend@~3.0.1:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.1.tgz#a755ea7bc1adfcc5a31ce7e762dbaadc5e636444"
|
||||
|
||||
extsprintf@1.3.0, extsprintf@^1.2.0:
|
||||
extsprintf@1.3.0:
|
||||
version "1.3.0"
|
||||
resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05"
|
||||
|
||||
extsprintf@^1.2.0:
|
||||
version "1.4.0"
|
||||
resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.0.tgz#e2689f8f356fad62cca65a3a91c5df5f9551692f"
|
||||
|
||||
fast-deep-equal@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-1.0.0.tgz#96256a3bc975595eb36d82e9929d060d893439ff"
|
||||
|
||||
fast-json-stable-stringify@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz#d5142c0caee6b1189f87d3a76111064f86c8bbf2"
|
||||
|
||||
fast-levenshtein@~2.0.4:
|
||||
version "2.0.6"
|
||||
resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917"
|
||||
@@ -772,7 +792,7 @@ getpass@^0.1.1:
|
||||
dependencies:
|
||||
assert-plus "^1.0.0"
|
||||
|
||||
glob@7.1.1, glob@^7.0.5, glob@^7.1.1:
|
||||
glob@7.1.1:
|
||||
version "7.1.1"
|
||||
resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.1.tgz#805211df04faaf1c63a3600306cdf5ade50b2ec8"
|
||||
dependencies:
|
||||
@@ -803,6 +823,17 @@ glob@^6.0.1:
|
||||
once "^1.3.0"
|
||||
path-is-absolute "^1.0.0"
|
||||
|
||||
glob@^7.0.5, glob@^7.1.1:
|
||||
version "7.1.2"
|
||||
resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.2.tgz#c19c9df9a028702d678612384a6552404c636d15"
|
||||
dependencies:
|
||||
fs.realpath "^1.0.0"
|
||||
inflight "^1.0.4"
|
||||
inherits "2"
|
||||
minimatch "^3.0.4"
|
||||
once "^1.3.0"
|
||||
path-is-absolute "^1.0.0"
|
||||
|
||||
graceful-fs@^4.1.2:
|
||||
version "4.1.11"
|
||||
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.11.tgz#0e8bdfe4d1ddb8854d64e04ea7c00e2a026e5658"
|
||||
@@ -811,9 +842,9 @@ graceful-fs@^4.1.2:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/graceful-readlink/-/graceful-readlink-1.0.1.tgz#4cafad76bc62f02fa039b2f94e9a3dd3a391a725"
|
||||
|
||||
grainstore@~1.6.0:
|
||||
version "1.6.4"
|
||||
resolved "https://registry.yarnpkg.com/grainstore/-/grainstore-1.6.4.tgz#617b93c5e2de8f544375202da89b9208a8b3d762"
|
||||
grainstore@1.7.0:
|
||||
version "1.7.0"
|
||||
resolved "https://registry.yarnpkg.com/grainstore/-/grainstore-1.7.0.tgz#28d78895c82e6201f7d0ff63af1056f3c0fda0d3"
|
||||
dependencies:
|
||||
carto "0.16.3"
|
||||
debug "~3.1.0"
|
||||
@@ -830,8 +861,8 @@ growl@1.9.2:
|
||||
resolved "https://registry.yarnpkg.com/growl/-/growl-1.9.2.tgz#0ea7743715db8d8de2c5ede1775e1b45ac85c02f"
|
||||
|
||||
handlebars@^4.0.1:
|
||||
version "4.0.10"
|
||||
resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.0.10.tgz#3d30c718b09a3d96f23ea4cc1f403c4d3ba9ff4f"
|
||||
version "4.0.11"
|
||||
resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.0.11.tgz#630a35dfe0294bc281edae6ffc5d329fc7982dcc"
|
||||
dependencies:
|
||||
async "^1.4.0"
|
||||
optimist "^0.6.1"
|
||||
@@ -967,8 +998,8 @@ inherits@2, inherits@2.0.3, inherits@~2.0.0, inherits@~2.0.1, inherits@~2.0.3:
|
||||
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de"
|
||||
|
||||
ini@~1.3.0:
|
||||
version "1.3.4"
|
||||
resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.4.tgz#0537cb79daf59b59a1a517dff706c86ec039162e"
|
||||
version "1.3.5"
|
||||
resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927"
|
||||
|
||||
invert-kv@^1.0.0:
|
||||
version "1.0.0"
|
||||
@@ -983,8 +1014,8 @@ is-arrayish@^0.2.1:
|
||||
resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d"
|
||||
|
||||
is-buffer@^1.1.5:
|
||||
version "1.1.5"
|
||||
resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.5.tgz#1f3b26ef613b214b88cbca23cc6c01d87961eecc"
|
||||
version "1.1.6"
|
||||
resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be"
|
||||
|
||||
is-builtin-module@^1.0.0:
|
||||
version "1.0.0"
|
||||
@@ -1042,8 +1073,8 @@ istanbul@~0.4.3:
|
||||
wordwrap "^1.0.0"
|
||||
|
||||
js-base64@^2.1.9:
|
||||
version "2.3.2"
|
||||
resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-2.3.2.tgz#a79a923666372b580f8e27f51845c6f7e8fbfbaf"
|
||||
version "2.4.0"
|
||||
resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-2.4.0.tgz#9e566fee624751a1d720c966cd6226d29d4025aa"
|
||||
|
||||
js-string-escape@1.0.1:
|
||||
version "1.0.1"
|
||||
@@ -1304,13 +1335,13 @@ mime@~1.3.4:
|
||||
version "1.3.6"
|
||||
resolved "https://registry.yarnpkg.com/mime/-/mime-1.3.6.tgz#591d84d3653a6b0b4a3b9df8de5aa8108e72e5e0"
|
||||
|
||||
"minimatch@2 || 3", minimatch@^3.0.0, minimatch@^3.0.2, minimatch@~3.0.2:
|
||||
"minimatch@2 || 3", minimatch@^3.0.0, minimatch@^3.0.2, minimatch@^3.0.4, minimatch@~3.0.2:
|
||||
version "3.0.4"
|
||||
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083"
|
||||
dependencies:
|
||||
brace-expansion "^1.1.7"
|
||||
|
||||
minimist@0.0.8, minimist@~0.0.1:
|
||||
minimist@0.0.8:
|
||||
version "0.0.8"
|
||||
resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d"
|
||||
|
||||
@@ -1318,6 +1349,10 @@ minimist@^1.2.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284"
|
||||
|
||||
minimist@~0.0.1:
|
||||
version "0.0.10"
|
||||
resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.10.tgz#de3f98543dbf96082be48ad1a0c7cda836301dcf"
|
||||
|
||||
minimist@~0.2.0:
|
||||
version "0.2.0"
|
||||
resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.2.0.tgz#4dffe525dae2b864c66c2e23c6271d7afdecefce"
|
||||
@@ -1344,7 +1379,11 @@ mocha@~3.4.1:
|
||||
mkdirp "0.5.1"
|
||||
supports-color "3.1.2"
|
||||
|
||||
moment@^2.10.6, moment@~2.18.1:
|
||||
moment@^2.10.6:
|
||||
version "2.19.4"
|
||||
resolved "https://registry.yarnpkg.com/moment/-/moment-2.19.4.tgz#17e5e2c6ead8819c8ecfad83a0acccb312e94682"
|
||||
|
||||
moment@~2.18.1:
|
||||
version "2.18.1"
|
||||
resolved "https://registry.yarnpkg.com/moment/-/moment-2.18.1.tgz#c36193dd3ce1c2eed2adb7c802dbbc77a81b1c0f"
|
||||
|
||||
@@ -1364,9 +1403,9 @@ mv@~2:
|
||||
ncp "~2.0.0"
|
||||
rimraf "~2.4.0"
|
||||
|
||||
nan@^2.0.8, nan@^2.3.4, nan@^2.4.0, nan@~2.7.0:
|
||||
version "2.7.0"
|
||||
resolved "https://registry.yarnpkg.com/nan/-/nan-2.7.0.tgz#d95bf721ec877e08db276ed3fc6eb78f9083ad46"
|
||||
nan@^2.0.8, nan@^2.3.4, nan@^2.4.0:
|
||||
version "2.8.0"
|
||||
resolved "https://registry.yarnpkg.com/nan/-/nan-2.8.0.tgz#ed715f3fe9de02b57a5e6252d90a96675e1f085a"
|
||||
|
||||
nan@~2.4.0:
|
||||
version "2.4.0"
|
||||
@@ -1376,6 +1415,10 @@ nan@~2.6.2:
|
||||
version "2.6.2"
|
||||
resolved "https://registry.yarnpkg.com/nan/-/nan-2.6.2.tgz#e4ff34e6c95fdfb5aecc08de6596f43605a7db45"
|
||||
|
||||
nan@~2.7.0:
|
||||
version "2.7.0"
|
||||
resolved "https://registry.yarnpkg.com/nan/-/nan-2.7.0.tgz#d95bf721ec877e08db276ed3fc6eb78f9083ad46"
|
||||
|
||||
ncp@~2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/ncp/-/ncp-2.0.0.tgz#195a21d6c46e361d2fb1281ba38b91e9df7bdbb3"
|
||||
@@ -1396,9 +1439,10 @@ nock@~2.11.0:
|
||||
propagate "0.3.x"
|
||||
|
||||
node-pre-gyp@~0.6.30, node-pre-gyp@~0.6.36, node-pre-gyp@~0.6.38:
|
||||
version "0.6.38"
|
||||
resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.6.38.tgz#e92a20f83416415bb4086f6d1fb78b3da73d113d"
|
||||
version "0.6.39"
|
||||
resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.6.39.tgz#c00e96860b23c0e1420ac7befc5044e1d78d8649"
|
||||
dependencies:
|
||||
detect-libc "^1.0.2"
|
||||
hawk "3.1.3"
|
||||
mkdirp "^0.5.1"
|
||||
nopt "^4.0.1"
|
||||
@@ -1568,6 +1612,10 @@ pg-connection-string@0.1.3:
|
||||
version "0.1.3"
|
||||
resolved "https://registry.yarnpkg.com/pg-connection-string/-/pg-connection-string-0.1.3.tgz#da1847b20940e42ee1492beaf65d49d91b245df7"
|
||||
|
||||
pg-int8@1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/pg-int8/-/pg-int8-1.0.1.tgz#943bd463bf5b71b4170115f80f8efc9a0c0eb78c"
|
||||
|
||||
pg-pool@1.*:
|
||||
version "1.8.0"
|
||||
resolved "https://registry.yarnpkg.com/pg-pool/-/pg-pool-1.8.0.tgz#f7ec73824c37a03f076f51bfdf70e340147c4f37"
|
||||
@@ -1576,15 +1624,16 @@ pg-pool@1.*:
|
||||
object-assign "4.1.0"
|
||||
|
||||
pg-types@1.*:
|
||||
version "1.12.1"
|
||||
resolved "https://registry.yarnpkg.com/pg-types/-/pg-types-1.12.1.tgz#d64087e3903b58ffaad279e7595c52208a14c3d2"
|
||||
version "1.13.0"
|
||||
resolved "https://registry.yarnpkg.com/pg-types/-/pg-types-1.13.0.tgz#75f490b8a8abf75f1386ef5ec4455ecf6b345c63"
|
||||
dependencies:
|
||||
pg-int8 "1.0.1"
|
||||
postgres-array "~1.0.0"
|
||||
postgres-bytea "~1.0.0"
|
||||
postgres-date "~1.0.0"
|
||||
postgres-interval "^1.1.0"
|
||||
|
||||
pg@cartodb/node-postgres#6.1.6-cdb1:
|
||||
"pg@github:cartodb/node-postgres#6.1.6-cdb1":
|
||||
version "6.1.6"
|
||||
resolved "https://codeload.github.com/cartodb/node-postgres/tar.gz/3eef52dd1e655f658a4ee8ac5697688b3ecfed44"
|
||||
dependencies:
|
||||
@@ -1729,8 +1778,8 @@ raw-body@2.3.2:
|
||||
unpipe "1.0.0"
|
||||
|
||||
rc@^1.1.7:
|
||||
version "1.2.1"
|
||||
resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.1.tgz#2e03e8e42ee450b8cb3dce65be1bf8974e1dfd95"
|
||||
version "1.2.2"
|
||||
resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.2.tgz#d8ce9cb57e8d64d9c7badd9876c7c34cbe3c7077"
|
||||
dependencies:
|
||||
deep-extend "~0.4.0"
|
||||
ini "~1.3.0"
|
||||
@@ -1752,7 +1801,7 @@ read-pkg@^1.0.0:
|
||||
normalize-package-data "^2.3.2"
|
||||
path-type "^1.0.0"
|
||||
|
||||
readable-stream@1.1, readable-stream@~1.1.9:
|
||||
readable-stream@1.1:
|
||||
version "1.1.13"
|
||||
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.1.13.tgz#f6eef764f514c89e2b9e23146a75ba106756d23e"
|
||||
dependencies:
|
||||
@@ -1782,6 +1831,15 @@ readable-stream@~1.0.2:
|
||||
isarray "0.0.1"
|
||||
string_decoder "~0.10.x"
|
||||
|
||||
readable-stream@~1.1.9:
|
||||
version "1.1.14"
|
||||
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.1.14.tgz#7cf4c54ef648e3813084c636dd2079e166c081d9"
|
||||
dependencies:
|
||||
core-util-is "~1.0.0"
|
||||
inherits "~2.0.1"
|
||||
isarray "0.0.1"
|
||||
string_decoder "~0.10.x"
|
||||
|
||||
redis-mpool@0.4.1, redis-mpool@~0.4.1:
|
||||
version "0.4.1"
|
||||
resolved "https://registry.yarnpkg.com/redis-mpool/-/redis-mpool-0.4.1.tgz#d917c0a4ed57a1291a9c6eb35434e6c0b7046f80"
|
||||
@@ -1891,9 +1949,9 @@ safe-json-stringify@~1:
|
||||
version "1.0.4"
|
||||
resolved "https://registry.yarnpkg.com/safe-json-stringify/-/safe-json-stringify-1.0.4.tgz#81a098f447e4bbc3ff3312a243521bc060ef5911"
|
||||
|
||||
"semver@2 || 3 || 4 || 5", semver@^5.1.0, semver@^5.3.0, semver@~5.3.0:
|
||||
version "5.3.0"
|
||||
resolved "https://registry.yarnpkg.com/semver/-/semver-5.3.0.tgz#9b2ce5d3de02d17c6012ad326aa6b4d0cf54f94f"
|
||||
"semver@2 || 3 || 4 || 5", semver@^5.1.0, semver@^5.3.0:
|
||||
version "5.4.1"
|
||||
resolved "https://registry.yarnpkg.com/semver/-/semver-5.4.1.tgz#e059c09d8571f0540823733433505d3a2f00b18e"
|
||||
|
||||
semver@4.3.2:
|
||||
version "4.3.2"
|
||||
@@ -1907,6 +1965,10 @@ semver@~5.0.3:
|
||||
version "5.0.3"
|
||||
resolved "https://registry.yarnpkg.com/semver/-/semver-5.0.3.tgz#77466de589cd5d3c95f138aa78bc569a3cb5d27a"
|
||||
|
||||
semver@~5.3.0:
|
||||
version "5.3.0"
|
||||
resolved "https://registry.yarnpkg.com/semver/-/semver-5.3.0.tgz#9b2ce5d3de02d17c6012ad326aa6b4d0cf54f94f"
|
||||
|
||||
send@0.16.1:
|
||||
version "0.16.1"
|
||||
resolved "https://registry.yarnpkg.com/send/-/send-0.16.1.tgz#a70e1ca21d1382c11d0d9f6231deb281080d7ab3"
|
||||
@@ -1965,8 +2027,8 @@ sntp@1.x.x:
|
||||
hoek "2.x.x"
|
||||
|
||||
sntp@2.x.x:
|
||||
version "2.0.2"
|
||||
resolved "https://registry.yarnpkg.com/sntp/-/sntp-2.0.2.tgz#5064110f0af85f7cfdb7d6b67a40028ce52b4b2b"
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/sntp/-/sntp-2.1.0.tgz#2c6cec14fedc2222739caf9b5c3d85d1cc5a2cc8"
|
||||
dependencies:
|
||||
hoek "4.x.x"
|
||||
|
||||
@@ -2004,10 +2066,14 @@ speedometer@~0.1.2:
|
||||
version "0.1.4"
|
||||
resolved "https://registry.yarnpkg.com/speedometer/-/speedometer-0.1.4.tgz#9876dbd2a169d3115402d48e6ea6329c8816a50d"
|
||||
|
||||
sphericalmercator@1.0.4, sphericalmercator@1.0.x, sphericalmercator@~1.0.1, sphericalmercator@~1.0.4:
|
||||
sphericalmercator@1.0.4:
|
||||
version "1.0.4"
|
||||
resolved "https://registry.yarnpkg.com/sphericalmercator/-/sphericalmercator-1.0.4.tgz#baad4e34187f06e87f2e92fc1280199fa1b01d4e"
|
||||
|
||||
sphericalmercator@1.0.x, sphericalmercator@~1.0.1, sphericalmercator@~1.0.4:
|
||||
version "1.0.5"
|
||||
resolved "https://registry.yarnpkg.com/sphericalmercator/-/sphericalmercator-1.0.5.tgz#ddc5a049e360e000d0fad9fc22c4071882584980"
|
||||
|
||||
split@^1.0.0:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/split/-/split-1.0.1.tgz#605bd9be303aa59fb35f9229fbea0ddec9ea07d9"
|
||||
@@ -2045,7 +2111,11 @@ sshpk@^1.7.0:
|
||||
jsbn "~0.1.0"
|
||||
tweetnacl "~0.14.0"
|
||||
|
||||
"statuses@>= 1.3.1 < 2", statuses@~1.3.1:
|
||||
"statuses@>= 1.3.1 < 2":
|
||||
version "1.4.0"
|
||||
resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.4.0.tgz#bb73d446da2796106efcc1b601a253d6c46bd087"
|
||||
|
||||
statuses@~1.3.1:
|
||||
version "1.3.1"
|
||||
resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.3.1.tgz#faf51b9eb74aaef3b3acf4ad5f61abf24cb7b93e"
|
||||
|
||||
@@ -2122,8 +2192,8 @@ supports-color@^3.1.0, supports-color@^3.1.2, supports-color@^3.2.3:
|
||||
has-flag "^1.0.0"
|
||||
|
||||
tar-pack@^3.4.0:
|
||||
version "3.4.0"
|
||||
resolved "https://registry.yarnpkg.com/tar-pack/-/tar-pack-3.4.0.tgz#23be2d7f671a8339376cbdb0b8fe3fdebf317984"
|
||||
version "3.4.1"
|
||||
resolved "https://registry.yarnpkg.com/tar-pack/-/tar-pack-3.4.1.tgz#e1dbc03a9b9d3ba07e896ad027317eb679a10a1f"
|
||||
dependencies:
|
||||
debug "^2.2.0"
|
||||
fstream "^1.0.10"
|
||||
@@ -2153,7 +2223,7 @@ 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-cdb4:
|
||||
"tilelive-bridge@github:cartodb/tilelive-bridge#2.3.1-cdb4":
|
||||
version "2.3.1-cdb4"
|
||||
resolved "https://codeload.github.com/cartodb/tilelive-bridge/tar.gz/faa2b638da2d119b78281575d40255cb523f6ca6"
|
||||
dependencies:
|
||||
@@ -2161,7 +2231,7 @@ tilelive-bridge@cartodb/tilelive-bridge#2.3.1-cdb4:
|
||||
mapnik-pool "~0.1.3"
|
||||
sphericalmercator "1.0.x"
|
||||
|
||||
tilelive-mapnik@cartodb/tilelive-mapnik#0.6.18-cdb3:
|
||||
"tilelive-mapnik@github:cartodb/tilelive-mapnik#0.6.18-cdb3":
|
||||
version "0.6.18-cdb3"
|
||||
resolved "https://codeload.github.com/cartodb/tilelive-mapnik/tar.gz/23bd1c31dd57d0b76c86b9f1eaf62462b3c17d01"
|
||||
dependencies:
|
||||
@@ -2198,9 +2268,9 @@ tunnel-agent@^0.6.0:
|
||||
dependencies:
|
||||
safe-buffer "^5.0.1"
|
||||
|
||||
turbo-carto@0.20.1:
|
||||
version "0.20.1"
|
||||
resolved "https://registry.yarnpkg.com/turbo-carto/-/turbo-carto-0.20.1.tgz#e9f5fa1408d9d4325a1e79333e6d242170f89e6d"
|
||||
turbo-carto@0.20.2:
|
||||
version "0.20.2"
|
||||
resolved "https://registry.yarnpkg.com/turbo-carto/-/turbo-carto-0.20.2.tgz#2b737597a65c2918432f70ea414f12fbec2b6a6f"
|
||||
dependencies:
|
||||
cartocolor "4.0.0"
|
||||
colorbrewer "1.0.0"
|
||||
@@ -2322,9 +2392,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@~4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/windshaft/-/windshaft-4.0.0.tgz#b28ffb3775db3a4f2578e79d73c9b854a1ae5854"
|
||||
windshaft@4.1.0:
|
||||
version "4.1.0"
|
||||
resolved "https://registry.yarnpkg.com/windshaft/-/windshaft-4.1.0.tgz#dc17c8369570c305171d1ab5ca130369bba04d58"
|
||||
dependencies:
|
||||
abaculus cartodb/abaculus#2.0.3-cdb1
|
||||
canvas cartodb/node-canvas#1.6.2-cdb2
|
||||
@@ -2332,7 +2402,7 @@ windshaft@~4.0.0:
|
||||
cartodb-psql "^0.10.1"
|
||||
debug "^3.1.0"
|
||||
dot "~1.0.2"
|
||||
grainstore "~1.6.0"
|
||||
grainstore "1.7.0"
|
||||
mapnik "3.5.14"
|
||||
queue-async "~1.0.7"
|
||||
redis-mpool "0.4.1"
|
||||
|
||||
Reference in New Issue
Block a user