[WiP] rename project from Caravel to Superset (#1576)

* Change in files

* Renamin files and folders

* cleaning up a single piece of lint

* Removing boat picture from docs

* add superset word mark

* Update rename note in docs

* Fixing images

* Pinning datatables

* Fixing issues with mapbox-gl

* Forgot to rename one file

* Linting

* v0.13.0

* adding pyyaml to dev-reqs
This commit is contained in:
Maxime Beauchemin
2016-11-09 23:08:22 -08:00
committed by GitHub
parent 973537fd9a
commit 15b67b2c6c
408 changed files with 2795 additions and 2787 deletions

View File

@@ -0,0 +1,186 @@
import $ from 'jquery';
import React, { PropTypes } from 'react';
import { connect } from 'react-redux';
import { Panel } from 'react-bootstrap';
import visMap from '../../../visualizations/main';
import { d3format } from '../../modules/utils';
import ExploreActionButtons from '../../explore/components/ExploreActionButtons';
const propTypes = {
can_download: PropTypes.bool.isRequired,
slice_name: PropTypes.string.isRequired,
viz_type: PropTypes.string.isRequired,
height: PropTypes.string.isRequired,
containerId: PropTypes.string.isRequired,
json_endpoint: PropTypes.string.isRequired,
csv_endpoint: PropTypes.string.isRequired,
standalone_endpoint: PropTypes.string.isRequired,
query: PropTypes.string.isRequired,
column_formats: PropTypes.object,
};
class ChartContainer extends React.Component {
constructor(props) {
super(props);
this.state = {
selector: `#${props.containerId}`,
mockSlice: {},
};
}
componentWillMount() {
this.setState({ mockSlice: this.getMockedSliceObject() });
}
componentDidMount() {
this.renderVis();
}
componentDidUpdate() {
this.renderVis();
}
getMockedSliceObject() {
return {
viewSqlQuery: this.props.query,
data: {
csv_endpoint: this.props.csv_endpoint,
json_endpoint: this.props.json_endpoint,
standalone_endpoint: this.props.standalone_endpoint,
},
containerId: this.props.containerId,
jsonEndpoint: () => this.props.json_endpoint,
container: {
html: (data) => {
// this should be a callback to clear the contents of the slice container
$(this.state.selector).html(data);
},
css: (dim, size) => {
// dimension can be 'height'
// pixel string can be '300px'
// should call callback to adjust height of chart
$(this.state.selector).css(dim, size);
},
height: () => parseInt(this.props.height, 10) - 100,
show: () => { this.render(); },
get: (n) => ($(this.state.selector).get(n)),
find: (classname) => ($(this.state.selector).find(classname)),
},
width: () => this.chartContainerRef.getBoundingClientRect().width,
height: () => parseInt(this.props.height, 10) - 100,
selector: this.state.selector,
setFilter: () => {
// set filter according to data in store
// used in FilterBox.onChange()
},
getFilters: () => (
// return filter objects from viz.formData
{}
),
done: () => {
// finished rendering callback
},
error(msg, xhr) {
let errorMsg = msg;
let errHtml = '';
try {
const o = JSON.parse(msg);
if (o.error) {
errorMsg = o.error;
}
} catch (e) {
// pass
}
errHtml = `<div class="alert alert-danger">${errorMsg}</div>`;
if (xhr) {
const extendedMsg = this.getErrorMsg(xhr);
if (extendedMsg) {
errHtml += `<div class="alert alert-danger">${extendedMsg}</div>`;
}
}
$(this.state.selector).html(errHtml);
this.render();
$('span.query').removeClass('disabled');
$('.query-and-save button').removeAttr('disabled');
},
d3format: (col, number) => {
// mock d3format function in Slice object in superset.js
const format = this.props.column_formats[col];
return d3format(format, number);
},
};
}
renderVis() {
visMap[this.props.viz_type](this.state.mockSlice).render();
}
render() {
return (
<div className="chart-container">
<Panel
style={{ height: this.props.height }}
header={
<div
id="slice-header"
className="panel-title"
>
{this.props.slice_name}
<div className="pull-right">
<ExploreActionButtons
slice={this.state.mockSlice}
canDownload={this.props.can_download}
/>
</div>
</div>
}
>
<div
id={this.props.containerId}
ref={(ref) => { this.chartContainerRef = ref; }}
className={this.props.viz_type}
/>
</Panel>
</div>
);
}
}
ChartContainer.propTypes = propTypes;
function mapStateToProps(state) {
return {
containerId: `slice-container-${state.viz.form_data.slice_id}`,
slice_name: state.viz.form_data.slice_name,
viz_type: state.viz.form_data.viz_type,
can_download: state.can_download,
csv_endpoint: state.viz.csv_endpoint,
json_endpoint: state.viz.json_endpoint,
standalone_endpoint: state.viz.standalone_endpoint,
query: state.viz.query,
column_formats: state.viz.column_formats,
};
}
function mapDispatchToProps() {
return {};
}
export default connect(mapStateToProps, mapDispatchToProps)(ChartContainer);

View File

@@ -0,0 +1,89 @@
import React from 'react';
import Select from 'react-select';
import { bindActionCreators } from 'redux';
import * as actions from '../actions/exploreActions';
import { connect } from 'react-redux';
import { VIZ_TYPES } from '../constants';
const propTypes = {
actions: React.PropTypes.object,
datasources: React.PropTypes.array,
datasourceId: React.PropTypes.number,
datasourceType: React.PropTypes.string,
vizType: React.PropTypes.string,
};
const defaultProps = {
datasources: [],
datasourceId: null,
datasourceType: null,
vizType: null,
};
class ChartControl extends React.Component {
componentWillMount() {
if (this.props.datasourceId) {
this.props.actions.setFormOpts(this.props.datasourceId, this.props.datasourceType);
}
}
changeDatasource(datasourceOpt) {
const val = (datasourceOpt) ? datasourceOpt.value : null;
this.props.actions.setDatasource(val);
this.props.actions.resetFormData();
this.props.actions.setFormOpts(val, this.props.datasourceType);
}
changeViz(opt) {
const val = opt ? opt.value : null;
this.props.actions.setFormData('vizType', val);
}
render() {
return (
<div className="panel">
<div className="panel-header">Chart Options</div>
<div className="panel-body">
<h5 className="section-heading">Datasource</h5>
<div className="row">
<Select
name="select-datasource"
placeholder="Select a datasource"
options={this.props.datasources.map((d) => ({ value: d[0], label: d[1] }))}
value={this.props.datasourceId}
autosize={false}
onChange={this.changeDatasource.bind(this)}
/>
</div>
<h5 className="section-heading">Viz Type</h5>
<div className="row">
<Select
name="select-viztype"
placeholder="Select a viz type"
options={VIZ_TYPES}
value={this.props.vizType}
autosize={false}
onChange={this.changeViz.bind(this)}
/>
</div>
</div>
</div>
);
}
}
ChartControl.propTypes = propTypes;
ChartControl.defaultProps = defaultProps;
function mapStateToProps(state) {
return {
datasources: state.datasources,
datasourceId: state.datasourceId,
datasourceType: state.datasourceType,
vizType: state.viz.formData.vizType,
};
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators(actions, dispatch),
};
}
export default connect(mapStateToProps, mapDispatchToProps)(ChartControl);

