Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Restructure SavedObject types internal representation #56378

Merged

Conversation

pgayvallet
Copy link
Contributor

@pgayvallet pgayvallet commented Jan 30, 2020

Summary

Fix #55857

Main goal of the PR is to introduce the new SavedObjectType type that is an aggregation of current SO schema, mappings and migrations (it will also handle validations) later but this is outside the scope of this PR)

export interface SavedObjectsType {
  name: string;
  hidden: boolean;
  namespaceAgnostic: boolean;
  indexPattern?: string;
  convertToAliasScript?: string;

  mappings: SavedObjectsTypeMappingDefinition;
  migrations?: SavedObjectMigrationMap;
}

Old legacy SO types registration are converted to the new format during the service setup phase.

PR also introduce the new SavedObjectTypeRegistry registry, that superseed the responsibilities of the now deprecated SavedObjectsSchema. It keep the list of all registered types, and is propagated alongs the SO internal services, such as the KibanaMigrator. This avoid passing savedObjectSchemas, savedObjectsMappings and savedObjectsMigrations everywhere and greatly simplifies a lot of internal APIs and calls.

Other things this PR does:

  • Some plugins were manually instantiating a SavedObjectsSchema to be able to instanciate their own SavedObjectSerializer. as the serializer now relies on a SavedObjectTypeRegistry, and as I did not want the registry to be accessible, I added a createSerializer API to SavedObjectStart to be able to get a ready-to-use serializer.
  • Rename and document a lot of previously internal types that are now public as SavedObjectSerializer and SavedObjectType are exported and referencing them
  • Expose a registerType API to saved object internal setup contract. This is only here for testing for now, and can only be made public once Adapt ES-Archiver to use savedObject types defined in the new platform #55860 is resolved.
  • Get rid of the LegacyConfig usages in the SavedObject codebase. As legacy types are converted during the setup phase, the legacy config is no longer needed on lower levels.

