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

Handle data sources and advanced settings as global object. #313

Merged
Show file tree
Hide file tree
Changes from 40 commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
66d6096
feat: POC implementation
SuZhou-Joe Mar 13, 2024
d275f61
feat: add some comment
SuZhou-Joe Mar 13, 2024
65f6be3
feat: revert dependency
SuZhou-Joe Mar 13, 2024
9c5d2d9
feat: update comment
SuZhou-Joe Mar 13, 2024
d5638ff
feat: address one TODO
SuZhou-Joe Mar 22, 2024
45589cf
feat: address TODO
SuZhou-Joe Mar 22, 2024
5d990c8
feat: add unit test
SuZhou-Joe Mar 22, 2024
08dc9e2
feat: some special logic on specific operation
SuZhou-Joe Mar 22, 2024
d3cccb8
feat: add integration test
SuZhou-Joe Mar 22, 2024
298c2cb
feat: declare workspaces as empty array for advanced settings
SuZhou-Joe Mar 24, 2024
85c522d
feat: unified workspaces parameters when parsing from router
SuZhou-Joe Mar 25, 2024
cdbe9a8
feat: improve code coverage
SuZhou-Joe Mar 25, 2024
cb7ebd9
feat: declare workspaces as null
SuZhou-Joe Mar 27, 2024
294d2c5
feat: use unified types
SuZhou-Joe Mar 27, 2024
42e0e05
feat: update comment
SuZhou-Joe Mar 28, 2024
1b32316
feat: remove null
SuZhou-Joe Apr 1, 2024
b7a6cb7
feat: address comments
SuZhou-Joe Apr 2, 2024
dd6cebf
feat: use request app to store request workspace id
SuZhou-Joe Apr 3, 2024
7fd5160
feat: use app state to store request workspace id
SuZhou-Joe Apr 3, 2024
9d72524
feat: remove workspaces when listing data sources
SuZhou-Joe Apr 3, 2024
be08979
temp: merge workspace-pr-intepr
SuZhou-Joe Apr 10, 2024
f65ffcf
feat: remove useless code change
SuZhou-Joe Apr 10, 2024
57ddd07
feat: throw error if the type is not allowed
SuZhou-Joe Apr 10, 2024
d5b8676
feat: add unit test
SuZhou-Joe Apr 10, 2024
395f6b0
feat: add integration test
SuZhou-Joe Apr 10, 2024
0fe0d37
feat: change the implementation
SuZhou-Joe Apr 15, 2024
ede24be
Merge branch 'workspace-pr-integr' into feature/list-data-source-with…
SuZhou-Joe Apr 15, 2024
5f487bc
feat: remove useless change
SuZhou-Joe Apr 15, 2024
4cfdfb0
feat: remove useless change
SuZhou-Joe Apr 15, 2024
2119304
feat: add integration test
SuZhou-Joe Apr 15, 2024
b1494b1
fix: unit test
SuZhou-Joe Apr 15, 2024
4097e94
Merge branch 'workspace-pr-integr' into feature/list-data-source-with…
SuZhou-Joe Apr 16, 2024
e61910d
feat: add error message
SuZhou-Joe Apr 16, 2024
49c771b
fix: integration test
SuZhou-Joe Apr 17, 2024
402d69b
fix: integration test
SuZhou-Joe Apr 17, 2024
cd77dba
feat: remove useless change
SuZhou-Joe Apr 17, 2024
1522637
feat: add test case and add restrict on create method
SuZhou-Joe Apr 17, 2024
21f73c6
feat: change type
SuZhou-Joe Apr 17, 2024
544083e
feat: change comment
SuZhou-Joe Apr 17, 2024
093de9e
feat: optimize test
SuZhou-Joe Apr 17, 2024
0c80e82
refactor: move logic to conflict check wrapper
SuZhou-Joe Apr 17, 2024
804e50f
feat: remove useless change
SuZhou-Joe Apr 17, 2024
0810ec5
fix: unit test
SuZhou-Joe Apr 17, 2024
39bb304
fix: unit test
SuZhou-Joe Apr 17, 2024
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
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,30 @@

import { SavedObject } from 'src/core/types';
import { isEqual } from 'lodash';
import packageInfo from '../../../../../../package.json';
import * as osdTestServer from '../../../../../core/test_helpers/osd_server';
import { DATA_SOURCE_SAVED_OBJECT_TYPE } from '../../../../data_source/common';

