diff --git a/changelogs/fragments/8501.yml b/changelogs/fragments/8501.yml new file mode 100644 index 000000000000..bf37aaa28f7d --- /dev/null +++ b/changelogs/fragments/8501.yml @@ -0,0 +1,2 @@ +feat: +- Add collaborator table to workspace detail page ([#8501](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/8501)) \ No newline at end of file diff --git a/src/plugins/workspace/public/components/workspace_creator/workspace_form_summary_panel.test.tsx b/src/plugins/workspace/public/components/workspace_creator/workspace_form_summary_panel.test.tsx index 5b26f89a2ed2..cac0c18b0287 100644 --- a/src/plugins/workspace/public/components/workspace_creator/workspace_form_summary_panel.test.tsx +++ b/src/plugins/workspace/public/components/workspace_creator/workspace_form_summary_panel.test.tsx @@ -101,9 +101,9 @@ describe('WorkspaceFormSummaryPanel', () => { expect(screen.getByText('Data Source 2')).toBeInTheDocument(); expect(screen.getByText('Data Source 3')).toBeInTheDocument(); expect(screen.getByText('user1')).toBeInTheDocument(); - expect(screen.getByText('Owner')).toBeInTheDocument(); + expect(screen.getByText('Admin')).toBeInTheDocument(); expect(screen.getByText('group1')).toBeInTheDocument(); - expect(screen.getByText('Read')).toBeInTheDocument(); + expect(screen.getByText('Read only')).toBeInTheDocument(); expect(screen.getByText('+1 more')).toBeInTheDocument(); expect(screen.queryByText('user2')).toBeNull(); expect(screen.getByText('Cancel')).toBeInTheDocument(); diff --git a/src/plugins/workspace/public/components/workspace_detail/__snapshots__/workspace_detail.test.tsx.snap b/src/plugins/workspace/public/components/workspace_detail/__snapshots__/workspace_detail.test.tsx.snap index f530088f15f5..0b7f2ad1ef6f 100644 --- a/src/plugins/workspace/public/components/workspace_detail/__snapshots__/workspace_detail.test.tsx.snap +++ b/src/plugins/workspace/public/components/workspace_detail/__snapshots__/workspace_detail.test.tsx.snap @@ -223,6 +223,11 @@ exports[`WorkspaceDetail render workspace detail page normally 1`] = ` +
+ Manage workspace access and permissions. +

diff --git a/src/plugins/workspace/public/components/workspace_detail/workspace_detail.test.tsx b/src/plugins/workspace/public/components/workspace_detail/workspace_detail.test.tsx index 07ed9d425679..f6902d427b36 100644 --- a/src/plugins/workspace/public/components/workspace_detail/workspace_detail.test.tsx +++ b/src/plugins/workspace/public/components/workspace_detail/workspace_detail.test.tsx @@ -16,6 +16,7 @@ import { WorkspaceFormProvider, WorkspaceOperationType } from '../workspace_form import { DataSourceConnectionType } from '../../../common/types'; import * as utilsExports from '../../utils'; import { IntlProvider } from 'react-intl'; +import { of } from 'rxjs'; // all applications const PublicAPPInfoMap = new Map([ @@ -131,6 +132,9 @@ const WorkspaceDetailPage = (props: any) => { return null; }, }, + collaboratorTypes: { + getTypes$: jest.fn().mockReturnValue(of([])), + }, }, }); diff --git a/src/plugins/workspace/public/components/workspace_detail_app.tsx b/src/plugins/workspace/public/components/workspace_detail_app.tsx index 66ea4f3f8b9c..a393026552a5 100644 --- a/src/plugins/workspace/public/components/workspace_detail_app.tsx +++ b/src/plugins/workspace/public/components/workspace_detail_app.tsx @@ -92,7 +92,7 @@ export const WorkspaceDetailApp = (props: WorkspaceDetailPropsWithOnAppLeave) => }, [currentWorkspace, savedObjects, http, notifications]); const handleWorkspaceFormSubmit = useCallback( - async (data: WorkspaceFormSubmitData) => { + async (data: WorkspaceFormSubmitData, refresh?: boolean) => { let result; if (isFormSubmitting) { return; @@ -127,7 +127,8 @@ export const WorkspaceDetailApp = (props: WorkspaceDetailPropsWithOnAppLeave) => defaultMessage: 'Update workspace successfully', }), }); - if (application && http) { + setIsFormSubmitting(false); + if (application && http && refresh) { // Redirect page after one second, leave one second time to show update successful toast. window.setTimeout(() => { window.location.href = formatUrlWithWorkspaceId( diff --git a/src/plugins/workspace/public/components/workspace_form/__snapshots__/add_collaborator_button.test.tsx.snap b/src/plugins/workspace/public/components/workspace_form/__snapshots__/add_collaborator_button.test.tsx.snap new file mode 100644 index 000000000000..98482f3f71b1 --- /dev/null +++ b/src/plugins/workspace/public/components/workspace_form/__snapshots__/add_collaborator_button.test.tsx.snap @@ -0,0 +1,124 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`AddCollaboratorButton should render normally 1`] = ` +Object { + "asFragment": [Function], + "baseElement": +
+
+
+ +
+
+
+ , + "container":
+
+
+ +
+
+
, + "debug": [Function], + "findAllByAltText": [Function], + "findAllByDisplayValue": [Function], + "findAllByLabelText": [Function], + "findAllByPlaceholderText": [Function], + "findAllByRole": [Function], + "findAllByTestId": [Function], + "findAllByText": [Function], + "findAllByTitle": [Function], + "findByAltText": [Function], + "findByDisplayValue": [Function], + "findByLabelText": [Function], + "findByPlaceholderText": [Function], + "findByRole": [Function], + "findByTestId": [Function], + "findByText": [Function], + "findByTitle": [Function], + "getAllByAltText": [Function], + "getAllByDisplayValue": [Function], + "getAllByLabelText": [Function], + "getAllByPlaceholderText": [Function], + "getAllByRole": [Function], + "getAllByTestId": [Function], + "getAllByText": [Function], + "getAllByTitle": [Function], + "getByAltText": [Function], + "getByDisplayValue": [Function], + "getByLabelText": [Function], + "getByPlaceholderText": [Function], + "getByRole": [Function], + "getByTestId": [Function], + "getByText": [Function], + "getByTitle": [Function], + "queryAllByAltText": [Function], + "queryAllByDisplayValue": [Function], + "queryAllByLabelText": [Function], + "queryAllByPlaceholderText": [Function], + "queryAllByRole": [Function], + "queryAllByTestId": [Function], + "queryAllByText": [Function], + "queryAllByTitle": [Function], + "queryByAltText": [Function], + "queryByDisplayValue": [Function], + "queryByLabelText": [Function], + "queryByPlaceholderText": [Function], + "queryByRole": [Function], + "queryByTestId": [Function], + "queryByText": [Function], + "queryByTitle": [Function], + "rerender": [Function], + "unmount": [Function], +} +`; diff --git a/src/plugins/workspace/public/components/workspace_form/__snapshots__/workspace_collaborator_table.test.tsx.snap b/src/plugins/workspace/public/components/workspace_form/__snapshots__/workspace_collaborator_table.test.tsx.snap new file mode 100644 index 000000000000..caaa8456ecdb --- /dev/null +++ b/src/plugins/workspace/public/components/workspace_form/__snapshots__/workspace_collaborator_table.test.tsx.snap @@ -0,0 +1,1262 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`WorkspaceCollaboratorTable should render normally 1`] = ` +Object { + "asFragment": [Function], + "baseElement": +
+
+
+
+
+
+ +
+ + +
+
+
+
+
+
+
+
+ +
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+ +
+ +
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+ +
+
+
+
+ + + ID + + + + + + Type + + + + + + Access level + + + + + + Actions + + +
+
+
+ +
+
+
+
+
+ ID +
+
+ + admin + +
+
+
+ Type +
+
+ + User + +
+
+
+ Access level +
+
+ + Admin + +
+
+
+ Actions +
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+
+ ID +
+
+ + group + +
+
+
+ Type +
+
+ + Group + +
+
+
+ Access level +
+
+ + Read only + +
+
+
+ Actions +
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+ +
+
+
+
+
+
+ , + "container":
+
+
+
+
+
+ +
+ + +
+
+
+
+
+
+
+
+ +
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+ +
+ +
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+ +
+
+
+
+ + + ID + + + + + + Type + + + + + + Access level + + + + + + Actions + + +
+
+
+ +
+
+
+
+
+ ID +
+
+ + admin + +
+
+
+ Type +
+
+ + User + +
+
+
+ Access level +
+
+ + Admin + +
+
+
+ Actions +
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+
+ ID +
+
+ + group + +
+
+
+ Type +
+
+ + Group + +
+
+
+ Access level +
+
+ + Read only + +
+
+
+ Actions +
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+ +
+
+
+
+
+
, + "debug": [Function], + "findAllByAltText": [Function], + "findAllByDisplayValue": [Function], + "findAllByLabelText": [Function], + "findAllByPlaceholderText": [Function], + "findAllByRole": [Function], + "findAllByTestId": [Function], + "findAllByText": [Function], + "findAllByTitle": [Function], + "findByAltText": [Function], + "findByDisplayValue": [Function], + "findByLabelText": [Function], + "findByPlaceholderText": [Function], + "findByRole": [Function], + "findByTestId": [Function], + "findByText": [Function], + "findByTitle": [Function], + "getAllByAltText": [Function], + "getAllByDisplayValue": [Function], + "getAllByLabelText": [Function], + "getAllByPlaceholderText": [Function], + "getAllByRole": [Function], + "getAllByTestId": [Function], + "getAllByText": [Function], + "getAllByTitle": [Function], + "getByAltText": [Function], + "getByDisplayValue": [Function], + "getByLabelText": [Function], + "getByPlaceholderText": [Function], + "getByRole": [Function], + "getByTestId": [Function], + "getByText": [Function], + "getByTitle": [Function], + "queryAllByAltText": [Function], + "queryAllByDisplayValue": [Function], + "queryAllByLabelText": [Function], + "queryAllByPlaceholderText": [Function], + "queryAllByRole": [Function], + "queryAllByTestId": [Function], + "queryAllByText": [Function], + "queryAllByTitle": [Function], + "queryByAltText": [Function], + "queryByDisplayValue": [Function], + "queryByLabelText": [Function], + "queryByPlaceholderText": [Function], + "queryByRole": [Function], + "queryByTestId": [Function], + "queryByText": [Function], + "queryByTitle": [Function], + "rerender": [Function], + "unmount": [Function], +} +`; diff --git a/src/plugins/workspace/public/components/workspace_form/add_collaborator_button.test.tsx b/src/plugins/workspace/public/components/workspace_form/add_collaborator_button.test.tsx new file mode 100644 index 000000000000..f5466a1f0996 --- /dev/null +++ b/src/plugins/workspace/public/components/workspace_form/add_collaborator_button.test.tsx @@ -0,0 +1,108 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import React from 'react'; +import { fireEvent, render } from '@testing-library/react'; + +import { AddCollaboratorButton } from './add_collaborator_button'; + +describe('AddCollaboratorButton', () => { + const mockProps = { + displayedTypes: [], + permissionSettings: [ + { + id: 0, + modes: ['library_write', 'write'], + type: 'user', + userId: 'admin', + }, + { + id: 1, + modes: ['library_read', 'read'], + type: 'group', + group: 'group', + }, + ], + handleSubmitPermissionSettings: jest.fn(), + }; + + it('should render normally', () => { + expect(render()).toMatchSnapshot(); + }); + + it('should display menu popover when clicked', () => { + const { getByTestId } = render(); + const button = getByTestId('add-collaborator-button'); + fireEvent.click(button); + expect(getByTestId('add-collaborator-popover')).toBeInTheDocument(); + }); + + it('should emit onAdd when clicked menu item', () => { + const mockOnAdd = jest.fn(); + const displayedTypes = [ + { + name: 'add user', + buttonLabel: 'add user', + onAdd: mockOnAdd, + id: 'user', + }, + { + name: 'add group', + buttonLabel: 'add group', + onAdd: mockOnAdd, + id: 'group', + }, + ]; + const { getByTestId, getByText } = render( + + ); + const button = getByTestId('add-collaborator-button'); + fireEvent.click(button); + expect(getByTestId('add-collaborator-popover')).toBeInTheDocument(); + const addUserButton = getByText('add user'); + fireEvent.click(addUserButton); + expect(mockOnAdd).toHaveBeenCalled(); + }); + + it('should call handleSubmitPermissionSettings with newPermissionSettings when adding in modal', () => { + const mockOnAdd = jest.fn().mockImplementation(({ onAddCollaborators }) => { + onAddCollaborators([ + { + accessLevel: 'readOnly', + collaboratorId: '2', + permissionType: 'user', + }, + ]); + }); + const displayedTypes = [ + { + name: 'add user', + buttonLabel: 'add user', + onAdd: mockOnAdd, + id: 'user', + }, + { + name: 'add group', + buttonLabel: 'add group', + onAdd: mockOnAdd, + id: 'group', + }, + ]; + const { getByTestId, getByText } = render( + + ); + const button = getByTestId('add-collaborator-button'); + fireEvent.click(button); + expect(getByTestId('add-collaborator-popover')).toBeInTheDocument(); + const addUserButton = getByText('add user'); + fireEvent.click(addUserButton); + expect(mockOnAdd).toHaveBeenCalled(); + expect(mockProps.handleSubmitPermissionSettings).toHaveBeenCalledWith([ + { id: 0, modes: ['library_write', 'write'], type: 'user', userId: 'admin' }, + { group: 'group', id: 1, modes: ['library_read', 'read'], type: 'group' }, + { id: 2, modes: ['library_read', 'read'], type: 'user', userId: '2' }, + ]); + }); +}); diff --git a/src/plugins/workspace/public/components/workspace_form/add_collaborator_button.tsx b/src/plugins/workspace/public/components/workspace_form/add_collaborator_button.tsx new file mode 100644 index 000000000000..fe957574603b --- /dev/null +++ b/src/plugins/workspace/public/components/workspace_form/add_collaborator_button.tsx @@ -0,0 +1,100 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import React, { useState, useRef, useCallback } from 'react'; +import { EuiPopover, EuiContextMenu, EuiButton, EuiContextMenuPanelDescriptor } from '@elastic/eui'; +import { i18n } from '@osd/i18n'; +import { WorkspaceCollaboratorType } from '../../services/workspace_collaborator_types_service'; +import { WorkspaceCollaborator } from '../../types'; +import { PermissionSetting } from './workspace_collaborator_table'; +import { generateNextPermissionSettingsId } from './utils'; +import { accessLevelNameToWorkspacePermissionModesMap } from '../../constants'; +import { WorkspacePermissionItemType } from './constants'; +import { WorkspacePermissionSetting } from './types'; + +interface Props { + displayedTypes: WorkspaceCollaboratorType[]; + permissionSettings: PermissionSetting[]; + handleSubmitPermissionSettings: (permissionSettings: WorkspacePermissionSetting[]) => void; +} + +export const AddCollaboratorButton = ({ + displayedTypes, + permissionSettings, + handleSubmitPermissionSettings, +}: Props) => { + const [isPopoverOpen, setIsPopoverOpen] = useState(false); + + const nextIdRef = useRef(generateNextPermissionSettingsId(permissionSettings)); + + const nextIdGenerator = useCallback(() => { + const nextId = nextIdRef.current; + nextIdRef.current++; + return nextId; + }, []); + + const onAddCollaborators = async (collaborators: WorkspaceCollaborator[]) => { + const addedSettings = collaborators.map(({ permissionType, accessLevel, collaboratorId }) => ({ + type: permissionType as WorkspacePermissionItemType, + modes: accessLevelNameToWorkspacePermissionModesMap[accessLevel], + id: nextIdGenerator(), + ...(permissionType === WorkspacePermissionItemType.User + ? { + userId: collaboratorId, + } + : { + group: collaboratorId, + }), + })); + const newPermissionSettings = [...permissionSettings, ...addedSettings]; + handleSubmitPermissionSettings(newPermissionSettings as WorkspacePermissionSetting[]); + }; + + const panelItems = displayedTypes.map(({ id, buttonLabel, onAdd }) => ({ + id, + name: buttonLabel, + onClick: () => { + onAdd({ onAddCollaborators }); + setIsPopoverOpen(false); + }, + })); + + return ( + setIsPopoverOpen((prev) => !prev)} + size="s" + data-test-subj="add-collaborator-button" + > + {i18n.translate('workspace.workspaceDetail.collaborator.add', { + defaultMessage: 'Add collaborators', + })} + + } + isOpen={isPopoverOpen} + closePopover={() => setIsPopoverOpen(false)} + panelPaddingSize="none" + anchorPosition="downLeft" + initialFocus={false} + > + + + ); +}; diff --git a/src/plugins/workspace/public/components/workspace_form/constants.ts b/src/plugins/workspace/public/components/workspace_form/constants.ts index 8d63d200e0de..4e1da05cb0a8 100644 --- a/src/plugins/workspace/public/components/workspace_form/constants.ts +++ b/src/plugins/workspace/public/components/workspace_form/constants.ts @@ -6,6 +6,7 @@ import { i18n } from '@osd/i18n'; import { WorkspacePermissionMode } from '../../../common/constants'; import { PermissionModeId } from '../../../../../core/public'; +import { WORKSPACE_ACCESS_LEVEL_NAMES } from '../../constants'; export enum WorkspaceOperationType { Create = 'create', @@ -126,29 +127,29 @@ export const PERMISSION_ACCESS_LEVEL_LABEL_ID = 'workspace-form-permission-acces export const permissionModeOptions = [ { value: PermissionModeId.Read, - inputDisplay: i18n.translate( - 'workspace.form.permissionSettingPanel.permissionModeOptions.read', - { - defaultMessage: 'Read', - } - ), + inputDisplay: WORKSPACE_ACCESS_LEVEL_NAMES.readOnly, }, { value: PermissionModeId.ReadAndWrite, - inputDisplay: i18n.translate( - 'workspace.form.permissionSettingPanel.permissionModeOptions.readAndWrite', - { - defaultMessage: 'Read & Write', - } - ), + inputDisplay: WORKSPACE_ACCESS_LEVEL_NAMES.readAndWrite, }, { value: PermissionModeId.Owner, - inputDisplay: i18n.translate( - 'workspace.form.permissionSettingPanel.permissionModeOptions.owner', - { - defaultMessage: 'Owner', - } - ), + inputDisplay: WORKSPACE_ACCESS_LEVEL_NAMES.admin, + }, +]; + +export const typeOptions = [ + { + value: WorkspacePermissionItemType.User, + inputDisplay: i18n.translate('workspace.form.permissionSettingPanel.typeOptions.user', { + defaultMessage: 'User', + }), + }, + { + value: WorkspacePermissionItemType.Group, + inputDisplay: i18n.translate('workspace.form.permissionSettingPanel.typeOptions.group', { + defaultMessage: 'Group', + }), }, ]; diff --git a/src/plugins/workspace/public/components/workspace_form/types.ts b/src/plugins/workspace/public/components/workspace_form/types.ts index 9c8da46e5be9..43c960f274d5 100644 --- a/src/plugins/workspace/public/components/workspace_form/types.ts +++ b/src/plugins/workspace/public/components/workspace_form/types.ts @@ -39,6 +39,7 @@ export interface WorkspaceFormSubmitData { color?: string; permissionSettings?: WorkspacePermissionSetting[]; selectedDataSourceConnections?: DataSourceConnection[]; + shouldNavigate?: boolean; } export enum WorkspaceFormErrorCode { @@ -78,7 +79,7 @@ export type WorkspaceFormErrors = { export interface WorkspaceFormProps { application: ApplicationStart; savedObjects: SavedObjectsStart; - onSubmit?: (formData: WorkspaceFormSubmitData) => void; + onSubmit?: (formData: WorkspaceFormSubmitData, refresh?: boolean) => void; defaultValues?: Partial; operationType: WorkspaceOperationType; permissionEnabled?: boolean; diff --git a/src/plugins/workspace/public/components/workspace_form/use_workspace_form.test.ts b/src/plugins/workspace/public/components/workspace_form/use_workspace_form.test.ts index 078ce9af3e45..52d65091cb89 100644 --- a/src/plugins/workspace/public/components/workspace_form/use_workspace_form.test.ts +++ b/src/plugins/workspace/public/components/workspace_form/use_workspace_form.test.ts @@ -124,7 +124,8 @@ describe('useWorkspaceForm', () => { expect.objectContaining({ name: 'test-workspace-name', features: ['use-case-observability'], - }) + }), + true ); }); it('should update selected use case', () => { diff --git a/src/plugins/workspace/public/components/workspace_form/use_workspace_form.ts b/src/plugins/workspace/public/components/workspace_form/use_workspace_form.ts index 5fb740ef12b2..a2c2cee12066 100644 --- a/src/plugins/workspace/public/components/workspace_form/use_workspace_form.ts +++ b/src/plugins/workspace/public/components/workspace_form/use_workspace_form.ts @@ -120,17 +120,36 @@ export const useWorkspaceForm = ({ return; } - onSubmit?.({ + onSubmit?.( + { + name: currentFormData.name!, + description: currentFormData.description, + color: currentFormData.color || '#FFFFFF', + features: currentFormData.features, + permissionSettings: currentFormData.permissionSettings as WorkspacePermissionSetting[], + selectedDataSourceConnections: currentFormData.selectedDataSourceConnections, + }, + true + ); + }, + [onSubmit, permissionEnabled] + ); + + const handleSubmitPermissionSettings = (settings: WorkspacePermissionSetting[]) => { + setPermissionSettings(settings); + const currentFormData = getFormDataRef.current(); + onSubmit?.( + { name: currentFormData.name!, description: currentFormData.description, color: currentFormData.color || '#FFFFFF', features: currentFormData.features, - permissionSettings: currentFormData.permissionSettings as WorkspacePermissionSetting[], + permissionSettings: settings, selectedDataSourceConnections: currentFormData.selectedDataSourceConnections, - }); - }, - [onSubmit, permissionEnabled] - ); + }, + false + ); + }; const handleColorChange = useCallback['onChange']>((text) => { setColor(text); @@ -165,5 +184,6 @@ export const useWorkspaceForm = ({ setPermissionSettings, setSelectedDataSourceConnections, onAppLeave, + handleSubmitPermissionSettings, }; }; diff --git a/src/plugins/workspace/public/components/workspace_form/utils.test.ts b/src/plugins/workspace/public/components/workspace_form/utils.test.ts index 9f2557ecbfa5..e387393c4a31 100644 --- a/src/plugins/workspace/public/components/workspace_form/utils.test.ts +++ b/src/plugins/workspace/public/components/workspace_form/utils.test.ts @@ -700,21 +700,21 @@ describe('isWorkspacePermissionSetting', () => { describe('getPermissionModeName', () => { it('should return Owner for a valid WorkspacePermissionMode mode', () => { const result = getPermissionModeName(['library_write', 'write'] as WorkspacePermissionMode[]); - expect(result).toBe('Owner'); + expect(result).toBe('Admin'); }); it('should return Read & write for a valid WorkspacePermissionMode mode', () => { const result = getPermissionModeName(['library_write', 'read'] as WorkspacePermissionMode[]); - expect(result).toBe('Read & Write'); + expect(result).toBe('Read and write'); }); it('should return Read for a valid WorkspacePermissionMode mode', () => { const result = getPermissionModeName(['library_read', 'read'] as WorkspacePermissionMode[]); - expect(result).toBe('Read'); + expect(result).toBe('Read only'); }); it('should return Read for a invalid WorkspacePermissionMode mode', () => { const result = getPermissionModeName([] as WorkspacePermissionMode[]); - expect(result).toBe('Read'); + expect(result).toBe('Read only'); }); }); diff --git a/src/plugins/workspace/public/components/workspace_form/utils.ts b/src/plugins/workspace/public/components/workspace_form/utils.ts index 5bbc1125fbeb..a1fdb319e1aa 100644 --- a/src/plugins/workspace/public/components/workspace_form/utils.ts +++ b/src/plugins/workspace/public/components/workspace_form/utils.ts @@ -422,26 +422,7 @@ export const generatePermissionSettingsState = ( ]; } - const finalPermissionSettings = [...(permissionSettings ?? [])]; - const userPermissionExists = finalPermissionSettings.find( - (setting) => setting.type === WorkspacePermissionItemType.User - ); - const groupPermissionExists = finalPermissionSettings.find( - (setting) => setting.type === WorkspacePermissionItemType.Group - ); - if (!userPermissionExists) { - finalPermissionSettings.push({ - ...emptyUserPermission, - id: generateNextPermissionSettingsId(finalPermissionSettings), - } as typeof finalPermissionSettings[0]); - } - if (!groupPermissionExists) { - finalPermissionSettings.push({ - ...emptyUserGroupPermission, - id: generateNextPermissionSettingsId(finalPermissionSettings), - } as typeof finalPermissionSettings[0]); - } - return finalPermissionSettings; + return [...(permissionSettings ?? [])]; }; interface PermissionSettingLike 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 new file mode 100644 index 000000000000..1b4e514e62c8 --- /dev/null +++ b/src/plugins/workspace/public/components/workspace_form/workspace_collaborator_table.test.tsx @@ -0,0 +1,146 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import React from 'react'; +import { fireEvent, render } from '@testing-library/react'; + +import { WorkspaceCollaboratorTable } from './workspace_collaborator_table'; +import { createOpenSearchDashboardsReactContext } from '../../../../opensearch_dashboards_react/public'; +import { coreMock } from '../../../../../core/public/mocks'; + +const mockCoreStart = coreMock.createStart(); + +const mockOverlays = { + openModal: jest.fn(), +}; + +const { Provider } = createOpenSearchDashboardsReactContext({ + ...mockCoreStart, + overlays: mockOverlays, +}); + +describe('WorkspaceCollaboratorTable', () => { + const mockProps = { + displayedCollaboratorTypes: [], + permissionSettings: [ + { + id: 0, + modes: ['library_write', 'write'], + type: 'user', + userId: 'admin', + }, + { + id: 1, + modes: ['library_read', 'read'], + type: 'group', + group: 'group', + }, + ], + handleSubmitPermissionSettings: jest.fn(), + }; + + it('should render normally', () => { + expect(render()).toMatchSnapshot(); + }); + + it('should render empty state when no permission settings', () => { + const permissionSettings = []; + + const { getByText } = render( + + ); + expect(getByText('Your workspace doesn’t have any collaborators.')).toBeInTheDocument(); + }); + + it('should render data on table based on permission settings', () => { + const { getByText } = render(); + expect(getByText('admin')).toBeInTheDocument(); + expect(getByText('group')).toBeInTheDocument(); + }); + + it('should openModal when clicking box actions menu', () => { + const permissionSettings = [ + { + id: 0, + modes: ['library_write', 'write'], + type: 'user', + userId: 'admin', + }, + ]; + + const { getByText, getByTestId } = render( + + + + ); + const action = getByTestId('workspace-detail-collaborator-table-actions-box'); + fireEvent.click(action); + const deleteCollaborator = getByText('Delete collaborator'); + fireEvent.click(deleteCollaborator); + expect(mockOverlays.openModal).toHaveBeenCalled(); + + const changeAccessLevel = getByText('Change access level'); + fireEvent.click(changeAccessLevel); + expect(mockOverlays.openModal).toHaveBeenCalled(); + }); + + it('should openModal when clicking multi selection delete', () => { + const permissionSettings = [ + { + id: 0, + modes: ['library_write', 'write'], + type: 'user', + userId: 'admin', + }, + { + id: 1, + modes: ['library_read', 'read'], + type: 'group', + group: 'group', + }, + ]; + + const { getByText, getByTestId } = render( + + + + ); + fireEvent.click(getByTestId('checkboxSelectRow-0')); + fireEvent.click(getByTestId('checkboxSelectRow-1')); + const deleteCollaborator = getByText('Delete 2 collaborators'); + fireEvent.click(deleteCollaborator); + expect(mockOverlays.openModal).toHaveBeenCalled(); + }); + + it('should openModal when clicking action tools when multi selection', () => { + const permissionSettings = [ + { + id: 0, + modes: ['library_write', 'write'], + type: 'user', + userId: 'admin', + }, + { + id: 1, + modes: ['library_read', 'read'], + type: 'group', + group: 'group', + }, + ]; + + const { getByText, getByTestId } = render( + + + + ); + 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(); + }); +}); 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 new file mode 100644 index 000000000000..0b510cb1f597 --- /dev/null +++ b/src/plugins/workspace/public/components/workspace_form/workspace_collaborator_table.tsx @@ -0,0 +1,393 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import React, { useMemo, useState } from 'react'; +import { + EuiSearchBarProps, + EuiBasicTableColumn, + EuiButtonIcon, + EuiConfirmModal, + EuiInMemoryTable, + EuiPopover, + EuiContextMenu, + EuiButton, + EuiTableSelectionType, + EuiEmptyPrompt, + EuiContextMenuPanelDescriptor, + EuiText, +} from '@elastic/eui'; +import { i18n } from '@osd/i18n'; +import { WorkspacePermissionSetting } from './types'; +import { WorkspacePermissionItemType, permissionModeOptions, typeOptions } from './constants'; +import { getPermissionModeId } from './utils'; +import { useOpenSearchDashboards } from '../../../../opensearch_dashboards_react/public'; +import { AddCollaboratorButton } from './add_collaborator_button'; +import { WorkspaceCollaboratorType } from '../../services/workspace_collaborator_types_service'; +import { + WORKSPACE_ACCESS_LEVEL_NAMES, + accessLevelNameToWorkspacePermissionModesMap, +} from '../../constants'; +import { WorkspaceCollaboratorAccessLevel } from '../../types'; + +export type PermissionSetting = Pick & + Partial; + +interface Props { + permissionSettings: PermissionSetting[]; + displayedCollaboratorTypes: WorkspaceCollaboratorType[]; + handleSubmitPermissionSettings: (permissionSettings: WorkspacePermissionSetting[]) => void; +} + +export const WorkspaceCollaboratorTable = ({ + permissionSettings, + displayedCollaboratorTypes, + handleSubmitPermissionSettings, +}: Props) => { + const [selection, setSelection] = useState([]); + const { overlays } = useOpenSearchDashboards(); + + const items = useMemo(() => { + return permissionSettings.map((setting) => { + const basicSettings = { + ...setting, + // This is used for table display and search match. + displayedType: + typeOptions.find((option) => option.value === setting.type)?.inputDisplay ?? '', + accessLevel: + permissionModeOptions.find( + (option) => option.value === getPermissionModeId(setting.modes ?? []) + )?.inputDisplay ?? '', + }; + // Unique primary key and filter null value + if (setting.type === WorkspacePermissionItemType.User) { + return { + ...basicSettings, + // Id represents the index of the permission setting in the array, will use primaryId for displayed id + primaryId: setting.userId, + }; + } else if (setting.type === WorkspacePermissionItemType.Group) { + return { + ...basicSettings, + primaryId: setting.group, + }; + } + return basicSettings; + }); + }, [permissionSettings]); + + const emptyStateMessage = useMemo(() => { + return ( + + {i18n.translate('workspace.workspaceDetail.collaborator.emptyState.title', { + defaultMessage: 'Your workspace doesn’t have any collaborators.', + })} + + } + titleSize="s" + body={i18n.translate('workspace.workspaceDetail.collaborator.emptyState.body', { + defaultMessage: + 'Currently you’re the only user who has access to the workspace as an owner. Share this workspace by adding collaborators.', + })} + actions={ + + } + /> + ); + }, [displayedCollaboratorTypes, permissionSettings, handleSubmitPermissionSettings]); + + const openDeleteConfirmModal = ({ onConfirm }: { onConfirm: () => void }) => { + const modal = overlays.openModal( + modal.close()} + onConfirm={onConfirm} + cancelButtonText="Cancel" + confirmButtonText="Confirm" + > + +

+ {i18n.translate('workspace.detail.collaborator.delete.confirm', { + defaultMessage: + 'Delete collaborator? The collaborators will not have access to the workspace.', + })} +

+
+
+ ); + return modal; + }; + + const renderToolsLeft = () => { + if (selection.length === 0) { + return; + } + + const onClick = () => { + const modal = openDeleteConfirmModal({ + onConfirm: () => { + let newSettings = permissionSettings; + selection.forEach(({ id }) => { + newSettings = newSettings.filter((_item) => _item.id !== id); + }); + handleSubmitPermissionSettings(newSettings as WorkspacePermissionSetting[]); + setSelection([]); + modal.close(); + }, + }); + }; + + return ( + + {i18n.translate('workspace.detail.collaborator.delete', { + defaultMessage: 'Delete {num} collaborators', + values: { + num: selection.length, + }, + })} + + ); + }; + + const renderToolsRight = () => { + if (selection.length === 0) { + return; + } + return ( + + ); + }; + + const search: EuiSearchBarProps = { + box: { + incremental: true, + }, + filters: [ + { + type: 'field_value_selection', + field: 'displayedType', + name: 'Type', + multiSelect: false, + options: Array.from(new Set(items.map(({ displayedType }) => displayedType ?? ''))).map( + (item) => ({ + value: item, + name: item, + }) + ), + }, + { + type: 'field_value_selection', + field: 'accessLevel', + name: 'Access level', + multiSelect: false, + options: Array.from(new Set(items.map(({ accessLevel }) => accessLevel ?? ''))).map( + (item) => ({ + value: item, + name: item, + }) + ), + }, + ], + toolsLeft: renderToolsLeft(), + toolsRight: renderToolsRight(), + }; + + const columns: Array> = [ + { + field: 'primaryId', + name: 'ID', + }, + { + field: 'displayedType', + name: 'Type', + }, + { + field: 'accessLevel', + name: 'Access level', + }, + { + name: 'Actions', + field: '', + render: (item: PermissionSetting) => ( + + ), + }, + ]; + const selectionValue: EuiTableSelectionType = { + onSelectionChange: (newSelection) => setSelection(newSelection), + }; + + return ( + + ); +}; + +const Actions = ({ + isTableAction, + selection, + permissionSettings, + handleSubmitPermissionSettings, + openDeleteConfirmModal, +}: { + isTableAction: boolean; + selection?: PermissionSetting[]; + permissionSettings: PermissionSetting[]; + handleSubmitPermissionSettings: (permissionSettings: WorkspacePermissionSetting[]) => void; + openDeleteConfirmModal?: ({ onConfirm }: { onConfirm: () => void }) => { close: () => void }; +}) => { + const [isPopoverOpen, setIsPopoverOpen] = useState(false); + const { overlays } = useOpenSearchDashboards(); + + const accessLevelOptions = (Object.keys( + WORKSPACE_ACCESS_LEVEL_NAMES + ) as WorkspaceCollaboratorAccessLevel[]).map((level) => ({ + name: WORKSPACE_ACCESS_LEVEL_NAMES[level], + onClick: async () => { + setIsPopoverOpen(false); + if (selection) { + const modal = overlays.openModal( + modal.close()} + onConfirm={() => { + let newSettings = permissionSettings; + selection.forEach(({ id }) => { + newSettings = newSettings.map((item) => + id === item.id + ? { + ...item, + modes: accessLevelNameToWorkspacePermissionModesMap[level], + } + : item + ); + }); + handleSubmitPermissionSettings(newSettings as WorkspacePermissionSetting[]); + modal.close(); + }} + cancelButtonText="Cancel" + confirmButtonText="Confirm" + > + +

+ {i18n.translate('workspace.detail.collaborator.changeAccessLevel.confirmation', { + defaultMessage: + 'Do you want to change access level to {numCollaborators} collaborator{pluralSuffix} to "{accessLevel}"?', + values: { + numCollaborators: selection.length, + pluralSuffix: selection.length > 1 ? 's' : '', + accessLevel: WORKSPACE_ACCESS_LEVEL_NAMES[level], + }, + })} +

+
+
+ ); + } + }, + icon: '', + })); + + const panelItems = ([ + { + id: 0, + items: [ + { + name: i18n.translate('workspace.detail.collaborator.actions.change.access', { + defaultMessage: 'Change access level', + }), + panel: 1, + }, + isTableAction && { + name: i18n.translate('workspace.detail.collaborator.actions.delete', { + defaultMessage: 'Delete collaborator', + }), + onClick: () => { + setIsPopoverOpen(false); + if (selection && openDeleteConfirmModal) { + const modal = openDeleteConfirmModal({ + onConfirm: () => { + let newSettings = permissionSettings; + selection.forEach(({ id }) => { + newSettings = newSettings.filter((_item) => _item.id !== id); + }); + handleSubmitPermissionSettings(newSettings as WorkspacePermissionSetting[]); + modal.close(); + }, + }); + } + }, + }, + ].filter(Boolean), + }, + { + id: 1, + title: i18n.translate('workspace.detail.collaborator.actions.change.access', { + defaultMessage: 'Change access level', + }), + items: accessLevelOptions, + }, + ] as unknown) as EuiContextMenuPanelDescriptor[]; + + const button = isTableAction ? ( + setIsPopoverOpen(true)} + data-test-subj="workspace-detail-collaborator-table-actions-box" + /> + ) : ( + setIsPopoverOpen(true)} + data-test-subj="workspace-detail-collaborator-table-actions" + > + {i18n.translate('workspace.detail.collaborator.actions.', { + defaultMessage: 'Actions', + })} + + ); + return ( + setIsPopoverOpen(false)} + panelPaddingSize="none" + anchorPosition="downLeft" + ownFocus={false} + > + + + ); +}; diff --git a/src/plugins/workspace/public/components/workspace_form/workspace_detail_form.tsx b/src/plugins/workspace/public/components/workspace_form/workspace_detail_form.tsx index c6e453c6714a..1466ef3bae66 100644 --- a/src/plugins/workspace/public/components/workspace_form/workspace_detail_form.tsx +++ b/src/plugins/workspace/public/components/workspace_form/workspace_detail_form.tsx @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -import React, { useRef } from 'react'; +import React from 'react'; import { EuiSpacer, EuiForm, @@ -17,38 +17,16 @@ import { } from '@elastic/eui'; import { i18n } from '@osd/i18n'; +import { useObservable } from 'react-use'; import { WorkspaceFormProps } from './types'; -import { WorkspacePermissionSettingPanel } from './workspace_permission_setting_panel'; -import { DetailTab, usersAndPermissionsTitle } from './constants'; +import { DetailTab } from './constants'; import { WorkspaceFormErrorCallout } from './workspace_form_error_callout'; import { useWorkspaceFormContext } from './workspace_form_context'; import { WorkspaceDetailFormDetails } from './workspace_detail_form_details'; - -interface FormGroupProps { - title: React.ReactNode; - children: React.ReactNode; - describe?: string; -} - -const FormGroup = ({ title, children, describe }: FormGroupProps) => ( - <> - - - -

{title}

-
- - {describe} - -
- - - {children} - -
- - -); +import { WorkspaceCollaboratorTable } from './workspace_collaborator_table'; +import { WorkspaceCollaboratorTypesService } from '../../services/workspace_collaborator_types_service'; +import { AddCollaboratorButton } from './add_collaborator_button'; +import { useOpenSearchDashboards } from '../../../../opensearch_dashboards_react/public'; interface WorkspaceDetailedFormProps extends Omit { detailTab?: DetailTab; @@ -56,7 +34,7 @@ interface WorkspaceDetailedFormProps extends Omit { - const { detailTab, detailTitle, defaultValues, availableUseCases } = props; + const { detailTab, detailTitle, availableUseCases } = props; const { formId, formData, @@ -66,11 +44,14 @@ export const WorkspaceDetailForm = (props: WorkspaceDetailedFormProps) => { numberOfErrors, handleResetForm, handleFormSubmit, - setPermissionSettings, + handleSubmitPermissionSettings, } = useWorkspaceFormContext(); - const disabledUserOrGroupInputIdsRef = useRef( - defaultValues?.permissionSettings?.map((item) => item.id) ?? [] - ); + + const { + services: { collaboratorTypes }, + } = useOpenSearchDashboards<{ collaboratorTypes: WorkspaceCollaboratorTypesService }>(); + + const displayedCollaboratorTypes = useObservable(collaboratorTypes.getTypes$()) ?? []; return ( { - {isEditing ? ( + {detailTab === DetailTab.Collaborators ? ( + + ) : isEditing ? ( { )} - + + {i18n.translate('workspace.detail.collaborator.description', { + defaultMessage: 'Manage workspace access and permissions.', + })} + + {detailTab === DetailTab.Collaborators ? ( + + ) : ( + + )} {numberOfErrors > 0 && ( <> @@ -120,21 +116,11 @@ export const WorkspaceDetailForm = (props: WorkspaceDetailedFormProps) => { )} {detailTab === DetailTab.Collaborators && ( - - - + )} diff --git a/src/plugins/workspace/public/components/workspace_form/workspace_form_context.tsx b/src/plugins/workspace/public/components/workspace_form/workspace_form_context.tsx index e9403cfd7e15..0ec3bffebc77 100644 --- a/src/plugins/workspace/public/components/workspace_form/workspace_form_context.tsx +++ b/src/plugins/workspace/public/components/workspace_form/workspace_form_context.tsx @@ -10,6 +10,7 @@ import { WorkspaceFormProps, WorkspaceFormErrors } from './types'; import { AppMountParameters, PublicAppInfo } from '../../../../../core/public'; import { useWorkspaceForm } from './use_workspace_form'; import { WorkspaceFormDataState } from '../workspace_form'; +import { WorkspacePermissionSetting } from './types'; interface WorkspaceFormContextProps { formId: string; @@ -31,6 +32,7 @@ interface WorkspaceFormContextProps { >; setSelectedDataSourceConnections: React.Dispatch>; onAppLeave: AppMountParameters['onAppLeave']; + handleSubmitPermissionSettings: (permissionSettings: WorkspacePermissionSetting[]) => void; } const initialContextValue: WorkspaceFormContextProps = {} as WorkspaceFormContextProps; diff --git a/src/plugins/workspace/public/components/workspace_form/workspace_permission_setting_input.test.tsx b/src/plugins/workspace/public/components/workspace_form/workspace_permission_setting_input.test.tsx index 72d0415f5984..ed2168330f6e 100644 --- a/src/plugins/workspace/public/components/workspace_form/workspace_permission_setting_input.test.tsx +++ b/src/plugins/workspace/public/components/workspace_form/workspace_permission_setting_input.test.tsx @@ -76,7 +76,7 @@ describe('WorkspacePermissionSettingInput', () => { }); expect(renderResult.getByDisplayValue('foo')).toBeInTheDocument(); - expect(renderResult.getByText('Read')).toBeInTheDocument(); + expect(renderResult.getByText('Read only')).toBeInTheDocument(); }); it('should render consistent group id and permission modes', () => { const { renderResult } = setup({ @@ -86,7 +86,7 @@ describe('WorkspacePermissionSettingInput', () => { }); expect(renderResult.getByDisplayValue('bar')).toBeInTheDocument(); - expect(renderResult.getByText('Read & Write')).toBeInTheDocument(); + expect(renderResult.getByText('Read and write')).toBeInTheDocument(); }); it('should call onGroupOrUserIdChange with user id', () => { const { renderResult, onGroupOrUserIdChangeMock } = setup(); @@ -116,7 +116,7 @@ describe('WorkspacePermissionSettingInput', () => { expect(onPermissionModesChangeMock).not.toHaveBeenCalled(); fireEvent.click(renderResult.getAllByTestId('workspace-permissionModeOptions')[0]); - fireEvent.click(renderResult.getByText('Owner')); + fireEvent.click(renderResult.getByText('Admin')); expect(onPermissionModesChangeMock).toHaveBeenCalledWith(['library_write', 'write'], 0); }); @@ -133,7 +133,7 @@ describe('WorkspacePermissionSettingInput', () => { expect(onTypeChangeMock).not.toHaveBeenCalled(); fireEvent.click(renderResult.getByTestId('workspace-typeOptions')); - fireEvent.click(renderResult.getByText('User Group')); + fireEvent.click(renderResult.getByText('Group')); expect(onTypeChangeMock).toHaveBeenCalledWith('group', 0); }); }); diff --git a/src/plugins/workspace/public/components/workspace_form/workspace_permission_setting_input.tsx b/src/plugins/workspace/public/components/workspace_form/workspace_permission_setting_input.tsx index 49421c22179e..4da620841398 100644 --- a/src/plugins/workspace/public/components/workspace_form/workspace_permission_setting_input.tsx +++ b/src/plugins/workspace/public/components/workspace_form/workspace_permission_setting_input.tsx @@ -20,24 +20,10 @@ import { PERMISSION_COLLABORATOR_LABEL_ID, PERMISSION_ACCESS_LEVEL_LABEL_ID, permissionModeOptions, + typeOptions, } from './constants'; import { getPermissionModeId } from './utils'; -const typeOptions = [ - { - value: WorkspacePermissionItemType.User, - inputDisplay: i18n.translate('workspace.form.permissionSettingPanel.typeOptions.user', { - defaultMessage: 'User', - }), - }, - { - value: WorkspacePermissionItemType.Group, - inputDisplay: i18n.translate('workspace.form.permissionSettingPanel.typeOptions.group', { - defaultMessage: 'User Group', - }), - }, -]; - export interface WorkspacePermissionSettingInputProps { index: number; type: WorkspacePermissionItemType; diff --git a/src/plugins/workspace/public/components/workspace_form/workspace_permission_setting_panel.test.tsx b/src/plugins/workspace/public/components/workspace_form/workspace_permission_setting_panel.test.tsx index 2bf0903361c0..24b2ec5b4e2e 100644 --- a/src/plugins/workspace/public/components/workspace_form/workspace_permission_setting_panel.test.tsx +++ b/src/plugins/workspace/public/components/workspace_form/workspace_permission_setting_panel.test.tsx @@ -77,10 +77,10 @@ describe('WorkspacePermissionSettingInput', () => { const { renderResult } = setup(); expect(renderResult.getByDisplayValue('foo')).toBeInTheDocument(); - expect(renderResult.getByText('Read')).toBeInTheDocument(); + expect(renderResult.getByText('Read only')).toBeInTheDocument(); expect(renderResult.getByDisplayValue('bar')).toBeInTheDocument(); - expect(renderResult.getByText('Read & Write')).toBeInTheDocument(); + expect(renderResult.getByText('Read and write')).toBeInTheDocument(); }); it('should call onChange with new user permission modes', () => { @@ -88,7 +88,7 @@ describe('WorkspacePermissionSettingInput', () => { expect(onChangeMock).not.toHaveBeenCalled(); fireEvent.click(renderResult.getAllByTestId('workspace-permissionModeOptions')[0]); - fireEvent.click(renderResult.getAllByText('Read & Write')[1]); + fireEvent.click(renderResult.getAllByText('Read and write')[1]); expect(onChangeMock).toHaveBeenCalledWith([ { id: 0, @@ -110,7 +110,7 @@ describe('WorkspacePermissionSettingInput', () => { expect(onChangeMock).not.toHaveBeenCalled(); fireEvent.click(renderResult.getAllByTestId('workspace-permissionModeOptions')[1]); - fireEvent.click(renderResult.getByText('Owner')); + fireEvent.click(renderResult.getByText('Admin')); expect(onChangeMock).toHaveBeenCalledWith([ { id: 0, diff --git a/src/plugins/workspace/public/constants.ts b/src/plugins/workspace/public/constants.ts index e81ac8ddf20c..2eb7f9bbfde2 100644 --- a/src/plugins/workspace/public/constants.ts +++ b/src/plugins/workspace/public/constants.ts @@ -6,6 +6,7 @@ import { i18n } from '@osd/i18n'; import { WorkspaceCollaboratorAccessLevel } from './types'; +import { WorkspacePermissionMode } from '../common/constants'; export const WORKSPACE_ACCESS_LEVEL_NAMES: { [key in WorkspaceCollaboratorAccessLevel]: string } = { readOnly: i18n.translate('workspace.accessLevel.readOnlyName', { @@ -18,3 +19,11 @@ export const WORKSPACE_ACCESS_LEVEL_NAMES: { [key in WorkspaceCollaboratorAccess defaultMessage: 'Admin', }), }; + +export const accessLevelNameToWorkspacePermissionModesMap: { + [key in WorkspaceCollaboratorAccessLevel]: WorkspacePermissionMode[]; +} = { + readOnly: [WorkspacePermissionMode.LibraryRead, WorkspacePermissionMode.Read], + readAndWrite: [WorkspacePermissionMode.LibraryWrite, WorkspacePermissionMode.Read], + admin: [WorkspacePermissionMode.LibraryWrite, WorkspacePermissionMode.Write], +};