Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
SQL Lab - A multi-tab SQL editor (#514)
* Carapal react mockup

This is really just a mock up written in React to try different
components. It could become scaffolding to build a prototype, or not.

* Merging in Alanna's theme tweaks for SQL lab

* Tweak the display of the alert message in navbar

* Sketching the middleware refresh for Queries

* Adjustments

* Implement timer sync.

* CTAS

* Refactor the queries to be stored as a dict. (#994)

* Download csv endpoint. (#992)

* CSV download engdpoint.

* Use lower case booleans.

* Replcate loop with the object lookup by key.

* First changes for the sync

* Address comments

* Fix query deletions. Update only the queries from the store.

* Sync queries using tmp_id.

* simplify

* Fix the tests in the carapal. (#1023)

* Sync queries using tmp_id.

* Fix the unit tests

* Bux fixes. Pass 2.

* Tweakin' & linting

* Adding alpha label to the SQL LAb navbar entry

* Fixing the python unit tests
  • Loading branch information
mistercrunch committed Aug 30, 2016
1 parent f17cfcb commit 38b8db8
Show file tree
Hide file tree
Showing 62 changed files with 4,268 additions and 560 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Expand Up @@ -18,6 +18,8 @@ dist
caravel.egg-info/
app.db
*.bak
.idea
*.sqllite

# Node.js, webpack artifacts
*.entry.js
Expand Down
1 change: 1 addition & 0 deletions MANIFEST.in
Expand Up @@ -3,6 +3,7 @@ recursive-include caravel/static *
recursive-exclude caravel/static/assets/node_modules *
recursive-include caravel/static/assets/node_modules/font-awesome *
recursive-exclude caravel/static/docs *
recursive-exclude caravel/static/spec *
recursive-exclude tests *
recursive-include caravel/data *
recursive-include caravel/migrations *
2 changes: 1 addition & 1 deletion caravel/assets/.eslintignore
@@ -1,5 +1,5 @@
node_modules/*
vendor/*
javascripts/dist/*
dist/*
stylesheets/*
spec/*
19 changes: 19 additions & 0 deletions caravel/assets/javascripts/SqlLab/TODO.md
@@ -0,0 +1,19 @@

# TODO
* Figure out how to organize the left panel, integrate Search
* collapse sql beyond 10 lines
* Security per-database (dropdown)
* Get a to work

## Cosmetic
* Result set font is too big
* lmiit/timer/buttons wrap
* table label is transparent
* SqlEditor buttons
* use react-bootstrap-prompt for query title input
* Make tabs look great

# PROJECT
* Write Runbook
* Confirm backups
* merge chef branch
116 changes: 116 additions & 0 deletions caravel/assets/javascripts/SqlLab/actions.js
@@ -0,0 +1,116 @@
export const RESET_STATE = 'RESET_STATE';
export const ADD_QUERY_EDITOR = 'ADD_QUERY_EDITOR';
export const REMOVE_QUERY_EDITOR = 'REMOVE_QUERY_EDITOR';
export const ADD_TABLE = 'ADD_TABLE';
export const REMOVE_TABLE = 'REMOVE_TABLE';
export const START_QUERY = 'START_QUERY';
export const STOP_QUERY = 'STOP_QUERY';
export const END_QUERY = 'END_QUERY';
export const REMOVE_QUERY = 'REMOVE_QUERY';
export const EXPAND_TABLE = 'EXPAND_TABLE';
export const COLLAPSE_TABLE = 'COLLAPSE_TABLE';
export const QUERY_SUCCESS = 'QUERY_SUCCESS';
export const QUERY_FAILED = 'QUERY_FAILED';
export const QUERY_EDITOR_SETDB = 'QUERY_EDITOR_SETDB';
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 SET_WORKSPACE_DB = 'SET_WORKSPACE_DB';
export const ADD_WORKSPACE_QUERY = 'ADD_WORKSPACE_QUERY';
export const REMOVE_WORKSPACE_QUERY = 'REMOVE_WORKSPACE_QUERY';
export const SET_ACTIVE_QUERY_EDITOR = 'SET_ACTIVE_QUERY_EDITOR';
export const ADD_ALERT = 'ADD_ALERT';
export const REMOVE_ALERT = 'REMOVE_ALERT';
export const REFRESH_QUERIES = 'REFRESH_QUERIES';

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

export function addQueryEditor(queryEditor) {
return { type: ADD_QUERY_EDITOR, queryEditor };
}

export function addAlert(alert) {
return { type: ADD_ALERT, alert };
}

export function removeAlert(alert) {
return { type: REMOVE_ALERT, alert };
}

export function setActiveQueryEditor(queryEditor) {
return { type: SET_ACTIVE_QUERY_EDITOR, queryEditor };
}

export function removeQueryEditor(queryEditor) {
return { type: REMOVE_QUERY_EDITOR, queryEditor };
}

export function removeQuery(query) {
return { type: REMOVE_QUERY, query };
}

export function queryEditorSetDb(queryEditor, dbId) {
return { type: QUERY_EDITOR_SETDB, queryEditor, dbId };
}

export function queryEditorSetSchema(queryEditor, schema) {
return { type: QUERY_EDITOR_SET_SCHEMA, queryEditor, schema };
}

export function queryEditorSetAutorun(queryEditor, autorun) {
return { type: QUERY_EDITOR_SET_AUTORUN, queryEditor, autorun };
}

export function queryEditorSetTitle(queryEditor, title) {
return { type: QUERY_EDITOR_SET_TITLE, queryEditor, title };
}

export function queryEditorSetSql(queryEditor, sql) {
return { type: QUERY_EDITOR_SET_SQL, queryEditor, sql };
}

export function addTable(table) {
return { type: ADD_TABLE, table };
}

export function expandTable(table) {
return { type: EXPAND_TABLE, table };
}

export function collapseTable(table) {
return { type: COLLAPSE_TABLE, table };
}

export function removeTable(table) {
return { type: REMOVE_TABLE, table };
}

export function startQuery(query) {
return { type: START_QUERY, query };
}

export function stopQuery(query) {
return { type: STOP_QUERY, query };
}

export function querySuccess(query, results) {
return { type: QUERY_SUCCESS, query, results };
}

export function queryFailed(query, msg) {
return { type: QUERY_FAILED, query, msg };
}

export function addWorkspaceQuery(query) {
return { type: ADD_WORKSPACE_QUERY, query };
}

export function removeWorkspaceQuery(query) {
return { type: REMOVE_WORKSPACE_QUERY, query };
}
export function refreshQueries(alteredQueries) {
return { type: REFRESH_QUERIES, alteredQueries };
}
6 changes: 6 additions & 0 deletions caravel/assets/javascripts/SqlLab/common.js
@@ -0,0 +1,6 @@
export const STATE_BSSTYLE_MAP = {
failed: 'danger',
pending: 'info',
running: 'warning',
success: 'success',
};
41 changes: 41 additions & 0 deletions caravel/assets/javascripts/SqlLab/components/Alerts.jsx
@@ -0,0 +1,41 @@
import React from 'react';
import { Alert } from 'react-bootstrap';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import * as Actions from '../actions';

class Alerts extends React.Component {
removeAlert(alert) {
this.props.actions.removeAlert(alert);
}
render() {
const alerts = this.props.alerts.map((alert) =>
<Alert
bsStyle={alert.bsStyle}
style={{ width: '500px', textAlign: 'midddle', margin: '10px auto' }}
>
{alert.msg}
<i
className="fa fa-close pull-right"
onClick={this.removeAlert.bind(this, alert)}
style={{ cursor: 'pointer' }}
/>
</Alert>
);
return (
<div>{alerts}</div>
);
}
}

Alerts.propTypes = {
alerts: React.PropTypes.array,
actions: React.PropTypes.object,
};

function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators(Actions, dispatch),
};
}
export default connect(null, mapDispatchToProps)(Alerts);
48 changes: 48 additions & 0 deletions caravel/assets/javascripts/SqlLab/components/ButtonWithTooltip.jsx
@@ -0,0 +1,48 @@
import React from 'react';
import { Button, OverlayTrigger, Tooltip } from 'react-bootstrap';

const ButtonWithTooltip = (props) => {
let tooltip = (
<Tooltip id="tooltip">
{props.tooltip}
</Tooltip>
);
return (
<OverlayTrigger
overlay={tooltip}
delayShow={300}
placement={props.placement}
delayHide={150}
>
<Button
onClick={props.onClick}
bsStyle={props.bsStyle}
bsSize={props.bsSize}
disabled={props.disabled}
className={props.className}
>
{props.children}
</Button>
</OverlayTrigger>
);
};

ButtonWithTooltip.defaultProps = {
onClick: () => {},
disabled: false,
placement: 'top',
bsStyle: 'default',
};

ButtonWithTooltip.propTypes = {
bsSize: React.PropTypes.string,
bsStyle: React.PropTypes.string,
children: React.PropTypes.element,
className: React.PropTypes.string,
disabled: React.PropTypes.bool,
onClick: React.PropTypes.func,
placement: React.PropTypes.string,
tooltip: React.PropTypes.string,
};

export default ButtonWithTooltip;
62 changes: 62 additions & 0 deletions caravel/assets/javascripts/SqlLab/components/LeftPane.jsx
@@ -0,0 +1,62 @@
import React from 'react';
import { Alert, Button } from 'react-bootstrap';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import * as Actions from '../actions';
import QueryLink from './QueryLink';

import 'react-select/dist/react-select.css';

const LeftPane = (props) => {
let queryElements;
if (props.workspaceQueries.length > 0) {
queryElements = props.workspaceQueries.map((q) => <QueryLink query={q} />);
} else {
queryElements = (
<Alert bsStyle="info">
Use the save button on the SQL editor to save a query
into this section for future reference.
</Alert>
);
}
return (
<div>
<div className="panel panel-default LeftPane">
<div className="panel-heading">
<div className="panel-title">
Saved Queries
</div>
</div>
<div className="panel-body">
{queryElements}
</div>
</div>
<br /><br />
<Button onClick={props.actions.resetState.bind(this)} bsStyle="danger">
Reset State
</Button>
</div>
);
};

LeftPane.propTypes = {
workspaceQueries: React.PropTypes.array,
actions: React.PropTypes.object,
};

LeftPane.defaultProps = {
workspaceQueries: [],
};

function mapStateToProps(state) {
return {
workspaceQueries: state.workspaceQueries,
};
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators(Actions, dispatch),
};
}

export default connect(mapStateToProps, mapDispatchToProps)(LeftPane);
54 changes: 54 additions & 0 deletions caravel/assets/javascripts/SqlLab/components/Link.jsx
@@ -0,0 +1,54 @@
import React from 'react';
import { OverlayTrigger, Tooltip } from 'react-bootstrap';


class Link extends React.Component {
render() {
let tooltip = (
<Tooltip id="tooltip">
{this.props.tooltip}
</Tooltip>
);
const link = (
<a
href={this.props.href}
onClick={this.props.onClick}
style={this.props.style}
className={'Link ' + this.props.className}
>
{this.props.children}
</a>
);
if (this.props.tooltip) {
return (
<OverlayTrigger
overlay={tooltip}
placement={this.props.placement}
delayShow={300}
delayHide={150}
>
{link}
</OverlayTrigger>
);
}
return link;
}
}
Link.propTypes = {
children: React.PropTypes.object,
className: React.PropTypes.string,
href: React.PropTypes.string,
onClick: React.PropTypes.func,
placement: React.PropTypes.string,
style: React.PropTypes.object,
tooltip: React.PropTypes.string,
};
Link.defaultProps = {
disabled: false,
href: '#',
tooltip: null,
placement: 'top',
onClick: () => {},
};

export default Link;

0 comments on commit 38b8db8

Please sign in to comment.