[content management / maps] Content management / Saved object schema abstraction (#155342)

## Summary

Abstract schema definitions for using Saved Objects with the content
management api. For most schema types, this will reduce creation to only
the attributes specific to a saved object. For Option types (create
options, update options, search options) the saved object api is more
complex and its likely that most SO types will only need to use a
portion of it. In these cases we recommend using the provided schema
definitions as a pattern for creating simpler schemas.

Follow up to - https://github.com/elastic/kibana/pull/154985 - expresses
types in schema form

---------

Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Matthew Kime 2023-04-27 10:30:14 -05:00 committed by GitHub
parent b01d60a994
commit 0c8f38b86f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 143 additions and 72 deletions

View file

@ -6,4 +6,5 @@
* Side Public License, v 1.
*/
export * from './types';
export * from './src/types';
export * from './src/schema';

View file

@ -0,0 +1,122 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import { schema, ObjectType } from '@kbn/config-schema';
export const apiError = schema.object({
error: schema.string(),
message: schema.string(),
statusCode: schema.number(),
metadata: schema.object({}, { unknowns: 'allow' }),
});
export const referenceSchema = schema.object(
{
name: schema.maybe(schema.string()),
type: schema.string(),
id: schema.string(),
},
{ unknowns: 'forbid' }
);
export const referencesSchema = schema.arrayOf(referenceSchema);
export const savedObjectSchema = (attributesSchema: ObjectType<any>) =>
schema.object(
{
id: schema.string(),
type: schema.string(),
version: schema.maybe(schema.string()),
createdAt: schema.maybe(schema.string()),
updatedAt: schema.maybe(schema.string()),
error: schema.maybe(apiError),
attributes: attributesSchema,
references: referencesSchema,
namespaces: schema.maybe(schema.arrayOf(schema.string())),
originId: schema.maybe(schema.string()),
},
{ unknowns: 'allow' }
);
export const objectTypeToGetResultSchema = (soSchema: ObjectType<any>) =>
schema.object(
{
item: soSchema,
meta: schema.object(
{
outcome: schema.oneOf([
schema.literal('exactMatch'),
schema.literal('aliasMatch'),
schema.literal('conflict'),
]),
aliasTargetId: schema.maybe(schema.string()),
aliasPurpose: schema.maybe(
schema.oneOf([
schema.literal('savedObjectConversion'),
schema.literal('savedObjectImport'),
])
),
},
{ unknowns: 'forbid' }
),
},
{ unknowns: 'forbid' }
);
// its recommended to create a subset of this schema for stricter validation
export const createOptionsSchemas = {
id: schema.maybe(schema.string()),
references: schema.maybe(referencesSchema),
overwrite: schema.maybe(schema.boolean()),
version: schema.maybe(schema.string()),
refresh: schema.maybe(schema.boolean()),
initialNamespaces: schema.maybe(schema.arrayOf(schema.string())),
};
export const schemaAndOr = schema.oneOf([schema.literal('AND'), schema.literal('OR')]);
// its recommended to create a subset of this schema for stricter validation
export const searchOptionsSchemas = {
page: schema.maybe(schema.number()),
perPage: schema.maybe(schema.number()),
sortField: schema.maybe(schema.string()),
sortOrder: schema.maybe(schema.oneOf([schema.literal('asc'), schema.literal('desc')])),
fields: schema.maybe(schema.arrayOf(schema.string())),
search: schema.maybe(schema.string()),
searchFields: schema.maybe(schema.oneOf([schema.string(), schema.arrayOf(schema.string())])),
rootSearchFields: schema.maybe(schema.arrayOf(schema.string())),
hasReference: schema.maybe(schema.oneOf([referenceSchema, schema.arrayOf(referenceSchema)])),
hasReferenceOperator: schema.maybe(schemaAndOr),
hasNoReference: schema.maybe(schema.oneOf([referenceSchema, schema.arrayOf(referenceSchema)])),
hasNoReferenceOperator: schema.maybe(schemaAndOr),
defaultSearchOperator: schema.maybe(schemaAndOr),
namespaces: schema.maybe(schema.arrayOf(schema.string())),
type: schema.maybe(schema.string()),
filter: schema.maybe(schema.string()),
pit: schema.maybe(
schema.object({ id: schema.string(), keepAlive: schema.maybe(schema.string()) })
),
};
// its recommended to create a subset of this schema for stricter validation
export const updateOptionsSchema = {
references: schema.maybe(referencesSchema),
version: schema.maybe(schema.string()),
refresh: schema.maybe(schema.oneOf([schema.boolean(), schema.literal('wait_for')])),
upsert: (attributesSchema: ObjectType<any>) => schema.maybe(savedObjectSchema(attributesSchema)),
retryOnConflict: schema.maybe(schema.number()),
};
export const createResultSchema = (soSchema: ObjectType<any>) =>
schema.object(
{
item: soSchema,
},
{ unknowns: 'forbid' }
);

View file

@ -17,6 +17,7 @@
],
"kbn_references": [
"@kbn/content-management-plugin",
"@kbn/config-schema",
"@kbn/core-saved-objects-api-server",
]
}

