mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
add SavedObjectType.management.displayName
(#113091)
* add `SavedObjectType.management.displayName` * fix unit tests * add FTR test * update generated doc * also update labels * fix unit tests
This commit is contained in:
parent
7010b67f2b
commit
daaa6f8c19
29 changed files with 303 additions and 68 deletions
|
@ -0,0 +1,13 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [SavedObjectsTypeManagementDefinition](./kibana-plugin-core-server.savedobjectstypemanagementdefinition.md) > [displayName](./kibana-plugin-core-server.savedobjectstypemanagementdefinition.displayname.md)
|
||||
|
||||
## SavedObjectsTypeManagementDefinition.displayName property
|
||||
|
||||
When specified, will be used instead of the type's name in SO management section's labels.
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
displayName?: string;
|
||||
```
|
|
@ -17,6 +17,7 @@ export interface SavedObjectsTypeManagementDefinition<Attributes = any>
|
|||
| Property | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| [defaultSearchField](./kibana-plugin-core-server.savedobjectstypemanagementdefinition.defaultsearchfield.md) | <code>string</code> | The default search field to use for this type. Defaults to <code>id</code>. |
|
||||
| [displayName](./kibana-plugin-core-server.savedobjectstypemanagementdefinition.displayname.md) | <code>string</code> | When specified, will be used instead of the type's name in SO management section's labels. |
|
||||
| [getEditUrl](./kibana-plugin-core-server.savedobjectstypemanagementdefinition.getediturl.md) | <code>(savedObject: SavedObject<Attributes>) => string</code> | Function returning the url to use to redirect to the editing page of this object. If not defined, editing will not be allowed. |
|
||||
| [getInAppUrl](./kibana-plugin-core-server.savedobjectstypemanagementdefinition.getinappurl.md) | <code>(savedObject: SavedObject<Attributes>) => {</code><br/><code> path: string;</code><br/><code> uiCapabilitiesPath: string;</code><br/><code> }</code> | Function returning the url to use to redirect to this object from the management section. If not defined, redirecting to the object will not be allowed. |
|
||||
| [getTitle](./kibana-plugin-core-server.savedobjectstypemanagementdefinition.gettitle.md) | <code>(savedObject: SavedObject<Attributes>) => string</code> | Function returning the title to display in the management table. If not defined, will use the object's type and id to generate a label. |
|
||||
|
|
|
@ -356,6 +356,10 @@ export interface SavedObjectsTypeManagementDefinition<Attributes = any> {
|
|||
* Is the type importable or exportable. Defaults to `false`.
|
||||
*/
|
||||
importableAndExportable?: boolean;
|
||||
/**
|
||||
* When specified, will be used instead of the type's name in SO management section's labels.
|
||||
*/
|
||||
displayName?: string;
|
||||
/**
|
||||
* 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
|
||||
|
|
|
@ -2739,6 +2739,7 @@ export interface SavedObjectsType<Attributes = any> {
|
|||
// @public
|
||||
export interface SavedObjectsTypeManagementDefinition<Attributes = any> {
|
||||
defaultSearchField?: string;
|
||||
displayName?: string;
|
||||
getEditUrl?: (savedObject: SavedObject<Attributes>) => string;
|
||||
getInAppUrl?: (savedObject: SavedObject<Attributes>) => {
|
||||
path: string;
|
||||
|
|
|
@ -13,4 +13,5 @@ export type {
|
|||
SavedObjectRelationKind,
|
||||
SavedObjectInvalidRelation,
|
||||
SavedObjectGetRelationshipsResponse,
|
||||
SavedObjectManagementTypeInfo,
|
||||
} from './types';
|
||||
|
|
|
@ -6,8 +6,8 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { SavedObject } from 'src/core/types';
|
||||
import { SavedObjectsNamespaceType } from 'src/core/public';
|
||||
import type { SavedObject } from 'src/core/types';
|
||||
import type { SavedObjectsNamespaceType } from 'src/core/public';
|
||||
|
||||
/**
|
||||
* The metadata injected into a {@link SavedObject | saved object} when returning
|
||||
|
@ -52,3 +52,10 @@ export interface SavedObjectGetRelationshipsResponse {
|
|||
relations: SavedObjectRelation[];
|
||||
invalidRelations: SavedObjectInvalidRelation[];
|
||||
}
|
||||
|
||||
export interface SavedObjectManagementTypeInfo {
|
||||
name: string;
|
||||
namespaceType: SavedObjectsNamespaceType;
|
||||
hidden: boolean;
|
||||
displayName: string;
|
||||
}
|
||||
|
|
|
@ -6,13 +6,14 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { HttpStart } from 'src/core/public';
|
||||
import type { HttpStart } from 'src/core/public';
|
||||
import type { SavedObjectManagementTypeInfo } from '../../common/types';
|
||||
|
||||
interface GetAllowedTypesResponse {
|
||||
types: string[];
|
||||
types: SavedObjectManagementTypeInfo[];
|
||||
}
|
||||
|
||||
export async function getAllowedTypes(http: HttpStart) {
|
||||
export async function getAllowedTypes(http: HttpStart): Promise<SavedObjectManagementTypeInfo[]> {
|
||||
const response = await http.get<GetAllowedTypesResponse>(
|
||||
'/api/kibana/management/saved_objects/_allowed_types'
|
||||
);
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* 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 type { SavedObjectManagementTypeInfo } from '../../common/types';
|
||||
import { getSavedObjectLabel } from './get_saved_object_label';
|
||||
|
||||
const toTypeInfo = (name: string, displayName?: string): SavedObjectManagementTypeInfo => ({
|
||||
name,
|
||||
displayName: displayName ?? name,
|
||||
hidden: false,
|
||||
namespaceType: 'single',
|
||||
});
|
||||
|
||||
describe('getSavedObjectLabel', () => {
|
||||
it('returns the type name if no types are provided', () => {
|
||||
expect(getSavedObjectLabel('foo', [])).toEqual('foo');
|
||||
});
|
||||
|
||||
it('returns the type name if type does not specify a display name', () => {
|
||||
expect(getSavedObjectLabel('foo', [toTypeInfo('foo')])).toEqual('foo');
|
||||
});
|
||||
|
||||
it('returns the type display name if type does specify a display name', () => {
|
||||
expect(getSavedObjectLabel('foo', [toTypeInfo('foo', 'fooDisplay')])).toEqual('fooDisplay');
|
||||
});
|
||||
});
|
|
@ -6,13 +6,12 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
export function getSavedObjectLabel(type: string) {
|
||||
switch (type) {
|
||||
case 'index-pattern':
|
||||
case 'index-patterns':
|
||||
case 'indexPatterns':
|
||||
return 'index patterns';
|
||||
default:
|
||||
return type;
|
||||
}
|
||||
import type { SavedObjectManagementTypeInfo } from '../../common/types';
|
||||
|
||||
/**
|
||||
* Returns the label to be used for given saved object type.
|
||||
*/
|
||||
export function getSavedObjectLabel(type: string, types: SavedObjectManagementTypeInfo[]) {
|
||||
const typeInfo = types.find((t) => t.name === type);
|
||||
return typeInfo?.displayName ?? type;
|
||||
}
|
||||
|
|
|
@ -14,6 +14,7 @@ import { i18n } from '@kbn/i18n';
|
|||
import { EuiLoadingSpinner } from '@elastic/eui';
|
||||
import { CoreSetup } from 'src/core/public';
|
||||
import { ManagementAppMountParams } from '../../../management/public';
|
||||
import type { SavedObjectManagementTypeInfo } from '../../common/types';
|
||||
import { StartDependencies, SavedObjectsManagementPluginStart } from '../plugin';
|
||||
import { getAllowedTypes } from './../lib';
|
||||
|
||||
|
@ -22,7 +23,7 @@ interface MountParams {
|
|||
mountParams: ManagementAppMountParams;
|
||||
}
|
||||
|
||||
let allowedObjectTypes: string[] | undefined;
|
||||
let allowedObjectTypes: SavedObjectManagementTypeInfo[] | undefined;
|
||||
|
||||
const title = i18n.translate('savedObjectsManagement.objects.savedObjectsTitle', {
|
||||
defaultMessage: 'Saved Objects',
|
||||
|
@ -33,15 +34,15 @@ const SavedObjectsTablePage = lazy(() => import('./saved_objects_table_page'));
|
|||
export const mountManagementSection = async ({ core, mountParams }: MountParams) => {
|
||||
const [coreStart, { data, savedObjectsTaggingOss, spaces: spacesApi }, pluginStart] =
|
||||
await core.getStartServices();
|
||||
const { capabilities } = coreStart.application;
|
||||
const { element, history, setBreadcrumbs } = mountParams;
|
||||
if (allowedObjectTypes === undefined) {
|
||||
|
||||
if (!allowedObjectTypes) {
|
||||
allowedObjectTypes = await getAllowedTypes(coreStart.http);
|
||||
}
|
||||
|
||||
coreStart.chrome.docTitle.change(title);
|
||||
|
||||
const capabilities = coreStart.application.capabilities;
|
||||
|
||||
const RedirectToHomeIfUnauthorized: React.FunctionComponent = ({ children }) => {
|
||||
const allowed = capabilities?.management?.kibana?.objects ?? false;
|
||||
|
||||
|
|
|
@ -2,6 +2,34 @@
|
|||
|
||||
exports[`SavedObjectsTable delete should show a confirm modal 1`] = `
|
||||
<DeleteConfirmModal
|
||||
allowedTypes={
|
||||
Array [
|
||||
Object {
|
||||
"displayName": "index-pattern",
|
||||
"hidden": false,
|
||||
"name": "index-pattern",
|
||||
"namespaceType": "single",
|
||||
},
|
||||
Object {
|
||||
"displayName": "visualization",
|
||||
"hidden": false,
|
||||
"name": "visualization",
|
||||
"namespaceType": "single",
|
||||
},
|
||||
Object {
|
||||
"displayName": "dashboard",
|
||||
"hidden": false,
|
||||
"name": "dashboard",
|
||||
"namespaceType": "single",
|
||||
},
|
||||
Object {
|
||||
"displayName": "search",
|
||||
"hidden": false,
|
||||
"name": "search",
|
||||
"namespaceType": "single",
|
||||
},
|
||||
]
|
||||
}
|
||||
isDeleting={false}
|
||||
onCancel={[Function]}
|
||||
onConfirm={[Function]}
|
||||
|
@ -118,6 +146,34 @@ exports[`SavedObjectsTable should render normally 1`] = `
|
|||
"has": [MockFunction],
|
||||
}
|
||||
}
|
||||
allowedTypes={
|
||||
Array [
|
||||
Object {
|
||||
"displayName": "index-pattern",
|
||||
"hidden": false,
|
||||
"name": "index-pattern",
|
||||
"namespaceType": "single",
|
||||
},
|
||||
Object {
|
||||
"displayName": "visualization",
|
||||
"hidden": false,
|
||||
"name": "visualization",
|
||||
"namespaceType": "single",
|
||||
},
|
||||
Object {
|
||||
"displayName": "dashboard",
|
||||
"hidden": false,
|
||||
"name": "dashboard",
|
||||
"namespaceType": "single",
|
||||
},
|
||||
Object {
|
||||
"displayName": "search",
|
||||
"hidden": false,
|
||||
"name": "search",
|
||||
"namespaceType": "single",
|
||||
},
|
||||
]
|
||||
}
|
||||
basePath={
|
||||
BasePath {
|
||||
"basePath": "",
|
||||
|
|
|
@ -395,6 +395,7 @@ exports[`Flyout should render import step 1`] = `
|
|||
|
||||
exports[`Flyout summary should display summary when import is complete 1`] = `
|
||||
<ImportSummary
|
||||
allowedTypes={Array []}
|
||||
basePath={
|
||||
BasePath {
|
||||
"basePath": "",
|
||||
|
|
|
@ -213,13 +213,13 @@ exports[`Relationships should render index patterns normally 1`] = `
|
|||
>
|
||||
<h2>
|
||||
<EuiToolTip
|
||||
content="index patterns"
|
||||
content="index-pattern"
|
||||
delay="regular"
|
||||
display="inlineBlock"
|
||||
position="top"
|
||||
>
|
||||
<EuiIcon
|
||||
aria-label="index patterns"
|
||||
aria-label="index-pattern"
|
||||
size="m"
|
||||
type="indexPatternApp"
|
||||
/>
|
||||
|
@ -374,13 +374,13 @@ exports[`Relationships should render invalid relations 1`] = `
|
|||
>
|
||||
<h2>
|
||||
<EuiToolTip
|
||||
content="index patterns"
|
||||
content="index-pattern"
|
||||
delay="regular"
|
||||
display="inlineBlock"
|
||||
position="top"
|
||||
>
|
||||
<EuiIcon
|
||||
aria-label="index patterns"
|
||||
aria-label="index-pattern"
|
||||
size="m"
|
||||
type="indexPatternApp"
|
||||
/>
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
import React from 'react';
|
||||
import { findTestSubject } from '@elastic/eui/lib/test';
|
||||
import { mountWithIntl } from '@kbn/test/jest';
|
||||
import { SavedObjectWithMetadata } from '../../../../common';
|
||||
import type { SavedObjectWithMetadata, SavedObjectManagementTypeInfo } from '../../../../common';
|
||||
import { DeleteConfirmModal } from './delete_confirm_modal';
|
||||
|
||||
interface CreateObjectOptions {
|
||||
|
@ -32,6 +32,7 @@ const createObject = ({
|
|||
});
|
||||
|
||||
describe('DeleteConfirmModal', () => {
|
||||
const allowedTypes: SavedObjectManagementTypeInfo[] = [];
|
||||
let onConfirm: jest.Mock;
|
||||
let onCancel: jest.Mock;
|
||||
|
||||
|
@ -47,6 +48,7 @@ describe('DeleteConfirmModal', () => {
|
|||
onConfirm={onConfirm}
|
||||
onCancel={onCancel}
|
||||
selectedObjects={[]}
|
||||
allowedTypes={allowedTypes}
|
||||
/>
|
||||
);
|
||||
expect(wrapper.find('EuiLoadingElastic')).toHaveLength(1);
|
||||
|
@ -61,6 +63,7 @@ describe('DeleteConfirmModal', () => {
|
|||
onConfirm={onConfirm}
|
||||
onCancel={onCancel}
|
||||
selectedObjects={objs}
|
||||
allowedTypes={allowedTypes}
|
||||
/>
|
||||
);
|
||||
expect(wrapper.find('.euiTableRow')).toHaveLength(3);
|
||||
|
@ -73,6 +76,7 @@ describe('DeleteConfirmModal', () => {
|
|||
onConfirm={onConfirm}
|
||||
onCancel={onCancel}
|
||||
selectedObjects={[]}
|
||||
allowedTypes={allowedTypes}
|
||||
/>
|
||||
);
|
||||
wrapper.find('EuiButtonEmpty').simulate('click');
|
||||
|
@ -88,6 +92,7 @@ describe('DeleteConfirmModal', () => {
|
|||
onConfirm={onConfirm}
|
||||
onCancel={onCancel}
|
||||
selectedObjects={[createObject()]}
|
||||
allowedTypes={allowedTypes}
|
||||
/>
|
||||
);
|
||||
wrapper.find('EuiButton').simulate('click');
|
||||
|
@ -109,6 +114,7 @@ describe('DeleteConfirmModal', () => {
|
|||
onConfirm={onConfirm}
|
||||
onCancel={onCancel}
|
||||
selectedObjects={objs}
|
||||
allowedTypes={allowedTypes}
|
||||
/>
|
||||
);
|
||||
expect(wrapper.find('.euiTableRow')).toHaveLength(1);
|
||||
|
@ -126,6 +132,7 @@ describe('DeleteConfirmModal', () => {
|
|||
onConfirm={onConfirm}
|
||||
onCancel={onCancel}
|
||||
selectedObjects={objs}
|
||||
allowedTypes={allowedTypes}
|
||||
/>
|
||||
);
|
||||
|
||||
|
@ -145,6 +152,7 @@ describe('DeleteConfirmModal', () => {
|
|||
onConfirm={onConfirm}
|
||||
onCancel={onCancel}
|
||||
selectedObjects={objs}
|
||||
allowedTypes={allowedTypes}
|
||||
/>
|
||||
);
|
||||
|
||||
|
@ -164,6 +172,7 @@ describe('DeleteConfirmModal', () => {
|
|||
onConfirm={onConfirm}
|
||||
onCancel={onCancel}
|
||||
selectedObjects={objs}
|
||||
allowedTypes={allowedTypes}
|
||||
/>
|
||||
);
|
||||
|
||||
|
@ -184,6 +193,7 @@ describe('DeleteConfirmModal', () => {
|
|||
onConfirm={onConfirm}
|
||||
onCancel={onCancel}
|
||||
selectedObjects={objs}
|
||||
allowedTypes={allowedTypes}
|
||||
/>
|
||||
);
|
||||
const callout = findTestSubject(wrapper, 'sharedObjectsWarning');
|
||||
|
@ -202,6 +212,7 @@ describe('DeleteConfirmModal', () => {
|
|||
onConfirm={onConfirm}
|
||||
onCancel={onCancel}
|
||||
selectedObjects={objs}
|
||||
allowedTypes={allowedTypes}
|
||||
/>
|
||||
);
|
||||
const callout = findTestSubject(wrapper, 'sharedObjectsWarning');
|
||||
|
|
|
@ -27,7 +27,7 @@ import {
|
|||
} from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { SavedObjectWithMetadata } from '../../../../common';
|
||||
import type { SavedObjectWithMetadata, SavedObjectManagementTypeInfo } from '../../../../common';
|
||||
import { getSavedObjectLabel } from '../../../lib';
|
||||
|
||||
export interface DeleteConfirmModalProps {
|
||||
|
@ -35,6 +35,7 @@ export interface DeleteConfirmModalProps {
|
|||
onConfirm: () => void;
|
||||
onCancel: () => void;
|
||||
selectedObjects: SavedObjectWithMetadata[];
|
||||
allowedTypes: SavedObjectManagementTypeInfo[];
|
||||
}
|
||||
|
||||
export const DeleteConfirmModal: FC<DeleteConfirmModalProps> = ({
|
||||
|
@ -42,6 +43,7 @@ export const DeleteConfirmModal: FC<DeleteConfirmModalProps> = ({
|
|||
onConfirm,
|
||||
onCancel,
|
||||
selectedObjects,
|
||||
allowedTypes,
|
||||
}) => {
|
||||
const undeletableObjects = useMemo(() => {
|
||||
return selectedObjects.filter((obj) => obj.meta.hiddenType);
|
||||
|
@ -145,7 +147,7 @@ export const DeleteConfirmModal: FC<DeleteConfirmModalProps> = ({
|
|||
),
|
||||
width: '50px',
|
||||
render: (type, { icon }) => (
|
||||
<EuiToolTip position="top" content={getSavedObjectLabel(type)}>
|
||||
<EuiToolTip position="top" content={getSavedObjectLabel(type, allowedTypes)}>
|
||||
<EuiIcon type={icon} />
|
||||
</EuiToolTip>
|
||||
),
|
||||
|
|
|
@ -47,9 +47,9 @@ describe('Flyout', () => {
|
|||
]),
|
||||
} as any,
|
||||
http,
|
||||
allowedTypes: ['search', 'index-pattern', 'visualization'],
|
||||
search,
|
||||
basePath,
|
||||
allowedTypes: [],
|
||||
};
|
||||
});
|
||||
|
||||
|
|
|
@ -37,6 +37,7 @@ import {
|
|||
IndexPattern,
|
||||
DataPublicPluginStart,
|
||||
} from '../../../../../data/public';
|
||||
import type { SavedObjectManagementTypeInfo } from '../../../../common/types';
|
||||
import {
|
||||
importFile,
|
||||
resolveImportErrors,
|
||||
|
@ -52,7 +53,6 @@ const CREATE_NEW_COPIES_DEFAULT = false;
|
|||
const OVERWRITE_ALL_DEFAULT = true;
|
||||
|
||||
export interface FlyoutProps {
|
||||
allowedTypes: string[];
|
||||
close: () => void;
|
||||
done: () => void;
|
||||
newIndexPatternUrl: string;
|
||||
|
@ -60,6 +60,7 @@ export interface FlyoutProps {
|
|||
http: HttpStart;
|
||||
basePath: IBasePath;
|
||||
search: DataPublicPluginStart['search'];
|
||||
allowedTypes: SavedObjectManagementTypeInfo[];
|
||||
}
|
||||
|
||||
export interface FlyoutState {
|
||||
|
@ -408,6 +409,7 @@ export class Flyout extends Component<FlyoutProps, FlyoutState> {
|
|||
}
|
||||
|
||||
renderBody() {
|
||||
const { allowedTypes } = this.props;
|
||||
const {
|
||||
status,
|
||||
loadingMessage,
|
||||
|
@ -439,6 +441,7 @@ export class Flyout extends Component<FlyoutProps, FlyoutState> {
|
|||
failedImports={failedImports}
|
||||
successfulImports={successfulImports}
|
||||
importWarnings={importWarnings ?? []}
|
||||
allowedTypes={allowedTypes}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -24,6 +24,7 @@ describe('ImportSummary', () => {
|
|||
failedImports: [],
|
||||
successfulImports: [],
|
||||
importWarnings: [],
|
||||
allowedTypes: [],
|
||||
...parts,
|
||||
});
|
||||
|
||||
|
|
|
@ -28,6 +28,7 @@ import type {
|
|||
SavedObjectsImportWarning,
|
||||
IBasePath,
|
||||
} from 'kibana/public';
|
||||
import type { SavedObjectManagementTypeInfo } from '../../../../common/types';
|
||||
import { getDefaultTitle, getSavedObjectLabel, FailedImport } from '../../../lib';
|
||||
import './import_summary.scss';
|
||||
|
||||
|
@ -38,6 +39,7 @@ export interface ImportSummaryProps {
|
|||
successfulImports: SavedObjectsImportSuccess[];
|
||||
importWarnings: SavedObjectsImportWarning[];
|
||||
basePath: IBasePath;
|
||||
allowedTypes: SavedObjectManagementTypeInfo[];
|
||||
}
|
||||
|
||||
interface ImportItem {
|
||||
|
@ -244,6 +246,7 @@ export const ImportSummary: FC<ImportSummaryProps> = ({
|
|||
successfulImports,
|
||||
importWarnings,
|
||||
basePath,
|
||||
allowedTypes,
|
||||
}) => {
|
||||
const importItems: ImportItem[] = useMemo(
|
||||
() =>
|
||||
|
@ -279,6 +282,7 @@ export const ImportSummary: FC<ImportSummaryProps> = ({
|
|||
<EuiHorizontalRule />
|
||||
{importItems.map((item, index) => {
|
||||
const { type, title, icon } = item;
|
||||
const typeLabel = getSavedObjectLabel(type, allowedTypes);
|
||||
return (
|
||||
<EuiFlexGroup
|
||||
responsive={false}
|
||||
|
@ -288,8 +292,8 @@ export const ImportSummary: FC<ImportSummaryProps> = ({
|
|||
className="savedObjectsManagementImportSummary__row"
|
||||
>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiToolTip position="top" content={getSavedObjectLabel(type)}>
|
||||
<EuiIcon aria-label={getSavedObjectLabel(type)} type={icon} size="s" />
|
||||
<EuiToolTip position="top" content={typeLabel}>
|
||||
<EuiIcon aria-label={typeLabel} type={icon} size="s" />
|
||||
</EuiToolTip>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem className="savedObjectsManagementImportSummary__title">
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
import React from 'react';
|
||||
import { shallowWithI18nProvider } from '@kbn/test/jest';
|
||||
import { httpServiceMock } from '../../../../../../core/public/mocks';
|
||||
import type { SavedObjectManagementTypeInfo } from '../../../../common/types';
|
||||
import { Relationships, RelationshipsProps } from './relationships';
|
||||
|
||||
jest.mock('../../../lib/fetch_export_by_type_and_search', () => ({
|
||||
|
@ -19,6 +20,15 @@ jest.mock('../../../lib/fetch_export_objects', () => ({
|
|||
fetchExportObjects: jest.fn(),
|
||||
}));
|
||||
|
||||
const allowedTypes: SavedObjectManagementTypeInfo[] = [
|
||||
{
|
||||
name: 'index-pattern',
|
||||
displayName: 'index-pattern',
|
||||
namespaceType: 'single',
|
||||
hidden: false,
|
||||
},
|
||||
];
|
||||
|
||||
describe('Relationships', () => {
|
||||
it('should render index patterns normally', async () => {
|
||||
const props: RelationshipsProps = {
|
||||
|
@ -71,6 +81,7 @@ describe('Relationships', () => {
|
|||
},
|
||||
},
|
||||
},
|
||||
allowedTypes,
|
||||
close: jest.fn(),
|
||||
};
|
||||
|
||||
|
@ -139,6 +150,7 @@ describe('Relationships', () => {
|
|||
},
|
||||
},
|
||||
},
|
||||
allowedTypes,
|
||||
close: jest.fn(),
|
||||
};
|
||||
|
||||
|
@ -206,6 +218,7 @@ describe('Relationships', () => {
|
|||
},
|
||||
},
|
||||
},
|
||||
allowedTypes,
|
||||
close: jest.fn(),
|
||||
};
|
||||
|
||||
|
@ -273,6 +286,7 @@ describe('Relationships', () => {
|
|||
},
|
||||
},
|
||||
},
|
||||
allowedTypes,
|
||||
close: jest.fn(),
|
||||
};
|
||||
|
||||
|
@ -312,6 +326,7 @@ describe('Relationships', () => {
|
|||
},
|
||||
},
|
||||
},
|
||||
allowedTypes,
|
||||
close: jest.fn(),
|
||||
};
|
||||
|
||||
|
@ -357,6 +372,7 @@ describe('Relationships', () => {
|
|||
},
|
||||
},
|
||||
},
|
||||
allowedTypes,
|
||||
close: jest.fn(),
|
||||
};
|
||||
|
||||
|
|
|
@ -25,6 +25,7 @@ import { SearchFilterConfig } from '@elastic/eui';
|
|||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { IBasePath } from 'src/core/public';
|
||||
import type { SavedObjectManagementTypeInfo } from '../../../../common/types';
|
||||
import { getDefaultTitle, getSavedObjectLabel } from '../../../lib';
|
||||
import {
|
||||
SavedObjectWithMetadata,
|
||||
|
@ -41,6 +42,7 @@ export interface RelationshipsProps {
|
|||
close: () => void;
|
||||
goInspectObject: (obj: SavedObjectWithMetadata) => void;
|
||||
canGoInApp: (obj: SavedObjectWithMetadata) => boolean;
|
||||
allowedTypes: SavedObjectManagementTypeInfo[];
|
||||
}
|
||||
|
||||
export interface RelationshipsState {
|
||||
|
@ -213,7 +215,7 @@ export class Relationships extends Component<RelationshipsProps, RelationshipsSt
|
|||
}
|
||||
|
||||
renderRelationshipsTable() {
|
||||
const { goInspectObject, basePath, savedObject } = this.props;
|
||||
const { goInspectObject, basePath, savedObject, allowedTypes } = this.props;
|
||||
const { relations, isLoading, error } = this.state;
|
||||
|
||||
if (error) {
|
||||
|
@ -238,10 +240,11 @@ export class Relationships extends Component<RelationshipsProps, RelationshipsSt
|
|||
),
|
||||
sortable: false,
|
||||
render: (type: string, object: SavedObjectWithMetadata) => {
|
||||
const typeLabel = getSavedObjectLabel(type, allowedTypes);
|
||||
return (
|
||||
<EuiToolTip position="top" content={getSavedObjectLabel(type)}>
|
||||
<EuiToolTip position="top" content={typeLabel}>
|
||||
<EuiIcon
|
||||
aria-label={getSavedObjectLabel(type)}
|
||||
aria-label={typeLabel}
|
||||
type={object.meta.icon || 'apps'}
|
||||
size="s"
|
||||
data-test-subj="relationshipsObjectType"
|
||||
|
@ -390,19 +393,16 @@ export class Relationships extends Component<RelationshipsProps, RelationshipsSt
|
|||
}
|
||||
|
||||
render() {
|
||||
const { close, savedObject } = this.props;
|
||||
const { close, savedObject, allowedTypes } = this.props;
|
||||
const typeLabel = getSavedObjectLabel(savedObject.type, allowedTypes);
|
||||
|
||||
return (
|
||||
<EuiFlyout onClose={close}>
|
||||
<EuiFlyoutHeader hasBorder>
|
||||
<EuiTitle size="m">
|
||||
<h2>
|
||||
<EuiToolTip position="top" content={getSavedObjectLabel(savedObject.type)}>
|
||||
<EuiIcon
|
||||
aria-label={getSavedObjectLabel(savedObject.type)}
|
||||
size="m"
|
||||
type={savedObject.meta.icon || 'apps'}
|
||||
/>
|
||||
<EuiToolTip position="top" content={typeLabel}>
|
||||
<EuiIcon aria-label={typeLabel} size="m" type={savedObject.meta.icon || 'apps'} />
|
||||
</EuiToolTip>
|
||||
|
||||
{savedObject.meta.title || getDefaultTitle(savedObject)}
|
||||
|
|
|
@ -36,6 +36,9 @@ const defaultProps: TableProps = {
|
|||
},
|
||||
},
|
||||
],
|
||||
allowedTypes: [
|
||||
{ name: 'index-pattern', displayName: 'index-pattern', hidden: false, namespaceType: 'single' },
|
||||
],
|
||||
selectionConfig: {
|
||||
onSelectionChange: () => {},
|
||||
},
|
||||
|
|
|
@ -28,6 +28,7 @@ import {
|
|||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { SavedObjectsTaggingApi } from '../../../../../saved_objects_tagging_oss/public';
|
||||
import type { SavedObjectManagementTypeInfo } from '../../../../common/types';
|
||||
import { getDefaultTitle, getSavedObjectLabel } from '../../../lib';
|
||||
import { SavedObjectWithMetadata } from '../../../types';
|
||||
import {
|
||||
|
@ -39,6 +40,7 @@ import {
|
|||
export interface TableProps {
|
||||
taggingApi?: SavedObjectsTaggingApi;
|
||||
basePath: IBasePath;
|
||||
allowedTypes: SavedObjectManagementTypeInfo[];
|
||||
actionRegistry: SavedObjectsManagementActionServiceStart;
|
||||
columnRegistry: SavedObjectsManagementColumnServiceStart;
|
||||
selectedSavedObjects: SavedObjectWithMetadata[];
|
||||
|
@ -145,6 +147,7 @@ export class Table extends PureComponent<TableProps, TableState> {
|
|||
actionRegistry,
|
||||
columnRegistry,
|
||||
taggingApi,
|
||||
allowedTypes,
|
||||
} = this.props;
|
||||
|
||||
const pagination = {
|
||||
|
@ -182,10 +185,11 @@ export class Table extends PureComponent<TableProps, TableState> {
|
|||
sortable: false,
|
||||
'data-test-subj': 'savedObjectsTableRowType',
|
||||
render: (type: string, object: SavedObjectWithMetadata) => {
|
||||
const typeLabel = getSavedObjectLabel(type, allowedTypes);
|
||||
return (
|
||||
<EuiToolTip position="top" content={getSavedObjectLabel(type)}>
|
||||
<EuiToolTip position="top" content={typeLabel}>
|
||||
<EuiIcon
|
||||
aria-label={getSavedObjectLabel(type)}
|
||||
aria-label={typeLabel}
|
||||
type={object.meta.icon || 'apps'}
|
||||
size="s"
|
||||
data-test-subj="objectType"
|
||||
|
|
|
@ -28,6 +28,7 @@ import {
|
|||
applicationServiceMock,
|
||||
} from '../../../../../core/public/mocks';
|
||||
import { dataPluginMock } from '../../../../data/public/mocks';
|
||||
import type { SavedObjectManagementTypeInfo } from '../../../common/types';
|
||||
import { actionServiceMock } from '../../services/action_service.mock';
|
||||
import { columnServiceMock } from '../../services/column_service.mock';
|
||||
import {
|
||||
|
@ -38,7 +39,14 @@ import {
|
|||
import { Flyout, Relationships } from './components';
|
||||
import { SavedObjectWithMetadata } from '../../types';
|
||||
|
||||
const allowedTypes = ['index-pattern', 'visualization', 'dashboard', 'search'];
|
||||
const convertType = (type: string): SavedObjectManagementTypeInfo => ({
|
||||
name: type,
|
||||
displayName: type,
|
||||
hidden: false,
|
||||
namespaceType: 'single',
|
||||
});
|
||||
|
||||
const allowedTypes = ['index-pattern', 'visualization', 'dashboard', 'search'].map(convertType);
|
||||
|
||||
const allSavedObjects = [
|
||||
{
|
||||
|
@ -377,7 +385,7 @@ describe('SavedObjectsTable', () => {
|
|||
|
||||
expect(fetchExportByTypeAndSearchMock).toHaveBeenCalledWith({
|
||||
http,
|
||||
types: allowedTypes,
|
||||
types: allowedTypes.map((type) => type.name),
|
||||
includeReferencesDeep: true,
|
||||
});
|
||||
expect(saveAsMock).toHaveBeenCalledWith(blob, 'export.ndjson');
|
||||
|
@ -406,7 +414,7 @@ describe('SavedObjectsTable', () => {
|
|||
|
||||
expect(fetchExportByTypeAndSearchMock).toHaveBeenCalledWith({
|
||||
http,
|
||||
types: allowedTypes,
|
||||
types: allowedTypes.map((type) => type.name),
|
||||
search: 'test*',
|
||||
includeReferencesDeep: true,
|
||||
});
|
||||
|
|
|
@ -23,6 +23,7 @@ import {
|
|||
import { RedirectAppLinks } from '../../../../kibana_react/public';
|
||||
import { SavedObjectsTaggingApi } from '../../../../saved_objects_tagging_oss/public';
|
||||
import { IndexPatternsContract } from '../../../../data/public';
|
||||
import type { SavedObjectManagementTypeInfo } from '../../../common/types';
|
||||
import {
|
||||
parseQuery,
|
||||
getSavedObjectCounts,
|
||||
|
@ -56,7 +57,7 @@ interface ExportAllOption {
|
|||
}
|
||||
|
||||
export interface SavedObjectsTableProps {
|
||||
allowedTypes: string[];
|
||||
allowedTypes: SavedObjectManagementTypeInfo[];
|
||||
actionRegistry: SavedObjectsManagementActionServiceStart;
|
||||
columnRegistry: SavedObjectsManagementColumnServiceStart;
|
||||
savedObjectsClient: SavedObjectsClientContract;
|
||||
|
@ -102,6 +103,7 @@ const unableFindSavedObjectNotificationMessage = i18n.translate(
|
|||
'savedObjectsManagement.objectsTable.unableFindSavedObjectNotificationMessage',
|
||||
{ defaultMessage: 'Unable to find saved object' }
|
||||
);
|
||||
|
||||
export class SavedObjectsTable extends Component<SavedObjectsTableProps, SavedObjectsTableState> {
|
||||
private _isMounted = false;
|
||||
|
||||
|
@ -114,7 +116,7 @@ export class SavedObjectsTable extends Component<SavedObjectsTableProps, SavedOb
|
|||
perPage: props.perPageConfig || 50,
|
||||
savedObjects: [],
|
||||
savedObjectCounts: props.allowedTypes.reduce((typeToCountMap, type) => {
|
||||
typeToCountMap[type] = 0;
|
||||
typeToCountMap[type.name] = 0;
|
||||
return typeToCountMap;
|
||||
}, {} as Record<string, number>),
|
||||
activeQuery: props.initialQuery ?? Query.parse(''),
|
||||
|
@ -146,9 +148,11 @@ export class SavedObjectsTable extends Component<SavedObjectsTableProps, SavedOb
|
|||
}
|
||||
|
||||
fetchCounts = async () => {
|
||||
const { allowedTypes, taggingApi } = this.props;
|
||||
const { taggingApi } = this.props;
|
||||
const { queryText, visibleTypes, selectedTags } = parseQuery(this.state.activeQuery);
|
||||
|
||||
const allowedTypes = this.props.allowedTypes.map((type) => type.name);
|
||||
|
||||
const selectedTypes = allowedTypes.filter(
|
||||
(type) => !visibleTypes || visibleTypes.includes(type)
|
||||
);
|
||||
|
@ -207,6 +211,11 @@ export class SavedObjectsTable extends Component<SavedObjectsTableProps, SavedOb
|
|||
const { activeQuery: query, page, perPage } = this.state;
|
||||
const { notifications, http, allowedTypes, taggingApi } = this.props;
|
||||
const { queryText, visibleTypes, selectedTags } = parseQuery(query);
|
||||
|
||||
const searchTypes = allowedTypes
|
||||
.map((type) => type.name)
|
||||
.filter((type) => !visibleTypes || visibleTypes.includes(type));
|
||||
|
||||
// "searchFields" is missing from the "findOptions" but gets injected via the API.
|
||||
// The API extracts the fields from each uiExports.savedObjectsManagement "defaultSearchField" attribute
|
||||
const findOptions: SavedObjectsFindOptions = {
|
||||
|
@ -214,7 +223,7 @@ export class SavedObjectsTable extends Component<SavedObjectsTableProps, SavedOb
|
|||
perPage,
|
||||
page: page + 1,
|
||||
fields: ['id'],
|
||||
type: allowedTypes.filter((type) => !visibleTypes || visibleTypes.includes(type)),
|
||||
type: searchTypes,
|
||||
};
|
||||
if (findOptions.type.length > 1) {
|
||||
findOptions.sortField = 'type';
|
||||
|
@ -520,8 +529,9 @@ export class SavedObjectsTable extends Component<SavedObjectsTableProps, SavedOb
|
|||
};
|
||||
|
||||
getRelationships = async (type: string, id: string) => {
|
||||
const { allowedTypes, http } = this.props;
|
||||
return await getRelationships(http, type, id, allowedTypes);
|
||||
const { http } = this.props;
|
||||
const allowedTypeNames = this.props.allowedTypes.map((t) => t.name);
|
||||
return await getRelationships(http, type, id, allowedTypeNames);
|
||||
};
|
||||
|
||||
renderFlyout() {
|
||||
|
@ -540,9 +550,9 @@ export class SavedObjectsTable extends Component<SavedObjectsTableProps, SavedOb
|
|||
http={this.props.http}
|
||||
indexPatterns={this.props.indexPatterns}
|
||||
newIndexPatternUrl={newIndexPatternUrl}
|
||||
allowedTypes={this.props.allowedTypes}
|
||||
basePath={this.props.http.basePath}
|
||||
search={this.props.search}
|
||||
allowedTypes={this.props.allowedTypes}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
@ -560,12 +570,15 @@ export class SavedObjectsTable extends Component<SavedObjectsTableProps, SavedOb
|
|||
close={this.onHideRelationships}
|
||||
goInspectObject={this.props.goInspectObject}
|
||||
canGoInApp={this.props.canGoInApp}
|
||||
allowedTypes={this.props.allowedTypes}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
renderDeleteConfirmModal() {
|
||||
const { isShowingDeleteConfirmModal, isDeleting, selectedSavedObjects } = this.state;
|
||||
const { allowedTypes } = this.props;
|
||||
|
||||
if (!isShowingDeleteConfirmModal) {
|
||||
return null;
|
||||
}
|
||||
|
@ -580,6 +593,7 @@ export class SavedObjectsTable extends Component<SavedObjectsTableProps, SavedOb
|
|||
this.setState({ isShowingDeleteConfirmModal: false });
|
||||
}}
|
||||
selectedObjects={selectedSavedObjects}
|
||||
allowedTypes={allowedTypes}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
@ -638,9 +652,9 @@ export class SavedObjectsTable extends Component<SavedObjectsTableProps, SavedOb
|
|||
};
|
||||
|
||||
const filterOptions = allowedTypes.map((type) => ({
|
||||
value: type,
|
||||
name: type,
|
||||
view: `${type} (${savedObjectCounts[type] || 0})`,
|
||||
value: type.name,
|
||||
name: type.name,
|
||||
view: `${type.displayName} (${savedObjectCounts[type.name] || 0})`,
|
||||
}));
|
||||
|
||||
return (
|
||||
|
@ -661,6 +675,7 @@ export class SavedObjectsTable extends Component<SavedObjectsTableProps, SavedOb
|
|||
basePath={http.basePath}
|
||||
taggingApi={taggingApi}
|
||||
initialQuery={this.props.initialQuery}
|
||||
allowedTypes={allowedTypes}
|
||||
itemId={'id'}
|
||||
actionRegistry={this.props.actionRegistry}
|
||||
columnRegistry={this.props.columnRegistry}
|
||||
|
|
|
@ -16,6 +16,7 @@ import { CoreStart, ChromeBreadcrumb } from 'src/core/public';
|
|||
import type { SpacesApi, SpacesContextProps } from '../../../../../x-pack/plugins/spaces/public';
|
||||
import { DataPublicPluginStart } from '../../../data/public';
|
||||
import { SavedObjectsTaggingApi } from '../../../saved_objects_tagging_oss/public';
|
||||
import type { SavedObjectManagementTypeInfo } from '../../common/types';
|
||||
import {
|
||||
SavedObjectsManagementActionServiceStart,
|
||||
SavedObjectsManagementColumnServiceStart,
|
||||
|
@ -38,7 +39,7 @@ const SavedObjectsTablePage = ({
|
|||
dataStart: DataPublicPluginStart;
|
||||
taggingApi?: SavedObjectsTaggingApi;
|
||||
spacesApi?: SpacesApi;
|
||||
allowedTypes: string[];
|
||||
allowedTypes: SavedObjectManagementTypeInfo[];
|
||||
actionRegistry: SavedObjectsManagementActionServiceStart;
|
||||
columnRegistry: SavedObjectsManagementColumnServiceStart;
|
||||
setBreadcrumbs: (crumbs: ChromeBreadcrumb[]) => void;
|
||||
|
|
|
@ -6,7 +6,17 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { IRouter } from 'src/core/server';
|
||||
import { IRouter, SavedObjectsType } from 'src/core/server';
|
||||
import { SavedObjectManagementTypeInfo } from '../../common';
|
||||
|
||||
const convertType = (sot: SavedObjectsType): SavedObjectManagementTypeInfo => {
|
||||
return {
|
||||
name: sot.name,
|
||||
namespaceType: sot.namespaceType,
|
||||
hidden: sot.hidden,
|
||||
displayName: sot.management?.displayName ?? sot.name,
|
||||
};
|
||||
};
|
||||
|
||||
export const registerGetAllowedTypesRoute = (router: IRouter) => {
|
||||
router.get(
|
||||
|
@ -18,7 +28,7 @@ export const registerGetAllowedTypesRoute = (router: IRouter) => {
|
|||
const allowedTypes = context.core.savedObjects.typeRegistry
|
||||
.getImportableAndExportableTypes()
|
||||
.filter((type) => type.management!.visibleInManagement ?? true)
|
||||
.map((type) => type.name);
|
||||
.map(convertType);
|
||||
|
||||
return res.ok({
|
||||
body: {
|
||||
|
|
|
@ -212,6 +212,24 @@ export class SavedObjectExportTransformsPlugin implements Plugin {
|
|||
visibleInManagement: true,
|
||||
},
|
||||
});
|
||||
|
||||
// example of a SO type specifying a display name
|
||||
savedObjects.registerType<{ enabled: boolean; title: string }>({
|
||||
name: 'test-with-display-name',
|
||||
hidden: false,
|
||||
namespaceType: 'single',
|
||||
mappings: {
|
||||
properties: {
|
||||
title: { type: 'text' },
|
||||
enabled: { type: 'boolean' },
|
||||
},
|
||||
},
|
||||
management: {
|
||||
defaultSearchField: 'title',
|
||||
importableAndExportable: true,
|
||||
displayName: 'my display name',
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
public start() {}
|
||||
|
|
|
@ -11,6 +11,7 @@ import expect from '@kbn/expect';
|
|||
import type { Response } from 'supertest';
|
||||
import type { PluginFunctionalProviderContext } from '../../services';
|
||||
import { SavedObject } from '../../../../src/core/types';
|
||||
import type { SavedObjectManagementTypeInfo } from '../../../../src/plugins/saved_objects_management/common/types';
|
||||
|
||||
function parseNdJson(input: string): Array<SavedObject<any>> {
|
||||
return input.split('\n').map((str) => JSON.parse(str));
|
||||
|
@ -97,17 +98,39 @@ export default function ({ getService }: PluginFunctionalProviderContext) {
|
|||
});
|
||||
|
||||
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);
|
||||
}));
|
||||
describe('GET /api/kibana/management/saved_objects/_allowed_types', () => {
|
||||
let types: SavedObjectManagementTypeInfo[];
|
||||
|
||||
before(async () => {
|
||||
await supertest
|
||||
.get('/api/kibana/management/saved_objects/_allowed_types')
|
||||
.set('kbn-xsrf', 'true')
|
||||
.expect(200)
|
||||
.then((response: Response) => {
|
||||
types = response.body.types as SavedObjectManagementTypeInfo[];
|
||||
});
|
||||
});
|
||||
|
||||
it('should only return types that are `visibleInManagement: true`', () => {
|
||||
const typeNames = types.map((type) => type.name);
|
||||
|
||||
expect(typeNames.includes('test-is-exportable')).to.eql(true);
|
||||
expect(typeNames.includes('test-visible-in-management')).to.eql(true);
|
||||
expect(typeNames.includes('test-not-visible-in-management')).to.eql(false);
|
||||
});
|
||||
|
||||
it('should return displayName for types specifying it', () => {
|
||||
const typeWithDisplayName = types.find((type) => type.name === 'test-with-display-name');
|
||||
expect(typeWithDisplayName !== undefined).to.eql(true);
|
||||
expect(typeWithDisplayName!.displayName).to.eql('my display name');
|
||||
|
||||
const typeWithoutDisplayName = types.find(
|
||||
(type) => type.name === 'test-visible-in-management'
|
||||
);
|
||||
expect(typeWithoutDisplayName !== undefined).to.eql(true);
|
||||
expect(typeWithoutDisplayName!.displayName).to.eql('test-visible-in-management');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue