Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bd6a439e0b | ||
|
|
c90dd4902f | ||
|
|
868e5c45fe | ||
|
|
7e1852ee88 | ||
|
|
5ae98bc7c9 | ||
|
|
1624e7de7d | ||
|
|
7a98f84890 | ||
|
|
9b181280d4 | ||
|
|
38e94b9e43 | ||
|
|
dc25bc6f4d | ||
|
|
f64a205603 | ||
|
|
a8480f5492 | ||
|
|
0acf26b37c | ||
|
|
2c068a1a15 | ||
|
|
b961c95121 | ||
|
|
82693211f0 | ||
|
|
e5467462cb | ||
|
|
ab5a4102cd | ||
|
|
d5ef937b31 | ||
|
|
bce02e3f51 | ||
|
|
aad9744d85 | ||
|
|
506b781f3a | ||
|
|
267fd5b9bc | ||
|
|
c362f2869e | ||
|
|
4f7f437527 | ||
|
|
ab5da5ba28 | ||
|
|
7531bb8942 | ||
|
|
811ee8ccdc | ||
|
|
51cb485ce3 | ||
|
|
83d08b8b8f | ||
|
|
ed3d44d591 | ||
|
|
895fe23203 | ||
|
|
af04a560c8 | ||
|
|
9124a17e86 | ||
|
|
99b0d4c111 | ||
|
|
84b98c234f | ||
|
|
bcc1428ebf | ||
|
|
2133056c04 | ||
|
|
4155a9d7f9 | ||
|
|
ed4825523c | ||
|
|
fdbb2bbdab | ||
|
|
c064d6d847 | ||
|
|
d33874bd3d | ||
|
|
96d32dd11f | ||
|
|
d6bc354ff3 | ||
|
|
7325a4fb4b | ||
|
|
90f00c5b29 | ||
|
|
8539c423ea | ||
|
|
e9bfbfce84 | ||
|
|
6e4f0664cb | ||
|
|
3c920c9d94 | ||
|
|
15b67b2c6c | ||
|
|
973537fd9a | ||
|
|
d70a74479d | ||
|
|
946e4b750a | ||
|
|
9789e3fb9b | ||
|
|
6a15679d87 | ||
|
|
ad1cd5577c | ||
|
|
55668ca621 | ||
|
|
0c221a28d1 | ||
|
|
a475551b23 | ||
|
|
bad7676414 | ||
|
|
51c0470f0b | ||
|
|
4530047c76 | ||
|
|
1bf83c3bf7 | ||
|
|
bb6ab11001 | ||
|
|
e4bd1884d3 | ||
|
|
4014a48f7d | ||
|
|
97ded32415 | ||
|
|
593ac081f0 | ||
|
|
69f0a4e1cb | ||
|
|
757e7de60c | ||
|
|
1d7d5469a9 | ||
|
|
98afc3e590 | ||
|
|
ea189790f1 | ||
|
|
62987077fa | ||
|
|
3b9f7cb3f1 | ||
|
|
5882c7e344 | ||
|
|
77b6e2cd2e | ||
|
|
88b1f956c7 | ||
|
|
d9b49ca2bc | ||
|
|
4156ad5a30 | ||
|
|
ae46561648 | ||
|
|
1700a807e9 | ||
|
|
0bab15b213 | ||
|
|
38d3075554 | ||
|
|
1b124bfb87 | ||
|
|
cdf4dd0302 | ||
|
|
a13bed2db6 | ||
|
|
26318f94fe | ||
|
|
769fb0820f | ||
|
|
52380534f3 | ||
|
|
2fd2526046 | ||
|
|
61509bbd44 | ||
|
|
76499afd8d | ||
|
|
4023f328f7 | ||
|
|
4f49cb555b | ||
|
|
4dc959a3e4 | ||
|
|
518fbf562c | ||
|
|
49828d3d9d | ||
|
|
248e6a7b05 | ||
|
|
5561e6b770 | ||
|
|
ab083b86f3 | ||
|
|
4bf525222a | ||
|
|
45efcb381c | ||
|
|
07a7736c71 | ||
|
|
d2826ab7af | ||
|
|
6ab769f382 |
@@ -6,7 +6,7 @@ engines:
|
||||
eslint:
|
||||
enabled: true
|
||||
config:
|
||||
config: caravel/assets/.eslintrc
|
||||
config: superset/assets/.eslintrc
|
||||
pep8:
|
||||
enabled: true
|
||||
fixme:
|
||||
@@ -19,17 +19,18 @@ engines:
|
||||
ratings:
|
||||
paths:
|
||||
- "**.py"
|
||||
- "caravel/assets/**.js"
|
||||
- "caravel/assets/**.jsx"
|
||||
- "superset/assets/**.js"
|
||||
- "superset/assets/**.jsx"
|
||||
exclude_paths:
|
||||
- ".*"
|
||||
- "**.pyc"
|
||||
- "**.gz"
|
||||
- "env/"
|
||||
- "tests/"
|
||||
- "caravel/assets/images/"
|
||||
- "caravel/assets/vendor/"
|
||||
- "caravel/assets/node_modules/"
|
||||
- "caravel/assets/javascripts/dist/"
|
||||
- "caravel/migrations"
|
||||
- "superset/ascii_art.py"
|
||||
- "superset/assets/images/"
|
||||
- "superset/assets/vendor/"
|
||||
- "superset/assets/node_modules/"
|
||||
- "superset/assets/javascripts/dist/"
|
||||
- "superset/migrations"
|
||||
- "docs/"
|
||||
|
||||
@@ -1,22 +1,25 @@
|
||||
*.pyc
|
||||
caravel/assets/coverage/*
|
||||
yarn-error.log
|
||||
_modules
|
||||
superset/assets/coverage/*
|
||||
changelog.sh
|
||||
.DS_Store
|
||||
.coverage
|
||||
_build
|
||||
_static
|
||||
_images
|
||||
caravel/bin/caravelc
|
||||
_modules
|
||||
superset/bin/supersetc
|
||||
env_py3
|
||||
.eggs
|
||||
build
|
||||
*.db
|
||||
tmp
|
||||
caravel_config.py
|
||||
superset_config.py
|
||||
local_config.py
|
||||
env
|
||||
dist
|
||||
caravel.egg-info/
|
||||
superset.egg-info/
|
||||
app.db
|
||||
*.bak
|
||||
.idea
|
||||
@@ -27,3 +30,4 @@ app.db
|
||||
*.js.map
|
||||
node_modules
|
||||
npm-debug.log
|
||||
yarn.lock
|
||||
|
||||
@@ -16,8 +16,8 @@ pep8:
|
||||
full: true
|
||||
ignore-paths:
|
||||
- docs
|
||||
- caravel/migrations/env.py
|
||||
- caravel/ascii_art.py
|
||||
- superset/migrations/env.py
|
||||
- superset/ascii_art.py
|
||||
ignore-patterns:
|
||||
- ^example/doc_.*\.py$
|
||||
- (^|/)docs(/|$)
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
[pycodestyle]
|
||||
max-line-length = 90
|
||||
@@ -24,10 +24,10 @@ env:
|
||||
before_install:
|
||||
- npm install -g npm@'>=3.9.5'
|
||||
before_script:
|
||||
- mysql -e 'drop database if exists caravel; create database caravel DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci' -u root
|
||||
- mysql -e 'drop database if exists superset; create database superset DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci' -u root
|
||||
- mysql -u root -e "CREATE USER 'mysqluser'@'localhost' IDENTIFIED BY 'mysqluserpassword';"
|
||||
- mysql -u root -e "GRANT ALL ON caravel.* TO 'mysqluser'@'localhost';"
|
||||
- psql -c 'create database caravel;' -U postgres
|
||||
- mysql -u root -e "GRANT ALL ON superset.* TO 'mysqluser'@'localhost';"
|
||||
- psql -c 'create database superset;' -U postgres
|
||||
- psql -c "CREATE USER postgresuser WITH PASSWORD 'pguserpassword';" -U postgres
|
||||
- export PATH=${PATH}:/tmp/hive/bin
|
||||
install:
|
||||
|
||||
@@ -1,5 +1,177 @@
|
||||
## Change Log
|
||||
|
||||
### 0.13.2 (2016/11/16 00:23 +00:00)
|
||||
- [895fe23](https://github.com/airbnb/superset/commit/895fe23203a85a4590f84625507849ce63d69f30) v0.13.2 (@mistercrunch)
|
||||
- [af04a56](https://github.com/airbnb/superset/commit/af04a560c887ecbcee40b53c358ee9c2ad2f44ad) Moved check to the correct place. (#1606) (@edevil)
|
||||
- [9124a17](https://github.com/airbnb/superset/commit/9124a17e864b8b2eb109af33fe1b8aad809069da) Removing ascii_art.p from code coverage analysis (@mistercrunch)
|
||||
- [99b0d4c](https://github.com/airbnb/superset/commit/99b0d4c111b66f6da0eb9991b54b375e2fbeecc4) Fix MySql time grain issue (#1590) (@mistercrunch)
|
||||
- [84b98c2](https://github.com/airbnb/superset/commit/84b98c234f852550ecf536e4a6e7ce2d7ebc5df6) Adding Greenplum to supported dbs (@mistercrunch)
|
||||
- [bcc1428](https://github.com/airbnb/superset/commit/bcc1428ebf1cf7e83c93e351858bee3cfbb2e9c2) Updating CODECLIMATE_REPO_TOKEN to new location (@mistercrunch)
|
||||
- [2133056](https://github.com/airbnb/superset/commit/2133056c04d20807ea0c503d0fed235ee20e94bb) Added different Select Fields (#1583) (@vera-liu)
|
||||
- [4155a9d](https://github.com/airbnb/superset/commit/4155a9d7f996d09ebdfc8df0db3dcbe9ccf9b529) Removing broken link to old docker image (#1591) (@kingo55)
|
||||
- [ed48255](https://github.com/airbnb/superset/commit/ed4825523ca54309272f044826f383c2606456a1) Fixed a bug with new dashboard (#1585) (@vera-liu)
|
||||
- [fdbb2bb](https://github.com/airbnb/superset/commit/fdbb2bbdab5e5dbb2f3496b67eb227dc3dc5f2a7) fixing the build (@mistercrunch)
|
||||
- [c064d6d](https://github.com/airbnb/superset/commit/c064d6d8475b07a63a3b5ca7b4dbd248a437f6ed) Correct part_fields variable name (#1586) (@geraneum)
|
||||
- [d33874b](https://github.com/airbnb/superset/commit/d33874bd3d9ffffca7f4726a29c3eb9de2a68d42) [hotfix] postgres issue when slice_id is missing (@mistercrunch)
|
||||
- [96d32dd](https://github.com/airbnb/superset/commit/96d32dd11f29afa3590b79d8683aaddd05f48a02) Improve Druid metadata fetching resilience (#1584) (@mistercrunch)
|
||||
- [d6bc354](https://github.com/airbnb/superset/commit/d6bc354ff3e2f24aeb459dbc1413371f2b072306) [hotfix] fix support for presto DATE and TIMESTAMP type (@mistercrunch)
|
||||
- [7325a4f](https://github.com/airbnb/superset/commit/7325a4fb4ba08f554454534fe9efe3d0eea5a6ce) [hotfix] table view not group by without orderby fails (@mistercrunch)
|
||||
- [90f00c5](https://github.com/airbnb/superset/commit/90f00c5b292ff83802d35bac49a26e6b257de409) Minor documentation touchups (@mistercrunch)
|
||||
|
||||
### 0.13.1 (2016/11/10 18:01 +00:00)
|
||||
- [8539c42](https://github.com/airbnb/superset/commit/8539c423ea61d84e8e0a81317275713103f99a8a) v0.13.1 (@mistercrunch)
|
||||
- [e9bfbfc](https://github.com/airbnb/superset/commit/e9bfbfce84b5ab851c839c70adf5298b2538e9dc) Removing boat pic from README (@mistercrunch)
|
||||
- [6e4f066](https://github.com/airbnb/superset/commit/6e4f0664cb49d5e7144dadba7ccda548cf58e905) [hotfix] lint (@mistercrunch)
|
||||
- [3c920c9](https://github.com/airbnb/superset/commit/3c920c9d943540cc8ed0d6e3dfd2ae0eba3acb70) [hotfix] datatables import issues (@mistercrunch)
|
||||
- [15b67b2](https://github.com/airbnb/superset/commit/15b67b2c6c3c2982f6620fce5d30bd05951458f7) [WiP] rename project from Caravel to Superset (#1576) (@mistercrunch)
|
||||
|
||||
### 0.13.0 (2016/11/10 05:37 +00:00)
|
||||
- [973537f](https://github.com/airbnb/superset/commit/973537fd9a60766a6ee99bd2e7080aa7db21f540) [hotfix] resizing widgets (@mistercrunch)
|
||||
- [d70a744](https://github.com/airbnb/superset/commit/d70a74479df87908de7a7b4df7c24c6b267bf9e3) Make Sqllab a one-page app -- body not scrollable (#1551) (@vera-liu)
|
||||
- [946e4b7](https://github.com/airbnb/superset/commit/946e4b750afeebbfa16e6ce7e9fc61575136b237) Reactifying the dashboard (#1572) (@mistercrunch)
|
||||
- [9789e3f](https://github.com/airbnb/superset/commit/9789e3fb9b658e1f38080915132ae43c541e68e9) Bind data preview tabs to sql editor (#1573) (@vera-liu)
|
||||
- [6a15679](https://github.com/airbnb/superset/commit/6a15679d876c5c76d177b624a5d69da80ac75a3f) [hotfix] encode csv to utf-8 (@mistercrunch)
|
||||
- [ad1cd55](https://github.com/airbnb/superset/commit/ad1cd5577c231e4100f5a214fe7a4d372de96a04) Pass values from global store to fields in exploreV2 (#1561) (@vera-liu)
|
||||
- [55668ca](https://github.com/airbnb/superset/commit/55668ca6217f52924288f3849a0aa54c28d40ce1) Link to database-urls in databaseadd view (#1480) (@dirkkelly)
|
||||
- [0c221a2](https://github.com/airbnb/superset/commit/0c221a28d10abc8cf4e929e50fd788a56136a9cf) add slice_name and table_name for title (#1567) (@ascott)
|
||||
- [a475551](https://github.com/airbnb/superset/commit/a475551b23d5830ab2945f615328f31d48df36ca) [sqllab] bind alt+enter shortcut in AceEditor to run a query (#1554) (@mistercrunch)
|
||||
- [bad7676](https://github.com/airbnb/superset/commit/bad7676414662b28a4b72eb680fbf42a5c6281a5) Bump cryptography dependency to 1.5.3 (#1569) (@xrmx)
|
||||
|
||||
### airbnb_prod.0.12.0.1 (2016/11/08 23:55 +00:00)
|
||||
- [51c0470](https://github.com/airbnb/superset/commit/51c0470f0be438312e90f2efb1f2e37291a30ce4) [explore v2] populate dynamic select field options (#1543) (@ascott)
|
||||
- [4530047](https://github.com/airbnb/superset/commit/4530047c769ba6d5953ef1547b8507c62f657942) Added action buttons to Chart Container of explore V2 (#1562) (@vera-liu)
|
||||
- [1bf83c3](https://github.com/airbnb/superset/commit/1bf83c3bf78de422df1b21c3049d106b1cb29385) [explore-v2] render columns based on length of fieldSets array (#1559) (@ascott)
|
||||
- [bb6ab11](https://github.com/airbnb/superset/commit/bb6ab110013f2e29933fb9a70d6891d4486eb49d) Vliu link form data explore v2 (#1540) (@vera-liu)
|
||||
- [e4bd188](https://github.com/airbnb/superset/commit/e4bd1884d34a1949b986410ebc385b75945afff8) [druid] adding support for dimensionspecs (#1545) (@mistercrunch)
|
||||
- [4014a48](https://github.com/airbnb/superset/commit/4014a48f7df9fe166c77132f3d8d83cf615ac176) Added cache prop to ResultSet (#1552) (@vera-liu)
|
||||
- [97ded32](https://github.com/airbnb/superset/commit/97ded32415e9e32ba2c6a7e4556a0ed96034244a) Update linting instructions. (#1478) (@pinkythalli97)
|
||||
- [593ac08](https://github.com/airbnb/superset/commit/593ac081f06af5e95dc784597473d42aea52cf28) Added scroll bar and option to collapse for Sql Editor tool bar (#1532) (@vera-liu)
|
||||
- [69f0a4e](https://github.com/airbnb/superset/commit/69f0a4e1cb2c7775617a15a5ba709836a568210c) Put data preview in south pane (#1486) (@vera-liu)
|
||||
- [757e7de](https://github.com/airbnb/superset/commit/757e7de60cf7aa04f15160942ceaadb46daa15b5) add oracle time_grains (#1544) (@gschrader)
|
||||
- [1d7d546](https://github.com/airbnb/superset/commit/1d7d5469a925690d4b4fd1e7a3cdf37779acffb6) [hotfix] remove failing Druid test (@mistercrunch)
|
||||
- [98afc3e](https://github.com/airbnb/superset/commit/98afc3e590ef25d08fecf159bf4411ac292e95f0) Added setFilter(), containerID and getFilter() to (#1360) (@vera-liu)
|
||||
- [ea18979](https://github.com/airbnb/superset/commit/ea189790f160dc6419ac97d8f6740afa62ba6e78) [hotfix] druid dist_bar viz issues with non-str x values (@mistercrunch)
|
||||
- [6298707](https://github.com/airbnb/superset/commit/62987077fa31ebbedafbaa745b36527b7df0c9f7) Read the user origin specification. (#1541) (@bkyryliuk)
|
||||
- [3b9f7cb](https://github.com/airbnb/superset/commit/3b9f7cb3f1d0dd1387d01204259b5ce7f2469793) [hotfix] groupby may be a set (@mistercrunch)
|
||||
- [5882c7e](https://github.com/airbnb/superset/commit/5882c7e3447a06875e63b4424994a0a5e5fc58a5) Added jquery methods to ChartContainer to get world_map viz working in exploreV2 (#1443) (@vera-liu)
|
||||
- [77b6e2c](https://github.com/airbnb/superset/commit/77b6e2cd2e796d063dbc6d42934143bf45bba8b0) Get pivot table working in explore v2 (#1432) (@vera-liu)
|
||||
- [88b1f95](https://github.com/airbnb/superset/commit/88b1f956c7cd8bd457d04648447fb23700b859ae) [explore-v2] handle field overrides (#1535) (@ascott)
|
||||
- [d9b49ca](https://github.com/airbnb/superset/commit/d9b49ca2bc1eab3221fc329d0ab5a9b421746110) [exploreV2] remove /exploreV2 endpoint, add v2 bootstrap data to /explore endpoint (#1536) (@ascott)
|
||||
- [4156ad5](https://github.com/airbnb/superset/commit/4156ad5a3054f5194ea94b6fc146e53e4ccb0b57) [explore-v2] control panel fixes (#1529) (@ascott)
|
||||
- [ae46561](https://github.com/airbnb/superset/commit/ae465616482c8e76634728bcd6990fcfb3b752ad) Support week_ending_saturday for Druid. (#1491) (@bkyryliuk)
|
||||
- [1700a80](https://github.com/airbnb/superset/commit/1700a807e9a8ebc4cfb2749293308be733e42473) [sqllab] templating refactor (#1504) (@mistercrunch)
|
||||
- [0bab15b](https://github.com/airbnb/superset/commit/0bab15b2132ae769ce81519f2d27858ad6d0b187) Update INTHEWILD.md (#1526) (@shashanksingh)
|
||||
- [38d3075](https://github.com/airbnb/superset/commit/38d307555487d84b60dd6169006bbb6aa9fc87a5) [explore V2] render all control panels and fields dynamically for each vis type (#1493) (@ascott)
|
||||
- [1b124bf](https://github.com/airbnb/superset/commit/1b124bfb87862a25a7b904e05929c23f0c61d0e2) [druid] optimize Druid queries where possible (#1517) (@mistercrunch)
|
||||
- [cdf4dd0](https://github.com/airbnb/superset/commit/cdf4dd03024cff40f17845ab9b57ae6a6070e197) Add yearly and quarterly granularities to mysql engine backend (#1518) (@plumbeo)
|
||||
- [a13bed2](https://github.com/airbnb/superset/commit/a13bed2db685a9fa913676bd71b570c81a190c6e) Moved sqllab tests from core_tests to sqllab_tests (#1502) (@vera-liu)
|
||||
- [26318f9](https://github.com/airbnb/superset/commit/26318f94fef888a4b54a022bbbd44f269cfbf9b3) Moved queriesArray from render() to local state (#1505) (@vera-liu)
|
||||
- [769fb08](https://github.com/airbnb/superset/commit/769fb0820fbefd1e58ced40fa79d2d7c923091e4) Strip sql and remove ; for csv download. (#1508) (@bkyryliuk)
|
||||
- [5238053](https://github.com/airbnb/superset/commit/52380534f360cbb0e3d7ab46889066cd2df47440) Moved ajax call for fetching table metadata from SqlEditorLeftBar to actions (#1494) (@vera-liu)
|
||||
- [2fd2526](https://github.com/airbnb/superset/commit/2fd252604689693c957a1b6875a18872a59d5ec8) Add support for jinja templates in WHERE/HAVING clauses (#1442) (@mistercrunch)
|
||||
- [61509bb](https://github.com/airbnb/superset/commit/61509bbd446bbcc21f4f79229c88d82441d1fb98) [sqllab] surfacing more table metadata (indices, pk, fks) (#1485) (@mistercrunch)
|
||||
- [76499af](https://github.com/airbnb/superset/commit/76499afd8d28289cac0cf0f2d7316e6e64bab089) [pep8] allowing 90 chars per line (@mistercrunch)
|
||||
- [4023f32](https://github.com/airbnb/superset/commit/4023f328f7893fbe4b0e0af1612d598e1c931f72) [sqllab] run only the part of the query that is selected (#1479) (@mistercrunch)
|
||||
- [4f49cb5](https://github.com/airbnb/superset/commit/4f49cb555be68c5e1daed817d1137fbd1c3f4e3f) Celery uses separate db engine with NullPool. (#1492) (@bkyryliuk)
|
||||
- [4dc959a](https://github.com/airbnb/superset/commit/4dc959a3e4464ed38d4b5e580ae1fc009178e185) Revert "NullPool for the celery worker." (#1488) (@bkyryliuk)
|
||||
- [518fbf5](https://github.com/airbnb/superset/commit/518fbf562cca638cf14d97dfae92756091630eb2) Minor Fixes (#1484) (@ronbak)
|
||||
- [49828d3](https://github.com/airbnb/superset/commit/49828d3d9d51c02b13ce916a85cfe912bee43c54) add step to pypi build/push (@mistercrunch)
|
||||
- [248e6a7](https://github.com/airbnb/superset/commit/248e6a7b05fd4ef30c4f2c006d0df935da84e052) fix name for postgresql (#1482) (@willgroves)
|
||||
- [5561e6b](https://github.com/airbnb/superset/commit/5561e6b77086ffafb58c363a22e97a5800590e47) Fix celery module import in comments. (#1474) (@bkyryliuk)
|
||||
- [ab083b8](https://github.com/airbnb/superset/commit/ab083b86f35c6f01dfa16bf84c8c212bf21743cf) [sqllab] slide animations when adding/removing/toggling TableElement (#1472) (@mistercrunch)
|
||||
- [4bf5252](https://github.com/airbnb/superset/commit/4bf525222a609320acc28232f25b7651c54cfda0) [sqllab] add autocomplete to AceEditor for table and column names (#1475) (@mistercrunch)
|
||||
- [45efcb3](https://github.com/airbnb/superset/commit/45efcb381c0d0b53b9de72a3437ec980b201bab0) Added time filter to query search page (#1329) (@vera-liu)
|
||||
- [07a7736](https://github.com/airbnb/superset/commit/07a7736c71050c20dc04661ab3f1d21f58cb3b39) NullPool for the celery worker. (#1465) (@bkyryliuk)
|
||||
- [d2826ab](https://github.com/airbnb/superset/commit/d2826ab7af4da1b36816150b71002ed966c553cd) Added checkbox in dist_bar viz to enable sorting of bars based on x axis labels (#1379) (@vera-liu)
|
||||
- [6ab769f](https://github.com/airbnb/superset/commit/6ab769f38227788fbffb1eadb0e43c656f7c1da0) CHANGELOG for 0.12.0 (@mistercrunch)
|
||||
|
||||
### 0.12.0 (2016/10/28 16:40 +00:00)
|
||||
- [3e1cd2b](https://github.com/airbnb/caravel/commit/3e1cd2bdcabce219dc01c6ce7b80850ecd50f9ba) v0.12.0 (@mistercrunch)
|
||||
- [22784b7](https://github.com/airbnb/caravel/commit/22784b7f069d59e3fa7df03cfea84df9e147af13) run_specific_test: take the test as parameter (#1469) (@xrmx)
|
||||
- [c492261](https://github.com/airbnb/caravel/commit/c4922615eb707228cdc1badd49bf2d293c74a699) [sqllab] add column sort feature to TableElement (#1467) (@mistercrunch)
|
||||
- [7307dda](https://github.com/airbnb/caravel/commit/7307ddad3c9b60ec2178286cbc27a302e158de83) Highlight affected slices for filter change in dashboard view (#1439) (@vera-liu)
|
||||
- [c7ba143](https://github.com/airbnb/caravel/commit/c7ba143d039aff61302017329860cd3db432b0f6) Fix typo in sqllab docs (@mistercrunch)
|
||||
- [b242063](https://github.com/airbnb/caravel/commit/b24206387b09946df40a834db2d3b1deae21f865) [sqllab] optimizing React (#1438) (@mistercrunch)
|
||||
- [64d1964](https://github.com/airbnb/caravel/commit/64d196442fcd17f098ba6d71da8bad1f84bdbb5a) Added dashboard standalone page (#1429) (@vera-liu)
|
||||
- [5944643](https://github.com/airbnb/caravel/commit/5944643da67acb1ec38ed8d52f9ae6f58d7549ac) [sqllab] add support for Jinja templating (#1426) (@mistercrunch)
|
||||
- [8c5e495](https://github.com/airbnb/caravel/commit/8c5e4952727d7ff6e96e61c1a38de2e600b02208) Add github issue template (#1436) (@xrmx)
|
||||
- [bb23685](https://github.com/airbnb/caravel/commit/bb23685b9db623be89714153361ee3b03b46a39d) Added average metric AVG() to default metrics (#1413) (@vera-liu)
|
||||
- [940659b](https://github.com/airbnb/caravel/commit/940659bc14ed276a68478b66055356cf3b48ba29) [sqllab] some frontend tests (#1400) (@mistercrunch)
|
||||
- [7c59337](https://github.com/airbnb/caravel/commit/7c5933732bcb199e486ac8c4bf7ecec71013117d) Filter immune slices array stores strings. (#1402) (@bkyryliuk)
|
||||
- [89df2fc](https://github.com/airbnb/caravel/commit/89df2fcf76bdaca34dfef19a9d5bb752a9859c6d) Adjusted top margin of heatmap plot to get it working in V2 (#1361) (@vera-liu)
|
||||
- [174a199](https://github.com/airbnb/caravel/commit/174a199c30ad6c24505092975b814688e8a37292) [hotfix] Query search is unreachable (@mistercrunch)
|
||||
- [6f1e7c3](https://github.com/airbnb/caravel/commit/6f1e7c3016b53cf8d3c0e28dba3538861fe56086) Added url shortner for sharing query link (#1314) (@vera-liu)
|
||||
- [9f81e23](https://github.com/airbnb/caravel/commit/9f81e23f8f0698c94a9f4c34b49cbf3dda1e2e87) Fixed css class not being used by slice container (#1359) (@vera-liu)
|
||||
- [19fab6e](https://github.com/airbnb/caravel/commit/19fab6eea71bbd97974a0ef8dd74451011f3862f) Get table viz work in explore v2: Added d3 format to mock slice (#1353) (@vera-liu)
|
||||
- [63161b1](https://github.com/airbnb/caravel/commit/63161b11c347d5a6d62f7ae7dc91fa3c30b5dc93) [sqllab] proper, quoted, select * on the server side (#1404) (@mistercrunch)
|
||||
- [4f886d6](https://github.com/airbnb/caravel/commit/4f886d65ecc208149cf9b7663492a81df868dcc2) Fix None view_menues in permissions. (#1409) (@bkyryliuk)
|
||||
- [62e0e19](https://github.com/airbnb/caravel/commit/62e0e195e8eaa53afa41a5fec89cc4486a7114a3) [docfix] d3.format docs have moved (@mistercrunch)
|
||||
- [e9d4749](https://github.com/airbnb/caravel/commit/e9d4749f4470aee294f1351baa9422173a2a62c8) [hotfix] sqllab presto (@mistercrunch)
|
||||
- [93f8e7d](https://github.com/airbnb/caravel/commit/93f8e7d8e9b25a1aaf07e2b0c3772c327e523f0e) Fix the js build running out of heap space (#1408) (@mistercrunch)
|
||||
- [3dea6e0](https://github.com/airbnb/caravel/commit/3dea6e0da538ed1c0a7761ccbf97bd8f12ac7f2f) [sqllab] adding more descriptive labels to left panel (#1407) (@mistercrunch)
|
||||
- [6fb3b30](https://github.com/airbnb/caravel/commit/6fb3b305ad23c27d0555ded2ab80820000fdec50) [sqllab] add support for results backends (#1377) (@mistercrunch)
|
||||
- [7dfe891](https://github.com/airbnb/caravel/commit/7dfe891cc1d294bd55982d63c9c1eb8f9e9e4c25) [hotfix] timeseries_limit_metric: Not a valid choice (@mistercrunch)
|
||||
- [5c3966a](https://github.com/airbnb/caravel/commit/5c3966a32d454a2fc7fcabd75c66594662361586) Override the role with perms for given datasources. (#1399) (@bkyryliuk)
|
||||
- [c198535](https://github.com/airbnb/caravel/commit/c198535292869ae1ced6d66d08de536188be7e05) Change slice ids in the position json during dashboard import. (#1380) (@bkyryliuk)
|
||||
- [ece69fb](https://github.com/airbnb/caravel/commit/ece69fbb75086cb8855789881a32c6daca4be483) Fix migration for make creator owners (#1262) (@ShengyaoQian)
|
||||
- [458651f](https://github.com/airbnb/caravel/commit/458651fa3e0098f35412772b622e7c0b63d34299) Add parens for custom where and having (#1337) (@yejianye)
|
||||
- [b2f7081](https://github.com/airbnb/caravel/commit/b2f7081c6f3dde80846493330f1ec1e5fa3a414c) bumping versions of JS packages to latest (#1352) (@mistercrunch)
|
||||
- [c255e89](https://github.com/airbnb/caravel/commit/c255e89219c209b5a5371134fbb9a9b90036ded4) [sqllab] show partition metadata for Presto (#1342) (@mistercrunch)
|
||||
- [2edce5b](https://github.com/airbnb/caravel/commit/2edce5bf8afa7b74fdafd8ab8a2e6394d46e6391) Enable "Run Query in New Tab" in SQL Lab (#1343) (@nickbarnwell)
|
||||
- [8f29944](https://github.com/airbnb/caravel/commit/8f299448ea954f75f576399f6bc113dd8ac1c824) [bugfix] text as subquery fails with 'Series Limit' (#1347) (@mistercrunch)
|
||||
- [ecb951b](https://github.com/airbnb/caravel/commit/ecb951bb7474b9c829d0eef12792f6c146757dba) Specify the metric to order by for Series Limit (#1351) (@mistercrunch)
|
||||
- [0dff6a9](https://github.com/airbnb/caravel/commit/0dff6a9030d8f2ff7a03654258f3b02f01d9d57e) Add quarter time grain to postgresql (#1362) (@xrmx)
|
||||
- [2095095](https://github.com/airbnb/caravel/commit/2095095895d9cf5bc8131fb51fbb4868fae124dc) Fixed big number issue (#1355) (@vera-liu)
|
||||
- [4fc8a17](https://github.com/airbnb/caravel/commit/4fc8a17f2ae18dcc2be778e52b3d3ed7be29e95a) [hotfix] use instead of prod for Travis build, take 3 (@mistercrunch)
|
||||
- [3cb737f](https://github.com/airbnb/caravel/commit/3cb737f8c890b018bd6b3275047a02f79abb7444) [hotfix] use instead of prod for Travis build, take2 (@mistercrunch)
|
||||
- [7449aa8](https://github.com/airbnb/caravel/commit/7449aa813b8b695e6dc3b06eb25004bc2a55614f) [hotfix] use instead of prod for Travis build (@mistercrunch)
|
||||
- [7a3bcc2](https://github.com/airbnb/caravel/commit/7a3bcc227cccdec756404ab6140a6ba0d3882419) [bugfix] NaN issue in Big Number viz (#1346) (@mistercrunch)
|
||||
- [b669a14](https://github.com/airbnb/caravel/commit/b669a140816b950237b7d66b73ac6af392f28d8a) [explore-v2] make chart container work with existing visualization files (#1333) (@ascott)
|
||||
- [9db4cc8](https://github.com/airbnb/caravel/commit/9db4cc8c6d0422f28357dc4283089ee3dde9a08e) add node/npm versions to contributing.md (#1344) (@ascott)
|
||||
- [65c744f](https://github.com/airbnb/caravel/commit/65c744f2424d111e950b385630bd8a13b3fa003e) Fix utc time calculation if provided datetime has tz info (#1287) (@labeneator)
|
||||
- [82bcadf](https://github.com/airbnb/caravel/commit/82bcadf7f829183fbf6feca81dcc6c69f08c358c) Moving 'CSS TEmplates' to the Manage menu category (#1336) (@mistercrunch)
|
||||
- [f0f8478](https://github.com/airbnb/caravel/commit/f0f8478922cbe8e4ac754fddcfd6183032bbb893) Revert "Override the role with perms for give datasources." (#1345) (@bkyryliuk)
|
||||
- [40e7057](https://github.com/airbnb/caravel/commit/40e7057bcec55421275d0121d365a2e4f2d38a29) Override the role with perms for give datasources. (#1335) (@bkyryliuk)
|
||||
- [11a8e35](https://github.com/airbnb/caravel/commit/11a8e3591d281a8cde6fb0ab57ed54ec97035e23) Some dashboard import/export fixes. (#1340) (@bkyryliuk)
|
||||
- [5bea398](https://github.com/airbnb/caravel/commit/5bea3986b26359eb98c62246c34729e2d0cc5a1b) [hotfix] handling json errors in explore view (@mistercrunch)
|
||||
- [89cb726](https://github.com/airbnb/caravel/commit/89cb726284b2a4f2707f26d3004ef9ae59b33970) [hotfix] explore errors are not raise properly 2 (@mistercrunch)
|
||||
- [4e9392d](https://github.com/airbnb/caravel/commit/4e9392d21bf7f43e8189eb4478cf9d737c3616bd) [hotfix] explore errors are not raise properly (@mistercrunch)
|
||||
- [b785d27](https://github.com/airbnb/caravel/commit/b785d27241e9f22dcb6a880414500ce8f9e67eb3) Taking out object spread operator (#1311) (@vera-liu)
|
||||
- [451860a](https://github.com/airbnb/caravel/commit/451860afcab971a0a16b521a7c884d1c4d910986) remove #app styling (#1312) (@ascott)
|
||||
- [4cf4e38](https://github.com/airbnb/caravel/commit/4cf4e3805c4d0a217fa6232ba1d6ee3a68c86ed2) Bugfix when there's only date filter in FilterBox (no groupby) (#1326) (@yejianye)
|
||||
- [ef2670c](https://github.com/airbnb/caravel/commit/ef2670ca32e8ba0a04f9dc7899db55812e2f9b46) Using inheritance scheme to organize db specific code (#1294) (@mistercrunch)
|
||||
- [8626c80](https://github.com/airbnb/caravel/commit/8626c80d3a491b842f8262c5807154903c107747) Stop duplicating datasources (#1321) (@bkyryliuk)
|
||||
- [5cb3cc2](https://github.com/airbnb/caravel/commit/5cb3cc2ed8c8c2f0295da7ecdf8ec33a526acee1) polyfill es2015 in older browsers and for phantomjs (#1323) (@ascott)
|
||||
- [73cd2ea](https://github.com/airbnb/caravel/commit/73cd2ea3b17574f8fef1112aa5e5b39f843882f6) Import / export of the dashboards. (#1197) (@bkyryliuk)
|
||||
- [cd2ab42](https://github.com/airbnb/caravel/commit/cd2ab42abcd2ea5f93dd285321c448c97abf4580) do not overwrite the stored password with the masked password (#1209) (@dennisobrien)
|
||||
- [bf1f5ea](https://github.com/airbnb/caravel/commit/bf1f5ea3de23a62eca4f823474119e4c15270738) [sqllab] use encodeURIComponent for copy query URL (#1317) (@mistercrunch)
|
||||
- [79460ab](https://github.com/airbnb/caravel/commit/79460abdd230b2d53a9f2da15ac5160ba6db400b) [SQLLab] Fix the usage of Redux DevTools Enhancer (#1278) (@zalmoxisus)
|
||||
- [1e6e144](https://github.com/airbnb/caravel/commit/1e6e144d24da38aef0d306889dc3b8f962ca4b32) Fixed viewing dashboards as anonymous (#1320) (@Rapsutin)
|
||||
- [fe66557](https://github.com/airbnb/caravel/commit/fe66557bbb4848feed762e859abcd5884d72d369) [explore-v2] hook up ExploreViewContainer to state and add specs (#1300) (@ascott)
|
||||
- [f8e2ce6](https://github.com/airbnb/caravel/commit/f8e2ce6ff367047405be60d7cbcfd68e4f4521d8) Change status color in tab to match with success (#1247) (@vera-liu)
|
||||
- [1967743](https://github.com/airbnb/caravel/commit/19677438c23a9beab907b5b51d99f54ef62c8f92) Add cascade delete to the 1 to composite relationships. (#1295) (@bkyryliuk)
|
||||
- [9012b11](https://github.com/airbnb/caravel/commit/9012b11101805c9810745f049363223f110bc93e) add ImmutableMultiDict back to views.py (#1298) (@ascott)
|
||||
- [f70d301](https://github.com/airbnb/caravel/commit/f70d301f0d37fe6c4f94cd7d6026a72a56087d34) Refactor the explore view (#1252) (@mistercrunch)
|
||||
- [b7d1f78](https://github.com/airbnb/caravel/commit/b7d1f78f5e38bdcc99c7b6a15645092f5d76ca4a) Put formData in store (#1281) (@vera-liu)
|
||||
- [3384e75](https://github.com/airbnb/caravel/commit/3384e7598ee9e82f8b83ae71e3e5b587c6546443) Fixing explore actions & slice controller interactions (#1292) (@mistercrunch)
|
||||
- [382b8e8](https://github.com/airbnb/caravel/commit/382b8e85da36ed1a8f235775b883e54f5db90ab7) [explore v2] add scrollbar to control panel container (#1284) (@ascott)
|
||||
- [0a3121c](https://github.com/airbnb/caravel/commit/0a3121c2438f74ab1bd3730eac7a11bad0b317f5) [doc] installation, load examples before init (@mistercrunch)
|
||||
- [ecfe1a2](https://github.com/airbnb/caravel/commit/ecfe1a241786bc1fae1d5d3c45b45e21703e33a7) Updated eslinter for object rest spread (#1289) (@vera-liu)
|
||||
- [609ae22](https://github.com/airbnb/caravel/commit/609ae22bdabfb9a9cb99d1a4bfba62367f03a8d5) less number of default workers. (#1206) (@StefanoOrdine)
|
||||
- [94578cb](https://github.com/airbnb/caravel/commit/94578cb6a7e91cb4ca741a8f437636d264b56322) reduce chunk size for countries table (#1279) (@vivo75)
|
||||
- [8a5f050](https://github.com/airbnb/caravel/commit/8a5f050f6cbf2a4eca7f2799a244f94218bb11c1) [explore v2] fix explorev2 chart errors (#1277) (@ascott)
|
||||
- [5c5b393](https://github.com/airbnb/caravel/commit/5c5b393f2fd7be5a4496c0ea0a0808f720fdc904) Change userId, dbId to username and dbname (#1274) (@vera-liu)
|
||||
- [f837733](https://github.com/airbnb/caravel/commit/f837733d858643152304469c899c096e31d2bf24) [explorev2] chart and controls (#1251) (@ascott)
|
||||
- [66b498d](https://github.com/airbnb/caravel/commit/66b498de25b6bb3145eb441f64ef7fdaffc5a747) Added controls for Table Viz (#1253) (@vera-liu)
|
||||
- [659bf6d](https://github.com/airbnb/caravel/commit/659bf6d7e80d70def5b6c46a9d8a3a262e37a13e) Moved time column and grains to models.py (#1255) (@vera-liu)
|
||||
- [a8a1690](https://github.com/airbnb/caravel/commit/a8a16900e7497083f14de3b6dc50063f845401b7) docs: add libsasl as system requirement on linux (#1257) (@xrmx)
|
||||
- [e50b59e](https://github.com/airbnb/caravel/commit/e50b59e5535bc653e3321a2630287540ef99df86) docs: document that gunicorn does not work on windows (#1258) (@xrmx)
|
||||
- [231804e](https://github.com/airbnb/caravel/commit/231804e2b47771ed6b31cb48e36c2f417973cad1) CHANGELOG: Add proper credit to tan31989 for #744 (#1259) (@xrmx)
|
||||
- [421a86a](https://github.com/airbnb/caravel/commit/421a86ade5ac5ec4d147bdfb913ce32a7004b455) Some polish on query search (#1222) (@vera-liu)
|
||||
- [140a055](https://github.com/airbnb/caravel/commit/140a055e4ef85080bbe3e7defdfeb2dd7b828b58) [docs] add line in installation instructions (@mistercrunch)
|
||||
- [5bf86d9](https://github.com/airbnb/caravel/commit/5bf86d91ec9911e6155335ff1f97dc48c2c16a21) [docs] suggest to upgrade pip and setuptools (@mistercrunch)
|
||||
- [715cdd9](https://github.com/airbnb/caravel/commit/715cdd98fb213e06c687ee8587ded9437039fb2d) Changelog for 0.11.0 (@mistercrunch)
|
||||
|
||||
### 0.11.0 (2016/10/05 04:27 +00:00)
|
||||
- [7a01d9d](https://github.com/airbnb/caravel/commit/7a01d9dbcbbd7678960c33c8cca1b13680290ac7) v0.11.0 (@mistercrunch)
|
||||
- [58dfa43](https://github.com/airbnb/caravel/commit/58dfa436ee53aac185b457e721bfe2303b8d006a) Do not shadow _ function. (#1254) (@bkyryliuk)
|
||||
|
||||
@@ -30,8 +30,8 @@ Look through the GitHub issues for features. Anything tagged with
|
||||
|
||||
### Documentation
|
||||
|
||||
Caravel could always use better documentation,
|
||||
whether as part of the official Caravel docs,
|
||||
Superset could always use better documentation,
|
||||
whether as part of the official Superset docs,
|
||||
in docstrings, `docs/*.rst` or even on the web as blog posts or
|
||||
articles.
|
||||
|
||||
@@ -49,16 +49,16 @@ If you are proposing a feature:
|
||||
|
||||
## Latest Documentation
|
||||
|
||||
Latest documentation and tutorial are available [here](http://airbnb.io/caravel)
|
||||
Latest documentation and tutorial are available [here](http://airbnb.io/superset)
|
||||
|
||||
## Setting up a Python development environment
|
||||
|
||||
Check the [OS dependencies](http://airbnb.io/caravel/installation.html#os-dependencies) before follows these steps.
|
||||
Check the [OS dependencies](http://airbnb.io/superset/installation.html#os-dependencies) before follows these steps.
|
||||
|
||||
# fork the repo on GitHub and then clone it
|
||||
# alternatively you may want to clone the main repo but that won't work
|
||||
# so well if you are planning on sending PRs
|
||||
# git clone git@github.com:airbnb/caravel.git
|
||||
# git clone git@github.com:airbnb/superset.git
|
||||
|
||||
# [optional] setup a virtual env and activate it
|
||||
virtualenv env
|
||||
@@ -68,24 +68,24 @@ Check the [OS dependencies](http://airbnb.io/caravel/installation.html#os-depend
|
||||
python setup.py develop
|
||||
|
||||
# Create an admin user
|
||||
fabmanager create-admin --app caravel
|
||||
fabmanager create-admin --app superset
|
||||
|
||||
# Initialize the database
|
||||
caravel db upgrade
|
||||
superset db upgrade
|
||||
|
||||
# Create default roles and permissions
|
||||
caravel init
|
||||
superset init
|
||||
|
||||
# Load some data to play with
|
||||
caravel load_examples
|
||||
superset load_examples
|
||||
|
||||
# start a dev web server
|
||||
caravel runserver -d
|
||||
superset runserver -d
|
||||
|
||||
|
||||
## Setting up the node / npm javascript environment
|
||||
|
||||
`caravel/assets` contains all npm-managed, front end assets.
|
||||
`superset/assets` contains all npm-managed, front end assets.
|
||||
Flask-Appbuilder itself comes bundled with jQuery and bootstrap.
|
||||
While these may be phased out over time, these packages are currently not
|
||||
managed with npm.
|
||||
@@ -112,14 +112,14 @@ export PATH="$HOME/.npm-packages/bin:$PATH"
|
||||
|
||||
#### npm packages
|
||||
To install third party libraries defined in `package.json`, run the
|
||||
following within the `caravel/assets/` directory which will install them in a
|
||||
following within the `superset/assets/` directory which will install them in a
|
||||
new `node_modules/` folder within `assets/`.
|
||||
|
||||
```
|
||||
npm install
|
||||
```
|
||||
|
||||
To parse and generate bundled files for caravel, run either of the
|
||||
To parse and generate bundled files for superset, run either of the
|
||||
following commands. The `dev` flag will keep the npm script running and
|
||||
re-run it upon any changes within the assets directory.
|
||||
|
||||
@@ -135,7 +135,7 @@ For every development session you will have to start a flask dev server
|
||||
as well as an npm watcher
|
||||
|
||||
```
|
||||
caravel runserver -d -p 8081
|
||||
superset runserver -d -p 8081
|
||||
npm run dev
|
||||
```
|
||||
|
||||
@@ -147,7 +147,7 @@ Python tests can be run with:
|
||||
|
||||
We use [Mocha](https://mochajs.org/), [Chai](http://chaijs.com/) and [Enzyme](http://airbnb.io/enzyme/) to test Javascript. Tests can be run with:
|
||||
|
||||
cd /caravel/caravel/assets/javascripts
|
||||
cd /superset/superset/assets/javascripts
|
||||
npm i
|
||||
npm run test
|
||||
|
||||
@@ -157,13 +157,13 @@ Lint the project with:
|
||||
|
||||
# for python changes
|
||||
flake8 changes tests
|
||||
flake8 changes caravel
|
||||
flake8 changes superset
|
||||
|
||||
# for javascript
|
||||
npm run lint
|
||||
|
||||
## Linting with codeclimate
|
||||
Codeclimate is a service we use to measure code quality and test coverage. To get codeclimate's report on your branch, ideally before sending your PR, you can setup codeclimate against your Caravel fork. After you push to your fork, you should be able to get the report at http://codeclimate.com . Alternatively, if you prefer to work locally, you can install the codeclimate cli tool.
|
||||
Codeclimate is a service we use to measure code quality and test coverage. To get codeclimate's report on your branch, ideally before sending your PR, you can setup codeclimate against your Superset fork. After you push to your fork, you should be able to get the report at http://codeclimate.com . Alternatively, if you prefer to work locally, you can install the codeclimate cli tool.
|
||||
|
||||
*Install the codeclimate cli tool*
|
||||
```
|
||||
@@ -193,12 +193,12 @@ Generate the documentation with:
|
||||
cd docs && ./build.sh
|
||||
|
||||
## CSS Themes
|
||||
As part of the npm build process, CSS for Caravel is compiled from `Less`, a dynamic stylesheet language.
|
||||
As part of the npm build process, CSS for Superset is compiled from `Less`, a dynamic stylesheet language.
|
||||
|
||||
It's possible to customize or add your own theme to Caravel, either by overriding CSS rules or preferably
|
||||
It's possible to customize or add your own theme to Superset, either by overriding CSS rules or preferably
|
||||
by modifying the Less variables or files in `assets/stylesheets/less/`.
|
||||
|
||||
The `variables.less` and `bootswatch.less` files that ship with Caravel are derived from
|
||||
The `variables.less` and `bootswatch.less` files that ship with Superset are derived from
|
||||
[Bootswatch](https://bootswatch.com) and thus extend Bootstrap. Modify variables in these files directly, or
|
||||
swap them out entirely with the equivalent files from other Bootswatch (themes)[https://github.com/thomaspark/bootswatch.git]
|
||||
|
||||
@@ -221,14 +221,14 @@ meets these guidelines:
|
||||
|
||||
## Translations
|
||||
|
||||
We use [Babel](http://babel.pocoo.org/en/latest/) to translate Caravel. The
|
||||
We use [Babel](http://babel.pocoo.org/en/latest/) to translate Superset. The
|
||||
key is to instrument the strings that need translation using
|
||||
`from flask_babel import lazy_gettext as _`. Once this is imported in
|
||||
a module, all you have to do is to `_("Wrap your strings")` using the
|
||||
underscore `_` "function".
|
||||
|
||||
To enable changing language in your environment, you can simply add the
|
||||
`LANGUAGES` parameter to your `caravel_config.py`. Having more than one
|
||||
`LANGUAGES` parameter to your `superset_config.py`. Having more than one
|
||||
options here will add a language selection dropdown on the right side of the
|
||||
navigation bar.
|
||||
|
||||
@@ -241,23 +241,23 @@ navigation bar.
|
||||
As per the [Flask AppBuilder documentation] about translation, to create a
|
||||
new language dictionary, run the following command:
|
||||
|
||||
pybabel init -i ./babel/messages.pot -d caravel/translations -l es
|
||||
pybabel init -i ./babel/messages.pot -d superset/translations -l es
|
||||
|
||||
Then it's a matter of running the statement below to gather all stings that
|
||||
need translation
|
||||
|
||||
fabmanager babel-extract --target caravel/translations/
|
||||
fabmanager babel-extract --target superset/translations/
|
||||
|
||||
You can then translate the strings gathered in files located under
|
||||
`caravel/translation`, where there's one per language. For the translations
|
||||
`superset/translation`, where there's one per language. For the translations
|
||||
to take effect, they need to be compiled using this command:
|
||||
|
||||
fabmanager babel-compile --target caravel/translations/
|
||||
fabmanager babel-compile --target superset/translations/
|
||||
|
||||
|
||||
## Adding new datasources
|
||||
|
||||
1. Create Models and Views for the datasource, add them under caravel folder, like a new my_models.py
|
||||
1. Create Models and Views for the datasource, add them under superset folder, like a new my_models.py
|
||||
with models for cluster, datasources, columns and metrics and my_views.py with clustermodelview
|
||||
and datasourcemodelview.
|
||||
|
||||
@@ -267,6 +267,6 @@ to take effect, they need to be compiled using this command:
|
||||
|
||||
For example:
|
||||
|
||||
`ADDITIONAL_MODULE_DS_MAP = {'caravel.my_models': ['MyDatasource', 'MyOtherDatasource']}`
|
||||
`ADDITIONAL_MODULE_DS_MAP = {'superset.my_models': ['MyDatasource', 'MyOtherDatasource']}`
|
||||
|
||||
This means it'll register MyDatasource and MyOtherDatasource in caravel.my_models module in the source registry.
|
||||
This means it'll register MyDatasource and MyOtherDatasource in superset.my_models module in the source registry.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Please use [pull requests](https://github.com/airbnb/caravel/pull/new/master)
|
||||
Please use [pull requests](https://github.com/airbnb/superset/pull/new/master)
|
||||
to add your organization and/or project to this document!
|
||||
|
||||
Organizations
|
||||
@@ -8,6 +8,7 @@ Organizations
|
||||
- [Maieutical Labs] (https://cloudschooling.it)
|
||||
- [Shopkick] (https://www.shopkick.com)
|
||||
- [Amino] (https://amino.com)
|
||||
- [Faasos] (http://faasos.com/)
|
||||
|
||||
Projects
|
||||
----------
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
Make sure these boxes are checked before submitting your issue - thank you!
|
||||
|
||||
- [ ] I have checked the caravel logs for python stacktraces and included it here as text if any
|
||||
- [ ] I have reproduced the issue with at least the latest released version of caravel
|
||||
- [ ] I have checked the superset logs for python stacktraces and included it here as text if any
|
||||
- [ ] I have reproduced the issue with at least the latest released version of superset
|
||||
- [ ] I have checked the issue tracker for the same issue and I haven't found one similar
|
||||
|
||||
|
||||
### Caravel version
|
||||
### Superset version
|
||||
|
||||
|
||||
### Expected results
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
recursive-include caravel/templates *
|
||||
recursive-include caravel/static *
|
||||
recursive-exclude caravel/static/assets/node_modules *
|
||||
recursive-include caravel/static/assets/node_modules/font-awesome *
|
||||
recursive-exclude caravel/static/docs *
|
||||
recursive-exclude caravel/static/spec *
|
||||
recursive-include superset/templates *
|
||||
recursive-include superset/static *
|
||||
recursive-exclude superset/static/assets/node_modules *
|
||||
recursive-include superset/static/assets/node_modules/font-awesome *
|
||||
recursive-exclude superset/static/docs *
|
||||
recursive-exclude superset/static/spec *
|
||||
recursive-exclude tests *
|
||||
recursive-include caravel/data *
|
||||
recursive-include caravel/migrations *
|
||||
recursive-include superset/data *
|
||||
recursive-include superset/migrations *
|
||||
|
||||
@@ -1,47 +1,51 @@
|
||||
Caravel
|
||||
Superset
|
||||
=========
|
||||
<img src="http://i.imgur.com/H0Kyvyi.jpg" style="border-radius: 20px; box-shadow:5px 5px 5px gray;" alt="Caravel" width="500"/>
|
||||
|
||||
[](https://travis-ci.org/airbnb/caravel)
|
||||
[](https://badge.fury.io/py/caravel)
|
||||
[](https://coveralls.io/github/airbnb/caravel?branch=master)
|
||||
[](https://codeclimate.com/github/airbnb/caravel/coverage)
|
||||
[](https://landscape.io/github/airbnb/caravel/master)
|
||||
[](https://codeclimate.com/github/airbnb/caravel)
|
||||
[](https://pypi.python.org/pypi/caravel)
|
||||
[](https://requires.io/github/airbnb/caravel/requirements/?branch=master)
|
||||
[](https://gitter.im/airbnb/caravel?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||
[](http://airbnb.io/caravel/)
|
||||
[](https://david-dm.org/airbnb/caravel?path=caravel/assets)
|
||||
[](https://travis-ci.org/airbnb/superset)
|
||||
[](https://badge.fury.io/py/superset)
|
||||
[](https://coveralls.io/github/airbnb/superset?branch=master)
|
||||
[](https://codeclimate.com/github/airbnb/superset/coverage)
|
||||
[](https://landscape.io/github/airbnb/superset/master)
|
||||
[](https://codeclimate.com/github/airbnb/superset)
|
||||
[](https://pypi.python.org/pypi/superset)
|
||||
[](https://requires.io/github/airbnb/superset/requirements/?branch=master)
|
||||
[](https://gitter.im/airbnb/superset?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||
[](http://airbnb.io/superset/)
|
||||
[](https://david-dm.org/airbnb/superset?path=superset/assets)
|
||||
|
||||
Caravel is a data exploration platform designed to be visual, intuitive
|
||||
**Superset** is a data exploration platform designed to be visual, intuitive
|
||||
and interactive.
|
||||
|
||||
[this project used to be named **Panoramix**]
|
||||
[this project used to be named **Caravel**, and **Panoramix** in the past]
|
||||
|
||||
|
||||
Screenshots & Gifs
|
||||
------------------
|
||||

|
||||
|
||||
---
|
||||

|
||||
**View Dashboards**
|
||||

|
||||
|
||||
---
|
||||

|
||||
<br/>
|
||||
**View/Edit a Slice**
|
||||

|
||||
|
||||
---
|
||||

|
||||
<br/>
|
||||
**Query and Visualize with SQL Lab**
|
||||

|
||||
|
||||
---
|
||||

|
||||
<br/>
|
||||

|
||||
|
||||
Caravel
|
||||

|
||||
|
||||

|
||||
|
||||
Superset
|
||||
---------
|
||||
Caravel's main goal is to make it easy to slice, dice and visualize data.
|
||||
Superset's main goal is to make it easy to slice, dice and visualize data.
|
||||
It empowers users to perform **analytics at the speed of thought**.
|
||||
|
||||
Caravel provides:
|
||||
Superset provides:
|
||||
* A quick way to intuitively visualize datasets by allowing users to create
|
||||
and share interactive dashboards
|
||||
* A rich set of visualizations to analyze your data, as well as a flexible
|
||||
@@ -54,7 +58,7 @@ Caravel provides:
|
||||
displayed in the UI, by defining which fields should show up in
|
||||
which dropdown and which aggregation and function (metrics) are
|
||||
made available to the user
|
||||
* Deep integration with Druid allows for Caravel to stay blazing fast while
|
||||
* Deep integration with Druid allows for Superset to stay blazing fast while
|
||||
slicing and dicing large, realtime datasets
|
||||
* Fast loading dashboards with configurable caching
|
||||
|
||||
@@ -62,7 +66,7 @@ Caravel provides:
|
||||
Database Support
|
||||
----------------
|
||||
|
||||
Caravel was originally designed on top of Druid.io, but quickly broadened
|
||||
Superset was originally designed on top of Druid.io, but quickly broadened
|
||||
its scope to support other databases through the use of SQLAlchemy, a Python
|
||||
ORM that is compatible with
|
||||
[most common databases](http://docs.sqlalchemy.org/en/rel_1_0/core/engines.html).
|
||||
@@ -83,55 +87,50 @@ power analytic dashboards and applications.*
|
||||
Installation & Configuration
|
||||
----------------------------
|
||||
|
||||
[See in the documentation](http://airbnb.io/caravel/installation.html)
|
||||
[See in the documentation](http://airbnb.io/superset/installation.html)
|
||||
|
||||
|
||||
More screenshots
|
||||
----------------
|
||||
|
||||

|
||||

|
||||
|
||||
---
|
||||

|
||||

|
||||
|
||||
---
|
||||

|
||||

|
||||
|
||||
---
|
||||

|
||||

|
||||
|
||||
---
|
||||

|
||||

|
||||
|
||||
---
|
||||

|
||||

|
||||
|
||||
---
|
||||

|
||||

|
||||
|
||||

|
||||
|
||||
|
||||
Resources
|
||||
-------------
|
||||
* [Caravel Google Group](https://groups.google.com/forum/#!forum/airbnb_caravel)
|
||||
* [Gitter (live chat) Channel](https://gitter.im/airbnb/caravel)
|
||||
* [Docker image 1](https://hub.docker.com/r/kochalex/caravel/)
|
||||
[Docker image 2](https://hub.docker.com/r/amancevice/caravel/) (community contributed)
|
||||
* [Superset Google Group](https://groups.google.com/forum/#!forum/airbnb_superset)
|
||||
* [Gitter (live chat) Channel](https://gitter.im/airbnb/superset)
|
||||
* [Docker image](https://hub.docker.com/r/amancevice/superset/) (community contributed)
|
||||
* [Slides from Strata (March 2016)](https://drive.google.com/open?id=0B5PVE0gzO81oOVJkdF9aNkJMSmM)
|
||||
|
||||
|
||||
Tip of the Hat
|
||||
--------------
|
||||
|
||||
Caravel would not be possible without these great frameworks / libs
|
||||
Superset would not be possible without these great frameworks / libs
|
||||
|
||||
* Flask App Builder - Allowing us to focus on building the app quickly while
|
||||
getting the foundation for free
|
||||
* The Flask ecosystem - Simply amazing. So much Plug, easy play.
|
||||
* NVD3 - One of the best charting libraries out there
|
||||
* Much more, check out the `install_requires` section in the [setup.py](https://github.com/airbnb/caravel/blob/master/setup.py) file!
|
||||
* Much more, check out the `install_requires` section in the [setup.py](https://github.com/airbnb/superset/blob/master/setup.py) file!
|
||||
|
||||
|
||||
Contributing
|
||||
------------
|
||||
|
||||
Interested in contributing? Casual hacking? Check out [Contributing.MD](https://github.com/airbnb/caravel/blob/master/CONTRIBUTING.md)
|
||||
Interested in contributing? Casual hacking? Check out [Contributing.MD](https://github.com/airbnb/superset/blob/master/CONTRIBUTING.md)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# TODO
|
||||
List of TODO items for Caravel
|
||||
List of TODO items for Superset
|
||||
|
||||
## Important
|
||||
* **Getting proper JS testing:** unit tests on the Python side are pretty
|
||||
@@ -7,7 +7,7 @@ List of TODO items for Caravel
|
||||
testing all the ajax-type calls
|
||||
* **Viz Plugins:** Allow people to define and share visualization plugins.
|
||||
ideally one would only need to drop in a set of files in a folder and
|
||||
Caravel would discover and expose the plugins
|
||||
Superset would discover and expose the plugins
|
||||
|
||||
## Features
|
||||
* **Dashboard URL filters:** `{dash_url}#fltin__fieldname__value1,value2`
|
||||
|
||||
@@ -29,7 +29,7 @@ script_location = migrations
|
||||
# are written from script.py.mako
|
||||
# output_encoding = utf-8
|
||||
|
||||
sqlalchemy.url = scheme://localhost/caravel
|
||||
sqlalchemy.url = scheme://localhost/superset
|
||||
|
||||
# Logging configuration
|
||||
[loggers]
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
[ignore: caravel/assets/node_modules/**]
|
||||
[python: caravel/**.py]
|
||||
[jinja2: caravel/**/templates/**.html]
|
||||
[ignore: superset/assets/node_modules/**]
|
||||
[python: superset/**.py]
|
||||
[jinja2: superset/**/templates/**.html]
|
||||
encoding = utf-8
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
{
|
||||
"presets" : ["airbnb", "es2015", "react"]
|
||||
}
|
||||
|
Before Width: | Height: | Size: 144 KiB |
|
Before Width: | Height: | Size: 3.0 KiB |
|
Before Width: | Height: | Size: 6.1 KiB |
|
Before Width: | Height: | Size: 236 KiB |
|
Before Width: | Height: | Size: 2.8 KiB |
|
Before Width: | Height: | Size: 93 KiB |
|
Before Width: | Height: | Size: 49 KiB |
|
Before Width: | Height: | Size: 121 KiB |
|
Before Width: | Height: | Size: 1.5 MiB |
|
Before Width: | Height: | Size: 147 KiB |
@@ -1,10 +0,0 @@
|
||||
export const STATE_BSSTYLE_MAP = {
|
||||
failed: 'danger',
|
||||
pending: 'info',
|
||||
fetching: 'info',
|
||||
running: 'warning',
|
||||
stopped: 'danger',
|
||||
success: 'success',
|
||||
};
|
||||
|
||||
export const STATUS_OPTIONS = ['success', 'failed', 'running'];
|
||||
@@ -1,51 +0,0 @@
|
||||
import React from 'react';
|
||||
import AceEditor from 'react-ace';
|
||||
import 'brace/mode/sql';
|
||||
import 'brace/theme/github';
|
||||
import 'brace/ext/language_tools';
|
||||
|
||||
const propTypes = {
|
||||
sql: React.PropTypes.string.isRequired,
|
||||
onBlur: React.PropTypes.func,
|
||||
};
|
||||
|
||||
const defaultProps = {
|
||||
onBlur: () => {},
|
||||
};
|
||||
|
||||
class AceEditorWrapper extends React.PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
sql: props.sql,
|
||||
};
|
||||
}
|
||||
textChange(text) {
|
||||
this.setState({ sql: text });
|
||||
}
|
||||
onBlur() {
|
||||
this.props.onBlur(this.state.sql);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<AceEditor
|
||||
mode="sql"
|
||||
theme="github"
|
||||
onBlur={this.onBlur.bind(this)}
|
||||
minLines={8}
|
||||
maxLines={30}
|
||||
onChange={this.textChange.bind(this)}
|
||||
height="200px"
|
||||
width="100%"
|
||||
editorProps={{ $blockScrolling: true }}
|
||||
enableBasicAutocompletion
|
||||
value={this.state.sql}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
AceEditorWrapper.defaultProps = defaultProps;
|
||||
AceEditorWrapper.propTypes = propTypes;
|
||||
|
||||
export default AceEditorWrapper;
|
||||
@@ -1,50 +0,0 @@
|
||||
import { Alert, Tab, Tabs } from 'react-bootstrap';
|
||||
import QueryHistory from './QueryHistory';
|
||||
import ResultSet from './ResultSet';
|
||||
import { areArraysShallowEqual } from '../../reduxUtils';
|
||||
import React from 'react';
|
||||
|
||||
import shortid from 'shortid';
|
||||
|
||||
const propTypes = {
|
||||
queries: React.PropTypes.array.isRequired,
|
||||
actions: React.PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
class SouthPane extends React.PureComponent {
|
||||
shouldComponentUpdate(nextProps) {
|
||||
return !areArraysShallowEqual(this.props.queries, nextProps.queries);
|
||||
}
|
||||
render() {
|
||||
let latestQuery;
|
||||
const props = this.props;
|
||||
if (props.queries.length > 0) {
|
||||
latestQuery = props.queries[props.queries.length - 1];
|
||||
}
|
||||
let results;
|
||||
if (latestQuery) {
|
||||
results = (
|
||||
<ResultSet showControls search query={latestQuery} actions={props.actions} />
|
||||
);
|
||||
} else {
|
||||
results = <Alert bsStyle="info">Run a query to display results here</Alert>;
|
||||
}
|
||||
return (
|
||||
<div className="SouthPane">
|
||||
<Tabs bsStyle="tabs" id={shortid.generate()}>
|
||||
<Tab title="Results" eventKey={1}>
|
||||
<div style={{ overflow: 'auto' }}>
|
||||
{results}
|
||||
</div>
|
||||
</Tab>
|
||||
<Tab title="Query History" eventKey={2}>
|
||||
<QueryHistory queries={props.queries} actions={props.actions} />
|
||||
</Tab>
|
||||
</Tabs>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
SouthPane.propTypes = propTypes;
|
||||
|
||||
export default SouthPane;
|
||||
@@ -1,226 +0,0 @@
|
||||
import React from 'react';
|
||||
|
||||
import { ButtonGroup, Well } from 'react-bootstrap';
|
||||
import shortid from 'shortid';
|
||||
|
||||
import CopyToClipboard from '../../components/CopyToClipboard';
|
||||
import Link from './Link';
|
||||
import ModalTrigger from '../../components/ModalTrigger';
|
||||
|
||||
const propTypes = {
|
||||
table: React.PropTypes.object,
|
||||
actions: React.PropTypes.object,
|
||||
};
|
||||
|
||||
const defaultProps = {
|
||||
table: null,
|
||||
actions: {},
|
||||
};
|
||||
|
||||
class TableElement extends React.PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
sortColumns: false,
|
||||
};
|
||||
}
|
||||
|
||||
popSelectStar() {
|
||||
const qe = {
|
||||
id: shortid.generate(),
|
||||
title: this.props.table.name,
|
||||
dbId: this.props.table.dbId,
|
||||
autorun: true,
|
||||
sql: this.props.table.selectStar,
|
||||
};
|
||||
this.props.actions.addQueryEditor(qe);
|
||||
}
|
||||
|
||||
collapseTable(e) {
|
||||
e.preventDefault();
|
||||
this.props.actions.collapseTable(this.props.table);
|
||||
}
|
||||
|
||||
expandTable(e) {
|
||||
e.preventDefault();
|
||||
this.props.actions.expandTable(this.props.table);
|
||||
}
|
||||
|
||||
removeTable() {
|
||||
this.props.actions.removeTable(this.props.table);
|
||||
}
|
||||
dataPreviewModal() {
|
||||
const query = {
|
||||
dbId: this.props.table.dbId,
|
||||
sql: this.props.table.selectStar,
|
||||
tableName: this.props.table.name,
|
||||
sqlEditorId: null,
|
||||
tab: '',
|
||||
runAsync: false,
|
||||
ctas: false,
|
||||
};
|
||||
this.props.actions.runQuery(query);
|
||||
}
|
||||
toggleSortColumns() {
|
||||
this.setState({ sortColumns: !this.state.sortColumns });
|
||||
}
|
||||
|
||||
render() {
|
||||
const table = this.props.table;
|
||||
let metadata = null;
|
||||
let buttonToggle;
|
||||
|
||||
let header;
|
||||
if (table.partitions) {
|
||||
let partitionQuery;
|
||||
let partitionClipBoard;
|
||||
if (table.partitions.partitionQuery) {
|
||||
partitionQuery = table.partitions.partitionQuery;
|
||||
const tt = 'Copy partition query to clipboard';
|
||||
partitionClipBoard = (
|
||||
<CopyToClipboard
|
||||
text={partitionQuery}
|
||||
shouldShowText={false}
|
||||
tooltipText={tt}
|
||||
copyNode={<i className="fa fa-clipboard" />}
|
||||
/>
|
||||
);
|
||||
}
|
||||
let latest = [];
|
||||
for (const k in table.partitions.latest) {
|
||||
latest.push(`${k}=${table.partitions.latest[k]}`);
|
||||
}
|
||||
latest = latest.join('/');
|
||||
header = (
|
||||
<Well bsSize="small">
|
||||
<div>
|
||||
<small>
|
||||
latest partition: {latest}
|
||||
</small> {partitionClipBoard}
|
||||
</div>
|
||||
</Well>
|
||||
);
|
||||
}
|
||||
if (table.expanded) {
|
||||
buttonToggle = (
|
||||
<a
|
||||
href="#"
|
||||
onClick={(e) => { this.collapseTable(e); }}
|
||||
>
|
||||
<strong>{table.name}</strong>
|
||||
<small className="m-l-5"><i className="fa fa-minus" /></small>
|
||||
</a>
|
||||
);
|
||||
const cols = table.columns.slice();
|
||||
if (this.state.sortColumns) {
|
||||
cols.sort((a, b) => a.name.toUpperCase() > b.name.toUpperCase());
|
||||
}
|
||||
metadata = (
|
||||
<div>
|
||||
{header}
|
||||
<div className="table-columns">
|
||||
{cols.map((col) => {
|
||||
let name = col.name;
|
||||
if (col.indexed) {
|
||||
name = <strong>{col.name}</strong>;
|
||||
}
|
||||
return (
|
||||
<div className="clearfix table-column" key={shortid.generate()}>
|
||||
<div className="pull-left m-l-10 col-name">
|
||||
{name}
|
||||
</div>
|
||||
<div className="pull-right text-muted">
|
||||
<small> {col.type}</small>
|
||||
</div>
|
||||
</div>);
|
||||
})}
|
||||
<hr />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
buttonToggle = (
|
||||
<a
|
||||
href="#"
|
||||
onClick={(e) => { this.expandTable(e); }}
|
||||
>
|
||||
{table.name}
|
||||
<small className="m-l-5"><i className="fa fa-plus" /></small>
|
||||
</a>
|
||||
);
|
||||
}
|
||||
let keyLink;
|
||||
if (table.indexes && table.indexes.length > 0) {
|
||||
keyLink = (
|
||||
<ModalTrigger
|
||||
modalTitle={
|
||||
<div>
|
||||
Keys for table <strong>{table.name}</strong>
|
||||
</div>
|
||||
}
|
||||
modalBody={
|
||||
<pre>{JSON.stringify(table.indexes, null, 4)}</pre>
|
||||
}
|
||||
triggerNode={
|
||||
<Link
|
||||
className="fa fa-key pull-left m-l-2"
|
||||
tooltip={`View indexes (${table.indexes.length})`}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<div className="TableElement">
|
||||
<div className="clearfix">
|
||||
<div className="pull-left">
|
||||
{buttonToggle}
|
||||
</div>
|
||||
<div className="pull-right">
|
||||
<ButtonGroup className="ws-el-controls pull-right">
|
||||
{keyLink}
|
||||
<Link
|
||||
className={
|
||||
`fa fa-sort-${!this.state.sortColumns ? 'alpha' : 'numeric'}-asc ` +
|
||||
'pull-left sort-cols m-l-2'}
|
||||
onClick={this.toggleSortColumns.bind(this)}
|
||||
tooltip={
|
||||
!this.state.sortColumns ?
|
||||
'Sort columns alphabetically' :
|
||||
'Original table column order'}
|
||||
href="#"
|
||||
/>
|
||||
<Link
|
||||
className="fa fa-search-plus pull-left m-l-2"
|
||||
onClick={this.dataPreviewModal.bind(this)}
|
||||
tooltip="Data preview"
|
||||
href="#"
|
||||
/>
|
||||
<CopyToClipboard
|
||||
copyNode={
|
||||
<a className="fa fa-clipboard pull-left m-l-2" />
|
||||
}
|
||||
text={table.selectStar}
|
||||
shouldShowText={false}
|
||||
tooltipText="Copy SELECT statement to clipboard"
|
||||
/>
|
||||
<Link
|
||||
className="fa fa-trash pull-left m-l-2"
|
||||
onClick={this.removeTable.bind(this)}
|
||||
tooltip="Remove from panel"
|
||||
href="#"
|
||||
/>
|
||||
</ButtonGroup>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
{metadata}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
TableElement.propTypes = propTypes;
|
||||
TableElement.defaultProps = defaultProps;
|
||||
|
||||
export default TableElement;
|
||||
@@ -1,42 +0,0 @@
|
||||
import React, { PropTypes } from 'react';
|
||||
|
||||
const propTypes = {
|
||||
modalId: PropTypes.string.isRequired,
|
||||
title: PropTypes.string,
|
||||
modalContent: PropTypes.node,
|
||||
customButton: PropTypes.node,
|
||||
};
|
||||
|
||||
function Modal({ modalId, title, modalContent, customButton }) {
|
||||
return (
|
||||
<div className="modal fade" id={modalId} role="dialog">
|
||||
<div className="modal-dialog" role="document">
|
||||
<div className="modal-content">
|
||||
<div className="modal-header">
|
||||
<button type="button" className="close" data-dismiss="modal" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
<h4 className="modal-title">{title}</h4>
|
||||
</div>
|
||||
<div className="modal-body">
|
||||
{modalContent}
|
||||
</div>
|
||||
<div className="modal-footer">
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-default"
|
||||
data-dismiss="modal"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
{customButton}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Modal.propTypes = propTypes;
|
||||
|
||||
export default Modal;
|
||||
@@ -1,148 +0,0 @@
|
||||
const $ = window.$ = require('jquery');
|
||||
export const SET_DATASOURCE = 'SET_DATASOURCE';
|
||||
export const SET_TIME_COLUMN_OPTS = 'SET_TIME_COLUMN_OPTS';
|
||||
export const SET_TIME_GRAIN_OPTS = 'SET_TIME_GRAIN_OPTS';
|
||||
export const SET_GROUPBY_COLUMN_OPTS = 'SET_GROUPBY_COLUMN_OPTS';
|
||||
export const SET_METRICS_OPTS = 'SET_METRICS_OPTS';
|
||||
export const SET_COLUMN_OPTS = 'SET_COLUMN_OPTS';
|
||||
export const SET_ORDERING_OPTS = 'SET_ORDERING_OPTS';
|
||||
export const TOGGLE_SEARCHBOX = 'TOGGLE_SEARCHBOX';
|
||||
export const SET_FILTER_COLUMN_OPTS = 'SET_FILTER_COLUMN_OPTS';
|
||||
export const ADD_FILTER = 'ADD_FILTER';
|
||||
export const SET_FILTER = 'SET_FILTER';
|
||||
export const REMOVE_FILTER = 'REMOVE_FILTER';
|
||||
export const CHANGE_FILTER_FIELD = 'CHANGE_FILTER_FIELD';
|
||||
export const CHANGE_FILTER_OP = 'CHANGE_FILTER_OP';
|
||||
export const CHANGE_FILTER_VALUE = 'CHANGE_FILTER_VALUE';
|
||||
export const RESET_FORM_DATA = 'RESET_FORM_DATA';
|
||||
export const CLEAR_ALL_OPTS = 'CLEAR_ALL_OPTS';
|
||||
export const SET_DATASOURCE_TYPE = 'SET_DATASOURCE_TYPE';
|
||||
export const SET_FORM_DATA = 'SET_FORM_DATA';
|
||||
|
||||
export function setTimeColumnOpts(timeColumnOpts) {
|
||||
return { type: SET_TIME_COLUMN_OPTS, timeColumnOpts };
|
||||
}
|
||||
|
||||
export function setTimeGrainOpts(timeGrainOpts) {
|
||||
return { type: SET_TIME_GRAIN_OPTS, timeGrainOpts };
|
||||
}
|
||||
|
||||
export function setGroupByColumnOpts(groupByColumnOpts) {
|
||||
return { type: SET_GROUPBY_COLUMN_OPTS, groupByColumnOpts };
|
||||
}
|
||||
|
||||
export function setMetricsOpts(metricsOpts) {
|
||||
return { type: SET_METRICS_OPTS, metricsOpts };
|
||||
}
|
||||
|
||||
export function setColumnOpts(columnOpts) {
|
||||
return { type: SET_COLUMN_OPTS, columnOpts };
|
||||
}
|
||||
|
||||
export function setOrderingOpts(orderingOpts) {
|
||||
return { type: SET_ORDERING_OPTS, orderingOpts };
|
||||
}
|
||||
|
||||
export function setFilterColumnOpts(filterColumnOpts) {
|
||||
return { type: SET_FILTER_COLUMN_OPTS, filterColumnOpts };
|
||||
}
|
||||
|
||||
export function resetFormData() {
|
||||
// Clear all form data when switching datasource
|
||||
return { type: RESET_FORM_DATA };
|
||||
}
|
||||
|
||||
export function clearAllOpts() {
|
||||
return { type: CLEAR_ALL_OPTS };
|
||||
}
|
||||
|
||||
export function setDatasourceType(datasourceType) {
|
||||
return { type: SET_DATASOURCE_TYPE, datasourceType };
|
||||
}
|
||||
|
||||
export function setFormOpts(datasourceId, datasourceType) {
|
||||
return function (dispatch) {
|
||||
const timeColumnOpts = [];
|
||||
const groupByColumnOpts = [];
|
||||
const metricsOpts = [];
|
||||
const filterColumnOpts = [];
|
||||
const timeGrainOpts = [];
|
||||
const columnOpts = [];
|
||||
const orderingOpts = [];
|
||||
|
||||
if (datasourceId) {
|
||||
const params = [`datasource_id=${datasourceId}`, `datasource_type=${datasourceType}`];
|
||||
const url = '/caravel/fetch_datasource_metadata?' + params.join('&');
|
||||
|
||||
$.get(url, (data, status) => {
|
||||
if (status === 'success') {
|
||||
data.time_columns.forEach((d) => {
|
||||
if (d) timeColumnOpts.push({ value: d, label: d });
|
||||
});
|
||||
data.groupby_cols.forEach((d) => {
|
||||
if (d) groupByColumnOpts.push({ value: d, label: d });
|
||||
});
|
||||
data.metrics.forEach((d) => {
|
||||
if (d) metricsOpts.push({ value: d[1], label: d[0] });
|
||||
});
|
||||
data.filter_cols.forEach((d) => {
|
||||
if (d) filterColumnOpts.push({ value: d, label: d });
|
||||
});
|
||||
data.time_grains.forEach((d) => {
|
||||
if (d) timeGrainOpts.push({ value: d, label: d });
|
||||
});
|
||||
data.columns.forEach((d) => {
|
||||
if (d) columnOpts.push({ value: d, label: d });
|
||||
});
|
||||
data.ordering_cols.forEach((d) => {
|
||||
if (d) orderingOpts.push({ value: d, label: d });
|
||||
});
|
||||
|
||||
// Repopulate options for controls
|
||||
dispatch(setTimeColumnOpts(timeColumnOpts));
|
||||
dispatch(setTimeGrainOpts(timeGrainOpts));
|
||||
dispatch(setGroupByColumnOpts(groupByColumnOpts));
|
||||
dispatch(setMetricsOpts(metricsOpts));
|
||||
dispatch(setFilterColumnOpts(filterColumnOpts));
|
||||
dispatch(setColumnOpts(columnOpts));
|
||||
dispatch(setOrderingOpts(orderingOpts));
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// Clear all Select options
|
||||
dispatch(clearAllOpts());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function setDatasource(datasourceId) {
|
||||
return { type: SET_DATASOURCE, datasourceId };
|
||||
}
|
||||
|
||||
export function toggleSearchBox(searchBox) {
|
||||
return { type: TOGGLE_SEARCHBOX, searchBox };
|
||||
}
|
||||
|
||||
export function addFilter(filter) {
|
||||
return { type: ADD_FILTER, filter };
|
||||
}
|
||||
|
||||
export function removeFilter(filter) {
|
||||
return { type: REMOVE_FILTER, filter };
|
||||
}
|
||||
|
||||
export function changeFilterField(filter, field) {
|
||||
return { type: CHANGE_FILTER_FIELD, filter, field };
|
||||
}
|
||||
|
||||
export function changeFilterOp(filter, op) {
|
||||
return { type: CHANGE_FILTER_OP, filter, op };
|
||||
}
|
||||
|
||||
export function changeFilterValue(filter, value) {
|
||||
return { type: CHANGE_FILTER_VALUE, filter, value };
|
||||
}
|
||||
|
||||
export function setFormData(key, value) {
|
||||
return { type: SET_FORM_DATA, key, value };
|
||||
}
|
||||
@@ -1,105 +0,0 @@
|
||||
import React, { PropTypes } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { Panel } from 'react-bootstrap';
|
||||
import visMap from '../../../visualizations/main';
|
||||
import { d3format } from '../../modules/utils';
|
||||
|
||||
const propTypes = {
|
||||
sliceName: PropTypes.string.isRequired,
|
||||
vizType: PropTypes.string.isRequired,
|
||||
height: PropTypes.string.isRequired,
|
||||
sliceContainerId: PropTypes.string.isRequired,
|
||||
jsonEndpoint: PropTypes.string.isRequired,
|
||||
columnFormats: PropTypes.object,
|
||||
};
|
||||
|
||||
class ChartContainer extends React.Component {
|
||||
componentDidMount() {
|
||||
this.renderVis();
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
this.renderVis();
|
||||
}
|
||||
|
||||
getMockedSliceObject() {
|
||||
return {
|
||||
jsonEndpoint: () => this.props.jsonEndpoint,
|
||||
|
||||
container: {
|
||||
html: () => {
|
||||
// this should be a callback to clear the contents of the slice container
|
||||
},
|
||||
|
||||
css: () => {
|
||||
// dimension can be 'height'
|
||||
// pixel string can be '300px'
|
||||
// should call callback to adjust height of chart
|
||||
},
|
||||
height: () => parseInt(this.props.height, 10) - 100,
|
||||
},
|
||||
|
||||
width: () => this.chartContainerRef.getBoundingClientRect().width,
|
||||
|
||||
height: () => parseInt(this.props.height, 10) - 100,
|
||||
|
||||
selector: `#${this.props.sliceContainerId}`,
|
||||
|
||||
done: () => {
|
||||
// finished rendering callback
|
||||
},
|
||||
d3format: (col, number) => {
|
||||
// mock d3format function in Slice object in caravel.js
|
||||
const format = this.props.columnFormats[col];
|
||||
return d3format(format, number);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
renderVis() {
|
||||
const slice = this.getMockedSliceObject();
|
||||
visMap[this.props.vizType](slice).render();
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className="chart-container">
|
||||
<Panel
|
||||
style={{ height: this.props.height }}
|
||||
header={
|
||||
<div
|
||||
id="slice-header"
|
||||
className="panel-title"
|
||||
>
|
||||
{this.props.sliceName}
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<div
|
||||
id={this.props.sliceContainerId}
|
||||
ref={(ref) => { this.chartContainerRef = ref; }}
|
||||
className={this.props.vizType}
|
||||
/>
|
||||
</Panel>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
ChartContainer.propTypes = propTypes;
|
||||
|
||||
function mapStateToProps(state) {
|
||||
return {
|
||||
sliceName: state.sliceName,
|
||||
vizType: state.viz.formData.vizType,
|
||||
sliceContainerId: `slice-container-${state.viz.formData.sliceId}`,
|
||||
jsonEndpoint: state.viz.jsonEndPoint,
|
||||
columnFormats: state.viz.columnFormats,
|
||||
};
|
||||
}
|
||||
|
||||
function mapDispatchToProps() {
|
||||
return {};
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(ChartContainer);
|
||||
@@ -1,89 +0,0 @@
|
||||
import React from 'react';
|
||||
import Select from 'react-select';
|
||||
import { bindActionCreators } from 'redux';
|
||||
import * as actions from '../actions/exploreActions';
|
||||
import { connect } from 'react-redux';
|
||||
import { VIZ_TYPES } from '../constants';
|
||||
|
||||
const propTypes = {
|
||||
actions: React.PropTypes.object,
|
||||
datasources: React.PropTypes.array,
|
||||
datasourceId: React.PropTypes.number,
|
||||
datasourceType: React.PropTypes.string,
|
||||
vizType: React.PropTypes.string,
|
||||
};
|
||||
|
||||
const defaultProps = {
|
||||
datasources: [],
|
||||
datasourceId: null,
|
||||
datasourceType: null,
|
||||
vizType: null,
|
||||
};
|
||||
|
||||
class ChartControl extends React.Component {
|
||||
componentWillMount() {
|
||||
if (this.props.datasourceId) {
|
||||
this.props.actions.setFormOpts(this.props.datasourceId, this.props.datasourceType);
|
||||
}
|
||||
}
|
||||
changeDatasource(datasourceOpt) {
|
||||
const val = (datasourceOpt) ? datasourceOpt.value : null;
|
||||
this.props.actions.setDatasource(val);
|
||||
this.props.actions.resetFormData();
|
||||
this.props.actions.setFormOpts(val, this.props.datasourceType);
|
||||
}
|
||||
changeViz(opt) {
|
||||
const val = opt ? opt.value : null;
|
||||
this.props.actions.setFormData('vizType', val);
|
||||
}
|
||||
render() {
|
||||
return (
|
||||
<div className="panel">
|
||||
<div className="panel-header">Chart Options</div>
|
||||
<div className="panel-body">
|
||||
<h5 className="section-heading">Datasource</h5>
|
||||
<div className="row">
|
||||
<Select
|
||||
name="select-datasource"
|
||||
placeholder="Select a datasource"
|
||||
options={this.props.datasources.map((d) => ({ value: d[0], label: d[1] }))}
|
||||
value={this.props.datasourceId}
|
||||
autosize={false}
|
||||
onChange={this.changeDatasource.bind(this)}
|
||||
/>
|
||||
</div>
|
||||
<h5 className="section-heading">Viz Type</h5>
|
||||
<div className="row">
|
||||
<Select
|
||||
name="select-viztype"
|
||||
placeholder="Select a viz type"
|
||||
options={VIZ_TYPES}
|
||||
value={this.props.vizType}
|
||||
autosize={false}
|
||||
onChange={this.changeViz.bind(this)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
ChartControl.propTypes = propTypes;
|
||||
ChartControl.defaultProps = defaultProps;
|
||||
|
||||
function mapStateToProps(state) {
|
||||
return {
|
||||
datasources: state.datasources,
|
||||
datasourceId: state.datasourceId,
|
||||
datasourceType: state.datasourceType,
|
||||
vizType: state.viz.formData.vizType,
|
||||
};
|
||||
}
|
||||
|
||||
function mapDispatchToProps(dispatch) {
|
||||
return {
|
||||
actions: bindActionCreators(actions, dispatch),
|
||||
};
|
||||
}
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(ChartControl);
|
||||
@@ -1,40 +0,0 @@
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { Panel } from 'react-bootstrap';
|
||||
import { DefaultControls, VIZ_CONTROL_MAPPING } from '../constants';
|
||||
|
||||
const propTypes = {
|
||||
vizType: React.PropTypes.string,
|
||||
};
|
||||
|
||||
const defaultProps = {
|
||||
vizType: null,
|
||||
};
|
||||
|
||||
function ControlPanelsContainer(props) {
|
||||
return (
|
||||
<Panel>
|
||||
<div className="scrollbar-container">
|
||||
<div className="scrollbar-content">
|
||||
{DefaultControls}
|
||||
{VIZ_CONTROL_MAPPING[props.vizType]}
|
||||
</div>
|
||||
</div>
|
||||
</Panel>
|
||||
);
|
||||
}
|
||||
|
||||
ControlPanelsContainer.propTypes = propTypes;
|
||||
ControlPanelsContainer.defaultProps = defaultProps;
|
||||
|
||||
function mapStateToProps(state) {
|
||||
return {
|
||||
vizType: state.viz.formData.vizType,
|
||||
};
|
||||
}
|
||||
|
||||
function mapDispatchToProps() {
|
||||
return {};
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(ControlPanelsContainer);
|
||||
@@ -1,46 +0,0 @@
|
||||
import React from 'react';
|
||||
import ChartContainer from './ChartContainer';
|
||||
import ControlPanelsContainer from './ControlPanelsContainer';
|
||||
import QueryAndSaveBtns from '../../explore/components/QueryAndSaveBtns';
|
||||
|
||||
export default class ExploreViewContainer extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
height: this.getHeight(),
|
||||
};
|
||||
}
|
||||
|
||||
getHeight() {
|
||||
const navHeight = 90;
|
||||
return `${window.innerHeight - navHeight}px`;
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div
|
||||
className="container-fluid"
|
||||
style={{
|
||||
height: this.state.height,
|
||||
overflow: 'hidden',
|
||||
}}
|
||||
>
|
||||
<div className="row">
|
||||
<div className="col-sm-4">
|
||||
<QueryAndSaveBtns
|
||||
canAdd="True"
|
||||
onQuery={() => {}}
|
||||
/>
|
||||
<br /><br />
|
||||
<ControlPanelsContainer />
|
||||
</div>
|
||||
<div className="col-sm-8">
|
||||
<ChartContainer
|
||||
height={this.state.height}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,68 +0,0 @@
|
||||
import React from 'react';
|
||||
import { bindActionCreators } from 'redux';
|
||||
import * as actions from '../actions/exploreActions';
|
||||
import { connect } from 'react-redux';
|
||||
import SelectArray from './SelectArray';
|
||||
|
||||
const propTypes = {
|
||||
actions: React.PropTypes.object,
|
||||
metricsOpts: React.PropTypes.array,
|
||||
metrics: React.PropTypes.array,
|
||||
groupByColumnOpts: React.PropTypes.array,
|
||||
groupByColumns: React.PropTypes.array,
|
||||
};
|
||||
|
||||
const defaultProps = {
|
||||
metricsOpts: [],
|
||||
metrics: [],
|
||||
groupByColumnOpts: [],
|
||||
groupByColumns: [],
|
||||
};
|
||||
|
||||
const GroupBy = (props) => {
|
||||
const selects = [
|
||||
{
|
||||
key: 'groupByColumns',
|
||||
title: 'Group By',
|
||||
options: props.groupByColumnOpts,
|
||||
value: props.groupByColumns,
|
||||
multi: true,
|
||||
width: '12',
|
||||
},
|
||||
{
|
||||
key: 'metrics',
|
||||
title: 'Metrics',
|
||||
options: props.metricsOpts,
|
||||
value: props.metrics,
|
||||
multi: true,
|
||||
width: '12',
|
||||
}];
|
||||
return (
|
||||
<div className="panel">
|
||||
<div className="panel-header">GroupBy</div>
|
||||
<div className="panel-body">
|
||||
<SelectArray selectArray={selects} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
GroupBy.propTypes = propTypes;
|
||||
GroupBy.defaultProps = defaultProps;
|
||||
|
||||
function mapStateToProps(state) {
|
||||
return {
|
||||
metricsOpts: state.metricsOpts,
|
||||
metrics: state.viz.formData.metrics,
|
||||
groupByColumnOpts: state.groupByColumnOpts,
|
||||
groupByColumns: state.viz.formData.groupByColumns,
|
||||
};
|
||||
}
|
||||
|
||||
function mapDispatchToProps(dispatch) {
|
||||
return {
|
||||
actions: bindActionCreators(actions, dispatch),
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(GroupBy);
|
||||
@@ -1,68 +0,0 @@
|
||||
import React from 'react';
|
||||
import SelectArray from './SelectArray';
|
||||
import { bindActionCreators } from 'redux';
|
||||
import * as actions from '../actions/exploreActions';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
const propTypes = {
|
||||
actions: React.PropTypes.object,
|
||||
columnOpts: React.PropTypes.array,
|
||||
columns: React.PropTypes.array,
|
||||
orderingOpts: React.PropTypes.array,
|
||||
orderings: React.PropTypes.array,
|
||||
};
|
||||
|
||||
const defaultProps = {
|
||||
columnOpts: [],
|
||||
columns: [],
|
||||
orderingOpts: [],
|
||||
orderings: [],
|
||||
};
|
||||
|
||||
const NotGroupBy = (props) => {
|
||||
const selects = [
|
||||
{
|
||||
key: 'columns',
|
||||
title: 'Columns',
|
||||
options: props.columnOpts,
|
||||
value: props.columns,
|
||||
multi: true,
|
||||
width: '12',
|
||||
},
|
||||
{
|
||||
key: 'orderings',
|
||||
title: 'Orderings',
|
||||
options: props.orderingOpts,
|
||||
value: props.orderings,
|
||||
multi: true,
|
||||
width: '12',
|
||||
}];
|
||||
return (
|
||||
<div className="panel">
|
||||
<div className="panel-header">Not GroupBy</div>
|
||||
<div className="panel-body">
|
||||
<SelectArray selectArray={selects} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
NotGroupBy.propTypes = propTypes;
|
||||
NotGroupBy.defaultProps = defaultProps;
|
||||
|
||||
function mapStateToProps(state) {
|
||||
return {
|
||||
columnOpts: state.columnOpts,
|
||||
columns: state.viz.formData.columns,
|
||||
orderingOpts: state.orderingOpts,
|
||||
orderings: state.viz.formData.orderings,
|
||||
};
|
||||
}
|
||||
|
||||
function mapDispatchToProps(dispatch) {
|
||||
return {
|
||||
actions: bindActionCreators(actions, dispatch),
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(NotGroupBy);
|
||||
@@ -1,61 +0,0 @@
|
||||
import React from 'react';
|
||||
import SelectArray from './SelectArray';
|
||||
import { bindActionCreators } from 'redux';
|
||||
import * as actions from '../actions/exploreActions';
|
||||
import { connect } from 'react-redux';
|
||||
import { timestampOptions, rowLimitOptions } from '../constants';
|
||||
|
||||
const propTypes = {
|
||||
actions: React.PropTypes.object,
|
||||
timeStampFormat: React.PropTypes.string,
|
||||
rowLimit: React.PropTypes.number,
|
||||
};
|
||||
|
||||
const defaultProps = {
|
||||
timeStampFormat: null,
|
||||
rowLimit: null,
|
||||
};
|
||||
|
||||
const Options = (props) => {
|
||||
const selects = [
|
||||
{
|
||||
key: 'timeStampFormat',
|
||||
title: 'Timestamp Format',
|
||||
options: timestampOptions.map((t) => ({ value: t[0], label: t[1] })),
|
||||
value: props.timeStampFormat,
|
||||
width: '12',
|
||||
},
|
||||
{
|
||||
key: 'rowLimit',
|
||||
title: 'Row Limit',
|
||||
options: rowLimitOptions.map((r) => ({ value: r, label: r })),
|
||||
value: props.rowLimit,
|
||||
width: '12',
|
||||
}];
|
||||
return (
|
||||
<div className="panel">
|
||||
<div className="panel-header">Options</div>
|
||||
<div className="panel-body">
|
||||
<SelectArray selectArray={selects} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
Options.propTypes = propTypes;
|
||||
Options.defaultProps = defaultProps;
|
||||
|
||||
function mapStateToProps(state) {
|
||||
return {
|
||||
timeStampFormat: state.viz.formData.timeStampFormat,
|
||||
rowLimit: state.viz.formData.rowLimit,
|
||||
};
|
||||
}
|
||||
|
||||
function mapDispatchToProps(dispatch) {
|
||||
return {
|
||||
actions: bindActionCreators(actions, dispatch),
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(Options);
|
||||
@@ -1,74 +0,0 @@
|
||||
import React from 'react';
|
||||
import Select from 'react-select';
|
||||
import { bindActionCreators } from 'redux';
|
||||
import * as actions from '../actions/exploreActions';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
const propTypes = {
|
||||
actions: React.PropTypes.object,
|
||||
selectArray: React.PropTypes.arrayOf(
|
||||
React.PropTypes.shape({
|
||||
key: React.PropTypes.string.isRequired,
|
||||
title: React.PropTypes.string.isRequired,
|
||||
options: React.PropTypes.array.isRequired,
|
||||
value: React.PropTypes.oneOfType([
|
||||
React.PropTypes.string,
|
||||
React.PropTypes.array,
|
||||
]),
|
||||
width: React.PropTypes.string,
|
||||
multi: React.PropTypes.bool,
|
||||
})
|
||||
).isRequired,
|
||||
};
|
||||
|
||||
const defaultProps = {
|
||||
selectArray: [],
|
||||
};
|
||||
|
||||
class SelectArray extends React.Component {
|
||||
changeSelectData(key, multi, opt) {
|
||||
if (multi) this.props.actions.setFormData(key, opt);
|
||||
else {
|
||||
const val = opt ? opt.value : null;
|
||||
this.props.actions.setFormData(key, val);
|
||||
}
|
||||
}
|
||||
render() {
|
||||
const selects = this.props.selectArray.map((obj) => (
|
||||
<div
|
||||
className={(obj.width) ? `col-sm-${obj.width}` : 'col-sm-6'}
|
||||
key={obj.key}
|
||||
>
|
||||
<h5 className="section-heading">{obj.title}</h5>
|
||||
<Select
|
||||
multi={obj.multi}
|
||||
name={`select-${obj.key}`}
|
||||
options={obj.options}
|
||||
value={obj.value}
|
||||
autosize={false}
|
||||
onChange={this.changeSelectData.bind(this, obj.key, obj.multi)}
|
||||
/>
|
||||
</div>
|
||||
));
|
||||
return (
|
||||
<div>
|
||||
{selects}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
SelectArray.propTypes = propTypes;
|
||||
SelectArray.defaultProps = defaultProps;
|
||||
|
||||
function mapStateToProps() {
|
||||
return {};
|
||||
}
|
||||
|
||||
function mapDispatchToProps(dispatch) {
|
||||
return {
|
||||
actions: bindActionCreators(actions, dispatch),
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(SelectArray);
|
||||
@@ -1,88 +0,0 @@
|
||||
import React from 'react';
|
||||
import { bindActionCreators } from 'redux';
|
||||
import * as actions from '../actions/exploreActions';
|
||||
import { connect } from 'react-redux';
|
||||
import { sinceOptions, untilOptions } from '../constants';
|
||||
import SelectArray from './SelectArray';
|
||||
|
||||
const propTypes = {
|
||||
actions: React.PropTypes.object,
|
||||
datasourceType: React.PropTypes.string,
|
||||
timeColumnOpts: React.PropTypes.array,
|
||||
timeColumn: React.PropTypes.string,
|
||||
timeGrainOpts: React.PropTypes.array,
|
||||
timeGrain: React.PropTypes.string,
|
||||
since: React.PropTypes.string,
|
||||
until: React.PropTypes.string,
|
||||
};
|
||||
|
||||
const defaultProps = {
|
||||
timeColumnOpts: [],
|
||||
timeColumn: null,
|
||||
timeGrainOpts: [],
|
||||
timeGrain: null,
|
||||
since: null,
|
||||
until: null,
|
||||
};
|
||||
|
||||
const TimeFilter = (props) => {
|
||||
const isDatasourceTypeTable = props.datasourceType === 'table';
|
||||
const timeColumnTitle = isDatasourceTypeTable ? 'Time Column' : 'Time Granularity';
|
||||
const timeGrainTitle = isDatasourceTypeTable ? 'Time Grain' : 'Origin';
|
||||
const selects = [
|
||||
{
|
||||
key: 'timeColumn',
|
||||
title: timeColumnTitle,
|
||||
options: props.timeColumnOpts,
|
||||
value: props.timeColumn,
|
||||
},
|
||||
{
|
||||
key: 'timeGrain',
|
||||
title: timeGrainTitle,
|
||||
options: props.timeGrainOpts,
|
||||
value: props.timeGrain,
|
||||
},
|
||||
{
|
||||
key: 'since',
|
||||
title: 'Since',
|
||||
options: sinceOptions.map((s) => ({ value: s, label: s })),
|
||||
value: props.since,
|
||||
},
|
||||
{
|
||||
key: 'until',
|
||||
title: 'Until',
|
||||
options: untilOptions.map((u) => ({ value: u, label: u })),
|
||||
value: props.until,
|
||||
}];
|
||||
return (
|
||||
<div className="panel">
|
||||
<div className="panel-header">Time Filter</div>
|
||||
<div className="panel-body">
|
||||
<SelectArray selectArray={selects} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
TimeFilter.propTypes = propTypes;
|
||||
TimeFilter.defaultProps = defaultProps;
|
||||
|
||||
function mapStateToProps(state) {
|
||||
return {
|
||||
datasourceType: state.datasourceType,
|
||||
timeColumnOpts: state.timeColumnOpts,
|
||||
timeColumn: state.viz.formData.timeColumn,
|
||||
timeGrainOpts: state.timeGrainOpts,
|
||||
timeGrain: state.viz.formData.timeGrain,
|
||||
since: state.viz.formData.since,
|
||||
until: state.viz.formData.until,
|
||||
};
|
||||
}
|
||||
|
||||
function mapDispatchToProps(dispatch) {
|
||||
return {
|
||||
actions: bindActionCreators(actions, dispatch),
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(TimeFilter);
|
||||
@@ -1,76 +0,0 @@
|
||||
import React from 'react';
|
||||
import TimeFilter from './components/TimeFilter';
|
||||
import ChartControl from './components/ChartControl';
|
||||
import GroupBy from './components/GroupBy';
|
||||
import SqlClause from './components/SqlClause';
|
||||
import Filters from './components/Filters';
|
||||
import NotGroupBy from './components/NotGroupBy';
|
||||
import Options from './components/Options';
|
||||
|
||||
export const VIZ_TYPES = [
|
||||
{ value: 'dist_bar', label: 'Distribution - Bar Chart', requiresTime: false },
|
||||
{ value: 'pie', label: 'Pie Chart', requiresTime: false },
|
||||
{ value: 'line', label: 'Time Series - Line Chart', requiresTime: true },
|
||||
{ value: 'bar', label: 'Time Series - Bar Chart', requiresTime: true },
|
||||
{ value: 'compare', label: 'Time Series - Percent Change', requiresTime: true },
|
||||
{ value: 'area', label: 'Time Series - Stacked', requiresTime: true },
|
||||
{ value: 'table', label: 'Table View', requiresTime: false },
|
||||
{ value: 'markup', label: 'Markup', requiresTime: false },
|
||||
{ value: 'pivot_table', label: 'Pivot Table', requiresTime: false },
|
||||
{ value: 'separator', label: 'Separator', requiresTime: false },
|
||||
{ value: 'word_cloud', label: 'Word Cloud', requiresTime: false },
|
||||
{ value: 'treemap', label: 'Treemap', requiresTime: false },
|
||||
{ value: 'cal_heatmap', label: 'Calendar Heatmap', requiresTime: true },
|
||||
{ value: 'box_plot', label: 'Box Plot', requiresTime: false },
|
||||
{ value: 'bubble', label: 'Bubble Chart', requiresTime: false },
|
||||
{ value: 'big_number', label: 'Big Number with Trendline', requiresTime: false },
|
||||
{ value: 'bubble', label: 'Bubble Chart', requiresTime: false },
|
||||
{ value: 'histogram', label: 'Histogram', requiresTime: false },
|
||||
{ value: 'sunburst', label: 'Sunburst', requiresTime: false },
|
||||
{ value: 'sankey', label: 'Sankey', requiresTime: false },
|
||||
{ value: 'directed_force', label: 'Directed Force Layout', requiresTime: false },
|
||||
{ value: 'world_map', label: 'World Map', requiresTime: false },
|
||||
{ value: 'filter_box', label: 'Filter Box', requiresTime: false },
|
||||
{ value: 'iframe', label: 'iFrame', requiresTime: false },
|
||||
{ value: 'para', label: 'Parallel Coordinates', requiresTime: false },
|
||||
{ value: 'heatmap', label: 'Heatmap', requiresTime: false },
|
||||
{ value: 'horizon', label: 'Horizon', requiresTime: false },
|
||||
{ value: 'mapbox', label: 'Mapbox', requiresTime: false },
|
||||
];
|
||||
|
||||
export const sinceOptions = ['1 hour ago', '12 hours ago', '1 day ago',
|
||||
'7 days ago', '28 days ago', '90 days ago', '1 year ago'];
|
||||
export const untilOptions = ['now', '1 day ago', '7 days ago',
|
||||
'28 days ago', '90 days ago', '1 year ago'];
|
||||
|
||||
export const timestampOptions = [
|
||||
['smart_date', 'Adaptative formating'],
|
||||
['%m/%d/%Y', '"%m/%d/%Y" | 01/14/2019'],
|
||||
['%Y-%m-%d', '"%Y-%m-%d" | 2019-01-14'],
|
||||
['%Y-%m-%d %H:%M:%S',
|
||||
'"%Y-%m-%d %H:%M:%S" | 2019-01-14 01:32:10'],
|
||||
['%H:%M:%S', '"%H:%M:%S" | 01:32:10'],
|
||||
];
|
||||
|
||||
export const rowLimitOptions = [10, 50, 100, 250, 500, 1000, 5000, 10000, 50000];
|
||||
|
||||
export const DefaultControls = (
|
||||
<div>
|
||||
<ChartControl />
|
||||
<TimeFilter />
|
||||
<GroupBy />
|
||||
<SqlClause />
|
||||
<Filters />
|
||||
</div>
|
||||
);
|
||||
|
||||
export const TableVizControls = (
|
||||
<div>
|
||||
<NotGroupBy />
|
||||
<Options />
|
||||
</div>
|
||||
);
|
||||
|
||||
export const VIZ_CONTROL_MAPPING = {
|
||||
table: TableVizControls,
|
||||
};
|
||||
@@ -1,49 +0,0 @@
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import ExploreViewContainer from './components/ExploreViewContainer';
|
||||
import { createStore, applyMiddleware, compose } from 'redux';
|
||||
import { Provider } from 'react-redux';
|
||||
import thunk from 'redux-thunk';
|
||||
|
||||
import { initialState } from './stores/store';
|
||||
|
||||
const exploreViewContainer = document.getElementById('js-explore-view-container');
|
||||
const bootstrapData = JSON.parse(exploreViewContainer.getAttribute('data-bootstrap'));
|
||||
|
||||
import { exploreReducer } from './reducers/exploreReducer';
|
||||
|
||||
const bootstrappedState = Object.assign(initialState, {
|
||||
datasources: bootstrapData.datasources,
|
||||
datasourceId: parseInt(bootstrapData.datasource_id, 10),
|
||||
datasourceType: bootstrapData.datasource_type,
|
||||
sliceName: bootstrapData.viz.form_data.slice_name,
|
||||
viz: {
|
||||
jsonEndPoint: bootstrapData.viz.json_endpoint,
|
||||
data: bootstrapData.viz.data,
|
||||
columnFormats: bootstrapData.viz.column_formats,
|
||||
formData: {
|
||||
sliceId: bootstrapData.viz.form_data.slice_id,
|
||||
vizType: bootstrapData.viz.form_data.viz_type,
|
||||
timeColumn: bootstrapData.viz.form_data.granularity_sqla,
|
||||
timeGrain: bootstrapData.viz.form_data.time_grain_sqla,
|
||||
metrics: [bootstrapData.viz.form_data.metrics].map((m) => ({ value: m, label: m })),
|
||||
since: bootstrapData.viz.form_data.since,
|
||||
until: bootstrapData.viz.form_data.until,
|
||||
having: bootstrapData.viz.form_data.having,
|
||||
where: bootstrapData.viz.form_data.where,
|
||||
rowLimit: bootstrapData.viz.form_data.row_limit,
|
||||
timeStampFormat: bootstrapData.viz.form_data.table_timestamp_format,
|
||||
},
|
||||
},
|
||||
});
|
||||
const store = createStore(exploreReducer, bootstrappedState,
|
||||
compose(applyMiddleware(thunk))
|
||||
);
|
||||
|
||||
ReactDOM.render(
|
||||
<Provider store={store}>
|
||||
<ExploreViewContainer />
|
||||
</Provider>,
|
||||
exploreViewContainer
|
||||
);
|
||||
|
||||
@@ -1,89 +0,0 @@
|
||||
import { defaultFormData, defaultOpts } from '../stores/store';
|
||||
import * as actions from '../actions/exploreActions';
|
||||
import { addToArr, removeFromArr, alterInArr } from '../../../utils/reducerUtils';
|
||||
|
||||
const setFormInViz = function (state, action) {
|
||||
const newFormData = Object.assign({}, state);
|
||||
newFormData[action.key] = action.value;
|
||||
return newFormData;
|
||||
};
|
||||
|
||||
const setVizInState = function (state, action) {
|
||||
switch (action.type) {
|
||||
case actions.SET_FORM_DATA:
|
||||
return Object.assign(
|
||||
{},
|
||||
state,
|
||||
{ formData: setFormInViz(state.formData, action) }
|
||||
);
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
export const exploreReducer = function (state, action) {
|
||||
const actionHandlers = {
|
||||
[actions.SET_DATASOURCE]() {
|
||||
return Object.assign({}, state, { datasourceId: action.datasourceId });
|
||||
},
|
||||
[actions.SET_TIME_COLUMN_OPTS]() {
|
||||
return Object.assign({}, state, { timeColumnOpts: action.timeColumnOpts });
|
||||
},
|
||||
[actions.SET_TIME_GRAIN_OPTS]() {
|
||||
return Object.assign({}, state, { timeGrainOpts: action.timeGrainOpts });
|
||||
},
|
||||
[actions.SET_GROUPBY_COLUMN_OPTS]() {
|
||||
return Object.assign({}, state, { groupByColumnOpts: action.groupByColumnOpts });
|
||||
},
|
||||
[actions.SET_METRICS_OPTS]() {
|
||||
return Object.assign({}, state, { metricsOpts: action.metricsOpts });
|
||||
},
|
||||
[actions.SET_COLUMN_OPTS]() {
|
||||
return Object.assign({}, state, { columnOpts: action.columnOpts });
|
||||
},
|
||||
[actions.SET_ORDERING_OPTS]() {
|
||||
return Object.assign({}, state, { orderingOpts: action.orderingOpts });
|
||||
},
|
||||
[actions.TOGGLE_SEARCHBOX]() {
|
||||
return Object.assign({}, state, { searchBox: action.searchBox });
|
||||
},
|
||||
[actions.SET_FILTER_COLUMN_OPTS]() {
|
||||
return Object.assign({}, state, { filterColumnOpts: action.filterColumnOpts });
|
||||
},
|
||||
[actions.ADD_FILTER]() {
|
||||
return addToArr(state, 'filters', action.filter);
|
||||
},
|
||||
[actions.REMOVE_FILTER]() {
|
||||
return removeFromArr(state, 'filters', action.filter);
|
||||
},
|
||||
[actions.CHANGE_FILTER_FIELD]() {
|
||||
return alterInArr(state, 'filters', action.filter, { field: action.field });
|
||||
},
|
||||
[actions.CHANGE_FILTER_OP]() {
|
||||
return alterInArr(state, 'filters', action.filter, { op: action.op });
|
||||
},
|
||||
[actions.CHANGE_FILTER_VALUE]() {
|
||||
return alterInArr(state, 'filters', action.filter, { value: action.value });
|
||||
},
|
||||
[actions.RESET_FORM_DATA]() {
|
||||
return Object.assign({}, state, defaultFormData);
|
||||
},
|
||||
[actions.CLEAR_ALL_OPTS]() {
|
||||
return Object.assign({}, state, defaultOpts);
|
||||
},
|
||||
[actions.SET_DATASOURCE_TYPE]() {
|
||||
return Object.assign({}, state, { datasourceType: action.datasourceType });
|
||||
},
|
||||
[actions.SET_FORM_DATA]() {
|
||||
return Object.assign(
|
||||
{},
|
||||
state,
|
||||
{ viz: setVizInState(state.viz, action) }
|
||||
);
|
||||
},
|
||||
};
|
||||
if (action.type in actionHandlers) {
|
||||
return actionHandlers[action.type]();
|
||||
}
|
||||
return state;
|
||||
};
|
||||
@@ -1,53 +0,0 @@
|
||||
// TODO: add datasource_type here after druid support is added
|
||||
export const defaultFormData = {
|
||||
sliceId: null,
|
||||
vizType: null,
|
||||
timeColumn: null,
|
||||
timeGrain: null,
|
||||
groupByColumns: [],
|
||||
metrics: [],
|
||||
since: null,
|
||||
until: null,
|
||||
having: null,
|
||||
where: null,
|
||||
columns: [],
|
||||
orderings: [],
|
||||
timeStampFormat: 'smart_date',
|
||||
rowLimit: 50000,
|
||||
searchBox: false,
|
||||
whereClause: '',
|
||||
havingClause: '',
|
||||
filters: [],
|
||||
};
|
||||
|
||||
export const initialState = {
|
||||
datasources: null,
|
||||
datasourceId: null,
|
||||
datasourceType: null,
|
||||
timeColumnOpts: [],
|
||||
timeGrainOpts: [],
|
||||
timeGrain: null,
|
||||
groupByColumnOpts: [],
|
||||
metricsOpts: [],
|
||||
columnOpts: [],
|
||||
orderingOpts: [],
|
||||
searchBox: false,
|
||||
whereClause: '',
|
||||
havingClause: '',
|
||||
filters: [],
|
||||
filterColumnOpts: [],
|
||||
viz: {
|
||||
columnFormats: {},
|
||||
formData: defaultFormData,
|
||||
},
|
||||
};
|
||||
|
||||
export const defaultOpts = {
|
||||
timeColumnOpts: [],
|
||||
timeGrainOpts: [],
|
||||
groupByColumnOpts: [],
|
||||
metricsOpts: [],
|
||||
filterColumnOpts: [],
|
||||
columnOpts: [],
|
||||
orderingOpts: [],
|
||||
};
|
||||
@@ -1,50 +0,0 @@
|
||||
import { it, describe } from 'mocha';
|
||||
import { expect } from 'chai';
|
||||
import shortid from 'shortid';
|
||||
import * as actions from '../../../../javascripts/explorev2/actions/exploreActions';
|
||||
import { initialState } from '../../../../javascripts/explorev2/stores/store';
|
||||
import { exploreReducer } from '../../../../javascripts/explorev2/reducers/exploreReducer';
|
||||
|
||||
describe('reducers', () => {
|
||||
it('should return new state with datasource id', () => {
|
||||
const newState = exploreReducer(initialState, actions.setDatasource(1));
|
||||
expect(newState.datasourceId).to.equal(1);
|
||||
});
|
||||
|
||||
it('should return new state with search box toggled', () => {
|
||||
const newState = exploreReducer(initialState, actions.toggleSearchBox(true));
|
||||
expect(newState.searchBox).to.equal(true);
|
||||
});
|
||||
|
||||
it('should return new state with added filter', () => {
|
||||
const newFilter = {
|
||||
id: shortid.generate(),
|
||||
eq: 'value',
|
||||
op: 'in',
|
||||
col: 'vals',
|
||||
};
|
||||
const newState = exploreReducer(initialState, actions.addFilter(newFilter));
|
||||
expect(newState.filters).to.deep.equal([newFilter]);
|
||||
});
|
||||
|
||||
it('should return new state with removed filter', () => {
|
||||
const filter1 = {
|
||||
id: shortid.generate(),
|
||||
eq: 'value',
|
||||
op: 'in',
|
||||
col: 'vals1',
|
||||
};
|
||||
const filter2 = {
|
||||
id: shortid.generate(),
|
||||
eq: 'value',
|
||||
op: 'not in',
|
||||
col: 'vals2',
|
||||
};
|
||||
const testState = {
|
||||
initialState,
|
||||
filters: [filter1, filter2],
|
||||
};
|
||||
const newState = exploreReducer(testState, actions.removeFilter(filter1));
|
||||
expect(newState.filters).to.deep.equal([filter2]);
|
||||
});
|
||||
});
|
||||
@@ -1,33 +0,0 @@
|
||||
import React from 'react';
|
||||
import Select from 'react-select';
|
||||
import { Button } from 'react-bootstrap';
|
||||
import QuerySearch from '../../../javascripts/SqlLab/components/QuerySearch';
|
||||
import { shallow } from 'enzyme';
|
||||
import { describe, it } from 'mocha';
|
||||
import { expect } from 'chai';
|
||||
|
||||
describe('QuerySearch', () => {
|
||||
const mockedProps = {
|
||||
actions: {},
|
||||
};
|
||||
it('is valid', () => {
|
||||
expect(
|
||||
React.isValidElement(<QuerySearch {...mockedProps} />)
|
||||
).to.equal(true);
|
||||
});
|
||||
|
||||
it('should have two Select', () => {
|
||||
const wrapper = shallow(<QuerySearch {...mockedProps} />);
|
||||
expect(wrapper.find(Select)).to.have.length(2);
|
||||
});
|
||||
|
||||
it('should have one input for searchText', () => {
|
||||
const wrapper = shallow(<QuerySearch {...mockedProps} />);
|
||||
expect(wrapper.find('input')).to.have.length(1);
|
||||
});
|
||||
|
||||
it('should have one Button', () => {
|
||||
const wrapper = shallow(<QuerySearch {...mockedProps} />);
|
||||
expect(wrapper.find(Button)).to.have.length(1);
|
||||
});
|
||||
});
|
||||
@@ -1,43 +0,0 @@
|
||||
import React from 'react';
|
||||
import Link from '../../../javascripts/SqlLab/components/Link';
|
||||
import TableElement from '../../../javascripts/SqlLab/components/TableElement';
|
||||
import { table } from './fixtures';
|
||||
import { mount, shallow } from 'enzyme';
|
||||
import { describe, it } from 'mocha';
|
||||
import { expect } from 'chai';
|
||||
|
||||
|
||||
describe('TableElement', () => {
|
||||
const mockedProps = {
|
||||
table,
|
||||
};
|
||||
it('renders', () => {
|
||||
expect(
|
||||
React.isValidElement(<TableElement />)
|
||||
).to.equal(true);
|
||||
});
|
||||
it('renders with props', () => {
|
||||
expect(
|
||||
React.isValidElement(<TableElement {...mockedProps} />)
|
||||
).to.equal(true);
|
||||
});
|
||||
it('has 3 Link elements', () => {
|
||||
const wrapper = shallow(<TableElement {...mockedProps} />);
|
||||
expect(wrapper.find(Link)).to.have.length(3);
|
||||
});
|
||||
it('has 14 columns', () => {
|
||||
const wrapper = shallow(<TableElement {...mockedProps} />);
|
||||
expect(wrapper.find('div.table-column')).to.have.length(14);
|
||||
});
|
||||
it('mounts', () => {
|
||||
mount(<TableElement {...mockedProps} />);
|
||||
});
|
||||
it('sorts columns', () => {
|
||||
const wrapper = mount(<TableElement {...mockedProps} />);
|
||||
expect(wrapper.state().sortColumns).to.equal(false);
|
||||
expect(wrapper.find('.col-name').first().text()).to.equal('id');
|
||||
wrapper.find('.sort-cols').simulate('click');
|
||||
expect(wrapper.state().sortColumns).to.equal(true);
|
||||
expect(wrapper.find('.col-name').first().text()).to.equal('last_login');
|
||||
});
|
||||
});
|
||||
@@ -1,122 +0,0 @@
|
||||
{% extends "caravel/basic.html" %}
|
||||
|
||||
{% block head_js %}
|
||||
{{ super() }}
|
||||
{% with filename="dashboard" %}
|
||||
{% include "caravel/partials/_script_tag.html" %}
|
||||
{% endwith %}
|
||||
{% endblock %}
|
||||
{% block title %}[dashboard] {{ dashboard.dashboard_title }}{% endblock %}
|
||||
{% block body %}
|
||||
|
||||
<div
|
||||
class="dashboard container-fluid"
|
||||
data-dashboard="{{ dashboard.json_data }}"
|
||||
data-css="{{ dashboard.css }}"
|
||||
data-user="{{ user_id }}"
|
||||
>
|
||||
{% include 'caravel/flash_wrapper.html' %}
|
||||
|
||||
<!-- Modal -->
|
||||
<div class="modal fade" id="css_modal" tabindex="-1" role="dialog">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content css">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||
<h4 class="modal-title" id="myModalLabel">Dashboard CSS</h4>
|
||||
<h6><strong>Styling applies to this dashboard only</strong></h6>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<select id="css_template" class="select2" style="margin-bottom: 5px;">
|
||||
<option value="" data-css="">CSS template</option>
|
||||
{% for t in templates %}
|
||||
<option value="{{ t.id }}" data-css="{{t.css}}">
|
||||
{{ t.template_name }}
|
||||
</option>
|
||||
{% endfor %}
|
||||
</select><br>
|
||||
<textarea id="dash_css" rows="30" cols="60">{{ dashboard.css }}</textarea>
|
||||
<input type="hidden" id="dashboard_id" value="{{ dashboard.id }}" />
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal">
|
||||
Close
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal fade" id="refresh_modal" tabindex="-1" role="dialog">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||
<h4 class="modal-title" id="myModalLabel">Refresh Interval</h4>
|
||||
<h6><strong>Choose how frequent should the dashboard refresh</strong></h6>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<select id="refresh_dash_interval" class="select2" style="margin-bottom: 5px;">
|
||||
<option value="0">Don't refresh</option>
|
||||
<option value="10">10 seconds</option>
|
||||
<option value="30">30 seconds</option>
|
||||
<option value="60">1 minute</option>
|
||||
<option value="300">5 minutes</option>
|
||||
</select><br>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal">
|
||||
Close
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% if not standalone_mode %}
|
||||
<div id="add-slice-container"></div>
|
||||
{% endif %}
|
||||
|
||||
<div class="title">
|
||||
<div class="row">
|
||||
<div class="col-md-8">
|
||||
<h1>
|
||||
<strong>
|
||||
{{ dashboard.dashboard_title }}
|
||||
</strong>
|
||||
<span class="favstar" class_name="Dashboard" obj_id="{{ dashboard.id }}"></span>
|
||||
</h1>
|
||||
</div>
|
||||
{% if not standalone_mode %}
|
||||
<div class="col-md-4">
|
||||
<div class="btn-group pull-right" role="group" >
|
||||
<button type="button" id="refresh_dash" class="btn btn-default btn-sm" data-toggle="tooltip" title="Force refresh the whole dashboard">
|
||||
<i class="fa fa-refresh"></i>
|
||||
</button>
|
||||
<button type="button" id="add-slice" class="btn btn-default btn-sm {{ "disabled disabledButton" if not dash_save_perm }}" data-toggle="modal" data-target="#add_slice_modal">
|
||||
<i class="fa fa-plus" data-toggle="tooltip" title="Add a new slice to the dashboard"></i>
|
||||
</button>
|
||||
<button type="button" id="refresh_dash_periodic" class="btn btn-default btn-sm" data-toggle="modal" data-target="#refresh_modal">
|
||||
<i class="fa fa-clock-o" data-toggle="tooltip" title="Decide how frequent should the dashboard refresh"></i>
|
||||
</button>
|
||||
<button type="button" id="filters" class="btn btn-default btn-sm" data-toggle="tooltip" title="View the list of active filters">
|
||||
<i class="fa fa-filter"></i>
|
||||
</button>
|
||||
<button type="button" id="css" class="btn btn-default btn-sm{{ "disabled disabledButton" if not dash_edit_perm }} " data-toggle="modal" data-target="#css_modal">
|
||||
<i class="fa fa-css3" data-toggle="tooltip" title="Edit the dashboard's CSS"></i>
|
||||
</button>
|
||||
<a id="editdash" class="btn btn-default btn-sm{{ "disabled disabledButton" if not dash_edit_perm }} " href="/dashboardmodelview/edit/{{ dashboard.id }}" title="Edit this dashboard's property" data-toggle="tooltip" >
|
||||
<i class="fa fa-edit"></i>
|
||||
</a>
|
||||
<button type="button" id="savedash" class="btn btn-default btn-sm {{ "disabled disabledButton" if not dash_save_perm }}" data-toggle="tooltip" title="Save the current positioning and CSS">
|
||||
<i class="fa fa-save"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- gridster class used for backwards compatibility -->
|
||||
<div id="grid-container" class="slice-grid gridster"></div>
|
||||
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -1,9 +1,11 @@
|
||||
codeclimate-test-reporter
|
||||
coveralls
|
||||
flake8
|
||||
mock
|
||||
mysqlclient
|
||||
nose
|
||||
psycopg2
|
||||
pyyaml
|
||||
sphinx
|
||||
sphinx-rtd-theme
|
||||
sphinxcontrib.youtube
|
||||
|
||||
@@ -87,9 +87,9 @@ qthelp:
|
||||
@echo
|
||||
@echo "Build finished; now you can run "qcollectiongenerator" with the" \
|
||||
".qhcp project file in $(BUILDDIR)/qthelp, like this:"
|
||||
@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/caravel.qhcp"
|
||||
@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/superset.qhcp"
|
||||
@echo "To view the help file:"
|
||||
@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/caravel.qhc"
|
||||
@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/superset.qhc"
|
||||
|
||||
applehelp:
|
||||
$(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp
|
||||
@@ -104,8 +104,8 @@ devhelp:
|
||||
@echo
|
||||
@echo "Build finished."
|
||||
@echo "To view the help file:"
|
||||
@echo "# mkdir -p $$HOME/.local/share/devhelp/caravel"
|
||||
@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/caravel"
|
||||
@echo "# mkdir -p $$HOME/.local/share/devhelp/superset"
|
||||
@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/superset"
|
||||
@echo "# devhelp"
|
||||
|
||||
epub:
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
<img src="_static/img/dash.png">
|
||||
<div class="carousel-caption">
|
||||
<div>
|
||||
<h1>Caravel</h1>
|
||||
<h1>Superset</h1>
|
||||
<p>
|
||||
an open source data visualization platform
|
||||
</p>
|
||||
@@ -80,7 +80,7 @@
|
||||
<hr/>
|
||||
<div class="container">
|
||||
<div class="jumbotron">
|
||||
<h1>Caravel</h1>
|
||||
<h1>Superset</h1>
|
||||
<p>
|
||||
is an open source data visualization platform that provides easy
|
||||
exploration of your data and allows you to create and share
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env bash
|
||||
rm -rf _build
|
||||
make html
|
||||
#cp -r ../caravel/assets/images/ _build/html/_static/img/
|
||||
cp -r ../caravel/assets/images/ _static/img/
|
||||
rm -rf /tmp/caravel-docs
|
||||
cp -r _build/html /tmp/caravel-docs
|
||||
#cp -r ../superset/assets/images/ _build/html/_static/img/
|
||||
cp -r ../superset/assets/images/ _static/img/
|
||||
rm -rf /tmp/superset-docs
|
||||
cp -r _build/html /tmp/superset-docs
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# caravel documentation build configuration file, created by
|
||||
# superset documentation build configuration file, created by
|
||||
# sphinx-quickstart on Thu Dec 17 15:42:06 2015.
|
||||
#
|
||||
# This file is execfile()d with the current directory set to its
|
||||
@@ -51,7 +51,7 @@ source_suffix = '.rst'
|
||||
master_doc = 'index'
|
||||
|
||||
# General information about the project.
|
||||
project = "Caravel's documentation"
|
||||
project = "Superset's documentation"
|
||||
copyright = None
|
||||
author = u'Maxime Beauchemin'
|
||||
|
||||
@@ -209,7 +209,7 @@ html_show_copyright = False
|
||||
#html_search_scorer = 'scorer.js'
|
||||
|
||||
# Output file base name for HTML help builder.
|
||||
htmlhelp_basename = 'caraveldoc'
|
||||
htmlhelp_basename = 'supersetdoc'
|
||||
|
||||
# -- Options for LaTeX output ---------------------------------------------
|
||||
|
||||
@@ -231,7 +231,7 @@ latex_elements = {
|
||||
# (source start file, target name, title,
|
||||
# author, documentclass [howto, manual, or own class]).
|
||||
latex_documents = [
|
||||
(master_doc, 'caravel.tex', u'Caravel Documentation',
|
||||
(master_doc, 'superset.tex', u'Superset Documentation',
|
||||
u'Maxime Beauchemin', 'manual'),
|
||||
]
|
||||
|
||||
@@ -261,7 +261,7 @@ latex_documents = [
|
||||
# One entry per manual page. List of tuples
|
||||
# (source start file, name, description, authors, manual section).
|
||||
man_pages = [
|
||||
(master_doc, 'Caravel', u'caravel Documentation',
|
||||
(master_doc, 'Superset', u'superset Documentation',
|
||||
[author], 1)
|
||||
]
|
||||
|
||||
@@ -275,8 +275,8 @@ man_pages = [
|
||||
# (source start file, target name, title, author,
|
||||
# dir menu entry, description, category)
|
||||
texinfo_documents = [
|
||||
(master_doc, 'Caravel', u'Caravel Documentation',
|
||||
author, 'Caravel', 'One line description of project.',
|
||||
(master_doc, 'Superset', u'Superset Documentation',
|
||||
author, 'Superset', 'One line description of project.',
|
||||
'Miscellaneous'),
|
||||
]
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
Druid
|
||||
=====
|
||||
|
||||
Caravel works well with Druid, though currently not all
|
||||
Superset works well with Druid, though currently not all
|
||||
advanced features out of Druid are covered. This page clarifies what is
|
||||
covered and what isn't and explains how to use some of the features.
|
||||
|
||||
@@ -15,7 +15,7 @@ Supported
|
||||
Aggregations
|
||||
------------
|
||||
|
||||
Common aggregations, or Druid metrics can be defined and used in Caravel.
|
||||
Common aggregations, or Druid metrics can be defined and used in Superset.
|
||||
The first and simpler use case is to use the checkbox matrix expose in your
|
||||
datasource's edit view (``Sources -> Druid Datasources ->
|
||||
[your datasource] -> Edit -> [tab] List Druid Column``).
|
||||
@@ -34,7 +34,7 @@ documentation.
|
||||
Post-Aggregations
|
||||
-----------------
|
||||
|
||||
Druid supports post aggregation and this works in Caravel. All you have to
|
||||
Druid supports post aggregation and this works in Superset. All you have to
|
||||
do is creating a metric, much like you would create an aggregation manually,
|
||||
but specify ``postagg`` as a ``Metric Type``. You then have to provide a valid
|
||||
json post-aggregation definition (as specified in the Druid docs) in the
|
||||
|
||||
@@ -4,7 +4,7 @@ FAQ
|
||||
|
||||
Can I query/join multiple tables at one time?
|
||||
---------------------------------------------
|
||||
Not directly no. A Caravel SQLAlchemy datasource can only be a single table
|
||||
Not directly no. A Superset SQLAlchemy datasource can only be a single table
|
||||
or a view.
|
||||
|
||||
When working with tables, the solution would be to materialize
|
||||
@@ -14,15 +14,15 @@ through some scheduled batch process.
|
||||
A view is a simple logical layer that abstract an arbitrary SQL queries as
|
||||
a virtual table. This can allow you to join and union multiple tables, and
|
||||
to apply some transformation using arbitrary SQL expressions. The limitation
|
||||
there is your database performance as Caravel effectively will run a query
|
||||
there is your database performance as Superset effectively will run a query
|
||||
on top of your query (view). A good practice may be to limit yourself to
|
||||
joining your main large table to one or many small tables only, and avoid
|
||||
using ``GROUP BY`` where possible as Caravel will do its own ``GROUP BY`` and
|
||||
using ``GROUP BY`` where possible as Superset will do its own ``GROUP BY`` and
|
||||
doing the work twice might slow down performance.
|
||||
|
||||
Whether you use a table or a view, the important factor is whether your
|
||||
database is fast enough to serve it in an interactive fashion to provide
|
||||
a good user experience in Caravel.
|
||||
a good user experience in Superset.
|
||||
|
||||
|
||||
How BIG can my data source be?
|
||||
@@ -42,7 +42,7 @@ framework, in the meantime, we've tagged a few pull requests as
|
||||
``example`` to give people examples of how to contribute new
|
||||
visualizations.
|
||||
|
||||
https://github.com/airbnb/caravel/issues?q=label%3Aexample+is%3Aclosed
|
||||
https://github.com/airbnb/superset/issues?q=label%3Aexample+is%3Aclosed
|
||||
|
||||
|
||||
Why are my queries timing out?
|
||||
@@ -53,14 +53,14 @@ it's because the web server is timing out web requests. If you want to
|
||||
increase the default (50), you can specify the timeout when starting the
|
||||
web server with the ``-t`` flag, which is expressed in seconds.
|
||||
|
||||
``caravel runserver -t 300``
|
||||
``superset runserver -t 300``
|
||||
|
||||
|
||||
Why is the map not visible in the mapbox visualization?
|
||||
-------------------------------------------------------
|
||||
|
||||
You need to register to mapbox.com, get an API key and configure it as
|
||||
``MAPBOX_API_KEY`` in ``caravel_config.py``.
|
||||
``MAPBOX_API_KEY`` in ``superset_config.py``.
|
||||
|
||||
|
||||
How to add dynamic filters to a dashboard?
|
||||
@@ -109,3 +109,13 @@ for dealing with the time boundary filtering mentioned above.
|
||||
But what happens with filtering when dealing with slices coming from
|
||||
different tables or databases? If the column name is shared, the filter will
|
||||
be applied, it's as simple as that.
|
||||
|
||||
Why does fabmanager or superset freezed/hung/not responding when started (my home directory is NFS mounted)?
|
||||
-----------------------------------------------------------------------------------------
|
||||
superset creates and uses an sqlite database at ``~/.superset/superset.db``. Sqlite is known to `don't work well if used on NFS`__ due to broken file locking implementation on NFS.
|
||||
|
||||
__ https://www.sqlite.org/lockingv3.html
|
||||
|
||||
One work around is to create a symlink from ~/.superset to a directory located on a non-NFS partition.
|
||||
|
||||
Another work around is to change where superset stores the sqlite database by adding ``SQLALCHEMY_DATABASE_URI = 'sqlite:////new/localtion/superset.db'`` in superset_config.py (create the file if needed), then adding the directory where superset_config.py lives to PYTHONPATH environment variable (e.g. ``export PYTHONPATH=/opt/logs/sandbox/airbnb/``).
|
||||
|
||||
@@ -1,15 +1,13 @@
|
||||
Caravel's documentation
|
||||
'''''''''''''''''''''''
|
||||
Superset's documentation
|
||||
''''''''''''''''''''''''
|
||||
|
||||
Caravel is a data exploration platform designed to be visual, intuitive
|
||||
Superset is a data exploration platform designed to be visual, intuitive
|
||||
and interactive.
|
||||
|
||||
.. image:: _static/img/caravel.jpg
|
||||
|
||||
----------------
|
||||
|
||||
.. warning:: This project used to be named Panoramix and has been renamed
|
||||
to Caravel in March 2016
|
||||
.. warning:: This project was originally named Panoramix, was renamed to
|
||||
Caravel in March 2016, and is currently named Superset as of November 2016
|
||||
|
||||
Overview
|
||||
=======================================
|
||||
|
||||
@@ -4,19 +4,19 @@ Installation & Configuration
|
||||
Getting Started
|
||||
---------------
|
||||
|
||||
Caravel is tested using Python 2.7 and Python 3.4+. Python 3 is the recommended version,
|
||||
Superset is tested using Python 2.7 and Python 3.4+. Python 3 is the recommended version,
|
||||
Python 2.6 won't be supported.
|
||||
|
||||
|
||||
OS dependencies
|
||||
---------------
|
||||
|
||||
Caravel stores database connection information in its metadata database.
|
||||
Superset stores database connection information in its metadata database.
|
||||
For that purpose, we use the ``cryptography`` Python library to encrypt
|
||||
connection passwords. Unfortunately this library has OS level dependencies.
|
||||
|
||||
You may want to attempt the next step
|
||||
("Caravel installation and initialization") and come back to this step if
|
||||
("Superset installation and initialization") and come back to this step if
|
||||
you encounter an error.
|
||||
|
||||
Here's how to install them:
|
||||
@@ -47,7 +47,7 @@ attempt it, download `get-pip.py <https://bootstrap.pypa.io/get-pip.py>`_, and r
|
||||
|
||||
Python virtualenv
|
||||
-----------------
|
||||
It is recommended to install Caravel inside a virtualenv. Python 3 already ships virtualenv, for
|
||||
It is recommended to install Superset inside a virtualenv. Python 3 already ships virtualenv, for
|
||||
Python 2 you need to install it. If it's packaged for your operating systems install it from there
|
||||
otherwise you can install from pip: ::
|
||||
|
||||
@@ -73,40 +73,40 @@ and ``setuptools`` libraries.::
|
||||
|
||||
pip install --upgrade setuptools pip
|
||||
|
||||
Caravel installation and initialization
|
||||
---------------------------------------
|
||||
Follow these few simple steps to install Caravel.::
|
||||
Superset installation and initialization
|
||||
----------------------------------------
|
||||
Follow these few simple steps to install Superset.::
|
||||
|
||||
# Install caravel
|
||||
pip install caravel
|
||||
# Install superset
|
||||
pip install superset
|
||||
|
||||
# Create an admin user
|
||||
fabmanager create-admin --app caravel
|
||||
fabmanager create-admin --app superset
|
||||
|
||||
# Initialize the database
|
||||
caravel db upgrade
|
||||
superset db upgrade
|
||||
|
||||
# Load some data to play with
|
||||
caravel load_examples
|
||||
superset load_examples
|
||||
|
||||
# Create default roles and permissions
|
||||
caravel init
|
||||
superset init
|
||||
|
||||
# Start the web server on port 8088
|
||||
caravel runserver -p 8088
|
||||
superset runserver -p 8088
|
||||
|
||||
# To start a development web server, use the -d switch
|
||||
# caravel runserver -d
|
||||
# superset runserver -d
|
||||
|
||||
|
||||
After installation, you should be able to point your browser to the right
|
||||
hostname:port `http://localhost:8088 <http://localhost:8088>`_, login using
|
||||
the credential you entered while creating the admin account, and navigate to
|
||||
`Menu -> Admin -> Refresh Metadata`. This action should bring in all of
|
||||
your datasources for Caravel to be aware of, and they should show up in
|
||||
your datasources for Superset to be aware of, and they should show up in
|
||||
`Menu -> Datasources`, from where you can start playing with your data!
|
||||
|
||||
Please note that *gunicorn*, Caravel default application server, does not
|
||||
Please note that *gunicorn*, Superset default application server, does not
|
||||
work on Windows so you need to use the development web server.
|
||||
The development web server though is not intended to be used on production systems
|
||||
so better use a supported platform that can run *gunicorn*.
|
||||
@@ -114,14 +114,14 @@ so better use a supported platform that can run *gunicorn*.
|
||||
Configuration behind a load balancer
|
||||
------------------------------------
|
||||
|
||||
If you are running caravel behind a load balancer or reverse proxy (e.g. NGINX
|
||||
If you are running superset behind a load balancer or reverse proxy (e.g. NGINX
|
||||
or ELB on AWS), you may need to utilise a healthcheck endpoint so that your
|
||||
load balancer knows if your caravel instance is running. This is provided
|
||||
load balancer knows if your superset instance is running. This is provided
|
||||
at ``/health`` which will return a 200 response containing "OK" if the
|
||||
webserver is running.
|
||||
|
||||
If the load balancer is inserting X-Forwarded-For/X-Forwarded-Proto headers, you
|
||||
should set `ENABLE_PROXY_FIX = True` in the caravel config file to extract and use
|
||||
should set `ENABLE_PROXY_FIX = True` in the superset config file to extract and use
|
||||
the headers.
|
||||
|
||||
|
||||
@@ -129,16 +129,16 @@ Configuration
|
||||
-------------
|
||||
|
||||
To configure your application, you need to create a file (module)
|
||||
``caravel_config.py`` and make sure it is in your PYTHONPATH. Here are some
|
||||
``superset_config.py`` and make sure it is in your PYTHONPATH. Here are some
|
||||
of the parameters you can copy / paste in that configuration module: ::
|
||||
|
||||
#---------------------------------------------------------
|
||||
# Caravel specific config
|
||||
# Superset specific config
|
||||
#---------------------------------------------------------
|
||||
ROW_LIMIT = 5000
|
||||
CARAVEL_WORKERS = 4
|
||||
SUPERSET_WORKERS = 4
|
||||
|
||||
CARAVEL_WEBSERVER_PORT = 8088
|
||||
SUPERSET_WEBSERVER_PORT = 8088
|
||||
#---------------------------------------------------------
|
||||
|
||||
#---------------------------------------------------------
|
||||
@@ -149,10 +149,10 @@ of the parameters you can copy / paste in that configuration module: ::
|
||||
|
||||
# The SQLAlchemy connection string to your database backend
|
||||
# This connection defines the path to the database that stores your
|
||||
# caravel metadata (slices, connections, tables, dashboards, ...).
|
||||
# superset metadata (slices, connections, tables, dashboards, ...).
|
||||
# Note that the connection information to connect to the datasources
|
||||
# you want to explore are managed directly in the web UI
|
||||
SQLALCHEMY_DATABASE_URI = 'sqlite:////path/to/caravel.db'
|
||||
SQLALCHEMY_DATABASE_URI = 'sqlite:////path/to/superset.db'
|
||||
|
||||
# Flask-WTF flag for CSRF
|
||||
CSRF_ENABLED = True
|
||||
@@ -161,24 +161,24 @@ of the parameters you can copy / paste in that configuration module: ::
|
||||
MAPBOX_API_KEY = ''
|
||||
|
||||
This file also allows you to define configuration parameters used by
|
||||
Flask App Builder, the web framework used by Caravel. Please consult
|
||||
Flask App Builder, the web framework used by Superset. Please consult
|
||||
the `Flask App Builder Documentation
|
||||
<http://flask-appbuilder.readthedocs.org/en/latest/config.html>`_
|
||||
for more information on how to configure Caravel.
|
||||
for more information on how to configure Superset.
|
||||
|
||||
Please make sure to change:
|
||||
|
||||
* *SQLALCHEMY_DATABASE_URI*, by default it is stored at *~/.caravel/caravel.db*
|
||||
* *SQLALCHEMY_DATABASE_URI*, by default it is stored at *~/.superset/superset.db*
|
||||
* *SECRET_KEY*, to a long random string
|
||||
|
||||
Database dependencies
|
||||
---------------------
|
||||
|
||||
Caravel does not ship bundled with connectivity to databases, except
|
||||
Superset does not ship bundled with connectivity to databases, except
|
||||
for Sqlite, which is part of the Python standard library.
|
||||
You'll need to install the required packages for the database you
|
||||
want to use as your metadata database as well as the packages needed to
|
||||
connect to the databases you want to access through Caravel.
|
||||
connect to the databases you want to access through Superset.
|
||||
|
||||
Here's a list of some of the recommended packages.
|
||||
|
||||
@@ -203,6 +203,8 @@ Here's a list of some of the recommended packages.
|
||||
+---------------+-------------------------------------+-------------------------------------------------+
|
||||
| SparkSQL | ``pip install pyhive`` | ``jdbc+hive://`` |
|
||||
+---------------+-------------------------------------+-------------------------------------------------+
|
||||
| Greenplum | ``pip install psycopg2`` | ``postgresql+psycopg2://`` |
|
||||
+---------------+-------------------------------------+-------------------------------------------------+
|
||||
|
||||
Note that many other database are supported, the main criteria being the
|
||||
existence of a functional SqlAlchemy dialect and Python driver. Googling
|
||||
@@ -213,9 +215,9 @@ database you want to connect to should get you to the right place.
|
||||
Caching
|
||||
-------
|
||||
|
||||
Caravel uses `Flask-Cache <https://pythonhosted.org/Flask-Cache/>`_ for
|
||||
Superset uses `Flask-Cache <https://pythonhosted.org/Flask-Cache/>`_ for
|
||||
caching purpose. Configuring your caching backend is as easy as providing
|
||||
a ``CACHE_CONFIG``, constant in your ``caravel_config.py`` that
|
||||
a ``CACHE_CONFIG``, constant in your ``superset_config.py`` that
|
||||
complies with the Flask-Cache specifications.
|
||||
|
||||
Flask-Cache supports multiple caching backends (Redis, Memcached,
|
||||
@@ -224,7 +226,7 @@ Memcached please use the pylibmc client library as python-memcached does
|
||||
not handle storing binary data correctly. If you use Redis, please install
|
||||
[python-redis](https://pypi.python.org/pypi/redis).
|
||||
|
||||
For setting your timeouts, this is done in the Caravel metadata and goes
|
||||
For setting your timeouts, this is done in the Superset metadata and goes
|
||||
up the "timeout searchpath", from your slice configuration, to your
|
||||
data source's configuration, to your database's and ultimately falls back
|
||||
into your global default defined in ``CACHE_CONFIG``.
|
||||
@@ -252,7 +254,7 @@ Schemas (Postgres & Redshift)
|
||||
|
||||
Postgres and Redshift, as well as other database,
|
||||
use the concept of **schema** as a logical entity
|
||||
on top of the **database**. For Caravel to connect to a specific schema,
|
||||
on top of the **database**. For Superset to connect to a specific schema,
|
||||
there's a **schema** parameter you can set in the table form.
|
||||
|
||||
|
||||
@@ -284,7 +286,7 @@ Druid
|
||||
|
||||
* Navigate to your datasources
|
||||
|
||||
Note that you can run the ``caravel refresh_druid`` command to refresh the
|
||||
Note that you can run the ``superset refresh_druid`` command to refresh the
|
||||
metadata from your Druid cluster(s)
|
||||
|
||||
|
||||
@@ -293,10 +295,10 @@ CORS
|
||||
|
||||
The extra CORS Dependency must be installed:
|
||||
|
||||
caravel[cors]
|
||||
superset[cors]
|
||||
|
||||
|
||||
The following keys in `caravel_config.py` can be specified to configure CORS:
|
||||
The following keys in `superset_config.py` can be specified to configure CORS:
|
||||
|
||||
|
||||
* ``ENABLE_CORS``: Must be set to True in order to enable CORS
|
||||
@@ -307,16 +309,16 @@ Upgrading
|
||||
|
||||
Upgrading should be as straightforward as running::
|
||||
|
||||
pip install caravel --upgrade
|
||||
caravel db upgrade
|
||||
caravel init
|
||||
pip install superset --upgrade
|
||||
superset db upgrade
|
||||
superset init
|
||||
|
||||
SQL Lab
|
||||
-------
|
||||
SQL Lab is a powerful SQL IDE that works with all SQLAlchemy compatible
|
||||
databases out there. By default, queries are run in a web request, and
|
||||
may eventually timeout as queries exceed the maximum duration of a web
|
||||
request in your environment, whether it'd be a reverse proxy or the Caravel
|
||||
request in your environment, whether it'd be a reverse proxy or the Superset
|
||||
server itself.
|
||||
|
||||
In the modern analytics world, it's not uncommon to run large queries that
|
||||
@@ -324,7 +326,7 @@ run for minutes or hours.
|
||||
To enable support for long running queries that
|
||||
execute beyond the typical web request's timeout (30-60 seconds), it is
|
||||
necessary to deploy an asynchronous backend, which consist of one or many
|
||||
Caravel worker, which is implemented as a Celery worker, and a Celery
|
||||
Superset worker, which is implemented as a Celery worker, and a Celery
|
||||
broker for which we recommend using Redis or RabbitMQ.
|
||||
|
||||
It's also preferable to setup an async result backend as a key value store
|
||||
@@ -333,20 +335,20 @@ details to come as to how to set this up here soon.
|
||||
|
||||
SQL Lab supports templating in queries, and it's possible to override
|
||||
the default Jinja context in your environment by defining the
|
||||
``JINJA_CONTEXT_ADDONS`` in your caravel configuration. Objects referenced
|
||||
``JINJA_CONTEXT_ADDONS`` in your superset configuration. Objects referenced
|
||||
in this dictionary are made available for users to use in their SQL.
|
||||
|
||||
|
||||
Making your own build
|
||||
---------------------
|
||||
|
||||
For more advanced users, you may want to build Caravel from sources. That
|
||||
For more advanced users, you may want to build Superset from sources. That
|
||||
would be the case if you fork the project to add features specific to
|
||||
your environment.::
|
||||
|
||||
# assuming $CARAVEL_HOME as the root of the repo
|
||||
cd $CARAVEL_HOME/caravel/assets
|
||||
# assuming $SUPERSET_HOME as the root of the repo
|
||||
cd $SUPERSET_HOME/superset/assets
|
||||
npm install
|
||||
npm run prod
|
||||
cd $CARAVEL_HOME
|
||||
cd $SUPERSET_HOME
|
||||
python setup.py install
|
||||
|
||||
@@ -1,14 +1,25 @@
|
||||
Security
|
||||
========
|
||||
Security in Caravel is handled by Flask AppBuilder (FAB). FAB is a
|
||||
Security in Superset is handled by Flask AppBuilder (FAB). FAB is a
|
||||
"Simple and rapid application development framework, built on top of Flask.".
|
||||
FAB provides authentication, user management, permissions and roles.
|
||||
|
||||
|
||||
Provided Roles
|
||||
--------------
|
||||
Caravel ships with 3 roles that are handled by Caravel itself. You can
|
||||
assume that these 3 roles will stay up-to-date as Caravel evolves.
|
||||
Superset ships with a set of roles that are handled by Superset itself.
|
||||
You can assume that these roles will stay up-to-date as Superset evolves.
|
||||
Even though it's possible for ``Admin`` usrs to do so, it is not recommended
|
||||
that you alter these roles in any way by removing
|
||||
or adding permissions to them as these roles will be re-synchronized to
|
||||
their original values as you run your next ``superset init`` command.
|
||||
|
||||
Since it's not recommended to alter the roles described here, it's right
|
||||
to assume that your security strategy should be to compose user access based
|
||||
on these base roles and roles that you create. For instance you could
|
||||
create a role ``Financial Analyst`` that would be made of set of permissions
|
||||
to a set of data sources (tables) and/or databases. Users would then be
|
||||
granted ``Gamma``, ``Financial Analyst``, and perhaps ``sql_lab``.
|
||||
|
||||
Admin
|
||||
"""""
|
||||
@@ -33,6 +44,12 @@ mostly content consumers, though they can create slices and dashboards.
|
||||
Also note that when Gamma users look at the dashboards and slices list view,
|
||||
they will only see the objects that they have access to.
|
||||
|
||||
sql_lab
|
||||
"""""""
|
||||
The ``sql_lab`` role grants access to SQL Lab. Note that while ``Admin``
|
||||
users have access to all databases by default, both ``Alpha`` and ``Gamma``
|
||||
users need to be given access on a per database basis.
|
||||
|
||||
|
||||
Managing Gamma per data source access
|
||||
-------------------------------------
|
||||
@@ -59,17 +76,17 @@ Customizing
|
||||
The permissions exposed by FAB are very granular and allow for a great level
|
||||
of customization. FAB creates many permissions automagically for each model
|
||||
that is create (can_add, can_delete, can_show, can_edit, ...) as well as for
|
||||
each view. On top of that, Caravel can expose more granular permissions like
|
||||
each view. On top of that, Superset can expose more granular permissions like
|
||||
``all_datasource_access``.
|
||||
|
||||
We do not recommend altering the 3 base roles as there
|
||||
are a set of assumptions that Caravel build upon. It is possible though for
|
||||
are a set of assumptions that Superset build upon. It is possible though for
|
||||
you to create your own roles, and union them to existing ones.
|
||||
|
||||
Permissions
|
||||
"""""""""""
|
||||
|
||||
Roles are composed of a set of permissions, and Caravel has many categories
|
||||
Roles are composed of a set of permissions, and Superset has many categories
|
||||
of permissions. Here are the different categories of permissions:
|
||||
|
||||
- **Model & action**: models are entities like ``Dashboard``,
|
||||
|
||||
@@ -9,7 +9,7 @@ Feature Overview
|
||||
----------------
|
||||
- Connects to just about any database backend
|
||||
- A multi-tab environment to work on multiple queries at a time
|
||||
- A smooth flow to visualize your query results using Caravel's rich
|
||||
- A smooth flow to visualize your query results using Superset's rich
|
||||
visualization capabilities
|
||||
- Browse database metadata: tables, columns, indexes, partitions
|
||||
- Support for long-running queries
|
||||
@@ -21,6 +21,9 @@ Feature Overview
|
||||
`Jinja templating language <http://jinja.pocoo.org/docs/dev/>`_
|
||||
which allows for using macros in your SQL code
|
||||
|
||||
Extra features
|
||||
--------------
|
||||
- Hit ``alt + enter`` as a keyboard shortcut to run your query
|
||||
|
||||
Templating with Jinja
|
||||
---------------------
|
||||
@@ -42,7 +45,7 @@ Available macros
|
||||
''''''''''''''''
|
||||
|
||||
We expose certain modules from Python's standard library in
|
||||
Caravel's Jinja context:
|
||||
Superset's Jinja context:
|
||||
- ``time``: ``time``
|
||||
- ``datetime``: ``datetime.datetime``
|
||||
- ``uuid``: ``uuid``
|
||||
@@ -53,5 +56,5 @@ Caravel's Jinja context:
|
||||
`Jinja's builtin filters <http://jinja.pocoo.org/docs/dev/templates/>`_ can be also be applied where needed.
|
||||
|
||||
|
||||
.. autoclass:: caravel.jinja_context.PrestoContext
|
||||
.. autoclass:: superset.jinja_context.PrestoTemplateProcessor
|
||||
:members:
|
||||
|
||||
@@ -3,7 +3,7 @@ Tutorial
|
||||
|
||||
This basic linear tutorial will take you through connecting to a database,
|
||||
adding a table, creating a slice and a dashboard. First you'll need to tell
|
||||
Caravel where to find the database you want to
|
||||
Superset where to find the database you want to
|
||||
query. First go to the database menu
|
||||
|
||||
.. image:: _static/img/tutorial/db_menu.png
|
||||
@@ -29,7 +29,7 @@ plus (``+``) sign there (similar to the one ).
|
||||
|
||||
Now enter the name of the table in the ``Table Name`` textbox, and select
|
||||
the database you just created in the ``Database`` dropdown, hit save. At this
|
||||
moment, Caravel fetched the column names, their data types and tries to guess
|
||||
moment, Superset fetched the column names, their data types and tries to guess
|
||||
which fields are metrics in dimensions. From the list view, edit the table
|
||||
that you just created by clicking the tiny pen icon.
|
||||
|
||||
@@ -42,9 +42,9 @@ showing you the list of columns in your table as well as their data types.
|
||||
.. image:: _static/img/tutorial/matrix.png
|
||||
:scale: 30 %
|
||||
|
||||
Click the checkboxes here that inform Caravel how your columns should be
|
||||
Click the checkboxes here that inform Superset how your columns should be
|
||||
shown in the explore view, and which metrics should be created. Make sure
|
||||
to inform Caravel about your date columns. You could also create
|
||||
to inform Superset about your date columns. You could also create
|
||||
"SQL expression" columns here, or metrics in that tab as aggregate expressions,
|
||||
but let's not do that just yet. Hit ``save``.
|
||||
|
||||
|
||||
@@ -2,11 +2,11 @@ Videos
|
||||
======
|
||||
|
||||
Here is a collection of short videos showing different aspect
|
||||
of Caravel.
|
||||
of Superset.
|
||||
|
||||
Quick Intro
|
||||
'''''''''''
|
||||
This video demonstrates how Caravel works at a high level, it shows how
|
||||
This video demonstrates how Superset works at a high level, it shows how
|
||||
to navigate through datasets and dashboards that are already available.
|
||||
|
||||
.. youtube:: https://www.youtube.com/watch?v=3Txm_nj_R7M
|
||||
@@ -41,7 +41,7 @@ to toggle them on dashboards.
|
||||
|
||||
Adding a Table
|
||||
''''''''''''''
|
||||
This videos shows you how to expose a new table in Caravel, and how to
|
||||
This videos shows you how to expose a new table in Superset, and how to
|
||||
define the semantics on how this can be accessed by others in the ``Explore``
|
||||
and ``Dashboard`` views.
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
cd caravel/assets/
|
||||
cd superset/assets/
|
||||
rm build/*
|
||||
npm run prod
|
||||
cd ../..
|
||||
python setup.py register
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
#!/usr/bin/env bash
|
||||
echo $DB
|
||||
rm -f .coverage
|
||||
export CARAVEL_CONFIG=tests.caravel_test_config
|
||||
export SUPERSET_CONFIG=tests.superset_test_config
|
||||
set -e
|
||||
caravel/bin/caravel version -v
|
||||
superset/bin/superset version -v
|
||||
export SOLO_TEST=1
|
||||
# e.g. tests.core_tests:CoreTests.test_templated_sql_json
|
||||
nosetests $1
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
#!/usr/bin/env bash
|
||||
echo $DB
|
||||
rm ~/.caravel/unittests.db
|
||||
rm ~/.caravel/celerydb.sqlite
|
||||
rm ~/.caravel/celery_results.sqlite
|
||||
rm ~/.superset/unittests.db
|
||||
rm ~/.superset/celerydb.sqlite
|
||||
rm ~/.superset/celery_results.sqlite
|
||||
rm -f .coverage
|
||||
export CARAVEL_CONFIG=tests.caravel_test_config
|
||||
export SUPERSET_CONFIG=tests.superset_test_config
|
||||
set -e
|
||||
caravel/bin/caravel db upgrade
|
||||
caravel/bin/caravel db upgrade # running twice on purpose as a test
|
||||
caravel/bin/caravel version -v
|
||||
superset/bin/superset db upgrade
|
||||
superset/bin/superset db upgrade # running twice on purpose as a test
|
||||
superset/bin/superset version -v
|
||||
python setup.py nosetests
|
||||
coveralls
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
[metadata]
|
||||
name = Caravel
|
||||
name = Superset
|
||||
summary = a data exploration platform
|
||||
description-file = README.md
|
||||
author = Maxime Beauchemin
|
||||
@@ -7,7 +7,7 @@ author-email = maximebeauchemin@gmail.com
|
||||
license = Apache License, Version 2.0
|
||||
|
||||
[files]
|
||||
packages = caravel
|
||||
packages = superset
|
||||
|
||||
[build_sphinx]
|
||||
source-dir = docs/
|
||||
@@ -22,7 +22,7 @@ verbosity=3
|
||||
detailed-errors=1
|
||||
with-coverage=1
|
||||
nocapture=1
|
||||
cover-package=caravel
|
||||
cover-package=superset
|
||||
|
||||
[pycodestyle]
|
||||
max-line-length=90
|
||||
|
||||
@@ -4,13 +4,13 @@ import json
|
||||
from setuptools import setup, find_packages
|
||||
|
||||
BASE_DIR = os.path.abspath(os.path.dirname(__file__))
|
||||
PACKAGE_DIR = os.path.join(BASE_DIR, 'caravel', 'static', 'assets')
|
||||
PACKAGE_DIR = os.path.join(BASE_DIR, 'superset', 'static', 'assets')
|
||||
PACKAGE_FILE = os.path.join(PACKAGE_DIR, 'package.json')
|
||||
with open(PACKAGE_FILE) as package_file:
|
||||
version_string = json.load(package_file)['version']
|
||||
|
||||
setup(
|
||||
name='caravel',
|
||||
name='superset',
|
||||
description=(
|
||||
"A interactive data visualization platform build on SqlAlchemy "
|
||||
"and druid.io"),
|
||||
@@ -18,10 +18,10 @@ setup(
|
||||
packages=find_packages(),
|
||||
include_package_data=True,
|
||||
zip_safe=False,
|
||||
scripts=['caravel/bin/caravel'],
|
||||
scripts=['superset/bin/superset'],
|
||||
install_requires=[
|
||||
'celery==3.1.23',
|
||||
'cryptography==1.4',
|
||||
'cryptography==1.5.3',
|
||||
'flask-appbuilder==1.8.1',
|
||||
'flask-cache==0.13.1',
|
||||
'flask-migrate==1.5.1',
|
||||
@@ -57,9 +57,9 @@ setup(
|
||||
],
|
||||
author='Maxime Beauchemin',
|
||||
author_email='maximebeauchemin@gmail.com',
|
||||
url='https://github.com/airbnb/caravel',
|
||||
url='https://github.com/airbnb/superset',
|
||||
download_url=(
|
||||
'https://github.com/airbnb/caravel/tarball/' + version_string),
|
||||
'https://github.com/airbnb/superset/tarball/' + version_string),
|
||||
classifiers=[
|
||||
'Programming Language :: Python :: 2.7',
|
||||
'Programming Language :: Python :: 3.4',
|
||||
|
||||
@@ -13,16 +13,18 @@ from flask_appbuilder import SQLA, AppBuilder, IndexView
|
||||
from flask_appbuilder.baseviews import expose
|
||||
from flask_cache import Cache
|
||||
from flask_migrate import Migrate
|
||||
from caravel.source_registry import SourceRegistry
|
||||
from superset.source_registry import SourceRegistry
|
||||
from werkzeug.contrib.fixers import ProxyFix
|
||||
from caravel import utils
|
||||
from superset import utils
|
||||
|
||||
|
||||
APP_DIR = os.path.dirname(__file__)
|
||||
CONFIG_MODULE = os.environ.get('CARAVEL_CONFIG', 'caravel.config')
|
||||
CONFIG_MODULE = os.environ.get('SUPERSET_CONFIG', 'superset.config')
|
||||
|
||||
app = Flask(__name__)
|
||||
app.config.from_object(CONFIG_MODULE)
|
||||
conf = app.config
|
||||
|
||||
if not app.debug:
|
||||
# In production mode, add log handler to sys.stderr.
|
||||
app.logger.addHandler(logging.StreamHandler())
|
||||
@@ -66,11 +68,11 @@ if app.config.get('UPLOAD_FOLDER'):
|
||||
class MyIndexView(IndexView):
|
||||
@expose('/')
|
||||
def index(self):
|
||||
return redirect('/caravel/welcome')
|
||||
return redirect('/superset/welcome')
|
||||
|
||||
appbuilder = AppBuilder(
|
||||
app, db.session,
|
||||
base_template='caravel/base.html',
|
||||
base_template='superset/base.html',
|
||||
indexview=MyIndexView,
|
||||
security_manager_class=app.config.get("CUSTOM_SECURITY_MANAGER"))
|
||||
|
||||
@@ -84,4 +86,4 @@ module_datasource_map = app.config.get("DEFAULT_MODULE_DS_MAP")
|
||||
module_datasource_map.update(app.config.get("ADDITIONAL_MODULE_DS_MAP"))
|
||||
SourceRegistry.register_sources(module_datasource_map)
|
||||
|
||||
from caravel import views, config # noqa
|
||||
from superset import views, config # noqa
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"presets" : ["airbnb", "es2015", "react"],
|
||||
}
|
||||
|
Before Width: | Height: | Size: 9.9 KiB After Width: | Height: | Size: 9.9 KiB |
|
Before Width: | Height: | Size: 459 KiB After Width: | Height: | Size: 459 KiB |
|
Before Width: | Height: | Size: 702 KiB After Width: | Height: | Size: 702 KiB |
|
Before Width: | Height: | Size: 50 KiB After Width: | Height: | Size: 50 KiB |
|
Before Width: | Height: | Size: 328 KiB After Width: | Height: | Size: 328 KiB |
|
Before Width: | Height: | Size: 102 KiB After Width: | Height: | Size: 102 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 4.6 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 552 KiB After Width: | Height: | Size: 552 KiB |
|
Before Width: | Height: | Size: 154 KiB After Width: | Height: | Size: 154 KiB |
|
Before Width: | Height: | Size: 190 KiB After Width: | Height: | Size: 190 KiB |
|
Before Width: | Height: | Size: 245 KiB After Width: | Height: | Size: 245 KiB |
|
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 133 KiB After Width: | Height: | Size: 133 KiB |
|
Before Width: | Height: | Size: 128 KiB After Width: | Height: | Size: 128 KiB |
|
Before Width: | Height: | Size: 141 KiB After Width: | Height: | Size: 141 KiB |
|
Before Width: | Height: | Size: 49 KiB After Width: | Height: | Size: 49 KiB |
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 106 KiB After Width: | Height: | Size: 106 KiB |
|
Before Width: | Height: | Size: 103 KiB After Width: | Height: | Size: 103 KiB |
|
Before Width: | Height: | Size: 49 KiB After Width: | Height: | Size: 49 KiB |
|
Before Width: | Height: | Size: 50 KiB After Width: | Height: | Size: 50 KiB |
|
Before Width: | Height: | Size: 27 KiB After Width: | Height: | Size: 27 KiB |