Things this PR does not:

  • Introduce any new public SO API from core.
  • Handle the savedObject validations (legacy validations are still used, but are not included in the new SavedObjectType structure

Checklist

Use strikethroughs to remove checklist items you don't feel are applicable to this PR.

For maintainers

Comment on lines -43 to -49
it('Fails if duplicate mappings are defined', () => {
const options = mockOptions();
options.savedObjectMappings = [
{
pluginId: 'aaa',
properties: { amap: { type: 'text' } },
},
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The registry already ensure types are only registered once. Boths tests and Map implementation are ensuring this, the test was obsolete.

Comment on lines 23 to 30
export type SavedObjectMigrationFn = (
doc: RawSavedObjectDoc,
log: SavedObjectsMigrationLogger
) => RawSavedObjectDoc;

export const schemaMock = {
create: createSchemaMock,
};
export interface SavedObjectMigrationMap {
[version: string]: SavedObjectMigrationFn;
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is undocumented for now as SavedObjectsType is still an internal type.

mock.isHidden.mockReturnValue(false);
mock.isNamespaceAgnostic.mockImplementation((type: string) => type === 'global');

return mock as any;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I couldn't find how to properly type the mock. TS keep being unhappy about the types private field on SavedObjectTypeRegistry. I didn't want to introduce an interface ONLY to have proper mock typing, so I force-casted.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤷‍♂ we've done in this in other places. You don't necessarily have to actually export the interface, just use it for the mock and export the mock function. It will be type compatible while still making sure the mock is the correct type.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Issue is not about returning the interface, it's that my mock doesn't respect the jest.Mocked<SavedObjectTypeRegistry> type, as it's does not have the readonly types property. Changing the mock type to jest.Mocked<PublicMethodsOf<SavedObjectTypeRegistry>> fails when using the mock with

Argument of type 'Mocked<Pick<SavedObjectTypeRegistry, "getType" | "registerType" | "getAllTypes" | "isNamespaceAgnostic" | "isHidden" | "getIndex">>' is not assignable to parameter of type 'SavedObjectTypeRegistry'.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The only problem here is that internal mocks aren't type-safe and might be out of sync when SavedObjectTypeRegistry updates the interface. #54906 postponed, probably adding a separate interface is not such a bad idea.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added the ISavedObjectTypeRegistry interface

Comment on lines +105 to +106
const converted = convertLegacyTypes(uiExports, legacyConfig);
expect(converted).toMatchSnapshot();
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I usually hate snapshot testing, however the structure generated is just too big to manually assert or even using inline snapshots.

Comment on lines 1534 to 1547
// @public (undocumented)
export interface SavedObjectsComplexFieldMapping {
// (undocumented)
dynamic?: string;
// (undocumented)
properties: SavedObjectsMappingProperties;
// (undocumented)
type?: string;
}

// @public (undocumented)
export interface SavedObjectsCoreFieldMapping {
// (undocumented)
enabled?: boolean;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All these types are made public because they are SavedObjectMapping underlying types. However we are closer to ES definitions than SO here, so I'm not sure documenting them got much value.

Copy link
Contributor

@mshustov mshustov Feb 3, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we link to elasticsearch docs describing the format of the mappings? https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping-types.html
Is it possible to add proper typings for in the future? For example, type is not just a string, but subset of strings.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we link to elasticsearch docs describing the format of the mappings?

Yea (thanks for the link). However these type are wrongly defined as public (as I picked the commits from my 'mappings' PR), and are not ATM exposed in any API. I would like to keep all documentation in the next PR where we expose registerType. Would declaring them back to internal for now be acceptable?

Is it possible to add proper typings for in the future?

We do want that. However I would really like to avoid redefining all direct ES types, and I think these types are not defined in the ES library we are using. Maybe this could be resolved when using the new es node library?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

However I would really like to avoid redefining all direct ES types, and I think these types are not defined in the ES library we are using. Maybe this could be resolved when using the new es node library?

I didn't find them in elasticsearch-js either. @delvedor are there any plans to add them?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hello! The new js client does not have type definitions for Elasticsearch's request and response bodies, only for URL and query parameters.
It does offer a solution for fixing this through generics, see the typescript documentation.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added link to the documentation. The generic does not answer our needs though, as in our part of the SO code, we have no way to be aware of the actual SO type we are working on.

We will have to improve the typings on our side I guess, but this should be done in another PR (We need improvement on pretty much everything in the src/core/server/saved_objects subfolders...)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need improvement on pretty much everything in the src/core/server/saved_objects subfolders...

Would you mind creating a list of follow-up tasks?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, will add this as a condition to merge this PR.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Created #56856

Comment on lines 89 to 91
const savedObjectTypes = convertLegacyTypes(uiExports, config);
const typeRegistry = new SavedObjectTypeRegistry();
savedObjectTypes.forEach(type => typeRegistry.registerType(type));
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adapted the ES-archiver to use new format. #55860 still need to be addressed.

Comment on lines -62 to +63
const schema = new SavedObjectsSchema(kbnServer.uiExports.savedObjectSchemas);
const schema = new SavedObjectsSchema(convertTypesToLegacySchema(typeRegistry.getAllTypes()));
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wanted to totally get rid of SavedObjectSchema, however the legacy service is still exposing it. Had to keep compatibility by converting back the types to the old schema format.

Comment on lines -35 to +38
// as we use this Schema solely to interact with Tasks, we
// can initialise it with solely the Tasks schema
const serializer = new SavedObjectsSerializer(new SavedObjectsSchema(savedObjectSchemas));
return new TaskManager({
taskManagerId: core.uuid.getInstanceUuid(),
config,
savedObjectsRepository,
serializer,
serializer: savedObjectsSerializer,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@elastic/kibana-alerting-services the serializer now requires an internal core type to be instantiated. I exposed an API from core to retrieve a configured serializer, and adapted your call to use it instead.

The createTaskManager method no longer relies on any legacy API, however I did not feel confident on impacting too much you codebase, so I kept the call as is.

You should be good to remove the registerLegacyAPI trick in x-pack/legacy/plugins/task_manager/server/index.ts and x-pack/plugins/task_manager/server/plugin.ts once this lands.

@pgayvallet pgayvallet added release_note:skip Skip the PR/issue when compiling release notes v7.7.0 Feature:New Platform Team:Core Core services & architecture: plugins, logging, config, saved objects, http, ES client, i18n, etc labels Jan 30, 2020
@pgayvallet pgayvallet requested a review from a team February 4, 2020 08:35
@pgayvallet
Copy link
Contributor Author

@joshdover @restrry I think I addressed all your current comments. PTAL

Copy link
Contributor

@rudolf rudolf left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Haven't worked through all the changes, but wanted to give the feedback I have so long.

src/core/server/mocks.ts Show resolved Hide resolved
};
export type SavedObjectMigrationFn = (
doc: RawSavedObjectDoc,
log: SavedObjectsMigrationLogger
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar to the SavedObjectsMigrationLogger we could make breaking changes and refactor the existing migrations, but since many migrations are written in javascript and a single uncaught exception can prevent kibana from starting up, this is risky. So I think it's better we keep the API and refactor it post 8.0.0.

src/core/server/server.api.md Outdated Show resolved Hide resolved
Copy link
Member

@kertal kertal left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code LGTM, just took a look at the modified test where KibanaApp is Codeowner.

Copy link
Member

@mistic mistic left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The changes to operations team files LGTM

@LeeDr
Copy link

LeeDr commented Feb 5, 2020

Old legacy SO types registration are converted to the new format during the service setup phase.

Could you explain when the setup phase happens?

On startup? Linked to migrations?
On saved object import?
When beats setup scripts run and used saved object api to create objects?

Also, we probably want v8.0.0 label on this PR assuming it's going in to master, and then would be backported to 7.x branch.

@pgayvallet
Copy link
Contributor Author

@LeeDr

Could you explain when the setup phase happens?

That would be core's setup phase. During Kibana startup, before plugin setup phase. Not sure to really understand the question?

We are talking about savedObjects types here, not actual SO documents. This is not impacting savedObjects data in any way.

* A map of {@link SavedObjectMigrationFn | migration functions} to be used for a given type.
* The map's keys must be valid semver versions.
*
* Migrations will be executed in order, starting from the lowest matching an higher version that the document
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit suggestion:

For a given document, only migrations with a higher version number than that of the document will be applied. Migrations are executed in order, starting from the lowest version and ending with the highest one.

Comment on lines 44 to 54
public isNamespaceAgnostic(type: string) {
return this.types.get(type)?.namespaceAgnostic ?? false;
}

public isHidden(type: string) {
return this.types.get(type)?.hidden ?? false;
}

public getIndex(type: string) {
return this.types.get(type)?.indexPattern;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe you can add a comment to the code?

@kibanamachine
Copy link
Contributor

💚 Build Succeeded

History

To update your PR or re-run it, just comment with:
@elasticmachine merge upstream

@pgayvallet pgayvallet merged commit ea7c78d into elastic:master Feb 6, 2020
pgayvallet added a commit to pgayvallet/kibana that referenced this pull request Feb 6, 2020
* adapt types and tests to prepare for new NP api

* rename and export public types

* update generated doc

* first implementation of registerMappings

* adapt es archiver to convert legacy mappings

* update generated doc

* fix more tests

* add unit tests

* add legacy-compat unit test

* add documentation and examples

* Introduce SavedObjectTypeRegistry and SavedObjectType types

* add and fix tests

* expose createSerializer API and fix usages

* remove registerMappings API, add internal registerType

* revert changes to migration guide

* adapt ES-archiver migrator creation

* export serializer-related types

* update generated doc

* add and use convertTypesToLegacySchema

* remove / move to internal some mapping types

* fix forEach closure context

* add missing docs

* fix core path

* some nits

* fix so_mixin tests

* fix integration tests

* fix integration tests for real

* add documentation on serializer + restructure files and types

* nit

* add and use the ISavedObjectTypeRegistry interface

* Add documentation, deprecates migrationLogger#warning

* better typing for SavedObjectsRawDoc._source

* nits

* update generated doc

* remove exposition of SavedObjectsTypeMappingDefinitions, update doc

* creates so internal contracts mocks

* improve documentation
pgayvallet added a commit that referenced this pull request Feb 6, 2020
* adapt types and tests to prepare for new NP api

* rename and export public types

* update generated doc

* first implementation of registerMappings

* adapt es archiver to convert legacy mappings

* update generated doc

* fix more tests

* add unit tests

* add legacy-compat unit test

* add documentation and examples

* Introduce SavedObjectTypeRegistry and SavedObjectType types

* add and fix tests

* expose createSerializer API and fix usages

* remove registerMappings API, add internal registerType

* revert changes to migration guide

* adapt ES-archiver migrator creation

* export serializer-related types

* update generated doc

* add and use convertTypesToLegacySchema

* remove / move to internal some mapping types

* fix forEach closure context

* add missing docs

* fix core path

* some nits

* fix so_mixin tests

* fix integration tests

* fix integration tests for real

* add documentation on serializer + restructure files and types

* nit

* add and use the ISavedObjectTypeRegistry interface

* Add documentation, deprecates migrationLogger#warning

* better typing for SavedObjectsRawDoc._source

* nits

* update generated doc

* remove exposition of SavedObjectsTypeMappingDefinitions, update doc

* creates so internal contracts mocks

* improve documentation
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Feature:New Platform release_note:skip Skip the PR/issue when compiling release notes Team:Core Core services & architecture: plugins, logging, config, saved objects, http, ES client, i18n, etc v7.7.0 v8.0.0
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Restructure savedObject types and declarations
10 participants