diff --git a/superset/assets/javascripts/explorev2/components/FieldSet.jsx b/superset/assets/javascripts/explorev2/components/FieldSet.jsx index b2a11823aa2c215d36ac13c6b8fa11a79c411e29..e2483a46ecd6839de883eb9a05a07b2c9f83374d 100644 --- a/superset/assets/javascripts/explorev2/components/FieldSet.jsx +++ b/superset/assets/javascripts/explorev2/components/FieldSet.jsx @@ -3,14 +3,18 @@ import TextField from './TextField'; import CheckboxField from './CheckboxField'; import TextAreaField from './TextAreaField'; import SelectField from './SelectField'; +import MetricList from './MetricList'; +import MetricField from './MetricField'; import ControlLabelWithTooltip from './ControlLabelWithTooltip'; const fieldMap = { - TextField, CheckboxField, - TextAreaField, + MetricField, + MetricList, SelectField, + TextAreaField, + TextField, }; const fieldTypes = Object.keys(fieldMap); diff --git a/superset/assets/javascripts/explorev2/components/Filters.jsx b/superset/assets/javascripts/explorev2/components/Filters.jsx index be0cd11289e61e25fed541589fc1c852bd707d05..e752df51f6a76c1c4329c359b6c31dea2d44a620 100644 --- a/superset/assets/javascripts/explorev2/components/Filters.jsx +++ b/superset/assets/javascripts/explorev2/components/Filters.jsx @@ -1,5 +1,4 @@ import React from 'react'; -// import { Tab, Row, Col, Nav, NavItem } from 'react-bootstrap'; import Filter from './Filter'; import { Button } from 'react-bootstrap'; import { connect } from 'react-redux'; diff --git a/superset/assets/javascripts/explorev2/components/MetricField.jsx b/superset/assets/javascripts/explorev2/components/MetricField.jsx new file mode 100644 index 0000000000000000000000000000000000000000..e21df6b47688e1e0528c259e43427c6cdbf1fe3e --- /dev/null +++ b/superset/assets/javascripts/explorev2/components/MetricField.jsx @@ -0,0 +1,303 @@ +import React, { PropTypes } from 'react'; +import Select from 'react-select'; +import { + Col, + FormControl, + FormGroup, + InputGroup, + Label, + OverlayTrigger, + Popover, + Radio, + Row, +} from 'react-bootstrap'; + +const numericAggFunctions = { + SUM: 'SUM({})', + MIN: 'MIN({})', + MAX: 'MAX({})', + AVG: 'AVG({})', + COUNT_DISTINCT: 'COUNT(DISTINCT {})', + COUNT: 'COUNT({})', +}; +const NUMERIC_TYPES = ['INT', 'INTEGER', 'BIGINT', 'DOUBLE', 'FLOAT', 'NUMERIC']; +const nonNumericAggFunctions = { + COUNT_DISTINCT: 'COUNT(DISTINCT {})', + COUNT: 'COUNT({})', +}; + +const propTypes = { + datasource: PropTypes.object, + column: PropTypes.string, + metricType: PropTypes.string, + onChange: PropTypes.func, + initialMetricType: PropTypes.string, + initialLabel: PropTypes.string, + initialSql: PropTypes.string, + onDelete: PropTypes.func, +}; + +const defaultProps = { + initialMetricType: 'free', + initialLabel: 'row_count', + initialSql: 'COUNT(*)', +}; + +export default class MetricField extends React.Component { + constructor(props) { + super(props); + this.state = { + aggregate: null, + label: props.initialLabel, + metricType: props.initialMetricType, + metricName: null, + sql: props.initialSql, + }; + this.getColumnOptions = this.getColumnOptions.bind(this); + } + onChange() { + console.log(this.state); + this.props.onChange(this.state); + } + onDelete() { + this.props.onDelete(); + } + getColumnOptions() { + return this.props.datasource.columns.map(col => ({ + value: col.column_name, + label: col.column_name, + column: col, + })); + } + setMetricType(v) { + this.setState({ metricType: v }); + } + getMetricOptions() { + return this.props.datasource.metrics.map(metric => ({ + value: metric.metric_name, + label: metric.metric_name, + metric, + type: 'metric', + })); + } + changeLabel(e) { + const label = e.target.value; + this.setState({ label }, this.onChange); + } + changeExpression(e) { + const sql = e.target.value; + this.setState({ sql, columnName: null, aggregate: null }, this.onChange); + } + optionify(arr) { + return arr.map(s => ({ value: s, label: s })); + } + changeColumnSection() { + let label; + if (this.state.aggregate && this.state.column) { + label = this.state.aggregate + '__' + this.state.column.column_name; + } else { + label = ''; + } + this.setState({ label }, this.onChange); + } + changeAggregate(opt) { + const aggregate = opt ? opt.value : null; + this.setState({ aggregate }, this.changeColumnSection); + } + changeRadio(e) { + this.setState({ metricType: e.target.value }); + } + changeMetric(opt) { + let metricName; + let label; + if (opt) { + metricName = opt.metric.metric_name; + label = metricName; + } + this.setState({ label, metricName }, this.onChange); + } + changeColumn(opt) { + let column; + let aggregate = this.state.aggregate; + if (opt) { + column = opt.column; + if (!aggregate) { + if (NUMERIC_TYPES.includes(column.type)) { + aggregate = 'SUM'; + } else { + aggregate = 'COUNT_DISTINCT'; + } + } + } else { + aggregate = null; + } + this.setState({ column, aggregate }, this.changeColumnSection); + } + renderOverlay() { + let aggregateOptions = []; + const column = this.state.column; + if (column) { + if (NUMERIC_TYPES.includes(column.type)) { + aggregateOptions = Object.keys(numericAggFunctions); + } else { + aggregateOptions = Object.keys(nonNumericAggFunctions); + } + } + const metricType = this.state.metricType; + return ( + + + + Label + + + +
+
+ + + + + +
+ ( +
+ Aggregate: {o.label} +
+ )} + /> +
+ +
+
+
+
+ + + + + +