mirror of
https://github.com/elastic/kibana.git
synced 2025-04-25 10:23:14 -04:00
fix [#178060](https://github.com/elastic/kibana/issues/178060) Updates the internal developer docs for transitions to new model versions. The end-user docs were updated in https://github.com/elastic/kibana/pull/176970 --------- Co-authored-by: Jean-Louis Leysens <jloleysens@gmail.com>
274 lines
No EOL
9.6 KiB
Text
274 lines
No EOL
9.6 KiB
Text
---
|
|
id: kibDevTutorialSavedObject
|
|
slug: /kibana-dev-docs/tutorials/saved-objects
|
|
title: Register a new saved object type
|
|
description: Learn how to register a new saved object type.
|
|
date: 2021-02-05
|
|
tags: ['kibana','onboarding', 'dev', 'architecture', 'tutorials']
|
|
---
|
|
|
|
Saved Object type definitions should be defined in their own `my_plugin/server/saved_objects` directory.
|
|
|
|
The folder should contain a file per type, named after the snake_case name of the type, and an index.ts file exporting all the types.
|
|
|
|
**src/plugins/my_plugin/server/saved_objects/dashboard_visualization.ts**
|
|
|
|
```ts
|
|
import { SavedObjectsType } from 'src/core/server';
|
|
|
|
export const dashboardVisualization: SavedObjectsType = {
|
|
name: 'dashboard_visualization', [1]
|
|
hidden: true,
|
|
switchToModelVersionAt: '8.10.0', // this is the default, feel free to omit it unless you intend to switch to using model versions before 8.10.0
|
|
namespaceType: 'multiple-isolated', [2]
|
|
mappings: {
|
|
dynamic: false,
|
|
properties: {
|
|
description: {
|
|
type: 'text',
|
|
},
|
|
hits: {
|
|
type: 'integer',
|
|
},
|
|
},
|
|
},
|
|
modelVersions: {
|
|
1: dashboardVisualizationModelVersionV1,
|
|
2: dashboardVisualizationModelVersionV2,
|
|
},
|
|
};
|
|
```
|
|
|
|
[1] Since the name of a Saved Object type forms part of the URL path for the public Saved Objects HTTP API,
|
|
these should follow our API URL path convention and always be written in snake case.
|
|
|
|
[2] This field determines "space behavior" -- whether these objects can exist in one space, multiple spaces, or all spaces. This value means
|
|
that objects of this type can only exist in a single space. See
|
|
<DocLink id="kibDevDocsSavedObjectsIntro" section="sharing-saved-objects" text="Sharing Saved Objects"/> for more information.
|
|
|
|
**src/plugins/my_plugin/server/saved_objects/index.ts**
|
|
|
|
```ts
|
|
export { dashboardVisualization } from './dashboard_visualization';
|
|
export { dashboard } from './dashboard';
|
|
```
|
|
|
|
**src/plugins/my_plugin/server/plugin.ts**
|
|
|
|
```ts
|
|
import { dashboard, dashboardVisualization } from './saved_objects';
|
|
|
|
export class MyPlugin implements Plugin {
|
|
setup({ savedObjects }) {
|
|
savedObjects.registerType(dashboard);
|
|
savedObjects.registerType(dashboardVisualization);
|
|
}
|
|
}
|
|
```
|
|
|
|
## Mappings
|
|
|
|
Each Saved Object type can define its own Elasticsearch field mappings. Because multiple Saved Object
|
|
types can share the same index, mappings defined by a type will be nested under a top-level field that matches the type name.
|
|
|
|
For example, the mappings defined by the dashboard_visualization Saved Object type:
|
|
|
|
**src/plugins/my_plugin/server/saved_objects/dashboard_visualization.ts**
|
|
|
|
```ts
|
|
import { SavedObjectsType } from 'src/core/server';
|
|
|
|
export const dashboardVisualization: SavedObjectsType = {
|
|
name: 'dashboard_visualization',
|
|
...
|
|
mappings: {
|
|
properties: {
|
|
dynamic: false,
|
|
description: {
|
|
type: 'text',
|
|
},
|
|
hits: {
|
|
type: 'integer',
|
|
},
|
|
},
|
|
},
|
|
modelVersions: { ... },
|
|
};
|
|
```
|
|
|
|
Will result in the following mappings being applied to the .kibana index:
|
|
|
|
```ts
|
|
{
|
|
"mappings": {
|
|
"dynamic": "strict",
|
|
"properties": {
|
|
...
|
|
"dashboard_vizualization": {
|
|
"dynamic": false,
|
|
"properties": {
|
|
"description": {
|
|
"type": "text",
|
|
},
|
|
"hits": {
|
|
"type": "integer",
|
|
},
|
|
},
|
|
}
|
|
}
|
|
}
|
|
}
|
|
```
|
|
Do not use field mappings like you would use data types for the columns of a SQL database. Instead, field mappings are analogous to a
|
|
SQL index. Only specify field mappings for the fields you wish to search on or query. By specifying `dynamic: false`
|
|
in any level of your mappings, Elasticsearch will accept and store any other fields even if they are not specified in your mappings.
|
|
|
|
Since Elasticsearch has a default limit of 1000 fields per index, plugins should carefully consider the
|
|
fields they add to the mappings. Similarly, Saved Object types should never use `dynamic: true` as this can cause an arbitrary
|
|
amount of fields to be added to the .kibana index.
|
|
|
|
## References
|
|
|
|
Declare <DocLink id="kibDevDocsSavedObjectsIntro" section="References" text="Saved Object references"/> by adding an id, type and name to the
|
|
`references` array.
|
|
|
|
```ts
|
|
router.get(
|
|
{ path: '/some-path', validate: false },
|
|
async (context, req, res) => {
|
|
const object = await context.core.savedObjects.client.create(
|
|
'dashboard',
|
|
{
|
|
title: 'my dashboard',
|
|
panels: [
|
|
{ visualization: 'vis1' }, [1]
|
|
],
|
|
indexPattern: 'indexPattern1'
|
|
},
|
|
{ references: [
|
|
{ id: '...', type: 'visualization', name: 'vis1' },
|
|
{ id: '...', type: 'index_pattern', name: 'indexPattern1' },
|
|
]
|
|
}
|
|
)
|
|
...
|
|
}
|
|
);
|
|
```
|
|
[1] Note how `dashboard.panels[0].visualization` stores the name property of the reference (not the id directly) to be able to uniquely
|
|
identify this reference. This guarantees that the id the reference points to always remains up to date. If a
|
|
visualization id was directly stored in `dashboard.panels[0].visualization` there is a risk that this id gets updated without
|
|
updating the reference in the references array.
|
|
|
|
## Migrations
|
|
|
|
Saved Objects support schema changes between Kibana versions, which we call migrations, implemented with model versions.
|
|
Model version transitions are applied when a Kibana installation is upgraded from one version to a newer version, when exports are imported via
|
|
the Saved Objects Management UI, or when a new object is created via the HTTP API.
|
|
|
|
### Defining model versions
|
|
|
|
Model versions are bound to a given [savedObject type](https://github.com/elastic/kibana/blob/9b330e493216e8dde3166451e4714966f63f5ab7/packages/core/saved-objects/core-saved-objects-server/src/saved_objects_type.ts#L22-L27)
|
|
|
|
When registering a SO type, a [modelVersions](https://github.com/elastic/kibana/blob/9a6a2ccdff619f827b31c40dd9ed30cb27203da7/packages/core/saved-objects/core-saved-objects-server/src/saved_objects_type.ts#L138-L177)
|
|
property is available. This attribute is a map of version numbers to [SavedObjectsModelVersion](https://github.com/elastic/kibana/blob/9a6a2ccdff619f827b31c40dd9ed30cb27203da7/packages/core/saved-objects/core-saved-objects-server/src/model_version/model_version.ts#L12-L20)
|
|
which is the top-level type/container to define model versions.
|
|
|
|
The modelVersion map is of the form `{ [version: number] => versionDefinition }`, using single integer to identify a version definition.
|
|
|
|
The first version must be numbered as version 1, incrementing by one for each new version.
|
|
|
|
```ts
|
|
import { schema } from '@kbn/config-schema';
|
|
import { SavedObjectsType } from 'src/core/server';
|
|
|
|
|
|
const schemaV1 = schema.object({ title: schema.string({ maxLength: 50, minLength: 1 }) });
|
|
const schemaV2 = schemaV1.extends({
|
|
description: schema.maybe(schema.string({ maxLength: 200, minLength: 1 })),
|
|
});
|
|
|
|
export const dashboardVisualization: SavedObjectsType = {
|
|
name: 'dashboard_visualization',
|
|
...
|
|
mappings: {
|
|
dynamic: false,
|
|
properties: {
|
|
title: { type: 'text' }, // This mapping was added before model versions
|
|
description: { type: 'text' }, // mappings introduced in v2
|
|
},
|
|
},
|
|
modelVersions: {
|
|
1: {
|
|
// Sometimes no changes are needed in the initial version, but you may have
|
|
// pre-existing mappings or data that must be transformed in some way
|
|
// In this case, title already has mappings defined.
|
|
changes: [],
|
|
schemas: {
|
|
// The forward compatible schema should allow any future versions of
|
|
// this SO to be converted to this version, since we are using
|
|
// @kbn/config-schema we opt-in to unknowns to allow the schema to
|
|
// successfully "downgrade" future SOs to this version.
|
|
forwardCompatibility: schemaV1.extends({}, { unknowns: 'ignore' }),
|
|
create: schemaV1,
|
|
},
|
|
},
|
|
2: {
|
|
changes: [
|
|
// In this second version we added new mappings for the description field.
|
|
{
|
|
type: 'mappings_addition',
|
|
addedMappings: {
|
|
description: { type: 'keyword' },
|
|
},
|
|
},
|
|
{
|
|
type: 'data_backfill',
|
|
backfillFn: (doc) => {
|
|
return {
|
|
attributes: {
|
|
description: 'my default description',
|
|
},
|
|
};
|
|
},
|
|
},
|
|
],
|
|
schemas: {
|
|
forwardCompatibility: schemaV2.extends({}, { unknowns: 'ignore' }),
|
|
create: schemaV2,
|
|
},
|
|
},
|
|
},
|
|
};
|
|
```
|
|
|
|
That way:
|
|
- SO type versions are decoupled from stack versioning
|
|
- SO type versions are independent between types
|
|
|
|
### Testing model versions
|
|
Bugs in model version transitions cause downtime for our users and therefore have a very high impact. Follow the <DocLink id="kibDevTutorialTestingPlugins" section="saved-objects-model-versions" text="Saved Objects model versions"/> section in the plugin testing guide.
|
|
|
|
### How to opt-out of the global savedObjects APIs?
|
|
|
|
There are 2 options, depending on the amount of flexibility you need:
|
|
For complete control over your HTTP APIs and custom handling, declare your type as `hidden`, as shown in the example.
|
|
The other option that allows you to build your own HTTP APIs and still use the client as-is is to declare your type as hidden from the global saved objects HTTP APIs as `hiddenFromHttpApis: true`
|
|
|
|
```ts
|
|
import { SavedObjectsType } from 'src/core/server';
|
|
|
|
export const foo: SavedObjectsType = {
|
|
name: 'foo',
|
|
hidden: false, [1]
|
|
hiddenFromHttpApis: true, [2]
|
|
namespaceType: 'multiple-isolated',
|
|
mappings: { ... },
|
|
modelVersions: { ... },
|
|
...
|
|
};
|
|
```
|
|
|
|
[1] Needs to be `false` to use the `hiddenFromHttpApis` option
|
|
|
|
[2] Set this to `true` to build your own HTTP API and have complete control over the route handler. |