Refactor dashboard naming flow and associated UI

pull/10616/head
Alex P 2017-10-10 15:02:19 -07:00 committed by Andrew Watkins
parent bf3ce3ed4c
commit 4395ef6e14
6 changed files with 155 additions and 112 deletions

View File

@ -1,49 +1,44 @@
import React, {PropTypes} from 'react' import React, {PropTypes} from 'react'
import classnames from 'classnames' import classnames from 'classnames'
import _ from 'lodash'
import AutoRefreshDropdown from 'shared/components/AutoRefreshDropdown' import AutoRefreshDropdown from 'shared/components/AutoRefreshDropdown'
import TimeRangeDropdown from 'shared/components/TimeRangeDropdown' import TimeRangeDropdown from 'shared/components/TimeRangeDropdown'
import SourceIndicator from 'shared/components/SourceIndicator' import SourceIndicator from 'shared/components/SourceIndicator'
import GraphTips from 'shared/components/GraphTips' import GraphTips from 'shared/components/GraphTips'
import DashboardHeaderEdit from 'src/dashboards/components/DashboardHeaderEdit'
const DashboardHeader = ({ const DashboardHeader = ({
onSave,
children, children,
buttonText, onCancel,
dashboard, isEditMode,
timeRange: {upper, lower},
zoomedTimeRange: {zoomedLower, zoomedUpper},
autoRefresh,
isHidden, isHidden,
dashboard,
onAddCell,
autoRefresh,
dashboardName,
onEditDashboard,
onManualRefresh,
handleChooseTimeRange, handleChooseTimeRange,
handleChooseAutoRefresh, handleChooseAutoRefresh,
onManualRefresh,
handleClickPresentationButton,
onAddCell,
onEditDashboard,
onToggleTempVarControls, onToggleTempVarControls,
showTemplateControlBar, showTemplateControlBar,
timeRange: {upper, lower},
handleClickPresentationButton,
zoomedTimeRange: {zoomedLower, zoomedUpper},
}) => }) =>
isHidden isHidden
? null ? null
: <div className="page-header full-width"> : <div className="page-header full-width">
<div className="page-header__container"> <div className="page-header__container">
<div className="page-header__left"> <div
{buttonText && className={
<div className="dropdown page-header-dropdown"> dashboard
<button ? 'page-header__left page-header__dash-editable'
className="dropdown-toggle" : 'page-header__left'
type="button" }
data-toggle="dropdown" >
>
<span>
{buttonText}
</span>
<span className="caret" />
</button>
<ul className="dropdown-menu">
{children}
</ul>
</div>}
{children.length > 1 {children.length > 1
? <div className="dropdown dashboard-switcher"> ? <div className="dropdown dashboard-switcher">
<button <button
@ -60,6 +55,17 @@ const DashboardHeader = ({
</ul> </ul>
</div> </div>
: null} : null}
{dashboard
? <DashboardHeaderEdit
onSave={onSave}
onCancel={onCancel}
dashboardName={dashboardName}
onEditDashboard={onEditDashboard}
isEditMode={isEditMode}
/>
: <h1 className="page-header__title">
{dashboardName}
</h1>}
</div> </div>
<div className="page-header__right"> <div className="page-header__right">
<GraphTips /> <GraphTips />
@ -70,15 +76,6 @@ const DashboardHeader = ({
Add Cell Add Cell
</button> </button>
: null} : null}
{dashboard
? <button
className="btn btn-default btn-sm"
onClick={onEditDashboard}
>
<span className="icon pencil" />
Rename
</button>
: null}
{dashboard {dashboard
? <div ? <div
className={classnames('btn btn-default btn-sm', { className={classnames('btn btn-default btn-sm', {
@ -123,7 +120,8 @@ DashboardHeader.defaultProps = {
DashboardHeader.propTypes = { DashboardHeader.propTypes = {
children: array, children: array,
buttonText: string, dashboardName: string.isRequired,
onEditDashboard: func.isRequired,
dashboard: shape({}), dashboard: shape({}),
timeRange: shape({ timeRange: shape({
lower: string, lower: string,
@ -131,15 +129,17 @@ DashboardHeader.propTypes = {
}).isRequired, }).isRequired,
autoRefresh: number.isRequired, autoRefresh: number.isRequired,
isHidden: bool.isRequired, isHidden: bool.isRequired,
isEditMode: bool,
handleChooseTimeRange: func.isRequired, handleChooseTimeRange: func.isRequired,
handleChooseAutoRefresh: func.isRequired, handleChooseAutoRefresh: func.isRequired,
onManualRefresh: func.isRequired, onManualRefresh: func.isRequired,
handleClickPresentationButton: func.isRequired, handleClickPresentationButton: func.isRequired,
onAddCell: func, onAddCell: func,
onEditDashboard: func,
onToggleTempVarControls: func, onToggleTempVarControls: func,
showTemplateControlBar: bool, showTemplateControlBar: bool,
zoomedTimeRange: shape({}), zoomedTimeRange: shape({}),
onCancel: func.isRequired,
onSave: func.isRequired,
} }
export default DashboardHeader export default DashboardHeader

View File

@ -1,70 +1,76 @@
import React, {PropTypes, Component} from 'react' import React, {PropTypes, Component} from 'react'
import ConfirmButtons from 'shared/components/ConfirmButtons' import {
DASHBOARD_NAME_MAX_LENGTH,
NEW_DASHBOARD,
} from 'src/dashboards/constants/index'
class DashboardEditHeader extends Component { class DashboardEditHeader extends Component {
constructor(props) { constructor(props) {
super(props) super(props)
const {dashboard: {name}} = props
this.state = { this.state = {
name, reset: false,
} }
} }
handleChange = e => { handleInputBlur = e => {
this.setState({name: e.target.value}) const {onSave, onCancel} = this.props
} const {reset} = this.state
handleFormSubmit = e => { if (reset) {
e.preventDefault()
const name = e.target.name.value
this.props.onSave(name)
}
handleKeyUp = e => {
const {onCancel} = this.props
if (e.key === 'Escape') {
onCancel() onCancel()
} else {
const newName = e.target.value || NEW_DASHBOARD.name
onSave(newName)
}
this.setState({reset: false})
}
handleKeyDown = e => {
if (e.key === 'Enter') {
this.inputRef.blur()
}
if (e.key === 'Escape') {
this.inputRef.value = this.props.dashboardName
this.setState({reset: true}, () => this.inputRef.blur())
} }
} }
render() { render() {
const {onSave, onCancel} = this.props const {onEditDashboard, isEditMode, dashboardName} = this.props
const {name} = this.state
return ( return (
<div className="page-header full-width"> <div className="dashboard-title">
<div className="page-header__container"> {isEditMode
<form ? <input
className="page-header__left" maxLength={DASHBOARD_NAME_MAX_LENGTH}
style={{flex: '1 0 0%'}} type="text"
onSubmit={this.handleFormSubmit} className="dashboard-title--input form-control"
> defaultValue={dashboardName}
<input autoComplete="off"
className="page-header--editing"
name="name"
value={name}
placeholder="Name this Dashboard"
onKeyUp={this.handleKeyUp}
autoFocus={true} autoFocus={true}
spellCheck={false} spellCheck={false}
autoComplete="off" onBlur={this.handleInputBlur}
onChange={this.handleChange} onKeyDown={this.handleKeyDown}
placeholder="Name this Dashboard"
ref={r => (this.inputRef = r)}
/> />
</form> : <h1 onClick={onEditDashboard}>
<ConfirmButtons item={name} onConfirm={onSave} onCancel={onCancel} /> {dashboardName}
</div> </h1>}
</div> </div>
) )
} }
} }
const {shape, func} = PropTypes const {bool, func, string} = PropTypes
DashboardEditHeader.propTypes = { DashboardEditHeader.propTypes = {
dashboard: shape({}), dashboardName: string.isRequired,
onCancel: func.isRequired,
onSave: func.isRequired, onSave: func.isRequired,
onCancel: func.isRequired,
isEditMode: bool,
onEditDashboard: func.isRequired,
} }
export default DashboardEditHeader export default DashboardEditHeader

View File

@ -109,3 +109,5 @@ export const TOOLTIP_CONTENT = {
export const TYPE_QUERY_CONFIG = 'queryConfig' export const TYPE_QUERY_CONFIG = 'queryConfig'
export const TYPE_IFQL = 'ifql' export const TYPE_IFQL = 'ifql'
export const DASHBOARD_NAME_MAX_LENGTH = 50

View File

@ -8,7 +8,6 @@ import Dygraph from 'src/external/dygraph'
import OverlayTechnologies from 'shared/components/OverlayTechnologies' import OverlayTechnologies from 'shared/components/OverlayTechnologies'
import CellEditorOverlay from 'src/dashboards/components/CellEditorOverlay' import CellEditorOverlay from 'src/dashboards/components/CellEditorOverlay'
import DashboardHeader from 'src/dashboards/components/DashboardHeader' import DashboardHeader from 'src/dashboards/components/DashboardHeader'
import DashboardHeaderEdit from 'src/dashboards/components/DashboardHeaderEdit'
import Dashboard from 'src/dashboards/components/Dashboard' import Dashboard from 'src/dashboards/components/Dashboard'
import TemplateVariableManager from 'src/dashboards/components/template_variables/Manager' import TemplateVariableManager from 'src/dashboards/components/template_variables/Manager'
import ManualRefresh from 'src/shared/components/ManualRefresh' import ManualRefresh from 'src/shared/components/ManualRefresh'
@ -309,40 +308,36 @@ class DashboardPage extends Component {
editQueryStatus={dashboardActions.editCellQueryStatus} editQueryStatus={dashboardActions.editCellQueryStatus}
/> />
: null} : null}
{isEditMode <DashboardHeader
? <DashboardHeaderEdit source={source}
dashboard={dashboard} sourceID={sourceID}
onSave={this.handleRenameDashboard} dashboard={dashboard}
onCancel={this.handleCancelEditDashboard} timeRange={timeRange}
/> isEditMode={isEditMode}
: <DashboardHeader autoRefresh={autoRefresh}
source={source} isHidden={inPresentationMode}
sourceID={sourceID} onAddCell={this.handleAddCell}
dashboard={dashboard} zoomedTimeRange={zoomedTimeRange}
timeRange={timeRange} onSave={this.handleRenameDashboard}
zoomedTimeRange={zoomedTimeRange} onCancel={this.handleCancelEditDashboard}
autoRefresh={autoRefresh} onEditDashboard={this.handleEditDashboard}
isHidden={inPresentationMode} dashboardName={dashboard ? dashboard.name : ''}
onAddCell={this.handleAddCell} showTemplateControlBar={showTemplateControlBar}
onEditDashboard={this.handleEditDashboard} handleChooseAutoRefresh={handleChooseAutoRefresh}
buttonText={dashboard ? dashboard.name : ''} handleChooseTimeRange={this.handleChooseTimeRange}
showTemplateControlBar={showTemplateControlBar} onToggleTempVarControls={this.handleToggleTempVarControls}
handleChooseAutoRefresh={handleChooseAutoRefresh} handleClickPresentationButton={handleClickPresentationButton}
onManualRefresh={onManualRefresh} >
handleChooseTimeRange={this.handleChooseTimeRange} {dashboards
onToggleTempVarControls={this.handleToggleTempVarControls} ? dashboards.map((d, i) =>
handleClickPresentationButton={handleClickPresentationButton} <li className="dropdown-item" key={i}>
> <Link to={`/sources/${sourceID}/dashboards/${d.id}`}>
{dashboards {d.name}
? dashboards.map((d, i) => </Link>
<li className="dropdown-item" key={i}> </li>
<Link to={`/sources/${sourceID}/dashboards/${d.id}`}> )
{d.name} : null}
</Link> </DashboardHeader>
</li>
)
: null}
</DashboardHeader>}
{dashboard {dashboard
? <Dashboard ? <Dashboard
source={source} source={source}

View File

@ -182,7 +182,7 @@ class HostPage extends Component {
<div className="page"> <div className="page">
<DashboardHeader <DashboardHeader
source={source} source={source}
buttonText={hostID} dashboardName={hostID}
timeRange={timeRange} timeRange={timeRange}
autoRefresh={autoRefresh} autoRefresh={autoRefresh}
isHidden={inPresentationMode} isHidden={inPresentationMode}

View File

@ -340,8 +340,48 @@ $tick-script-overlay-margin: 30px;
----------------------------------------------------------------------------- -----------------------------------------------------------------------------
*/ */
.dropdown.dashboard-switcher { .dropdown.dashboard-switcher {
margin-right: 4px; margin-right: 10px;
} }
.dropdown.dashboard-switcher .btn.dropdown-toggle { .dropdown.dashboard-switcher .btn.dropdown-toggle {
justify-content: center; justify-content: center;
} }
/*
Dashboard Name Editing
-----------------------------------------------------------------------------
*/
.page-header__left.page-header__dash-editable,
.dashboard-title,
.dashboard-title input[type="text"].form-control.dashboard-title--input {
flex: 1 0 0;
}
.dashboard-title {
display: flex;
input[type="text"].form-control.dashboard-title--input,
input[type="text"].form-control.dashboard-title--input:focus {
border: 0;
border-radius: 0;
padding: 0;
font-size: $page-header-size;
font-weight: $page-header-weight;
color: $c-pool;
box-shadow: none;
background-color: transparent;
}
h1 {
margin: 0;
letter-spacing: 0;
text-transform: none;
font-size: $page-header-size;
font-weight: $page-header-weight;
transition: color 0.25s ease;
&:hover {
cursor: text;
color: $c-pool;
}
}
}