View File

@@ -0,0 +1,38 @@
import React, { PropTypes } from 'react';
import { Checkbox } from 'react-bootstrap';
import ControlLabelWithTooltip from './ControlLabelWithTooltip';
const propTypes = {
name: PropTypes.string.isRequired,
value: PropTypes.bool,
label: PropTypes.string,
description: PropTypes.string,
onChange: PropTypes.func,
};
const defaultProps = {
value: false,
label: null,
description: null,
onChange: () => {},
};
export default class CheckboxField extends React.Component {
onToggle() {
this.props.onChange(this.props.name);
}
render() {
return (
<Checkbox
inline
checked={this.props.value}
onChange={this.onToggle.bind(this)}
>
<ControlLabelWithTooltip label={this.props.label} description={this.props.description} />
</Checkbox>
);
}
}
CheckboxField.propTypes = propTypes;
CheckboxField.defaultProps = defaultProps;

View File

@@ -0,0 +1,26 @@
import React, { PropTypes } from 'react';
import { ControlLabel } from 'react-bootstrap';
import InfoTooltipWithTrigger from '../../components/InfoTooltipWithTrigger';
const propTypes = {
label: PropTypes.string.isRequired,
description: PropTypes.string,
};
const defaultProps = {
description: null,
};
export default function ControlLabelWithTooltip({ label, description }) {
return (
<ControlLabel>
{label} &nbsp;
{description &&
<InfoTooltipWithTrigger label={label} tooltip={description} />
}
</ControlLabel>
);
}
ControlLabelWithTooltip.propTypes = propTypes;
ControlLabelWithTooltip.defaultProps = defaultProps;

