Variables: Add confirmation modal when deleting variables (#56016)

This commit is contained in:
kay delaney 2022-09-30 16:32:14 +01:00 committed by GitHub
parent e2ef41be72
commit 81a39b7e5b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 100 additions and 14 deletions

View File

@ -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}

View 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',
}),
};

View File

@ -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>
);
}

View File

@ -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>
);
}

View File

@ -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 (

View File

@ -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 =

View File

@ -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 }>>) => {