Compare commits

...

1678 Commits

Author SHA1 Message Date
Raul Marin
a7157532f1 Release 5.2.0 2018-02-01 09:31:46 +01:00
Raúl Marín
55fd660d69 Merge pull request #859 from Algunenano/master_cache_features
Add Mapnik 'cache-features' option
2018-02-01 09:27:07 +01:00
Raul Marin
150c6ee4be Update to Windshaft 5.3.3 2018-01-31 17:24:15 +01:00
Raul Marin
d0df8b1533 Update yarn.lock 2018-01-31 15:40:23 +01:00
Raul Marin
43e1de31fa Add Mapnik 'cache-features' option 2018-01-31 12:18:28 +01:00
Rafa de la Torre
33ed9ab47d Stub next version 2018-01-30 10:16:08 +01:00
Rafa de la Torre
749a08336a Merge pull request #857 from CartoDB/upgrade-to-mapnik-3.6.2-carto.2
Upgrade to node mapnik 3.6.2-carto.2
2018-01-30 10:13:15 +01:00
Rafa de la Torre
467097b3cc Merge remote-tracking branch 'origin/master' into upgrade-to-mapnik-3.6.2-carto.2 2018-01-30 10:07:01 +01:00
Javier Goizueta
487aca52d0 Stub next version 2018-01-29 18:25:14 +01:00
Javier Goizueta
072956addd Release 5.0.1 2018-01-29 18:16:30 +01:00
Rafa de la Torre
781d2d3a28 Update NEWS.md 2018-01-29 17:41:46 +01:00
Rafa de la Torre
2a767cdb83 Update yarn.lock 2018-01-29 17:40:49 +01:00
Rafa de la Torre
e3cf69ac1a Bump windshaft version
That version contains our flavor of mapnik 3.0.15 with a bunch of
patches. See
https://github.com/CartoDB/Windshaft/blob/master/NEWS.md#version-431
2018-01-29 17:40:17 +01:00
Javier Goizueta
27b5420358 Merge pull request #856 from CartoDB/841-the_geom_webmercator-type
Check the type of the_geom_webmercator for aggregation
2018-01-29 16:13:19 +01:00
Javier Goizueta
7641542e67 Check the type of the_geom_webmercator for aggregation
Fixes #841
2018-01-29 15:48:35 +01:00
Javier Goizueta
debb174af4 Add test for aggregation without the_geom
Only the_geom_webmercator is required for aggregation
See #841
2018-01-29 15:44:24 +01:00
Eneko Lakasta
2bd4c9e814 Merge pull request #851 from CartoDB/1259-category-widget-error-group-by-missing
use original column name in group by instead of alias
2018-01-29 15:38:07 +01:00
Simon Martín
0dc7872256 stubs next version 2018-01-29 15:26:41 +01:00
Simon Martín
1e56ba1de9 Merge pull request #854 from CartoDB/respect-types-aggreagation
Respect category type of aggregation dataview
2018-01-29 15:24:38 +01:00
Simon Martín
6f4e338dcb version 5.0.0 2018-01-29 15:19:07 +01:00
Eneko Lakasta
941ebf7d80 Merge branch 'master' into 1259-category-widget-error-group-by-missing 2018-01-29 14:51:59 +01:00
Simon Martín
c38bf6ade8 Merge branch 'master' into respect-types-aggreagation 2018-01-29 14:51:20 +01:00
Javier Goizueta
44c4db93da Merge pull request #855 from CartoDB/846-fix-point-grid
Add cartodb_id to point-grid aggregations
2018-01-29 14:48:17 +01:00
Javier Goizueta
f644b3a226 Add cartodb_id to point-grid aggregation
Fixes #846
2018-01-29 12:49:27 +01:00
Javier Goizueta
7c9b4b7283 Add test to check that cartodb_id is preseent in aggregations
See #846
This revealss that point-grid aggregation is missing cartodb_id
2018-01-29 12:40:59 +01:00
Simon Martín
8c839e214d changing the value of string 2018-01-26 15:44:21 +01:00
Simon Martín
99421b613c moving 'other' outside of the query allowing queries of different types 2018-01-26 15:24:21 +01:00
Simon Martín
bc7a556297 removing category cast to string in aggregation 2018-01-26 12:37:10 +01:00
Eneko Lakasta
220f1d6a73 use original column name in group by instead of alias 2018-01-18 15:27:54 +01:00
Daniel
767dde0b1e Merge pull request #850 from CartoDB/fix-named-map-force-all-layer
Fix named map regression: default to all layer
2018-01-16 19:22:56 +01:00
Daniel García Aubert
da32d96607 Fix regression: default to all layers if layer filter is not provided 2018-01-16 17:57:22 +01:00
Daniel García Aubert
76da828168 Use error label as middleware argument 2018-01-16 17:55:09 +01:00
Simon Martín
068c242148 Merge pull request #848 from CartoDB/removing-windshaft-carto-testing-image
Removing docker windshaft-carto-testing image
2018-01-16 15:00:34 +01:00
Simon Martín
e4c409f9a5 removing docker windshaft-carto-testing image 2018-01-16 13:01:32 +01:00
Simon Martín
00ffd75781 removing docker-publish command 2018-01-16 12:59:13 +01:00
Daniel
128ab53c55 Merge pull request #847 from CartoDB/fix-res-locals-named-maps
Do not pass the entire res.locals to named maps provider cache
2018-01-15 19:02:01 +01:00
Daniel García Aubert
ce4050e3e3 Extrac method to get only user params 2018-01-15 18:09:54 +01:00
Daniel García Aubert
b82767c60d Pass a copy of res.locals w/o new data boud to named-map-provider-cache 2018-01-15 17:40:34 +01:00
Raul Marin
0fdab08600 Torque boundaries tests: Sort objects before comparison
Order is not guaranteed by torque and changed behaviour from PG 9.5 to 10
2018-01-15 16:44:10 +01:00
Javier Goizueta
4ba2632a92 Merge pull request #839 from CartoDB/mapconfig-aggregation-spec
Aggregation documetation
2018-01-12 15:04:45 +01:00
Raul Ochoa
d9e66c5964 Link to overviews doc 2018-01-11 15:15:33 +00:00
Raul Ochoa
72bebf1960 Fix typo 2018-01-11 15:15:25 +00:00
Eneko Lakasta
3fa2869665 Merge pull request #840 from CartoDB/984-ICU-dat-not-loading
984 icu dat not loading
2018-01-11 15:22:42 +01:00
Raul Ochoa
e57c4c824b fix invalid json 2018-01-11 11:45:44 +00:00
Eneko Lakasta
8e68e5395d remove .only from test 2018-01-11 12:23:16 +01:00
Eneko Lakasta
0236935212 please jshint 2018-01-11 12:22:51 +01:00
Eneko Lakasta
86e20b4b26 recreate test images with new font 2018-01-11 12:15:23 +01:00
Eneko Lakasta
86d58fea7b use DejaVu Sans Book instead of Open Sans Italic in test 2018-01-11 12:09:04 +01:00
Eneko Lakasta
9934d69736 adjust test image tolerance 2018-01-11 11:57:36 +01:00
Eneko Lakasta
ae48a01e26 extract setICUEnvVariable() to it's own module 2018-01-11 11:57:11 +01:00
Eneko Lakasta
4d11403be2 console.log error in test. For testing purposes only. 2018-01-11 10:49:46 +01:00
Eneko Lakasta
bcd14e4f77 add test to check that labels are wrapped 2018-01-10 22:20:19 +01:00
Eneko Lakasta
60d2cc0a4f set ICU_DATA env variable also in tests 2018-01-10 21:06:47 +01:00
Eneko Lakasta
5e53920aae move glob require to the beginning of the file 2018-01-10 16:27:51 +01:00
Eneko Lakasta
9c556964e5 use glob module to get the icu_data directory 2018-01-10 15:15:43 +01:00
Eneko Lakasta
d292a922f6 set ICU_DATA 3 alternatives 2018-01-10 14:51:48 +01:00
Eneko Lakasta
c016175a23 please jshint 2018-01-10 11:24:08 +01:00
Eneko Lakasta
1b85951e06 Merge branch 'master' into 984-ICU-dat-not-loading 2018-01-10 11:19:29 +01:00
Eneko Lakasta
a4e98163fb set ICU_DATA env variable at app bootstrap 2018-01-10 11:13:49 +01:00
Javier Goizueta
99324b15ef Remove placement examples 2018-01-09 15:58:20 +01:00
Javier Goizueta
e34410fd2c Add references to general aggregation documentation in MapConfig spec 2018-01-09 15:08:46 +01:00
Javier Goizueta
cef7545c17 Add documentation section for aggregation 2018-01-09 14:51:55 +01:00
Javier Goizueta
de8ed27207 Document the tilejon and url metadata. 2018-01-09 14:51:37 +01:00
Javier Goizueta
0cfb204c04 Add MapConfig extension for aggregation 2018-01-09 14:49:33 +01:00
Daniel
fc82ca7490 Merge pull request #834 from CartoDB/middlewarify-named-maps-controller
Middlewarify named maps controller
2018-01-09 11:40:59 +01:00
Daniel García Aubert
183c8291bc Use arrow functions when it applies 2018-01-09 11:20:20 +01:00
Daniel García Aubert
d908ffdbca Don't use arrow functions when there is no needed 2018-01-09 11:17:07 +01:00
Raul Ochoa
00a4f481f6 stubs next version 2018-01-04 02:04:48 +00:00
Raul Ochoa
e0bd042bde Release 4.8.0 2018-01-04 02:04:04 +00:00
Raul Ochoa
f881efdc11 Update news 2018-01-04 02:03:28 +00:00
Raul Ochoa
bda5022811 Merge pull request #838 from CartoDB/url-template-metadata
Add urlTemplate URLs to metadata
2018-01-04 00:44:10 +01:00
Raul Ochoa
d5b5ef584d Be explicit about requesting urlTemplate+subdomains format 2018-01-03 23:33:59 +00:00
Raul Ochoa
2cda43dc8d Promote https urls over http 2018-01-03 22:18:59 +00:00
Raul Ochoa
f7f513a61a Add urlTemplate URLs to metadata
This is useful when using client libraries like leaflet.
2018-01-03 20:53:03 +00:00
Raul Ochoa
940c982b68 Stubs next version 2018-01-03 19:22:38 +00:00
Raul Ochoa
d949d1c27f Release 4.7.0 2018-01-03 19:18:39 +00:00
Raul Ochoa
aa1d411fb8 Update news and bump version 2018-01-03 19:17:58 +00:00
Raul Ochoa
f297374449 Merge pull request #837 from CartoDB/tilejson
Return tilejson in metadata
2018-01-03 20:13:44 +01:00
Raul Ochoa
060b93c314 Rename middleware fn name 2018-01-03 18:44:10 +00:00
Raul Ochoa
3ceeaedf02 Fix test after breaking it with linting changes 2018-01-03 16:19:14 +00:00
Raul Ochoa
c6ba9e6102 Fix linting 2018-01-03 16:10:09 +00:00
Raul Ochoa
bf40b240d3 Return tilejson in metadata
It returns tilejson for each individual layer and also for all vector and raster layers.
2018-01-03 16:54:45 +01:00
Raul Ochoa
5d4d2bddd6 Implementation for getTilesUrls
This will be useful for generating the tilejson in the metadata
2018-01-03 16:05:19 +01:00
Raul Ochoa
95dfd87c96 Add test cases for getTilesUrls 2018-01-03 16:04:31 +01:00
Raul Ochoa
eab9e8846e Reorg suite to accommodate getTileURLs 2018-01-03 12:57:01 +00:00
Daniel
788bc302a0 Merge pull request #833 from CartoDB/middlewarify-named-map-admin-controller
Middlewarify named map admin controller
2018-01-03 13:20:14 +01:00
Daniel García Aubert
1ba240d099 Rename middleware function 2018-01-03 13:15:11 +01:00
Daniel
ee0405da1e Merge pull request #836 from CartoDB/disable-default-aggregation
Add test to check layer aggregation disabling
2018-01-03 13:09:31 +01:00
Daniel
5e9b326d03 Merge pull request #835 from CartoDB/skip-polygon-layer-vector-map-config
In vector-only map-config, only aggregate layers with points
2018-01-03 13:09:07 +01:00
Daniel García Aubert
1f30367e59 Add test to check layer aggregation disabling 2018-01-03 12:40:00 +01:00
Daniel García Aubert
26a2f73c2a Update NEWS 2018-01-03 12:30:46 +01:00
Daniel García Aubert
60005e2f7f Fix bad assertion 2018-01-03 12:24:07 +01:00
Daniel García Aubert
1c7da2c4b3 Going green: do not fail when map-config is vector-only and a layer doesn't have points 2018-01-03 12:00:25 +01:00
Daniel García Aubert
3799dd2574 Going red: fail when vector only map-config has a polygon layer 2018-01-03 11:14:20 +01:00
Raul Ochoa
7efb2a2344 Stubs next version 2018-01-02 15:40:54 +00:00
Raul Ochoa
88777abc2c Release 4.6.0 2018-01-02 15:40:10 +00:00
Raul Ochoa
4d9a6f8fbe update news 2018-01-02 15:39:37 +00:00
Daniel
3d9c2e66c5 Merge pull request #830 from CartoDB/pg-mvt-do-not-filter-columns
Aggregation: be able to return a complete row sample as default aggregation
2018-01-02 15:36:08 +01:00
Daniel García Aubert
6bbe715aa6 Update NEWS 2018-01-02 12:57:54 +01:00
Daniel García Aubert
ba002fdb2c Update windshaft to 4.20 2018-01-02 12:38:58 +01:00
Daniel García Aubert
49c97e2cf2 Use default argument 2018-01-02 10:56:45 +01:00
Daniel García Aubert
41e65a9633 Remove max cyclomatic complexity 2018-01-01 18:06:56 +01:00
Daniel García Aubert
feae766e62 Create middleware to fetch named map template 2018-01-01 16:54:35 +01:00
Daniel García Aubert
e3bdeec8ca Simplify middleware 2018-01-01 16:21:22 +01:00
Daniel García Aubert
80c4207c74 Remove underscore dependencie 2017-12-30 18:18:37 +01:00
Daniel García Aubert
80e4306fbc Remove step and assert dependencies 2017-12-30 18:03:26 +01:00
Daniel García Aubert
543d257a20 Move sendResponse to a middleware 2017-12-30 17:18:12 +01:00
Daniel García Aubert
8a023e3d2f Keep error label 2017-12-30 16:08:46 +01:00
Daniel García Aubert
f13b45862d Move incrementMapViews to a middlewares 2017-12-30 16:04:24 +01:00
Daniel García Aubert
731fe4c00f Move getStaticImageOptions and getImage to a middlewares 2017-12-30 15:21:20 +01:00
Daniel García Aubert
500cbb959f Move method to a middleware 2017-12-30 14:13:23 +01:00
Daniel García Aubert
108a319143 Do not use step 2017-12-29 19:33:49 +01:00
Daniel García Aubert
ef5ea5b4cb Create and use getNamedMapProvider middleware 2017-12-29 19:31:02 +01:00
Raul Ochoa
10d1381e51 Merge remote-tracking branch 'origin/master' into pg-mvt-do-not-filter-columns 2017-12-29 17:50:09 +00:00
Daniel García Aubert
dfef7ff3c0 Use spread assignment 2017-12-29 18:45:45 +01:00
Daniel García Aubert
83d0ce4040 Rename method 2017-12-29 18:25:08 +01:00
Daniel García Aubert
75f72c4d07 Return empty aggregation configuration when the map-config is vector-only and the layer has no aggregation 2017-12-29 17:52:28 +01:00
Daniel García Aubert
adb9e55fb2 Avoid snake_case notation 2017-12-29 16:30:42 +01:00
Daniel García Aubert
5d3726de44 Use original variable name 2017-12-29 16:24:38 +01:00
Daniel García Aubert
f186e4736b Use template string 2017-12-29 16:19:00 +01:00
Daniel García Aubert
a00c2b1eef Now main middlewares return a named function with the right context bound 2017-12-29 16:15:48 +01:00
Daniel García Aubert
64d601179d Use const instead of var 2017-12-29 15:22:17 +01:00
Daniel García Aubert
cf2b73e473 Move to up intermediate middlewares 2017-12-29 15:19:52 +01:00
Daniel García Aubert
70932c23df Remove step and assert dependencies 2017-12-29 15:17:29 +01:00
Daniel García Aubert
519d49bd10 Remove finish function and respond in the main middleware 2017-12-29 15:04:44 +01:00
Daniel García Aubert
bf814c4442 keep error label 2017-12-29 13:05:01 +01:00
Daniel García Aubert
f136993c50 Use checkContentType middleware 2017-12-29 12:44:56 +01:00
Daniel García Aubert
ba008ab518 Remove unused function 2017-12-29 12:36:56 +01:00
Daniel García Aubert
e4ed6ee1cc Use authorizedByAPIKey middleware 2017-12-29 12:34:50 +01:00
Daniel García Aubert
fda7661dad Create authorizedByAPIKey middleware 2017-12-29 12:23:52 +01:00
Raul Ochoa
79233471c6 Merge pull request #832 from CartoDB/layers-filters
Support individual layer id filters
2017-12-28 19:48:18 +01:00
Raul Ochoa
a75beefe6e Upgrades windshaft to 4.1.1 2017-12-28 18:34:53 +00:00
Raul Ochoa
e43ccf4f12 Going red: individual layer id filters fail
Depends on https://github.com/CartoDB/Windshaft/pull/584.
2017-12-28 18:19:52 +00:00
Raul Ochoa
cd8e320534 Merge pull request #831 from CartoDB/fail-on-uncaught-exceptions
Make tests to fail if they got an uncaught exception
2017-12-28 19:15:29 +01:00
Raul Ochoa
d7f4d39aa2 Don't not override the full client but only the provided methods 2017-12-28 18:09:41 +00:00
Raul Ochoa
89333185a9 Make tests to fail if they got an uncaught exception 2017-12-28 16:37:17 +00:00
Daniel García Aubert
99b95cf839 Move check of default-aggregation to mapconfig model 2017-12-28 13:50:59 +01:00
Daniel García Aubert
9fbc56b82c Remove FIXME notes to test against PostGIS vector renderer 2017-12-28 13:13:32 +01:00
Daniel García Aubert
9a1bc51fdb Use aggregation-mapconfig's method to discover columns to be exposed used to aggregate 2017-12-28 13:12:41 +01:00
Daniel García Aubert
d42257127b Add method to discover required columns or all of them if it's a default aggregation 2017-12-28 13:11:32 +01:00
Daniel García Aubert
5a730c6df1 Remove exited containers after running test against docker 2017-12-28 13:10:11 +01:00
Daniel García Aubert
418c8691d1 Support default full-sample aggregation for postgis vector renderer 2017-12-27 20:08:43 +01:00
Daniel García Aubert
9885045b41 Do not default to null 2017-12-27 12:48:06 +01:00
Daniel García Aubert
062e6f9594 Merge branch 'full-sample' of github.com:CartoDB/Windshaft-cartodb into pg-mvt-do-not-filter-columns 2017-12-27 12:45:43 +01:00
Daniel
d8428938ae Merge pull request #829 from CartoDB/vector-tiles-doc
Update vector tiles documentation
2017-12-26 11:42:06 +01:00
Simon Martín
ca5f280cb3 updating NEWS.md 2017-12-26 11:03:12 +01:00
Simon Martín
524d5a5597 Merge pull request #828 from Algunenano/i827_timestamp
Timeseries: Avoid collisions with columns named 'timestamp'
2017-12-26 09:55:46 +01:00
Daniel García Aubert
a43779b050 Get columns from layer query a set them into layer opptions 2017-12-22 18:19:57 +01:00
Javier Goizueta
ef3917fa6f Update vector tiles documentation
This update reflects the fact that CartoCSS is now optional for vector tiles.
2017-12-22 17:18:42 +01:00
Raul Marin
031e1253ca Numeric histograms: Avoid conflicts with 'bin' named columns 2017-12-22 17:12:57 +01:00
Raul Marin
8012d76b68 Timeseries: Avoid collisions with columns named 'timestamp' 2017-12-22 15:58:40 +01:00
Javier Goizueta
d726c9ad01 Fix point-sample aggregation
it failed in  the case of aggregate columns with the name of base columns
2017-12-22 15:48:30 +01:00
Javier Goizueta
1ce8076699 Change default aggregation placement to point-sample
For consistency with the default aggregation.
2017-12-22 15:46:29 +01:00
Javier Goizueta
54f32113f3 Add some aggregation tests 2017-12-22 15:45:34 +01:00
Javier Goizueta
19bf079f2d Exclude test from PostGIS 2.4 2017-12-22 15:45:14 +01:00
Daniel García Aubert
b7ecde5c9d Add function get columns for layer's query 2017-12-22 13:43:30 +01:00
Daniel García Aubert
a2f804d79f Use full-sample aggregation mode 2017-12-22 12:15:37 +01:00
Daniel García Aubert
efdfabf3e9 Remove method 2017-12-22 12:14:34 +01:00
Javier Goizueta
e9a4fc4b2c Use full-sample aggregation only as default
Sampling is performed only when placement, columns or dimensions are specified;
otherwise the regular centroid/grid-point/grid-center is used without sampling.
2017-12-22 11:31:33 +01:00
Daniel García Aubert
a1d536642e Merge branch 'full-sample' into pg-mvt-do-not-filter-columns 2017-12-21 20:01:07 +01:00
Daniel García Aubert
3c00266666 Add support for aggregated columns in mvt format 2017-12-21 20:00:17 +01:00
Daniel García Aubert
7f64d15944 Merge branch 'master' into full-sample 2017-12-20 15:58:58 +01:00
Daniel
8259271184 Merge pull request #826 from CartoDB/fix-typo-dimensions
Fix typo
2017-12-20 15:02:32 +01:00
Daniel García Aubert
20366cedb4 Skip test for PostGis 2.4 2017-12-20 14:53:34 +01:00
Daniel García Aubert
a102d1d366 jshint, I hate you 2017-12-20 14:41:29 +01:00
Daniel García Aubert
4b97b4fd26 Fix typo 2017-12-20 14:35:47 +01:00
Daniel
b94debf10e Merge pull request #825 from CartoDB/export-supported-placements
Export supported placements and create static methods to expose them
2017-12-20 13:27:23 +01:00
Daniel García Aubert
60030784c1 Export supported placements and create static methods to expose them in a fancy way 2017-12-20 12:42:29 +01:00
Daniel García Aubert
cc9b190e5d Minor style formats 2017-12-19 16:17:37 +01:00
Daniel García Aubert
4946ca688c Add test to check full-sample query 2017-12-19 16:17:13 +01:00
Daniel García Aubert
d2828ecaff Update test 2017-12-19 13:07:57 +01:00
Daniel García Aubert
5a3dd6a914 Use supported placemets of aggregation-query 2017-12-19 13:00:18 +01:00
Daniel García Aubert
bcd2fd8f88 Export supported placements 2017-12-19 12:59:33 +01:00
Daniel García Aubert
94a5e66881 Merge branch 'master' into full-sample 2017-12-19 12:47:36 +01:00
Daniel García Aubert
d55b78f76b Update next release version in NEWS 2017-12-19 12:46:29 +01:00
Daniel García Aubert
42149f9ae7 Update NEWS 2017-12-19 12:45:30 +01:00
Daniel
1e08d946b1 Merge pull request #822 from CartoDB/aggregation-validation
Validate aggregation input params
2017-12-19 12:42:28 +01:00
Daniel García Aubert
f22216e6d2 Catch error threw from constructor and follow node callback pattern 2017-12-19 12:23:54 +01:00
Raul Marin
d9cf830fb4 Stub for next release 2017-12-19 12:23:27 +01:00
Raul Marin
b762008c79 Release 4.5.0 2017-12-19 12:23:27 +01:00
Raul Marin
ca2c2b80d8 Update NEWS 2017-12-19 12:09:14 +01:00
Raul Marin
f946dfa65f Date histograms: Add tests for the new aggregation modes 2017-12-19 12:09:14 +01:00
Raul Marin
418f5faa11 Date histogram: Reduce the threshold to change in auto mode to 100 2017-12-19 12:09:14 +01:00
Raul Marin
bba6db9dbf Date histogram: Add second, decade, century and millenium aggregations 2017-12-19 12:09:14 +01:00
Daniel García Aubert
326cad2f2c Typo 2017-12-19 10:54:20 +01:00
Daniel García Aubert
34808d6147 Improve naming 2017-12-19 10:50:53 +01:00
Daniel García Aubert
79b04bbdfd Rename param 2017-12-19 10:47:53 +01:00
Daniel García Aubert
45a663d5ae Split columns validator 2017-12-19 10:43:34 +01:00
Daniel García Aubert
cace6169c0 Add function to create layer errors 2017-12-19 10:25:41 +01:00
Daniel García Aubert
bdce2f95f2 Add validations for columns 2017-12-18 20:42:26 +01:00
Javier Goizueta
506e16fc87 Experimental full-sample aggregation 2017-12-18 20:18:37 +01:00
Daniel García Aubert
c367743d76 Export SUPPORTED_AGGREGATE_FUNCTIONS 2017-12-18 20:06:16 +01:00
Daniel García Aubert
fa7140e736 Rename argument 2017-12-18 19:52:50 +01:00
Daniel García Aubert
c63226cd26 Improve function naming 2017-12-18 19:51:55 +01:00
Daniel García Aubert
777df6337b Style typo 2017-12-18 19:47:11 +01:00
Daniel García Aubert
2dda0a80da Improve error context 2017-12-18 19:35:12 +01:00
Daniel García Aubert
e2bd97eea6 Move validation to the constructor 2017-12-18 19:19:02 +01:00
Daniel García Aubert
fb03cd3424 Move aggregation validation to its own module 2017-12-18 19:17:43 +01:00
Daniel García Aubert
8a48b96c53 Rename file 2017-12-18 19:06:01 +01:00
Daniel García Aubert
76b0c94835 Rename file 2017-12-18 19:05:49 +01:00
Daniel García Aubert
6a36aa1f13 Order checks to validate if a layer should be adapted 2017-12-18 18:56:53 +01:00
Daniel García Aubert
800870e783 Remove local variable 2017-12-18 18:55:32 +01:00
Daniel García Aubert
6638ba91c3 Refactor supported geometry types 2017-12-18 18:53:44 +01:00
Daniel García Aubert
47e4b9da0d Encapsulate threshold layer validation in aggregation-mapconfig 2017-12-18 18:43:14 +01:00
Daniel García Aubert
81e0c3a098 Add RESOLUTION default getter 2017-12-18 18:26:08 +01:00
Daniel García Aubert
2068861988 Add PLACEMENT default getter 2017-12-18 18:24:09 +01:00
Daniel García Aubert
878f3bd627 Move .sql() to aggregation-mapconfig 2017-12-18 18:17:01 +01:00
Daniel García Aubert
170fcc1973 Move static methods 2017-12-18 17:42:12 +01:00
Daniel García Aubert
d0c88ce21d Improve naming 2017-12-18 17:26:41 +01:00
Javier Goizueta
86d8f28661 Merge pull request #818 from CartoDB/fix-aggr-grid-point
Fix grid-point aggregation placement
2017-12-18 16:37:39 +01:00
Javier Goizueta
e97147ddb4 Fix grid-point aggregation placement 2017-12-18 15:48:51 +01:00
Simon Martín
a40bc4a527 Merge pull request #816 from CartoDB/stringify-for-error-log
Stringify for error log
2017-12-18 15:03:28 +01:00
Simon Martín
77f64bee8c stringifyForLogs more usual case first 2017-12-18 14:54:36 +01:00
Daniel García Aubert
e81a16ce0d Improve validation by applying refactor 2017-12-18 14:31:53 +01:00
Daniel García Aubert
153a792fcb Improve validation by applying refactor 2017-12-18 14:25:44 +01:00
Daniel García Aubert
5c1b1e3214 Improve validation by applying refactor 2017-12-18 14:21:03 +01:00
Daniel García Aubert
0bca3d6f33 Validate placement, threshold and resolution 2017-12-18 13:42:27 +01:00
Simon Martín
14e90a6c76 add line at EOF 2017-12-18 12:59:44 +01:00
Simon Martín
a57cd25bec test escape chars function 2017-12-18 12:35:44 +01:00
Simon Martín
a46f7b3099 nested options and using it 2017-12-18 12:34:56 +01:00
Simon Martín
cb7fb97a13 escape chars function 2017-12-18 11:14:27 +01:00
Daniel García Aubert
e4ae3e235d Merge branch 'master' of github.com:CartoDB/Windshaft-cartodb 2017-12-18 10:17:04 +01:00
Javier Goizueta
423620b6c5 Merge pull request #813 from CartoDB/point-grid-bug
Fix point-grid aggregation bug
2017-12-15 11:27:56 +01:00
Javier Goizueta
877ed63090 Add tests for the different aggregation placement values 2017-12-15 11:14:17 +01:00
Ivan Malagon
8e9f61f9f1 Merge pull request #809 from CartoDB/analyses-filters-params
Add `no_filters` param to dataviews
2017-12-15 11:13:56 +01:00
Javier Goizueta
81e54660bb Fix point-grid aggregation bug 2017-12-15 10:52:41 +01:00
Daniel García Aubert
4d6c501fa5 Merge branch 'master' of github.com:CartoDB/Windshaft-cartodb 2017-12-15 10:31:55 +01:00
Raul Ochoa
55d9c02f8d Merge pull request #812 from CartoDB/fix-agg-point-sample-grid
Use source query as attribute instead of function
2017-12-14 19:58:46 +01:00
Raul Ochoa
a0c24d132e Use source query as attribute instead of function 2017-12-14 18:43:59 +00:00
Daniel García Aubert
6dd4914460 Update NEWS 2017-12-14 18:52:54 +01:00
Daniel
ee4e7b01a9 Merge pull request #806 from CartoDB/mapconfig-aggregation
Mapconfig aggregation
2017-12-14 18:38:00 +01:00
Daniel García Aubert
434de7786c Fix test from merge 2017-12-14 18:26:15 +01:00
Daniel García Aubert
07b4cb78b1 Merge branch 'master' into mapconfig-aggregation 2017-12-14 18:19:54 +01:00
Javier Goizueta
6b472c0b20 Experimental aggregation dimensions
This is not meant por public consumption (exposing SQL expressions is undesiderable)
2017-12-14 17:51:49 +01:00
Ivan Malagon
4b0a4dd675 Update NEWS.md 2017-12-14 17:44:46 +01:00
Ivan Malagon
97f8c361ed Merge branch 'master' into analyses-filters-params 2017-12-14 17:40:14 +01:00
Javier Goizueta
0c044636ef Fix mode aggregation 2017-12-14 17:22:50 +01:00
Javier Goizueta
f95c310462 Redefine aggregation torque to match Torque
Now the resolution aggregation parameter has the same meaning as in Torque (-torque-resolution in CartoCSS)
2017-12-14 17:04:23 +01:00
Javier Goizueta
9d8ce6bc44 Refactor aggregation resolution 2017-12-14 17:04:23 +01:00
Daniel García Aubert
e4407ece84 Merge branch 'mapconfig-aggregation' of github.com:CartoDB/Windshaft-cartodb into mapconfig-aggregation 2017-12-14 16:39:07 +01:00
Javier Goizueta
507d105ab2 Add mode aggregation 2017-12-14 16:37:40 +01:00
Javier Goizueta
ba6cca46a1 Fix aggregation queries 2017-12-14 16:37:15 +01:00
Javier Goizueta
753ada0e76 Add cartodb_id to test datasets 2017-12-14 16:36:24 +01:00
Daniel García Aubert
d311dccce8 Add test to check tangram compatibility 2017-12-14 16:35:09 +01:00
Javier Goizueta
b81cfe418a Always add a _cdb_feature_count to aggregated queries 2017-12-14 15:02:03 +01:00
Daniel García Aubert
8ee4a2c049 Merge branch 'mapconfig-aggregation' of github.com:CartoDB/Windshaft-cartodb into mapconfig-aggregation 2017-12-14 14:15:20 +01:00
Daniel García Aubert
a987f6ac05 Fix issue when the sql has single quotes defined and the aggregation metadata query was not able to estimate row count 2017-12-14 14:14:55 +01:00
Javier Goizueta
b0e47ecc62 Fix aggregation resolution parameter
It was implemented as the inverse of the intended value
2017-12-14 12:23:02 +01:00
Javier Goizueta
daa3fdca11 Fix bug in point-grid aggregation 2017-12-14 12:14:31 +01:00
Daniel García Aubert
bcfc43a517 jshint, my old friend 2017-12-14 11:22:00 +01:00
Daniel García Aubert
b83351a504 Use last release of windshaft 2017-12-13 20:07:23 +01:00
Daniel García Aubert
1edf684475 Fix test 2017-12-13 20:04:06 +01:00
Daniel García Aubert
0bc68d7144 Do not override sql_raw 2017-12-13 19:46:25 +01:00
Daniel García Aubert
52d1cd47db Do not validate aggregation missing columns. It will fail afterwards in map validation 2017-12-13 19:24:17 +01:00
Daniel García Aubert
98e8d745b1 Support sql_wrap for aggregation 2017-12-13 17:01:43 +01:00
Daniel García Aubert
e8740af6ef Fix issue when sql_wrap is provided and aggregation metadata query fails 2017-12-13 16:34:36 +01:00
Simon Martín
a00f468e62 NEWS.md 2017-12-13 15:07:55 +01:00
Simon Martín
27a52b66c6 Merge pull request #800 from CartoDB/errorLogs
Logging all errors
2017-12-13 15:05:11 +01:00
Simon Martín
96b9d498fd Merge branch 'master' into errorLogs 2017-12-13 14:50:51 +01:00
Javier Goizueta
6d30903531 Merge branch 'mapconfig-aggregation' of github.com:CartoDB/Windshaft-cartodb into mapconfig-aggregation
# Conflicts:
#	lib/cartodb/models/mapconfig/adapter/aggregation-mapconfig-adapter.js
2017-12-13 12:53:20 +01:00
Javier Goizueta
4a63fed943 Simplify Aggregation classes
We're using the same aggregation queries for the Raster and Vector cases, so we don't need the class hierarchies used to handled them differently.
AggregationProxy has been renamed to Aggregation
2017-12-13 12:35:17 +01:00
Daniel García Aubert
6fe73862f3 Create a MapConfig's subclass to delegate aggregation 2017-12-13 11:42:51 +01:00
Ivan Malagon
1664975dd1 Add spec 2017-12-13 10:43:43 +01:00
Ivan Malagon
02ac25181e Return error if no_filters and own_filter are present 2017-12-13 09:45:35 +01:00
Daniel García Aubert
239aa12622 Merge branch 'mapconfig-aggregation' of github.com:CartoDB/Windshaft-cartodb into mapconfig-aggregation 2017-12-12 20:28:50 +01:00
Daniel García Aubert
aa43eb8953 Remove aggregation validation and use MapConfig validation 2017-12-12 20:10:42 +01:00
Daniel García Aubert
6d46a21005 Validate aggregation query param 2017-12-12 19:23:21 +01:00
Raul Ochoa
fb7f79594d Merge remote-tracking branch 'origin/master' into mapconfig-aggregation 2017-12-12 17:15:22 +00:00
Daniel García Aubert
f390a10830 Remove methods that check map-config aggregation and use the ones that MapConfig model provides 2017-12-12 17:58:42 +01:00
Javier Goizueta
4193f96c03 Fix point-grid aggregation query 2017-12-12 17:38:39 +01:00
Raul Ochoa
afa1e2881f Stubs next version 2017-12-12 16:24:12 +00:00
Raul Ochoa
3fa6750f9a Release 4.4.0 2017-12-12 16:23:37 +00:00
Raul Ochoa
1c842c1592 Merge pull request #810 from CartoDB/upgrade-camshaft
Upgrades camshaft to 0.60.0
2017-12-12 17:22:06 +01:00
Raul Ochoa
3c88634d09 Upgrades camshaft to 0.60.0 2017-12-12 16:14:49 +00:00
Simon Martín
19bb11adc5 line at EOF 2017-12-12 16:59:07 +01:00
Javier Goizueta
4405d61845 Remove support for arbitrary aggregation SQL expressions.
Only the supported aggregate functions can be used now, currently count, sum, avg, min & max.
2017-12-12 16:17:42 +01:00
Simon Martín
1bb716ef33 Merge branch 'master' into errorLogs 2017-12-12 16:15:30 +01:00
Javier Goizueta
eb2825eea8 Merge branch 'mapconfig-aggregation' of github.com:CartoDB/Windshaft-cartodb into mapconfig-aggregation
# Conflicts:
#	lib/cartodb/models/aggregation/aggregation-templates.js
2017-12-12 16:08:58 +01:00
Ivan Malagon
811f2bdae3 Fix linter problem 2017-12-12 16:04:25 +01:00
Ivan Malagon
53bc14bc9e Add missing contributors 2017-12-12 16:02:39 +01:00
Ivan Malagon
50ddfaa968 Fix broken spec 2017-12-12 15:56:16 +01:00
Daniel García Aubert
ae35acd21d typo 2017-12-12 15:54:36 +01:00
Daniel García Aubert
d4d32bdfa3 Make jshint more happy 2017-12-12 15:53:35 +01:00
Javier Goizueta
3b7db0b08f Fix typo 2017-12-12 15:48:25 +01:00
Ivan Malagon
43fec74372 Modify params specs 2017-12-12 15:12:33 +01:00
Mario de Frutos
c7f5f310f0 Stubs next version 2017-12-12 13:46:54 +01:00
Daniel García Aubert
e26cfb2efb Remove magic number 2017-12-12 13:32:01 +01:00
Mario de Frutos
b32d056efe Updated NEWS.md 2017-12-12 13:22:57 +01:00
Mario de Frutos
65308ea2eb Updated NEWS.md 2017-12-12 13:21:27 +01:00
Mario de Frutos
8d16bf566d Force png tile generation for static maps (#808)
* Force png tile generation for static maps

If the user tries to generate a static map requesting JPG it will fail
because is going to try to generate the tiles using JPG as format which
is not supported by now, this bug was introduced in the version 4.0.1

So we now force, again, the tiles to be generated as PNG but we pass
the requested format, JPG, to windshaft to generate the final image as
the user reqests

* Added support to define image format in the image assertions

* Added test for JPEG static image generation

Also I've added support for:

- JPEG images
- Different tolerance based on the file type, it seems that due to
  different compression we need different tolerance for JPG images
2017-12-12 13:20:22 +01:00
Daniel García Aubert
0b27d174ef Check if query retrieves results 2017-12-12 12:53:29 +01:00
Daniel García Aubert
869f2ac322 Improve error message 2017-12-12 12:39:12 +01:00
Daniel García Aubert
5bc1903677 Add test to check if cartoccs and aggregation definition are fully compatible 2017-12-12 12:15:13 +01:00
Daniel García Aubert
faaebaa07d Remove console.log 2017-12-12 12:02:10 +01:00
Daniel García Aubert
eceffda87f Do not use control flag 2017-12-12 12:01:25 +01:00
Daniel García Aubert
e93fe13b41 Get the right columns from aggregation 2017-12-12 11:57:38 +01:00
Ivan Malagon
245d24ea29 Merge branch 'master' into analyses-filters-params 2017-12-12 11:54:32 +01:00
Ivan Malagon
605be77a04 Add nofilters query param 2017-12-12 11:54:09 +01:00
Daniel García Aubert
acd0610517 Merge branch 'mapconfig-aggregation' of github.com:CartoDB/Windshaft-cartodb into mapconfig-aggregation 2017-12-12 11:31:56 +01:00
Javier Goizueta
e37682403c Fix test
Note that the CartoCSS should reference columns of the aggregated table
2017-12-12 11:22:10 +01:00
Javier Goizueta
b2fcbdd8a3 Implement aggregation columns 2017-12-12 11:22:09 +01:00
Daniel García Aubert
2f68d658f0 Remove local variable 2017-12-12 11:10:12 +01:00
Daniel García Aubert
85e7245a33 Remove control flag 2017-12-12 11:07:04 +01:00
Simon Martín
2db2546cca changing error log format 2017-12-12 11:04:06 +01:00
Simon Martín
b3d7909849 removing default error log value 2017-12-12 11:03:52 +01:00
Simon Martín
b035b5d384 Merge branch 'master' into errorLogs 2017-12-12 10:59:07 +01:00
Daniel García Aubert
f52cc276be Remove control flag 2017-12-12 10:57:50 +01:00
Daniel García Aubert
c637caf9c9 Replace nested conditional with guard clause 2017-12-12 10:56:23 +01:00
Daniel García Aubert
d405987a96 Replace nested conditional with guard clause 2017-12-12 10:49:05 +01:00
Daniel García Aubert
06efe410ef Replace nested conditional with guard clause (early return) 2017-12-12 10:43:49 +01:00
Daniel García Aubert
5bf4eba215 Remove unused thenable 2017-12-11 19:35:59 +01:00
Daniel García Aubert
87c4848e19 Improve namig 2017-12-11 19:22:15 +01:00
Daniel García Aubert
3f075ca432 Remove unused argument 2017-12-11 19:18:29 +01:00
Daniel García Aubert
6725025e1a Improve naming for a method 2017-12-11 19:17:32 +01:00
Daniel García Aubert
8d42909eab Change argument order to be more consistent 2017-12-11 19:14:16 +01:00
Daniel García Aubert
d947700646 Get connection at the begining of adapt layers functionality 2017-12-11 19:12:10 +01:00
Daniel García Aubert
cc68b84212 Extract checkLayerAggregationMetadata method 2017-12-11 19:06:53 +01:00
Daniel García Aubert
446449bbde Move variable declaration close to the place that it's used 2017-12-11 18:47:20 +01:00
Daniel García Aubert
b1f788fb57 Remove unuseful callback 2017-12-11 18:42:03 +01:00
Daniel García Aubert
f80e7112bc Merge branch 'mapconfig-aggregation' of github.com:CartoDB/Windshaft-cartodb into mapconfig-aggregation 2017-12-11 18:35:54 +01:00
Daniel García Aubert
68f967e582 Extract adaptLayer method 2017-12-11 18:34:22 +01:00
Javier Goizueta
2edcbb4724 Implement aggregation queries.
Implmented for placements: centroid, point-gird, point-sample.
Aggregated columns not yet implemented (only count).
Aggregation could be made more efficient by using quadkeys
2017-12-11 18:33:06 +01:00
Daniel García Aubert
006dd86614 Merge branch 'master' into mapconfig-aggregation 2017-12-11 17:36:58 +01:00
Daniel García Aubert
dab204ea71 Do not aggregate if rows cout is lower than threshold or the layer's sql has geometries distinct of points 2017-12-11 17:32:06 +01:00
Raul Marin
f2fa650661 Stub for next release 2017-12-11 17:24:26 +01:00
Simon Martín
1c6c3962db Merge branch 'master' into errorLogs 2017-12-11 12:59:42 +01:00
Raul Marin
aa57cdefb3 Release 4.3.0 2017-12-11 11:27:13 +01:00
Raul Marin
7c5b7641d8 Date histogram: Add missing GROUP BY column 2017-12-11 11:27:13 +01:00
Raul Marin
eb4a49ec92 Update NEWs file with the changes 2017-12-11 11:27:13 +01:00
Raul Marin
88f02458db Timeseries tests: Make jshint happy 2017-12-11 11:27:13 +01:00
Raul Marin
1b405e42c2 Date histogram optimizations 2017-12-11 11:27:13 +01:00
Raul Marin
bb5bfd10ee Timeseries tests: Make them work with any DB setup 2017-12-11 11:27:13 +01:00
Raul Marin
088a8b81a6 Timeseries tests: Set timestamps to the start of the aggregations
Also, increase the end of the range check so it includes some data
2017-12-11 11:27:13 +01:00
Raul Marin
243e982bd6 Optimize formulae queries 2017-12-11 11:27:13 +01:00
Raul Marin
dfe01c836c Escape getQueryRowCount with $$ 2017-12-11 11:27:13 +01:00
Raul Marin
fcbf5ffcc5 Move sql helper functions to query-utils.js 2017-12-11 11:27:13 +01:00
Raul Marin
90c9ad18e0 Optimize histogram queries 2017-12-11 11:27:13 +01:00
Daniel García Aubert
214d684fcc Adapt layer when is vector only map-caonfig 2017-12-05 20:39:30 +01:00
Daniel García Aubert
9118e2dc5e Add tests 2017-12-05 20:21:20 +01:00
Daniel García Aubert
e7592ee570 Improve error message 2017-12-05 17:44:52 +01:00
Daniel García Aubert
81d99ca655 Make test to pass 2017-12-05 16:52:15 +01:00
Daniel García Aubert
7b35701fa8 Extract method 2017-12-05 16:50:18 +01:00
Daniel García Aubert
4f8b541010 Mark aggregation queries 2017-12-05 13:12:25 +01:00
Daniel García Aubert
55dd049812 Be able to skip aggregation to create a layergroup with aggregation defined already 2017-12-05 12:59:32 +01:00
Daniel García Aubert
66b41a6ae7 Now .getLayergroup() in test client accepts params to perform custom instantiations 2017-12-05 12:09:31 +01:00
Daniel García Aubert
499e9de75d Use devel branch of windshaft 2017-12-04 19:49:35 +01:00
Daniel García Aubert
855f47e446 Detect incompatible CartoCSS or interactivity for raster aggregation 2017-12-04 19:48:06 +01:00
Daniel García Aubert
fc472e65b6 Update yarn.lock 2017-12-04 15:31:45 +01:00
Daniel García Aubert
91e0e0fd18 Merge branch 'master' into mapconfig-aggregation 2017-12-04 14:49:44 +01:00
Daniel García Aubert
16a36a9d7a Stubs next version 2017-12-04 13:35:23 +01:00
Daniel García Aubert
893b886a1e Release 4.2.0 2017-12-04 13:34:02 +01:00
Daniel
a60b335151 Merge pull request #803 from CartoDB/vr-aggregation
Allow to request MVT tiles without CartoCSS
2017-12-04 13:32:15 +01:00
Daniel García Aubert
9ee0e2c3d0 Merge branch 'master' into vr-aggregation 2017-12-04 13:26:53 +01:00
Daniel García Aubert
565cfb7fbe Update NEWS 2017-12-04 13:23:09 +01:00
Daniel García Aubert
0c8a31fad9 Remove duplicated code 2017-12-04 13:17:37 +01:00
Daniel García Aubert
169b95809a Upgrade windshaft to version 4.1.0 2017-12-04 13:06:45 +01:00
Daniel García Aubert
077f19d506 Integrate aggregation and get metadata for layergroup 2017-12-04 12:40:53 +01:00
Simon Martín
ed51513b5e adding error header acceptance test 2017-12-01 17:52:20 +01:00
Daniel García Aubert
52630b8084 Minor improvementes 2017-12-01 17:06:42 +01:00
Daniel García Aubert
6f04214f5d Simplify to pass test 2017-12-01 17:06:03 +01:00
Daniel García Aubert
f376a7cdd5 Use aggregation adapter before the overviews one 2017-12-01 17:05:01 +01:00
Daniel García Aubert
b7c6f5acdf Merge branch 'vr-aggregation' into mapconfig-aggregation 2017-12-01 16:23:54 +01:00
Daniel García Aubert
0887e5d5f7 Extract method 2017-12-01 15:43:15 +01:00
Daniel García Aubert
23c0cb757d Fix tests according to the last changes in windshaft 2017-12-01 13:52:28 +01:00
Daniel García Aubert
d01857923e Plug aggregation mapconfig adapter 2017-11-30 19:31:00 +01:00
Daniel García Aubert
deb29f2c77 Implement aggregation mapconfig adapter (happy case) 2017-11-30 19:20:59 +01:00
Daniel García Aubert
d937ed31d5 Add params to instantiate aggregation 2017-11-30 19:10:57 +01:00
Daniel García Aubert
73ae736603 Add aggregation proxy 2017-11-30 19:02:30 +01:00
Daniel García Aubert
1767b83d09 Aggregation query models: bootstrap hierarchy classes 2017-11-30 15:34:20 +01:00
Simon Martín
ba3af551e3 update test file name 2017-11-30 15:04:38 +01:00
Simon Martín
e0d4a9e596 change funcion name 2017-11-30 15:04:07 +01:00
Daniel García Aubert
e18e86f565 jshint, jshint, jshint 2017-11-29 18:28:42 +01:00
Daniel García Aubert
496778c276 Separate suites 2017-11-29 18:17:51 +01:00
Mario de Frutos
0ba4975360 Bump next version 2017-11-29 18:02:46 +01:00
Daniel García Aubert
e3d95fa654 Merge branch 'master' into vr-aggregation 2017-11-29 17:16:57 +01:00
Daniel García Aubert
cded5afdcb Ahh! jshint.. my old friend 2017-11-29 16:56:28 +01:00
Daniel García Aubert
1b6de9961a Do not use polygons 2017-11-29 16:47:56 +01:00
Mario de Frutos
a20e789302 Release 4.1.1 2017-11-29 16:34:48 +01:00
Mario de Frutos
f41af41bd4 Update to turbo-carto 0.20.2 (#805) 2017-11-29 16:32:22 +01:00
Daniel García Aubert
c9e0f330c0 Add test to check incompatible layers 2017-11-29 16:15:52 +01:00
Daniel García Aubert
f9428682f9 Remove mocha filter 2017-11-29 14:38:06 +01:00
Daniel García Aubert
330f8f3cb5 Test invalid format for vector layergroup 2017-11-29 14:10:56 +01:00
Daniel García Aubert
8270699b8e Tests to fect mvt tiles without styles 2017-11-29 13:12:09 +01:00
Simon Martín
555d3f558c changing error log structure 2017-11-28 18:22:55 +01:00
Simon Martín
386d6bfea8 removing unneeded check 2017-11-28 18:19:28 +01:00
Ubuntu
51c19c0b2e Skip test 2017-11-28 16:49:55 +00:00
Simon Martín
479b8be639 ensuring errored JSONP write a error status code in log 2017-11-28 17:27:05 +01:00
Simon Martín
a007fce913 ensuring vars 2017-11-28 16:02:12 +01:00
Simon Martín
100a2986b9 ensuring all properties in errors headers 2017-11-27 18:43:48 +01:00
Simon Martín
752bfe779e forgotten 'only' 2017-11-27 18:15:27 +01:00
Simon Martín
8cf878f723 testing X-Tiler-Errors existence 2017-11-27 18:14:02 +01:00
Simon Martín
605d7057c9 fix copying array of errors and adding error.label to logs 2017-11-27 18:12:44 +01:00
Simon Martín
60e4defa66 default value in errors header 2017-11-27 17:04:50 +01:00
Simon Martín
e7b8d9b223 moving logErrors to right position 2017-11-27 16:55:11 +01:00
Simon Martín
e041b5b8a9 removing ~lost space 2017-11-27 16:52:19 +01:00
Simon Martín
4a2950796b rest of environments config 2017-11-27 16:48:25 +01:00
Simon Martín
9a8f72b8db format details 2017-11-27 16:47:45 +01:00
Simon Martín
667925c455 adding error name, ensuring data and moving errors copy 2017-11-27 16:43:04 +01:00
Daniel García Aubert
de376eef86 vr-aggregation: link to windshaft#vr-aggregation 2017-11-27 14:52:30 +01:00
Simon Martín
f24217a400 cloning object and removing logs 2017-11-24 18:06:17 +01:00
Simon Martín
84fd01535c adding errors to errors header 2017-11-24 17:53:07 +01:00
Simon Martín
e362fca9eb adding new header 2017-11-24 17:52:26 +01:00
Daniel
0a106cd038 Merge pull request #795 from CartoDB/middlewarify-analyses-controller
Middlewarify analyses controller
2017-11-23 12:27:04 +01:00
Daniel García Aubert
1a78b8a75a Merge branch 'master' into middlewarify-analyses-controller 2017-11-23 11:47:57 +01:00
Daniel García Aubert
e131df601c Use ES6 template string for route definition 2017-11-19 14:05:20 +01:00
Daniel García Aubert
967d9b76e6 Fix EOF 2017-11-19 13:47:07 +01:00
Daniel García Aubert
bee04e2553 Usr ES6 string templates 2017-11-19 12:51:35 +01:00
Daniel García Aubert
37111f396d Unify get catalog and tables middlewares 2017-11-19 12:37:09 +01:00
Daniel García Aubert
4df46fe5ea Move creation of PG client to a middleware 2017-11-17 19:20:42 +01:00
Daniel García Aubert
b1b2054f0a Split metadata function into two middlewares 2017-11-17 19:14:31 +01:00
Daniel García Aubert
c1f2b96bfc Remove step from catalog middleware 2017-11-17 18:32:46 +01:00
Daniel García Aubert
804c6645fa Make catalog method a regular middleware factory 2017-11-17 18:28:37 +01:00
Daniel García Aubert
5d6ccc07fd Move functionality that prepares catalog to be used as response 2017-11-17 18:25:13 +01:00
Daniel García Aubert
a585ba5480 Use early retutn node pattern 2017-11-17 18:07:19 +01:00
Simon Martín
448dcc7d82 Merge pull request #779 from CartoDB/res-locals-user
Don't overwrite data in copy from req.params to res.locals
2017-11-16 11:48:16 +01:00
Simon Martín
0aaafa2068 Merge branch 'master' into res-locals-user 2017-11-16 11:03:43 +01:00
Simon Martín
1aa981d556 Merge pull request #787 from CartoDB/timeoutVectorImage
Timeout vector image
2017-11-16 11:01:16 +01:00
Simon Martín
ccce598b04 Merge branch 'master' into res-locals-user 2017-11-15 18:37:49 +01:00
Simon Martín
667b2a9cb1 removing underscore dependency in locals middleware 2017-11-15 16:56:21 +01:00
Simon Martín
298882f410 remove with spaces and adding line as EOF 2017-11-15 15:07:30 +01:00
Raul Marin
6aaa5f99e2 Test: PARALLEL compatibility (OSX) 2017-11-14 16:18:22 +01:00
Simon Martín
22e3016cd3 Merge branch 'master' into timeoutVectorImage 2017-11-14 15:54:16 +01:00
Daniel García Aubert
d5c552a03a Move functionality that sets cache control header to a middleware 2017-11-14 13:53:42 +01:00
Daniel García Aubert
a5347c27e3 Move sendResponse method to a middleware 2017-11-14 13:49:12 +01:00
Daniel García Aubert
520e84e46b Add acceptance test for analyses controller 2017-11-14 13:46:47 +01:00
Daniel García Aubert
27521964c7 Remove schema from analyses catalog query perfoming that query in the same way that we do for pooling the node status in analyses backend 2017-11-14 12:01:21 +01:00
Daniel García Aubert
bdf4827300 Fix bad call to next middleware callback 2017-11-14 11:28:06 +01:00
Raul Marin
172b3ece71 Test: Strip PARALLEL labels for PostgreSQL releases before 9.6 2017-11-13 14:59:13 +01:00
Daniel
71146dbfaf Merge pull request #790 from CartoDB/fix-issue-maps-controller-refactor
Fix issue maps controller refactor
2017-11-13 13:04:41 +01:00
Daniel García Aubert
38ca5db51b Inline method to set cache-cannel, rollback extract setCacheChannel method for named maps controller 2017-11-13 12:35:46 +01:00
Daniel García Aubert
590233e3ee Inline method to set cache-cannel, rollback extract setCacheChannel method 2017-11-13 12:15:12 +01:00
Daniel García Aubert
6f59c61c8b Add more steps while profiling 2017-11-08 14:27:35 +01:00
Daniel García Aubert
aff5fcda63 Add namespace for getMapConfig step while profiling 2017-11-08 14:08:27 +01:00
Daniel García Aubert
56d33b7f5b Add profiling 2017-11-08 10:52:25 +01:00
Daniel
749b205944 Merge pull request #788 from CartoDB/786-number-categories-param
Add a "category" query param to define the number of categories to be ranked for aggregation dataviews
2017-11-07 17:11:25 +01:00
Daniel
ad0c035e2d Merge pull request #784 from CartoDB/middlewarify-maps-controller
[Middlewarify] Middlewarify map controller
2017-11-07 17:09:02 +01:00
Daniel García Aubert
d15ccd271e Merge branch 'master' into middlewarify-maps-controller 2017-11-07 16:36:52 +01:00
Daniel García Aubert
2aee357006 Improve test structure 2017-11-07 16:28:37 +01:00
Daniel García Aubert
fc9dce0cca Fix typo 2017-11-07 16:22:36 +01:00
Daniel García Aubert
9149f72f42 Update NEWS 2017-11-07 16:21:55 +01:00
Daniel García Aubert
743bb0723b Add query param to define the number of categories to be ranked 2017-11-07 16:14:47 +01:00
Simon Martín
0bf36fa058 remove unneeded encoding control 2017-11-07 16:02:37 +01:00
Simon Martín
970310bf7f add vectorError middleware to named map endpoint 2017-11-07 16:01:08 +01:00
Simon Martín
4fc90db495 timeout error vector with lines 2017-11-07 15:57:33 +01:00
Daniel García Aubert
50ecdb5fee Add test to ensure that categories param is used to compose the aggregation dataview 2017-11-07 12:51:48 +01:00
Simon Martín
1ea4fc50c9 make jshint happy 2017-11-07 11:17:32 +01:00
Simon Martín
cda9a09b8e vector timeout error tests 2017-11-07 11:08:01 +01:00
Simon Martín
216c877f4b verctor error middleware 2017-11-07 11:07:38 +01:00
Simon Martín
33fbff5011 some improvements mvt timeout error script 2017-11-07 11:06:53 +01:00
Daniel García Aubert
c48e89826d Split middleware to follow SRP 2017-11-07 09:50:52 +01:00
Daniel
52542e4a88 Merge pull request #783 from CartoDB/refactor-send-method
[Middlewarify] Don't use a base method to send responses
2017-11-07 09:37:57 +01:00
Daniel García Aubert
693a2e7bee Order middlewares 2017-11-05 19:13:56 +01:00
Daniel García Aubert
f9ba3c41d3 Create new middlewares to init profiling and another to check JSON content-type 2017-11-05 18:55:23 +01:00
Simon Martín
ac153232d0 mvt timeout error creation script 2017-11-03 17:21:01 +01:00
Daniel García Aubert
46289f27df Remove TODO 2017-11-03 15:26:25 +01:00
Daniel García Aubert
05ccf20634 Rename function 2017-11-03 15:12:18 +01:00
Daniel García Aubert
6acb873d95 Enforce all middlewares to follow the same constructor pattern 2017-11-03 15:06:15 +01:00
Daniel García Aubert
65e8609fec Do not bind context if not needed 2017-11-03 09:47:46 +01:00
Daniel García Aubert
677f6caab8 remove funtion 2017-11-03 09:38:36 +01:00
Daniel García Aubert
cb167313d2 Unify middleware builder functions 2017-11-03 09:37:01 +01:00
Daniel García Aubert
2854d0252c Fix typo 2017-11-03 08:48:13 +01:00
Daniel García Aubert
717332d941 Compose instantiate layergroup middleware 2017-11-02 19:24:33 +01:00
Daniel García Aubert
4607e4a12d Compose create layergroup middleware 2017-11-02 19:03:20 +01:00
Daniel García Aubert
3e7106002d Rename response middleware 2017-11-02 18:39:46 +01:00
Daniel García Aubert
08b91f935d Rename error middleware 2017-11-02 18:38:34 +01:00
Daniel García Aubert
1d08734721 Rename middleware 2017-11-02 18:28:37 +01:00
Daniel García Aubert
b11b872b75 Remove step requirement 2017-11-02 11:29:43 +01:00
Daniel García Aubert
93bd2c9e50 Build afterLayergroupCreate middleware as an array of middlewares instead of preforming all them as one middleware 2017-11-02 10:43:22 +01:00
Daniel García Aubert
658763da8c Build after layergroup create while registering routes 2017-11-02 10:33:39 +01:00
Daniel García Aubert
d2b5eaa8c3 Do not proxy create and intantiate middlewares 2017-11-02 10:28:33 +01:00
Daniel García Aubert
eb5bf52bd9 Move profiler start to the right place 2017-11-02 10:22:30 +01:00
Daniel García Aubert
c8000e5cf8 Make a middleware to respond layergroup 2017-11-01 20:06:32 +01:00
Daniel García Aubert
46c76d6a4c Create middleware for layergroup creation (anonymous map) 2017-11-01 19:57:20 +01:00
Daniel García Aubert
e6bec5ccb0 Make style linter happy 2017-11-01 19:28:32 +01:00
Daniel García Aubert
125587522f Create middleware for layergroup creation 2017-11-01 19:27:01 +01:00
Daniel García Aubert
aeb9585708 extract prepare mapconfig and get template to their respective middlewares 2017-11-01 19:02:07 +01:00
Daniel García Aubert
8ed5df0072 Move prepeareConfigFn to a middleware 2017-11-01 17:57:35 +01:00
Daniel García Aubert
6bbaeaa286 Create a custom error middleware to augment error info 2017-10-31 20:49:26 +01:00
Daniel García Aubert
3d15551cb5 Minor style umprovements 2017-10-31 20:10:37 +01:00
Daniel García Aubert
e0ffeb0adc extract surrogate key functionality to its own middleware 2017-10-31 19:50:36 +01:00
Daniel García Aubert
e06f8fe25e Set layergroup-id header in the right middleware 2017-10-31 18:54:32 +01:00
Daniel García Aubert
da2228088e Extract context metadata (turbo-carto) functionallity to its own middleware 2017-10-31 18:42:11 +01:00
Daniel García Aubert
cdc39c8cae Extract addAnalysesMetadata functionallity to its own middleware 2017-10-31 18:25:17 +01:00
Daniel García Aubert
99fa66c026 Extract hash template for layergroup id and dataviews/widgets to a middlewares 2017-10-31 18:06:14 +01:00
Daniel García Aubert
d85a5d83b7 Make afterLayergroupCreate function as a 'middleware' builder 2017-10-31 17:59:32 +01:00
Daniel García Aubert
bb02494e02 Do not perform "increment map view count" in parallel 2017-10-31 17:09:42 +01:00
Daniel García Aubert
39eb0f7bec Avoid regression and update comment 2017-10-31 16:58:00 +01:00
Daniel García Aubert
5f7d5f6ec8 Get analyses results from res.locals 2017-10-31 16:01:18 +01:00
Daniel García Aubert
a4b2044e10 missing early return 2017-10-31 15:58:41 +01:00
Daniel García Aubert
d1093686a3 Avoid to hold info in local variables 2017-10-31 15:51:42 +01:00
Daniel García Aubert
12822c4341 Follow node.js convention regarding early returns 2017-10-31 15:49:10 +01:00
Daniel García Aubert
fab87e2168 Get layergroup from locals. It's not provided by previous middleware anymore 2017-10-31 15:47:59 +01:00
Daniel García Aubert
34e219353c do not pass layergroup since it's already available in res.locals 2017-10-31 14:47:29 +01:00
Daniel García Aubert
3cf4a8f70b Extract layergroup data augmentation to its own "middleware" 2017-10-31 13:46:03 +01:00
Daniel García Aubert
48172d4dc1 make afterLayergroupCreate to follow the middleware signature 2017-10-31 13:36:17 +01:00
Daniel García Aubert
467bee4c91 Split afterLayergroupCreate method in multiple "pre-middlewares" 2017-10-31 13:13:20 +01:00
Daniel García Aubert
3f2ef63976 Extract cache channel to its own method 2017-10-31 11:38:54 +01:00
Daniel García Aubert
235f5e4566 Extract cache channel to its own method 2017-10-31 11:38:18 +01:00
Daniel García Aubert
3f49743cd0 Remove BaseController dependency and remove unused code 2017-10-30 19:30:03 +01:00
Daniel García Aubert
fb3afaa6ab Fix jshint max-complexity issue by using extract method 2017-10-30 19:29:19 +01:00
Daniel García Aubert
b6c405bf68 Remove send method in base controller and remove BaseController class 2017-10-30 19:28:40 +01:00
Daniel
da87a95dd9 Merge pull request #781 from CartoDB/upgrade-windshaft-4.0.1
Upgrade windshaft to version 4.0.1
2017-10-27 11:33:42 +02:00
Daniel García Aubert
cd7c604d10 Update NEWS 2017-10-27 11:18:10 +02:00
Daniel García Aubert
b7227e0581 Upgrade windshaft to version 4.0.1 2017-10-27 11:11:58 +02:00
Iñigo Medina
c564f5467a Merge pull request #780 from CartoDB/docs-format-steps
Fixed numbering formatting for steps
2017-10-20 20:05:41 +02:00
csobier
b04cc9c228 Fixed numbering formatting for steps
No code changes, just formatting. (The API repos require different format of numbering then that docs. I couldn't see it until the output was live).
2017-10-20 12:52:40 -04:00
Simon Martín
c0df0d12c6 ensure dont overwrite data in copy from req.params to res.locals 2017-10-20 17:21:55 +02:00
Daniel García Aubert
00f81db57e Fixed default value for own_filter 2017-10-20 16:47:56 +02:00
Daniel García Aubert
0c9d60b573 Add support for no_filters params in dataviews 2017-10-20 16:19:24 +02:00
Iñigo Medina
5645cd16b3 Merge pull request #778 from CartoDB/docs-1236-vector-tiles
mvt content added to anonymous map chapter, under retrieve resources …
2017-10-19 16:22:56 +02:00
csobier
eb6da1398e mvt content added to anonymous map chapter, under retrieve resources heading- WIP 2017-10-18 13:34:33 -04:00
Daniel García Aubert
35c5cd34c2 Stubs next version 2017-10-18 15:19:37 +02:00
Daniel García Aubert
6c51667ffb Merge branch 'master' of github.com:CartoDB/Windshaft-cartodb 2017-10-18 15:18:14 +02:00
Daniel García Aubert
1396ca9fe3 Release 4.0.1 2017-10-18 15:17:17 +02:00
Iñigo Medina
56bb083239 Merge pull request #775 from CartoDB/docs-1236-vector-tiles
added map tile rendering and MVT info to Maps API doc- WIP
2017-10-18 15:15:38 +02:00
Daniel García Aubert
ddcb812218 Update NEWS 2017-10-18 15:14:03 +02:00
Daniel
66c3d58b92 Merge pull request #777 from CartoDB/upgrade-camshaft-0.59.4
Upgrade camshaft to version 0.59.4
2017-10-18 15:08:02 +02:00
Daniel García Aubert
9326217c18 Upgrade camshaft to version 0.59.4 2017-10-18 14:36:04 +02:00
csobier
1207764c18 added map tile rendering and MVT info to Maps API doc- WIP 2017-10-16 15:54:49 -04:00
Daniel
73a633ae7d Merge pull request #767 from CartoDB/stats-middleware
Implement stats middleware removing some duplicated code
2017-10-16 16:08:55 +02:00
Daniel García Aubert
3068ff1ea4 Update NEWS 2017-10-16 15:16:21 +02:00
Daniel García Aubert
9ad6d0cbcc Merge branch 'master' into stats-middleware 2017-10-16 15:06:20 +02:00
Daniel
86389382fa Merge pull request #773 from CartoDB/fix-test-postgis-2.3
Fix tests in master branch while testing in devel environment (NOT CI)
2017-10-16 14:49:27 +02:00
Daniel García Aubert
c2bf7b075c Fix travis to export environment variable (POSTGIS_VERSION) 2017-10-16 13:24:46 +02:00
Daniel García Aubert
294a222669 Configure travis to export environment variable (POSTGIS_VERSION) 2017-10-16 13:20:30 +02:00
Daniel García Aubert
515146bf28 Configure travis to export environment variable (POSTGIS_VERSION) 2017-10-16 13:12:06 +02:00
Daniel García Aubert
a1c08f9bf7 Revert docker-test config 2017-10-16 12:53:23 +02:00
Daniel García Aubert
f8ff41be01 Do not run test if postgis version is lower than or equal to 2.4 2017-10-16 12:15:28 +02:00
Daniel García Aubert
67ab12e8e7 Add environment variable to check whether to run test against postgis 2.4 or not 2017-10-16 11:08:46 +02:00
David M
d959ef5007 Merge pull request #772 from CartoDB/windshaft4
Target Windshaft ~4.0.0, yarn.lock updated
2017-10-11 11:44:40 +02:00
David Manzanares
8cc4fe5b56 Target Windshaft ~4.0.0, yarn.lock updated 2017-10-11 11:34:27 +02:00
David M
22a34d763c Merge pull request #762 from CartoDB/mvt-path-conf
Add configuration flag to enable/disable direct PgSQL MVT
2017-10-11 10:42:09 +02:00
Raul Ochoa
ad227a5240 Merge remote-tracking branch 'origin/master' into analyses-filters 2017-10-10 16:35:11 +00:00
David Manzanares
d30f710534 Merge remote-tracking branch 'origin/master' into mvt-path-conf 2017-10-10 17:53:28 +02:00
Daniel García Aubert
02304dc450 Merge branch 'master' into stats-middleware 2017-10-10 16:56:35 +02:00
Daniel García Aubert
893fac31a7 Update NEWS 2017-10-10 16:44:11 +02:00
Simon Martín
abef8918c0 Merge pull request #751 from CartoDB/middlewarify
Middlewarify
2017-10-10 15:10:14 +02:00
Simon Martín
8380d291d0 Merge branch 'master' into middlewarify 2017-10-10 14:59:50 +02:00
Daniel García Aubert
251e636ad2 Fix bad argument list while calling to staticMap function 2017-10-10 11:58:24 +02:00
Raul Ochoa
286059b8a3 Merge pull request #770 from CartoDB/test-client-consistent-signature-fix
Make all calls to finish to match (err, res) signature
2017-10-09 18:40:15 +02:00
David M
b18bf967fd Workaround lack of template creation clean-up 2017-10-09 18:27:57 +02:00
Daniel García Aubert
a81e98995a Merge branch 'middlewarify' into stats-middleware 2017-10-09 17:54:32 +02:00
Raul Ochoa
a797e13eb3 Make all calls to finish to match (err, res) signature 2017-10-09 15:51:42 +00:00
Daniel
5e073f39bd Merge pull request #765 from CartoDB/res-locals
req.params to res.locals
2017-10-09 17:44:01 +02:00
David M
8a88b29665 update dependencies 2017-10-09 17:16:29 +02:00
David M
d77739dfa4 PostGIS MVT v2 compliance test skipped without skipping mapnik 2017-10-09 16:46:23 +02:00
Simon Martín
484e0fda2f undo changing services params 2017-10-09 16:29:35 +02:00
David M
3827901535 Skip test to ignore MVT v2 compliance 2017-10-09 15:53:50 +02:00
David M
82648df21c Fix jshint 2017-10-09 15:49:51 +02:00
David M
1766cd0ad4 Buffersize test fixed to test PostGIS and mapnik MVT paths 2017-10-09 15:42:24 +02:00
David M
6af83d7630 Use yarn instead of npm in docker 2017-10-09 14:54:03 +02:00
David M
28501f6b9d MVT PostGIS limit test fixed 2017-10-09 14:46:01 +02:00
Simon Martín
e3405ea2fc doing changes after merge with middlewarify 2017-10-09 12:27:58 +02:00
Simon Martín
5c0f597cbb Merge branch 'middlewarify' into res-locals 2017-10-09 10:55:43 +02:00
Daniel García Aubert
7289394f6a Missing EOL 2017-10-07 19:16:15 +02:00
Daniel García Aubert
1ba1c488fa Do not decorate response methods to set header and send stats 2017-10-07 19:02:26 +02:00
David Manzanares
10f9f61e1e Merge branch 'mvt-path-conf' of github.com:CartoDB/Windshaft-cartodb into mvt-path-conf 2017-10-06 18:24:48 +02:00
David Manzanares
0e20958220 Remove dockerfile 2017-10-06 18:24:29 +02:00
David Manzanares
28cb05e45b Use windshaft docker image 2017-10-06 18:24:11 +02:00
Raul Ochoa
c004e105ef Drain client on after hooks 2017-10-06 16:17:26 +00:00
Raul Ochoa
f456237aa7 Drain client on after hook 2017-10-06 15:53:47 +00:00
David Manzanares
7f66189164 Fix travis.yaml 2017-10-06 16:44:28 +02:00
David Manzanares
cac16f8b66 Travis dockerified 2017-10-06 16:30:35 +02:00
David Manzanares
a706fd81ba Restore MVT path configuration after each suite pass 2017-10-06 16:21:34 +02:00
David Manzanares
43885f130b Fix HTTP status code distinction between 200 and 204 2017-10-06 16:19:00 +02:00
David Manzanares
58be2b8fc5 Merge remote-tracking branch 'origin/improve-test-client' into mvt-path-conf 2017-10-06 16:08:13 +02:00
David Manzanares
78671aa499 remove redundant format support 2017-10-06 16:07:47 +02:00
David Manzanares
d29da0bcc3 Test both MVT paths: mapnik and PostGIS 2017-10-06 16:07:24 +02:00
Raul Ochoa
e9d0b3b77d Merge pull request #769 from CartoDB/improve-test-client
Improve test client
2017-10-06 16:03:30 +02:00
David Manzanares
4e6253b717 Return HTTP 204 for empty tiles 2017-10-06 16:02:16 +02:00
David M
44eb323764 Merge pull request #768 from CartoDB/test-support-fixes
Test support fixes
2017-10-06 15:57:25 +02:00
Raul Ochoa
d4015085c7 Include test/support as part of jshint validation 2017-10-06 15:28:01 +02:00
Raul Ochoa
b9c511ee60 Remove unused file 2017-10-06 15:27:48 +02:00
Raul Ochoa
64fe070ab2 Put layergroupId handling close 2017-10-06 15:27:03 +02:00
Raul Ochoa
5d750f3b98 Several jshint fixes 2017-10-06 15:24:58 +02:00
Raul Ochoa
664892bba9 Complexity already fixed 2017-10-06 15:15:43 +02:00
Raul Ochoa
38c50e0bec Fix jshint hint 2017-10-06 15:15:33 +02:00
Raul Ochoa
6c0e6210d6 Split response validation 2017-10-06 15:15:16 +02:00
Raul Ochoa
f350206990 Strict check 2017-10-06 12:54:37 +00:00
Raul Ochoa
c8d2c9ea37 Do NOT throw error when not being in step context 2017-10-06 12:41:50 +00:00
David Manzanares
cab2d6d5d4 package.json docker-bash script added 2017-10-06 11:03:08 +02:00
Daniel García Aubert
242e63716f Merge branch 'middlewarify' into stats-middleware 2017-10-05 18:21:02 +02:00
Daniel García Aubert
c70b8cb5bf Set X-Served-By-DB-Host header in db-conn-setup middleware 2017-10-05 18:05:46 +02:00
Simon Martín
06138a82a8 Merge branch 'middlewarify' into res-locals 2017-10-05 17:53:15 +02:00
Daniel García Aubert
678fbb1c8f Remove bad argument to middleware callback 2017-10-05 17:28:41 +02:00
Simon Martín
2f310a15bd do not overwrite creation of res.locals 2017-10-05 17:23:07 +02:00
Daniel García Aubert
bf637ccd5b Implement stats middleware removing some duplicated code 2017-10-05 17:06:42 +02:00
David Manzanares
f387f2ee6f Testing dockerified 2017-10-05 16:08:31 +02:00
David Manzanares
34d9e5a4eb Fix MVT test 2017-10-05 16:08:05 +02:00
David Manzanares
54b7ee85c2 Geojson tests adapted to MVT 2017-10-05 14:38:43 +02:00
Simon Martín
9083fc2e20 fix forgotten comment 2017-10-05 12:44:03 +02:00
David Manzanares
72a9a3e097 updated deps 2017-10-05 12:26:19 +02:00
David Manzanares
102228c55b Merge remote-tracking branch 'origin/master' into mvt-path-conf 2017-10-05 12:19:24 +02:00
David Manzanares
148e6e6ae5 Merge branch 'master' into mvt-path-conf 2017-10-05 12:18:08 +02:00
David Manzanares
226653207a target windshaft master branch 2017-10-05 12:17:55 +02:00
Daniel García Aubert
b93c09959c Back to use just one router 2017-10-05 12:12:21 +02:00
Simon Martín
5abe25c316 undo style/format changes 2017-10-05 11:35:49 +02:00
Simon Martín
1f03a6b181 using res.locals instead of params in AuthApi 2017-10-05 11:28:41 +02:00
Simon Martín
16e8202782 stubs next version 2017-10-04 17:11:17 +02:00
Simon Martín
4afa7f70d7 release 4.0.0 2017-10-04 17:06:58 +02:00
Simon Martín
5045f81fe3 Merge pull request #764 from CartoDB/express-v4.15.5
upgrade node modules to enhance security
2017-10-04 15:54:41 +02:00
Simon Martín
ec8fcc7302 change param name and comments updated 2017-10-04 12:50:27 +02:00
Raul Ochoa
2afb6b5ac2 Regenerate yarn.lock to pickup grainstore version 2017-10-04 10:32:36 +00:00
Simon Martín
19e2515a8e Merge pull request #749 from CartoDB/dataview-factory-refactor
Dataview factory refactor
2017-10-04 12:18:55 +02:00
Raul Ochoa
5d6156a257 Add missing upgraded dep 2017-10-04 10:16:26 +00:00
Raul Ochoa
9e217d9199 Merge branch 'master' into express-v4.15.5 2017-10-04 10:16:07 +00:00
Raul Ochoa
a224a0bf91 Unify all pending changes in 4.0.0 version 2017-10-04 10:14:09 +00:00
Raul Ochoa
87bcb7ebf2 Update list of upgraded deps 2017-10-04 10:12:54 +00:00
Raul Ochoa
d06ba8b1f8 Merge remote-tracking branch 'origin/master' into express-v4.15.5 2017-10-04 10:11:08 +00:00
Simon Martín
2b37a406bc Merge pull request #763 from CartoDB/remove-list-dataview
Removes `list` dataview type
2017-10-04 12:01:42 +02:00
Simon Martín
0a507d02bc Merge branch 'master' into remove-list-dataview 2017-10-04 11:36:45 +02:00
Raul Ochoa
49fd75f0b6 Merge remote-tracking branch 'origin/master' into express-v4.15.5 2017-10-04 09:32:46 +00:00
Simon Martín
514aa53152 Merge pull request #748 from CartoDB/base-dataview-refactor
Base dataview refactor
2017-10-04 11:17:38 +02:00
Simon Martín
8fe31c45f3 fix 'this' scope with arrow function 2017-10-04 11:10:17 +02:00
Simon Martín
fe4c22d2ea Merge branch 'master' into base-dataview-refactor 2017-10-04 11:08:43 +02:00
Simon Martín
d27cce915c Merge pull request #747 from CartoDB/formula-dataview-refactor
Formula dataview refactor
2017-10-04 10:10:57 +02:00
Simon Martín
1c3f2b93e3 prepareRequest and prepareResponse in prepare-context.test 2017-10-03 17:58:16 +02:00
Simon Martín
21720267cf from req.context to res.locals 2017-10-03 17:47:57 +02:00
Raul Ochoa
da832263a4 Upgrade turbo-carto, camshaft, and cartodb-psql 2017-10-03 11:46:03 +00:00
Raul Ochoa
69bd14793f Upgrade to windshaft 3.3.3
Regenerate yarn.lock file
2017-10-03 11:21:47 +00:00
Raul Ochoa
54dd15c0b0 Merge remote-tracking branch 'origin/master' into express-v4.15.5 2017-10-03 11:15:31 +00:00
Simon Martín
3ce10690d6 send res.locals instead of res when possible 2017-10-03 13:06:12 +02:00
Simon Martín
6bfc5d8891 fix function name and removing comments of localsMiddleware 2017-10-03 13:03:02 +02:00
Simon Martín
430e1513d8 fix incorrect function parameter 2017-10-03 13:00:52 +02:00
Rafa de la Torre
28c8632532 Merge pull request #766 from CartoDB/update-yarn-v0.27.5
Update yarn version to v0.27.5
2017-10-03 12:54:46 +02:00
Rafa de la Torre
89172f280f Change yarn dep version range specification
In theory the caret `^` should work but as rochoa pointed out it does
not. So changing it (also for the sake of clarity).
2017-10-03 12:43:09 +02:00
Rafa de la Torre
0a7506e4b2 Update yarn version to v0.27.5 2017-10-03 12:10:43 +02:00
Raul Ochoa
2b1f12e9d5 Allow to instantiate maps with analyses filters
This decouples filters from dataviews. They are more verbose now.

Misses validation of filters.
2017-10-02 19:16:44 +02:00
Simon Martín
4fd3c99531 Merge pull request #746 from CartoDB/list-dataview-refactor
List dataview refactor
2017-10-02 17:28:54 +02:00
Simon Martín
1e4c63a6dc Merge pull request #745 from CartoDB/aggregation-dataview-refactor
Aggregation dataview refactor
2017-10-02 17:24:12 +02:00
Simon Martín
742420b159 Merge pull request #744 from CartoDB/time-series-refactor
Histogram refactor
2017-10-02 17:18:21 +02:00
Rafa de la Torre
b8783a6447 Stub NEWS/package for next version 2017-10-02 14:49:01 +02:00
Rafa de la Torre
45dece65f2 Update 3.13.0 release date in NEWS.md 2017-10-02 14:45:43 +02:00
Simon Martín
c894414192 going green 2017-10-02 12:28:29 +02:00
Simon Martín
aa62529041 updating preprare-context test to allow the new res.locals usage 2017-10-02 12:09:19 +02:00
Simon Martín
55f593eae6 adding forgotten semicolon 2017-10-02 12:08:10 +02:00
Simon Martín
f9d87bc40f res.locals fixing controllers 2017-10-02 12:07:35 +02:00
Eneko Lakasta
818cdbd99b upgrade express again (4.16.0) 2017-09-29 17:41:57 +02:00
Simon Martín
783eb0eec7 res.locals format and layer in namep maps 2017-09-29 17:03:57 +02:00
Simon Martín
c22a35489d res.locals forgotten things and make jshint happy 2017-09-29 14:38:28 +02:00
Simon Martín
482feabce2 res.locals in named maps controller 2017-09-29 14:37:55 +02:00
Simon Martín
0a753400e0 res.locals in map controller 2017-09-29 12:54:21 +02:00
Simon Martín
a21648ab4a res.locals in layergroup controller 2017-09-29 12:32:46 +02:00
Simon Martín
b4d03c074a not move db params to res.locals.db 2017-09-29 11:07:11 +02:00
Eneko Lakasta
75c8a73423 upgrade debug to version 3.1 2017-09-29 10:42:20 +02:00
Eneko Lakasta
649383a3df upgrade request module to version 2.83.0 2017-09-28 18:28:04 +02:00
Eneko Lakasta
52402c0333 Revert "upgrade request module"
This reverts commit cacb92b0c4.
2017-09-28 18:25:40 +02:00
Eneko Lakasta
cacb92b0c4 upgrade request module 2017-09-28 18:24:50 +02:00
David Manzanares
5463248578 Changed PostGIS MVT flag name 2017-09-28 17:57:59 +02:00
Eneko Lakasta
aaf95b9223 upgrade body-parser (package.json and yarn.lock) 2017-09-28 17:41:49 +02:00
Eneko Lakasta
6149df1810 upgrade body-parser to version 1.17.2
version 1.17.2 requires a patched query string (qs) module
2017-09-28 17:37:43 +02:00
Eneko Lakasta
ff47027a51 upgrade express version to 4.15.5 2017-09-28 17:20:07 +02:00
David Manzanares
e5cae8b8e3 Added flag documentation 2017-09-28 13:24:50 +02:00
Raul Ochoa
78b75c7a88 Removes list dataview type 2017-09-28 10:47:44 +00:00
Javier Goizueta
56d7c2c140 Merge pull request #760 from CartoDB/upgrade-camshaft-to-0.59.1
Upgrade camshsft to 0.59.1
2017-09-28 12:09:46 +02:00
David Manzanares
ad1abb28af Add configuration flag to enable/disable direct PgSQL MVT 2017-09-28 12:08:22 +02:00
Simon Martín
f824fc5243 base and analyses controller 2017-09-28 12:02:34 +02:00
Simon Martín
4a2cc6a5f8 res.locals in auth_api 2017-09-28 11:55:36 +02:00
Simon
ca612dd02a res.locals in context middlewares 2017-09-28 11:43:12 +02:00
Unknown
fedcb0d0f9 remove unused middleware 2017-09-28 11:23:53 +02:00
Javier Goizueta
c9f0902703 Upgrade camshsft to 0.59.1
This fixes duplicate column names in aggregate-intersection analysis
2017-09-27 18:44:06 +02:00
Simon Martín
1739cee11d Merge pull request #752 from CartoDB/layergroup-token-param
Layergroup token param
2017-09-27 16:44:58 +02:00
Simon
178b9e8563 moving layergroup-token middleware to middlewarify style 2017-09-27 16:32:49 +02:00
Simon
ac474cb253 Merge branch 'middlewarify' into layergroup-token-param 2017-09-27 16:31:08 +02:00
Simon Martín
79510185da Merge pull request #753 from CartoDB/layergroup-token-middleware
Layergroup Token parsing as middleware
2017-09-27 11:44:15 +02:00
Raul Ochoa
c960535709 Merge pull request #756 from CartoDB/upgrade-deps
Upgrades camshaft, cartodb-query-tables, and turbo-carto
2017-09-26 18:32:31 +02:00
Simon
84cd93b1b0 make jshint happy 2017-09-26 18:25:47 +02:00
Simon
134cc9ac0c changing req.locals to res.locals 2017-09-26 18:23:49 +02:00
Daniel García Aubert
615229fc31 Remove comment 2017-09-26 17:32:50 +02:00
Daniel García Aubert
4600005a86 Bring ported test back 2017-09-26 17:31:57 +02:00
Simon
383c8305cc Merge branch 'middlewarify' of github.com:CartoDB/Windshaft-cartodb into middlewarify 2017-09-26 15:40:00 +02:00
Simon
b94dfe066d removing some repeated things 2017-09-26 15:39:48 +02:00
Daniel García Aubert
de267917f4 Merge branch 'middlewarify' of github.com:CartoDB/Windshaft-cartodb into middlewarify 2017-09-26 15:17:20 +02:00
Simon
3f6afb4530 validation middleware for layer route (conflicting route) 2017-09-26 14:56:20 +02:00
Simon
540fda1e6c Merge branch 'master' into middlewarify 2017-09-26 14:53:57 +02:00
Daniel García Aubert
e0e67df91c Merge branch 'master' into middlewarify 2017-09-25 20:04:33 +02:00
Daniel García Aubert
4899c7ffef Inject prepare context middleware to controllers 2017-09-25 19:40:27 +02:00
Raul Ochoa
ac42223439 Allow 6.9.0 version 2017-09-25 15:34:16 +00:00
Raul Ochoa
1110abaa9a Merge pull request #644 from CartoDB/check-node-version
Check node version
2017-09-25 17:33:26 +02:00
Raul Ochoa
3023111896 Reuse existing log methods 2017-09-25 14:55:17 +00:00
Raul Ochoa
66380197f4 Do not explode by - token 2017-09-25 14:53:17 +00:00
Raul Ochoa
8daa4bb08a Merge remote-tracking branch 'origin/master' into check-node-version 2017-09-25 14:51:50 +00:00
Raul Ochoa
b943b09532 Merge pull request #736 from CartoDB/static-named-maps-override-bbox
Static named maps override bbox
2017-09-25 14:10:27 +02:00
Raul Ochoa
eda18726fd Merge pull request #755 from CartoDB/analyses-use-cases-tests
Analyses use cases tests
2017-09-25 14:06:44 +02:00
Daniel García Aubert
f0920aedef Remove unused module 2017-09-25 13:43:15 +02:00
Daniel García Aubert
b236112069 Split prepare context middleware and fix unit test 2017-09-25 13:40:22 +02:00
Raul Ochoa
d3dafc8a40 Regenerate lock file 2017-09-25 11:24:33 +00:00
Raul Ochoa
c734f43643 Upgrades camshaft, cartodb-query-tables, and turbo-carto
Better support for query variables.
2017-09-25 11:11:05 +00:00
Daniel García Aubert
0e8fb68794 Extract token param to a middleware 2017-09-22 18:49:21 +02:00
Daniel García Aubert
f7b9287c93 Return an array of middlewares instead of big one in prepare context 2017-09-22 18:24:16 +02:00
Daniel García Aubert
85d4c81e58 Remove legacy hack 2017-09-22 18:15:48 +02:00
Daniel García Aubert
ff19a8a2fe Rename test 2017-09-22 17:59:51 +02:00
Daniel García Aubert
3bab081438 Rename req2params by prepareContext 2017-09-22 17:56:47 +02:00
Daniel García Aubert
6dc9cc0b23 Remove req2params dependency 2017-09-22 17:56:08 +02:00
Raul Ochoa
3134f40eac Remove advanced use cases that no longer make sense 2017-09-22 15:42:52 +00:00
Raul Ochoa
5cc31cabe2 Fix and enable some old tests related to analyses use cases 2017-09-22 15:41:58 +00:00
Daniel García Aubert
8fd35849c7 Merge branch 'middlewarify' of github.com:CartoDB/Windshaft-cartodb into middlewarify 2017-09-22 17:15:41 +02:00
Simon
c09899913f finishing integration of lzma middleware 2017-09-22 16:46:39 +02:00
Daniel García Aubert
0bdeee64a7 Use express router to group controllers' enpoints and reuse common middleware for named maps admin controller 2017-09-22 16:45:34 +02:00
Daniel García Aubert
ee8619c470 Use express router to group controllers' enpoints and reuse common middleware for analysis controller 2017-09-22 16:28:52 +02:00
Simon
9d81321d78 Merge branch 'master' into middlewarify 2017-09-22 16:26:56 +02:00
Simon Martín
ca63c2ef1a Merge pull request #721 from CartoDB/lzma-middleware
Implement LZMA query param inflating as middleware
2017-09-22 16:16:36 +02:00
Daniel García Aubert
b0486f9bae Use express router to group enpoints and reuse common middlewares for layergroup controller 2017-09-22 15:44:12 +02:00
Raul Ochoa
2eb1c0f3e0 Remove unused import 2017-09-22 12:59:14 +00:00
Raul Ochoa
22b7828725 Layergroup Token parsing as middleware
Reuses LayergroupToken model from tests.
2017-09-22 12:05:40 +00:00
Raul Ochoa
78404b1308 Merge remote-tracking branch 'origin/master' into layergroup-token-param 2017-09-22 11:12:42 +00:00
Raul Ochoa
45698207d9 Merge remote-tracking branch 'origin/master' into static-named-maps-override-bbox 2017-09-22 11:08:12 +00:00
Daniel García Aubert
9bd862ffaf Remove req2params from BaseController and update related test to use the middleware 2017-09-22 01:08:46 +02:00
Daniel García Aubert
8139cdf8b2 Use req2params middleware for name maps static views endpoint 2017-09-22 00:58:44 +02:00
Daniel García Aubert
a8898a8022 Use req2params middleware for name maps tile endpoint 2017-09-22 00:48:44 +02:00
Daniel García Aubert
df5ec0f4d9 Use req2params middleware for analysis catalog endpoint 2017-09-22 00:42:17 +02:00
Daniel García Aubert
51ba3db4ac Use req2params middleware for instantiate named map endpoint 2017-09-22 00:31:16 +02:00
Daniel García Aubert
d31e52a625 Fix format, break line in bad position 2017-09-21 22:55:30 +02:00
Daniel García Aubert
3a8b99a14e Use req2params middleware for tile and layer endpoint 2017-09-21 22:53:31 +02:00
Daniel García Aubert
fac1ab4a1c Use req2params middleware for attributes endpoint 2017-09-21 22:47:08 +02:00
Daniel García Aubert
a9b0acc317 Use req2params middleware for static map (bbox & center) endpoint 2017-09-21 22:43:59 +02:00
Daniel García Aubert
5cb2e5d3c5 Skip temporaly ported test 2017-09-21 21:53:05 +02:00
Daniel García Aubert
e2ed0058d8 Use req2params middleware for layergroup create endpoint 2017-09-21 21:52:34 +02:00
Daniel García Aubert
2f499a148a Use req2params middleware for dataview endpoint 2017-09-21 14:33:32 +02:00
Daniel García Aubert
49204650c6 Use req2params middleware for datavie search endpoint 2017-09-21 14:30:19 +02:00
Daniel García Aubert
234576ab5f Use req2params middleware for analisys node status endpoint 2017-09-21 13:37:32 +02:00
Daniel García Aubert
02cd6a43ad Move req2params method to a its own middleware 2017-09-21 13:27:22 +02:00
Daniel García Aubert
429f070372 Pass node's response object to req2params 2017-09-21 12:22:33 +02:00
Daniel García Aubert
3b9c561cee Change signature of req2params to follow express' middleware pattern 2017-09-21 11:54:37 +02:00
Daniel García Aubert
daeae5d95c Implement error-middleware to handle errors at top level 2017-09-21 11:46:42 +02:00
Raul Ochoa
33121871b0 Stubs next version 2017-09-18 12:47:37 +00:00
Raul Ochoa
f133d983e8 Release 3.12.10 2017-09-18 12:46:40 +00:00
Raul Ochoa
b6237c7bfa Merge pull request #750 from CartoDB/upgrade-windshaft
Upgrades windshaft to 3.3.2
2017-09-18 13:17:12 +02:00
Raul Ochoa
a5d9bfa0ec Upgrades windshaft to 3.3.2 2017-09-18 10:34:04 +00:00
Simon
222cfb90fd Removing 'self' vars using arrow functions 2017-09-18 12:20:59 +02:00
Simon
f63fab40ed Removing 'self' vars using arrow functions 2017-09-18 11:34:18 +02:00
Daniel García Aubert
61ea05d1c2 Do not assign a value by default for special float values counters 2017-09-15 14:51:02 +02:00
Daniel García Aubert
64c3e68303 Fix double declaration of 'result' 2017-09-15 14:48:54 +02:00
Daniel García Aubert
d4bb4edd1d Applyy extract method to check input options 2017-09-15 14:43:41 +02:00
Daniel García Aubert
419b29e609 Do not prefix with '_' template context 2017-09-15 14:43:26 +02:00
Daniel García Aubert
c7ed3d34e8 Use const instead of var to declare variables 2017-09-15 14:43:13 +02:00
Daniel García Aubert
1959a841fd Use arrow function to take advantage of bound context 2017-09-15 14:26:22 +02:00
Daniel García Aubert
ef5049f28f Use destructuring assignment to improve readability 2017-09-15 14:07:46 +02:00
Daniel García Aubert
d5d9044686 Use const keyword to declare variables 2017-09-15 14:05:23 +02:00
Daniel García Aubert
5d632d936e Use ES6 class syntax 2017-09-15 14:04:09 +02:00
Daniel García Aubert
90c4796d4e Remove empty line 2017-09-15 13:41:54 +02:00
Daniel García Aubert
ada58f6ea2 Use const keyword to declare varibles 2017-09-15 13:35:00 +02:00
Daniel García Aubert
b4ce13e429 Use object shorthand notation 2017-09-15 11:56:59 +02:00
Daniel García Aubert
11f7b38c69 Do not use dot module to build column type query 2017-09-15 11:54:56 +02:00
Daniel García Aubert
9771979b8f Missing call to super class in constructor 2017-09-15 10:59:07 +02:00
Daniel García Aubert
c00a93f414 Use destruturing assignment to format the formula result 2017-09-15 10:58:11 +02:00
Daniel García Aubert
ecbc7a28e7 Declare constants with const keyword 2017-09-15 10:49:20 +02:00
Daniel García Aubert
68dfed8b85 Use ES6 class syntax 2017-09-15 10:48:44 +02:00
Daniel García Aubert
2437288d9d Replace widget word by dataview 2017-09-15 10:37:51 +02:00
Daniel García Aubert
9c64d674b3 Do not use underscore 2017-09-14 18:02:13 +02:00
Daniel García Aubert
a4ecc18f2f Use default values for constructor's arguments 2017-09-14 17:57:24 +02:00
Daniel García Aubert
1063d81c1b Rename debug namespace 2017-09-14 17:56:40 +02:00
Daniel García Aubert
dcb9b8ec52 Rename BaseWidget by BaseDataview 2017-09-14 17:56:17 +02:00
Daniel García Aubert
dbb23bf9f0 Remove jshint's complaints 2017-09-14 17:24:13 +02:00
Daniel García Aubert
2a0b15f085 Remove prefix '_' while passing context param to templates 2017-09-14 17:22:34 +02:00
Daniel García Aubert
d0e2c9f898 Use debug module to print sql for debugging purposes 2017-09-14 17:21:21 +02:00
Daniel García Aubert
d328b534a5 Replace widget word by dataview 2017-09-14 17:19:16 +02:00
Daniel García Aubert
050e9776d1 Use const for requirements, constants and variables that are initialized once 2017-09-14 17:18:20 +02:00
Daniel García Aubert
c8ff61c531 Use ES6 class syntax 2017-09-14 17:15:43 +02:00
Daniel García Aubert
cdc56e703c Rename BaseWidget by BaseDataview 2017-09-14 17:10:51 +02:00
Daniel García Aubert
9a4794ee10 Remove dot requirement and use template strings to build list aggregation query 2017-09-14 17:09:55 +02:00
Daniel García Aubert
51907b9545 Apply extract method to condition 2017-09-14 16:56:55 +02:00
Daniel García Aubert
1f3b0beddf Fix missing parameter 2017-09-14 16:47:05 +02:00
Daniel García Aubert
38e2c040d1 Use template string to escape literals 2017-09-14 16:45:45 +02:00
Daniel García Aubert
46860541fe Apply extract method to validate input options 2017-09-14 16:42:25 +02:00
Daniel García Aubert
c2e99219ef Use ES6 goodies to refactor format method 2017-09-14 16:30:46 +02:00
Daniel García Aubert
cc2cf78264 Rename all 'widget' ocurrences by 'dataview' 2017-09-14 16:12:39 +02:00
Daniel García Aubert
746292610a Rename debug namespace 2017-09-14 16:10:23 +02:00
Daniel García Aubert
b05083bcfc Move search's templates along aggregation's templates 2017-09-14 16:09:44 +02:00
Daniel García Aubert
cd13107a4d Use ES6 let & const to declare variables 2017-09-14 16:08:12 +02:00
Daniel García Aubert
46254eaf74 rename BaseWidget by BaseDataview 2017-09-14 15:59:15 +02:00
Daniel García Aubert
086eff01a9 Use ES6 class syntax 2017-09-14 15:57:18 +02:00
Daniel García Aubert
02949003a9 Build search query in two steps 2017-09-14 15:48:54 +02:00
Daniel García Aubert
0a894da0df Remove prefix to context's properties 2017-09-14 15:27:57 +02:00
Daniel García Aubert
e2ab48bee2 Remove prefix to context's properties 2017-09-14 15:22:58 +02:00
Daniel García Aubert
132fce84c5 Remove string template in the middle 2017-09-14 15:07:30 +02:00
Daniel García Aubert
b1508af007 Remove prefix to context's props in order to share it throught all templates avoiding to duplicate passing custom params again and again 2017-09-14 13:14:12 +02:00
Daniel García Aubert
65dca454f4 Move aggragation query to its own query template 2017-09-14 12:18:03 +02:00
Daniel García Aubert
3682740f08 Build aggregation query with string templates avoiding to join all inner templates 2017-09-14 12:01:41 +02:00
Daniel García Aubert
a434015d5b Move categories CTE template out of aggregation class 2017-09-14 11:27:03 +02:00
Daniel García Aubert
2f4f719f55 Use object properties in class method instead of passing them as parameters 2017-09-13 19:42:25 +02:00
Daniel García Aubert
75645e2d7a Use string templates to build categories CTE sql 2017-09-13 19:34:09 +02:00
Daniel García Aubert
4d1a53c20f Use string templates to build error message 2017-09-13 19:27:25 +02:00
Daniel García Aubert
ee471184b9 Use default values for input params 2017-09-13 19:19:25 +02:00
Daniel García Aubert
4518b7cb6e Declare requirement with const keyword 2017-09-13 19:17:16 +02:00
Daniel García Aubert
306df5be5a Replace underscore's function by ES6 equivalents 2017-09-13 19:16:08 +02:00
Daniel García Aubert
33e8657e35 Declare constants with const keyword 2017-09-13 18:40:09 +02:00
Daniel García Aubert
6fd3388fa2 Replace dot templates by ES6 string templates 2017-09-13 18:38:54 +02:00
Daniel García Aubert
4a89ad57d7 Remove '_' as prefix for template's context properties 2017-09-12 13:05:46 +02:00
Daniel García Aubert
c0cfdad7d1 Use hasOwnProperty method to check if histogram is a time-series 2017-09-12 10:38:53 +02:00
Daniel García Aubert
8f797c3c41 Fix EOF 2017-09-12 10:16:16 +02:00
Daniel García Aubert
2576c3e7d5 Rename _shouldOverrideRange & _shouldOverrideBins methods 2017-09-12 10:14:55 +02:00
Daniel García Aubert
3a936474cf Fix bad merge with master 2017-09-11 19:48:46 +02:00
Daniel García Aubert
a98f5bf08b Merge branch 'master' into time-series-refactor 2017-09-11 19:38:21 +02:00
Daniel García Aubert
03babcb43b Simplify condition and remove unused method 2017-09-11 19:31:38 +02:00
Daniel García Aubert
9aa5a9e850 Improve comment 2017-09-11 19:26:28 +02:00
Daniel García Aubert
e3bffcd39d Use inline functions to filter desired fields of the row 2017-09-11 19:24:01 +02:00
Daniel García Aubert
5fc2b46d56 Fix bad condition 2017-09-11 19:14:11 +02:00
Daniel García Aubert
7c69240748 Use parameter default value 2017-09-11 19:10:06 +02:00
Daniel García Aubert
ee43378c68 Use arrow function 2017-09-11 19:09:05 +02:00
Daniel García Aubert
09981c2560 Extract method to check valid aggregation 2017-09-11 18:57:16 +02:00
Daniel García Aubert
fd9534797c Minor refactors 2017-09-11 18:44:14 +02:00
Daniel García Aubert
38e7e71328 Implement template method pattern to format histogram query output 2017-09-11 17:19:02 +02:00
Daniel García Aubert
271932a80d Extract condition to a method 2017-09-11 17:17:42 +02:00
Daniel García Aubert
4f33e0d794 Rename Histogram.dataview by Histogram.histogramImplementation 2017-09-11 15:34:42 +02:00
Daniel García Aubert
ec23bfc79b Rename HistogramBase by BaseHistogram 2017-09-11 13:54:46 +02:00
Daniel García Aubert
6c3fa045cd Rename HistogramBase by BaseHistogram 2017-09-11 13:53:05 +02:00
Simon
d75ee965ae changing some 'var' to 'let/const' 2017-09-11 11:48:33 +02:00
Simon
5e9b2e45c7 creating HistogramBase with the common functions of NumericHistogram and DateHistogram 2017-09-11 11:32:20 +02:00
Simon
e4a20fa954 adding forgotten return 2017-09-08 17:57:35 +02:00
Simon
a20900210d removing unneeded _isDateHistogram function 2017-09-08 17:54:25 +02:00
Simon
2650c3b3e6 removing self=this assignment 2017-09-08 17:51:02 +02:00
Simon
25ef2610aa Varible declarations to let/const 2017-09-08 17:43:10 +02:00
Simon
92f6f59e07 Fix jshint style errors 2017-09-08 16:13:23 +02:00
Simon
5e07cc2ad1 Remove unneeded condittion 2017-09-08 16:06:54 +02:00
Simon
5593d92c4b Do not choose histogram implementation until getResult() 2017-09-08 15:55:23 +02:00
Simon
29f32cb9cc Expose dataview's methods to bypass concrete overview's implementations 2017-09-08 15:53:00 +02:00
Simon
1d4935cc9a Fix undefined while destrutcuring assignment 2017-09-08 15:50:01 +02:00
Simon
f75b4312a1 Fix undefined while destructuring assignment 2017-09-08 15:49:25 +02:00
Simon
23dd143fa5 Make Histogram class as context of state pattern 2017-09-08 12:48:08 +02:00
Simon
7d42afcdb4 remove unnecessary properties of NumericHistogram 2017-09-08 12:22:24 +02:00
Simon
78b95d05d0 make private functions 2017-09-08 12:21:22 +02:00
Simon
fb753e50a2 remove getOffset function 2017-09-08 12:19:05 +02:00
Simon
c863cdd9f6 remove getAggregation function 2017-09-08 12:17:29 +02:00
Simon
a4ebce52db remove unnecessary properties in format function 2017-09-08 12:16:56 +02:00
Simon
4a00a2d673 rename buildQueryTpl by _buildQueryTpl 2017-09-08 12:15:21 +02:00
Simon
38f0e23efe rename buildNumericHistogramQueryTpl by buildQueryTpl 2017-09-08 12:10:31 +02:00
Simon
7f14785091 fix namespace for debugging 2017-09-08 12:07:49 +02:00
Simon
db969a51ad make public some private functions 2017-09-08 12:07:07 +02:00
Simon
3441ad6aa9 rename _buildDateHistogramQueryTpl by _buildQueryTpl 2017-09-08 12:04:25 +02:00
Simon
347dea8f66 naming private functions 2017-09-08 12:01:15 +02:00
Simon
a3112aa929 fix function name in recursion 2017-09-08 11:55:28 +02:00
Simon
157946cc42 Rename DateHistogram class 2017-09-08 11:54:30 +02:00
Simon
8ce25d958c fix namespace for debugging 2017-09-08 11:53:47 +02:00
Daniel García Aubert
7e099be134 Add specific implementations of histograms based on column type; still not used 2017-09-08 10:29:54 +02:00
Javier Torres
6b2e2b2241 Stub next release (package.json) 2017-09-07 16:00:27 +02:00
Javier Torres
855e5c9e4c Stub next release 2017-09-07 15:59:32 +02:00
Javier Torres
a24792f46d Release 3.12.9 2017-09-07 15:57:11 +02:00
Javier Torres
0eb57f6801 Merge pull request #743 from CartoDB/718-quantiles_turbo_carto
Do not use distinct when calculating quantiles
2017-09-07 15:55:47 +02:00
Javier Torres
f1246cb060 Bump to 3.12.9 2017-09-07 15:54:43 +02:00
Javier Torres
7dd5c5b15d Do not use distinct when calculating quantiles 2017-09-07 14:39:25 +02:00
Ivan Malagon
806c13beac Release 3.12.8 2017-09-07 10:51:23 +02:00
Ivan Malagon
69f110e037 Merge pull request #742 from CartoDB/fix-histogram-out-of-range
Fix out of range bug in date histograms
2017-09-07 10:49:49 +02:00
Ivan Malagon
d77075295e Update version and NEWS 2017-09-07 10:48:21 +02:00
Daniel García Aubert
63a7ee08d0 Avoid nested ternaries for date histograms 2017-09-06 19:15:39 +02:00
Daniel García Aubert
b63a67a5b8 Avoid nested ternaries 2017-09-06 18:33:51 +02:00
Daniel García Aubert
1ac8455dc2 Use template strings to build histogram query 2017-09-06 18:13:34 +02:00
Daniel García Aubert
9f52e58be8 Rename BaseWidget by BaseDataview 2017-09-06 16:54:08 +02:00
Daniel García Aubert
4edf18f77a Remove underscore requirement 2017-09-06 16:52:17 +02:00
Daniel García Aubert
b5d2de8edc Do not use _.omit() 2017-09-06 16:49:00 +02:00
Ivan Malagon
bd8d147a7d Fix out of range bug in date histograms 2017-09-06 16:21:01 +02:00
Daniel García Aubert
8ac041805c Use typeof !== string instead of underscore's equivalent 2017-09-06 16:20:01 +02:00
Daniel García Aubert
6e0dc8666d Use .hasOwnProperty() instead of underscore's equivalent 2017-09-06 16:14:29 +02:00
Daniel García Aubert
3e55bd2abb Make happy to jshint 2017-09-06 15:56:52 +02:00
Daniel García Aubert
da1d0550f6 Use const keyword for constants 2017-09-06 15:52:13 +02:00
Daniel García Aubert
c37ef36a61 Move parseOffset function to a class method 2017-09-06 15:49:05 +02:00
Daniel García Aubert
9e3e1cad9a Move getWidth function to a class method 2017-09-06 15:47:21 +02:00
Daniel García Aubert
e84f30488f Move getBinsCount function to a class method 2017-09-06 15:45:51 +02:00
Daniel García Aubert
49a60caffc Move getBinEnd function to a class method 2017-09-06 15:42:29 +02:00
Daniel García Aubert
392e004879 Move getBinStart and populateBinStart function to a class method 2017-09-06 15:38:23 +02:00
Daniel García Aubert
288656301b Move getOffset function to a class method 2017-09-06 15:37:28 +02:00
Daniel García Aubert
96740b82ed Move getAggregation function to a class method 2017-09-06 13:52:02 +02:00
Daniel García Aubert
1be66e1552 Use const for requirements 2017-09-06 13:47:19 +02:00
Daniel García Aubert
5ba2dfbbd6 Use ES6 class syntax 2017-09-06 13:43:54 +02:00
Daniel García Aubert
af4b3d81cd make happy to jshint 2017-09-06 11:55:40 +02:00
Daniel García Aubert
bbd42b73f2 Remove dot requirement 2017-09-06 11:44:52 +02:00
Daniel García Aubert
8e2535745e Use template string for columnCastTpl 2017-09-06 11:44:25 +02:00
Daniel García Aubert
4f75f6c07b Use template string for dateBinsQueryTpl and dateHistogramQueryTpl 2017-09-06 11:41:24 +02:00
Daniel García Aubert
0ede3013db Use template string for dateOverrideBasicsQueryTpl 2017-09-06 10:57:38 +02:00
Daniel García Aubert
0b79ac76db Use template string for dateBasicsQueryTpl 2017-09-06 10:39:17 +02:00
Daniel García Aubert
2739364193 Use template string for histogramQueryTpl 2017-09-05 15:53:21 +02:00
Daniel García Aubert
adcff54589 Use template string for nansQueryTpl 2017-09-05 15:53:06 +02:00
Daniel García Aubert
734cfa6d83 Fix undefined argument 2017-09-05 15:51:31 +02:00
Daniel García Aubert
7ea6b3e371 Use template string for infinitiesQueryTpl 2017-09-05 12:21:30 +02:00
Daniel García Aubert
f1018f3272 Use template string for nullsQueryTpl 2017-09-05 12:18:42 +02:00
Daniel García Aubert
151bdec1fd Use template string for overrideBinsQueryTpl 2017-09-05 12:16:38 +02:00
Daniel García Aubert
5d413ac1f9 Use template string for overrideBasicsQueryTpl 2017-09-05 11:26:38 +02:00
Daniel García Aubert
37b1376767 Fix bad find & replace 2017-09-05 11:26:27 +02:00
Daniel García Aubert
00741bc0a4 Use template string for basicsQueryTpl 2017-09-05 10:41:48 +02:00
Daniel García Aubert
c580600590 Extract template to filter out special numeric values 2017-09-05 10:36:18 +02:00
Daniel García Aubert
6373fe8652 Use template string for filteredQueryTpl 2017-09-04 19:01:58 +02:00
Daniel García Aubert
5ce419d863 Use template string for dateIntervalQueryTpl 2017-09-04 18:42:30 +02:00
Mario de Frutos
7be5361433 Upgrade to camshaft 0.58.1 (#739) 2017-09-01 11:31:06 +02:00
Mario de Frutos
5332fd3baa Stubs next version 2017-08-31 10:21:00 +02:00
Mario de Frutos
77f1aa7e0c Update to camshaft 0.58.0 (#737) 2017-08-31 10:12:58 +02:00
Raul Ochoa
e1990fc2f9 Use the correct fixture image 2017-08-29 13:29:39 +00:00
Raul Ochoa
ca6eb609b2 Update news 2017-08-29 13:08:14 +00:00
Raul Ochoa
91ce3a5489 Going green: allow to use bbox param, along lon, lat, and zoom
The `bbox` param was removed from the base controller, the rest kept
working as they are declared in the base, but it's better to declare
them here as well.

Fixes #735.
2017-08-29 13:05:03 +00:00
Raul Ochoa
fc0dbaaab1 Going red: Bounding box parameter ignored in static named maps 2017-08-29 13:04:20 +00:00
Mario de Frutos
03dc260104 Stubs new version 2017-08-24 12:09:51 +02:00
Mario de Frutos
d644376f88 Update to camshaft 0.57.0 (#733)
* Update to camshaft 0.57.0

* Yarn.lock with update to camshaft 0.57.0
2017-08-24 12:07:43 +02:00
Mario de Frutos
ed0bfa5f63 Stubs next version 2017-08-23 16:53:01 +02:00
Mario de Frutos
ca0b927f51 Update camshaft to 0.56.0 (#731) 2017-08-23 16:34:42 +02:00
Simon
cd27d6aa02 Stub NEWS/package for next version 2017-08-22 16:37:23 +02:00
Simon
7aefca3f82 updating NEWS.md 2017-08-22 16:26:36 +02:00
Simon Martín
3d409274e0 Merge pull request #730 from CartoDB/upgrade-camshaft
Upgrade camshaft to 0.55.8

All working fine in local and staging
2017-08-22 16:13:06 +02:00
Raul Ochoa
4491fa2faf Upgrade camshaft to 0.55.8 2017-08-22 14:17:17 +02:00
Raul Ochoa
0ed46930dd Stubs next version 2017-08-16 15:37:29 +02:00
Raul Ochoa
f3b7a857f2 Release 3.12.2 2017-08-16 15:36:16 +02:00
Raul Ochoa
3a22adf966 Update news 2017-08-16 15:35:34 +02:00
Raul Ochoa
1c6a76af72 Merge pull request #726 from CartoDB/725-polygon-count
725 fix polygon count
2017-08-16 15:33:19 +02:00
Raul Ochoa
175d3ac317 Merge pull request #728 from CartoDB/fix-test-gauge-error
Restore statsClient after performing test
2017-08-16 15:32:14 +02:00
Simon
175d070f09 using const instead of let and var and adding assert.ifError 2017-08-16 10:07:27 +02:00
Raul Ochoa
339f1aafa9 Stubs next version 2017-08-13 17:56:23 +02:00
Raul Ochoa
fef0dc302a Release 3.12.1 2017-08-13 17:55:45 +02:00
Raul Ochoa
58fec46117 Merge pull request #729 from CartoDB/upgrade-deps
Upgrades cartodb-psql, windshaft, and camshaft
2017-08-13 17:54:53 +02:00
Raul Ochoa
7be74d6ce1 Upgrades cartodb-psql, windshaft, and camshaft 2017-08-13 17:40:06 +02:00
Daniel García Aubert
d0f5ebd7ab Restore statsClient after performing test 2017-08-11 17:55:55 +02:00
Simon
92d33bf7fd linter details for polygons count test 2017-08-10 18:20:15 +02:00
Daniel García Aubert
490adbce4b Stubs next version 2017-08-10 18:19:20 +02:00
Simon
fab7832dee added ascii art for polygons count test 2017-08-10 18:16:53 +02:00
Simon
e678957a8f move polygon count test to widgets regression testfile, and check the only returned polygon is the expected one 2017-08-10 18:09:18 +02:00
Daniel García Aubert
e1e22de65f Release 3.12.0 2017-08-10 17:54:09 +02:00
Daniel
43312922fc Merge pull request #717 from CartoDB/response-time-limit
[limits] Response time limits
2017-08-10 17:35:47 +02:00
Daniel García Aubert
01a22a45bb Move setUserDatabaseTimeoutLimit from class method to a instance method 2017-08-10 17:09:05 +02:00
Raul Ochoa
9524433437 Use instance server 2017-08-10 16:24:40 +02:00
Raul Ochoa
14d5ee4178 Remove user param 2017-08-10 16:22:45 +02:00
Raul Ochoa
e7c206762d String comparison and regex to match errors instead of indexOf 2017-08-10 16:09:26 +02:00
Raul Ochoa
69eaa72819 String comparison and regex to match errors instead of indexOf 2017-08-10 16:06:10 +02:00
Raul Ochoa
23edf78a67 Remove unnecessary step 2017-08-10 15:58:25 +02:00
Simon
44c5eb051d formatting the query of polygon count test 2017-08-10 11:05:36 +02:00
Simon
814b123b2b fix 725 using the ST_Intersects function instead of && 2017-08-09 18:55:14 +02:00
Simon
ff560ffde7 add test boundingBox-polygon-counter 2017-08-09 18:49:59 +02:00
Daniel García Aubert
14f85abd39 Merge branch 'master' into response-time-limit 2017-08-09 18:48:38 +02:00
Daniel García Aubert
ce97844f37 Upgrade windshaft version to 3.3.0 2017-08-09 18:42:44 +02:00
Daniel García Aubert
d27a281067 Upgrade cartodb-redis to 0.14.0 2017-08-09 17:55:39 +02:00
Daniel García Aubert
3611752677 Stubs next version 2017-08-08 11:25:18 +02:00
Daniel García Aubert
b24858a17c Release 3.11.0 2017-08-08 11:21:44 +02:00
Daniel
26a967d0a7 Merge pull request #723 from CartoDB/fix-time-series-issues
Fix time series issues
2017-08-08 10:51:13 +02:00
Daniel García Aubert
c643160671 Prefix date interval query (to calculate automatic aggregation) to avoid name collision 2017-08-08 10:32:53 +02:00
Daniel García Aubert
e7a0b246a3 Prefix with double underscore 2017-08-08 10:20:36 +02:00
Daniel García Aubert
3c061769c6 Prefix basics columns to avoid name collision 2017-08-08 10:11:35 +02:00
Daniel García Aubert
7e159c565b Prefix iqr query calculation to avoid name collision 2017-08-08 10:03:40 +02:00
Daniel García Aubert
ff3d7ed7b2 Fix jshint typo 2017-08-08 09:57:12 +02:00
Daniel García Aubert
cf71489a7f Prefix nans and infinities counters to avoid name collision 2017-08-08 09:54:49 +02:00
Daniel García Aubert
c7e5dbf158 Fix query aliases 2017-08-08 09:46:34 +02:00
Daniel García Aubert
34cf45bc9d Prefix infinities query to avoid name collision 2017-08-08 09:43:31 +02:00
Daniel García Aubert
7e058955ea Use final naming to group by 2017-08-08 09:39:51 +02:00
Daniel García Aubert
a9e3bc3cda Prefix filtered source to avoid name collision 2017-08-08 09:36:23 +02:00
Daniel García Aubert
3ee064a59f Prefix nulls query to avoid name collision 2017-08-08 09:32:13 +02:00
Daniel García Aubert
f3ababffc1 Prefix bins query to avoid name collision 2017-08-08 09:12:52 +02:00
Daniel García Aubert
0f8de9e74b Add prefix to basics query to avoid name collision 2017-08-08 09:06:36 +02:00
Daniel García Aubert
91d5a0e4e4 Remove column avg_val in group_by 2017-08-08 08:46:26 +02:00
Daniel García Aubert
e446160151 Use final columns to group by 2017-08-08 08:45:05 +02:00
Daniel García Aubert
823925d091 Add prefix to bins_array to avoid name collision 2017-08-08 08:41:22 +02:00
Daniel García Aubert
994e58bef7 Add prefix to bins_number and nulls_cout to avoid name collision 2017-08-08 08:33:26 +02:00
Daniel García Aubert
5c80ff8191 Extract query: move condition to a method 2017-08-07 19:24:15 +02:00
Daniel García Aubert
0f45675652 Update NEWS 2017-08-07 19:06:31 +02:00
Daniel García Aubert
b2bbc329ea Apply prefix for intermediate query variables to avoid name colision 2017-08-07 19:03:49 +02:00
Daniel García Aubert
2024e89c6a Update news 2017-08-07 18:30:17 +02:00
Daniel García Aubert
1f8da14c2a Cast to timestamp start_date and end_date to calculate bins when date column is timestamptz 2017-08-07 18:27:24 +02:00
Daniel García Aubert
660078f284 Fix minor issues with timezones 2017-08-07 16:53:08 +02:00
Raul Ochoa
e9d925334c Move layergroup-token to models
We will share it between tests and a middleware to parse the token.
2017-08-04 17:51:10 +02:00
Raul Ochoa
399561d076 Implement LZMA query param inflating as middleware
The req2params method is doing too many things,
this is an initial step to do fewer things in that method.
2017-08-04 17:30:46 +02:00
Daniel García Aubert
7eae2a0618 Stubs next version 2017-08-04 09:51:16 +02:00
Daniel García Aubert
e52cf0960c Release 3.10.1 2017-08-04 09:46:00 +02:00
Daniel García Aubert
c39a6a6806 Update news 2017-08-04 09:42:21 +02:00
Javier Goizueta
82cab3ccc7 Merge pull request #719 from CartoDB/ramp-infinities-nans
Exclude Infinities & NaNs from ramps
2017-08-04 09:38:44 +02:00
Javier Goizueta
e01730e8e4 Syntax fixes 2017-08-03 19:16:08 +02:00
Javier Goizueta
eed33fc76d Add tests for excluding NaNs, Ininities from ramps 2017-08-03 19:07:02 +02:00
Daniel
2a531024d7 Merge pull request #720 from CartoDB/fix-aggregation-falsy-values
Fix bad condition when timestampt_start has falsy values
2017-08-03 18:36:37 +02:00
Daniel García Aubert
48ad7059e1 Going green: do not rely on falsy conditional 2017-08-03 18:23:55 +02:00
Daniel García Aubert
6c063095a3 Going red: aggregation is undefined when automattic mode is enabled and timestamp start is 1970-01-01 (epoch) 2017-08-03 18:18:35 +02:00
Daniel García Aubert
1d1a046439 Stubs next version 2017-08-03 15:33:32 +02:00
Daniel García Aubert
fe7a2451ef Release 3.10.0 2017-08-03 15:25:47 +02:00
Daniel
a696bdc723 Merge pull request #706 from CartoDB/705-special-numeric-values
Support special numeric values for json responses
2017-08-03 15:21:29 +02:00
Daniel
a98c884e1a Merge pull request #698 from CartoDB/691-date-histogram
Support histograms with date/time expressions for aggregations
2017-08-03 15:16:13 +02:00
Daniel García Aubert
431ca9c56f Update NEWS 2017-08-03 15:15:37 +02:00
Daniel García Aubert
b56d2ec30b Validate aggregation value 2017-08-03 12:24:05 +02:00
Daniel García Aubert
90ded34af7 Do not fail if layergroup is undefined 2017-08-03 12:22:30 +02:00
Daniel García Aubert
7fed91900d Handle error 2017-08-03 12:19:34 +02:00
Javier Goizueta
b4799124e6 Exclude non-finite values when computing ramps 2017-08-02 17:28:46 +02:00
Daniel García Aubert
1bc5c04489 Remove unused method 2017-08-02 13:15:40 +02:00
Daniel García Aubert
0a57e86cb8 Do not build data histogram infinitely when overriding aggregation with auto mode 2017-08-02 12:06:10 +02:00
Daniel García Aubert
3574700c2d Remove tiler render limit 2017-08-02 11:07:44 +02:00
Daniel García Aubert
ab879e2634 Use new version of getUserTimeoutRenderLimits 2017-08-01 19:13:55 +02:00
Daniel García Aubert
9034508244 Support automattic aggregation only when aggregation para is set to 'auto' 2017-08-01 17:15:45 +02:00
Daniel García Aubert
b2b68ffd5c Merge branch 'master' into 691-date-histogram 2017-08-01 16:07:27 +02:00
Daniel García Aubert
0594407b38 Change error message 2017-08-01 15:03:09 +02:00
Daniel García Aubert
262f854e68 Remove error wrapping 2017-08-01 13:10:55 +02:00
Daniel García Aubert
9258ad7ecc Fix style typo 2017-08-01 12:56:03 +02:00
Daniel García Aubert
46fee774bd Fix misconfiguration in test's hook 2017-08-01 12:54:41 +02:00
Daniel García Aubert
05ddf1d505 Add test to check if asset fallback is working when enabled and database timeout erro happens 2017-08-01 12:53:29 +02:00
Daniel García Aubert
4c3e3005aa Apply asset fallback to database timeout errors 2017-08-01 12:52:34 +02:00
Daniel García Aubert
7d13603163 Implement test to validate database timeout error for static api 2017-08-01 11:58:43 +02:00
Daniel García Aubert
40af73d524 Implement test to check render timeout error for static api 2017-08-01 11:47:50 +02:00
Daniel García Aubert
91b3e373b7 Add helper method to fetch static images 2017-08-01 11:46:48 +02:00
Daniel García Aubert
aa4bb62f38 Fix test 2017-08-01 10:29:46 +02:00
Daniel García Aubert
9af372381c Fix content-type assertion 2017-08-01 10:29:29 +02:00
Daniel García Aubert
0c4e67d6a8 Implemented database timeout test while requesting tiles 2017-08-01 10:21:39 +02:00
Daniel García Aubert
dd5209b9a7 Add torque.png timeout error test 2017-08-01 09:39:37 +02:00
Daniel García Aubert
44fc34b1ce Improve timeout error message 2017-07-31 20:07:31 +02:00
Daniel García Aubert
1fdc0621e7 Categorize timeout errors 2017-07-31 19:36:07 +02:00
Daniel García Aubert
5974413d5c Use 429 to indicate timeout errors 2017-07-31 19:08:29 +02:00
Daniel García Aubert
bb59902535 Refactoring tests hooks 2017-07-31 18:52:09 +02:00
Daniel García Aubert
49d2f513c6 Fix typo 2017-07-31 18:51:23 +02:00
Daniel García Aubert
b1114fc606 Add timeout test for toque.json tiles 2017-07-31 18:26:45 +02:00
Daniel García Aubert
227c2b336b Uncomment database timeout configuration 2017-07-31 18:25:34 +02:00
Daniel García Aubert
ac7509b01a Expose function to clean up database connections 2017-07-31 18:24:42 +02:00
Daniel García Aubert
9b5482489e Fix content-type defaults 2017-07-31 18:23:17 +02:00
Daniel García Aubert
f079c24554 Use parsed body variable 2017-07-31 18:22:13 +02:00
Daniel García Aubert
04da57fe0c Do not create layergroup if it is already provided 2017-07-31 18:14:32 +02:00
Daniel García Aubert
aa6d01f151 Add timeout test for attributes 2017-07-31 18:12:33 +02:00
Daniel García Aubert
435d902e45 Expose function to clean all database connections in the pool 2017-07-31 18:10:14 +02:00
Daniel García Aubert
664db4b5cf Pass proper param to check content-type and status 2017-07-31 18:07:44 +02:00
Daniel García Aubert
64f19b65ec Remove attributes param 2017-07-31 18:01:19 +02:00
Daniel García Aubert
398369a5c7 Do not rely on expected defaults headers 2017-07-31 17:58:33 +02:00
Daniel García Aubert
f2e043b063 Do not expose database error info 2017-07-31 17:56:58 +02:00
Daniel García Aubert
6936107b68 Adjust pg_sleep to timeout 2017-07-28 16:04:11 +02:00
Daniel García Aubert
c3e137bb00 Update dependency 2017-07-28 16:02:54 +02:00
Daniel García Aubert
cca570e832 Uncomment DB and role configuration 2017-07-28 13:23:13 +02:00
Daniel García Aubert
815eac5a48 Add hook to refresh all connections in the pool 2017-07-28 13:22:16 +02:00
Daniel García Aubert
b023a155b7 Be more accurate with timeouts and pg_sleep 2017-07-28 13:21:17 +02:00
Daniel García Aubert
33e77a42f2 Separate user timeout suites between render and database 2017-07-27 18:50:27 +02:00
Daniel García Aubert
664a4e673a Add missing after-each hooks and merged duplicates 2017-07-27 17:08:29 +02:00
Daniel García Aubert
eba97a41e5 Going green, solves issue with role timeout in database 2017-07-27 16:30:57 +02:00
Daniel García Aubert
9e491e7e9a Fix tests names 2017-07-27 16:29:36 +02:00
Daniel García Aubert
522fc79d71 Do not remove redis' keys if layergroup was not created 2017-07-27 12:46:38 +02:00
Daniel García Aubert
768d06c582 Assert layer params is defined but mapnik layers 2017-07-26 18:35:40 +02:00
Daniel García Aubert
058f19ab36 Fix skipped test 2017-07-26 13:27:58 +02:00
Daniel García Aubert
788b2f0683 Implement test to validate response limits work as expected 2017-07-25 19:16:37 +02:00
Daniel García Aubert
526e850f26 Add method to set statements timeout for user's role and database 2017-07-25 19:15:43 +02:00
Daniel García Aubert
444595d49d Rely on windshaft's repo branch 2017-07-25 19:14:04 +02:00
Daniel García Aubert
eee4fc815e Do not expose database error details 2017-07-25 19:11:56 +02:00
Daniel García Aubert
edacd85d5c Merge branch 'master' into response-time-limit 2017-07-24 11:47:51 +02:00
Daniel García Aubert
52da3bfa55 Stub next version 2017-07-21 11:05:51 +02:00
Daniel García Aubert
35b9448e9a Release 3.9.8 2017-07-21 10:58:37 +02:00
Daniel
9959e009eb Merge pull request #714 from CartoDB/bump-windshaft-3.2.2
Bump windshaft version to 3.2.2
2017-07-21 10:44:35 +02:00
Daniel García Aubert
106b9a64b2 Update NEWS 2017-07-21 10:39:33 +02:00
Daniel García Aubert
42d05f29ee Bump windshaft version to 3.2.2 2017-07-20 19:47:01 +02:00
Daniel García Aubert
fc3a959da1 Stub next version 2017-07-20 15:00:39 +02:00
Daniel García Aubert
20003c49ce Release 3.9.7 2017-07-20 14:53:20 +02:00
Daniel
d1d9401539 Merge pull request #713 from CartoDB/upgrade-turbo-carto-to-0.19.2
Bump turbo-carto version to 0.19.2
2017-07-20 14:50:02 +02:00
Daniel García Aubert
cc8a1df388 Bump turbo-carto version to 0.19.2 2017-07-20 12:11:09 +02:00
Daniel García Aubert
e9bc0732c0 Use switch statement instead of if ... else if ... else ... 2017-07-19 12:33:57 +02:00
Daniel García Aubert
8907082a85 Parse body relying on content-type header 2017-07-19 12:24:37 +02:00
Daniel García Aubert
87eb5407a8 WIP: implement timeout limit for raster 2017-07-18 20:50:31 +02:00
Daniel García Aubert
a17916488b Update config to get the right key to enable stats metadate after layergroup creation 2017-07-18 17:40:36 +02:00
Daniel García Aubert
669707b26c Fix typo 2017-07-18 11:56:54 +02:00
Daniel García Aubert
40dc94e010 Merge branch 'master' into response-time-limit 2017-07-18 11:25:23 +02:00
Daniel
868930de46 Merge pull request #712 from CartoDB/mvt-no-content
Respond with 204 when mvt is empty
2017-07-18 11:23:12 +02:00
Daniel García Aubert
f306c26da6 Update News 2017-07-18 11:08:39 +02:00
Daniel García Aubert
446e2d0802 Add empty line at the end of file 2017-07-18 11:05:45 +02:00
Daniel García Aubert
0aab434f13 Remove duplicated assertion 2017-07-18 10:52:24 +02:00
Daniel García Aubert
ff13996255 Add test to check that mvt returns 200 when tile has data 2017-07-18 10:44:27 +02:00
Daniel García Aubert
eccc3597aa Respond with 204 when vector tile is empty 2017-07-17 19:44:18 +02:00
Carlos Matallín
6766b76545 Merge pull request #710 from CartoDB/691-date-histogram-offset
replace timezone => offset
2017-07-14 18:47:23 +02:00
Carlos Matallín
e30b883906 Merge branch '691-date-histogram' into 691-date-histogram-offset 2017-07-14 18:38:13 +02:00
Carlos Matallín
70b4d5b7fd replace timezone => offset 2017-07-14 18:30:36 +02:00
Daniel García Aubert
0fffafa1db Add timestamp_start in histogram summary to help to build the histogram in frontend side 2017-07-14 18:22:05 +02:00
Daniel García Aubert
21b8655f85 Return timezone applied or 0 if not present 2017-07-13 19:42:22 +02:00
Daniel García Aubert
c8286233be Do not apply timezone for minute an hour aggregations 2017-07-12 17:08:55 +02:00
Daniel García Aubert
b67f6053e8 Should respect first timestamp as bin_start 2017-07-12 15:19:28 +02:00
Daniel García Aubert
967dca9578 Improve readability 2017-07-12 15:10:39 +02:00
Daniel García Aubert
a35b1e3e86 Stubs next version 2017-07-11 09:59:30 +02:00
Daniel García Aubert
5b8ecd3df0 Release 3.9.6 2017-07-11 09:53:12 +02:00
Daniel
5ea5c1b2dc Merge pull request #708 from CartoDB/11245-aggregation-search
widgets: support for aggregation in search results
2017-07-11 09:44:33 +02:00
Daniel García Aubert
e36266a80f Added test to check all aggregation operations work as expected when searching dataviews 2017-07-10 12:29:25 +02:00
Carlos Matallín
b1c9dd537e Update Makefile 2017-07-07 17:47:28 +02:00
Carlos Matallín
dd934a3913 linter 2017-07-07 17:44:32 +02:00
Carlos Matallín
7fa154c062 widgets: add aggregation for search results, specs 2017-07-07 17:38:15 +02:00
Carlos Matallín
f7a763b637 widgets: add aggregation for search results 2017-07-07 17:09:17 +02:00
Daniel García Aubert
ad1506ae97 Remove empty lines 2017-07-06 16:24:50 +02:00
Daniel García Aubert
32bcf9ca89 Fix jshint typo 2017-07-06 16:24:18 +02:00
Daniel García Aubert
23aab7a09f User feature branch of cartodb-redis 2017-07-06 16:23:42 +02:00
Daniel García Aubert
37c970903e Avoid uncaught exception when layergroup is not present 2017-07-05 19:09:14 +02:00
Daniel García Aubert
0684c1b9d3 Work in progress: get timeout from redis 2017-07-05 19:08:19 +02:00
Daniel García Aubert
468f641af8 Going green: automatic mode works with dates 2017-06-29 16:57:27 +02:00
Daniel García Aubert
6d2934b30b Going red: add test to check automatic mode works with dates 2017-06-29 16:53:52 +02:00
Daniel García Aubert
7018af18b6 Support automatic aggregation for time-series histogram 2017-06-28 19:58:45 +02:00
Daniel García Aubert
f507f7a74b Stub next version 2017-06-27 17:18:08 +02:00
Daniel García Aubert
2f1cacdfc7 Release 3.9.5 2017-06-27 17:07:52 +02:00
Daniel García Aubert
3a442bea44 Update NEWS 2017-06-27 17:06:24 +02:00
Daniel
49f5b0b480 Merge pull request #700 from CartoDB/dataviews-special-float-values
Handle Infinity and NaN values for dataviews
2017-06-27 16:58:44 +02:00
Daniel García Aubert
01027b73da Fix jshint typo 2017-06-27 14:36:18 +02:00
Daniel García Aubert
af42fba53b Check that quarter aggreagtion uses filters properly in date histogram dataview 2017-06-27 14:28:23 +02:00
Daniel García Aubert
3e12bfe27a Going green: support special numeric values for json responses 2017-06-27 11:53:22 +02:00
Daniel García Aubert
13764e18ce Going red: attributes service do not support special numeric values (Infinity, -Infinity, NaN) 2017-06-27 11:21:05 +02:00
Javier Goizueta
b2f3735e95 The formula widget wasn't using the no_filters query for checking column types 2017-06-23 18:59:51 +02:00
Javier Goizueta
166e29e8ce Forward queries parameter from overview dataviews to base dataviews 2017-06-23 16:53:16 +02:00
Daniel García Aubert
32274e66fd Dataview formula: count infinities and nans as we do with nulls 2017-06-23 12:24:22 +02:00
Rafa de la Torre
15b88c6a67 Stub next version 2017-06-22 18:26:33 +02:00
Rafa de la Torre
2dae09c35b Fix yarn.lock and update HOWTO_RELEASE
In previous commit I updated all the dependencies with the newest
versions when I really intended to update just one. After asking the
node gurus we agreed on changing the docs to reflect a less aggressive
strategy for dependency upgrades.
2017-06-22 18:11:58 +02:00
Daniel García Aubert
a6daca9628 Support date histograms using timestamp with and without timezones 2017-06-22 18:04:23 +02:00
Rafa de la Torre
14e71b929a Release version 3.9.4 2017-06-22 17:48:55 +02:00
Daniel García Aubert
6f7cb75256 Fix bad datetime conversion 2017-06-21 20:19:02 +02:00
Daniel García Aubert
5555b8ad8e Going green: support numeric NaN values for dataviews 2017-06-21 18:59:36 +02:00
Daniel García Aubert
e44d418db3 Going red: Add test to check that aggregation dataview supports numeric special value 2017-06-21 18:44:21 +02:00
Ivan Malagon
6bfedef7eb Cast histogram width bucket to timestamp 2017-06-19 12:47:08 +02:00
Daniel García Aubert
77cb3dbbdc Merge branch 'master' into 691-date-histogram 2017-06-19 10:59:28 +02:00
Daniel García Aubert
17aebf53e2 Merge branch 'master' into dataviews-special-float-values 2017-06-19 10:58:13 +02:00
Daniel García Aubert
02f117af1b Merge branch 'dataviews-special-float-values' of github.com:CartoDB/Windshaft-cartodb into dataviews-special-float-values 2017-06-16 13:01:39 +02:00
Daniel García Aubert
b1ac5b8ca9 Handle special float values only if column is float 2017-06-16 12:57:46 +02:00
Mario de Frutos
e2b976d9d0 Fix version 2017-06-16 11:16:17 +02:00
Mario de Frutos
8e95cf20c0 Upgrade camshaft to 0.55.5 (#703) 2017-06-16 11:13:40 +02:00
Daniel García Aubert
20d7f1a7c5 Handle special float values only when aggregation columns is float (overviews) 2017-06-15 19:22:26 +02:00
Daniel García Aubert
115d8fe685 Handle special float values only when aggregation columns is float 2017-06-15 19:07:31 +02:00
Mario de Frutos
2a366ec16f Stubs next version 2017-06-15 18:32:28 +02:00
Mario de Frutos
849caf9b58 Upgrade to cammshaft 0.55.4 (#702) 2017-06-15 18:30:13 +02:00
Daniel García Aubert
ad570ab6f2 Use dataview base to get column type in formula dataviews 2017-06-15 18:04:35 +02:00
Daniel García Aubert
443c1100d7 Formula dataview: support special values only if column is a float column 2017-06-15 16:31:31 +02:00
Daniel García Aubert
7d0af4e259 Going green: handle special float values for formula when overviews are involved 2017-06-14 19:44:48 +02:00
Daniel García Aubert
81f60959e5 Going green: handle special float values for formula when overviews are involved 2017-06-14 19:20:39 +02:00
Daniel García Aubert
ef849aec34 Going red: add test to check that special float values are not being filtered out in formula dataview when the layer uses overviews 2017-06-14 19:19:08 +02:00
Daniel García Aubert
dee00e6abd Going green: handle special float values for histogram when overviews are involved 2017-06-14 19:00:37 +02:00
Daniel García Aubert
06d40e8b1e Going red: add test to check that special float values are not being filtered out when the layer uses overviews 2017-06-14 18:57:32 +02:00
Daniel García Aubert
3f17c8b15a Filter out special float values before categorizing them 2017-06-14 15:05:46 +02:00
Daniel García Aubert
668b22628c Going green: support special float values in aggergation dataview with overviews 2017-06-13 19:01:59 +02:00
Daniel García Aubert
c08db78a0b Going red: implement test to check aggregation with overviews support special float values 2017-06-13 19:01:28 +02:00
Daniel García Aubert
551b6d409a Remove bad condition 2017-06-13 09:44:53 +02:00
Daniel García Aubert
3ae66e4143 Do not filter special values out if aggregation column is not defined 2017-06-13 09:30:43 +02:00
Daniel García Aubert
227937bf4c Remove test filter 2017-06-12 19:50:24 +02:00
Daniel García Aubert
cb7ec5d556 Fix jshint typos 2017-06-12 19:49:58 +02:00
Daniel García Aubert
8b2fa27ba7 Calculate aggregation filtering out special float values 2017-06-12 19:45:06 +02:00
Daniel García Aubert
962fa05574 Remove console 2017-06-12 19:28:49 +02:00
Daniel García Aubert
75d07745e6 Improve readability 2017-06-12 19:22:37 +02:00
Daniel García Aubert
7b5111614c Summarize special float values for ranked aggregation 2017-06-12 19:21:41 +02:00
Daniel García Aubert
e60bb770db Summarizes infinity and NaN values 2017-06-12 18:55:33 +02:00
Daniel García Aubert
ba6dc62a38 Going red: implementet test to check special float values support 2017-06-12 18:15:39 +02:00
Daniel García Aubert
e6aededf08 Fix typo 2017-06-12 17:19:05 +02:00
Daniel García Aubert
0aae29fb4b Fix jshint typo 2017-06-09 15:28:18 +02:00
Daniel García Aubert
9ba65bd5a4 Going green: Fix test for formula overviews flavour 2017-06-09 15:18:52 +02:00
Daniel García Aubert
7a3498e8ec Going red: formula does not work with Infinity or NaN values 2017-06-09 12:17:16 +02:00
Daniel García Aubert
fe5c76d65b Remove jshint hook 2017-06-08 19:25:05 +02:00
Daniel García Aubert
29a6658e3d Migrate dataviews endpoints to use the allow-query-params 2017-06-08 19:22:33 +02:00
Daniel García Aubert
2772fc62d2 Use a set/dict for checking the existence 2017-06-08 18:38:44 +02:00
Daniel García Aubert
0d4ac64f00 Merge branch 'master' into 691-date-histogram 2017-06-08 18:34:25 +02:00
Daniel
271887eb46 Merge pull request #696 from CartoDB/dataview-backend-small-refactor
Dataview backend small refactor
2017-06-08 18:24:59 +02:00
Daniel García Aubert
cd53eda0a5 Handle Infinity and NaN values for histograms 2017-06-08 16:01:41 +02:00
Daniel García Aubert
6c301403e3 Histogram going red: fails while quering Infinity and NanN values 2017-06-08 15:59:33 +02:00
Daniel García Aubert
47af013157 Merge branch 'master' into 691-date-histogram 2017-06-07 16:11:50 +02:00
Daniel García Aubert
35d4fb4d27 Improve readability, using extract method pattern 2017-06-07 16:11:09 +02:00
Daniel García Aubert
42e2f9e4b1 Update commented use cases 2017-06-07 15:54:28 +02:00
Daniel García Aubert
02bf1dd2d7 Release 3.9.1 2017-06-06 17:34:38 +02:00
Daniel
d365e092b9 Merge pull request #699 from CartoDB/update_camshaft_0553
Bump camshaft version to  0.55.3
2017-06-06 17:32:18 +02:00
Daniel García Aubert
45d1d07ea2 Use released version of comshaft (0.55.3) 2017-06-06 17:21:33 +02:00
Mario de Frutos
f14a61528a Upgrade to camshaft 0.55.3 2017-06-06 10:52:37 +02:00
Daniel García Aubert
d3bcf6f80d Do not omit nonexistent property 2017-06-06 09:15:49 +02:00
Daniel García Aubert
eeea51e10d Removed unused column in group by statement 2017-06-05 17:26:37 +02:00
Daniel García Aubert
9337cd948c Fix typo 2017-06-05 16:26:29 +02:00
Daniel García Aubert
527e005952 Merge branch '691-date-histogram' of github.com:CartoDB/Windshaft-cartodb into 691-date-histogram 2017-06-05 16:26:09 +02:00
Daniel García Aubert
1ff0954390 Fix typo 2017-06-05 16:25:47 +02:00
Daniel García Aubert
e82d688a18 Fix typo 2017-06-05 16:09:47 +02:00
Daniel García Aubert
95a6ad3b86 Support quarter aggregation in histograms over date columns 2017-06-05 16:04:42 +02:00
Daniel García Aubert
d01787842f Support UTC timezone override 2017-06-05 15:23:04 +02:00
Daniel García Aubert
c86f92f8eb Improve test description 2017-06-05 15:05:23 +02:00
Daniel García Aubert
003227fb29 Fix assertion 2017-06-05 14:59:35 +02:00
Daniel García Aubert
869408b7b7 Use Eastern Daylight Time while testing 2017-06-05 14:50:49 +02:00
Daniel García Aubert
dc844f8131 Remove console.log 2017-06-05 14:23:53 +02:00
Daniel García Aubert
71e9e62db0 Improved histogram assertion with moment.js 2017-06-05 14:18:24 +02:00
Daniel García Aubert
6ff3b33cde Removed bins_start as query output 2017-06-05 14:17:50 +02:00
Daniel García Aubert
32eeb57fce Reduce complexity in function 2017-06-02 19:00:26 +02:00
Daniel García Aubert
8bc38a375a Support timezone aggregation 2017-06-02 18:37:49 +02:00
Daniel García Aubert
c1fac13d6b Be able to accept timezone parameter 2017-06-02 12:45:34 +02:00
Daniel García Aubert
6374d2e4b6 Fix typo 2017-06-02 12:17:55 +02:00
Daniel García Aubert
9c34428984 Allow override start and end params 2017-06-02 12:15:43 +02:00
Daniel García Aubert
1d66e49910 WIP implemented date histogram 2017-06-01 20:07:46 +02:00
Raul Ochoa
4b562e6768 Merge conditions 2017-05-31 13:00:37 -04:00
Raul Ochoa
b4fbe0b8cf No in advance var definition 2017-05-31 12:58:35 -04:00
Raul Ochoa
62514fc563 Extract query rewrite data to function 2017-05-31 12:58:20 -04:00
Raul Ochoa
ef3cad6599 Extract to function 2017-05-31 12:52:41 -04:00
Raul Ochoa
4e53803b3b Create BBoxFilter independently from rewrite data 2017-05-31 12:51:07 -04:00
Raul Ochoa
40a73f2eaf Merge pull request #695 from CartoDB/fix-bboxes-filters
Add test to detect and fix incorrect bbox filter splitting
2017-05-31 11:23:42 -04:00
Javier Goizueta
31557b06be Add test to detect and fix incorrect bbox filter splitting
When bbox crosses date line and is split in two, the eastern box wasn't correct
2017-05-31 11:09:51 -04:00
Daniel García Aubert
c8ea595f47 Stubs next version 2017-05-31 12:14:58 +02:00
Daniel García Aubert
dbd0398d9b Release 3.9.0 2017-05-31 12:13:25 +02:00
Daniel
4f7ec0dd4d Merge pull request #683 from CartoDB/664-layergroup-stats
Use windshaft-stats to extract info about layer metadata
2017-05-31 11:58:00 +02:00
Daniel García Aubert
5a1623b667 Merge branch 'master' into 664-layergroup-stats 2017-05-31 11:50:32 +02:00
Daniel García Aubert
2c509d97b1 Point windshaft to the last released version 2017-05-31 11:46:46 +02:00
Daniel García Aubert
da3f30dd9f Update NEWS 2017-05-31 11:27:41 +02:00
Mario de Frutos
440953b1cd If we have the stats FF disbabled return empty array instead of null
But we keep checking for elements in the returned object because
we don't want to include the stats property if the FF is disabled
2017-05-30 10:47:47 +02:00
Raul Ochoa
45471597bb Merge pull request #693 from CartoDB/test-improvements
Do not assert inside assert.response
2017-05-26 13:20:44 +02:00
Raul Ochoa
882aeacac2 Rewrite test to take advantage of changes in assert.response/TestClient
This should avoid the issue of preventing the whole suite to halt, as
in https://travis-ci.org/CartoDB/Windshaft-cartodb/builds/236337027.
2017-05-26 13:13:19 +02:00
Raul Ochoa
248adab05b Catch response body if any to capture Redis keys 2017-05-26 13:06:04 +02:00
Raul Ochoa
f4e99629f6 Do not assert inside response, but pass error into callback
Preferably we should put response outside of assert and change its
callback signature. However, I don't think it is worth the effort
right now.
2017-05-26 13:02:02 +02:00
Daniel
df08463dcf Merge pull request #690 from CartoDB/allow-es6-syntax
Allow es6 syntax
2017-05-25 14:28:02 +02:00
Daniel García Aubert
b84bb469e5 Allow es6 syntax 2017-05-25 11:26:04 +02:00
Daniel
26e4d19635 Merge pull request #689 from CartoDB/upgrade-camshaft-to-0.55.2
Upgrade camshaft to 0.55.2
2017-05-24 12:01:39 +02:00
Daniel García Aubert
69984ed895 Upgrade camshaft to 0.55.2 2017-05-24 11:54:30 +02:00
Daniel García Aubert
d471905358 Merge branch 'master' into 664-layergroup-stats 2017-05-23 13:47:27 +02:00
Daniel García Aubert
e5223980cf Update NEWS 2017-05-23 13:46:56 +02:00
Daniel
1f65968de3 Merge pull request #688 from CartoDB/upgrade-camshaft-to-0.55.1
Upgrade camshaft to 0.55.1
2017-05-23 13:45:57 +02:00
Daniel García Aubert
51c11aff03 Upgrade camshaft to 0.55.1 2017-05-23 13:37:34 +02:00
Daniel García Aubert
87e6e64d42 Merge branch 'master' into 664-layergroup-stats 2017-05-23 12:41:14 +02:00
Raul Ochoa
7dfb7c605e Merge pull request #687 from CartoDB/remove-turbo-carto-promise-global
Remove Promise hack from turbo-carto adapter
2017-05-22 18:52:27 +02:00
Raul Ochoa
8b0964ad7e Remove Promise hack from turbo-carto adapter 2017-05-22 18:45:24 +02:00
Daniel García Aubert
5135b6e14a Stubs next version 2017-05-22 14:57:03 +02:00
Daniel García Aubert
dcb26560e3 Release 3.8.0 2017-05-22 14:50:41 +02:00
Daniel
921468668b Merge pull request #686 from CartoDB/upgrade-camshaft-to-0.55.0
Upgrade camshaft to 0.55.0 and turbo-carto to 0.19.1
2017-05-22 14:39:10 +02:00
Mario de Frutos
76c2f8dc0e Includes release changes of turbo-carto too 2017-05-22 14:33:14 +02:00
Daniel García Aubert
7efb196247 Upgrade camshaft to 0.55.0 2017-05-22 13:09:42 +02:00
Raul Ochoa
95c324ddd1 Merge pull request #685 from CartoDB/update-dev-deps
Update dev deps
2017-05-18 18:25:32 +02:00
Raul Ochoa
0a3d1fbdf9 Upgrade jshint and fix test 2017-05-18 17:39:56 +02:00
Raul Ochoa
45c1ccab9e Upgrade mocha dep 2017-05-18 17:39:26 +02:00
Daniel García Aubert
cc2a96579b Merge branch 'master' into 664-layergroup-stats 2017-05-18 16:39:52 +02:00
Daniel García Aubert
61b19856e9 Stubs next version 2017-05-18 16:36:50 +02:00
Daniel García Aubert
67aa2d1a00 Release 3.7.1 2017-05-18 16:34:56 +02:00
Daniel
c6ee2eac62 Merge pull request #684 from CartoDB/fix-buffersize-undefined-in-mapconfig
Fix buffersize undefined in mapconfig instantiation
2017-05-18 16:33:31 +02:00
Daniel García Aubert
3978d58d66 Remove empty line 2017-05-18 16:12:28 +02:00
Daniel García Aubert
cd86387fa7 Merge branch 'fix-buffersize-undefined-in-mapconfig' into 664-layergroup-stats 2017-05-18 15:58:12 +02:00
Daniel García Aubert
3ce38d7081 Going green: fix type error when no buffersize is defined in mapconfig 2017-05-18 15:49:57 +02:00
Daniel García Aubert
e9112da305 Going red: add test to check undefined buffersize in requested mapconfig throws error 2017-05-18 15:41:41 +02:00
Mario de Frutos
c9e6e921cb Merge branch 'master' into 664-layergroup-stats 2017-05-18 13:45:38 +02:00
Daniel García Aubert
4e715f6ba4 Stubs next version 2017-05-18 13:24:48 +02:00
Daniel García Aubert
8f156b9f13 Release 3.7.0 2017-05-18 13:23:22 +02:00
Daniel
954876f738 Merge pull request #673 from CartoDB/665-buffer-size
Manage multiple values of buffer-size for different formats
2017-05-18 13:20:25 +02:00
Daniel García Aubert
fd178bcf71 Upgrade windshaft to 3.2.0 2017-05-18 13:14:51 +02:00
Daniel García Aubert
acaff98da5 Merge branch 'master' into 665-buffer-size 2017-05-18 13:08:46 +02:00
Mario de Frutos
ed56094be2 PR changes 2017-05-18 11:51:12 +02:00
Daniel García Aubert
c65518cf41 Get back layer-stats from windshaft-stats 2017-05-17 20:16:43 +02:00
Daniel García Aubert
fb4ee61b83 Use setInmmediate vs process.nextTick 2017-05-17 12:55:05 +02:00
Daniel García Aubert
808c729a0e Now supported formats for buffer-size customization are bound to the adapter 2017-05-17 12:33:41 +02:00
Daniel García Aubert
4602fb3ecf Send stats for png32 tiles 2017-05-17 12:16:16 +02:00
Daniel García Aubert
c59996303d Send stats for mvt tiles 2017-05-17 12:04:11 +02:00
Daniel García Aubert
13b1978d49 Include layer param to reach the right tile for grid.json 2017-05-17 11:40:53 +02:00
Daniel García Aubert
e13ae8d5af Do not make optional layer param in URL template 2017-05-17 11:40:18 +02:00
Daniel García Aubert
0f16c0e396 Merge branch '664-layergroup-stats' of github.com:CartoDB/Windshaft-cartodb into 664-layergroup-stats 2017-05-16 12:15:40 +02:00
Daniel García Aubert
29361f5392 Use windhshaft with latest changes for layer stats 2017-05-16 12:07:17 +02:00
Mario de Frutos
422867762b package.json for staging 2017-05-12 17:14:07 +02:00
Mario de Frutos
5969c99e8a Removed not used parameters for layer stats 2017-05-12 16:42:10 +02:00
Mario de Frutos
5417933ecc Change to tests for layer stats because now uses CDB_EstimateRowCount fuction 2017-05-12 16:42:10 +02:00
Mario de Frutos
59585b5cd9 We leave only one feature flag for stats 2017-05-12 16:42:10 +02:00
Mario de Frutos
522c86e6f2 Change from branch to commits for staging dependencies 2017-05-12 16:42:04 +02:00
Mario de Frutos
1a7fd9bf31 Changed stats name from featureCount to estimatedFeatureCount 2017-05-12 16:40:34 +02:00
Mario de Frutos
7596df96ed package.json for staging 2017-05-12 12:58:13 +02:00
Mario de Frutos
44cca38538 Change to tests for layer stats because now uses CDB_EstimateRowCount fuction 2017-05-12 12:51:45 +02:00
Mario de Frutos
f6fff6953e We leave only one feature flag for stats 2017-05-12 12:50:57 +02:00
Mario de Frutos
35df0c3a68 Change from branch to commits for staging dependencies 2017-05-11 14:26:00 +02:00
Daniel García Aubert
f9c8178d99 Stubs next version 2017-05-11 13:46:34 +02:00
Daniel García Aubert
787ca1607a Release 3.6.6 2017-05-11 13:37:43 +02:00
Daniel
7179c0a5f1 Merge pull request #682 from CartoDB/camshaft-error-node-id
Upgrade camshaft to 0.54.4
2017-05-11 13:29:29 +02:00
Daniel García Aubert
b739db1023 Use released version of camshaft 2017-05-11 13:19:14 +02:00
Daniel García Aubert
66a898cdc2 Upgrade camshaft to get error node-id 2017-05-11 12:55:53 +02:00
Mario de Frutos
61f9ea6e86 Changed stats name from featureCount to estimatedFeatureCount 2017-05-11 12:47:35 +02:00
Daniel García Aubert
5a44d6c547 Drop geojson support for buffersize customization 2017-05-10 18:35:30 +02:00
Daniel García Aubert
53d1b2fbbf Rename mapconfig-named-map-adapter by mapconfig-buffer-size-adapter 2017-05-10 18:16:22 +02:00
Daniel García Aubert
2c9d30e042 Be more flexible validating buffer-size customization 2017-05-10 17:49:28 +02:00
Mario de Frutos
968677e275 package.json poiting to staging branches 2017-05-10 17:26:12 +02:00
Mario de Frutos
daf19c5e27 Stats backend only provides stats not metadata 2017-05-10 17:17:01 +02:00
Raul Ochoa
ac94118798 Merge pull request #678 from CartoDB/print-attributions
added print attributions from Docs FAQs to Static Maps API content
2017-05-10 09:26:07 +02:00
Mario de Frutos
7d5b6b0820 Lint changes and yarn.lock 2017-05-09 18:24:24 +02:00
Mario de Frutos
b87e442801 Remove vertex count from stats tests 2017-05-09 18:24:11 +02:00
Raul Ochoa
1a197bb9cf Stubs next version 2017-05-09 15:06:49 +02:00
Raul Ochoa
5b96db2ba2 Release 3.6.5 2017-05-09 15:06:00 +02:00
Raul Ochoa
3b687ce09a Merge pull request #681 from CartoDB/upgrade-camshaft
Upgrades camshaft to 0.54.3
2017-05-09 15:05:15 +02:00
Raul Ochoa
7bb039b13c Upgrades camshaft to 0.54.3 2017-05-09 14:58:25 +02:00
Mario de Frutos
474d68687c Include vertexCount in the stats tests 2017-05-09 14:47:39 +02:00
Mario de Frutos
b25540720c Added acceptance tests for stats functionallity 2017-05-09 14:36:35 +02:00
Mario de Frutos
759d28f12f Include feature flag to enable/disable stats 2017-05-09 12:51:22 +02:00
Mario de Frutos
15c68711aa Create stats backend to decouple stats logic from map controller 2017-05-09 12:31:16 +02:00
Mario de Frutos
568d6b5458 Include the layers metadata into the layergroup object 2017-05-09 11:59:11 +02:00
Mario de Frutos
525c0f2afa Added rendererCache as dependency for map controller 2017-05-09 11:58:48 +02:00
Mario de Frutos
3f6c8fa51c Use windshaft-stats to get layer stats 2017-05-08 18:42:40 +02:00
Raul Ochoa
0ac53db73a Stubs next version 2017-05-05 16:04:25 +02:00
Raul Ochoa
36e9239056 Release 3.6.4 2017-05-05 16:03:53 +02:00
Raul Ochoa
4e6e267f10 Fix news 2017-05-05 16:03:38 +02:00
Raul Ochoa
2c235b6629 Merge pull request #680 from CartoDB/upgrade-deps
Upgrade deps
2017-05-05 16:02:24 +02:00
Raul Ochoa
6bd7537467 Upgrade deps 2017-05-05 15:57:02 +02:00
Daniel García Aubert
55a351d751 Point windshaft to a specific buffer-size commit 2017-05-03 17:35:55 +02:00
Daniel García Aubert
05d3b3bf66 Point windshaft to specific commit for buffer-size configuration 2017-05-03 17:21:08 +02:00
Daniel García Aubert
e97466378e Add test for different formats to anonymous maps and named maps with placeholders 2017-05-03 11:17:51 +02:00
csobier
8426dd00f1 added print attributions from Docs FAQs to Static Maps API content 2017-05-02 12:48:55 -04:00
Daniel García Aubert
b2b6cf1f02 Merge branch '665-buffer-size' of github.com:CartoDB/Windshaft-cartodb into 665-buffer-size 2017-04-28 19:23:49 +02:00
Daniel García Aubert
c9af38ecd0 Fix issue when 'grid.json' format is not captured properly due to a weird behaviour in regex 2017-04-28 19:21:51 +02:00
Mario de Frutos
be58adb1b9 Be able to override buffer-size configuration without placeholders in named maps 2017-04-28 19:20:00 +02:00
Mario de Frutos
bfb283c5ba wip 2017-04-28 14:46:36 +02:00
Mario de Frutos
332a56b736 Mapconfig only support object for the buffer-size property 2017-04-28 14:22:16 +02:00
Daniel García Aubert
2f4e4246a4 Refactor test-client in order to use same interface for named and anonymous maps 2017-04-26 18:27:18 +02:00
Daniel García Aubert
c481d6473c Use parseInt instead of number constructor 2017-04-26 17:01:21 +02:00
Daniel García Aubert
40c0e306af Remove invalid assertions 2017-04-25 20:40:17 +02:00
Daniel García Aubert
0d840e6daf Javascript style typo 2017-04-25 19:41:30 +02:00
Daniel García Aubert
07e507e1aa Remove dictionary as placeholder type for named maps 2017-04-25 19:40:12 +02:00
Mario de Frutos
7ea7a991aa Buffersize customizable through named maps' placeholders 2017-04-25 19:27:31 +02:00
Daniel García Aubert
0577fa5308 Add test 2017-04-25 17:54:31 +02:00
Daniel García Aubert
f29ee1b4ac Add test to use placeholder buffer-size value 2017-04-25 15:48:23 +02:00
Daniel García Aubert
0c08713521 First attempt: support buffer-size configuration for named maps 2017-04-25 14:34:17 +02:00
Raul Ochoa
567928a7f5 Stubs next version 2017-04-25 12:36:29 +02:00
Raul Ochoa
ae9e211f30 Release 3.6.3 2017-04-25 12:35:34 +02:00
Raul Ochoa
b5b75df91a Merge pull request #674 from CartoDB/upgrade-windshaft
Upgrades windshaft to 3.1.1
2017-04-25 12:33:22 +02:00
Raul Ochoa
8ddccc0b0c Upgrades windshaft to 3.1.1 2017-04-25 12:19:49 +02:00
Daniel García Aubert
383a1a330a Test with buffer-size 0 2017-04-25 10:43:07 +02:00
Raul Ochoa
95195fff6f Stubs next version 2017-04-24 19:33:03 +02:00
Raul Ochoa
93b77dc4c1 Release 3.6.2 2017-04-24 19:32:10 +02:00
Raul Ochoa
4aee7fb1b8 Merge pull request #671 from CartoDB/upgrade-deps
Upgrades grainstore to 1.6.3
2017-04-24 19:31:20 +02:00
Raul Ochoa
a6d68dba5e Upgrades grainstore to 1.6.3 2017-04-24 19:11:53 +02:00
Daniel García Aubert
109c550187 Remove filter 2017-04-24 18:57:20 +02:00
Daniel García Aubert
06353941e6 Implement test to exercise buffer-size configuration by format 2017-04-24 18:56:15 +02:00
Daniel García Aubert
fed953d195 Support mvt tiles 2017-04-24 18:55:08 +02:00
Raul Ochoa
883f87c7c8 Document gc_interval configuration entry 2017-04-24 18:28:51 +02:00
Raul Ochoa
14d37268d6 Stubs next version 2017-04-24 14:44:38 +02:00
Raul Ochoa
4b6181039d Release 3.6.1 2017-04-24 14:43:46 +02:00
Raul Ochoa
47944671c6 fix release date 2017-04-24 14:43:23 +02:00
Raul Ochoa
f33a7dd665 Merge pull request #668 from CartoDB/upgrade-camshaft
Upgrade camshaft to 0.54.1
2017-04-24 14:41:06 +02:00
Raul Ochoa
781e5a71bf Upgrade camshaft to 0.54.1 2017-04-24 12:29:08 +02:00
Raul Ochoa
c4ff884ad0 Merge pull request #666 from CartoDB/upgrade-camshaft
Upgrades camshaft to 0.54.0
2017-04-20 18:33:07 +02:00
Raul Ochoa
02b9f85b16 Upgrades camshaft to 0.54.0 2017-04-20 18:25:57 +02:00
Daniel García Aubert
2756252368 Stubs next version 2017-04-11 19:23:04 +02:00
Daniel García Aubert
a386abf5a5 Release 3.5.1 2017-04-11 18:34:18 +02:00
Daniel
e5c2c35a81 Merge pull request #662 from CartoDB/upgrade-camshaft-to-0.53.1
Upgrade camshaft to 0.53.1
2017-04-11 18:31:49 +02:00
Daniel García Aubert
227112c7aa Update yarn.lock 2017-04-11 18:27:54 +02:00
Daniel García Aubert
a4ed37bdfc Upgrade camshaft to 0.53.1 2017-04-11 18:19:39 +02:00
Daniel García Aubert
c6a62cee61 Stubs next version 2017-04-10 16:13:30 +02:00
Daniel García Aubert
891bc818b2 Release 3.5.0 2017-04-10 12:20:44 +02:00
Daniel
ebe25d6f20 Merge pull request #638 from CartoDB/629_fix_cache_invalidation
Fix cache invalidation problem with affected tables
2017-04-10 11:55:57 +02:00
Daniel García Aubert
92ec17218b Upgrade camshaft to 0.53.0 2017-04-10 11:36:17 +02:00
Mario de Frutos
e8a0f6b7b6 Point to camshaft branch to test properly 2017-04-07 16:00:29 +02:00
Mario de Frutos
125c39967c Make the cache headers tests idempotent 2017-04-07 15:59:58 +02:00
Mario de Frutos
4132bc755d Add cdb_invalidate_varnish function fixture to tests 2017-04-07 15:59:58 +02:00
Mario de Frutos
9707881bf9 Include check for surrogate-key header and renamed the test file 2017-04-07 15:59:58 +02:00
Mario de Frutos
fa6493ae44 Affected tables are now included in X-Cache-Channel 2017-04-07 15:59:58 +02:00
Mario de Frutos
0c387cf6d9 Add more tests for x-cache-channel but with analysis 2017-04-07 15:59:58 +02:00
Mario de Frutos
5e4d1d5c1c Get affected tables and add it to the layergroup 2017-04-07 15:59:58 +02:00
Raul Ochoa
4d82fd65f6 Merge pull request #659 from CartoDB/upgrade-camshaft
Upgrades camshaft to 0.52.0
2017-04-07 14:41:14 +02:00
Raul Ochoa
6d3644f13b Upgrades camshaft to 0.52.0 2017-04-07 13:29:30 +02:00
Raul Ochoa
7a5aa7ba35 Stubs next version 2017-04-03 16:22:15 +02:00
Raul Ochoa
9c9609eb2b Release 3.4.0 2017-04-03 16:21:29 +02:00
Raul Ochoa
418d3c074f Merge pull request #657 from CartoDB/upgrade-camshaft
Upgrade camshaft to 0.51.0
2017-04-03 16:15:48 +02:00
Raul Ochoa
6bbda3d41e Upgrade camshaft to 0.51.0 2017-04-03 15:53:39 +02:00
Raul Ochoa
25669bb3f2 Stubs next version 2017-04-03 13:07:53 +02:00
Raul Ochoa
508d495a23 Release 3.3.0 2017-04-03 13:07:05 +02:00
Raul Ochoa
06427dc009 Bump version and add news 2017-04-03 13:06:37 +02:00
Raul Ochoa
c325df1414 Merge pull request #655 from CartoDB/static-maps-layers-filter
Static maps layers filter
2017-04-03 12:07:38 +02:00
Raul Ochoa
07447160e3 Merge pull request #654 from CartoDB/non-conditional-profiler
Remove all conditional branches to call req.profiler
2017-04-03 11:58:38 +02:00
Raul Ochoa
ededc73fd7 Throw on invalid params argument 2017-03-31 18:39:29 +02:00
Raul Ochoa
cad02bfad7 Remove all conditional branches to call req.profiler
req.profiler is created in a middleware for all requests.
2017-03-30 20:31:53 +02:00
Raul Ochoa
94299f0452 Configure extra allowed params per endpoint via middleware
Instead of making all params available in all endpoints, we control
what endpoints allow what extra params.

Dataviews endpoints should be migrated to this.
2017-03-30 20:12:55 +02:00
Raul Ochoa
ae5d82c41d Add test to go red 2017-03-30 20:12:20 +02:00
Raul Ochoa
6468822295 Remove layer param before creating a better solution 2017-03-30 20:08:45 +02:00
Raul Ochoa
777ae31426 Merge branch 'master' into static-maps-layers-filter 2017-03-30 19:33:59 +02:00
Raul Ochoa
1ca56fb81c Update news 2017-03-30 17:52:25 +02:00
Raul Ochoa
5d74e1eafe Stubs next version 2017-03-30 14:33:41 +02:00
Raul Ochoa
f3fdd7ff25 Release 3.2.0 2017-03-30 14:32:48 +02:00
Raul Ochoa
fbbe69dac0 Merge pull request #652 from CartoDB/upgrade-windshaft
Update windshaft to 3.1.0
2017-03-30 14:32:21 +02:00
Raul Ochoa
ac54179f14 Update windshaft to 3.1.0 2017-03-30 14:12:31 +02:00
Raul Ochoa
50d296e46c Merge pull request #650 from CartoDB/upgrade-grainstore-1.6.2
Upgrade dependencies: grainstore@1.6.2
2017-03-29 16:16:50 +02:00
Raul Ochoa
616ba6500c Merge pull request #649 from CartoDB/gc
Active GC interval
2017-03-29 16:11:48 +02:00
Daniel García Aubert
d9968f2c91 Upgrade dependencies: grainstore@1.6.2 2017-03-29 16:02:37 +02:00
Raul Ochoa
8ca9c5bcf7 Active GC interval
Interval timer is configurable, disabling by using <=0 value.
2017-03-29 15:56:30 +02:00
Daniel García Aubert
6a4b412cd3 Avoid lint 2017-03-27 18:27:55 +02:00
Daniel García Aubert
2374711d63 Remove reference to issue 2017-03-24 17:34:05 +01:00
Daniel García Aubert
213a3e297c Check node version 2017-03-24 17:28:23 +01:00
csobier
a7b0618f91 Merge pull request #643 from CartoDB/docs-fix-broken-hyperlinks
fixed docs example links, Andy changed his user name so links broke. …
2017-03-24 09:44:50 -04:00
csobier
e9896e34e1 found and fixed a typo 2017-03-24 09:33:15 -04:00
csobier
28bd03765a fixed docs example links, Andy changed his user name so links broke. I fixed. 2017-03-24 09:04:31 -04:00
Daniel García Aubert
24a86ae8df Stubs next version 2017-03-23 11:04:12 +01:00
Daniel García Aubert
f5c349e105 Release 3.1.1 2017-03-23 11:02:04 +01:00
Raul Ochoa
e8d2e28dba Merge pull request #642 from CartoDB/resources-urls-crc-hash
Use crc32 instead of md5 for computing subdomain candidate
2017-03-23 01:21:50 +01:00
Raul Ochoa
e0c2423ace Remove unused import 2017-03-23 01:14:56 +01:00
Raul Ochoa
5e429ba71f Use crc32 instead of md5 for computing subdomain candidate 2017-03-23 01:03:45 +01:00
Daniel García Aubert
64dfdba94d Release 3.1.0 2017-03-22 19:20:24 +01:00
Daniel
3866413504 Merge pull request #641 from CartoDB/resource-urls
Generate URLs for resources based on CDN + template rules
2017-03-22 19:15:08 +01:00
Raul Ochoa
2da834784f Generate URLs for resources based on CDN + template rules 2017-03-22 18:58:37 +01:00
Daniel García Aubert
d6181da32b Stubs next version 2017-03-22 13:53:48 +01:00
Daniel García Aubert
8287b94a25 Release 3.0.2 2017-03-22 13:20:59 +01:00
Daniel García Aubert
bc633301fe Update HOWTO_RELEASE 2017-03-22 13:11:12 +01:00
Daniel García Aubert
ed94fb4a66 Define yarn version of clients that must be used 2017-03-22 13:10:06 +01:00
Daniel García Aubert
fc27086052 Remove script to generate shrinkwrap 2017-03-22 13:08:12 +01:00
Daniel García Aubert
de1d1961e3 Upgrade yarn.lock depdendencies: depth > 1 2017-03-22 12:55:22 +01:00
Daniel García Aubert
a90a9383b4 Stubs next version 2017-03-21 19:45:25 +01:00
Daniel García Aubert
fd244287d5 Release 3.0.1 2017-03-21 19:43:46 +01:00
Daniel
fafe9e7e8a Merge pull request #640 from CartoDB/upgrade-windshaft-3.0.1
Bump windshaft version to 3.0.1
2017-03-21 19:35:54 +01:00
Daniel García Aubert
db37513206 Bump windshaft version to 3.0.1 2017-03-21 19:15:41 +01:00
Daniel García Aubert
c023088a3f Stubs next version 2017-03-21 15:44:33 +01:00
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
8694c120bc Allow to overwrite layers filter in static maps images 2017-03-15 11:00:10 +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
221 changed files with 20195 additions and 9037 deletions

6
.gitignore vendored
View File

@@ -2,10 +2,12 @@ node_modules*
config.status*
config/environments/*.js
.idea
.vscode
.nvmrc
tools/munin/windshaft.conf
logs/
pids/
redis.pid
test.log
npm-debug.log
*.log
coverage/
.DS_Store

View File

@@ -1,4 +1,3 @@
test/results/
test/monkey/
test/benchmark.js
test/support/

View File

@@ -40,7 +40,7 @@
"debug" : false, // true: Allow debugger statements e.g. browser breakpoints.
// "eqnull" : false, // true: Tolerate use of `== null`
// "es5" : false, // true: Allow ES5 syntax (ex: getters and setters)
// "esnext" : false, // true: Allow ES.next (ES6) syntax (ex: `const`)
"esnext" : true, // true: Allow ES.next (ES6) syntax (ex: `const`)
// "moz" : false, // true: Allow Mozilla specific syntax (extends and overrides esnext features)
// // (ex: `for each`, multiple try/catch, function expression…)
// "evil" : false, // true: Tolerate use of `eval` and `new Function()`

View File

@@ -1,35 +1,14 @@
addons:
apt:
packages:
- pkg-config
- libcairo2-dev
- libjpeg8-dev
- libgif-dev
sudo: required
dist: trusty
services:
- docker
before_install:
- npm install -g npm@2
- lsb_release -a
- sudo apt-get -qq purge postgis* postgresql*
- sudo rm -Rf /var/lib/postgresql /etc/postgresql
- sudo apt-add-repository --yes ppa:cartodb/postgresql-9.5
- sudo apt-add-repository --yes ppa:cartodb/gis
- sudo apt-get update
- sudo apt-get install -q --force-yes postgresql-9.5-postgis-2.2 postgresql-plpython-9.5
- echo -e "local\tall\tall\ttrust\nhost\tall\tall\t127.0.0.1/32\ttrust\nhost\tall\tall\t::1/128\ttrust" |sudo tee /etc/postgresql/9.5/main/pg_hba.conf
- sudo service postgresql restart
- createdb template_postgis
- createuser publicuser
- psql -c "CREATE EXTENSION postgis" template_postgis
- docker pull cartoimages/windshaft-testing
env:
- NPROCS=1 JOBS=1 PGUSER=postgres
script:
- docker run -e POSTGIS_VERSION=2.4 -v `pwd`:/srv cartoimages/windshaft-testing bash docker-test.sh
language: node_js
node_js:
- "0.10"
language: generic
notifications:
irc:
channels:
- "irc.freenode.org#cartodb"
use_notice: true

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`
5. Commit package.json, npm-shrinwrap.json, NEWS
4. If there are modified dependencies in package.json, update them with `yarn upgrade {{package_name}}@{{version}}`
5. Commit package.json, yarn.lock, 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.27.5 <1.0.0
- 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,7 @@ all:
@$(SHELL) ./scripts/install.sh
clean:
rm -rf node_modules/*
rm -rf node_modules/
distclean: clean
rm config.status*
@@ -16,7 +16,7 @@ config.status--test:
./configure --environment=test
config/environments/test.js: config.status--test
./config.status--test
./config.status--test
TEST_SUITE := $(shell find test/{acceptance,integration,unit} -name "*.js")
TEST_SUITE_UNIT := $(shell find test/unit -name "*.js")

801
NEWS.md
View File

@@ -1,5 +1,806 @@
# Changelog
## 5.2.0
Released 2018-02-01
Announcements:
- Upgrade windshaft to [4.3.3](https://github.com/CartoDB/windshaft/releases/tag/4.3.2) adding support for cache-features' in Mapnik/CartoDB layers.
## 5.1.0
Released 2018-01-30
New features:
- Now mapnik has support for fine-grained metrics.
- Variables can be passed for later substitution in postgis datasource.
Announcements:
- Upgrade windshaft to [4.3.1](https://github.com/CartoDB/windshaft/releases/tag/4.3.1). Underneath it upgrades mapnik and all the related dependencies.
## 5.0.1
Released 2018-01-29
Bug Fixes:
- Allow aggregation for queries with no the_geom (only the_geom_webmercator) #856
## 5.0.0
Released 2018-01-29
Backward incompatible changes:
- Aggregation dataview returns categories with the same type as the database type. For example, if we are aggretating by a numeric field, the resulting JSON will contain a number instead of a stringified number.
## 4.8.0
Released 2018-01-04
New features:
- Return url template in metadata #838.
Bux fixes:
- Tests: Order torque objects before comparison
## 4.7.0
Released 2018-01-03
New features:
- Return tilejson in metadata #837.
Bug fixes:
- Allow to create vector map-config for layers that doesn't have points. Layers with lines or polygons won't be aggregated by default.
## 4.6.0
Released 2018-01-02
Announcements:
- Upgrades windshaft to [4.2.0](https://github.com/CartoDB/windshaft/releases/tag/4.2.0).
- Validate aggregation input params.
- Fix column names collisions in histograms [#828](https://github.com/CartoDB/Windshaft-cartodb/pull/828).
- Add full-sample aggregation support for vector map-config.
## 4.5.0
Released 2017-12-19
Announcements:
- Date histograms: Add second, decade, century and millenium aggregations
- Date histograms: Switch the auto threshold from 366 buckets to 100.
- Logging all errors.
- Add support for aggregated visualizations.
- Allow vector-only map-config creation.
- Histograms: Now they accept a `no_filters` parameter.
## 4.4.0
Released 2017-12-12
Announcements:
- Upgrades camshaft to [0.60.0](https://github.com/CartoDB/camshaft/releases/tag/0.60.0).
## 4.3.1
Released 2017-12-12
Bug fix:
- Fixed bug introduced in version 4.0.1 that brokes the static map generation using JPG as format #808
## 4.3.0
Released 2017-12-11
Announcements:
- Optimize Formula queries.
- Optimize Formula queries in overviews.
- Optimize Numeric Histogram queries.
- Optimize Date Histogram queries.
- Date Histograms: Now returns the same value for max/min/avg/timestamp per bin.
- Date Histograms: Now it should return the same no matter the DB/Client time zone.
## 4.2.0
Released 2017-12-04
Announcements:
- Allow to request MVT tiles without CartoCSS
- Upgrades windshaft to [4.1.0](https://github.com/CartoDB/windshaft/releases/tag/4.1.0).
## 4.1.1
Released 2017-11-29
Announcements:
- Upgrades turbo-carto to [0.20.2](https://github.com/CartoDB/turbo-carto/releases/tag/0.20.2).
## 4.1.0
Released 2017-mm-dd
Announcements:
- Upgrades windshaft to [4.0.1](https://github.com/CartoDB/windshaft/releases/tag/4.0.1).
- Add `categories` query param to define the number of categories to be ranked for aggregation dataviews.
## 4.0.1
Released 2017-10-18
Announcements:
- Upgrades camshaft to [0.59.4](https://github.com/CartoDB/camshaft/releases/tag/0.59.4).
- Upgrades windshaft to [4.0.0](https://github.com/CartoDB/windshaft/releases/tag/4.0.0).
- Split and move `req2params` method to multiple middlewares.
- Use express error handler middleware to respond in case of something went wrong.
- Use `res.locals` object to share info between middlewares and leave `req.params` as an object containing properties mapped to the named route params.
- Move `LZMA` decompression to its own middleware.
- Implement stats middleware removing some duplicated code while sending response.
## 4.0.0
Released 2017-10-04
Backward incompatible changes:
- Removes `list` dataview type.
Announcements:
- Upgrades body-parser to 1.18.2.
- Upgrades express to 4.16.0.
- Upgrades debug to 3.1.0.
- Upgrades request to 2.83.0.
- Upgrades turbo-carto to [0.20.1](https://github.com/CartoDB/turbo-carto/releases/tag/0.20.1)
- Upgrades cartodb-psql to [0.10.2](https://github.com/CartoDB/node-cartodb-psql/releases/tag/0.10.2).
- Upgrades camshaft to [0.59.2](https://github.com/CartoDB/camshaft/releases/tag/0.59.2).
- Upgrades windshaft to [3.3.3](https://github.com/CartoDB/windshaft/releases/tag/3.3.3).
- Upgrades yarn minimum version requirement to v0.27.5
## 3.13.0
Released 2017-10-02
- Upgrades camshaft, cartodb-query-tables, and turbo-carto: better support for query variables.
Bugfixes:
- Bounding box parameter ignored in static named maps #735.
- camhaft 0.59.1 fixes duplicate columns in aggregate-intersection analysis
## 3.12.10
Released 2017-09-18
- Upgrades windshaft to [3.3.2](https://github.com/CartoDB/windshaft/releases/tag/3.3.2).
## 3.12.9
Released 2017-09-07
Bug fixes:
- Do not use distinct when calculating quantiles. #743
## 3.12.8
Released 2017-09-07
Bug fixes:
- Integer out of range in date histograms. (https://github.com/CartoDB/support/issues/962)
## 3.12.7
Released 2017-09-01
- Upgrades camshaft to [0.58.1](https://github.com/CartoDB/camshaft/releases/tag/0.58.1).
## 3.12.6
Released 2017-08-31
- Upgrades camshaft to [0.58.0](https://github.com/CartoDB/camshaft/releases/tag/0.58.0).
## 3.12.5
Released 2017-08-24
- Upgrades camshaft to [0.57.0](https://github.com/CartoDB/camshaft/releases/tag/0.57.0).
## 3.12.4
Released 2017-08-23
Announcements:
- Upgrades camshaft to [0.56.0](https://github.com/CartoDB/camshaft/releases/tag/0.56.0).
## 3.12.3
Released 2017-08-22
Announcements:
- Upgrades camshaft to [0.55.8](https://github.com/CartoDB/camshaft/releases/tag/0.55.8).
## 3.12.2
Released 2017-08-16
Bug fixes:
- Polygon count problems #725.
## 3.12.1
Released 2017-08-13
- Upgrades cartodb-psql to [0.10.1](https://github.com/CartoDB/node-cartodb-psql/releases/tag/0.10.1).
- Upgrades windshaft to [3.3.1](https://github.com/CartoDB/windshaft/releases/tag/3.3.1).
- Upgrades camshaft to [0.55.7](https://github.com/CartoDB/camshaft/releases/tag/0.55.7).
## 3.12.0
Released 2017-08-10
Announcements:
- Apply max tile response time for requests to layergoup, tiles, static maps, attributes and dataviews services #717.
- Upgrades windshaft to [3.3.0](https://github.com/CartoDB/windshaft/releases/tag/3.3.0).
- Upgrades cartodb-redis to [0.14.0](https://github.com/CartoDB/node-cartodb-redis/releases/tag/0.14.0).
## 3.11.0
Released 2017-08-08
Announcements:
- Allow to override with any aggregation for histograms instantiated w/o aggregation.
Bug fixes:
- Apply timezone after truncating the minimun date for each bin to calculate timestamps in time-series.
- Support timestamp with timezones to calculate the number of bins in time-series.
- Fixed issue related to name collision while building time-series query.
## 3.10.1
Released 2017-08-04
Bug fixes:
- Exclude Infinities & NaNs from ramps #719.
- Fixed issue in time-series when aggregation starts at 1970-01-01 (epoch) #720.
## 3.10.0
Released 2017-08-03
Announcements:
- Improve time-series dataview, now supports date aggregations (e.g: daily, weekly, monthly, etc.) and timezones (UTC by default) #698.
- Support special numeric values (±Infinity, NaN) for json responses #706
## 3.9.8
Released 2017-07-21
- Upgrades windshaft to [3.2.2](https://github.com/CartoDB/windshaft/releases/tag/3.2.2).
## 3.9.7
Released 2017-07-20
Bug fixes:
- Respond with 204 (No content) when vector tile has no data #712
Announcements:
- Upgrades turbo-carto to [0.19.2](https://github.com/CartoDB/turbo-carto/releases/tag/0.19.2)
## 3.9.6
Released 2017-07-11
- Dataviews: support for aggregation in search results #708
## 3.9.5
Released 2017-06-27
- Dataviews: support special numeric values (±Infinity, NaN) #700
## 3.9.4
Released 2017-06-22
Announcements:
- Upgrades camshaft to [0.55.6](https://github.com/CartoDB/camshaft/releases/tag/0.55.6).
## 3.9.3
Released 2017-06-16
Announcements:
- Upgrades camshaft to [0.55.5](https://github.com/CartoDB/camshaft/releases/tag/0.55.5).
## 3.9.2
Released 2017-06-16
Announcements:
- Upgrades camshaft to [0.55.4](https://github.com/CartoDB/camshaft/releases/tag/0.55.4).
## 3.9.1
Released 2017-06-06
Announcements:
- Upgrades camshaft to [0.55.3](https://github.com/CartoDB/camshaft/releases/tag/0.55.3).
## 3.9.0
Released 2017-05-31
Announcements:
- Upgrades windshaft to [3.2.1](https://github.com/CartoDB/windshaft/releases/tag/3.2.1).
- Add support to retrieve info about layer stats in map instantiation.
- Upgrades camshaft to [0.55.2](https://github.com/CartoDB/camshaft/releases/tag/0.55.2).
- Remove promise polyfill from turbo-carto adapter
## 3.8.0
Released 2017-05-22
Announcements:
- Upgrades camshaft to [0.55.0](https://github.com/CartoDB/camshaft/releases/tag/0.55.0).
- Upgrades turbo-carto to [0.19.1](https://github.com/CartoDB/turbo-carto/releases/tag/0.19.1)
## 3.7.1
Released 2017-05-18
Bug fixes:
- Fix buffersize assignment when is not defined in requested mapconfig.
## 3.7.0
Released 2017-05-18
Announcements:
- Manage multiple values of buffer-size for different formats
- Upgrades windshaft to [3.2.0](https://github.com/CartoDB/windshaft/releases/tag/3.2.0).
## 3.6.6
Released 2017-05-11
Announcements:
- Upgrades camshaft to [0.54.4](https://github.com/CartoDB/camshaft/releases/tag/0.54.4).
## 3.6.5
Released 2017-05-09
Announcements:
- Upgrades camshaft to [0.54.3](https://github.com/CartoDB/camshaft/releases/tag/0.54.3).
## 3.6.4
Released 2017-05-05
Announcements:
- Upgrade cartodb-psql to [0.8.0](https://github.com/CartoDB/node-cartodb-psql/releases/tag/0.8.0).
- Upgrades camshaft to [0.54.2](https://github.com/CartoDB/camshaft/releases/tag/0.54.2).
- Upgrades windshaft to [3.1.2](https://github.com/CartoDB/windshaft/releases/tag/3.1.2).
## 3.6.3
Released 2017-04-25
Announcements:
- Upgrades windshaft to [3.1.1](https://github.com/CartoDB/windshaft/releases/tag/3.1.1).
## 3.6.2
Released 2017-04-24
Announcements:
- Upgrades grainstore to [1.6.3](https://github.com/CartoDB/grainstore/releases/tag/1.6.3).
## 3.6.1
Released 2017-04-24
Announcements:
- Upgrades camshaft to [0.54.1](https://github.com/CartoDB/camshaft/releases/tag/0.54.1).
## 3.6.0
Released 2017-04-20
Announcements:
- Upgrades camshaft to [0.54.0](https://github.com/CartoDB/camshaft/releases/tag/0.54.0).
## 3.5.1
Released 2017-04-11
Announcements:
- Upgrades camshaft to [0.53.1](https://github.com/CartoDB/camshaft/releases/tag/0.53.1).
## 3.5.0
Released 2017-04-10
Bug fixes:
- Fix invalidation of cache for maps with analyses #638.
Announcements:
- Upgrades camshaft to [0.53.0](https://github.com/CartoDB/camshaft/releases/tag/0.53.0).
## 3.4.0
Released 2017-04-03
Announcements:
- Upgrades camshaft to [0.51.0](https://github.com/CartoDB/camshaft/releases/tag/0.51.0).
## 3.3.0
Released 2017-04-03
New features:
- Static map endpoints allow specifying the layers to render #653.
## 3.2.0
Released 2017-03-30
Announcements:
- Upgrades windshaft to [3.1.0](https://github.com/CartoDB/windshaft/releases/tag/3.1.0).
- Active GC interval.
## 3.1.1
Released 2017-03-23
Bug fixes:
- Use crc32 instead of md5 for computing subdomain candidate #642
## 3.1.0
Released 2017-03-22
Features:
- Generate URLs for resources based on CDN and template rules
## 3.0.2
Released 2017-03-22
Bug fixes:
- Upgrade dependencies
- Improve docs: remove mentions to NPM and use yarn instead
- Remove script to generate npm-shrinkwrap file
## 3.0.1
Released 2017-03-21
Announcements:
- Upgrades windshaft to [3.0.1](https://github.com/CartoDB/windshaft/releases/tag/3.0.1).
## 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

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
```

109
app.js
View File

@@ -2,23 +2,48 @@ var http = require('http');
var https = require('https');
var path = require('path');
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';
}
var semver = require('semver');
const setICUEnvVariable = require('./lib/cartodb/utils/icu_data_env_setter');
// jshint undef:false
var log = console.log.bind(console);
var logError = console.error.bind(console);
// jshint undef:true
var nodejsVersion = process.versions.node;
if (!semver.satisfies(nodejsVersion, '>=6.9.0')) {
logError(`Node version ${nodejsVersion} is not supported, please use Node.js 6.9 or higher.`);
process.exit(1);
}
// This function should be called before the require('yargs').
setICUEnvVariable();
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 +58,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 +73,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 +120,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 +137,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');
});
@@ -123,3 +146,17 @@ process.on('SIGHUP', function() {
process.on('uncaughtException', function(err) {
global.logger.error('Uncaught exception: ' + err.stack);
});
if (global.gc) {
var gcInterval = Number.isFinite(global.environment.gc_interval) ?
global.environment.gc_interval :
10000;
if (gcInterval > 0) {
setInterval(function gcForcedCycle() {
var start = Date.now();
global.gc();
global.statsClient.timing('windshaft.gc', Date.now() - start);
}, gcInterval);
}
}

Binary file not shown.

View File

@@ -6,6 +6,9 @@ var config = {
// Its default size is 4, but it can be changed at startup time (the absolute maximum is 128).
// See http://docs.libuv.org/en/latest/threadpool.html
,uv_threadpool_size: undefined
// Time in milliseconds to force GC cycle.
// Disable by using <=0 value.
,gc_interval: 10000
// Regular expression pattern to extract username
// from hostname. Must have a single grabbing block.
,user_from_host: '^(.*)\\.localhost'
@@ -23,6 +26,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
@@ -37,7 +55,7 @@ var config = {
,socket_timeout: 600000
,enable_cors: true
,cache_enabled: false
,log_format: ':req[X-Real-IP] :method :req[Host]:url :status :response-time ms -> :res[Content-Type] (:res[X-Tiler-Profiler])'
,log_format: ':req[X-Real-IP] :method :req[Host]:url :status :response-time ms -> :res[Content-Type] (:res[X-Tiler-Profiler]) (:res[X-Tiler-Errors])'
// If log_filename is given logs will be written
// there, in append mode. Otherwise stdout is used (default).
// Log file will be re-opened on receiving the HUP signal
@@ -89,6 +107,20 @@ var config = {
// Milliseconds since last access before renderer cache item expires
cache_ttl: 60000,
statsInterval: 5000, // milliseconds between each report to statsd about number of renderers and mapnik pool status
mvt: {
//If enabled, MVTs will be generated with PostGIS directly, instead of using Mapnik,
//PostGIS 2.4 is required for this to work
//If disabled it will use Mapnik MVT generation
usePostGIS: false,
dbPoolParams: {
// maximum number of resources to create at any given time
size: 16,
// max milliseconds a resource can go unused before it should be destroyed
idleTimeout: 3000,
// frequency to check for idle resources
reapInterval: 1000
}
},
mapnik: {
// The size of the pool of internal mapnik backend
// This pool size is per mapnik renderer created in Windshaft's RendererFactory
@@ -96,6 +128,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
@@ -168,7 +204,11 @@ var config = {
// which cost is no more expensive than snapping and results are
// much closer to the original geometry
removeRepeatedPoints: false // this requires postgis >=2.2
}
},
// If enabled Mapnik will reuse the features retrieved from the database
// instead of requesting them once per style inside a layer
'cache-features': true
},
http: {
@@ -210,6 +250,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: {
@@ -290,8 +342,7 @@ var config = {
// whether the affected tables for a given SQL must query directly postgresql or use the SQL API
cdbQueryTablesFromPostgres: true,
// whether in mapconfig is available stats & metadata for each layer
layerMetadata: true
layerStats: true
}
};

View File

@@ -6,6 +6,9 @@ var config = {
// Its default size is 4, but it can be changed at startup time (the absolute maximum is 128).
// See http://docs.libuv.org/en/latest/threadpool.html
,uv_threadpool_size: undefined
// Time in milliseconds to force GC cycle.
// Disable by using <=0 value.
,gc_interval: 10000
// Regular expression pattern to extract username
// from hostname. Must have a single grabbing block.
,user_from_host: '^(.*)\\.cartodb\\.com$'
@@ -23,6 +26,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
@@ -37,7 +55,7 @@ var config = {
,socket_timeout: 600000
,enable_cors: true
,cache_enabled: true
,log_format: ':req[X-Real-IP] :method :req[Host]:url :status :response-time ms -> :res[Content-Type] (:res[X-Tiler-Profiler])'
,log_format: ':req[X-Real-IP] :method :req[Host]:url :status :response-time ms -> :res[Content-Type] (:res[X-Tiler-Profiler]) (:res[X-Tiler-Errors])'
// If log_filename is given logs will be written
// there, in append mode. Otherwise stdout is used (default).
// Log file will be re-opened on receiving the HUP signal
@@ -83,6 +101,20 @@ var config = {
// Milliseconds since last access before renderer cache item expires
cache_ttl: 60000,
statsInterval: 5000, // milliseconds between each report to statsd about number of renderers and mapnik pool status
mvt: {
//If enabled, MVTs will be generated with PostGIS directly, instead of using Mapnik,
//PostGIS 2.4 is required for this to work
//If disabled it will use Mapnik MVT generation
usePostGIS: false,
dbPoolParams: {
// maximum number of resources to create at any given time
size: 16,
// max milliseconds a resource can go unused before it should be destroyed
idleTimeout: 3000,
// frequency to check for idle resources
reapInterval: 1000
}
},
mapnik: {
// The size of the pool of internal mapnik backend
// This pool size is per mapnik renderer created in Windshaft's RendererFactory
@@ -90,6 +122,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
@@ -162,7 +198,11 @@ var config = {
// which cost is no more expensive than snapping and results are
// much closer to the original geometry
removeRepeatedPoints: false // this requires postgis >=2.2
}
},
// If enabled Mapnik will reuse the features retrieved from the database
// instead of requesting them once per style inside a layer
'cache-features': true
},
http: {
@@ -204,6 +244,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: {
@@ -290,7 +342,7 @@ var config = {
// whether the affected tables for a given SQL must query directly postgresql or use the SQL API
cdbQueryTablesFromPostgres: true,
// whether in mapconfig is available stats & metadata for each layer
layerMetadata: false
layerStats: false
}
};

View File

@@ -6,6 +6,9 @@ var config = {
// Its default size is 4, but it can be changed at startup time (the absolute maximum is 128).
// See http://docs.libuv.org/en/latest/threadpool.html
,uv_threadpool_size: undefined
// Time in milliseconds to force GC cycle.
// Disable by using <=0 value.
,gc_interval: 10000
// Regular expression pattern to extract username
// from hostname. Must have a single grabbing block.
,user_from_host: '^(.*)\\.cartodb\\.com$'
@@ -23,6 +26,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
@@ -37,7 +55,7 @@ var config = {
,socket_timeout: 600000
,enable_cors: true
,cache_enabled: true
,log_format: ':req[X-Real-IP] :method :req[Host]:url :status :response-time ms (:res[X-Tiler-Profiler]) -> :res[Content-Type]'
,log_format: ':req[X-Real-IP] :method :req[Host]:url :status :response-time ms (:res[X-Tiler-Profiler]) -> :res[Content-Type] (:res[X-Tiler-Errors])'
// If log_filename is given logs will be written
// there, in append mode. Otherwise stdout is used (default).
// Log file will be re-opened on receiving the HUP signal
@@ -83,6 +101,20 @@ var config = {
// Milliseconds since last access before renderer cache item expires
cache_ttl: 60000,
statsInterval: 5000, // milliseconds between each report to statsd about number of renderers and mapnik pool status
mvt: {
//If enabled, MVTs will be generated with PostGIS directly, instead of using Mapnik,
//PostGIS 2.4 is required for this to work
//If disabled it will use Mapnik MVT generation
usePostGIS: false,
dbPoolParams: {
// maximum number of resources to create at any given time
size: 16,
// max milliseconds a resource can go unused before it should be destroyed
idleTimeout: 3000,
// frequency to check for idle resources
reapInterval: 1000
}
},
mapnik: {
// The size of the pool of internal mapnik backend
// This pool size is per mapnik renderer created in Windshaft's RendererFactory
@@ -90,6 +122,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
@@ -162,7 +198,11 @@ var config = {
// which cost is no more expensive than snapping and results are
// much closer to the original geometry
removeRepeatedPoints: false // this requires postgis >=2.2
}
},
// If enabled Mapnik will reuse the features retrieved from the database
// instead of requesting them once per style inside a layer
'cache-features': true
},
http: {
@@ -204,6 +244,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: {
@@ -290,7 +342,7 @@ var config = {
// whether the affected tables for a given SQL must query directly postgresql or use the SQL API
cdbQueryTablesFromPostgres: true,
// whether in mapconfig is available stats & metadata for each layer
layerMetadata: true
layerStats: true
}
};

View File

@@ -6,6 +6,9 @@ var config = {
// Its default size is 4, but it can be changed at startup time (the absolute maximum is 128).
// See http://docs.libuv.org/en/latest/threadpool.html
,uv_threadpool_size: undefined
// Time in milliseconds to force GC cycle.
// Disable by using <=0 value.
,gc_interval: 10000
// Regular expression pattern to extract username
// from hostname. Must have a single grabbing block.
,user_from_host: '(.*)'
@@ -23,6 +26,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
@@ -37,7 +54,7 @@ var config = {
,socket_timeout: 600000
,enable_cors: true
,cache_enabled: false
,log_format: '[:date] :req[X-Real-IP] :method :req[Host]:url :status :response-time ms -> :res[Content-Type] (:res[X-Tiler-Profiler])'
,log_format: '[:date] :req[X-Real-IP] :method :req[Host]:url :status :response-time ms -> :res[Content-Type] (:res[X-Tiler-Profiler]) (:res[X-Tiler-Errors])'
// If log_filename is given logs will be written
// there, in append mode. Otherwise stdout is used (default).
// Log file will be re-opened on receiving the HUP signal
@@ -83,6 +100,20 @@ var config = {
// Milliseconds since last access before renderer cache item expires
cache_ttl: 60000,
statsInterval: 5000, // milliseconds between each report to statsd about number of renderers and mapnik pool status
mvt: {
//If enabled, MVTs will be generated with PostGIS directly, instead of using Mapnik,
//PostGIS 2.4 is required for this to work
//If disabled it will use Mapnik MVT generation
usePostGIS: false,
dbPoolParams: {
// maximum number of resources to create at any given time
size: 16,
// max milliseconds a resource can go unused before it should be destroyed
idleTimeout: 3000,
// frequency to check for idle resources
reapInterval: 1000
}
},
mapnik: {
// The size of the pool of internal mapnik backend
// This pool size is per mapnik renderer created in Windshaft's RendererFactory
@@ -90,6 +121,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
@@ -162,7 +197,12 @@ var config = {
// which cost is no more expensive than snapping and results are
// much closer to the original geometry
removeRepeatedPoints: false // this requires postgis >=2.2
}
},
// If enabled Mapnik will reuse the features retrieved from the database
// instead of requesting them once per style inside a layer
'cache-features': true
},
http: {
timeout: 2000, // the timeout in ms for a http tile request
@@ -205,6 +245,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: {
@@ -285,7 +337,7 @@ var config = {
// whether the affected tables for a given SQL must query directly postgresql or use the SQL API
cdbQueryTablesFromPostgres: true,
// whether in mapconfig is available stats & metadata for each layer
layerMetadata: true
layerStats: true
}
};

11
docker-test.sh Normal file
View File

@@ -0,0 +1,11 @@
export NPROCS=1 && export JOBS=1 && export CXX=g++-4.9 && export PGUSER=postgres
npm install -g yarn@0.27.5
yarn
/etc/init.d/postgresql start
createdb template_postgis && createuser publicuser
psql -c "CREATE EXTENSION postgis" template_postgis
POSTGIS_VERSION=2.4 npm test

View File

@@ -17,3 +17,4 @@ You can create two types of maps with the Maps API:
* [Anonymous Maps](anonymous_maps.md)
* [Named Maps](named_maps.md)
* [Static Maps API](static_maps_api.md)
* [MapConfig File Format]([local file in the docs repo](https://github.com/CartoDB/docs/blob/master/_app/_mapsapi/06-mapconfig.md))

View File

@@ -0,0 +1,62 @@
# 1. Purpose
This specification describes an extension for
[MapConfig 1.7.0](https://github.com/CartoDB/Windshaft/blob/master/doc/MapConfig-1.7.0.md) version.
# 2. Changes over specification
This extension introduces a new layer options for aggregated data tile generation.
## 2.1 Aggregation options
The layer options attribute is extended with a new optional `aggregation` attribute.
The value of this attribute can be `false` to explicitly disable aggregation for the layer.
```javascript
{
aggregation: {
// OPTIONAL
// string, defines the placement of aggregated geometries. Can be one of:
// * "point-sample", the default places geometries at a sample point (one of the aggregated geometries)
// * "point-grid" places geometries at the center of the aggregation grid cells
// * "centroid" places geometriea at the average position of the aggregated points
// See https://github.com/CartoDB/Windshaft-cartodb/blob/master/docs/aggregation.md#placement for more details
placement: "point-sample",
// OPTIONAL
// object, defines the columns of the aggregated datasets. Each property corresponds to a columns name and
// should contain an object with two properties: "aggregate_function" (one of "sum", "max", "min", "avg", "mode" or "count"),
// and "aggregated_column" (the name of a column of the original layer query or "*")
// A column defined as `"_cdb_features_count": {"aggregate_function": "count", aggregated_column: "*"}`
// is always generated in addition to the defined columns.
// The column names `cartodb_id`, `the_geom`, `the_geom_webmercator` and `_cdb_feature_count` cannot be used
// for aggregated columns, as they correspond to columns always present in the result.
// See https://github.com/CartoDB/Windshaft-cartodb/blob/master/docs/aggregation.md#columns for more details
columns: {
"aggregated_column_1": {
"aggregate_function": "sum",
"aggregated_column": "original_column_1"
}
},
// OPTIONAL
// Number, defines the cell-size of the spatial aggregation grid as a pixel resolution power of two (1/4, 1/2,... 2, 4, 16)
// to scale from 256x256 pixels; the default is 1 corresponding to 256x256 cells per tile.
// See https://github.com/CartoDB/Windshaft-cartodb/blob/master/docs/aggregation.md#resolution for more details
resolution: 1,
// OPTIONAL
// Number, the minimum number of (estimated) rows in the dataset (query results) for aggregation to be applied.
// See https://github.com/CartoDB/Windshaft-cartodb/blob/master/docs/aggregation.md#threshold for more details
threshold: 500000
}
}
```
# History
## 1.0.0
- Initial version

View File

@@ -8,50 +8,13 @@ This specification describes an extension for
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.
It makes possible to get tabular data from analysis nodes: 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.
An aggregation is a list with aggregated results by a column and a given aggregation function.
Definition
```

187
docs/aggregation.md Normal file
View File

@@ -0,0 +1,187 @@
# Tile Aggregation
To be able to represent a large amount of data (say, hundred of thousands to millions of points) in a tile. This can be useful both for raster tiles (where the aggregation reduces the number of features to be rendered) and vector tiles (the tile contais less features).
Aggregation is available only for point geometries. During aggregation the points are grouped using a grid; all the points laying in the same cell of the grid are summarized in a single aggregated result point.
- The position of the aggregated point is controlled by the `placement` parameter.
- The aggregated rows always contain at least a column, named `_cdb_feature_count`, which contains the number of the original points that the aggregated point represents.
### Special default aggregation
When no placement or columns are specified a special default aggregation is performed.
This special mode performs only spatial aggregation (using a grid defined by the requested tile and the resolution, parameter, as all the other cases), and returns a _random_ record from each group (grid cell) with all its columns and an additional `_cdb_features_count` with the number of features in the group.
Regarding the randomness of the sample: currently we use the row with the minimum `cartodb_id` value in each group.
The rationale behind having this special aggregation with all the original columns is to provide a mostly transparent way to handle large datasets without having to provide special map configurations for those cases (i.e. preserving the logic used to produce the maps with smaller datasets). [Overviews have been used so far with this intent](https://carto.com/docs/tips-and-tricks/back-end-data-performance/), but they are inflexible.
### User defined aggregations
When either a explicit placement or columns are requested we no longer use the special, query; we use one determined by the placement (which will default to "centroid"), and it will have as columns only the aggregated columns specified, in addition to `_cdb_features_count`, which is always present.
We might decide in the future to allow sampling column values for any of the different placement modes.
### Behaviour for raster and vector tiles
The vector tiles from a vector-only map will be aggregated by default.
However, Raster tiles (or vector tiles from a map which defines CartoCSS styles) will be aggregated only upon request.
Aggregation that would otherwise occur can be disabled by passing an `aggregation=false` parameter to the map instantiation HTTP call.
To control how aggregation is performed, an aggregation option can be added to the layer:
```json
{
"layers": [
{
"options": {
"sql": "SELECT * FROM data",
"aggregation": {
"placement": "centroid",
"columns": {
"value": {
"aggregate_function": "sum",
"aggregated_column": "value"
}
}
}
}
}
]
}
```
Even if aggregation is explicitly requested it may not be activated, e.g., if the geometries are not points
or the whole dataset is too small. The map instantiation response contains metadata that informs if any particular
layer will be aggregated when tiles are requested, both for vector (mvt) and raster (png) tiles.
```json
{
"layergroupid": "7b97b6e76590fef889b63edd2efb1c79:1513608333045",
"metadata": {
"layers": [
{
"type": "mapnik",
"id": "layer0",
"meta": {
"stats": {
"estimatedFeatureCount": 6232136
},
"aggregation": {
"png": true,
"mvt": true
}
}
}
]
}
}
```
## Aggregation parameters
The aggregation parameters for a layer are defined inside an `aggregation` option of the layer:
```json
{
"layers": [
{
"options": {
"sql": "SELECT * FROM data",
"aggregation": {"...": "..."}
}
}
]
}
```
### `placement`
Determines the kind of aggregated geometry generated:
#### `point-sample`
This is the default placement. It will place the aggregated point at a random sample of the grouped points,
like the default aggregation does. No other attribute is sampled, though, the point will contain the aggregated attributes determined by the `columns` parameter.
#### `point-grid`
Generates points at the center of the aggregation grid cells (squares).
#### `centroid`
Generates points with the averaged coordinated of the grouped points (i.e. the points inside each grid cell).
### `columns`
The aggregated attributes defined by `columns` are computed by a applying an _aggregate function_ to all the points in each group.
Valid aggregate functions are `sum`, `avg` (average), `min` (minimum), `max` (maximum) and `mode` (the most frequent value in the group).
The values to be aggregated are defined by the _aggregated column_ of the source data. The column keys define the name of the resulting column in the aggregated dataset.
For example here we define three aggregate attributes named `total`, `max_price` and `price` which are all computed with the same column, `price`,
of the original dataset applying three different aggregate functions.
```json
{
"columns": {
"total": { "aggregate_function": "sum", "aggregated_column": "price" },
"max_price": { "aggregate_function": "max", "aggregated_column": "price" },
"price": { "aggregate_function": "avg", "aggregated_column": "price" }
}
}
```
> Note that you can use the original column names as names of the result, but all the result column names must be unique. In particular, the names `cartodb_id`, `the_geom`, `the_geom_webmercator` and `_cdb_feature_count` cannot be used for aggregated columns, as they correspond to columns always present in the result.
### `resolution`
Defines the cell-size of the spatial aggregation grid. This is equivalent to the [CartoCSS `-torque-resolution`](https://carto.com/docs/carto-engine/cartocss/properties-for-torque/#-torque-resolution-float) property of Torque maps.
The aggregation cells are `resolution`×`resolution` pixels in size, where pixels here are defined to be 1/256 of the (linear) size of a tile.
The default value is 1, so that aggregation coincides with raster pixels. A value of 2 would make each cell to be 4 (2×2) pixels, and a value of
0.5 would yield 4 cells per pixel. In teneral values less than 1 produce sub-pixel precision.
> Note that is independent of the number of pixels for raster tile or the coordinate resolution (mvt_extent) of vector tiles.
### `threshold`
This is the minimum number of (estimated) rows in the dataset (query results) for aggregation to be applied. If the number of rows estimate is less than the threshold aggregation will be disabled for the layer; the instantiation response will reflect that and tiles will be generated without aggregation.
### Example
```json
{
"version": "1.7.0",
"extent": [-20037508.5, -20037508.5, 20037508.5, 20037508.5],
"srid": 3857,
"maxzoom": 18,
"minzoom": 3,
"layers": [
{
"type": "mapnik",
"options": {
"sql": "select * from table",
"cartocss": "#table { marker-width: [total]; marker-fill: ramp(value, (red, green, blue), jenks); }",
"cartocss_version": "2.3.0",
"aggregation": {
"placement": "centroid",
"columns": {
"value": {
"aggregate_function": "avg",
"aggregated_column": "value"
},
"total": {
"aggregate_function": "sum",
"aggregated_column": "value"
}
},
"resolution": 2,
"threshold": 500000
}
}
}
]
}
```

View File

@@ -1,6 +1,7 @@
# Anonymous Maps
Anonymous Maps allows you to instantiate a map given SQL and CartoCSS. It also allows you to add interaction capabilities using [UTF Grid.](https://github.com/mapbox/utfgrid-spec)
Anonymous Maps allows you to instantiate a map given SQL and CartoCSS. It also allows you to add interaction capabilities using [UTF Grid.](https://github.com/mapbox/utfgrid-spec).
Alternatively, you can get the data for the map (geometry and attributes for each layer) using vector tiles (in which case CartoCSS is not required).
## Instantiate
@@ -41,6 +42,13 @@ updated_at | The ISO date of the last time the data involved in the query was up
metadata | Includes information about the layers.
cdn_url | URLs to fetch the data using the best CDN for your zone.
**Improved response metadata**
Originally, you needed to concantenate the `layergroupid` with the correct domain and the path for the tiles.
Now, for convenience, the layergroup includes the final URLs in two formats:
1. Leaflet's urlTemplate alike: useful when working with raster tiles or with libraries with an API similar to Leaflet's one.
1. [TileJSON spec](https://github.com/mapbox/tilejson-spec): useful when working with Mapbox GL or any other library that supports TileJSON.
### Example
#### Call
@@ -61,30 +69,231 @@ curl 'https://{username}.carto.com/api/v1/map' -H 'Content-Type: application/jso
"type": "mapnik",
"meta": {}
}
]
],
"tilejson": {
"raster": {
"tilejson": "2.2.0",
"tiles": [
"http://a.cdb.com/c01a54877c62831bb51720263f91fb33/{z}/{x}/{y}.png",
"http://b.cdb.com/c01a54877c62831bb51720263f91fb33/{z}/{x}/{y}.png"
]
}
},
"url": {
"raster": {
"urlTemplate": "http://{s}.cdb.com/c01a54877c62831bb51720263f91fb33/{z}/{x}/{y}.png",
"subdomains": ["a", "b"]
}
}
},
"cdn_url": {
"http": "http://cdb.com",
"https": "https://cdb.com"
"https": "https://cdb.com",
"templates": {
"http": { "subdomains": ["a","b"], "url": "http://{s}.cdb.com" },
"https": { "subdomains": ["a","b"], "url": "https://{s}.example.com" },
}
}
}
```
### Retrieve resources from the layergroup
## Map Tile Rendering
Map tiles are used to create the graphic representation of your map in a web browser. Tiles can be requested either as pre-rendered *raster* tiles (images) or as *vector* map data to be rendered by the client (browser).
- **Raster**: If a tile is requested as a raster image format, like PNG, the map will be rendered on the server, using the CartoCSS styles defined in the layers of the map. It is necessary that all the layers of a map define CartoCSS styles in order to obtain raster tiles. Raster tiles are made up of 256x256 pixels; to avoid graphic quality issues tiles should be used unscaled to represent the zoom level (Z) for which they are requested. In order to render tiles, data will be retrieved from the database (in vector format) on the server-side.
- **Vector**: Tiles can also be requested as MVT (Mapbox Vector Tiles). In this case, only the geospatial vector data, without any styling, is returned. These tiles should be processed in the client-side to render the map. In this case layers do not need to define CartoCSS, as any rendering and styling will be performed on the client side. The vector data of a tile represents real-world geometries by defining the vertices of points, lines or polygons in a tile-specific coordinate system.
## Retrieve resources from the layergroup
When you have a layergroup, there are several resources for retrieving layergoup details such as, accessing Mapnik tiles, getting individual layers, accessing defined Attributes, and blending and layer selection.
#### Mapnik tiles
### Raster tiles
These tiles will get just the Mapnik layers. To get individual layers, see the following section.
These raster tiles are PNG images that represent only the Mapnik layers of a map. See [individual layers](#individual-layers) for details about how to retrieve other layers.
```bash
https://{username}.carto.com/api/v1/map/{layergroupid}/{z}/{x}/{y}.png
```
#### Individual layers
### Mapbox Vector Tiles (MVT)
The MapConfig specification holds the layers definition in a 0-based index. Layers can be requested individually in different formats depending on the layer type.
[Mapbox Vector Tiles (MVT)](https://www.mapbox.com/vector-tiles/specification/) are map tiles that transfer geographic vector data to the client-side. Browser performance is fast since you can pan and zoom without having to query the server.
CARTO uses Web Graphics Library (WebGL) to process MVT files on the browser. This is useful since WebGL is compatible with most web browsers, include support for multiple client-side mapping engines, and do not require additional information from the server; which makes it more efficient for rendering map tiles. However, you can use any implementation tool for processing MVT files.
The following examples describe how to fetch MVT tiles with a cURL request.
#### MVT and Windshaft
CARTO uses Windshaft as the map tiler library to render multilayer maps with the Maps API. You can use Windshaft to request MVT using the same layer type that is used for requesting raster tiles (Mapnik layer). Simply change the file format `.mvt` in the URL.
```bash
https://{username}.cartodb.com/api/v1/map/HASH/:layer/{z}/{x}/{y}.mvt
```
The following example instantiates an anonymous map with layer options:
```bash
{
user_name: 'mycartodbuser',
sublayers: [{
sql: "SELECT * FROM table_name";
cartocss: '#layer { marker-fill: #F0F0F0; }'
}],
maps_api_template: 'https://{user}.cartodb.com' // Optional
}
```
**Note**: If no layer type is specified, Mapnik tiles are used by default. To access MVT tiles, specify `https://{username}.cartodb.com/api/v1/map/HASH/{z}/{x}/{y}.mvt` as the `maps_api_template` variable.
**Tip:** If you are using [Named Maps](https://carto.com/docs/carto-engine/maps-api/named-maps/) to instantiate a layer, indicate the MVT file format and layer in the response:
```bash
https://{username}.cartodb.com/api/v1/map/named/:templateId/:layer/{z}/{x}/{y}.mvt
```
For all layers in a Named Map, you must indicate Mapnik as the layer filter:
```bash
https://{username}.cartodb.com/api/v1/map/named/:templateId/mapnik/{z}/{x}/{y}.mvt
```
#### Layergroup Filter for MVT Tiles
To filter layers using Windshaft, use the following request where layers are numbered:
```bash
https://{username}.cartodb.com/api/v1/map/HASH/0,1,2/{z}/{x}/{y}.mvt
```
To request all layers, remove the layergroup filter parameter:
```bash
https://{username}.cartodb.com/api/v1/map/HASH/{z}/{x}/{y}.mvt
```
To filter a specific layer:
```bash
https://{username}.cartodb.com/api/v1/map/HASH/2/{z}/{x}/{y}.mvt
```
#### Example 1: MVT Tiles with Windshaft, CARTO.js, and MapboxGL
1) Import the required libraries:
```bash
<script src='https://api.tiles.mapbox.com/mapbox-gl-js/v0.9.0/mapbox-gl.js'></script>
<link href='https://api.tiles.mapbox.com/mapbox-gl-js/v0.9.0/mapbox-gl.css' rel='stylesheet' />
<script src="http://libs.cartocdn.com/cartodb.js/v3/3.15/cartodb.core.js"></script>
```
2) Configure Map Client:
```bash
mapboxgl.accessToken = '{yourMapboxToken}';
```
3) Create Map Object (Mapbox):
```bash
var map = new mapboxgl.Map({
container: 'map',
zoom: 1,
minZoom: 0,
maxZoom: 18,
center: [30, 0]
});
```
4) Define Layer Options (CARTO):
```bash
var layerOptions = {
user_name: "{username}",
sublayers: [{
sql: "SELECT * FROM {table_name}",
cartocss: "...",
}]
};
```
5) Request Tiles (from CARTO) and Set to Map Object (Mapbox):
**Note:** By default, [CARTO core functions](https://carto.com/docs/carto-engine/carto-js/core-api/) retrieve URLs for fully rendered tiles. You must replace the default format (.png) with the MVT format (.mvt).
```bash
cartodb.Tiles.getTiles(layerOptions, function(result, err) {
var tiles = result.tiles.map(function(tileUrl) {
return tileUrl
.replace('{s}', 'a')
.replace(/\.png/, '.mvt');
});
map.setStyle(simpleStyle(tiles));
});
```
#### Example 2: MVT Libraries with Windshaft and MapboxGL
When you are not including CARTO.js to implement MVT tiles, you must use the `map.setStyle` parameter to specify vector map rendering.
1) Import the required libraries:
```bash
<script src='https://api.tiles.mapbox.com/mapbox-gl-js/v0.9.0/mapbox-gl.js'></script>
<link href='https://api.tiles.mapbox.com/mapbox-gl-js/v0.9.0/mapbox-gl.css' rel='stylesheet'/>
```
2) Configure Map Client:
```bash
mapboxgl.accessToken = '{yourMapboxToken}';
```
3) Create Map Object (Mapbox):
```bash
var map = new mapboxgl.Map({
container: 'map',
zoom: 1,
minZoom: 0,
maxZoom: 18,
center: [30, 0]
});
```
4) Set the Style
```bash
map.setStyle({
"version": 7,
"glyphs": "...",
"constants": {...},
"sources": {
"cartodb": {
"type": "vector",
"tiles": [ "http://{username}.cartodb.com/api/v1/map/named/templateId/mapnik/{z}/{x}/{y}.mvt"
],
"maxzoom": 18
}
},
"layers": [{...}]
});
```
**Tip:** If you are using MapboxGL, see the following resource for additional information.
- [MapboxGL API Reference](https://www.mapbox.com/mapbox-gl-js/api/)
- [MapboxGL Style Specifications](https://www.mapbox.com/mapbox-gl-js/style-spec/)
- [Example of MapboxGL Implementation](https://www.mapbox.com/mapbox-gl-js/examples/)
### Individual layers
The MapConfig specification holds the layers definition in a 0-based index. Layers can be requested individually, in different formats, depending on the layer type.
Individual layers can be accessed using that 0-based index. For UTF grid tiles:
@@ -100,19 +309,19 @@ If the MapConfig had a Torque layer at index 1 it could be possible to request i
https://{username}.carto.com/api/v1/map/{layergroupid}/1/{z}/{x}/{y}.torque.json
```
#### Attributes defined in `attributes` section
### Attributes defined in `attributes` section
```bash
https://{username}.carto.com/api/v1/map/{layergroupid}/{layer}/attributes/{feature_id}
```
Which returns JSON with the attributes defined, like:
Which returns JSON with the attributes defined, such as:
```javascript
{ "c": 1, "d": 2 }
```
#### Blending and layer selection
### Blending and layer selection
```bash
https://{username}.carto.com/api/v1/map/{layergroupid}/{layer_filter}/{z}/{x}/{y}.png
@@ -141,13 +350,7 @@ https://{username}.carto.com/api/v1/map/{layergroupid}/0,3,4/{z}/{x}/{y}.png
Some notes about filtering:
- Invalid index values or out of bounds indexes will end in `Invalid layer filtering` errors.
- 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.
- Ordering is not considered. So right now filtering layers 0,3,4 is the very same thing as filtering 3,4,0. As this may change in the future, **it is recommended** to always select the layers in ascending order so that you will always get consistent behavior.
## Create JSONP
@@ -188,7 +391,6 @@ callback({
})
```
## Remove
Anonymous Maps cannot be removed by an API call. They will expire after about five minutes, or sometimes longer. If an Anonymous Map expires and tiles are requested from it, an error will be raised. This could happen if a user leaves a map open and after time, returns to the map and attempts to interact with it in a way that requires new tiles (e.g. zoom). The client will need to go through the steps of creating the map again to fix the problem.

View File

@@ -22,6 +22,6 @@ Errors are reported using standard HTTP codes and extended information encoded i
If you use JSONP, the 200 HTTP code is always returned so the JavaScript client can receive errors from the JSON object.
## CORS support
## CORS Support
All the endpoints, which might be accessed using a web browser, add CORS headers and allow OPTIONS method.

View File

@@ -1,10 +1,10 @@
# 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 CARTO servers, to select data from your CARTO 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 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.
@@ -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.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.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.
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.carto.com/carto-engine/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 [Layergroup 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.
@@ -160,7 +164,7 @@ curl -X POST \
#### 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
{
@@ -516,18 +520,18 @@ 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 CARTO.js
- [Named Map selectors with interaction](http://bl.ocks.org/ohasselblad/515a8af1f99d5e690484)
- [Named Map selectors with interaction](http://bl.ocks.org/andy-esch/515a8af1f99d5e690484)
- [Named Map with interactivity](http://bl.ocks.org/ohasselblad/d1a45b8ff5e7bd90cd68)
- [Named Map with interactivity](http://bl.ocks.org/andy-esch/d1a45b8ff5e7bd90cd68)
- [Toggling sublayers in a Named Map](http://bl.ocks.org/ohasselblad/c1a0f4913610eec53cd3)
- [Toggling sublayers in a Named Map](http://bl.ocks.org/andy-esch/c1a0f4913610eec53cd3)
## Fetching XYZ Tiles for Named Maps
@@ -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:

View File

@@ -146,10 +146,15 @@ It is important to note that generated images are cached from the live data refe
### 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 CARTO Editor and CARTO Engine. 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.
* If you are publishing your map as a static image with the API, you must manually add [attributions](https://carto.com/attribution) for your static map image. For example, add the following attribution code:
{% highlight javascript %}
attribution: '&copy; <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors, &copy; <a href="https://carto.com/attributions">CARTO</a>
{% endhighlight %}
## Examples

View File

@@ -19,22 +19,22 @@ function AuthApi(pgConnection, metadataBackend, mapStore, templateMaps) {
module.exports = AuthApi;
// Check if a request is authorized by a signer
// Check if the user is authorized by a signer
//
// @param req express request object
// @param res express response object
// @param callback function(err, signed_by) signed_by will be
// null if the request is not signed by anyone
// or will be a string cartodb username otherwise.
//
AuthApi.prototype.authorizedBySigner = function(req, callback) {
if ( ! req.params.token || ! req.params.signer ) {
AuthApi.prototype.authorizedBySigner = function(res, callback) {
if ( ! res.locals.token || ! res.locals.signer ) {
return callback(null, false); // no signer requested
}
var self = this;
var layergroup_id = req.params.token;
var auth_token = req.params.auth_token;
var layergroup_id = res.locals.token;
var auth_token = res.locals.auth_token;
this.mapStore.load(layergroup_id, function(err, mapConfig) {
if (err) {
@@ -84,30 +84,29 @@ AuthApi.prototype.authorizedByAPIKey = function(user, req, callback) {
* Check access authorization
*
* @param req - standard req object. Importantly contains table and host information
* @param res - standard res object. Contains the auth parameters in locals
* @param callback function(err, allowed) is access allowed not?
*/
AuthApi.prototype.authorize = function(req, callback) {
AuthApi.prototype.authorize = function(req, res, callback) {
var self = this;
var user = req.context.user;
var user = res.locals.user;
step(
function () {
self.authorizedByAPIKey(user, req, this);
},
function checkApiKey(err, authorized){
if (req.profiler) {
req.profiler.done('authorizedByAPIKey');
}
req.profiler.done('authorizedByAPIKey');
assert.ifError(err);
// if not authorized by api_key, continue
if (!authorized) {
// not authorized by api_key, check if authorized by signer
return self.authorizedBySigner(req, this);
return self.authorizedBySigner(res, this);
}
// authorized by api key, login as the given username and stop
self.pgConnection.setDBAuth(user, req.params, function(err) {
self.pgConnection.setDBAuth(user, res.locals, function(err) {
callback(err, true); // authorized (or error)
});
},
@@ -122,7 +121,7 @@ AuthApi.prototype.authorize = function(req, callback) {
// if no signer name was given, let dbparams and
// PostgreSQL do the rest.
//
if ( ! req.params.signer ) {
if ( ! res.locals.signer ) {
return callback(null, true); // authorized so far
}
@@ -130,10 +129,8 @@ AuthApi.prototype.authorize = function(req, callback) {
return callback(null, false);
}
self.pgConnection.setDBAuth(user, req.params, function(err) {
if (req.profiler) {
req.profiler.done('setDBAuth');
}
self.pgConnection.setDBAuth(user, res.locals, function(err) {
req.profiler.done('setDBAuth');
callback(err, true); // authorized (or error)
});
}

View File

@@ -1,3 +1,5 @@
var step = require('step');
/**
*
* @param metadataBackend
@@ -13,16 +15,65 @@ function UserLimitsApi(metadataBackend, options) {
module.exports = UserLimitsApi;
UserLimitsApi.prototype.getRenderLimits = function (username, callback) {
UserLimitsApi.prototype.getRenderLimits = function (username, apiKey, callback) {
var self = this;
this.metadataBackend.getTilerRenderLimit(username, function handleTilerLimits(err, renderLimit) {
var limits = {
cacheOnTimeout: self.options.limits.cacheOnTimeout || false,
render: self.options.limits.render || 0
};
self.getTimeoutRenderLimit(username, apiKey, function (err, timeoutRenderLimit) {
if (err) {
return callback(err);
}
return callback(null, {
cacheOnTimeout: self.options.limits.cacheOnTimeout || false,
render: renderLimit || self.options.limits.render || 0
});
if (timeoutRenderLimit && timeoutRenderLimit.render) {
if (Number.isFinite(timeoutRenderLimit.render)) {
limits.render = timeoutRenderLimit.render;
}
}
return callback(null, limits);
});
};
UserLimitsApi.prototype.getTimeoutRenderLimit = function (username, apiKey, callback) {
var self = this;
step(
function isAuthorized() {
var next = this;
if (!apiKey) {
return next(null, false);
}
self.metadataBackend.getUserMapKey(username, function (err, userApiKey) {
if (err) {
return next(err);
}
return next(null, userApiKey === apiKey);
});
},
function getUserTimeoutRenderLimits(err, authorized) {
var next = this;
if (err) {
return next(err);
}
self.metadataBackend.getUserTimeoutRenderLimits(username, function (err, timeoutRenderLimit) {
if (err) {
return next(err);
}
next(null, {
render: authorized ? timeoutRenderLimit.render : timeoutRenderLimit.renderPublic
});
});
},
callback
);
};

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

@@ -37,56 +37,32 @@ DataviewBackend.prototype.getDataview = function (mapConfigProvider, user, param
throw new Error("Dataview '" + dataviewName + "' does not exists");
}
var pg = new PSQL(dbParamsFromReqParams(params));
var ownFilter = +params.own_filter;
ownFilter = !!ownFilter;
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
});
var noFilters = +params.no_filters;
if (Number.isFinite(ownFilter) && Number.isFinite(noFilters)) {
err = new Error();
err.message = 'Both own_filter and no_filters cannot be sent in the same request';
err.type = 'dataview';
err.http_status = 400;
return callback(err);
}
var pg = new PSQL(dbParamsFromReqParams(params));
var query = getDataviewQuery(dataviewDefinition, ownFilter, noFilters);
if (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 queryRewriteData = getQueryRewriteData(mapConfig, dataviewDefinition, params);
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;
return overrides;
},
{ownFilter: ownFilter}
);
var dataview = dataviewFactory.getDataview(query, dataviewDefinition);
dataview.getResult(pg, overrideParams, this);
dataview.getResult(pg, getOverrideParams(params, !!ownFilter), this);
},
function returnCallback(err, result) {
return callback(err, result);
@@ -94,9 +70,67 @@ DataviewBackend.prototype.getDataview = function (mapConfigProvider, user, param
);
};
DataviewBackend.prototype.search = function (mapConfigProvider, user, params, callback) {
var dataviewName = params.dataviewName;
function getDataviewQuery(dataviewDefinition, ownFilter, noFilters) {
if (noFilters) {
return dataviewDefinition.sql.no_filters;
} else if (ownFilter === 1) {
return dataviewDefinition.sql.own_filter_on;
} else {
return dataviewDefinition.sql.own_filter_off;
}
}
function getQueryRewriteData(mapConfig, dataviewDefinition, params) {
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 && 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 });
}
return queryRewriteData;
}
function getOverrideParams(params, ownFilter) {
var overrideParams = _.reduce(_.pick(params, 'start', 'end', 'bins', 'offset', 'categories'),
function castNumbers(overrides, val, k) {
if (!Number.isFinite(+val)) {
throw new Error('Invalid number format for parameter \'' + k + '\'');
}
overrides[k] = +val;
return overrides;
},
{ownFilter: ownFilter}
);
// validation will be delegated to the proper dataview
if (params.aggregation !== undefined) {
overrideParams.aggregation = params.aggregation;
}
return overrideParams;
}
DataviewBackend.prototype.search = function (mapConfigProvider, user, dataviewName, params, callback) {
step(
function getMapConfig() {
mapConfigProvider.getMapConfig(this);

View File

@@ -0,0 +1,16 @@
function EmptyLayerStats(types) {
this._types = types || {};
}
EmptyLayerStats.prototype.is = function (type) {
return this._types[type] ? this._types[type] : false;
};
EmptyLayerStats.prototype.getStats =
function (layer, dbConnection, callback) {
setImmediate(function() {
callback(null, {});
});
};
module.exports = EmptyLayerStats;

View File

@@ -0,0 +1,23 @@
var LayerStats = require('./layer-stats');
var EmptyLayerStats = require('./empty-layer-stats');
var MapnikLayerStats = require('./mapnik-layer-stats');
var TorqueLayerStats = require('./torque-layer-stats');
module.exports = function LayerStatsFactory(type) {
var layerStatsIterator = [];
var selectedType = type || 'ALL';
if (selectedType === 'ALL') {
layerStatsIterator.push(new EmptyLayerStats({ http: true, plain: true }));
layerStatsIterator.push(new MapnikLayerStats());
layerStatsIterator.push(new TorqueLayerStats());
} else if (selectedType === 'mapnik') {
layerStatsIterator.push(new EmptyLayerStats({ http: true, plain: true, torque: true }));
layerStatsIterator.push(new MapnikLayerStats());
} else if (selectedType === 'torque') {
layerStatsIterator.push(new EmptyLayerStats({ http: true, plain: true, mapnik: true }));
layerStatsIterator.push(new TorqueLayerStats());
}
return new LayerStats(layerStatsIterator);
};

View File

@@ -0,0 +1,45 @@
var queue = require('queue-async');
function LayerStats(layerStatsIterator) {
this.layerStatsIterator = layerStatsIterator;
}
LayerStats.prototype.getStats = function (mapConfig, dbConnection, callback) {
var self = this;
var stats = [];
if (!mapConfig.getLayers().length) {
return callback(null, stats);
}
var metaQueue = queue(mapConfig.getLayers().length);
mapConfig.getLayers().forEach(function (layer, layerId) {
var layerType = mapConfig.layerType(layerId);
for (var i = 0; i < self.layerStatsIterator.length; i++) {
if (self.layerStatsIterator[i].is(layerType)) {
var getStats = self.layerStatsIterator[i].getStats.bind(self.layerStatsIterator[i]);
metaQueue.defer(getStats, layer, dbConnection);
break;
}
}
});
metaQueue.awaitAll(function (err, results) {
if (err) {
return callback(err);
}
if (!results) {
return callback(null, null);
}
mapConfig.getLayers().forEach(function (layer, layerIndex) {
stats[layerIndex] = results[layerIndex];
});
return callback(err, stats);
});
};
module.exports = LayerStats;

View File

@@ -0,0 +1,28 @@
var queryUtils = require('../../utils/query-utils');
function MapnikLayerStats () {
this._types = {
mapnik: true,
cartodb: true
};
}
MapnikLayerStats.prototype.is = function (type) {
return this._types[type] ? this._types[type] : false;
};
MapnikLayerStats.prototype.getStats =
function (layer, dbConnection, callback) {
var queryRowCountSql = queryUtils.getQueryRowCount(layer.options.sql);
// This query would gather stats for postgresql table if not exists
dbConnection.query(queryRowCountSql, function (err, res) {
if (err) {
return callback(null, {estimatedFeatureCount: -1});
} else {
// We decided that the relation is 1 row == 1 feature
return callback(null, {estimatedFeatureCount: res.rows[0].rows});
}
});
};
module.exports = MapnikLayerStats;

View File

@@ -0,0 +1,16 @@
function TorqueLayerStats() {
this._types = {
torque: true
};
}
TorqueLayerStats.prototype.is = function (type) {
return this._types[type] ? this._types[type] : false;
};
TorqueLayerStats.prototype.getStats =
function (layer, dbConnection, callback) {
return callback(null, {});
};
module.exports = TorqueLayerStats;

View File

@@ -0,0 +1,16 @@
var layerStats = require('./layer-stats/factory');
function StatsBackend() {
}
module.exports = StatsBackend;
StatsBackend.prototype.getStats = function(mapConfig, dbConnection, callback) {
var enabledFeatures = global.environment.enabledFeatures;
var layerStatsEnabled = enabledFeatures ? enabledFeatures.layerStats: false;
if (layerStatsEnabled) {
layerStats().getStats(mapConfig, dbConnection, callback);
} else {
return callback(null, []);
}
};

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;
}
@@ -431,14 +430,18 @@ var _reNumber = /^([-+]?[\d\.]?\d+([eE][+-]?\d+)?)$/,
_reCSSColorVal = /^#[0-9a-fA-F]{3,6}$/;
function _replaceVars (str, params) {
//return _.template(str, params); // lazy way, possibly dangerous
// Construct regular expressions for each param
// Construct regular expressions for each param
Object.keys(params).forEach(function(k) {
str = str.replace(new RegExp("<%=\\s*" + k + "\\s*%>", "g"), params[k]);
});
return str;
}
o.instance = function(template, params) {
function isObject(val) {
return ( _.isObject(val) && !_.isArray(val) && !_.isFunction(val));
}
TemplateMaps.prototype.instance = function(template, params) {
var all_params = {};
var phold = template.placeholders || {};
Object.keys(phold).forEach(function(k) {
@@ -475,6 +478,13 @@ o.instance = function(template, params) {
// NOTE: we're deep-cloning the layergroup here
var layergroup = JSON.parse(JSON.stringify(template.layergroup));
if (layergroup.buffersize && isObject(layergroup.buffersize)) {
Object.keys(layergroup.buffersize).forEach(function(k) {
layergroup.buffersize[k] = parseInt(_replaceVars(layergroup.buffersize[k], all_params), 10);
});
}
for (var i=0; i<layergroup.layers.length; ++i) {
var lyropt = layergroup.layers[i].options;
@@ -500,7 +510,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

@@ -6,16 +6,25 @@ 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'
'FROM ({{=it._sql}}) _table_sql WHERE {{=it._column}} IS NOT NULL',
'AND',
' {{=it._column}} != \'infinity\'::float',
'AND',
' {{=it._column}} != \'-infinity\'::float',
'AND',
' {{=it._column}} != \'NaN\'::float'
].join('\n'));
}
var methods = {
quantiles: 'CDB_QuantileBins(array_agg(distinct({{=it._column}}::numeric)), {{=it._buckets}}) as quantiles',
quantiles: 'CDB_QuantileBins(array_agg({{=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'
jenks: 'CDB_JenksBins(array_agg({{=it._column}}::numeric), {{=it._buckets}}) as jenks',
headtails: 'CDB_HeadsTailsBins(array_agg({{=it._column}}::numeric), {{=it._buckets}}) as headtails'
};
var methodTemplates = Object.keys(methods).reduce(function(methodTemplates, methodName) {
@@ -29,7 +38,7 @@ methodTemplates.category = dot.template([
' 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',
' ORDER BY 2 DESC, 1 ASC',
'),',
'agg_categories AS (',
' SELECT category',
@@ -74,11 +83,14 @@ PostgresDatasource.prototype.getRamp = function (column, buckets, method, callba
return callback(err);
}
resultSet = resultSet || {};
var result = resultSet.rows || [];
var result = getResult(resultSet);
var strategy = method2strategy[methodName];
var ramp = result[0][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
@@ -89,8 +101,16 @@ PostgresDatasource.prototype.getRamp = function (column, buckets, method, callba
});
}
return callback(null, { ramp: ramp, strategy: strategy });
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,152 +1,150 @@
var step = require('step');
var assert = require('assert');
var dot = require('dot');
dot.templateSettings.strip = false;
var PSQL = require('cartodb-psql');
var util = require('util');
var BaseController = require('./base');
var cors = require('../middleware/cors');
var userMiddleware = require('../middleware/user');
function AnalysesController(authApi, pgConnection) {
BaseController.call(this, authApi, pgConnection);
function AnalysesController(prepareContext) {
this.prepareContext = prepareContext;
}
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 });
}
}
AnalysesController.prototype.register = function (app) {
app.get(
`${app.base_url_mapconfig}/analyses/catalog`,
cors(),
userMiddleware,
this.prepareContext,
this.createPGClient(),
this.getDataFromQuery({ queryTemplate: catalogQueryTpl, key: 'catalog' }),
this.getDataFromQuery({ queryTemplate: tablesQueryTpl, key: 'tables' }),
this.prepareResponse(),
this.setCacheControlHeader(),
this.sendResponse(),
this.unathorizedError()
);
};
var catalogQueryTpl = dot.template(
'SELECT analysis_def->>\'type\' as type, * FROM cartodb.cdb_analysis_catalog WHERE username = \'{{=it._username}}\''
);
var tablesQueryTpl = dot.template([
"WITH analysis_tables AS (",
" SELECT",
" n.nspname AS nspname,",
" c.relname AS relname,",
" pg_total_relation_size(",
" format('%s.%s', pg_catalog.quote_ident(n.nspname), pg_catalog.quote_ident(c.relname))",
" ) AS size,",
" format('%s.%s', pg_catalog.quote_ident(nspname), pg_catalog.quote_ident(relname)) AS fully_qualified_name",
" FROM pg_catalog.pg_class c, pg_catalog.pg_namespace n",
" WHERE c.relnamespace = n.oid",
" AND pg_catalog.quote_ident(c.relname) ~ '^analysis_[a-z0-9]{10}_[a-z0-9]{40}$'",
" AND n.nspname IN ('{{=it._username}}', 'public')",
")",
"SELECT *, pg_size_pretty(size) as size_pretty",
"FROM analysis_tables",
"ORDER BY size DESC"
].join('\n'));
function getMetadata(username, pg, callback) {
var results = {
catalog: [],
tables: []
AnalysesController.prototype.createPGClient = function () {
return function createPGClientMiddleware (req, res, next) {
res.locals.pg = new PSQL(dbParamsFromReqParams(res.locals));
next();
};
step(
function getCatalog() {
pg.query(catalogQueryTpl({_username: username}), this, true); // use read-only transaction
},
function handleCatalog(err, resultSet) {
assert.ifError(err);
resultSet = resultSet || {};
results.catalog = resultSet.rows || [];
this();
},
function getTables(err) {
assert.ifError(err);
pg.query(tablesQueryTpl({_username: username}), this, true); // use read-only transaction
},
function handleTables(err, resultSet) {
assert.ifError(err);
resultSet = resultSet || {};
results.tables = resultSet.rows || [];
this();
},
function finish(err) {
};
AnalysesController.prototype.getDataFromQuery = function ({ queryTemplate, key }) {
const readOnlyTransactionOn = true;
return function getCatalogMiddleware(req, res, next) {
const { pg, user } = res.locals;
const sql = queryTemplate({ _username: user });
pg.query(sql, (err, resultSet = {}) => {
if (err) {
return callback(err);
return next(err);
}
return callback(null, results);
}
);
}
res.locals[key] = resultSet.rows || [];
next();
}, readOnlyTransactionOn);
};
};
AnalysesController.prototype.prepareResponse = function () {
return function prepareResponseMiddleware (req, res, next) {
const { catalog, tables } = res.locals;
const analysisIdToTable = tables.reduce((analysisIdToTable, table) => {
const analysisId = table.relname.split('_')[2];
if (analysisId && analysisId.length === 40) {
analysisIdToTable[analysisId] = table;
}
return analysisIdToTable;
}, {});
const analysisCatalog = catalog.map(analysis => {
if (analysisIdToTable.hasOwnProperty(analysis.node_id)) {
analysis.table = analysisIdToTable[analysis.node_id];
}
return analysis;
})
.sort((analysisA, analysisB) => {
if (!!analysisA.table && !!analysisB.table) {
return analysisB.table.size - analysisA.table.size;
}
if (!!analysisA.table) {
return -1;
}
if (!!analysisB.table) {
return 1;
}
return -1;
});
res.body = { catalog: analysisCatalog };
next();
};
};
AnalysesController.prototype.setCacheControlHeader = function () {
return function setCacheControlHeaderMiddleware (req, res, next) {
res.set('Cache-Control', 'public,max-age=10,must-revalidate');
next();
};
};
AnalysesController.prototype.sendResponse = function() {
return function sendResponseMiddleware (req, res) {
res.status(200);
if (req.query && req.query.callback) {
res.jsonp(res.body);
} else {
res.json(res.body);
}
};
};
AnalysesController.prototype.unathorizedError = function () {
return function unathorizedErrorMiddleware(err, req, res, next) {
if (err.message.match(/permission\sdenied/)) {
err = new Error('Unauthorized');
err.http_status = 401;
}
next(err);
};
};
const catalogQueryTpl = ctx => `
SELECT analysis_def->>'type' as type, * FROM cdb_analysis_catalog WHERE username = '${ctx._username}'
`;
var tablesQueryTpl = ctx => `
WITH analysis_tables AS (
SELECT
n.nspname AS nspname,
c.relname AS relname,
pg_total_relation_size(
format('%s.%s', pg_catalog.quote_ident(n.nspname), pg_catalog.quote_ident(c.relname))
) AS size,
format('%s.%s', pg_catalog.quote_ident(nspname), pg_catalog.quote_ident(relname)) AS fully_qualified_name
FROM pg_catalog.pg_class c, pg_catalog.pg_namespace n
WHERE c.relnamespace = n.oid
AND pg_catalog.quote_ident(c.relname) ~ '^analysis_[a-z0-9]{10}_[a-z0-9]{40}$'
AND n.nspname IN ('${ctx._username}', 'public')
)
SELECT *, pg_size_pretty(size) as size_pretty
FROM analysis_tables
ORDER BY size DESC
`;
function dbParamsFromReqParams(params) {
var dbParams = {};

View File

@@ -1,295 +0,0 @@
var assert = require('assert');
var _ = require('underscore');
var step = require('step');
var debug = require('debug')('windshaft:cartodb');
var LZMA = require('lzma').LZMA;
var lzmaWorker = new LZMA();
// Whitelist query parameters and attach format
var REQUEST_QUERY_PARAMS_WHITELIST = [
'config',
'map_key',
'api_key',
'auth_token',
'callback',
'zoom',
'lon',
'lat',
// widgets & filters
'filters', // json
'own_filter', // 0, 1
'bbox', // w,s,e,n
'bins', // number
'start', // number
'end', // number
'column_type', // string
// widgets search
'q'
];
function BaseController(authApi, pgConnection) {
this.authApi = authApi;
this.pgConnection = pgConnection;
}
module.exports = BaseController;
// jshint maxcomplexity:9
/**
* Whitelist input and get database name & default geometry type from
* subdomain/user metadata held in CartoDB Redis
* @param req - standard express request obj. Should have host & table
* @param callback
*/
BaseController.prototype.req2params = function(req, callback){
var self = this;
if ( req.query.lzma ) {
// Decode (from base64)
var lzma = new Buffer(req.query.lzma, 'base64')
.toString('binary')
.split('')
.map(function(c) {
return c.charCodeAt(0) - 128;
});
// Decompress
lzmaWorker.decompress(
lzma,
function(result) {
if (req.profiler) {
req.profiler.done('lzma');
}
try {
delete req.query.lzma;
_.extend(req.query, JSON.parse(result));
self.req2params(req, callback);
} catch (err) {
req.profiler.done('req2params');
callback(new Error('Error parsing lzma as JSON: ' + err));
}
}
);
return;
}
req.query = _.pick(req.query, REQUEST_QUERY_PARAMS_WHITELIST);
req.params = _.extend({}, req.params); // shuffle things as request is a strange array/object
var user = req.context.user;
if ( req.params.token ) {
// Token might match the following patterns:
// - {user}@{tpl_id}@{token}:{cache_buster}
var tksplit = req.params.token.split(':');
req.params.token = tksplit[0];
if ( tksplit.length > 1 ) {
req.params.cache_buster= tksplit[1];
}
tksplit = req.params.token.split('@');
if ( tksplit.length > 1 ) {
req.params.signer = tksplit.shift();
if ( ! req.params.signer ) {
req.params.signer = user;
}
else if ( req.params.signer !== user ) {
var err = new Error(
'Cannot use map signature of user "' + req.params.signer + '" on db of user "' + user + '"'
);
err.http_status = 403;
req.profiler.done('req2params');
callback(err);
return;
}
if ( tksplit.length > 1 ) {
/*var template_hash = */tksplit.shift(); // unused
}
req.params.token = tksplit.shift();
}
}
// bring all query values onto req.params object
_.extend(req.params, req.query);
if (req.profiler) {
req.profiler.done('req2params.setup');
}
step(
function getPrivacy(){
self.authApi.authorize(req, this);
},
function validateAuthorization(err, authorized) {
if (req.profiler) {
req.profiler.done('authorize');
}
assert.ifError(err);
if(!authorized) {
err = new Error("Sorry, you are unauthorized (permission denied)");
err.http_status = 403;
throw err;
}
return null;
},
function getDatabase(err){
assert.ifError(err);
self.pgConnection.setDBConn(user, req.params, this);
},
function finishSetup(err) {
if ( err ) {
req.profiler.done('req2params');
return callback(err, req);
}
// Add default database connection parameters
// if none given
_.defaults(req.params, {
dbuser: global.environment.postgres.user,
dbpassword: global.environment.postgres.password,
dbhost: global.environment.postgres.host,
dbport: global.environment.postgres.port
});
req.profiler.done('req2params');
callback(null, req);
}
);
};
// jshint maxcomplexity:6
// jshint maxcomplexity:9
BaseController.prototype.send = function(req, res, body, status, headers) {
if (req.params.dbhost) {
res.set('X-Served-By-DB-Host', req.params.dbhost);
}
if (req.profiler) {
res.set('X-Tiler-Profiler', req.profiler.toJSONString());
}
if (headers) {
res.set(headers);
}
res.status(status);
if (!Buffer.isBuffer(body) && typeof body === 'object') {
if (req.query && req.query.callback) {
res.jsonp(body);
} else {
res.json(body);
}
} else {
res.send(body);
}
if (req.profiler) {
try {
// May throw due to dns, see
// See http://github.com/CartoDB/Windshaft/issues/166
req.profiler.sendStats();
} catch (err) {
debug("error sending profiling stats: " + err);
}
}
};
// jshint maxcomplexity:6
BaseController.prototype.sendError = function(req, res, err, label) {
var allErrors = Array.isArray(err) ? err : [err];
label = label || 'UNKNOWN';
err = allErrors[0] || new Error(label);
allErrors[0] = err;
var statusCode = findStatusCode(err);
debug('[%s ERROR] -- %d: %s, %s', label, statusCode, err, err.stack);
// If a callback was requested, force status to 200
if (req.query && req.query.callback) {
statusCode = 200;
}
var errorResponseBody = {
errors: allErrors.map(errorMessage),
errors_with_context: allErrors.map(errorMessageWithContext)
};
this.send(req, res, errorResponseBody, statusCode);
};
function stripConnectionInfo(message) {
// Strip connection info, if any
return message
// See https://github.com/CartoDB/Windshaft/issues/173
.replace(/Connection string: '[^']*'\n\s/im, '')
// 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) {
var statusCode;
if ( err.http_status ) {
statusCode = err.http_status;
} else {
statusCode = statusFromErrorMessage('' + err);
}
return statusCode;
}
module.exports.findStatusCode = findStatusCode;
function statusFromErrorMessage(errMsg) {
// Find an appropriate statusCode based on message
// jshint maxcomplexity:7
var statusCode = 400;
if ( -1 !== errMsg.indexOf('permission denied') ) {
statusCode = 403;
}
else if ( -1 !== errMsg.indexOf('authentication failed') ) {
statusCode = 403;
}
else if (errMsg.match(/Postgis Plugin.*[\s|\n].*column.*does not exist/)) {
statusCode = 400;
}
else if ( -1 !== errMsg.indexOf('does not exist') ) {
if ( -1 !== errMsg.indexOf(' role ') ) {
statusCode = 403; // role 'xxx' does not exist
} else if ( errMsg.match(/function .* does not exist/) ) {
statusCode = 400; // invalid SQL (SQL function does not exist)
} else {
statusCode = 404;
}
}
return statusCode;
}

View File

@@ -1,11 +1,10 @@
var assert = require('assert');
var step = require('step');
var util = require('util');
var BaseController = require('./base');
var cors = require('../middleware/cors');
var userMiddleware = require('../middleware/user');
var allowQueryParams = require('../middleware/allow-query-params');
var vectorError = require('../middleware/vector-error');
var DataviewBackend = require('../backends/dataview');
var AnalysisStatusBackend = require('../backends/analysis-status');
@@ -27,10 +26,8 @@ var QueryTables = require('cartodb-query-tables');
* @param {AnalysisBackend} analysisBackend
* @constructor
*/
function LayergroupController(authApi, pgConnection, mapStore, tileBackend, previewBackend, attributesBackend,
function LayergroupController(prepareContext, pgConnection, mapStore, tileBackend, previewBackend, attributesBackend,
surrogateKeysCache, userLimitsApi, layergroupAffectedTables, analysisBackend) {
BaseController.call(this, authApi, pgConnection);
this.pgConnection = pgConnection;
this.mapStore = mapStore;
this.tileBackend = tileBackend;
@@ -42,75 +39,143 @@ function LayergroupController(authApi, pgConnection, mapStore, tileBackend, prev
this.dataviewBackend = new DataviewBackend(analysisBackend);
this.analysisStatusBackend = new AnalysisStatusBackend();
}
util.inherits(LayergroupController, BaseController);
this.prepareContext = prepareContext;
}
module.exports = LayergroupController;
LayergroupController.prototype.register = function(app) {
app.get(app.base_url_mapconfig +
'/:token/:z/:x/:y@:scale_factor?x.:format', cors(), userMiddleware,
this.tile.bind(this));
app.get(
app.base_url_mapconfig + '/:token/:z/:x/:y@:scale_factor?x.:format',
cors(),
userMiddleware,
this.prepareContext,
this.tile.bind(this),
vectorError()
);
app.get(app.base_url_mapconfig +
'/:token/:z/:x/:y.:format', cors(), userMiddleware,
this.tile.bind(this));
app.get(
app.base_url_mapconfig + '/:token/:z/:x/:y.:format',
cors(),
userMiddleware,
this.prepareContext,
this.tile.bind(this),
vectorError()
);
app.get(app.base_url_mapconfig +
'/:token/:layer/:z/:x/:y.(:format)', cors(), userMiddleware,
this.layer.bind(this));
app.get(
app.base_url_mapconfig + '/:token/:layer/:z/:x/:y.(:format)',
cors(),
userMiddleware,
validateLayerRouteMiddleware,
this.prepareContext,
this.layer.bind(this),
vectorError()
);
app.get(app.base_url_mapconfig +
'/:token/:layer/attributes/:fid', cors(), userMiddleware,
this.attributes.bind(this));
app.get(
app.base_url_mapconfig + '/:token/:layer/attributes/:fid',
cors(),
userMiddleware,
this.prepareContext,
this.attributes.bind(this)
);
app.get(app.base_url_mapconfig +
'/static/center/:token/:z/:lat/:lng/:width/:height.:format', cors(), userMiddleware,
this.center.bind(this));
app.get(
app.base_url_mapconfig + '/static/center/:token/:z/:lat/:lng/:width/:height.:format',
cors(),
userMiddleware,
allowQueryParams(['layer']),
this.prepareContext,
this.center.bind(this)
);
app.get(app.base_url_mapconfig +
'/static/bbox/:token/:west,:south,:east,:north/:width/:height.:format', cors(), userMiddleware,
this.bbox.bind(this));
app.get(
app.base_url_mapconfig + '/static/bbox/:token/:west,:south,:east,:north/:width/:height.:format',
cors(),
userMiddleware,
allowQueryParams(['layer']),
this.prepareContext,
this.bbox.bind(this)
);
// Undocumented/non-supported API endpoint methods.
// Use at your own peril.
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));
var allowedDataviewQueryParams = [
'filters', // json
'own_filter', // 0, 1
'no_filters', // 0, 1
'bbox', // w,s,e,n
'start', // number
'end', // number
'column_type', // string
'bins', // number
'aggregation', //string
'offset', // number
'q', // widgets search
'categories', // number
];
app.get(app.base_url_mapconfig +
'/:token/analysis/node/:nodeId', cors(), userMiddleware,
this.analysisNodeStatus.bind(this));
app.get(
app.base_url_mapconfig + '/:token/dataview/:dataviewName',
cors(),
userMiddleware,
allowQueryParams(allowedDataviewQueryParams),
this.prepareContext,
this.dataview.bind(this)
);
app.get(
app.base_url_mapconfig + '/:token/:layer/widget/:dataviewName',
cors(),
userMiddleware,
allowQueryParams(allowedDataviewQueryParams),
this.prepareContext,
this.dataview.bind(this)
);
app.get(
app.base_url_mapconfig + '/:token/dataview/:dataviewName/search',
cors(),
userMiddleware,
allowQueryParams(allowedDataviewQueryParams),
this.prepareContext,
this.dataviewSearch.bind(this)
);
app.get(
app.base_url_mapconfig + '/:token/:layer/widget/:dataviewName/search',
cors(),
userMiddleware,
allowQueryParams(allowedDataviewQueryParams),
this.prepareContext,
this.dataviewSearch.bind(this)
);
app.get(
app.base_url_mapconfig + '/:token/analysis/node/:nodeId',
cors(),
userMiddleware,
this.prepareContext,
this.analysisNodeStatus.bind(this)
);
};
LayergroupController.prototype.analysisNodeStatus = function(req, res) {
LayergroupController.prototype.analysisNodeStatus = function(req, res, next) {
var self = this;
step(
function setupParams() {
self.req2params(req, this);
},
function retrieveNodeStatus(err) {
assert.ifError(err);
self.analysisStatusBackend.getNodeStatus(req.params, this);
function retrieveNodeStatus() {
self.analysisStatusBackend.getNodeStatus(res.locals, this);
},
function finish(err, nodeStatus, stats) {
req.profiler.add(stats || {});
if (err) {
self.sendError(req, res, err, 'GET NODE STATUS');
err.label = 'GET NODE STATUS';
next(err);
} else {
self.sendResponse(req, res, nodeStatus, 200, {
'Cache-Control': 'public,max-age=5',
@@ -121,54 +186,50 @@ LayergroupController.prototype.analysisNodeStatus = function(req, res) {
);
};
LayergroupController.prototype.dataview = function(req, res) {
LayergroupController.prototype.dataview = function(req, res, next) {
var self = this;
step(
function setupParams() {
self.req2params(req, this);
},
function retrieveDataview(err) {
assert.ifError(err);
function retrieveDataview() {
var mapConfigProvider = new MapStoreMapConfigProvider(
self.mapStore, req.context.user, self.userLimitsApi, req.params
self.mapStore, res.locals.user, self.userLimitsApi, res.locals
);
self.dataviewBackend.getDataview(
mapConfigProvider,
res.locals.user,
res.locals,
this
);
self.dataviewBackend.getDataview(mapConfigProvider, req.context.user, req.params, this);
},
function finish(err, dataview, stats) {
req.profiler.add(stats || {});
if (err) {
self.sendError(req, res, err, 'GET DATAVIEW');
err.label = 'GET DATAVIEW';
next(err);
} else {
self.sendResponse(req, res, dataview, 200);
}
}
);
};
LayergroupController.prototype.dataviewSearch = function(req, res) {
LayergroupController.prototype.dataviewSearch = function(req, res, next) {
var self = this;
step(
function setupParams() {
self.req2params(req, this);
},
function searchDataview(err) {
assert.ifError(err);
function searchDataview() {
var mapConfigProvider = new MapStoreMapConfigProvider(
self.mapStore, req.context.user, self.userLimitsApi, req.params
self.mapStore, res.locals.user, self.userLimitsApi, res.locals
);
self.dataviewBackend.search(mapConfigProvider, req.context.user, req.params, this);
self.dataviewBackend.search(mapConfigProvider, res.locals.user, req.params.dataviewName, res.locals, this);
},
function finish(err, searchResult, stats) {
req.profiler.add(stats || {});
if (err) {
self.sendError(req, res, err, 'GET DATAVIEW SEARCH');
err.label = 'GET DATAVIEW SEARCH';
next(err);
} else {
self.sendResponse(req, res, searchResult, 200);
}
@@ -177,28 +238,24 @@ LayergroupController.prototype.dataviewSearch = function(req, res) {
};
LayergroupController.prototype.attributes = function(req, res) {
LayergroupController.prototype.attributes = function(req, res, next) {
var self = this;
req.profiler.start('windshaft.maplayer_attribute');
step(
function setupParams() {
self.req2params(req, this);
},
function retrieveFeatureAttributes(err) {
assert.ifError(err);
function retrieveFeatureAttributes() {
var mapConfigProvider = new MapStoreMapConfigProvider(
self.mapStore, req.context.user, self.userLimitsApi, req.params
self.mapStore, res.locals.user, self.userLimitsApi, res.locals
);
self.attributesBackend.getFeatureAttributes(mapConfigProvider, req.params, false, this);
self.attributesBackend.getFeatureAttributes(mapConfigProvider, res.locals, false, this);
},
function finish(err, tile, stats) {
req.profiler.add(stats || {});
if (err) {
self.sendError(req, res, err, 'GET ATTRIBUTES');
err.label = 'GET ATTRIBUTES';
next(err);
} else {
self.sendResponse(req, res, tile, 200);
}
@@ -208,49 +265,48 @@ LayergroupController.prototype.attributes = function(req, res) {
};
// Gets a tile for a given token and set of tile ZXY coords. (OSM style)
LayergroupController.prototype.tile = function(req, res) {
LayergroupController.prototype.tile = function(req, res, next) {
req.profiler.start('windshaft.map_tile');
this.tileOrLayer(req, res);
this.tileOrLayer(req, res, next);
};
// Gets a tile for a given token, layer set of tile ZXY coords. (OSM style)
LayergroupController.prototype.layer = function(req, res, next) {
if (req.params.token === 'static') {
return next();
}
req.profiler.start('windshaft.maplayer_tile');
this.tileOrLayer(req, res);
this.tileOrLayer(req, res, next);
};
LayergroupController.prototype.tileOrLayer = function (req, res) {
LayergroupController.prototype.tileOrLayer = function (req, res, next) {
var self = this;
step(
function mapController$prepareParams() {
self.req2params(req, this);
},
function mapController$getTileOrGrid(err) {
assert.ifError(err);
function mapController$getTileOrGrid() {
self.tileBackend.getTile(
new MapStoreMapConfigProvider(self.mapStore, req.context.user, self.userLimitsApi, req.params),
new MapStoreMapConfigProvider(self.mapStore, res.locals.user, self.userLimitsApi, res.locals),
req.params, this
);
},
function mapController$finalize(err, tile, headers, stats) {
req.profiler.add(stats);
self.finalizeGetTileOrGrid(err, req, res, tile, headers);
self.finalizeGetTileOrGrid(err, req, res, tile, headers, next);
}
);
};
function getStatusCode(tile, format){
return tile.length===0 && format==='mvt'? 204:200;
}
// This function is meant for being called as the very last
// step by all endpoints serving tiles or grids
LayergroupController.prototype.finalizeGetTileOrGrid = function(err, req, res, tile, headers) {
LayergroupController.prototype.finalizeGetTileOrGrid = function(err, req, res, tile, headers, next) {
var supportedFormats = {
grid_json: true,
json_torque: true,
torque_json: true,
png: true
png: true,
png32: true,
mvt: true
};
var formatStat = 'invalid';
@@ -272,52 +328,52 @@ LayergroupController.prototype.finalizeGetTileOrGrid = function(err, req, res, t
}
err.message = errMsg;
this.sendError(req, res, err, 'TILE RENDER');
err.label = 'TILE RENDER';
next(err);
global.statsClient.increment('windshaft.tiles.error');
global.statsClient.increment('windshaft.tiles.' + formatStat + '.error');
} else {
this.sendResponse(req, res, tile, 200, headers);
this.sendResponse(req, res, tile, getStatusCode(tile, formatStat), headers);
global.statsClient.increment('windshaft.tiles.success');
global.statsClient.increment('windshaft.tiles.' + formatStat + '.success');
}
};
LayergroupController.prototype.bbox = function(req, res) {
LayergroupController.prototype.bbox = function(req, res, next) {
this.staticMap(req, res, +req.params.width, +req.params.height, {
west: +req.params.west,
north: +req.params.north,
east: +req.params.east,
south: +req.params.south
});
}, null, next);
};
LayergroupController.prototype.center = function(req, res) {
LayergroupController.prototype.center = function(req, res, next) {
this.staticMap(req, res, +req.params.width, +req.params.height, +req.params.z, {
lng: +req.params.lng,
lat: +req.params.lat
});
}, next);
};
LayergroupController.prototype.staticMap = function(req, res, width, height, zoom /* bounds */, center) {
LayergroupController.prototype.staticMap = function(req, res, width, height, zoom /* bounds */, center, next) {
var format = req.params.format === 'jpg' ? 'jpeg' : 'png';
req.params.layer = 'all';
req.params.format = 'png';
// We force always the tile to be generated using PNG because
// is the only format we support by now
res.locals.format = 'png';
res.locals.layer = res.locals.layer || 'all';
var self = this;
step(
function reqParams() {
self.req2params(req, this);
},
function getImage(err) {
assert.ifError(err);
function getImage() {
if (center) {
self.previewBackend.getImage(
new MapStoreMapConfigProvider(self.mapStore, req.context.user, self.userLimitsApi, req.params),
new MapStoreMapConfigProvider(self.mapStore, res.locals.user, self.userLimitsApi, res.locals),
format, width, height, zoom, center, this);
} else {
self.previewBackend.getImage(
new MapStoreMapConfigProvider(self.mapStore, req.context.user, self.userLimitsApi, req.params),
new MapStoreMapConfigProvider(self.mapStore, res.locals.user, self.userLimitsApi, res.locals),
format, width, height, zoom /* bounds */, this);
}
},
@@ -326,7 +382,8 @@ LayergroupController.prototype.staticMap = function(req, res, width, height, zoo
req.profiler.add(stats || {});
if (err) {
self.sendError(req, res, err, 'STATIC_MAP');
err.label = 'STATIC_MAP';
next(err);
} else {
res.set('Content-Type', headers['Content-Type'] || 'image/' + format);
self.sendResponse(req, res, image, 200);
@@ -338,22 +395,24 @@ 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
var lastUpdated;
if (req.params.cache_buster) {
if (res.locals.cache_buster) {
// Assuming cache_buster is a timestamp
lastUpdated = new Date(parseInt(req.params.cache_buster));
lastUpdated = new Date(parseInt(res.locals.cache_buster));
} else {
lastUpdated = new Date();
}
res.set('Last-Modified', lastUpdated.toUTCString());
var dbName = req.params.dbname;
var dbName = res.locals.dbname;
step(
function getAffectedTables() {
self.getAffectedTables(req.context.user, dbName, req.params.token, this);
self.getAffectedTables(res.locals.user, dbName, res.locals.token, this);
},
function sendResponse(err, affectedTables) {
req.profiler.done('affectedTables');
@@ -364,10 +423,24 @@ LayergroupController.prototype.sendResponse = function(req, res, body, status, h
res.set('X-Cache-Channel', affectedTables.getCacheChannel());
self.surrogateKeysCache.tag(res, affectedTables);
}
self.send(req, res, body, status, headers);
if (headers) {
res.set(headers);
}
res.status(status);
if (!Buffer.isBuffer(body) && typeof body === 'object') {
if (req.query && req.query.callback) {
res.jsonp(body);
} else {
res.json(body);
}
} else {
res.send(body);
}
}
);
};
LayergroupController.prototype.getAffectedTables = function(user, dbName, layergroupId, callback) {
@@ -386,13 +459,15 @@ LayergroupController.prototype.getAffectedTables = function(user, dbName, layerg
function getSQL(err, mapConfig) {
assert.ifError(err);
var queries = mapConfig.getLayers()
.map(function(lyr) {
return lyr.options.sql;
})
.filter(function(sql) {
return !!sql;
});
var queries = [];
mapConfig.getLayers().forEach(function(layer) {
queries.push(layer.options.sql);
if (layer.options.affected_tables) {
layer.options.affected_tables.map(function(table) {
queries.push('SELECT * FROM ' + table + ' LIMIT 0');
});
}
});
return queries.length ? queries.join(';') : null;
},
@@ -427,3 +502,12 @@ LayergroupController.prototype.getAffectedTables = function(user, dbName, layerg
callback
);
};
function validateLayerRouteMiddleware(req, res, next) {
if (req.params.token === 'static') {
return next('route');
}
next();
}

View File

@@ -1,14 +1,12 @@
var _ = require('underscore');
var assert = require('assert');
var step = require('step');
var windshaft = require('windshaft');
var QueryTables = require('cartodb-query-tables');
var util = require('util');
var BaseController = require('./base');
var ResourceLocator = require('../models/resource-locator');
var cors = require('../middleware/cors');
var userMiddleware = require('../middleware/user');
const allowQueryParams = require('../middleware/allow-query-params');
var MapConfig = windshaft.model.MapConfig;
var Datasource = windshaft.model.Datasource;
@@ -28,13 +26,12 @@ var CreateLayergroupMapConfigProvider = require('../models/mapconfig/provider/cr
* @param {UserLimitsApi} userLimitsApi
* @param {LayergroupAffectedTables} layergroupAffectedTables
* @param {MapConfigAdapter} mapConfigAdapter
* @param {StatsBackend} statsBackend
* @constructor
*/
function MapController(authApi, pgConnection, templateMaps, mapBackend, metadataBackend,
surrogateKeysCache, userLimitsApi, layergroupAffectedTables, mapConfigAdapter) {
BaseController.call(this, authApi, pgConnection);
function MapController(prepareContext, pgConnection, templateMaps, mapBackend, metadataBackend,
surrogateKeysCache, userLimitsApi, layergroupAffectedTables, mapConfigAdapter,
statsBackend) {
this.pgConnection = pgConnection;
this.templateMaps = templateMaps;
this.mapBackend = mapBackend;
@@ -44,285 +41,429 @@ function MapController(authApi, pgConnection, templateMaps, mapBackend, metadata
this.layergroupAffectedTables = layergroupAffectedTables;
this.mapConfigAdapter = mapConfigAdapter;
}
this.resourceLocator = new ResourceLocator(global.environment);
util.inherits(MapController, BaseController);
this.statsBackend = statsBackend;
this.prepareContext = prepareContext;
}
module.exports = MapController;
MapController.prototype.register = function(app) {
app.get(app.base_url_mapconfig, cors(), userMiddleware, this.createGet.bind(this));
app.post(app.base_url_mapconfig, cors(), userMiddleware, this.createPost.bind(this));
app.get(app.base_url_templated + '/:template_id/jsonp', cors(), userMiddleware, this.jsonp.bind(this));
app.post(app.base_url_templated + '/:template_id', cors(), userMiddleware, this.instantiate.bind(this));
const { base_url_mapconfig, base_url_templated } = app;
const useTemplate = true;
app.get(base_url_mapconfig, this.composeCreateMapMiddleware());
app.post(base_url_mapconfig, this.composeCreateMapMiddleware());
app.get(`${base_url_templated}/:template_id/jsonp`, this.composeCreateMapMiddleware(useTemplate));
app.post(`${base_url_templated}/:template_id`, this.composeCreateMapMiddleware(useTemplate));
app.options(app.base_url_mapconfig, cors('Content-Type'));
};
MapController.prototype.createGet = function(req, res){
req.profiler.start('windshaft.createmap_get');
MapController.prototype.composeCreateMapMiddleware = function (useTemplate = false) {
const isTemplateInstantiation = useTemplate;
const useTemplateHash = useTemplate;
const includeQuery = !useTemplate;
const label = useTemplate ? 'NAMED MAP LAYERGROUP' : 'ANONYMOUS LAYERGROUP';
const addContext = !useTemplate;
this.create(req, res, function createGet$prepareConfig(err, req) {
assert.ifError(err);
if ( ! req.params.config ) {
throw new Error('layergroup GET needs a "config" parameter');
}
return JSON.parse(req.params.config);
});
return [
cors(),
userMiddleware,
allowQueryParams(['aggregation']),
this.prepareContext,
this.initProfiler(isTemplateInstantiation),
this.checkJsonContentType(),
useTemplate ? this.checkInstantiteLayergroup() : this.checkCreateLayergroup(),
useTemplate ? this.getTemplate() : this.prepareAdapterMapConfig(),
useTemplate ? this.instantiateLayergroup() : this.createLayergroup(),
this.incrementMapViewCount(),
this.augmentLayergroupData(),
this.getAffectedTables(),
this.setCacheChannel(),
this.setLastModified(),
this.setLastUpdatedTimeToLayergroup(),
this.setCacheControl(),
this.setLayerStats(),
this.setLayergroupIdHeader(useTemplateHash),
this.setDataviewsAndWidgetsUrlsToLayergroupMetadata(),
this.setAnalysesMetadataToLayergroup(includeQuery),
this.setTurboCartoMetadataToLayergroup(),
this.setAggregationMetadataToLayergroup(),
this.setTilejsonMetadataToLayergroup(),
this.setSurrogateKeyHeader(),
this.sendResponse(),
this.augmentError({ label, addContext })
];
};
MapController.prototype.createPost = function(req, res) {
req.profiler.start('windshaft.createmap_post');
MapController.prototype.initProfiler = function (isTemplateInstantiation) {
const operation = isTemplateInstantiation ? 'instance_template' : 'createmap';
this.create(req, res, function createPost$prepareConfig(err, req) {
assert.ifError(err);
if (!req.is('application/json')) {
throw new Error('layergroup POST data must be of type application/json');
}
return req.body;
});
return function initProfilerMiddleware (req, res, next) {
req.profiler.start(`windshaft-cartodb.${operation}_${req.method.toLowerCase()}`);
req.profiler.done(`${operation}.initProfilerMiddleware`);
next();
};
};
MapController.prototype.instantiate = function(req, res) {
if (req.profiler) {
req.profiler.start('windshaft-cartodb.instance_template_post');
}
this.instantiateTemplate(req, res, function prepareTemplateParams(callback) {
if (!req.is('application/json')) {
return callback(new Error('Template POST data must be of type application/json'));
MapController.prototype.checkJsonContentType = function () {
return function checkJsonContentTypeMiddleware(req, res, next) {
if (req.method === 'POST' && !req.is('application/json')) {
return next(new Error('POST data must be of type application/json'));
}
return callback(null, req.body);
});
req.profiler.done('checkJsonContentTypeMiddleware');
next();
};
};
MapController.prototype.jsonp = function(req, res) {
if (req.profiler) {
req.profiler.start('windshaft-cartodb.instance_template_get');
}
MapController.prototype.checkInstantiteLayergroup = function () {
return function checkInstantiteLayergroupMiddleware(req, res, next) {
if (req.method === 'GET') {
const { callback, config } = req.query;
this.instantiateTemplate(req, res, function prepareJsonTemplateParams(callback) {
var err = null;
if ( req.query.callback === undefined || req.query.callback.length === 0) {
err = new Error('callback parameter should be present and be a function name');
}
if (callback === undefined || callback.length === 0) {
return next(new Error('callback parameter should be present and be a function name'));
}
var templateParams = {};
if (req.query.config) {
try {
templateParams = JSON.parse(req.query.config);
} catch(e) {
err = new Error('Invalid config parameter, should be a valid JSON');
if (config) {
try {
req.body = JSON.parse(config);
} catch(e) {
return next(new Error('Invalid config parameter, should be a valid JSON'));
}
}
}
return callback(err, templateParams);
});
req.profiler.done('checkInstantiteLayergroup');
return next();
};
};
MapController.prototype.create = function(req, res, prepareConfigFn) {
var self = this;
MapController.prototype.checkCreateLayergroup = function () {
return function checkCreateLayergroupMiddleware (req, res, next) {
if (req.method === 'GET') {
const { config } = res.locals;
var mapConfig;
if (!config) {
return next(new Error('layergroup GET needs a "config" parameter'));
}
var context = {};
try {
req.body = JSON.parse(config);
} catch (err) {
return next(err);
}
}
step(
function setupParams(){
self.req2params(req, this);
},
prepareConfigFn,
function prepareAdapterMapConfig(err, requestMapConfig) {
assert.ifError(err);
context.analysisConfiguration = {
user: req.context.user,
req.profiler.done('checkCreateLayergroup');
return next();
};
};
MapController.prototype.getTemplate = function () {
return function getTemplateMiddleware (req, res, next) {
const templateParams = req.body;
const { user } = res.locals;
const mapconfigProvider = new NamedMapMapConfigProvider(
this.templateMaps,
this.pgConnection,
this.metadataBackend,
this.userLimitsApi,
this.mapConfigAdapter,
user,
req.params.template_id,
templateParams,
res.locals.auth_token,
res.locals
);
mapconfigProvider.getMapConfig((err, mapconfig, rendererParams) => {
req.profiler.done('named.getMapConfig');
if (err) {
return next(err);
}
res.locals.mapconfig = mapconfig;
res.locals.rendererParams = rendererParams;
res.locals.mapconfigProvider = mapconfigProvider;
next();
});
}.bind(this);
};
MapController.prototype.prepareAdapterMapConfig = function () {
return function prepareAdapterMapConfigMiddleware(req, res, next) {
const requestMapConfig = req.body;
const { user, dbhost, dbport, dbname, dbuser, dbpassword, api_key } = res.locals;
const context = {
analysisConfiguration: {
user,
db: {
host: req.params.dbhost,
port: req.params.dbport,
dbname: req.params.dbname,
user: req.params.dbuser,
pass: req.params.dbpassword
host: dbhost,
port: dbport,
dbname: dbname,
user: dbuser,
pass: dbpassword
},
batch: {
username: req.context.user,
apiKey: req.params.api_key
username: user,
apiKey: api_key
}
};
self.mapConfigAdapter.getMapConfig(req.context.user, requestMapConfig, req.params, context, this);
},
function createLayergroup(err, requestMapConfig) {
assert.ifError(err);
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),
this
);
},
function afterLayergroupCreate(err, layergroup) {
assert.ifError(err);
self.afterLayergroupCreate(req, res, mapConfig, layergroup, context.analysesResults, this);
},
function finish(err, layergroup) {
if (err) {
self.sendError(req, res, err, 'ANONYMOUS LAYERGROUP');
} else {
var analysesResults = context.analysesResults || [];
addDataviewsAndWidgetsUrls(req.context.user, layergroup, mapConfig.obj());
addAnalysesMetadata(req.context.user, layergroup, analysesResults, true);
res.set('X-Layergroup-Id', layergroup.layergroupid);
self.send(req, res, layergroup, 200);
}
}
);
};
this.mapConfigAdapter.getMapConfig(user, requestMapConfig, res.locals, context, (err, requestMapConfig) => {
req.profiler.done('anonymous.getMapConfig');
if (err) {
return next(err);
}
req.body = requestMapConfig;
res.locals.context = context;
next();
});
}.bind(this);
};
MapController.prototype.instantiateTemplate = function(req, res, prepareParamsFn) {
var self = this;
MapController.prototype.createLayergroup = function () {
return function createLayergroupMiddleware (req, res, next) {
const requestMapConfig = req.body;
const { context, user } = res.locals;
const datasource = context.datasource || Datasource.EmptyDatasource();
const mapconfig = new MapConfig(requestMapConfig, datasource);
const mapconfigProvider =
new CreateLayergroupMapConfigProvider(mapconfig, user, this.userLimitsApi, res.locals);
var cdbuser = req.context.user;
res.locals.mapconfig = mapconfig;
res.locals.analysesResults = context.analysesResults;
var mapConfigProvider;
var mapConfig;
step(
function setupParams(){
self.req2params(req, this);
},
function getTemplateParams() {
prepareParamsFn(this);
},
function getTemplate(err, templateParams) {
assert.ifError(err);
mapConfigProvider = new NamedMapMapConfigProvider(
self.templateMaps,
self.pgConnection,
self.metadataBackend,
self.userLimitsApi,
self.mapConfigAdapter,
cdbuser,
req.params.template_id,
templateParams,
req.query.auth_token,
req.params
);
mapConfigProvider.getMapConfig(this);
},
function createLayergroup(err, mapConfig_, rendererParams) {
assert.ifError(err);
mapConfig = mapConfig_;
self.mapBackend.createLayergroup(
mapConfig, rendererParams,
new CreateLayergroupMapConfigProvider(mapConfig, cdbuser, self.userLimitsApi, rendererParams),
this
);
},
function afterLayergroupCreate(err, layergroup) {
assert.ifError(err);
self.afterLayergroupCreate(req, res, mapConfig, layergroup, mapConfigProvider.analysesResults, this);
},
function finishTemplateInstantiation(err, layergroup) {
this.mapBackend.createLayergroup(mapconfig, res.locals, mapconfigProvider, (err, layergroup) => {
req.profiler.done('createLayergroup');
if (err) {
self.sendError(req, res, err, 'NAMED MAP LAYERGROUP');
} else {
var templateHash = self.templateMaps.fingerPrint(mapConfigProvider.template).substring(0, 8);
layergroup.layergroupid = cdbuser + '@' + templateHash + '@' + layergroup.layergroupid;
addDataviewsAndWidgetsUrls(cdbuser, layergroup, mapConfig.obj());
addAnalysesMetadata(cdbuser, layergroup, mapConfigProvider.analysesResults);
res.set('X-Layergroup-Id', layergroup.layergroupid);
self.surrogateKeysCache.tag(res, new NamedMapsCacheEntry(cdbuser, mapConfigProvider.getTemplateName()));
self.send(req, res, layergroup, 200);
return next(err);
}
}
);
res.locals.layergroup = layergroup;
next();
});
}.bind(this);
};
MapController.prototype.afterLayergroupCreate = function(req, res, mapconfig, layergroup, analysesResults, callback) {
var self = this;
MapController.prototype.instantiateLayergroup = function () {
return function instantiateLayergroupMiddleware (req, res, next) {
const { user, mapconfig, rendererParams } = res.locals;
const mapconfigProvider =
new CreateLayergroupMapConfigProvider(mapconfig, user, this.userLimitsApi, rendererParams);
var username = req.context.user;
this.mapBackend.createLayergroup(mapconfig, rendererParams, mapconfigProvider, (err, layergroup) => {
req.profiler.done('createLayergroup');
if (err) {
return next(err);
}
var tasksleft = 2; // redis key and affectedTables
var errors = [];
res.locals.layergroup = layergroup;
var done = function(err) {
if ( err ) {
errors.push('' + err);
}
if ( ! --tasksleft ) {
err = errors.length ? new Error(errors.join('\n')) : null;
callback(err, layergroup);
}
const { mapconfigProvider } = res.locals;
res.locals.analysesResults = mapconfigProvider.analysesResults;
res.locals.template = mapconfigProvider.template;
res.locals.templateName = mapconfigProvider.getTemplateName();
res.locals.context = mapconfigProvider.context;
next();
});
}.bind(this);
};
MapController.prototype.incrementMapViewCount = function () {
return function incrementMapViewCountMiddleware(req, res, next) {
const { mapconfig, user } = res.locals;
// Error won't blow up, just be logged.
this.metadataBackend.incMapviewCount(user, mapconfig.obj().stat_tag, (err) => {
req.profiler.done('incMapviewCount');
if (err) {
global.logger.log(`ERROR: failed to increment mapview count for user '${user}': ${err.message}`);
}
next();
});
}.bind(this);
};
MapController.prototype.augmentLayergroupData = function () {
return function augmentLayergroupDataMiddleware (req, res, next) {
const { layergroup } = res.locals;
// include in layergroup response the variables in serverMedata
// those variables are useful to send to the client information
// about how to reach this server or information about it
_.extend(layergroup, global.environment.serverMetadata);
next();
};
};
function getTemplateUrl(url) {
return url.https || url.http;
}
function getTilejson(tiles, grids) {
const tilejson = {
tilejson: '2.2.0',
tiles: tiles.https || tiles.http
};
// include in layergroup response the variables in serverMedata
// those variables are useful to send to the client information
// about how to reach this server or information about it
_.extend(layergroup, global.environment.serverMetadata);
if (grids) {
tilejson.grids = grids.https || grids.http;
}
// Don't wait for the mapview count increment to
// take place before proceeding. Error will be logged
// asynchronously
this.metadataBackend.incMapviewCount(username, mapconfig.obj().stat_tag, function(err) {
if (req.profiler) {
req.profiler.done('incMapviewCount');
}
if ( err ) {
global.logger.log("ERROR: failed to increment mapview count for user '" + username + "': " + err);
}
done();
});
return tilejson;
}
var sql = mapconfig.getLayers().map(function(layer) {
return layer.options.sql;
}).join(';');
MapController.prototype.setTilejsonMetadataToLayergroup = function () {
return function augmentLayergroupTilejsonMiddleware (req, res, next) {
const { layergroup, user, mapconfig } = res.locals;
var dbName = req.params.dbname;
var layergroupId = layergroup.layergroupid;
step(
function getPgConnection() {
self.pgConnection.getConnection(username, this);
},
function getAffectedTablesAndLastUpdatedTime(err, connection) {
assert.ifError(err);
QueryTables.getAffectedTablesFromQuery(connection, sql, this);
},
function handleAffectedTablesAndLastUpdatedTime(err, result) {
if (req.profiler) {
req.profiler.done('queryTablesAndLastUpdated');
}
assert.ifError(err);
// feed affected tables cache so it can be reused from, for instance, layergroup controller
self.layergroupAffectedTables.set(dbName, layergroupId, result);
var lastUpdateTime = result.getLastUpdatedAt();
lastUpdateTime = getLastUpdatedTime(analysesResults, lastUpdateTime) || lastUpdateTime;
// last update for layergroup cache buster
layergroup.layergroupid = layergroup.layergroupid + ':' + lastUpdateTime;
layergroup.last_updated = new Date(lastUpdateTime).toISOString();
if (req.method === 'GET') {
var ttl = global.environment.varnish.layergroupTtl || 86400;
res.set('Cache-Control', 'public,max-age='+ttl+',must-revalidate');
res.set('Last-Modified', (new Date()).toUTCString());
res.set('X-Cache-Channel', result.getCacheChannel());
if (result.tables && result.tables.length > 0) {
self.surrogateKeysCache.tag(res, result);
const isVectorOnlyMapConfig = mapconfig.isVectorOnlyMapConfig();
let hasMapnikLayers = false;
layergroup.metadata.layers.forEach((layerMetadata, index) => {
const layerId = mapconfig.getLayerId(index);
const rasterResource = `${layergroup.layergroupid}/${layerId}/{z}/{x}/{y}.png`;
if (mapconfig.layerType(index) === 'mapnik') {
hasMapnikLayers = true;
const vectorResource = `${layergroup.layergroupid}/${layerId}/{z}/{x}/{y}.mvt`;
const layerTilejson = {
vector: getTilejson(this.resourceLocator.getTileUrls(user, vectorResource))
};
if (!isVectorOnlyMapConfig) {
let grids = null;
const layer = mapconfig.getLayer(index);
if (layer.options.interactivity) {
const gridResource = `${layergroup.layergroupid}/${layerId}/{z}/{x}/{y}.grid.json`;
grids = this.resourceLocator.getTileUrls(user, gridResource);
}
layerTilejson.raster = getTilejson(
this.resourceLocator.getTileUrls(user, rasterResource),
grids
);
}
layerMetadata.tilejson = layerTilejson;
} else {
layerMetadata.tilejson = {
raster: getTilejson(this.resourceLocator.getTileUrls(user, rasterResource))
};
}
});
const tilejson = {};
const url = {};
if (hasMapnikLayers) {
const vectorResource = `${layergroup.layergroupid}/{z}/{x}/{y}.mvt`;
tilejson.vector = getTilejson(
this.resourceLocator.getTileUrls(user, vectorResource)
);
url.vector = getTemplateUrl(this.resourceLocator.getTemplateUrls(user, vectorResource));
if (!isVectorOnlyMapConfig) {
const rasterResource = `${layergroup.layergroupid}/{z}/{x}/{y}.png`;
tilejson.raster = getTilejson(
this.resourceLocator.getTileUrls(user, rasterResource)
);
url.raster = getTemplateUrl(this.resourceLocator.getTemplateUrls(user, rasterResource));
}
}
layergroup.metadata.tilejson = tilejson;
layergroup.metadata.url = url;
next();
}.bind(this);
};
MapController.prototype.getAffectedTables = function () {
return function getAffectedTablesMiddleware (req, res, next) {
const { dbname, layergroup, user, mapconfig } = res.locals;
this.pgConnection.getConnection(user, (err, connection) => {
if (err) {
return next(err);
}
return null;
},
function finish(err) {
done(err);
const sql = [];
mapconfig.getLayers().forEach(function(layer) {
sql.push(layer.options.sql);
if (layer.options.affected_tables) {
layer.options.affected_tables.map(function(table) {
sql.push('SELECT * FROM ' + table + ' LIMIT 0');
});
}
});
QueryTables.getAffectedTablesFromQuery(connection, sql.join(';'), (err, affectedTables) => {
req.profiler.done('getAffectedTablesFromQuery');
if (err) {
return next(err);
}
// feed affected tables cache so it can be reused from, for instance, layergroup controller
this.layergroupAffectedTables.set(dbname, layergroup.layergroupId, affectedTables);
res.locals.affectedTables = affectedTables;
next();
});
});
}.bind(this);
};
MapController.prototype.setCacheChannel = function () {
return function setCacheChannelMiddleware (req, res, next) {
const { affectedTables } = res.locals;
if (req.method === 'GET') {
res.set('X-Cache-Channel', affectedTables.getCacheChannel());
}
);
next();
};
};
MapController.prototype.setLastModified = function () {
return function setLastModifiedMiddleware (req, res, next) {
if (req.method === 'GET') {
res.set('Last-Modified', (new Date()).toUTCString());
}
next();
};
};
MapController.prototype.setLastUpdatedTimeToLayergroup = function () {
return function setLastUpdatedTimeToLayergroupMiddleware (req, res, next) {
const { affectedTables, layergroup, analysesResults } = res.locals;
var lastUpdateTime = affectedTables.getLastUpdatedAt();
lastUpdateTime = getLastUpdatedTime(analysesResults, lastUpdateTime) || lastUpdateTime;
// last update for layergroup cache buster
layergroup.layergroupid = layergroup.layergroupid + ':' + lastUpdateTime;
layergroup.last_updated = new Date(lastUpdateTime).toISOString();
next();
};
};
function getLastUpdatedTime(analysesResults, lastUpdateTime) {
@@ -338,7 +479,116 @@ function getLastUpdatedTime(analysesResults, lastUpdateTime) {
}, lastUpdateTime);
}
function addAnalysesMetadata(username, layergroup, analysesResults, includeQuery) {
MapController.prototype.setCacheControl = function () {
return function setCacheControlMiddleware (req, res, next) {
if (req.method === 'GET') {
var ttl = global.environment.varnish.layergroupTtl || 86400;
res.set('Cache-Control', 'public,max-age='+ttl+',must-revalidate');
}
next();
};
};
MapController.prototype.setLayerStats = function () {
return function setLayerStatsMiddleware(req, res, next) {
const { user, mapconfig, layergroup } = res.locals;
this.pgConnection.getConnection(user, (err, connection) => {
if (err) {
return next(err);
}
this.statsBackend.getStats(mapconfig, connection, function(err, layersStats) {
if (err) {
return next(err);
}
if (layersStats.length > 0) {
layergroup.metadata.layers.forEach(function (layer, index) {
layer.meta.stats = layersStats[index];
});
}
next();
});
});
}.bind(this);
};
MapController.prototype.setLayergroupIdHeader = function (useTemplateHash) {
return function setLayergroupIdHeaderMiddleware (req, res, next) {
const { layergroup, user, template } = res.locals;
if (useTemplateHash) {
var templateHash = this.templateMaps.fingerPrint(template).substring(0, 8);
layergroup.layergroupid = `${user}@${templateHash}@${layergroup.layergroupid}`;
}
res.set('X-Layergroup-Id', layergroup.layergroupid);
next();
}.bind(this);
};
MapController.prototype.setDataviewsAndWidgetsUrlsToLayergroupMetadata = function () {
return function setDataviewsAndWidgetsUrlsToLayergroupMetadataMiddleware (req, res, next) {
const { layergroup, user, mapconfig } = res.locals;
this.addDataviewsAndWidgetsUrls(user, layergroup, mapconfig.obj());
next();
}.bind(this);
};
// TODO this should take into account several URL patterns
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: this.resourceLocator.getUrls(username, resource)
};
}.bind(this));
};
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) {
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] = {
type: mapConfigLayer.options.widgets[widgetName].type,
url: this.resourceLocator.getUrls(username, resource)
};
}.bind(this));
}
return layer;
}.bind(this));
}
};
MapController.prototype.setAnalysesMetadataToLayergroup = function (includeQuery) {
return function setAnalysesMetadataToLayergroupMiddleware (req, res, next) {
const { layergroup, user, analysesResults = [] } = res.locals;
this.addAnalysesMetadata(user, layergroup, analysesResults, includeQuery);
next();
}.bind(this);
};
MapController.prototype.addAnalysesMetadata = function(username, layergroup, analysesResults, includeQuery) {
includeQuery = includeQuery || false;
analysesResults = analysesResults || [];
layergroup.metadata.analyses = [];
@@ -351,7 +601,7 @@ function addAnalysesMetadata(username, layergroup, analysesResults, includeQuery
var nodeResource = layergroup.layergroupid + '/analysis/node/' + node.id();
var nodeRepr = {
status: node.getStatus(),
url: getUrls(username, nodeResource)
url: this.resourceLocator.getUrls(username, nodeResource)
};
if (includeQuery) {
nodeRepr.query = node.getQuery();
@@ -363,59 +613,117 @@ function addAnalysesMetadata(username, layergroup, analysesResults, includeQuery
}
return nodesIdMap;
}, {})
}.bind(this), {})
});
});
}
}.bind(this));
};
// TODO this should take into account several URL patterns
function addDataviewsAndWidgetsUrls(username, layergroup, mapConfig) {
addDataviewsUrls(username, layergroup, mapConfig);
addWidgetsUrl(username, layergroup, mapConfig);
}
MapController.prototype.setTurboCartoMetadataToLayergroup = function () {
return function setTurboCartoMetadataToLayergroupMiddleware (req, res, next) {
const { layergroup, mapconfig, context } = res.locals;
function addDataviewsUrls(username, layergroup, mapConfig) {
layergroup.metadata.dataviews = layergroup.metadata.dataviews || {};
var dataviews = mapConfig.dataviews || {};
addTurboCartoContextMetadata(layergroup, mapconfig.obj(), context);
Object.keys(dataviews).forEach(function(dataviewName) {
var resource = layergroup.layergroupid + '/dataview/' + dataviewName;
layergroup.metadata.dataviews[dataviewName] = {
url: getUrls(username, resource)
};
});
}
next();
};
};
function addWidgetsUrl(username, layergroup, mapConfig) {
function addTurboCartoContextMetadata(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) {
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] = {
type: mapConfigLayer.options.widgets[widgetName].type,
url: getUrls(username, resource)
};
});
if (context.turboCarto && Array.isArray(context.turboCarto.layers)) {
layer.meta.cartocss_meta = context.turboCarto.layers[layerIndex];
}
return layer;
});
}
}
function getUrls(username, resource) {
var cdnUrl = global.environment.serverMetadata && global.environment.serverMetadata.cdn_url;
if (cdnUrl) {
return {
http: 'http://' + cdnUrl.http + '/' + username + '/api/v1/map/' + resource,
https: 'https://' + cdnUrl.https + '/' + username + '/api/v1/map/' + resource
};
} else {
var port = global.environment.port;
return {
http: 'http://' + username + '.' + 'localhost.lan:' + port + '/api/v1/map/' + resource
};
// TODO: see how evolve this function, it's a good candidate to be refactored
MapController.prototype.setAggregationMetadataToLayergroup = function () {
return function setAggregationMetadataToLayergroupMiddleware (req, res, next) {
const { layergroup, mapconfig, context } = res.locals;
addAggregationContextMetadata(layergroup, mapconfig.obj(), context);
next();
};
};
function addAggregationContextMetadata(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.aggregation && Array.isArray(context.aggregation.layers)) {
layer.meta.aggregation = context.aggregation.layers[layerIndex];
}
return layer;
});
}
}
MapController.prototype.setSurrogateKeyHeader = function () {
return function setSurrogateKeyHeaderMiddleware(req, res, next) {
const { affectedTables, user, templateName } = res.locals;
if (req.method === 'GET' && affectedTables.tables && affectedTables.tables.length > 0) {
this.surrogateKeysCache.tag(res, affectedTables);
}
if (templateName) {
this.surrogateKeysCache.tag(res, new NamedMapsCacheEntry(user, templateName));
}
next();
}.bind(this);
};
MapController.prototype.sendResponse = function () {
return function sendResponseMiddleware (req, res) {
req.profiler.done('res');
const { layergroup } = res.locals;
res.status(200);
if (req.query && req.query.callback) {
res.jsonp(layergroup);
} else {
res.json(layergroup);
}
};
};
MapController.prototype.augmentError = function (options) {
const { addContext = false, label = 'MAPS CONTROLLER' } = options;
return function augmentErrorMiddleware (err, req, res, next) {
req.profiler.done('error');
const { mapconfig } = res.locals;
if (addContext) {
err = Number.isFinite(err.layerIndex) ? populateError(err, mapconfig) : err;
}
err.label = label;
next(err);
};
};
function populateError(err, mapConfig) {
var error = new Error(err.message);
error.http_status = err.http_status;
if (!err.http_status && err.message.indexOf('column "the_geom_webmercator" does not exist') >= 0) {
error.http_status = 400;
}
error.type = 'layer';
error.subtype = err.message.indexOf('Postgis Plugin') >= 0 ? 'query' : undefined;
error.layer = {
id: mapConfig.getLayerId(err.layerIndex),
index: err.layerIndex,
type: mapConfig.layerType(err.layerIndex)
};
return error;
}

View File

@@ -1,190 +1,10 @@
var step = require('step');
var assert = require('assert');
var _ = require('underscore');
var NamedMapsCacheEntry = require('../cache/model/named_maps_entry');
const NamedMapsCacheEntry = require('../cache/model/named_maps_entry');
const cors = require('../middleware/cors');
const userMiddleware = require('../middleware/user');
const allowQueryParams = require('../middleware/allow-query-params');
const vectorError = require('../middleware/vector-error');
var util = require('util');
var BaseController = require('./base');
var cors = require('../middleware/cors');
var userMiddleware = require('../middleware/user');
function NamedMapsController(authApi, pgConnection, namedMapProviderCache, tileBackend, previewBackend,
surrogateKeysCache, tablesExtentApi, metadataBackend) {
BaseController.call(this, authApi, pgConnection);
this.namedMapProviderCache = namedMapProviderCache;
this.tileBackend = tileBackend;
this.previewBackend = previewBackend;
this.surrogateKeysCache = surrogateKeysCache;
this.tablesExtentApi = tablesExtentApi;
this.metadataBackend = metadataBackend;
}
util.inherits(NamedMapsController, BaseController);
module.exports = NamedMapsController;
NamedMapsController.prototype.register = function(app) {
app.get(app.base_url_templated +
'/:template_id/:layer/:z/:x/:y.(:format)', cors(), userMiddleware,
this.tile.bind(this));
app.get(app.base_url_mapconfig +
'/static/named/:template_id/:width/:height.:format', cors(), userMiddleware,
this.staticMap.bind(this));
};
NamedMapsController.prototype.sendResponse = function(req, res, resource, headers, namedMapProvider) {
this.surrogateKeysCache.tag(res, new NamedMapsCacheEntry(req.context.user, namedMapProvider.getTemplateName()));
res.set('Content-Type', headers['content-type'] || headers['Content-Type'] || 'image/png');
res.set('Cache-Control', 'public,max-age=7200,must-revalidate');
var self = this;
step(
function getAffectedTablesAndLastUpdatedTime() {
namedMapProvider.getAffectedTablesAndLastUpdatedTime(this);
},
function sendResponse(err, result) {
req.profiler.done('affectedTables');
if (err) {
global.logger.log('ERROR generating cache channel: ' + err);
}
if (!result || !!result.tables) {
// we increase cache control as we can invalidate it
res.set('Cache-Control', 'public,max-age=31536000');
var lastModifiedDate;
if (Number.isFinite(result.lastUpdatedTime)) {
lastModifiedDate = new Date(result.getLastUpdatedAt());
} else {
lastModifiedDate = new Date();
}
res.set('Last-Modified', lastModifiedDate.toUTCString());
res.set('X-Cache-Channel', result.getCacheChannel());
if (result.tables.length > 0) {
self.surrogateKeysCache.tag(res, result);
}
}
self.send(req, res, resource, 200);
}
);
};
NamedMapsController.prototype.tile = function(req, res) {
var self = this;
var cdbUser = req.context.user;
var namedMapProvider;
step(
function reqParams() {
self.req2params(req, this);
},
function getNamedMapProvider(err) {
assert.ifError(err);
self.namedMapProviderCache.get(
cdbUser,
req.params.template_id,
req.query.config,
req.query.auth_token,
req.params,
this
);
},
function getTile(err, _namedMapProvider) {
assert.ifError(err);
namedMapProvider = _namedMapProvider;
self.tileBackend.getTile(namedMapProvider, req.params, this);
},
function handleImage(err, tile, headers, stats) {
if (req.profiler) {
req.profiler.add(stats);
}
if (err) {
self.sendError(req, res, err, 'NAMED_MAP_TILE');
} else {
self.sendResponse(req, res, tile, headers, namedMapProvider);
}
}
);
};
NamedMapsController.prototype.staticMap = function(req, res) {
var self = this;
var cdbUser = req.context.user;
var format = req.params.format === 'jpg' ? 'jpeg' : 'png';
req.params.format = 'png';
req.params.layer = 'all';
var namedMapProvider;
step(
function reqParams() {
self.req2params(req, this);
},
function getNamedMapProvider(err) {
assert.ifError(err);
self.namedMapProviderCache.get(
cdbUser,
req.params.template_id,
req.query.config,
req.query.auth_token,
req.params,
this
);
},
function prepareImageOptions(err, _namedMapProvider) {
assert.ifError(err);
namedMapProvider = _namedMapProvider;
self.getStaticImageOptions(cdbUser, req.params, namedMapProvider, this);
},
function getImage(err, imageOpts) {
assert.ifError(err);
var width = +req.params.width;
var height = +req.params.height;
if (!_.isUndefined(imageOpts.zoom) && imageOpts.center) {
self.previewBackend.getImage(
namedMapProvider, format, width, height, imageOpts.zoom, imageOpts.center, this);
} else {
self.previewBackend.getImage(
namedMapProvider, format, width, height, imageOpts.bounds, this);
}
},
function incrementMapViews(err, image, headers, stats) {
assert.ifError(err);
var next = this;
namedMapProvider.getMapConfig(function(mapConfigErr, mapConfig) {
self.metadataBackend.incMapviewCount(cdbUser, mapConfig.obj().stat_tag, function(sErr) {
if (err) {
global.logger.log("ERROR: failed to increment mapview count for user '%s': %s", cdbUser, sErr);
}
next(err, image, headers, stats);
});
});
},
function handleImage(err, image, headers, stats) {
if (req.profiler) {
req.profiler.done('render-' + format);
req.profiler.add(stats || {});
}
if (err) {
self.sendError(req, res, err, 'STATIC_VIZ_MAP');
} else {
self.sendResponse(req, res, image, headers, namedMapProvider);
}
}
);
};
var DEFAULT_ZOOM_CENTER = {
const DEFAULT_ZOOM_CENTER = {
zoom: 1,
center: {
lng: 0,
@@ -196,88 +16,368 @@ function numMapper(n) {
return +n;
}
NamedMapsController.prototype.getStaticImageOptions = function(cdbUser, params, namedMapProvider, callback) {
var self = this;
function getRequestParams(locals) {
const params = Object.assign({}, locals);
if ([params.zoom, params.lon, params.lat].map(numMapper).every(Number.isFinite)) {
return callback(null, {
zoom: params.zoom,
center: {
lng: params.lon,
lat: params.lat
}
});
}
delete params.template;
delete params.affectedTablesAndLastUpdate;
delete params.namedMapProvider;
delete params.allowedQueryParams;
if (params.bbox) {
var bbox = params.bbox.split(',').map(numMapper);
if (bbox.length === 4 && bbox.every(Number.isFinite)) {
return callback(null, {
bounds: {
west: bbox[0],
south: bbox[1],
east: bbox[2],
north: bbox[3]
}
});
}
}
return params;
}
step(
function getTemplate() {
namedMapProvider.getTemplate(this);
},
function handleTemplateView(err, template) {
assert.ifError(err);
function NamedMapsController(prepareContext, namedMapProviderCache, tileBackend, previewBackend,
surrogateKeysCache, tablesExtentApi, metadataBackend) {
this.namedMapProviderCache = namedMapProviderCache;
this.tileBackend = tileBackend;
this.previewBackend = previewBackend;
this.surrogateKeysCache = surrogateKeysCache;
this.tablesExtentApi = tablesExtentApi;
this.metadataBackend = metadataBackend;
this.prepareContext = prepareContext;
}
if (template.view) {
var zoomCenter = templateZoomCenter(template.view);
if (zoomCenter) {
if (Number.isFinite(+params.zoom)) {
zoomCenter.zoom = +params.zoom;
}
return zoomCenter;
}
module.exports = NamedMapsController;
var bounds = templateBounds(template.view);
if (bounds) {
return bounds;
}
}
NamedMapsController.prototype.register = function(app) {
app.get(
app.base_url_templated + '/:template_id/:layer/:z/:x/:y.(:format)',
cors(),
userMiddleware,
this.prepareContext,
this.getNamedMapProvider('NAMED_MAP_TILE'),
this.getAffectedTables(),
this.getTile('NAMED_MAP_TILE'),
this.setSurrogateKey(),
this.setCacheChannelHeader(),
this.setLastModifiedHeader(),
this.setCacheControlHeader(),
this.setContentTypeHeader(),
this.respond(),
vectorError()
);
return false;
},
function estimateBoundsIfNoImageOpts(err, imageOpts) {
if (imageOpts) {
return imageOpts;
}
var next = this;
namedMapProvider.getAffectedTablesAndLastUpdatedTime(function(err, affectedTablesAndLastUpdate) {
if (err) {
return next(null);
}
var affectedTables = affectedTablesAndLastUpdate.tables || [];
if (affectedTables.length === 0) {
return next(null);
}
self.tablesExtentApi.getBounds(cdbUser, affectedTables, function(err, result) {
return next(null, result);
});
});
},
function returnCallback(err, imageOpts) {
return callback(err, imageOpts || DEFAULT_ZOOM_CENTER);
}
app.get(
app.base_url_mapconfig + '/static/named/:template_id/:width/:height.:format',
cors(),
userMiddleware,
allowQueryParams(['layer', 'zoom', 'lon', 'lat', 'bbox']),
this.prepareContext,
this.getNamedMapProvider('STATIC_VIZ_MAP'),
this.getAffectedTables(),
this.getTemplate('STATIC_VIZ_MAP'),
this.prepareLayerFilterFromPreviewLayers('STATIC_VIZ_MAP'),
this.getStaticImageOptions(),
this.getImage('STATIC_VIZ_MAP'),
this.incrementMapViews(),
this.setSurrogateKey(),
this.setCacheChannelHeader(),
this.setLastModifiedHeader(),
this.setCacheControlHeader(),
this.setContentTypeHeader(),
this.respond()
);
};
NamedMapsController.prototype.getNamedMapProvider = function (label) {
return function getNamedMapProviderMiddleware (req, res, next) {
const { user } = res.locals;
const { config, auth_token } = req.query;
const { template_id } = req.params;
// We force always the tile to be generated using PNG because
// is the only format we support by now
res.locals.format = 'png';
res.locals.layer = res.locals.layer || 'all';
const params = getRequestParams(res.locals);
this.namedMapProviderCache.get(user, template_id, config, auth_token, params, (err, namedMapProvider) => {
if (err) {
err.label = label;
return next(err);
}
res.locals.namedMapProvider = namedMapProvider;
next();
});
}.bind(this);
};
NamedMapsController.prototype.getAffectedTables = function () {
return function getAffectedTables (req, res, next) {
const { namedMapProvider } = res.locals;
namedMapProvider.getAffectedTablesAndLastUpdatedTime((err, affectedTablesAndLastUpdate) => {
req.profiler.done('affectedTables');
if (err) {
return next(err);
}
res.locals.affectedTablesAndLastUpdate = affectedTablesAndLastUpdate;
next();
});
}.bind(this);
};
NamedMapsController.prototype.getTemplate = function (label) {
return function getTemplateMiddleware (req, res, next) {
const { namedMapProvider } = res.locals;
namedMapProvider.getTemplate((err, template) => {
if (err) {
err.label = label;
return next(err);
}
res.locals.template = template;
next();
});
};
};
NamedMapsController.prototype.prepareLayerFilterFromPreviewLayers = function (label) {
return function prepareLayerFilterFromPreviewLayersMiddleware (req, res, next) {
const { user, template } = res.locals;
const { template_id } = req.params;
const { config, auth_token } = req.query;
if (!template || !template.view || !template.view.preview_layers) {
return next();
}
var previewLayers = template.view.preview_layers;
var layerVisibilityFilter = [];
template.layergroup.layers.forEach((layer, index) => {
if (previewLayers[''+index] !== false && previewLayers[layer.id] !== false) {
layerVisibilityFilter.push(''+index);
}
});
if (!layerVisibilityFilter.length) {
return next();
}
const params = getRequestParams(res.locals);
// overwrites 'all' default filter
params.layer = layerVisibilityFilter.join(',');
// recreates the provider
this.namedMapProviderCache.get(user, template_id, config, auth_token, params, (err, provider) => {
if (err) {
err.label = label;
return next(err);
}
res.locals.namedMapProvider = provider;
next();
});
}.bind(this);
};
NamedMapsController.prototype.getTile = function (label) {
return function getTileMiddleware (req, res, next) {
const { namedMapProvider } = res.locals;
this.tileBackend.getTile(namedMapProvider, req.params, (err, tile, headers, stats) => {
req.profiler.add(stats);
if (err) {
err.label = label;
return next(err);
}
res.locals.body = tile;
res.locals.headers = headers;
res.locals.stats = stats;
next();
});
}.bind(this);
};
NamedMapsController.prototype.getStaticImageOptions = function () {
return function getStaticImageOptionsMiddleware(req, res, next) {
const { user, namedMapProvider, template } = res.locals;
const imageOpts = getImageOptions(res.locals, template);
if (imageOpts) {
res.locals.imageOpts = imageOpts;
return next();
}
res.locals.imageOpts = DEFAULT_ZOOM_CENTER;
namedMapProvider.getAffectedTablesAndLastUpdatedTime((err, affectedTablesAndLastUpdate) => {
if (err) {
return next();
}
var affectedTables = affectedTablesAndLastUpdate.tables || [];
if (affectedTables.length === 0) {
return next();
}
this.tablesExtentApi.getBounds(user, affectedTables, (err, bounds) => {
if (err) {
return next();
}
res.locals.imageOpts = bounds;
return next();
});
});
}.bind(this);
};
function getImageOptions (params, template) {
const { zoom, lon, lat, bbox } = params;
let imageOpts = getImageOptionsFromCoordinates(zoom, lon, lat);
if (imageOpts) {
return imageOpts;
}
imageOpts = getImageOptionsFromBoundingBox(bbox);
if (imageOpts) {
return imageOpts;
}
imageOpts = getImageOptionsFromTemplate(template, zoom);
if (imageOpts) {
return imageOpts;
}
}
function getImageOptionsFromCoordinates (zoom, lon, lat) {
if ([zoom, lon, lat].map(numMapper).every(Number.isFinite)) {
return {
zoom: zoom,
center: {
lng: lon,
lat: lat
}
};
}
}
function getImageOptionsFromTemplate (template, zoom) {
if (template.view) {
var zoomCenter = templateZoomCenter(template.view);
if (zoomCenter) {
if (Number.isFinite(+zoom)) {
zoomCenter.zoom = +zoom;
}
return zoomCenter;
}
var bounds = templateBounds(template.view);
if (bounds) {
return bounds;
}
}
}
function getImageOptionsFromBoundingBox (bbox = '') {
var _bbox = bbox.split(',').map(numMapper);
if (_bbox.length === 4 && _bbox.every(Number.isFinite)) {
return {
bounds: {
west: _bbox[0],
south: _bbox[1],
east: _bbox[2],
north: _bbox[3]
}
};
}
}
NamedMapsController.prototype.getImage = function (label) {
return function getImageMiddleware (req, res, next) {
const { imageOpts, namedMapProvider } = res.locals;
const { zoom, center, bounds } = imageOpts;
let { width, height } = req.params;
width = +width;
height = +height;
const format = req.params.format === 'jpg' ? 'jpeg' : 'png';
if (zoom !== undefined && center) {
return this.previewBackend.getImage(namedMapProvider, format, width, height, zoom, center,
(err, image, headers, stats) => {
if (err) {
err.label = label;
return next(err);
}
res.locals.body = image;
res.locals.headers = headers;
res.locals.stats = stats;
next();
});
}
this.previewBackend.getImage(namedMapProvider, format, width, height, bounds, (err, image, headers, stats) => {
if (err) {
err.label = label;
return next(err);
}
res.locals.body = image;
res.locals.headers = headers;
res.locals.stats = stats;
next();
});
}.bind(this);
};
function incrementMapViewsError (ctx) {
return `ERROR: failed to increment mapview count for user '${ctx.user}': ${ctx.err}`;
}
NamedMapsController.prototype.incrementMapViews = function () {
return function incrementMapViewsMiddleware(req, res, next) {
const { user, namedMapProvider } = res.locals;
namedMapProvider.getMapConfig((err, mapConfig) => {
if (err) {
global.logger.log(incrementMapViewsError({ user, err }));
return next();
}
const statTag = mapConfig.obj().stat_tag;
this.metadataBackend.incMapviewCount(user, statTag, (err) => {
if (err) {
global.logger.log(incrementMapViewsError({ user, err }));
}
next();
});
});
}.bind(this);
};
function templateZoomCenter(view) {
if (!_.isUndefined(view.zoom) && view.center) {
if (view.zoom !== undefined && view.center) {
return {
zoom: view.zoom,
center: view.center
@@ -288,9 +388,8 @@ function templateZoomCenter(view) {
function templateBounds(view) {
if (view.bounds) {
var hasAllBounds = _.every(['west', 'south', 'east', 'north'], function(prop) {
return Number.isFinite(view.bounds[prop]);
});
var hasAllBounds = ['west', 'south', 'east', 'north'].every(prop => Number.isFinite(view.bounds[prop]));
if (hasAllBounds) {
return {
bounds: {
@@ -306,3 +405,86 @@ function templateBounds(view) {
}
return false;
}
NamedMapsController.prototype.setCacheChannelHeader = function () {
return function setCacheChannelHeaderMiddleware (req, res, next) {
const { affectedTablesAndLastUpdate } = res.locals;
if (!affectedTablesAndLastUpdate || !!affectedTablesAndLastUpdate.tables) {
res.set('X-Cache-Channel', affectedTablesAndLastUpdate.getCacheChannel());
}
next();
};
};
NamedMapsController.prototype.setSurrogateKey = function () {
return function setSurrogateKeyMiddleware(req, res, next) {
const { user, namedMapProvider, affectedTablesAndLastUpdate } = res.locals;
this.surrogateKeysCache.tag(res, new NamedMapsCacheEntry(user, namedMapProvider.getTemplateName()));
if (!affectedTablesAndLastUpdate || !!affectedTablesAndLastUpdate.tables) {
if (affectedTablesAndLastUpdate.tables.length > 0) {
this.surrogateKeysCache.tag(res, affectedTablesAndLastUpdate);
}
}
next();
}.bind(this);
};
NamedMapsController.prototype.setLastModifiedHeader = function () {
return function setLastModifiedHeaderMiddleware(req, res, next) {
const { affectedTablesAndLastUpdate } = res.locals;
if (!affectedTablesAndLastUpdate || !!affectedTablesAndLastUpdate.tables) {
var lastModifiedDate;
if (Number.isFinite(affectedTablesAndLastUpdate.lastUpdatedTime)) {
lastModifiedDate = new Date(affectedTablesAndLastUpdate.getLastUpdatedAt());
} else {
lastModifiedDate = new Date();
}
res.set('Last-Modified', lastModifiedDate.toUTCString());
}
next();
};
};
NamedMapsController.prototype.setCacheControlHeader = function () {
return function setCacheControlHeaderMiddleware(req, res, next) {
const { affectedTablesAndLastUpdate } = res.locals;
res.set('Cache-Control', 'public,max-age=7200,must-revalidate');
if (!affectedTablesAndLastUpdate || !!affectedTablesAndLastUpdate.tables) {
// we increase cache control as we can invalidate it
res.set('Cache-Control', 'public,max-age=31536000');
}
next();
};
};
NamedMapsController.prototype.setContentTypeHeader = function () {
return function setContentTypeHeaderMiddleware(req, res, next) {
const { headers = {} } = res.locals;
res.set('Content-Type', headers['content-type'] || headers['Content-Type'] || 'image/png');
next();
};
};
NamedMapsController.prototype.respond = function () {
return function respondMiddleware (req, res) {
const { body, stats = {}, format } = res.locals;
req.profiler.done('render-' + format);
req.profiler.add(stats);
res.status(200);
res.send(body);
};
};

View File

@@ -1,13 +1,6 @@
var step = require('step');
var assert = require('assert');
var templateName = require('../backends/template_maps').templateName;
var util = require('util');
var BaseController = require('./base');
var cors = require('../middleware/cors');
var userMiddleware = require('../middleware/user');
const { templateName } = require('../backends/template_maps');
const cors = require('../middleware/cors');
const userMiddleware = require('../middleware/user');
/**
* @param {AuthApi} authApi
@@ -15,189 +8,197 @@ var userMiddleware = require('../middleware/user');
* @param {TemplateMaps} templateMaps
* @constructor
*/
function NamedMapsAdminController(authApi, pgConnection, templateMaps) {
BaseController.call(this, authApi, pgConnection);
function NamedMapsAdminController(authApi, templateMaps) {
this.authApi = authApi;
this.templateMaps = templateMaps;
}
util.inherits(NamedMapsAdminController, BaseController);
module.exports = NamedMapsAdminController;
NamedMapsAdminController.prototype.register = function(app) {
app.post(app.base_url_templated, cors(), userMiddleware, this.create.bind(this));
app.put(app.base_url_templated + '/:template_id', cors(), userMiddleware, this.update.bind(this));
app.get(app.base_url_templated + '/:template_id', cors(), userMiddleware, this.retrieve.bind(this));
app.delete(app.base_url_templated + '/:template_id', cors(), userMiddleware, this.destroy.bind(this));
app.get(app.base_url_templated, cors(), userMiddleware, this.list.bind(this));
app.options(app.base_url_templated + '/:template_id', cors('Content-Type'));
};
NamedMapsAdminController.prototype.register = function (app) {
const { base_url_templated } = app;
NamedMapsAdminController.prototype.create = function(req, res) {
var self = this;
app.post(
`${base_url_templated}/`,
cors(),
userMiddleware,
this.checkContentType('POST', 'POST TEMPLATE'),
this.authorizedByAPIKey('create', 'POST TEMPLATE'),
this.create()
);
var cdbuser = req.context.user;
app.put(
`${base_url_templated}/:template_id`,
cors(),
userMiddleware,
this.checkContentType('PUT', 'PUT TEMPLATE'),
this.authorizedByAPIKey('update', 'PUT TEMPLATE'),
this.update()
);
step(
function checkPerms(){
self.authApi.authorizedByAPIKey(cdbuser, req, this);
},
function addTemplate(err, authenticated) {
assert.ifError(err);
ifUnauthenticated(authenticated, 'Only authenticated users can get template maps');
ifInvalidContentType(req, 'template POST data must be of type application/json');
var cfg = req.body;
self.templateMaps.addTemplate(cdbuser, cfg, this);
},
function prepareResponse(err, tpl_id){
assert.ifError(err);
return { template_id: tpl_id };
},
finishFn(self, req, res, 'POST TEMPLATE')
app.get(
`${base_url_templated}/:template_id`,
cors(),
userMiddleware,
this.authorizedByAPIKey('get', 'GET TEMPLATE'),
this.retrieve()
);
app.delete(
`${base_url_templated}/:template_id`,
cors(),
userMiddleware,
this.authorizedByAPIKey('delete', 'DELETE TEMPLATE'),
this.destroy()
);
app.get(
`${base_url_templated}/`,
cors(),
userMiddleware,
this.authorizedByAPIKey('list', 'GET TEMPLATE LIST'),
this.list()
);
app.options(
`${base_url_templated}/:template_id`,
cors('Content-Type')
);
};
NamedMapsAdminController.prototype.update = function(req, res) {
var self = this;
NamedMapsAdminController.prototype.authorizedByAPIKey = function (action, label) {
return function authorizedByAPIKeyMiddleware (req, res, next) {
const { user } = res.locals;
var cdbuser = req.context.user;
var template;
var tpl_id;
this.authApi.authorizedByAPIKey(user, req, (err, authenticated) => {
if (err) {
return next(err);
}
step(
function checkPerms(){
self.authApi.authorizedByAPIKey(cdbuser, req, this);
},
function updateTemplate(err, authenticated) {
assert.ifError(err);
ifUnauthenticated(authenticated, 'Only authenticated user can update templated maps');
ifInvalidContentType(req, 'template PUT data must be of type application/json');
if (!authenticated) {
const error = new Error(`Only authenticated user can ${action} templated maps`);
error.http_status = 403;
error.label = label;
return next(error);
}
template = req.body;
tpl_id = templateName(req.params.template_id);
self.templateMaps.updTemplate(cdbuser, tpl_id, template, this);
},
function prepareResponse(err){
assert.ifError(err);
return { template_id: tpl_id };
},
finishFn(self, req, res, 'PUT TEMPLATE')
);
next();
});
}.bind(this);
};
NamedMapsAdminController.prototype.retrieve = function(req, res) {
var self = this;
NamedMapsAdminController.prototype.checkContentType = function (action, label) {
return function checkContentTypeMiddleware (req, res, next) {
if (!req.is('application/json')) {
const error = new Error(`template ${action} data must be of type application/json`);
error.label = label;
return next(error);
}
next();
};
};
if (req.profiler) {
NamedMapsAdminController.prototype.create = function () {
return function createTemplateMiddleware (req, res, next) {
const { user } = res.locals;
const template = req.body;
this.templateMaps.addTemplate(user, template, (err, templateId) => {
if (err) {
return next(err);
}
res.status(200);
const method = req.query.callback ? 'jsonp' : 'json';
res[method]({ template_id: templateId });
});
}.bind(this);
};
NamedMapsAdminController.prototype.update = function () {
return function updateTemplateMiddleware (req, res, next) {
const { user } = res.locals;
const template = req.body;
const templateId = templateName(req.params.template_id);
this.templateMaps.updTemplate(user, templateId, template, (err) => {
if (err) {
return next(err);
}
res.status(200);
const method = req.query.callback ? 'jsonp' : 'json';
res[method]({ template_id: templateId });
});
}.bind(this);
};
NamedMapsAdminController.prototype.retrieve = function () {
return function retrieveTemplateMiddleware (req, res, next) {
req.profiler.start('windshaft-cartodb.get_template');
}
var cdbuser = req.context.user;
var tpl_id;
step(
function checkPerms(){
self.authApi.authorizedByAPIKey(cdbuser, req, this);
},
function getTemplate(err, authenticated) {
assert.ifError(err);
ifUnauthenticated(authenticated, 'Only authenticated users can get template maps');
const { user } = res.locals;
const templateId = templateName(req.params.template_id);
tpl_id = templateName(req.params.template_id);
self.templateMaps.getTemplate(cdbuser, tpl_id, this);
},
function prepareResponse(err, tpl_val) {
assert.ifError(err);
if ( ! tpl_val ) {
err = new Error("Cannot find template '" + tpl_id + "' of user '" + cdbuser + "'");
err.http_status = 404;
throw err;
this.templateMaps.getTemplate(user, templateId, (err, template) => {
if (err) {
return next(err);
}
if (!template) {
const error = new Error(`Cannot find template '${templateId}' of user '${user}'`);
error.http_status = 404;
return next(error);
}
// auth_id was added by ourselves,
// so we remove it before returning to the user
delete tpl_val.auth_id;
return { template: tpl_val };
},
finishFn(self, req, res, 'GET TEMPLATE')
);
delete template.auth_id;
res.status(200);
const method = req.query.callback ? 'jsonp' : 'json';
res[method]({ template });
});
}.bind(this);
};
NamedMapsAdminController.prototype.destroy = function(req, res) {
var self = this;
if (req.profiler) {
NamedMapsAdminController.prototype.destroy = function () {
return function destroyTemplateMiddleware (req, res, next) {
req.profiler.start('windshaft-cartodb.delete_template');
}
var cdbuser = req.context.user;
var tpl_id;
step(
function checkPerms(){
self.authApi.authorizedByAPIKey(cdbuser, req, this);
},
function deleteTemplate(err, authenticated) {
assert.ifError(err);
ifUnauthenticated(authenticated, 'Only authenticated users can delete template maps');
const { user } = res.locals;
const templateId = templateName(req.params.template_id);
tpl_id = templateName(req.params.template_id);
self.templateMaps.delTemplate(cdbuser, tpl_id, this);
},
function prepareResponse(err/*, tpl_val*/){
assert.ifError(err);
return '';
},
finishFn(self, req, res, 'DELETE TEMPLATE', 204)
);
this.templateMaps.delTemplate(user, templateId, (err/* , tpl_val */) => {
if (err) {
return next(err);
}
res.status(204);
const method = req.query.callback ? 'jsonp' : 'json';
res[method]('');
});
}.bind(this);
};
NamedMapsAdminController.prototype.list = function(req, res) {
var self = this;
if ( req.profiler ) {
NamedMapsAdminController.prototype.list = function () {
return function listTemplatesMiddleware (req, res, next) {
req.profiler.start('windshaft-cartodb.get_template_list');
}
var cdbuser = req.context.user;
const { user } = res.locals;
step(
function checkPerms(){
self.authApi.authorizedByAPIKey(cdbuser, req, this);
},
function listTemplates(err, authenticated) {
assert.ifError(err);
ifUnauthenticated(authenticated, 'Only authenticated user can list templated maps');
this.templateMaps.listTemplates(user, (err, templateIds) => {
if (err) {
return next(err);
}
self.templateMaps.listTemplates(cdbuser, this);
},
function prepareResponse(err, tpl_ids){
assert.ifError(err);
return { template_ids: tpl_ids };
},
finishFn(self, req, res, 'GET TEMPLATE LIST')
);
res.status(200);
const method = req.query.callback ? 'jsonp' : 'json';
res[method]({ template_ids: templateIds });
});
}.bind(this);
};
function finishFn(controller, req, res, description, status) {
return function finish(err, response){
if (err) {
controller.sendError(req, res, err, description);
} else {
controller.send(req, res, response, status || 200);
}
};
}
function ifUnauthenticated(authenticated, description) {
if (!authenticated) {
var err = new Error(description);
err.http_status = 403;
throw err;
}
}
function ifInvalidContentType(req, description) {
if (!req.is('application/json')) {
throw new Error(description);
}
}

View File

@@ -0,0 +1,9 @@
module.exports = function allowQueryParams(params) {
if (!Array.isArray(params)) {
throw new Error('allowQueryParams must receive an Array of params');
}
return function allowQueryParamsMiddleware(req, res, next) {
res.locals.allowedQueryParams = params;
next();
};
};

View File

@@ -0,0 +1,20 @@
module.exports = function authorizeMiddleware (authApi) {
return function (req, res, next) {
req.profiler.done('req2params.setup');
authApi.authorize(req, res, (err, authorized) => {
req.profiler.done('authorize');
if (err) {
return next(err);
}
if(!authorized) {
err = new Error("Sorry, you are unauthorized (permission denied)");
err.http_status = 403;
return next(err);
}
return next();
});
};
};

View File

@@ -0,0 +1,32 @@
const _ = require('underscore');
// Whitelist query parameters and attach format
const REQUEST_QUERY_PARAMS_WHITELIST = [
'config',
'map_key',
'api_key',
'auth_token',
'callback',
'zoom',
'lon',
'lat',
// analysis
'filters' // json
];
module.exports = function cleanUpQueryParamsMiddleware () {
return function cleanUpQueryParams (req, res, next) {
var allowedQueryParams = REQUEST_QUERY_PARAMS_WHITELIST;
if (Array.isArray(res.locals.allowedQueryParams)) {
allowedQueryParams = allowedQueryParams.concat(res.locals.allowedQueryParams);
}
req.query = _.pick(req.query, allowedQueryParams);
// bring all query values onto res.locals object
_.extend(res.locals, req.query);
next();
};
};

View File

@@ -0,0 +1,31 @@
const _ = require('underscore');
module.exports = function dbConnSetupMiddleware(pgConnection) {
return function dbConnSetup(req, res, next) {
const user = res.locals.user;
pgConnection.setDBConn(user, res.locals, (err) => {
if (err) {
if (err.message && -1 !== err.message.indexOf('name not found')) {
err.http_status = 404;
}
req.profiler.done('req2params');
return next(err);
}
// Add default database connection parameters
// if none given
_.defaults(res.locals, {
dbuser: global.environment.postgres.user,
dbpassword: global.environment.postgres.password,
dbhost: global.environment.postgres.host,
dbport: global.environment.postgres.port
});
res.set('X-Served-By-DB-Host', res.locals.dbhost);
req.profiler.done('req2params');
next(null);
});
};
};

View File

@@ -0,0 +1,15 @@
const locals = require('./locals');
const cleanUpQueryParams = require('./clean-up-query-params');
const layergroupToken = require('./layergroup-token');
const authorize = require('./authorize');
const dbConnSetup = require('./db-conn-setup');
module.exports = function prepareContextMiddleware(authApi, pgConnection) {
return [
locals,
cleanUpQueryParams(),
layergroupToken,
authorize(authApi),
dbConnSetup(pgConnection)
];
};

View File

@@ -0,0 +1,32 @@
var LayergroupToken = require('../../models/layergroup-token');
module.exports = function layergroupTokenMiddleware(req, res, next) {
if (!res.locals.token) {
return next();
}
var user = res.locals.user;
var layergroupToken = LayergroupToken.parse(res.locals.token);
res.locals.token = layergroupToken.token;
res.locals.cache_buster = layergroupToken.cacheBuster;
if (layergroupToken.signer) {
res.locals.signer = layergroupToken.signer;
if (!res.locals.signer) {
res.locals.signer = user;
} else if (res.locals.signer !== user) {
var err = new Error(`Cannot use map signature of user "${res.locals.signer}" on db of user "${user}"`);
err.type = 'auth';
err.http_status = 403;
if (req.query && req.query.callback) {
err.http_status = 200;
}
req.profiler.done('req2params');
return next(err);
}
}
return next();
};

View File

@@ -0,0 +1,6 @@
module.exports = function localsMiddleware(req, res, next) {
// save req.params in res.locals
res.locals = Object.assign(req.params || {}, res.locals);
next();
};

View File

@@ -0,0 +1,213 @@
const _ = require('underscore');
const debug = require('debug')('windshaft:cartodb:error-middleware');
module.exports = function errorMiddleware (/* options */) {
return function error (err, req, res, next) {
// jshint unused:false
// jshint maxcomplexity:9
var allErrors = Array.isArray(err) ? err : [err];
allErrors = populateTimeoutErrors(allErrors);
const label = err.label || 'UNKNOWN';
err = allErrors[0] || new Error(label);
allErrors[0] = err;
var statusCode = findStatusCode(err);
if (err.message === 'Tile does not exist' && res.locals.format === 'mvt') {
statusCode = 204;
}
setErrorHeader(allErrors, statusCode, res);
debug('[%s ERROR] -- %d: %s, %s', label, statusCode, err, err.stack);
// If a callback was requested, force status to 200
if (req.query && req.query.callback) {
statusCode = 200;
}
var errorResponseBody = {
errors: allErrors.map(errorMessage),
errors_with_context: allErrors.map(errorMessageWithContext)
};
res.status(statusCode);
if (req.query && req.query.callback) {
res.jsonp(errorResponseBody);
} else {
res.json(errorResponseBody);
}
};
};
function isRenderTimeoutError (err) {
return err.message === 'Render timed out';
}
function isDatasourceTimeoutError (err) {
return err.message && err.message.match(/canceling statement due to statement timeout/i);
}
function isTimeoutError (err) {
return isRenderTimeoutError(err) || isDatasourceTimeoutError(err);
}
function populateTimeoutErrors (errors) {
return errors.map(function (error) {
if (isRenderTimeoutError(error)) {
error.subtype = 'render';
}
if (isDatasourceTimeoutError(error)) {
error.subtype = 'datasource';
}
if (isTimeoutError(error)) {
error.message = 'You are over platform\'s limits. Please contact us to know more details';
error.type = 'limit';
error.http_status = 429;
}
return error;
});
}
function findStatusCode(err) {
var statusCode;
if ( err.http_status ) {
statusCode = err.http_status;
} else {
statusCode = statusFromErrorMessage('' + err);
}
return statusCode;
}
module.exports.findStatusCode = findStatusCode;
function statusFromErrorMessage(errMsg) {
// Find an appropriate statusCode based on message
// jshint maxcomplexity:7
var statusCode = 400;
if ( -1 !== errMsg.indexOf('permission denied') ) {
statusCode = 403;
}
else if ( -1 !== errMsg.indexOf('authentication failed') ) {
statusCode = 403;
}
else if (errMsg.match(/Postgis Plugin.*[\s|\n].*column.*does not exist/)) {
statusCode = 400;
}
else if ( -1 !== errMsg.indexOf('does not exist') ) {
if ( -1 !== errMsg.indexOf(' role ') ) {
statusCode = 403; // role 'xxx' does not exist
} else if ( errMsg.match(/function .* does not exist/) ) {
statusCode = 400; // invalid SQL (SQL function does not exist)
} else {
statusCode = 404;
}
}
return statusCode;
}
function errorMessage(err) {
// See https://github.com/Vizzuality/Windshaft-cartodb/issues/68
var message = (_.isString(err) ? err : err.message) || 'Unknown error';
return stripConnectionInfo(message);
}
module.exports.errorMessage = errorMessage;
function stripConnectionInfo(message) {
// Strip connection info, if any
return message
// See https://github.com/CartoDB/Windshaft/issues/173
.replace(/Connection string: '[^']*'\n\s/im, '')
// See https://travis-ci.org/CartoDB/Windshaft/jobs/20703062#L1644
.replace(/is the server.*encountered/im, 'encountered');
}
var ERROR_INFO_TO_EXPOSE = {
message: true,
layer: true,
type: true,
analysis: true,
subtype: true
};
function shouldBeExposed (prop) {
return !!ERROR_INFO_TO_EXPOSE[prop];
}
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) && shouldBeExposed(prop)) {
error[prop] = err[prop];
}
}
return error;
}
function setErrorHeader(errors, statusCode, res) {
let errorsCopy = errors.slice(0);
const mainError = errorsCopy.shift();
let errorsLog = {
mainError: {
statusCode: statusCode || 200,
message: mainError.message,
name: mainError.name,
label: mainError.label,
type: mainError.type,
subtype: mainError.subtype
}
};
errorsLog.moreErrors = errorsCopy.map(error => {
return {
message: error.message,
name: error.name,
label: error.label,
type: error.type,
subtype: error.subtype
};
});
res.set('X-Tiler-Errors', stringifyForLogs(errorsLog));
}
/**
* Remove problematic nested characters
* from object for logs RegEx
*
* @param {Object} object
*/
function stringifyForLogs(object) {
Object.keys(object).map(key => {
if(typeof object[key] === 'string') {
object[key] = object[key].replace(/[^a-zA-Z0-9]/g, ' ');
} else if (typeof object[key] === 'object') {
stringifyForLogs(object[key]);
} else if (object[key] instanceof Array) {
for (let element of object[key]) {
stringifyForLogs(element);
}
}
});
return JSON.stringify(object);
}

View File

@@ -0,0 +1,30 @@
'use strict';
const LZMA = require('lzma').LZMA;
const lzmaWorker = new LZMA();
module.exports = function lzmaMiddleware(req, res, next) {
if (!req.query.hasOwnProperty('lzma')) {
return next();
}
// Decode (from base64)
var lzma = new Buffer(req.query.lzma, 'base64')
.toString('binary')
.split('')
.map(function(c) {
return c.charCodeAt(0) - 128;
});
// Decompress
lzmaWorker.decompress(lzma, function(result) {
try {
delete req.query.lzma;
Object.assign(req.query, JSON.parse(result));
next();
} catch (err) {
next(new Error('Error parsing lzma as JSON: ' + err));
}
});
};

View File

@@ -0,0 +1,27 @@
const Profiler = require('../stats/profiler_proxy');
const debug = require('debug')('windshaft:cartodb:stats');
const onHeaders = require('on-headers');
module.exports = function statsMiddleware(options) {
const { enabled = true, statsClient } = options;
return function stats(req, res, next) {
req.profiler = new Profiler({
statsd_client: statsClient,
profile: enabled
});
onHeaders(res, () => res.set('X-Tiler-Profiler', req.profiler.toJSONString()));
res.on('finish', () => {
try {
// May throw due to dns, see: http://github.com/CartoDB/Windshaft/issues/166
req.profiler.sendStats();
} catch (err) {
debug("error sending profiling stats: " + err);
}
});
next();
};
};

View File

@@ -2,6 +2,7 @@ var CdbRequest = require('../models/cdb_request');
var cdbRequest = new CdbRequest();
module.exports = function userMiddleware(req, res, next) {
req.context.user = cdbRequest.userByReq(req);
res.locals.user = cdbRequest.userByReq(req);
next();
};

View File

@@ -0,0 +1,30 @@
const fs = require('fs');
const timeoutErrorVectorTile = fs.readFileSync(__dirname + '/../../../assets/render-timeout-fallback.mvt');
module.exports = function vectorError() {
return function vectorErrorMiddleware(err, req, res, next) {
if(req.params.format === 'mvt') {
if (isTimeoutError(err)) {
res.set('Content-Type', 'application/x-protobuf');
return res.status(429).send(timeoutErrorVectorTile);
}
}
next(err);
};
};
function isRenderTimeoutError (err) {
return err.message === 'Render timed out';
}
function isDatasourceTimeoutError (err) {
return err.message && err.message.match(/canceling statement due to statement timeout/i);
}
function isTimeoutError (err) {
return isRenderTimeoutError(err) || isDatasourceTimeoutError(err);
}

View File

@@ -0,0 +1,233 @@
const MapConfig = require('windshaft').model.MapConfig;
const aggregationQuery = require('./aggregation-query');
const aggregationValidator = require('./aggregation-validator');
const {
createPositiveNumberValidator,
createIncludesValueValidator,
createAggregationColumnsValidator
} = aggregationValidator;
const SubstitutionTokens = require('../../utils/substitution-tokens');
const removeDuplicates = arr => [...new Set(arr)];
function prepareSql(sql) {
return sql && SubstitutionTokens.replace(sql, {
bbox: 'ST_MakeEnvelope(0,0,0,0)',
scale_denominator: '0',
pixel_width: '1',
pixel_height: '1'
});
}
module.exports = class AggregationMapConfig extends MapConfig {
static get AGGREGATIONS () {
return aggregationQuery.SUPPORTED_AGGREGATE_FUNCTIONS;
}
static get PLACEMENTS () {
return aggregationQuery.SUPPORTED_PLACEMENTS;
}
static get THRESHOLD () {
return 1e5; // 100K
}
static get RESOLUTION () {
return 1;
}
static get SUPPORTED_GEOMETRY_TYPES () {
return [
'ST_Point'
];
}
static supportsGeometryType(geometryType) {
return AggregationMapConfig.SUPPORTED_GEOMETRY_TYPES.includes(geometryType);
}
static getAggregationGeometryColumn() {
return aggregationQuery.GEOMETRY_COLUMN;
}
constructor (user, config, connection, datasource) {
super(config, datasource);
const validate = aggregationValidator(this);
const positiveNumberValidator = createPositiveNumberValidator(this);
const includesValidPlacementsValidator = createIncludesValueValidator(this, AggregationMapConfig.PLACEMENTS);
const aggregationColumnsValidator = createAggregationColumnsValidator(this, AggregationMapConfig.AGGREGATIONS);
validate('resolution', positiveNumberValidator);
validate('placement', includesValidPlacementsValidator);
validate('threshold', positiveNumberValidator);
validate('columns', aggregationColumnsValidator);
this.user = user;
this.pgConnection = connection;
}
getAggregatedQuery (index) {
const { sql_raw, sql } = this.getLayer(index).options;
const {
// The default aggregation has no placement, columns or dimensions;
// this enables the special "full-sample" aggregation.
resolution = AggregationMapConfig.RESOLUTION,
threshold = AggregationMapConfig.THRESHOLD,
placement,
columns = {},
dimensions = {}
} = this.getAggregation(index);
return aggregationQuery({
query: sql_raw || sql,
resolution,
threshold,
placement,
columns,
dimensions,
isDefaultAggregation: this._isDefaultLayerAggregation(index)
});
}
isAggregationMapConfig () {
return this.isVectorOnlyMapConfig() || this.hasAnyLayerAggregation();
}
isAggregationLayer (index) {
return this.isVectorOnlyMapConfig() || this.hasLayerAggregation(index);
}
hasAnyLayerAggregation () {
const layers = this.getLayers();
for (let index = 0; index < layers.length; index++) {
if (this.hasLayerAggregation(index)) {
return true;
}
}
return false;
}
hasLayerAggregation (index) {
const layer = this.getLayer(index);
const { aggregation } = layer.options;
return aggregation !== undefined && (typeof aggregation === 'object' || typeof aggregation === 'boolean');
}
getAggregation (index) {
if (this.isVectorOnlyMapConfig() && !this.hasLayerAggregation(index)) {
return {};
}
const { aggregation } = this.getLayer(index).options;
if (typeof aggregation === 'boolean') {
return {};
}
return aggregation;
}
getLayerAggregationColumns (index, callback) {
if (this._isDefaultLayerAggregation(index)) {
const skipGeoms = true;
return this.getLayerColumns(index, skipGeoms, (err, columns) => {
if (err) {
return callback(err);
}
return callback(null, columns);
});
}
const columns = this._getLayerAggregationRequiredColumns(index);
return callback(null, columns);
}
_getLayerAggregationRequiredColumns (index) {
const { columns, dimensions } = this.getAggregation(index);
let aggregatedColumns = [];
if (columns) {
aggregatedColumns = Object.keys(columns)
.map(key => columns[key].aggregated_column)
.filter(aggregatedColumn => typeof aggregatedColumn === 'string');
}
let dimensionsColumns = [];
if (dimensions) {
dimensionsColumns = Object.keys(dimensions)
.map(key => dimensions[key])
.filter(dimension => typeof dimension === 'string');
}
return removeDuplicates(aggregatedColumns.concat(dimensionsColumns));
}
doesLayerReachThreshold(index, featureCount) {
const threshold = this.getAggregation(index) && this.getAggregation(index).threshold ?
this.getAggregation(index).threshold :
AggregationMapConfig.THRESHOLD;
return featureCount >= threshold;
}
getLayerColumns (index, skipGeoms, callback) {
const geomColumns = ['the_geom', 'the_geom_webmercator'];
const limitedQuery = ctx => `SELECT * FROM (${ctx.query}) __cdb_schema LIMIT 0`;
const layer = this.getLayer(index);
this.pgConnection.getConnection(this.user, (err, connection) => {
if (err) {
return callback(err);
}
const sql = limitedQuery({
query: prepareSql(layer.options.sql)
});
connection.query(sql, (err, result) => {
if (err) {
return callback(err);
}
let columns = result.fields || [];
columns = columns.map(({ name }) => name);
if (skipGeoms) {
columns = columns.filter((column) => !geomColumns.includes(column));
}
return callback(err, columns);
});
});
}
_isDefaultLayerAggregation (index) {
const aggregation = this.getAggregation(index);
return (this.isVectorOnlyMapConfig() && !this.hasLayerAggregation(index)) ||
aggregation === true ||
this._isDefaultAggregation(aggregation);
}
_isDefaultAggregation (aggregation) {
return aggregation.placement === undefined &&
aggregation.columns === undefined &&
this._isEmptyParameter(aggregation.dimensions);
}
_isEmptyParameter(parameter) {
return parameter === undefined || parameter === null || this._isEmptyObject(parameter);
}
_isEmptyObject (parameter) {
return typeof parameter === 'object' && Object.keys(parameter).length === 0;
}
};

View File

@@ -0,0 +1,253 @@
const DEFAULT_PLACEMENT = 'point-sample';
/**
* Returns a template function (function that accepts template parameters and returns a string)
* to generate an aggregation query.
* Valid options to define the query template are:
* - placement
* - columns
* - dimensions*
* The query template parameters taken by the result template function are:
* - sourceQuery
* - res
* - columns
* - dimensions
*/
const templateForOptions = (options) => {
let templateFn = defaultAggregationQueryTemplate;
if (!options.isDefaultAggregation) {
templateFn = aggregationQueryTemplates[options.placement || DEFAULT_PLACEMENT];
if (!templateFn) {
throw new Error("Invalid Aggregation placement: '" + options.placement + "'");
}
}
return templateFn;
};
/**
* Generates an aggregation query given the aggregation options:
* - query
* - resolution - defined as in torque:
* aggregation cell is resolution*resolution pixels, where tiles are always 256x256 pixels
* - columns
* - placement
* - dimensions
*
* The default aggregation (when no explicit placement, columns or dimensions are present) returns
* a sample record (with all the original columns and _cdb_feature_count) for each aggregation group.
* When placement, columns or dimensions are specified, columns are aggregated as requested
* (by default only _cdb_feature_count) and with the_geom_webmercator as defined by placement.
*/
const queryForOptions = (options) => templateForOptions(options)({
sourceQuery: options.query,
res: 256/options.resolution,
columns: options.columns,
dimensions: options.dimensions
});
module.exports = queryForOptions;
const SUPPORTED_AGGREGATE_FUNCTIONS = {
'count': {
sql: (column_name, params) => `count(${params.aggregated_column || '*'})`
},
'avg': {
sql: (column_name, params) => `avg(${params.aggregated_column || column_name})`
},
'sum': {
sql: (column_name, params) => `sum(${params.aggregated_column || column_name})`
},
'min': {
sql: (column_name, params) => `min(${params.aggregated_column || column_name})`
},
'max': {
sql: (column_name, params) => `max(${params.aggregated_column || column_name})`
},
'mode': {
sql: (column_name, params) => `_cdb_mode(${params.aggregated_column || column_name})`
}
};
module.exports.SUPPORTED_AGGREGATE_FUNCTIONS = Object.keys(SUPPORTED_AGGREGATE_FUNCTIONS);
const sep = (list) => {
let expr = list.join(', ');
return expr ? ', ' + expr : expr;
};
const aggregateColumns = ctx => {
return Object.assign({
_cdb_feature_count: {
aggregate_function: 'count'
}
}, ctx.columns || {});
};
const aggregateColumnNames = (ctx, table) => {
let columns = aggregateColumns(ctx);
if (table) {
return sep(Object.keys(columns).map(
column_name => `${table}.${column_name}`
));
}
return sep(Object.keys(columns));
};
const aggregateColumnDefs = ctx => {
let columns = aggregateColumns(ctx);
return sep(Object.keys(columns).map(column_name => {
const aggregate_function = columns[column_name].aggregate_function || 'count';
const aggregate_definition = SUPPORTED_AGGREGATE_FUNCTIONS[aggregate_function];
if (!aggregate_definition) {
throw new Error("Invalid Aggregate function: '" + aggregate_function + "'");
}
const aggregate_expression = aggregate_definition.sql(column_name, columns[column_name]);
return `${aggregate_expression} AS ${column_name}`;
}));
};
const aggregateDimensions = ctx => ctx.dimensions || {};
const dimensionNames = (ctx, table) => {
let dimensions = aggregateDimensions(ctx);
if (table) {
return sep(Object.keys(dimensions).map(
dimension_name => `${table}.${dimension_name}`
));
}
return sep(Object.keys(dimensions));
};
const dimensionDefs = ctx => {
let dimensions = aggregateDimensions(ctx);
return sep(Object.keys(dimensions).map(dimension_name => {
const expression = dimensions[dimension_name];
return `${expression} AS ${dimension_name}`;
}));
};
// SQL expression to compute the aggregation resolution (grid cell size).
// This is equivalent to `${256/ctx.res}*CDB_XYZ_Resolution(CDB_ZoomFromScale(!scale_denominator!))`
// This is defined by the ctx.res parameter, which is the number of grid cells per tile linear dimension
// (i.e. each tile is divided into ctx.res*ctx.res cells).
const gridResolution = ctx => `(${256*0.00028/ctx.res}*!scale_denominator!)::double precision`;
// Notes:
// * We need to filter spatially using !bbox! to make the queries efficient because
// the filter added by Mapnik (wrapping the query)
// is only applied after the aggregation.
// * This queries are used for rendering and the_geom is omitted in the results for better performance
// The special default aggregation includes all the columns of a sample row per grid cell and
// the count (_cdb_feature_count) of the aggregated rows.
const defaultAggregationQueryTemplate = ctx => `
WITH
_cdb_params AS (
SELECT
${gridResolution(ctx)} AS res,
!bbox! AS bbox
),
_cdb_clusters AS (
SELECT
MIN(cartodb_id) AS cartodb_id
${dimensionDefs(ctx)}
${aggregateColumnDefs(ctx)}
FROM (${ctx.sourceQuery}) _cdb_query, _cdb_params
WHERE _cdb_query.the_geom_webmercator && _cdb_params.bbox
GROUP BY
Floor(ST_X(_cdb_query.the_geom_webmercator)/_cdb_params.res),
Floor(ST_Y(_cdb_query.the_geom_webmercator)/_cdb_params.res)
${dimensionNames(ctx)}
) SELECT
_cdb_query.*
${aggregateColumnNames(ctx)}
FROM
_cdb_clusters INNER JOIN (${ctx.sourceQuery}) _cdb_query
ON (_cdb_clusters.cartodb_id = _cdb_query.cartodb_id)
`;
const aggregationQueryTemplates = {
'centroid': ctx => `
WITH
_cdb_params AS (
SELECT
${gridResolution(ctx)} AS res,
!bbox! AS bbox
)
SELECT
row_number() over() AS cartodb_id,
ST_SetSRID(
ST_MakePoint(
AVG(ST_X(_cdb_query.the_geom_webmercator)),
AVG(ST_Y(_cdb_query.the_geom_webmercator))
), 3857
) AS the_geom_webmercator
${dimensionDefs(ctx)}
${aggregateColumnDefs(ctx)}
FROM (${ctx.sourceQuery}) _cdb_query, _cdb_params
WHERE _cdb_query.the_geom_webmercator && _cdb_params.bbox
GROUP BY
Floor(ST_X(_cdb_query.the_geom_webmercator)/_cdb_params.res),
Floor(ST_Y(_cdb_query.the_geom_webmercator)/_cdb_params.res)
${dimensionNames(ctx)}
`,
'point-grid': ctx => `
WITH
_cdb_params AS (
SELECT
${gridResolution(ctx)} AS res,
!bbox! AS bbox
),
_cdb_clusters AS (
SELECT
Floor(ST_X(_cdb_query.the_geom_webmercator)/_cdb_params.res)::int AS _cdb_gx,
Floor(ST_Y(_cdb_query.the_geom_webmercator)/_cdb_params.res)::int AS _cdb_gy
${dimensionDefs(ctx)}
${aggregateColumnDefs(ctx)}
FROM (${ctx.sourceQuery}) _cdb_query, _cdb_params
WHERE the_geom_webmercator && _cdb_params.bbox
GROUP BY _cdb_gx, _cdb_gy ${dimensionNames(ctx)}
)
SELECT
row_number() over() AS cartodb_id,
ST_SetSRID(ST_MakePoint((_cdb_gx+0.5)*res, (_cdb_gy+0.5)*res), 3857) AS the_geom_webmercator
${dimensionNames(ctx)}
${aggregateColumnNames(ctx)}
FROM _cdb_clusters, _cdb_params
`,
'point-sample': ctx => `
WITH
_cdb_params AS (
SELECT
${gridResolution(ctx)} AS res,
!bbox! AS bbox
),
_cdb_clusters AS (
SELECT
MIN(cartodb_id) AS cartodb_id
${dimensionDefs(ctx)}
${aggregateColumnDefs(ctx)}
FROM (${ctx.sourceQuery}) _cdb_query, _cdb_params
WHERE _cdb_query.the_geom_webmercator && _cdb_params.bbox
GROUP BY
Floor(ST_X(_cdb_query.the_geom_webmercator)/_cdb_params.res),
Floor(ST_Y(_cdb_query.the_geom_webmercator)/_cdb_params.res)
${dimensionNames(ctx)}
)
SELECT
_cdb_clusters.cartodb_id,
the_geom, the_geom_webmercator
${dimensionNames(ctx, '_cdb_query')}
${aggregateColumnNames(ctx, '_cdb_clusters')}
FROM
_cdb_clusters INNER JOIN (${ctx.sourceQuery}) _cdb_query
ON (_cdb_clusters.cartodb_id = _cdb_query.cartodb_id)
`
};
module.exports.SUPPORTED_PLACEMENTS = Object.keys(aggregationQueryTemplates);
module.exports.GEOMETRY_COLUMN = 'the_geom_webmercator';

View File

@@ -0,0 +1,93 @@
module.exports = function aggregationValidator (mapconfig) {
return function validateProperty (key, validator) {
for (let index = 0; index < mapconfig.getLayers().length; index++) {
const aggregation = mapconfig.getAggregation(index);
if (aggregation === undefined || aggregation[key] === undefined) {
continue;
}
validator(aggregation[key], key, index);
}
};
};
module.exports.createIncludesValueValidator = function (mapconfig, validValues) {
return function validateIncludesValue (value, key, index) {
if (!validValues.includes(value)) {
const message = `Invalid ${key}. Valid values: ${validValues.join(', ')}`;
throw createLayerError(message, mapconfig, index);
}
};
};
module.exports.createPositiveNumberValidator = function (mapconfig) {
return function validatePositiveNumber (value, key, index) {
if (!Number.isFinite(value) || value <= 0) {
const message = `Invalid ${key}, should be a number greather than 0`;
throw createLayerError(message, mapconfig, index);
}
};
};
module.exports.createAggregationColumnsValidator = function (mapconfig, validAggregatedFunctions) {
const validateAggregationColumnNames = createAggregationColumnNamesValidator(mapconfig);
const validateAggregateFunction = createAggregateFunctionValidator(mapconfig, validAggregatedFunctions);
const validateAggregatedColumn = createAggregatedColumnValidator(mapconfig);
return function validateAggregationColumns (value, key, index) {
validateAggregationColumnNames(value, key, index);
validateAggregateFunction(value, key, index);
validateAggregatedColumn(value, key, index);
};
};
function createAggregationColumnNamesValidator(mapconfig) {
return function validateAggregationColumnNames (value, key, index) {
Object.keys(value).forEach((columnName) => {
if (columnName.length <= 0) {
const message = `Invalid column name, should be a non empty string`;
throw createLayerError(message, mapconfig, index);
}
});
};
}
function createAggregateFunctionValidator (mapconfig, validAggregatedFunctions) {
return function validateAggregateFunction (value, key, index) {
Object.keys(value).forEach((columnName) => {
const { aggregate_function } = value[columnName];
if (!validAggregatedFunctions.includes(aggregate_function)) {
const message = `Unsupported aggregation function ${aggregate_function},` +
` valid ones: ${validAggregatedFunctions.join(', ')}`;
throw createLayerError(message, mapconfig, index);
}
});
};
}
function createAggregatedColumnValidator (mapconfig) {
return function validateAggregatedColumn (value, key, index) {
Object.keys(value).forEach((columnName) => {
const { aggregated_column } = value[columnName];
if (typeof aggregated_column !== 'string' || aggregated_column <= 0) {
const message = `Invalid aggregated column, should be a non empty string`;
throw createLayerError(message, mapconfig, index);
}
});
};
}
function createLayerError(message, mapconfig, index) {
const error = new Error(message);
error.type = 'layer';
error.layer = {
id: mapconfig.getLayerId(index),
index: index,
type: mapconfig.layerType(index)
};
return error;
}

View File

@@ -1,58 +1,178 @@
var _ = require('underscore');
var BaseWidget = require('./base');
var debug = require('debug')('windshaft:widget:aggregation');
const BaseDataview = require('./base');
const debug = require('debug')('windshaft:dataview:aggregation');
var dot = require('dot');
dot.templateSettings.strip = false;
const filteredQueryTpl = ctx => `
filtered_source AS (
SELECT *
FROM (${ctx.query}) _cdb_filtered_source
${ctx.aggregationColumn && ctx.isFloatColumn ? `
WHERE
${ctx.aggregationColumn} != 'infinity'::float
AND
${ctx.aggregationColumn} != '-infinity'::float
AND
${ctx.aggregationColumn} != 'NaN'::float` :
''
}
)
`;
var summaryQueryTpl = dot.template([
'summary AS (',
' SELECT',
' count(1) 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'));
const summaryQueryTpl = ctx => `
summary AS (
SELECT
count(1) AS count,
sum(CASE WHEN ${ctx.column} IS NULL THEN 1 ELSE 0 END) AS nulls_count
${ctx.isFloatColumn ? `,
sum(
CASE
WHEN ${ctx.aggregationColumn} = 'infinity'::float OR ${ctx.aggregationColumn} = '-infinity'::float
THEN 1
ELSE 0
END
) AS infinities_count,
sum(CASE WHEN ${ctx.aggregationColumn} = 'NaN'::float THEN 1 ELSE 0 END) AS nans_count` :
''
}
FROM (${ctx.query}) _cdb_aggregation_nulls
)
`;
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',
' GROUP BY {{=it._column}}',
' ORDER BY 2 DESC',
')'
].join('\n'));
const rankedCategoriesQueryTpl = ctx => `
categories AS(
SELECT
${ctx.column} AS category,
${ctx.aggregationFn} AS value,
row_number() OVER (ORDER BY ${ctx.aggregationFn} desc) as rank
FROM filtered_source
${ctx.aggregationColumn !== null ? `WHERE ${ctx.aggregationColumn} IS NOT NULL` : ''}
GROUP BY ${ctx.column}
ORDER BY 2 DESC
)
`;
var categoriesSummaryQueryTpl = dot.template([
'categories_summary AS(',
' SELECT count(1) categories_count, max(value) max_val, min(value) min_val',
' FROM categories',
')'
].join('\n'));
const categoriesSummaryMinMaxQueryTpl = () => `
categories_summary_min_max AS(
SELECT
max(value) max_val,
min(value) min_val
FROM categories
)
`;
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',
' 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',
' WHERE rank >= {{=it._limit}}',
'GROUP BY nulls_count, min_val, max_val, count, categories_count'
].join('\n'));
const categoriesSummaryCountQueryTpl = ctx => `
categories_summary_count AS(
SELECT count(1) AS categories_count
FROM (
SELECT ${ctx.column} AS category
FROM (${ctx.query}) _cdb_categories
GROUP BY ${ctx.column}
) _cdb_categories_count
)
`;
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',
'GROUP BY category, nulls_count, min_val, max_val, count, categories_count',
'ORDER BY value DESC'
].join('\n'));
const specialNumericValuesColumns = () => `, nans_count, infinities_count`;
var CATEGORIES_LIMIT = 6;
const rankedAggregationQueryTpl = ctx => `
SELECT
category,
value,
false as agg,
nulls_count,
min_val,
max_val,
count,
categories_count
${ctx.isFloatColumn ? `${specialNumericValuesColumns(ctx)}` : '' }
FROM categories, summary, categories_summary_min_max, categories_summary_count
WHERE rank < ${ctx.limit}
UNION ALL
SELECT
null category,
${ctx.aggregation !== 'count' ? ctx.aggregation : 'sum'}(value) as value,
true as agg,
nulls_count,
min_val,
max_val,
count,
categories_count
${ctx.isFloatColumn ? `${specialNumericValuesColumns(ctx)}` : '' }
FROM categories, summary, categories_summary_min_max, categories_summary_count
WHERE rank >= ${ctx.limit}
GROUP BY
nulls_count,
min_val,
max_val,
count,
categories_count
${ctx.isFloatColumn ? `${specialNumericValuesColumns(ctx)}` : '' }
`;
var VALID_OPERATIONS = {
const aggregationQueryTpl = ctx => `
SELECT
${ctx.column} AS category,
${ctx.aggregationFn} AS value,
false as agg,
nulls_count,
min_val,
max_val,
count,
categories_count
${ctx.isFloatColumn ? `${specialNumericValuesColumns(ctx)}` : '' }
FROM (${ctx.query}) _cdb_aggregation_all, summary, categories_summary_min_max, categories_summary_count
GROUP BY
${ctx.column},
nulls_count,
min_val,
max_val,
count,
categories_count
${ctx.isFloatColumn ? `${specialNumericValuesColumns(ctx)}` : '' }
ORDER BY value DESC
`;
const aggregationFnQueryTpl = ctx => `${ctx.aggregation}(${ctx.aggregationColumn})`;
const aggregationDataviewQueryTpl = ctx => `
WITH
${filteredQueryTpl(ctx)},
${summaryQueryTpl(ctx)},
${rankedCategoriesQueryTpl(ctx)},
${categoriesSummaryMinMaxQueryTpl(ctx)},
${categoriesSummaryCountQueryTpl(ctx)}
${!!ctx.override.ownFilter ? `${aggregationQueryTpl(ctx)}` : `${rankedAggregationQueryTpl(ctx)}`}
`;
const filterCategoriesQueryTpl = ctx => `
SELECT
${ctx.column} AS category,
${ctx.value} AS value
FROM (${ctx.query}) _cdb_aggregation_search
WHERE CAST(${ctx.column} as text) ILIKE ${ctx.userQuery}
GROUP BY ${ctx.column}
`;
const searchQueryTpl = ctx => `
WITH
search_unfiltered AS (
${ctx.searchUnfiltered}
),
search_filtered AS (
${ctx.searchFiltered}
),
search_union AS (
SELECT * FROM search_unfiltered
UNION ALL
SELECT * FROM search_filtered
)
SELECT category, sum(value) AS value
FROM search_union
GROUP BY category
ORDER BY value desc
`;
const CATEGORIES_LIMIT = 6;
const VALID_OPERATIONS = {
count: [],
sum: ['aggregationColumn'],
avg: ['aggregationColumn'],
@@ -60,7 +180,7 @@ var VALID_OPERATIONS = {
max: ['aggregationColumn']
};
var TYPE = 'aggregation';
const TYPE = 'aggregation';
/**
{
@@ -71,212 +191,160 @@ var TYPE = 'aggregation';
}
}
*/
function Aggregation(query, options) {
if (!_.isString(options.column)) {
throw new Error('Aggregation expects `column` in widget options');
module.exports = class Aggregation extends BaseDataview {
constructor (query, options = {}, queries = {}) {
super();
this._checkOptions(options);
this.query = query;
this.queries = queries;
this.column = options.column;
this.aggregation = options.aggregation;
this.aggregationColumn = options.aggregationColumn;
this._isFloatColumn = null;
}
if (!_.isString(options.aggregation)) {
throw new Error('Aggregation expects `aggregation` operation in widget options');
}
if (!VALID_OPERATIONS[options.aggregation]) {
throw new Error("Aggregation does not support '" + options.aggregation + "' operation");
}
var requiredOptions = VALID_OPERATIONS[options.aggregation];
var missingOptions = _.difference(requiredOptions, Object.keys(options));
if (missingOptions.length > 0) {
throw new Error(
"Aggregation '" + options.aggregation + "' is missing some options: " + missingOptions.join(',')
);
}
BaseWidget.apply(this);
this.query = query;
this.column = options.column;
this.aggregation = options.aggregation;
this.aggregationColumn = options.aggregationColumn;
}
Aggregation.prototype = new BaseWidget();
Aggregation.prototype.constructor = Aggregation;
module.exports = Aggregation;
Aggregation.prototype.sql = function(psql, override, callback) {
if (!callback) {
callback = override;
override = {};
}
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'),
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()
}),
categoriesSummaryQueryTpl({
_query: _query,
_column: this.column
})
].join(',\n'),
rankedAggregationQueryTpl({
_query: _query,
_column: this.column,
_limit: CATEGORIES_LIMIT
})
].join('\n');
}
debug(aggregationSql);
return callback(null, aggregationSql);
};
var aggregationFnQueryTpl = dot.template('{{=it._aggregationFn}}({{=it._aggregationColumn}})');
Aggregation.prototype.getAggregationSql = function() {
return aggregationFnQueryTpl({
_aggregationFn: this.aggregation,
_aggregationColumn: this.aggregationColumn || 1
});
};
Aggregation.prototype.format = function(result) {
var categories = [];
var count = 0;
var nulls = 0;
var minValue = 0;
var maxValue = 0;
var categoriesCount = 0;
if (result.rows.length) {
var firstRow = result.rows[0];
count = firstRow.count;
nulls = firstRow.nulls_count;
minValue = firstRow.min_val;
maxValue = firstRow.max_val;
categoriesCount = firstRow.categories_count;
result.rows.forEach(function(row) {
categories.push(_.omit(row, 'count', 'nulls_count', 'min_val', 'max_val', 'categories_count'));
});
}
return {
aggregation: this.aggregation,
count: count,
nulls: nulls,
min: minValue,
max: maxValue,
categoriesCount: categoriesCount,
categories: categories
};
};
var filterCategoriesQueryTpl = dot.template([
'SELECT {{=it._column}} AS category, {{=it._value}} AS value',
'FROM ({{=it._query}}) _cdb_aggregation_search',
'WHERE CAST({{=it._column}} as text) ILIKE {{=it._userQuery}}',
'GROUP BY {{=it._column}}'
].join('\n'));
var searchQueryTpl = dot.template([
'WITH',
'search_unfiltered AS (',
' {{=it._searchUnfiltered}}',
'),',
'search_filtered AS (',
' {{=it._searchFiltered}}',
'),',
'search_union AS (',
' SELECT * FROM search_unfiltered',
' UNION ALL',
' SELECT * FROM search_filtered',
')',
'SELECT category, sum(value) AS value',
'FROM search_union',
'GROUP BY category',
'ORDER BY value desc'
].join('\n'));
Aggregation.prototype.search = function(psql, userQuery, callback) {
var self = this;
var _userQuery = psql.escapeLiteral('%' + userQuery + '%');
// TODO unfiltered will be wrong as filters are already applied at this point
var query = searchQueryTpl({
_searchUnfiltered: filterCategoriesQueryTpl({
_query: this.query,
_column: this.column,
_value: '0',
_userQuery: _userQuery
}),
_searchFiltered: filterCategoriesQueryTpl({
_query: this.query,
_column: this.column,
_value: 'count(1)',
_userQuery: _userQuery
})
});
psql.query(query, function(err, result) {
if (err) {
return callback(err, result);
_checkOptions (options) {
if (typeof options.column !== 'string') {
throw new Error(`Aggregation expects 'column' in dataview options`);
}
return callback(null, {type: self.getType(), categories: result.rows });
}, true); // use read-only transaction
};
if (typeof options.aggregation !== 'string') {
throw new Error(`Aggregation expects 'aggregation' operation in dataview options`);
}
Aggregation.prototype.getType = function() {
return TYPE;
};
if (!VALID_OPERATIONS[options.aggregation]) {
throw new Error(`Aggregation does not support '${options.aggregation}' operation`);
}
Aggregation.prototype.toString = function() {
return JSON.stringify({
_type: TYPE,
_query: this.query,
_column: this.column,
_aggregation: this.aggregation
});
const requiredOptions = VALID_OPERATIONS[options.aggregation];
const missingOptions = requiredOptions.filter(requiredOption => !options.hasOwnProperty(requiredOption));
if (missingOptions.length > 0) {
throw new Error(
`Aggregation '${options.aggregation}' is missing some options: ${missingOptions.join(',')}`
);
}
}
sql (psql, override, callback) {
if (!callback) {
callback = override;
override = {};
}
if (this._shouldCheckColumnType()) {
this._isFloatColumn = false;
this.getColumnType(psql, this.aggregationColumn, this.queries.no_filters, (err, type) => {
if (!err && !!type) {
this._isFloatColumn = type.float;
}
this.sql(psql, override, callback);
});
return null;
}
const limit = Number.isFinite(override.categories) && override.categories > 0 ?
override.categories :
CATEGORIES_LIMIT;
const aggregationSql = aggregationDataviewQueryTpl({
override: override,
query: this.query,
column: this.column,
aggregation: this.aggregation,
aggregationColumn: this.aggregation !== 'count' ? this.aggregationColumn : null,
aggregationFn: aggregationFnQueryTpl({
aggregation: this.aggregation,
aggregationColumn: this.aggregationColumn || 1
}),
isFloatColumn: this._isFloatColumn,
limit
});
debug(aggregationSql);
return callback(null, aggregationSql);
}
_shouldCheckColumnType () {
return this.aggregationColumn && this._isFloatColumn === null;
}
format (result) {
const {
count = 0,
nulls_count = 0,
nans_count = 0,
infinities_count = 0,
min_val = 0,
max_val = 0,
categories_count = 0
} = result.rows[0] || {};
return {
aggregation: this.aggregation,
count: count,
nulls: nulls_count,
nans: nans_count,
infinities: infinities_count,
min: min_val,
max: max_val,
categoriesCount: categories_count,
categories: result.rows.map(({ category, value, agg }) => {
return {
category: agg ? 'Other' : category,
value,
agg
};
})
};
}
search (psql, userQuery, callback) {
const escapedUserQuery = psql.escapeLiteral(`%${userQuery}%`);
const value = this.aggregation !== 'count' && this.aggregationColumn ?
`${this.aggregation}(${this.aggregationColumn})` :
'count(1)';
// TODO unfiltered will be wrong as filters are already applied at this point
const query = searchQueryTpl({
searchUnfiltered: filterCategoriesQueryTpl({
query: this.query,
column: this.column,
value: '0',
userQuery: escapedUserQuery
}),
searchFiltered: filterCategoriesQueryTpl({
query: this.query,
column: this.column,
value: value,
userQuery: escapedUserQuery
})
});
debug(query);
psql.query(query, (err, result) => {
if (err) {
return callback(err, result);
}
return callback(null, {type: this.getType(), categories: result.rows });
}, true); // use read-only transaction
}
getType () {
return TYPE;
}
toString () {
return JSON.stringify({
_type: TYPE,
_query: this.query,
_column: this.column,
_aggregation: this.aggregation
});
}
};

View File

@@ -1,26 +1,59 @@
function BaseDataview() {}
const FLOAT_OIDS = {
700: true,
701: true,
1700: true
};
module.exports = BaseDataview;
const DATE_OIDS = {
1082: true,
1114: true,
1184: true
};
BaseDataview.prototype.getResult = function(psql, override, callback) {
var self = this;
this.sql(psql, override, function(err, query) {
psql.query(query, function(err, result) {
const columnTypeQueryTpl = ctx => `SELECT pg_typeof(${ctx.column})::oid FROM (${ctx.query}) _cdb_column_type limit 1`;
function getPGTypeName (pgType) {
return {
float: FLOAT_OIDS.hasOwnProperty(pgType),
date: DATE_OIDS.hasOwnProperty(pgType)
};
}
module.exports = class BaseDataview {
getResult (psql, override, callback) {
this.sql(psql, override, (err, query) => {
if (err) {
return callback(err, result);
return callback(err);
}
result = self.format(result, override);
result.type = self.getType();
psql.query(query, (err, result) => {
if (err) {
return callback(err, result);
}
return callback(null, result);
result = this.format(result, override);
result.type = this.getType();
}, true); // use read-only transaction
});
return callback(null, result);
};
BaseDataview.prototype.search = function(psql, userQuery, callback) {
return callback(null, this.format({ rows: [] }));
}, true); // use read-only transaction
});
}
search (psql, userQuery, callback) {
return callback(null, this.format({ rows: [] }));
}
getColumnType (psql, column, query, callback) {
const readOnlyTransaction = true;
const columnTypeQuery = columnTypeQueryTpl({ column, query });
psql.query(columnTypeQuery, (err, result) => {
if (err) {
return callback(err);
}
const pgType = result.rows[0].pg_typeof;
callback(null, getPGTypeName(pgType));
}, readOnlyTransaction);
}
};

View File

@@ -1,18 +1,20 @@
var dataviews = require('./');
const dataviews = require('./');
var DataviewFactory = {
dataviews: Object.keys(dataviews).reduce(function(allDataviews, dataviewClassName) {
allDataviews[dataviewClassName.toLowerCase()] = dataviews[dataviewClassName];
return allDataviews;
}, {}),
module.exports = class DataviewFactory {
static get dataviews() {
return Object.keys(dataviews).reduce((allDataviews, dataviewClassName) => {
allDataviews[dataviewClassName.toLowerCase()] = dataviews[dataviewClassName];
return allDataviews;
}, {});
}
static getDataview (query, dataviewDefinition) {
const { type, options, sql } = dataviewDefinition;
getDataview: function(query, dataviewDefinition) {
var type = dataviewDefinition.type;
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, options, sql);
}
};
module.exports = DataviewFactory;

View File

@@ -1,18 +1,16 @@
var _ = require('underscore');
var BaseWidget = require('./base');
var debug = require('debug')('windshaft:widget:formula');
const BaseDataview = require('./base');
const debug = require('debug')('windshaft:dataview:formula');
const utils = require('../../utils/query-utils');
var dot = require('dot');
dot.templateSettings.strip = false;
const formulaQueryTpl = ctx =>
`SELECT
${ctx.operation}(${utils.handleFloatColumn(ctx)}) AS result,
${utils.countNULLs(ctx)} AS nulls_count
${ctx.isFloatColumn ? `,${utils.countInfinites(ctx)} AS infinities_count,` : ``}
${ctx.isFloatColumn ? `${utils.countNaNs(ctx)} AS nans_count` : ``}
FROM (${ctx.query}) __cdb_formula`;
var formulaQueryTpl = dot.template([
'SELECT',
'{{=it._operation}}({{=it._column}}) 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'));
var VALID_OPERATIONS = {
const VALID_OPERATIONS = {
count: true,
avg: true,
sum: true,
@@ -20,7 +18,7 @@ var VALID_OPERATIONS = {
max: true
};
var TYPE = 'formula';
const TYPE = 'formula';
/**
{
@@ -31,74 +29,90 @@ var TYPE = 'formula';
}
}
*/
function Formula(query, options) {
if (!_.isString(options.operation)) {
throw new Error('Formula expects `operation` in widget options');
module.exports = class Formula extends BaseDataview {
constructor (query, options = {}, queries = {}) {
super();
this._checkOptions(options);
this.query = query;
this.queries = queries;
this.column = options.column || '1';
this.operation = options.operation;
this._isFloatColumn = null;
}
if (!VALID_OPERATIONS[options.operation]) {
throw new Error("Formula does not support '" + options.operation + "' operation");
_checkOptions (options) {
if (typeof options.operation !== 'string') {
throw new Error(`Formula expects 'operation' in dataview options`);
}
if (!VALID_OPERATIONS[options.operation]) {
throw new Error(`Formula does not support '${options.operation}' operation`);
}
if (options.operation !== 'count' && typeof options.column !== 'string') {
throw new Error(`Formula expects 'column' in dataview options`);
}
}
if (options.operation !== 'count' && !_.isString(options.column)) {
throw new Error('Formula expects `column` in widget options');
sql (psql, override, callback) {
if (!callback) {
callback = override;
override = {};
}
if (this._isFloatColumn === null) {
this._isFloatColumn = false;
this.getColumnType(psql, this.column, this.queries.no_filters, (err, type) => {
if (!err && !!type) {
this._isFloatColumn = type.float;
}
this.sql(psql, override, callback);
});
return null;
}
const formulaSql = formulaQueryTpl({
isFloatColumn: this._isFloatColumn,
query: this.query,
operation: this.operation,
column: this.column
});
debug(formulaSql);
return callback(null, formulaSql);
}
BaseWidget.apply(this);
format (res) {
const {
result = 0,
nulls_count = 0,
nans_count,
infinities_count
} = res.rows[0] || {};
this.query = query;
this.column = options.column || '1';
this.operation = options.operation;
}
Formula.prototype = new BaseWidget();
Formula.prototype.constructor = Formula;
module.exports = Formula;
Formula.prototype.sql = function(psql, override, callback) {
if (!callback) {
callback = override;
override = {};
return {
operation: this.operation,
result,
nulls: nulls_count,
nans: nans_count,
infinities: infinities_count
};
}
var _query = this.query;
var formulaSql = formulaQueryTpl({
_query: _query,
_operation: this.operation,
_column: this.column
});
getType () {
return TYPE;
}
debug(formulaSql);
return callback(null, formulaSql);
};
Formula.prototype.format = function(result) {
var formattedResult = {
operation: this.operation,
result: 0,
nulls: 0
};
if (result.rows.length) {
formattedResult.operation = this.operation;
formattedResult.result = result.rows[0].result;
formattedResult.nulls = result.rows[0].nulls_count;
}
return formattedResult;
};
Formula.prototype.getType = function() {
return TYPE;
};
Formula.prototype.toString = function() {
return JSON.stringify({
_type: TYPE,
_query: this.query,
_column: this.column,
_operation: this.operation
});
toString () {
return JSON.stringify({
_type: TYPE,
_query: this.query,
_column: this.column,
_operation: this.operation
});
}
};

View File

@@ -1,307 +1,72 @@
var _ = require('underscore');
var BaseWidget = require('./base');
var debug = require('debug')('windshaft:dataview:histogram');
const debug = require('debug')('windshaft:dataview:histogram');
const NumericHistogram = require('./histograms/numeric-histogram');
const DateHistogram = require('./histograms/date-histogram');
var dot = require('dot');
dot.templateSettings.strip = false;
const DATE_HISTOGRAM = 'DateHistogram';
const NUMERIC_HISTOGRAM = 'NumericHistogram';
var columnTypeQueryTpl = dot.template(
'SELECT pg_typeof({{=it.column}})::oid FROM ({{=it.query}}) _cdb_histogram_column_type limit 1'
);
var columnCastTpl = dot.template("date_part('epoch', {{=it.column}})");
module.exports = class Histogram {
constructor (query, options, queries) {
this.query = query;
this.options = options || {};
this.queries = queries;
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,',
' avg({{=it._column}}) AS avg_val, count(1) 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,',
' avg({{=it._column}}) AS avg_val, count(1) 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,',
' avg({{=it._column}})::numeric AS avg,',
' 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'));
var TYPE = 'histogram';
/**
{
type: 'histogram',
options: {
column: 'name',
bins: 10 // OPTIONAL
}
}
*/
function Histogram(query, options) {
if (!_.isString(options.column)) {
throw new Error('Histogram expects `column` in widget options');
this.histogramImplementation = this._getHistogramImplementation();
}
this.query = query;
this.column = options.column;
this.bins = options.bins;
_getHistogramImplementation (override) {
let implementation = null;
this._columnType = null;
}
Histogram.prototype = new BaseWidget();
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.query
});
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') {
_column = columnCastTpl({column: _column});
}
var _query = this.query;
var basicsQuery, binsQuery;
if (override && _.has(override, 'start') && _.has(override, 'end') && _.has(override, 'bins')) {
debug('overriding with %j', override);
basicsQuery = overrideBasicsQueryTpl({
_query: _query,
_column: _column,
_start: getBinStart(override),
_end: getBinEnd(override)
});
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');
switch (this._getHistogramSubtype(override)) {
case DATE_HISTOGRAM:
debug('Delegating to DateHistogram with options: %j and overriding: %j', this.options, override);
implementation = new DateHistogram(this.query, this.options, this.queries);
break;
case NUMERIC_HISTOGRAM:
debug('Delegating to NumericHistogram with options: %j and overriding: %j', this.options, override);
implementation = new NumericHistogram(this.query, this.options, this.queries);
break;
default:
throw new Error('Unsupported Histogram type');
}
return implementation;
}
_getHistogramSubtype (override) {
if(this._isDateHistogram(override)) {
return DATE_HISTOGRAM;
}
var histogramSql = [
"WITH",
[
basicsQuery,
binsQuery,
nullsQueryTpl({
_query: _query,
_column: _column
})
].join(',\n'),
histogramQueryTpl({
_query: _query,
_column: _column
})
].join('\n');
return NUMERIC_HISTOGRAM;
}
debug(histogramSql);
_isDateHistogram (override = {}) {
return (this.options.hasOwnProperty('aggregation') || override.hasOwnProperty('aggregation'));
}
return callback(null, histogramSql);
};
Histogram.prototype.format = function(result, override) {
override = override || {};
var buckets = [];
var binsCount = getBinsCount(override);
var width = getWidth(override);
var binsStart = getBinStart(override);
var nulls = 0;
var avg;
if (result.rows.length) {
var firstRow = result.rows[0];
binsCount = firstRow.bins_number;
width = firstRow.bin_width || width;
avg = firstRow.avg_val;
nulls = firstRow.nulls_count;
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');
});
}
return {
bin_width: width,
bins_count: binsCount,
bins_start: binsStart,
nulls: nulls,
avg: avg,
bins: buckets
};
};
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;
}
function getWidth(override) {
var width = 0;
var binsCount = override.bins;
if (binsCount && Number.isFinite(override.start) && Number.isFinite(override.end)) {
width = (override.end - override.start) / binsCount;
}
return width;
}
Histogram.prototype.getType = function() {
return TYPE;
};
Histogram.prototype.toString = function() {
return JSON.stringify({
_type: TYPE,
_column: this.column,
_query: this.query
});
getResult (psql, override, callback) {
this.histogramImplementation = this._getHistogramImplementation(override);
this.histogramImplementation.getResult(psql, override, callback);
}
// In order to keep previous behaviour with overviews,
// we have to expose the following methods to bypass
// the concrete overview implementation
sql (psql, override, callback) {
this.histogramImplementation.sql(psql, override, callback);
}
format (result, override) {
return this.histogramImplementation.format(result, override);
}
getType () {
return this.histogramImplementation.getType();
}
toString () {
return this.histogramImplementation.toString();
}
};

View File

@@ -0,0 +1,85 @@
const BaseDataview = require('../base');
const TYPE = 'histogram';
module.exports = class BaseHistogram extends BaseDataview {
constructor (query, options, queries) {
super();
if (typeof options.column !== 'string') {
throw new Error('Histogram expects `column` in widget options');
}
this.query = query;
this.queries = queries;
this.column = options.column;
this.bins = options.bins;
this._columnType = null;
}
sql (psql, override, callback) {
if (!callback) {
callback = override;
override = {};
}
if (this._columnType === null) {
this.getColumnType(psql, this.column, this.queries.no_filters, (err, type) => {
// assume numeric, will fail later
this._columnType = 'numeric';
if (!err && !!type) {
this._columnType = Object.keys(type).find(function (key) {
return type[key];
});
}
this.sql(psql, override, callback);
}, true); // use read-only transaction
return null;
}
return this._buildQuery(psql, override, callback);
}
format (result, override) {
const histogram = this._getSummary(result, override);
histogram.bins = this._getBuckets(result);
return histogram;
}
getType () {
return TYPE;
}
toString () {
return JSON.stringify({
_type: TYPE,
_column: this.column,
_query: this.query
});
}
_hasOverridenRange (override) {
return override && override.hasOwnProperty('start') && override.hasOwnProperty('end');
}
_getBinStart (override = {}) {
if (this._hasOverridenRange(override)) {
return Math.min(override.start, override.end);
}
return override.start || 0;
}
_getBinEnd (override = {}) {
if (this._hasOverridenRange(override)) {
return Math.max(override.start, override.end);
}
return override.end || 0;
}
_getBinsCount (override = {}) {
return override.bins || 0;
}
};

View File

@@ -0,0 +1,322 @@
const BaseHistogram = require('./base-histogram');
const debug = require('debug')('windshaft:dataview:date-histogram');
const utils = require('../../../utils/query-utils');
/**
* Gets the name of a timezone with the same offset as the required
* using the pg_timezone_names table. We do this because it's simpler to pass
* the name than to pass the offset itself as PostgreSQL uses different
* sign convention. For example: TIME ZONE 'CET' is equal to TIME ZONE 'UTC-1',
* not 'UTC+1' which would be expected.
* Gives priority to Etc/GMT±N timezones but still support odd offsets like 8.5
* hours for Asia/Pyongyang.
* It also makes it easier to, in the future, support the input of expected timezone
* instead of the offset; that is using 'Europe/Madrid' instead of
* '+3600' or '+7200'. The daylight saving status can be handled by postgres.
*/
const offsetNameQueryTpl = ctx => `
WITH __wd_tz AS
(
SELECT name
FROM pg_timezone_names
WHERE utc_offset = interval '${ctx.offset} hours'
ORDER BY CASE WHEN name LIKE 'Etc/GMT%' THEN 0 ELSE 1 END
LIMIT 1
),`;
/**
* Function to get the subquery that places each row in its bin depending on
* the aggregation. Since the data stored is in epoch we need to adapt it to
* our timezone so when calling date_trunc it falls into the correct bin
*/
function dataBucketsQuery(ctx) {
var condition_str = '';
if (ctx.start !== 0) {
condition_str = `WHERE ${ctx.column} >= to_timestamp(${ctx.start})`;
}
if (ctx.end !== 0) {
if (condition_str === '') {
condition_str = `WHERE ${ctx.column} <= to_timestamp(${ctx.end})`;
}
else {
condition_str += ` and ${ctx.column} <= to_timestamp(${ctx.end})`;
}
}
return `
__wd_buckets AS
(
SELECT
date_trunc('${ctx.aggregation}', timezone(__wd_tz.name, ${ctx.column}::timestamptz)) as timestamp,
count(*) as freq,
${utils.countNULLs(ctx)} as nulls_count
FROM
(
${ctx.query}
) __source, __wd_tz
${condition_str}
GROUP BY 1, __wd_tz.name
),`;
}
/**
* Function that generates an array with all the possible bins between the
* start and end date. If not provided we use the min and max generated from
* the dataBucketsQuery
*/
function allBucketsArrayQuery(ctx) {
var extra_from = ``;
var series_start = ``;
var series_end = ``;
if (ctx.start === 0) {
extra_from = `, __wd_buckets GROUP BY __wd_tz.name`;
series_start = `min(__wd_buckets.timestamp)`;
} else {
series_start = `date_trunc('${ctx.aggregation}', timezone(__wd_tz.name, to_timestamp(${ctx.start})))`;
}
if (ctx.end === 0) {
extra_from = `, __wd_buckets GROUP BY __wd_tz.name`;
series_end = `max(__wd_buckets.timestamp)`;
} else {
series_end = `date_trunc('${ctx.aggregation}', timezone(__wd_tz.name, to_timestamp(${ctx.end})))`;
}
return `
__wd_all_buckets AS
(
SELECT ARRAY(
SELECT
generate_series(
${series_start},
${series_end},
interval '${ctx.interval}') as bin_start
FROM __wd_tz${extra_from}
) as bins
)`;
}
const dateIntervalQueryTpl = ctx => `
WITH
__cdb_dates AS (
SELECT
MAX(${ctx.column}::timestamp) AS __cdb_end,
MIN(${ctx.column}::timestamp) AS __cdb_start
FROM (${ctx.query}) __cdb_source
),
__cdb_interval_in_days AS (
SELECT
DATE_PART('day', __cdb_end - __cdb_start) AS __cdb_days
FROM __cdb_dates
),
__cdb_interval_in_hours AS (
SELECT
__cdb_days * 24 + DATE_PART('hour', __cdb_end - __cdb_start) AS __cdb_hours
FROM __cdb_interval_in_days, __cdb_dates
),
__cdb_interval_in_minutes AS (
SELECT
__cdb_hours * 60 + DATE_PART('minute', __cdb_end - __cdb_start) AS __cdb_minutes
FROM __cdb_interval_in_hours, __cdb_dates
),
__cdb_interval_in_seconds AS (
SELECT
__cdb_minutes * 60 + DATE_PART('second', __cdb_end - __cdb_start) AS __cdb_seconds
FROM __cdb_interval_in_minutes, __cdb_dates
)
SELECT
ROUND(__cdb_days / 365243) AS millennium,
ROUND(__cdb_days / 36525) AS century,
ROUND(__cdb_days / 3652) AS decade,
ROUND(__cdb_days / 365) AS year,
ROUND(__cdb_days / 91) AS quarter,
ROUND(__cdb_days / 30) AS month,
ROUND(__cdb_days / 7) AS week,
__cdb_days AS day,
__cdb_hours AS hour,
__cdb_minutes AS minute,
__cdb_seconds AS second
FROM __cdb_interval_in_days, __cdb_interval_in_hours, __cdb_interval_in_minutes, __cdb_interval_in_seconds
`;
/** Constant to switch between aggregations in auto mode */
const MAX_INTERVAL_VALUE = 100;
const DATE_AGGREGATIONS = {
'auto': true,
'second' : true,
'minute': true,
'hour': true,
'day': true,
'week': true,
'month': true,
'quarter': true,
'year': true,
'decade' : true,
'century' : true,
'millennium' : true
};
/**
date_histogram: {
type: 'histogram',
options: {
column: 'date', // column data type: date
aggregation: 'day' // MANDATORY
offset: -7200 // OPTIONAL (UTC offset in seconds)
}
}
*/
module.exports = class DateHistogram extends BaseHistogram {
constructor (query, options, queries) {
super(query, options, queries);
this.aggregation = options.aggregation;
this.offset = options.offset;
}
_buildQueryTpl (ctx) {
return `
${offsetNameQueryTpl(ctx)}
${dataBucketsQuery(ctx)}
${allBucketsArrayQuery(ctx)}
SELECT
array_position(__wd_all_buckets.bins, __wd_buckets.timestamp) - 1 as bin,
date_part('epoch', timezone(__wd_tz.name, __wd_buckets.timestamp)) AS timestamp,
__wd_buckets.freq as freq,
date_part('epoch', timezone(__wd_tz.name, (__wd_all_buckets.bins)[1])) as timestamp_start,
array_length(__wd_all_buckets.bins, 1) as bins_number,
date_part('epoch', interval '${ctx.interval}') as bin_width,
__wd_buckets.nulls_count as nulls_count
FROM __wd_buckets, __wd_all_buckets, __wd_tz
GROUP BY __wd_tz.name, __wd_all_buckets.bins, __wd_buckets.timestamp, __wd_buckets.nulls_count, __wd_buckets.freq
ORDER BY bin ASC;
`;
}
_buildQuery (psql, override, callback) {
if (!this._isValidAggregation(override)) {
return callback(new Error('Invalid aggregation value. Valid ones: ' +
Object.keys(DATE_AGGREGATIONS).join(', ')
));
}
if (this._getAggregation(override) === 'auto') {
this._getAutomaticAggregation(psql, function (err, aggregation) {
if (err || aggregation === 'none') {
this.aggregation = 'day';
} else {
this.aggregation = aggregation;
}
override.aggregation = this.aggregation;
this._buildQuery(psql, override, callback);
}.bind(this));
return null;
}
var interval = this._getAggregation(override) === 'quarter' ?
'3 months' : '1 ' + this._getAggregation(override);
const histogramSql = this._buildQueryTpl({
override: override,
query: this.query,
column: this.column,
aggregation: this._getAggregation(override),
start: this._getBinStart(override),
end: this._getBinEnd(override),
offset: this._parseOffset(override),
interval: interval
});
debug(histogramSql);
return callback(null, histogramSql);
}
_isValidAggregation (override) {
return DATE_AGGREGATIONS.hasOwnProperty(this._getAggregation(override));
}
_getAutomaticAggregation (psql, callback) {
const dateIntervalQuery = dateIntervalQueryTpl({
query: this.query,
column: this.column
});
psql.query(dateIntervalQuery, function (err, result) {
if (err) {
return callback(err);
}
const aggregations = result.rows[0];
const aggregation = Object.keys(aggregations)
.map(key => ({ name: key, value: aggregations[key] }))
.reduce((closer, current) => {
if (current.value > MAX_INTERVAL_VALUE) {
return closer;
}
const closerDiff = MAX_INTERVAL_VALUE - closer.value;
const currentDiff = MAX_INTERVAL_VALUE - current.value;
if (Number.isFinite(current.value) && closerDiff > currentDiff) {
return current;
}
return closer;
}, { name: 'none', value: -1 });
callback(null, aggregation.name);
});
}
_getSummary (result, override) {
const firstRow = result.rows[0] || {};
return {
aggregation: this._getAggregation(override),
offset: this._getOffset(override),
timestamp_start: firstRow.timestamp_start,
bin_width: firstRow.bin_width || 0,
bins_count: firstRow.bins_number || 0,
bins_start: firstRow.timestamp,
nulls: firstRow.nulls_count,
infinities: firstRow.infinities_count,
nans: firstRow.nans_count,
avg: firstRow.avg_val
};
}
_getBuckets (result) {
result.rows.forEach(function(row) {
row.min = row.max = row.avg = row.timestamp;
});
return result.rows.map(({ bin, min, max, avg, freq, timestamp }) => ({ bin, min, max, avg, freq, timestamp }));
}
_getAggregation (override = {}) {
return override.aggregation ? override.aggregation : this.aggregation;
}
_getOffset (override = {}) {
return Number.isFinite(override.offset) ? override.offset : (this.offset || 0);
}
_parseOffset (override) {
if (this._shouldIgnoreOffset(override)) {
return '0';
}
const offsetInHours = Math.ceil(this._getOffset(override) / 3600);
return '' + offsetInHours;
}
_shouldIgnoreOffset (override) {
return (this._getAggregation(override) === 'hour' || this._getAggregation(override) === 'minute');
}
};

View File

@@ -0,0 +1,195 @@
const BaseHistogram = require('./base-histogram');
const debug = require('debug')('windshaft:dataview:numeric-histogram');
const utils = require('../../../utils/query-utils');
/** Query to get min and max values from the query */
const irqQueryTpl = ctx => `
__cdb_filtered_source AS (
SELECT *
FROM (${ctx.query}) __cdb_filtered_source_query
WHERE ${utils.handleFloatColumn(ctx)} IS NOT NULL
),
__cdb_basics AS (
SELECT
max(${ctx.column}) AS __cdb_max_val,
min(${ctx.column}) AS __cdb_min_val,
count(1) AS __cdb_total_rows
FROM __cdb_filtered_source
)
`;
/* Query to calculate the number of bins (needs irqQueryTpl before it*/
const binsQueryTpl = ctx => `
__cdb_iqrange AS (
SELECT max(quartile_max) - min(quartile_max) AS __cdb_iqr
FROM (
SELECT quartile, max(_cdb_iqr_column) AS quartile_max from (
SELECT ${ctx.column} AS _cdb_iqr_column, ntile(4) over (order by ${ctx.column}
) AS quartile
FROM __cdb_filtered_source) _cdb_quartiles
WHERE quartile = 1 or quartile = 3
GROUP BY 1
) __cdb_iqr
),
__cdb_bins AS (
SELECT
CASE WHEN __cdb_total_rows = 0 OR __cdb_iqr = 0
THEN 1
ELSE GREATEST(
LEAST(${ctx.minBins}, CAST(__cdb_total_rows AS INT)),
LEAST(
CAST(((__cdb_max_val - __cdb_min_val) / (2 * __cdb_iqr * power(__cdb_total_rows, 1/3))) AS INT),
${ctx.maxBins}
)
)
END AS __cdb_bins_number
FROM __cdb_basics, __cdb_iqrange, __cdb_filtered_source
LIMIT 1
)
`;
const BIN_MIN_NUMBER = 6;
const BIN_MAX_NUMBER = 48;
/**
Numeric histogram:
{
type: 'histogram',
options: {
column: 'name', // column data type: numeric
bins: 10 // OPTIONAL
}
}
*/
module.exports = class NumericHistogram extends BaseHistogram {
constructor (query, options, queries) {
super(query, options, queries);
}
_buildQuery (psql, override, callback) {
const histogramSql = this._buildQueryTpl({
column: this._columnType === 'date' ? utils.columnCastTpl({ column: this.column }) : this.column,
isFloatColumn: this._columnType === 'float',
query: this.query,
start: this._getBinStart(override),
end: this._getBinEnd(override),
bins: this._getBinsCount(override),
minBins: BIN_MIN_NUMBER,
maxBins: BIN_MAX_NUMBER
});
debug(histogramSql);
return callback(null, histogramSql);
}
/**
* ctx: Object with the following values
* ctx.column -- Column for the histogram
* ctx.isFloatColumn - Whether the column is float or not
* ctx.query -- Subquery to extract data
* ctx.start -- Start value for the bins. [>= end to force calculation]
* ctx.end -- End value for the bins.
* ctx.bins -- Numbers of bins to generate [<0 to force calculation]
* ctx.minBins - If !full min bins to calculate [Optional]
* ctx.maxBins - If !full max bins to calculate [Optional]
*/
_buildQueryTpl (ctx) {
var extra_tables = ``;
var extra_queries = ``;
var extra_groupby = ``;
if (ctx.start >= ctx.end) {
ctx.end = `__cdb_basics.__cdb_max_val`;
ctx.start = `__cdb_basics.__cdb_min_val`;
extra_groupby = `, __cdb_basics.__cdb_max_val, __cdb_basics.__cdb_min_val`;
extra_tables = `, __cdb_basics`;
extra_queries = `WITH ${irqQueryTpl(ctx)}`;
}
if (ctx.bins <= 0) {
ctx.bins = `__cdb_bins.__cdb_bins_number`;
extra_groupby += `, __cdb_bins.__cdb_bins_number`;
extra_tables += `, __cdb_bins`;
extra_queries = `WITH ${irqQueryTpl(ctx)}, ${binsQueryTpl(ctx)}`;
}
return `
${extra_queries}
SELECT
(${ctx.end} - ${ctx.start}) / ${ctx.bins}::float AS bin_width,
${ctx.bins} as bins_number,
${utils.countNULLs(ctx)} AS nulls_count,
${utils.countInfinites(ctx)} AS infinities_count,
${utils.countNaNs(ctx)} AS nans_count,
min(${utils.handleFloatColumn(ctx)}) AS min,
max(${utils.handleFloatColumn(ctx)}) AS max,
avg(${utils.handleFloatColumn(ctx)}) AS avg,
sum(CASE WHEN (${utils.handleFloatColumn(ctx)} is not NULL) THEN 1 ELSE 0 END) as freq,
CASE WHEN ${ctx.start} = ${ctx.end}
THEN 0
ELSE GREATEST(1, LEAST(
${ctx.bins},
WIDTH_BUCKET(${utils.handleFloatColumn(ctx)}, ${ctx.start}, ${ctx.end}, ${ctx.bins}))) - 1
END AS bin
FROM
(
${ctx.query}
) __cdb_filtered_source_query${extra_tables}
GROUP BY 10${extra_groupby}
ORDER BY 10;`;
}
_hasOverridenBins (override) {
return override && override.hasOwnProperty('bins');
}
_getSummary (result, override) {
const firstRow = result.rows[0] || {};
var total_nulls = 0;
var total_infinities = 0;
var total_nans = 0;
var total_avg = 0;
var total_count = 0;
result.rows.forEach(function(row) {
total_nulls += row.nulls_count;
total_infinities += row.infinities_count;
total_nans += row.nans_count;
total_avg += row.avg * row.freq;
total_count += row.freq;
});
if (total_count !== 0) {
total_avg /= total_count;
}
return {
bin_width: firstRow.bin_width,
bins_count: firstRow.bins_number,
bins_start: this._populateBinStart(firstRow, override),
nulls: total_nulls,
infinities: total_infinities,
nans: total_nans,
avg: total_avg
};
}
_getBuckets (result) {
return result.rows.map(({ bin, min, max, avg, freq }) => ({ bin, min, max, avg, freq }));
}
_populateBinStart (firstRow, override = {}) {
let binStart;
if (override.hasOwnProperty('start')) {
binStart = this._getBinStart(override);
} else {
binStart = firstRow.min;
}
return binStart;
}
};

View File

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

View File

@@ -1,66 +0,0 @@
var dot = require('dot');
dot.templateSettings.strip = false;
var BaseWidget = require('./base');
var TYPE = 'list';
var listSqlTpl = dot.template('select {{=it._columns}} from ({{=it._query}}) as _cdb_list');
/**
{
type: 'list',
options: {
columns: ['name', 'description']
}
}
*/
function List(query, options) {
options = options || {};
if (!Array.isArray(options.columns)) {
throw new Error('List expects `columns` array in widget options');
}
BaseWidget.apply(this);
this.query = query;
this.columns = options.columns;
}
List.prototype = new BaseWidget();
List.prototype.constructor = List;
module.exports = List;
List.prototype.sql = function(psql, override, callback) {
if (!callback) {
callback = override;
}
var listSql = listSqlTpl({
_query: this.query,
_columns: this.columns.join(', ')
});
return callback(null, listSql);
};
List.prototype.format = function(result) {
return {
rows: result.rows
};
};
List.prototype.getType = function() {
return TYPE;
};
List.prototype.toString = function() {
return JSON.stringify({
_type: TYPE,
_query: this.query,
_columns: this.columns.join(', ')
});
};

View File

@@ -1,14 +1,36 @@
var BaseOverviewsDataview = require('./base');
var BaseDataview = require('../aggregation');
var debug = require('debug')('windshaft:widget:aggregation:overview');
var dot = require('dot');
dot.templateSettings.strip = false;
var filteredQueryTpl = dot.template([
'filtered_source AS (',
' SELECT *',
' FROM ({{=it._query}}) _cdb_filtered_source',
' {{?it._aggregationColumn && it._isFloatColumn}}WHERE',
' {{=it._aggregationColumn}} != \'infinity\'::float',
' AND',
' {{=it._aggregationColumn}} != \'-infinity\'::float',
' AND',
' {{=it._aggregationColumn}} != \'NaN\'::float{{?}}',
')'
].join(' \n'));
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',
' {{?it._isFloatColumn}},sum(',
' CASE',
' WHEN {{=it._aggregationColumn}} = \'infinity\'::float OR {{=it._aggregationColumn}} = \'-infinity\'::float',
' THEN 1',
' ELSE 0',
' END',
' ) AS infinities_count,',
' sum(CASE WHEN {{=it._aggregationColumn}} = \'NaN\'::float THEN 1 ELSE 0 END) AS nans_count{{?}}',
' FROM ({{=it._query}}) _cdb_aggregation_nulls',
')'
].join('\n'));
@@ -17,47 +39,65 @@ 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',
' FROM filtered_source',
' {{?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 filtered_source',
' 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',
'SELECT CAST(category AS text), value, false as agg, nulls_count, min_val, max_val,',
' count, categories_count{{?it._isFloatColumn}}, nans_count, infinities_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',
'SELECT \'Other\' category, sum(value), true as agg, nulls_count, min_val, max_val,',
' count, categories_count{{?it._isFloatColumn}}, nans_count, infinities_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'
'GROUP BY nulls_count, min_val, max_val, count,',
' categories_count{{?it._isFloatColumn}}, nans_count, infinities_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',
'GROUP BY category, nulls_count, min_val, max_val, count, categories_count',
' nulls_count, min_val, max_val, count, categories_count{{?it._isFloatColumn}}, nans_count, infinities_count{{?}}',
'FROM filtered_source, summary, categories_summary_min_max, categories_summary_count',
'GROUP BY category, nulls_count, min_val, max_val, count,',
' categories_count{{?it._isFloatColumn}}, nans_count, infinities_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);
function Aggregation(query, options, queryRewriter, queryRewriteData, params, queries) {
BaseOverviewsDataview.call(this, query, options, BaseDataview, queryRewriter, queryRewriteData, params, queries);
this.query = query;
this.queries = queries;
this.column = options.column;
this.aggregation = options.aggregation;
this.aggregationColumn = options.aggregationColumn;
this._isFloatColumn = null;
}
Aggregation.prototype = Object.create(BaseOverviewsDataview.prototype);
@@ -65,34 +105,62 @@ Aggregation.prototype.constructor = Aggregation;
module.exports = Aggregation;
Aggregation.prototype.sql = function(psql, filters, override, callback) {
Aggregation.prototype.sql = function(psql, override, callback) {
var self = this;
if (!callback) {
callback = override;
override = {};
}
var _query = this.rewrittenQuery(this.query);
var _aggregationColumn = this.aggregation !== 'count' ? this.aggregationColumn : null;
if (this.aggregationColumn && this._isFloatColumn === null) {
this._isFloatColumn = false;
this.getColumnType(psql, this.aggregationColumn, this.queries.no_filters, function (err, type) {
if (!err && !!type) {
self._isFloatColumn = type.float;
}
self.sql(psql, override, callback);
});
return null;
}
var aggregationSql;
if (!!override.ownFilter) {
aggregationSql = [
"WITH",
[
summaryQueryTpl({
filteredQueryTpl({
_isFloatColumn: this._isFloatColumn,
_query: _query,
_column: this.column
_column: this.column,
_aggregationColumn: _aggregationColumn
}),
summaryQueryTpl({
_isFloatColumn: this._isFloatColumn,
_query: _query,
_column: this.column,
_aggregationColumn: _aggregationColumn
}),
rankedCategoriesQueryTpl({
_query: _query,
_column: this.column,
_aggregation: this.getAggregationSql()
_aggregation: this.getAggregationSql(),
_aggregationColumn: _aggregationColumn
}),
categoriesSummaryQueryTpl({
categoriesSummaryMinMaxQueryTpl({
_query: _query,
_column: this.column
}),
categoriesSummaryCountQueryTpl({
_query: _query,
_column: this.column
})
].join(',\n'),
aggregationQueryTpl({
_isFloatColumn: this._isFloatColumn,
_query: _query,
_column: this.column,
_aggregation: this.getAggregationSql(),
@@ -103,21 +171,35 @@ Aggregation.prototype.sql = function(psql, filters, override, callback) {
aggregationSql = [
"WITH",
[
summaryQueryTpl({
filteredQueryTpl({
_isFloatColumn: this._isFloatColumn,
_query: _query,
_column: this.column
_column: this.column,
_aggregationColumn: _aggregationColumn
}),
summaryQueryTpl({
_isFloatColumn: this._isFloatColumn,
_query: _query,
_column: this.column,
_aggregationColumn: _aggregationColumn
}),
rankedCategoriesQueryTpl({
_query: _query,
_column: this.column,
_aggregation: this.getAggregationSql()
_aggregation: this.getAggregationSql(),
_aggregationColumn: _aggregationColumn
}),
categoriesSummaryQueryTpl({
categoriesSummaryMinMaxQueryTpl({
_query: _query,
_column: this.column
}),
categoriesSummaryCountQueryTpl({
_query: _query,
_column: this.column
})
].join(',\n'),
rankedAggregationQueryTpl({
_isFloatColumn: this._isFloatColumn,
_query: _query,
_column: this.column,
_limit: CATEGORIES_LIMIT
@@ -125,6 +207,8 @@ Aggregation.prototype.sql = function(psql, filters, override, callback) {
].join('\n');
}
debug(aggregationSql);
return callback(null, aggregationSql);
};

View File

@@ -1,14 +1,15 @@
var _ = require('underscore');
var BaseDataview = require('../base');
function BaseOverviewsDataview(query, queryOptions, BaseDataview, queryRewriter, queryRewriteData, options) {
function BaseOverviewsDataview(query, queryOptions, BaseDataview, queryRewriter, queryRewriteData, options, queries) {
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);
this.queries = queries;
this.baseDataview = new this.BaseDataview(this.query, this.queryOptions, this.queries);
}
module.exports = BaseOverviewsDataview;
@@ -55,20 +56,20 @@ BaseOverviewsDataview.prototype.rewrittenQuery = function(query) {
};
// Default behaviour
BaseOverviewsDataview.prototype.defaultSql = function(psql, filters, override, callback) {
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, filters, override, callback);
return dataview.sql(psql, override, callback);
};
// default implementation that can be override in derived classes:
BaseOverviewsDataview.prototype.sql = function(psql, filters, override, callback) {
return this.defaultSql(psql, filters, override, callback);
BaseOverviewsDataview.prototype.sql = function(psql, override, callback) {
return this.defaultSql(psql, override, callback);
};
BaseOverviewsDataview.prototype.search = function(psql, userQuery, callback) {

View File

@@ -14,7 +14,8 @@ OverviewsDataviewFactory.prototype.getDataview = function(query, dataviewDefinit
return parentFactory.getDataview(query, dataviewDefinition);
}
return new dataviews[type](
query, dataviewDefinition.options, this.queryRewriter, this.queryRewriteData, this.options
query, dataviewDefinition.options, this.queryRewriter, this.queryRewriteData, this.options,
dataviewDefinition.sql
);
};

View File

@@ -1,34 +1,44 @@
var BaseOverviewsDataview = require('./base');
var BaseDataview = require('../formula');
var debug = require('debug')('windshaft:widget:formula:overview');
const utils = require('../../../utils/query-utils');
var dot = require('dot');
dot.templateSettings.strip = false;
var formulaQueryTpls = {
'count': dot.template([
'SELECT',
'sum(_feature_count) AS result,',
'(SELECT count(1) FROM ({{=it._query}}) _cdb_formula_nulls WHERE {{=it._column}} IS NULL) AS nulls_count',
'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')),
const VALID_OPERATIONS = {
count: true,
sum: true,
avg: true
};
function Formula(query, options, queryRewriter, queryRewriteData, params) {
BaseOverviewsDataview.call(this, query, options, BaseDataview, queryRewriter, queryRewriteData, params);
/** Formulae to calculate the end result using _feature_count from the overview table*/
function dataviewResult(ctx) {
switch (ctx.operation) {
case 'count':
return `sum(_feature_count)`;
case 'sum':
return `sum(${utils.handleFloatColumn(ctx)}*_feature_count)`;
case 'avg':
return `sum(${utils.handleFloatColumn(ctx)}*_feature_count)/sum(_feature_count) `;
}
return `${ctx.operation}(${utils.handleFloatColumn(ctx)})`;
}
const formulaQueryTpl = ctx =>
`SELECT
${dataviewResult(ctx)} AS result,
${utils.countNULLs(ctx)} AS nulls_count
${ctx.isFloatColumn ? `,${utils.countInfinites(ctx)} AS infinities_count,` : ``}
${ctx.isFloatColumn ? `${utils.countNaNs(ctx)} AS nans_count` : ``}
FROM (${ctx.query}) __cdb_formula`;
function Formula(query, options, queryRewriter, queryRewriteData, params, queries) {
BaseOverviewsDataview.call(this, query, options, BaseDataview, queryRewriter, queryRewriteData, params, queries);
this.column = options.column || '1';
this.operation = options.operation;
this._isFloatColumn = null;
this.queries = queries;
}
Formula.prototype = Object.create(BaseOverviewsDataview.prototype);
@@ -36,21 +46,33 @@ Formula.prototype.constructor = Formula;
module.exports = Formula;
Formula.prototype.sql = function(psql, filters, 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);
Formula.prototype.sql = function (psql, override, callback) {
var self = this;
if (!VALID_OPERATIONS[this.operation]) {
return this.defaultSql(psql, override, callback);
}
// default behaviour
return this.defaultSql(psql, filters, override, callback);
if (this._isFloatColumn === null) {
this._isFloatColumn = false;
this.getColumnType(psql, this.column, this.queries.no_filters, function (err, type) {
if (!err && !!type) {
self._isFloatColumn = type.float;
}
self.sql(psql, override, callback);
});
return null;
}
var formulaSql = formulaQueryTpl({
isFloatColumn: this._isFloatColumn,
query: this.rewrittenQuery(this.query),
operation: this.operation,
column: this.column
});
callback = callback || override;
debug(formulaSql);
return callback(null, formulaSql);
};

View File

@@ -1,24 +1,35 @@
var _ = require('underscore');
var BaseOverviewsDataview = require('./base');
var BaseDataview = require('../histogram');
var debug = require('debug')('windshaft:dataview:histogram:overview');
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 columnCastTpl = dot.template("date_part('epoch', {{=it.column}})");
var BIN_MIN_NUMBER = 6;
var BIN_MAX_NUMBER = 48;
var filteredQueryTpl = dot.template([
'filtered_source AS (',
' SELECT *',
' FROM ({{=it._query}}) _cdb_filtered_source',
' WHERE',
' {{=it._column}} IS NOT NULL',
' {{?it._isFloatColumn}}AND',
' {{=it._column}} != \'infinity\'::float',
' AND',
' {{=it._column}} != \'-infinity\'::float',
' AND',
' {{=it._column}} != \'NaN\'::float{{?}}',
')'
].join(' \n'));
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',
' FROM filtered_source',
')'
].join(' \n'));
@@ -27,7 +38,7 @@ var overrideBasicsQueryTpl = dot.template([
' 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',
' FROM filtered_source',
')'
].join('\n'));
@@ -38,7 +49,7 @@ var iqrQueryTpl = dot.template([
' 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',
' FROM filtered_source) _cdb_quartiles',
' WHERE quartile = 1 or quartile = 3',
' GROUP BY quartile',
' ) _cdb_iqr',
@@ -57,7 +68,7 @@ var binsQueryTpl = dot.template([
' )',
' )',
' END AS bins_number',
' FROM basics, iqrange, ({{=it._query}}) _cdb_bins',
' FROM basics, iqrange, filtered_source',
' LIMIT 1',
')'
].join('\n'));
@@ -77,11 +88,34 @@ var nullsQueryTpl = dot.template([
')'
].join('\n'));
var infinitiesQueryTpl = dot.template([
'infinities AS (',
' SELECT',
' count(*) AS infinities_count',
' FROM ({{=it._query}}) _cdb_histogram_infinities',
' WHERE',
' {{=it._column}} = \'infinity\'::float',
' OR',
' {{=it._column}} = \'-infinity\'::float',
')'
].join('\n'));
var nansQueryTpl = dot.template([
'nans AS (',
' SELECT',
' count(*) AS nans_count',
' FROM ({{=it._query}}) _cdb_histogram_infinities',
' WHERE {{=it._column}} = \'NaN\'::float',
')'
].join('\n'));
var histogramQueryTpl = dot.template([
'SELECT',
' (max_val - min_val) / cast(bins_number as float) AS bin_width,',
' bins_number,',
' nulls_count,',
' {{?it._isFloatColumn}}infinities_count,',
' nans_count,{{?}}',
' avg_val,',
' CASE WHEN min_val = max_val',
' THEN 0',
@@ -91,16 +125,17 @@ var histogramQueryTpl = dot.template([
' 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',
'FROM filtered_source, basics, nulls, bins{{?it._isFloatColumn}},infinities, nans{{?}}',
'GROUP BY bin, bins_number, bin_width, nulls_count, avg_val',
' {{?it._isFloatColumn}}, infinities_count, nans_count{{?}}',
'ORDER BY bin'
].join('\n'));
function Histogram(query, options, queryRewriter, queryRewriteData, params) {
BaseOverviewsDataview.call(this, query, options, BaseDataview, queryRewriter, queryRewriteData, params);
function Histogram(query, options, queryRewriter, queryRewriteData, params, queries) {
BaseOverviewsDataview.call(this, query, options, BaseDataview, queryRewriter, queryRewriteData, params, queries);
this.query = query;
this.queries = queries;
this.column = options.column;
this.bins = options.bins;
@@ -112,36 +147,23 @@ Histogram.prototype.constructor = Histogram;
module.exports = Histogram;
var DATE_OIDS = {
1082: true,
1114: true,
1184: true
};
Histogram.prototype.sql = function(psql, override, callback) {
var self = this;
if (!callback) {
callback = override;
override = {};
}
var self = this;
var _column = this.column;
var columnTypeQuery = columnTypeQueryTpl({
column: _column, query: this.rewrittenQuery(this.query)
});
if (this._columnType === null) {
psql.query(columnTypeQuery, function(err, result) {
this.getColumnType(psql, this.column, this.queries.no_filters, function (err, type) {
// 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';
}
if (!err && !!type) {
self._columnType = Object.keys(type).find(function (key) {
return type[key];
});
}
self.sql(psql, override, callback);
}, true); // use read-only transaction
@@ -149,14 +171,29 @@ Histogram.prototype.sql = function(psql, override, callback) {
}
if (this._columnType === 'date') {
_column = columnCastTpl({column: _column});
// 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 histogramSql = this._buildQuery(override);
return callback(null, histogramSql);
};
Histogram.prototype._buildQuery = function (override) {
var filteredQuery, basicsQuery, binsQuery;
var _column = this.column;
var _query = this.rewrittenQuery(this.query);
var basicsQuery, binsQuery;
filteredQuery = filteredQueryTpl({
_isFloatColumn: this._columnType === 'float',
_query: _query,
_column: _column
});
if (override && _.has(override, 'start') && _.has(override, 'end') && _.has(override, 'bins')) {
if (this._shouldOverride(override)) {
debug('overriding with %j', override);
basicsQuery = overrideBasicsQueryTpl({
_query: _query,
_column: _column,
@@ -175,7 +212,7 @@ Histogram.prototype.sql = function(psql, override, callback) {
_column: _column
});
if (override && _.has(override, 'bins')) {
if (this._shouldOverrideBins(override)) {
binsQuery = [
overrideBinsQueryTpl({
_bins: override.bins
@@ -196,22 +233,50 @@ Histogram.prototype.sql = function(psql, override, callback) {
}
}
var cteSql = [
filteredQuery,
basicsQuery,
binsQuery,
nullsQueryTpl({
_query: _query,
_column: _column
})
];
var histogramSql = [
"WITH",
[
basicsQuery,
binsQuery,
nullsQueryTpl({
if (this._columnType === 'float') {
cteSql.push(
infinitiesQueryTpl({
_query: _query,
_column: _column
}),
nansQueryTpl({
_query: _query,
_column: _column
})
].join(',\n'),
);
}
var histogramSql = [
"WITH",
cteSql.join(',\n'),
histogramQueryTpl({
_isFloatColumn: this._columnType === 'float',
_query: _query,
_column: _column
})
].join('\n');
return callback(null, histogramSql);
debug(histogramSql);
return histogramSql;
};
Histogram.prototype._shouldOverride = function (override) {
return override && _.has(override, 'start') && _.has(override, 'end') && _.has(override, 'bins');
};
Histogram.prototype._shouldOverrideBins = function (override) {
return override && _.has(override, 'bins');
};

View File

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

View File

@@ -1,11 +0,0 @@
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

@@ -8,7 +8,7 @@ var filterQueryTpl = dot.template([
].join('\n'));
var bboxFilterTpl = dot.template(
'{{=it._column}} && ST_Transform(ST_MakeEnvelope({{=it._bbox}}, 4326), {{=it._srid}})'
'ST_Intersects({{=it._column}}, ST_Transform(ST_MakeEnvelope({{=it._bbox}}, 4326), {{=it._srid}}))'
);
var LATITUDE_MAX_VALUE = 85.0511287798066;
@@ -66,7 +66,8 @@ function getBoundingBoxes(west, south, east, north) {
bboxes.push([west, south, east, north]);
} else {
bboxes.push([west, south, 180, north]);
bboxes.push([-180, south, east % 180, north]);
// here we assume west,east have been adjusted => west >= -180 => east > 180
bboxes.push([-180, south, east - 360, north]);
}
return bboxes;

View File

@@ -0,0 +1,172 @@
const AggregationMapConfig = require('../../aggregation/aggregation-mapconfig');
const queryUtils = require('../../../utils/query-utils');
const unsupportedGeometryTypeErrorMessage = ctx =>
`Unsupported geometry type: ${ctx.geometryType}. ` +
`Aggregation is available only for geometry type: ${AggregationMapConfig.SUPPORTED_GEOMETRY_TYPES}`;
const invalidAggregationParamValueErrorMessage = ctx =>
`Invalid value for 'aggregation' query param: ${ctx.value}. Valid ones are 'true' or 'false'`;
module.exports = class AggregationMapConfigAdapter {
constructor (pgConnection) {
this.pgConnection = pgConnection;
}
getMapConfig (user, requestMapConfig, params, context, callback) {
if (!this._isValidAggregationQueryParam(params)) {
return callback(new Error(invalidAggregationParamValueErrorMessage({ value: params.aggregation })));
}
let mapConfig;
try {
mapConfig = new AggregationMapConfig(user, requestMapConfig, this.pgConnection);
} catch (err) {
return callback(err);
}
if (!this._shouldAdapt(mapConfig, params)) {
return callback(null, requestMapConfig);
}
this.pgConnection.getConnection(user, (err, connection) => {
if (err) {
return callback(err);
}
this._adaptLayers(connection, mapConfig, requestMapConfig, context, callback);
});
}
_isValidAggregationQueryParam (params) {
const { aggregation } = params;
return aggregation === undefined || aggregation === 'true' || aggregation === 'false';
}
_shouldAdapt (mapConfig, params) {
const { aggregation } = params;
if (aggregation === 'false') {
return false;
}
if (aggregation === 'true' || mapConfig.isAggregationMapConfig()) {
return true;
}
return false;
}
_adaptLayers (connection, mapConfig, requestMapConfig, context, callback) {
const adaptLayerPromises = requestMapConfig.layers.map((layer, index) => {
return this._adaptLayer(connection, mapConfig, layer, index);
});
Promise.all(adaptLayerPromises)
.then(results => {
context.aggregation = {
layers: []
};
results.forEach(({ layer, index, adapted }) => {
if (adapted) {
requestMapConfig.layers[index] = layer;
}
const aggregatedFormats = this._getAggregationMetadata(mapConfig, layer, adapted);
context.aggregation.layers.push(aggregatedFormats);
});
callback(null, requestMapConfig);
})
.catch(err => callback(err));
}
_adaptLayer (connection, mapConfig, layer, index) {
return new Promise((resolve, reject) => {
this._shouldAdaptLayer(connection, mapConfig, layer, index, (err, shouldAdapt) => {
if (err) {
return reject(err);
}
if (!shouldAdapt) {
return resolve({ layer, index, adapted: shouldAdapt });
}
const sqlQueryWrap = layer.options.sql_wrap;
let aggregationSql = mapConfig.getAggregatedQuery(index);
if (sqlQueryWrap) {
aggregationSql = sqlQueryWrap.replace(/<%=\s*sql\s*%>/g, aggregationSql);
}
layer.options.sql = aggregationSql;
mapConfig.getLayerAggregationColumns(index, (err, columns) => {
if (err) {
return reject(err);
}
layer.options.columns = columns;
return resolve({ layer, index, adapted: shouldAdapt });
});
});
});
}
_shouldAdaptLayer (connection, mapConfig, layer, index, callback) {
if (!mapConfig.isAggregationLayer(index)) {
return callback(null, false);
}
const aggregationMetadata = queryUtils.getAggregationMetadata({
query: layer.options.sql_raw ? layer.options.sql_raw : layer.options.sql,
geometryColumn: AggregationMapConfig.getAggregationGeometryColumn()
});
connection.query(aggregationMetadata, (err, res) => {
if (err) {
return callback(null, false);
}
const result = res.rows[0] || {};
if (!mapConfig.isVectorOnlyMapConfig() && !AggregationMapConfig.supportsGeometryType(result.type)) {
const message = unsupportedGeometryTypeErrorMessage({ geometryType: result.type });
const error = new Error(message);
error.type = 'layer';
error.layer = {
id: mapConfig.getLayerId(index),
index: index,
type: mapConfig.layerType(index)
};
return callback(error);
}
if (mapConfig.isVectorOnlyMapConfig() && !AggregationMapConfig.supportsGeometryType(result.type)) {
return callback(null, false);
}
if (!mapConfig.doesLayerReachThreshold(index, result.count)) {
return callback(null, false);
}
callback(null, true);
});
}
_getAggregationMetadata (mapConfig, layer, adapted) {
if (!adapted) {
return { png: false, mvt: false };
}
if (mapConfig.isVectorOnlyMapConfig()) {
return { png: false, mvt: true };
}
return { png: true, mvt: true };
}
};

View File

@@ -58,6 +58,13 @@ AnalysisMapConfigAdapter.prototype.getMapConfig = function(user, requestMapConfi
requestMapConfig = appendFiltersToNodes(requestMapConfig, dataviewsFiltersBySourceId);
// Expected format for analyses filters
// filters = {analyses: {
// a1: [{min, max}, {accept, reject}],
// b1: [{range, column, min, max}, {category, column, accept, reject}]
// }}
requestMapConfig = appendFiltersToNodes(requestMapConfig, filters.analyses);
function createAnalysis(analysisDefinition, done) {
self.analysisBackend.create(analysisConfiguration, analysisDefinition, function (err, analysis) {
if (err) {
@@ -115,6 +122,7 @@ AnalysisMapConfigAdapter.prototype.getMapConfig = function(user, requestMapConfi
}
layer.options.sql = analysisSql;
layer.options.columns = getDataviewsColumns(getLayerDataviews(layer, dataviews));
layer.options.affected_tables = getAllAffectedTablesFromSourceNodes(layerNode);
} else {
missingNodesErrors.push(
new Error('Missing analysis node.id="' + layerSourceId +'" for layer='+layerIndex)
@@ -199,6 +207,7 @@ function dataviewQuery(node, dataviewName, ownFilter) {
function appendFiltersToNodes(requestMapConfig, dataviewsFiltersBySourceId) {
var analyses = requestMapConfig.analyses || [];
dataviewsFiltersBySourceId = dataviewsFiltersBySourceId || {};
requestMapConfig.analyses = analyses.map(function(analysisDefinition) {
var analysisGraph = new camshaft.reference.AnalysisGraph(analysisDefinition);
@@ -330,4 +339,13 @@ function AnalysisError(message) {
this.message = message;
}
function getAllAffectedTablesFromSourceNodes(node) {
var affectedTables = node.getAllInputNodes(function (node) {
return node.getType() === 'source';
}).reduce(function(list, node) {
return list.concat(node.getAffectedTables());
},[]);
return affectedTables;
}
require('util').inherits(AnalysisError, Error);

View File

@@ -0,0 +1,25 @@
function MapConfigBufferSizeAdapter() {
this.formats = ['png', 'png32', 'mvt', 'grid.json'];
}
module.exports = MapConfigBufferSizeAdapter;
MapConfigBufferSizeAdapter.prototype.getMapConfig = function (user, requestMapConfig, params, context, callback) {
if (!context.templateParams || !context.templateParams.buffersize) {
return callback(null, requestMapConfig);
}
this.formats.forEach(function (format) {
if (Number.isFinite(context.templateParams.buffersize[format])) {
if (requestMapConfig.buffersize === undefined) {
requestMapConfig.buffersize = {};
}
requestMapConfig.buffersize[format] = context.templateParams.buffersize[format];
}
});
setImmediate(function () {
callback(null, requestMapConfig);
});
};

View File

@@ -43,7 +43,6 @@ MapConfigNamedLayersAdapter.prototype.getMapConfig = function (user, requestMapC
if (nestedNamedLayers.length > 0) {
var nestedNamedMapsError = new Error('Nested named layers are not allowed');
// nestedNamedMapsError.http_status = 400;
return done(nestedNamedMapsError);
}

View File

@@ -33,7 +33,7 @@ MapConfigOverviewsAdapter.prototype.getMapConfig = function(user, requestMapConf
step(
function collectFiltersData() {
var filters, unfiltered_query;
if ( layer.options.source && analysesResults ) {
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 ) {

View File

@@ -48,6 +48,11 @@ TurboCartoAdapter.prototype.getMapConfig = function (user, requestMapConfig, par
}
requestMapConfig.layers = results.map(function(result) { return result.layer; });
context.turboCarto = {
layers: results.map(function(result) {
return result.meta;
})
};
return callback(null, requestMapConfig);
});
@@ -84,7 +89,7 @@ TurboCartoAdapter.prototype._parseCartoCss = function (username, params, layer,
}
var pg = new PSQL(dbParamsFromReqParams(params));
function processCallback(err, cartocss) {
function processCallback(err, cartocss, meta) {
// Only return turbo-carto errors
if (err && err.name === 'TurboCartoError') {
var error = new Error(err.message);
@@ -105,7 +110,7 @@ TurboCartoAdapter.prototype._parseCartoCss = function (username, params, layer,
if (cartocss) {
layer.options.cartocss = cartocss;
}
return callback(null, { layer: layer });
return callback(null, { layer: layer, meta: meta });
}
var layerSql = layer.options.sql;

View File

@@ -26,7 +26,7 @@ CreateLayergroupMapConfigProvider.prototype.getMapConfig = function(callback) {
var context = {};
step(
function prepareContextLimits() {
self.userLimitsApi.getRenderLimits(self.user, this);
self.userLimitsApi.getRenderLimits(self.user, self.params.api_key, this);
},
function handleRenderLimits(err, renderLimits) {
assert.ifError(err);

View File

@@ -27,7 +27,7 @@ MapStoreMapConfigProvider.prototype.getMapConfig = function(callback) {
var context = {};
step(
function prepareContextLimits() {
self.userLimitsApi.getRenderLimits(self.user, this);
self.userLimitsApi.getRenderLimits(self.user, self.params.api_key, this);
},
function handleRenderLimits(err, renderLimits) {
assert.ifError(err);

View File

@@ -90,6 +90,7 @@ NamedMapMapConfigProvider.prototype.getMapConfig = function(callback) {
},
function instantiateTemplate(err, templateParams) {
assert.ifError(err);
context.templateParams = templateParams;
return self.templateMaps.instance(self.template, templateParams);
},
function prepareAdapterMapConfig(err, requestMapConfig) {
@@ -113,13 +114,14 @@ NamedMapMapConfigProvider.prototype.getMapConfig = function(callback) {
function prepareContextLimits(err, _mapConfig) {
assert.ifError(err);
mapConfig = _mapConfig;
self.userLimitsApi.getRenderLimits(self.owner, this);
self.userLimitsApi.getRenderLimits(self.owner, self.params.api_key, this);
},
function cacheAndReturnMapConfig(err, renderLimits) {
self.err = err;
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

@@ -0,0 +1,258 @@
var dot = require('dot');
dot.templateSettings.strip = false;
function ResourceLocator(environment) {
this.environment = environment;
this.resourcesUrlTemplates = null;
if (this.environment.resources_url_templates) {
var templates = 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}}');
}
}
}
module.exports = ResourceLocator;
ResourceLocator.prototype.getTileUrls = function(username, resourcePath) {
if (this.resourcesUrlTemplates) {
const urls = this.getUrlsFromTemplate(username, new TileResource(resourcePath));
return {
http: Array.isArray(urls.http) ? urls.http : [urls.http],
https: Array.isArray(urls.https) ? urls.https : [urls.https]
};
}
var cdnUrls = getCdnUrls(this.environment.serverMetadata, username, new TileResource(resourcePath));
if (cdnUrls) {
return cdnUrls;
} else {
var port = this.environment.port;
return {
http: [`http://${username}.localhost.lan:${port}/api/v1/map/${resourcePath}`]
};
}
};
ResourceLocator.prototype.getTemplateUrls = function(username, resourcePath) {
if (this.resourcesUrlTemplates) {
return this.getUrlsFromTemplate(username, new TemplateResource(resourcePath), true);
}
var cdnUrls = getCdnUrls(this.environment.serverMetadata, username, new TemplateResource(resourcePath));
if (cdnUrls) {
return cdnUrls;
} else {
var port = this.environment.port;
return {
http: {
urlTemplate: `http://${username}.localhost.lan:${port}/api/v1/map/${resourcePath}`,
subdomains: []
}
};
}
};
ResourceLocator.prototype.getUrls = function(username, resourcePath) {
if (this.resourcesUrlTemplates) {
return this.getUrlsFromTemplate(username, new Resource(resourcePath));
}
var cdnUrls = getCdnUrls(this.environment.serverMetadata, username, new Resource(resourcePath));
if (cdnUrls) {
return cdnUrls;
} else {
var port = this.environment.port;
return {
http: `http://${username}.localhost.lan:${port}/api/v1/map/${resourcePath}`
};
}
};
function urlForTemplate(tpl, username, cdnDomain, resource, templated) {
cdnDomain = cdnDomain || {};
if (templated) {
return {
urlTemplate: tpl({
cdn_url: (cdnDomain.hasOwnProperty('urlTemplate') ? cdnDomain.urlTemplate : cdnDomain),
user: username,
port: this.environment.port,
resource: resource.getPath()
}),
subdomains: cdnDomain.subdomains || []
};
}
if (Array.isArray(cdnDomain)) {
return cdnDomain.map(d => tpl({
cdn_url: d,
user: username,
port: this.environment.port,
resource: resource.getPath()
}));
} else {
return tpl({
cdn_url: cdnDomain,
user: username,
port: this.environment.port,
resource: resource.getPath()
});
}
}
ResourceLocator.prototype.getUrlsFromTemplate = function(username, resource, templated) {
var urls = {};
var cdnDomain = getCdnDomain(this.environment.serverMetadata, resource) || {};
if (this.resourcesUrlTemplates.http) {
urls.http = urlForTemplate(this.resourcesUrlTemplates.http, username, cdnDomain.http, resource, templated);
}
if (this.resourcesUrlTemplates.https) {
urls.https = urlForTemplate(this.resourcesUrlTemplates.https, username, cdnDomain.https, resource, templated);
}
return urls;
};
class Resource {
constructor (resourcePath) {
this.resourcePath = resourcePath;
}
getPath () {
return this.resourcePath;
}
getDomain (domain, subdomains) {
if (!subdomains) {
return domain;
}
return domain.replace('{s}', subdomain(subdomains, this.resourcePath));
}
getUrl (baseUrl, username, subdomains) {
let urls = getUrl(baseUrl, username, this.resourcePath);
if (subdomains) {
urls = urls.replace('{s}', subdomain(subdomains, this.resourcePath));
}
return urls;
}
}
class TileResource extends Resource {
constructor (resourcePath) {
super(resourcePath);
}
getDomain (domain, subdomains) {
if (!subdomains) {
return domain;
}
return subdomains.map(s => domain.replace('{s}', s));
}
getUrl (baseUrl, username, subdomains) {
if (!subdomains) {
return [super.getUrl(baseUrl, username)];
}
return subdomains.map(subdomain => {
return getUrl(baseUrl, username, this.resourcePath)
.replace('{s}', subdomain);
});
}
}
class TemplateResource extends Resource {
constructor (resourcePath) {
super(resourcePath);
}
getDomain (domain, subdomains) {
return {
urlTemplate: domain,
subdomains: subdomains || []
};
}
getUrl (baseUrl, username, subdomains) {
return {
urlTemplate: getUrl(baseUrl, username, this.resourcePath),
subdomains: subdomains || []
};
}
}
function getUrl(baseUrl, username, path) {
return `${baseUrl}/${username}/api/v1/map/${path}`;
}
function getCdnUrls(serverMetadata, username, resource) {
if (serverMetadata && serverMetadata.cdn_url) {
var cdnUrl = serverMetadata.cdn_url;
var httpUrls = resource.getUrl(`http://${cdnUrl.http}`, username);
var httpsUrls = resource.getUrl(`https://${cdnUrl.https}`, username);
if (cdnUrl.templates) {
var templates = cdnUrl.templates;
httpUrls = resource.getUrl(templates.http.url, username, templates.http.subdomains);
httpsUrls = resource.getUrl(templates.https.url, username, templates.https.subdomains);
}
return {
http: httpUrls,
https: httpsUrls,
};
}
return null;
}
function getCdnDomain(serverMetadata, resource) {
if (serverMetadata && serverMetadata.cdn_url) {
var cdnUrl = serverMetadata.cdn_url;
var httpDomain = resource.getDomain(cdnUrl.http);
var httpsDomain = resource.getDomain(cdnUrl.https);
if (cdnUrl.templates) {
var templates = cdnUrl.templates;
var httpUrlTemplate = templates.http.url;
var httpsUrlTemplate = templates.https.url;
httpDomain = httpUrlTemplate.replace(/^(http[s]*:\/\/)/, '');
httpDomain = resource.getDomain(httpDomain, templates.http.subdomains);
httpsDomain = httpsUrlTemplate.replace(/^(http[s]*:\/\/)/, '');
httpsDomain = resource.getDomain(httpsDomain, templates.https.subdomains);
}
return {
http: httpDomain,
https: httpsDomain,
};
}
return null;
}
// ref https://jsperf.com/js-crc32
function crcTable() {
var c;
var table = [];
for (var n = 0; n < 256; n++) {
c = n;
for (var k = 0; k < 8; k++) {
c = ((c & 1) ? (0xEDB88320 ^ (c >>> 1)) : (c >>> 1));
}
table[n] = c;
}
return table;
}
var CRC_TABLE = crcTable();
function crc32(str) {
var crc = 0 ^ (-1);
for (var i = 0; i < str.length; i++) {
crc = (crc >>> 8) ^ CRC_TABLE[(crc ^ str.charCodeAt(i)) & 0xFF];
}
return (crc ^ (-1)) >>> 0;
}
function subdomain(subdomains, resource) {
var index = crc32(resource) % subdomains.length;
return subdomains[index];
}
module.exports.subdomain = subdomain;

View File

@@ -12,7 +12,8 @@ var VarnishHttpCacheBackend = require('./cache/backend/varnish_http');
var FastlyCacheBackend = require('./cache/backend/fastly');
var StatsClient = require('./stats/client');
var Profiler = require('./stats/profiler_proxy');
const stats = require('./middleware/stats');
var RendererStatsReporter = require('./stats/reporter/renderer');
var windshaft = require('windshaft');
@@ -35,12 +36,21 @@ var timeoutErrorTile = require('fs').readFileSync(timeoutErrorTilePath, {encodin
var SqlWrapMapConfigAdapter = require('./models/mapconfig/adapter/sql-wrap-mapconfig-adapter');
var MapConfigNamedLayersAdapter = require('./models/mapconfig/adapter/mapconfig-named-layers-adapter');
var MapConfigBufferSizeAdapter = require('./models/mapconfig/adapter/mapconfig-buffer-size-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 AggregationMapConfigAdapter = require('./models/mapconfig/adapter/aggregation-mapconfig-adapter');
var MapConfigAdapter = require('./models/mapconfig/adapter');
var StatsBackend = require('./backends/stats');
const lzmaMiddleware = require('./middleware/lzma');
const errorMiddleware = require('./middleware/error-middleware');
const prepareContextMiddleware = require('./middleware/context');
module.exports = function(serverOptions) {
// Make stats client globally accessible
global.statsClient = StatsClient.getInstance(serverOptions.statsd);
@@ -115,8 +125,27 @@ module.exports = function(serverOptions) {
var onTileErrorStrategy;
if (global.environment.enabledFeatures.onTileErrorStrategy !== false) {
onTileErrorStrategy = function onTileErrorStrategy$TimeoutTile(err, tile, headers, stats, format, callback) {
if (err && err.message === 'Render timed out' && format === 'png') {
return callback(null, timeoutErrorTile, { 'Content-Type': 'image/png' }, {});
function isRenderTimeoutError (err) {
return err.message === 'Render timed out';
}
function isDatasourceTimeoutError (err) {
return err.message && err.message.match(/canceling statement due to statement timeout/i);
}
function isTimeoutError (err) {
return isRenderTimeoutError(err) || isDatasourceTimeoutError(err);
}
function isRasterFormat (format) {
return format === 'png' || format === 'jpg';
}
if (isTimeoutError(err) && isRasterFormat(format)) {
return callback(null, timeoutErrorTile, {
'Content-Type': 'image/png',
}, {});
} else {
return callback(err, tile, headers, stats);
}
@@ -130,7 +159,8 @@ module.exports = function(serverOptions) {
grainstore: serverOptions.grainstore,
mapnik: serverOptions.renderer.mapnik
},
http: serverOptions.renderer.http
http: serverOptions.renderer.http,
mvt: serverOptions.renderer.mvt
});
// initialize render cache
@@ -147,16 +177,21 @@ 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 statsBackend = new StatsBackend();
var layergroupAffectedTablesCache = new LayergroupAffectedTablesCache();
app.layergroupAffectedTablesCache = layergroupAffectedTablesCache;
var mapConfigAdapter = new MapConfigAdapter(
new MapConfigNamedLayersAdapter(templateMaps, pgConnection),
new MapConfigBufferSizeAdapter(),
new SqlWrapMapConfigAdapter(),
new DataviewsWidgetsAdapter(),
new AnalysisMapConfigAdapter(analysisBackend),
new AggregationMapConfigAdapter(pgConnection),
new MapConfigOverviewsAdapter(overviewsMetadataApi, filterStatsApi),
new TurboCartoAdapter()
);
@@ -180,12 +215,16 @@ module.exports = function(serverOptions) {
var versions = getAndValidateVersions(serverOptions);
const prepareContext = typeof serverOptions.req2params === 'function' ?
serverOptions.req2params :
prepareContextMiddleware(authApi, pgConnection);
/*******************************************************************************************************************
* Routing
******************************************************************************************************************/
new controller.Layergroup(
authApi,
prepareContext,
pgConnection,
mapStore,
tileBackend,
@@ -198,7 +237,7 @@ module.exports = function(serverOptions) {
).register(app);
new controller.Map(
authApi,
prepareContext,
pgConnection,
templateMaps,
mapBackend,
@@ -206,12 +245,12 @@ module.exports = function(serverOptions) {
surrogateKeysCache,
userLimitsApi,
layergroupAffectedTablesCache,
mapConfigAdapter
mapConfigAdapter,
statsBackend
).register(app);
new controller.NamedMaps(
authApi,
pgConnection,
prepareContext,
namedMapProviderCache,
tileBackend,
previewBackend,
@@ -220,9 +259,9 @@ module.exports = function(serverOptions) {
metadataBackend
).register(app);
new controller.NamedMapsAdmin(authApi, pgConnection, templateMaps).register(app);
new controller.NamedMapsAdmin(authApi, templateMaps).register(app);
new controller.Analyses(authApi, pgConnection).register(app);
new controller.Analyses(prepareContext).register(app);
new controller.ServerInfo(versions).register(app);
@@ -230,6 +269,8 @@ module.exports = function(serverOptions) {
* END Routing
******************************************************************************************************************/
app.use(errorMiddleware());
return app;
};
@@ -302,15 +343,28 @@ function bootstrap(opts) {
app.enable('jsonp callback');
app.disable('x-powered-by');
app.disable('etag');
// Fix: https://github.com/CartoDB/Windshaft-cartodb/issues/705
// See: http://expressjs.com/en/4x/api.html#app.set
app.set('json replacer', function (key, value) {
if (value !== value) {
return 'NaN';
}
if (value === Infinity) {
return 'Infinity';
}
if (value === -Infinity) {
return '-Infinity';
}
return value;
});
app.use(bodyParser.json());
app.use(function bootstrap$prepareRequestResponse(req, res, next) {
req.context = req.context || {};
req.profiler = new Profiler({
statsd_client: global.statsClient,
profile: opts.useProfiler
});
if (global.environment && global.environment.api_hostname) {
res.set('X-Served-By-Host', global.environment.api_hostname);
}
@@ -318,6 +372,13 @@ function bootstrap(opts) {
next();
});
app.use(stats({
enabled: opts.useProfiler,
statsClient: global.statsClient
}));
app.use(lzmaMiddleware);
// temporary measure until we upgrade to newer version expressjs so we can check err.status
app.use(function(err, req, res, next) {
if (err) {

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
@@ -76,6 +81,7 @@ module.exports = {
statsInterval: rendererConfig.statsInterval
},
renderer: {
mvt: rendererConfig.mvt,
mapnik: _.defaults(rendererConfig.mapnik, {
geojson: {
dbPoolParams: {
@@ -95,7 +101,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

@@ -0,0 +1,19 @@
'use strict';
const glob = require('glob');
const path = require('path');
// See https://github.com/CartoDB/support/issues/984
// CartoCSS properties text-wrap-width/text-wrap-character not working
function setICUEnvVariable() {
if (process.env.ICU_DATA === undefined) {
const regexedPath = '/node_modules/mapnik/lib/binding/*/share/mapnik/icu/';
const directory = glob.sync(path.join(__dirname, '../../..', regexedPath));
if (directory && directory.length > 0) {
process.env.ICU_DATA = directory[0];
}
}
}
module.exports = setICUEnvVariable;

View File

@@ -0,0 +1,73 @@
function prepareQuery(sql) {
var affectedTableRegexCache = {
bbox: /!bbox!/g,
scale_denominator: /!scale_denominator!/g,
pixel_width: /!pixel_width!/g,
pixel_height: /!pixel_height!/g
};
return sql
.replace(affectedTableRegexCache.bbox, 'ST_MakeEnvelope(0,0,0,0)')
.replace(affectedTableRegexCache.scale_denominator, '0')
.replace(affectedTableRegexCache.pixel_width, '1')
.replace(affectedTableRegexCache.pixel_height, '1');
}
module.exports.extractTableNames = function extractTableNames(query) {
return [
'SELECT * FROM CDB_QueryTablesText($windshaft$',
prepareQuery(query),
'$windshaft$) as tablenames'
].join('');
};
function getQueryRowEstimation(query) {
return 'select CDB_EstimateRowCount($windshaft$' + query + '$windshaft$) as rows';
}
module.exports.getQueryRowCount = getQueryRowEstimation;
module.exports.getAggregationMetadata = ctx => `
WITH
rowEstimation AS (
${getQueryRowEstimation(ctx.query)}
),
geometryType AS (
SELECT ST_GeometryType(${ctx.geometryColumn}) as geom_type
FROM (${ctx.query}) AS __cdb_query WHERE ${ctx.geometryColumn} IS NOT NULL LIMIT 1
)
SELECT
rows AS count,
geom_type AS type
FROM rowEstimation, geometryType;
`;
/** Cast the column to epoch */
module.exports.columnCastTpl = function columnCastTpl(ctx) {
return `date_part('epoch', ${ctx.column})`;
};
/** If the column type is float, ignore any non numeric result (infinity / NaN) */
module.exports.handleFloatColumn = function handleFloatColumn(ctx) {
return `${!ctx.isFloatColumn ? `${ctx.column}` :
`nullif(nullif(nullif(${ctx.column}, 'infinity'::float), '-infinity'::float), 'NaN'::float)`
}`;
};
/** Count NULL appearances */
module.exports.countNULLs= function countNULLs(ctx) {
return `sum(CASE WHEN (${ctx.column} IS NULL) THEN 1 ELSE 0 END)`;
};
/** Count only infinity (positive and negative) appearances */
module.exports.countInfinites = function countInfinites(ctx) {
return `${!ctx.isFloatColumn ? `0` :
`sum(CASE WHEN (${ctx.column} = 'infinity'::float OR ${ctx.column} = '-infinity'::float) THEN 1 ELSE 0 END)`
}`;
};
/** Count only NaNs appearances*/
module.exports.countNaNs = function countNaNs(ctx) {
return `${!ctx.isFloatColumn ? `0` :
`sum(CASE WHEN (${ctx.column} = 'NaN'::float) THEN 1 ELSE 0 END)`
}`;
};

4013
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.69.0",
"version": "5.2.0",
"description": "A map tile server for CartoDB",
"keywords": [
"cartodb"
@@ -16,46 +16,61 @@
"contributors": [
"Simon Tokumine <simon@vizzuality.com>",
"Javi Santana <jsantana@vizzuality.com>",
"Sandro Santilli <strk@vizzuality.com>"
"Sandro Santilli <strk@vizzuality.com>",
"Carlos Matallín <matallo@carto.com>",
"Daniel Garcia Aubert <dgaubert@carto.com>",
"Mario de Frutos <mario.defrutos@carto.com>",
"Ivan Malagon <ivan@carto.com>",
"Simon Martin <simon@carto.com>"
],
"dependencies": {
"body-parser": "~1.14.0",
"camshaft": "0.39.0",
"cartodb-psql": "~0.6.1",
"cartodb-query-tables": "~0.1.0",
"cartodb-redis": "0.13.1",
"debug": "~2.2.0",
"body-parser": "^1.18.2",
"camshaft": "0.60.0",
"cartodb-psql": "0.10.2",
"cartodb-query-tables": "0.3.0",
"cartodb-redis": "0.14.0",
"debug": "^3.1.0",
"dot": "~1.0.2",
"express": "~4.13.3",
"express": "~4.16.0",
"fastly-purge": "~1.0.1",
"glob": "^7.1.2",
"log4js": "cartodb/log4js-node#cdb",
"lru-cache": "2.6.5",
"lzma": "~2.3.2",
"node-statsd": "~0.0.7",
"on-headers": "^1.0.1",
"queue-async": "~1.0.7",
"redis-mpool": "~0.4.0",
"request": "~2.62.0",
"redis-mpool": "0.4.1",
"request": "^2.83.0",
"semver": "~5.3.0",
"step": "~0.0.6",
"step-profiler": "~0.3.0",
"turbo-carto": "0.16.0",
"turbo-carto": "0.20.2",
"underscore": "~1.6.0",
"windshaft": "2.4.0"
"windshaft": "4.3.3",
"yargs": "~5.0.0"
},
"devDependencies": {
"istanbul": "~0.4.3",
"jshint": "~2.6.0",
"mocha": "~1.21.4",
"jshint": "~2.9.4",
"mocha": "~3.4.1",
"moment": "~2.18.1",
"nock": "~2.11.0",
"redis": "~0.8.6",
"semver": "~1.1.4",
"redis": "~0.12.1",
"strftime": "~0.8.2"
},
"scripts": {
"lint": "jshint lib test",
"preinstall": "make pre-install",
"test": "make test-all"
"test": "make test-all",
"update-internal-deps": "rm -rf node_modules && rm -f yarn.lock && yarn",
"docker-install": "sudo apt install docker.io && sudo usermod -aG docker $(whoami)",
"docker-pull": "docker pull cartoimages/windshaft-testing",
"docker-test": "docker run -v `pwd`:/srv cartoimages/windshaft-testing bash docker-test.sh && docker ps --filter status=dead --filter status=exited -aq | xargs -r docker rm -v",
"docker-bash": "docker run -it -v `pwd`:/srv cartoimages/windshaft-testing bash"
},
"engines": {
"node": ">=0.8 <0.11",
"npm": ">=2.14.16"
"node": ">=6.9",
"yarn": ">=0.27.5 <1.0.0"
}
}

View File

@@ -137,7 +137,7 @@ if test x"$OPT_COVERAGE" = xyes; then
./node_modules/.bin/istanbul cover node_modules/.bin/_mocha -- -u tdd -t 5000 ${TESTS}
else
echo "Running tests"
mocha -u tdd -t 5000 ${TESTS}
./node_modules/.bin/_mocha -c -u tdd -t 5000 ${TESTS}
fi
ret=$?

View File

@@ -4,4 +4,4 @@ if [[ "$OSTYPE" == "darwin"* ]]; then
export PKG_CONFIG_PATH=/usr/local/lib/pkgconfig:/opt/X11/lib/pkgconfig
fi
npm install
yarn

37
scripts/mvt-timeout-error.py Executable file
View File

@@ -0,0 +1,37 @@
#!/usr/bin/env python
import mapbox_vector_tile
lines_list = []
# main diagonal line
lines_list.append({ "geometry":"LINESTRING (0 0, 4096 4096)"})
# diagonal lines
for i in range(4096/32, 4096, 4096/32):
start = i
end = 4096 - i
lines_list.append({ "geometry":"LINESTRING (0 " + str(start) + ", " + str(end) + " 4096)" })
lines_list.append({ "geometry":"LINESTRING (" + str(start) + " 0, 4096 " + str(end) + ")" })
# box lines
lines_list.append({ "geometry":"LINESTRING (0 0, 0 4096)"})
lines_list.append({ "geometry":"LINESTRING (0 4096, 4096 4096)"})
lines_list.append({ "geometry":"LINESTRING (4096 4096, 4096 0)"})
lines_list.append({ "geometry":"LINESTRING (4096 0, 0 0)"})
tile = mapbox_vector_tile.encode([
{
"name": "errorTileSquareLayer",
"features": [{ "geometry":"POLYGON ((0 0, 0 4096, 4096 4096, 4096 0, 0 0))" }]
},
{
"name": "errorTileStripesLayer",
"features": lines_list
}
])
with open('./assets/render-timeout-fallback.mvt', 'w+') as f:
f.write(tile)

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,114 @@
require('../../support/test_helper');
var assert = require('../../support/assert');
var TestClient = require('../../support/test-client');
describe('analyses controller', function () {
const mapConfig = {
version: '1.5.0',
layers:
[{
type: 'cartodb',
options:
{
source: { id: 'a1' },
cartocss: TestClient.CARTOCSS.POLYGONS,
cartocss_version: '2.3.0'
}
}],
dataviews: {},
analyses:
[{
id: 'a1',
type: 'buffer',
params: {
source: {
type: 'source',
params: {
query: 'select * from analysis_banks limit 1'
}
},
radius: 250
}
}]
};
beforeEach(function () {
this.testClient = new TestClient(mapConfig, 1234);
});
it('should get an array of analyses from catalog', function (done) {
this.testClient.getAnalysesCatalog({}, (err, result) => {
if (err) {
return done(err);
}
assert.ok(Array.isArray(result.catalog));
done();
});
});
it('should support jsonp responses', function (done) {
this.testClient.getAnalysesCatalog({ jsonp: 'jsonp_test' }, (err, result) => {
if (err) {
return done(err);
}
assert.ok(result);
let didRunJsonCallback = false;
// jshint ignore:start
function jsonp_test(body) {
assert.ok(Array.isArray(body.catalog));
didRunJsonCallback = true;
}
eval(result);
// jshint ignore:end
assert.ok(didRunJsonCallback);
done();
});
});
it('should respond "unauthorized" when missing api_key', function (done) {
const apiKey = this.testClient.apiKey;
this.testClient.apiKey = null;
this.testClient.getAnalysesCatalog({ status: 401 }, (err, result) => {
if (err) {
return done(err);
}
assert.deepEqual(result.errors[0], 'Unauthorized');
this.testClient.apiKey = apiKey;
done();
});
});
it('should get an array of analyses from catalog', function (done) {
this.testClient.getTile(0, 0, 0, (err) => {
if (err) {
return done(err);
}
this.testClient.getAnalysesCatalog({}, (err, result) => {
if (err) {
return done(err);
}
assert.ok(Array.isArray(result.catalog));
assert.ok(result.catalog.length >= 2); // buffer & source at least
result.catalog
.filter(analysis => analysis.node_id === '0a215e1f3405381cf0ea6b3b0deb6fdcfdc2fcaa')
.forEach(analysis => assert.equal(analysis.type, 'buffer'));
this.testClient.drain(done);
});
});
});
});

View File

@@ -0,0 +1,150 @@
require('../../support/test_helper');
const assert = require('../../support/assert');
const TestClient = require('../../support/test-client');
describe('analysis-filters-params', () => {
const CARTOCSS = `#layer {
marker-fill-opacity: 1;
marker-line-color: white;
marker-line-width: 0.5;
marker-line-opacity: 1;
marker-placement: point;
marker-type: ellipse;
marker-width: 8;
marker-fill: red;
marker-allow-overlap: true;
}`;
const mapConfig = {
version: '1.6.0',
layers: [
{
"type": "cartodb",
"options": {
"source": {
"id": "a1"
},
"cartocss": CARTOCSS,
"cartocss_version": "2.3.0"
}
}
],
dataviews: {
pop_max_histogram: {
source: {
id: 'a1'
},
type: 'histogram',
options: {
column: 'pop_max'
}
},
pop_min_histogram: {
source: {
id: 'a1'
},
type: 'histogram',
options: {
column: 'pop_min'
}
}
},
analyses: [
{
"id": "a1",
"type": "source",
"params": {
"query": "select * from populated_places_simple_reduced"
}
}
]
};
var params = {
filters: {
dataviews: {
pop_max_histogram: {
min: 2e6
},
pop_min_histogram: {
max: 2e6
}
}
}
};
it('should get a filtered histogram dataview with all filters', function(done) {
const testClient = new TestClient(mapConfig, 1234);
const testParams = Object.assign({}, params, {
own_filter: 1
});
testClient.getDataview('pop_max_histogram', testParams, (err, dataview) => {
assert.ok(!err, err);
assert.equal(dataview.type, 'histogram');
assert.equal(dataview.bins_count, 6);
testClient.drain(done);
});
});
it('should get a filtered histogram dataview with all filters except my own filter', function(done) {
const testClient = new TestClient(mapConfig, 1234);
const testParams = Object.assign({}, params, {
own_filter: 0
});
testClient.getDataview('pop_max_histogram', testParams, (err, dataview) => {
assert.ok(!err, err);
assert.equal(dataview.type, 'histogram');
assert.equal(dataview.bins_count, 24);
testClient.drain(done);
});
});
it('should get a filtered histogram dataview without filters', function(done) {
const testClient = new TestClient(mapConfig, 1234);
const testParams = Object.assign({}, params, {
no_filters: 1
});
testClient.getDataview('pop_max_histogram', testParams, (err, dataview) => {
assert.ok(!err, err);
assert.equal(dataview.type, 'histogram');
assert.equal(dataview.bins_count, 48);
testClient.drain(done);
});
});
it('should return an error if both no_filters and own_filter params are present', function (done) {
const testClient = new TestClient(mapConfig, 1234);
const expectedError = {
errors: ['Both own_filter and no_filters cannot be sent in the same request'],
errors_with_context: [{
type: 'dataview',
message: 'Both own_filter and no_filters cannot be sent in the same request'
}]
};
const testParams = Object.assign({}, params, {
no_filters: 1,
own_filter: 0,
response: {
status: 400
}
});
testClient.getDataview('pop_max_histogram', testParams, (err, dataview) => {
assert.deepEqual(dataview, expectedError);
testClient.drain(done);
});
});
});

View File

@@ -0,0 +1,84 @@
require('../../support/test_helper');
const assert = require('../../support/assert');
const TestClient = require('../../support/test-client');
describe('analysis-layers-dataviews', () => {
const CARTOCSS = `#layer {
marker-fill-opacity: 1;
marker-line-color: white;
marker-line-width: 0.5;
marker-line-opacity: 1;
marker-placement: point;
marker-type: ellipse;
marker-width: 8;
marker-fill: red;
marker-allow-overlap: true;
}`;
const mapConfig = {
version: '1.6.0',
layers: [
{
"type": "cartodb",
"options": {
"source": {
"id": "a1"
},
"cartocss": CARTOCSS,
"cartocss_version": "2.3.0"
}
}
],
dataviews: {
pop_max_histogram: {
source: {
id: 'a1'
},
type: 'histogram',
options: {
column: 'pop_max'
}
}
},
analyses: [
{
"id": "a1",
"type": "source",
"params": {
"query": "select * from populated_places_simple_reduced"
}
}
]
};
it('should get a filtered histogram dataview', function(done) {
const testClient = new TestClient(mapConfig, 1234);
const params = {
filters: {
analyses: {
'a1': [
{
type: 'range',
column: 'pop_max',
params: {
min: 2e6
}
}
]
}
}
};
testClient.getDataview('pop_max_histogram', params, (err, dataview) => {
assert.ok(!err, err);
assert.equal(dataview.type, 'histogram');
assert.equal(dataview.bins_start, 2008000);
testClient.drain(done);
});
});
});

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