Skip to content

Commit

Permalink
feat: import dashboards with source mappings (#4068)
Browse files Browse the repository at this point in the history
Co-authored-by: Iris Scholten <[email protected]>
Co-authored-by: Alex Paxton <[email protected]>
  • Loading branch information
3 people committed Aug 2, 2018
1 parent 87b2a65 commit 0ecf1b0
Show file tree
Hide file tree
Showing 13 changed files with 943 additions and 35 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
### Features

1. [#4033](https://github.com/influxdata/chronograf/pull/4033): Include sources id, links, and names in dashboard export
1. [#4068](https://github.com/influxdata/chronograf/pull/4068): Add ability to map sources when importing dashboard

### UI Improvements

Expand Down
8 changes: 6 additions & 2 deletions ui/src/dashboards/components/DashboardsPageContents.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,12 @@ import FancyScrollbar from 'src/shared/components/FancyScrollbar'
import {ErrorHandling} from 'src/shared/decorators/errors'
import OverlayTechnology from 'src/reusable_ui/components/overlays/OverlayTechnology'

import {Dashboard} from 'src/types'
import {Dashboard, Source} from 'src/types'
import {Notification} from 'src/types/notifications'

interface Props {
source: Source
sources: Source[]
dashboards: Dashboard[]
onDeleteDashboard: (dashboard: Dashboard) => () => void
onCreateDashboard: () => void
Expand Down Expand Up @@ -140,7 +142,7 @@ class DashboardsPageContents extends Component<Props, State> {
}

private get renderImportOverlay(): JSX.Element {
const {onImportDashboard, notify} = this.props
const {onImportDashboard, notify, sources, source} = this.props
const {isOverlayVisible} = this.state

return (
Expand All @@ -149,6 +151,8 @@ class DashboardsPageContents extends Component<Props, State> {
onDismissOverlay={this.handleToggleOverlay}
onImportDashboard={onImportDashboard}
notify={notify}
source={source}
sources={sources}
/>
</OverlayTechnology>
)
Expand Down
257 changes: 257 additions & 0 deletions ui/src/dashboards/components/ImportDashboardMappings.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,257 @@
// Libraries
import React, {Component} from 'react'
import _ from 'lodash'

// Components
import Dropdown from 'src/shared/components/Dropdown'

// Utils
import {getDeep} from 'src/utils/wrappers'
import {
mapCells,
getSourceInfo,
createSourceMappings,
} from 'src/dashboards/utils/importDashboardMappings'

// Constants
import {DYNAMIC_SOURCE, DYNAMIC_SOURCE_ITEM} from 'src/dashboards/constants'

// Types
import {Source, Cell} from 'src/types'
import {
SourcesCells,
SourceMappings,
ImportedSources,
SourceItemValue,
} from 'src/types/dashboards'

interface Props {
cells: Cell[]
source: Source
sources: Source[]
importedSources: ImportedSources
onSubmit: (cells: Cell[]) => void
}

interface State {
sourcesCells: SourcesCells
sourceMappings: SourceMappings
}

class ImportDashboardMappings extends Component<Props, State> {
constructor(props: Props) {
super(props)
this.state = {sourcesCells: {}, sourceMappings: {}}
}

public componentDidMount() {
const {cells, importedSources, source} = this.props

if (_.isEmpty(cells)) {
return
}

const {sourcesCells, sourceMappings} = createSourceMappings(
source,
cells,
importedSources
)

this.setState({sourcesCells, sourceMappings})
}

public render() {
return (
<>
{this.description}
{this.table}
<div className="dash-map--footer">
<button
className="dash-map--submit btn btn-sm btn-success"
onClick={this.handleSubmit}
>
Done
</button>
</div>
</>
)
}

private get noMappings(): JSX.Element {
return (
<div
data-test="no-mapping"
className="generic-empty-state dash-map--empty"
>
<h5>No source mappings required</h5>
</div>
)
}

private get arrow(): JSX.Element {
return (
<div className="fancytable--td provider--arrow">
<span />
</div>
)
}

private get description(): JSX.Element {
const description = [
'Match the sources from your imported dashboard with your available sources below. A ',
<strong key="emphasis">Dynamic Source</strong>,
' allows the cell source to change based on your currently selected source.',
]

return (
<div className="alert alert-grey">
<span className="icon graphline-2" />
<div className="alert-message">{description}</div>
</div>
)
}

private get table(): JSX.Element {
const {sourcesCells} = this.state

if (_.isEmpty(sourcesCells)) {
return this.noMappings
}

return (
<table className="table dash-map--table">
{this.header}
<tbody>{this.tableBody}</tbody>
</table>
)
}

private get tableBody(): JSX.Element[] {
const {importedSources} = this.props
const {sourcesCells} = this.state

const rows = _.reduce(
sourcesCells,
(acc, __, i) => {
if (i !== DYNAMIC_SOURCE && sourcesCells[i]) {
const sourceName = getDeep<string>(
importedSources,
`${i}.name`,
'Source'
)
acc.push(this.getRow(sourceName, i))
}
return acc
},
[]
)
if (sourcesCells[DYNAMIC_SOURCE]) {
const noSourceRow = this.getRow('Dynamic Source', DYNAMIC_SOURCE)
rows.push(noSourceRow)
}
return rows
}

private getRow(sourceName: string, sourceID: string): JSX.Element {
let sourceLabel = `${sourceName} (${sourceID})`
let description = 'Cells that use this Source:'
if (sourceID === DYNAMIC_SOURCE) {
sourceLabel = sourceName
description = 'Cells using Dynamic Source:'
}
return (
<tr key={sourceID}>
<td className="dash-map--table-cell dash-map--table-half">
<div className="dash-map--source" data-test="source-label">
{sourceLabel}
</div>
<div className="dash-map--header">{description}</div>
{this.getCellsForSource(sourceID)}
</td>
<td className="dash-map--table-cell dash-map--table-center">
{this.arrow}
</td>
<td className="dash-map--table-cell dash-map--table-half">
<Dropdown
className="dropdown-stretch"
buttonColor="btn-default"
buttonSize="btn-sm"
items={this.getSourceItems(sourceID)}
onChoose={this.handleChooseDropdown}
selected={this.getSelected(sourceID)}
/>
</td>
</tr>
)
}

private getSourceItems(importedSourceID: string): SourceItemValue[] {
const {sources} = this.props

const sourceItems = sources.map(source => {
const sourceInfo = getSourceInfo(source)
const sourceMap: SourceItemValue = {
sourceInfo,
importedSourceID,
text: source.name,
}
return sourceMap
})
sourceItems.push({...DYNAMIC_SOURCE_ITEM, importedSourceID})
return sourceItems
}

private get header(): JSX.Element {
return (
<thead>
<tr>
<th className="dash-map--table-half">Sources in Dashboard</th>
<th className="dash-map--table-center" />
<th className="dash-map--table-half">Available Sources</th>
</tr>
</thead>
)
}

private getSelected(importedSourceID: string): string {
const {sources} = this.props
const {sourceMappings} = this.state

const sourceMapping = sourceMappings[importedSourceID]
if (sourceMapping) {
return sourceMappings[importedSourceID].name
}

return sources[0].name
}

private getCellsForSource(sourceID: string): JSX.Element[] {
const {sourcesCells} = this.state

return _.map(sourcesCells[sourceID], c => {
return (
<div className="dash-map--cell" key={c.id}>
{c.name}
</div>
)
})
}

private handleChooseDropdown = (item: SourceItemValue): void => {
const {sourceMappings} = this.state

sourceMappings[item.importedSourceID] = item.sourceInfo
this.setState({sourceMappings})
}

private handleSubmit = (): void => {
const {cells, onSubmit, importedSources} = this.props
const {sourceMappings} = this.state

const mappedCells = mapCells(cells, sourceMappings, importedSources)

onSubmit(mappedCells)
}
}

export default ImportDashboardMappings
Loading

0 comments on commit 0ecf1b0

Please sign in to comment.