diff --git a/lib/cartodb/cache/named_map_provider_cache.js b/lib/cartodb/cache/named_map_provider_cache.js index df536af5..80195e41 100644 --- a/lib/cartodb/cache/named_map_provider_cache.js +++ b/lib/cartodb/cache/named_map_provider_cache.js @@ -2,17 +2,21 @@ var _ = require('underscore'); var dot = require('dot'); var NamedMapMapConfigProvider = require('../models/mapconfig/named_map_provider'); var MapConfigNamedLayersAdapter = require('../models/mapconfig_named_layers_adapter'); +var AnalysisMapConfigAdapter = require('../models/analysis_mapconfig_adapter'); var templateName = require('../backends/template_maps').templateName; var queue = require('queue-async'); var LruCache = require("lru-cache"); -function NamedMapProviderCache(templateMaps, pgConnection, userLimitsApi, overviewsAdapter, turboCartocssAdapter) { +function NamedMapProviderCache(templateMaps, pgConnection, metadataBackend, userLimitsApi, overviewsAdapter, + turboCartocssAdapter) { this.templateMaps = templateMaps; this.pgConnection = pgConnection; + this.metadataBackend = metadataBackend; this.userLimitsApi = userLimitsApi; this.namedLayersAdapter = new MapConfigNamedLayersAdapter(templateMaps); + this.analysisMapConfigAdapter = new AnalysisMapConfigAdapter(); this.overviewsAdapter = overviewsAdapter; this.turboCartocssAdapter = turboCartocssAdapter; @@ -30,10 +34,12 @@ NamedMapProviderCache.prototype.get = function(user, templateId, config, authTok namedMapProviders[providerKey] = new NamedMapMapConfigProvider( this.templateMaps, this.pgConnection, + this.metadataBackend, this.userLimitsApi, this.namedLayersAdapter, this.overviewsAdapter, this.turboCartocssAdapter, + this.analysisMapConfigAdapter, user, templateId, config, diff --git a/lib/cartodb/controllers/map.js b/lib/cartodb/controllers/map.js index 97d949df..3e73dc43 100644 --- a/lib/cartodb/controllers/map.js +++ b/lib/cartodb/controllers/map.js @@ -257,10 +257,12 @@ MapController.prototype.instantiateTemplate = function(req, res, prepareParamsFn mapConfigProvider = new NamedMapMapConfigProvider( self.templateMaps, self.pgConnection, + self.metadataBackend, self.userLimitsApi, self.namedLayersAdapter, self.overviewsAdapter, self.turboCartoCssAdapter, + self.analysisMapConfigAdapter, cdbuser, req.params.template_id, templateParams, diff --git a/lib/cartodb/models/mapconfig/named_map_provider.js b/lib/cartodb/models/mapconfig/named_map_provider.js index deeb39a2..b4646f7b 100644 --- a/lib/cartodb/models/mapconfig/named_map_provider.js +++ b/lib/cartodb/models/mapconfig/named_map_provider.js @@ -11,14 +11,16 @@ var QueryTables = require('cartodb-query-tables'); * @constructor * @type {NamedMapMapConfigProvider} */ -function NamedMapMapConfigProvider(templateMaps, pgConnection, userLimitsApi, - namedLayersAdapter, overviewsAdapter, turboCartoCssAdapter, +function NamedMapMapConfigProvider(templateMaps, pgConnection, metadataBackend, userLimitsApi, + namedLayersAdapter, overviewsAdapter, turboCartoCssAdapter, analysisMapConfigAdapter, owner, templateId, config, authToken, params) { this.templateMaps = templateMaps; this.pgConnection = pgConnection; + this.metadataBackend = metadataBackend; this.userLimitsApi = userLimitsApi; this.namedLayersAdapter = namedLayersAdapter; this.turboCartoCssAdapter = turboCartoCssAdapter; + this.analysisMapConfigAdapter = analysisMapConfigAdapter; this.overviewsAdapter = overviewsAdapter; this.owner = owner; @@ -53,15 +55,29 @@ NamedMapMapConfigProvider.prototype.getMapConfig = function(callback) { var mapConfig = null; var datasource = null; var rendererParams; + var apiKey; step( function getTemplate() { self.getTemplate(this); }, - function prepareParams(err, tpl) { + function prepareDbParams(err, tpl) { + assert.ifError(err); + self.template = tpl; + + rendererParams = _.extend({}, self.params, { + user: self.owner + }); + self.setDBParams(self.owner, rendererParams, this); + }, + function getUserApiKey(err) { + assert.ifError(err); + self.metadataBackend.getUserMapKey(self.owner, this); + }, + function prepareParams(err, _apiKey) { assert.ifError(err); - self.template = tpl; + apiKey = _apiKey; var templateParams = {}; if (self.config) { @@ -78,6 +94,34 @@ NamedMapMapConfigProvider.prototype.getMapConfig = function(callback) { assert.ifError(err); return self.templateMaps.instance(self.template, templateParams); }, + function prepareAnalysisLayers(err, requestMapConfig) { + assert.ifError(err); + var analysisConfiguration = { + db: { + host: rendererParams.dbhost, + port: rendererParams.dbport, + dbname: rendererParams.dbname, + user: rendererParams.dbuser, + pass: rendererParams.dbpassword + }, + batch: { + // TODO load this from configuration + endpoint: 'http://127.0.0.1:8080/api/v1/sql/job', + username: self.owner, + apiKey: apiKey + } + }; + + var filters = {}; + if (self.params.filters) { + try { + filters = JSON.parse(self.params.filters); + } catch (e) { + // ignore + } + } + self.analysisMapConfigAdapter.getLayers(analysisConfiguration, requestMapConfig, filters, this); + }, function prepareLayergroup(err, _mapConfig) { assert.ifError(err); var next = this; @@ -126,17 +170,10 @@ NamedMapMapConfigProvider.prototype.getMapConfig = function(callback) { return next(null, _mapConfig, datasource); }); }, - function beforeLayergroupCreate(err, _mapConfig, _datasource) { + function prepareContextLimits(err, _mapConfig, _datasource) { assert.ifError(err); mapConfig = _mapConfig; datasource = _datasource; - rendererParams = _.extend({}, self.params, { - user: self.owner - }); - self.setDBParams(self.owner, rendererParams, this); - }, - function prepareContextLimits(err) { - assert.ifError(err); self.userLimitsApi.getRenderLimits(self.owner, this); }, function cacheAndReturnMapConfig(err, renderLimits) { diff --git a/lib/cartodb/server.js b/lib/cartodb/server.js index 5f628501..e04f835d 100644 --- a/lib/cartodb/server.js +++ b/lib/cartodb/server.js @@ -152,6 +152,7 @@ module.exports = function(serverOptions) { var namedMapProviderCache = new NamedMapProviderCache( templateMaps, pgConnection, + metadataBackend, userLimitsApi, overviewsAdapter, turboCartocssAdapter diff --git a/test/acceptance/analysis/named-maps.js b/test/acceptance/analysis/named-maps.js new file mode 100644 index 00000000..17447e5c --- /dev/null +++ b/test/acceptance/analysis/named-maps.js @@ -0,0 +1,192 @@ +var assert = require('../../support/assert'); +var step = require('step'); + +//var mapnik = require('windshaft').mapnik; + +var helper = require('../../support/test_helper'); + +var CartodbWindshaft = require('../../../lib/cartodb/server'); +var serverOptions = require('../../../lib/cartodb/server_options'); +var server = new CartodbWindshaft(serverOptions); + +var LayergroupToken = require('../../../lib/cartodb/models/layergroup_token'); + +describe('named-maps analysis', function() { + + var IMAGE_TOLERANCE_PER_MIL = 20; + + var username = 'localhost'; + var widgetsTemplateName = 'widgets-template'; + + var layergroupid; + var layergroup; + var keysToDelete; + + beforeEach(function(done) { + keysToDelete = {}; + + var widgetsTemplate = { + version: '0.0.1', + name: widgetsTemplateName, + layergroup: { + version: '1.5.0', + layers: [ + { + "type": "cartodb", + "options": { + "source": { + "id": "HEAD" + }, + "cartocss": '#buffer { polygon-fill: red; }', + "cartocss_version": "2.3.0" + } + } + ], + analyses: [ + { + "id": "HEAD", + "type": "buffer", + "params": { + "source": { + "id": "2570e105-7b37-40d2-bdf4-1af889598745", + "type": "source", + "params": { + "query": "select * from populated_places_simple_reduced" + } + }, + "radio": 50000 + } + } + ] + } + }; + + var template_params = {}; + + step( + function createTemplate() + { + var next = this; + assert.response( + server, + { + url: '/api/v1/map/named?api_key=1234', + method: 'POST', + headers: { + host: username, + 'Content-Type': 'application/json' + }, + data: JSON.stringify(widgetsTemplate) + }, + { + status: 200 + }, + function(res, err) { + next(err, res); + } + ); + }, + function instantiateTemplate(err, res) { + assert.ifError(err); + + assert.deepEqual(JSON.parse(res.body), { template_id: widgetsTemplateName }); + var next = this; + assert.response( + server, + { + url: '/api/v1/map/named/' + widgetsTemplateName, + method: 'POST', + headers: { + host: username, + 'Content-Type': 'application/json' + }, + data: JSON.stringify(template_params) + }, + { + status: 200 + }, + function(res) { + next(null, res); + } + ); + }, + function finish(err, res) { + assert.ifError(err); + + layergroup = JSON.parse(res.body); + assert.ok(layergroup.hasOwnProperty('layergroupid'), "Missing 'layergroupid' from: " + res.body); + layergroupid = layergroup.layergroupid; + + keysToDelete['map_cfg|' + LayergroupToken.parse(layergroup.layergroupid).token] = 0; + keysToDelete['user:localhost:mapviews:global'] = 5; + + return done(); + } + ); + + }); + + afterEach(function(done) { + step( + function deleteTemplate(err) { + assert.ifError(err); + var next = this; + assert.response( + server, + { + url: '/api/v1/map/named/' + widgetsTemplateName + '?api_key=1234', + method: 'DELETE', + headers: { + host: username + } + }, + { + status: 204 + }, + function(res, err) { + next(err, res); + } + ); + }, + function deleteRedisKeys(err) { + assert.ifError(err); + helper.deleteRedisKeys(keysToDelete, done); + } + ); + }); + + it('should be able to retrieve widgets from all URLs', function(done) { + assert.response( + server, + { + url: '/api/v1/map/' + layergroupid + '/6/31/24.png', + method: 'GET', + encoding: 'binary', + headers: { + host: username + } + }, + { + status: 200, + headers: { + 'Content-Type': 'image/png' + } + }, + function(res, err) { + if (err) { + return done(err); + } + +// var image = mapnik.Image.fromBytes(new Buffer(res.body, 'binary')); +// assert.ok(image); + var fixturePath = './test/fixtures/analysis/named-map-buffer.png'; + assert.imageBufferIsSimilarToFile(res.body, fixturePath, IMAGE_TOLERANCE_PER_MIL, function(err) { + assert.ok(!err, err); + done(); + }); + + } + ); + }); + +}); diff --git a/test/fixtures/analysis/named-map-buffer.png b/test/fixtures/analysis/named-map-buffer.png new file mode 100644 index 00000000..6aee72e9 Binary files /dev/null and b/test/fixtures/analysis/named-map-buffer.png differ