From 077c4ab9071078a7f1e76d0bb9122f8b88cb1c62 Mon Sep 17 00:00:00 2001 From: Raul Ochoa Date: Thu, 7 Apr 2016 16:18:48 +0200 Subject: [PATCH] Adds analysis MapConfig adapter to named maps --- lib/cartodb/cache/named_map_provider_cache.js | 8 +- lib/cartodb/controllers/map.js | 2 + .../models/mapconfig/named_map_provider.js | 61 ++++-- lib/cartodb/server.js | 1 + test/acceptance/analysis/named-maps.js | 192 ++++++++++++++++++ test/fixtures/analysis/named-map-buffer.png | Bin 0 -> 5814 bytes 6 files changed, 251 insertions(+), 13 deletions(-) create mode 100644 test/acceptance/analysis/named-maps.js create mode 100644 test/fixtures/analysis/named-map-buffer.png 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 0000000000000000000000000000000000000000..6aee72e9dac394512b73271aa7b61c83f00c3843 GIT binary patch literal 5814 zcmV;n7D?%eP) zt9n#T6NRzdz;^>kBo2#dF&$FKY|4z*A>T`Eh^%FdK0%QGtLLRWO$$uMtX<+-Yy7IOOaiT%v`tDuENNnu! zOl%UM)9>r^Mo+!%LmJbdVcmL{F$&VbHu@Hzvmet3ZyS-uG-yw-ASk63FdgX0zVu*3$`Omw2HG$6da+=@MEI%ararBH~ z!|BSs6uQjMc$im1T@B=92R541KwWpg2Iz#I>T)Ybo|p{)UCjQ_Q~Wh8_m7EJg1utY zVA9Y2GV2BCcu^#SAOnSZ#GtXf^)Ay=Hf4AppwmSYZv%RyawAFa_m5mY`z^(DfF7yX zK<vj80}s#YJ<(Iu4{NBZCq=xIL>{Eq9rcO4M}Ds}$UKI4d=s3U^9ck5my z3me6cjh=g#VJVw3Tn6Z1(M6BI9$C^L(8qP3`1Nz?KP(0Y^~lV|@BzsW{I+b$aTTC* zWl;JX9&&(*jX+#tQ}6e80cZayy7aCuV!+7C|Ah|kfx+2-iqboHkpm1=`VBPhy^H1k zKAUp=4A8N%=(Tr!5d#iX^lj<%=@0l$0ZrNE-KjDuwVww$0yOC5)!Hg}Q{IaJohpmI zdDj;qRe^4AygODF@c@wY1Jv`+yHjOSH#a|oaQ{C|_ulnINL8R`9^M@*i@5wJeSv&W zb$Lmgq`JNhA}a)X&FS5_GKen)cL3DmRl?n0Kv%yq0m|*)Iv;@rv;K^cI7xNs0Ml?Z*_qKz$kU+`rSLF(@(nI_WouUppSfbcd*D}>22?-z0gBciEZ(^?*9*7_kz`lArhd34l&mX3x`mm#+!XQ~%ol9nV{` z>XAe!ob{8isjE+xZNj^J=4?v%5}MT1yYqQUW_)@4gnRu&purRM-eoie>-PA&E1jOF z`w4+A{mmyX?{b=fb@=;B1Cb@-bt&(k2rsNGzhjS}(Q|d)IyY#~0^-6_x&9S4A^K<`V}aNs958a&DG5a`~&nU9Khd8<(jw}z`e2JCbU z^bDt2d-5)CHL#wCK6t9%F;MSJ4gga?qbrjDI`_6Uu>*~l|2+)=z0dyAC|-Hnnb-n{ zL^a-JUW~`6*;o?0z1_Zoi_5E?06jkLap`SW;sUU=J+)CCD%sPeYaVxVcW+znYl%=_07(8(#}zQ4Bt zpm`!i1KoMskTN$#7c_j_lLNq$ zC)-s(828GF;)5TZfkc_Aylqttp>&7WM_6BB)`03>{> z&f8{9fI13&bTF{oNYK$|B|x`$?wy(gz!9K_G%ez@`ONnU0apMyfSwK985vcg!1&7V z3PCa_pw$ilTgpHnmA2G*NK%u%VaHz%bh{=iwRdGOm04Am)?dq@E&uOA|ABcJN%mH8t zrExxEN3Tz@Ywg2X89-!J*L*dGgaK)-ZA8en<6Fnf%Run1<-m=EIVxM(hLAs?{XSW? za&H=z@2hvG<^Vv#QD#TaaBVqEG+zxk3~(F(NNk`7tPvi^kO9c_Yyd%@0OJ5aTtK`B zF(q}JSqgR}OHylP2SOsxhQEvlfp-DQ$N_-(0X^xDoE%dQ>rBoBkZ2WPQGT*=N(ZoV zwXUgaLmONKC?5v^(jNFkUe1+Cs5W#EjJyKk06^-4*y-4g$>cl$30|+ZC^uO#@K3%n zM;{FB5jsN9s@!CSAYo3t9htn*0No0N^$xTsGg%>}p{L4;zo(Cewvii=khcO>WhN^F zxo7{a%wB4O`b2~q1gy$SRt7Xjf_sBrDANa9peun0F40ZOOI8AO`S0DBYv`_ltt2R_ zMPABNl9kArby>+uK*B^ACuE*T1Ug_^=R-IqIgm7SgC;8k3A5pz(0N0<$+2b7blR%&w&_05)q!MHk~oXErIVkRzm{_Z zX00YZAZPH;kl2|B83PhTyv-1=$p{$(^cgSj4$A?65D%bxQ|}I2Ro*rwBn{~A7T~A1 zO$kXwfKFRex8C+7Bo&}1AR+4oet|6(y70CqA*n!L(SW=GAY=+)J^(`24SZkzt)+L~ zb|fSvP#<;g4!l$hq9J4s&}~5G+khKy8xoQlr=Zw1I;aOqJVLjHnWuLCzd`QPdImM;g(pkngsF(HGLMt&Eda}UYvIj|#kk>n{Y zpHGuIc-69ug!}=4-@_}C{P@i>|3H`KUS+*%RZc?wg1E^-G47)Ym+Yp!#Tj=xim*4vxK^0E~n z2PuPrTpeDNnOr)y93IMbngq*iqvGVBNyOYs?{5~M+Zy){%>f`FrBOfkoSld*$QA#< z0l)(Mi{<`MW~v`Y41Zr{HF*nOmcESZP7D%1j^b^*^kDh^bt?(n5PxyTbteP4PX0Ln z)Sz^Ht3w}I88AOx5;k@9*}Efi0B8hk5;+``IFxXE5Evv+iSBK`RBWvE5;EAGtQp_Knns@@(p;EXin+q;)@_#u&ocm zp)H@>@>r(!;sD@DX$aBocNxxU0CZ-*K42K1v+5cAyUoeu!_NW0 z8u;(|htxEoWp9*@f4SC<&jH{B&?q(c${8U(W;p}sZBB#?Meg#hiw8jW+|Nn{$sx?E z(ivs6Zk5eNek)QQw;?g@^8}FCYp=TjpmcVL=#pG*V@Bj{*r!yY$w3(6ocNE`R|`Az z26S?XdZx3U0RaCxeT|1B?#iE4Wpx0Z{WI;Ugbq96TW|+}MrwN3O;}kQUk>m*4%8-l z%pu?2e_uQc*aP|&pLe~4mZkB%pNW=%XLM{m=Dp1xP4o~fAn$ojQ&}1R!n0`Ygb6x5WMTM-2QDW&2Kw12eyU!f2|+&ty18l_K5|Donmrbpfyev%YH9Vi z0ci8+NEfPU^q1a+8IbG;4`$av@{rCwtvZ7c^0;w_P%Wd&flCJ8YB}h8G(!gXu9{kv zYcL+VkBcI1LBpz6C1i@BzS{ftv?-mBXzSA%F1_C;}i;qSP*Kz8hZfjIf#)|p4^ZmU=hrM51sR4k__FT@}YS|kU$T|D} zEj;>k7P%U(xwwFdI$CM@RuQ?q=j0`y+W_BDTWPld&_OxFVRCjYPb35SSw~gMLqNn& z!PbkeYgHKcXE7Dxx$G%`Zo91{4pMa-#gKdX-?LIV0wG@7^@sugVrxaV&nSzXl+5r41LP(G=eK2Ytp} zSnFsK8&{yuQ+peSQl-cP2wwZm)xjh-u7I}yQ518AH~`zsBE@Q zo~r)rr|`+ndDMn5qPt^zz1-tBjn(nh>( z)|0;0uAoCdGTOTg^a*IGR4Ko&tQUwHcEE@D1ZrMYpU)f zaN>tXFEv5OpjL_*9y$P+K&H0p-2|NYkx6@v&A)#Uz%WC~z7$R60JH^~kKWsMJ*i$Y zkbxhXG~*z)bC3*BnbT0Gee@Fy1+J}n5ugJo6Jj_CJqn&c?zGgS zeKev0<^!yy+u9W~^n;VP0Xu)TWHI$Pc$42enk5&x;}}w&1#G_ic>vq>pt@JVBYtqv zy<@5PmdigmaUNVQBlm&e&6qMq1H28Wp}L7dM*isHLkH-rYZ(?Qo#MsBZ_1pf?gx?$ z0^p;vHB|TIpVmK$`U8P<;#m<=(DQ)IvFh^eseA*#>_AKO-J(Lq{BZT;lhCUndG$m( z@RV}``FCY0`MO5qFW-^%P*9Z0Js7+aT%t+Qx??M3%n#R3MfCVP@=?)kToACPK)xfJB~4@kFKgRZzT+uTyNF9MDHh!hV{)&Tjh zsnWe0um!mfVJ&3;^7Dv~sPB9b81!*PmC8A27ie}VZV6gBd5!pxl))!+K_C9=DN~2- z0v(7g4+!NNQdWBr1HAV;Foo>c=$ZRcx!BpRYyYl@f53->*KR=x7t$mc|zz5CF1&|Ts%ocP^ zOJ`qkh^ieUA*+R>c^W2so2(3EH6jD{uIsq`udGncFvE2mI>>lsF7kQuZQ!HYEky!= zzOvTLsynnfg!>$|ZQNLtnuuqXM{~dwuV3l}<4!o0A97a|KxyOue4GDeW*` z84rA{6fW$DyaoRficDR$N@KgmfrKJ6{;bFv%picM1+E3duBlgQiQ+3jR193ebR~2{S&YtnjGylv5y1ci1 zWmfk8Jskob9mo;e-=Uos9C0l>;J>_@Bvs%-jl#HQH>)X?Wq!~p1(UJns@o;vm>qPMU6 zRN2SFlUSgyt2i$`3cNwyI0xPetOYP5ObvZIU{{&_y*&MvDs6@%G6BT!Nb5n5a>#XH zTb>2<4D_lmNd_Oj z1GLl2l^zp4+DZn}N6s@)4@&QEt08&^W&+Ia+FA$wIba*p-csq_J6I*q8UIs&2D2u8 zK2aaNxKW4nRwV`KUbC0kCX*yU>Zsp5`dNk9y^lwh0pDbbYnB}Doy@k2ehyiWu5ySY z&?SHE?6K514ZhhB;2Ue{Ym4?wCIF2q&wj;}QDXDKq@T$D&f%_pwrJC2B+!|koxiv?Eyx>IY9+}GYc(Ls?7b+5Jo-s>PCo!|podDmEGcVgn^9N-nk1`mjeL7@cQ5YK zi``$*&uT4X=BiZz#_J!_qpLXRs$fd|gAA&&zn9fjRiAfg@RhfXYKVzJJOO9_V;XxM zCo2Srk2o)qD^$5jG0lF9KCS|PS=xw<0G$1gDf}CX$nOpuGG+a0Kp!#XRtg`ciu@Ut z2R$B%A?PQjyt=&WW(?5nhr6w2N+upY65_>A<`Jz5b3VjdKe0 zz|q@YQzciboc%pPAE(GxN|mJs`CdQf-M=ya13r2{*w4n8>i_@%07*qoM6N<$f~1Z= ArvLx| literal 0 HcmV?d00001