You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@superset.apache.org by ma...@apache.org on 2018/08/02 17:52:41 UTC
[incubator-superset] branch master updated: [sql lab] simplify the
visualize flow (#5523)
This is an automated email from the ASF dual-hosted git repository.
maximebeauchemin pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-superset.git
The following commit(s) were added to refs/heads/master by this push:
new fe6846b [sql lab] simplify the visualize flow (#5523)
fe6846b is described below
commit fe6846b8db1cd7e7f8db5b26ffcbf5e662ab14e3
Author: Maxime Beauchemin <ma...@gmail.com>
AuthorDate: Thu Aug 2 10:52:38 2018 -0700
[sql lab] simplify the visualize flow (#5523)
* [sql lab] simplify the visualize flow
The "visualize flow" linking SQL Lab to the "explore view" has never
worked so great for people, here's a list of issues:
* it's not really clear to users that their query is wrapped as a
subquery, and the explore view runs queries on top of it
* lint + fix tests
* Addressing comments
---
package-lock.json | 743 ---------------------
superset/assets/package.json | 2 +-
.../sqllab/ExploreResultsButton_spec.jsx | 225 +++++++
.../spec/javascripts/sqllab/ResultSet_spec.jsx | 20 +-
.../javascripts/sqllab/VisualizeModal_spec.jsx | 378 -----------
.../src/SqlLab/components/ExploreResultsButton.jsx | 167 +++++
.../assets/src/SqlLab/components/QueryTable.jsx | 11 -
.../assets/src/SqlLab/components/ResultSet.jsx | 105 ++-
.../assets/src/SqlLab/components/SouthPane.jsx | 3 +
.../src/SqlLab/components/TabbedSqlEditors.jsx | 1 +
.../src/SqlLab/components/VisualizeModal.jsx | 313 ---------
superset/assets/src/SqlLab/constants.js | 9 -
superset/assets/src/explore/visTypes.jsx | 1 +
superset/assets/yarn.lock | 4 +
superset/db_engine_specs.py | 2 +
superset/models/core.py | 5 +
superset/views/core.py | 37 +-
tests/sqllab_tests.py | 27 +-
18 files changed, 475 insertions(+), 1578 deletions(-)
diff --git a/package-lock.json b/package-lock.json
deleted file mode 100644
index dea4bad..0000000
--- a/package-lock.json
+++ /dev/null
@@ -1,743 +0,0 @@
-{
- "requires": true,
- "lockfileVersion": 1,
- "dependencies": {
- "ansi-regex": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
- "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8="
- },
- "ansi-styles": {
- "version": "2.2.1",
- "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz",
- "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4="
- },
- "babel-code-frame": {
- "version": "6.26.0",
- "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz",
- "integrity": "sha1-Y/1D99weO7fONZR9uP42mj9Yx0s=",
- "requires": {
- "chalk": "^1.1.3",
- "esutils": "^2.0.2",
- "js-tokens": "^3.0.2"
- }
- },
- "babel-helper-builder-binary-assignment-operator-visitor": {
- "version": "6.24.1",
- "resolved": "https://registry.npmjs.org/babel-helper-builder-binary-assignment-operator-visitor/-/babel-helper-builder-binary-assignment-operator-visitor-6.24.1.tgz",
- "integrity": "sha1-zORReto1b0IgvK6KAsKzRvmlZmQ=",
- "dev": true,
- "requires": {
- "babel-helper-explode-assignable-expression": "^6.24.1",
- "babel-runtime": "^6.22.0",
- "babel-types": "^6.24.1"
- }
- },
- "babel-helper-call-delegate": {
- "version": "6.24.1",
- "resolved": "https://registry.npmjs.org/babel-helper-call-delegate/-/babel-helper-call-delegate-6.24.1.tgz",
- "integrity": "sha1-7Oaqzdx25Bw0YfiL/Fdb0Nqi340=",
- "requires": {
- "babel-helper-hoist-variables": "^6.24.1",
- "babel-runtime": "^6.22.0",
- "babel-traverse": "^6.24.1",
- "babel-types": "^6.24.1"
- }
- },
- "babel-helper-define-map": {
- "version": "6.26.0",
- "resolved": "https://registry.npmjs.org/babel-helper-define-map/-/babel-helper-define-map-6.26.0.tgz",
- "integrity": "sha1-pfVtq0GiX5fstJjH66ypgZ+Vvl8=",
- "requires": {
- "babel-helper-function-name": "^6.24.1",
- "babel-runtime": "^6.26.0",
- "babel-types": "^6.26.0",
- "lodash": "^4.17.4"
- }
- },
- "babel-helper-explode-assignable-expression": {
- "version": "6.24.1",
- "resolved": "https://registry.npmjs.org/babel-helper-explode-assignable-expression/-/babel-helper-explode-assignable-expression-6.24.1.tgz",
- "integrity": "sha1-8luCz33BBDPFX3BZLVdGQArCLKo=",
- "dev": true,
- "requires": {
- "babel-runtime": "^6.22.0",
- "babel-traverse": "^6.24.1",
- "babel-types": "^6.24.1"
- }
- },
- "babel-helper-function-name": {
- "version": "6.24.1",
- "resolved": "https://registry.npmjs.org/babel-helper-function-name/-/babel-helper-function-name-6.24.1.tgz",
- "integrity": "sha1-00dbjAPtmCQqJbSDUasYOZ01gKk=",
- "requires": {
- "babel-helper-get-function-arity": "^6.24.1",
- "babel-runtime": "^6.22.0",
- "babel-template": "^6.24.1",
- "babel-traverse": "^6.24.1",
- "babel-types": "^6.24.1"
- }
- },
- "babel-helper-get-function-arity": {
- "version": "6.24.1",
- "resolved": "https://registry.npmjs.org/babel-helper-get-function-arity/-/babel-helper-get-function-arity-6.24.1.tgz",
- "integrity": "sha1-j3eCqpNAfEHTqlCQj4mwMbG2hT0=",
- "requires": {
- "babel-runtime": "^6.22.0",
- "babel-types": "^6.24.1"
- }
- },
- "babel-helper-hoist-variables": {
- "version": "6.24.1",
- "resolved": "https://registry.npmjs.org/babel-helper-hoist-variables/-/babel-helper-hoist-variables-6.24.1.tgz",
- "integrity": "sha1-HssnaJydJVE+rbyZFKc/VAi+enY=",
- "requires": {
- "babel-runtime": "^6.22.0",
- "babel-types": "^6.24.1"
- }
- },
- "babel-helper-optimise-call-expression": {
- "version": "6.24.1",
- "resolved": "https://registry.npmjs.org/babel-helper-optimise-call-expression/-/babel-helper-optimise-call-expression-6.24.1.tgz",
- "integrity": "sha1-96E0J7qfc/j0+pk8VKl4gtEkQlc=",
- "requires": {
- "babel-runtime": "^6.22.0",
- "babel-types": "^6.24.1"
- }
- },
- "babel-helper-regex": {
- "version": "6.26.0",
- "resolved": "https://registry.npmjs.org/babel-helper-regex/-/babel-helper-regex-6.26.0.tgz",
- "integrity": "sha1-MlxZ+QL4LyS3T6zu0DY5VPZJXnI=",
- "requires": {
- "babel-runtime": "^6.26.0",
- "babel-types": "^6.26.0",
- "lodash": "^4.17.4"
- }
- },
- "babel-helper-remap-async-to-generator": {
- "version": "6.24.1",
- "resolved": "https://registry.npmjs.org/babel-helper-remap-async-to-generator/-/babel-helper-remap-async-to-generator-6.24.1.tgz",
- "integrity": "sha1-XsWBgnrXI/7N04HxySg5BnbkVRs=",
- "dev": true,
- "requires": {
- "babel-helper-function-name": "^6.24.1",
- "babel-runtime": "^6.22.0",
- "babel-template": "^6.24.1",
- "babel-traverse": "^6.24.1",
- "babel-types": "^6.24.1"
- }
- },
- "babel-helper-replace-supers": {
- "version": "6.24.1",
- "resolved": "https://registry.npmjs.org/babel-helper-replace-supers/-/babel-helper-replace-supers-6.24.1.tgz",
- "integrity": "sha1-v22/5Dk40XNpohPKiov3S2qQqxo=",
- "requires": {
- "babel-helper-optimise-call-expression": "^6.24.1",
- "babel-messages": "^6.23.0",
- "babel-runtime": "^6.22.0",
- "babel-template": "^6.24.1",
- "babel-traverse": "^6.24.1",
- "babel-types": "^6.24.1"
- }
- },
- "babel-messages": {
- "version": "6.23.0",
- "resolved": "https://registry.npmjs.org/babel-messages/-/babel-messages-6.23.0.tgz",
- "integrity": "sha1-8830cDhYA1sqKVHG7F7fbGLyYw4=",
- "requires": {
- "babel-runtime": "^6.22.0"
- }
- },
- "babel-plugin-check-es2015-constants": {
- "version": "6.22.0",
- "resolved": "https://registry.npmjs.org/babel-plugin-check-es2015-constants/-/babel-plugin-check-es2015-constants-6.22.0.tgz",
- "integrity": "sha1-NRV7EBQm/S/9PaP3XH0ekYNbv4o=",
- "requires": {
- "babel-runtime": "^6.22.0"
- }
- },
- "babel-plugin-dynamic-import-node": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-1.2.0.tgz",
- "integrity": "sha512-yeDwKaLgGdTpXL7RgGt5r6T4LmnTza/hUn5Ul8uZSGGMtEjYo13Nxai7SQaGCTEzUtg9Zq9qJn0EjEr7SeSlTQ==",
- "requires": {
- "babel-plugin-syntax-dynamic-import": "^6.18.0"
- }
- },
- "babel-plugin-syntax-async-functions": {
- "version": "6.13.0",
- "resolved": "https://registry.npmjs.org/babel-plugin-syntax-async-functions/-/babel-plugin-syntax-async-functions-6.13.0.tgz",
- "integrity": "sha1-ytnK0RkbWtY0vzCuCHI5HgZHvpU=",
- "dev": true
- },
- "babel-plugin-syntax-dynamic-import": {
- "version": "6.18.0",
- "resolved": "https://registry.npmjs.org/babel-plugin-syntax-dynamic-import/-/babel-plugin-syntax-dynamic-import-6.18.0.tgz",
- "integrity": "sha1-jWomIpyDdFqZgqRBBRVyyqF5sdo="
- },
- "babel-plugin-syntax-exponentiation-operator": {
- "version": "6.13.0",
- "resolved": "https://registry.npmjs.org/babel-plugin-syntax-exponentiation-operator/-/babel-plugin-syntax-exponentiation-operator-6.13.0.tgz",
- "integrity": "sha1-nufoM3KQ2pUoggGmpX9BcDF4MN4=",
- "dev": true
- },
- "babel-plugin-syntax-trailing-function-commas": {
- "version": "6.22.0",
- "resolved": "https://registry.npmjs.org/babel-plugin-syntax-trailing-function-commas/-/babel-plugin-syntax-trailing-function-commas-6.22.0.tgz",
- "integrity": "sha1-ugNgk3+NBuQBgKQ/4NVhb/9TLPM=",
- "dev": true
- },
- "babel-plugin-transform-async-to-generator": {
- "version": "6.24.1",
- "resolved": "https://registry.npmjs.org/babel-plugin-transform-async-to-generator/-/babel-plugin-transform-async-to-generator-6.24.1.tgz",
- "integrity": "sha1-ZTbjeK/2yx1VF6wOQOs+n8jQh2E=",
- "dev": true,
- "requires": {
- "babel-helper-remap-async-to-generator": "^6.24.1",
- "babel-plugin-syntax-async-functions": "^6.8.0",
- "babel-runtime": "^6.22.0"
- }
- },
- "babel-plugin-transform-es2015-arrow-functions": {
- "version": "6.22.0",
- "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-arrow-functions/-/babel-plugin-transform-es2015-arrow-functions-6.22.0.tgz",
- "integrity": "sha1-RSaSy3EdX3ncf4XkQM5BufJE0iE=",
- "requires": {
- "babel-runtime": "^6.22.0"
- }
- },
- "babel-plugin-transform-es2015-block-scoped-functions": {
- "version": "6.22.0",
- "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-block-scoped-functions/-/babel-plugin-transform-es2015-block-scoped-functions-6.22.0.tgz",
- "integrity": "sha1-u8UbSflk1wy42OC5ToICRs46YUE=",
- "requires": {
- "babel-runtime": "^6.22.0"
- }
- },
- "babel-plugin-transform-es2015-block-scoping": {
- "version": "6.26.0",
- "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-block-scoping/-/babel-plugin-transform-es2015-block-scoping-6.26.0.tgz",
- "integrity": "sha1-1w9SmcEwjQXBL0Y4E7CgnnOxiV8=",
- "requires": {
- "babel-runtime": "^6.26.0",
- "babel-template": "^6.26.0",
- "babel-traverse": "^6.26.0",
- "babel-types": "^6.26.0",
- "lodash": "^4.17.4"
- }
- },
- "babel-plugin-transform-es2015-classes": {
- "version": "6.24.1",
- "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-classes/-/babel-plugin-transform-es2015-classes-6.24.1.tgz",
- "integrity": "sha1-WkxYpQyclGHlZLSyo7+ryXolhNs=",
- "requires": {
- "babel-helper-define-map": "^6.24.1",
- "babel-helper-function-name": "^6.24.1",
- "babel-helper-optimise-call-expression": "^6.24.1",
- "babel-helper-replace-supers": "^6.24.1",
- "babel-messages": "^6.23.0",
- "babel-runtime": "^6.22.0",
- "babel-template": "^6.24.1",
- "babel-traverse": "^6.24.1",
- "babel-types": "^6.24.1"
- }
- },
- "babel-plugin-transform-es2015-computed-properties": {
- "version": "6.24.1",
- "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-computed-properties/-/babel-plugin-transform-es2015-computed-properties-6.24.1.tgz",
- "integrity": "sha1-b+Ko0WiV1WNPTNmZttNICjCBWbM=",
- "requires": {
- "babel-runtime": "^6.22.0",
- "babel-template": "^6.24.1"
- }
- },
- "babel-plugin-transform-es2015-destructuring": {
- "version": "6.23.0",
- "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-destructuring/-/babel-plugin-transform-es2015-destructuring-6.23.0.tgz",
- "integrity": "sha1-mXux8auWf2gtKwh2/jWNYOdlxW0=",
- "requires": {
- "babel-runtime": "^6.22.0"
- }
- },
- "babel-plugin-transform-es2015-duplicate-keys": {
- "version": "6.24.1",
- "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-duplicate-keys/-/babel-plugin-transform-es2015-duplicate-keys-6.24.1.tgz",
- "integrity": "sha1-c+s9MQypaePvnskcU3QabxV2Qj4=",
- "requires": {
- "babel-runtime": "^6.22.0",
- "babel-types": "^6.24.1"
- }
- },
- "babel-plugin-transform-es2015-for-of": {
- "version": "6.23.0",
- "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-for-of/-/babel-plugin-transform-es2015-for-of-6.23.0.tgz",
- "integrity": "sha1-9HyVsrYT3x0+zC/bdXNiPHUkhpE=",
- "requires": {
- "babel-runtime": "^6.22.0"
- }
- },
- "babel-plugin-transform-es2015-function-name": {
- "version": "6.24.1",
- "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-function-name/-/babel-plugin-transform-es2015-function-name-6.24.1.tgz",
- "integrity": "sha1-g0yJhTvDaxrw86TF26qU/Y6sqos=",
- "requires": {
- "babel-helper-function-name": "^6.24.1",
- "babel-runtime": "^6.22.0",
- "babel-types": "^6.24.1"
- }
- },
- "babel-plugin-transform-es2015-literals": {
- "version": "6.22.0",
- "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-literals/-/babel-plugin-transform-es2015-literals-6.22.0.tgz",
- "integrity": "sha1-T1SgLWzWbPkVKAAZox0xklN3yi4=",
- "requires": {
- "babel-runtime": "^6.22.0"
- }
- },
- "babel-plugin-transform-es2015-modules-amd": {
- "version": "6.24.1",
- "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-amd/-/babel-plugin-transform-es2015-modules-amd-6.24.1.tgz",
- "integrity": "sha1-Oz5UAXI5hC1tGcMBHEvS8AoA0VQ=",
- "requires": {
- "babel-plugin-transform-es2015-modules-commonjs": "^6.24.1",
- "babel-runtime": "^6.22.0",
- "babel-template": "^6.24.1"
- }
- },
- "babel-plugin-transform-es2015-modules-commonjs": {
- "version": "6.26.2",
- "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-commonjs/-/babel-plugin-transform-es2015-modules-commonjs-6.26.2.tgz",
- "integrity": "sha512-CV9ROOHEdrjcwhIaJNBGMBCodN+1cfkwtM1SbUHmvyy35KGT7fohbpOxkE2uLz1o6odKK2Ck/tz47z+VqQfi9Q==",
- "requires": {
- "babel-plugin-transform-strict-mode": "^6.24.1",
- "babel-runtime": "^6.26.0",
- "babel-template": "^6.26.0",
- "babel-types": "^6.26.0"
- }
- },
- "babel-plugin-transform-es2015-modules-systemjs": {
- "version": "6.24.1",
- "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-systemjs/-/babel-plugin-transform-es2015-modules-systemjs-6.24.1.tgz",
- "integrity": "sha1-/4mhQrkRmpBhlfXxBuzzBdlAfSM=",
- "requires": {
- "babel-helper-hoist-variables": "^6.24.1",
- "babel-runtime": "^6.22.0",
- "babel-template": "^6.24.1"
- }
- },
- "babel-plugin-transform-es2015-modules-umd": {
- "version": "6.24.1",
- "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-umd/-/babel-plugin-transform-es2015-modules-umd-6.24.1.tgz",
- "integrity": "sha1-rJl+YoXNGO1hdq22B9YCNErThGg=",
- "requires": {
- "babel-plugin-transform-es2015-modules-amd": "^6.24.1",
- "babel-runtime": "^6.22.0",
- "babel-template": "^6.24.1"
- }
- },
- "babel-plugin-transform-es2015-object-super": {
- "version": "6.24.1",
- "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-object-super/-/babel-plugin-transform-es2015-object-super-6.24.1.tgz",
- "integrity": "sha1-JM72muIcuDp/hgPa0CH1cusnj40=",
- "requires": {
- "babel-helper-replace-supers": "^6.24.1",
- "babel-runtime": "^6.22.0"
- }
- },
- "babel-plugin-transform-es2015-parameters": {
- "version": "6.24.1",
- "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-parameters/-/babel-plugin-transform-es2015-parameters-6.24.1.tgz",
- "integrity": "sha1-V6w1GrScrxSpfNE7CfZv3wpiXys=",
- "requires": {
- "babel-helper-call-delegate": "^6.24.1",
- "babel-helper-get-function-arity": "^6.24.1",
- "babel-runtime": "^6.22.0",
- "babel-template": "^6.24.1",
- "babel-traverse": "^6.24.1",
- "babel-types": "^6.24.1"
- }
- },
- "babel-plugin-transform-es2015-shorthand-properties": {
- "version": "6.24.1",
- "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-shorthand-properties/-/babel-plugin-transform-es2015-shorthand-properties-6.24.1.tgz",
- "integrity": "sha1-JPh11nIch2YbvZmkYi5R8U3jiqA=",
- "requires": {
- "babel-runtime": "^6.22.0",
- "babel-types": "^6.24.1"
- }
- },
- "babel-plugin-transform-es2015-spread": {
- "version": "6.22.0",
- "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-spread/-/babel-plugin-transform-es2015-spread-6.22.0.tgz",
- "integrity": "sha1-1taKmfia7cRTbIGlQujdnxdG+NE=",
- "requires": {
- "babel-runtime": "^6.22.0"
- }
- },
- "babel-plugin-transform-es2015-sticky-regex": {
- "version": "6.24.1",
- "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-sticky-regex/-/babel-plugin-transform-es2015-sticky-regex-6.24.1.tgz",
- "integrity": "sha1-AMHNsaynERLN8M9hJsLta0V8zbw=",
- "requires": {
- "babel-helper-regex": "^6.24.1",
- "babel-runtime": "^6.22.0",
- "babel-types": "^6.24.1"
- }
- },
- "babel-plugin-transform-es2015-template-literals": {
- "version": "6.22.0",
- "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-template-literals/-/babel-plugin-transform-es2015-template-literals-6.22.0.tgz",
- "integrity": "sha1-qEs0UPfp+PH2g51taH2oS7EjbY0=",
- "requires": {
- "babel-runtime": "^6.22.0"
- }
- },
- "babel-plugin-transform-es2015-typeof-symbol": {
- "version": "6.23.0",
- "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-typeof-symbol/-/babel-plugin-transform-es2015-typeof-symbol-6.23.0.tgz",
- "integrity": "sha1-3sCfHN3/lLUqxz1QXITfWdzOs3I=",
- "requires": {
- "babel-runtime": "^6.22.0"
- }
- },
- "babel-plugin-transform-es2015-unicode-regex": {
- "version": "6.24.1",
- "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-unicode-regex/-/babel-plugin-transform-es2015-unicode-regex-6.24.1.tgz",
- "integrity": "sha1-04sS9C6nMj9yk4fxinxa4frrNek=",
- "requires": {
- "babel-helper-regex": "^6.24.1",
- "babel-runtime": "^6.22.0",
- "regexpu-core": "^2.0.0"
- }
- },
- "babel-plugin-transform-exponentiation-operator": {
- "version": "6.24.1",
- "resolved": "https://registry.npmjs.org/babel-plugin-transform-exponentiation-operator/-/babel-plugin-transform-exponentiation-operator-6.24.1.tgz",
- "integrity": "sha1-KrDJx/MJj6SJB3cruBP+QejeOg4=",
- "dev": true,
- "requires": {
- "babel-helper-builder-binary-assignment-operator-visitor": "^6.24.1",
- "babel-plugin-syntax-exponentiation-operator": "^6.8.0",
- "babel-runtime": "^6.22.0"
- }
- },
- "babel-plugin-transform-regenerator": {
- "version": "6.26.0",
- "resolved": "https://registry.npmjs.org/babel-plugin-transform-regenerator/-/babel-plugin-transform-regenerator-6.26.0.tgz",
- "integrity": "sha1-4HA2lvveJ/Cj78rPi03KL3s6jy8=",
- "requires": {
- "regenerator-transform": "^0.10.0"
- }
- },
- "babel-plugin-transform-strict-mode": {
- "version": "6.24.1",
- "resolved": "https://registry.npmjs.org/babel-plugin-transform-strict-mode/-/babel-plugin-transform-strict-mode-6.24.1.tgz",
- "integrity": "sha1-1fr3qleKZbvlkc9e2uBKDGcCB1g=",
- "requires": {
- "babel-runtime": "^6.22.0",
- "babel-types": "^6.24.1"
- }
- },
- "babel-preset-env": {
- "version": "1.7.0",
- "resolved": "https://registry.npmjs.org/babel-preset-env/-/babel-preset-env-1.7.0.tgz",
- "integrity": "sha512-9OR2afuKDneX2/q2EurSftUYM0xGu4O2D9adAhVfADDhrYDaxXV0rBbevVYoY9n6nyX1PmQW/0jtpJvUNr9CHg==",
- "dev": true,
- "requires": {
- "babel-plugin-check-es2015-constants": "^6.22.0",
- "babel-plugin-syntax-trailing-function-commas": "^6.22.0",
- "babel-plugin-transform-async-to-generator": "^6.22.0",
- "babel-plugin-transform-es2015-arrow-functions": "^6.22.0",
- "babel-plugin-transform-es2015-block-scoped-functions": "^6.22.0",
- "babel-plugin-transform-es2015-block-scoping": "^6.23.0",
- "babel-plugin-transform-es2015-classes": "^6.23.0",
- "babel-plugin-transform-es2015-computed-properties": "^6.22.0",
- "babel-plugin-transform-es2015-destructuring": "^6.23.0",
- "babel-plugin-transform-es2015-duplicate-keys": "^6.22.0",
- "babel-plugin-transform-es2015-for-of": "^6.23.0",
- "babel-plugin-transform-es2015-function-name": "^6.22.0",
- "babel-plugin-transform-es2015-literals": "^6.22.0",
- "babel-plugin-transform-es2015-modules-amd": "^6.22.0",
- "babel-plugin-transform-es2015-modules-commonjs": "^6.23.0",
- "babel-plugin-transform-es2015-modules-systemjs": "^6.23.0",
- "babel-plugin-transform-es2015-modules-umd": "^6.23.0",
- "babel-plugin-transform-es2015-object-super": "^6.22.0",
- "babel-plugin-transform-es2015-parameters": "^6.23.0",
- "babel-plugin-transform-es2015-shorthand-properties": "^6.22.0",
- "babel-plugin-transform-es2015-spread": "^6.22.0",
- "babel-plugin-transform-es2015-sticky-regex": "^6.22.0",
- "babel-plugin-transform-es2015-template-literals": "^6.22.0",
- "babel-plugin-transform-es2015-typeof-symbol": "^6.23.0",
- "babel-plugin-transform-es2015-unicode-regex": "^6.22.0",
- "babel-plugin-transform-exponentiation-operator": "^6.22.0",
- "babel-plugin-transform-regenerator": "^6.22.0",
- "browserslist": "^3.2.6",
- "invariant": "^2.2.2",
- "semver": "^5.3.0"
- }
- },
- "babel-preset-es2015": {
- "version": "6.24.1",
- "resolved": "https://registry.npmjs.org/babel-preset-es2015/-/babel-preset-es2015-6.24.1.tgz",
- "integrity": "sha1-1EBQ1rwsn+6nAqrzjXJ6AhBTiTk=",
- "requires": {
- "babel-plugin-check-es2015-constants": "^6.22.0",
- "babel-plugin-transform-es2015-arrow-functions": "^6.22.0",
- "babel-plugin-transform-es2015-block-scoped-functions": "^6.22.0",
- "babel-plugin-transform-es2015-block-scoping": "^6.24.1",
- "babel-plugin-transform-es2015-classes": "^6.24.1",
- "babel-plugin-transform-es2015-computed-properties": "^6.24.1",
- "babel-plugin-transform-es2015-destructuring": "^6.22.0",
- "babel-plugin-transform-es2015-duplicate-keys": "^6.24.1",
- "babel-plugin-transform-es2015-for-of": "^6.22.0",
- "babel-plugin-transform-es2015-function-name": "^6.24.1",
- "babel-plugin-transform-es2015-literals": "^6.22.0",
- "babel-plugin-transform-es2015-modules-amd": "^6.24.1",
- "babel-plugin-transform-es2015-modules-commonjs": "^6.24.1",
- "babel-plugin-transform-es2015-modules-systemjs": "^6.24.1",
- "babel-plugin-transform-es2015-modules-umd": "^6.24.1",
- "babel-plugin-transform-es2015-object-super": "^6.24.1",
- "babel-plugin-transform-es2015-parameters": "^6.24.1",
- "babel-plugin-transform-es2015-shorthand-properties": "^6.24.1",
- "babel-plugin-transform-es2015-spread": "^6.22.0",
- "babel-plugin-transform-es2015-sticky-regex": "^6.24.1",
- "babel-plugin-transform-es2015-template-literals": "^6.22.0",
- "babel-plugin-transform-es2015-typeof-symbol": "^6.22.0",
- "babel-plugin-transform-es2015-unicode-regex": "^6.24.1",
- "babel-plugin-transform-regenerator": "^6.24.1"
- }
- },
- "babel-runtime": {
- "version": "6.26.0",
- "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz",
- "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=",
- "requires": {
- "core-js": "^2.4.0",
- "regenerator-runtime": "^0.11.0"
- }
- },
- "babel-template": {
- "version": "6.26.0",
- "resolved": "https://registry.npmjs.org/babel-template/-/babel-template-6.26.0.tgz",
- "integrity": "sha1-3gPi0WOWsGn0bdn/+FIfsaDjXgI=",
- "requires": {
- "babel-runtime": "^6.26.0",
- "babel-traverse": "^6.26.0",
- "babel-types": "^6.26.0",
- "babylon": "^6.18.0",
- "lodash": "^4.17.4"
- }
- },
- "babel-traverse": {
- "version": "6.26.0",
- "resolved": "https://registry.npmjs.org/babel-traverse/-/babel-traverse-6.26.0.tgz",
- "integrity": "sha1-RqnL1+3MYsjlwGTi0tjQ9ANXZu4=",
- "requires": {
- "babel-code-frame": "^6.26.0",
- "babel-messages": "^6.23.0",
- "babel-runtime": "^6.26.0",
- "babel-types": "^6.26.0",
- "babylon": "^6.18.0",
- "debug": "^2.6.8",
- "globals": "^9.18.0",
- "invariant": "^2.2.2",
- "lodash": "^4.17.4"
- }
- },
- "babel-types": {
- "version": "6.26.0",
- "resolved": "https://registry.npmjs.org/babel-types/-/babel-types-6.26.0.tgz",
- "integrity": "sha1-o7Bz+Uq0nrb6Vc1lInozQ4BjJJc=",
- "requires": {
- "babel-runtime": "^6.26.0",
- "esutils": "^2.0.2",
- "lodash": "^4.17.4",
- "to-fast-properties": "^1.0.3"
- }
- },
- "babylon": {
- "version": "6.18.0",
- "resolved": "https://registry.npmjs.org/babylon/-/babylon-6.18.0.tgz",
- "integrity": "sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ=="
- },
- "browserslist": {
- "version": "3.2.8",
- "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-3.2.8.tgz",
- "integrity": "sha512-WHVocJYavUwVgVViC0ORikPHQquXwVh939TaelZ4WDqpWgTX/FsGhl/+P4qBUAGcRvtOgDgC+xftNWWp2RUTAQ==",
- "dev": true,
- "requires": {
- "caniuse-lite": "^1.0.30000844",
- "electron-to-chromium": "^1.3.47"
- }
- },
- "caniuse-lite": {
- "version": "1.0.30000856",
- "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30000856.tgz",
- "integrity": "sha512-x3mYcApHMQemyaHuH/RyqtKCGIYTgEA63fdi+VBvDz8xUSmRiVWTLeyKcoGQCGG6UPR9/+4qG4OKrTa6aSQRKg==",
- "dev": true
- },
- "chalk": {
- "version": "1.1.3",
- "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
- "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=",
- "requires": {
- "ansi-styles": "^2.2.1",
- "escape-string-regexp": "^1.0.2",
- "has-ansi": "^2.0.0",
- "strip-ansi": "^3.0.0",
- "supports-color": "^2.0.0"
- }
- },
- "core-js": {
- "version": "2.5.7",
- "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.5.7.tgz",
- "integrity": "sha512-RszJCAxg/PP6uzXVXL6BsxSXx/B05oJAQ2vkJRjyjrEcNVycaqOmNb5OTxZPE3xa5gwZduqza6L9JOCenh/Ecw=="
- },
- "debug": {
- "version": "2.6.9",
- "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
- "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
- "requires": {
- "ms": "2.0.0"
- }
- },
- "electron-to-chromium": {
- "version": "1.3.48",
- "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.48.tgz",
- "integrity": "sha1-07DYWTgUBE4JLs4hCPw6ya6kuQA=",
- "dev": true
- },
- "escape-string-regexp": {
- "version": "1.0.5",
- "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
- "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ="
- },
- "esutils": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz",
- "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs="
- },
- "globals": {
- "version": "9.18.0",
- "resolved": "https://registry.npmjs.org/globals/-/globals-9.18.0.tgz",
- "integrity": "sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ=="
- },
- "has-ansi": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz",
- "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=",
- "requires": {
- "ansi-regex": "^2.0.0"
- }
- },
- "invariant": {
- "version": "2.2.4",
- "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz",
- "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==",
- "requires": {
- "loose-envify": "^1.0.0"
- }
- },
- "js-tokens": {
- "version": "3.0.2",
- "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz",
- "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls="
- },
- "jsesc": {
- "version": "0.5.0",
- "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz",
- "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0="
- },
- "lodash": {
- "version": "4.17.10",
- "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.10.tgz",
- "integrity": "sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg=="
- },
- "loose-envify": {
- "version": "1.3.1",
- "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.3.1.tgz",
- "integrity": "sha1-0aitM/qc4OcT1l/dCsi3SNR4yEg=",
- "requires": {
- "js-tokens": "^3.0.0"
- }
- },
- "ms": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
- "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
- },
- "private": {
- "version": "0.1.8",
- "resolved": "https://registry.npmjs.org/private/-/private-0.1.8.tgz",
- "integrity": "sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg=="
- },
- "regenerate": {
- "version": "1.4.0",
- "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.0.tgz",
- "integrity": "sha512-1G6jJVDWrt0rK99kBjvEtziZNCICAuvIPkSiUFIQxVP06RCVpq3dmDo2oi6ABpYaDYaTRr67BEhL8r1wgEZZKg=="
- },
- "regenerator-runtime": {
- "version": "0.11.1",
- "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz",
- "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg=="
- },
- "regenerator-transform": {
- "version": "0.10.1",
- "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.10.1.tgz",
- "integrity": "sha512-PJepbvDbuK1xgIgnau7Y90cwaAmO/LCLMI2mPvaXq2heGMR3aWW5/BQvYrhJ8jgmQjXewXvBjzfqKcVOmhjZ6Q==",
- "requires": {
- "babel-runtime": "^6.18.0",
- "babel-types": "^6.19.0",
- "private": "^0.1.6"
- }
- },
- "regexpu-core": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-2.0.0.tgz",
- "integrity": "sha1-SdA4g3uNz4v6W5pCE5k45uoq4kA=",
- "requires": {
- "regenerate": "^1.2.1",
- "regjsgen": "^0.2.0",
- "regjsparser": "^0.1.4"
- }
- },
- "regjsgen": {
- "version": "0.2.0",
- "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.2.0.tgz",
- "integrity": "sha1-bAFq3qxVT3WCP+N6wFuS1aTtsfc="
- },
- "regjsparser": {
- "version": "0.1.5",
- "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.1.5.tgz",
- "integrity": "sha1-fuj4Tcb6eS0/0K4ijSS9lJ6tIFw=",
- "requires": {
- "jsesc": "~0.5.0"
- }
- },
- "semver": {
- "version": "5.5.0",
- "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz",
- "integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==",
- "dev": true
- },
- "strip-ansi": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
- "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
- "requires": {
- "ansi-regex": "^2.0.0"
- }
- },
- "supports-color": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz",
- "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc="
- },
- "to-fast-properties": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-1.0.3.tgz",
- "integrity": "sha1-uDVx+k2MJbguIxsG46MFXeTKGkc="
- }
- }
-}
diff --git a/superset/assets/package.json b/superset/assets/package.json
index 7eaa845..510ba8e 100644
--- a/superset/assets/package.json
+++ b/superset/assets/package.json
@@ -94,6 +94,7 @@
"react-addons-shallow-compare": "^15.4.2",
"react-bootstrap": "^0.31.5",
"react-bootstrap-datetimepicker": "0.0.22",
+ "react-bootstrap-dialog": "^0.10.0",
"react-bootstrap-slider": "2.1.5",
"react-bootstrap-table": "^4.3.1",
"react-color": "^2.13.8",
@@ -167,7 +168,6 @@
"react-addons-test-utils": "^15.6.2",
"react-test-renderer": "^15.6.2",
"redux-mock-store": "^1.2.3",
- "//": "known minor issues in >5.0",
"sinon": "^4.5.0",
"style-loader": "^0.21.0",
"transform-loader": "^0.2.3",
diff --git a/superset/assets/spec/javascripts/sqllab/ExploreResultsButton_spec.jsx b/superset/assets/spec/javascripts/sqllab/ExploreResultsButton_spec.jsx
new file mode 100644
index 0000000..b57ddd4
--- /dev/null
+++ b/superset/assets/spec/javascripts/sqllab/ExploreResultsButton_spec.jsx
@@ -0,0 +1,225 @@
+import React from 'react';
+import configureStore from 'redux-mock-store';
+import thunk from 'redux-thunk';
+
+import { shallow } from 'enzyme';
+import { describe, it } from 'mocha';
+import { expect } from 'chai';
+import sinon from 'sinon';
+
+import $ from 'jquery';
+import shortid from 'shortid';
+import { queries } from './fixtures';
+import { sqlLabReducer } from '../../../src/SqlLab/reducers';
+import * as actions from '../../../src/SqlLab/actions';
+import ExploreResultsButton from '../../../src/SqlLab/components/ExploreResultsButton';
+import * as exploreUtils from '../../../src/explore/exploreUtils';
+import Button from '../../../src/components/Button';
+
+describe('ExploreResultsButton', () => {
+ const middlewares = [thunk];
+ const mockStore = configureStore(middlewares);
+ const database = {
+ allows_subquery: true,
+ };
+ const initialState = {
+ sqlLab: {
+ ...sqlLabReducer(undefined, {}),
+ common: {
+ conf: { SUPERSET_WEBSERVER_TIMEOUT: 45 },
+ },
+ },
+ };
+ const store = mockStore(initialState);
+ const mockedProps = {
+ database,
+ show: true,
+ query: queries[0],
+ };
+ const mockColumns = {
+ ds: {
+ is_date: true,
+ is_dim: false,
+ name: 'ds',
+ type: 'STRING',
+ },
+ gender: {
+ is_date: false,
+ is_dim: true,
+ name: 'gender',
+ type: 'STRING',
+ },
+ };
+ const mockChartTypeBarChart = {
+ label: 'Distribution - Bar Chart',
+ requiresTime: false,
+ value: 'dist_bar',
+ };
+ const mockChartTypeTB = {
+ label: 'Time Series - Bar Chart',
+ requiresTime: true,
+ value: 'bar',
+ };
+ const getExploreResultsButtonWrapper = () => (
+ shallow(<ExploreResultsButton {...mockedProps} />, {
+ context: { store },
+ }).dive());
+
+ it('renders', () => {
+ expect(React.isValidElement(<ExploreResultsButton />)).to.equal(true);
+ });
+ it('renders with props', () => {
+ expect(
+ React.isValidElement(<ExploreResultsButton {...mockedProps} />),
+ ).to.equal(true);
+ });
+ it('renders a Button', () => {
+ const wrapper = getExploreResultsButtonWrapper();
+ expect(wrapper.find(Button)).to.have.length(1);
+ });
+
+ describe('getColumnFromProps', () => {
+ it('should require valid query parameter in props', () => {
+ const emptyQuery = {
+ database,
+ show: true,
+ query: {},
+ };
+ const wrapper = shallow(<ExploreResultsButton {...emptyQuery} />, {
+ context: { store },
+ }).dive();
+ expect(wrapper.state().hints).to.deep.equal([]);
+ });
+ });
+
+ describe('datasourceName', () => {
+ let wrapper;
+ let stub;
+ beforeEach(() => {
+ wrapper = getExploreResultsButtonWrapper();
+ stub = sinon.stub(shortid, 'generate').returns('abcd');
+ });
+ afterEach(() => {
+ stub.restore();
+ });
+
+ it('should generate data source name from query', () => {
+ const sampleQuery = queries[0];
+ const name = wrapper.instance().datasourceName();
+ expect(name).to.equal(`${sampleQuery.user}-${sampleQuery.tab}-abcd`);
+ });
+ it('should generate data source name with empty query', () => {
+ wrapper.setProps({ query: {} });
+ const name = wrapper.instance().datasourceName();
+ expect(name).to.equal('undefined-abcd');
+ });
+
+ it('should build viz options', () => {
+ wrapper.setState({ chartType: mockChartTypeTB });
+ const spy = sinon.spy(wrapper.instance(), 'buildVizOptions');
+ wrapper.instance().buildVizOptions();
+ expect(spy.returnValues[0]).to.deep.equal({
+ schema: 'test_schema',
+ sql: wrapper.instance().props.query.sql,
+ dbId: wrapper.instance().props.query.dbId,
+ columns: Object.values(mockColumns),
+ templateParams: undefined,
+ datasourceName: 'admin-Demo-abcd',
+ });
+ });
+ });
+
+ it('should build visualize advise for long query', () => {
+ const longQuery = { ...queries[0], endDttm: 1476910666798 };
+ const props = {
+ show: true,
+ query: longQuery,
+ database,
+ };
+ const longQueryWrapper = shallow(<ExploreResultsButton {...props} />, {
+ context: { store },
+ }).dive();
+ const inst = longQueryWrapper.instance();
+ expect(inst.getQueryDuration()).to.equal(100.7050400390625);
+ });
+
+ describe('visualize', () => {
+ const wrapper = getExploreResultsButtonWrapper();
+ const mockOptions = { attr: 'mockOptions' };
+ wrapper.setState({
+ chartType: mockChartTypeBarChart,
+ datasourceName: 'mockDatasourceName',
+ });
+
+ let ajaxSpy;
+ let datasourceSpy;
+ beforeEach(() => {
+ ajaxSpy = sinon.spy($, 'ajax');
+ sinon.stub(JSON, 'parse').callsFake(() => ({ table_id: 107 }));
+ sinon.stub(exploreUtils, 'getExploreUrlAndPayload').callsFake(() => ({ url: 'mockURL', payload: { datasource: '107__table' } }));
+ sinon.spy(exploreUtils, 'exportChart');
+ sinon.stub(wrapper.instance(), 'buildVizOptions').callsFake(() => (mockOptions));
+ datasourceSpy = sinon.stub(actions, 'createDatasource');
+ });
+ afterEach(() => {
+ ajaxSpy.restore();
+ JSON.parse.restore();
+ exploreUtils.getExploreUrlAndPayload.restore();
+ exploreUtils.exportChart.restore();
+ wrapper.instance().buildVizOptions.restore();
+ datasourceSpy.restore();
+ });
+
+ it('should build request', () => {
+ wrapper.instance().visualize();
+ expect(ajaxSpy.callCount).to.equal(1);
+
+ const spyCall = ajaxSpy.getCall(0);
+ expect(spyCall.args[0].type).to.equal('POST');
+ expect(spyCall.args[0].url).to.equal('/superset/sqllab_viz/');
+ expect(spyCall.args[0].data.data).to.equal(JSON.stringify(mockOptions));
+ });
+ it('should open new window', () => {
+ const infoToastSpy = sinon.spy();
+
+ datasourceSpy.callsFake(() => {
+ const d = $.Deferred();
+ d.resolve('done');
+ return d.promise();
+ });
+
+ wrapper.setProps({
+ actions: {
+ createDatasource: datasourceSpy,
+ addInfoToast: infoToastSpy,
+ },
+ });
+
+ wrapper.instance().visualize();
+ expect(exploreUtils.exportChart.callCount).to.equal(1);
+ expect(exploreUtils.exportChart.getCall(0).args[0].datasource).to.equal('107__table');
+ expect(infoToastSpy.callCount).to.equal(1);
+ });
+ it('should add error toast', () => {
+ const dangerToastSpy = sinon.spy();
+
+ datasourceSpy.callsFake(() => {
+ const d = $.Deferred();
+ d.reject('error message');
+ return d.promise();
+ });
+
+
+ wrapper.setProps({
+ actions: {
+ createDatasource: datasourceSpy,
+ addDangerToast: dangerToastSpy,
+ },
+ });
+
+ wrapper.instance().visualize();
+ expect(exploreUtils.exportChart.callCount).to.equal(0);
+ expect(dangerToastSpy.callCount).to.equal(1);
+ });
+ });
+});
diff --git a/superset/assets/spec/javascripts/sqllab/ResultSet_spec.jsx b/superset/assets/spec/javascripts/sqllab/ResultSet_spec.jsx
index b1f2708..8ca9acd 100644
--- a/superset/assets/spec/javascripts/sqllab/ResultSet_spec.jsx
+++ b/superset/assets/spec/javascripts/sqllab/ResultSet_spec.jsx
@@ -4,9 +4,9 @@ import { describe, it } from 'mocha';
import { expect } from 'chai';
import sinon from 'sinon';
-import { Alert, ProgressBar, Button } from 'react-bootstrap';
+import { Alert, ProgressBar } from 'react-bootstrap';
import FilterableTable from '../../../src/components/FilterableTable/FilterableTable';
-import VisualizeModal from '../../../src/SqlLab/components/VisualizeModal';
+import ExploreResultsButton from '../../../src/SqlLab/components/ExploreResultsButton';
import ResultSet from '../../../src/SqlLab/components/ResultSet';
import { queries, stoppedQuery, runningQuery, cachedQuery } from './fixtures';
@@ -48,20 +48,6 @@ describe('ResultSet', () => {
const wrapper = shallow(<ResultSet {...mockedProps} />);
expect(wrapper.find(FilterableTable)).to.have.length(1);
});
- describe('getControls', () => {
- it('should render controls', () => {
- const wrapper = shallow(<ResultSet {...mockedProps} />);
- wrapper.instance().getControls();
- expect(wrapper.find(Button)).to.have.length(2);
- expect(wrapper.find('input').props().placeholder).to.equal('Search Results');
- });
- it('should handle no controls', () => {
- const wrapper = shallow(<ResultSet {...mockedProps} />);
- wrapper.setProps({ search: false, visualize: false, csv: false });
- const controls = wrapper.instance().getControls();
- expect(controls.props.className).to.equal('noControls');
- });
- });
describe('componentWillReceiveProps', () => {
const wrapper = shallow(<ResultSet {...mockedProps} />);
let spy;
@@ -88,7 +74,7 @@ describe('ResultSet', () => {
const wrapper = shallow(<ResultSet {...mockedProps} />);
const filterableTable = wrapper.find(FilterableTable);
expect(filterableTable.props().data).to.equal(mockedProps.query.results.data);
- expect(wrapper.find(VisualizeModal)).to.have.length(1);
+ expect(wrapper.find(ExploreResultsButton)).to.have.length(1);
});
it('should render empty results', () => {
const wrapper = shallow(<ResultSet {...mockedProps} />);
diff --git a/superset/assets/spec/javascripts/sqllab/VisualizeModal_spec.jsx b/superset/assets/spec/javascripts/sqllab/VisualizeModal_spec.jsx
deleted file mode 100644
index 5eb4802..0000000
--- a/superset/assets/spec/javascripts/sqllab/VisualizeModal_spec.jsx
+++ /dev/null
@@ -1,378 +0,0 @@
-import React from 'react';
-import configureStore from 'redux-mock-store';
-import thunk from 'redux-thunk';
-
-import { Modal } from 'react-bootstrap';
-import { shallow } from 'enzyme';
-import { describe, it } from 'mocha';
-import { expect } from 'chai';
-import sinon from 'sinon';
-
-import $ from 'jquery';
-import shortid from 'shortid';
-import { queries } from './fixtures';
-import { sqlLabReducer } from '../../../src/SqlLab/reducers';
-import * as actions from '../../../src/SqlLab/actions';
-import { VISUALIZE_VALIDATION_ERRORS } from '../../../src/SqlLab/constants';
-import VisualizeModal from '../../../src/SqlLab/components/VisualizeModal';
-import * as exploreUtils from '../../../src/explore/exploreUtils';
-
-describe('VisualizeModal', () => {
- const middlewares = [thunk];
- const mockStore = configureStore(middlewares);
- const initialState = {
- sqlLab: {
- ...sqlLabReducer(undefined, {}),
- common: {
- conf: { SUPERSET_WEBSERVER_TIMEOUT: 45 },
- },
- },
- };
- const store = mockStore(initialState);
- const mockedProps = {
- show: true,
- query: queries[0],
- };
- const mockColumns = {
- ds: {
- is_date: true,
- is_dim: false,
- name: 'ds',
- type: 'STRING',
- },
- gender: {
- is_date: false,
- is_dim: true,
- name: 'gender',
- type: 'STRING',
- },
- };
- const mockChartTypeBarChart = {
- label: 'Distribution - Bar Chart',
- requiresTime: false,
- value: 'dist_bar',
- };
- const mockChartTypeTB = {
- label: 'Time Series - Bar Chart',
- requiresTime: true,
- value: 'bar',
- };
- const mockEvent = {
- target: {
- value: 'mock event value',
- },
- };
- const getVisualizeModalWrapper = () => (
- shallow(<VisualizeModal {...mockedProps} />, {
- context: { store },
- }).dive());
-
- it('renders', () => {
- expect(React.isValidElement(<VisualizeModal />)).to.equal(true);
- });
- it('renders with props', () => {
- expect(
- React.isValidElement(<VisualizeModal {...mockedProps} />),
- ).to.equal(true);
- });
- it('renders a Modal', () => {
- const wrapper = getVisualizeModalWrapper();
- expect(wrapper.find(Modal)).to.have.length(1);
- });
-
- describe('getColumnFromProps', () => {
- it('should require valid query parameter in props', () => {
- const emptyQuery = {
- show: true,
- query: {},
- };
- const wrapper = shallow(<VisualizeModal {...emptyQuery} />, {
- context: { store },
- }).dive();
- expect(wrapper.state().columns).to.deep.equal({});
- });
- it('should set columns state', () => {
- const wrapper = getVisualizeModalWrapper();
- expect(wrapper.state().columns).to.deep.equal(mockColumns);
- });
- it('should not change columns state when closing Modal', () => {
- const wrapper = getVisualizeModalWrapper();
- expect(wrapper.state().columns).to.deep.equal(mockColumns);
-
- // first change columns state
- const newColumns = {
- ds: {
- is_date: true,
- is_dim: false,
- name: 'ds',
- type: 'STRING',
- },
- name: {
- is_date: false,
- is_dim: true,
- name: 'name',
- type: 'STRING',
- },
- };
- wrapper.instance().setState({ columns: newColumns });
- // then close Modal
- wrapper.setProps({ show: false });
- expect(wrapper.state().columns).to.deep.equal(newColumns);
- });
- });
-
- describe('datasourceName', () => {
- const wrapper = getVisualizeModalWrapper();
- let stub;
- beforeEach(() => {
- stub = sinon.stub(shortid, 'generate').returns('abcd');
- });
- afterEach(() => {
- stub.restore();
- });
-
- it('should generate data source name from query', () => {
- const sampleQuery = queries[0];
- const name = wrapper.instance().datasourceName();
- expect(name).to.equal(`${sampleQuery.user}-${sampleQuery.db}-${sampleQuery.tab}-abcd`);
- });
- it('should generate data source name with empty query', () => {
- wrapper.setProps({ query: {} });
- const name = wrapper.instance().datasourceName();
- expect(name).to.equal('undefined-abcd');
- });
- });
-
- describe('mergedColumns', () => {
- const wrapper = getVisualizeModalWrapper();
- const oldColumns = {
- ds: 1,
- gender: 2,
- };
-
- it('should merge by column name', () => {
- wrapper.setState({ columns: {} });
- const mc = wrapper.instance().mergedColumns();
- expect(mc).to.deep.equal(mockColumns);
- });
- it('should not override current state', () => {
- wrapper.setState({ columns: oldColumns });
-
- const mc = wrapper.instance().mergedColumns();
- expect(mc.ds).to.equal(oldColumns.ds);
- expect(mc.gender).to.equal(oldColumns.gender);
- });
- });
-
- describe('validate', () => {
- const wrapper = getVisualizeModalWrapper();
- let columnsStub;
- beforeEach(() => {
- columnsStub = sinon.stub(wrapper.instance(), 'mergedColumns');
- });
- afterEach(() => {
- columnsStub.restore();
- });
-
- it('should validate column name', () => {
- columnsStub.returns(mockColumns);
- wrapper.instance().validate();
- expect(wrapper.state().hints).to.have.length(0);
- wrapper.instance().mergedColumns.restore();
- });
- it('should hint invalid column name', () => {
- columnsStub.returns({
- '&': 1,
- });
- wrapper.instance().validate();
- expect(wrapper.state().hints).to.have.length(1);
- wrapper.instance().mergedColumns.restore();
- });
- it('should hint empty chartType', () => {
- columnsStub.returns(mockColumns);
- wrapper.setState({ chartType: null });
- wrapper.instance().validate();
- expect(wrapper.state().hints).to.have.length(1);
- expect(wrapper.state().hints[0])
- .to.have.string(VISUALIZE_VALIDATION_ERRORS.REQUIRE_CHART_TYPE);
- });
- it('should check time series', () => {
- columnsStub.returns(mockColumns);
- wrapper.setState({ chartType: mockChartTypeTB });
- wrapper.instance().validate();
- expect(wrapper.state().hints).to.have.length(0);
-
- // no is_date columns
- columnsStub.returns({
- ds: {
- is_date: false,
- is_dim: false,
- name: 'ds',
- type: 'STRING',
- },
- gender: {
- is_date: false,
- is_dim: true,
- name: 'gender',
- type: 'STRING',
- },
- });
- wrapper.setState({ chartType: mockChartTypeTB });
- wrapper.instance().validate();
- expect(wrapper.state().hints).to.have.length(1);
- expect(wrapper.state().hints[0]).to.have.string(VISUALIZE_VALIDATION_ERRORS.REQUIRE_TIME);
- });
- it('should validate after change checkbox', () => {
- const spy = sinon.spy(wrapper.instance(), 'validate');
- columnsStub.returns(mockColumns);
-
- wrapper.instance().changeCheckbox('is_dim', 'gender', mockEvent);
- expect(spy.callCount).to.equal(1);
- spy.restore();
- });
- it('should validate after change Agg function', () => {
- const spy = sinon.spy(wrapper.instance(), 'validate');
- columnsStub.returns(mockColumns);
-
- wrapper.instance().changeAggFunction('num', { label: 'MIN(x)', value: 'min' });
- expect(spy.callCount).to.equal(1);
- spy.restore();
- });
- });
-
- it('should validate after change chart type', () => {
- const wrapper = getVisualizeModalWrapper();
- wrapper.setState({ chartType: mockChartTypeTB });
- const spy = sinon.spy(wrapper.instance(), 'validate');
-
- wrapper.instance().changeChartType(mockChartTypeBarChart);
- expect(spy.callCount).to.equal(1);
- expect(wrapper.state().chartType).to.equal(mockChartTypeBarChart);
- });
-
- it('should validate after change datasource name', () => {
- const wrapper = getVisualizeModalWrapper();
- const spy = sinon.spy(wrapper.instance(), 'validate');
-
- wrapper.instance().changeDatasourceName(mockEvent);
- expect(spy.callCount).to.equal(1);
- expect(wrapper.state().datasourceName).to.equal(mockEvent.target.value);
- });
-
- it('should build viz options', () => {
- const wrapper = getVisualizeModalWrapper();
- wrapper.setState({ chartType: mockChartTypeTB });
- const spy = sinon.spy(wrapper.instance(), 'buildVizOptions');
- wrapper.instance().buildVizOptions();
- expect(spy.returnValues[0]).to.deep.equal({
- chartType: wrapper.state().chartType.value,
- datasourceName: wrapper.state().datasourceName,
- columns: wrapper.state().columns,
- schema: 'test_schema',
- sql: wrapper.instance().props.query.sql,
- dbId: wrapper.instance().props.query.dbId,
- templateParams: wrapper.instance().props.templateParams,
- });
- });
-
- it('should build visualize advise for long query', () => {
- const longQuery = { ...queries[0], endDttm: 1476910666798 };
- const props = {
- show: true,
- query: longQuery,
- };
- const longQueryWrapper = shallow(<VisualizeModal {...props} />, {
- context: { store },
- }).dive();
- const alertWrapper = shallow(longQueryWrapper.instance().buildVisualizeAdvise());
- expect(alertWrapper.hasClass('alert')).to.equal(true);
- expect(alertWrapper.text()).to.contain(
- 'This query took 101 seconds to run, and the explore view times out at 45 seconds');
- });
-
- it('should not build visualize advise', () => {
- const wrapper = getVisualizeModalWrapper();
- expect(wrapper.instance().buildVisualizeAdvise()).to.be.a('undefined');
- });
-
- describe('visualize', () => {
- const wrapper = getVisualizeModalWrapper();
- const mockOptions = { attr: 'mockOptions' };
- wrapper.setState({
- chartType: mockChartTypeBarChart,
- columns: mockColumns,
- datasourceName: 'mockDatasourceName',
- });
-
- let ajaxSpy;
- let datasourceSpy;
- beforeEach(() => {
- ajaxSpy = sinon.spy($, 'ajax');
- sinon.stub(JSON, 'parse').callsFake(() => ({ table_id: 107 }));
- sinon.stub(exploreUtils, 'getExploreUrlAndPayload').callsFake(() => ({ url: 'mockURL', payload: { datasource: '107__table' } }));
- sinon.spy(exploreUtils, 'exportChart');
- sinon.stub(wrapper.instance(), 'buildVizOptions').callsFake(() => (mockOptions));
- datasourceSpy = sinon.stub(actions, 'createDatasource');
- });
- afterEach(() => {
- ajaxSpy.restore();
- JSON.parse.restore();
- exploreUtils.getExploreUrlAndPayload.restore();
- exploreUtils.exportChart.restore();
- wrapper.instance().buildVizOptions.restore();
- datasourceSpy.restore();
- });
-
- it('should build request', () => {
- wrapper.instance().visualize();
- expect(ajaxSpy.callCount).to.equal(1);
-
- const spyCall = ajaxSpy.getCall(0);
- expect(spyCall.args[0].type).to.equal('POST');
- expect(spyCall.args[0].url).to.equal('/superset/sqllab_viz/');
- expect(spyCall.args[0].data.data).to.equal(JSON.stringify(mockOptions));
- });
- it('should open new window', () => {
- const infoToastSpy = sinon.spy();
-
- datasourceSpy.callsFake(() => {
- const d = $.Deferred();
- d.resolve('done');
- return d.promise();
- });
-
- wrapper.setProps({
- actions: {
- createDatasource: datasourceSpy,
- addInfoToast: infoToastSpy,
- },
- });
-
- wrapper.instance().visualize();
- expect(exploreUtils.exportChart.callCount).to.equal(1);
- expect(exploreUtils.exportChart.getCall(0).args[0].datasource).to.equal('107__table');
- expect(infoToastSpy.callCount).to.equal(1);
- });
- it('should add error toast', () => {
- const dangerToastSpy = sinon.spy();
-
- datasourceSpy.callsFake(() => {
- const d = $.Deferred();
- d.reject('error message');
- return d.promise();
- });
-
-
- wrapper.setProps({
- actions: {
- createDatasource: datasourceSpy,
- addDangerToast: dangerToastSpy,
- },
- });
-
- wrapper.instance().visualize();
- expect(exploreUtils.exportChart.callCount).to.equal(0);
- expect(dangerToastSpy.callCount).to.equal(1);
- });
- });
-});
diff --git a/superset/assets/src/SqlLab/components/ExploreResultsButton.jsx b/superset/assets/src/SqlLab/components/ExploreResultsButton.jsx
new file mode 100644
index 0000000..493b103
--- /dev/null
+++ b/superset/assets/src/SqlLab/components/ExploreResultsButton.jsx
@@ -0,0 +1,167 @@
+/* eslint no-undef: 2 */
+import moment from 'moment';
+import React from 'react';
+import PropTypes from 'prop-types';
+import { bindActionCreators } from 'redux';
+import { connect } from 'react-redux';
+import { Alert } from 'react-bootstrap';
+import Dialog from 'react-bootstrap-dialog';
+
+import shortid from 'shortid';
+import { exportChart } from '../../explore/exploreUtils';
+import * as actions from '../actions';
+import InfoTooltipWithTrigger from '../../components/InfoTooltipWithTrigger';
+import { t } from '../../locales';
+import Button from '../../components/Button';
+
+const propTypes = {
+ actions: PropTypes.object.isRequired,
+ query: PropTypes.object,
+ errorMessage: PropTypes.string,
+ timeout: PropTypes.number,
+ database: PropTypes.object.isRequired,
+};
+const defaultProps = {
+ query: {},
+};
+
+class ExploreResultsButton extends React.PureComponent {
+ constructor(props) {
+ super(props);
+ this.state = {
+ hints: [],
+ };
+ this.visualize = this.visualize.bind(this);
+ this.onClick = this.onClick.bind(this);
+ }
+ onClick() {
+ const timeout = this.props.timeout;
+ if (Math.round(this.getQueryDuration()) > timeout) {
+ this.dialog.show({
+ title: 'Explore',
+ body: this.renderTimeoutWarning(),
+ actions: [
+ Dialog.CancelAction(),
+ Dialog.OKAction(() => {
+ this.visualize();
+ }),
+ ],
+ bsSize: 'large',
+ onHide: (dialog) => {
+ dialog.hide();
+ },
+ });
+ } else {
+ this.visualize();
+ }
+ }
+ getColumns() {
+ const props = this.props;
+ if (props.query && props.query.results && props.query.results.columns) {
+ return props.query.results.columns;
+ }
+ return [];
+ }
+ getQueryDuration() {
+ return moment.duration(this.props.query.endDttm - this.props.query.startDttm).asSeconds();
+ }
+ datasourceName() {
+ const { query } = this.props;
+ const uniqueId = shortid.generate();
+ let datasourceName = uniqueId;
+ if (query) {
+ datasourceName = query.user ? `${query.user}-` : '';
+ datasourceName += `${query.tab}-${uniqueId}`;
+ }
+ return datasourceName;
+ }
+ buildVizOptions() {
+ const { schema, sql, dbId, templateParams } = this.props.query;
+ return {
+ dbId,
+ schema,
+ sql,
+ templateParams,
+ datasourceName: this.datasourceName(),
+ columns: this.getColumns(),
+ };
+ }
+ visualize() {
+ this.props.actions.createDatasource(this.buildVizOptions(), this)
+ .done((resp) => {
+ const columns = this.getColumns();
+ const data = JSON.parse(resp);
+ const mainGroupBy = columns.filter(d => d.is_dim)[0];
+ const formData = {
+ datasource: `${data.table_id}__table`,
+ metrics: [],
+ viz_type: 'table',
+ since: '100 years ago',
+ all_columns: columns.map(c => c.name),
+ row_limit: 1000,
+ };
+ if (mainGroupBy) {
+ formData.groupby = [mainGroupBy.name];
+ }
+ this.props.actions.addInfoToast(t('Creating a data source and creating a new tab'));
+
+ // open new window for data visualization
+ exportChart(formData);
+ })
+ .fail(() => {
+ this.props.actions.addDangerToast(this.props.errorMessage);
+ });
+ }
+ renderTimeoutWarning() {
+ return (
+ <Alert bsStyle="warning">
+ {
+ t('This query took %s seconds to run, ', Math.round(this.getQueryDuration())) +
+ t('and the explore view times out at %s seconds ', this.props.timeout) +
+ t('following this flow will most likely lead to your query timing out. ') +
+ t('We recommend your summarize your data further before following that flow. ') +
+ t('If activated you can use the ')
+ }
+ <strong>CREATE TABLE AS </strong>
+ {t('feature to store a summarized data set that you can then explore.')}
+ </Alert>);
+ }
+ render() {
+ return (
+ <Button
+ bsSize="small"
+ onClick={this.onClick}
+ disabled={!this.props.database.allows_subquery}
+ tooltip={t('Explore the result set in the data exploration view')}
+ >
+ <Dialog
+ ref={(el) => {
+ this.dialog = el;
+ }}
+ />
+ <InfoTooltipWithTrigger
+ icon="line-chart"
+ placement="top"
+ label="explore"
+ /> {t('Explore')}
+ </Button>);
+ }
+}
+ExploreResultsButton.propTypes = propTypes;
+ExploreResultsButton.defaultProps = defaultProps;
+
+function mapStateToProps({ sqlLab }) {
+ return {
+ errorMessage: sqlLab.errorMessage,
+ timeout: sqlLab.common ? sqlLab.common.conf.SUPERSET_WEBSERVER_TIMEOUT : null,
+ };
+}
+
+function mapDispatchToProps(dispatch) {
+ return {
+ actions: bindActionCreators(actions, dispatch),
+ };
+}
+
+export { ExploreResultsButton };
+export default connect(mapStateToProps, mapDispatchToProps)(ExploreResultsButton);
diff --git a/superset/assets/src/SqlLab/components/QueryTable.jsx b/superset/assets/src/SqlLab/components/QueryTable.jsx
index 30d158b..76c3990 100644
--- a/superset/assets/src/SqlLab/components/QueryTable.jsx
+++ b/superset/assets/src/SqlLab/components/QueryTable.jsx
@@ -5,7 +5,6 @@ import moment from 'moment';
import { Table } from 'reactable';
import { Label, ProgressBar, Well } from 'react-bootstrap';
import Link from './Link';
-import VisualizeModal from './VisualizeModal';
import ResultSet from './ResultSet';
import ModalTrigger from '../../components/ModalTrigger';
import HighlightedSql from './HighlightedSql';
@@ -172,11 +171,6 @@ class QueryTable extends React.PureComponent {
q.actions = (
<div style={{ width: '75px' }}>
<Link
- className="fa fa-line-chart m-r-3"
- tooltip={t('Visualize the data out of this query')}
- onClick={this.showVisualizeModal.bind(this, query)}
- />
- <Link
className="fa fa-pencil m-r-3"
onClick={this.restoreSql.bind(this, query)}
tooltip={t('Overwrite text in editor with a query on this table')}
@@ -199,11 +193,6 @@ class QueryTable extends React.PureComponent {
}).reverse();
return (
<div className="QueryTable">
- <VisualizeModal
- show={this.state.showVisualizeModal}
- query={this.state.activeQuery}
- onHide={this.hideVisualizeModal.bind(this)}
- />
<Table
columns={this.props.columns}
className="table table-condensed"
diff --git a/superset/assets/src/SqlLab/components/ResultSet.jsx b/superset/assets/src/SqlLab/components/ResultSet.jsx
index 1acda09..6fa6a27 100644
--- a/superset/assets/src/SqlLab/components/ResultSet.jsx
+++ b/superset/assets/src/SqlLab/components/ResultSet.jsx
@@ -4,7 +4,7 @@ import { Alert, Button, ButtonGroup, ProgressBar } from 'react-bootstrap';
import shortid from 'shortid';
import Loading from '../../components/Loading';
-import VisualizeModal from './VisualizeModal';
+import ExploreResultsButton from './ExploreResultsButton';
import HighlightedSql from './HighlightedSql';
import FilterableTable from '../../components/FilterableTable/FilterableTable';
import QueryStateLabel from './QueryStateLabel';
@@ -19,6 +19,7 @@ const propTypes = {
visualize: PropTypes.bool,
cache: PropTypes.bool,
height: PropTypes.number.isRequired,
+ database: PropTypes.object,
};
const defaultProps = {
search: true,
@@ -38,9 +39,10 @@ export default class ResultSet extends React.PureComponent {
super(props);
this.state = {
searchText: '',
- showModal: false,
+ showExploreResultsButton: false,
data: null,
};
+ this.toggleExploreResultsButton = this.toggleExploreResultsButton.bind(this);
}
componentDidMount() {
// only do this the first time the component is rendered/mounted
@@ -61,56 +63,6 @@ export default class ResultSet extends React.PureComponent {
this.fetchResults(nextProps.query);
}
}
- getControls() {
- if (this.props.search || this.props.visualize || this.props.csv) {
- let csvButton;
- if (this.props.csv) {
- csvButton = (
- <Button bsSize="small" href={'/superset/csv/' + this.props.query.id}>
- <i className="fa fa-file-text-o" /> {t('.CSV')}
- </Button>
- );
- }
- let visualizeButton;
- if (this.props.visualize) {
- visualizeButton = (
- <Button
- bsSize="small"
- onClick={this.showModal.bind(this)}
- >
- <i className="fa fa-line-chart m-l-1" /> {t('Visualize')}
- </Button>
- );
- }
- let searchBox;
- if (this.props.search) {
- searchBox = (
- <input
- type="text"
- onChange={this.changeSearch.bind(this)}
- className="form-control input-sm"
- placeholder={t('Search Results')}
- />
- );
- }
- return (
- <div className="ResultSetControls">
- <div className="clearfix">
- <div className="pull-left">
- <ButtonGroup>
- {visualizeButton}
- {csvButton}
- </ButtonGroup>
- </div>
- <div className="pull-right">
- {searchBox}
- </div>
- </div>
- </div>
- );
- }
- return <div className="noControls" />;
- }
clearQueryResults(query) {
this.props.actions.clearQueryResults(query);
}
@@ -124,11 +76,8 @@ export default class ResultSet extends React.PureComponent {
};
this.props.actions.addQueryEditor(qe);
}
- showModal() {
- this.setState({ showModal: true });
- }
- hideModal() {
- this.setState({ showModal: false });
+ toggleExploreResultsButton() {
+ this.setState({ showExploreResultsButton: !this.state.showExploreResultsButton });
}
changeSearch(event) {
this.setState({ searchText: event.target.value });
@@ -145,6 +94,41 @@ export default class ResultSet extends React.PureComponent {
this.props.actions.runQuery(query, true);
}
}
+ renderControls() {
+ if (this.props.search || this.props.visualize || this.props.csv) {
+ return (
+ <div className="ResultSetControls">
+ <div className="clearfix">
+ <div className="pull-left">
+ <ButtonGroup>
+ {this.props.visualize &&
+ <ExploreResultsButton
+ query={this.props.query}
+ database={this.props.database}
+ actions={this.props.actions}
+ />}
+ {this.props.csv &&
+ <Button bsSize="small" href={'/superset/csv/' + this.props.query.id}>
+ <i className="fa fa-file-text-o" /> {t('.CSV')}
+ </Button>}
+ </ButtonGroup>
+ </div>
+ <div className="pull-right">
+ {this.props.search &&
+ <input
+ type="text"
+ onChange={this.changeSearch.bind(this)}
+ className="form-control input-sm"
+ placeholder={t('Search Results')}
+ />
+ }
+ </div>
+ </div>
+ </div>
+ );
+ }
+ return <div className="noControls" />;
+ }
render() {
const query = this.props.query;
const height = Math.max(0,
@@ -189,12 +173,7 @@ export default class ResultSet extends React.PureComponent {
if (data && data.length > 0) {
return (
<div>
- <VisualizeModal
- show={this.state.showModal}
- query={this.props.query}
- onHide={this.hideModal.bind(this)}
- />
- {this.getControls.bind(this)()}
+ {this.renderControls.bind(this)()}
{sql}
<FilterableTable
data={data}
diff --git a/superset/assets/src/SqlLab/components/SouthPane.jsx b/superset/assets/src/SqlLab/components/SouthPane.jsx
index 73ba069..b55fdda 100644
--- a/superset/assets/src/SqlLab/components/SouthPane.jsx
+++ b/superset/assets/src/SqlLab/components/SouthPane.jsx
@@ -20,6 +20,7 @@ const propTypes = {
actions: PropTypes.object.isRequired,
activeSouthPaneTab: PropTypes.string,
height: PropTypes.number,
+ databases: PropTypes.object.isRequired,
};
const defaultProps = {
@@ -46,6 +47,7 @@ class SouthPane extends React.PureComponent {
query={latestQuery}
actions={props.actions}
height={innerTabHeight}
+ database={this.props.databases[latestQuery.dbId]}
/>
);
} else {
@@ -100,6 +102,7 @@ class SouthPane extends React.PureComponent {
function mapStateToProps({ sqlLab }) {
return {
activeSouthPaneTab: sqlLab.activeSouthPaneTab,
+ databases: sqlLab.databases,
};
}
diff --git a/superset/assets/src/SqlLab/components/TabbedSqlEditors.jsx b/superset/assets/src/SqlLab/components/TabbedSqlEditors.jsx
index 9ec7271..0732a1c 100644
--- a/superset/assets/src/SqlLab/components/TabbedSqlEditors.jsx
+++ b/superset/assets/src/SqlLab/components/TabbedSqlEditors.jsx
@@ -41,6 +41,7 @@ class TabbedSqlEditors extends React.PureComponent {
}
componentDidMount() {
const query = URI(window.location).search(true);
+ // Popping a new tab based on the querystring
if (query.id || query.sql || query.savedQueryId || query.datasourceKey) {
if (query.id) {
this.props.actions.popStoredQuery(query.id);
diff --git a/superset/assets/src/SqlLab/components/VisualizeModal.jsx b/superset/assets/src/SqlLab/components/VisualizeModal.jsx
deleted file mode 100644
index c02656e..0000000
--- a/superset/assets/src/SqlLab/components/VisualizeModal.jsx
+++ /dev/null
@@ -1,313 +0,0 @@
-/* eslint no-undef: 2 */
-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 { exportChart } from '../../explore/exploreUtils';
-import * as actions from '../actions';
-import { VISUALIZE_VALIDATION_ERRORS } from '../constants';
-import visTypes from '../../explore/visTypes';
-import { t } from '../../locales';
-
-const CHART_TYPES = Object.keys(visTypes)
- .filter(typeName => !!visTypes[typeName].showOnExplore)
- .map((typeName) => {
- const vis = visTypes[typeName];
- return {
- value: typeName,
- label: vis.label,
- requiresTime: !!vis.requiresTime,
- };
- });
-
-const propTypes = {
- actions: PropTypes.object.isRequired,
- onHide: PropTypes.func,
- query: PropTypes.object,
- show: PropTypes.bool,
- schema: PropTypes.string,
- datasource: PropTypes.string,
- errorMessage: PropTypes.string,
- timeout: PropTypes.number,
-};
-const defaultProps = {
- show: false,
- query: {},
- onHide: () => {},
-};
-
-class VisualizeModal extends React.PureComponent {
- constructor(props) {
- super(props);
- this.state = {
- chartType: CHART_TYPES[0],
- datasourceName: this.datasourceName(),
- columns: this.getColumnFromProps(),
- schema: props.query ? props.query.schema : null,
- hints: [],
- };
- }
- componentDidMount() {
- this.validate();
- }
- getColumnFromProps() {
- const props = this.props;
- if (!props ||
- !props.query ||
- !props.query.results ||
- !props.query.results.columns) {
- return {};
- }
- const columns = {};
- props.query.results.columns.forEach((col) => {
- columns[col.name] = col;
- });
- return columns;
- }
- datasourceName() {
- const { query } = this.props;
- const uniqueId = shortid.generate();
- let datasourceName = uniqueId;
- if (query) {
- datasourceName = query.user ? `${query.user}-` : '';
- datasourceName += query.db ? `${query.db}-` : '';
- datasourceName += `${query.tab}-${uniqueId}`;
- }
- return datasourceName;
- }
- validate() {
- const hints = [];
- const cols = this.mergedColumns();
- const re = /^\w+$/;
- Object.keys(cols).forEach((colName) => {
- if (!re.test(colName)) {
- hints.push(
- <div>
- {t('%s is not right as a column name, please alias it ' +
- '(as in SELECT count(*) ', colName)} <strong>{t('AS my_alias')}</strong>) {t('using only ' +
- 'alphanumeric characters and underscores')}
- </div>);
- }
- });
- if (this.state.chartType === null) {
- hints.push(VISUALIZE_VALIDATION_ERRORS.REQUIRE_CHART_TYPE);
- } else if (this.state.chartType.requiresTime) {
- let hasTime = false;
- for (const colName in cols) {
- const col = cols[colName];
- if (col.hasOwnProperty('is_date') && col.is_date) {
- hasTime = true;
- }
- }
- if (!hasTime) {
- hints.push(VISUALIZE_VALIDATION_ERRORS.REQUIRE_TIME);
- }
- }
- this.setState({ hints });
- }
- changeChartType(option) {
- this.setState({ chartType: option }, this.validate);
- }
- mergedColumns() {
- const columns = Object.assign({}, this.state.columns);
- if (this.props.query && this.props.query.results.columns) {
- this.props.query.results.columns.forEach((col) => {
- if (columns[col.name] === undefined) {
- columns[col.name] = col;
- }
- });
- }
- return columns;
- }
- buildVizOptions() {
- return {
- chartType: this.state.chartType.value,
- schema: this.state.schema,
- datasourceName: this.state.datasourceName,
- columns: this.state.columns,
- sql: this.props.query.sql,
- dbId: this.props.query.dbId,
- templateParams: this.props.query.templateParams,
- };
- }
- buildVisualizeAdvise() {
- let advise;
- const timeout = this.props.timeout;
- const queryDuration = moment.duration(this.props.query.endDttm - this.props.query.startDttm);
- if (Math.round(queryDuration.asMilliseconds()) > timeout * 1000) {
- advise = (
- <Alert bsStyle="warning">
- This query took {Math.round(queryDuration.asSeconds())} seconds to run,
- and the explore view times out at {timeout} 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((resp) => {
- const columns = Object.keys(this.state.columns).map(k => this.state.columns[k]);
- const data = JSON.parse(resp);
- const mainGroupBy = columns.filter(d => d.is_dim)[0];
- const formData = {
- datasource: `${data.table_id}__table`,
- viz_type: this.state.chartType.value,
- since: '100 years ago',
- limit: '0',
- };
- if (mainGroupBy) {
- formData.groupby = [mainGroupBy.name];
- }
- this.props.actions.addInfoToast(t('Creating a data source and creating a new tab'));
-
- // open new window for data visualization
- exportChart(formData);
- })
- .fail(() => {
- this.props.actions.addDangerToast(this.props.errorMessage);
- });
- }
- changeDatasourceName(event) {
- this.setState({ datasourceName: event.target.value }, this.validate);
- }
- changeCheckbox(attr, columnName, event) {
- let columns = this.mergedColumns();
- const column = Object.assign({}, columns[columnName], { [attr]: event.target.checked });
- columns = Object.assign({}, columns, { [columnName]: column });
- this.setState({ columns }, this.validate);
- }
- changeAggFunction(columnName, option) {
- let columns = this.mergedColumns();
- const val = (option) ? option.value : null;
- const column = Object.assign({}, columns[columnName], { agg: val });
- columns = Object.assign({}, columns, { [columnName]: column });
- this.setState({ columns }, this.validate);
- }
- render() {
- if (!(this.props.query) || !(this.props.query.results) || !(this.props.query.results.columns)) {
- return (
- <div className="VisualizeModal">
- <Modal show={this.props.show} onHide={this.props.onHide}>
- <Modal.Body>
- {t('No results available for this query')}
- </Modal.Body>
- </Modal>
- </div>
- );
- }
- const tableData = this.props.query.results.columns.map(col => ({
- column: col.name,
- is_dimension: (
- <input
- type="checkbox"
- onChange={this.changeCheckbox.bind(this, 'is_dim', col.name)}
- checked={(this.state.columns[col.name]) ? this.state.columns[col.name].is_dim : false}
- className="form-control"
- />
- ),
- is_date: (
- <input
- type="checkbox"
- className="form-control"
- onChange={this.changeCheckbox.bind(this, 'is_date', col.name)}
- checked={(this.state.columns[col.name]) ? this.state.columns[col.name].is_date : false}
- />
- ),
- agg_func: (
- <Select
- options={[
- { value: 'sum', label: 'SUM(x)' },
- { value: 'min', label: 'MIN(x)' },
- { value: 'max', label: 'MAX(x)' },
- { value: 'avg', label: 'AVG(x)' },
- { value: 'count_distinct', label: 'COUNT(DISTINCT x)' },
- ]}
- onChange={this.changeAggFunction.bind(this, col.name)}
- value={(this.state.columns[col.name]) ? this.state.columns[col.name].agg : null}
- />
- ),
- }));
- const alerts = this.state.hints.map((hint, i) => (
- <Alert bsStyle="warning" key={i}>{hint}</Alert>
- ));
- const modal = (
- <div className="VisualizeModal">
- <Modal show={this.props.show} onHide={this.props.onHide}>
- <Modal.Header closeButton>
- <Modal.Title>{t('Visualize')}</Modal.Title>
- </Modal.Header>
- <Modal.Body>
- {alerts}
- {this.buildVisualizeAdvise()}
- <div className="row">
- <Col md={6}>
- {t('Chart Type')}
- <Select
- name="select-chart-type"
- placeholder={t('[Chart Type]')}
- options={CHART_TYPES}
- value={(this.state.chartType) ? this.state.chartType.value : null}
- autosize={false}
- onChange={this.changeChartType.bind(this)}
- />
- </Col>
- <Col md={6}>
- {t('Datasource Name')}
- <input
- type="text"
- className="form-control input-sm"
- placeholder={t('datasource name')}
- onChange={this.changeDatasourceName.bind(this)}
- value={this.state.datasourceName}
- />
- </Col>
- </div>
- <hr />
- <Table
- className="table table-condensed"
- columns={['column', 'is_dimension', 'is_date', 'agg_func']}
- data={tableData}
- />
- <Button
- onClick={this.visualize.bind(this)}
- bsStyle="primary"
- disabled={(this.state.hints.length > 0)}
- >
- {t('Visualize')}
- </Button>
- </Modal.Body>
- </Modal>
- </div>
- );
- return modal;
- }
-}
-VisualizeModal.propTypes = propTypes;
-VisualizeModal.defaultProps = defaultProps;
-
-function mapStateToProps({ sqlLab }) {
- return {
- datasource: sqlLab.datasource,
- errorMessage: sqlLab.errorMessage,
- timeout: sqlLab.common ? sqlLab.common.conf.SUPERSET_WEBSERVER_TIMEOUT : null,
- };
-}
-
-function mapDispatchToProps(dispatch) {
- return {
- actions: bindActionCreators(actions, dispatch),
- };
-}
-
-export { VisualizeModal };
-export default connect(mapStateToProps, mapDispatchToProps)(VisualizeModal);
diff --git a/superset/assets/src/SqlLab/constants.js b/superset/assets/src/SqlLab/constants.js
index 6af44e4..ce69704 100644
--- a/superset/assets/src/SqlLab/constants.js
+++ b/superset/assets/src/SqlLab/constants.js
@@ -1,5 +1,3 @@
-import { t } from '../locales';
-
export const STATE_BSSTYLE_MAP = {
failed: 'danger',
pending: 'info',
@@ -25,10 +23,3 @@ export const TIME_OPTIONS = [
'90 days ago',
'1 year ago',
];
-
-export const VISUALIZE_VALIDATION_ERRORS = {
- REQUIRE_CHART_TYPE: t('Pick a chart type!'),
- REQUIRE_TIME: t('To use this chart type you need at least one column flagged as a date'),
- REQUIRE_DIMENSION: t('To use this chart type you need at least one dimension'),
- REQUIRE_AGGREGATION_FUNCTION: t('To use this chart type you need at least one aggregation function'),
-};
diff --git a/superset/assets/src/explore/visTypes.jsx b/superset/assets/src/explore/visTypes.jsx
index 6dd307e..df8dfbb 100644
--- a/superset/assets/src/explore/visTypes.jsx
+++ b/superset/assets/src/explore/visTypes.jsx
@@ -913,6 +913,7 @@ export const visTypes = {
{
label: t('NOT GROUPED BY'),
description: t('Use this section if you want to query atomic rows'),
+ expanded: true,
controlSetRows: [
['all_columns'],
['order_by_cols'],
diff --git a/superset/assets/yarn.lock b/superset/assets/yarn.lock
index e27a87d..669ee0a 100644
--- a/superset/assets/yarn.lock
+++ b/superset/assets/yarn.lock
@@ -8993,6 +8993,10 @@ react-bootstrap-datetimepicker@0.0.22:
classnames "^2.1.2"
moment "^2.8.2"
+react-bootstrap-dialog@^0.10.0:
+ version "0.10.0"
+ resolved "https://registry.yarnpkg.com/react-bootstrap-dialog/-/react-bootstrap-dialog-0.10.0.tgz#fca5c84804ea2b6debe3833c6d4b7480bcff0175"
+
react-bootstrap-slider@2.1.5:
version "2.1.5"
resolved "https://registry.yarnpkg.com/react-bootstrap-slider/-/react-bootstrap-slider-2.1.5.tgz#2f79e57b69ddf2b5bd23310bddbd2de0c6bdfef3"
diff --git a/superset/db_engine_specs.py b/superset/db_engine_specs.py
index ee14017..4967d30 100644
--- a/superset/db_engine_specs.py
+++ b/superset/db_engine_specs.py
@@ -100,6 +100,7 @@ class BaseEngineSpec(object):
limit_method = LimitMethod.FORCE_LIMIT
time_secondary_columns = False
inner_joins = True
+ allows_subquery = True
@classmethod
def get_time_grains(cls):
@@ -1368,6 +1369,7 @@ class DruidEngineSpec(BaseEngineSpec):
"""Engine spec for Druid.io"""
engine = 'druid'
inner_joins = False
+ allows_subquery = False
time_grain_functions = {
None: '{col}',
diff --git a/superset/models/core.py b/superset/models/core.py
index 9264fcf..3aa7038 100644
--- a/superset/models/core.py
+++ b/superset/models/core.py
@@ -624,6 +624,10 @@ class Database(Model, AuditMixinNullable, ImportMixin):
return self.verbose_name if self.verbose_name else self.database_name
@property
+ def allows_subquery(self):
+ return self.db_engine_spec.allows_subquery
+
+ @property
def data(self):
return {
'id': self.id,
@@ -631,6 +635,7 @@ class Database(Model, AuditMixinNullable, ImportMixin):
'backend': self.backend,
'allow_multi_schema_metadata_fetch':
self.allow_multi_schema_metadata_fetch,
+ 'allows_subquery': self.allows_subquery,
}
@property
diff --git a/superset/views/core.py b/superset/views/core.py
index 17eb34b..93e79f3 100755
--- a/superset/views/core.py
+++ b/superset/views/core.py
@@ -321,6 +321,7 @@ class DatabaseAsync(DatabaseView):
'expose_in_sqllab', 'allow_ctas', 'force_ctas_schema',
'allow_run_async', 'allow_run_sync', 'allow_dml',
'allow_multi_schema_metadata_fetch', 'allow_csv_upload',
+ 'allows_subquery',
]
@@ -2203,7 +2204,6 @@ class Superset(BaseSupersetView):
SqlaTable = ConnectorRegistry.sources['table']
data = json.loads(request.form.get('data'))
table_name = data.get('datasourceName')
- template_params = data.get('templateParams')
table = (
db.session.query(SqlaTable)
.filter_by(table_name=table_name)
@@ -2219,43 +2219,24 @@ class Superset(BaseSupersetView):
table.sql = q.stripped()
db.session.add(table)
cols = []
- dims = []
- metrics = []
- for column_name, config in data.get('columns').items():
- is_dim = config.get('is_dim', False)
+ for config in data.get('columns'):
+ column_name = config.get('name')
SqlaTable = ConnectorRegistry.sources['table']
TableColumn = SqlaTable.column_class
SqlMetric = SqlaTable.metric_class
col = TableColumn(
column_name=column_name,
- filterable=is_dim,
- groupby=is_dim,
+ filterable=True,
+ groupby=True,
is_dttm=config.get('is_date', False),
type=config.get('type', False),
)
cols.append(col)
- if is_dim:
- dims.append(col)
- agg = config.get('agg')
- if agg:
- if agg == 'count_distinct':
- metrics.append(SqlMetric(
- metric_name='{agg}__{column_name}'.format(**locals()),
- expression='COUNT(DISTINCT {column_name})'
- .format(**locals()),
- ))
- else:
- metrics.append(SqlMetric(
- metric_name='{agg}__{column_name}'.format(**locals()),
- expression='{agg}({column_name})'.format(**locals()),
- ))
- if not metrics:
- metrics.append(SqlMetric(
- metric_name='count'.format(**locals()),
- expression='count(*)'.format(**locals()),
- ))
+
table.columns = cols
- table.metrics = metrics
+ table.metrics = [
+ SqlMetric(metric_name='count', expression='count(*)'),
+ ]
db.session.commit()
return self.json_response(json.dumps({
'table_id': table.id,
diff --git a/tests/sqllab_tests.py b/tests/sqllab_tests.py
index 51c336b..3d0daed 100644
--- a/tests/sqllab_tests.py
+++ b/tests/sqllab_tests.py
@@ -236,21 +236,18 @@ class SqlLabTests(SupersetTestCase):
'chartType': 'dist_bar',
'datasourceName': 'test_viz_flow_table',
'schema': 'superset',
- 'columns': {
- 'viz_type': {
- 'is_date': False,
- 'type': 'STRING',
- 'nam:qe': 'viz_type',
- 'is_dim': True,
- },
- 'ccount': {
- 'is_date': False,
- 'type': 'OBJECT',
- 'name': 'ccount',
- 'is_dim': True,
- 'agg': 'sum',
- },
- },
+ 'columns': [{
+ 'is_date': False,
+ 'type': 'STRING',
+ 'nam:qe': 'viz_type',
+ 'is_dim': True,
+ }, {
+ 'is_date': False,
+ 'type': 'OBJECT',
+ 'name': 'ccount',
+ 'is_dim': True,
+ 'agg': 'sum',
+ }],
'sql': """\
SELECT viz_type, count(1) as ccount
FROM slices