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 React, { useEffect, useRef, useState } from 'react';
|
||||||
|
|
||||||
import { GrafanaTheme2 } from '@grafana/data';
|
import { GrafanaTheme2 } from '@grafana/data';
|
||||||
@ -25,6 +25,8 @@ export interface ConfirmModalProps {
|
|||||||
dismissText?: string;
|
dismissText?: string;
|
||||||
/** Icon for the modal header */
|
/** Icon for the modal header */
|
||||||
icon?: IconName;
|
icon?: IconName;
|
||||||
|
/** Additional styling for modal container */
|
||||||
|
modalClass?: string;
|
||||||
/** Text user needs to fill in before confirming */
|
/** Text user needs to fill in before confirming */
|
||||||
confirmationText?: string;
|
confirmationText?: string;
|
||||||
/** Text for alternative button */
|
/** Text for alternative button */
|
||||||
@ -46,6 +48,7 @@ export const ConfirmModal = ({
|
|||||||
confirmationText,
|
confirmationText,
|
||||||
dismissText = 'Cancel',
|
dismissText = 'Cancel',
|
||||||
alternativeText,
|
alternativeText,
|
||||||
|
modalClass,
|
||||||
icon = 'exclamation-triangle',
|
icon = 'exclamation-triangle',
|
||||||
onConfirm,
|
onConfirm,
|
||||||
onDismiss,
|
onDismiss,
|
||||||
@ -66,7 +69,7 @@ export const ConfirmModal = ({
|
|||||||
}, [isOpen]);
|
}, [isOpen]);
|
||||||
|
|
||||||
return (
|
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}>
|
<div className={styles.modalText}>
|
||||||
{body}
|
{body}
|
||||||
{description ? <div className={styles.modalDescription}>{description}</div> : null}
|
{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 { KeyedVariableIdentifier } from '../state/types';
|
||||||
import { toKeyedVariableIdentifier, toVariablePayload } from '../utils';
|
import { toKeyedVariableIdentifier, toVariablePayload } from '../utils';
|
||||||
|
|
||||||
|
import { ConfirmDeleteModal } from './ConfirmDeleteModal';
|
||||||
import { VariableEditorEditor } from './VariableEditorEditor';
|
import { VariableEditorEditor } from './VariableEditorEditor';
|
||||||
import { VariableEditorList } from './VariableEditorList';
|
import { VariableEditorList } from './VariableEditorList';
|
||||||
import { createNewVariable, initListMode } from './actions';
|
import { createNewVariable, initListMode } from './actions';
|
||||||
@ -60,7 +61,15 @@ const connector = connect(mapStateToProps, mapDispatchToProps);
|
|||||||
|
|
||||||
type Props = OwnProps & ConnectedProps<typeof connector>;
|
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() {
|
componentDidMount() {
|
||||||
this.props.initListMode(this.props.dashboard.uid);
|
this.props.initListMode(this.props.dashboard.uid);
|
||||||
}
|
}
|
||||||
@ -82,8 +91,17 @@ class VariableEditorContainerUnconnected extends PureComponent<Props> {
|
|||||||
this.props.duplicateVariable(identifier);
|
this.props.duplicateVariable(identifier);
|
||||||
};
|
};
|
||||||
|
|
||||||
onRemoveVariable = (identifier: KeyedVariableIdentifier) => {
|
onModalOpen = (identifier: KeyedVariableIdentifier) => {
|
||||||
this.props.removeVariable(identifier);
|
this.setState({ variableId: identifier });
|
||||||
|
};
|
||||||
|
|
||||||
|
onModalClose = () => {
|
||||||
|
this.setState({ variableId: undefined });
|
||||||
|
};
|
||||||
|
|
||||||
|
onRemoveVariable = () => {
|
||||||
|
this.props.removeVariable(this.state.variableId!);
|
||||||
|
this.onModalClose();
|
||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
@ -100,7 +118,7 @@ class VariableEditorContainerUnconnected extends PureComponent<Props> {
|
|||||||
onEdit={this.onEditVariable}
|
onEdit={this.onEditVariable}
|
||||||
onChangeOrder={this.onChangeVariableOrder}
|
onChangeOrder={this.onChangeVariableOrder}
|
||||||
onDuplicate={this.onDuplicateVariable}
|
onDuplicate={this.onDuplicateVariable}
|
||||||
onDelete={this.onRemoveVariable}
|
onDelete={this.onModalOpen}
|
||||||
usages={this.props.usages}
|
usages={this.props.usages}
|
||||||
usagesNetwork={this.props.usagesNetwork}
|
usagesNetwork={this.props.usagesNetwork}
|
||||||
/>
|
/>
|
||||||
@ -109,6 +127,12 @@ class VariableEditorContainerUnconnected extends PureComponent<Props> {
|
|||||||
<VariablesUnknownTable variables={this.props.variables} dashboard={this.props.dashboard} />
|
<VariablesUnknownTable variables={this.props.variables} dashboard={this.props.dashboard} />
|
||||||
)}
|
)}
|
||||||
{variableToEdit && <VariableEditorEditor identifier={toKeyedVariableIdentifier(variableToEdit)} />}
|
{variableToEdit && <VariableEditorEditor identifier={toKeyedVariableIdentifier(variableToEdit)} />}
|
||||||
|
<ConfirmDeleteModal
|
||||||
|
isOpen={this.state.variableId !== undefined}
|
||||||
|
varName={this.state.variableId?.id ?? ''}
|
||||||
|
onConfirm={this.onRemoveVariable}
|
||||||
|
onDismiss={this.onModalClose}
|
||||||
|
/>
|
||||||
</Page>
|
</Page>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -20,6 +20,7 @@ import { KeyedVariableIdentifier } from '../state/types';
|
|||||||
import { VariableHide } from '../types';
|
import { VariableHide } from '../types';
|
||||||
import { toKeyedVariableIdentifier, toVariablePayload } from '../utils';
|
import { toKeyedVariableIdentifier, toVariablePayload } from '../utils';
|
||||||
|
|
||||||
|
import { ConfirmDeleteModal } from './ConfirmDeleteModal';
|
||||||
import { VariableHideSelect } from './VariableHideSelect';
|
import { VariableHideSelect } from './VariableHideSelect';
|
||||||
import { VariableSectionHeader } from './VariableSectionHeader';
|
import { VariableSectionHeader } from './VariableSectionHeader';
|
||||||
import { VariableTextField } from './VariableTextField';
|
import { VariableTextField } from './VariableTextField';
|
||||||
@ -61,7 +62,15 @@ export interface OwnProps {
|
|||||||
|
|
||||||
type Props = OwnProps & ConnectedProps<typeof connector>;
|
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 {
|
componentDidMount(): void {
|
||||||
this.props.variableEditorMount(this.props.identifier);
|
this.props.variableEditorMount(this.props.identifier);
|
||||||
}
|
}
|
||||||
@ -120,8 +129,17 @@ export class VariableEditorEditorUnConnected extends PureComponent<Props> {
|
|||||||
this.props.updateOptions(toKeyedVariableIdentifier(this.props.variable));
|
this.props.updateOptions(toKeyedVariableIdentifier(this.props.variable));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
onModalOpen = () => {
|
||||||
|
this.setState({ showDeleteModal: true });
|
||||||
|
};
|
||||||
|
|
||||||
|
onModalClose = () => {
|
||||||
|
this.setState({ showDeleteModal: false });
|
||||||
|
};
|
||||||
|
|
||||||
onDelete = () => {
|
onDelete = () => {
|
||||||
this.props.removeVariable(this.props.identifier);
|
this.props.removeVariable(this.props.identifier);
|
||||||
|
this.onModalClose();
|
||||||
locationService.partial({ editIndex: null });
|
locationService.partial({ editIndex: null });
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -192,7 +210,7 @@ export class VariableEditorEditorUnConnected extends PureComponent<Props> {
|
|||||||
{hasOptions(this.props.variable) ? <VariableValuesPreview variable={this.props.variable} /> : null}
|
{hasOptions(this.props.variable) ? <VariableValuesPreview variable={this.props.variable} /> : null}
|
||||||
|
|
||||||
<HorizontalGroup spacing="md">
|
<HorizontalGroup spacing="md">
|
||||||
<Button variant="destructive" onClick={this.onDelete}>
|
<Button variant="destructive" onClick={this.onModalOpen}>
|
||||||
Delete
|
Delete
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
@ -212,6 +230,12 @@ export class VariableEditorEditorUnConnected extends PureComponent<Props> {
|
|||||||
</HorizontalGroup>
|
</HorizontalGroup>
|
||||||
</VerticalGroup>
|
</VerticalGroup>
|
||||||
</form>
|
</form>
|
||||||
|
<ConfirmDeleteModal
|
||||||
|
isOpen={this.state.showDeleteModal}
|
||||||
|
varName={this.props.editor.name}
|
||||||
|
onConfirm={this.onDelete}
|
||||||
|
onDismiss={this.onModalClose}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -40,7 +40,7 @@ export function VariableEditorList({
|
|||||||
}
|
}
|
||||||
reportInteraction('Variable drag and drop');
|
reportInteraction('Variable drag and drop');
|
||||||
const identifier = JSON.parse(result.draggableId);
|
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 (
|
return (
|
||||||
|
@ -90,7 +90,8 @@ export const createNewVariable =
|
|||||||
(key: string | null | undefined, type: VariableType = 'query'): ThunkResult<void> =>
|
(key: string | null | undefined, type: VariableType = 'query'): ThunkResult<void> =>
|
||||||
(dispatch, getState) => {
|
(dispatch, getState) => {
|
||||||
const rootStateKey = toStateKey(key);
|
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 identifier: VariableIdentifier = { type, id };
|
||||||
const global = false;
|
const global = false;
|
||||||
const index = getNewVariableIndex(rootStateKey, getState());
|
const index = getNewVariableIndex(rootStateKey, getState());
|
||||||
@ -102,7 +103,7 @@ export const createNewVariable =
|
|||||||
toKeyedAction(rootStateKey, addVariable(toVariablePayload<AddVariable>(identifier, { global, model, index })))
|
toKeyedAction(rootStateKey, addVariable(toVariablePayload<AddVariable>(identifier, { global, model, index })))
|
||||||
);
|
);
|
||||||
|
|
||||||
locationService.partial({ editIndex: index });
|
locationService.partial({ editIndex: varsByKey.length });
|
||||||
};
|
};
|
||||||
|
|
||||||
export const initListMode =
|
export const initListMode =
|
||||||
|
@ -67,9 +67,9 @@ const sharedReducerSlice = createSlice({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const variableStates = Object.values(state);
|
const variableStates = Object.values(state).sort((a, b) => a.index - b.index);
|
||||||
for (let index = 0; index < variableStates.length; index++) {
|
for (let i = 0; i < variableStates.length; i++) {
|
||||||
variableStates[index].index = index;
|
variableStates[i].index = i;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
duplicateVariable: (state: VariablesState, action: PayloadAction<VariablePayload<{ newId: string }>>) => {
|
duplicateVariable: (state: VariablesState, action: PayloadAction<VariablePayload<{ newId: string }>>) => {
|
||||||
|
Loading…
Reference in New Issue
Block a user