Skip to content

Commit

Permalink
Patch/first pr (opensearch-project#194)
Browse files Browse the repository at this point in the history
* temp: add unit test

Signed-off-by: SuZhou-Joe <suzhou@amazon.com>

* feat: add function test for workspace CRUD routes

Signed-off-by: SuZhou-Joe <suzhou@amazon.com>

* feat: use saved objects client instead of internal repository

Signed-off-by: SuZhou-Joe <suzhou@amazon.com>

* feat: update CHANGELOG

Signed-off-by: SuZhou-Joe <suzhou@amazon.com>

* feat: exclude permission check wrapper

Signed-off-by: SuZhou-Joe <suzhou@amazon.com>

* feat: add integration test

Signed-off-by: SuZhou-Joe <suzhou@amazon.com>

* feat: add configuration

Signed-off-by: SuZhou-Joe <suzhou@amazon.com>

* feat: enable workspace flag when run workspace related test

Signed-off-by: SuZhou-Joe <suzhou@amazon.com>

---------

Signed-off-by: SuZhou-Joe <suzhou@amazon.com>
  • Loading branch information
SuZhou-Joe authored Sep 22, 2023
1 parent f630277 commit 855c3a8
Show file tree
Hide file tree
Showing 12 changed files with 350 additions and 30 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ Inspired from [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
- Adds Data explorer framework and implements Discover using it ([#4806](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/4806))
- [Theme] Use themes' definitions to render the initial view ([#4936](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/4936/))
- [Theme] Make `next` theme the default ([#4854](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/4854/))
- [Workspace] Setup workspace skeleton and implement basic CRUD API ([#5075](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/5075/))

### 🐛 Bug Fixes

Expand Down
6 changes: 6 additions & 0 deletions src/plugins/workspace/common/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

export const WORKSPACE_SAVED_OBJECTS_CLIENT_WRAPPER_ID = 'workspace';
12 changes: 12 additions & 0 deletions src/plugins/workspace/config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

import { schema, TypeOf } from '@osd/config-schema';

export const configSchema = schema.object({
enabled: schema.boolean({ defaultValue: false }),
});

export type ConfigSchema = TypeOf<typeof configSchema>;
9 changes: 8 additions & 1 deletion src/plugins/workspace/server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,19 @@
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/
import { PluginInitializerContext } from '../../../core/server';
import { PluginConfigDescriptor, PluginInitializerContext } from '../../../core/server';
import { WorkspacePlugin } from './plugin';
import { configSchema } from '../config';

// 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 const config: PluginConfigDescriptor = {
schema: configSchema,
};

export { WorkspaceFindOptions } from './types';
146 changes: 146 additions & 0 deletions src/plugins/workspace/server/integration_tests/routes.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

import { WorkspaceAttribute } from 'src/core/types';
import { omit } from 'lodash';
import * as osdTestServer from '../../../../core/test_helpers/osd_server';

const testWorkspace: WorkspaceAttribute = {
id: 'fake_id',
name: 'test_workspace',
description: 'test_workspace_description',
};

describe('workspace service', () => {
let root: ReturnType<typeof osdTestServer.createRoot>;
let opensearchServer: osdTestServer.TestOpenSearchUtils;
beforeAll(async () => {
const { startOpenSearch, startOpenSearchDashboards } = osdTestServer.createTestServers({
adjustTimeout: (t: number) => jest.setTimeout(t),
settings: {
osd: {
workspace: {
enabled: true,
},
},
},
});
opensearchServer = await startOpenSearch();
const startOSDResp = await startOpenSearchDashboards();
root = startOSDResp.root;
}, 30000);
afterAll(async () => {
await root.shutdown();
await opensearchServer.stop();
});
describe('Workspace CRUD apis', () => {
afterEach(async () => {
const listResult = await osdTestServer.request
.post(root, `/api/workspaces/_list`)
.send({
page: 1,
})
.expect(200);
await Promise.all(
listResult.body.result.workspaces.map((item: WorkspaceAttribute) =>
osdTestServer.request.delete(root, `/api/workspaces/${item.id}`).expect(200)
)
);
});
it('create', async () => {
await osdTestServer.request
.post(root, `/api/workspaces`)
.send({
attributes: testWorkspace,
})
.expect(400);

const result: any = await osdTestServer.request
.post(root, `/api/workspaces`)
.send({
attributes: omit(testWorkspace, 'id'),
})
.expect(200);

expect(result.body.success).toEqual(true);
expect(typeof result.body.result.id).toBe('string');
});
it('get', async () => {
const result = await osdTestServer.request
.post(root, `/api/workspaces`)
.send({
attributes: omit(testWorkspace, 'id'),
})
.expect(200);

const getResult = await osdTestServer.request.get(
root,
`/api/workspaces/${result.body.result.id}`
);
expect(getResult.body.result.name).toEqual(testWorkspace.name);
});
it('update', async () => {
const result: any = await osdTestServer.request
.post(root, `/api/workspaces`)
.send({
attributes: omit(testWorkspace, 'id'),
})
.expect(200);

await osdTestServer.request
.put(root, `/api/workspaces/${result.body.result.id}`)
.send({
attributes: {
...omit(testWorkspace, 'id'),
name: 'updated',
},
})
.expect(200);

const getResult = await osdTestServer.request.get(
root,
`/api/workspaces/${result.body.result.id}`
);

expect(getResult.body.success).toEqual(true);
expect(getResult.body.result.name).toEqual('updated');
});
it('delete', async () => {
const result: any = await osdTestServer.request
.post(root, `/api/workspaces`)
.send({
attributes: omit(testWorkspace, 'id'),
})
.expect(200);

await osdTestServer.request
.delete(root, `/api/workspaces/${result.body.result.id}`)
.expect(200);

const getResult = await osdTestServer.request.get(
root,
`/api/workspaces/${result.body.result.id}`
);

expect(getResult.body.success).toEqual(false);
});
it('list', async () => {
await osdTestServer.request
.post(root, `/api/workspaces`)
.send({
attributes: omit(testWorkspace, 'id'),
})
.expect(200);

const listResult = await osdTestServer.request
.post(root, `/api/workspaces/_list`)
.send({
page: 1,
})
.expect(200);
expect(listResult.body.result.total).toEqual(1);
});
});
});
11 changes: 9 additions & 2 deletions src/plugins/workspace/server/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,13 @@
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/
import { PluginInitializerContext, CoreSetup, Plugin, Logger } from '../../../core/server';
import {
PluginInitializerContext,
CoreSetup,
Plugin,
Logger,
CoreStart,
} from '../../../core/server';
import { IWorkspaceDBImpl } from './types';
import { WorkspaceClientWithSavedObject } from './workspace_client';
import { registerRoutes } from './routes';
Expand Down Expand Up @@ -33,8 +39,9 @@ export class WorkspacePlugin implements Plugin<{}, {}> {
};
}

public start() {
public start(core: CoreStart) {
this.logger.debug('Starting Workspace service');
this.client?.setSavedObjects(core.savedObjects);

return {
client: this.client as IWorkspaceDBImpl,
Expand Down
10 changes: 1 addition & 9 deletions src/plugins/workspace/server/routes/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,15 +55,7 @@ export function registerRoutes({
return res.ok({ body: result });
}
return res.ok({
body: {
...result,
result: {
...result.result,
workspaces: result.result.workspaces.map((workspace) => ({
...workspace,
})),
},
},
body: result,
});
})
);
Expand Down
11 changes: 7 additions & 4 deletions src/plugins/workspace/server/saved_objects/workspace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,16 +25,19 @@ export const workspace: SavedObjectsType = {
* In opensearch, string[] is also mapped to text
*/
features: {
type: 'text',
type: 'keyword',
},
color: {
type: 'text',
type: 'keyword',
},
icon: {
type: 'text',
type: 'keyword',
},
defaultVISTheme: {
type: 'text',
type: 'keyword',
},
reserved: {
type: 'boolean',
},
},
},
Expand Down
4 changes: 2 additions & 2 deletions src/plugins/workspace/server/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
SavedObjectsFindResponse,
CoreSetup,
WorkspaceAttribute,
ISavedObjectsRepository,
SavedObjectsServiceStart,
} from '../../../core/server';

export interface WorkspaceFindOptions {
Expand All @@ -29,7 +29,7 @@ export interface IRequestDetail {

export interface IWorkspaceDBImpl {
setup(dep: CoreSetup): Promise<IResponse<boolean>>;
setInternalRepository(repository: ISavedObjectsRepository): void;
setSavedObjects(savedObjects: SavedObjectsServiceStart): void;
create(
requestDetail: IRequestDetail,
payload: Omit<WorkspaceAttribute, 'id'>
Expand Down
37 changes: 25 additions & 12 deletions src/plugins/workspace/server/workspace_client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,13 @@ import type {
SavedObjectsClientContract,
CoreSetup,
WorkspaceAttribute,
ISavedObjectsRepository,
SavedObjectsServiceStart,
} from '../../../core/server';
import { WORKSPACE_TYPE } 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';

const WORKSPACE_ID_SIZE = 6;

Expand All @@ -23,15 +24,20 @@ const DUPLICATE_WORKSPACE_NAME_ERROR = i18n.translate('workspace.duplicate.name.

export class WorkspaceClientWithSavedObject implements IWorkspaceDBImpl {
private setupDep: CoreSetup;

private internalSavedObjectsRepository?: ISavedObjectsRepository;
setInternalRepository(repository: ISavedObjectsRepository) {
this.internalSavedObjectsRepository = repository;
}
private savedObjects?: SavedObjectsServiceStart;

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

private getScopedClientWithoutPermission(
requestDetail: IRequestDetail
): SavedObjectsClientContract | undefined {
return this.savedObjects?.getScopedClient(requestDetail.request, {
excludedWrappers: [WORKSPACE_SAVED_OBJECTS_CLIENT_WRAPPER_ID],
});
}

private getSavedObjectClientsFromRequestDetail(
requestDetail: IRequestDetail
): SavedObjectsClientContract {
Expand Down Expand Up @@ -63,11 +69,13 @@ export class WorkspaceClientWithSavedObject implements IWorkspaceDBImpl {
const attributes = payload;
const id = generateRandomId(WORKSPACE_ID_SIZE);
const client = this.getSavedObjectClientsFromRequestDetail(requestDetail);
const existingWorkspaceRes = await this.internalSavedObjectsRepository?.find({
type: WORKSPACE_TYPE,
search: attributes.name,
searchFields: ['name'],
});
const existingWorkspaceRes = await this.getScopedClientWithoutPermission(requestDetail)?.find(
{
type: WORKSPACE_TYPE,
search: attributes.name,
searchFields: ['name'],
}
);
if (existingWorkspaceRes && existingWorkspaceRes.total > 0) {
throw new Error(DUPLICATE_WORKSPACE_NAME_ERROR);
}
Expand Down Expand Up @@ -148,7 +156,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) {
const existingWorkspaceRes = await this.internalSavedObjectsRepository?.find({
const existingWorkspaceRes = await this.getScopedClientWithoutPermission(
requestDetail
)?.find({
type: WORKSPACE_TYPE,
search: attributes.name,
searchFields: ['name'],
Expand Down Expand Up @@ -184,6 +194,9 @@ export class WorkspaceClientWithSavedObject implements IWorkspaceDBImpl {
};
}
}
public setSavedObjects(savedObjects: SavedObjectsServiceStart) {
this.savedObjects = savedObjects;
}
public async destroy(): Promise<IResponse<boolean>> {
return {
success: true,
Expand Down
1 change: 1 addition & 0 deletions test/api_integration/apis/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,5 +45,6 @@ export default function ({ loadTestFile }) {
loadTestFile(require.resolve('./stats'));
loadTestFile(require.resolve('./ui_metric'));
loadTestFile(require.resolve('./telemetry'));
loadTestFile(require.resolve('./workspace'));
});
}
Loading

0 comments on commit 855c3a8

Please sign in to comment.