-
Notifications
You must be signed in to change notification settings - Fork 14k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[Explore] Streamlined metric definitions for SQLA and Druid (#4663)
* adding streamlined metric editing * addressing lint issues on new metrics control * enabling druid
- Loading branch information
1 parent
7e1b6b7
commit 68dec24
Showing
36 changed files
with
1,517 additions
and
45 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
export default class AdhocMetric { | ||
constructor(adhocMetric) { | ||
this.column = adhocMetric.column; | ||
this.aggregate = adhocMetric.aggregate; | ||
this.hasCustomLabel = !!(adhocMetric.hasCustomLabel && adhocMetric.label); | ||
this.fromFormData = !!adhocMetric.optionName; | ||
this.label = this.hasCustomLabel ? adhocMetric.label : this.getDefaultLabel(); | ||
|
||
this.optionName = adhocMetric.optionName || | ||
`metric_${Math.random().toString(36).substring(2, 15)}_${Math.random().toString(36).substring(2, 15)}`; | ||
} | ||
|
||
getDefaultLabel() { | ||
return `${this.aggregate || ''}(${(this.column && this.column.column_name) || ''})`; | ||
} | ||
|
||
duplicateWith(nextFields) { | ||
return new AdhocMetric({ | ||
...this, | ||
...nextFields, | ||
}); | ||
} | ||
|
||
equals(adhocMetric) { | ||
return adhocMetric.label === this.label && | ||
adhocMetric.aggregate === this.aggregate && | ||
( | ||
(adhocMetric.column && adhocMetric.column.column_name) === | ||
(this.column && this.column.column_name) | ||
); | ||
} | ||
} |
141 changes: 141 additions & 0 deletions
141
superset/assets/javascripts/explore/components/AdhocMetricEditPopover.jsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,141 @@ | ||
import React from 'react'; | ||
import PropTypes from 'prop-types'; | ||
import { Button, ControlLabel, FormGroup, Popover } from 'react-bootstrap'; | ||
import VirtualizedSelect from 'react-virtualized-select'; | ||
|
||
import { AGGREGATES } from '../constants'; | ||
import { t } from '../../locales'; | ||
import VirtualizedRendererWrap from '../../components/VirtualizedRendererWrap'; | ||
import OnPasteSelect from '../../components/OnPasteSelect'; | ||
import AdhocMetricEditPopoverTitle from './AdhocMetricEditPopoverTitle'; | ||
import columnType from '../propTypes/columnType'; | ||
import AdhocMetric from '../AdhocMetric'; | ||
import ColumnOption from '../../components/ColumnOption'; | ||
|
||
const propTypes = { | ||
adhocMetric: PropTypes.instanceOf(AdhocMetric).isRequired, | ||
onChange: PropTypes.func.isRequired, | ||
onClose: PropTypes.func.isRequired, | ||
columns: PropTypes.arrayOf(columnType), | ||
datasourceType: PropTypes.string, | ||
}; | ||
|
||
const defaultProps = { | ||
columns: [], | ||
}; | ||
|
||
export default class AdhocMetricEditPopover extends React.Component { | ||
constructor(props) { | ||
super(props); | ||
this.onSave = this.onSave.bind(this); | ||
this.onColumnChange = this.onColumnChange.bind(this); | ||
this.onAggregateChange = this.onAggregateChange.bind(this); | ||
this.onLabelChange = this.onLabelChange.bind(this); | ||
this.state = { adhocMetric: this.props.adhocMetric }; | ||
this.selectProps = { | ||
multi: false, | ||
name: 'select-column', | ||
labelKey: 'label', | ||
autosize: false, | ||
clearable: true, | ||
selectWrap: VirtualizedSelect, | ||
}; | ||
} | ||
|
||
onSave() { | ||
this.props.onChange(this.state.adhocMetric); | ||
this.props.onClose(); | ||
} | ||
|
||
onColumnChange(column) { | ||
this.setState({ adhocMetric: this.state.adhocMetric.duplicateWith({ column }) }); | ||
} | ||
|
||
onAggregateChange(aggregate) { | ||
// we construct this object explicitly to overwrite the value in the case aggregate is null | ||
this.setState({ | ||
adhocMetric: this.state.adhocMetric.duplicateWith({ | ||
aggregate: aggregate && aggregate.aggregate, | ||
}), | ||
}); | ||
} | ||
|
||
onLabelChange(e) { | ||
this.setState({ | ||
adhocMetric: this.state.adhocMetric.duplicateWith({ | ||
label: e.target.value, hasCustomLabel: true, | ||
}), | ||
}); | ||
} | ||
|
||
render() { | ||
const { adhocMetric, columns, onChange, onClose, datasourceType, ...popoverProps } = this.props; | ||
|
||
const columnSelectProps = { | ||
placeholder: t('%s column(s)', columns.length), | ||
options: columns, | ||
value: this.state.adhocMetric.column && this.state.adhocMetric.column.column_name, | ||
onChange: this.onColumnChange, | ||
optionRenderer: VirtualizedRendererWrap(option => ( | ||
<ColumnOption column={option} showType /> | ||
)), | ||
valueRenderer: column => column.column_name, | ||
valueKey: 'column_name', | ||
}; | ||
|
||
const aggregateSelectProps = { | ||
placeholder: t('%s aggregates(s)', Object.keys(AGGREGATES).length), | ||
options: Object.keys(AGGREGATES).map(aggregate => ({ aggregate })), | ||
value: this.state.adhocMetric.aggregate, | ||
onChange: this.onAggregateChange, | ||
optionRenderer: VirtualizedRendererWrap(aggregate => aggregate.aggregate), | ||
valueRenderer: aggregate => aggregate.aggregate, | ||
valueKey: 'aggregate', | ||
}; | ||
|
||
if (this.props.datasourceType === 'druid') { | ||
aggregateSelectProps.options = aggregateSelectProps.options.filter(( | ||
option => option.aggregate !== 'AVG' | ||
)); | ||
} | ||
|
||
const popoverTitle = ( | ||
<AdhocMetricEditPopoverTitle | ||
adhocMetric={this.state.adhocMetric} | ||
onChange={this.onLabelChange} | ||
/> | ||
); | ||
|
||
const stateIsValid = this.state.adhocMetric.column && this.state.adhocMetric.aggregate; | ||
const hasUnsavedChanges = this.state.adhocMetric.equals(this.props.adhocMetric); | ||
|
||
return ( | ||
<Popover | ||
id="metrics-edit-popover" | ||
title={popoverTitle} | ||
{...popoverProps} | ||
> | ||
<FormGroup> | ||
<ControlLabel><strong>column</strong></ControlLabel> | ||
<OnPasteSelect {...this.selectProps} {...columnSelectProps} /> | ||
</FormGroup> | ||
<FormGroup> | ||
<ControlLabel><strong>aggregate</strong></ControlLabel> | ||
<OnPasteSelect {...this.selectProps} {...aggregateSelectProps} /> | ||
</FormGroup> | ||
<Button | ||
disabled={!stateIsValid} | ||
bsStyle={(hasUnsavedChanges || !stateIsValid) ? 'default' : 'primary'} | ||
bsSize="small" | ||
className="m-r-5" | ||
onClick={this.onSave} | ||
> | ||
Save | ||
</Button> | ||
<Button bsSize="small" onClick={this.props.onClose}>Close</Button> | ||
</Popover> | ||
); | ||
} | ||
} | ||
AdhocMetricEditPopover.propTypes = propTypes; | ||
AdhocMetricEditPopover.defaultProps = defaultProps; |
79 changes: 79 additions & 0 deletions
79
superset/assets/javascripts/explore/components/AdhocMetricEditPopoverTitle.jsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
import React from 'react'; | ||
import PropTypes from 'prop-types'; | ||
import { FormControl, OverlayTrigger, Tooltip } from 'react-bootstrap'; | ||
import AdhocMetric from '../AdhocMetric'; | ||
|
||
const propTypes = { | ||
adhocMetric: PropTypes.instanceOf(AdhocMetric), | ||
onChange: PropTypes.func.isRequired, | ||
}; | ||
|
||
export default class AdhocMetricEditPopoverTitle extends React.Component { | ||
constructor(props) { | ||
super(props); | ||
this.onMouseOver = this.onMouseOver.bind(this); | ||
this.onMouseOut = this.onMouseOut.bind(this); | ||
this.onClick = this.onClick.bind(this); | ||
this.onBlur = this.onBlur.bind(this); | ||
this.state = { | ||
isHovered: false, | ||
isEditable: false, | ||
}; | ||
} | ||
|
||
onMouseOver() { | ||
this.setState({ isHovered: true }); | ||
} | ||
|
||
onMouseOut() { | ||
this.setState({ isHovered: false }); | ||
} | ||
|
||
onClick() { | ||
this.setState({ isEditable: true }); | ||
} | ||
|
||
onBlur() { | ||
this.setState({ isEditable: false }); | ||
} | ||
|
||
refFunc(ref) { | ||
if (ref) { | ||
ref.focus(); | ||
} | ||
} | ||
|
||
render() { | ||
const { adhocMetric, onChange } = this.props; | ||
|
||
const editPrompt = <Tooltip id="edit-metric-label-tooltip">Click to edit label</Tooltip>; | ||
|
||
return ( | ||
<OverlayTrigger | ||
placement="top" | ||
overlay={editPrompt} | ||
onMouseOver={this.onMouseOver} | ||
onMouseOut={this.onMouseOut} | ||
onClick={this.onClick} | ||
onBlur={this.onBlur} | ||
> | ||
{this.state.isEditable ? | ||
<FormControl | ||
className="metric-edit-popover-label-input" | ||
type="text" | ||
placeholder={adhocMetric.label} | ||
value={adhocMetric.hasCustomLabel ? adhocMetric.label : ''} | ||
onChange={onChange} | ||
inputRef={this.refFunc} | ||
/> : | ||
<span> | ||
{adhocMetric.hasCustomLabel ? adhocMetric.label : 'My Metric'} | ||
| ||
<i className="fa fa-pencil" style={{ color: this.state.isHovered ? 'black' : 'grey' }} /> | ||
</span> | ||
} | ||
</OverlayTrigger> | ||
); | ||
} | ||
} | ||
AdhocMetricEditPopoverTitle.propTypes = propTypes; |
60 changes: 60 additions & 0 deletions
60
superset/assets/javascripts/explore/components/AdhocMetricOption.jsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
import React from 'react'; | ||
import PropTypes from 'prop-types'; | ||
import { Label, OverlayTrigger } from 'react-bootstrap'; | ||
|
||
import AdhocMetricEditPopover from './AdhocMetricEditPopover'; | ||
import AdhocMetric from '../AdhocMetric'; | ||
import columnType from '../propTypes/columnType'; | ||
|
||
const propTypes = { | ||
adhocMetric: PropTypes.instanceOf(AdhocMetric), | ||
onMetricEdit: PropTypes.func.isRequired, | ||
columns: PropTypes.arrayOf(columnType), | ||
multi: PropTypes.bool, | ||
datasourceType: PropTypes.string, | ||
}; | ||
|
||
export default class AdhocMetricOption extends React.PureComponent { | ||
constructor(props) { | ||
super(props); | ||
this.closeMetricEditOverlay = this.closeMetricEditOverlay.bind(this); | ||
} | ||
|
||
closeMetricEditOverlay() { | ||
this.refs.overlay.hide(); | ||
} | ||
|
||
render() { | ||
const { adhocMetric } = this.props; | ||
const overlay = ( | ||
<AdhocMetricEditPopover | ||
adhocMetric={adhocMetric} | ||
onChange={this.props.onMetricEdit} | ||
onClose={this.closeMetricEditOverlay} | ||
columns={this.props.columns} | ||
datasourceType={this.props.datasourceType} | ||
/> | ||
); | ||
|
||
return ( | ||
<OverlayTrigger | ||
ref="overlay" | ||
placement="right" | ||
trigger="click" | ||
disabled | ||
overlay={overlay} | ||
rootClose | ||
defaultOverlayShown={!adhocMetric.fromFormData} | ||
> | ||
<Label style={{ margin: this.props.multi ? 0 : 3, cursor: 'pointer' }}> | ||
<div onMouseDownCapture={(e) => { e.stopPropagation(); }}> | ||
<span className="m-r-5 option-label"> | ||
{adhocMetric.label} | ||
</span> | ||
</div> | ||
</Label> | ||
</OverlayTrigger> | ||
); | ||
} | ||
} | ||
AdhocMetricOption.propTypes = propTypes; |
22 changes: 22 additions & 0 deletions
22
superset/assets/javascripts/explore/components/AggregateOption.jsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
import React from 'react'; | ||
import PropTypes from 'prop-types'; | ||
|
||
import ColumnTypeLabel from '../../components/ColumnTypeLabel'; | ||
import aggregateOptionType from '../propTypes/aggregateOptionType'; | ||
|
||
const propTypes = { | ||
aggregate: aggregateOptionType, | ||
showType: PropTypes.bool, | ||
}; | ||
|
||
export default function AggregateOption({ aggregate, showType }) { | ||
return ( | ||
<div> | ||
{showType && <ColumnTypeLabel type="aggregate" />} | ||
<span className="m-r-5 option-label"> | ||
{aggregate.aggregate_name} | ||
</span> | ||
</div> | ||
); | ||
} | ||
AggregateOption.propTypes = propTypes; |
Oops, something went wrong.