Skip to content
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
1 change: 1 addition & 0 deletions .codeclimate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,4 @@ exclude_paths:
- "caravel/assets/vendor/"
- "caravel/assets/node_modules/"
- "caravel/assets/javascripts/dist/"
- "caravel/migrations"
30 changes: 30 additions & 0 deletions caravel/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

from flask import Flask, redirect
from flask_appbuilder import SQLA, AppBuilder, IndexView
from sqlalchemy import event, exc
from flask_appbuilder.baseviews import expose
from flask_cache import Cache
from flask_migrate import Migrate
Expand All @@ -30,6 +31,35 @@

db = SQLA(app)


@event.listens_for(db.engine, 'checkout')
def checkout(dbapi_con, con_record, con_proxy):
"""
Making sure the connection is live, and preventing against:
'MySQL server has gone away'

Copied from:
http://stackoverflow.com/questions/30630120/mysql-keeps-losing-connection-during-celery-tasks
"""
try:
try:
if hasattr(dbapi_con, 'ping'):
dbapi_con.ping(False)
else:
cursor = dbapi_con.cursor()
cursor.execute("SELECT 1")
except TypeError:
app.logger.debug('MySQL connection died. Restoring...')
dbapi_con.ping()
except dbapi_con.OperationalError as e:
app.logger.warning(e)
if e.args[0] in (2006, 2013, 2014, 2045, 2055):
Copy link
Member

Choose a reason for hiding this comment

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

could you please add the comment explaining the values in the tuple ?

raise exc.DisconnectionError()
else:
raise
return db


cache = Cache(app, config=app.config.get('CACHE_CONFIG'))

