Remove legacy SavedObjects (#76852) (#76978)

* remove legacy SO integration

* cleanup integration in the legacy platorm

* remove so schema

* update docs

* remove leftovers, update docs

* update docs after merge master
This commit is contained in:
Mikhail Shustov 2020-09-09 08:06:18 +03:00 committed by GitHub
parent 6ffa3d12c3
commit 0d0e9b66d3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
44 changed files with 47 additions and 2026 deletions

View file

@ -16,8 +16,6 @@ export interface SavedObjectsServiceSetup
When plugins access the Saved Objects client, a new client is created using the factory provided to `setClientFactory` and wrapped by all wrappers registered through `addClientWrapper`<!-- -->.
All the setup APIs will throw if called after the service has started, and therefor cannot be used from legacy plugin code. Legacy plugins should use the legacy savedObject service until migrated.
## Example 1

View file

@ -14,10 +14,6 @@ See the [mappings format](./kibana-plugin-core-server.savedobjectstypemappingdef
registerType: (type: SavedObjectsType) => void;
```
## Remarks
The type definition is an aggregation of the legacy savedObjects `schema`<!-- -->, `mappings` and `migration` concepts. This API is the single entry point to register saved object types in the new platform.
## Example

2
kibana.d.ts vendored
View file

@ -39,8 +39,6 @@ export namespace Legacy {
export type KibanaConfig = LegacyKibanaServer.KibanaConfig;
export type Request = LegacyKibanaServer.Request;
export type ResponseToolkit = LegacyKibanaServer.ResponseToolkit;
export type SavedObjectsClient = LegacyKibanaServer.SavedObjectsClient;
export type SavedObjectsService = LegacyKibanaServer.SavedObjectsLegacyService;
export type Server = LegacyKibanaServer.Server;
export type InitPluginFunction = LegacyKibanaPluginSpec.InitPluginFunction;

View file

@ -266,9 +266,7 @@ export {
SavedObjectUnsanitizedDoc,
SavedObjectsRepositoryFactory,
SavedObjectsResolveImportErrorsOptions,
SavedObjectsSchema,
SavedObjectsSerializer,
SavedObjectsLegacyService,
SavedObjectsUpdateOptions,
SavedObjectsUpdateResponse,
SavedObjectsAddToNamespacesOptions,

View file

@ -24,13 +24,7 @@ type LegacyServiceMock = jest.Mocked<PublicMethodsOf<LegacyService> & { legacyId
const createDiscoverPluginsMock = (): LegacyServiceDiscoverPlugins => ({
pluginSpecs: [],
uiExports: {
savedObjectSchemas: {},
savedObjectMappings: [],
savedObjectMigrations: {},
savedObjectValidations: {},
savedObjectsManagement: {},
},
uiExports: {},
navLinks: [],
pluginExtendedConfig: {
get: jest.fn(),

View file

@ -341,11 +341,9 @@ export class LegacyService implements CoreService {
registerStaticDir: setupDeps.core.http.registerStaticDir,
},
hapiServer: setupDeps.core.http.server,
kibanaMigrator: startDeps.core.savedObjects.migrator,
uiPlugins: setupDeps.uiPlugins,
elasticsearch: setupDeps.core.elasticsearch,
rendering: setupDeps.core.rendering,
savedObjectsClientProvider: startDeps.core.savedObjects.clientProvider,
legacy: this.legacyInternals,
},
logger: this.coreContext.logger,

View file

@ -24,7 +24,6 @@ import { KibanaRequest, LegacyRequest } from '../http';
import { InternalCoreSetup, InternalCoreStart } from '../internal_types';
import { PluginsServiceSetup, PluginsServiceStart, UiPlugins } from '../plugins';
import { InternalRenderingServiceSetup } from '../rendering';
import { SavedObjectsLegacyUiExports } from '../types';
/**
* @internal
@ -128,13 +127,13 @@ export type LegacyNavLink = Omit<ChromeNavLink, 'baseUrl' | 'legacy' | 'order' |
* @internal
* @deprecated
*/
export type LegacyUiExports = SavedObjectsLegacyUiExports & {
export interface LegacyUiExports {
defaultInjectedVarProviders?: VarsProvider[];
injectedVarsReplacers?: VarsReplacer[];
navLinkSpecs?: LegacyNavLinkSpec[] | null;
uiAppSpecs?: Array<LegacyAppSpec | undefined>;
unknown?: [{ pluginSpec: LegacyPluginSpec; type: unknown }];
};
}
/**
* @public

View file

@ -1,184 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`convertLegacyTypes converts the legacy mappings using default values if no schemas are specified 1`] = `
Array [
Object {
"convertToAliasScript": undefined,
"hidden": false,
"indexPattern": undefined,
"management": undefined,
"mappings": Object {
"properties": Object {
"fieldA": Object {
"type": "text",
},
},
},
"migrations": Object {},
"name": "typeA",
"namespaceType": "single",
},
Object {
"convertToAliasScript": undefined,
"hidden": false,
"indexPattern": undefined,
"management": undefined,
"mappings": Object {
"properties": Object {
"fieldB": Object {
"type": "text",
},
},
},
"migrations": Object {},
"name": "typeB",
"namespaceType": "single",
},
Object {
"convertToAliasScript": undefined,
"hidden": false,
"indexPattern": undefined,
"management": undefined,
"mappings": Object {
"properties": Object {
"fieldC": Object {
"type": "text",
},
},
},
"migrations": Object {},
"name": "typeC",
"namespaceType": "single",
},
]
`;
exports[`convertLegacyTypes merges everything when all are present 1`] = `
Array [
Object {
"convertToAliasScript": undefined,
"hidden": true,
"indexPattern": "myIndex",
"management": undefined,
"mappings": Object {
"properties": Object {
"fieldA": Object {
"type": "text",
},
},
},
"migrations": Object {
"1.0.0": [Function],
"2.0.4": [Function],
},
"name": "typeA",
"namespaceType": "agnostic",
},
Object {
"convertToAliasScript": "some alias script",
"hidden": false,
"indexPattern": undefined,
"management": undefined,
"mappings": Object {
"properties": Object {
"anotherFieldB": Object {
"type": "boolean",
},
"fieldB": Object {
"type": "text",
},
},
},
"migrations": Object {},
"name": "typeB",
"namespaceType": "single",
},
Object {
"convertToAliasScript": undefined,
"hidden": false,
"indexPattern": undefined,
"management": undefined,
"mappings": Object {
"properties": Object {
"fieldC": Object {
"type": "text",
},
},
},
"migrations": Object {
"1.5.3": [Function],
},
"name": "typeC",
"namespaceType": "single",
},
]
`;
exports[`convertLegacyTypes merges the mappings and the schema to create the type when schema exists for the type 1`] = `
Array [
Object {
"convertToAliasScript": undefined,
"hidden": true,
"indexPattern": "fooBar",
"management": undefined,
"mappings": Object {
"properties": Object {
"fieldA": Object {
"type": "text",
},
},
},
"migrations": Object {},
"name": "typeA",
"namespaceType": "agnostic",
},
Object {
"convertToAliasScript": undefined,
"hidden": false,
"indexPattern": "barBaz",
"management": undefined,
"mappings": Object {
"properties": Object {
"fieldB": Object {
"type": "text",
},
},
},
"migrations": Object {},
"name": "typeB",
"namespaceType": "multiple",
},
Object {
"convertToAliasScript": undefined,
"hidden": false,
"indexPattern": undefined,
"management": undefined,
"mappings": Object {
"properties": Object {
"fieldC": Object {
"type": "text",
},
},
},
"migrations": Object {},
"name": "typeC",
"namespaceType": "single",
},
Object {
"convertToAliasScript": undefined,
"hidden": false,
"indexPattern": "bazQux",
"management": undefined,
"mappings": Object {
"properties": Object {
"fieldD": Object {
"type": "text",
},
},
},
"migrations": Object {},
"name": "typeD",
"namespaceType": "agnostic",
},
]
`;

View file

@ -19,8 +19,6 @@
export * from './service';
export { SavedObjectsSchema } from './schema';
export * from './import';
export {

View file

@ -48,7 +48,6 @@ describe('DocumentMigrator', () => {
return {
kibanaVersion: '25.2.3',
typeRegistry: createRegistry(),
validateDoc: _.noop,
log: mockLogger,
};
}
@ -60,7 +59,6 @@ describe('DocumentMigrator', () => {
name: 'foo',
migrations: _.noop as any,
}),
validateDoc: _.noop,
log: mockLogger,
};
expect(() => new DocumentMigrator(invalidDefinition)).toThrow(
@ -77,7 +75,6 @@ describe('DocumentMigrator', () => {
bar: (doc) => doc,
},
}),
validateDoc: _.noop,
log: mockLogger,
};
expect(() => new DocumentMigrator(invalidDefinition)).toThrow(
@ -94,7 +91,6 @@ describe('DocumentMigrator', () => {
'1.2.3': 23 as any,
},
}),
validateDoc: _.noop,
log: mockLogger,
};
expect(() => new DocumentMigrator(invalidDefinition)).toThrow(
@ -633,27 +629,6 @@ describe('DocumentMigrator', () => {
bbb: '3.2.3',
});
});
test('fails if the validate doc throws', () => {
const migrator = new DocumentMigrator({
...testOpts(),
typeRegistry: createRegistry({
name: 'aaa',
migrations: {
'2.3.4': (d) => set(d, 'attributes.counter', 42),
},
}),
validateDoc: (d) => {
if ((d.attributes as any).counter === 42) {
throw new Error('Meaningful!');
}
},
});
const doc = { id: '1', type: 'foo', attributes: {}, migrationVersion: {}, aaa: {} };
expect(() => migrator.migrate(doc)).toThrow(/Meaningful/);
});
});
function renameAttr(path: string, newPath: string) {

View file

@ -73,12 +73,9 @@ import { SavedObjectMigrationFn } from '../types';
export type TransformFn = (doc: SavedObjectUnsanitizedDoc) => SavedObjectUnsanitizedDoc;
type ValidateDoc = (doc: SavedObjectUnsanitizedDoc) => void;
interface DocumentMigratorOptions {
kibanaVersion: string;
typeRegistry: ISavedObjectTypeRegistry;
validateDoc: ValidateDoc;
log: Logger;
}
@ -113,19 +110,16 @@ export class DocumentMigrator implements VersionedTransformer {
* @param {DocumentMigratorOptions} opts
* @prop {string} kibanaVersion - The current version of Kibana
* @prop {SavedObjectTypeRegistry} typeRegistry - The type registry to get type migrations from
* @prop {ValidateDoc} validateDoc - A function which, given a document throws an error if it is
* not up to date. This is used to ensure we don't let unmigrated documents slip through.
* @prop {Logger} log - The migration logger
* @memberof DocumentMigrator
*/
constructor({ typeRegistry, kibanaVersion, log, validateDoc }: DocumentMigratorOptions) {
constructor({ typeRegistry, kibanaVersion, log }: DocumentMigratorOptions) {
validateMigrationDefinition(typeRegistry);
this.migrations = buildActiveMigrations(typeRegistry, log);
this.transformDoc = buildDocumentTransform({
kibanaVersion,
migrations: this.migrations,
validateDoc,
});
}
@ -231,21 +225,16 @@ function buildActiveMigrations(
* Creates a function which migrates and validates any document that is passed to it.
*/
function buildDocumentTransform({
kibanaVersion,
migrations,
validateDoc,
}: {
kibanaVersion: string;
migrations: ActiveMigrations;
validateDoc: ValidateDoc;
}): TransformFn {
return function transformAndValidate(doc: SavedObjectUnsanitizedDoc) {
const result = doc.migrationVersion
? applyMigrations(doc, migrations)
: markAsUpToDate(doc, migrations);
validateDoc(result);
// In order to keep tests a bit more stable, we won't
// tack on an empy migrationVersion to docs that have
// no migrations defined.

View file

@ -134,7 +134,6 @@ const mockOptions = () => {
const options: MockedOptions = {
logger: loggingSystemMock.create().get(),
kibanaVersion: '8.2.3',
savedObjectValidations: {},
typeRegistry: createRegistry([
{
name: 'testtype',

View file

@ -28,7 +28,6 @@ import { BehaviorSubject } from 'rxjs';
import { Logger } from '../../../logging';
import { IndexMapping, SavedObjectsTypeMappingDefinitions } from '../../mappings';
import { SavedObjectUnsanitizedDoc, SavedObjectsSerializer } from '../../serialization';
import { docValidator, PropertyValidators } from '../../validation';
import { buildActiveMappings, IndexMigrator, MigrationResult, MigrationStatus } from '../core';
import { DocumentMigrator, VersionedTransformer } from '../core/document_migrator';
import { MigrationEsClient } from '../core/';
@ -44,7 +43,6 @@ export interface KibanaMigratorOptions {
kibanaConfig: KibanaConfigType;
kibanaVersion: string;
logger: Logger;
savedObjectValidations: PropertyValidators;
}
export type IKibanaMigrator = Pick<KibanaMigrator, keyof KibanaMigrator>;
@ -80,7 +78,6 @@ export class KibanaMigrator {
typeRegistry,
kibanaConfig,
savedObjectsConfig,
savedObjectValidations,
kibanaVersion,
logger,
}: KibanaMigratorOptions) {
@ -94,7 +91,6 @@ export class KibanaMigrator {
this.documentMigrator = new DocumentMigrator({
kibanaVersion,
typeRegistry,
validateDoc: docValidator(savedObjectValidations || {}),
log: this.log,
});
// Building the active mappings (and associated md5sums) is an expensive

View file

@ -26,8 +26,7 @@ import {
SavedObjectsServiceSetup,
SavedObjectsServiceStart,
} from './saved_objects_service';
import { mockKibanaMigrator } from './migrations/kibana/kibana_migrator.mock';
import { savedObjectsClientProviderMock } from './service/lib/scoped_client_provider.mock';
import { savedObjectsRepositoryMock } from './service/lib/repository.mock';
import { savedObjectsClientMock } from './service/saved_objects_client.mock';
import { typeRegistryMock } from './saved_objects_type_registry.mock';
@ -54,11 +53,7 @@ const createStartContractMock = () => {
};
const createInternalStartContractMock = () => {
const internalStartContract: jest.Mocked<InternalSavedObjectsServiceStart> = {
...createStartContractMock(),
clientProvider: savedObjectsClientProviderMock.create(),
migrator: mockKibanaMigrator.create(),
};
const internalStartContract: jest.Mocked<InternalSavedObjectsServiceStart> = createStartContractMock();
return internalStartContract;
};

View file

@ -33,7 +33,6 @@ import { Env } from '../config';
import { configServiceMock } from '../mocks';
import { elasticsearchServiceMock } from '../elasticsearch/elasticsearch_service.mock';
import { elasticsearchClientMock } from '../elasticsearch/client/mocks';
import { legacyServiceMock } from '../legacy/legacy_service.mock';
import { httpServiceMock } from '../http/http_service.mock';
import { httpServerMock } from '../http/http_server.mocks';
import { SavedObjectsClientFactoryProvider } from './service/lib';
@ -65,7 +64,6 @@ describe('SavedObjectsService', () => {
return {
http: httpServiceMock.createInternalSetupContract(),
elasticsearch: elasticsearchMock,
legacyPlugins: legacyServiceMock.createDiscoverPlugins(),
};
};
@ -239,8 +237,7 @@ describe('SavedObjectsService', () => {
await soService.setup(createSetupDeps());
expect(migratorInstanceMock.runMigrations).toHaveBeenCalledTimes(0);
const startContract = await soService.start(createStartDeps());
expect(startContract.migrator).toBe(migratorInstanceMock);
await soService.start(createStartDeps());
expect(migratorInstanceMock.runMigrations).toHaveBeenCalledTimes(1);
});

View file

@ -23,12 +23,10 @@ import { CoreService } from '../../types';
import {
SavedObjectsClient,
SavedObjectsClientProvider,
ISavedObjectsClientProvider,
SavedObjectsClientProviderOptions,
} from './';
import { KibanaMigrator, IKibanaMigrator } from './migrations';
import { CoreContext } from '../core_context';
import { LegacyServiceDiscoverPlugins } from '../legacy';
import {
ElasticsearchClient,
IClusterClient,
@ -49,9 +47,7 @@ import {
SavedObjectsClientWrapperFactory,
} from './service/lib/scoped_client_provider';
import { Logger } from '../logging';
import { convertLegacyTypes } from './utils';
import { SavedObjectTypeRegistry, ISavedObjectTypeRegistry } from './saved_objects_type_registry';
import { PropertyValidators } from './validation';
import { SavedObjectsSerializer } from './serialization';
import { registerRoutes } from './routes';
import { ServiceStatus } from '../status';
@ -67,9 +63,6 @@ import { createMigrationEsClient } from './migrations/core/';
* the factory provided to `setClientFactory` and wrapped by all wrappers
* registered through `addClientWrapper`.
*
* All the setup APIs will throw if called after the service has started, and therefor cannot be used
* from legacy plugin code. Legacy plugins should use the legacy savedObject service until migrated.
*
* @example
* ```ts
* import { SavedObjectsClient, CoreSetup } from 'src/core/server';
@ -155,9 +148,6 @@ export interface SavedObjectsServiceSetup {
* }
* }
* ```
*
* @remarks The type definition is an aggregation of the legacy savedObjects `schema`, `mappings` and `migration` concepts.
* This API is the single entry point to register saved object types in the new platform.
*/
registerType: (type: SavedObjectsType) => void;
@ -230,16 +220,7 @@ export interface SavedObjectsServiceStart {
getTypeRegistry: () => ISavedObjectTypeRegistry;
}
export interface InternalSavedObjectsServiceStart extends SavedObjectsServiceStart {
/**
* @deprecated Exposed only for injecting into Legacy
*/
migrator: IKibanaMigrator;
/**
* @deprecated Exposed only for injecting into Legacy
*/
clientProvider: ISavedObjectsClientProvider;
}
export type InternalSavedObjectsServiceStart = SavedObjectsServiceStart;
/**
* Factory provided when invoking a {@link SavedObjectsClientFactoryProvider | client factory provider}
@ -271,7 +252,6 @@ export interface SavedObjectsRepositoryFactory {
/** @internal */
export interface SavedObjectsSetupDeps {
http: InternalHttpServiceSetup;
legacyPlugins: LegacyServiceDiscoverPlugins;
elasticsearch: InternalElasticsearchServiceSetup;
}
@ -296,9 +276,8 @@ export class SavedObjectsService
private clientFactoryProvider?: SavedObjectsClientFactoryProvider;
private clientFactoryWrappers: WrappedClientFactoryWrapper[] = [];
private migrator$ = new Subject<KibanaMigrator>();
private migrator$ = new Subject<IKibanaMigrator>();
private typeRegistry = new SavedObjectTypeRegistry();
private validations: PropertyValidators = {};
private started = false;
constructor(private readonly coreContext: CoreContext) {
@ -310,13 +289,6 @@ export class SavedObjectsService
this.setupDeps = setupDeps;
const legacyTypes = convertLegacyTypes(
setupDeps.legacyPlugins.uiExports,
setupDeps.legacyPlugins.pluginExtendedConfig
);
legacyTypes.forEach((type) => this.typeRegistry.registerType(type));
this.validations = setupDeps.legacyPlugins.uiExports.savedObjectValidations || {};
const savedObjectsConfig = await this.coreContext.configService
.atPath<SavedObjectsConfigType>('savedObjects')
.pipe(first())
@ -471,8 +443,6 @@ export class SavedObjectsService
this.started = true;
return {
migrator,
clientProvider,
getScopedClient: clientProvider.getClient.bind(clientProvider),
createScopedRepository: repositoryFactory.createScopedRepository,
createInternalRepository: repositoryFactory.createInternalRepository,
@ -488,13 +458,12 @@ export class SavedObjectsService
savedObjectsConfig: SavedObjectsMigrationConfigType,
client: IClusterClient,
migrationsRetryDelay?: number
): KibanaMigrator {
): IKibanaMigrator {
return new KibanaMigrator({
typeRegistry: this.typeRegistry,
logger: this.logger,
kibanaVersion: this.coreContext.env.packageInfo.version,
savedObjectsConfig,
savedObjectValidations: this.validations,
kibanaConfig,
client: createMigrationEsClient(client.asInternalUser, this.logger, migrationsRetryDelay),
});

View file

@ -1,20 +0,0 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
export { SavedObjectsSchema, SavedObjectsSchemaDefinition } from './schema';

View file

@ -1,106 +0,0 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { SavedObjectsSchema, SavedObjectsSchemaDefinition } from './schema';
describe('#isNamespaceAgnostic', () => {
const expectResult = (expected: boolean, schemaDefinition?: SavedObjectsSchemaDefinition) => {
const schema = new SavedObjectsSchema(schemaDefinition);
const result = schema.isNamespaceAgnostic('foo');
expect(result).toBe(expected);
};
it(`returns false when no schema is defined`, () => {
expectResult(false);
});
it(`returns false for unknown types`, () => {
expectResult(false, { bar: {} });
});
it(`returns false for non-namespace-agnostic type`, () => {
expectResult(false, { foo: { isNamespaceAgnostic: false } });
expectResult(false, { foo: { isNamespaceAgnostic: undefined } });
});
it(`returns true for explicitly namespace-agnostic type`, () => {
expectResult(true, { foo: { isNamespaceAgnostic: true } });
});
});
describe('#isSingleNamespace', () => {
const expectResult = (expected: boolean, schemaDefinition?: SavedObjectsSchemaDefinition) => {
const schema = new SavedObjectsSchema(schemaDefinition);
const result = schema.isSingleNamespace('foo');
expect(result).toBe(expected);
};
it(`returns true when no schema is defined`, () => {
expectResult(true);
});
it(`returns true for unknown types`, () => {
expectResult(true, { bar: {} });
});
it(`returns false for explicitly namespace-agnostic type`, () => {
expectResult(false, { foo: { isNamespaceAgnostic: true } });
});
it(`returns false for explicitly multi-namespace type`, () => {
expectResult(false, { foo: { multiNamespace: true } });
});
it(`returns true for non-namespace-agnostic and non-multi-namespace type`, () => {
expectResult(true, { foo: { isNamespaceAgnostic: false, multiNamespace: false } });
expectResult(true, { foo: { isNamespaceAgnostic: false, multiNamespace: undefined } });
expectResult(true, { foo: { isNamespaceAgnostic: undefined, multiNamespace: false } });
expectResult(true, { foo: { isNamespaceAgnostic: undefined, multiNamespace: undefined } });
});
});
describe('#isMultiNamespace', () => {
const expectResult = (expected: boolean, schemaDefinition?: SavedObjectsSchemaDefinition) => {
const schema = new SavedObjectsSchema(schemaDefinition);
const result = schema.isMultiNamespace('foo');
expect(result).toBe(expected);
};
it(`returns false when no schema is defined`, () => {
expectResult(false);
});
it(`returns false for unknown types`, () => {
expectResult(false, { bar: {} });
});
it(`returns false for explicitly namespace-agnostic type`, () => {
expectResult(false, { foo: { isNamespaceAgnostic: true } });
});
it(`returns false for non-multi-namespace type`, () => {
expectResult(false, { foo: { multiNamespace: false } });
expectResult(false, { foo: { multiNamespace: undefined } });
});
it(`returns true for non-namespace-agnostic and explicitly multi-namespace type`, () => {
expectResult(true, { foo: { isNamespaceAgnostic: false, multiNamespace: true } });
expectResult(true, { foo: { isNamespaceAgnostic: undefined, multiNamespace: true } });
});
});

View file

@ -1,116 +0,0 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { LegacyConfig } from '../../legacy';
/**
* @deprecated
* @internal
**/
interface SavedObjectsSchemaTypeDefinition {
isNamespaceAgnostic?: boolean;
multiNamespace?: boolean;
hidden?: boolean;
indexPattern?: ((config: LegacyConfig) => string) | string;
convertToAliasScript?: string;
}
/**
* @deprecated
* @internal
**/
export interface SavedObjectsSchemaDefinition {
[type: string]: SavedObjectsSchemaTypeDefinition;
}
/**
* @deprecated This is only used by the {@link SavedObjectsLegacyService | legacy savedObjects service}
* @internal
**/
export class SavedObjectsSchema {
private readonly definition?: SavedObjectsSchemaDefinition;
constructor(schemaDefinition?: SavedObjectsSchemaDefinition) {
this.definition = schemaDefinition;
}
public isHiddenType(type: string) {
if (this.definition && this.definition.hasOwnProperty(type)) {
return Boolean(this.definition[type].hidden);
}
return false;
}
public getIndexForType(config: LegacyConfig, type: string): string | undefined {
if (this.definition != null && this.definition.hasOwnProperty(type)) {
const { indexPattern } = this.definition[type];
return typeof indexPattern === 'function' ? indexPattern(config) : indexPattern;
} else {
return undefined;
}
}
public getConvertToAliasScript(type: string): string | undefined {
if (this.definition != null && this.definition.hasOwnProperty(type)) {
return this.definition[type].convertToAliasScript;
}
}
public isNamespaceAgnostic(type: string) {
// if no plugins have registered a Saved Objects Schema,
// this.schema will be undefined, and no types are namespace agnostic
if (!this.definition) {
return false;
}
const typeSchema = this.definition[type];
if (!typeSchema) {
return false;
}
return Boolean(typeSchema.isNamespaceAgnostic);
}
public isSingleNamespace(type: string) {
// if no plugins have registered a Saved Objects Schema,
// this.schema will be undefined, and all types are namespace isolated
if (!this.definition) {
return true;
}
const typeSchema = this.definition[type];
if (!typeSchema) {
return true;
}
return !Boolean(typeSchema.isNamespaceAgnostic) && !Boolean(typeSchema.multiNamespace);
}
public isMultiNamespace(type: string) {
// if no plugins have registered a Saved Objects Schema,
// this.schema will be undefined, and no types are multi-namespace
if (!this.definition) {
return false;
}
const typeSchema = this.definition[type];
if (!typeSchema) {
return false;
}
return !Boolean(typeSchema.isNamespaceAgnostic) && Boolean(typeSchema.multiNamespace);
}
}

View file

@ -17,37 +17,6 @@
* under the License.
*/
import { Readable } from 'stream';
import { SavedObjectsClientProvider } from './lib';
import { SavedObjectsClient } from './saved_objects_client';
import { SavedObjectsExportOptions } from '../export';
import { SavedObjectsImportOptions, SavedObjectsImportResponse } from '../import';
import { SavedObjectsSchema } from '../schema';
import { SavedObjectsResolveImportErrorsOptions } from '../import/types';
/**
* @internal
* @deprecated
*/
export interface SavedObjectsLegacyService {
// ATTENTION: these types are incomplete
addScopedSavedObjectsClientWrapperFactory: SavedObjectsClientProvider['addClientWrapperFactory'];
setScopedSavedObjectsClientFactory: SavedObjectsClientProvider['setClientFactory'];
getScopedSavedObjectsClient: SavedObjectsClientProvider['getClient'];
SavedObjectsClient: typeof SavedObjectsClient;
types: string[];
schema: SavedObjectsSchema;
getSavedObjectsRepository(...rest: any[]): any;
importExport: {
objectLimit: number;
importSavedObjects(options: SavedObjectsImportOptions): Promise<SavedObjectsImportResponse>;
resolveImportErrors(
options: SavedObjectsResolveImportErrorsOptions
): Promise<SavedObjectsImportResponse>;
getSortedObjectsForExport(options: SavedObjectsExportOptions): Promise<Readable>;
};
}
export {
SavedObjectsRepository,
SavedObjectsClientProvider,

View file

@ -153,7 +153,6 @@ describe('SavedObjectsRepository', () => {
typeRegistry: registry,
kibanaVersion: '2.0.0',
log: {},
validateDoc: jest.fn(),
});
const getMockGetResponse = ({ type, id, references, namespace, originId }) => ({

View file

@ -31,7 +31,7 @@ import { getSearchDsl } from './search_dsl';
import { includedFields } from './included_fields';
import { SavedObjectsErrorHelpers, DecoratedError } from './errors';
import { decodeRequestVersion, encodeVersion, encodeHitVersion } from '../../version';
import { KibanaMigrator } from '../../migrations';
import { IKibanaMigrator } from '../../migrations';
import {
SavedObjectsSerializer,
SavedObjectSanitizedDoc,
@ -85,7 +85,7 @@ export interface SavedObjectsRepositoryOptions {
client: ElasticsearchClient;
typeRegistry: SavedObjectTypeRegistry;
serializer: SavedObjectsSerializer;
migrator: KibanaMigrator;
migrator: IKibanaMigrator;
allowedTypes: string[];
}
@ -120,7 +120,7 @@ export type ISavedObjectsRepository = Pick<SavedObjectsRepository, keyof SavedOb
* @public
*/
export class SavedObjectsRepository {
private _migrator: KibanaMigrator;
private _migrator: IKibanaMigrator;
private _index: string;
private _mappings: IndexMapping;
private _registry: SavedObjectTypeRegistry;
@ -137,7 +137,7 @@ export class SavedObjectsRepository {
* @internal
*/
public static createRepository(
migrator: KibanaMigrator,
migrator: IKibanaMigrator,
typeRegistry: SavedObjectTypeRegistry,
indexName: string,
client: ElasticsearchClient,

View file

@ -18,9 +18,8 @@
*/
import { SavedObjectsClient } from './service/saved_objects_client';
import { SavedObjectsTypeMappingDefinition, SavedObjectsTypeMappingDefinitions } from './mappings';
import { SavedObjectsTypeMappingDefinition } from './mappings';
import { SavedObjectMigrationMap } from './migrations';
import { PropertyValidators } from './validation';
export {
SavedObjectsImportResponse,
@ -34,9 +33,6 @@ export {
SavedObjectsImportRetry,
} from './import/types';
import { LegacyConfig } from '../legacy';
import { SavedObjectUnsanitizedDoc } from './serialization';
import { SavedObjectsMigrationLogger } from './migrations/core/migration_logger';
import { SavedObject } from '../../types';
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
@ -269,92 +265,3 @@ export interface SavedObjectsTypeManagementDefinition {
*/
getInAppUrl?: (savedObject: SavedObject<any>) => { path: string; uiCapabilitiesPath: string };
}
/**
* @internal
* @deprecated
*/
export interface SavedObjectsLegacyUiExports {
savedObjectMappings: SavedObjectsLegacyMapping[];
savedObjectMigrations: SavedObjectsLegacyMigrationDefinitions;
savedObjectSchemas: SavedObjectsLegacySchemaDefinitions;
savedObjectValidations: PropertyValidators;
savedObjectsManagement: SavedObjectsLegacyManagementDefinition;
}
/**
* @internal
* @deprecated
*/
export interface SavedObjectsLegacyMapping {
pluginId: string;
properties: SavedObjectsTypeMappingDefinitions;
}
/**
* @internal
* @deprecated Use {@link SavedObjectsTypeManagementDefinition | management definition} when registering
* from new platform plugins
*/
export interface SavedObjectsLegacyManagementDefinition {
[key: string]: SavedObjectsLegacyManagementTypeDefinition;
}
/**
* @internal
* @deprecated
*/
export interface SavedObjectsLegacyManagementTypeDefinition {
isImportableAndExportable?: boolean;
defaultSearchField?: string;
icon?: string;
getTitle?: (savedObject: SavedObject<any>) => string;
getEditUrl?: (savedObject: SavedObject<any>) => string;
getInAppUrl?: (savedObject: SavedObject<any>) => { path: string; uiCapabilitiesPath: string };
}
/**
* @internal
* @deprecated
*/
export interface SavedObjectsLegacyMigrationDefinitions {
[type: string]: SavedObjectLegacyMigrationMap;
}
/**
* @internal
* @deprecated
*/
export interface SavedObjectLegacyMigrationMap {
[version: string]: SavedObjectLegacyMigrationFn;
}
/**
* @internal
* @deprecated
*/
export type SavedObjectLegacyMigrationFn = (
doc: SavedObjectUnsanitizedDoc,
log: SavedObjectsMigrationLogger
) => SavedObjectUnsanitizedDoc;
/**
* @internal
* @deprecated
*/
interface SavedObjectsLegacyTypeSchema {
isNamespaceAgnostic?: boolean;
/** Cannot be used in conjunction with `isNamespaceAgnostic` */
multiNamespace?: boolean;
hidden?: boolean;
indexPattern?: ((config: LegacyConfig) => string) | string;
convertToAliasScript?: string;
}
/**
* @internal
* @deprecated
*/
export interface SavedObjectsLegacySchemaDefinitions {
[type: string]: SavedObjectsLegacyTypeSchema;
}

View file

@ -1,445 +0,0 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { legacyServiceMock } from '../legacy/legacy_service.mock';
import { convertLegacyTypes, convertTypesToLegacySchema } from './utils';
import { SavedObjectsLegacyUiExports, SavedObjectsType } from './types';
import { LegacyConfig, SavedObjectMigrationContext } from 'kibana/server';
import { SavedObjectUnsanitizedDoc } from './serialization';
describe('convertLegacyTypes', () => {
let legacyConfig: ReturnType<typeof legacyServiceMock.createLegacyConfig>;
beforeEach(() => {
legacyConfig = legacyServiceMock.createLegacyConfig();
});
it('converts the legacy mappings using default values if no schemas are specified', () => {
const uiExports: SavedObjectsLegacyUiExports = {
savedObjectMappings: [
{
pluginId: 'pluginA',
properties: {
typeA: {
properties: {
fieldA: { type: 'text' },
},
},
typeB: {
properties: {
fieldB: { type: 'text' },
},
},
},
},
{
pluginId: 'pluginB',
properties: {
typeC: {
properties: {
fieldC: { type: 'text' },
},
},
},
},
],
savedObjectMigrations: {},
savedObjectSchemas: {},
savedObjectValidations: {},
savedObjectsManagement: {},
};
const converted = convertLegacyTypes(uiExports, legacyConfig);
expect(converted).toMatchSnapshot();
});
it('merges the mappings and the schema to create the type when schema exists for the type', () => {
const uiExports: SavedObjectsLegacyUiExports = {
savedObjectMappings: [
{
pluginId: 'pluginA',
properties: {
typeA: {
properties: {
fieldA: { type: 'text' },
},
},
},
},
{
pluginId: 'pluginB',
properties: {
typeB: {
properties: {
fieldB: { type: 'text' },
},
},
},
},
{
pluginId: 'pluginC',
properties: {
typeC: {
properties: {
fieldC: { type: 'text' },
},
},
},
},
{
pluginId: 'pluginD',
properties: {
typeD: {
properties: {
fieldD: { type: 'text' },
},
},
},
},
],
savedObjectMigrations: {},
savedObjectSchemas: {
typeA: {
indexPattern: 'fooBar',
hidden: true,
isNamespaceAgnostic: true,
},
typeB: {
indexPattern: 'barBaz',
hidden: false,
multiNamespace: true,
},
typeD: {
indexPattern: 'bazQux',
hidden: false,
// if both isNamespaceAgnostic and multiNamespace are true, the resulting namespaceType is 'agnostic'
isNamespaceAgnostic: true,
multiNamespace: true,
},
},
savedObjectValidations: {},
savedObjectsManagement: {},
};
const converted = convertLegacyTypes(uiExports, legacyConfig);
expect(converted).toMatchSnapshot();
});
it('invokes indexPattern to retrieve the index when it is a function', () => {
const indexPatternAccessor: (config: LegacyConfig) => string = jest.fn((config) => {
config.get('foo.bar');
return 'myIndex';
});
const uiExports: SavedObjectsLegacyUiExports = {
savedObjectMappings: [
{
pluginId: 'pluginA',
properties: {
typeA: {
properties: {
fieldA: { type: 'text' },
},
},
},
},
],
savedObjectMigrations: {},
savedObjectSchemas: {
typeA: {
indexPattern: indexPatternAccessor,
hidden: true,
isNamespaceAgnostic: true,
},
},
savedObjectValidations: {},
savedObjectsManagement: {},
};
const converted = convertLegacyTypes(uiExports, legacyConfig);
expect(indexPatternAccessor).toHaveBeenCalledWith(legacyConfig);
expect(legacyConfig.get).toHaveBeenCalledWith('foo.bar');
expect(converted.length).toEqual(1);
expect(converted[0].indexPattern).toEqual('myIndex');
});
it('import migrations from the uiExports', () => {
const migrationsA = {
'1.0.0': jest.fn(),
'2.0.4': jest.fn(),
};
const migrationsB = {
'1.5.3': jest.fn(),
};
const uiExports: SavedObjectsLegacyUiExports = {
savedObjectMappings: [
{
pluginId: 'pluginA',
properties: {
typeA: {
properties: {
fieldA: { type: 'text' },
},
},
},
},
{
pluginId: 'pluginB',
properties: {
typeB: {
properties: {
fieldC: { type: 'text' },
},
},
},
},
],
savedObjectMigrations: {
typeA: migrationsA,
typeB: migrationsB,
},
savedObjectSchemas: {},
savedObjectValidations: {},
savedObjectsManagement: {},
};
const converted = convertLegacyTypes(uiExports, legacyConfig);
expect(converted.length).toEqual(2);
expect(Object.keys(converted[0]!.migrations!)).toEqual(Object.keys(migrationsA));
expect(Object.keys(converted[1]!.migrations!)).toEqual(Object.keys(migrationsB));
});
it('converts the migration to the new format', () => {
const legacyMigration = jest.fn();
const migrationsA = {
'1.0.0': legacyMigration,
};
const uiExports: SavedObjectsLegacyUiExports = {
savedObjectMappings: [
{
pluginId: 'pluginA',
properties: {
typeA: {
properties: {
fieldA: { type: 'text' },
},
},
},
},
],
savedObjectMigrations: {
typeA: migrationsA,
},
savedObjectSchemas: {},
savedObjectValidations: {},
savedObjectsManagement: {},
};
const converted = convertLegacyTypes(uiExports, legacyConfig);
expect(Object.keys(converted[0]!.migrations!)).toEqual(['1.0.0']);
const migration = converted[0]!.migrations!['1.0.0']!;
const doc = {} as SavedObjectUnsanitizedDoc;
const context = { log: {} } as SavedObjectMigrationContext;
migration(doc, context);
expect(legacyMigration).toHaveBeenCalledTimes(1);
expect(legacyMigration).toHaveBeenCalledWith(doc, context.log);
});
it('imports type management information', () => {
const uiExports: SavedObjectsLegacyUiExports = {
savedObjectMappings: [
{
pluginId: 'pluginA',
properties: {
typeA: {
properties: {
fieldA: { type: 'text' },
},
},
},
},
{
pluginId: 'pluginB',
properties: {
typeB: {
properties: {
fieldB: { type: 'text' },
},
},
typeC: {
properties: {
fieldC: { type: 'text' },
},
},
},
},
],
savedObjectsManagement: {
typeA: {
isImportableAndExportable: true,
icon: 'iconA',
defaultSearchField: 'searchFieldA',
getTitle: (savedObject) => savedObject.id,
},
typeB: {
isImportableAndExportable: false,
icon: 'iconB',
getEditUrl: (savedObject) => `/some-url/${savedObject.id}`,
getInAppUrl: (savedObject) => ({ path: 'path', uiCapabilitiesPath: 'ui-path' }),
},
},
savedObjectMigrations: {},
savedObjectSchemas: {},
savedObjectValidations: {},
};
const converted = convertLegacyTypes(uiExports, legacyConfig);
expect(converted.length).toEqual(3);
const [typeA, typeB, typeC] = converted;
expect(typeA.management).toEqual({
importableAndExportable: true,
icon: 'iconA',
defaultSearchField: 'searchFieldA',
getTitle: uiExports.savedObjectsManagement.typeA.getTitle,
});
expect(typeB.management).toEqual({
importableAndExportable: false,
icon: 'iconB',
getEditUrl: uiExports.savedObjectsManagement.typeB.getEditUrl,
getInAppUrl: uiExports.savedObjectsManagement.typeB.getInAppUrl,
});
expect(typeC.management).toBeUndefined();
});
it('merges everything when all are present', () => {
const uiExports: SavedObjectsLegacyUiExports = {
savedObjectMappings: [
{
pluginId: 'pluginA',
properties: {
typeA: {
properties: {
fieldA: { type: 'text' },
},
},
typeB: {
properties: {
fieldB: { type: 'text' },
anotherFieldB: { type: 'boolean' },
},
},
},
},
{
pluginId: 'pluginB',
properties: {
typeC: {
properties: {
fieldC: { type: 'text' },
},
},
},
},
],
savedObjectMigrations: {
typeA: {
'1.0.0': jest.fn(),
'2.0.4': jest.fn(),
},
typeC: {
'1.5.3': jest.fn(),
},
},
savedObjectSchemas: {
typeA: {
indexPattern: jest.fn((config) => {
config.get('foo.bar');
return 'myIndex';
}),
hidden: true,
isNamespaceAgnostic: true,
},
typeB: {
convertToAliasScript: 'some alias script',
hidden: false,
},
},
savedObjectValidations: {},
savedObjectsManagement: {},
};
const converted = convertLegacyTypes(uiExports, legacyConfig);
expect(converted).toMatchSnapshot();
});
});
describe('convertTypesToLegacySchema', () => {
it('converts types to the legacy schema format', () => {
const types: SavedObjectsType[] = [
{
name: 'typeA',
hidden: false,
namespaceType: 'agnostic',
mappings: { properties: {} },
convertToAliasScript: 'some script',
},
{
name: 'typeB',
hidden: true,
namespaceType: 'single',
indexPattern: 'myIndex',
mappings: { properties: {} },
},
{
name: 'typeC',
hidden: false,
namespaceType: 'multiple',
mappings: { properties: {} },
},
];
expect(convertTypesToLegacySchema(types)).toEqual({
typeA: {
hidden: false,
isNamespaceAgnostic: true,
multiNamespace: false,
convertToAliasScript: 'some script',
},
typeB: {
hidden: true,
isNamespaceAgnostic: false,
multiNamespace: false,
indexPattern: 'myIndex',
},
typeC: {
hidden: false,
isNamespaceAgnostic: false,
multiNamespace: true,
},
});
});
});

View file

@ -1,117 +0,0 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { LegacyConfig } from '../legacy';
import { SavedObjectMigrationMap } from './migrations';
import {
SavedObjectsNamespaceType,
SavedObjectsType,
SavedObjectsLegacyUiExports,
SavedObjectLegacyMigrationMap,
SavedObjectsLegacyManagementTypeDefinition,
SavedObjectsTypeManagementDefinition,
} from './types';
import { SavedObjectsSchemaDefinition } from './schema';
/**
* Converts the legacy savedObjects mappings, schema, and migrations
* to actual {@link SavedObjectsType | saved object types}
*/
export const convertLegacyTypes = (
{
savedObjectMappings = [],
savedObjectMigrations = {},
savedObjectSchemas = {},
savedObjectsManagement = {},
}: SavedObjectsLegacyUiExports,
legacyConfig: LegacyConfig
): SavedObjectsType[] => {
return savedObjectMappings.reduce((types, { properties }) => {
return [
...types,
...Object.entries(properties).map(([type, mappings]) => {
const schema = savedObjectSchemas[type];
const migrations = savedObjectMigrations[type];
const management = savedObjectsManagement[type];
const namespaceType = (schema?.isNamespaceAgnostic
? 'agnostic'
: schema?.multiNamespace
? 'multiple'
: 'single') as SavedObjectsNamespaceType;
return {
name: type,
hidden: schema?.hidden ?? false,
namespaceType,
mappings,
indexPattern:
typeof schema?.indexPattern === 'function'
? schema.indexPattern(legacyConfig)
: schema?.indexPattern,
convertToAliasScript: schema?.convertToAliasScript,
migrations: convertLegacyMigrations(migrations ?? {}),
management: management ? convertLegacyTypeManagement(management) : undefined,
};
}),
];
}, [] as SavedObjectsType[]);
};
/**
* Convert {@link SavedObjectsType | saved object types} to the legacy {@link SavedObjectsSchemaDefinition | schema} format
*/
export const convertTypesToLegacySchema = (
types: SavedObjectsType[]
): SavedObjectsSchemaDefinition => {
return types.reduce((schema, type) => {
return {
...schema,
[type.name]: {
isNamespaceAgnostic: type.namespaceType === 'agnostic',
multiNamespace: type.namespaceType === 'multiple',
hidden: type.hidden,
indexPattern: type.indexPattern,
convertToAliasScript: type.convertToAliasScript,
},
};
}, {} as SavedObjectsSchemaDefinition);
};
const convertLegacyMigrations = (
legacyMigrations: SavedObjectLegacyMigrationMap
): SavedObjectMigrationMap => {
return Object.entries(legacyMigrations).reduce((migrated, [version, migrationFn]) => {
return {
...migrated,
[version]: (doc, context) => migrationFn(doc, context.log),
};
}, {} as SavedObjectMigrationMap);
};
const convertLegacyTypeManagement = (
legacyTypeManagement: SavedObjectsLegacyManagementTypeDefinition
): SavedObjectsTypeManagementDefinition => {
return {
importableAndExportable: legacyTypeManagement.isImportableAndExportable,
defaultSearchField: legacyTypeManagement.defaultSearchField,
icon: legacyTypeManagement.icon,
getTitle: legacyTypeManagement.getTitle,
getEditUrl: legacyTypeManagement.getEditUrl,
getInAppUrl: legacyTypeManagement.getInAppUrl,
};
};

View file

@ -1,67 +0,0 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
/*
* This is the core logic for validating saved object properties. The saved object client
* and migrations consume this in order to validate saved object documents prior to
* persisting them.
*/
interface SavedObjectDoc {
type: string;
[prop: string]: any;
}
/**
* A dictionary of property name -> validation function. The property name
* is generally the document's type (e.g. "dashboard"), but will also
* match other properties.
*
* For example, the "acl" and "dashboard" validators both apply to the
* following saved object: { type: "dashboard", attributes: {}, acl: "sdlaj3w" }
*
* @export
* @interface Validators
*/
export interface PropertyValidators {
[prop: string]: ValidateDoc;
}
export type ValidateDoc = (doc: SavedObjectDoc) => void;
/**
* Creates a function which uses a dictionary of property validators to validate
* individual saved object documents.
*
* @export
* @param {Validators} validators
* @param {SavedObjectDoc} doc
*/
export function docValidator(validators: PropertyValidators = {}): ValidateDoc {
return function validateDoc(doc: SavedObjectDoc) {
Object.keys(doc)
.concat(doc.type)
.forEach((prop) => {
const validator = validators[prop];
if (validator) {
validator(doc);
}
});
};
}

View file

@ -1,63 +0,0 @@
# Saved Object Validations
The saved object client supports validation of documents during create / bulkCreate operations.
This allows us tighter control over what documents get written to the saved object index, and helps us keep the index in a healthy state.
## Creating validations
Plugin authors can write their own validations by adding a `validations` property to their uiExports. A validation is nothing more than a dictionary of `{[prop: string]: validationFunction}` where:
* `prop` - a root-property on a saved object document
* `validationFunction` - a function that takes a document and throws an error if it does not meet expectations.
## Example
```js
// In myFanciPlugin...
uiExports: {
validations: {
myProperty(doc) {
if (doc.attributes.someField === undefined) {
throw new Error(`Document ${doc.id} did not define "someField"`);
}
},
someOtherProp(doc) {
if (doc.attributes.counter < 0) {
throw new Error(`Document ${doc.id} cannot have a negative counter.`);
}
},
},
},
```
In this example, `myFanciPlugin` defines validations for two properties: `myProperty` and `someOtherProp`.
This means that no other plugin can define validations for myProperty or someOtherProp.
The `myProperty` validation would run for any doc that has a `type="myProperty"` or for any doc that has a root-level property of `myProperty`. e.g. it would apply to all documents in the following array:
```js
[
{
type: 'foo',
attributes: { stuff: 'here' },
myProperty: 'shazm!',
},
{
type: 'myProperty',
attributes: { shazm: true },
},
];
```
Validating properties other than just 'type' allows us to support potential future saved object scenarios in which plugins might want to annotate other plugin documents, such as a security plugin adding an acl to another document:
```js
{
type: 'dashboard',
attributes: { stuff: 'here' },
acl: '342343',
}
```

View file

@ -1,54 +0,0 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { docValidator } from './index';
describe('docValidator', () => {
test('does not run validators that have no application to the doc', () => {
const validators = {
foo: () => {
throw new Error('Boom!');
},
};
expect(() => docValidator(validators)({ type: 'shoo', bar: 'hi' })).not.toThrow();
});
test('validates the doc type', () => {
const validators = {
foo: () => {
throw new Error('Boom!');
},
};
expect(() => docValidator(validators)({ type: 'foo' })).toThrow(/Boom!/);
});
test('validates various props', () => {
const validators = {
a: jest.fn(),
b: jest.fn(),
c: jest.fn(),
};
docValidator(validators)({ type: 'a', b: 'foo' });
expect(validators.c).not.toHaveBeenCalled();
expect(validators.a.mock.calls).toEqual([[{ type: 'a', b: 'foo' }]]);
expect(validators.b.mock.calls).toEqual([[{ type: 'a', b: 'foo' }]]);
});
});

View file

@ -1411,19 +1411,30 @@ export interface LegacyServiceStartDeps {
plugins: Record<string, unknown>;
}
// Warning: (ae-forgotten-export) The symbol "SavedObjectsLegacyUiExports" needs to be exported by the entry point index.d.ts
//
// @internal @deprecated (undocumented)
export type LegacyUiExports = SavedObjectsLegacyUiExports & {
export interface LegacyUiExports {
// Warning: (ae-forgotten-export) The symbol "VarsProvider" needs to be exported by the entry point index.d.ts
//
// (undocumented)
defaultInjectedVarProviders?: VarsProvider[];
// Warning: (ae-forgotten-export) The symbol "VarsReplacer" needs to be exported by the entry point index.d.ts
//
// (undocumented)
injectedVarsReplacers?: VarsReplacer[];
// Warning: (ae-forgotten-export) The symbol "LegacyNavLinkSpec" needs to be exported by the entry point index.d.ts
//
// (undocumented)
navLinkSpecs?: LegacyNavLinkSpec[] | null;
// Warning: (ae-forgotten-export) The symbol "LegacyAppSpec" needs to be exported by the entry point index.d.ts
//
// (undocumented)
uiAppSpecs?: Array<LegacyAppSpec | undefined>;
// (undocumented)
unknown?: [{
pluginSpec: LegacyPluginSpec;
type: unknown;
}];
};
}
// Warning: (ae-forgotten-export) The symbol "lifecycleResponseFactory" needs to be exported by the entry point index.d.ts
//
@ -2437,33 +2448,6 @@ export interface SavedObjectsIncrementCounterOptions extends SavedObjectsBaseOpt
refresh?: MutatingOperationRefreshSetting;
}
// @internal @deprecated (undocumented)
export interface SavedObjectsLegacyService {
// Warning: (ae-forgotten-export) The symbol "SavedObjectsClientProvider" needs to be exported by the entry point index.d.ts
//
// (undocumented)
addScopedSavedObjectsClientWrapperFactory: SavedObjectsClientProvider['addClientWrapperFactory'];
// (undocumented)
getSavedObjectsRepository(...rest: any[]): any;
// (undocumented)
getScopedSavedObjectsClient: SavedObjectsClientProvider['getClient'];
// (undocumented)
importExport: {
objectLimit: number;
importSavedObjects(options: SavedObjectsImportOptions): Promise<SavedObjectsImportResponse>;
resolveImportErrors(options: SavedObjectsResolveImportErrorsOptions): Promise<SavedObjectsImportResponse>;
getSortedObjectsForExport(options: SavedObjectsExportOptions): Promise<Readable>;
};
// (undocumented)
SavedObjectsClient: typeof SavedObjectsClient;
// (undocumented)
schema: SavedObjectsSchema;
// (undocumented)
setScopedSavedObjectsClientFactory: SavedObjectsClientProvider['setClientFactory'];
// (undocumented)
types: string[];
}
// @public
export interface SavedObjectsMappingProperties {
// (undocumented)
@ -2517,10 +2501,10 @@ export class SavedObjectsRepository {
bulkUpdate<T = unknown>(objects: Array<SavedObjectsBulkUpdateObject<T>>, options?: SavedObjectsBulkUpdateOptions): Promise<SavedObjectsBulkUpdateResponse<T>>;
checkConflicts(objects?: SavedObjectsCheckConflictsObject[], options?: SavedObjectsBaseOptions): Promise<SavedObjectsCheckConflictsResponse>;
create<T = unknown>(type: string, attributes: T, options?: SavedObjectsCreateOptions): Promise<SavedObject<T>>;
// Warning: (ae-forgotten-export) The symbol "KibanaMigrator" needs to be exported by the entry point index.d.ts
// Warning: (ae-forgotten-export) The symbol "IKibanaMigrator" needs to be exported by the entry point index.d.ts
//
// @internal
static createRepository(migrator: KibanaMigrator, typeRegistry: SavedObjectTypeRegistry, indexName: string, client: ElasticsearchClient, includedHiddenTypes?: string[], injectedConstructor?: any): ISavedObjectsRepository;
static createRepository(migrator: IKibanaMigrator, typeRegistry: SavedObjectTypeRegistry, indexName: string, client: ElasticsearchClient, includedHiddenTypes?: string[], injectedConstructor?: any): ISavedObjectsRepository;
delete(type: string, id: string, options?: SavedObjectsDeleteOptions): Promise<{}>;
deleteByNamespace(namespace: string, options?: SavedObjectsDeleteByNamespaceOptions): Promise<any>;
deleteFromNamespaces(type: string, id: string, namespaces: string[], options?: SavedObjectsDeleteFromNamespacesOptions): Promise<SavedObjectsDeleteFromNamespacesResponse>;
@ -2548,24 +2532,6 @@ export interface SavedObjectsResolveImportErrorsOptions {
typeRegistry: ISavedObjectTypeRegistry;
}
// @internal @deprecated (undocumented)
export class SavedObjectsSchema {
// Warning: (ae-forgotten-export) The symbol "SavedObjectsSchemaDefinition" needs to be exported by the entry point index.d.ts
constructor(schemaDefinition?: SavedObjectsSchemaDefinition);
// (undocumented)
getConvertToAliasScript(type: string): string | undefined;
// (undocumented)
getIndexForType(config: LegacyConfig, type: string): string | undefined;
// (undocumented)
isHiddenType(type: string): boolean;
// (undocumented)
isMultiNamespace(type: string): boolean;
// (undocumented)
isNamespaceAgnostic(type: string): boolean;
// (undocumented)
isSingleNamespace(type: string): boolean;
}
// @public
export class SavedObjectsSerializer {
// @internal
@ -2888,11 +2854,7 @@ export const validBodyOutput: readonly ["data", "stream"];
// Warnings were encountered during analysis:
//
// src/core/server/http/router/response.ts:316:3 - (ae-forgotten-export) The symbol "KibanaResponse" needs to be exported by the entry point index.d.ts
// src/core/server/legacy/types.ts:132:3 - (ae-forgotten-export) The symbol "VarsProvider" needs to be exported by the entry point index.d.ts
// src/core/server/legacy/types.ts:133:3 - (ae-forgotten-export) The symbol "VarsReplacer" needs to be exported by the entry point index.d.ts
// src/core/server/legacy/types.ts:134:3 - (ae-forgotten-export) The symbol "LegacyNavLinkSpec" needs to be exported by the entry point index.d.ts
// src/core/server/legacy/types.ts:135:3 - (ae-forgotten-export) The symbol "LegacyAppSpec" needs to be exported by the entry point index.d.ts
// src/core/server/legacy/types.ts:136:16 - (ae-forgotten-export) The symbol "LegacyPluginSpec" needs to be exported by the entry point index.d.ts
// src/core/server/legacy/types.ts:135:16 - (ae-forgotten-export) The symbol "LegacyPluginSpec" needs to be exported by the entry point index.d.ts
// src/core/server/plugins/types.ts:266:3 - (ae-forgotten-export) The symbol "KibanaConfigType" needs to be exported by the entry point index.d.ts
// src/core/server/plugins/types.ts:266:3 - (ae-forgotten-export) The symbol "SharedGlobalConfigKeys" needs to be exported by the entry point index.d.ts
// src/core/server/plugins/types.ts:268:3 - (ae-forgotten-export) The symbol "PathConfigType" needs to be exported by the entry point index.d.ts

View file

@ -142,7 +142,6 @@ export class Server {
const savedObjectsSetup = await this.savedObjects.setup({
http: httpSetup,
elasticsearch: elasticsearchServiceSetup,
legacyPlugins,
});
const uiSettingsSetup = await this.uiSettings.setup({

View file

@ -36,8 +36,6 @@ describe('createOrUpgradeSavedConfig()', () => {
let esServer: TestElasticsearchUtils;
let kbn: TestKibanaUtils;
let kbnServer: TestKibanaUtils['kbnServer'];
beforeAll(async function () {
servers = createTestServers({
adjustTimeout: (t) => {
@ -46,10 +44,8 @@ describe('createOrUpgradeSavedConfig()', () => {
});
esServer = await servers.startES();
kbn = await servers.startKibana();
kbnServer = kbn.kbnServer;
const savedObjects = kbnServer.server.savedObjects;
savedObjectsClient = savedObjects.getScopedSavedObjectsClient(
savedObjectsClient = kbn.coreStart.savedObjects.getScopedClient(
httpServerMock.createKibanaRequest()
);

View file

@ -68,8 +68,7 @@ export function getServices() {
const callCluster = esServer.es.getCallCluster();
const savedObjects = kbnServer.server.savedObjects;
const savedObjectsClient = savedObjects.getScopedSavedObjectsClient(
const savedObjectsClient = kbn.coreStart.savedObjects.getScopedClient(
httpServerMock.createKibanaRequest()
);

View file

@ -32,6 +32,7 @@ import { resolve } from 'path';
import { BehaviorSubject } from 'rxjs';
import supertest from 'supertest';
import { CoreStart } from 'src/core/server';
import { LegacyAPICaller } from '../server/elasticsearch';
import { CliArgs, Env } from '../server/config';
import { Root } from '../server/root';
@ -167,6 +168,7 @@ export interface TestElasticsearchUtils {
export interface TestKibanaUtils {
root: Root;
coreStart: CoreStart;
kbnServer: KbnServer;
stop: () => Promise<void>;
}
@ -286,13 +288,14 @@ export function createTestServers({
const root = createRootWithCorePlugins(kbnSettings);
await root.setup();
await root.start();
const coreStart = await root.start();
const kbnServer = getKbnServer(root);
return {
root,
kbnServer,
coreStart,
stop: async () => await root.shutdown(),
};
},

View file

@ -18,14 +18,10 @@
*/
import { Server } from '../../server/kbn_server';
import { Capabilities } from '../../../core/server';
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
import { SavedObjectsLegacyManagementDefinition } from '../../../core/server/saved_objects/types';
export type InitPluginFunction = (server: Server) => void;
export interface UiExports {
injectDefaultVars?: (server: Server) => { [key: string]: any };
savedObjectsManagement?: SavedObjectsLegacyManagementDefinition;
mappings?: unknown;
}
export interface PluginSpecOptions {

View file

@ -19,11 +19,6 @@
import { Server } from '../server/kbn_server';
import { Capabilities } from '../../core/server';
// Disable lint errors for imports from src/core/* until SavedObjects migration is complete
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
import { SavedObjectsSchemaDefinition } from '../../core/server/saved_objects/schema';
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
import { SavedObjectsLegacyManagementDefinition } from '../../core/server/saved_objects/types';
import { AppCategory } from '../../core/types';
/**
@ -70,8 +65,6 @@ export interface LegacyPluginOptions {
home: string[];
mappings: any;
migrations: any;
savedObjectSchemas: SavedObjectsSchemaDefinition;
savedObjectsManagement: SavedObjectsLegacyManagementDefinition;
visTypes: string[];
embeddableActions?: string[];
embeddableFactories?: string[];

View file

@ -17,33 +17,24 @@
* under the License.
*/
import { ResponseObject, Server } from 'hapi';
import { UnwrapPromise } from '@kbn/utility-types';
import { Server } from 'hapi';
import { TelemetryCollectionManagerPluginSetup } from 'src/plugins/telemetry_collection_manager/server';
import {
ConfigService,
CoreSetup,
CoreStart,
ElasticsearchServiceSetup,
EnvironmentMode,
LoggerFactory,
SavedObjectsClientContract,
SavedObjectsLegacyService,
SavedObjectsClientProviderOptions,
IUiSettingsClient,
PackageInfo,
LegacyRequest,
LegacyServiceSetupDeps,
LegacyServiceStartDeps,
LegacyServiceDiscoverPlugins,
} from '../../core/server';
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
import { LegacyConfig, ILegacyService, ILegacyInternals } from '../../core/server/legacy';
import { LegacyConfig, ILegacyInternals } from '../../core/server/legacy';
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
import { UiPlugins } from '../../core/server/plugins';
import { CallClusterWithRequest, ElasticsearchPlugin } from '../core_plugins/elasticsearch';
import { ElasticsearchPlugin } from '../core_plugins/elasticsearch';
import { UsageCollectionSetup } from '../../plugins/usage_collection/server';
import { HomeServerPluginSetup } from '../../plugins/home/server';
@ -61,16 +52,9 @@ declare module 'hapi' {
interface Server {
config: () => KibanaConfig;
savedObjects: SavedObjectsLegacyService;
logWithMetadata: (tags: string[], message: string, meta: Record<string, any>) => void;
newPlatform: KbnServer['newPlatform'];
}
interface Request {
getSavedObjectsClient(options?: SavedObjectsClientProviderOptions): SavedObjectsClientContract;
getBasePath(): string;
getUiSettingsService(): IUiSettingsClient;
}
}
type KbnMixinFunc = (kbnServer: KbnServer, server: Server, config: any) => Promise<any> | void;
@ -86,11 +70,9 @@ export interface KibanaCore {
__internals: {
elasticsearch: LegacyServiceSetupDeps['core']['elasticsearch'];
hapiServer: LegacyServiceSetupDeps['core']['http']['server'];
kibanaMigrator: LegacyServiceStartDeps['core']['savedObjects']['migrator'];
legacy: ILegacyInternals;
rendering: LegacyServiceSetupDeps['core']['rendering'];
uiPlugins: UiPlugins;
savedObjectsClientProvider: LegacyServiceStartDeps['core']['savedObjects']['clientProvider'];
};
env: {
mode: Readonly<EnvironmentMode>;
@ -149,6 +131,3 @@ export default class KbnServer {
// Re-export commonly used hapi types.
export { Server, Request, ResponseToolkit } from 'hapi';
// Re-export commonly accessed api types.
export { SavedObjectsLegacyService, SavedObjectsClient } from 'src/core/server';

View file

@ -33,7 +33,6 @@ import pidMixin from './pid';
import configCompleteMixin from './config/complete';
import { optimizeMixin } from '../../optimize';
import * as Plugins from './plugins';
import { savedObjectsMixin } from './saved_objects/saved_objects_mixin';
import { uiMixin } from '../ui';
import { i18nMixin } from './i18n';
@ -108,9 +107,6 @@ export default class KbnServer {
uiMixin,
// setup saved object routes
savedObjectsMixin,
// setup routes that serve the @kbn/optimizer output
optimizeMixin,

View file

@ -1,104 +0,0 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
// Disable lint errors for imports from src/core/server/saved_objects until SavedObjects migration is complete
/* eslint-disable @kbn/eslint/no-restricted-paths */
import { SavedObjectsSchema } from '../../../core/server/saved_objects/schema';
import {
SavedObjectsClient,
SavedObjectsRepository,
exportSavedObjectsToStream,
importSavedObjectsFromStream,
resolveSavedObjectsImportErrors,
} from '../../../core/server/saved_objects';
import { convertTypesToLegacySchema } from '../../../core/server/saved_objects/utils';
export function savedObjectsMixin(kbnServer, server) {
const migrator = kbnServer.newPlatform.__internals.kibanaMigrator;
const typeRegistry = kbnServer.newPlatform.start.core.savedObjects.getTypeRegistry();
const mappings = migrator.getActiveMappings();
const allTypes = typeRegistry.getAllTypes().map((t) => t.name);
const visibleTypes = typeRegistry.getVisibleTypes().map((t) => t.name);
const schema = new SavedObjectsSchema(convertTypesToLegacySchema(typeRegistry.getAllTypes()));
server.decorate('server', 'kibanaMigrator', migrator);
const serializer = kbnServer.newPlatform.start.core.savedObjects.createSerializer();
const createRepository = (callCluster, includedHiddenTypes = []) => {
if (typeof callCluster !== 'function') {
throw new TypeError('Repository requires a "callCluster" function to be provided.');
}
// throw an exception if an extraType is not defined.
includedHiddenTypes.forEach((type) => {
if (!allTypes.includes(type)) {
throw new Error(`Missing mappings for saved objects type '${type}'`);
}
});
const combinedTypes = visibleTypes.concat(includedHiddenTypes);
const allowedTypes = [...new Set(combinedTypes)];
const config = server.config();
return new SavedObjectsRepository({
index: config.get('kibana.index'),
migrator,
mappings,
typeRegistry,
serializer,
allowedTypes,
callCluster,
});
};
const provider = kbnServer.newPlatform.__internals.savedObjectsClientProvider;
const service = {
types: visibleTypes,
SavedObjectsClient,
SavedObjectsRepository,
getSavedObjectsRepository: createRepository,
getScopedSavedObjectsClient: (...args) => provider.getClient(...args),
setScopedSavedObjectsClientFactory: (...args) => provider.setClientFactory(...args),
addScopedSavedObjectsClientWrapperFactory: (...args) =>
provider.addClientWrapperFactory(...args),
importExport: {
objectLimit: server.config().get('savedObjects.maxImportExportSize'),
importSavedObjects: importSavedObjectsFromStream,
resolveImportErrors: resolveSavedObjectsImportErrors,
getSortedObjectsForExport: exportSavedObjectsToStream,
},
schema,
};
server.decorate('server', 'savedObjects', service);
const savedObjectsClientCache = new WeakMap();
server.decorate('request', 'getSavedObjectsClient', function (options) {
const request = this;
if (savedObjectsClientCache.has(request)) {
return savedObjectsClientCache.get(request);
}
const savedObjectsClient = server.savedObjects.getScopedSavedObjectsClient(request, options);
savedObjectsClientCache.set(request, savedObjectsClient);
return savedObjectsClient;
});
}

View file

@ -1,267 +0,0 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { savedObjectsMixin } from './saved_objects_mixin';
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
import { mockKibanaMigrator } from '../../../core/server/saved_objects/migrations/kibana/kibana_migrator.mock';
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
import { savedObjectsClientProviderMock } from '../../../core/server/saved_objects/service/lib/scoped_client_provider.mock';
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
import { convertLegacyTypes } from '../../../core/server/saved_objects/utils';
import { SavedObjectTypeRegistry } from '../../../core/server';
import { coreMock } from '../../../core/server/mocks';
const mockConfig = {
get: jest.fn().mockReturnValue('anything'),
};
const savedObjectMappings = [
{
pluginId: 'testtype',
properties: {
testtype: {
properties: {
name: { type: 'keyword' },
},
},
},
},
{
pluginId: 'testtype2',
properties: {
doc1: {
properties: {
name: { type: 'keyword' },
},
},
doc2: {
properties: {
name: { type: 'keyword' },
},
},
},
},
{
pluginId: 'secretPlugin',
properties: {
hiddentype: {
properties: {
secret: { type: 'keyword' },
},
},
},
},
];
const savedObjectSchemas = {
hiddentype: {
hidden: true,
},
doc1: {
indexPattern: 'other-index',
},
};
const savedObjectTypes = convertLegacyTypes(
{
savedObjectMappings,
savedObjectSchemas,
savedObjectMigrations: {},
},
mockConfig
);
const typeRegistry = new SavedObjectTypeRegistry();
savedObjectTypes.forEach((type) => typeRegistry.registerType(type));
const migrator = mockKibanaMigrator.create({
types: savedObjectTypes,
});
describe('Saved Objects Mixin', () => {
let mockKbnServer;
let mockServer;
const mockCallCluster = jest.fn();
const stubCallCluster = jest.fn();
const config = {
'kibana.index': 'kibana.index',
'savedObjects.maxImportExportSize': 10000,
};
const stubConfig = jest.fn((key) => {
return config[key];
});
beforeEach(() => {
const clientProvider = savedObjectsClientProviderMock.create();
mockServer = {
log: jest.fn(),
route: jest.fn(),
decorate: jest.fn(),
config: () => {
return {
get: stubConfig,
};
},
plugins: {
elasticsearch: {
getCluster: () => {
return {
callWithRequest: mockCallCluster,
callWithInternalUser: stubCallCluster,
};
},
waitUntilReady: jest.fn(),
},
},
};
const coreStart = coreMock.createStart();
coreStart.savedObjects.getTypeRegistry.mockReturnValue(typeRegistry);
mockKbnServer = {
newPlatform: {
__internals: {
kibanaMigrator: migrator,
savedObjectsClientProvider: clientProvider,
},
setup: {
core: coreMock.createSetup(),
},
start: {
core: coreStart,
},
},
server: mockServer,
ready: () => {},
pluginSpecs: {
some: () => {
return true;
},
},
uiExports: {
savedObjectMappings,
savedObjectSchemas,
},
};
});
describe('Saved object service', () => {
let service;
beforeEach(async () => {
await savedObjectsMixin(mockKbnServer, mockServer);
const call = mockServer.decorate.mock.calls.filter(
([objName, methodName]) => objName === 'server' && methodName === 'savedObjects'
);
service = call[0][2];
});
it('should return all but hidden types', async () => {
expect(service).toBeDefined();
expect(service.types).toEqual(['testtype', 'doc1', 'doc2']);
});
const mockCallEs = jest.fn();
describe('repository creation', () => {
it('should not allow a repository with an undefined type', () => {
expect(() => {
service.getSavedObjectsRepository(mockCallEs, ['extraType']);
}).toThrow(new Error("Missing mappings for saved objects type 'extraType'"));
});
it('should create a repository without hidden types', () => {
const repository = service.getSavedObjectsRepository(mockCallEs);
expect(repository).toBeDefined();
expect(repository._allowedTypes).toEqual(['testtype', 'doc1', 'doc2']);
});
it('should create a repository with a unique list of allowed types', () => {
const repository = service.getSavedObjectsRepository(mockCallEs, ['doc1', 'doc1', 'doc1']);
expect(repository._allowedTypes).toEqual(['testtype', 'doc1', 'doc2']);
});
it('should create a repository with extraTypes minus duplicate', () => {
const repository = service.getSavedObjectsRepository(mockCallEs, [
'hiddentype',
'hiddentype',
]);
expect(repository._allowedTypes).toEqual(['testtype', 'doc1', 'doc2', 'hiddentype']);
});
it('should not allow a repository without a callCluster function', () => {
expect(() => {
service.getSavedObjectsRepository({});
}).toThrow(new Error('Repository requires a "callCluster" function to be provided.'));
});
});
describe('get client', () => {
it('should have a method to get the client', () => {
expect(service).toHaveProperty('getScopedSavedObjectsClient');
});
it('should have a method to set the client factory', () => {
expect(service).toHaveProperty('setScopedSavedObjectsClientFactory');
});
it('should have a method to add a client wrapper factory', () => {
expect(service).toHaveProperty('addScopedSavedObjectsClientWrapperFactory');
});
it('should allow you to set a scoped saved objects client factory', () => {
expect(() => {
service.setScopedSavedObjectsClientFactory({});
}).not.toThrowError();
});
it('should allow you to add a scoped saved objects client wrapper factory', () => {
expect(() => {
service.addScopedSavedObjectsClientWrapperFactory({});
}).not.toThrowError();
});
});
describe('#getSavedObjectsClient', () => {
let getSavedObjectsClient;
beforeEach(() => {
savedObjectsMixin(mockKbnServer, mockServer);
const call = mockServer.decorate.mock.calls.filter(
([objName, methodName]) => objName === 'request' && methodName === 'getSavedObjectsClient'
);
getSavedObjectsClient = call[0][2];
});
it('should be callable', () => {
mockServer.savedObjects = service;
getSavedObjectsClient = getSavedObjectsClient.bind({});
expect(() => {
getSavedObjectsClient();
}).not.toThrowError();
});
it('should use cached request object', () => {
mockServer.savedObjects = service;
getSavedObjectsClient = getSavedObjectsClient.bind({ _test: 'me' });
const savedObjectsClient = getSavedObjectsClient();
expect(getSavedObjectsClient()).toEqual(savedObjectsClient);
});
});
});
});

View file

@ -1,117 +0,0 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import expect from '@kbn/expect';
import { PluginPack } from '../../../plugin_discovery';
import { collectUiExports } from '../collect_ui_exports';
const specs = new PluginPack({
path: '/dev/null',
pkg: {
name: 'test',
version: 'kibana',
},
provider({ Plugin }) {
return [
new Plugin({
id: 'test',
uiExports: {
savedObjectSchemas: {
foo: {
isNamespaceAgnostic: true,
},
},
},
}),
new Plugin({
id: 'test2',
uiExports: {
savedObjectSchemas: {
bar: {
isNamespaceAgnostic: true,
},
},
},
}),
];
},
}).getPluginSpecs();
describe('plugin discovery', () => {
describe('collectUiExports()', () => {
it('merges uiExports from all provided plugin specs', () => {
const uiExports = collectUiExports(specs);
expect(uiExports.savedObjectSchemas).to.eql({
foo: {
isNamespaceAgnostic: true,
},
bar: {
isNamespaceAgnostic: true,
},
});
});
it(`throws an error when migrations and mappings aren't defined in the same plugin`, () => {
const invalidSpecs = new PluginPack({
path: '/dev/null',
pkg: {
name: 'test',
version: 'kibana',
},
provider({ Plugin }) {
return [
new Plugin({
id: 'test',
uiExports: {
mappings: {
'test-type': {
properties: {},
},
},
},
}),
new Plugin({
id: 'test2',
uiExports: {
migrations: {
'test-type': {
'1.2.3': (doc) => {
return doc;
},
},
},
},
}),
];
},
}).getPluginSpecs();
expect(() => collectUiExports(invalidSpecs)).to.throwError((err) => {
expect(err).to.be.a(Error);
expect(err).to.have.property(
'message',
'Migrations and mappings must be defined together in the uiExports of a single plugin. ' +
'test2 defines migrations for types test-type but does not define their mappings.'
);
});
});
});
});

