Skip to content

Commit

Permalink
Convert permission settings in client side
Browse files Browse the repository at this point in the history
Signed-off-by: Lin Wang <[email protected]>
  • Loading branch information
wanglam committed Apr 3, 2024
1 parent 227a0d8 commit b914e9a
Show file tree
Hide file tree
Showing 14 changed files with 170 additions and 68 deletions.
2 changes: 2 additions & 0 deletions src/core/types/saved_objects.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,3 +126,5 @@ export interface SavedObjectError {
statusCode: number;
metadata?: Record<string, unknown>;
}

export type SavedObjectPermissions = Permissions;
8 changes: 7 additions & 1 deletion src/core/types/workspace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
* SPDX-License-Identifier: Apache-2.0
*/

import { Permissions } from '../server/saved_objects';

export interface WorkspaceAttribute {
id: string;
name: string;
Expand All @@ -14,6 +16,10 @@ export interface WorkspaceAttribute {
defaultVISTheme?: string;
}

export interface WorkspaceObject extends WorkspaceAttribute {
export interface WorkspaceAttributeWithPermission extends WorkspaceAttribute {
permissions?: Permissions;
}

export interface WorkspaceObject extends WorkspaceAttributeWithPermission {
readonly?: boolean;
}
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ describe('WorkspaceCreator', () => {
color: '#000000',
description: 'test workspace description',
}),
expect.any(Array)
undefined
);
await waitFor(() => {
expect(notificationToastsAddSuccess).toHaveBeenCalled();
Expand All @@ -174,7 +174,7 @@ describe('WorkspaceCreator', () => {
name: 'test workspace name',
features: expect.arrayContaining(['app1', 'app2', 'app3']),
}),
expect.any(Array)
undefined
);
await waitFor(() => {
expect(notificationToastsAddSuccess).toHaveBeenCalled();
Expand All @@ -201,7 +201,14 @@ describe('WorkspaceCreator', () => {
expect.objectContaining({
name: 'test workspace name',
}),
expect.arrayContaining([expect.objectContaining({ type: 'user', userId: 'test user id' })])
{
read: {
users: ['test user id'],
},
library_read: {
users: ['test user id'],
},
}
);
await waitFor(() => {
expect(notificationToastsAddSuccess).toHaveBeenCalled();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { WorkspaceForm, WorkspaceFormSubmitData, WorkspaceOperationType } from '
import { WORKSPACE_OVERVIEW_APP_ID } from '../../../common/constants';
import { formatUrlWithWorkspaceId } from '../../../../../core/public/utils';
import { WorkspaceClient } from '../../workspace_client';
import { convertPermissionSettingsToPermissions } from '../workspace_form/utils';

export const WorkspaceCreator = () => {
const {
Expand All @@ -22,8 +23,11 @@ export const WorkspaceCreator = () => {
async (data: WorkspaceFormSubmitData) => {
let result;
try {
const { permissions, ...attributes } = data;
result = await workspaceClient.create(attributes, permissions);
const { permissionSettings, ...attributes } = data;
result = await workspaceClient.create(
attributes,
convertPermissionSettingsToPermissions(permissionSettings)
);
} catch (error) {
notifications?.toasts.addDanger({
title: i18n.translate('workspace.create.failed', {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

import type { WorkspacePermissionItemType, WorkspaceOperationType } from './constants';
import type { WorkspacePermissionMode } from '../../../common/constants';
import type { App, ApplicationStart } from '../../../../../core/public';
import type { ApplicationStart } from '../../../../../core/public';

export type WorkspacePermissionSetting =
| { type: WorkspacePermissionItemType.User; userId: string; modes: WorkspacePermissionMode[] }
Expand All @@ -18,7 +18,7 @@ export interface WorkspaceFormSubmitData {
color?: string;
icon?: string;
defaultVISTheme?: string;
permissions: WorkspacePermissionSetting[];
permissionSettings?: WorkspacePermissionSetting[];
}

export interface WorkspaceFormData extends WorkspaceFormSubmitData {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,8 @@ export const useWorkspaceForm = ({ application, defaultValues, onSubmit }: Works
const [permissionSettings, setPermissionSettings] = useState<
Array<Partial<WorkspacePermissionSetting>>
>(
defaultValues?.permissions && defaultValues.permissions.length > 0
? defaultValues.permissions
defaultValues?.permissionSettings && defaultValues.permissionSettings.length > 0
? defaultValues.permissionSettings
: []
);

Expand All @@ -58,7 +58,7 @@ export const useWorkspaceForm = ({ application, defaultValues, onSubmit }: Works
description,
features: selectedFeatureIds,
color,
permissions: permissionSettings,
permissionSettings,
});
const getFormDataRef = useRef(getFormData);
getFormDataRef.current = getFormData;
Expand Down Expand Up @@ -96,12 +96,15 @@ export const useWorkspaceForm = ({ application, defaultValues, onSubmit }: Works
}),
};
}
const permissionErrors: string[] = new Array(formData.permissions.length);
for (let i = 0; i < formData.permissions.length; i++) {
const permission = formData.permissions[i];
const permissionErrors: string[] = new Array(formData.permissionSettings.length);
for (let i = 0; i < formData.permissionSettings.length; i++) {
const permission = formData.permissionSettings[i];
if (isValidWorkspacePermissionSetting(permission)) {
if (
isUserOrGroupPermissionSettingDuplicated(formData.permissions.slice(0, i), permission)
isUserOrGroupPermissionSettingDuplicated(
formData.permissionSettings.slice(0, i),
permission
)
) {
permissionErrors[i] = i18n.translate('workspace.form.permission.invalidate.group', {
defaultMessage: 'Duplicate permission setting',
Expand Down Expand Up @@ -162,8 +165,11 @@ export const useWorkspaceForm = ({ application, defaultValues, onSubmit }: Works
formData.features = defaultValues?.features ?? [];
}

const permissions = formData.permissions.filter(isValidWorkspacePermissionSetting);
onSubmit?.({ ...formData, name: formData.name!, permissions });
onSubmit?.({
...formData,
name: formData.name!,
permissionSettings: formData.permissionSettings.filter(isValidWorkspacePermissionSetting),
});
},
[defaultFeatures, onSubmit, defaultValues?.features]
);
Expand Down
76 changes: 76 additions & 0 deletions src/plugins/workspace/public/components/workspace_form/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
*/

import { WorkspacePermissionMode, DEFAULT_CHECKED_FEATURES_IDS } from '../../../common/constants';
import type { SavedObjectPermissions } from '../../../../../core/types';

import {
WorkspaceFeature,
Expand Down Expand Up @@ -95,3 +96,78 @@ export const getPermissionModeId = (modes: WorkspacePermissionMode[]) => {
}
return PermissionModeId.Read;
};

export const convertPermissionSettingsToPermissions = (
permissionItems: WorkspacePermissionSetting[] | undefined
) => {
if (!permissionItems || permissionItems.length === 0) {
return undefined;
}
return permissionItems.reduce<SavedObjectPermissions>((previous, current) => {
current.modes.forEach((mode) => {
if (!previous[mode]) {
previous[mode] = {};
}
switch (current.type) {
case 'user':
previous[mode].users = [...(previous[mode].users || []), current.userId];
break;
case 'group':
previous[mode].groups = [...(previous[mode].groups || []), current.group];
break;
}
});
return previous;
}, {});
};

const isWorkspacePermissionMode = (test: string): test is WorkspacePermissionMode =>
test === WorkspacePermissionMode.LibraryRead ||
test === WorkspacePermissionMode.LibraryWrite ||
test === WorkspacePermissionMode.Read ||
test === WorkspacePermissionMode.Write;

export const convertPermissionsToPermissionSettings = (permissions: SavedObjectPermissions) => {
const userPermissionSettings: WorkspacePermissionSetting[] = [];
const groupPermissionSettings: WorkspacePermissionSetting[] = [];
const settingType2Modes: { [key: string]: WorkspacePermissionMode[] } = {};

Object.keys(permissions).forEach((mode) => {
if (!isWorkspacePermissionMode(mode)) {
return;
}
if (permissions[mode].users) {
permissions[mode].users?.forEach((userId) => {
const settingTypeKey = `userId-${userId}`;
const modes = settingType2Modes[settingTypeKey] ? settingType2Modes[settingTypeKey] : [];

modes.push(mode);
if (modes.length === 1) {
userPermissionSettings.push({
type: WorkspacePermissionItemType.User,
userId,
modes,
});
settingType2Modes[settingTypeKey] = modes;
}
});
permissions[mode].groups?.forEach((group) => {
const settingTypeKey = `group-${group}`;
const modes = settingType2Modes[settingTypeKey] ? settingType2Modes[settingTypeKey] : [];

modes.push(mode);
if (modes.length === 1) {
userPermissionSettings.push({
type: WorkspacePermissionItemType.Group,
group,
modes,
});
}
});
}
});

return [...userPermissionSettings, ...groupPermissionSettings].filter(
isValidWorkspacePermissionSetting
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ export const WorkspaceForm = (props: WorkspaceFormProps) => {
<WorkspacePermissionSettingPanel
errors={formErrors.permissions}
onChange={setPermissionSettings}
permissionSettings={formData.permissions}
permissionSettings={formData.permissionSettings}
lastAdminItemDeletable={!!permissionLastAdminItemDeletable}
data-test-subj={`workspaceForm-permissionSettingPanel`}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,14 @@ describe('WorkspaceUpdater', () => {
description: 'test workspace description',
features: expect.arrayContaining(['app1', 'app2', 'app3']),
}),
expect.arrayContaining([expect.objectContaining({ type: 'user', userId: 'test user id' })])
{
read: {
users: ['test user id'],
},
library_read: {
users: ['test user id'],
},
}
);
await waitFor(() => {
expect(notificationToastsAddSuccess).toHaveBeenCalled();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,27 +6,30 @@
import React, { useCallback, useEffect, useState } from 'react';
import { EuiPage, EuiPageBody, EuiPageHeader, EuiPageContent } from '@elastic/eui';
import { i18n } from '@osd/i18n';
import { WorkspaceAttribute } from 'opensearch-dashboards/public';
import { useObservable } from 'react-use';
import { of } from 'rxjs';
import { useOpenSearchDashboards } from '../../../../opensearch_dashboards_react/public';
import { WorkspaceForm, WorkspaceFormSubmitData, WorkspaceOperationType } from '../workspace_form';
import { WORKSPACE_OVERVIEW_APP_ID } from '../../../common/constants';
import { formatUrlWithWorkspaceId } from '../../../../../core/public/utils';
import { WorkspaceAttributeWithPermission } from '../../../../../core/types';
import { WorkspaceClient } from '../../workspace_client';
import { WorkspaceFormData, WorkspacePermissionSetting } from '../workspace_form/types';

interface WorkspaceWithPermission extends WorkspaceAttribute {
permissions?: WorkspacePermissionSetting[];
}
import {
convertPermissionSettingsToPermissions,
convertPermissionsToPermissionSettings,
} from '../workspace_form/utils';

function getFormDataFromWorkspace(
currentWorkspace: WorkspaceAttribute | null | undefined
): WorkspaceFormData {
const currentWorkspaceWithPermission = (currentWorkspace || {}) as WorkspaceWithPermission;
currentWorkspace: WorkspaceAttributeWithPermission | null | undefined
) {
if (!currentWorkspace) {
return null;
}
return {
...currentWorkspaceWithPermission,
permissions: currentWorkspaceWithPermission.permissions || [],
...currentWorkspace,
permissionSettings: currentWorkspace.permissions
? convertPermissionsToPermissionSettings(currentWorkspace.permissions)
: currentWorkspace.permissions,
};
}

Expand All @@ -38,7 +41,7 @@ export const WorkspaceUpdater = () => {
const isPermissionEnabled = application?.capabilities.workspaces.permissionEnabled;

const currentWorkspace = useObservable(workspaces ? workspaces.currentWorkspace$ : of(null));
const [currentWorkspaceFormData, setCurrentWorkspaceFormData] = useState<WorkspaceFormData>(
const [currentWorkspaceFormData, setCurrentWorkspaceFormData] = useState(
getFormDataFromWorkspace(currentWorkspace)
);

Expand All @@ -59,8 +62,12 @@ export const WorkspaceUpdater = () => {
}

try {
const { permissions, ...attributes } = data;
result = await workspaceClient.update(currentWorkspace.id, attributes, permissions);
const { permissionSettings, ...attributes } = data;
result = await workspaceClient.update(
currentWorkspace.id,
attributes,
convertPermissionSettingsToPermissions(permissionSettings)
);
} catch (error) {
notifications?.toasts.addDanger({
title: i18n.translate('workspace.update.failed', {
Expand Down Expand Up @@ -100,7 +107,7 @@ export const WorkspaceUpdater = () => {
[notifications?.toasts, currentWorkspace, http, application, workspaceClient]
);

if (!currentWorkspaceFormData.name) {
if (!currentWorkspaceFormData) {
return null;
}

Expand Down
Loading

0 comments on commit b914e9a

Please sign in to comment.