Fix issue switching between dashboards
parent
352cf0e0d0
commit
577276f52d
|
@ -280,35 +280,6 @@ export const getDashboardsAsync: DashboardsActions.GetDashboardsDispatcher = ():
|
|||
}
|
||||
}
|
||||
|
||||
// gets update-to-date names of dashboards, but does not dispatch action
|
||||
// in order to avoid duplicate and out-of-sync state problems in redux
|
||||
export const getDashboardsNamesAsync: DashboardsActions.GetDashboardsNamesDispatcher = (
|
||||
sourceID: string
|
||||
): DashboardsActions.GetDashboardsNamesThunk => async (
|
||||
dispatch: Dispatch<ErrorsActions.ErrorThrownActionCreator>
|
||||
): Promise<DashboardsModels.DashboardName[] | void> => {
|
||||
try {
|
||||
// TODO: change this from getDashboardsAJAX to getDashboardsNamesAJAX
|
||||
// to just get dashboard names (and links) as api view call when that
|
||||
// view API is implemented (issue #3594), rather than getting whole
|
||||
// dashboard for each
|
||||
const {
|
||||
data: {dashboards},
|
||||
} = (await getDashboardsAJAX()) as AxiosResponse<
|
||||
DashboardsApis.DashboardsResponse
|
||||
>
|
||||
const dashboardsNames = dashboards.map(({id, name}) => ({
|
||||
id,
|
||||
name,
|
||||
link: `/sources/${sourceID}/dashboards/${id}`,
|
||||
}))
|
||||
return dashboardsNames
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
dispatch(errorThrown(error))
|
||||
}
|
||||
}
|
||||
|
||||
export const getDashboardAsync = (dashboardID: number) => async (
|
||||
dispatch
|
||||
): Promise<DashboardsModels.Dashboard | null> => {
|
||||
|
|
|
@ -32,7 +32,8 @@ interface Props {
|
|||
zoomedTimeRange: QueriesModels.TimeRange
|
||||
onCancel: () => void
|
||||
onSave: (name: string) => Promise<void>
|
||||
names: DashboardsModels.DashboardName[]
|
||||
dashboardLinks: DashboardsModels.DashboardSwitcherLink[]
|
||||
activeDashboardLink?: DashboardsModels.DashboardSwitcherLink
|
||||
isHidden: boolean
|
||||
}
|
||||
|
||||
|
@ -145,11 +146,14 @@ class DashboardHeader extends Component<Props> {
|
|||
}
|
||||
|
||||
private get dashboardSwitcher(): JSX.Element {
|
||||
const {names, activeDashboard} = this.props
|
||||
const {dashboardLinks, activeDashboardLink} = this.props
|
||||
|
||||
if (names && names.length > 1) {
|
||||
if (dashboardLinks.length > 1) {
|
||||
return (
|
||||
<DashboardSwitcher names={names} activeDashboard={activeDashboard} />
|
||||
<DashboardSwitcher
|
||||
links={dashboardLinks}
|
||||
activeLink={activeDashboardLink}
|
||||
/>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,104 +0,0 @@
|
|||
import React, {Component} from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import {Link} from 'react-router'
|
||||
import _ from 'lodash'
|
||||
import classnames from 'classnames'
|
||||
import OnClickOutside from 'shared/components/OnClickOutside'
|
||||
import {ErrorHandling} from 'src/shared/decorators/errors'
|
||||
import FancyScrollbar from 'src/shared/components/FancyScrollbar'
|
||||
import {DROPDOWN_MENU_MAX_HEIGHT} from 'src/shared/constants/index'
|
||||
|
||||
@ErrorHandling
|
||||
class DashboardSwitcher extends Component {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
|
||||
this.state = {
|
||||
isOpen: false,
|
||||
}
|
||||
}
|
||||
|
||||
handleToggleMenu = () => {
|
||||
this.setState({isOpen: !this.state.isOpen})
|
||||
}
|
||||
|
||||
handleCloseMenu = () => {
|
||||
this.setState({isOpen: false})
|
||||
}
|
||||
|
||||
handleClickOutside = () => {
|
||||
this.setState({isOpen: false})
|
||||
}
|
||||
|
||||
render() {
|
||||
const {activeDashboard} = this.props
|
||||
const {isOpen} = this.state
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classnames('dropdown dashboard-switcher', {open: isOpen})}
|
||||
>
|
||||
<button
|
||||
className="btn btn-square btn-default btn-sm dropdown-toggle"
|
||||
onClick={this.handleToggleMenu}
|
||||
>
|
||||
<span className="icon dash-h" />
|
||||
</button>
|
||||
<ul className="dropdown-menu">
|
||||
<FancyScrollbar
|
||||
autoHeight={true}
|
||||
maxHeight={DROPDOWN_MENU_MAX_HEIGHT}
|
||||
>
|
||||
{this.sortedList.map(({name, link}) => (
|
||||
<NameLink
|
||||
key={link}
|
||||
name={name}
|
||||
link={link}
|
||||
activeName={activeDashboard}
|
||||
onClose={this.handleCloseMenu}
|
||||
/>
|
||||
))}
|
||||
</FancyScrollbar>
|
||||
</ul>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
get sortedList() {
|
||||
const {names} = this.props
|
||||
return _.sortBy(names, ({name}) => name.toLowerCase())
|
||||
}
|
||||
}
|
||||
|
||||
const NameLink = ({name, link, activeName, onClose}) => (
|
||||
<li
|
||||
className={classnames('dropdown-item', {
|
||||
active: name === activeName,
|
||||
})}
|
||||
>
|
||||
<Link to={link} onClick={onClose}>
|
||||
{name}
|
||||
</Link>
|
||||
</li>
|
||||
)
|
||||
|
||||
const {arrayOf, func, shape, string} = PropTypes
|
||||
|
||||
DashboardSwitcher.propTypes = {
|
||||
activeDashboard: string.isRequired,
|
||||
names: arrayOf(
|
||||
shape({
|
||||
link: string.isRequired,
|
||||
name: string.isRequired,
|
||||
})
|
||||
).isRequired,
|
||||
}
|
||||
|
||||
NameLink.propTypes = {
|
||||
name: string.isRequired,
|
||||
link: string.isRequired,
|
||||
activeName: string.isRequired,
|
||||
onClose: func.isRequired,
|
||||
}
|
||||
|
||||
export default OnClickOutside(DashboardSwitcher)
|
|
@ -0,0 +1,89 @@
|
|||
import React, {PureComponent} from 'react'
|
||||
import {Link} from 'react-router'
|
||||
import _ from 'lodash'
|
||||
|
||||
import OnClickOutside from 'src/shared/components/OnClickOutside'
|
||||
import FancyScrollbar from 'src/shared/components/FancyScrollbar'
|
||||
|
||||
import {ErrorHandling} from 'src/shared/decorators/errors'
|
||||
|
||||
import {DROPDOWN_MENU_MAX_HEIGHT} from 'src/shared/constants/index'
|
||||
|
||||
import {DashboardSwitcherLink} from 'src/types/dashboards'
|
||||
|
||||
interface Props {
|
||||
links: DashboardSwitcherLink[]
|
||||
activeLink?: DashboardSwitcherLink
|
||||
}
|
||||
|
||||
interface State {
|
||||
isOpen: boolean
|
||||
}
|
||||
|
||||
@ErrorHandling
|
||||
class DashboardSwitcher extends PureComponent<Props, State> {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
|
||||
this.state = {isOpen: false}
|
||||
}
|
||||
|
||||
public render() {
|
||||
const {isOpen} = this.state
|
||||
|
||||
const openClass = isOpen ? 'open' : ''
|
||||
|
||||
return (
|
||||
<div className={`dropdown dashboard-switcher ${openClass}`}>
|
||||
<button
|
||||
className="btn btn-square btn-default btn-sm dropdown-toggle"
|
||||
onClick={this.handleToggleMenu}
|
||||
>
|
||||
<span className="icon dash-h" />
|
||||
</button>
|
||||
<ul className="dropdown-menu">
|
||||
<FancyScrollbar
|
||||
autoHeight={true}
|
||||
maxHeight={DROPDOWN_MENU_MAX_HEIGHT}
|
||||
>
|
||||
{this.links}
|
||||
</FancyScrollbar>
|
||||
</ul>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
public handleClickOutside = () => {
|
||||
this.setState({isOpen: false})
|
||||
}
|
||||
|
||||
private handleToggleMenu = () => {
|
||||
this.setState({isOpen: !this.state.isOpen})
|
||||
}
|
||||
|
||||
private handleCloseMenu = () => {
|
||||
this.setState({isOpen: false})
|
||||
}
|
||||
|
||||
private get links(): JSX.Element[] {
|
||||
const {links, activeLink} = this.props
|
||||
|
||||
return _.sortBy(links, ['text', 'key']).map(link => {
|
||||
let activeClass = ''
|
||||
|
||||
if (activeLink && link.key === activeLink.key) {
|
||||
activeClass = 'active'
|
||||
}
|
||||
|
||||
return (
|
||||
<li key={link.key} className={`dropdown-item ${activeClass}`}>
|
||||
<Link to={link.to} onClick={this.handleCloseMenu}>
|
||||
{link.text}
|
||||
</Link>
|
||||
</li>
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export default OnClickOutside(DashboardSwitcher)
|
|
@ -58,7 +58,7 @@ interface DashboardActions {
|
|||
syncURLQueryParamsFromQueryParamsObject: DashboardsActions.SyncURLQueryFromQueryParamsObjectDispatcher
|
||||
putDashboard: DashboardsActions.PutDashboardDispatcher
|
||||
putDashboardByID: DashboardsActions.PutDashboardByIDDispatcher
|
||||
getDashboardsNamesAsync: DashboardsActions.GetDashboardsNamesDispatcher
|
||||
getDashboardsAsync: DashboardsActions.GetDashboardsDispatcher
|
||||
getDashboardWithHydratedAndSyncedTempVarsAsync: DashboardsActions.GetDashboardWithHydratedAndSyncedTempVarsAsyncDispatcher
|
||||
setTimeRange: DashboardsActions.SetTimeRangeActionCreator
|
||||
addDashboardCellAsync: DashboardsActions.AddDashboardCellDispatcher
|
||||
|
@ -135,7 +135,13 @@ class DashboardPage extends Component<Props, State> {
|
|||
}
|
||||
|
||||
public async componentDidMount() {
|
||||
const {source, getAnnotationsAsync, timeRange, autoRefresh} = this.props
|
||||
const {
|
||||
source,
|
||||
getAnnotationsAsync,
|
||||
timeRange,
|
||||
autoRefresh,
|
||||
getDashboardsAsync,
|
||||
} = this.props
|
||||
|
||||
const annotationRange = millisecondTimeRange(timeRange)
|
||||
getAnnotationsAsync(source.links.annotations, annotationRange)
|
||||
|
@ -150,7 +156,10 @@ class DashboardPage extends Component<Props, State> {
|
|||
|
||||
await this.getDashboard()
|
||||
|
||||
this.getDashboardsNames()
|
||||
// We populate all dashboards in the redux store so that we can consume
|
||||
// them in `this.dashboardLinks`. See
|
||||
// https://github.com/influxdata/chronograf/issues/3594
|
||||
getDashboardsAsync()
|
||||
}
|
||||
|
||||
public componentWillReceiveProps(nextProps: Props) {
|
||||
|
@ -212,8 +221,6 @@ class DashboardPage extends Component<Props, State> {
|
|||
handleHideCellEditorOverlay,
|
||||
handleClickPresentationButton,
|
||||
} = this.props
|
||||
const {dashboardsNames} = this.state
|
||||
|
||||
const low = zoomedLower || lower
|
||||
const up = zoomedUpper || upper
|
||||
|
||||
|
@ -283,7 +290,6 @@ class DashboardPage extends Component<Props, State> {
|
|||
/>
|
||||
) : null}
|
||||
<DashboardHeader
|
||||
names={dashboardsNames}
|
||||
dashboard={dashboard}
|
||||
timeRange={timeRange}
|
||||
isEditMode={isEditMode}
|
||||
|
@ -295,6 +301,8 @@ class DashboardPage extends Component<Props, State> {
|
|||
onSave={this.handleRenameDashboard}
|
||||
onCancel={this.handleCancelEditDashboard}
|
||||
onEditDashboard={this.handleEditDashboard}
|
||||
dashboardLinks={this.dashboardLinks}
|
||||
activeDashboardLink={this.activeDashboardLink}
|
||||
activeDashboard={dashboard ? dashboard.name : ''}
|
||||
showTemplateControlBar={showTemplateControlBar}
|
||||
handleChooseAutoRefresh={handleChooseAutoRefresh}
|
||||
|
@ -353,19 +361,6 @@ class DashboardPage extends Component<Props, State> {
|
|||
)
|
||||
}
|
||||
|
||||
private getDashboardsNames = async (): Promise<void> => {
|
||||
const {
|
||||
params: {sourceID},
|
||||
} = this.props
|
||||
|
||||
// TODO: remove any once react-redux connect is properly typed
|
||||
const dashboardsNames = (await this.props.getDashboardsNamesAsync(
|
||||
sourceID
|
||||
)) as any
|
||||
|
||||
this.setState({dashboardsNames})
|
||||
}
|
||||
|
||||
private inView = (cell: DashboardsModels.Cell): boolean => {
|
||||
const {scrollTop, windowHeight} = this.state
|
||||
const bufferValue = 600
|
||||
|
@ -445,7 +440,6 @@ class DashboardPage extends Component<Props, State> {
|
|||
|
||||
this.props.updateDashboard(newDashboard)
|
||||
await this.props.putDashboard(newDashboard)
|
||||
this.getDashboardsNames()
|
||||
}
|
||||
|
||||
private handleDeleteDashboardCell = (cell: DashboardsModels.Cell): void => {
|
||||
|
@ -511,6 +505,30 @@ class DashboardPage extends Component<Props, State> {
|
|||
|
||||
this.setState({scrollTop: target.scrollTop})
|
||||
}
|
||||
|
||||
private get dashboardLinks(): DashboardsModels.DashboardSwitcherLink[] {
|
||||
const {dashboards, source} = this.props
|
||||
|
||||
return dashboards.map(d => {
|
||||
return {
|
||||
key: String(d.id),
|
||||
text: d.name,
|
||||
to: `/sources/${source.id}/dashboards/${d.id}`,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private get activeDashboardLink(): DashboardsModels.DashboardSwitcherLink | null {
|
||||
const {dashboard} = this.props
|
||||
|
||||
if (!dashboard) {
|
||||
return null
|
||||
}
|
||||
|
||||
const {dashboardLinks} = this
|
||||
|
||||
return dashboardLinks.find(link => link.key === String(dashboard.id))
|
||||
}
|
||||
}
|
||||
|
||||
const mstp = (state, {params: {dashboardID}}) => {
|
||||
|
|
|
@ -168,21 +168,16 @@ class HostPage extends Component {
|
|||
const {
|
||||
autoRefresh,
|
||||
onManualRefresh,
|
||||
params: {hostID, sourceID},
|
||||
params: {hostID},
|
||||
inPresentationMode,
|
||||
handleChooseAutoRefresh,
|
||||
handleClickPresentationButton,
|
||||
} = this.props
|
||||
const {timeRange, hosts} = this.state
|
||||
const names = _.map(hosts, ({name}) => ({
|
||||
name,
|
||||
link: `/sources/${sourceID}/hosts/${name}`,
|
||||
}))
|
||||
const {timeRange} = this.state
|
||||
|
||||
return (
|
||||
<div className="page">
|
||||
<DashboardHeader
|
||||
names={names}
|
||||
timeRange={timeRange}
|
||||
activeDashboard={hostID}
|
||||
autoRefresh={autoRefresh}
|
||||
|
@ -191,6 +186,8 @@ class HostPage extends Component {
|
|||
handleChooseAutoRefresh={handleChooseAutoRefresh}
|
||||
handleChooseTimeRange={this.handleChooseTimeRange}
|
||||
handleClickPresentationButton={handleClickPresentationButton}
|
||||
dashboardLinks={this.dashboardLinks}
|
||||
activeDashboardLink={this.activeDashboardLink}
|
||||
/>
|
||||
<FancyScrollbar
|
||||
className={classnames({
|
||||
|
@ -205,6 +202,32 @@ class HostPage extends Component {
|
|||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
get dashboardLinks() {
|
||||
const {
|
||||
params: {sourceID},
|
||||
} = this.props
|
||||
const {hosts} = this.state
|
||||
|
||||
if (!sourceID || !hosts) {
|
||||
return []
|
||||
}
|
||||
|
||||
return Object.values(hosts).map(({name}) => ({
|
||||
key: name,
|
||||
text: name,
|
||||
to: `/sources/${sourceID}/hosts/${name}`,
|
||||
}))
|
||||
}
|
||||
|
||||
get activeDashboardLink() {
|
||||
const {
|
||||
params: {hostID},
|
||||
} = this.props
|
||||
const {dashboardLinks} = this
|
||||
|
||||
return dashboardLinks.find(d => d.key === hostID)
|
||||
}
|
||||
}
|
||||
|
||||
const {shape, string, bool, func, number} = PropTypes
|
||||
|
|
|
@ -128,3 +128,9 @@ export enum ThresholdType {
|
|||
BG = 'background',
|
||||
Base = 'base',
|
||||
}
|
||||
|
||||
export interface DashboardSwitcherLink {
|
||||
key: string
|
||||
text: string
|
||||
to: string
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue