Skip to content

Commit

Permalink
[workspace]add admin validation when changing the last administrator …
Browse files Browse the repository at this point in the history
…to a lesser access (#8621)

* fix conflict

Signed-off-by: Qxisylolo <qianxisy@amazon.com>

* fix conflict

Signed-off-by: Qxisylolo <qianxisy@amazon.com>

* add tests

Signed-off-by: Qxisylolo <qianxisy@amazon.com>

* add tess

Signed-off-by: Qxisylolo <qianxisy@amazon.com>

* fix typo and test

Signed-off-by: tygao <tygao@amazon.com>

---------

Signed-off-by: Qxisylolo <qianxisy@amazon.com>
Signed-off-by: tygao <tygao@amazon.com>
Co-authored-by: tygao <tygao@amazon.com>
(cherry picked from commit a41f0c5)
Signed-off-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
  • Loading branch information
github-actions[bot] and raintygao committed Oct 18, 2024
1 parent d9c5c78 commit 208bf1a
Show file tree
Hide file tree
Showing 2 changed files with 163 additions and 77 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +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';
import { coreMock } from '../../../../../core/public/mocks';
Expand All @@ -28,14 +28,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', () => {
Expand Down Expand Up @@ -64,6 +59,10 @@ describe('getDisplayedTypes', () => {
});

describe('WorkspaceCollaboratorTable', () => {
beforeEach(() => {
mockOverlays.openModal.mockClear();
});

const mockProps = {
displayedCollaboratorTypes,
permissionSettings: [
Expand Down Expand Up @@ -188,7 +187,7 @@ describe('WorkspaceCollaboratorTable', () => {
expect(mockOverlays.openModal).toHaveBeenCalled();
});

it('should openModal when clicking action tools when multi selection', () => {
it('should openModal and show warning text when changing last admin to a less permission level', async () => {
const permissionSettings = [
{
id: 0,
Expand All @@ -204,17 +203,53 @@ describe('WorkspaceCollaboratorTable', () => {
},
];

const { getByText, getByTestId } = render(
const handleSubmitPermissionSettingsMock = jest.fn();

const { getByText, getByTestId, getByRole } = render(
<Provider>
<WorkspaceCollaboratorTable {...mockProps} permissionSettings={permissionSettings} />
<WorkspaceCollaboratorTable
{...mockProps}
permissionSettings={permissionSettings}
handleSubmitPermissionSettings={handleSubmitPermissionSettingsMock}
/>
<div data-test-subj="modal-container" />
</Provider>
);

mockOverlays.openModal.mockReturnValue({
onClose: Promise.resolve(),
close: async () => {
ReactDOM.unmountComponentAtNode(getByTestId('modal-container'));
},
});

fireEvent.click(getByTestId('checkboxSelectRow-0'));
fireEvent.click(getByTestId('checkboxSelectRow-1'));
const actions = getByTestId('workspace-detail-collaborator-table-actions');
fireEvent.click(actions);
const changeAccessLevel = getByText('Change access level');
fireEvent.click(changeAccessLevel);
expect(mockOverlays.openModal).toHaveBeenCalled();
fireEvent.click(getByText('Change access level'));
await waitFor(() => {
fireEvent.click(within(getByRole('dialog')).getByText('Read only'));
});
mockOverlays.openModal.mock.calls[0][0](getByTestId('modal-container'));
await waitFor(() => {
expect(getByText('Confirm')).toBeInTheDocument();
});
expect(
getByText(
'By changing the last administrator to a lesser access, only application administrators will be able to manage this workspace'
)
).toBeInTheDocument();
jest.useFakeTimers();
fireEvent.click(getByText('Confirm'));

await waitFor(() => {
expect(handleSubmitPermissionSettingsMock).toHaveBeenCalledWith([
{ id: 0, modes: ['library_read', 'read'], type: 'user', userId: 'admin' },
{ group: 'group', id: 1, modes: ['library_read', 'read'], type: 'group' },
]);
});
jest.runAllTimers();
jest.useRealTimers();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -65,9 +65,18 @@ const deletionModalWarning = i18n.translate(
'workspace.workspace.detail.collaborator.modal.delete.warning',
{
defaultMessage:
'Currently you’re the only user who has access to the workspace as an owner. Share this workspace by adding collaborators.',
'By removing the last administrator, only application administrators will be able to manage this workspace',
}
);

const changeAccessModalWarning = i18n.translate(
'workspace.workspace.detail.collaborator.modal.changeAccessLevel.warning',
{
defaultMessage:
'By changing the last administrator to a lesser access, only application administrators will be able to manage this workspace',
}
);

const deletionModalConfirm = i18n.translate('workspace.detail.collaborator.modal.delete.confirm', {
defaultMessage: 'Delete collaborator? The collaborators will not have access to the workspace.',
});
Expand Down Expand Up @@ -152,7 +161,7 @@ export const WorkspaceCollaboratorTable = ({
});
}, [permissionSettings, displayedCollaboratorTypes]);

const adminCollarboratorsNum = useMemo(() => {
const adminCollaboratorsNum = useMemo(() => {
const admins = items.filter((item) => item.accessLevel === WORKSPACE_ACCESS_LEVEL_NAMES.admin);
return admins.length;
}, [items]);
Expand Down Expand Up @@ -202,9 +211,10 @@ export const WorkspaceCollaboratorTable = ({
(item) => item.accessLevel === WORKSPACE_ACCESS_LEVEL_NAMES.admin
).length;
const shouldShowWarning =
adminCollarboratorsNum === adminOfSelection && adminCollarboratorsNum !== 0;
adminCollaboratorsNum === adminOfSelection && adminCollaboratorsNum !== 0;
const modal = overlays.openModal(
<EuiConfirmModal
data-test-subj="delete-confirm-modal"
title={i18n.translate('workspace.detail.collaborator.actions.delete', {
defaultMessage: 'Delete collaborator',
})}
Expand All @@ -221,6 +231,57 @@ export const WorkspaceCollaboratorTable = ({
return modal;
};

const openChangeAccessLevelModal = ({
onConfirm,
selections,
type,
}: {
onConfirm: () => void;
selections: PermissionSettingWithAccessLevelAndDisplayedType[];
type: WorkspaceCollaboratorAccessLevel;
}) => {
let shouldShowWarning = false;
if (type !== 'admin') {
const adminOfSelection = selections.filter(
(item) => item.accessLevel === WORKSPACE_ACCESS_LEVEL_NAMES.admin
).length;
shouldShowWarning = adminCollaboratorsNum - adminOfSelection < 1 && adminCollaboratorsNum > 0;
}

const modal = overlays.openModal(
<EuiConfirmModal
data-test-subj="change-access-confirm-modal"
title={i18n.translate('workspace.detail.collaborator.table.change.access.level', {
defaultMessage: 'Change access level',
})}
onCancel={() => {
modal.close();

Check warning on line 258 in src/plugins/workspace/public/components/workspace_form/workspace_collaborator_table.tsx

View check run for this annotation

Codecov / codecov/patch

src/plugins/workspace/public/components/workspace_form/workspace_collaborator_table.tsx#L258

Added line #L258 was not covered by tests
}}
onConfirm={onConfirm}
cancelButtonText={deletionModalCancelButton}
confirmButtonText={deletionModalConfirmButton}
>
<EuiText color={shouldShowWarning ? 'danger' : 'default'}>
<p>
{shouldShowWarning
? changeAccessModalWarning
: i18n.translate('workspace.detail.collaborator.changeAccessLevel.confirmation', {
defaultMessage:
'Do you want to change access level of {numCollaborators} collaborator{pluralSuffix} to "{accessLevel}"?',
values: {
numCollaborators: selections.length,
pluralSuffix: selections.length > 1 ? 's' : '',
accessLevel: type,
},
})}
</p>
</EuiText>
</EuiConfirmModal>
);

return modal;
};

const renderToolsLeft = () => {
if (selection.length === 0) {
return;
Expand Down Expand Up @@ -283,6 +344,7 @@ export const WorkspaceCollaboratorTable = ({
isTableAction={false}
selection={selection}
handleSubmitPermissionSettings={handleSubmitPermissionSettings}
openChangeAccessLevelModal={openChangeAccessLevelModal}
/>
);
};
Expand Down Expand Up @@ -362,6 +424,7 @@ export const WorkspaceCollaboratorTable = ({
permissionSettings={permissionSettings}
handleSubmitPermissionSettings={handleSubmitPermissionSettings}
openDeleteConfirmModal={openDeleteConfirmModal}
openChangeAccessLevelModal={openChangeAccessLevelModal}
/>
),
},
Expand Down Expand Up @@ -391,6 +454,7 @@ const Actions = ({
permissionSettings,
handleSubmitPermissionSettings,
openDeleteConfirmModal,
openChangeAccessLevelModal,
}: {
isTableAction: boolean;
selection?: PermissionSettingWithAccessLevelAndDisplayedType[];
Expand All @@ -405,80 +469,67 @@ const Actions = ({
onConfirm: () => void;
selections: PermissionSettingWithAccessLevelAndDisplayedType[];
}) => { close: () => void };
openChangeAccessLevelModal?: ({
onConfirm,
selections,
type,
}: {
onConfirm: () => void;
selections: PermissionSettingWithAccessLevelAndDisplayedType[];
type: WorkspaceCollaboratorAccessLevel;
}) => { close: () => void };
}) => {
const [isPopoverOpen, setIsPopoverOpen] = useState(false);
const {
overlays,
services: { notifications },
} = useOpenSearchDashboards();

const accessLevelOptions = (Object.keys(
WORKSPACE_ACCESS_LEVEL_NAMES
) as WorkspaceCollaboratorAccessLevel[]).map((level) => ({
name: WORKSPACE_ACCESS_LEVEL_NAMES[level],
onClick: async () => {
onClick: () => {
setIsPopoverOpen(false);
if (selection) {
const modal = overlays.openModal(
<EuiConfirmModal
title={i18n.translate('workspace.detail.collaborator.table.change.access.level', {
defaultMessage: 'Change access level',
})}
onCancel={() => modal.close()}
onConfirm={async () => {
let newSettings = permissionSettings;
selection.forEach(({ id }) => {
newSettings = newSettings.map((item) =>
id === item.id
? {
...item,
modes: accessLevelNameToWorkspacePermissionModesMap[level],
}
: item
);
});
const result = await handleSubmitPermissionSettings(
newSettings as WorkspacePermissionSetting[]
);
if (result?.success) {
notifications?.toasts?.addSuccess({
title: i18n.translate(
'workspace.detail.collaborator.change.access.success.title',
{
defaultMessage: 'The access level changed',
if (selection && openChangeAccessLevelModal) {
const modal = openChangeAccessLevelModal({
onConfirm: async () => {
let newSettings = permissionSettings;
selection.forEach(({ id }) => {
newSettings = newSettings.map((item) =>
id === item.id
? {
...item,
modes: accessLevelNameToWorkspacePermissionModesMap[level],
}
),
text: i18n.translate('workspace.detail.collaborator.change.access.success.body', {
defaultMessage:
'The access level is changed to {level} for {num} collaborator{pluralSuffix, select, true {} other {s}}.',
values: {
level: WORKSPACE_ACCESS_LEVEL_NAMES[level],
num: selection.length,
pluralSuffix: selection.length === 1,
},
}),
});
}
modal.close();
}}
cancelButtonText="Cancel"
confirmButtonText="Confirm"
>
<EuiText>
<p>
{i18n.translate('workspace.detail.collaborator.changeAccessLevel.confirmation', {
: item
);
});

const result = await handleSubmitPermissionSettings(
newSettings as WorkspacePermissionSetting[]
);

if (result?.success) {
notifications?.toasts?.addSuccess({

Check warning on line 513 in src/plugins/workspace/public/components/workspace_form/workspace_collaborator_table.tsx

View check run for this annotation

Codecov / codecov/patch

src/plugins/workspace/public/components/workspace_form/workspace_collaborator_table.tsx#L513

Added line #L513 was not covered by tests
title: i18n.translate('workspace.detail.collaborator.change.access.success.title', {
defaultMessage: 'The access level changed',
}),
text: i18n.translate('workspace.detail.collaborator.change.access.success.body', {
defaultMessage:
'Do you want to change access level to {numCollaborators} collaborator{pluralSuffix, select, true {} other {s}} to "{accessLevel}"?',
'The access level is changed to {level} for {num} collaborator{pluralSuffix, select, true {} other {s}}.',
values: {
numCollaborators: selection.length,
pluralSuffix: selection.length === 1,
accessLevel: WORKSPACE_ACCESS_LEVEL_NAMES[level],
level: WORKSPACE_ACCESS_LEVEL_NAMES[level],
num: selection.length,
pluralSuffix: selection.length === 1 ? '' : 's',
},
})}
</p>
</EuiText>
</EuiConfirmModal>
);
}),
});
}
modal.close();
},
selections: selection,
type: level,
});
}
},
icon: '',
Expand Down

0 comments on commit 208bf1a

Please sign in to comment.