View File

@@ -0,0 +1,43 @@
import React, { PropTypes } from 'react';
import { Panel } from 'react-bootstrap';
import InfoTooltipWithTrigger from '../../components/InfoTooltipWithTrigger';
const propTypes = {
label: PropTypes.string,
description: PropTypes.string,
tooltip: PropTypes.string,
children: PropTypes.node.isRequired,
};
const defaultProps = {
label: null,
description: null,
tooltip: null,
};
export default class ControlPanelSection extends React.Component {
renderHeader() {
const { label, tooltip } = this.props;
let header;
if (label) {
header = (
<div>
{label} &nbsp;
{tooltip && <InfoTooltipWithTrigger label={label} tooltip={tooltip} />}
</div>
);
}
return header;
}
render() {
return (
<Panel header={this.renderHeader()}>
{this.props.children}
</Panel>
);
}
}
ControlPanelSection.propTypes = propTypes;
ControlPanelSection.defaultProps = defaultProps;

View File

@@ -0,0 +1,99 @@
/* eslint camelcase: 0 */
import React, { PropTypes } from 'react';
import { bindActionCreators } from 'redux';
import * as actions from '../actions/exploreActions';
import { connect } from 'react-redux';
import { Panel } from 'react-bootstrap';
import { visTypes, commonControlPanelSections } from '../stores/store';
import ControlPanelSection from './ControlPanelSection';
import FieldSetRow from './FieldSetRow';
const propTypes = {
datasource_id: PropTypes.number.isRequired,
datasource_type: PropTypes.string.isRequired,
actions: PropTypes.object.isRequired,
fields: PropTypes.object.isRequired,
isDatasourceMetaLoading: PropTypes.bool.isRequired,
form_data: PropTypes.object.isRequired,
y_axis_zero: PropTypes.any,
};
class ControlPanelsContainer extends React.Component {
componentWillMount() {
const { datasource_id, datasource_type } = this.props;
if (datasource_id) {
this.props.actions.fetchFieldOptions(datasource_id, datasource_type);
}
}
onChange(name, value) {
this.props.actions.setFieldValue(name, value);
}
sectionsToRender() {
const viz = visTypes[this.props.form_data.viz_type];
const { datasourceAndVizType, sqlClause } = commonControlPanelSections;
const sectionsToRender = [datasourceAndVizType].concat(viz.controlPanelSections, sqlClause);
return sectionsToRender;
}
fieldOverrides() {
const viz = visTypes[this.props.form_data.viz_type];
return viz.fieldOverrides;
}
render() {
return (
<Panel>
{!this.props.isDatasourceMetaLoading &&
<div className="scrollbar-container">
<div className="scrollbar-content">
{this.sectionsToRender().map((section) => (
<ControlPanelSection
key={section.label}
label={section.label}
tooltip={section.description}
>
{section.fieldSetRows.map((fieldSets, i) => (
<FieldSetRow
key={`${section.label}-fieldSetRow-${i}`}
fieldSets={fieldSets}
fieldOverrides={this.fieldOverrides()}
onChange={this.onChange.bind(this)}
fields={this.props.fields}
form_data={this.props.form_data}
/>
))}
</ControlPanelSection>
))}
{/* TODO: add filters section */}
</div>
</div>
}
</Panel>
);
}
}
ControlPanelsContainer.propTypes = propTypes;
function mapStateToProps(state) {
return {
isDatasourceMetaLoading: state.isDatasourceMetaLoading,
fields: state.fields,
datasource_id: state.datasource_id,
datasource_type: state.datasource_type,
form_data: state.viz.form_data,
};
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators(actions, dispatch),
};
}
export { ControlPanelsContainer };
export default connect(mapStateToProps, mapDispatchToProps)(ControlPanelsContainer);

