Compare commits

..

340 Commits

Author SHA1 Message Date
Maxime Beauchemin
bfc6b1cca9 0.19.0 2017-08-01 22:16:23 -07:00
Maxime Beauchemin
62fcdf2a92 [explore] DatasourceControl to pick datasource in modal (#3210)
* [explore] DatasourceControl to pick datasource in modal

Makes it easier to change datasource, also makes it such that the list
of all datasources doesn't need to be loaded upfront.

* Adding more metadata
2017-08-01 12:08:00 -07:00
Maxime Beauchemin
48821b5101 [sqllab] fix UI shows 'The query returned no results' momentarily (#3214)
this is visible when running async queries between the fetching and
success state as the rows are getting cached in the component
2017-08-01 10:26:13 -07:00
Maxime Beauchemin
3b129253a3 [explore] nvd3 sort values in rich tooltip (#3197) 2017-08-01 10:25:52 -07:00
Maxime Beauchemin
48760849ec [sqllab/cosmetics] add margin-top for labels in query history (#3222) 2017-08-01 10:25:13 -07:00
Xingze Zhang
9c1ca07c40 [docs] update url in CONTRIBUTING.md (#3212) 2017-08-01 08:54:35 -07:00
Maxime Beauchemin
774ad45efb [bugfix] capture Hive job_id pre-url transformation (#3213) 2017-07-31 22:22:08 -07:00
Xingze Zhang
299e9ce6b8 fix issue 3204 (#3205) 2017-07-31 12:24:19 -07:00
Maxime Beauchemin
219f33f0d1 Speed up JS build time (#3203)
Also bumping a few related libs
2017-07-28 17:34:09 -07:00
Andrew Pariser
58a704b84c Autofocus search input in VizTypeControl modal onEnter (#2929) 2017-07-28 14:57:24 -07:00
Rogan
b58cfbcb91 add combine config for metrics in pivot table (#3086)
* add combine config for metrics in pivot table

* change method to stack/unstack

* update backendSync
2017-07-28 14:16:38 -07:00
Brian Wolfe
1e325d9645 [druid] Allow custom druid postaggregators (#3146)
* [druid] Allow custom druid postaggregators

Also, fix the postaggregation for approxHistogram quantiles so it adds
the dependent field and that can show up in the graphs/tables.

In general, postAggregators add significant power, we should probably
support including custom postAggregators. Plywood has standard
postAggregators here, and a customAggregator escape hatch that allows
you to define custom postAggregators.

This commit adds a similar capability for Superset and a additional
field/fields/fieldName breakdown of the typical naming for dependent
aggregations, which should make it significantly easier to develop
approxHistogram and custom postAggregation-required dashboards.

* [druid] Minor style cleanup in tests file.

* [druid] Apply code review suggestions

* break out CustomPostAggregator into separate class. This just cleans
  up the creation of the postaggregator a little bit.
* minor style issues.
* move the function around so the git diff is more readable
2017-07-28 11:45:59 -07:00
Maxime Beauchemin
ad5a4389a2 Adding 'apache' to docs (#3194) 2017-07-28 11:42:01 -07:00
Maxime Beauchemin
e4fba0ffb7 [bugfix] fix merge conflict that broke Hive support (#3196) 2017-07-27 21:34:15 -07:00
Maxime Beauchemin
e584a9673f Add BigQuery engine specifications (#3193)
As contributed by @mxmzdlv on issue #945
2017-07-27 14:01:13 -07:00
Maxime Beauchemin
b888802e05 [sqllab] improve Hive support (#3187)
* [sqllab] improve Hive support

* Fix "Transport not open" bug
* Getting progress bar to show
* Bump pyhive to 0.4.0
* Getting [Track Job] button to show

* Fix testzz
2017-07-27 14:00:19 -07:00
Maxime Beauchemin
25c599d040 Escaping the user's SQL in the explore view (#3186)
* Escaping the user's SQL in the explore view

When executing SQL from SQL Lab, we use a lower level API to the
database which doesn't require escaping the SQL. When going through
the explore view, the stack chain leading to the same method may need
escaping depending on how the DBAPI driver is written, and that is the
case for Presto (and perhaps other drivers).

* Using regex to avoid doubling doubles
2017-07-27 09:47:31 -07:00
Riccardo Magliocchetti
fb866a937b Bump cryptography to 1.9 (#3065)
As 1.7.2 doesn't compile here with openssl 1.1.0f
2017-07-26 14:11:11 -07:00
Fokko Driesprong
aa95e03eb9 Fix the segment interval for pulling metadata (#3174)
The end of the interval would be on the truncated today date, which
means that you will exclude today. If your realtime ingestion job
runs shorter than a day, the metadata cannot be pulled from the
druid cluster.
2017-07-26 09:28:08 -07:00
Maxime Beauchemin
cf1d0f38ad [bugfix] visualize flow error: 'Metric x is not valid' (#3181)
The metric name in the frontend doesn't match the one generated on the
backend. It turns out the explore view will default to the first
metric so specifying one isn't needed.
2017-07-26 09:22:25 -07:00
Maxime Beauchemin
fca982c609 [bugfix] fix bar order (#3180) 2017-07-26 09:22:03 -07:00
Riccardo Magliocchetti
747bf80474 docs: fixup installation examples code indentation (#3169) 2017-07-26 09:20:06 -07:00
Dmitry Goryunov
4f7fd65c8b add Zalando to the list of organizations (#3171) 2017-07-26 08:32:06 -07:00
timfeirg
6045063e78 fix hive.fetch_logs (#2968) 2017-07-25 21:43:19 -07:00
Maxime Beauchemin
0ec9cd4ad2 [bugfix] numeric value for date fields in table viz (#3036)
Bug was present only when using the NOT GROUPED BY option

fixes https://github.com/ApacheInfra/superset/issues/3027
2017-07-25 21:30:40 -07:00
Rich @ RadICS
4268513653 Add 'show/hide totals' option to pivot table vis (#3101) 2017-07-25 21:11:38 -07:00
Rogan
7654eef110 add title description to model view (#3045)
* add title description to model view

* add missing import
2017-07-25 21:06:34 -07:00
Rogan
b301ba1f57 Datasource cannot be empty (#3035) 2017-07-25 21:05:31 -07:00
Maxime Beauchemin
95509f2000 [bugfix] only filterable columns should show up in FilterBox list (#3105)
* [bugfix] only filterable columns should show up in FilterBox list

* Touchups
2017-07-25 20:50:41 -07:00
Maxime Beauchemin
49ab09101b Fixing the damn build (#3179)
* Fixing the build

* Going deeper
2017-07-25 13:57:29 -07:00
Chris Williams
40d9e15126 Add event-flow visualization (#3102)
* [event-flow] add event flow visualizaton type from @data-ui/event-flow.

* [event-flow] update vis thumbnail

* [event-flow] update row limit label, remove duplicate chart controls

* [dependencies] add @data-ui/event-flow 0.0.2

* [linting] fix multiple imports

* [deps] bump mapbox-gl and react-map-gl to fix build

* [event-flow] bump to 0.0.3 for es2015 + stage-0 babel presets

* [deps] revert mapbox version bumps

* [event-flow] update png, bump to newest version, address reviewer comments, add min event count form.

* [event-flow] pin version

* [event-flow][spec] add test for coveralls

* [event-flow] revert spec
2017-07-21 16:29:25 -07:00
Maxime Beauchemin
a141695b2b Prevent people from deleting datasources that have associate slices (#3163) 2017-07-19 16:24:20 -07:00
Maxime Beauchemin
d01e67a159 More logging to csv endpoint (#3164) 2017-07-19 15:59:30 -07:00
Maxime Beauchemin
c34df3eea4 [bugfix] SQLA instance has been deleted (#3159)
Related Msg:
sqlalchemy.orm.exc.ObjectDeletedError: Instance '<PermissionView at
0x7f10306b0e90>' has been deleted, or its row is otherwise not present.
2017-07-19 01:35:00 -07:00
luc
56bcbb0f8e add douban to the orgs. (#3157) 2017-07-19 01:18:34 -07:00
Maxime Beauchemin
51f1aa3106 [docs] use yarn in CONTRIBUTING.md (#3150)
* [docs] use yarn in CONTRIBUTING.md

* Updating badges
2017-07-19 01:17:56 -07:00
Maxime Beauchemin
d7e419127c [bugfix] fails on None view_menu (#3155)
* [bugfix]  fails on None view_menu

* Update coveralls token
2017-07-18 19:42:20 -07:00
Grace Guo
27fab0d54f allow user press Enter key to end editing title (#3112) 2017-07-17 08:58:10 -07:00
Maxime Beauchemin
091e93c831 [minor] change tooltip on 'Edit slice properties' (#3116)
* [minor] change tooltip on 'Edit slice properties'

* Upgrading npm version for travis

* Bumping node version to most recent
2017-07-16 22:29:30 -07:00
Maxime Beauchemin
bb6b2da267 Prevent SQLA warning related to SQLALCHEMY_TRACK_MODIFICATION (#3133) 2017-07-16 22:02:22 -07:00
Shao-Yen "Fred" Cheng
7abe2d5eee [#3137] Use state.datasource.type instead of state.datasource_type when rendering ControlPanelsContainer (#3138) 2017-07-16 21:04:34 -07:00
Maxime Beauchemin
7b015faae9 [heatmap] basic non empty validation (#3119) 2017-07-15 20:07:57 -07:00
Ke Zhu
e834154030 Fixes #3134 by correct response content-type of /testconn (#3135) 2017-07-15 09:52:37 -07:00
丁桂涛
256a521bf1 [Celery] fix the celery worker concurrency settings (#3126) 2017-07-14 09:44:41 -07:00
Maxime Beauchemin
a626f994bf [CLI] Improve the missing perm creation logic (#3118)
I don't think this worked as intended
2017-07-13 15:53:20 -07:00
Maxime Beauchemin
76dda688b1 Using yarn instead of npm install (#3120) 2017-07-13 15:16:39 -07:00
Grace Guo
163a6a20e5 clarify Superset different timeout config (#3044)
* clarify different timeout configuration for superset
2017-06-30 15:47:37 -07:00
Maxime Beauchemin
3d136aa0a4 Update docs links on README 2017-06-28 06:27:51 -07:00
Grace Guo
ff3057de5d fix default checkbox alignment (#3042) 2017-06-26 18:06:12 -07:00
Maxime Beauchemin
7045018d86 New chart type : Chord Diagrams (#3013) 2017-06-26 16:44:47 -07:00
Maxime Beauchemin
a55f963e52 [bugfix] issue creating separators & markup slices (#3041) 2017-06-26 11:00:21 -07:00
fabianmenges
8dfe2b70b2 Bug fix: Datasource -> Slice relationship (#3011) 2017-06-22 17:30:09 -07:00
Maxime Beauchemin
fdbb569c3e 0.18.5 2017-06-22 15:46:09 -07:00
Maxime Beauchemin
cc3e63f1de [docs] adding info on how to create new viz 2017-06-22 12:37:42 -07:00
Grace Guo
a48e246aa0 add test coverage for sql lab components (#3022) 2017-06-22 10:31:13 -07:00
Maxime Beauchemin
6eba6cac0b [hotfix] xAxis broken on time series viz 2017-06-21 16:35:50 -07:00
Maxime Beauchemin
bd706ebbd1 Fixing image sizes for horizon and country_map 2017-06-21 14:34:56 -07:00
Maxime Beauchemin
abbf138cfb Bumping SQLLAB_ASYNC_TIME_LIMIT_SEC default config to 6 hours 2017-06-21 14:16:53 -07:00
Grace Guo
de346a3eba CONTRIBUTING: add python unittes setup (#3021) 2017-06-21 12:52:04 -07:00
Maxime Beauchemin
99e1de58bc A set of minor fixes (#3014) 2017-06-21 12:09:49 -07:00
Maxime Beauchemin
5344a80535 Bumping flask-appbuilder to 1.9.1 2017-06-21 11:32:41 -07:00
Maxime Beauchemin
0b09a74d37 Downgrading celery to 3.1.25
https://github.com/celery/celery/issues/3932 is a blocker for Airbnb
2017-06-21 11:29:22 -07:00
Riccardo Magliocchetti
e21745ac9d CONTRIBUTING: ask to quote python stacktraces (#3017) 2017-06-21 10:12:34 -07:00
Maxime Beauchemin
593861eac6 [bugfix] fix metrics/metric control default (#3009)
We should pick an arbitrary metric by default. This broke when
improving the metrics selector recently.
2017-06-21 09:31:29 -07:00
Maxime Beauchemin
53dead9c29 Revert "Bumping FAB to 1.9.1"
This reverts commit f64e2ba7d5.
2017-06-20 22:25:58 -07:00
Maxime Beauchemin
f64e2ba7d5 Bumping FAB to 1.9.1 2017-06-20 21:52:16 -07:00
Benjamin Yolken
80515d2a92 Revert change in npm run build command (#3012) 2017-06-20 21:34:17 -07:00
Maxime Beauchemin
4d72afb54b Adding missing dependency and linting webpack.js (#3008) 2017-06-20 14:04:36 -07:00
Maxime Beauchemin
06fcaa3095 Stabilizing Celery / SQL Lab (#2981)
* upgrade celery to 4.0.2
* using Redis for unit tests (sqla broker not supported in Celery 4)
* Setting Celery's soft_time_limit based on `SQLLAB_ASYNC_TIME_LIMIT_SEC` config
* Better error handling in async tasks
* Better statsd logging in async tasks
* show [pending/running] query status in Results tab
* systematically using sqla NullPool on worker (async) to limit number
  of database connections
2017-06-20 13:55:03 -07:00
Benjamin Yolken
de88764e93 Misc. tweaks to improve webpack performance (#2994)
* Misc. tweaks to improve webpack performance

* Fix code climate style issues
2017-06-20 08:40:33 -07:00
Rogan
9051e1f3e2 translations extracted by pybabel (#2990) 2017-06-19 23:58:39 -07:00
Andrew Chen
ba93e6a2d1 Fix importing config from env var (#2983) 2017-06-19 23:51:34 -07:00
Maxime Beauchemin
7e5e229f48 Improving TextAreaControl to support code and modal (#2988) 2017-06-19 22:27:16 -07:00
Maxime Beauchemin
b9915e7ecf [pivot viz] fix formatting and verbose names (#2957)
* [pivot viz] fix formatting and verbose names

* Fixing tests
2017-06-19 22:10:54 -07:00
Maxime Beauchemin
3e51c61dbf [explore] improve bubble viz (#2927)
* [explore] improve bubble viz

* allow for custom axis formatters
* reorg the control panels

* Addressing comments
2017-06-19 22:09:09 -07:00
Grace Guo
591e512327 add test coverage for SqlEditorLeftBar (#2984) 2017-06-19 11:39:32 -07:00
Grace Guo
fc5db474b7 fix is_owner check (#2985) 2017-06-19 10:57:30 -07:00
Maxime Beauchemin
c083aec8e8 [FilterBox] use column verbose names (#2970) 2017-06-17 09:46:27 -07:00
Maxime Beauchemin
1ce3b81d01 Imporving the README 2017-06-16 23:35:46 -07:00
Maxime Beauchemin
89cd10b3ce [bufix] superset views not getting from_dttm-to_dttm in template context (#2978) 2017-06-15 22:49:55 -07:00
Maxime Beauchemin
712297480c [CLI] adding 'superset flower' command (flower is a UI for Celery) (#2963)
* [CLI] adding 'superset flower' command (flower is a UI for Celery)

* Addressing comments
2017-06-15 17:02:17 -07:00
Alanna Scott
6ddccaaa9b tests for saveQuery, fetchQueryResults, runQuery, postStopQuery (#2979) 2017-06-15 15:35:31 -07:00
Grace Guo
1dcf2c4326 fix local state 'columns' (#2896)
* fix local state 'columns'
* fix method name per code review
2017-06-15 15:30:13 -07:00
Alanna Scott
f0a8ea644b test props on alert wrapper (#2977) 2017-06-15 14:18:34 -07:00
Grace Guo
fb6ef26a24 add test coverage for ResultSet component (#2972)
* add test coverage for ResultSet component
2017-06-15 13:51:09 -07:00
Alanna Scott
00b34d7fbd [js-testing] add tests for explore actions (#2976)
* move to explore folder and delete explorev2 folder

* add tests for fetchDatasourceMetadata

* tests for fetchDatasources, fetchDatasourceMetadata

* use $.ajax for fetch dashboards and write test
2017-06-15 12:26:22 -07:00
Grace Guo
8329ea2b9b Edit Dashboard title and Slice title in place (#2940)
* Edit Dashboard title and Slice title in place

Add EditableTitle component into Dashboard and Explore view to support edit title inline.
2017-06-14 12:52:12 -07:00
Alanna Scott
da0a87a735 add test for ControlPanelSection (#2961) 2017-06-14 10:12:29 -07:00
Maxime Beauchemin
7aeca39c46 [js] remove unused npm dependencies (#2952) 2017-06-13 22:08:13 -07:00
Alanna Scott
e1751c065c add new slice test (#2939)
* sort explicitly on label

* add simple test for /slicemodelview/add endpoint

* make comments and method names more clear

* fix test name

* be more explicit, test status_code
2017-06-13 09:44:26 -07:00
Alanna Scott
e5151cb915 [js] version js file names using webpack chunkhash (#2951)
* get compiled js file names

* make manifest available as template var

* use script src directly to avoid flash of unstyled content in the case of csstheme.js

* linting

* attempt to fix tests

* exception

* print the path when no manifest file found

* handle case when manifest.json is not present for some reason, or in the case of tests
2017-06-13 09:44:00 -07:00
Maxime Beauchemin
24e3c7f89a [dashboard] notify instead of modal onSave (#2941)
* [dashboard] notify instead of modal onSave

* Addressing comments
2017-06-12 21:04:46 -07:00
Maxime Beauchemin
987cb9978d [hotfix] bumping pandas version to 0.20.2 2017-06-13 00:03:33 +00:00
Nishant Bangarwa
274d21795f Fix handling of Chunked requests (#1742)
* Fix handling of Chunked requests

Add fix for handling chunk encoding requests.
If ENABLE_CHUNK_ENCODING is set to true, for requests with transfer
encoding set to true. It will set wsgi.input_terminated to true which
tells werkzeug to ignore content-length and read the stream till the
end.

 break comment in multiple lines

* remove debug print logging
2017-06-12 13:46:14 -07:00
Maxime Beauchemin
0d2c2b0681 [table viz] get metrics to right-align (#2943)
Moved the histogram to be rooted on the right side as well

fixes https://github.com/airbnb/superset/issues/2933
2017-06-12 13:35:00 -07:00
Maxime Beauchemin
1eff48facb Bumping some dependencies (#2945)
Most notably Flask AppBuilder to 1.9.0
2017-06-12 13:21:14 -07:00
timfeirg
17bd7512ff remove reference for CSRF_ENABLED, and use WTF_CSRF_ENABLED instead (#2946) 2017-06-12 13:17:59 -07:00
Maxime Beauchemin
16141ecb94 [explore] improve metric(s) and groupby(s) controls (#2921)
* [explore] improve metric(s) and groupby(s) controls

- surface verbose_name, description & expression in controls
- [table viz] surface verbose name in table header

* Fixing tests

* Addressing comments

* Fixing tests (once more)
2017-06-09 11:29:55 -07:00
Alanna Scott
34f381bc25 v 0.18.5-alpha.3 2017-06-08 11:21:07 -07:00
Alanna Scott
24292dbc11 add new slice form improvements (#2928)
* dont set first datasource as default

* not used

* add a disabled class if datasource is not selected

* sort datasources alphabettically

* make btn disabled is no datasource is selected

* fix linting
2017-06-08 10:49:02 -07:00
Maxime Beauchemin
24a2f5b8f0 [big number] various improvements (#2912)
* [big number] various improvements

* dynamic number of X axis ticks based on width to prevent label overlap
* corrected overflow on the x axis
* improved tooltips (precise arrow and visible data point circle on hover)

* Fixing tooltips in heatmap viz
2017-06-08 08:50:30 -07:00
Maxime Beauchemin
0e6f754af9 Fix #2814 - json serialization edge case (#2924) 2017-06-08 08:49:01 -07:00
Alanna Scott
fb85f008fa v 0.18.5-alpha.2 2017-06-07 23:12:16 -07:00
Maxime Beauchemin
85fb4a933d [hotfix] only apply SQLAlchemy limit where needed
Observed issue in dev environment where SQL queries were emitted with
`LIMIT 0`. This fix addresses it.
2017-06-07 22:56:56 -07:00
Alanna Scott
d3ace6d63f [js-testing] type checking for dates.js (#2893)
* tests for dates.js

* linting

* test fduration output

* ignore warnings in code climate

* use eslint-2 otherwise defaults to eslint-3

* test eslint 1

* remove channel rule

* disable checks

* checks for eslint engine
2017-06-07 22:27:21 -07:00
Alanna Scott
205eed8350 add more tests for Timer (#2889) 2017-06-07 22:12:07 -07:00
Alanna Scott
5bf40e2256 [slices] add simple new slice form (#2800)
* initial structure for add new slice page

* simplify add slice form

* add a test

* fix long line

* use underscore for template name

* fix controls path

* fix vis types select
2017-06-07 22:08:59 -07:00
Denny Biasiolli
7c28e4eace updating react-alert dependency to v2.0.1 (#2923) 2017-06-07 12:07:15 -07:00
Xiao Hanyu
a882f7a55f Ignore pyenv files in .gitignore. (#2906)
Some python developers use Python version manager to create and switch Python
enviroments, pyenv (http://github.com/yyuu/pyenv) is one of the version managers.

When you use `pyenv local x.x.x`, `pyenv` will put a `.python-version` file in
project directory, which, in general, should not be commited to git.
2017-06-06 23:44:57 -07:00
Maxime Beauchemin
9aba77db74 0.18.5-alpha.1 2017-06-06 12:22:27 -07:00
Grace Guo
c9e5fbb09b [dashboard]Add timeout message on Dashboard view (#2910)
Same as explore view, if query takes > 45 seconds not returned, we will show Query timeout warning message. Otherwise user will see 504 Gateway timeout error.
2017-06-06 12:05:05 -07:00
Maxime Beauchemin
65f25a1e5a [explore] fix IN filter on numeric field (#2908) 2017-06-05 17:41:08 -07:00
Josh Walters
e0dd5d9d1d Removed __time column from druid metadata refresh, added long and double schema support (#2911)
* Removed __time column from load, added long/double schema support

* Format fix
2017-06-05 17:40:47 -07:00
Grace Guo
737db25b6e fix visualize modal checkbox alignment (#2897)
in Visualize Modal, table headers are left-aligned. So columns checkboxes should also be left-aligned.
2017-06-05 10:38:42 -07:00
Maurizio Napolitano
bea702269c update for the italian translation (#2902)
* italian translation

* update for the italian translation
2017-06-04 18:03:34 -07:00
Maxime Beauchemin
d78da8a9bf [docs faq] how do I add new columns to an existing table (#2888) 2017-06-04 18:02:10 -07:00
Riccardo Magliocchetti
ed8153ff8b update contributing a bit re contributing code (#2903)
* CONTRIBUTING: document starter_task label

* CONTRIBUTING: PR should work travis python versions

* CONTRIBUTING: move PR guidelines at the top

* CONTRIBUTING: no need to open a new pr after review
2017-06-04 18:00:33 -07:00
Riccardo Magliocchetti
f7ce100180 travis: no need to install python3.5 (#2904)
As we are testing on 3.4 and 2.7.
2017-06-04 17:56:43 -07:00
Maxime Beauchemin
72291d65c9 0.18.4 2017-06-04 17:54:31 -07:00
Grace Guo
0d3f57a78d fix a linting error (#2895) 2017-06-02 15:52:39 -07:00
Alanna Scott
04fb0e2c2d fix js error on explore view (#2894)
* fix js error on explore view

* bump version
2017-06-02 14:36:51 -07:00
Maxime Beauchemin
155fec0a6b Updating CHANGELOG.md to 0.18.3 2017-06-02 13:06:21 -07:00
Maxime Beauchemin
d90044cd52 0.18.3 2017-06-02 08:27:56 -07:00
fabianmenges
62bd4eb211 Converting filter argument to number if column is number (#2891) 2017-06-02 08:13:46 -07:00
Alanna Scott
677c427b16 delete DataPreviewModal, it doesn't get used anywhere (#2882) 2017-06-01 14:30:15 -07:00
justinr1234
52b0716571 [bugfix] allow database macro to work when CSRF is diabled (#2884) 2017-06-01 09:41:55 -07:00
Xiao Hanyu
04b662ea11 Fix a type error in rst' sub list. (#2881)
Sub list in rst requires a blank line above and below.

Ref:
- http://docutils.sourceforge.net/docs/ref/rst/restructuredtext.html#bullet-lists
2017-05-31 21:26:17 -07:00
Grace Guo
db052b17ea Add visualize advise for long query (#2879)
in SqlLab view, if query takes over 45 seconds, we will show advise to store a summarized data set before user clicks on Visualize button.
This advise will not block Visualize button.

fixes https://github.com/airbnb/superset/issues/2733
2017-05-31 15:50:26 -07:00
Maxime Beauchemin
e300273e71 [explore] adding y_axis_bounds to force Y axis bounds (#2878)
* [explore] adding y_axis_bounds to force Y axis

* Handling comments
2017-05-31 14:47:09 -07:00
Maxime Beauchemin
c5f2eafc90 [explore] 'Save as' -> 'Save' as it can be used to overwrite (#2875)
* [explore] 'Save as' -> 'Save' as it can be used to overwrite

* Fix tests
2017-05-31 14:31:13 -07:00
Alanna Scott
90e4d6469d [js-testing] more tests for SelectControl (#2877)
* rename spec folder

* remove special handling for viz_type control since it now uses VizTypeControl

* add test for getOptions

* linting

* add test for cwp

* linting
2017-05-31 10:52:02 -07:00
Grace Guo
1e7773eb16 Improve visualize modal test coverage (#2811)
add unit tests for VisualizeModal component
2017-05-30 18:16:22 -07:00
Maxime Beauchemin
3c89c8cc46 0.18.3-alpha.3 2017-05-30 11:24:14 -07:00
Maxime Beauchemin
74086dae2b [bars] fix sort numeric bar on x axis (#2812) 2017-05-30 11:18:25 -07:00
Maxime Beauchemin
66403f1876 [explore] viz type selector as modal (#2787)
* [explore] viz type selector as modal

* Addressing comments

* Adressing comments
2017-05-30 11:05:18 -07:00
Maxime Beauchemin
3a4cd3ae24 Applying specified limit in bubble plot (#2815)
* Applying specified limit in bubble plot

* Keeping bubbles proportinate
2017-05-30 11:04:44 -07:00
Maxime Beauchemin
4ffc1f613e Fix filter values populating for views (#2816) 2017-05-30 10:51:34 -07:00
Maxime Beauchemin
dfbba84400 0.18.3-alpha.2 2017-05-30 10:39:17 -07:00
Grace Guo
77864e6cf4 reduce clientside timeout limit (#2820)
it was set to 60 seconds. But it seems gateway will return 504 around 50 seconds.
Will reduce client side timeout limit to 45 seconds.
2017-05-25 16:07:02 -07:00
Maxime Beauchemin
4d12251806 [explore] include ControlHeader as part of Control interface (#2809)
* [explore] include ControlHeader as part of Control interface

* Adressing comments
2017-05-25 11:54:26 -07:00
Alanna Scott
0c9f9b695b [clarity/consistency] rename /explorev2/ -> /explore/ (#2802)
* rename /explorev2/ -> /explore/

* add redirect for existing explorev2 urls

* fix long line

* remove extra line

* fix missed ref in spec
2017-05-24 17:12:28 -07:00
Marek Vavruša
a4a2bf7ae9 filter_box: allow creatable entries (#2804)
The reason is that it should be able to create arbitrary filters
over attributes with very high cardinality, where loading all
possible combinations into the filter box is infeasible,
e.g. IPv6 addresses.
2017-05-24 14:41:58 -07:00
Grace Guo
d0a04cde49 apply redux for VisualizeModal (#2795)
* redux visualize modal

* redux visualize modal

- apply redux
- add unit test for connect container component
- fix lint error

* redux visualize modal

- apply redux
- add unit test for connect container component
- fix lint error
2017-05-24 11:58:54 -07:00
Maxime Beauchemin
69685b9dcc Fixing country maps (#2801) 2017-05-24 09:27:04 -07:00
ymatagne
b308a3eb4e Added Country Map : New Visualization tools (#2708)
* feat(visualization): Create new visualization's tools that display country with geojson file. Currently only  france and Spain are loaded

* refacto(visualization): fix issues created by codeclimate

* refacto(visualization): fix issues created by travis for mysql databases

* refacto(visualization): fix issues created by codeclimate

* refacto(visualization): fix issues created by codeclimate

* refacto(visualization): fix issues created by codeclimate

* refacto(visualization): fix issues created by codeclimate

* fix(visualization): fix issues

* doc(visualization): Init visualization documentation

* doc(visualization): init visualization documentation for country

* fix(visualization): implement iso 3166-2 for id of country

* fix(visualization): implement iso 3166-2 for id of country

* doc(visualization): init visualization documentation for country

* fix(visualization): implement iso 3166-2 for id of country

* feat(country_map): add ukraine map in component

* fix(visualization): Test dont working and add color based on metrics

* refacto(visualization): fix issues for es6 and color component
2017-05-23 10:43:33 -07:00
yamdraco
bfa40bd360 [hotfix] 'filter box from and to date filter #2649' (#2785) 2017-05-22 21:15:10 -07:00
ShengyaoQian
b0e2904c24 Updating permission when refreshing druid datasource (#2655)
* Updating permission when refreshing druid datasource

* Adding test

* Fix style

* Deletion view_menu after db, table, cluster, ds deletion

* Update table model

* Linting

* Override _delete instead of post_delete

* fix

* lint

* fix multi delete

* fix

* Refactoring

* Amending
2017-05-22 16:41:32 -07:00
Maxime Beauchemin
ce506bdf65 Logging a few more actions (#2783)
* Logging a few more actions

* adding statsd dep
2017-05-19 10:25:58 -07:00
Maxime Beauchemin
922cc037bf 0.18.3-alpha.1 2017-05-18 15:59:28 -07:00
Maxime Beauchemin
8252ada1f9 [docs] more details on how filters are applied (#2778) 2017-05-18 15:56:42 -07:00
Maxime Beauchemin
a2d2f8bb8c Enable filter value autocomplete in examples (#2781) 2017-05-18 15:56:24 -07:00
Maxime Beauchemin
7c5f61d6a6 Adding some STATSD logging (#2715) 2017-05-18 15:25:09 -07:00
Maxime Beauchemin
841e18a08c [sql lab] make database ordering alphabetical in left panel (#2769) 2017-05-17 21:31:16 -07:00
Maxime Beauchemin
dbc7fef7f5 [sql lab] fix user timestamp is off (#2774) 2017-05-17 15:34:19 -07:00
Maxime Beauchemin
cbfe3cb2dc 0.18.2 2017-05-16 23:08:56 -07:00
Maxime Beauchemin
5fcd25def1 0.18.1-alpha.2 2017-05-16 21:58:02 -07:00
Maxime Beauchemin
fe3f5f69ae [hotfix] 'No numeric types to aggregate' 2017-05-16 21:57:02 -07:00
Maxime Beauchemin
2395fbbdaa Adding end_result_backend_time to Query model (#2766)
This will help us keep track on how long it takes to push the data
into the results backend.
2017-05-16 20:34:38 -07:00
Grace Guo
960b26c7a2 Show clear and actionable query timeout error message (#2763)
* Show clear and actionable query timeout error message

1. Instead of waiting for a long time or server-side response 504 Gateway timeout, explore view now add a query timeout threshold. After timeout it will show specific querytimeout message.
2. fix alert box close button position.

* Show clear and actionable query timeout error message

1. Instead of waiting for a long time or server-side response 504 Gateway timeout, explore view now add a query timeout threshold. After timeout it will show specific querytimeout message.
2. fix alert box close button position.
3. fix a linting error.
2017-05-16 13:12:12 -07:00
Maxime Beauchemin
28ac3504d6 0.18.1-alpha.1 2017-05-16 00:03:47 -07:00
Maxime Beauchemin
ecc00bdd26 [explore] a bit less margin in left panel (#2758)
* [explore] a bit less margin in left panel

* fix tests

* forcing react-select 1.0.0-rc.3
2017-05-16 00:02:37 -07:00
eeve
e7946451d6 fixed 500 error when export dashboard (#2760)
https://github.com/airbnb/superset/blob/master/superset/views/core.py#L474

http://flask-appbuilder.readthedocs.io/en/latest/actions.html?highlight=action

```
@action("mulexport", __("Export"), __("Export dashboards?"), "fa-database")
    def mulexport(self, items):
        ids = ''.join('&id={}'.format(d.id) for d in items)
```
change to 
```
@action("mulexport", __("Export"), __("Export dashboards?"), "fa-database")
    def mulexport(self, items):
        if not isinstance(items, list):
            items = [items]
        ids = ''.join('&id={}'.format(d.id) for d in items)
```

fixed:
#2184
#2667
2017-05-15 21:33:07 -07:00
Anthony Bush
9b34600c8e Remove duplicate text (#2761) 2017-05-15 21:31:37 -07:00
Maxime Beauchemin
884610861b 0.18.0 2017-05-13 16:33:03 -07:00
yileic
d9bd3d6460 fix percentage change viz (#2757) 2017-05-13 15:46:02 -07:00
Maxime Beauchemin
38375be5c3 Fix issues around % signs and Presto (#2755)
* Fix issues around % signs and Presto

* Fix mysql
2017-05-12 09:45:33 -07:00
Rich @ RadICS
91d951ac42 Change hardcoded references to 'User' security model to allow custom class override (#2728) 2017-05-12 09:37:24 -07:00
yileic
d79a45ff32 add number format to pivot table (#2703)
* modify pivot table number format

* lint code

* lint code

* lint

* lint

* change variable name

* add number format to pivot table

* clean

* fix code climate
2017-05-11 20:34:07 -07:00
yileic
818251fc85 make margin consistent (#2753) 2017-05-11 20:31:05 -07:00
Maxime Beauchemin
75abd1f44a 0.18.0-alpha.4 2017-05-11 08:26:06 -07:00
Maxime Beauchemin
f55df3b18b [sql lab] fix responsivity of grid (#2742)
* [sql lab] fix responsivity of grid

* Lint
2017-05-11 08:24:08 -07:00
Maxime Beauchemin
5dbfdefae8 [sql lab] fix partitionned table has no partitions (#2740) 2017-05-11 08:23:43 -07:00
Maxime Beauchemin
e5584440ce Speed up all CRUD list views (#2747)
Load times on list view pages like Slices, Dashboards, Tables and Database
have grown to be terrible over time.

After a bit of digging, I found that the not specifying `search_columns`
in ModelViews actually means "all columns" and that for each filter,
FAB goes and fetches a list of all values to prepopulate the
filter dropdowns. That means that the list of tables would fetch all
slices and all users upfront which is horrible. Worse, database list
view would fetch all queries with is insane.

This picks a subset of columns for search/filters and changes the
default to show only 100 elements per page instead of 500
2017-05-11 08:22:30 -07:00
Grace Guo
d5e9d5d045 fix auto select override pre-selected value bug (#2745)
auto select should only apply to cases where select control doesn't have pre-selected value prop. If select control has pre-selected value (passed in from value prop), auto select first avaliable options should not apply.
2017-05-10 17:27:43 -07:00
yileic
3208a014ff fix dual line chart margin (#2737) 2017-05-09 17:44:08 -07:00
Maxime Beauchemin
874c12ad2d 0.18.0-alpha.3 2017-05-09 16:14:16 -07:00
Maxime Beauchemin
22d8075c53 Making the stop button instantaneous (#2738) 2017-05-09 16:10:08 -07:00
Maxime Beauchemin
baebba1159 0.18.0-alpha.2 2017-05-09 13:37:25 -07:00
Maxime Beauchemin
04748b4cda [SQL Lab] fix gamma metadata access (#2702) 2017-05-09 13:36:49 -07:00
Maxime Beauchemin
a471afe206 [sql lab] improvements to the left panel (#2709)
* [sql lab] improvements to the left panel

* better error handling with error notifications
* table is added instantly with a loading image

* Fixing tests

* Fixed tests
2017-05-09 13:36:10 -07:00
Boris Hajduk
5d0a01d0d0 Decimal is a valid numeric type (#2720)
* Decimal is a valid numeric type

This will allow automatic creation of sum__ and avg__ metrics

* formatting for line length at 80 chars

* Reformating again, for the mandatory 79 chars maximum.
2017-05-08 22:19:52 -07:00
Maxime Beauchemin
9e1272e97c 0.18.0-alpha.1 2017-05-08 18:04:26 -07:00
Maxime Beauchemin
aeebd8840d [table] fixing CSS glitches on table view (#2725)
Somehow the CSS trick we use to display histogram-type visual elements
in table views started showing some odd glitches since
what I believe was a not-so-recent version of webkit.

The bug is around the `linear-gradient` call under `background-image`
prop being used to show non-gradient or "hard" colors, while using
transparency.

I was able to find a workaround that addresses things in chrome by using
a fake [minimal] gradient instead of a flat color.
2017-05-08 17:59:23 -07:00
YOU
55d3b012e5 refactor: recentActions ajax call is redundant (#2722) 2017-05-06 21:27:40 -07:00
Maxime Beauchemin
a6e1e18244 [sql lab] fix CREATE TABLE AS (#2719) 2017-05-06 21:23:55 -07:00
YOU
46d7a925bb chore: remove unused methods with invalid models.Query usage (#2721)
* fix: models.Query is not defined

* chore: remove unused Query and methods
2017-05-06 21:23:17 -07:00
Maxime Beauchemin
5929ab7689 [dashboard] fix missing datasource issue (#2718)
Addresses a bug that happens when rendering a dashboard for which
a datasource behind one of the slices has been deleted
2017-05-05 16:48:41 -07:00
graceguo-supercat
fffb7b500a [sql lab]Add autoSelect (#2713)
In sql editor database dropdown list, we want to auto-select the first available option.
2017-05-05 11:00:06 -07:00
Maxime Beauchemin
cb14640a82 Removing uneeded results_backends.py (#2717) 2017-05-04 23:41:10 -07:00
Maxime Beauchemin
d65054e015 [sql lab] fix csv export where % in query (#2711)
* [sql lab] fix csv export where  in query

* Prgoress
2017-05-04 16:34:18 -07:00
Maxime Beauchemin
5d5060eca6 Remove unecessary handling of %% (#2714) 2017-05-04 14:11:10 -07:00
Marek Vavruša
9ff351532a Basic integration with ClickHouse (#1844)
* db_engine_specs: added engine spec for clickhouse

* models: group by time without join on subquery

if this is enabled, it will remove the subquery when grouping by time,
and group by time will be rewritten as group by timeslot, ...

this particularly fixes clickhouse backend

* docs: added clickhouse

* models: allow using secondary dttm columns as constraints

* superset/forms: configurable default slice since time

* superset/models: respect dialect quoting

* dttm handling, codeclimate tweaks
2017-05-03 13:57:39 -07:00
Songyan Ho
59a6f447ec Fix missing curUserId from SliderAdder.jsx (#2705) 2017-05-01 21:58:14 -07:00
Maxime Beauchemin
1887b5e934 0.17.6 2017-05-01 13:13:06 -07:00
Maxime Beauchemin
33758bfff5 0.17.6-alpha.2 2017-05-01 09:00:48 -07:00
Alanna Scott
2d5beb1f91 improve csrf expiration error handling (#2695)
* add message

* re run query on result set mount if there is session time out error
2017-05-01 08:59:44 -07:00
Justin Park
5fd0e7d028 [vis] bar values should match y axis format (#2701)
It passes the user yAxis format value to the totalValue function to render the label text accordingly
2017-05-01 08:59:02 -07:00
Songyan Ho
ef0c4be067 Fix for referring specific svg (#2612)
* Fix for referring specific svg

* Remove trailing space
2017-04-30 11:08:40 -07:00
Maxime Beauchemin
ac3aba7c7d [sql lab] visualization flow: fixing the groupby parameter (#2681) 2017-04-30 11:04:29 -07:00
yileic
0fdb57a181 fix (#2696) 2017-04-30 11:01:34 -07:00
Maxime Beauchemin
3e7b5df287 [explore] fix empty chart when changing viz type (#2698)
* [explore] fix empty chart when changing viz type

* Lint
2017-04-30 10:58:45 -07:00
Denny Biasiolli
3cd16cf368 Fix test's warnings (#2697)
* tests: adding required props to FilterableTable mockedProps

* tests: adding required prop `height` to QuerySearch mockedProps

* tests: adding required prop `height` to ResultSet mockedProps

* tests: adding required prop `height` to SqlEditorLeftBar mockedProps

* tests: fix warning in Timer component
2017-04-30 10:58:09 -07:00
Riccardo Magliocchetti
a58adc862e Fix orm query in HiveEngineSpec.handle_cursor (#2699)
Fix #2643
2017-04-30 10:55:56 -07:00
yileic
7d88f80a9b hack to dynamically adjust y axis left margin (#2689)
* hack to dynamic adjust y axis margin

* merge into the previous hack

* render for both hack
2017-04-28 19:56:19 -07:00
yileic
09be02f70a fix x axis label (#2691) 2017-04-28 17:59:09 -07:00
alanmcruickshank
50fcdd3a34 Adding tails.com to inthewild (#2685) 2017-04-28 17:57:32 -07:00
Denny Biasiolli
dee36491c5 Fix js warnings (#2693)
* react: using prop-types package to fix deprecated React.PropTypes property warning

https://facebook.github.io/react/warnings/dont-call-proptypes.html

* removing babel devDependency because has been deprecated in favor of babel-cli

this fixes a warning during `npm install`:
```
npm WARN deprecated babel@6.23.0:
In 6.x, the babel package has been deprecated in favor of babel-cli.
Check https://opencollective.com/babel to support the Babel maintainers
```

* js: setting ExploreActionButtons.queryEndpoint PropType as required

because it's required in the child component DisplayQueryButton

* js(tests): using object in expandedSlices prop type of SliceCell tests

* js(tests): adding required props to SqlEditor mockedProps

* js(tests): adding required prop editorHeight to TabbedSqlEditors mockedProps

* js: removing unused moments dependency
2017-04-28 17:56:29 -07:00
yileic
903612ac6c fix (#2692) 2017-04-28 15:16:55 -07:00
yileic
ce705054fa fix a bug in 'getMaxLabelSize' and x axis label not shown problem (#2694)
* fix

* fix code climate
2017-04-28 15:14:04 -07:00
graceguo-supercat
c589616883 [sql lab] Update event handler name (#2680)
Update SqlEditor database change event handler name to be more specific.
2017-04-28 11:42:48 -07:00
Riccardo Magliocchetti
58309f275f Mark a few more string for translation (#2651)
* Mark some more string for translations

Fix #2629

* Rebuild translations

* Please codeclimate
2017-04-26 21:57:56 -07:00
Tom Paine
edf4e4f24e Update README.md (#2676)
Spelling
2017-04-26 21:57:11 -07:00
Maxime Beauchemin
b3e0b5b586 Specifying cryptography version in install docs 2017-04-26 14:20:27 -07:00
Maxime Beauchemin
68802989bc Pinning cryptography lib to 1.7.2 2017-04-26 10:32:25 -07:00
Alanna Scott
70887d72e2 0.17.6-alpha.1 2017-04-25 22:13:29 -07:00
Maxime Beauchemin
1922225042 Alternate fix for #2665 (#2671)
* Alternate fix for #2665

* Addressing comments

* Fix doctest
2017-04-25 18:18:17 -07:00
Alanna Scott
0bdc3010d8 [vis] fix pivot table scrolling when using more than 1 groupy col (#2674)
* fix scrolling when more than 1 groupby

* generalize function arguments

* fix pivot table scrolling in js

* add padding rules back

* linting
2017-04-25 16:14:59 -07:00
robert-digit
1df37e6e4d add option for pulling favourited dashboards by username (#2661)
* add option for pulling favourited dashboards by username

add tests

* fix too many lines pep8 error
2017-04-24 20:48:25 -07:00
Alanna Scott
e9ed416654 fix csrf error on import_dashboards (#2672) 2017-04-24 18:38:17 -07:00
Maxime Beauchemin
03c42b5b87 Showing slices on datasource edit form (#2645)
* Showing slices on datasource edit form

* fixing build
2017-04-24 17:40:34 -07:00
Maxime Beauchemin
e055e6d2c2 Fixing PropTypes warning messages (#2670)
* Fixing PropTypes warning message

React recently started warning on the upcoming deprecation of
React.PropTypes, the new approach is to use the `prop-types`
npm package instead.

* Fixing the tests
2017-04-24 17:39:57 -07:00
Maxime Beauchemin
29780821e8 [druid] fixing the having clause in the explore view (#2648)
* [druid] fixing the having clause in the explore view

* Backend

* Lint

* Fixing tests
2017-04-24 16:31:16 -07:00
Maxime Beauchemin
f10ee13901 [druid] fix FilterBox viz gets timestamps as values if granularity <> all (#2647) 2017-04-24 10:27:26 -07:00
Maxime Beauchemin
cdfc4a35b2 Optimizing the standalone view (#2663)
The standalone view doesn't need to fetch the datasources asynchronously
to fill in the dataources dropdown, it also doesn't need to render any
of the controls.
2017-04-23 22:13:23 -07:00
yileic
eb762c8bfe add the missing right bracket (#2662) 2017-04-22 12:36:47 -07:00
Riccardo Magliocchetti
83abfef830 superset: fix argument swap for SqliteEngineSpec.get_table_names (#2664)
Fix #2660
2017-04-22 12:35:39 -07:00
Maxime Beauchemin
54137ad023 Updating CHANGELOG 2017-04-21 14:15:21 -07:00
Maxime Beauchemin
4be6bfafaa 0.17.5 2017-04-21 13:56:23 -07:00
Alanna Scott
9ba6d489f3 v0.17.5-alpha.10 (#2654) 2017-04-20 15:37:08 -07:00
Alanna Scott
af4bd40853 fix scrolling on markup vis (#2644) 2017-04-20 12:39:24 -07:00
Alanna Scott
84fa0d1940 don't default sort by to first column (#2653) 2017-04-20 12:34:35 -07:00
Maxime Beauchemin
938e13a429 [hotfix] Presto's latest_sub_partion rendering fail 2017-04-20 17:44:56 +00:00
Alanna Scott
dc364daffd [query-search] fix scrolling on query search and pagination styling (#2646)
* fix scrolling for query search, getHeight was returning NAN

* add styling for query search pagination

* remove console.log

* make pagination buttons match bootstrap theme small buttons
2017-04-19 21:02:53 -07:00
Rogan
1cadfecd4b update core.py label and zh po file (#2642) 2017-04-19 12:46:07 -07:00
Alanna Scott
5b26667fd5 0.17.5-alpha.9 2017-04-19 01:58:49 -07:00
Alanna Scott
899caf9449 [sql-lab] fix scrolling in left hand panel for table meta data (#2641)
* fix scrolling in left hand panel for table meta data

* add height prop

* this prop is not used
2017-04-18 23:24:04 -07:00
Maxime Beauchemin
e6063f2ddf 0.17.5-alpha.8 2017-04-18 16:06:23 -07:00
Maxime Beauchemin
46486f82d9 Moving the warning message to the navbar (#2640)
At Airbnb we use these WARNING_MSG configuration element to make it
clear that we're in the staging environment.

Before this PR it would render under the navbar and mess up some of the
heights configurations, where you wouldn't be able to scroll all the way
to the bottom of the page. This fixes it.
2017-04-18 16:03:32 -07:00
Alanna Scott
0089762955 0.17.5-alpha.7 2017-04-18 14:35:31 -07:00
Alanna Scott
2bd60c0747 [vis] fix line chart when slice is really narrow (#2620)
* only show legend if larger than small breakpoint

* fix text length getter, it was returning -Infinity
2017-04-18 14:29:51 -07:00
Alanna Scott
db6cd21504 [sqllab] table refactor (#2587)
* make react-virtualized table work
use dynamic sizing for cell width
enable filtering
require height prop for result set component

* fix tests and linting

* move some state to props

* move getTextWidth to visUtils

* make striped rows optional

* fix striped proptype

* update name to FilterableTable

* add basic test and fix linting

* accept array of columns keys rather than an array of objects that needs to be mapped

* move container div inside the component

* rename styles

* fit table component to width if it's smaller than parent container

* move stylesheet to javascript folder otherwise it throws an error on npm run prod

* move css to index.jsx

* fix result set spec

* fix linting and test

* fix result set props

* keep list immutable
2017-04-18 14:29:38 -07:00
Maxime Beauchemin
f40499e550 [dashboard] improve error handling on dashboard (#2624)
* [dashboard] improve error handling on dashboard

* lint

* More wordy error msg
2017-04-18 13:29:54 -07:00
Maxime Beauchemin
67a85b9831 Fix dashboard edit button is disabled (#2634)
* Fix dashboard edit button is disabled

* Addressing comments
2017-04-18 13:29:02 -07:00
Maxime Beauchemin
cb3384b3b2 Improving Presto error message in explore/dashboard (#2633)
* Improving Presto error message in explore/dashboard

* lint
2017-04-18 12:30:17 -07:00
Maxime Beauchemin
ac51a30f98 Remove metrics control non-null validator in Table context (#2635) 2017-04-18 12:29:43 -07:00
Maxime Beauchemin
91fe02cdc8 Setting adjust_database_uri for HiveEngineSpec (#2636)
* Setting adjust_database_uri for HiveEngineSpec

* fix tests
2017-04-18 12:29:13 -07:00
Maxime Beauchemin
76042be7c3 [hotfix/sqllab] setting up the connection in the try: block 2017-04-17 17:32:21 -07:00
Maxime Beauchemin
d3f55a0905 0.17.5-alpha.6 2017-04-17 17:58:31 +00:00
Maxime Beauchemin
efaef8fe09 [hotfix] fix endpoint 2017-04-17 17:58:13 +00:00
Maxime Beauchemin
8757a24d89 0.17.5-alpha.5 2017-04-17 09:47:10 -07:00
Maxime Beauchemin
63785f039c [hotfix] using UTC for caching timestamps 2017-04-17 16:45:50 +00:00
Maxime Beauchemin
d6689ee700 0.17.5-alpha.4 2017-04-17 09:08:29 -07:00
Maxime Beauchemin
787daf6005 A nice CacheLabel React component (#2628)
Introducing a nice component as a label that show when data was
loaded from cache, when the cache was taken (in humanize duration as in
`a few minutes ago`) in a tooltip, and it can act as a button that
can trigger a force-refresh.

While working on it, it became clear that it was going to be hard to
use this component in the Dashboard view since it's not pure React.
I'm planning on refactoring the dashboard view with proper React/Redux
and introducing the CachedLabel component at that point.

While digging around in the Dashboard view I realized that there was a
bunch on unused code around managing timers that was used in explorev1
and decided to rip it out.
2017-04-17 09:06:56 -07:00
Maxime Beauchemin
23aeee5a9c Slice level cache_timeout isn't taken into consideration (#2631) 2017-04-17 08:48:33 -07:00
Joe Francia
70c6cad0e3 Add UNIX socket option to runserver (#2627)
* Add UNIX socket option to runserver

Added an optional parameter to runserver to server from a UNIX socket instead of an address:port. I believe it is fairly common to server from sockets when using a web server like Nginx on the same host.

* Collapsed if/else logic for address or socket

Also wrapped help description for socket parameter
2017-04-16 23:19:02 -07:00
Maxime Beauchemin
6b1bf3b395 [hotfix] missing explore's main.css 2017-04-15 14:10:51 -07:00
Maxime Beauchemin
f5216f6047 Adding owner(s) to dashboard makes them own underlying slices (#2610) 2017-04-14 15:38:06 -07:00
Alanna Scott
15654a3082 fix filters on dashboard (#2619) 2017-04-14 15:35:37 -07:00
Maxime Beauchemin
baff0cba38 Fix separator widget CSS (#2623) 2017-04-14 12:32:10 -07:00
Maxime Beauchemin
c4ee098bb7 0.17.5-alpha.3 2017-04-13 17:41:30 -07:00
Maxime Beauchemin
612b8ca3d7 [hotfix] legacy url not handled properly 2017-04-13 17:40:36 -07:00
Maxime Beauchemin
c43a9fd554 [hotfix] fixing the build 2017-04-13 15:58:04 -07:00
Maxime Beauchemin
6c68a21e4f [sql lab] fix alt-enter runs out-of-date SQL (#2603) 2017-04-13 15:47:20 -07:00
Maxime Beauchemin
db02b33e09 [explore] fix query text modal while loading (#2596)
* [explore] fix and clean

Currently it's not possible to view queries while they are running, the
spinner appears endlessly. I decided to rearrange things a bit while
debugging so I could see clearly through it.

* Adding NotImplemented methods to base classes

* Fixing

* Piling up
2017-04-13 15:46:57 -07:00
Maxime Beauchemin
2df6baa7a7 [sql lab] sorting database names in dropdowns (#2611) 2017-04-13 15:23:22 -07:00
Maxime Beauchemin
fc7bd63039 [sql lab] fixes issues specific to Sqlite (#2606) 2017-04-13 15:19:58 -07:00
Maxime Beauchemin
959a09cc92 [dashboard] fix css padding for markup viz (#2602) 2017-04-13 15:16:46 -07:00
Maxime Beauchemin
366ecefbaa Bumping the JS libs to fix the build (#2616)
* bumping the js libs

* New linting rules

* More linting

* More

* Done linting

* npm >=4.5.0

* Bumping node

* Tweaking the build

* Fixing the damn build

* Fixing the apps
2017-04-13 15:04:09 -07:00
Maxime Beauchemin
a2b30f35fc 0.17.5-alpha.2 2017-04-13 18:39:05 +00:00
Maxime Beauchemin
8bceda8134 [hotfix] fix iframe viz 2017-04-13 18:37:27 +00:00
Maxime Beauchemin
bae1067015 0.17.5-alpha.1 2017-04-13 01:09:49 +00:00
Maxime Beauchemin
116dca3e6f validationErrors is undedfined 2017-04-13 01:08:37 +00:00
Maxime Beauchemin
b448394077 0.17.5-alpha.0 2017-04-12 13:56:12 -07:00
Alanna Scott
09f407f553 add tooltips to big number vis (#2599) 2017-04-12 13:24:07 -07:00
Maxime Beauchemin
0479118efc 0.17.5.dev0 2017-04-12 13:09:53 -07:00
Songyan Ho
c93411b1e7 Fix for merge string as array (#2597) 2017-04-12 09:40:41 -07:00
Maxime Beauchemin
31283f1424 Fix metric formating in Dashboard view + some refactoring (#2598)
* Fix metric formating in Dashboard view + some refactoring

* Fixing build
2017-04-12 09:37:49 -07:00
Maxime Beauchemin
93c6597cf4 Adding docs/ to .eslintignore 2017-04-11 20:33:54 -07:00
Maxime Beauchemin
4446c745a8 Fix backend-sync 2 2017-04-11 20:31:01 -07:00
Maxime Beauchemin
38e90fe309 Fix backend-sync 2017-04-11 20:28:04 -07:00
Steven Niemitz
f5489467e5 Use correct dialect for escaping functions. (#2593)
Column functions are currently escaped with the
same dialect that is being used by Superset for its
metadata storage.  Instead the dialect of the target
table should be used when escaping column names.
2017-04-11 17:36:30 -07:00
Alanna Scott
ab0bc5a3fa handle percentage case for tooltips (#2570) 2017-04-11 12:26:34 -07:00
Riccardo Magliocchetti
412634cb57 Add missing flask-wtf dependency (#2586) 2017-04-11 12:25:54 -07:00
Maxime Beauchemin
a0ddbb9ec9 Make form_data dict a macro (#2585) 2017-04-10 22:04:25 -07:00
Maxime Beauchemin
a803705ddc [bugfix] clarifying how to create a slice (#2565)
* [bugfix] clarifying how to create a slice

First time users [rightfully] think that the proper way to create a
slice is to click the `+` button on the `Slices` CRUD page.

Changing / simplifying the redirect logic and message flashing that was
failing here somehow.

* Changing message from danger to info
2017-04-10 21:46:19 -07:00
Maxime Beauchemin
75a358c616 [explore] force control validation before runQuery (#2544)
* [explore] force control validation before runQuery

* Addressing comments
2017-04-10 21:45:44 -07:00
Maxime Beauchemin
493ba18362 Adding macros current_user_id & current_username (#2582)
* Adding macros current_user_id & current_username

* Addressing comments
2017-04-10 21:42:25 -07:00
Maxime Beauchemin
5e4fca4ea4 Bumping a set of Python libraries to the latest release (#2575)
* Bumping a set of Python libraries to the latest release

* fixing flask-sqlalchemy==2.0
2017-04-10 15:37:30 -07:00
Maxime Beauchemin
d289783b67 Adding Vertica to installation docs (#2581) 2017-04-10 15:37:14 -07:00
Maxime Beauchemin
ac84fc2b65 Fixing confusion when selecting schema across engines (#2572) 2017-04-10 15:36:58 -07:00
Maxime Beauchemin
40b3d3b3ef [hotfix] add csrf_token api endpoint 2017-04-10 12:27:03 -07:00
Maxime Beauchemin
50a9e13f9b [hotfix] add csrf_token api endpoint 2017-04-10 12:10:27 -07:00
Maxime Beauchemin
66bff01b45 Changelog for 0.17.4 2017-04-10 09:41:32 -07:00
Maxime Beauchemin
9691234b7e [hotfix] casting db_id to int 2017-04-07 17:02:00 -07:00
Maxime Beauchemin
ddeabdd048 Fixing CSRF issues (#2569)
* 0.17.4

* Fixing CSRF issues

Since turning CSRF across the site with Flask-WTF, a few POST request
have been failing. This PR addresses these issues.
2017-04-07 10:57:31 -07:00
Maxime Beauchemin
3ed45ab98c [bugfix] saved query restore wouldn't pick the db (#2568) 2017-04-07 07:48:47 -07:00
Maxime Beauchemin
ca08e7051e 0.17.4rc5 2017-04-06 14:09:30 -07:00
Alanna Scott
1fb21b8b45 [revert] reverting big num changes (#2567)
* Revert "[big num] make sure scatterplot dots align properly (#2559)"

This reverts commit f24ddfd467.

* Revert "Revert "measure x axis labels too and use the longest to determine margins" (#2550)"

This reverts commit fe68bc31c3.

* Revert "[bug num vis] fix sizing for single digits (#2548)"

This reverts commit 3d2c791ff1.

* revert all big num changes.

* fix linting

* make document global, fix linting
2017-04-06 13:15:42 -07:00
rumbin
c581ea8661 Alternative PR for: Some bytes/str issues in py3 w/ zlib and json (#2558)
* sql_lab.py: compress via utils

* utils.py: added zlib_compress and zlib_compress_to_string

* core.py: converted to use zlib_decompress_to_string; renamed uncompress to decompress in utils.py

* utils_tests.py: added test for compress/decompress

* fixed broken utils test; removed redundant code and empty lines from utils.py

* utils.py: corrected docstrings, removed unnecessary 'else'

* removed yet another superfluous else
2017-04-06 09:42:43 -07:00
rumbin
f19d1958c5 INTHEWILD: added Endress+Hauser (#2562) 2017-04-06 09:40:37 -07:00
Maxime Beauchemin
9c99be510b [hotfix] iframe viz is broken 2017-04-06 09:11:54 -07:00
Alanna Scott
7a08cdcb1c 0.17.4rc3 2017-04-05 12:47:46 -07:00
Alanna Scott
f24ddfd467 [big num] make sure scatterplot dots align properly (#2559)
* use cardinal so scatterplot lines up properly

* use min 30 for margins, accounts for small y axis labels
2017-04-05 12:41:24 -07:00
Maxime Beauchemin
23a8ea5636 v0.17.4rc2 2017-04-05 12:02:16 -07:00
Maxime Beauchemin
2c04d3c250 [bugfix] save dash fails with CSRF related error (#2552) 2017-04-05 12:01:17 -07:00
Maxime Beauchemin
337454b646 [hotfix] slice with missing datasource related 2017-04-05 19:00:49 +00:00
Maxime Beauchemin
b7f46ebe75 [hotfix] dashboard fails when a slice is missing its datasource 2017-04-05 18:52:17 +00:00
Maxime Beauchemin
10773f96a7 URL Params macro (#2537) 2017-04-05 10:45:32 -07:00
rhunwicks
b97a8275d4 Clarify docs on Redis package required for caching (#2557) 2017-04-05 10:07:36 -07:00
Maxime Beauchemin
081bdca71e [hotfix] [sql lab] fix sqlite errors when schema is selected 2017-04-05 00:26:15 -07:00
openmax
62959ca38b Db2 Grain Correct Data Format (#2545)
* Db2 Grain Correct Data Format

* Db2 Grain Correct Data Format.

* Db2 Grain Correct Data Format + typo

* Db2 Grain Correct Data Format +typo
2017-04-05 00:03:24 -07:00
Alanna Scott
fe68bc31c3 Revert "measure x axis labels too and use the longest to determine margins" (#2550)
This reverts commit cc640c1c4f.
2017-04-04 22:47:48 -07:00
Alanna Scott
3d2c791ff1 [bug num vis] fix sizing for single digits (#2548)
* fix sizing for single digits

* measure x axis labels too and use the longest to determine margins
2017-04-04 22:34:00 -07:00
Maxime Beauchemin
d40ce52139 v0.17.4rc1 2017-04-04 20:22:36 -07:00
Maxime Beauchemin
122891c29b [sql lab] allow users to save their queries (#2528)
* Allow users to save their queries

Fixing tests .

* Adding placeholder for Query Description

* initJQueryCSRF -> initJQueryAjaxCSRF
2017-04-04 20:15:19 -07:00
Alanna Scott
c1d9918abe [vis] bug num improvements (#2523)
* fix js error, make should render viz more readable, add tooltips, style axis and add min/max to xaxis

* put getTextWidth in utils

* add document to eslint globals config
2017-04-04 20:05:36 -07:00
Alanna Scott
d93b1fc686 [sql-lab] make query history scrollable (#2499)
* make query history scrollable

* not around results
2017-04-04 15:46:11 -07:00
Maxime Beauchemin
02c5cac26f [hotfix] adding filterable to DruidColumnInlineView.edit_columns 2017-04-04 15:15:02 -07:00
Maxime Beauchemin
6566377740 [sql lab] fix table dropdown with large schema make UI unresponsive (#2547)
Indexing was done on render, moving to doing it only when the data
changes.
2017-04-04 12:19:44 -07:00
Maxime Beauchemin
db6b2f3ae1 pylint errors will now break the build (#2543)
* Linting pylint errors

* Backing off of an unecessary change
2017-04-03 21:53:06 -07:00
Maxime Beauchemin
c31210b96d Some column description clarifications (#2536) 2017-04-03 21:52:35 -07:00
Alanna Scott
dcc6f2a18f serve roboto font locally (#2519)
* serve roboto locally

* add trailing comma

* fix long line for linter
2017-04-03 13:05:13 -07:00
Riccardo Magliocchetti
0c0666caa0 druid: use six.string_types instead of basestring (#2541)
Which is not available in python3

Fix #2539
2017-04-03 08:25:08 -07:00
dwa
243eeadfd6 installation instructions for AWS Athena (#2538) 2017-04-03 08:22:40 -07:00
dwa
9ba5b49d8a WIP: Initial commit to support the athena DB (#2531)
* Initial commit to support the athena DB

This work was done at tobii.com (in collaboration with knowit.se), and depends on:

- A patched version of PyAthenaJDBC (https://github.com/dwa/PyAthenaJDBC/tree/dwa-tobii-dict_formatter)
- A patched version of PyHive (https://github.com/dwa/PyHive/tree/dwa-tobii-sqlalchemy-athena)

And can be used like so:
athena://<user>:<password>@athena.us-east-1.amazonaws.com/?region_name=<region>&s3_staging_dir=s3%3A//<staging_bucket_of_choice>

* Rebased, and fixed two lint issues

* rename athena engine: athena -> awsathena
2017-04-02 20:29:33 -07:00
Maxime Beauchemin
c870bd414e 0.17.3 2017-04-01 16:55:38 -07:00
Maxime Beauchemin
4b01e92509 [dashboard] allow bar charts to scroll on x axis (#2513) 2017-03-31 08:28:19 -07:00
Maxime Beauchemin
513a090cdc [sql lab] address lingering spinner in schema select (#2512) 2017-03-31 08:27:48 -07:00
Maxime Beauchemin
abe79d1427 Deprecate is_featured as a datasource attribute (#2485) 2017-03-31 08:27:20 -07:00
Maxime Beauchemin
b81968dc20 Redirect to explore view when saving a table (#2479) 2017-03-31 08:26:49 -07:00
Maxime Beauchemin
66cc546a30 [hotfix] fixing the (one js lint err in the) build 2017-03-31 07:52:26 -07:00
dwa
6e899ac55f added tobii (#2526) 2017-03-30 13:13:18 -07:00
Maxime Beauchemin
6b52384024 [hotfix] fix pending queries race condition 2017-03-30 19:54:55 +00:00
Maxime Beauchemin
0a1d8db357 v0.17.3rc3 2017-03-29 19:14:14 +00:00
Maxime Beauchemin
6f68ddb505 Adding to polling states 2017-03-29 19:13:37 +00:00
Maxime Beauchemin
4f59abf189 v0.17.3rc2 2017-03-29 12:02:54 -07:00
Maxime Beauchemin
5c441f4ddb [hotfix] queries trigger polling (#2517) 2017-03-29 11:58:08 -07:00
Maxime Beauchemin
be023aba8d 0.17.3rc1 2017-03-29 09:50:19 -07:00
Wyndham Blanton
53990201bc forgotten query_datasources_by_name function (#2497)
* forgotten query_datasources_by_name function

* use normal formats
2017-03-29 09:47:26 -07:00
Maxime Beauchemin
37783d685f Updating CHANGELOG 2017-03-29 07:56:36 -07:00
330 changed files with 18386 additions and 11902 deletions

View File

@@ -5,6 +5,11 @@ engines:
enabled: false
eslint:
enabled: true
checks:
import/extensions:
enabled: false
import/no-extraneous-dependencies:
enabled: false
config:
config: superset/assets/.eslintrc
pep8:

View File

@@ -1 +1 @@
repo_token: eESbYiv4An6KEvjpmguDs4L7YkubXbqn1
repo_token: 4P9MpvLrZfJKzHdGZsdV3MzO43OZJgYFn

3
.gitignore vendored
View File

@@ -24,6 +24,8 @@ app.db
*.bak
.idea
*.sqllite
.vscode
.python-version
# Node.js, webpack artifacts
*.entry.js
@@ -32,3 +34,4 @@ node_modules
npm-debug.log
yarn.lock
superset/assets/version_info.json

View File

@@ -65,7 +65,7 @@ confidence=
# --enable=similarities". If you want to run only the classes checker, but have
# no Warning level messages displayed, use"--disable=all --enable=classes
# --disable=W"
disable=standarderror-builtin,long-builtin,dict-view-method,intern-builtin,suppressed-message,no-absolute-import,unpacking-in-except,apply-builtin,delslice-method,indexing-exception,old-raise-syntax,print-statement,cmp-builtin,reduce-builtin,useless-suppression,coerce-method,input-builtin,cmp-method,raw_input-builtin,nonzero-method,backtick,basestring-builtin,setslice-method,reload-builtin,oct-method,map-builtin-not-iterating,execfile-builtin,old-octal-literal,zip-builtin-not-iterating,buffer-builtin,getslice-method,metaclass-assignment,xrange-builtin,long-suffix,round-builtin,range-builtin-not-iterating,next-method-called,dict-iter-method,parameter-unpacking,unicode-builtin,unichr-builtin,import-star-module-level,raising-string,filter-builtin-not-iterating,old-ne-operator,using-cmp-argument,coerce-builtin,file-builtin,old-division,hex-method
disable=standarderror-builtin,long-builtin,dict-view-method,intern-builtin,suppressed-message,no-absolute-import,unpacking-in-except,apply-builtin,delslice-method,indexing-exception,old-raise-syntax,print-statement,cmp-builtin,reduce-builtin,useless-suppression,coerce-method,input-builtin,cmp-method,raw_input-builtin,nonzero-method,backtick,basestring-builtin,setslice-method,reload-builtin,oct-method,map-builtin-not-iterating,execfile-builtin,old-octal-literal,zip-builtin-not-iterating,buffer-builtin,getslice-method,metaclass-assignment,xrange-builtin,long-suffix,round-builtin,range-builtin-not-iterating,next-method-called,dict-iter-method,parameter-unpacking,unicode-builtin,unichr-builtin,import-star-module-level,raising-string,filter-builtin-not-iterating,old-ne-operator,using-cmp-argument,coerce-builtin,file-builtin,old-division,hex-method,invalid-unary-operand-type
[REPORTS]
@@ -99,7 +99,7 @@ evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / stateme
[BASIC]
# Good variable names which should always be accepted, separated by a comma
good-names=i,j,k,ex,Run,_,d,e,v,o,l,x
good-names=i,j,k,ex,Run,_,d,e,v,o,l,x,ts
# Bad variable names which should always be refused, separated by a comma
bad-names=foo,bar,baz,toto,tutu,tata
@@ -277,7 +277,7 @@ ignore-mixin-members=yes
# (useful for modules/projects where namespaces are manipulated during runtime
# and thus existing member attributes cannot be deduced by static analysis. It
# supports qualified module names, as well as Unix pattern matching.
ignored-modules=
ignored-modules=numpy,pandas,alembic.op,sqlalchemy,alembic.context,flask_appbuilder.security.sqla.PermissionView.role,flask_appbuilder.Model.metadata,flask_appbuilder.Base.metadata
# List of class names for which member attributes should not be checked (useful
# for classes with dynamically set attributes). This supports the use of

View File

@@ -1,28 +1,25 @@
language: python
services:
- redis-server
addons:
code_climate:
repo_token: 5f3a06c425eef7be4b43627d7d07a3e46c45bdc07155217825ff7c49cb6a470c
apt:
sources:
- deadsnakes
packages:
- python3.5
cache:
directories:
- $HOME/.wheelhouse/
env:
global:
- TRAVIS_CACHE=$HOME/.travis_cache/
- TRAVIS_NODE_VERSION="5.11"
- TRAVIS_NODE_VERSION="7.10.0"
matrix:
- TOX_ENV=javascript
- TOX_ENV=pylint
- TOX_ENV=py34-postgres
- TOX_ENV=py34-sqlite
- TOX_ENV=py27-mysql
- TOX_ENV=py27-sqlite
before_install:
- npm install -g npm@'>=3.9.5'
- npm install -g npm@'>=5.0.3'
before_script:
- mysql -e 'drop database if exists superset; create database superset DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci' -u root
- mysql -u root -e "CREATE USER 'mysqluser'@'localhost' IDENTIFIED BY 'mysqluserpassword';"

View File

@@ -1,5 +1,329 @@
## Change Log
## Change Log
### 0.18.3 (2017/06/02 15:27 +00:00)
- [d90044c](https://github.com/airbnb/superset/commit/d90044cd523253aa01b5cdf6509b43abeef34079) 0.18.3 (@mistercrunch)
- [62bd4eb](https://github.com/airbnb/superset/commit/62bd4eb2118e4f1e771b58cd455b928be002a461) Converting filter argument to number if column is number (#2891) (@fabianmenges)
- [677c427](https://github.com/airbnb/superset/commit/677c427b1615a2a9b9054932b5e07868d7d63e42) delete DataPreviewModal, it doesn't get used anywhere (#2882) (@ascott)
- [52b0716](https://github.com/airbnb/superset/commit/52b0716571c5b68f65d08719e54343e0a6ec8c8e) [bugfix] allow database macro to work when CSRF is diabled (#2884) (@justinr1234)
- [04b662e](https://github.com/airbnb/superset/commit/04b662ea1154b75e2c362aed97e33946251d1bed) Fix a type error in rst' sub list. (#2881) (@xiaohanyu)
- [db052b1](https://github.com/airbnb/superset/commit/db052b17ea1782c472687095e1365d071e592dd0) Add visualize advise for long query (#2879) (@graceguo-supercat)
- [e300273](https://github.com/airbnb/superset/commit/e300273e71f0b6ca8b63ae974cc13534d94c56de) [explore] adding y_axis_bounds to force Y axis bounds (#2878) (@mistercrunch)
- [c5f2eaf](https://github.com/airbnb/superset/commit/c5f2eafc906bebf1d238e62ff5a8821cf549220c) [explore] 'Save as' -> 'Save' as it can be used to overwrite (#2875) (@mistercrunch)
- [90e4d64](https://github.com/airbnb/superset/commit/90e4d6469d3d420cf839a4c9f3a2408ab0ff0a42) [js-testing] more tests for SelectControl (#2877) (@ascott)
- [1e7773e](https://github.com/airbnb/superset/commit/1e7773eb169d29607991d0c4619ee930104e18de) Improve visualize modal test coverage (#2811) (@graceguo-supercat)
- [3c89c8c](https://github.com/airbnb/superset/commit/3c89c8cc4613d938e4e8726c30142fa32b1249f7) 0.18.3-alpha.3 (@mistercrunch)
- [74086da](https://github.com/airbnb/superset/commit/74086dae2ba66029917a7ed640cfcfc7555c0520) [bars] fix sort numeric bar on x axis (#2812) (@mistercrunch)
- [66403f1](https://github.com/airbnb/superset/commit/66403f1876bb7bcf31eb8187517c116a2d5b6b96) [explore] viz type selector as modal (#2787) (@mistercrunch)
- [3a4cd3a](https://github.com/airbnb/superset/commit/3a4cd3ae24c811e21fb76c5f9efecb2a6c4a6da2) Applying specified limit in bubble plot (#2815) (@mistercrunch)
- [4ffc1f6](https://github.com/airbnb/superset/commit/4ffc1f613e1d2bf012dc5eb66751fea8c698804e) Fix filter values populating for views (#2816) (@mistercrunch)
- [dfbba84](https://github.com/airbnb/superset/commit/dfbba84400986451f5068354a7626537e7b377a4) 0.18.3-alpha.2 (@mistercrunch)
- [77864e6](https://github.com/airbnb/superset/commit/77864e6cf4d5394a5080300097ae21cf9abfcc53) reduce clientside timeout limit (#2820) (@graceguo-supercat)
- [4d12251](https://github.com/airbnb/superset/commit/4d1225180618b4c4f05c67f6534bf273692a9243) [explore] include ControlHeader as part of Control interface (#2809) (@mistercrunch)
- [0c9f9b6](https://github.com/airbnb/superset/commit/0c9f9b695b921c7e17cec83e1d51e4aa3cae7cc9) [clarity/consistency] rename /explorev2/ -> /explore/ (#2802) (@ascott)
- [a4a2bf7](https://github.com/airbnb/superset/commit/a4a2bf7ae9cc1075a38cc980bb696d6512125f2b) filter_box: allow creatable entries (#2804) (@vavrusa)
- [d0a04cd](https://github.com/airbnb/superset/commit/d0a04cde49c86185b779b16f1da05450b1ccf571) apply redux for VisualizeModal (#2795) (@graceguo-supercat)
- [69685b9](https://github.com/airbnb/superset/commit/69685b9dcc0c26f819ff7edeedc10ee302e97f83) Fixing country maps (#2801) (@mistercrunch)
- [b308a3e](https://github.com/airbnb/superset/commit/b308a3eb4eb08de39edbff6490c06a6bef7b7ce1) Added Country Map : New Visualization tools (#2708) (@ymatagne)
- [bfa40bd](https://github.com/airbnb/superset/commit/bfa40bd360021874628477ee3eb2272033cd2d54) [hotfix] 'filter box from and to date filter #2649' (#2785) (@yamdraco)
- [b0e2904](https://github.com/airbnb/superset/commit/b0e2904c245e5569473f425af2806271e33a5176) Updating permission when refreshing druid datasource (#2655) (@ShengyaoQian)
- [ce506bd](https://github.com/airbnb/superset/commit/ce506bdf65b46f5bccee84ca8254417f63b124a5) Logging a few more actions (#2783) (@mistercrunch)
- [922cc03](https://github.com/airbnb/superset/commit/922cc037bf859c935cc5eacbcd96bf6fa004c5fe) 0.18.3-alpha.1 (@mistercrunch)
- [8252ada](https://github.com/airbnb/superset/commit/8252ada1f9ac94866b91eac55b77ed6d0ef7d02f) [docs] more details on how filters are applied (#2778) (@mistercrunch)
- [a2d2f8b](https://github.com/airbnb/superset/commit/a2d2f8bb8c006adcf80bb2ac18b82f3b486f366a) Enable filter value autocomplete in examples (#2781) (@mistercrunch)
- [7c5f61d](https://github.com/airbnb/superset/commit/7c5f61d6a61e1a3cb6d20d0918610abafa2cd326) Adding some STATSD logging (#2715) (@mistercrunch)
- [841e18a](https://github.com/airbnb/superset/commit/841e18a08c4b375fdff5b846f5d9f108903a3eb6) [sql lab] make database ordering alphabetical in left panel (#2769) (@mistercrunch)
- [dbc7fef](https://github.com/airbnb/superset/commit/dbc7fef7f501cd59ebd99524fa171d9e4dee0325) [sql lab] fix user timestamp is off (#2774) (@mistercrunch)
### 0.18.2 (2017/05/17 06:08 +00:00)
- [cbfe3cb](https://github.com/airbnb/superset/commit/cbfe3cb2dcadedeedfcf55f2bb4883b76a484b36) 0.18.2 (@mistercrunch)
- [5fcd25d](https://github.com/airbnb/superset/commit/5fcd25def15b5990c10b5045cb89b88f94ba8716) 0.18.1-alpha.2 (@mistercrunch)
- [fe3f5f6](https://github.com/airbnb/superset/commit/fe3f5f69ae83eaf96415926661b272ebb460a4e0) [hotfix] 'No numeric types to aggregate' (@mistercrunch)
- [2395fbb](https://github.com/airbnb/superset/commit/2395fbbdaaa5c81c3559f17cedf2e998b8341cb9) Adding `end_result_backend_time` to Query model (#2766) (@mistercrunch)
- [960b26c](https://github.com/airbnb/superset/commit/960b26c7a2cad58b39c359f44c385980434dacb8) Show clear and actionable query timeout error message (#2763) (@graceguo-supercat)
- [28ac350](https://github.com/airbnb/superset/commit/28ac3504d67f76c31aac23435dd15d76e0ca75bf) 0.18.1-alpha.1 (@mistercrunch)
- [ecc00bd](https://github.com/airbnb/superset/commit/ecc00bdd26517a832a35529f0fdd7d17972e6f8f) [explore] a bit less margin in left panel (#2758) (@mistercrunch)
- [e794645](https://github.com/airbnb/superset/commit/e7946451d6aa84229d0c70b7a683b29b784ca1a1) fixed 500 error when export dashboard (#2760) (@eeve)
- [9b34600](https://github.com/airbnb/superset/commit/9b34600c8e8df72ecbdfaaf94bdf023c08e26701) Remove duplicate text (#2761) (@awbush)
- [8846108](https://github.com/airbnb/superset/commit/884610861b8d39da91e4c1bce7934172d2486da9) 0.18.0 (@mistercrunch)
- [d9bd3d6](https://github.com/airbnb/superset/commit/d9bd3d646006d9e6ddf2ccded21d7984935c98c8) fix percentage change viz (#2757) (@yileic)
- [38375be](https://github.com/airbnb/superset/commit/38375be5c3062caf9f6cd4d6ac62109e1940bd10) Fix issues around % signs and Presto (#2755) (@mistercrunch)
- [91d951a](https://github.com/airbnb/superset/commit/91d951ac422a6411e59b2ec195011ea4dc44b0b1) Change hardcoded references to 'User' security model to allow custom class override (#2728) (@RichRadics)
### 0.18.0 (2017/05/12 03:36 +00:00)
- [d79a45f](https://github.com/airbnb/superset/commit/d79a45ff32ff8e2b5d601601507680dccf693bbc) add number format to pivot table (#2703) (@yileic)
- [818251f](https://github.com/airbnb/superset/commit/818251fc8519167be7e0730083c4a694972fb06b) make margin consistent (#2753) (@yileic)
- [75abd1f](https://github.com/airbnb/superset/commit/75abd1f44a12208fd54f28a1185d40ea8639396a) 0.18.0-alpha.4 (@mistercrunch)
- [f55df3b](https://github.com/airbnb/superset/commit/f55df3b18b67ff58d5ad3f6f12f7fe76e9697084) [sql lab] fix responsivity of grid (#2742) (@mistercrunch)
- [5dbfdef](https://github.com/airbnb/superset/commit/5dbfdefae82674d71ab1f1464622d199a6f72f30) [sql lab] fix partitionned table has no partitions (#2740) (@mistercrunch)
- [e558444](https://github.com/airbnb/superset/commit/e5584440cecdb0d9a79ba6c4ff1050e73e774035) Speed up all CRUD list views (#2747) (@mistercrunch)
- [d5e9d5d](https://github.com/airbnb/superset/commit/d5e9d5d045d342ef203c7d7cd480011626b6d7a1) fix auto select override pre-selected value bug (#2745) (@graceguo-supercat)
- [3208a01](https://github.com/airbnb/superset/commit/3208a014ffccfe44238fb5982f6e391a1764d4b3) fix dual line chart margin (#2737) (@yileic)
- [874c12a](https://github.com/airbnb/superset/commit/874c12ad2d99059b2f8f979bdd05e77d8768db03) 0.18.0-alpha.3 (@mistercrunch)
- [22d8075](https://github.com/airbnb/superset/commit/22d8075c536bdfc1790b23381e0017720cc921a5) Making the stop button instantaneous (#2738) (@mistercrunch)
- [baebba1](https://github.com/airbnb/superset/commit/baebba115930e95076e9252987fb4e107ddd23d5) 0.18.0-alpha.2 (@mistercrunch)
- [04748b4](https://github.com/airbnb/superset/commit/04748b4cdac7a4fd53f5d7716a36ceebea7bf7ca) [SQL Lab] fix gamma metadata access (#2702) (@mistercrunch)
- [a471afe](https://github.com/airbnb/superset/commit/a471afe206ca3357ea4c27c1accf94d44c8f5122) [sql lab] improvements to the left panel (#2709) (@mistercrunch)
- [5d0a01d](https://github.com/airbnb/superset/commit/5d0a01d0d099a9d73ba04fded7cad223a10e86f2) Decimal is a valid numeric type (#2720) (@hajdbo)
- [9e1272e](https://github.com/airbnb/superset/commit/9e1272e97c88a3ce2e8dfd15fa819de52e331342) 0.18.0-alpha.1 (@mistercrunch)
- [aeebd88](https://github.com/airbnb/superset/commit/aeebd8840d0551a7cd9a2d43039dcd07648d66ed) [table] fixing CSS glitches on table view (#2725) (@mistercrunch)
- [55d3b01](https://github.com/airbnb/superset/commit/55d3b012e504f788732488bf11a1ec7ef223a821) refactor: recentActions ajax call is redundant (#2722) (@S-YOU)
- [a6e1e18](https://github.com/airbnb/superset/commit/a6e1e18244951b5bf68ac76e897a16713c7d279b) [sql lab] fix CREATE TABLE AS (#2719) (@mistercrunch)
- [46d7a92](https://github.com/airbnb/superset/commit/46d7a925bb4be8891c89ac05d2f447e5ec750bf5) chore: remove unused methods with invalid models.Query usage (#2721) (@S-YOU)
- [5929ab7](https://github.com/airbnb/superset/commit/5929ab76890232068bef5412f23a54226dbed0ec) [dashboard] fix missing datasource issue (#2718) (@mistercrunch)
- [fffb7b5](https://github.com/airbnb/superset/commit/fffb7b500a313b9f9d76eecab18241bf0406c933) [sql lab]Add autoSelect (#2713) (@graceguo-supercat)
- [cb14640](https://github.com/airbnb/superset/commit/cb14640a823c3c6cc768169391779064c2469a68) Removing uneeded results_backends.py (#2717) (@mistercrunch)
- [d65054e](https://github.com/airbnb/superset/commit/d65054e01533f62e63e1c8788da4b97309f65cee) [sql lab] fix csv export where `%` in query (#2711) (@mistercrunch)
- [5d5060e](https://github.com/airbnb/superset/commit/5d5060eca61ad1a7f6baf7adced2985088c82d95) Remove unecessary handling of %% (#2714) (@mistercrunch)
- [9ff3515](https://github.com/airbnb/superset/commit/9ff351532ada993afc4da5060b9a942216e2a74b) Basic integration with ClickHouse (#1844) (@vavrusa)
- [59a6f44](https://github.com/airbnb/superset/commit/59a6f447ec1e92a0319a617ecdf4e22847bf5d95) Fix missing curUserId from SliderAdder.jsx (#2705) (@songyanho)
### 0.17.6 (2017/05/01 20:13 +00:00)
- [1887b5e](https://github.com/airbnb/superset/commit/1887b5e934b17864850a46d6e88a606b594e0634) 0.17.6 (@mistercrunch)
- [33758bf](https://github.com/airbnb/superset/commit/33758bfff59b9cdc86b58973244ef0613942dc80) 0.17.6-alpha.2 (@mistercrunch)
- [2d5beb1](https://github.com/airbnb/superset/commit/2d5beb1f91c304df948e943100cbfb66345a00e9) improve csrf expiration error handling (#2695) (@ascott)
- [5fd0e7d](https://github.com/airbnb/superset/commit/5fd0e7d02828df4bb02b6a3b34779517fe637123) [vis] bar values should match y axis format (#2701) (@justinpark)
- [ef0c4be](https://github.com/airbnb/superset/commit/ef0c4be06743083da5c33ed9ffbda7ff77b43e74) Fix for referring specific svg (#2612) (@songyanho)
- [ac3aba7](https://github.com/airbnb/superset/commit/ac3aba7c7da905f0f4bfeb0f80efb87136812b90) [sql lab] visualization flow: fixing the groupby parameter (#2681) (@mistercrunch)
- [0fdb57a](https://github.com/airbnb/superset/commit/0fdb57a18153912c2675b54a2c4e5e00691c7cc0) fix (#2696) (@yileic)
- [3e7b5df](https://github.com/airbnb/superset/commit/3e7b5df2876d1014a8411a57cdd7bb18ce64e5d9) [explore] fix empty chart when changing viz type (#2698) (@mistercrunch)
- [3cd16cf](https://github.com/airbnb/superset/commit/3cd16cf3680169dd17a542d83c84f94ca8220805) Fix test's warnings (#2697) (@dennybiasiolli)
- [a58adc8](https://github.com/airbnb/superset/commit/a58adc862ec28842a3ac7f9f1f205fa1c173015e) Fix orm query in HiveEngineSpec.handle_cursor (#2699) (@xrmx)
- [7d88f80](https://github.com/airbnb/superset/commit/7d88f80a9b2003f4529560160aea41c444283a9e) hack to dynamically adjust y axis left margin (#2689) (@yileic)
- [09be02f](https://github.com/airbnb/superset/commit/09be02f70a716819332629144160360e21c8920b) fix x axis label (#2691) (@yileic)
- [50fcdd3](https://github.com/airbnb/superset/commit/50fcdd3a34f3efe58435760ba0bf321d6767a3a5) Adding tails.com to inthewild (#2685) (@alanmcruickshank)
- [dee3649](https://github.com/airbnb/superset/commit/dee36491c5e9394c19dfbc00def353675079aa83) Fix js warnings (#2693) (@dennybiasiolli)
- [903612a](https://github.com/airbnb/superset/commit/903612ac6c802dd042417c1fa8a742812a2094bb) fix (#2692) (@yileic)
- [ce70505](https://github.com/airbnb/superset/commit/ce705054fa2e738612d746c8f07b577f6941a6e3) fix a bug in 'getMaxLabelSize' and x axis label not shown problem (#2694) (@yileic)
- [c589616](https://github.com/airbnb/superset/commit/c5896168830c0901a6488817d970a92ce3fa8f9a) [sql lab] Update event handler name (#2680) (@graceguo-supercat)
- [58309f2](https://github.com/airbnb/superset/commit/58309f275f3b9d123f75e795fe3e42b918a66aab) Mark a few more string for translation (#2651) (@xrmx)
- [edf4e4f](https://github.com/airbnb/superset/commit/edf4e4f24e9eeceb44e2b9a98d2cb02d8cefa6dd) Update README.md (#2676) (@aioue)
- [b3e0b5b](https://github.com/airbnb/superset/commit/b3e0b5b586d530cdd1def10ef38550f680f1922b) Specifying cryptography version in install docs (@mistercrunch)
- [6880298](https://github.com/airbnb/superset/commit/68802989bc8a13f76988eed004dc07fa06208733) Pinning cryptography lib to 1.7.2 (@mistercrunch)
- [70887d7](https://github.com/airbnb/superset/commit/70887d72e2a54fdae2ba58ad8f408528bd694878) 0.17.6-alpha.1 (@ascott)
- [1922225](https://github.com/airbnb/superset/commit/192222504292aed3ffa7368f85ca6cdc7f92aab5) Alternate fix for #2665 (#2671) (@mistercrunch)
- [0bdc301](https://github.com/airbnb/superset/commit/0bdc3010d8da0789df99d588105d6e62cc6b3280) [vis] fix pivot table scrolling when using more than 1 groupy col (#2674) (@ascott)
- [1df37e6](https://github.com/airbnb/superset/commit/1df37e6e4d609f7c0751faf2360a98a896e30f46) add option for pulling favourited dashboards by username (#2661) (@robert-digit)
- [e9ed416](https://github.com/airbnb/superset/commit/e9ed4166549cb515f5c15357b2395c3858736df1) fix csrf error on import_dashboards (#2672) (@ascott)
- [03c42b5](https://github.com/airbnb/superset/commit/03c42b5b87429f2a29ce1b5ef8c13e9a004b907e) Showing slices on datasource edit form (#2645) (@mistercrunch)
- [e055e6d](https://github.com/airbnb/superset/commit/e055e6d2c22e6087bf7c906174c08d543617a514) Fixing PropTypes warning messages (#2670) (@mistercrunch)
- [2978082](https://github.com/airbnb/superset/commit/29780821e8797fee3fbcbcfacbe3e0871750d19b) [druid] fixing the having clause in the explore view (#2648) (@mistercrunch)
- [f10ee13](https://github.com/airbnb/superset/commit/f10ee139011767fd72e20ff0fb6a6411f6b06d6b) [druid] fix FilterBox viz gets timestamps as values if granularity != all (#2647) (@mistercrunch)
- [cdfc4a3](https://github.com/airbnb/superset/commit/cdfc4a35b22e8b07bf2b0503214074761143e7d9) Optimizing the standalone view (#2663) (@mistercrunch)
- [eb762c8](https://github.com/airbnb/superset/commit/eb762c8bfec56a0da664c4aa71baf2197f58042f) add the missing right bracket (#2662) (@yileic)
- [83abfef](https://github.com/airbnb/superset/commit/83abfef8304a8fe83248d959136e7421ba0e04a8) superset: fix argument swap for SqliteEngineSpec.get_table_names (#2664) (@xrmx)
- [54137ad](https://github.com/airbnb/superset/commit/54137ad023f046780bd0e2089ccc57620787f118) Updating CHANGELOG (@mistercrunch)
### 0.17.5 (2017/04/21 20:56 +00:00)
- [4be6bfa](https://github.com/airbnb/superset/commit/4be6bfafaa07695cf47a9a27977855ab30ff87e4) 0.17.5 (@mistercrunch)
- [9ba6d48](https://github.com/airbnb/superset/commit/9ba6d489f31d2aba38594dac3cb7d75fbabcdc78) v0.17.5-alpha.10 (#2654) (@ascott)
- [af4bd40](https://github.com/airbnb/superset/commit/af4bd40853d477e5104d62c81931b65be0026b50) fix scrolling on markup vis (#2644) (@ascott)
- [84fa0d1](https://github.com/airbnb/superset/commit/84fa0d19407d7fe662ea1945bb0f9e2570945e50) don't default sort by to first column (#2653) (@ascott)
- [938e13a](https://github.com/airbnb/superset/commit/938e13a4297f13354ae7a989de1268cddbf042c4) [hotfix] Presto's latest_sub_partion rendering fail (@mistercrunch)
- [dc364da](https://github.com/airbnb/superset/commit/dc364daffddd740bd827aaaa71d07c13a928c7ef) [query-search] fix scrolling on query search and pagination styling (#2646) (@ascott)
- [1cadfec](https://github.com/airbnb/superset/commit/1cadfecd4bc0c69e0c66ddce12727fd5607b4364) update core.py label and zh po file (#2642) (@RoganW)
- [5b26667](https://github.com/airbnb/superset/commit/5b26667fd520ccf2b668f9d3f7b1a928a1bdaba7) 0.17.5-alpha.9 (@ascott)
- [899caf9](https://github.com/airbnb/superset/commit/899caf94497e22ab8d6a59f695d3805999ab2e4c) [sql-lab] fix scrolling in left hand panel for table meta data (#2641) (@ascott)
- [e6063f2](https://github.com/airbnb/superset/commit/e6063f2ddf14ae9ddb8c9fd8b5ebd548e272e7d5) 0.17.5-alpha.8 (@mistercrunch)
- [46486f8](https://github.com/airbnb/superset/commit/46486f82d9352cd7d340e4dfb6bd9ba2676b2c6c) Moving the warning message to the navbar (#2640) (@mistercrunch)
- [0089762](https://github.com/airbnb/superset/commit/00897629558a4d89c5a500b173b345d8136f52fa) 0.17.5-alpha.7 (@ascott)
- [2bd60c0](https://github.com/airbnb/superset/commit/2bd60c074716faf57a8a10e3a9c73b9fe500080f) [vis] fix line chart when slice is really narrow (#2620) (@ascott)
- [db6cd21](https://github.com/airbnb/superset/commit/db6cd215040220f3cf3636f1decb25139f395cad) [sqllab] table refactor (#2587) (@ascott)
- [f40499e](https://github.com/airbnb/superset/commit/f40499e550ae3ed7fea534b8c657c2c76d9f6070) [dashboard] improve error handling on dashboard (#2624) (@mistercrunch)
- [67a85b9](https://github.com/airbnb/superset/commit/67a85b9831f86a78fe987037000c8f2e6bd9c7d7) Fix dashboard edit button is disabled (#2634) (@mistercrunch)
- [cb3384b](https://github.com/airbnb/superset/commit/cb3384b3b225c1c392453616ce09c1c9e7136baa) Improving Presto error message in explore/dashboard (#2633) (@mistercrunch)
- [ac51a30](https://github.com/airbnb/superset/commit/ac51a30f98019d8cc593f03cd0f542aa35646889) Remove metrics control non-null validator in Table context (#2635) (@mistercrunch)
- [91fe02c](https://github.com/airbnb/superset/commit/91fe02cdc879097d517cddc06a8492536bc3b6ea) Setting adjust_database_uri for HiveEngineSpec (#2636) (@mistercrunch)
- [76042be](https://github.com/airbnb/superset/commit/76042be7c3b52c5019114b372dc841137205e14f) [hotfix/sqllab] setting up the connection in the try: block (@mistercrunch)
- [d3f55a0](https://github.com/airbnb/superset/commit/d3f55a0905e4aa7a2da22113ea8e25c174903c9f) 0.17.5-alpha.6 (@mistercrunch)
- [efaef8f](https://github.com/airbnb/superset/commit/efaef8fe0924ff39e77edbe8fe5e2ed337adccf3) [hotfix] fix endpoint (@mistercrunch)
- [8757a24](https://github.com/airbnb/superset/commit/8757a24d89e44c13b44b8ae84be9ae12a50b8d48) 0.17.5-alpha.5 (@mistercrunch)
- [63785f0](https://github.com/airbnb/superset/commit/63785f039c0f7763c410c9e7a40a0d477b9bf23a) [hotfix] using UTC for caching timestamps (@mistercrunch)
- [d6689ee](https://github.com/airbnb/superset/commit/d6689ee700102926ebd7efa352cb3c66d970445f) 0.17.5-alpha.4 (@mistercrunch)
- [787daf6](https://github.com/airbnb/superset/commit/787daf6005d325b68a52644804d802a1168cf314) A nice CacheLabel React component (#2628) (@mistercrunch)
- [23aeee5](https://github.com/airbnb/superset/commit/23aeee5a9c1c17ef486fa5106da964acc0b4bd43) Slice level cache_timeout isn't taken into consideration (#2631) (@mistercrunch)
- [70c6cad](https://github.com/airbnb/superset/commit/70c6cad0e3b5e276158c1d9057de40b6fd9498eb) Add UNIX socket option to runserver (#2627) (@scaba)
- [6b1bf3b](https://github.com/airbnb/superset/commit/6b1bf3b395a61a8549b9f3297c8411bb41db52fa) [hotfix] missing explore's main.css (@mistercrunch)
- [f5216f6](https://github.com/airbnb/superset/commit/f5216f60479a2580b5a5532ec8c1b0882905d7a0) Adding owner(s) to dashboard makes them own underlying slices (#2610) (@mistercrunch)
- [15654a3](https://github.com/airbnb/superset/commit/15654a3082f4d6030fafd38d671299670443374e) fix filters on dashboard (#2619) (@ascott)
- [baff0cb](https://github.com/airbnb/superset/commit/baff0cba38554ae28e1024f2114ee5765648c45b) Fix separator widget CSS (#2623) (@mistercrunch)
- [c4ee098](https://github.com/airbnb/superset/commit/c4ee098bb7ffc0db970eba608bfe464ee7b3e60b) 0.17.5-alpha.3 (@mistercrunch)
- [612b8ca](https://github.com/airbnb/superset/commit/612b8ca3d7de9988f22b8f7f81c583ff0b8b19bc) [hotfix] legacy url not handled properly (@mistercrunch)
- [c43a9fd](https://github.com/airbnb/superset/commit/c43a9fd5541d5de0987607d913eddd899ee84e75) [hotfix] fixing the build (@mistercrunch)
- [6c68a21](https://github.com/airbnb/superset/commit/6c68a21e4f77757df57042eedbb91a96b21fa040) [sql lab] fix alt-enter runs out-of-date SQL (#2603) (@mistercrunch)
- [db02b33](https://github.com/airbnb/superset/commit/db02b33e09dd6391fb1d8f777b19d5c16a511abe) [explore] fix query text modal while loading (#2596) (@mistercrunch)
- [2df6baa](https://github.com/airbnb/superset/commit/2df6baa7a741a4bfff786b0f675f1165b122aaac) [sql lab] sorting database names in dropdowns (#2611) (@mistercrunch)
- [fc7bd63](https://github.com/airbnb/superset/commit/fc7bd630399c9f4f936b59be347d5d4d0e516744) [sql lab] fixes issues specific to Sqlite (#2606) (@mistercrunch)
- [959a09c](https://github.com/airbnb/superset/commit/959a09cc92682854e0051fae302bd866d9d5b8b5) [dashboard] fix css padding for markup viz (#2602) (@mistercrunch)
- [366ecef](https://github.com/airbnb/superset/commit/366ecefbaaf4cc31234cc981ebab7eb420efe019) Bumping the JS libs to fix the build (#2616) (@mistercrunch)
- [a2b30f3](https://github.com/airbnb/superset/commit/a2b30f35fcc3c081448d55df31ad445d4d9cec6c) 0.17.5-alpha.2 (@mistercrunch)
- [8bceda8](https://github.com/airbnb/superset/commit/8bceda8134c6e53f62b5b30910ec0eeb02f051e7) [hotfix] fix iframe viz (@mistercrunch)
- [bae1067](https://github.com/airbnb/superset/commit/bae1067015a4b8041d39b806062a45c1950e3f7c) 0.17.5-alpha.1 (@mistercrunch)
- [116dca3](https://github.com/airbnb/superset/commit/116dca3e6fc78ef109f0385b6c4dbf2650dda080) validationErrors is undedfined (@mistercrunch)
- [b448394](https://github.com/airbnb/superset/commit/b448394077eef18b9ed01c9703c58cf05f14d34c) 0.17.5-alpha.0 (@mistercrunch)
- [09f407f](https://github.com/airbnb/superset/commit/09f407f553eb8bca90f6da79cf60c742296488c7) add tooltips to big number vis (#2599) (@ascott)
- [0479118](https://github.com/airbnb/superset/commit/0479118efc0069da2b62c20dc631ebbd00493758) 0.17.5.dev0 (@mistercrunch)
- [c93411b](https://github.com/airbnb/superset/commit/c93411b1e75bc59b174d1256f3108d783eea3264) Fix for merge string as array (#2597) (@songyanho)
- [31283f1](https://github.com/airbnb/superset/commit/31283f14246ccd9f7cac4df238bd65cdd68c840b) Fix metric formating in Dashboard view + some refactoring (#2598) (@mistercrunch)
- [93c6597](https://github.com/airbnb/superset/commit/93c6597cf4f2c47869f9cf4f62cfdbe80cd37760) Adding docs/ to .eslintignore (@mistercrunch)
- [4446c74](https://github.com/airbnb/superset/commit/4446c745a88d4b31de8a30ccb45b4cd6dcc49dae) Fix backend-sync 2 (@mistercrunch)
- [38e90fe](https://github.com/airbnb/superset/commit/38e90fe3098018655a8e2206a053c60c8d7e64aa) Fix backend-sync (@mistercrunch)
- [f548946](https://github.com/airbnb/superset/commit/f5489467e534f3625079843cad82ae12de6cbf3a) Use correct dialect for escaping functions. (#2593) (@steveniemitz)
- [ab0bc5a](https://github.com/airbnb/superset/commit/ab0bc5a3fa81f3951b50828400de6bfc54235860) handle percentage case for tooltips (#2570) (@ascott)
- [412634c](https://github.com/airbnb/superset/commit/412634cb57f6094bbba168d252b8b5dbd56da954) Add missing flask-wtf dependency (#2586) (@xrmx)
- [a0ddbb9](https://github.com/airbnb/superset/commit/a0ddbb9ec954a65a77350bc054b83e35bc4dffeb) Make form_data dict a macro (#2585) (@mistercrunch)
- [a803705](https://github.com/airbnb/superset/commit/a803705ddca1c869ccd50977d033faa5c8a0464b) [bugfix] clarifying how to create a slice (#2565) (@mistercrunch)
- [75a358c](https://github.com/airbnb/superset/commit/75a358c616bf30e15dc752da59de1c3cd333a89c) [explore] force control validation before runQuery (#2544) (@mistercrunch)
- [493ba18](https://github.com/airbnb/superset/commit/493ba1836241146502d5dfa4d5c9ea57f4d8ad6c) Adding macros current_user_id & current_username (#2582) (@mistercrunch)
- [5e4fca4](https://github.com/airbnb/superset/commit/5e4fca4ea4b896a9c427032f30a30b59efbcec63) Bumping a set of Python libraries to the latest release (#2575) (@mistercrunch)
- [d289783](https://github.com/airbnb/superset/commit/d289783b67ef6503f58a330bcd729e3301c20393) Adding Vertica to installation docs (#2581) (@mistercrunch)
- [ac84fc2](https://github.com/airbnb/superset/commit/ac84fc2b65976c1473488e2ed47979d880df34f5) Fixing confusion when selecting schema across engines (#2572) (@mistercrunch)
- [40b3d3b](https://github.com/airbnb/superset/commit/40b3d3b3ef61124ec38d49130b57d751b98a1a67) [hotfix] add csrf_token api endpoint (@mistercrunch)
- [50a9e13](https://github.com/airbnb/superset/commit/50a9e13f9bb9bf0c10ca3b748e1989a15c97ee8a) [hotfix] add csrf_token api endpoint (@mistercrunch)
- [66bff01](https://github.com/airbnb/superset/commit/66bff01b45658547d9853e0d0c22e788550d8267) Changelog for 0.17.4 (@mistercrunch)
- [9691234](https://github.com/airbnb/superset/commit/9691234b7e33d8162253a6971f6eb298c037b748) [hotfix] casting db_id to int (@mistercrunch)
### 0.17.4 (2017/04/07 17:57 +00:00)
- [ddeabdd](https://github.com/airbnb/superset/commit/ddeabdd048296732c24bb7ca2317e5d3683fa17d) Fixing CSRF issues (#2569) (@mistercrunch)
- [3ed45ab](https://github.com/airbnb/superset/commit/3ed45ab98c2e351ced9520bcfce222dd34d45a94) [bugfix] saved query restore wouldn't pick the db (#2568) (@mistercrunch)
- [ca08e70](https://github.com/airbnb/superset/commit/ca08e7051ef47b5a8693dc5088c3af23b582b267) 0.17.4rc5 (@mistercrunch)
- [1fb21b8](https://github.com/airbnb/superset/commit/1fb21b8b45f1bdde4e89386ec791cfa7620986c4) [revert] reverting big num changes (#2567) (@ascott)
- [c581ea8](https://github.com/airbnb/superset/commit/c581ea8661dd229f42d5959a6745a26ce8f03289) Alternative PR for: Some bytes/str issues in py3 w/ zlib and json (#2558) (@rumbin)
- [f19d195](https://github.com/airbnb/superset/commit/f19d1958c51212ae19cef98cfa135b8898cb6bea) INTHEWILD: added Endress+Hauser (#2562) (@rumbin)
- [9c99be5](https://github.com/airbnb/superset/commit/9c99be510b3aaeb4329e1a33413129e1aaacebdd) [hotfix] iframe viz is broken (@mistercrunch)
- [7a08cdc](https://github.com/airbnb/superset/commit/7a08cdcb1c4378986cb1dc6934143a684ab49502) 0.17.4rc3 (@ascott)
- [f24ddfd](https://github.com/airbnb/superset/commit/f24ddfd467731d82206b5cf8185ac0c576a8a6f9) [big num] make sure scatterplot dots align properly (#2559) (@ascott)
- [23a8ea5](https://github.com/airbnb/superset/commit/23a8ea563693bd10635e0dadd97403958ca9d891) v0.17.4rc2 (@mistercrunch)
- [2c04d3c](https://github.com/airbnb/superset/commit/2c04d3c25098d15c9cc6f30a70f787eae6899c74) [bugfix] save dash fails with CSRF related error (#2552) (@mistercrunch)
- [337454b](https://github.com/airbnb/superset/commit/337454b646c71bc313714b849dec49b4d7459a4d) [hotfix] slice with missing datasource related (@mistercrunch)
- [b7f46eb](https://github.com/airbnb/superset/commit/b7f46ebe75b23d5a26d3c17fd219e1b05bff9327) [hotfix] dashboard fails when a slice is missing its datasource (@mistercrunch)
- [10773f9](https://github.com/airbnb/superset/commit/10773f96a7197197bcd5d6dea6d959c081d23156) URL Params macro (#2537) (@mistercrunch)
- [b97a827](https://github.com/airbnb/superset/commit/b97a8275d48c40beaff0a6fb7cf56257dd281fe6) Clarify docs on Redis package required for caching (#2557) (@rhunwicks)
- [081bdca](https://github.com/airbnb/superset/commit/081bdca71ef4979b77e0b7d5f9ab5cc8e6cde2dc) [hotfix] [sql lab] fix sqlite errors when schema is selected (@mistercrunch)
- [62959ca](https://github.com/airbnb/superset/commit/62959ca38ba31e5cc5a1d2b7b9e16bf981f97300) Db2 Grain Correct Data Format (#2545) (@openmax)
- [fe68bc3](https://github.com/airbnb/superset/commit/fe68bc31c334e553131cf6e7f6da7d2359646f6c) Revert "measure x axis labels too and use the longest to determine margins" (#2550) (@ascott)
- [3d2c791](https://github.com/airbnb/superset/commit/3d2c791ff1279af4c19108ee220a37c24710686a) [bug num vis] fix sizing for single digits (#2548) (@ascott)
- [d40ce52](https://github.com/airbnb/superset/commit/d40ce52139d7b9319ac8b86e5d77d3f4998b5926) v0.17.4rc1 (@mistercrunch)
- [122891c](https://github.com/airbnb/superset/commit/122891c29b8f46c986207d4903ae584b0ac44a8e) [sql lab] allow users to save their queries (#2528) (@mistercrunch)
- [c1d9918](https://github.com/airbnb/superset/commit/c1d9918abeb3bca33721fc5af9be59e89fffccbf) [vis] bug num improvements (#2523) (@ascott)
- [d93b1fc](https://github.com/airbnb/superset/commit/d93b1fc686e0f0518b04203e1b65967f6b84d441) [sql-lab] make query history scrollable (#2499) (@ascott)
- [02c5cac](https://github.com/airbnb/superset/commit/02c5cac26f853634bb0d244579c366abcb400c77) [hotfix] adding filterable to DruidColumnInlineView.edit_columns (@mistercrunch)
- [6566377](https://github.com/airbnb/superset/commit/65663777402c6770b796ca3f65fedc59d756da82) [sql lab] fix table dropdown with large schema make UI unresponsive (#2547) (@mistercrunch)
- [db6b2f3](https://github.com/airbnb/superset/commit/db6b2f3ae19740c86a537f031ec7fbee4b341742) pylint errors will now break the build (#2543) (@mistercrunch)
- [c31210b](https://github.com/airbnb/superset/commit/c31210b96d0b3aa96e826657da6e6754c87e59a0) Some column description clarifications (#2536) (@mistercrunch)
- [dcc6f2a](https://github.com/airbnb/superset/commit/dcc6f2a18fadda5ddb1a507508cf049b9932f02b) serve roboto font locally (#2519) (@ascott)
- [0c0666c](https://github.com/airbnb/superset/commit/0c0666caa0e7ce5725416115389fe797113b731c) druid: use six.string_types instead of basestring (#2541) (@xrmx)
- [243eead](https://github.com/airbnb/superset/commit/243eeadfd6994c6ac93467a334601fb23278888f) installation instructions for AWS Athena (#2538) (@dwa)
- [9ba5b49](https://github.com/airbnb/superset/commit/9ba5b49d8ac197a5ba908b229bd9061ce98c5fca) WIP: Initial commit to support the athena DB (#2531) (@dwa)
### 0.17.3 (2017/04/01 23:55 +00:00)
- [c870bd4](https://github.com/airbnb/superset/commit/c870bd414ecafe9b83f223dd5894544dc34fba0f) 0.17.3 (@mistercrunch)
- [4b01e92](https://github.com/airbnb/superset/commit/4b01e92509a903bcc25260749ee055be12e5949c) [dashboard] allow bar charts to scroll on x axis (#2513) (@mistercrunch)
- [513a090](https://github.com/airbnb/superset/commit/513a090cdc852c287c62c5d4dc47d6168974251c) [sql lab] address lingering spinner in schema select (#2512) (@mistercrunch)
- [abe79d1](https://github.com/airbnb/superset/commit/abe79d1427b94fb126bd3aec5511bbe2c905d94c) Deprecate is_featured as a datasource attribute (#2485) (@mistercrunch)
- [b81968d](https://github.com/airbnb/superset/commit/b81968dc2051d0cef22da1633fb315f076c7d94c) Redirect to explore view when saving a table (#2479) (@mistercrunch)
- [66cc546](https://github.com/airbnb/superset/commit/66cc546a300d63dd06385a0c35e2268453f70fd6) [hotfix] fixing the (one js lint err in the) build (@mistercrunch)
- [6e899ac](https://github.com/airbnb/superset/commit/6e899ac55f5fe093a9081c4001e28b945bc0f8f6) added tobii (#2526) (@dwa)
- [6b52384](https://github.com/airbnb/superset/commit/6b523840244ae55519641e621d83aac1342fc51b) [hotfix] fix pending queries race condition (@mistercrunch)
- [0a1d8db](https://github.com/airbnb/superset/commit/0a1d8db35783d37c2eea774f2e0c3b4d6a064fde) v0.17.3rc3 (@mistercrunch)
- [6f68ddb](https://github.com/airbnb/superset/commit/6f68ddb50516efff1525f6aebc28f67dbad8c38b) Adding to polling states (@mistercrunch)
- [4f59abf](https://github.com/airbnb/superset/commit/4f59abf189f9b34654854607c5f943e774fbccbe) v0.17.3rc2 (@mistercrunch)
- [5c441f4](https://github.com/airbnb/superset/commit/5c441f4ddb84a58080b577497760ea65b7537f53) [hotfix] queries trigger polling (#2517) (@mistercrunch)
- [be023ab](https://github.com/airbnb/superset/commit/be023aba8d972ad5460beed07f768f10398621c4) 0.17.3rc1 (@mistercrunch)
- [5399020](https://github.com/airbnb/superset/commit/53990201bc97ad2035e10500eda0d4c935f3bfb1) forgotten query_datasources_by_name function (#2497) (@wyndhblb)
- [37783d6](https://github.com/airbnb/superset/commit/37783d685fe7c7b52a3188ace5f1504f8e2eb367) Updating CHANGELOG (@mistercrunch)
### 0.17.2 (2017/03/29 14:46 +00:00)
- [25fdcac](https://github.com/airbnb/superset/commit/25fdcaca8b1dd32c05ecd7fd57d4cf32e8f72447) v0.17.2 (@mistercrunch)
- [ce6e7c1](https://github.com/airbnb/superset/commit/ce6e7c135943130fb104b10d284aca93e4f2d4dc) [hotfix] missing logging import in db_engined_specs (@mistercrunch)
- [9116766](https://github.com/airbnb/superset/commit/91167665b19ca094fd51dadf9538c12fd61bc55a) Track both query start time and button push time to track delay (#2502) (@mistercrunch)
- [f374345](https://github.com/airbnb/superset/commit/f374345860f4a3e307ea1772b099ce038868aa71) Adding a .pylintrc file and a bit of linting (#2507) (@mistercrunch)
- [d3b50cb](https://github.com/airbnb/superset/commit/d3b50cb92e046de316725f0e02fdd816573b6234) [explore] remove grey background in standalone mode (#2503) (@mistercrunch)
- [a58194b](https://github.com/airbnb/superset/commit/a58194bdb06a966c6d642bdfa7c80195cc9cf996) 0.17.2rc4 (@mistercrunch)
- [5f3484a](https://github.com/airbnb/superset/commit/5f3484ac5981f2dd6aa447cbc2e4f2a9afe4518c) Handle errors when the MQ is down (#2494) (@mistercrunch)
- [e14b74f](https://github.com/airbnb/superset/commit/e14b74fdbf876aeeeea7baa125926dcf719a0098) [explore] fixing bugs in controls (#2496) (@mistercrunch)
- [56f2885](https://github.com/airbnb/superset/commit/56f28859b7315767d3e312aa3777b777156a658d) Fixing filter_box css padding (#2498) (@mistercrunch)
- [f3cdb3b](https://github.com/airbnb/superset/commit/f3cdb3b787b804071a8f183569027169e432cb46) Add ibm_db_sa TimeStamp and Datatime Grain Spec. (#2500) (@openmax)
- [b35f6b0](https://github.com/airbnb/superset/commit/b35f6b0a94df2017b3bd908c6589d786f9feec6b) 0.17.2rc3 (@mistercrunch)
- [5574cfe](https://github.com/airbnb/superset/commit/5574cfef59a7326487f7cebf9a575070ab19a3c2) Fixing out-of-sync security (#2493) (@mistercrunch)
- [c301558](https://github.com/airbnb/superset/commit/c3015583ce09f259bf36e018ebd0820f9c0cb92f) Stabilizing master (#2478) (@mistercrunch)
- [7cc2c93](https://github.com/airbnb/superset/commit/7cc2c930ede176242cc9066baf16c00fccd23e01) [docs] adding notes on the Public role (#2486) (@mistercrunch)
- [2662bf1](https://github.com/airbnb/superset/commit/2662bf19df690c0a133318406a6935a27245a82f) v0.17.2rc2 (@mistercrunch)
- [62e3fe2](https://github.com/airbnb/superset/commit/62e3fe2345afa6fa8b8cb0da7f20bb44b48a0e54) [hotfix] SqlaTable has no attribute column_cls (@mistercrunch)
- [7d25d17](https://github.com/airbnb/superset/commit/7d25d171e29dce859c455a03d33cc4c3054775c7) [release] update to 0.17.2rc1 (#2492) (@ascott)
- [c5859c7](https://github.com/airbnb/superset/commit/c5859c7254448d2cdbc877b00580bd95fa6e7e2e) [hotfix] druid queries 'There was no query executed' issue (@mistercrunch)
- [dd7b4b8](https://github.com/airbnb/superset/commit/dd7b4b8310e27310f80a0415a2bd877f3098127b) Revert "[sql-lab] revert react-virtualized-select (#2489)" (#2491) (@ascott)
- [93551a6](https://github.com/airbnb/superset/commit/93551a65b845f430aaefaa50f75a40857f1b6ed6) only fetch tables if we have a schema, otherwise reset options. (#2490) (@ascott)
- [7eafbab](https://github.com/airbnb/superset/commit/7eafbabe65b430a2347ed6bba3e08d1f4d8c1946) [sql-lab] revert react-virtualized-select (#2489) (@ascott)
- [43dd948](https://github.com/airbnb/superset/commit/43dd948476190cf75649164f57a9b73687ca3d99) [sql-lab] performance updates - make ui more responsive (#2469) (@ascott)
- [75e7f2d](https://github.com/airbnb/superset/commit/75e7f2d22c7185f2a6393b7ded7a2b5d0558cfca) [hotfix] bumping QUERY_UPDATE_FREQ from 1000 to 2000ms (@mistercrunch)
- [26662ee](https://github.com/airbnb/superset/commit/26662eed9e08475a4c41d715f5a3608876a1afd1) Fixed CSS syntax for background linear-gradient (#2482) (@songyanho)
- [121b1d0](https://github.com/airbnb/superset/commit/121b1d0951049ac30ab35a225269c03451d14bbc) Refactoring more in the connector base classes (#2431) (@mistercrunch)
- [398036d](https://github.com/airbnb/superset/commit/398036d77e0c817796e3735797eae56e80dce437) [hotfix] 'NoneType' object has no attribute 'upper' (@mistercrunch)
- [59d5fcf](https://github.com/airbnb/superset/commit/59d5fcf88c0f484a189c7f02968982b2d39512ad) [hotfix] fixing checkboxes in Tables->Columns (@mistercrunch)
- [1f8e48b](https://github.com/airbnb/superset/commit/1f8e48b374adb091bf41e60634e99b132a37bf62) [sqllab] assign types for visualize flow (#2458) (@mistercrunch)
- [7bf19b1](https://github.com/airbnb/superset/commit/7bf19b12327504aec2be2c2fd36404efb1faec05) [WiP] making doubling '%' not required (#2459) (@mistercrunch)
- [1590b8c](https://github.com/airbnb/superset/commit/1590b8c7e5a070183a3c0e3153aec63992e3c60f) Speeding up polling by not checking access (#2466) (@mistercrunch)
- [22522fc](https://github.com/airbnb/superset/commit/22522fc05f8859b94299576f4a8b07bd42eaba00) [sql-lab] improve table select performance (#2457) (@ascott)
- [c9b59fa](https://github.com/airbnb/superset/commit/c9b59fab1fded7fa08fc9d44a8c18427865e11dd) Update INTHEWILD.md (#2455) (@jakubczaplicki)
- [69152e0](https://github.com/airbnb/superset/commit/69152e087a2b9900e53a8a97ed318c271f725649) [explore] remove 'SQL Clauses' section when using Druid (#2449) (@mistercrunch)
- [652e572](https://github.com/airbnb/superset/commit/652e572b56054cefdfe83403db2125a9d85f5a6a) [sql-lab] make results table scroll in static container (#2426) (@ascott)
- [65c89f5](https://github.com/airbnb/superset/commit/65c89f54dc845ee4f77b230fb334eba225bb14e6) [hotfix] merging db migration scripts (#2448) (@mistercrunch)
- [edf5c0e](https://github.com/airbnb/superset/commit/edf5c0e83b2c9023ef3ad9cabc7ce19d32551d84) [dist_bar] fix x scroll when overflowing (#2440) (@mistercrunch)
- [a4abbfe](https://github.com/airbnb/superset/commit/a4abbfe1266ab1593ba82ef23d6e733b3d8e8e33) Fix formatting in README.md (#2441) (@imagejan)
- [7b28bce](https://github.com/airbnb/superset/commit/7b28bcef15cd37284780e605f5076ede2864afb4) Fix documentation for adding a Redshift database (#2447) (@alexdebrie)
- [8042ac8](https://github.com/airbnb/superset/commit/8042ac876e80c08d72489287777cb1e9672b177a) [explore] improved filters (#2330) (@mistercrunch)
- [82bc907](https://github.com/airbnb/superset/commit/82bc907088be26206a7c8d1c84311ca399eb73b9) fix a bug in pie chart (#2423) (@yileic)
- [e2b572d](https://github.com/airbnb/superset/commit/e2b572d9e229fee503128cab8d7bff02a9cd881b) Prevent alarming users with stacktrace when using sqlite (@mistercrunch)
- [e71596d](https://github.com/airbnb/superset/commit/e71596dc45f96e6a3fdfb09f5ee6b00a9cdd018c) make dualline thumbnail have consistent size as other thumbnails (#2434) (@yileic)
- [c3be58d](https://github.com/airbnb/superset/commit/c3be58db437746c168e706e8a5710e2a1d363d0d) Add verbose name to db and druid cluster (#2429) (@bkyryliuk)
- [3d77a12](https://github.com/airbnb/superset/commit/3d77a12aa9b27375a02f7092c16a2883bd171913) Display the first partition. (#2425) (@bkyryliuk)
- [36deb8d](https://github.com/airbnb/superset/commit/36deb8da7157893d08d01148ab538f963aacf140) Allow users to alter column types (#2424) (@mistercrunch)
### 0.17.1 (2017/03/16 15:44 +00:00)
- [05ee8c0](https://github.com/airbnb/superset/commit/05ee8c0e3675b0ab88cf85d061971177ed871144) v0.17.1 (@mistercrunch)
- [5ca55a5](https://github.com/airbnb/superset/commit/5ca55a55858a59f6340284cfe4081fd26d2be489) [hotfix on dist_bar] bringing back overwritten handling of ints and tuples (@mistercrunch)
- [1b330a8](https://github.com/airbnb/superset/commit/1b330a8c55f0ec114970855c6682627434216ab5) Use connector registry for metrics (#2420) (@bkyryliuk)
- [696678c](https://github.com/airbnb/superset/commit/696678c9816d934c10243395e12e8483e2a42df0) Replace query once query response returned (#2415) (@vera-liu)
- [20aec3c](https://github.com/airbnb/superset/commit/20aec3cfcad082f921cdfc347382b2d0d4a4fd54) Use connector registry to fetch the table column class. (#2419) (@bkyryliuk)
- [4ded37e](https://github.com/airbnb/superset/commit/4ded37e71edf1306ee9652cf32324b5dc14a1328) [hotfix] handle missing or empty column type (@mistercrunch)
- [0674ed8](https://github.com/airbnb/superset/commit/0674ed846c478bd97e7b6953c375b5a3ab26802b) Use list instead of numpy array (#2412) (@bkyryliuk)
- [3107152](https://github.com/airbnb/superset/commit/3107152f5bb973b8b1a9c5cf3bc2bee1d7f47e0d) Revert "Preprocess the where clauses." (#2411) (@bkyryliuk)
- [5b19528](https://github.com/airbnb/superset/commit/5b19528662f5ea645ead5b066144d69469ecbf7c) Display full name. (#2378) (@bkyryliuk)
- [357773c](https://github.com/airbnb/superset/commit/357773c631ad39260109ec45d9c313d6d45cea89) Preprocess the where clauses. (#2405) (@bkyryliuk)
- [5e43d07](https://github.com/airbnb/superset/commit/5e43d074c3be9b909fe0b473f00e743b4ba9d56e) [explore ] templating can now reference query elements (#2388) (@mistercrunch)
- [08bdcd5](https://github.com/airbnb/superset/commit/08bdcd52b856f5ed6c91ab9c3805cf259223da0a) Fix bad d3.format metric setting and/or value === Infinity (#2399) (@mistercrunch)
- [0b8522b](https://github.com/airbnb/superset/commit/0b8522be502988d8b9bf64a8ea203068adf5957d) [filter_box] fix time filter and inverted instantFilter (#2402) (@mistercrunch)
- [c02a7fe](https://github.com/airbnb/superset/commit/c02a7fe7634b311c6f42968a9f7bafef147092e1) Add more tests to Save Modal specs (#2313) (@vera-liu)
- [dcd5bde](https://github.com/airbnb/superset/commit/dcd5bdeb00b8333d67234bde79397b5195fbb8f9) fix unicode issues (#2308 #2282) (#2401) (@asdf2014)
- [562b4f0](https://github.com/airbnb/superset/commit/562b4f04156f20a2aeec8d2c27758571d0aaa514) Do not silence error message for query. (#2396) (@bkyryliuk)
- [6160a3f](https://github.com/airbnb/superset/commit/6160a3fdffdcebe618191462633414c0dff7de30) Implement stop query functionality. (#2387) (@bkyryliuk)
- [0779da6](https://github.com/airbnb/superset/commit/0779da6d244c5686f6ab5faf2ef5067019266460) Keep column order in .csv (#2377) (@bkyryliuk)
- [740624b](https://github.com/airbnb/superset/commit/740624ba01edcfa305d6ff0a2676d413e59377f4) Fix monthly time grain in sqllite (#2380) (@bkyryliuk)
- [2969cc9](https://github.com/airbnb/superset/commit/2969cc9993325acc730b794f9d0d0b07fe1a60ec) Refactoring Druid & SQLa into a proper "Connector" interface (#2362) (@mistercrunch)
- [9a8c3a0](https://github.com/airbnb/superset/commit/9a8c3a044710bcfc578b77b498af1f15685f2bca) [table] metric ordering is wrong in some cases (#2373) (@mistercrunch)
- [e817382](https://github.com/airbnb/superset/commit/e817382efd5f18e09534a70f95cc47332288356e) Add more tests to Filter spec (#2315) (@vera-liu)
- [422d1fe](https://github.com/airbnb/superset/commit/422d1feb3e2ac964b77cd9756ed0fc1293056373) 0.17.1rc2 (@mistercrunch)
- [2b0cb2b](https://github.com/airbnb/superset/commit/2b0cb2b0a56d033cd59c13ae3326c3d922d53b22) Fix partition query (#2353) (@bkyryliuk)
- [705d09d](https://github.com/airbnb/superset/commit/705d09d3d07e05e3a5e070d25a348a45288d7721) Remove duplicate (#2351) (@bkyryliuk)
- [9114d86](https://github.com/airbnb/superset/commit/9114d86ecd0dbd0ad20c24d56f1fd29604de2047) Add hive to superset + monkey patch the pyhive (#2134) (@bkyryliuk)
- [ad4a950](https://github.com/airbnb/superset/commit/ad4a950b5646abef823357274298014292af091f) Fixes filters emitted from table viz (#2335) (@mistercrunch)
- [bd480e0](https://github.com/airbnb/superset/commit/bd480e0c6b9d5127d96d8c18fd53ebe6193aec70) Fix duplicate property DruidDatasource.database (#2348) (@mistercrunch)
- [af3415b](https://github.com/airbnb/superset/commit/af3415b0406399ba41c1592d31440b722556f90b) [filter_box] option to delay filtering with apply button (#2338) (@mistercrunch)
- [b4a96bd](https://github.com/airbnb/superset/commit/b4a96bd8409d38462ea14d2fbafa2f975defd741) Fix for RuntimeError: dictionary changed size during iteration (#2320) (@moranrf)
- [9d8d421](https://github.com/airbnb/superset/commit/9d8d4213840cc07d95ac971437632aeae4ff58b5) [hotfix] fix world map (@mistercrunch)
- [f6ffc00](https://github.com/airbnb/superset/commit/f6ffc007481c658293ae209982dfc369c0f82f95) Allow running Flask Blueprints alongside Superset (#2337) (@mistercrunch)
- [e35016f](https://github.com/airbnb/superset/commit/e35016f07d3880cd557f48c72c617e68d61b125d) remove unneeded tooltip/description text (#2303) (@ascott)
- [af8e252](https://github.com/airbnb/superset/commit/af8e2523a820080e06aaf3e50ff309ada1422bc6) fix version (#2336) (@ascott)
- [492df94](https://github.com/airbnb/superset/commit/492df94b2a3a4767af40edfb5ab074632baaea4d) [sqllab] reserved words should be upper case (#2316) (@mistercrunch)
- [b62f7e2](https://github.com/airbnb/superset/commit/b62f7e2820fc140a318b69b84c54a8a5fe965ed0) [version] use rc for production only releases (#2334) (@ascott)
- [5cc2fc1](https://github.com/airbnb/superset/commit/5cc2fc157cfc2dc14363424533eade67e1f48543) v0.17.1 (#2333) (@ascott)
- [266c049](https://github.com/airbnb/superset/commit/266c049f2df96bb7c815c91dd9f650fbefd6b513) Fix bug with breakdown (#2312) (@vera-liu)
- [4e848c8](https://github.com/airbnb/superset/commit/4e848c8cb55be50c5c5ddbb58694b20d4223a2a3) Updating CHANGELOG (@mistercrunch)
- [efff1ac](https://github.com/airbnb/superset/commit/efff1ac4a1d2b26ef932ac6937be0456fb901c09) Temp hack to make druid filters work in dashboard (#2300) (@vera-liu)
- [fc64a75](https://github.com/airbnb/superset/commit/fc64a75fbd5a3aed7a62b741a9a057acb2b9dca8) v0.17.0 (#2298) (@vera-liu)
### 0.17.0 (2017/02/28 19:47 +00:00)
0.17.0 introduces major changes that **YOU CANNOT ROLLBACK FROM**, take
a backup of your app's database before starting the upgrade progress.
@@ -228,7 +552,6 @@ translations
- [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)
@@ -263,10 +586,8 @@ translations
- [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)
@@ -286,8 +607,6 @@ translations
- [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)
@@ -396,7 +715,6 @@ translations
- [a475551](https://github.com/airbnb/superset/commit/a475551b23d5830ab2945f615328f31d48df36ca) [sqllab] bind alt+enter shortcut in AceEditor to run a query (#1554) (@mistercrunch)
- [bad7676](https://github.com/airbnb/superset/commit/bad7676414662b28a4b72eb680fbf42a5c6281a5) Bump cryptography dependency to 1.5.3 (#1569) (@xrmx)
### airbnb_prod.0.12.0.1 (2016/11/08 23:55 +00:00)
- [51c0470](https://github.com/airbnb/superset/commit/51c0470f0be438312e90f2efb1f2e37291a30ce4) [explore v2] populate dynamic select field options (#1543) (@ascott)
- [4530047](https://github.com/airbnb/superset/commit/4530047c769ba6d5953ef1547b8507c62f657942) Added action buttons to Chart Container of explore V2 (#1562) (@vera-liu)
- [1bf83c3](https://github.com/airbnb/superset/commit/1bf83c3bf78de422df1b21c3049d106b1cb29385) [explore-v2] render columns based on length of fieldSets array (#1559) (@ascott)
@@ -631,7 +949,6 @@ translations
- [508feb2](https://github.com/airbnb/caravel/commit/508feb2bad581068a47193e06d35ed5926b25191) [hotfix] getting presto on track
- [9f8eef4](https://github.com/airbnb/caravel/commit/9f8eef498c93ce2945e3d17cda1c3d2a24d05c92) [theme] a little bit less blue (#1024) (@mistercrunch)
### airbnb_prod.0.10.0.2 (2016/08/30 18:08 +00:00)
- [561828c](https://github.com/airbnb/caravel/commit/561828c2f890e179c4116f773cc7507103804dc6) [SQL Lab] moving the db/schema/table select to the left (#1038) (@mistercrunch)
- [fc1e637](https://github.com/airbnb/caravel/commit/fc1e63761cc86a5673433b0e2efc0081325684d3) Adding celery_tests.py (@mistercrunch)
- [38b8db8](https://github.com/airbnb/caravel/commit/38b8db8051490375d021c51fb76a9f1bdc3ccf1f) SQL Lab - A multi-tab SQL editor (#514) (@mistercrunch)
@@ -654,7 +971,6 @@ translations
- [c7467f5](https://github.com/airbnb/caravel/commit/c7467f544c10281538b8504add9510f3fcf20e60) Documenting making your own build (#990) (@mistercrunch)
- [30ef8eb](https://github.com/airbnb/caravel/commit/30ef8eba37073c2cb0d2a6356fe3a9bcabab4913) [ui] hack bootswatch/cosmo theme to get better tabs for sql-lab (and other things) (#975) (@ascott)
### airbnb_prod.0.10.0.1 (2016/08/18 07:02 +00:00)
- [23a5463](https://github.com/airbnb/caravel/commit/23a54632081aa589e05a820ac774c8b4f6993252) Hack around the "last migration doesn't stamp" Alembic bug (#967) (@mistercrunch)
- [84213ab](https://github.com/airbnb/caravel/commit/84213ab8cd27369f796131c68c075f12f8bd0ce2) [line] growth vs factor option for 'Period Ratio' (#970) (@mistercrunch)
- [379cf6c](https://github.com/airbnb/caravel/commit/379cf6cbd9527617b6aae60501ad2fa4e252bb8f) [ui] tweaks and improvements (#965) (@ascott)

View File

@@ -18,6 +18,9 @@ If you are reporting a bug, please include:
troubleshooting.
- Detailed steps to reproduce the bug.
When you post python stack traces please quote them using
[markdown blocks](https://help.github.com/articles/creating-and-highlighting-code-blocks/).
### Fix Bugs
Look through the GitHub issues for bugs. Anything tagged with "bug" is
@@ -26,7 +29,7 @@ open to whoever wants to implement it.
### Implement Features
Look through the GitHub issues for features. Anything tagged with
"feature" is open to whoever wants to implement it.
"feature" or "starter_task" is open to whoever wants to implement it.
### Documentation
@@ -47,9 +50,27 @@ If you are proposing a feature:
- Remember that this is a volunteer-driven project, and that
contributions are welcome :)
## Pull Request Guidelines
Before you submit a pull request from your forked repo, check that it
meets these guidelines:
1. The pull request should include tests, either as doctests,
unit tests, or both.
2. If the pull request adds functionality, the docs should be updated
as part of the same PR. Doc string are often sufficient, make
sure to follow the sphinx compatible standards.
3. The pull request should work for Python 2.7, and ideally python 3.4+.
``from __future__ import`` will be required in every `.py` file soon.
4. Code will be reviewed by re running the unittests, flake8 and syntax
should be as rigorous as the core Python project.
5. Please rebase and resolve all conflicts before submitting.
6. If you are asked to update your pull request with some changes there's
no need to create a new one. Push your changes to the same branch.
## Documentation
The latest documentation and tutorial are available [here](http://airbnb.io/superset).
The latest documentation and tutorial are available [here](https://superset.incubator.apache.org/).
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
@@ -123,7 +144,7 @@ referenced in the rst, e.g.
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
When the docs are being pushed to [Apache Superset (incubating)](https://superset.incubator.apache.org/), images
will be moved from there to the _\_static/img_ directory, just like they're referenced
in the docs.
@@ -140,12 +161,12 @@ instead.
## Setting up a Python development environment
Check the [OS dependencies](http://airbnb.io/superset/installation.html#os-dependencies) before follows these steps.
Check the [OS dependencies](https://superset.incubator.apache.org/installation.html#os-dependencies) before follows these steps.
# fork the repo on GitHub and then clone it
# alternatively you may want to clone the main repo but that won't work
# so well if you are planning on sending PRs
# git clone git@github.com:airbnb/superset.git
# git clone git@github.com:apache/incubator-superset.git
# [optional] setup a virtual env and activate it
virtualenv env
@@ -202,8 +223,13 @@ To install third party libraries defined in `package.json`, run the
following within the `superset/assets/` directory which will install them in a
new `node_modules/` folder within `assets/`.
```
npm install
```bash
# from the root of the repository, move to where our JS package.json lives
cd superset/assets/
# install yarn, a replacement for `npm install` that is faster and more deterministic
npm install -g yarn
# run yarn to fetch all the dependencies
yarn
```
To parse and generate bundled files for superset, run either of the
@@ -231,6 +257,11 @@ npm run dev
## Testing
Before running python unit tests, please setup local testing environment:
```
pip install -r dev-reqs.txt
```
Python tests can be run with:
./run_tests.sh
@@ -292,23 +323,6 @@ The `variables.less` and `bootswatch.less` files that ship with Superset are der
[Bootswatch](https://bootswatch.com) and thus extend Bootstrap. Modify variables in these files directly, or
swap them out entirely with the equivalent files from other Bootswatch (themes)[https://github.com/thomaspark/bootswatch.git]
## Pull Request Guidelines
Before you submit a pull request from your forked repo, check that it
meets these guidelines:
1. The pull request should include tests, either as doctests,
unit tests, or both.
2. If the pull request adds functionality, the docs should be updated
as part of the same PR. Doc string are often sufficient, make
sure to follow the sphinx compatible standards.
3. The pull request should work for Python 2.6, 2.7, and ideally python 3.3.
``from __future__ import`` will be required in every `.py` file soon.
4. Code will be reviewed by re running the unittests, flake8 and syntax
should be as rigorous as the core Python project.
5. Please rebase and resolve all conflicts before submitting.
## Translations
We use [Babel](http://babel.pocoo.org/en/latest/) to translate Superset. The
@@ -360,3 +374,9 @@ to take effect, they need to be compiled using this command:
`ADDITIONAL_MODULE_DS_MAP = {'superset.my_models': ['MyDatasource', 'MyOtherDatasource']}`
This means it'll register MyDatasource and MyOtherDatasource in superset.my_models module in the source registry.
## Creating a new visualization type
Here's an example as a Github PR with comments that describe what the
different sections of the code do:
https://github.com/apache/incubator-superset/pull/3013

View File

@@ -1,22 +0,0 @@
Please use [pull requests](https://github.com/airbnb/superset/pull/new/master)
to add your organization and/or project to this document!
Organizations
----------
- [Airbnb](https://github.com/airbnb)
- [GfK Data Lab](http://datalab.gfk.com)
- [Maieutical Labs](https://cloudschooling.it)
- [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/)
- [Udemy](https://www.udemy.com/)
- [Tooploox](https://www.tooploox.com/)
Projects
----------
- None we know of yet

131
README.md
View File

@@ -1,17 +1,14 @@
Superset
=========
[![Build Status](https://travis-ci.org/airbnb/superset.svg?branch=master)](https://travis-ci.org/airbnb/superset)
[![Build Status](https://travis-ci.org/apache/incubator-superset.svg?branch=master)](https://travis-ci.org/apache/incubator-superset)
[![PyPI version](https://badge.fury.io/py/superset.svg)](https://badge.fury.io/py/superset)
[![Coverage Status](https://coveralls.io/repos/airbnb/superset/badge.svg?branch=master&service=github)](https://coveralls.io/github/airbnb/superset?branch=master)
[![JS Test Coverage](https://codeclimate.com/github/airbnb/superset/badges/coverage.svg)](https://codeclimate.com/github/airbnb/superset/coverage)
[![Code Health](https://landscape.io/github/airbnb/superset/master/landscape.svg?style=flat)](https://landscape.io/github/airbnb/superset/master)
[![Code Climate](https://codeclimate.com/github/airbnb/superset/badges/gpa.svg)](https://codeclimate.com/github/airbnb/superset)
[![Coverage Status](https://coveralls.io/repos/apache/incubator-superset/badge.svg?branch=master&service=github)](https://coveralls.io/github/apache/incubator-superset?branch=master)
[![PyPI](https://img.shields.io/pypi/pyversions/superset.svg?maxAge=2592000)](https://pypi.python.org/pypi/superset)
[![Requirements Status](https://requires.io/github/airbnb/superset/requirements.svg?branch=master)](https://requires.io/github/airbnb/superset/requirements/?branch=master)
[![Join the chat at https://gitter.im/airbnb/superset](https://badges.gitter.im/airbnb/superset.svg)](https://gitter.im/airbnb/superset?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
[![Documentation](https://img.shields.io/badge/docs-airbnb.io-blue.svg)](http://airbnb.io/superset/)
[![dependencies Status](https://david-dm.org/airbnb/superset/status.svg?path=superset/assets)](https://david-dm.org/airbnb/superset?path=superset/assets)
[![Requirements Status](https://requires.io/github/apache/incubator-superset/requirements.svg?branch=master)](https://requires.io/github/apache/incubator-superset/requirements/?branch=master)
[![Join the chat at https://gitter.im/apache/incubator-superset](https://badges.gitter.im/apache/incubator-superset.svg)](https://gitter.im/apache/incubator-superset?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
[![Documentation](https://img.shields.io/badge/docs-apache.org-blue.svg)](https://superset.incubator.apache.org)
[![dependencies Status](https://david-dm.org/apache/incubator-superset/status.svg?path=superset/assets)](https://david-dm.org/apache/incubator-superset?path=superset/assets)
<img
src="https://cloud.githubusercontent.com/assets/130878/20946612/49a8a25c-bbc0-11e6-8314-10bef902af51.png"
@@ -19,8 +16,8 @@ Superset
width="500"
/>
**Superset** is a data exploration platform designed to be visual, intuitive
and interactive.
**Apache Superset** (incubating) is a modern, enterprise-ready
business intelligence web application
[this project used to be named **Caravel**, and **Panoramix** in the past]
@@ -52,24 +49,26 @@ Screenshots & Gifs
![superset-query-search](https://cloud.githubusercontent.com/assets/130878/20234706/0f430a10-a835-11e6-8a0d-8b26cc2e6bbd.png)
Superset
---------
Superset's main goal is to make it easy to slice, dice and visualize data.
It empowers users to perform **analytics at the speed of thought**.
Apache Superset
---------------
Apache Superset is a data exploration and visualization web application.
Superset provides:
* A quick way to intuitively visualize datasets by allowing users to create
and share interactive dashboards
* A rich set of visualizations to analyze your data, as well as a flexible
way to extend the capabilities
* An intuitive interface to explore and visualize datasets, and
create interactive dashboards.
* A wide array of beautiful visualizations to showcase your data.
* Easy, code-free, user flows to drill down and slice and dice the data
underlying exposed dashboards. The dashboards and charts acts as a starting
point for deeper analysis.
* A state of the art SQL editor/IDE exposing a rich metadata browser, and
an easy workflow to create visualizations out of any result set.
* An extensible, high granularity security model allowing intricate rules
on who can access which features, and integration with major
authentication providers (database, OpenID, LDAP, OAuth & REMOTE_USER
through Flask AppBuiler)
* A simple semantic layer, allowing to control how data sources are
displayed in the UI, by defining which fields should show up in
which dropdown and which aggregation and function (metrics) are
made available to the user
on who can access which product features and datasets.
Integration with major
authentication backends (database, OpenID, LDAP, OAuth, REMOTE_USER, ...)
* A lightweight semantic layer, allowing to control how data sources are
exposed to the user by defining dimensions and metrics
* Out of the box support for most SQL-speaking databases
* Deep integration with Druid allows for Superset to stay blazing fast while
slicing and dicing large, realtime datasets
* Fast loading dashboards with configurable caching
@@ -78,15 +77,41 @@ Superset provides:
Database Support
----------------
Superset was originally designed on top of Druid.io, but quickly broadened
its scope to support other databases through the use of SQLAlchemy, a Python
Superset speaks many SQL dialects through SQLAlchemy, a Python
ORM that is compatible with
[most common databases](http://docs.sqlalchemy.org/en/rel_1_0/core/engines.html).
Superset can be used to visualize data out of most databases:
* MySQL
* Postgres
* Vertica
* Oracle
* Microsoft SQL Server
* SQLite
* Greenplum
* Firebird
* MariaDB
* Sybase
* IBM DB2
* Exasol
* MonetDB
* Snowflake
* Redshift
* **more!** look for the availability of a SQLAlchemy dialect for your database
to find out whether it will work with Superset
What is Druid?
-------------
From their website at http://druid.io
Druid!
------
On top of having the ability to query your relational databases,
Superset has ships with deep integration with Druid (a real time distributed
column-store). When querying Druid,
Superset can query humongous amounts of data on top of real time dataset.
Note that Superset does not require Druid in any way to function, it's simply
another database backend that it can query.
Here's a description of Druid from the http://druid.io website:
*Druid is an open-source analytics data store designed for
business intelligence (OLAP) queries on event data. Druid provides low
@@ -99,7 +124,7 @@ power analytic dashboards and applications.*
Installation & Configuration
----------------------------
[See in the documentation](http://airbnb.io/superset/installation.html)
[See in the documentation](https://superset.incubator.apache.org/installation.html)
More screenshots
@@ -130,19 +155,35 @@ Resources
* [Slides from Strata (March 2016)](https://drive.google.com/open?id=0B5PVE0gzO81oOVJkdF9aNkJMSmM)
Tip of the Hat
--------------
Superset would not be possible without these great frameworks / libs
* Flask App Builder - Allowing us to focus on building the app quickly while
getting the foundation for free
* The Flask ecosystem - Simply amazing. So much Plug, easy play.
* NVD3 - One of the best charting libraries out there
* Much more, check out the `install_requires` section in the [setup.py](https://github.com/airbnb/superset/blob/master/setup.py) file!
Contributing
------------
Interested in contributing? Casual hacking? Check out [Contributing.MD](https://github.com/airbnb/superset/blob/master/CONTRIBUTING.md)
Interested in contributing? Casual hacking? Check out
[Contributing.MD](https://github.com/airbnb/superset/blob/master/CONTRIBUTING.md)
Who uses Apache Superset (incubating)?
--------------------------------------
Here's a list of organizations who have taken the time to send a PR to let
the world know they are using Superset. Join our growing community!
- [Airbnb](https://github.com/airbnb)
- [Amino](https://amino.com)
- [Brilliant.org](https://brilliant.org/)
- [Clark.de](http://clark.de/)
- [Digit Game Studios](https://www.digitgaming.com/)
- [Douban](https://www.douban.com/)
- [Endress+Hauser](http://www.endress.com/)
- [FBK - ICT center](http://ict.fbk.eu)
- [Faasos](http://faasos.com/)
- [GfK Data Lab](http://datalab.gfk.com)
- [Maieutical Labs](https://cloudschooling.it)
- [Qunar](https://www.qunar.com/)
- [Shopkick](https://www.shopkick.com)
- [Tails.com](https://tails.com)
- [Tobii](http://www.tobii.com/)
- [Tooploox](https://www.tooploox.com/)
- [Udemy](https://www.udemy.com/)
- [Yahoo!](www.yahoo.com)
- [Zalando](https://www.zalando.com)

File diff suppressed because it is too large Load Diff

View File

@@ -1,10 +1,15 @@
codeclimate-test-reporter
coveralls
flake8
flask_cors
mock
mysqlclient
nose
psycopg2
pylint
pythrifthiveapi
pyyaml
redis
statsd
# Also install everything we need to build Sphinx docs
-r dev-reqs-for-docs.txt

View File

@@ -51,7 +51,7 @@ source_suffix = '.rst'
master_doc = 'index'
# General information about the project.
project = "Superset's documentation"
project = "Apache Superset"
copyright = None
author = u'Maxime Beauchemin'

View File

@@ -48,12 +48,21 @@ https://github.com/airbnb/superset/issues?q=label%3Aexample+is%3Aclosed
Why are my queries timing out?
------------------------------
If you are seeing timeouts (504 Gateway Time-out) when running queries,
it's because the web server is timing out web requests. If you want to
increase the default (50), you can specify the timeout when starting the
web server with the ``-t`` flag, which is expressed in seconds.
There are many reasons may cause long query timing out.
``superset runserver -t 300``
- For running long query from Sql Lab, by default Superset allows it run as long as 6 hours before it being killed by celery. If you want to increase the time for running query, you can specify the timeout in configuration. For example:
``SQLLAB_ASYNC_TIME_LIMIT_SEC = 60 * 60 * 6``
- Superset is running on gunicorn web server, which may time out web requests. If you want to increase the default (50), you can specify the timeout when starting the web server with the ``-t`` flag, which is expressed in seconds.
``superset runserver -t 300``
- If you are seeing timeouts (504 Gateway Time-out) when loading dashboard or explore slice, you are probably behind gateway or proxy server (such as Nginx). If it did not receive a timely response from Superset server (which is processing long queries), these web servers will send 504 status code to clients directly. Superset has a client-side timeout limit to address this issue. If query didn't come back within clint-side timeout (45 seconds by default), Superset will display warning message to avoid gateway timeout message. If you have a longer gateway timeout limit, you can change client-side timeout limit settings from ``/superset/superset/assets/javascripts/constants.js`` file and rebuild js package:
``export const QUERY_TIMEOUT_THRESHOLD = 45000;``
Why is the map not visible in the mapbox visualization?
@@ -78,6 +87,11 @@ The widget also has a checkbox ``Date Filter``, which enables time filtering
capabilities to your dashboard. After checking the box and refreshing, you'll
see a ``from`` and a ``to`` dropdown show up.
By default, the filtering will be applied to all the slices that are built
on top of a datasource that shares the column name that the filter is based
on. It's also a requirement for that column to be checked as "filterable"
in the column tab of the table editor.
But what about if you don't want certain widgets to get filtered on your
dashboard? You can do that by editing your dashboard, and in the form,
edit the ``JSON Metadata`` field, more specifically the
@@ -119,3 +133,22 @@ __ https://www.sqlite.org/lockingv3.html
One work around is to create a symlink from ~/.superset to a directory located on a non-NFS partition.
Another work around is to change where superset stores the sqlite database by adding ``SQLALCHEMY_DATABASE_URI = 'sqlite:////new/localtion/superset.db'`` in superset_config.py (create the file if needed), then adding the directory where superset_config.py lives to PYTHONPATH environment variable (e.g. ``export PYTHONPATH=/opt/logs/sandbox/airbnb/``).
How do I add new columns to an existing table
---------------------------------------------
Table schemas evolve, and Superset needs to reflect that. It's pretty common
in the life cycle of a dashboard to want to add a new dimension or metric.
To get Superset to discover your new columns, all you have to do is to
go to ``Menu -> Sources -> Tables``, click the ``edit`` icon next to the
table who's schema has changed, and hit ``Save`` from the ``Detail`` tab.
Behind the scene, the new columns will get merged it. Following this,
you may want to
re-edit the table afterwards to configure the ``Column`` tab, check the
appropriate boxes and save again.
How do I go about developing a new visualization type?
------------------------------------------------------
Here's an example as a Github PR with comments that describe what the
different sections of the code do:
https://github.com/airbnb/superset/pull/3013

View File

@@ -1,33 +1,48 @@
Superset's documentation
''''''''''''''''''''''''
.. image:: _static/img/s.png
Apache Superset (incubating)
''''''''''''''''''''''''''''
Apache Superset (incubating) is a modern, enterprise-ready business
intelligence web application
Superset is a data exploration platform designed to be visual, intuitive
and interactive.
----------------
.. warning:: This project was originally named Panoramix, was renamed to
Caravel in March 2016, and is currently named Superset as of November 2016
.. important::
**Disclaimer**: Apache Superset is an effort undergoing incubation at The
Apache Software Foundation (ASF), sponsored by the Apache Incubator.
Incubation is required of all newly accepted projects until a further
review indicates that the infrastructure, communications, and
decision making process have stabilized in a manner consistent with
other successful ASF projects. While incubation status is not
necessarily a reflection of the completeness or stability of
the code, it does indicate that the project has yet to be fully
endorsed by the ASF.
Overview
=======================================
Features
---------
- A rich set of data visualizations, integrated from some of the best
visualization libraries
- Create and share simple dashboards
- An extensible, high-granularity security/permission model allowing
intricate rules on who can access individual features and the dataset
- A rich set of data visualizations
- An easy-to-use interface for exploring and visualizing data
- Create and share dashboards
- Enterprise-ready authentication with integration with major authentication
providers (database, OpenID, LDAP, OAuth & REMOTE_USER through
Flask AppBuilder)
- An extensible, high-granularity security/permission model allowing
intricate rules on who can access individual features and the dataset
- A simple semantic layer, allowing users to control how data sources are
displayed in the UI by defining which fields should show up in which
drop-down and which aggregation and function metrics are made available
to the user
- Integration with most RDBMS through SqlAlchemy
- Integration with most SQL-speaking RDBMS through SQLAlchemy
- Deep integration with Druid.io
------
@@ -55,6 +70,7 @@ Contents
tutorial
security
sqllab
visualization
videos
gallery
druid

View File

@@ -36,7 +36,7 @@ that the required dependencies are installed: ::
**OSX**, system python is not recommended. brew's python also ships with pip ::
brew install pkg-config libffi openssl python
env LDFLAGS="-L$(brew --prefix openssl)/lib" CFLAGS="-I$(brew --prefix openssl)/include" pip install cryptography
env LDFLAGS="-L$(brew --prefix openssl)/lib" CFLAGS="-I$(brew --prefix openssl)/include" pip install cryptography==1.7.2
**Windows** isn't officially supported at this point, but if you want to
attempt it, download `get-pip.py <https://bootstrap.pypa.io/get-pip.py>`_, and run ``python get-pip.py`` which may need admin access. Then run the following: ::
@@ -156,7 +156,7 @@ of the parameters you can copy / paste in that configuration module: ::
SQLALCHEMY_DATABASE_URI = 'sqlite:////path/to/superset.db'
# Flask-WTF flag for CSRF
CSRF_ENABLED = True
WTF_CSRF_ENABLED = True
# Set this API key to enable Mapbox visualizations
MAPBOX_API_KEY = ''
@@ -206,12 +206,35 @@ Here's a list of some of the recommended packages.
+---------------+-------------------------------------+-------------------------------------------------+
| Greenplum | ``pip install psycopg2`` | ``postgresql+psycopg2://`` |
+---------------+-------------------------------------+-------------------------------------------------+
| Athena | ``pip install "PyAthenaJDBC>1.0.9"``| ``awsathena+jdbc://`` |
+---------------+-------------------------------------+-------------------------------------------------+
| Vertica | ``pip install | ``vertica+vertica_python://`` |
| | sqlalchemy-vertica-python`` | |
+---------------+-------------------------------------+-------------------------------------------------+
| ClickHouse | ``pip install | ``clickhouse://`` |
| | sqlalchemy-clickhouse`` | |
+---------------+-------------------------------------+-------------------------------------------------+
Note that many other database are supported, the main criteria being the
existence of a functional SqlAlchemy dialect and Python driver. Googling
the keyword ``sqlalchemy`` in addition of a keyword that describes the
database you want to connect to should get you to the right place.
(AWS) Athena
------------
This currently relies on an unreleased future version of `PyAthenaJDBC <https://github.com/laughingman7743/PyAthenaJDBC>`_. If you're adventurous or simply impatient, you can install directly from git: ::
pip install git+https://github.com/laughingman7743/PyAthenaJDBC@support_sqlalchemy
The connection string for Athena looks like this ::
awsathena+jdbc://{aws_access_key_id}:{aws_secret_access_key}@athena.{region_name}.amazonaws.com/{schema_name}?s3_staging_dir={s3_staging_dir}&...
Where you need to escape/encode at least the s3_staging_dir, i.e., ::
s3://... -> s3%3A//...
Caching
-------
@@ -223,9 +246,11 @@ complies with the Flask-Cache specifications.
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
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>`.
the `redis <https://pypi.python.org/pypi/redis>`_ Python package: ::
pip install redis
For setting your timeouts, this is done in the Superset metadata and goes
up the "timeout searchpath", from your slice configuration, to your
@@ -341,28 +366,74 @@ Upgrading should be as straightforward as running::
SQL Lab
-------
SQL Lab is a powerful SQL IDE that works with all SQLAlchemy compatible
databases out there. By default, queries are run in a web request, and
databases. By default, queries are executed in the scope of a web
request so they
may eventually timeout as queries exceed the maximum duration of a web
request in your environment, whether it'd be a reverse proxy or the Superset
server itself.
In the modern analytics world, it's not uncommon to run large queries that
run for minutes or hours.
On large analytic databases, it's common to run queries that
execute for minutes or hours.
To enable support for long running queries that
execute beyond the typical web request's timeout (30-60 seconds), it is
necessary to deploy an asynchronous backend, which consist of one or many
Superset worker, which is implemented as a Celery worker, and a Celery
broker for which we recommend using Redis or RabbitMQ.
necessary to configure an asynchronous backend for Superset which consist of:
It's also preferable to setup an async result backend as a key value store
that can hold the long-running query results for a period of time. More
details to come as to how to set this up here soon.
* one or many Superset worker (which is implemented as a Celery worker), and
can be started with the ``superset worker`` command, run
``superset worker --help`` to view the related options
* a celery broker (message queue) for which we recommend using Redis
or RabbitMQ
* a results backend that defines where the worker will persist the query
results
SQL Lab supports templating in queries, and it's possible to override
Configuring Celery requires defining a ``CELERY_CONFIG`` in your
``superset_config.py``. Both the worker and web server processes should
have the same configuration.
.. code-block:: python
class CeleryConfig(object):
BROKER_URL = 'redis://localhost:6379/0'
CELERY_IMPORTS = ('superset.sql_lab', )
CELERY_RESULT_BACKEND = 'redis://localhost:6379/0'
CELERY_ANNOTATIONS = {'tasks.add': {'rate_limit': '10/s'}}
CELERY_CONFIG = CeleryConfig
To setup a result backend, you need to pass an instance of a derivative
of ``werkzeug.contrib.cache.BaseCache`` to the ``RESULTS_BACKEND``
configuration key in your ``superset_config.py``. It's possible to use
Memcached, Redis, S3 (https://pypi.python.org/pypi/s3werkzeugcache),
memory or the file system (in a single server-type setup or for testing),
or to write your own caching interface. Your ``superset_config.py`` may
look something like:
.. code-block:: python
# On S3
from s3cache.s3cache import S3Cache
S3_CACHE_BUCKET = 'foobar-superset'
S3_CACHE_KEY_PREFIX = 'sql_lab_result'
RESULTS_BACKEND = S3Cache(S3_CACHE_BUCKET, S3_CACHE_KEY_PREFIX)
# On Redis
from werkzeug.contrib.cache import RedisCache
RESULTS_BACKEND = RedisCache(
host='localhost', port=6379, key_prefix='superset_results')
Also note that SQL Lab supports Jinja templating in queries, and that it's
possible to overload
the default Jinja context in your environment by defining the
``JINJA_CONTEXT_ADDONS`` in your superset configuration. Objects referenced
in this dictionary are made available for users to use in their SQL.
.. code-block:: python
JINJA_CONTEXT_ADDONS = {
'my_crazy_macro': lambda x: x*2,
}
Making your own build
---------------------
@@ -384,7 +455,7 @@ Blueprints
`Blueprints are Flask's reusable apps <http://flask.pocoo.org/docs/0.12/blueprints/>`_.
Superset allows you to specify an array of Blueprints
an array of Blueprints in your ``superset_config`` module. Here's
in your ``superset_config`` module. Here's
an example on how this can work with a simple Blueprint. By doing
so, you can expect Superset to serve a page that says "OK"
at the ``/simple_page`` url. This can allow you to run other things such

View File

@@ -13,9 +13,11 @@ Feature Overview
visualization capabilities
- Browse database metadata: tables, columns, indexes, partitions
- Support for long-running queries
- uses the `Celery distributed queue <http://www.python.org/>`_
to dispatch query handling to workers
- supports defining a "results backend" to persist query results
- A search engine to find queries executed in the past
- Supports templating using the
`Jinja templating language <http://jinja.pocoo.org/docs/dev/>`_
@@ -58,3 +60,5 @@ Superset's Jinja context:
.. autoclass:: superset.jinja_context.PrestoTemplateProcessor
:members:
.. autofunction:: superset.jinja_context.url_param

1759
docs/visualization.rst Normal file

File diff suppressed because it is too large Load Diff

2
pylint-errors.sh Executable file
View File

@@ -0,0 +1,2 @@
#!/bin/bash
pylint superset --errors-only

View File

@@ -43,29 +43,32 @@ setup(
scripts=['superset/bin/superset'],
install_requires=[
'boto3==1.4.4',
'celery==3.1.23',
'cryptography==1.7.2',
'flask-appbuilder==1.8.1',
'celery==3.1.25',
'colorama==0.3.9',
'cryptography==1.9',
'flask-appbuilder==1.9.1',
'flask-cache==0.13.1',
'flask-migrate==1.5.1',
'flask-migrate==2.0.3',
'flask-script==2.0.5',
'flask-sqlalchemy==2.0',
'flask-testing==0.6.1',
'flask-sqlalchemy==2.1',
'flask-testing==0.6.2',
'flask-wtf==0.14.2',
'flower==0.9.1',
'future>=0.16.0, <0.17',
'humanize==0.5.1',
'gunicorn==19.6.0',
'gunicorn==19.7.1',
'markdown==2.6.8',
'pandas==0.18.1',
'pandas==0.20.2',
'parsedatetime==2.0.0',
'pydruid==0.3.1',
'PyHive>=0.2.1',
'PyHive>=0.4.0',
'python-dateutil==2.6.0',
'requests==2.13.0',
'requests==2.17.3',
'simplejson==3.10.0',
'six==1.10.0',
'sqlalchemy==1.1.5',
'sqlalchemy-utils==0.32.12',
'sqlparse==0.1.19',
'sqlalchemy==1.1.9',
'sqlalchemy-utils==0.32.14',
'sqlparse==0.2.3',
'thrift>=0.9.3',
'thrift-sasl>=0.2.1',
],
@@ -77,6 +80,7 @@ setup(
'coverage',
'mock',
'nose',
'redis',
],
author='Maxime Beauchemin',
author_email='maximebeauchemin@gmail.com',

View File

@@ -14,6 +14,7 @@ from flask import Flask, redirect
from flask_appbuilder import SQLA, AppBuilder, IndexView
from flask_appbuilder.baseviews import expose
from flask_migrate import Migrate
from flask_wtf.csrf import CSRFProtect
from werkzeug.contrib.fixers import ProxyFix
from superset.connectors.connector_registry import ConnectorRegistry
@@ -31,6 +32,21 @@ app = Flask(__name__)
app.config.from_object(CONFIG_MODULE)
conf = app.config
@app.context_processor
def get_js_manifest():
manifest = {}
try:
with open(APP_DIR + '/static/assets/dist/manifest.json', 'r') as f:
manifest = json.load(f)
except Exception as e:
print(
"no manifest file found at " +
APP_DIR + "/static/assets/dist/manifest.json"
)
return dict(js_manifest=manifest)
for bp in conf.get('BLUEPRINTS'):
try:
print("Registering blueprint: '{}'".format(bp.name))
@@ -50,6 +66,8 @@ logging.getLogger('pyhive.presto').setLevel(logging.INFO)
db = SQLA(app)
if conf.get('WTF_CSRF_ENABLED'):
csrf = CSRFProtect(app)
utils.pessimistic_connection_handling(db.engine.pool)
@@ -77,6 +95,20 @@ if app.config.get('ENABLE_CORS'):
if app.config.get('ENABLE_PROXY_FIX'):
app.wsgi_app = ProxyFix(app.wsgi_app)
if app.config.get('ENABLE_CHUNK_ENCODING'):
class ChunkedEncodingFix(object):
def __init__(self, app):
self.app = app
def __call__(self, environ, start_response):
# Setting wsgi.input_terminated tells werkzeug.wsgi to ignore
# content-length and read the stream till the end.
if 'chunked' == environ.get('HTTP_TRANSFER_ENCODING', '').lower():
environ['wsgi.input_terminated'] = True
return self.app(environ, start_response)
app.wsgi_app = ChunkedEncodingFix(app.wsgi_app)
if app.config.get('UPLOAD_FOLDER'):
try:
os.makedirs(app.config.get('UPLOAD_FOLDER'))

View File

@@ -7,3 +7,4 @@ node_modules/*
node_modules*/*
stylesheets/*
vendor/*
docs/*

View File

@@ -5,6 +5,9 @@
"experimentalObjectRestSpread": true
}
},
"globals": {
"document": true,
},
"rules": {
"prefer-template": 0,
"new-cap": 0,
@@ -14,5 +17,26 @@
"func-names": 0,
"react/jsx-no-bind": 0,
"no-confusing-arrow": 0,
"jsx-a11y/no-static-element-interactions": 0,
"jsx-a11y/anchor-has-content": 0,
"react/require-default-props": 0,
"no-plusplus": 0,
"no-mixed-operators": 0,
"no-continue": 0,
"no-bitwise": 0,
"no-undef": 0,
"no-multi-assign": 0,
"react/no-array-index-key": 0,
"no-restricted-properties": 0,
"no-prototype-builtins": 0,
"jsx-a11y/href-no-hash": 0,
"react/forbid-prop-types": 0,
"class-methods-use-this": 0,
"import/no-named-as-default": 0,
"import/prefer-default-export": 0,
"react/no-unescaped-entities": 0,
"react/no-unused-prop-types": 0,
"react/no-string-refs": 0,
}
}

View File

@@ -1,7 +1,7 @@
{
"fields": {
"controls": {
"datasource": {
"type": "SelectField",
"type": "SelectControl",
"label": "Datasource",
"isLoading": true,
"clearable": false,
@@ -9,7 +9,7 @@
"description": ""
},
"viz_type": {
"type": "SelectField",
"type": "SelectControl",
"label": "Visualization Type",
"clearable": false,
"default": "table",
@@ -129,6 +129,11 @@
"Directed Force Layout",
"/static/assets/images/viz_thumbnails/directed_force.png"
],
[
"country_map",
"Country Map",
"/static/assets/images/viz_thumbnails/country_map.png"
],
[
"world_map",
"World Map",
@@ -168,7 +173,7 @@
"description": "The type of visualization to display"
},
"metrics": {
"type": "SelectField",
"type": "SelectControl",
"multi": true,
"label": "Metrics",
"validators": [
@@ -177,27 +182,27 @@
"description": "One or many metrics to display"
},
"order_by_cols": {
"type": "SelectField",
"type": "SelectControl",
"multi": true,
"label": "Ordering",
"default": [],
"description": "One or many metrics to display"
},
"metric": {
"type": "SelectField",
"type": "SelectControl",
"label": "Metric",
"clearable": false,
"description": "Choose the metric"
},
"metric_2": {
"type": "SelectField",
"type": "SelectControl",
"label": "Right Axis Metric",
"choices": [],
"default": [],
"description": "Choose a metric for right axis"
},
"stacked_style": {
"type": "SelectField",
"type": "SelectControl",
"label": "Stacked Style",
"choices": [
[
@@ -217,7 +222,7 @@
"description": ""
},
"linear_color_scheme": {
"type": "SelectField",
"type": "SelectControl",
"label": "Linear Color Scheme",
"choices": [
[
@@ -241,7 +246,7 @@
"description": ""
},
"normalize_across": {
"type": "SelectField",
"type": "SelectControl",
"label": "Normalize Across",
"choices": [
[
@@ -261,7 +266,7 @@
"description": "Color will be rendered based on a ratio of the cell against the sum of across this criteria"
},
"horizon_color_scale": {
"type": "SelectField",
"type": "SelectControl",
"label": "Horizon Color Scale",
"choices": [
[
@@ -281,7 +286,7 @@
"description": "Defines how the color are attributed."
},
"canvas_image_rendering": {
"type": "SelectField",
"type": "SelectControl",
"label": "Rendering",
"choices": [
[
@@ -297,7 +302,7 @@
"description": "image-rendering CSS attribute of the canvas object that defines how the browser scales up the image"
},
"xscale_interval": {
"type": "SelectField",
"type": "SelectControl",
"label": "XScale Interval",
"choices": [
[
@@ -505,7 +510,7 @@
"description": "Number of steps to take between ticks when displaying the X scale"
},
"yscale_interval": {
"type": "SelectField",
"type": "SelectControl",
"label": "YScale Interval",
"choices": [
[
@@ -712,62 +717,142 @@
"default": null,
"description": "Number of steps to take between ticks when displaying the Y scale"
},
"include_time": {
"type": "CheckboxControl",
"label": "Include Time",
"description": "Whether to include the time granularity as defined in the time section",
"default": false
},
"bar_stacked": {
"type": "CheckboxField",
"type": "CheckboxControl",
"label": "Stacked Bars",
"renderTrigger": true,
"default": false,
"description": null
},
"show_markers": {
"type": "CheckboxField",
"type": "CheckboxControl",
"label": "Show Markers",
"renderTrigger": true,
"default": false,
"description": "Show data points as circle markers on the lines"
},
"show_bar_value": {
"type": "CheckboxField",
"type": "CheckboxControl",
"label": "Bar Values",
"default": false,
"renderTrigger": true,
"description": "Show the value on top of the bar"
},
"order_bars": {
"type": "CheckboxField",
"type": "CheckboxControl",
"label": "Sort Bars",
"default": false,
"description": "Sort bars by x labels."
},
"combine_metric": {
"type": "CheckboxControl",
"label": "Combine Metrics",
"default": false,
"description": "Display metrics side by side within each column, as opposed to each column being displayed side by side for each metric."
},
"show_controls": {
"type": "CheckboxField",
"type": "CheckboxControl",
"label": "Extra Controls",
"renderTrigger": true,
"default": false,
"description": "Whether to show extra controls or not. Extra controls include things like making mulitBar charts stacked or side by side."
},
"reduce_x_ticks": {
"type": "CheckboxField",
"type": "CheckboxControl",
"label": "Reduce X ticks",
"renderTrigger": true,
"default": false,
"description": "Reduces the number of X axis ticks to be rendered. If true, the x axis wont overflow and labels may be missing. If false, a minimum width will be applied to columns and the width may overflow into an horizontal scroll."
},
"include_series": {
"type": "CheckboxField",
"type": "CheckboxControl",
"label": "Include Series",
"renderTrigger": true,
"default": false,
"description": "Include series name as an axis"
},
"secondary_metric": {
"type": "SelectField",
"type": "SelectControl",
"label": "Color Metric",
"default": null,
"description": "A metric to use for color"
},
"select_country": {
"type": "SelectControl",
"label": "Country Name Type",
"default": "France",
"choices": [
[
"Algeria",
"Algeria"
],
[
"Belgium",
"Belgium"
],
[
"Brasil",
"Brasil"
],
[
"China",
"China"
],
[
"Germany",
"Germany"
],
[
"Egypt",
"Egypt"
],
[
"France",
"France"
],
[
"Italy",
"Italy"
],
[
"Morocco",
"Morocco"
],
[
"Nederlanden",
"Nederlanden"
],
[
"Russia",
"Russia"
],
[
"Singapore",
"Singapore"
],
[
"Spain",
"Spain"
],
[
"Uk",
"Uk"
],
[
"Usa",
"Usa"
]
],
"description": "The name of country that Superset should display"
},
"country_fieldtype": {
"type": "SelectField",
"type": "SelectControl",
"label": "Country Field Type",
"default": "cca2",
"choices": [
@@ -791,40 +876,40 @@
"description": "The country code standard that Superset should expect to find in the [country] column"
},
"groupby": {
"type": "SelectField",
"type": "SelectControl",
"multi": true,
"label": "Group by",
"default": [],
"description": "One or many fields to group by"
"description": "One or many controls to group by"
},
"columns": {
"type": "SelectField",
"type": "SelectControl",
"multi": true,
"label": "Columns",
"default": [],
"description": "One or many fields to pivot as columns"
"description": "One or many controls to pivot as columns"
},
"all_columns": {
"type": "SelectField",
"type": "SelectControl",
"multi": true,
"label": "Columns",
"default": [],
"description": "Columns to display"
},
"all_columns_x": {
"type": "SelectField",
"type": "SelectControl",
"label": "X",
"default": null,
"description": "Columns to display"
},
"all_columns_y": {
"type": "SelectField",
"type": "SelectControl",
"label": "Y",
"default": null,
"description": "Columns to display"
},
"druid_time_origin": {
"type": "SelectField",
"type": "SelectControl",
"freeForm": true,
"label": "Origin",
"choices": [
@@ -841,7 +926,7 @@
"description": "Defines the origin where time buckets start, accepts natural dates as in `now`, `sunday` or `1970-01-01`"
},
"bottom_margin": {
"type": "SelectField",
"type": "SelectControl",
"freeForm": true,
"label": "Bottom Margin",
"choices": [
@@ -878,7 +963,7 @@
"description": "Bottom marging, in pixels, allowing for more room for axis labels"
},
"granularity": {
"type": "SelectField",
"type": "SelectControl",
"freeForm": true,
"label": "Time Granularity",
"default": "one day",
@@ -939,7 +1024,7 @@
"description": "The time granularity for the visualization. Note that you can type and use simple natural language as in `10 seconds`, `1 day` or `56 weeks`"
},
"domain_granularity": {
"type": "SelectField",
"type": "SelectControl",
"label": "Domain",
"default": "month",
"choices": [
@@ -967,7 +1052,7 @@
"description": "The time unit used for the grouping of blocks"
},
"subdomain_granularity": {
"type": "SelectField",
"type": "SelectControl",
"label": "Subdomain",
"default": "day",
"choices": [
@@ -995,7 +1080,7 @@
"description": "The time unit for each block. Should be a smaller unit than domain_granularity. Should be larger or equal to Time Grain"
},
"link_length": {
"type": "SelectField",
"type": "SelectControl",
"freeForm": true,
"label": "Link Length",
"default": "200",
@@ -1036,7 +1121,7 @@
"description": "Link length in the force layout"
},
"charge": {
"type": "SelectField",
"type": "SelectControl",
"freeForm": true,
"label": "Charge",
"default": "-500",
@@ -1085,17 +1170,17 @@
"description": "Charge in the force layout"
},
"granularity_sqla": {
"type": "SelectField",
"type": "SelectControl",
"label": "Time Column",
"description": "The time column for the visualization. Note that you can define arbitrary expression that return a DATETIME column in the table or. Also note that the filter below is applied against this column or expression"
},
"time_grain_sqla": {
"type": "SelectField",
"type": "SelectControl",
"label": "Time Grain",
"description": "The time granularity for the visualization. This applies a date transformation to alter your time column and defines a new time granularity. The options here are defined on a per database engine basis in the Superset source code."
},
"resample_rule": {
"type": "SelectField",
"type": "SelectControl",
"freeForm": true,
"label": "Resample Rule",
"default": null,
@@ -1132,7 +1217,7 @@
"description": "Pandas resample rule"
},
"resample_how": {
"type": "SelectField",
"type": "SelectControl",
"freeForm": true,
"label": "Resample How",
"default": null,
@@ -1157,7 +1242,7 @@
"description": "Pandas resample how"
},
"resample_fillmethod": {
"type": "SelectField",
"type": "SelectControl",
"freeForm": true,
"label": "Resample Fill Method",
"default": null,
@@ -1178,7 +1263,7 @@
"description": "Pandas resample fill method"
},
"since": {
"type": "SelectField",
"type": "SelectControl",
"freeForm": true,
"label": "Since",
"default": "7 days ago",
@@ -1219,7 +1304,7 @@
"description": "Timestamp from filter. This supports free form typing and natural language as in `1 day ago`, `28 days` or `3 years`"
},
"until": {
"type": "SelectField",
"type": "SelectControl",
"freeForm": true,
"label": "Until",
"default": "now",
@@ -1251,7 +1336,7 @@
]
},
"max_bubble_size": {
"type": "SelectField",
"type": "SelectControl",
"freeForm": true,
"label": "Max Bubble Size",
"default": "25",
@@ -1287,7 +1372,7 @@
]
},
"whisker_options": {
"type": "SelectField",
"type": "SelectControl",
"freeForm": true,
"label": "Whisker/outlier options",
"default": "Tukey",
@@ -1312,20 +1397,18 @@
]
},
"treemap_ratio": {
"type": "TextField",
"type": "TextControl",
"label": "Ratio",
"isFloat": true,
"default": 1.618033988749895,
"description": "Target aspect ratio for treemap tiles."
},
"number_format": {
"type": "SelectField",
"type": "SelectControl",
"freeForm": true,
"label": "Number format",
"default": [
".3s",
".3s | 12.3k"
],
"renderTrigger": true,
"default": ".3s",
"choices": [
[
".3s",
@@ -1355,7 +1438,7 @@
"description": "D3 format syntax: https://github.com/d3/d3-format"
},
"row_limit": {
"type": "SelectField",
"type": "SelectControl",
"freeForm": true,
"label": "Row limit",
"default": null,
@@ -1399,7 +1482,7 @@
]
},
"limit": {
"type": "SelectField",
"type": "SelectControl",
"freeForm": true,
"label": "Series limit",
"choices": [
@@ -1436,13 +1519,13 @@
"description": "Limits the number of time series that get displayed"
},
"timeseries_limit_metric": {
"type": "SelectField",
"type": "SelectControl",
"label": "Sort By",
"default": null,
"description": "Metric used to define the top series"
},
"rolling_type": {
"type": "SelectField",
"type": "SelectControl",
"label": "Rolling",
"default": "None",
"choices": [
@@ -1470,83 +1553,83 @@
"description": "Defines a rolling window function to apply, works along with the [Periods] text box"
},
"rolling_periods": {
"type": "TextField",
"type": "TextControl",
"label": "Periods",
"isInt": true,
"description": "Defines the size of the rolling window function, relative to the time granularity selected"
},
"series": {
"type": "SelectField",
"type": "SelectControl",
"label": "Series",
"default": null,
"description": "Defines the grouping of entities. Each series is shown as a specific color on the chart and has a legend toggle"
},
"entity": {
"type": "SelectField",
"type": "SelectControl",
"label": "Entity",
"default": null,
"description": "This define the element to be plotted on the chart"
},
"x": {
"type": "SelectField",
"type": "SelectControl",
"label": "X Axis",
"default": null,
"description": "Metric assigned to the [X] axis"
},
"y": {
"type": "SelectField",
"type": "SelectControl",
"label": "Y Axis",
"default": null,
"description": "Metric assigned to the [Y] axis"
},
"size": {
"type": "SelectField",
"type": "SelectControl",
"label": "Bubble Size",
"default": null
},
"url": {
"type": "TextField",
"type": "TextControl",
"label": "URL",
"description": "The URL, this field is templated, so you can integrate {{ width }} and/or {{ height }} in your URL string.",
"default": "https: //www.youtube.com/embed/JkI5rg_VcQ4"
"description": "The URL, this control is templated, so you can integrate {{ width }} and/or {{ height }} in your URL string.",
"default": "https://www.youtube.com/embed/AdSZJzb-aX8"
},
"x_axis_label": {
"type": "TextField",
"type": "TextControl",
"label": "X Axis Label",
"renderTrigger": true,
"default": ""
},
"y_axis_label": {
"type": "TextField",
"type": "TextControl",
"label": "Y Axis Label",
"renderTrigger": true,
"default": ""
},
"where": {
"type": "TextField",
"type": "TextControl",
"label": "Custom WHERE clause",
"default": "",
"description": "The text in this box gets included in your query's WHERE clause, as an AND to other criteria. You can include complex expression, parenthesis and anything else supported by the backend it is directed towards."
},
"having": {
"type": "TextField",
"type": "TextControl",
"label": "Custom HAVING clause",
"default": "",
"description": "The text in this box gets included in your query's HAVING clause, as an AND to other criteria. You can include complex expression, parenthesis and anything else supported by the backend it is directed towards."
},
"compare_lag": {
"type": "TextField",
"type": "TextControl",
"label": "Comparison Period Lag",
"isInt": true,
"description": "Based on granularity, number of time periods to compare against"
},
"compare_suffix": {
"type": "TextField",
"type": "TextControl",
"label": "Comparison suffix",
"description": "Suffix to apply after the percentage display"
},
"table_timestamp_format": {
"type": "SelectField",
"type": "SelectControl",
"freeForm": true,
"label": "Table Timestamp Format",
"default": "smart_date",
@@ -1575,7 +1658,7 @@
"description": "Timestamp Format"
},
"series_height": {
"type": "SelectField",
"type": "SelectControl",
"freeForm": true,
"label": "Series Height",
"default": "25",
@@ -1616,7 +1699,7 @@
"description": "Pixel height of each series"
},
"page_length": {
"type": "SelectField",
"type": "SelectControl",
"freeForm": true,
"label": "Page Length",
"default": 0,
@@ -1661,7 +1744,7 @@
"description": "Rows per page, 0 means no pagination"
},
"x_axis_format": {
"type": "SelectField",
"type": "SelectControl",
"freeForm": true,
"label": "X axis format",
"renderTrigger": true,
@@ -1691,7 +1774,7 @@
"description": "D3 format syntax: https://github.com/d3/d3-format"
},
"y_axis_format": {
"type": "SelectField",
"type": "SelectControl",
"freeForm": true,
"label": "Y axis format",
"renderTrigger": true,
@@ -1725,7 +1808,7 @@
"description": "D3 format syntax: https://github.com/d3/d3-format"
},
"y_axis_2_format": {
"type": "SelectField",
"type": "SelectControl",
"freeForm": true,
"label": "Right axis format",
"default": ".3s",
@@ -1758,7 +1841,7 @@
"description": "D3 format syntax: https://github.com/d3/d3-format"
},
"markup_type": {
"type": "SelectField",
"type": "SelectControl",
"label": "Markup Type",
"choices": [
[
@@ -1774,7 +1857,7 @@
"description": "Pick your favorite markup language"
},
"rotation": {
"type": "SelectField",
"type": "SelectControl",
"label": "Rotation",
"choices": [
[
@@ -1794,7 +1877,7 @@
"description": "Rotation to apply to words in the cloud"
},
"line_interpolation": {
"type": "SelectField",
"type": "SelectControl",
"label": "Line Style",
"renderTrigger": true,
"choices": [
@@ -1827,7 +1910,7 @@
"description": "Line interpolation as defined by d3.js"
},
"pie_label_type": {
"type": "SelectField",
"type": "SelectControl",
"label": "Label Type",
"default": "key",
"choices": [
@@ -1847,13 +1930,13 @@
"description": "What should be shown on the label?"
},
"code": {
"type": "TextAreaField",
"type": "TextAreaControl",
"label": "Code",
"description": "Put your code here",
"default": ""
},
"pandas_aggfunc": {
"type": "SelectField",
"type": "SelectControl",
"label": "Aggregation function",
"clearable": false,
"choices": [
@@ -1890,127 +1973,134 @@
"description": "Aggregate function to apply when pivoting and computing the total rows and columns"
},
"size_from": {
"type": "TextField",
"type": "TextControl",
"isInt": true,
"label": "Font Size From",
"default": "20",
"description": "Font size for the smallest value in the list"
},
"size_to": {
"type": "TextField",
"type": "TextControl",
"isInt": true,
"label": "Font Size To",
"default": "150",
"description": "Font size for the biggest value in the list"
},
"instant_filtering": {
"type": "CheckboxControl",
"label": "Instant Filtering",
"renderTrigger": true,
"default": true,
"description": "Whether to apply filters as they change, or wait forusers to hit an [Apply] button"
},
"show_brush": {
"type": "CheckboxField",
"type": "CheckboxControl",
"label": "Range Filter",
"renderTrigger": true,
"default": false,
"description": "Whether to display the time range interactive selector"
},
"date_filter": {
"type": "CheckboxField",
"type": "CheckboxControl",
"label": "Date Filter",
"default": false,
"description": "Whether to include a time filter"
},
"show_datatable": {
"type": "CheckboxField",
"type": "CheckboxControl",
"label": "Data Table",
"default": false,
"description": "Whether to display the interactive data table"
},
"include_search": {
"type": "CheckboxField",
"type": "CheckboxControl",
"label": "Search Box",
"renderTrigger": true,
"default": false,
"description": "Whether to include a client side search box"
},
"table_filter": {
"type": "CheckboxField",
"type": "CheckboxControl",
"label": "Table Filter",
"default": false,
"description": "Whether to apply filter when table cell is clicked"
},
"show_bubbles": {
"type": "CheckboxField",
"type": "CheckboxControl",
"label": "Show Bubbles",
"default": false,
"renderTrigger": true,
"description": "Whether to display bubbles on top of countries"
},
"show_legend": {
"type": "CheckboxField",
"type": "CheckboxControl",
"label": "Legend",
"renderTrigger": true,
"default": true,
"description": "Whether to display the legend (toggles)"
},
"x_axis_showminmax": {
"type": "CheckboxField",
"type": "CheckboxControl",
"label": "X bounds",
"renderTrigger": true,
"default": true,
"description": "Whether to display the min and max values of the X axis"
},
"rich_tooltip": {
"type": "CheckboxField",
"type": "CheckboxControl",
"label": "Rich Tooltip",
"renderTrigger": true,
"default": true,
"description": "The rich tooltip shows a list of all series for that point in time"
},
"y_axis_zero": {
"type": "CheckboxField",
"type": "CheckboxControl",
"label": "Y Axis Zero",
"default": false,
"renderTrigger": true,
"description": "Force the Y axis to start at 0 instead of the minimum value"
},
"y_log_scale": {
"type": "CheckboxField",
"type": "CheckboxControl",
"label": "Y Log Scale",
"default": false,
"renderTrigger": true,
"description": "Use a log scale for the Y axis"
},
"x_log_scale": {
"type": "CheckboxField",
"type": "CheckboxControl",
"label": "X Log Scale",
"default": false,
"renderTrigger": true,
"description": "Use a log scale for the X axis"
},
"donut": {
"type": "CheckboxField",
"type": "CheckboxControl",
"label": "Donut",
"default": false,
"description": "Do you want a donut or a pie?"
},
"labels_outside": {
"type": "CheckboxField",
"type": "CheckboxControl",
"label": "Put labels outside",
"default": true,
"description": "Put the labels outside the pie?"
},
"contribution": {
"type": "CheckboxField",
"type": "CheckboxControl",
"label": "Contribution",
"default": false,
"description": "Compute the contribution to the total"
},
"num_period_compare": {
"type": "TextField",
"type": "TextControl",
"label": "Period Ratio",
"default": "",
"isInt": true,
"description": "[integer] Number of period to compare against, this is relative to the granularity selected"
},
"period_ratio_type": {
"type": "SelectField",
"type": "SelectControl",
"label": "Period Ratio Type",
"default": "growth",
"choices": [
@@ -2030,25 +2120,25 @@
"description": "`factor` means (new/previous), `growth` is ((new/previous) - 1), `value` is (new-previous)"
},
"time_compare": {
"type": "TextField",
"type": "TextControl",
"label": "Time Shift",
"default": null,
"description": "Overlay a timeseries from a relative time period. Expects relative time delta in natural language (example: 24 hours, 7 days, 56 weeks, 365 days"
"description": "Overlay a timeseries from a relative time period. Expects relative time delta in natural language (example: 24 hours, 7 days, 56 weeks, 365 days)"
},
"subheader": {
"type": "TextField",
"type": "TextControl",
"label": "Subheader",
"description": "Description text that shows up below your Big Number"
},
"mapbox_label": {
"type": "SelectField",
"type": "SelectControl",
"multi": true,
"label": "label",
"default": [],
"description": "`count` is COUNT(*) if a group by is used. Numerical columns will be aggregated with the aggregator. Non-numerical columns will be used to label points. Leave empty to get a count of points in each cluster."
},
"mapbox_style": {
"type": "SelectField",
"type": "SelectControl",
"label": "Map Style",
"choices": [
[
@@ -2080,7 +2170,7 @@
"description": "Base layer map style"
},
"clustering_radius": {
"type": "SelectField",
"type": "SelectControl",
"freeForm": true,
"label": "Clustering Radius",
"default": "60",
@@ -2125,13 +2215,13 @@
"description": "The radius (in pixels) the algorithm uses to define a cluster. Choose 0 to turn off clustering, but beware that a large number of points (>1000) will cause lag."
},
"point_radius": {
"type": "SelectField",
"type": "SelectControl",
"label": "Point Radius",
"default": "Auto",
"description": "The radius of individual points (ones that are not in a cluster). Either a numerical column or `Auto`, which scales the point based on the largest cluster"
},
"point_radius_unit": {
"type": "SelectField",
"type": "SelectControl",
"label": "Point Radius Unit",
"default": "Pixels",
"choices": [
@@ -2151,14 +2241,14 @@
"description": "The unit of measure for the specified point radius"
},
"global_opacity": {
"type": "TextField",
"type": "TextControl",
"label": "Opacity",
"default": 1,
"isFloat": true,
"description": "Opacity of all clusters, points, and labels. Between 0 and 1."
},
"viewport_zoom": {
"type": "TextField",
"type": "TextControl",
"label": "Zoom",
"isFloat": true,
"default": 11,
@@ -2166,7 +2256,7 @@
"places": 8
},
"viewport_latitude": {
"type": "TextField",
"type": "TextControl",
"label": "Default latitude",
"default": 37.772123,
"isFloat": true,
@@ -2174,7 +2264,7 @@
"places": 8
},
"viewport_longitude": {
"type": "TextField",
"type": "TextControl",
"label": "Default longitude",
"default": -122.405293,
"isFloat": true,
@@ -2182,13 +2272,13 @@
"places": 8
},
"render_while_dragging": {
"type": "CheckboxField",
"type": "CheckboxControl",
"label": "Live render",
"default": true,
"description": "Points and clusters will update as viewport is being changed"
},
"mapbox_color": {
"type": "SelectField",
"type": "SelectControl",
"freeForm": true,
"label": "RGB Color",
"default": "rgb(0, 122, 135)",
@@ -2221,58 +2311,64 @@
"description": "The color for points and clusters in RGB"
},
"ranges": {
"type": "TextField",
"type": "TextControl",
"label": "Ranges",
"default": "",
"description": "Ranges to highlight with shading"
},
"range_labels": {
"type": "TextField",
"type": "TextControl",
"label": "Range labels",
"default": "",
"description": "Labels for the ranges"
},
"markers": {
"type": "TextField",
"type": "TextControl",
"label": "Markers",
"default": "",
"description": "List of values to mark with triangles"
},
"marker_labels": {
"type": "TextField",
"type": "TextControl",
"label": "Marker labels",
"default": "",
"description": "Labels for the markers"
},
"marker_lines": {
"type": "TextField",
"type": "TextControl",
"label": "Marker lines",
"default": "",
"description": "List of values to mark with lines"
},
"marker_line_labels": {
"type": "TextField",
"type": "TextControl",
"label": "Marker line labels",
"default": "",
"description": "Labels for the marker lines"
},
"filters": {
"type": "FilterField",
"type": "FilterControl",
"label": "",
"default": [],
"description": ""
},
"having_filters": {
"type": "FilterField",
"type": "FilterControl",
"label": "",
"default": [],
"description": ""
},
"slice_id": {
"type": "HiddenField",
"type": "HiddenControl",
"label": "Slice ID",
"hidden": true,
"description": "The id of the active slice"
},
"cache_timeout": {
"type": "HiddenControl",
"label": "Cache Timeout (seconds)",
"hidden": true,
"description": "The number of seconds before expiring the cache"
}
}
}

1
superset/assets/docs Symbolic link
View File

@@ -0,0 +1 @@
../../docs/_build/html/

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 154 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 398 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 296 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 100 KiB

After

Width:  |  Height:  |  Size: 161 KiB

View File

@@ -1,5 +1,7 @@
/* global notify */
import shortid from 'shortid';
import { now } from '../modules/dates';
const $ = require('jquery');
export const RESET_STATE = 'RESET_STATE';
@@ -33,11 +35,29 @@ export const QUERY_FAILED = 'QUERY_FAILED';
export const CLEAR_QUERY_RESULTS = 'CLEAR_QUERY_RESULTS';
export const REMOVE_DATA_PREVIEW = 'REMOVE_DATA_PREVIEW';
export const CHANGE_DATA_PREVIEW_ID = 'CHANGE_DATA_PREVIEW_ID';
export const SAVE_QUERY = 'SAVE_QUERY';
export const CREATE_DATASOURCE_STARTED = 'CREATE_DATASOURCE_STARTED';
export const CREATE_DATASOURCE_SUCCESS = 'CREATE_DATASOURCE_SUCCESS';
export const CREATE_DATASOURCE_FAILED = 'CREATE_DATASOURCE_FAILED';
export function resetState() {
return { type: RESET_STATE };
}
export function saveQuery(query) {
const url = '/savedqueryviewapi/api/create';
$.ajax({
type: 'POST',
url,
data: query,
success: () => notify.success('Your query was saved'),
error: () => notify.error('Your query could not be saved'),
dataType: 'json',
});
return { type: SAVE_QUERY };
}
export function startQuery(query) {
Object.assign(query, {
id: query.id ? query.id : shortid.generate(),
@@ -98,7 +118,6 @@ export function fetchQueryResults(query) {
export function runQuery(query) {
return function (dispatch) {
dispatch(startQuery(query));
const sqlJsonUrl = '/superset/sql_json/';
const sqlJsonRequest = {
client_id: query.id,
database_id: query.dbId,
@@ -111,6 +130,7 @@ export function runQuery(query) {
tmp_table_name: query.tempTableName,
select_as_cta: query.ctas,
};
const sqlJsonUrl = '/superset/sql_json/' + location.search;
$.ajax({
type: 'POST',
dataType: 'json',
@@ -135,6 +155,9 @@ export function runQuery(query) {
} else if (msg === null) {
msg = `[${textStatus}] ${errorThrown}`;
}
if (msg.indexOf('CSRF token') > 0) {
msg = 'Your session timed out, please refresh your page and try again.';
}
dispatch(queryFailed(query, msg));
},
});
@@ -145,15 +168,17 @@ export function postStopQuery(query) {
return function (dispatch) {
const stopQueryUrl = '/superset/stop_query/';
const stopQueryRequestData = { client_id: query.id };
dispatch(stopQuery(query));
$.ajax({
type: 'POST',
dataType: 'json',
url: stopQueryUrl,
data: stopQueryRequestData,
success() {
if (!query.runAsync) {
dispatch(stopQuery(query));
}
notify.success('Query was stopped.');
},
error() {
notify.error('Failed at stopping query.');
},
});
};
@@ -228,6 +253,18 @@ export function mergeTable(table, query) {
export function addTable(query, tableName, schemaName) {
return function (dispatch) {
let table = {
dbId: query.dbId,
queryEditorId: query.id,
schema: schemaName,
name: tableName,
};
dispatch(mergeTable(Object.assign({}, table, {
isMetadataLoading: true,
isExtraMetadataLoading: true,
expanded: false,
})));
let url = `/superset/table/${query.dbId}/${tableName}/${schemaName}/`;
$.get(url, (data) => {
const dataPreviewQuery = {
@@ -241,36 +278,33 @@ export function addTable(query, tableName, schemaName) {
ctas: false,
};
// Merge table to tables in state
dispatch(mergeTable(
Object.assign(data, {
dbId: query.dbId,
queryEditorId: query.id,
schema: schemaName,
expanded: true,
}), dataPreviewQuery)
);
const newTable = Object.assign({}, table, data, {
expanded: true,
isMetadataLoading: false,
});
dispatch(mergeTable(newTable, dataPreviewQuery));
// Run query to get preview data for table
dispatch(runQuery(dataPreviewQuery));
})
.fail(() => {
dispatch(
addAlert({
msg: 'Error occurred while fetching metadata',
bsStyle: 'danger',
})
);
const newTable = Object.assign({}, table, {
isMetadataLoading: false,
});
dispatch(mergeTable(newTable));
notify.error('Error occurred while fetching table metadata');
});
url = `/superset/extra_table_metadata/${query.dbId}/${tableName}/${schemaName}/`;
$.get(url, (data) => {
const table = {
dbId: query.dbId,
queryEditorId: query.id,
schema: schemaName,
name: tableName,
};
Object.assign(table, data);
table = Object.assign({}, table, data, { isExtraMetadataLoading: false });
dispatch(mergeTable(table));
})
.fail(() => {
const newTable = Object.assign({}, table, {
isExtraMetadataLoading: false,
});
dispatch(mergeTable(newTable));
notify.error('Error occurred while fetching table metadata');
});
};
}
@@ -328,6 +362,61 @@ export function popStoredQuery(urlId) {
};
dispatch(addQueryEditor(queryEditorProps));
},
error: () => notify.error("The query couldn't be loaded"),
});
};
}
export function popSavedQuery(saveQueryId) {
return function (dispatch) {
$.ajax({
type: 'GET',
url: `/savedqueryviewapi/api/get/${saveQueryId}`,
success: (data) => {
const sq = data.result;
const queryEditorProps = {
title: sq.label,
dbId: sq.db_id ? parseInt(sq.db_id, 10) : null,
schema: sq.schema,
autorun: false,
sql: sq.sql,
};
dispatch(addQueryEditor(queryEditorProps));
},
error: () => notify.error("The query couldn't be loaded"),
});
};
}
export function createDatasourceStarted() {
return { type: CREATE_DATASOURCE_STARTED };
}
export function createDatasourceSuccess(response) {
const data = JSON.parse(response);
const datasource = `${data.table_id}__table`;
return { type: CREATE_DATASOURCE_SUCCESS, datasource };
}
export function createDatasourceFailed(err) {
return { type: CREATE_DATASOURCE_FAILED, err };
}
export function createDatasource(vizOptions, context) {
return (dispatch) => {
dispatch(createDatasourceStarted());
return $.ajax({
type: 'POST',
url: '/superset/sqllab_viz/',
data: {
data: JSON.stringify(vizOptions),
},
context,
dataType: 'json',
success: (resp) => {
dispatch(createDatasourceSuccess(resp));
},
error: () => {
dispatch(createDatasourceFailed('An error occurred while creating the data source'));
},
});
};
}

View File

@@ -1,4 +1,5 @@
import React from 'react';
import PropTypes from 'prop-types';
import AceEditor from 'react-ace';
import 'brace/mode/sql';
import 'brace/theme/github';
@@ -25,12 +26,12 @@ const sqlWords = sqlKeywords.map(s => ({
}));
const propTypes = {
actions: React.PropTypes.object.isRequired,
onBlur: React.PropTypes.func,
onAltEnter: React.PropTypes.func,
sql: React.PropTypes.string.isRequired,
tables: React.PropTypes.array,
queryEditor: React.PropTypes.object.isRequired,
actions: PropTypes.object.isRequired,
onBlur: PropTypes.func,
onAltEnter: PropTypes.func,
sql: PropTypes.string.isRequired,
tables: PropTypes.array,
queryEditor: PropTypes.object.isRequired,
};
const defaultProps = {
@@ -59,21 +60,19 @@ class AceEditorWrapper extends React.PureComponent {
this.setState({ sql: nextProps.sql });
}
}
textChange(text) {
this.setState({ sql: text });
}
onBlur() {
this.props.onBlur(this.state.sql);
}
getCompletions(aceEditor, session, pos, prefix, callback) {
callback(null, this.state.words);
onAltEnter() {
this.props.onBlur(this.state.sql);
this.props.onAltEnter();
}
onEditorLoad(editor) {
editor.commands.addCommand({
name: 'runQuery',
bindKey: { win: 'Alt-enter', mac: 'Alt-enter' },
exec: () => {
this.props.onAltEnter();
this.onAltEnter();
},
});
editor.$blockScrolling = Infinity; // eslint-disable-line no-param-reassign
@@ -82,15 +81,18 @@ class AceEditorWrapper extends React.PureComponent {
this.props.queryEditor, editor.getSelectedText());
});
}
getCompletions(aceEditor, session, pos, prefix, callback) {
callback(null, this.state.words);
}
setAutoCompleter(props) {
// Loading table and column names as auto-completable words
let words = [];
const columns = {};
const tables = props.tables || [];
tables.forEach(t => {
tables.forEach((t) => {
words.push({ name: t.name, value: t.name, score: 55, meta: 'table' });
const cols = t.columns || [];
cols.forEach(col => {
cols.forEach((col) => {
columns[col.name] = null; // using an object as a unique set
});
});
@@ -107,6 +109,9 @@ class AceEditorWrapper extends React.PureComponent {
}
});
}
textChange(text) {
this.setState({ sql: text });
}
render() {
return (
<AceEditor

View File

@@ -1,34 +0,0 @@
import React from 'react';
import { Alert } from 'react-bootstrap';
class Alerts extends React.PureComponent {
removeAlert(alert) {
this.props.actions.removeAlert(alert);
}
render() {
const alerts = this.props.alerts.map((alert) =>
<Alert
key={alert.id}
bsStyle={alert.bsStyle}
style={{ width: '500px', textAlign: 'midddle', margin: '10px auto' }}
>
{alert.msg}
<i
className="fa fa-close pull-right"
onClick={this.removeAlert.bind(this, alert)}
style={{ cursor: 'pointer' }}
/>
</Alert>
);
return (
<div>{alerts}</div>
);
}
}
Alerts.propTypes = {
alerts: React.PropTypes.array,
actions: React.PropTypes.object,
};
export default Alerts;

View File

@@ -1,21 +1,22 @@
const $ = window.$ = require('jquery');
import * as Actions from '../actions';
import React from 'react';
import PropTypes from 'prop-types';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import TabbedSqlEditors from './TabbedSqlEditors';
import QueryAutoRefresh from './QueryAutoRefresh';
import QuerySearch from './QuerySearch';
import Alerts from './Alerts';
import AlertsWrapper from '../../components/AlertsWrapper';
import * as Actions from '../actions';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
const $ = window.$ = require('jquery');
class App extends React.PureComponent {
constructor(props) {
super(props);
this.state = {
hash: window.location.hash,
contentHeight: this.getHeight(),
contentHeight: '0px',
};
}
componentDidMount() {
@@ -28,20 +29,25 @@ class App extends React.PureComponent {
window.removeEventListener('hashchange', this.onHashChanged.bind(this));
window.removeEventListener('resize', this.handleResize.bind(this));
}
onHashChanged() {
this.setState({ hash: window.location.hash });
}
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();
const warningEl = $('#navbar-warning');
const navTabsEl = $('.nav-tabs');
const searchHeaderEl = $('#search-header');
const alertEl = $('#sqllab-alerts');
const headerNavEl = $('header .navbar');
const navHeight = headerNavEl.outerHeight() + parseInt(headerNavEl.css('marginBottom'), 10);
const searchHeaderHeight = searchHeaderEl.outerHeight() + parseInt(searchHeaderEl.css('marginBottom'), 10);
const headerHeight = navTabsEl.outerHeight() ? navTabsEl.outerHeight() : searchHeaderHeight;
const warningHeight = warningEl.length > 0 ? warningEl.outerHeight() : 0;
const alertHeight = alertEl.length > 0 ? alertEl.outerHeight() : 0;
return `${window.innerHeight - navHeight - headerHeight - warningHeight - alertHeight}px`;
}
handleResize() {
this.setState({ contentHeight: this.getHeight() });
}
onHashChanged() {
this.setState({ hash: window.location.hash });
}
render() {
let content;
if (this.state.hash) {
@@ -64,7 +70,7 @@ class App extends React.PureComponent {
}
return (
<div className="App SqlLab">
<Alerts id="sqllab-alerts" alerts={this.props.alerts} actions={this.props.actions} />
<AlertsWrapper />
<div className="container-fluid">
{content}
</div>
@@ -74,8 +80,8 @@ class App extends React.PureComponent {
}
App.propTypes = {
alerts: React.PropTypes.array,
actions: React.PropTypes.object,
alerts: PropTypes.array,
actions: PropTypes.object,
};
function mapStateToProps(state) {

View File

@@ -1,9 +1,10 @@
import React from 'react';
import PropTypes from 'prop-types';
import { OverlayTrigger, Tooltip } from 'react-bootstrap';
const propTypes = {
column: React.PropTypes.object.isRequired,
column: PropTypes.object.isRequired,
};
const iconMap = {
@@ -17,43 +18,39 @@ const tooltipTitleMap = {
index: 'Index',
};
class ColumnElement extends React.PureComponent {
render() {
const col = this.props.column;
let name = col.name;
let icons;
if (col.keys && col.keys.length > 0) {
name = <strong>{col.name}</strong>;
icons = col.keys.map((key, i) => (
<span key={i} className="ColumnElement">
<OverlayTrigger
placement="right"
overlay={
<Tooltip id="idx-json" bsSize="lg">
<strong>{tooltipTitleMap[key.type]}</strong>
<hr />
<pre className="text-small">
{JSON.stringify(key, null, ' ')}
</pre>
</Tooltip>
}
>
<i className={`fa text-muted m-l-2 ${iconMap[key.type]}`} />
</OverlayTrigger>
</span>
));
}
return (
<div className="clearfix table-column">
<div className="pull-left m-l-10 col-name">
{name}{icons}
</div>
<div className="pull-right text-muted">
<small> {col.type}</small>
</div>
</div>);
export default function ColumnElement(props) {
const col = props.column;
let name = col.name;
let icons;
if (col.keys && col.keys.length > 0) {
name = <strong>{col.name}</strong>;
icons = col.keys.map((key, i) => (
<span key={i} className="ColumnElement">
<OverlayTrigger
placement="right"
overlay={
<Tooltip id="idx-json" bsSize="lg">
<strong>{tooltipTitleMap[key.type]}</strong>
<hr />
<pre className="text-small">
{JSON.stringify(key, null, ' ')}
</pre>
</Tooltip>
}
>
<i className={`fa text-muted m-l-2 ${iconMap[key.type]}`} />
</OverlayTrigger>
</span>
));
}
return (
<div className="clearfix table-column">
<div className="pull-left m-l-10 col-name">
{name}{icons}
</div>
<div className="pull-right text-muted">
<small> {col.type}</small>
</div>
</div>);
}
ColumnElement.propTypes = propTypes;
export default ColumnElement;

View File

@@ -1,9 +1,10 @@
import React from 'react';
import PropTypes from 'prop-types';
import CopyToClipboard from '../../components/CopyToClipboard';
import { storeQuery } from '../../../utils/common';
const propTypes = {
queryEditor: React.PropTypes.object.isRequired,
queryEditor: PropTypes.object.isRequired,
};
export default class CopyQueryTabUrl extends React.PureComponent {

View File

@@ -1,58 +0,0 @@
import * as Actions from '../actions';
import React from 'react';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import { Modal } from 'react-bootstrap';
import ResultSet from './ResultSet';
const propTypes = {
queries: React.PropTypes.object,
actions: React.PropTypes.object,
showDataPreviewModal: React.PropTypes.bool,
dataPreviewQueryId: React.PropTypes.string,
};
class DataPreviewModal extends React.PureComponent {
hide() {
this.props.actions.hideDataPreview();
}
render() {
if (this.props.showDataPreviewModal && this.props.dataPreviewQueryId) {
const query = this.props.queries[this.props.dataPreviewQueryId];
return (
<Modal
show={this.props.showDataPreviewModal}
onHide={this.hide.bind(this)}
bsStyle="lg"
>
<Modal.Header closeButton>
<Modal.Title>
Data preview for <strong>{query.tableName}</strong>
</Modal.Title>
</Modal.Header>
<Modal.Body>
<ResultSet query={query} visualize={false} csv={false} actions={this.props.actions} />
</Modal.Body>
</Modal>
);
}
return null;
}
}
DataPreviewModal.propTypes = propTypes;
function mapStateToProps(state) {
return {
queries: state.queries,
showDataPreviewModal: state.showDataPreviewModal,
dataPreviewQueryId: state.dataPreviewQueryId,
};
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators(Actions, dispatch),
};
}
export default connect(mapStateToProps, mapDispatchToProps)(DataPreviewModal);

View File

@@ -1,4 +1,5 @@
import React from 'react';
import PropTypes from 'prop-types';
import SyntaxHighlighter from 'react-syntax-highlighter';
import { github } from 'react-syntax-highlighter/dist/styles';
import ModalTrigger from '../../components/ModalTrigger';
@@ -10,11 +11,11 @@ const defaultProps = {
};
const propTypes = {
sql: React.PropTypes.string.isRequired,
rawSql: React.PropTypes.string,
maxWidth: React.PropTypes.number,
maxLines: React.PropTypes.number,
shrink: React.PropTypes.bool,
sql: PropTypes.string.isRequired,
rawSql: PropTypes.string,
maxWidth: PropTypes.number,
maxLines: PropTypes.number,
shrink: PropTypes.bool,
};
class HighlightedSql extends React.Component {
@@ -25,38 +26,35 @@ class HighlightedSql extends React.Component {
};
}
shrinkSql() {
const props = this.props;
const sql = props.sql || '';
const sql = this.props.sql || '';
let lines = sql.split('\n');
if (lines.length >= props.maxLines) {
lines = lines.slice(0, props.maxLines);
if (lines.length >= this.props.maxLines) {
lines = lines.slice(0, this.props.maxLines);
lines.push('{...}');
}
return lines.map((line) => {
if (line.length > props.maxWidth) {
return line.slice(0, props.maxWidth) + '{...}';
if (line.length > this.props.maxWidth) {
return line.slice(0, this.props.maxWidth) + '{...}';
}
return line;
})
.join('\n');
}
triggerNode() {
const props = this.props;
let shownSql = props.shrink ? this.shrinkSql(props.sql) : props.sql;
const shownSql = this.props.shrink ? this.shrinkSql(this.props.sql) : this.props.sql;
return (
<SyntaxHighlighter language="sql" style={github}>
{shownSql}
</SyntaxHighlighter>);
}
generateModal() {
const props = this.props;
let rawSql;
if (props.rawSql && props.rawSql !== this.props.sql) {
if (this.props.rawSql && this.props.rawSql !== this.props.sql) {
rawSql = (
<div>
<h4>Raw SQL</h4>
<SyntaxHighlighter language="sql" style={github}>
{props.rawSql}
{this.props.rawSql}
</SyntaxHighlighter>
</div>
);

View File

@@ -1,14 +1,15 @@
import React from 'react';
import PropTypes from 'prop-types';
import { OverlayTrigger, Tooltip } from 'react-bootstrap';
const propTypes = {
children: React.PropTypes.node,
className: React.PropTypes.string,
href: React.PropTypes.string,
onClick: React.PropTypes.func,
placement: React.PropTypes.string,
style: React.PropTypes.object,
tooltip: React.PropTypes.string,
children: PropTypes.node,
className: PropTypes.string,
href: PropTypes.string,
onClick: PropTypes.func,
placement: PropTypes.string,
style: PropTypes.object,
tooltip: PropTypes.string,
};
const defaultProps = {
className: '',
@@ -22,7 +23,7 @@ const defaultProps = {
class Link extends React.PureComponent {
render() {
let tooltip = (
const tooltip = (
<Tooltip id="tooltip">
{this.props.tooltip}
</Tooltip>
@@ -34,7 +35,7 @@ class Link extends React.PureComponent {
style={this.props.style}
className={'Link ' + this.props.className}
>
{this.props.children}
{this.props.children}
</a>
);
if (this.props.tooltip) {

View File

@@ -1,9 +1,11 @@
import React from 'react';
import PropTypes from 'prop-types';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import * as Actions from '../actions';
const $ = require('jquery');
const QUERY_UPDATE_FREQ = 2000;
const QUERY_UPDATE_BUFFER_MS = 5000;
@@ -19,7 +21,8 @@ class QueryAutoRefresh extends React.PureComponent {
const { queries } = this.props;
const queryKeys = Object.keys(queries);
const queriesAsArray = queryKeys.map(key => queries[key]);
return queriesAsArray.some(q => q.state === 'running' || q.state === 'started');
return queriesAsArray.some(q =>
['running', 'started', 'pending', 'fetching'].indexOf(q.state) >= 0);
}
startTimer() {
if (!(this.timer)) {
@@ -33,7 +36,7 @@ class QueryAutoRefresh extends React.PureComponent {
stopwatch() {
// only poll /superset/queries/ if there are started or running queries
if (this.shouldCheckForQueries()) {
const url = '/superset/queries/' + (this.props.queriesLastUpdate - QUERY_UPDATE_BUFFER_MS);
const url = `/superset/queries/${this.props.queriesLastUpdate - QUERY_UPDATE_BUFFER_MS}`;
$.getJSON(url, (data) => {
if (Object.keys(data).length > 0) {
this.props.actions.refreshQueries(data);
@@ -46,9 +49,9 @@ class QueryAutoRefresh extends React.PureComponent {
}
}
QueryAutoRefresh.propTypes = {
queries: React.PropTypes.object.isRequired,
actions: React.PropTypes.object.isRequired,
queriesLastUpdate: React.PropTypes.number.isRequired,
queries: PropTypes.object.isRequired,
actions: PropTypes.object.isRequired,
queriesLastUpdate: PropTypes.number.isRequired,
};
function mapStateToProps(state) {

View File

@@ -1,11 +1,12 @@
import React from 'react';
import QueryTable from './QueryTable';
import PropTypes from 'prop-types';
import { Alert } from 'react-bootstrap';
import QueryTable from './QueryTable';
const propTypes = {
queries: React.PropTypes.array.isRequired,
actions: React.PropTypes.object.isRequired,
queries: PropTypes.array.isRequired,
actions: PropTypes.object.isRequired,
};
const QueryHistory = (props) => {

View File

@@ -1,5 +1,5 @@
const $ = window.$ = require('jquery');
import React from 'react';
import PropTypes from 'prop-types';
import { Button } from 'react-bootstrap';
import Select from 'react-select';
import QueryTable from './QueryTable';
@@ -8,8 +8,11 @@ import { now, epochTimeXHoursAgo,
import { STATUS_OPTIONS, TIME_OPTIONS } from '../constants';
import AsyncSelect from '../../components/AsyncSelect';
const $ = window.$ = require('jquery');
const propTypes = {
actions: React.PropTypes.object.isRequired,
actions: PropTypes.object.isRequired,
height: PropTypes.number.isRequired,
};
class QuerySearch extends React.PureComponent {
@@ -75,7 +78,7 @@ class QuerySearch extends React.PureComponent {
}
insertParams(baseUrl, params) {
const validParams = params.filter(
function (p) { return p !== ''; }
function (p) { return p !== ''; },
);
return baseUrl + '?' + validParams.join('&');
}
@@ -94,7 +97,7 @@ class QuerySearch extends React.PureComponent {
return options;
}
dbMutator(data) {
const options = data.result.map((db) => ({ value: db.id, label: db.database_name }));
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({
@@ -154,8 +157,8 @@ class QuerySearch extends React.PureComponent {
<Select
name="select-from"
placeholder="[From]-"
options={TIME_OPTIONS.
slice(1, TIME_OPTIONS.length).map((t) => ({ value: t, label: t }))}
options={TIME_OPTIONS
.slice(1, TIME_OPTIONS.length).map(t => ({ value: t, label: t }))}
value={this.state.from}
autosize={false}
onChange={this.changeFrom.bind(this)}
@@ -165,7 +168,7 @@ class QuerySearch extends React.PureComponent {
<Select
name="select-to"
placeholder="[To]-"
options={TIME_OPTIONS.map((t) => ({ value: t, label: t }))}
options={TIME_OPTIONS.map(t => ({ value: t, label: t }))}
value={this.state.to}
autosize={false}
onChange={this.changeTo.bind(this)}
@@ -175,7 +178,7 @@ class QuerySearch extends React.PureComponent {
<Select
name="select-status"
placeholder="[Query Status]"
options={STATUS_OPTIONS.map((s) => ({ value: s, label: s }))}
options={STATUS_OPTIONS.map(s => ({ value: s, label: s }))}
value={this.state.status}
isLoading={false}
autosize={false}
@@ -190,23 +193,23 @@ class QuerySearch extends React.PureComponent {
(<img className="loading" alt="Loading..." src="/static/assets/images/loading.gif" />)
:
(
<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 className="scrollbar-container">
<div
className="scrollbar-content"
style={{ height: this.props.height }}
>
<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>
)
}
</div>

View File

@@ -0,0 +1,19 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Label } from 'react-bootstrap';
import { STATE_BSSTYLE_MAP } from '../constants';
const propTypes = {
query: PropTypes.object.isRequired,
};
export default function QueryStateLabel({ query }) {
const bsStyle = STATE_BSSTYLE_MAP[query.state];
return (
<Label className="m-r-3" bsStyle={bsStyle}>
{query.state}
</Label>
);
}
QueryStateLabel.propTypes = propTypes;

View File

@@ -1,4 +1,5 @@
import React from 'react';
import PropTypes from 'prop-types';
import moment from 'moment';
import { Table } from 'reactable';
@@ -8,16 +9,16 @@ import VisualizeModal from './VisualizeModal';
import ResultSet from './ResultSet';
import ModalTrigger from '../../components/ModalTrigger';
import HighlightedSql from './HighlightedSql';
import { STATE_BSSTYLE_MAP } from '../constants';
import { fDuration } from '../../modules/dates';
import { storeQuery } from '../../../utils/common';
import QueryStateLabel from './QueryStateLabel';
const propTypes = {
columns: React.PropTypes.array,
actions: React.PropTypes.object,
queries: React.PropTypes.array,
onUserClicked: React.PropTypes.func,
onDbClicked: React.PropTypes.func,
columns: PropTypes.array,
actions: PropTypes.object,
queries: PropTypes.array,
onUserClicked: PropTypes.func,
onDbClicked: PropTypes.func,
};
const defaultProps = {
columns: ['started', 'duration', 'rows'],
@@ -134,14 +135,16 @@ class QueryTable extends React.PureComponent {
modalTitle={'Data preview'}
beforeOpen={this.openAsyncResults.bind(this, query)}
onExit={this.clearQueryResults.bind(this, query)}
modalBody={<ResultSet showSql query={query} actions={this.props.actions} />}
modalBody={
<ResultSet showSql query={query} actions={this.props.actions} height={400} />
}
/>
);
} else {
// 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.output = [schemaUsed, q.tempTable].filter(v => (v)).join('.');
}
q.progress = (
<ProgressBar
@@ -161,9 +164,7 @@ class QueryTable extends React.PureComponent {
}
q.state = (
<div>
<span className={'m-r-3 label label-' + STATE_BSSTYLE_MAP[q.state]}>
{q.state}
</span>
<QueryStateLabel query={query} />
{errorTooltip}
</div>
);

View File

@@ -1,42 +1,48 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Alert, Button, ButtonGroup, ProgressBar } from 'react-bootstrap';
import { Table } from 'reactable';
import shortid from 'shortid';
import VisualizeModal from './VisualizeModal';
import HighlightedSql from './HighlightedSql';
import FilterableTable from '../../components/FilterableTable/FilterableTable';
import QueryStateLabel from './QueryStateLabel';
const propTypes = {
actions: React.PropTypes.object,
csv: React.PropTypes.bool,
query: React.PropTypes.object,
search: React.PropTypes.bool,
searchText: React.PropTypes.string,
showSql: React.PropTypes.bool,
visualize: React.PropTypes.bool,
cache: React.PropTypes.bool,
actions: PropTypes.object,
csv: PropTypes.bool,
query: PropTypes.object,
search: PropTypes.bool,
showSql: PropTypes.bool,
visualize: PropTypes.bool,
cache: PropTypes.bool,
height: PropTypes.number.isRequired,
};
const defaultProps = {
search: true,
visualize: true,
showSql: false,
csv: true,
searchText: '',
actions: {},
cache: false,
};
const RESULT_SET_CONTROLS_HEIGHT = 46;
class ResultSet extends React.PureComponent {
export default class ResultSet extends React.PureComponent {
constructor(props) {
super(props);
this.state = {
searchText: '',
showModal: false,
data: [],
resultSetHeight: '0',
data: null,
height: props.search ? props.height - RESULT_SET_CONTROLS_HEIGHT : props.height,
};
}
componentDidMount() {
// only do this the first time the component is rendered/mounted
this.reRunQueryIfSessionTimeoutErrorOnMount();
}
componentWillReceiveProps(nextProps) {
// when new results comes in, save them locally and clear in store
if (this.props.cache && (!nextProps.query.cached)
@@ -44,7 +50,7 @@ class ResultSet extends React.PureComponent {
&& nextProps.query.results.data.length > 0) {
this.setState(
{ data: nextProps.query.results.data },
this.clearQueryResults(nextProps.query)
this.clearQueryResults(nextProps.query),
);
}
if (nextProps.query.resultsKey
@@ -52,36 +58,6 @@ class ResultSet extends React.PureComponent {
this.fetchResults(nextProps.query);
}
}
componentWillMount() {
// hack to get height of result set table so it can be fixed and scroll in place
if (this.state.resultSetHeight === '0') {
// calculate result set table height
// document.getElementById('brace-editor').getBoundingClientRect().height;
const sqlEditorHeight = 192;
// document.getElementById('js-sql-toolbar').getBoundingClientRect().height;
const sqlToolbar = 30;
// document.getElementsByClassName('nav-tabs')[0].getBoundingClientRect().height * 2;
const tabsHeight = 88;
// document.getElementsByTagName('header')[0].getBoundingClientRect().height;
const headerHeight = 59;
// this needs to be hardcoded since this element is in this component and has not mounted yet
const resultsControlsHeight = 30;
const sum =
sqlEditorHeight +
sqlToolbar +
tabsHeight +
resultsControlsHeight +
headerHeight;
this.setState({ resultSetHeight: window.innerHeight - sum - 95 });
}
}
getControls() {
if (this.props.search || this.props.visualize || this.props.csv) {
let csvButton;
@@ -160,41 +136,22 @@ class ResultSet extends React.PureComponent {
reFetchQueryResults(query) {
this.props.actions.reFetchQueryResults(query);
}
reRunQueryIfSessionTimeoutErrorOnMount() {
const { query } = this.props;
if (query.errorMessage && query.errorMessage.indexOf('session timed out') > 0) {
this.props.actions.runQuery(query, true);
}
}
render() {
const query = this.props.query;
const results = query.results;
let data;
if (this.props.cache && query.cached) {
data = this.state.data;
} else {
data = results ? results.data : [];
}
let sql;
if (query.state === 'stopped') {
return <Alert bsStyle="warning">Query was stopped</Alert>;
}
if (this.props.showSql) {
sql = <HighlightedSql sql={query.sql} />;
}
if (['running', 'pending', 'fetching'].indexOf(query.state) > -1) {
let progressBar;
if (query.progress > 0 && query.state === 'running') {
progressBar = (
<ProgressBar
striped
now={query.progress}
label={`${query.progress}%`}
/>);
}
return (
<div>
<img className="loading" alt="Loading..." src="/static/assets/images/loading.gif" />
{progressBar}
</div>
);
if (query.state === 'stopped') {
return <Alert bsStyle="warning">Query was stopped</Alert>;
} else if (query.state === 'failed') {
return <Alert bsStyle="danger">{query.errorMessage}</Alert>;
} else if (query.state === 'success' && query.ctas) {
@@ -213,7 +170,14 @@ class ResultSet extends React.PureComponent {
</Alert>
</div>);
} else if (query.state === 'success') {
if (results && data && data.length > 0) {
const results = query.results;
let data;
if (this.props.cache && query.cached) {
data = this.state.data;
} else if (results && results.data) {
data = results.data;
}
if (data && data.length > 0) {
return (
<div>
<VisualizeModal
@@ -223,30 +187,16 @@ class ResultSet extends React.PureComponent {
/>
{this.getControls.bind(this)()}
{sql}
<div className="ResultSet" style={{ height: `${this.state.resultSetHeight}px` }}>
<Table
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"
filterBy={this.state.searchText}
filterable={results.columns.map((c) => c.name)}
hideFilterInput
/>
</div>
<FilterableTable
data={data}
orderedColumnKeys={results.columns.map(col => col.name)}
height={this.state.height}
filterText={this.state.searchText}
/>
</div>
);
} else if (data && data.length === 0) {
return <Alert bsStyle="warning">The query returned no data</Alert>;
}
}
if (query.cached) {
@@ -260,10 +210,37 @@ class ResultSet extends React.PureComponent {
</Button>
);
}
return <Alert bsStyle="warning">The query returned no data</Alert>;
let progressBar;
let trackingUrl;
if (query.progress > 0 && query.state === 'running') {
progressBar = (
<ProgressBar
striped
now={query.progress}
label={`${query.progress}%`}
/>);
}
if (query.trackingUrl) {
trackingUrl = (
<Button
bsSize="small"
onClick={() => { window.open(query.trackingUrl); }}
>
Track Job
</Button>
);
}
return (
<div>
<img className="loading" alt="Loading..." src="/static/assets/images/loading.gif" />
<QueryStateLabel query={query} />
{progressBar}
<div>
{trackingUrl}
</div>
</div>
);
}
}
ResultSet.propTypes = propTypes;
ResultSet.defaultProps = defaultProps;
export default ResultSet;

View File

@@ -1,14 +1,18 @@
import React, { PropTypes } from 'react';
import React from 'react';
import PropTypes from 'prop-types';
import Button from '../../components/Button';
const propTypes = {
allowAsync: PropTypes.bool.isRequired,
dbId: PropTypes.number.isRequired,
dbId: PropTypes.number,
queryState: PropTypes.string.isRequired,
runQuery: PropTypes.func.isRequired,
selectedText: PropTypes.string,
stopQuery: PropTypes.func.isRequired,
};
const defaultProps = {
allowAsync: false,
};
export default function RunQueryActionButton(props) {
const runBtnText = props.selectedText ? 'Run Selected Query' : 'Run Query';
@@ -28,7 +32,7 @@ export default function RunQueryActionButton(props) {
onClick={() => props.runQuery(false)}
key="run-btn"
>
<i className="fa fa-table" /> {runBtnText}
<i className="fa fa-refresh" /> {runBtnText}
</Button>
);
@@ -69,3 +73,4 @@ export default function RunQueryActionButton(props) {
}
RunQueryActionButton.propTypes = propTypes;
RunQueryActionButton.defaultProps = defaultProps;

View File

@@ -0,0 +1,131 @@
import React from 'react';
import PropTypes from 'prop-types';
import { FormControl, FormGroup, Overlay, Popover, Row, Col } from 'react-bootstrap';
import Button from '../../components/Button';
const propTypes = {
defaultLabel: PropTypes.string,
sql: PropTypes.string,
schema: PropTypes.string,
dbId: PropTypes.number,
animation: PropTypes.bool,
onSave: PropTypes.func,
};
const defaultProps = {
defaultLabel: 'Undefined',
animation: true,
onSave: () => {},
};
class SaveQuery extends React.PureComponent {
constructor(props) {
super(props);
this.state = {
description: '',
label: props.defaultLabel,
showSave: false,
};
this.toggleSave = this.toggleSave.bind(this);
this.onSave = this.onSave.bind(this);
this.onCancel = this.onCancel.bind(this);
this.onLabelChange = this.onLabelChange.bind(this);
this.onDescriptionChange = this.onDescriptionChange.bind(this);
}
onSave() {
const query = {
label: this.state.label,
description: this.state.description,
db_id: this.props.dbId,
schema: this.props.schema,
sql: this.props.sql,
};
this.props.onSave(query);
this.setState({ showSave: false });
}
onCancel() {
this.setState({ showSave: false });
}
onLabelChange(e) {
this.setState({ label: e.target.value });
}
onDescriptionChange(e) {
this.setState({ description: e.target.value });
}
toggleSave(e) {
this.setState({ target: e.target, showSave: !this.state.showSave });
}
renderPopover() {
return (
<Popover id="embed-code-popover">
<FormGroup bsSize="small" style={{ width: '350px' }}>
<Row>
<Col md={12}>
<small>
<label className="control-label" htmlFor="embed-height">
Label
</label>
</small>
<FormControl
type="text"
placeholder="Label for your query"
value={this.state.label}
onChange={this.onLabelChange}
/>
</Col>
</Row>
<br />
<Row>
<Col md={12}>
<small>
<label className="control-label" htmlFor="embed-height">Description</label>
</small>
<FormControl
componentClass="textarea"
placeholder="Write a description for your query"
value={this.state.description}
onChange={this.onDescriptionChange}
/>
</Col>
</Row>
<br />
<Row>
<Col md={12}>
<Button
bsStyle="primary"
onClick={this.onSave}
className="m-r-3"
>
Save
</Button>
<Button onClick={this.onCancel} className="cancelQuery">
Cancel
</Button>
</Col>
</Row>
</FormGroup>
</Popover>
);
}
render() {
return (
<span className="SaveQuery">
<Overlay
trigger="click"
target={this.state.target}
show={this.state.showSave}
placement="bottom"
animation={this.props.animation}
>
{this.renderPopover()}
</Overlay>
<Button bsSize="small" className="toggleSave" onClick={this.toggleSave}>
<i className="fa fa-save" /> Save Query
</Button>
</span>
);
}
}
SaveQuery.propTypes = propTypes;
SaveQuery.defaultProps = defaultProps;
export default SaveQuery;

View File

@@ -1,22 +1,23 @@
import React from 'react';
import PropTypes from 'prop-types';
import shortid from 'shortid';
import { Alert, Tab, Tabs } from 'react-bootstrap';
import QueryHistory from './QueryHistory';
import ResultSet from './ResultSet';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import * as Actions from '../actions';
import React from 'react';
import shortid from 'shortid';
import * as Actions from '../actions';
import QueryHistory from './QueryHistory';
import ResultSet from './ResultSet';
/*
editorQueries are queries executed by users passed from SqlEditor component
dataPrebiewQueries are all queries executed for preview of table data (from SqlEditorLeft)
*/
const propTypes = {
editorQueries: React.PropTypes.array.isRequired,
dataPreviewQueries: React.PropTypes.array.isRequired,
actions: React.PropTypes.object.isRequired,
activeSouthPaneTab: React.PropTypes.string,
editorQueries: PropTypes.array.isRequired,
dataPreviewQueries: PropTypes.array.isRequired,
actions: PropTypes.object.isRequired,
activeSouthPaneTab: PropTypes.string,
};
const defaultProps = {
@@ -24,6 +25,36 @@ const defaultProps = {
};
class SouthPane extends React.PureComponent {
constructor(props) {
super(props);
this.state = {
innerTabHeight: this.getInnerTabHeight(),
};
}
getInnerTabHeight() {
// hack to get height the tab container so it can be fixed and scroll in place
// calculate inner tab height
// document.getElementById('brace-editor').getBoundingClientRect().height;
const sqlEditorHeight = 192;
// document.getElementById('js-sql-toolbar').getBoundingClientRect().height;
const sqlToolbar = 30;
// document.getElementsByClassName('nav-tabs')[0].getBoundingClientRect().height * 2;
const tabsHeight = 88;
// document.getElementsByTagName('header')[0].getBoundingClientRect().height;
const headerHeight = 59;
const sum =
sqlEditorHeight +
sqlToolbar +
tabsHeight +
headerHeight;
return window.innerHeight - sum - 95;
}
switchTab(id) {
this.props.actions.setActiveSouthPaneTab(id);
}
@@ -36,19 +67,32 @@ class SouthPane extends React.PureComponent {
let results;
if (latestQuery) {
results = (
<ResultSet showControls search query={latestQuery} actions={props.actions} />
<ResultSet
showControls
search
query={latestQuery}
actions={props.actions}
height={this.state.innerTabHeight}
/>
);
} else {
results = <Alert bsStyle="info">Run a query to display results here</Alert>;
}
const dataPreviewTabs = props.dataPreviewQueries.map((query) => (
const dataPreviewTabs = props.dataPreviewQueries.map(query => (
<Tab
title={`Preview for ${query.tableName}`}
eventKey={query.id}
key={query.id}
>
<ResultSet query={query} visualize={false} csv={false} actions={props.actions} cache />
<ResultSet
query={query}
visualize={false}
csv={false}
actions={props.actions}
cache
height={this.state.innerTabHeight}
/>
</Tab>
));
@@ -70,7 +114,9 @@ class SouthPane extends React.PureComponent {
title="Query History"
eventKey="History"
>
<QueryHistory queries={props.editorQueries} actions={props.actions} />
<div style={{ height: `${this.state.innerTabHeight}px`, overflow: 'scroll' }}>
<QueryHistory queries={props.editorQueries} actions={props.actions} />
</div>
</Tab>
{dataPreviewTabs}
</Tabs>

View File

@@ -1,4 +1,5 @@
import React from 'react';
import PropTypes from 'prop-types';
import {
Col,
FormGroup,
@@ -15,22 +16,23 @@ import {
import Button from '../../components/Button';
import SouthPane from './SouthPane';
import SaveQuery from './SaveQuery';
import Timer from '../../components/Timer';
import SqlEditorLeftBar from './SqlEditorLeftBar';
import AceEditorWrapper from './AceEditorWrapper';
import { STATE_BSSTYLE_MAP } from '../constants.js';
import { STATE_BSSTYLE_MAP } from '../constants';
import RunQueryActionButton from './RunQueryActionButton';
const propTypes = {
actions: React.PropTypes.object.isRequired,
height: React.PropTypes.string.isRequired,
database: React.PropTypes.object,
latestQuery: React.PropTypes.object,
tables: React.PropTypes.array.isRequired,
editorQueries: React.PropTypes.array.isRequired,
dataPreviewQueries: React.PropTypes.array.isRequired,
queryEditor: React.PropTypes.object.isRequired,
hideLeftBar: React.PropTypes.bool,
actions: PropTypes.object.isRequired,
height: PropTypes.string.isRequired,
database: PropTypes.object,
latestQuery: PropTypes.object,
tables: PropTypes.array.isRequired,
editorQueries: PropTypes.array.isRequired,
dataPreviewQueries: PropTypes.array.isRequired,
queryEditor: PropTypes.object.isRequired,
hideLeftBar: PropTypes.bool,
};
const defaultProps = {
@@ -58,6 +60,9 @@ class SqlEditor extends React.PureComponent {
this.startQuery();
}
}
setQueryEditorSql(sql) {
this.props.actions.queryEditorSetSql(this.props.queryEditor, sql);
}
runQuery(runAsync = false) {
let effectiveRunAsync = runAsync;
if (!this.props.database.allow_run_sync) {
@@ -86,9 +91,6 @@ class SqlEditor extends React.PureComponent {
createTableAs() {
this.startQuery(true, true);
}
setQueryEditorSql(sql) {
this.props.actions.queryEditorSetSql(this.props.queryEditor, sql);
}
ctasChanged(event) {
this.setState({ ctas: event.target.value });
}
@@ -101,6 +103,7 @@ class SqlEditor extends React.PureComponent {
}
render() {
const qe = this.props.queryEditor;
let limitWarning = null;
if (this.props.latestQuery && this.props.latestQuery.limit_reached) {
const tooltip = (
@@ -148,13 +151,20 @@ class SqlEditor extends React.PureComponent {
<div className="pull-left">
<Form inline>
<RunQueryActionButton
allowAsync={this.props.database && this.props.database.allow_run_async}
dbId={this.props.queryEditor.dbId}
allowAsync={this.props.database ? this.props.database.allow_run_async : false}
dbId={qe.dbId}
queryState={this.props.latestQuery && this.props.latestQuery.state}
runQuery={this.runQuery.bind(this)}
selectedText={this.props.queryEditor.selectedText}
selectedText={qe.selectedText}
stopQuery={this.stopQuery.bind(this)}
/>
<SaveQuery
defaultLabel={qe.title}
sql={qe.sql}
onSave={this.props.actions.saveQuery}
schema={qe.schema}
dbId={qe.dbId}
/>
{ctasControls}
</Form>
</div>
@@ -183,16 +193,26 @@ class SqlEditor extends React.PureComponent {
<Collapse
in={!this.props.hideLeftBar}
>
<Col md={3}>
<Col
xs={6}
sm={5}
md={4}
lg={3}
>
<SqlEditorLeftBar
style={{ height: this.props.height }}
height={this.sqlEditorHeight()}
queryEditor={this.props.queryEditor}
tables={this.props.tables}
actions={this.props.actions}
/>
</Col>
</Collapse>
<Col md={this.props.hideLeftBar ? 12 : 9}>
<Col
xs={this.props.hideLeftBar ? 12 : 6}
sm={this.props.hideLeftBar ? 12 : 7}
md={this.props.hideLeftBar ? 12 : 8}
lg={this.props.hideLeftBar ? 12 : 9}
>
<AceEditorWrapper
actions={this.props.actions}
onBlur={this.setQueryEditorSql.bind(this)}

View File

@@ -1,15 +1,20 @@
const $ = window.$ = require('jquery');
/* global notify */
import React from 'react';
import PropTypes from 'prop-types';
import { Button } from 'react-bootstrap';
import TableElement from './TableElement';
import AsyncSelect from '../../components/AsyncSelect';
import Select from 'react-virtualized-select';
import createFilterOptions from 'react-select-fast-filter-options';
import TableElement from './TableElement';
import AsyncSelect from '../../components/AsyncSelect';
const $ = window.$ = require('jquery');
const propTypes = {
queryEditor: React.PropTypes.object.isRequired,
tables: React.PropTypes.array,
actions: React.PropTypes.object,
queryEditor: PropTypes.object.isRequired,
height: PropTypes.number.isRequired,
tables: PropTypes.array,
actions: PropTypes.object,
};
const defaultProps = {
@@ -31,9 +36,10 @@ class SqlEditorLeftBar extends React.PureComponent {
this.fetchSchemas(this.props.queryEditor.dbId);
this.fetchTables(this.props.queryEditor.dbId, this.props.queryEditor.schema);
}
onChange(db) {
const val = (db) ? db.value : null;
onDatabaseChange(db) {
const val = db ? db.value : null;
this.setState({ schemaOptions: [] });
this.props.actions.queryEditorSetSchema(this.props.queryEditor, null);
this.props.actions.queryEditorSetDb(this.props.queryEditor, val);
if (!(db)) {
this.setState({ tableOptions: [] });
@@ -42,8 +48,16 @@ class SqlEditorLeftBar extends React.PureComponent {
this.fetchSchemas(val);
}
}
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 }));
}
dbMutator(data) {
const options = data.result.map((db) => ({ value: db.id, label: db.database_name }));
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({
@@ -56,28 +70,26 @@ class SqlEditorLeftBar extends React.PureComponent {
resetState() {
this.props.actions.resetState();
}
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) {
// This can be large so it shouldn't be put in the Redux store
if (dbId && schema) {
this.setState({ tableLoading: true, tableOptions: [] });
const url = `/superset/tables/${dbId}/${schema}/${substr}/`;
$.get(url, (data) => {
$.get(url).done((data) => {
const filterOptions = createFilterOptions({ options: data.options });
this.setState({
filterOptions,
tableLoading: false,
tableOptions: data.options,
tableLength: data.tableLength,
});
})
.fail(() => {
this.setState({ tableLoading: false, tableOptions: [], tableLength: 0 });
notify.error('Error while fetching table list');
});
} else {
this.setState({ tableLoading: false, tableOptions: [] });
this.setState({ tableLoading: false, tableOptions: [], filterOptions: null });
}
}
changeTable(tableOpt) {
@@ -97,11 +109,7 @@ class SqlEditorLeftBar extends React.PureComponent {
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;
@@ -113,98 +121,110 @@ class SqlEditorLeftBar extends React.PureComponent {
if (actualDbId) {
this.setState({ schemaLoading: true });
const url = `/superset/schemas/${actualDbId}/`;
$.get(url, (data) => {
const schemaOptions = data.schemas.map((s) => ({ value: s, label: s }));
this.setState({ schemaOptions });
this.setState({ schemaLoading: false });
$.get(url).done((data) => {
const schemaOptions = data.schemas.map(s => ({ value: s, label: s }));
this.setState({ schemaOptions, schemaLoading: false });
})
.fail(() => {
this.setState({ schemaLoading: false, schemaOptions: [] });
notify.error('Error while fetching schema list');
});
}
}
closePopover(ref) {
this.refs[ref].hide();
}
render() {
const shouldShowReset = window.location.search === '?reset=1';
const options = this.state.tableOptions;
const filterOptions = createFilterOptions({ options });
const tableMetaDataHeight = this.props.height - 130; // 130 is the height of the selects above
return (
<div className="scrollbar-container">
<div className="clearfix sql-toolbar scrollbar-content">
<div>
<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) => (
<div>
<span className="text-muted">Database:</span> {o.label}
</div>
)}
mutator={this.dbMutator.bind(this)}
placeholder="Select a database"
/>
</div>
<div className="m-t-5">
<div className="clearfix sql-toolbar">
<div>
<AsyncSelect
dataEndpoint={
'/databaseasync/api/' +
'read?_flt_0_expose_in_sqllab=1&' +
'_oc_DatabaseAsync=database_name&' +
'_od_DatabaseAsync=asc'
}
onChange={this.onDatabaseChange.bind(this)}
onAsyncError={() => notify.error('Error while fetching database list')}
value={this.props.queryEditor.dbId}
databaseId={this.props.queryEditor.dbId}
actions={this.props.actions}
valueRenderer={o => (
<div>
<span className="text-muted">Database:</span> {o.label}
</div>
)}
mutator={this.dbMutator.bind(this)}
placeholder="Select a database"
autoSelect
/>
</div>
<div className="m-t-5">
<Select
name="select-schema"
placeholder={`Select a schema (${this.state.schemaOptions.length})`}
options={this.state.schemaOptions}
value={this.props.queryEditor.schema}
valueRenderer={o => (
<div>
<span className="text-muted">Schema:</span> {o.label}
</div>
)}
isLoading={this.state.schemaLoading}
autosize={false}
onChange={this.changeSchema.bind(this)}
/>
</div>
<div className="m-t-5">
{this.props.queryEditor.schema &&
<Select
name="select-schema"
placeholder={`Select a schema (${this.state.schemaOptions.length})`}
options={this.state.schemaOptions}
value={this.props.queryEditor.schema}
valueRenderer={(o) => (
<div>
<span className="text-muted">Schema:</span> {o.label}
</div>
)}
isLoading={this.state.schemaLoading}
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.changeSchema.bind(this)}
onChange={this.changeTable.bind(this)}
filterOptions={this.state.filterOptions}
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>
<div className="m-t-5">
{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)}
filterOptions={filterOptions}
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">
{this.props.tables.map((table) => (
<TableElement
table={table}
key={table.id}
actions={this.props.actions}
/>
))}
</div>
{shouldShowReset &&
<Button bsSize="small" bsStyle="danger" onClick={this.resetState.bind(this)}>
<i className="fa fa-bomb" /> Reset State
</Button>
}
</div>
<hr />
<div className="m-t-5">
<div className="scrollbar-container">
<div className="scrollbar-content" style={{ height: tableMetaDataHeight }}>
{this.props.tables.map(table => (
<TableElement
table={table}
key={table.id}
actions={this.props.actions}
/>
))}
</div>
</div>
</div>
{shouldShowReset &&
<Button bsSize="small" bsStyle="danger" onClick={this.resetState.bind(this)}>
<i className="fa fa-bomb" /> Reset State
</Button>
}
</div>
);
}

View File

@@ -1,21 +1,24 @@
import React from 'react';
import PropTypes from 'prop-types';
import { DropdownButton, MenuItem, Tab, Tabs } from 'react-bootstrap';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import URI from 'urijs';
import * as Actions from '../actions';
import SqlEditor from './SqlEditor';
import CopyQueryTabUrl from './CopyQueryTabUrl';
import { areArraysShallowEqual } from '../../reduxUtils';
import { getParamFromQuery } from '../../../utils/common';
const propTypes = {
actions: React.PropTypes.object.isRequired,
databases: React.PropTypes.object.isRequired,
queries: React.PropTypes.object.isRequired,
queryEditors: React.PropTypes.array,
tabHistory: React.PropTypes.array.isRequired,
tables: React.PropTypes.array.isRequired,
editorHeight: React.PropTypes.string.isRequired,
actions: PropTypes.object.isRequired,
defaultDbId: PropTypes.number,
databases: PropTypes.object.isRequired,
queries: PropTypes.object.isRequired,
queryEditors: PropTypes.array,
tabHistory: PropTypes.array.isRequired,
tables: PropTypes.array.isRequired,
editorHeight: PropTypes.string.isRequired,
};
const defaultProps = {
queryEditors: [],
@@ -35,19 +38,19 @@ class TabbedSqlEditors extends React.PureComponent {
};
}
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');
const query = URI(window.location).search(true);
if (query.id || query.sql || query.savedQueryId) {
if (query.id) {
this.props.actions.popStoredQuery(query.id);
} else if (query.savedQueryId) {
this.props.actions.popSavedQuery(query.savedQueryId);
} else if (query.sql) {
let dbId = query.dbid;
if (dbId) {
dbId = parseInt(dbId, 10);
} else {
const databases = this.props.databases;
const dbName = getParamFromQuery(queryString, 'dbname');
const dbName = query.dbname;
if (dbName) {
Object.keys(databases).forEach((db) => {
if (databases[db].database_name === dbName) {
@@ -57,22 +60,17 @@ class TabbedSqlEditors extends React.PureComponent {
}
}
const newQueryEditor = {
title: getParamFromQuery(queryString, 'title'),
title: query.title,
dbId,
schema: getParamFromQuery(queryString, 'schema'),
autorun: getParamFromQuery(queryString, 'autorun'),
sql: getParamFromQuery(queryString, 'sql'),
schema: query.schema,
autorun: query.autorun,
sql: query.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 nextActiveQeId = nextProps.tabHistory[nextProps.tabHistory.length - 1];
const queriesArray = [];
@@ -96,6 +94,11 @@ class TabbedSqlEditors extends React.PureComponent {
this.setState({ dataPreviewQueries });
}
}
popNewTab() {
queryCount++;
// Clean the url in browser history
window.history.replaceState({}, document.title, this.state.sqlLabUrl);
}
renameTab(qe) {
/* eslint no-alert: 0 */
const newTitle = prompt('Enter a new title for the tab');
@@ -190,7 +193,7 @@ class TabbedSqlEditors extends React.PureComponent {
{isSelected &&
<SqlEditor
height={this.props.editorHeight}
tables={this.props.tables.filter((t) => (t.queryEditorId === qe.id))}
tables={this.props.tables.filter(t => (t.queryEditorId === qe.id))}
queryEditor={qe}
editorQueries={this.state.queriesArray}
dataPreviewQueries={this.state.dataPreviewQueries}

View File

@@ -1,4 +1,5 @@
import React from 'react';
import PropTypes from 'prop-types';
import { ButtonGroup, Collapse, Well } from 'react-bootstrap';
import shortid from 'shortid';
@@ -7,11 +8,12 @@ import CopyToClipboard from '../../components/CopyToClipboard';
import Link from './Link';
import ColumnElement from './ColumnElement';
import ModalTrigger from '../../components/ModalTrigger';
import Loading from '../../components/Loading';
const propTypes = {
table: React.PropTypes.object,
actions: React.PropTypes.object,
timeout: React.PropTypes.number, // used for tests
table: PropTypes.object,
actions: PropTypes.object,
timeout: PropTypes.number, // used for tests
};
const defaultProps = {
@@ -57,7 +59,11 @@ class TableElement extends React.PureComponent {
this.setState({ sortColumns: !this.state.sortColumns });
}
renderHeader() {
removeFromStore() {
this.props.actions.removeTable(this.props.table);
}
renderWell() {
const table = this.props.table;
let header;
if (table.partitions) {
@@ -92,40 +98,9 @@ class TableElement extends React.PureComponent {
}
return header;
}
renderMetadata() {
const table = this.props.table;
let cols;
if (table.columns) {
cols = table.columns.slice();
if (this.state.sortColumns) {
cols.sort((a, b) => a.name.toUpperCase() > b.name.toUpperCase());
}
}
const metadata = (
<Collapse
in={table.expanded}
timeout={this.props.timeout}
>
<div>
{this.renderHeader()}
<div className="table-columns">
{cols && cols.map(col => (
<ColumnElement column={col} key={col.name} />
))}
<hr />
</div>
</div>
</Collapse>
);
return metadata;
}
removeFromStore() {
this.props.actions.removeTable(this.props.table);
}
render() {
const table = this.props.table;
renderControls() {
let keyLink;
const table = this.props.table;
if (table.indexes && table.indexes.length > 0) {
keyLink = (
<ModalTrigger
@@ -146,6 +121,94 @@ class TableElement extends React.PureComponent {
/>
);
}
return (
<ButtonGroup className="ws-el-controls pull-right">
{keyLink}
<Link
className={
`fa fa-sort-${!this.state.sortColumns ? 'alpha' : 'numeric'}-asc ` +
'pull-left sort-cols m-l-2'}
onClick={this.toggleSortColumns.bind(this)}
tooltip={
!this.state.sortColumns ?
'Sort columns alphabetically' :
'Original table column order'}
href="#"
/>
{table.selectStar &&
<CopyToClipboard
copyNode={
<a className="fa fa-clipboard pull-left m-l-2" />
}
text={table.selectStar}
shouldShowText={false}
tooltipText="Copy SELECT statement to clipboard"
/>
}
<Link
className="fa fa-times table-remove pull-left m-l-2"
onClick={this.removeTable.bind(this)}
tooltip="Remove table preview"
href="#"
/>
</ButtonGroup>
);
}
renderHeader() {
const table = this.props.table;
return (
<div className="clearfix">
<div className="pull-left">
<a
href="#"
className="table-name"
onClick={(e) => { this.toggleTable(e); }}
>
<strong>{table.name}</strong>
<small className="m-l-5">
<i className={`fa fa-${table.expanded ? 'minus' : 'plus'}-square-o`} />
</small>
</a>
</div>
<div className="pull-right">
{table.isMetadataLoading || table.isExtraMetadataLoading ?
<Loading size={20} />
:
this.renderControls()
}
</div>
</div>
);
}
renderBody() {
const table = this.props.table;
let cols;
if (table.columns) {
cols = table.columns.slice();
if (this.state.sortColumns) {
cols.sort((a, b) => a.name.toUpperCase() > b.name.toUpperCase());
}
}
const metadata = (
<Collapse
in={table.expanded}
timeout={this.props.timeout}
>
<div>
{this.renderWell()}
<div className="table-columns">
{cols && cols.map(col => (
<ColumnElement column={col} key={col.name} />
))}
<hr />
</div>
</div>
</Collapse>
);
return metadata;
}
render() {
return (
<Collapse
in={this.state.expanded}
@@ -154,54 +217,9 @@ class TableElement extends React.PureComponent {
onExited={this.removeFromStore.bind(this)}
>
<div className="TableElement">
<div className="clearfix">
<div className="pull-left">
<a
href="#"
className="table-name"
onClick={(e) => { this.toggleTable(e); }}
>
<strong>{table.name}</strong>
<small className="m-l-5">
<i className={`fa fa-${table.expanded ? 'minus' : 'plus'}-square-o`} />
</small>
</a>
</div>
<div className="pull-right">
<ButtonGroup className="ws-el-controls pull-right">
{keyLink}
<Link
className={
`fa fa-sort-${!this.state.sortColumns ? 'alpha' : 'numeric'}-asc ` +
'pull-left sort-cols m-l-2'}
onClick={this.toggleSortColumns.bind(this)}
tooltip={
!this.state.sortColumns ?
'Sort columns alphabetically' :
'Original table column order'}
href="#"
/>
{table.selectStar &&
<CopyToClipboard
copyNode={
<a className="fa fa-clipboard pull-left m-l-2" />
}
text={table.selectStar}
shouldShowText={false}
tooltipText="Copy SELECT statement to clipboard"
/>
}
<Link
className="fa fa-trash table-remove pull-left m-l-2"
onClick={this.removeTable.bind(this)}
tooltip="Remove table preview"
href="#"
/>
</ButtonGroup>
</div>
</div>
{this.renderHeader()}
<div>
{this.renderMetadata()}
{this.renderBody()}
</div>
</div>
</Collapse>

View File

@@ -1,10 +1,18 @@
/* global notify */
import moment from 'moment';
import React from 'react';
import PropTypes from 'prop-types';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import { Alert, Button, Col, Modal } from 'react-bootstrap';
import Select from 'react-select';
import { Table } from 'reactable';
import shortid from 'shortid';
import $ from 'jquery';
import { getExploreUrl } from '../../explore/exploreUtils';
import * as actions from '../actions';
import { VISUALIZE_VALIDATION_ERRORS } from '../constants';
import { QUERY_TIMEOUT_THRESHOLD } from '../../constants';
const CHART_TYPES = [
{ value: 'dist_bar', label: 'Distribution - Bar Chart', requiresTime: false },
@@ -14,9 +22,12 @@ const CHART_TYPES = [
];
const propTypes = {
onHide: React.PropTypes.func,
query: React.PropTypes.object,
show: React.PropTypes.bool,
actions: PropTypes.object.isRequired,
onHide: PropTypes.func,
query: PropTypes.object,
show: PropTypes.bool,
datasource: PropTypes.string,
errorMessage: PropTypes.string,
};
const defaultProps = {
show: false,
@@ -30,28 +41,26 @@ class VisualizeModal extends React.PureComponent {
this.state = {
chartType: CHART_TYPES[0],
datasourceName: this.datasourceName(),
columns: {},
columns: this.getColumnFromProps(),
hints: [],
};
}
componentDidMount() {
this.validate();
}
componentWillReceiveProps(nextProps) {
this.setStateFromProps(nextProps);
}
setStateFromProps(props) {
if (
getColumnFromProps() {
const props = this.props;
if (!props ||
!props.query ||
!props.query.results ||
!props.query.results.columns) {
return;
return {};
}
const columns = {};
props.query.results.columns.forEach((col) => {
columns[col.name] = col;
});
this.setState({ columns });
return columns;
}
datasourceName() {
const { query } = this.props;
@@ -79,7 +88,7 @@ class VisualizeModal extends React.PureComponent {
}
});
if (this.state.chartType === null) {
hints.push('Pick a chart type!');
hints.push(VISUALIZE_VALIDATION_ERRORS.REQUIRE_CHART_TYPE);
} else if (this.state.chartType.requiresTime) {
let hasTime = false;
for (const colName in cols) {
@@ -89,7 +98,7 @@ class VisualizeModal extends React.PureComponent {
}
}
if (!hasTime) {
hints.push('To use this chart type you need at least one column flagged as a date');
hints.push(VISUALIZE_VALIDATION_ERRORS.REQUIRE_TIME);
}
}
this.setState({ hints });
@@ -108,29 +117,55 @@ class VisualizeModal extends React.PureComponent {
}
return columns;
}
visualize() {
const vizOptions = {
buildVizOptions() {
return {
chartType: this.state.chartType.value,
datasourceName: this.state.datasourceName,
columns: this.state.columns,
sql: this.props.query.executedSql,
sql: this.props.query.sql,
dbId: this.props.query.dbId,
};
$.ajax({
type: 'POST',
url: '/superset/sqllab_viz/',
async: false,
data: {
data: JSON.stringify(vizOptions),
},
success: (url) => {
window.open(url);
},
});
}
buildVisualizeAdvise() {
let advise;
const queryDuration = moment.duration(this.props.query.endDttm - this.props.query.startDttm);
if (Math.round(queryDuration.asMilliseconds()) > QUERY_TIMEOUT_THRESHOLD) {
advise = (
<Alert bsStyle="warning">
This query took {Math.round(queryDuration.asSeconds())} seconds to run,
and the explore view times out at {QUERY_TIMEOUT_THRESHOLD / 1000} seconds,
following this flow will most likely lead to your query timing out.
We recommend your summarize your data further before following that flow.
If activated you can use the <strong>CREATE TABLE AS</strong> feature
to store a summarized data set that you can then explore.
</Alert>);
}
return advise;
}
visualize() {
this.props.actions.createDatasource(this.buildVizOptions(), this)
.done(() => {
const columns = Object.keys(this.state.columns).map(k => this.state.columns[k]);
const mainGroupBy = columns.filter(d => d.is_dim)[0];
const formData = {
datasource: this.props.datasource,
viz_type: this.state.chartType.value,
since: '100 years ago',
limit: '0',
};
if (mainGroupBy) {
formData.groupby = [mainGroupBy.name];
}
notify.info('Creating a data source and popping a new tab');
window.open(getExploreUrl(formData));
})
.fail(() => {
notify.error(this.props.errorMessage);
});
}
changeDatasourceName(event) {
this.setState({ datasourceName: event.target.value });
this.validate();
this.setState({ datasourceName: event.target.value }, this.validate);
}
changeCheckbox(attr, columnName, event) {
let columns = this.mergedColumns();
@@ -157,7 +192,7 @@ class VisualizeModal extends React.PureComponent {
</div>
);
}
const tableData = this.props.query.results.columns.map((col) => ({
const tableData = this.props.query.results.columns.map(col => ({
column: col.name,
is_dimension: (
<input
@@ -200,6 +235,7 @@ class VisualizeModal extends React.PureComponent {
</Modal.Header>
<Modal.Body>
{alerts}
{this.buildVisualizeAdvise()}
<div className="row">
<Col md={6}>
Chart Type
@@ -246,4 +282,18 @@ class VisualizeModal extends React.PureComponent {
VisualizeModal.propTypes = propTypes;
VisualizeModal.defaultProps = defaultProps;
export default VisualizeModal;
function mapStateToProps(state) {
return {
datasource: state.datasource,
errorMessage: state.errorMessage,
};
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators(actions, dispatch),
};
}
export { VisualizeModal };
export default connect(mapStateToProps, mapDispatchToProps)(VisualizeModal);

View File

@@ -22,3 +22,10 @@ export const TIME_OPTIONS = [
'90 days ago',
'1 year ago',
];
export const VISUALIZE_VALIDATION_ERRORS = {
REQUIRE_CHART_TYPE: 'Pick a chart type!',
REQUIRE_TIME: 'To use this chart type you need at least one column flagged as a date',
REQUIRE_DIMENSION: 'To use this chart type you need at least one dimension',
REQUIRE_AGGREGATION_FUNCTION: 'To use this chart type you need at least one aggregation function',
};

View File

@@ -1,25 +1,27 @@
const $ = window.$ = require('jquery');
const jQuery = window.jQuery = $; // eslint-disable-line
require('bootstrap');
import React from 'react';
import { render } from 'react-dom';
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';
import { getInitialState, sqlLabReducer } from './reducers';
import { initEnhancer } from '../reduxUtils';
import { initJQueryAjax } from '../modules/utils';
import App from './components/App';
import { appSetup } from '../common';
import './main.css';
import '../../stylesheets/reactable-pagination.css';
import '../components/FilterableTable/FilterableTableStyles.css';
require('./main.css');
appSetup();
initJQueryAjax();
const appContainer = document.getElementById('app');
const bootstrapData = JSON.parse(appContainer.getAttribute('data-bootstrap'));
const state = Object.assign({}, getInitialState(bootstrapData.defaultDbId), bootstrapData);
let store = createStore(
const store = createStore(
sqlLabReducer, state, compose(applyMiddleware(thunkMiddleware), initEnhancer()));
// jquery hack to highlight the navbar menu
@@ -29,5 +31,5 @@ render(
<Provider store={store}>
<App />
</Provider>,
appContainer
appContainer,
);

View File

@@ -237,16 +237,6 @@ div.tablePopover:hover {
padding-bottom: 3px;
padding-top: 3px;
}
.ResultSet {
overflow: auto;
border-bottom: 1px solid #ccc;
}
.ResultSet table {
margin-bottom: 0px;
}
.ResultSet table tr.last {
border-bottom: none;
}
.ace_editor {
border: 1px solid #ccc;
margin: 0px 0px 10px 0px;
@@ -275,7 +265,7 @@ div.tablePopover:hover {
}
.QueryTable .label {
margin-top: 5px;
display: inline-block;
}
.ResultsModal .modal-body {

View File

@@ -2,7 +2,7 @@ import shortid from 'shortid';
import * as actions from './actions';
import { now } from '../modules/dates';
import { addToObject, alterInObject, alterInArr, removeFromArr, getFromArr, addToArr }
from '../reduxUtils.js';
from '../reduxUtils';
export function getInitialState(defaultDbId) {
const defaultQueryEditor = {
@@ -36,7 +36,7 @@ export const sqlLabReducer = function (state, action) {
return addToArr(newState, 'queryEditors', action.queryEditor);
},
[actions.CLONE_QUERY_TO_NEW_TAB]() {
const progenitor = state.queryEditors.find((qe) =>
const progenitor = state.queryEditors.find(qe =>
qe.id === state.tabHistory[state.tabHistory.length - 1]);
const qe = {
id: shortid.generate(),
@@ -52,7 +52,7 @@ export const sqlLabReducer = function (state, action) {
[actions.REMOVE_QUERY_EDITOR]() {
let newState = removeFromArr(state, 'queryEditors', action.queryEditor);
// List of remaining queryEditor ids
const qeIds = newState.queryEditors.map((qe) => qe.id);
const qeIds = newState.queryEditors.map(qe => qe.id);
const queries = {};
Object.keys(state.queries).forEach((k) => {
const query = state.queries[k];
@@ -61,7 +61,7 @@ export const sqlLabReducer = function (state, action) {
}
});
let tabHistory = state.tabHistory.slice();
tabHistory = tabHistory.filter((id) => qeIds.indexOf(id) > -1);
tabHistory = tabHistory.filter(id => qeIds.indexOf(id) > -1);
newState = Object.assign({}, newState, { tabHistory, queries });
return newState;
},
@@ -187,7 +187,7 @@ export const sqlLabReducer = function (state, action) {
return alterInObject(state, 'queries', action.query, alts);
},
[actions.SET_ACTIVE_QUERY_EDITOR]() {
const qeIds = state.queryEditors.map((qe) => qe.id);
const qeIds = state.queryEditors.map(qe => qe.id);
if (qeIds.indexOf(action.queryEditor.id) > -1) {
const tabHistory = state.tabHistory.slice();
tabHistory.push(action.queryEditor.id);
@@ -233,11 +233,14 @@ export const sqlLabReducer = function (state, action) {
let newQueries = Object.assign({}, state.queries);
// Fetch the updates to the queries present in the store.
let change = false;
let queriesLastUpdate = state.queriesLastUpdate;
for (const id in action.alteredQueries) {
const changedQuery = action.alteredQueries[id];
if (!state.queries.hasOwnProperty(id) ||
(state.queries[id].changedOn !== changedQuery.changedOn &&
state.queries[id].state !== 'stopped')) {
state.queries[id].state !== 'stopped') {
if (changedQuery.changedOn > queriesLastUpdate) {
queriesLastUpdate = changedQuery.changedOn;
}
newQueries[id] = Object.assign({}, state.queries[id], changedQuery);
change = true;
}
@@ -245,9 +248,27 @@ export const sqlLabReducer = function (state, action) {
if (!change) {
newQueries = state.queries;
}
const queriesLastUpdate = now();
return Object.assign({}, state, { queries: newQueries, queriesLastUpdate });
},
[actions.CREATE_DATASOURCE_STARTED]() {
return Object.assign({}, state, {
isDatasourceLoading: true,
errorMessage: null,
});
},
[actions.CREATE_DATASOURCE_SUCCESS]() {
return Object.assign({}, state, {
isDatasourceLoading: false,
errorMessage: null,
datasource: action.datasource,
});
},
[actions.CREATE_DATASOURCE_FAILED]() {
return Object.assign({}, state, {
isDatasourceLoading: false,
errorMessage: action.err,
});
},
};
if (action.type in actionHandlers) {
return actionHandlers[action.type]();

View File

@@ -0,0 +1,98 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Button, Panel, Grid, Row, Col } from 'react-bootstrap';
import Select from 'react-virtualized-select';
import visTypes from '../explore/stores/visTypes';
const propTypes = {
datasources: PropTypes.arrayOf(PropTypes.shape({
label: PropTypes.string.isRequired,
value: PropTypes.string.isRequired,
})).isRequired,
};
export default class AddSliceContainer extends React.PureComponent {
constructor(props) {
super(props);
const visTypeKeys = Object.keys(visTypes);
this.vizTypeOptions = visTypeKeys.map(vt => ({ label: visTypes[vt].label, value: vt }));
this.state = {
visType: 'table',
};
}
exploreUrl() {
const baseUrl = `/superset/explore/${this.state.datasourceType}/${this.state.datasourceId}`;
const formData = encodeURIComponent(JSON.stringify({ viz_type: this.state.visType }));
return `${baseUrl}?form_data=${formData}`;
}
gotoSlice() {
window.location.href = this.exploreUrl();
}
changeDatasource(e) {
this.setState({
datasourceValue: e.value,
datasourceId: e.value.split('__')[0],
datasourceType: e.value.split('__')[1],
});
}
changeVisType(e) {
this.setState({ visType: e.value });
}
isBtnDisabled() {
return !(this.state.datasourceId && this.state.visType);
}
render() {
return (
<div className="container">
<Panel header={<h3>Create a new slice</h3>}>
<Grid>
<Row>
<Col xs={12} sm={6}>
<div>
<p>Choose a datasource</p>
<Select
clearable={false}
name="select-datasource"
onChange={this.changeDatasource.bind(this)}
options={this.props.datasources}
placeholder="Choose a datasource"
value={this.state.datasourceValue}
/>
</div>
<br />
<div>
<p>Choose a visualization type</p>
<Select
clearable={false}
name="select-vis-type"
onChange={this.changeVisType.bind(this)}
options={this.vizTypeOptions}
placeholder="Choose a visualization type"
value={this.state.visType}
/>
</div>
<br />
<Button
bsStyle="primary"
disabled={this.isBtnDisabled()}
onClick={this.gotoSlice.bind(this)}
>
Create new slice
</Button>
<br /><br />
</Col>
</Row>
</Grid>
</Panel>
</div>
);
}
}
AddSliceContainer.propTypes = propTypes;

View File

@@ -0,0 +1,14 @@
import React from 'react';
import ReactDOM from 'react-dom';
import { appSetup } from '../common';
import AddSliceContainer from './AddSliceContainer';
appSetup();
const addSliceContainer = document.getElementById('js-add-slice-container');
const bootstrapData = JSON.parse(addSliceContainer.getAttribute('data-bootstrap'));
ReactDOM.render(
<AddSliceContainer datasources={bootstrapData.datasources} />,
addSliceContainer,
);

View File

@@ -1,5 +1,8 @@
const $ = require('jquery');
/* eslint-disable global-require */
import $ from 'jquery';
const utils = require('./modules/utils');
$(document).ready(function () {
$(':checkbox[data-checkbox-api-prefix]').change(function () {
const $this = $(this);
@@ -8,3 +11,11 @@ $(document).ready(function () {
utils.toggleCheckbox(prefix, '#' + id);
});
});
export function appSetup() {
// A set of hacks to allow apps to run within a FAB template
// this allows for the server side generated menus to function
window.$ = $;
window.jQuery = $;
require('bootstrap');
}

View File

@@ -0,0 +1,18 @@
import React from 'react';
import AlertContainer from 'react-alert';
export default class AlertsWrapper extends React.PureComponent {
render() {
return (
<AlertContainer
ref={(ref) => {
global.notify = ref;
}}
offset={14}
position="top right"
theme="dark"
time={5000}
transition="fade"
/>);
}
}

View File

@@ -1,19 +1,24 @@
const $ = window.$ = require('jquery');
import React from 'react';
import PropTypes from 'prop-types';
import Select from 'react-select';
const $ = window.$ = require('jquery');
const propTypes = {
dataEndpoint: React.PropTypes.string.isRequired,
onChange: React.PropTypes.func.isRequired,
mutator: React.PropTypes.func.isRequired,
value: React.PropTypes.number,
valueRenderer: React.PropTypes.func,
placeholder: React.PropTypes.string,
dataEndpoint: PropTypes.string.isRequired,
onChange: PropTypes.func.isRequired,
mutator: PropTypes.func.isRequired,
onAsyncError: PropTypes.func,
value: PropTypes.number,
valueRenderer: PropTypes.func,
placeholder: PropTypes.string,
autoSelect: PropTypes.bool,
};
const defaultProps = {
placeholder: 'Select ...',
valueRenderer: (o) => (<div>{o.label}</div>),
valueRenderer: o => (<div>{o.label}</div>),
onAsyncError: () => {},
};
class AsyncSelect extends React.PureComponent {
@@ -27,16 +32,26 @@ class AsyncSelect extends React.PureComponent {
componentDidMount() {
this.fetchOptions();
}
onChange(opt) {
this.props.onChange(opt);
}
fetchOptions() {
this.setState({ isLoading: true });
const mutator = this.props.mutator;
$.get(this.props.dataEndpoint, (data) => {
this.setState({ options: mutator ? mutator(data) : data, isLoading: false });
if (!this.props.value && this.props.autoSelect && this.state.options.length) {
this.onChange(this.state.options[0]);
}
})
.fail(() => {
this.props.onAsyncError();
})
.always(() => {
this.setState({ isLoading: false });
});
}
onChange(opt) {
this.props.onChange(opt);
}
render() {
return (
<div>

View File

@@ -1,4 +1,5 @@
import React, { PropTypes } from 'react';
import React from 'react';
import PropTypes from 'prop-types';
import { Button as BootstrapButton, Tooltip, OverlayTrigger } from 'react-bootstrap';
import { slugify } from '../modules/utils';

View File

@@ -0,0 +1,69 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Label } from 'react-bootstrap';
import moment from 'moment';
import TooltipWrapper from './TooltipWrapper';
const propTypes = {
onClick: PropTypes.func,
cachedTimestamp: PropTypes.string,
className: PropTypes.string,
};
class CacheLabel extends React.PureComponent {
constructor(props) {
super(props);
this.state = {
tooltipContent: '',
hovered: false,
};
}
updateTooltipContent() {
const cachedText = this.props.cachedTimestamp ? (
<span>
Loaded data cached <b>{moment.utc(this.props.cachedTimestamp).fromNow()}</b>
</span>) :
'Loaded from cache';
const tooltipContent = (
<span>
{cachedText}.
Click to force-refresh
</span>
);
this.setState({ tooltipContent });
}
mouseOver() {
this.updateTooltipContent();
this.setState({ hovered: true });
}
mouseOut() {
this.setState({ hovered: false });
}
render() {
const labelStyle = this.state.hovered ? 'primary' : 'default';
return (
<TooltipWrapper
tooltip={this.state.tooltipContent}
label="cache-desc"
>
<Label
className={this.props.className}
bsStyle={labelStyle}
style={{ fontSize: '10px', marginRight: '5px', cursor: 'pointer' }}
onClick={this.props.onClick}
onMouseOver={this.mouseOver.bind(this)}
onMouseOut={this.mouseOut.bind(this)}
>
cached <i className="fa fa-refresh" />
</Label>
</TooltipWrapper>);
}
}
CacheLabel.propTypes = propTypes;
export default CacheLabel;

View File

@@ -0,0 +1,34 @@
import React from 'react';
import PropTypes from 'prop-types';
import InfoTooltipWithTrigger from './InfoTooltipWithTrigger';
const propTypes = {
column: PropTypes.object.isRequired,
};
export default function ColumnOption({ column }) {
return (
<span>
<span className="m-r-5 option-label">
{column.verbose_name || column.column_name}
</span>
{column.description &&
<InfoTooltipWithTrigger
className="m-r-5 text-muted"
icon="info"
tooltip={column.description}
label={`descr-${column.column_name}`}
/>
}
{column.expression && column.expression !== column.column_name &&
<InfoTooltipWithTrigger
className="m-r-5 text-muted"
icon="question-circle-o"
tooltip={column.expression}
label={`expr-${column.column_name}`}
/>
}
</span>);
}
ColumnOption.propTypes = propTypes;

View File

@@ -1,4 +1,5 @@
import React, { PropTypes } from 'react';
import React from 'react';
import PropTypes from 'prop-types';
import { Tooltip, OverlayTrigger, MenuItem } from 'react-bootstrap';
const propTypes = {
@@ -97,7 +98,7 @@ export default class CopyToClipboard extends React.Component {
onClick={this.onClick.bind(this)}
onMouseOut={this.onMouseOut}
>
{this.props.copyNode}
{this.props.copyNode}
</OverlayTrigger>
</span>
);

View File

@@ -0,0 +1,93 @@
import React from 'react';
import PropTypes from 'prop-types';
import TooltipWrapper from './TooltipWrapper';
const propTypes = {
title: PropTypes.string,
canEdit: PropTypes.bool,
onSaveTitle: PropTypes.func.isRequired,
};
const defaultProps = {
title: 'Title',
canEdit: false,
};
class EditableTitle extends React.PureComponent {
constructor(props) {
super(props);
this.state = {
isEditing: false,
title: this.props.title,
lastTitle: this.props.title,
};
this.handleClick = this.handleClick.bind(this);
this.handleBlur = this.handleBlur.bind(this);
this.handleChange = this.handleChange.bind(this);
this.handleKeyPress = this.handleKeyPress.bind(this);
}
handleClick() {
if (!this.props.canEdit) {
return;
}
this.setState({
isEditing: true,
});
}
handleBlur() {
if (!this.props.canEdit) {
return;
}
this.setState({
isEditing: false,
});
if (this.state.lastTitle !== this.state.title) {
this.setState({
lastTitle: this.state.title,
});
this.props.onSaveTitle(this.state.title);
}
}
handleChange(ev) {
if (!this.props.canEdit) {
return;
}
this.setState({
title: ev.target.value,
});
}
handleKeyPress(ev) {
if (ev.key === 'Enter') {
ev.preventDefault();
this.handleBlur();
}
}
render() {
return (
<span className="editable-title">
<TooltipWrapper
label="title"
tooltip={this.props.canEdit ? 'click to edit title' : 'You don\'t have the rights to alter this title.'}
>
<input
required
type={this.state.isEditing ? 'text' : 'button'}
value={this.state.title}
onChange={this.handleChange}
onBlur={this.handleBlur}
onClick={this.handleClick}
onKeyPress={this.handleKeyPress}
/>
</TooltipWrapper>
</span>
);
}
}
EditableTitle.propTypes = propTypes;
EditableTitle.defaultProps = defaultProps;
export default EditableTitle;

View File

@@ -1,4 +1,5 @@
import React, { PropTypes } from 'react';
import React from 'react';
import PropTypes from 'prop-types';
import cx from 'classnames';
import TooltipWrapper from './TooltipWrapper';

View File

@@ -0,0 +1,193 @@
import { List } from 'immutable';
import PropTypes from 'prop-types';
import React, { PureComponent } from 'react';
import {
Column,
Table,
SortDirection,
SortIndicator,
} from 'react-virtualized';
import { getTextWidth } from '../../modules/visUtils';
const propTypes = {
orderedColumnKeys: PropTypes.array.isRequired,
data: PropTypes.array.isRequired,
height: PropTypes.number.isRequired,
filterText: PropTypes.string,
headerHeight: PropTypes.number,
overscanRowCount: PropTypes.number,
rowHeight: PropTypes.number,
striped: PropTypes.bool,
};
const defaultProps = {
filterText: '',
headerHeight: 32,
overscanRowCount: 10,
rowHeight: 32,
striped: true,
};
export default class FilterableTable extends PureComponent {
constructor(props) {
super(props);
this.list = List(this.formatTableData(props.data));
this.headerRenderer = this.headerRenderer.bind(this);
this.rowClassName = this.rowClassName.bind(this);
this.sort = this.sort.bind(this);
this.widthsForColumnsByKey = this.getWidthsForColumns();
this.totalTableWidth = props.orderedColumnKeys
.map(key => this.widthsForColumnsByKey[key])
.reduce((curr, next) => curr + next);
this.state = {
sortBy: null,
sortDirection: SortDirection.ASC,
fitted: false,
};
}
componentDidMount() {
this.fitTableToWidthIfNeeded();
}
getDatum(list, index) {
return list.get(index % list.size);
}
getWidthsForColumns() {
const PADDING = 40; // accounts for cell padding and width of sorting icon
const widthsByColumnKey = {};
this.props.orderedColumnKeys.forEach((key) => {
const colWidths = this.list
.map(d => getTextWidth(d[key]) + PADDING) // get width for each value for a key
.push(getTextWidth(key) + PADDING); // add width of column key to end of list
// set max width as value for key
widthsByColumnKey[key] = Math.max(...colWidths);
});
return widthsByColumnKey;
}
fitTableToWidthIfNeeded() {
const containerWidth = this.container.getBoundingClientRect().width;
if (containerWidth > this.totalTableWidth) {
this.totalTableWidth = containerWidth - 2; // accomodates 1px border on container
}
this.setState({ fitted: true });
}
formatTableData(data) {
const formattedData = data.map((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;
});
return formattedData;
}
hasMatch(text, row) {
const values = [];
for (const key in row) {
if (row.hasOwnProperty(key)) {
values.push(row[key].toLowerCase());
}
}
return values.some(v => v.includes(text.toLowerCase()));
}
headerRenderer({ dataKey, label, sortBy, sortDirection }) {
return (
<div>
{label}
{sortBy === dataKey &&
<SortIndicator sortDirection={sortDirection} />
}
</div>
);
}
rowClassName({ index }) {
let className = '';
if (this.props.striped) {
className = index % 2 === 0 ? 'even-row' : 'odd-row';
}
return className;
}
sort({ sortBy, sortDirection }) {
this.setState({ sortBy, sortDirection });
}
render() {
const { sortBy, sortDirection } = this.state;
const {
filterText,
headerHeight,
height,
orderedColumnKeys,
overscanRowCount,
rowHeight,
} = this.props;
let sortedAndFilteredList = this.list;
// filter list
if (filterText) {
sortedAndFilteredList = this.list.filter(row => this.hasMatch(filterText, row));
}
// sort list
if (sortBy) {
sortedAndFilteredList = sortedAndFilteredList
.sortBy(item => item[sortBy])
.update(list => sortDirection === SortDirection.DESC ? list.reverse() : list);
}
const rowGetter = ({ index }) => this.getDatum(sortedAndFilteredList, index);
return (
<div
style={{ height }}
className="filterable-table-container"
ref={(ref) => { this.container = ref; }}
>
{this.state.fitted &&
<Table
ref="Table"
headerHeight={headerHeight}
height={height - 2}
overscanRowCount={overscanRowCount}
rowClassName={this.rowClassName}
rowHeight={rowHeight}
rowGetter={rowGetter}
rowCount={sortedAndFilteredList.size}
sort={this.sort}
sortBy={sortBy}
sortDirection={sortDirection}
width={this.totalTableWidth}
>
{orderedColumnKeys.map(columnKey => (
<Column
dataKey={columnKey}
disableSort={false}
headerRenderer={this.headerRenderer}
width={this.widthsForColumnsByKey[columnKey]}
label={columnKey}
key={columnKey}
/>
))}
</Table>
}
</div>
);
}
}
FilterableTable.propTypes = propTypes;
FilterableTable.defaultProps = defaultProps;

View File

@@ -0,0 +1,60 @@
.ReactVirtualized__Table__headerRow {
font-weight: 700;
display: flex;
flex-direction: row;
align-items: center;
}
.ReactVirtualized__Table__row {
display: flex;
flex-direction: row;
}
.ReactVirtualized__Table__headerTruncatedText {
display: inline-block;
max-width: 100%;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
}
.ReactVirtualized__Table__headerColumn,
.ReactVirtualized__Table__rowColumn {
min-width: 0px;
border-right: 1px solid #ccc;
align-self: center;
padding: 12px;
font-size: 12px;
}
.ReactVirtualized__Table__headerColumn:last-of-type,
.ReactVirtualized__Table__rowColumn:last-of-type {
border-right: 0px;
}
.ReactVirtualized__Table__headerColumn:focus,
.ReactVirtualized__Table__Grid:focus {
outline: none;
}
.ReactVirtualized__Table__rowColumn {
text-overflow: ellipsis;
white-space: nowrap;
}
.ReactVirtualized__Table__sortableHeaderColumn {
cursor: pointer;
}
.ReactVirtualized__Table__sortableHeaderIconContainer {
display: flex;
align-items: center;
}
.ReactVirtualized__Table__sortableHeaderIcon {
flex: 0 0 24px;
height: 1em;
width: 1em;
fill: currentColor;
}
.even-row { background: #f2f2f2; }
.odd-row { background: #ffffff; }
.even-row,
.odd-row {
border: none;
}
.filterable-table-container {
overflow: auto;
border: 1px solid #ccc;
}

View File

@@ -1,21 +1,33 @@
import React, { PropTypes } from 'react';
import React from 'react';
import PropTypes from 'prop-types';
import { Tooltip, OverlayTrigger } from 'react-bootstrap';
import { slugify } from '../modules/utils';
const propTypes = {
label: PropTypes.string.isRequired,
tooltip: PropTypes.string.isRequired,
icon: PropTypes.string,
className: PropTypes.string,
onClick: PropTypes.func,
};
const defaultProps = {
icon: 'question-circle-o',
};
export default function InfoTooltipWithTrigger({ label, tooltip }) {
export default function InfoTooltipWithTrigger({ label, tooltip, icon, className, onClick }) {
return (
<OverlayTrigger
placement="right"
overlay={<Tooltip id={`${slugify(label)}-tooltip`}>{tooltip}</Tooltip>}
>
<i className="fa fa-question-circle-o" />
<i
className={`fa fa-${icon} ${className}`}
onClick={onClick}
style={{ cursor: onClick ? 'pointer' : null }}
/>
</OverlayTrigger>
);
}
InfoTooltipWithTrigger.propTypes = propTypes;
InfoTooltipWithTrigger.defaultProps = defaultProps;

View File

@@ -0,0 +1,27 @@
import React from 'react';
import PropTypes from 'prop-types';
const propTypes = {
size: PropTypes.number,
};
const defaultProps = {
size: 25,
};
export default function Loading(props) {
return (
<img
className="loading"
alt="Loading..."
src="/static/assets/images/loading.gif"
style={{
width: props.size,
height: props.size,
padding: 0,
margin: 0,
}}
/>
);
}
Loading.propTypes = propTypes;
Loading.defaultProps = defaultProps;

View File

@@ -0,0 +1,32 @@
import React from 'react';
import PropTypes from 'prop-types';
import InfoTooltipWithTrigger from './InfoTooltipWithTrigger';
const propTypes = {
metric: PropTypes.object.isRequired,
};
export default function MetricOption({ metric }) {
return (
<div>
<span className="m-r-5 option-label">
{metric.verbose_name || metric.metric_name}
</span>
{metric.description &&
<InfoTooltipWithTrigger
className="m-r-5 text-muted"
icon="info"
tooltip={metric.description}
label={`descr-${metric.metric_name}`}
/>
}
<InfoTooltipWithTrigger
className="m-r-5 text-muted"
icon="question-circle-o"
tooltip={metric.expression}
label={`expr-${metric.metric_name}`}
/>
</div>);
}
MetricOption.propTypes = propTypes;

View File

@@ -1,9 +1,11 @@
import React, { PropTypes } from 'react';
import React from 'react';
import PropTypes from 'prop-types';
import { Modal } from 'react-bootstrap';
import Button from './Button';
import cx from 'classnames';
import Button from './Button';
const propTypes = {
animation: PropTypes.bool,
triggerNode: PropTypes.node.isRequired,
modalTitle: PropTypes.node.isRequired,
modalBody: PropTypes.node, // not required because it can be generated by beforeOpen
@@ -17,6 +19,7 @@ const propTypes = {
};
const defaultProps = {
animation: true,
beforeOpen: () => {},
onExit: () => {},
isButton: false,
@@ -46,6 +49,7 @@ export default class ModalTrigger extends React.Component {
renderModal() {
return (
<Modal
animation={this.props.animation}
show={this.state.showModal}
onHide={this.close}
onExit={this.props.onExit}
@@ -73,12 +77,17 @@ export default class ModalTrigger extends React.Component {
});
if (this.props.isButton) {
return (
<Button tooltip={this.props.tooltip} onClick={this.open}>
<Button
className="modal-trigger"
tooltip={this.props.tooltip}
onClick={this.open}
>
{this.props.triggerNode}
{this.renderModal()}
</Button>
);
}
/* eslint-disable jsx-a11y/interactive-supports-focus */
return (
<span className={classNames} onClick={this.open} role="button">
{this.props.triggerNode}

View File

@@ -1,12 +1,29 @@
import React from 'react';
import PropTypes from 'prop-types';
import { now, fDuration } from '../modules/dates';
class Timer extends React.PureComponent {
const propTypes = {
endTime: PropTypes.number,
isRunning: PropTypes.bool.isRequired,
startTime: PropTypes.number,
status: PropTypes.string,
style: PropTypes.object,
};
const defaultProps = {
endTime: null,
startTime: null,
status: 'success',
style: null,
};
export default class Timer extends React.PureComponent {
constructor(props) {
super(props);
this.state = {
clockStr: '',
};
this.stopwatch = this.stopwatch.bind(this);
}
componentWillMount() {
this.startTimer();
@@ -15,13 +32,12 @@ class Timer extends React.PureComponent {
this.stopTimer();
}
startTimer() {
if (!(this.timer)) {
this.timer = setInterval(this.stopwatch.bind(this), 30);
if (!this.timer) {
this.timer = setInterval(this.stopwatch, 30);
}
}
stopTimer() {
clearInterval(this.timer);
this.timer = null;
this.timer = clearInterval(this.timer);
}
stopwatch() {
if (this.props && this.props.startTime) {
@@ -53,19 +69,6 @@ class Timer extends React.PureComponent {
return timerSpan;
}
}
Timer.propTypes = {
startTime: React.PropTypes.number,
endTime: React.PropTypes.number,
isRunning: React.PropTypes.bool.isRequired,
status: React.PropTypes.string,
style: React.PropTypes.object,
};
Timer.defaultProps = {
startTime: null,
endTime: null,
status: 'success',
style: null,
};
export default Timer;
Timer.propTypes = propTypes;
Timer.defaultProps = defaultProps;

View File

@@ -1,10 +1,11 @@
import React, { PropTypes } from 'react';
import React from 'react';
import PropTypes from 'prop-types';
import { Tooltip, OverlayTrigger } from 'react-bootstrap';
import { slugify } from '../modules/utils';
const propTypes = {
label: PropTypes.string.isRequired,
tooltip: PropTypes.string.isRequired,
tooltip: PropTypes.node.isRequired,
children: PropTypes.node.isRequired,
placement: PropTypes.string,
};

View File

@@ -0,0 +1 @@
export const QUERY_TIMEOUT_THRESHOLD = 45000;

View File

@@ -1,2 +1,2 @@
require('../stylesheets/less/index.less');
require('../stylesheets/react-select/select.less');
import '../stylesheets/less/index.less';
import '../stylesheets/react-select/select.less';

View File

@@ -1,35 +1,34 @@
const $ = window.$ = require('jquery');
const jQuery = window.jQuery = require('jquery'); // eslint-disable-line
const px = require('../modules/superset');
const d3 = require('d3');
const urlLib = require('url');
const utils = require('../modules/utils');
const { Alert } = require('react-bootstrap');
import React from 'react';
import { render } from 'react-dom';
import d3 from 'd3';
import { Alert } from 'react-bootstrap';
import moment from 'moment';
import GridLayout from './components/GridLayout';
import Header from './components/Header';
import { appSetup } from '../common';
import AlertsWrapper from '../components/AlertsWrapper';
require('bootstrap');
require('../../stylesheets/dashboard.css');
import '../../stylesheets/dashboard.css';
export function getInitialState(dashboardData, context) {
const dashboard = Object.assign({ context }, utils.controllerInterface, dashboardData);
const px = require('../modules/superset');
const urlLib = require('url');
const utils = require('../modules/utils');
appSetup();
export function getInitialState(boostrapData) {
const dashboard = Object.assign({}, utils.controllerInterface, boostrapData.dashboard_data);
dashboard.firstLoad = true;
dashboard.posDict = {};
if (dashboard.position_json) {
dashboard.position_json.forEach(position => {
dashboard.position_json.forEach((position) => {
dashboard.posDict[position.slice_id] = position;
});
}
dashboard.curUserId = dashboard.context.user_id;
dashboard.refreshTimer = null;
const state = {
dashboard,
};
const state = Object.assign({}, boostrapData, { dashboard });
return state;
}
@@ -56,19 +55,22 @@ function renderAlert() {
button on the top right to save your changes.
</Alert>
</div>,
document.getElementById('alert-container')
document.getElementById('alert-container'),
);
}
function initDashboardView(dashboard) {
render(
<Header dashboard={dashboard} />,
document.getElementById('dashboard-header')
<div>
<AlertsWrapper />
<Header dashboard={dashboard} />
</div>,
document.getElementById('dashboard-header'),
);
// eslint-disable-next-line no-param-reassign
dashboard.reactGridLayout = render(
<GridLayout dashboard={dashboard} />,
document.getElementById('grid-container')
document.getElementById('grid-container'),
);
// Displaying widget controls on hover
@@ -78,7 +80,7 @@ function initDashboardView(dashboard) {
},
function () {
$(this).find('.chart-controls').fadeOut(300);
}
},
);
$('div.grid-container').css('visibility', 'visible');
@@ -98,19 +100,20 @@ function initDashboardView(dashboard) {
$('[data-toggle="tooltip"]').tooltip({ container: 'body' });
}
export function dashboardContainer(dashboard) {
export function dashboardContainer(dashboard, datasources, userid) {
return Object.assign({}, dashboard, {
type: 'dashboard',
filters: {},
curUserId: userid,
init() {
this.sliceObjects = [];
dashboard.slices.forEach((data) => {
if (data.error) {
const html = '<div class="alert alert-danger">' + data.error + '</div>';
$('#slice_' + data.slice_id).find('.token').html(html);
const html = `<div class="alert alert-danger">${data.error}</div>`;
$(`#slice_${data.slice_id}`).find('.token').html(html);
} else {
const slice = px.Slice(data, this);
$('#slice_' + data.slice_id).find('a.refresh').click(() => {
const slice = px.Slice(data, datasources[data.form_data.datasource], this);
$(`#slice_${data.slice_id}`).find('a.refresh').click(() => {
slice.render(true);
});
this.sliceObjects.push(slice);
@@ -146,13 +149,15 @@ export function dashboardContainer(dashboard) {
done(slice) {
const refresh = slice.getWidgetHeader().find('.refresh');
const data = slice.data;
const cachedWhen = moment.utc(data.cached_dttm).fromNow();
if (data !== undefined && data.is_cached) {
refresh
.addClass('danger')
.attr('title',
'Served from data cached at ' + data.cached_dttm +
'. Click to force refresh')
.tooltip('fixTitle');
.attr(
'title',
`Served from data cached ${cachedWhen}. ` +
'Click to force refresh')
.tooltip('fixTitle');
} else {
refresh
.removeClass('danger')
@@ -195,8 +200,13 @@ export function dashboardContainer(dashboard) {
}
if (!(col in this.filters[sliceId]) || !merge) {
this.filters[sliceId][col] = vals;
} else {
// d3.merge pass in array of arrays while some value form filter components
// from and to filter box require string to be process and return
} else if (this.filters[sliceId][col] instanceof Array) {
this.filters[sliceId][col] = d3.merge([this.filters[sliceId][col], vals]);
} else {
this.filters[sliceId][col] = d3.merge([[this.filters[sliceId][col]], vals])[0] || '';
}
if (refresh) {
this.refreshExcept(sliceId);
@@ -237,7 +247,7 @@ export function dashboardContainer(dashboard) {
const dash = this;
const maxRandomDelay = Math.max(interval * 0.2, 5000);
const refreshAll = () => {
dash.sliceObjects.forEach(slice => {
dash.sliceObjects.forEach((slice) => {
const force = !dash.firstLoad;
setTimeout(() => {
slice.render(force);
@@ -260,7 +270,7 @@ export function dashboardContainer(dashboard) {
},
refreshExcept(sliceId) {
const immune = this.metadata.filter_immune_slices || [];
this.sliceObjects.forEach(slice => {
this.sliceObjects.forEach((slice) => {
if (slice.data.slice_id !== sliceId && immune.indexOf(slice.data.slice_id) === -1) {
slice.render();
const sliceSeletor = $(`#${slice.data.slice_id}-cell`);
@@ -326,21 +336,25 @@ export function dashboardContainer(dashboard) {
const errorMsg = getAjaxErrorMsg(error);
utils.showModal({
title: 'Error',
body: 'Sorry, there was an error adding slices to this dashboard: </ br>' + errorMsg,
body: 'Sorry, there was an error adding slices to this dashboard: ' + errorMsg,
});
},
});
},
updateDashboardTitle(title) {
this.dashboard_title = title;
this.onChange();
},
});
}
$(document).ready(() => {
// Getting bootstrapped data from the DOM
const dashboardData = $('.dashboard').data('dashboard');
const contextData = $('.dashboard').data('context');
utils.initJQueryAjax();
const dashboardData = $('.dashboard').data('bootstrap');
const state = getInitialState(dashboardData, contextData);
const dashboard = dashboardContainer(state.dashboard);
const state = getInitialState(dashboardData);
const dashboard = dashboardContainer(state.dashboard, state.datasources, state.user_id);
initDashboardView(dashboard);
dashboard.init();
});

View File

@@ -1,22 +1,22 @@
import React from 'react';
import PropTypes from 'prop-types';
import ModalTrigger from '../../components/ModalTrigger';
const propTypes = {
triggerNode: React.PropTypes.node.isRequired,
code: React.PropTypes.string,
codeCallback: React.PropTypes.func,
triggerNode: PropTypes.node.isRequired,
code: PropTypes.string,
codeCallback: PropTypes.func,
};
const defaultProps = {
codeCallback: () => {},
};
export default class CodeModal extends React.PureComponent {
constructor(props) {
super(props);
this.state = {
refreshFrequency: props.initialRefreshFrequency,
};
this.state = { code: props.code };
}
beforeOpen() {
let code = this.props.code;

View File

@@ -1,8 +1,7 @@
const $ = window.$ = require('jquery');
import React from 'react';
import PropTypes from 'prop-types';
import { ButtonGroup } from 'react-bootstrap';
import Button from '../../components/Button';
import CssEditor from './CssEditor';
import RefreshIntervalModal from './RefreshIntervalModal';
@@ -10,8 +9,10 @@ import SaveModal from './SaveModal';
import CodeModal from './CodeModal';
import SliceAdder from './SliceAdder';
const $ = window.$ = require('jquery');
const propTypes = {
dashboard: React.PropTypes.object.isRequired,
dashboard: PropTypes.object.isRequired,
};
class Controls extends React.PureComponent {
@@ -22,14 +23,9 @@ class Controls extends React.PureComponent {
cssTemplates: [],
};
}
refresh() {
this.props.dashboard.sliceObjects.forEach((slice) => {
slice.render(true);
});
}
componentWillMount() {
$.get('/csstemplateasyncmodelview/api/read', (data) => {
const cssTemplates = data.result.map((row) => ({
const cssTemplates = data.result.map(row => ({
value: row.template_name,
css: row.css,
label: row.template_name,
@@ -37,13 +33,17 @@ class Controls extends React.PureComponent {
this.setState({ cssTemplates });
});
}
refresh() {
this.props.dashboard.sliceObjects.forEach((slice) => {
slice.render(true);
});
}
changeCss(css) {
this.setState({ css });
this.props.dashboard.onChange();
}
render() {
const dashboard = this.props.dashboard;
const canSave = dashboard.context.dash_save_perm;
const emailBody = `Checkout this dashboard: ${window.location.href}`;
const emailLink = 'mailto:?Subject=Superset%20Dashboard%20'
+ `${dashboard.dashboard_title}&Body=${emailBody}`;
@@ -81,11 +81,16 @@ class Controls extends React.PureComponent {
onChange={this.changeCss.bind(this)}
/>
<Button
disabled={!canSave}
onClick={() => { window.location = emailLink; }}
>
<i className="fa fa-envelope" />
</Button>
<Button
disabled={!dashboard.dash_edit_perm}
onClick={() => {
window.location = `/dashboardmodelview/edit/${dashboard.id}`;
}}
tooltip="Edit this dashboard's property"
tooltip="Edit this dashboard's properties"
>
<i className="fa fa-edit" />
</Button>
@@ -93,14 +98,11 @@ class Controls extends React.PureComponent {
dashboard={dashboard}
css={this.state.css}
triggerNode={
<i className="fa fa-save" />
<Button disabled={!dashboard.dash_save_perm}>
<i className="fa fa-save" />
</Button>
}
/>
<Button
onClick={() => { window.location = emailLink; }}
>
<i className="fa fa-envelope"></i>
</Button>
</ButtonGroup>
);
}

View File

@@ -1,18 +1,18 @@
import React from 'react';
import ModalTrigger from '../../components/ModalTrigger';
import PropTypes from 'prop-types';
import Select from 'react-select';
import AceEditor from 'react-ace';
import 'brace/mode/css';
import 'brace/theme/github';
import ModalTrigger from '../../components/ModalTrigger';
const propTypes = {
initialCss: React.PropTypes.string,
triggerNode: React.PropTypes.node.isRequired,
onChange: React.PropTypes.func,
templates: React.PropTypes.array,
initialCss: PropTypes.string,
triggerNode: PropTypes.node.isRequired,
onChange: PropTypes.func,
templates: PropTypes.array,
};
const defaultProps = {

View File

@@ -1,10 +1,13 @@
import $ from 'jquery';
import React, { PropTypes } from 'react';
import React from 'react';
import PropTypes from 'prop-types';
import { Responsive, WidthProvider } from 'react-grid-layout';
import $ from 'jquery';
import SliceCell from './SliceCell';
require('react-grid-layout/css/styles.css');
require('react-resizable/css/styles.css');
const ResponsiveReactGridLayout = WidthProvider(Responsive);
const propTypes = {
@@ -98,7 +101,7 @@ class GridLayout extends React.Component {
id={'slice_' + slice.slice_id}
key={slice.slice_id}
data-slice-id={slice.slice_id}
className="widget"
className={`widget ${slice.form_data.viz_type}`}
>
<SliceCell
slice={slice}

View File

@@ -1,9 +1,11 @@
import React from 'react';
import PropTypes from 'prop-types';
import Controls from './Controls';
import EditableTitle from '../../components/EditableTitle';
const propTypes = {
dashboard: React.PropTypes.object,
dashboard: PropTypes.object,
};
const defaultProps = {
};
@@ -13,19 +15,27 @@ class Header extends React.PureComponent {
super(props);
this.state = {
};
this.handleSaveTitle = this.handleSaveTitle.bind(this);
}
handleSaveTitle(title) {
this.props.dashboard.updateDashboardTitle(title);
}
render() {
const dashboard = this.props.dashboard;
return (
<div className="title">
<div className="pull-left">
<h1>
{dashboard.dashboard_title} &nbsp;
<h1 className="outer-container">
<EditableTitle
title={dashboard.dashboard_title}
canEdit={dashboard.dash_save_perm}
onSaveTitle={this.handleSaveTitle}
/>
<span is class="favstar" class_name="Dashboard" obj_id={dashboard.id} />
</h1>
</div>
<div className="pull-right" style={{ marginTop: '35px' }}>
{!this.props.dashboard.context.standalone_mode &&
{!this.props.dashboard.standalone_mode &&
<Controls dashboard={dashboard} />
}
</div>

View File

@@ -1,12 +1,12 @@
import React from 'react';
import ModalTrigger from '../../components/ModalTrigger';
import PropTypes from 'prop-types';
import Select from 'react-select';
import ModalTrigger from '../../components/ModalTrigger';
const propTypes = {
triggerNode: React.PropTypes.node.isRequired,
initialRefreshFrequency: React.PropTypes.number,
onChange: React.PropTypes.func,
triggerNode: PropTypes.node.isRequired,
initialRefreshFrequency: PropTypes.number,
onChange: PropTypes.func,
};
const defaultProps = {

View File

@@ -1,15 +1,16 @@
const $ = window.$ = require('jquery');
/* global notify */
import React from 'react';
import PropTypes from 'prop-types';
import { Button, FormControl, FormGroup, Radio } from 'react-bootstrap';
import { getAjaxErrorMsg, showModal } from '../../modules/utils';
import { getAjaxErrorMsg } from '../../modules/utils';
import ModalTrigger from '../../components/ModalTrigger';
const $ = window.$ = require('jquery');
const propTypes = {
css: React.PropTypes.string,
dashboard: React.PropTypes.object.isRequired,
triggerNode: React.PropTypes.node.isRequired,
css: PropTypes.string,
dashboard: PropTypes.object.isRequired,
triggerNode: PropTypes.node.isRequired,
};
class SaveModal extends React.PureComponent {
@@ -53,19 +54,13 @@ class SaveModal extends React.PureComponent {
if (saveType === 'newDashboard') {
window.location = '/superset/dashboard/' + resp.id + '/';
} else {
showModal({
title: 'Success',
body: 'This dashboard was saved successfully.',
});
notify.success('This dashboard was saved successfully.');
}
},
error(error) {
saveModal.close();
const errorMsg = getAjaxErrorMsg(error);
showModal({
title: 'Error',
body: 'Sorry, there was an error saving this dashboard: </ br>' + errorMsg,
});
notify.error('Sorry, there was an error saving this dashboard: </ br>' + errorMsg);
},
});
}
@@ -84,6 +79,7 @@ class SaveModal extends React.PureComponent {
positions,
css: this.state.css,
expanded_slices: expandedSlices,
dashboard_title: dashboard.dashboard_title,
};
let url = null;
if (saveType === 'overwrite') {
@@ -108,7 +104,6 @@ class SaveModal extends React.PureComponent {
<ModalTrigger
ref={(modal) => { this.modal = modal; }}
triggerNode={this.props.triggerNode}
isButton
modalTitle="Save Dashboard"
modalBody={
<FormGroup>

View File

@@ -1,7 +1,10 @@
import React from 'react';
import $ from 'jquery';
import React, { PropTypes } from 'react';
import PropTypes from 'prop-types';
import { BootstrapTable, TableHeaderColumn } from 'react-bootstrap-table';
import ModalTrigger from '../../components/ModalTrigger';
require('react-bootstrap-table/css/react-bootstrap-table.css');
const propTypes = {
@@ -39,7 +42,7 @@ class SliceAdder extends React.Component {
this.slicesRequest = $.ajax({
url: uri,
type: 'GET',
success: response => {
success: (response) => {
// Prepare slice data for table
const slices = response.result.map(slice => ({
id: slice.id,
@@ -54,7 +57,7 @@ class SliceAdder extends React.Component {
slicesLoaded: true,
});
},
error: error => {
error: (error) => {
this.errored = true;
this.setState({
errorMsg: this.props.dashboard.getAjaxErrorMsg(error),

View File

@@ -1,4 +1,6 @@
import React, { PropTypes } from 'react';
/* eslint-disable react/no-danger */
import React from 'react';
import PropTypes from 'prop-types';
const propTypes = {
slice: PropTypes.object.isRequired,
@@ -62,9 +64,7 @@ function SliceCell({ expandedSlices, removeSlice, slice }) {
expandedSlices[String(slice.slice_id)] ? {} : { display: 'none' }
}
dangerouslySetInnerHTML={{ __html: slice.description_markeddown }}
>
</div>
/>
<div className="row chart-container">
<input type="hidden" value="false" />
<div id={'token_' + slice.slice_id} className="token col-md-12">
@@ -76,8 +76,7 @@ function SliceCell({ expandedSlices, removeSlice, slice }) {
<div
id={'con_' + slice.slice_id}
className={`slice_container ${slice.form_data.viz_type}`}
>
</div>
/>
</div>
</div>
</div>

View File

@@ -1,7 +1,10 @@
/* eslint camelcase: 0 */
const $ = window.$ = require('jquery');
const FAVESTAR_BASE_URL = '/superset/favstar/slice';
import { getExploreUrl } from '../exploreUtils';
import { QUERY_TIMEOUT_THRESHOLD } from '../../constants';
const $ = window.$ = require('jquery');
const FAVESTAR_BASE_URL = '/superset/favstar/slice';
export const SET_DATASOURCE_TYPE = 'SET_DATASOURCE_TYPE';
export function setDatasourceType(datasourceType) {
@@ -13,11 +16,6 @@ export function setDatasource(datasource) {
return { type: SET_DATASOURCE, datasource };
}
export const SET_DATASOURCES = 'SET_DATASOURCES';
export function setDatasources(datasources) {
return { type: SET_DATASOURCES, datasources };
}
export const FETCH_DATASOURCE_STARTED = 'FETCH_DATASOURCE_STARTED';
export function fetchDatasourceStarted() {
return { type: FETCH_DATASOURCE_STARTED };
@@ -33,21 +31,6 @@ export function fetchDatasourceFailed(error) {
return { type: FETCH_DATASOURCE_FAILED, error };
}
export const FETCH_DATASOURCES_STARTED = 'FETCH_DATASOURCES_STARTED';
export function fetchDatasourcesStarted() {
return { type: FETCH_DATASOURCES_STARTED };
}
export const FETCH_DATASOURCES_SUCCEEDED = 'FETCH_DATASOURCES_SUCCEEDED';
export function fetchDatasourcesSucceeded() {
return { type: FETCH_DATASOURCES_SUCCEEDED };
}
export const FETCH_DATASOURCES_FAILED = 'FETCH_DATASOURCES_FAILED';
export function fetchDatasourcesFailed(error) {
return { type: FETCH_DATASOURCES_FAILED, error };
}
export const RESET_FIELDS = 'RESET_FIELDS';
export function resetControls() {
return { type: RESET_FIELDS };
@@ -80,24 +63,6 @@ export function fetchDatasourceMetadata(datasourceKey, alsoTriggerQuery = false)
};
}
export function fetchDatasources() {
return function (dispatch) {
dispatch(fetchDatasourcesStarted());
const url = '/superset/datasources/';
$.ajax({
type: 'GET',
url,
success: (data) => {
dispatch(setDatasources(data));
dispatch(fetchDatasourcesSucceeded());
},
error(error) {
dispatch(fetchDatasourcesFailed(error.responseJSON.error));
},
});
};
}
export const TOGGLE_FAVE_STAR = 'TOGGLE_FAVE_STAR';
export function toggleFaveStar(isStarred) {
return { type: TOGGLE_FAVE_STAR, isStarred };
@@ -148,6 +113,11 @@ export function chartUpdateStopped(queryRequest) {
return { type: CHART_UPDATE_STOPPED };
}
export const CHART_UPDATE_TIMEOUT = 'CHART_UPDATE_TIMEOUT';
export function chartUpdateTimeout(statusText) {
return { type: CHART_UPDATE_TIMEOUT, statusText };
}
export const CHART_UPDATE_FAILED = 'CHART_UPDATE_FAILED';
export function chartUpdateFailed(queryResponse) {
return { type: CHART_UPDATE_FAILED, queryResponse };
@@ -186,16 +156,19 @@ export function fetchDashboardsFailed(userId) {
export function fetchDashboards(userId) {
return function (dispatch) {
const url = '/dashboardmodelviewasync/api/read?_flt_0_owners=' + userId;
$.get(url, function (data, status) {
if (status === 'success') {
$.ajax({
type: 'GET',
url,
success: (data) => {
const choices = [];
for (let i = 0; i < data.pks.length; i++) {
choices.push({ value: data.pks[i], label: data.result[i].dashboard_title });
}
dispatch(fetchDashboardsSucceeded(choices));
} else {
},
error: () => {
dispatch(fetchDashboardsFailed(userId));
}
},
});
};
}
@@ -204,6 +177,10 @@ export const SAVE_SLICE_FAILED = 'SAVE_SLICE_FAILED';
export function saveSliceFailed() {
return { type: SAVE_SLICE_FAILED };
}
export const SAVE_SLICE_SUCCESS = 'SAVE_SLICE_SUCCESS';
export function saveSliceSuccess(data) {
return { type: SAVE_SLICE_SUCCESS, data };
}
export const REMOVE_SAVE_MODAL_ALERT = 'REMOVE_SAVE_MODAL_ALERT';
export function removeSaveModalAlert() {
@@ -212,10 +189,9 @@ export function removeSaveModalAlert() {
export function saveSlice(url) {
return function (dispatch) {
$.get(url, (data, status) => {
return $.get(url, (data, status) => {
if (status === 'success') {
// Go to new slice url or dashboard url
window.location = data;
dispatch(saveSliceSuccess(data));
} else {
dispatch(saveSliceFailed());
}
@@ -223,6 +199,11 @@ export function saveSlice(url) {
};
}
export const UPDATE_CHART_TITLE = 'UPDATE_CHART_TITLE';
export function updateChartTitle(slice_name) {
return { type: UPDATE_CHART_TITLE, slice_name };
}
export const UPDATE_CHART_STATUS = 'UPDATE_CHART_STATUS';
export function updateChartStatus(status) {
return { type: UPDATE_CHART_STATUS, status };
@@ -232,12 +213,20 @@ export const RUN_QUERY = 'RUN_QUERY';
export function runQuery(formData, force = false) {
return function (dispatch) {
const url = getExploreUrl(formData, 'json', force);
const queryRequest = $.getJSON(url, function (queryResponse) {
dispatch(chartUpdateSucceeded(queryResponse));
}).fail(function (err) {
if (err.statusText !== 'abort') {
dispatch(chartUpdateFailed(err.responseJSON));
}
const queryRequest = $.ajax({
url,
dataType: 'json',
success(queryResponse) {
dispatch(chartUpdateSucceeded(queryResponse));
},
error(err) {
if (err.statusText === 'timeout') {
dispatch(chartUpdateTimeout(err.statusText));
} else if (err.statusText !== 'abort') {
dispatch(chartUpdateFailed(err.responseJSON));
}
},
timeout: QUERY_TIMEOUT_THRESHOLD,
});
dispatch(chartUpdateStarted(queryRequest));
};

View File

@@ -1,15 +1,19 @@
import $ from 'jquery';
import React, { PropTypes } from 'react';
import React from 'react';
import PropTypes from 'prop-types';
import Mustache from 'mustache';
import { connect } from 'react-redux';
import { Alert, Collapse, Label, Panel } from 'react-bootstrap';
import { Alert, Collapse, Panel } from 'react-bootstrap';
import visMap from '../../../visualizations/main';
import { d3format } from '../../modules/utils';
import ExploreActionButtons from './ExploreActionButtons';
import EditableTitle from '../../components/EditableTitle';
import FaveStar from '../../components/FaveStar';
import TooltipWrapper from '../../components/TooltipWrapper';
import Timer from '../../components/Timer';
import { getExploreUrl } from '../exploreUtils';
import { getFormDataFromControls } from '../stores/store';
import CachedLabel from '../../components/CachedLabel';
const CHART_STATUS_MAP = {
failed: 'danger',
@@ -20,6 +24,7 @@ const CHART_STATUS_MAP = {
const propTypes = {
actions: PropTypes.object.isRequired,
alert: PropTypes.string,
can_overwrite: PropTypes.bool.isRequired,
can_download: PropTypes.bool.isRequired,
chartStatus: PropTypes.string,
chartUpdateEndTime: PropTypes.number,
@@ -33,6 +38,11 @@ const propTypes = {
viz_type: PropTypes.string.isRequired,
formData: PropTypes.object,
latestQueryFormData: PropTypes.object,
queryResponse: PropTypes.object,
triggerRender: PropTypes.bool,
standalone: PropTypes.bool,
datasourceType: PropTypes.string,
datasourceId: PropTypes.number,
};
class ChartContainer extends React.PureComponent {
@@ -44,19 +54,9 @@ class ChartContainer extends React.PureComponent {
};
}
renderViz() {
this.props.actions.renderTriggered();
const mockSlice = this.getMockedSliceObject();
this.setState({ mockSlice });
try {
visMap[this.props.viz_type](mockSlice, this.props.queryResponse);
} catch (e) {
this.props.actions.chartRenderingFailed(e);
}
}
componentDidUpdate(prevProps) {
if (
this.props.queryResponse &&
(
prevProps.queryResponse !== this.props.queryResponse ||
prevProps.height !== this.props.height ||
@@ -64,6 +64,7 @@ class ChartContainer extends React.PureComponent {
) && !this.props.queryResponse.error
&& this.props.chartStatus !== 'failed'
&& this.props.chartStatus !== 'stopped'
&& this.props.chartStatus !== 'loading'
) {
this.renderViz();
}
@@ -72,35 +73,41 @@ class ChartContainer extends React.PureComponent {
getMockedSliceObject() {
const props = this.props;
const getHeight = () => {
const headerHeight = this.props.standalone ? 0 : 100;
const headerHeight = props.standalone ? 0 : 100;
return parseInt(props.height, 10) - headerHeight;
};
return {
viewSqlQuery: this.props.queryResponse.query,
viewSqlQuery: props.queryResponse.query,
containerId: props.containerId,
datasource: props.datasource,
selector: this.state.selector,
formData: this.props.formData,
formData: props.formData,
container: {
html: (data) => {
// this should be a callback to clear the contents of the slice container
$(this.state.selector).html(data);
},
css: (dim, size) => {
// dimension can be 'height'
// pixel string can be '300px'
// should call callback to adjust height of chart
$(this.state.selector).css(dim, size);
css: (property, value) => {
$(this.state.selector).css(property, value);
},
height: getHeight,
show: () => { },
get: (n) => ($(this.state.selector).get(n)),
find: (classname) => ($(this.state.selector).find(classname)),
get: n => ($(this.state.selector).get(n)),
find: classname => ($(this.state.selector).find(classname)),
},
width: () => this.chartContainerRef.getBoundingClientRect().width,
height: getHeight,
render_template: (s) => {
const context = {
width: this.width,
height: this.height,
};
return Mustache.render(s, context);
},
setFilter: () => {},
getFilters: () => (
@@ -126,10 +133,9 @@ class ChartContainer extends React.PureComponent {
},
data: {
csv_endpoint: getExploreUrl(this.props.formData, 'csv'),
json_endpoint: getExploreUrl(this.props.formData, 'json'),
standalone_endpoint: getExploreUrl(
this.props.formData, 'standalone'),
csv_endpoint: getExploreUrl(props.formData, 'csv'),
json_endpoint: getExploreUrl(props.formData, 'json'),
standalone_endpoint: getExploreUrl(props.formData, 'standalone'),
},
};
@@ -139,6 +145,22 @@ class ChartContainer extends React.PureComponent {
this.props.actions.removeChartAlert();
}
runQuery() {
this.props.actions.runQuery(this.props.formData, true);
}
updateChartTitle(newTitle) {
const params = {
slice_name: newTitle,
action: 'overwrite',
};
const saveUrl = getExploreUrl(this.props.formData, 'base', false, null, params);
this.props.actions.saveSlice(saveUrl)
.then(() => {
this.props.actions.updateChartTitle(newTitle);
});
}
renderChartTitle() {
let title;
if (this.props.slice) {
@@ -149,15 +171,29 @@ class ChartContainer extends React.PureComponent {
return title;
}
renderViz() {
this.props.actions.renderTriggered();
const mockSlice = this.getMockedSliceObject();
this.setState({ mockSlice });
try {
visMap[this.props.viz_type](mockSlice, this.props.queryResponse);
} catch (e) {
this.props.actions.chartRenderingFailed(e);
}
}
renderAlert() {
/* eslint-disable react/no-danger */
const msg = (
<div>
{this.props.alert}
<i
className="fa fa-close pull-right"
onClick={this.removeAlert.bind(this)}
style={{ cursor: 'pointer' }}
/>
<p
dangerouslySetInnerHTML={{ __html: this.props.alert }}
/>
</div>);
return (
<div>
@@ -194,7 +230,7 @@ class ChartContainer extends React.PureComponent {
}
<div
id={this.props.containerId}
ref={ref => { this.chartContainerRef = ref; }}
ref={(ref) => { this.chartContainerRef = ref; }}
className={this.props.viz_type}
style={{
opacity: loading ? '0.25' : '1',
@@ -203,9 +239,6 @@ class ChartContainer extends React.PureComponent {
</div>
);
}
runQuery() {
this.props.actions.runQuery(this.props.formData, true);
}
render() {
if (this.props.standalone) {
@@ -214,7 +247,6 @@ class ChartContainer extends React.PureComponent {
return this.renderChart();
}
const queryResponse = this.props.queryResponse;
const query = queryResponse && queryResponse.query ? queryResponse.query : null;
return (
<div className="chart-container">
<Panel
@@ -224,7 +256,11 @@ class ChartContainer extends React.PureComponent {
id="slice-header"
className="clearfix panel-title-large"
>
{this.renderChartTitle()}
<EditableTitle
title={this.renderChartTitle()}
canEdit={this.props.can_overwrite}
onSaveTitle={this.updateChartTitle.bind(this)}
/>
{this.props.slice &&
<span>
@@ -236,7 +272,7 @@ class ChartContainer extends React.PureComponent {
<TooltipWrapper
label="edit-desc"
tooltip="Edit Description"
tooltip="Edit slice properties"
>
<a
className="edit-desc-icon"
@@ -250,19 +286,12 @@ class ChartContainer extends React.PureComponent {
<div className="pull-right">
{this.props.chartStatus === 'success' &&
this.props.queryResponse &&
this.props.queryResponse.is_cached &&
<TooltipWrapper
tooltip="Loaded from cache. Click to force refresh"
label="cache-desc"
>
<Label
style={{ fontSize: '10px', marginRight: '5px', cursor: 'pointer' }}
onClick={this.runQuery.bind(this)}
>
cached
</Label>
</TooltipWrapper>
this.props.queryResponse &&
this.props.queryResponse.is_cached &&
<CachedLabel
onClick={this.runQuery.bind(this)}
cachedTimestamp={queryResponse.cached_dttm}
/>
}
<Timer
startTime={this.props.chartUpdateStartTime}
@@ -274,7 +303,8 @@ class ChartContainer extends React.PureComponent {
<ExploreActionButtons
slice={this.state.mockSlice}
canDownload={this.props.can_download}
query={query}
chartStatus={this.props.chartStatus}
queryResponse={queryResponse}
queryEndpoint={getExploreUrl(this.props.latestQueryFormData, 'query')}
/>
</div>
@@ -294,10 +324,12 @@ function mapStateToProps(state) {
const formData = getFormDataFromControls(state.controls);
return {
alert: state.chartAlert,
can_overwrite: state.can_overwrite,
can_download: state.can_download,
chartStatus: state.chartStatus,
chartUpdateEndTime: state.chartUpdateEndTime,
chartUpdateStartTime: state.chartUpdateStartTime,
datasource: state.datasource,
column_formats: state.datasource ? state.datasource.column_formats : null,
containerId: state.slice ? `slice-container-${state.slice.slice_id}` : 'slice-container',
formData,
@@ -309,7 +341,8 @@ function mapStateToProps(state) {
table_name: formData.datasource_name,
viz_type: formData.viz_type,
triggerRender: state.triggerRender,
datasourceType: state.datasource ? state.datasource.type : null,
datasourceType: state.datasource.type,
datasourceId: state.datasource_id,
};
}

View File

@@ -1,20 +1,26 @@
import React, { PropTypes } from 'react';
import ControlHeader from './ControlHeader';
import React from 'react';
import PropTypes from 'prop-types';
import BoundsControl from './controls/BoundsControl';
import CheckboxControl from './controls/CheckboxControl';
import DatasourceControl from './controls/DatasourceControl';
import FilterControl from './controls/FilterControl';
import HiddenControl from './controls/HiddenControl';
import SelectControl from './controls/SelectControl';
import TextAreaControl from './controls/TextAreaControl';
import TextControl from './controls/TextControl';
import VizTypeControl from './controls/VizTypeControl';
const controlMap = {
BoundsControl,
CheckboxControl,
DatasourceControl,
FilterControl,
HiddenControl,
SelectControl,
TextAreaControl,
TextControl,
VizTypeControl,
};
const controlTypes = Object.keys(controlMap);
@@ -22,6 +28,7 @@ const propTypes = {
actions: PropTypes.object.isRequired,
name: PropTypes.string.isRequired,
type: PropTypes.oneOf(controlTypes).isRequired,
hidden: PropTypes.bool,
label: PropTypes.string.isRequired,
choices: PropTypes.arrayOf(PropTypes.array),
description: PropTypes.string,
@@ -40,6 +47,7 @@ const propTypes = {
const defaultProps = {
renderTrigger: false,
validators: [],
hidden: false,
validationErrors: [],
};
@@ -49,18 +57,31 @@ export default class Control extends React.PureComponent {
this.validate = this.validate.bind(this);
this.onChange = this.onChange.bind(this);
}
componentDidMount() {
this.validateAndSetValue(this.props.value, []);
}
onChange(value, errors) {
let validationErrors = this.validate(value);
this.validateAndSetValue(value, errors);
}
validateAndSetValue(value, errors) {
let validationErrors = this.props.validationErrors;
let currentErrors = this.validate(value);
if (errors && errors.length > 0) {
validationErrors = validationErrors.concat(errors);
currentErrors = validationErrors.concat(errors);
}
if (validationErrors.length + currentErrors.length > 0) {
validationErrors = currentErrors;
}
if (value !== this.props.value || validationErrors !== this.props.validationErrors) {
this.props.actions.setControlValue(this.props.name, value, validationErrors);
}
this.props.actions.setControlValue(this.props.name, value, validationErrors);
}
validate(value) {
const validators = this.props.validators;
const validationErrors = [];
if (validators && validators.length > 0) {
validators.forEach(f => {
validators.forEach((f) => {
const v = f(value);
if (v) {
validationErrors.push(v);
@@ -74,13 +95,6 @@ export default class Control extends React.PureComponent {
const divStyle = this.props.hidden ? { display: 'none' } : null;
return (
<div style={divStyle}>
<ControlHeader
label={this.props.label}
description={this.props.description}
renderTrigger={this.props.renderTrigger}
validationErrors={this.props.validationErrors}
rightNode={this.props.rightNode}
/>
<ControlType
onChange={this.onChange}
{...this.props}

View File

@@ -1,4 +1,5 @@
import React, { PropTypes } from 'react';
import React from 'react';
import PropTypes from 'prop-types';
import { ControlLabel, OverlayTrigger, Tooltip } from 'react-bootstrap';
import InfoTooltipWithTrigger from '../../components/InfoTooltipWithTrigger';
@@ -8,6 +9,7 @@ const propTypes = {
validationErrors: PropTypes.array,
renderTrigger: PropTypes.bool,
rightNode: PropTypes.node,
leftNode: PropTypes.node,
};
const defaultProps = {
@@ -16,7 +18,7 @@ const defaultProps = {
};
export default function ControlHeader({
label, description, validationErrors, renderTrigger, rightNode }) {
label, description, validationErrors, renderTrigger, leftNode, rightNode }) {
const hasError = (validationErrors.length > 0);
return (
<div>
@@ -63,6 +65,9 @@ export default function ControlHeader({
{' '}
</span>
}
{leftNode &&
<span>{leftNode}</span>
}
</ControlLabel>
</div>
{rightNode &&

View File

@@ -1,4 +1,5 @@
import React, { PropTypes } from 'react';
import React from 'react';
import PropTypes from 'prop-types';
import { Panel } from 'react-bootstrap';
import InfoTooltipWithTrigger from '../../components/InfoTooltipWithTrigger';

View File

@@ -1,14 +1,15 @@
/* eslint camelcase: 0 */
import React, { PropTypes } from 'react';
import React from 'react';
import PropTypes from 'prop-types';
import { bindActionCreators } from 'redux';
import * as actions from '../actions/exploreActions';
import { connect } from 'react-redux';
import { Panel, Alert } from 'react-bootstrap';
import { sectionsToRender } from '../stores/visTypes';
import { Alert } from 'react-bootstrap';
import { sectionsToRender, visTypes } from '../stores/visTypes';
import ControlPanelSection from './ControlPanelSection';
import ControlRow from './ControlRow';
import Control from './Control';
import controls from '../stores/controls';
import * as actions from '../actions/exploreActions';
const propTypes = {
actions: PropTypes.object.isRequired,
@@ -18,7 +19,6 @@ const propTypes = {
controls: PropTypes.object.isRequired,
form_data: PropTypes.object.isRequired,
isDatasourceMetaLoading: PropTypes.bool.isRequired,
y_axis_zero: PropTypes.any,
};
class ControlPanelsContainer extends React.Component {
@@ -28,7 +28,15 @@ class ControlPanelsContainer extends React.Component {
this.getControlData = this.getControlData.bind(this);
}
getControlData(controlName) {
const mapF = controls[controlName].mapStateToProps;
// Identifying mapStateToProps function to apply (logic can't be in store)
let mapF = controls[controlName].mapStateToProps;
// Looking to find mapStateToProps override for this viz type
const controlOverrides = visTypes[this.props.controls.viz_type.value].controlOverrides || {};
if (controlOverrides[controlName] && controlOverrides[controlName].mapStateToProps) {
mapF = controlOverrides[controlName].mapStateToProps;
}
// Applying mapStateToProps if needed
if (mapF) {
return Object.assign({}, this.props.controls[controlName], mapF(this.props.exploreState));
}
@@ -43,7 +51,7 @@ class ControlPanelsContainer extends React.Component {
render() {
return (
<div className="scrollbar-container">
<Panel className="scrollbar-content">
<div className="scrollbar-content">
{this.props.alert &&
<Alert bsStyle="warning">
{this.props.alert}
@@ -54,7 +62,7 @@ class ControlPanelsContainer extends React.Component {
/>
</Alert>
}
{this.sectionsToRender().map((section) => (
{this.sectionsToRender().map(section => (
<ControlPanelSection
key={section.label}
label={section.label}
@@ -64,20 +72,21 @@ class ControlPanelsContainer extends React.Component {
<ControlRow
key={`controlsetrow-${i}`}
controls={controlSets.map(controlName => (
<Control
name={controlName}
key={`control-${controlName}`}
value={this.props.form_data[controlName]}
validationErrors={this.props.controls[controlName].validationErrors}
actions={this.props.actions}
{...this.getControlData(controlName)}
/>
controlName &&
<Control
name={controlName}
key={`control-${controlName}`}
value={this.props.form_data[controlName]}
validationErrors={this.props.controls[controlName].validationErrors}
actions={this.props.actions}
{...this.getControlData(controlName)}
/>
))}
/>
))}
</ControlPanelSection>
))}
</Panel>
</div>
</div>
);
}

View File

@@ -1,4 +1,5 @@
import React, { PropTypes } from 'react';
import React from 'react';
import PropTypes from 'prop-types';
const NUM_COLUMNS = 12;

View File

@@ -0,0 +1,101 @@
import React from 'react';
import PropTypes from 'prop-types';
import SyntaxHighlighter from 'react-syntax-highlighter';
import { github } from 'react-syntax-highlighter/dist/styles';
import ModalTrigger from './../../components/ModalTrigger';
const $ = window.$ = require('jquery');
const propTypes = {
animation: PropTypes.bool,
queryResponse: PropTypes.object,
chartStatus: PropTypes.string,
queryEndpoint: PropTypes.string.isRequired,
};
const defaultProps = {
animation: true,
};
export default class DisplayQueryButton extends React.PureComponent {
constructor(props) {
super(props);
this.state = {
language: null,
query: null,
isLoading: false,
error: null,
};
this.beforeOpen = this.beforeOpen.bind(this);
this.fetchQuery = this.fetchQuery.bind(this);
}
setStateFromQueryResponse() {
const qr = this.props.queryResponse;
this.setState({
language: qr.language,
query: qr.query,
isLoading: false,
});
}
fetchQuery() {
this.setState({ isLoading: true });
$.ajax({
type: 'GET',
url: this.props.queryEndpoint,
success: (data) => {
this.setState({
language: data.language,
query: data.query,
isLoading: false,
error: null,
});
},
error: (data) => {
this.setState({
error: data.responseJSON ? data.responseJSON.error : 'Error...',
isLoading: false,
});
},
});
}
beforeOpen() {
if (['loading', null].indexOf(this.props.chartStatus) >= 0 || !this.props.queryResponse) {
this.fetchQuery();
} else {
this.setStateFromQueryResponse();
}
}
renderModalBody() {
if (this.state.isLoading) {
return (<img
className="loading"
alt="Loading..."
src="/static/assets/images/loading.gif"
/>);
} else if (this.state.error) {
return <pre>{this.state.error}</pre>;
} else if (this.state.query) {
return (
<SyntaxHighlighter language={this.state.language} style={github}>
{this.state.query}
</SyntaxHighlighter>);
}
return null;
}
render() {
return (
<ModalTrigger
animation={this.props.animation}
isButton
triggerNode={<span>Query</span>}
modalTitle="Query"
bsSize="large"
beforeOpen={this.beforeOpen}
modalBody={this.renderModalBody()}
/>
);
}
}
DisplayQueryButton.propTypes = propTypes;
DisplayQueryButton.defaultProps = defaultProps;

View File

@@ -1,6 +1,7 @@
import React, { PropTypes } from 'react';
import CopyToClipboard from './../../components/CopyToClipboard';
import React from 'react';
import PropTypes from 'prop-types';
import { Popover, OverlayTrigger } from 'react-bootstrap';
import CopyToClipboard from './../../components/CopyToClipboard';
const propTypes = {
slice: PropTypes.object.isRequired,
@@ -56,14 +57,13 @@ export default class EmbedCodeButton extends React.Component {
rows="4"
readOnly
className="form-control input-sm"
>
</textarea>
/>
</div>
<div className="col-sm-2">
<CopyToClipboard
shouldShowText={false}
text={html}
copyNode={<i className="fa fa-clipboard" title="Copy to clipboard"></i>}
copyNode={<i className="fa fa-clipboard" title="Copy to clipboard" />}
/>
</div>
</div>
@@ -112,7 +112,7 @@ export default class EmbedCodeButton extends React.Component {
overlay={this.renderPopover()}
>
<span className="btn btn-default btn-sm">
<i className="fa fa-code"></i>&nbsp;
<i className="fa fa-code" />&nbsp;
</span>
</OverlayTrigger>
);

View File

@@ -1,4 +1,5 @@
import React, { PropTypes } from 'react';
import React from 'react';
import PropTypes from 'prop-types';
import cx from 'classnames';
import URLShortLinkButton from './URLShortLinkButton';
import EmbedCodeButton from './EmbedCodeButton';
@@ -7,11 +8,13 @@ import DisplayQueryButton from './DisplayQueryButton';
const propTypes = {
canDownload: PropTypes.oneOfType([PropTypes.string, PropTypes.bool]).isRequired,
slice: PropTypes.object,
queryEndpoint: PropTypes.string,
query: PropTypes.string,
queryEndpoint: PropTypes.string.isRequired,
queryResponse: PropTypes.object,
chartStatus: PropTypes.string,
};
export default function ExploreActionButtons({ canDownload, slice, query, queryEndpoint }) {
export default function ExploreActionButtons({
chartStatus, canDownload, slice, queryResponse, queryEndpoint }) {
const exportToCSVClasses = cx('btn btn-default btn-sm', {
'disabled disabledButton': !canDownload,
});
@@ -27,8 +30,9 @@ export default function ExploreActionButtons({ canDownload, slice, query, queryE
className="btn btn-default btn-sm"
title="Export to .json"
target="_blank"
rel="noopener noreferrer"
>
<i className="fa fa-file-code-o"></i> .json
<i className="fa fa-file-code-o" /> .json
</a>
<a
@@ -36,13 +40,15 @@ export default function ExploreActionButtons({ canDownload, slice, query, queryE
className={exportToCSVClasses}
title="Export to .csv format"
target="_blank"
rel="noopener noreferrer"
>
<i className="fa fa-file-text-o"></i> .csv
<i className="fa fa-file-text-o" /> .csv
</a>
<DisplayQueryButton
query={query}
queryResponse={queryResponse}
queryEndpoint={queryEndpoint}
chartStatus={chartStatus}
/>
</div>
);

View File

@@ -1,19 +1,20 @@
/* eslint camelcase: 0 */
import React, { PropTypes } from 'react';
import React from 'react';
import PropTypes from 'prop-types';
import { bindActionCreators } from 'redux';
import * as actions from '../actions/exploreActions';
import { connect } from 'react-redux';
import ChartContainer from './ChartContainer';
import ControlPanelsContainer from './ControlPanelsContainer';
import SaveModal from './SaveModal';
import QueryAndSaveBtns from './QueryAndSaveBtns';
import { getExploreUrl } from '../exploreUtils';
import * as actions from '../actions/exploreActions';
import { getFormDataFromControls } from '../stores/store';
const propTypes = {
actions: PropTypes.object.isRequired,
datasource_type: PropTypes.string.isRequired,
chartStatus: PropTypes.string.isRequired,
chartStatus: PropTypes.string,
controls: PropTypes.object.isRequired,
forcedHeight: PropTypes.string,
form_data: PropTypes.object.isRequired,
@@ -32,8 +33,8 @@ class ExploreViewContainer extends React.Component {
}
componentDidMount() {
this.props.actions.fetchDatasources();
window.addEventListener('resize', this.handleResize.bind(this));
this.triggerQueryIfNeeded();
}
componentWillReceiveProps(np) {
@@ -47,9 +48,7 @@ class ExploreViewContainer extends React.Component {
}
componentDidUpdate() {
if (this.props.triggerQuery) {
this.runQuery();
}
this.triggerQueryIfNeeded();
}
componentWillUnmount() {
@@ -61,7 +60,8 @@ class ExploreViewContainer extends React.Component {
this.props.actions.removeControlPanelAlert();
this.props.actions.removeChartAlert();
this.runQuery();
this.props.actions.triggerQuery();
history.pushState(
{},
document.title,
@@ -81,8 +81,10 @@ class ExploreViewContainer extends React.Component {
}
runQuery() {
this.props.actions.runQuery(this.props.form_data);
triggerQueryIfNeeded() {
if (this.props.triggerQuery && !this.hasErrors()) {
this.props.actions.runQuery(this.props.form_data);
}
}
handleResize() {
@@ -95,6 +97,11 @@ class ExploreViewContainer extends React.Component {
toggleModal() {
this.setState({ showModal: !this.state.showModal });
}
hasErrors() {
const ctrls = this.props.controls;
return Object.keys(ctrls).some(
k => ctrls[k].validationErrors && ctrls[k].validationErrors.length > 0);
}
renderErrorMessage() {
// Returns an error message as a node if any errors are in the store
const errors = [];
@@ -105,7 +112,7 @@ class ExploreViewContainer extends React.Component {
<div key={controlName}>
<strong>{`[ ${control.label} ] `}</strong>
{control.validationErrors.join('. ')}
</div>
</div>,
);
}
}
@@ -138,7 +145,7 @@ class ExploreViewContainer extends React.Component {
overflow: 'hidden',
}}
>
{this.state.showModal &&
{this.state.showModal &&
<SaveModal
onHide={this.toggleModal.bind(this)}
actions={this.props.actions}
@@ -177,7 +184,7 @@ function mapStateToProps(state) {
const form_data = getFormDataFromControls(state.controls);
return {
chartStatus: state.chartStatus,
datasource_type: state.datasource_type,
datasource_type: state.datasource.type,
controls: state.controls,
form_data,
standalone: state.standalone,

View File

@@ -1,15 +1,17 @@
import React, { PropTypes } from 'react';
import React from 'react';
import PropTypes from 'prop-types';
import { ButtonGroup, OverlayTrigger, Tooltip } from 'react-bootstrap';
import Button from '../../components/Button';
import classnames from 'classnames';
import Button from '../../components/Button';
const propTypes = {
canAdd: PropTypes.string.isRequired,
onQuery: PropTypes.func.isRequired,
onSave: PropTypes.func,
onStop: PropTypes.func,
loading: PropTypes.bool,
errorMessage: PropTypes.string,
errorMessage: PropTypes.node,
};
const defaultProps = {
@@ -37,6 +39,7 @@ export default function QueryAndSaveBtns(
className="query"
onClick={onQuery}
bsStyle={qryButtonStyle}
disabled={!!errorMessage}
>
<i className="fa fa-bolt" /> Query
</Button>
@@ -53,7 +56,7 @@ export default function QueryAndSaveBtns(
disabled={saveButtonDisabled}
onClick={onSave}
>
<i className="fa fa-plus-circle"></i> Save as
<i className="fa fa-plus-circle" /> Save
</Button>
</ButtonGroup>
{errorMessage &&

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