SOM: hide actions for hidden types (#98290)

* SOM: hide actions for hidden types

* fix FTR tests

* add and fix tests

* fix unit tests

* fix test types

* fix FTR test assertions

* add more FTR tests

* delete old file
This commit is contained in:
Pierre Gayvallet 2021-04-28 07:58:45 +02:00 committed by GitHub
parent 6c635b3b98
commit 9dae1ef5b1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
28 changed files with 895 additions and 82 deletions

View file

@ -6,7 +6,7 @@
* Side Public License, v 1.
*/
export {
export type {
SavedObjectWithMetadata,
SavedObjectMetadata,
SavedObjectRelation,

View file

@ -19,6 +19,7 @@ export interface SavedObjectMetadata {
editUrl?: string;
inAppUrl?: { path: string; uiCapabilitiesPath: string };
namespaceType?: SavedObjectsNamespaceType;
hiddenType?: boolean;
}
/**

View file

@ -89,7 +89,7 @@ export class SavedObjectEdition extends Component<
<EuiPageContent horizontalPosition="center" data-test-subj="savedObjectsEdit">
<Header
canEdit={canEdit}
canDelete={canDelete}
canDelete={canDelete && !object?.meta.hiddenType}
canViewInApp={canView}
type={type}
onDeleteClick={() => this.delete()}

View file

@ -9,10 +9,12 @@ exports[`SavedObjectsTable delete should show a confirm modal 1`] = `
Array [
Object {
"id": "1",
"meta": Object {},
"type": "index-pattern",
},
Object {
"id": "3",
"meta": Object {},
"type": "dashboard",
},
]

View file

@ -6,7 +6,7 @@
* Side Public License, v 1.
*/
import React, { FC } from 'react';
import React, { FC, useMemo } from 'react';
import {
EuiInMemoryTable,
EuiLoadingElastic,
@ -23,6 +23,7 @@ import {
EuiButtonEmpty,
EuiButton,
EuiSpacer,
EuiCallOut,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
@ -42,6 +43,13 @@ export const DeleteConfirmModal: FC<DeleteConfirmModalProps> = ({
onCancel,
selectedObjects,
}) => {
const undeletableObjects = useMemo(() => {
return selectedObjects.filter((obj) => obj.meta.hiddenType);
}, [selectedObjects]);
const deletableObjects = useMemo(() => {
return selectedObjects.filter((obj) => !obj.meta.hiddenType);
}, [selectedObjects]);
if (isDeleting) {
return (
<EuiOverlayMask>
@ -49,7 +57,6 @@ export const DeleteConfirmModal: FC<DeleteConfirmModalProps> = ({
</EuiOverlayMask>
);
}
// can't use `EuiConfirmModal` here as the confirm modal body is wrapped
// inside a `<p>` element, causing UI glitches with the table.
return (
@ -63,6 +70,29 @@ export const DeleteConfirmModal: FC<DeleteConfirmModalProps> = ({
</EuiModalHeaderTitle>
</EuiModalHeader>
<EuiModalBody>
{undeletableObjects.length > 0 && (
<>
<EuiCallOut
data-test-subj="cannotDeleteObjectsConfirmWarning"
title={
<FormattedMessage
id="savedObjectsManagement.objectsTable.deleteConfirmModal.cannotDeleteCallout.title"
defaultMessage="Some objects cannot deleted"
/>
}
iconType="alert"
color="warning"
>
<p>
<FormattedMessage
id="savedObjectsManagement.objectsTable.deleteConfirmModal.cannotDeleteCallout.content"
defaultMessage="Some of the selected objects cannot be deleted, and are not listed in the table summary"
/>
</p>
</EuiCallOut>
<EuiSpacer size="s" />
</>
)}
<p>
<FormattedMessage
id="savedObjectsManagement.deleteSavedObjectsConfirmModalDescription"
@ -71,7 +101,7 @@ export const DeleteConfirmModal: FC<DeleteConfirmModalProps> = ({
</p>
<EuiSpacer size="m" />
<EuiInMemoryTable
items={selectedObjects}
items={deletableObjects}
columns={[
{
field: 'type',

View file

@ -479,8 +479,8 @@ describe('SavedObjectsTable', () => {
const component = shallowRender();
const mockSelectedSavedObjects = [
{ id: '1', type: 'index-pattern' },
{ id: '3', type: 'dashboard' },
{ id: '1', type: 'index-pattern', meta: {} },
{ id: '3', type: 'dashboard', meta: {} },
] as SavedObjectWithMetadata[];
// Ensure all promises resolve
@ -498,8 +498,8 @@ describe('SavedObjectsTable', () => {
it('should delete selected objects', async () => {
const mockSelectedSavedObjects = [
{ id: '1', type: 'index-pattern' },
{ id: '3', type: 'dashboard' },
{ id: '1', type: 'index-pattern', meta: {} },
{ id: '3', type: 'dashboard', meta: {} },
] as SavedObjectWithMetadata[];
const mockSavedObjects = mockSelectedSavedObjects.map((obj) => ({
@ -529,7 +529,6 @@ describe('SavedObjectsTable', () => {
await component.instance().delete();
expect(defaultProps.indexPatterns.clearCache).toHaveBeenCalled();
expect(mockSavedObjectsClient.bulkGet).toHaveBeenCalledWith(mockSelectedSavedObjects);
expect(mockSavedObjectsClient.delete).toHaveBeenCalledWith(
mockSavedObjects[0].type,
mockSavedObjects[0].id,
@ -542,5 +541,44 @@ describe('SavedObjectsTable', () => {
);
expect(component.state('selectedSavedObjects').length).toBe(0);
});
it('should not delete hidden selected objects', async () => {
const mockSelectedSavedObjects = [
{ id: '1', type: 'index-pattern', meta: {} },
{ id: '3', type: 'hidden-type', meta: { hiddenType: true } },
] as SavedObjectWithMetadata[];
const mockSavedObjects = mockSelectedSavedObjects.map((obj) => ({
id: obj.id,
type: obj.type,
source: {},
}));
const mockSavedObjectsClient = {
...defaultProps.savedObjectsClient,
bulkGet: jest.fn().mockImplementation(() => ({
savedObjects: mockSavedObjects,
})),
delete: jest.fn(),
};
const component = shallowRender({ savedObjectsClient: mockSavedObjectsClient });
// Ensure all promises resolve
await new Promise((resolve) => process.nextTick(resolve));
// Ensure the state changes are reflected
component.update();
// Set some as selected
component.instance().onSelectionChanged(mockSelectedSavedObjects);
await component.instance().delete();
expect(defaultProps.indexPatterns.clearCache).toHaveBeenCalled();
expect(mockSavedObjectsClient.delete).toHaveBeenCalledTimes(1);
expect(mockSavedObjectsClient.delete).toHaveBeenCalledWith('index-pattern', '1', {
force: true,
});
});
});
});

View file

@ -455,10 +455,9 @@ export class SavedObjectsTable extends Component<SavedObjectsTableProps, SavedOb
await this.props.indexPatterns.clearCache();
}
const objects = await savedObjectsClient.bulkGet(selectedSavedObjects);
const deletes = objects.savedObjects.map((object) =>
savedObjectsClient.delete(object.type, object.id, { force: true })
);
const deletes = selectedSavedObjects
.filter((object) => !object.meta.hiddenType)
.map((object) => savedObjectsClient.delete(object.type, object.id, { force: true }));
await Promise.all(deletes);
// Unset this

View file

@ -15,6 +15,7 @@ export interface SavedObjectsManagementRecord {
icon: string;
title: string;
namespaceType: SavedObjectsNamespaceType;
hiddenType: boolean;
};
references: SavedObjectReference[];
namespaces?: string[];

View file

@ -317,6 +317,7 @@ describe('findRelationships', () => {
title: 'title',
icon: 'icon',
editUrl: 'editUrl',
hiddenType: false,
inAppUrl: {
path: 'path',
uiCapabilitiesPath: 'uiCapabilitiesPath',

View file

@ -49,6 +49,7 @@ describe('injectMetaAttributes', () => {
uiCapabilitiesPath: 'uiCapabilitiesPath',
},
namespaceType: 'single',
hiddenType: false,
},
});
});

View file

@ -25,6 +25,7 @@ export function injectMetaAttributes<T = unknown>(
result.meta.editUrl = savedObjectsManagement.getEditUrl(savedObject);
result.meta.inAppUrl = savedObjectsManagement.getInAppUrl(savedObject);
result.meta.namespaceType = savedObjectsManagement.getNamespaceType(savedObject);
result.meta.hiddenType = savedObjectsManagement.isHidden(savedObject);
return result;
}

View file

@ -19,6 +19,7 @@ const createManagementMock = () => {
getEditUrl: jest.fn(),
getInAppUrl: jest.fn(),
getNamespaceType: jest.fn(),
isHidden: jest.fn().mockReturnValue(false),
};
return mocked;
};

View file

@ -44,4 +44,8 @@ export class SavedObjectsManagement {
public getNamespaceType(savedObject: SavedObject) {
return this.registry.getType(savedObject.type)?.namespaceType;
}
public isHidden(savedObject: SavedObject) {
return this.registry.getType(savedObject.type)?.hidden ?? false;
}
}

View file

@ -240,6 +240,7 @@ export default function ({ getService }: FtrProviderContext) {
expect(resp.body.saved_objects[0].meta).to.eql({
icon: 'discoverApp',
title: 'OneRecord',
hiddenType: false,
editUrl:
'/management/kibana/objects/savedSearches/960372e0-3224-11e8-a572-ffca06da1357',
inAppUrl: {
@ -259,6 +260,7 @@ export default function ({ getService }: FtrProviderContext) {
expect(resp.body.saved_objects[0].meta).to.eql({
icon: 'dashboardApp',
title: 'Dashboard',
hiddenType: false,
editUrl:
'/management/kibana/objects/savedDashboards/b70c7ae0-3224-11e8-a572-ffca06da1357',
inAppUrl: {
@ -278,6 +280,7 @@ export default function ({ getService }: FtrProviderContext) {
expect(resp.body.saved_objects[0].meta).to.eql({
icon: 'visualizeApp',
title: 'VisualizationFromSavedSearch',
hiddenType: false,
editUrl:
'/management/kibana/objects/savedVisualizations/a42c0580-3224-11e8-a572-ffca06da1357',
inAppUrl: {
@ -289,6 +292,7 @@ export default function ({ getService }: FtrProviderContext) {
expect(resp.body.saved_objects[1].meta).to.eql({
icon: 'visualizeApp',
title: 'Visualization',
hiddenType: false,
editUrl:
'/management/kibana/objects/savedVisualizations/add810b0-3224-11e8-a572-ffca06da1357',
inAppUrl: {
@ -308,6 +312,7 @@ export default function ({ getService }: FtrProviderContext) {
expect(resp.body.saved_objects[0].meta).to.eql({
icon: 'indexPatternApp',
title: 'saved_objects*',
hiddenType: false,
editUrl:
'/management/kibana/indexPatterns/patterns/8963ca30-3224-11e8-a572-ffca06da1357',
inAppUrl: {

View file

@ -27,6 +27,7 @@ export default function ({ getService }: FtrProviderContext) {
uiCapabilitiesPath: schema.string(),
}),
namespaceType: schema.string(),
hiddenType: schema.boolean(),
}),
});
const invalidRelationSchema = schema.object({
@ -89,6 +90,7 @@ export default function ({ getService }: FtrProviderContext) {
uiCapabilitiesPath: 'management.kibana.indexPatterns',
},
namespaceType: 'single',
hiddenType: false,
},
},
{
@ -105,6 +107,7 @@ export default function ({ getService }: FtrProviderContext) {
uiCapabilitiesPath: 'visualize.show',
},
namespaceType: 'single',
hiddenType: false,
},
},
]);
@ -132,6 +135,7 @@ export default function ({ getService }: FtrProviderContext) {
uiCapabilitiesPath: 'management.kibana.indexPatterns',
},
namespaceType: 'single',
hiddenType: false,
},
relationship: 'child',
},
@ -148,6 +152,7 @@ export default function ({ getService }: FtrProviderContext) {
uiCapabilitiesPath: 'visualize.show',
},
namespaceType: 'single',
hiddenType: false,
},
relationship: 'parent',
},
@ -192,6 +197,7 @@ export default function ({ getService }: FtrProviderContext) {
uiCapabilitiesPath: 'visualize.show',
},
namespaceType: 'single',
hiddenType: false,
},
},
{
@ -208,6 +214,7 @@ export default function ({ getService }: FtrProviderContext) {
uiCapabilitiesPath: 'visualize.show',
},
namespaceType: 'single',
hiddenType: false,
},
},
]);
@ -232,6 +239,7 @@ export default function ({ getService }: FtrProviderContext) {
uiCapabilitiesPath: 'visualize.show',
},
namespaceType: 'single',
hiddenType: false,
},
relationship: 'child',
},
@ -248,6 +256,7 @@ export default function ({ getService }: FtrProviderContext) {
uiCapabilitiesPath: 'visualize.show',
},
namespaceType: 'single',
hiddenType: false,
},
relationship: 'child',
},
@ -292,6 +301,7 @@ export default function ({ getService }: FtrProviderContext) {
uiCapabilitiesPath: 'discover.show',
},
namespaceType: 'single',
hiddenType: false,
},
},
{
@ -308,6 +318,7 @@ export default function ({ getService }: FtrProviderContext) {
uiCapabilitiesPath: 'dashboard.show',
},
namespaceType: 'single',
hiddenType: false,
},
},
]);
@ -334,6 +345,7 @@ export default function ({ getService }: FtrProviderContext) {
uiCapabilitiesPath: 'discover.show',
},
namespaceType: 'single',
hiddenType: false,
},
relationship: 'child',
},
@ -378,6 +390,7 @@ export default function ({ getService }: FtrProviderContext) {
uiCapabilitiesPath: 'discover.show',
},
namespaceType: 'single',
hiddenType: false,
},
},
{
@ -394,6 +407,7 @@ export default function ({ getService }: FtrProviderContext) {
uiCapabilitiesPath: 'visualize.show',
},
namespaceType: 'single',
hiddenType: false,
},
},
]);
@ -420,6 +434,7 @@ export default function ({ getService }: FtrProviderContext) {
uiCapabilitiesPath: 'discover.show',
},
namespaceType: 'single',
hiddenType: false,
},
relationship: 'parent',
},
@ -466,6 +481,7 @@ export default function ({ getService }: FtrProviderContext) {
uiCapabilitiesPath: 'visualize.show',
},
namespaceType: 'single',
hiddenType: false,
title: 'Visualization',
},
relationship: 'child',

View file

@ -0,0 +1,88 @@
{
"type": "doc",
"value": {
"index": ".kibana",
"type": "doc",
"id": "test-actions-export-hidden:obj_1",
"source": {
"test-actions-export-hidden": {
"title": "hidden object 1"
},
"type": "test-actions-export-hidden",
"migrationVersion": {},
"updated_at": "2018-12-21T00:43:07.096Z"
}
}
}
{
"type": "doc",
"value": {
"index": ".kibana",
"type": "doc",
"id": "test-actions-export-hidden:obj_2",
"source": {
"test-actions-export-hidden": {
"title": "hidden object 2"
},
"type": "test-actions-export-hidden",
"migrationVersion": {},
"updated_at": "2018-12-21T00:43:07.096Z"
}
}
}
{
"type": "doc",
"value": {
"index": ".kibana",
"type": "doc",
"id": "visualization:75c3e060-1e7c-11e9-8488-65449e65d0ed",
"source": {
"visualization": {
"title": "A Pie",
"visState": "{\"title\":\"A Pie\",\"type\":\"pie\",\"params\":{\"type\":\"pie\",\"addTooltip\":true,\"addLegend\":true,\"legendPosition\":\"right\",\"isDonut\":true,\"labels\":{\"show\":false,\"values\":true,\"last_level\":true,\"truncate\":100},\"dimensions\":{\"metric\":{\"accessor\":0,\"format\":{\"id\":\"number\"},\"params\":{},\"aggType\":\"count\"}}},\"aggs\":[{\"id\":\"1\",\"enabled\":true,\"type\":\"count\",\"schema\":\"metric\",\"params\":{}},{\"id\":\"2\",\"enabled\":true,\"type\":\"terms\",\"schema\":\"segment\",\"params\":{\"field\":\"geo.src\",\"size\":5,\"order\":\"desc\",\"orderBy\":\"1\",\"otherBucket\":false,\"otherBucketLabel\":\"Other\",\"missingBucket\":false,\"missingBucketLabel\":\"Missing\"}}]}",
"uiStateJSON": "{}",
"description": "",
"version": 1,
"kibanaSavedObjectMeta": {
"searchSourceJSON": "{\"index\":\"logstash-*\",\"query\":{\"query\":\"\",\"language\":\"lucene\"},\"filter\":[]}"
}
},
"type": "visualization",
"updated_at": "2019-01-22T19:32:31.206Z"
},
"references" : [
{
"name" : "kibanaSavedObjectMeta.searchSourceJSON.index",
"type" : "index-pattern",
"id" : "logstash-*"
}
]
}
}
{
"type": "doc",
"value": {
"index": ".kibana",
"type": "doc",
"id": "dashboard:i-exist",
"source": {
"dashboard": {
"title": "A Dashboard",
"hits": 0,
"description": "",
"panelsJSON": "[{\"gridData\":{\"w\":24,\"h\":15,\"x\":0,\"y\":0,\"i\":\"1\"},\"version\":\"7.0.0\",\"panelIndex\":\"1\",\"type\":\"visualization\",\"id\":\"75c3e060-1e7c-11e9-8488-65449e65d0ed\",\"embeddableConfig\":{}}]",
"optionsJSON": "{\"darkTheme\":false,\"useMargins\":true,\"hidePanelTitles\":false}",
"version": 1,
"timeRestore": false,
"kibanaSavedObjectMeta": {
"searchSourceJSON": "{\"query\":{\"query\":\"\",\"language\":\"lucene\"},\"filter\":[]}"
}
},
"type": "dashboard",
"updated_at": "2019-01-22T19:32:47.232Z"
}
}
}

View file

@ -0,0 +1,504 @@
{
"type": "index",
"value": {
"index": ".kibana",
"settings": {
"index": {
"number_of_shards": "1",
"auto_expand_replicas": "0-1",
"number_of_replicas": "0"
}
},
"mappings": {
"dynamic": true,
"properties": {
"test-actions-export-hidden": {
"properties": {
"title": { "type": "text" }
}
},
"test-export-transform": {
"properties": {
"title": { "type": "text" },
"enabled": { "type": "boolean" }
}
},
"test-export-add": {
"properties": {
"title": { "type": "text" }
}
},
"test-export-add-dep": {
"properties": {
"title": { "type": "text" }
}
},
"test-export-transform-error": {
"properties": {
"title": { "type": "text" }
}
},
"test-export-invalid-transform": {
"properties": {
"title": { "type": "text" }
}
},
"apm-telemetry": {
"properties": {
"has_any_services": {
"type": "boolean"
},
"services_per_agent": {
"properties": {
"go": {
"type": "long",
"null_value": 0
},
"java": {
"type": "long",
"null_value": 0
},
"js-base": {
"type": "long",
"null_value": 0
},
"nodejs": {
"type": "long",
"null_value": 0
},
"python": {
"type": "long",
"null_value": 0
},
"ruby": {
"type": "long",
"null_value": 0
}
}
}
}
},
"canvas-workpad": {
"dynamic": "false",
"properties": {
"@created": {
"type": "date"
},
"@timestamp": {
"type": "date"
},
"id": {
"type": "text",
"index": false
},
"name": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword"
}
}
}
}
},
"config": {
"dynamic": "true",
"properties": {
"accessibility:disableAnimations": {
"type": "boolean"
},
"buildNum": {
"type": "keyword"
},
"dateFormat:tz": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
},
"defaultIndex": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
},
"telemetry:optIn": {
"type": "boolean"
}
}
},
"dashboard": {
"properties": {
"description": {
"type": "text"
},
"hits": {
"type": "integer"
},
"kibanaSavedObjectMeta": {
"properties": {
"searchSourceJSON": {
"type": "text"
}
}
},
"optionsJSON": {
"type": "text"
},
"panelsJSON": {
"type": "text"
},
"refreshInterval": {
"properties": {
"display": {
"type": "keyword"
},
"pause": {
"type": "boolean"
},
"section": {
"type": "integer"
},
"value": {
"type": "integer"
}
}
},
"timeFrom": {
"type": "keyword"
},
"timeRestore": {
"type": "boolean"
},
"timeTo": {
"type": "keyword"
},
"title": {
"type": "text"
},
"uiStateJSON": {
"type": "text"
},
"version": {
"type": "integer"
}
}
},
"map": {
"properties": {
"bounds": {
"type": "geo_shape",
"tree": "quadtree"
},
"description": {
"type": "text"
},
"layerListJSON": {
"type": "text"
},
"mapStateJSON": {
"type": "text"
},
"title": {
"type": "text"
},
"uiStateJSON": {
"type": "text"
},
"version": {
"type": "integer"
}
}
},
"graph-workspace": {
"properties": {
"description": {
"type": "text"
},
"kibanaSavedObjectMeta": {
"properties": {
"searchSourceJSON": {
"type": "text"
}
}
},
"numLinks": {
"type": "integer"
},
"numVertices": {
"type": "integer"
},
"title": {
"type": "text"
},
"version": {
"type": "integer"
},
"wsState": {
"type": "text"
}
}
},
"index-pattern": {
"properties": {
"fieldFormatMap": {
"type": "text"
},
"fields": {
"type": "text"
},
"intervalName": {
"type": "keyword"
},
"notExpandable": {
"type": "boolean"
},
"sourceFilters": {
"type": "text"
},
"timeFieldName": {
"type": "keyword"
},
"title": {
"type": "text"
},
"type": {
"type": "keyword"
},
"typeMeta": {
"type": "keyword"
}
}
},
"kql-telemetry": {
"properties": {
"optInCount": {
"type": "long"
},
"optOutCount": {
"type": "long"
}
}
},
"migrationVersion": {
"dynamic": "true",
"properties": {
"index-pattern": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
},
"space": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
}
}
},
"namespace": {
"type": "keyword"
},
"search": {
"properties": {
"columns": {
"type": "keyword"
},
"description": {
"type": "text"
},
"hits": {
"type": "integer"
},
"kibanaSavedObjectMeta": {
"properties": {
"searchSourceJSON": {
"type": "text"
}
}
},
"sort": {
"type": "keyword"
},
"title": {
"type": "text"
},
"version": {
"type": "integer"
}
}
},
"server": {
"properties": {
"uuid": {
"type": "keyword"
}
}
},
"space": {
"properties": {
"_reserved": {
"type": "boolean"
},
"color": {
"type": "keyword"
},
"description": {
"type": "text"
},
"disabledFeatures": {
"type": "keyword"
},
"initials": {
"type": "keyword"
},
"name": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 2048
}
}
}
}
},
"spaceId": {
"type": "keyword"
},
"telemetry": {
"properties": {
"enabled": {
"type": "boolean"
}
}
},
"timelion-sheet": {
"properties": {
"description": {
"type": "text"
},
"hits": {
"type": "integer"
},
"kibanaSavedObjectMeta": {
"properties": {
"searchSourceJSON": {
"type": "text"
}
}
},
"timelion_chart_height": {
"type": "integer"
},
"timelion_columns": {
"type": "integer"
},
"timelion_interval": {
"type": "keyword"
},
"timelion_other_interval": {
"type": "keyword"
},
"timelion_rows": {
"type": "integer"
},
"timelion_sheet": {
"type": "text"
},
"title": {
"type": "text"
},
"version": {
"type": "integer"
}
}
},
"type": {
"type": "keyword"
},
"updated_at": {
"type": "date"
},
"url": {
"properties": {
"accessCount": {
"type": "long"
},
"accessDate": {
"type": "date"
},
"createDate": {
"type": "date"
},
"url": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 2048
}
}
}
}
},
"visualization": {
"properties": {
"description": {
"type": "text"
},
"kibanaSavedObjectMeta": {
"properties": {
"searchSourceJSON": {
"type": "text"
}
}
},
"savedSearchId": {
"type": "keyword"
},
"title": {
"type": "text"
},
"uiStateJSON": {
"type": "text"
},
"version": {
"type": "integer"
},
"visState": {
"type": "text"
}
}
},
"references": {
"properties": {
"id": {
"type": "keyword"
},
"name": {
"type": "keyword"
},
"type": {
"type": "keyword"
}
},
"type": "nested"
}
}
}
}
}

