Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[sql lab] allow users to save their queries #2528

Merged
merged 3 commits into from
Apr 5, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .pylintrc
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / stateme
[BASIC]

# Good variable names which should always be accepted, separated by a comma
good-names=i,j,k,ex,Run,_,d,e,v,o,l,x
good-names=i,j,k,ex,Run,_,d,e,v,o,l,x,ts

# Bad variable names which should always be refused, separated by a comma
bad-names=foo,bar,baz,toto,tutu,tata
Expand Down
3 changes: 3 additions & 0 deletions superset/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
from flask_appbuilder import SQLA, AppBuilder, IndexView
from flask_appbuilder.baseviews import expose
from flask_migrate import Migrate
from flask_wtf.csrf import CSRFProtect
from werkzeug.contrib.fixers import ProxyFix

from superset.connectors.connector_registry import ConnectorRegistry
Expand Down Expand Up @@ -50,6 +51,8 @@

db = SQLA(app)

if conf.get('WTF_CSRF_ENABLED'):
csrf = CSRFProtect(app)

utils.pessimistic_connection_handling(db.engine.pool)

Expand Down
36 changes: 36 additions & 0 deletions superset/assets/javascripts/SqlLab/actions.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/* global notify */
import shortid from 'shortid';
import { now } from '../modules/dates';
const $ = require('jquery');
Expand Down Expand Up @@ -33,11 +34,25 @@ export const QUERY_FAILED = 'QUERY_FAILED';
export const CLEAR_QUERY_RESULTS = 'CLEAR_QUERY_RESULTS';
export const REMOVE_DATA_PREVIEW = 'REMOVE_DATA_PREVIEW';
export const CHANGE_DATA_PREVIEW_ID = 'CHANGE_DATA_PREVIEW_ID';
export const SAVE_QUERY = 'SAVE_QUERY';

export function resetState() {
return { type: RESET_STATE };
}

export function saveQuery(query) {
const url = '/savedqueryviewapi/api/create';
$.ajax({
type: 'POST',
url,
data: query,
success: () => notify.success('Your query was saved'),
error: () => notify.error('Your query could not be saved'),
dataType: 'json',
});
return { type: SAVE_QUERY };
}

export function startQuery(query) {
Object.assign(query, {
id: query.id ? query.id : shortid.generate(),
Expand Down Expand Up @@ -328,6 +343,27 @@ export function popStoredQuery(urlId) {
};
dispatch(addQueryEditor(queryEditorProps));
},
error: () => notify.error("The query couldn't be loaded"),
});
};
}
export function popSavedQuery(saveQueryId) {
return function (dispatch) {
$.ajax({
type: 'GET',
url: `/savedqueryviewapi/api/get/${saveQueryId}`,
success: (data) => {
const sq = data.result;
const queryEditorProps = {
title: sq.label,
dbId: sq.db_id,
schema: sq.schema,
autorun: false,
sql: sq.sql,
};
dispatch(addQueryEditor(queryEditorProps));
},
error: () => notify.error("The query couldn't be loaded"),
});
};
}
34 changes: 0 additions & 34 deletions superset/assets/javascripts/SqlLab/components/Alerts.jsx

This file was deleted.

18 changes: 18 additions & 0 deletions superset/assets/javascripts/SqlLab/components/AlertsWrapper.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import React from 'react';
import AlertContainer from 'react-alert';

export default class AlertsWrapper extends React.PureComponent {
render() {
return (
<AlertContainer
ref={ref => {
global.notify = ref;
}}
offset={14}
position="top right"
theme="dark"
time={5000}
transition="fade"
/>);
}
}
4 changes: 2 additions & 2 deletions superset/assets/javascripts/SqlLab/components/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import React from 'react';
import TabbedSqlEditors from './TabbedSqlEditors';
import QueryAutoRefresh from './QueryAutoRefresh';
import QuerySearch from './QuerySearch';
import Alerts from './Alerts';
import AlertsWrapper from './AlertsWrapper';