migrate = Migrate(app, db, directory=APP_DIR + "/migrations")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ class QueryTable extends React.Component {
if (q.endDttm) {
q.duration = fDuration(q.startDttm, q.endDttm);
}
q.started = moment.utc(q.startDttm).format('HH:mm:ss');
q.started = moment(q.startDttm).format('HH:mm:ss');
const source = (q.ctas) ? q.executedSql : q.sql;
q.sql = (
<SqlShrink sql={source} />
Expand Down
2 changes: 1 addition & 1 deletion caravel/assets/javascripts/SqlLab/components/ResultSet.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ class ResultSet extends React.Component {
</div>
);
}
if (results && results.data.length > 0) {
if (results && results.data && results.data.length > 0) {
return (
<div>
<VisualizeModal
Expand Down
64 changes: 25 additions & 39 deletions caravel/assets/javascripts/SqlLab/components/SqlEditor.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,7 @@ import {
InputGroup,
Form,
FormControl,
DropdownButton,
Label,
MenuItem,
OverlayTrigger,
Row,
Tooltip,
Expand All @@ -27,7 +25,6 @@ import { connect } from 'react-redux';
import * as Actions from '../actions';

import shortid from 'shortid';
import ButtonWithTooltip from './ButtonWithTooltip';
import SouthPane from './SouthPane';
import Timer from './Timer';

Expand All @@ -52,8 +49,8 @@ class SqlEditor extends React.Component {
this.startQuery();
}
}
runQuery() {
this.startQuery();
runQuery(runAsync = false) {
this.startQuery(runAsync);
}
startQuery(runAsync = false, ctas = false) {
const that = this;
Expand All @@ -76,10 +73,10 @@ class SqlEditor extends React.Component {

const sqlJsonUrl = '/caravel/sql_json/';
const sqlJsonRequest = {
async: runAsync,
client_id: query.id,
database_id: this.props.queryEditor.dbId,
json: true,
runAsync,
schema: this.props.queryEditor.schema,
select_as_cta: ctas,
sql: this.props.queryEditor.sql,
Expand Down Expand Up @@ -149,17 +146,36 @@ class SqlEditor extends React.Component {
}

render() {
let runButtons = (
<ButtonGroup bsSize="small" className="inline m-r-5 pull-left">
let runButtons = [];
if (this.props.database && this.props.database.allow_run_sync) {
runButtons.push(
<Button
bsSize="small"
bsStyle="primary"
style={{ width: '100px' }}
onClick={this.runQuery.bind(this)}
onClick={this.runQuery.bind(this, false)}
disabled={!(this.props.queryEditor.dbId)}
>
<i className="fa fa-table" /> Run Query
</Button>
);
}
if (this.props.database && this.props.database.allow_run_async) {
Copy link

@ascott ascott Sep 13, 2016

Choose a reason for hiding this comment

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

should this be an else if block? seems like you only want 1 run button. could also use case statements rather than series of if blocks.

runButtons.push(
Copy link

@ascott ascott Sep 13, 2016

Choose a reason for hiding this comment

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

would be nice to DRY this up. could make a <RunButton /> component to encapsulate the logic and returns the correct button based on the input:

<RunButton
  async={this.props.database && this.props.database.allow_run_async}
  sync={this.props.database && this.props.database.allow_run_sync}
  isRunning={this.props.latestQuery && this.props.latestQuery.state === 'running'}
/>

Copy link

Choose a reason for hiding this comment

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

could probably simply further by only passing the async prop. if async is false, use sync.

<RunButton
  async={this.props.database && this.props.database.allow_run_async}
  isRunning={this.props.latestQuery && this.props.latestQuery.state === 'running'}
/>

<Button
bsSize="small"
bsStyle="primary"
style={{ width: '100px' }}
Copy link

Choose a reason for hiding this comment

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

i know we haven't internationalized this yet, but in another language this button may need to be more than 100px wide. why is this width needed here?

onClick={this.runQuery.bind(this, true)}
disabled={!(this.props.queryEditor.dbId)}
>
<i className="fa fa-table" /> Run Async
</Button>
);
}
runButtons = (
<ButtonGroup bsSize="small" className="inline m-r-5 pull-left">
{runButtons}
</ButtonGroup>
);
if (this.props.latestQuery && this.props.latestQuery.state === 'running') {
Expand All @@ -176,35 +192,6 @@ class SqlEditor extends React.Component {
</ButtonGroup>
);
}
const rightButtons = (
<ButtonGroup className="inlineblock">
<ButtonWithTooltip
tooltip="Save this query in your workspace"
placement="left"
bsSize="small"
onClick={this.addWorkspaceQuery.bind(this)}
>
<i className="fa fa-save" />&nbsp;
</ButtonWithTooltip>
<DropdownButton
id="ddbtn-export"
pullRight
bsSize="small"
title={<i className="fa fa-file-o" />}
>
<MenuItem
onClick={this.notImplemented}
>
<i className="fa fa-file-text-o" /> export to .csv
</MenuItem>
<MenuItem
onClick={this.notImplemented}
>
<i className="fa fa-file-code-o" /> export to .json
</MenuItem>
</DropdownButton>
</ButtonGroup>
);
let limitWarning = null;
const rowLimit = 1000;
if (this.props.latestQuery && this.props.latestQuery.rows === rowLimit) {
Expand Down Expand Up @@ -256,7 +243,6 @@ class SqlEditor extends React.Component {
<div className="pull-right">
{limitWarning}
<Timer query={this.props.latestQuery} />
{rightButtons}
</div>
</div>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,12 @@ class SqlEditorTopToolbar extends React.Component {
}
fetchDatabaseOptions() {
this.setState({ databaseLoading: true });
const url = '/databaseasync/api/read?_flt_0_expose_in_sqllab=1';
const url = (
'/databaseasync/api/read?' +
'_flt_0_expose_in_sqllab=1&' +
'_oc_DatabaseAsync=database_name&' +
'_od_DatabaseAsync=asc'
);
$.get(url, (data) => {
const options = data.result.map((db) => ({ value: db.id, label: db.database_name }));
this.props.actions.setDatabases(data.result);
Expand Down
16 changes: 8 additions & 8 deletions caravel/assets/javascripts/SqlLab/components/TableElement.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -63,12 +63,12 @@ class TableElement extends React.Component {
metadata = (
<div>
{this.props.table.columns.map((col) => (
<div className="row">
<div className="col-sm-8">
<div className="m-l-5">{col.name}</div>
<div className="clearfix">
<div className="pull-left m-l-10">
{col.name}
</div>
<div className="col-sm-4">
<div className="pull-right text-muted"><small>{col.type}</small></div>
<div className="pull-right text-muted">
<small> {col.type}</small>
</div>
</div>
))}
Expand All @@ -88,11 +88,11 @@ class TableElement extends React.Component {
}
return (
<div>
<div className="row">
<div className="col-sm-9 m-b-10">
<div className="clearfix">
<div className="pull-left">
{buttonToggle}
</div>
<div className="col-sm-3">
<div className="pull-right">
<ButtonGroup className="ws-el-controls pull-right">
<Link
className="fa fa-pencil pull-left m-l-2"
Expand Down
4 changes: 2 additions & 2 deletions caravel/assets/javascripts/SqlLab/components/Timer.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@ class Timer extends React.Component {
}
stopwatch() {
if (this.props && this.props.query) {
const since = this.props.query.endDttm || now();
const clockStr = fDuration(this.props.query.startDttm, since);
const endDttm = this.props.query.endDttm || now();
const clockStr = fDuration(this.props.query.startDttm, endDttm);
this.setState({ clockStr });
if (this.props.query.state !== 'running') {
this.stopTimer();
Expand Down
3 changes: 0 additions & 3 deletions caravel/assets/javascripts/SqlLab/main.css
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,3 @@ div.tablePopover:hover {
.SouthPane .tab-content {
padding-top: 10px;
}
.SqlEditor textarea {
display: none;
}
52 changes: 37 additions & 15 deletions caravel/assets/javascripts/SqlLab/reducers.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ function addToObject(state, arrKey, obj) {

function alterInObject(state, arrKey, obj, alterations) {
const newObject = Object.assign({}, state[arrKey]);
newObject[obj.id] = (Object.assign({}, newObject[obj.id], alterations));
newObject[obj.id] = Object.assign({}, newObject[obj.id], alterations);
return Object.assign({}, state, { [arrKey]: newObject });
}

Expand Down Expand Up @@ -65,6 +65,16 @@ function removeFromArr(state, arrKey, obj, idKey = 'id') {
return Object.assign({}, state, { [arrKey]: newArr });
}

function getFromArr(arr, id) {
Copy link

Choose a reason for hiding this comment

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

this might be more clear as getObjectByIdFromArray

let obj;
arr.forEach((o) => {
if (o.id === id) {
obj = o;
}
});
return obj;
}

function addToArr(state, arrKey, obj) {
const newObj = Object.assign({}, obj);
if (!newObj.id) {
Expand All @@ -87,9 +97,16 @@ export const sqlLabReducer = function (state, action) {
let newState = removeFromArr(state, 'queryEditors', action.queryEditor);
// List of remaining queryEditor ids
const qeIds = newState.queryEditors.map((qe) => qe.id);
let th = state.tabHistory.slice();
th = th.filter((id) => qeIds.includes(id));
newState = Object.assign({}, newState, { tabHistory: th });
const queries = {};
Object.keys(state.queries).forEach((k) => {
const query = state.queries[k];
if (qeIds.includes(query.sqlEditorId)) {
queries[k] = query;
}
});
let tabHistory = state.tabHistory.slice();
tabHistory = tabHistory.filter((id) => qeIds.includes(id));
newState = Object.assign({}, newState, { tabHistory, queries });
return newState;
},
[actions.REMOVE_QUERY]() {
Expand All @@ -113,20 +130,31 @@ export const sqlLabReducer = function (state, action) {
return removeFromArr(state, 'tables', action.table);
},
[actions.START_QUERY]() {
const newState = addToObject(state, 'queries', action.query);
const qe = getFromArr(state.queryEditors, action.query.sqlEditorId);
let newState = Object.assign({}, state);
if (qe.latestQueryId) {
const q = Object.assign({}, state.queries[qe.latestQueryId], { results: null });
const queries = Object.assign({}, state.queries, { [q.id]: q });
newState = Object.assign({}, state, { queries });
}
newState = addToObject(newState, 'queries', action.query);
const sqlEditor = { id: action.query.sqlEditorId };
return alterInArr(newState, 'queryEditors', sqlEditor, { latestQueryId: action.query.id });
},
[actions.STOP_QUERY]() {
return alterInObject(state, 'queries', action.query, { state: 'stopped' });
},
[actions.QUERY_SUCCESS]() {
let rows;
if (action.results.data) {
rows = action.results.data.length;
}
const alts = {
state: 'success',
results: action.results,
rows: action.results.data.length,
progress: 100,
endDttm: now(),
progress: 100,
results: action.results,
rows,
state: 'success',
};
return alterInObject(state, 'queries', action.query, alts);
},
Expand Down Expand Up @@ -158,12 +186,6 @@ export const sqlLabReducer = function (state, action) {
[actions.QUERY_EDITOR_SET_AUTORUN]() {
return alterInArr(state, 'queryEditors', action.queryEditor, { autorun: action.autorun });
},
[actions.ADD_WORKSPACE_QUERY]() {
return addToArr(state, 'workspaceQueries', action.query);
},
[actions.REMOVE_WORKSPACE_QUERY]() {
return removeFromArr(state, 'workspaceQueries', action.query);
},
[actions.ADD_ALERT]() {
return addToArr(state, 'alerts', action.alert);
},
Expand Down
1 change: 0 additions & 1 deletion caravel/assets/visualizations/nvd3_vis.css
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ g.caravel path {
}

text.nv-axislabel {
// font-weight: bold;
font-size: 14px;
}

Expand Down
3 changes: 3 additions & 0 deletions caravel/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,9 @@ class CeleryConfig(object):
# The db id here results in selecting this one as a default in SQL Lab
DEFAULT_DB_ID = None

# Timeout duration for SQL Lab synchronous queries
Copy link
Member

Choose a reason for hiding this comment

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

s/Timeout duration for SQL Lab synchronous queries/Timeout duration for SQL Lab synchronous queries in seconds.

SQLLAB_TIMEOUT = 30

try:
from caravel_config import * # noqa
except ImportError:
Expand Down
28 changes: 28 additions & 0 deletions caravel/migrations/versions/4500485bde7d_allow_run_sync_async.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
"""allow_run_sync_async

Revision ID: 4500485bde7d
Revises: 41f6a59a61f2
Create Date: 2016-09-12 23:33:14.789632

"""

# revision identifiers, used by Alembic.
revision = '4500485bde7d'
down_revision = '41f6a59a61f2'

from alembic import op
import sqlalchemy as sa


def upgrade():
op.add_column('dbs', sa.Column('allow_run_async', sa.Boolean(), nullable=True))
op.add_column('dbs', sa.Column('allow_run_sync', sa.Boolean(), nullable=True))


def downgrade():
try:
op.drop_column('dbs', 'allow_run_sync')
op.drop_column('dbs', 'allow_run_async')
except Exception:
pass

Loading