mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Variables: Add confirmation modal when deleting variables (#56016)
This commit is contained in:
parent
e2ef41be72
commit
81a39b7e5b
@ -1,4 +1,4 @@
|
||||
import { css } from '@emotion/css';
|
||||
import { css, cx } from '@emotion/css';
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
|
||||
import { GrafanaTheme2 } from '@grafana/data';
|
||||
@ -25,6 +25,8 @@ export interface ConfirmModalProps {
|
||||
dismissText?: string;
|
||||
/** Icon for the modal header */
|
||||
icon?: IconName;
|
||||
/** Additional styling for modal container */
|
||||
modalClass?: string;
|
||||
/** Text user needs to fill in before confirming */
|
||||
confirmationText?: string;
|
||||
/** Text for alternative button */
|
||||
@ -46,6 +48,7 @@ export const ConfirmModal = ({
|
||||
confirmationText,
|
||||
dismissText = 'Cancel',
|
||||
alternativeText,
|
||||
modalClass,
|
||||
icon = 'exclamation-triangle',
|
||||
onConfirm,
|
||||
onDismiss,
|
||||
@ -66,7 +69,7 @@ export const ConfirmModal = ({
|
||||
}, [isOpen]);
|
||||
|
||||
return (
|
||||
<Modal className={styles.modal} title={title} icon={icon} isOpen={isOpen} onDismiss={onDismiss}>
|
||||
<Modal className={cx(styles.modal, modalClass)} title={title} icon={icon} isOpen={isOpen} onDismiss={onDismiss}>
|
||||
<div className={styles.modalText}>
|
||||
{body}
|
||||
{description ? <div className={styles.modalDescription}>{description}</div> : null}
|
||||
|
34
public/app/features/variables/editor/ConfirmDeleteModal.tsx
Normal file
34
public/app/features/variables/editor/ConfirmDeleteModal.tsx
Normal file
@ -0,0 +1,34 @@
|
||||
import { css } from '@emotion/css';
|
||||
import React from 'react';
|
||||
|
||||
import { ConfirmModal } from '@grafana/ui';
|
||||
|
||||
interface Props {
|
||||
varName: string;
|
||||
isOpen: boolean;
|
||||
onConfirm: () => void;
|
||||
onDismiss: () => void;
|
||||
}
|
||||
|
||||
export function ConfirmDeleteModal({ varName, isOpen = false, onConfirm, onDismiss }: Props) {
|
||||
return (
|
||||
<ConfirmModal
|
||||
title="Delete variable"
|
||||
isOpen={isOpen}
|
||||
onConfirm={onConfirm}
|
||||
onDismiss={onDismiss}
|
||||
body={`
|
||||
Are you sure you want to delete variable "${varName}"?
|
||||
`}
|
||||
modalClass={styles.modal}
|
||||
confirmText="Delete"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = {
|
||||
modal: css({
|
||||
width: 'max-content',
|
||||
maxWidth: '80vw',
|
||||
}),
|
||||
};
|
@ -14,6 +14,7 @@ import { changeVariableOrder, duplicateVariable, removeVariable } from '../state
|
||||
import { KeyedVariableIdentifier } from '../state/types';
|
||||
import { toKeyedVariableIdentifier, toVariablePayload } from '../utils';
|
||||
|
||||
import { ConfirmDeleteModal } from './ConfirmDeleteModal';
|
||||
import { VariableEditorEditor } from './VariableEditorEditor';
|
||||
import { VariableEditorList } from './VariableEditorList';
|
||||
import { createNewVariable, initListMode } from './actions';
|
||||
@ -60,7 +61,15 @@ const connector = connect(mapStateToProps, mapDispatchToProps);
|
||||
|
||||
type Props = OwnProps & ConnectedProps<typeof connector>;
|
||||
|
||||
class VariableEditorContainerUnconnected extends PureComponent<Props> {
|
||||
interface State {
|
||||
variableId?: KeyedVariableIdentifier;
|
||||
}
|
||||
|
||||
class VariableEditorContainerUnconnected extends PureComponent<Props, State> {
|
||||
state: State = {
|
||||
variableId: undefined,
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
this.props.initListMode(this.props.dashboard.uid);
|
||||
}
|
||||
@ -82,8 +91,17 @@ class VariableEditorContainerUnconnected extends PureComponent<Props> {
|
||||
this.props.duplicateVariable(identifier);
|
||||
};
|
||||
|
||||
onRemoveVariable = (identifier: KeyedVariableIdentifier) => {
|
||||
this.props.removeVariable(identifier);
|
||||
onModalOpen = (identifier: KeyedVariableIdentifier) => {
|
||||
this.setState({ variableId: identifier });
|
||||
};
|
||||
|
||||
onModalClose = () => {
|
||||
this.setState({ variableId: undefined });
|
||||
};
|
||||
|
||||
onRemoveVariable = () => {
|
||||
this.props.removeVariable(this.state.variableId!);
|
||||
this.onModalClose();
|
||||
};
|
||||
|
||||
render() {
|
||||
@ -100,7 +118,7 @@ class VariableEditorContainerUnconnected extends PureComponent<Props> {
|
||||
onEdit={this.onEditVariable}
|
||||
onChangeOrder={this.onChangeVariableOrder}
|
||||
onDuplicate={this.onDuplicateVariable}
|
||||
onDelete={this.onRemoveVariable}
|
||||
onDelete={this.onModalOpen}
|
||||
usages={this.props.usages}
|
||||
usagesNetwork={this.props.usagesNetwork}
|
||||
/>
|
||||
@ -109,6 +127,12 @@ class VariableEditorContainerUnconnected extends PureComponent<Props> {
|
||||
<VariablesUnknownTable variables={this.props.variables} dashboard={this.props.dashboard} />
|
||||
)}
|
||||
{variableToEdit && <VariableEditorEditor identifier={toKeyedVariableIdentifier(variableToEdit)} />}
|
||||
<ConfirmDeleteModal
|
||||
isOpen={this.state.variableId !== undefined}
|
||||
varName={this.state.variableId?.id ?? ''}
|
||||
onConfirm={this.onRemoveVariable}
|
||||
onDismiss={this.onModalClose}
|
||||
/>
|
||||
</Page>
|
||||
);
|
||||
}
|
||||
|
@ -20,6 +20,7 @@ import { KeyedVariableIdentifier } from '../state/types';
|
||||
import { VariableHide } from '../types';
|
||||
import { toKeyedVariableIdentifier, toVariablePayload } from '../utils';
|
||||
|
||||
import { ConfirmDeleteModal } from './ConfirmDeleteModal';
|
||||
import { VariableHideSelect } from './VariableHideSelect';
|
||||
import { VariableSectionHeader } from './VariableSectionHeader';
|
||||
import { VariableTextField } from './VariableTextField';
|
||||
@ -61,7 +62,15 @@ export interface OwnProps {
|
||||
|
||||
type Props = OwnProps & ConnectedProps<typeof connector>;
|
||||
|
||||
export class VariableEditorEditorUnConnected extends PureComponent<Props> {
|
||||
interface State {
|
||||
showDeleteModal: boolean;
|
||||
}
|
||||
|
||||
export class VariableEditorEditorUnConnected extends PureComponent<Props, State> {
|
||||
state: State = {
|
||||
showDeleteModal: false,
|
||||
};
|
||||
|
||||
componentDidMount(): void {
|
||||
this.props.variableEditorMount(this.props.identifier);
|
||||
}
|
||||
@ -120,8 +129,17 @@ export class VariableEditorEditorUnConnected extends PureComponent<Props> {
|
||||
this.props.updateOptions(toKeyedVariableIdentifier(this.props.variable));
|
||||
};
|
||||
|
||||
onModalOpen = () => {
|
||||
this.setState({ showDeleteModal: true });
|
||||
};
|
||||
|
||||
onModalClose = () => {
|
||||
this.setState({ showDeleteModal: false });
|
||||
};
|
||||
|
||||
onDelete = () => {
|
||||
this.props.removeVariable(this.props.identifier);
|
||||
this.onModalClose();
|
||||
locationService.partial({ editIndex: null });
|
||||
};
|
||||
|
||||
@ -192,7 +210,7 @@ export class VariableEditorEditorUnConnected extends PureComponent<Props> {
|
||||
{hasOptions(this.props.variable) ? <VariableValuesPreview variable={this.props.variable} /> : null}
|
||||
|
||||
<HorizontalGroup spacing="md">
|
||||
<Button variant="destructive" onClick={this.onDelete}>
|
||||
<Button variant="destructive" onClick={this.onModalOpen}>
|
||||
Delete
|
||||
</Button>
|
||||
<Button
|
||||
@ -212,6 +230,12 @@ export class VariableEditorEditorUnConnected extends PureComponent<Props> {
|
||||
</HorizontalGroup>
|
||||
</VerticalGroup>
|
||||
</form>
|
||||
<ConfirmDeleteModal
|
||||
isOpen={this.state.showDeleteModal}
|
||||
varName={this.props.editor.name}
|
||||
onConfirm={this.onDelete}
|
||||
onDismiss={this.onModalClose}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -40,7 +40,7 @@ export function VariableEditorList({
|
||||
}
|
||||
reportInteraction('Variable drag and drop');
|
||||
const identifier = JSON.parse(result.draggableId);
|
||||
onChangeOrder(identifier, result.source.index, result.destination.index);
|
||||
onChangeOrder(identifier, variables[result.source.index].index, variables[result.destination.index].index);
|
||||
};
|
||||
|
||||
return (
|
||||
|
@ -90,7 +90,8 @@ export const createNewVariable =
|
||||
(key: string | null | undefined, type: VariableType = 'query'): ThunkResult<void> =>
|
||||
(dispatch, getState) => {
|
||||
const rootStateKey = toStateKey(key);
|
||||
const id = getNextAvailableId(type, getVariablesByKey(rootStateKey, getState()));
|
||||
const varsByKey = getVariablesByKey(rootStateKey, getState());
|
||||
const id = getNextAvailableId(type, varsByKey);
|
||||
const identifier: VariableIdentifier = { type, id };
|
||||
const global = false;
|
||||
const index = getNewVariableIndex(rootStateKey, getState());
|
||||
@ -102,7 +103,7 @@ export const createNewVariable =
|
||||
toKeyedAction(rootStateKey, addVariable(toVariablePayload<AddVariable>(identifier, { global, model, index })))
|
||||
);
|
||||
|
||||
locationService.partial({ editIndex: index });
|
||||
locationService.partial({ editIndex: varsByKey.length });
|
||||
};
|
||||
|
||||
export const initListMode =
|
||||
|
@ -67,9 +67,9 @@ const sharedReducerSlice = createSlice({
|
||||
return;
|
||||
}
|
||||
|
||||
const variableStates = Object.values(state);
|
||||
for (let index = 0; index < variableStates.length; index++) {
|
||||
variableStates[index].index = index;
|
||||
const variableStates = Object.values(state).sort((a, b) => a.index - b.index);
|
||||
for (let i = 0; i < variableStates.length; i++) {
|
||||
variableStates[i].index = i;
|
||||
}
|
||||
},
|
||||
duplicateVariable: (state: VariablesState, action: PayloadAction<VariablePayload<{ newId: string }>>) => {
|
||||
|
Loading…
Reference in New Issue
Block a user