Skip to content

Commit

Permalink
Put data preview in south pane (#1486)
Browse files Browse the repository at this point in the history
* Put data preview in south pane

Before: data preview of a selected table appears as a modal, but for
some cases users may want to view data and edit sql at the same time

After:
 - data preview of a selected table pops up a new tab in South Pane
 - data are saved to local state and flushed in global store in
   ResultSet component

* Moved dataPreviewId to table object

* Put back preview icon for fetching preview data

* Revert "Put back preview icon for fetching preview data"

This reverts commit b6f5dcf.

* Added option to retrieve preview results after refresh
  • Loading branch information
vera-liu committed Nov 4, 2016
1 parent 757e7de commit 69f0a4e
Show file tree
Hide file tree
Showing 10 changed files with 206 additions and 57 deletions.
59 changes: 50 additions & 9 deletions caravel/assets/javascripts/SqlLab/actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export const QUERY_EDITOR_SET_SQL = 'QUERY_EDITOR_SET_SQL';
export const QUERY_EDITOR_SET_SELECTED_TEXT = 'QUERY_EDITOR_SET_SELECTED_TEXT';
export const SET_DATABASES = 'SET_DATABASES';
export const SET_ACTIVE_QUERY_EDITOR = 'SET_ACTIVE_QUERY_EDITOR';
export const SET_ACTIVE_SOUTHPANE_TAB = 'SET_ACTIVE_SOUTHPANE_TAB';
export const ADD_ALERT = 'ADD_ALERT';
export const REMOVE_ALERT = 'REMOVE_ALERT';
export const REFRESH_QUERIES = 'REFRESH_QUERIES';
Expand All @@ -31,18 +32,20 @@ export const REQUEST_QUERY_RESULTS = 'REQUEST_QUERY_RESULTS';
export const QUERY_SUCCESS = 'QUERY_SUCCESS';
export const QUERY_FAILED = 'QUERY_FAILED';
export const CLEAR_QUERY_RESULTS = 'CLEAR_QUERY_RESULTS';
export const HIDE_DATA_PREVIEW = 'HIDE_DATA_PREVIEW';
export const REMOVE_DATA_PREVIEW = 'REMOVE_DATA_PREVIEW';
export const CHANGE_DATA_PREVIEW_ID = 'CHANGE_DATA_PREVIEW_ID';

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

export function startQuery(query) {
Object.assign(query, {
id: shortid.generate(),
id: query.id ? query.id : shortid.generate(),
progress: 0,
startDttm: now(),
state: (query.runAsync) ? 'pending' : 'running',
cached: false,
});
return { type: START_QUERY, query };
}
Expand All @@ -63,8 +66,8 @@ export function clearQueryResults(query) {
return { type: CLEAR_QUERY_RESULTS, query };
}

export function hideDataPreview() {
return { type: HIDE_DATA_PREVIEW };
export function removeDataPreview(table) {
return { type: REMOVE_DATA_PREVIEW, table };
}

export function requestQueryResults(query) {
Expand Down Expand Up @@ -166,6 +169,10 @@ export function setActiveQueryEditor(queryEditor) {
return { type: SET_ACTIVE_QUERY_EDITOR, queryEditor };
}

export function setActiveSouthPaneTab(tabId) {
return { type: SET_ACTIVE_SOUTHPANE_TAB, tabId };
}

export function removeQueryEditor(queryEditor) {
return { type: REMOVE_QUERY_EDITOR, queryEditor };
}
Expand Down Expand Up @@ -198,22 +205,35 @@ export function queryEditorSetSelectedText(queryEditor, sql) {
return { type: QUERY_EDITOR_SET_SELECTED_TEXT, queryEditor, sql };
}

export function mergeTable(table) {
return { type: MERGE_TABLE, table };
export function mergeTable(table, query) {
return { type: MERGE_TABLE, table, query };
}

export function addTable(query, tableName) {
return function (dispatch) {
let url = `/caravel/table/${query.dbId}/${tableName}/${query.schema}/`;
$.get(url, (data) => {
dispatch(
mergeTable(Object.assign(data, {
const dataPreviewQuery = {
id: shortid.generate(),
dbId: query.dbId,
sql: data.selectStar,
tableName,
sqlEditorId: null,
tab: '',
runAsync: false,
ctas: false,
};
// Merge table to tables in state
dispatch(mergeTable(
Object.assign(data, {
dbId: query.dbId,
queryEditorId: query.id,
schema: query.schema,
expanded: true,
}))
}), dataPreviewQuery)
);
// Run query to get preview data for table
dispatch(runQuery(dataPreviewQuery));
})
.fail(() => {
dispatch(
Expand All @@ -238,6 +258,27 @@ export function addTable(query, tableName) {
};
}

export function changeDataPreviewId(oldQueryId, newQuery) {
return { type: CHANGE_DATA_PREVIEW_ID, oldQueryId, newQuery };
}

export function reFetchQueryResults(query) {
return function (dispatch) {
const newQuery = {
id: shortid.generate(),
dbId: query.dbId,
sql: query.sql,
tableName: query.tableName,
sqlEditorId: null,
tab: '',
runAsync: false,
ctas: false,
};
dispatch(runQuery(newQuery));
dispatch(changeDataPreviewId(query.id, newQuery));
};
}

export function expandTable(table) {
return { type: EXPAND_TABLE, table };
}
Expand Down
2 changes: 0 additions & 2 deletions caravel/assets/javascripts/SqlLab/components/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import TabbedSqlEditors from './TabbedSqlEditors';
import QueryAutoRefresh from './QueryAutoRefresh';
import QuerySearch from './QuerySearch';
import Alerts from './Alerts';
import DataPreviewModal from './DataPreviewModal';

import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
Expand Down Expand Up @@ -49,7 +48,6 @@ class App extends React.PureComponent {
return (
<div className="App SqlLab">
<Alerts alerts={this.props.alerts} actions={this.props.actions} />
<DataPreviewModal />
<div className="container-fluid">
{content}
</div>
Expand Down
31 changes: 30 additions & 1 deletion caravel/assets/javascripts/SqlLab/components/ResultSet.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,20 @@ class ResultSet extends React.PureComponent {
this.state = {
searchText: '',
showModal: false,
results: null,
};
}
componentWillReceiveProps(nextProps) {
// when new results comes in, save them locally and clear in store
if ((!nextProps.query.cached)
&& nextProps.query.results
&& nextProps.query.results.data.length > 0) {
this.setState(
{ results: nextProps.query.results },
this.clearQueryResults(nextProps.query)
);
}
}
getControls() {
if (this.props.search || this.props.visualize || this.props.csv) {
let csvButton;
Expand Down Expand Up @@ -83,6 +95,9 @@ class ResultSet extends React.PureComponent {
}
return <div className="noControls" />;
}
clearQueryResults(query) {
this.props.actions.clearQueryResults(query);
}
popSelectStar() {
const qe = {
id: shortid.generate(),
Expand All @@ -105,10 +120,14 @@ class ResultSet extends React.PureComponent {
fetchResults(query) {
this.props.actions.fetchQueryResults(query);
}
reFetchQueryResults(query) {
this.props.actions.reFetchQueryResults(query);
}
render() {
const query = this.props.query;
const results = query.results;
const results = (this.props.query.cached) ? this.state.results : query.results;
let sql;

if (this.props.showSql) {
sql = <HighlightedSql sql={query.sql} />;
}
Expand Down Expand Up @@ -181,6 +200,16 @@ class ResultSet extends React.PureComponent {
);
}
}
if (query.cached) {
return (
<a
href="#"
onClick={this.reFetchQueryResults.bind(this, query)}
>
click to retrieve results
</a>
);
}
return (<Alert bsStyle="warning">The query returned no data</Alert>);
}
}
Expand Down
75 changes: 65 additions & 10 deletions caravel/assets/javascripts/SqlLab/components/SouthPane.jsx
Original file line number Diff line number Diff line change
@@ -1,25 +1,43 @@
import { Alert, Tab, Tabs } from 'react-bootstrap';
import QueryHistory from './QueryHistory';
import ResultSet from './ResultSet';
import { areArraysShallowEqual } from '../../reduxUtils';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import * as Actions from '../actions';
import React from 'react';
import { areArraysShallowEqual } from '../../reduxUtils';

import shortid from 'shortid';

/*
editorQueries are queries executed by users passed from SqlEditor component
dataPrebiewQueries are all queries executed for preview of table data (from SqlEditorLeft)
*/
const propTypes = {
queries: React.PropTypes.array.isRequired,
editorQueries: React.PropTypes.array.isRequired,
dataPreviewQueries: React.PropTypes.array.isRequired,
actions: React.PropTypes.object.isRequired,
activeSouthPaneTab: React.PropTypes.string,
};

const defaultProps = {
activeSouthPaneTab: 'Results',
};

class SouthPane extends React.PureComponent {
switchTab(id) {
this.props.actions.setActiveSouthPaneTab(id);
}
shouldComponentUpdate(nextProps) {
return !areArraysShallowEqual(this.props.queries, nextProps.queries);
return !areArraysShallowEqual(this.props.editorQueries, nextProps.editorQueries)
|| !areArraysShallowEqual(this.props.dataPreviewQueries, nextProps.dataPreviewQueries)
|| this.props.activeSouthPaneTab !== nextProps.activeSouthPaneTab;
}
render() {
let latestQuery;
const props = this.props;
if (props.queries.length > 0) {
latestQuery = props.queries[props.queries.length - 1];
if (props.editorQueries.length > 0) {
latestQuery = props.editorQueries[props.editorQueries.length - 1];
}
let results;
if (latestQuery) {
Expand All @@ -29,22 +47,59 @@ class SouthPane extends React.PureComponent {
} else {
results = <Alert bsStyle="info">Run a query to display results here</Alert>;
}

const dataPreviewTabs = props.dataPreviewQueries.map((query) => (
<Tab
title={`Preview for ${query.tableName}`}
eventKey={query.id}
key={query.id}
>
<ResultSet query={query} visualize={false} csv={false} actions={props.actions} />
</Tab>
));

return (
<div className="SouthPane">
<Tabs bsStyle="tabs" id={shortid.generate()}>
<Tab title="Results" eventKey={1}>
<Tabs
bsStyle="tabs"
id={shortid.generate()}
activeKey={this.props.activeSouthPaneTab}
onSelect={this.switchTab.bind(this)}
>
<Tab
title="Results"
eventKey="Results"
>
<div style={{ overflow: 'auto' }}>
{results}
</div>
</Tab>
<Tab title="Query History" eventKey={2}>
<QueryHistory queries={props.queries} actions={props.actions} />
<Tab
title="Query History"
eventKey="History"
>
<QueryHistory queries={props.editorQueries} actions={props.actions} />
</Tab>
{dataPreviewTabs}
</Tabs>
</div>
);
}
}

function mapStateToProps(state) {
return {
activeSouthPaneTab: state.activeSouthPaneTab,
};
}

function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators(Actions, dispatch),
};
}

SouthPane.propTypes = propTypes;
SouthPane.defaultProps = defaultProps;

export default SouthPane;
export default connect(mapStateToProps, mapDispatchToProps)(SouthPane);
7 changes: 5 additions & 2 deletions caravel/assets/javascripts/SqlLab/components/SqlEditor.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ const propTypes = {
latestQuery: React.PropTypes.object,
networkOn: React.PropTypes.bool,
tables: React.PropTypes.array.isRequired,
queries: React.PropTypes.array.isRequired,
editorQueries: React.PropTypes.array.isRequired,
dataPreviewQueries: React.PropTypes.array.isRequired,
queryEditor: React.PropTypes.object.isRequired,
};

Expand Down Expand Up @@ -70,6 +71,7 @@ class SqlEditor extends React.PureComponent {
ctas,
};
this.props.actions.runQuery(query);
this.props.actions.setActiveSouthPaneTab('Results');
}
stopQuery() {
this.props.actions.stopQuery(this.props.latestQuery);
Expand Down Expand Up @@ -224,7 +226,8 @@ class SqlEditor extends React.PureComponent {
{editorBottomBar}
<br />
<SouthPane
queries={this.props.queries}
editorQueries={this.props.editorQueries}
dataPreviewQueries={this.props.dataPreviewQueries}
actions={this.props.actions}
/>
</Col>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,15 @@ class TabbedSqlEditors extends React.PureComponent {
database = this.props.databases[qe.dbId];
}
const state = (latestQuery) ? latestQuery.state : '';

const dataPreviewQueries = [];
this.props.tables.forEach((table) => {
const queryId = table.dataPreviewQueryId;
if (queryId && this.props.queries[queryId]) {
dataPreviewQueries.push(this.props.queries[queryId]);
}
});

const tabTitle = (
<div>
<div className={'circle ' + state} /> {qe.title} {' '}
Expand Down Expand Up @@ -152,7 +161,8 @@ class TabbedSqlEditors extends React.PureComponent {
<SqlEditor
tables={this.props.tables.filter((t) => (t.queryEditorId === qe.id))}
queryEditor={qe}
queries={this.state.queriesArray}
editorQueries={this.state.queriesArray}
dataPreviewQueries={dataPreviewQueries}
latestQuery={latestQuery}
database={database}
actions={this.props.actions}
Expand Down
Loading

0 comments on commit 69f0a4e

Please sign in to comment.