Merge pull request #1697 from influxdata/hotfix/custom-time-range
Custom Time Range Indicator Hotfixpull/10616/head
commit
f9be34655f
|
@ -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}
|
||||
|
|
|
@ -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}
|
||||
/>
|
||||
)
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -59,6 +59,7 @@ class StatusPage extends Component {
|
|||
source={source}
|
||||
shouldNotBeEditable={true}
|
||||
isStatusPage={true}
|
||||
isEditable={false}
|
||||
/>
|
||||
: <span>Loading Status Page...</span>}
|
||||
</div>
|
||||
|
|
Loading…
Reference in New Issue