import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
Expand Down Expand Up @@ -64,7 +64,7 @@ class App extends React.PureComponent {
}
return (
<div className="App SqlLab">
<Alerts id="sqllab-alerts" alerts={this.props.alerts} actions={this.props.actions} />
<AlertsWrapper />
<div className="container-fluid">
{content}
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export default function RunQueryActionButton(props) {
onClick={() => props.runQuery(false)}
key="run-btn"
>
<i className="fa fa-table" /> {runBtnText}
<i className="fa fa-refresh" /> {runBtnText}
</Button>
);

Expand Down
131 changes: 131 additions & 0 deletions superset/assets/javascripts/SqlLab/components/SaveQuery.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
/* global notify */
import React from 'react';
import { FormControl, FormGroup, Overlay, Popover, Row, Col } from 'react-bootstrap';
import Button from '../../components/Button';

const propTypes = {
defaultLabel: React.PropTypes.string,
sql: React.PropTypes.string,
schema: React.PropTypes.string,
dbId: React.PropTypes.number,
animation: React.PropTypes.bool,
onSave: React.PropTypes.func,
};
const defaultProps = {
defaultLabel: 'Undefined',
animation: true,
onSave: () => {},
};

class SaveQuery extends React.PureComponent {
constructor(props) {
super(props);
this.state = {
description: '',
label: props.defaultLabel,
showSave: false,
};
this.toggleSave = this.toggleSave.bind(this);
this.onSave = this.onSave.bind(this);
this.onCancel = this.onCancel.bind(this);
this.onLabelChange = this.onLabelChange.bind(this);
this.onDescriptionChange = this.onDescriptionChange.bind(this);
}
onSave() {
const query = {
label: this.state.label,
description: this.state.description,
db_id: this.props.dbId,
schema: this.props.schema,
sql: this.props.sql,
};
this.props.onSave(query);
this.setState({ showSave: false });
}
onCancel() {
this.setState({ showSave: false });
}
onLabelChange(e) {
this.setState({ label: e.target.value });
}
onDescriptionChange(e) {
this.setState({ description: e.target.value });
}
renderPopover() {
return (
<Popover id="embed-code-popover">
<FormGroup bsSize="small" style={{ width: '350px' }}>
<Row>
<Col md={12}>
<small>
<label className="control-label" htmlFor="embed-height">
Label
</label>
</small>
<FormControl
type="text"
placeholder="Label for your query"
value={this.state.label}
onChange={this.onLabelChange}
/>
</Col>
</Row>
<br />
<Row>
<Col md={12}>
<small>
<label className="control-label" htmlFor="embed-height">Description</label>
</small>
<FormControl
componentClass="textarea"
placeholder="Write a description for your query"
value={this.state.description}
onChange={this.onDescriptionChange}
/>
</Col>
</Row>
<br />
<Row>
<Col md={12}>
<Button
bsStyle="primary"
onClick={this.onSave}
className="m-r-3"
>
Save
</Button>
<Button onClick={this.onCancel} className="cancelQuery">
Cancel
</Button>
</Col>
</Row>
</FormGroup>
</Popover>
);
}
toggleSave(e) {
this.setState({ target: e.target, showSave: !this.state.showSave });
}
render() {
return (
<span className="SaveQuery">
<Overlay
trigger="click"
target={this.state.target}
show={this.state.showSave}
placement="bottom"
animation={this.props.animation}
>
{this.renderPopover()}
</Overlay>
<Button bsSize="small" className="toggleSave" onClick={this.toggleSave}>
<i className="fa fa-save" /> Save Query
</Button>
</span>
);
}
}
SaveQuery.propTypes = propTypes;
SaveQuery.defaultProps = defaultProps;

export default SaveQuery;
13 changes: 11 additions & 2 deletions superset/assets/javascripts/SqlLab/components/SqlEditor.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
import Button from '../../components/Button';

import SouthPane from './SouthPane';
import SaveQuery from './SaveQuery';
import Timer from '../../components/Timer';
import SqlEditorLeftBar from './SqlEditorLeftBar';
import AceEditorWrapper from './AceEditorWrapper';
Expand Down Expand Up @@ -101,6 +102,7 @@ class SqlEditor extends React.PureComponent {
}

render() {
const qe = this.props.queryEditor;
let limitWarning = null;
if (this.props.latestQuery && this.props.latestQuery.limit_reached) {
const tooltip = (
Expand Down Expand Up @@ -149,12 +151,19 @@ class SqlEditor extends React.PureComponent {
<Form inline>
<RunQueryActionButton
allowAsync={this.props.database ? this.props.database.allow_run_async : false}
dbId={this.props.queryEditor.dbId}
dbId={qe.dbId}
queryState={this.props.latestQuery && this.props.latestQuery.state}
runQuery={this.runQuery.bind(this)}
selectedText={this.props.queryEditor.selectedText}
selectedText={qe.selectedText}
stopQuery={this.stopQuery.bind(this)}
/>
<SaveQuery
defaultLabel={qe.title}
sql={qe.sql}
onSave={this.props.actions.saveQuery}
schema={qe.schema}
dbId={qe.dbId}
/>
{ctasControls}
</Form>
</div>
Expand Down
28 changes: 14 additions & 14 deletions superset/assets/javascripts/SqlLab/components/TabbedSqlEditors.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import * as Actions from '../actions';
import SqlEditor from './SqlEditor';
import CopyQueryTabUrl from './CopyQueryTabUrl';
import { areArraysShallowEqual } from '../../reduxUtils';
import { getParamFromQuery } from '../../../utils/common';
import URI from 'urijs';
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what is the advantage of urijs over using getParamFromQuery? it does look a little cleaner, but i'm thinking it might not be worth the addition depending on the package size.
how big of a lib is urijs?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the sql lab entry js file 13.2mb 🙈
screenshot 2017-04-03 17 46 22

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I really need it for this PR though: #2537

We should dig into what is taking those 13 mb. I'll try to dig into that, I bet urijs doesn't really weight in..


const propTypes = {
actions: React.PropTypes.object.isRequired,
Expand Down Expand Up @@ -35,19 +35,19 @@ class TabbedSqlEditors extends React.PureComponent {
};
}
componentDidMount() {
const search = window.location.search;
if (search) {
const queryString = search.substring(1);
const urlId = getParamFromQuery(queryString, 'id');
if (urlId) {
this.props.actions.popStoredQuery(urlId);
} else {
let dbId = getParamFromQuery(queryString, 'dbid');
const query = URI(window.location).search(true);
if (query.id || query.sql || query.savedQueryId) {
if (query.id) {
this.props.actions.popStoredQuery(query.id);
} else if (query.savedQueryId) {
this.props.actions.popSavedQuery(query.savedQueryId);
} else if (query.sql) {
let dbId = query.dbid;
if (dbId) {
dbId = parseInt(dbId, 10);
} else {
const databases = this.props.databases;
const dbName = getParamFromQuery(queryString, 'dbname');
const dbName = query.dbname;
if (dbName) {
Object.keys(databases).forEach((db) => {
if (databases[db].database_name === dbName) {
Expand All @@ -57,11 +57,11 @@ class TabbedSqlEditors extends React.PureComponent {
}
}
const newQueryEditor = {
title: getParamFromQuery(queryString, 'title'),
title: query.title,
dbId,
schema: getParamFromQuery(queryString, 'schema'),
autorun: getParamFromQuery(queryString, 'autorun'),
sql: getParamFromQuery(queryString, 'sql'),
schema: query.schema,
autorun: query.autorun,
sql: query.sql,
};
this.props.actions.addQueryEditor(newQueryEditor);
}
Expand Down
Loading