Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature: create management / public workspaces when calling list api #236

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion src/core/server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -347,7 +347,13 @@ export {
} from './metrics';

export { AppCategory, WorkspaceAttribute } from '../types';
export { DEFAULT_APP_CATEGORIES, WORKSPACE_TYPE } from '../utils';
export {
DEFAULT_APP_CATEGORIES,
PUBLIC_WORKSPACE_ID,
MANAGEMENT_WORKSPACE_ID,
WORKSPACE_TYPE,
PERSONAL_WORKSPACE_ID_PREFIX,
} from '../utils';

export {
SavedObject,
Expand Down
6 changes: 6 additions & 0 deletions src/core/utils/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,9 @@
export const WORKSPACE_TYPE = 'workspace';

export const WORKSPACE_PATH_PREFIX = '/w';

export const PUBLIC_WORKSPACE_ID = 'public';

export const MANAGEMENT_WORKSPACE_ID = 'management';

export const PERSONAL_WORKSPACE_ID_PREFIX = 'personal';
8 changes: 7 additions & 1 deletion src/core/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,5 +37,11 @@ export {
IContextProvider,
} from './context';
export { DEFAULT_APP_CATEGORIES } from './default_app_categories';
export { WORKSPACE_PATH_PREFIX, WORKSPACE_TYPE } from './constants';
export { getWorkspaceIdFromUrl, formatUrlWithWorkspaceId, cleanWorkspaceId } from './workspace';
export {
WORKSPACE_PATH_PREFIX,
PUBLIC_WORKSPACE_ID,
MANAGEMENT_WORKSPACE_ID,
WORKSPACE_TYPE,
PERSONAL_WORKSPACE_ID_PREFIX,
} from './constants';
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { ContextValue, ServicesContextProvider } from '../contexts';
import { serviceContextMock } from '../contexts/services_context.mock';
import { wrapWithIntl, nextTick } from 'test_utils/enzyme_helpers';
import { ReactWrapper, mount } from 'enzyme';
import { waitFor } from '@testing-library/dom';

const mockFile = new File(['{"text":"Sample JSON data"}'], 'sample.json', {
type: 'application/json',
Expand Down Expand Up @@ -122,9 +123,11 @@ describe('ImportFlyout Component', () => {
expect(component.find(overwriteOptionIdentifier).first().props().checked).toBe(false);

// should update existing query
expect(mockUpdate).toBeCalledTimes(1);
expect(mockClose).toBeCalledTimes(1);
expect(mockRefresh).toBeCalledTimes(1);
await waitFor(() => {
expect(mockUpdate).toBeCalledTimes(1);
expect(mockClose).toBeCalledTimes(1);
expect(mockRefresh).toBeCalledTimes(1);
});
});

it('should handle errors during import', async () => {
Expand Down
1 change: 1 addition & 0 deletions src/plugins/workspace/common/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
* SPDX-License-Identifier: Apache-2.0
*/

export const WORKSPACE_UPDATE_APP_ID = 'workspace_update';
export const WORKSPACE_OVERVIEW_APP_ID = 'workspace_overview';
export const WORKSPACE_FATAL_ERROR_APP_ID = 'workspace_fatal_error';
export const WORKSPACE_SAVED_OBJECTS_CLIENT_WRAPPER_ID = 'workspace';
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@ describe('workspace service', () => {
page: 1,
})
.expect(200);
expect(listResult.body.result.total).toEqual(1);
expect(listResult.body.result.total).toEqual(3);
});
});
});
2 changes: 1 addition & 1 deletion src/plugins/workspace/server/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ export class WorkspacePlugin implements Plugin<{}, {}> {
public async setup(core: CoreSetup) {
this.logger.debug('Setting up Workspaces service');

this.client = new WorkspaceClientWithSavedObject(core);
this.client = new WorkspaceClientWithSavedObject(core, this.logger);

await this.client.setup(core);

Expand Down
127 changes: 122 additions & 5 deletions src/plugins/workspace/server/workspace_client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,24 +11,41 @@ import type {
WorkspaceAttribute,
SavedObjectsServiceStart,
} from '../../../core/server';
import { WORKSPACE_TYPE } from '../../../core/server';
import {
DEFAULT_APP_CATEGORIES,
MANAGEMENT_WORKSPACE_ID,
PUBLIC_WORKSPACE_ID,
WORKSPACE_TYPE,
Logger,
} from '../../../core/server';
import { IWorkspaceDBImpl, WorkspaceFindOptions, IResponse, IRequestDetail } from './types';
import { workspace } from './saved_objects';
import { generateRandomId } from './utils';
import { WORKSPACE_SAVED_OBJECTS_CLIENT_WRAPPER_ID } from '../common/constants';
import {
WORKSPACE_OVERVIEW_APP_ID,
WORKSPACE_SAVED_OBJECTS_CLIENT_WRAPPER_ID,
WORKSPACE_UPDATE_APP_ID,
} from '../common/constants';

const WORKSPACE_ID_SIZE = 6;

const DUPLICATE_WORKSPACE_NAME_ERROR = i18n.translate('workspace.duplicate.name.error', {
defaultMessage: 'workspace name has already been used, try with a different name',
});

const RESERVED_WORKSPACE_NAME_ERROR = i18n.translate('workspace.reserved.name.error', {
defaultMessage: 'reserved workspace name cannot be changed',
});

export class WorkspaceClientWithSavedObject implements IWorkspaceDBImpl {
private setupDep: CoreSetup;
private logger: Logger;

private savedObjects?: SavedObjectsServiceStart;

constructor(core: CoreSetup) {
constructor(core: CoreSetup, logger: Logger) {
this.setupDep = core;
this.logger = logger;
}

private getScopedClientWithoutPermission(
Expand All @@ -55,6 +72,55 @@ export class WorkspaceClientWithSavedObject implements IWorkspaceDBImpl {
private formatError(error: Error | any): string {
return error.message || error.error || 'Error';
}
private async checkAndCreateWorkspace(
savedObjectClient: SavedObjectsClientContract | undefined,
workspaceId: string,
workspaceAttribute: Omit<WorkspaceAttribute, 'id' | 'permissions'>
) {
try {
await savedObjectClient?.get(WORKSPACE_TYPE, workspaceId);
} catch (error) {
this.logger.debug(error?.toString() || '');
this.logger.info(`Workspace ${workspaceId} is not found, create it by using internal user`);
try {
const createResult = await savedObjectClient?.create(WORKSPACE_TYPE, workspaceAttribute, {
id: workspaceId,
});
if (createResult?.id) {
this.logger.info(`Created workspace ${createResult.id}.`);
}
} catch (e) {
this.logger.error(`Create ${workspaceId} workspace error: ${e?.toString() || ''}`);
}
}
}
private async setupPublicWorkspace(savedObjectClient?: SavedObjectsClientContract) {
return this.checkAndCreateWorkspace(savedObjectClient, PUBLIC_WORKSPACE_ID, {
name: i18n.translate('workspaces.public.workspace.default.name', {
defaultMessage: 'Global workspace',
}),
features: ['*', `!@${DEFAULT_APP_CATEGORIES.management.id}`],
reserved: true,
});
}
private async setupManagementWorkspace(savedObjectClient?: SavedObjectsClientContract) {
const DSM_APP_ID = 'dataSources';
const DEV_TOOLS_APP_ID = 'dev_tools';

return this.checkAndCreateWorkspace(savedObjectClient, MANAGEMENT_WORKSPACE_ID, {
name: i18n.translate('workspaces.management.workspace.default.name', {
defaultMessage: 'Management',
}),
features: [
`@${DEFAULT_APP_CATEGORIES.management.id}`,
WORKSPACE_OVERVIEW_APP_ID,
WORKSPACE_UPDATE_APP_ID,
DSM_APP_ID,
DEV_TOOLS_APP_ID,
],
reserved: true,
});
}
public async setup(core: CoreSetup): Promise<IResponse<boolean>> {
this.setupDep.savedObjects.registerType(workspace);
return {
Expand Down Expand Up @@ -105,7 +171,7 @@ export class WorkspaceClientWithSavedObject implements IWorkspaceDBImpl {
options: WorkspaceFindOptions
): ReturnType<IWorkspaceDBImpl['list']> {
try {
const {
let {
saved_objects: savedObjects,
...others
} = await this.getSavedObjectClientsFromRequestDetail(requestDetail).find<WorkspaceAttribute>(
Expand All @@ -114,6 +180,49 @@ export class WorkspaceClientWithSavedObject implements IWorkspaceDBImpl {
type: WORKSPACE_TYPE,
}
);
const scopedClientWithoutPermissionCheck = this.getScopedClientWithoutPermission(
requestDetail
);
const tasks: Array<Promise<unknown>> = [];

/**
* Setup public workspace if public workspace can not be found
*/
const hasPublicWorkspace = savedObjects.some((item) => item.id === PUBLIC_WORKSPACE_ID);

if (!hasPublicWorkspace) {
tasks.push(this.setupPublicWorkspace(scopedClientWithoutPermissionCheck));
}

/**
* Setup management workspace if management workspace can not be found
*/
const hasManagementWorkspace = savedObjects.some(
(item) => item.id === MANAGEMENT_WORKSPACE_ID
);
if (!hasManagementWorkspace) {
tasks.push(this.setupManagementWorkspace(scopedClientWithoutPermissionCheck));
}

try {
await Promise.all(tasks);
if (tasks.length) {
const {
saved_objects: retryFindSavedObjects,
...retryFindOthers
} = await this.getSavedObjectClientsFromRequestDetail(requestDetail).find<
WorkspaceAttribute
>({
...options,
type: WORKSPACE_TYPE,
});
savedObjects = retryFindSavedObjects;
others = retryFindOthers;
}
} catch (e) {
this.logger.error(`Some error happened when initializing reserved workspace: ${e}`);
}

return {
success: true,
result: {
Expand Down Expand Up @@ -157,6 +266,9 @@ export class WorkspaceClientWithSavedObject implements IWorkspaceDBImpl {
const client = this.getSavedObjectClientsFromRequestDetail(requestDetail);
const workspaceInDB: SavedObject<WorkspaceAttribute> = await client.get(WORKSPACE_TYPE, id);
if (workspaceInDB.attributes.name !== attributes.name) {
if (workspaceInDB.attributes.reserved) {
throw new Error(RESERVED_WORKSPACE_NAME_ERROR);
}
const existingWorkspaceRes = await this.getScopedClientWithoutPermission(
requestDetail
)?.find({
Expand All @@ -169,7 +281,12 @@ export class WorkspaceClientWithSavedObject implements IWorkspaceDBImpl {
throw new Error(DUPLICATE_WORKSPACE_NAME_ERROR);
}
}
await client.update<Omit<WorkspaceAttribute, 'id'>>(WORKSPACE_TYPE, id, attributes, {});

await client.create<Omit<WorkspaceAttribute, 'id'>>(WORKSPACE_TYPE, attributes, {
id,
overwrite: true,
version: workspaceInDB.version,
});
return {
success: true,
result: true,
Expand Down
Loading