Compare commits

...

748 Commits

Author SHA1 Message Date
Daniel García Aubert
59f6217c4f Release 3.0.0 2017-03-21 15:34:47 +01:00
Daniel
0e43fbbb34 Merge pull request #635 from CartoDB/node-v6
Support Node v6 LTS and upgrade to Mapnik v3.x
2017-03-21 15:30:59 +01:00
Daniel García Aubert
1720f22247 Improve doc: 'yarn install' command is deprecated, use 'yarn' instead 2017-03-21 14:58:09 +01:00
Daniel García Aubert
3f791d25b5 Add a note about moving from npm to yarn 2017-03-17 17:58:33 +01:00
Daniel García Aubert
acd3047500 Update NEWS 2017-03-17 17:49:19 +01:00
Daniel García Aubert
7b3a4aa2a8 Update README.md 2017-03-17 17:41:20 +01:00
Daniel García Aubert
4bfaeeb44b Update INSTALL.md 2017-03-17 17:37:35 +01:00
Daniel García Aubert
a094ae7197 Update NEWS 2017-03-17 17:30:58 +01:00
Daniel García Aubert
ef0362d118 Fix windshaft version to 3.0.0 to avoid warning message while starting the service 2017-03-17 17:21:09 +01:00
Daniel García Aubert
5a5763684d Don't provide a npm's shrinkwrap anymore 2017-03-17 17:17:17 +01:00
Daniel García Aubert
6e575300e3 Update yarn.lock 2017-03-17 17:13:49 +01:00
Daniel García Aubert
8109fc4d46 Merge branch 'master' into node-v6 2017-03-17 17:06:58 +01:00
Daniel García Aubert
e0519e7851 Stubs next version 2017-03-17 17:06:39 +01:00
Daniel García Aubert
6334df5f5f Merge branch 'master' into node-v6 2017-03-17 17:03:53 +01:00
Daniel García Aubert
38294d29f5 Release 2.89.0 2017-03-17 16:45:18 +01:00
Daniel
5b131cc8a7 Merge pull request #639 from CartoDB/upgrade-windshaft-to-2.8.0
Upgrade windshaft to 2.8.0
2017-03-17 16:37:07 +01:00
Daniel García Aubert
5dee654132 Upgrade windshaft to 2.8.0 2017-03-17 16:26:26 +01:00
Daniel García Aubert
d902476780 Fix assertions, now MapCofig.getLayer() return {} if layer not found 2017-03-17 11:06:40 +00:00
Ubuntu
bc5dabef3c Revert "Fix assertions, now MapCofig.getLayer() return {} if layer not found"
This reverts commit c839a0b0a3.
2017-03-17 11:04:50 +00:00
Daniel García Aubert
024f1e4851 Fix assertions, now MapCofig.getLayer() return {} if layer not found 2017-03-17 10:45:59 +00:00
Raul Ochoa
5f87417d9e Merge pull request #637 from CartoDB/histogram-type-discovery
Histogram column type discovery query uses non-filtered query
2017-03-17 11:03:15 +01:00
Raul Ochoa
fa94550261 Include changes for overviews implementation 2017-03-16 19:15:34 +01:00
Daniel García Aubert
11efbf034e Drop support for Node 0.10 2017-03-16 17:18:44 +01:00
Daniel García Aubert
c839a0b0a3 Fix assertions, now MapCofig.getLayer() return {} if layer not found 2017-03-16 16:44:41 +01:00
Daniel García Aubert
420b657db8 Upgrade windshaft to 3.0.0 2017-03-16 16:42:43 +01:00
Raul Ochoa
2656a26272 Merge pull request #622 from strk/typo
Trip epoch is over...
2017-03-16 16:26:53 +01:00
Raul Ochoa
992b2b6ba7 Histogram column type discovery query uses non-filtered query
Pass all queries to the dataview and use the no filters one for
discovering what is the column type associated to the histogram dataview.
2017-03-13 18:40:29 +01:00
Raul Ochoa
924f009390 Test for #606: function avg(timestamp with time zone) does not exist 2017-03-13 18:36:43 +01:00
Raul Ochoa
48a1244fa0 Stubs next version 2017-03-10 11:06:47 +01:00
Raul Ochoa
8789a959e5 Release 2.88.4 2017-03-10 11:06:06 +01:00
Raul Ochoa
5765ac59cc Merge pull request #636 from CartoDB/upgrade-camshaft
Upgrades camshaft to 0.50.3
2017-03-10 11:05:30 +01:00
Raul Ochoa
3b9bf96431 Upgrades camshaft to 0.50.3 2017-03-10 10:58:07 +01:00
Daniel García Aubert
a5fe5e7052 Update shrinkwrap 2017-03-08 10:56:01 +01:00
Daniel García Aubert
0f96b5a4a5 Update yarn.lock 2017-03-08 10:24:04 +01:00
Daniel García Aubert
25babeae56 Merge branch 'node-v6-yarn' into node-v6 2017-03-07 18:01:16 +01:00
Raul Ochoa
1951e79962 Make assertions compatible 2017-03-07 13:27:13 +01:00
Daniel García Aubert
1e0e31cc1c Merge branch 'node-v6' of github.com:CartoDB/Windshaft-cartodb into node-v6 2017-03-07 13:02:18 +01:00
Daniel García Aubert
8d35f72fcb Back to use current assertions as grainstore implements a fallback mechanism when translates styles 2017-03-07 12:59:52 +01:00
Daniel García Aubert
5f3e515131 Back test to use '=~' operator which is now supported by carto@0.15.1-cdb-3 in windshaft 2017-03-07 12:58:06 +01:00
Raul Ochoa
49236fce86 Upgrade locked deps 2017-03-02 11:38:09 +01:00
Raul Ochoa
9d2dde7a5a Merge branch 'node-v6' into node-v6-yarn 2017-03-02 11:07:52 +01:00
Raul Ochoa
c3e703237c Merge remote-tracking branch 'origin/master' into node-v6 2017-03-02 11:07:43 +01:00
Mario de Frutos
8868066445 Stubs next version 2017-03-02 11:00:53 +01:00
Mario de Frutos
b446c31cbc Other category now uses the selected aggregated function (#633)
* Other category in category widget uses selected aggregation function
Fixes https://github.com/CartoDB/Windshaft-cartodb/issues/628
2017-03-02 10:48:20 +01:00
Raul Ochoa
2d6e7070a6 Stubs next version 2017-02-23 18:12:54 +01:00
Raul Ochoa
473e0cb902 Release 2.88.2 2017-02-23 18:12:14 +01:00
Raul Ochoa
1a8fca0534 Formatting 2017-02-23 18:11:39 +01:00
Raul Ochoa
e24bc12fc9 Merge pull request #632 from CartoDB/upgrade-camshaft
Upgrades camshaft to 0.50.2
2017-02-23 18:10:22 +01:00
Raul Ochoa
e77c9141ed Upgrades camshaft to 0.50.2 2017-02-23 18:00:55 +01:00
Raul Ochoa
321157b17b Make target to generate shrinkwrap file applying prune
It removes dev dependencies from the local node_modules
and avoid getting those into shrinkwrap file.
2017-02-23 17:57:52 +01:00
Raul Ochoa
6ac6574b4c clean target to completely delete node_modules dir 2017-02-23 17:57:44 +01:00
Raul Ochoa
70ff0a9b8f Update freezed deps 2017-02-22 19:17:00 +01:00
Raul Ochoa
15bdb57a22 Merge remote-tracking branch 'origin/node-v6' into node-v6-yarn 2017-02-22 19:02:55 +01:00
Raul Ochoa
bb54e5520c Merge remote-tracking branch 'origin/master' into node-v6 2017-02-22 19:02:27 +01:00
Raul Ochoa
933d486a57 Merge pull request #630 from CartoDB/close-response-timer
Close timer for response preparation
2017-02-22 18:46:56 +01:00
Raul Ochoa
8a1c7f5b52 Close timer for response preparation
Timer for affectedTables is taking everything from response timing,
adding a tag to represent all the response preparation.
That way affectedTables only represents the time for retrieving the
affected tables themselves.
2017-02-22 18:38:25 +01:00
Rafa de la Torre
822954be5d Stub next version 2017-02-21 11:06:54 +01:00
Rafa de la Torre
57dc17518c Release 2.88.1 2017-02-21 11:01:15 +01:00
Mario de Frutos
3df8d4844e Stubs next version 2017-02-21 08:17:09 +01:00
Mario de Frutos
5a0443618f Merge pull request #626 from CartoDB/camshaft_v050
Camshaft 0.50.0
2017-02-21 08:08:34 +01:00
Raul Ochoa
8a76cd506f Fix issue with global Promise object 2017-02-20 18:46:48 +01:00
Mario de Frutos
50d05eae47 Version 2.88.0
Update camshaft version to 0.50.0
2017-02-20 18:46:47 +01:00
Raul Ochoa
ca41b3b600 Merge pull request #627 from CartoDB/change_release
We don't announce release deploy in google groups anymore
2017-02-20 10:31:39 +01:00
Mario de Frutos
43a17ddc7d We don't announce release deploy in google groups anymore 2017-02-17 18:01:59 +01:00
Mario de Frutos
dfa347f860 Update camshaft dependency to 0.50 2017-02-17 16:29:34 +01:00
Raul Ochoa
6033027812 Merge branch 'node-v6-shrinkwrap' into node-v6-yarn 2017-02-17 13:42:32 +01:00
Raul Ochoa
9ee6f7fbb8 Regenerate npm-shrinkwrap.json with npm@2.x 2017-02-17 13:41:58 +01:00
Raul Ochoa
60c0754800 Make assertions compatible 2017-02-17 13:36:15 +01:00
Raul Ochoa
a9251c5e71 Fix issue with global Promise object 2017-02-17 13:35:41 +01:00
Raul Ochoa
7daeddc946 Use yarn 2017-02-17 11:27:01 +01:00
Raul Ochoa
dbdb00070e Remove artificial node-zipfile dep 2017-02-17 11:26:46 +01:00
Raul Ochoa
e5c3c282ef Log Node.js version on startup 2017-02-10 10:03:06 +01:00
Raul Ochoa
caba79b5e2 Make target to generate shrinkwrap file applying prune
It removes dev dependencies from the local node_modules
and avoid getting those into shrinkwrap file.
2017-02-07 20:03:47 +01:00
Raul Ochoa
d359ea7fa6 clean target to completely delete node_modules dir 2017-02-07 19:55:37 +01:00
Raul Ochoa
c0abbe570f Add npm-shrinkwrap.json file 2017-02-07 18:56:45 +01:00
Raul Ochoa
229a2c0c3c Remove npm-shrinkwrap.json until we solve the issue associated to it 2017-02-07 18:50:57 +01:00
Daniel García Aubert
3f185c9c69 revert: disable cache for ci builds 2017-02-07 15:28:21 +01:00
Daniel García Aubert
f86f72ab27 disable cache for ci builds 2017-02-07 15:19:05 +01:00
Daniel García Aubert
74af17cc65 Add ubuntu toolchain for builds 2017-02-07 12:12:17 +01:00
Daniel García Aubert
aaa3e34c7f Use gcc 4.9 for build in travis 2017-02-07 11:55:21 +01:00
Daniel García Aubert
a053f198f5 Generate shrinkwrap 2017-02-07 11:45:48 +01:00
Daniel García Aubert
852ba68895 Upgrade redis in dev-dependency 2017-02-07 11:44:53 +01:00
Daniel García Aubert
1b22d176d6 Merge branch 'master' into node-v6 2017-02-06 19:03:16 +01:00
Daniel García Aubert
0ccbedf551 Use updated branch of windshaft 2017-02-06 18:25:32 +01:00
Raul Ochoa
28f1179336 Stubs next version 2017-02-02 16:44:41 +01:00
Raul Ochoa
de4d9e285e Release 2.87.5 2017-02-02 16:43:43 +01:00
Raul Ochoa
e0faaac822 Update news 2017-02-02 16:43:04 +01:00
Raul Ochoa
c84f27dd3f Merge pull request #623 from CartoDB/fix-dataviews-override-params-casting
Cast all dataview overrides values to Number
2017-02-02 16:42:48 +01:00
Raul Ochoa
12279d5c00 Cast dataview override values to Number or throw error
We were letting params expected as Numbers to be passed as any type
when they were not Numbers.
2017-02-02 16:20:16 +01:00
Raul Ochoa
281588abd2 Add test to validate bins param is casted as Number 2017-02-02 16:12:49 +01:00
Sandro Santilli
7e206b84aa Fix typo 2017-01-31 13:16:36 +01:00
Mario de Frutos
f69f999694 Fixed contributing link 2017-01-25 10:43:24 +01:00
Daniel García Aubert
c0c062592f Stubs next version 2017-01-20 11:40:00 +01:00
Daniel García Aubert
06885e2ba3 Release 2.87.4 2017-01-20 11:32:11 +01:00
Daniel
89a268d087 Merge pull request #617 from CartoDB/497-null-category
Be able to not compute NULL categories and null values
2017-01-20 11:26:12 +01:00
Daniel García Aubert
34424e713c Test on Node v6 2017-01-19 12:42:53 +01:00
Daniel García Aubert
89f381439f Pass test 2017-01-19 12:24:04 +01:00
Daniel García Aubert
6a80be9df3 Update windshaft dependency 2017-01-19 12:23:43 +01:00
Daniel García Aubert
fde1923acb Removed invalid selector 2017-01-19 11:31:55 +01:00
Daniel García Aubert
d486e1d34f Use released version of camshaft that supports node v6 2017-01-18 14:43:02 +01:00
csobier
3648b8b0b1 removed "default" from descriptions
As per a customer Support inquiry, I removed "default" from the Static Maps Limits section, as it was confusing to the user that it implied
2017-01-17 11:21:23 -05:00
Daniel García Aubert
83301238d2 Port changes to overviews 2017-01-17 17:10:08 +01:00
Daniel García Aubert
a4a1fb930a Be able to not compute NULL categories and null values wheter aggregation operation is not 'count' 2017-01-17 17:09:17 +01:00
Daniel García Aubert
6555353e0e Improve test to handle NULL values in category and aggregation columns using any operation 2017-01-16 19:23:08 +01:00
Daniel García Aubert
f5f0601e53 Add test to check if NULL category count values properly 2017-01-16 17:00:28 +01:00
Raul Ochoa
2598595e42 Merge pull request #614 from CartoDB/zipfile-cartodb
Use zipfile from cartodb
2016-12-23 14:47:40 +01:00
Raul Ochoa
49b78a85c9 Use zipfile from cartodb
Adding it as dependency in combination with loose definition in
millstone makes it to use the top level defined dependency.
2016-12-22 19:12:30 +01:00
Daniel García Aubert
35b12ebd6c Test unused directive just for mapnik 2.3.x 2016-12-19 17:29:24 +01:00
Javier Goizueta
0918c8e68c Stub next version 2016-12-19 17:20:18 +01:00
Javier Goizueta
1603a07de1 Release 2.87.3 2016-12-19 17:13:12 +01:00
Javier Goizueta
0a37aa4ba1 Merge pull request #604 from CartoDB/fix-overviews-dataviews
Fixes for dataviews using overviews
2016-12-19 16:55:13 +01:00
Javier Goizueta
b721a80fcc Merge branch 'master' into fix-overviews-dataviews 2016-12-19 16:45:28 +01:00
Rafa de la Torre
01365d035e Stub version 2.87.3 2016-12-19 16:38:27 +01:00
Rafa de la Torre
a4f059e20f Merge pull request #605 from CartoDB/update-camshaft-0.48.5
Update camshaft to 0.48.5
2016-12-19 16:33:27 +01:00
Daniel García Aubert
eb758bbf36 Use a valid port from env 2016-12-19 16:19:41 +01:00
Daniel García Aubert
bc2441e66a Use a valid port 2016-12-19 16:17:41 +01:00
Daniel García Aubert
7c1792bbd2 Test regression and unused directives only for mapnik ~2.3.x 2016-12-19 16:16:50 +01:00
Daniel García Aubert
2fdbc3e61c Fix error message in assertion 2016-12-19 16:15:07 +01:00
Daniel García Aubert
2ace705122 Fix error message in assertion 2016-12-19 16:13:55 +01:00
Daniel García Aubert
4b817062d8 Bump version of dependencies to be Node v6 compatible 2016-12-19 16:12:57 +01:00
Rafa de la Torre
79c35118d7 Update camshaft to 0.48.5
Use exception safe Dataservices API functions. See
https://github.com/CartoDB/dataservices-api/issues/314 and
https://github.com/CartoDB/camshaft/issues/242
2016-12-19 15:52:25 +01:00
Javier Goizueta
6a4f5d52ec Don't use overviews for date histograms 2016-12-16 17:51:36 +01:00
Javier Goizueta
ccaae2dd66 Remove spurious parameter from overviews dataviews functions
In the overviews-specialized dataview classes the sql-generating
functions had an unneeded parameter filters.
In some cases, since this parameter was not being paaased from
the base dataviews class it was masking the override parameter.
2016-12-16 17:37:05 +01:00
Raul Ochoa
d335e64f88 Stubs next version 2016-12-13 14:16:49 +01:00
Raul Ochoa
177d7ed07a Release 2.87.1 2016-12-13 14:15:50 +01:00
Raul Ochoa
85a1e15b58 Merge pull request #603 from CartoDB/upgrade-deps
Upgrade windshaft and request deps
2016-12-13 14:15:04 +01:00
Raul Ochoa
432b58a078 Upgrade windshaft and request deps 2016-12-13 14:10:42 +01:00
xavijam
75e3c5daef Ready for next release 2016-12-13 10:43:16 +01:00
Raul Ochoa
deb71c27b0 Merge pull request #601 from CartoDB/update-turbocarto
Update turbocarto package version
2016-12-12 18:56:54 +01:00
Raul Ochoa
8f5e1de6d8 Merge branch 'master' into update-turbocarto 2016-12-12 18:51:04 +01:00
Raul Ochoa
4836d62d7a Merge pull request #602 from CartoDB/travis-improvements
Travis improvements
2016-12-12 18:50:39 +01:00
Raul Ochoa
d27b0617b2 Remove comments 2016-12-12 18:44:25 +01:00
Raul Ochoa
28a2c29a39 Attempt to use postgis 2.3 2016-12-12 18:39:57 +01:00
Raul Ochoa
fcb6478407 Attempt to use postgis 2.2 2016-12-12 18:33:19 +01:00
Raul Ochoa
6d72afe40e Test sudoless travis 2016-12-12 17:30:14 +01:00
Raul Ochoa
e775266c64 Remove notifications 2016-12-12 17:29:32 +01:00
xavijam
12f25b38c0 Updated news 2016-12-12 17:03:56 +01:00
xavijam
c67a1107cb Package version updated with turbo-carto cahnges 2016-12-12 17:02:31 +01:00
xavijam
34bfb0d62c Updated news 2016-12-12 17:02:15 +01:00
Javier Goizueta
ede45cad1f Stub next version 2016-12-02 15:25:52 +01:00
Javier Goizueta
75fe4c8aed Release 2.86.1 2016-12-02 15:19:29 +01:00
Javier Goizueta
12e272a7e5 Merge pull request #599 from CartoDB/597-overviews-sql_wrap
Queries with sql_wrap should not be rewritten
2016-12-02 15:07:59 +01:00
Javier Goizueta
cfcba4e578 Wueries with sql_wrap should not be rewritten 2016-12-02 14:00:21 +01:00
Raul Ochoa
37ab898426 Stubs next version 2016-12-02 10:43:00 +01:00
Raul Ochoa
68865ea929 Release 2.86.0 2016-12-02 10:41:23 +01:00
Raul Ochoa
86674faa22 Update news and bump version 2016-12-02 10:41:02 +01:00
Raul Ochoa
f07947ce45 Merge pull request #597 from CartoDB/upgrade-windshaft
Upgrade windshaft
2016-12-02 10:32:44 +01:00
Raul Ochoa
6a50f59e25 Regenerate npm-shrinkwrap.json 2016-12-01 17:13:32 +01:00
Raul Ochoa
bfacd56800 Allow to use workers for transforming cartocss into mapnik XML 2016-12-01 17:02:40 +01:00
Raul Ochoa
45dea8b0c1 Stubs next version 2016-11-30 11:09:18 +01:00
Raul Ochoa
2f7f8cf2d8 Release 2.85.1 2016-11-30 11:07:29 +01:00
Raul Ochoa
31611b6a28 Upgrades camshaft to 0.48.4 2016-11-30 10:59:44 +01:00
Raul Ochoa
d1cd4b0c2b Stubs next version 2016-11-24 16:15:47 +01:00
Raul Ochoa
c8ba1c3e7c Release 2.85.0 2016-11-24 16:14:48 +01:00
Raul Ochoa
fbc8fe4c2d Update news and bump version 2016-11-24 16:11:08 +01:00
Raul Ochoa
54ec9b48db Ignore vscode settings dir 2016-11-24 15:41:09 +01:00
Raul Ochoa
488698d5e2 Merge pull request #594 from CartoDB/resources-url-templates
Allow to set resource URL templates with substitution tokens
2016-11-24 15:37:23 +01:00
Raul Ochoa
58c407aabb Alternative development example configuration for user in path instead of host 2016-11-24 12:57:45 +01:00
Raul Ochoa
fe750f23bc Closer to reality production example configuration 2016-11-24 12:56:38 +01:00
Raul Ochoa
87a01a5cfd Example configuration with just HTTP 2016-11-24 12:56:02 +01:00
Javier Goizueta
74dd669bb0 Stub next version 2016-11-23 15:29:50 +01:00
Javier Goizueta
36a50389f5 Release 2.84.2 2016-11-23 15:09:24 +01:00
Javier Goizueta
4f2d7434c7 Merge pull request #596 from CartoDB/upgrade-camshaft-to-0.48.3
Upgrade Camshaft version to 0.48.3
2016-11-23 15:05:43 +01:00
Javier Goizueta
b0a0848476 Upgrade Camshaft version to 0.48.3 2016-11-23 13:17:16 +01:00
Javier Goizueta
9fcd897e54 Stub next version 2016-11-23 11:57:05 +01:00
Javier Goizueta
daa8fff21e Release 2.84.1 2016-11-23 11:53:37 +01:00
Javier Goizueta
785229ddea Merge pull request #595 from CartoDB/upgrade-camshaft-to-0.48.2
Upgrade camshaft version to 0.48.2
2016-11-23 11:50:45 +01:00
Javier Goizueta
8bb11bf1d4 Upgrade camshaft version to 0.48.2 2016-11-23 11:36:38 +01:00
Raul Ochoa
1f975e15c1 Default to empty object for cdn URLs 2016-11-22 17:01:34 +01:00
Raul Ochoa
6c69ba54db Use actual CDN url for HTTP and HTTPS 2016-11-22 16:44:06 +01:00
Raul Ochoa
49f9904d00 Allow to set resource URL templates with substitution tokens 2016-11-22 16:41:31 +01:00
Raul Ochoa
7afd0dfa4e Remove prototype reference 2016-11-22 13:38:56 +01:00
Raul Ochoa
b1b6a437a7 Merge pull request #593 from CartoDB/592-doc-preview-layers
Fix DOC for named map  and preview layers
2016-11-22 12:52:24 +01:00
Daniel García Aubert
e4d5006591 Merge branch 'master' into 592-doc-preview-layers 2016-11-22 11:06:18 +01:00
Daniel García Aubert
627b3771d3 Fix doc for layer preview 2016-11-22 11:04:38 +01:00
Raul Ochoa
f4758e84e8 Stubs next version 2016-11-11 16:17:20 +01:00
Raul Ochoa
8dfe2098ed Release 2.84.0 2016-11-11 16:16:21 +01:00
Raul Ochoa
c56a4ee036 Update news and bump version 2016-11-11 16:15:59 +01:00
Raul Ochoa
c32623b821 Stubs next version 2016-11-11 16:15:08 +01:00
Raul Ochoa
3cd0a947f7 Merge pull request #586 from CartoDB/docs-switch-to-builder
switched gui tool from editor to builder
2016-11-11 12:52:38 +01:00
Raul Ochoa
8eea1cf4e7 Merge pull request #590 from CartoDB/analyses-limits-configuration
Analyses limit configuration allows to set other limits than timeout
2016-11-11 12:52:21 +01:00
Javier Goizueta
b5fccd5bbe Merge pull request #589 from CartoDB/upgrade-camshaft-to-0.48.1
Release 2.83.1
2016-11-10 18:48:14 +01:00
Raul Ochoa
e74ce9dfd8 Analyses limit configuration allows to set other limits than timeout
Configuration is now defined as a dictionary instead of just timeouts
per analysis type
2016-11-10 18:41:59 +01:00
Javier Goizueta
3743365a83 Release 2.83.1
Upgrade camshaft to 0.48.1
2016-11-10 18:16:42 +01:00
Javier Goizueta
8aeb2173d1 Release 2.83.0 2016-11-10 13:02:39 +01:00
Javier Goizueta
9a2b17d952 Merge pull request #588 from CartoDB/upgrade-camshaft-to-0.48.0
Upgrade camshaft to 0.48.0
2016-11-10 12:47:18 +01:00
Javier Goizueta
6f54cce01a Upgrade camshaft to 0.48.0
Upgrade camshaft to have analysis limits checking
2016-11-10 12:23:15 +01:00
Daniel García Aubert
6901b2049e Release 2.82.0 2016-11-08 18:48:01 +01:00
Daniel García Aubert
d0dcc027df Upgrade camshaft to version 0.47.0 2016-11-08 18:44:58 +01:00
Daniel García Aubert
b693005118 Stubs next version 2016-11-05 14:41:15 +01:00
Daniel García Aubert
ab4a0e836f Release 2.81.1 2016-11-05 14:36:49 +01:00
Daniel
abe02db6c6 Merge pull request #585 from CartoDB/fix-map-validator-basemap
Fix issues related to rollout
2016-11-05 14:35:06 +01:00
Daniel García Aubert
a2cd5dd32d Upgrade camshaft and windshaft 2016-11-05 14:12:03 +01:00
Daniel García Aubert
49b46a6096 Use address column in styles 2016-11-05 11:43:39 +01:00
csobier
94f420ca3f switched gui tool from editor to builder 2016-11-02 09:34:55 -04:00
Daniel García Aubert
5e530105df Stubs next version 2016-11-02 14:30:11 +01:00
Daniel García Aubert
2f82d34c4b Release 2.81.0 2016-11-02 10:36:19 +01:00
Daniel García Aubert
81fd01d0ac Upgrade camshaft to 0.46.2 2016-11-01 14:49:13 +01:00
Daniel García Aubert
9faac9f9fe Retrieve error with context if map validation fails 2016-11-01 11:00:58 +01:00
Daniel García Aubert
d04787a60c Fix test 2016-10-31 22:48:09 +01:00
Daniel García Aubert
f5dbf94b52 Stubs next version 2016-10-26 14:47:59 +02:00
Daniel García Aubert
5bec2d9b15 Release 2.80.2 2016-10-26 14:34:24 +02:00
Daniel García Aubert
fe64f0c63c Update NEWS 2016-10-26 14:30:56 +02:00
Daniel García Aubert
c20fd9691a Fix order in categories query to make it compatible with lenyends 2016-10-26 13:16:24 +02:00
Daniel García Aubert
eb323fbff9 Merge branch 'master' of github.com:CartoDB/Windshaft-cartodb 2016-10-25 16:28:34 +02:00
Daniel García Aubert
211f6b9a74 Stubs next version 2016-10-25 16:27:53 +02:00
Daniel García Aubert
b6c003ec63 Stubs next version 2016-10-25 16:15:03 +02:00
Daniel García Aubert
93d4bf2a72 Release 2.80.1 2016-10-25 16:02:32 +02:00
Daniel García Aubert
c6cb573383 Since crankshaft is installed by default in template-postgis we have to remove it before running test 2016-10-25 15:55:24 +02:00
Daniel García Aubert
f4ce671ea4 Upgrade camshaft version to 0.46.1 2016-10-25 14:54:52 +02:00
Raul Ochoa
147f7cbabb Stubs next version 2016-10-20 15:24:37 +02:00
Raul Ochoa
b05d5a141e Release 2.80.0 2016-10-20 15:23:17 +02:00
Raul Ochoa
d34e0306f8 Update news 2016-10-20 15:22:16 +02:00
Raul Ochoa
bd9f48dd24 Merge pull request #579 from CartoDB/upgrade-camshaft-to-0.46.0
Upgrade camshaft to 0.46.0
2016-10-20 15:20:14 +02:00
Javier Goizueta
9805990d79 Update npm-shrinkwrap 2016-10-20 15:11:53 +02:00
Raul Ochoa
dbbe60967c Bump version and update news 2016-10-20 15:04:46 +02:00
Raul Ochoa
0ef91c1904 Merge pull request #580 from CartoDB/analyses-config-limits
Default analyses limits can be defined in configuration
2016-10-20 15:02:44 +02:00
Raul Ochoa
376573459c Default analyses limits can be defined in configuration 2016-10-20 14:03:42 +02:00
Javier Goizueta
9c6d7c0ff9 Upgrade camshaft to 0.46.0
This version of camshaft requires a CDB_CheckAnalysisQuota function
to check analysis cache quota.
2016-10-20 12:56:18 +02:00
Raul Ochoa
30a95b7da3 Stubs next version 2016-10-11 16:56:22 +02:00
Raul Ochoa
e6a60aef9a Release 2.79.0 2016-10-11 16:55:15 +02:00
Raul Ochoa
5c2024581f Bump version 2016-10-11 16:55:03 +02:00
Raul Ochoa
f7ea2bb51e Merge pull request #578 from CartoDB/upgrade-turbo-carto
Upgrades turbo-carto to 0.18.0
2016-10-11 16:53:39 +02:00
Raul Ochoa
3e4da8ab57 Upgrades turbo-carto to 0.18.0 2016-10-11 16:41:26 +02:00
Raul Ochoa
7352a28908 Fix variable assigning to itself 2016-10-11 16:01:43 +02:00
Raul Ochoa
d1928ee578 Merge pull request #577 from CartoDB/analysis-limits-configuration
Retrieve analysis limits and pass them into camshaft
2016-10-11 16:00:07 +02:00
Raul Ochoa
cd978d7384 Retrieve analysis limits and pass them into camshaft 2016-10-11 15:46:11 +02:00
Raul Ochoa
cde0d8f5e2 Stubs next version 2016-09-30 18:26:33 +02:00
Raul Ochoa
7bacfcc2e4 Release 2.78.1 2016-09-30 18:25:00 +02:00
Raul Ochoa
241fe36103 Upgrades camshaft to 0.44.2 2016-09-30 18:24:44 +02:00
Raul Ochoa
441714a656 Stubs next version 2016-09-29 11:36:06 +02:00
Raul Ochoa
bd3fdb7f16 Release 2.78.0 2016-09-29 11:35:03 +02:00
Raul Ochoa
775af6feee Merge pull request #575 from CartoDB/cartocss-meta
Add metadata about processed turbo-carto rules
2016-09-28 19:44:52 +02:00
Raul Ochoa
adf5c17e0d Use context from adapters as provider context 2016-09-28 19:22:03 +02:00
Raul Ochoa
beb2d96a32 Upgrades turbo-carto to 0.17.1 2016-09-28 19:21:43 +02:00
Raul Ochoa
2a4ae88bc0 Merge remote-tracking branch 'origin/master' into cartocss-meta 2016-09-28 17:06:36 +02:00
Raul Ochoa
b76098ba45 Upgrades turbo-carto to 0.17.0 2016-09-28 16:57:04 +02:00
Raul Ochoa
c095027f8e Regenerate npm-shrinkwrap.json 2016-09-28 14:45:33 +02:00
Raul Ochoa
9d1db19907 Regenerate npm-shrinkwrap.json for turbo-carto dep 2016-09-28 11:39:58 +02:00
Daniel García Aubert
260e321537 Stub next version 2016-09-28 11:04:03 +02:00
Daniel García Aubert
073603b527 Release 2.77.1 2016-09-28 10:55:50 +02:00
Daniel García Aubert
17b259cf31 Upgrade camshaft to version 0.44.1 2016-09-28 10:54:27 +02:00
Daniel García Aubert
8f0f0026e9 Stubs next version 2016-09-27 12:28:44 +02:00
Daniel García Aubert
59dae2b545 Release 2.77.0 2016-09-26 19:11:12 +02:00
Daniel García Aubert
4670f69ead Upgrade camshaft to version 0.44.0 2016-09-26 19:05:16 +02:00
Daniel
16fbd25a34 Merge pull request #576 from CartoDB/conf-analysis-logger
Pass logger configuration to analysis backend and create a stream
2016-09-26 18:14:52 +02:00
Daniel García Aubert
2d75985cb3 Recreate stream on SIGHUP event 2016-09-26 17:39:27 +02:00
Daniel García Aubert
f963fb321e Set default config of analysis logger for test env 2016-09-23 17:35:46 +02:00
Daniel García Aubert
10feea0d48 Pass logger configuration to analysis backend and create a stream based on config 2016-09-23 17:11:04 +02:00
Raul Ochoa
b6b9b0ac36 Regenerate npm-shrinkwrap 2016-09-21 17:13:59 +02:00
Raul Ochoa
5551e85853 Update news and bump version 2016-09-21 16:06:43 +02:00
Raul Ochoa
1f0fa5031b Rely on turbo-carto metadata branch 2016-09-20 16:19:46 +02:00
Raul Ochoa
263294a3f5 Add metadata only for existing layers in turbocarto context 2016-09-20 16:19:22 +02:00
Raul Ochoa
f9df30f70b Append turbo-carto metadata per layer 2016-09-20 16:09:21 +02:00
Raul Ochoa
61d31ec054 Handle metadata from turbo-carto 2016-09-20 16:08:53 +02:00
Raul Ochoa
c8917bfc4c Return stats in callback 2016-09-20 16:00:33 +02:00
Raul Ochoa
36b69a05e5 Compute some stats in queries 2016-09-20 16:00:06 +02:00
Raul Ochoa
c8d2f66467 Stubs next version 2016-09-15 12:14:12 +02:00
Raul Ochoa
7416bb0e56 Release 2.76.0 2016-09-15 12:13:36 +02:00
Raul Ochoa
9182d0132d Merge pull request #574 from CartoDB/travis-addon
Travis' pg 9.5 addon
2016-09-15 11:17:51 +02:00
Raul Ochoa
9be9357ade Add libpango 2016-09-15 01:57:49 +02:00
Raul Ochoa
7f414f8adf Attempt to use travis' pg 9.5 addon 2016-09-15 01:51:36 +02:00
Raul Ochoa
3af9549939 Merge pull request #573 from CartoDB/external-config
Allow to use `--config /path/to/config.js` to specify configuration file
2016-09-15 01:49:43 +02:00
Raul Ochoa
41f248d731 Use logError 2016-09-15 01:35:38 +02:00
Raul Ochoa
18a517b7bf Allow to use --config /path/to/config.js to specify configuration file 2016-09-15 01:25:33 +02:00
Raul Ochoa
5150204389 Travis' Node.js 0.10 already uses npm 2.x 2016-09-14 21:22:40 +02:00
Raul Ochoa
3b16e7729d Merge pull request #572 from CartoDB/logs-conf
Allow to use absolute paths for log files
2016-09-14 21:04:19 +02:00
Raul Ochoa
76d27c9fce Allow to use absolute paths for log files
Fixes #570
2016-09-14 20:15:38 +02:00
Raul Ochoa
4ce6e41000 Stubs next version 2016-09-14 20:06:05 +02:00
Daniel García Aubert
33260cdbd9 Release 2.75.0 2016-09-14 09:48:20 +02:00
Raul Ochoa
85d81ba7fd Upgrades camshaft to 0.42.2 2016-09-09 13:40:31 +02:00
Raul Ochoa
7e8a3ca21f Stubs next version 2016-09-07 17:01:39 +02:00
Raul Ochoa
e9e4dc1f5c Release 2.74.1 2016-09-07 17:00:23 +02:00
Raul Ochoa
17c30e165a Upgrades camshaft to 0.42.1
Test fixture updated as it no longers generate bounds based on
table estimated bounds
2016-09-07 16:43:18 +02:00
Daniel García Aubert
c45c6ceb15 Stubs next version 2016-09-06 19:10:58 +02:00
Daniel García Aubert
d73c2c465f Release 2.74.0 2016-09-06 19:03:50 +02:00
Daniel
d4fc53939b Merge pull request #568 from CartoDB/567-show-hide-named-maps
Show & hide support in named maps
2016-09-06 18:53:23 +02:00
Daniel García Aubert
4becb65bec Stubs next version 2016-09-06 16:33:21 +02:00
Daniel García Aubert
f64e16c790 Release 2.73.1 2016-09-06 16:27:12 +02:00
Daniel García Aubert
1772011627 Add missinng column in fixture table cdb_analysis_catalog 2016-09-06 16:19:06 +02:00
Raul Ochoa
5b8f785e2b Regenerate npm-shrinkwrap.json 2016-09-06 16:01:05 +02:00
Daniel García Aubert
a0e3b77006 Release 2.73.0 2016-09-06 15:51:03 +02:00
Daniel García Aubert
908070ecd7 Fix jshint issue 2016-09-01 16:53:10 +02:00
Daniel García Aubert
7c6a58cd30 Made explicit to recreate named-map-provider 2016-09-01 16:42:06 +02:00
Daniel García Aubert
b0990a1132 Removed template-maps backend from named-maps controller 2016-09-01 16:31:30 +02:00
Daniel García Aubert
c6988cdb88 Improved and implemented new test for layer visibility suite 2016-09-01 12:37:56 +02:00
Daniel García Aubert
e0d304b033 Applied approach similar to static image options 2016-09-01 12:37:04 +02:00
Daniel García Aubert
e4a9f2d64c Added template map as dependency of named maps controller 2016-09-01 12:35:35 +02:00
Daniel García Aubert
0236fe3ca9 Implemented new scenario 2016-08-31 20:41:07 +02:00
Daniel García Aubert
1bed8623a2 Overwrites rendererparams instead of layergroup in mapconfig 2016-08-31 20:40:55 +02:00
Daniel García Aubert
df7d957914 Implemented acceptance test for layer visibility in previews 2016-08-31 19:41:23 +02:00
Daniel García Aubert
30c4b00f33 New approach: decorates named_map_provider to return the with visible layers 2016-08-31 19:40:00 +02:00
Daniel García Aubert
ab27886460 Renamed option by for name maps template 2016-08-31 19:37:48 +02:00
Daniel García Aubert
31e18d04d7 Undo unneeded optimization 2016-08-31 13:57:28 +02:00
Daniel García Aubert
8155484510 Improved test visibility layer with layerId 2016-08-29 10:46:55 +02:00
Daniel García Aubert
b61f1d2b53 Attached layer_visibility property to the named template 2016-08-26 17:30:03 +02:00
Daniel García Aubert
2e274b936a Improved test to check all possible values of visibility in named maps templates 2016-08-26 15:07:06 +02:00
Daniel García Aubert
bf3e311b57 Avoid unnecessary complexity 2016-08-26 14:46:23 +02:00
Daniel García Aubert
6a7613de6b Improved layergroup instantiation to filter non visible layers 2016-08-26 14:42:33 +02:00
Daniel García Aubert
ee46549e04 First approach 2016-08-25 20:04:23 +02:00
Raul Ochoa
377f3d4aff Removes constraint on filtered mapnik layers 2016-08-25 18:34:19 +02:00
Daniel García Aubert
752d47d71e Stubs next version 2016-08-23 11:25:39 +02:00
Daniel García Aubert
367157b80c Release 2.72.0 2016-08-23 11:19:48 +02:00
Daniel García Aubert
53542f1cd6 Stubs next version 2016-08-17 15:15:18 +02:00
Daniel García Aubert
7a8f156abf Release 2.71.0 2016-08-17 15:12:42 +02:00
Daniel García Aubert
c60cc57a0d Upgrades Windshaft to version 2.5.0 2016-08-17 15:07:29 +02:00
Raul Ochoa
8de6ec9a21 Stubs next version 2016-08-16 12:25:12 +02:00
Raul Ochoa
44b6f4be7e Release 2.70.0 2016-08-16 12:24:23 +02:00
Raul Ochoa
280be1751c Merge pull request #565 from CartoDB/update-camshaft
Upgrade camshaft to 0.40.0
2016-08-16 12:19:25 +02:00
Raul Ochoa
701a73a2c5 Upgrade camshaft to 0.40.0 2016-08-16 11:33:32 +02:00
Raul Ochoa
b578eada07 Stubs next version 2016-08-12 10:34:33 +02:00
Raul Ochoa
8100f155dc Release 2.69.1 2016-08-12 10:33:10 +02:00
Raul Ochoa
9f1a014004 Upgrades windshaft to 2.4.2 2016-08-12 10:24:06 +02:00
Raul Ochoa
e35e0e157c Stubs next version 2016-08-11 12:06:33 +02:00
Raul Ochoa
3aff328af3 Release 2.69.0 2016-08-11 12:05:56 +02:00
Raul Ochoa
ffb086045a Bump version 2016-08-11 12:05:37 +02:00
Raul Ochoa
c0786dfa6f Merge pull request #564 from CartoDB/fix-travis
Avoid 'No such file or directory'
2016-08-10 12:59:29 +02:00
Raul Ochoa
ddc33fa52b Avoid 'No such file or directory' 2016-08-10 10:30:04 +02:00
Raul Ochoa
9f2d6a5d41 Merge pull request #563 from CartoDB/upgrade-camshaft
Upgrade camshaft to 0.39.0
2016-08-09 17:01:09 +02:00
Raul Ochoa
64e884a092 force-yes for postgresql-plpython-9.5 2016-08-09 15:45:21 +02:00
Raul Ochoa
17ec174683 Use --force-yes 2016-08-09 15:41:17 +02:00
Raul Ochoa
3666cbee94 Upgrade camshaft to 0.39.0 2016-08-09 15:29:54 +02:00
Raul Ochoa
25de018f7d Add comment about row_limit config parameter 2016-08-01 12:59:38 +02:00
Raul Ochoa
6597851b48 Stubs next version 2016-07-21 21:04:39 +02:00
Raul Ochoa
0399131968 Release 2.68.0 2016-07-21 21:03:10 +02:00
Raul Ochoa
86836e7f89 Upgrades turbo-carto to 0.16.0 2016-07-21 21:02:06 +02:00
Daniel García Aubert
df346b11d3 Release 2.67.1 2016-07-21 16:05:12 +02:00
Daniel García Aubert
27f74b3fe2 Stubs next version 2016-07-21 10:41:42 +02:00
Daniel García Aubert
87dec64ad1 Release 2.67.0 2016-07-21 10:34:10 +02:00
Daniel García Aubert
54c787162a Update NEWS 2016-07-21 10:27:46 +02:00
Daniel García Aubert
6e92e699dc Upgrades camshaft version to 0.38.0 2016-07-21 10:25:53 +02:00
Raul Ochoa
7950f43db3 Stubs next version 2016-07-20 19:38:09 +02:00
Raul Ochoa
d300677315 Release 2.66.2 2016-07-20 19:26:27 +02:00
Raul Ochoa
bd4d29dd14 Upgrades turbo-carto to 0.15.1 2016-07-20 19:25:53 +02:00
Raul Ochoa
18a84433f4 Merge pull request #562 from CartoDB/git-for-log4js-dep
Use git for log4js dependency
2016-07-20 19:21:02 +02:00
Raul Ochoa
768ebf0ef2 Use git for log4js dependency 2016-07-20 18:34:57 +02:00
Raul Ochoa
1c20cb5478 Stubs next version 2016-07-20 11:20:12 +02:00
Raul Ochoa
279587ea11 Release 2.66.1 2016-07-20 11:16:23 +02:00
Raul Ochoa
25df193390 Merge pull request #560 from CartoDB/upgrade-turbo-carto
Upgrades turbo-carto to 0.15.0
2016-07-19 20:03:30 +02:00
Raul Ochoa
9ce81693bd Merge remote-tracking branch 'origin/master' into upgrade-turbo-carto
Conflicts:
	npm-shrinkwrap.json
2016-07-19 19:46:19 +02:00
Raul Ochoa
16765e092f Upgrades turbo-carto to 0.15.0 2016-07-19 19:28:53 +02:00
Daniel García Aubert
237e1257c4 Stubs next version 2016-07-18 13:34:14 +02:00
Daniel García Aubert
665859b17d Release 2.66.0 2016-07-18 13:31:55 +02:00
Raul Ochoa
b5a6d6974c Do not cast type in category ramp 2016-07-18 11:58:19 +02:00
Raul Ochoa
26bab029f4 Prepare for new turbo-carto versio 2016-07-18 11:57:27 +02:00
Raul Ochoa
ed7bb07b03 Output actual error message 2016-07-18 11:13:37 +02:00
Raul Ochoa
c87277ad01 Adjust to fail in specific scenario 2016-07-18 11:13:01 +02:00
Raul Ochoa
62be259a90 Rename turbo-cartocss -> turbo-carto 2016-07-18 10:29:13 +02:00
Daniel García Aubert
80798f984b Stubs next version 2016-07-15 12:15:47 +02:00
Daniel García Aubert
e32409880c Release 2.65.0 2016-07-15 12:07:37 +02:00
Daniel García Aubert
7b6eb2940e Upgrades camshaft version to 0.37.0 2016-07-15 12:01:29 +02:00
Raul Ochoa
87ad8df22f Merge pull request #557 from CartoDB/upgrade-cartodb-redis
Upgrades cartodb-redis to 0.13.1
2016-07-13 16:19:19 +02:00
Raul Ochoa
9fe20036a1 Upgrades cartodb-redis to 0.13.1
Which removes strftime dependency

Closes #285
2016-07-13 16:05:16 +02:00
Raul Ochoa
2b0d8d43bd Merge pull request #555 from CartoDB/analyses-controller
Add controller to list user analyses
2016-07-12 16:53:43 +02:00
Raul Ochoa
3af340d384 Add controller to list user analyses 2016-07-12 16:08:48 +02:00
Daniel García Aubert
20c1ad8d87 Stubs next version 2016-07-12 11:27:09 +02:00
Daniel García Aubert
8c351c7c46 Release 2.64.0 2016-07-12 11:23:40 +02:00
Daniel
6647e986d9 Merge pull request #554 from CartoDB/546-add-node-id-to-analyis-errors
add node id to analyis errors
2016-07-12 10:40:00 +02:00
Daniel García Aubert
77f691520c Merge branch 'master' into 546-add-node-id-to-analyis-errors 2016-07-11 16:06:58 +02:00
Daniel García Aubert
dfaa6ec024 Fixes #546, added node_id property to analysis errors 2016-07-11 15:48:26 +02:00
Raul Ochoa
41c574f5df Stubs next version 2016-07-11 10:50:49 +02:00
Raul Ochoa
10e901dcaa Release 2.63.0 2016-07-11 10:50:01 +02:00
Raul Ochoa
1d3626b4e1 Bump version 2016-07-11 10:49:45 +02:00
Raul Ochoa
bc419a51d6 Merge pull request #553 from CartoDB/node-error-on-create
Return last error message for failed nodes on map creation
2016-07-11 10:48:10 +02:00
Raul Ochoa
d0980a2872 Return last error message for failed nodes on map creation 2016-07-11 10:42:46 +02:00
Raul Ochoa
74719e48d9 Upgrades camshaft to 0.35.0 2016-07-11 10:42:14 +02:00
Raul Ochoa
d7c6b45438 Merge pull request #552 from CartoDB/upgrade-lzma
Upgrades lzma to 2.3.2
2016-07-08 16:20:07 +02:00
Raul Ochoa
551cfd87ee Upgrades lzma to 2.3.2 2016-07-08 13:52:07 +02:00
Carlos Matallín
ba29873a8e Merge pull request #548 from CartoDB/docs-781
rebranding
2016-07-07 18:49:39 +02:00
Daniel García Aubert
d9e2bb4537 Stubs next version 2016-07-07 18:35:16 +02:00
Daniel García Aubert
16d7b15d67 Release 2.62.0 2016-07-07 18:19:39 +02:00
Daniel García Aubert
b9da97fedd Upgrades camshaft to version 0.34.0 2016-07-07 18:13:19 +02:00
Raul Ochoa
bdf3b0393a Stubs next version 2016-07-07 00:39:49 +02:00
Raul Ochoa
a18f701466 Release 2.61.2 2016-07-07 00:38:58 +02:00
Raul Ochoa
71c7d8a90c Limit analysis creation concurrency 2016-07-07 00:36:59 +02:00
Raul Ochoa
99e766d952 Upgrades camshaft to 0.33.3 2016-07-07 00:36:39 +02:00
Raul Ochoa
fc78a0ed36 Stubs next version 2016-07-06 22:59:51 +02:00
Raul Ochoa
d4d398f583 Release 2.61.1 2016-07-06 22:59:06 +02:00
Raul Ochoa
be766ec803 Merge pull request #547 from CartoDB/mapconfig-dataviews
Use mapconfig to to store/retrieve dataviews queries
2016-07-06 22:58:11 +02:00
Raul Ochoa
57bb8dbbe3 Use mapconfig to to store/retrieve dataviews queries 2016-07-06 22:31:08 +02:00
Raul Ochoa
c539d4fbbd Change camshaft naming from filters 2016-07-06 21:11:39 +02:00
Raul Ochoa
a7dddcebe8 Stubs next version 2016-07-06 18:12:11 +02:00
Raul Ochoa
a9275845ff Release 2.61.0 2016-07-06 18:11:31 +02:00
Raul Ochoa
e5bf9efdb9 Upgrades camshaft to 0.33.2 2016-07-06 18:11:01 +02:00
Raul Ochoa
85073345ec Merge pull request #545 from CartoDB/turbo-carto-multiple-errors
Turbo carto multiple errors
2016-07-06 12:56:12 +02:00
Raul Ochoa
80d5b29902 More clear turbo-carto error messages: no context in message 2016-07-06 12:34:09 +02:00
Raul Ochoa
f55b748d20 Upgrade turbo-carto to 0.14.0 2016-07-06 12:33:08 +02:00
Raul Ochoa
870468ddf7 Merge pull request #543 from CartoDB/turbo-carto-multiple-errors
Return multiple turbo-carto errors
2016-07-06 00:43:00 +02:00
Raul Ochoa
b3107916ce Return multiple turbo-carto errors
Closes #541
2016-07-06 00:32:30 +02:00
Raul Ochoa
9cf856ab78 Merge pull request #542 from CartoDB/upgrade-turbo-carto
Upgrades turbo-carto to 0.13.0
2016-07-05 19:59:06 +02:00
Raul Ochoa
bbd047a940 Upgrades turbo-carto to 0.13.0 2016-07-05 19:53:13 +02:00
Daniel García Aubert
d3dfb0a7ff Stubs next version 2016-07-05 19:24:26 +02:00
Daniel García Aubert
5a8f9db79c Release 2.60.0 2016-07-05 18:39:20 +02:00
Daniel García Aubert
fbe20386b6 Upgrades camshaft version to 0.32.0 2016-07-05 18:31:47 +02:00
Raul Ochoa
6245b40015 Stubs next version 2016-07-05 15:51:36 +02:00
Raul Ochoa
3e959d8dc0 Release 2.59.1 2016-07-05 15:50:57 +02:00
Raul Ochoa
564df920d1 Upgrades camshaft to 0.31.0 2016-07-05 15:50:31 +02:00
Raul Ochoa
7e2c467a4f Stubs next version 2016-07-05 15:25:38 +02:00
Raul Ochoa
3a42305408 Release 2.59.0 2016-07-05 15:25:00 +02:00
Raul Ochoa
09616777e6 Merge pull request #540 from CartoDB/upgrade-camshaft
Upgrades camshaft to 0.30.0
2016-07-05 15:24:05 +02:00
Raul Ochoa
c2a5569b8c Upgrades camshaft to 0.30.0 2016-07-05 14:59:09 +02:00
Raul Ochoa
38bea2108b Stubs next version 2016-07-05 12:16:35 +02:00
Raul Ochoa
ab0777b45f Release 2.58.0 2016-07-05 12:15:55 +02:00
Raul Ochoa
962f94387b Fix release date 2016-07-05 12:15:41 +02:00
Raul Ochoa
de5600b4fd Bump version 2016-07-05 12:15:09 +02:00
Raul Ochoa
188a202f02 Merge pull request #538 from CartoDB/upgrade-camshaft
Upgrades camshaft to 0.29.2
2016-07-05 12:14:20 +02:00
Raul Ochoa
19f39b87f5 Merge branch 'master' into upgrade-camshaft
Conflicts:
	NEWS.md
2016-07-05 11:42:57 +02:00
Raul Ochoa
c759f314f9 Merge pull request #539 from CartoDB/nodes-metadata-fix
Return full list of nodes in response metadata
2016-07-05 11:42:13 +02:00
Raul Ochoa
6c98f14c64 Return full list of nodes in response metadata 2016-07-05 11:26:52 +02:00
Raul Ochoa
f2348e1b24 Upgrades camshaft to 0.29.2 2016-07-05 11:25:24 +02:00
Raul Ochoa
4b5a10fe61 Upgrades camshaft to 0.29.1 2016-07-05 10:26:48 +02:00
Raul Ochoa
163fa58b5a Merge pull request #537 from CartoDB/analysis-failed-status-error-message
Include error message in node status endpoint
2016-07-05 10:22:43 +02:00
csobier
2bb03225cb updated all api examples with rebranded account url- confirmed by luis 2016-07-04 17:30:16 -04:00
Raul Ochoa
b3bbb6af01 Include error message in node status endpoint 2016-07-04 18:40:11 +02:00
Raul Ochoa
28d711e1f4 Stubs next version 2016-07-04 17:54:39 +02:00
Raul Ochoa
410fdb8343 Release 2.57.0 2016-07-04 17:53:58 +02:00
Raul Ochoa
21c2f3bdd1 Merge pull request #536 from CartoDB/upgrade-camshaft
Upgrade camshaft to 0.28.1
2016-07-04 17:53:20 +02:00
Raul Ochoa
baae080318 Merge remote-tracking branch 'origin/master' into upgrade-camshaft
Conflicts:
	NEWS.md
	package.json
2016-07-04 17:04:54 +02:00
Raul Ochoa
81c0796056 Upgrades camshaft to 0.28.1 2016-07-04 17:03:56 +02:00
Daniel García Aubert
6ea5c5f414 Stubs next version 2016-07-04 16:29:30 +02:00
Daniel García Aubert
7e37705843 Release 2.56.0 2016-07-04 16:08:38 +02:00
Daniel García Aubert
425b5e6b4a Upgrades camshaft version to 0.27.0 2016-07-04 15:56:04 +02:00
Raul Ochoa
8942c72fb2 Stubs next version 2016-07-04 10:28:44 +02:00
Raul Ochoa
e9359fdd73 Release 2.55.0 2016-07-04 10:28:07 +02:00
Raul Ochoa
5c4308abc1 Merge pull request #534 from CartoDB/turbo-carto-valid-ramps
Skip null values for quantification methods
2016-07-04 02:30:23 +02:00
Raul Ochoa
61576b671b Update news 2016-07-04 02:23:48 +02:00
Raul Ochoa
1e4d6bb942 Merge remote-tracking branch 'origin/master' into turbo-carto-valid-ramps 2016-07-04 02:22:41 +02:00
Raul Ochoa
ff6c0addb4 Merge pull request #533 from CartoDB/upgrade-camshaft
Upgrades camshaft to 0.26.0
2016-07-04 02:22:24 +02:00
Raul Ochoa
98cd524c07 Skip null values for quantification methods generating null values 2016-07-04 02:15:54 +02:00
Raul Ochoa
746d57ff42 Red: expose issues with some quantification method when query returns empty 2016-07-04 02:11:52 +02:00
Raul Ochoa
7319822419 Upgrades camshaft to 0.26.0 2016-07-04 02:07:12 +02:00
Raul Ochoa
58bcde3818 Merge pull request #532 from CartoDB/upgrade-camshaft
Upgrade camshaft to 0.25.0
2016-07-03 11:51:56 +02:00
Raul Ochoa
b57d08f38e Output nodes for test 2016-07-03 11:45:10 +02:00
Raul Ochoa
739a8cef32 Regenerate npm-shrinkwrap.json for 0.25.0 2016-07-03 11:27:04 +02:00
Raul Ochoa
8a6e31e025 Merge remote-tracking branch 'origin/master' into upgrade-camshaft 2016-07-03 10:59:12 +02:00
Raul Ochoa
616aac9771 Upgrade camshaft to 0.25.0
Use new configuration with user for nodes.
2016-07-03 10:49:45 +02:00
Raul Ochoa
23a1b7484e Merge pull request #531 from CartoDB/pg-9.5
Use postgresql 9.5 in travis
2016-07-02 20:05:55 +02:00
Raul Ochoa
9624ee1c76 Attempt to use postgresql 9.5 2016-07-02 19:54:34 +02:00
Raul Ochoa
4c557be2c2 Update to use latest cdb_analysis_catalog
It avoids to execute queries that are extension specifics
2016-07-02 19:50:59 +02:00
Raul Ochoa
7577ee8015 Stubs next version 2016-07-01 20:17:11 +02:00
Daniel García Aubert
8c73914da4 Release 2.54.0 2016-06-30 15:00:21 +02:00
Raul Ochoa
604ba300aa Bump version 2016-06-30 13:33:54 +02:00
Raul Ochoa
876166ab74 Update news 2016-06-30 13:33:25 +02:00
Raul Ochoa
1bf8fda770 Merge pull request #530 from CartoDB/upgrade-camshaft
Upgrades camshaft to 0.23.0
2016-06-30 13:31:01 +02:00
Raul Ochoa
cd32218cea Upgrades camshaft to 0.23.0 2016-06-30 13:14:30 +02:00
Daniel
0fd0974738 Merge pull request #525 from CartoDB/fix-error-context
Fix error context, replaced `turbo-carto` error type by `layer` type
2016-06-29 19:15:44 +02:00
Daniel García Aubert
ed7f95a1a7 Merge branch 'master' into fix-error-context 2016-06-29 19:02:54 +02:00
Daniel García Aubert
a36c1c52ae Fixs typo 2016-06-29 17:19:01 +02:00
Daniel García Aubert
226d948c4d Upgrades windshaft to version 2.4.0 2016-06-29 17:08:55 +02:00
Raul Ochoa
84c67f977e Stubs next version 2016-06-29 16:50:19 +02:00
Raul Ochoa
c09cda84a3 Release 2.53.5 2016-06-29 16:49:34 +02:00
Raul Ochoa
bd36ea1829 news style 2016-06-29 16:49:13 +02:00
Raul Ochoa
01a47925e0 Merge pull request #529 from CartoDB/528-invalid-error-regression
Fixes invalid error on node not found from dataview
2016-06-29 16:47:16 +02:00
Raul Ochoa
dd8a70eb95 Uses node list so identical nodes are not de-duplicated and can be used with different ids
Fixes #528
2016-06-29 16:10:26 +02:00
Raul Ochoa
013bdba4ff Add regression test wheren node id can't be found and it should 2016-06-29 16:08:38 +02:00
Raul Ochoa
c1acc54d55 Add constants for cartocss symbolizers 2016-06-29 16:07:42 +02:00
Raul Ochoa
5f3fb6e5f7 Adds fake CDB_KMeans function 2016-06-29 16:07:01 +02:00
Daniel García Aubert
e3fac9c161 Updated NEWS 2016-06-29 16:03:52 +02:00
Daniel García Aubert
accab9e78a Gets layerId from the layer that raises the error 2016-06-29 12:21:15 +02:00
Raul Ochoa
cb53d140e3 Stubs next version 2016-06-28 19:48:47 +02:00
Raul Ochoa
72986d1946 Release 2.53.4 2016-06-28 19:47:59 +02:00
Raul Ochoa
2195c55518 Upgrades camshaft to 0.22.4 2016-06-28 19:44:55 +02:00
Raul Ochoa
c418ba1908 Stubs next version 2016-06-28 14:39:02 +02:00
Raul Ochoa
934356e5cc Release 2.53.3 2016-06-28 14:38:25 +02:00
Raul Ochoa
c40235a910 Upgrades camshaft to 0.22.3 2016-06-28 14:37:21 +02:00
Raul Ochoa
cd8338196e Stubs next version 2016-06-28 13:08:08 +02:00
Raul Ochoa
2143e87401 Release 2.53.2 2016-06-28 13:07:19 +02:00
Raul Ochoa
f0a536ee1e Upgrades camshaft to 0.22.2 2016-06-28 13:06:46 +02:00
Raul Ochoa
dde4b63c6b Stubs next version 2016-06-28 00:25:29 +02:00
Raul Ochoa
0e7bcc4b56 Release 2.53.1 2016-06-28 00:24:40 +02:00
Raul Ochoa
e4816b4322 Merge pull request #527 from CartoDB/upgrade-camshaft
Upgrades camshaft to 0.22.1
2016-06-28 00:21:33 +02:00
Raul Ochoa
af4f29c538 Upgrades camshaft to 0.22.1 2016-06-28 00:17:33 +02:00
Daniel García Aubert
016adb64ef Fix error context, replaced turbo-carto error type by layer type. Context is no longer used, custom property for each type will be used instead. 2016-06-26 18:43:04 +02:00
Raul Ochoa
7cedccedcd Stubs next version 2016-06-24 14:59:23 +02:00
Raul Ochoa
a9c12d4534 Release 2.53.0 2016-06-24 14:58:44 +02:00
Raul Ochoa
77f71b1978 Merge pull request #523 from CartoDB/upgrade-camshaft
Upgrades camshaft to 0.22.0
2016-06-24 14:56:05 +02:00
Raul Ochoa
1c029fbc7b Upgrades camshaft to 0.22.0 2016-06-24 14:51:44 +02:00
Raul Ochoa
2bc0d8d145 Stubs next version 2016-06-23 19:09:16 +02:00
Raul Ochoa
4c2af88f92 Release 2.52.0 2016-06-23 19:08:36 +02:00
Raul Ochoa
ddd5d2a0b0 Bump version 2016-06-23 19:08:10 +02:00
Raul Ochoa
d5cb59dc84 Merge pull request #521 from CartoDB/upgrade-camshaft
Upgrades camshaft to 0.21.0
2016-06-23 19:06:25 +02:00
Raul Ochoa
f21581630a Upgrades camshaft to 0.21.0 2016-06-23 19:01:59 +02:00
Raul Ochoa
3fef37d06b Stubs next version 2016-06-22 17:23:04 +02:00
Raul Ochoa
a8b93896ed Release 2.51.0 2016-06-22 17:22:20 +02:00
Raul Ochoa
6c1e9bf0ca Bump version 2016-06-22 17:21:58 +02:00
Raul Ochoa
1d8947d404 Merge pull request #520 from CartoDB/upgrade-camshaft
Upgrades camshaft to 0.20.0
2016-06-22 15:13:17 +02:00
Raul Ochoa
834377b342 Upgrades camshaft to 0.20.0 2016-06-22 15:03:26 +02:00
Daniel García Aubert
c2e0eb05e5 Updated NEWS. #519 2016-06-21 18:43:27 +02:00
Daniel
256032ca4a Merge pull request #519 from CartoDB/fix-error-with-context
Now errors with context have the same schema.
2016-06-21 18:41:46 +02:00
Daniel García Aubert
d80f2b9566 Now errors with context have the same schema. 2016-06-21 18:26:10 +02:00
Raul Ochoa
9e2f0371ba Merge pull request #518 from CartoDB/better-analyses-errors
Improve error messages for missing analyses for layers and dataviews
2016-06-21 17:34:44 +02:00
Raul Ochoa
a2e74a3e1b Improve error messages for missing analyses for layers and dataviews 2016-06-21 17:25:28 +02:00
Raul Ochoa
f04a5a1ab9 Merge pull request #517 from CartoDB/turbo-carto-datasource-improvements
Split turbo-carto adapter substitutions tokens query
2016-06-21 15:48:07 +02:00
Raul Ochoa
7114311b75 Split turbo-carto adapter substitutions tokens query 2016-06-21 15:05:44 +02:00
Daniel García Aubert
1e9e092dc3 Stubs next version 2016-06-21 14:52:27 +02:00
Daniel García Aubert
98b3a5ba23 Release 2.50.0 2016-06-21 14:41:52 +02:00
Daniel García Aubert
200966e806 Merge branch 'upgrade-camshaft-0.19.0' 2016-06-21 14:34:28 +02:00
Daniel García Aubert
407430b81e Updated shrinkwrap 2016-06-21 14:20:54 +02:00
Raul Ochoa
c4b1fc039c Merge pull request #516 from CartoDB/turbo-carto-datasource-improvements
Pixel size query for turbo-carto adapter using radians and degrees
2016-06-21 14:12:41 +02:00
Raul Ochoa
d00379af6b Pixel size query for turbo-carto adapter using radians and degrees instead of meters 2016-06-21 14:01:43 +02:00
Raul Ochoa
7cee0f3ee3 Merge pull request #515 from CartoDB/turbo-carto-datasource-improvements
Use psql client instead of pg query runner to use proper params
2016-06-21 13:24:36 +02:00
Daniel García Aubert
2588346f1b Bumped camshaft version to 0.19.0 2016-06-21 13:19:01 +02:00
Raul Ochoa
863128013d Use psql client instead of pg query runner to use proper params 2016-06-21 12:08:40 +02:00
Raul Ochoa
51eb8eb67f Upgrades camshaft to 0.18.0 2016-06-20 17:09:55 +02:00
Raul Ochoa
28e01fd8ac Bump version 2016-06-20 16:47:38 +02:00
Raul Ochoa
726e153ad5 Merge pull request #514 from CartoDB/dataview-aggregation-operations
Add support for min, max, and avg operations in aggregation dataview
2016-06-20 16:40:55 +02:00
Raul Ochoa
e8df09c85b Add support for min, max, and avg operations in aggregation dataview 2016-06-20 16:26:24 +02:00
Raul Ochoa
2b4fb2971d Stubs next version 2016-06-20 13:44:24 +02:00
Raul Ochoa
933b36cca0 Release 2.49.1 2016-06-20 13:35:59 +02:00
Raul Ochoa
37a7cfb6ba Update news 2016-06-20 13:35:38 +02:00
Raul Ochoa
8a1cda159c Merge pull request #512 from CartoDB/turbo-carto-datasource-fixes
Use an empty array as default value for falsy ramps
2016-06-20 13:33:02 +02:00
Raul Ochoa
403dcbebcd Use an empty array as default value for falsy ramps
Fixes #507
2016-06-20 13:27:39 +02:00
Raul Ochoa
373ad69306 Merge branch 'master' into turbo-carto-datasource-fixes 2016-06-20 13:27:02 +02:00
Raul Ochoa
b2029e09f5 Add CDB_OverviewsSupport sql from extension to fix CDB_Overviews calls 2016-06-20 13:26:30 +02:00
Raul Ochoa
4f37d2d0c2 Empty results should keep working, going red 2016-06-20 13:09:01 +02:00
Raul Ochoa
a5b07bc2a8 Upgrades turbo-carto to 0.12.1 2016-06-20 13:07:51 +02:00
Raul Ochoa
1544a5622d Merge pull request #511 from CartoDB/dataview-the_geom-query
Dataview the geom query
2016-06-17 17:08:48 +02:00
Raul Ochoa
d49a877771 Fix reversed own filter option 2016-06-17 16:26:45 +02:00
Raul Ochoa
0f4747743c Merge branch 'master' into dataview-the_geom-query 2016-06-17 15:55:07 +02:00
Raul Ochoa
368e4522e7 Merge pull request #510 from CartoDB/updated-at-from-analyses-result
Pick last update time for layergroupid from analyses results
2016-06-17 15:44:11 +02:00
Raul Ochoa
cb1d1bb115 Pick last update time for layergroupid from analyses results 2016-06-17 14:46:22 +02:00
Raul Ochoa
612cc3dd41 Use the_geom for intermediate dataviews 2016-06-16 17:27:00 +02:00
Raul Ochoa
bff082e577 Stubs next version 2016-06-15 19:27:48 +02:00
Raul Ochoa
ea41750a14 Release 2.49.0 2016-06-15 19:26:25 +02:00
Raul Ochoa
458376a665 Merge pull request #506 from CartoDB/upgrade-camshaft
Upgrade camshaft to 0.17.1
2016-06-15 19:24:48 +02:00
Raul Ochoa
f0284907c4 Upgrade camshaft to 0.17.1 2016-06-15 19:13:37 +02:00
Raul Ochoa
ad0385ccf7 Merge pull request #505 from CartoDB/upgrade-camshaft
Upgrade camshaft to 0.17.0
2016-06-15 16:58:23 +02:00
Raul Ochoa
4350fc3c65 Upgrade camshaft to 0.17.0 2016-06-15 16:48:00 +02:00
Raul Ochoa
fc3422b9e5 Merge pull request #503 from CartoDB/upgrade-camshaft
Upgrades camshaft to 0.16.0
2016-06-15 11:13:50 +02:00
Raul Ochoa
9703c19fb4 Upgrades camshaft to 0.16.0 2016-06-15 11:01:11 +02:00
Daniel García Aubert
d36f2fb354 Stubs next version 2016-06-14 17:50:40 +02:00
Daniel García Aubert
c837785314 Release 2.48.0 2016-06-14 17:48:07 +02:00
Daniel
33014a9f45 Merge pull request #500 from CartoDB/478-error-context
Adds more error information when either analysis or turbo-carto is not well formed
2016-06-14 15:56:00 +02:00
Daniel García Aubert
c16d0b8605 Fixed broken tests 2016-06-14 10:50:50 +02:00
Daniel García Aubert
c88e4c5173 Merge branch 'master' into 478-error-context 2016-06-14 10:43:00 +02:00
Daniel García Aubert
0540696c3e Avoid to expose internal naming 2016-06-14 10:27:35 +02:00
Raul Ochoa
5f59a97a02 Merge pull request #502 from CartoDB/upgrade-camshaft
Upgrades camshaft to 0.15.1
2016-06-14 01:41:41 +02:00
Raul Ochoa
d3b815c3c7 Upgrades camshaft to 0.15.1 2016-06-14 01:36:15 +02:00
Raul Ochoa
d2a8dcbede Merge pull request #501 from CartoDB/upgrade-camshaft
Upgrades camshaft to 0.15.0
2016-06-13 22:14:47 +02:00
Daniel García Aubert
4854e879a6 Updated npm-shrinkwrap 2016-06-13 19:49:20 +02:00
Daniel García Aubert
ddc99cebff Upgrades turbo-carto to 0.12.0 2016-06-13 18:40:14 +02:00
Raul Ochoa
47470d4f2b Upgrades camshaft to 0.15.0 2016-06-13 18:04:48 +02:00
Daniel García Aubert
d9297d54de made error_with_context non optional and adapted test's assertion 2016-06-13 16:14:01 +02:00
Daniel García Aubert
2d821f957e Merge branch 'master' into 478-error-context 2016-06-13 12:29:00 +02:00
Daniel García Aubert
c0ce6e7a8a WIP fixes 478, adds more error information when either analysis or turbo-carto is not well formed. 2016-06-13 12:20:56 +02:00
Raul Ochoa
3f620c6cdd Stubs next version 2016-06-13 10:47:15 +02:00
Raul Ochoa
9160d8018d Release 2.47.1 2016-06-13 10:43:45 +02:00
Raul Ochoa
51307bcc69 Upgrades camshaft to 0.14.1 2016-06-10 18:38:49 +02:00
Raul Ochoa
d29651ee80 Stubs next version 2016-06-10 14:41:08 +02:00
Raul Ochoa
18640077aa Release 2.47.0 2016-06-10 14:40:28 +02:00
Raul Ochoa
59563c893b Merge pull request #499 from CartoDB/upgrade-camshaft
Upgrades camshaft to 0.14.0
2016-06-10 14:19:39 +02:00
Raul Ochoa
90fd1786e1 Upgrades camshaft to 0.14.0 2016-06-10 14:05:23 +02:00
Raul Ochoa
4a11115dd0 Improve errors for dataviews validation 2016-06-09 18:13:54 +02:00
Raul Ochoa
baf3e774c5 Stubs next version 2016-06-09 10:33:35 +02:00
Raul Ochoa
ac296411d5 Release 2.46.0 2016-06-09 10:15:26 +02:00
Raul Ochoa
09cea4d6d4 Merge pull request #498 from CartoDB/upgrade-windshaft
Upgrades windshaft to 2.3.0
2016-06-08 19:45:53 +02:00
Raul Ochoa
382ff2416f Upgrades windshaft to 2.3.0 2016-06-08 19:14:48 +02:00
Raul Ochoa
27036379dd Merge pull request #494 from CartoDB/upgrade-windshaft
Upgrades windshaft to 2.2.0
2016-06-07 19:34:35 +02:00
Raul Ochoa
f4e6e140e0 Upgrades windshaft to 2.2.0 2016-06-07 19:26:41 +02:00
Raul Ochoa
b4e5cb88d9 Merge pull request #491 from CartoDB/issue-375
Sort start and end override params to correct bins width
2016-06-06 17:22:28 +02:00
Raul Ochoa
3269fef845 Sort start and end override params
Fixes #375
2016-06-06 17:10:52 +02:00
Raul Ochoa
e797719b41 Append url params for widgets 2016-06-06 17:10:36 +02:00
Raul Ochoa
284a8f2465 Deduplicate and skip falsy column names for geojson queries
Although Windshaft is already removing duplicates and skipping falsy
columns it's better to provide it with good input.

Closes #476
2016-06-06 15:58:16 +02:00
Raul Ochoa
54ea656da2 Merge pull request #490 from CartoDB/geojson-substitution-tokens
Upgrades windshaft to 2.1.0
2016-06-06 15:36:38 +02:00
Raul Ochoa
b4aaadf40b Upgrades windshaft to 2.1.0
Adds support for substitution tokens in geojson tiles

Fixes #484.
2016-06-06 15:29:58 +02:00
Raul Ochoa
74d2e3ef75 Merge pull request #489 from CartoDB/warn-about-deps
Warn on application start about non-matching dependencies
2016-06-06 12:11:38 +02:00
Raul Ochoa
b10e4c11d9 Warn on application start about non-matching dependencies 2016-06-06 12:07:43 +02:00
Raul Ochoa
075e141a9c Merge pull request #488 from CartoDB/upgrade-deps
Upgrades turbo-carto and camshaft deps
2016-06-06 12:04:56 +02:00
Raul Ochoa
04acf895f0 Upgrades turbo-carto and camshaft deps 2016-06-06 11:59:56 +02:00
Raul Ochoa
21608bf2e2 Merge pull request #487 from CartoDB/dataviews-adapter-fixes
Dataviews adapter working with non sql, non source layers
2016-06-06 11:56:31 +02:00
Raul Ochoa
653beb1952 Dataviews/widgets adapter working with non sql, non source, and non widgets layers
Ref #480
2016-06-06 11:46:27 +02:00
Raul Ochoa
050d33ff14 Use the_geom_webmercator; srid=3857 for the bounding box filter 2016-06-02 20:39:15 +02:00
Raul Ochoa
1ae86e039b Dataviews adapter: skip layers not containing SQL or widgets 2016-06-02 20:17:39 +02:00
Raul Ochoa
f75cadf6ba Dataviews adapter should work when there is a mix of layers with and without widgets 2016-06-02 19:51:16 +02:00
csobier
9acb980b82 more reverted code applied 2016-06-02 13:22:07 -04:00
Raul Ochoa
93d0fe9176 Stubs next version 2016-06-02 16:21:40 +02:00
Raul Ochoa
6a15cd0566 Release 2.45.0 2016-06-02 16:19:39 +02:00
Raul Ochoa
614fe3f703 Update news and bump version 2016-06-02 14:45:00 +02:00
Raul Ochoa
82d4bb3046 Merge pull request #485 from CartoDB/mapconfig-dataviews-adapter
Mapconfig dataviews adapter
2016-06-02 14:42:22 +02:00
Raul Ochoa
f49c13b1b3 Do not apply twice metadata in anonymous maps 2016-06-02 14:28:32 +02:00
Raul Ochoa
828b817aca Append widgets metadata from mapconfig 2016-06-02 14:14:11 +02:00
Raul Ochoa
7f26f01743 Upgrade windshaft to 2.0.1 2016-06-02 13:38:05 +02:00
Raul Ochoa
50da63fc63 Upgrades windshaft to 2.0.0 2016-06-02 13:02:50 +02:00
Raul Ochoa
f8f6508449 Merge branch 'master' into mapconfig-dataviews-adapter
Conflicts:
	NEWS.md
	npm-shrinkwrap.json
2016-06-02 10:57:43 +02:00
Raul Ochoa
7256eb0935 Upgrade camshaft to 0.12.1 2016-06-02 10:54:23 +02:00
Raul Ochoa
cb08b42e54 Merge pull request #486 from CartoDB/upgrade-turbo-carto
Upgrades turbo-carto to 0.10.1
2016-06-01 19:43:58 +02:00
Raul Ochoa
9e7caeff94 Upgrades turbo-carto to 0.10.1 2016-06-01 19:32:17 +02:00
Raul Ochoa
e72a1d73be Geojson + column selection tests 2016-06-01 19:06:01 +02:00
Raul Ochoa
aaacad81e7 Add bbox unit tests 2016-06-01 19:05:46 +02:00
Raul Ochoa
55ee5b3b01 Ported histogram tests from windshaft 2016-06-01 15:03:18 +02:00
Raul Ochoa
94bf2748be Ignore errors coming from overviews adapter 2016-06-01 15:03:02 +02:00
Raul Ochoa
9a4aa7c1fa Add params to url if present in override option 2016-06-01 15:00:30 +02:00
Raul Ochoa
3e71365a95 Update camshaft to 0.12.0 2016-06-01 15:00:00 +02:00
Raul Ochoa
018ffcea7c List widget tests ported from windshaft 2016-06-01 11:51:31 +02:00
Raul Ochoa
e24ba9f495 Ported formula widget tests from windshaft 2016-06-01 11:48:37 +02:00
Raul Ochoa
0e2e069503 Remove empty line 2016-06-01 11:48:28 +02:00
Raul Ochoa
c4bbff3802 Tests for aggregation dataview ported from windshaft 2016-06-01 11:44:24 +02:00
Raul Ochoa
290054ef5d Add widget search support in test client 2016-06-01 11:43:19 +02:00
Raul Ochoa
4c25828540 Fix sql signature in agg, formula, and list dataviews 2016-06-01 11:42:24 +02:00
Javier Goizueta
5eda4888ed Stub next version 2016-06-01 10:51:36 +02:00
Javier Goizueta
7c322d9411 Release 2.44.1 2016-06-01 10:47:46 +02:00
Javier Goizueta
bd35d4e78a Merge pull request #468 from CartoDB/466-overviews-dataviews
Implement overviews support for all dataview types
2016-06-01 10:35:07 +02:00
csobier
7d623faf4b reverted rebranded coe, as instructed. Legacy cartodb code appears instead. Also applied ALL CAPS for rebranded name, as instructed 2016-05-31 13:40:41 -04:00
Raul Ochoa
6eb711e70b Merge remote-tracking branch 'origin/master' into mapconfig-dataviews-adapter 2016-05-31 18:51:13 +02:00
Raul Ochoa
81ff0152c0 Merge pull request #481 from CartoDB/plpython-query-statements
Use plpython version of CDB_QueryStatements
2016-05-31 18:50:39 +02:00
Raul Ochoa
8a07f9f57e Create plpythonu extension 2016-05-31 18:45:43 +02:00
Raul Ochoa
ca367d0fe7 Use plpython version of CDB_QueryStatements 2016-05-31 18:39:03 +02:00
Raul Ochoa
cd7adbd792 Return a dataview/widget from response body 2016-05-31 18:20:16 +02:00
Raul Ochoa
5b76ec9f68 Merge remote-tracking branch 'origin/master' into mapconfig-dataviews-adapter 2016-05-31 17:14:28 +02:00
Raul Ochoa
bb21270aab Merge pull request #479 from CartoDB/improve-prepare_db
Improve prepare db
2016-05-31 17:14:11 +02:00
Raul Ochoa
22f3a54fbf Option to skip sql files download 2016-05-31 16:57:28 +02:00
Raul Ochoa
6644711969 Use a variable instead of a tmp file 2016-05-31 16:46:57 +02:00
Raul Ochoa
989df4a8a4 curl over all remote files at the same time 2016-05-31 16:42:42 +02:00
Javier Goizueta
d5423c88ea Replace use of the name widget by dataview for consistency 2016-05-31 15:30:38 +02:00
Javier Goizueta
5838b7a455 Remove debugging messages 2016-05-31 15:19:33 +02:00
Raul Ochoa
86e8cedfab All remote sql files together 2016-05-31 15:17:41 +02:00
Raul Ochoa
93c31c5433 Stubs next version 2016-05-31 10:35:25 +02:00
Raul Ochoa
4ca8fddd50 Release 2.44.0 2016-05-31 10:29:15 +02:00
Raul Ochoa
8cc46fd2a3 Correct versions for updated packages 2016-05-31 10:08:06 +02:00
Raul Ochoa
b2d8f53a5c Merge branch 'master' into mapconfig-dataviews-adapter 2016-05-31 09:41:22 +02:00
Raul Ochoa
8e8e59addc Merge pull request #477 from CartoDB/update-camshaft
Upgrades camshaft to 0.11.0
2016-05-30 21:29:00 +02:00
Raul Ochoa
63e52878a1 Upgrades camshaft to 0.11.0 2016-05-30 19:23:56 +02:00
Javier Goizueta
ef276bd51e Merge branch 'master' into 466-overviews-dataviews 2016-05-30 17:26:06 +02:00
Javier Goizueta
7ac3784f32 Increase the ratio used to select an overview level from a bounding box
This value would ideally be adjusted to prevent the grid size of the
overview used being greater that one pixel. So, this should be the
larger dimension of the map window in pixels.
2016-05-30 17:21:56 +02:00
Raul Ochoa
e12133e24b Merge remote-tracking branch 'origin/master' into mapconfig-dataviews-adapter 2016-05-27 15:35:29 +02:00
Raul Ochoa
be01781373 Merge pull request #475 from CartoDB/update-camshaft
Upgrades camshaft to 0.10.0
2016-05-27 15:33:16 +02:00
Raul Ochoa
65523768f9 Upgrades camshaft to 0.10.0 2016-05-27 15:26:52 +02:00
Raul Ochoa
f602ea88e2 Convert widgets from layers into dataviews
It also converts filters so full dataviews backend is reusable, that removes
widgets backend dependency.
2016-05-26 19:32:58 +02:00
csobier
0f2401b0cc docs 781 applied to windshaft-api docs 2016-05-26 10:22:52 -04:00
Raul Ochoa
da6870cf1e Adds new adapter to transform widgets into dataviews 2016-05-26 11:57:55 +02:00
Raul Ochoa
06e420aa70 Merge pull request #473 from CartoDB/analyses-mapconfig-extensions
Tests for generic MapConfig adapter
2016-05-26 11:45:17 +02:00
Raul Ochoa
c667e64d7f Simplify test as we just validate val value 2016-05-26 11:36:03 +02:00
Raul Ochoa
5c3dd8b09d validate execution order 2016-05-26 11:30:28 +02:00
Raul Ochoa
f7c528277b Add tests for generic MapConfig adapter 2016-05-26 11:23:19 +02:00
Raul Ochoa
2ff33b5010 Generic MapConfig adapter can receive an arbitrary number of adapters 2016-05-26 11:02:43 +02:00
Raul Ochoa
d2f4e3ee74 Merge pull request #472 from CartoDB/analyses-mapconfig-extensions
Analyses mapconfig extensions
2016-05-25 18:51:09 +02:00
Raul Ochoa
730486b27b Initial description of dataviews mapconfig extension 2016-05-25 18:36:59 +02:00
Raul Ochoa
c4b4a93a0d Add map config extension for analyses 2016-05-25 17:53:09 +02:00
Raul Ochoa
f34213a147 Reorder public/private functions 2016-05-25 17:24:28 +02:00
Raul Ochoa
862f8b4ce6 Merge pull request #471 from CartoDB/mapconfig-fix-adapters-order
Order of adapters: named maps should expand named layers as first step
2016-05-25 13:50:34 +02:00
Raul Ochoa
5a2afa9b89 Order of adapters: named maps should expand named layers as first step 2016-05-24 19:16:38 +02:00
Raul Ochoa
4759d178d3 No vars for adapters 2016-05-24 18:43:09 +02:00
Raul Ochoa
777fb78abc Merge pull request #469 from CartoDB/mapconfig-reorg
Unify getMapConfig signature in adapters
2016-05-24 18:32:47 +02:00
Raul Ochoa
faa24caf5b Use generic map config adapter 2016-05-23 23:35:42 +02:00
Raul Ochoa
5e6529363b Remove unused var 2016-05-23 23:29:41 +02:00
Raul Ochoa
a785ebef65 Use generic map config adapter 2016-05-23 23:29:06 +02:00
Raul Ochoa
4137de5adf Remove class members 2016-05-23 22:01:08 +02:00
Raul Ochoa
f012e6092f Remove unused var 2016-05-23 21:58:42 +02:00
Raul Ochoa
9ce4929d87 Use generic adapter in named maps 2016-05-23 21:56:38 +02:00
Raul Ochoa
8efe844474 Use generic adapter 2016-05-23 21:37:06 +02:00
Raul Ochoa
02cb80daa1 Use context for datasource 2/2 2016-05-23 19:14:03 +02:00
Raul Ochoa
e9d1951d48 Use context for datasource 1/2 2016-05-23 19:09:57 +02:00
Raul Ochoa
a11cc28dc7 Use context for analyses results 2016-05-23 18:59:23 +02:00
Raul Ochoa
a8fdd6726e Fix style 2016-05-23 18:36:03 +02:00
Raul Ochoa
7ad8a99373 Unify getMapConfig signature for overviews adapter 2016-05-23 18:35:16 +02:00
Javier Goizueta
c0a24108ba Implement overviews histogram dataviews 2016-05-23 18:11:10 +02:00
Javier Goizueta
ae9b8a0380 Remove comment 2016-05-23 18:10:46 +02:00
Raul Ochoa
31a0b01a27 Rename param 2016-05-23 18:08:42 +02:00
Raul Ochoa
efcb73e0d1 Named layers adapter with getMapConfig signature 2016-05-23 18:03:45 +02:00
Javier Goizueta
f008c74419 Specific aggregation dataview implementation for overviews 2016-05-23 17:42:26 +02:00
Javier Goizueta
4a646d4700 Refactor overviews formula dataview 2016-05-23 17:20:04 +02:00
Javier Goizueta
657b262d92 Override all dataview types for overviews
All are using now the default behaviour defined in the base
class.
2016-05-23 17:06:52 +02:00
Javier Goizueta
988412fc07 Define default overviews dataview behaviour in base class 2016-05-23 16:53:28 +02:00
Raul Ochoa
70750d2c43 Unify getMapConfig signature 2016-05-23 16:50:26 +02:00
Raul Ochoa
9c1db98f67 Unifiy getMapConfig signature 2016-05-23 16:44:14 +02:00
Raul Ochoa
12c44fda6f Unify getMapConfig interface 2016-05-23 16:20:42 +02:00
Raul Ochoa
a42756ba24 Merge pull request #467 from CartoDB/mapconfig-reorg
Adapter.getMapConfig interface
2016-05-23 15:59:55 +02:00
Raul Ochoa
6ccdb6cefd Overviews adapter with getMapConfig interface 2016-05-23 15:52:31 +02:00
Raul Ochoa
9f6ce64a31 Named maps adapter with getMapConfig interface 2016-05-23 15:39:11 +02:00
Raul Ochoa
3e35604df0 turbo-carto adapter with getMapConfig interface 2016-05-23 15:18:20 +02:00
Raul Ochoa
01a69ef15c Merge remote-tracking branch 'origin/master' into mapconfig-reorg 2016-05-23 15:14:25 +02:00
Raul Ochoa
5adbc98c2b Merge pull request #461 from CartoDB/turbo-carto-tokens
SubstitutionTokens based on origin data
2016-05-23 15:13:39 +02:00
Raul Ochoa
fb045f1836 Merge branch 'master' into turbo-carto-tokens 2016-05-23 15:06:55 +02:00
Raul Ochoa
ee49b8b2a2 Turbo-carto adapter into adapters package 2016-05-23 14:18:58 +02:00
Javier Goizueta
5ba72b4894 Create base class for overviews dataviews 2016-05-23 14:18:45 +02:00
Raul Ochoa
6975db6ecf Merge pull request #465 from CartoDB/mapconfig-reorg
Mapconfig providers and adapters re-org
2016-05-23 14:12:47 +02:00
Raul Ochoa
8134aca14d Named map provider into providers package 2016-05-23 13:32:28 +02:00
Raul Ochoa
215bbbd29c Store provider into providers package 2016-05-23 13:29:34 +02:00
Raul Ochoa
c4b6f65404 Create map provider into providers package 2016-05-23 13:28:11 +02:00
Raul Ochoa
69f40e6f6a Removed duplicated declaration 2016-05-23 13:26:34 +02:00
Raul Ochoa
20725900b6 Overviews adapter into adapters package 2016-05-23 13:25:11 +02:00
Raul Ochoa
ab984729f5 Named layers adapter into adapters package 2016-05-23 13:16:34 +02:00
Raul Ochoa
8553326c1b Merge remote-tracking branch 'origin/master' into mapconfig-reorg 2016-05-23 13:11:14 +02:00
Raul Ochoa
9f8551058d Analysis adapter into adapter package 2016-05-23 13:10:52 +02:00
Raul Ochoa
5895871fad Make tests running before checkstyle/lint 2016-05-23 13:03:56 +02:00
Raul Ochoa
c372d69e98 LayergroupToken only makes sense at testing environment 2016-05-23 13:01:23 +02:00
Javier Goizueta
bacaee138a Merge pull request #463 from CartoDB/camshaft-getfilters
Use Camshaft's API to get node filters
2016-05-19 19:19:02 +02:00
Javier Goizueta
3add61ec57 Use Camshaft's API to get node filters 2016-05-19 18:32:49 +02:00
Raul Ochoa
02ae50eef0 Merge pull request #462 from CartoDB/turbo-carto-category
Adds turbo-carto category quantification with exact strategy
2016-05-19 17:11:39 +02:00
Raul Ochoa
b308259e6f Merge branch 'master' into turbo-carto-category
Conflicts:
	lib/cartodb/utils/style/postgres-datasource.js
2016-05-19 16:58:31 +02:00
Raul Ochoa
14a0afc7c0 Merge branch 'master' into turbo-carto-tokens 2016-05-19 16:56:00 +02:00
Raul Ochoa
d74daf39c7 Upgrades turbo-carto to 0.10.0 2016-05-19 16:54:56 +02:00
Raul Ochoa
eb091caf4a Merge pull request #460 from CartoDB/turbo-carto-invalid-method
Fail on turbo-carto invalid quantification methods
2016-05-19 16:11:05 +02:00
Raul Ochoa
424cc6d93b Fail on turbo-carto invalid quantification methods 2016-05-19 15:54:58 +02:00
Raul Ochoa
3bacfecc49 Merge branch 'master' into turbo-carto-category 2016-05-19 13:43:35 +02:00
Raul Ochoa
64dd033c94 Merge branch 'master' into turbo-carto-tokens 2016-05-19 13:39:36 +02:00
Raul Ochoa
caec04f63b Merge pull request #459 from CartoDB/sql-wrap-adapter
Adds support for sql wrap in all layers
2016-05-19 13:38:59 +02:00
Raul Ochoa
2e79781711 Adds support for sql wrap in all layers
Previously it was only working for analyses ones.
2016-05-19 13:34:29 +02:00
Javier Goizueta
289ffbbedc Release 2.43.1 2016-05-19 12:33:41 +02:00
Javier Goizueta
e0f0751b28 Merge pull request #458 from CartoDB/457-bbox-queryrewrite
Fix dataview bbox bug when no query rewrite data exists
2016-05-19 12:29:17 +02:00
Raul Ochoa
f30be00eb9 Remove console 2016-05-19 12:14:46 +02:00
Raul Ochoa
ee94b8a587 Very raw implementation of SubstitutionTokens based on origin data 2016-05-19 12:13:49 +02:00
Raul Ochoa
fd3f928d81 Fix test table 2016-05-19 12:13:37 +02:00
Raul Ochoa
ba08745c23 Adds hasTokens method to SubstitutionTokens 2016-05-19 12:10:19 +02:00
Raul Ochoa
573932efba Simplify condition and use positive naming for parsing cartocss 2016-05-19 11:48:57 +02:00
Raul Ochoa
31344a1c75 Adds test case with analysis 2016-05-19 11:42:28 +02:00
Raul Ochoa
c7f37047b0 Save original query from analysis before wrapping it 2016-05-19 11:41:06 +02:00
Javier Goizueta
2a06405a58 Move definition to the scope where it's needed 2016-05-18 18:21:17 +02:00
Javier Goizueta
9206b1a1b5 Fix dataviews/overviews tests and add some new cases 2016-05-18 18:16:32 +02:00
Javier Goizueta
5989ab344d Add test to detect problem #457 2016-05-18 18:02:08 +02:00
Javier Goizueta
a1e024e228 Fix dataview problem for bbox with no query rewrite data
Fixes #457
2016-05-18 17:49:09 +02:00
Javier Goizueta
8628d3b671 Stub next version 2016-05-18 16:15:27 +02:00
Javier Goizueta
7ce4104d2f Release version 2.43.0 2016-05-18 16:11:30 +02:00
Javier Goizueta
a26a5d6f5a Merge pull request #449 from CartoDB/overviews-widgets-2
Use overviews for widgets (dataviews, filtered queries)
2016-05-18 16:08:17 +02:00
Javier Goizueta
e98a5aeff0 Small code clean-up 2016-05-18 15:48:30 +02:00
Javier Goizueta
4c375780c7 replace underscore functions by standard (ES5) equivalents
Note: _.find(a,...) is not replaced by a.find(...)
because it is not available for all the collections
we need it for.
2016-05-18 15:43:20 +02:00
Javier Goizueta
48415fb1f3 Merge branch 'master' into overviews-widgets-2 2016-05-18 13:58:55 +02:00
Javier Goizueta
8da7cf73c1 Remove comment 2016-05-18 13:55:09 +02:00
Javier Goizueta
ba30f460ee Remove comment
Overviews will not be used for dataview search
2016-05-18 13:42:58 +02:00
Javier Goizueta
e1aa0bc7ae Use JSON format for EXPLAIN 2016-05-18 13:09:55 +02:00
Javier Goizueta
3987e83b7a Add tests for query rewriter with filters 2016-05-18 12:34:51 +02:00
Javier Goizueta
858d976637 Add tests for query rewriter using specific zoom level 2016-05-18 11:53:30 +02:00
Javier Goizueta
48d2978997 Test filters query rewrite data 2016-05-18 11:45:14 +02:00
Javier Goizueta
1872fbd021 Add test cases for dataview formulae
Check the overriden (sum,avg,count) and non-overriden (min, max) cases.
2016-05-18 10:48:13 +02:00
Javier Goizueta
bbb1b4a7b9 Add tests missing file 2016-05-18 08:11:52 +02:00
Javier Goizueta
aa0ddaae95 Remove comment 2016-05-18 08:07:48 +02:00
Javier Goizueta
cb3706e5cf Update Query Rewriter comments 2016-05-18 08:04:11 +02:00
Javier Goizueta
3d8f6576aa Implement category and range filters 2016-05-18 07:48:11 +02:00
Javier Goizueta
24f7bc6596 Add tests for dataviews with overviews 2016-05-18 07:47:30 +02:00
Raul Ochoa
a1934c87d5 Adds turbo-carto category quantification with exact strategy 2016-05-17 19:45:37 +02:00
Javier Goizueta
7a6b1ec871 Fix tests for MapConfigOverviewsAdaptar changes 2016-05-17 16:01:10 +02:00
Raul Ochoa
cfdac1bcb0 Stubs next version 2016-05-17 15:46:21 +02:00
Javier Goizueta
42ef40282b 💄 shorten long lines 2016-05-17 15:46:13 +02:00
Javier Goizueta
7f7204df6c Add filter stats information to query rewriter data 2016-05-17 15:41:31 +02:00
Javier Goizueta
3c6d930434 Fix bug 2016-05-17 15:39:32 +02:00
Javier Goizueta
df63fbbd04 Refactor filter application into own model
This also avoids storing an object in the overviews query rewriter
for the bbox filter (a plain data structure is used instead).
2016-05-17 13:55:00 +02:00
Javier Goizueta
9d82e8c27c Use bounding box of dataviews to select overviews level 2016-05-13 20:47:36 +02:00
Javier Goizueta
224eb392ba Add overviews-dependent dataviews behaviour
Now QueryRewriter is used in dataview objects they can decide
whether overviews are applicable, have the oportunity to
adapt queries for overviews, etc.
This is done by having overviews-related behaviour in models/dataview/overviews
and falling back to the regular models/dataview.
2016-05-13 18:46:58 +02:00
Javier Goizueta
b574489950 Refactor to reduce cyclomatic complexity 2016-05-12 18:47:24 +02:00
Javier Goizueta
85788f42a6 Adapt QueryRewriter to new requirements 2016-05-12 18:30:10 +02:00
Javier Goizueta
5fb7f07498 Prevent problems with missing layers in mapconfig 2016-05-12 18:29:30 +02:00
Javier Goizueta
fd44b62f26 Fix tests for new MapConfigOverviewsAdapter interface 2016-05-12 17:52:39 +02:00
Javier Goizueta
3300c095ed Merge branch 'master' into overviews-widgets-2 2016-05-12 17:37:24 +02:00
Javier Goizueta
55cf0a8447 Fix typo 2016-05-12 16:43:09 +02:00
Javier Goizueta
64a87690ee 💄 Fix line lengths, etc. 2016-05-12 16:20:34 +02:00
Javier Goizueta
3890014250 Fix QueryRewriter use
QueryRewriter should be passed the query that would be used otherwise.
If QR cannot handle it, it will be returned unmodified.
So QR must be used when a query has been prepared and the result
of QR should be used to replace it.
2016-05-12 10:25:09 +02:00
Javier Goizueta
65612f0109 Add filters information at map instantion time to the query rewriter data 2016-05-11 19:24:13 +02:00
Javier Goizueta
fa19f90a6a Apply overviews query rewriter to dataviews
This requires the QueryRewriter to handle a filters parametes in
its data (with Camshaft filter definitions) and a final
options parameters with a bounding_box parameter.
2016-05-11 18:18:22 +02:00
152 changed files with 11607 additions and 5773 deletions

2
.gitignore vendored
View File

@@ -2,6 +2,8 @@ node_modules*
config.status*
config/environments/*.js
.idea
.vscode
.nvmrc
tools/munin/windshaft.conf
logs/
pids/

View File

@@ -1,29 +1,27 @@
sudo: false
dist: trusty
addons:
postgresql: "9.3"
postgresql: "9.5"
apt:
sources:
- ubuntu-toolchain-r-test
packages:
- postgresql-9.5-postgis-2.3
- postgresql-plpython-9.5
- pkg-config
- libcairo2-dev
- libjpeg8-dev
- libgif-dev
- libpango1.0-dev
- g++-4.9
before_install:
- npm install -g npm@2
- createdb template_postgis
- createuser publicuser
- psql -c "CREATE EXTENSION postgis" template_postgis
env:
- NPROCS=1 JOBS=1 PGUSER=postgres
- NPROCS=1 JOBS=1 PGUSER=postgres CXX=g++-4.9
language: node_js
node_js:
- "0.10"
notifications:
irc:
channels:
- "irc.freenode.org#cartodb"
use_notice: true
- "6"

View File

@@ -8,4 +8,4 @@ We love pull requests from everyone, see [Contributing to Open Source on GitHub]
## Submitting Contributions
* You will need to sign a Contributor License Agreement (CLA) before making a submission. [Learn more here](https://cartodb.com/contributing).
* You will need to sign a Contributor License Agreement (CLA) before making a submission. [Learn more here](https://carto.com/contributions).

View File

@@ -1,11 +1,10 @@
1. Test (make clean all check), fix if broken before proceeding
2. Ensure proper version in package.json
2. Ensure proper version in package.json
3. Ensure NEWS section exists for the new version, review it, add release date
4. Recreate npm-shrinkwrap.json with: `npm install --no-shrinkwrap && npm shrinkwrap`
4. Recreate npm-shrinkwrap.json with: `make shrinkwrap`
5. Commit package.json, npm-shrinwrap.json, NEWS
6. git tag -a Major.Minor.Patch # use NEWS section as content
7. Announce on cartodb@googlegroups.com
8. Stub NEWS/package for next version
7. Stub NEWS/package for next version
Versions:

View File

@@ -4,11 +4,11 @@
Make sure that you have the requirements needed. These are
- Core
- Node.js >=0.8
- npm >=1.2.1 <2.0.0
- Node.js >=6.9.x
- yarn >=0.21.3
- PostgreSQL >8.3.x, PostGIS >1.5.x
- Redis >2.4.0 (http://www.redis.io)
- Mapnik 2.0.1, 2.0.2, 2.1.0, 2.2.0, 2.3.0. See [Installing Mapnik](https://github.com/CartoDB/Windshaft#installing-mapnik).
- Mapnik >3.x. See [Installing Mapnik](https://github.com/CartoDB/Windshaft#installing-mapnik).
- Windshaft: check [Windshaft dependencies and installation notes](https://github.com/CartoDB/Windshaft#dependencies)
- libcairo2-dev, libpango1.0-dev, libjpeg8-dev and libgif-dev for server side canvas support
@@ -43,11 +43,11 @@ psql -d template_postgis -c 'CREATE EXTENSION postgis;'
To fetch and build all node-based dependencies, run:
```
npm install
yarn
```
Note that the ```npm install``` step will populate the node_modules/
Note that the ```yarn``` step will populate the node_modules/
directory with modules, some of which being compiled on demand. If you
happen to have startup errors you may need to force rebuilding those
modules. At any time just wipe out the node_modules/ directory and run
```npm install``` again.
```yarn``` again.

View File

@@ -7,7 +7,13 @@ all:
@$(SHELL) ./scripts/install.sh
clean:
rm -rf node_modules/*
rm -rf node_modules/
shrinkwrap: clean
rm npm-shrinkwrap.json
npm install --no-shrinkwrap --production
npm prune
npm shrinkwrap
distclean: clean
rm config.status*
@@ -43,7 +49,7 @@ jshint:
@echo "***jshint***"
@./node_modules/.bin/jshint lib/ test/ app.js
test-all: jshint test
test-all: test jshint
coverage:
@RUNTESTFLAGS=--with-coverage make test

744
NEWS.md
View File

@@ -1,5 +1,749 @@
# Changelog
## 3.0.0
Released 2017-03-21
Announcements:
- Supports Node v6.9.x
- Drops support for Node v0.10.x
- Upgrades windshaft to 3.0.0
- Upgrades cartodb-query-tables to 0.2.0
- Upgrades cartodb-redis to 0.13.2
- Upgrades redis-mpool to 0.4.1
**Note**: Due to this [issue](https://github.com/npm/npm/issues/15713), Windshaft-cartodb must be installed with `yarn` instead of `npm` providing just a `yarn.lock` to get consistent installs across machines.
## 2.89.0
Released 2017-03-17
**Deprecation warning**: v2.89.0 is the last release that supports Node v0.10.x. Next mayor release will support Node v6.9.x and further versions.
Announcements:
- Upgrades windshaft to [2.8.0](https://github.com/CartoDB/windshaft/releases/tag/2.8.0).
Bug fixes:
- Histogram column type discovery query uses non-filtered query #637
## 2.88.4
Released 2017-03-10
Announcements:
- Upgrades camshaft to [0.50.3](https://github.com/CartoDB/camshaft/releases/tag/0.50.3).
## 2.88.3
Released 2017-03-02
Bug fixes:
- Category dataviews now uses the proper aggregation function for the 'Other' category. See https://github.com/CartoDB/Windshaft-cartodb/issues/628
## 2.88.2
Released 2017-02-23
Announcements:
- Upgrades camshaft to [0.50.2](https://github.com/CartoDB/camshaft/releases/tag/0.50.2).
## 2.88.1
Released 2017-02-21
Announcements:
- Upgrades camshaft to [0.50.1](https://github.com/CartoDB/camshaft/releases/tag/0.50.1)
## 2.88.0
Released 2017-02-21
Announcements:
- Upgrades camshaft to [0.50.0](https://github.com/CartoDB/camshaft/releases/tag/0.50.0).
- Upgrades cartodb-psql to [0.7.1](https://github.com/CartoDB/node-cartodb-psql/releases/tag/0.7.1).
- Upgrades windshaft to [2.7.0](https://github.com/CartoDB/windshaft/releases/tag/2.7.0).
## 2.87.5
Released 2017-02-02
Bug fixes:
- Cast dataview override values to Number or throw error.
## 2.87.4
Released 2017-01-20
Bug fixes:
- Be able to not compute NULL categories and null values in aggregation dataviews #617.
## 2.87.3
Released 2016-12-19
Bug fixes:
- Fix overviews-related dataviews problems. See https://github.com/CartoDB/Windshaft-cartodb/pull/604
## 2.87.2
Released 2016-12-19
- Use exception safe Dataservices API functions. See https://github.com/CartoDB/dataservices-api/issues/314 and https://github.com/CartoDB/camshaft/issues/242
## 2.87.1
Released 2016-12-13
Announcements:
- Upgrades windshaft to [2.6.4](https://github.com/CartoDB/Windshaft/releases/tag/2.6.4).
- Upgrades request dependency.
- Regenerate npm-shrinkwrap.json: missing dependency updates.
## 2.87.0
Released 2016-12-12
Enhancements:
- Upgrade turbo-carto dependency to version 0.19.0.
## 2.86.1
Released 2016-12-02
Bug fixes:
- Maps with analyses and `sql_wrap` were broken #599.
## 2.86.0
Released 2016-12-02
Announcements:
- Upgrades windshaft to [2.6.3](https://github.com/CartoDB/Windshaft/releases/tag/2.6.3).
## 2.85.1
Released 2016-11-30
Announcements:
- Upgrades camshaft to [0.48.4](https://github.com/CartoDB/camshaft/releases/tag/0.48.4).
## 2.85.0
Released 2016-11-24
New features:
- Allow to set resource URL templates with substitution tokens #594.
## 2.84.2
Released 2016-11-23
Announcements:
- Upgrades camshaft to [0.48.3](https://github.com/CartoDB/camshaft/releases/tag/0.48.3).
## 2.84.1
Released 2016-11-23
Announcements:
- Upgrades camshaft to [0.48.2](https://github.com/CartoDB/camshaft/releases/tag/0.48.2).
## 2.84.0
Released 2016-11-11
New features:
- Analyses limit configuration allows to set other limits than timeout.
## 2.83.1
Released 2016-11-10
Announcements:
- Upgrades camshaft to [0.48.1](https://github.com/CartoDB/camshaft/releases/tag/0.48.1).
## 2.83.0
Released 2016-11-10
Announcements:
- Upgrades camshaft to [0.48.0](https://github.com/CartoDB/camshaft/releases/tag/0.48.0).
## 2.82.0
Released 2016-11-08
Announcements:
- Upgrades camshaft to [0.47.0](https://github.com/CartoDB/camshaft/releases/tag/0.47.0).
## 2.81.1
Released 2016-11-05
Announcements:
- Upgrades windshaft to [2.6.2](https://github.com/CartoDB/windshaft/releases/tag/2.6.2).
- Upgrades camshaft to [0.46.3](https://github.com/CartoDB/camshaft/releases/tag/0.46.3).
## 2.81.0
Released 2016-11-02
Enhancements:
- Returns errors with context when query layer does not retrieve geometry column
Announcements:
- Upgrades windshaft to [2.6.1](https://github.com/CartoDB/windshaft/releases/tag/2.6.1).
- Upgrades camshaft to [0.46.2](https://github.com/CartoDB/camshaft/releases/tag/0.46.2).
## 2.80.2
Released 2016-10-26
Bug fixes:
- Fix order in categories query to get ramps
## 2.80.1
Released 2016-10-25
Announcements:
- Upgrades camshaft to [0.46.1](https://github.com/CartoDB/camshaft/releases/tag/0.46.1).
## 2.80.0
Released 2016-10-20
Announcements:
- Upgrades camshaft to [0.46.0](https://github.com/CartoDB/camshaft/releases/tag/0.46.0).
New features:
- Default analyses limits can be defined in configuration.
## 2.79.0
Released 2016-10-11
New features:
- Retrieve analysis limits and pass them into camshaft.
Announcements:
- Upgrades turbo-carto to [0.18.0](https://github.com/CartoDB/turbo-carto/releases/tag/0.18.0).
- Upgrades camshaft to [0.45.0](https://github.com/CartoDB/camshaft/releases/tag/0.45.0).
## 2.78.1
Released 2016-09-30
Announcements:
- Upgrades camshaft to [0.44.2](https://github.com/CartoDB/camshaft/releases/tag/0.44.2).
## 2.78.0
Released 2016-09-29
New features:
- Add metadata about processed turbo-carto rules.
Announcements:
- Upgrades turbo-carto to [0.17.1](https://github.com/CartoDB/turbo-carto/releases/tag/0.17.1).
## 2.77.1
Released 2016-09-28
Announcements:
- Upgrades camshaft to [0.44.1](https://github.com/CartoDB/camshaft/releases/tag/0.44.1).
## 2.77.0
Released 2016-09-26
Announcements:
- Upgrades camshaft to [0.44.0](https://github.com/CartoDB/camshaft/releases/tag/0.44.0).
- Adds a new configuration for camshaft: logger stream.
## 2.76.0
Released 2016-09-15
New features:
- Allow to use `--config /path/to/config.js` to specify configuration file.
- Environment will be loaded from config file if `environment` key is present, otherwise it keeps current behaviour.
Bug fixes:
- Allow to use absolute paths for log files #570.
## 2.75.0
Released 2016-09-14
Announcements:
- Upgrades camshaft to [0.43.0](https://github.com/CartoDB/camshaft/releases/tag/0.43.0).
## 2.74.1
Released 2016-09-07
Announcements:
- Upgrades camshaft to [0.42.1](https://github.com/CartoDB/camshaft/releases/tag/0.42.1).
## 2.74.0
Released 2016-09-06
Enhancements:
- Layers in previews can be shown or hidden using `preview_layers` property in template map
## 2.73.1
Released 2016-09-06
Bug fixes:
- Fixes missing column in fixture table `cdb_analysis_catalog`.
## 2.73.0
Released 2016-09-06
Announcements:
- Upgrades camshaft to [0.42.0](https://github.com/CartoDB/camshaft/releases/tag/0.42.0).
## 2.72.0
Released 2016-08-23
Announcements:
- Upgrades camshaft to [0.41.0](https://github.com/CartoDB/camshaft/releases/tag/0.41.0).
## 2.71.0
Released 2016-08-17
Announcements:
- Upgrades windshaft to [2.5.0](https://github.com/CartoDB/windshaft/releases/tag/2.5.0).
## 2.70.0
Released 2016-08-16
Announcements:
- Upgrades camshaft to [0.40.0](https://github.com/CartoDB/camshaft/releases/tag/0.40.0).
## 2.69.1
Released 2016-08-12
Announcements:
- Upgrades windshaft to [2.4.2](https://github.com/CartoDB/windshaft/releases/tag/2.4.2).
## 2.69.0
Released 2016-08-11
Announcements:
- Upgrades camshaft to [0.39.0](https://github.com/CartoDB/camshaft/releases/tag/0.39.0).
## 2.68.0
Released 2016-07-21
Announcements:
- Upgrades turbo-carto to [0.16.0](https://github.com/CartoDB/turbo-carto/releases/tag/0.16.0).
## 2.67.1
Released 2016-07-21
Announcements:
- Upgrades camshaft to [0.38.1](https://github.com/CartoDB/camshaft/releases/tag/0.38.1).
## 2.67.0
Released 2016-07-21
Announcements:
- Upgrades camshaft to [0.38.0](https://github.com/CartoDB/camshaft/releases/tag/0.38.0).
## 2.66.2
Released 2016-07-20
Announcements:
- Upgrades turbo-carto to [0.15.1](https://github.com/CartoDB/turbo-carto/releases/tag/0.15.1).
## 2.66.1
Released 2016-07-20
Announcements:
- Upgrades turbo-carto to [0.15.0](https://github.com/CartoDB/turbo-carto/releases/tag/0.15.0).
## 2.66.0
Released 2016-07-18
Announcements:
- Available new endpoint to check user analyses.
- Upgrades camshaft to [0.37.1](https://github.com/CartoDB/camshaft/releases/tag/0.37.1).
## 2.65.0
Released 2016-07-15
Announcements:
- Upgrades cartodb-redis to 0.13.1.
- Upgrades camshaft to [0.37.0](https://github.com/CartoDB/camshaft/releases/tag/0.37.0).
## 2.64.0
Released 2016-07-12
Announcements:
- Upgrades camshaft to [0.36.0](https://github.com/CartoDB/camshaft/releases/tag/0.36.0).
## 2.63.0
Released 2016-07-11
Enhancements:
- Return last error message for failed nodes on map creation.
Announcements:
- Upgrades camshaft to [0.35.0](https://github.com/CartoDB/camshaft/releases/tag/0.35.0).
- Upgrades lzma to 2.3.2.
## 2.62.0
Released 2016-07-07
Announcements:
- Upgrades camshaft to [0.34.0](https://github.com/CartoDB/camshaft/releases/tag/0.34.0).
## 2.61.2
Released 2016-07-07
Announcements:
- Limit analysis creation concurrency.
- Upgrades camshaft to [0.33.3](https://github.com/CartoDB/camshaft/releases/tag/0.33.3).
## 2.61.1
Released 2016-07-06
Enhancements:
- Dataviews use mapconfig to store/retrieve their queries instead of instantiating analyses again.
## 2.61.0
Released 2016-07-06
Enhancements:
- More clear turbo-carto error messages: no context in message.
- Return multiple turbo-carto errors #541.
Announcements:
- Upgrades turbo-carto to [0.14.0](https://github.com/CartoDB/turbo-carto/releases/tag/0.14.0).
- Upgrades camshaft to [0.33.2](https://github.com/CartoDB/camshaft/releases/tag/0.33.2).
## 2.60.0
Released 2016-07-05
Announcements:
- Upgrades camshaft to [0.32.0](https://github.com/CartoDB/camshaft/releases/tag/0.32.0).
## 2.59.1
Released 2016-07-05
Announcements:
- Upgrades camshaft to [0.31.0](https://github.com/CartoDB/camshaft/releases/tag/0.31.0).
## 2.59.0
Released 2016-07-05
Announcements:
- Upgrades camshaft to [0.30.0](https://github.com/CartoDB/camshaft/releases/tag/0.30.0).
## 2.58.0
Released 2016-07-05
Announcements:
- Upgrades camshaft to [0.29.2](https://github.com/CartoDB/camshaft/releases/tag/0.29.2).
Bug fixes:
- Return full list of nodes in response metadata.
## 2.57.0
Released 2016-07-04
Announcements:
- Upgrades camshaft to [0.28.1](https://github.com/CartoDB/camshaft/releases/tag/0.28.1).
## 2.56.0
Released 2016-07-04
Announcements:
- Upgrades camshaft to [0.27.0](https://github.com/CartoDB/camshaft/releases/tag/0.27.0).
## 2.55.0
Released 2016-07-04
Enhancements:
- Skip null values for quantification methods generating null values.
Announcements:
- Uses new configuration for camshaft: analysis node has an associated user/owner.
- Upgrades camshaft to [0.26.0](https://github.com/CartoDB/camshaft/releases/tag/0.26.0).
## 2.54.0
Released 2016-06-30
Improvements:
- Errors with context: replaced `turbo-carto` error type by `layer` type.
Announcements:
- Upgrades camshaft to [0.23.0](https://github.com/CartoDB/camshaft/releases/tag/0.23.0)
## 2.53.5
Released 2016-06-29
Bug fixes:
- Uses node list so identical nodes are not de-duplicated and can be used with different ids #528.
## 2.53.4
Released 2016-06-28
Announcements:
- Upgrades camshaft to [0.22.4](https://github.com/CartoDB/camshaft/releases/tag/0.22.4)
## 2.53.3
Released 2016-06-28
Announcements:
- Upgrades camshaft to [0.22.3](https://github.com/CartoDB/camshaft/releases/tag/0.22.3)
## 2.53.2
Released 2016-06-28
Announcements:
- Upgrades camshaft to [0.22.2](https://github.com/CartoDB/camshaft/releases/tag/0.22.2)
## 2.53.1
Released 2016-06-28
Announcements:
- Upgrades camshaft to [0.22.1](https://github.com/CartoDB/camshaft/releases/tag/0.22.1)
## 2.53.0
Released 2016-06-24
Announcements:
- Upgrades camshaft to [0.22.0](https://github.com/CartoDB/camshaft/releases/tag/0.22.0)
## 2.52.0
Released 2016-06-23
Announcements:
- Upgrades camshaft to [0.21.0](https://github.com/CartoDB/camshaft/releases/tag/0.21.0)
## 2.51.0
Released 2016-06-21
Enhancements:
- Split turbo-carto adapter substitutions tokens query.
- Now errors with context have the same schema. #519
- Responses with error now return the layer-id to give more info to the user.
Announcements:
- Upgrades camshaft to [0.20.0](https://github.com/CartoDB/camshaft/releases/tag/0.20.0)
## 2.50.0
Released 2016-06-21
Bug fixes:
- Pixel size query for turbo-carto adapter using radians and degrees instead of meters.
New features:
- Add support for min, max, and avg operations in aggregation dataview #513.
Announcements:
- Upgrades camshaft to [0.19.0](https://github.com/CartoDB/camshaft/releases/tag/0.19.0)
## 2.49.1
Released 2016-06-20
Announcements:
- Upgrades turbo-carto to [0.12.1](https://github.com/CartoDB/turbo-carto/releases/tag/0.12.1).
Bug fixes:
- Use an empty array as default value for falsy ramps #512.
- Use the_geom for intermediate dataviews #511.
- Pick last update time for layergroupid from analyses results #510.
## 2.49.0
Released 2016-06-15
Announcements:
- Upgrades camshaft to [0.17.1](https://github.com/CartoDB/camshaft/releases/tag/0.17.1)
## 2.48.0
Released 2016-06-14
Announcements:
- Upgrades camshaft to [0.15.1](https://github.com/CartoDB/camshaft/releases/tag/0.15.1)
- Responses with more context info if analysis or turbo-carto fails during map creation.
## 2.47.1
Released 2016-06-13
Announcements:
- Upgrades camshaft to [0.14.1](https://github.com/CartoDB/camshaft/releases/tag/0.14.1)
## 2.47.0
Released 2016-06-10
Announcements:
- Upgrades camshaft to [0.14.0](https://github.com/CartoDB/camshaft/releases/tag/0.14.0)
## 2.46.0
Released 2016-06-09
Improvements:
- Support for substitution tokens in geojson tiles
- Warn on application start about non-matching dependencies
Announcements:
- Upgrades windshaft to [2.3.0](https://github.com/CartoDB/camshaft/releases/tag/2.3.0)
- Upgrades camshaft to [0.13.0](https://github.com/CartoDB/camshaft/releases/tag/0.13.0)
- Upgrades turbo-carto to [0.11.0](https://github.com/CartoDB/turbo-carto/releases/tag/0.11.0)
Bug fixes:
- Column provided for geojson renderer should not be null #476
- Dataviews/widgets adapter working with non sql, non source, and non widgets layers
## 2.45.0
Released 2016-06-02
Improvements:
- Removes Windshaft's widgets dependency.
- Makes widgets/dataviews endpoint compatible, but all using dataviews backend instead of widgets from Windshaft.
- Keeps adding widgets metadata in map instantiations for old clients.
Announcements:
- Upgrades windshaft to [2.0.1](https://github.com/CartoDB/camshaft/releases/tag/2.0.1 )
- Upgrades camshaft to [0.12.1](https://github.com/CartoDB/camshaft/releases/tag/0.12.1)
- Upgrades turbo-carto to [0.10.1](https://github.com/CartoDB/turbo-carto/releases/tag/0.10.1)
## 2.44.1
Released 2016-06-01
Improvements:
- Extend overviews support to histogram and aggregation dataviews
- Test improvements
## 2.44.0
Released 2016-05-31
Announcements:
- Upgrades camshaft to [0.11.0](https://github.com/CartoDB/camshaft/releases/tag/0.11.0)
- Upgrades turbo-carto to [0.10.0](https://github.com/CartoDB/turbo-carto/releases/tag/0.10.0)
New features:
- Adds support for sql wrap in all layers
Bug fixes:
- Fail on turbo-carto invalid quantification methods
## 2.43.1
Released 2016-05-19
Bug fixes:
- Dataview error when bbox present without query rewrite data #458
## 2.43.0
Released 2016-05-18
New features:
- Overviews now support dataviews and filtering #449
## 2.42.2
Released 2016-05-17

View File

@@ -32,14 +32,14 @@ Upgrading
Checkout your commit/branch. If you need to reinstall dependencies (you can check [NEWS](NEWS.md)) do the following:
```
rm -rf node_modules; npm install
rm -rf node_modules; yarn
```
Run
---
```
node app.js <env>
node app.js <env>
```
Where <env> is the name of a configuration file under config/environments/.
@@ -71,12 +71,12 @@ See [CONTRIBUTING.md](CONTRIBUTING.md).
### Developing with a custom windshaft version
If you plan or want to use a custom / not released yet version of windshaft (or any other dependency) the best option is
to use `npm link`. You can read more about it at [npm-link: Symlink a package folder](https://docs.npmjs.com/cli/link).
to use `yarn link`. You can read more about it at [yarn-link: Symlink a package folder](https://yarnpkg.com/en/docs/cli/link).
**Quick start**:
```shell
~/windshaft-directory $ npm install
~/windshaft-directory $ npm link
~/windshaft-cartodb-directory $ npm link windshaft
~/windshaft-directory $ yarn
~/windshaft-directory $ yarn link
~/windshaft-cartodb-directory $ yarn link windshaft
```

83
app.js
View File

@@ -5,20 +5,35 @@ var fs = require('fs');
var _ = require('underscore');
var ENVIRONMENT;
if ( process.argv[2] ) {
ENVIRONMENT = process.argv[2];
} else if ( process.env.NODE_ENV ) {
ENVIRONMENT = process.env.NODE_ENV;
} else {
ENVIRONMENT = 'development';
}
// jshint undef:false
var log = console.log.bind(console);
var logError = console.error.bind(console);
// jshint undef:true
var argv = require('yargs')
.usage('Usage: $0 <environment> [options]')
.help('h')
.example(
'$0 production -c /etc/sql-api/config.js',
'start server in production environment with /etc/sql-api/config.js as config file'
)
.alias('h', 'help')
.alias('c', 'config')
.nargs('c', 1)
.describe('c', 'Load configuration from path')
.argv;
var environmentArg = argv._[0] || process.env.NODE_ENV || 'development';
var configurationFile = path.resolve(argv.config || './config/environments/' + environmentArg + '.js');
if (!fs.existsSync(configurationFile)) {
logError('Configuration file "%s" does not exist', configurationFile);
process.exit(1);
}
global.environment = require(configurationFile);
var ENVIRONMENT = argv._[0] || process.env.NODE_ENV || global.environment.environment;
process.env.NODE_ENV = ENVIRONMENT;
var availableEnvironments = {
production: true,
staging: true,
@@ -33,16 +48,6 @@ if (!availableEnvironments[ENVIRONMENT]){
}
process.env.NODE_ENV = ENVIRONMENT;
// set environment specific variables
global.environment = require('./config/environments/' + ENVIRONMENT);
global.log4js = require('log4js');
var log4js_config = {
appenders: [],
replaceConsole: true
};
if (global.environment.uv_threadpool_size) {
process.env.UV_THREADPOOL_SIZE = global.environment.uv_threadpool_size;
}
@@ -58,25 +63,31 @@ var agentOptions = _.defaults(global.environment.httpAgent || {}, {
http.globalAgent = new http.Agent(agentOptions);
https.globalAgent = new https.Agent(agentOptions);
global.log4js = require('log4js');
var log4jsConfig = {
appenders: [],
replaceConsole: true
};
if ( global.environment.log_filename ) {
var logdir = path.dirname(global.environment.log_filename);
// See cwd inlog4js.configure call below
logdir = path.resolve(__dirname, logdir);
if ( ! fs.existsSync(logdir) ) {
logError("Log filename directory does not exist: " + logdir);
process.exit(1);
}
log("Logs will be written to " + global.environment.log_filename);
log4js_config.appenders.push(
{ type: "file", filename: global.environment.log_filename }
);
var logFilename = path.resolve(global.environment.log_filename);
var logDirectory = path.dirname(logFilename);
if (!fs.existsSync(logDirectory)) {
logError("Log filename directory does not exist: " + logDirectory);
process.exit(1);
}
log("Logs will be written to " + logFilename);
log4jsConfig.appenders.push(
{ type: "file", absolute: true, filename: logFilename }
);
} else {
log4js_config.appenders.push(
{ type: "console", layout: { type:'basic' } }
);
log4jsConfig.appenders.push(
{ type: "console", layout: { type:'basic' } }
);
}
global.log4js.configure(log4js_config, { cwd: __dirname });
global.log4js.configure(log4jsConfig);
global.logger = global.log4js.getLogger();
global.environment.api_hostname = require('os').hostname().split('.')[0];
@@ -99,6 +110,8 @@ var listener = server.listen(serverOptions.bind.port, serverOptions.bind.host, b
var version = require("./package").version;
listener.on('listening', function() {
log("Using Node.js %s", process.version);
log('Using configuration file "%s"', configurationFile);
log(
"Windshaft tileserver %s started on %s:%s PID=%d (%s)",
version, serverOptions.bind.host, serverOptions.bind.port, process.pid, ENVIRONMENT
@@ -114,7 +127,7 @@ setInterval(function() {
process.on('SIGHUP', function() {
global.log4js.clearAndShutdownAppenders(function() {
global.log4js.configure(log4js_config);
global.log4js.configure(log4jsConfig);
global.logger = global.log4js.getLogger();
log('Log files reloaded');
});

View File

@@ -23,6 +23,21 @@ var config = {
// "tiles/layergroup" is for compatibility with versions up to 1.6.x
,base_url_detached: '(?:/api/v1/map|/user/:user/api/v1/map|/tiles/layergroup)'
// Resource URLs expose endpoints to request/retrieve metadata associated to Maps: dataviews, analysis node status.
//
// This URLs depend on how `base_url_detached` and `user_from_host` are configured: the application can be
// configured to accept request with the {user} in the header host or in the request path.
// It also might depend on the configured cdn_url via `serverMetadata.cdn_url`.
//
// This template allows to make the endpoints generation more flexible, the template exposes the following params:
// 1. {{=it.cdn_url}}: will be used when `serverMetadata.cdn_url` exists.
// 2. {{=it.user}}: will use the username as extraced from `user_from_host` or `base_url_detached`.
// 3. {{=it.port}}: will use the `port` from this very same configuration file.
,resources_url_templates: {
http: 'http://{{=it.user}}.localhost.lan:{{=it.port}}/api/v1/map',
https: 'http://localhost.lan:{{=it.port}}/user/{{=it.user}}/api/v1/map'
}
// Maximum number of connections for one process
// 128 is a good value with a limit of 1024 open file descriptors
,maxConnections:128
@@ -62,6 +77,7 @@ var config = {
extent: "-180,-90,180,90",
srid: 4326,
*/
// max number of rows to return when querying data, 0 means no limit
row_limit: 65535,
simplify_geometries: true,
use_overviews: true, // use overviews to retrieve raster
@@ -95,6 +111,10 @@ var config = {
// Important: check the configuration of uv_threadpool_size to use suitable value
poolSize: 8,
// Whether grainstore will use a child process or not to transform CartoCSS into Mapnik XML.
// This will prevent blocking the main thread.
useCartocssWorkers: false,
// Metatile is the number of tiles-per-side that are going
// to be rendered at once. If all of them will be requested
// we'd have saved time. If only one will be used, we'd have
@@ -209,6 +229,18 @@ var config = {
endpoint: 'http://127.0.0.1:8080/api/v2/sql/job',
// the template to use for adding the host header in the batch api requests
hostHeaderTemplate: '{{=it.username}}.localhost.lan'
},
logger: {
// If filename is given logs comming from analysis client will be written
// there, in append mode. Otherwise 'log_filename' is used. Otherwise stdout is used (default).
// Log file will be re-opened on receiving the HUP signal
filename: '/tmp/analysis.log'
},
// Define max execution time in ms for analyses or tags
// If analysis or tag are not found in redis this values will be used as default.
limits: {
moran: { timeout: 120000, maxNumberOfRows: 1e5 },
cpu2x: { timeout: 60000 }
}
}
,millstone: {

View File

@@ -23,6 +23,21 @@ var config = {
// "tiles/layergroup" is for compatibility with versions up to 1.6.x
,base_url_detached: '(?:/api/v1/map|/user/:user/api/v1/map|/tiles/layergroup)'
// Resource URLs expose endpoints to request/retrieve metadata associated to Maps: dataviews, analysis node status.
//
// This URLs depend on how `base_url_detached` and `user_from_host` are configured: the application can be
// configured to accept request with the {user} in the header host or in the request path.
// It also might depend on the configured cdn_url via `serverMetadata.cdn_url`.
//
// This template allows to make the endpoints generation more flexible, the template exposes the following params:
// 1. {{=it.cdn_url}}: will be used when `serverMetadata.cdn_url` exists.
// 2. {{=it.user}}: will use the username as extraced from `user_from_host` or `base_url_detached`.
// 3. {{=it.port}}: will use the `port` from this very same configuration file.
,resources_url_templates: {
http: 'http://{{=it.cdn_url}}/{{=it.user}}/api/v1/map',
https: 'https://{{=it.cdn_url}}/{{=it.user}}/api/v1/map'
}
// Maximum number of connections for one process
// 128 is a good value with a limit of 1024 open file descriptors
,maxConnections:128
@@ -56,6 +71,7 @@ var config = {
host: '127.0.0.1',
port: 6432,
extent: "-20037508.3,-20037508.3,20037508.3,20037508.3",
// max number of rows to return when querying data, 0 means no limit
row_limit: 65535,
/*
* Set persist_connection to false if you want
@@ -89,6 +105,10 @@ var config = {
// Important: check the configuration of uv_threadpool_size to use suitable value
poolSize: 8,
// Whether grainstore will use a child process or not to transform CartoCSS into Mapnik XML.
// This will prevent blocking the main thread.
useCartocssWorkers: false,
// Metatile is the number of tiles-per-side that are going
// to be rendered at once. If all of them will be requested
// we'd have saved time. If only one will be used, we'd have
@@ -203,6 +223,18 @@ var config = {
endpoint: 'http://127.0.0.1:8080/api/v2/sql/job',
// the template to use for adding the host header in the batch api requests
hostHeaderTemplate: '{{=it.username}}.localhost.lan'
},
logger: {
// If filename is given logs comming from analysis client will be written
// there, in append mode. Otherwise 'log_filename' is used. Otherwise stdout is used (default).
// Log file will be re-opened on receiving the HUP signal
filename: 'logs/analysis.log'
},
// Define max execution time in ms for analyses or tags
// If analysis or tag are not found in redis this values will be used as default.
limits: {
moran: { timeout: 120000, maxNumberOfRows: 1e5 },
cpu2x: { timeout: 60000 }
}
}
,millstone: {

View File

@@ -23,6 +23,21 @@ var config = {
// "/tiles/layergroup" is for compatibility with versions up to 1.6.x
,base_url_detached: '(?:/api/v1/map|/user/:user/api/v1/map|/tiles/layergroup)'
// Resource URLs expose endpoints to request/retrieve metadata associated to Maps: dataviews, analysis node status.
//
// This URLs depend on how `base_url_detached` and `user_from_host` are configured: the application can be
// configured to accept request with the {user} in the header host or in the request path.
// It also might depend on the configured cdn_url via `serverMetadata.cdn_url`.
//
// This template allows to make the endpoints generation more flexible, the template exposes the following params:
// 1. {{=it.cdn_url}}: will be used when `serverMetadata.cdn_url` exists.
// 2. {{=it.user}}: will use the username as extraced from `user_from_host` or `base_url_detached`.
// 3. {{=it.port}}: will use the `port` from this very same configuration file.
,resources_url_templates: {
http: 'http://{{=it.user}}.localhost.lan:{{=it.port}}/api/v1/map',
https: 'https://{{=it.cdn_url}}/{{=it.user}}/api/v1/map'
}
// Maximum number of connections for one process
// 128 is a good value with a limit of 1024 open file descriptors
,maxConnections:128
@@ -56,6 +71,7 @@ var config = {
host: '127.0.0.1',
port: 6432,
extent: "-20037508.3,-20037508.3,20037508.3,20037508.3",
// max number of rows to return when querying data, 0 means no limit
row_limit: 65535,
simplify_geometries: true,
use_overviews: true, // use overviews to retrieve raster
@@ -89,6 +105,10 @@ var config = {
// Important: check the configuration of uv_threadpool_size to use suitable value
poolSize: 8,
// Whether grainstore will use a child process or not to transform CartoCSS into Mapnik XML.
// This will prevent blocking the main thread.
useCartocssWorkers: false,
// Metatile is the number of tiles-per-side that are going
// to be rendered at once. If all of them will be requested
// we'd have saved time. If only one will be used, we'd have
@@ -203,6 +223,18 @@ var config = {
endpoint: 'http://127.0.0.1:8080/api/v2/sql/job',
// the template to use for adding the host header in the batch api requests
hostHeaderTemplate: '{{=it.username}}.localhost.lan'
},
logger: {
// If filename is given logs comming from analysis client will be written
// there, in append mode. Otherwise 'log_filename' is used. Otherwise stdout is used (default).
// Log file will be re-opened on receiving the HUP signal
filename: 'logs/analysis.log'
},
// Define max execution time in ms for analyses or tags
// If analysis or tag are not found in redis this values will be used as default.
limits: {
moran: { timeout: 120000, maxNumberOfRows: 1e5 },
cpu2x: { timeout: 60000 }
}
}
,millstone: {

View File

@@ -23,6 +23,20 @@ var config = {
// "tiles/layergroup" is for compatibility with versions up to 1.6.x
,base_url_detached: '(?:/api/v1/map|/user/:user/api/v1/map|/tiles/layergroup)'
// Resource URLs expose endpoints to request/retrieve metadata associated to Maps: dataviews, analysis node status.
//
// This URLs depend on how `base_url_detached` and `user_from_host` are configured: the application can be
// configured to accept request with the {user} in the header host or in the request path.
// It also might depend on the configured cdn_url via `serverMetadata.cdn_url`.
//
// This template allows to make the endpoints generation more flexible, the template exposes the following params:
// 1. {{=it.cdn_url}}: will be used when `serverMetadata.cdn_url` exists.
// 2. {{=it.user}}: will use the username as extraced from `user_from_host` or `base_url_detached`.
// 3. {{=it.port}}: will use the `port` from this very same configuration file.
,resources_url_templates: {
http: 'http://{{=it.user}}.localhost.lan:{{=it.port}}/api/v1/map'
}
// Maximum number of connections for one process
// 128 is a good value with a limit of 1024 open file descriptors
,maxConnections:128
@@ -56,6 +70,7 @@ var config = {
host: '127.0.0.1',
port: 5432,
extent: "-20037508.3,-20037508.3,20037508.3,20037508.3",
// max number of rows to return when querying data, 0 means no limit
row_limit: 65535,
simplify_geometries: true,
use_overviews: true, // use overviews to retrieve raster
@@ -89,6 +104,10 @@ var config = {
// Important: check the configuration of uv_threadpool_size to use suitable value
poolSize: 8,
// Whether grainstore will use a child process or not to transform CartoCSS into Mapnik XML.
// This will prevent blocking the main thread.
useCartocssWorkers: false,
// Metatile is the number of tiles-per-side that are going
// to be rendered at once. If all of them will be requested
// we'd have saved time. If only one will be used, we'd have
@@ -204,6 +223,18 @@ var config = {
endpoint: 'http://127.0.0.1:8080/api/v2/sql/job',
// the template to use for adding the host header in the batch api requests
hostHeaderTemplate: '{{=it.username}}.localhost.lan'
},
logger: {
// If filename is given logs comming from analysis client will be written
// there, in append mode. Otherwise 'log_filename' is used. Otherwise stdout is used (default).
// Log file will be re-opened on receiving the HUP signal
filename: 'node-windshaft.log'
},
// Define max execution time in ms for analyses or tags
// If analysis or tag are not found in redis this values will be used as default.
limits: {
moran: { timeout: 120000, maxNumberOfRows: 1e5 },
cpu2x: { timeout: 60000 }
}
}
,millstone: {

View File

@@ -1,11 +1,11 @@
# Maps API
The CartoDB Maps API allows you to generate maps based on data hosted in your CartoDB account and apply custom SQL and CartoCSS to the data. The API generates a XYZ-based URL to fetch Web Mercator projected tiles, using web clients such as [Leaflet](http://leafletjs.com), [Google Maps](https://developers.google.com/maps/), or [OpenLayers](http://openlayers.org/).
The CARTO Maps API allows you to generate maps based on data hosted in your CARTO account and apply custom SQL and CartoCSS to the data. The API generates a XYZ-based URL to fetch Web Mercator projected tiles, using web clients such as [Leaflet](http://leafletjs.com), [Google Maps](https://developers.google.com/maps/), or [OpenLayers](http://openlayers.org/).
You can create two types of maps with the Maps API:
- **Anonymous Maps**
You can create maps using your CartoDB public data. Any client can change the read-only SQL and CartoCSS parameters that generate the map tiles. These maps can be created from a JavaScript application alone and no authenticated calls are needed. See [this CartoDB.js example](/cartodb-platform/cartodb-js/getting-started/).
You can create maps using your CARTO public data. Any client can change the read-only SQL and CartoCSS parameters that generate the map tiles. These maps can be created from a JavaScript application alone and no authenticated calls are needed. See [this CARTO.js example](/carto-engine/carto-js/getting-started/).
- **Named Maps**
There are also maps that have access to your private data. These maps require an owner to setup and modify any SQL and CartoCSS parameters and are not modifiable without new setup calls.

View File

@@ -0,0 +1,93 @@
# 1. Purpose
This specification describes an extension for
[MapConfig 1.4.0](https://github.com/CartoDB/Windshaft/blob/master/doc/MapConfig-1.4.0.md) version.
# 2. Changes over specification
This extension targets layers with `sql` option, including layer types: `cartodb`, `mapnik`, and `torque`.
It extends MapConfig with a new attribute: `analyses`.
## 2.1 Analyses attribute
The new analyses attribute must be an array of analyses as per [camshaft](https://github.com/CartoDB/camshaft). Each
analysis must adhere to the [camshaft-reference](https://github.com/CartoDB/camshaft/blob/0.8.0/reference/versions/0.7.0/reference.json) specification.
Each node can have an id that can be later references to consume the query from MapConfig's layers.
Basic analyses example:
```javascript
[
{
// REQUIRED
// string, `id` free identifier that can be reference from any layer
"id": "HEAD",
// REQUIRED
// string, `type` camshaft's analysis type
"type": "source",
// REQUIRED
// object, `params` will depend on `type`, check camshaft-reference for more information
"params": {
"query": "select * from your_table"
}
}
]
```
# 2.2. Integration with layers
As pointed before an analysis node id can be referenced from layers to consume its output query.
The layer consuming the output must reference it with the following option:
```
{
"options": {
// REQUIRED
// object, `source` as in the future we might want to have other source options
"source": {
// REQUIRED
// string, `id` the analysis node identifier
"id": "HEAD"
}
}
}
```
## 2.3. Complete example
```
{
"version": "1.4.0",
"layers": [
{
"type": "cartodb",
"options": {
"source": {
"id": "HEAD"
},
"cartocss": "...",
"cartocss_version": "2.3.0"
}
}
],
"analyses": [
{
"id": "HEAD",
"type": "source",
"params": {
"query": "select * from your_table"
}
}
]
}
```
# History
## 1.0.0
- Initial version

View File

@@ -0,0 +1,314 @@
# 1. Purpose
This specification describes an extension for
[MapConfig 1.4.0](https://github.com/CartoDB/Windshaft/blob/master/doc/MapConfig-1.4.0.md) version.
# 2. Changes over specification
This extension depends on Analyses extension. It extends MapConfig with a new attribute: `dataviews`.
It makes possible to get tabular data from analysis nodes: lists, aggregated lists, aggregations, and histograms.
## 2.1. Dataview types
### List
A list is a simple result set per row where is possible to retrieve several columns from the original layer query.
Definition
```
{
// REQUIRED
// string, `type` the list type
“type”: “list”,
// REQUIRED
// object, `options` dataview params
“options”: {
// REQUIRED
// array, `columns` to select for the list
“columns”: [“name”, “description”]
}
}
```
Expected output
```
{
"type": "list",
"rows": [
{
"{columnName1}": "val1",
"{columnName2}": 100
},
{
"{columnName1}": "val2",
"{columnName2}": 200
}
]
}
```
### Aggregation
An aggregation is very similar to a list but results are aggregated by a column and a given aggregation function.
Definition
```
{
// REQUIRED
// string, `type` the aggregation type
“type”: “aggregation”,
// REQUIRED
// object, `options` dataview params
“options”: {
// REQUIRED
// string, `column` column name to aggregate by
“column”: “country”,
// REQUIRED
// string, `aggregation` operation to perform
“aggregation”: “count”
// OPTIONAL
// string, `aggregationColumn` column value to aggregate
// This param is required when `aggregation` is different than "count"
“aggregationColumn”: “population”
}
}
```
Expected output
```
{
"type": "aggregation",
"categories": [
{
"category": "foo",
"value": 100
},
{
"category": "bar",
"value": 200
}
]
}
```
### Histograms
Histograms represent the data distribution for a column.
Definition
```
{
// REQUIRED
// string, `type` the histogram type
“type”: “histogram”,
// REQUIRED
// object, `options` dataview params
“options”: {
// REQUIRED
// string, `column` column name to aggregate by
“column”: “name”,
// OPTIONAL
// number, `bins` how many buckets the histogram should use
“bins”: 10
}
}
```
Expected output
```
{
"type": "histogram",
"bins": [{"bin": 0, "start": 2, "end": 2, "min": 2, "max": 2, "freq": 1}, null, null, {"bin": 3, "min": 40, "max": 44, "freq": 2}, null],
"width": 10
}
```
### Formula
Formulas given a final value representing the whole dataset.
Definition
```
{
// REQUIRED
// string, `type` the formula type
“type”: “formula”,
// REQUIRED
// object, `options` dataview params
“options”: {
// REQUIRED
// string, `column` column name to aggregate by
“column”: “name”,
// REQUIRED
// string, `aggregation` operation to perform
“operation”: “count”
}
}
```
Operation must be: “min”, “max”, “count”, “avg”, or “sum”.
Result
```
{
"type": "formula",
"operation": "count",
"result": 1000,
"nulls": 0
}
```
## 2.2 Dataviews attribute
The new dataviews attribute must be a dictionary of dataviews.
An analysis node id can be referenced from dataviews to consume its output query.
The layer consuming the output must reference it with the following option:
```
{
// REQUIRED
// object, `source` as in the future we might want to have other source options
"source": {
// REQUIRED
// string, `id` the analysis node identifier
"id": "HEAD"
}
}
```
## 2.3. Complete example
```
{
"version": "1.4.0",
"layers": [
{
"type": "cartodb",
"options": {
"source": {
"id": "HEAD"
},
"cartocss": "...",
"cartocss_version": "2.3.0"
}
}
],
"dataviews" {
"basic_histogram": {
"source": {
"id": "HEAD"
},
"type": "histogram",
"options": {
"column": "pop_max"
}
}
},
"analyses": [
{
"id": "HEAD",
"type": "source",
"params": {
"query": "select * from your_table"
}
}
]
}
```
## 3. Filters
Camshaft's analyses expose a filtering capability and `aggregation` and `histogram` dataviews get them for free with
this extension. Filters are available with the very dataview id, so if you have a "basic_histogram" histogram dataview
you can filter with a range filter with "basic_histogram" name.
## 3.1 Filter types
### Category
Allows to remove results that are not contained within a set of elements.
Initially this filter can be applied to a `numeric` or `text` columns.
Params
```
{
“accept”: [“Spain”, “Germany”]
“reject”: [“Japan”]
}
```
### Range filter
Allows to remove results that dont satisfy numeric min and max values.
Filter is applied to a numeric column.
Params
```
{
“min”: 0,
“max”: 1000
}
```
## 3.2. How to apply filters
Filters must be applied at map instantiation time.
With :mapconfig as a valid MapConfig and with :filters (a valid JSON) as:
### Anonymous map
`GET /api/v1/map?config=:mapconfig&filters=:filters`
`POST /api/v1/map?filters=:filters`
with `BODY=:mapconfig`
If in the future we need to support a bigger filters param and it doesnt fit in the query string,
we might solve it by accepting:
`POST /api/v1/map`
with `BODY={“config”: :mapconfig, “filters”: :filters}`
### Named map
Assume :params (a valid JSON) as named maps params, like in: `{“color”: “red”}`
`GET /api/v1/named/:name/jsonp?config=:params&filters=:filters&callback=cb`
`POST /api/v1/named/:name?filters=:filters`
with `BODY=:params`
If, again, in the future we need to support a bigger filters param that doesnt fit in the query string,
we might solve it by accepting:
`POST /api/v1/named/:name`
with `BODY={“config”: :params, “filters”: :filters}`
## 3.3 Bounding box special filter
A bounding box filter allows to remove results that dont satisfy a geospatial range.
The bounding box special filter is available per dataview and there is no need to create a bounding box definition as
its always possible to apply a bbox filter per dataview.
A dataview can get its result filtered by bounding box by sending a bbox param in the query string,
param must be in the form `west,south,east,north`.
So applying a bbox filter to a dataview looks like:
GET /api/v1/map/:layergroupid/dataview/:dataview_name?bbox=-90,-45,90,45
# History
## 1.0.0-alpha
- WIP document

View File

@@ -28,7 +28,7 @@ POST /api/v1/map
}
```
See [MapConfig File Formats](http://docs.cartodb.com/cartodb-platform/maps-api/mapconfig/) for details.
See [MapConfig File Formats](http://docs.carto.com/carto-engine/maps-api/mapconfig/) for details.
#### Response
@@ -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://{username}.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}.carto.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://{username}.cartodb.com/api/v1/map' -H 'Content-Type: application/json' -d @mapconfig.json
curl 'https://{username}.carto.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://{username}.cartodb.com/api/v1/map/{layergroupid}/{z}/{x}/{y}.png
https://{username}.carto.com/api/v1/map/{layergroupid}/{z}/{x}/{y}.png
```
#### Individual layers
@@ -89,7 +89,7 @@ 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://{username}.cartodb.com/api/v1/map/{layergroupid}/{layer}/{z}/{x}/{y}.grid.json
https://{username}.carto.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.
@@ -97,13 +97,13 @@ In this case, `layer` as 0 returns the UTF grid tiles/attributes for layer 0, th
If the MapConfig had a Torque layer at index 1 it could be possible to request it with:
```bash
https://{username}.cartodb.com/api/v1/map/{layergroupid}/1/{z}/{x}/{y}.torque.json
https://{username}.carto.com/api/v1/map/{layergroupid}/1/{z}/{x}/{y}.torque.json
```
#### Attributes defined in `attributes` section
```bash
https://{username}.cartodb.com/api/v1/map/{layergroupid}/{layer}/attributes/{feature_id}
https://{username}.carto.com/api/v1/map/{layergroupid}/{layer}/attributes/{feature_id}
```
Which returns JSON with the attributes defined, like:
@@ -115,7 +115,7 @@ Which returns JSON with the attributes defined, like:
#### Blending and layer selection
```bash
https://{username}.cartodb.com/api/v1/map/{layergroupid}/{layer_filter}/{z}/{x}/{y}.png
https://{username}.carto.com/api/v1/map/{layergroupid}/{layer_filter}/{z}/{x}/{y}.png
```
Note: currently format is limited to `png`.
@@ -127,7 +127,7 @@ Note: currently format is limited to `png`.
Using `all` as `layer_filter` will blend all layers in the layergroup
```bash
https://{username}.cartodb.com/api/v1/map/{layergroupid}/all/{z}/{x}/{y}.png
https://{username}.carto.com/api/v1/map/{layergroupid}/all/{z}/{x}/{y}.png
```
- Filter by layer index
@@ -135,15 +135,12 @@ https://{username}.cartodb.com/api/v1/map/{layergroupid}/all/{z}/{x}/{y}.png
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://{username}.cartodb.com/api/v1/map/{layergroupid}/0,3,4/{z}/{x}/{y}.png
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.
- Once a Mapnik layer is selected, all Mapnik layers will get blended. As this may change in the future **it is
recommended** to always select all Mapnik layers if you want to select at least one 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 you will get a
consistent behavior in the future.
@@ -172,7 +169,7 @@ callback | JSON callback name.
#### Call
```bash
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"
curl "https://{username}.carto.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

View File

@@ -4,7 +4,7 @@ The following concepts are the same for every endpoint in the API except when it
## Auth
By default, users do not have access to private tables in CartoDB. In order to instantiate a map from private table data an API Key is required. Additionally, to include some endpoints, an API Key must be included (e.g. creating a Named Map).
By default, users do not have access to private tables in CARTO. In order to instantiate a map from private table data an API Key is required. Additionally, to include some endpoints, an API Key must be included (e.g. creating a Named Map).
To execute an authorized request, `api_key=YOURAPIKEY` should be added to the request URL. The param can be also passed as POST param. Using HTTPS is mandatory when you are performing requests that include your `api_key`.

View File

@@ -1,12 +1,12 @@
# Named Maps
Named Maps are essentially the same as Anonymous Maps except the MapConfig is stored on the server, and the map is given a unique name. You can create Named Maps from private data, and users without an API Key can view your Named Map (while keeping your data private).
Named Maps are essentially the same as Anonymous Maps except the MapConfig is stored on the server, and the map is given a unique name. You can create Named Maps from private data, and users without an API Key can view your Named Map (while keeping your data private).
The Named Map workflow consists of uploading a MapConfig file to CartoDB servers, to select data from your CartoDB user database by using SQL, and specifying the CartoCSS for your map.
The Named Map workflow consists of uploading a MapConfig file to CARTO servers, to select data from your CARTO user database by using SQL, and specifying the CartoCSS for your map.
The response back from the API provides the template_id of your Named Map as the `name` (the identifier of your Named Map), which is the name that you specified in the MapConfig. You can which you can then use to create your Named Map details, or [fetch XYZ tiles](#fetching-xyz-tiles-for-named-maps) directly for Named Maps.
The response back from the API provides the template_id of your Named Map as the `name` (the identifier of your Named Map), which is the name that you specified in the MapConfig. You can which you can then use to create your Named Map details, or [fetch XYZ tiles](#fetching-xyz-tiles-for-named-maps) directly for Named Maps.
**Tip:** You can also use a Named Map that you created (which is defined by its `name`), to create a map using CartoDB.js. This is achieved by adding the [`namedmap` type](http://docs.cartodb.com/cartodb-platform/cartodb-js/layer-source-object/#named-maps-layer-source-object-type-namedmap) layer source object to draw the Named Map.
**Tip:** You can also use a Named Map that you created (which is defined by its `name`), to create a map using CARTO.js. This is achieved by adding the [`namedmap` type](http://docs.carto.com/carto-engine/carto-js/layer-source-object/#named-maps-layer-source-object-type-namedmap) layer source object to draw the Named Map.
The main differences, compared to Anonymous Maps, is that Named Maps include:
@@ -14,11 +14,11 @@ The main differences, compared to Anonymous Maps, is that Named Maps include:
This allows you to control who is able to see the map based on an auth token, and create a secure Named Map with password-protection.
- **template map**
The template map is static and may contain placeholders, enabling you to modify your maps appearance by using variables. Templates maps are persistent with no preset expiration. They can only be created, or deleted, by a CartoDB user with a valid API KEY (See [auth argument](#arguments)).
The template map is static and may contain placeholders, enabling you to modify your maps appearance by using variables. Templates maps are persistent with no preset expiration. They can only be created, or deleted, by a CARTO user with a valid API KEY (See [auth argument](#arguments)).
Uploading a MapConfig creates a Named Map. MapConfigs are uploaded to the server by sending the server a "template".json file, which contain the [MapConfig specifications](http://docs.cartodb.com/cartodb-platform/maps-api/mapconfig/).
Uploading a MapConfig creates a Named Map. MapConfigs are uploaded to the server by sending the server a "template".json file, which contain the [MapConfig specifications](http://docs.carto.com/carto-engine/maps-api/mapconfig/).
**Note:** There is a limit of 4,096 Named Maps allowed per account. If you need to create more Named Maps, it is recommended to use a single Named Map and change the variables using [placeholders](#placeholder-format), instead of uploading multiple [Named Map MapConfigs](http://docs.cartodb.com/cartodb-platform/maps-api/mapconfig/#named-map-layer-options).
**Note:** There is a limit of 4,096 Named Maps allowed per account. If you need to create more Named Maps, it is recommended to use a single Named Map and change the variables using [placeholders](#placeholder-format), instead of uploading multiple [Named Map MapConfigs](http://docs.carto.com/carto-engine/maps-api/mapconfig/#named-map-layer-options).
## Create
@@ -33,7 +33,7 @@ POST /api/v1/map/named
Params | Description
--- | ---
api_key | is required
MapConfig | a [Named Map MapConfig](http://docs.cartodb.com/cartodb-platform/maps-api/mapconfig/#named-map-layer-options) is required to create a Named Map
MapConfig | a [Named Map MapConfig](http://docs.carto.com/carto-engine/maps-api/mapconfig/#named-map-layer-options) is required to create a Named Map
#### template.json
@@ -84,6 +84,10 @@ The `name` argument defines how to name this "template_name".json. Note that the
"south": -45,
"east": 45,
"north": 45
},
"preview_layers": {
"0": true,
"layer1": false
}
}
}
@@ -95,22 +99,22 @@ Params | Description
--- | ---
name | There can only be _one_ template with the same name for any user. Valid names start with a letter or a number, and only contain letters, numbers, dashes (-), or underscores (_). _This is specific to the name of your Named Map that is specified in the `name` property of the template file_.
auth |
auth |
--- | ---
&#124;_ method | `"token"` or `"open"` (`"open"` is the default if no method is specified. Use `"token"` to password-protect your map)
&#124;_ valid_tokens | when `"method"` is set to `"token"`, the values listed here allow you to instantiate the Named Map. See this [example](http://docs.cartodb.com/faqs/manipulating-your-data/#how-to-create-a-password-protected-named-map) for how to create a password-protected map.
&#124;_ valid_tokens | when `"method"` is set to `"token"`, the values listed here allow you to instantiate the Named Map. See this [example](http://docs.carto.com/faqs/manipulating-your-data/#how-to-create-a-password-protected-named-map) for how to create a password-protected map.
placeholders | Placeholders are variables that can be placed in your template.json file's SQL or CartoCSS.
layergroup | the layergroup configurations, as specified in the template. See [MapConfig File Format](http://docs.cartodb.com/cartodb-platform/maps-api/mapconfig/) for more information.
view (optional) | extra keys to specify the view area for the map. It can be used to have a static preview of a Named Map without having to instantiate it. It is possible to specify it with `center` + `zoom` or with a bounding box `bbox`. Center+zoom takes precedence over bounding box.
layergroup | the layergroup configurations, as specified in the template. See [MapConfig File Format](http://docs.carto.com/carto-engine/maps-api/mapconfig/) for more information.
view (optional) | extra keys to specify the view area for the map. It can be used to have a static preview of a Named Map without having to instantiate it. It is possible to specify it with `center` + `zoom` or with a bounding box `bbox`. Center+zoom takes precedence over bounding box. Also it is possible to choose which layers are visible or not with `preview_layers` indicating its visibility by layer index or id (visible by default).
--- | ---
&#124;_ zoom | The zoom level to use
&#124;_ center |
&#124;_ center |
--- | ---
&#124;_ &#124;_ lng | The longitude to use for the center
&#124;_ &#124;_ lat | The latitude to use for the center
&#124;_ bounds |
&#124;_ bounds |
--- | ---
&#124;_ &#124;_ west | LowerCorner longitude for the bounding box, in decimal degrees (aka most western)
&#124;_ &#124;_ south | LowerCorner latitude for the bounding box, in decimal degrees (aka most southern)
@@ -120,7 +124,7 @@ view (optional) | extra keys to specify the view area for the map. It can be use
### Placeholder Format
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).
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.carto.com/carto-engine/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 inside the Named Maps API.
@@ -155,12 +159,12 @@ 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://{username}.cartodb.com/api/v1/map/named?api_key={api_key}'
'https://{username}.carto.com/api/v1/map/named?api_key={api_key}'
```
#### Response
The response back from the API provides the name of your MapConfig as a template, enabling you to edit the Named Map details by inserting your variables into the template where placeholders are defined, and create custom queries using SQL.
The response back from the API provides the name of your MapConfig as a template, enabling you to edit the Named Map details by inserting your variables into the template where placeholders are defined, and create custom queries using SQL.
```javascript
{
@@ -170,7 +174,7 @@ The response back from the API provides the name of your MapConfig as a template
## Instantiate
Instantiating a Named Map allows you to fetch the map tiles. You can use the Maps API to instantiate, or use the CartoDB.js `createLayer()` function. The result is an Anonymous Map.
Instantiating a Named Map allows you to fetch the map tiles. You can use the Maps API to instantiate, or use the CARTO.js `createLayer()` function. The result is an Anonymous Map.
#### Definition
@@ -209,7 +213,7 @@ Valid auth token will be needed, if required by the template.
curl -X POST \
-H 'Content-Type: application/json' \
-d @params.json \
'https://{username}.cartodb.com/api/v1/map/named/{template_name}?auth_token={auth_token}'
'https://{username}.carto.com/api/v1/map/named/{template_name}?auth_token={auth_token}'
```
#### Response
@@ -229,7 +233,7 @@ curl -X POST \
}
```
You can then use the `layergroupid` for fetching tiles and grids as you would normally (see [Anonymous Maps](http://docs.cartodb.com/cartodb-platform/maps-api/anonymous-maps/)).
You can then use the `layergroupid` for fetching tiles and grids as you would normally (see [Anonymous Maps](http://docs.carto.com/carto-engine/maps-api/anonymous-maps/)).
## Update
@@ -261,7 +265,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://{username}.cartodb.com/api/v1/map/named/{template_name}?api_key={api_key}'
'https://{username}.carto.com/api/v1/map/named/{template_name}?api_key={api_key}'
```
#### Response
@@ -303,7 +307,7 @@ api_key | is required
#### Call
```bash
curl -X DELETE 'https://{username}.cartodb.com/api/v1/map/named/{template_name}?api_key={api_key}'
curl -X DELETE 'https://{username}.carto.com/api/v1/map/named/{template_name}?api_key={api_key}'
```
#### Response
@@ -337,7 +341,7 @@ api_key | is required
#### Call
```bash
curl -X GET 'https://{username}.cartodb.com/api/v1/map/named?api_key={api_key}'
curl -X GET 'https://{username}.carto.com/api/v1/map/named?api_key={api_key}'
```
#### Response
@@ -377,7 +381,7 @@ api_key | is required
#### Call
```bash
curl -X GET 'https://{username}.cartodb.com/api/v1/map/named/{template_name}?api_key={api_key}'
curl -X GET 'https://{username}.carto.com/api/v1/map/named/{template_name}?api_key={api_key}'
```
#### Response
@@ -418,7 +422,7 @@ callback | JSON callback name
#### Call
```bash
curl 'https://{username}.cartodb.com/api/v1/map/named/{template_name}/jsonp?auth_token={auth_token}&callback=callback&config=template_params_json'
curl 'https://{username}.carto.com/api/v1/map/named/{template_name}/jsonp?auth_token={auth_token}&callback=callback&config=template_params_json'
```
#### Response
@@ -450,9 +454,9 @@ callback({
})
```
## CartoDB.js for Named Maps
## CARTO.js for Named Maps
You can use a Named Map that you created (which is defined by its `name`), to create a map using CartoDB.js. This is achieved by adding the [`namedmap` type](http://docs.cartodb.com/cartodb-platform/cartodb-js/layer-source-object/#named-maps-layer-source-object-type-namedmap) layer source object to draw the Named Map.
You can use a Named Map that you created (which is defined by its `name`), to create a map using CARTO.js. This is achieved by adding the [`namedmap` type](http://docs.carto.com/carto-engine/carto-js/layer-source-object/#named-maps-layer-source-object-type-namedmap) layer source object to draw the Named Map.
```javascript
{
@@ -482,17 +486,17 @@ You can use a Named Map that you created (which is defined by its `name`), to cr
**Note:** Instantiating a Named Map over a `createLayer` does not require an API Key and by default, does not include auth tokens. _If_ you defined auth tokens for the Named Map configuration, then you will have to include them.
[CartoDB.js](http://docs.cartodb.com/cartodb-platform/cartodb-js/) has methods for accessing your Named Maps.
[CARTO.js](http://docs.carto.com/carto-engine/carto-js/) has methods for accessing your Named Maps.
1. [layer.setParams()](http://docs.cartodb.com/cartodb-platform/cartodb-js/api-methods/#layersetparamskey-value) allows you to change the template variables (in the placeholders object) via JavaScript
1. [layer.setParams()](http://docs.carto.com/carto-engine/carto-js/api-methods/#layersetparamskey-value) allows you to change the template variables (in the placeholders object) via JavaScript
**Note:** The CartoDB.js `layer.setParams()` function is not supported when using Named Maps for Torque. Alternatively, you can create a [Torque layer in a Named Map](http://bl.ocks.org/iriberri/de37be6406f9cc7cfe5a)
**Note:** The CARTO.js `layer.setParams()` function is not supported when using Named Maps for Torque. Alternatively, you can create a [Torque layer in a Named Map](http://bl.ocks.org/iriberri/de37be6406f9cc7cfe5a)
2. [layer.setAuthToken()](http://docs.cartodb.com/cartodb-platform/cartodb-js/api-methods/#layersetauthtokenauthtoken) allows you to set the auth tokens to create the layer
2. [layer.setAuthToken()](http://docs.carto.com/carto-engine/carto-js/api-methods/#layersetauthtokenauthtoken) allows you to set the auth tokens to create the layer
### Torque Layer in a Named Map
If you are creating a Torque layer in a Named Map without using the Torque.js library, you can apply the Torque layer by applying the following code with CartoDBjs:
If you are creating a Torque layer in a Named Map without using the Torque.js library, you can apply the Torque layer by applying the following code with CARTO.js:
```javascript
// add cartodb layer with one sublayer
@@ -516,12 +520,12 @@ If you are creating a Torque layer in a Named Map without using the Torque.js li
})
.addTo(map)
.done(function(layer) {
});
}
```
#### Examples of Named Maps created with CartoDB.js
#### Examples of Named Maps created with CARTO.js
- [Named Map selectors with interaction](http://bl.ocks.org/ohasselblad/515a8af1f99d5e690484)
@@ -535,7 +539,7 @@ Optionally, authenticated users can fetch projected tiles (XYZ tiles or Mapnik R
### Fetch XYZ Tiles Directly with a URL
Authenticated users, with an auth token, can use XYZ-based URLs to fetch tiles directly, and instantiate the Named Map as part of the request to your application. You do not have to do any other steps to initialize your map.
Authenticated users, with an auth token, can use XYZ-based URLs to fetch tiles directly, and instantiate the Named Map as part of the request to your application. You do not have to do any other steps to initialize your map.
To call a template_id in a URL:
@@ -543,21 +547,21 @@ To call a template_id in a URL:
For example, a complete URL might appear as:
"https://{username}.cartodb.com/api/v1/map/named/{template_id}/{layer}/{z}/{x}/{y}.png"
"https://{username}.carto.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.
- [`template_id`](http://docs.carto.com/carto-engine/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://{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"
- To show just the basemap layer, enter the number value `0` in the layer placeholder "https://{username}.carto.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}.carto.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}.carto.com/api/v1/map/named/{template_id}/all/{z}/{x}/{y}.png"
- To show a [list of layers](http://docs.carto.com/carto-engine/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}.carto.com/api/v1/map/named/{template_id}/0,1/{z}/{x}/{y}.png"
### Get Mapnik Retina Tiles
Mapnik Retina tiles are not directly supported for Named Maps, so you cannot use the Named Map template_id. To fetch Mapnik Retina tiles, get the [layergroupid](http://docs.cartodb.com/cartodb-platform/maps-api/named-maps/#response-1) to initialize the map.
Mapnik Retina tiles are not directly supported for Named Maps, so you cannot use the Named Map template_id. To fetch Mapnik Retina tiles, get the [layergroupid](http://docs.carto.com/carto-engine/maps-api/named-maps/#response-1) to initialize the map.
Instantiate the map by using your `layergroupid` in the token placeholder:

View File

@@ -22,10 +22,10 @@ $.ajax({
type: 'POST',
dataType: 'json',
contentType: 'application/json',
url: 'https://{username}.cartodb.com/api/v1/map',
url: 'https://{username}.carto.com/api/v1/map',
data: JSON.stringify(mapconfig),
success: function(data) {
var templateUrl = 'https://{username}.cartodb.com/api/v1/map/' + data.layergroupid + '/{z}/{x}/{y}.png'
var templateUrl = 'https://{username}.carto.com/api/v1/map/' + data.layergroupid + '/{z}/{x}/{y}.png'
console.log(templateUrl);
}
})
@@ -33,7 +33,7 @@ $.ajax({
## Named Maps
Let's create a Named Map using some private tables in a CartoDB account.
Let's create a Named Map using some private tables in a CARTO account.
The following map config sets up a map of European countries that have a white fill color:
```javascript
@@ -56,12 +56,12 @@ The following map config sets up a map of European countries that have a white f
}
```
The MapConfig needs to be sent to CartoDB's Map API using an authenticated call. Here we will use a command line tool called `curl`. For more info about this tool, see [this blog post](http://quickleft.com/blog/command-line-tutorials-curl), or type `man curl` in bash. Using `curl`, and storing the config from above in a file `MapConfig.json`, the call would look like:
The MapConfig needs to be sent to CARTO's Map API using an authenticated call. Here we will use a command line tool called `curl`. For more info about this tool, see [this blog post](http://quickleft.com/blog/command-line-tutorials-curl), or type `man curl` in bash. Using `curl`, and storing the config from above in a file `MapConfig.json`, the call would look like:
#### Call
```bash
curl 'https://{username}.cartodb.com/api/v1/map/named?api_key={api_key}' -H 'Content-Type: application/json' -d @mapconfig.json
curl 'https://{username}.carto.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://{username}.cartodb.com/api/v1/map/named/{template_id}' -H 'Content-Type: application/json'
curl -X POST 'https://{username}.carto.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://{username}.cartodb.com/api/v1/map/{layergroupid}/{z}/{x}/{y}.png
https://{username}.carto.com/api/v1/map/{layergroupid}/{z}/{x}/{y}.png
```

View File

@@ -78,7 +78,7 @@ format | the format for the image, supported types: `png`, `jpg`
--- | ---
&#124;_ jpg | will have a default quality of 85.
A Named Maps static image will get its constraints from the [`view` argument of the Create Named Map function](http://docs.cartodb.com/cartodb-platform/maps-api/named-maps/#arguments). If `view` is not defined, it will estimate the extent based on the involved tables, otherwise it fallbacks to `"zoom": 1`, `"lng": 0` and `"lat": 0`.
A Named Maps static image will get its constraints from the [`view` argument of the Create Named Map function](http://docs.carto.com/carto-engine/maps-api/named-maps/#arguments). If `view` is not defined, it will estimate the extent based on the involved tables, otherwise it fallbacks to `"zoom": 1`, `"lng": 0` and `"lat": 0`.
#### Layers
@@ -122,9 +122,9 @@ By manipulating the `"urlTemplate"` custom basemaps can be used in generating st
},
```
**CartoDB**
**CARTO**
As described in the [MapConfig File Format](http://docs.cartodb.com/cartodb-platform/maps-api/mapconfig/), a "cartodb" type layer is now just an alias to a "mapnik" type layer as above, intended for backwards compatibility.
As described in the [MapConfig File Format](http://docs.carto.com/carto-engine/maps-api/mapconfig/), a "cartodb" type layer is now just an alias to a "mapnik" type layer as above, intended for backwards compatibility.
```javascript
{
@@ -142,18 +142,18 @@ Additionally, static images from Torque maps and other map layers can be used to
### Caching
It is important to note that generated images are cached from the live data referenced with the `layergroupid token` on the specified CartoDB account. This means that if the data changes, the cached image will also change. When linking dynamically, it is important to take into consideration the state of the data and longevity of the static image to avoid broken images or changes in how the image is displayed. To obtain a static snapshot of the map as it is today and preserve the image long-term regardless of changes in data, the image must be saved and stored locally.
It is important to note that generated images are cached from the live data referenced with the `layergroupid token` on the specified CARTO account. This means that if the data changes, the cached image will also change. When linking dynamically, it is important to take into consideration the state of the data and longevity of the static image to avoid broken images or changes in how the image is displayed. To obtain a static snapshot of the map as it is today and preserve the image long-term regardless of changes in data, the image must be saved and stored locally.
### Limits
* While images can encompass an entirety of a map, the default limit for pixel range is 8192 x 8192.
* Image resolution by default is set to 72 DPI
* JPEG quality by default is 85%
* Timeout limits for generating static maps are the same across the CartoDB Editor and Platform. It is important to ensure timely processing of queries.
* While images can encompass an entirety of a map, the limit for pixel range is 8192 x 8192.
* Image resolution is set to 72 DPI
* JPEG quality is 85%
* Timeout limits for generating static maps are the same across CARTO Builder and CARTO Engine. It is important to ensure timely processing of queries.
## Examples
After instantiating a map from a CartoDB account:
After instantiating a map from a CARTO account:
#### Call

View File

@@ -0,0 +1,59 @@
var _ = require('underscore');
var step = require('step');
var AnalysisFilter = require('../models/filter/analysis');
function FilterStatsApi(pgQueryRunner) {
this.pgQueryRunner = pgQueryRunner;
}
module.exports = FilterStatsApi;
function getEstimatedRows(pgQueryRunner, username, query, callback) {
pgQueryRunner.run(username, "EXPLAIN (FORMAT JSON)"+query, function(err, result_rows) {
if (err){
callback(err);
return;
}
var rows;
if ( result_rows[0] && result_rows[0]['QUERY PLAN'] &&
result_rows[0]['QUERY PLAN'][0] && result_rows[0]['QUERY PLAN'][0].Plan ) {
rows = result_rows[0]['QUERY PLAN'][0].Plan['Plan Rows'];
}
return callback(null, rows);
});
}
FilterStatsApi.prototype.getFilterStats = function (username, unfiltered_query, filters, callback) {
var stats = {};
var self = this;
step(
function getUnfilteredRows() {
getEstimatedRows(self.pgQueryRunner, username, unfiltered_query, this);
},
function receiveUnfilteredRows(err, rows) {
if (err){
callback(err);
return;
}
stats.unfiltered_rows = rows;
this(null, rows);
},
function getFilteredRows() {
if ( filters && !_.isEmpty(filters)) {
var analysisFilter = new AnalysisFilter(filters);
var query = analysisFilter.sql(unfiltered_query);
getEstimatedRows(self.pgQueryRunner, username, query, this);
} else {
this(null, null);
}
},
function receiveFilteredRows(err, rows) {
if (err){
callback(err);
return;
}
stats.filtered_rows = rows;
callback(null, stats);
}
);
};

View File

@@ -9,7 +9,10 @@ module.exports = AnalysisStatusBackend;
AnalysisStatusBackend.prototype.getNodeStatus = function (params, callback) {
var nodeId = params.nodeId;
var statusQuery = 'SELECT node_id, status, updated_at FROM cdb_analysis_catalog where node_id = \'' + nodeId + '\'';
var statusQuery = [
'SELECT node_id, status, updated_at, last_error_message as error_message',
'FROM cdb_analysis_catalog where node_id = \'' + nodeId + '\''
].join(' ');
var pg = new PSQL(dbParamsFromReqParams(params));
pg.query(statusQuery, function(err, result) {
@@ -21,10 +24,16 @@ AnalysisStatusBackend.prototype.getNodeStatus = function (params, callback) {
var rows = result.rows || [];
return callback(null, rows[0] || {
var statusResponse = rows[0] || {
node_id: nodeId,
status: 'unknown'
});
};
if (statusResponse.status !== 'failed') {
delete statusResponse.error_message;
}
return callback(null, statusResponse);
}, true); // use read-only transaction
};

View File

@@ -1,19 +1,93 @@
var camshaft = require('camshaft');
'use strict';
function AnalysisBackend(options) {
var batchConfig = options.batch || {};
var _ = require('underscore');
var camshaft = require('camshaft');
var fs = require('fs');
var REDIS_LIMITS = {
DB: 5,
PREFIX: 'limits:analyses:' // + username
};
function AnalysisBackend (metadataBackend, options) {
this.metadataBackend = metadataBackend;
this.options = options || {};
this.options.limits = this.options.limits || {};
this.setBatchConfig(this.options.batch);
this.setLoggerConfig(this.options.logger);
}
module.exports = AnalysisBackend;
AnalysisBackend.prototype.setBatchConfig = function (options) {
var batchConfig = options || {};
batchConfig.endpoint = batchConfig.endpoint || 'http://127.0.0.1:8080/api/v1/sql/job';
batchConfig.inlineExecution = batchConfig.inlineExecution || false;
batchConfig.hostHeaderTemplate = batchConfig.hostHeaderTemplate || '{{=it.username}}.localhost.lan';
this.batchConfig = batchConfig;
}
};
module.exports = AnalysisBackend;
AnalysisBackend.prototype.setLoggerConfig = function (options) {
this.loggerConfig = options || {};
if (this.loggerConfig.filename) {
this.stream = fs.createWriteStream(this.loggerConfig.filename, { flags: 'a', encoding: 'utf8' });
process.on('SIGHUP', function () {
if (this.stream) {
this.stream.destroy();
}
this.stream = fs.createWriteStream(this.loggerConfig.filename, { flags: 'a', encoding: 'utf8' });
}.bind(this));
}
};
AnalysisBackend.prototype.create = function(analysisConfiguration, analysisDefinition, callback) {
analysisConfiguration.batch.endpoint = this.batchConfig.endpoint;
analysisConfiguration.batch.inlineExecution = this.batchConfig.inlineExecution;
analysisConfiguration.batch.hostHeaderTemplate = this.batchConfig.hostHeaderTemplate;
camshaft.create(analysisConfiguration, analysisDefinition, callback);
analysisConfiguration.logger = {
stream: this.stream ? this.stream : process.stdout
};
this.getAnalysesLimits(analysisConfiguration.user, function(err, limits) {
analysisConfiguration.limits = limits || {};
camshaft.create(analysisConfiguration, analysisDefinition, callback);
});
};
AnalysisBackend.prototype.getAnalysesLimits = function(username, callback) {
var self = this;
var analysesLimits = {
analyses: {
// buffer: {
// timeout: 1000,
// maxNumberOfRows: 1e6
// }
}
};
Object.keys(self.options.limits).forEach(function(analysisTypeOrTag) {
analysesLimits.analyses[analysisTypeOrTag] = _.extend({}, self.options.limits[analysisTypeOrTag]);
});
var analysesLimitsKey = REDIS_LIMITS.PREFIX + username;
this.metadataBackend.redisCmd(REDIS_LIMITS.DB, 'HGETALL', [analysesLimitsKey], function(err, analysesTimeouts) {
// analysesTimeouts wil be something like: { moran: 3000, intersection: 5000 }
analysesTimeouts = analysesTimeouts || {};
Object.keys(analysesTimeouts).forEach(function(analysisType) {
analysesLimits.analyses[analysisType] = _.defaults(
{
timeout: Number.isFinite(+analysesTimeouts[analysisType]) ? +analysesTimeouts[analysisType] : 0
},
analysesLimits.analyses[analysisType]
);
});
return callback(null, analysesLimits);
});
};

View File

@@ -2,13 +2,19 @@ var assert = require('assert');
var _ = require('underscore');
var PSQL = require('cartodb-psql');
var camshaft = require('camshaft');
var step = require('step');
var Timer = require('../stats/timer');
var BBoxFilter = require('../models/filter/bbox');
var DataviewFactory = require('../models/dataview/factory');
var DataviewFactoryWithOverviews = require('../models/dataview/overviews/factory');
var OverviewsQueryRewriter = require('../utils/overviews_query_rewriter');
var overviewsQueryRewriter = new OverviewsQueryRewriter({
zoom_level: 'CDB_ZoomFromScale(!scale_denominator!)'
});
var dot = require('dot');
dot.templateSettings.strip = false;
function DataviewBackend(analysisBackend) {
this.analysisBackend = analysisBackend;
@@ -16,215 +22,102 @@ function DataviewBackend(analysisBackend) {
module.exports = DataviewBackend;
DataviewBackend.prototype.getDataview = function (mapConfigProvider, user, params, callback) {
var self = this;
var timer = new Timer();
var dataviewName = params.dataviewName;
var mapConfig;
var dataviewDefinition;
step(
function getMapConfig() {
mapConfigProvider.getMapConfig(this);
},
function _getDataviewDefinition(err, _mapConfig) {
function runDataviewQuery(err, mapConfig) {
assert.ifError(err);
mapConfig = _mapConfig;
var _dataviewDefinition = getDataviewDefinition(mapConfig.obj(), dataviewName);
if (!_dataviewDefinition) {
var dataviewDefinition = getDataviewDefinition(mapConfig.obj(), dataviewName);
if (!dataviewDefinition) {
throw new Error("Dataview '" + dataviewName + "' does not exists");
}
dataviewDefinition = _dataviewDefinition;
return dataviewDefinition;
},
function loadAnalysis(err) {
assert.ifError(err);
var analysisConfiguration = {
db: {
host: params.dbhost,
port: params.dbport,
dbname: params.dbname,
user: params.dbuser,
pass: params.dbpassword
},
batch: {
username: user,
apiKey: params.api_key
}
};
var sourceId = dataviewDefinition.source.id;
var analysisDefinition = getAnalysisDefinition(mapConfig.obj().analyses, sourceId);
var next = this;
self.analysisBackend.create(analysisConfiguration, analysisDefinition, function(err, analysis) {
if (err) {
return next(err);
}
var sourceId2Node = {};
var rootNode = analysis.getRoot();
if (rootNode.params && rootNode.params.id) {
sourceId2Node[rootNode.params.id] = rootNode;
}
analysis.getSortedNodes().forEach(function(node) {
if (node.params && node.params.id) {
sourceId2Node[node.params.id] = node;
}
});
var node = sourceId2Node[sourceId];
if (!node) {
return next(new Error('Analysis node not found for dataview'));
}
return next(null, node);
});
},
function runDataviewQuery(err, node) {
assert.ifError(err);
var pg = new PSQL(dbParamsFromReqParams(params));
var ownFilter = +params.own_filter;
ownFilter = !!ownFilter;
var query;
if (ownFilter) {
query = node.getQuery();
} else {
var applyFilters = {};
applyFilters[dataviewName] = false;
query = node.getQuery(applyFilters);
var query = (ownFilter) ? dataviewDefinition.sql.own_filter_on : dataviewDefinition.sql.own_filter_off;
var sourceId = dataviewDefinition.source.id; // node.id
var layer = _.find(mapConfig.obj().layers, function(l) {
return l.options.source && (l.options.source.id === sourceId);
});
var queryRewriteData = layer && layer.options.query_rewrite_data;
if (queryRewriteData && dataviewDefinition.node.type === 'source') {
queryRewriteData = _.extend({}, queryRewriteData, {
filters: dataviewDefinition.node.filters,
unfiltered_query: dataviewDefinition.sql.own_filter_on
});
}
if (params.bbox) {
var bboxFilter = new BBoxFilter({column: 'the_geom', srid: 4326}, {bbox: params.bbox});
var bboxFilter = new BBoxFilter({column: 'the_geom_webmercator', srid: 3857}, {bbox: params.bbox});
query = bboxFilter.sql(query);
if ( queryRewriteData ) {
var bbox_filter_definition = {
type: 'bbox',
options: {
column: 'the_geom_webmercator',
srid: 3857
},
params: {
bbox: params.bbox
}
};
queryRewriteData = _.extend(queryRewriteData, { bbox_filter: bbox_filter_definition });
}
}
var dataviewFactory = DataviewFactoryWithOverviews.getFactory(
overviewsQueryRewriter, queryRewriteData, { bbox: params.bbox }
);
var overrideParams = _.reduce(_.pick(params, 'start', 'end', 'bins'),
function castNumbers(overrides, val, k) {
overrides[k] = Number.isFinite(+val) ? +val : val;
if (!Number.isFinite(+val)) {
throw new Error('Invalid number format for parameter \'' + k + '\'');
}
overrides[k] = +val;
return overrides;
},
{ownFilter: ownFilter}
);
var dataview = DataviewFactory.getDataview(query, dataviewDefinition);
var dataview = dataviewFactory.getDataview(query, dataviewDefinition);
dataview.getResult(pg, overrideParams, this);
},
function returnCallback(err, result) {
return callback(err, result, timer.getTimes());
return callback(err, result);
}
);
};
DataviewBackend.prototype.search = function (mapConfigProvider, user, params, callback) {
var self = this;
var timer = new Timer();
var dataviewName = params.dataviewName;
var mapConfig;
var dataviewDefinition;
step(
function getMapConfig() {
mapConfigProvider.getMapConfig(this);
},
function _getDataviewDefinition(err, _mapConfig) {
function runDataviewSearchQuery(err, mapConfig) {
assert.ifError(err);
mapConfig = _mapConfig;
var _dataviewDefinition = getDataviewDefinition(mapConfig.obj(), dataviewName);
if (!_dataviewDefinition) {
var dataviewDefinition = getDataviewDefinition(mapConfig.obj(), dataviewName);
if (!dataviewDefinition) {
throw new Error("Dataview '" + dataviewName + "' does not exists");
}
dataviewDefinition = _dataviewDefinition;
return dataviewDefinition;
},
function loadAnalysis(err) {
assert.ifError(err);
var analysisConfiguration = {
db: {
host: params.dbhost,
port: params.dbport,
dbname: params.dbname,
user: params.dbuser,
pass: params.dbpassword
},
batch: {
// TODO load this from configuration
endpoint: 'http://127.0.0.1:8080/api/v1/sql/job',
username: user,
apiKey: params.api_key
}
};
var sourceId = dataviewDefinition.source.id;
var analysisDefinition = getAnalysisDefinition(mapConfig.obj().analyses, sourceId);
var next = this;
self.analysisBackend.create(analysisConfiguration, analysisDefinition, function(err, analysis) {
if (err) {
return next(err);
}
var sourceId2Node = {};
var rootNode = analysis.getRoot();
if (rootNode.params && rootNode.params.id) {
sourceId2Node[rootNode.params.id] = rootNode;
}
analysis.getSortedNodes().forEach(function(node) {
if (node.params && node.params.id) {
sourceId2Node[node.params.id] = node;
}
});
var node = sourceId2Node[sourceId];
if (!node) {
return next(new Error('Analysis node not found for dataview'));
}
return next(null, node);
});
},
function runDataviewQuery(err, node) {
assert.ifError(err);
var pg = new PSQL(dbParamsFromReqParams(params));
var ownFilter = +params.own_filter;
ownFilter = !!ownFilter;
var query;
if (ownFilter) {
query = node.getQuery();
} else {
var applyFilters = {};
applyFilters[dataviewName] = false;
query = node.getQuery(applyFilters);
}
var query = (ownFilter) ? dataviewDefinition.sql.own_filter_on : dataviewDefinition.sql.own_filter_off;
if (params.bbox) {
var bboxFilter = new BBoxFilter({column: 'the_geom', srid: 4326}, {bbox: params.bbox});
@@ -237,23 +130,11 @@ DataviewBackend.prototype.search = function (mapConfigProvider, user, params, ca
dataview.search(pg, userQuery, this);
},
function returnCallback(err, result) {
return callback(err, result, timer.getTimes());
return callback(err, result);
}
);
};
function getAnalysisDefinition(mapConfigAnalyses, sourceId) {
mapConfigAnalyses = mapConfigAnalyses || [];
for (var i = 0; i < mapConfigAnalyses.length; i++) {
var analysisGraph = new camshaft.reference.AnalysisGraph(mapConfigAnalyses[i]);
var nodes = analysisGraph.getNodesWithId();
if (nodes.hasOwnProperty(sourceId)) {
return mapConfigAnalyses[i];
}
}
throw new Error('There is no associated analysis for the dataview source id');
}
function getDataviewDefinition(mapConfig, dataviewName) {
var dataviews = mapConfig.dataviews || {};
return dataviews[dataviewName];
@@ -277,4 +158,4 @@ function dbParamsFromReqParams(params) {
dbParams.dbname = params.dbname;
}
return dbParams;
}
}

View File

@@ -55,11 +55,9 @@ util.inherits(TemplateMaps, EventEmitter);
module.exports = TemplateMaps;
var o = TemplateMaps.prototype;
//--------------- PRIVATE METHODS --------------------------------
o._userTemplateLimit = function() {
TemplateMaps.prototype._userTemplateLimit = function() {
return this.opts.max_user_templates || 0;
};
@@ -70,7 +68,7 @@ o._userTemplateLimit = function() {
* @param redisArgs - the arguments for the redis function in an array
* @param callback - function to pass results too.
*/
o._redisCmd = function(redisFunc, redisArgs, callback) {
TemplateMaps.prototype._redisCmd = function(redisFunc, redisArgs, callback) {
var redisClient;
var that = this;
var db = that.db_signatures;
@@ -97,7 +95,7 @@ o._redisCmd = function(redisFunc, redisArgs, callback) {
var _reValidNameIdentifier = /^[a-z0-9][0-9a-z_\-]*$/i;
var _reValidPlaceholderIdentifier = /^[a-z][0-9a-z_]*$/i;
// jshint maxcomplexity:15
o._checkInvalidTemplate = function(template) {
TemplateMaps.prototype._checkInvalidTemplate = function(template) {
if ( template.version !== '0.0.1' ) {
return new Error("Unsupported template version " + template.version);
}
@@ -200,7 +198,7 @@ function templateDefaults(template) {
// @param callback function(err, tpl_id)
// Return template identifier (only valid for given user)
//
o.addTemplate = function(owner, template, callback) {
TemplateMaps.prototype.addTemplate = function(owner, template, callback) {
var self = this;
template = templateDefaults(template);
@@ -258,7 +256,7 @@ o.addTemplate = function(owner, template, callback) {
//
// @param callback function(err)
//
o.delTemplate = function(owner, tpl_id, callback) {
TemplateMaps.prototype.delTemplate = function(owner, tpl_id, callback) {
var self = this;
step(
function deleteTemplate() {
@@ -297,7 +295,8 @@ o.delTemplate = function(owner, tpl_id, callback) {
//
// @param callback function(err)
//
o.updTemplate = function(owner, tpl_id, template, callback) {
TemplateMaps.prototype.updTemplate = function(owner, tpl_id, template, callback) {
var self = this;
template = templateDefaults(template);
@@ -356,7 +355,7 @@ o.updTemplate = function(owner, tpl_id, template, callback) {
// @param callback function(err, tpl_id_list)
// Returns a list of template identifiers
//
o.listTemplates = function(owner, callback) {
TemplateMaps.prototype.listTemplates = function(owner, callback) {
this._redisCmd('HKEYS', [ this.key_usr_tpl({owner:owner}) ], callback);
};
@@ -370,7 +369,7 @@ o.listTemplates = function(owner, callback) {
// @param callback function(err, template)
// Return full template definition
//
o.getTemplate = function(owner, tpl_id, callback) {
TemplateMaps.prototype.getTemplate = function(owner, tpl_id, callback) {
var self = this;
step(
function getTemplate() {
@@ -386,7 +385,7 @@ o.getTemplate = function(owner, tpl_id, callback) {
);
};
o.isAuthorized = function(template, authTokens) {
TemplateMaps.prototype.isAuthorized = function(template, authTokens) {
if (!template) {
return false;
}
@@ -438,7 +437,7 @@ function _replaceVars (str, params) {
});
return str;
}
o.instance = function(template, params) {
TemplateMaps.prototype.instance = function(template, params) {
var all_params = {};
var phold = template.placeholders || {};
Object.keys(phold).forEach(function(k) {
@@ -500,7 +499,7 @@ o.instance = function(template, params) {
};
// Return a fingerPrint of the object
o.fingerPrint = function(template) {
TemplateMaps.prototype.fingerPrint = function(template) {
return crypto.createHash('md5')
.update(JSON.stringify(template))
.digest('hex')

View File

@@ -0,0 +1,110 @@
'use strict';
var dot = require('dot');
dot.templateSettings.strip = false;
function createTemplate(method) {
return dot.template([
'SELECT',
'min({{=it._column}}) min_val,',
'max({{=it._column}}) max_val,',
'avg({{=it._column}}) avg_val,',
method,
'FROM ({{=it._sql}}) _table_sql WHERE {{=it._column}} IS NOT NULL'
].join('\n'));
}
var methods = {
quantiles: 'CDB_QuantileBins(array_agg(distinct({{=it._column}}::numeric)), {{=it._buckets}}) as quantiles',
equal: 'CDB_EqualIntervalBins(array_agg({{=it._column}}::numeric), {{=it._buckets}}) as equal',
jenks: 'CDB_JenksBins(array_agg(distinct({{=it._column}}::numeric)), {{=it._buckets}}) as jenks',
headtails: 'CDB_HeadsTailsBins(array_agg(distinct({{=it._column}}::numeric)), {{=it._buckets}}) as headtails'
};
var methodTemplates = Object.keys(methods).reduce(function(methodTemplates, methodName) {
methodTemplates[methodName] = createTemplate(methods[methodName]);
return methodTemplates;
}, {});
methodTemplates.category = dot.template([
'WITH',
'categories AS (',
' SELECT {{=it._column}} AS category, count(1) AS value, row_number() OVER (ORDER BY count(1) desc) as rank',
' FROM ({{=it._sql}}) _cdb_aggregation_all',
' GROUP BY {{=it._column}}',
' ORDER BY 2 DESC, 1 ASC',
'),',
'agg_categories AS (',
' SELECT category',
' FROM categories',
' WHERE rank <= {{=it._buckets}}',
')',
'SELECT array_agg(category) AS category FROM agg_categories'
].join('\n'));
var STRATEGY = {
SPLIT: 'split',
EXACT: 'exact'
};
var method2strategy = {
headtails: STRATEGY.SPLIT,
category: STRATEGY.EXACT
};
function PostgresDatasource (psql, query) {
this.psql = psql;
this.query = query;
}
PostgresDatasource.prototype.getName = function () {
return 'PostgresDatasource';
};
PostgresDatasource.prototype.getRamp = function (column, buckets, method, callback) {
if (method && !methodTemplates.hasOwnProperty(method)) {
return callback(new Error(
'Invalid method "' + method + '", valid methods: [' + Object.keys(methodTemplates).join(',') + ']'
));
}
var methodName = method || 'quantiles';
var template = methodTemplates[methodName];
var query = template({ _column: column, _sql: this.query, _buckets: buckets });
this.psql.query(query, function (err, resultSet) {
if (err) {
return callback(err);
}
var result = getResult(resultSet);
var strategy = method2strategy[methodName];
var ramp = result[methodName] || [];
var stats = {
min_val: result.min_val,
max_val: result.max_val,
avg_val: result.avg_val
};
// Skip null values from ramp
// Generated turbo-carto won't be correct, but better to keep it working than failing
// TODO fix cartodb-postgres extension quantification functions
ramp = ramp.filter(function(value) { return value !== null; });
if (strategy !== STRATEGY.EXACT) {
ramp = ramp.sort(function(a, b) {
return a - b;
});
}
return callback(null, { ramp: ramp, strategy: strategy, stats: stats });
}, true); // use read-only transaction
};
function getResult(resultSet) {
resultSet = resultSet || {};
var result = resultSet.rows || [];
result = result[0] || {};
return result;
}
module.exports = PostgresDatasource;

View File

@@ -1,24 +1,17 @@
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 NamedMapMapConfigProvider = require('../models/mapconfig/provider/named-map-provider');
var templateName = require('../backends/template_maps').templateName;
var queue = require('queue-async');
var LruCache = require("lru-cache");
function NamedMapProviderCache(templateMaps, pgConnection, metadataBackend, analysisBackend, userLimitsApi,
overviewsAdapter, turboCartoAdapter) {
function NamedMapProviderCache(templateMaps, pgConnection, metadataBackend, userLimitsApi, mapConfigAdapter) {
this.templateMaps = templateMaps;
this.pgConnection = pgConnection;
this.metadataBackend = metadataBackend;
this.userLimitsApi = userLimitsApi;
this.namedLayersAdapter = new MapConfigNamedLayersAdapter(templateMaps);
this.analysisMapConfigAdapter = new AnalysisMapConfigAdapter(analysisBackend);
this.overviewsAdapter = overviewsAdapter;
this.turboCartoAdapter = turboCartoAdapter;
this.mapConfigAdapter = mapConfigAdapter;
this.providerCache = new LruCache({ max: 2000 });
}
@@ -36,10 +29,7 @@ NamedMapProviderCache.prototype.get = function(user, templateId, config, authTok
this.pgConnection,
this.metadataBackend,
this.userLimitsApi,
this.namedLayersAdapter,
this.overviewsAdapter,
this.turboCartoAdapter,
this.analysisMapConfigAdapter,
this.mapConfigAdapter,
user,
templateId,
config,

View File

@@ -0,0 +1,169 @@
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(authApi, pgConnection) {
BaseController.call(this, authApi, pgConnection);
}
util.inherits(AnalysesController, BaseController);
module.exports = AnalysesController;
AnalysesController.prototype.register = function(app) {
app.get(app.base_url_mapconfig + '/analyses/catalog', cors(), userMiddleware, this.catalog.bind(this));
};
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) {
var self = this;
var username = req.context.user;
step(
function reqParams() {
self.req2params(req, this);
},
function catalogQuery(err) {
assert.ifError(err);
var pg = new PSQL(dbParamsFromReqParams(req.params));
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;
}
self.sendError(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: []
};
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) {
if (err) {
return callback(err);
}
return callback(null, results);
}
);
}
function dbParamsFromReqParams(params) {
var dbParams = {};
if ( params.dbuser ) {
dbParams.user = params.dbuser;
}
if ( params.dbpassword ) {
dbParams.pass = params.dbpassword;
}
if ( params.dbhost ) {
dbParams.host = params.dbhost;
}
if ( params.dbport ) {
dbParams.port = params.dbport;
}
if ( params.dbname ) {
dbParams.dbname = params.dbname;
}
return dbParams;
}

View File

@@ -214,15 +214,15 @@ BaseController.prototype.sendError = function(req, res, err, label) {
statusCode = 200;
}
var errorResponseBody = { errors: allErrors.map(errorMessage) };
var errorResponseBody = {
errors: allErrors.map(errorMessage),
errors_with_context: allErrors.map(errorMessageWithContext)
};
this.send(req, res, errorResponseBody, statusCode);
};
function errorMessage(err) {
// See https://github.com/Vizzuality/Windshaft-cartodb/issues/68
var message = (_.isString(err) ? err : err.message) || 'Unknown error';
function stripConnectionInfo(message) {
// Strip connection info, if any
return message
// See https://github.com/CartoDB/Windshaft/issues/173
@@ -230,6 +230,32 @@ function errorMessage(err) {
// See https://travis-ci.org/CartoDB/Windshaft/jobs/20703062#L1644
.replace(/is the server.*encountered/im, 'encountered');
}
function errorMessage(err) {
// See https://github.com/Vizzuality/Windshaft-cartodb/issues/68
var message = (_.isString(err) ? err : err.message) || 'Unknown error';
return stripConnectionInfo(message);
}
function errorMessageWithContext(err) {
// See https://github.com/Vizzuality/Windshaft-cartodb/issues/68
var message = (_.isString(err) ? err : err.message) || 'Unknown error';
var error = {
type: err.type || 'unknown',
message: stripConnectionInfo(message),
};
for (var prop in err) {
// type & message are properties from Error's prototype and will be skipped
if (err.hasOwnProperty(prop)) {
error[prop] = err[prop];
}
}
return error;
}
module.exports.errorMessage = errorMessage;
function findStatusCode(err) {

View File

@@ -1,4 +1,5 @@
module.exports = {
Analyses: require('./analyses'),
Layergroup: require('./layergroup'),
Map: require('./map'),
NamedMaps: require('./named_maps'),

View File

@@ -10,7 +10,7 @@ var userMiddleware = require('../middleware/user');
var DataviewBackend = require('../backends/dataview');
var AnalysisStatusBackend = require('../backends/analysis-status');
var MapStoreMapConfigProvider = require('../models/mapconfig/map_store_provider');
var MapStoreMapConfigProvider = require('../models/mapconfig/provider/map-store-provider');
var QueryTables = require('cartodb-query-tables');
@@ -21,7 +21,6 @@ var QueryTables = require('cartodb-query-tables');
* @param {TileBackend} tileBackend
* @param {PreviewBackend} previewBackend
* @param {AttributesBackend} attributesBackend
* @param {WidgetBackend} widgetBackend
* @param {SurrogateKeysCache} surrogateKeysCache
* @param {UserLimitsApi} userLimitsApi
* @param {LayergroupAffectedTables} layergroupAffectedTables
@@ -29,7 +28,7 @@ var QueryTables = require('cartodb-query-tables');
* @constructor
*/
function LayergroupController(authApi, pgConnection, mapStore, tileBackend, previewBackend, attributesBackend,
widgetBackend, surrogateKeysCache, userLimitsApi, layergroupAffectedTables, analysisBackend) {
surrogateKeysCache, userLimitsApi, layergroupAffectedTables, analysisBackend) {
BaseController.call(this, authApi, pgConnection);
this.pgConnection = pgConnection;
@@ -37,7 +36,6 @@ function LayergroupController(authApi, pgConnection, mapStore, tileBackend, prev
this.tileBackend = tileBackend;
this.previewBackend = previewBackend;
this.attributesBackend = attributesBackend;
this.widgetBackend = widgetBackend;
this.surrogateKeysCache = surrogateKeysCache;
this.userLimitsApi = userLimitsApi;
this.layergroupAffectedTables = layergroupAffectedTables;
@@ -78,21 +76,19 @@ LayergroupController.prototype.register = function(app) {
// Undocumented/non-supported API endpoint methods.
// Use at your own peril.
app.get(app.base_url_mapconfig +
'/:token/:layer/widget/:widgetName', cors(), userMiddleware,
this.widget.bind(this));
app.get(app.base_url_mapconfig +
'/:token/:layer/widget/:widgetName/search', cors(), userMiddleware,
this.widgetSearch.bind(this));
app.get(app.base_url_mapconfig +
'/:token/dataview/:dataviewName', cors(), userMiddleware,
this.dataview.bind(this));
app.get(app.base_url_mapconfig +
'/:token/:layer/widget/:dataviewName', cors(), userMiddleware,
this.dataview.bind(this));
app.get(app.base_url_mapconfig +
'/:token/dataview/:dataviewName/search', cors(), userMiddleware,
this.dataviewSearch.bind(this));
app.get(app.base_url_mapconfig +
'/:token/:layer/widget/:dataviewName/search', cors(), userMiddleware,
this.dataviewSearch.bind(this));
app.get(app.base_url_mapconfig +
'/:token/analysis/node/:nodeId', cors(), userMiddleware,
@@ -181,62 +177,6 @@ LayergroupController.prototype.dataviewSearch = function(req, res) {
};
LayergroupController.prototype.widget = function(req, res) {
var self = this;
step(
function setupParams() {
self.req2params(req, this);
},
function retrieveList(err) {
assert.ifError(err);
var mapConfigProvider = new MapStoreMapConfigProvider(
self.mapStore, req.context.user, self.userLimitsApi, req.params
);
self.widgetBackend.getWidget(mapConfigProvider, req.params, this);
},
function finish(err, widget, stats) {
req.profiler.add(stats || {});
if (err) {
self.sendError(req, res, err, 'GET WIDGET');
} else {
self.sendResponse(req, res, widget, 200);
}
}
);
};
LayergroupController.prototype.widgetSearch = function(req, res) {
var self = this;
step(
function setupParams() {
self.req2params(req, this);
},
function retrieveList(err) {
assert.ifError(err);
var mapConfigProvider = new MapStoreMapConfigProvider(
self.mapStore, req.context.user, self.userLimitsApi, req.params
);
self.widgetBackend.search(mapConfigProvider, req.params, this);
},
function finish(err, searchResult, stats) {
req.profiler.add(stats || {});
if (err) {
self.sendError(req, res, err, 'GET WIDGET');
} else {
self.sendResponse(req, res, searchResult, 200);
}
}
);
};
LayergroupController.prototype.attributes = function(req, res) {
var self = this;
@@ -398,6 +338,8 @@ LayergroupController.prototype.staticMap = function(req, res, width, height, zoo
LayergroupController.prototype.sendResponse = function(req, res, body, status, headers) {
var self = this;
req.profiler.done('res');
res.set('Cache-Control', 'public,max-age=31536000');
// Set Last-Modified header

View File

@@ -1,4 +1,6 @@
var _ = require('underscore');
var dot = require('dot');
dot.templateSettings.strip = false;
var assert = require('assert');
var step = require('step');
var windshaft = require('windshaft');
@@ -15,10 +17,8 @@ var Datasource = windshaft.model.Datasource;
var NamedMapsCacheEntry = require('../cache/model/named_maps_entry');
var MapConfigNamedLayersAdapter = require('../models/mapconfig_named_layers_adapter');
var AnalysisMapConfigAdapter = require('../models/analysis-mapconfig-adapter');
var NamedMapMapConfigProvider = require('../models/mapconfig/named_map_provider');
var CreateLayergroupMapConfigProvider = require('../models/mapconfig/create_layergroup_provider');
var NamedMapMapConfigProvider = require('../models/mapconfig/provider/named-map-provider');
var CreateLayergroupMapConfigProvider = require('../models/mapconfig/provider/create-layergroup-provider');
/**
* @param {AuthApi} authApi
@@ -29,14 +29,11 @@ var CreateLayergroupMapConfigProvider = require('../models/mapconfig/create_laye
* @param {SurrogateKeysCache} surrogateKeysCache
* @param {UserLimitsApi} userLimitsApi
* @param {LayergroupAffectedTables} layergroupAffectedTables
* @param {MapConfigOverviewsAdapter} overviewsAdapter
* @param {TurboCartoAdapter} turboCartoAdapter
* @param {AnalysisBackend} analysisBackend
* @param {MapConfigAdapter} mapConfigAdapter
* @constructor
*/
function MapController(authApi, pgConnection, templateMaps, mapBackend, metadataBackend,
surrogateKeysCache, userLimitsApi, layergroupAffectedTables,
overviewsAdapter, turboCartoAdapter, analysisBackend) {
surrogateKeysCache, userLimitsApi, layergroupAffectedTables, mapConfigAdapter) {
BaseController.call(this, authApi, pgConnection);
@@ -47,11 +44,22 @@ function MapController(authApi, pgConnection, templateMaps, mapBackend, metadata
this.surrogateKeysCache = surrogateKeysCache;
this.userLimitsApi = userLimitsApi;
this.layergroupAffectedTables = layergroupAffectedTables;
this.turboCartoAdapter = turboCartoAdapter;
this.analysisMapConfigAdapter = new AnalysisMapConfigAdapter(analysisBackend);
this.namedLayersAdapter = new MapConfigNamedLayersAdapter(templateMaps);
this.overviewsAdapter = overviewsAdapter;
this.mapConfigAdapter = mapConfigAdapter;
this.resourcesUrlTemplates = null;
if (global.environment.resources_url_templates) {
var templates = global.environment.resources_url_templates;
if (templates.http) {
this.resourcesUrlTemplates = this.resourcesUrlTemplates || {};
this.resourcesUrlTemplates.http = dot.template(templates.http + '/{{=it.resource}}');
}
if (templates.https) {
this.resourcesUrlTemplates = this.resourcesUrlTemplates || {};
this.resourcesUrlTemplates.https = dot.template(templates.https + '/{{=it.resource}}');
}
}
}
util.inherits(MapController, BaseController);
@@ -132,16 +140,18 @@ MapController.prototype.create = function(req, res, prepareConfigFn) {
var self = this;
var mapConfig;
var analysesResults = [];
var context = {};
step(
function setupParams(){
self.req2params(req, this);
},
prepareConfigFn,
function prepareAnalysisLayers(err, requestMapConfig) {
function prepareAdapterMapConfig(err, requestMapConfig) {
assert.ifError(err);
var analysisConfiguration = {
context.analysisConfiguration = {
user: req.context.user,
db: {
host: req.params.dbhost,
port: req.params.dbport,
@@ -154,68 +164,12 @@ MapController.prototype.create = function(req, res, prepareConfigFn) {
apiKey: req.params.api_key
}
};
var filters = {};
if (req.params.filters) {
try {
filters = JSON.parse(req.params.filters);
} catch (e) {
// ignore
}
}
self.analysisMapConfigAdapter.getMapConfig(analysisConfiguration, requestMapConfig, filters, this);
self.mapConfigAdapter.getMapConfig(req.context.user, requestMapConfig, req.params, context, this);
},
function beforeLayergroupCreate(err, requestMapConfig, _analysesResults) {
function createLayergroup(err, requestMapConfig) {
assert.ifError(err);
var next = this;
analysesResults = _analysesResults;
self.namedLayersAdapter.getLayers(req.context.user, requestMapConfig.layers, self.pgConnection,
function(err, layers, datasource) {
if (err) {
return next(err);
}
if (layers) {
requestMapConfig.layers = layers;
}
return next(null, requestMapConfig, datasource);
}
);
},
function addOverviewsInformation(err, requestMapConfig, datasource) {
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, datasource);
});
},
function parseTurboCarto(err, requestMapConfig, datasource) {
assert.ifError(err);
var next = this;
self.turboCartoAdapter.getLayers(req.context.user, requestMapConfig.layers, function (err, layers) {
if (err) {
return next(err);
}
if (layers) {
requestMapConfig.layers = layers;
}
return next(null, requestMapConfig, datasource);
});
},
function createLayergroup(err, requestMapConfig, datasource) {
assert.ifError(err);
mapConfig = new MapConfig(requestMapConfig, datasource || Datasource.EmptyDatasource());
var datasource = context.datasource || Datasource.EmptyDatasource();
mapConfig = new MapConfig(requestMapConfig, datasource);
self.mapBackend.createLayergroup(
mapConfig, req.params,
new CreateLayergroupMapConfigProvider(mapConfig, req.context.user, self.userLimitsApi, req.params),
@@ -224,14 +178,34 @@ MapController.prototype.create = function(req, res, prepareConfigFn) {
},
function afterLayergroupCreate(err, layergroup) {
assert.ifError(err);
self.afterLayergroupCreate(req, res, mapConfig, analysesResults, layergroup, this);
self.afterLayergroupCreate(req, res, mapConfig, layergroup, context.analysesResults, this);
},
function finish(err, layergroup) {
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;
}
self.sendError(req, res, err, 'ANONYMOUS LAYERGROUP');
} else {
addWidgetsUrl(req.context.user, layergroup);
var analysesResults = context.analysesResults || [];
self.addDataviewsAndWidgetsUrls(req.context.user, layergroup, mapConfig.obj());
self.addAnalysesMetadata(req.context.user, layergroup, analysesResults, true);
addContextMetadata(layergroup, mapConfig.obj(), context);
res.set('X-Layergroup-Id', layergroup.layergroupid);
self.send(req, res, layergroup, 200);
}
@@ -239,6 +213,17 @@ MapController.prototype.create = function(req, res, prepareConfigFn) {
);
};
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.instantiateTemplate = function(req, res, prepareParamsFn) {
var self = this;
@@ -261,10 +246,7 @@ MapController.prototype.instantiateTemplate = function(req, res, prepareParamsFn
self.pgConnection,
self.metadataBackend,
self.userLimitsApi,
self.namedLayersAdapter,
self.overviewsAdapter,
self.turboCartoAdapter,
self.analysisMapConfigAdapter,
self.mapConfigAdapter,
cdbuser,
req.params.template_id,
templateParams,
@@ -284,7 +266,7 @@ MapController.prototype.instantiateTemplate = function(req, res, prepareParamsFn
},
function afterLayergroupCreate(err, layergroup) {
assert.ifError(err);
self.afterLayergroupCreate(req, res, mapConfig, [], layergroup, this);
self.afterLayergroupCreate(req, res, mapConfig, layergroup, mapConfigProvider.analysesResults, this);
},
function finishTemplateInstantiation(err, layergroup) {
if (err) {
@@ -293,9 +275,10 @@ MapController.prototype.instantiateTemplate = function(req, res, prepareParamsFn
var templateHash = self.templateMaps.fingerPrint(mapConfigProvider.template).substring(0, 8);
layergroup.layergroupid = cdbuser + '@' + templateHash + '@' + layergroup.layergroupid;
addWidgetsUrl(cdbuser, layergroup);
addDataviewsUrls(cdbuser, layergroup, mapConfig.obj());
addAnalysesMetadata(cdbuser, layergroup, mapConfigProvider.analysesResults);
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()));
@@ -306,8 +289,7 @@ MapController.prototype.instantiateTemplate = function(req, res, prepareParamsFn
);
};
MapController.prototype.afterLayergroupCreate = function(req, res, mapconfig, analysesResults, layergroup, callback) {
MapController.prototype.afterLayergroupCreate = function(req, res, mapconfig, layergroup, analysesResults, callback) {
var self = this;
var username = req.context.user;
@@ -366,14 +348,13 @@ MapController.prototype.afterLayergroupCreate = function(req, res, mapconfig, an
// feed affected tables cache so it can be reused from, for instance, layergroup controller
self.layergroupAffectedTables.set(dbName, layergroupId, result);
// last update for layergroup cache buster
layergroup.layergroupid = layergroup.layergroupid + ':' + result.getLastUpdatedAt();
layergroup.last_updated = new Date(result.getLastUpdatedAt()).toISOString();
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();
// TODO this should take into account several URL patterns
addWidgetsUrl(username, layergroup);
addDataviewsUrls(username, layergroup, mapconfig.obj());
addAnalysesMetadata(username, layergroup, analysesResults, true);
if (req.method === 'GET') {
var ttl = global.environment.varnish.layergroupTtl || 86400;
res.set('Cache-Control', 'public,max-age='+ttl+',must-revalidate');
@@ -392,61 +373,90 @@ MapController.prototype.afterLayergroupCreate = function(req, res, mapconfig, an
);
};
function addAnalysesMetadata(username, layergroup, analysesResults, includeQuery) {
function getLastUpdatedTime(analysesResults, lastUpdateTime) {
if (!Array.isArray(analysesResults)) {
return lastUpdateTime;
}
return analysesResults.reduce(function(lastUpdateTime, analysis) {
return analysis.getNodes().reduce(function(lastNodeUpdatedAtTime, node) {
var nodeUpdatedAtDate = node.getUpdatedAt();
var nodeUpdatedTimeAt = (nodeUpdatedAtDate && nodeUpdatedAtDate.getTime()) || 0;
return nodeUpdatedTimeAt > lastNodeUpdatedAtTime ? nodeUpdatedTimeAt : lastNodeUpdatedAtTime;
}, lastUpdateTime);
}, lastUpdateTime);
}
MapController.prototype.addAnalysesMetadata = function(username, layergroup, analysesResults, includeQuery) {
includeQuery = includeQuery || false;
analysesResults = analysesResults || [];
layergroup.metadata.analyses = [];
analysesResults.forEach(function(analysis) {
var nodes = analysis.getSortedNodes();
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();
nodesIdMap[node.params.id] = {
var nodeRepr = {
status: node.getStatus(),
url: getUrls(username, nodeResource)
url: this.getUrls(username, nodeResource)
};
if (includeQuery) {
nodesIdMap[node.params.id].query = node.getQuery();
nodeRepr.query = node.getQuery();
}
if (node.getStatus() === 'failed') {
nodeRepr.error_message = node.getErrorMessage();
}
nodesIdMap[node.params.id] = nodeRepr;
}
return nodesIdMap;
}, {})
}.bind(this), {})
});
});
}
}.bind(this));
};
function addDataviewsUrls(username, layergroup, mapConfig) {
// TODO this should take into account several URL patterns
MapController.prototype.addDataviewsAndWidgetsUrls = function(username, layergroup, mapConfig) {
this.addDataviewsUrls(username, layergroup, mapConfig);
this.addWidgetsUrl(username, layergroup, mapConfig);
};
MapController.prototype.addDataviewsUrls = function(username, layergroup, mapConfig) {
layergroup.metadata.dataviews = layergroup.metadata.dataviews || {};
var dataviews = mapConfig.dataviews || {};
Object.keys(dataviews).forEach(function(dataviewName) {
var resource = layergroup.layergroupid + '/dataview/' + dataviewName;
layergroup.metadata.dataviews[dataviewName] = {
url: getUrls(username, resource)
url: this.getUrls(username, resource)
};
});
}
}.bind(this));
};
function addWidgetsUrl(username, layergroup) {
if (layergroup.metadata && Array.isArray(layergroup.metadata.layers)) {
MapController.prototype.addWidgetsUrl = function(username, layergroup, mapConfig) {
if (layergroup.metadata && Array.isArray(layergroup.metadata.layers) && Array.isArray(mapConfig.layers)) {
layergroup.metadata.layers = layergroup.metadata.layers.map(function(layer, layerIndex) {
if (layer.widgets) {
Object.keys(layer.widgets).forEach(function(widgetName) {
var mapConfigLayer = mapConfig.layers[layerIndex];
if (mapConfigLayer.options && mapConfigLayer.options.widgets) {
layer.widgets = layer.widgets || {};
Object.keys(mapConfigLayer.options.widgets).forEach(function(widgetName) {
var resource = layergroup.layergroupid + '/' + layerIndex + '/widget/' + widgetName;
layer.widgets[widgetName].url = getUrls(username, resource);
});
layer.widgets[widgetName] = {
type: mapConfigLayer.options.widgets[widgetName].type,
url: this.getUrls(username, resource)
};
}.bind(this));
}
return layer;
});
}.bind(this));
}
};
}
function getUrls(username, resource) {
MapController.prototype.getUrls = function(username, resource) {
if (this.resourcesUrlTemplates) {
return this.getUrlsFromTemplate(username, resource);
}
var cdnUrl = global.environment.serverMetadata && global.environment.serverMetadata.cdn_url;
if (cdnUrl) {
return {
@@ -459,4 +469,29 @@ function getUrls(username, resource) {
http: 'http://' + username + '.' + 'localhost.lan:' + port + '/api/v1/map/' + resource
};
}
}
};
MapController.prototype.getUrlsFromTemplate = function(username, resource) {
var urls = {};
var cdnUrl = global.environment.serverMetadata && global.environment.serverMetadata.cdn_url || {};
if (this.resourcesUrlTemplates.http) {
urls.http = this.resourcesUrlTemplates.http({
cdn_url: cdnUrl.http,
user: username,
port: global.environment.port,
resource: resource
});
}
if (this.resourcesUrlTemplates.https) {
urls.https = this.resourcesUrlTemplates.https({
cdn_url: cdnUrl.https,
user: username,
port: global.environment.port,
resource: resource
});
}
return urls;
};

View File

@@ -137,9 +137,15 @@ NamedMapsController.prototype.staticMap = function(req, res) {
this
);
},
function prepareImageOptions(err, _namedMapProvider) {
function prepareLayerVisibility(err, _namedMapProvider) {
assert.ifError(err);
namedMapProvider = _namedMapProvider;
self.prepareLayerFilterFromPreviewLayers(cdbUser, req, namedMapProvider, this);
},
function prepareImageOptions(err) {
assert.ifError(err);
self.getStaticImageOptions(cdbUser, req.params, namedMapProvider, this);
},
function getImage(err, imageOpts) {
@@ -184,6 +190,45 @@ NamedMapsController.prototype.staticMap = function(req, res) {
);
};
NamedMapsController.prototype.prepareLayerFilterFromPreviewLayers = function (user, req, namedMapProvider, callback) {
var self = this;
namedMapProvider.getTemplate(function (err, template) {
if (err) {
return callback(err);
}
if (!template || !template.view || !template.view.preview_layers) {
return callback();
}
var previewLayers = template.view.preview_layers;
var layerVisibilityFilter = [];
template.layergroup.layers.forEach(function (layer, index) {
if (previewLayers[''+index] !== false && previewLayers[layer.id] !== false) {
layerVisibilityFilter.push(''+index);
}
});
if (!layerVisibilityFilter.length) {
return callback();
}
// overwrites 'all' default filter
req.params.layer = layerVisibilityFilter.join(',');
// recreates the provider
self.namedMapProviderCache.get(
user,
req.params.template_id,
req.query.config,
req.query.auth_token,
req.params,
callback
);
});
};
var DEFAULT_ZOOM_CENTER = {
zoom: 1,
center: {

View File

@@ -1,21 +1,13 @@
var windshaft = require('windshaft');
var HealthCheck = require('../monitoring/health_check');
var WELCOME_MSG = "This is the CartoDB Maps API, " +
"see the documentation at http://docs.cartodb.com/cartodb-platform/maps-api.html";
var versions = {
windshaft: windshaft.version,
grainstore: windshaft.grainstore.version(),
node_mapnik: windshaft.mapnik.version,
mapnik: windshaft.mapnik.versions.mapnik,
windshaft_cartodb: require('../../../package.json').version
};
function ServerInfoController() {
function ServerInfoController(versions) {
this.healthConfig = global.environment.health || {};
this.healthCheck = new HealthCheck(global.environment.disabled_file);
this.versions = versions || {};
}
module.exports = ServerInfoController;
@@ -31,7 +23,7 @@ ServerInfoController.prototype.welcome = function(req, res) {
};
ServerInfoController.prototype.version = function(req, res) {
res.status(200).send(versions);
res.status(200).send(this.versions);
};
ServerInfoController.prototype.health = function(req, res) {

View File

@@ -19,25 +19,38 @@ var rankedCategoriesQueryTpl = dot.template([
' SELECT {{=it._column}} AS category, {{=it._aggregation}} AS value,',
' row_number() OVER (ORDER BY {{=it._aggregation}} desc) as rank',
' FROM ({{=it._query}}) _cdb_aggregation_all',
' {{?it._aggregationColumn!==null}}WHERE {{=it._aggregationColumn}} IS NOT NULL{{?}}',
' GROUP BY {{=it._column}}',
' ORDER BY 2 DESC',
')'
].join('\n'));
var categoriesSummaryQueryTpl = dot.template([
'categories_summary AS(',
' SELECT count(1) categories_count, max(value) max_val, min(value) min_val',
var categoriesSummaryMinMaxQueryTpl = dot.template([
'categories_summary_min_max AS(',
' SELECT max(value) max_val, min(value) min_val',
' FROM categories',
')'
].join('\n'));
var categoriesSummaryCountQueryTpl = dot.template([
'categories_summary_count AS(',
' SELECT count(1) AS categories_count',
' FROM (',
' SELECT {{=it._column}} AS category',
' FROM ({{=it._query}}) _cdb_categories',
' GROUP BY {{=it._column}}',
' ) _cdb_categories_count',
')'
].join('\n'));
var rankedAggregationQueryTpl = dot.template([
'SELECT CAST(category AS text), value, false as agg, nulls_count, min_val, max_val, count, categories_count',
' FROM categories, summary, categories_summary',
' FROM categories, summary, categories_summary_min_max, categories_summary_count',
' WHERE rank < {{=it._limit}}',
'UNION ALL',
'SELECT \'Other\' category, sum(value), true as agg, nulls_count, min_val, max_val, count, categories_count',
' FROM categories, summary, categories_summary',
'SELECT \'Other\' category, {{=it._aggregationFn}}(value) as value, true as agg, nulls_count, min_val, max_val,',
' count, categories_count',
' FROM categories, summary, categories_summary_min_max, categories_summary_count',
' WHERE rank >= {{=it._limit}}',
'GROUP BY nulls_count, min_val, max_val, count, categories_count'
].join('\n'));
@@ -45,7 +58,7 @@ var rankedAggregationQueryTpl = dot.template([
var aggregationQueryTpl = dot.template([
'SELECT CAST({{=it._column}} AS text) AS category, {{=it._aggregation}} AS value, false as agg,',
' nulls_count, min_val, max_val, count, categories_count',
'FROM ({{=it._query}}) _cdb_aggregation_all, summary, categories_summary',
'FROM ({{=it._query}}) _cdb_aggregation_all, summary, categories_summary_min_max, categories_summary_count',
'GROUP BY category, nulls_count, min_val, max_val, count, categories_count',
'ORDER BY value DESC'
].join('\n'));
@@ -54,7 +67,10 @@ var CATEGORIES_LIMIT = 6;
var VALID_OPERATIONS = {
count: [],
sum: ['aggregationColumn']
sum: ['aggregationColumn'],
avg: ['aggregationColumn'],
min: ['aggregationColumn'],
max: ['aggregationColumn']
};
var TYPE = 'aggregation';
@@ -102,7 +118,7 @@ Aggregation.prototype.constructor = Aggregation;
module.exports = Aggregation;
Aggregation.prototype.sql = function(psql, filters, override, callback) {
Aggregation.prototype.sql = function(psql, override, callback) {
if (!callback) {
callback = override;
override = {};
@@ -111,24 +127,10 @@ Aggregation.prototype.sql = function(psql, filters, override, callback) {
var _query = this.query;
var aggregationSql;
if (!!override.ownFilter) {
aggregationSql = [
"WITH",
[
summaryQueryTpl({
_query: _query,
_column: this.column
}),
rankedCategoriesQueryTpl({
_query: _query,
_column: this.column,
_aggregation: this.getAggregationSql()
}),
categoriesSummaryQueryTpl({
_query: _query,
_column: this.column
})
].join(',\n'),
this.getCategoriesCTESql(_query, this.column, this.aggregation, this.aggregationColumn),
aggregationQueryTpl({
_query: _query,
_column: this.column,
@@ -138,25 +140,11 @@ Aggregation.prototype.sql = function(psql, filters, override, callback) {
].join('\n');
} else {
aggregationSql = [
"WITH",
[
summaryQueryTpl({
_query: _query,
_column: this.column
}),
rankedCategoriesQueryTpl({
_query: _query,
_column: this.column,
_aggregation: this.getAggregationSql()
}),
categoriesSummaryQueryTpl({
_query: _query,
_column: this.column
})
].join(',\n'),
this.getCategoriesCTESql(_query, this.column, this.aggregation, this.aggregationColumn),
rankedAggregationQueryTpl({
_query: _query,
_column: this.column,
_aggregationFn: this.aggregation !== 'count' ? this.aggregation : 'sum',
_limit: CATEGORIES_LIMIT
})
].join('\n');
@@ -167,6 +155,32 @@ Aggregation.prototype.sql = function(psql, filters, override, callback) {
return callback(null, aggregationSql);
};
Aggregation.prototype.getCategoriesCTESql = function(query, column, aggregation, aggregationColumn) {
return [
"WITH",
[
summaryQueryTpl({
_query: query,
_column: column
}),
rankedCategoriesQueryTpl({
_query: query,
_column: column,
_aggregation: this.getAggregationSql(),
_aggregationColumn: aggregation !== 'count' ? aggregationColumn : null
}),
categoriesSummaryMinMaxQueryTpl({
_query: query,
_column: column
}),
categoriesSummaryCountQueryTpl({
_query: query,
_column: column
})
].join(',\n')
].join('\n');
};
var aggregationFnQueryTpl = dot.template('{{=it._aggregationFn}}({{=it._aggregationColumn}})');
Aggregation.prototype.getAggregationSql = function() {
return aggregationFnQueryTpl({
@@ -198,6 +212,7 @@ Aggregation.prototype.format = function(result) {
}
return {
aggregation: this.aggregation,
count: count,
nulls: nulls,
min: minValue,

View File

@@ -11,7 +11,7 @@ var DataviewFactory = {
if (!this.dataviews[type]) {
throw new Error('Invalid dataview type: "' + type + '"');
}
return new this.dataviews[type](query, dataviewDefinition.options);
return new this.dataviews[type](query, dataviewDefinition.options, dataviewDefinition.sql);
}
};

View File

@@ -56,7 +56,7 @@ Formula.prototype.constructor = Formula;
module.exports = Formula;
Formula.prototype.sql = function(psql, filters, override, callback) {
Formula.prototype.sql = function(psql, override, callback) {
if (!callback) {
callback = override;
override = {};

View File

@@ -109,12 +109,13 @@ var TYPE = 'histogram';
}
}
*/
function Histogram(query, options) {
function Histogram(query, options, queries) {
if (!_.isString(options.column)) {
throw new Error('Histogram expects `column` in widget options');
}
this.query = query;
this.queries = queries;
this.column = options.column;
this.bins = options.bins;
@@ -143,7 +144,7 @@ Histogram.prototype.sql = function(psql, override, callback) {
var _column = this.column;
var columnTypeQuery = columnTypeQueryTpl({
column: _column, query: this.query
column: _column, query: this.queries.no_filters
});
if (this._columnType === null) {
@@ -174,8 +175,8 @@ Histogram.prototype.sql = function(psql, override, callback) {
basicsQuery = overrideBasicsQueryTpl({
_query: _query,
_column: _column,
_start: override.start,
_end: override.end
_start: getBinStart(override),
_end: getBinEnd(override)
});
binsQuery = [
@@ -248,7 +249,7 @@ Histogram.prototype.format = function(result, override) {
width = firstRow.bin_width || width;
avg = firstRow.avg_val;
nulls = firstRow.nulls_count;
binsStart = override.hasOwnProperty('start') ? override.start : firstRow.min;
binsStart = override.hasOwnProperty('start') ? getBinStart(override) : firstRow.min;
buckets = result.rows.map(function(row) {
return _.omit(row, 'bins_number', 'bin_width', 'nulls_count', 'avg_val');
@@ -266,9 +267,19 @@ Histogram.prototype.format = function(result, override) {
};
function getBinStart(override) {
if (override.hasOwnProperty('start') && override.hasOwnProperty('end')) {
return Math.min(override.start, override.end);
}
return override.start || 0;
}
function getBinEnd(override) {
if (override.hasOwnProperty('start') && override.hasOwnProperty('end')) {
return Math.max(override.start, override.end);
}
return override.end || 0;
}
function getBinsCount(override) {
return override.bins || 0;
}

View File

@@ -34,7 +34,7 @@ List.prototype.constructor = List;
module.exports = List;
List.prototype.sql = function(psql, filters, override, callback) {
List.prototype.sql = function(psql, override, callback) {
if (!callback) {
callback = override;
}

View File

@@ -0,0 +1,163 @@
var BaseOverviewsDataview = require('./base');
var BaseDataview = require('../aggregation');
var dot = require('dot');
dot.templateSettings.strip = false;
var summaryQueryTpl = dot.template([
'summary AS (',
' SELECT',
' sum(_feature_count) AS count,',
' sum(CASE WHEN {{=it._column}} IS NULL THEN 1 ELSE 0 END) AS nulls_count',
' FROM ({{=it._query}}) _cdb_aggregation_nulls',
')'
].join('\n'));
var rankedCategoriesQueryTpl = dot.template([
'categories AS(',
' SELECT {{=it._column}} AS category, {{=it._aggregation}} AS value,',
' row_number() OVER (ORDER BY {{=it._aggregation}} desc) as rank',
' FROM ({{=it._query}}) _cdb_aggregation_all',
' {{?it._aggregationColumn!==null}}WHERE {{=it._aggregationColumn}} IS NOT NULL{{?}}',
' GROUP BY {{=it._column}}',
' ORDER BY 2 DESC',
')'
].join('\n'));
var categoriesSummaryMinMaxQueryTpl = dot.template([
'categories_summary_min_max AS(',
' SELECT max(value) max_val, min(value) min_val',
' FROM categories',
')'
].join('\n'));
var categoriesSummaryCountQueryTpl = dot.template([
'categories_summary_count AS(',
' SELECT count(1) AS categories_count',
' FROM (',
' SELECT {{=it._column}} AS category',
' FROM ({{=it._query}}) _cdb_categories',
' GROUP BY {{=it._column}}',
' ) _cdb_categories_count',
')'
].join('\n'));
var rankedAggregationQueryTpl = dot.template([
'SELECT CAST(category AS text), value, false as agg, nulls_count, min_val, max_val, count, categories_count',
' FROM categories, summary, categories_summary_min_max, categories_summary_count',
' WHERE rank < {{=it._limit}}',
'UNION ALL',
'SELECT \'Other\' category, sum(value), true as agg, nulls_count, min_val, max_val, count, categories_count',
' FROM categories, summary, categories_summary_min_max, categories_summary_count',
' WHERE rank >= {{=it._limit}}',
'GROUP BY nulls_count, min_val, max_val, count, categories_count'
].join('\n'));
var aggregationQueryTpl = dot.template([
'SELECT CAST({{=it._column}} AS text) AS category, {{=it._aggregation}} AS value, false as agg,',
' nulls_count, min_val, max_val, count, categories_count',
'FROM ({{=it._query}}) _cdb_aggregation_all, summary, categories_summary_min_max, categories_summary_count',
'GROUP BY category, nulls_count, min_val, max_val, count, categories_count',
'ORDER BY value DESC'
].join('\n'));
var CATEGORIES_LIMIT = 6;
function Aggregation(query, options, queryRewriter, queryRewriteData, params) {
BaseOverviewsDataview.call(this, query, options, BaseDataview, queryRewriter, queryRewriteData, params);
this.query = query;
this.column = options.column;
this.aggregation = options.aggregation;
this.aggregationColumn = options.aggregationColumn;
}
Aggregation.prototype = Object.create(BaseOverviewsDataview.prototype);
Aggregation.prototype.constructor = Aggregation;
module.exports = Aggregation;
Aggregation.prototype.sql = function(psql, override, callback) {
if (!callback) {
callback = override;
override = {};
}
var _query = this.rewrittenQuery(this.query);
var aggregationSql;
if (!!override.ownFilter) {
aggregationSql = [
"WITH",
[
summaryQueryTpl({
_query: _query,
_column: this.column
}),
rankedCategoriesQueryTpl({
_query: _query,
_column: this.column,
_aggregation: this.getAggregationSql(),
_aggregationColumn: this.aggregation !== 'count' ? this.aggregationColumn : null
}),
categoriesSummaryMinMaxQueryTpl({
_query: _query,
_column: this.column
}),
categoriesSummaryCountQueryTpl({
_query: _query,
_column: this.column
})
].join(',\n'),
aggregationQueryTpl({
_query: _query,
_column: this.column,
_aggregation: this.getAggregationSql(),
_limit: CATEGORIES_LIMIT
})
].join('\n');
} else {
aggregationSql = [
"WITH",
[
summaryQueryTpl({
_query: _query,
_column: this.column
}),
rankedCategoriesQueryTpl({
_query: _query,
_column: this.column,
_aggregation: this.getAggregationSql(),
_aggregationColumn: this.aggregation !== 'count' ? this.aggregationColumn : null
}),
categoriesSummaryMinMaxQueryTpl({
_query: _query,
_column: this.column
}),
categoriesSummaryCountQueryTpl({
_query: _query,
_column: this.column
})
].join(',\n'),
rankedAggregationQueryTpl({
_query: _query,
_column: this.column,
_limit: CATEGORIES_LIMIT
})
].join('\n');
}
return callback(null, aggregationSql);
};
var aggregationFnQueryTpl = {
count: dot.template('sum(_feature_count)'),
sum: dot.template('sum({{=it._aggregationColumn}}*_feature_count)')
};
Aggregation.prototype.getAggregationSql = function() {
return aggregationFnQueryTpl[this.aggregation]({
_aggregationFn: this.aggregation,
_aggregationColumn: this.aggregationColumn || 1
});
};

View File

@@ -0,0 +1,88 @@
var _ = require('underscore');
var BaseDataview = require('../base');
function BaseOverviewsDataview(query, queryOptions, BaseDataview, queryRewriter, queryRewriteData, options) {
this.BaseDataview = BaseDataview;
this.query = query;
this.queryOptions = queryOptions;
this.queryRewriter = queryRewriter;
this.queryRewriteData = queryRewriteData;
this.options = options;
this.baseDataview = new this.BaseDataview(this.query, this.queryOptions);
}
module.exports = BaseOverviewsDataview;
BaseOverviewsDataview.prototype = new BaseDataview();
BaseOverviewsDataview.prototype.constructor = BaseOverviewsDataview;
// TODO: parameterized these settings
var SETTINGS = {
// use overviews as a default fallback strategy
defaultOverviews: false,
// minimum ratio of bounding box size to grid size
// (this would ideally be based on the viewport size in pixels)
zoomLevelFactor: 1024.0
};
// Compute zoom level so that the the resolution grid size of the
// selected overview is smaller (zoomLevelFactor times smaller at least)
// than the bounding box size.
BaseOverviewsDataview.prototype.zoomLevelForBbox = function(bbox) {
var pxPerTile = 256.0;
var earthWidth = 360.0;
// TODO: now we assume overviews are computed for 1-pixel tolerance;
// should use extended overviews metadata to compute this properly.
if ( bbox ) {
var bboxValues = _.map(bbox.split(','), function(v) { return +v; });
var w = Math.abs(bboxValues[2]-bboxValues[0]);
var h = Math.abs(bboxValues[3]-bboxValues[1]);
var maxDim = Math.min(w, h);
// Find minimum suitable z
// note that the QueryRewirter will use the minimum level overview
// of level >= z if it exists, and otherwise the base table
var z = Math.ceil(-Math.log(maxDim*pxPerTile/earthWidth/SETTINGS.zoomLevelFactor)/Math.log(2.0));
return Math.max(z, 0);
}
return 0;
};
BaseOverviewsDataview.prototype.rewrittenQuery = function(query) {
var zoom_level = this.zoomLevelForBbox(this.options.bbox);
return this.queryRewriter.query(query, this.queryRewriteData, { zoom_level: zoom_level });
};
// Default behaviour
BaseOverviewsDataview.prototype.defaultSql = function(psql, override, callback) {
var query = this.query;
var dataview = this.baseDataview;
if ( SETTINGS.defaultOverviews ) {
query = this.rewrittenQuery(query);
dataview = new this.BaseDataview(query, this.queryOptions);
}
return dataview.sql(psql, override, callback);
};
// default implementation that can be override in derived classes:
BaseOverviewsDataview.prototype.sql = function(psql, override, callback) {
return this.defaultSql(psql, override, callback);
};
BaseOverviewsDataview.prototype.search = function(psql, userQuery, callback) {
return this.baseDataview.search(psql, userQuery, callback);
};
BaseOverviewsDataview.prototype.format = function(result) {
return this.baseDataview.format(result);
};
BaseOverviewsDataview.prototype.getType = function() {
return this.baseDataview.getType();
};
BaseOverviewsDataview.prototype.toString = function() {
return this.baseDataview.toString();
};

View File

@@ -0,0 +1,33 @@
var parentFactory = require('../factory');
var dataviews = require('./');
function OverviewsDataviewFactory(queryRewriter, queryRewriteData, options) {
this.queryRewriter = queryRewriter;
this.queryRewriteData = queryRewriteData;
this.options = options;
}
OverviewsDataviewFactory.prototype.getDataview = function(query, dataviewDefinition) {
var type = dataviewDefinition.type;
var dataviews = OverviewsDataviewMetaFactory.dataviews;
if ( !this.queryRewriter || !this.queryRewriteData || !dataviews[type] ) {
return parentFactory.getDataview(query, dataviewDefinition);
}
return new dataviews[type](
query, dataviewDefinition.options, this.queryRewriter, this.queryRewriteData, this.options,
dataviewDefinition.sql
);
};
var OverviewsDataviewMetaFactory = {
dataviews: Object.keys(dataviews).reduce(function(allDataviews, dataviewClassName) {
allDataviews[dataviewClassName.toLowerCase()] = dataviews[dataviewClassName];
return allDataviews;
}, {}),
getFactory: function(queryRewriter, queryRewriteData, options) {
return new OverviewsDataviewFactory(queryRewriter, queryRewriteData, options);
},
};
module.exports = OverviewsDataviewMetaFactory;

View File

@@ -0,0 +1,56 @@
var BaseOverviewsDataview = require('./base');
var BaseDataview = require('../formula');
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',
'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',
'FROM ({{=it._query}}) _cdb_formula'
].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',
'FROM ({{=it._query}}) _cdb_formula'
].join('\n')),
};
function Formula(query, options, queryRewriter, queryRewriteData, params) {
BaseOverviewsDataview.call(this, query, options, BaseDataview, queryRewriter, queryRewriteData, params);
this.column = options.column || '1';
this.operation = options.operation;
}
Formula.prototype = Object.create(BaseOverviewsDataview.prototype);
Formula.prototype.constructor = Formula;
module.exports = Formula;
Formula.prototype.sql = function(psql, override, callback) {
var formulaQueryTpl = formulaQueryTpls[this.operation];
if ( formulaQueryTpl ) {
// supported formula for use with overviews
var formulaSql = formulaQueryTpl({
_query: this.rewrittenQuery(this.query),
_operation: this.operation,
_column: this.column
});
callback = callback || override;
return callback(null, formulaSql);
}
// default behaviour
return this.defaultSql(psql, override, callback);
};

View File

@@ -0,0 +1,219 @@
var _ = require('underscore');
var BaseOverviewsDataview = require('./base');
var BaseDataview = require('../histogram');
var dot = require('dot');
dot.templateSettings.strip = false;
var columnTypeQueryTpl = dot.template(
'SELECT pg_typeof({{=it.column}})::oid FROM ({{=it.query}}) _cdb_histogram_column_type limit 1'
);
var BIN_MIN_NUMBER = 6;
var BIN_MAX_NUMBER = 48;
var basicsQueryTpl = dot.template([
'basics AS (',
' SELECT',
' max({{=it._column}}) AS max_val, min({{=it._column}}) AS min_val,',
' sum({{=it._column}}*_feature_count)/sum(_feature_count) AS avg_val, sum(_feature_count) AS total_rows',
' FROM ({{=it._query}}) _cdb_basics',
')'
].join(' \n'));
var overrideBasicsQueryTpl = dot.template([
'basics AS (',
' SELECT',
' max({{=it._end}}) AS max_val, min({{=it._start}}) AS min_val,',
' sum({{=it._column}}*_feature_count)/sum(_feature_count) AS avg_val, sum(_feature_count) AS total_rows',
' FROM ({{=it._query}}) _cdb_basics',
')'
].join('\n'));
var iqrQueryTpl = dot.template([
'iqrange AS (',
' SELECT max(quartile_max) - min(quartile_max) AS iqr',
' FROM (',
' SELECT quartile, max(_cdb_iqr_column) AS quartile_max from (',
' SELECT {{=it._column}} AS _cdb_iqr_column, ntile(4) over (order by {{=it._column}}',
' ) AS quartile',
' FROM ({{=it._query}}) _cdb_rank) _cdb_quartiles',
' WHERE quartile = 1 or quartile = 3',
' GROUP BY quartile',
' ) _cdb_iqr',
')'
].join('\n'));
var binsQueryTpl = dot.template([
'bins AS (',
' SELECT CASE WHEN total_rows = 0 OR iqr = 0',
' THEN 1',
' ELSE GREATEST(',
' LEAST({{=it._minBins}}, CAST(total_rows AS INT)),',
' LEAST(',
' CAST(((max_val - min_val) / (2 * iqr * power(total_rows, 1/3))) AS INT),',
' {{=it._maxBins}}',
' )',
' )',
' END AS bins_number',
' FROM basics, iqrange, ({{=it._query}}) _cdb_bins',
' LIMIT 1',
')'
].join('\n'));
var overrideBinsQueryTpl = dot.template([
'bins AS (',
' SELECT {{=it._bins}} AS bins_number',
')'
].join('\n'));
var nullsQueryTpl = dot.template([
'nulls AS (',
' SELECT',
' count(*) AS nulls_count',
' FROM ({{=it._query}}) _cdb_histogram_nulls',
' WHERE {{=it._column}} IS NULL',
')'
].join('\n'));
var histogramQueryTpl = dot.template([
'SELECT',
' (max_val - min_val) / cast(bins_number as float) AS bin_width,',
' bins_number,',
' nulls_count,',
' avg_val,',
' CASE WHEN min_val = max_val',
' THEN 0',
' ELSE GREATEST(1, LEAST(WIDTH_BUCKET({{=it._column}}, min_val, max_val, bins_number), bins_number)) - 1',
' END AS bin,',
' min({{=it._column}})::numeric AS min,',
' max({{=it._column}})::numeric AS max,',
' sum({{=it._column}}*_feature_count)/sum(_feature_count)::numeric AS avg,',
' sum(_feature_count) AS freq',
'FROM ({{=it._query}}) _cdb_histogram, basics, nulls, bins',
'WHERE {{=it._column}} IS NOT NULL',
'GROUP BY bin, bins_number, bin_width, nulls_count, avg_val',
'ORDER BY bin'
].join('\n'));
function Histogram(query, options, queryRewriter, queryRewriteData, params, queries) {
BaseOverviewsDataview.call(this, query, options, BaseDataview, queryRewriter, queryRewriteData, params);
this.query = query;
this.queries = queries;
this.column = options.column;
this.bins = options.bins;
this._columnType = null;
}
Histogram.prototype = Object.create(BaseOverviewsDataview.prototype);
Histogram.prototype.constructor = Histogram;
module.exports = Histogram;
var DATE_OIDS = {
1082: true,
1114: true,
1184: true
};
Histogram.prototype.sql = function(psql, override, callback) {
if (!callback) {
callback = override;
override = {};
}
var self = this;
var _column = this.column;
var columnTypeQuery = columnTypeQueryTpl({
column: _column, query: this.rewrittenQuery(this.queries.no_filters)
});
if (this._columnType === null) {
psql.query(columnTypeQuery, function(err, result) {
// assume numeric, will fail later
self._columnType = 'numeric';
if (!err && !!result.rows[0]) {
var pgType = result.rows[0].pg_typeof;
if (DATE_OIDS.hasOwnProperty(pgType)) {
self._columnType = 'date';
}
}
self.sql(psql, override, callback);
}, true); // use read-only transaction
return null;
}
if (this._columnType === 'date') {
// overviews currently aggregate dates to NULL
// to avoid problem we don't use overviews for histograms of date columns
return this.defaultSql(psql, override, callback);
}
var _query = this.rewrittenQuery(this.query);
var basicsQuery, binsQuery;
if (override && _.has(override, 'start') && _.has(override, 'end') && _.has(override, 'bins')) {
basicsQuery = overrideBasicsQueryTpl({
_query: _query,
_column: _column,
_start: override.start,
_end: override.end
});
binsQuery = [
overrideBinsQueryTpl({
_bins: override.bins
})
].join(',\n');
} else {
basicsQuery = basicsQueryTpl({
_query: _query,
_column: _column
});
if (override && _.has(override, 'bins')) {
binsQuery = [
overrideBinsQueryTpl({
_bins: override.bins
})
].join(',\n');
} else {
binsQuery = [
iqrQueryTpl({
_query: _query,
_column: _column
}),
binsQueryTpl({
_query: _query,
_minBins: BIN_MIN_NUMBER,
_maxBins: BIN_MAX_NUMBER
})
].join(',\n');
}
}
var histogramSql = [
"WITH",
[
basicsQuery,
binsQuery,
nullsQueryTpl({
_query: _query,
_column: _column
})
].join(',\n'),
histogramQueryTpl({
_query: _query,
_column: _column
})
].join('\n');
return callback(null, histogramSql);
};

View File

@@ -0,0 +1,6 @@
module.exports = {
Aggregation: require('./aggregation'),
Formula: require('./formula'),
Histogram: require('./histogram'),
List: require('./list')
};

View File

@@ -0,0 +1,11 @@
var BaseOverviewsDataview = require('./base');
var BaseDataview = require('../list');
function List(query, options, queryRewriter, queryRewriteData, params) {
BaseOverviewsDataview.call(this, query, options, BaseDataview, queryRewriter, queryRewriteData, params);
}
List.prototype = Object.create(BaseOverviewsDataview.prototype);
List.prototype.constructor = List;
module.exports = List;

View File

@@ -0,0 +1,35 @@
var filters = {
category: require('./analysis/category'),
range: require('./analysis/range')
};
function createFilter(filterDefinition) {
var filterType = filterDefinition.type.toLowerCase();
if (!filters.hasOwnProperty(filterType)) {
throw new Error('Unknown filter type: ' + filterType);
}
return new filters[filterType](filterDefinition.column, filterDefinition.params);
}
function AnalysisFilters(filters) {
this.filters = filters;
}
AnalysisFilters.prototype.sql = function(rawSql) {
var filters = this.filters || {};
var applyFilters = {};
return Object.keys(filters)
.filter(function(filterName) {
return applyFilters.hasOwnProperty(filterName) ? applyFilters[filterName] : true;
})
.map(function(filterName) {
var filterDefinition = filters[filterName];
return createFilter(filterDefinition);
})
.reduce(function(sql, filter) {
return filter.sql(sql);
}, rawSql);
};
module.exports = AnalysisFilters;

View File

@@ -0,0 +1,79 @@
'use strict';
var debug = require('debug')('windshaft:filter:category');
var dot = require('dot');
dot.templateSettings.strip = false;
var filterQueryTpl = dot.template([
'SELECT *',
'FROM ({{=it._sql}}) _analysis_category_filter',
'WHERE {{=it._filters}}'
].join('\n'));
var escapeStringTpl = dot.template('$escape_{{=it._i}}${{=it._value}}$escape_{{=it._i}}$');
var inConditionTpl = dot.template('{{=it._column}} IN ({{=it._values}})');
var notInConditionTpl = dot.template('{{=it._column}} NOT IN ({{=it._values}})');
function Category(column, filterParams) {
this.column = column;
if (!Array.isArray(filterParams.accept) && !Array.isArray(filterParams.reject)) {
throw new Error('Category filter expects at least one array in accept or reject params');
}
if (Array.isArray(filterParams.accept) && Array.isArray(filterParams.reject)) {
if (filterParams.accept.length === 0 && filterParams.reject.length === 0) {
throw new Error(
'Category filter expects one value either in accept or reject params when both are provided'
);
}
}
this.accept = filterParams.accept;
this.reject = filterParams.reject;
}
module.exports = Category;
/*
- accept: [] => reject all
- reject: [] => accept all
*/
Category.prototype.sql = function(rawSql) {
var valueFilters = [];
if (Array.isArray(this.accept)) {
if (this.accept.length > 0) {
valueFilters.push(inConditionTpl({
_column: this.column,
_values: this.accept.map(function(value, i) {
return Number.isFinite(value) ? value : escapeStringTpl({_i: i, _value: value});
}).join(',')
}));
} else {
valueFilters.push('0 = 1');
}
}
if (Array.isArray(this.reject)) {
if (this.reject.length > 0) {
valueFilters.push(notInConditionTpl({
_column: this.column,
_values: this.reject.map(function (value, i) {
return Number.isFinite(value) ? value : escapeStringTpl({_i: i, _value: value});
}).join(',')
}));
} else {
valueFilters.push('1 = 1');
}
}
debug(filterQueryTpl({
_sql: rawSql,
_filters: valueFilters.join(' AND ')
}));
return filterQueryTpl({
_sql: rawSql,
_filters: valueFilters.join(' AND ')
});
};

View File

@@ -0,0 +1,43 @@
'use strict';
var dot = require('dot');
dot.templateSettings.strip = false;
var betweenFilterTpl = dot.template('{{=it._column}} BETWEEN {{=it._min}} AND {{=it._max}}');
var minFilterTpl = dot.template('{{=it._column}} >= {{=it._min}}');
var maxFilterTpl = dot.template('{{=it._column}} <= {{=it._max}}');
var filterQueryTpl = dot.template('SELECT * FROM ({{=it._sql}}) _analysis_range_filter WHERE {{=it._filter}}');
function Range(column, filterParams) {
this.column = column;
if (!Number.isFinite(filterParams.min) && !Number.isFinite(filterParams.max)) {
throw new Error('Range filter expect to have at least one value in min or max numeric params');
}
this.min = filterParams.min;
this.max = filterParams.max;
this.columnType = filterParams.columnType;
}
module.exports = Range;
Range.prototype.sql = function(rawSql) {
var minMaxFilter;
if (Number.isFinite(this.min) && Number.isFinite(this.max)) {
minMaxFilter = betweenFilterTpl({
_column: this.column,
_min: this.min,
_max: this.max
});
} else if (Number.isFinite(this.min)) {
minMaxFilter = minFilterTpl({ _column: this.column, _min: this.min });
} else {
minMaxFilter = maxFilterTpl({ _column: this.column, _max: this.max });
}
return filterQueryTpl({
_sql: rawSql,
_filter: minMaxFilter
});
};

View File

@@ -11,73 +11,25 @@ function AnalysisMapConfigAdapter(analysisBackend) {
module.exports = AnalysisMapConfigAdapter;
var SKIP_COLUMNS = {
'the_geom': true,
'the_geom_webmercator': true
};
function skipColumns(columnNames) {
return columnNames
.filter(function(columnName) { return !SKIP_COLUMNS[columnName]; });
}
var layerQueryTemplate = dot.template([
'SELECT {{=it._columns}}',
'FROM ({{=it._query}}) _cdb_analysis_query'
].join('\n'));
function layerQuery(node) {
if (node.type === 'source') {
return node.getQuery();
}
var _columns = ['ST_Transform(the_geom, 3857) the_geom_webmercator'].concat(skipColumns(node.getColumns()));
return layerQueryTemplate({ _query: node.getQuery(), _columns: _columns.join(', ') });
}
function appendFiltersToNodes(requestMapConfig, dataviewsFiltersBySourceId) {
var analyses = requestMapConfig.analyses || [];
requestMapConfig.analyses = analyses.map(function(analysisDefinition) {
var analysisGraph = new camshaft.reference.AnalysisGraph(analysisDefinition);
var definition = analysisDefinition;
Object.keys(dataviewsFiltersBySourceId).forEach(function(sourceId) {
definition = analysisGraph.getDefinitionWith(sourceId, {filters: dataviewsFiltersBySourceId[sourceId] });
});
return definition;
});
return requestMapConfig;
}
function shouldAdaptLayers(requestMapConfig) {
return Array.isArray(requestMapConfig.layers) &&
Array.isArray(requestMapConfig.analyses) && requestMapConfig.analyses.length > 0;
}
var DATAVIEW_TYPE_2_FILTER_TYPE = {
aggregation: 'category',
histogram: 'range'
};
function getFilter(dataview, params) {
var type = dataview.type;
return {
type: DATAVIEW_TYPE_2_FILTER_TYPE[type],
column: dataview.options.column,
params: params
};
}
AnalysisMapConfigAdapter.prototype.getMapConfig = function(analysisConfiguration, requestMapConfig, filters, callback) {
AnalysisMapConfigAdapter.prototype.getMapConfig = function(user, requestMapConfig, params, context, callback) {
// jshint maxcomplexity:7
var self = this;
filters = filters || {};
if (!shouldAdaptLayers(requestMapConfig)) {
return callback(null, requestMapConfig);
}
var analysisConfiguration = context.analysisConfiguration;
var filters = {};
if (params.filters) {
try {
filters = JSON.parse(params.filters);
} catch (e) {
// ignore
}
}
var dataviewsFilters = filters.dataviews || {};
debug(dataviewsFilters);
var dataviews = requestMapConfig.dataviews || {};
@@ -107,10 +59,23 @@ AnalysisMapConfigAdapter.prototype.getMapConfig = function(analysisConfiguration
requestMapConfig = appendFiltersToNodes(requestMapConfig, dataviewsFiltersBySourceId);
function createAnalysis(analysisDefinition, done) {
self.analysisBackend.create(analysisConfiguration, analysisDefinition, done);
self.analysisBackend.create(analysisConfiguration, analysisDefinition, function (err, analysis) {
if (err) {
var error = new Error(err.message);
error.type = 'analysis';
error.analysis = {
id: analysisDefinition.id,
node_id: err.node_id,
type: analysisDefinition.type
};
return done(error);
}
done(null, analysis);
});
}
var analysesQueue = queue(requestMapConfig.analyses.length);
var analysesQueue = queue(1);
requestMapConfig.analyses.forEach(function(analysis) {
analysesQueue.defer(createAnalysis, analysis);
});
@@ -126,7 +91,7 @@ AnalysisMapConfigAdapter.prototype.getMapConfig = function(analysisConfiguration
sourceId2Query[rootNode.params.id] = rootNode;
}
analysis.getSortedNodes().forEach(function(node) {
analysis.getNodes().forEach(function(node) {
if (node.params && node.params.id) {
sourceId2Query[node.params.id] = node;
}
@@ -145,13 +110,11 @@ AnalysisMapConfigAdapter.prototype.getMapConfig = function(analysisConfiguration
var analysisSql = layerQuery(layerNode);
var sqlQueryWrap = layer.options.sql_wrap;
if (sqlQueryWrap) {
layer.options.sql_raw = analysisSql;
analysisSql = sqlQueryWrap.replace(/<%=\s*sql\s*%>/g, analysisSql);
}
layer.options.sql = analysisSql;
var layerDataviews = getLayerDataviews(layer, dataviews);
layer.options.columns = layerDataviews.reduce(function(columns, dataview) {
return columns.concat(getDataviewColumns(dataview));
}, []);
layer.options.columns = getDataviewsColumns(getLayerDataviews(layer, dataviews));
} else {
missingNodesErrors.push(
new Error('Missing analysis node.id="' + layerSourceId +'" for layer='+layerIndex)
@@ -161,16 +124,115 @@ AnalysisMapConfigAdapter.prototype.getMapConfig = function(analysisConfiguration
return layer;
});
debug('mapconfig output', JSON.stringify(requestMapConfig, null, 4));
if (missingNodesErrors.length > 0) {
return callback(missingNodesErrors);
var missingDataviewsNodesErrors = getMissingDataviewsSourceIds(dataviews, sourceId2Node);
if (missingNodesErrors.length > 0 || missingDataviewsNodesErrors.length > 0) {
return callback(missingNodesErrors.concat(missingDataviewsNodesErrors));
}
return callback(null, requestMapConfig, analysesResults);
// Augment dataviews with sql from analyses
Object.keys(dataviews).forEach(function(dataviewName) {
var dataview = requestMapConfig.dataviews[dataviewName];
var dataviewSourceId = dataview.source.id;
var dataviewNode = sourceId2Node[dataviewSourceId];
dataview.node = {
type: dataviewNode.type,
filters: dataviewNode.getFilters()
};
dataview.sql = {
own_filter_on: dataviewQuery(dataviewNode, dataviewName, true),
own_filter_off: dataviewQuery(dataviewNode, dataviewName, false),
no_filters: dataviewNode.getQuery(Object.keys(dataviewNode.getFilters())
.reduce(function(applyFilters, filterId) {
applyFilters[filterId] = false;
return applyFilters;
}, {})
)
};
});
if (Object.keys(dataviews).length > 0) {
requestMapConfig.dataviews = dataviews;
}
debug('mapconfig output', JSON.stringify(requestMapConfig, null, 4));
context.analysesResults = analysesResults;
return callback(null, requestMapConfig);
});
};
var SKIP_COLUMNS = {
'the_geom': true,
'the_geom_webmercator': true
};
function skipColumns(columnNames) {
return columnNames
.filter(function(columnName) { return !SKIP_COLUMNS[columnName]; });
}
var wrappedQueryTpl = dot.template([
'SELECT {{=it._columns}}',
'FROM ({{=it._query}}) _cdb_analysis_query'
].join('\n'));
function layerQuery(node) {
if (node.type === 'source') {
return node.getQuery();
}
var _columns = ['ST_Transform(the_geom, 3857) the_geom_webmercator'].concat(skipColumns(node.getColumns()));
return wrappedQueryTpl({ _query: node.getQuery(), _columns: _columns.join(', ') });
}
function dataviewQuery(node, dataviewName, ownFilter) {
var applyFilters = {};
if (!ownFilter) {
applyFilters[dataviewName] = false;
}
if (node.type === 'source') {
return node.getQuery(applyFilters);
}
var _columns = ['ST_Transform(the_geom, 3857) the_geom_webmercator'].concat(skipColumns(node.getColumns()));
return wrappedQueryTpl({ _query: node.getQuery(applyFilters), _columns: _columns.join(', ') });
}
function appendFiltersToNodes(requestMapConfig, dataviewsFiltersBySourceId) {
var analyses = requestMapConfig.analyses || [];
requestMapConfig.analyses = analyses.map(function(analysisDefinition) {
var analysisGraph = new camshaft.reference.AnalysisGraph(analysisDefinition);
var definition = analysisDefinition;
Object.keys(dataviewsFiltersBySourceId).forEach(function(sourceId) {
definition = analysisGraph.getDefinitionWith(sourceId, {filters: dataviewsFiltersBySourceId[sourceId] });
});
return definition;
});
return requestMapConfig;
}
function shouldAdaptLayers(requestMapConfig) {
return Array.isArray(requestMapConfig.layers) && requestMapConfig.layers.some(getLayerSourceId) ||
(Array.isArray(requestMapConfig.analyses) && requestMapConfig.analyses.length > 0) ||
requestMapConfig.dataviews;
}
var DATAVIEW_TYPE_2_FILTER_TYPE = {
aggregation: 'category',
histogram: 'range'
};
function getFilter(dataview, params) {
var type = dataview.type;
return {
type: DATAVIEW_TYPE_2_FILTER_TYPE[type],
column: dataview.options.column,
params: params
};
}
function getLayerSourceId(layer) {
return layer.options.source && layer.options.source.id;
}
@@ -195,11 +257,22 @@ function getLayerDataviews(layer, dataviews) {
return layerDataviews;
}
function getDataviewsColumns(dataviews) {
return Object.keys(dataviews.reduce(function(columnsDict, dataview) {
getDataviewColumns(dataview).forEach(function(columnName) {
if (!!columnName) {
columnsDict[columnName] = true;
}
});
return columnsDict;
}, {}));
}
function getDataviewColumns(dataview) {
var columns = [];
var options = dataview.options;
['column', 'aggregationColumn'].forEach(function(opt) {
if (options.hasOwnProperty(opt)) {
if (options.hasOwnProperty(opt) && !!options[opt]) {
columns.push(options[opt]);
}
});
@@ -211,6 +284,15 @@ function getDataviewsList(dataviews) {
}
function getDataviewsErrors(dataviews) {
var dataviewType = typeof dataviews;
if (dataviewType !== 'object') {
return [new Error('"dataviews" must be a valid JSON object: "' + dataviewType + '" type found')];
}
if (Array.isArray(dataviews)) {
return [new Error('"dataviews" must be a valid JSON object: "array" type found')];
}
var errors = [];
Object.keys(dataviews).forEach(function(dataviewName) {
@@ -226,3 +308,26 @@ function getDataviewsErrors(dataviews) {
return errors;
}
function getMissingDataviewsSourceIds(dataviews, sourceId2Node) {
var missingDataviewsSourceIds = [];
Object.keys(dataviews).forEach(function(dataviewName) {
var dataview = dataviews[dataviewName];
var dataviewSourceId = getDataviewSourceId(dataview);
if (!sourceId2Node.hasOwnProperty(dataviewSourceId)) {
missingDataviewsSourceIds.push(new AnalysisError('Node with `source.id="' + dataviewSourceId +'"`' +
' not found in analyses for dataview "' + dataviewName + '"'));
}
});
return missingDataviewsSourceIds;
}
function AnalysisError(message) {
Error.captureStackTrace(this, this.constructor);
this.name = this.constructor.name;
this.type = 'analysis';
this.message = message;
}
require('util').inherits(AnalysisError, Error);

View File

@@ -0,0 +1,98 @@
function DataviewsWidgetsMapConfigAdapter() {
}
module.exports = DataviewsWidgetsMapConfigAdapter;
DataviewsWidgetsMapConfigAdapter.prototype.getMapConfig = function(user, requestMapConfig, params, context, callback) {
if (!shouldAdapt(requestMapConfig)) {
return callback(null, requestMapConfig);
}
// prepare placeholders for new dataviews created from widgets
requestMapConfig.analyses = requestMapConfig.analyses || [];
requestMapConfig.dataviews = requestMapConfig.dataviews || {};
requestMapConfig.layers.forEach(function(layer, index) {
var layerSourceId = getLayerSourceId(layer);
if (!layer.options.widgets) {
return;
}
if (!layerSourceId && !layer.options.sql) {
return;
}
var dataviewSourceId = layerSourceId || 'cdb-layer-source-' + index;
// Append a new analysis if layer has no source id but sql.
if (!layerSourceId) {
requestMapConfig.analyses.push(
{
id: dataviewSourceId,
type: 'source',
params: {
query: layer.options.sql
}
}
);
}
var source = { id: dataviewSourceId };
var layerWidgets = layer.options.widgets || {};
Object.keys(layerWidgets).forEach(function(widgetId) {
var dataview = layerWidgets[widgetId];
requestMapConfig.dataviews[widgetId] = {
source: source,
type: dataview.type,
options: dataview.options
};
});
layer.options.source = source;
delete layer.options.sql;
// don't delete widgets for now as it might be useful for old clients
//delete layer.options.widgets;
});
// filters have to be rewritten also
var filters = getFilters(params);
var layersFilters = filters.layers || [];
filters.dataviews = filters.dataviews || {};
layersFilters.forEach(function(layerFilters) {
Object.keys(layerFilters).forEach(function(filterName) {
if (!filters.dataviews.hasOwnProperty(filterName)) {
filters.dataviews[filterName] = layerFilters[filterName];
}
});
});
delete filters.layers;
params.filters = JSON.stringify(filters);
return callback(null, requestMapConfig);
};
function shouldAdapt(requestMapConfig) {
return Array.isArray(requestMapConfig.layers) && requestMapConfig.layers.some(function hasWidgets(layer) {
return layer.options && layer.options.widgets && Object.keys(layer.options.widgets).length > 0;
});
}
function getLayerSourceId(layer) {
return layer.options.source && layer.options.source.id;
}
function getFilters(params) {
var filters = {};
if (params.filters) {
try {
filters = JSON.parse(params.filters);
} catch (e) {
// ignore
}
}
return filters;
}

View File

@@ -0,0 +1,26 @@
'use strict';
function MapConfigAdapter(adapters) {
this.adapters = Array.isArray(adapters) ? adapters : Array.apply(null, arguments);
}
module.exports = MapConfigAdapter;
MapConfigAdapter.prototype.getMapConfig = function(user, requestMapConfig, params, context, callback) {
var self = this;
var i = 0;
var tasksLeft = this.adapters.length;
function next(err, _requestMapConfig) {
if (err) {
return callback(err);
}
if (tasksLeft-- === 0) {
return callback(null, _requestMapConfig);
}
var nextAdapter = self.adapters[i++];
nextAdapter.getMapConfig(user, _requestMapConfig, params, context, next);
}
next(null, requestMapConfig);
};

View File

@@ -2,17 +2,20 @@ var queue = require('queue-async');
var _ = require('underscore');
var Datasource = require('windshaft').model.Datasource;
function MapConfigNamedLayersAdapter(templateMaps) {
function MapConfigNamedLayersAdapter(templateMaps, pgConnection) {
this.templateMaps = templateMaps;
this.pgConnection = pgConnection;
}
module.exports = MapConfigNamedLayersAdapter;
MapConfigNamedLayersAdapter.prototype.getLayers = function(username, layers, dbMetadata, callback) {
MapConfigNamedLayersAdapter.prototype.getMapConfig = function (user, requestMapConfig, params, context, callback) {
var self = this;
var layers = requestMapConfig.layers;
if (!layers) {
return callback(null);
return callback(null, requestMapConfig);
}
var adaptLayersQueue = queue(layers.length);
@@ -28,9 +31,9 @@ MapConfigNamedLayersAdapter.prototype.getLayers = function(username, layers, dbM
var templateConfigParams = layer.options.config || {};
var templateAuthTokens = layer.options.auth_tokens;
self.templateMaps.getTemplate(username, templateName, function(err, template) {
self.templateMaps.getTemplate(user, templateName, function(err, template) {
if (err || !template) {
return done(new Error("Template '" + templateName + "' of user '" + username + "' not found"));
return done(new Error("Template '" + templateName + "' of user '" + user + "' not found"));
}
if (self.templateMaps.isAuthorized(template, templateAuthTokens)) {
@@ -96,7 +99,10 @@ MapConfigNamedLayersAdapter.prototype.getLayers = function(username, layers, dbM
});
return callback(null, layers, datasourceBuilder.build());
requestMapConfig.layers = layers;
context.datasource = datasourceBuilder.build();
return callback(null, requestMapConfig);
}
@@ -104,7 +110,7 @@ MapConfigNamedLayersAdapter.prototype.getLayers = function(username, layers, dbM
if (_.some(layers, isNamedTypeLayer)) {
// Lazy load dbAuth
dbMetadata.setDBAuth(username, dbAuth, function(err) {
this.pgConnection.setDBAuth(user, dbAuth, function(err) {
if (err) {
return callback(err);
}
@@ -114,7 +120,8 @@ MapConfigNamedLayersAdapter.prototype.getLayers = function(username, layers, dbM
adaptLayersQueue.awaitAll(layersAdaptQueueFinish);
});
} else {
return callback(null, layers, datasourceBuilder.build());
context.datasource = datasourceBuilder.build();
return callback(null, requestMapConfig);
}
};

View File

@@ -0,0 +1,101 @@
var step = require('step');
var queue = require('queue-async');
var _ = require('underscore');
function MapConfigOverviewsAdapter(overviewsMetadataApi, filterStatsApi) {
this.overviewsMetadataApi = overviewsMetadataApi;
this.filterStatsApi = filterStatsApi;
}
module.exports = MapConfigOverviewsAdapter;
MapConfigOverviewsAdapter.prototype.getMapConfig = function(user, requestMapConfig, params, context, callback) {
var self = this;
var layers = requestMapConfig.layers;
var analysesResults = context.analysesResults;
if (!layers || layers.length === 0) {
return callback(null, requestMapConfig);
}
var augmentLayersQueue = queue(layers.length);
function augmentLayer(layer, done) {
if ( layer.type !== 'mapnik' && layer.type !== 'cartodb' ) {
return done(null, layer);
}
self.overviewsMetadataApi.getOverviewsMetadata(user, layer.options.sql, function(err, metadata){
if (err) {
done(err, layer);
} else {
var query_rewrite_data = { overviews: metadata };
step(
function collectFiltersData() {
var filters, unfiltered_query;
if ( layer.options.source && analysesResults && !layer.options.sql_wrap) {
var sourceId = layer.options.source.id;
var node = _.find(analysesResults, function(a){ return a.rootNode.params.id === sourceId; });
if ( node ) {
node = node.rootNode;
filters = node.getFilters();
var filters_disabler = Object.keys(filters).reduce(
function(disabler, filter_id){ disabler[filter_id] = false; return disabler; },
{}
);
unfiltered_query = node.getQuery(filters_disabler);
query_rewrite_data.filters = filters;
query_rewrite_data.unfiltered_query = unfiltered_query;
}
}
this(null, filters, unfiltered_query);
},
function collectStatsData(err, filters, unfiltered_query) {
var next_step = this;
if ( filters ) {
self.filterStatsApi.getFilterStats(
user,
unfiltered_query, filters,
function(err, stats) {
if ( !err ) {
query_rewrite_data.filter_stats = stats;
}
return next_step(err);
}
);
} else {
return next_step(null);
}
},
function addDataToLayer(err) {
if ( !err && !_.isEmpty(metadata) ) {
layer = _.extend({}, layer);
layer.options = _.extend({}, layer.options, { query_rewrite_data: query_rewrite_data });
}
done(null, layer);
}
);
}
});
}
function layersAugmentQueueFinish(err, layers) {
if (err) {
return callback(err);
}
if (!layers || layers.length === 0) {
return callback(new Error('Missing layers array from layergroup config'));
}
requestMapConfig.layers = layers;
return callback(null, requestMapConfig);
}
layers.forEach(function(layer) {
augmentLayersQueue.defer(augmentLayer, layer);
});
augmentLayersQueue.awaitAll(layersAugmentQueueFinish);
};

View File

@@ -0,0 +1,25 @@
function SqlWrapMapConfigAdapter() {
}
module.exports = SqlWrapMapConfigAdapter;
SqlWrapMapConfigAdapter.prototype.getMapConfig = function(user, requestMapConfig, params, context, callback) {
if (requestMapConfig && Array.isArray(requestMapConfig.layers)) {
requestMapConfig.layers = requestMapConfig.layers.map(function(layer) {
if (layer.options) {
var sqlQueryWrap = layer.options.sql_wrap;
if (sqlQueryWrap) {
var layerSql = layer.options.sql;
if (layerSql) {
layer.options.sql_raw = layerSql;
layer.options.sql = sqlQueryWrap.replace(/<%=\s*sql\s*%>/g, layerSql);
}
}
}
return layer;
});
}
return callback(null, requestMapConfig);
};

View File

@@ -0,0 +1,187 @@
'use strict';
var dot = require('dot');
dot.templateSettings.strip = false;
var queue = require('queue-async');
var PSQL = require('cartodb-psql');
/**
* cartodb-psql creates `global.Promise` as an empty constructor.
* However, `turbo-carto` relies on a polyfil that fails to create the polyfil
* as it finds `global.Promise` but it doesn't find `Promise.resolve`.
*/
global.Promise = global.Promise || function() {};
global.Promise.resolve = global.Promise.resolve || function() {};
var turboCarto = require('turbo-carto');
var SubstitutionTokens = require('../../../utils/substitution-tokens');
var PostgresDatasource = require('../../../backends/turbo-carto-postgres-datasource');
var MapConfig = require('windshaft').model.MapConfig;
function TurboCartoAdapter() {
}
module.exports = TurboCartoAdapter;
TurboCartoAdapter.prototype.getMapConfig = function (user, requestMapConfig, params, context, callback) {
var self = this;
var layers = requestMapConfig.layers;
if (!layers || layers.length === 0) {
return callback(null, requestMapConfig);
}
var parseCartoQueue = queue(layers.length);
layers.forEach(function(layer, index) {
var layerId = MapConfig.getLayerId(requestMapConfig, index);
parseCartoQueue.defer(self._parseCartoCss.bind(self), user, params, layer, index, layerId);
});
parseCartoQueue.awaitAll(function (err, results) {
if (err) {
return callback(err);
}
var errors = results.reduce(function(errors, result) {
if (result.error) {
errors.push(result.error);
}
return errors;
}, []);
if (errors.length > 0) {
return callback(errors);
}
requestMapConfig.layers = results.map(function(result) { return result.layer; });
context.turboCarto = {
layers: results.map(function(result) {
return result.meta;
})
};
return callback(null, requestMapConfig);
});
};
var tokensQueryTpl = dot.template([
'WITH input_query AS (',
' {{=it._sql}}',
'),',
'bbox_query AS (',
' SELECT ST_SetSRID(ST_Extent(the_geom_webmercator), 3857) as bbox from input_query',
'),',
'zoom_query as (',
' SELECT GREATEST(',
' ceil(log(40075017000 / 256 / GREATEST(ST_XMax(bbox) - ST_XMin(bbox), ST_YMax(bbox) - ST_YMin(bbox)))/log(2)),',
' 0) as zoom',
' FROM bbox_query',
'),',
'pixel_size_query as (',
' SELECT 40075017 * cos(radians(ST_Y(ST_Transform(ST_Centroid(bbox), 4326)))) / 2 ^ ((zoom) + 8) as pixel_size',
' FROM bbox_query, zoom_query',
'),',
'scale_denominator_query as (',
' SELECT (pixel_size / 0.00028)::numeric as scale_denominator',
' FROM pixel_size_query',
')',
'select ST_AsText(bbox) bbox, pixel_size, scale_denominator, zoom',
'from bbox_query, pixel_size_query, scale_denominator_query, zoom_query'
].join('\n'));
TurboCartoAdapter.prototype._parseCartoCss = function (username, params, layer, layerIndex, layerId, callback) {
if (!shouldParseLayerCartocss(layer)) {
return callback(null, { layer: layer });
}
var pg = new PSQL(dbParamsFromReqParams(params));
function processCallback(err, cartocss, meta) {
// Only return turbo-carto errors
if (err && err.name === 'TurboCartoError') {
var error = new Error(err.message);
error.http_status = 400;
error.type = 'layer';
error.subtype = 'turbo-carto';
error.layer = {
id: layerId,
index: layerIndex,
type: layer.type,
context: err.context
};
return callback(null, { error: error });
}
// Try to continue in the rest of the cases
if (cartocss) {
layer.options.cartocss = cartocss;
}
return callback(null, { layer: layer, meta: meta });
}
var layerSql = layer.options.sql;
var layerRawSql = layer.options.sql_raw;
if (SubstitutionTokens.hasTokens(layerSql) && layerRawSql) {
var self = this;
var tokensQuery = tokensQueryTpl({_sql: layerRawSql});
return pg.query(tokensQuery, function(err, resultSet) {
if (err) {
return processCallback(err);
}
resultSet = resultSet || {};
var rows = resultSet.rows || [];
var result = rows[0] || {};
var tokens = {
bbox: 'ST_SetSRID(ST_GeomFromText(\'' + result.bbox + '\'), 3857)',
scale_denominator: result.scale_denominator,
pixel_width: result.pixel_size,
pixel_height: result.pixel_size
};
var sql = SubstitutionTokens.replace(layerSql, tokens);
self.process(pg, layer.options.cartocss, sql, processCallback);
}, true); // use read-only transaction
}
var tokens = {
bbox: 'ST_MakeEnvelope(-20037508.34,-20037508.34,20037508.34,20037508.34,3857)',
scale_denominator: '500000001',
pixel_width: '156412',
pixel_height: '156412'
};
var sql = SubstitutionTokens.replace(layerSql, tokens);
this.process(pg, layer.options.cartocss, sql, processCallback);
};
TurboCartoAdapter.prototype.process = function (psql, cartocss, sql, callback) {
var datasource = new PostgresDatasource(psql, sql);
turboCarto(cartocss, datasource, callback);
};
function shouldParseLayerCartocss(layer) {
return layer && layer.options && layer.options.cartocss && layer.options.sql;
}
function dbParamsFromReqParams(params) {
var dbParams = {};
if ( params.dbuser ) {
dbParams.user = params.dbuser;
}
if ( params.dbpassword ) {
dbParams.pass = params.dbpassword;
}
if ( params.dbhost ) {
dbParams.host = params.dbhost;
}
if ( params.dbport ) {
dbParams.port = params.dbport;
}
if ( params.dbname ) {
dbParams.dbname = params.dbname;
}
return dbParams;
}

View File

@@ -1,7 +1,7 @@
var assert = require('assert');
var step = require('step');
var MapStoreMapConfigProvider = require('./map_store_provider');
var MapStoreMapConfigProvider = require('./map-store-provider');
/**
* @param {MapConfig} mapConfig

View File

@@ -4,24 +4,20 @@ var crypto = require('crypto');
var dot = require('dot');
var step = require('step');
var MapConfig = require('windshaft').model.MapConfig;
var templateName = require('../../backends/template_maps').templateName;
var templateName = require('../../../backends/template_maps').templateName;
var QueryTables = require('cartodb-query-tables');
/**
* @constructor
* @type {NamedMapMapConfigProvider}
*/
function NamedMapMapConfigProvider(templateMaps, pgConnection, metadataBackend, userLimitsApi,
namedLayersAdapter, overviewsAdapter, turboCartoAdapter, analysisMapConfigAdapter,
function NamedMapMapConfigProvider(templateMaps, pgConnection, metadataBackend, userLimitsApi, mapConfigAdapter,
owner, templateId, config, authToken, params) {
this.templateMaps = templateMaps;
this.pgConnection = pgConnection;
this.metadataBackend = metadataBackend;
this.userLimitsApi = userLimitsApi;
this.namedLayersAdapter = namedLayersAdapter;
this.turboCartoAdapter = turboCartoAdapter;
this.analysisMapConfigAdapter = analysisMapConfigAdapter;
this.overviewsAdapter = overviewsAdapter;
this.mapConfigAdapter = mapConfigAdapter;
this.owner = owner;
this.templateName = templateName(templateId);
@@ -54,10 +50,11 @@ NamedMapMapConfigProvider.prototype.getMapConfig = function(callback) {
var self = this;
var mapConfig = null;
var datasource = null;
var rendererParams;
var apiKey;
var context = {};
step(
function getTemplate() {
self.getTemplate(this);
@@ -95,9 +92,10 @@ NamedMapMapConfigProvider.prototype.getMapConfig = function(callback) {
assert.ifError(err);
return self.templateMaps.instance(self.template, templateParams);
},
function prepareAnalysisLayers(err, requestMapConfig) {
function prepareAdapterMapConfig(err, requestMapConfig) {
assert.ifError(err);
var analysisConfiguration = {
context.analysisConfiguration = {
user: self.owner,
db: {
host: rendererParams.dbhost,
port: rendererParams.dbport,
@@ -110,76 +108,19 @@ NamedMapMapConfigProvider.prototype.getMapConfig = function(callback) {
apiKey: apiKey
}
};
var filters = {};
if (self.params.filters) {
try {
filters = JSON.parse(self.params.filters);
} catch (e) {
// ignore
}
}
self.analysisMapConfigAdapter.getMapConfig(analysisConfiguration, requestMapConfig, filters, this);
self.mapConfigAdapter.getMapConfig(self.owner, requestMapConfig, rendererParams, context, this);
},
function prepareLayergroup(err, _mapConfig, analysesResults) {
assert.ifError(err);
var next = this;
self.analysesResults = analysesResults || [];
self.namedLayersAdapter.getLayers(self.owner, _mapConfig.layers, self.pgConnection,
function(err, layers, datasource) {
if (err) {
return next(err);
}
if (layers) {
_mapConfig.layers = layers;
}
return next(null, _mapConfig, datasource);
}
);
},
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 parseTurboCarto(err, _mapConfig, datasource) {
assert.ifError(err);
var next = this;
self.turboCartoAdapter.getLayers(self.owner, _mapConfig.layers, function (err, layers) {
if (err) {
return next(err);
}
if (layers) {
_mapConfig.layers = layers;
}
return next(null, _mapConfig, datasource);
});
},
function prepareContextLimits(err, _mapConfig, _datasource) {
function prepareContextLimits(err, _mapConfig) {
assert.ifError(err);
mapConfig = _mapConfig;
datasource = _datasource;
self.userLimitsApi.getRenderLimits(self.owner, this);
},
function cacheAndReturnMapConfig(err, renderLimits) {
self.err = err;
self.mapConfig = (mapConfig === null) ? null : new MapConfig(mapConfig, datasource);
self.mapConfig = (mapConfig === null) ? null : new MapConfig(mapConfig, context.datasource);
self.analysesResults = context.analysesResults || [];
self.rendererParams = rendererParams;
self.context = context;
self.context.limits = renderLimits || {};
return callback(self.err, self.mapConfig, self.rendererParams, self.context);
}

View File

@@ -1,53 +0,0 @@
var queue = require('queue-async');
var _ = require('underscore');
function MapConfigOverviewsAdapter(overviewsMetadataApi) {
this.overviewsMetadataApi = overviewsMetadataApi;
}
module.exports = MapConfigOverviewsAdapter;
MapConfigOverviewsAdapter.prototype.getLayers = function(username, layers, callback) {
var self = this;
if (!layers || layers.length === 0) {
return callback(null, layers);
}
var augmentLayersQueue = queue(layers.length);
function augmentLayer(layer, done) {
if ( layer.type !== 'mapnik' && layer.type !== 'cartodb' ) {
return done(null, layer);
}
self.overviewsMetadataApi.getOverviewsMetadata(username, layer.options.sql, function(err, metadata){
if (err) {
done(err, layer);
} else {
if ( !_.isEmpty(metadata) ) {
layer = _.extend({}, layer);
layer.options = _.extend({}, layer.options, { query_rewrite_data: { overviews: metadata } });
}
done(null, layer);
}
});
}
function layersAugmentQueueFinish(err, layers) {
if (err) {
return callback(err);
}
if (!layers || layers.length === 0) {
return callback(new Error('Missing layers array from layergroup config'));
}
return callback(null, layers);
}
layers.forEach(function(layer) {
augmentLayersQueue.defer(augmentLayer, layer);
});
augmentLayersQueue.awaitAll(layersAugmentQueueFinish);
};

View File

@@ -3,7 +3,6 @@ var bodyParser = require('body-parser');
var RedisPool = require('redis-mpool');
var cartodbRedis = require('cartodb-redis');
var _ = require('underscore');
var debug = require('debug')('windshaft:cartodb');
var controller = require('./controllers');
@@ -21,6 +20,7 @@ var mapnik = windshaft.mapnik;
var TemplateMaps = require('./backends/template_maps.js');
var OverviewsMetadataApi = require('./api/overviews_metadata_api');
var FilterStatsApi = require('./api/filter_stats_api');
var UserLimitsApi = require('./api/user_limits_api');
var AuthApi = require('./api/auth_api');
var LayergroupAffectedTablesCache = require('./cache/layergroup_affected_tables');
@@ -33,10 +33,13 @@ var AnalysisBackend = require('./backends/analysis');
var timeoutErrorTilePath = __dirname + '/../../assets/render-timeout-fallback.png';
var timeoutErrorTile = require('fs').readFileSync(timeoutErrorTilePath, {encoding: null});
var MapConfigOverviewsAdapter = require('./models/mapconfig_overviews_adapter');
var TurboCartoParser = require('./utils/style/turbo-carto-parser');
var TurboCartoAdapter = require('./utils/style/turbo-carto-adapter');
var SqlWrapMapConfigAdapter = require('./models/mapconfig/adapter/sql-wrap-mapconfig-adapter');
var MapConfigNamedLayersAdapter = require('./models/mapconfig/adapter/mapconfig-named-layers-adapter');
var AnalysisMapConfigAdapter = require('./models/mapconfig/adapter/analysis-mapconfig-adapter');
var MapConfigOverviewsAdapter = require('./models/mapconfig/adapter/mapconfig-overviews-adapter');
var TurboCartoAdapter = require('./models/mapconfig/adapter/turbo-carto-adapter');
var DataviewsWidgetsAdapter = require('./models/mapconfig/adapter/dataviews-widgets-adapter');
var MapConfigAdapter = require('./models/mapconfig/adapter');
module.exports = function(serverOptions) {
// Make stats client globally accessible
@@ -59,6 +62,7 @@ module.exports = function(serverOptions) {
var pgConnection = new PgConnection(metadataBackend);
var pgQueryRunner = new PgQueryRunner(pgConnection);
var overviewsMetadataApi = new OverviewsMetadataApi(pgQueryRunner);
var filterStatsApi = new FilterStatsApi(pgQueryRunner);
var userLimitsApi = new UserLimitsApi(metadataBackend, {
limits: {
cacheOnTimeout: serverOptions.renderer.mapnik.limits.cacheOnTimeout || false,
@@ -143,24 +147,27 @@ module.exports = function(serverOptions) {
var tileBackend = new windshaft.backend.Tile(rendererCache);
var mapValidatorBackend = new windshaft.backend.MapValidator(tileBackend, attributesBackend);
var mapBackend = new windshaft.backend.Map(rendererCache, mapStore, mapValidatorBackend);
var analysisBackend = new AnalysisBackend(serverOptions.analysis);
var analysisBackend = new AnalysisBackend(metadataBackend, serverOptions.analysis);
var layergroupAffectedTablesCache = new LayergroupAffectedTablesCache();
app.layergroupAffectedTablesCache = layergroupAffectedTablesCache;
var overviewsAdapter = new MapConfigOverviewsAdapter(overviewsMetadataApi);
var turboCartoParser = new TurboCartoParser(pgQueryRunner);
var turboCartoAdapter = new TurboCartoAdapter(turboCartoParser);
var mapConfigAdapter = new MapConfigAdapter(
new MapConfigNamedLayersAdapter(templateMaps, pgConnection),
new SqlWrapMapConfigAdapter(),
new DataviewsWidgetsAdapter(),
new AnalysisMapConfigAdapter(analysisBackend),
new MapConfigOverviewsAdapter(overviewsMetadataApi, filterStatsApi),
new TurboCartoAdapter()
);
var namedMapProviderCache = new NamedMapProviderCache(
templateMaps,
pgConnection,
metadataBackend,
analysisBackend,
userLimitsApi,
overviewsAdapter,
turboCartoAdapter
mapConfigAdapter
);
['update', 'delete'].forEach(function(eventType) {
@@ -172,6 +179,8 @@ module.exports = function(serverOptions) {
var TablesExtentApi = require('./api/tables_extent_api');
var tablesExtentApi = new TablesExtentApi(pgQueryRunner);
var versions = getAndValidateVersions(serverOptions);
/*******************************************************************************************************************
* Routing
******************************************************************************************************************/
@@ -183,7 +192,6 @@ module.exports = function(serverOptions) {
tileBackend,
previewBackend,
attributesBackend,
new windshaft.backend.Widget(),
surrogateKeysCache,
userLimitsApi,
layergroupAffectedTablesCache,
@@ -199,9 +207,7 @@ module.exports = function(serverOptions) {
surrogateKeysCache,
userLimitsApi,
layergroupAffectedTablesCache,
overviewsAdapter,
turboCartoAdapter,
analysisBackend
mapConfigAdapter
).register(app);
new controller.NamedMaps(
@@ -217,7 +223,9 @@ module.exports = function(serverOptions) {
new controller.NamedMapsAdmin(authApi, pgConnection, templateMaps).register(app);
new controller.ServerInfo().register(app);
new controller.Analyses(authApi, pgConnection).register(app);
new controller.ServerInfo(versions).register(app);
/*******************************************************************************************************************
* END Routing
@@ -230,12 +238,45 @@ function validateOptions(opts) {
if (!_.isString(opts.base_url) || !_.isString(opts.base_url_mapconfig) || !_.isString(opts.base_url_templated)) {
throw new Error("Must initialise server with: 'base_url'/'base_url_mapconfig'/'base_url_templated' URLs");
}
}
// Be nice and warn if configured mapnik version is != instaled mapnik version
if (mapnik.versions.mapnik !== opts.grainstore.mapnik_version) {
debug('WARNING: detected mapnik version (' + mapnik.versions.mapnik + ')' +
' != configured mapnik version (' + opts.grainstore.mapnik_version + ')');
function getAndValidateVersions(options) {
// jshint undef:false
var warn = console.warn.bind(console);
// jshint undef:true
var packageDefinition = require('../../package.json');
var declaredDependencies = packageDefinition.dependencies || {};
var installedDependenciesVersions = {
camshaft: require('camshaft').version,
grainstore: windshaft.grainstore.version(),
mapnik: windshaft.mapnik.versions.mapnik,
node_mapnik: windshaft.mapnik.version,
'turbo-carto': require('turbo-carto').version,
windshaft: windshaft.version,
windshaft_cartodb: packageDefinition.version
};
var dependenciesToValidate = ['camshaft', 'turbo-carto', 'windshaft'];
dependenciesToValidate.forEach(function(depName) {
var declaredDependencyVersion = declaredDependencies[depName];
var installedDependencyVersion = installedDependenciesVersions[depName];
if (declaredDependencyVersion !== installedDependencyVersion) {
warn(
'Dependency="%s" installed version="%s" does not match declared version="%s". Check your installation.',
depName, installedDependencyVersion, declaredDependencyVersion
);
}
});
// Be nice and warn if configured mapnik version is != installed mapnik version
if (mapnik.versions.mapnik !== options.grainstore.mapnik_version) {
warn('WARNING: detected mapnik version (' + mapnik.versions.mapnik + ')' +
' != configured mapnik version (' + options.grainstore.mapnik_version + ')');
}
return installedDependenciesVersions;
}
function bootstrapFonts(opts) {

View File

@@ -36,7 +36,11 @@ var analysisConfig = _.defaults(global.environment.analysis || {}, {
inlineExecution: false,
endpoint: 'http://127.0.0.1:8080/api/v2/sql/job',
hostHeaderTemplate: '{{=it.username}}.localhost.lan'
}
},
logger: {
filename: undefined
},
limits: {}
});
module.exports = {
@@ -66,6 +70,7 @@ module.exports = {
},
datasource: global.environment.postgres,
cachedir: global.environment.millstone.cache_basedir,
use_workers: rendererConfig.mapnik.useCartocssWorkers || false,
mapnik_version: global.environment.mapnik_version,
mapnik_tile_format: global.environment.mapnik_tile_format || 'png',
default_layergroup_ttl: global.environment.mapConfigTTL || 7200
@@ -95,7 +100,11 @@ module.exports = {
inlineExecution: analysisConfig.batch.inlineExecution,
endpoint: analysisConfig.batch.endpoint,
hostHeaderTemplate: analysisConfig.batch.hostHeaderTemplate
}
},
logger: {
filename: analysisConfig.logger.filename
},
limits: analysisConfig.limits
},
// Do not send unwatch on release. See http://github.com/CartoDB/Windshaft-cartodb/issues/161
redis: _.extend(global.environment.redis, {unwatchOnRelease: false}),

View File

@@ -1,5 +1,26 @@
var _ = require('underscore');
var TableNameParser = require('./table_name_parser');
var BBoxFilter = require('../models/filter/bbox');
var AnalysisFilter = require('../models/filter/analysis');
// Minimim number of filtered rows to use overviews
var FILTER_MIN_ROWS = 65536;
// Maximum filtered fraction to not apply overviews
var FILTER_MAX_FRACTION = 0.2;
function apply_filters_to_query(query, filters, bbox_filter) {
if ( filters && !_.isEmpty(filters)) {
var analysisFilter = new AnalysisFilter(filters);
query = analysisFilter.sql(query);
}
if ( bbox_filter ) {
var bboxFilter = new BBoxFilter(bbox_filter.options, bbox_filter.params);
query = bboxFilter.sql(query);
}
return query;
}
function OverviewsQueryRewriter(options) {
this.options = options;
@@ -119,26 +140,34 @@ function replace_table_in_query(sql, old_table_name, replacement) {
return sql.replace(new RegExp(regexp, 'g'), replacement);
}
function overviews_query(query, overviews, zoom_level_expression) {
function replace_table_in_query_with_schema(query, table, schema, replacement) {
if ( replacement ) {
query = replace_table_in_query(query, table, replacement);
var parsed_table = TableNameParser.parse(table);
if (!parsed_table.schema && schema) {
// replace also the qualified table name, if the table wasn't qualified
parsed_table.schema = schema;
table = TableNameParser.table_identifier(parsed_table);
query = replace_table_in_query(query, table, replacement);
}
}
return query;
}
// Build query to use overviews for a variant zoom level (given by a expression to
// be evaluated by the database server)
function overviews_query_with_zoom_expression(query, overviews, zoom_level_expression) {
var replaced_query = query;
var sql = "WITH\n _vovw_scale AS ( SELECT " + zoom_level_expression + " AS _vovw_z )";
var replacement;
for ( var table in overviews ) {
if (overviews.hasOwnProperty(table)) {
var table_overviews = overviews[table];
var table_view = overviews_view_name(table);
var schema = table_overviews.schema;
replacement = "(\n" + overviews_view_for_table(table, table_overviews) + "\n ) AS " + table_view;
replaced_query = replace_table_in_query(replaced_query, table, replacement);
var parsed_table = TableNameParser.parse(table);
if (!parsed_table.schema && schema) {
// replace also the qualified table name, if the table wasn't qualified
parsed_table.schema = schema;
table = TableNameParser.table_identifier(parsed_table);
replaced_query = replace_table_in_query(replaced_query, table, replacement);
}
}
}
_.each(Object.keys(overviews), function(table) {
var table_overviews = overviews[table];
var table_view = overviews_view_name(table);
var schema = table_overviews.schema;
replacement = "(\n" + overviews_view_for_table(table, table_overviews) + "\n ) AS " + table_view;
replaced_query = replace_table_in_query_with_schema(replaced_query, table, schema, replacement);
});
if ( replaced_query !== query ) {
sql += "\n";
sql += replaced_query;
@@ -148,34 +177,128 @@ function overviews_query(query, overviews, zoom_level_expression) {
return sql;
}
// Build query to use overviews for a specific zoom level value
function overviews_query_with_definite_zoom(query, overviews, zoom_level) {
var replaced_query = query;
var replacement;
_.each(Object.keys(overviews), function(table) {
var table_overviews = overviews[table];
var schema = table_overviews.schema;
replacement = overview_table_for_zoom_level(table_overviews, zoom_level);
replaced_query = replace_table_in_query_with_schema(replaced_query, table, schema, replacement);
});
return replaced_query;
}
// Find a suitable overview table for a specific zoom_level
function overview_table_for_zoom_level(table_overviews, zoom_level) {
var overview_table;
if ( table_overviews ) {
overview_table = table_overviews[zoom_level];
if ( !overview_table ) {
_.every(Object.keys(table_overviews).sort(function(x,y){ return x-y; }), function(overview_zoom) {
if ( +overview_zoom > +zoom_level ) {
overview_table = table_overviews[overview_zoom];
return false;
} else {
return true;
}
});
}
}
if ( overview_table ) {
overview_table = overview_table.table;
}
return overview_table;
}
// Transform an SQL query so that it uses overviews.
// overviews contains metadata about the overviews to be used:
// { 'table-name': {1: { table: 'overview-table-1' }, ... }, ... }
//
// For a given query `SELECT * FROM table`, if any of tables in it
// has overviews as defined by the provided metadat, the query will
// be transform into something similar to this:
//
// WITH _vovw_scale AS ( ... ), -- define scale level
// WITH _vovw_table AS ( ... ), -- define union of overviews and base table
// SELECT * FROM _vovw_table -- query with table replaced by _vovw_table
// SELECT * FROM -- in the query the table is replaced by:
// ( ... ) AS _vovw_table -- a union of overviews and base table
//
// This transformation can in principle be applied to arbitrary queries
// (except for the case of queries that include the name of tables with
// overviews inside text literals: at the current table name substitution
// doesnn't prevent substitution inside literals).
// But the transformation will currently only be applied to simple queries
// of the form detected by the overviews_supported_query function.
OverviewsQueryRewriter.prototype.query = function(query, data) {
var overviews = this.overviews_metadata(data);
if ( !overviews || !this.is_supported_query(query)) {
// The data argument has the form:
// {
// overviews: // overview tables metadata
// { 'table-name': {1: { table: 'overview-table-1' }, ... }, ... },
// zoom_level: ..., // optional zoom level
// filters: ..., // filters definition
// unfiltered_query: ..., // query without the filters
// bbox_filter: ... // bounding-box filter
// }
OverviewsQueryRewriter.prototype.query = function(query, data, options) {
options = options || {};
data = data || {};
var overviews = data.overviews;
var unfiltered_query = data.unfiltered_query;
var filters = data.filters;
var bbox_filter = data.bbox_filter;
if ( !unfiltered_query ) {
unfiltered_query = query;
}
if ( !should_use_overviews(unfiltered_query, data) ) {
return query;
}
var zoom_level_expression = this.options.zoom_level || '0';
return overviews_query(query, overviews, zoom_level_expression);
var rewritten_query;
var zoom_level_expression = this.options.zoom_level;
var zoom_level = zoom_level_for_query(unfiltered_query, zoom_level_expression, options);
rewritten_query = overviews_query(unfiltered_query, overviews, zoom_level, zoom_level_expression);
if ( rewritten_query === unfiltered_query ) {
// could not or didn't need to alter the query
rewritten_query = query;
} else {
rewritten_query = apply_filters_to_query(rewritten_query, filters, bbox_filter);
}
return rewritten_query;
};
OverviewsQueryRewriter.prototype.is_supported_query = function(sql) {
function zoom_level_for_query(query, zoom_level_expression, options) {
var zoom_level = null;
if ( _.has(options, 'zoom_level') ) {
zoom_level = options.zoom_level || '0';
}
if ( zoom_level === null && !zoom_level_expression ) {
zoom_level = '0';
}
return zoom_level;
}
function overviews_query(query, overviews, zoom_level, zoom_level_expression) {
if ( zoom_level || zoom_level === '0' || zoom_level === 0 ) {
return overviews_query_with_definite_zoom(query, overviews, zoom_level);
} else {
return overviews_query_with_zoom_expression(query, overviews, zoom_level_expression);
}
}
function should_use_overviews(query, data) {
data = data || {};
var use_overviews = data.overviews && is_supported_query(query);
if ( use_overviews && data.filters && data.filter_stats ) {
var filtered_rows = data.filter_stats.filtered_rows;
var unfiltered_rows = data.filter_stats.unfiltered_rows;
if ( unfiltered_rows && (filtered_rows || filtered_rows === 0) ) {
use_overviews = filtered_rows >= FILTER_MIN_ROWS ||
(filtered_rows/unfiltered_rows) > FILTER_MAX_FRACTION;
}
}
return use_overviews;
}
function is_supported_query(sql) {
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');
@@ -187,8 +310,4 @@ OverviewsQueryRewriter.prototype.is_supported_query = function(sql) {
'i'
);
return !!(sql.match(unwrapped_query) || sql.match(wrapped_query));
};
OverviewsQueryRewriter.prototype.overviews_metadata = function(data) {
return data && data.overviews;
};
}

View File

@@ -1,59 +0,0 @@
'use strict';
var dot = require('dot');
dot.templateSettings.strip = false;
function createTemplate(method) {
return dot.template([
'SELECT',
method,
'FROM ({{=it._sql}}) _table_sql WHERE {{=it._column}} IS NOT NULL'
].join('\n'));
}
var methods = {
quantiles: 'CDB_QuantileBins(array_agg(distinct({{=it._column}}::numeric)), {{=it._buckets}}) as quantiles',
equal: 'CDB_EqualIntervalBins(array_agg({{=it._column}}::numeric), {{=it._buckets}}) as equal',
jenks: 'CDB_JenksBins(array_agg(distinct({{=it._column}}::numeric)), {{=it._buckets}}) as jenks',
headtails: 'CDB_HeadsTailsBins(array_agg(distinct({{=it._column}}::numeric)), {{=it._buckets}}) as headtails'
};
var methodTemplates = Object.keys(methods).reduce(function(methodTemplates, methodName) {
methodTemplates[methodName] = createTemplate(methods[methodName]);
return methodTemplates;
}, {});
var method2strategy = {
headtails: 'split'
};
function PostgresDatasource (pgQueryRunner, username, query) {
this.pgQueryRunner = pgQueryRunner;
this.username = username;
this.query = query;
}
PostgresDatasource.prototype.getName = function () {
return 'PostgresDatasource';
};
PostgresDatasource.prototype.getRamp = function (column, buckets, method, callback) {
var methodName = methods.hasOwnProperty(method) ? method : 'quantiles';
var template = methodTemplates[methodName];
var query = template({ _column: column, _sql: this.query, _buckets: buckets });
this.pgQueryRunner.run(this.username, query, function (err, result) {
if (err) {
return callback(err);
}
var ramp = result[0][methodName].sort(function(a, b) {
return a - b;
});
return callback(null, { ramp: ramp, strategy: method2strategy[methodName] });
});
};
module.exports = PostgresDatasource;

View File

@@ -1,70 +0,0 @@
'use strict';
var queue = require('queue-async');
var SubstitutionTokens = require('../substitution-tokens');
function TurboCartoAdapter(turboCartoParser) {
this.turboCartoParser = turboCartoParser;
}
module.exports = TurboCartoAdapter;
TurboCartoAdapter.prototype.getLayers = function (username, layers, callback) {
var self = this;
if (!layers || layers.length === 0) {
return callback(null, layers);
}
var parseCartoQueue = queue(layers.length);
layers.forEach(function(layer) {
parseCartoQueue.defer(self._parseCartoCss.bind(self), username, layer);
});
parseCartoQueue.awaitAll(function (err, layers) {
if (err) {
return callback(err);
}
return callback(null, layers);
});
};
TurboCartoAdapter.prototype._parseCartoCss = function (username, layer, callback) {
if (isNotLayerToParseCartocss(layer)) {
return process.nextTick(function () {
callback(null, layer);
});
}
var sql = SubstitutionTokens.replace(layer.options.sql, {
bbox: 'ST_MakeEnvelope(-20037508.34,-20037508.34,20037508.34,20037508.34,3857)',
scale_denominator: '500000001',
pixel_width: '156412',
pixel_height: '156412'
});
this.turboCartoParser.process(username, layer.options.cartocss, sql, function (err, cartocss) {
// Only return turbo-carto errors
if (err && err.name === 'TurboCartoError') {
err = new Error('turbo-carto: ' + err.message);
err.http_status = 400;
return callback(err);
}
// Try to continue in the rest of the cases
if (cartocss) {
layer.options.cartocss = cartocss;
}
return callback(null, layer);
});
};
function isNotLayerToParseCartocss(layer) {
if (!layer || !layer.options || !layer.options.cartocss || !layer.options.sql) {
return true;
}
return false;
}

View File

@@ -1,15 +0,0 @@
'use strict';
var turboCarto = require('turbo-carto');
var PostgresDatasource = require('./postgres-datasource');
function TurboCartoParser (pgQueryRunner) {
this.pgQueryRunner = pgQueryRunner;
}
module.exports = TurboCartoParser;
TurboCartoParser.prototype.process = function (username, cartocss, sql, callback) {
var datasource = new PostgresDatasource(this.pgQueryRunner, username, sql);
turboCarto(cartocss, datasource, callback);
};

View File

@@ -6,6 +6,16 @@ var SUBSTITUTION_TOKENS = {
};
var SubstitutionTokens = {
tokens: function(sql) {
return Object.keys(SUBSTITUTION_TOKENS).filter(function(tokenName) {
return !!sql.match(SUBSTITUTION_TOKENS[tokenName]);
});
},
hasTokens: function(sql) {
return this.tokens(sql).length > 0;
},
replace: function(sql, replaceValues) {
Object.keys(replaceValues).forEach(function(token) {
if (SUBSTITUTION_TOKENS[token]) {

4007
npm-shrinkwrap.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,7 @@
{
"private": true,
"name": "windshaft-cartodb",
"version": "2.42.2",
"version": "3.0.0",
"description": "A map tile server for CartoDB",
"keywords": [
"cartodb"
@@ -20,33 +20,34 @@
],
"dependencies": {
"body-parser": "~1.14.0",
"camshaft": "0.8.0",
"cartodb-psql": "~0.6.1",
"cartodb-query-tables": "~0.1.0",
"cartodb-redis": "~0.13.0",
"camshaft": "0.50.3",
"cartodb-psql": "~0.7.1",
"cartodb-query-tables": "0.2.0",
"cartodb-redis": "0.13.2",
"debug": "~2.2.0",
"dot": "~1.0.2",
"express": "~4.13.3",
"fastly-purge": "~1.0.1",
"log4js": "https://github.com/CartoDB/log4js-node/tarball/cdb",
"log4js": "cartodb/log4js-node#cdb",
"lru-cache": "2.6.5",
"lzma": "~1.3.7",
"lzma": "~2.3.2",
"node-statsd": "~0.0.7",
"queue-async": "~1.0.7",
"redis-mpool": "~0.4.0",
"request": "~2.62.0",
"redis-mpool": "0.4.1",
"request": "~2.79.0",
"step": "~0.0.6",
"step-profiler": "~0.3.0",
"turbo-carto": "0.9.2",
"turbo-carto": "0.19.0",
"underscore": "~1.6.0",
"windshaft": "1.19.0"
"windshaft": "3.0.0",
"yargs": "~5.0.0"
},
"devDependencies": {
"istanbul": "~0.4.3",
"jshint": "~2.6.0",
"mocha": "~1.21.4",
"nock": "~2.11.0",
"redis": "~0.8.6",
"redis": "~0.12.1",
"semver": "~1.1.4",
"strftime": "~0.8.2"
},
@@ -55,7 +56,7 @@
"test": "make test-all"
},
"engines": {
"node": ">=0.8 <0.11",
"npm": ">=2.14.16"
"node": ">=6.9",
"npm": ">=3.10"
}
}

View File

@@ -5,6 +5,7 @@ OPT_CREATE_PGSQL=yes # create the PostgreSQL test environment
OPT_DROP_REDIS=yes # drop the redis test environment
OPT_DROP_PGSQL=yes # drop the PostgreSQL test environment
OPT_COVERAGE=no # run tests with coverage
OPT_DOWNLOAD_SQL=yes # download a fresh copy of sql files
export PGAPPNAME=cartodb_tiler_tester
@@ -73,6 +74,10 @@ while [ -n "$1" ]; do
OPT_CREATE_REDIS=no
shift
continue
elif test "$1" = "--no-sql-download"; then
OPT_DOWNLOAD_SQL=no
shift
continue
elif test "$1" = "--with-coverage"; then
OPT_COVERAGE=yes
shift
@@ -113,6 +118,9 @@ fi
if test x"$OPT_CREATE_REDIS" != xyes; then
PREPARE_DB_OPTS="$PREPARE_DB_OPTS --skip-redis"
fi
if test x"$OPT_DOWNLOAD_SQL" != xyes; then
PREPARE_DB_OPTS="$PREPARE_DB_OPTS --no-sql-download"
fi
echo "Preparing the environment"
cd ${BASEDIR}/test/support

View File

@@ -202,7 +202,7 @@ describe('analysis-layers', function() {
assert.equal(analyses.length, 1, 'Invalid number of analyses in metadata');
var nodes = analyses[0].nodes;
var nodesIds = Object.keys(nodes);
assert.deepEqual(nodesIds, ['2570e105-7b37-40d2-bdf4-1af889598745', 'HEAD']);
assert.deepEqual(nodesIds, ['HEAD', '2570e105-7b37-40d2-bdf4-1af889598745']);
nodesIds.forEach(function(nodeId) {
var node = nodes[nodeId];
assert.ok(node.hasOwnProperty('url'), 'Missing "url" attribute in node');

View File

@@ -20,6 +20,13 @@ describe('analysis-layers error cases', function() {
}
};
var AUTH_ERROR_RESPONSE = {
status: 403,
headers: {
'Content-Type': 'application/json; charset=utf-8'
}
};
it('should handle missing analysis nodes for layers', function(done) {
var mapConfig = createMapConfig(
[
@@ -64,4 +71,307 @@ describe('analysis-layers error cases', function() {
testClient.drain(done);
});
});
it('should handle missing analyses when layers point to nonexistent one', function(done) {
var mapConfig = createMapConfig(
[
{
"type": "http",
"options": {
"urlTemplate": "http://{s}.basemaps.cartocdn.com/light_only_labels/{z}/{x}/{y}.png",
"subdomains": "abcd"
}
},
{
"type": "cartodb",
"options": {
"source": {
"id": "ID-FOR-NONEXISTENT-ANALYSIS"
},
"cartocss": '#polygons { polygon-fill: red; }',
"cartocss_version": "2.3.0"
}
}
]
);
var testClient = new TestClient(mapConfig, 1234);
testClient.getLayergroup(ERROR_RESPONSE, function(err, layergroupResult) {
assert.ok(!err, err);
assert.equal(layergroupResult.errors.length, 1);
assert.equal(
layergroupResult.errors[0],
'Missing analysis node.id="ID-FOR-NONEXISTENT-ANALYSIS" for layer=1'
);
testClient.drain(done);
});
});
it('should handle missing analyses when dataviews point to nonexistent one', function(done) {
var mapConfig = createMapConfig(
[
{
"type": "http",
"options": {
"urlTemplate": "http://{s}.basemaps.cartocdn.com/light_only_labels/{z}/{x}/{y}.png",
"subdomains": "abcd"
}
},
{
"type": "cartodb",
"options": {
"sql": "select * from populated_places_simple_reduced",
"cartocss": '#polygons { polygon-fill: red; }',
"cartocss_version": "2.3.0"
}
}
],
{
pop_max_histogram: {
source: {
id: 'ID-FOR-NONEXISTENT-ANALYSIS'
},
type: 'histogram',
options: {
column: 'pop_max'
}
}
}
);
var testClient = new TestClient(mapConfig, 1234);
testClient.getLayergroup(ERROR_RESPONSE, function(err, layergroupResult) {
assert.ok(!err, err);
assert.equal(layergroupResult.errors.length, 1);
assert.equal(layergroupResult.errors[0], 'Node with `source.id="ID-FOR-NONEXISTENT-ANALYSIS"`' +
' not found in analyses for dataview "pop_max_histogram"');
testClient.drain(done);
});
});
it('camshaft: should return error missing analysis nodes for layers with some context', function(done) {
var mapConfig = createMapConfig(
[
{
"type": "cartodb",
"options": {
"source": {
"id": "HEAD"
},
"cartocss": '#polygons { polygon-fill: red; }',
"cartocss_version": "2.3.0"
}
}
],
{},
[
{
"id": "HEAD",
"type": "buffer",
"params": {
"source": {
"id": "HEAD",
"type": "source",
"params": {
"query": "select * from populated_places_simple_reduced"
}
},
"radius": 50000
}
}
]
);
var testClient = new TestClient(mapConfig, 11111);
testClient.getLayergroup(AUTH_ERROR_RESPONSE, function(err, layergroupResult) {
assert.ok(!err, err);
assert.equal(layergroupResult.errors.length, 1);
assert.equal(
layergroupResult.errors[0],
'Analysis requires authentication with API key: permission denied.'
);
assert.equal(layergroupResult.errors_with_context[0].type, 'analysis');
assert.equal(
layergroupResult.errors_with_context[0].message,
'Analysis requires authentication with API key: permission denied.'
);
assert.equal(layergroupResult.errors_with_context[0].analysis.id, 'HEAD');
assert.equal(layergroupResult.errors_with_context[0].analysis.type, 'buffer');
testClient.drain(done);
});
});
it('camshaft: should return error: Missing required param "radius"; with context', function(done) {
var mapConfig = createMapConfig(
[
{
"type": "cartodb",
"options": {
"source": {
"id": "HEAD"
},
"cartocss": '#polygons { polygon-fill: red; }',
"cartocss_version": "2.3.0"
}
}
],
{},
[
{
"id": "HEAD",
"type": "buffer",
"params": {
"source": {
"id": "HEAD2",
"type": "source",
"params": {
"query": "select * from populated_places_simple_reduced"
}
}
}
}
]
);
var testClient = new TestClient(mapConfig, 1234);
testClient.getLayergroup(ERROR_RESPONSE, function(err, layergroupResult) {
assert.ok(!err, err);
assert.equal(layergroupResult.errors.length, 1);
assert.equal(
layergroupResult.errors[0],
'Missing required param "radius"'
);
assert.equal(layergroupResult.errors_with_context[0].type, 'analysis');
assert.equal(layergroupResult.errors_with_context[0].message, 'Missing required param "radius"');
assert.equal(layergroupResult.errors_with_context[0].analysis.id, 'HEAD');
assert.equal(layergroupResult.errors_with_context[0].analysis.type, 'buffer');
testClient.drain(done);
});
});
it('should return missing param error of outer node indicating the node_id and context', function(done) {
var mapConfig = createMapConfig([{
"type": "cartodb",
"options": {
"source": {
"id": "HEAD"
},
"cartocss": '#polygons { polygon-fill: red; }',
"cartocss_version": "2.3.0"
}
}], {}, [{
"id": "HEAD",
"type": "buffer",
"params": {
"source": {
"id": "HEAD2",
"type": "buffer",
"params": {
"source": {
"id": "HEAD3",
"type": "source",
"params": {
"query": "select * from populated_places_simple_reduced"
}
},
"radius": 10
}
}
}
// radius: 'missing'
}]);
var testClient = new TestClient(mapConfig, 1234);
testClient.getLayergroup(ERROR_RESPONSE, function(err, layergroupResult) {
assert.ok(!err, err);
assert.equal(layergroupResult.errors.length, 1);
assert.equal(
layergroupResult.errors[0],
'Missing required param "radius"'
);
assert.equal(layergroupResult.errors_with_context[0].type, 'analysis');
assert.equal(layergroupResult.errors_with_context[0].message, 'Missing required param "radius"');
assert.equal(layergroupResult.errors_with_context[0].analysis.id, 'HEAD');
assert.equal(layergroupResult.errors_with_context[0].analysis.type, 'buffer');
assert.equal(layergroupResult.errors_with_context[0].analysis.node_id, 'HEAD');
testClient.drain(done);
});
});
it('should return invalid param type error of inner node indicating the node_id and context', function(done) {
var mapConfig = createMapConfig([{
"type": "cartodb",
"options": {
"source": {
"id": "HEAD"
},
"cartocss": '#polygons { polygon-fill: red; }',
"cartocss_version": "2.3.0"
}
}], {}, [{
"id": "HEAD",
"type": "buffer",
"params": {
"source": {
"id": "HEAD2",
"type": "buffer",
"params": {
"source": {
"id": "HEAD3",
"type": "source",
"params": {
"query": "select * from populated_places_simple_reduced"
}
},
"radius": 'invalid_radius'
}
},
"radius": 10
}
}]);
var testClient = new TestClient(mapConfig, 1234);
testClient.getLayergroup(ERROR_RESPONSE, function(err, layergroupResult) {
assert.ok(!err, err);
assert.equal(layergroupResult.errors.length, 1);
assert.equal(
layergroupResult.errors[0],
'Invalid type for param "radius", expects "number" type, got `"invalid_radius"`'
);
assert.equal(layergroupResult.errors_with_context[0].type, 'analysis');
assert.equal(
layergroupResult.errors_with_context[0].message,
'Invalid type for param "radius", expects "number" type, got `"invalid_radius"`'
);
assert.equal(layergroupResult.errors_with_context[0].analysis.id, 'HEAD');
assert.equal(layergroupResult.errors_with_context[0].analysis.type, 'buffer');
assert.equal(layergroupResult.errors_with_context[0].analysis.node_id, 'HEAD2');
testClient.drain(done);
});
});
});

View File

@@ -7,7 +7,7 @@ var serverOptions = require('../../../lib/cartodb/server_options');
var server = new CartodbWindshaft(serverOptions);
var TestClient = require('../../support/test-client');
var LayergroupToken = require('../../../lib/cartodb/models/layergroup_token');
var LayergroupToken = require('../../support/layergroup-token');
describe('named-maps analysis', function() {
@@ -141,7 +141,7 @@ describe('named-maps analysis', function() {
assert.equal(analyses.length, 1, 'Invalid number of analyses in metadata');
var nodes = analyses[0].nodes;
var nodesIds = Object.keys(nodes);
assert.deepEqual(nodesIds, ['2570e105-7b37-40d2-bdf4-1af889598745', 'HEAD']);
assert.deepEqual(nodesIds, ['HEAD', '2570e105-7b37-40d2-bdf4-1af889598745']);
nodesIds.forEach(function(nodeId) {
var node = nodes[nodeId];
assert.ok(node.hasOwnProperty('url'), 'Missing "url" attribute in node');

View File

@@ -0,0 +1,115 @@
require('../../support/test_helper');
var assert = require('../../support/assert');
var TestClient = require('../../support/test-client');
describe('analysis-layers regressions', function() {
it('should return a complete list of nodes from analysis', function(done) {
var mapConfig = {
"version": "1.5.0",
"layers": [
{
"type": "cartodb",
"options": {
"cartocss": TestClient.CARTOCSS.POINTS,
"cartocss_version": "2.1.1",
"interactivity": [],
"source": {
"id": "a4"
}
}
},
{
"type": "cartodb",
"options": {
"cartocss": TestClient.CARTOCSS.POINTS,
"cartocss_version": "2.1.0",
"interactivity": [],
"source": {
"id": "b1"
}
}
}
],
"dataviews": {
"74493a30-4679-4b72-a60c-b6f808b57c98": {
"type": "histogram",
"source": {
"id": "b0"
},
"options": {
"column": "customer_value",
"bins": 10
}
}
},
"analyses": [
{
"id": "a4",
"type": "point-in-polygon",
"params": {
"polygons_source": {
"id": "a3",
"type": "buffer",
"params": {
"source": {
"id": "a2",
"type": "centroid",
"params": {
"source": {
"id": "b1",
"type": "kmeans",
"params": {
"source": {
"id": "b0",
"type": "source",
"params": {
"query": "SELECT * FROM populated_places_simple_reduced"
}
},
"clusters": 5
}
},
"category_column": "cluster_no"
}
},
"radius": 200000
}
},
"points_source": {
"id": "customer_home_locations",
"type": "source",
"params": {
"query": "SELECT * FROM populated_places_simple_reduced"
}
}
}
}
]
};
var testClient = new TestClient(mapConfig, 1234);
testClient.getLayergroup(function(err, layergroupResult) {
assert.ok(!err, err);
assert.ok(layergroupResult);
assert.ok(layergroupResult.metadata);
var analyses = layergroupResult.metadata.analyses;
assert.ok(analyses);
assert.equal(analyses.length, 1);
var expectedIds = ['customer_home_locations', 'b0', 'b1', 'a2', 'a3', 'a4'];
expectedIds.forEach(function(expectedId) {
assert.ok(
analyses[0].nodes.hasOwnProperty(expectedId),
'Missing "' + expectedId + '" from node list.'
);
});
assert.equal(Object.keys(analyses[0].nodes).length, expectedIds.length, Object.keys(analyses[0].nodes));
testClient.drain(done);
});
});
});

View File

@@ -0,0 +1,148 @@
require('../../support/test_helper');
var assert = require('../../support/assert');
var TestClient = require('../../support/test-client');
describe('aggregations happy cases', function() {
afterEach(function(done) {
if (this.testClient) {
this.testClient.drain(done);
} else {
done();
}
});
function aggregationOperationMapConfig(operation, query, column, aggregationColumn) {
column = column || 'adm0name';
aggregationColumn = aggregationColumn || 'pop_max';
query = query || 'select * from populated_places_simple_reduced';
var mapConfig = {
version: '1.5.0',
layers: [
{
type: 'mapnik',
options: {
sql: query,
cartocss: '#layer0 { marker-fill: red; marker-width: 10; }',
cartocss_version: '2.0.1',
widgets: {}
}
}
]
};
mapConfig.layers[0].options.widgets[column] = {
type: 'aggregation',
options: {
column: column,
aggregation: operation,
aggregationColumn: aggregationColumn
}
};
return mapConfig;
}
var operations = ['count', 'sum', 'avg', 'max', 'min'];
operations.forEach(function(operation) {
it('should be able to use "' + operation + '" as aggregation operation', function(done) {
this.testClient = new TestClient(aggregationOperationMapConfig(operation));
this.testClient.getDataview('adm0name', { own_filter: 0 }, function (err, aggregation) {
assert.ok(!err, err);
assert.ok(aggregation);
assert.equal(aggregation.type, 'aggregation');
assert.equal(aggregation.aggregation, operation);
done();
});
});
});
var query = [
'select 1 as val, \'a\' as cat, ST_Transform(ST_SetSRID(ST_MakePoint(0,0),4326),3857) as the_geom_webmercator',
'select null, \'b\', ST_Transform(ST_SetSRID(ST_MakePoint(0,1),4326),3857)',
'select null, \'b\', ST_Transform(ST_SetSRID(ST_MakePoint(1,0),4326),3857)',
'select null, null, ST_Transform(ST_SetSRID(ST_MakePoint(1,1),4326),3857)'
].join(' UNION ALL ');
operations.forEach(function (operation) {
var not = operation === 'count' ? ' not ' : ' ';
var description = 'should' +
not +
'handle NULL values in category and aggregation columns using "' +
operation +
'" as aggregation operation';
it(description, function (done) {
this.testClient = new TestClient(aggregationOperationMapConfig(operation, query, 'cat', 'val'));
this.testClient.getDataview('cat', { own_filter: 0 }, function (err, aggregation) {
assert.ifError(err);
assert.ok(aggregation);
assert.equal(aggregation.type, 'aggregation');
assert.ok(aggregation.categories);
assert.equal(aggregation.categoriesCount, 3);
assert.equal(aggregation.count, 4);
assert.equal(aggregation.nulls, 1);
var hasNullCategory = false;
aggregation.categories.forEach(function (category) {
if (category.category === null) {
hasNullCategory = true;
}
});
if (operation === 'count') {
assert.ok(hasNullCategory, 'aggregation has not a category NULL');
} else {
assert.ok(!hasNullCategory, 'aggregation has category NULL');
}
done();
});
});
});
var operations_and_values = {'count': 9, 'sum': 45, 'avg': 5, 'max': 9, 'min': 1};
var query_other = [
'select generate_series(1,3) as val, \'other_a\' as cat, NULL as the_geom_webmercator',
'select generate_series(4,6) as val, \'other_b\' as cat, NULL as the_geom_webmercator',
'select generate_series(7,9) as val, \'other_c\' as cat, NULL as the_geom_webmercator',
'select generate_series(10,12) as val, \'category_1\' as cat, NULL as the_geom_webmercator',
'select generate_series(10,12) as val, \'category_2\' as cat, NULL as the_geom_webmercator',
'select generate_series(10,12) as val, \'category_3\' as cat, NULL as the_geom_webmercator',
'select generate_series(10,12) as val, \'category_4\' as cat, NULL as the_geom_webmercator',
'select generate_series(10,12) as val, \'category_5\' as cat, NULL as the_geom_webmercator'
].join(' UNION ALL ');
Object.keys(operations_and_values).forEach(function (operation) {
var description = 'should aggregate OTHER category using "' + operation + '"';
it(description, function (done) {
this.testClient = new TestClient(aggregationOperationMapConfig(operation, query_other, 'cat', 'val'));
this.testClient.getDataview('cat', { own_filter: 0 }, function (err, aggregation) {
assert.ifError(err);
assert.ok(aggregation);
assert.equal(aggregation.type, 'aggregation');
assert.ok(aggregation.categories);
assert.equal(aggregation.categoriesCount, 8);
assert.equal(aggregation.count, 24);
assert.equal(aggregation.nulls, 0);
var aggregated_categories = aggregation.categories.filter( function(category) {
return category.agg === true;
});
assert.equal(aggregated_categories.length, 1);
assert.equal(aggregated_categories[0].value, operations_and_values[operation]);
done();
});
});
});
});

View File

@@ -0,0 +1,87 @@
require('../../support/test_helper');
var assert = require('../../support/assert');
var TestClient = require('../../support/test-client');
describe('histogram-dataview', function() {
afterEach(function(done) {
if (this.testClient) {
this.testClient.drain(done);
} else {
done();
}
});
var ERROR_RESPONSE = {
status: 400,
headers: {
'Content-Type': 'application/json; charset=utf-8'
}
};
function createMapConfig(dataviews) {
return {
version: '1.5.0',
layers: [
{
"type": "cartodb",
"options": {
"source": {
"id": "HEAD"
},
"cartocss": "#points { marker-width: 10; marker-fill: red; }",
"cartocss_version": "2.3.0"
}
}
],
dataviews: dataviews,
analyses: [
{
"id": "HEAD",
"type": "source",
"params": {
"query": "select null::geometry the_geom_webmercator, x from generate_series(0,1000) x"
}
}
]
};
}
it('should fail when invalid dataviews object is provided, string case', function(done) {
var mapConfig = createMapConfig("wadus-string");
this.testClient = new TestClient(mapConfig, 1234);
this.testClient.getLayergroup(ERROR_RESPONSE, function(err, errObj) {
assert.ok(!err, err);
assert.deepEqual(errObj.errors, [ '"dataviews" must be a valid JSON object: "string" type found' ]);
done();
});
});
it('should fail when invalid dataviews object is provided, array case', function(done) {
var mapConfig = createMapConfig([]);
this.testClient = new TestClient(mapConfig, 1234);
this.testClient.getLayergroup(ERROR_RESPONSE, function(err, errObj) {
assert.ok(!err, err);
assert.deepEqual(errObj.errors, [ '"dataviews" must be a valid JSON object: "array" type found' ]);
done();
});
});
it('should work with empty but valid objects', function(done) {
var mapConfig = createMapConfig({});
this.testClient = new TestClient(mapConfig, 1234);
this.testClient.getLayergroup(function(err, layergroup) {
assert.ok(!err, err);
assert.ok(layergroup);
assert.ok(layergroup.layergroupid);
done();
});
});
});

View File

@@ -0,0 +1,100 @@
require('../../support/test_helper');
var assert = require('../../support/assert');
var TestClient = require('../../support/test-client');
describe('histogram-dataview', function() {
afterEach(function(done) {
if (this.testClient) {
this.testClient.drain(done);
} else {
done();
}
});
function createMapConfig(layers, dataviews, analysis) {
return {
version: '1.5.0',
layers: layers,
dataviews: dataviews || {},
analyses: analysis || []
};
}
var mapConfig = createMapConfig(
[
{
"type": "cartodb",
"options": {
"source": {
"id": "2570e105-7b37-40d2-bdf4-1af889598745"
},
"cartocss": "#points { marker-width: 10; marker-fill: red; }",
"cartocss_version": "2.3.0"
}
}
],
{
pop_max_histogram: {
source: {
id: '2570e105-7b37-40d2-bdf4-1af889598745'
},
type: 'histogram',
options: {
column: 'x'
}
}
},
[
{
"id": "2570e105-7b37-40d2-bdf4-1af889598745",
"type": "source",
"params": {
"query": "select null::geometry the_geom_webmercator, x from generate_series(0,1000) x"
}
}
]
);
it('should get bin_width right when max > min in filter', function(done) {
var params = {
bins: 10,
start: 1e3,
end: 0
};
this.testClient = new TestClient(mapConfig, 1234);
this.testClient.getDataview('pop_max_histogram', params, function(err, dataview) {
assert.ok(!err, err);
assert.equal(dataview.type, 'histogram');
assert.ok(dataview.bin_width > 0, 'Unexpected bin width: ' + dataview.bin_width);
dataview.bins.forEach(function(bin) {
assert.ok(bin.min <= bin.max, 'bin min < bin max: ' + JSON.stringify(bin));
});
done();
});
});
it('should cast all overridable params to numbers', function(done) {
var params = {
bins: '256 AS other, (select 256 * 2) AS bins_number--',
start: 1e3,
end: 0,
response: TestClient.RESPONSE.ERROR
};
this.testClient = new TestClient(mapConfig, 1234);
this.testClient.getDataview('pop_max_histogram', params, function(err, res) {
assert.ok(!err, err);
assert.ok(res.errors);
assert.equal(res.errors.length, 1);
assert.ok(res.errors[0].match(/Invalid number format for parameter 'bins'/));
done();
});
});
});

View File

@@ -0,0 +1,449 @@
require('../../support/test_helper');
var assert = require('../../support/assert');
var TestClient = require('../../support/test-client');
describe('dataviews using tables without overviews', function() {
var nonOverviewsMapConfig = {
version: '1.5.0',
analyses: [
{ id: 'data-source',
type: 'source',
params: {
query: 'select * from populated_places_simple_reduced'
}
}
],
dataviews: {
country_places_count: {
type: 'formula',
source: {id: 'data-source'},
options: {
column: 'adm0_a3',
operation: 'count'
}
},
country_categories: {
type: 'aggregation',
source: {id: 'data-source'},
options: {
column: 'adm0_a3',
aggregation: 'count'
}
}
},
layers: [
{
type: 'mapnik',
options: {
sql: 'select * from populated_places_simple_reduced',
cartocss: '#layer { marker-fill: red; marker-width: 32; marker-allow-overlap: true; }',
cartocss_version: '2.3.0',
source: { id: 'data-source' }
}
}
]
};
it("should expose a formula", function(done) {
var testClient = new TestClient(nonOverviewsMapConfig);
testClient.getDataview('country_places_count', { own_filter: 0 }, function(err, formula_result) {
if (err) {
return done(err);
}
assert.deepEqual(formula_result, { operation: 'count', result: 7313, nulls: 0, type: 'formula' });
testClient.drain(done);
});
});
it("should admit a bbox", function(done) {
var params = {
bbox: "-170,-80,170,80"
};
var testClient = new TestClient(nonOverviewsMapConfig);
testClient.getDataview('country_places_count', params, function(err, formula_result) {
if (err) {
return done(err);
}
assert.deepEqual(formula_result, { operation: 'count', result: 7253, nulls: 0, type: 'formula' });
testClient.drain(done);
});
});
describe('filters', function() {
describe('category', function () {
it("should expose a filtered formula", function (done) {
var params = {
filters: {
dataviews: {country_categories: {accept: ['CAN']}}
}
};
var testClient = new TestClient(nonOverviewsMapConfig);
testClient.getDataview('country_places_count', params, function (err, formula_result) {
if (err) {
return done(err);
}
assert.deepEqual(formula_result, { operation: 'count', result: 256, nulls: 0, type: 'formula' });
testClient.drain(done);
});
});
it("should expose a filtered formula and admit a bbox", function (done) {
var params = {
filters: {
dataviews: {country_categories: {accept: ['CAN']}}
},
bbox: "-170,-80,170,80"
};
var testClient = new TestClient(nonOverviewsMapConfig);
testClient.getDataview('country_places_count', params, function (err, formula_result) {
if (err) {
return done(err);
}
assert.deepEqual(formula_result, { operation: 'count', result: 254, nulls: 0, type: 'formula' });
testClient.drain(done);
});
});
});
});
});
describe('dataviews using tables with overviews', function() {
var overviewsMapConfig = {
version: '1.5.0',
analyses: [
{ id: 'data-source',
type: 'source',
params: {
query: 'select * from test_table_overviews'
}
}
],
dataviews: {
test_sum: {
type: 'formula',
source: {id: 'data-source'},
options: {
column: 'value',
operation: 'sum'
}
},
test_categories: {
type: 'aggregation',
source: {id: 'data-source'},
options: {
column: 'name',
aggregation: 'count',
aggregationColumn: 'name',
}
},
test_histogram: {
type: 'histogram',
source: {id: 'data-source'},
options: {
column: 'value',
bins: 2
}
},
test_histogram_date: {
type: 'histogram',
source: {id: 'data-source'},
options: {
column: 'updated_at',
bins: 2
}
},
test_avg: {
type: 'formula',
source: {id: 'data-source'},
options: {
column: 'value',
operation: 'avg'
}
},
test_count: {
type: 'formula',
source: {id: 'data-source'},
options: {
column: 'value',
operation: 'count'
}
},
test_min: {
type: 'formula',
source: {id: 'data-source'},
options: {
column: 'value',
operation: 'min'
}
},
test_max: {
type: 'formula',
source: {id: 'data-source'},
options: {
column: 'value',
operation: 'max'
}
}
},
layers: [
{
type: 'mapnik',
options: {
sql: 'select * from test_table_overviews',
cartocss: '#layer { marker-fill: red; marker-width: 32; marker-allow-overlap: true; }',
cartocss_version: '2.3.0',
source: { id: 'data-source' }
}
}
]
};
it("should expose a sum formula", function(done) {
var testClient = new TestClient(overviewsMapConfig);
testClient.getDataview('test_sum', { own_filter: 0 }, function(err, formula_result) {
if (err) {
return done(err);
}
assert.deepEqual(formula_result, {"operation":"sum","result":15,"nulls":0,"type":"formula"});
testClient.drain(done);
});
});
it("should expose an avg formula", function(done) {
var testClient = new TestClient(overviewsMapConfig);
testClient.getDataview('test_avg', { own_filter: 0 }, function(err, formula_result) {
if (err) {
return done(err);
}
assert.deepEqual(formula_result, {"operation":"avg","result":3,"nulls":0,"type":"formula"});
testClient.drain(done);
});
});
it("should expose a count formula", function(done) {
var testClient = new TestClient(overviewsMapConfig);
testClient.getDataview('test_count', { own_filter: 0 }, function(err, formula_result) {
if (err) {
return done(err);
}
assert.deepEqual(formula_result, {"operation":"count","result":5,"nulls":0,"type":"formula"});
testClient.drain(done);
});
});
it("should expose a max formula", function(done) {
var testClient = new TestClient(overviewsMapConfig);
testClient.getDataview('test_max', { own_filter: 0 }, function(err, formula_result) {
if (err) {
return done(err);
}
assert.deepEqual(formula_result, {"operation":"max","result":5,"nulls":0,"type":"formula"});
testClient.drain(done);
});
});
it("should expose a min formula", function(done) {
var testClient = new TestClient(overviewsMapConfig);
testClient.getDataview('test_min', { own_filter: 0 }, function(err, formula_result) {
if (err) {
return done(err);
}
assert.deepEqual(formula_result, {"operation":"min","result":1,"nulls":0,"type":"formula"});
testClient.drain(done);
});
});
it("should admit a bbox", function(done) {
var params = {
bbox: "-170,-80,170,80"
};
var testClient = new TestClient(overviewsMapConfig);
testClient.getDataview('test_sum', params, function(err, formula_result) {
if (err) {
return done(err);
}
assert.deepEqual(formula_result, {"operation":"sum","result":15,"nulls":0,"type":"formula"});
testClient.drain(done);
});
});
it("should expose a histogram", function (done) {
var testClient = new TestClient(overviewsMapConfig);
testClient.getDataview('test_histogram', function (err, histogram) {
if (err) {
return done(err);
}
assert.ok(histogram);
assert.equal(histogram.type, 'histogram');
assert.ok(Array.isArray(histogram.bins));
testClient.drain(done);
});
});
describe('filters', function() {
describe('histogram', function () {
it("should expose a filtered histogram", function (done) {
var params = {
filters: {
dataviews: { test_histogram: { min: 2 } }
}
};
var testClient = new TestClient(overviewsMapConfig);
testClient.getDataview('test_histogram', params, function (err, histogram) {
if (err) {
return done(err);
}
assert.ok(histogram);
assert.equal(histogram.type, 'histogram');
assert.ok(Array.isArray(histogram.bins));
assert.equal(histogram.bins.length, 4);
testClient.drain(done);
});
});
it("should expose a filtered histogram with no results", function (done) {
var params = {
filters: {
dataviews: { test_histogram: { max: -1 } }
}
};
var testClient = new TestClient(overviewsMapConfig);
testClient.getDataview('test_histogram', params, function (err, histogram) {
if (err) {
return done(err);
}
assert.ok(histogram);
assert.equal(histogram.type, 'histogram');
assert.ok(Array.isArray(histogram.bins));
assert.equal(histogram.bins.length, 0);
testClient.drain(done);
});
});
it("should expose a filtered date histogram with no results", function (done) {
// This most likely works because the overviews will pass
// the responsibility to the normal dataviews.
var params = {
filters: {
dataviews: { test_histogram_date: { max: -1 } }
}
};
var testClient = new TestClient(overviewsMapConfig);
testClient.getDataview('test_histogram_date', params, function (err, histogram) {
if (err) {
return done(err);
}
assert.ok(histogram);
assert.equal(histogram.type, 'histogram');
assert.ok(Array.isArray(histogram.bins));
assert.equal(histogram.bins.length, 0);
testClient.drain(done);
});
});
});
describe('category', function () {
var params = {
filters: {
dataviews: {test_categories: {accept: ['Hawai']}}
}
};
it("should expose a filtered sum formula", function (done) {
var testClient = new TestClient(overviewsMapConfig);
testClient.getDataview('test_sum', params, function (err, formula_result) {
if (err) {
return done(err);
}
assert.deepEqual(formula_result, {"operation":"sum","result":1,"nulls":0,"type":"formula"});
testClient.drain(done);
});
});
it("should expose a filtered avg formula", function(done) {
var testClient = new TestClient(overviewsMapConfig);
testClient.getDataview('test_avg', params, function(err, formula_result) {
if (err) {
return done(err);
}
assert.deepEqual(formula_result, {"operation":"avg","result":1,"nulls":0,"type":"formula"});
testClient.drain(done);
});
});
it("should expose a filtered count formula", function(done) {
var testClient = new TestClient(overviewsMapConfig);
testClient.getDataview('test_count', params, function(err, formula_result) {
if (err) {
return done(err);
}
assert.deepEqual(formula_result, {"operation":"count","result":1,"nulls":0,"type":"formula"});
testClient.drain(done);
});
});
it("should expose a filterd max formula", function(done) {
var testClient = new TestClient(overviewsMapConfig);
testClient.getDataview('test_max', params, function(err, formula_result) {
if (err) {
return done(err);
}
assert.deepEqual(formula_result, {"operation":"max","result":1,"nulls":0,"type":"formula"});
testClient.drain(done);
});
});
it("should expose a filterd min formula", function(done) {
var testClient = new TestClient(overviewsMapConfig);
testClient.getDataview('test_min', params, function(err, formula_result) {
if (err) {
return done(err);
}
assert.deepEqual(formula_result, {"operation":"min","result":1,"nulls":0,"type":"formula"});
testClient.drain(done);
});
});
it("should expose a filtered sum formula with bbox", function (done) {
var bboxparams = {
filters: {
dataviews: {test_categories: {accept: ['Hawai']}}
},
bbox: "-170,-80,170,80"
};
var testClient = new TestClient(overviewsMapConfig);
testClient.getDataview('test_sum', bboxparams, function (err, formula_result) {
if (err) {
return done(err);
}
assert.deepEqual(formula_result, {"operation":"sum","result":1,"nulls":0,"type":"formula"});
testClient.drain(done);
});
});
});
});
});

View File

@@ -1,6 +1,6 @@
var assert = require('../support/assert');
var step = require('step');
var LayergroupToken = require('../../lib/cartodb/models/layergroup_token');
var LayergroupToken = require('../support/layergroup-token');
var testHelper = require(__dirname + '/../support/test_helper');
var CartodbWindshaft = require(__dirname + '/../../lib/cartodb/server');
var serverOptions = require(__dirname + '/../../lib/cartodb/server_options');

View File

@@ -0,0 +1,68 @@
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);
describe('error with context', function () {
var layerOK = {
options: {
sql: 'select cartodb_id, ST_Translate(the_geom_webmercator, 5e6, 0) as the_geom_webmercator ' +
'from test_table',
cartocss: '#layer { marker-fill:red; marker-width:32; marker-allow-overlap:true; }',
cartocss_version: '2.0.1',
interactivity: 'cartodb_id'
}
};
var layerKO = {
options: {
sql: 'select cartodb_id from test_table offset 3', // it doesn't return the_geom_webmercator so it must fail
cartocss: '#layer { marker-fill:blue; marker-allow-overlap:true; }',
cartocss_version: '2.0.2',
interactivity: 'cartodb_id'
}
};
var DB_ERROR_MESSAGE = 'Postgis Plugin: ERROR: column "the_geom_webmercator" does not exist';
var scenarios = [{
description: 'layergroup with 2 layers, second one has query error',
layergroup: {
version: '1.0.0',
layers: [layerOK, layerKO]
},
expectedFailingLayer: { id: 'layer1', index: 1, type: 'mapnik' }
}, {
description: 'layergroup with 2 layers, first one has query error',
layergroup: {
version: '1.0.0',
layers: [layerKO, layerOK]
},
expectedFailingLayer: { id: 'layer0', index: 0, type: 'mapnik' }
}];
scenarios.forEach(function (scenario) {
it(scenario.description, function (done) {
assert.response(server, {
url: '/api/v1/map',
method: 'POST',
headers: {
host: 'localhost',
'Content-Type': 'application/json'
},
data: JSON.stringify(scenario.layergroup)
}, {
status: 400
}, function (res) {
var parsedBody = JSON.parse(res.body);
assert.ok(Array.isArray(parsedBody.errors_with_context));
var err = parsedBody.errors_with_context[0];
assert.equal(err.type, 'layer');
assert.equal(err.subtype, 'query');
assert.ok(err.message.indexOf(DB_ERROR_MESSAGE) >= 0);
assert.deepEqual(err.layer, scenario.expectedFailingLayer);
done();
});
});
});
});

View File

@@ -0,0 +1,343 @@
require('../support/test_helper');
var assert = require('../support/assert');
var TestClient = require('../support/test-client');
describe('use only needed columns', function() {
function getFeatureByCartodbId(features, cartodbId) {
for (var i = 0, len = features.length; i < len; i++) {
if (features[i].properties.cartodb_id === cartodbId) {
return features[i];
}
}
return {};
}
var options = { format: 'geojson', layer: 0 };
afterEach(function(done) {
if (this.testClient) {
this.testClient.drain(done);
} else {
done();
}
});
it('with aggregation widget, interactivity and cartocss columns', function(done) {
var widgetMapConfig = {
version: '1.5.0',
layers: [{
type: 'mapnik',
options: {
sql: 'select * from populated_places_simple_reduced',
cartocss: '#layer0 { marker-fill: red; marker-width: 10; [name="Madrid"] { marker-fill: green; } }',
cartocss_version: '2.0.1',
widgets: {
adm0name: {
type: 'aggregation',
options: {
column: 'adm0name',
aggregation: 'sum',
aggregationColumn: 'pop_max'
}
}
},
interactivity: "cartodb_id,pop_min"
}
}]
};
this.testClient = new TestClient(widgetMapConfig);
this.testClient.getTile(0, 0, 0, options, function (err, res, geojsonTile) {
assert.ok(!err, err);
assert.deepEqual(getFeatureByCartodbId(geojsonTile.features, 1109).properties, {
cartodb_id: 1109,
name: 'Mardin',
adm0name: 'Turkey',
pop_max: 71373,
pop_min: 57586
});
done();
});
});
it('should not duplicate columns', function(done) {
var widgetMapConfig = {
version: '1.5.0',
layers: [{
type: 'mapnik',
options: {
sql: 'select * from populated_places_simple_reduced',
cartocss: ['#layer0 {',
'marker-fill: red;',
'marker-width: 10;',
'[name="Madrid"] { marker-fill: green; } ',
'[pop_max>100000] { marker-fill: black; } ',
'}'].join('\n'),
cartocss_version: '2.3.0',
widgets: {
adm0name: {
type: 'aggregation',
options: {
column: 'adm0name',
aggregation: 'sum',
aggregationColumn: 'pop_max'
}
}
},
interactivity: "cartodb_id,pop_max"
}
}]
};
this.testClient = new TestClient(widgetMapConfig);
this.testClient.getTile(0, 0, 0, options, function (err, res, geojsonTile) {
assert.ok(!err, err);
assert.deepEqual(getFeatureByCartodbId(geojsonTile.features, 1109).properties, {
cartodb_id: 1109,
name: 'Mardin',
adm0name: 'Turkey',
pop_max: 71373
});
done();
});
});
it('with formula widget, no interactivity and no cartocss columns', function(done) {
var formulaWidgetMapConfig = {
version: '1.5.0',
layers: [{
type: 'mapnik',
options: {
sql: 'select * from populated_places_simple_reduced where pop_max > 0 and pop_max < 600000',
cartocss: '#layer0 { marker-fill: red; marker-width: 10; }',
cartocss_version: '2.0.1',
interactivity: 'cartodb_id',
widgets: {
pop_max_f: {
type: 'formula',
options: {
column: 'pop_max',
operation: 'count'
}
}
}
}
}]
};
this.testClient = new TestClient(formulaWidgetMapConfig);
this.testClient.getTile(0, 0, 0, options, function (err, res, geojsonTile) {
assert.ok(!err, err);
assert.deepEqual(getFeatureByCartodbId(geojsonTile.features, 1109).properties, {
cartodb_id: 1109,
pop_max: 71373
});
done();
});
});
it('with cartocss with multiple expressions', function(done) {
var formulaWidgetMapConfig = {
version: '1.5.0',
layers: [{
type: 'mapnik',
options: {
sql: 'select * from populated_places_simple_reduced where pop_max > 0 and pop_max < 600000',
cartocss: '#layer0 { marker-fill: red; marker-width: 10; }' +
'#layer0 { marker-width: 14; [name="Madrid"] { marker-width: 20; } }' +
'#layer0[pop_max>1000] { marker-width: 14; [name="Madrid"] { marker-width: 20; } }' +
'#layer0[adm0name=~".*Turkey*"] { marker-width: 14; [name="Madrid"] { marker-width: 20; } }',
cartocss_version: '2.0.1',
interactivity: 'cartodb_id'
}
}]
};
this.testClient = new TestClient(formulaWidgetMapConfig);
this.testClient.getTile(0, 0, 0, options, function (err, res, geojsonTile) {
assert.ok(!err, err);
assert.deepEqual(getFeatureByCartodbId(geojsonTile.features, 1109).properties, {
cartodb_id: 1109,
pop_max:71373,
name:"Mardin",
adm0name:"Turkey"
});
done();
});
});
it('should work with mapnik substitution tokens', function(done) {
var cartocss = [
"#layer {",
" line-width: 2;",
" line-color: #3B3B58;",
" line-opacity: 1;",
" polygon-opacity: 0.7;",
" polygon-fill: ramp([points_count], (#E5F5F9,#99D8C9,#2CA25F))",
"}"
].join('\n');
var sql = [
'WITH hgrid AS (',
' SELECT CDB_HexagonGrid(',
' ST_Expand(!bbox!, greatest(!pixel_width!,!pixel_height!) * 100),',
' greatest(!pixel_width!,!pixel_height!) * 100',
' ) as cell',
')',
'SELECT',
' hgrid.cell as the_geom_webmercator,',
' count(1) as points_count,',
' count(1)/power(100 * CDB_XYZ_Resolution(CDB_ZoomFromScale(!scale_denominator!)), 2) as points_density,',
' 1 as cartodb_id',
'FROM hgrid, (SELECT * FROM populated_places_simple_reduced) i',
'where ST_Intersects(i.the_geom_webmercator, hgrid.cell)',
'GROUP BY hgrid.cell'
].join('\n');
var mapConfig = {
"version": "1.4.0",
"layers": [
{
"type": 'mapnik',
"options": {
"cartocss_version": '2.3.0',
"sql": sql,
"cartocss": cartocss
}
}
]
};
this.testClient = new TestClient(mapConfig);
this.testClient.getTile(0, 0, 0, { format: 'geojson', layer: 0 }, function(err, res, geojson) {
assert.ok(!err, err);
assert.ok(geojson);
assert.equal(geojson.features.length, 5);
done();
});
});
it('should skip empty and null columns for geojson tiles', function(done) {
var mapConfig = {
"analyses": [
{
"id": "a0",
"params": {
"query": "SELECT * FROM test_table"
},
"type": "source"
}
],
"dataviews": {
"4e7b0e07-6d21-4b83-9adb-6d7e17eea6ca": {
"options": {
"aggregationColumn": null,
"column": "cartodb_id",
"operation": "avg"
},
"source": {
"id": "a0"
},
"type": "formula"
},
"74f590f8-625c-4e95-922f-34ad3e9919c0": {
"options": {
"aggregation": "sum",
"aggregationColumn": "cartodb_id",
"column": "name"
},
"source": {
"id": "a0"
},
"type": "aggregation"
},
"98a75757-3006-400a-b028-fb613a6c0b69": {
"options": {
"aggregationColumn": null,
"column": "cartodb_id",
"operation": "sum"
},
"source": {
"id": "a0"
},
"type": "formula"
},
"ebbc97b2-87d2-4895-9e1f-2f012df3679d": {
"options": {
"aggregationColumn": null,
"bins": "12",
"column": "cartodb_id"
},
"source": {
"id": "a0"
},
"type": "histogram"
},
"ebc0653f-3581-469c-8b31-c969e440a865": {
"options": {
"aggregationColumn": null,
"column": "cartodb_id",
"operation": "avg"
},
"source": {
"id": "a0"
},
"type": "formula"
}
},
"layers": [
{
"options": {
"subdomains": "abcd",
"urlTemplate": "http://{s}.basemaps.cartocdn.com/light_nolabels/{z}/{x}/{y}.png"
},
"type": "http"
},
{
"options": {
"attributes": {
"columns": [
"name",
"address"
],
"id": "cartodb_id"
},
"cartocss": "#layer { marker-width: 10; marker-fill: red; }",
"cartocss_version": "2.3.0",
"interactivity": "cartodb_id",
"layer_name": "wadus",
"source": {
"id": "a0"
}
},
"type": "cartodb"
},
{
"options": {
"subdomains": "abcd",
"urlTemplate": "http://{s}.basemaps.cartocdn.com/light_only_labels/{z}/{x}/{y}.png"
},
"type": "http"
}
]
};
this.testClient = new TestClient(mapConfig);
this.testClient.getTile(0, 0, 0, { format: 'geojson', layer: 0 }, function(err, res, geojson) {
assert.ok(!err, err);
assert.ok(geojson);
assert.equal(geojson.features.length, 5);
assert.deepEqual(Object.keys(geojson.features[0].properties), ['cartodb_id', 'name']);
done();
});
});
});

View File

@@ -7,7 +7,7 @@ var redis = require('redis');
var CartodbWindshaft = require('../../lib/cartodb/server');
var serverOptions = require('../../lib/cartodb/server_options');
var LayergroupToken = require('../../lib/cartodb/models/layergroup_token');
var LayergroupToken = require('../support/layergroup-token');
describe('render limits', function() {
@@ -106,7 +106,7 @@ describe('render limits', function() {
},
function(res) {
var parsed = JSON.parse(res.body);
assert.deepEqual(parsed, { errors: [ 'Render timed out' ] });
assert.deepEqual(parsed.errors, [ 'Render timed out' ]);
done();
}
);
@@ -171,7 +171,7 @@ describe('render limits', function() {
},
function(res) {
var parsed = JSON.parse(res.body);
assert.deepEqual(parsed, { errors: ['Render timed out'] });
assert.deepEqual(parsed.errors, ['Render timed out']);
done();
}
);

View File

@@ -5,8 +5,11 @@ var step = require('step');
var strftime = require('strftime');
var redis_stats_db = 5;
var mapnik = require('windshaft').mapnik;
var semver = require('semver');
var helper = require(__dirname + '/../support/test_helper');
var LayergroupToken = require('../../lib/cartodb/models/layergroup_token');
var LayergroupToken = require('../support/layergroup-token');
var windshaft_fixtures = __dirname + '/../../node_modules/windshaft/test/fixtures';
@@ -574,7 +577,7 @@ describe(suiteName, function() {
if ( err ) {
return done(err);
}
// trip epoch
// strip epoch
expected_token = expected_token.split(':')[0];
keysToDelete['map_cfg|' + expected_token] = 0;
keysToDelete['user:localhost:mapviews:global'] = 5;
@@ -973,70 +976,72 @@ describe(suiteName, function() {
});
// See https://github.com/CartoDB/Windshaft-cartodb/issues/93
it("accepts unused directives", function(done) {
var layergroup = {
version: '1.0.0',
layers: [
{ options: {
sql: "select 'SRID=3857;POINT(0 0)'::geometry as the_geom_webmercator",
cartocss: '#layer { point-transform:"scale(20)"; }',
cartocss_version: '2.0.1'
} }
]
};
var expected_token; // = "e34dd7e235138a062f8ba7ad051aa3a7";
step(
function do_post()
{
var next = this;
assert.response(server, {
url: layergroup_url,
method: 'POST',
headers: {host: 'localhost', 'Content-Type': 'application/json' },
data: JSON.stringify(layergroup)
}, {}, function(res) {
assert.equal(res.statusCode, 200, res.body);
var parsedBody = JSON.parse(res.body);
if ( expected_token ) {
assert.equal(parsedBody.layergroupid, expected_token + ':' + expected_last_updated_epoch);
assert.equal(res.headers['x-layergroup-id'], parsedBody.layergroupid);
}
else {
var token_components = parsedBody.layergroupid.split(':');
expected_token = token_components[0];
expected_last_updated_epoch = token_components[1];
}
next(null, res);
});
},
function do_get_tile(err)
{
assert.ifError(err);
var next = this;
assert.response(server, {
url: layergroup_url + "/" + expected_token + ':cb0/0/0/0.png',
method: 'GET',
headers: {host: 'localhost' },
encoding: 'binary'
}, {}, function(res) {
assert.equal(res.statusCode, 200, res.body);
assert.equal(res.headers['content-type'], "image/png");
assert.imageBufferIsSimilarToFile(res.body, windshaft_fixtures + '/test_default_mapnik_point.png',
IMAGE_EQUALS_TOLERANCE_PER_MIL, function(err/*, similarity*/) {
next(err);
if (semver.satisfies(mapnik.versions.mapnik, '2.3.x')) {
it("accepts unused directives", function(done) {
var layergroup = {
version: '1.0.0',
layers: [
{ options: {
sql: "select 'SRID=3857;POINT(0 0)'::geometry as the_geom_webmercator",
cartocss: '#layer { point-transform:"scale(20)"; }',
cartocss_version: '2.0.1'
} }
]
};
var expected_token; // = "e34dd7e235138a062f8ba7ad051aa3a7";
step(
function do_post()
{
var next = this;
assert.response(server, {
url: layergroup_url,
method: 'POST',
headers: {host: 'localhost', 'Content-Type': 'application/json' },
data: JSON.stringify(layergroup)
}, {}, function(res) {
assert.equal(res.statusCode, 200, res.body);
var parsedBody = JSON.parse(res.body);
if ( expected_token ) {
assert.equal(parsedBody.layergroupid, expected_token + ':' + expected_last_updated_epoch);
assert.equal(res.headers['x-layergroup-id'], parsedBody.layergroupid);
}
);
});
},
function finish(err) {
keysToDelete['user:localhost:mapviews:global'] = 5;
keysToDelete['map_cfg|' + expected_token] = 0;
done(err);
}
);
});
else {
var token_components = parsedBody.layergroupid.split(':');
expected_token = token_components[0];
expected_last_updated_epoch = token_components[1];
}
next(null, res);
});
},
function do_get_tile(err)
{
assert.ifError(err);
var next = this;
assert.response(server, {
url: layergroup_url + "/" + expected_token + ':cb0/0/0/0.png',
method: 'GET',
headers: {host: 'localhost' },
encoding: 'binary'
}, {}, function(res) {
assert.equal(res.statusCode, 200, res.body);
assert.equal(res.headers['content-type'], "image/png");
assert.imageBufferIsSimilarToFile(res.body, windshaft_fixtures + '/test_default_mapnik_point.png',
IMAGE_EQUALS_TOLERANCE_PER_MIL, function(err/*, similarity*/) {
next(err);
}
);
});
},
function finish(err) {
keysToDelete['user:localhost:mapviews:global'] = 5;
keysToDelete['map_cfg|' + expected_token] = 0;
done(err);
}
);
});
}
// See https://github.com/CartoDB/Windshaft-cartodb/issues/91
// and https://github.com/CartoDB/Windshaft-cartodb/issues/38
it("tiles for private tables can be fetched with api_key", function(done) {

View File

@@ -4,7 +4,7 @@ var assert = require('../support/assert');
var _ = require('underscore');
var LayergroupToken = require('../../lib/cartodb/models/layergroup_token');
var LayergroupToken = require('../support/layergroup-token');
var PgQueryRunner = require('../../lib/cartodb/backends/pg_query_runner');
var QueryTables = require('cartodb-query-tables');
@@ -228,7 +228,9 @@ describe('tests from old api translated to multilayer', function() {
},
function(res) {
var parsed = JSON.parse(res.body);
assert.deepEqual(parsed, { errors: [ 'Unexpected token W' ] });
assert.ok(parsed.errors);
assert.equal(parsed.errors.length, 1);
assert.ok(parsed.errors[0].match(/^Unexpected token W/));
done();
}
@@ -334,9 +336,7 @@ describe('tests from old api translated to multilayer', function() {
assert.ok(!res.headers.hasOwnProperty('x-cache-channel'));
var parsed = JSON.parse(res.body);
assert.deepEqual(parsed, {
errors: ["fake error message"]
});
assert.deepEqual(parsed.errors, ["fake error message"]);
done();
}

View File

@@ -5,7 +5,7 @@ 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 LayergroupToken = require('../support/layergroup-token');
var RedisPool = require('redis-mpool');
var TemplateMaps = require('../../lib/cartodb/backends/template_maps.js');
@@ -181,7 +181,7 @@ describe('named_layers', function() {
}
var parsedBody = JSON.parse(response.body);
assert.deepEqual(parsedBody, { errors: ["Template 'nonexistent' of user 'localhost' not found"] });
assert.deepEqual(parsedBody.errors, ["Template 'nonexistent' of user 'localhost' not found"]);
return null;
},
@@ -234,10 +234,7 @@ describe('named_layers', function() {
}
var parsedBody = JSON.parse(response.body);
assert.deepEqual(
parsedBody,
{ errors: [ "Unauthorized 'auth_valid_template' template instantiation" ] }
);
assert.deepEqual(parsedBody.errors, [ "Unauthorized 'auth_valid_template' template instantiation" ]);
return null;
},
@@ -347,7 +344,7 @@ describe('named_layers', function() {
}
var parsedBody = JSON.parse(response.body);
assert.deepEqual(parsedBody, { errors: [ 'Nested named layers are not allowed' ] });
assert.deepEqual(parsedBody.errors, ['Nested named layers are not allowed' ]);
return null;
},

View File

@@ -0,0 +1,210 @@
var step = require('step');
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 RedisPool = require('redis-mpool');
var TemplateMaps = require('../../lib/cartodb/backends/template_maps.js');
var mapnik = require('windshaft').mapnik;
var IMAGE_TOLERANCE = 20;
describe('layers visibility for previews', function() {
// configure redis pool instance to use in tests
var redisPool = new RedisPool(global.environment.redis);
var templateMaps = new TemplateMaps(redisPool, {
max_user_templates: global.environment.maxUserTemplates
});
var username = 'localhost';
function createLayer (color, layerId) {
var mod;
if (color === 'red') {
mod = 0;
} else if (color === 'orange') {
mod = 1;
} else if (color === 'blue') {
mod = 2;
} else {
mod = 0;
}
return {
type: 'mapnik',
id: layerId,
options: {
sql: 'select * from populated_places_simple_reduced where cartodb_id % 3 = ' + mod,
cartocss: '#layer { marker-fill: ' + color + '; }',
cartocss_version: '2.3.0'
}
};
}
function createTemplate(scenario) {
return {
version: '0.0.1',
name: scenario.name,
auth: {
method: 'open'
},
view: {
bounds: {
west: 0,
south: 0,
east: 45,
north: 45
},
zoom: 4,
center: {
lng: 40,
lat: 20
},
preview_layers: scenario.layerPerview
},
layergroup: {
layers: scenario.layers
}
};
}
afterEach(function (done) {
test_helper.deleteRedisKeys({
'user:localhost:mapviews:global': 5
}, done);
});
function previewFixture(version) {
return './test/fixtures/previews/populated_places_simple_reduced-' + version + '.png';
}
var threeLayerPointDistintColor = [
createLayer('red'),
createLayer('orange'),
createLayer('blue', 'layer2')
];
var scenarios = [{
name: 'preview_layers_red',
layerPerview: {
'0': true,
'1': false,
'layer2': false
},
layers: threeLayerPointDistintColor
}, {
name: 'preview_layers_orange',
layerPerview: {
'0': false,
'1': true,
'layer2': false
},
layers: threeLayerPointDistintColor
}, {
name: 'preview_layers_blue',
layerPerview: {
'0': false,
'1': false,
'layer2': true
},
layers: threeLayerPointDistintColor
}, {
name: 'preview_layers_red_orange',
layerPerview: {
'0': true,
'1': true,
'layer2': false
},
layers: threeLayerPointDistintColor
}, {
name: 'preview_layers_red_blue',
layerPerview: {
'0': true,
'1': false,
'layer2': true
},
layers: threeLayerPointDistintColor
}, {
name: 'preview_layers_orange_blue',
layerPerview: {
'0': false,
'1': true,
'layer2': true
},
layers: threeLayerPointDistintColor
}, {
name: 'preview_layers_red_orange_blue',
layerPerview: {
'0': true,
'1': true,
'layer2': true
},
layers: threeLayerPointDistintColor
}, {
name: 'preview_layers_all',
layerPerview: {},
layers: threeLayerPointDistintColor
}, {
name: 'preview_layers_undefined',
layerPerview: undefined,
layers: threeLayerPointDistintColor
}];
scenarios.forEach(function (scenario) {
it('should filter layers for template: ' + scenario.name, function (done) {
step(
function addTemplate () {
var next = this;
var template = createTemplate(scenario);
templateMaps.addTemplate(username, template, next);
},
function requestPreview (err) {
assert.ifError(err);
var next = this;
assert.response(server, {
url: '/api/v1/map/static/named/' + scenario.name + '/640/480.png',
method: 'GET',
headers: {
host: 'localhost'
},
encoding: 'binary'
}, {
status: 200,
headers: {
'content-type': 'image/png'
}
}, function (res, err) {
next(err, res);
});
},
function checkPreview (err, res) {
assert.ifError(err);
var next = this;
var img = mapnik.Image.fromBytes(new Buffer(res.body, 'binary'));
var previewFixturePath = previewFixture(scenario.name);
assert.imageIsSimilarToFile(img, previewFixturePath, IMAGE_TOLERANCE, next);
},
function deleteTemplate(err) {
assert.ifError(err);
var next = this;
templateMaps.delTemplate(username, scenario.name, next);
},
function finish (err) {
done(err);
}
);
});
});
});

View File

@@ -169,8 +169,8 @@ describe('named maps authentication', function() {
getNamedTile(nonexistentName, 0, 0, 0, { status: 404 }, function(err, res) {
assert.ok(!err);
assert.deepEqual(
JSON.parse(res.body),
{ errors: ["Template '" + nonexistentName + "' of user '" + username + "' not found"] }
JSON.parse(res.body).errors,
["Template '" + nonexistentName + "' of user '" + username + "' not found"]
);
done();
});
@@ -179,7 +179,7 @@ describe('named maps authentication', function() {
it('should return 403 if not properly authorized', function(done) {
getNamedTile(tokenAuthTemplateName, 0, 0, 0, { status: 403 }, function(err, res) {
assert.ok(!err);
assert.deepEqual(JSON.parse(res.body), { errors: ['Unauthorized template instantiation'] });
assert.deepEqual(JSON.parse(res.body).errors, ['Unauthorized template instantiation']);
done();
});
});
@@ -238,8 +238,8 @@ describe('named maps authentication', function() {
getStaticMap(nonexistentName, { status: 404 }, function(err, res) {
assert.ok(!err);
assert.deepEqual(
JSON.parse(res.body),
{ errors: ["Template '" + nonexistentName + "' of user '" + username + "' not found"] }
JSON.parse(res.body).errors,
["Template '" + nonexistentName + "' of user '" + username + "' not found"]
);
done();
});
@@ -248,7 +248,7 @@ describe('named maps authentication', function() {
it('should return 403 if not properly authorized', function(done) {
getStaticMap(tokenAuthTemplateName, { status: 403 }, function(err, res) {
assert.ok(!err);
assert.deepEqual(JSON.parse(res.body), { errors: ['Unauthorized template instantiation'] });
assert.deepEqual(JSON.parse(res.body).errors, ['Unauthorized template instantiation']);
done();
});
});

View File

@@ -124,8 +124,8 @@ describe('named maps provider cache', function() {
getNamedTile({ statusCode: 404 }, function(err, res) {
assert.ok(!err);
assert.deepEqual(
JSON.parse(res.body),
{ errors: ["Template 'template_with_color' of user 'localhost' not found"] }
JSON.parse(res.body).errors,
["Template 'template_with_color' of user 'localhost' not found"]
);
// add template again so it's clean in afterEach

View File

@@ -5,7 +5,7 @@ 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 LayergroupToken = require('../support/layergroup-token');
var RedisPool = require('redis-mpool');
@@ -109,3 +109,118 @@ describe('overviews metadata', function() {
);
});
});
describe('overviews metadata with filters', function() {
// configure redis pool instance to use in tests
var redisPool = new RedisPool(global.environment.redis);
var keysToDelete;
beforeEach(function() {
keysToDelete = {};
});
afterEach(function(done) {
test_helper.deleteRedisKeys(keysToDelete, done);
});
it("layers with overviews", function(done) {
var layergroup = {
version: '1.5.0',
layers: [
{
type: 'cartodb',
options: {
sql: 'SELECT * FROM test_table_overviews',
source: { id: 'with_overviews' },
cartocss: '#layer { marker-fill: black; }',
cartocss_version: '2.3.0'
}
}
],
dataviews: {
test_names: {
type: 'aggregation',
source: {id: 'with_overviews'},
options: {
column: 'name',
aggregation: 'count'
}
}
},
analyses: [
{ id: 'with_overviews',
type: 'source',
params: {
query: 'select * from test_table_overviews'
}
}
]
};
var filters = {
dataviews: {
test_names: { accept: ['Hawai'] }
}
};
var layergroup_url = '/api/v1/map';
var expected_token;
step(
function do_post()
{
var next = this;
assert.response(server, {
url: layergroup_url + '?filters=' + JSON.stringify(filters),
method: 'POST',
headers: {host: 'localhost', 'Content-Type': 'application/json' },
data: JSON.stringify(layergroup)
}, {}, function(res) {
assert.equal(res.statusCode, 200, res.body);
var parsedBody = JSON.parse(res.body);
assert.equal(res.headers['x-layergroup-id'], parsedBody.layergroupid);
expected_token = parsedBody.layergroupid;
next(null, res);
});
},
function do_get_mapconfig(err)
{
assert.ifError(err);
var next = this;
var mapStore = new windshaft.storage.MapStore({
pool: redisPool,
expire_time: 500000
});
mapStore.load(LayergroupToken.parse(expected_token).token, function(err, mapConfig) {
assert.ifError(err);
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: {
schema: 'public',
1: { table: '_vovw_1_test_table_overviews' },
2: { table: '_vovw_2_test_table_overviews' }
}
},
filters: { test_names: { type: 'category', column: 'name', params: { accept: [ 'Hawai' ] } } },
unfiltered_query: 'select * from test_table_overviews',
filter_stats: { unfiltered_rows: 5, filtered_rows: 1 }
};
assert.deepEqual(mapConfig._cfg.layers[0].options.query_rewrite_data, expected_data);
});
next(err);
},
function finish(err) {
keysToDelete['map_cfg|' + LayergroupToken.parse(expected_token).token] = 0;
keysToDelete['user:localhost:mapviews:global'] = 5;
done(err);
}
);
});
});

View File

@@ -5,7 +5,7 @@ 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 LayergroupToken = require('../support/layergroup-token');
var RedisPool = require('redis-mpool');

View File

@@ -6,7 +6,7 @@ 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');
var LayergroupToken = require('../../support/layergroup-token');
describe('attributes', function() {
@@ -231,7 +231,11 @@ describe('attributes', function() {
assert.equal(res.statusCode, 200, res.statusCode + ': ' + res.body);
assert.equal(
res.body,
'/**/ typeof test === \'function\' && test({"errors":["Layer 0 has no exposed attributes"]});'
'/**/ typeof test === \'function\' && ' +
'test({"errors":["Layer 0 has no exposed attributes"],' +
'"errors_with_context":[{' +
'"type":"unknown","message":"Layer 0 has no exposed attributes"' +
'}]});'
);
return null;
},

View File

@@ -138,11 +138,9 @@ describe('blend http fallback', function() {
testClient.getTileLayer(mapConfig, tileRequest, expectedResponse, function(err, res) {
assert.ok(!err);
var parsedBody = JSON.parse(res.body);
assert.deepEqual(parsedBody, {
errors: [
"Unable to fetch http tile: http://127.0.0.1:8033/error404/1/0/0.png [404]"
]
});
assert.deepEqual(parsedBody.errors, [
"Unable to fetch http tile: http://127.0.0.1:8033/error404/1/0/0.png [404]"
]);
done();
});
});

View File

@@ -119,12 +119,11 @@ describe('external resources', function() {
var mapConfig = testClient.defaultTableMapConfig('test_table_3', style);
testClient.createLayergroup(mapConfig, { statusCode: 400 }, function(err, res) {
assert.deepEqual(JSON.parse(res.body), {
errors: ["Unable to download '" + url + "' for 'style0' (server returned 404)"]
});
assert.deepEqual(JSON.parse(res.body).errors, [
"Unable to download '" + url + "' for 'style0' (server returned 404)"]
);
done();
});
});
});

View File

@@ -7,7 +7,7 @@ var step = require('step');
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 LayergroupToken = require('../../support/layergroup-token');
var BaseController = require('../../../lib/cartodb/controllers/base');
describe('multilayer', function() {

View File

@@ -31,7 +31,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, ["layergroup POST data must be of type application/json"]);
done();
});
});
@@ -44,7 +44,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":["Missing layers array from layergroup config"]});
assert.deepEqual(parsedBody.errors, ["Missing layers array from layergroup config"]);
done();
});
});
@@ -58,7 +58,10 @@ describe('multilayer error cases', function() {
assert.equal(res.statusCode, 200);
assert.equal(
res.body,
'/**/ typeof test === \'function\' && test({"errors":["Missing layers array from layergroup config"]});'
'/**/ typeof test === \'function\' && ' +
'test({"errors":["Missing layers array from layergroup config"],' +
'"errors_with_context":[{"type":"unknown",' +
'"message":"Missing layers array from layergroup config"}]});'
);
done();
});
@@ -83,7 +86,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:["Missing cartocss_version for layer 0 options"]});
assert.deepEqual(parsedBody.errors, ["Missing cartocss_version for layer 0 options"]);
done();
});
});
@@ -355,7 +358,7 @@ describe('multilayer error cases', function() {
var mapConfig = testClient.singleLayerMapConfig('select * from test_table', null, null, 'name');
testClient.getGrid(mapConfig, 1, 13, 4011, 3088, defaultErrorExpectedResponse, function(err, res) {
assert.deepEqual(JSON.parse(res.body), { errors: ["Layer '1' not found in layergroup"] });
assert.deepEqual(JSON.parse(res.body).errors, ["Layer '1' not found in layergroup"]);
done();
});
});
@@ -383,7 +386,7 @@ describe('multilayer error cases', function() {
// FIXME: should be 404
assert.equal(res.statusCode, 400, res.statusCode + ':' + res.body);
var parsed = JSON.parse(res.body);
assert.deepEqual(parsed, {"errors": ["Invalid or nonexistent map configuration token 'deadbeef'"]});
assert.deepEqual(parsed.errors, ["Invalid or nonexistent map configuration token 'deadbeef'"]);
return null;
},
function finish(err) {
@@ -416,7 +419,9 @@ describe('multilayer error cases', function() {
},
function(res) {
var parsedBody = JSON.parse(res.body);
assert.deepEqual(parsedBody, { errors: ['SyntaxError: Unexpected token {'] });
assert.ok(parsedBody.errors);
assert.equal(parsedBody.errors.length, 1);
assert.ok(parsedBody.errors[0].match(/^SyntaxError: Unexpected token {/));
done();
}
);

View File

@@ -5,7 +5,7 @@ var _ = require('underscore');
var cartodbServer = require('../../../lib/cartodb/server');
var getLayerTypeFn = require('windshaft').model.MapConfig.prototype.getType;
var PortedServerOptions = require('./support/ported_server_options');
var LayergroupToken = require('../../../lib/cartodb/models/layergroup_token');
var LayergroupToken = require('../../support/layergroup-token');
var BaseController = require('../../../lib/cartodb/controllers/base');

View File

@@ -6,7 +6,7 @@ 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 LayergroupToken = require('../../support/layergroup-token');
describe('raster', function() {
@@ -148,11 +148,10 @@ describe('raster', function() {
assert.ok(!err);
checkCORSHeaders(res);
var parsedBody = JSON.parse(res.body);
assert.deepEqual(parsedBody, { errors: [ 'Mapnik raster layers do not support interactivity' ] });
assert.deepEqual(parsedBody.errors, [ 'Mapnik raster layers do not support interactivity' ]);
done();
}
);
});
});

View File

@@ -29,7 +29,7 @@ describe('regressions', function() {
contentType: 'application/json; charset=utf-8'
};
requestTile('/0/0/0.png?testUnexpectedError=1', options, function(err, res) {
assert.deepEqual(JSON.parse(res.body), { "errors": ["test unexpected error"] });
assert.deepEqual(JSON.parse(res.body).errors, ["test unexpected error"]);
finish(done);
});
});

View File

@@ -6,7 +6,7 @@ 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 LayergroupToken = require('../../support/layergroup-token');
describe('retina support', function() {
@@ -129,7 +129,7 @@ describe('retina support', function() {
},
function(res, err) {
assert.ok(!err, 'Failed to request 0/0/0' + scaleFactor + '.png tile');
assert.deepEqual(JSON.parse(res.body), { errors: ["Tile with specified resolution not found"] } );
assert.deepEqual(JSON.parse(res.body).errors, ["Tile with specified resolution not found"]);
done();
}

Some files were not shown because too many files have changed in this diff Show More