mistercrunch closed pull request #5523: [sql lab] simplify the visualize flow
URL: https://github.com/apache/incubator-superset/pull/5523
This is a PR merged from a forked repository.
As GitHub hides the original diff on merge, it is displayed below for
the sake of provenance:
As this is a foreign pull request (from a fork), the diff is supplied
below (as it won't show otherwise due to GitHub magic):
diff --git a/package-lock.json b/package-lock.json
deleted file mode 100644
index dea4bad056..0000000000
--- 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 c427a6dfc5..5a50fdccd5 100644
--- a/superset/assets/package.json
+++ b/superset/assets/package.json
@@ -93,6 +93,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",
@@ -166,7 +167,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 0000000000..b57ddd4950
--- /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 b1f2708a5b..8ca9acdf58 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 5eb4802a7d..0000000000
--- 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 0000000000..493b103c24
--- /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 30d158b718..76c3990919 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';
@@ -171,11 +170,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)}
@@ -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 6f502ea14e..1b7ac9402b 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 73ba069761..b55fdda422 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 9ec7271e60..0732a1cc49 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 c02656e088..0000000000
--- 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 6af44e4651..ce697046a9 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 6dd307e754..df8dfbb654 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 11e645613f..b72ebfd344 100644
--- a/superset/assets/yarn.lock
+++ b/superset/assets/yarn.lock
@@ -2435,7 +2435,7 @@ class-utils@^0.3.5:
isobject "^3.0.0"
static-extend "^0.1.1"
[email protected], classnames@^2.1.2, classnames@^2.2.3, classnames@^2.2.4,
classnames@^2.2.5:
+classnames@^2.1.2, classnames@^2.2.3, classnames@^2.2.4, classnames@^2.2.5:
version "2.2.6"
resolved
"https://registry.yarnpkg.com/classnames/-/classnames-2.2.6.tgz#43935bffdd291f326dad0a205309b38d00f650ce"
@@ -6400,7 +6400,7 @@ lodash.isarray@^3.0.0:
version "3.0.4"
resolved
"https://registry.yarnpkg.com/lodash.isarray/-/lodash.isarray-3.0.4.tgz#79e4eb88c36a8122af86f844aa9bcd851b5fbb55"
-lodash.isequal@^4.0.0, lodash.isequal@^4.1.1:
+lodash.isequal@^4.1.1:
version "4.5.0"
resolved
"https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0"
@@ -8766,6 +8766,10 @@ [email protected]:
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"
+
[email protected]:
version "2.1.5"
resolved
"https://registry.yarnpkg.com/react-bootstrap-slider/-/react-bootstrap-slider-2.1.5.tgz#2f79e57b69ddf2b5bd23310bddbd2de0c6bdfef3"
@@ -8842,7 +8846,7 @@ react-dom@^15.6.2:
object-assign "^4.1.0"
prop-types "^15.5.10"
[email protected], "react-draggable@^2.2.6 || ^3.0.3":
+"react-draggable@^2.2.6 || ^3.0.3":
version "3.0.5"
resolved
"https://registry.yarnpkg.com/react-draggable/-/react-draggable-3.0.5.tgz#c031e0ed4313531f9409d6cd84c8ebcec0ddfe2d"
dependencies:
@@ -8857,16 +8861,6 @@ react-gravatar@^2.6.1:
md5 "^2.1.0"
query-string "^4.2.2"
[email protected]:
- version "0.16.6"
- resolved
"https://registry.yarnpkg.com/react-grid-layout/-/react-grid-layout-0.16.6.tgz#9b2407a2b946c2260ebaf66f13b556e1da4efeb2"
- dependencies:
- classnames "2.x"
- lodash.isequal "^4.0.0"
- prop-types "15.x"
- react-draggable "3.x"
- react-resizable "1.x"
-
react-input-autosize@^2.1.2:
version "2.2.1"
resolved
"https://registry.yarnpkg.com/react-input-autosize/-/react-input-autosize-2.2.1.tgz#ec428fa15b1592994fb5f9aa15bb1eb6baf420f8"
@@ -8938,7 +8932,7 @@ react-redux@^5.0.2:
loose-envify "^1.1.0"
prop-types "^15.6.0"
[email protected], react-resizable@^1.3.3:
+react-resizable@^1.3.3:
version "1.7.5"
resolved
"https://registry.yarnpkg.com/react-resizable/-/react-resizable-1.7.5.tgz#83eb75bb3684da6989bbbf4f826e1470f0af902e"
dependencies:
diff --git a/superset/db_engine_specs.py b/superset/db_engine_specs.py
index ebc8802668..78e6671a09 100644
--- a/superset/db_engine_specs.py
+++ b/superset/db_engine_specs.py
@@ -70,6 +70,7 @@ class BaseEngineSpec(object):
limit_method = LimitMethod.FORCE_LIMIT
time_secondary_columns = False
inner_joins = True
+ allows_subquery = True
@classmethod
def fetch_data(cls, cursor, limit):
@@ -1430,6 +1431,7 @@ class DruidEngineSpec(BaseEngineSpec):
"""Engine spec for Druid.io"""
engine = 'druid'
inner_joins = False
+ allows_subquery = False
time_grains = (
Grain('Time Column', _('Time Column'), '{col}', None),
diff --git a/superset/models/core.py b/superset/models/core.py
index 60af53d0b2..5a9e492a17 100644
--- a/superset/models/core.py
+++ b/superset/models/core.py
@@ -623,6 +623,10 @@ def __repr__(self):
def name(self):
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 {
@@ -631,6 +635,7 @@ def data(self):
'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 3dfccbf273..4d62fb810b 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',
]
@@ -2202,7 +2203,6 @@ def sqllab_viz(self):
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)
@@ -2218,43 +2218,24 @@ def sqllab_viz(self):
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 51c336b21d..3d0daed213 100644
--- a/tests/sqllab_tests.py
+++ b/tests/sqllab_tests.py
@@ -236,21 +236,18 @@ def test_sqllab_viz(self):
'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
----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on GitHub and use the
URL above to go to the specific comment.
For queries about this service, please contact Infrastructure at:
[email protected]
With regards,
Apache Git Services
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]