mirror of
https://github.com/apache/superset.git
synced 2026-04-19 08:04:53 +00:00
[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:
committed by
GitHub
parent
973537fd9a
commit
15b67b2c6c
@@ -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);
|
||||
@@ -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);
|
||||
@@ -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;
|
||||
@@ -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}
|
||||
{description &&
|
||||
<InfoTooltipWithTrigger label={label} tooltip={description} />
|
||||
}
|
||||
</ControlLabel>
|
||||
);
|
||||
}
|
||||
|
||||
ControlLabelWithTooltip.propTypes = propTypes;
|
||||
ControlLabelWithTooltip.defaultProps = defaultProps;
|
||||
@@ -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}
|
||||
{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;
|
||||
@@ -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);
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
127
superset/assets/javascripts/explorev2/components/Filters.jsx
Normal file
127
superset/assets/javascripts/explorev2/components/Filters.jsx
Normal 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);
|
||||
68
superset/assets/javascripts/explorev2/components/GroupBy.jsx
Normal file
68
superset/assets/javascripts/explorev2/components/GroupBy.jsx
Normal 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);
|
||||
@@ -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);
|
||||
61
superset/assets/javascripts/explorev2/components/Options.jsx
Normal file
61
superset/assets/javascripts/explorev2/components/Options.jsx
Normal 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);
|
||||
@@ -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);
|
||||
@@ -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;
|
||||
@@ -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);
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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);
|
||||
Reference in New Issue
Block a user