From 9a1ed400cae739f8e9916955b5717f3007153c00 Mon Sep 17 00:00:00 2001 From: Maxime Beauchemin Date: Tue, 19 Dec 2017 15:55:58 -0800 Subject: [PATCH] [sql lab] deeper support for templating (#3996) * [sql lab] deeper support for templating * Fixing py tests * Fix typo --- docs/sqllab.rst | 12 +- superset/assets/javascripts/SqlLab/actions.js | 6 + .../SqlLab/components/SqlEditor.jsx | 10 ++ .../components/TemplateParamsEditor.jsx | 129 ++++++++++++++++++ .../assets/javascripts/SqlLab/reducers.js | 3 + .../components/InfoTooltipWithTrigger.jsx | 2 +- superset/sql_lab.py | 11 +- superset/views/core.py | 9 +- 8 files changed, 174 insertions(+), 8 deletions(-) create mode 100644 superset/assets/javascripts/SqlLab/components/TemplateParamsEditor.jsx diff --git a/docs/sqllab.rst b/docs/sqllab.rst index a1da6c7f4529..6b87543906bb 100644 --- a/docs/sqllab.rst +++ b/docs/sqllab.rst @@ -48,17 +48,25 @@ Available macros We expose certain modules from Python's standard library in Superset's Jinja context: + - ``time``: ``time`` - ``datetime``: ``datetime.datetime`` - ``uuid``: ``uuid`` - ``random``: ``random`` - ``relativedelta``: ``dateutil.relativedelta.relativedelta`` -- more to come! `Jinja's builtin filters `_ can be also be applied where needed. - .. autoclass:: superset.jinja_context.PrestoTemplateProcessor :members: .. autofunction:: superset.jinja_context.url_param + +Extending macros +'''''''''''''''' + +As mentioned in the `Installation & Configuration`_ documentation, +it's possible for administrators to expose more more macros in their +environment using the configuration variable ``JINJA_CONTEXT_ADDONS``. +All objects referenced in this dictionary will become available for users +to integrate in their queries in **SQL Lab**. diff --git a/superset/assets/javascripts/SqlLab/actions.js b/superset/assets/javascripts/SqlLab/actions.js index 2541ee585646..d1fbfea46fdb 100644 --- a/superset/assets/javascripts/SqlLab/actions.js +++ b/superset/assets/javascripts/SqlLab/actions.js @@ -20,6 +20,7 @@ export const QUERY_EDITOR_SET_SCHEMA = 'QUERY_EDITOR_SET_SCHEMA'; export const QUERY_EDITOR_SET_TITLE = 'QUERY_EDITOR_SET_TITLE'; export const QUERY_EDITOR_SET_AUTORUN = 'QUERY_EDITOR_SET_AUTORUN'; export const QUERY_EDITOR_SET_SQL = 'QUERY_EDITOR_SET_SQL'; +export const QUERY_EDITOR_SET_TEMPLATE_PARAMS = 'QUERY_EDITOR_SET_TEMPLATE_PARAMS'; export const QUERY_EDITOR_SET_SELECTED_TEXT = 'QUERY_EDITOR_SET_SELECTED_TEXT'; export const QUERY_EDITOR_PERSIST_HEIGHT = 'QUERY_EDITOR_PERSIST_HEIGHT'; @@ -132,6 +133,7 @@ export function runQuery(query) { tab: query.tab, tmp_table_name: query.tempTableName, select_as_cta: query.ctas, + templateParams: query.templateParams, }; const sqlJsonUrl = '/superset/sql_json/' + location.search; $.ajax({ @@ -248,6 +250,10 @@ export function queryEditorSetSql(queryEditor, sql) { return { type: QUERY_EDITOR_SET_SQL, queryEditor, sql }; } +export function queryEditorSetTemplateParams(queryEditor, templateParams) { + return { type: QUERY_EDITOR_SET_TEMPLATE_PARAMS, queryEditor, templateParams }; +} + export function queryEditorSetSelectedText(queryEditor, sql) { return { type: QUERY_EDITOR_SET_SELECTED_TEXT, queryEditor, sql }; } diff --git a/superset/assets/javascripts/SqlLab/components/SqlEditor.jsx b/superset/assets/javascripts/SqlLab/components/SqlEditor.jsx index 4dcaede726b8..4b2e8999a76d 100644 --- a/superset/assets/javascripts/SqlLab/components/SqlEditor.jsx +++ b/superset/assets/javascripts/SqlLab/components/SqlEditor.jsx @@ -15,6 +15,7 @@ import { import SplitPane from 'react-split-pane'; import Button from '../../components/Button'; +import TemplateParamsEditor from './TemplateParamsEditor'; import SouthPane from './SouthPane'; import SaveQuery from './SaveQuery'; import Timer from '../../components/Timer'; @@ -24,6 +25,7 @@ import { STATE_BSSTYLE_MAP } from '../constants'; import RunQueryActionButton from './RunQueryActionButton'; import { t } from '../../locales'; + const propTypes = { actions: PropTypes.object.isRequired, height: PropTypes.string.isRequired, @@ -95,6 +97,7 @@ class SqlEditor extends React.PureComponent { tab: qe.title, schema: qe.schema, tempTableName: ctas ? this.state.ctas : '', + templateParams: qe.templateParams, runAsync, ctas, }; @@ -189,6 +192,13 @@ class SqlEditor extends React.PureComponent {
+ { + this.props.actions.queryEditorSetTemplateParams(qe, params); + }} + code={qe.templateParams} + /> {limitWarning} {this.props.latestQuery && {}, + code: '{}', +}; + +export default class TemplateParamsEditor extends React.Component { + constructor(props) { + super(props); + const codeText = props.code || '{}'; + this.state = { + codeText, + parsedJSON: null, + isValid: true, + }; + this.onChange = this.onChange.bind(this); + } + componentDidMount() { + this.onChange(this.state.codeText); + } + onChange(value) { + const codeText = value; + let isValid; + let parsedJSON = {}; + try { + parsedJSON = JSON.parse(value); + isValid = true; + } catch (e) { + isValid = false; + } + this.setState({ parsedJSON, isValid, codeText }); + if (isValid) { + this.props.onChange(codeText); + } else { + this.props.onChange('{}'); + } + } + renderDoc() { + return ( +

+ Assign a set of parameters as JSON below + (example: {'{"my_table": "foo"}'}), + and they become available + in your SQL (example: SELECT * FROM {'{{ my_table }}'} ) + by using + + Jinja templating + syntax. +

+ ); + } + renderModalBody() { + return ( +
+ {this.renderDoc()} + +
+ ); + } + render() { + const paramCount = this.state.parsedJSON ? Object.keys(this.state.parsedJSON).length : 0; + return ( + + {`${t('parameters')} `} + {paramCount > 0 && + {paramCount} + } + {!this.state.isValid && + + } + + } + modalBody={this.renderModalBody(true)} + /> + ); + } +} + +TemplateParamsEditor.propTypes = propTypes; +TemplateParamsEditor.defaultProps = defaultProps; diff --git a/superset/assets/javascripts/SqlLab/reducers.js b/superset/assets/javascripts/SqlLab/reducers.js index 3a49bd1b881c..f01f2c3bb73a 100644 --- a/superset/assets/javascripts/SqlLab/reducers.js +++ b/superset/assets/javascripts/SqlLab/reducers.js @@ -211,6 +211,9 @@ export const sqlLabReducer = function (state, action) { [actions.QUERY_EDITOR_SET_SQL]() { return alterInArr(state, 'queryEditors', action.queryEditor, { sql: action.sql }); }, + [actions.QUERY_EDITOR_SET_TEMPLATE_PARAMS]() { + return alterInArr(state, 'queryEditors', action.queryEditor, { templateParams: action.templateParams }); + }, [actions.QUERY_EDITOR_SET_SELECTED_TEXT]() { return alterInArr(state, 'queryEditors', action.queryEditor, { selectedText: action.sql }); }, diff --git a/superset/assets/javascripts/components/InfoTooltipWithTrigger.jsx b/superset/assets/javascripts/components/InfoTooltipWithTrigger.jsx index d86d0515e718..caacb914a50d 100644 --- a/superset/assets/javascripts/components/InfoTooltipWithTrigger.jsx +++ b/superset/assets/javascripts/components/InfoTooltipWithTrigger.jsx @@ -21,7 +21,7 @@ const tooltipStyle = { wordWrap: 'break-word' }; export default function InfoTooltipWithTrigger({ label, tooltip, icon, className, onClick, placement, bsStyle }) { - const iconClass = `fa fa-${icon} ${className} ${bsStyle ? 'text-' + bsStyle : ''}`; + const iconClass = `fa fa-${icon} ${className} ${bsStyle ? `text-${bsStyle}` : ''}`; const iconEl = (