mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
NestedFolders: stay in the modal whilst actions complete (#69730)
* stay in the modal whilst actions complete * don't return anything here to fix types * ensure we're always resetting button state
This commit is contained in:
parent
9799a28fad
commit
266751b96d
@ -1,10 +1,23 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { render, screen, waitFor } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import React from 'react';
|
||||
|
||||
import { ConfirmModal } from './ConfirmModal';
|
||||
|
||||
describe('ConfirmModal', () => {
|
||||
const mockOnConfirm = jest.fn();
|
||||
|
||||
let user: ReturnType<typeof userEvent.setup>;
|
||||
|
||||
beforeEach(() => {
|
||||
jest.useFakeTimers();
|
||||
user = userEvent.setup({ delay: null });
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.useRealTimers();
|
||||
});
|
||||
|
||||
it('should render correct title, body, dismiss-, alternative- and confirm-text', () => {
|
||||
render(
|
||||
<ConfirmModal
|
||||
@ -76,7 +89,7 @@ describe('ConfirmModal', () => {
|
||||
dismissText="Dismiss Text"
|
||||
isOpen={true}
|
||||
confirmationText="My confirmation text"
|
||||
onConfirm={() => {}}
|
||||
onConfirm={mockOnConfirm}
|
||||
onDismiss={() => {}}
|
||||
onAlternative={() => {}}
|
||||
/>
|
||||
@ -85,7 +98,49 @@ describe('ConfirmModal', () => {
|
||||
expect(screen.getByRole('button', { name: 'Please Confirm' })).toBeInTheDocument();
|
||||
expect(screen.getByRole('button', { name: 'Please Confirm' })).toBeDisabled();
|
||||
|
||||
await userEvent.type(screen.getByPlaceholderText('Type "My confirmation text" to confirm'), 'mY CoNfIrMaTiOn TeXt');
|
||||
expect(screen.getByRole('button', { name: 'Please Confirm' })).not.toBeDisabled();
|
||||
await user.type(screen.getByPlaceholderText('Type "My confirmation text" to confirm'), 'mY CoNfIrMaTiOn TeXt');
|
||||
expect(screen.getByRole('button', { name: 'Please Confirm' })).toBeEnabled();
|
||||
|
||||
await user.click(screen.getByRole('button', { name: 'Please Confirm' }));
|
||||
expect(mockOnConfirm).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('returning a promise in the onConfirm callback disables the button whilst the callback is in progress', async () => {
|
||||
mockOnConfirm.mockImplementation(() => {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
resolve('');
|
||||
}, 1000);
|
||||
});
|
||||
});
|
||||
render(
|
||||
<ConfirmModal
|
||||
title="Some Title"
|
||||
body="Some Body"
|
||||
confirmText="Please Confirm"
|
||||
alternativeText="Alternative Text"
|
||||
dismissText="Dismiss Text"
|
||||
isOpen={true}
|
||||
confirmationText="My confirmation text"
|
||||
onConfirm={mockOnConfirm}
|
||||
onDismiss={() => {}}
|
||||
onAlternative={() => {}}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(screen.getByRole('button', { name: 'Please Confirm' })).toBeInTheDocument();
|
||||
expect(screen.getByRole('button', { name: 'Please Confirm' })).toBeDisabled();
|
||||
|
||||
await user.type(screen.getByPlaceholderText('Type "My confirmation text" to confirm'), 'My confirmation text');
|
||||
expect(screen.getByRole('button', { name: 'Please Confirm' })).toBeEnabled();
|
||||
|
||||
await user.click(screen.getByRole('button', { name: 'Please Confirm' }));
|
||||
expect(mockOnConfirm).toHaveBeenCalled();
|
||||
expect(screen.getByRole('button', { name: 'Please Confirm' })).toBeDisabled();
|
||||
|
||||
jest.runAllTimers();
|
||||
await waitFor(() => {
|
||||
return expect(screen.getByRole('button', { name: 'Please Confirm' })).toBeEnabled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -37,8 +37,10 @@ export interface ConfirmModalProps {
|
||||
alternativeText?: string;
|
||||
/** Confirm button variant */
|
||||
confirmButtonVariant?: ButtonVariant;
|
||||
/** Confirm action callback */
|
||||
onConfirm(): void;
|
||||
/** Confirm action callback
|
||||
* Return a promise to disable the confirm button until the promise is resolved
|
||||
*/
|
||||
onConfirm(): void | Promise<void>;
|
||||
/** Dismiss action callback */
|
||||
onDismiss(): void;
|
||||
/** Alternative action callback */
|
||||
@ -83,6 +85,15 @@ export const ConfirmModal = ({
|
||||
}
|
||||
}, [isOpen, confirmationText]);
|
||||
|
||||
const onConfirmClick = async () => {
|
||||
setDisabled(true);
|
||||
try {
|
||||
await onConfirm();
|
||||
} finally {
|
||||
setDisabled(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal className={cx(styles.modal, modalClass)} title={title} icon={icon} isOpen={isOpen} onDismiss={onDismiss}>
|
||||
<div className={styles.modalText}>
|
||||
@ -102,7 +113,7 @@ export const ConfirmModal = ({
|
||||
</Button>
|
||||
<Button
|
||||
variant={confirmButtonVariant}
|
||||
onClick={onConfirm}
|
||||
onClick={onConfirmClick}
|
||||
disabled={disabled}
|
||||
ref={buttonRef}
|
||||
data-testid={selectors.pages.ConfirmModal.delete}
|
||||
|
@ -56,7 +56,11 @@ export const CloneRuleButton = React.forwardRef<HTMLAnchorElement, CloneRuleButt
|
||||
</div>
|
||||
}
|
||||
confirmText="Copy"
|
||||
onConfirm={() => provRuleCloneUrl && locationService.push(provRuleCloneUrl)}
|
||||
onConfirm={() => {
|
||||
if (provRuleCloneUrl) {
|
||||
locationService.push(provRuleCloneUrl);
|
||||
}
|
||||
}}
|
||||
onDismiss={() => setProvRuleCloneUrl(undefined)}
|
||||
/>
|
||||
</>
|
||||
|
@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
import React, { useState } from 'react';
|
||||
|
||||
import { Space } from '@grafana/experimental';
|
||||
import { ConfirmModal } from '@grafana/ui';
|
||||
@ -10,15 +10,22 @@ import { DescendantCount } from './DescendantCount';
|
||||
|
||||
export interface Props {
|
||||
isOpen: boolean;
|
||||
onConfirm: () => void;
|
||||
onConfirm: () => Promise<void>;
|
||||
onDismiss: () => void;
|
||||
selectedItems: DashboardTreeSelection;
|
||||
}
|
||||
|
||||
export const DeleteModal = ({ onConfirm, onDismiss, selectedItems, ...props }: Props) => {
|
||||
const onDelete = () => {
|
||||
onConfirm();
|
||||
const [isDeleting, setIsDeleting] = useState(false);
|
||||
const onDelete = async () => {
|
||||
setIsDeleting(true);
|
||||
try {
|
||||
await onConfirm();
|
||||
setIsDeleting(false);
|
||||
onDismiss();
|
||||
} catch {
|
||||
setIsDeleting(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
@ -31,7 +38,7 @@ export const DeleteModal = ({ onConfirm, onDismiss, selectedItems, ...props }: P
|
||||
</>
|
||||
}
|
||||
confirmationText="Delete"
|
||||
confirmText="Delete"
|
||||
confirmText={isDeleting ? 'Deleting...' : 'Delete'}
|
||||
onDismiss={onDismiss}
|
||||
onConfirm={onDelete}
|
||||
title="Delete"
|
||||
|
@ -11,20 +11,27 @@ import { DescendantCount } from './DescendantCount';
|
||||
|
||||
export interface Props {
|
||||
isOpen: boolean;
|
||||
onConfirm: (targetFolderUid: string) => void;
|
||||
onConfirm: (targetFolderUid: string) => Promise<void>;
|
||||
onDismiss: () => void;
|
||||
selectedItems: DashboardTreeSelection;
|
||||
}
|
||||
|
||||
export const MoveModal = ({ onConfirm, onDismiss, selectedItems, ...props }: Props) => {
|
||||
const [moveTarget, setMoveTarget] = useState<string>();
|
||||
const [isMoving, setIsMoving] = useState(false);
|
||||
const selectedFolders = Object.keys(selectedItems.folder).filter((uid) => selectedItems.folder[uid]);
|
||||
|
||||
const onMove = () => {
|
||||
const onMove = async () => {
|
||||
if (moveTarget !== undefined) {
|
||||
onConfirm(moveTarget);
|
||||
}
|
||||
setIsMoving(true);
|
||||
try {
|
||||
await onConfirm(moveTarget);
|
||||
setIsMoving(false);
|
||||
onDismiss();
|
||||
} catch {
|
||||
setIsMoving(false);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
@ -45,8 +52,8 @@ export const MoveModal = ({ onConfirm, onDismiss, selectedItems, ...props }: Pro
|
||||
<Button onClick={onDismiss} variant="secondary" fill="outline">
|
||||
Cancel
|
||||
</Button>
|
||||
<Button disabled={moveTarget === undefined} onClick={onMove} variant="primary">
|
||||
Move
|
||||
<Button disabled={moveTarget === undefined || isMoving} onClick={onMove} variant="primary">
|
||||
{isMoving ? 'Moving...' : 'Move'}
|
||||
</Button>
|
||||
</Modal.ButtonRow>
|
||||
</Modal>
|
||||
|
Loading…
Reference in New Issue
Block a user