mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
Spaces - Migrate to NP Saved Objects Service (#58716)
* use NP saved objects service for type and wrapper registration * simplifying * additional testing * revert snapshot changes * removing dependency on legacy saved objects service * consolidate mocks * fixing imports * addrress PR feedback * remove unused docs * adjust tests for updated corestart contract * address test flakiness * address flakiness, part 2 * address test flakiness Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
This commit is contained in:
parent
678d2206c6
commit
37c826229b
48 changed files with 953 additions and 678 deletions
|
@ -12,9 +12,7 @@ import { SpacesServiceSetup } from '../../../plugins/spaces/server';
|
|||
import { SpacesPluginSetup } from '../../../plugins/spaces/server';
|
||||
// @ts-ignore
|
||||
import { AuditLogger } from '../../server/lib/audit_logger';
|
||||
import mappings from './mappings.json';
|
||||
import { wrapError } from './server/lib/errors';
|
||||
import { migrateToKibana660 } from './server/lib/migrations';
|
||||
// @ts-ignore
|
||||
import { watchStatusAndLicenseToInitialize } from '../../server/lib/watch_status_and_license_to_initialize';
|
||||
import { initEnterSpaceView } from './server/routes/views';
|
||||
|
@ -39,18 +37,6 @@ export const spaces = (kibana: Record<string, any>) =>
|
|||
managementSections: [],
|
||||
apps: [],
|
||||
hacks: ['plugins/spaces/legacy'],
|
||||
mappings,
|
||||
migrations: {
|
||||
space: {
|
||||
'6.6.0': migrateToKibana660,
|
||||
},
|
||||
},
|
||||
savedObjectSchemas: {
|
||||
space: {
|
||||
isNamespaceAgnostic: true,
|
||||
hidden: true,
|
||||
},
|
||||
},
|
||||
home: [],
|
||||
injectDefaultVars(server: Server) {
|
||||
return {
|
||||
|
@ -100,7 +86,6 @@ export const spaces = (kibana: Record<string, any>) =>
|
|||
const { registerLegacyAPI, createDefaultSpace } = spacesPlugin.__legacyCompat;
|
||||
|
||||
registerLegacyAPI({
|
||||
savedObjects: server.savedObjects,
|
||||
auditLogger: {
|
||||
create: (pluginId: string) =>
|
||||
new AuditLogger(server, pluginId, server.config(), server.plugins.xpack_main.info),
|
||||
|
|
|
@ -1,34 +0,0 @@
|
|||
{
|
||||
"space": {
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "text",
|
||||
"fields": {
|
||||
"keyword": {
|
||||
"type": "keyword",
|
||||
"ignore_above": 2048
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": {
|
||||
"type": "text"
|
||||
},
|
||||
"initials": {
|
||||
"type": "keyword"
|
||||
},
|
||||
"color": {
|
||||
"type": "keyword"
|
||||
},
|
||||
"disabledFeatures": {
|
||||
"type": "keyword"
|
||||
},
|
||||
"imageUrl": {
|
||||
"type": "text",
|
||||
"index": false
|
||||
},
|
||||
"_reserved": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,40 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { migrateToKibana660 } from './migrate_6x';
|
||||
|
||||
describe('migrateTo660', () => {
|
||||
it('adds a "disabledFeatures" attribute initialized as an empty array', () => {
|
||||
expect(
|
||||
migrateToKibana660({
|
||||
id: 'space:foo',
|
||||
attributes: {},
|
||||
})
|
||||
).toEqual({
|
||||
id: 'space:foo',
|
||||
attributes: {
|
||||
disabledFeatures: [],
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('does not initialize "disabledFeatures" if the property already exists', () => {
|
||||
// This scenario shouldn't happen organically. Protecting against defects in the migration.
|
||||
expect(
|
||||
migrateToKibana660({
|
||||
id: 'space:foo',
|
||||
attributes: {
|
||||
disabledFeatures: ['foo', 'bar', 'baz'],
|
||||
},
|
||||
})
|
||||
).toEqual({
|
||||
id: 'space:foo',
|
||||
attributes: {
|
||||
disabledFeatures: ['foo', 'bar', 'baz'],
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,5 +0,0 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`it throws all other errors from the saved objects client when checking for the default space 1`] = `"unit test: unexpected exception condition"`;
|
||||
|
||||
exports[`it throws other errors if there is an error creating the default space 1`] = `"unit test: some other unexpected error"`;
|
|
@ -4,20 +4,31 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
import {
|
||||
SavedObjectsSchema,
|
||||
SavedObjectsLegacyService,
|
||||
SavedObjectsClientContract,
|
||||
SavedObjectsImportResponse,
|
||||
SavedObjectsImportOptions,
|
||||
SavedObjectsExportOptions,
|
||||
} from 'src/core/server';
|
||||
import { copySavedObjectsToSpacesFactory } from './copy_to_spaces';
|
||||
import { Readable } from 'stream';
|
||||
import { coreMock, savedObjectsTypeRegistryMock, httpServerMock } from 'src/core/server/mocks';
|
||||
|
||||
jest.mock('../../../../../../src/core/server', () => {
|
||||
return {
|
||||
exportSavedObjectsToStream: jest.fn(),
|
||||
importSavedObjectsFromStream: jest.fn(),
|
||||
};
|
||||
});
|
||||
import {
|
||||
exportSavedObjectsToStream,
|
||||
importSavedObjectsFromStream,
|
||||
} from '../../../../../../src/core/server';
|
||||
|
||||
interface SetupOpts {
|
||||
objects: Array<{ type: string; id: string; attributes: Record<string, any> }>;
|
||||
getSortedObjectsForExportImpl?: (opts: SavedObjectsExportOptions) => Promise<Readable>;
|
||||
importSavedObjectsImpl?: (opts: SavedObjectsImportOptions) => Promise<SavedObjectsImportResponse>;
|
||||
exportSavedObjectsToStreamImpl?: (opts: SavedObjectsExportOptions) => Promise<Readable>;
|
||||
importSavedObjectsFromStreamImpl?: (
|
||||
opts: SavedObjectsImportOptions
|
||||
) => Promise<SavedObjectsImportResponse>;
|
||||
}
|
||||
|
||||
const expectStreamToContainObjects = async (
|
||||
|
@ -40,49 +51,75 @@ const expectStreamToContainObjects = async (
|
|||
|
||||
describe('copySavedObjectsToSpaces', () => {
|
||||
const setup = (setupOpts: SetupOpts) => {
|
||||
const savedObjectsClient = (null as unknown) as SavedObjectsClientContract;
|
||||
const coreStart = coreMock.createStart();
|
||||
|
||||
const savedObjectsService: SavedObjectsLegacyService = ({
|
||||
importExport: {
|
||||
objectLimit: 1000,
|
||||
getSortedObjectsForExport:
|
||||
setupOpts.getSortedObjectsForExportImpl ||
|
||||
jest.fn().mockResolvedValue(
|
||||
new Readable({
|
||||
objectMode: true,
|
||||
read() {
|
||||
setupOpts.objects.forEach(o => this.push(o));
|
||||
|
||||
this.push(null);
|
||||
},
|
||||
})
|
||||
),
|
||||
importSavedObjects:
|
||||
setupOpts.importSavedObjectsImpl ||
|
||||
jest.fn().mockImplementation(async (importOpts: SavedObjectsImportOptions) => {
|
||||
await expectStreamToContainObjects(importOpts.readStream, setupOpts.objects);
|
||||
const response: SavedObjectsImportResponse = {
|
||||
success: true,
|
||||
successCount: setupOpts.objects.length,
|
||||
};
|
||||
|
||||
return Promise.resolve(response);
|
||||
}),
|
||||
const typeRegistry = savedObjectsTypeRegistryMock.create();
|
||||
typeRegistry.getAllTypes.mockReturnValue([
|
||||
{
|
||||
name: 'dashboard',
|
||||
namespaceAgnostic: false,
|
||||
hidden: false,
|
||||
mappings: { properties: {} },
|
||||
},
|
||||
types: ['dashboard', 'visualization', 'globalType'],
|
||||
schema: new SavedObjectsSchema({
|
||||
globalType: { isNamespaceAgnostic: true },
|
||||
}),
|
||||
} as unknown) as SavedObjectsLegacyService;
|
||||
{
|
||||
name: 'visualization',
|
||||
namespaceAgnostic: false,
|
||||
hidden: false,
|
||||
mappings: { properties: {} },
|
||||
},
|
||||
{
|
||||
name: 'globaltype',
|
||||
namespaceAgnostic: true,
|
||||
hidden: false,
|
||||
mappings: { properties: {} },
|
||||
},
|
||||
]);
|
||||
|
||||
typeRegistry.isNamespaceAgnostic.mockImplementation((type: string) =>
|
||||
typeRegistry.getAllTypes().some(t => t.name === type && t.namespaceAgnostic)
|
||||
);
|
||||
|
||||
coreStart.savedObjects.getTypeRegistry.mockReturnValue(typeRegistry);
|
||||
|
||||
(exportSavedObjectsToStream as jest.Mock).mockImplementation(
|
||||
async (opts: SavedObjectsExportOptions) => {
|
||||
return (
|
||||
setupOpts.exportSavedObjectsToStreamImpl?.(opts) ??
|
||||
new Readable({
|
||||
objectMode: true,
|
||||
read() {
|
||||
setupOpts.objects.forEach(o => this.push(o));
|
||||
|
||||
this.push(null);
|
||||
},
|
||||
})
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
(importSavedObjectsFromStream as jest.Mock).mockImplementation(
|
||||
async (opts: SavedObjectsImportOptions) => {
|
||||
const defaultImpl = async () => {
|
||||
await expectStreamToContainObjects(opts.readStream, setupOpts.objects);
|
||||
const response: SavedObjectsImportResponse = {
|
||||
success: true,
|
||||
successCount: setupOpts.objects.length,
|
||||
};
|
||||
|
||||
return Promise.resolve(response);
|
||||
};
|
||||
|
||||
return setupOpts.importSavedObjectsFromStreamImpl?.(opts) ?? defaultImpl();
|
||||
}
|
||||
);
|
||||
|
||||
return {
|
||||
savedObjectsClient,
|
||||
savedObjectsService,
|
||||
savedObjects: coreStart.savedObjects,
|
||||
};
|
||||
};
|
||||
|
||||
it('uses the Saved Objects Service to perform an export followed by a series of imports', async () => {
|
||||
const { savedObjectsClient, savedObjectsService } = setup({
|
||||
const { savedObjects } = setup({
|
||||
objects: [
|
||||
{
|
||||
type: 'dashboard',
|
||||
|
@ -102,9 +139,12 @@ describe('copySavedObjectsToSpaces', () => {
|
|||
],
|
||||
});
|
||||
|
||||
const request = httpServerMock.createKibanaRequest();
|
||||
|
||||
const copySavedObjectsToSpaces = copySavedObjectsToSpacesFactory(
|
||||
savedObjectsClient,
|
||||
savedObjectsService
|
||||
savedObjects,
|
||||
() => 1000,
|
||||
request
|
||||
);
|
||||
|
||||
const result = await copySavedObjectsToSpaces('sourceSpace', ['destination1', 'destination2'], {
|
||||
|
@ -133,8 +173,7 @@ describe('copySavedObjectsToSpaces', () => {
|
|||
}
|
||||
`);
|
||||
|
||||
expect((savedObjectsService.importExport.getSortedObjectsForExport as jest.Mock).mock.calls)
|
||||
.toMatchInlineSnapshot(`
|
||||
expect((exportSavedObjectsToStream as jest.Mock).mock.calls).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
Array [
|
||||
Object {
|
||||
|
@ -148,14 +187,23 @@ describe('copySavedObjectsToSpaces', () => {
|
|||
"type": "dashboard",
|
||||
},
|
||||
],
|
||||
"savedObjectsClient": null,
|
||||
"savedObjectsClient": Object {
|
||||
"bulkCreate": [MockFunction],
|
||||
"bulkGet": [MockFunction],
|
||||
"bulkUpdate": [MockFunction],
|
||||
"create": [MockFunction],
|
||||
"delete": [MockFunction],
|
||||
"errors": [Function],
|
||||
"find": [MockFunction],
|
||||
"get": [MockFunction],
|
||||
"update": [MockFunction],
|
||||
},
|
||||
},
|
||||
],
|
||||
]
|
||||
`);
|
||||
|
||||
expect((savedObjectsService.importExport.importSavedObjects as jest.Mock).mock.calls)
|
||||
.toMatchInlineSnapshot(`
|
||||
expect((importSavedObjectsFromStream as jest.Mock).mock.calls).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
Array [
|
||||
Object {
|
||||
|
@ -203,7 +251,17 @@ describe('copySavedObjectsToSpaces', () => {
|
|||
},
|
||||
"readable": false,
|
||||
},
|
||||
"savedObjectsClient": null,
|
||||
"savedObjectsClient": Object {
|
||||
"bulkCreate": [MockFunction],
|
||||
"bulkGet": [MockFunction],
|
||||
"bulkUpdate": [MockFunction],
|
||||
"create": [MockFunction],
|
||||
"delete": [MockFunction],
|
||||
"errors": [Function],
|
||||
"find": [MockFunction],
|
||||
"get": [MockFunction],
|
||||
"update": [MockFunction],
|
||||
},
|
||||
"supportedTypes": Array [
|
||||
"dashboard",
|
||||
"visualization",
|
||||
|
@ -256,7 +314,17 @@ describe('copySavedObjectsToSpaces', () => {
|
|||
},
|
||||
"readable": false,
|
||||
},
|
||||
"savedObjectsClient": null,
|
||||
"savedObjectsClient": Object {
|
||||
"bulkCreate": [MockFunction],
|
||||
"bulkGet": [MockFunction],
|
||||
"bulkUpdate": [MockFunction],
|
||||
"create": [MockFunction],
|
||||
"delete": [MockFunction],
|
||||
"errors": [Function],
|
||||
"find": [MockFunction],
|
||||
"get": [MockFunction],
|
||||
"update": [MockFunction],
|
||||
},
|
||||
"supportedTypes": Array [
|
||||
"dashboard",
|
||||
"visualization",
|
||||
|
@ -285,9 +353,10 @@ describe('copySavedObjectsToSpaces', () => {
|
|||
attributes: {},
|
||||
},
|
||||
];
|
||||
const { savedObjectsClient, savedObjectsService } = setup({
|
||||
|
||||
const { savedObjects } = setup({
|
||||
objects,
|
||||
importSavedObjectsImpl: async opts => {
|
||||
importSavedObjectsFromStreamImpl: async opts => {
|
||||
if (opts.namespace === 'failure-space') {
|
||||
throw new Error(`Some error occurred!`);
|
||||
}
|
||||
|
@ -299,9 +368,12 @@ describe('copySavedObjectsToSpaces', () => {
|
|||
},
|
||||
});
|
||||
|
||||
const request = httpServerMock.createKibanaRequest();
|
||||
|
||||
const copySavedObjectsToSpaces = copySavedObjectsToSpacesFactory(
|
||||
savedObjectsClient,
|
||||
savedObjectsService
|
||||
savedObjects,
|
||||
() => 1000,
|
||||
request
|
||||
);
|
||||
|
||||
const result = await copySavedObjectsToSpaces(
|
||||
|
@ -343,7 +415,7 @@ describe('copySavedObjectsToSpaces', () => {
|
|||
});
|
||||
|
||||
it(`handles stream read errors`, async () => {
|
||||
const { savedObjectsClient, savedObjectsService } = setup({
|
||||
const { savedObjects } = setup({
|
||||
objects: [
|
||||
{
|
||||
type: 'dashboard',
|
||||
|
@ -361,7 +433,7 @@ describe('copySavedObjectsToSpaces', () => {
|
|||
attributes: {},
|
||||
},
|
||||
],
|
||||
getSortedObjectsForExportImpl: opts => {
|
||||
exportSavedObjectsToStreamImpl: opts => {
|
||||
return Promise.resolve(
|
||||
new Readable({
|
||||
objectMode: true,
|
||||
|
@ -373,9 +445,12 @@ describe('copySavedObjectsToSpaces', () => {
|
|||
},
|
||||
});
|
||||
|
||||
const request = httpServerMock.createKibanaRequest();
|
||||
|
||||
const copySavedObjectsToSpaces = copySavedObjectsToSpacesFactory(
|
||||
savedObjectsClient,
|
||||
savedObjectsService
|
||||
savedObjects,
|
||||
() => 1000,
|
||||
request
|
||||
);
|
||||
|
||||
await expect(
|
||||
|
|
|
@ -4,42 +4,42 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import {
|
||||
SavedObjectsClientContract,
|
||||
SavedObjectsLegacyService,
|
||||
SavedObject,
|
||||
} from 'src/core/server';
|
||||
import { SavedObject, KibanaRequest, CoreStart } from 'src/core/server';
|
||||
import { Readable } from 'stream';
|
||||
import { SavedObjectsClientProviderOptions } from 'src/core/server';
|
||||
import {
|
||||
exportSavedObjectsToStream,
|
||||
importSavedObjectsFromStream,
|
||||
} from '../../../../../../src/core/server';
|
||||
import { spaceIdToNamespace } from '../utils/namespace';
|
||||
import { CopyOptions, CopyResponse } from './types';
|
||||
import { getEligibleTypes } from './lib/get_eligible_types';
|
||||
import { createReadableStreamFromArray } from './lib/readable_stream_from_array';
|
||||
import { createEmptyFailureResponse } from './lib/create_empty_failure_response';
|
||||
import { readStreamToCompletion } from './lib/read_stream_to_completion';
|
||||
|
||||
export const COPY_TO_SPACES_SAVED_OBJECTS_CLIENT_OPTS: SavedObjectsClientProviderOptions = {
|
||||
excludedWrappers: ['spaces'],
|
||||
};
|
||||
import { COPY_TO_SPACES_SAVED_OBJECTS_CLIENT_OPTS } from './lib/saved_objects_client_opts';
|
||||
|
||||
export function copySavedObjectsToSpacesFactory(
|
||||
savedObjectsClient: SavedObjectsClientContract,
|
||||
savedObjectsService: SavedObjectsLegacyService
|
||||
savedObjects: CoreStart['savedObjects'],
|
||||
getImportExportObjectLimit: () => number,
|
||||
request: KibanaRequest
|
||||
) {
|
||||
const { importExport, types, schema } = savedObjectsService;
|
||||
const eligibleTypes = getEligibleTypes({ types, schema });
|
||||
const { getTypeRegistry, getScopedClient } = savedObjects;
|
||||
|
||||
const savedObjectsClient = getScopedClient(request, COPY_TO_SPACES_SAVED_OBJECTS_CLIENT_OPTS);
|
||||
|
||||
const eligibleTypes = getEligibleTypes(getTypeRegistry());
|
||||
|
||||
const exportRequestedObjects = async (
|
||||
sourceSpaceId: string,
|
||||
options: Pick<CopyOptions, 'includeReferences' | 'objects'>
|
||||
) => {
|
||||
const objectStream = await importExport.getSortedObjectsForExport({
|
||||
const objectStream = await exportSavedObjectsToStream({
|
||||
namespace: spaceIdToNamespace(sourceSpaceId),
|
||||
includeReferencesDeep: options.includeReferences,
|
||||
excludeExportDetails: true,
|
||||
objects: options.objects,
|
||||
savedObjectsClient,
|
||||
exportSizeLimit: importExport.objectLimit,
|
||||
exportSizeLimit: getImportExportObjectLimit(),
|
||||
});
|
||||
|
||||
return readStreamToCompletion<SavedObject>(objectStream);
|
||||
|
@ -51,9 +51,9 @@ export function copySavedObjectsToSpacesFactory(
|
|||
options: CopyOptions
|
||||
) => {
|
||||
try {
|
||||
const importResponse = await importExport.importSavedObjects({
|
||||
const importResponse = await importSavedObjectsFromStream({
|
||||
namespace: spaceIdToNamespace(spaceId),
|
||||
objectLimit: importExport.objectLimit,
|
||||
objectLimit: getImportExportObjectLimit(),
|
||||
overwrite: options.overwrite,
|
||||
savedObjectsClient,
|
||||
supportedTypes: eligibleTypes,
|
||||
|
|
|
@ -4,11 +4,13 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { SavedObjectsLegacyService } from 'src/core/server';
|
||||
import { SavedObjectTypeRegistry } from 'src/core/server';
|
||||
|
||||
export function getEligibleTypes({
|
||||
types,
|
||||
schema,
|
||||
}: Pick<SavedObjectsLegacyService, 'schema' | 'types'>) {
|
||||
return types.filter(type => !schema.isNamespaceAgnostic(type));
|
||||
export function getEligibleTypes(
|
||||
typeRegistry: Pick<SavedObjectTypeRegistry, 'getAllTypes' | 'isNamespaceAgnostic'>
|
||||
) {
|
||||
return typeRegistry
|
||||
.getAllTypes()
|
||||
.filter(type => !typeRegistry.isNamespaceAgnostic(type.name))
|
||||
.map(type => type.name);
|
||||
}
|
||||
|
|
|
@ -4,9 +4,8 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
export function migrateToKibana660(doc: Record<string, any>) {
|
||||
if (!doc.attributes.hasOwnProperty('disabledFeatures')) {
|
||||
doc.attributes.disabledFeatures = [];
|
||||
}
|
||||
return doc;
|
||||
}
|
||||
import { SavedObjectsClientProviderOptions } from 'src/core/server';
|
||||
|
||||
export const COPY_TO_SPACES_SAVED_OBJECTS_CLIENT_OPTS: SavedObjectsClientProviderOptions = {
|
||||
excludedWrappers: ['spaces'],
|
||||
};
|
|
@ -4,20 +4,29 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
import {
|
||||
SavedObjectsSchema,
|
||||
SavedObjectsLegacyService,
|
||||
SavedObjectsClientContract,
|
||||
SavedObjectsImportResponse,
|
||||
SavedObjectsResolveImportErrorsOptions,
|
||||
SavedObjectsExportOptions,
|
||||
} from 'src/core/server';
|
||||
import { coreMock, savedObjectsTypeRegistryMock, httpServerMock } from 'src/core/server/mocks';
|
||||
import { Readable } from 'stream';
|
||||
import { resolveCopySavedObjectsToSpacesConflictsFactory } from './resolve_copy_conflicts';
|
||||
|
||||
jest.mock('../../../../../../src/core/server', () => {
|
||||
return {
|
||||
exportSavedObjectsToStream: jest.fn(),
|
||||
resolveSavedObjectsImportErrors: jest.fn(),
|
||||
};
|
||||
});
|
||||
import {
|
||||
exportSavedObjectsToStream,
|
||||
resolveSavedObjectsImportErrors,
|
||||
} from '../../../../../../src/core/server';
|
||||
|
||||
interface SetupOpts {
|
||||
objects: Array<{ type: string; id: string; attributes: Record<string, any> }>;
|
||||
getSortedObjectsForExportImpl?: (opts: SavedObjectsExportOptions) => Promise<Readable>;
|
||||
resolveImportErrorsImpl?: (
|
||||
exportSavedObjectsToStreamImpl?: (opts: SavedObjectsExportOptions) => Promise<Readable>;
|
||||
resolveSavedObjectsImportErrorsImpl?: (
|
||||
opts: SavedObjectsResolveImportErrorsOptions
|
||||
) => Promise<SavedObjectsImportResponse>;
|
||||
}
|
||||
|
@ -42,52 +51,76 @@ const expectStreamToContainObjects = async (
|
|||
|
||||
describe('resolveCopySavedObjectsToSpacesConflicts', () => {
|
||||
const setup = (setupOpts: SetupOpts) => {
|
||||
const savedObjectsService: SavedObjectsLegacyService = ({
|
||||
importExport: {
|
||||
objectLimit: 1000,
|
||||
getSortedObjectsForExport:
|
||||
setupOpts.getSortedObjectsForExportImpl ||
|
||||
jest.fn().mockResolvedValue(
|
||||
new Readable({
|
||||
objectMode: true,
|
||||
read() {
|
||||
setupOpts.objects.forEach(o => this.push(o));
|
||||
const coreStart = coreMock.createStart();
|
||||
|
||||
this.push(null);
|
||||
},
|
||||
})
|
||||
),
|
||||
resolveImportErrors:
|
||||
setupOpts.resolveImportErrorsImpl ||
|
||||
jest
|
||||
.fn()
|
||||
.mockImplementation(async (resolveOpts: SavedObjectsResolveImportErrorsOptions) => {
|
||||
await expectStreamToContainObjects(resolveOpts.readStream, setupOpts.objects);
|
||||
|
||||
const response: SavedObjectsImportResponse = {
|
||||
success: true,
|
||||
successCount: setupOpts.objects.length,
|
||||
};
|
||||
|
||||
return response;
|
||||
}),
|
||||
const typeRegistry = savedObjectsTypeRegistryMock.create();
|
||||
typeRegistry.getAllTypes.mockReturnValue([
|
||||
{
|
||||
name: 'dashboard',
|
||||
namespaceAgnostic: false,
|
||||
hidden: false,
|
||||
mappings: { properties: {} },
|
||||
},
|
||||
types: ['dashboard', 'visualization', 'globalType'],
|
||||
schema: new SavedObjectsSchema({
|
||||
globalType: { isNamespaceAgnostic: true },
|
||||
}),
|
||||
} as unknown) as SavedObjectsLegacyService;
|
||||
{
|
||||
name: 'visualization',
|
||||
namespaceAgnostic: false,
|
||||
hidden: false,
|
||||
mappings: { properties: {} },
|
||||
},
|
||||
{
|
||||
name: 'globaltype',
|
||||
namespaceAgnostic: true,
|
||||
hidden: false,
|
||||
mappings: { properties: {} },
|
||||
},
|
||||
]);
|
||||
|
||||
const savedObjectsClient = (null as unknown) as SavedObjectsClientContract;
|
||||
typeRegistry.isNamespaceAgnostic.mockImplementation((type: string) =>
|
||||
typeRegistry.getAllTypes().some(t => t.name === type && t.namespaceAgnostic)
|
||||
);
|
||||
|
||||
coreStart.savedObjects.getTypeRegistry.mockReturnValue(typeRegistry);
|
||||
|
||||
(exportSavedObjectsToStream as jest.Mock).mockImplementation(
|
||||
async (opts: SavedObjectsExportOptions) => {
|
||||
return (
|
||||
setupOpts.exportSavedObjectsToStreamImpl?.(opts) ??
|
||||
new Readable({
|
||||
objectMode: true,
|
||||
read() {
|
||||
setupOpts.objects.forEach(o => this.push(o));
|
||||
|
||||
this.push(null);
|
||||
},
|
||||
})
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
(resolveSavedObjectsImportErrors as jest.Mock).mockImplementation(
|
||||
async (opts: SavedObjectsResolveImportErrorsOptions) => {
|
||||
const defaultImpl = async () => {
|
||||
await expectStreamToContainObjects(opts.readStream, setupOpts.objects);
|
||||
|
||||
const response: SavedObjectsImportResponse = {
|
||||
success: true,
|
||||
successCount: setupOpts.objects.length,
|
||||
};
|
||||
|
||||
return response;
|
||||
};
|
||||
|
||||
return setupOpts.resolveSavedObjectsImportErrorsImpl?.(opts) ?? defaultImpl();
|
||||
}
|
||||
);
|
||||
|
||||
return {
|
||||
savedObjectsClient,
|
||||
savedObjectsService,
|
||||
savedObjects: coreStart.savedObjects,
|
||||
};
|
||||
};
|
||||
|
||||
it('uses the Saved Objects Service to perform an export followed by a series of conflict resolution calls', async () => {
|
||||
const { savedObjectsClient, savedObjectsService } = setup({
|
||||
const { savedObjects } = setup({
|
||||
objects: [
|
||||
{
|
||||
type: 'dashboard',
|
||||
|
@ -107,9 +140,12 @@ describe('resolveCopySavedObjectsToSpacesConflicts', () => {
|
|||
],
|
||||
});
|
||||
|
||||
const request = httpServerMock.createKibanaRequest();
|
||||
|
||||
const resolveCopySavedObjectsToSpacesConflicts = resolveCopySavedObjectsToSpacesConflictsFactory(
|
||||
savedObjectsClient,
|
||||
savedObjectsService
|
||||
savedObjects,
|
||||
() => 1000,
|
||||
request
|
||||
);
|
||||
|
||||
const result = await resolveCopySavedObjectsToSpacesConflicts('sourceSpace', {
|
||||
|
@ -153,8 +189,7 @@ describe('resolveCopySavedObjectsToSpacesConflicts', () => {
|
|||
}
|
||||
`);
|
||||
|
||||
expect((savedObjectsService.importExport.getSortedObjectsForExport as jest.Mock).mock.calls)
|
||||
.toMatchInlineSnapshot(`
|
||||
expect((exportSavedObjectsToStream as jest.Mock).mock.calls).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
Array [
|
||||
Object {
|
||||
|
@ -168,14 +203,23 @@ describe('resolveCopySavedObjectsToSpacesConflicts', () => {
|
|||
"type": "dashboard",
|
||||
},
|
||||
],
|
||||
"savedObjectsClient": null,
|
||||
"savedObjectsClient": Object {
|
||||
"bulkCreate": [MockFunction],
|
||||
"bulkGet": [MockFunction],
|
||||
"bulkUpdate": [MockFunction],
|
||||
"create": [MockFunction],
|
||||
"delete": [MockFunction],
|
||||
"errors": [Function],
|
||||
"find": [MockFunction],
|
||||
"get": [MockFunction],
|
||||
"update": [MockFunction],
|
||||
},
|
||||
},
|
||||
],
|
||||
]
|
||||
`);
|
||||
|
||||
expect((savedObjectsService.importExport.resolveImportErrors as jest.Mock).mock.calls)
|
||||
.toMatchInlineSnapshot(`
|
||||
expect((resolveSavedObjectsImportErrors as jest.Mock).mock.calls).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
Array [
|
||||
Object {
|
||||
|
@ -230,7 +274,17 @@ describe('resolveCopySavedObjectsToSpacesConflicts', () => {
|
|||
"type": "visualization",
|
||||
},
|
||||
],
|
||||
"savedObjectsClient": null,
|
||||
"savedObjectsClient": Object {
|
||||
"bulkCreate": [MockFunction],
|
||||
"bulkGet": [MockFunction],
|
||||
"bulkUpdate": [MockFunction],
|
||||
"create": [MockFunction],
|
||||
"delete": [MockFunction],
|
||||
"errors": [Function],
|
||||
"find": [MockFunction],
|
||||
"get": [MockFunction],
|
||||
"update": [MockFunction],
|
||||
},
|
||||
"supportedTypes": Array [
|
||||
"dashboard",
|
||||
"visualization",
|
||||
|
@ -290,7 +344,17 @@ describe('resolveCopySavedObjectsToSpacesConflicts', () => {
|
|||
"type": "visualization",
|
||||
},
|
||||
],
|
||||
"savedObjectsClient": null,
|
||||
"savedObjectsClient": Object {
|
||||
"bulkCreate": [MockFunction],
|
||||
"bulkGet": [MockFunction],
|
||||
"bulkUpdate": [MockFunction],
|
||||
"create": [MockFunction],
|
||||
"delete": [MockFunction],
|
||||
"errors": [Function],
|
||||
"find": [MockFunction],
|
||||
"get": [MockFunction],
|
||||
"update": [MockFunction],
|
||||
},
|
||||
"supportedTypes": Array [
|
||||
"dashboard",
|
||||
"visualization",
|
||||
|
@ -320,9 +384,9 @@ describe('resolveCopySavedObjectsToSpacesConflicts', () => {
|
|||
},
|
||||
];
|
||||
|
||||
const { savedObjectsClient, savedObjectsService } = setup({
|
||||
const { savedObjects } = setup({
|
||||
objects,
|
||||
resolveImportErrorsImpl: async opts => {
|
||||
resolveSavedObjectsImportErrorsImpl: async opts => {
|
||||
if (opts.namespace === 'failure-space') {
|
||||
throw new Error(`Some error occurred!`);
|
||||
}
|
||||
|
@ -334,9 +398,12 @@ describe('resolveCopySavedObjectsToSpacesConflicts', () => {
|
|||
},
|
||||
});
|
||||
|
||||
const request = httpServerMock.createKibanaRequest();
|
||||
|
||||
const resolveCopySavedObjectsToSpacesConflicts = resolveCopySavedObjectsToSpacesConflictsFactory(
|
||||
savedObjectsClient,
|
||||
savedObjectsService
|
||||
savedObjects,
|
||||
() => 1000,
|
||||
request
|
||||
);
|
||||
|
||||
const result = await resolveCopySavedObjectsToSpacesConflicts('sourceSpace', {
|
||||
|
@ -396,9 +463,9 @@ describe('resolveCopySavedObjectsToSpacesConflicts', () => {
|
|||
});
|
||||
|
||||
it(`handles stream read errors`, async () => {
|
||||
const { savedObjectsClient, savedObjectsService } = setup({
|
||||
const { savedObjects } = setup({
|
||||
objects: [],
|
||||
getSortedObjectsForExportImpl: opts => {
|
||||
exportSavedObjectsToStreamImpl: opts => {
|
||||
return Promise.resolve(
|
||||
new Readable({
|
||||
objectMode: true,
|
||||
|
@ -410,9 +477,12 @@ describe('resolveCopySavedObjectsToSpacesConflicts', () => {
|
|||
},
|
||||
});
|
||||
|
||||
const request = httpServerMock.createKibanaRequest();
|
||||
|
||||
const resolveCopySavedObjectsToSpacesConflicts = resolveCopySavedObjectsToSpacesConflictsFactory(
|
||||
savedObjectsClient,
|
||||
savedObjectsService
|
||||
savedObjects,
|
||||
() => 1000,
|
||||
request
|
||||
);
|
||||
|
||||
await expect(
|
||||
|
|
|
@ -4,37 +4,42 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import {
|
||||
SavedObjectsClientContract,
|
||||
SavedObjectsLegacyService,
|
||||
SavedObject,
|
||||
} from 'src/core/server';
|
||||
import { Readable } from 'stream';
|
||||
import { SavedObject, CoreStart, KibanaRequest } from 'src/core/server';
|
||||
import {
|
||||
exportSavedObjectsToStream,
|
||||
resolveSavedObjectsImportErrors,
|
||||
} from '../../../../../../src/core/server';
|
||||
import { spaceIdToNamespace } from '../utils/namespace';
|
||||
import { CopyOptions, ResolveConflictsOptions, CopyResponse } from './types';
|
||||
import { getEligibleTypes } from './lib/get_eligible_types';
|
||||
import { createEmptyFailureResponse } from './lib/create_empty_failure_response';
|
||||
import { readStreamToCompletion } from './lib/read_stream_to_completion';
|
||||
import { createReadableStreamFromArray } from './lib/readable_stream_from_array';
|
||||
import { COPY_TO_SPACES_SAVED_OBJECTS_CLIENT_OPTS } from './lib/saved_objects_client_opts';
|
||||
|
||||
export function resolveCopySavedObjectsToSpacesConflictsFactory(
|
||||
savedObjectsClient: SavedObjectsClientContract,
|
||||
savedObjectsService: SavedObjectsLegacyService
|
||||
savedObjects: CoreStart['savedObjects'],
|
||||
getImportExportObjectLimit: () => number,
|
||||
request: KibanaRequest
|
||||
) {
|
||||
const { importExport, types, schema } = savedObjectsService;
|
||||
const eligibleTypes = getEligibleTypes({ types, schema });
|
||||
const { getTypeRegistry, getScopedClient } = savedObjects;
|
||||
|
||||
const savedObjectsClient = getScopedClient(request, COPY_TO_SPACES_SAVED_OBJECTS_CLIENT_OPTS);
|
||||
|
||||
const eligibleTypes = getEligibleTypes(getTypeRegistry());
|
||||
|
||||
const exportRequestedObjects = async (
|
||||
sourceSpaceId: string,
|
||||
options: Pick<CopyOptions, 'includeReferences' | 'objects'>
|
||||
) => {
|
||||
const objectStream = await importExport.getSortedObjectsForExport({
|
||||
const objectStream = await exportSavedObjectsToStream({
|
||||
namespace: spaceIdToNamespace(sourceSpaceId),
|
||||
includeReferencesDeep: options.includeReferences,
|
||||
excludeExportDetails: true,
|
||||
objects: options.objects,
|
||||
savedObjectsClient,
|
||||
exportSizeLimit: importExport.objectLimit,
|
||||
exportSizeLimit: getImportExportObjectLimit(),
|
||||
});
|
||||
return readStreamToCompletion<SavedObject>(objectStream);
|
||||
};
|
||||
|
@ -50,9 +55,9 @@ export function resolveCopySavedObjectsToSpacesConflictsFactory(
|
|||
}>
|
||||
) => {
|
||||
try {
|
||||
const importResponse = await importExport.resolveImportErrors({
|
||||
const importResponse = await resolveSavedObjectsImportErrors({
|
||||
namespace: spaceIdToNamespace(spaceId),
|
||||
objectLimit: importExport.objectLimit,
|
||||
objectLimit: getImportExportObjectLimit(),
|
||||
savedObjectsClient,
|
||||
supportedTypes: eligibleTypes,
|
||||
readStream: objectsStream,
|
||||
|
|
|
@ -4,9 +4,8 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import Boom from 'boom';
|
||||
import { createDefaultSpace } from './create_default_space';
|
||||
import { SavedObjectsLegacyService, IClusterClient } from 'src/core/server';
|
||||
import { SavedObjectsErrorHelpers } from 'src/core/server';
|
||||
|
||||
interface MockServerSettings {
|
||||
defaultExists?: boolean;
|
||||
|
@ -23,7 +22,7 @@ const createMockDeps = (settings: MockServerSettings = {}) => {
|
|||
simulateCreateErrorCondition = false,
|
||||
} = settings;
|
||||
|
||||
const mockGet = jest.fn().mockImplementation(() => {
|
||||
const mockGet = jest.fn().mockImplementation((type, id) => {
|
||||
if (simulateGetErrorCondition) {
|
||||
throw new Error('unit test: unexpected exception condition');
|
||||
}
|
||||
|
@ -31,12 +30,14 @@ const createMockDeps = (settings: MockServerSettings = {}) => {
|
|||
if (defaultExists) {
|
||||
return;
|
||||
}
|
||||
throw Boom.notFound('unit test: default space not found');
|
||||
throw SavedObjectsErrorHelpers.createGenericNotFoundError(type, id);
|
||||
});
|
||||
|
||||
const mockCreate = jest.fn().mockImplementation(() => {
|
||||
if (simulateConflict) {
|
||||
throw new Error('unit test: default space already exists');
|
||||
throw SavedObjectsErrorHelpers.decorateConflictError(
|
||||
new Error('unit test: default space already exists')
|
||||
);
|
||||
}
|
||||
if (simulateCreateErrorCondition) {
|
||||
throw new Error('unit test: some other unexpected error');
|
||||
|
@ -45,18 +46,9 @@ const createMockDeps = (settings: MockServerSettings = {}) => {
|
|||
return null;
|
||||
});
|
||||
|
||||
const mockServer = {
|
||||
config: jest.fn().mockReturnValue({
|
||||
get: jest.fn(),
|
||||
}),
|
||||
return {
|
||||
savedObjects: {
|
||||
SavedObjectsClient: {
|
||||
errors: {
|
||||
isNotFoundError: (e: Error) => e.message === 'unit test: default space not found',
|
||||
isConflictError: (e: Error) => e.message === 'unit test: default space already exists',
|
||||
},
|
||||
},
|
||||
getSavedObjectsRepository: jest.fn().mockImplementation(() => {
|
||||
createInternalRepository: jest.fn().mockImplementation(() => {
|
||||
return {
|
||||
get: mockGet,
|
||||
create: mockCreate,
|
||||
|
@ -64,18 +56,6 @@ const createMockDeps = (settings: MockServerSettings = {}) => {
|
|||
}),
|
||||
},
|
||||
};
|
||||
|
||||
mockServer.config().get.mockImplementation((key: string) => {
|
||||
return settings[key];
|
||||
});
|
||||
|
||||
return {
|
||||
config: mockServer.config(),
|
||||
savedObjects: (mockServer.savedObjects as unknown) as SavedObjectsLegacyService,
|
||||
esClient: ({
|
||||
callAsInternalUser: jest.fn(),
|
||||
} as unknown) as jest.Mocked<IClusterClient>,
|
||||
};
|
||||
};
|
||||
|
||||
test(`it creates the default space when one does not exist`, async () => {
|
||||
|
@ -85,7 +65,7 @@ test(`it creates the default space when one does not exist`, async () => {
|
|||
|
||||
await createDefaultSpace(deps);
|
||||
|
||||
const repository = deps.savedObjects.getSavedObjectsRepository();
|
||||
const repository = deps.savedObjects.createInternalRepository();
|
||||
|
||||
expect(repository.get).toHaveBeenCalledTimes(1);
|
||||
expect(repository.create).toHaveBeenCalledTimes(1);
|
||||
|
@ -109,7 +89,7 @@ test(`it does not attempt to recreate the default space if it already exists`, a
|
|||
|
||||
await createDefaultSpace(deps);
|
||||
|
||||
const repository = deps.savedObjects.getSavedObjectsRepository();
|
||||
const repository = deps.savedObjects.createInternalRepository();
|
||||
|
||||
expect(repository.get).toHaveBeenCalledTimes(1);
|
||||
expect(repository.create).toHaveBeenCalledTimes(0);
|
||||
|
@ -121,7 +101,9 @@ test(`it throws all other errors from the saved objects client when checking for
|
|||
simulateGetErrorCondition: true,
|
||||
});
|
||||
|
||||
expect(createDefaultSpace(deps)).rejects.toThrowErrorMatchingSnapshot();
|
||||
expect(createDefaultSpace(deps)).rejects.toThrowErrorMatchingInlineSnapshot(
|
||||
`"unit test: unexpected exception condition"`
|
||||
);
|
||||
});
|
||||
|
||||
test(`it ignores conflict errors if the default space already exists`, async () => {
|
||||
|
@ -132,7 +114,7 @@ test(`it ignores conflict errors if the default space already exists`, async ()
|
|||
|
||||
await createDefaultSpace(deps);
|
||||
|
||||
const repository = deps.savedObjects.getSavedObjectsRepository();
|
||||
const repository = deps.savedObjects.createInternalRepository();
|
||||
|
||||
expect(repository.get).toHaveBeenCalledTimes(1);
|
||||
expect(repository.create).toHaveBeenCalledTimes(1);
|
||||
|
@ -144,5 +126,7 @@ test(`it throws other errors if there is an error creating the default space`, a
|
|||
simulateCreateErrorCondition: true,
|
||||
});
|
||||
|
||||
expect(createDefaultSpace(deps)).rejects.toThrowErrorMatchingSnapshot();
|
||||
expect(createDefaultSpace(deps)).rejects.toThrowErrorMatchingInlineSnapshot(
|
||||
`"unit test: some other unexpected error"`
|
||||
);
|
||||
});
|
||||
|
|
|
@ -5,23 +5,20 @@
|
|||
*/
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { SavedObjectsLegacyService, IClusterClient } from 'src/core/server';
|
||||
import { SavedObjectsServiceStart, SavedObjectsRepository } from 'src/core/server';
|
||||
import { SavedObjectsErrorHelpers } from '../../../../../src/core/server';
|
||||
import { DEFAULT_SPACE_ID } from '../../common/constants';
|
||||
|
||||
interface Deps {
|
||||
esClient: IClusterClient;
|
||||
savedObjects: SavedObjectsLegacyService;
|
||||
savedObjects: Pick<SavedObjectsServiceStart, 'createInternalRepository'>;
|
||||
}
|
||||
|
||||
export async function createDefaultSpace({ esClient, savedObjects }: Deps) {
|
||||
const { getSavedObjectsRepository, SavedObjectsClient } = savedObjects;
|
||||
export async function createDefaultSpace({ savedObjects }: Deps) {
|
||||
const { createInternalRepository } = savedObjects;
|
||||
|
||||
const savedObjectsRepository = getSavedObjectsRepository(esClient.callAsInternalUser, ['space']);
|
||||
const savedObjectsRepository = createInternalRepository(['space']);
|
||||
|
||||
const defaultSpaceExists = await doesDefaultSpaceExist(
|
||||
SavedObjectsClient,
|
||||
savedObjectsRepository
|
||||
);
|
||||
const defaultSpaceExists = await doesDefaultSpaceExist(savedObjectsRepository);
|
||||
|
||||
if (defaultSpaceExists) {
|
||||
return;
|
||||
|
@ -51,19 +48,19 @@ export async function createDefaultSpace({ esClient, savedObjects }: Deps) {
|
|||
// Ignore conflict errors.
|
||||
// It is possible that another Kibana instance, or another invocation of this function
|
||||
// created the default space in the time it took this to complete.
|
||||
if (SavedObjectsClient.errors.isConflictError(error)) {
|
||||
if (SavedObjectsErrorHelpers.isConflictError(error)) {
|
||||
return;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async function doesDefaultSpaceExist(SavedObjectsClient: any, savedObjectsRepository: any) {
|
||||
async function doesDefaultSpaceExist(savedObjectsRepository: Pick<SavedObjectsRepository, 'get'>) {
|
||||
try {
|
||||
await savedObjectsRepository.get('space', DEFAULT_SPACE_ID);
|
||||
return true;
|
||||
} catch (e) {
|
||||
if (SavedObjectsClient.errors.isNotFoundError(e)) {
|
||||
if (SavedObjectsErrorHelpers.isNotFoundError(e)) {
|
||||
return false;
|
||||
}
|
||||
throw e;
|
||||
|
|
|
@ -11,7 +11,6 @@ import { kibanaTestUser } from '@kbn/test';
|
|||
import { initSpacesOnRequestInterceptor } from './on_request_interceptor';
|
||||
import {
|
||||
CoreSetup,
|
||||
SavedObjectsLegacyService,
|
||||
SavedObjectsErrorHelpers,
|
||||
IBasePath,
|
||||
IRouter,
|
||||
|
@ -19,9 +18,10 @@ import {
|
|||
import {
|
||||
elasticsearchServiceMock,
|
||||
loggingServiceMock,
|
||||
coreMock,
|
||||
} from '../../../../../../src/core/server/mocks';
|
||||
import * as kbnTestServer from '../../../../../../src/test_utils/kbn_server';
|
||||
import { LegacyAPI, PluginsSetup } from '../../plugin';
|
||||
import { PluginsSetup } from '../../plugin';
|
||||
import { SpacesService } from '../../spaces_service';
|
||||
import { SpacesAuditLogger } from '../audit_logger';
|
||||
import { convertSavedObjectToSpace } from '../../routes/lib';
|
||||
|
@ -152,35 +152,30 @@ describe.skip('onPostAuthInterceptor', () => {
|
|||
] as Feature[],
|
||||
} as PluginsSetup['features'];
|
||||
|
||||
const savedObjectsService = {
|
||||
SavedObjectsClient: {
|
||||
errors: SavedObjectsErrorHelpers,
|
||||
},
|
||||
getSavedObjectsRepository: jest.fn().mockImplementation(() => {
|
||||
return {
|
||||
get: (type: string, id: string) => {
|
||||
if (type === 'space') {
|
||||
const space = availableSpaces.find(s => s.id === id);
|
||||
if (space) {
|
||||
return space;
|
||||
}
|
||||
throw SavedObjectsErrorHelpers.createGenericNotFoundError(type, id);
|
||||
const mockRepository = jest.fn().mockImplementation(() => {
|
||||
return {
|
||||
get: (type: string, id: string) => {
|
||||
if (type === 'space') {
|
||||
const space = availableSpaces.find(s => s.id === id);
|
||||
if (space) {
|
||||
return space;
|
||||
}
|
||||
},
|
||||
create: () => null,
|
||||
};
|
||||
}),
|
||||
};
|
||||
throw SavedObjectsErrorHelpers.createGenericNotFoundError(type, id);
|
||||
}
|
||||
},
|
||||
create: () => null,
|
||||
};
|
||||
});
|
||||
|
||||
const legacyAPI = {
|
||||
savedObjects: (savedObjectsService as unknown) as SavedObjectsLegacyService,
|
||||
} as LegacyAPI;
|
||||
const coreStart = coreMock.createStart();
|
||||
coreStart.savedObjects.createInternalRepository.mockImplementation(mockRepository);
|
||||
coreStart.savedObjects.createScopedRepository.mockImplementation(mockRepository);
|
||||
|
||||
const service = new SpacesService(loggingMock, () => legacyAPI);
|
||||
const service = new SpacesService(loggingMock);
|
||||
|
||||
const spacesService = await service.setup({
|
||||
http: (http as unknown) as CoreSetup['http'],
|
||||
elasticsearch: elasticsearchServiceMock.createSetup(),
|
||||
getStartServices: async () => [coreStart, {}, {}],
|
||||
authorization: securityMock.createSetup().authz,
|
||||
getSpacesAuditLogger: () => ({} as SpacesAuditLogger),
|
||||
config$: Rx.of(spacesConfig),
|
||||
|
|
|
@ -8,25 +8,15 @@ import * as Rx from 'rxjs';
|
|||
import { DEFAULT_SPACE_ID } from '../../common/constants';
|
||||
import { createSpacesTutorialContextFactory } from './spaces_tutorial_context_factory';
|
||||
import { SpacesService } from '../spaces_service';
|
||||
import { SavedObjectsLegacyService } from 'src/core/server';
|
||||
import { SpacesAuditLogger } from './audit_logger';
|
||||
import {
|
||||
elasticsearchServiceMock,
|
||||
coreMock,
|
||||
loggingServiceMock,
|
||||
} from '../../../../../src/core/server/mocks';
|
||||
import { coreMock, loggingServiceMock } from '../../../../../src/core/server/mocks';
|
||||
import { spacesServiceMock } from '../spaces_service/spaces_service.mock';
|
||||
import { LegacyAPI } from '../plugin';
|
||||
import { spacesConfig } from './__fixtures__';
|
||||
import { securityMock } from '../../../security/server/mocks';
|
||||
|
||||
const log = loggingServiceMock.createLogger();
|
||||
|
||||
const legacyAPI: LegacyAPI = {
|
||||
savedObjects: {} as SavedObjectsLegacyService,
|
||||
} as LegacyAPI;
|
||||
|
||||
const service = new SpacesService(log, () => legacyAPI);
|
||||
const service = new SpacesService(log);
|
||||
|
||||
describe('createSpacesTutorialContextFactory', () => {
|
||||
it('should create a valid context factory', async () => {
|
||||
|
@ -49,7 +39,7 @@ describe('createSpacesTutorialContextFactory', () => {
|
|||
it('should create context with the current space id for the default space', async () => {
|
||||
const spacesService = await service.setup({
|
||||
http: coreMock.createSetup().http,
|
||||
elasticsearch: elasticsearchServiceMock.createSetup(),
|
||||
getStartServices: async () => [coreMock.createStart(), {}, {}],
|
||||
authorization: securityMock.createSetup().authz,
|
||||
getSpacesAuditLogger: () => ({} as SpacesAuditLogger),
|
||||
config$: Rx.of(spacesConfig),
|
||||
|
|
|
@ -68,5 +68,30 @@ describe('Spaces Plugin', () => {
|
|||
|
||||
expect(usageCollection.getCollectorByType('spaces')).toBeDefined();
|
||||
});
|
||||
|
||||
it('registers the "space" saved object type and client wrapper', async () => {
|
||||
const initializerContext = coreMock.createPluginInitializerContext({});
|
||||
const core = coreMock.createSetup() as CoreSetup<PluginsSetup>;
|
||||
const features = featuresPluginMock.createSetup();
|
||||
const licensing = licensingMock.createSetup();
|
||||
|
||||
const plugin = new Plugin(initializerContext);
|
||||
|
||||
await plugin.setup(core, { features, licensing });
|
||||
|
||||
expect(core.savedObjects.registerType).toHaveBeenCalledWith({
|
||||
name: 'space',
|
||||
namespaceAgnostic: true,
|
||||
hidden: true,
|
||||
mappings: expect.any(Object),
|
||||
migrations: expect.any(Object),
|
||||
});
|
||||
|
||||
expect(core.savedObjects.addClientWrapper).toHaveBeenCalledWith(
|
||||
Number.MIN_SAFE_INTEGER,
|
||||
'spaces',
|
||||
expect.any(Function)
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -7,12 +7,7 @@
|
|||
import { Observable } from 'rxjs';
|
||||
import { UsageCollectionSetup } from 'src/plugins/usage_collection/server';
|
||||
import { HomeServerPluginSetup } from 'src/plugins/home/server';
|
||||
import {
|
||||
SavedObjectsLegacyService,
|
||||
CoreSetup,
|
||||
Logger,
|
||||
PluginInitializerContext,
|
||||
} from '../../../../src/core/server';
|
||||
import { CoreSetup, Logger, PluginInitializerContext } from '../../../../src/core/server';
|
||||
import {
|
||||
PluginSetupContract as FeaturesPluginSetup,
|
||||
PluginStartContract as FeaturesPluginStart,
|
||||
|
@ -22,7 +17,6 @@ import { LicensingPluginSetup } from '../../licensing/server';
|
|||
import { createDefaultSpace } from './lib/create_default_space';
|
||||
// @ts-ignore
|
||||
import { AuditLogger } from '../../../../server/lib/audit_logger';
|
||||
import { spacesSavedObjectsClientWrapperFactory } from './lib/saved_objects_client/saved_objects_client_wrapper_factory';
|
||||
import { SpacesAuditLogger } from './lib/audit_logger';
|
||||
import { createSpacesTutorialContextFactory } from './lib/spaces_tutorial_context_factory';
|
||||
import { registerSpacesUsageCollector } from './usage_collection';
|
||||
|
@ -34,13 +28,13 @@ import { initExternalSpacesApi } from './routes/api/external';
|
|||
import { initInternalSpacesApi } from './routes/api/internal';
|
||||
import { initSpacesViewsRoutes } from './routes/views';
|
||||
import { setupCapabilities } from './capabilities';
|
||||
import { SpacesSavedObjectsService } from './saved_objects';
|
||||
|
||||
/**
|
||||
* Describes a set of APIs that is available in the legacy platform only and required by this plugin
|
||||
* to function properly.
|
||||
*/
|
||||
export interface LegacyAPI {
|
||||
savedObjects: SavedObjectsLegacyService;
|
||||
auditLogger: {
|
||||
create: (pluginId: string) => AuditLogger;
|
||||
};
|
||||
|
@ -108,16 +102,19 @@ export class Plugin {
|
|||
core: CoreSetup<PluginsStart>,
|
||||
plugins: PluginsSetup
|
||||
): Promise<SpacesPluginSetup> {
|
||||
const service = new SpacesService(this.log, this.getLegacyAPI);
|
||||
const service = new SpacesService(this.log);
|
||||
|
||||
const spacesService = await service.setup({
|
||||
http: core.http,
|
||||
elasticsearch: core.elasticsearch,
|
||||
getStartServices: core.getStartServices,
|
||||
authorization: plugins.security ? plugins.security.authz : null,
|
||||
getSpacesAuditLogger: this.getSpacesAuditLogger,
|
||||
config$: this.config$,
|
||||
});
|
||||
|
||||
const savedObjectsService = new SpacesSavedObjectsService();
|
||||
savedObjectsService.setup({ core, spacesService });
|
||||
|
||||
const viewRouter = core.http.createRouter();
|
||||
initSpacesViewsRoutes({
|
||||
viewRouter,
|
||||
|
@ -128,7 +125,8 @@ export class Plugin {
|
|||
initExternalSpacesApi({
|
||||
externalRouter,
|
||||
log: this.log,
|
||||
getSavedObjects: () => this.getLegacyAPI().savedObjects,
|
||||
getStartServices: core.getStartServices,
|
||||
getImportExportObjectLimit: core.savedObjects.getImportExportObjectLimit,
|
||||
spacesService,
|
||||
});
|
||||
|
||||
|
@ -170,12 +168,11 @@ export class Plugin {
|
|||
__legacyCompat: {
|
||||
registerLegacyAPI: (legacyAPI: LegacyAPI) => {
|
||||
this.legacyAPI = legacyAPI;
|
||||
this.setupLegacyComponents(spacesService);
|
||||
},
|
||||
createDefaultSpace: async () => {
|
||||
const [coreStart] = await core.getStartServices();
|
||||
return await createDefaultSpace({
|
||||
esClient: core.elasticsearch.adminClient,
|
||||
savedObjects: this.getLegacyAPI().savedObjects,
|
||||
savedObjects: coreStart.savedObjects,
|
||||
});
|
||||
},
|
||||
},
|
||||
|
@ -183,14 +180,4 @@ export class Plugin {
|
|||
}
|
||||
|
||||
public stop() {}
|
||||
|
||||
private setupLegacyComponents(spacesService: SpacesServiceSetup) {
|
||||
const legacyAPI = this.getLegacyAPI();
|
||||
const { addScopedSavedObjectsClientWrapperFactory, types } = legacyAPI.savedObjects;
|
||||
addScopedSavedObjectsClientWrapperFactory(
|
||||
Number.MIN_SAFE_INTEGER,
|
||||
'spaces',
|
||||
spacesSavedObjectsClientWrapperFactory(spacesService, types)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,43 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { Readable } from 'stream';
|
||||
import { createPromiseFromStreams, createConcatStream } from 'src/legacy/utils';
|
||||
|
||||
async function readStreamToCompletion(stream: Readable) {
|
||||
return (await (createPromiseFromStreams([stream, createConcatStream([])]) as unknown)) as any[];
|
||||
}
|
||||
|
||||
export const createExportSavedObjectsToStreamMock = () => {
|
||||
return jest.fn().mockResolvedValue(
|
||||
new Readable({
|
||||
objectMode: true,
|
||||
read() {
|
||||
this.push(null);
|
||||
},
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
export const createImportSavedObjectsFromStreamMock = () => {
|
||||
return jest.fn().mockImplementation(async (opts: Record<string, any>) => {
|
||||
const objectsToImport: any[] = await readStreamToCompletion(opts.readStream);
|
||||
return {
|
||||
success: true,
|
||||
successCount: objectsToImport.length,
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
export const createResolveSavedObjectsImportErrorsMock = () => {
|
||||
return jest.fn().mockImplementation(async (opts: Record<string, any>) => {
|
||||
const objectsToImport: any[] = await readStreamToCompletion(opts.readStream);
|
||||
return {
|
||||
success: true,
|
||||
successCount: objectsToImport.length,
|
||||
};
|
||||
});
|
||||
};
|
|
@ -1,108 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { Readable } from 'stream';
|
||||
import { createPromiseFromStreams, createConcatStream } from 'src/legacy/utils/streams';
|
||||
import { SavedObjectsSchema, SavedObjectsLegacyService } from 'src/core/server';
|
||||
import { LegacyAPI } from '../../../plugin';
|
||||
import { Space } from '../../../../common/model/space';
|
||||
import { createSpaces } from '.';
|
||||
|
||||
async function readStreamToCompletion(stream: Readable) {
|
||||
return (await (createPromiseFromStreams([stream, createConcatStream([])]) as unknown)) as any[];
|
||||
}
|
||||
|
||||
interface LegacyAPIOpts {
|
||||
spaces?: Space[];
|
||||
}
|
||||
|
||||
export const createLegacyAPI = ({
|
||||
spaces = createSpaces().map(s => ({ id: s.id, ...s.attributes })),
|
||||
}: LegacyAPIOpts = {}) => {
|
||||
const mockSavedObjectsClientContract = {
|
||||
get: jest.fn((type, id) => {
|
||||
const result = spaces.filter(s => s.id === id);
|
||||
if (!result.length) {
|
||||
throw new Error(`not found: [${type}:${id}]`);
|
||||
}
|
||||
return result[0];
|
||||
}),
|
||||
find: jest.fn(() => {
|
||||
return {
|
||||
total: spaces.length,
|
||||
saved_objects: spaces,
|
||||
};
|
||||
}),
|
||||
create: jest.fn((type, attributes, { id }) => {
|
||||
if (spaces.find(s => s.id === id)) {
|
||||
throw new Error('conflict');
|
||||
}
|
||||
return {};
|
||||
}),
|
||||
update: jest.fn((type, id) => {
|
||||
if (!spaces.find(s => s.id === id)) {
|
||||
throw new Error('not found: during update');
|
||||
}
|
||||
return {};
|
||||
}),
|
||||
delete: jest.fn((type: string, id: string) => {
|
||||
return {};
|
||||
}),
|
||||
deleteByNamespace: jest.fn(),
|
||||
};
|
||||
|
||||
const savedObjectsService = ({
|
||||
types: ['visualization', 'dashboard', 'index-pattern', 'globalType'],
|
||||
schema: new SavedObjectsSchema({
|
||||
space: {
|
||||
isNamespaceAgnostic: true,
|
||||
hidden: true,
|
||||
},
|
||||
globalType: {
|
||||
isNamespaceAgnostic: true,
|
||||
},
|
||||
}),
|
||||
getScopedSavedObjectsClient: jest.fn().mockResolvedValue(mockSavedObjectsClientContract),
|
||||
importExport: {
|
||||
objectLimit: 10000,
|
||||
getSortedObjectsForExport: jest.fn().mockResolvedValue(
|
||||
new Readable({
|
||||
objectMode: true,
|
||||
read() {
|
||||
this.push(null);
|
||||
},
|
||||
})
|
||||
),
|
||||
importSavedObjects: jest.fn().mockImplementation(async (opts: Record<string, any>) => {
|
||||
const objectsToImport: any[] = await readStreamToCompletion(opts.readStream);
|
||||
return {
|
||||
success: true,
|
||||
successCount: objectsToImport.length,
|
||||
};
|
||||
}),
|
||||
resolveImportErrors: jest.fn().mockImplementation(async (opts: Record<string, any>) => {
|
||||
const objectsToImport: any[] = await readStreamToCompletion(opts.readStream);
|
||||
return {
|
||||
success: true,
|
||||
successCount: objectsToImport.length,
|
||||
};
|
||||
}),
|
||||
},
|
||||
SavedObjectsClient: {
|
||||
errors: {
|
||||
isNotFoundError: jest.fn((e: any) => e.message.startsWith('not found:')),
|
||||
isConflictError: jest.fn((e: any) => e.message.startsWith('conflict')),
|
||||
},
|
||||
},
|
||||
} as unknown) as jest.Mocked<SavedObjectsLegacyService>;
|
||||
|
||||
const legacyAPI: jest.Mocked<LegacyAPI> = {
|
||||
auditLogger: {} as any,
|
||||
savedObjects: savedObjectsService,
|
||||
};
|
||||
|
||||
return legacyAPI;
|
||||
};
|
|
@ -0,0 +1,86 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { SavedObjectsClientContract, SavedObjectsErrorHelpers } from 'src/core/server';
|
||||
import { coreMock, savedObjectsTypeRegistryMock } from '../../../../../../../src/core/server/mocks';
|
||||
|
||||
export const createMockSavedObjectsService = (spaces: any[] = []) => {
|
||||
const mockSavedObjectsClientContract = ({
|
||||
get: jest.fn((type, id) => {
|
||||
const result = spaces.filter(s => s.id === id);
|
||||
if (!result.length) {
|
||||
throw SavedObjectsErrorHelpers.createGenericNotFoundError(type, id);
|
||||
}
|
||||
return result[0];
|
||||
}),
|
||||
find: jest.fn(() => {
|
||||
return {
|
||||
total: spaces.length,
|
||||
saved_objects: spaces,
|
||||
};
|
||||
}),
|
||||
create: jest.fn((type, attributes, { id }) => {
|
||||
if (spaces.find(s => s.id === id)) {
|
||||
throw SavedObjectsErrorHelpers.decorateConflictError(new Error(), 'space conflict');
|
||||
}
|
||||
return {};
|
||||
}),
|
||||
update: jest.fn((type, id) => {
|
||||
if (!spaces.find(s => s.id === id)) {
|
||||
throw SavedObjectsErrorHelpers.createGenericNotFoundError(type, id);
|
||||
}
|
||||
return {};
|
||||
}),
|
||||
delete: jest.fn((type: string, id: string) => {
|
||||
return {};
|
||||
}),
|
||||
deleteByNamespace: jest.fn(),
|
||||
} as unknown) as jest.Mocked<SavedObjectsClientContract>;
|
||||
|
||||
const { savedObjects } = coreMock.createStart();
|
||||
|
||||
const typeRegistry = savedObjectsTypeRegistryMock.create();
|
||||
typeRegistry.getAllTypes.mockReturnValue([
|
||||
{
|
||||
name: 'visualization',
|
||||
namespaceAgnostic: false,
|
||||
hidden: false,
|
||||
mappings: { properties: {} },
|
||||
},
|
||||
{
|
||||
name: 'dashboard',
|
||||
namespaceAgnostic: false,
|
||||
hidden: false,
|
||||
mappings: { properties: {} },
|
||||
},
|
||||
{
|
||||
name: 'index-pattern',
|
||||
namespaceAgnostic: false,
|
||||
hidden: false,
|
||||
mappings: { properties: {} },
|
||||
},
|
||||
{
|
||||
name: 'globalType',
|
||||
namespaceAgnostic: true,
|
||||
hidden: false,
|
||||
mappings: { properties: {} },
|
||||
},
|
||||
{
|
||||
name: 'space',
|
||||
namespaceAgnostic: true,
|
||||
hidden: true,
|
||||
mappings: { properties: {} },
|
||||
},
|
||||
]);
|
||||
typeRegistry.isNamespaceAgnostic.mockImplementation((type: string) =>
|
||||
typeRegistry.getAllTypes().some(t => t.name === type && t.namespaceAgnostic)
|
||||
);
|
||||
savedObjects.getTypeRegistry.mockReturnValue(typeRegistry);
|
||||
|
||||
savedObjects.getScopedClient.mockReturnValue(mockSavedObjectsClientContract);
|
||||
|
||||
return savedObjects;
|
||||
};
|
|
@ -5,6 +5,11 @@
|
|||
*/
|
||||
|
||||
export { createSpaces } from './create_spaces';
|
||||
export { createLegacyAPI } from './create_legacy_api';
|
||||
export { createMockSavedObjectsRepository } from './create_mock_so_repository';
|
||||
export { createMockSavedObjectsService } from './create_mock_so_service';
|
||||
export { mockRouteContext, mockRouteContextWithInvalidLicense } from './route_contexts';
|
||||
export {
|
||||
createExportSavedObjectsToStreamMock,
|
||||
createImportSavedObjectsFromStreamMock,
|
||||
createResolveSavedObjectsImportErrorsMock,
|
||||
} from './create_copy_to_space_mocks';
|
||||
|
|
|
@ -6,17 +6,20 @@
|
|||
import * as Rx from 'rxjs';
|
||||
import {
|
||||
createSpaces,
|
||||
createLegacyAPI,
|
||||
createMockSavedObjectsRepository,
|
||||
mockRouteContext,
|
||||
mockRouteContextWithInvalidLicense,
|
||||
createExportSavedObjectsToStreamMock,
|
||||
createImportSavedObjectsFromStreamMock,
|
||||
createResolveSavedObjectsImportErrorsMock,
|
||||
createMockSavedObjectsService,
|
||||
} from '../__fixtures__';
|
||||
import { CoreSetup, IRouter, kibanaResponseFactory, RouteValidatorConfig } from 'src/core/server';
|
||||
import {
|
||||
loggingServiceMock,
|
||||
elasticsearchServiceMock,
|
||||
httpServiceMock,
|
||||
httpServerMock,
|
||||
coreMock,
|
||||
} from 'src/core/server/mocks';
|
||||
import { SpacesService } from '../../../spaces_service';
|
||||
import { SpacesAuditLogger } from '../../../lib/audit_logger';
|
||||
|
@ -25,25 +28,55 @@ import { initCopyToSpacesApi } from './copy_to_space';
|
|||
import { spacesConfig } from '../../../lib/__fixtures__';
|
||||
import { securityMock } from '../../../../../security/server/mocks';
|
||||
import { ObjectType } from '@kbn/config-schema';
|
||||
jest.mock('../../../../../../../src/core/server', () => {
|
||||
return {
|
||||
exportSavedObjectsToStream: jest.fn(),
|
||||
importSavedObjectsFromStream: jest.fn(),
|
||||
resolveSavedObjectsImportErrors: jest.fn(),
|
||||
kibanaResponseFactory: jest.requireActual('src/core/server').kibanaResponseFactory,
|
||||
};
|
||||
});
|
||||
import {
|
||||
exportSavedObjectsToStream,
|
||||
importSavedObjectsFromStream,
|
||||
resolveSavedObjectsImportErrors,
|
||||
} from '../../../../../../../src/core/server';
|
||||
|
||||
describe('copy to space', () => {
|
||||
const spacesSavedObjects = createSpaces();
|
||||
const spaces = spacesSavedObjects.map(s => ({ id: s.id, ...s.attributes }));
|
||||
|
||||
beforeEach(() => {
|
||||
(exportSavedObjectsToStream as jest.Mock).mockReset();
|
||||
(importSavedObjectsFromStream as jest.Mock).mockReset();
|
||||
(resolveSavedObjectsImportErrors as jest.Mock).mockReset();
|
||||
});
|
||||
|
||||
const setup = async () => {
|
||||
const httpService = httpServiceMock.createSetupContract();
|
||||
const router = httpService.createRouter('') as jest.Mocked<IRouter>;
|
||||
|
||||
const legacyAPI = createLegacyAPI({ spaces });
|
||||
|
||||
const savedObjectsRepositoryMock = createMockSavedObjectsRepository(spacesSavedObjects);
|
||||
|
||||
(exportSavedObjectsToStream as jest.Mock).mockImplementation(
|
||||
createExportSavedObjectsToStreamMock()
|
||||
);
|
||||
(importSavedObjectsFromStream as jest.Mock).mockImplementation(
|
||||
createImportSavedObjectsFromStreamMock()
|
||||
);
|
||||
(resolveSavedObjectsImportErrors as jest.Mock).mockImplementation(
|
||||
createResolveSavedObjectsImportErrorsMock()
|
||||
);
|
||||
|
||||
const log = loggingServiceMock.create().get('spaces');
|
||||
|
||||
const service = new SpacesService(log, () => legacyAPI);
|
||||
const coreStart = coreMock.createStart();
|
||||
coreStart.savedObjects = createMockSavedObjectsService(spaces);
|
||||
|
||||
const service = new SpacesService(log);
|
||||
const spacesService = await service.setup({
|
||||
http: (httpService as unknown) as CoreSetup['http'],
|
||||
elasticsearch: elasticsearchServiceMock.createSetup(),
|
||||
getStartServices: async () => [coreStart, {}, {}],
|
||||
authorization: securityMock.createSetup().authz,
|
||||
getSpacesAuditLogger: () => ({} as SpacesAuditLogger),
|
||||
config$: Rx.of(spacesConfig),
|
||||
|
@ -65,7 +98,8 @@ describe('copy to space', () => {
|
|||
|
||||
initCopyToSpacesApi({
|
||||
externalRouter: router,
|
||||
getSavedObjects: () => legacyAPI.savedObjects,
|
||||
getStartServices: async () => [coreStart, {}, {}],
|
||||
getImportExportObjectLimit: () => 1000,
|
||||
log,
|
||||
spacesService,
|
||||
});
|
||||
|
@ -76,6 +110,7 @@ describe('copy to space', () => {
|
|||
] = router.post.mock.calls;
|
||||
|
||||
return {
|
||||
coreStart,
|
||||
copyToSpace: {
|
||||
routeValidation: ctsRouteDefinition.validate as RouteValidatorConfig<{}, {}, {}>,
|
||||
routeHandler: ctsRouteHandler,
|
||||
|
@ -85,7 +120,6 @@ describe('copy to space', () => {
|
|||
routeHandler: resolveRouteHandler,
|
||||
},
|
||||
savedObjectsRepositoryMock,
|
||||
legacyAPI,
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -115,7 +149,7 @@ describe('copy to space', () => {
|
|||
objects: [],
|
||||
};
|
||||
|
||||
const { copyToSpace, legacyAPI } = await setup();
|
||||
const { copyToSpace, coreStart } = await setup();
|
||||
|
||||
const request = httpServerMock.createKibanaRequest({
|
||||
body: payload,
|
||||
|
@ -124,12 +158,9 @@ describe('copy to space', () => {
|
|||
|
||||
await copyToSpace.routeHandler(mockRouteContext, request, kibanaResponseFactory);
|
||||
|
||||
expect(legacyAPI.savedObjects.getScopedSavedObjectsClient).toHaveBeenCalledWith(
|
||||
expect.any(Object),
|
||||
{
|
||||
excludedWrappers: ['spaces'],
|
||||
}
|
||||
);
|
||||
expect(coreStart.savedObjects.getScopedClient).toHaveBeenCalledWith(request, {
|
||||
excludedWrappers: ['spaces'],
|
||||
});
|
||||
});
|
||||
|
||||
it(`requires space IDs to be unique`, async () => {
|
||||
|
@ -185,7 +216,7 @@ describe('copy to space', () => {
|
|||
],
|
||||
};
|
||||
|
||||
const { copyToSpace, legacyAPI } = await setup();
|
||||
const { copyToSpace } = await setup();
|
||||
|
||||
const request = httpServerMock.createKibanaRequest({
|
||||
body: payload,
|
||||
|
@ -201,9 +232,8 @@ describe('copy to space', () => {
|
|||
const { status } = response;
|
||||
|
||||
expect(status).toEqual(200);
|
||||
expect(legacyAPI.savedObjects.importExport.importSavedObjects).toHaveBeenCalledTimes(1);
|
||||
const [importCallOptions] = (legacyAPI.savedObjects.importExport
|
||||
.importSavedObjects as any).mock.calls[0];
|
||||
expect(importSavedObjectsFromStream).toHaveBeenCalledTimes(1);
|
||||
const [importCallOptions] = (importSavedObjectsFromStream as jest.Mock).mock.calls[0];
|
||||
|
||||
expect(importCallOptions).toMatchObject({
|
||||
namespace: 'a-space',
|
||||
|
@ -217,7 +247,7 @@ describe('copy to space', () => {
|
|||
objects: [{ type: 'visualization', id: 'bar' }],
|
||||
};
|
||||
|
||||
const { copyToSpace, legacyAPI } = await setup();
|
||||
const { copyToSpace } = await setup();
|
||||
|
||||
const request = httpServerMock.createKibanaRequest({
|
||||
body: payload,
|
||||
|
@ -233,16 +263,14 @@ describe('copy to space', () => {
|
|||
const { status } = response;
|
||||
|
||||
expect(status).toEqual(200);
|
||||
expect(legacyAPI.savedObjects.importExport.importSavedObjects).toHaveBeenCalledTimes(2);
|
||||
const [firstImportCallOptions] = (legacyAPI.savedObjects.importExport
|
||||
.importSavedObjects as any).mock.calls[0];
|
||||
expect(importSavedObjectsFromStream).toHaveBeenCalledTimes(2);
|
||||
const [firstImportCallOptions] = (importSavedObjectsFromStream as jest.Mock).mock.calls[0];
|
||||
|
||||
expect(firstImportCallOptions).toMatchObject({
|
||||
namespace: 'a-space',
|
||||
});
|
||||
|
||||
const [secondImportCallOptions] = (legacyAPI.savedObjects.importExport
|
||||
.importSavedObjects as any).mock.calls[1];
|
||||
const [secondImportCallOptions] = (importSavedObjectsFromStream as jest.Mock).mock.calls[1];
|
||||
|
||||
expect(secondImportCallOptions).toMatchObject({
|
||||
namespace: 'b-space',
|
||||
|
@ -284,7 +312,7 @@ describe('copy to space', () => {
|
|||
objects: [{ type: 'visualization', id: 'bar' }],
|
||||
};
|
||||
|
||||
const { resolveConflicts, legacyAPI } = await setup();
|
||||
const { resolveConflicts, coreStart } = await setup();
|
||||
|
||||
const request = httpServerMock.createKibanaRequest({
|
||||
body: payload,
|
||||
|
@ -293,12 +321,9 @@ describe('copy to space', () => {
|
|||
|
||||
await resolveConflicts.routeHandler(mockRouteContext, request, kibanaResponseFactory);
|
||||
|
||||
expect(legacyAPI.savedObjects.getScopedSavedObjectsClient).toHaveBeenCalledWith(
|
||||
expect.any(Object),
|
||||
{
|
||||
excludedWrappers: ['spaces'],
|
||||
}
|
||||
);
|
||||
expect(coreStart.savedObjects.getScopedClient).toHaveBeenCalledWith(request, {
|
||||
excludedWrappers: ['spaces'],
|
||||
});
|
||||
});
|
||||
|
||||
it(`requires objects to be unique`, async () => {
|
||||
|
@ -365,7 +390,7 @@ describe('copy to space', () => {
|
|||
],
|
||||
};
|
||||
|
||||
const { resolveConflicts, legacyAPI } = await setup();
|
||||
const { resolveConflicts } = await setup();
|
||||
|
||||
const request = httpServerMock.createKibanaRequest({
|
||||
body: payload,
|
||||
|
@ -381,9 +406,10 @@ describe('copy to space', () => {
|
|||
const { status } = response;
|
||||
|
||||
expect(status).toEqual(200);
|
||||
expect(legacyAPI.savedObjects.importExport.resolveImportErrors).toHaveBeenCalledTimes(1);
|
||||
const [resolveImportErrorsCallOptions] = (legacyAPI.savedObjects.importExport
|
||||
.resolveImportErrors as any).mock.calls[0];
|
||||
expect(resolveSavedObjectsImportErrors).toHaveBeenCalledTimes(1);
|
||||
const [
|
||||
resolveImportErrorsCallOptions,
|
||||
] = (resolveSavedObjectsImportErrors as jest.Mock).mock.calls[0];
|
||||
|
||||
expect(resolveImportErrorsCallOptions).toMatchObject({
|
||||
namespace: 'a-space',
|
||||
|
@ -412,7 +438,7 @@ describe('copy to space', () => {
|
|||
},
|
||||
};
|
||||
|
||||
const { resolveConflicts, legacyAPI } = await setup();
|
||||
const { resolveConflicts } = await setup();
|
||||
|
||||
const request = httpServerMock.createKibanaRequest({
|
||||
body: payload,
|
||||
|
@ -428,17 +454,19 @@ describe('copy to space', () => {
|
|||
const { status } = response;
|
||||
|
||||
expect(status).toEqual(200);
|
||||
expect(legacyAPI.savedObjects.importExport.resolveImportErrors).toHaveBeenCalledTimes(2);
|
||||
const [resolveImportErrorsFirstCallOptions] = (legacyAPI.savedObjects.importExport
|
||||
.resolveImportErrors as any).mock.calls[0];
|
||||
expect(resolveSavedObjectsImportErrors).toHaveBeenCalledTimes(2);
|
||||
const [
|
||||
resolveImportErrorsFirstCallOptions,
|
||||
] = (resolveSavedObjectsImportErrors as jest.Mock).mock.calls[0];
|
||||
|
||||
expect(resolveImportErrorsFirstCallOptions).toMatchObject({
|
||||
namespace: 'a-space',
|
||||
supportedTypes: ['visualization', 'dashboard', 'index-pattern'],
|
||||
});
|
||||
|
||||
const [resolveImportErrorsSecondCallOptions] = (legacyAPI.savedObjects.importExport
|
||||
.resolveImportErrors as any).mock.calls[1];
|
||||
const [
|
||||
resolveImportErrorsSecondCallOptions,
|
||||
] = (resolveSavedObjectsImportErrors as jest.Mock).mock.calls[1];
|
||||
|
||||
expect(resolveImportErrorsSecondCallOptions).toMatchObject({
|
||||
namespace: 'b-space',
|
||||
|
|
|
@ -12,7 +12,6 @@ import {
|
|||
resolveCopySavedObjectsToSpacesConflictsFactory,
|
||||
} from '../../../lib/copy_to_spaces';
|
||||
import { ExternalRouteDeps } from '.';
|
||||
import { COPY_TO_SPACES_SAVED_OBJECTS_CLIENT_OPTS } from '../../../lib/copy_to_spaces/copy_to_spaces';
|
||||
import { SPACE_ID_REGEX } from '../../../lib/space_schema';
|
||||
import { createLicensedRouteHandler } from '../../lib';
|
||||
|
||||
|
@ -22,7 +21,7 @@ const areObjectsUnique = (objects: SavedObjectIdentifier[]) =>
|
|||
_.uniq(objects, (o: SavedObjectIdentifier) => `${o.type}:${o.id}`).length === objects.length;
|
||||
|
||||
export function initCopyToSpacesApi(deps: ExternalRouteDeps) {
|
||||
const { externalRouter, spacesService, getSavedObjects } = deps;
|
||||
const { externalRouter, spacesService, getImportExportObjectLimit, getStartServices } = deps;
|
||||
|
||||
externalRouter.post(
|
||||
{
|
||||
|
@ -67,13 +66,12 @@ export function initCopyToSpacesApi(deps: ExternalRouteDeps) {
|
|||
},
|
||||
},
|
||||
createLicensedRouteHandler(async (context, request, response) => {
|
||||
const savedObjectsClient = getSavedObjects().getScopedSavedObjectsClient(
|
||||
request,
|
||||
COPY_TO_SPACES_SAVED_OBJECTS_CLIENT_OPTS
|
||||
);
|
||||
const [startServices] = await getStartServices();
|
||||
|
||||
const copySavedObjectsToSpaces = copySavedObjectsToSpacesFactory(
|
||||
savedObjectsClient,
|
||||
getSavedObjects()
|
||||
startServices.savedObjects,
|
||||
getImportExportObjectLimit,
|
||||
request
|
||||
);
|
||||
const { spaces: destinationSpaceIds, objects, includeReferences, overwrite } = request.body;
|
||||
const sourceSpaceId = spacesService.getSpaceId(request);
|
||||
|
@ -128,13 +126,12 @@ export function initCopyToSpacesApi(deps: ExternalRouteDeps) {
|
|||
},
|
||||
},
|
||||
createLicensedRouteHandler(async (context, request, response) => {
|
||||
const savedObjectsClient = getSavedObjects().getScopedSavedObjectsClient(
|
||||
request,
|
||||
COPY_TO_SPACES_SAVED_OBJECTS_CLIENT_OPTS
|
||||
);
|
||||
const [startServices] = await getStartServices();
|
||||
|
||||
const resolveCopySavedObjectsToSpacesConflicts = resolveCopySavedObjectsToSpacesConflictsFactory(
|
||||
savedObjectsClient,
|
||||
getSavedObjects()
|
||||
startServices.savedObjects,
|
||||
getImportExportObjectLimit,
|
||||
request
|
||||
);
|
||||
const { objects, includeReferences, retries } = request.body;
|
||||
const sourceSpaceId = spacesService.getSpaceId(request);
|
||||
|
|
|
@ -7,7 +7,6 @@
|
|||
import * as Rx from 'rxjs';
|
||||
import {
|
||||
createSpaces,
|
||||
createLegacyAPI,
|
||||
createMockSavedObjectsRepository,
|
||||
mockRouteContext,
|
||||
mockRouteContextWithInvalidLicense,
|
||||
|
@ -15,9 +14,9 @@ import {
|
|||
import { CoreSetup, IRouter, kibanaResponseFactory, RouteValidatorConfig } from 'src/core/server';
|
||||
import {
|
||||
loggingServiceMock,
|
||||
elasticsearchServiceMock,
|
||||
httpServiceMock,
|
||||
httpServerMock,
|
||||
coreMock,
|
||||
} from 'src/core/server/mocks';
|
||||
import { SpacesService } from '../../../spaces_service';
|
||||
import { SpacesAuditLogger } from '../../../lib/audit_logger';
|
||||
|
@ -29,22 +28,21 @@ import { ObjectType } from '@kbn/config-schema';
|
|||
|
||||
describe('Spaces Public API', () => {
|
||||
const spacesSavedObjects = createSpaces();
|
||||
const spaces = spacesSavedObjects.map(s => ({ id: s.id, ...s.attributes }));
|
||||
|
||||
const setup = async () => {
|
||||
const httpService = httpServiceMock.createSetupContract();
|
||||
const router = httpService.createRouter('') as jest.Mocked<IRouter>;
|
||||
|
||||
const legacyAPI = createLegacyAPI({ spaces });
|
||||
|
||||
const savedObjectsRepositoryMock = createMockSavedObjectsRepository(spacesSavedObjects);
|
||||
|
||||
const log = loggingServiceMock.create().get('spaces');
|
||||
|
||||
const service = new SpacesService(log, () => legacyAPI);
|
||||
const coreStart = coreMock.createStart();
|
||||
|
||||
const service = new SpacesService(log);
|
||||
const spacesService = await service.setup({
|
||||
http: (httpService as unknown) as CoreSetup['http'],
|
||||
elasticsearch: elasticsearchServiceMock.createSetup(),
|
||||
getStartServices: async () => [coreStart, {}, {}],
|
||||
authorization: securityMock.createSetup().authz,
|
||||
getSpacesAuditLogger: () => ({} as SpacesAuditLogger),
|
||||
config$: Rx.of(spacesConfig),
|
||||
|
@ -66,7 +64,8 @@ describe('Spaces Public API', () => {
|
|||
|
||||
initDeleteSpacesApi({
|
||||
externalRouter: router,
|
||||
getSavedObjects: () => legacyAPI.savedObjects,
|
||||
getStartServices: async () => [coreStart, {}, {}],
|
||||
getImportExportObjectLimit: () => 1000,
|
||||
log,
|
||||
spacesService,
|
||||
});
|
||||
|
|
|
@ -5,13 +5,14 @@
|
|||
*/
|
||||
|
||||
import { schema } from '@kbn/config-schema';
|
||||
import { SavedObjectsErrorHelpers } from '../../../../../../../src/core/server';
|
||||
import { wrapError } from '../../../lib/errors';
|
||||
import { SpacesClient } from '../../../lib/spaces_client';
|
||||
import { ExternalRouteDeps } from '.';
|
||||
import { createLicensedRouteHandler } from '../../lib';
|
||||
|
||||
export function initDeleteSpacesApi(deps: ExternalRouteDeps) {
|
||||
const { externalRouter, getSavedObjects, spacesService } = deps;
|
||||
const { externalRouter, spacesService } = deps;
|
||||
|
||||
externalRouter.delete(
|
||||
{
|
||||
|
@ -23,7 +24,6 @@ export function initDeleteSpacesApi(deps: ExternalRouteDeps) {
|
|||
},
|
||||
},
|
||||
createLicensedRouteHandler(async (context, request, response) => {
|
||||
const { SavedObjectsClient } = getSavedObjects();
|
||||
const spacesClient: SpacesClient = await spacesService.scopedClient(request);
|
||||
|
||||
const id = request.params.id;
|
||||
|
@ -31,7 +31,7 @@ export function initDeleteSpacesApi(deps: ExternalRouteDeps) {
|
|||
try {
|
||||
await spacesClient.delete(id);
|
||||
} catch (error) {
|
||||
if (SavedObjectsClient.errors.isNotFoundError(error)) {
|
||||
if (SavedObjectsErrorHelpers.isNotFoundError(error)) {
|
||||
return response.notFound();
|
||||
}
|
||||
return response.customError(wrapError(error));
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
import * as Rx from 'rxjs';
|
||||
import {
|
||||
createSpaces,
|
||||
createLegacyAPI,
|
||||
createMockSavedObjectsRepository,
|
||||
mockRouteContextWithInvalidLicense,
|
||||
mockRouteContext,
|
||||
|
@ -15,9 +14,9 @@ import { initGetSpaceApi } from './get';
|
|||
import { CoreSetup, IRouter, kibanaResponseFactory } from 'src/core/server';
|
||||
import {
|
||||
loggingServiceMock,
|
||||
elasticsearchServiceMock,
|
||||
httpServiceMock,
|
||||
httpServerMock,
|
||||
coreMock,
|
||||
} from 'src/core/server/mocks';
|
||||
import { SpacesService } from '../../../spaces_service';
|
||||
import { SpacesAuditLogger } from '../../../lib/audit_logger';
|
||||
|
@ -33,16 +32,16 @@ describe('GET space', () => {
|
|||
const httpService = httpServiceMock.createSetupContract();
|
||||
const router = httpService.createRouter('') as jest.Mocked<IRouter>;
|
||||
|
||||
const legacyAPI = createLegacyAPI({ spaces });
|
||||
const coreStart = coreMock.createStart();
|
||||
|
||||
const savedObjectsRepositoryMock = createMockSavedObjectsRepository(spacesSavedObjects);
|
||||
|
||||
const log = loggingServiceMock.create().get('spaces');
|
||||
|
||||
const service = new SpacesService(log, () => legacyAPI);
|
||||
const service = new SpacesService(log);
|
||||
const spacesService = await service.setup({
|
||||
http: (httpService as unknown) as CoreSetup['http'],
|
||||
elasticsearch: elasticsearchServiceMock.createSetup(),
|
||||
getStartServices: async () => [coreStart, {}, {}],
|
||||
authorization: securityMock.createSetup().authz,
|
||||
getSpacesAuditLogger: () => ({} as SpacesAuditLogger),
|
||||
config$: Rx.of(spacesConfig),
|
||||
|
@ -64,7 +63,8 @@ describe('GET space', () => {
|
|||
|
||||
initGetSpaceApi({
|
||||
externalRouter: router,
|
||||
getSavedObjects: () => legacyAPI.savedObjects,
|
||||
getStartServices: async () => [coreStart, {}, {}],
|
||||
getImportExportObjectLimit: () => 1000,
|
||||
log,
|
||||
spacesService,
|
||||
});
|
||||
|
|
|
@ -5,12 +5,13 @@
|
|||
*/
|
||||
|
||||
import { schema } from '@kbn/config-schema';
|
||||
import { SavedObjectsErrorHelpers } from '../../../../../../../src/core/server';
|
||||
import { wrapError } from '../../../lib/errors';
|
||||
import { ExternalRouteDeps } from '.';
|
||||
import { createLicensedRouteHandler } from '../../lib';
|
||||
|
||||
export function initGetSpaceApi(deps: ExternalRouteDeps) {
|
||||
const { externalRouter, spacesService, getSavedObjects } = deps;
|
||||
const { externalRouter, spacesService } = deps;
|
||||
|
||||
externalRouter.get(
|
||||
{
|
||||
|
@ -23,15 +24,13 @@ export function initGetSpaceApi(deps: ExternalRouteDeps) {
|
|||
},
|
||||
createLicensedRouteHandler(async (context, request, response) => {
|
||||
const spaceId = request.params.id;
|
||||
|
||||
const { SavedObjectsClient } = getSavedObjects();
|
||||
const spacesClient = await spacesService.scopedClient(request);
|
||||
|
||||
try {
|
||||
const space = await spacesClient.get(spaceId);
|
||||
return response.ok({ body: space });
|
||||
} catch (error) {
|
||||
if (SavedObjectsClient.errors.isNotFoundError(error)) {
|
||||
if (SavedObjectsErrorHelpers.isNotFoundError(error)) {
|
||||
return response.notFound();
|
||||
}
|
||||
return response.customError(wrapError(error));
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
import * as Rx from 'rxjs';
|
||||
import {
|
||||
createSpaces,
|
||||
createLegacyAPI,
|
||||
createMockSavedObjectsRepository,
|
||||
mockRouteContext,
|
||||
mockRouteContextWithInvalidLicense,
|
||||
|
@ -14,9 +13,9 @@ import {
|
|||
import { CoreSetup, kibanaResponseFactory, IRouter } from 'src/core/server';
|
||||
import {
|
||||
loggingServiceMock,
|
||||
elasticsearchServiceMock,
|
||||
httpServiceMock,
|
||||
httpServerMock,
|
||||
coreMock,
|
||||
} from 'src/core/server/mocks';
|
||||
import { SpacesService } from '../../../spaces_service';
|
||||
import { SpacesAuditLogger } from '../../../lib/audit_logger';
|
||||
|
@ -33,16 +32,16 @@ describe('GET /spaces/space', () => {
|
|||
const httpService = httpServiceMock.createSetupContract();
|
||||
const router = httpService.createRouter('') as jest.Mocked<IRouter>;
|
||||
|
||||
const legacyAPI = createLegacyAPI({ spaces });
|
||||
const coreStart = coreMock.createStart();
|
||||
|
||||
const savedObjectsRepositoryMock = createMockSavedObjectsRepository(spacesSavedObjects);
|
||||
|
||||
const log = loggingServiceMock.create().get('spaces');
|
||||
|
||||
const service = new SpacesService(log, () => legacyAPI);
|
||||
const service = new SpacesService(log);
|
||||
const spacesService = await service.setup({
|
||||
http: (httpService as unknown) as CoreSetup['http'],
|
||||
elasticsearch: elasticsearchServiceMock.createSetup(),
|
||||
getStartServices: async () => [coreStart, {}, {}],
|
||||
authorization: securityMock.createSetup().authz,
|
||||
getSpacesAuditLogger: () => ({} as SpacesAuditLogger),
|
||||
config$: Rx.of(spacesConfig),
|
||||
|
@ -64,7 +63,8 @@ describe('GET /spaces/space', () => {
|
|||
|
||||
initGetAllSpacesApi({
|
||||
externalRouter: router,
|
||||
getSavedObjects: () => legacyAPI.savedObjects,
|
||||
getStartServices: async () => [coreStart, {}, {}],
|
||||
getImportExportObjectLimit: () => 1000,
|
||||
log,
|
||||
spacesService,
|
||||
});
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { Logger, SavedObjectsLegacyService, IRouter } from 'src/core/server';
|
||||
import { Logger, IRouter, CoreSetup } from 'src/core/server';
|
||||
import { initDeleteSpacesApi } from './delete';
|
||||
import { initGetSpaceApi } from './get';
|
||||
import { initGetAllSpacesApi } from './get_all';
|
||||
|
@ -15,7 +15,8 @@ import { initCopyToSpacesApi } from './copy_to_space';
|
|||
|
||||
export interface ExternalRouteDeps {
|
||||
externalRouter: IRouter;
|
||||
getSavedObjects: () => SavedObjectsLegacyService;
|
||||
getStartServices: CoreSetup['getStartServices'];
|
||||
getImportExportObjectLimit: () => number;
|
||||
spacesService: SpacesServiceSetup;
|
||||
log: Logger;
|
||||
}
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
import * as Rx from 'rxjs';
|
||||
import {
|
||||
createSpaces,
|
||||
createLegacyAPI,
|
||||
createMockSavedObjectsRepository,
|
||||
mockRouteContext,
|
||||
mockRouteContextWithInvalidLicense,
|
||||
|
@ -14,9 +13,9 @@ import {
|
|||
import { CoreSetup, kibanaResponseFactory, IRouter, RouteValidatorConfig } from 'src/core/server';
|
||||
import {
|
||||
loggingServiceMock,
|
||||
elasticsearchServiceMock,
|
||||
httpServerMock,
|
||||
httpServiceMock,
|
||||
coreMock,
|
||||
} from 'src/core/server/mocks';
|
||||
import { SpacesService } from '../../../spaces_service';
|
||||
import { SpacesAuditLogger } from '../../../lib/audit_logger';
|
||||
|
@ -28,22 +27,21 @@ import { ObjectType } from '@kbn/config-schema';
|
|||
|
||||
describe('Spaces Public API', () => {
|
||||
const spacesSavedObjects = createSpaces();
|
||||
const spaces = spacesSavedObjects.map(s => ({ id: s.id, ...s.attributes }));
|
||||
|
||||
const setup = async () => {
|
||||
const httpService = httpServiceMock.createSetupContract();
|
||||
const router = httpService.createRouter('') as jest.Mocked<IRouter>;
|
||||
|
||||
const legacyAPI = createLegacyAPI({ spaces });
|
||||
const coreStart = coreMock.createStart();
|
||||
|
||||
const savedObjectsRepositoryMock = createMockSavedObjectsRepository(spacesSavedObjects);
|
||||
|
||||
const log = loggingServiceMock.create().get('spaces');
|
||||
|
||||
const service = new SpacesService(log, () => legacyAPI);
|
||||
const service = new SpacesService(log);
|
||||
const spacesService = await service.setup({
|
||||
http: (httpService as unknown) as CoreSetup['http'],
|
||||
elasticsearch: elasticsearchServiceMock.createSetup(),
|
||||
getStartServices: async () => [coreStart, {}, {}],
|
||||
authorization: securityMock.createSetup().authz,
|
||||
getSpacesAuditLogger: () => ({} as SpacesAuditLogger),
|
||||
config$: Rx.of(spacesConfig),
|
||||
|
@ -65,7 +63,8 @@ describe('Spaces Public API', () => {
|
|||
|
||||
initPostSpacesApi({
|
||||
externalRouter: router,
|
||||
getSavedObjects: () => legacyAPI.savedObjects,
|
||||
getStartServices: async () => [coreStart, {}, {}],
|
||||
getImportExportObjectLimit: () => 1000,
|
||||
log,
|
||||
spacesService,
|
||||
});
|
||||
|
@ -145,7 +144,7 @@ describe('Spaces Public API', () => {
|
|||
const { status, payload: responsePayload } = response;
|
||||
|
||||
expect(status).toEqual(409);
|
||||
expect(responsePayload.message).toEqual('space conflict');
|
||||
expect(responsePayload.message).toEqual('A space with the identifier a-space already exists.');
|
||||
});
|
||||
|
||||
it('should not require disabledFeatures to be specified', async () => {
|
||||
|
|
|
@ -4,13 +4,14 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
import Boom from 'boom';
|
||||
import { SavedObjectsErrorHelpers } from '../../../../../../../src/core/server';
|
||||
import { wrapError } from '../../../lib/errors';
|
||||
import { spaceSchema } from '../../../lib/space_schema';
|
||||
import { ExternalRouteDeps } from '.';
|
||||
import { createLicensedRouteHandler } from '../../lib';
|
||||
|
||||
export function initPostSpacesApi(deps: ExternalRouteDeps) {
|
||||
const { externalRouter, log, spacesService, getSavedObjects } = deps;
|
||||
const { externalRouter, log, spacesService } = deps;
|
||||
|
||||
externalRouter.post(
|
||||
{
|
||||
|
@ -21,7 +22,6 @@ export function initPostSpacesApi(deps: ExternalRouteDeps) {
|
|||
},
|
||||
createLicensedRouteHandler(async (context, request, response) => {
|
||||
log.debug(`Inside POST /api/spaces/space`);
|
||||
const { SavedObjectsClient } = getSavedObjects();
|
||||
const spacesClient = await spacesService.scopedClient(request);
|
||||
|
||||
const space = request.body;
|
||||
|
@ -31,7 +31,7 @@ export function initPostSpacesApi(deps: ExternalRouteDeps) {
|
|||
const createdSpace = await spacesClient.create(space);
|
||||
return response.ok({ body: createdSpace });
|
||||
} catch (error) {
|
||||
if (SavedObjectsClient.errors.isConflictError(error)) {
|
||||
if (SavedObjectsErrorHelpers.isConflictError(error)) {
|
||||
const { body } = wrapError(
|
||||
Boom.conflict(`A space with the identifier ${space.id} already exists.`)
|
||||
);
|
||||
|
|
|
@ -7,7 +7,6 @@
|
|||
import * as Rx from 'rxjs';
|
||||
import {
|
||||
createSpaces,
|
||||
createLegacyAPI,
|
||||
createMockSavedObjectsRepository,
|
||||
mockRouteContext,
|
||||
mockRouteContextWithInvalidLicense,
|
||||
|
@ -15,9 +14,9 @@ import {
|
|||
import { CoreSetup, IRouter, kibanaResponseFactory, RouteValidatorConfig } from 'src/core/server';
|
||||
import {
|
||||
loggingServiceMock,
|
||||
elasticsearchServiceMock,
|
||||
httpServiceMock,
|
||||
httpServerMock,
|
||||
coreMock,
|
||||
} from 'src/core/server/mocks';
|
||||
import { SpacesService } from '../../../spaces_service';
|
||||
import { SpacesAuditLogger } from '../../../lib/audit_logger';
|
||||
|
@ -29,22 +28,21 @@ import { ObjectType } from '@kbn/config-schema';
|
|||
|
||||
describe('PUT /api/spaces/space', () => {
|
||||
const spacesSavedObjects = createSpaces();
|
||||
const spaces = spacesSavedObjects.map(s => ({ id: s.id, ...s.attributes }));
|
||||
|
||||
const setup = async () => {
|
||||
const httpService = httpServiceMock.createSetupContract();
|
||||
const router = httpService.createRouter('') as jest.Mocked<IRouter>;
|
||||
|
||||
const legacyAPI = createLegacyAPI({ spaces });
|
||||
const coreStart = coreMock.createStart();
|
||||
|
||||
const savedObjectsRepositoryMock = createMockSavedObjectsRepository(spacesSavedObjects);
|
||||
|
||||
const log = loggingServiceMock.create().get('spaces');
|
||||
|
||||
const service = new SpacesService(log, () => legacyAPI);
|
||||
const service = new SpacesService(log);
|
||||
const spacesService = await service.setup({
|
||||
http: (httpService as unknown) as CoreSetup['http'],
|
||||
elasticsearch: elasticsearchServiceMock.createSetup(),
|
||||
getStartServices: async () => [coreStart, {}, {}],
|
||||
authorization: securityMock.createSetup().authz,
|
||||
getSpacesAuditLogger: () => ({} as SpacesAuditLogger),
|
||||
config$: Rx.of(spacesConfig),
|
||||
|
@ -66,7 +64,8 @@ describe('PUT /api/spaces/space', () => {
|
|||
|
||||
initPutSpacesApi({
|
||||
externalRouter: router,
|
||||
getSavedObjects: () => legacyAPI.savedObjects,
|
||||
getStartServices: async () => [coreStart, {}, {}],
|
||||
getImportExportObjectLimit: () => 1000,
|
||||
log,
|
||||
spacesService,
|
||||
});
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
*/
|
||||
|
||||
import { schema } from '@kbn/config-schema';
|
||||
import { SavedObjectsErrorHelpers } from '../../../../../../../src/core/server';
|
||||
import { Space } from '../../../../common/model/space';
|
||||
import { wrapError } from '../../../lib/errors';
|
||||
import { spaceSchema } from '../../../lib/space_schema';
|
||||
|
@ -12,7 +13,7 @@ import { ExternalRouteDeps } from '.';
|
|||
import { createLicensedRouteHandler } from '../../lib';
|
||||
|
||||
export function initPutSpacesApi(deps: ExternalRouteDeps) {
|
||||
const { externalRouter, spacesService, getSavedObjects } = deps;
|
||||
const { externalRouter, spacesService } = deps;
|
||||
|
||||
externalRouter.put(
|
||||
{
|
||||
|
@ -25,7 +26,6 @@ export function initPutSpacesApi(deps: ExternalRouteDeps) {
|
|||
},
|
||||
},
|
||||
createLicensedRouteHandler(async (context, request, response) => {
|
||||
const { SavedObjectsClient } = getSavedObjects();
|
||||
const spacesClient = await spacesService.scopedClient(request);
|
||||
|
||||
const space = request.body;
|
||||
|
@ -35,7 +35,7 @@ export function initPutSpacesApi(deps: ExternalRouteDeps) {
|
|||
try {
|
||||
result = await spacesClient.update(id, { ...space });
|
||||
} catch (error) {
|
||||
if (SavedObjectsClient.errors.isNotFoundError(error)) {
|
||||
if (SavedObjectsErrorHelpers.isNotFoundError(error)) {
|
||||
return response.notFound();
|
||||
}
|
||||
return response.customError(wrapError(error));
|
||||
|
|
|
@ -4,9 +4,9 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
import * as Rx from 'rxjs';
|
||||
import { createLegacyAPI, mockRouteContextWithInvalidLicense } from '../__fixtures__';
|
||||
import { mockRouteContextWithInvalidLicense } from '../__fixtures__';
|
||||
import { CoreSetup, kibanaResponseFactory } from 'src/core/server';
|
||||
import { httpServiceMock, httpServerMock, elasticsearchServiceMock } from 'src/core/server/mocks';
|
||||
import { httpServiceMock, httpServerMock, coreMock } from 'src/core/server/mocks';
|
||||
import { SpacesService } from '../../../spaces_service';
|
||||
import { SpacesAuditLogger } from '../../../lib/audit_logger';
|
||||
import { spacesConfig } from '../../../lib/__fixtures__';
|
||||
|
@ -17,12 +17,12 @@ describe('GET /internal/spaces/_active_space', () => {
|
|||
const httpService = httpServiceMock.createSetupContract();
|
||||
const router = httpServiceMock.createRouter();
|
||||
|
||||
const legacyAPI = createLegacyAPI();
|
||||
const coreStart = coreMock.createStart();
|
||||
|
||||
const service = new SpacesService(null as any, () => legacyAPI);
|
||||
const service = new SpacesService(null as any);
|
||||
const spacesService = await service.setup({
|
||||
http: (httpService as unknown) as CoreSetup['http'],
|
||||
elasticsearch: elasticsearchServiceMock.createSetup(),
|
||||
getStartServices: async () => [coreStart, {}, {}],
|
||||
authorization: null,
|
||||
getSpacesAuditLogger: () => ({} as SpacesAuditLogger),
|
||||
config$: Rx.of(spacesConfig),
|
||||
|
|
|
@ -4,4 +4,4 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
export { migrateToKibana660 } from './migrate_6x';
|
||||
export { SpacesSavedObjectsService } from './saved_objects_service';
|
40
x-pack/plugins/spaces/server/saved_objects/mappings.ts
Normal file
40
x-pack/plugins/spaces/server/saved_objects/mappings.ts
Normal file
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { deepFreeze } from '../../../../../src/core/utils';
|
||||
|
||||
export const SpacesSavedObjectMappings = deepFreeze({
|
||||
properties: {
|
||||
name: {
|
||||
type: 'text',
|
||||
fields: {
|
||||
keyword: {
|
||||
type: 'keyword',
|
||||
ignore_above: 2048,
|
||||
},
|
||||
},
|
||||
},
|
||||
description: {
|
||||
type: 'text',
|
||||
},
|
||||
initials: {
|
||||
type: 'keyword',
|
||||
},
|
||||
color: {
|
||||
type: 'keyword',
|
||||
},
|
||||
disabledFeatures: {
|
||||
type: 'keyword',
|
||||
},
|
||||
imageUrl: {
|
||||
type: 'text',
|
||||
index: false,
|
||||
},
|
||||
_reserved: {
|
||||
type: 'boolean',
|
||||
},
|
||||
},
|
||||
});
|
|
@ -5,16 +5,24 @@
|
|||
*/
|
||||
|
||||
import { migrateToKibana660 } from './migrate_6x';
|
||||
import { SavedObjectMigrationContext } from 'src/core/server';
|
||||
|
||||
const mockContext = {} as SavedObjectMigrationContext;
|
||||
|
||||
describe('migrateTo660', () => {
|
||||
it('adds a "disabledFeatures" attribute initialized as an empty array', () => {
|
||||
expect(
|
||||
migrateToKibana660({
|
||||
id: 'space:foo',
|
||||
attributes: {},
|
||||
})
|
||||
migrateToKibana660(
|
||||
{
|
||||
id: 'space:foo',
|
||||
type: 'space',
|
||||
attributes: {},
|
||||
},
|
||||
mockContext
|
||||
)
|
||||
).toEqual({
|
||||
id: 'space:foo',
|
||||
type: 'space',
|
||||
attributes: {
|
||||
disabledFeatures: [],
|
||||
},
|
||||
|
@ -24,14 +32,19 @@ describe('migrateTo660', () => {
|
|||
it('does not initialize "disabledFeatures" if the property already exists', () => {
|
||||
// This scenario shouldn't happen organically. Protecting against defects in the migration.
|
||||
expect(
|
||||
migrateToKibana660({
|
||||
id: 'space:foo',
|
||||
attributes: {
|
||||
disabledFeatures: ['foo', 'bar', 'baz'],
|
||||
migrateToKibana660(
|
||||
{
|
||||
id: 'space:foo',
|
||||
type: 'space',
|
||||
attributes: {
|
||||
disabledFeatures: ['foo', 'bar', 'baz'],
|
||||
},
|
||||
},
|
||||
})
|
||||
mockContext
|
||||
)
|
||||
).toEqual({
|
||||
id: 'space:foo',
|
||||
type: 'space',
|
||||
attributes: {
|
||||
disabledFeatures: ['foo', 'bar', 'baz'],
|
||||
},
|
|
@ -4,9 +4,11 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
export function migrateToKibana660(doc: Record<string, any>) {
|
||||
import { SavedObjectMigrationFn } from 'src/core/server';
|
||||
|
||||
export const migrateToKibana660: SavedObjectMigrationFn = doc => {
|
||||
if (!doc.attributes.hasOwnProperty('disabledFeatures')) {
|
||||
doc.attributes.disabledFeatures = [];
|
||||
}
|
||||
return doc;
|
||||
}
|
||||
};
|
|
@ -4,19 +4,21 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { SavedObjectsClientWrapperFactory } from 'src/core/server';
|
||||
import {
|
||||
SavedObjectsClientWrapperFactory,
|
||||
SavedObjectsClientWrapperOptions,
|
||||
} from 'src/core/server';
|
||||
import { SpacesSavedObjectsClient } from './spaces_saved_objects_client';
|
||||
import { SpacesServiceSetup } from '../../spaces_service/spaces_service';
|
||||
import { SpacesServiceSetup } from '../spaces_service/spaces_service';
|
||||
|
||||
export function spacesSavedObjectsClientWrapperFactory(
|
||||
spacesService: SpacesServiceSetup,
|
||||
types: string[]
|
||||
spacesService: SpacesServiceSetup
|
||||
): SavedObjectsClientWrapperFactory {
|
||||
return ({ client, request }) =>
|
||||
return (options: SavedObjectsClientWrapperOptions) =>
|
||||
new SpacesSavedObjectsClient({
|
||||
baseClient: client,
|
||||
request,
|
||||
baseClient: options.client,
|
||||
request: options.request,
|
||||
spacesService,
|
||||
types,
|
||||
typeRegistry: options.typeRegistry,
|
||||
});
|
||||
}
|
|
@ -0,0 +1,82 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { coreMock } from 'src/core/server/mocks';
|
||||
import { spacesServiceMock } from '../spaces_service/spaces_service.mock';
|
||||
import { SpacesSavedObjectsService } from './saved_objects_service';
|
||||
|
||||
describe('SpacesSavedObjectsService', () => {
|
||||
describe('#setup', () => {
|
||||
it('registers the "space" saved object type with appropriate mappings and migrations', () => {
|
||||
const core = coreMock.createSetup();
|
||||
const spacesService = spacesServiceMock.createSetupContract();
|
||||
|
||||
const service = new SpacesSavedObjectsService();
|
||||
service.setup({ core, spacesService });
|
||||
|
||||
expect(core.savedObjects.registerType).toHaveBeenCalledTimes(1);
|
||||
expect(core.savedObjects.registerType.mock.calls[0]).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
Object {
|
||||
"hidden": true,
|
||||
"mappings": Object {
|
||||
"properties": Object {
|
||||
"_reserved": Object {
|
||||
"type": "boolean",
|
||||
},
|
||||
"color": Object {
|
||||
"type": "keyword",
|
||||
},
|
||||
"description": Object {
|
||||
"type": "text",
|
||||
},
|
||||
"disabledFeatures": Object {
|
||||
"type": "keyword",
|
||||
},
|
||||
"imageUrl": Object {
|
||||
"index": false,
|
||||
"type": "text",
|
||||
},
|
||||
"initials": Object {
|
||||
"type": "keyword",
|
||||
},
|
||||
"name": Object {
|
||||
"fields": Object {
|
||||
"keyword": Object {
|
||||
"ignore_above": 2048,
|
||||
"type": "keyword",
|
||||
},
|
||||
},
|
||||
"type": "text",
|
||||
},
|
||||
},
|
||||
},
|
||||
"migrations": Object {
|
||||
"6.6.0": [Function],
|
||||
},
|
||||
"name": "space",
|
||||
"namespaceAgnostic": true,
|
||||
},
|
||||
]
|
||||
`);
|
||||
});
|
||||
|
||||
it('registers the client wrapper', () => {
|
||||
const core = coreMock.createSetup();
|
||||
const spacesService = spacesServiceMock.createSetupContract();
|
||||
|
||||
const service = new SpacesSavedObjectsService();
|
||||
service.setup({ core, spacesService });
|
||||
|
||||
expect(core.savedObjects.addClientWrapper).toHaveBeenCalledTimes(1);
|
||||
expect(core.savedObjects.addClientWrapper).toHaveBeenCalledWith(
|
||||
Number.MIN_SAFE_INTEGER,
|
||||
'spaces',
|
||||
expect.any(Function)
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { CoreSetup } from 'src/core/server';
|
||||
import { SpacesSavedObjectMappings } from './mappings';
|
||||
import { migrateToKibana660 } from './migrations';
|
||||
import { spacesSavedObjectsClientWrapperFactory } from './saved_objects_client_wrapper_factory';
|
||||
import { SpacesServiceSetup } from '../spaces_service';
|
||||
|
||||
interface SetupDeps {
|
||||
core: Pick<CoreSetup, 'savedObjects' | 'getStartServices'>;
|
||||
spacesService: SpacesServiceSetup;
|
||||
}
|
||||
|
||||
export class SpacesSavedObjectsService {
|
||||
public setup({ core, spacesService }: SetupDeps) {
|
||||
core.savedObjects.registerType({
|
||||
name: 'space',
|
||||
hidden: true,
|
||||
namespaceAgnostic: true,
|
||||
mappings: SpacesSavedObjectMappings,
|
||||
migrations: {
|
||||
'6.6.0': migrateToKibana660,
|
||||
},
|
||||
});
|
||||
|
||||
core.savedObjects.addClientWrapper(
|
||||
Number.MIN_SAFE_INTEGER,
|
||||
'spaces',
|
||||
spacesSavedObjectsClientWrapperFactory(spacesService)
|
||||
);
|
||||
}
|
||||
}
|
|
@ -4,12 +4,33 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { DEFAULT_SPACE_ID } from '../../../common/constants';
|
||||
import { DEFAULT_SPACE_ID } from '../../common/constants';
|
||||
import { SpacesSavedObjectsClient } from './spaces_saved_objects_client';
|
||||
import { spacesServiceMock } from '../../spaces_service/spaces_service.mock';
|
||||
import { savedObjectsClientMock } from '../../../../../../src/core/server/mocks';
|
||||
import { spacesServiceMock } from '../spaces_service/spaces_service.mock';
|
||||
import { savedObjectsClientMock } from '../../../../../src/core/server/mocks';
|
||||
import { SavedObjectTypeRegistry } from 'src/core/server';
|
||||
|
||||
const types = ['foo', 'bar', 'space'];
|
||||
const typeRegistry = new SavedObjectTypeRegistry();
|
||||
typeRegistry.registerType({
|
||||
name: 'foo',
|
||||
namespaceAgnostic: false,
|
||||
hidden: false,
|
||||
mappings: { properties: {} },
|
||||
});
|
||||
|
||||
typeRegistry.registerType({
|
||||
name: 'bar',
|
||||
namespaceAgnostic: false,
|
||||
hidden: false,
|
||||
mappings: { properties: {} },
|
||||
});
|
||||
|
||||
typeRegistry.registerType({
|
||||
name: 'space',
|
||||
namespaceAgnostic: true,
|
||||
hidden: true,
|
||||
mappings: { properties: {} },
|
||||
});
|
||||
|
||||
const createMockRequest = () => ({});
|
||||
|
||||
|
@ -44,7 +65,7 @@ const createMockResponse = () => ({
|
|||
request,
|
||||
baseClient,
|
||||
spacesService,
|
||||
types,
|
||||
typeRegistry,
|
||||
});
|
||||
|
||||
await expect(
|
||||
|
@ -63,7 +84,7 @@ const createMockResponse = () => ({
|
|||
request,
|
||||
baseClient,
|
||||
spacesService,
|
||||
types,
|
||||
typeRegistry,
|
||||
});
|
||||
const type = Symbol();
|
||||
const id = Symbol();
|
||||
|
@ -89,7 +110,7 @@ const createMockResponse = () => ({
|
|||
request,
|
||||
baseClient,
|
||||
spacesService,
|
||||
types,
|
||||
typeRegistry,
|
||||
});
|
||||
|
||||
await expect(
|
||||
|
@ -110,7 +131,7 @@ const createMockResponse = () => ({
|
|||
request,
|
||||
baseClient,
|
||||
spacesService,
|
||||
types,
|
||||
typeRegistry,
|
||||
});
|
||||
|
||||
const objects = [{ type: 'foo' }];
|
||||
|
@ -136,7 +157,7 @@ const createMockResponse = () => ({
|
|||
request,
|
||||
baseClient,
|
||||
spacesService,
|
||||
types,
|
||||
typeRegistry,
|
||||
});
|
||||
|
||||
await expect(
|
||||
|
@ -160,7 +181,7 @@ const createMockResponse = () => ({
|
|||
request,
|
||||
baseClient,
|
||||
spacesService,
|
||||
types,
|
||||
typeRegistry,
|
||||
});
|
||||
const options = Object.freeze({ type: 'foo' });
|
||||
|
||||
|
@ -189,7 +210,7 @@ const createMockResponse = () => ({
|
|||
request,
|
||||
baseClient,
|
||||
spacesService,
|
||||
types,
|
||||
typeRegistry,
|
||||
});
|
||||
|
||||
const options = Object.freeze({ type: ['foo', 'bar'] });
|
||||
|
@ -213,7 +234,7 @@ const createMockResponse = () => ({
|
|||
request,
|
||||
baseClient,
|
||||
spacesService,
|
||||
types,
|
||||
typeRegistry,
|
||||
});
|
||||
|
||||
await expect(
|
||||
|
@ -232,7 +253,7 @@ const createMockResponse = () => ({
|
|||
request,
|
||||
baseClient,
|
||||
spacesService,
|
||||
types,
|
||||
typeRegistry,
|
||||
});
|
||||
|
||||
const type = Symbol();
|
||||
|
@ -259,7 +280,7 @@ const createMockResponse = () => ({
|
|||
request,
|
||||
baseClient,
|
||||
spacesService,
|
||||
types,
|
||||
typeRegistry,
|
||||
});
|
||||
|
||||
await expect(
|
||||
|
@ -280,7 +301,7 @@ const createMockResponse = () => ({
|
|||
request,
|
||||
baseClient,
|
||||
spacesService,
|
||||
types,
|
||||
typeRegistry,
|
||||
});
|
||||
|
||||
const objects = [{ type: 'foo' }];
|
||||
|
@ -306,7 +327,7 @@ const createMockResponse = () => ({
|
|||
request,
|
||||
baseClient,
|
||||
spacesService,
|
||||
types,
|
||||
typeRegistry,
|
||||
});
|
||||
|
||||
await expect(
|
||||
|
@ -326,7 +347,7 @@ const createMockResponse = () => ({
|
|||
request,
|
||||
baseClient,
|
||||
spacesService,
|
||||
types,
|
||||
typeRegistry,
|
||||
});
|
||||
|
||||
const type = Symbol();
|
||||
|
@ -358,7 +379,7 @@ const createMockResponse = () => ({
|
|||
request,
|
||||
baseClient,
|
||||
spacesService,
|
||||
types,
|
||||
typeRegistry,
|
||||
});
|
||||
|
||||
const actualReturnValue = await client.bulkUpdate([
|
||||
|
@ -390,7 +411,7 @@ const createMockResponse = () => ({
|
|||
request,
|
||||
baseClient,
|
||||
spacesService,
|
||||
types,
|
||||
typeRegistry,
|
||||
});
|
||||
|
||||
await expect(
|
||||
|
@ -410,7 +431,7 @@ const createMockResponse = () => ({
|
|||
request,
|
||||
baseClient,
|
||||
spacesService,
|
||||
types,
|
||||
typeRegistry,
|
||||
});
|
||||
|
||||
const type = Symbol();
|
|
@ -13,15 +13,16 @@ import {
|
|||
SavedObjectsCreateOptions,
|
||||
SavedObjectsFindOptions,
|
||||
SavedObjectsUpdateOptions,
|
||||
ISavedObjectTypeRegistry,
|
||||
} from 'src/core/server';
|
||||
import { SpacesServiceSetup } from '../../spaces_service/spaces_service';
|
||||
import { spaceIdToNamespace } from '../utils/namespace';
|
||||
import { SpacesServiceSetup } from '../spaces_service/spaces_service';
|
||||
import { spaceIdToNamespace } from '../lib/utils/namespace';
|
||||
|
||||
interface SpacesSavedObjectsClientOptions {
|
||||
baseClient: SavedObjectsClientContract;
|
||||
request: any;
|
||||
spacesService: SpacesServiceSetup;
|
||||
types: string[];
|
||||
typeRegistry: ISavedObjectTypeRegistry;
|
||||
}
|
||||
|
||||
const coerceToArray = (param: string | string[]) => {
|
||||
|
@ -45,11 +46,11 @@ export class SpacesSavedObjectsClient implements SavedObjectsClientContract {
|
|||
public readonly errors: SavedObjectsClientContract['errors'];
|
||||
|
||||
constructor(options: SpacesSavedObjectsClientOptions) {
|
||||
const { baseClient, request, spacesService, types } = options;
|
||||
const { baseClient, request, spacesService, typeRegistry } = options;
|
||||
|
||||
this.client = baseClient;
|
||||
this.spaceId = spacesService.getSpaceId(request);
|
||||
this.types = types;
|
||||
this.types = typeRegistry.getAllTypes().map(t => t.name);
|
||||
this.errors = baseClient.errors;
|
||||
}
|
||||
|
|
@ -5,58 +5,53 @@
|
|||
*/
|
||||
import * as Rx from 'rxjs';
|
||||
import { SpacesService } from './spaces_service';
|
||||
import {
|
||||
coreMock,
|
||||
elasticsearchServiceMock,
|
||||
httpServerMock,
|
||||
loggingServiceMock,
|
||||
} from 'src/core/server/mocks';
|
||||
import { coreMock, httpServerMock, loggingServiceMock } from 'src/core/server/mocks';
|
||||
import { SpacesAuditLogger } from '../lib/audit_logger';
|
||||
import {
|
||||
KibanaRequest,
|
||||
SavedObjectsLegacyService,
|
||||
SavedObjectsErrorHelpers,
|
||||
HttpServiceSetup,
|
||||
SavedObjectsRepository,
|
||||
} from 'src/core/server';
|
||||
import { DEFAULT_SPACE_ID } from '../../common/constants';
|
||||
import { getSpaceIdFromPath } from '../../common/lib/spaces_url_parser';
|
||||
import { LegacyAPI } from '../plugin';
|
||||
import { spacesConfig } from '../lib/__fixtures__';
|
||||
import { securityMock } from '../../../security/server/mocks';
|
||||
|
||||
const mockLogger = loggingServiceMock.createLogger();
|
||||
|
||||
const createService = async (serverBasePath: string = '') => {
|
||||
const legacyAPI = {
|
||||
savedObjects: ({
|
||||
getSavedObjectsRepository: jest.fn().mockReturnValue({
|
||||
get: jest.fn().mockImplementation((type, id) => {
|
||||
if (type === 'space' && id === 'foo') {
|
||||
return Promise.resolve({
|
||||
id: 'space:foo',
|
||||
attributes: {
|
||||
name: 'Foo Space',
|
||||
disabledFeatures: [],
|
||||
},
|
||||
});
|
||||
}
|
||||
if (type === 'space' && id === 'default') {
|
||||
return Promise.resolve({
|
||||
id: 'space:default',
|
||||
attributes: {
|
||||
name: 'Default Space',
|
||||
disabledFeatures: [],
|
||||
_reserved: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
throw SavedObjectsErrorHelpers.createGenericNotFoundError(type, id);
|
||||
}),
|
||||
}),
|
||||
} as unknown) as SavedObjectsLegacyService,
|
||||
} as LegacyAPI;
|
||||
const spacesService = new SpacesService(mockLogger);
|
||||
|
||||
const spacesService = new SpacesService(mockLogger, () => legacyAPI);
|
||||
const coreStart = coreMock.createStart();
|
||||
|
||||
const respositoryMock = ({
|
||||
get: jest.fn().mockImplementation((type, id) => {
|
||||
if (type === 'space' && id === 'foo') {
|
||||
return Promise.resolve({
|
||||
id: 'space:foo',
|
||||
attributes: {
|
||||
name: 'Foo Space',
|
||||
disabledFeatures: [],
|
||||
},
|
||||
});
|
||||
}
|
||||
if (type === 'space' && id === 'default') {
|
||||
return Promise.resolve({
|
||||
id: 'space:default',
|
||||
attributes: {
|
||||
name: 'Default Space',
|
||||
disabledFeatures: [],
|
||||
_reserved: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
throw SavedObjectsErrorHelpers.createGenericNotFoundError(type, id);
|
||||
}),
|
||||
} as unknown) as SavedObjectsRepository;
|
||||
|
||||
coreStart.savedObjects.createInternalRepository.mockReturnValue(respositoryMock);
|
||||
coreStart.savedObjects.createScopedRepository.mockReturnValue(respositoryMock);
|
||||
|
||||
const httpSetup = coreMock.createSetup().http;
|
||||
httpSetup.basePath = {
|
||||
|
@ -73,7 +68,7 @@ const createService = async (serverBasePath: string = '') => {
|
|||
|
||||
const spacesServiceSetup = await spacesService.setup({
|
||||
http: httpSetup,
|
||||
elasticsearch: elasticsearchServiceMock.createSetup(),
|
||||
getStartServices: async () => [coreStart, {}, {}],
|
||||
config$: Rx.of(spacesConfig),
|
||||
authorization: securityMock.createSetup().authz,
|
||||
getSpacesAuditLogger: () => new SpacesAuditLogger({}),
|
||||
|
|
|
@ -9,7 +9,6 @@ import { Observable, Subscription } from 'rxjs';
|
|||
import { Legacy } from 'kibana';
|
||||
import { Logger, KibanaRequest, CoreSetup } from '../../../../../src/core/server';
|
||||
import { SecurityPluginSetup } from '../../../security/server';
|
||||
import { LegacyAPI } from '../plugin';
|
||||
import { SpacesClient } from '../lib/spaces_client';
|
||||
import { ConfigType } from '../config';
|
||||
import { getSpaceIdFromPath, addSpaceIdToPath } from '../../common/lib/spaces_url_parser';
|
||||
|
@ -37,7 +36,7 @@ export interface SpacesServiceSetup {
|
|||
|
||||
interface SpacesServiceDeps {
|
||||
http: CoreSetup['http'];
|
||||
elasticsearch: CoreSetup['elasticsearch'];
|
||||
getStartServices: CoreSetup['getStartServices'];
|
||||
authorization: SecurityPluginSetup['authz'] | null;
|
||||
config$: Observable<ConfigType>;
|
||||
getSpacesAuditLogger(): any;
|
||||
|
@ -46,11 +45,11 @@ interface SpacesServiceDeps {
|
|||
export class SpacesService {
|
||||
private configSubscription$?: Subscription;
|
||||
|
||||
constructor(private readonly log: Logger, private readonly getLegacyAPI: () => LegacyAPI) {}
|
||||
constructor(private readonly log: Logger) {}
|
||||
|
||||
public async setup({
|
||||
http,
|
||||
elasticsearch,
|
||||
getStartServices,
|
||||
authorization,
|
||||
config$,
|
||||
getSpacesAuditLogger,
|
||||
|
@ -69,18 +68,15 @@ export class SpacesService {
|
|||
};
|
||||
|
||||
const getScopedClient = async (request: KibanaRequest) => {
|
||||
const [coreStart] = await getStartServices();
|
||||
|
||||
return config$
|
||||
.pipe(
|
||||
map(config => {
|
||||
const internalRepository = this.getLegacyAPI().savedObjects.getSavedObjectsRepository(
|
||||
elasticsearch.adminClient.callAsInternalUser,
|
||||
['space']
|
||||
);
|
||||
const internalRepository = coreStart.savedObjects.createInternalRepository(['space']);
|
||||
|
||||
const callCluster = elasticsearch.adminClient.asScoped(request).callAsCurrentUser;
|
||||
|
||||
const callWithRequestRepository = this.getLegacyAPI().savedObjects.getSavedObjectsRepository(
|
||||
callCluster,
|
||||
const callWithRequestRepository = coreStart.savedObjects.createScopedRepository(
|
||||
request,
|
||||
['space']
|
||||
);
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@ import expect from '@kbn/expect';
|
|||
import { FtrProviderContext } from '../../ftr_provider_context';
|
||||
|
||||
export default ({ getPageObjects, getService }: FtrProviderContext) => {
|
||||
const pageObjects = getPageObjects(['common', 'endpoint']);
|
||||
const pageObjects = getPageObjects(['common', 'endpoint', 'header']);
|
||||
const esArchiver = getService('esArchiver');
|
||||
const testSubjects = getService('testSubjects');
|
||||
|
||||
|
@ -18,6 +18,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
|
|||
before(async () => {
|
||||
await esArchiver.load('endpoint/metadata/api_feature');
|
||||
await pageObjects.common.navigateToUrlWithBrowserHistory('endpoint', '/hosts');
|
||||
await pageObjects.header.waitUntilLoadingHasFinished();
|
||||
});
|
||||
|
||||
it('finds title', async () => {
|
||||
|
@ -114,6 +115,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
|
|||
// clear out the data and reload the page
|
||||
await esArchiver.unload('endpoint/metadata/api_feature');
|
||||
await pageObjects.common.navigateToUrlWithBrowserHistory('endpoint', '/hosts');
|
||||
await pageObjects.header.waitUntilLoadingHasFinished();
|
||||
});
|
||||
after(async () => {
|
||||
// reload the data so the other tests continue to pass
|
||||
|
@ -135,6 +137,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
|
|||
'/hosts',
|
||||
'selected_host=fc0ff548-feba-41b6-8367-65e8790d0eaf'
|
||||
);
|
||||
await pageObjects.header.waitUntilLoadingHasFinished();
|
||||
});
|
||||
|
||||
it('shows a flyout', async () => {
|
||||
|
|
|
@ -9,11 +9,12 @@ import { FtrProviderContext } from '../../ftr_provider_context';
|
|||
export function UptimeNavigationProvider({ getService, getPageObjects }: FtrProviderContext) {
|
||||
const retry = getService('retry');
|
||||
const testSubjects = getService('testSubjects');
|
||||
const PageObjects = getPageObjects(['common']);
|
||||
const PageObjects = getPageObjects(['common', 'header']);
|
||||
|
||||
const goToUptimeRoot = async () => {
|
||||
await retry.tryForTime(30 * 1000, async () => {
|
||||
await PageObjects.common.navigateToApp('uptime');
|
||||
await PageObjects.header.waitUntilLoadingHasFinished();
|
||||
await testSubjects.existOrFail('uptimeOverviewPage', { timeout: 2000 });
|
||||
});
|
||||
};
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue