diff --git a/package-lock.json b/package-lock.json deleted file mode 100644 index dea4bad056ffd..0000000000000 --- 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 7eaa845e4bf90..510ba8e865f73 100644 --- a/superset/assets/package.json +++ b/superset/assets/package.json @@ -94,6 +94,7 @@ "react-addons-shallow-compare": "^15.4.2", "react-bootstrap": "^0.31.5", "react-bootstrap-datetimepicker": "0.0.22", + "react-bootstrap-dialog": "^0.10.0", "react-bootstrap-slider": "2.1.5", "react-bootstrap-table": "^4.3.1", "react-color": "^2.13.8", @@ -167,7 +168,6 @@ "react-addons-test-utils": "^15.6.2", "react-test-renderer": "^15.6.2", "redux-mock-store": "^1.2.3", - "//": "known minor issues in >5.0", "sinon": "^4.5.0", "style-loader": "^0.21.0", "transform-loader": "^0.2.3", diff --git a/superset/assets/spec/javascripts/sqllab/ExploreResultsButton_spec.jsx b/superset/assets/spec/javascripts/sqllab/ExploreResultsButton_spec.jsx new file mode 100644 index 0000000000000..b57ddd49507bf --- /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(, { + context: { store }, + }).dive()); + + it('renders', () => { + expect(React.isValidElement()).to.equal(true); + }); + it('renders with props', () => { + expect( + React.isValidElement(), + ).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(, { + 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(, { + 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 b1f2708a5b6d3..8ca9acdf58d93 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(); expect(wrapper.find(FilterableTable)).to.have.length(1); }); - describe('getControls', () => { - it('should render controls', () => { - const wrapper = shallow(); - 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(); - 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(); let spy; @@ -88,7 +74,7 @@ describe('ResultSet', () => { const wrapper = shallow(); 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(); 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 5eb4802a7de2f..0000000000000 --- 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(, { - context: { store }, - }).dive()); - - it('renders', () => { - expect(React.isValidElement()).to.equal(true); - }); - it('renders with props', () => { - expect( - React.isValidElement(), - ).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(, { - 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(, { - 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 0000000000000..493b103c24066 --- /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 ( + + { + 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 ') + } + CREATE TABLE AS + {t('feature to store a summarized data set that you can then explore.')} + ); + } + render() { + return ( + ); + } +} +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 30d158b71896e..76c39909199f3 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 = (
- - - {t('.CSV')} - - ); - } - let visualizeButton; - if (this.props.visualize) { - visualizeButton = ( - - ); - } - let searchBox; - if (this.props.search) { - searchBox = ( - - ); - } - return ( -
-
-
- - {visualizeButton} - {csvButton} - -
-
- {searchBox} -
-
-
- ); - } - return
; - } 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 ( +
+
+
+ + {this.props.visualize && + } + {this.props.csv && + } + +
+
+ {this.props.search && + + } +
+
+
+ ); + } + return
; + } 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 (
- - {this.getControls.bind(this)()} + {this.renderControls.bind(this)()} {sql} ); } 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 9ec7271e60df3..0732a1cc49be5 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 c02656e08888c..0000000000000 --- 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( -
- {t('%s is not right as a column name, please alias it ' + - '(as in SELECT count(*) ', colName)} {t('AS my_alias')}) {t('using only ' + - 'alphanumeric characters and underscores')} -
); - } - }); - 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 = ( - - 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 CREATE TABLE AS feature - to store a summarized data set that you can then explore. - ); - } - 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 ( -
- - - {t('No results available for this query')} - - -
- ); - } - const tableData = this.props.query.results.columns.map(col => ({ - column: col.name, - is_dimension: ( - - ), - is_date: ( - - ), - agg_func: ( - - -
- {t('Datasource Name')} - - - -
-
- - - - - ); - 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 6af44e4651b2e..ce697046a9680 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 6dd307e754717..df8dfbb654806 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 e27a87dd319da..669ee0a963161 100644 --- a/superset/assets/yarn.lock +++ b/superset/assets/yarn.lock @@ -8993,6 +8993,10 @@ react-bootstrap-datetimepicker@0.0.22: classnames "^2.1.2" moment "^2.8.2" +react-bootstrap-dialog@^0.10.0: + version "0.10.0" + resolved "https://registry.yarnpkg.com/react-bootstrap-dialog/-/react-bootstrap-dialog-0.10.0.tgz#fca5c84804ea2b6debe3833c6d4b7480bcff0175" + react-bootstrap-slider@2.1.5: version "2.1.5" resolved "https://registry.yarnpkg.com/react-bootstrap-slider/-/react-bootstrap-slider-2.1.5.tgz#2f79e57b69ddf2b5bd23310bddbd2de0c6bdfef3" diff --git a/superset/db_engine_specs.py b/superset/db_engine_specs.py index ee140177366e0..4967d30647cad 100644 --- a/superset/db_engine_specs.py +++ b/superset/db_engine_specs.py @@ -100,6 +100,7 @@ class BaseEngineSpec(object): limit_method = LimitMethod.FORCE_LIMIT time_secondary_columns = False inner_joins = True + allows_subquery = True @classmethod def get_time_grains(cls): @@ -1368,6 +1369,7 @@ class DruidEngineSpec(BaseEngineSpec): """Engine spec for Druid.io""" engine = 'druid' inner_joins = False + allows_subquery = False time_grain_functions = { None: '{col}', diff --git a/superset/models/core.py b/superset/models/core.py index 9264fcfe36c2a..3aa70381e3170 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 17eb34ba87f3b..93e79f3e399b8 100755 --- a/superset/views/core.py +++ b/superset/views/core.py @@ -321,6 +321,7 @@ class DatabaseAsync(DatabaseView): 'expose_in_sqllab', 'allow_ctas', 'force_ctas_schema', 'allow_run_async', 'allow_run_sync', 'allow_dml', 'allow_multi_schema_metadata_fetch', 'allow_csv_upload', + 'allows_subquery', ] @@ -2203,7 +2204,6 @@ 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) @@ -2219,43 +2219,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 51c336b21de8c..3d0daed213beb 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