const dashboard: Omit<SavedObject, 'id'> = {
type: 'dashboard',
attributes: {},
references: [],
};

const dataSource: Omit<SavedObject, 'id'> = {
type: DATA_SOURCE_SAVED_OBJECT_TYPE,
attributes: {
title: 'test data source',
},
references: [],
};

const advancedSettings: Omit<SavedObject, 'id'> = {
type: 'config',
attributes: {},
references: [],
};

interface WorkspaceAttributes {
id: string;
name?: string;
Expand All @@ -32,6 +48,9 @@ describe('workspace_id_consumer integration test', () => {
adjustTimeout: (t: number) => jest.setTimeout(t),
settings: {
osd: {
data_source: {
enabled: true,
},
workspace: {
enabled: true,
},
Expand Down Expand Up @@ -110,6 +129,38 @@ describe('workspace_id_consumer integration test', () => {
});
});

it('create disallowed types within workspace', async () => {
const createDataSourceResult = await osdTestServer.request
.post(root, `/w/${createdFooWorkspace.id}/api/saved_objects/${dataSource.type}`)
.send({
attributes: dataSource.attributes,
})
.expect(400);

expect(createDataSourceResult.body).toMatchInlineSnapshot(`
Object {
"error": "Bad Request",
"message": "Unsupport type in workspace: 'data-source' is not allowed to create in workspace.",
"statusCode": 400,
}
`);

const createConfigResult = await osdTestServer.request
.post(root, `/w/${createdFooWorkspace.id}/api/saved_objects/config`)
.send({
attributes: dataSource.attributes,
SuZhou-Joe marked this conversation as resolved.
Show resolved Hide resolved
})
.expect(400);

expect(createConfigResult.body).toMatchInlineSnapshot(`
Object {
"error": "Bad Request",
"message": "Unsupport type in workspace: 'config' is not allowed to create in workspace.",
"statusCode": 400,
}
`);
});

it('bulk create', async () => {
await clearFooAndBar();
const createResultFoo = await osdTestServer.request
Expand Down Expand Up @@ -140,6 +191,79 @@ describe('workspace_id_consumer integration test', () => {
);
});

it('bulk create with disallowed types in workspace', async () => {
await clearFooAndBar();

// import advanced settings and data sources should throw error
const createResultFoo = await osdTestServer.request
.post(root, `/w/${createdFooWorkspace.id}/api/saved_objects/_bulk_create`)
.send([
{
...dataSource,
id: 'foo',
},
{
...advancedSettings,
id: packageInfo.version,
},
])
.expect(200);
expect(createResultFoo.body.saved_objects[0].error).toEqual(
expect.objectContaining({
message:
"Unsupport type in workspace: 'data-source' is not allowed to import in workspace.",
statusCode: 400,
})
);
expect(createResultFoo.body.saved_objects[1].error).toEqual(
expect.objectContaining({
message: "Unsupport type in workspace: 'config' is not allowed to import in workspace.",
statusCode: 400,
})
);

// Data source should not be created
await osdTestServer.request
.get(
root,
`/w/${createdFooWorkspace.id}/api/saved_objects/${DATA_SOURCE_SAVED_OBJECT_TYPE}/foo`
)
.expect(404);

// Advanced settings should not be created within workspace
const findAdvancedSettings = await osdTestServer.request
.get(root, `/w/${createdFooWorkspace.id}/api/saved_objects/_find?type=config`)
.expect(200);
expect(findAdvancedSettings.body.total).toEqual(0);
});

it('bulk create with disallowed types out of workspace', async () => {
await clearFooAndBar();

// import advanced settings and data sources should throw error
const createResultFoo = await osdTestServer.request
.post(root, `/api/saved_objects/_bulk_create`)
.send([
{
...advancedSettings,
id: packageInfo.version,
},
])
.expect(200);
expect(createResultFoo.body).toEqual({
saved_objects: [
expect.objectContaining({
type: advancedSettings.type,
}),
],
});

const findAdvancedSettings = await osdTestServer.request
.get(root, `/api/saved_objects/_find?type=${advancedSettings.type}`)
.expect(200);
expect(findAdvancedSettings.body.total).toEqual(1);
});

it('checkConflicts when importing ndjson', async () => {
await clearFooAndBar();
const createResultFoo = await osdTestServer.request
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { updateWorkspaceState } from '../../../../core/server/utils';
import { SavedObject } from '../../../../core/public';
import { httpServerMock, savedObjectsClientMock, coreMock } from '../../../../core/server/mocks';
import { WorkspaceIdConsumerWrapper } from './workspace_id_consumer_wrapper';
import { DATA_SOURCE_SAVED_OBJECT_TYPE } from '../../../../plugins/data_source/common';

describe('WorkspaceIdConsumerWrapper', () => {
const requestHandlerContext = coreMock.createRequestHandlerContext();
Expand Down Expand Up @@ -65,6 +66,38 @@ describe('WorkspaceIdConsumerWrapper', () => {

expect(mockedClient.create.mock.calls[0][2]?.hasOwnProperty('workspaces')).toEqual(false);
});

it(`Should throw error when trying to create disallowed type in workspace`, async () => {
expect(() =>
wrapperClient.create(
DATA_SOURCE_SAVED_OBJECT_TYPE,
{
name: 'foo',
},

{
workspaces: ['foo'],
}
)
).toThrowErrorMatchingInlineSnapshot(
`"Unsupport type in workspace: 'data-source' is not allowed to create in workspace."`
);

expect(() =>
wrapperClient.create(
'config',
{
name: 'foo',
},

{
workspaces: ['foo'],
}
)
).toThrowErrorMatchingInlineSnapshot(
`"Unsupport type in workspace: 'config' is not allowed to create in workspace."`
);
});
});

describe('bulkCreate', () => {
Expand All @@ -85,6 +118,37 @@ describe('WorkspaceIdConsumerWrapper', () => {
}
);
});

it(`Should return error when trying to create unallowed type within a workspace`, async () => {
SuZhou-Joe marked this conversation as resolved.
Show resolved Hide resolved
mockedClient.bulkCreate.mockResolvedValueOnce({ saved_objects: [] });
const result = await wrapperClient.bulkCreate([
getSavedObject({
type: 'config',
id: 'foo',
}),
getSavedObject({
type: DATA_SOURCE_SAVED_OBJECT_TYPE,
id: 'foo',
}),
]);

expect(mockedClient.bulkCreate).toBeCalledWith([], {
workspaces: ['foo'],
});
expect(result.saved_objects[0].error).toEqual(
expect.objectContaining({
message: "Unsupport type in workspace: 'config' is not allowed to import in workspace.",
statusCode: 400,
})
);
expect(result.saved_objects[1].error).toEqual(
expect.objectContaining({
message:
"Unsupport type in workspace: 'data-source' is not allowed to import in workspace.",
statusCode: 400,
})
);
});
});

describe('checkConflict', () => {
Expand Down Expand Up @@ -114,5 +178,15 @@ describe('WorkspaceIdConsumerWrapper', () => {
workspaces: ['foo'],
});
});

it(`workspaces parameters should be removed when finding data sources`, async () => {
await wrapperClient.find({
type: DATA_SOURCE_SAVED_OBJECT_TYPE,
workspaces: ['foo'],
});
expect(mockedClient.find).toBeCalledWith({
type: DATA_SOURCE_SAVED_OBJECT_TYPE,
});
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,15 @@ import {
SavedObjectsCheckConflictsObject,
OpenSearchDashboardsRequest,
SavedObjectsFindOptions,
SavedObjectsErrorHelpers,
SavedObject,
} from '../../../../core/server';
import { DATA_SOURCE_SAVED_OBJECT_TYPE } from '../../../../plugins/data_source/common';

type WorkspaceOptions = Pick<SavedObjectsBaseOptions, 'workspaces'> | undefined;

const UI_SETTINGS_SAVED_OBJECTS_TYPE = 'config';

export class WorkspaceIdConsumerWrapper {
private formatWorkspaceIdParams<T extends WorkspaceOptions>(
request: OpenSearchDashboardsRequest,
Expand All @@ -37,23 +42,95 @@ export class WorkspaceIdConsumerWrapper {
...(finalWorkspaces.length ? { workspaces: finalWorkspaces } : {}),
};
}
private isDataSourceType(type: SavedObjectsFindOptions['type']): boolean {
raintygao marked this conversation as resolved.
Show resolved Hide resolved
if (Array.isArray(type)) {
return type.every((item) => item === DATA_SOURCE_SAVED_OBJECT_TYPE);
}

return type === DATA_SOURCE_SAVED_OBJECT_TYPE;
}
private isConfigType(type: SavedObject['type']): boolean {
return type === UI_SETTINGS_SAVED_OBJECTS_TYPE;
}
private formatFindParams(options: SavedObjectsFindOptions): SavedObjectsFindOptions {
const isListingDataSource = this.isDataSourceType(options.type);
return isListingDataSource ? { ...options, workspaces: null } : options;
}
public wrapperFactory: SavedObjectsClientWrapperFactory = (wrapperOptions) => {
return {
...wrapperOptions.client,
create: <T>(type: string, attributes: T, options: SavedObjectsCreateOptions = {}) =>
wrapperOptions.client.create(
create: <T>(type: string, attributes: T, options: SavedObjectsCreateOptions = {}) => {
const { workspaces } = this.formatWorkspaceIdParams(wrapperOptions.request, options);
if (workspaces?.length && (this.isDataSourceType(type) || this.isConfigType(type))) {
// For 2.14, data source can only be created without workspace info
// config can not be created inside a workspace
throw SavedObjectsErrorHelpers.decorateBadRequestError(
new Error(`'${type}' is not allowed to create in workspace.`),
'Unsupport type in workspace'
);
}

return wrapperOptions.client.create(
type,
attributes,
this.formatWorkspaceIdParams(wrapperOptions.request, options)
),
bulkCreate: <T = unknown>(
);
},
bulkCreate: async <T = unknown>(
wanglam marked this conversation as resolved.
Show resolved Hide resolved
objects: Array<SavedObjectsBulkCreateObject<T>>,
options: SavedObjectsCreateOptions = {}
) =>
wrapperOptions.client.bulkCreate(
objects,
) => {
const { workspaces } = this.formatWorkspaceIdParams(wrapperOptions.request, options);
const disallowedSavedObjects: Array<SavedObjectsBulkCreateObject<T>> = [];
const allowedSavedObjects: Array<SavedObjectsBulkCreateObject<T>> = [];
objects.forEach((item) => {
const isImportIntoWorkspace = workspaces?.length || item.workspaces?.length;
// config can not be created inside a workspace
if (this.isConfigType(item.type) && isImportIntoWorkspace) {
disallowedSavedObjects.push(item);
return;
}

// For 2.14, data source can only be created without workspace info
if (this.isDataSourceType(item.type) && isImportIntoWorkspace) {
disallowedSavedObjects.push(item);
return;
}

allowedSavedObjects.push(item);
return;
});

if (!disallowedSavedObjects.length) {
return await wrapperOptions.client.bulkCreate(
objects,
this.formatWorkspaceIdParams(wrapperOptions.request, options)
);
}

const allowedSavedObjectsBulkCreateResult = await wrapperOptions.client.bulkCreate(
allowedSavedObjects,
this.formatWorkspaceIdParams(wrapperOptions.request, options)
),
);

return {
saved_objects: [
...allowedSavedObjectsBulkCreateResult.saved_objects,
...disallowedSavedObjects.map((item) => ({
references: [],
id: '',
...item,
error: {
...SavedObjectsErrorHelpers.decorateBadRequestError(
new Error(`'${item.type}' is not allowed to import in workspace.`),
'Unsupport type in workspace'
SuZhou-Joe marked this conversation as resolved.
Show resolved Hide resolved
).output.payload,
metadata: { isNotOverwritable: true },
},
})),
],
};
},
checkConflicts: (
objects: SavedObjectsCheckConflictsObject[] = [],
options: SavedObjectsBaseOptions = {}
Expand All @@ -64,7 +141,14 @@ export class WorkspaceIdConsumerWrapper {
),
delete: wrapperOptions.client.delete,
find: (options: SavedObjectsFindOptions) =>
wrapperOptions.client.find(this.formatWorkspaceIdParams(wrapperOptions.request, options)),
wrapperOptions.client.find(
this.formatWorkspaceIdParams(
wrapperOptions.request,
// The `formatFindParams` is a workaroud for 2.14 to always list global data sources,
SuZhou-Joe marked this conversation as resolved.
Show resolved Hide resolved
// should remove this workaround in 2.15 once readonly share is available
SuZhou-Joe marked this conversation as resolved.
Show resolved Hide resolved
this.formatFindParams(options)
)
),
bulkGet: wrapperOptions.client.bulkGet,
get: wrapperOptions.client.get,
update: wrapperOptions.client.update,
Expand Down
Loading