View File

@@ -0,0 +1,46 @@
import React from 'react';
import ChartContainer from './ChartContainer';
import ControlPanelsContainer from './ControlPanelsContainer';
import QueryAndSaveBtns from '../../explore/components/QueryAndSaveBtns';
export default class ExploreViewContainer extends React.Component {
constructor(props) {
super(props);
this.state = {
height: this.getHeight(),
};
}
getHeight() {
const navHeight = 90;
return `${window.innerHeight - navHeight}px`;
}
render() {
return (
<div
className="container-fluid"
style={{
height: this.state.height,
overflow: 'hidden',
}}
>
<div className="row">
<div className="col-sm-4">
<QueryAndSaveBtns
canAdd="True"
onQuery={() => {}}
/>
<br /><br />
<ControlPanelsContainer />
</div>
<div className="col-sm-8">
<ChartContainer
height={this.state.height}
/>
</div>
</div>
</div>
);
}
}

View File

@@ -0,0 +1,82 @@
import React, { PropTypes } from 'react';
import TextField from './TextField';
import CheckboxField from './CheckboxField';
import TextAreaField from './TextAreaField';
import SelectField from './SelectField';
import { fieldTypes } from '../stores/store';
const propTypes = {
name: PropTypes.string.isRequired,
type: PropTypes.oneOf(fieldTypes).isRequired,
label: PropTypes.string.isRequired,
choices: PropTypes.arrayOf(PropTypes.array),
description: PropTypes.string,
places: PropTypes.number,
validators: PropTypes.any,
onChange: React.PropTypes.func,
value: PropTypes.oneOf([PropTypes.string, PropTypes.bool, PropTypes.array]).isRequired,
};
const defaultProps = {
choices: null,
description: null,
places: null,
validators: null,
onChange: () => {},
};
export default class FieldSet extends React.Component {
renderCheckBoxField() {
return (
<CheckboxField
{...this.props}
/>);
}
renderTextAreaField() {
return (
<TextAreaField
{...this.props}
/>);
}
renderSelectField() {
return (
<SelectField
{...this.props}
/>);
}
renderTextField() {
return (
<TextField
{...this.props}
/>);
}
render() {
const type = this.props.type;
const selectTypes = [
'SelectField',
'SelectCustomMultiField',
'SelectMultipleSortableField',
'FreeFormSelectField',
];
let field;
if (type === 'CheckboxField') {
field = this.renderCheckBoxField();
} else if (selectTypes.includes(type)) {
field = this.renderSelectField();
} else if (['TextField', 'IntegerField'].includes(type)) {
field = this.renderTextField();
} else if (type === 'TextAreaField') {
field = this.renderTextAreaField();
}
return field;
}
}
FieldSet.propTypes = propTypes;
FieldSet.defaultProps = defaultProps;

View File

@@ -0,0 +1,52 @@
import React, { PropTypes } from 'react';
import FieldSet from './FieldSet';
const NUM_COLUMNS = 12;
const propTypes = {
fields: PropTypes.object.isRequired,
fieldSets: PropTypes.array.isRequired,
fieldOverrides: PropTypes.object,
onChange: PropTypes.func,
form_data: PropTypes.object.isRequired,
};
const defaultProps = {
fieldOverrides: {},
onChange: () => {},
};
export default class FieldSetRow extends React.Component {
getFieldData(fs) {
const { fields, fieldOverrides } = this.props;
let fieldData = fields[fs];
if (fieldOverrides.hasOwnProperty(fs)) {
const overrideData = fieldOverrides[fs];
fieldData = Object.assign({}, fieldData, overrideData);
}
return fieldData;
}
render() {
const colSize = NUM_COLUMNS / this.props.fieldSets.length;
return (
<div className="row">
{this.props.fieldSets.map((fs) => {
const fieldData = this.getFieldData(fs);
return (
<div className={`col-lg-${colSize} col-xs-12`} key={fs}>
<FieldSet
name={fs}
onChange={this.props.onChange}
value={this.props.form_data[fs]}
{...fieldData}
/>
</div>
);
})}
</div>
);
}
}
FieldSetRow.propTypes = propTypes;
FieldSetRow.defaultProps = defaultProps;

