diff --git a/docs/sqllab.rst b/docs/sqllab.rst
index a1da6c7f45291..6b87543906bb9 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 2541ee585646b..d1fbfea46fdb2 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 4dcaede726b8b..4b2e8999a76db 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 (
+
+ );
+ }
+ 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 3a49bd1b881cc..f01f2c3bb73a2 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 d86d0515e718c..caacb914a50de 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 = (