View file

@ -294,10 +294,12 @@ export function SavedObjectsPageProvider({ getService, getPageObjects }: FtrProv
return await testSubjects.isEnabled('savedObjectsManagementDelete');
}
async clickDelete() {
async clickDelete({ confirmDelete = true }: { confirmDelete?: boolean } = {}) {
await testSubjects.click('savedObjectsManagementDelete');
await testSubjects.click('confirmModalConfirmButton');
await this.waitTableIsLoaded();
if (confirmDelete) {
await testSubjects.click('confirmModalConfirmButton');
await this.waitTableIsLoaded();
}
}
async getImportWarnings() {

View file

@ -90,8 +90,6 @@ export class SavedObjectExportTransformsPlugin implements Plugin {
},
});
/////////////
/////////////
// example of a SO type that will throw an object-transform-error
savedObjects.registerType({
name: 'test-export-transform-error',
@ -134,8 +132,29 @@ export class SavedObjectExportTransformsPlugin implements Plugin {
},
},
});
// example of a SO type that is exportable while being hidden
savedObjects.registerType({
name: 'test-actions-export-hidden',
hidden: true,
namespaceType: 'single',
mappings: {
properties: {
title: { type: 'text' },
enabled: {
type: 'boolean',
},
},
},
management: {
defaultSearchField: 'title',
importableAndExportable: true,
getTitle: (obj) => obj.attributes.title,
},
});
}
public start() {}
public stop() {}
}

