[SOM] Add visibleInManagement option to management metadata (#112073)

* implement SavedObjectsTypeManagementDefinition.visibleInManagement

* update generated doc

* improve FTR tests

* fix FTR test
This commit is contained in:
Pierre Gayvallet 2021-09-21 10:30:30 +02:00 committed by GitHub
parent 0af821aaf9
commit 221ee74c6b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 737 additions and 0 deletions

View file

@ -27,6 +27,7 @@ async function getDeprecations({ esClient, savedObjectsClient }: GetDeprecations
const deprecations: DeprecationsDetails[] = [];
const count = await getFooCount(savedObjectsClient);
if (count > 0) {
// Example of a manual correctiveAction
deprecations.push({
title: i18n.translate('xpack.foo.deprecations.title', {
defaultMessage: `Foo's are deprecated`

View file

@ -25,4 +25,5 @@ export interface SavedObjectsTypeManagementDefinition<Attributes = any>
| [isExportable](./kibana-plugin-core-server.savedobjectstypemanagementdefinition.isexportable.md) | <code>SavedObjectsExportablePredicate&lt;Attributes&gt;</code> | Optional hook to specify whether an object should be exportable.<!-- -->If specified, <code>isExportable</code> will be called during export for each of this type's objects in the export, and the ones not matching the predicate will be excluded from the export.<!-- -->When implementing both <code>isExportable</code> and <code>onExport</code>, it is mandatory that <code>isExportable</code> returns the same value for an object before and after going though the export transform. E.g <code>isExportable(objectBeforeTransform) === isExportable(objectAfterTransform)</code> |
| [onExport](./kibana-plugin-core-server.savedobjectstypemanagementdefinition.onexport.md) | <code>SavedObjectsExportTransform&lt;Attributes&gt;</code> | An optional export transform function that can be used transform the objects of the registered type during the export process.<!-- -->It can be used to either mutate the exported objects, or add additional objects (of any type) to the export list.<!-- -->See [the transform type documentation](./kibana-plugin-core-server.savedobjectsexporttransform.md) for more info and examples.<!-- -->When implementing both <code>isExportable</code> and <code>onExport</code>, it is mandatory that <code>isExportable</code> returns the same value for an object before and after going though the export transform. E.g <code>isExportable(objectBeforeTransform) === isExportable(objectAfterTransform)</code> |
| [onImport](./kibana-plugin-core-server.savedobjectstypemanagementdefinition.onimport.md) | <code>SavedObjectsImportHook&lt;Attributes&gt;</code> | An optional [import hook](./kibana-plugin-core-server.savedobjectsimporthook.md) to use when importing given type.<!-- -->Import hooks are executed during the savedObjects import process and allow to interact with the imported objects. See the [hook documentation](./kibana-plugin-core-server.savedobjectsimporthook.md) for more info. |
| [visibleInManagement](./kibana-plugin-core-server.savedobjectstypemanagementdefinition.visibleinmanagement.md) | <code>boolean</code> | When set to false, the type will not be listed or searchable in the SO management section. Main usage of setting this property to false for a type is when objects from the type should be included in the export via references or export hooks, but should not directly appear in the SOM. Defaults to <code>true</code>. |

View file

@ -0,0 +1,18 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [kibana-plugin-core-server](./kibana-plugin-core-server.md) &gt; [SavedObjectsTypeManagementDefinition](./kibana-plugin-core-server.savedobjectstypemanagementdefinition.md) &gt; [visibleInManagement](./kibana-plugin-core-server.savedobjectstypemanagementdefinition.visibleinmanagement.md)
## SavedObjectsTypeManagementDefinition.visibleInManagement property
When set to false, the type will not be listed or searchable in the SO management section. Main usage of setting this property to false for a type is when objects from the type should be included in the export via references or export hooks, but should not directly appear in the SOM. Defaults to `true`<!-- -->.
<b>Signature:</b>
```typescript
visibleInManagement?: boolean;
```
## Remarks
`importableAndExportable` must be `true` to specify this property.

View file

@ -47,6 +47,59 @@ describe('SavedObjectTypeRegistry', () => {
}).toThrowErrorMatchingInlineSnapshot(`"Type 'typeA' is already registered"`);
});
it('throws when `management.visibleInManagement` is specified but `management.importableAndExportable` is undefined or false', () => {
expect(() => {
registry.registerType(
createType({
name: 'typeA',
management: {
visibleInManagement: true,
},
})
);
}).toThrowErrorMatchingInlineSnapshot(
`"Type typeA: 'management.importableAndExportable' must be 'true' when specifying 'management.visibleInManagement'"`
);
expect(() => {
registry.registerType(
createType({
name: 'typeA',
management: {
visibleInManagement: false,
},
})
);
}).toThrowErrorMatchingInlineSnapshot(
`"Type typeA: 'management.importableAndExportable' must be 'true' when specifying 'management.visibleInManagement'"`
);
expect(() => {
registry.registerType(
createType({
name: 'typeA',
management: {
importableAndExportable: false,
visibleInManagement: false,
},
})
);
}).toThrowErrorMatchingInlineSnapshot(
`"Type typeA: 'management.importableAndExportable' must be 'true' when specifying 'management.visibleInManagement'"`
);
expect(() => {
registry.registerType(
createType({
name: 'typeA',
management: {
importableAndExportable: true,
visibleInManagement: false,
},
})
);
}).not.toThrow();
});
it('throws when `management.onExport` is specified but `management.importableAndExportable` is undefined or false', () => {
expect(() => {
registry.registerType(

View file

@ -134,5 +134,10 @@ const validateType = ({ name, management }: SavedObjectsType) => {
`Type ${name}: 'management.importableAndExportable' must be 'true' when specifying 'management.onExport'`
);
}
if (management.visibleInManagement !== undefined && !management.importableAndExportable) {
throw new Error(
`Type ${name}: 'management.importableAndExportable' must be 'true' when specifying 'management.visibleInManagement'`
);
}
}
};

View file

@ -356,6 +356,15 @@ export interface SavedObjectsTypeManagementDefinition<Attributes = any> {
* Is the type importable or exportable. Defaults to `false`.
*/
importableAndExportable?: boolean;
/**
* When set to false, the type will not be listed or searchable in the SO management section.
* Main usage of setting this property to false for a type is when objects from the type should
* be included in the export via references or export hooks, but should not directly appear in the SOM.
* Defaults to `true`.
*
* @remarks `importableAndExportable` must be `true` to specify this property.
*/
visibleInManagement?: boolean;
/**
* The default search field to use for this type. Defaults to `id`.
*/

View file

@ -2751,6 +2751,7 @@ export interface SavedObjectsTypeManagementDefinition<Attributes = any> {
isExportable?: SavedObjectsExportablePredicate<Attributes>;
onExport?: SavedObjectsExportTransform<Attributes>;
onImport?: SavedObjectsImportHook<Attributes>;
visibleInManagement?: boolean;
}
// @public

View file

@ -17,6 +17,7 @@ export const registerGetAllowedTypesRoute = (router: IRouter) => {
async (context, req, res) => {
const allowedTypes = context.core.savedObjects.typeRegistry
.getImportableAndExportableTypes()
.filter((type) => type.management!.visibleInManagement ?? true)
.map((type) => type.name);
return res.ok({

View file

@ -0,0 +1,20 @@
{
"type": "doc",
"value": {
"id": "test-not-visible-in-management:vim-1",
"index": ".kibana",
"source": {
"coreMigrationVersion": "7.14.0",
"references": [
],
"test-not-visible-in-management": {
"enabled": true,
"title": "vim-1"
},
"type": "test-not-visible-in-management",
"updated_at": "2018-12-21T00:43:07.096Z"
},
"type": "_doc"
}
}

View file

@ -0,0 +1,477 @@
{
"type": "index",
"value": {
"aliases": {
".kibana_$KIBANA_PACKAGE_VERSION": {},
".kibana": {}
},
"index": ".kibana_$KIBANA_PACKAGE_VERSION_001",
"mappings": {
"_meta": {
"migrationMappingPropertyHashes": {
"application_usage_daily": "43b8830d5d0df85a6823d290885fc9fd",
"application_usage_totals": "3d1b76c39bfb2cc8296b024d73854724",
"application_usage_transactional": "3d1b76c39bfb2cc8296b024d73854724",
"config": "c63748b75f39d0c54de12d12c1ccbc20",
"core-usage-stats": "3d1b76c39bfb2cc8296b024d73854724",
"coreMigrationVersion": "2f4316de49999235636386fe51dc06c1",
"dashboard": "40554caf09725935e2c02e02563a2d07",
"index-pattern": "45915a1ad866812242df474eb0479052",
"kql-telemetry": "d12a98a6f19a2d273696597547e064ee",
"legacy-url-alias": "6155300fd11a00e23d5cbaa39f0fce0a",
"migrationVersion": "4a1746014a75ade3a714e1db5763276f",
"namespace": "2f4316de49999235636386fe51dc06c1",
"namespaces": "2f4316de49999235636386fe51dc06c1",
"originId": "2f4316de49999235636386fe51dc06c1",
"query": "11aaeb7f5f7fa5bb43f25e18ce26e7d9",
"references": "7997cf5a56cc02bdc9c93361bde732b0",
"sample-data-telemetry": "7d3cfeb915303c9641c59681967ffeb4",
"search": "db2c00e39b36f40930a3b9fc71c823e1",
"search-telemetry": "3d1b76c39bfb2cc8296b024d73854724",
"telemetry": "36a616f7026dfa617d6655df850fe16d",
"type": "2f4316de49999235636386fe51dc06c1",
"ui-counter": "0d409297dc5ebe1e3a1da691c6ee32e3",
"ui-metric": "0d409297dc5ebe1e3a1da691c6ee32e3",
"updated_at": "00da57df13e94e9d98437d13ace4bfe0",
"url": "c7f66a0df8b1b52f17c28c4adb111105",
"usage-counters": "8cc260bdceffec4ffc3ad165c97dc1b4",
"visualization": "f819cf6636b75c9e76ba733a0c6ef355"
}
},
"dynamic": "strict",
"properties": {
"apm-telemetry": {
"dynamic": "false",
"type": "object"
},
"application_usage_daily": {
"dynamic": "false",
"properties": {
"timestamp": {
"type": "date"
}
}
},
"application_usage_totals": {
"dynamic": "false",
"type": "object"
},
"application_usage_transactional": {
"dynamic": "false",
"type": "object"
},
"canvas-workpad": {
"dynamic": "false",
"type": "object"
},
"config": {
"dynamic": "false",
"properties": {
"buildNum": {
"type": "keyword"
}
}
},
"core-usage-stats": {
"dynamic": "false",
"type": "object"
},
"coreMigrationVersion": {
"type": "keyword"
},
"dashboard": {
"properties": {
"description": {
"type": "text"
},
"hits": {
"doc_values": false,
"index": false,
"type": "integer"
},
"kibanaSavedObjectMeta": {
"properties": {
"searchSourceJSON": {
"index": false,
"type": "text"
}
}
},
"optionsJSON": {
"index": false,
"type": "text"
},
"panelsJSON": {
"index": false,
"type": "text"
},
"refreshInterval": {
"properties": {
"display": {
"doc_values": false,
"index": false,
"type": "keyword"
},
"pause": {
"doc_values": false,
"index": false,
"type": "boolean"
},
"section": {
"doc_values": false,
"index": false,
"type": "integer"
},
"value": {
"doc_values": false,
"index": false,
"type": "integer"
}
}
},
"timeFrom": {
"doc_values": false,
"index": false,
"type": "keyword"
},
"timeRestore": {
"doc_values": false,
"index": false,
"type": "boolean"
},
"timeTo": {
"doc_values": false,
"index": false,
"type": "keyword"
},
"title": {
"type": "text"
},
"version": {
"type": "integer"
}
}
},
"graph-workspace": {
"dynamic": "false",
"type": "object"
},
"index-pattern": {
"dynamic": "false",
"properties": {
"title": {
"type": "text"
},
"type": {
"type": "keyword"
}
}
},
"kql-telemetry": {
"properties": {
"optInCount": {
"type": "long"
},
"optOutCount": {
"type": "long"
}
}
},
"legacy-url-alias": {
"dynamic": "false",
"properties": {
"disabled": {
"type": "boolean"
},
"sourceId": {
"type": "keyword"
},
"targetType": {
"type": "keyword"
}
}
},
"map": {
"dynamic": "false",
"type": "object"
},
"migrationVersion": {
"dynamic": "true",
"type": "object"
},
"namespace": {
"type": "keyword"
},
"namespaces": {
"type": "keyword"
},
"originId": {
"type": "keyword"
},
"query": {
"properties": {
"description": {
"type": "text"
},
"filters": {
"enabled": false,
"type": "object"
},
"query": {
"properties": {
"language": {
"type": "keyword"
},
"query": {
"index": false,
"type": "keyword"
}
}
},
"timefilter": {
"enabled": false,
"type": "object"
},
"title": {
"type": "text"
}
}
},
"references": {
"properties": {
"id": {
"type": "keyword"
},
"name": {
"type": "keyword"
},
"type": {
"type": "keyword"
}
},
"type": "nested"
},
"sample-data-telemetry": {
"properties": {
"installCount": {
"type": "long"
},
"unInstallCount": {
"type": "long"
}
}
},
"search": {
"properties": {
"columns": {
"doc_values": false,
"index": false,
"type": "keyword"
},
"description": {
"type": "text"
},
"grid": {
"enabled": false,
"type": "object"
},
"hideChart": {
"doc_values": false,
"index": false,
"type": "boolean"
},
"hits": {
"doc_values": false,
"index": false,
"type": "integer"
},
"kibanaSavedObjectMeta": {
"properties": {
"searchSourceJSON": {
"index": false,
"type": "text"
}
}
},
"sort": {
"doc_values": false,
"index": false,
"type": "keyword"
},
"title": {
"type": "text"
},
"version": {
"type": "integer"
}
}
},
"search-telemetry": {
"dynamic": "false",
"type": "object"
},
"server": {
"dynamic": "false",
"type": "object"
},
"space": {
"dynamic": "false",
"type": "object"
},
"spaceId": {
"type": "keyword"
},
"telemetry": {
"properties": {
"allowChangingOptInStatus": {
"type": "boolean"
},
"enabled": {
"type": "boolean"
},
"lastReported": {
"type": "date"
},
"lastVersionChecked": {
"type": "keyword"
},
"reportFailureCount": {
"type": "integer"
},
"reportFailureVersion": {
"type": "keyword"
},
"sendUsageFrom": {
"type": "keyword"
},
"userHasSeenNotice": {
"type": "boolean"
}
}
},
"test-export-add": {
"dynamic": "false",
"type": "object"
},
"test-export-add-dep": {
"dynamic": "false",
"type": "object"
},
"test-export-invalid-transform": {
"dynamic": "false",
"type": "object"
},
"test-export-transform": {
"dynamic": "false",
"type": "object"
},
"test-export-transform-error": {
"dynamic": "false",
"type": "object"
},
"test-visible-in-management": {
"dynamic": "false",
"type": "object"
},
"test-not-visible-in-management": {
"dynamic": "false",
"type": "object"
},
"type": {
"type": "keyword"
},
"ui-counter": {
"properties": {
"count": {
"type": "integer"
}
}
},
"ui-metric": {
"properties": {
"count": {
"type": "integer"
}
}
},
"updated_at": {
"type": "date"
},
"url": {
"properties": {
"accessCount": {
"type": "long"
},
"accessDate": {
"type": "date"
},
"createDate": {
"type": "date"
},
"url": {
"fields": {
"keyword": {
"ignore_above": 2048,
"type": "keyword"
}
},
"type": "text"
}
}
},
"usage-counters": {
"dynamic": "false",
"properties": {
"domainId": {
"type": "keyword"
}
}
},
"visualization": {
"properties": {
"description": {
"type": "text"
},
"kibanaSavedObjectMeta": {
"properties": {
"searchSourceJSON": {
"index": false,
"type": "text"
}
}
},
"savedSearchRefName": {
"doc_values": false,
"index": false,
"type": "keyword"
},
"title": {
"type": "text"
},
"uiStateJSON": {
"index": false,
"type": "text"
},
"version": {
"type": "integer"
},
"visState": {
"index": false,
"type": "text"
}
}
}
}
},
"settings": {
"index": {
"auto_expand_replicas": "0-1",
"number_of_replicas": "0",
"number_of_shards": "1",
"priority": "10",
"refresh_interval": "1s",
"routing_partition_size": "1"
}
}
}
}

View file

@ -176,6 +176,42 @@ export class SavedObjectExportTransformsPlugin implements Plugin {
},
},
});
// example of a SO type with `visibleInManagement: false`
savedObjects.registerType<{ enabled: boolean; title: string }>({
name: 'test-not-visible-in-management',
hidden: false,
namespaceType: 'single',
mappings: {
properties: {
title: { type: 'text' },
enabled: { type: 'boolean' },
},
},
management: {
defaultSearchField: 'title',
importableAndExportable: true,
visibleInManagement: false,
},
});
// example of a SO type with `visibleInManagement: true`
savedObjects.registerType<{ enabled: boolean; title: string }>({
name: 'test-visible-in-management',
hidden: false,
namespaceType: 'single',
mappings: {
properties: {
title: { type: 'text' },
enabled: { type: 'boolean' },
},
},
management: {
defaultSearchField: 'title',
importableAndExportable: true,
visibleInManagement: true,
},
});
}
public start() {}

View file

@ -0,0 +1 @@
{"attributes": { "title": "Saved object type that is not visible in management" }, "id":"ff3773b0-9ate-11e7-ahb3-3dcb94193fab", "references":[], "type":"test-not-visible-in-management", "version":1}

View file

@ -16,5 +16,6 @@ export default function ({ loadTestFile }: PluginFunctionalProviderContext) {
loadTestFile(require.resolve('./export_transform'));
loadTestFile(require.resolve('./import_warnings'));
loadTestFile(require.resolve('./hidden_types'));
loadTestFile(require.resolve('./visible_in_management'));
});
}

View file

@ -0,0 +1,113 @@
/*
* 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 { join } from 'path';
import expect from '@kbn/expect';
import type { Response } from 'supertest';
import type { PluginFunctionalProviderContext } from '../../services';
import { SavedObject } from '../../../../src/core/types';
function parseNdJson(input: string): Array<SavedObject<any>> {
return input.split('\n').map((str) => JSON.parse(str));
}
export default function ({ getService }: PluginFunctionalProviderContext) {
const supertest = getService('supertest');
const esArchiver = getService('esArchiver');
describe('types with `visibleInManagement` ', () => {
before(async () => {
await esArchiver.load(
'test/functional/fixtures/es_archiver/saved_objects_management/visible_in_management'
);
});
after(async () => {
await esArchiver.unload(
'test/functional/fixtures/es_archiver/saved_objects_management/visible_in_management'
);
});
describe('export', () => {
it('allows to export them directly by id', async () => {
await supertest
.post('/api/saved_objects/_export')
.set('kbn-xsrf', 'true')
.send({
objects: [
{
type: 'test-not-visible-in-management',
id: 'vim-1',
},
],
excludeExportDetails: true,
})
.expect(200)
.then((resp) => {
const objects = parseNdJson(resp.text);
expect(objects.map((obj) => obj.id)).to.eql(['vim-1']);
});
});
it('allows to export them directly by type', async () => {
await supertest
.post('/api/saved_objects/_export')
.set('kbn-xsrf', 'true')
.send({
type: ['test-not-visible-in-management'],
excludeExportDetails: true,
})
.expect(200)
.then((resp) => {
const objects = parseNdJson(resp.text);
expect(objects.map((obj) => obj.id)).to.eql(['vim-1']);
});
});
});
describe('import', () => {
it('allows to import them', async () => {
await supertest
.post('/api/saved_objects/_import')
.set('kbn-xsrf', 'true')
.attach('file', join(__dirname, './exports/_import_non_visible_in_management.ndjson'))
.expect(200)
.then((resp) => {
expect(resp.body).to.eql({
success: true,
successCount: 1,
successResults: [
{
id: 'ff3773b0-9ate-11e7-ahb3-3dcb94193fab',
meta: {
title: 'Saved object type that is not visible in management',
},
type: 'test-not-visible-in-management',
},
],
warnings: [],
});
});
});
});
describe('savedObjects management APIS', () => {
it('GET /api/kibana/management/saved_objects/_allowed_types should only return types that are `visibleInManagement: true`', async () =>
await supertest
.get('/api/kibana/management/saved_objects/_allowed_types')
.set('kbn-xsrf', 'true')
.expect(200)
.then((response: Response) => {
const { types } = response.body;
expect(types.includes('test-is-exportable')).to.eql(true);
expect(types.includes('test-visible-in-management')).to.eql(true);
expect(types.includes('test-not-visible-in-management')).to.eql(false);
}));
});
});
}