fix(ui/dashboard): repair import to remap sources in variables

pull/5646/head
Pavel Zavora 2020-12-15 17:17:09 +01:00
parent 92553b4e8b
commit 9bd4c82293
7 changed files with 146 additions and 29 deletions

View File

@ -9,9 +9,9 @@ import DragAndDrop from 'src/shared/components/DragAndDrop'
import ImportDashboardMappings from 'src/dashboards/components/import_dashboard_mappings/ImportDashboardMappings'
import {notifyDashboardImportFailed} from 'src/shared/copy/notifications'
import {Dashboard, Cell, Source} from 'src/types'
import {Dashboard, Cell, Source, Template} from 'src/types'
import {Notification} from 'src/types/notifications'
import {ImportedSources} from 'src/types/dashboards'
import {ImportedSources, SourceMappings} from 'src/types/dashboards'
interface Props {
source: Source
@ -59,7 +59,7 @@ class ImportDashboardOverlay extends PureComponent<Props, State> {
}
private get renderStep(): JSX.Element {
const {step, importedSources, cells} = this.state
const {step, importedSources, cells, dashboard} = this.state
const {source, sources} = this.props
switch (step) {
@ -78,6 +78,7 @@ class ImportDashboardOverlay extends PureComponent<Props, State> {
source={source}
sources={sources}
importedSources={importedSources}
variables={dashboard.templates || []}
onSubmit={this.handleUploadDashboard}
/>
)
@ -117,6 +118,19 @@ class ImportDashboardOverlay extends PureComponent<Props, State> {
if (!_.isEmpty(dashboard)) {
const cells = getDeep<Cell[]>(dashboard, 'cells', [])
const importedSources = getDeep<ImportedSources>(meta, 'sources', {})
const templates = (dashboard.templates || []) as Template[]
templates.forEach(t => {
if (
t.sourceID &&
t.sourceID !== 'dynamic' &&
!importedSources[t.sourceID]
) {
importedSources[t.sourceID] = {
name: `Variable source ${t.sourceID}`,
link: `/chronograf/v1/sources/${t.sourceID}`,
}
}
})
this.setState({
cells,
dashboard,
@ -133,12 +147,23 @@ class ImportDashboardOverlay extends PureComponent<Props, State> {
}
}
private handleUploadDashboard = (cells: Cell[]): void => {
private handleUploadDashboard = (
cells: Cell[],
mappings: SourceMappings
): void => {
const {dashboard} = this.state
const {onImportDashboard, onDismissOverlay} = this.props
const templates = (dashboard.templates || []).map(x => {
if (!x.sourceID) {
return x
} else {
const mapping = mappings[x.sourceID]
return {...x, sourceID: mapping ? mapping.id : undefined}
}
})
onImportDashboard({...dashboard, cells})
onImportDashboard({...dashboard, cells, templates})
onDismissOverlay()
}
}

View File

@ -17,7 +17,7 @@ import {
import {DYNAMIC_SOURCE, DYNAMIC_SOURCE_ITEM} from 'src/dashboards/constants'
// Types
import {Source, Cell} from 'src/types'
import {Source, Cell, Template} from 'src/types'
import {
SourcesCells,
SourceMappings,
@ -30,7 +30,8 @@ interface Props {
source: Source
sources: Source[]
importedSources: ImportedSources
onSubmit: (cells: Cell[]) => void
variables: Template[]
onSubmit: (cells: Cell[], mappings: SourceMappings) => void
}
interface State {
@ -45,7 +46,7 @@ class ImportDashboardMappings extends Component<Props, State> {
}
public componentDidMount() {
const {cells, importedSources, source} = this.props
const {cells, importedSources, source, variables} = this.props
if (_.isEmpty(cells)) {
return
@ -54,7 +55,8 @@ class ImportDashboardMappings extends Component<Props, State> {
const {sourcesCells, sourceMappings} = createSourceMappings(
source,
cells,
importedSources
importedSources,
variables
)
this.setState({sourcesCells, sourceMappings})
@ -154,10 +156,8 @@ class ImportDashboardMappings extends Component<Props, State> {
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}>
@ -165,8 +165,8 @@ class ImportDashboardMappings extends Component<Props, State> {
<div className="dash-map--source" data-test="source-label">
{sourceLabel}
</div>
<div className="dash-map--header">{description}</div>
{this.getCellsForSource(sourceID)}
{this.getVariablesForSource(sourceID)}
</td>
<td className="dash-map--table-cell dash-map--table-center">
{this.arrow}
@ -225,16 +225,45 @@ class ImportDashboardMappings extends Component<Props, State> {
return sources[0].name
}
private getCellsForSource(sourceID: string): JSX.Element[] {
private getCellsForSource(sourceID: string): JSX.Element {
const {sourcesCells} = this.state
return _.map(sourcesCells[sourceID], c => {
if (sourcesCells[sourceID].length) {
return (
<div className="dash-map--cell" key={c.id}>
{c.name}
</div>
<>
<div className="dash-map--header">Cells that use this Source:</div>
{_.map(sourcesCells[sourceID], c => {
return (
<div className="dash-map--cell" key={c.id}>
{c.name}
</div>
)
})}
</>
)
})
}
}
private getVariablesForSource(sourceID: string): JSX.Element | undefined {
const {variables} = this.props
const sourceVariables = variables.filter(x => x.sourceID === sourceID)
if (sourceVariables.length) {
return (
<>
<div className="dash-map--header">
Variables that use this source:
</div>
{_.map(sourceVariables, v => {
return (
<div className="dash-map--cell" key={`var-${v.id}`}>
{v.tempVar}
</div>
)
})}
</>
)
}
}
private handleChooseDropdown = (item: SourceItemValue): void => {
@ -250,7 +279,7 @@ class ImportDashboardMappings extends Component<Props, State> {
const mappedCells = mapCells(cells, sourceMappings, importedSources)
onSubmit(mappedCells)
onSubmit(mappedCells, sourceMappings)
}
}

View File

@ -8,7 +8,7 @@ import {getDeep} from 'src/utils/wrappers'
import {DYNAMIC_SOURCE, DYNAMIC_SOURCE_INFO} from 'src/dashboards/constants'
// Types
import {CellQuery, Cell, Source} from 'src/types'
import {CellQuery, Cell, Source, Template} from 'src/types'
import {
CellInfo,
SourceInfo,
@ -22,7 +22,8 @@ const REGEX_SOURCE_ID = /sources\/(\d+)/g
export const createSourceMappings = (
source,
cells,
importedSources
importedSources,
variables: Template[] = []
): {sourcesCells: SourcesCells; sourceMappings: SourceMappings} => {
let sourcesCells: SourcesCells = {}
const sourceMappings: SourceMappings = {}
@ -66,6 +67,13 @@ export const createSourceMappings = (
},
sourcesCells
)
// add sources also for variables
variables.forEach(v => {
if (v.sourceID && !sourceMappings[v.sourceID]) {
sourceMappings[v.sourceID] = sourceInfo
sourcesCells[v.sourceID] = []
}
})
if (cellsWithNoSource.length) {
sourcesCells[DYNAMIC_SOURCE] = cellsWithNoSource

View File

@ -113,8 +113,9 @@ class TemplateVariableEditor extends PureComponent<Props, State> {
sourceID = props.template.sourceID
selectedSource = props.sources.find(source => source.id === sourceID)
if (!selectedSource) {
const v = props.template.tempVar
console.error(
`Template for tempVar '${props.template.tempVar}' uses source '${sourceID}' that does not exist. Using dynamic source.`
`Variable '${v}' uses source '${sourceID}' that does not exist.`
)
sourceID = DYNAMIC_SOURCE_DATABASE_ID
}
@ -482,11 +483,11 @@ class TemplateVariableEditor extends PureComponent<Props, State> {
}
}
const mapDispatchToProps = {
const mdtp = {
notify: notifyActionCreator,
}
const mapStateToProps = state => {
const mstp = state => {
const {sources} = state
return {
@ -494,7 +495,4 @@ const mapStateToProps = state => {
}
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(withRouter(TemplateVariableEditor))
export default connect(mstp, mdtp)(withRouter(TemplateVariableEditor))

View File

@ -11,6 +11,7 @@ const setup = (override = {}) => {
sources: [source],
importedSources: {},
onSubmit: () => {},
variables: [],
...override,
}

View File

@ -7,10 +7,11 @@ import {
} from 'src/dashboards/utils/importDashboardMappings'
// fixtures
import {source, cell, query} from 'test/fixtures'
import {source, cell, query, template} from 'test/fixtures'
// constants
import {DYNAMIC_SOURCE, DYNAMIC_SOURCE_INFO} from 'src/dashboards/constants'
import {Template} from 'src/types'
describe('Dashboards.Utils.importDashboardMappings', () => {
describe('createSourceMappings', () => {
@ -150,6 +151,53 @@ describe('Dashboards.Utils.importDashboardMappings', () => {
)
expect(sourceMappings).toEqual(expected)
})
it('maps also imported variables', () => {
const currentSource = {...source, id: 11, name: 'MY SOURCE'}
const sourceLink1 = '/chronograf/v1/sources/1'
const cellName1 = 'Cell 1'
const cellID1 = '1'
const queryWithSource1 = {...query, source: sourceLink1}
const cellWithSource1 = {
...cell,
name: cellName1,
queries: [queryWithSource1],
i: cellID1,
}
const cells = [cellWithSource1]
const importedSources = {
1: {name: 'Source 1', link: sourceLink1},
}
const sourceInfo = {
name: currentSource.name,
id: currentSource.id,
link: currentSource.links.self,
}
const variables: Template[] = [
{
...template,
sourceID: undefined,
},
{
...template,
id: 'var2',
sourceID: '2',
},
]
const expectedMapping = {
1: sourceInfo,
2: sourceInfo,
}
const {sourcesCells, sourceMappings} = createSourceMappings(
currentSource,
cells,
importedSources,
variables
)
expect(sourceMappings).toEqual(expectedMapping)
expect(sourcesCells[2]).toEqual([])
})
})
describe('mapQueriesInCell', () => {

View File

@ -54,6 +54,14 @@ export const source: Source = {
authentication: SourceAuthenticationMethod.Basic,
}
export const template: Template = {
id: '1',
label: 'var',
tempVar: ':var:',
type: TemplateType.Constant,
values: [],
}
export const service: Service = {
id: '1',
sourceID: '1',