You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@superset.apache.org by ma...@apache.org on 2018/08/02 17:52:41 UTC

[incubator-superset] branch master updated: [sql lab] simplify the visualize flow (#5523)

This is an automated email from the ASF dual-hosted git repository.

maximebeauchemin pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-superset.git


The following commit(s) were added to refs/heads/master by this push:
     new fe6846b  [sql lab] simplify the visualize flow (#5523)
fe6846b is described below

commit fe6846b8db1cd7e7f8db5b26ffcbf5e662ab14e3
Author: Maxime Beauchemin <ma...@gmail.com>
AuthorDate: Thu Aug 2 10:52:38 2018 -0700

    [sql lab] simplify the visualize flow (#5523)
    
    * [sql lab] simplify the visualize flow
    
    The "visualize flow" linking SQL Lab to the "explore view" has never
    worked so great for people, here's a list of issues:
    
    * it's not really clear to users that their query is wrapped as a
    subquery, and the explore view runs queries on top of it
    
    * lint + fix tests
    
    * Addressing comments
---
 package-lock.json                                  | 743 ---------------------
 superset/assets/package.json                       |   2 +-
 .../sqllab/ExploreResultsButton_spec.jsx           | 225 +++++++
 .../spec/javascripts/sqllab/ResultSet_spec.jsx     |  20 +-
 .../javascripts/sqllab/VisualizeModal_spec.jsx     | 378 -----------
 .../src/SqlLab/components/ExploreResultsButton.jsx | 167 +++++
 .../assets/src/SqlLab/components/QueryTable.jsx    |  11 -
 .../assets/src/SqlLab/components/ResultSet.jsx     | 105 ++-
 .../assets/src/SqlLab/components/SouthPane.jsx     |   3 +
 .../src/SqlLab/components/TabbedSqlEditors.jsx     |   1 +
 .../src/SqlLab/components/VisualizeModal.jsx       | 313 ---------
 superset/assets/src/SqlLab/constants.js            |   9 -
 superset/assets/src/explore/visTypes.jsx           |   1 +
 superset/assets/yarn.lock                          |   4 +
 superset/db_engine_specs.py                        |   2 +
 superset/models/core.py                            |   5 +
 superset/views/core.py                             |  37 +-
 tests/sqllab_tests.py                              |  27 +-
 18 files changed, 475 insertions(+), 1578 deletions(-)

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