[sqllab] some frontend tests (#1400)
* [sqllab] some frontend tests * linting * Addressing comments * Addressing unaddressed comments * Touchups
This commit is contained in:
parent
7c5933732b
commit
940659bc14
|
|
@ -1,4 +1,5 @@
|
|||
*.pyc
|
||||
caravel/assets/coverage/*
|
||||
changelog.sh
|
||||
.DS_Store
|
||||
.coverage
|
||||
|
|
|
|||
|
|
@ -5,6 +5,5 @@ dist/*
|
|||
images/*
|
||||
node_modules/*
|
||||
node_modules*/*
|
||||
spec/*
|
||||
stylesheets/*
|
||||
vendor/*
|
||||
|
|
|
|||
|
|
@ -18,8 +18,6 @@ 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_DATABASES = 'SET_DATABASES';
|
||||
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';
|
||||
|
|
@ -141,7 +139,8 @@ export function setDatabases(databases) {
|
|||
}
|
||||
|
||||
export function addQueryEditor(queryEditor) {
|
||||
return { type: ADD_QUERY_EDITOR, queryEditor };
|
||||
const newQe = Object.assign({}, queryEditor, { id: shortid.generate() });
|
||||
return { type: ADD_QUERY_EDITOR, queryEditor: newQe };
|
||||
}
|
||||
|
||||
export function cloneQueryToNewTab(query) {
|
||||
|
|
@ -153,7 +152,9 @@ export function setNetworkStatus(networkOn) {
|
|||
}
|
||||
|
||||
export function addAlert(alert) {
|
||||
return { type: ADD_ALERT, alert };
|
||||
const o = Object.assign({}, alert);
|
||||
o.id = shortid.generate();
|
||||
return { type: ADD_ALERT, o };
|
||||
}
|
||||
|
||||
export function removeAlert(alert) {
|
||||
|
|
@ -208,13 +209,6 @@ export function removeTable(table) {
|
|||
return { type: REMOVE_TABLE, table };
|
||||
}
|
||||
|
||||
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 };
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,5 @@
|
|||
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) {
|
||||
|
|
@ -11,6 +8,7 @@ class Alerts extends React.Component {
|
|||
render() {
|
||||
const alerts = this.props.alerts.map((alert) =>
|
||||
<Alert
|
||||
key={alert.id}
|
||||
bsStyle={alert.bsStyle}
|
||||
style={{ width: '500px', textAlign: 'midddle', margin: '10px auto' }}
|
||||
>
|
||||
|
|
@ -33,9 +31,4 @@ Alerts.propTypes = {
|
|||
actions: React.PropTypes.object,
|
||||
};
|
||||
|
||||
function mapDispatchToProps(dispatch) {
|
||||
return {
|
||||
actions: bindActionCreators(Actions, dispatch),
|
||||
};
|
||||
}
|
||||
export default connect(null, mapDispatchToProps)(Alerts);
|
||||
export default Alerts;
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ class App extends React.Component {
|
|||
}
|
||||
return (
|
||||
<div className="App SqlLab">
|
||||
<Alerts alerts={this.props.alerts} />
|
||||
<Alerts alerts={this.props.alerts} actions={this.props.actions} />
|
||||
<DataPreviewModal />
|
||||
<div className="container-fluid">
|
||||
{content}
|
||||
|
|
@ -60,6 +60,7 @@ class App extends React.Component {
|
|||
|
||||
App.propTypes = {
|
||||
alerts: React.PropTypes.array,
|
||||
actions: React.PropTypes.object,
|
||||
};
|
||||
|
||||
function mapStateToProps(state) {
|
||||
|
|
@ -73,4 +74,5 @@ function mapDispatchToProps(dispatch) {
|
|||
};
|
||||
}
|
||||
|
||||
export { App };
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(App);
|
||||
|
|
|
|||
|
|
@ -1,48 +0,0 @@
|
|||
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;
|
||||
|
|
@ -3,11 +3,11 @@ import CopyToClipboard from '../../components/CopyToClipboard';
|
|||
import { getShortUrl } from '../../../utils/common';
|
||||
|
||||
const propTypes = {
|
||||
qe: React.PropTypes.object,
|
||||
queryEditor: React.PropTypes.object,
|
||||
};
|
||||
|
||||
const defaultProps = {
|
||||
qe: null,
|
||||
queryEditor: null,
|
||||
};
|
||||
|
||||
export default class CopyQueryTabUrl extends React.Component {
|
||||
|
|
@ -20,7 +20,7 @@ export default class CopyQueryTabUrl extends React.Component {
|
|||
|
||||
componentWillMount() {
|
||||
const params = [];
|
||||
const qe = this.props.qe;
|
||||
const qe = this.props.queryEditor;
|
||||
if (qe.dbId) params.push('dbid=' + qe.dbId);
|
||||
if (qe.title) params.push('title=' + encodeURIComponent(qe.title));
|
||||
if (qe.schema) params.push('schema=' + encodeURIComponent(qe.schema));
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ class DataPreviewModal extends React.Component {
|
|||
</Modal.Title>
|
||||
</Modal.Header>
|
||||
<Modal.Body>
|
||||
<ResultSet query={query} visualize={false} csv={false} />
|
||||
<ResultSet query={query} visualize={false} csv={false} actions={this.props.actions} />
|
||||
</Modal.Body>
|
||||
</Modal>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,9 +1,6 @@
|
|||
const $ = window.$ = require('jquery');
|
||||
import React from 'react';
|
||||
import { bindActionCreators } from 'redux';
|
||||
import Select from 'react-select';
|
||||
import { connect } from 'react-redux';
|
||||
import * as Actions from '../actions';
|
||||
|
||||
class DatabaseSelect extends React.Component {
|
||||
constructor(props) {
|
||||
|
|
@ -53,15 +50,4 @@ DatabaseSelect.propTypes = {
|
|||
valueRenderer: React.PropTypes.func,
|
||||
};
|
||||
|
||||
DatabaseSelect.defaultProps = {
|
||||
onChange: () => {},
|
||||
databaseId: null,
|
||||
};
|
||||
|
||||
function mapDispatchToProps(dispatch) {
|
||||
return {
|
||||
actions: bindActionCreators(Actions, dispatch),
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(null, mapDispatchToProps)(DatabaseSelect);
|
||||
export default DatabaseSelect;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,24 @@
|
|||
import React from 'react';
|
||||
import { OverlayTrigger, Tooltip } from 'react-bootstrap';
|
||||
|
||||
const propTypes = {
|
||||
children: React.PropTypes.node,
|
||||
className: React.PropTypes.string,
|
||||
href: React.PropTypes.string,
|
||||
onClick: React.PropTypes.func,
|
||||
placement: React.PropTypes.string,
|
||||
style: React.PropTypes.object,
|
||||
tooltip: React.PropTypes.string,
|
||||
};
|
||||
const defaultProps = {
|
||||
className: '',
|
||||
href: '#',
|
||||
onClick: () => {},
|
||||
placement: 'top',
|
||||
style: {},
|
||||
tooltip: null,
|
||||
};
|
||||
|
||||
|
||||
class Link extends React.Component {
|
||||
render() {
|
||||
|
|
@ -34,21 +52,7 @@ class Link extends React.Component {
|
|||
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: () => {},
|
||||
};
|
||||
Link.propTypes = propTypes;
|
||||
Link.defaultProps = defaultProps;
|
||||
|
||||
export default Link;
|
||||
|
|
|
|||
|
|
@ -30,14 +30,10 @@ class QueryAutoRefresh extends React.Component {
|
|||
if (Object.keys(data).length > 0) {
|
||||
this.props.actions.refreshQueries(data);
|
||||
}
|
||||
if (!this.props.networkOn) {
|
||||
this.props.actions.setNetworkStatus(true);
|
||||
}
|
||||
this.props.actions.setNetworkStatus(true);
|
||||
})
|
||||
.fail(() => {
|
||||
if (this.props.networkOn) {
|
||||
this.props.actions.setNetworkStatus(false);
|
||||
}
|
||||
this.props.actions.setNetworkStatus(false);
|
||||
});
|
||||
}
|
||||
render() {
|
||||
|
|
@ -47,7 +43,6 @@ class QueryAutoRefresh extends React.Component {
|
|||
QueryAutoRefresh.propTypes = {
|
||||
actions: React.PropTypes.object,
|
||||
queriesLastUpdate: React.PropTypes.number,
|
||||
networkOn: React.PropTypes.bool,
|
||||
};
|
||||
QueryAutoRefresh.defaultProps = {
|
||||
// queries: null,
|
||||
|
|
@ -56,7 +51,6 @@ QueryAutoRefresh.defaultProps = {
|
|||
function mapStateToProps(state) {
|
||||
return {
|
||||
queriesLastUpdate: state.queriesLastUpdate,
|
||||
networkOn: state.networkOn,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,28 +1,23 @@
|
|||
import React from 'react';
|
||||
|
||||
import { connect } from 'react-redux';
|
||||
import { bindActionCreators } from 'redux';
|
||||
import * as Actions from '../actions';
|
||||
|
||||
import QueryTable from './QueryTable';
|
||||
import { Alert } from 'react-bootstrap';
|
||||
|
||||
const propTypes = {
|
||||
queries: React.PropTypes.array.isRequired,
|
||||
actions: React.PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
const QueryHistory = (props) => {
|
||||
const activeQeId = props.tabHistory[props.tabHistory.length - 1];
|
||||
const queriesArray = [];
|
||||
for (const id in props.queries) {
|
||||
if (props.queries[id].sqlEditorId === activeQeId) {
|
||||
queriesArray.push(props.queries[id]);
|
||||
}
|
||||
}
|
||||
if (queriesArray.length > 0) {
|
||||
if (props.queries.length > 0) {
|
||||
return (
|
||||
<QueryTable
|
||||
columns={[
|
||||
'state', 'started', 'duration', 'progress',
|
||||
'rows', 'sql', 'output', 'actions',
|
||||
]}
|
||||
queries={queriesArray}
|
||||
queries={props.queries}
|
||||
actions={props.actions}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
@ -32,27 +27,6 @@ const QueryHistory = (props) => {
|
|||
</Alert>
|
||||
);
|
||||
};
|
||||
QueryHistory.propTypes = propTypes;
|
||||
|
||||
QueryHistory.defaultProps = {
|
||||
queries: {},
|
||||
};
|
||||
|
||||
QueryHistory.propTypes = {
|
||||
queries: React.PropTypes.object,
|
||||
tabHistory: React.PropTypes.array,
|
||||
actions: React.PropTypes.object,
|
||||
};
|
||||
|
||||
function mapStateToProps(state) {
|
||||
return {
|
||||
queries: state.queries,
|
||||
tabHistory: state.tabHistory,
|
||||
};
|
||||
}
|
||||
function mapDispatchToProps(dispatch) {
|
||||
return {
|
||||
actions: bindActionCreators(Actions, dispatch),
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(QueryHistory);
|
||||
export default QueryHistory;
|
||||
|
|
|
|||
|
|
@ -1,62 +0,0 @@
|
|||
import React from 'react';
|
||||
import Link from './Link';
|
||||
import { connect } from 'react-redux';
|
||||
import { bindActionCreators } from 'redux';
|
||||
import * as Actions from '../actions';
|
||||
import shortid from 'shortid';
|
||||
|
||||
class QueryLink extends React.Component {
|
||||
popTab() {
|
||||
const qe = {
|
||||
id: shortid.generate(),
|
||||
title: this.props.query.title,
|
||||
dbId: this.props.query.dbId,
|
||||
autorun: false,
|
||||
sql: this.props.query.sql,
|
||||
};
|
||||
this.props.actions.addQueryEditor(qe);
|
||||
}
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<div className="clearfix">
|
||||
<div className="pull-left">
|
||||
<a
|
||||
href="#"
|
||||
tooltip="Pop this query in a new tab"
|
||||
onClick={this.popTab.bind(this)}
|
||||
>
|
||||
{this.props.query.title}
|
||||
</a>
|
||||
</div>
|
||||
<div className="pull-right">
|
||||
<Link
|
||||
onClick={this.props.actions.removeWorkspaceQuery.bind(this, this.props.query)}
|
||||
tooltip="Remove query from workspace"
|
||||
href="#"
|
||||
>
|
||||
×
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
<hr />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
QueryLink.propTypes = {
|
||||
query: React.PropTypes.object,
|
||||
actions: React.PropTypes.object,
|
||||
};
|
||||
|
||||
QueryLink.defaultProps = {
|
||||
};
|
||||
|
||||
function mapDispatchToProps(dispatch) {
|
||||
return {
|
||||
actions: bindActionCreators(Actions, dispatch),
|
||||
};
|
||||
}
|
||||
export default connect(null, mapDispatchToProps)(QueryLink);
|
||||
|
||||
|
|
@ -7,6 +7,10 @@ import QueryTable from './QueryTable';
|
|||
import DatabaseSelect from './DatabaseSelect';
|
||||
import { STATUS_OPTIONS } from '../common';
|
||||
|
||||
const propTypes = {
|
||||
actions: React.PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
class QuerySearch extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
|
@ -132,11 +136,11 @@ class QuerySearch extends React.Component {
|
|||
onUserClicked={this.onUserClicked.bind(this)}
|
||||
onDbClicked={this.onDbClicked.bind(this)}
|
||||
queries={this.state.queriesArray}
|
||||
actions={this.props.actions}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
QuerySearch.propTypes = propTypes;
|
||||
export default QuerySearch;
|
||||
|
|
|
|||
|
|
@ -1,9 +1,5 @@
|
|||
import React from 'react';
|
||||
|
||||
import { connect } from 'react-redux';
|
||||
import { bindActionCreators } from 'redux';
|
||||
import * as Actions from '../actions';
|
||||
|
||||
import moment from 'moment';
|
||||
import { Table } from 'reactable';
|
||||
import { Label, ProgressBar } from 'react-bootstrap';
|
||||
|
|
@ -67,6 +63,9 @@ class QueryTable extends React.Component {
|
|||
clearQueryResults(query) {
|
||||
this.props.actions.clearQueryResults(query);
|
||||
}
|
||||
removeQuery(query) {
|
||||
this.props.actions.removeQuery(query);
|
||||
}
|
||||
|
||||
render() {
|
||||
const data = this.props.queries.map((query) => {
|
||||
|
|
@ -111,7 +110,7 @@ class QueryTable extends React.Component {
|
|||
modalTitle={'Data preview'}
|
||||
beforeOpen={this.openAsyncResults.bind(this, query)}
|
||||
onExit={this.clearQueryResults.bind(this, query)}
|
||||
modalBody={<ResultSet showSql query={query} />}
|
||||
modalBody={<ResultSet showSql query={query} actions={this.props.actions} />}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
|
|
@ -163,7 +162,7 @@ class QueryTable extends React.Component {
|
|||
<Link
|
||||
className="fa fa-trash m-r-3"
|
||||
tooltip="Remove query from log"
|
||||
onClick={this.props.actions.removeQuery.bind(this, query)}
|
||||
onClick={this.removeQuery.bind(this, query)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
|
@ -198,13 +197,4 @@ class QueryTable extends React.Component {
|
|||
QueryTable.propTypes = propTypes;
|
||||
QueryTable.defaultProps = defaultProps;
|
||||
|
||||
function mapStateToProps() {
|
||||
return {};
|
||||
}
|
||||
function mapDispatchToProps(dispatch) {
|
||||
return {
|
||||
actions: bindActionCreators(Actions, dispatch),
|
||||
};
|
||||
}
|
||||
export { QueryTable };
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(QueryTable);
|
||||
export default QueryTable;
|
||||
|
|
|
|||
|
|
@ -3,10 +3,6 @@ import { Alert, Button, ButtonGroup, ProgressBar } from 'react-bootstrap';
|
|||
import { Table } from 'reactable';
|
||||
import shortid from 'shortid';
|
||||
|
||||
import { connect } from 'react-redux';
|
||||
import { bindActionCreators } from 'redux';
|
||||
import * as Actions from '../actions';
|
||||
|
||||
import VisualizeModal from './VisualizeModal';
|
||||
import HighlightedSql from './HighlightedSql';
|
||||
|
||||
|
|
@ -116,7 +112,7 @@ class ResultSet extends React.Component {
|
|||
if (this.props.showSql) {
|
||||
sql = <HighlightedSql sql={query.sql} />;
|
||||
}
|
||||
if (['running', 'pending', 'fetching'].includes(query.state)) {
|
||||
if (['running', 'pending', 'fetching'].indexOf(query.state) > -1) {
|
||||
let progressBar;
|
||||
if (query.progress > 0 && query.state === 'running') {
|
||||
progressBar = (
|
||||
|
|
@ -191,12 +187,4 @@ class ResultSet extends React.Component {
|
|||
ResultSet.propTypes = propTypes;
|
||||
ResultSet.defaultProps = defaultProps;
|
||||
|
||||
function mapStateToProps() {
|
||||
return {};
|
||||
}
|
||||
function mapDispatchToProps(dispatch) {
|
||||
return {
|
||||
actions: bindActionCreators(Actions, dispatch),
|
||||
};
|
||||
}
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(ResultSet);
|
||||
export default ResultSet;
|
||||
|
|
|
|||
|
|
@ -3,16 +3,21 @@ import QueryHistory from './QueryHistory';
|
|||
import ResultSet from './ResultSet';
|
||||
import React from 'react';
|
||||
|
||||
import { connect } from 'react-redux';
|
||||
import { bindActionCreators } from 'redux';
|
||||
import * as Actions from '../actions';
|
||||
import shortid from 'shortid';
|
||||
|
||||
const propTypes = {
|
||||
queries: React.PropTypes.array.isRequired,
|
||||
actions: React.PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
const SouthPane = function (props) {
|
||||
let results = <div />;
|
||||
const latestQuery = props.latestQuery;
|
||||
let latestQuery;
|
||||
if (props.queries.length > 0) {
|
||||
latestQuery = props.queries[props.queries.length - 1];
|
||||
}
|
||||
let results;
|
||||
if (latestQuery) {
|
||||
results = <ResultSet showControls search query={latestQuery} />;
|
||||
results = <ResultSet showControls search query={latestQuery} actions={props.actions} />;
|
||||
} else {
|
||||
results = <Alert bsStyle="info">Run a query to display results here</Alert>;
|
||||
}
|
||||
|
|
@ -25,24 +30,12 @@ const SouthPane = function (props) {
|
|||
</div>
|
||||
</Tab>
|
||||
<Tab title="Query History" eventKey={2}>
|
||||
<QueryHistory />
|
||||
<QueryHistory queries={props.queries} actions={props.actions} />
|
||||
</Tab>
|
||||
</Tabs>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
SouthPane.propTypes = propTypes;
|
||||
|
||||
SouthPane.propTypes = {
|
||||
latestQuery: React.PropTypes.object,
|
||||
actions: React.PropTypes.object,
|
||||
};
|
||||
|
||||
SouthPane.defaultProps = {
|
||||
};
|
||||
|
||||
function mapDispatchToProps(dispatch) {
|
||||
return {
|
||||
actions: bindActionCreators(Actions, dispatch),
|
||||
};
|
||||
}
|
||||
export default connect(null, mapDispatchToProps)(SouthPane);
|
||||
export default SouthPane;
|
||||
|
|
|
|||
|
|
@ -18,16 +18,29 @@ import 'brace/mode/sql';
|
|||
import 'brace/theme/github';
|
||||
import 'brace/ext/language_tools';
|
||||
|
||||
import { bindActionCreators } from 'redux';
|
||||
import { connect } from 'react-redux';
|
||||
import * as Actions from '../actions';
|
||||
|
||||
import shortid from 'shortid';
|
||||
import SouthPane from './SouthPane';
|
||||
import Timer from './Timer';
|
||||
|
||||
import SqlEditorLeftBar from './SqlEditorLeftBar';
|
||||
|
||||
const propTypes = {
|
||||
actions: React.PropTypes.object.isRequired,
|
||||
database: React.PropTypes.object,
|
||||
latestQuery: React.PropTypes.object,
|
||||
networkOn: React.PropTypes.bool,
|
||||
tables: React.PropTypes.array.isRequired,
|
||||
queries: React.PropTypes.array.isRequired,
|
||||
queryEditor: React.PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
const defaultProps = {
|
||||
networkOn: true,
|
||||
database: null,
|
||||
latestQuery: null,
|
||||
};
|
||||
|
||||
|
||||
class SqlEditor extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
|
@ -72,15 +85,6 @@ class SqlEditor extends React.Component {
|
|||
this.setState({ sql: text });
|
||||
this.props.actions.queryEditorSetSql(this.props.queryEditor, text);
|
||||
}
|
||||
addWorkspaceQuery() {
|
||||
this.props.actions.addWorkspaceQuery({
|
||||
id: shortid.generate(),
|
||||
sql: this.state.sql,
|
||||
dbId: this.props.queryEditor.dbId,
|
||||
schema: this.props.queryEditor.schema,
|
||||
title: this.props.queryEditor.title,
|
||||
});
|
||||
}
|
||||
ctasChange() {}
|
||||
visualize() {}
|
||||
ctasChanged(event) {
|
||||
|
|
@ -130,7 +134,9 @@ class SqlEditor extends React.Component {
|
|||
{runButtons}
|
||||
</ButtonGroup>
|
||||
);
|
||||
if (this.props.latestQuery && ['running', 'pending'].includes(this.props.latestQuery.state)) {
|
||||
if (
|
||||
this.props.latestQuery &&
|
||||
['running', 'pending'].indexOf(this.props.latestQuery.state) > -1) {
|
||||
runButtons = (
|
||||
<ButtonGroup bsSize="small" className="inline m-r-5 pull-left">
|
||||
<Button
|
||||
|
|
@ -202,7 +208,12 @@ class SqlEditor extends React.Component {
|
|||
<div className="SqlEditor" style={{ minHeight: this.sqlEditorHeight() }}>
|
||||
<Row>
|
||||
<Col md={3}>
|
||||
<SqlEditorLeftBar queryEditor={this.props.queryEditor} />
|
||||
<SqlEditorLeftBar
|
||||
queryEditor={this.props.queryEditor}
|
||||
tables={this.props.tables}
|
||||
networkOn={this.props.networkOn}
|
||||
actions={this.props.actions}
|
||||
/>
|
||||
</Col>
|
||||
<Col md={9}>
|
||||
<AceEditor
|
||||
|
|
@ -220,32 +231,17 @@ class SqlEditor extends React.Component {
|
|||
/>
|
||||
{editorBottomBar}
|
||||
<br />
|
||||
<SouthPane latestQuery={this.props.latestQuery} sqlEditor={this} />
|
||||
<SouthPane
|
||||
queries={this.props.queries}
|
||||
actions={this.props.actions}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
SqlEditor.defaultProps = defaultProps;
|
||||
SqlEditor.propTypes = propTypes;
|
||||
|
||||
SqlEditor.propTypes = {
|
||||
actions: React.PropTypes.object,
|
||||
database: React.PropTypes.object,
|
||||
latestQuery: React.PropTypes.object,
|
||||
queryEditor: React.PropTypes.object,
|
||||
};
|
||||
|
||||
SqlEditor.defaultProps = {
|
||||
};
|
||||
|
||||
function mapStateToProps() {
|
||||
return {};
|
||||
}
|
||||
|
||||
function mapDispatchToProps(dispatch) {
|
||||
return {
|
||||
actions: bindActionCreators(Actions, dispatch),
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(SqlEditor);
|
||||
export default SqlEditor;
|
||||
|
|
|
|||
|
|
@ -1,14 +1,23 @@
|
|||
const $ = window.$ = require('jquery');
|
||||
import React from 'react';
|
||||
|
||||
import { bindActionCreators } from 'redux';
|
||||
import { connect } from 'react-redux';
|
||||
import * as Actions from '../actions';
|
||||
import Select from 'react-select';
|
||||
import { Label, Button } from 'react-bootstrap';
|
||||
import TableElement from './TableElement';
|
||||
import DatabaseSelect from './DatabaseSelect';
|
||||
|
||||
const propTypes = {
|
||||
queryEditor: React.PropTypes.object.isRequired,
|
||||
tables: React.PropTypes.array,
|
||||
actions: React.PropTypes.object,
|
||||
networkOn: React.PropTypes.bool,
|
||||
};
|
||||
|
||||
const defaultProps = {
|
||||
tables: [],
|
||||
networkOn: true,
|
||||
actions: {},
|
||||
};
|
||||
|
||||
class SqlEditorLeftBar extends React.Component {
|
||||
constructor(props) {
|
||||
|
|
@ -115,7 +124,6 @@ class SqlEditorLeftBar extends React.Component {
|
|||
if (!this.props.networkOn) {
|
||||
networkAlert = <p><Label bsStyle="danger">OFFLINE</Label></p>;
|
||||
}
|
||||
const tables = this.props.tables.filter((t) => (t.queryEditorId === this.props.queryEditor.id));
|
||||
const shouldShowReset = window.location.search === '?reset=1';
|
||||
return (
|
||||
<div className="clearfix sql-toolbar">
|
||||
|
|
@ -124,6 +132,7 @@ class SqlEditorLeftBar extends React.Component {
|
|||
<DatabaseSelect
|
||||
onChange={this.onChange.bind(this)}
|
||||
databaseId={this.props.queryEditor.dbId}
|
||||
actions={this.props.actions}
|
||||
valueRenderer={(o) => (
|
||||
<div>
|
||||
<span className="text-muted">Database:</span> {o.label}
|
||||
|
|
@ -161,8 +170,13 @@ class SqlEditorLeftBar extends React.Component {
|
|||
</div>
|
||||
<hr />
|
||||
<div className="m-t-5">
|
||||
{tables.map((table) => (
|
||||
<TableElement table={table} queryEditor={this.props.queryEditor} key={table.id} />
|
||||
{this.props.tables.map((table) => (
|
||||
<TableElement
|
||||
table={table}
|
||||
queryEditor={this.props.queryEditor}
|
||||
key={table.id}
|
||||
actions={this.props.actions}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
{shouldShowReset &&
|
||||
|
|
@ -174,29 +188,7 @@ class SqlEditorLeftBar extends React.Component {
|
|||
);
|
||||
}
|
||||
}
|
||||
SqlEditorLeftBar.propTypes = propTypes;
|
||||
SqlEditorLeftBar.defaultProps = defaultProps;
|
||||
|
||||
SqlEditorLeftBar.propTypes = {
|
||||
queryEditor: React.PropTypes.object,
|
||||
tables: React.PropTypes.array,
|
||||
actions: React.PropTypes.object,
|
||||
networkOn: React.PropTypes.bool,
|
||||
};
|
||||
|
||||
SqlEditorLeftBar.defaultProps = {
|
||||
tables: [],
|
||||
};
|
||||
|
||||
function mapStateToProps(state) {
|
||||
return {
|
||||
tables: state.tables,
|
||||
networkOn: state.networkOn,
|
||||
};
|
||||
}
|
||||
|
||||
function mapDispatchToProps(dispatch) {
|
||||
return {
|
||||
actions: bindActionCreators(Actions, dispatch),
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(SqlEditorLeftBar);
|
||||
export default SqlEditorLeftBar;
|
||||
|
|
|
|||
|
|
@ -4,20 +4,21 @@ import { connect } from 'react-redux';
|
|||
import { bindActionCreators } from 'redux';
|
||||
import * as Actions from '../actions';
|
||||
import SqlEditor from './SqlEditor';
|
||||
import shortid from 'shortid';
|
||||
import { getParamFromQuery } from '../../../utils/common';
|
||||
import CopyQueryTabUrl from './CopyQueryTabUrl';
|
||||
|
||||
const propTypes = {
|
||||
actions: React.PropTypes.object,
|
||||
databases: React.PropTypes.object,
|
||||
queries: React.PropTypes.object,
|
||||
actions: React.PropTypes.object.isRequired,
|
||||
databases: React.PropTypes.object.isRequired,
|
||||
queries: React.PropTypes.object.isRequired,
|
||||
queryEditors: React.PropTypes.array,
|
||||
tabHistory: React.PropTypes.array,
|
||||
tabHistory: React.PropTypes.array.isRequired,
|
||||
tables: React.PropTypes.array.isRequired,
|
||||
networkOn: React.PropTypes.bool,
|
||||
};
|
||||
const defaultProps = {
|
||||
tabHistory: [],
|
||||
queryEditors: [],
|
||||
networkOn: true,
|
||||
};
|
||||
|
||||
let queryCount = 1;
|
||||
|
|
@ -39,7 +40,6 @@ class TabbedSqlEditors extends React.Component {
|
|||
if (this.state.query) {
|
||||
queryCount++;
|
||||
const queryEditorProps = {
|
||||
id: shortid.generate(),
|
||||
title: getParamFromQuery(this.state.query, 'title'),
|
||||
dbId: parseInt(getParamFromQuery(this.state.query, 'dbid'), 10),
|
||||
schema: getParamFromQuery(this.state.query, 'schema'),
|
||||
|
|
@ -72,7 +72,6 @@ class TabbedSqlEditors extends React.Component {
|
|||
queryCount++;
|
||||
const activeQueryEditor = this.activeQueryEditor();
|
||||
const qe = {
|
||||
id: shortid.generate(),
|
||||
title: `Untitled Query ${queryCount}`,
|
||||
dbId: (activeQueryEditor) ? activeQueryEditor.dbId : null,
|
||||
schema: (activeQueryEditor) ? activeQueryEditor.schema : null,
|
||||
|
|
@ -88,10 +87,26 @@ class TabbedSqlEditors extends React.Component {
|
|||
this.props.actions.setActiveQueryEditor({ id: key });
|
||||
}
|
||||
}
|
||||
removeQueryEditor(qe) {
|
||||
this.props.actions.removeQueryEditor(qe);
|
||||
}
|
||||
render() {
|
||||
const editors = this.props.queryEditors.map((qe, i) => {
|
||||
let latestQuery = this.props.queries[qe.latestQueryId];
|
||||
const database = this.props.databases[qe.dbId];
|
||||
const isSelected = (qe.id === this.activeQueryEditor().id);
|
||||
const queriesArray = [];
|
||||
for (const id in this.props.queries) {
|
||||
if (this.props.queries[id].sqlEditorId === qe.id) {
|
||||
queriesArray.push(this.props.queries[id]);
|
||||
}
|
||||
}
|
||||
let latestQuery;
|
||||
if (qe.latestQueryId) {
|
||||
latestQuery = this.props.queries[qe.latestQueryId];
|
||||
}
|
||||
let database;
|
||||
if (qe.dbId) {
|
||||
database = this.props.databases[qe.dbId];
|
||||
}
|
||||
const state = (latestQuery) ? latestQuery.state : '';
|
||||
const tabTitle = (
|
||||
<div>
|
||||
|
|
@ -101,14 +116,14 @@ class TabbedSqlEditors extends React.Component {
|
|||
id={'ddbtn-tab-' + i}
|
||||
title=""
|
||||
>
|
||||
<MenuItem eventKey="1" onClick={this.props.actions.removeQueryEditor.bind(this, qe)}>
|
||||
<MenuItem eventKey="1" onClick={this.removeQueryEditor.bind(this, qe)}>
|
||||
<i className="fa fa-close" /> close tab
|
||||
</MenuItem>
|
||||
<MenuItem eventKey="2" onClick={this.renameTab.bind(this, qe)}>
|
||||
<i className="fa fa-i-cursor" /> rename tab
|
||||
</MenuItem>
|
||||
<MenuItem eventKey="3">
|
||||
<i className="fa fa-clipboard" /> <CopyQueryTabUrl qe={qe} />
|
||||
<i className="fa fa-clipboard" /> <CopyQueryTabUrl queryEditor={qe} />
|
||||
</MenuItem>
|
||||
</DropdownButton>
|
||||
</div>
|
||||
|
|
@ -121,11 +136,17 @@ class TabbedSqlEditors extends React.Component {
|
|||
>
|
||||
<div className="panel panel-default">
|
||||
<div className="panel-body">
|
||||
<SqlEditor
|
||||
queryEditor={qe}
|
||||
latestQuery={latestQuery}
|
||||
database={database}
|
||||
/>
|
||||
{isSelected &&
|
||||
<SqlEditor
|
||||
tables={this.props.tables.filter((t) => (t.queryEditorId === qe.id))}
|
||||
queryEditor={qe}
|
||||
queries={queriesArray}
|
||||
latestQuery={latestQuery}
|
||||
database={database}
|
||||
actions={this.props.actions}
|
||||
networkOn={this.props.networkOn}
|
||||
/>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</Tab>);
|
||||
|
|
@ -138,7 +159,13 @@ class TabbedSqlEditors extends React.Component {
|
|||
id="a11y-query-editor-tabs"
|
||||
>
|
||||
{editors}
|
||||
<Tab title={<div><i className="fa fa-plus-circle" /> </div>} eventKey="add_tab" />
|
||||
<Tab
|
||||
title={
|
||||
<div>
|
||||
<i className="fa fa-plus-circle" />
|
||||
</div>}
|
||||
eventKey="add_tab"
|
||||
/>
|
||||
</Tabs>
|
||||
);
|
||||
}
|
||||
|
|
@ -152,6 +179,8 @@ function mapStateToProps(state) {
|
|||
queryEditors: state.queryEditors,
|
||||
queries: state.queries,
|
||||
tabHistory: state.tabHistory,
|
||||
networkOn: state.networkOn,
|
||||
tables: state.tables,
|
||||
};
|
||||
}
|
||||
function mapDispatchToProps(dispatch) {
|
||||
|
|
@ -160,4 +189,5 @@ function mapDispatchToProps(dispatch) {
|
|||
};
|
||||
}
|
||||
|
||||
export { TabbedSqlEditors };
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(TabbedSqlEditors);
|
||||
|
|
|
|||
|
|
@ -1,7 +1,4 @@
|
|||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { bindActionCreators } from 'redux';
|
||||
import * as Actions from '../actions';
|
||||
|
||||
import { ButtonGroup, Well } from 'react-bootstrap';
|
||||
import shortid from 'shortid';
|
||||
|
|
@ -187,7 +184,7 @@ class TableElement extends React.Component {
|
|||
<Link
|
||||
className="fa fa-trash pull-left m-l-2"
|
||||
onClick={this.removeTable.bind(this)}
|
||||
tooltip="Remove from workspace"
|
||||
tooltip="Remove from panel"
|
||||
href="#"
|
||||
/>
|
||||
</ButtonGroup>
|
||||
|
|
@ -203,10 +200,4 @@ class TableElement extends React.Component {
|
|||
TableElement.propTypes = propTypes;
|
||||
TableElement.defaultProps = defaultProps;
|
||||
|
||||
function mapDispatchToProps(dispatch) {
|
||||
return {
|
||||
actions: bindActionCreators(Actions, dispatch),
|
||||
};
|
||||
}
|
||||
export default connect(null, mapDispatchToProps)(TableElement);
|
||||
export { TableElement };
|
||||
export default TableElement;
|
||||
|
|
|
|||
|
|
@ -1,23 +0,0 @@
|
|||
import React from 'react';
|
||||
import { BootstrapTable, TableHeaderColumn } from 'react-bootstrap-table';
|
||||
|
||||
const TableMetadata = function (props) {
|
||||
return (
|
||||
<BootstrapTable
|
||||
condensed
|
||||
data={props.table.columns}
|
||||
>
|
||||
<TableHeaderColumn dataField="id" isKey hidden>
|
||||
id
|
||||
</TableHeaderColumn>
|
||||
<TableHeaderColumn dataField="name">Name</TableHeaderColumn>
|
||||
<TableHeaderColumn dataField="type">Type</TableHeaderColumn>
|
||||
</BootstrapTable>
|
||||
);
|
||||
};
|
||||
|
||||
TableMetadata.propTypes = {
|
||||
table: React.PropTypes.object,
|
||||
};
|
||||
|
||||
export default TableMetadata;
|
||||
|
|
@ -1,10 +1,6 @@
|
|||
import React from 'react';
|
||||
import { Alert, Button, Col, Modal } from 'react-bootstrap';
|
||||
|
||||
import { connect } from 'react-redux';
|
||||
import { bindActionCreators } from 'redux';
|
||||
import * as Actions from '../actions';
|
||||
|
||||
import Select from 'react-select';
|
||||
import { Table } from 'reactable';
|
||||
import shortid from 'shortid';
|
||||
|
|
@ -16,6 +12,17 @@ const CHART_TYPES = [
|
|||
{ value: 'bar', label: 'Time Series - Bar Chart', requiresTime: true },
|
||||
];
|
||||
|
||||
const propTypes = {
|
||||
onHide: React.PropTypes.func,
|
||||
query: React.PropTypes.object,
|
||||
show: React.PropTypes.bool,
|
||||
};
|
||||
const defaultProps = {
|
||||
show: false,
|
||||
query: {},
|
||||
onHide: () => {},
|
||||
};
|
||||
|
||||
class VisualizeModal extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
|
@ -34,7 +41,10 @@ class VisualizeModal extends React.Component {
|
|||
this.validate();
|
||||
}
|
||||
setStateFromProps() {
|
||||
if (!this.props.query || !this.props.query.results.columns) {
|
||||
if (
|
||||
!this.props.query ||
|
||||
!this.props.query.results ||
|
||||
!this.props.query.results.columns) {
|
||||
return;
|
||||
}
|
||||
const columns = {};
|
||||
|
|
@ -204,22 +214,7 @@ class VisualizeModal extends React.Component {
|
|||
return modal;
|
||||
}
|
||||
}
|
||||
VisualizeModal.propTypes = {
|
||||
query: React.PropTypes.object,
|
||||
show: React.PropTypes.bool,
|
||||
onHide: React.PropTypes.func,
|
||||
};
|
||||
VisualizeModal.defaultProps = {
|
||||
show: false,
|
||||
onHide: () => {},
|
||||
};
|
||||
VisualizeModal.propTypes = propTypes;
|
||||
VisualizeModal.defaultProps = defaultProps;
|
||||
|
||||
function mapStateToProps() {
|
||||
return {};
|
||||
}
|
||||
function mapDispatchToProps(dispatch) {
|
||||
return {
|
||||
actions: bindActionCreators(Actions, dispatch),
|
||||
};
|
||||
}
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(VisualizeModal);
|
||||
export default VisualizeModal;
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import { now } from '../modules/dates';
|
|||
import { addToObject, alterInObject, alterInArr, removeFromArr, getFromArr, addToArr }
|
||||
from '../reduxUtils.js';
|
||||
|
||||
const defaultQueryEditor = {
|
||||
export const defaultQueryEditor = {
|
||||
id: shortid.generate(),
|
||||
title: 'Untitled Query',
|
||||
sql: 'SELECT *\nFROM\nWHERE',
|
||||
|
|
@ -23,7 +23,6 @@ export const initialState = {
|
|||
queryEditors: [defaultQueryEditor],
|
||||
tabHistory: [defaultQueryEditor.id],
|
||||
tables: [],
|
||||
workspaceQueries: [],
|
||||
queriesLastUpdate: 0,
|
||||
};
|
||||
|
||||
|
|
@ -56,12 +55,12 @@ export const sqlLabReducer = function (state, action) {
|
|||
const queries = {};
|
||||
Object.keys(state.queries).forEach((k) => {
|
||||
const query = state.queries[k];
|
||||
if (qeIds.includes(query.sqlEditorId)) {
|
||||
if (qeIds.indexOf(query.sqlEditorId) > -1) {
|
||||
queries[k] = query;
|
||||
}
|
||||
});
|
||||
let tabHistory = state.tabHistory.slice();
|
||||
tabHistory = tabHistory.filter((id) => qeIds.includes(id));
|
||||
tabHistory = tabHistory.filter((id) => qeIds.indexOf(id) > -1);
|
||||
newState = Object.assign({}, newState, { tabHistory, queries });
|
||||
return newState;
|
||||
},
|
||||
|
|
@ -153,7 +152,7 @@ export const sqlLabReducer = function (state, action) {
|
|||
},
|
||||
[actions.SET_ACTIVE_QUERY_EDITOR]() {
|
||||
const qeIds = state.queryEditors.map((qe) => qe.id);
|
||||
if (qeIds.includes(action.queryEditor.id)) {
|
||||
if (qeIds.indexOf(action.queryEditor.id) > -1) {
|
||||
const tabHistory = state.tabHistory.slice();
|
||||
tabHistory.push(action.queryEditor.id);
|
||||
return Object.assign({}, state, { tabHistory });
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@
|
|||
"test": "spec"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "npm run lint && mocha --compilers js:babel-core/register --require spec/helpers/browser.js --recursive spec/**/*_spec.*",
|
||||
"test": "mocha --compilers js:babel-core/register --require spec/helpers/browser.js --recursive spec/**/*_spec.*",
|
||||
"cover": "babel-node ./node_modules/.bin/istanbul cover _mocha -- --require spec/helpers/browser.js --recursive spec/**/*_spec.*",
|
||||
"dev": "NODE_ENV=dev webpack -d --watch --colors --progress",
|
||||
"prod": "NODE_ENV=production node --max_old_space_size=8192 ./node_modules/webpack/bin/webpack.js -p --colors --progress",
|
||||
|
|
|
|||
|
|
@ -17,4 +17,6 @@ Object.keys(document.defaultView).forEach((property) => {
|
|||
|
||||
global.navigator = {
|
||||
userAgent: 'node.js',
|
||||
platform: 'linux',
|
||||
appName: 'Netscape',
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,13 +1,14 @@
|
|||
import React from 'react';
|
||||
import { beforeEach, describe, it } from 'mocha';
|
||||
import { expect } from 'chai';
|
||||
import { shallow } from 'enzyme';
|
||||
|
||||
import QueryAndSaveButtons from '../../../../javascripts/explore/components/QueryAndSaveBtns';
|
||||
|
||||
describe('QueryAndSaveButtons', () => {
|
||||
let defaultProps = {
|
||||
const defaultProps = {
|
||||
canAdd: 'True',
|
||||
onQuery: () => {}
|
||||
onQuery: () => {},
|
||||
};
|
||||
|
||||
// It must render
|
||||
|
|
|
|||
|
|
@ -0,0 +1,24 @@
|
|||
import React from 'react';
|
||||
import Alerts from '../../../javascripts/SqlLab/components/Alerts';
|
||||
import { Alert } from 'react-bootstrap';
|
||||
import { shallow } from 'enzyme';
|
||||
import { describe, it } from 'mocha';
|
||||
import { expect } from 'chai';
|
||||
import { alert } from './fixtures';
|
||||
|
||||
|
||||
describe('Alerts', () => {
|
||||
const mockedProps = {
|
||||
alerts: [alert],
|
||||
};
|
||||
it('is valid', () => {
|
||||
expect(React.isValidElement(<Alerts />)).to.equal(true);
|
||||
});
|
||||
it('is valid with props', () => {
|
||||
expect(React.isValidElement(<Alerts {...mockedProps} />)).to.equal(true);
|
||||
});
|
||||
it('renders an Alert', () => {
|
||||
const wrapper = shallow(<Alerts {...mockedProps} />);
|
||||
expect(wrapper.find(Alert)).to.have.length(1);
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
import React from 'react';
|
||||
import App from '../../../javascripts/SqlLab/components/App';
|
||||
import { describe, it } from 'mocha';
|
||||
import { expect } from 'chai';
|
||||
|
||||
|
||||
describe('App', () => {
|
||||
it('is valid', () => {
|
||||
expect(React.isValidElement(<App />)).to.equal(true);
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
import React from 'react';
|
||||
import CopyQueryTabUrl from '../../../javascripts/SqlLab/components/CopyQueryTabUrl';
|
||||
import CopyToClipboard from '../../../javascripts/components/CopyToClipboard';
|
||||
import { shallow } from 'enzyme';
|
||||
import { describe, it } from 'mocha';
|
||||
import { expect } from 'chai';
|
||||
import { initialState } from './fixtures';
|
||||
|
||||
describe('CopyQueryTabUrl', () => {
|
||||
const mockedProps = {
|
||||
queryEditor: initialState.queryEditors[0],
|
||||
};
|
||||
it('should be valid', () => {
|
||||
expect(React.isValidElement(<CopyQueryTabUrl />)).to.equal(true);
|
||||
});
|
||||
it('renders with props', () => {
|
||||
expect(
|
||||
React.isValidElement(<CopyQueryTabUrl {...mockedProps} />)
|
||||
).to.equal(true);
|
||||
});
|
||||
it('renders a CopyToClipboard', () => {
|
||||
const wrapper = shallow(<CopyQueryTabUrl {...mockedProps} />);
|
||||
expect(wrapper.find(CopyToClipboard)).to.have.length(1);
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
import React from 'react';
|
||||
import HighlightedSql from '../../../javascripts/SqlLab/components/HighlightedSql';
|
||||
import SyntaxHighlighter from 'react-syntax-highlighter';
|
||||
import { shallow } from 'enzyme';
|
||||
import { describe, it } from 'mocha';
|
||||
import { expect } from 'chai';
|
||||
|
||||
|
||||
describe('HighlightedSql', () => {
|
||||
const sql = "SELECT * FROM test WHERE something='fkldasjfklajdslfkjadlskfjkldasjfkladsjfkdjsa'";
|
||||
it('renders', () => {
|
||||
expect(React.isValidElement(<HighlightedSql />)).to.equal(true);
|
||||
});
|
||||
it('renders with props', () => {
|
||||
expect(React.isValidElement(<HighlightedSql sql={sql} />))
|
||||
.to.equal(true);
|
||||
});
|
||||
it('renders a SyntaxHighlighter', () => {
|
||||
const wrapper = shallow(<HighlightedSql sql={sql} />);
|
||||
expect(wrapper.find(SyntaxHighlighter)).to.have.length(1);
|
||||
});
|
||||
it('renders a SyntaxHighlighter while using shrink', () => {
|
||||
const wrapper = shallow(<HighlightedSql sql={sql} shrink maxWidth={20} />);
|
||||
expect(wrapper.find(SyntaxHighlighter)).to.have.length(1);
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
import React from 'react';
|
||||
import Link from '../../../javascripts/SqlLab/components/Link';
|
||||
import { shallow } from 'enzyme';
|
||||
import { describe, it } from 'mocha';
|
||||
import { expect } from 'chai';
|
||||
|
||||
|
||||
describe('Link', () => {
|
||||
const mockedProps = {
|
||||
tooltip: 'This is a tooltip',
|
||||
href: 'http://www.airbnb.com',
|
||||
};
|
||||
it('renders', () => {
|
||||
expect(React.isValidElement(<Link>TEST</Link>)).to.equal(true);
|
||||
});
|
||||
it('renders with props', () => {
|
||||
expect(
|
||||
React.isValidElement(<Link {...mockedProps} >TEST</Link>)
|
||||
).to.equal(true);
|
||||
});
|
||||
it('renders an anchor tag', () => {
|
||||
const wrapper = shallow(<Link {...mockedProps} >TEST</Link>);
|
||||
expect(wrapper.find('a')).to.have.length(1);
|
||||
});
|
||||
});
|
||||
|
|
@ -7,24 +7,27 @@ import { describe, it } from 'mocha';
|
|||
import { expect } from 'chai';
|
||||
|
||||
describe('QuerySearch', () => {
|
||||
it('should render', () => {
|
||||
const mockedProps = {
|
||||
actions: {},
|
||||
};
|
||||
it('is valid', () => {
|
||||
expect(
|
||||
React.isValidElement(<QuerySearch />)
|
||||
React.isValidElement(<QuerySearch {...mockedProps} />)
|
||||
).to.equal(true);
|
||||
});
|
||||
|
||||
it('should have two Select', () => {
|
||||
const wrapper = shallow(<QuerySearch />);
|
||||
const wrapper = shallow(<QuerySearch {...mockedProps} />);
|
||||
expect(wrapper.find(Select)).to.have.length(2);
|
||||
});
|
||||
|
||||
it('should have one input for searchText', () => {
|
||||
const wrapper = shallow(<QuerySearch />);
|
||||
const wrapper = shallow(<QuerySearch {...mockedProps} />);
|
||||
expect(wrapper.find('input')).to.have.length(1);
|
||||
});
|
||||
|
||||
it('should have one Button', () => {
|
||||
const wrapper = shallow(<QuerySearch />);
|
||||
const wrapper = shallow(<QuerySearch {...mockedProps} />);
|
||||
expect(wrapper.find(Button)).to.have.length(1);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -0,0 +1,26 @@
|
|||
import React from 'react';
|
||||
import QueryTable from '../../../javascripts/SqlLab/components/QueryTable';
|
||||
import { queries } from './fixtures';
|
||||
import { mount } from 'enzyme';
|
||||
import { describe, it } from 'mocha';
|
||||
import { expect } from 'chai';
|
||||
|
||||
|
||||
describe('QueryTable', () => {
|
||||
const mockedProps = {
|
||||
queries,
|
||||
};
|
||||
it('is valid', () => {
|
||||
expect(React.isValidElement(<QueryTable />)).to.equal(true);
|
||||
});
|
||||
it('is valid with props', () => {
|
||||
expect(
|
||||
React.isValidElement(<QueryTable {...mockedProps} />)
|
||||
).to.equal(true);
|
||||
});
|
||||
it('renders a proper table', () => {
|
||||
const wrapper = mount(<QueryTable {...mockedProps} />);
|
||||
expect(wrapper.find('table')).to.have.length(1);
|
||||
expect(wrapper.find('tr')).to.have.length(3);
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
import React from 'react';
|
||||
import ResultSet from '../../../javascripts/SqlLab/components/ResultSet';
|
||||
import { shallow } from 'enzyme';
|
||||
import { describe, it } from 'mocha';
|
||||
import { expect } from 'chai';
|
||||
import { queries } from './fixtures';
|
||||
import { Table } from 'reactable';
|
||||
|
||||
describe('ResultSet', () => {
|
||||
const mockedProps = {
|
||||
query: queries[0],
|
||||
};
|
||||
it('renders', () => {
|
||||
expect(React.isValidElement(<ResultSet />)).to.equal(true);
|
||||
});
|
||||
it('renders with props', () => {
|
||||
expect(
|
||||
React.isValidElement(<ResultSet />)
|
||||
).to.equal(true);
|
||||
});
|
||||
it('renders a Table', () => {
|
||||
const wrapper = shallow(<ResultSet {...mockedProps} />);
|
||||
expect(wrapper.find(Table)).to.have.length(1);
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
import React from 'react';
|
||||
import SqlEditorLeftBar from '../../../javascripts/SqlLab/components/SqlEditorLeftBar';
|
||||
import TableElement from '../../../javascripts/SqlLab/components/TableElement';
|
||||
import { mount } from 'enzyme';
|
||||
import { table, defaultQueryEditor } from './fixtures';
|
||||
import { describe, it } from 'mocha';
|
||||
import { expect } from 'chai';
|
||||
|
||||
|
||||
describe('SqlEditorLeftBar', () => {
|
||||
const mockedProps = {
|
||||
tables: [table],
|
||||
queryEditor: defaultQueryEditor,
|
||||
};
|
||||
it('is valid', () => {
|
||||
expect(
|
||||
React.isValidElement(<SqlEditorLeftBar {...mockedProps} />)
|
||||
).to.equal(true);
|
||||
});
|
||||
it('renders a TableElement', () => {
|
||||
const wrapper = mount(<SqlEditorLeftBar {...mockedProps} />);
|
||||
expect(wrapper.find(TableElement)).to.have.length(1);
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
import React from 'react';
|
||||
import SqlEditor from '../../../javascripts/SqlLab/components/SqlEditor';
|
||||
import SqlEditorLeftBar from '../../../javascripts/SqlLab/components/SqlEditorLeftBar';
|
||||
import { shallow } from 'enzyme';
|
||||
import { describe, it } from 'mocha';
|
||||
import { expect } from 'chai';
|
||||
import { initialState, queries, table } from './fixtures';
|
||||
|
||||
describe('SqlEditor', () => {
|
||||
const mockedProps = {
|
||||
actions: {},
|
||||
database: {},
|
||||
queryEditor: initialState.queryEditors[0],
|
||||
latestQuery: queries[0],
|
||||
tables: [table],
|
||||
queries,
|
||||
};
|
||||
it('is valid', () => {
|
||||
expect(
|
||||
React.isValidElement(<SqlEditor {...mockedProps} />)
|
||||
).to.equal(true);
|
||||
});
|
||||
it('render a SqlEditorLeftBar', () => {
|
||||
const wrapper = shallow(<SqlEditor {...mockedProps} />);
|
||||
expect(wrapper.find(SqlEditorLeftBar)).to.have.length(1);
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
import React from 'react';
|
||||
import { TabbedSqlEditors } from '../../../javascripts/SqlLab/components/TabbedSqlEditors';
|
||||
import { Tab } from 'react-bootstrap';
|
||||
import { shallow } from 'enzyme';
|
||||
import { describe, it } from 'mocha';
|
||||
import { expect } from 'chai';
|
||||
import { initialState } from './fixtures';
|
||||
|
||||
describe('TabbedSqlEditors', () => {
|
||||
const mockedProps = {
|
||||
actions: {},
|
||||
databases: {},
|
||||
tables: [],
|
||||
queries: {},
|
||||
queryEditors: initialState.queryEditors,
|
||||
tabHistory: initialState.tabHistory,
|
||||
};
|
||||
it('is valid', () => {
|
||||
expect(
|
||||
React.isValidElement(<TabbedSqlEditors {...mockedProps} />)
|
||||
).to.equal(true);
|
||||
});
|
||||
it('shallow mounts', () => {
|
||||
const wrapper = shallow(<TabbedSqlEditors {...mockedProps} />);
|
||||
expect(wrapper.find(Tab)).to.have.length(2);
|
||||
});
|
||||
});
|
||||
|
|
@ -1,148 +1,22 @@
|
|||
import React from 'react';
|
||||
import Link from '../../../javascripts/SqlLab/components/Link';
|
||||
import { Button } from 'react-bootstrap';
|
||||
import { TableElement } from '../../../javascripts/SqlLab/components/TableElement';
|
||||
import { shallow } from 'enzyme';
|
||||
import TableElement from '../../../javascripts/SqlLab/components/TableElement';
|
||||
import { table } from './fixtures';
|
||||
import { mount, shallow } from 'enzyme';
|
||||
import { describe, it } from 'mocha';
|
||||
import { expect } from 'chai';
|
||||
|
||||
|
||||
describe('TableElement', () => {
|
||||
|
||||
const mockedProps = {
|
||||
'table': {
|
||||
"dbId": 1,
|
||||
"queryEditorId": "rJ-KP47a",
|
||||
"schema": "caravel",
|
||||
"name": "ab_user",
|
||||
"id": "r11Vgt60",
|
||||
"indexes": [
|
||||
{
|
||||
"unique": true,
|
||||
"column_names": [
|
||||
"username"
|
||||
],
|
||||
"type": "UNIQUE",
|
||||
"name": "username"
|
||||
},
|
||||
{
|
||||
"unique": true,
|
||||
"column_names": [
|
||||
"email"
|
||||
],
|
||||
"type": "UNIQUE",
|
||||
"name": "email"
|
||||
},
|
||||
{
|
||||
"unique": false,
|
||||
"column_names": [
|
||||
"created_by_fk"
|
||||
],
|
||||
"name": "created_by_fk"
|
||||
},
|
||||
{
|
||||
"unique": false,
|
||||
"column_names": [
|
||||
"changed_by_fk"
|
||||
],
|
||||
"name": "changed_by_fk"
|
||||
}
|
||||
],
|
||||
"columns": [
|
||||
{
|
||||
"indexed": false,
|
||||
"longType": "INTEGER(11)",
|
||||
"type": "INTEGER",
|
||||
"name": "id"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"longType": "VARCHAR(64)",
|
||||
"type": "VARCHAR",
|
||||
"name": "first_name"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"longType": "VARCHAR(64)",
|
||||
"type": "VARCHAR",
|
||||
"name": "last_name"
|
||||
},
|
||||
{
|
||||
"indexed": true,
|
||||
"longType": "VARCHAR(64)",
|
||||
"type": "VARCHAR",
|
||||
"name": "username"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"longType": "VARCHAR(256)",
|
||||
"type": "VARCHAR",
|
||||
"name": "password"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"longType": "TINYINT(1)",
|
||||
"type": "TINYINT",
|
||||
"name": "active"
|
||||
},
|
||||
{
|
||||
"indexed": true,
|
||||
"longType": "VARCHAR(64)",
|
||||
"type": "VARCHAR",
|
||||
"name": "email"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"longType": "DATETIME",
|
||||
"type": "DATETIME",
|
||||
"name": "last_login"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"longType": "INTEGER(11)",
|
||||
"type": "INTEGER",
|
||||
"name": "login_count"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"longType": "INTEGER(11)",
|
||||
"type": "INTEGER",
|
||||
"name": "fail_login_count"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"longType": "DATETIME",
|
||||
"type": "DATETIME",
|
||||
"name": "created_on"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"longType": "DATETIME",
|
||||
"type": "DATETIME",
|
||||
"name": "changed_on"
|
||||
},
|
||||
{
|
||||
"indexed": true,
|
||||
"longType": "INTEGER(11)",
|
||||
"type": "INTEGER",
|
||||
"name": "created_by_fk"
|
||||
},
|
||||
{
|
||||
"indexed": true,
|
||||
"longType": "INTEGER(11)",
|
||||
"type": "INTEGER",
|
||||
"name": "changed_by_fk"
|
||||
}
|
||||
],
|
||||
"expanded": true
|
||||
}
|
||||
}
|
||||
it('should just render', () => {
|
||||
table,
|
||||
};
|
||||
it('renders', () => {
|
||||
expect(
|
||||
React.isValidElement(<TableElement />)
|
||||
).to.equal(true);
|
||||
});
|
||||
it('should render with props', () => {
|
||||
it('renders with props', () => {
|
||||
expect(
|
||||
React.isValidElement(<TableElement {...mockedProps} />)
|
||||
).to.equal(true);
|
||||
|
|
@ -155,4 +29,7 @@ describe('TableElement', () => {
|
|||
const wrapper = shallow(<TableElement {...mockedProps} />);
|
||||
expect(wrapper.find('div.table-column')).to.have.length(14);
|
||||
});
|
||||
it('mounts', () => {
|
||||
mount(<TableElement {...mockedProps} />);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -0,0 +1,24 @@
|
|||
import React from 'react';
|
||||
import Timer from '../../../javascripts/SqlLab/components/Timer';
|
||||
import { shallow } from 'enzyme';
|
||||
import { describe, it } from 'mocha';
|
||||
import { expect } from 'chai';
|
||||
import { queries } from './fixtures';
|
||||
|
||||
|
||||
describe('Timer', () => {
|
||||
const mockedProps = {
|
||||
query: queries[0],
|
||||
};
|
||||
it('renders', () => {
|
||||
expect(React.isValidElement(<Timer />)).to.equal(true);
|
||||
});
|
||||
it('renders with props', () => {
|
||||
expect(React.isValidElement(<Timer {...mockedProps} />))
|
||||
.to.equal(true);
|
||||
});
|
||||
it('renders a span', () => {
|
||||
const wrapper = shallow(<Timer {...mockedProps} />);
|
||||
expect(wrapper.find('span')).to.have.length(1);
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
import React from 'react';
|
||||
import VisualizeModal from '../../../javascripts/SqlLab/components/VisualizeModal';
|
||||
import { queries } from './fixtures';
|
||||
import { Modal } from 'react-bootstrap';
|
||||
import { shallow } from 'enzyme';
|
||||
import { describe, it } from 'mocha';
|
||||
import { expect } from 'chai';
|
||||
|
||||
|
||||
describe('VisualizeModal', () => {
|
||||
const mockedProps = {
|
||||
show: true,
|
||||
query: queries[0],
|
||||
};
|
||||
it('renders', () => {
|
||||
expect(React.isValidElement(<VisualizeModal />)).to.equal(true);
|
||||
});
|
||||
it('renders with props', () => {
|
||||
expect(
|
||||
React.isValidElement(<VisualizeModal {...mockedProps} />)
|
||||
).to.equal(true);
|
||||
});
|
||||
it('renders a Modal', () => {
|
||||
const wrapper = shallow(<VisualizeModal {...mockedProps} />);
|
||||
expect(wrapper.find(Modal)).to.have.length(1);
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,229 @@
|
|||
export const alert = { bsStyle: 'danger', msg: 'Ooops', id: 'lksvmcx32' };
|
||||
export const table = {
|
||||
dbId: 1,
|
||||
selectStar: 'SELECT * FROM ab_user',
|
||||
queryEditorId: 'rJ-KP47a',
|
||||
schema: 'caravel',
|
||||
name: 'ab_user',
|
||||
id: 'r11Vgt60',
|
||||
indexes: [
|
||||
{
|
||||
unique: true,
|
||||
column_names: [
|
||||
'username',
|
||||
],
|
||||
type: 'UNIQUE',
|
||||
name: 'username',
|
||||
},
|
||||
{
|
||||
unique: true,
|
||||
column_names: [
|
||||
'email',
|
||||
],
|
||||
type: 'UNIQUE',
|
||||
name: 'email',
|
||||
},
|
||||
{
|
||||
unique: false,
|
||||
column_names: [
|
||||
'created_by_fk',
|
||||
],
|
||||
name: 'created_by_fk',
|
||||
},
|
||||
{
|
||||
unique: false,
|
||||
column_names: [
|
||||
'changed_by_fk',
|
||||
],
|
||||
name: 'changed_by_fk',
|
||||
},
|
||||
],
|
||||
columns: [
|
||||
{
|
||||
indexed: false,
|
||||
longType: 'INTEGER(11)',
|
||||
type: 'INTEGER',
|
||||
name: 'id',
|
||||
},
|
||||
{
|
||||
indexed: false,
|
||||
longType: 'VARCHAR(64)',
|
||||
type: 'VARCHAR',
|
||||
name: 'first_name',
|
||||
},
|
||||
{
|
||||
indexed: false,
|
||||
longType: 'VARCHAR(64)',
|
||||
type: 'VARCHAR',
|
||||
name: 'last_name',
|
||||
},
|
||||
{
|
||||
indexed: true,
|
||||
longType: 'VARCHAR(64)',
|
||||
type: 'VARCHAR',
|
||||
name: 'username',
|
||||
},
|
||||
{
|
||||
indexed: false,
|
||||
longType: 'VARCHAR(256)',
|
||||
type: 'VARCHAR',
|
||||
name: 'password',
|
||||
},
|
||||
{
|
||||
indexed: false,
|
||||
longType: 'TINYINT(1)',
|
||||
type: 'TINYINT',
|
||||
name: 'active',
|
||||
},
|
||||
{
|
||||
indexed: true,
|
||||
longType: 'VARCHAR(64)',
|
||||
type: 'VARCHAR',
|
||||
name: 'email',
|
||||
},
|
||||
{
|
||||
indexed: false,
|
||||
longType: 'DATETIME',
|
||||
type: 'DATETIME',
|
||||
name: 'last_login',
|
||||
},
|
||||
{
|
||||
indexed: false,
|
||||
longType: 'INTEGER(11)',
|
||||
type: 'INTEGER',
|
||||
name: 'login_count',
|
||||
},
|
||||
{
|
||||
indexed: false,
|
||||
longType: 'INTEGER(11)',
|
||||
type: 'INTEGER',
|
||||
name: 'fail_login_count',
|
||||
},
|
||||
{
|
||||
indexed: false,
|
||||
longType: 'DATETIME',
|
||||
type: 'DATETIME',
|
||||
name: 'created_on',
|
||||
},
|
||||
{
|
||||
indexed: false,
|
||||
longType: 'DATETIME',
|
||||
type: 'DATETIME',
|
||||
name: 'changed_on',
|
||||
},
|
||||
{
|
||||
indexed: true,
|
||||
longType: 'INTEGER(11)',
|
||||
type: 'INTEGER',
|
||||
name: 'created_by_fk',
|
||||
},
|
||||
{
|
||||
indexed: true,
|
||||
longType: 'INTEGER(11)',
|
||||
type: 'INTEGER',
|
||||
name: 'changed_by_fk',
|
||||
},
|
||||
],
|
||||
expanded: true,
|
||||
};
|
||||
export const defaultQueryEditor = {
|
||||
id: 'dfsadfs',
|
||||
title: 'Untitled Query',
|
||||
sql: 'SELECT *\nFROM\nWHERE',
|
||||
latestQueryId: null,
|
||||
autorun: false,
|
||||
dbId: null,
|
||||
};
|
||||
export const queries = [
|
||||
{
|
||||
dbId: 1,
|
||||
sql: 'SELECT *FROM caravel.slices',
|
||||
sqlEditorId: 'SJ8YO72R',
|
||||
tab: 'Demo',
|
||||
runAsync: false,
|
||||
ctas: false,
|
||||
id: 'BkA1CLrJg',
|
||||
progress: 100,
|
||||
startDttm: 1476910566092.96,
|
||||
state: 'success',
|
||||
changedOn: 1476910566000,
|
||||
tempTable: null,
|
||||
userId: 1,
|
||||
executedSql: null,
|
||||
changed_on: '2016-10-19T20:56:06',
|
||||
rows: 42,
|
||||
endDttm: 1476910566798,
|
||||
limit_reached: false,
|
||||
schema: null,
|
||||
errorMessage: null,
|
||||
db: 'main',
|
||||
user: 'admin',
|
||||
limit: 1000,
|
||||
serverId: 141,
|
||||
resultsKey: null,
|
||||
results: {
|
||||
columns: ['col1', 'col2'],
|
||||
data: [
|
||||
{ col1: 0, col2: 1 },
|
||||
{ col1: 2, col2: 3 },
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
dbId: 1,
|
||||
sql: 'SELECT *FROM caravel.slices',
|
||||
sqlEditorId: 'SJ8YO72R',
|
||||
tab: 'Demo',
|
||||
runAsync: true,
|
||||
ctas: false,
|
||||
id: 'S1zeAISkx',
|
||||
progress: 100,
|
||||
startDttm: 1476910570802.2,
|
||||
state: 'success',
|
||||
changedOn: 1476910572000,
|
||||
tempTable: null,
|
||||
userId: 1,
|
||||
executedSql: (
|
||||
'SELECT * \nFROM (SELECT created_on, changed_on, id, slice_name, ' +
|
||||
'druid_datasource_id, table_id, datasource_type, datasource_name, ' +
|
||||
'viz_type, params, created_by_fk, changed_by_fk, description, ' +
|
||||
'cache_timeout, perm\nFROM caravel.slices) AS inner_qry \n LIMIT 1000'
|
||||
),
|
||||
changed_on: '2016-10-19T20:56:12',
|
||||
rows: 42,
|
||||
endDttm: 1476910579693,
|
||||
limit_reached: false,
|
||||
schema: null,
|
||||
errorMessage: null,
|
||||
db: 'main',
|
||||
user: 'admin',
|
||||
limit: 1000,
|
||||
serverId: 142,
|
||||
resultsKey: '417149f4-cd27-4f80-91f3-c45c871003f7',
|
||||
results: null,
|
||||
},
|
||||
];
|
||||
|
||||
export const initialState = {
|
||||
alerts: [],
|
||||
showDataPreviewModal: false,
|
||||
dataPreviewQueryId: null,
|
||||
networkOn: true,
|
||||
queries: {},
|
||||
databases: {},
|
||||
queryEditors: [defaultQueryEditor],
|
||||
tabHistory: [defaultQueryEditor.id],
|
||||
tables: [],
|
||||
workspaceQueries: [],
|
||||
queriesLastUpdate: 0,
|
||||
};
|
||||
|
||||
export const query = {
|
||||
dbId: 1,
|
||||
sql: 'SELECT * FROM something',
|
||||
sqlEditorId: defaultQueryEditor.id,
|
||||
tab: 'unimportant',
|
||||
tempTableName: null,
|
||||
runAsync: false,
|
||||
ctas: false,
|
||||
};
|
||||
|
|
@ -1,18 +1,19 @@
|
|||
import * as r from '../../../javascripts/SqlLab/reducers';
|
||||
import * as actions from '../../../javascripts/SqlLab/actions';
|
||||
import { describe, it } from 'mocha';
|
||||
import { beforeEach, describe, it } from 'mocha';
|
||||
import { expect } from 'chai';
|
||||
import { alert, table, initialState } from './fixtures';
|
||||
|
||||
describe('sqlLabReducer', () => {
|
||||
describe('CLONE_QUERY_TO_NEW_TAB', () => {
|
||||
const testQuery = { sql: 'SELECT * FROM...', dbId: 1, id: 1 };
|
||||
const state = Object.assign(r.initialState, { queries: [testQuery] });
|
||||
const newState = r.sqlLabReducer(state, actions.cloneQueryToNewTab(testQuery));
|
||||
const testQuery = { sql: 'SELECT * FROM...', dbId: 1, id: 'flasj233' };
|
||||
let newState = Object.assign({}, initialState, { queries: { [testQuery.id]: testQuery } });
|
||||
newState = r.sqlLabReducer(newState, actions.cloneQueryToNewTab(testQuery));
|
||||
|
||||
it('should have at most one more tab', () => {
|
||||
expect(newState.queryEditors).have.length(2);
|
||||
});
|
||||
|
||||
|
||||
it('should have the same SQL as the cloned query', () => {
|
||||
expect(newState.queryEditors[1].sql).to.equal(testQuery.sql);
|
||||
});
|
||||
|
|
@ -25,4 +26,124 @@ describe('sqlLabReducer', () => {
|
|||
expect(newState.tabHistory[1]).to.eq(newState.queryEditors[1].id);
|
||||
});
|
||||
});
|
||||
describe('Alerts', () => {
|
||||
const state = Object.assign({}, initialState);
|
||||
let newState;
|
||||
it('should add one alert', () => {
|
||||
newState = r.sqlLabReducer(state, actions.addAlert(alert));
|
||||
expect(newState.alerts).to.have.lengthOf(1);
|
||||
});
|
||||
it('should remove one alert', () => {
|
||||
newState = r.sqlLabReducer(newState, actions.removeAlert(newState.alerts[0]));
|
||||
expect(newState.alerts).to.have.lengthOf(0);
|
||||
});
|
||||
});
|
||||
describe('Query editors actions', () => {
|
||||
let newState;
|
||||
let defaultQueryEditor;
|
||||
let qe;
|
||||
beforeEach(() => {
|
||||
newState = Object.assign({}, initialState);
|
||||
defaultQueryEditor = newState.queryEditors[0];
|
||||
qe = Object.assign({}, defaultQueryEditor);
|
||||
newState = r.sqlLabReducer(newState, actions.addQueryEditor(qe));
|
||||
qe = newState.queryEditors[newState.queryEditors.length - 1];
|
||||
});
|
||||
it('should add a query editor', () => {
|
||||
expect(newState.queryEditors).to.have.lengthOf(2);
|
||||
});
|
||||
it('should remove a query editor', () => {
|
||||
expect(newState.queryEditors).to.have.lengthOf(2);
|
||||
newState = r.sqlLabReducer(newState, actions.removeQueryEditor(qe));
|
||||
expect(newState.queryEditors).to.have.lengthOf(1);
|
||||
});
|
||||
it('should set q query editor active', () => {
|
||||
newState = r.sqlLabReducer(newState, actions.addQueryEditor(qe));
|
||||
newState = r.sqlLabReducer(newState, actions.setActiveQueryEditor(defaultQueryEditor));
|
||||
expect(newState.tabHistory[newState.tabHistory.length - 1]).equals(defaultQueryEditor.id);
|
||||
});
|
||||
it('should not fail while setting DB', () => {
|
||||
const dbId = 9;
|
||||
newState = r.sqlLabReducer(newState, actions.queryEditorSetDb(qe, dbId));
|
||||
expect(newState.queryEditors[1].dbId).to.equal(dbId);
|
||||
});
|
||||
it('should not fail while setting schema', () => {
|
||||
const schema = 'foo';
|
||||
newState = r.sqlLabReducer(newState, actions.queryEditorSetSchema(qe, schema));
|
||||
expect(newState.queryEditors[1].schema).to.equal(schema);
|
||||
});
|
||||
it('should not fail while setting autorun ', () => {
|
||||
newState = r.sqlLabReducer(newState, actions.queryEditorSetAutorun(qe, false));
|
||||
expect(newState.queryEditors[1].autorun).to.equal(false);
|
||||
newState = r.sqlLabReducer(newState, actions.queryEditorSetAutorun(qe, true));
|
||||
expect(newState.queryEditors[1].autorun).to.equal(true);
|
||||
});
|
||||
it('should not fail while setting title', () => {
|
||||
const title = 'a new title';
|
||||
newState = r.sqlLabReducer(newState, actions.queryEditorSetTitle(qe, title));
|
||||
expect(newState.queryEditors[1].title).to.equal(title);
|
||||
});
|
||||
it('should not fail while setting Sql', () => {
|
||||
const sql = 'SELECT nothing from dev_null';
|
||||
newState = r.sqlLabReducer(newState, actions.queryEditorSetSql(qe, sql));
|
||||
expect(newState.queryEditors[1].sql).to.equal(sql);
|
||||
});
|
||||
});
|
||||
describe('Tables', () => {
|
||||
let newState;
|
||||
let newTable;
|
||||
beforeEach(() => {
|
||||
newTable = Object.assign({}, table);
|
||||
newState = r.sqlLabReducer(initialState, actions.mergeTable(newTable));
|
||||
newTable = newState.tables[0];
|
||||
});
|
||||
it('should add a table', () => {
|
||||
// Testing that beforeEach actually added the table
|
||||
expect(newState.tables).to.have.lengthOf(1);
|
||||
});
|
||||
it('should merge the table attributes', () => {
|
||||
// Merging the extra attribute
|
||||
newTable.extra = true;
|
||||
newState = r.sqlLabReducer(newState, actions.mergeTable(newTable));
|
||||
expect(newState.tables).to.have.lengthOf(1);
|
||||
expect(newState.tables[0].extra).to.equal(true);
|
||||
});
|
||||
it('should expand and collapse a table', () => {
|
||||
newState = r.sqlLabReducer(newState, actions.collapseTable(newTable));
|
||||
expect(newState.tables[0].expanded).to.equal(false);
|
||||
newState = r.sqlLabReducer(newState, actions.expandTable(newTable));
|
||||
expect(newState.tables[0].expanded).to.equal(true);
|
||||
});
|
||||
it('should remove a table', () => {
|
||||
newState = r.sqlLabReducer(newState, actions.removeTable(newTable));
|
||||
expect(newState.tables).to.have.lengthOf(0);
|
||||
});
|
||||
});
|
||||
describe('Run Query', () => {
|
||||
let newState;
|
||||
let query;
|
||||
let newQuery;
|
||||
beforeEach(() => {
|
||||
newState = Object.assign({}, initialState);
|
||||
newQuery = Object.assign({}, query);
|
||||
});
|
||||
it('should start a query', () => {
|
||||
newState = r.sqlLabReducer(newState, actions.startQuery(newQuery));
|
||||
expect(Object.keys(newState.queries)).to.have.lengthOf(1);
|
||||
});
|
||||
it('should stop the query', () => {
|
||||
newState = r.sqlLabReducer(newState, actions.startQuery(newQuery));
|
||||
newState = r.sqlLabReducer(newState, actions.stopQuery(newQuery));
|
||||
const q = newState.queries[Object.keys(newState.queries)[0]];
|
||||
expect(q.state).to.equal('stopped');
|
||||
});
|
||||
it('should remove a query', () => {
|
||||
newState = r.sqlLabReducer(newState, actions.startQuery(newQuery));
|
||||
newState = r.sqlLabReducer(newState, actions.removeQuery(newQuery));
|
||||
expect(Object.keys(newState.queries)).to.have.lengthOf(0);
|
||||
});
|
||||
it('should refresh queries when polling returns empty', () => {
|
||||
newState = r.sqlLabReducer(newState, actions.refreshQueries({}));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
assets/coverage/lcov-report/
|
||||
Loading…
Reference in New Issue