grafana/public/app/features/dashboard-scene/settings/VariablesEditView.tsx
Sergej-Vlasov 26e1a5887a
DashboardScene: Reset editIndex on variable delete (#84589)
* reset edit index on variable delete

* adjust delete variable test

* adjust test to be more in line with user flow
2024-03-18 12:02:12 +00:00

289 lines
9.4 KiB
TypeScript

import React from 'react';
import { NavModel, NavModelItem, PageLayoutType } from '@grafana/data';
import { SceneComponentProps, SceneObjectBase, SceneVariable, SceneVariables, sceneGraph } from '@grafana/scenes';
import { Page } from 'app/core/components/Page/Page';
import { DashboardScene } from '../scene/DashboardScene';
import { NavToolbarActions } from '../scene/NavToolbarActions';
import { getDashboardSceneFor } from '../utils/utils';
import { EditListViewSceneUrlSync } from './EditListViewSceneUrlSync';
import { DashboardEditView, DashboardEditViewState, useDashboardEditPageNav } from './utils';
import { VariableEditorForm } from './variables/VariableEditorForm';
import { VariableEditorList } from './variables/VariableEditorList';
import {
EditableVariableType,
RESERVED_GLOBAL_VARIABLE_NAME_REGEX,
WORD_CHARACTERS_REGEX,
getVariableDefault,
getVariableScene,
} from './variables/utils';
export interface VariablesEditViewState extends DashboardEditViewState {
editIndex?: number | undefined;
}
export class VariablesEditView extends SceneObjectBase<VariablesEditViewState> implements DashboardEditView {
public static Component = VariableEditorSettingsListView;
public getUrlKey(): string {
return 'variables';
}
protected _urlSync = new EditListViewSceneUrlSync(this);
public getDashboard(): DashboardScene {
return getDashboardSceneFor(this);
}
public getVariableSet(): SceneVariables {
return sceneGraph.getVariables(this.getDashboard());
}
private getVariableIndex = (identifier: string) => {
const variables = this.getVariables();
return variables.findIndex((variable) => variable.state.name === identifier);
};
private replaceEditVariable = (newVariable: SceneVariable) => {
// Find the index of the variable to be deleted
const variableIndex = this.state.editIndex ?? -1;
const { variables } = this.getVariableSet().state;
const variable = variables[variableIndex];
if (!variable) {
// Handle the case where the variable is not found
console.error('Variable not found');
return;
}
const updatedVariables = [...variables.slice(0, variableIndex), newVariable, ...variables.slice(variableIndex + 1)];
// Update the state or the variables array
this.getVariableSet().setState({ variables: updatedVariables });
};
public onDelete = (identifier: string) => {
// Find the index of the variable to be deleted
const variableIndex = this.getVariableIndex(identifier);
const { variables } = this.getVariableSet().state;
if (variableIndex === -1) {
// Handle the case where the variable is not found
console.error('Variable not found');
return;
}
// Create a new array excluding the variable to be deleted
const updatedVariables = [...variables.slice(0, variableIndex), ...variables.slice(variableIndex + 1)];
// Update the state or the variables array
this.getVariableSet().setState({ variables: updatedVariables });
// Remove editIndex otherwise switches to next variable in list
this.setState({ editIndex: undefined });
};
public getVariables() {
return this.getVariableSet().state.variables;
}
public onDuplicated = (identifier: string) => {
const variableIndex = this.getVariableIndex(identifier);
const variables = this.getVariableSet().state.variables;
if (variableIndex === -1) {
console.error('Variable not found');
return;
}
const variableToUpdate = variables[variableIndex];
let copyNumber = 0;
let newName = `copy_of_${variableToUpdate.state.name}`;
// Check if the name is unique, if not, increment the copy number
while (variables.some((v) => v.state.name === newName)) {
copyNumber++;
newName = `copy_of_${variableToUpdate.state.name}_${copyNumber}`;
}
//clone the original variable
const newVariable = variableToUpdate.clone(variableToUpdate.state);
// update state name of the new variable
newVariable.setState({ name: newName });
const updatedVariables = [
...variables.slice(0, variableIndex + 1),
newVariable,
...variables.slice(variableIndex + 1),
];
this.getVariableSet().setState({ variables: updatedVariables });
};
public onOrderChanged = (fromIndex: number, toIndex: number) => {
const variables = this.getVariableSet().state.variables;
if (!this.getVariableSet()) {
return;
}
// check the index are within the variables array
if (fromIndex < 0 || fromIndex >= variables.length || toIndex < 0 || toIndex >= variables.length) {
console.error('Invalid index');
return;
}
const updatedVariables = [...variables];
// Remove the variable from the array
const movedItem = updatedVariables.splice(fromIndex, 1);
updatedVariables.splice(toIndex, 0, movedItem[0]);
const variablesScene = this.getVariableSet();
variablesScene.setState({ variables: updatedVariables });
};
public onEdit = (identifier: string) => {
const variableIndex = this.getVariableIndex(identifier);
if (variableIndex === -1) {
console.error('Variable not found');
return;
}
this.setState({ editIndex: variableIndex });
};
public onAdd = () => {
const variables = this.getVariables();
const variableIndex = variables.length;
//add the new variable to the end of the array
const defaultNewVariable = getVariableDefault(variables);
this.getVariableSet().setState({ variables: [...this.getVariables(), defaultNewVariable] });
this.setState({ editIndex: variableIndex });
};
public onTypeChange = (type: EditableVariableType) => {
// Find the index of the variable to be deleted
const variableIndex = this.state.editIndex ?? -1;
const { variables } = this.getVariableSet().state;
const variable = variables[variableIndex];
if (!variable) {
// Handle the case where the variable is not found
console.error('Variable not found');
return;
}
const { name, label } = variable.state;
const newVariable = getVariableScene(type, { name, label });
this.replaceEditVariable(newVariable);
};
public onGoBack = () => {
this.setState({ editIndex: undefined });
};
public onValidateVariableName = (name: string, key: string | undefined): [true, string] | [false, null] => {
let errorText = null;
if (!RESERVED_GLOBAL_VARIABLE_NAME_REGEX.test(name)) {
errorText = "Template names cannot begin with '__', that's reserved for Grafana's global variables";
}
if (!WORD_CHARACTERS_REGEX.test(name)) {
errorText = 'Only word characters are allowed in variable names';
}
const variable = this.getVariableSet().getByName(name)?.state;
if (variable && variable.key !== key) {
errorText = 'Variable with the same name already exists';
}
if (errorText) {
return [true, errorText];
}
return [false, null];
};
}
function VariableEditorSettingsListView({ model }: SceneComponentProps<VariablesEditView>) {
const dashboard = model.getDashboard();
const { navModel, pageNav } = useDashboardEditPageNav(dashboard, model.getUrlKey());
// get variables from dashboard state
const { onDelete, onDuplicated, onOrderChanged, onEdit, onTypeChange, onGoBack, onAdd } = model;
const { variables } = model.getVariableSet().useState();
const { editIndex } = model.useState();
if (editIndex !== undefined && variables[editIndex]) {
const variable = variables[editIndex];
if (variable) {
return (
<VariableEditorSettingsView
variable={variable}
onTypeChange={onTypeChange}
onGoBack={onGoBack}
pageNav={pageNav}
navModel={navModel}
dashboard={dashboard}
onDelete={onDelete}
onValidateVariableName={model.onValidateVariableName}
/>
);
}
}
return (
<Page navModel={navModel} pageNav={pageNav} layout={PageLayoutType.Standard}>
<NavToolbarActions dashboard={dashboard} />
<VariableEditorList
variables={variables}
onDelete={onDelete}
onDuplicate={onDuplicated}
onChangeOrder={onOrderChanged}
onAdd={onAdd}
onEdit={onEdit}
/>
</Page>
);
}
interface VariableEditorSettingsEditViewProps {
variable: SceneVariable;
pageNav: NavModelItem;
navModel: NavModel;
dashboard: DashboardScene;
onTypeChange: (variableType: EditableVariableType) => void;
onGoBack: () => void;
onDelete: (variableName: string) => void;
onValidateVariableName: (name: string, key: string | undefined) => [true, string] | [false, null];
}
function VariableEditorSettingsView({
variable,
pageNav,
navModel,
dashboard,
onTypeChange,
onGoBack,
onDelete,
onValidateVariableName,
}: VariableEditorSettingsEditViewProps) {
const parentTab = pageNav.children!.find((p) => p.active)!;
parentTab.parentItem = pageNav;
const { name } = variable.useState();
const editVariablePageNav = {
text: name,
parentItem: parentTab,
};
return (
<Page navModel={navModel} pageNav={editVariablePageNav} layout={PageLayoutType.Standard}>
<NavToolbarActions dashboard={dashboard} />
<VariableEditorForm
variable={variable}
onTypeChange={onTypeChange}
onGoBack={onGoBack}
onDelete={onDelete}
onValidateVariableName={onValidateVariableName}
// force refresh when navigating using back/forward between variables
key={variable.state.key}
/>
</Page>
);
}