View File

@@ -0,0 +1,127 @@
import React from 'react';
// import { Tab, Row, Col, Nav, NavItem } from 'react-bootstrap';
import Select from 'react-select';
import { Button } from 'react-bootstrap';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import * as actions from '../actions/exploreActions';
import shortid from 'shortid';
const propTypes = {
actions: React.PropTypes.object,
filterColumnOpts: React.PropTypes.array,
filters: React.PropTypes.array,
};
const defaultProps = {
filterColumnOpts: [],
filters: [],
};
class Filters extends React.Component {
constructor(props) {
super(props);
this.state = {
opOpts: ['in', 'not in'],
};
}
changeField(filter, fieldOpt) {
const val = (fieldOpt) ? fieldOpt.value : null;
this.props.actions.changeFilterField(filter, val);
}
changeOp(filter, opOpt) {
const val = (opOpt) ? opOpt.value : null;
this.props.actions.changeFilterOp(filter, val);
}
changeValue(filter, value) {
this.props.actions.changeFilterValue(filter, value);
}
removeFilter(filter) {
this.props.actions.removeFilter(filter);
}
addFilter() {
this.props.actions.addFilter({
id: shortid.generate(),
field: null,
op: null,
value: null,
});
}
render() {
const filters = this.props.filters.map((filter) => (
<div>
<Select
className="row"
multi={false}
name="select-column"
placeholder="Select column"
options={this.props.filterColumnOpts}
value={filter.field}
autosize={false}
onChange={this.changeField.bind(this, filter)}
/>
<div className="row">
<Select
className="col-sm-3"
multi={false}
name="select-op"
placeholder="Select operator"
options={this.state.opOpts.map((o) => ({ value: o, label: o }))}
value={filter.op}
autosize={false}
onChange={this.changeOp.bind(this, filter)}
/>
<div className="col-sm-6">
<input
type="text"
onChange={this.changeValue.bind(this, filter)}
className="form-control input-sm"
placeholder="Filter value"
/>
</div>
<div className="col-sm-3">
<Button
bsStyle="primary"
onClick={this.removeFilter.bind(this, filter)}
>
<i className="fa fa-minus" />
</Button>
</div>
</div>
</div>
)
);
return (
<div className="panel space-1">
<div className="panel-header">Filters</div>
<div className="panel-body">
{filters}
<Button
bsStyle="primary"
onClick={this.addFilter.bind(this)}
>
<i className="fa fa-plus" />Add Filter
</Button>
</div>
</div>
);
}
}
Filters.propTypes = propTypes;
Filters.defaultProps = defaultProps;
function mapStateToProps(state) {
return {
filterColumnOpts: state.filterColumnOpts,
filters: state.filters,
};
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators(actions, dispatch),
};
}
export default connect(mapStateToProps, mapDispatchToProps)(Filters);

View File

