[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
This commit is contained in:
parent
1b9e5d4174
commit
fe6846b8db
|
|
@ -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="
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -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} />);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -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);
|
||||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
@ -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'),
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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'],
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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}',
|
||||
|
|
|
|||
|
|
@ -623,6 +623,10 @@ class Database(Model, AuditMixinNullable, ImportMixin):
|
|||
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 @@ 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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in New Issue