URL Params macro (#2537)

This commit is contained in:
Maxime Beauchemin 2017-04-05 10:45:32 -07:00 committed by GitHub
parent b97a8275d4
commit 10773f96a7
11 changed files with 151 additions and 35 deletions

View File

@ -1,9 +1,12 @@
.. image:: _static/img/s.png
Superset's documentation Superset's documentation
'''''''''''''''''''''''' ''''''''''''''''''''''''
Superset is a data exploration platform designed to be visual, intuitive Superset is a data exploration platform designed to be visual, intuitive
and interactive. and interactive.
---------------- ----------------
.. warning:: This project was originally named Panoramix, was renamed to .. warning:: This project was originally named Panoramix, was renamed to

View File

@ -58,3 +58,5 @@ Superset's Jinja context:
.. autoclass:: superset.jinja_context.PrestoTemplateProcessor .. autoclass:: superset.jinja_context.PrestoTemplateProcessor
:members: :members:
.. autofunction:: superset.jinja_context.url_param

1
superset/assets/docs Symbolic link
View File

@ -0,0 +1 @@
../../docs/_build/html/

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View File

@ -113,7 +113,6 @@ export function fetchQueryResults(query) {
export function runQuery(query) { export function runQuery(query) {
return function (dispatch) { return function (dispatch) {
dispatch(startQuery(query)); dispatch(startQuery(query));
const sqlJsonUrl = '/superset/sql_json/';
const sqlJsonRequest = { const sqlJsonRequest = {
client_id: query.id, client_id: query.id,
database_id: query.dbId, database_id: query.dbId,
@ -126,6 +125,7 @@ export function runQuery(query) {
tmp_table_name: query.tempTableName, tmp_table_name: query.tempTableName,
select_as_cta: query.ctas, select_as_cta: query.ctas,
}; };
const sqlJsonUrl = '/superset/sql_json/' + location.search;
$.ajax({ $.ajax({
type: 'POST', type: 'POST',
dataType: 'json', dataType: 'json',

View File

@ -1,3 +1,4 @@
/* global notify */
import React from 'react'; import React from 'react';
import { Alert, Button, Col, Modal } from 'react-bootstrap'; import { Alert, Button, Col, Modal } from 'react-bootstrap';
@ -5,6 +6,7 @@ import Select from 'react-select';
import { Table } from 'reactable'; import { Table } from 'reactable';
import shortid from 'shortid'; import shortid from 'shortid';
import $ from 'jquery'; import $ from 'jquery';
import { getExploreUrl } from '../../explorev2/exploreUtils';
const CHART_TYPES = [ const CHART_TYPES = [
{ value: 'dist_bar', label: 'Distribution - Bar Chart', requiresTime: false }, { value: 'dist_bar', label: 'Distribution - Bar Chart', requiresTime: false },
@ -89,7 +91,9 @@ class VisualizeModal extends React.PureComponent {
} }
} }
if (!hasTime) { if (!hasTime) {
hints.push('To use this chart type you need at least one column flagged as a date'); hints.push(
'To use this chart type you need at least one column ' +
'flagged as a date');
} }
} }
this.setState({ hints }); this.setState({ hints });
@ -113,9 +117,10 @@ class VisualizeModal extends React.PureComponent {
chartType: this.state.chartType.value, chartType: this.state.chartType.value,
datasourceName: this.state.datasourceName, datasourceName: this.state.datasourceName,
columns: this.state.columns, columns: this.state.columns,
sql: this.props.query.executedSql, sql: this.props.query.sql,
dbId: this.props.query.dbId, dbId: this.props.query.dbId,
}; };
notify.info('Creating a data source and popping a new tab');
$.ajax({ $.ajax({
type: 'POST', type: 'POST',
url: '/superset/sqllab_viz/', url: '/superset/sqllab_viz/',
@ -123,9 +128,28 @@ class VisualizeModal extends React.PureComponent {
data: { data: {
data: JSON.stringify(vizOptions), data: JSON.stringify(vizOptions),
}, },
success: (url) => { dataType: 'json',
window.open(url); success: resp => {
const columns = Object.keys(this.state.columns).map(k => this.state.columns[k]);
const data = JSON.parse(resp);
const mainMetric = columns.filter(d => d.agg)[0];
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 (mainMetric) {
formData.metrics = [mainMetric.name];
formData.metric = mainMetric.name;
}
if (mainGroupBy) {
formData.groupby = mainGroupBy.name;
}
window.open(getExploreUrl(formData));
}, },
error: () => notify('An error occurred while creating the data source'),
}); });
} }
changeDatasourceName(event) { changeDatasourceName(event) {

View File

@ -1,26 +1,43 @@
/* eslint camelcase: 0 */ /* eslint camelcase: 0 */
export function getExploreUrl(form_data, endpoint = 'base', force = false) { import URI from 'urijs';
export function getExploreUrl(form_data, endpointType = 'base', force = false, curUrl = null) {
if (!form_data.datasource) { if (!form_data.datasource) {
return null; return null;
} }
// The search params from the window.location are carried through,
// but can be specified with curUrl (used for unit tests to spoof
// the window.location).
let uri = URI(window.location.search);
if (curUrl) {
uri = URI(URI(curUrl).search());
}
// Building the directory part of the URI
let directory = '/superset/explore/';
if (['json', 'csv', 'query'].indexOf(endpointType) >= 0) {
directory = '/superset/explore_json/';
}
const [datasource_id, datasource_type] = form_data.datasource.split('__'); const [datasource_id, datasource_type] = form_data.datasource.split('__');
let params = `${datasource_type}/${datasource_id}/`; directory += `${datasource_type}/${datasource_id}/`;
params += '?form_data=' + encodeURIComponent(JSON.stringify(form_data));
// Building the querystring (search) part of the URI
const search = uri.search(true);
search.form_data = JSON.stringify(form_data);
if (force) { if (force) {
params += '&force=true'; search.force = 'true';
} }
switch (endpoint) { if (endpointType === 'csv') {
case 'base': search.csv = 'true';
return `/superset/explore/${params}`;
case 'json':
return `/superset/explore_json/${params}`;
case 'csv':
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}`;
} }
if (endpointType === 'standalone') {
search.standalone = 'true';
}
if (endpointType === 'query') {
search.query = 'true';
}
uri = uri.search(search).directory(directory);
return uri.toString();
} }

View File

@ -0,0 +1,59 @@
import { it, describe } from 'mocha';
import { expect } from 'chai';
import URI from 'urijs';
import { getExploreUrl } from '../../../javascripts/explorev2/exploreUtils.js';
describe('utils', () => {
const formData = {
datasource: '1__table',
};
const sFormData = JSON.stringify(formData);
function compareURI(uri1, uri2) {
expect(uri1.toString()).to.equal(uri2.toString());
}
it('getExploreUrl generates proper base url', () => {
// This assertion is to show clearly the value of location.href
// in the context of unit tests.
expect(location.href).to.equal('about:blank');
compareURI(
URI(getExploreUrl(formData, 'base', false, 'http://superset.com')),
URI('/superset/explore/table/1/').search({ form_data: sFormData })
);
});
it('getExploreUrl generates proper json url', () => {
compareURI(
URI(getExploreUrl(formData, 'json', false, 'superset.com')),
URI('/superset/explore_json/table/1/').search({ form_data: sFormData })
);
});
it('getExploreUrl generates proper json forced url', () => {
compareURI(
URI(getExploreUrl(formData, 'json', true, 'superset.com')),
URI('/superset/explore_json/table/1/')
.search({ form_data: sFormData, force: 'true' })
);
});
it('getExploreUrl generates proper csv URL', () => {
compareURI(
URI(getExploreUrl(formData, 'csv', false, 'superset.com')),
URI('/superset/explore_json/table/1/')
.search({ form_data: sFormData, csv: 'true' })
);
});
it('getExploreUrl generates proper standalone URL', () => {
compareURI(
URI(getExploreUrl(formData, 'standalone', false, 'superset.com')),
URI('/superset/explore/table/1/')
.search({ form_data: sFormData, standalone: 'true' })
);
});
it('getExploreUrl preserves main URLs params', () => {
compareURI(
URI(getExploreUrl(formData, 'json', false, 'superset.com?foo=bar')),
URI('/superset/explore_json/table/1/')
.search({ foo: 'bar', form_data: sFormData })
);
});
});

View File

@ -6,6 +6,7 @@ from __future__ import unicode_literals
import inspect import inspect
from jinja2.sandbox import SandboxedEnvironment from jinja2.sandbox import SandboxedEnvironment
from flask import request
from datetime import datetime, timedelta from datetime import datetime, timedelta
from dateutil.relativedelta import relativedelta from dateutil.relativedelta import relativedelta
@ -27,6 +28,18 @@ BASE_CONTEXT = {
BASE_CONTEXT.update(config.get('JINJA_CONTEXT_ADDONS', {})) BASE_CONTEXT.update(config.get('JINJA_CONTEXT_ADDONS', {}))
def url_param(param, default=None):
"""Get a url paramater
:param param: the url parameter to lookup
:type param: str
:param default: the value to return in the absence of the parameter
:type default: str
"""
print(request.args)
return request.args.get(param, default)
class BaseTemplateProcessor(object): class BaseTemplateProcessor(object):
"""Base class for database-specific jinja context """Base class for database-specific jinja context
@ -52,7 +65,9 @@ class BaseTemplateProcessor(object):
self.schema = query.schema self.schema = query.schema
elif table: elif table:
self.schema = table.schema self.schema = table.schema
self.context = {} self.context = {
'url_param': url_param,
}
self.context.update(kwargs) self.context.update(kwargs)
self.context.update(BASE_CONTEXT) self.context.update(BASE_CONTEXT)
if self.engine: if self.engine:

View File

@ -1,6 +1,8 @@
import logging
import sqlparse import sqlparse
from sqlparse.sql import IdentifierList, Identifier from sqlparse.sql import IdentifierList, Identifier
from sqlparse.tokens import DML, Keyword, Name from sqlparse.tokens import Keyword, Name
RESULT_OPERATIONS = {'UNION', 'INTERSECT', 'EXCEPT'} RESULT_OPERATIONS = {'UNION', 'INTERSECT', 'EXCEPT'}
PRECEDES_TABLE_NAME = {'FROM', 'JOIN', 'DESC', 'DESCRIBE', 'WITH'} PRECEDES_TABLE_NAME = {'FROM', 'JOIN', 'DESC', 'DESCRIBE', 'WITH'}
@ -13,6 +15,7 @@ class SupersetQuery(object):
self._table_names = set() self._table_names = set()
self._alias_names = set() self._alias_names = set()
# TODO: multistatement support # TODO: multistatement support
logging.info("Parsing with sqlparse statement {}".format(self.sql))
self._parsed = sqlparse.parse(self.sql) self._parsed = sqlparse.parse(self.sql)
for statement in self._parsed: for statement in self._parsed:
self.__extract_from_token(statement) self.__extract_from_token(statement)

View File

@ -1710,7 +1710,6 @@ class Superset(BaseSupersetView):
SqlaTable = ConnectorRegistry.sources['table'] SqlaTable = ConnectorRegistry.sources['table']
data = json.loads(request.form.get('data')) data = json.loads(request.form.get('data'))
table_name = data.get('datasourceName') table_name = data.get('datasourceName')
viz_type = data.get('chartType')
SqlaTable = ConnectorRegistry.sources['table'] SqlaTable = ConnectorRegistry.sources['table']
table = ( table = (
db.session.query(SqlaTable) db.session.query(SqlaTable)
@ -1762,16 +1761,9 @@ class Superset(BaseSupersetView):
table.columns = cols table.columns = cols
table.metrics = metrics table.metrics = metrics
db.session.commit() db.session.commit()
params = { return self.json_response(json.dumps({
'viz_type': viz_type, 'table_id': table.id,
'groupby': dims[0].column_name if dims else None, }))
'metrics': metrics[0].metric_name if metrics else None,
'metric': metrics[0].metric_name if metrics else None,
'since': '100 years ago',
'limit': '0',
}
params = "&".join([k + '=' + v for k, v in params.items() if v])
return '/superset/explore/table/{table.id}/?{params}'.format(**locals())
@has_access @has_access
@expose("/table/<database_id>/<table_name>/<schema>/") @expose("/table/<database_id>/<table_name>/<schema>/")