Skip to content

Commit

Permalink
[Workspace][Feature]Setup workspace skeleton and implement basic CRUD…
Browse files Browse the repository at this point in the history
… API (#5075)

* feature: setup workspace skeleton and implement basic CRUD API on workspace

Signed-off-by: Zhou Su <suzhou@dev-dsk-suzhou-2a-8ce7a7a7.us-west-2.amazon.com>

* feat: remove useless required plugins and logger typo

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

* feat: setup public side skeleton

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

* 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>

* feat: optimization according to PR comments

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

* feat: add JSDoc for workspace client

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

* Update src/plugins/workspace/server/integration_tests/routes.test.ts

Co-authored-by: Josh Romero <rmerqg@amazon.com>
Signed-off-by: SuZhou-Joe <suzhou@amazon.com>

* feat: remove hard-coded delay

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

* feat: optimize unit test

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

* fix: unit test

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

* feat: Only allow workspace CRUD APIs to modify workspace metadata.

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

* feat: add integration test for new changes

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

---------

Signed-off-by: Zhou Su <suzhou@dev-dsk-suzhou-2a-8ce7a7a7.us-west-2.amazon.com>
Signed-off-by: SuZhou-Joe <suzhou@amazon.com>
Co-authored-by: Zhou Su <suzhou@dev-dsk-suzhou-2a-8ce7a7a7.us-west-2.amazon.com>
Co-authored-by: Josh Romero <rmerqg@amazon.com>
  • Loading branch information
3 people authored Oct 30, 2023
1 parent b796940 commit eeb3251
Show file tree
Hide file tree
Showing 21 changed files with 1,085 additions and 2 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ Inspired from [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
- [Theme] Make `next` theme the default ([#4854](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/4854))
- [Discover] Update embeddable for saved searches ([#5081](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/5081))
- [Workspace] Add core workspace service module to enable the implementation of workspace features within OSD plugins ([#5092](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/5092))
- [Workspace] Setup workspace skeleton and implement basic CRUD API ([#5075](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/5075))

### 🐛 Bug Fixes

Expand Down
4 changes: 2 additions & 2 deletions src/core/server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -345,8 +345,8 @@ export {
MetricsServiceStart,
} from './metrics';

export { AppCategory } from '../types';
export { DEFAULT_APP_CATEGORIES } from '../utils';
export { AppCategory, WorkspaceAttribute } from '../types';
export { DEFAULT_APP_CATEGORIES, WORKSPACE_TYPE } 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
@@ -0,0 +1,6 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

export const WORKSPACE_TYPE = 'workspace';
1 change: 1 addition & 0 deletions src/core/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,4 @@ export {
IContextProvider,
} from './context';
export { DEFAULT_APP_CATEGORIES } from './default_app_categories';
export { WORKSPACE_TYPE } from './constants';
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>;
11 changes: 11 additions & 0 deletions src/plugins/workspace/opensearch_dashboards.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"id": "workspace",
"version": "opensearchDashboards",
"server": true,
"ui": false,
"requiredPlugins": [
"savedObjects"
],
"optionalPlugins": [],
"requiredBundles": []
}
10 changes: 10 additions & 0 deletions src/plugins/workspace/public/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

import { WorkspacePlugin } from './plugin';

export function plugin() {
return new WorkspacePlugin();
}
18 changes: 18 additions & 0 deletions src/plugins/workspace/public/plugin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

import { Plugin } from '../../../core/public';

export class WorkspacePlugin implements Plugin<{}, {}, {}> {
public async setup() {
return {};
}

public start() {
return {};
}

public stop() {}
}
21 changes: 21 additions & 0 deletions src/plugins/workspace/server/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

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';
222 changes: 222 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,222 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

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

const omitId = <T extends { id?: string }>(object: T): Omit<T, 'id'> => {
const { id, ...others } = object;
return others;
};

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;
});
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: omitId(testWorkspace),
})
.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: omitId(testWorkspace),
})
.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: omitId(testWorkspace),
})
.expect(200);

await osdTestServer.request
.put(root, `/api/workspaces/${result.body.result.id}`)
.send({
attributes: {
...omitId(testWorkspace),
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: omitId(testWorkspace),
})
.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: omitId(testWorkspace),
})
.expect(200);

await osdTestServer.request
.post(root, `/api/workspaces`)
.send({
attributes: {
...omitId(testWorkspace),
name: 'another test workspace',
},
})
.expect(200);

const listResult = await osdTestServer.request
.post(root, `/api/workspaces/_list`)
.send({
page: 1,
})
.expect(200);
expect(listResult.body.result.total).toEqual(2);
});
it('unable to perform operations on workspace by calling saved objects APIs', async () => {
const result = await osdTestServer.request
.post(root, `/api/workspaces`)
.send({
attributes: omitId(testWorkspace),
})
.expect(200);

/**
* Can not create workspace by saved objects API
*/
await osdTestServer.request
.post(root, `/api/saved_objects/workspace`)
.send({
attributes: {
...omitId(testWorkspace),
name: 'another test workspace',
},
})
.expect(400);

/**
* Can not get workspace by saved objects API
*/
await osdTestServer.request
.get(root, `/api/saved_objects/workspace/${result.body.result.id}`)
.expect(404);

/**
* Can not update workspace by saved objects API
*/
await osdTestServer.request
.put(root, `/api/saved_objects/workspace/${result.body.result.id}`)
.send({
attributes: {
name: 'another test workspace',
},
})
.expect(404);

/**
* Can not delete workspace by saved objects API
*/
await osdTestServer.request
.delete(root, `/api/saved_objects/workspace/${result.body.result.id}`)
.expect(404);

/**
* Can not find workspace by saved objects API
*/
const findResult = await osdTestServer.request
.get(root, `/api/saved_objects/_find?type=workspace`)
.expect(200);
const listResult = await osdTestServer.request
.post(root, `/api/workspaces/_list`)
.send({
page: 1,
})
.expect(200);
expect(findResult.body.total).toEqual(0);
expect(listResult.body.result.total).toEqual(1);
});
});
});
53 changes: 53 additions & 0 deletions src/plugins/workspace/server/plugin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

import {
PluginInitializerContext,
CoreSetup,
Plugin,
Logger,
CoreStart,
} from '../../../core/server';
import { IWorkspaceClientImpl } from './types';
import { WorkspaceClient } from './workspace_client';
import { registerRoutes } from './routes';

export class WorkspacePlugin implements Plugin<{}, {}> {
private readonly logger: Logger;
private client?: IWorkspaceClientImpl;

constructor(initializerContext: PluginInitializerContext) {
this.logger = initializerContext.logger.get('plugins', 'workspace');
}

public async setup(core: CoreSetup) {
this.logger.debug('Setting up Workspaces service');

this.client = new WorkspaceClient(core);

await this.client.setup(core);

registerRoutes({
http: core.http,
logger: this.logger,
client: this.client as IWorkspaceClientImpl,
});

return {
client: this.client,
};
}

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

return {
client: this.client as IWorkspaceClientImpl,
};
}

public stop() {}
}
Loading

0 comments on commit eeb3251

Please sign in to comment.