diff --git a/public/app/features/variables/editor/VariableEditorContainer.tsx b/public/app/features/variables/editor/VariableEditorContainer.tsx index 96fab774102..4182c07affd 100644 --- a/public/app/features/variables/editor/VariableEditorContainer.tsx +++ b/public/app/features/variables/editor/VariableEditorContainer.tsx @@ -2,7 +2,7 @@ import React, { MouseEvent, PureComponent } from 'react'; import { Icon } from '@grafana/ui'; import { selectors } from '@grafana/e2e-selectors'; -import { NEW_VARIABLE_ID, toVariableIdentifier, toVariablePayload, VariableIdentifier } from '../state/types'; +import { toVariableIdentifier, toVariablePayload, VariableIdentifier } from '../state/types'; import { StoreState } from '../../../types'; import { VariableEditorEditor } from './VariableEditorEditor'; import { MapDispatchToProps, MapStateToProps } from 'react-redux'; @@ -78,16 +78,7 @@ class VariableEditorContainerUnconnected extends PureComponent { > Variables - {this.props.idInEditor === NEW_VARIABLE_ID && ( - - - New - - )} - {this.props.idInEditor && this.props.idInEditor !== NEW_VARIABLE_ID && ( + {this.props.idInEditor && ( { return; } - if (this.props.variable.id !== NEW_VARIABLE_ID) { - await this.props.onEditorUpdate(this.props.identifier); - } - - if (this.props.variable.id === NEW_VARIABLE_ID) { - await this.props.onEditorAdd(this.props.identifier); - } + await this.props.onEditorUpdate(this.props.identifier); }; render() { @@ -115,7 +109,6 @@ export class VariableEditorEditorUnConnected extends PureComponent { if (!EditorToRender) { return null; } - const newVariable = this.props.variable.id && this.props.variable.id === NEW_VARIABLE_ID; const loading = variable.state === LoadingState.Loading; return ( @@ -148,8 +141,8 @@ export class VariableEditorEditorUnConnected extends PureComponent { onChange={this.onTypeChange} aria-label={selectors.pages.Dashboard.Settings.Variables.Edit.General.generalTypeSelect} > - {variableAdapters.list().map(({ id, name }) => ( - ))} @@ -211,7 +204,7 @@ export class VariableEditorEditorUnConnected extends PureComponent { aria-label={selectors.pages.Dashboard.Settings.Variables.Edit.General.submitButton} disabled={loading} > - {newVariable ? 'Add' : 'Update'} + Update {loading ? : null} @@ -232,7 +225,6 @@ const mapDispatchToProps: MapDispatchToProps = { changeVariableName, changeVariableProp, onEditorUpdate, - onEditorAdd, changeVariableType, updateOptions, }; diff --git a/public/app/features/variables/editor/actions.test.ts b/public/app/features/variables/editor/actions.test.ts new file mode 100644 index 00000000000..d8a2d636ae4 --- /dev/null +++ b/public/app/features/variables/editor/actions.test.ts @@ -0,0 +1,23 @@ +import { constantBuilder, customBuilder } from '../shared/testing/builders'; +import { getNextAvailableId } from './actions'; + +describe('getNextAvailableId', () => { + describe('when called with a custom type and there is already 2 variables', () => { + it('then the correct id should be created', () => { + const custom1 = customBuilder() + .withId('custom0') + .withName('custom0') + .build(); + const constant1 = constantBuilder() + .withId('custom1') + .withName('custom1') + .build(); + const variables = [custom1, constant1]; + const type = 'custom'; + + const result = getNextAvailableId(type, variables); + + expect(result).toEqual('custom2'); + }); + }); +}); diff --git a/public/app/features/variables/editor/actions.ts b/public/app/features/variables/editor/actions.ts index 593055abebf..ff332409353 100644 --- a/public/app/features/variables/editor/actions.ts +++ b/public/app/features/variables/editor/actions.ts @@ -9,17 +9,12 @@ import { variableEditorUnMounted, } from './reducer'; import { variableAdapters } from '../adapters'; -import { - AddVariable, - NEW_VARIABLE_ID, - toVariableIdentifier, - toVariablePayload, - VariableIdentifier, -} from '../state/types'; +import { AddVariable, toVariableIdentifier, toVariablePayload, VariableIdentifier } from '../state/types'; import cloneDeep from 'lodash/cloneDeep'; import { VariableType } from '@grafana/data'; -import { addVariable, removeVariable, storeNewVariable } from '../state/sharedReducer'; +import { addVariable, removeVariable } from '../state/sharedReducer'; import { updateOptions } from '../state/actions'; +import { VariableModel } from '../types'; export const variableEditorMount = (identifier: VariableIdentifier): ThunkResult => { return async dispatch => { @@ -30,9 +25,6 @@ export const variableEditorMount = (identifier: VariableIdentifier): ThunkResult export const variableEditorUnMount = (identifier: VariableIdentifier): ThunkResult => { return async (dispatch, getState) => { dispatch(variableEditorUnMounted(toVariablePayload(identifier))); - if (getState().templating.variables[NEW_VARIABLE_ID]) { - dispatch(removeVariable(toVariablePayload({ type: identifier.type, id: NEW_VARIABLE_ID }, { reIndex: false }))); - } }; }; @@ -43,17 +35,6 @@ export const onEditorUpdate = (identifier: VariableIdentifier): ThunkResult => { - return async (dispatch, getState) => { - const newVariableInState = getVariable(NEW_VARIABLE_ID, getState()); - const id = newVariableInState.name; - dispatch(storeNewVariable(toVariablePayload({ type: identifier.type, id }))); - await dispatch(updateOptions(identifier)); - dispatch(switchToListMode()); - dispatch(removeVariable(toVariablePayload({ type: identifier.type, id: NEW_VARIABLE_ID }, { reIndex: false }))); - }; -}; - export const changeVariableName = (identifier: VariableIdentifier, newName: string): ThunkResult => { return (dispatch, getState) => { let errorText = null; @@ -77,18 +58,10 @@ export const changeVariableName = (identifier: VariableIdentifier, newName: stri return; } - const thunkToCall = identifier.id === NEW_VARIABLE_ID ? completeChangeNewVariableName : completeChangeVariableName; - dispatch(thunkToCall(identifier, newName)); + dispatch(completeChangeVariableName(identifier, newName)); }; }; -export const completeChangeNewVariableName = ( - identifier: VariableIdentifier, - newName: string -): ThunkResult => dispatch => { - dispatch(changeVariableNameSucceeded(toVariablePayload(identifier, { newName }))); -}; - export const completeChangeVariableName = (identifier: VariableIdentifier, newName: string): ThunkResult => ( dispatch, getState @@ -109,13 +82,14 @@ export const completeChangeVariableName = (identifier: VariableIdentifier, newNa dispatch(removeVariable(toVariablePayload(identifier, { reIndex: false }))); }; -export const switchToNewMode = (): ThunkResult => (dispatch, getState) => { - const type: VariableType = 'query'; - const id = NEW_VARIABLE_ID; - const global = false; - const model = cloneDeep(variableAdapters.get(type).initialState); - const index = getNewVariabelIndex(getState()); +export const switchToNewMode = (type: VariableType = 'query'): ThunkResult => (dispatch, getState) => { + const id = getNextAvailableId(type, getVariables(getState())); const identifier = { type, id }; + const global = false; + const index = getNewVariabelIndex(getState()); + const model = cloneDeep(variableAdapters.get(type).initialState); + model.id = id; + model.name = id; dispatch( addVariable( toVariablePayload(identifier, { global, model, index }) @@ -131,3 +105,14 @@ export const switchToEditMode = (identifier: VariableIdentifier): ThunkResult => dispatch => { dispatch(clearIdInEditor()); }; + +export function getNextAvailableId(type: VariableType, variables: VariableModel[]): string { + let counter = 0; + let nextId = `${type}${counter}`; + + while (variables.find(variable => variable.id === nextId)) { + nextId = `${type}${++counter}`; + } + + return nextId; +} diff --git a/public/app/features/variables/inspect/VariablesDependenciesButton.tsx b/public/app/features/variables/inspect/VariablesDependenciesButton.tsx index 21a55aceab9..92f68731c89 100644 --- a/public/app/features/variables/inspect/VariablesDependenciesButton.tsx +++ b/public/app/features/variables/inspect/VariablesDependenciesButton.tsx @@ -21,6 +21,10 @@ export const UnProvidedVariablesDependenciesButton: FC = ({ variables }) const nodes = useMemo(() => createDependencyNodes(variables), [variables]); const edges = useMemo(() => createDependencyEdges(variables), [variables]); + if (!edges.length) { + return null; + } + return ( } getSelectedDataSourceValue = (): string => { - if (!this.props.editor.extended?.dataSources.length) { + if (!this.props.editor.extended?.dataSources?.length) { return ''; } const foundItem = this.props.editor.extended?.dataSources.find(ds => ds.value === this.props.variable.datasource); @@ -201,7 +201,7 @@ export class QueryVariableEditorUnConnected extends PureComponent selectors.pages.Dashboard.Settings.Variables.Edit.QueryVariable.queryOptionsDataSourceSelect } > - {this.props.editor.extended?.dataSources.length && + {this.props.editor.extended?.dataSources?.length && this.props.editor.extended?.dataSources.map(ds => (