View file

@ -6,24 +6,12 @@
*/
import { schema } from '@kbn/config-schema';
import type { ContentManagementServicesDefinition as ServicesDefinition } from '@kbn/object-versioning';
const apiError = schema.object({
error: schema.string(),
message: schema.string(),
statusCode: schema.number(),
metadata: schema.object({}, { unknowns: 'allow' }),
});
const referenceSchema = schema.object(
{
name: schema.maybe(schema.string()),
type: schema.string(),
id: schema.string(),
},
{ unknowns: 'forbid' }
);
const referencesSchema = schema.arrayOf(referenceSchema);
import {
savedObjectSchema,
objectTypeToGetResultSchema,
createOptionsSchemas,
createResultSchema,
} from '@kbn/content-management-utils';
const mapAttributesSchema = schema.object(
{
@ -36,48 +24,19 @@ const mapAttributesSchema = schema.object(
{ unknowns: 'forbid' }
);
const mapSavedObjectSchema = schema.object(
{
id: schema.string(),
type: schema.string(),
version: schema.maybe(schema.string()),
createdAt: schema.maybe(schema.string()),
updatedAt: schema.maybe(schema.string()),
error: schema.maybe(apiError),
attributes: mapAttributesSchema,
references: referencesSchema,
namespaces: schema.maybe(schema.arrayOf(schema.string())),
originId: schema.maybe(schema.string()),
},
{ unknowns: 'allow' }
);
const mapSavedObjectSchema = savedObjectSchema(mapAttributesSchema);
const getResultSchema = schema.object(
{
item: mapSavedObjectSchema,
meta: schema.object(
{
outcome: schema.oneOf([
schema.literal('exactMatch'),
schema.literal('aliasMatch'),
schema.literal('conflict'),
]),
aliasTargetId: schema.maybe(schema.string()),
aliasPurpose: schema.maybe(
schema.oneOf([
schema.literal('savedObjectConversion'),
schema.literal('savedObjectImport'),
])
),
},
{ unknowns: 'forbid' }
),
},
{ unknowns: 'forbid' }
const searchOptionsSchema = schema.maybe(
schema.object(
{
onlyTitle: schema.maybe(schema.boolean()),
},
{ unknowns: 'forbid' }
)
);
const createOptionsSchema = schema.object({
references: schema.maybe(referencesSchema),
references: schema.maybe(createOptionsSchemas.references),
});
// Content management service definition.
@ -86,7 +45,7 @@ export const serviceDefinition: ServicesDefinition = {
get: {
out: {
result: {
schema: getResultSchema,
schema: objectTypeToGetResultSchema(mapSavedObjectSchema),
},
},
},
@ -101,12 +60,7 @@ export const serviceDefinition: ServicesDefinition = {
},
out: {
result: {
schema: schema.object(
{
item: mapSavedObjectSchema,
},
{ unknowns: 'forbid' }
),
schema: createResultSchema(mapSavedObjectSchema),
},
},
},
@ -123,14 +77,7 @@ export const serviceDefinition: ServicesDefinition = {
search: {
in: {
options: {
schema: schema.maybe(
schema.object(
{
onlyTitle: schema.maybe(schema.boolean()),
},
{ unknowns: 'forbid' }
)
),
schema: searchOptionsSchema,
},
},
},