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 all 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('saved_objects_wrapper_for_check_workspace_conflict integration test',
adjustTimeout: (t: number) => jest.setTimeout(t),
settings: {
osd: {
data_source: {
enabled: true,
},
workspace: {
enabled: true,
},
Expand Down Expand Up @@ -150,6 +169,40 @@ describe('saved_objects_wrapper_for_check_workspace_conflict integration test',
});
});

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

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

const createConfigResult = await osdTestServer.request
.post(root, `/api/saved_objects/config`)
.send({
attributes: advancedSettings.attributes,
workspaces: [createdFooWorkspace.id],
})
.expect(400);

expect(createConfigResult.body).toMatchInlineSnapshot(`
Object {
"error": "Bad Request",
"message": "Unsupported 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 @@ -259,6 +312,79 @@ describe('saved_objects_wrapper_for_check_workspace_conflict 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:
"Unsupported 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: "Unsupported 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 { SavedObject } from '../../../../core/public';
import { httpServerMock, savedObjectsClientMock, coreMock } from '../../../../core/server/mocks';
import { WorkspaceConflictSavedObjectsClientWrapper } from './saved_objects_wrapper_for_check_workspace_conflict';
import { SavedObjectsSerializer } from '../../../../core/server';
import { DATA_SOURCE_SAVED_OBJECT_TYPE } from '../../../../plugins/data_source/common';

describe('WorkspaceConflictSavedObjectsClientWrapper', () => {
const requestHandlerContext = coreMock.createRequestHandlerContext();
Expand Down Expand Up @@ -115,6 +116,38 @@ describe('WorkspaceConflictSavedObjectsClientWrapper', () => {
})
);
});

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

{
workspaces: ['foo'],
}
)
).rejects.toMatchInlineSnapshot(
`[Error: Unsupported type in workspace: 'data-source' is not allowed to create in workspace.]`
);

await expect(
wrapperClient.create(
'config',
{
name: 'foo',
},

{
workspaces: ['foo'],
}
)
).rejects.toMatchInlineSnapshot(
`[Error: Unsupported type in workspace: 'config' is not allowed to create in workspace.]`
);
});
});

describe('bulkCreateWithWorkspaceConflictCheck', () => {
Expand Down Expand Up @@ -291,6 +324,41 @@ describe('WorkspaceConflictSavedObjectsClientWrapper', () => {
}
`);
});
it(`Should return error when trying to create disallowed types within a workspace`, async () => {
mockedClient.bulkCreate.mockResolvedValueOnce({ saved_objects: [] });
const result = await wrapperClient.bulkCreate(
[
getSavedObject({
type: 'config',
id: 'foo',
}),
getSavedObject({
type: DATA_SOURCE_SAVED_OBJECT_TYPE,
id: 'foo',
}),
],
{
workspaces: ['foo'],
}
);

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

describe('checkConflictWithWorkspaceConflictCheck', () => {
Expand Down Expand Up @@ -393,4 +461,21 @@ describe('WorkspaceConflictSavedObjectsClientWrapper', () => {
`);
});
});

describe('find', () => {
beforeEach(() => {
mockedClient.find.mockClear();
});

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,
workspaces: null,
});
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,11 @@ import {
SavedObjectsSerializer,
SavedObjectsCheckConflictsObject,
SavedObjectsCheckConflictsResponse,
SavedObjectsFindOptions,
} from '../../../../core/server';
import { DATA_SOURCE_SAVED_OBJECT_TYPE } from '../../../data_source/common';

const UI_SETTINGS_SAVED_OBJECTS_TYPE = 'config';

const errorContent = (error: Boom.Boom) => error.output.payload;

Expand All @@ -36,6 +40,21 @@ export class WorkspaceConflictSavedObjectsClientWrapper {
);
}

private isDataSourceType(type: SavedObjectsFindOptions['type']): boolean {
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;
}

/**
* Workspace is a concept to manage saved objects and the `workspaces` field of each object indicates workspaces the object belongs to.
* When user tries to update an existing object's attribute, workspaces field should be preserved. Below are some cases that this conflict wrapper will take effect:
Expand All @@ -49,6 +68,16 @@ export class WorkspaceConflictSavedObjectsClientWrapper {
options: SavedObjectsCreateOptions = {}
) => {
const { workspaces, id, overwrite } = 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.`),
'Unsupported type in workspace'
);
}

let savedObjectWorkspaces = options?.workspaces;

/**
Expand Down Expand Up @@ -89,12 +118,33 @@ export class WorkspaceConflictSavedObjectsClientWrapper {
objects: Array<SavedObjectsBulkCreateObject<T>>,
options: SavedObjectsCreateOptions = {}
): Promise<SavedObjectsBulkResponse<T>> => {
const { overwrite, namespace } = options;
const { overwrite, namespace, workspaces } = 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;
});

/**
* When overwrite, filter out all the objects that have ids
*/
const bulkGetDocs = overwrite
? objects
? allowedSavedObjects
.filter((object) => !!object.id)
.map((object) => {
/**
Expand Down Expand Up @@ -169,7 +219,7 @@ export class WorkspaceConflictSavedObjectsClientWrapper {
/**
* Get all the objects that do not conflict on workspaces
*/
const objectsNoWorkspaceConflictError = objects.filter(
const objectsNoWorkspaceConflictError = allowedSavedObjects.filter(
(item) =>
!objectsConflictWithWorkspace.find(
(errorItems) =>
Expand Down Expand Up @@ -211,6 +261,18 @@ export class WorkspaceConflictSavedObjectsClientWrapper {
...realBulkCreateResult,
saved_objects: [
...objectsConflictWithWorkspace,
...disallowedSavedObjects.map((item) => ({
references: [],
id: '',
...item,
error: {
...SavedObjectsErrorHelpers.decorateBadRequestError(
new Error(`'${item.type}' is not allowed to import in workspace.`),
'Unsupported type in workspace'
).output.payload,
metadata: { isNotOverwritable: true },
},
})),
...(realBulkCreateResult?.saved_objects || []),
],
} as SavedObjectsBulkResponse<T>;
Expand Down Expand Up @@ -316,7 +378,10 @@ export class WorkspaceConflictSavedObjectsClientWrapper {
bulkCreate: bulkCreateWithWorkspaceConflictCheck,
checkConflicts: checkConflictWithWorkspaceConflictCheck,
delete: wrapperOptions.client.delete,
find: wrapperOptions.client.find,
find: (options: SavedObjectsFindOptions) =>
// TODO: The `formatFindParams` is a workaround for 2.14 to always list global data sources,
// should remove this workaround in the upcoming release once readonly share is available.
wrapperOptions.client.find(this.formatFindParams(options)),
bulkGet: wrapperOptions.client.bulkGet,
get: wrapperOptions.client.get,
update: wrapperOptions.client.update,
Expand Down
Loading