Compare commits
219 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ac499eb0cd | ||
|
|
8071967ff1 | ||
|
|
d47f1762da | ||
|
|
75fe4c8aed | ||
|
|
12e272a7e5 | ||
|
|
cfcba4e578 | ||
|
|
37ab898426 | ||
|
|
68865ea929 | ||
|
|
86674faa22 | ||
|
|
f07947ce45 | ||
|
|
6a50f59e25 | ||
|
|
bfacd56800 | ||
|
|
45dea8b0c1 | ||
|
|
2f7f8cf2d8 | ||
|
|
31611b6a28 | ||
|
|
d1cd4b0c2b | ||
|
|
c8ba1c3e7c | ||
|
|
fbc8fe4c2d | ||
|
|
54ec9b48db | ||
|
|
488698d5e2 | ||
|
|
58c407aabb | ||
|
|
fe750f23bc | ||
|
|
87a01a5cfd | ||
|
|
74dd669bb0 | ||
|
|
36a50389f5 | ||
|
|
4f2d7434c7 | ||
|
|
b0a0848476 | ||
|
|
9fcd897e54 | ||
|
|
daa8fff21e | ||
|
|
785229ddea | ||
|
|
8bb11bf1d4 | ||
|
|
1f975e15c1 | ||
|
|
6c69ba54db | ||
|
|
49f9904d00 | ||
|
|
7afd0dfa4e | ||
|
|
b1b6a437a7 | ||
|
|
e4d5006591 | ||
|
|
627b3771d3 | ||
|
|
f4758e84e8 | ||
|
|
8dfe2098ed | ||
|
|
c56a4ee036 | ||
|
|
c32623b821 | ||
|
|
3cd0a947f7 | ||
|
|
8eea1cf4e7 | ||
|
|
b5fccd5bbe | ||
|
|
e74ce9dfd8 | ||
|
|
3743365a83 | ||
|
|
8aeb2173d1 | ||
|
|
9a2b17d952 | ||
|
|
6f54cce01a | ||
|
|
6901b2049e | ||
|
|
d0dcc027df | ||
|
|
b693005118 | ||
|
|
ab4a0e836f | ||
|
|
abe02db6c6 | ||
|
|
a2cd5dd32d | ||
|
|
49b46a6096 | ||
|
|
94f420ca3f | ||
|
|
5e530105df | ||
|
|
2f82d34c4b | ||
|
|
81fd01d0ac | ||
|
|
9faac9f9fe | ||
|
|
d04787a60c | ||
|
|
f5dbf94b52 | ||
|
|
5bec2d9b15 | ||
|
|
fe64f0c63c | ||
|
|
c20fd9691a | ||
|
|
eb323fbff9 | ||
|
|
211f6b9a74 | ||
|
|
b6c003ec63 | ||
|
|
93d4bf2a72 | ||
|
|
c6cb573383 | ||
|
|
f4ce671ea4 | ||
|
|
147f7cbabb | ||
|
|
b05d5a141e | ||
|
|
d34e0306f8 | ||
|
|
bd9f48dd24 | ||
|
|
9805990d79 | ||
|
|
dbbe60967c | ||
|
|
0ef91c1904 | ||
|
|
376573459c | ||
|
|
9c6d7c0ff9 | ||
|
|
30a95b7da3 | ||
|
|
e6a60aef9a | ||
|
|
5c2024581f | ||
|
|
f7ea2bb51e | ||
|
|
3e4da8ab57 | ||
|
|
7352a28908 | ||
|
|
d1928ee578 | ||
|
|
cd978d7384 | ||
|
|
cde0d8f5e2 | ||
|
|
7bacfcc2e4 | ||
|
|
241fe36103 | ||
|
|
441714a656 | ||
|
|
bd3fdb7f16 | ||
|
|
775af6feee | ||
|
|
adf5c17e0d | ||
|
|
beb2d96a32 | ||
|
|
2a4ae88bc0 | ||
|
|
b76098ba45 | ||
|
|
c095027f8e | ||
|
|
9d1db19907 | ||
|
|
260e321537 | ||
|
|
073603b527 | ||
|
|
17b259cf31 | ||
|
|
8f0f0026e9 | ||
|
|
59dae2b545 | ||
|
|
4670f69ead | ||
|
|
16fbd25a34 | ||
|
|
2d75985cb3 | ||
|
|
f963fb321e | ||
|
|
10feea0d48 | ||
|
|
b6b9b0ac36 | ||
|
|
5551e85853 | ||
|
|
1f0fa5031b | ||
|
|
263294a3f5 | ||
|
|
f9df30f70b | ||
|
|
61d31ec054 | ||
|
|
c8917bfc4c | ||
|
|
36b69a05e5 | ||
|
|
c8d2f66467 | ||
|
|
7416bb0e56 | ||
|
|
9182d0132d | ||
|
|
9be9357ade | ||
|
|
7f414f8adf | ||
|
|
3af9549939 | ||
|
|
41f248d731 | ||
|
|
18a517b7bf | ||
|
|
5150204389 | ||
|
|
3b16e7729d | ||
|
|
76d27c9fce | ||
|
|
4ce6e41000 | ||
|
|
33260cdbd9 | ||
|
|
85d81ba7fd | ||
|
|
7e8a3ca21f | ||
|
|
e9e4dc1f5c | ||
|
|
17c30e165a | ||
|
|
c45c6ceb15 | ||
|
|
d73c2c465f | ||
|
|
d4fc53939b | ||
|
|
4becb65bec | ||
|
|
f64e16c790 | ||
|
|
1772011627 | ||
|
|
5b8f785e2b | ||
|
|
a0e3b77006 | ||
|
|
908070ecd7 | ||
|
|
7c6a58cd30 | ||
|
|
b0990a1132 | ||
|
|
c6988cdb88 | ||
|
|
e0d304b033 | ||
|
|
e4a9f2d64c | ||
|
|
0236fe3ca9 | ||
|
|
1bed8623a2 | ||
|
|
df7d957914 | ||
|
|
30c4b00f33 | ||
|
|
ab27886460 | ||
|
|
31e18d04d7 | ||
|
|
8155484510 | ||
|
|
b61f1d2b53 | ||
|
|
2e274b936a | ||
|
|
bf3e311b57 | ||
|
|
6a7613de6b | ||
|
|
ee46549e04 | ||
|
|
377f3d4aff | ||
|
|
752d47d71e | ||
|
|
367157b80c | ||
|
|
53542f1cd6 | ||
|
|
7a8f156abf | ||
|
|
c60cc57a0d | ||
|
|
8de6ec9a21 | ||
|
|
44b6f4be7e | ||
|
|
280be1751c | ||
|
|
701a73a2c5 | ||
|
|
b578eada07 | ||
|
|
8100f155dc | ||
|
|
9f1a014004 | ||
|
|
e35e0e157c | ||
|
|
3aff328af3 | ||
|
|
ffb086045a | ||
|
|
c0786dfa6f | ||
|
|
ddc33fa52b | ||
|
|
9f2d6a5d41 | ||
|
|
64e884a092 | ||
|
|
17ec174683 | ||
|
|
3666cbee94 | ||
|
|
25de018f7d | ||
|
|
6597851b48 | ||
|
|
0399131968 | ||
|
|
86836e7f89 | ||
|
|
df346b11d3 | ||
|
|
27f74b3fe2 | ||
|
|
87dec64ad1 | ||
|
|
54c787162a | ||
|
|
6e92e699dc | ||
|
|
7950f43db3 | ||
|
|
d300677315 | ||
|
|
bd4d29dd14 | ||
|
|
18a84433f4 | ||
|
|
768ebf0ef2 | ||
|
|
1c20cb5478 | ||
|
|
279587ea11 | ||
|
|
25df193390 | ||
|
|
9ce81693bd | ||
|
|
16765e092f | ||
|
|
237e1257c4 | ||
|
|
665859b17d | ||
|
|
b5a6d6974c | ||
|
|
26bab029f4 | ||
|
|
ed7bb07b03 | ||
|
|
c87277ad01 | ||
|
|
62be259a90 | ||
|
|
80798f984b | ||
|
|
e32409880c | ||
|
|
7b6eb2940e | ||
|
|
87ad8df22f | ||
|
|
9fe20036a1 | ||
|
|
2b0d8d43bd | ||
|
|
3af340d384 | ||
|
|
20c1ad8d87 |
1
.gitignore
vendored
@@ -2,6 +2,7 @@ node_modules*
|
||||
config.status*
|
||||
config/environments/*.js
|
||||
.idea
|
||||
.vscode
|
||||
tools/munin/windshaft.conf
|
||||
logs/
|
||||
pids/
|
||||
|
||||
20
.travis.yml
@@ -1,24 +1,20 @@
|
||||
dist: trusty # only environment that supports Postgres 9.5 at this time
|
||||
sudo: required
|
||||
addons:
|
||||
postgresql: "9.5"
|
||||
apt:
|
||||
packages:
|
||||
- postgresql-9.5-postgis-2.3
|
||||
- postgresql-contrib-9.5
|
||||
- postgresql-plpython-9.5
|
||||
- postgis
|
||||
- pkg-config
|
||||
- libcairo2-dev
|
||||
- libjpeg8-dev
|
||||
- libgif-dev
|
||||
- libpango1.0-dev
|
||||
|
||||
before_install:
|
||||
- npm install -g npm@2
|
||||
- lsb_release -a
|
||||
- sudo mv /etc/apt/sources.list.d/pgdg-source.list* /tmp
|
||||
- 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 postgresql-9.5-postgis-2.2
|
||||
- sudo apt-get install -q 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
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
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
|
||||
|
||||
315
NEWS.md
@@ -1,5 +1,320 @@
|
||||
# Changelog
|
||||
|
||||
## 2.86.2
|
||||
Released 2017-09-01
|
||||
|
||||
Bug fixes:
|
||||
- Adding a new cartodb-psql fixing a pg vulnerability
|
||||
|
||||
|
||||
## 2.86.1
|
||||
Released 2016-12-02
|
||||
|
||||
Bug fixes:
|
||||
- Maps with analyses and `sql_wrap` were broken #599.
|
||||
|
||||
|
||||
## 2.86.0
|
||||
Released 2016-12-02
|
||||
|
||||
Announcements:
|
||||
- Upgrades windshaft to [2.6.3](https://github.com/CartoDB/Windshaft/releases/tag/2.6.3).
|
||||
|
||||
|
||||
## 2.85.1
|
||||
Released 2016-11-30
|
||||
|
||||
Announcements:
|
||||
- Upgrades camshaft to [0.48.4](https://github.com/CartoDB/camshaft/releases/tag/0.48.4).
|
||||
|
||||
|
||||
## 2.85.0
|
||||
Released 2016-11-24
|
||||
|
||||
New features:
|
||||
- Allow to set resource URL templates with substitution tokens #594.
|
||||
|
||||
|
||||
## 2.84.2
|
||||
Released 2016-11-23
|
||||
|
||||
Announcements:
|
||||
- Upgrades camshaft to [0.48.3](https://github.com/CartoDB/camshaft/releases/tag/0.48.3).
|
||||
|
||||
|
||||
## 2.84.1
|
||||
Released 2016-11-23
|
||||
|
||||
Announcements:
|
||||
- Upgrades camshaft to [0.48.2](https://github.com/CartoDB/camshaft/releases/tag/0.48.2).
|
||||
|
||||
|
||||
## 2.84.0
|
||||
Released 2016-11-11
|
||||
|
||||
New features:
|
||||
- Analyses limit configuration allows to set other limits than timeout.
|
||||
|
||||
|
||||
## 2.83.1
|
||||
Released 2016-11-10
|
||||
|
||||
Announcements:
|
||||
- Upgrades camshaft to [0.48.1](https://github.com/CartoDB/camshaft/releases/tag/0.48.1).
|
||||
|
||||
|
||||
## 2.83.0
|
||||
Released 2016-11-10
|
||||
|
||||
Announcements:
|
||||
- Upgrades camshaft to [0.48.0](https://github.com/CartoDB/camshaft/releases/tag/0.48.0).
|
||||
|
||||
|
||||
## 2.82.0
|
||||
Released 2016-11-08
|
||||
|
||||
Announcements:
|
||||
- Upgrades camshaft to [0.47.0](https://github.com/CartoDB/camshaft/releases/tag/0.47.0).
|
||||
|
||||
|
||||
## 2.81.1
|
||||
Released 2016-11-05
|
||||
|
||||
Announcements:
|
||||
- Upgrades windshaft to [2.6.2](https://github.com/CartoDB/windshaft/releases/tag/2.6.2).
|
||||
- Upgrades camshaft to [0.46.3](https://github.com/CartoDB/camshaft/releases/tag/0.46.3).
|
||||
|
||||
|
||||
## 2.81.0
|
||||
Released 2016-11-02
|
||||
|
||||
Enhancements:
|
||||
- Returns errors with context when query layer does not retrieve geometry column
|
||||
|
||||
Announcements:
|
||||
- Upgrades windshaft to [2.6.1](https://github.com/CartoDB/windshaft/releases/tag/2.6.1).
|
||||
- Upgrades camshaft to [0.46.2](https://github.com/CartoDB/camshaft/releases/tag/0.46.2).
|
||||
|
||||
|
||||
## 2.80.2
|
||||
Released 2016-10-26
|
||||
|
||||
Bug fixes:
|
||||
- Fix order in categories query to get ramps
|
||||
|
||||
|
||||
## 2.80.1
|
||||
Released 2016-10-25
|
||||
|
||||
Announcements:
|
||||
- Upgrades camshaft to [0.46.1](https://github.com/CartoDB/camshaft/releases/tag/0.46.1).
|
||||
|
||||
|
||||
## 2.80.0
|
||||
Released 2016-10-20
|
||||
|
||||
Announcements:
|
||||
- Upgrades camshaft to [0.46.0](https://github.com/CartoDB/camshaft/releases/tag/0.46.0).
|
||||
|
||||
New features:
|
||||
- Default analyses limits can be defined in configuration.
|
||||
|
||||
|
||||
## 2.79.0
|
||||
Released 2016-10-11
|
||||
|
||||
New features:
|
||||
- Retrieve analysis limits and pass them into camshaft.
|
||||
|
||||
Announcements:
|
||||
- Upgrades turbo-carto to [0.18.0](https://github.com/CartoDB/turbo-carto/releases/tag/0.18.0).
|
||||
- Upgrades camshaft to [0.45.0](https://github.com/CartoDB/camshaft/releases/tag/0.45.0).
|
||||
|
||||
|
||||
## 2.78.1
|
||||
Released 2016-09-30
|
||||
|
||||
Announcements:
|
||||
- Upgrades camshaft to [0.44.2](https://github.com/CartoDB/camshaft/releases/tag/0.44.2).
|
||||
|
||||
|
||||
## 2.78.0
|
||||
Released 2016-09-29
|
||||
|
||||
New features:
|
||||
- Add metadata about processed turbo-carto rules.
|
||||
|
||||
Announcements:
|
||||
- Upgrades turbo-carto to [0.17.1](https://github.com/CartoDB/turbo-carto/releases/tag/0.17.1).
|
||||
|
||||
|
||||
## 2.77.1
|
||||
|
||||
Released 2016-09-28
|
||||
|
||||
Announcements:
|
||||
- Upgrades camshaft to [0.44.1](https://github.com/CartoDB/camshaft/releases/tag/0.44.1).
|
||||
|
||||
|
||||
## 2.77.0
|
||||
|
||||
Released 2016-09-26
|
||||
|
||||
Announcements:
|
||||
- Upgrades camshaft to [0.44.0](https://github.com/CartoDB/camshaft/releases/tag/0.44.0).
|
||||
- Adds a new configuration for camshaft: logger stream.
|
||||
|
||||
|
||||
## 2.76.0
|
||||
|
||||
Released 2016-09-15
|
||||
|
||||
New features:
|
||||
- Allow to use `--config /path/to/config.js` to specify configuration file.
|
||||
- Environment will be loaded from config file if `environment` key is present, otherwise it keeps current behaviour.
|
||||
|
||||
Bug fixes:
|
||||
- Allow to use absolute paths for log files #570.
|
||||
|
||||
|
||||
## 2.75.0
|
||||
|
||||
Released 2016-09-14
|
||||
|
||||
Announcements:
|
||||
- Upgrades camshaft to [0.43.0](https://github.com/CartoDB/camshaft/releases/tag/0.43.0).
|
||||
|
||||
|
||||
## 2.74.1
|
||||
|
||||
Released 2016-09-07
|
||||
|
||||
Announcements:
|
||||
- Upgrades camshaft to [0.42.1](https://github.com/CartoDB/camshaft/releases/tag/0.42.1).
|
||||
|
||||
|
||||
## 2.74.0
|
||||
|
||||
Released 2016-09-06
|
||||
|
||||
Enhancements:
|
||||
- Layers in previews can be shown or hidden using `preview_layers` property in template map
|
||||
|
||||
|
||||
## 2.73.1
|
||||
|
||||
Released 2016-09-06
|
||||
|
||||
Bug fixes:
|
||||
- Fixes missing column in fixture table `cdb_analysis_catalog`.
|
||||
|
||||
|
||||
## 2.73.0
|
||||
|
||||
Released 2016-09-06
|
||||
|
||||
Announcements:
|
||||
- Upgrades camshaft to [0.42.0](https://github.com/CartoDB/camshaft/releases/tag/0.42.0).
|
||||
|
||||
|
||||
## 2.72.0
|
||||
|
||||
Released 2016-08-23
|
||||
|
||||
Announcements:
|
||||
- Upgrades camshaft to [0.41.0](https://github.com/CartoDB/camshaft/releases/tag/0.41.0).
|
||||
|
||||
|
||||
## 2.71.0
|
||||
|
||||
Released 2016-08-17
|
||||
|
||||
Announcements:
|
||||
- Upgrades windshaft to [2.5.0](https://github.com/CartoDB/windshaft/releases/tag/2.5.0).
|
||||
|
||||
|
||||
## 2.70.0
|
||||
|
||||
Released 2016-08-16
|
||||
|
||||
Announcements:
|
||||
- Upgrades camshaft to [0.40.0](https://github.com/CartoDB/camshaft/releases/tag/0.40.0).
|
||||
|
||||
|
||||
## 2.69.1
|
||||
|
||||
Released 2016-08-12
|
||||
|
||||
Announcements:
|
||||
- Upgrades windshaft to [2.4.2](https://github.com/CartoDB/windshaft/releases/tag/2.4.2).
|
||||
|
||||
|
||||
## 2.69.0
|
||||
|
||||
Released 2016-08-11
|
||||
|
||||
Announcements:
|
||||
- Upgrades camshaft to [0.39.0](https://github.com/CartoDB/camshaft/releases/tag/0.39.0).
|
||||
|
||||
|
||||
## 2.68.0
|
||||
|
||||
Released 2016-07-21
|
||||
|
||||
Announcements:
|
||||
- Upgrades turbo-carto to [0.16.0](https://github.com/CartoDB/turbo-carto/releases/tag/0.16.0).
|
||||
|
||||
|
||||
## 2.67.1
|
||||
|
||||
Released 2016-07-21
|
||||
|
||||
Announcements:
|
||||
- Upgrades camshaft to [0.38.1](https://github.com/CartoDB/camshaft/releases/tag/0.38.1).
|
||||
|
||||
|
||||
## 2.67.0
|
||||
|
||||
Released 2016-07-21
|
||||
|
||||
Announcements:
|
||||
- Upgrades camshaft to [0.38.0](https://github.com/CartoDB/camshaft/releases/tag/0.38.0).
|
||||
|
||||
|
||||
## 2.66.2
|
||||
|
||||
Released 2016-07-20
|
||||
|
||||
Announcements:
|
||||
- Upgrades turbo-carto to [0.15.1](https://github.com/CartoDB/turbo-carto/releases/tag/0.15.1).
|
||||
|
||||
|
||||
## 2.66.1
|
||||
|
||||
Released 2016-07-20
|
||||
|
||||
Announcements:
|
||||
- Upgrades turbo-carto to [0.15.0](https://github.com/CartoDB/turbo-carto/releases/tag/0.15.0).
|
||||
|
||||
|
||||
## 2.66.0
|
||||
|
||||
Released 2016-07-18
|
||||
|
||||
Announcements:
|
||||
- Available new endpoint to check user analyses.
|
||||
- Upgrades camshaft to [0.37.1](https://github.com/CartoDB/camshaft/releases/tag/0.37.1).
|
||||
|
||||
|
||||
## 2.65.0
|
||||
|
||||
Released 2016-07-15
|
||||
|
||||
Announcements:
|
||||
- Upgrades cartodb-redis to 0.13.1.
|
||||
- Upgrades camshaft to [0.37.0](https://github.com/CartoDB/camshaft/releases/tag/0.37.0).
|
||||
|
||||
|
||||
## 2.64.0
|
||||
|
||||
Released 2016-07-12
|
||||
|
||||
82
app.js
@@ -5,20 +5,35 @@ var fs = require('fs');
|
||||
|
||||
var _ = require('underscore');
|
||||
|
||||
var ENVIRONMENT;
|
||||
if ( process.argv[2] ) {
|
||||
ENVIRONMENT = process.argv[2];
|
||||
} else if ( process.env.NODE_ENV ) {
|
||||
ENVIRONMENT = process.env.NODE_ENV;
|
||||
} else {
|
||||
ENVIRONMENT = 'development';
|
||||
}
|
||||
|
||||
// jshint undef:false
|
||||
var log = console.log.bind(console);
|
||||
var logError = console.error.bind(console);
|
||||
// jshint undef:true
|
||||
|
||||
var argv = require('yargs')
|
||||
.usage('Usage: $0 <environment> [options]')
|
||||
.help('h')
|
||||
.example(
|
||||
'$0 production -c /etc/sql-api/config.js',
|
||||
'start server in production environment with /etc/sql-api/config.js as config file'
|
||||
)
|
||||
.alias('h', 'help')
|
||||
.alias('c', 'config')
|
||||
.nargs('c', 1)
|
||||
.describe('c', 'Load configuration from path')
|
||||
.argv;
|
||||
|
||||
var environmentArg = argv._[0] || process.env.NODE_ENV || 'development';
|
||||
var configurationFile = path.resolve(argv.config || './config/environments/' + environmentArg + '.js');
|
||||
if (!fs.existsSync(configurationFile)) {
|
||||
logError('Configuration file "%s" does not exist', configurationFile);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
global.environment = require(configurationFile);
|
||||
var ENVIRONMENT = argv._[0] || process.env.NODE_ENV || global.environment.environment;
|
||||
process.env.NODE_ENV = ENVIRONMENT;
|
||||
|
||||
var availableEnvironments = {
|
||||
production: true,
|
||||
staging: true,
|
||||
@@ -33,16 +48,6 @@ if (!availableEnvironments[ENVIRONMENT]){
|
||||
}
|
||||
|
||||
process.env.NODE_ENV = ENVIRONMENT;
|
||||
|
||||
// set environment specific variables
|
||||
global.environment = require('./config/environments/' + ENVIRONMENT);
|
||||
|
||||
global.log4js = require('log4js');
|
||||
var log4js_config = {
|
||||
appenders: [],
|
||||
replaceConsole: true
|
||||
};
|
||||
|
||||
if (global.environment.uv_threadpool_size) {
|
||||
process.env.UV_THREADPOOL_SIZE = global.environment.uv_threadpool_size;
|
||||
}
|
||||
@@ -58,25 +63,31 @@ var agentOptions = _.defaults(global.environment.httpAgent || {}, {
|
||||
http.globalAgent = new http.Agent(agentOptions);
|
||||
https.globalAgent = new https.Agent(agentOptions);
|
||||
|
||||
|
||||
global.log4js = require('log4js');
|
||||
var log4jsConfig = {
|
||||
appenders: [],
|
||||
replaceConsole: true
|
||||
};
|
||||
|
||||
if ( global.environment.log_filename ) {
|
||||
var logdir = path.dirname(global.environment.log_filename);
|
||||
// See cwd inlog4js.configure call below
|
||||
logdir = path.resolve(__dirname, logdir);
|
||||
if ( ! fs.existsSync(logdir) ) {
|
||||
logError("Log filename directory does not exist: " + logdir);
|
||||
process.exit(1);
|
||||
}
|
||||
log("Logs will be written to " + global.environment.log_filename);
|
||||
log4js_config.appenders.push(
|
||||
{ type: "file", filename: global.environment.log_filename }
|
||||
);
|
||||
var logFilename = path.resolve(global.environment.log_filename);
|
||||
var logDirectory = path.dirname(logFilename);
|
||||
if (!fs.existsSync(logDirectory)) {
|
||||
logError("Log filename directory does not exist: " + logDirectory);
|
||||
process.exit(1);
|
||||
}
|
||||
log("Logs will be written to " + logFilename);
|
||||
log4jsConfig.appenders.push(
|
||||
{ type: "file", absolute: true, filename: logFilename }
|
||||
);
|
||||
} else {
|
||||
log4js_config.appenders.push(
|
||||
{ type: "console", layout: { type:'basic' } }
|
||||
);
|
||||
log4jsConfig.appenders.push(
|
||||
{ type: "console", layout: { type:'basic' } }
|
||||
);
|
||||
}
|
||||
|
||||
global.log4js.configure(log4js_config, { cwd: __dirname });
|
||||
global.log4js.configure(log4jsConfig);
|
||||
global.logger = global.log4js.getLogger();
|
||||
|
||||
global.environment.api_hostname = require('os').hostname().split('.')[0];
|
||||
@@ -99,6 +110,7 @@ var listener = server.listen(serverOptions.bind.port, serverOptions.bind.host, b
|
||||
var version = require("./package").version;
|
||||
|
||||
listener.on('listening', function() {
|
||||
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 +126,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');
|
||||
});
|
||||
|
||||
@@ -23,6 +23,21 @@ var config = {
|
||||
// "tiles/layergroup" is for compatibility with versions up to 1.6.x
|
||||
,base_url_detached: '(?:/api/v1/map|/user/:user/api/v1/map|/tiles/layergroup)'
|
||||
|
||||
// Resource URLs expose endpoints to request/retrieve metadata associated to Maps: dataviews, analysis node status.
|
||||
//
|
||||
// This URLs depend on how `base_url_detached` and `user_from_host` are configured: the application can be
|
||||
// configured to accept request with the {user} in the header host or in the request path.
|
||||
// It also might depend on the configured cdn_url via `serverMetadata.cdn_url`.
|
||||
//
|
||||
// This template allows to make the endpoints generation more flexible, the template exposes the following params:
|
||||
// 1. {{=it.cdn_url}}: will be used when `serverMetadata.cdn_url` exists.
|
||||
// 2. {{=it.user}}: will use the username as extraced from `user_from_host` or `base_url_detached`.
|
||||
// 3. {{=it.port}}: will use the `port` from this very same configuration file.
|
||||
,resources_url_templates: {
|
||||
http: 'http://{{=it.user}}.localhost.lan:{{=it.port}}/api/v1/map',
|
||||
https: 'http://localhost.lan:{{=it.port}}/user/{{=it.user}}/api/v1/map'
|
||||
}
|
||||
|
||||
// Maximum number of connections for one process
|
||||
// 128 is a good value with a limit of 1024 open file descriptors
|
||||
,maxConnections:128
|
||||
@@ -62,6 +77,7 @@ var config = {
|
||||
extent: "-180,-90,180,90",
|
||||
srid: 4326,
|
||||
*/
|
||||
// max number of rows to return when querying data, 0 means no limit
|
||||
row_limit: 65535,
|
||||
simplify_geometries: true,
|
||||
use_overviews: true, // use overviews to retrieve raster
|
||||
@@ -95,6 +111,10 @@ var config = {
|
||||
// Important: check the configuration of uv_threadpool_size to use suitable value
|
||||
poolSize: 8,
|
||||
|
||||
// Whether grainstore will use a child process or not to transform CartoCSS into Mapnik XML.
|
||||
// This will prevent blocking the main thread.
|
||||
useCartocssWorkers: false,
|
||||
|
||||
// Metatile is the number of tiles-per-side that are going
|
||||
// to be rendered at once. If all of them will be requested
|
||||
// we'd have saved time. If only one will be used, we'd have
|
||||
@@ -209,6 +229,18 @@ var config = {
|
||||
endpoint: 'http://127.0.0.1:8080/api/v2/sql/job',
|
||||
// the template to use for adding the host header in the batch api requests
|
||||
hostHeaderTemplate: '{{=it.username}}.localhost.lan'
|
||||
},
|
||||
logger: {
|
||||
// If filename is given logs comming from analysis client will be written
|
||||
// there, in append mode. Otherwise 'log_filename' is used. Otherwise stdout is used (default).
|
||||
// Log file will be re-opened on receiving the HUP signal
|
||||
filename: '/tmp/analysis.log'
|
||||
},
|
||||
// Define max execution time in ms for analyses or tags
|
||||
// If analysis or tag are not found in redis this values will be used as default.
|
||||
limits: {
|
||||
moran: { timeout: 120000, maxNumberOfRows: 1e5 },
|
||||
cpu2x: { timeout: 60000 }
|
||||
}
|
||||
}
|
||||
,millstone: {
|
||||
|
||||
@@ -23,6 +23,21 @@ var config = {
|
||||
// "tiles/layergroup" is for compatibility with versions up to 1.6.x
|
||||
,base_url_detached: '(?:/api/v1/map|/user/:user/api/v1/map|/tiles/layergroup)'
|
||||
|
||||
// Resource URLs expose endpoints to request/retrieve metadata associated to Maps: dataviews, analysis node status.
|
||||
//
|
||||
// This URLs depend on how `base_url_detached` and `user_from_host` are configured: the application can be
|
||||
// configured to accept request with the {user} in the header host or in the request path.
|
||||
// It also might depend on the configured cdn_url via `serverMetadata.cdn_url`.
|
||||
//
|
||||
// This template allows to make the endpoints generation more flexible, the template exposes the following params:
|
||||
// 1. {{=it.cdn_url}}: will be used when `serverMetadata.cdn_url` exists.
|
||||
// 2. {{=it.user}}: will use the username as extraced from `user_from_host` or `base_url_detached`.
|
||||
// 3. {{=it.port}}: will use the `port` from this very same configuration file.
|
||||
,resources_url_templates: {
|
||||
http: 'http://{{=it.cdn_url}}/{{=it.user}}/api/v1/map',
|
||||
https: 'https://{{=it.cdn_url}}/{{=it.user}}/api/v1/map'
|
||||
}
|
||||
|
||||
// Maximum number of connections for one process
|
||||
// 128 is a good value with a limit of 1024 open file descriptors
|
||||
,maxConnections:128
|
||||
@@ -56,6 +71,7 @@ var config = {
|
||||
host: '127.0.0.1',
|
||||
port: 6432,
|
||||
extent: "-20037508.3,-20037508.3,20037508.3,20037508.3",
|
||||
// max number of rows to return when querying data, 0 means no limit
|
||||
row_limit: 65535,
|
||||
/*
|
||||
* Set persist_connection to false if you want
|
||||
@@ -89,6 +105,10 @@ var config = {
|
||||
// Important: check the configuration of uv_threadpool_size to use suitable value
|
||||
poolSize: 8,
|
||||
|
||||
// Whether grainstore will use a child process or not to transform CartoCSS into Mapnik XML.
|
||||
// This will prevent blocking the main thread.
|
||||
useCartocssWorkers: false,
|
||||
|
||||
// Metatile is the number of tiles-per-side that are going
|
||||
// to be rendered at once. If all of them will be requested
|
||||
// we'd have saved time. If only one will be used, we'd have
|
||||
@@ -203,6 +223,18 @@ var config = {
|
||||
endpoint: 'http://127.0.0.1:8080/api/v2/sql/job',
|
||||
// the template to use for adding the host header in the batch api requests
|
||||
hostHeaderTemplate: '{{=it.username}}.localhost.lan'
|
||||
},
|
||||
logger: {
|
||||
// If filename is given logs comming from analysis client will be written
|
||||
// there, in append mode. Otherwise 'log_filename' is used. Otherwise stdout is used (default).
|
||||
// Log file will be re-opened on receiving the HUP signal
|
||||
filename: 'logs/analysis.log'
|
||||
},
|
||||
// Define max execution time in ms for analyses or tags
|
||||
// If analysis or tag are not found in redis this values will be used as default.
|
||||
limits: {
|
||||
moran: { timeout: 120000, maxNumberOfRows: 1e5 },
|
||||
cpu2x: { timeout: 60000 }
|
||||
}
|
||||
}
|
||||
,millstone: {
|
||||
|
||||
@@ -23,6 +23,21 @@ var config = {
|
||||
// "/tiles/layergroup" is for compatibility with versions up to 1.6.x
|
||||
,base_url_detached: '(?:/api/v1/map|/user/:user/api/v1/map|/tiles/layergroup)'
|
||||
|
||||
// Resource URLs expose endpoints to request/retrieve metadata associated to Maps: dataviews, analysis node status.
|
||||
//
|
||||
// This URLs depend on how `base_url_detached` and `user_from_host` are configured: the application can be
|
||||
// configured to accept request with the {user} in the header host or in the request path.
|
||||
// It also might depend on the configured cdn_url via `serverMetadata.cdn_url`.
|
||||
//
|
||||
// This template allows to make the endpoints generation more flexible, the template exposes the following params:
|
||||
// 1. {{=it.cdn_url}}: will be used when `serverMetadata.cdn_url` exists.
|
||||
// 2. {{=it.user}}: will use the username as extraced from `user_from_host` or `base_url_detached`.
|
||||
// 3. {{=it.port}}: will use the `port` from this very same configuration file.
|
||||
,resources_url_templates: {
|
||||
http: 'http://{{=it.user}}.localhost.lan:{{=it.port}}/api/v1/map',
|
||||
https: 'https://{{=it.cdn_url}}/{{=it.user}}/api/v1/map'
|
||||
}
|
||||
|
||||
// Maximum number of connections for one process
|
||||
// 128 is a good value with a limit of 1024 open file descriptors
|
||||
,maxConnections:128
|
||||
@@ -56,6 +71,7 @@ var config = {
|
||||
host: '127.0.0.1',
|
||||
port: 6432,
|
||||
extent: "-20037508.3,-20037508.3,20037508.3,20037508.3",
|
||||
// max number of rows to return when querying data, 0 means no limit
|
||||
row_limit: 65535,
|
||||
simplify_geometries: true,
|
||||
use_overviews: true, // use overviews to retrieve raster
|
||||
@@ -89,6 +105,10 @@ var config = {
|
||||
// Important: check the configuration of uv_threadpool_size to use suitable value
|
||||
poolSize: 8,
|
||||
|
||||
// Whether grainstore will use a child process or not to transform CartoCSS into Mapnik XML.
|
||||
// This will prevent blocking the main thread.
|
||||
useCartocssWorkers: false,
|
||||
|
||||
// Metatile is the number of tiles-per-side that are going
|
||||
// to be rendered at once. If all of them will be requested
|
||||
// we'd have saved time. If only one will be used, we'd have
|
||||
@@ -203,6 +223,18 @@ var config = {
|
||||
endpoint: 'http://127.0.0.1:8080/api/v2/sql/job',
|
||||
// the template to use for adding the host header in the batch api requests
|
||||
hostHeaderTemplate: '{{=it.username}}.localhost.lan'
|
||||
},
|
||||
logger: {
|
||||
// If filename is given logs comming from analysis client will be written
|
||||
// there, in append mode. Otherwise 'log_filename' is used. Otherwise stdout is used (default).
|
||||
// Log file will be re-opened on receiving the HUP signal
|
||||
filename: 'logs/analysis.log'
|
||||
},
|
||||
// Define max execution time in ms for analyses or tags
|
||||
// If analysis or tag are not found in redis this values will be used as default.
|
||||
limits: {
|
||||
moran: { timeout: 120000, maxNumberOfRows: 1e5 },
|
||||
cpu2x: { timeout: 60000 }
|
||||
}
|
||||
}
|
||||
,millstone: {
|
||||
|
||||
@@ -23,6 +23,20 @@ var config = {
|
||||
// "tiles/layergroup" is for compatibility with versions up to 1.6.x
|
||||
,base_url_detached: '(?:/api/v1/map|/user/:user/api/v1/map|/tiles/layergroup)'
|
||||
|
||||
// Resource URLs expose endpoints to request/retrieve metadata associated to Maps: dataviews, analysis node status.
|
||||
//
|
||||
// This URLs depend on how `base_url_detached` and `user_from_host` are configured: the application can be
|
||||
// configured to accept request with the {user} in the header host or in the request path.
|
||||
// It also might depend on the configured cdn_url via `serverMetadata.cdn_url`.
|
||||
//
|
||||
// This template allows to make the endpoints generation more flexible, the template exposes the following params:
|
||||
// 1. {{=it.cdn_url}}: will be used when `serverMetadata.cdn_url` exists.
|
||||
// 2. {{=it.user}}: will use the username as extraced from `user_from_host` or `base_url_detached`.
|
||||
// 3. {{=it.port}}: will use the `port` from this very same configuration file.
|
||||
,resources_url_templates: {
|
||||
http: 'http://{{=it.user}}.localhost.lan:{{=it.port}}/api/v1/map'
|
||||
}
|
||||
|
||||
// Maximum number of connections for one process
|
||||
// 128 is a good value with a limit of 1024 open file descriptors
|
||||
,maxConnections:128
|
||||
@@ -56,6 +70,7 @@ var config = {
|
||||
host: '127.0.0.1',
|
||||
port: 5432,
|
||||
extent: "-20037508.3,-20037508.3,20037508.3,20037508.3",
|
||||
// max number of rows to return when querying data, 0 means no limit
|
||||
row_limit: 65535,
|
||||
simplify_geometries: true,
|
||||
use_overviews: true, // use overviews to retrieve raster
|
||||
@@ -89,6 +104,10 @@ var config = {
|
||||
// Important: check the configuration of uv_threadpool_size to use suitable value
|
||||
poolSize: 8,
|
||||
|
||||
// Whether grainstore will use a child process or not to transform CartoCSS into Mapnik XML.
|
||||
// This will prevent blocking the main thread.
|
||||
useCartocssWorkers: false,
|
||||
|
||||
// Metatile is the number of tiles-per-side that are going
|
||||
// to be rendered at once. If all of them will be requested
|
||||
// we'd have saved time. If only one will be used, we'd have
|
||||
@@ -204,6 +223,18 @@ var config = {
|
||||
endpoint: 'http://127.0.0.1:8080/api/v2/sql/job',
|
||||
// the template to use for adding the host header in the batch api requests
|
||||
hostHeaderTemplate: '{{=it.username}}.localhost.lan'
|
||||
},
|
||||
logger: {
|
||||
// If filename is given logs comming from analysis client will be written
|
||||
// there, in append mode. Otherwise 'log_filename' is used. Otherwise stdout is used (default).
|
||||
// Log file will be re-opened on receiving the HUP signal
|
||||
filename: 'node-windshaft.log'
|
||||
},
|
||||
// Define max execution time in ms for analyses or tags
|
||||
// If analysis or tag are not found in redis this values will be used as default.
|
||||
limits: {
|
||||
moran: { timeout: 120000, maxNumberOfRows: 1e5 },
|
||||
cpu2x: { timeout: 60000 }
|
||||
}
|
||||
}
|
||||
,millstone: {
|
||||
|
||||
@@ -141,9 +141,6 @@ 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.
|
||||
|
||||
@@ -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 |
|
||||
--- | ---
|
||||
|_ method | `"token"` or `"open"` (`"open"` is the default if no method is specified. Use `"token"` to password-protect your map)
|
||||
|_ 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).
|
||||
--- | ---
|
||||
|_ zoom | The zoom level to use
|
||||
|
||||
|_ center |
|
||||
|_ center |
|
||||
--- | ---
|
||||
|_ |_ lng | The longitude to use for the center
|
||||
|_ |_ lat | The latitude to use for the center
|
||||
|
||||
|_ bounds |
|
||||
|_ bounds |
|
||||
--- | ---
|
||||
|_ |_ west | LowerCorner longitude for the bounding box, in decimal degrees (aka most western)
|
||||
|_ |_ south | LowerCorner latitude for the bounding box, in decimal degrees (aka most southern)
|
||||
@@ -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,7 +520,7 @@ If you are creating a Torque layer in a Named Map without using the Torque.js li
|
||||
})
|
||||
.addTo(map)
|
||||
.done(function(layer) {
|
||||
|
||||
|
||||
});
|
||||
}
|
||||
```
|
||||
@@ -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:
|
||||
|
||||
|
||||
@@ -149,7 +149,7 @@ It is important to note that generated images are cached from the live data refe
|
||||
* 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.
|
||||
* Timeout limits for generating static maps are the same across CARTO Builder and CARTO Engine. It is important to ensure timely processing of queries.
|
||||
|
||||
## Examples
|
||||
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
};
|
||||
|
||||
@@ -55,11 +55,9 @@ util.inherits(TemplateMaps, EventEmitter);
|
||||
module.exports = TemplateMaps;
|
||||
|
||||
|
||||
var o = TemplateMaps.prototype;
|
||||
|
||||
//--------------- PRIVATE METHODS --------------------------------
|
||||
|
||||
o._userTemplateLimit = function() {
|
||||
TemplateMaps.prototype._userTemplateLimit = function() {
|
||||
return this.opts.max_user_templates || 0;
|
||||
};
|
||||
|
||||
@@ -70,7 +68,7 @@ o._userTemplateLimit = function() {
|
||||
* @param redisArgs - the arguments for the redis function in an array
|
||||
* @param callback - function to pass results too.
|
||||
*/
|
||||
o._redisCmd = function(redisFunc, redisArgs, callback) {
|
||||
TemplateMaps.prototype._redisCmd = function(redisFunc, redisArgs, callback) {
|
||||
var redisClient;
|
||||
var that = this;
|
||||
var db = that.db_signatures;
|
||||
@@ -97,7 +95,7 @@ o._redisCmd = function(redisFunc, redisArgs, callback) {
|
||||
var _reValidNameIdentifier = /^[a-z0-9][0-9a-z_\-]*$/i;
|
||||
var _reValidPlaceholderIdentifier = /^[a-z][0-9a-z_]*$/i;
|
||||
// jshint maxcomplexity:15
|
||||
o._checkInvalidTemplate = function(template) {
|
||||
TemplateMaps.prototype._checkInvalidTemplate = function(template) {
|
||||
if ( template.version !== '0.0.1' ) {
|
||||
return new Error("Unsupported template version " + template.version);
|
||||
}
|
||||
@@ -200,7 +198,7 @@ function templateDefaults(template) {
|
||||
// @param callback function(err, tpl_id)
|
||||
// Return template identifier (only valid for given user)
|
||||
//
|
||||
o.addTemplate = function(owner, template, callback) {
|
||||
TemplateMaps.prototype.addTemplate = function(owner, template, callback) {
|
||||
var self = this;
|
||||
|
||||
template = templateDefaults(template);
|
||||
@@ -258,7 +256,7 @@ o.addTemplate = function(owner, template, callback) {
|
||||
//
|
||||
// @param callback function(err)
|
||||
//
|
||||
o.delTemplate = function(owner, tpl_id, callback) {
|
||||
TemplateMaps.prototype.delTemplate = function(owner, tpl_id, callback) {
|
||||
var self = this;
|
||||
step(
|
||||
function deleteTemplate() {
|
||||
@@ -297,7 +295,8 @@ o.delTemplate = function(owner, tpl_id, callback) {
|
||||
//
|
||||
// @param callback function(err)
|
||||
//
|
||||
o.updTemplate = function(owner, tpl_id, template, callback) {
|
||||
TemplateMaps.prototype.updTemplate = function(owner, tpl_id, template, callback) {
|
||||
|
||||
var self = this;
|
||||
|
||||
template = templateDefaults(template);
|
||||
@@ -356,7 +355,7 @@ o.updTemplate = function(owner, tpl_id, template, callback) {
|
||||
// @param callback function(err, tpl_id_list)
|
||||
// Returns a list of template identifiers
|
||||
//
|
||||
o.listTemplates = function(owner, callback) {
|
||||
TemplateMaps.prototype.listTemplates = function(owner, callback) {
|
||||
this._redisCmd('HKEYS', [ this.key_usr_tpl({owner:owner}) ], callback);
|
||||
};
|
||||
|
||||
@@ -370,7 +369,7 @@ o.listTemplates = function(owner, callback) {
|
||||
// @param callback function(err, template)
|
||||
// Return full template definition
|
||||
//
|
||||
o.getTemplate = function(owner, tpl_id, callback) {
|
||||
TemplateMaps.prototype.getTemplate = function(owner, tpl_id, callback) {
|
||||
var self = this;
|
||||
step(
|
||||
function getTemplate() {
|
||||
@@ -386,7 +385,7 @@ o.getTemplate = function(owner, tpl_id, callback) {
|
||||
);
|
||||
};
|
||||
|
||||
o.isAuthorized = function(template, authTokens) {
|
||||
TemplateMaps.prototype.isAuthorized = function(template, authTokens) {
|
||||
if (!template) {
|
||||
return false;
|
||||
}
|
||||
@@ -438,7 +437,7 @@ function _replaceVars (str, params) {
|
||||
});
|
||||
return str;
|
||||
}
|
||||
o.instance = function(template, params) {
|
||||
TemplateMaps.prototype.instance = function(template, params) {
|
||||
var all_params = {};
|
||||
var phold = template.placeholders || {};
|
||||
Object.keys(phold).forEach(function(k) {
|
||||
@@ -500,7 +499,7 @@ o.instance = function(template, params) {
|
||||
};
|
||||
|
||||
// Return a fingerPrint of the object
|
||||
o.fingerPrint = function(template) {
|
||||
TemplateMaps.prototype.fingerPrint = function(template) {
|
||||
return crypto.createHash('md5')
|
||||
.update(JSON.stringify(template))
|
||||
.digest('hex')
|
||||
|
||||
@@ -6,6 +6,9 @@ dot.templateSettings.strip = false;
|
||||
function createTemplate(method) {
|
||||
return dot.template([
|
||||
'SELECT',
|
||||
'min({{=it._column}}) min_val,',
|
||||
'max({{=it._column}}) max_val,',
|
||||
'avg({{=it._column}}) avg_val,',
|
||||
method,
|
||||
'FROM ({{=it._sql}}) _table_sql WHERE {{=it._column}} IS NOT NULL'
|
||||
].join('\n'));
|
||||
@@ -29,17 +32,12 @@ 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 \'__other\' category',
|
||||
' SELECT category',
|
||||
' FROM categories',
|
||||
' WHERE rank >= {{=it._buckets}}',
|
||||
' GROUP BY 1',
|
||||
' UNION ALL',
|
||||
' SELECT CAST(category AS text)',
|
||||
' FROM categories',
|
||||
' WHERE rank < {{=it._buckets}}',
|
||||
' WHERE rank <= {{=it._buckets}}',
|
||||
')',
|
||||
'SELECT array_agg(category) AS category FROM agg_categories'
|
||||
].join('\n'));
|
||||
@@ -79,11 +77,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
|
||||
@@ -94,8 +95,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;
|
||||
|
||||
169
lib/cartodb/controllers/analyses.js
Normal file
@@ -0,0 +1,169 @@
|
||||
var step = require('step');
|
||||
var assert = require('assert');
|
||||
var dot = require('dot');
|
||||
dot.templateSettings.strip = false;
|
||||
var PSQL = require('cartodb-psql');
|
||||
|
||||
var util = require('util');
|
||||
var BaseController = require('./base');
|
||||
|
||||
var cors = require('../middleware/cors');
|
||||
var userMiddleware = require('../middleware/user');
|
||||
|
||||
function AnalysesController(authApi, pgConnection) {
|
||||
BaseController.call(this, authApi, pgConnection);
|
||||
}
|
||||
|
||||
util.inherits(AnalysesController, BaseController);
|
||||
|
||||
module.exports = AnalysesController;
|
||||
|
||||
AnalysesController.prototype.register = function(app) {
|
||||
app.get(app.base_url_mapconfig + '/analyses/catalog', cors(), userMiddleware, this.catalog.bind(this));
|
||||
};
|
||||
|
||||
AnalysesController.prototype.sendResponse = function(req, res, resource) {
|
||||
res.set('Cache-Control', 'public,max-age=10,must-revalidate');
|
||||
this.send(req, res, resource, 200);
|
||||
};
|
||||
|
||||
AnalysesController.prototype.catalog = function(req, res) {
|
||||
var self = this;
|
||||
var username = req.context.user;
|
||||
|
||||
step(
|
||||
function reqParams() {
|
||||
self.req2params(req, this);
|
||||
},
|
||||
function catalogQuery(err) {
|
||||
assert.ifError(err);
|
||||
var pg = new PSQL(dbParamsFromReqParams(req.params));
|
||||
getMetadata(username, pg, this);
|
||||
},
|
||||
function prepareResponse(err, results) {
|
||||
assert.ifError(err);
|
||||
|
||||
var analysisIdToTable = results.tables.reduce(function(analysisIdToTable, table) {
|
||||
var analysisId = table.relname.split('_')[2];
|
||||
if (analysisId && analysisId.length === 40) {
|
||||
analysisIdToTable[analysisId] = table;
|
||||
}
|
||||
return analysisIdToTable;
|
||||
}, {});
|
||||
|
||||
var catalogWithTables = results.catalog.map(function(analysis) {
|
||||
if (analysisIdToTable.hasOwnProperty(analysis.node_id)) {
|
||||
analysis.table = analysisIdToTable[analysis.node_id];
|
||||
}
|
||||
return analysis;
|
||||
});
|
||||
|
||||
return catalogWithTables.sort(function(analysisA, analysisB) {
|
||||
if (!!analysisA.table && !!analysisB.table) {
|
||||
return analysisB.table.size - analysisA.table.size;
|
||||
}
|
||||
|
||||
if (!!analysisA.table) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!!analysisB.table) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
return -1;
|
||||
});
|
||||
},
|
||||
function sendResponse(err, catalogWithTables) {
|
||||
if (err) {
|
||||
if (err.message.match(/permission\sdenied/)) {
|
||||
err = new Error('Unauthorized');
|
||||
err.http_status = 401;
|
||||
}
|
||||
self.sendError(req, res, err);
|
||||
} else {
|
||||
self.sendResponse(req, res, { catalog: catalogWithTables });
|
||||
}
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
var catalogQueryTpl = dot.template(
|
||||
'SELECT analysis_def->>\'type\' as type, * FROM cartodb.cdb_analysis_catalog WHERE username = \'{{=it._username}}\''
|
||||
);
|
||||
|
||||
var tablesQueryTpl = dot.template([
|
||||
"WITH analysis_tables AS (",
|
||||
" SELECT",
|
||||
" n.nspname AS nspname,",
|
||||
" c.relname AS relname,",
|
||||
" pg_total_relation_size(",
|
||||
" format('%s.%s', pg_catalog.quote_ident(n.nspname), pg_catalog.quote_ident(c.relname))",
|
||||
" ) AS size,",
|
||||
" format('%s.%s', pg_catalog.quote_ident(nspname), pg_catalog.quote_ident(relname)) AS fully_qualified_name",
|
||||
" FROM pg_catalog.pg_class c, pg_catalog.pg_namespace n",
|
||||
" WHERE c.relnamespace = n.oid",
|
||||
" AND pg_catalog.quote_ident(c.relname) ~ '^analysis_[a-z0-9]{10}_[a-z0-9]{40}$'",
|
||||
" AND n.nspname IN ('{{=it._username}}', 'public')",
|
||||
")",
|
||||
"SELECT *, pg_size_pretty(size) as size_pretty",
|
||||
"FROM analysis_tables",
|
||||
"ORDER BY size DESC"
|
||||
].join('\n'));
|
||||
|
||||
|
||||
function getMetadata(username, pg, callback) {
|
||||
var results = {
|
||||
catalog: [],
|
||||
tables: []
|
||||
};
|
||||
step(
|
||||
function getCatalog() {
|
||||
pg.query(catalogQueryTpl({_username: username}), this, true); // use read-only transaction
|
||||
},
|
||||
function handleCatalog(err, resultSet) {
|
||||
assert.ifError(err);
|
||||
resultSet = resultSet || {};
|
||||
results.catalog = resultSet.rows || [];
|
||||
this();
|
||||
},
|
||||
function getTables(err) {
|
||||
assert.ifError(err);
|
||||
pg.query(tablesQueryTpl({_username: username}), this, true); // use read-only transaction
|
||||
},
|
||||
function handleTables(err, resultSet) {
|
||||
assert.ifError(err);
|
||||
resultSet = resultSet || {};
|
||||
results.tables = resultSet.rows || [];
|
||||
this();
|
||||
},
|
||||
function finish(err) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
return callback(null, results);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
function dbParamsFromReqParams(params) {
|
||||
var dbParams = {};
|
||||
if ( params.dbuser ) {
|
||||
dbParams.user = params.dbuser;
|
||||
}
|
||||
if ( params.dbpassword ) {
|
||||
dbParams.pass = params.dbpassword;
|
||||
}
|
||||
if ( params.dbhost ) {
|
||||
dbParams.host = params.dbhost;
|
||||
}
|
||||
if ( params.dbport ) {
|
||||
dbParams.port = params.dbport;
|
||||
}
|
||||
if ( params.dbname ) {
|
||||
dbParams.dbname = params.dbname;
|
||||
}
|
||||
return dbParams;
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
module.exports = {
|
||||
Analyses: require('./analyses'),
|
||||
Layergroup: require('./layergroup'),
|
||||
Map: require('./map'),
|
||||
NamedMaps: require('./named_maps'),
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
var _ = require('underscore');
|
||||
var dot = require('dot');
|
||||
dot.templateSettings.strip = false;
|
||||
var assert = require('assert');
|
||||
var step = require('step');
|
||||
var windshaft = require('windshaft');
|
||||
@@ -44,6 +46,20 @@ function MapController(authApi, pgConnection, templateMaps, mapBackend, metadata
|
||||
this.layergroupAffectedTables = layergroupAffectedTables;
|
||||
|
||||
this.mapConfigAdapter = mapConfigAdapter;
|
||||
|
||||
this.resourcesUrlTemplates = null;
|
||||
if (global.environment.resources_url_templates) {
|
||||
var templates = global.environment.resources_url_templates;
|
||||
|
||||
if (templates.http) {
|
||||
this.resourcesUrlTemplates = this.resourcesUrlTemplates || {};
|
||||
this.resourcesUrlTemplates.http = dot.template(templates.http + '/{{=it.resource}}');
|
||||
}
|
||||
if (templates.https) {
|
||||
this.resourcesUrlTemplates = this.resourcesUrlTemplates || {};
|
||||
this.resourcesUrlTemplates.https = dot.template(templates.https + '/{{=it.resource}}');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
util.inherits(MapController, BaseController);
|
||||
@@ -166,11 +182,30 @@ MapController.prototype.create = function(req, res, prepareConfigFn) {
|
||||
},
|
||||
function finish(err, layergroup) {
|
||||
if (err) {
|
||||
if (Number.isFinite(err.layerIndex)) {
|
||||
var error = new Error(err.message);
|
||||
error.http_status = err.http_status;
|
||||
|
||||
if (!err.http_status && err.message.indexOf('column "the_geom_webmercator" does not exist') >= 0) {
|
||||
error.http_status = 400;
|
||||
}
|
||||
|
||||
error.type = 'layer';
|
||||
error.subtype = err.message.indexOf('Postgis Plugin') >= 0 ? 'query' : undefined;
|
||||
error.layer = {
|
||||
id: mapConfig.getLayerId(err.layerIndex),
|
||||
index: err.layerIndex,
|
||||
type: mapConfig.layerType(err.layerIndex)
|
||||
};
|
||||
|
||||
err = error;
|
||||
}
|
||||
self.sendError(req, res, err, 'ANONYMOUS LAYERGROUP');
|
||||
} else {
|
||||
var analysesResults = context.analysesResults || [];
|
||||
addDataviewsAndWidgetsUrls(req.context.user, layergroup, mapConfig.obj());
|
||||
addAnalysesMetadata(req.context.user, layergroup, analysesResults, true);
|
||||
self.addDataviewsAndWidgetsUrls(req.context.user, layergroup, mapConfig.obj());
|
||||
self.addAnalysesMetadata(req.context.user, layergroup, analysesResults, true);
|
||||
addContextMetadata(layergroup, mapConfig.obj(), context);
|
||||
res.set('X-Layergroup-Id', layergroup.layergroupid);
|
||||
self.send(req, res, layergroup, 200);
|
||||
}
|
||||
@@ -178,6 +213,17 @@ MapController.prototype.create = function(req, res, prepareConfigFn) {
|
||||
);
|
||||
};
|
||||
|
||||
function addContextMetadata(layergroup, mapConfig, context) {
|
||||
if (layergroup.metadata && Array.isArray(layergroup.metadata.layers) && Array.isArray(mapConfig.layers)) {
|
||||
layergroup.metadata.layers = layergroup.metadata.layers.map(function(layer, layerIndex) {
|
||||
if (context.turboCarto && Array.isArray(context.turboCarto.layers)) {
|
||||
layer.meta.cartocss_meta = context.turboCarto.layers[layerIndex];
|
||||
}
|
||||
return layer;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
MapController.prototype.instantiateTemplate = function(req, res, prepareParamsFn) {
|
||||
var self = this;
|
||||
|
||||
@@ -229,8 +275,10 @@ MapController.prototype.instantiateTemplate = function(req, res, prepareParamsFn
|
||||
var templateHash = self.templateMaps.fingerPrint(mapConfigProvider.template).substring(0, 8);
|
||||
layergroup.layergroupid = cdbuser + '@' + templateHash + '@' + layergroup.layergroupid;
|
||||
|
||||
addDataviewsAndWidgetsUrls(cdbuser, layergroup, mapConfig.obj());
|
||||
addAnalysesMetadata(cdbuser, layergroup, mapConfigProvider.analysesResults);
|
||||
var _mapConfig = mapConfig.obj();
|
||||
self.addDataviewsAndWidgetsUrls(cdbuser, layergroup, _mapConfig);
|
||||
self.addAnalysesMetadata(cdbuser, layergroup, mapConfigProvider.analysesResults);
|
||||
addContextMetadata(layergroup, _mapConfig, mapConfigProvider.context);
|
||||
|
||||
res.set('X-Layergroup-Id', layergroup.layergroupid);
|
||||
self.surrogateKeysCache.tag(res, new NamedMapsCacheEntry(cdbuser, mapConfigProvider.getTemplateName()));
|
||||
@@ -338,7 +386,7 @@ function getLastUpdatedTime(analysesResults, lastUpdateTime) {
|
||||
}, lastUpdateTime);
|
||||
}
|
||||
|
||||
function addAnalysesMetadata(username, layergroup, analysesResults, includeQuery) {
|
||||
MapController.prototype.addAnalysesMetadata = function(username, layergroup, analysesResults, includeQuery) {
|
||||
includeQuery = includeQuery || false;
|
||||
analysesResults = analysesResults || [];
|
||||
layergroup.metadata.analyses = [];
|
||||
@@ -351,7 +399,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.getUrls(username, nodeResource)
|
||||
};
|
||||
if (includeQuery) {
|
||||
nodeRepr.query = node.getQuery();
|
||||
@@ -363,30 +411,30 @@ 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.addDataviewsAndWidgetsUrls = function(username, layergroup, mapConfig) {
|
||||
this.addDataviewsUrls(username, layergroup, mapConfig);
|
||||
this.addWidgetsUrl(username, layergroup, mapConfig);
|
||||
};
|
||||
|
||||
function addDataviewsUrls(username, layergroup, mapConfig) {
|
||||
MapController.prototype.addDataviewsUrls = function(username, layergroup, mapConfig) {
|
||||
layergroup.metadata.dataviews = layergroup.metadata.dataviews || {};
|
||||
var dataviews = mapConfig.dataviews || {};
|
||||
|
||||
Object.keys(dataviews).forEach(function(dataviewName) {
|
||||
var resource = layergroup.layergroupid + '/dataview/' + dataviewName;
|
||||
layergroup.metadata.dataviews[dataviewName] = {
|
||||
url: getUrls(username, resource)
|
||||
url: this.getUrls(username, resource)
|
||||
};
|
||||
});
|
||||
}
|
||||
}.bind(this));
|
||||
};
|
||||
|
||||
function addWidgetsUrl(username, layergroup, mapConfig) {
|
||||
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];
|
||||
@@ -396,16 +444,19 @@ function addWidgetsUrl(username, layergroup, mapConfig) {
|
||||
var resource = layergroup.layergroupid + '/' + layerIndex + '/widget/' + widgetName;
|
||||
layer.widgets[widgetName] = {
|
||||
type: mapConfigLayer.options.widgets[widgetName].type,
|
||||
url: getUrls(username, resource)
|
||||
url: this.getUrls(username, resource)
|
||||
};
|
||||
});
|
||||
}.bind(this));
|
||||
}
|
||||
return layer;
|
||||
});
|
||||
}.bind(this));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
function getUrls(username, resource) {
|
||||
MapController.prototype.getUrls = function(username, resource) {
|
||||
if (this.resourcesUrlTemplates) {
|
||||
return this.getUrlsFromTemplate(username, resource);
|
||||
}
|
||||
var cdnUrl = global.environment.serverMetadata && global.environment.serverMetadata.cdn_url;
|
||||
if (cdnUrl) {
|
||||
return {
|
||||
@@ -418,4 +469,29 @@ function getUrls(username, resource) {
|
||||
http: 'http://' + username + '.' + 'localhost.lan:' + port + '/api/v1/map/' + resource
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
MapController.prototype.getUrlsFromTemplate = function(username, resource) {
|
||||
var urls = {};
|
||||
var cdnUrl = global.environment.serverMetadata && global.environment.serverMetadata.cdn_url || {};
|
||||
|
||||
if (this.resourcesUrlTemplates.http) {
|
||||
urls.http = this.resourcesUrlTemplates.http({
|
||||
cdn_url: cdnUrl.http,
|
||||
user: username,
|
||||
port: global.environment.port,
|
||||
resource: resource
|
||||
});
|
||||
}
|
||||
|
||||
if (this.resourcesUrlTemplates.https) {
|
||||
urls.https = this.resourcesUrlTemplates.https({
|
||||
cdn_url: cdnUrl.https,
|
||||
user: username,
|
||||
port: global.environment.port,
|
||||
resource: resource
|
||||
});
|
||||
}
|
||||
|
||||
return urls;
|
||||
};
|
||||
|
||||
@@ -137,9 +137,15 @@ NamedMapsController.prototype.staticMap = function(req, res) {
|
||||
this
|
||||
);
|
||||
},
|
||||
function prepareImageOptions(err, _namedMapProvider) {
|
||||
function prepareLayerVisibility(err, _namedMapProvider) {
|
||||
assert.ifError(err);
|
||||
|
||||
namedMapProvider = _namedMapProvider;
|
||||
|
||||
self.prepareLayerFilterFromPreviewLayers(cdbUser, req, namedMapProvider, this);
|
||||
},
|
||||
function prepareImageOptions(err) {
|
||||
assert.ifError(err);
|
||||
self.getStaticImageOptions(cdbUser, req.params, namedMapProvider, this);
|
||||
},
|
||||
function getImage(err, imageOpts) {
|
||||
@@ -184,6 +190,45 @@ NamedMapsController.prototype.staticMap = function(req, res) {
|
||||
);
|
||||
};
|
||||
|
||||
NamedMapsController.prototype.prepareLayerFilterFromPreviewLayers = function (user, req, namedMapProvider, callback) {
|
||||
var self = this;
|
||||
namedMapProvider.getTemplate(function (err, template) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
if (!template || !template.view || !template.view.preview_layers) {
|
||||
return callback();
|
||||
}
|
||||
|
||||
var previewLayers = template.view.preview_layers;
|
||||
var layerVisibilityFilter = [];
|
||||
|
||||
template.layergroup.layers.forEach(function (layer, index) {
|
||||
if (previewLayers[''+index] !== false && previewLayers[layer.id] !== false) {
|
||||
layerVisibilityFilter.push(''+index);
|
||||
}
|
||||
});
|
||||
|
||||
if (!layerVisibilityFilter.length) {
|
||||
return callback();
|
||||
}
|
||||
|
||||
// overwrites 'all' default filter
|
||||
req.params.layer = layerVisibilityFilter.join(',');
|
||||
|
||||
// recreates the provider
|
||||
self.namedMapProviderCache.get(
|
||||
user,
|
||||
req.params.template_id,
|
||||
req.query.config,
|
||||
req.query.auth_token,
|
||||
req.params,
|
||||
callback
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
var DEFAULT_ZOOM_CENTER = {
|
||||
zoom: 1,
|
||||
center: {
|
||||
|
||||
@@ -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 ) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -120,6 +120,7 @@ NamedMapMapConfigProvider.prototype.getMapConfig = function(callback) {
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -147,7 +147,8 @@ module.exports = function(serverOptions) {
|
||||
var tileBackend = new windshaft.backend.Tile(rendererCache);
|
||||
var mapValidatorBackend = new windshaft.backend.MapValidator(tileBackend, attributesBackend);
|
||||
var mapBackend = new windshaft.backend.Map(rendererCache, mapStore, mapValidatorBackend);
|
||||
var analysisBackend = new AnalysisBackend(serverOptions.analysis);
|
||||
|
||||
var analysisBackend = new AnalysisBackend(metadataBackend, serverOptions.analysis);
|
||||
|
||||
var layergroupAffectedTablesCache = new LayergroupAffectedTablesCache();
|
||||
app.layergroupAffectedTablesCache = layergroupAffectedTablesCache;
|
||||
@@ -222,6 +223,8 @@ module.exports = function(serverOptions) {
|
||||
|
||||
new controller.NamedMapsAdmin(authApi, pgConnection, templateMaps).register(app);
|
||||
|
||||
new controller.Analyses(authApi, pgConnection).register(app);
|
||||
|
||||
new controller.ServerInfo(versions).register(app);
|
||||
|
||||
/*******************************************************************************************************************
|
||||
|
||||
@@ -36,7 +36,11 @@ var analysisConfig = _.defaults(global.environment.analysis || {}, {
|
||||
inlineExecution: false,
|
||||
endpoint: 'http://127.0.0.1:8080/api/v2/sql/job',
|
||||
hostHeaderTemplate: '{{=it.username}}.localhost.lan'
|
||||
}
|
||||
},
|
||||
logger: {
|
||||
filename: undefined
|
||||
},
|
||||
limits: {}
|
||||
});
|
||||
|
||||
module.exports = {
|
||||
@@ -66,6 +70,7 @@ module.exports = {
|
||||
},
|
||||
datasource: global.environment.postgres,
|
||||
cachedir: global.environment.millstone.cache_basedir,
|
||||
use_workers: rendererConfig.mapnik.useCartocssWorkers || false,
|
||||
mapnik_version: global.environment.mapnik_version,
|
||||
mapnik_tile_format: global.environment.mapnik_tile_format || 'png',
|
||||
default_layergroup_ttl: global.environment.mapConfigTTL || 7200
|
||||
@@ -95,7 +100,11 @@ module.exports = {
|
||||
inlineExecution: analysisConfig.batch.inlineExecution,
|
||||
endpoint: analysisConfig.batch.endpoint,
|
||||
hostHeaderTemplate: analysisConfig.batch.hostHeaderTemplate
|
||||
}
|
||||
},
|
||||
logger: {
|
||||
filename: analysisConfig.logger.filename
|
||||
},
|
||||
limits: analysisConfig.limits
|
||||
},
|
||||
// Do not send unwatch on release. See http://github.com/CartoDB/Windshaft-cartodb/issues/161
|
||||
redis: _.extend(global.environment.redis, {unwatchOnRelease: false}),
|
||||
|
||||
4043
npm-shrinkwrap.json
generated
15
package.json
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"private": true,
|
||||
"name": "windshaft-cartodb",
|
||||
"version": "2.64.0",
|
||||
"version": "2.86.2",
|
||||
"description": "A map tile server for CartoDB",
|
||||
"keywords": [
|
||||
"cartodb"
|
||||
@@ -20,15 +20,15 @@
|
||||
],
|
||||
"dependencies": {
|
||||
"body-parser": "~1.14.0",
|
||||
"camshaft": "0.36.0",
|
||||
"cartodb-psql": "~0.6.1",
|
||||
"camshaft": "0.48.4",
|
||||
"cartodb-psql": "0.10.1",
|
||||
"cartodb-query-tables": "~0.1.0",
|
||||
"cartodb-redis": "~0.13.0",
|
||||
"cartodb-redis": "0.13.1",
|
||||
"debug": "~2.2.0",
|
||||
"dot": "~1.0.2",
|
||||
"express": "~4.13.3",
|
||||
"fastly-purge": "~1.0.1",
|
||||
"log4js": "https://github.com/CartoDB/log4js-node/tarball/cdb",
|
||||
"log4js": "cartodb/log4js-node#cdb",
|
||||
"lru-cache": "2.6.5",
|
||||
"lzma": "~2.3.2",
|
||||
"node-statsd": "~0.0.7",
|
||||
@@ -37,9 +37,10 @@
|
||||
"request": "~2.62.0",
|
||||
"step": "~0.0.6",
|
||||
"step-profiler": "~0.3.0",
|
||||
"turbo-carto": "0.14.0",
|
||||
"turbo-carto": "0.18.0",
|
||||
"underscore": "~1.6.0",
|
||||
"windshaft": "2.4.0"
|
||||
"windshaft": "2.6.3",
|
||||
"yargs": "~5.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"istanbul": "~0.4.3",
|
||||
|
||||
68
test/acceptance/errors-with-context.js
Normal file
@@ -0,0 +1,68 @@
|
||||
var assert = require('../support/assert');
|
||||
var CartodbWindshaft = require(__dirname + '/../../lib/cartodb/server');
|
||||
var serverOptions = require(__dirname + '/../../lib/cartodb/server_options');
|
||||
var server = new CartodbWindshaft(serverOptions);
|
||||
|
||||
describe('error with context', function () {
|
||||
var layerOK = {
|
||||
options: {
|
||||
sql: 'select cartodb_id, ST_Translate(the_geom_webmercator, 5e6, 0) as the_geom_webmercator ' +
|
||||
'from test_table',
|
||||
cartocss: '#layer { marker-fill:red; marker-width:32; marker-allow-overlap:true; }',
|
||||
cartocss_version: '2.0.1',
|
||||
interactivity: 'cartodb_id'
|
||||
}
|
||||
};
|
||||
var layerKO = {
|
||||
options: {
|
||||
sql: 'select cartodb_id from test_table offset 3', // it doesn't return the_geom_webmercator so it must fail
|
||||
cartocss: '#layer { marker-fill:blue; marker-allow-overlap:true; }',
|
||||
cartocss_version: '2.0.2',
|
||||
interactivity: 'cartodb_id'
|
||||
}
|
||||
};
|
||||
|
||||
var DB_ERROR_MESSAGE = 'Postgis Plugin: ERROR: column "the_geom_webmercator" does not exist';
|
||||
var scenarios = [{
|
||||
description: 'layergroup with 2 layers, second one has query error',
|
||||
layergroup: {
|
||||
version: '1.0.0',
|
||||
layers: [layerOK, layerKO]
|
||||
},
|
||||
expectedFailingLayer: { id: 'layer1', index: 1, type: 'mapnik' }
|
||||
}, {
|
||||
description: 'layergroup with 2 layers, first one has query error',
|
||||
layergroup: {
|
||||
version: '1.0.0',
|
||||
layers: [layerKO, layerOK]
|
||||
},
|
||||
expectedFailingLayer: { id: 'layer0', index: 0, type: 'mapnik' }
|
||||
}];
|
||||
|
||||
scenarios.forEach(function (scenario) {
|
||||
it(scenario.description, function (done) {
|
||||
assert.response(server, {
|
||||
url: '/api/v1/map',
|
||||
method: 'POST',
|
||||
headers: {
|
||||
host: 'localhost',
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
data: JSON.stringify(scenario.layergroup)
|
||||
}, {
|
||||
status: 400
|
||||
}, function (res) {
|
||||
var parsedBody = JSON.parse(res.body);
|
||||
|
||||
assert.ok(Array.isArray(parsedBody.errors_with_context));
|
||||
|
||||
var err = parsedBody.errors_with_context[0];
|
||||
assert.equal(err.type, 'layer');
|
||||
assert.equal(err.subtype, 'query');
|
||||
assert.ok(err.message.indexOf(DB_ERROR_MESSAGE) >= 0);
|
||||
assert.deepEqual(err.layer, scenario.expectedFailingLayer);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
210
test/acceptance/named_layers_visibility.js
Normal file
@@ -0,0 +1,210 @@
|
||||
var step = require('step');
|
||||
var test_helper = require('../support/test_helper');
|
||||
|
||||
var assert = require('../support/assert');
|
||||
var CartodbWindshaft = require(__dirname + '/../../lib/cartodb/server');
|
||||
var serverOptions = require(__dirname + '/../../lib/cartodb/server_options');
|
||||
var server = new CartodbWindshaft(serverOptions);
|
||||
|
||||
var RedisPool = require('redis-mpool');
|
||||
var TemplateMaps = require('../../lib/cartodb/backends/template_maps.js');
|
||||
var mapnik = require('windshaft').mapnik;
|
||||
|
||||
var IMAGE_TOLERANCE = 20;
|
||||
|
||||
describe('layers visibility for previews', function() {
|
||||
// configure redis pool instance to use in tests
|
||||
var redisPool = new RedisPool(global.environment.redis);
|
||||
var templateMaps = new TemplateMaps(redisPool, {
|
||||
max_user_templates: global.environment.maxUserTemplates
|
||||
});
|
||||
|
||||
var username = 'localhost';
|
||||
|
||||
function createLayer (color, layerId) {
|
||||
var mod;
|
||||
|
||||
if (color === 'red') {
|
||||
mod = 0;
|
||||
} else if (color === 'orange') {
|
||||
mod = 1;
|
||||
} else if (color === 'blue') {
|
||||
mod = 2;
|
||||
} else {
|
||||
mod = 0;
|
||||
}
|
||||
|
||||
return {
|
||||
type: 'mapnik',
|
||||
id: layerId,
|
||||
options: {
|
||||
sql: 'select * from populated_places_simple_reduced where cartodb_id % 3 = ' + mod,
|
||||
cartocss: '#layer { marker-fill: ' + color + '; }',
|
||||
cartocss_version: '2.3.0'
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function createTemplate(scenario) {
|
||||
return {
|
||||
version: '0.0.1',
|
||||
name: scenario.name,
|
||||
auth: {
|
||||
method: 'open'
|
||||
},
|
||||
view: {
|
||||
bounds: {
|
||||
west: 0,
|
||||
south: 0,
|
||||
east: 45,
|
||||
north: 45
|
||||
},
|
||||
zoom: 4,
|
||||
center: {
|
||||
lng: 40,
|
||||
lat: 20
|
||||
},
|
||||
preview_layers: scenario.layerPerview
|
||||
},
|
||||
layergroup: {
|
||||
layers: scenario.layers
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
afterEach(function (done) {
|
||||
test_helper.deleteRedisKeys({
|
||||
'user:localhost:mapviews:global': 5
|
||||
}, done);
|
||||
});
|
||||
|
||||
function previewFixture(version) {
|
||||
return './test/fixtures/previews/populated_places_simple_reduced-' + version + '.png';
|
||||
}
|
||||
|
||||
var threeLayerPointDistintColor = [
|
||||
createLayer('red'),
|
||||
createLayer('orange'),
|
||||
createLayer('blue', 'layer2')
|
||||
];
|
||||
|
||||
var scenarios = [{
|
||||
name: 'preview_layers_red',
|
||||
layerPerview: {
|
||||
'0': true,
|
||||
'1': false,
|
||||
'layer2': false
|
||||
},
|
||||
layers: threeLayerPointDistintColor
|
||||
}, {
|
||||
name: 'preview_layers_orange',
|
||||
layerPerview: {
|
||||
'0': false,
|
||||
'1': true,
|
||||
'layer2': false
|
||||
},
|
||||
layers: threeLayerPointDistintColor
|
||||
}, {
|
||||
name: 'preview_layers_blue',
|
||||
layerPerview: {
|
||||
'0': false,
|
||||
'1': false,
|
||||
'layer2': true
|
||||
},
|
||||
layers: threeLayerPointDistintColor
|
||||
}, {
|
||||
name: 'preview_layers_red_orange',
|
||||
layerPerview: {
|
||||
'0': true,
|
||||
'1': true,
|
||||
'layer2': false
|
||||
},
|
||||
layers: threeLayerPointDistintColor
|
||||
}, {
|
||||
name: 'preview_layers_red_blue',
|
||||
layerPerview: {
|
||||
'0': true,
|
||||
'1': false,
|
||||
'layer2': true
|
||||
},
|
||||
layers: threeLayerPointDistintColor
|
||||
}, {
|
||||
name: 'preview_layers_orange_blue',
|
||||
layerPerview: {
|
||||
'0': false,
|
||||
'1': true,
|
||||
'layer2': true
|
||||
},
|
||||
layers: threeLayerPointDistintColor
|
||||
}, {
|
||||
name: 'preview_layers_red_orange_blue',
|
||||
layerPerview: {
|
||||
'0': true,
|
||||
'1': true,
|
||||
'layer2': true
|
||||
},
|
||||
layers: threeLayerPointDistintColor
|
||||
}, {
|
||||
name: 'preview_layers_all',
|
||||
layerPerview: {},
|
||||
layers: threeLayerPointDistintColor
|
||||
}, {
|
||||
name: 'preview_layers_undefined',
|
||||
layerPerview: undefined,
|
||||
layers: threeLayerPointDistintColor
|
||||
}];
|
||||
|
||||
scenarios.forEach(function (scenario) {
|
||||
it('should filter layers for template: ' + scenario.name, function (done) {
|
||||
step(
|
||||
function addTemplate () {
|
||||
var next = this;
|
||||
var template = createTemplate(scenario);
|
||||
|
||||
templateMaps.addTemplate(username, template, next);
|
||||
},
|
||||
function requestPreview (err) {
|
||||
assert.ifError(err);
|
||||
|
||||
var next = this;
|
||||
|
||||
assert.response(server, {
|
||||
url: '/api/v1/map/static/named/' + scenario.name + '/640/480.png',
|
||||
method: 'GET',
|
||||
headers: {
|
||||
host: 'localhost'
|
||||
},
|
||||
encoding: 'binary'
|
||||
}, {
|
||||
status: 200,
|
||||
headers: {
|
||||
'content-type': 'image/png'
|
||||
}
|
||||
}, function (res, err) {
|
||||
next(err, res);
|
||||
});
|
||||
},
|
||||
function checkPreview (err, res) {
|
||||
assert.ifError(err);
|
||||
|
||||
var next = this;
|
||||
var img = mapnik.Image.fromBytes(new Buffer(res.body, 'binary'));
|
||||
var previewFixturePath = previewFixture(scenario.name);
|
||||
|
||||
assert.imageIsSimilarToFile(img, previewFixturePath, IMAGE_TOLERANCE, next);
|
||||
},
|
||||
function deleteTemplate(err) {
|
||||
assert.ifError(err);
|
||||
|
||||
var next = this;
|
||||
|
||||
templateMaps.delTemplate(username, scenario.name, next);
|
||||
},
|
||||
function finish (err) {
|
||||
done(err);
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
@@ -50,14 +50,14 @@ describe('turbo-carto error cases', function() {
|
||||
});
|
||||
|
||||
it('should return invalid number of ramp error', function(done) {
|
||||
this.testClient = new TestClient(makeMapconfig('ramp([pop_max], (8,24,96), (8,24,96,128))'));
|
||||
this.testClient = new TestClient(makeMapconfig('ramp([pop_max], 8, 96, 3, (8,24,96,128))'));
|
||||
this.testClient.getLayergroup(ERROR_RESPONSE, function(err, layergroup) {
|
||||
assert.ok(!err, err);
|
||||
|
||||
assert.ok(layergroup.hasOwnProperty('errors'));
|
||||
assert.equal(layergroup.errors.length, 1);
|
||||
assert.ok(layergroup.errors[0].match(/^Failed\sto\sprocess/));
|
||||
assert.ok(layergroup.errors[0].match(/invalid\sramp\slength/i));
|
||||
assert.ok(layergroup.errors[0].match(/^Failed\sto\sprocess/), layergroup.errors[0]);
|
||||
assert.ok(layergroup.errors[0].match(/invalid\sramp\slength/i), layergroup.errors[0]);
|
||||
|
||||
done();
|
||||
});
|
||||
@@ -287,4 +287,97 @@ describe('turbo-carto regressions', function() {
|
||||
});
|
||||
});
|
||||
|
||||
var scenarios = [
|
||||
{
|
||||
desc: 'numeric datasource',
|
||||
cartocss: [
|
||||
"#points {",
|
||||
" marker-fill: ramp([scalerank], colorbrewer(Reds), category);",
|
||||
"}"
|
||||
].join('\n'),
|
||||
expected: [
|
||||
'#points {',
|
||||
' marker-fill: #fee5d9;',
|
||||
' [ scalerank = 6 ] {',
|
||||
' marker-fill: #fcae91',
|
||||
' }',
|
||||
' [ scalerank = 8 ] {',
|
||||
' marker-fill: #fb6a4a',
|
||||
' }',
|
||||
' [ scalerank = 4 ] {',
|
||||
' marker-fill: #de2d26',
|
||||
' }',
|
||||
' [ scalerank = 10 ] {',
|
||||
' marker-fill: #a50f15',
|
||||
' }',
|
||||
'}',
|
||||
].join('\n')
|
||||
},
|
||||
{
|
||||
desc: 'string datasource',
|
||||
cartocss: [
|
||||
"#points {",
|
||||
" marker-fill: ramp([adm0name], colorbrewer(Reds), category);",
|
||||
"}"
|
||||
].join('\n'),
|
||||
expected: [
|
||||
'#points {',
|
||||
' marker-fill: #fee5d9;',
|
||||
' [ adm0name = "Russia" ] {',
|
||||
' marker-fill: #fcae91',
|
||||
' }',
|
||||
' [ adm0name = "China" ] {',
|
||||
' marker-fill: #fb6a4a',
|
||||
' }',
|
||||
' [ adm0name = "Brazil" ] {',
|
||||
' marker-fill: #de2d26',
|
||||
' }',
|
||||
' [ adm0name = "Canada" ] {',
|
||||
' marker-fill: #a50f15',
|
||||
' }',
|
||||
'}',
|
||||
].join('\n')
|
||||
},
|
||||
{
|
||||
desc: 'numeric manual',
|
||||
cartocss: [
|
||||
"#points {",
|
||||
" marker-fill: ramp([scalerank], colorbrewer(Reds), (-1, 6, 8, 4, 10), category);",
|
||||
"}"
|
||||
].join('\n'),
|
||||
expected: [
|
||||
'#points {',
|
||||
' marker-fill: #fee5d9;',
|
||||
' [ scalerank = 6 ] {',
|
||||
' marker-fill: #fcae91',
|
||||
' }',
|
||||
' [ scalerank = 8 ] {',
|
||||
' marker-fill: #fb6a4a',
|
||||
' }',
|
||||
' [ scalerank = 4 ] {',
|
||||
' marker-fill: #de2d26',
|
||||
' }',
|
||||
' [ scalerank = 10 ] {',
|
||||
' marker-fill: #a50f15',
|
||||
' }',
|
||||
'}',
|
||||
].join('\n')
|
||||
}
|
||||
];
|
||||
|
||||
scenarios.forEach(function(scenario) {
|
||||
it('category ramps should use original type: ' + scenario.desc, function(done) {
|
||||
var mapConfig = makeMapconfig('SELECT * FROM populated_places_simple_reduced', scenario.cartocss);
|
||||
this.testClient = new TestClient(mapConfig);
|
||||
this.testClient.getLayergroup(function(err, layergroup) {
|
||||
assert.ok(!err, err);
|
||||
|
||||
assert.ok(layergroup.hasOwnProperty('layergroupid'));
|
||||
assert.deepEqual(layergroup.metadata.layers[0].meta.cartocss, scenario.expected);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 44 KiB |
BIN
test/fixtures/previews/populated_places_simple_reduced-preview_layers_all.png
vendored
Normal file
|
After Width: | Height: | Size: 59 KiB |
BIN
test/fixtures/previews/populated_places_simple_reduced-preview_layers_blue.png
vendored
Normal file
|
After Width: | Height: | Size: 28 KiB |
BIN
test/fixtures/previews/populated_places_simple_reduced-preview_layers_orange.png
vendored
Normal file
|
After Width: | Height: | Size: 33 KiB |
BIN
test/fixtures/previews/populated_places_simple_reduced-preview_layers_orange_blue.png
vendored
Normal file
|
After Width: | Height: | Size: 48 KiB |
BIN
test/fixtures/previews/populated_places_simple_reduced-preview_layers_red.png
vendored
Normal file
|
After Width: | Height: | Size: 28 KiB |
BIN
test/fixtures/previews/populated_places_simple_reduced-preview_layers_red_blue.png
vendored
Normal file
|
After Width: | Height: | Size: 44 KiB |
BIN
test/fixtures/previews/populated_places_simple_reduced-preview_layers_red_orange.png
vendored
Normal file
|
After Width: | Height: | Size: 48 KiB |
BIN
test/fixtures/previews/populated_places_simple_reduced-preview_layers_red_orange_blue.png
vendored
Normal file
|
After Width: | Height: | Size: 59 KiB |
BIN
test/fixtures/previews/populated_places_simple_reduced-preview_layers_undefined.png
vendored
Normal file
|
After Width: | Height: | Size: 59 KiB |
167
test/integration/analysis-backend-limits.js
Normal file
@@ -0,0 +1,167 @@
|
||||
var testHelper = require('../support/test_helper');
|
||||
|
||||
var assert = require('assert');
|
||||
var redis = require('redis');
|
||||
|
||||
var RedisPool = require('redis-mpool');
|
||||
var cartodbRedis = require('cartodb-redis');
|
||||
|
||||
var AnalysisBackend = require('../../lib/cartodb/backends/analysis');
|
||||
|
||||
describe('analysis-backend limits', function() {
|
||||
|
||||
var redisClient;
|
||||
var keysToDelete;
|
||||
var user = 'localhost';
|
||||
|
||||
beforeEach(function() {
|
||||
redisClient = redis.createClient(global.environment.redis.port);
|
||||
keysToDelete = {};
|
||||
var redisPool = new RedisPool(global.environment.redis);
|
||||
this.metadataBackend = cartodbRedis({pool: redisPool});
|
||||
});
|
||||
|
||||
afterEach(function(done) {
|
||||
redisClient.quit(function() {
|
||||
testHelper.deleteRedisKeys(keysToDelete, done);
|
||||
});
|
||||
});
|
||||
|
||||
function withAnalysesLimits(limits, callback) {
|
||||
redisClient.SELECT(5, function(err) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
var analysesLimitsKey = 'limits:analyses:' + user;
|
||||
redisClient.HMSET([analysesLimitsKey].concat(limits), function(err) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
keysToDelete[analysesLimitsKey] = 5;
|
||||
return callback();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
it("should use limits from configuration", function(done) {
|
||||
var analysisBackend = new AnalysisBackend(this.metadataBackend, {
|
||||
limits: {
|
||||
moran: { timeout: 5000 },
|
||||
kmeans: { timeout: 5000 }
|
||||
}
|
||||
});
|
||||
analysisBackend.getAnalysesLimits(user, function(err, result) {
|
||||
assert.ok(!err, err);
|
||||
|
||||
assert.ok(result.analyses.moran);
|
||||
assert.equal(result.analyses.moran.timeout, 5000);
|
||||
|
||||
assert.ok(result.analyses.kmeans);
|
||||
assert.equal(result.analyses.kmeans.timeout, 5000);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it("should use limits from redis", function(done) {
|
||||
var self = this;
|
||||
var limits = ['moran', 5000];
|
||||
|
||||
withAnalysesLimits(limits, function(err) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
|
||||
var analysisBackend = new AnalysisBackend(self.metadataBackend);
|
||||
analysisBackend.getAnalysesLimits(user, function(err, result) {
|
||||
assert.ok(!err, err);
|
||||
|
||||
assert.ok(result.analyses.moran);
|
||||
assert.equal(result.analyses.moran.timeout, 5000);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("should use limits from redis and configuration, redis takes priority", function(done) {
|
||||
var self = this;
|
||||
var limits = ['moran', 5000];
|
||||
|
||||
withAnalysesLimits(limits, function(err) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
|
||||
var analysisBackend = new AnalysisBackend(self.metadataBackend, {
|
||||
limits: {
|
||||
moran: { timeout: 1000 }
|
||||
}
|
||||
});
|
||||
analysisBackend.getAnalysesLimits(user, function(err, result) {
|
||||
assert.ok(!err, err);
|
||||
|
||||
assert.ok(result.analyses.moran);
|
||||
assert.equal(result.analyses.moran.timeout, 5000);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("should use limits from redis and configuration, defaulting for values not present in redis", function(done) {
|
||||
var self = this;
|
||||
var limits = ['moran', 5000];
|
||||
|
||||
withAnalysesLimits(limits, function(err) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
|
||||
var analysisBackend = new AnalysisBackend(self.metadataBackend, {
|
||||
limits: {
|
||||
moran: { timeout: 1000 },
|
||||
kmeans: { timeout: 1000 }
|
||||
}
|
||||
});
|
||||
analysisBackend.getAnalysesLimits(user, function(err, result) {
|
||||
assert.ok(!err, err);
|
||||
|
||||
assert.ok(result.analyses.moran);
|
||||
assert.equal(result.analyses.moran.timeout, 5000);
|
||||
|
||||
assert.ok(result.analyses.kmeans);
|
||||
assert.equal(result.analyses.kmeans.timeout, 1000);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("should allow to set other limits per analysis via configuration, and keep timeout from redis", function(done) {
|
||||
var self = this;
|
||||
var limits = ['aggregate-intersection', 5000];
|
||||
|
||||
withAnalysesLimits(limits, function(err) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
|
||||
var analysisBackend = new AnalysisBackend(self.metadataBackend, {
|
||||
limits: {
|
||||
'aggregate-intersection': { timeout: 10000, maxNumberOfRows: 1e5 }
|
||||
}
|
||||
});
|
||||
analysisBackend.getAnalysesLimits(user, function(err, result) {
|
||||
assert.ok(!err, err);
|
||||
|
||||
assert.ok(result.analyses['aggregate-intersection']);
|
||||
assert.equal(result.analyses['aggregate-intersection'].timeout, 5000);
|
||||
assert.equal(result.analyses['aggregate-intersection'].maxNumberOfRows, 1e5);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
@@ -75,7 +75,7 @@ if test x"$PREPARE_PGSQL" = xyes; then
|
||||
dropdb "${TEST_DB}"
|
||||
createdb -Ttemplate_postgis -EUTF8 "${TEST_DB}" || die "Could not create test database"
|
||||
|
||||
LOCAL_SQL_SCRIPTS='analysis_catalog windshaft.test gadm4 ported/populated_places_simple_reduced'
|
||||
LOCAL_SQL_SCRIPTS='analysis_catalog windshaft.test gadm4 ported/populated_places_simple_reduced cdb_analysis_check'
|
||||
REMOTE_SQL_SCRIPTS='CDB_QueryStatements CDB_QueryTables CDB_CartodbfyTable CDB_TableMetadata CDB_ForeignTable CDB_UserTables CDB_ColumnNames CDB_ZoomFromScale CDB_OverviewsSupport CDB_Overviews CDB_QuantileBins CDB_JenksBins CDB_HeadsTailsBins CDB_EqualIntervalBins CDB_Hexagon CDB_XYZ'
|
||||
|
||||
CURL_ARGS=""
|
||||
|
||||
@@ -26,5 +26,7 @@ CREATE TABLE IF NOT EXISTS
|
||||
-- last job modifying the node
|
||||
last_modified_by uuid,
|
||||
-- store error message for failures
|
||||
last_error_message text
|
||||
last_error_message text,
|
||||
-- cached tables involved in the analysis
|
||||
cache_tables regclass[] NOT NULL DEFAULT '{}'
|
||||
);
|
||||
|
||||
6
test/support/sql/cdb_analysis_check.sql
Normal file
@@ -0,0 +1,6 @@
|
||||
CREATE OR REPLACE FUNCTION CDB_CheckAnalysisQuota(table_name TEXT)
|
||||
RETURNS void AS
|
||||
$$
|
||||
BEGIN
|
||||
END;
|
||||
$$ LANGUAGE PLPGSQL;
|
||||
@@ -634,7 +634,8 @@ GRANT SELECT ON TABLE analysis_rent_listings TO :PUBLICUSER;
|
||||
--
|
||||
GRANT SELECT, UPDATE, INSERT, DELETE ON cdb_analysis_catalog TO :TESTUSER;
|
||||
|
||||
create schema cdb_crankshaft;
|
||||
DROP EXTENSION IF EXISTS crankshaft;
|
||||
CREATE SCHEMA IF NOT EXISTS cdb_crankshaft;
|
||||
GRANT USAGE ON SCHEMA cdb_crankshaft TO :TESTUSER;
|
||||
CREATE TYPE kmeans_type as (cartodb_id numeric, cluster_no numeric);
|
||||
CREATE OR REPLACE FUNCTION cdb_crankshaft.CDB_KMeans(query text, no_clusters integer,no_init integer default 20)
|
||||
|
||||