Skip to content
Snippets Groups Projects
Unverified Commit e945aef2 authored by Tom Robinson's avatar Tom Robinson
Browse files

named aggregation #2

parent 89892e8a
No related branches found
No related tags found
No related merge requests found
import React, { Component, PropTypes } from "react";
import Popover from "metabase/components/Popover.jsx";
import { NamedClause } from "metabase/lib/query";
import cx from "classnames";
const NamedColumn = ({ valueProp, updaterProp, nameIsEditable }) => (ComposedComponent) => class NamedWidget extends Component {
constructor(props, context) {
super(props, context);
this.state = {
isHovered: false
};
}
render() {
const name = NamedClause.getName(this.props[valueProp]);
const clause = NamedClause.getContent(this.props[valueProp]);
const props = {
...this.props,
name: name,
[valueProp]: clause,
[updaterProp]: (clause) => this.props[updaterProp](name ? ["named", clause, name] : clause)
};
const isEditable = this.state.isHovered && this.props[updaterProp] && nameIsEditable(props);
return (
<div
className={cx(this.props.className, "relative")}
onMouseEnter={() => this.setState({ isHovered: true })}
onMouseLeave={() => this.setState({ isHovered: false })}
>
<ComposedComponent {...props} className="spread" />
<Popover isOpen={isEditable}>
<input
className="input m1"
value={name || ""}
onChange={(e) => this.props[updaterProp](e.target.value ? ["named", clause, e.target.value] : clause)}
autoFocus
/>
</Popover>
</div>
);
}
}
export default NamedColumn;
......@@ -628,6 +628,14 @@ export const NamedClause = {
},
getContent(clause) {
return NamedClause.isNamed(clause) ? clause[1] : clause;
},
setName(clause, name) {
return ["named", NamedClause.getContent(clause), name];
},
setContent(clause, content) {
return NamedClause.isNamed(clause) ?
["named", content, NamedClause.getName(clause)] :
content;
}
}
......@@ -672,7 +680,8 @@ export const AggregationClause = {
},
isCustom(aggregation) {
return aggregation && isMath(aggregation) || (
// for now treal all named clauses as custom
return aggregation && NamedClause.isNamed(aggregation) || isMath(aggregation) || (
AggregationClause.isStandard(aggregation) && _.any(aggregation.slice(1), (arg) => isMath(arg))
);
},
......
......@@ -9,7 +9,7 @@ import Tooltip from "metabase/components/Tooltip.jsx";
import Button from "metabase/components/Button.jsx";
import Query from "metabase/lib/query";
import { AggregationClause } from "metabase/lib/query";
import { AggregationClause, NamedClause } from "metabase/lib/query";
import _ from "underscore";
......@@ -23,8 +23,8 @@ export default class AggregationPopover extends Component {
this.state = {
aggregation: (props.isNew ? [] : props.aggregation),
choosingField: (props.aggregation && props.aggregation.length > 1 && AggregationClause.isStandard(props.aggregation)),
editingAggregation: (props.aggregation && props.aggregation.length > 1 && AggregationClause.isCustom(props.aggregation))
choosingField: AggregationClause.isStandard(props.aggregation),
editingAggregation: AggregationClause.isCustom(props.aggregation)
};
_.bindAll(this, "commitAggregation", "onPickAggregation", "onPickField", "onClearAggregation");
......@@ -85,7 +85,7 @@ export default class AggregationPopover extends Component {
itemIsSelected(item) {
const { aggregation } = this.props;
return item.isSelected(aggregation);
return item.isSelected(NamedClause.getContent(aggregation));
}
renderItemExtra(item, itemIndex) {
......@@ -115,7 +115,8 @@ export default class AggregationPopover extends Component {
render() {
const { availableAggregations, tableMetadata, isNew } = this.props;
const { aggregation, choosingField, editingAggregation } = this.state;
const { choosingField, editingAggregation } = this.state;
const aggregation = NamedClause.getContent(this.state.aggregation);
let selectedAggregation;
if (AggregationClause.isMetric(aggregation)) {
......@@ -180,8 +181,13 @@ export default class AggregationPopover extends Component {
expression={aggregation}
tableMetadata={tableMetadata}
customFields={this.props.customFields}
onChange={(parsedExpression) => this.setState({aggregation: parsedExpression, error: null})}
onError={(errorMessage) => this.setState({error: errorMessage})}
onChange={(parsedExpression) => this.setState({
aggregation: NamedClause.setContent(this.state.aggregation, parsedExpression),
error: null
})}
onError={(errorMessage) => this.setState({
error: errorMessage
})}
/>
{ this.state.error != null && (
Array.isArray(this.state.error) ?
......@@ -191,6 +197,16 @@ export default class AggregationPopover extends Component {
:
<div className="text-error mb1">{this.state.error.message}</div>
)}
<input
className="input block full my1"
value={NamedClause.getName(this.state.aggregation)}
onChange={(e) => this.setState({
aggregation: e.target.value ?
NamedClause.setName(aggregation, e.target.value) :
aggregation
})}
placeholder="Aggregation name (optional)"
/>
<Button className="full" primary disabled={this.state.error} onClick={() => this.commitAggregation(this.state.aggregation)}>
{isNew ? "Add" : "Update"} Aggregation
</Button>
......
......@@ -5,21 +5,15 @@ import FieldName from './FieldName.jsx';
import Clearable from './Clearable.jsx';
import Popover from "metabase/components/Popover.jsx";
import NamedColumn from "metabase/hoc/NamedColumn.jsx";
import Query from "metabase/lib/query";
import { AggregationClause } from "metabase/lib/query";
import { AggregationClause, NamedClause } from "metabase/lib/query";
import { getAggregator } from "metabase/lib/schema_metadata";
import { format } from "metabase/lib/expressions/formatter";
import cx from "classnames";
import _ from "underscore";
@NamedColumn({
valueProp: "aggregation",
updaterProp: "updateAggregation",
nameIsEditable: (props) => props.aggregation && props.aggregation[0] && props.aggregation[0] != "rows"
})
export default class AggregationWidget extends Component {
constructor(props, context) {
super(props, context);
......@@ -121,7 +115,9 @@ export default class AggregationWidget extends Component {
render() {
const { aggregation, addButton, name } = this.props;
if (aggregation && aggregation.length > 0) {
let aggregationName = AggregationClause.isCustom(aggregation) ?
let aggregationName = NamedClause.isNamed(aggregation) ?
NamedClause.getName(aggregation)
: AggregationClause.isCustom(aggregation) ?
this.renderCustomAggregation()
: AggregationClause.isMetric(aggregation) ?
this.renderMetricAggregation()
......
......@@ -103,7 +103,10 @@ export default class TokenizedInput extends Component {
inputNode.removeChild(inputNode.firstChild);
}
ReactDOM.render(<TokenizedExpression source={this._getValue()} />, inputNode);
restore();
if (document.activeElement === inputNode) {
restore();
}
}
render() {
const { className, onFocus, onBlur, onClick } = this.props;
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment