From d4b10387065603d67e98dd2873d5cdb565607c5e Mon Sep 17 00:00:00 2001 From: Lin Wang Date: Wed, 16 Oct 2024 12:48:05 +0800 Subject: [PATCH 1/2] Keep confirm modal and disable confirm button during operation Signed-off-by: Lin Wang --- .../workspace_collaborator_table.test.tsx | 129 ++++++++++++++++-- .../workspace_collaborator_table.tsx | 58 ++++++-- 2 files changed, 165 insertions(+), 22 deletions(-) diff --git a/src/plugins/workspace/public/components/workspace_form/workspace_collaborator_table.test.tsx b/src/plugins/workspace/public/components/workspace_form/workspace_collaborator_table.test.tsx index d823fe477474..60e1cb6920bd 100644 --- a/src/plugins/workspace/public/components/workspace_form/workspace_collaborator_table.test.tsx +++ b/src/plugins/workspace/public/components/workspace_form/workspace_collaborator_table.test.tsx @@ -4,7 +4,8 @@ */ import React from 'react'; -import { fireEvent, render } from '@testing-library/react'; +import { fireEvent, render, waitFor, within } from '@testing-library/react'; +import ReactDOM from 'react-dom'; import { WorkspaceCollaboratorTable, getDisplayedType } from './workspace_collaborator_table'; import { createOpenSearchDashboardsReactContext } from '../../../../opensearch_dashboards_react/public'; @@ -28,14 +29,9 @@ const displayedCollaboratorTypes = [ }, ]; -const mockOverlays = { - openModal: jest.fn(), -}; +const mockOverlays = mockCoreStart.overlays; -const { Provider } = createOpenSearchDashboardsReactContext({ - ...mockCoreStart, - overlays: mockOverlays, -}); +const { Provider } = createOpenSearchDashboardsReactContext(mockCoreStart); describe('getDisplayedTypes', () => { it('should return undefined if not match any collaborator type', () => { @@ -64,6 +60,10 @@ describe('getDisplayedTypes', () => { }); describe('WorkspaceCollaboratorTable', () => { + beforeEach(() => { + mockOverlays.openModal.mockClear(); + }); + const mockProps = { displayedCollaboratorTypes, permissionSettings: [ @@ -133,6 +133,60 @@ describe('WorkspaceCollaboratorTable', () => { expect(mockOverlays.openModal).toHaveBeenCalled(); }); + it('should disable delete confirm button when submitting', async () => { + const permissionSettings = [ + { + id: 0, + modes: ['library_write', 'write'], + type: 'user', + userId: 'admin', + }, + ]; + const handleSubmitPermissionSettingsMock = () => + new Promise((resolve) => { + setTimeout(resolve, 1000); + }); + + const { getByText, getByTestId, queryByText } = render( + + <> + +
+ + + ); + + mockOverlays.openModal.mockReturnValue({ + onClose: Promise.resolve(), + close: async () => { + ReactDOM.unmountComponentAtNode(getByTestId('confirm-modal-container')); + }, + }); + const action = getByTestId('workspace-detail-collaborator-table-actions-box'); + fireEvent.click(action); + const deleteCollaborator = getByText('Delete collaborator'); + fireEvent.click(deleteCollaborator); + + mockOverlays.openModal.mock.calls[0][0](getByTestId('confirm-modal-container')); + await waitFor(() => { + expect(getByText('Confirm')).toBeInTheDocument(); + }); + jest.useFakeTimers(); + fireEvent.click(getByText('Confirm')); + await waitFor(() => { + expect(getByText('Confirm').closest('button')).toBeDisabled(); + }); + jest.runAllTimers(); + jest.useRealTimers(); + await waitFor(() => { + expect(queryByText('Confirm')).toBe(null); + }); + }); + it('should openModal when clicking one selection delete', () => { const permissionSettings = [ { @@ -188,7 +242,7 @@ describe('WorkspaceCollaboratorTable', () => { expect(mockOverlays.openModal).toHaveBeenCalled(); }); - it('should openModal when clicking action tools when multi selection', () => { + it('should openModal when clicking action tools when multi selection', async () => { const permissionSettings = [ { id: 0, @@ -204,7 +258,7 @@ describe('WorkspaceCollaboratorTable', () => { }, ]; - const { getByText, getByTestId } = render( + const { getByText, getByTestId, getByRole } = render( @@ -215,6 +269,61 @@ describe('WorkspaceCollaboratorTable', () => { fireEvent.click(actions); const changeAccessLevel = getByText('Change access level'); fireEvent.click(changeAccessLevel); + await waitFor(() => { + fireEvent.click(within(getByRole('dialog')).getByText('Admin')); + }); expect(mockOverlays.openModal).toHaveBeenCalled(); }); + + it('should disable change access level confirm button when submitting', async () => { + const permissionSettings = [ + { + id: 0, + modes: ['library_write', 'write'], + type: 'user', + userId: 'admin', + }, + ]; + const handleSubmitPermissionSettingsMock = () => + new Promise((resolve) => { + setTimeout(resolve, 1000); + }); + + const { getByText, getByTestId, getByRole } = render( + + <> + +
+ + + ); + mockOverlays.openModal.mockReturnValue({ + onClose: Promise.resolve(), + close: async () => { + ReactDOM.unmountComponentAtNode(getByTestId('confirm-modal-container')); + }, + }); + const action = getByTestId('workspace-detail-collaborator-table-actions-box'); + fireEvent.click(action); + fireEvent.click(getByText('Change access level')); + await waitFor(() => { + fireEvent.click(within(getByRole('dialog')).getByText('Read only')); + }); + + mockOverlays.openModal.mock.calls[0][0](getByTestId('confirm-modal-container')); + await waitFor(() => { + expect(getByText('Confirm')).toBeInTheDocument(); + }); + jest.useFakeTimers(); + fireEvent.click(getByText('Confirm')); + await waitFor(() => { + expect(getByText('Confirm').closest('button')).toBeDisabled(); + }); + jest.runAllTimers(); + jest.useRealTimers(); + }); }); diff --git a/src/plugins/workspace/public/components/workspace_form/workspace_collaborator_table.tsx b/src/plugins/workspace/public/components/workspace_form/workspace_collaborator_table.tsx index 0fb31283f5eb..179a0e80143e 100644 --- a/src/plugins/workspace/public/components/workspace_form/workspace_collaborator_table.tsx +++ b/src/plugins/workspace/public/components/workspace_form/workspace_collaborator_table.tsx @@ -19,8 +19,10 @@ import { EuiText, EuiFlexGroup, EuiFlexItem, + EuiConfirmModalProps, } from '@elastic/eui'; import { i18n } from '@osd/i18n'; +import { useMountedState } from 'react-use'; import { WorkspacePermissionSetting } from './types'; import { WorkspacePermissionItemType } from './constants'; import { getPermissionModeId, isWorkspacePermissionSetting } from './utils'; @@ -72,6 +74,38 @@ const deletionModalConfirm = i18n.translate('workspace.detail.collaborator.modal defaultMessage: 'Delete collaborator? The collaborators will not have access to the workspace.', }); +const BaseConfirmModal = ({ + onCancel, + onConfirm, + ...restProps +}: React.PropsWithChildren< + EuiConfirmModalProps & { + onConfirm: () => Promise; + onCancel: () => void; + } +>) => { + const [isProcessing, setIsProcessing] = useState(false); + const isMounted = useMountedState(); + return ( + { + setIsProcessing(true); + try { + await onConfirm(); + } finally { + if (isMounted()) { + setIsProcessing(false); + } + } + }} + confirmButtonDisabled={isProcessing} + isLoading={isProcessing} + /> + ); +}; + const convertPermissionSettingToWorkspaceCollaborator = ( permissionSetting: WorkspacePermissionSetting ) => ({ @@ -193,7 +227,7 @@ export const WorkspaceCollaboratorTable = ({ onConfirm, selections, }: { - onConfirm: () => void; + onConfirm: () => Promise; selections: PermissionSettingWithAccessLevelAndDisplayedType[]; }) => { const adminOfSelection = selections.filter( @@ -202,7 +236,7 @@ export const WorkspaceCollaboratorTable = ({ const shouldShowWarning = adminCollarboratorsNum === adminOfSelection && adminCollarboratorsNum !== 0; const modal = overlays.openModal( -

{shouldShowWarning ? deletionModalWarning : deletionModalConfirm}

-
+ ); return modal; }; @@ -226,12 +260,12 @@ export const WorkspaceCollaboratorTable = ({ const onClick = () => { const modal = openDeleteConfirmModal({ - onConfirm: () => { + onConfirm: async () => { let newSettings = permissionSettings; selection.forEach(({ id }) => { newSettings = newSettings.filter((_item) => _item.id !== id); }); - handleSubmitPermissionSettings(newSettings as WorkspacePermissionSetting[]); + await handleSubmitPermissionSettings(newSettings as WorkspacePermissionSetting[]); setSelection([]); modal.close(); }, @@ -367,7 +401,7 @@ const Actions = ({ onConfirm, selections, }: { - onConfirm: () => void; + onConfirm: () => Promise; selections: PermissionSettingWithAccessLevelAndDisplayedType[]; }) => { close: () => void }; }) => { @@ -382,12 +416,12 @@ const Actions = ({ setIsPopoverOpen(false); if (selection) { const modal = overlays.openModal( - modal.close()} - onConfirm={() => { + onConfirm={async () => { let newSettings = permissionSettings; selection.forEach(({ id }) => { newSettings = newSettings.map((item) => @@ -399,7 +433,7 @@ const Actions = ({ : item ); }); - handleSubmitPermissionSettings(newSettings as WorkspacePermissionSetting[]); + await handleSubmitPermissionSettings(newSettings as WorkspacePermissionSetting[]); modal.close(); }} cancelButtonText="Cancel" @@ -418,7 +452,7 @@ const Actions = ({ })}

-
+ ); } }, @@ -443,12 +477,12 @@ const Actions = ({ setIsPopoverOpen(false); if (selection && openDeleteConfirmModal) { const modal = openDeleteConfirmModal({ - onConfirm: () => { + onConfirm: async () => { let newSettings = permissionSettings; selection.forEach(({ id }) => { newSettings = newSettings.filter((_item) => _item.id !== id); }); - handleSubmitPermissionSettings(newSettings as WorkspacePermissionSetting[]); + await handleSubmitPermissionSettings(newSettings as WorkspacePermissionSetting[]); modal.close(); }, selections: selection, From f51744524563d634884651f60c1c86efaf4f9b47 Mon Sep 17 00:00:00 2001 From: "opensearch-changeset-bot[bot]" <154024398+opensearch-changeset-bot[bot]@users.noreply.github.com> Date: Wed, 16 Oct 2024 04:49:58 +0000 Subject: [PATCH 2/2] Changeset file for PR #8604 created/updated --- changelogs/fragments/8604.yml | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 changelogs/fragments/8604.yml diff --git a/changelogs/fragments/8604.yml b/changelogs/fragments/8604.yml new file mode 100644 index 000000000000..0b186eaa5ef1 --- /dev/null +++ b/changelogs/fragments/8604.yml @@ -0,0 +1,2 @@ +feat: +- [Workspace]Keep confirm modal and disable confirm button during collaborators operation ([#8604](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/8604)) \ No newline at end of file