{title}
- {transformedValue?.map((item, index) => (
-
-
+ {permissionSettings.map((item, index) => (
+
+
{
+ if (
+ lastAdminItemDeletable ||
+ // Permission setting can be deleted if there are more than one admin setting
+ [...userPermissionSettings, ...groupPermissionSettings].filter(
+ (permission) =>
+ permission.modes && getPermissionModeId(permission.modes) === PermissionModeId.Admin
+ ).length > 1
+ ) {
+ return { userNonDeletableIndex: -1, groupNonDeletableIndex: -1 };
+ }
+ return {
+ userNonDeletableIndex: userPermissionSettings.findIndex(
+ (permission) =>
+ permission.modes && getPermissionModeId(permission.modes) === PermissionModeId.Admin
+ ),
+ groupNonDeletableIndex: groupPermissionSettings.findIndex(
+ (permission) =>
+ permission.modes && getPermissionModeId(permission.modes) === PermissionModeId.Admin
+ ),
+ };
+ }, [userPermissionSettings, groupPermissionSettings, lastAdminItemDeletable]);
+
+ const nextIdRef = useRef(generateNextPermissionSettingsId(permissionSettings));
+
const handleUserPermissionSettingsChange = useCallback(
(newSettings) => {
onChange?.([...newSettings, ...groupPermissionSettings]);
@@ -193,30 +192,18 @@ export const WorkspacePermissionSettingPanel = ({
[userPermissionSettings, onChange]
);
- const nonDeletableIndex = useMemo(() => {
- let userNonDeletableIndex = -1;
- let groupNonDeletableIndex = -1;
- const newPermissionSettings = [...userPermissionSettings, ...groupPermissionSettings];
- if (!lastAdminItemDeletable) {
- const adminPermissionSettings = newPermissionSettings.filter(
- (permission) => getPermissionModeId(permission.modes ?? []) === PermissionModeId.Admin
- );
- if (adminPermissionSettings.length === 1) {
- if (adminPermissionSettings[0].type === WorkspacePermissionItemType.User) {
- userNonDeletableIndex = userPermissionSettings.findIndex(
- (permission) => getPermissionModeId(permission.modes ?? []) === PermissionModeId.Admin
- );
- } else {
- groupNonDeletableIndex = groupPermissionSettings.findIndex(
- (permission) => getPermissionModeId(permission.modes ?? []) === PermissionModeId.Admin
- );
- }
- }
- }
- return { userNonDeletableIndex, groupNonDeletableIndex };
- }, [userPermissionSettings, groupPermissionSettings, lastAdminItemDeletable]);
+ const nextIdGenerator = useCallback(() => {
+ const nextId = nextIdRef.current;
+ nextIdRef.current++;
+ return nextId;
+ }, []);
- const { userNonDeletableIndex, groupNonDeletableIndex } = nonDeletableIndex;
+ useEffect(() => {
+ nextIdRef.current = Math.max(
+ nextIdRef.current,
+ generateNextPermissionSettingsId(permissionSettings)
+ );
+ }, [permissionSettings]);
return (
@@ -229,6 +216,7 @@ export const WorkspacePermissionSettingPanel = ({
nonDeletableIndex={userNonDeletableIndex}
permissionSettings={userPermissionSettings}
type={WorkspacePermissionItemType.User}
+ nextIdGenerator={nextIdGenerator}
/>
);
diff --git a/src/plugins/workspace/public/components/workspace_updater/workspace_updater.test.tsx b/src/plugins/workspace/public/components/workspace_updater/workspace_updater.test.tsx
index ff07076d99d1..2b56bf322388 100644
--- a/src/plugins/workspace/public/components/workspace_updater/workspace_updater.test.tsx
+++ b/src/plugins/workspace/public/components/workspace_updater/workspace_updater.test.tsx
@@ -130,7 +130,7 @@ describe('WorkspaceUpdater', () => {
});
it('update workspace successfully', async () => {
- const { getByTestId, getByText } = render();
+ const { getByTestId, getByText, getAllByText } = render();
const nameInput = getByTestId('workspaceForm-workspaceDetails-nameInputText');
fireEvent.input(nameInput, {
target: { value: 'test workspace name' },
@@ -153,7 +153,7 @@ describe('WorkspaceUpdater', () => {
fireEvent.click(getByText('Users & Permissions'));
fireEvent.click(getByTestId('workspaceForm-permissionSettingPanel-user-addNew'));
- const userIdInput = getByTestId('workspaceForm-permissionSettingPanel-0-userId');
+ const userIdInput = getAllByText('Select')[0];
fireEvent.click(userIdInput);
fireEvent.input(getByTestId('comboBoxSearchInput'), {
target: { value: 'test user id' },
diff --git a/src/plugins/workspace/public/components/workspace_updater/workspace_updater.tsx b/src/plugins/workspace/public/components/workspace_updater/workspace_updater.tsx
index 1f67f2063d9b..201175c11c8e 100644
--- a/src/plugins/workspace/public/components/workspace_updater/workspace_updater.tsx
+++ b/src/plugins/workspace/public/components/workspace_updater/workspace_updater.tsx
@@ -9,15 +9,17 @@ import { i18n } from '@osd/i18n';
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 {
- convertPermissionSettingsToPermissions,
+ WorkspaceForm,
+ WorkspaceFormSubmitData,
+ WorkspaceOperationType,
convertPermissionsToPermissionSettings,
-} from '../workspace_form/utils';
+ convertPermissionSettingsToPermissions,
+} from '../workspace_form';
function getFormDataFromWorkspace(
currentWorkspace: WorkspaceAttributeWithPermission | null | undefined
@@ -37,7 +39,6 @@ export const WorkspaceUpdater = () => {
const {
services: { application, workspaces, notifications, http, workspaceClient },
} = useOpenSearchDashboards<{ workspaceClient: WorkspaceClient }>();
-
const isPermissionEnabled = application?.capabilities.workspaces.permissionEnabled;
const currentWorkspace = useObservable(workspaces ? workspaces.currentWorkspace$ : of(null));
@@ -45,10 +46,6 @@ export const WorkspaceUpdater = () => {
getFormDataFromWorkspace(currentWorkspace)
);
- useEffect(() => {
- setCurrentWorkspaceFormData(getFormDataFromWorkspace(currentWorkspace));
- }, [currentWorkspace]);
-
const handleWorkspaceFormSubmit = useCallback(
async (data: WorkspaceFormSubmitData) => {
let result;
@@ -68,6 +65,28 @@ export const WorkspaceUpdater = () => {
attributes,
convertPermissionSettingsToPermissions(permissionSettings)
);
+ if (result?.success) {
+ notifications?.toasts.addSuccess({
+ title: i18n.translate('workspace.update.success', {
+ defaultMessage: 'Update workspace successfully',
+ }),
+ });
+ if (application && http) {
+ // Redirect page after one second, leave one second time to show update successful toast.
+ window.setTimeout(() => {
+ window.location.href = formatUrlWithWorkspaceId(
+ application.getUrlForApp(WORKSPACE_OVERVIEW_APP_ID, {
+ absolute: true,
+ }),
+ currentWorkspace.id,
+ http.basePath
+ );
+ }, 1000);
+ }
+ return;
+ } else {
+ throw new Error(result?.error ? result?.error : 'update workspace failed');
+ }
} catch (error) {
notifications?.toasts.addDanger({
title: i18n.translate('workspace.update.failed', {
@@ -77,36 +96,14 @@ export const WorkspaceUpdater = () => {
});
return;
}
- if (result?.success) {
- notifications?.toasts.addSuccess({
- title: i18n.translate('workspace.update.success', {
- defaultMessage: 'Update workspace successfully',
- }),
- });
- if (application && http) {
- // Redirect page after one second, leave one second time to show update successful toast.
- window.setTimeout(() => {
- window.location.href = formatUrlWithWorkspaceId(
- application.getUrlForApp(WORKSPACE_OVERVIEW_APP_ID, {
- absolute: true,
- }),
- currentWorkspace.id,
- http.basePath
- );
- }, 1000);
- }
- return;
- }
- notifications?.toasts.addDanger({
- title: i18n.translate('workspace.update.failed', {
- defaultMessage: 'Failed to update workspace',
- }),
- text: result?.error,
- });
},
[notifications?.toasts, currentWorkspace, http, application, workspaceClient]
);
+ useEffect(() => {
+ setCurrentWorkspaceFormData(getFormDataFromWorkspace(currentWorkspace));
+ }, [currentWorkspace]);
+
if (!currentWorkspaceFormData) {
return null;
}
@@ -130,7 +127,7 @@ export const WorkspaceUpdater = () => {
onSubmit={handleWorkspaceFormSubmit}
operationType={WorkspaceOperationType.Update}
permissionEnabled={isPermissionEnabled}
- permissionLastAdminItemDeletable
+ permissionLastAdminItemDeletable={false}
/>
)}
diff --git a/src/plugins/workspace/public/workspace_client.ts b/src/plugins/workspace/public/workspace_client.ts
index edd9613cd9c1..754064ed0c76 100644
--- a/src/plugins/workspace/public/workspace_client.ts
+++ b/src/plugins/workspace/public/workspace_client.ts
@@ -162,7 +162,7 @@ export class WorkspaceClient {
/**
* A bypass layer to get current workspace id
*/
- public getCurrentWorkspaceId(): IResponse {
+ public getCurrentWorkspaceId(): IResponse {
const currentWorkspaceId = this.workspaces.currentWorkspaceId$.getValue();
if (!currentWorkspaceId) {
return {
@@ -182,7 +182,7 @@ export class WorkspaceClient {
/**
* Do a find in the latest workspace list with current workspace id
*/
- public async getCurrentWorkspace(): Promise> {
+ public async getCurrentWorkspace(): Promise> {
const currentWorkspaceIdResp = this.getCurrentWorkspaceId();
if (currentWorkspaceIdResp.success) {
const currentWorkspaceResp = await this.get(currentWorkspaceIdResp.result);
@@ -202,10 +202,10 @@ export class WorkspaceClient {
public async create(
attributes: Omit,
permissions?: SavedObjectPermissions
- ): Promise>> {
+ ): Promise>> {
const path = this.getPath();
- const result = await this.safeFetch(path, {
+ const result = await this.safeFetch(path, {
method: 'POST',
body: JSON.stringify({
attributes,
diff --git a/src/plugins/workspace/server/integration_tests/routes.test.ts b/src/plugins/workspace/server/integration_tests/routes.test.ts
index 175319074d10..002b6653a60b 100644
--- a/src/plugins/workspace/server/integration_tests/routes.test.ts
+++ b/src/plugins/workspace/server/integration_tests/routes.test.ts
@@ -356,6 +356,32 @@ describe('workspace service api integration test', () => {
// Global workspace will be created by default after workspace list API called.
expect(listResult.body.result.total).toEqual(2);
});
+ it('should able to update workspace with partial attributes', async () => {
+ const result: any = await osdTestServer.request
+ .post(root, `/api/workspaces`)
+ .send({
+ attributes: omitId(testWorkspace),
+ })
+ .expect(200);
+
+ await osdTestServer.request
+ .put(root, `/api/workspaces/${result.body.result.id}`)
+ .send({
+ attributes: {
+ name: 'updated',
+ },
+ })
+ .expect(200);
+
+ const getResult = await osdTestServer.request.get(
+ root,
+ `/api/workspaces/${result.body.result.id}`
+ );
+
+ expect(getResult.body.success).toEqual(true);
+ expect(getResult.body.result.name).toEqual('updated');
+ expect(getResult.body.result.description).toEqual(testWorkspace.description);
+ });
});
describe('Duplicate saved objects APIs', () => {
diff --git a/src/plugins/workspace/server/routes/index.ts b/src/plugins/workspace/server/routes/index.ts
index 1693e4636017..2c002539aae6 100644
--- a/src/plugins/workspace/server/routes/index.ts
+++ b/src/plugins/workspace/server/routes/index.ts
@@ -30,14 +30,23 @@ const workspacePermissions = schema.recordOf(
schema.recordOf(principalType, schema.arrayOf(schema.string()), {})
);
-const workspaceAttributesSchema = schema.object({
+const workspaceOptionalAttributesSchema = {
description: schema.maybe(schema.string()),
- name: schema.string(),
features: schema.maybe(schema.arrayOf(schema.string())),
color: schema.maybe(schema.string()),
icon: schema.maybe(schema.string()),
defaultVISTheme: schema.maybe(schema.string()),
reserved: schema.maybe(schema.boolean()),
+};
+
+const createWorkspaceAttributesSchema = schema.object({
+ name: schema.string(),
+ ...workspaceOptionalAttributesSchema,
+});
+
+const updateWorkspaceAttributesSchema = schema.object({
+ name: schema.maybe(schema.string()),
+ ...workspaceOptionalAttributesSchema,
});
export function registerRoutes({
@@ -121,7 +130,7 @@ export function registerRoutes({
path: `${WORKSPACES_API_BASE_URL}`,
validate: {
body: schema.object({
- attributes: workspaceAttributesSchema,
+ attributes: createWorkspaceAttributesSchema,
permissions: schema.maybe(workspacePermissions),
}),
},
@@ -167,7 +176,7 @@ export function registerRoutes({
id: schema.string(),
}),
body: schema.object({
- attributes: workspaceAttributesSchema,
+ attributes: updateWorkspaceAttributesSchema,
permissions: schema.maybe(workspacePermissions),
}),
},
diff --git a/src/plugins/workspace/server/types.ts b/src/plugins/workspace/server/types.ts
index bb8073b3a857..6f903389fff8 100644
--- a/src/plugins/workspace/server/types.ts
+++ b/src/plugins/workspace/server/types.ts
@@ -97,7 +97,7 @@ export interface IWorkspaceClientImpl {
update(
requestDetail: IRequestDetail,
id: string,
- payload: Omit
+ payload: Partial>
): Promise>;
/**
* Delete a given workspace
diff --git a/src/plugins/workspace/server/workspace_client.ts b/src/plugins/workspace/server/workspace_client.ts
index b4c8f2ebd637..ac16744e67d6 100644
--- a/src/plugins/workspace/server/workspace_client.ts
+++ b/src/plugins/workspace/server/workspace_client.ts
@@ -244,7 +244,7 @@ export class WorkspaceClient implements IWorkspaceClientImpl {
public async update(
requestDetail: IRequestDetail,
id: string,
- payload: Omit
+ payload: Partial>
): Promise> {
const { permissions, ...attributes } = payload;
try {
@@ -267,12 +267,16 @@ export class WorkspaceClient implements IWorkspaceClientImpl {
throw new Error(DUPLICATE_WORKSPACE_NAME_ERROR);
}
}
- await client.create>(WORKSPACE_TYPE, attributes, {
- id,
- permissions,
- overwrite: true,
- version: workspaceInDB.version,
- });
+ await client.create>(
+ WORKSPACE_TYPE,
+ { ...workspaceInDB.attributes, ...attributes },
+ {
+ id,
+ permissions,
+ overwrite: true,
+ version: workspaceInDB.version,
+ }
+ );
return {
success: true,
result: true,