View file

@ -15,6 +15,5 @@ export default function ({ loadTestFile }: PluginFunctionalProviderContext) {
loadTestFile(require.resolve('./resolve_import_errors'));
loadTestFile(require.resolve('./find'));
loadTestFile(require.resolve('./delete'));
loadTestFile(require.resolve('./interface/saved_objects_management'));
});
}

View file

@ -1,55 +0,0 @@
/*
* 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 path from 'path';
import expect from '@kbn/expect';
import { PluginFunctionalProviderContext } from '../../../services';
export default function ({ getPageObjects, getService }: PluginFunctionalProviderContext) {
const PageObjects = getPageObjects(['common', 'settings', 'header', 'savedObjects']);
const esArchiver = getService('esArchiver');
const fixturePaths = {
hiddenImportable: path.join(__dirname, 'exports', '_import_hidden_importable.ndjson'),
hiddenNonImportable: path.join(__dirname, 'exports', '_import_hidden_non_importable.ndjson'),
};
describe('Saved objects management Interface', () => {
before(() => esArchiver.emptyKibanaIndex());
beforeEach(async () => {
await PageObjects.settings.navigateTo();
await PageObjects.settings.clickKibanaSavedObjects();
});
describe('importable/exportable hidden type', () => {
it('imports objects successfully', async () => {
await PageObjects.savedObjects.importFile(fixturePaths.hiddenImportable);
await PageObjects.savedObjects.checkImportSucceeded();
});
it('shows test-hidden-importable-exportable in table', async () => {
await PageObjects.savedObjects.searchForObject('type:(test-hidden-importable-exportable)');
const results = await PageObjects.savedObjects.getTableSummary();
expect(results.length).to.be(1);
const { title } = results[0];
expect(title).to.be(
'test-hidden-importable-exportable [id=ff3733a0-9fty-11e7-ahb3-3dcb94193fab]'
);
});
});
describe('non-importable/exportable hidden type', () => {
it('fails to import object', async () => {
await PageObjects.savedObjects.importFile(fixturePaths.hiddenNonImportable);
await PageObjects.savedObjects.checkImportSucceeded();
const errorsCount = await PageObjects.savedObjects.getImportErrorsCount();
expect(errorsCount).to.be(1);
});
});
});
}

View file

@ -0,0 +1,127 @@
/*
* 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 path from 'path';
import expect from '@kbn/expect';
import { PluginFunctionalProviderContext } from '../../services';
const fixturePaths = {
hiddenImportable: path.join(__dirname, 'exports', '_import_hidden_importable.ndjson'),
hiddenNonImportable: path.join(__dirname, 'exports', '_import_hidden_non_importable.ndjson'),
};
export default function ({ getService, getPageObjects }: PluginFunctionalProviderContext) {
const PageObjects = getPageObjects(['common', 'settings', 'header', 'savedObjects']);
const supertest = getService('supertest');
const esArchiver = getService('esArchiver');
const testSubjects = getService('testSubjects');
describe('saved objects management with hidden types', () => {
before(async () => {
await esArchiver.load(
'../functional/fixtures/es_archiver/saved_objects_management/hidden_types'
);
});
after(async () => {
await esArchiver.unload(
'../functional/fixtures/es_archiver/saved_objects_management/hidden_types'
);
});
beforeEach(async () => {
await PageObjects.settings.navigateTo();
await PageObjects.settings.clickKibanaSavedObjects();
});
describe('API calls', () => {
it('should flag the object as hidden in its meta', async () => {
await supertest
.get('/api/kibana/management/saved_objects/_find?type=test-actions-export-hidden')
.set('kbn-xsrf', 'true')
.expect(200)
.then((resp) => {
expect(
resp.body.saved_objects.map((obj: any) => ({
id: obj.id,
type: obj.type,
hidden: obj.meta.hiddenType,
}))
).to.eql([
{
id: 'obj_1',
type: 'test-actions-export-hidden',
hidden: true,
},
{
id: 'obj_2',
type: 'test-actions-export-hidden',
hidden: true,
},
]);
});
});
});
describe('Delete modal', () => {
it('should display a warning then trying to delete hidden saved objects', async () => {
await PageObjects.savedObjects.clickCheckboxByTitle('A Pie');
await PageObjects.savedObjects.clickCheckboxByTitle('A Dashboard');
await PageObjects.savedObjects.clickCheckboxByTitle('hidden object 1');
await PageObjects.savedObjects.clickDelete({ confirmDelete: false });
expect(await testSubjects.exists('cannotDeleteObjectsConfirmWarning')).to.eql(true);
});
it('should not delete the hidden objects when performing the operation', async () => {
await PageObjects.savedObjects.clickCheckboxByTitle('A Pie');
await PageObjects.savedObjects.clickCheckboxByTitle('hidden object 1');
await PageObjects.savedObjects.clickDelete({ confirmDelete: true });
const objectNames = (await PageObjects.savedObjects.getTableSummary()).map(
(obj) => obj.title
);
expect(objectNames.includes('hidden object 1')).to.eql(true);
expect(objectNames.includes('A Pie')).to.eql(false);
});
});
describe('importing hidden types', () => {
describe('importable/exportable hidden type', () => {
it('imports objects successfully', async () => {
await PageObjects.savedObjects.importFile(fixturePaths.hiddenImportable);
await PageObjects.savedObjects.checkImportSucceeded();
});
it('shows test-hidden-importable-exportable in table', async () => {
await PageObjects.savedObjects.searchForObject(
'type:(test-hidden-importable-exportable)'
);
const results = await PageObjects.savedObjects.getTableSummary();
expect(results.length).to.be(1);
const { title } = results[0];
expect(title).to.be(
'test-hidden-importable-exportable [id=ff3733a0-9fty-11e7-ahb3-3dcb94193fab]'
);
});
});
describe('non-importable/exportable hidden type', () => {
it('fails to import object', async () => {
await PageObjects.savedObjects.importFile(fixturePaths.hiddenNonImportable);
await PageObjects.savedObjects.checkImportSucceeded();
const errorsCount = await PageObjects.savedObjects.getImportErrorsCount();
expect(errorsCount).to.be(1);
});
});
});
});
}

View file

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

View file

@ -55,7 +55,7 @@ export class CopyToSpaceSavedObjectsManagementAction extends SavedObjectsManagem
icon: 'copy',
type: 'icon',
available: (object: SavedObjectsManagementRecord) => {
return object.meta.namespaceType !== 'agnostic';
return object.meta.namespaceType !== 'agnostic' && !object.meta.hiddenType;
},
onClick: (object: SavedObjectsManagementRecord) => {
this.start(object);

View file

@ -33,7 +33,12 @@ const OBJECTS = {
MY_DASHBOARD: {
type: 'dashboard',
id: 'foo',
meta: { title: 'my-dashboard-title', icon: 'dashboardApp', namespaceType: 'single' },
meta: {
title: 'my-dashboard-title',
icon: 'dashboardApp',
namespaceType: 'single',
hiddenType: false,
},
references: [
{ type: 'visualization', id: 'foo', name: 'Visualization foo' },
{ type: 'visualization', id: 'bar', name: 'Visualization bar' },
@ -42,25 +47,45 @@ const OBJECTS = {
VISUALIZATION_FOO: {
type: 'visualization',
id: 'bar',
meta: { title: 'visualization-foo-title', icon: 'visualizeApp', namespaceType: 'single' },
meta: {
title: 'visualization-foo-title',
icon: 'visualizeApp',
namespaceType: 'single',
hiddenType: false,
},
references: [{ type: 'index-pattern', id: 'foo', name: 'Index pattern foo' }],
} as SavedObjectsManagementRecord,
VISUALIZATION_BAR: {
type: 'visualization',
id: 'baz',
meta: { title: 'visualization-bar-title', icon: 'visualizeApp', namespaceType: 'single' },
meta: {
title: 'visualization-bar-title',
icon: 'visualizeApp',
namespaceType: 'single',
hiddenType: false,
},
references: [{ type: 'index-pattern', id: 'bar', name: 'Index pattern bar' }],
} as SavedObjectsManagementRecord,
INDEX_PATTERN_FOO: {
type: 'index-pattern',
id: 'foo',
meta: { title: 'index-pattern-foo-title', icon: 'indexPatternApp', namespaceType: 'single' },
meta: {
title: 'index-pattern-foo-title',
icon: 'indexPatternApp',
namespaceType: 'single',
hiddenType: false,
},
references: [],
} as SavedObjectsManagementRecord,
INDEX_PATTERN_BAR: {
type: 'index-pattern',
id: 'bar',
meta: { title: 'index-pattern-bar-title', icon: 'indexPatternApp', namespaceType: 'single' },
meta: {
title: 'index-pattern-bar-title',
icon: 'indexPatternApp',
namespaceType: 'single',
hiddenType: false,
},
references: [],
} as SavedObjectsManagementRecord,
};
@ -70,6 +95,7 @@ interface ObjectProperties {
id: string;
meta: { title?: string; icon?: string };
}
const createSuccessResult = ({ type, id, meta }: ObjectProperties) => {
return { type, id, meta };
};
@ -190,6 +216,7 @@ describe('summarizeCopyResult', () => {
"obj": Object {
"id": "bar",
"meta": Object {
"hiddenType": false,
"icon": "visualizeApp",
"namespaceType": "single",
"title": "visualization-foo-title",

View file

@ -40,7 +40,8 @@ export class ShareToSpaceSavedObjectsManagementAction extends SavedObjectsManage
const hasCapability =
!this.actionContext ||
!!this.actionContext.capabilities.savedObjectsManagement.shareIntoSpace;
return object.meta.namespaceType === 'multiple' && hasCapability;
const { namespaceType, hiddenType } = object.meta;
return namespaceType === 'multiple' && !hiddenType && hasCapability;
},
onClick: (object: SavedObjectsManagementRecord) => {
this.isDataChanged = false;