diff --git a/superset/assets/javascripts/explorev2/components/ChartContainer.jsx b/superset/assets/javascripts/explorev2/components/ChartContainer.jsx
index 6599ff5d278f5..c00b82217239f 100644
--- a/superset/assets/javascripts/explorev2/components/ChartContainer.jsx
+++ b/superset/assets/javascripts/explorev2/components/ChartContainer.jsx
@@ -31,6 +31,8 @@ const propTypes = {
slice: PropTypes.object.isRequired,
table_name: PropTypes.string,
viz_type: PropTypes.string.isRequired,
+ formData: PropTypes.object,
+ latestQueryFormData: PropTypes.object,
};
class ChartContainer extends React.PureComponent {
@@ -226,13 +228,12 @@ class ChartContainer extends React.PureComponent {
status={CHART_STATUS_MAP[this.props.chartStatus]}
style={{ fontSize: '10px', marginRight: '5px' }}
/>
- {this.state.mockSlice &&
-
- }
+
}
@@ -256,8 +257,8 @@ function mapStateToProps(state) {
chartUpdateStartTime: state.chartUpdateStartTime,
column_formats: state.datasource ? state.datasource.column_formats : null,
containerId: state.slice ? `slice-container-${state.slice.slice_id}` : 'slice-container',
- datasource_type: state.datasource_type,
formData,
+ latestQueryFormData: state.latestQueryFormData,
isStarred: state.isStarred,
queryResponse: state.queryResponse,
slice: state.slice,
diff --git a/superset/assets/javascripts/explorev2/components/DisplayQueryButton.jsx b/superset/assets/javascripts/explorev2/components/DisplayQueryButton.jsx
index 990c0930cd463..05fe4946f32e0 100644
--- a/superset/assets/javascripts/explorev2/components/DisplayQueryButton.jsx
+++ b/superset/assets/javascripts/explorev2/components/DisplayQueryButton.jsx
@@ -1,25 +1,49 @@
import React, { PropTypes } from 'react';
import ModalTrigger from './../../components/ModalTrigger';
+const $ = window.$ = require('jquery');
const propTypes = {
- query: PropTypes.string,
+ queryEndpoint: PropTypes.string.isRequired,
};
-const defaultProps = {
- query: '',
-};
-
-export default function DisplayQueryButton({ query }) {
- const modalBody = (
{query}
);
- return (
- Query}
- modalTitle="Query"
- modalBody={modalBody}
- />
- );
+export default class DisplayQueryButton extends React.PureComponent {
+ constructor(props) {
+ super(props);
+ this.state = {
+ modalBody: ,
+ };
+ }
+ beforeOpen() {
+ this.setState({
+ modalBody:
+ (),
+ });
+ $.ajax({
+ type: 'GET',
+ url: this.props.queryEndpoint,
+ success: (data) => {
+ this.setState({ modalBody: ({data.query}
) });
+ },
+ error(data) {
+ this.setState({ modalBody: ({data.error}
) });
+ },
+ });
+ }
+ render() {
+ return (
+ Query}
+ modalTitle="Query"
+ beforeOpen={this.beforeOpen.bind(this)}
+ modalBody={this.state.modalBody}
+ />
+ );
+ }
}
DisplayQueryButton.propTypes = propTypes;
-DisplayQueryButton.defaultProps = defaultProps;
diff --git a/superset/assets/javascripts/explorev2/components/ExploreActionButtons.jsx b/superset/assets/javascripts/explorev2/components/ExploreActionButtons.jsx
index b88410006106b..5823c93bf6f5a 100644
--- a/superset/assets/javascripts/explorev2/components/ExploreActionButtons.jsx
+++ b/superset/assets/javascripts/explorev2/components/ExploreActionButtons.jsx
@@ -6,40 +6,47 @@ import DisplayQueryButton from './DisplayQueryButton';
const propTypes = {
canDownload: PropTypes.oneOfType([PropTypes.string, PropTypes.bool]).isRequired,
- slice: PropTypes.object.isRequired,
- query: PropTypes.string,
+ slice: PropTypes.object,
+ queryEndpoint: PropTypes.string,
};
-export default function ExploreActionButtons({ canDownload, slice, query }) {
+export default function ExploreActionButtons({ canDownload, slice, queryEndpoint }) {
const exportToCSVClasses = cx('btn btn-default btn-sm', {
'disabled disabledButton': !canDownload,
});
- return (
-
-
+ if (slice) {
+ return (
+
+
+
+ );
+ }
+ return (
+
);
}
diff --git a/superset/assets/javascripts/explorev2/exploreUtils.js b/superset/assets/javascripts/explorev2/exploreUtils.js
index 71b97260ba373..24b7b60eae466 100644
--- a/superset/assets/javascripts/explorev2/exploreUtils.js
+++ b/superset/assets/javascripts/explorev2/exploreUtils.js
@@ -12,6 +12,8 @@ export function getExploreUrl(form_data, dummy, endpoint = 'base') {
return `/superset/explore_json/${params}&csv=true`;
case 'standalone':
return `/superset/explore/${params}&standalone=true`;
+ case 'query':
+ return `/superset/explore_json/${params}&query=true`;
default:
return `/superset/explore/${params}`;
}
diff --git a/superset/assets/javascripts/explorev2/index.jsx b/superset/assets/javascripts/explorev2/index.jsx
index db4c8248577de..6ad9e0a09043e 100644
--- a/superset/assets/javascripts/explorev2/index.jsx
+++ b/superset/assets/javascripts/explorev2/index.jsx
@@ -7,7 +7,7 @@ import { Provider } from 'react-redux';
import thunk from 'redux-thunk';
import { now } from '../modules/dates';
import { initEnhancer } from '../reduxUtils';
-import { getFieldsState } from './stores/store';
+import { getFieldsState, getFormDataFromFields } from './stores/store';
// jquery and bootstrap required to make bootstrap dropdown menu's work
@@ -32,6 +32,7 @@ const bootstrappedState = Object.assign(
chartUpdateStartTime: now(),
dashboards: [],
fields,
+ latestQueryFormData: getFormDataFromFields(fields),
filterColumnOpts: [],
isDatasourceMetaLoading: false,
isStarred: false,
diff --git a/superset/assets/javascripts/explorev2/reducers/exploreReducer.js b/superset/assets/javascripts/explorev2/reducers/exploreReducer.js
index 0684d6d878b0e..bcb57411bd095 100644
--- a/superset/assets/javascripts/explorev2/reducers/exploreReducer.js
+++ b/superset/assets/javascripts/explorev2/reducers/exploreReducer.js
@@ -88,6 +88,7 @@ export const exploreReducer = function (state, action) {
chartUpdateStartTime: now(),
triggerQuery: false,
queryRequest: action.queryRequest,
+ latestQueryFormData: getFormDataFromFields(state.fields),
});
},
[actions.CHART_UPDATE_STOPPED]() {
diff --git a/superset/models.py b/superset/models.py
index b7f190fc9e511..07d8eaacd4e89 100644
--- a/superset/models.py
+++ b/superset/models.py
@@ -1237,8 +1237,9 @@ def values_for_column(self,
con=engine
)
- def query( # sqla
- self, groupby, metrics,
+ def get_query_str( # sqla
+ self, engine, qry_start_dttm,
+ groupby, metrics,
granularity,
from_dttm, to_dttm,
filter=None, # noqa
@@ -1261,7 +1262,6 @@ def query( # sqla
cols = {col.column_name: col for col in self.columns}
metrics_dict = {m.metric_name: m for m in self.metrics}
- qry_start_dttm = datetime.now()
if not granularity and is_timeseries:
raise Exception(_(
@@ -1421,13 +1421,18 @@ def visit_column(element, compiler, **kw):
qry = qry.select_from(tbl)
- engine = self.database.get_sqla_engine()
sql = "{}".format(
qry.compile(
engine, compile_kwargs={"literal_binds": True},),
)
logging.info(sql)
sql = sqlparse.format(sql, reindent=True)
+ return sql
+
+ def query(self, query_obj):
+ qry_start_dttm = datetime.now()
+ engine = self.database.get_sqla_engine()
+ sql = self.get_query_str(engine, qry_start_dttm, **query_obj)
status = QueryStatus.SUCCESS
error_message = None
df = None
@@ -2266,8 +2271,9 @@ def values_for_column(self,
return df
- def query( # druid
- self, groupby, metrics,
+ def get_query_str( # druid
+ self, client, qry_start_dttm,
+ groupby, metrics,
granularity,
from_dttm, to_dttm,
filter=None, # noqa
@@ -2279,13 +2285,12 @@ def query( # druid
orderby=None,
extras=None, # noqa
select=None, # noqa
- columns=None, ):
+ columns=None, phase=2):
"""Runs a query against Druid and returns a dataframe.
This query interface is common to SqlAlchemy and Druid
"""
# TODO refactor into using a TBD Query object
- qry_start_dttm = datetime.now()
if not is_timeseries:
granularity = 'all'
inner_from_dttm = inner_from_dttm or from_dttm
@@ -2381,7 +2386,6 @@ def recursive_get_fields(_conf):
if having_filters:
qry['having'] = having_filters
- client = self.cluster.get_pydruid_client()
orig_filters = filters
if len(groupby) == 0:
del qry['dimensions']
@@ -2420,6 +2424,8 @@ def recursive_get_fields(_conf):
query_str += json.dumps(
client.query_builder.last_query.query_dict, indent=2)
query_str += "\n"
+ if phase == 1:
+ return query_str
query_str += (
"//\nPhase 2 (built based on phase one's results)\n")
df = client.export_pandas()
@@ -2459,15 +2465,23 @@ def recursive_get_fields(_conf):
client.groupby(**qry)
query_str += json.dumps(
client.query_builder.last_query.query_dict, indent=2)
+ return query_str
+
+ def query(self, query_obj):
+ qry_start_dttm = datetime.now()
+ client = self.cluster.get_pydruid_client()
+ query_str = self.get_query_str(client, qry_start_dttm, **query_obj)
df = client.export_pandas()
if df is None or df.size == 0:
raise Exception(_("No data was returned."))
df.columns = [
DTTM_ALIAS if c == 'timestamp' else c for c in df.columns]
+ is_timeseries = query_obj['is_timeseries'] \
+ if 'is_timeseries' in query_obj else True
if (
not is_timeseries and
- granularity == "all" and
+ query_obj['granularity'] == "all" and
DTTM_ALIAS in df.columns):
del df[DTTM_ALIAS]
@@ -2475,11 +2489,11 @@ def recursive_get_fields(_conf):
cols = []
if DTTM_ALIAS in df.columns:
cols += [DTTM_ALIAS]
- cols += [col for col in groupby if col in df.columns]
- cols += [col for col in metrics if col in df.columns]
+ cols += [col for col in query_obj['groupby'] if col in df.columns]
+ cols += [col for col in query_obj['metrics'] if col in df.columns]
df = df[cols]
- time_offset = DruidDatasource.time_offset(granularity)
+ time_offset = DruidDatasource.time_offset(query_obj['granularity'])
def increment_timestamp(ts):
dt = utils.parse_human_datetime(ts).replace(
diff --git a/superset/views.py b/superset/views.py
index 48724fd1d67ec..9abac1405fcc3 100755
--- a/superset/views.py
+++ b/superset/views.py
@@ -1486,6 +1486,24 @@ def explore_json(self, datasource_type, datasource_id):
headers=generate_download_headers("csv"),
mimetype="application/csv")
+ if request.args.get("query") == "true":
+ try:
+ query_obj = viz_obj.query_obj()
+ engine = viz_obj.datasource.database.get_sqla_engine() \
+ if datasource_type == 'table' \
+ else viz_obj.datasource.cluster.get_pydruid_client()
+ if datasource_type == 'druid':
+ # only retrive first phase query for druid
+ query_obj['phase'] = 1
+ query = viz_obj.datasource.get_query_str(
+ engine, datetime.now(), **query_obj)
+ except Exception as e:
+ return json_error_response(e)
+ return Response(
+ json.dumps({'query': query}),
+ status=200,
+ mimetype="application/json")
+
payload = {}
status = 200
try:
diff --git a/superset/viz.py b/superset/viz.py
index 85ee8dce5f6d7..20e9728fe6da1 100755
--- a/superset/viz.py
+++ b/superset/viz.py
@@ -106,10 +106,10 @@ def get_df(self, query_obj=None):
timestamp_format = dttm_col.python_date_format
# The datasource here can be different backend but the interface is common
- self.results = self.datasource.query(**query_obj)
+ self.results = self.datasource.query(query_obj)
+ self.query = self.results.query
self.status = self.results.status
self.error_message = self.results.error_message
- self.query = self.results.query
df = self.results.df
# Transform the timestamp we received from database to pandas supported