Merge pull request #1697 from influxdata/hotfix/custom-time-range

Custom Time Range Indicator Hotfix
pull/10616/head
Andrew Watkins 2017-07-07 16:52:29 -07:00 committed by GitHub
commit f9be34655f
8 changed files with 232 additions and 133 deletions

View File

@ -26,16 +26,19 @@ const Dashboard = ({
}) => {
const cells = dashboard.cells.map(cell => {
const dashboardCell = {...cell}
dashboardCell.queries = dashboardCell.queries.map(
({label, query, queryConfig, db}) => ({
label,
query,
queryConfig,
db,
database: db,
text: query,
})
)
dashboardCell.queries = dashboardCell.queries.map(({
label,
query,
queryConfig,
db,
}) => ({
label,
query,
queryConfig,
db,
database: db,
text: query,
}))
return dashboardCell
})
@ -57,6 +60,7 @@ const Dashboard = ({
{cells.length
? <LayoutRenderer
templates={templatesIncludingDashTime}
isEditable={true}
cells={cells}
timeRange={timeRange}
autoRefresh={autoRefresh}

View File

@ -174,7 +174,7 @@ export const HostPage = React.createClass({
autoRefresh={autoRefresh}
source={source}
host={this.props.params.hostID}
shouldNotBeEditable={true}
isEditable={false}
synchronizer={this.synchronizer}
/>
)

View File

@ -0,0 +1,52 @@
import React, {PropTypes} from 'react'
import classnames from 'classnames'
import OnClickOutside from 'react-onclickoutside'
const ContextMenu = OnClickOutside(({
isOpen,
toggleMenu,
onEdit,
onRename,
onDelete,
cell,
}) => (
<div
className={classnames('dash-graph--options', {
'dash-graph--options-show': isOpen,
})}
onClick={toggleMenu}
>
<button className="btn btn-info btn-xs">
<span className="icon caret-down" />
</button>
<ul className="dash-graph--options-menu">
<li onClick={() => onEdit(cell)}>Edit</li>
<li onClick={onRename(cell.x, cell.y, cell.isEditing)}>Rename</li>
<li onClick={() => onDelete(cell)}>Delete</li>
</ul>
</div>
))
const ContextMenuContainer = props => {
if (!props.isEditable) {
return null
}
return <ContextMenu {...props} />
}
const {bool, func, shape} = PropTypes
ContextMenuContainer.propTypes = {
isOpen: bool,
toggleMenu: func,
onEdit: func,
onRename: func,
onDelete: func,
cell: shape(),
isEditable: bool,
}
ContextMenu.propTypes = ContextMenuContainer.propTypes
export default ContextMenuContainer

View File

@ -0,0 +1,26 @@
import React, {PropTypes} from 'react'
import _ from 'lodash'
const CustomTimeIndicator = ({queries}) => {
const q = queries.find(({query}) => !query.includes(':dashboardTime:'))
const customLower = _.get(q, ['queryConfig', 'range', 'lower'], null)
const customUpper = _.get(q, ['queryConfig', 'range', 'upper'], null)
if (!customLower) {
return null
}
const customTimeRange = customUpper
? `${customLower} AND ${customUpper}`
: customLower
return <span className="dash-graph--custom-time">{customTimeRange}</span>
}
const {arrayOf, shape} = PropTypes
CustomTimeIndicator.propTypes = {
queries: arrayOf(shape()),
}
export default CustomTimeIndicator

View File

@ -138,6 +138,7 @@ class LayoutRenderer extends Component {
autoRefresh,
templates,
synchronizer,
isEditable,
} = this.props
return cells.map(cell => {
@ -146,6 +147,7 @@ class LayoutRenderer extends Component {
return (
<div key={cell.i}>
<NameableGraph
isEditable={isEditable}
onEditCell={onEditCell}
onRenameCell={onRenameCell}
onUpdateCell={onUpdateCell}
@ -290,6 +292,7 @@ LayoutRenderer.propTypes = {
shouldNotBeEditable: bool,
synchronizer: func,
isStatusPage: bool,
isEditable: bool,
}
export default LayoutRenderer

View File

@ -1,157 +1,88 @@
import React, {PropTypes} from 'react'
import classnames from 'classnames'
import OnClickOutside from 'react-onclickoutside'
import React, {Component, PropTypes} from 'react'
const {array, bool, func, node, number, shape, string} = PropTypes
import NameableGraphHeader from 'shared/components/NameableGraphHeader'
import ContextMenu from 'shared/components/ContextMenu'
const NameableGraph = React.createClass({
propTypes: {
cell: shape({
name: string.isRequired,
isEditing: bool,
x: number.isRequired,
y: number.isRequired,
queries: array.isRequired,
}).isRequired,
children: node.isRequired,
onEditCell: func,
onRenameCell: func,
onUpdateCell: func,
onDeleteCell: func,
onSummonOverlayTechnologies: func,
shouldNotBeEditable: bool,
},
getInitialState() {
return {
class NameableGraph extends Component {
constructor(props) {
super(props)
this.state = {
isMenuOpen: false,
}
},
this.toggleMenu = ::this.toggleMenu
this.closeMenu = ::this.closeMenu
}
toggleMenu() {
this.setState({
isMenuOpen: !this.state.isMenuOpen,
})
},
}
closeMenu() {
this.setState({
isMenuOpen: false,
})
},
renderCustomTimeIndicator() {
const {cell} = this.props
let customTimeRange = null
cell.queries.map(q => {
if (!q.query.includes(':dashboardTime:')) {
customTimeRange = q.queryConfig.range.lower.split(' ').reverse()[0]
}
})
return customTimeRange
? <span className="dash-graph--custom-time">{customTimeRange}</span>
: null
},
}
render() {
const {
cell,
cell: {x, y, name, isEditing},
onEditCell,
onRenameCell,
onUpdateCell,
onDeleteCell,
onSummonOverlayTechnologies,
shouldNotBeEditable,
isEditable,
children,
} = this.props
const isEditable = !!(onEditCell || onRenameCell || onUpdateCell)
let nameOrField
if (isEditing && isEditable) {
nameOrField = (
<input
className="form-control input-sm dash-graph--name-edit"
type="text"
value={name}
autoFocus={true}
onChange={onRenameCell(x, y)}
onBlur={onUpdateCell(cell)}
onKeyUp={evt => {
if (evt.key === 'Enter') {
onUpdateCell(cell)()
}
if (evt.key === 'Escape') {
onEditCell(x, y, true)()
}
}}
/>
)
} else {
nameOrField = (
<span className="dash-graph--name">
{name}
{this.renderCustomTimeIndicator()}
</span>
)
}
let onStartRenaming
if (!isEditing && isEditable) {
onStartRenaming = onEditCell
} else {
onStartRenaming = () => {
// no-op
}
}
return (
<div className="dash-graph">
<div
className={classnames('dash-graph--heading', {
'dash-graph--heading-draggable': !shouldNotBeEditable,
})}
>
{nameOrField}
</div>
{shouldNotBeEditable
? null
: <ContextMenu
isOpen={this.state.isMenuOpen}
toggleMenu={this.toggleMenu}
onEdit={onSummonOverlayTechnologies}
onRename={onStartRenaming}
onDelete={onDeleteCell}
cell={cell}
handleClickOutside={this.closeMenu}
/>}
<NameableGraphHeader
cell={cell}
isEditable={isEditable}
onEditCell={onEditCell}
onRenameCell={onRenameCell}
onUpdateCell={onUpdateCell}
/>
<ContextMenu
cell={cell}
onDelete={onDeleteCell}
onRename={!cell.isEditing && isEditable ? onEditCell : () => {}}
toggleMenu={this.toggleMenu}
isOpen={this.state.isMenuOpen}
isEditable={isEditable}
handleClickOutside={this.closeMenu}
onEdit={onSummonOverlayTechnologies}
/>
<div className="dash-graph--container">
{children}
</div>
</div>
)
},
})
}
}
const {array, bool, func, node, number, shape, string} = PropTypes
NameableGraph.propTypes = {
cell: shape({
name: string.isRequired,
isEditing: bool,
x: number.isRequired,
y: number.isRequired,
queries: array,
}).isRequired,
children: node.isRequired,
onEditCell: func,
onRenameCell: func,
onUpdateCell: func,
onDeleteCell: func,
onSummonOverlayTechnologies: func,
shouldNotBeEditable: bool,
isEditable: bool,
}
const ContextMenu = OnClickOutside(
({isOpen, toggleMenu, onEdit, onRename, onDelete, cell}) =>
<div
className={classnames('dash-graph--options', {
'dash-graph--options-show': isOpen,
})}
onClick={toggleMenu}
>
<button className="btn btn-info btn-xs">
<span className="icon caret-down" />
</button>
<ul className="dash-graph--options-menu">
<li onClick={() => onEdit(cell)}>Edit</li>
<li onClick={onRename(cell.x, cell.y, cell.isEditing)}>Rename</li>
<li onClick={() => onDelete(cell)}>Delete</li>
</ul>
</div>
)
export default NameableGraph

View File

@ -0,0 +1,82 @@
import React, {PropTypes} from 'react'
import classnames from 'classnames'
import CustomTimeIndicator from 'shared/components/CustomTimeIndicator'
const NameableGraphHeader = ({
isEditable,
onEditCell,
onRenameCell,
onUpdateCell,
cell,
cell: {x, y, name, queries},
}) => {
const isInputVisible = isEditable && cell.isEditing
const className = classnames('dash-graph--heading', {
'dash-graph--heading-draggable': isEditable,
})
const onKeyUp = evt => {
if (evt.key === 'Enter') {
onUpdateCell(cell)()
}
if (evt.key === 'Escape') {
onEditCell(x, y, true)()
}
}
return (
<div className={className}>
{isInputVisible
? <GraphNameInput
value={name}
onChange={onRenameCell(x, y)}
onBlur={onUpdateCell(cell)}
onKeyUp={onKeyUp}
/>
: <GraphName name={name} queries={queries} />}
</div>
)
}
const {arrayOf, bool, func, string, shape} = PropTypes
NameableGraphHeader.propTypes = {
cell: shape(),
onEditCell: func,
onRenameCell: func,
onUpdateCell: func,
isEditable: bool,
}
const GraphName = ({name, queries}) =>
<span className="dash-graph--name">
{name}
{queries && queries.length
? <CustomTimeIndicator queries={queries} />
: null}
</span>
GraphName.propTypes = {
name: string,
queries: arrayOf(shape()),
}
const GraphNameInput = ({value, onKeyUp, onChange, onBlur}) =>
<input
className="form-control input-sm dash-graph--name-edit"
type="text"
value={value}
autoFocus={true}
onChange={onChange}
onBlur={onBlur}
onKeyUp={onKeyUp}
/>
GraphNameInput.propTypes = {
value: string,
onKeyUp: func,
onChange: func,
onBlur: func,
}
export default NameableGraphHeader

View File

@ -59,6 +59,7 @@ class StatusPage extends Component {
source={source}
shouldNotBeEditable={true}
isStatusPage={true}
isEditable={false}
/>
: <span>Loading Status Page...</span>}
</div>