@@ -0,0 +1,68 @@
import React from 'react';
import { bindActionCreators } from 'redux';
import * as actions from '../actions/exploreActions';
import { connect } from 'react-redux';
import SelectArray from './SelectArray';
const propTypes = {
actions: React.PropTypes.object,
metricsOpts: React.PropTypes.array,
metrics: React.PropTypes.array,
groupByColumnOpts: React.PropTypes.array,
groupByColumns: React.PropTypes.array,
};
const defaultProps = {
metricsOpts: [],
metrics: [],
groupByColumnOpts: [],
groupByColumns: [],
};
const GroupBy = (props) => {
const selects = [
{
key: 'groupByColumns',
title: 'Group By',
options: props.groupByColumnOpts,
value: props.groupByColumns,
multi: true,
width: '12',
},
{
key: 'metrics',
title: 'Metrics',
options: props.metricsOpts,
value: props.metrics,
multi: true,
width: '12',
}];
return (
<div className="panel">
<div className="panel-header">GroupBy</div>
<div className="panel-body">
<SelectArray selectArray={selects} />
</div>
</div>
);
};
GroupBy.propTypes = propTypes;
GroupBy.defaultProps = defaultProps;
function mapStateToProps(state) {
return {
metricsOpts: state.metricsOpts,
metrics: state.viz.formData.metrics,
groupByColumnOpts: state.groupByColumnOpts,
groupByColumns: state.viz.formData.groupByColumns,
};
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators(actions, dispatch),
};
}
export default connect(mapStateToProps, mapDispatchToProps)(GroupBy);

View File

@@ -0,0 +1,68 @@
import React from 'react';
import SelectArray from './SelectArray';
import { bindActionCreators } from 'redux';
import * as actions from '../actions/exploreActions';
import { connect } from 'react-redux';
const propTypes = {
actions: React.PropTypes.object,
columnOpts: React.PropTypes.array,
columns: React.PropTypes.array,
orderingOpts: React.PropTypes.array,
orderings: React.PropTypes.array,
};
const defaultProps = {
columnOpts: [],
columns: [],
orderingOpts: [],
orderings: [],
};
const NotGroupBy = (props) => {
const selects = [
{
key: 'columns',
title: 'Columns',
options: props.columnOpts,
value: props.columns,
multi: true,
width: '12',
},
{
key: 'orderings',
title: 'Orderings',
options: props.orderingOpts,
value: props.orderings,
multi: true,
width: '12',
}];
return (
<div className="panel">
<div className="panel-header">Not GroupBy</div>
<div className="panel-body">
<SelectArray selectArray={selects} />
</div>
</div>
);
};
NotGroupBy.propTypes = propTypes;
NotGroupBy.defaultProps = defaultProps;
function mapStateToProps(state) {
return {
columnOpts: state.columnOpts,
columns: state.viz.formData.columns,
orderingOpts: state.orderingOpts,
orderings: state.viz.formData.orderings,
};
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators(actions, dispatch),
};
}
export default connect(mapStateToProps, mapDispatchToProps)(NotGroupBy);

View File

@@ -0,0 +1,61 @@
import React from 'react';
import SelectArray from './SelectArray';
import { bindActionCreators } from 'redux';
import * as actions from '../actions/exploreActions';
import { connect } from 'react-redux';
import { timestampOptions, rowLimitOptions } from '../constants';
const propTypes = {
actions: React.PropTypes.object,
timeStampFormat: React.PropTypes.string,
rowLimit: React.PropTypes.number,
};
const defaultProps = {
timeStampFormat: null,
rowLimit: null,
};
const Options = (props) => {
const selects = [
{
key: 'timeStampFormat',
title: 'Timestamp Format',
options: timestampOptions.map((t) => ({ value: t[0], label: t[1] })),
value: props.timeStampFormat,
width: '12',
},
{
key: 'rowLimit',
title: 'Row Limit',
options: rowLimitOptions.map((r) => ({ value: r, label: r })),
value: props.rowLimit,
width: '12',
}];
return (
<div className="panel">
<div className="panel-header">Options</div>
<div className="panel-body">
<SelectArray selectArray={selects} />
</div>
</div>
);
};
Options.propTypes = propTypes;
Options.defaultProps = defaultProps;
function mapStateToProps(state) {
return {
timeStampFormat: state.viz.formData.timeStampFormat,
rowLimit: state.viz.formData.rowLimit,
};
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators(actions, dispatch),
};
}
export default connect(mapStateToProps, mapDispatchToProps)(Options);

View File

