diff --git a/NEWS.md b/NEWS.md index 7e7b4bff..5292a220 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,9 +1,16 @@ # Changelog -## 2.31.2 +## 2.31.3 Released 2016-mm-dd +## 2.31.2 + +Released 2016-04-04 + +Bug fixes: + - Overviews integration for named layers #400 + - Support wrapped queries in named layers #405 ## 2.31.1 diff --git a/docs/anonymous_maps.md b/docs/anonymous_maps.md index 0769d0fc..c7ef74aa 100644 --- a/docs/anonymous_maps.md +++ b/docs/anonymous_maps.md @@ -36,7 +36,7 @@ The response includes: Attributes | Description --- | --- -layergroupid | The ID for that map, used to compose the URL for the tiles. The final URL is: `https://{account}.cartodb.com/api/v1/map/:layergroupid/{z}/{x}/{y}.png` +layergroupid | The ID for that map, used to compose the URL for the tiles. The final URL is: `https://{username}.cartodb.com/api/v1/map/{layergroupid}/{z}/{x}/{y}.png` updated_at | The ISO date of the last time the data involved in the query was updated. metadata | Includes information about the layers. cdn_url | URLs to fetch the data using the best CDN for your zone. @@ -46,7 +46,7 @@ cdn_url | URLs to fetch the data using the best CDN for your zone. #### Call ```bash -curl 'https://documentation.cartodb.com/api/v1/map' -H 'Content-Type: application/json' -d @mapconfig.json +curl 'https://{username}.cartodb.com/api/v1/map' -H 'Content-Type: application/json' -d @mapconfig.json ``` #### Response @@ -79,7 +79,7 @@ When you have a layergroup, there are several resources for retrieving layergoup These tiles will get just the Mapnik layers. To get individual layers, see the following section. ```bash -https://documentation.cartodb.com/api/v1/map/c01a54877c62831bb51720263f91fb33:0/{z}/{x}/{y}.png +https://{username}.cartodb.com/api/v1/map/{layergroupid}/{z}/{x}/{y}.png ``` #### Individual layers @@ -89,21 +89,21 @@ The MapConfig specification holds the layers definition in a 0-based index. Laye Individual layers can be accessed using that 0-based index. For UTF grid tiles: ```bash -https://documentation.cartodb.com/api/v1/map/c01a54877c62831bb51720263f91fb33:0/:layer/{z}/{x}/{y}.grid.json +https://{username}.cartodb.com/api/v1/map/{layergroupid}/{layer}/{z}/{x}/{y}.grid.json ``` -In this case, `:layer` as 0 returns the UTF grid tiles/attributes for layer 0, the only layer in the example MapConfig. +In this case, `layer` as 0 returns the UTF grid tiles/attributes for layer 0, the only layer in the example MapConfig. If the MapConfig had a Torque layer at index 1 it could be possible to request it with: ```bash -https://documentation.cartodb.com/api/v1/map/c01a54877c62831bb51720263f91fb33:0/1/{z}/{x}/{y}.torque.json +https://{username}.cartodb.com/api/v1/map/{layergroupid}/1/{z}/{x}/{y}.torque.json ``` #### Attributes defined in `attributes` section ```bash -https://documentation.cartodb.com/api/v1/map/c01a54877c62831bb51720263f91fb33:0/:layer/attributes/:feature_id +https://{username}.cartodb.com/api/v1/map/{layergroupid}/{layer}/attributes/{feature_id} ``` Which returns JSON with the attributes defined, like: @@ -115,19 +115,19 @@ Which returns JSON with the attributes defined, like: #### Blending and layer selection ```bash -https://documentation.cartodb.com/api/v1/map/c01a54877c62831bb51720263f91fb33:0/:layer_filter/{z}/{x}/{y}.png +https://{username}.cartodb.com/api/v1/map/{layergroupid}/{layer_filter}/{z}/{x}/{y}.png ``` Note: currently format is limited to `png`. -`:layer_filter` can be used to select some layers to be rendered together. `:layer_filter` supports two formats: +`layer_filter` can be used to select some layers to be rendered together. `layer_filter` supports two formats: - `all` alias -Using `all` as `:layer_filter` will blend all layers in the layergroup +Using `all` as `layer_filter` will blend all layers in the layergroup ```bash -https://documentation.cartodb.com/api/v1/map/c01a54877c62831bb51720263f91fb33:0/all/{z}/{x}/{y}.png +https://{username}.cartodb.com/api/v1/map/{layergroupid}/all/{z}/{x}/{y}.png ``` - Filter by layer index @@ -135,7 +135,7 @@ https://documentation.cartodb.com/api/v1/map/c01a54877c62831bb51720263f91fb33:0/ A list of comma separated layer indexes can be used to just render a subset of layers. For example `0,3,4` will filter and blend layers with indexes 0, 3, and 4. ```bash -https://documentation.cartodb.com/api/v1/map/c01a54877c62831bb51720263f91fb33:0/0,3,4/{z}/{x}/{y}.png +https://{username}.cartodb.com/api/v1/map/{layergroupid}/0,3,4/{z}/{x}/{y}.png ``` Some notes about filtering: @@ -172,7 +172,7 @@ callback | JSON callback name. #### Call ```bash -curl "https://documentation.cartodb.com/api/v1/map?callback=callback&config=%7B%22version%22%3A%221.0.1%22%2C%22layers%22%3A%5B%7B%22type%22%3A%22cartodb%22%2C%22options%22%3A%7B%22sql%22%3A%22select+%2A+from+european_countries_e%22%2C%22cartocss%22%3A%22%23european_countries_e%7B+polygon-fill%3A+%23FF6600%3B+%7D%22%2C%22cartocss_version%22%3A%222.3.0%22%2C%22interactivity%22%3A%5B%22cartodb_id%22%5D%7D%7D%5D%7D" +curl "https://{username}.cartodb.com/api/v1/map?callback=callback&config=%7B%22version%22%3A%221.0.1%22%2C%22layers%22%3A%5B%7B%22type%22%3A%22cartodb%22%2C%22options%22%3A%7B%22sql%22%3A%22select+%2A+from+european_countries_e%22%2C%22cartocss%22%3A%22%23european_countries_e%7B+polygon-fill%3A+%23FF6600%3B+%7D%22%2C%22cartocss_version%22%3A%222.3.0%22%2C%22interactivity%22%3A%5B%22cartodb_id%22%5D%7D%7D%5D%7D" ``` #### Response diff --git a/docs/named_maps.md b/docs/named_maps.md index c933693a..3606465a 100644 --- a/docs/named_maps.md +++ b/docs/named_maps.md @@ -122,7 +122,7 @@ view (optional) | extra keys to specify the view area for the map. It can be use Placeholders are variables that can be placed in your template.json file. Placeholders need to be defined with a `type` and a default value for MapConfigs. See details about defining a MapConfig `type` for [Layergoup configurations](http://docs.cartodb.com/cartodb-platform/maps-api/mapconfig/#layergroup-configurations). -Valid placeholder names start with a letter and can only contain letters, numbers, or underscores. They have to be written between the `<%=` and `%>` strings in order to be replaced. +Valid placeholder names start with a letter and can only contain letters, numbers, or underscores. They have to be written between the `<%=` and `%>` strings in order to be replaced inside the Named Maps API. #### Example @@ -155,7 +155,7 @@ This is the call for creating the Named Map. It is sending the template.json fil curl -X POST \ -H 'Content-Type: application/json' \ -d @template.json \ - 'https://documentation.cartodb.com/api/v1/map/named?api_key=APIKEY' + 'https://{username}.cartodb.com/api/v1/map/named?api_key={api_key}' ``` #### Response @@ -164,7 +164,7 @@ The response back from the API provides the name of your MapConfig as a template ```javascript { - "template_id":"name", + "template_id":"name" } ``` @@ -175,7 +175,7 @@ Instantiating a Named Map allows you to fetch the map tiles. You can use the Map #### Definition ```html -POST /api/v1/map/named/:template_name +POST /api/v1/map/named/{template_name} ``` #### Param @@ -198,7 +198,7 @@ The fields you pass as `params.json` depend on the variables allowed by the Name #### Example -You can initialize a template map by passing all of the required parameters in a POST to `/api/v1/map/named/:template_name`. +You can initialize a template map by passing all of the required parameters in a POST to `/api/v1/map/named/{template_name}`. Valid auth token will be needed, if required by the template. @@ -209,7 +209,7 @@ Valid auth token will be needed, if required by the template. curl -X POST \ -H 'Content-Type: application/json' \ -d @params.json \ - 'https://documentation.cartodb.com/api/v1/map/named/@template_name?auth_token=AUTH_TOKEN' + 'https://{username}.cartodb.com/api/v1/map/named/{template_name}?auth_token={auth_token}' ``` #### Response @@ -236,7 +236,7 @@ You can then use the `layergroupid` for fetching tiles and grids as you would no #### Definition ```bash -PUT /api/v1/map/named/:template_name +PUT /api/v1/map/named/{template_name} ``` #### Params @@ -261,7 +261,7 @@ Updating a Named Map removes all the Named Map instances, so they need to be ini curl -X PUT \ -H 'Content-Type: application/json' \ -d @template.json \ - 'https://documentation.cartodb.com/api/v1/map/named/:template_name?api_key=APIKEY' + 'https://{username}.cartodb.com/api/v1/map/named/{template_name}?api_key={api_key}' ``` #### Response @@ -289,7 +289,7 @@ Deletes the specified template map from the server, and disables any previously #### Definition ```bash -DELETE /api/v1/map/named/:template_name +DELETE /api/v1/map/named/{template_name} ``` #### Params @@ -303,7 +303,7 @@ api_key | is required #### Call ```bash -curl -X DELETE 'https://documentation.cartodb.com/api/v1/map/named/:template_name?api_key=APIKEY' +curl -X DELETE 'https://{username}.cartodb.com/api/v1/map/named/{template_name}?api_key={api_key}' ``` #### Response @@ -337,7 +337,7 @@ api_key | is required #### Call ```bash -curl -X GET 'https://documentation.cartodb.com/api/v1/map/named?api_key=APIKEY' +curl -X GET 'https://{username}.cartodb.com/api/v1/map/named?api_key={api_key}' ``` #### Response @@ -363,7 +363,7 @@ This gets the definition of a requested template. #### Definition ```bash -GET /api/v1/map/named/:template_name +GET /api/v1/map/named/{template_name} ``` #### Params @@ -377,7 +377,7 @@ api_key | is required #### Call ```bash -curl -X GET 'https://documentation.cartodb.com/api/v1/map/named/:template_name?api_key=APIKEY' +curl -X GET 'https://{username}.cartodb.com/api/v1/map/named/{template_name}?api_key={api_key}' ``` #### Response @@ -403,7 +403,7 @@ If using a [JSONP](https://en.wikipedia.org/wiki/JSONP) (for old browsers) reque #### Definition ```bash -GET /api/v1/map/named/:template_name/jsonp +GET /api/v1/map/named/{template_name}/jsonp ``` #### Params @@ -418,7 +418,7 @@ callback | JSON callback name #### Call ```bash -curl 'https://documentation.cartodb.com/api/v1/map/named/:template_name/jsonp?auth_token=AUTH_TOKEN&callback=callback&config=template_params_json' +curl 'https://{username}.cartodb.com/api/v1/map/named/{template_name}/jsonp?auth_token={auth_token}&callback=callback&config=template_params_json' ``` #### Response @@ -455,7 +455,7 @@ You can use a Named Map that you created (which is defined by its `name`), to cr ```javascript { - user_name: '{your_user_name}', // Required + user_name: '{username}', // Required type: 'namedmap', // Required named_map: { name: '{name_of_map}', // Required, the 'name' of the Named Map that you have created @@ -506,20 +506,20 @@ Authenticated users, with an auth token, can use XYZ-based URLs to fetch tiles d To call a template_id in a URL: -`/:template_id/:layer/:z/:x/:y.(:format)` +`/{template_id}/{layer}/{z}/{x}/{y}.{format}` For example, a complete URL might appear as: -"https://{your user name}.cartodb.com/api/v1/map/named/{template_id}/{layer}/{z}/{x}/{y}.png" +"https://{username}.cartodb.com/api/v1/map/named/{template_id}/{layer}/{z}/{x}/{y}.png" The placeholders indicate the following: - [`template_id`](http://docs.cartodb.com/cartodb-platform/maps-api/named-maps/#response) is the response of your Named Map. - layers can be a number (referring to the # layer of your map), all layers of your map, or a list of layers. - - To show just the basemap layer, enter the number value `0` in the layer placeholder "https://{your user name}.cartodb.com/api/v1/map/named/{template_id}/0/{z}/{x}/{y}.png" - - To show the first layer, enter the number value `1` in the layer placeholder "https://{your user name}.cartodb.com/api/v1/map/named/{template_id}/1/{z}/{x}/{y}.png" - - To show all layers, enter the value `all` for the layer placeholder "https://{your user name}.cartodb.com/api/v1/map/named/{template_id}/all/{z}/{x}/{y}.png" - - To show a [list of layers](http://docs.cartodb.com/cartodb-platform/maps-api/anonymous-maps/#blending-and-layer-selection), enter the comma separated layer value as 0,1,2 in the layer placeholder. For example, to show the basemap and the first layer, "https://{your user name}.cartodb.com/api/v1/map/named/{template_id}/0,1/{z}/{x}/{y}.png" + - To show just the basemap layer, enter the number value `0` in the layer placeholder "https://{username}.cartodb.com/api/v1/map/named/{template_id}/0/{z}/{x}/{y}.png" + - To show the first layer, enter the number value `1` in the layer placeholder "https://{username}.cartodb.com/api/v1/map/named/{template_id}/1/{z}/{x}/{y}.png" + - To show all layers, enter the value `all` for the layer placeholder "https://{username}.cartodb.com/api/v1/map/named/{template_id}/all/{z}/{x}/{y}.png" + - To show a [list of layers](http://docs.cartodb.com/cartodb-platform/maps-api/anonymous-maps/#blending-and-layer-selection), enter the comma separated layer value as 0,1,2 in the layer placeholder. For example, to show the basemap and the first layer, "https://{username}.cartodb.com/api/v1/map/named/{template_id}/0,1/{z}/{x}/{y}.png" ### Get Mapnik Retina Tiles @@ -528,4 +528,4 @@ Mapnik Retina tiles are not directly supported for Named Maps, so you cannot use Instantiate the map by using your `layergroupid` in the token placeholder: - `:token/:z/:x/:y@:scale_factor?x.:format` + `{token}/{z}/{x}/{y}@{scale_factor}?{x}.{format}` diff --git a/docs/quickstart.md b/docs/quickstart.md index 057a74e4..62099396 100644 --- a/docs/quickstart.md +++ b/docs/quickstart.md @@ -22,10 +22,10 @@ $.ajax({ type: 'POST', dataType: 'json', contentType: 'application/json', - url: 'https://documentation.cartodb.com/api/v1/map', + url: 'https://{username}.cartodb.com/api/v1/map', data: JSON.stringify(mapconfig), success: function(data) { - var templateUrl = 'https://documentation.cartodb.com/api/v1/map/' + data.layergroupid + '/{z}/{x}/{y}.png' + var templateUrl = 'https://{username}.cartodb.com/api/v1/map/' + data.layergroupid + '/{z}/{x}/{y}.png' console.log(templateUrl); } }) @@ -61,7 +61,7 @@ The MapConfig needs to be sent to CartoDB's Map API using an authenticated call. #### Call ```bash -curl 'https://{account}.cartodb.com/api/v1/map/named?api_key=APIKEY' -H 'Content-Type: application/json' -d @mapconfig.json +curl 'https://{username}.cartodb.com/api/v1/map/named?api_key={api_key}' -H 'Content-Type: application/json' -d @mapconfig.json ``` To get the `URL` to fetch the tiles you need to instantiate the map, where `template_id` is the template name from the previous response. @@ -69,7 +69,7 @@ To get the `URL` to fetch the tiles you need to instantiate the map, where `temp #### Call ```bash -curl -X POST 'https://{account}.cartodb.com/api/v1/map/named/:template_id' -H 'Content-Type: application/json' +curl -X POST 'https://{username}.cartodb.com/api/v1/map/named/{template_id}' -H 'Content-Type: application/json' ``` The response will return JSON with properties for the `layergroupid`, the timestamp (`last_updated`) of the last data modification and some key/value pairs with `metadata` for the `layers`. @@ -96,5 +96,5 @@ Note: all `layers` in `metadata` will always have a `type` string and a `meta` d You can use the `layergroupid` to instantiate a URL template for accessing tiles on the client. Here we use the `layergroupid` from the example response above in this URL template: ```bash -https://documentation.cartodb.com/api/v1/map/c01a54877c62831bb51720263f91fb33:0/{z}/{x}/{y}.png +https://{username}.cartodb.com/api/v1/map/{layergroupid}/{z}/{x}/{y}.png ``` diff --git a/docs/static_maps_api.md b/docs/static_maps_api.md index 9fc7ae4f..ab28c9c5 100644 --- a/docs/static_maps_api.md +++ b/docs/static_maps_api.md @@ -11,18 +11,18 @@ Begin by instantiating either a Named or Anonymous Map using the `layergroupid t #### Definition ```bash -GET /api/v1/map/static/center/:token/:z/:lat/:lng/:width/:height.:format +GET /api/v1/map/static/center/{token}/{z}/{lat}/{lng}/{width}/{height}.{format} ``` #### Params Param | Description --- | --- -:token | the layergroupid token from the map instantiation -:z | the zoom level of the map -:lat | the latitude for the center of the map +token | the layergroupid token from the map instantiation +z | the zoom level of the map +lat | the latitude for the center of the map -:format | the format for the image, supported types: `png`, `jpg` +format | the format for the image, supported types: `png`, `jpg` --- | --- |_ jpg | will have a default quality of 85. @@ -31,33 +31,33 @@ Param | Description #### Definition ```bash -GET /api/v1/map/static/bbox/:token/:bbox/:width/:height.:format` +GET /api/v1/map/static/bbox/{token}/{bbox}/{width}/{height}.{format}` ``` #### Params Param | Description --- | --- -:token | the layergroupid token from the map instantiation +token | the layergroupid token from the map instantiation -:bbox | the bounding box in WGS 84 (EPSG:4326), comma separated values for: +bbox | the bounding box in WGS 84 (EPSG:4326), comma separated values for: --- | --- | LowerCorner longitude, in decimal degrees (aka most western) | LowerCorner latitude, in decimal degrees (aka most southern) | UpperCorner longitude, in decimal degrees (aka most eastern) | UpperCorner latitude, in decimal degrees (aka most northern) -:width | the width in pixels for the output image -:height | the height in pixels for the output image -:format | the format for the image, supported types: `png`, `jpg` +width | the width in pixels for the output image +height | the height in pixels for the output image +format | the format for the image, supported types: `png`, `jpg` -:format | the bounding box in WGS 84 (EPSG:4326), comma separated values for: +format | the bounding box in WGS 84 (EPSG:4326), comma separated values for: --- | --- |_ jpg | will have a default quality of 85. Note: you can see this endpoint as ```bash -GET /api/v1/map/static/bbox/:token/:west,:south,:east,:north/:width/:height.:format` +GET /api/v1/map/static/bbox/{token}/{west},{south},{east},{north}/{width}/{height}.{format}` ``` ### Named Map @@ -65,19 +65,18 @@ GET /api/v1/map/static/bbox/:token/:west,:south,:east,:north/:width/:height.:for #### Definition ```bash -GET /api/v1/map/static/named/:name/:width/:height.:format +GET /api/v1/map/static/named/{name}/{width}/{height}.{format} ``` #### Params Param | Description --- | --- -:name | the name of the Named Map -:width | the width in pixels for the output image -:height | the height in pixels for the output image -:height | the height in pixels for the output image +name | the name of the Named Map +width | the width in pixels for the output image +height | the height in pixels for the output image -:format | the format for the image, supported types: `png`, `jpg` +format | the format for the image, supported types: `png`, `jpg` --- | --- |_ jpg | will have a default quality of 85. @@ -161,7 +160,7 @@ After instantiating a map from a CartoDB account: #### Call ```bash - GET /api/v1/map/static/center/4b615ff367e498e770e7d05e99181873:1420231989550.8699/14/40.71502926732618/-73.96039009094238/600/400.png + GET /api/v1/map/static/center/{layergroupid}/{z}/{x}/{y}/{width}/{height}.png ``` #### Response diff --git a/lib/cartodb/cache/named_map_provider_cache.js b/lib/cartodb/cache/named_map_provider_cache.js index 7bb8e25c..df536af5 100644 --- a/lib/cartodb/cache/named_map_provider_cache.js +++ b/lib/cartodb/cache/named_map_provider_cache.js @@ -7,12 +7,13 @@ var queue = require('queue-async'); var LruCache = require("lru-cache"); -function NamedMapProviderCache(templateMaps, pgConnection, userLimitsApi, turboCartocssAdapter) { +function NamedMapProviderCache(templateMaps, pgConnection, userLimitsApi, overviewsAdapter, turboCartocssAdapter) { this.templateMaps = templateMaps; this.pgConnection = pgConnection; this.userLimitsApi = userLimitsApi; this.namedLayersAdapter = new MapConfigNamedLayersAdapter(templateMaps); + this.overviewsAdapter = overviewsAdapter; this.turboCartocssAdapter = turboCartocssAdapter; this.providerCache = new LruCache({ max: 2000 }); @@ -31,6 +32,7 @@ NamedMapProviderCache.prototype.get = function(user, templateId, config, authTok this.pgConnection, this.userLimitsApi, this.namedLayersAdapter, + this.overviewsAdapter, this.turboCartocssAdapter, user, templateId, diff --git a/lib/cartodb/controllers/map.js b/lib/cartodb/controllers/map.js index 60494284..97d949df 100644 --- a/lib/cartodb/controllers/map.js +++ b/lib/cartodb/controllers/map.js @@ -19,7 +19,6 @@ var MapConfigNamedLayersAdapter = require('../models/mapconfig_named_layers_adap var AnalysisMapConfigAdapter = require('../models/analysis_mapconfig_adapter'); var NamedMapMapConfigProvider = require('../models/mapconfig/named_map_provider'); var CreateLayergroupMapConfigProvider = require('../models/mapconfig/create_layergroup_provider'); -var MapConfigOverviewsAdapter = require('../models/mapconfig_overviews_adapter'); /** * @param {AuthApi} authApi @@ -34,8 +33,8 @@ var MapConfigOverviewsAdapter = require('../models/mapconfig_overviews_adapter') * @constructor */ function MapController(authApi, pgConnection, templateMaps, mapBackend, metadataBackend, - overviewsMetadataApi, - surrogateKeysCache, userLimitsApi, layergroupAffectedTables, turboCartoCssAdapter) { + surrogateKeysCache, userLimitsApi, layergroupAffectedTables, + overviewsAdapter, turboCartoCssAdapter) { BaseController.call(this, authApi, pgConnection); @@ -43,7 +42,6 @@ function MapController(authApi, pgConnection, templateMaps, mapBackend, metadata this.templateMaps = templateMaps; this.mapBackend = mapBackend; this.metadataBackend = metadataBackend; - this.overviewsMetadataApi = overviewsMetadataApi; this.surrogateKeysCache = surrogateKeysCache; this.userLimitsApi = userLimitsApi; this.layergroupAffectedTables = layergroupAffectedTables; @@ -51,7 +49,7 @@ function MapController(authApi, pgConnection, templateMaps, mapBackend, metadata this.analysisMapConfigAdapter = new AnalysisMapConfigAdapter(); this.namedLayersAdapter = new MapConfigNamedLayersAdapter(templateMaps); - this.overviewsAdapter = new MapConfigOverviewsAdapter(this.overviewsMetadataApi); + this.overviewsAdapter = overviewsAdapter; } util.inherits(MapController, BaseController); @@ -261,6 +259,7 @@ MapController.prototype.instantiateTemplate = function(req, res, prepareParamsFn self.pgConnection, self.userLimitsApi, self.namedLayersAdapter, + self.overviewsAdapter, self.turboCartoCssAdapter, cdbuser, req.params.template_id, @@ -270,22 +269,6 @@ MapController.prototype.instantiateTemplate = function(req, res, prepareParamsFn ); mapConfigProvider.getMapConfig(this); }, - function addOverviewsInformation(err, requestMapConfig, rendererParams/*, context*/) { - assert.ifError(err); - var next = this; - self.overviewsAdapter.getLayers(req.context.user, requestMapConfig.layers, - function(err, layers) { - if (err) { - return next(err); - } - - if (layers) { - requestMapConfig.layers = layers; - } - return next(null, requestMapConfig, rendererParams); - } - ); - }, function createLayergroup(err, mapConfig_, rendererParams) { assert.ifError(err); mapConfig = mapConfig_; diff --git a/lib/cartodb/models/mapconfig/named_map_provider.js b/lib/cartodb/models/mapconfig/named_map_provider.js index ebf1f55c..deeb39a2 100644 --- a/lib/cartodb/models/mapconfig/named_map_provider.js +++ b/lib/cartodb/models/mapconfig/named_map_provider.js @@ -11,13 +11,15 @@ var QueryTables = require('cartodb-query-tables'); * @constructor * @type {NamedMapMapConfigProvider} */ -function NamedMapMapConfigProvider(templateMaps, pgConnection, userLimitsApi, namedLayersAdapter, - turboCartoCssAdapter, owner, templateId, config, authToken, params) { +function NamedMapMapConfigProvider(templateMaps, pgConnection, userLimitsApi, + namedLayersAdapter, overviewsAdapter, turboCartoCssAdapter, + owner, templateId, config, authToken, params) { this.templateMaps = templateMaps; this.pgConnection = pgConnection; this.userLimitsApi = userLimitsApi; this.namedLayersAdapter = namedLayersAdapter; this.turboCartoCssAdapter = turboCartoCssAdapter; + this.overviewsAdapter = overviewsAdapter; this.owner = owner; this.templateName = templateName(templateId); @@ -92,6 +94,22 @@ NamedMapMapConfigProvider.prototype.getMapConfig = function(callback) { } ); }, + function addOverviewsInformation(err, _mapConfig, datasource) { + assert.ifError(err); + var next = this; + + self.overviewsAdapter.getLayers(self.owner, _mapConfig.layers, function(err, layers) { + if (err) { + return next(err); + } + + if (layers) { + _mapConfig.layers = layers; + } + + return next(null, _mapConfig, datasource); + }); + }, function parseTurboCartoCss(err, _mapConfig, datasource) { assert.ifError(err); var next = this; diff --git a/lib/cartodb/server.js b/lib/cartodb/server.js index 28eea3c5..5f628501 100644 --- a/lib/cartodb/server.js +++ b/lib/cartodb/server.js @@ -30,6 +30,8 @@ var PgConnection = require('./backends/pg_connection'); var timeoutErrorTilePath = __dirname + '/../../assets/render-timeout-fallback.png'; var timeoutErrorTile = require('fs').readFileSync(timeoutErrorTilePath, {encoding: null}); +var MapConfigOverviewsAdapter = require('./models/mapconfig_overviews_adapter'); + var TurboCartocssParser = require('./utils/style/turbo-cartocss-parser'); var TurboCartocssAdapter = require('./utils/style/turbo-cartocss-adapter'); @@ -142,6 +144,8 @@ module.exports = function(serverOptions) { var layergroupAffectedTablesCache = new LayergroupAffectedTablesCache(); app.layergroupAffectedTablesCache = layergroupAffectedTablesCache; + var overviewsAdapter = new MapConfigOverviewsAdapter(overviewsMetadataApi); + var turboCartoCssParser = new TurboCartocssParser(pgQueryRunner); var turboCartocssAdapter = new TurboCartocssAdapter(turboCartoCssParser); @@ -149,6 +153,7 @@ module.exports = function(serverOptions) { templateMaps, pgConnection, userLimitsApi, + overviewsAdapter, turboCartocssAdapter ); @@ -184,10 +189,10 @@ module.exports = function(serverOptions) { templateMaps, mapBackend, metadataBackend, - overviewsMetadataApi, surrogateKeysCache, userLimitsApi, layergroupAffectedTablesCache, + overviewsAdapter, turboCartocssAdapter ).register(app); diff --git a/lib/cartodb/utils/overviews_query_rewriter.js b/lib/cartodb/utils/overviews_query_rewriter.js index 622c2018..2b253908 100644 --- a/lib/cartodb/utils/overviews_query_rewriter.js +++ b/lib/cartodb/utils/overviews_query_rewriter.js @@ -168,9 +168,16 @@ OverviewsQueryRewriter.prototype.query = function(query, data) { }; OverviewsQueryRewriter.prototype.is_supported_query = function(sql) { - return !!sql.match( - /^\s*SELECT\s+[\*a-z0-9_,\s]+?\s+FROM\s+((\"[^"]+\"|[a-z0-9_]+)\.)?(\"[^"]+\"|[a-z0-9_]+)\s*;?\s*$/i + var basic_query = /\s*SELECT\s+[\*a-z0-9_,\s]+?\s+FROM\s+((\"[^"]+\"|[a-z0-9_]+)\.)?(\"[^"]+\"|[a-z0-9_]+)\s*;?\s*/i; + var unwrapped_query = new RegExp("^"+basic_query.source+"$", 'i'); + // queries for named maps are wrapped like this: + var wrapped_query = new RegExp( + "^\\s*SELECT\\s+\\*\\s+FROM\\s+\\(" + + basic_query.source + + "\\)\\s+AS\\s+wrapped_query\\s+WHERE\\s+\\d+=1\\s*$", + 'i' ); + return !!(sql.match(unwrapped_query) || sql.match(wrapped_query)); }; OverviewsQueryRewriter.prototype.overviews_metadata = function(data) { diff --git a/test/acceptance/overviews_metadata_named_maps.js b/test/acceptance/overviews_metadata_named_maps.js new file mode 100644 index 00000000..38326986 --- /dev/null +++ b/test/acceptance/overviews_metadata_named_maps.js @@ -0,0 +1,174 @@ +var test_helper = require('../support/test_helper'); + +var assert = require('../support/assert'); +var CartodbWindshaft = require(__dirname + '/../../lib/cartodb/server'); +var serverOptions = require(__dirname + '/../../lib/cartodb/server_options'); +var server = new CartodbWindshaft(serverOptions); + +var LayergroupToken = require('../../lib/cartodb/models/layergroup_token'); + +var RedisPool = require('redis-mpool'); + +var step = require('step'); + +var windshaft = require('windshaft'); + + +describe('overviews metadata for named maps', function() { + // configure redis pool instance to use in tests + var redisPool = new RedisPool(global.environment.redis); + + var overviews_layer = { + type: 'cartodb', + options: { + sql: 'SELECT * FROM test_table_overviews', + cartocss: '#layer { marker-fill: black; }', + cartocss_version: '2.3.0' + } + }; + + var non_overviews_layer = { + type: 'cartodb', + options: { + sql: 'SELECT * FROM test_table', + cartocss: '#layer { marker-fill: black; }', + cartocss_version: '2.3.0' + } + }; + + var keysToDelete; + + beforeEach(function() { + keysToDelete = {}; + }); + + afterEach(function(done) { + test_helper.deleteRedisKeys(keysToDelete, done); + }); + + var templateId = 'overviews-template-1'; + + var template = { + version: '0.0.1', + name: templateId, + auth: { method: 'open' }, + layergroup: { + version: '1.0.0', + layers: [overviews_layer, non_overviews_layer] + } + }; + + it("should add overviews data to layers", function(done) { + step( + function postTemplate() + { + var next = this; + + assert.response(server, { + url: '/api/v1/map/named?api_key=1234', + method: 'POST', + headers: {host: 'localhost', 'Content-Type': 'application/json' }, + data: JSON.stringify(template) + }, {}, function(res, err) { + next(err, res); + }); + }, + function checkTemplate(err, res) { + assert.ifError(err); + + var next = this; + assert.equal(res.statusCode, 200); + assert.deepEqual(JSON.parse(res.body), { + template_id: templateId + }); + next(null); + }, + function instantiateTemplate(err) { + assert.ifError(err); + + var next = this; + assert.response(server, { + url: '/api/v1/map/named/' + templateId, + method: 'POST', + headers: { + host: 'localhost', + 'Content-Type': 'application/json' + } + }, {}, + function(res, err) { + return next(err, res); + }); + + }, + function checkInstanciation(err, res) { + assert.ifError(err); + + var next = this; + + assert.equal(res.statusCode, 200); + + var parsedBody = JSON.parse(res.body); + + keysToDelete['map_cfg|' + LayergroupToken.parse(parsedBody.layergroupid).token] = 0; + keysToDelete['user:localhost:mapviews:global'] = 5; + + assert.ok(parsedBody.layergroupid); + assert.ok(parsedBody.last_updated); + + next(null, parsedBody.layergroupid); + }, + + function checkMapconfig(err, layergroupId) + { + assert.ifError(err); + + var next = this; + + var mapStore = new windshaft.storage.MapStore({ + pool: redisPool, + expire_time: 500000 + }); + mapStore.load(LayergroupToken.parse(layergroupId).token, function(err, mapConfig) { + assert.ifError(err); + assert.deepEqual(non_overviews_layer, mapConfig._cfg.layers[1]); + assert.equal(mapConfig._cfg.layers[0].type, 'cartodb'); + assert.ok(mapConfig._cfg.layers[0].options.query_rewrite_data); + var expected_data = { + overviews: { + test_table_overviews: { + 1: { table: '_vovw_1_test_table_overviews' }, + 2: { table: '_vovw_2_test_table_overviews' } + } + } + }; + assert.deepEqual(mapConfig._cfg.layers[0].options.query_rewrite_data, expected_data); + }); + + next(err); + }, + function deleteTemplate(err) { + assert.ifError(err); + + var next = this; + + assert.response(server, { + url: '/api/v1/map/named/' + templateId + '?api_key=1234', + method: 'DELETE', + headers: { host: 'localhost' } + }, {}, function (res, err) { + next(err, res); + }); + }, + function checkDeleteTemplate(err, res) { + assert.ifError(err); + assert.equal(res.statusCode, 204); + assert.ok(!res.body); + + return null; + }, + function finish(err) { + done(err); + } + ); + }); +}); diff --git a/test/unit/cartodb/overviews_query_rewriter.js b/test/unit/cartodb/overviews_query_rewriter.js index b58b55b0..f79e82c7 100644 --- a/test/unit/cartodb/overviews_query_rewriter.js +++ b/test/unit/cartodb/overviews_query_rewriter.js @@ -395,5 +395,32 @@ describe('Overviews query rewriter', function() { done(); }); - + it('generates overviews for wrapped query', function(done){ + var sql = "SELECT * FROM (SELECT * FROM table1) AS wrapped_query WHERE 1=1"; + var data = { + overviews: { + table1: { + 0: { table: 'table1_ov0' }, + 1: { table: 'table1_ov1' }, + 2: { table: 'table1_ov2' } + } + } + }; + var overviews_sql = overviewsQueryRewriter.query(sql, data); + var expected_sql = "\ + WITH\ + _vovw_scale AS ( SELECT ZoomLevel() AS _vovw_z )\ + SELECT * FROM (SELECT * FROM (\ + SELECT * FROM table1_ov0, _vovw_scale WHERE _vovw_z = 0\ + UNION ALL\ + SELECT * FROM table1_ov1, _vovw_scale WHERE _vovw_z = 1\ + UNION ALL\ + SELECT * FROM table1_ov2, _vovw_scale WHERE _vovw_z = 2\ + UNION ALL\ + SELECT * FROM table1, _vovw_scale WHERE _vovw_z > 2\ + ) AS _vovw_table1) AS wrapped_query WHERE 1=1\ + "; + assertSameSql(overviews_sql, expected_sql); + done(); + }); });