From 166c06dddf1e940d616de86dc1dc89e394386a85 Mon Sep 17 00:00:00 2001 From: Yulong Ruan Date: Fri, 11 Aug 2023 15:16:09 +0800 Subject: [PATCH] Refactor workspace server plugin (#82) - move workspace server to plugins - refactor ACL types - refactor core workspace service and move workspace client to workspace plugin public - rename workspaces -> workspace --------- Signed-off-by: Yulong Ruan --- src/core/public/application/types.ts | 6 +- src/core/public/core_system.ts | 10 +- src/core/public/index.ts | 22 ++--- src/core/public/utils/index.ts | 6 +- src/core/public/workspace/consts.ts | 10 -- src/core/public/workspace/index.ts | 10 +- .../workspace/workspaces_service.mock.ts | 21 +---- .../public/workspace/workspaces_service.ts | 64 +++++++------ src/core/server/index.ts | 13 ++- .../constants.ts | 0 src/core/server/saved_objects/index.ts | 4 + .../permission_control/acl.test.ts | 21 ++--- .../saved_objects/permission_control/acl.ts | 5 +- .../permission_control/client.ts | 5 +- .../saved_objects/routes/bulk_create.ts | 9 +- src/core/server/saved_objects/routes/find.ts | 7 +- .../server/saved_objects/routes/import.ts | 9 +- src/core/server/saved_objects/routes/share.ts | 2 +- src/core/server/server.ts | 13 --- src/core/server/types.ts | 1 - src/core/server/workspaces/index.ts | 20 ---- src/core/server/workspaces/ui_settings.ts | 24 ----- src/core/server/workspaces/utils.ts | 22 ----- src/core/utils/constants.ts | 9 +- src/core/utils/index.ts | 3 +- .../objects_table/components/copy_modal.tsx | 9 +- .../objects_table/saved_objects_table.tsx | 12 +-- .../saved_objects_management/public/plugin.ts | 6 +- .../server/routes/find.ts | 7 +- .../workspace/opensearch_dashboards.json | 2 +- src/plugins/workspace/public/application.tsx | 11 ++- .../public/components/utils/workspace.ts | 10 +- .../components/utils/workspace_column.tsx | 2 +- .../workspace_creator/workspace_creator.tsx | 17 ++-- .../components/workspace_list/index.tsx | 14 +-- .../public/components/workspace_overview.tsx | 10 +- .../workspace_updater/workspace_updater.tsx | 40 ++++---- .../workspace_dropdown_list.tsx | 18 ++-- src/plugins/workspace/public/index.ts | 4 +- src/plugins/workspace/public/mount.tsx | 8 +- src/plugins/workspace/public/plugin.ts | 84 ++++++----------- src/plugins/workspace/public/utils.ts | 31 ++++++ .../workspace/public/workspace_client.ts} | 75 +++++++++------ src/plugins/workspace/server/index.ts | 24 +++++ .../workspace/server/plugin.ts} | 94 +++++++------------ .../workspace/server}/routes/index.ts | 31 +++--- .../workspace/server}/saved_objects/index.ts | 0 .../server}/saved_objects/workspace.ts | 3 +- .../workspace_saved_objects_client_wrapper.ts | 54 ++++++----- .../workspace/server}/types.ts | 16 ++-- .../workspace/server/workspace_client.ts} | 19 ++-- 51 files changed, 427 insertions(+), 490 deletions(-) delete mode 100644 src/core/public/workspace/consts.ts rename src/core/server/{workspaces => saved_objects}/constants.ts (100%) delete mode 100644 src/core/server/workspaces/index.ts delete mode 100644 src/core/server/workspaces/ui_settings.ts delete mode 100644 src/core/server/workspaces/utils.ts create mode 100644 src/plugins/workspace/public/utils.ts rename src/{core/public/workspace/workspaces_client.ts => plugins/workspace/public/workspace_client.ts} (79%) create mode 100644 src/plugins/workspace/server/index.ts rename src/{core/server/workspaces/workspaces_service.ts => plugins/workspace/server/plugin.ts} (58%) rename src/{core/server/workspaces => plugins/workspace/server}/routes/index.ts (90%) rename src/{core/server/workspaces => plugins/workspace/server}/saved_objects/index.ts (100%) rename src/{core/server/workspaces => plugins/workspace/server}/saved_objects/workspace.ts (86%) rename src/{core/server/workspaces => plugins/workspace/server}/saved_objects/workspace_saved_objects_client_wrapper.ts (87%) rename src/{core/server/workspaces => plugins/workspace/server}/types.ts (84%) rename src/{core/server/workspaces/workspaces_client.ts => plugins/workspace/server/workspace_client.ts} (86%) diff --git a/src/core/public/application/types.ts b/src/core/public/application/types.ts index 792d5195c4c6..69300c949fbe 100644 --- a/src/core/public/application/types.ts +++ b/src/core/public/application/types.ts @@ -47,7 +47,7 @@ import { IUiSettingsClient } from '../ui_settings'; import { SavedObjectsStart } from '../saved_objects'; import { AppCategory } from '../../types'; import { ScopedHistory } from './scoped_history'; -import { WorkspacesStart } from '../workspace'; +import { WorkspaceStart } from '../workspace'; /** * Accessibility status of an application. @@ -345,8 +345,8 @@ export interface AppMountContext { injectedMetadata: { getInjectedVar: (name: string, defaultValue?: any) => unknown; }; - /** {@link WorkspacesService} */ - workspaces: WorkspacesStart; + /** {@link WorkspaceService} */ + workspaces: WorkspaceStart; }; } diff --git a/src/core/public/core_system.ts b/src/core/public/core_system.ts index 73536b76d76b..d4683087cdab 100644 --- a/src/core/public/core_system.ts +++ b/src/core/public/core_system.ts @@ -54,7 +54,7 @@ import { ContextService } from './context'; import { IntegrationsService } from './integrations'; import { CoreApp } from './core_app'; import type { InternalApplicationSetup, InternalApplicationStart } from './application/types'; -import { WorkspacesService } from './workspace'; +import { WorkspaceService } from './workspace'; interface Params { rootDomElement: HTMLElement; @@ -111,7 +111,7 @@ export class CoreSystem { private readonly rootDomElement: HTMLElement; private readonly coreContext: CoreContext; - private readonly workspaces: WorkspacesService; + private readonly workspaces: WorkspaceService; private fatalErrorsSetup: FatalErrorsSetup | null = null; constructor(params: Params) { @@ -140,7 +140,7 @@ export class CoreSystem { this.rendering = new RenderingService(); this.application = new ApplicationService(); this.integrations = new IntegrationsService(); - this.workspaces = new WorkspacesService(); + this.workspaces = new WorkspaceService(); this.coreContext = { coreId: Symbol('core'), env: injectedMetadata.env }; @@ -163,7 +163,7 @@ export class CoreSystem { const http = this.http.setup({ injectedMetadata, fatalErrors: this.fatalErrorsSetup }); const uiSettings = this.uiSettings.setup({ http, injectedMetadata }); const notifications = this.notifications.setup({ uiSettings }); - const workspaces = await this.workspaces.setup({ http, uiSettings }); + const workspaces = this.workspaces.setup(); const pluginDependencies = this.plugins.getOpaqueIds(); const context = this.context.setup({ @@ -225,7 +225,7 @@ export class CoreSystem { targetDomElement: notificationsTargetDomElement, }); const application = await this.application.start({ http, overlays }); - const workspaces = await this.workspaces.start(); + const workspaces = this.workspaces.start(); const chrome = await this.chrome.start({ application, docLinks, diff --git a/src/core/public/index.ts b/src/core/public/index.ts index 086c14da4163..50f92b4a2fe4 100644 --- a/src/core/public/index.ts +++ b/src/core/public/index.ts @@ -88,7 +88,7 @@ import { HandlerParameters, } from './context'; import { Branding } from '../types'; -import { WorkspacesStart, WorkspacesSetup } from './workspace'; +import { WorkspaceStart, WorkspaceSetup } from './workspace'; export { PackageInfo, EnvironmentMode } from '../server/types'; /** @interal */ @@ -240,8 +240,8 @@ export interface CoreSetup; - /** {@link WorkspacesSetup} */ - workspaces: WorkspacesSetup; + /** {@link WorkspaceSetup} */ + workspaces: WorkspaceSetup; } /** @@ -296,8 +296,8 @@ export interface CoreStart { getInjectedVar: (name: string, defaultValue?: any) => unknown; getBranding: () => Branding; }; - /** {@link WorkspacesStart} */ - workspaces: WorkspacesStart; + /** {@link WorkspaceStart} */ + workspaces: WorkspaceStart; } export { @@ -348,14 +348,8 @@ export { export { __osdBootstrap__ } from './osd_bootstrap'; -export { - WorkspacesClientContract, - WorkspacesClient, - WorkspacesStart, - WorkspacesService, - WorkspaceAttribute, - WorkspaceFindOptions, - WorkspacePermissionMode, -} from './workspace'; +export { WorkspaceStart, WorkspaceService, WorkspaceAttribute } from './workspace'; + +export { WorkspacePermissionMode, PUBLIC_WORKSPACE, MANAGEMENT_WORKSPACE } from '../utils'; export { getWorkspaceIdFromUrl, WORKSPACE_TYPE } from './utils'; diff --git a/src/core/public/utils/index.ts b/src/core/public/utils/index.ts index 4f958a60ae66..a6d76a87e313 100644 --- a/src/core/public/utils/index.ts +++ b/src/core/public/utils/index.ts @@ -32,8 +32,4 @@ export { shareWeakReplay } from './share_weak_replay'; export { Sha256 } from './crypto'; export { MountWrapper, mountReactNode } from './mount'; export { getWorkspaceIdFromUrl, WORKSPACE_TYPE } from './workspace'; -export { - WORKSPACE_PATH_PREFIX, - PUBLIC_WORKSPACE, - WORKSPACE_FEATURE_FLAG_KEY_IN_UI_SETTINGS, -} from '../../utils'; +export { WORKSPACE_PATH_PREFIX, PUBLIC_WORKSPACE } from '../../utils'; diff --git a/src/core/public/workspace/consts.ts b/src/core/public/workspace/consts.ts deleted file mode 100644 index b02fa29f1013..000000000000 --- a/src/core/public/workspace/consts.ts +++ /dev/null @@ -1,10 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -export const WORKSPACES_API_BASE_URL = '/api/workspaces'; - -export enum WORKSPACE_ERROR_REASON_MAP { - WORKSPACE_STALED = 'WORKSPACE_STALED', -} diff --git a/src/core/public/workspace/index.ts b/src/core/public/workspace/index.ts index d523ce3ae6ab..c2c12bf20715 100644 --- a/src/core/public/workspace/index.ts +++ b/src/core/public/workspace/index.ts @@ -2,11 +2,5 @@ * Copyright OpenSearch Contributors * SPDX-License-Identifier: Apache-2.0 */ -export { WorkspacesClientContract, WorkspacesClient } from './workspaces_client'; -export { WorkspacesStart, WorkspacesService, WorkspacesSetup } from './workspaces_service'; -export type { - WorkspaceAttribute, - WorkspaceFindOptions, - WorkspaceRoutePermissionItem, -} from '../../server/types'; -export { PermissionMode as WorkspacePermissionMode } from '../../utils/constants'; +export { WorkspaceStart, WorkspaceService, WorkspaceSetup } from './workspaces_service'; +export type { WorkspaceAttribute } from './workspaces_service'; diff --git a/src/core/public/workspace/workspaces_service.mock.ts b/src/core/public/workspace/workspaces_service.mock.ts index 245303cb4c93..08e2ae597713 100644 --- a/src/core/public/workspace/workspaces_service.mock.ts +++ b/src/core/public/workspace/workspaces_service.mock.ts @@ -11,24 +11,9 @@ const workspaceList$ = new BehaviorSubject([]); const currentWorkspace$ = new BehaviorSubject(null); const createWorkspacesSetupContractMock = () => ({ - client: { - currentWorkspaceId$, - workspaceList$, - currentWorkspace$, - init: jest.fn(), - stop: jest.fn(), - enterWorkspace: jest.fn(), - exitWorkspace: jest.fn(), - create: jest.fn(), - delete: jest.fn(), - list: jest.fn(), - getCurrentWorkspace: jest.fn(), - getCurrentWorkspaceId: jest.fn(), - get: jest.fn(), - update: jest.fn(), - }, - formatUrlWithWorkspaceId: jest.fn(), - setFormatUrlWithWorkspaceId: jest.fn(), + currentWorkspaceId$, + workspaceList$, + currentWorkspace$, }); const createWorkspacesStartContractMock = createWorkspacesSetupContractMock; diff --git a/src/core/public/workspace/workspaces_service.ts b/src/core/public/workspace/workspaces_service.ts index 34520a46787b..eb75bc2e81f5 100644 --- a/src/core/public/workspace/workspaces_service.ts +++ b/src/core/public/workspace/workspaces_service.ts @@ -2,50 +2,54 @@ * Copyright OpenSearch Contributors * SPDX-License-Identifier: Apache-2.0 */ -import { CoreService } from 'src/core/types'; -import { WorkspacesClient, WorkspacesClientContract } from './workspaces_client'; -import type { WorkspaceAttribute } from '../../server/types'; -import { HttpSetup } from '../http'; -import { IUiSettingsClient } from '../ui_settings'; +import { BehaviorSubject } from 'rxjs'; +import { CoreService } from '../../types'; /** * @public */ -export interface WorkspacesStart { - client: WorkspacesClientContract; - formatUrlWithWorkspaceId: (url: string, id: WorkspaceAttribute['id']) => string; - setFormatUrlWithWorkspaceId: (formatFn: WorkspacesStart['formatUrlWithWorkspaceId']) => void; +export interface WorkspaceStart { + currentWorkspaceId$: BehaviorSubject; + currentWorkspace$: BehaviorSubject; + workspaceList$: BehaviorSubject; } -export type WorkspacesSetup = WorkspacesStart; +export type WorkspaceSetup = WorkspaceStart; -export class WorkspacesService implements CoreService { - private client?: WorkspacesClientContract; - private formatUrlWithWorkspaceId(url: string, id: string) { - return url; - } - private setFormatUrlWithWorkspaceId(formatFn: WorkspacesStart['formatUrlWithWorkspaceId']) { - this.formatUrlWithWorkspaceId = formatFn; - } - public async setup({ http }: { http: HttpSetup; uiSettings: IUiSettingsClient }) { - this.client = new WorkspacesClient(http); +export interface WorkspaceAttribute { + id: string; + name: string; + description?: string; + features?: string[]; + color?: string; + icon?: string; + defaultVISTheme?: string; +} +export class WorkspaceService implements CoreService { + private currentWorkspaceId$ = new BehaviorSubject(''); + private workspaceList$ = new BehaviorSubject([]); + private currentWorkspace$ = new BehaviorSubject(null); + + public setup(): WorkspaceSetup { return { - client: this.client, - formatUrlWithWorkspaceId: (url: string, id: string) => this.formatUrlWithWorkspaceId(url, id), - setFormatUrlWithWorkspaceId: (fn: WorkspacesStart['formatUrlWithWorkspaceId']) => - this.setFormatUrlWithWorkspaceId(fn), + currentWorkspaceId$: this.currentWorkspaceId$, + currentWorkspace$: this.currentWorkspace$, + workspaceList$: this.workspaceList$, }; } - public async start(): Promise { + + public start(): WorkspaceStart { return { - client: this.client as WorkspacesClientContract, - formatUrlWithWorkspaceId: this.formatUrlWithWorkspaceId, - setFormatUrlWithWorkspaceId: (fn: WorkspacesStart['formatUrlWithWorkspaceId']) => - this.setFormatUrlWithWorkspaceId(fn), + currentWorkspaceId$: this.currentWorkspaceId$, + currentWorkspace$: this.currentWorkspace$, + workspaceList$: this.workspaceList$, }; } + public async stop() { - this.client?.stop(); + this.currentWorkspace$.unsubscribe(); + this.currentWorkspaceId$.unsubscribe(); + this.workspaceList$.unsubscribe(); } } diff --git a/src/core/server/index.ts b/src/core/server/index.ts index 0518a3a52250..53c229caccbc 100644 --- a/src/core/server/index.ts +++ b/src/core/server/index.ts @@ -322,6 +322,10 @@ export { SavedObjectsShareObjects, SavedObjectsAddToWorkspacesOptions, SavedObjectsAddToWorkspacesResponse, + WORKSPACE_TYPE, + Permissions, + ACL, + SavedObjectsPermissionControlContract, } from './saved_objects'; export { @@ -349,7 +353,12 @@ export { } from './metrics'; export { AppCategory } from '../types'; -export { DEFAULT_APP_CATEGORIES } from '../utils'; +export { + DEFAULT_APP_CATEGORIES, + WorkspacePermissionMode, + PUBLIC_WORKSPACE, + MANAGEMENT_WORKSPACE, +} from '../utils'; export { SavedObject, @@ -511,5 +520,3 @@ export const config = { appenders: appendersSchema as Type, }, }; - -export { formatWorkspaces, workspacesValidator, WORKSPACE_TYPE } from './workspaces'; diff --git a/src/core/server/workspaces/constants.ts b/src/core/server/saved_objects/constants.ts similarity index 100% rename from src/core/server/workspaces/constants.ts rename to src/core/server/saved_objects/constants.ts diff --git a/src/core/server/saved_objects/index.ts b/src/core/server/saved_objects/index.ts index 06b2b65fd184..e75dcff897f8 100644 --- a/src/core/server/saved_objects/index.ts +++ b/src/core/server/saved_objects/index.ts @@ -84,3 +84,7 @@ export { export { savedObjectsConfig, savedObjectsMigrationConfig } from './saved_objects_config'; export { SavedObjectTypeRegistry, ISavedObjectTypeRegistry } from './saved_objects_type_registry'; + +export { WORKSPACE_TYPE } from './constants'; +export { Permissions, ACL } from './permission_control/acl'; +export { SavedObjectsPermissionControlContract } from './permission_control/client'; diff --git a/src/core/server/saved_objects/permission_control/acl.test.ts b/src/core/server/saved_objects/permission_control/acl.test.ts index cec90bbec891..4498c4768c2c 100644 --- a/src/core/server/saved_objects/permission_control/acl.test.ts +++ b/src/core/server/saved_objects/permission_control/acl.test.ts @@ -3,7 +3,6 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { PermissionMode } from '../../../../core/utils/constants'; import { Principals, Permissions, ACL } from './acl'; describe('SavedObjectTypeRegistry', () => { @@ -19,13 +18,13 @@ describe('SavedObjectTypeRegistry', () => { }; acl = new ACL(permissions); expect( - acl.hasPermission([PermissionMode.Read], { + acl.hasPermission(['read'], { users: ['user1'], groups: [], }) ).toEqual(true); expect( - acl.hasPermission([PermissionMode.Read], { + acl.hasPermission(['read'], { users: ['user2'], groups: [], }) @@ -35,7 +34,7 @@ describe('SavedObjectTypeRegistry', () => { it('test add permission', () => { acl = new ACL(); const result1 = acl - .addPermission([PermissionMode.Read], { + .addPermission(['read'], { users: ['user1'], groups: [], }) @@ -44,7 +43,7 @@ describe('SavedObjectTypeRegistry', () => { acl.resetPermissions(); const result2 = acl - .addPermission([PermissionMode.Write, PermissionMode.Management], { + .addPermission(['write', 'management'], { users: ['user2'], groups: ['group1', 'group2'], }) @@ -64,11 +63,11 @@ describe('SavedObjectTypeRegistry', () => { }; acl = new ACL(permissions1); const result1 = acl - .removePermission([PermissionMode.Read], { + .removePermission(['read'], { users: ['user1'], groups: [], }) - .removePermission([PermissionMode.Write], { + .removePermission(['write'], { users: [], groups: ['group2'], }) @@ -88,7 +87,7 @@ describe('SavedObjectTypeRegistry', () => { acl = new ACL(permissions2); const result2 = acl - .removePermission([PermissionMode.Read, PermissionMode.Write], { + .removePermission(['read', 'write'], { users: ['user1'], groups: ['group1'], }) @@ -125,11 +124,7 @@ describe('SavedObjectTypeRegistry', () => { users: ['user1'], groups: ['group1'], }; - const result = ACL.genereateGetPermittedSavedObjectsQueryDSL( - [PermissionMode.Read], - principals, - 'workspace' - ); + const result = ACL.genereateGetPermittedSavedObjectsQueryDSL(['read'], principals, 'workspace'); expect(result).toEqual({ query: { bool: { diff --git a/src/core/server/saved_objects/permission_control/acl.ts b/src/core/server/saved_objects/permission_control/acl.ts index 1d34b9f6c401..cc17e8170ac8 100644 --- a/src/core/server/saved_objects/permission_control/acl.ts +++ b/src/core/server/saved_objects/permission_control/acl.ts @@ -3,7 +3,10 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { PrincipalType } from '../../../utils/constants'; +export enum PrincipalType { + Users = 'users', + Groups = 'groups', +} export interface Principals { users?: string[]; diff --git a/src/core/server/saved_objects/permission_control/client.ts b/src/core/server/saved_objects/permission_control/client.ts index e00274ce1a0c..b3dda958e0f0 100644 --- a/src/core/server/saved_objects/permission_control/client.ts +++ b/src/core/server/saved_objects/permission_control/client.ts @@ -7,9 +7,8 @@ import { OpenSearchDashboardsRequest } from '../../http'; import { ensureRawRequest } from '../../http/router'; import { SavedObjectsServiceStart } from '../saved_objects_service'; import { SavedObjectsBulkGetObject } from '../service'; -import { ACL, Principals, TransformedPermission } from './acl'; -import { PrincipalType } from '../../../utils/constants'; -import { WORKSPACE_TYPE } from '../../workspaces'; +import { ACL, Principals, TransformedPermission, PrincipalType } from './acl'; +import { WORKSPACE_TYPE } from '../constants'; export type SavedObjectsPermissionControlContract = Pick< SavedObjectsPermissionControl, diff --git a/src/core/server/saved_objects/routes/bulk_create.ts b/src/core/server/saved_objects/routes/bulk_create.ts index a43a69464308..056b1b795550 100644 --- a/src/core/server/saved_objects/routes/bulk_create.ts +++ b/src/core/server/saved_objects/routes/bulk_create.ts @@ -30,7 +30,6 @@ import { schema } from '@osd/config-schema'; import { IRouter } from '../../http'; -import { formatWorkspaces, workspacesValidator } from '../../workspaces'; export const registerBulkCreateRoute = (router: IRouter) => { router.post( @@ -39,7 +38,9 @@ export const registerBulkCreateRoute = (router: IRouter) => { validate: { query: schema.object({ overwrite: schema.boolean({ defaultValue: false }), - workspaces: workspacesValidator, + workspaces: schema.maybe( + schema.oneOf([schema.string(), schema.arrayOf(schema.string())]) + ), }), body: schema.arrayOf( schema.object({ @@ -64,7 +65,9 @@ export const registerBulkCreateRoute = (router: IRouter) => { }, router.handleLegacyErrors(async (context, req, res) => { const { overwrite } = req.query; - const workspaces = formatWorkspaces(req.query.workspaces); + const workspaces = req.query.workspaces + ? Array().concat(req.query.workspaces) + : undefined; const result = await context.core.savedObjects.client.bulkCreate(req.body, { overwrite, workspaces, diff --git a/src/core/server/saved_objects/routes/find.ts b/src/core/server/saved_objects/routes/find.ts index 2ee7005f8e4f..36fa7c2cd9f5 100644 --- a/src/core/server/saved_objects/routes/find.ts +++ b/src/core/server/saved_objects/routes/find.ts @@ -30,7 +30,6 @@ import { schema } from '@osd/config-schema'; import { IRouter } from '../../http'; -import { formatWorkspaces, workspacesValidator } from '../../workspaces'; export const registerFindRoute = (router: IRouter) => { router.get( @@ -60,7 +59,9 @@ export const registerFindRoute = (router: IRouter) => { namespaces: schema.maybe( schema.oneOf([schema.string(), schema.arrayOf(schema.string())]) ), - workspaces: workspacesValidator, + workspaces: schema.maybe( + schema.oneOf([schema.string(), schema.arrayOf(schema.string())]) + ), }), }, }, @@ -69,7 +70,7 @@ export const registerFindRoute = (router: IRouter) => { const namespaces = typeof req.query.namespaces === 'string' ? [req.query.namespaces] : req.query.namespaces; - const workspaces = formatWorkspaces(query.workspaces); + const workspaces = query.workspaces ? Array().concat(query.workspaces) : undefined; const result = await context.core.savedObjects.client.find({ perPage: query.per_page, diff --git a/src/core/server/saved_objects/routes/import.ts b/src/core/server/saved_objects/routes/import.ts index 9675d608541c..60e6cb254ee5 100644 --- a/src/core/server/saved_objects/routes/import.ts +++ b/src/core/server/saved_objects/routes/import.ts @@ -35,7 +35,6 @@ import { IRouter } from '../../http'; import { importSavedObjectsFromStream } from '../import'; import { SavedObjectConfig } from '../saved_objects_config'; import { createSavedObjectsStreamFromNdJson } from './utils'; -import { formatWorkspaces, workspacesValidator } from '../../workspaces'; interface FileStream extends Readable { hapi: { @@ -61,7 +60,9 @@ export const registerImportRoute = (router: IRouter, config: SavedObjectConfig) { overwrite: schema.boolean({ defaultValue: false }), createNewCopies: schema.boolean({ defaultValue: false }), - workspaces: workspacesValidator, + workspaces: schema.maybe( + schema.oneOf([schema.string(), schema.arrayOf(schema.string())]) + ), }, { validate: (object) => { @@ -93,7 +94,9 @@ export const registerImportRoute = (router: IRouter, config: SavedObjectConfig) }); } - const workspaces = formatWorkspaces(req.query.workspaces); + const workspaces = req.query.workspaces + ? Array().concat(req.query.workspaces) + : undefined; const result = await importSavedObjectsFromStream({ savedObjectsClient: context.core.savedObjects.client, diff --git a/src/core/server/saved_objects/routes/share.ts b/src/core/server/saved_objects/routes/share.ts index 526ad24893c6..d14bd7e72c31 100644 --- a/src/core/server/saved_objects/routes/share.ts +++ b/src/core/server/saved_objects/routes/share.ts @@ -8,7 +8,7 @@ import { IRouter } from '../../http'; import { exportSavedObjectsToStream } from '../export'; import { validateObjects } from './utils'; import { collectSavedObjects } from '../import/collect_saved_objects'; -import { WORKSPACE_TYPE } from '../../workspaces'; +import { WORKSPACE_TYPE } from '../constants'; import { PUBLIC_WORKSPACE } from '../../../utils/constants'; const SHARE_LIMIT = 10000; diff --git a/src/core/server/server.ts b/src/core/server/server.ts index afdbdc7b4846..d4c041725ac7 100644 --- a/src/core/server/server.ts +++ b/src/core/server/server.ts @@ -62,7 +62,6 @@ import { RequestHandlerContext } from '.'; import { InternalCoreSetup, InternalCoreStart, ServiceConfigDescriptor } from './internal_types'; import { CoreUsageDataService } from './core_usage_data'; import { CoreRouteHandlerContext } from './core_route_handler_context'; -import { WorkspacesService } from './workspaces'; const coreId = Symbol('core'); const rootConfigPath = ''; @@ -87,7 +86,6 @@ export class Server { private readonly coreApp: CoreApp; private readonly auditTrail: AuditTrailService; private readonly coreUsageData: CoreUsageDataService; - private readonly workspaces: WorkspacesService; #pluginsInitialized?: boolean; private coreStart?: InternalCoreStart; @@ -120,7 +118,6 @@ export class Server { this.auditTrail = new AuditTrailService(core); this.logging = new LoggingService(core); this.coreUsageData = new CoreUsageDataService(core); - this.workspaces = new WorkspacesService(core); } public async setup() { @@ -175,12 +172,6 @@ export class Server { const metricsSetup = await this.metrics.setup({ http: httpSetup }); - await this.workspaces.setup({ - http: httpSetup, - savedObject: savedObjectsSetup, - uiSettings: uiSettingsSetup, - }); - const statusSetup = await this.status.setup({ opensearch: opensearchServiceSetup, pluginDependencies: pluginTree.asNames, @@ -262,9 +253,6 @@ export class Server { opensearch: opensearchStart, savedObjects: savedObjectsStart, }); - await this.workspaces.start({ - savedObjects: savedObjectsStart, - }); this.coreStart = { capabilities: capabilitiesStart, @@ -307,7 +295,6 @@ export class Server { await this.status.stop(); await this.logging.stop(); await this.auditTrail.stop(); - await this.workspaces.stop(); } private registerCoreContext(coreSetup: InternalCoreSetup) { diff --git a/src/core/server/types.ts b/src/core/server/types.ts index f6e54c201dae..90ccef575807 100644 --- a/src/core/server/types.ts +++ b/src/core/server/types.ts @@ -35,4 +35,3 @@ export * from './ui_settings/types'; export * from './legacy/types'; export type { EnvironmentMode, PackageInfo } from '@osd/config'; export { Branding } from '../../core/types'; -export * from './workspaces/types'; diff --git a/src/core/server/workspaces/index.ts b/src/core/server/workspaces/index.ts deleted file mode 100644 index cbebde2237f7..000000000000 --- a/src/core/server/workspaces/index.ts +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ -export { - WorkspacesService, - InternalWorkspacesServiceSetup, - WorkspacesServiceStart, - WorkspacesServiceSetup, - InternalWorkspacesServiceStart, -} from './workspaces_service'; - -export { - WorkspaceAttribute, - WorkspaceFindOptions, - WorkspaceAttributeWithPermission, -} from './types'; - -export { workspacesValidator, formatWorkspaces } from './utils'; -export { WORKSPACE_TYPE } from './constants'; diff --git a/src/core/server/workspaces/ui_settings.ts b/src/core/server/workspaces/ui_settings.ts deleted file mode 100644 index da9c9feb64d8..000000000000 --- a/src/core/server/workspaces/ui_settings.ts +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -import { i18n } from '@osd/i18n'; -import { schema } from '@osd/config-schema'; - -import { UiSettingsParams } from 'opensearch-dashboards/server'; - -export const uiSettings: Record = { - 'workspace:enabled': { - name: i18n.translate('core.ui_settings.params.workspace.enableWorkspaceTitle', { - defaultMessage: 'Enable Workspace', - }), - value: false, - requiresPageReload: true, - description: i18n.translate('core.ui_settings.params.workspace.enableWorkspaceTitle', { - defaultMessage: 'Enable or disable OpenSearch Dashboards Workspace', - }), - category: ['workspace'], - schema: schema.boolean(), - }, -}; diff --git a/src/core/server/workspaces/utils.ts b/src/core/server/workspaces/utils.ts deleted file mode 100644 index 955b49a1e5e3..000000000000 --- a/src/core/server/workspaces/utils.ts +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -import { schema } from '@osd/config-schema'; - -export const workspacesValidator = schema.maybe( - schema.oneOf([schema.string(), schema.arrayOf(schema.string())]) -); - -export function formatWorkspaces(workspaces?: string | string[]): string[] | undefined { - if (Array.isArray(workspaces)) { - return workspaces; - } - - if (!workspaces) { - return undefined; - } - - return [workspaces]; -} diff --git a/src/core/utils/constants.ts b/src/core/utils/constants.ts index 5ebf5c0af141..004e9b58a91b 100644 --- a/src/core/utils/constants.ts +++ b/src/core/utils/constants.ts @@ -5,7 +5,7 @@ export const WORKSPACE_PATH_PREFIX = '/w'; -export enum PermissionMode { +export enum WorkspacePermissionMode { Read = 'read', Write = 'write', Management = 'management', @@ -13,13 +13,6 @@ export enum PermissionMode { LibraryWrite = 'library_write', } -export enum PrincipalType { - Users = 'users', - Groups = 'groups', -} - export const PUBLIC_WORKSPACE = 'public'; export const MANAGEMENT_WORKSPACE = 'management'; - -export const WORKSPACE_FEATURE_FLAG_KEY_IN_UI_SETTINGS = 'workspace:enabled'; diff --git a/src/core/utils/index.ts b/src/core/utils/index.ts index 4118a850c828..2adab8bd8926 100644 --- a/src/core/utils/index.ts +++ b/src/core/utils/index.ts @@ -39,8 +39,7 @@ export { export { DEFAULT_APP_CATEGORIES } from './default_app_categories'; export { WORKSPACE_PATH_PREFIX, - PermissionMode, + WorkspacePermissionMode, PUBLIC_WORKSPACE, - WORKSPACE_FEATURE_FLAG_KEY_IN_UI_SETTINGS, MANAGEMENT_WORKSPACE, } from './constants'; diff --git a/src/plugins/saved_objects_management/public/management_section/objects_table/components/copy_modal.tsx b/src/plugins/saved_objects_management/public/management_section/objects_table/components/copy_modal.tsx index d18c78dae0ad..b510dc450498 100644 --- a/src/plugins/saved_objects_management/public/management_section/objects_table/components/copy_modal.tsx +++ b/src/plugins/saved_objects_management/public/management_section/objects_table/components/copy_modal.tsx @@ -30,9 +30,8 @@ import { EuiIcon, EuiCallOut, } from '@elastic/eui'; -import { WorkspaceAttribute, WorkspacesStart } from 'opensearch-dashboards/public'; +import { WorkspaceAttribute, WorkspaceStart } from 'opensearch-dashboards/public'; import { i18n } from '@osd/i18n'; -import { iteratorSymbol } from 'immer/dist/internal'; import { SavedObjectWithMetadata } from '../../../types'; import { getSavedObjectLabel } from '../../../lib'; import { SAVED_OBJECT_TYPE_WORKSAPCE } from '../../../constants'; @@ -40,7 +39,7 @@ import { SAVED_OBJECT_TYPE_WORKSAPCE } from '../../../constants'; type WorkspaceOption = EuiComboBoxOptionOption; interface Props { - workspaces: WorkspacesStart; + workspaces: WorkspaceStart; onCopy: ( savedObjects: SavedObjectWithMetadata[], includeReferencesDeep: boolean, @@ -81,8 +80,8 @@ export class SavedObjectsCopyModal extends React.Component { async componentDidMount() { const { workspaces } = this.props; - const workspaceList = await workspaces.client.workspaceList$; - const currentWorkspace = await workspaces.client.currentWorkspace$; + const workspaceList = workspaces.workspaceList$; + const currentWorkspace = workspaces.currentWorkspace$; if (!!currentWorkspace?.value?.name) { const currentWorkspaceName = currentWorkspace.value.name; diff --git a/src/plugins/saved_objects_management/public/management_section/objects_table/saved_objects_table.tsx b/src/plugins/saved_objects_management/public/management_section/objects_table/saved_objects_table.tsx index b82fede2498e..517b4a069266 100644 --- a/src/plugins/saved_objects_management/public/management_section/objects_table/saved_objects_table.tsx +++ b/src/plugins/saved_objects_management/public/management_section/objects_table/saved_objects_table.tsx @@ -61,12 +61,13 @@ import { FormattedMessage } from '@osd/i18n/react'; import { SavedObjectsClientContract, SavedObjectsFindOptions, - WorkspacesStart, + WorkspaceStart, HttpStart, OverlayStart, NotificationsStart, ApplicationStart, -} from 'src/core/public'; + PUBLIC_WORKSPACE, +} from '../../../../../core/public'; import { RedirectAppLinks } from '../../../../opensearch_dashboards_react/public'; import { IndexPatternsContract } from '../../../../data/public'; import { @@ -94,7 +95,6 @@ import { import { Header, Table, Flyout, Relationships } from './components'; import { DataPublicPluginStart } from '../../../../../plugins/data/public'; import { SavedObjectsCopyModal } from './components/copy_modal'; -import { PUBLIC_WORKSPACE } from '../../../../../core/public/utils'; interface ExportAllOption { id: string; @@ -110,7 +110,7 @@ export interface SavedObjectsTableProps { savedObjectsClient: SavedObjectsClientContract; indexPatterns: IndexPatternsContract; http: HttpStart; - workspaces: WorkspacesStart; + workspaces: WorkspaceStart; search: DataPublicPluginStart['search']; overlays: OverlayStart; notifications: NotificationsStart; @@ -175,7 +175,7 @@ export class SavedObjectsTable extends Component + this.props.workspaces.currentWorkspaceId$.subscribe((workspaceId) => this.setState({ workspaceId, }) diff --git a/src/plugins/saved_objects_management/public/plugin.ts b/src/plugins/saved_objects_management/public/plugin.ts index e3263f396512..3e72ee464aeb 100644 --- a/src/plugins/saved_objects_management/public/plugin.ts +++ b/src/plugins/saved_objects_management/public/plugin.ts @@ -68,6 +68,7 @@ export interface SavedObjectsManagementPluginSetup { columns: SavedObjectsManagementColumnServiceSetup; namespaces: SavedObjectsManagementNamespaceServiceSetup; serviceRegistry: ISavedObjectsManagementServiceRegistry; + registerLibrarySubApp: () => void; } export interface SavedObjectsManagementPluginStart { @@ -174,7 +175,6 @@ export class SavedObjectsManagementPlugin const actionSetup = this.actionService.setup(); const columnSetup = this.columnService.setup(); const namespaceSetup = this.namespaceService.setup(); - const isWorkspaceEnabled = core.uiSettings.get('workspace:enabled'); if (home) { home.featureCatalogue.register({ @@ -213,9 +213,6 @@ export class SavedObjectsManagementPlugin // sets up the context mappings and registers any triggers/actions for the plugin bootstrap(uiActions); - if (isWorkspaceEnabled) { - this.registerLibrarySubApp(core); - } // depends on `getStartServices`, should not be awaited registerServices(this.serviceRegistry, core.getStartServices); @@ -225,6 +222,7 @@ export class SavedObjectsManagementPlugin columns: columnSetup, namespaces: namespaceSetup, serviceRegistry: this.serviceRegistry, + registerLibrarySubApp: () => this.registerLibrarySubApp(core), }; } diff --git a/src/plugins/saved_objects_management/server/routes/find.ts b/src/plugins/saved_objects_management/server/routes/find.ts index af1f96aae58a..61211532e96c 100644 --- a/src/plugins/saved_objects_management/server/routes/find.ts +++ b/src/plugins/saved_objects_management/server/routes/find.ts @@ -34,7 +34,6 @@ import { DataSourceAttributes } from 'src/plugins/data_source/common/data_source import { getIndexPatternTitle } from '../../../data/common/index_patterns/utils'; import { injectMetaAttributes } from '../lib'; import { ISavedObjectsManagement } from '../services'; -import { formatWorkspaces, workspacesValidator } from '../../../../core/server'; export const registerFindRoute = ( router: IRouter, @@ -65,7 +64,9 @@ export const registerFindRoute = ( fields: schema.oneOf([schema.string(), schema.arrayOf(schema.string())], { defaultValue: [], }), - workspaces: workspacesValidator, + workspaces: schema.maybe( + schema.oneOf([schema.string(), schema.arrayOf(schema.string())]) + ), }), }, }, @@ -96,7 +97,7 @@ export const registerFindRoute = ( ...req.query, fields: undefined, searchFields: [...searchFields], - workspaces: formatWorkspaces(req.query.workspaces), + workspaces: req.query.workspaces ? Array().concat(req.query.workspaces) : undefined, }); const savedObjects = await Promise.all( diff --git a/src/plugins/workspace/opensearch_dashboards.json b/src/plugins/workspace/opensearch_dashboards.json index 0efc89373a94..5ab644da4e91 100644 --- a/src/plugins/workspace/opensearch_dashboards.json +++ b/src/plugins/workspace/opensearch_dashboards.json @@ -1,7 +1,7 @@ { "id": "workspace", "version": "opensearchDashboards", - "server": false, + "server": true, "ui": true, "requiredPlugins": ["savedObjects"], "optionalPlugins": ["savedObjectsManagement"], diff --git a/src/plugins/workspace/public/application.tsx b/src/plugins/workspace/public/application.tsx index 9772390f7118..fc06e52d2e81 100644 --- a/src/plugins/workspace/public/application.tsx +++ b/src/plugins/workspace/public/application.tsx @@ -11,10 +11,13 @@ import { WorkspaceListApp } from './components/workspace_list_app'; import { WorkspaceCreatorApp } from './components/workspace_creator_app'; import { WorkspaceUpdaterApp } from './components/workspace_updater_app'; import { WorkspaceOverviewApp } from './components/workspace_overview_app'; +import { WorkspaceClient } from './workspace_client'; + +export type Services = CoreStart & { workspaceClient: WorkspaceClient }; export const renderListApp = ( { element, history, appBasePath }: AppMountParameters, - services: CoreStart + services: Services ) => { ReactDOM.render( @@ -29,7 +32,7 @@ export const renderListApp = ( }; export const renderCreatorApp = ( { element, history, appBasePath }: AppMountParameters, - services: CoreStart + services: Services ) => { ReactDOM.render( @@ -45,7 +48,7 @@ export const renderCreatorApp = ( export const renderUpdateApp = ( { element, history, appBasePath }: AppMountParameters, - services: CoreStart + services: Services ) => { ReactDOM.render( @@ -61,7 +64,7 @@ export const renderUpdateApp = ( export const renderOverviewApp = ( { element, history, appBasePath }: AppMountParameters, - services: CoreStart + services: Services ) => { ReactDOM.render( diff --git a/src/plugins/workspace/public/components/utils/workspace.ts b/src/plugins/workspace/public/components/utils/workspace.ts index fa38e29fdc61..63d88ae93e19 100644 --- a/src/plugins/workspace/public/components/utils/workspace.ts +++ b/src/plugins/workspace/public/components/utils/workspace.ts @@ -5,15 +5,17 @@ import { WORKSPACE_OVERVIEW_APP_ID } from '../../../common/constants'; import { CoreStart } from '../../../../../core/public'; +import { formatUrlWithWorkspaceId } from '../../utils'; -type Core = Pick; +type Core = Pick; -export const switchWorkspace = ({ workspaces, application }: Core, id: string) => { - const newUrl = workspaces?.formatUrlWithWorkspaceId( +export const switchWorkspace = ({ application, http }: Core, id: string) => { + const newUrl = formatUrlWithWorkspaceId( application.getUrlForApp(WORKSPACE_OVERVIEW_APP_ID, { absolute: true, }), - id + id, + http.basePath ); if (newUrl) { window.location.href = newUrl; diff --git a/src/plugins/workspace/public/components/utils/workspace_column.tsx b/src/plugins/workspace/public/components/utils/workspace_column.tsx index 9ad78a889bf6..0d1899959cfe 100644 --- a/src/plugins/workspace/public/components/utils/workspace_column.tsx +++ b/src/plugins/workspace/public/components/utils/workspace_column.tsx @@ -20,7 +20,7 @@ interface WorkspaceColumnProps { } function WorkspaceColumn({ coreSetup, workspaces, record }: WorkspaceColumnProps) { - const workspaceList = useObservable(coreSetup.workspaces.client.workspaceList$); + const workspaceList = useObservable(coreSetup.workspaces.workspaceList$); const wsLookUp = workspaceList?.reduce((map, ws) => { return map.set(ws.id, ws.name); diff --git a/src/plugins/workspace/public/components/workspace_creator/workspace_creator.tsx b/src/plugins/workspace/public/components/workspace_creator/workspace_creator.tsx index f2bc86c9f4cf..eec5a03392aa 100644 --- a/src/plugins/workspace/public/components/workspace_creator/workspace_creator.tsx +++ b/src/plugins/workspace/public/components/workspace_creator/workspace_creator.tsx @@ -11,17 +11,19 @@ import { useOpenSearchDashboards } from '../../../../../plugins/opensearch_dashb import { WorkspaceForm, WorkspaceFormData } from './workspace_form'; import { WORKSPACE_OVERVIEW_APP_ID, WORKSPACE_OP_TYPE_CREATE } from '../../../common/constants'; +import { formatUrlWithWorkspaceId } from '../../utils'; +import { WorkspaceClient } from '../../workspace_client'; export const WorkspaceCreator = () => { const { - services: { application, workspaces, notifications }, - } = useOpenSearchDashboards(); + services: { application, notifications, http, workspaceClient }, + } = useOpenSearchDashboards<{ workspaceClient: WorkspaceClient }>(); const handleWorkspaceFormSubmit = useCallback( async (data: WorkspaceFormData) => { let result; try { - result = await workspaces?.client.create(data); + result = await workspaceClient.create(data); } catch (error) { notifications?.toasts.addDanger({ title: i18n.translate('workspace.create.failed', { @@ -37,12 +39,13 @@ export const WorkspaceCreator = () => { defaultMessage: 'Create workspace successfully', }), }); - if (application && workspaces) { - window.location.href = workspaces.formatUrlWithWorkspaceId( + if (application && http) { + window.location.href = formatUrlWithWorkspaceId( application.getUrlForApp(WORKSPACE_OVERVIEW_APP_ID, { absolute: true, }), - result.result.id + result.result.id, + http.basePath ); } return; @@ -54,7 +57,7 @@ export const WorkspaceCreator = () => { text: result?.error, }); }, - [notifications?.toasts, workspaces, application] + [notifications?.toasts, http, application, workspaceClient] ); return ( diff --git a/src/plugins/workspace/public/components/workspace_list/index.tsx b/src/plugins/workspace/public/components/workspace_list/index.tsx index 4568836b87af..5599ae6bcf77 100644 --- a/src/plugins/workspace/public/components/workspace_list/index.tsx +++ b/src/plugins/workspace/public/components/workspace_list/index.tsx @@ -15,8 +15,8 @@ import { CriteriaWithPagination, } from '@elastic/eui'; import useObservable from 'react-use/lib/useObservable'; -import { useMemo } from 'react'; -import { useCallback } from 'react'; +import { useMemo, useCallback } from 'react'; +import { of } from 'rxjs'; import { WorkspaceAttribute } from '../../../../../core/public'; import { useOpenSearchDashboards } from '../../../../../plugins/opensearch_dashboards_react/public'; @@ -24,7 +24,7 @@ import { switchWorkspace } from '../utils/workspace'; export const WorkspaceList = () => { const { - services: { workspaces, application }, + services: { workspaces, application, http }, } = useOpenSearchDashboards(); const [pageIndex, setPageIndex] = useState(0); @@ -32,7 +32,7 @@ export const WorkspaceList = () => { const [sortField, setSortField] = useState<'name' | 'id'>('name'); const [sortDirection, setSortDirection] = useState('asc'); - const workspaceList = useObservable(workspaces!.client.workspaceList$, []); + const workspaceList = useObservable(workspaces?.workspaceList$ ?? of([]), []); const pageOfItems = useMemo(() => { return workspaceList @@ -45,11 +45,11 @@ export const WorkspaceList = () => { const handleSwitchWorkspace = useCallback( (id: string) => { - if (workspaces && application) { - switchWorkspace({ workspaces, application }, id); + if (application && http) { + switchWorkspace({ application, http }, id); } }, - [workspaces, application] + [application, http] ); const columns = [ diff --git a/src/plugins/workspace/public/components/workspace_overview.tsx b/src/plugins/workspace/public/components/workspace_overview.tsx index 55de87d20b66..5a7f9d4117c5 100644 --- a/src/plugins/workspace/public/components/workspace_overview.tsx +++ b/src/plugins/workspace/public/components/workspace_overview.tsx @@ -3,8 +3,8 @@ * SPDX-License-Identifier: Apache-2.0 */ -import React, { useState } from 'react'; -import { EuiPageHeader, EuiButton, EuiPanel, EuiSpacer, EuiTitle } from '@elastic/eui'; +import React from 'react'; +import { EuiPageHeader, EuiPanel, EuiSpacer, EuiTitle } from '@elastic/eui'; import { useObservable } from 'react-use'; import { of } from 'rxjs'; import { ApplicationStart } from '../../../../core/public'; @@ -12,12 +12,10 @@ import { useOpenSearchDashboards } from '../../../opensearch_dashboards_react/pu export const WorkspaceOverview = () => { const { - services: { workspaces, application, notifications }, + services: { workspaces }, } = useOpenSearchDashboards<{ application: ApplicationStart }>(); - const currentWorkspace = useObservable( - workspaces ? workspaces.client.currentWorkspace$ : of(null) - ); + const currentWorkspace = useObservable(workspaces ? workspaces.currentWorkspace$ : of(null)); return ( <> 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 a4e519b045e0..c474b4c3a2df 100644 --- a/src/plugins/workspace/public/components/workspace_updater/workspace_updater.tsx +++ b/src/plugins/workspace/public/components/workspace_updater/workspace_updater.tsx @@ -19,17 +19,16 @@ import { WorkspaceAttribute } from 'opensearch-dashboards/public'; import { useOpenSearchDashboards } from '../../../../opensearch_dashboards_react/public'; import { WorkspaceForm, WorkspaceFormData } from '../workspace_creator/workspace_form'; import { WORKSPACE_OVERVIEW_APP_ID, WORKSPACE_OP_TYPE_UPDATE } from '../../../common/constants'; -import { ApplicationStart } from '../../../../../core/public'; import { DeleteWorkspaceModal } from '../delete_workspace_modal'; +import { formatUrlWithWorkspaceId } from '../../utils'; +import { WorkspaceClient } from '../../workspace_client'; export const WorkspaceUpdater = () => { const { - services: { application, workspaces, notifications, http }, - } = useOpenSearchDashboards<{ application: ApplicationStart }>(); + services: { application, workspaces, notifications, http, workspaceClient }, + } = useOpenSearchDashboards<{ workspaceClient: WorkspaceClient }>(); - const currentWorkspace = useObservable( - workspaces ? workspaces.client.currentWorkspace$ : of(null) - ); + const currentWorkspace = useObservable(workspaces ? workspaces.currentWorkspace$ : of(null)); const excludedAttribute = 'id'; const { [excludedAttribute]: removedProperty, ...otherAttributes } = @@ -57,7 +56,7 @@ export const WorkspaceUpdater = () => { return; } try { - result = await workspaces?.client.update(currentWorkspace?.id, data); + result = await workspaceClient.update(currentWorkspace?.id, data); } catch (error) { notifications?.toasts.addDanger({ title: i18n.translate('workspace.update.failed', { @@ -73,13 +72,16 @@ export const WorkspaceUpdater = () => { defaultMessage: 'Update workspace successfully', }), }); - window.location.href = - workspaces?.formatUrlWithWorkspaceId( - application.getUrlForApp(WORKSPACE_OVERVIEW_APP_ID, { - absolute: true, - }), - currentWorkspace.id - ) || ''; + if (application && http) { + window.location.href = + formatUrlWithWorkspaceId( + application.getUrlForApp(WORKSPACE_OVERVIEW_APP_ID, { + absolute: true, + }), + currentWorkspace.id, + http.basePath + ) || ''; + } return; } notifications?.toasts.addDanger({ @@ -89,7 +91,7 @@ export const WorkspaceUpdater = () => { text: result?.error, }); }, - [notifications?.toasts, workspaces, currentWorkspace, application] + [notifications?.toasts, currentWorkspace, application, http, workspaceClient] ); if (!currentWorkspaceFormData.name) { @@ -99,7 +101,7 @@ export const WorkspaceUpdater = () => { if (currentWorkspace?.id) { let result; try { - result = await workspaces?.client.delete(currentWorkspace?.id); + result = await workspaceClient.delete(currentWorkspace?.id); } catch (error) { notifications?.toasts.addDanger({ title: i18n.translate('workspace.delete.failed', { @@ -125,7 +127,7 @@ export const WorkspaceUpdater = () => { } } setDeleteWorkspaceModalVisible(false); - if (http) { + if (http && application) { const homeUrl = application.getUrlForApp('home', { path: '/', absolute: false, @@ -140,7 +142,7 @@ export const WorkspaceUpdater = () => { const exitWorkspace = async () => { let result; try { - result = await workspaces?.client.exitWorkspace(); + result = await workspaceClient.exitWorkspace(); } catch (error) { notifications?.toasts.addDanger({ title: i18n.translate('workspace.exit.failed', { @@ -159,7 +161,7 @@ export const WorkspaceUpdater = () => { }); return; } - if (http) { + if (http && application) { const homeUrl = application.getUrlForApp('home', { path: '/', absolute: false, diff --git a/src/plugins/workspace/public/containers/workspace_dropdown_list/workspace_dropdown_list.tsx b/src/plugins/workspace/public/containers/workspace_dropdown_list/workspace_dropdown_list.tsx index 60bbed924d42..9bb4eec842c3 100644 --- a/src/plugins/workspace/public/containers/workspace_dropdown_list/workspace_dropdown_list.tsx +++ b/src/plugins/workspace/public/containers/workspace_dropdown_list/workspace_dropdown_list.tsx @@ -7,15 +7,21 @@ import React, { useState, useCallback, useMemo, useEffect } from 'react'; import { EuiButton, EuiComboBox, EuiComboBoxOptionOption } from '@elastic/eui'; import useObservable from 'react-use/lib/useObservable'; -import { ApplicationStart, WorkspaceAttribute, WorkspacesStart } from '../../../../../core/public'; +import { + ApplicationStart, + HttpSetup, + WorkspaceAttribute, + WorkspaceStart, +} from '../../../../../core/public'; import { WORKSPACE_CREATE_APP_ID } from '../../../common/constants'; import { switchWorkspace } from '../../components/utils/workspace'; type WorkspaceOption = EuiComboBoxOptionOption; interface WorkspaceDropdownListProps { - workspaces: WorkspacesStart; + workspaces: WorkspaceStart; application: ApplicationStart; + http: HttpSetup; } function workspaceToOption(workspace: WorkspaceAttribute): WorkspaceOption { @@ -28,8 +34,8 @@ export function getErrorMessage(err: any) { } export function WorkspaceDropdownList(props: WorkspaceDropdownListProps) { - const workspaceList = useObservable(props.workspaces.client.workspaceList$, []); - const currentWorkspace = useObservable(props.workspaces.client.currentWorkspace$, null); + const workspaceList = useObservable(props.workspaces.workspaceList$, []); + const currentWorkspace = useObservable(props.workspaces.currentWorkspace$, null); const [loading, setLoading] = useState(false); const [workspaceOptions, setWorkspaceOptions] = useState([] as WorkspaceOption[]); @@ -57,10 +63,10 @@ export function WorkspaceDropdownList(props: WorkspaceDropdownListProps) { /** switch the workspace */ setLoading(true); const id = workspaceOption[0].key!; - switchWorkspace({ workspaces: props.workspaces, application: props.application }, id); + switchWorkspace({ http: props.http, application: props.application }, id); setLoading(false); }, - [props.application, props.workspaces] + [props.application, props.http] ); const onCreateWorkspaceClick = () => { diff --git a/src/plugins/workspace/public/index.ts b/src/plugins/workspace/public/index.ts index 9f5c720fc9d5..99161a7edbd7 100644 --- a/src/plugins/workspace/public/index.ts +++ b/src/plugins/workspace/public/index.ts @@ -3,8 +3,8 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { WorkspacesPlugin } from './plugin'; +import { WorkspacePlugin } from './plugin'; export function plugin() { - return new WorkspacesPlugin(); + return new WorkspacePlugin(); } diff --git a/src/plugins/workspace/public/mount.tsx b/src/plugins/workspace/public/mount.tsx index dc2ff1de8c1e..7de85b89ea07 100644 --- a/src/plugins/workspace/public/mount.tsx +++ b/src/plugins/workspace/public/mount.tsx @@ -5,23 +5,25 @@ import React from 'react'; import ReactDOM from 'react-dom'; -import { ApplicationStart, ChromeStart, WorkspacesStart } from '../../../core/public'; +import { ApplicationStart, ChromeStart, HttpSetup, WorkspaceStart } from '../../../core/public'; import { WorkspaceDropdownList } from './containers/workspace_dropdown_list'; export const mountDropdownList = ({ application, workspaces, chrome, + http, }: { application: ApplicationStart; - workspaces: WorkspacesStart; + workspaces: WorkspaceStart; chrome: ChromeStart; + http: HttpSetup; }) => { chrome.navControls.registerLeft({ order: 0, mount: (element) => { ReactDOM.render( - , + , element ); return () => { diff --git a/src/plugins/workspace/public/plugin.ts b/src/plugins/workspace/public/plugin.ts index 8776b7bef8c8..ebc204a351a3 100644 --- a/src/plugins/workspace/public/plugin.ts +++ b/src/plugins/workspace/public/plugin.ts @@ -16,8 +16,8 @@ import { CoreStart, Plugin, WorkspaceAttribute, - WorkspacesStart, DEFAULT_APP_CATEGORIES, + HttpSetup, } from '../../../core/public'; import { WORKSPACE_LIST_APP_ID, @@ -29,59 +29,32 @@ import { import { mountDropdownList } from './mount'; import { SavedObjectsManagementPluginSetup } from '../../saved_objects_management/public'; import { getWorkspaceColumn } from './components/utils/workspace_column'; -import { - getWorkspaceIdFromUrl, - PUBLIC_WORKSPACE, - WORKSPACE_PATH_PREFIX, -} from '../../../core/public/utils'; -import { WORKSPACE_FEATURE_FLAG_KEY_IN_UI_SETTINGS } from '../../../core/public/utils'; +import { getWorkspaceIdFromUrl } from '../../../core/public/utils'; +import { formatUrlWithWorkspaceId } from './utils'; +import { WorkspaceClient } from './workspace_client'; +import { Services } from './application'; -interface WorkspacesPluginSetupDeps { +interface WorkspacePluginSetupDeps { savedObjectsManagement?: SavedObjectsManagementPluginSetup; } -export class WorkspacesPlugin implements Plugin<{}, {}, WorkspacesPluginSetupDeps> { - private coreSetup?: CoreSetup; +export class WorkspacePlugin implements Plugin<{}, {}, WorkspacePluginSetupDeps> { private coreStart?: CoreStart; private currentWorkspaceSubscription?: Subscription; private getWorkspaceIdFromURL(): string | null { return getWorkspaceIdFromUrl(window.location.href); } - private getPatchedUrl = (url: string, workspaceId: string) => { - const newUrl = new URL(url, window.location.href); - /** - * Patch workspace id into path - */ - newUrl.pathname = this.coreSetup?.http.basePath.remove(newUrl.pathname) || ''; - if (workspaceId) { - newUrl.pathname = `${WORKSPACE_PATH_PREFIX}/${workspaceId}${newUrl.pathname}`; - } else { - newUrl.pathname = newUrl.pathname.replace(/^\/w\/([^\/]*)/, ''); - } - - newUrl.pathname = - this.coreSetup?.http.basePath.prepend(newUrl.pathname, { - withoutWorkspace: true, - }) || ''; + public async setup(core: CoreSetup, { savedObjectsManagement }: WorkspacePluginSetupDeps) { + const workspaceClient = new WorkspaceClient(core.http, core.workspaces); + workspaceClient.init(); - return newUrl.toString(); - }; - public async setup(core: CoreSetup, { savedObjectsManagement }: WorkspacesPluginSetupDeps) { - // If workspace feature is disabled, it will not load the workspace plugin - if (core.uiSettings.get(WORKSPACE_FEATURE_FLAG_KEY_IN_UI_SETTINGS) === false) { - return {}; - } - - this.coreSetup = core; - core.workspaces.client.init(); - core.workspaces.setFormatUrlWithWorkspaceId((url, id) => this.getPatchedUrl(url, id)); /** * Retrieve workspace id from url */ const workspaceId = this.getWorkspaceIdFromURL(); if (workspaceId) { - const result = await core.workspaces.client.enterWorkspace(workspaceId); + const result = await workspaceClient.enterWorkspace(workspaceId); if (!result.success) { core.fatalErrors.add( result.error || @@ -96,11 +69,15 @@ export class WorkspacesPlugin implements Plugin<{}, {}, WorkspacesPluginSetupDep */ savedObjectsManagement?.columns.register(getWorkspaceColumn(core)); - type WorkspaceAppType = (params: AppMountParameters, services: CoreStart) => () => void; + // register apps for library object management + savedObjectsManagement?.registerLibrarySubApp(); + + type WorkspaceAppType = (params: AppMountParameters, services: Services) => () => void; const mountWorkspaceApp = async (params: AppMountParameters, renderApp: WorkspaceAppType) => { const [coreStart] = await core.getStartServices(); const services = { ...coreStart, + workspaceClient, }; return renderApp(params, services); @@ -168,16 +145,17 @@ export class WorkspacesPlugin implements Plugin<{}, {}, WorkspacesPluginSetupDep private workspaceToChromeNavLink( workspace: WorkspaceAttribute, - workspacesStart: WorkspacesStart, + http: HttpSetup, application: ApplicationStart, index: number ): ChromeNavLink { const id = WORKSPACE_OVERVIEW_APP_ID + '/' + workspace.id; - const url = workspacesStart?.formatUrlWithWorkspaceId( + const url = formatUrlWithWorkspaceId( application.getUrlForApp(WORKSPACE_OVERVIEW_APP_ID, { absolute: true, }), - workspace.id + workspace.id, + http.basePath ); return { id, @@ -197,13 +175,11 @@ export class WorkspacesPlugin implements Plugin<{}, {}, WorkspacesPluginSetupDep private async _changeSavedObjectCurrentWorkspace() { if (this.coreStart) { - return this.coreStart.workspaces.client.currentWorkspaceId$.subscribe( - (currentWorkspaceId) => { - if (currentWorkspaceId) { - this.coreStart?.savedObjects.client.setCurrentWorkspace(currentWorkspaceId); - } + return this.coreStart.workspaces.currentWorkspaceId$.subscribe((currentWorkspaceId) => { + if (currentWorkspaceId) { + this.coreStart?.savedObjects.client.setCurrentWorkspace(currentWorkspaceId); } - ); + }); } } @@ -219,8 +195,8 @@ export class WorkspacesPlugin implements Plugin<{}, {}, WorkspacesPluginSetupDep private filterNavLinks(core: CoreStart) { const navLinksService = core.chrome.navLinks; const chromeNavLinks$ = navLinksService.getNavLinks$(); - const workspaceList$ = core.workspaces.client.workspaceList$; - const currentWorkspace$ = core.workspaces.client.currentWorkspace$; + const workspaceList$ = core.workspaces.workspaceList$; + const currentWorkspace$ = core.workspaces.currentWorkspace$; combineLatest([ workspaceList$, chromeNavLinks$.pipe(map(this.changeCategoryNameByWorkspaceFeatureFlag)), @@ -235,7 +211,7 @@ export class WorkspacesPlugin implements Plugin<{}, {}, WorkspacesPluginSetupDep workspaceList .filter((workspace, index) => index < 5) .map((workspace, index) => - this.workspaceToChromeNavLink(workspace, core.workspaces, core.application, index) + this.workspaceToChromeNavLink(workspace, core.http, core.application, index) ) .forEach((workspaceNavLink) => filteredNavLinks.set(workspaceNavLink.id, workspaceNavLink) @@ -268,17 +244,13 @@ export class WorkspacesPlugin implements Plugin<{}, {}, WorkspacesPluginSetupDep } public start(core: CoreStart) { - // If workspace feature is disabled, it will not load the workspace plugin - if (core.uiSettings.get(WORKSPACE_FEATURE_FLAG_KEY_IN_UI_SETTINGS) === false) { - return {}; - } - this.coreStart = core; mountDropdownList({ application: core.application, workspaces: core.workspaces, chrome: core.chrome, + http: core.http, }); this.currentWorkspaceSubscription = this._changeSavedObjectCurrentWorkspace(); if (core) { diff --git a/src/plugins/workspace/public/utils.ts b/src/plugins/workspace/public/utils.ts new file mode 100644 index 000000000000..71d3e3c1caf2 --- /dev/null +++ b/src/plugins/workspace/public/utils.ts @@ -0,0 +1,31 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { IBasePath } from '../../../core/public'; +import { WORKSPACE_PATH_PREFIX } from '../../../core/public/utils'; + +export const formatUrlWithWorkspaceId = ( + url: string, + workspaceId: string, + basePath?: IBasePath +) => { + const newUrl = new URL(url, window.location.href); + /** + * Patch workspace id into path + */ + newUrl.pathname = basePath?.remove(newUrl.pathname) || ''; + if (workspaceId) { + newUrl.pathname = `${WORKSPACE_PATH_PREFIX}/${workspaceId}${newUrl.pathname}`; + } else { + newUrl.pathname = newUrl.pathname.replace(/^\/w\/([^\/]*)/, ''); + } + + newUrl.pathname = + basePath?.prepend(newUrl.pathname, { + withoutWorkspace: true, + }) || ''; + + return newUrl.toString(); +}; diff --git a/src/core/public/workspace/workspaces_client.ts b/src/plugins/workspace/public/workspace_client.ts similarity index 79% rename from src/core/public/workspace/workspaces_client.ts rename to src/plugins/workspace/public/workspace_client.ts index 2f8c204bf418..9c2fed440d1f 100644 --- a/src/core/public/workspace/workspaces_client.ts +++ b/src/plugins/workspace/public/workspace_client.ts @@ -2,19 +2,23 @@ * Copyright OpenSearch Contributors * SPDX-License-Identifier: Apache-2.0 */ -import type { PublicContract } from '@osd/utility-types'; -import { BehaviorSubject, combineLatest } from 'rxjs'; +import { combineLatest } from 'rxjs'; import { isEqual } from 'lodash'; -import { HttpFetchError, HttpFetchOptions, HttpSetup } from '../http'; -import { WorkspaceAttribute, WorkspaceFindOptions, WorkspaceRoutePermissionItem } from '.'; -import { WORKSPACES_API_BASE_URL, WORKSPACE_ERROR_REASON_MAP } from './consts'; -/** - * WorkspacesClientContract as implemented by the {@link WorkspacesClient} - * - * @public - */ -export type WorkspacesClientContract = PublicContract; +import { + HttpFetchError, + HttpFetchOptions, + HttpSetup, + WorkspaceAttribute, + WorkspaceStart, +} from '../../../core/public'; +import { WorkspacePermissionMode } from '../../../core/public'; + +const WORKSPACES_API_BASE_URL = '/api/workspaces'; + +enum WORKSPACE_ERROR_REASON_MAP { + WORKSPACE_STALED = 'WORKSPACE_STALED', +} const join = (...uriComponents: Array) => uriComponents @@ -32,21 +36,38 @@ type IResponse = error?: string; }; +type WorkspaceRoutePermissionItem = { + modes: Array< + | WorkspacePermissionMode.LibraryRead + | WorkspacePermissionMode.LibraryWrite + | WorkspacePermissionMode.Management + >; +} & ({ type: 'user'; userId: string } | { type: 'group'; group: string }); + +interface WorkspaceFindOptions { + page?: number; + perPage?: number; + search?: string; + searchFields?: string[]; + sortField?: string; + sortOrder?: string; +} + /** * Workspaces is OpenSearchDashboards's visualize mechanism allowing admins to * organize related features * * @public */ -export class WorkspacesClient { +export class WorkspaceClient { private http: HttpSetup; - public currentWorkspaceId$ = new BehaviorSubject(''); - public workspaceList$ = new BehaviorSubject([]); - public currentWorkspace$ = new BehaviorSubject(null); - constructor(http: HttpSetup) { + private workspaces: WorkspaceStart; + + constructor(http: HttpSetup, workspaces: WorkspaceStart) { this.http = http; + this.workspaces = workspaces; - combineLatest([this.workspaceList$, this.currentWorkspaceId$]).subscribe( + combineLatest([workspaces.workspaceList$, workspaces.currentWorkspaceId$]).subscribe( ([workspaceList, currentWorkspaceId]) => { if (workspaceList.length) { const currentWorkspace = this.findWorkspace([workspaceList, currentWorkspaceId]); @@ -54,18 +75,18 @@ export class WorkspacesClient { /** * Do a simple idempotent verification here */ - if (!isEqual(currentWorkspace, this.currentWorkspace$.getValue())) { - this.currentWorkspace$.next(currentWorkspace); + if (!isEqual(currentWorkspace, workspaces.currentWorkspace$.getValue())) { + workspaces.currentWorkspace$.next(currentWorkspace); } if (currentWorkspaceId && !currentWorkspace?.id) { /** * Current workspace is staled */ - this.currentWorkspaceId$.error({ + workspaces.currentWorkspaceId$.error({ reason: WORKSPACE_ERROR_REASON_MAP.WORKSPACE_STALED, }); - this.currentWorkspace$.error({ + workspaces.currentWorkspace$.error({ reason: WORKSPACE_ERROR_REASON_MAP.WORKSPACE_STALED, }); } @@ -137,14 +158,14 @@ export class WorkspacesClient { }); if (result?.success) { - this.workspaceList$.next(result.result.workspaces); + this.workspaces.workspaceList$.next(result.result.workspaces); } } public async enterWorkspace(id: string): Promise> { const workspaceResp = await this.get(id); if (workspaceResp.success) { - this.currentWorkspaceId$.next(id); + this.workspaces.currentWorkspaceId$.next(id); return { success: true, result: null, @@ -155,7 +176,7 @@ export class WorkspacesClient { } public async exitWorkspace(): Promise> { - this.currentWorkspaceId$.next(''); + this.workspaces.currentWorkspaceId$.next(''); return { success: true, result: null, @@ -163,7 +184,7 @@ export class WorkspacesClient { } public async getCurrentWorkspaceId(): Promise> { - const currentWorkspaceId = this.currentWorkspaceId$.getValue(); + const currentWorkspaceId = this.workspaces.currentWorkspaceId$.getValue(); if (!currentWorkspaceId) { return { success: false, @@ -305,7 +326,7 @@ export class WorkspacesClient { } public stop() { - this.workspaceList$.unsubscribe(); - this.currentWorkspaceId$.unsubscribe(); + this.workspaces.workspaceList$.unsubscribe(); + this.workspaces.currentWorkspaceId$.unsubscribe(); } } diff --git a/src/plugins/workspace/server/index.ts b/src/plugins/workspace/server/index.ts new file mode 100644 index 000000000000..4e11dc50dab9 --- /dev/null +++ b/src/plugins/workspace/server/index.ts @@ -0,0 +1,24 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { schema } from '@osd/config-schema'; + +import { PluginConfigDescriptor, PluginInitializerContext } from '../../../core/server'; +import { WorkspacePlugin } from './plugin'; + +// This exports static code and TypeScript types, +// as well as, OpenSearch Dashboards Platform `plugin()` initializer. + +export function plugin(initializerContext: PluginInitializerContext) { + return new WorkspacePlugin(initializerContext); +} + +export { MlCommonsPluginSetup, MlCommonsPluginStart } from './types'; + +export const config: PluginConfigDescriptor = { + schema: schema.object({ + enabled: schema.boolean({ defaultValue: false }), + }), +}; diff --git a/src/core/server/workspaces/workspaces_service.ts b/src/plugins/workspace/server/plugin.ts similarity index 58% rename from src/core/server/workspaces/workspaces_service.ts rename to src/plugins/workspace/server/plugin.ts index 2c322e38edb6..1189af540d88 100644 --- a/src/core/server/workspaces/workspaces_service.ts +++ b/src/plugins/workspace/server/plugin.ts @@ -2,58 +2,32 @@ * Copyright OpenSearch Contributors * SPDX-License-Identifier: Apache-2.0 */ -import { URL } from 'node:url'; import { i18n } from '@osd/i18n'; -import { CoreService } from '../../types'; -import { CoreContext } from '../core_context'; -import { InternalHttpServiceSetup } from '../http'; -import { Logger } from '../logging'; -import { registerRoutes } from './routes'; + import { + PluginInitializerContext, + CoreSetup, + CoreStart, + Plugin, + Logger, ISavedObjectsRepository, - InternalSavedObjectsServiceSetup, - SavedObjectsServiceStart, -} from '../saved_objects'; + WORKSPACE_TYPE, + ACL, + PUBLIC_WORKSPACE, + MANAGEMENT_WORKSPACE, + Permissions, + WorkspacePermissionMode, +} from '../../../core/server'; import { IWorkspaceDBImpl, WorkspaceAttribute } from './types'; -import { WorkspacesClientWithSavedObject } from './workspaces_client'; +import { WorkspaceClientWithSavedObject } from './workspace_client'; import { WorkspaceSavedObjectsClientWrapper } from './saved_objects'; -import { InternalUiSettingsServiceSetup } from '../ui_settings'; -import { uiSettings } from './ui_settings'; -import { WORKSPACE_TYPE } from './constants'; -import { MANAGEMENT_WORKSPACE, PUBLIC_WORKSPACE, PermissionMode } from '../../utils'; -import { ACL, Permissions } from '../saved_objects/permission_control/acl'; - -export interface WorkspacesServiceSetup { - client: IWorkspaceDBImpl; -} - -export interface WorkspacesServiceStart { - client: IWorkspaceDBImpl; -} - -export interface WorkspacesSetupDeps { - http: InternalHttpServiceSetup; - savedObject: InternalSavedObjectsServiceSetup; - uiSettings: InternalUiSettingsServiceSetup; -} - -export interface WorkpsaceStartDeps { - savedObjects: SavedObjectsServiceStart; -} - -export type InternalWorkspacesServiceSetup = WorkspacesServiceSetup; -export type InternalWorkspacesServiceStart = WorkspacesServiceStart; +import { registerRoutes } from './routes'; -export class WorkspacesService - implements CoreService { - private logger: Logger; +export class WorkspacePlugin implements Plugin<{}, {}> { + private readonly logger: Logger; private client?: IWorkspaceDBImpl; - constructor(coreContext: CoreContext) { - this.logger = coreContext.logger.get('workspaces-service'); - } - - private proxyWorkspaceTrafficToRealHandler(setupDeps: WorkspacesSetupDeps) { + private proxyWorkspaceTrafficToRealHandler(setupDeps: CoreSetup) { /** * Proxy all {basePath}/w/{workspaceId}{osdPath*} paths to {basePath}{osdPath*} */ @@ -70,28 +44,30 @@ export class WorkspacesService }); } - public async setup(setupDeps: WorkspacesSetupDeps): Promise { - this.logger.debug('Setting up Workspaces service'); + constructor(initializerContext: PluginInitializerContext) { + this.logger = initializerContext.logger.get('plugins', 'workspace'); + } - setupDeps.uiSettings.register(uiSettings); + public async setup(core: CoreSetup) { + this.logger.debug('Setting up Workspaces service'); - this.client = new WorkspacesClientWithSavedObject(setupDeps); + this.client = new WorkspaceClientWithSavedObject(core); - await this.client.setup(setupDeps); + await this.client.setup(core); const workspaceSavedObjectsClientWrapper = new WorkspaceSavedObjectsClientWrapper( - setupDeps.savedObject.permissionControl + core.savedObjects.permissionControl ); - setupDeps.savedObject.addClientWrapper( + core.savedObjects.addClientWrapper( 0, 'workspace', workspaceSavedObjectsClientWrapper.wrapperFactory ); - this.proxyWorkspaceTrafficToRealHandler(setupDeps); + this.proxyWorkspaceTrafficToRealHandler(core); registerRoutes({ - http: setupDeps.http, + http: core.http, logger: this.logger, client: this.client as IWorkspaceDBImpl, }); @@ -129,15 +105,15 @@ export class WorkspacesService } } - private async setupWorkspaces(startDeps: WorkpsaceStartDeps) { + private async setupWorkspaces(startDeps: CoreStart) { const internalRepository = startDeps.savedObjects.createInternalRepository(); const publicWorkspaceACL = new ACL().addPermission( - [PermissionMode.LibraryRead, PermissionMode.LibraryWrite], + [WorkspacePermissionMode.LibraryRead, WorkspacePermissionMode.LibraryWrite], { users: ['*'], } ); - const managementWorkspaceACL = new ACL().addPermission([PermissionMode.LibraryRead], { + const managementWorkspaceACL = new ACL().addPermission([WorkspacePermissionMode.LibraryRead], { users: ['*'], }); @@ -165,15 +141,15 @@ export class WorkspacesService ]); } - public async start(startDeps: WorkpsaceStartDeps): Promise { + public start(core: CoreStart) { this.logger.debug('Starting SavedObjects service'); - this.setupWorkspaces(startDeps); + this.setupWorkspaces(core); return { client: this.client as IWorkspaceDBImpl, }; } - public async stop() {} + public stop() {} } diff --git a/src/core/server/workspaces/routes/index.ts b/src/plugins/workspace/server/routes/index.ts similarity index 90% rename from src/core/server/workspaces/routes/index.ts rename to src/plugins/workspace/server/routes/index.ts index 67c3fcfd77a0..2ae62079322b 100644 --- a/src/core/server/workspaces/routes/index.ts +++ b/src/plugins/workspace/server/routes/index.ts @@ -4,18 +4,21 @@ */ import { schema } from '@osd/config-schema'; -import { PermissionMode } from '../../../utils/constants'; -import { ACL, Permissions } from '../../saved_objects/permission_control/acl'; -import { InternalHttpServiceSetup } from '../../http'; -import { Logger } from '../../logging'; +import { + ACL, + Permissions, + CoreSetup, + Logger, + WorkspacePermissionMode, +} from '../../../../core/server'; import { IWorkspaceDBImpl, WorkspaceRoutePermissionItem } from '../types'; const WORKSPACES_API_BASE_URL = '/api/workspaces'; const workspacePermissionMode = schema.oneOf([ - schema.literal(PermissionMode.LibraryRead), - schema.literal(PermissionMode.LibraryWrite), - schema.literal(PermissionMode.Management), + schema.literal(WorkspacePermissionMode.LibraryRead), + schema.literal(WorkspacePermissionMode.LibraryWrite), + schema.literal(WorkspacePermissionMode.Management), ]); const workspacePermission = schema.oneOf([ @@ -81,12 +84,12 @@ export function registerRoutes({ }: { client: IWorkspaceDBImpl; logger: Logger; - http: InternalHttpServiceSetup; + http: CoreSetup['http']; }) { - const router = http.createRouter(WORKSPACES_API_BASE_URL); + const router = http.createRouter(); router.post( { - path: '/_list', + path: `${WORKSPACES_API_BASE_URL}/_list`, validate: { body: schema.object({ search: schema.maybe(schema.string()), @@ -126,7 +129,7 @@ export function registerRoutes({ ); router.get( { - path: '/{id}', + path: `${WORKSPACES_API_BASE_URL}/{id}`, validate: { params: schema.object({ id: schema.string(), @@ -160,7 +163,7 @@ export function registerRoutes({ ); router.post( { - path: '', + path: `${WORKSPACES_API_BASE_URL}`, validate: { body: schema.object({ attributes: workspaceAttributesSchema, @@ -186,7 +189,7 @@ export function registerRoutes({ ); router.put( { - path: '/{id?}', + path: `${WORKSPACES_API_BASE_URL}/{id?}`, validate: { params: schema.object({ id: schema.string(), @@ -217,7 +220,7 @@ export function registerRoutes({ ); router.delete( { - path: '/{id?}', + path: `${WORKSPACES_API_BASE_URL}/{id?}`, validate: { params: schema.object({ id: schema.string(), diff --git a/src/core/server/workspaces/saved_objects/index.ts b/src/plugins/workspace/server/saved_objects/index.ts similarity index 100% rename from src/core/server/workspaces/saved_objects/index.ts rename to src/plugins/workspace/server/saved_objects/index.ts diff --git a/src/core/server/workspaces/saved_objects/workspace.ts b/src/plugins/workspace/server/saved_objects/workspace.ts similarity index 86% rename from src/core/server/workspaces/saved_objects/workspace.ts rename to src/plugins/workspace/server/saved_objects/workspace.ts index 284ed013b7ed..5142185b0c2d 100644 --- a/src/core/server/workspaces/saved_objects/workspace.ts +++ b/src/plugins/workspace/server/saved_objects/workspace.ts @@ -3,8 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { SavedObjectsType } from 'opensearch-dashboards/server'; -import { WORKSPACE_TYPE } from '../constants'; +import { SavedObjectsType, WORKSPACE_TYPE } from '../../../../core/server'; export const workspace: SavedObjectsType = { name: WORKSPACE_TYPE, diff --git a/src/core/server/workspaces/saved_objects/workspace_saved_objects_client_wrapper.ts b/src/plugins/workspace/server/saved_objects/workspace_saved_objects_client_wrapper.ts similarity index 87% rename from src/core/server/workspaces/saved_objects/workspace_saved_objects_client_wrapper.ts rename to src/plugins/workspace/server/saved_objects/workspace_saved_objects_client_wrapper.ts index 5f852d48a068..bd1d68bd0f7b 100644 --- a/src/core/server/workspaces/saved_objects/workspace_saved_objects_client_wrapper.ts +++ b/src/plugins/workspace/server/saved_objects/workspace_saved_objects_client_wrapper.ts @@ -19,11 +19,11 @@ import { SavedObjectsDeleteOptions, SavedObjectsFindOptions, SavedObjectsShareObjects, -} from 'opensearch-dashboards/server'; -import { SavedObjectsPermissionControlContract } from '../../saved_objects/permission_control/client'; -import { WORKSPACE_TYPE } from '../constants'; -import { PermissionMode } from '../../../utils'; -import { ACL } from '../../saved_objects/permission_control/acl'; + SavedObjectsPermissionControlContract, + WORKSPACE_TYPE, + ACL, + WorkspacePermissionMode, +} from '../../../../core/server'; // Can't throw unauthorized for now, the page will be refreshed if unauthorized const generateWorkspacePermissionError = () => @@ -51,8 +51,8 @@ const isWorkspacesLikeAttributes = (attributes: unknown): attributes is Attribut Array.isArray((attributes as { workspaces: unknown }).workspaces); export class WorkspaceSavedObjectsClientWrapper { - private formatPermissionModeToStringArray( - permission: PermissionMode | PermissionMode[] + private formatWorkspacePermissionModeToStringArray( + permission: WorkspacePermissionMode | WorkspacePermissionMode[] ): string[] { if (Array.isArray(permission)) { return permission; @@ -63,7 +63,7 @@ export class WorkspaceSavedObjectsClientWrapper { private async validateMultiWorkspacesPermissions( workspaces: string[] | undefined, request: OpenSearchDashboardsRequest, - permissionMode: PermissionMode | PermissionMode[] + permissionMode: WorkspacePermissionMode | WorkspacePermissionMode[] ) { if (!workspaces) { return; @@ -76,7 +76,7 @@ export class WorkspaceSavedObjectsClientWrapper { type: WORKSPACE_TYPE, id: workspaceId, }, - this.formatPermissionModeToStringArray(permissionMode) + this.formatWorkspacePermissionModeToStringArray(permissionMode) )) ) { throw generateWorkspacePermissionError(); @@ -87,7 +87,7 @@ export class WorkspaceSavedObjectsClientWrapper { private async validateAtLeastOnePermittedWorkspaces( workspaces: string[] | undefined, request: OpenSearchDashboardsRequest, - permissionMode: PermissionMode | PermissionMode[] + permissionMode: WorkspacePermissionMode | WorkspacePermissionMode[] ) { if (!workspaces) { return; @@ -101,7 +101,7 @@ export class WorkspaceSavedObjectsClientWrapper { type: WORKSPACE_TYPE, id: workspaceId, }, - this.formatPermissionModeToStringArray(permissionMode) + this.formatWorkspacePermissionModeToStringArray(permissionMode) ) ) { permitted = true; @@ -133,7 +133,7 @@ export class WorkspaceSavedObjectsClientWrapper { await this.validateMultiWorkspacesPermissions( objectToDeleted.workspaces, wrapperOptions.request, - PermissionMode.Management + WorkspacePermissionMode.Management ); return await wrapperOptions.client.delete(type, id, options); }; @@ -144,8 +144,8 @@ export class WorkspaceSavedObjectsClientWrapper { ): Promise> => { if (options.workspaces) { await this.validateMultiWorkspacesPermissions(options.workspaces, wrapperOptions.request, [ - PermissionMode.Write, - PermissionMode.Management, + WorkspacePermissionMode.Write, + WorkspacePermissionMode.Management, ]); } return await wrapperOptions.client.bulkCreate(objects, options); @@ -160,7 +160,7 @@ export class WorkspaceSavedObjectsClientWrapper { await this.validateMultiWorkspacesPermissions( attributes.workspaces, wrapperOptions.request, - PermissionMode.Management + WorkspacePermissionMode.Management ); } return await wrapperOptions.client.create(type, attributes, options); @@ -175,7 +175,7 @@ export class WorkspaceSavedObjectsClientWrapper { await this.validateAtLeastOnePermittedWorkspaces( objectToGet.workspaces, wrapperOptions.request, - PermissionMode.Read + WorkspacePermissionMode.Read ); return objectToGet; }; @@ -189,7 +189,7 @@ export class WorkspaceSavedObjectsClientWrapper { await this.validateAtLeastOnePermittedWorkspaces( object.workspaces, wrapperOptions.request, - PermissionMode.Read + WorkspacePermissionMode.Read ); } return objectToBulkGet; @@ -202,7 +202,11 @@ export class WorkspaceSavedObjectsClientWrapper { if (this.isRelatedToWorkspace(options.type)) { const queryDSLForQueryingWorkspaces = ACL.genereateGetPermittedSavedObjectsQueryDSL( - [PermissionMode.LibraryRead, PermissionMode.LibraryWrite, PermissionMode.Management], + [ + WorkspacePermissionMode.LibraryRead, + WorkspacePermissionMode.LibraryWrite, + WorkspacePermissionMode.Management, + ], principals, WORKSPACE_TYPE ); @@ -210,7 +214,11 @@ export class WorkspaceSavedObjectsClientWrapper { } else { const permittedWorkspaceIds = await this.permissionControl.getPermittedWorkspaceIds( wrapperOptions.request, - [PermissionMode.LibraryRead, PermissionMode.LibraryWrite, PermissionMode.Management] + [ + WorkspacePermissionMode.LibraryRead, + WorkspacePermissionMode.LibraryWrite, + WorkspacePermissionMode.Management, + ] ); if (options.workspaces) { const isEveryWorkspaceIsPermitted = options.workspaces.every((item) => @@ -221,7 +229,7 @@ export class WorkspaceSavedObjectsClientWrapper { } } else { const queryDSL = ACL.genereateGetPermittedSavedObjectsQueryDSL( - [PermissionMode.Read, PermissionMode.Write], + [WorkspacePermissionMode.Read, WorkspacePermissionMode.Write], principals, options.type ); @@ -270,8 +278,8 @@ export class WorkspaceSavedObjectsClientWrapper { ) => { // target workspaces await this.validateMultiWorkspacesPermissions(targetWorkspaces, wrapperOptions.request, [ - PermissionMode.LibraryWrite, - PermissionMode.Management, + WorkspacePermissionMode.LibraryWrite, + WorkspacePermissionMode.Management, ]); // saved_objects @@ -280,7 +288,7 @@ export class WorkspaceSavedObjectsClientWrapper { objects.map((savedObj) => ({ ...savedObj, })), - [PermissionMode.Write] + [WorkspacePermissionMode.Write] ); if (!permitted) { diff --git a/src/core/server/workspaces/types.ts b/src/plugins/workspace/server/types.ts similarity index 84% rename from src/core/server/workspaces/types.ts rename to src/plugins/workspace/server/types.ts index 1a1ae8583639..72afd87fff90 100644 --- a/src/core/server/workspaces/types.ts +++ b/src/plugins/workspace/server/types.ts @@ -7,12 +7,10 @@ import { OpenSearchDashboardsRequest, RequestHandlerContext, SavedObjectsFindResponse, -} from '..'; - -import { Permissions } from '../saved_objects/permission_control/acl'; -import { PermissionMode } from '../../utils/constants'; - -import { WorkspacesSetupDeps } from './workspaces_service'; + CoreSetup, + WorkspacePermissionMode, + Permissions, +} from '../../../core/server'; export interface WorkspaceAttribute { id: string; @@ -44,7 +42,7 @@ export interface IRequestDetail { } export interface IWorkspaceDBImpl { - setup(dep: WorkspacesSetupDeps): Promise>; + setup(dep: CoreSetup): Promise>; create( requestDetail: IRequestDetail, payload: Omit @@ -84,6 +82,8 @@ export type IResponse = export type WorkspaceRoutePermissionItem = { modes: Array< - PermissionMode.LibraryRead | PermissionMode.LibraryWrite | PermissionMode.Management + | WorkspacePermissionMode.LibraryRead + | WorkspacePermissionMode.LibraryWrite + | WorkspacePermissionMode.Management >; } & ({ type: 'user'; userId: string } | { type: 'group'; group: string }); diff --git a/src/core/server/workspaces/workspaces_client.ts b/src/plugins/workspace/server/workspace_client.ts similarity index 86% rename from src/core/server/workspaces/workspaces_client.ts rename to src/plugins/workspace/server/workspace_client.ts index 698c80c1488a..a44060e7193b 100644 --- a/src/core/server/workspaces/workspaces_client.ts +++ b/src/plugins/workspace/server/workspace_client.ts @@ -2,7 +2,8 @@ * Copyright OpenSearch Contributors * SPDX-License-Identifier: Apache-2.0 */ -import { SavedObject, SavedObjectError, SavedObjectsClientContract } from '../types'; +import type { SavedObject, SavedObjectsClientContract, CoreSetup } from '../../../core/server'; +import { WORKSPACE_TYPE } from '../../../core/server'; import { IWorkspaceDBImpl, WorkspaceAttribute, @@ -11,14 +12,12 @@ import { IRequestDetail, WorkspaceAttributeWithPermission, } from './types'; -import { WorkspacesSetupDeps } from './workspaces_service'; import { workspace } from './saved_objects'; -import { WORKSPACE_TYPE } from './constants'; -export class WorkspacesClientWithSavedObject implements IWorkspaceDBImpl { - private setupDep: WorkspacesSetupDeps; - constructor(dep: WorkspacesSetupDeps) { - this.setupDep = dep; +export class WorkspaceClientWithSavedObject implements IWorkspaceDBImpl { + private setupDep: CoreSetup; + constructor(core: CoreSetup) { + this.setupDep = core; } private getSavedObjectClientsFromRequestDetail( requestDetail: IRequestDetail @@ -34,11 +33,11 @@ export class WorkspacesClientWithSavedObject implements IWorkspaceDBImpl { id: savedObject.id, }; } - private formatError(error: SavedObjectError | Error | any): string { + private formatError(error: Error | any): string { return error.message || error.error || 'Error'; } - public async setup(dep: WorkspacesSetupDeps): Promise> { - this.setupDep.savedObject.registerType(workspace); + public async setup(core: CoreSetup): Promise> { + this.setupDep.savedObjects.registerType(workspace); return { success: true, result: true,