View file

@ -193,13 +193,11 @@ export function uiRenderMixin(kbnServer, server, config) {
async function renderApp(h) {
const app = { getId: () => 'core' };
const { http } = kbnServer.newPlatform.setup.core;
const {
rendering,
legacy,
savedObjectsClientProvider: savedObjects,
} = kbnServer.newPlatform.__internals;
const { savedObjects } = kbnServer.newPlatform.start.core;
const { rendering, legacy } = kbnServer.newPlatform.__internals;
const req = KibanaRequest.from(h.request);
const uiSettings = kbnServer.newPlatform.start.core.uiSettings.asScopedToClient(
savedObjects.getClient(h.request)
savedObjects.getScopedClient(req)
);
const vars = await legacy.getVars(app.getId(), h.request, {
apmConfig: getApmConfig(h.request.path),

View file

@ -379,14 +379,12 @@ async function migrateIndex({
index,
migrations,
mappingProperties,
validateDoc,
obsoleteIndexTemplatePattern,
}: {
esClient: ElasticsearchClient;
index: string;
migrations: Record<string, SavedObjectMigrationMap>;
mappingProperties: SavedObjectsTypeMappingDefinitions;
validateDoc?: (doc: any) => void;
obsoleteIndexTemplatePattern?: string;
}) {
const typeRegistry = new SavedObjectTypeRegistry();
@ -396,7 +394,6 @@ async function migrateIndex({
const documentMigrator = new DocumentMigrator({
kibanaVersion: '99.9.9',
typeRegistry,
validateDoc: validateDoc || _.noop,
log: getLogMock(),
});

View file

@ -5,7 +5,7 @@
*/
import { merge } from 'lodash';
import { Server } from 'hapi';
import { SavedObjectsClient } from 'src/core/server';
import { PromiseReturnType } from '../../../../../observability/typings/common';
import {
@ -32,10 +32,6 @@ export interface ApmIndicesConfig {
export type ApmIndicesName = keyof ApmIndicesConfig;
export type ScopedSavedObjectsClient = ReturnType<
Server['savedObjects']['getScopedSavedObjectsClient']
>;
async function getApmIndicesSavedObject(
savedObjectsClient: ISavedObjectsClient
) {

View file

@ -13,7 +13,6 @@ import {
} from 'src/core/server';
import { PickByValue, Optional } from 'utility-types';
import { Observable } from 'rxjs';
import { Server } from 'hapi';
import { ObservabilityPluginSetup } from '../../../observability/server';
import { SecurityPluginSetup } from '../../../security/server';
import { MlPluginSetup } from '../../../ml/server';
@ -57,12 +56,6 @@ export interface Route<
}) => Promise<TReturn>;
}
export type APMLegacyServer = Pick<Server, 'savedObjects' | 'log'> & {
plugins: {
elasticsearch: Server['plugins']['elasticsearch'];
};
};
export type APMRequestHandlerContext<
TDecodedParams extends { [key in keyof Params]: any } = {}
> = RequestHandlerContext & {