@@ -0,0 +1,74 @@
import React from 'react';
import Select from 'react-select';
import { bindActionCreators } from 'redux';
import * as actions from '../actions/exploreActions';
import { connect } from 'react-redux';
const propTypes = {
actions: React.PropTypes.object,
selectArray: React.PropTypes.arrayOf(
React.PropTypes.shape({
key: React.PropTypes.string.isRequired,
title: React.PropTypes.string.isRequired,
options: React.PropTypes.array.isRequired,
value: React.PropTypes.oneOfType([
React.PropTypes.string,
React.PropTypes.array,
]),
width: React.PropTypes.string,
multi: React.PropTypes.bool,
})
).isRequired,
};
const defaultProps = {
selectArray: [],
};
class SelectArray extends React.Component {
changeSelectData(key, multi, opt) {
if (multi) this.props.actions.setFormData(key, opt);
else {
const val = opt ? opt.value : null;
this.props.actions.setFormData(key, val);
}
}
render() {
const selects = this.props.selectArray.map((obj) => (
<div
className={(obj.width) ? `col-sm-${obj.width}` : 'col-sm-6'}
key={obj.key}
>
<h5 className="section-heading">{obj.title}</h5>
<Select
multi={obj.multi}
name={`select-${obj.key}`}
options={obj.options}
value={obj.value}
autosize={false}
onChange={this.changeSelectData.bind(this, obj.key, obj.multi)}
/>
</div>
));
return (
<div>
{selects}
</div>
);
}
}
SelectArray.propTypes = propTypes;
SelectArray.defaultProps = defaultProps;
function mapStateToProps() {
return {};
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators(actions, dispatch),
};
}
export default connect(mapStateToProps, mapDispatchToProps)(SelectArray);

View File

@@ -0,0 +1,48 @@
import React, { PropTypes } from 'react';
import { FormGroup, FormControl } from 'react-bootstrap';
import ControlLabelWithTooltip from './ControlLabelWithTooltip';
import { slugify } from '../../modules/utils';
const propTypes = {
name: PropTypes.string.isRequired,
choices: PropTypes.array,
value: PropTypes.string,
label: PropTypes.string,
description: PropTypes.string,
onChange: PropTypes.func,
};
const defaultProps = {
value: '',
label: null,
description: null,
onChange: () => {},
};
export default class SelectField extends React.Component {
onChange(opt) {
this.props.onChange(this.props.name, opt.target.value);
}
render() {
return (
<FormGroup controlId={`formControlsSelect-${slugify(this.props.label)}`}>
<ControlLabelWithTooltip
label={this.props.label}
description={this.props.description}
/>
<FormControl
componentClass="select"
placeholder="select"
onChange={this.onChange.bind(this)}
value={this.props.value}
>
{this.props.choices.map((c) => <option key={c[0]} value={c[0]}>{c[1]}</option>)}
</FormControl>
</FormGroup>
);
}
}
SelectField.propTypes = propTypes;
SelectField.defaultProps = defaultProps;

View File

@@ -0,0 +1,55 @@
import React from 'react';
import { bindActionCreators } from 'redux';
import * as actions from '../actions/exploreActions';
import { connect } from 'react-redux';
const propTypes = {
actions: React.PropTypes.object,
};
class SqlClause extends React.Component {
onChange(key, event) {
this.props.actions.setFormData(key, event.target.value);
}
render() {
return (
<div className="panel">
<div className="panel-header">SQL</div>
<div className="panel-body">
<div className="row">
<h5 className="section-heading">Where</h5>
<input
type="text"
onChange={this.onChange.bind(this, 'where')}
className="form-control input-sm"
placeholder="Where Clause"
/>
</div>
<div className="row">
<h5 className="section-heading">Having</h5>
<input
type="text"
onChange={this.onChange.bind(this, 'having')}
className="form-control input-sm"
placeholder="Having Clause"
/>
</div>
</div>
</div>
);
}
}
SqlClause.propTypes = propTypes;
function mapStateToProps() {
return {};
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators(actions, dispatch),
};
}
export default connect(mapStateToProps, mapDispatchToProps)(SqlClause);

View File

@@ -0,0 +1,40 @@
import React, { PropTypes } from 'react';
import { FormGroup, FormControl } from 'react-bootstrap';
import ControlLabelWithTooltip from './ControlLabelWithTooltip';
const propTypes = {
name: PropTypes.string.isRequired,
label: PropTypes.string,
description: PropTypes.string,
onChange: PropTypes.func,
value: PropTypes.string,
};
const defaultProps = {
label: null,
description: null,
onChange: () => {},
value: '',
};
export default class TextAreaField extends React.Component {
onChange(event) {
this.props.onChange(this.props.name, event.target.value);
}
render() {
return (
<FormGroup controlId="formControlsTextarea">
<ControlLabelWithTooltip label={this.props.label} description={this.props.description} />
<FormControl
componentClass="textarea"
placeholder="textarea"
onChange={this.onChange.bind(this)}
value={this.props.value}
/>
</FormGroup>
);
}
}
TextAreaField.propTypes = propTypes;
TextAreaField.defaultProps = defaultProps;

View File

@@ -0,0 +1,43 @@
import React, { PropTypes } from 'react';
import { FormGroup, FormControl } from 'react-bootstrap';
import ControlLabelWithTooltip from './ControlLabelWithTooltip';
const propTypes = {
name: PropTypes.string.isRequired,
label: PropTypes.string,
description: PropTypes.string,
onChange: PropTypes.func,
value: PropTypes.string,
};
const defaultProps = {
label: null,
description: null,
onChange: () => {},
value: '',
};
export default class TextField extends React.Component {
onChange(event) {
this.props.onChange(this.props.name, event.target.value);
}
render() {
return (
<FormGroup controlId="formInlineName">
<ControlLabelWithTooltip
label={this.props.label}
description={this.props.description}
/>
<FormControl
type="text"
placeholder=""
onChange={this.onChange.bind(this)}
value={this.props.value}
/>
</FormGroup>
);
}
}
TextField.propTypes = propTypes;
TextField.defaultProps = defaultProps;

View File

@@ -0,0 +1,88 @@
import React from 'react';
import { bindActionCreators } from 'redux';
import * as actions from '../actions/exploreActions';
import { connect } from 'react-redux';
import { sinceOptions, untilOptions } from '../constants';
import SelectArray from './SelectArray';
const propTypes = {
actions: React.PropTypes.object,
datasourceType: React.PropTypes.string,
timeColumnOpts: React.PropTypes.array,
timeColumn: React.PropTypes.string,
timeGrainOpts: React.PropTypes.array,
timeGrain: React.PropTypes.string,
since: React.PropTypes.string,
until: React.PropTypes.string,
};
const defaultProps = {
timeColumnOpts: [],
timeColumn: null,
timeGrainOpts: [],
timeGrain: null,
since: null,
until: null,
};
const TimeFilter = (props) => {
const isDatasourceTypeTable = props.datasourceType === 'table';
const timeColumnTitle = isDatasourceTypeTable ? 'Time Column' : 'Time Granularity';
const timeGrainTitle = isDatasourceTypeTable ? 'Time Grain' : 'Origin';
const selects = [
{
key: 'timeColumn',
title: timeColumnTitle,
options: props.timeColumnOpts,
value: props.timeColumn,
},
{
key: 'timeGrain',
title: timeGrainTitle,
options: props.timeGrainOpts,
value: props.timeGrain,
},
{
key: 'since',
title: 'Since',
options: sinceOptions.map((s) => ({ value: s, label: s })),
value: props.since,
},
{
key: 'until',
title: 'Until',
options: untilOptions.map((u) => ({ value: u, label: u })),
value: props.until,
}];
return (
<div className="panel">
<div className="panel-header">Time Filter</div>
<div className="panel-body">
<SelectArray selectArray={selects} />
</div>
</div>
);
};
TimeFilter.propTypes = propTypes;
TimeFilter.defaultProps = defaultProps;
function mapStateToProps(state) {
return {
datasourceType: state.datasourceType,
timeColumnOpts: state.timeColumnOpts,
timeColumn: state.viz.formData.timeColumn,
timeGrainOpts: state.timeGrainOpts,
timeGrain: state.viz.formData.timeGrain,
since: state.viz.formData.since,
until: state.viz.formData.until,
};
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators(actions, dispatch),
};
}
export default connect(mapStateToProps, mapDispatchToProps)(TimeFilter);