Compare commits
256 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
acc880c4df | ||
|
|
557b557503 | ||
|
|
3018356588 | ||
|
|
ede4dffcb7 | ||
|
|
cad392eb76 | ||
|
|
0296158100 | ||
|
|
b2a4692a02 | ||
|
|
2fbadea9e3 | ||
|
|
dc05be36a6 | ||
|
|
dac0d1d0dc | ||
|
|
459f7160ac | ||
|
|
aff524d843 | ||
|
|
3a91667e92 | ||
|
|
3e0d3584f7 | ||
|
|
1e47d6fb41 | ||
|
|
d5ba88b407 | ||
|
|
ce1e18b31b | ||
|
|
ec84aa7577 | ||
|
|
8b4d72cf32 | ||
|
|
85e6e65a47 | ||
|
|
7cad3655f5 | ||
|
|
b9e7f292c3 | ||
|
|
fc85034c60 | ||
|
|
f5e3d0cc02 | ||
|
|
fe377e8b94 | ||
|
|
5bb87138e9 | ||
|
|
579e58206e | ||
|
|
172b6ce892 | ||
|
|
0cc8eff1c3 | ||
|
|
3b023e5eaa | ||
|
|
615d8f1624 | ||
|
|
b4409ace21 | ||
|
|
dbee6aca1f | ||
|
|
acfe62eaf7 | ||
|
|
527a8af060 | ||
|
|
a5a931a670 | ||
|
|
2f05efaf12 | ||
|
|
83ef8a2e12 | ||
|
|
c564881867 | ||
|
|
b16930f35d | ||
|
|
2d910e3f07 | ||
|
|
daa1420c8e | ||
|
|
cea310e50b | ||
|
|
fcdd5c6752 | ||
|
|
2ace73e9a1 | ||
|
|
80cfb08794 | ||
|
|
1edc2b91cf | ||
|
|
1f58e18b6f | ||
|
|
f2bf316058 | ||
|
|
9cd38fa1ed | ||
|
|
edb0111775 | ||
|
|
de4f9e8d1a | ||
|
|
461e41cd61 | ||
|
|
716406198e | ||
|
|
68592aeddf | ||
|
|
b927ff6eef | ||
|
|
ce50e6e4fe | ||
|
|
167ed33bba | ||
|
|
0ee1abf31a | ||
|
|
6a0a1af67e | ||
|
|
f85481d51b | ||
|
|
00b6b0ac68 | ||
|
|
1546b1ae71 | ||
|
|
1e94498d9d | ||
|
|
0f7189b859 | ||
|
|
a6e0f1b75a | ||
|
|
543c22bb50 | ||
|
|
07e067cf0b | ||
|
|
6c256a34a9 | ||
|
|
6b2eb04a73 | ||
|
|
898d80ba38 | ||
|
|
ea8e4ad05b | ||
|
|
27aeac6859 | ||
|
|
8da371e324 | ||
|
|
0c59fe933d | ||
|
|
e169c67760 | ||
|
|
3a5a927dc6 | ||
|
|
2d419e4253 | ||
|
|
87869a29c9 | ||
|
|
544211f5ec | ||
|
|
f6ac95e2dd | ||
|
|
63bef2f844 | ||
|
|
4a8cd04de6 | ||
|
|
85806624db | ||
|
|
1ac2273984 | ||
|
|
a8c29c4ffe | ||
|
|
31af01c4f2 | ||
|
|
b1bba96d04 | ||
|
|
c5c730224e | ||
|
|
7441cf7d39 | ||
|
|
45c72d25df | ||
|
|
3fff631b32 | ||
|
|
bfa2891b23 | ||
|
|
5715f52fef | ||
|
|
1f2126f463 | ||
|
|
27ed0b37bf | ||
|
|
cdbd2f8507 | ||
|
|
e46ba2b4a4 | ||
|
|
1c338ba742 | ||
|
|
2b7673ad5d | ||
|
|
2f27353015 | ||
|
|
1b8c3f420a | ||
|
|
a3a070855c | ||
|
|
e84c6393b8 | ||
|
|
7413dd9f4b | ||
|
|
9cbd667eb7 | ||
|
|
37fb56c61c | ||
|
|
404a94cadb | ||
|
|
0807a8d016 | ||
|
|
4a9888157e | ||
|
|
83fbdcceac | ||
|
|
b070ef5fdb | ||
|
|
7d380dcd14 | ||
|
|
a15dbd992d | ||
|
|
52c5d235af | ||
|
|
495f6460a4 | ||
|
|
a96024d0e7 | ||
|
|
99b84d2909 | ||
|
|
24728b8b47 | ||
|
|
9750e49df8 | ||
|
|
bf31783d0c | ||
|
|
87eacf88c3 | ||
|
|
1dbfb99ead | ||
|
|
ff4020ea73 | ||
|
|
0ce7fc18a8 | ||
|
|
470a6e9d76 | ||
|
|
fc74fbeeaa | ||
|
|
9c6a5793b9 | ||
|
|
49b6b38741 | ||
|
|
a385ee9e97 | ||
|
|
f0917c62f2 | ||
|
|
94d20168da | ||
|
|
2d866e3ffa | ||
|
|
5d94d7067e | ||
|
|
7323f4c2ab | ||
|
|
eca6dfef6a | ||
|
|
761462ef93 | ||
|
|
98e83255e6 | ||
|
|
cbf3562a6f | ||
|
|
a2c41bbace | ||
|
|
2a12a3c702 | ||
|
|
2ab6a411f4 | ||
|
|
14ed10bdb0 | ||
|
|
49e6fd5bfb | ||
|
|
af872fa4d4 | ||
|
|
cec4cf014c | ||
|
|
783ad703d0 | ||
|
|
222671675c | ||
|
|
9a62d94630 | ||
|
|
c3edc6e24b | ||
|
|
119b0c55e9 | ||
|
|
c14c7edc5e | ||
|
|
e3b296c558 | ||
|
|
c2d29fb54b | ||
|
|
7aab8b0ae3 | ||
|
|
861a3bd4ae | ||
|
|
9bc7ad9cd5 | ||
|
|
a1e3fc1c23 | ||
|
|
242869db3a | ||
|
|
8924bb79e7 | ||
|
|
a0d103dac3 | ||
|
|
d52b299df8 | ||
|
|
092432f04f | ||
|
|
ea8e6634d6 | ||
|
|
3e6f90cf72 | ||
|
|
16731056ed | ||
|
|
0712894353 | ||
|
|
36fad803ed | ||
|
|
6732f01cb7 | ||
|
|
bb04e6fcfa | ||
|
|
007ee88d33 | ||
|
|
7a5bb94754 | ||
|
|
e06a0cd89b | ||
|
|
b6cba13293 | ||
|
|
d929bbfe30 | ||
|
|
bf67d64708 | ||
|
|
92aa1a6124 | ||
|
|
733ab8014b | ||
|
|
6aaa49f0bf | ||
|
|
638f27c2df | ||
|
|
84a3b55912 | ||
|
|
552d46479b | ||
|
|
fa9c066ffe | ||
|
|
e1e20b8757 | ||
|
|
2fb94a89e2 | ||
|
|
7a9604a3c9 | ||
|
|
e099088012 | ||
|
|
34e107e7d3 | ||
|
|
2254a4d0b4 | ||
|
|
9f7486f402 | ||
|
|
699602d1c5 | ||
|
|
2993ff1d75 | ||
|
|
afb3c24d5a | ||
|
|
8ef730b5fe | ||
|
|
866cfe5279 | ||
|
|
68c2eab6b9 | ||
|
|
aeda5bd260 | ||
|
|
a95cd71456 | ||
|
|
34d0dd9d6e | ||
|
|
401d9afd54 | ||
|
|
74edb936a5 | ||
|
|
c1558578d7 | ||
|
|
3597fdb7f8 | ||
|
|
43f2a379a1 | ||
|
|
69702e3a19 | ||
|
|
eb0655cf85 | ||
|
|
d8864bc92b | ||
|
|
89fc9d7c80 | ||
|
|
76aa9f7e10 | ||
|
|
abd0974897 | ||
|
|
c4e943a24f | ||
|
|
a3106bcb3d | ||
|
|
b045075a96 | ||
|
|
09d597f3ad | ||
|
|
9d4c3d83d0 | ||
|
|
95580a004f | ||
|
|
723f90755e | ||
|
|
324205f77a | ||
|
|
0a40d8ce8f | ||
|
|
168a25239e | ||
|
|
7eef46e941 | ||
|
|
50da4f8c07 | ||
|
|
2d0ebeae1b | ||
|
|
1a16491971 | ||
|
|
7f4f250970 | ||
|
|
25acb78071 | ||
|
|
e822d5a1b7 | ||
|
|
32fc0ff6d0 | ||
|
|
94dde075b3 | ||
|
|
65e92327ab | ||
|
|
0be02e67a5 | ||
|
|
7327c97e4c | ||
|
|
03b21dcf0a | ||
|
|
dc98c6739f | ||
|
|
fcb870728d | ||
|
|
7919428a1e | ||
|
|
3496a80f5a | ||
|
|
56b917a5c2 | ||
|
|
18c43aaea2 | ||
|
|
c43fc38f69 | ||
|
|
c07f0ab9c7 | ||
|
|
1c429b27bc | ||
|
|
b7019ad4f3 | ||
|
|
84e8f741ae | ||
|
|
e3a9b393c2 | ||
|
|
16aba517e4 | ||
|
|
205928e6df | ||
|
|
39ce4aa049 | ||
|
|
cef4a8296a | ||
|
|
b370ef0229 | ||
|
|
6b80f5bb35 | ||
|
|
bdae570a69 | ||
|
|
face5245a9 | ||
|
|
db1ed2a765 | ||
|
|
10982dec3c | ||
|
|
6825e75681 |
@@ -27,7 +27,6 @@ exclude_paths:
|
||||
- "**.gz"
|
||||
- "env/"
|
||||
- "tests/"
|
||||
- "superset/ascii_art.py"
|
||||
- "superset/assets/images/"
|
||||
- "superset/assets/vendor/"
|
||||
- "superset/assets/node_modules/"
|
||||
|
||||
1
.gitignore
vendored
@@ -31,3 +31,4 @@ app.db
|
||||
node_modules
|
||||
npm-debug.log
|
||||
yarn.lock
|
||||
superset/assets/version_info.json
|
||||
|
||||
@@ -17,7 +17,6 @@ pep8:
|
||||
ignore-paths:
|
||||
- docs
|
||||
- superset/migrations/env.py
|
||||
- superset/ascii_art.py
|
||||
ignore-patterns:
|
||||
- ^example/doc_.*\.py$
|
||||
- (^|/)docs(/|$)
|
||||
|
||||
@@ -34,5 +34,4 @@ install:
|
||||
- pip install --upgrade pip
|
||||
- pip install tox tox-travis
|
||||
- rm -rf ~/.nvm && git clone https://github.com/creationix/nvm.git ~/.nvm && (cd ~/.nvm && git checkout `git describe --abbrev=0 --tags`) && source ~/.nvm/nvm.sh && nvm install $TRAVIS_NODE_VERSION
|
||||
- npm install
|
||||
script: tox -e $TOX_ENV
|
||||
|
||||
142
CHANGELOG.md
@@ -1,5 +1,147 @@
|
||||
## Change Log
|
||||
|
||||
### 0.15.1 (2016/12/28 21:29 +00:00)
|
||||
- [092432f](https://github.com/airbnb/superset/commit/092432f04f0033e60493f009728a7bfd6a744b22) v0.15.1 (@mistercrunch)
|
||||
- [ea8e663](https://github.com/airbnb/superset/commit/ea8e6634d6c304cde3a42c65d37ec694f76b8cec) read anon user role from config, remove reference to public role (#1878) (@willgroves)
|
||||
- [3e6f90c](https://github.com/airbnb/superset/commit/3e6f90cf722f205e1f13ef7228be6d0d767c1d1d) Upgrading pydruid version and adopt 'merge' flag during refresh_druid operation (#1879) (@dkhwangbo)
|
||||
- [1673105](https://github.com/airbnb/superset/commit/16731056edf25cd4422fa674de827215df9027b1) [sqllab] async queries - better error handling (#1853) (@mistercrunch)
|
||||
- [0712894](https://github.com/airbnb/superset/commit/0712894353825ea2faa19d8000ca031c44debf39) Improving database logging by adding duration, referrer and post data (#1830) (@mistercrunch)
|
||||
- [36fad80](https://github.com/airbnb/superset/commit/36fad803edf6666188746f270e498e03c2df363c) sqllab: don't hold database deletion because of query reference (#1863) (@xrmx)
|
||||
- [6732f01](https://github.com/airbnb/superset/commit/6732f01cb7bb08e1b180bb74f263bf50317d7462) Enable freeform-select with fetched column values for filter values (#1697) (@vera-liu)
|
||||
- [bb04e6f](https://github.com/airbnb/superset/commit/bb04e6fcfa1042b4532b50d8898555813be7fa29) Use APP_ICON in template (#1855) (@szmate1618)
|
||||
- [007ee88](https://github.com/airbnb/superset/commit/007ee88d33f92e6d052122b35ccad84d176029a7) [explorev2] improving the scrolling/scrollbars placement (#1840) (@mistercrunch)
|
||||
|
||||
### airbnb_prod.0.15.0.1 (2016/12/15 22:06 +00:00)
|
||||
- [7a5bb94](https://github.com/airbnb/superset/commit/7a5bb947542fdc20e2ec70e18b1cf418b8d1dba8) Stop ChartContainer from rendering twice on chartStatus change (#1828) (@vera-liu)
|
||||
- [e06a0cd](https://github.com/airbnb/superset/commit/e06a0cd89bc84f3a7e75ee6f03df8c9c3a2badeb) Add force_ctas_schema to query model when enabled (#1825) (@vera-liu)
|
||||
- [b6cba13](https://github.com/airbnb/superset/commit/b6cba13293101f3022c16e120e96383b59cae03e) [explorev2] enabling redux dev tools (#1842) (@mistercrunch)
|
||||
- [d929bbf](https://github.com/airbnb/superset/commit/d929bbfe3010f795cdf9ef28b48e9e249cf7ef86) [explorev2] making QueryAndSaveBtns disabled while running queries (#1841) (@mistercrunch)
|
||||
- [bf67d64](https://github.com/airbnb/superset/commit/bf67d64708834800669e8be30ffa695d7a6db022) [explorev2] making Datasource an Viz controls not clearable (#1845) (@mistercrunch)
|
||||
- [92aa1a6](https://github.com/airbnb/superset/commit/92aa1a612476fdef5f19eb68f961324a333b346c) Permissions refactoring, optimizations and unit testing. (#1798) (@bkyryliuk)
|
||||
- [733ab80](https://github.com/airbnb/superset/commit/733ab8014bd83ae4e0e4961b9c454ad238556bd8) [explorev2] using a loader to load the explorev2 specific css (#1843) (@mistercrunch)
|
||||
- [6aaa49f](https://github.com/airbnb/superset/commit/6aaa49f0bf182f449a970a1e15d86111987dde32) Change default gunicorn address (#1838) (@amancevice)
|
||||
- [638f27c](https://github.com/airbnb/superset/commit/638f27c2df6540a50d5cba92847bc7000b33cb1b) [sqllab] Fix sql expression bug with count distinct metrics (#1805) (@vera-liu)
|
||||
- [84a3b55](https://github.com/airbnb/superset/commit/84a3b559128bb7717869f5fa7339bfe1ac4003cc) [explorev2] remove unused file SqlClause.jsx (#1839) (@mistercrunch)
|
||||
- [552d464](https://github.com/airbnb/superset/commit/552d46479bbf819e4fa7fb811ec05cff104a840c) [explorev2] no bootstrap data, just metadata in exploreV2 (#1827) (@mistercrunch)
|
||||
- [fa9c066](https://github.com/airbnb/superset/commit/fa9c066ffe6b71a36f97e2e0d9fdb94b94183ca6) Add email-to option in action buttons for dashboard and slice (#1705) (@vera-liu)
|
||||
- [e1e20b8](https://github.com/airbnb/superset/commit/e1e20b875748f677312d8c5ed3daf87113143b63) Sort searched queries by recency (#1735) (@vera-liu)
|
||||
- [2fb94a8](https://github.com/airbnb/superset/commit/2fb94a89e2538710c5293404acaf6d8f0ef847ea) Add ADDITIONAL_MIDDLEWARE option to config (#1832) (@jr-minnaar)
|
||||
- [7a9604a](https://github.com/airbnb/superset/commit/7a9604a3c918ce5fad1010e9e95fbf961a95420d) Workaround for slices "Not Found" issue in IE 11 (#1821) (@rlei)
|
||||
- [e099088](https://github.com/airbnb/superset/commit/e0990880121643b63f66cc18fe917ea9fbe98554) [hotfix] fixing the build (@mistercrunch)
|
||||
- [34e107e](https://github.com/airbnb/superset/commit/34e107e7d35b7d48385a13268b2ed713a2808114) [explore-v2] add config option for explore v2 beta users, and send through v2 path (#1671) (@ascott)
|
||||
|
||||
### 0.15.0 (2016/12/12 19:30 +00:00)
|
||||
- [2254a4d](https://github.com/airbnb/superset/commit/2254a4d0b4480da7563c7c3afdeadecf66b08cf5) v0.15.0 (@mistercrunch)
|
||||
- [9f7486f](https://github.com/airbnb/superset/commit/9f7486f4029fcbc18dfdeaeb4ff3c3c07242ad2b) remove extra call to get_viz in explorev2 (#1812) (@vera-liu)
|
||||
- [699602d](https://github.com/airbnb/superset/commit/699602d1c5bdd7d15f6adb5f22e458c268bc1306) Add tooltips to RunAsync and CTAS button (#1792) (@vera-liu)
|
||||
- [2993ff1](https://github.com/airbnb/superset/commit/2993ff1d75ff2391ffab388544f198cbb78364a9) Add NVD3's bullet chart (#1775) (@darabos)
|
||||
- [afb3c24](https://github.com/airbnb/superset/commit/afb3c24d5a4951e7cc5a5714a79eb201e0b1aa24) Showing more fields in DatabaseView (@mistercrunch)
|
||||
- [8ef730b](https://github.com/airbnb/superset/commit/8ef730b5feb8e27116552ca863e8dac42b1aa6ec) Added timer to explore v2 and share it with sqllab (#1802) (@vera-liu)
|
||||
- [866cfe5](https://github.com/airbnb/superset/commit/866cfe52794d5c6df9f85f088243fa85f66eaec7) Add schema name to output column in query history (#1790) (@vera-liu)
|
||||
- [68c2eab](https://github.com/airbnb/superset/commit/68c2eab6b93a13966b19aae20fb6c82fc34e3bcf) [hotfix] handling 0% change in big number with trendline (#1801) (@mistercrunch)
|
||||
- [aeda5bd](https://github.com/airbnb/superset/commit/aeda5bd2606946a5cb6fd7dd5ecd4b73413c79a9) [sqllab] config item for SQLLAB_DEFAULT_DBID (#1793) (@mistercrunch)
|
||||
- [a95cd71](https://github.com/airbnb/superset/commit/a95cd71456a0b24c08d4d020238fd9d89b2c9cd7) Add viz thumbnails to viz_type select (#1794) (@vera-liu)
|
||||
- [34d0dd9](https://github.com/airbnb/superset/commit/34d0dd9d6e074e41968c39247d66e19fd551c57b) adjust header nav links so they are all aligned on the base line (#1786) (@ascott)
|
||||
- [401d9af](https://github.com/airbnb/superset/commit/401d9afd54ce46f5c63a821094350098f07823a1) [ui] update logo, favicon, and new primary color (#1781) (@ascott)
|
||||
- [74edb93](https://github.com/airbnb/superset/commit/74edb936a599ed54528389814ddf83e74f8452fd) [WIP] Add http to copied url and move function to componentWillReceiveProps (#1780) (@vera-liu)
|
||||
- [c155857](https://github.com/airbnb/superset/commit/c1558578d7c555fb7bb9ee30eb98bad4d28fbd07) [explorev2] Breaking down large files, fixing JS warnings (#1773) (@mistercrunch)
|
||||
|
||||
### airbnb_prod.0.13.0.3 (2016/12/06 07:18 +00:00)
|
||||
- [3597fdb](https://github.com/airbnb/superset/commit/3597fdb7f869929e0b09ff0144ff430b81e67853) Filter table list based on the user permissions. (#1769) (@bkyryliuk)
|
||||
|
||||
### airbnb_prod.0.13.0.2 (2016/12/06 01:17 +00:00)
|
||||
- [43f2a37](https://github.com/airbnb/superset/commit/43f2a379a1b88b260d0a4bdeac97b3cb4478afcf) Make cell-click filter in table viz optional (#1762) (@vera-liu)
|
||||
- [69702e3](https://github.com/airbnb/superset/commit/69702e3a1956ef5587e5aea2bffb3720b9b4cd35) Create users if not found. (#1753) (@bkyryliuk)
|
||||
- [eb0655c](https://github.com/airbnb/superset/commit/eb0655cf85daad4329cf8630fe99e2a50d0e7e5a) [sqllab] Fixed js error when results are not available (#1715) (@vera-liu)
|
||||
- [d8864bc](https://github.com/airbnb/superset/commit/d8864bc92b7566fa4ebb9d4863d75bd5eaa1e9c4) Enable overwrite sql in QueryHistory (#1731) (@vera-liu)
|
||||
- [89fc9d7](https://github.com/airbnb/superset/commit/89fc9d7c80564e2f02949ed33113a63fc1e898e4) Make entire menuitem clickable for copy query (#1747) (@vera-liu)
|
||||
- [76aa9f7](https://github.com/airbnb/superset/commit/76aa9f7e1047de5e41e8bd5bfee610fcd569b8ad) [explorev2] fix textfield and druid bug (#1732) (@vera-liu)
|
||||
- [abd0974](https://github.com/airbnb/superset/commit/abd097489738eacc37fcd5dc9605d483c84071f6) Fix superset cli for python3 (#1760) (@xrmx)
|
||||
- [c4e943a](https://github.com/airbnb/superset/commit/c4e943a24fe89824e59a105d6ce6e5d9255717c1) [sqllab] making 'click to retrieve results' a button (#1737) (@mistercrunch)
|
||||
- [a3106bc](https://github.com/airbnb/superset/commit/a3106bcb3d0bc6fb0e5df2c356d453563a07c8a2) [bugfix] bignumber comparison wrong with neg values (#1743) (@mistercrunch)
|
||||
- [b045075](https://github.com/airbnb/superset/commit/b045075a96232f1e2f2e14bdaa171eba67b0fa29) Sankey Tooltip fix (#1748) (#1750) (@ddol)
|
||||
- [09d597f](https://github.com/airbnb/superset/commit/09d597f3adf4b238dc2a43b802fdda22b451dfe5) Prevent duplicated view_menu perms (#1751) (@bkyryliuk)
|
||||
- [9d4c3d8](https://github.com/airbnb/superset/commit/9d4c3d83d0907925f1f37cbe48c5decd2c54639f) Update role based on usernames not emails. (#1749) (@bkyryliuk)
|
||||
- [95580a0](https://github.com/airbnb/superset/commit/95580a004fe3322ff1c623ac4eec72150e1355b5) [explorev2] cosmetic, smaller size for input text (#1746) (@mistercrunch)
|
||||
- [723f907](https://github.com/airbnb/superset/commit/723f90755e89e46287e212b18844c9f7abc6cf60) Fixing the sourcemap in dev mode (#1744) (@mistercrunch)
|
||||
- [324205f](https://github.com/airbnb/superset/commit/324205f77ac7a77b6546da482979842d58ce9fbb) [sqllab] bugfix where a query has the same alias twice as output (#1734) (@mistercrunch)
|
||||
- [0a40d8c](https://github.com/airbnb/superset/commit/0a40d8ce8f08ba6edc18370032aa85571495b570) Rremove unused symlinks (#1736) (@yolken)
|
||||
- [168a252](https://github.com/airbnb/superset/commit/168a25239e712fe9eccf676f26df75fd91bd126f) State that npm should be between 3.9 and 4 (@bkyryliuk)
|
||||
- [7eef46e](https://github.com/airbnb/superset/commit/7eef46e9413b01ec15be515567a9619c695d5501) Adding links pointing to the new user profile page (#1704) (@mistercrunch)
|
||||
- [50da4f8](https://github.com/airbnb/superset/commit/50da4f8c0708f91ff24c0abd7189a137a1415bf6) Support running superset via pex (#1713) (@yolken)
|
||||
|
||||
### airbnb_prod.0.13.0.1 (2016/12/01 19:59 +00:00)
|
||||
- [2d0ebea](https://github.com/airbnb/superset/commit/2d0ebeae1bfd5e385000c9aa952891cf474821c6) [explorev2] Make chart container more responsive (#1724) (@vera-liu)
|
||||
- [1a16491](https://github.com/airbnb/superset/commit/1a164919715d6eaf1a999b97daaa53acb94a827d) Display full table name (schema + name) if possible. (#1728) (@bkyryliuk)
|
||||
- [7f4f250](https://github.com/airbnb/superset/commit/7f4f25097046dac9b436ef87f041debe2713827a) Redirects to login page if user not logged in at welcome page (#1723) (@vera-liu)
|
||||
- [25acb78](https://github.com/airbnb/superset/commit/25acb78071acc2eec8b44cb7019f269c1b2a6deb) Pass schema to the select star query. (#1714) (@bkyryliuk)
|
||||
- [e822d5a](https://github.com/airbnb/superset/commit/e822d5a1b7eb8f0cabcfcc85f5201df8199db796) Make edit / add / delete perms available to all users. (#1722) (@bkyryliuk)
|
||||
- [32fc0ff](https://github.com/airbnb/superset/commit/32fc0ff6d0b437766d16db128a7b1d40a09080bb) [Bugfix] autocomplete in sqleditor doesnot use newly loaded table columns (#1712) (@vera-liu)
|
||||
|
||||
### 0.14.1 (2016/11/29 23:57 +00:00)
|
||||
- [94dde07](https://github.com/airbnb/superset/commit/94dde075b3eab41797725e1e02c7f87b6b45471a) v0.14.1 (@mistercrunch)
|
||||
- [65e9232](https://github.com/airbnb/superset/commit/65e92327abdd0d521a9dcb65319165b163da356c) Druid hotfix. (#1710) (@bkyryliuk)
|
||||
- [0be02e6](https://github.com/airbnb/superset/commit/0be02e67a554d323efe4ed119a59bba53c559477) Updating CHANGELOG 0.14.0 (@mistercrunch)
|
||||
- [7327c97](https://github.com/airbnb/superset/commit/7327c97e4c5efcf7c5b080a1e534d7b44129bb8b) v0.14.0 (@mistercrunch)
|
||||
|
||||
### 0.14.0 (2016/11/29 23:03 +00:00)
|
||||
- [03b21dc](https://github.com/airbnb/superset/commit/03b21dcf0a3fc18e1290f7770004d3b74df8cef3) [explorev2] Bug fixes in Save Modal (#1707) (@vera-liu)
|
||||
- [dc98c67](https://github.com/airbnb/superset/commit/dc98c6739fcccc8edc60ef7e761cb1491005f644) Implement table name extraction. (#1598) (@bkyryliuk)
|
||||
- [fcb8707](https://github.com/airbnb/superset/commit/fcb870728db69bbee092d20c3f78cb7785fe2e61) Add per schema permissions. (#1698) (@bkyryliuk)
|
||||
- [7919428](https://github.com/airbnb/superset/commit/7919428a1e02457a50ae00439e827f996403f71c) Vliu explorev2 bugs (#1701) (@vera-liu)
|
||||
- [3496a80](https://github.com/airbnb/superset/commit/3496a80f5a85a0b66e59ec259ed13ca9ba3d5ba0) make stack trace more readable (#1672) (@ascott)
|
||||
- [56b917a](https://github.com/airbnb/superset/commit/56b917a5c206d3083d9d9d3d0606b976c64b6044) [explore-v2] fix errors on table view (#1675) (@ascott)
|
||||
- [18c43aa](https://github.com/airbnb/superset/commit/18c43aaea2f889e50211b22f0a68269f314bcafa) make chart title larger, fix explore actions btn spacing (#1680) (@ascott)
|
||||
- [c43fc38](https://github.com/airbnb/superset/commit/c43fc38f69d6284729cd47368e796117adcc1d1b) [druid] fix having clause (#1694) (@mistercrunch)
|
||||
- [c07f0ab](https://github.com/airbnb/superset/commit/c07f0ab9c72430f5892f701d6cba35718ef322ad) Config programmatic roles in the config.py (#1664) (@bkyryliuk)
|
||||
- [1c429b2](https://github.com/airbnb/superset/commit/1c429b27bc425aa8ba0f8cc6b43887cfb91dcd15) Fixing issue #1689 (#1696) (@mistercrunch)
|
||||
- [b7019ad](https://github.com/airbnb/superset/commit/b7019ad4f343ecbd5d33ce4a5800a72a9f4301b6) [sqllab] bugfix SouthPane doesn't update as expected (#1699) (@mistercrunch)
|
||||
- [84e8f74](https://github.com/airbnb/superset/commit/84e8f741ae969888c4f2501ada132f58bdcfb249) Add 'Save As' feature for dashboards (#1669) (@the-dcruz)
|
||||
- [e3a9b39](https://github.com/airbnb/superset/commit/e3a9b393c26ab173fe3ffe3dd14191705cab7119) Missing merge_perm function. Fixes 1691. (#1692) (@niconoe)
|
||||
- [16aba51](https://github.com/airbnb/superset/commit/16aba517e4640300c9a71f6186776671540bc488) Use smaller size for node max_old_space_size (#1679) (@xrmx)
|
||||
- [205928e](https://github.com/airbnb/superset/commit/205928e6df892060cdd3ffe0af6a1217a848f301) docs: fix python-redis link markup (#1683) (@xrmx)
|
||||
- [39ce4aa](https://github.com/airbnb/superset/commit/39ce4aa049fffef3b9f6e368d64130ae85cb86d8) Added filter in ControlPanelsContainer for explore V2 (#1647) (@vera-liu)
|
||||
- [cef4a82](https://github.com/airbnb/superset/commit/cef4a8296a6a9d46503dd63e268be3a35e9e8e91) [sqllab] adding a sql preprocessor for Presto (#1670) (@mistercrunch)
|
||||
- [b370ef0](https://github.com/airbnb/superset/commit/b370ef0229377c6b85f78d9ba080d00ff6dba58e) Rerender chart without clicking query button for fields (#1658) (@vera-liu)
|
||||
- [6b80f5b](https://github.com/airbnb/superset/commit/6b80f5bb35e497c79fe458b25ba87266e3c0f3bf) Get sections to render when switching datasource (#1660) (@vera-liu)
|
||||
- [bdae570](https://github.com/airbnb/superset/commit/bdae570a69cd948987b05fed2e7653a221ef0d80) Temperary fix of a slice bug (#1648) (@vera-liu)
|
||||
- [face524](https://github.com/airbnb/superset/commit/face5245a99d13089b9fa4cfa7521ee2ca6b209c) Make explore container resize with browser window (#1608) (@vera-liu)
|
||||
- [db1ed2a](https://github.com/airbnb/superset/commit/db1ed2a765d317e55377f2550f169b78f981b4a0) Calculate height dynamically using jquery for scrollable sqllab (#1611) (@vera-liu)
|
||||
- [10982de](https://github.com/airbnb/superset/commit/10982dec3c69f1bed709b38616417eada995d2f4) Make QueryTable scrollable in Query Search page (#1656) (@vera-liu)
|
||||
- [6825e75](https://github.com/airbnb/superset/commit/6825e75681b1249d066d9fa0bf0dca9f1824bb24) Fixed bug with querylink passing sql object instead of string (#1659) (@vera-liu)
|
||||
- [bd6a439](https://github.com/airbnb/superset/commit/bd6a439e0b2a3a76f8aece91f11a7eee2ebf6d29) [QuerySearch] Add loading status to QuerySearch page (#1657) (@vera-liu)
|
||||
- [c90dd49](https://github.com/airbnb/superset/commit/c90dd4902f18bb11c46bc38b8f70bfc14cfc2171) Programatically sync the role with user list. (#1619) (@bkyryliuk)
|
||||
- [868e5c4](https://github.com/airbnb/superset/commit/868e5c45fed8e090750dffe88660f3943f373c19) Redirect URL requests with "caravel" to "superset" (#1651) (@kingo55)
|
||||
- [7e1852e](https://github.com/airbnb/superset/commit/7e1852ee883628d38b2e3bb71e2b2b03fad41ba3) User profile pages (favorites, created content, recent activity, security & access) (#1615) (@mistercrunch)
|
||||
- [5ae98bc](https://github.com/airbnb/superset/commit/5ae98bc7c9b432683d03d30a30631a6efd7a78a3) Improving jinja2 security by using SandboxedEnvironment (#1632) (@mistercrunch)
|
||||
- [1624e7d](https://github.com/airbnb/superset/commit/1624e7de7dd50f1c4f5fdd9153adac4ba5b983d2) Add all_tables endpoint to allow airpal / superset perm sync. (#1614) (@bkyryliuk)
|
||||
- [7a98f84](https://github.com/airbnb/superset/commit/7a98f848909ca2099e29d3f485fd299037142e65) Admin / Alpha permission cleanup and fixes. (#1645) (@bkyryliuk)
|
||||
- [9b18128](https://github.com/airbnb/superset/commit/9b181280d44171cb0c724a07f50488eb08f98e72) include jQuery and bootstrap (#1642) (@ascott)
|
||||
- [38e94b9](https://github.com/airbnb/superset/commit/38e94b9e43f82c682f311fe1563c8f502ae4157a) Save modal component for explore v2 (#1612) (@vera-liu)
|
||||
- [dc25bc6](https://github.com/airbnb/superset/commit/dc25bc6f4d5eeb74665dd353bafda5d97ef5faa1) Fix alpha permission checks. (#1641) (@bkyryliuk)
|
||||
- [f64a205](https://github.com/airbnb/superset/commit/f64a2056038e96883e31419df5fcd4fa396dffb6) Use Alert for visualization error (#1639) (@vera-liu)
|
||||
- [a8480f5](https://github.com/airbnb/superset/commit/a8480f54922775992a28edd7878b1cfa7690264e) Added Alert for ControlPanel and ChartContainer (#1626) (@vera-liu)
|
||||
- [0acf26b](https://github.com/airbnb/superset/commit/0acf26b37c7a59cb976cf7a929caf7cc5a1a968e) Fixed a bug with switching viz_type in exploreV2 (#1631) (@vera-liu)
|
||||
- [2c068a1](https://github.com/airbnb/superset/commit/2c068a1a1583fa61db2f1797b0fcb2618cd6dbe3) increase space between fieldsset rows (#1629) (@ascott)
|
||||
- [b961c95](https://github.com/airbnb/superset/commit/b961c95121e5e4d4342a2926746dbf8a62bd77ea) dim visualization during refresh (#1636) (@mistercrunch)
|
||||
- [8269321](https://github.com/airbnb/superset/commit/82693211f0545affbdc306561a1abb4478c2de9a) Update faq.rst (#1637) (@dodysw)
|
||||
- [e546746](https://github.com/airbnb/superset/commit/e5467462cb73630a9b487891845ab1f01245f2a8) Make nvd3 refresh smoother. (#1618) (@the-dcruz)
|
||||
- [ab5a410](https://github.com/airbnb/superset/commit/ab5a4102cd8921ca2df234bfa6133973ba83a425) [dashboard] give user feedback when there are unsaved changes (#1633) (@ascott)
|
||||
- [d5ef937](https://github.com/airbnb/superset/commit/d5ef937b315f4afc679349369b4e7ac7455748f0) Fixed bugs with viz in exploreV2 (#1609) (@vera-liu)
|
||||
- [bce02e3](https://github.com/airbnb/superset/commit/bce02e3f518237c03273e3ed4d9d1a13d9f8f6a9) [security] improving the security scheme (#1587) (@mistercrunch)
|
||||
- [aad9744](https://github.com/airbnb/superset/commit/aad9744d85b50721d55d5770aad70ba1ee397ede) add new screenshots (#1589) (@ascott)
|
||||
- [506b781](https://github.com/airbnb/superset/commit/506b781f3a6048b433c12d25c1dbce614b5bd31b) [explore-v2] add fave star and edit button to chart header (#1623) (@ascott)
|
||||
- [267fd5b](https://github.com/airbnb/superset/commit/267fd5b9bc4f21a55c4664ae8c3ee717cc1be82c) [table viz] adding support for pagination (#1616) (@mistercrunch)
|
||||
- [c362f28](https://github.com/airbnb/superset/commit/c362f2869e012a4eeb9b76ff654ee3e82a190979) More Dashboard UX unit tests (#1603) (@mistercrunch)
|
||||
- [4f7f437](https://github.com/airbnb/superset/commit/4f7f43752798f57daa8cd8b8ed8a9cbc9c948000) Vliu put datasource in store (#1610) (@vera-liu)
|
||||
- [ab5da5b](https://github.com/airbnb/superset/commit/ab5da5ba2811ac6c2350c7d0534dd209906318af) [table viz] allow sorting on any column (#1601) (@mistercrunch)
|
||||
- [7531bb8](https://github.com/airbnb/superset/commit/7531bb89429547fb541c36fe365791cd742d82a1) Fixed dashboard controls for standalone bug (#1617) (@vera-liu)
|
||||
- [811ee8c](https://github.com/airbnb/superset/commit/811ee8ccdc76a2630a4c8014df26558391b981fe) Deleted unused components in exploreV2 (#1613) (@vera-liu)
|
||||
- [51cb485](https://github.com/airbnb/superset/commit/51cb485ce3e8cb80c72ec8c732281a78441396fd) Add standalone to reactified dashboard page (#1596) (@vera-liu)
|
||||
- [83d08b8](https://github.com/airbnb/superset/commit/83d08b8b8f7c73cbf4de25cadeab93dd3fdfc2fc) Get query button working in explorev2 (#1581) (@vera-liu)
|
||||
- [ed3d44d](https://github.com/airbnb/superset/commit/ed3d44d5919fc2ba739cf8d82e75e2680630646d) Changelog entries for 0.13.2 (@mistercrunch)
|
||||
|
||||
|
||||
### 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)
|
||||
|
||||
@@ -47,9 +47,96 @@ If you are proposing a feature:
|
||||
- Remember that this is a volunteer-driven project, and that
|
||||
contributions are welcome :)
|
||||
|
||||
## Latest Documentation
|
||||
## Documentation
|
||||
|
||||
Latest documentation and tutorial are available [here](http://airbnb.io/superset)
|
||||
The latest documentation and tutorial are available [here](http://airbnb.io/superset).
|
||||
|
||||
Contributing to the official documentation is relatively easy, once you've setup
|
||||
your environment and done an edit end-to-end. The docs can be found in the
|
||||
`docs/` subdirectory of the repository, and are written in the
|
||||
[reStructuredText format](https://en.wikipedia.org/wiki/ReStructuredText) (.rst).
|
||||
If you've written Markdown before, you'll find the reStructuredText format familiar.
|
||||
|
||||
Superset uses [Sphinx](http://www.sphinx-doc.org/en/1.5.1/) to convert the rst files
|
||||
in `docs/` to the final HTML output users see.
|
||||
|
||||
Before you start changing the docs, you'll want to
|
||||
[fork the Superset project on Github](https://help.github.com/articles/fork-a-repo/).
|
||||
Once that new repository has been created, clone it on your local machine:
|
||||
|
||||
git clone git@github.com:your_username/superset.git
|
||||
|
||||
At this point, you may also want to create a
|
||||
[Python virtual environment](http://docs.python-guide.org/en/latest/dev/virtualenvs/)
|
||||
to manage the Python packages you're about to install:
|
||||
|
||||
virtualenv superset-dev
|
||||
source superset-dev/bin/activate
|
||||
|
||||
Finally, to make changes to the rst files and build the docs using Sphinx,
|
||||
you'll need to install a handful of dependencies from the repo you cloned:
|
||||
|
||||
cd superset
|
||||
pip install -r dev-reqs-for-docs.txt
|
||||
|
||||
To get the feel for how to edit and build the docs, let's edit a file, build
|
||||
the docs and see our changes in action. First, you'll want to
|
||||
[create a new branch](https://git-scm.com/book/en/v2/Git-Branching-Basic-Branching-and-Merging)
|
||||
to work on your changes:
|
||||
|
||||
git checkout -b changes-to-docs
|
||||
|
||||
Now, go ahead and edit one of the files under `docs/`, say `docs/tutorial.rst`
|
||||
- change it however you want. Check out the
|
||||
[ReStructuredText Primer](http://docutils.sourceforge.net/docs/user/rst/quickstart.html)
|
||||
for a reference on the formatting of the rst files.
|
||||
|
||||
Once you've made your changes, run this command from the root of the Superset
|
||||
repo to convert the docs into HTML:
|
||||
|
||||
python setup.py build_sphinx
|
||||
|
||||
You'll see a lot of output as Sphinx handles the conversion. After it's done, the
|
||||
HTML Sphinx generated should be in `docs/_build/html`. Go ahead and navigate there
|
||||
and start a simple web server so we can check out the docs in a browser:
|
||||
|
||||
cd docs/_build/html
|
||||
python -m SimpleHTTPServer
|
||||
|
||||
This will start a small Python web server listening on port 8000. Point your
|
||||
browser to [http://localhost:8000/](http://localhost:8000/), find the file
|
||||
you edited earlier, and check out your changes!
|
||||
|
||||
If you've made a change you'd like to contribute to the actual docs, just commit
|
||||
your code, push your new branch to Github:
|
||||
|
||||
git add docs/tutorial.rst
|
||||
git commit -m 'Awesome new change to tutorial'
|
||||
git push origin changes-to-docs
|
||||
|
||||
Then, [open a pull request](https://help.github.com/articles/about-pull-requests/).
|
||||
|
||||
If you're adding new images to the documentation, you'll notice that the images
|
||||
referenced in the rst, e.g.
|
||||
|
||||
.. image:: _static/img/tutorial/tutorial_01_sources_database.png
|
||||
|
||||
aren't actually included in that directory. _Instead_, you'll want to add and commit
|
||||
images (and any other static assets) to the _superset/assets/images_ directory.
|
||||
When the docs are being pushed to [airbnb.io](http://airbnb.io/superset/), images
|
||||
will be moved from there to the _\_static/img_ directory, just like they're referenced
|
||||
in the docs.
|
||||
|
||||
For example, the image referenced above actually lives in
|
||||
|
||||
superset/assets/images/tutorial
|
||||
|
||||
Since the image is moved during the documentation build process, the docs reference the
|
||||
image in
|
||||
|
||||
_static/img/tutorial
|
||||
|
||||
instead.
|
||||
|
||||
## Setting up a Python development environment
|
||||
|
||||
@@ -91,7 +178,7 @@ While these may be phased out over time, these packages are currently not
|
||||
managed with npm.
|
||||
|
||||
### Node/npm versions
|
||||
Make sure you are using recent versions of node and npm. No problems have been found with node>=5.10 and npm>=3.9.
|
||||
Make sure you are using recent versions of node and npm. No problems have been found with node>=5.10 and 4.0. > npm>=3.9.
|
||||
|
||||
### Using npm to generate bundled files
|
||||
|
||||
@@ -124,6 +211,9 @@ following commands. The `dev` flag will keep the npm script running and
|
||||
re-run it upon any changes within the assets directory.
|
||||
|
||||
```
|
||||
# Copies a conf file from the frontend to the backend
|
||||
npm run sync-backend
|
||||
|
||||
# Compiles the production / optimized js & css
|
||||
npm run prod
|
||||
|
||||
|
||||
@@ -9,6 +9,11 @@ Organizations
|
||||
- [Shopkick] (https://www.shopkick.com)
|
||||
- [Amino] (https://amino.com)
|
||||
- [Faasos] (http://faasos.com/)
|
||||
- [Clark.de] (http://clark.de/)
|
||||
- [Yahoo!] (www.yahoo.com)
|
||||
- [Digit Game Studios] (https://www.digitgaming.com/)
|
||||
- [Brilliant.org] (https://brilliant.org/)
|
||||
- [Qunar] (https://www.qunar.com/)
|
||||
|
||||
Projects
|
||||
----------
|
||||
|
||||
@@ -13,6 +13,12 @@ Superset
|
||||
[](http://airbnb.io/superset/)
|
||||
[](https://david-dm.org/airbnb/superset?path=superset/assets)
|
||||
|
||||
<img
|
||||
src="https://cloud.githubusercontent.com/assets/130878/20946612/49a8a25c-bbc0-11e6-8314-10bef902af51.png"
|
||||
alt="Superset"
|
||||
width="500"
|
||||
/>
|
||||
|
||||
**Superset** is a data exploration platform designed to be visual, intuitive
|
||||
and interactive.
|
||||
|
||||
|
||||
3
dev-reqs-for-docs.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
sphinx
|
||||
sphinx-rtd-theme
|
||||
sphinxcontrib.youtube
|
||||
@@ -6,6 +6,5 @@ mysqlclient
|
||||
nose
|
||||
psycopg2
|
||||
pyyaml
|
||||
sphinx
|
||||
sphinx-rtd-theme
|
||||
sphinxcontrib.youtube
|
||||
# Also install everything we need to build Sphinx docs
|
||||
-r dev-reqs-for-docs.txt
|
||||
|
||||
@@ -119,10 +119,7 @@ html_theme_path = [sphinx_rtd_theme.get_html_theme_path()]
|
||||
# Theme options are theme-specific and customize the look and feel of a theme
|
||||
# further. For a list of options available for each theme, see the
|
||||
# documentation.
|
||||
html_theme_options = {
|
||||
'collapse_navigation': False,
|
||||
'display_version': False,
|
||||
}
|
||||
html_theme_options = {}
|
||||
|
||||
# Add any paths that contain custom themes here, relative to this directory.
|
||||
#html_theme_path = []
|
||||
|
||||
@@ -49,6 +49,9 @@ Gallery
|
||||
.. image:: _static/img/viz_thumbnails/big_number_total.png
|
||||
:scale: 25 %
|
||||
|
||||
.. image:: _static/img/viz_thumbnails/bullet.png
|
||||
:scale: 25 %
|
||||
|
||||
.. image:: _static/img/viz_thumbnails/dist_bar.png
|
||||
:scale: 25 %
|
||||
|
||||
|
||||
@@ -4,8 +4,9 @@ Installation & Configuration
|
||||
Getting Started
|
||||
---------------
|
||||
|
||||
Superset is tested using Python 2.7 and Python 3.4+. Python 3 is the recommended version,
|
||||
Python 2.6 won't be supported.
|
||||
Superset is tested against Python ``2.7`` and Python ``3.4``.
|
||||
Airbnb currently uses 2.7.* in production. We do not plan on supporting
|
||||
Python ``2.6``.
|
||||
|
||||
|
||||
OS dependencies
|
||||
@@ -80,7 +81,7 @@ Follow these few simple steps to install Superset.::
|
||||
# Install superset
|
||||
pip install superset
|
||||
|
||||
# Create an admin user
|
||||
# Create an admin user (you will be prompted to set username, first and last name before setting a password)
|
||||
fabmanager create-admin --app superset
|
||||
|
||||
# Initialize the database
|
||||
@@ -92,8 +93,8 @@ Follow these few simple steps to install Superset.::
|
||||
# Create default roles and permissions
|
||||
superset init
|
||||
|
||||
# Start the web server on port 8088
|
||||
superset runserver -p 8088
|
||||
# Start the web server on port 8088, use -p to bind to another port
|
||||
superset runserver
|
||||
|
||||
# To start a development web server, use the -d switch
|
||||
# superset runserver -d
|
||||
@@ -224,7 +225,7 @@ Flask-Cache supports multiple caching backends (Redis, Memcached,
|
||||
SimpleCache (in-memory), or the local filesystem). If you are going to use
|
||||
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).
|
||||
`python-redis <https://pypi.python.org/pypi/redis>`.
|
||||
|
||||
For setting your timeouts, this is done in the Superset metadata and goes
|
||||
up the "timeout searchpath", from your slice configuration, to your
|
||||
@@ -304,6 +305,30 @@ 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
|
||||
* ``CORS_OPTIONS``: options passed to Flask-CORS (`documentation <http://flask-cors.corydolphin.com/en/latest/api.html#extension>`)
|
||||
|
||||
|
||||
MIDDLEWARE
|
||||
----------
|
||||
|
||||
Superset allows you to add your own middleware. To add your own middleware, update the ``ADDITIONAL_MIDDLEWARE`` key in
|
||||
your `superset_config.py`. ``ADDITIONAL_MIDDLEWARE`` should be a list of your additional middleware classes.
|
||||
|
||||
For example, to use AUTH_REMOTE_USER from behind a proxy server like nginx, you have to add a simple middleware class to
|
||||
add the value of ``HTTP_X_PROXY_REMOTE_USER`` (or any other custom header from the proxy) to Gunicorn's ``REMOTE_USER``
|
||||
environment variable: ::
|
||||
|
||||
class RemoteUserMiddleware(object):
|
||||
def __init__(self, app):
|
||||
self.app = app
|
||||
def __call__(self, environ, start_response):
|
||||
user = environ.pop('HTTP_X_PROXY_REMOTE_USER', None)
|
||||
environ['REMOTE_USER'] = user
|
||||
return self.app(environ, start_response)
|
||||
|
||||
ADDITIONAL_MIDDLEWARE = [RemoteUserMiddleware, ]
|
||||
|
||||
*Adapted from http://flask.pocoo.org/snippets/69/*
|
||||
|
||||
|
||||
Upgrading
|
||||
---------
|
||||
|
||||
@@ -349,6 +374,6 @@ your environment.::
|
||||
# assuming $SUPERSET_HOME as the root of the repo
|
||||
cd $SUPERSET_HOME/superset/assets
|
||||
npm install
|
||||
npm run prod
|
||||
npm run build
|
||||
cd $SUPERSET_HOME
|
||||
python setup.py install
|
||||
|
||||
@@ -1,100 +1,308 @@
|
||||
Tutorial
|
||||
========
|
||||
Tutorial for Superset Administrators
|
||||
====================================
|
||||
|
||||
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
|
||||
Superset where to find the database you want to
|
||||
query. First go to the database menu
|
||||
This tutorial targets a Superset administrator: someone configuring Superset
|
||||
for an organization on behalf of users. We'll show you how to connect Superset
|
||||
to a new database and configure a table in that database for analysis. You'll
|
||||
also explore the data you've exposed and add a visualization to a dashboard
|
||||
so that you get a feel for the end-to-end user experience.
|
||||
|
||||
.. image:: _static/img/tutorial/db_menu.png
|
||||
:scale: 30 %
|
||||
Connecting to a new database
|
||||
----------------------------
|
||||
|
||||
Now click on the ``+`` button to add a new entry
|
||||
We assume you already have a database configured and can connect to it from the
|
||||
instance on which you’re running Superset. If you’re just testing Superset and
|
||||
want to explore sample data, you can load some
|
||||
`sample PostgreSQL datasets <https://wiki.postgresql.org/wiki/Sample_Databases>`_
|
||||
into a fresh DB, or configure the
|
||||
`example weather data <https://github.com/dylburger/noaa-ghcn-weather-data>`_
|
||||
we use here.
|
||||
|
||||
.. image:: _static/img/tutorial/db_plus.png
|
||||
:scale: 30 %
|
||||
Under the **Sources** menu, select the *Databases* option:
|
||||
|
||||
Fill in an arbitrary reference name for the database, and you SQLAlchemy
|
||||
URI. To figure out how to construct your URI, check out the
|
||||
`SQLAlchemy documentation <http://docs.sqlalchemy.org/en/rel_1_0/core/engines.html>`_.
|
||||
Then you can test your connection. If it works, you'll see a positive popup
|
||||
and list of the tables that SQLAlchemy has found for that URI.
|
||||
.. image:: _static/img/tutorial/tutorial_01_sources_database.png
|
||||
:scale: 70%
|
||||
|
||||
.. image:: _static/img/tutorial/db_added.png
|
||||
:scale: 30 %
|
||||
On the resulting page, click on the green plus sign, near the top left:
|
||||
|
||||
Once your database has been added, it's time to add your table. Navigate
|
||||
using the navigation bar at the top to ``Sources -> Tables`` and click the
|
||||
plus (``+``) sign there (similar to the one ).
|
||||
.. image:: _static/img/tutorial/tutorial_02_add_database.png
|
||||
:scale: 70%
|
||||
|
||||
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, 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.
|
||||
You can configure a number of advanced options on this page, but for
|
||||
this walkthrough, you’ll only need to do **two things**:
|
||||
|
||||
.. image:: _static/img/tutorial/pen.png
|
||||
:scale: 30 %
|
||||
1. Name your database connection:
|
||||
|
||||
Now you're in the table editor, click on the "List Table Column" tab,
|
||||
showing you the list of columns in your table as well as their data types.
|
||||
.. image:: _static/img/tutorial/tutorial_03_database_name.png
|
||||
:scale: 70%
|
||||
|
||||
.. image:: _static/img/tutorial/matrix.png
|
||||
:scale: 30 %
|
||||
2. Provide the SQLAlchemy Connection URI and test the connection:
|
||||
|
||||
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 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``.
|
||||
.. image:: _static/img/tutorial/tutorial_04_sqlalchemy_connection_string.png
|
||||
:scale: 70%
|
||||
|
||||
You should now be back in the ``Table List`` view. Click on the name of the
|
||||
table you just created. You enter the "Explore" view for your table.
|
||||
This example shows the connection string for our test weather database.
|
||||
As noted in the text below the URI, you should refer to the SQLAlchemy
|
||||
documentation on
|
||||
`creating new connection URIs <http://docs.sqlalchemy.org/en/rel_1_0/core/engines.html#database-urls>`_
|
||||
for your target database.
|
||||
|
||||
.. image:: _static/img/tutorial/explore.png
|
||||
:scale: 30 %
|
||||
Click the **Test Connection** button to confirm things work end to end.
|
||||
Once Superset can successfully connect and authenticate, you should see
|
||||
a popup like this:
|
||||
|
||||
The next step is to create a Slice. First, make sure to use a time filter
|
||||
that is relevant.
|
||||
.. image:: _static/img/tutorial/tutorial_05_connection_popup.png
|
||||
:scale: 50%
|
||||
|
||||
.. note::
|
||||
Moreover, you should also see the list of tables Superset can read from
|
||||
the schema you’re connected to, at the bottom of the page:
|
||||
|
||||
You can use some "natural language time expressions"
|
||||
either as relative (as in ``now``, ``4 weeks ago``, or ``1 year ago``) as well
|
||||
as hard date or time expressions (as in ``3015``, ``3016-01-01`` or
|
||||
``May``).
|
||||
.. image:: _static/img/tutorial/tutorial_06_list_of_tables.png
|
||||
:scale: 70%
|
||||
|
||||
Alter the form's option and click ``Query`` until you get to an interesting
|
||||
cut of data, and click ``SAVE AS``, enter a name, and you just created your first
|
||||
slice.
|
||||
If the connection looks good, save the configuration by clicking the **Save**
|
||||
button at the bottom of the page:
|
||||
|
||||
.. image:: _static/img/tutorial/created.png
|
||||
:scale: 30 %
|
||||
.. image:: _static/img/tutorial/tutorial_07_save_button.png
|
||||
:scale: 70%
|
||||
|
||||
This slice is now accessible in the slice list from the
|
||||
``Menu -> Slices`` at any time. Note that this view is easily filterable and
|
||||
searchable.
|
||||
Adding a new table
|
||||
------------------
|
||||
|
||||
.. image:: _static/img/tutorial/search.png
|
||||
:scale: 30 %
|
||||
Now that you’ve configured a database, you’ll need to add specific tables
|
||||
to Superset that you’d like to query.
|
||||
|
||||
Now let's create a dashboard. A dashboard is simply a collection of slices
|
||||
with metadata around their sizes, positions, CSS style and a few other things.
|
||||
Navigate to the dashboard list view ``Menu -> Dashboard`` and click the plus
|
||||
(``+``) sign. In the form, enter a name and pick the slice you just created.
|
||||
Under the **Sources** menu, select the *Tables* option:
|
||||
|
||||
.. image:: _static/img/tutorial/new_dash.png
|
||||
:scale: 30 %
|
||||
.. image:: _static/img/tutorial/tutorial_08_sources_tables.png
|
||||
:scale: 70%
|
||||
|
||||
Hit ``Save``, you should be back in ``Menu -> Dashboard``. Now enter your
|
||||
new dashboard.
|
||||
On the resulting page, click on the green plus sign, near the top left:
|
||||
|
||||
.. image:: _static/img/tutorial/in_new_dash.png
|
||||
:scale: 30 %
|
||||
.. image:: _static/img/tutorial/tutorial_09_add_new_table.png
|
||||
:scale: 70%
|
||||
|
||||
Here you are. You can now resize and move the different slice(s), style them
|
||||
in the CSS modal window, and save right from here. For now, renaming the
|
||||
dashboard or adding on a new slice is done through the dashboard edit view,
|
||||
which is the same form as you used when you originally created the dashboard,
|
||||
and is accessible by clicking the ``edit`` pen icon from the dashboard list
|
||||
view (``Menu -> Dashboards``)
|
||||
You only need a few pieces of information to add a new table to Superset:
|
||||
|
||||
* The name of the table
|
||||
|
||||
.. image:: _static/img/tutorial/tutorial_10_table_name.png
|
||||
:scale: 70%
|
||||
|
||||
* The target database from the **Database** drop-down menu (i.e. the one
|
||||
you just added above)
|
||||
|
||||
.. image:: _static/img/tutorial/tutorial_11_choose_db.png
|
||||
:scale: 70%
|
||||
|
||||
* Optionally, the database schema. If the table exists in the “default” schema
|
||||
(e.g. the *public* schema in PostgreSQL or Redshift), you can leave the schema
|
||||
field blank.
|
||||
|
||||
Click on the **Save** button to save the configuration:
|
||||
|
||||
.. image:: _static/img/tutorial/tutorial_07_save_button.png
|
||||
:scale: 70%
|
||||
|
||||
When redirected back to the list of tables, you should see a message indicating
|
||||
that your table was created:
|
||||
|
||||
.. image:: _static/img/tutorial/tutorial_12_table_creation_success_msg.png
|
||||
:scale: 70%
|
||||
|
||||
This message also directs you to edit the table configuration. We’ll edit a limited
|
||||
portion of the configuration now - just to get you started - and leave the rest for
|
||||
a more advanced tutorial.
|
||||
|
||||
Click on the edit button next to the table you’ve created:
|
||||
|
||||
.. image:: _static/img/tutorial/tutorial_13_edit_table_config.png
|
||||
:scale: 70%
|
||||
|
||||
On the resulting page, click on the **List Table Column** tab. Here, you’ll define the
|
||||
way you can use specific columns of your table when exploring your data. We’ll run
|
||||
through these options to describe their purpose:
|
||||
|
||||
* If you want users to group metrics by a specific field, mark it as **Groupable**.
|
||||
* If you need to filter on a specific field, mark it as **Filterable**.
|
||||
* Is this field something you’d like to get the distinct count of? Check the **Count
|
||||
Distinct** box.
|
||||
* Is this a metric you want to sum, or get basic summary statistics for? The **Sum**,
|
||||
**Min**, and **Max** columns will help.
|
||||
* The **is temporal** field should be checked for any date or time fields. We’ll cover
|
||||
how this manifests itself in analyses in a moment.
|
||||
|
||||
Here’s how we’ve configured fields for the weather data. Even for measures like the
|
||||
weather measurements (precipitation, snowfall, etc.), it’s ideal to group and filter
|
||||
by these values:
|
||||
|
||||
.. image:: _static/img/tutorial/tutorial_14_field_config.png
|
||||
|
||||
As with the configurations above, click the **Save** button to save these settings.
|
||||
|
||||
Exploring your data
|
||||
-------------------
|
||||
|
||||
To start exploring your data, simply click on the table name you just created in
|
||||
the list of available tables:
|
||||
|
||||
.. image:: _static/img/tutorial/tutorial_15_click_table_name.png
|
||||
|
||||
By default, you’ll be presented with a Table View:
|
||||
|
||||
.. image:: _static/img/tutorial/tutorial_16_datasource_chart_type.png
|
||||
|
||||
Let’s walk through a basic query to get the count of all records in our table.
|
||||
First, we’ll need to change the **Since** filter to capture the range of our data.
|
||||
You can use simple phrases to apply these filters, like "3 years ago":
|
||||
|
||||
.. image:: _static/img/tutorial/tutorial_17_choose_time_range.png
|
||||
|
||||
The upper limit for time, the **Until** filter, defaults to "now", which may or may
|
||||
not be what you want.
|
||||
|
||||
Look for the Metrics section under the **GROUP BY** header, and start typing "Count"
|
||||
- you’ll see a list of metrics matching what you type:
|
||||
|
||||
.. image:: _static/img/tutorial/tutorial_18_choose_metric.png
|
||||
|
||||
Select the *COUNT(\*)* metric, then click the green **Query** button near the top
|
||||
of the explore:
|
||||
|
||||
.. image:: _static/img/tutorial/tutorial_19_click_query.png
|
||||
|
||||
You’ll see your results in the table:
|
||||
|
||||
.. image:: _static/img/tutorial/tutorial_20_count_star_result.png
|
||||
|
||||
Let’s group this by the *weather_description* field to get the count of records by
|
||||
the type of weather recorded by adding it to the *Group by* section:
|
||||
|
||||
.. image:: _static/img/tutorial/tutorial_21_group_by.png
|
||||
|
||||
and run the query:
|
||||
|
||||
.. image:: _static/img/tutorial/tutorial_22_group_by_result.png
|
||||
|
||||
Let’s find a more useful data point: the top 10 times and places that recorded the
|
||||
highest temperature in 2015.
|
||||
|
||||
We replace *weather_description* with *latitude*, *longitude* and *measurement_date* in the
|
||||
*Group by* section:
|
||||
|
||||
.. image:: _static/img/tutorial/tutorial_23_group_by_more_dimensions.png
|
||||
|
||||
And replace *COUNT(\*)* with *max__measurement_flag*:
|
||||
|
||||
.. image:: _static/img/tutorial/tutorial_24_max_metric.png
|
||||
|
||||
The *max__measurement_flag* metric was created when we checked the box under **Max** and
|
||||
next to the *measurement_flag* field, indicating that this field was numeric and that
|
||||
we wanted to find its maximum value when grouped by specific fields.
|
||||
|
||||
In our case, *measurement_flag* is the value of the measurement taken, which clearly
|
||||
depends on the type of measurement (the researchers recorded different values for
|
||||
precipitation and temperature). Therefore, we must filter our query only on records
|
||||
where the *weather_description* is equal to "Maximum temperature", which we do in
|
||||
the **Filters** section at the bottom of the explore:
|
||||
|
||||
.. image:: _static/img/tutorial/tutorial_25_max_temp_filter.png
|
||||
|
||||
Finally, since we only care about the top 10 measurements, we limit our results to
|
||||
10 records using the *Row limit* option under the **Options** header:
|
||||
|
||||
.. image:: _static/img/tutorial/tutorial_26_row_limit.png
|
||||
|
||||
We click **Query** and get the following results:
|
||||
|
||||
.. image:: _static/img/tutorial/tutorial_27_top_10_max_temps.png
|
||||
|
||||
In this dataset, the maximum temperature is recorded in tenths of a degree Celsius.
|
||||
The top value of 1370, measured in the middle of Nevada, is equal to 137 C, or roughly
|
||||
278 degrees F. It’s unlikely this value was correctly recorded. We’ve already been able
|
||||
to investigate some outliers with Superset, but this just scratches the surface of what
|
||||
we can do.
|
||||
|
||||
You may want to do a couple more things with this measure:
|
||||
|
||||
* The default formatting shows values like 1.37k, which may be difficult for some
|
||||
users to read. It’s likely you may want to see the full, comma-separated value.
|
||||
You can change the formatting of any measure by editing its config (*Edit Table
|
||||
Config > List Sql Metric > Edit Metric > D3Format*)
|
||||
* Moreover, you may want to see the temperature measurements in plain degrees C,
|
||||
not tenths of a degree. Or you may want to convert the temperature to degrees
|
||||
Fahrenheit. You can change the SQL that gets executed agains the database, baking
|
||||
the logic into the measure itself (*Edit Table Config > List Sql Metric > Edit
|
||||
Metric > SQL Expression*)
|
||||
|
||||
For now, though, let’s create a better visualization of these data and add it to
|
||||
a dashboard.
|
||||
|
||||
We change the Chart Type to "Distribution - Bar Chart":
|
||||
|
||||
.. image:: _static/img/tutorial/tutorial_28_bar_chart.png
|
||||
|
||||
Our filter on Maximum temperature measurements was retained, but the query and
|
||||
formatting options are dependent on the chart type, so you’ll have to set the
|
||||
values again:
|
||||
|
||||
.. image:: _static/img/tutorial/tutorial_29_bar_chart_series_metrics.png
|
||||
|
||||
You should note the extensive formatting options for this chart: the ability to
|
||||
set axis labels, margins, ticks, etc. To make the data presentable to a broad
|
||||
audience, you’ll want to apply many of these to slices that end up in dashboards.
|
||||
For now, though, we run our query and get the following chart:
|
||||
|
||||
.. image:: _static/img/tutorial/tutorial_30_bar_chart_results.png
|
||||
:scale: 70%
|
||||
|
||||
Creating a slice and dashboard
|
||||
------------------------------
|
||||
|
||||
This view might be interesting to researchers, so let’s save it. In Superset,
|
||||
a saved query is called a **Slice**.
|
||||
|
||||
To create a slice, click the **Save as** button near the top-left of the
|
||||
explore:
|
||||
|
||||
.. image:: _static/img/tutorial/tutorial_19_click_query.png
|
||||
|
||||
A popup should appear, asking you to name the slice, and optionally add it to a
|
||||
dashboard. Since we haven’t yet created any dashboards, we can create one and
|
||||
immediately add our slice to it. Let’s do it:
|
||||
|
||||
.. image:: _static/img/tutorial/tutorial_31_save_slice_to_dashboard.png
|
||||
:scale: 70%
|
||||
|
||||
Click Save, which will direct you back to your original query. We see that
|
||||
our slice and dashboard were successfully created:
|
||||
|
||||
.. image:: _static/img/tutorial/tutorial_32_save_slice_confirmation.png
|
||||
:scale: 70%
|
||||
|
||||
Let’s check out our new dashboard. We click on the **Dashboards** menu:
|
||||
|
||||
.. image:: _static/img/tutorial/tutorial_33_dashboard.png
|
||||
|
||||
and find the dashboard we just created:
|
||||
|
||||
.. image:: _static/img/tutorial/tutorial_34_weather_dashboard.png
|
||||
|
||||
Things seemed to have worked - our slice is here!
|
||||
|
||||
.. image:: _static/img/tutorial/tutorial_35_slice_on_dashboard.png
|
||||
:scale: 70%
|
||||
|
||||
But it’s a bit smaller than we might like. Luckily, you can adjust the size
|
||||
of slices in a dashboard by clicking, holding and dragging the bottom-right
|
||||
corner to your desired dimensions:
|
||||
|
||||
.. image:: _static/img/tutorial/tutorial_36_adjust_dimensions.gif
|
||||
:scale: 120%
|
||||
|
||||
After adjusting the size, you’ll be asked to click on the icon near the
|
||||
top-right of the dashboard to save the new configuration.
|
||||
|
||||
Congrats! You’ve successfully linked, analyzed, and visualized data in Superset.
|
||||
There are a wealth of other table configuration and visualization options, so
|
||||
please start exploring and creating slices and dashboards of your own.
|
||||
|
||||
6
pypi_push.sh
Normal file → Executable file
@@ -1,7 +1,7 @@
|
||||
# first bump up package.json manually, commit and tag
|
||||
rm superset/assets/dist/*
|
||||
cd superset/assets/
|
||||
rm build/*
|
||||
npm run prod
|
||||
npm run build
|
||||
cd ../..
|
||||
python setup.py register
|
||||
python setup.py sdist upload
|
||||
|
||||
|
||||
@@ -7,7 +7,6 @@ rm -f .coverage
|
||||
export SUPERSET_CONFIG=tests.superset_test_config
|
||||
set -e
|
||||
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
|
||||
|
||||
50
scripts/permissions_cleanup.py
Normal file
@@ -0,0 +1,50 @@
|
||||
from superset import sm
|
||||
from collections import defaultdict
|
||||
|
||||
|
||||
def cleanup_permissions():
|
||||
# 1. Clean up duplicates.
|
||||
pvms = sm.get_session.query(sm.permissionview_model).all()
|
||||
print('# of permission view menues is: {}'.format(len(pvms)))
|
||||
pvms_dict = defaultdict(list)
|
||||
for pvm in pvms:
|
||||
pvms_dict[(pvm.permission, pvm.view_menu)].append(pvm)
|
||||
duplicates = [v for v in pvms_dict.values() if len(v) > 1]
|
||||
len(duplicates)
|
||||
|
||||
for pvm_list in duplicates:
|
||||
first_prm = pvm_list[0]
|
||||
roles = set(first_prm.role)
|
||||
for pvm in pvm_list[1:]:
|
||||
roles = roles.union(pvm.role)
|
||||
sm.get_session.delete(pvm)
|
||||
first_prm.roles = list(roles)
|
||||
sm.get_session.commit()
|
||||
|
||||
pvms = sm.get_session.query(sm.permissionview_model).all()
|
||||
print('STage 1: # of permission view menues is: {}'.format(len(pvms)))
|
||||
|
||||
# 2. Clean up None permissions or view menues
|
||||
pvms = sm.get_session.query(sm.permissionview_model).all()
|
||||
for pvm in pvms:
|
||||
if not (pvm.view_menu and pvm.permission):
|
||||
sm.get_session.delete(pvm)
|
||||
sm.get_session.commit()
|
||||
|
||||
pvms = sm.get_session.query(sm.permissionview_model).all()
|
||||
print('Stage 2: # of permission view menues is: {}'.format(len(pvms)))
|
||||
|
||||
# 3. Delete empty permission view menues from roles
|
||||
roles = sm.get_session.query(sm.role_model).all()
|
||||
for role in roles:
|
||||
role.permissions = [p for p in role.permissions if p]
|
||||
sm.get_session.commit()
|
||||
|
||||
# 4. Delete empty roles from permission view menues
|
||||
pvms = sm.get_session.query(sm.permissionview_model).all()
|
||||
for pvm in pvms:
|
||||
pvm.role = [r for r in pvm.role if r]
|
||||
sm.get_session.commit()
|
||||
|
||||
|
||||
cleanup_permissions()
|
||||
45
setup.py
@@ -1,5 +1,5 @@
|
||||
import imp
|
||||
import os
|
||||
import subprocess
|
||||
import json
|
||||
from setuptools import setup, find_packages
|
||||
|
||||
@@ -9,6 +9,28 @@ PACKAGE_FILE = os.path.join(PACKAGE_DIR, 'package.json')
|
||||
with open(PACKAGE_FILE) as package_file:
|
||||
version_string = json.load(package_file)['version']
|
||||
|
||||
|
||||
def get_git_sha():
|
||||
try:
|
||||
s = str(subprocess.check_output(['git', 'rev-parse', 'HEAD']))
|
||||
return s.strip()
|
||||
except:
|
||||
return ""
|
||||
|
||||
GIT_SHA = get_git_sha()
|
||||
version_info = {
|
||||
'GIT_SHA': GIT_SHA,
|
||||
'version': version_string,
|
||||
}
|
||||
print("-==-" * 15)
|
||||
print("VERSION: " + version_string)
|
||||
print("GIT SHA: " + GIT_SHA)
|
||||
print("-==-" * 15)
|
||||
|
||||
with open(os.path.join(PACKAGE_DIR, 'version_info.json'), 'w') as version_file:
|
||||
json.dump(version_info, version_file)
|
||||
|
||||
|
||||
setup(
|
||||
name='superset',
|
||||
description=(
|
||||
@@ -20,31 +42,32 @@ setup(
|
||||
zip_safe=False,
|
||||
scripts=['superset/bin/superset'],
|
||||
install_requires=[
|
||||
'boto3==1.4.4',
|
||||
'celery==3.1.23',
|
||||
'cryptography==1.5.3',
|
||||
'cryptography==1.7.2',
|
||||
'flask-appbuilder==1.8.1',
|
||||
'flask-cache==0.13.1',
|
||||
'flask-migrate==1.5.1',
|
||||
'flask-script==2.0.5',
|
||||
'flask-testing==0.5.0',
|
||||
'flask-sqlalchemy==2.0',
|
||||
'flask-testing==0.6.1',
|
||||
'humanize==0.5.1',
|
||||
'gunicorn==19.6.0',
|
||||
'markdown==2.6.6',
|
||||
'markdown==2.6.8',
|
||||
'pandas==0.18.1',
|
||||
'parsedatetime==2.0.0',
|
||||
'pydruid==0.3.0',
|
||||
'pydruid==0.3.1',
|
||||
'PyHive>=0.2.1',
|
||||
'python-dateutil==2.5.3',
|
||||
'requests==2.10.0',
|
||||
'simplejson==3.8.2',
|
||||
'python-dateutil==2.6.0',
|
||||
'requests==2.13.0',
|
||||
'simplejson==3.10.0',
|
||||
'six==1.10.0',
|
||||
'sqlalchemy==1.0.13',
|
||||
'sqlalchemy-utils==0.32.7',
|
||||
'sqlalchemy==1.1.5',
|
||||
'sqlalchemy-utils==0.32.12',
|
||||
'sqlparse==0.1.19',
|
||||
'thrift>=0.9.3',
|
||||
'thrift-sasl>=0.2.1',
|
||||
'werkzeug==0.11.10',
|
||||
'werkzeug==0.11.15',
|
||||
],
|
||||
extras_require={
|
||||
'cors': ['Flask-Cors>=2.0.0'],
|
||||
|
||||
@@ -6,12 +6,12 @@ from __future__ import unicode_literals
|
||||
|
||||
import logging
|
||||
import os
|
||||
import json
|
||||
from logging.handlers import TimedRotatingFileHandler
|
||||
|
||||
from flask import Flask, redirect
|
||||
from flask_appbuilder import SQLA, AppBuilder, IndexView
|
||||
from flask_appbuilder.baseviews import expose
|
||||
from flask_cache import Cache
|
||||
from flask_migrate import Migrate
|
||||
from superset.source_registry import SourceRegistry
|
||||
from werkzeug.contrib.fixers import ProxyFix
|
||||
@@ -21,21 +21,30 @@ from superset import utils
|
||||
APP_DIR = os.path.dirname(__file__)
|
||||
CONFIG_MODULE = os.environ.get('SUPERSET_CONFIG', 'superset.config')
|
||||
|
||||
with open(APP_DIR + '/static/assets/backendSync.json', 'r') as f:
|
||||
frontend_config = json.load(f)
|
||||
|
||||
|
||||
app = Flask(__name__)
|
||||
app.config.from_object(CONFIG_MODULE)
|
||||
conf = app.config
|
||||
|
||||
if conf.get('SILENCE_FAB'):
|
||||
logging.getLogger('flask_appbuilder').setLevel(logging.ERROR)
|
||||
|
||||
if not app.debug:
|
||||
# In production mode, add log handler to sys.stderr.
|
||||
app.logger.addHandler(logging.StreamHandler())
|
||||
app.logger.setLevel(logging.INFO)
|
||||
logging.getLogger('pyhive.presto').setLevel(logging.INFO)
|
||||
|
||||
db = SQLA(app)
|
||||
|
||||
|
||||
utils.pessimistic_connection_handling(db.engine.pool)
|
||||
|
||||
cache = Cache(app, config=app.config.get('CACHE_CONFIG'))
|
||||
cache = utils.setup_cache(app, conf.get('CACHE_CONFIG'))
|
||||
tables_cache = utils.setup_cache(app, conf.get('TABLE_NAMES_CACHE_CONFIG'))
|
||||
|
||||
migrate = Migrate(app, db, directory=APP_DIR + "/migrations")
|
||||
|
||||
@@ -64,6 +73,9 @@ if app.config.get('UPLOAD_FOLDER'):
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
for middleware in app.config.get('ADDITIONAL_MIDDLEWARE'):
|
||||
app.wsgi_app = middleware(app.wsgi_app)
|
||||
|
||||
|
||||
class MyIndexView(IndexView):
|
||||
@expose('/')
|
||||
|
||||
@@ -1,82 +0,0 @@
|
||||
from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
from __future__ import unicode_literals
|
||||
|
||||
error = (
|
||||
"MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\n"+
|
||||
"MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMNNMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\n"+
|
||||
"MMMMMMMMMMMMMMMMMMMMMMMMMMMMMM8OI++=~~~~~~=+?IODMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\n"+
|
||||
"MMMMMMMMMMMMMMMMMMMMMMMMMD$~~~~~~~~~~~~~~~~~~~~~~~=$MMMMMMMMMMMMMMMMMMMMMMMMMMMM\n"+
|
||||
"MMMMMMMMMMMMMMMMMMMMMMN8?:~~~~~~~~~~~~~~~~~~~~~~~~~~=+8NMMMMMMMMMMMMMMMMMMMMMMMM\n"+
|
||||
"MMMMMMMMMMMMMMMMMMMMO=~~~~~~~~~~~~~~~~~+I??~~~~~~~~~~~~~+DMMMMMMMMMMMMMMMMMMMMMM\n"+
|
||||
"MMMMMMMMMMMMMMMMMMNI~~~~~~~~~~~~~~~~~~IIIII=~~~~~~~~~~~~~~=NMMMMMMMMMMMMMMMMMMMM\n"+
|
||||
"MMMMMMMMMMMMMMMMM+=~~~~~~~~~~~~~~~~~~~=III+~~~~~~~~~~~~~~~~~?8MMMMMMMMMMMMMMMMMM\n"+
|
||||
"MMMMMMMMMMMMMMMM?~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~+++=~~~~8MMMMMMMMMMMMMMMMM\n"+
|
||||
"MMMMMMMMMMMMMMI=~~~~~~~~~~~~~~~~~~~~~~~~~III?I~~~~~~~~,:++++++~~8MMMMMMMMMMMMMMM\n"+
|
||||
"MMMMMMMMMMMMN7~~~~~~~~~~~~~~~~==+=~~~~~~=IIIII~~~~~~:. ..:=++=~=MMMMMMMMMMMMMMM\n"+
|
||||
"MMMMMMMMMMMO=~~~~~~~~~~~~~~~~+++=~~~~~~~~??I?I~~~~~~. ...,~~~~IMMMMMMMMMMMMM\n"+
|
||||
"MMMMMMMMMMM~~~~~~~~~~~~~~~~~+++:,~~~~~~~~~~~?=~~~~~:. ..~~~~~OMMMMMMMMMMMM\n"+
|
||||
"MMMMMMMMM$=~~~~~~~~~~~~~~~=++:.. ..~~~~~~~~~~~~~~~~,. . . :~~~~~OMMMMMMMMMMM\n"+
|
||||
"MMMMMMMMM~~~~~~~~~~~~~~~~+++,. .~~~~~~~~~~~~~~~.. .. . .~~~~~=OMMMMMMMMMM\n"+
|
||||
"MMMMMMMM?~~~~~~~~~~~~~~~=+~. .~~~~~~~~~~~~~~. ,MMMMM,=~~~~~~NMMMMMMMMM\n"+
|
||||
"MMMMMMMN~~~~~~~~~~~~~~~~~,. .,~~~~~~~~~~~~~.. ZMMM,+Z:~~~~~~$MMMMMMMMM\n"+
|
||||
"MMMMMM8?~~~~~~~~~~~~~~~~~.. ..~~~~~~~~~~~~~:. DMMM,+D~~~~~~~~IMMMMMMMM\n"+
|
||||
"MMMMMMI~~~~~~~~~~~~~~~~~~.. :MMMO~~~~~~~~~~~~~~~,.. ?MMMMMI~~~~~~~~~MMMMMMMM\n"+
|
||||
"MMMMMM=~~~~~~~~~~~~~~~~~~.. MMM+=M:~~~~~~~~~~~~~:. .:IM$~~~~~~~~~~~8MMMMMMM\n"+
|
||||
"MMMMMD~~~~~~~~~~~~~~~~~~~:. MMM:,M:~~~~~~~~~~~~~~~.......:~~~~~~~~~~$MMMMMMM\n"+
|
||||
"MMMMMI~~~~~~~~~~~~~~~~~~~~, MMMMMM~~~~~~~~~~~~~~~~~~,..:~~~~~~~~~~~~+MMMMMMM\n"+
|
||||
"MMMMD+~~~~~~~~~~~~~~~~~~~~~. $MMMM$~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~=MMMMMMM\n"+
|
||||
"MMMM8~~~~~~~~~~~~~~~~~~~~~~:. . .:~~~~~~,..:. .=~~~~~~~~~~~~~~~~~~~~MMMMMMM\n"+
|
||||
"MMMMO~~~~~~~~~~~~~~~~~~~~~~~:, .:~~~~~=8.. .+ . =8ZI~~~~~~~~~~~~~~~~=MMMMMMM\n"+
|
||||
"MMMMZ=~~~~~~~~~~~~~~~~~~~~~~~~:,,,:~~~~~~IZ8:. .O....888?~~~~~~~~~~~~~~~+MMMMMMM\n"+
|
||||
"MMMMO=~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~?888=...I~I88888O?~~~~~~~~~~~~~~7MMMMMMM\n"+
|
||||
"MMMMO~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~Z888OO88888888888O?~~~~~~~~~~~~~OMMMMMMM\n"+
|
||||
"MMMMD+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~=8888888888888888888~~~~~~~~~~~~+MMMMMMMM\n"+
|
||||
"MMMMM7~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~?8888888888888888888?~~~~~~~~~~=$MMMMMMMM\n"+
|
||||
"MMMMMD~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~=$8888888888888888888O~~~~~~~~~~8MMMMMMMMM\n"+
|
||||
"MMMMMN=~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~+Z88888888888888888ZZ7=~~~~~~~~?MMMMMMMMMM\n"+
|
||||
"MMMMMMZ=~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~+Z88888888Z7I===~~~~~~~~~~~~~=OMMMMMMMMMMM\n"+
|
||||
"MMMMMMN$~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~=$88888O7?=~~~~~~~~~~~~~~~~~~OMMMMMMMMMMMM\n"+
|
||||
"MMMMMMMM?~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~I8OZ+~~~~~~~~~~~~~~~~~~~~=DMMMMMMMMMMMMMM\n"+
|
||||
"MMMMMMMM8=~~~~~~~~~~~~~~~~~~~~~~~~~~~~~+$+=~~~~~~~~~~~~~~~~~~~~+MMMMMMMMMMMMMMMM\n"+
|
||||
"MMMMMMMMMD7~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~=$DMMMMMMMMMMMMMMMMMM\n"+
|
||||
"MMMMMMMMMMM?~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~=$OMMMMMMMMMMMMMMMMMMMMM\n"+
|
||||
"MMMMMMMMMMMMD7=~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~+ZMMMMMMMMMMMMMMMMMMMMMMMMMM\n"+
|
||||
"MMMMMMMMMMMMMMZ7=~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~78MMMMMMMMMMMMMMMMMMMMMMMMMMMMM\n"+
|
||||
"MMMMMMMMMMMMMMMMM8OI=~~~~~~~~~~~~~~~~~~~=+?ZDNMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\n"+
|
||||
"MMMMMMMMMMMMMMMMMMMMNDZ7?++~=~==~+?IONMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\n"+
|
||||
"MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\n"+
|
||||
"MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\n"+
|
||||
"MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\n"+
|
||||
"MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM")
|
||||
|
||||
stacktrace="""
|
||||
-------------------------------------------------------------------------------------------------------
|
||||
=======================================================================================================
|
||||
-------------------------------------------------------------------------------------------------------
|
||||
___ ___ ___
|
||||
( ) ( ) ( )
|
||||
.--. | |_ .---. .--. | | ___ | |_ ___ .-. .---. .--. .--.
|
||||
/ _ \ ( __) / .-, \ / \ | | ( ) ( __) ( ) \ / .-, \ / \ / \\
|
||||
. .' `. ; | | (__) ; | | .-. ; | | ' / | | | ' .-. ; (__) ; | | .-. ; | .-. ;
|
||||
| ' | | | | ___ .'` | | |(___) | |,' / | | ___ | / (___) .'` | | |(___) | | | |
|
||||
_\_`.(___) | |( ) / .'| | | | | . '. | |( ) | | / .'| | | | | |/ |
|
||||
( ). '. | | | | | / | | | | ___ | | `. \ | | | | | | | / | | | | ___ | ' _.'
|
||||
| | `\ | | ' | | ; | ; | | '( ) | | \ \ | ' | | | | ; | ; | | '( ) | .'.-.
|
||||
; '._,' ' ' `-' ; ' `-' | ' `-' | | | \ . ' `-' ; | | ' `-' | ' `-' | ' `-' /
|
||||
'.___.' `.__. `.__.'_. `.__,' (___ ) (___) `.__. (___) `.__.'_. `.__,' `.__.'
|
||||
|
||||
-------------------------------------------------------------------------------------------------------
|
||||
=======================================================================================================
|
||||
-------------------------------------------------------------------------------------------------------
|
||||
"""
|
||||
|
||||
boat = """\
|
||||
+ +
|
||||
)`.).
|
||||
)``)``) .~~
|
||||
).-'.-')|)
|
||||
|-).-).-'_'-/
|
||||
~~~\ `o-o-o' /~~~~
|
||||
~~~'---.____/~~~"""
|
||||
2278
superset/assets/backendSync.json
Normal file
45
superset/assets/branding/Full Lockup With Text.svg
Normal file
@@ -0,0 +1,45 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="210px" height="202px" viewBox="0 0 210 202" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<!-- Generator: Sketch 42 (36781) - http://www.bohemiancoding.com/sketch -->
|
||||
<title>Full Lockup With Text</title>
|
||||
<desc>Created with Sketch.</desc>
|
||||
<defs>
|
||||
<path d="M55.8666667,41.25 C64.4268817,50.85 73.137276,55.65 83.3494624,55.65 C100.019355,55.65 112.183871,43.95 112.183871,27.9 C112.183871,11.85 100.019355,0 83.3494624,0 C73.7379928,0 64.8774194,5.4 56.3172043,14.85 C47.9071685,5.25 38.8964158,0 28.8344086,0 C12.1645161,0 -2.84217094e-14,11.85 -2.84217094e-14,27.9 C-2.84217094e-14,43.95 12.1645161,55.65 28.8344086,55.65 C39.046595,55.65 47.0060932,50.85 55.8666667,41.25 Z" id="path-1"></path>
|
||||
<mask id="mask-2" maskContentUnits="userSpaceOnUse" maskUnits="objectBoundingBox" x="-5" y="-5" width="122.183871" height="65.65">
|
||||
<rect x="-5" y="-5" width="122.183871" height="65.65" fill="white"></rect>
|
||||
<use xlink:href="#path-1" fill="black"></use>
|
||||
</mask>
|
||||
</defs>
|
||||
<g id="Main" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="Superset" transform="translate(-177.000000, -812.000000)">
|
||||
<g id="Full-Lockup-With-Text" transform="translate(177.000000, 812.000000)">
|
||||
<g id="Group-7" transform="translate(0.500000, 0.500000)">
|
||||
<g id="Group-17">
|
||||
<g id="Group-6-Copy">
|
||||
<g id="Group" fill="#484848">
|
||||
<path d="M0,80.85 C8.56021505,80.85 15.0179211,84.6 15.0179211,94.8 L15.0179211,116.1 C15.0179211,135.9 25.9810036,145.2 44.1526882,145.2 L48.8082437,145.2 L48.8082437,130.05 L44.3028674,130.05 C36.944086,130.05 31.8379928,126.6 31.8379928,116.55 L31.8379928,91.5 C31.8379928,81.9 26.281362,75 16.6698925,72.6 C26.281362,70.05 31.8379928,63.3 31.8379928,53.7 L31.8379928,28.5 C31.8379928,18.45 36.944086,15.15 44.3028674,15.15 L48.8082437,15.15 L48.8082437,0 L44.1526882,0 C25.9810036,0 15.0179211,9.15 15.0179211,28.95 L15.0179211,50.25 C15.0179211,60.45 8.56021505,64.2 0,64.2 L0,80.85 Z" id="{-copy-4"></path>
|
||||
<path d="M160.691756,80.85 C169.251971,80.85 175.709677,84.6 175.709677,94.8 L175.709677,116.1 C175.709677,135.9 186.67276,145.2 204.844444,145.2 L209.5,145.2 L209.5,130.05 L204.994624,130.05 C197.635842,130.05 192.529749,126.6 192.529749,116.55 L192.529749,91.5 C192.529749,81.9 186.973118,75 177.361649,72.6 C186.973118,70.05 192.529749,63.3 192.529749,53.7 L192.529749,28.5 C192.529749,18.45 197.635842,15.15 204.994624,15.15 L209.5,15.15 L209.5,0 L204.844444,0 C186.67276,0 175.709677,9.15 175.709677,28.95 L175.709677,50.25 C175.709677,60.45 169.251971,64.2 160.691756,64.2 L160.691756,80.85 Z" id="{-copy-5" transform="translate(185.095878, 72.600000) rotate(-180.000000) translate(-185.095878, -72.600000) "></path>
|
||||
<path d="M104.67491,86.25 C95.8143369,95.85 87.8548387,100.65 77.6426523,100.65 C60.9727599,100.65 48.8082437,88.95 48.8082437,72.9 C48.8082437,56.85 60.9727599,45 77.6426523,45 C87.7046595,45 96.7154122,50.25 105.125448,59.85 C113.685663,50.4 122.546237,45 132.157706,45 C148.827599,45 160.992115,56.85 160.992115,72.9 C160.992115,88.95 148.827599,100.65 132.157706,100.65 C121.94552,100.65 113.235125,95.85 104.67491,86.25 Z M77.9430108,62.1 C70.8845878,62.1 66.6795699,66.9 66.6795699,73.05 C66.6795699,79.2 70.8845878,83.85 77.9430108,83.85 C83.8,83.85 89.0562724,79.35 94.0121864,73.35 C88.755914,66.9 83.9501792,62.1 77.9430108,62.1 Z M131.857348,83.85 C126.000358,83.85 121.044444,79.2 115.788172,73.05 C121.194624,66.6 125.850179,62.1 131.857348,62.1 C138.915771,62.1 143.120789,66.9 143.120789,73.05 C143.120789,79.2 138.915771,83.85 131.857348,83.85 Z" id="∞"></path>
|
||||
</g>
|
||||
<rect id="Bottom" fill="#FFFFFF" transform="translate(116.947287, 85.695730) rotate(-320.000000) translate(-116.947287, -85.695730) " x="107.936535" y="73.3847709" width="18.0215054" height="24.6219184"></rect>
|
||||
<rect id="Top" fill="#FFFFFF" transform="translate(91.942412, 61.695730) rotate(-320.000000) translate(-91.942412, -61.695730) " x="82.9316592" y="49.3847709" width="18.0215054" height="24.6219184"></rect>
|
||||
</g>
|
||||
<text id="Superset-Copy" font-family="Roboto-Black, Roboto" font-size="37.5" font-weight="700" letter-spacing="0.500625014" fill="#484848">
|
||||
<tspan x="26.281362" y="192.625">Superset</tspan>
|
||||
</text>
|
||||
</g>
|
||||
</g>
|
||||
<g id="Group-10" transform="translate(49.500000, 45.500000)">
|
||||
<g id="WORK-SPACE">
|
||||
<path d="M55.8666667,41.25 C64.4268817,50.85 73.137276,55.65 83.3494624,55.65 C100.019355,55.65 112.183871,43.95 112.183871,27.9 C112.183871,11.85 100.019355,0 83.3494624,0 C73.7379928,0 64.8774194,5.4 56.3172043,14.85 C47.9071685,5.25 38.8964158,0 28.8344086,0 C12.1645161,0 -2.84217094e-14,11.85 -2.84217094e-14,27.9 C-2.84217094e-14,43.95 12.1645161,55.65 28.8344086,55.65 C39.046595,55.65 47.0060932,50.85 55.8666667,41.25 Z" id="∞-copy-2" fill="#484848"></path>
|
||||
<path d="M35.3031737,7.82736301 L54.6231734,7.82736301 C54.6231734,7.82736301 54.1382597,11.9130391 54.1201021,16.2068622 C54.1019446,20.5006853 53.079701,24.1223631 53.079701,24.1223631 L35.3031737,24.1223631 L35.3031737,7.82736301 Z" id="Path" fill="#00D1C1" transform="translate(44.963174, 15.974863) rotate(-50.000000) translate(-44.963174, -15.974863) "></path>
|
||||
<rect id="Path-Copy" fill="#00D1C1" transform="translate(67.518574, 40.130521) rotate(-50.000000) translate(-67.518574, -40.130521) " x="57.8585742" y="31.9830205" width="19.3199997" height="16.2950001"></rect>
|
||||
<path d="M29.134767,17.1 C35.1419355,17.1 39.9476703,21.9 45.2039427,28.35 C40.2480287,34.35 34.9917563,38.85 29.134767,38.85 C22.0763441,38.85 17.8713262,34.2 17.8713262,28.05 C17.8713262,21.9 22.0763441,17.1 29.134767,17.1 Z" id="Path" fill="#FFFFFF"></path>
|
||||
<path d="M83.0491039,38.85 C77.1921147,38.85 72.2362007,34.2 66.9799283,28.05 C72.3863799,21.6 77.0419355,17.1 83.0491039,17.1 C90.1075269,17.1 94.3125448,21.9 94.3125448,28.05 C94.3125448,34.2 90.1075269,38.85 83.0491039,38.85 Z" id="Path" fill="#FFFFFF"></path>
|
||||
</g>
|
||||
<use id="∞-copy-2" stroke="#FFFFFF" mask="url(#mask-2)" stroke-width="10" xlink:href="#path-1"></use>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 6.6 KiB |
BIN
superset/assets/branding/Full Lockup With Text@2x.png
Normal file
|
After Width: | Height: | Size: 29 KiB |
34
superset/assets/branding/Full Lockup Without Text@1x.svg
Normal file
@@ -0,0 +1,34 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="210px" height="146px" viewBox="0 0 210 146" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<!-- Generator: Sketch 42 (36781) - http://www.bohemiancoding.com/sketch -->
|
||||
<title>Full Lockup Without Text@1x</title>
|
||||
<desc>Created with Sketch.</desc>
|
||||
<defs>
|
||||
<path d="M55.8666667,41.25 C64.4268817,50.85 73.137276,55.65 83.3494624,55.65 C100.019355,55.65 112.183871,43.95 112.183871,27.9 C112.183871,11.85 100.019355,0 83.3494624,0 C73.7379928,0 64.8774194,5.4 56.3172043,14.85 C47.9071685,5.25 38.8964158,0 28.8344086,0 C12.1645161,0 -2.84217094e-14,11.85 -2.84217094e-14,27.9 C-2.84217094e-14,43.95 12.1645161,55.65 28.8344086,55.65 C39.046595,55.65 47.0060932,50.85 55.8666667,41.25 Z" id="path-1"></path>
|
||||
<mask id="mask-2" maskContentUnits="userSpaceOnUse" maskUnits="objectBoundingBox" x="-5" y="-5" width="122.183871" height="65.65">
|
||||
<rect x="-5" y="-5" width="122.183871" height="65.65" fill="white"></rect>
|
||||
<use xlink:href="#path-1" fill="black"></use>
|
||||
</mask>
|
||||
</defs>
|
||||
<g id="Main" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="Superset" transform="translate(-177.000000, -466.000000)">
|
||||
<g id="Full-Lockup-Without-Text" transform="translate(177.000000, 466.000000)">
|
||||
<path d="M0.5,81.35 C9.06021505,81.35 15.5179211,85.1 15.5179211,95.3 L15.5179211,116.6 C15.5179211,136.4 26.4810036,145.7 44.6526882,145.7 L49.3082437,145.7 L49.3082437,130.55 L44.8028674,130.55 C37.444086,130.55 32.3379928,127.1 32.3379928,117.05 L32.3379928,92 C32.3379928,82.4 26.781362,75.5 17.1698925,73.1 C26.781362,70.55 32.3379928,63.8 32.3379928,54.2 L32.3379928,29 C32.3379928,18.95 37.444086,15.65 44.8028674,15.65 L49.3082437,15.65 L49.3082437,0.5 L44.6526882,0.5 C26.4810036,0.5 15.5179211,9.65 15.5179211,29.45 L15.5179211,50.75 C15.5179211,60.95 9.06021505,64.7 0.5,64.7 L0.5,81.35 Z" id="{-copy-4" fill="#484848"></path>
|
||||
<path d="M161.191756,81.35 C169.751971,81.35 176.209677,85.1 176.209677,95.3 L176.209677,116.6 C176.209677,136.4 187.17276,145.7 205.344444,145.7 L210,145.7 L210,130.55 L205.494624,130.55 C198.135842,130.55 193.029749,127.1 193.029749,117.05 L193.029749,92 C193.029749,82.4 187.473118,75.5 177.861649,73.1 C187.473118,70.55 193.029749,63.8 193.029749,54.2 L193.029749,29 C193.029749,18.95 198.135842,15.65 205.494624,15.65 L210,15.65 L210,0.5 L205.344444,0.5 C187.17276,0.5 176.209677,9.65 176.209677,29.45 L176.209677,50.75 C176.209677,60.95 169.751971,64.7 161.191756,64.7 L161.191756,81.35 Z" id="{-copy-5" fill="#484848" transform="translate(185.595878, 73.100000) rotate(-180.000000) translate(-185.595878, -73.100000) "></path>
|
||||
<path d="M105.366667,86.75 C96.5060932,96.35 88.546595,101.15 78.3344086,101.15 C61.6645161,101.15 49.5,89.45 49.5,73.4 C49.5,57.35 61.6645161,45.5 78.3344086,45.5 C88.3964158,45.5 97.4071685,50.75 105.817204,60.35 C114.377419,50.9 123.237993,45.5 132.849462,45.5 C149.519355,45.5 161.683871,57.35 161.683871,73.4 C161.683871,89.45 149.519355,101.15 132.849462,101.15 C122.637276,101.15 113.926882,96.35 105.366667,86.75 Z M78.634767,62.6 C71.5763441,62.6 67.3713262,67.4 67.3713262,73.55 C67.3713262,79.7 71.5763441,84.35 78.634767,84.35 C84.4917563,84.35 89.7480287,79.85 94.7039427,73.85 C89.4476703,67.4 84.6419355,62.6 78.634767,62.6 Z M132.549104,84.35 C126.692115,84.35 121.736201,79.7 116.479928,73.55 C121.88638,67.1 126.541935,62.6 132.549104,62.6 C139.607527,62.6 143.812545,67.4 143.812545,73.55 C143.812545,79.7 139.607527,84.35 132.549104,84.35 Z" id="∞" fill="#484848"></path>
|
||||
<rect id="Bottom" fill="#FFFFFF" transform="translate(117.815969, 86.222742) rotate(-320.000000) translate(-117.815969, -86.222742) " x="108.805216" y="73.9117829" width="18.0215054" height="24.6219184"></rect>
|
||||
<polygon id="Top" fill="#FFFFFF" transform="translate(93.488745, 61.141018) rotate(-320.000000) translate(-93.488745, -61.141018) " points="84.477992 50.894853 102.499497 50.894853 102.499497 71.3871824 84.5471936 70.9586997"></polygon>
|
||||
<g id="Group-10" transform="translate(49.500000, 45.500000)">
|
||||
<g id="WORK-SPACE">
|
||||
<path d="M55.8666667,41.25 C64.4268817,50.85 73.137276,55.65 83.3494624,55.65 C100.019355,55.65 112.183871,43.95 112.183871,27.9 C112.183871,11.85 100.019355,0 83.3494624,0 C73.7379928,0 64.8774194,5.4 56.3172043,14.85 C47.9071685,5.25 38.8964158,0 28.8344086,0 C12.1645161,0 -2.84217094e-14,11.85 -2.84217094e-14,27.9 C-2.84217094e-14,43.95 12.1645161,55.65 28.8344086,55.65 C39.046595,55.65 47.0060932,50.85 55.8666667,41.25 Z" id="∞-copy-2" fill="#484848"></path>
|
||||
<path d="M35.3031737,7.82736301 L54.6231734,7.82736301 C54.6231734,7.82736301 54.1382597,11.9130391 54.1201021,16.2068622 C54.1019446,20.5006853 53.079701,24.1223631 53.079701,24.1223631 L35.3031737,24.1223631 L35.3031737,7.82736301 Z" id="Path" fill="#00D1C1" transform="translate(44.963174, 15.974863) rotate(-50.000000) translate(-44.963174, -15.974863) "></path>
|
||||
<rect id="Path-Copy" fill="#00D1C1" transform="translate(67.518574, 40.130521) rotate(-50.000000) translate(-67.518574, -40.130521) " x="57.8585742" y="31.9830205" width="19.3199997" height="16.2950001"></rect>
|
||||
<path d="M29.134767,17.1 C35.1419355,17.1 39.9476703,21.9 45.2039427,28.35 C40.2480287,34.35 34.9917563,38.85 29.134767,38.85 C22.0763441,38.85 17.8713262,34.2 17.8713262,28.05 C17.8713262,21.9 22.0763441,17.1 29.134767,17.1 Z" id="Path" fill="#FFFFFF"></path>
|
||||
<path d="M83.0491039,38.85 C77.1921147,38.85 72.2362007,34.2 66.9799283,28.05 C72.3863799,21.6 77.0419355,17.1 83.0491039,17.1 C90.1075269,17.1 94.3125448,21.9 94.3125448,28.05 C94.3125448,34.2 90.1075269,38.85 83.0491039,38.85 Z" id="Path" fill="#FFFFFF"></path>
|
||||
</g>
|
||||
<use id="∞-copy-2" stroke="#FFFFFF" mask="url(#mask-2)" stroke-width="10" xlink:href="#path-1"></use>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 6.1 KiB |
BIN
superset/assets/branding/Full Lockup Without Text@2x.png
Normal file
|
After Width: | Height: | Size: 22 KiB |
35
superset/assets/branding/Horizontal.svg
Normal file
@@ -0,0 +1,35 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="350px" height="66px" viewBox="0 0 350 66" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<!-- Generator: Sketch 42 (36781) - http://www.bohemiancoding.com/sketch -->
|
||||
<title>Horizontal</title>
|
||||
<desc>Created with Sketch.</desc>
|
||||
<defs>
|
||||
<path d="M55.8666667,41.25 C64.4268817,50.85 73.137276,55.65 83.3494624,55.65 C100.019355,55.65 112.183871,43.95 112.183871,27.9 C112.183871,11.85 100.019355,0 83.3494624,0 C73.7379928,0 64.8774194,5.4 56.3172043,14.85 C47.9071685,5.25 38.8964158,0 28.8344086,0 C12.1645161,0 -2.84217094e-14,11.85 -2.84217094e-14,27.9 C-2.84217094e-14,43.95 12.1645161,55.65 28.8344086,55.65 C39.046595,55.65 47.0060932,50.85 55.8666667,41.25 Z" id="path-1"></path>
|
||||
<mask id="mask-2" maskContentUnits="userSpaceOnUse" maskUnits="objectBoundingBox" x="-5" y="-5" width="122.183871" height="65.65">
|
||||
<rect x="-5" y="-5" width="122.183871" height="65.65" fill="white"></rect>
|
||||
<use xlink:href="#path-1" fill="black"></use>
|
||||
</mask>
|
||||
</defs>
|
||||
<g id="Main" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="Superset" transform="translate(-319.000000, -195.000000)">
|
||||
<g id="Horizontal" transform="translate(324.000000, 200.000000)">
|
||||
<g id="Group-3">
|
||||
<text id="Superset" font-family="Roboto-Black, Roboto" font-size="50" font-weight="700" letter-spacing="0.670000017" fill="#484848">
|
||||
<tspan x="137" y="46">Superse</tspan>
|
||||
<tspan x="328.335508" y="46">t</tspan>
|
||||
</text>
|
||||
<g id="Group-10-Copy-8">
|
||||
<g id="WORK-SPACE">
|
||||
<path d="M55.8666667,41.25 C64.4268817,50.85 73.137276,55.65 83.3494624,55.65 C100.019355,55.65 112.183871,43.95 112.183871,27.9 C112.183871,11.85 100.019355,0 83.3494624,0 C73.7379928,0 64.8774194,5.4 56.3172043,14.85 C47.9071685,5.25 38.8964158,0 28.8344086,0 C12.1645161,0 -2.84217094e-14,11.85 -2.84217094e-14,27.9 C-2.84217094e-14,43.95 12.1645161,55.65 28.8344086,55.65 C39.046595,55.65 47.0060932,50.85 55.8666667,41.25 Z" id="∞-copy-2" fill="#484848"></path>
|
||||
<path d="M35.3031737,7.82736301 L54.6231734,7.82736301 C54.6231734,7.82736301 54.1382597,11.9130391 54.1201021,16.2068622 C54.1019446,20.5006853 53.079701,24.1223631 53.079701,24.1223631 L35.3031737,24.1223631 L35.3031737,7.82736301 Z" id="Path" fill="#00D1C1" transform="translate(44.963174, 15.974863) rotate(-50.000000) translate(-44.963174, -15.974863) "></path>
|
||||
<rect id="Path-Copy" fill="#00D1C1" transform="translate(67.518574, 40.130521) rotate(-50.000000) translate(-67.518574, -40.130521) " x="57.8585742" y="31.9830205" width="19.3199997" height="16.2950001"></rect>
|
||||
<path d="M29.134767,17.1 C35.1419355,17.1 39.9476703,21.9 45.2039427,28.35 C40.2480287,34.35 34.9917563,38.85 29.134767,38.85 C22.0763441,38.85 17.8713262,34.2 17.8713262,28.05 C17.8713262,21.9 22.0763441,17.1 29.134767,17.1 Z" id="Path" fill="#FFFFFF"></path>
|
||||
<path d="M83.0491039,38.85 C77.1921147,38.85 72.2362007,34.2 66.9799283,28.05 C72.3863799,21.6 77.0419355,17.1 83.0491039,17.1 C90.1075269,17.1 94.3125448,21.9 94.3125448,28.05 C94.3125448,34.2 90.1075269,38.85 83.0491039,38.85 Z" id="Path" fill="#FFFFFF"></path>
|
||||
</g>
|
||||
<use id="∞-copy-2" stroke="#FFFFFF" mask="url(#mask-2)" stroke-width="10" xlink:href="#path-1"></use>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.6 KiB |
BIN
superset/assets/branding/Horizontal@2x.png
Normal file
|
After Width: | Height: | Size: 22 KiB |
BIN
superset/assets/branding/Solo Mark.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
27
superset/assets/branding/Solo Mark@1x.svg
Normal file
@@ -0,0 +1,27 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="123px" height="66px" viewBox="0 0 123 66" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<!-- Generator: Sketch 42 (36781) - http://www.bohemiancoding.com/sketch -->
|
||||
<title>Solo Mark@1x</title>
|
||||
<desc>Created with Sketch.</desc>
|
||||
<defs>
|
||||
<path d="M55.8666667,41.25 C64.4268817,50.85 73.137276,55.65 83.3494624,55.65 C100.019355,55.65 112.183871,43.95 112.183871,27.9 C112.183871,11.85 100.019355,0 83.3494624,0 C73.7379928,0 64.8774194,5.4 56.3172043,14.85 C47.9071685,5.25 38.8964158,0 28.8344086,0 C12.1645161,0 -2.84217094e-14,11.85 -2.84217094e-14,27.9 C-2.84217094e-14,43.95 12.1645161,55.65 28.8344086,55.65 C39.046595,55.65 47.0060932,50.85 55.8666667,41.25 Z" id="path-1"></path>
|
||||
<mask id="mask-2" maskContentUnits="userSpaceOnUse" maskUnits="objectBoundingBox" x="-5" y="-5" width="122.183871" height="65.65">
|
||||
<rect x="-5" y="-5" width="122.183871" height="65.65" fill="white"></rect>
|
||||
<use xlink:href="#path-1" fill="black"></use>
|
||||
</mask>
|
||||
</defs>
|
||||
<g id="Main" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="Superset" transform="translate(-787.000000, -557.000000)">
|
||||
<g id="Solo-Mark" transform="translate(792.000000, 562.000000)">
|
||||
<g id="WORK-SPACE">
|
||||
<path d="M55.8666667,41.25 C64.4268817,50.85 73.137276,55.65 83.3494624,55.65 C100.019355,55.65 112.183871,43.95 112.183871,27.9 C112.183871,11.85 100.019355,0 83.3494624,0 C73.7379928,0 64.8774194,5.4 56.3172043,14.85 C47.9071685,5.25 38.8964158,0 28.8344086,0 C12.1645161,0 -2.84217094e-14,11.85 -2.84217094e-14,27.9 C-2.84217094e-14,43.95 12.1645161,55.65 28.8344086,55.65 C39.046595,55.65 47.0060932,50.85 55.8666667,41.25 Z" id="∞-copy-2" fill="#484848"></path>
|
||||
<path d="M35.3031737,7.82736301 L54.6231734,7.82736301 C54.6231734,7.82736301 54.1382597,11.9130391 54.1201021,16.2068622 C54.1019446,20.5006853 53.079701,24.1223631 53.079701,24.1223631 L35.3031737,24.1223631 L35.3031737,7.82736301 Z" id="Path" fill="#00D1C1" transform="translate(44.963174, 15.974863) rotate(-50.000000) translate(-44.963174, -15.974863) "></path>
|
||||
<rect id="Path-Copy" fill="#00D1C1" transform="translate(67.518574, 40.130521) rotate(-50.000000) translate(-67.518574, -40.130521) " x="57.8585742" y="31.9830205" width="19.3199997" height="16.2950001"></rect>
|
||||
<path d="M29.134767,17.1 C35.1419355,17.1 39.9476703,21.9 45.2039427,28.35 C40.2480287,34.35 34.9917563,38.85 29.134767,38.85 C22.0763441,38.85 17.8713262,34.2 17.8713262,28.05 C17.8713262,21.9 22.0763441,17.1 29.134767,17.1 Z" id="Path" fill="#FFFFFF"></path>
|
||||
<path d="M83.0491039,38.85 C77.1921147,38.85 72.2362007,34.2 66.9799283,28.05 C72.3863799,21.6 77.0419355,17.1 83.0491039,17.1 C90.1075269,17.1 94.3125448,21.9 94.3125448,28.05 C94.3125448,34.2 90.1075269,38.85 83.0491039,38.85 Z" id="Path" fill="#FFFFFF"></path>
|
||||
</g>
|
||||
<use id="∞-copy-2" stroke="#FFFFFF" mask="url(#mask-2)" stroke-width="10" xlink:href="#path-1"></use>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.2 KiB |
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 5.5 KiB |
BIN
superset/assets/images/superset-logo@2x.png
Normal file
|
After Width: | Height: | Size: 4.0 KiB |
|
Before Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 154 KiB |
|
Before Width: | Height: | Size: 190 KiB |
|
Before Width: | Height: | Size: 245 KiB |
|
Before Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 133 KiB |
|
Before Width: | Height: | Size: 128 KiB |
|
Before Width: | Height: | Size: 141 KiB |
|
Before Width: | Height: | Size: 49 KiB |
|
Before Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 106 KiB |
BIN
superset/assets/images/tutorial/tutorial_01_sources_database.png
Normal file
|
After Width: | Height: | Size: 19 KiB |
BIN
superset/assets/images/tutorial/tutorial_02_add_database.png
Normal file
|
After Width: | Height: | Size: 19 KiB |
BIN
superset/assets/images/tutorial/tutorial_03_database_name.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 52 KiB |
BIN
superset/assets/images/tutorial/tutorial_05_connection_popup.png
Normal file
|
After Width: | Height: | Size: 82 KiB |
BIN
superset/assets/images/tutorial/tutorial_06_list_of_tables.png
Normal file
|
After Width: | Height: | Size: 23 KiB |
BIN
superset/assets/images/tutorial/tutorial_07_save_button.png
Normal file
|
After Width: | Height: | Size: 8.0 KiB |
BIN
superset/assets/images/tutorial/tutorial_08_sources_tables.png
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
superset/assets/images/tutorial/tutorial_09_add_new_table.png
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
superset/assets/images/tutorial/tutorial_10_table_name.png
Normal file
|
After Width: | Height: | Size: 26 KiB |
BIN
superset/assets/images/tutorial/tutorial_11_choose_db.png
Normal file
|
After Width: | Height: | Size: 22 KiB |
|
After Width: | Height: | Size: 32 KiB |
|
After Width: | Height: | Size: 32 KiB |
BIN
superset/assets/images/tutorial/tutorial_14_field_config.png
Normal file
|
After Width: | Height: | Size: 60 KiB |
BIN
superset/assets/images/tutorial/tutorial_15_click_table_name.png
Normal file
|
After Width: | Height: | Size: 7.7 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 11 KiB |
BIN
superset/assets/images/tutorial/tutorial_18_choose_metric.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
superset/assets/images/tutorial/tutorial_19_click_query.png
Normal file
|
After Width: | Height: | Size: 5.6 KiB |
|
After Width: | Height: | Size: 5.2 KiB |
BIN
superset/assets/images/tutorial/tutorial_21_group_by.png
Normal file
|
After Width: | Height: | Size: 6.7 KiB |
BIN
superset/assets/images/tutorial/tutorial_22_group_by_result.png
Normal file
|
After Width: | Height: | Size: 22 KiB |
|
After Width: | Height: | Size: 8.0 KiB |
BIN
superset/assets/images/tutorial/tutorial_24_max_metric.png
Normal file
|
After Width: | Height: | Size: 6.6 KiB |
BIN
superset/assets/images/tutorial/tutorial_25_max_temp_filter.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
superset/assets/images/tutorial/tutorial_26_row_limit.png
Normal file
|
After Width: | Height: | Size: 4.8 KiB |
BIN
superset/assets/images/tutorial/tutorial_27_top_10_max_temps.png
Normal file
|
After Width: | Height: | Size: 49 KiB |
BIN
superset/assets/images/tutorial/tutorial_28_bar_chart.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 20 KiB |
|
After Width: | Height: | Size: 74 KiB |
|
After Width: | Height: | Size: 33 KiB |
|
After Width: | Height: | Size: 24 KiB |
BIN
superset/assets/images/tutorial/tutorial_33_dashboard.png
Normal file
|
After Width: | Height: | Size: 5.1 KiB |
|
After Width: | Height: | Size: 6.5 KiB |
|
After Width: | Height: | Size: 65 KiB |
|
After Width: | Height: | Size: 123 KiB |
BIN
superset/assets/images/viz_thumbnails/bullet.png
Normal file
|
After Width: | Height: | Size: 8.6 KiB |
BIN
superset/assets/images/viz_thumbnails/dual_line.png
Normal file
|
After Width: | Height: | Size: 155 KiB |
@@ -85,8 +85,12 @@ export function fetchQueryResults(query) {
|
||||
success(results) {
|
||||
dispatch(querySuccess(query, results));
|
||||
},
|
||||
error() {
|
||||
dispatch(queryFailed(query, 'Failed at retrieving results from the results backend'));
|
||||
error(err) {
|
||||
let msg = 'Failed at retrieving results from the results backend';
|
||||
if (err.responseJSON && err.responseJSON.error) {
|
||||
msg = err.responseJSON.error;
|
||||
}
|
||||
dispatch(queryFailed(query, msg));
|
||||
},
|
||||
});
|
||||
};
|
||||
@@ -209,9 +213,9 @@ export function mergeTable(table, query) {
|
||||
return { type: MERGE_TABLE, table, query };
|
||||
}
|
||||
|
||||
export function addTable(query, tableName) {
|
||||
export function addTable(query, tableName, schemaName) {
|
||||
return function (dispatch) {
|
||||
let url = `/superset/table/${query.dbId}/${tableName}/${query.schema}/`;
|
||||
let url = `/superset/table/${query.dbId}/${tableName}/${schemaName}/`;
|
||||
$.get(url, (data) => {
|
||||
const dataPreviewQuery = {
|
||||
id: shortid.generate(),
|
||||
@@ -228,7 +232,7 @@ export function addTable(query, tableName) {
|
||||
Object.assign(data, {
|
||||
dbId: query.dbId,
|
||||
queryEditorId: query.id,
|
||||
schema: query.schema,
|
||||
schema: schemaName,
|
||||
expanded: true,
|
||||
}), dataPreviewQuery)
|
||||
);
|
||||
@@ -244,12 +248,12 @@ export function addTable(query, tableName) {
|
||||
);
|
||||
});
|
||||
|
||||
url = `/superset/extra_table_metadata/${query.dbId}/${tableName}/${query.schema}/`;
|
||||
url = `/superset/extra_table_metadata/${query.dbId}/${tableName}/${schemaName}/`;
|
||||
$.get(url, (data) => {
|
||||
const table = {
|
||||
dbId: query.dbId,
|
||||
queryEditorId: query.id,
|
||||
schema: query.schema,
|
||||
schema: schemaName,
|
||||
name: tableName,
|
||||
};
|
||||
Object.assign(table, data);
|
||||
@@ -294,3 +298,23 @@ export function removeTable(table) {
|
||||
export function refreshQueries(alteredQueries) {
|
||||
return { type: REFRESH_QUERIES, alteredQueries };
|
||||
}
|
||||
|
||||
export function popStoredQuery(urlId) {
|
||||
return function (dispatch) {
|
||||
$.ajax({
|
||||
type: 'GET',
|
||||
url: `/kv/${urlId}`,
|
||||
success: (data) => {
|
||||
const newQuery = JSON.parse(data);
|
||||
const queryEditorProps = {
|
||||
title: newQuery.title ? newQuery.title : 'shared query',
|
||||
dbId: newQuery.dbId ? parseInt(newQuery.dbId, 10) : null,
|
||||
schema: newQuery.schema ? newQuery.schema : null,
|
||||
autorun: newQuery.autorun ? newQuery.autorun : false,
|
||||
sql: newQuery.sql ? newQuery.sql : 'SELECT ...',
|
||||
};
|
||||
dispatch(addQueryEditor(queryEditorProps));
|
||||
},
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
@@ -33,11 +33,14 @@ class AceEditorWrapper extends React.PureComponent {
|
||||
componentDidMount() {
|
||||
// Making sure no text is selected from previous mount
|
||||
this.props.actions.queryEditorSetSelectedText(this.props.queryEditor, null);
|
||||
this.setAutoCompleter();
|
||||
this.setAutoCompleter(this.props);
|
||||
}
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if (!areArraysShallowEqual(this.props.tables, nextProps.tables)) {
|
||||
this.setAutoCompleter();
|
||||
this.setAutoCompleter(nextProps);
|
||||
}
|
||||
if (nextProps.sql !== this.props.sql) {
|
||||
this.setState({ sql: nextProps.sql });
|
||||
}
|
||||
}
|
||||
textChange(text) {
|
||||
@@ -63,11 +66,11 @@ class AceEditorWrapper extends React.PureComponent {
|
||||
this.props.queryEditor, editor.getSelectedText());
|
||||
});
|
||||
}
|
||||
setAutoCompleter() {
|
||||
setAutoCompleter(props) {
|
||||
// Loading table and column names as auto-completable words
|
||||
let words = [];
|
||||
const columns = {};
|
||||
const tables = this.props.tables || [];
|
||||
const tables = props.tables || [];
|
||||
tables.forEach(t => {
|
||||
words.push({ name: t.name, value: t.name, score: 55, meta: 'table' });
|
||||
const cols = t.columns || [];
|
||||
@@ -78,13 +81,15 @@ class AceEditorWrapper extends React.PureComponent {
|
||||
words = words.concat(Object.keys(columns).map(col => (
|
||||
{ name: col, value: col, score: 50, meta: 'column' }
|
||||
)));
|
||||
this.setState({ words });
|
||||
const completer = {
|
||||
getCompletions: this.getCompletions.bind(this),
|
||||
};
|
||||
if (langTools) {
|
||||
langTools.setCompleters([completer, langTools.keyWordCompleter]);
|
||||
}
|
||||
|
||||
this.setState({ words }, () => {
|
||||
const completer = {
|
||||
getCompletions: this.getCompletions.bind(this),
|
||||
};
|
||||
if (langTools) {
|
||||
langTools.setCompleters([completer, langTools.keyWordCompleter]);
|
||||
}
|
||||
});
|
||||
}
|
||||
render() {
|
||||
return (
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
const $ = window.$ = require('jquery');
|
||||
import * as Actions from '../actions';
|
||||
import React from 'react';
|
||||
|
||||
@@ -14,13 +15,29 @@ class App extends React.PureComponent {
|
||||
super(props);
|
||||
this.state = {
|
||||
hash: window.location.hash,
|
||||
contentHeight: this.getHeight(),
|
||||
};
|
||||
}
|
||||
componentDidMount() {
|
||||
/* eslint-disable react/no-did-mount-set-state */
|
||||
this.setState({ contentHeight: this.getHeight() });
|
||||
window.addEventListener('hashchange', this.onHashChanged.bind(this));
|
||||
window.addEventListener('resize', this.handleResize.bind(this));
|
||||
}
|
||||
componentWillUnmount() {
|
||||
window.removeEventListener('hashchange', this.onHashChanged.bind(this));
|
||||
window.removeEventListener('resize', this.handleResize.bind(this));
|
||||
}
|
||||
getHeight() {
|
||||
const navHeight = 90;
|
||||
const headerHeight = $('.nav-tabs').outerHeight() ?
|
||||
$('.nav-tabs').outerHeight() : $('#search-header').outerHeight();
|
||||
const warningHeight = $('#navbar-warning').outerHeight();
|
||||
const alertHeight = $('#sqllab-alerts').outerHeight();
|
||||
return `${window.innerHeight - navHeight - headerHeight - warningHeight - alertHeight}px`;
|
||||
}
|
||||
handleResize() {
|
||||
this.setState({ contentHeight: this.getHeight() });
|
||||
}
|
||||
onHashChanged() {
|
||||
this.setState({ hash: window.location.hash });
|
||||
@@ -32,7 +49,7 @@ class App extends React.PureComponent {
|
||||
<div className="container-fluid">
|
||||
<div className="row">
|
||||
<div className="col-md-12">
|
||||
<QuerySearch />
|
||||
<QuerySearch height={this.state.contentHeight} actions={this.props.actions} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -41,13 +58,13 @@ class App extends React.PureComponent {
|
||||
content = (
|
||||
<div>
|
||||
<QueryAutoRefresh />
|
||||
<TabbedSqlEditors />
|
||||
<TabbedSqlEditors editorHeight={this.state.contentHeight} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<div className="App SqlLab">
|
||||
<Alerts alerts={this.props.alerts} actions={this.props.actions} />
|
||||
<Alerts id="sqllab-alerts" alerts={this.props.alerts} actions={this.props.actions} />
|
||||
<div className="container-fluid">
|
||||
{content}
|
||||
</div>
|
||||
|
||||
@@ -1,47 +1,36 @@
|
||||
import React from 'react';
|
||||
import CopyToClipboard from '../../components/CopyToClipboard';
|
||||
import { getShortUrl } from '../../../utils/common';
|
||||
import { storeQuery } from '../../../utils/common';
|
||||
|
||||
const propTypes = {
|
||||
queryEditor: React.PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
export default class CopyQueryTabUrl extends React.PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
shortUrl: '',
|
||||
};
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
getUrl(callback) {
|
||||
const qe = this.props.queryEditor;
|
||||
const params = [];
|
||||
if (qe.dbId) params.push('dbid=' + qe.dbId);
|
||||
if (qe.title) params.push('title=' + encodeURIComponent(qe.title));
|
||||
if (qe.schema) params.push('schema=' + encodeURIComponent(qe.schema));
|
||||
if (qe.autorun) params.push('autorun=' + qe.autorun);
|
||||
if (qe.sql) params.push('sql=' + encodeURIComponent(qe.sql));
|
||||
|
||||
const queryString = params.join('&');
|
||||
const queryLink = window.location.pathname + '?' + queryString;
|
||||
getShortUrl(queryLink, this.onShortUrlSuccess.bind(this));
|
||||
}
|
||||
|
||||
onShortUrlSuccess(data) {
|
||||
this.setState({
|
||||
shortUrl: data,
|
||||
});
|
||||
const sharedQuery = {
|
||||
dbId: qe.dbId,
|
||||
title: qe.title,
|
||||
schema: qe.schema,
|
||||
autorun: qe.autorun,
|
||||
sql: qe.sql,
|
||||
};
|
||||
storeQuery(sharedQuery, callback);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<CopyToClipboard
|
||||
inMenu
|
||||
text={this.state.shortUrl}
|
||||
copyNode={<span>share query</span>}
|
||||
copyNode={(
|
||||
<div>
|
||||
<i className="fa fa-clipboard" /> <span>share query</span>
|
||||
</div>
|
||||
)}
|
||||
tooltipText="copy URL to clipboard"
|
||||
shouldShowText={false}
|
||||
getText={this.getUrl.bind(this)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,61 +0,0 @@
|
||||
const $ = window.$ = require('jquery');
|
||||
import React from 'react';
|
||||
import Select from 'react-select';
|
||||
|
||||
const propTypes = {
|
||||
onChange: React.PropTypes.func,
|
||||
actions: React.PropTypes.object,
|
||||
databaseId: React.PropTypes.number,
|
||||
valueRenderer: React.PropTypes.func,
|
||||
};
|
||||
|
||||
class DatabaseSelect extends React.PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
databaseLoading: false,
|
||||
databaseOptions: [],
|
||||
};
|
||||
}
|
||||
componentDidMount() {
|
||||
this.fetchDatabaseOptions();
|
||||
}
|
||||
changeDb(db) {
|
||||
this.props.onChange(db);
|
||||
}
|
||||
fetchDatabaseOptions() {
|
||||
this.setState({ databaseLoading: true });
|
||||
const url = '/databaseasync/api/read?_flt_0_expose_in_sqllab=1';
|
||||
$.get(url, (data) => {
|
||||
const options = data.result.map((db) => ({ value: db.id, label: db.database_name }));
|
||||
this.setState({ databaseOptions: options, databaseLoading: false });
|
||||
this.props.actions.setDatabases(data.result);
|
||||
if (data.result.length === 0) {
|
||||
this.props.actions.addAlert({
|
||||
bsStyle: 'danger',
|
||||
msg: "It seems you don't have access to any database",
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<Select
|
||||
name="select-db"
|
||||
placeholder={`Select a database (${this.state.databaseOptions.length})`}
|
||||
options={this.state.databaseOptions}
|
||||
value={this.props.databaseId}
|
||||
isLoading={this.state.databaseLoading}
|
||||
autosize={false}
|
||||
onChange={this.changeDb.bind(this)}
|
||||
valueRenderer={this.props.valueRenderer}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
DatabaseSelect.propTypes = propTypes;
|
||||
|
||||
export default DatabaseSelect;
|
||||
@@ -1,5 +1,4 @@
|
||||
import React from 'react';
|
||||
import { Well } from 'react-bootstrap';
|
||||
import SyntaxHighlighter from 'react-syntax-highlighter';
|
||||
import { github } from 'react-syntax-highlighter/dist/styles';
|
||||
import ModalTrigger from '../../components/ModalTrigger';
|
||||
@@ -45,11 +44,9 @@ class HighlightedSql extends React.Component {
|
||||
const props = this.props;
|
||||
let shownSql = props.shrink ? this.shrinkSql(props.sql) : props.sql;
|
||||
return (
|
||||
<Well>
|
||||
<SyntaxHighlighter language="sql" style={github}>
|
||||
{shownSql}
|
||||
</SyntaxHighlighter>
|
||||
</Well>);
|
||||
<SyntaxHighlighter language="sql" style={github}>
|
||||
{shownSql}
|
||||
</SyntaxHighlighter>);
|
||||
}
|
||||
generateModal() {
|
||||
const props = this.props;
|
||||
|
||||
@@ -3,10 +3,10 @@ import React from 'react';
|
||||
import { Button } from 'react-bootstrap';
|
||||
import Select from 'react-select';
|
||||
import QueryTable from './QueryTable';
|
||||
import DatabaseSelect from './DatabaseSelect';
|
||||
import { now, epochTimeXHoursAgo,
|
||||
epochTimeXDaysAgo, epochTimeXYearsAgo } from '../../modules/dates';
|
||||
import { STATUS_OPTIONS, TIME_OPTIONS } from '../constants';
|
||||
import AsyncSelect from '../../components/AsyncSelect';
|
||||
|
||||
const propTypes = {
|
||||
actions: React.PropTypes.object.isRequired,
|
||||
@@ -21,16 +21,13 @@ class QuerySearch extends React.PureComponent {
|
||||
databaseId: null,
|
||||
userId: null,
|
||||
searchText: null,
|
||||
from: null,
|
||||
to: null,
|
||||
from: '28 days ago',
|
||||
to: 'now',
|
||||
status: 'success',
|
||||
queriesArray: [],
|
||||
queriesLoading: true,
|
||||
};
|
||||
}
|
||||
componentWillMount() {
|
||||
this.fetchUsers();
|
||||
}
|
||||
componentDidMount() {
|
||||
this.refreshQueries();
|
||||
}
|
||||
@@ -89,18 +86,23 @@ class QuerySearch extends React.PureComponent {
|
||||
changeSearch(event) {
|
||||
this.setState({ searchText: event.target.value });
|
||||
}
|
||||
fetchUsers() {
|
||||
this.setState({ userLoading: true });
|
||||
const url = '/users/api/read';
|
||||
$.getJSON(url, (data, status) => {
|
||||
if (status === 'success') {
|
||||
const options = [];
|
||||
for (let i = 0; i < data.pks.length; i++) {
|
||||
options.push({ value: data.pks[i], label: data.result[i].username });
|
||||
}
|
||||
this.setState({ userOptions: options, userLoading: false });
|
||||
}
|
||||
});
|
||||
userMutator(data) {
|
||||
const options = [];
|
||||
for (let i = 0; i < data.pks.length; i++) {
|
||||
options.push({ value: data.pks[i], label: data.result[i].username });
|
||||
}
|
||||
return options;
|
||||
}
|
||||
dbMutator(data) {
|
||||
const options = data.result.map((db) => ({ value: db.id, label: db.database_name }));
|
||||
this.props.actions.setDatabases(data.result);
|
||||
if (data.result.length === 0) {
|
||||
this.props.actions.addAlert({
|
||||
bsStyle: 'danger',
|
||||
msg: "It seems you don't have access to any database",
|
||||
});
|
||||
}
|
||||
return options;
|
||||
}
|
||||
refreshQueries() {
|
||||
this.setState({ queriesLoading: true });
|
||||
@@ -116,33 +118,28 @@ class QuerySearch extends React.PureComponent {
|
||||
const url = this.insertParams('/superset/search_queries', params);
|
||||
$.getJSON(url, (data, status) => {
|
||||
if (status === 'success') {
|
||||
const newQueriesArray = [];
|
||||
for (const id in data) {
|
||||
newQueriesArray.push(data[id]);
|
||||
}
|
||||
this.setState({ queriesArray: newQueriesArray, queriesLoading: false });
|
||||
this.setState({ queriesArray: data, queriesLoading: false });
|
||||
}
|
||||
});
|
||||
}
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<div className="row space-1">
|
||||
<div id="search-header" className="row space-1">
|
||||
<div className="col-sm-2">
|
||||
<Select
|
||||
name="select-user"
|
||||
placeholder="[User]"
|
||||
options={this.state.userOptions}
|
||||
<AsyncSelect
|
||||
dataEndpoint="/users/api/read"
|
||||
mutator={this.userMutator}
|
||||
value={this.state.userId}
|
||||
isLoading={this.state.userLoading}
|
||||
autosize={false}
|
||||
onChange={this.changeUser.bind(this)}
|
||||
/>
|
||||
</div>
|
||||
<div className="col-sm-2">
|
||||
<DatabaseSelect
|
||||
<AsyncSelect
|
||||
onChange={this.onChange.bind(this)}
|
||||
databaseId={this.state.databaseId}
|
||||
dataEndpoint="/databaseasync/api/read?_flt_0_expose_in_sqllab=1"
|
||||
value={this.state.databaseId}
|
||||
mutator={this.dbMutator.bind(this)}
|
||||
/>
|
||||
</div>
|
||||
<div className="col-sm-4">
|
||||
@@ -192,16 +189,25 @@ class QuerySearch extends React.PureComponent {
|
||||
{this.state.queriesLoading ?
|
||||
(<img className="loading" alt="Loading..." src="/static/assets/images/loading.gif" />)
|
||||
:
|
||||
(<QueryTable
|
||||
columns={[
|
||||
'state', 'db', 'user', 'date',
|
||||
'progress', 'rows', 'sql', 'querylink',
|
||||
]}
|
||||
onUserClicked={this.onUserClicked.bind(this)}
|
||||
onDbClicked={this.onDbClicked.bind(this)}
|
||||
queries={this.state.queriesArray}
|
||||
actions={this.props.actions}
|
||||
/>)
|
||||
(
|
||||
<div
|
||||
style={{ height: this.props.height }}
|
||||
className="scrollbar-container"
|
||||
>
|
||||
<div className="scrollbar-content">
|
||||
<QueryTable
|
||||
columns={[
|
||||
'state', 'db', 'user', 'time',
|
||||
'progress', 'rows', 'sql', 'querylink',
|
||||
]}
|
||||
onUserClicked={this.onUserClicked.bind(this)}
|
||||
onDbClicked={this.onDbClicked.bind(this)}
|
||||
queries={this.state.queriesArray}
|
||||
actions={this.props.actions}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -2,7 +2,7 @@ import React from 'react';
|
||||
|
||||
import moment from 'moment';
|
||||
import { Table } from 'reactable';
|
||||
import { Label, ProgressBar } from 'react-bootstrap';
|
||||
import { Label, ProgressBar, Well } from 'react-bootstrap';
|
||||
import Link from './Link';
|
||||
import VisualizeModal from './VisualizeModal';
|
||||
import ResultSet from './ResultSet';
|
||||
@@ -10,7 +10,7 @@ import ModalTrigger from '../../components/ModalTrigger';
|
||||
import HighlightedSql from './HighlightedSql';
|
||||
import { STATE_BSSTYLE_MAP } from '../constants';
|
||||
import { fDuration } from '../../modules/dates';
|
||||
import { getLink } from '../../../utils/common';
|
||||
import { storeQuery } from '../../../utils/common';
|
||||
|
||||
const propTypes = {
|
||||
columns: React.PropTypes.array,
|
||||
@@ -38,17 +38,23 @@ class QueryTable extends React.PureComponent {
|
||||
activeQuery: null,
|
||||
};
|
||||
}
|
||||
getQueryLink(dbId, sql) {
|
||||
const params = ['dbid=' + dbId, 'sql=' + sql, 'title=Untitled Query'];
|
||||
const link = getLink(this.state.cleanUri, params);
|
||||
return encodeURI(link);
|
||||
callback(url) {
|
||||
window.open(url);
|
||||
}
|
||||
openQuery(dbId, schema, sql) {
|
||||
const newQuery = {
|
||||
dbId,
|
||||
title: 'Untitled Query',
|
||||
schema,
|
||||
sql,
|
||||
};
|
||||
storeQuery(newQuery, this.callback);
|
||||
}
|
||||
hideVisualizeModal() {
|
||||
this.setState({ showVisualizeModal: false });
|
||||
}
|
||||
showVisualizeModal(query) {
|
||||
this.setState({ showVisualizeModal: true });
|
||||
this.setState({ activeQuery: query });
|
||||
this.setState({ activeQuery: query, showVisualizeModal: true });
|
||||
}
|
||||
restoreSql(query) {
|
||||
this.props.actions.queryEditorSetSql({ id: query.sqlEditorId }, query.sql);
|
||||
@@ -66,14 +72,20 @@ class QueryTable extends React.PureComponent {
|
||||
removeQuery(query) {
|
||||
this.props.actions.removeQuery(query);
|
||||
}
|
||||
|
||||
render() {
|
||||
const data = this.props.queries.map((query) => {
|
||||
const q = Object.assign({}, query);
|
||||
if (q.endDttm) {
|
||||
q.duration = fDuration(q.startDttm, q.endDttm);
|
||||
}
|
||||
q.date = moment(q.startDttm).format('MMM Do YYYY');
|
||||
const time = moment(q.startDttm).format().split('T');
|
||||
q.time = (
|
||||
<div>
|
||||
<span>
|
||||
{time[0]} <br /> {time[1]}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
q.user = (
|
||||
<button
|
||||
className="btn btn-link btn-xs"
|
||||
@@ -91,8 +103,20 @@ class QueryTable extends React.PureComponent {
|
||||
</button>
|
||||
);
|
||||
q.started = moment(q.startDttm).format('HH:mm:ss');
|
||||
q.querylink = (
|
||||
<div style={{ width: '100px' }}>
|
||||
<button
|
||||
className="btn btn-link btn-xs"
|
||||
onClick={this.openQuery.bind(this, q.dbId, q.schema, q.sql)}
|
||||
>
|
||||
<i className="fa fa-external-link" />Open in SQL Editor
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
q.sql = (
|
||||
<HighlightedSql sql={q.sql} rawSql={q.executedSql} shrink maxWidth={60} />
|
||||
<Well>
|
||||
<HighlightedSql sql={q.sql} rawSql={q.executedSql} shrink maxWidth={60} />
|
||||
</Well>
|
||||
);
|
||||
if (q.resultsKey) {
|
||||
q.output = (
|
||||
@@ -114,7 +138,10 @@ class QueryTable extends React.PureComponent {
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
q.output = q.tempTable;
|
||||
// if query was run using ctas and force_ctas_schema was set
|
||||
// tempTable will have the schema
|
||||
const schemaUsed = q.ctas && q.tempTable && q.tempTable.includes('.') ? '' : q.schema;
|
||||
q.output = [schemaUsed, q.tempTable].filter((v) => (v)).join('.');
|
||||
}
|
||||
q.progress = (
|
||||
<ProgressBar
|
||||
@@ -166,16 +193,6 @@ class QueryTable extends React.PureComponent {
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
q.querylink = (
|
||||
<div style={{ width: '100px' }}>
|
||||
<a
|
||||
href={this.getQueryLink(q.dbId, q.sql)}
|
||||
className="btn btn-primary btn-xs"
|
||||
>
|
||||
<i className="fa fa-external-link" />Open in SQL Editor
|
||||
</a>
|
||||
</div>
|
||||
);
|
||||
return q;
|
||||
}).reverse();
|
||||
return (
|
||||
@@ -189,6 +206,7 @@ class QueryTable extends React.PureComponent {
|
||||
columns={this.props.columns}
|
||||
className="table table-condensed"
|
||||
data={data}
|
||||
itemsPerPage={50}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -46,6 +46,10 @@ class ResultSet extends React.PureComponent {
|
||||
this.clearQueryResults(nextProps.query)
|
||||
);
|
||||
}
|
||||
if (nextProps.query.resultsKey
|
||||
&& nextProps.query.resultsKey !== this.props.query.resultsKey) {
|
||||
this.fetchResults(nextProps.query);
|
||||
}
|
||||
}
|
||||
getControls() {
|
||||
if (this.props.search || this.props.visualize || this.props.csv) {
|
||||
@@ -137,6 +141,10 @@ class ResultSet extends React.PureComponent {
|
||||
|
||||
let sql;
|
||||
|
||||
if (query.state === 'stopped') {
|
||||
return <Alert bsStyle="warning">Query was stopped</Alert>;
|
||||
}
|
||||
|
||||
if (this.props.showSql) {
|
||||
sql = <HighlightedSql sql={query.sql} />;
|
||||
}
|
||||
@@ -186,7 +194,18 @@ class ResultSet extends React.PureComponent {
|
||||
{sql}
|
||||
<div className="ResultSet">
|
||||
<Table
|
||||
data={data}
|
||||
data={data.map(function (row) {
|
||||
const newRow = {};
|
||||
for (const k in row) {
|
||||
const val = row[k];
|
||||
if (typeof(val) === 'string') {
|
||||
newRow[k] = val;
|
||||
} else {
|
||||
newRow[k] = JSON.stringify(val);
|
||||
}
|
||||
}
|
||||
return newRow;
|
||||
})}
|
||||
columns={results.columns.map((col) => col.name)}
|
||||
sortable
|
||||
className="table table-condensed table-bordered"
|
||||
@@ -197,29 +216,20 @@ class ResultSet extends React.PureComponent {
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
} else if (query.resultsKey) {
|
||||
return (
|
||||
<div>
|
||||
<Alert bsStyle="warning">This query was run asynchronously
|
||||
<Button bsSize="sm" onClick={this.fetchResults.bind(this, query)}>
|
||||
Fetch results
|
||||
</Button>
|
||||
</Alert>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
if (query.cached) {
|
||||
return (
|
||||
<a
|
||||
href="#"
|
||||
<Button
|
||||
bsSize="sm"
|
||||
bsStyle="primary"
|
||||
onClick={this.reFetchQueryResults.bind(this, query)}
|
||||
>
|
||||
click to retrieve results
|
||||
</a>
|
||||
Fetch data preview
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
return (<Alert bsStyle="warning">The query returned no data</Alert>);
|
||||
return <Alert bsStyle="warning">The query returned no data</Alert>;
|
||||
}
|
||||
}
|
||||
ResultSet.propTypes = propTypes;
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
import React, { PropTypes } from 'react';
|
||||
import Button from '../../components/Button';
|
||||
|
||||
const propTypes = {
|
||||
allowAsync: PropTypes.bool.isRequired,
|
||||
dbId: PropTypes.number.isRequired,
|
||||
queryState: PropTypes.string.isRequired,
|
||||
runQuery: PropTypes.func.isRequired,
|
||||
selectedText: PropTypes.string,
|
||||
stopQuery: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default function RunQueryActionButton(props) {
|
||||
const runBtnText = props.selectedText ? 'Run Selected Query' : 'Run Query';
|
||||
const btnStyle = props.selectedText ? 'warning' : 'primary';
|
||||
const shouldShowStopBtn = ['running', 'pending'].indexOf(props.queryState) > -1;
|
||||
const asyncToolTip = 'Run query asynchronously';
|
||||
|
||||
const commonBtnProps = {
|
||||
bsSize: 'small',
|
||||
bsStyle: btnStyle,
|
||||
disabled: !(props.dbId),
|
||||
};
|
||||
|
||||
const syncBtn = (
|
||||
<Button
|
||||
{...commonBtnProps}
|
||||
onClick={() => props.runQuery(false)}
|
||||
key="run-btn"
|
||||
>
|
||||
<i className="fa fa-table" /> {runBtnText}
|
||||
</Button>
|
||||
);
|
||||
|
||||
const asyncBtn = (
|
||||
<Button
|
||||
{...commonBtnProps}
|
||||
onClick={() => props.runQuery(true)}
|
||||
key="run-async-btn"
|
||||
tooltip={asyncToolTip}
|
||||
>
|
||||
<i className="fa fa-table" /> {runBtnText}
|
||||
</Button>
|
||||
);
|
||||
|
||||
const stopBtn = (
|
||||
<Button
|
||||
{...commonBtnProps}
|
||||
onClick={props.stopQuery}
|
||||
>
|
||||
<i className="fa fa-stop" /> Stop
|
||||
</Button>
|
||||
);
|
||||
|
||||
let button;
|
||||
if (shouldShowStopBtn) {
|
||||
button = stopBtn;
|
||||
} else if (props.allowAsync) {
|
||||
button = asyncBtn;
|
||||
} else {
|
||||
button = syncBtn;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="inline m-r-5 pull-left">
|
||||
{button}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
RunQueryActionButton.propTypes = propTypes;
|
||||
@@ -5,7 +5,6 @@ import { connect } from 'react-redux';
|
||||
import { bindActionCreators } from 'redux';
|
||||
import * as Actions from '../actions';
|
||||
import React from 'react';
|
||||
import { areArraysShallowEqual } from '../../reduxUtils';
|
||||
|
||||
import shortid from 'shortid';
|
||||
|
||||
@@ -28,11 +27,6 @@ class SouthPane extends React.PureComponent {
|
||||
switchTab(id) {
|
||||
this.props.actions.setActiveSouthPaneTab(id);
|
||||
}
|
||||
shouldComponentUpdate(nextProps) {
|
||||
return !areArraysShallowEqual(this.props.editorQueries, nextProps.editorQueries)
|
||||
|| !areArraysShallowEqual(this.props.dataPreviewQueries, nextProps.dataPreviewQueries)
|
||||
|| this.props.activeSouthPaneTab !== nextProps.activeSouthPaneTab;
|
||||
}
|
||||
render() {
|
||||
let latestQuery;
|
||||
const props = this.props;
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
import React from 'react';
|
||||
import {
|
||||
Button,
|
||||
ButtonGroup,
|
||||
Col,
|
||||
FormGroup,
|
||||
InputGroup,
|
||||
@@ -14,14 +12,18 @@ import {
|
||||
Collapse,
|
||||
} from 'react-bootstrap';
|
||||
|
||||
import SouthPane from './SouthPane';
|
||||
import Timer from './Timer';
|
||||
import Button from '../../components/Button';
|
||||
|
||||
import SouthPane from './SouthPane';
|
||||
import Timer from '../../components/Timer';
|
||||
import SqlEditorLeftBar from './SqlEditorLeftBar';
|
||||
import AceEditorWrapper from './AceEditorWrapper';
|
||||
import { STATE_BSSTYLE_MAP } from '../constants.js';
|
||||
import RunQueryActionButton from './RunQueryActionButton';
|
||||
|
||||
const propTypes = {
|
||||
actions: React.PropTypes.object.isRequired,
|
||||
height: React.PropTypes.string.isRequired,
|
||||
database: React.PropTypes.object,
|
||||
latestQuery: React.PropTypes.object,
|
||||
networkOn: React.PropTypes.bool,
|
||||
@@ -73,7 +75,7 @@ class SqlEditor extends React.PureComponent {
|
||||
sqlEditorId: qe.id,
|
||||
tab: qe.title,
|
||||
schema: qe.schema,
|
||||
tempTableName: this.state.ctas,
|
||||
tempTableName: ctas ? this.state.ctas : '',
|
||||
runAsync,
|
||||
ctas,
|
||||
};
|
||||
@@ -101,62 +103,6 @@ class SqlEditor extends React.PureComponent {
|
||||
}
|
||||
|
||||
render() {
|
||||
let runButtons = [];
|
||||
let runText = 'Run Query';
|
||||
let btnStyle = 'primary';
|
||||
if (this.props.queryEditor.selectedText) {
|
||||
runText = 'Run Selection';
|
||||
btnStyle = 'warning';
|
||||
}
|
||||
if (this.props.database && this.props.database.allow_run_sync) {
|
||||
runButtons.push(
|
||||
<Button
|
||||
bsSize="small"
|
||||
bsStyle={btnStyle}
|
||||
style={{ width: '100px' }}
|
||||
onClick={this.runQuery.bind(this, false)}
|
||||
disabled={!(this.props.queryEditor.dbId)}
|
||||
key="run-btn"
|
||||
>
|
||||
<i className="fa fa-table" /> {runText}
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
if (this.props.database && this.props.database.allow_run_async) {
|
||||
runButtons.push(
|
||||
<Button
|
||||
bsSize="small"
|
||||
bsStyle={btnStyle}
|
||||
style={{ width: '100px' }}
|
||||
onClick={this.runQuery.bind(this, true)}
|
||||
disabled={!(this.props.queryEditor.dbId)}
|
||||
key="run-async-btn"
|
||||
>
|
||||
<i className="fa fa-table" /> Run Async
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
runButtons = (
|
||||
<ButtonGroup bsSize="small" className="inline m-r-5 pull-left">
|
||||
{runButtons}
|
||||
</ButtonGroup>
|
||||
);
|
||||
if (
|
||||
this.props.latestQuery &&
|
||||
['running', 'pending'].indexOf(this.props.latestQuery.state) > -1) {
|
||||
runButtons = (
|
||||
<ButtonGroup bsSize="small" className="inline m-r-5 pull-left">
|
||||
<Button
|
||||
bsStyle="primary"
|
||||
bsSize="small"
|
||||
style={{ width: '100px' }}
|
||||
onClick={this.stopQuery.bind(this)}
|
||||
>
|
||||
<a className="fa fa-stop" /> Stop
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
);
|
||||
}
|
||||
let limitWarning = null;
|
||||
if (this.props.latestQuery && this.props.latestQuery.limit_reached) {
|
||||
const tooltip = (
|
||||
@@ -174,6 +120,7 @@ class SqlEditor extends React.PureComponent {
|
||||
}
|
||||
let ctasControls;
|
||||
if (this.props.database && this.props.database.allow_ctas) {
|
||||
const ctasToolTip = 'Create table as with query results';
|
||||
ctasControls = (
|
||||
<FormGroup>
|
||||
<InputGroup>
|
||||
@@ -189,6 +136,7 @@ class SqlEditor extends React.PureComponent {
|
||||
bsSize="small"
|
||||
disabled={this.state.ctas.length === 0}
|
||||
onClick={this.createTableAs.bind(this)}
|
||||
tooltip={ctasToolTip}
|
||||
>
|
||||
<i className="fa fa-table" /> CTAS
|
||||
</Button>
|
||||
@@ -201,24 +149,45 @@ class SqlEditor extends React.PureComponent {
|
||||
<div className="sql-toolbar clearfix">
|
||||
<div className="pull-left">
|
||||
<Form inline>
|
||||
{runButtons}
|
||||
<RunQueryActionButton
|
||||
allowAsync={this.props.database && this.props.database.allow_run_async}
|
||||
dbId={this.props.queryEditor.dbId}
|
||||
queryState={this.props.latestQuery && this.props.latestQuery.state}
|
||||
runQuery={this.runQuery.bind(this)}
|
||||
selectedText={this.props.queryEditor.selectedText}
|
||||
stopQuery={this.stopQuery.bind(this)}
|
||||
/>
|
||||
{ctasControls}
|
||||
</Form>
|
||||
</div>
|
||||
<div className="pull-right">
|
||||
{limitWarning}
|
||||
<Timer query={this.props.latestQuery} />
|
||||
{this.props.latestQuery &&
|
||||
<Timer
|
||||
startTime={this.props.latestQuery.startDttm}
|
||||
endTime={this.props.latestQuery.endDttm}
|
||||
state={STATE_BSSTYLE_MAP[this.props.latestQuery.state]}
|
||||
isRunning={this.props.latestQuery.state === 'running'}
|
||||
/>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<div className="SqlEditor" style={{ minHeight: this.sqlEditorHeight() }}>
|
||||
<div
|
||||
className="SqlEditor"
|
||||
style={{
|
||||
minHeight: this.sqlEditorHeight(),
|
||||
height: this.props.height,
|
||||
}}
|
||||
>
|
||||
<Row>
|
||||
<Collapse
|
||||
in={!this.props.hideLeftBar}
|
||||
>
|
||||
<Col md={3}>
|
||||
<SqlEditorLeftBar
|
||||
style={{ height: this.props.height }}
|
||||
queryEditor={this.props.queryEditor}
|
||||
tables={this.props.tables}
|
||||
networkOn={this.props.networkOn}
|
||||
|
||||
@@ -3,7 +3,7 @@ import React from 'react';
|
||||
import Select from 'react-select';
|
||||
import { Label, Button } from 'react-bootstrap';
|
||||
import TableElement from './TableElement';
|
||||
import DatabaseSelect from './DatabaseSelect';
|
||||
import AsyncSelect from '../../components/AsyncSelect';
|
||||
|
||||
const propTypes = {
|
||||
queryEditor: React.PropTypes.object.isRequired,
|
||||
@@ -30,8 +30,8 @@ class SqlEditorLeftBar extends React.PureComponent {
|
||||
};
|
||||
}
|
||||
componentWillMount() {
|
||||
this.fetchSchemas();
|
||||
this.fetchTables();
|
||||
this.fetchSchemas(this.props.queryEditor.dbId);
|
||||
this.fetchTables(this.props.queryEditor.dbId, this.props.queryEditor.schema);
|
||||
}
|
||||
onChange(db) {
|
||||
const val = (db) ? db.value : null;
|
||||
@@ -44,25 +44,65 @@ class SqlEditorLeftBar extends React.PureComponent {
|
||||
this.fetchSchemas(val);
|
||||
}
|
||||
}
|
||||
dbMutator(data) {
|
||||
const options = data.result.map((db) => ({ value: db.id, label: db.database_name }));
|
||||
this.props.actions.setDatabases(data.result);
|
||||
if (data.result.length === 0) {
|
||||
this.props.actions.addAlert({
|
||||
bsStyle: 'danger',
|
||||
msg: "It seems you don't have access to any database",
|
||||
});
|
||||
}
|
||||
return options;
|
||||
}
|
||||
resetState() {
|
||||
this.props.actions.resetState();
|
||||
}
|
||||
fetchTables(dbId, schema) {
|
||||
const actualDbId = dbId || this.props.queryEditor.dbId;
|
||||
if (actualDbId) {
|
||||
const actualSchema = schema || this.props.queryEditor.schema;
|
||||
this.setState({ tableLoading: true });
|
||||
this.setState({ tableOptions: [] });
|
||||
const url = `/superset/tables/${actualDbId}/${actualSchema}`;
|
||||
getTableNamesBySubStr(input) {
|
||||
if (!this.props.queryEditor.dbId || !input) {
|
||||
return Promise.resolve({ options: [] });
|
||||
}
|
||||
const url = `/superset/tables/${this.props.queryEditor.dbId}/\
|
||||
${this.props.queryEditor.schema}/${input}`;
|
||||
return $.get(url).then((data) => ({ options: data.options }));
|
||||
}
|
||||
// TODO: move fetching methods to the actions.
|
||||
fetchTables(dbId, schema, substr) {
|
||||
if (dbId) {
|
||||
this.setState({ tableLoading: true, tableOptions: [] });
|
||||
const url = `/superset/tables/${dbId}/${schema}/${substr}/`;
|
||||
$.get(url, (data) => {
|
||||
let tableOptions = data.tables.map((s) => ({ value: s, label: s }));
|
||||
const views = data.views.map((s) => ({ value: s, label: '[view] ' + s }));
|
||||
tableOptions = [...tableOptions, ...views];
|
||||
this.setState({ tableOptions });
|
||||
this.setState({ tableLoading: false });
|
||||
this.setState({
|
||||
tableLoading: false,
|
||||
tableOptions: data.options,
|
||||
tableLength: data.tableLength,
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
changeTable(tableOpt) {
|
||||
if (!tableOpt) {
|
||||
this.setState({ tableName: '' });
|
||||
return;
|
||||
}
|
||||
const namePieces = tableOpt.value.split('.');
|
||||
let tableName = namePieces[0];
|
||||
let schemaName = this.props.queryEditor.schema;
|
||||
if (namePieces.length === 1) {
|
||||
this.setState({ tableName });
|
||||
} else {
|
||||
schemaName = namePieces[0];
|
||||
tableName = namePieces[1];
|
||||
this.setState({ tableName });
|
||||
this.props.actions.queryEditorSetSchema(this.props.queryEditor, schemaName);
|
||||
this.fetchTables(this.props.queryEditor.dbId, schemaName);
|
||||
}
|
||||
this.setState({ tableLoading: true });
|
||||
// TODO: handle setting the tableLoading state depending on success or
|
||||
// failure of the addTable async call in the action.
|
||||
this.props.actions.addTable(this.props.queryEditor, tableName, schemaName);
|
||||
this.setState({ tableLoading: false });
|
||||
}
|
||||
changeSchema(schemaOpt) {
|
||||
const schema = (schemaOpt) ? schemaOpt.value : null;
|
||||
this.props.actions.queryEditorSetSchema(this.props.queryEditor, schema);
|
||||
@@ -72,10 +112,9 @@ class SqlEditorLeftBar extends React.PureComponent {
|
||||
const actualDbId = dbId || this.props.queryEditor.dbId;
|
||||
if (actualDbId) {
|
||||
this.setState({ schemaLoading: true });
|
||||
const url = `/databasetablesasync/api/read?_flt_0_id=${actualDbId}`;
|
||||
const url = `/superset/schemas/${actualDbId}/`;
|
||||
$.get(url, (data) => {
|
||||
const schemas = data.result[0].all_schema_names;
|
||||
const schemaOptions = schemas.map((s) => ({ value: s, label: s }));
|
||||
const schemaOptions = data.schemas.map((s) => ({ value: s, label: s }));
|
||||
this.setState({ schemaOptions });
|
||||
this.setState({ schemaLoading: false });
|
||||
});
|
||||
@@ -84,14 +123,6 @@ class SqlEditorLeftBar extends React.PureComponent {
|
||||
closePopover(ref) {
|
||||
this.refs[ref].hide();
|
||||
}
|
||||
changeTable(tableOpt) {
|
||||
const tableName = tableOpt.value;
|
||||
const qe = this.props.queryEditor;
|
||||
|
||||
this.setState({ tableLoading: true });
|
||||
this.props.actions.addTable(qe, tableName);
|
||||
this.setState({ tableLoading: false });
|
||||
}
|
||||
render() {
|
||||
let networkAlert = null;
|
||||
if (!this.props.networkOn) {
|
||||
@@ -103,8 +134,10 @@ class SqlEditorLeftBar extends React.PureComponent {
|
||||
<div className="clearfix sql-toolbar scrollbar-content">
|
||||
{networkAlert}
|
||||
<div>
|
||||
<DatabaseSelect
|
||||
<AsyncSelect
|
||||
dataEndpoint="/databaseasync/api/read?_flt_0_expose_in_sqllab=1"
|
||||
onChange={this.onChange.bind(this)}
|
||||
value={this.props.queryEditor.dbId}
|
||||
databaseId={this.props.queryEditor.dbId}
|
||||
actions={this.props.actions}
|
||||
valueRenderer={(o) => (
|
||||
@@ -112,9 +145,9 @@ class SqlEditorLeftBar extends React.PureComponent {
|
||||
<span className="text-muted">Database:</span> {o.label}
|
||||
</div>
|
||||
)}
|
||||
mutator={this.dbMutator.bind(this)}
|
||||
placeholder="Select a database"
|
||||
/>
|
||||
</div>
|
||||
<div className="m-t-5">
|
||||
<Select
|
||||
name="select-schema"
|
||||
placeholder={`Select a schema (${this.state.schemaOptions.length})`}
|
||||
@@ -131,15 +164,29 @@ class SqlEditorLeftBar extends React.PureComponent {
|
||||
/>
|
||||
</div>
|
||||
<div className="m-t-5">
|
||||
<Select
|
||||
name="select-table"
|
||||
ref="selectTable"
|
||||
isLoading={this.state.tableLoading}
|
||||
placeholder={`Add a table (${this.state.tableOptions.length})`}
|
||||
autosize={false}
|
||||
onChange={this.changeTable.bind(this)}
|
||||
options={this.state.tableOptions}
|
||||
/>
|
||||
{this.props.queryEditor.schema &&
|
||||
<Select
|
||||
name="select-table"
|
||||
ref="selectTable"
|
||||
isLoading={this.state.tableLoading}
|
||||
value={this.state.tableName}
|
||||
placeholder={`Add a table (${this.state.tableOptions.length})`}
|
||||
autosize={false}
|
||||
onChange={this.changeTable.bind(this)}
|
||||
options={this.state.tableOptions}
|
||||
/>
|
||||
}
|
||||
{!this.props.queryEditor.schema &&
|
||||
<Select.Async
|
||||
name="async-select-table"
|
||||
ref="selectTable"
|
||||
value={this.state.tableName}
|
||||
placeholder={"Type to search ..."}
|
||||
autosize={false}
|
||||
onChange={this.changeTable.bind(this)}
|
||||
loadOptions={this.getTableNamesBySubStr.bind(this)}
|
||||
/>
|
||||
}
|
||||
</div>
|
||||
<hr />
|
||||
<div className="m-t-5">
|
||||
|
||||
@@ -4,9 +4,9 @@ import { connect } from 'react-redux';
|
||||
import { bindActionCreators } from 'redux';
|
||||
import * as Actions from '../actions';
|
||||
import SqlEditor from './SqlEditor';
|
||||
import { getParamFromQuery } from '../../../utils/common';
|
||||
import CopyQueryTabUrl from './CopyQueryTabUrl';
|
||||
import { areObjectsEqual } from '../../reduxUtils';
|
||||
import { areArraysShallowEqual } from '../../reduxUtils';
|
||||
import { getParamFromQuery } from '../../../utils/common';
|
||||
|
||||
const propTypes = {
|
||||
actions: React.PropTypes.object.isRequired,
|
||||
@@ -16,6 +16,7 @@ const propTypes = {
|
||||
tabHistory: React.PropTypes.array.isRequired,
|
||||
tables: React.PropTypes.array.isRequired,
|
||||
networkOn: React.PropTypes.bool,
|
||||
editorHeight: React.PropTypes.string.isRequired,
|
||||
};
|
||||
const defaultProps = {
|
||||
queryEditors: [],
|
||||
@@ -27,45 +28,75 @@ let queryCount = 1;
|
||||
class TabbedSqlEditors extends React.PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
const uri = window.location.toString();
|
||||
const search = window.location.search;
|
||||
const cleanUri = search ? uri.substring(0, uri.indexOf('?')) : uri;
|
||||
const query = search.substring(1);
|
||||
const sqlLabUrl = '/superset/sqllab';
|
||||
this.state = {
|
||||
uri,
|
||||
cleanUri,
|
||||
query,
|
||||
sqlLabUrl,
|
||||
queriesArray: [],
|
||||
dataPreviewQueries: [],
|
||||
hideLeftBar: false,
|
||||
};
|
||||
}
|
||||
componentWillMount() {
|
||||
if (this.state.query) {
|
||||
queryCount++;
|
||||
const queryEditorProps = {
|
||||
title: getParamFromQuery(this.state.query, 'title'),
|
||||
dbId: parseInt(getParamFromQuery(this.state.query, 'dbid'), 10),
|
||||
schema: getParamFromQuery(this.state.query, 'schema'),
|
||||
autorun: getParamFromQuery(this.state.query, 'autorun'),
|
||||
sql: getParamFromQuery(this.state.query, 'sql'),
|
||||
};
|
||||
this.props.actions.addQueryEditor(queryEditorProps);
|
||||
// Clean the url in browser history
|
||||
window.history.replaceState({}, document.title, this.state.cleanUri);
|
||||
componentDidMount() {
|
||||
const search = window.location.search;
|
||||
if (search) {
|
||||
const queryString = search.substring(1);
|
||||
const urlId = getParamFromQuery(queryString, 'id');
|
||||
if (urlId) {
|
||||
this.props.actions.popStoredQuery(urlId);
|
||||
} else {
|
||||
let dbId = getParamFromQuery(queryString, 'dbid');
|
||||
if (dbId) {
|
||||
dbId = parseInt(dbId, 10);
|
||||
} else {
|
||||
const databases = this.props.databases;
|
||||
const dbName = getParamFromQuery(queryString, 'dbname');
|
||||
if (dbName) {
|
||||
Object.keys(databases).forEach((db) => {
|
||||
if (databases[db].database_name === dbName) {
|
||||
dbId = databases[db].id;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
const newQueryEditor = {
|
||||
title: getParamFromQuery(queryString, 'title'),
|
||||
dbId,
|
||||
schema: getParamFromQuery(queryString, 'schema'),
|
||||
autorun: getParamFromQuery(queryString, 'autorun'),
|
||||
sql: getParamFromQuery(queryString, 'sql'),
|
||||
};
|
||||
this.props.actions.addQueryEditor(newQueryEditor);
|
||||
}
|
||||
this.popNewTab();
|
||||
}
|
||||
}
|
||||
popNewTab() {
|
||||
queryCount++;
|
||||
// Clean the url in browser history
|
||||
window.history.replaceState({}, document.title, this.state.sqlLabUrl);
|
||||
}
|
||||
componentWillReceiveProps(nextProps) {
|
||||
const activeQeId = this.props.tabHistory[this.props.tabHistory.length - 1];
|
||||
const newActiveQeId = nextProps.tabHistory[nextProps.tabHistory.length - 1];
|
||||
if (activeQeId !== newActiveQeId || !areObjectsEqual(this.props.queries, nextProps.queries)) {
|
||||
const queriesArray = [];
|
||||
for (const id in this.props.queries) {
|
||||
if (this.props.queries[id].sqlEditorId === newActiveQeId) {
|
||||
queriesArray.push(this.props.queries[id]);
|
||||
}
|
||||
const nextActiveQeId = nextProps.tabHistory[nextProps.tabHistory.length - 1];
|
||||
const queriesArray = [];
|
||||
for (const id in nextProps.queries) {
|
||||
if (nextProps.queries[id].sqlEditorId === nextActiveQeId) {
|
||||
queriesArray.push(nextProps.queries[id]);
|
||||
}
|
||||
}
|
||||
if (!areArraysShallowEqual(queriesArray, this.state.queriesArray)) {
|
||||
this.setState({ queriesArray });
|
||||
}
|
||||
|
||||
const dataPreviewQueries = [];
|
||||
nextProps.tables.forEach((table) => {
|
||||
const queryId = table.dataPreviewQueryId;
|
||||
if (queryId && nextProps.queries[queryId] && table.queryEditorId === nextActiveQeId) {
|
||||
dataPreviewQueries.push(nextProps.queries[queryId]);
|
||||
}
|
||||
});
|
||||
if (!areArraysShallowEqual(dataPreviewQueries, this.state.dataPreviewQueries)) {
|
||||
this.setState({ dataPreviewQueries });
|
||||
}
|
||||
}
|
||||
renameTab(qe) {
|
||||
/* eslint no-alert: 0 */
|
||||
@@ -89,7 +120,9 @@ class TabbedSqlEditors extends React.PureComponent {
|
||||
const activeQueryEditor = this.activeQueryEditor();
|
||||
const qe = {
|
||||
title: `Untitled Query ${queryCount}`,
|
||||
dbId: (activeQueryEditor) ? activeQueryEditor.dbId : null,
|
||||
dbId: (activeQueryEditor && activeQueryEditor.dbId) ?
|
||||
activeQueryEditor.dbId :
|
||||
this.props.defaultDbId,
|
||||
schema: (activeQueryEditor) ? activeQueryEditor.schema : null,
|
||||
autorun: false,
|
||||
sql: 'SELECT ...',
|
||||
@@ -123,14 +156,6 @@ class TabbedSqlEditors extends React.PureComponent {
|
||||
}
|
||||
const state = (latestQuery) ? latestQuery.state : '';
|
||||
|
||||
const dataPreviewQueries = [];
|
||||
this.props.tables.forEach((table) => {
|
||||
const queryId = table.dataPreviewQueryId;
|
||||
if (queryId && this.props.queries[queryId] && table.queryEditorId === qe.id) {
|
||||
dataPreviewQueries.push(this.props.queries[queryId]);
|
||||
}
|
||||
});
|
||||
|
||||
const tabTitle = (
|
||||
<div>
|
||||
<div className={'circle ' + state} /> {qe.title} {' '}
|
||||
@@ -146,9 +171,7 @@ class TabbedSqlEditors extends React.PureComponent {
|
||||
<i className="fa fa-i-cursor" /> rename tab
|
||||
</MenuItem>
|
||||
{qe &&
|
||||
<MenuItem eventKey="3">
|
||||
<i className="fa fa-clipboard" /> <CopyQueryTabUrl queryEditor={qe} />
|
||||
</MenuItem>
|
||||
<CopyQueryTabUrl queryEditor={qe} />
|
||||
}
|
||||
<MenuItem eventKey="4" onClick={this.toggleLeftBar.bind(this)}>
|
||||
<i className="fa fa-cogs" />
|
||||
@@ -168,10 +191,11 @@ class TabbedSqlEditors extends React.PureComponent {
|
||||
<div className="panel-body">
|
||||
{isSelected &&
|
||||
<SqlEditor
|
||||
height={this.props.editorHeight}
|
||||
tables={this.props.tables.filter((t) => (t.queryEditorId === qe.id))}
|
||||
queryEditor={qe}
|
||||
editorQueries={this.state.queriesArray}
|
||||
dataPreviewQueries={dataPreviewQueries}
|
||||
dataPreviewQueries={this.state.dataPreviewQueries}
|
||||
latestQuery={latestQuery}
|
||||
database={database}
|
||||
actions={this.props.actions}
|
||||
@@ -213,6 +237,7 @@ function mapStateToProps(state) {
|
||||
tabHistory: state.tabHistory,
|
||||
networkOn: state.networkOn,
|
||||
tables: state.tables,
|
||||
defaultDbId: state.defaultDbId,
|
||||
};
|
||||
}
|
||||
function mapDispatchToProps(dispatch) {
|
||||
|
||||
@@ -1,61 +0,0 @@
|
||||
import React from 'react';
|
||||
import { now, fDuration } from '../../modules/dates';
|
||||
|
||||
import { STATE_BSSTYLE_MAP } from '../constants.js';
|
||||
|
||||
class Timer extends React.PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
clockStr: '',
|
||||
};
|
||||
}
|
||||
componentWillMount() {
|
||||
this.startTimer();
|
||||
}
|
||||
componentWillUnmount() {
|
||||
this.stopTimer();
|
||||
}
|
||||
startTimer() {
|
||||
if (!(this.timer)) {
|
||||
this.timer = setInterval(this.stopwatch.bind(this), 30);
|
||||
}
|
||||
}
|
||||
stopTimer() {
|
||||
clearInterval(this.timer);
|
||||
this.timer = null;
|
||||
}
|
||||
stopwatch() {
|
||||
if (this.props && this.props.query) {
|
||||
const endDttm = this.props.query.endDttm || now();
|
||||
const clockStr = fDuration(this.props.query.startDttm, endDttm);
|
||||
this.setState({ clockStr });
|
||||
if (this.props.query.state !== 'running') {
|
||||
this.stopTimer();
|
||||
}
|
||||
}
|
||||
}
|
||||
render() {
|
||||
if (this.props.query && this.props.query.state === 'running') {
|
||||
this.startTimer();
|
||||
}
|
||||
let timerSpan = null;
|
||||
if (this.props && this.props.query) {
|
||||
const bsStyle = STATE_BSSTYLE_MAP[this.props.query.state];
|
||||
timerSpan = (
|
||||
<span className={'inlineBlock m-r-5 label label-' + bsStyle}>
|
||||
{this.state.clockStr}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
return timerSpan;
|
||||
}
|
||||
}
|
||||
Timer.propTypes = {
|
||||
query: React.PropTypes.object,
|
||||
};
|
||||
Timer.defaultProps = {
|
||||
query: null,
|
||||
};
|
||||
|
||||
export default Timer;
|
||||
@@ -4,6 +4,7 @@ import { Alert, Button, Col, Modal } from 'react-bootstrap';
|
||||
import Select from 'react-select';
|
||||
import { Table } from 'reactable';
|
||||
import shortid from 'shortid';
|
||||
import $ from 'jquery';
|
||||
|
||||
const CHART_TYPES = [
|
||||
{ value: 'dist_bar', label: 'Distribution - Bar Chart', requiresTime: false },
|
||||
@@ -26,33 +27,43 @@ const defaultProps = {
|
||||
class VisualizeModal extends React.PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
const uniqueId = shortid.generate();
|
||||
this.state = {
|
||||
chartType: CHART_TYPES[0],
|
||||
datasourceName: uniqueId,
|
||||
datasourceName: this.datasourceName(),
|
||||
columns: {},
|
||||
hints: [],
|
||||
};
|
||||
}
|
||||
componentWillMount() {
|
||||
this.setStateFromProps();
|
||||
}
|
||||
componentDidMount() {
|
||||
this.validate();
|
||||
}
|
||||
setStateFromProps() {
|
||||
componentWillReceiveProps(nextProps) {
|
||||
this.setStateFromProps(nextProps);
|
||||
}
|
||||
setStateFromProps(props) {
|
||||
if (
|
||||
!this.props.query ||
|
||||
!this.props.query.results ||
|
||||
!this.props.query.results.columns) {
|
||||
!props.query ||
|
||||
!props.query.results ||
|
||||
!props.query.results.columns) {
|
||||
return;
|
||||
}
|
||||
const columns = {};
|
||||
this.props.query.results.columns.forEach((col) => {
|
||||
props.query.results.columns.forEach((col) => {
|
||||
columns[col.name] = col;
|
||||
});
|
||||
this.setState({ columns });
|
||||
}
|
||||
datasourceName() {
|
||||
const { query } = this.props;
|
||||
const uniqueId = shortid.generate();
|
||||
let datasourceName = uniqueId;
|
||||
if (query) {
|
||||
datasourceName = query.user ? `${query.user}-` : '';
|
||||
datasourceName += query.db ? `${query.db}-` : '';
|
||||
datasourceName += `${query.tab}-${uniqueId}`;
|
||||
}
|
||||
return datasourceName;
|
||||
}
|
||||
validate() {
|
||||
const hints = [];
|
||||
const cols = this.mergedColumns();
|
||||
@@ -102,10 +113,20 @@ class VisualizeModal extends React.PureComponent {
|
||||
chartType: this.state.chartType.value,
|
||||
datasourceName: this.state.datasourceName,
|
||||
columns: this.state.columns,
|
||||
sql: this.props.query.sql,
|
||||
sql: this.props.query.executedSql,
|
||||
dbId: this.props.query.dbId,
|
||||
};
|
||||
window.open('/superset/sqllab_viz/?data=' + JSON.stringify(vizOptions));
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
url: '/superset/sqllab_viz/',
|
||||
async: false,
|
||||
data: {
|
||||
data: JSON.stringify(vizOptions),
|
||||
},
|
||||
success: (url) => {
|
||||
window.open(url);
|
||||
},
|
||||
});
|
||||
}
|
||||
changeDatasourceName(event) {
|
||||
this.setState({ datasourceName: event.target.value });
|
||||
@@ -125,8 +146,16 @@ class VisualizeModal extends React.PureComponent {
|
||||
this.setState({ columns }, this.validate);
|
||||
}
|
||||
render() {
|
||||
if (!(this.props.query)) {
|
||||
return <div />;
|
||||
if (!(this.props.query) || !(this.props.query.results) || !(this.props.query.results.columns)) {
|
||||
return (
|
||||
<div className="VisualizeModal">
|
||||
<Modal show={this.props.show} onHide={this.props.onHide}>
|
||||
<Modal.Body>
|
||||
No results available for this query
|
||||
</Modal.Body>
|
||||
</Modal>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
const tableData = this.props.query.results.columns.map((col) => ({
|
||||
column: col.name,
|
||||
|
||||
@@ -4,8 +4,8 @@ require('bootstrap');
|
||||
|
||||
import React from 'react';
|
||||
import { render } from 'react-dom';
|
||||
import { initialState, sqlLabReducer } from './reducers';
|
||||
import { enhancer } from '../reduxUtils';
|
||||
import { getInitialState, sqlLabReducer } from './reducers';
|
||||
import { initEnhancer } from '../reduxUtils';
|
||||
import { createStore, compose, applyMiddleware } from 'redux';
|
||||
import { Provider } from 'react-redux';
|
||||
import thunkMiddleware from 'redux-thunk';
|
||||
@@ -15,8 +15,12 @@ import App from './components/App';
|
||||
|
||||
require('./main.css');
|
||||
|
||||
const appContainer = document.getElementById('app');
|
||||
const bootstrapData = JSON.parse(appContainer.getAttribute('data-bootstrap'));
|
||||
const state = Object.assign({}, getInitialState(bootstrapData.defaultDbId), bootstrapData);
|
||||
|
||||
let store = createStore(
|
||||
sqlLabReducer, initialState, compose(applyMiddleware(thunkMiddleware), enhancer()));
|
||||
sqlLabReducer, state, compose(applyMiddleware(thunkMiddleware), initEnhancer()));
|
||||
|
||||
// jquery hack to highlight the navbar menu
|
||||
$('a:contains("SQL Lab")').parent().addClass('active');
|
||||
@@ -25,5 +29,5 @@ render(
|
||||
<Provider store={store}>
|
||||
<App />
|
||||
</Provider>,
|
||||
document.getElementById('app')
|
||||
appContainer
|
||||
);
|
||||
|
||||
@@ -36,7 +36,7 @@ body {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
height: 95%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.scrollbar-content {
|
||||
@@ -47,7 +47,7 @@ body {
|
||||
bottom: 0px;
|
||||
overflow: scroll;
|
||||
margin-right: 0px;
|
||||
margin-bottom: 100px;
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
|
||||
.Workspace .btn-sm {
|
||||
|
||||