[embeddable] remove legacy embeddable factories from 'Add from library' flyout (#202823)

Part of https://github.com/elastic/kibana/issues/180059

PR removes legacy embeddable factory support from Canvas and Dashboard
`Add from library` flyout

PR also does the following clean-ups
1) Renames folder, files, and component from `add_panel_flyout` to
`add_from_library_flyout`. When component was originally created,
dashboard `Add panel` button did not exist, and `Add from library`
button was called `Add panel`. Now that dashboard contains `Add panel`
and `Add from library` buttons, the old naming convention is super
confusing and not longer lines up with the current UI.
2) moves registry to `add_from_library` folder so that the registry is
in closer proximity to its usage.
2) Renames `registerReactEmbeddableSavedObject` to
`registerAddFromLibraryType` because
`registerReactEmbeddableSavedObject` does not clearly specifying what
the registry enables.

---------

Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
This commit is contained in:
Nathan Reese 2024-12-05 12:55:40 -07:00 committed by GitHub
parent 178baa8468
commit d508b5da9c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
25 changed files with 284 additions and 658 deletions

View file

@ -81,13 +81,14 @@ export const RegisterEmbeddable = () => {
<EuiSpacer size="l" />
<EuiText>
<h2>Saved object embeddables</h2>
<h2>
Show saved object type in <em>Add from library</em> menu
</h2>
<p>
Embeddable factories, such as Lens, Maps, Links, that can reference saved objects should
register their saved object types using{' '}
<strong>registerReactEmbeddableSavedObject</strong>. The <em>Add from library</em> flyout
on Dashboards uses this registry to list saved objects. The example function below could
be called from the public start contract for a plugin.
register their saved object types using <strong>registerAddFromLibraryType</strong>. The{' '}
<em>Add from library</em> flyout on Dashboards uses this registry to list saved objects.
The example function below could be called from the public start contract for a plugin.
</p>
</EuiText>
<EuiSpacer size="s" />

View file

@ -14,14 +14,13 @@ const MY_SAVED_OBJECT_TYPE = 'mySavedObjectType';
const APP_ICON = 'logoKibana';
export const registerMyEmbeddableSavedObject = (embeddableSetup: EmbeddableSetup) =>
embeddableSetup.registerReactEmbeddableSavedObject({
embeddableSetup.registerAddFromLibraryType({
onAdd: (container, savedObject) => {
container.addNewPanel({
panelType: MY_EMBEDDABLE_TYPE,
initialState: savedObject.attributes,
});
},
embeddableType: MY_EMBEDDABLE_TYPE,
savedObjectType: MY_SAVED_OBJECT_TYPE,
savedObjectName: 'Some saved object',
getIconForSavedObject: () => APP_ICON,

View file

@ -7,18 +7,14 @@
* License v3.0 only", or the "Server Side Public License, v 1".
*/
import { isErrorEmbeddable, openAddPanelFlyout } from '@kbn/embeddable-plugin/public';
import { isErrorEmbeddable, openAddFromLibraryFlyout } from '@kbn/embeddable-plugin/public';
import { DashboardContainer } from '../dashboard_container';
export function addFromLibrary(this: DashboardContainer) {
if (isErrorEmbeddable(this)) return;
this.openOverlay(
openAddPanelFlyout({
openAddFromLibraryFlyout({
container: this,
onAddPanel: (id: string) => {
this.setScrollToPanelId(id);
this.setHighlightPanelId(id);
},
onClose: () => {
this.clearOverlays();
},

View file

@ -412,7 +412,7 @@ export class DiscoverPlugin
return this.getDiscoverServices(coreStart, deps, profilesManager, ebtManager);
};
plugins.embeddable.registerReactEmbeddableSavedObject<SavedSearchAttributes>({
plugins.embeddable.registerAddFromLibraryType<SavedSearchAttributes>({
onAdd: async (container, savedObject) => {
const services = await getDiscoverServicesForEmbeddable();
const initialState = await deserializeState({
@ -427,7 +427,6 @@ export class DiscoverPlugin
initialState,
});
},
embeddableType: SEARCH_EMBEDDABLE_TYPE,
savedObjectType: SavedSearchType,
savedObjectName: i18n.translate('discover.savedSearch.savedObjectName', {
defaultMessage: 'Saved search',

View file

@ -0,0 +1,111 @@
/*
* 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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/
import * as React from 'react';
import { fireEvent, render, screen } from '@testing-library/react';
import { SavedObjectCommon } from '@kbn/saved-objects-finder-plugin/common';
import { AddFromLibraryFlyout } from './add_from_library_flyout';
import { usageCollection } from '../kibana_services';
import { getMockPresentationContainer } from '@kbn/presentation-containers/mocks';
import { registerAddFromLibraryType } from './registry';
import { PresentationContainer } from '@kbn/presentation-containers';
import { HasType } from '@kbn/presentation-publishing';
// Mock saved objects finder component so we can call the onChoose method.
jest.mock('@kbn/saved-objects-finder-plugin/public', () => {
return {
SavedObjectFinder: jest
.fn()
.mockImplementation(
({
onChoose,
}: {
onChoose: (id: string, type: string, name: string, so: unknown) => Promise<void>;
}) => (
<>
<button
id="soFinderAddButton"
data-test-subj="soFinderAddButton"
onClick={() =>
onChoose?.(
'awesomeId',
'AWESOME_EMBEDDABLE',
'Awesome sauce',
{} as unknown as SavedObjectCommon
)
}
>
Add embeddable!
</button>
</>
)
),
};
});
describe('add from library flyout', () => {
let container: PresentationContainer & HasType;
const onAdd = jest.fn();
beforeAll(() => {
registerAddFromLibraryType({
onAdd,
savedObjectType: 'AWESOME_EMBEDDABLE',
savedObjectName: 'Awesome sauce',
getIconForSavedObject: () => 'happyface',
});
});
beforeEach(() => {
onAdd.mockClear();
container = {
type: 'DASHBOARD_CONTAINER',
...getMockPresentationContainer(),
};
});
test('renders SavedObjectFinder', async () => {
const { container: componentContainer } = render(
<AddFromLibraryFlyout container={container} />
);
// component should not contain an extra flyout
// https://github.com/elastic/kibana/issues/64789
const flyout = componentContainer.querySelector('.euiFlyout');
expect(flyout).toBeNull();
const dummyButton = screen.queryAllByTestId('soFinderAddButton');
expect(dummyButton).toHaveLength(1);
});
test('calls the registered onAdd method', async () => {
render(<AddFromLibraryFlyout container={container} />);
expect(Object.values(container.children$.value).length).toBe(0);
fireEvent.click(screen.getByTestId('soFinderAddButton'));
// flush promises
await new Promise((r) => setTimeout(r, 1));
expect(onAdd).toHaveBeenCalledWith(container, {});
});
test('runs telemetry function on add embeddable', async () => {
render(<AddFromLibraryFlyout container={container} />);
expect(Object.values(container.children$.value).length).toBe(0);
fireEvent.click(screen.getByTestId('soFinderAddButton'));
// flush promises
await new Promise((r) => setTimeout(r, 1));
expect(usageCollection.reportUiCounter).toHaveBeenCalledWith(
'DASHBOARD_CONTAINER',
'click',
'AWESOME_EMBEDDABLE:add'
);
});
});

View file

@ -0,0 +1,109 @@
/*
* 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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/
import React, { useCallback } from 'react';
import { i18n } from '@kbn/i18n';
import { EuiFlyoutBody, EuiFlyoutHeader, EuiTitle } from '@elastic/eui';
import { SavedObjectCommon } from '@kbn/saved-objects-finder-plugin/common';
import {
SavedObjectFinder,
SavedObjectFinderProps,
type SavedObjectMetaData,
} from '@kbn/saved-objects-finder-plugin/public';
import { METRIC_TYPE } from '@kbn/analytics';
import { apiHasType } from '@kbn/presentation-publishing';
import { CanAddNewPanel } from '@kbn/presentation-containers';
import {
core,
savedObjectsTaggingOss,
contentManagement,
usageCollection,
} from '../kibana_services';
import { EmbeddableFactoryNotFoundError } from '../lib';
import { getAddFromLibraryType, useAddFromLibraryTypes } from './registry';
const runAddTelemetry = (
parent: unknown,
savedObject: SavedObjectCommon,
savedObjectMetaData: SavedObjectMetaData
) => {
if (!apiHasType(parent)) return;
const type = savedObjectMetaData.getSavedObjectSubType
? savedObjectMetaData.getSavedObjectSubType(savedObject)
: savedObjectMetaData.type;
usageCollection?.reportUiCounter?.(parent.type, METRIC_TYPE.CLICK, `${type}:add`);
};
export const AddFromLibraryFlyout = ({
container,
modalTitleId,
}: {
container: CanAddNewPanel;
modalTitleId?: string;
}) => {
const libraryTypes = useAddFromLibraryTypes();
const onChoose: SavedObjectFinderProps['onChoose'] = useCallback(
async (
id: SavedObjectCommon['id'],
type: SavedObjectCommon['type'],
name: string,
savedObject: SavedObjectCommon
) => {
const libraryType = getAddFromLibraryType(type);
if (!libraryType) {
core.notifications.toasts.addWarning(new EmbeddableFactoryNotFoundError(type).message);
return;
}
libraryType.onAdd(container, savedObject);
runAddTelemetry(container, savedObject, libraryType.savedObjectMetaData);
},
[container]
);
return (
<>
<EuiFlyoutHeader hasBorder>
<EuiTitle size="s">
<h2 id={modalTitleId}>
{i18n.translate('embeddableApi.addPanel.Title', { defaultMessage: 'Add from library' })}
</h2>
</EuiTitle>
</EuiFlyoutHeader>
<EuiFlyoutBody>
<SavedObjectFinder
id="embeddableAddPanel"
services={{
contentClient: contentManagement.client,
savedObjectsTagging: savedObjectsTaggingOss?.getTaggingApi(),
uiSettings: core.uiSettings,
}}
onChoose={onChoose}
savedObjectMetaData={libraryTypes}
showFilter={true}
noItemsMessage={i18n.translate('embeddableApi.addPanel.noMatchingObjectsMessage', {
defaultMessage: 'No matching objects found.',
})}
getTooltipText={(item) => {
return item.managed
? i18n.translate('embeddableApi.addPanel.managedPanelTooltip', {
defaultMessage:
'Elastic manages this panel. Adding it to a dashboard unlinks it from the library.',
})
: undefined;
}}
/>
</EuiFlyoutBody>
</>
);
};

View file

@ -17,32 +17,25 @@ import { CanAddNewPanel } from '@kbn/presentation-containers';
import { core } from '../kibana_services';
const LazyAddPanelFlyout = React.lazy(async () => {
const module = await import('./add_panel_flyout');
return { default: module.AddPanelFlyout };
const module = await import('./add_from_library_flyout');
return { default: module.AddFromLibraryFlyout };
});
const htmlId = htmlIdGenerator('modalTitleId');
export const openAddPanelFlyout = ({
export const openAddFromLibraryFlyout = ({
container,
onAddPanel,
onClose,
}: {
container: CanAddNewPanel;
onAddPanel?: (id: string) => void;
onClose?: () => void;
}): OverlayRef => {
const modalTitleId = htmlId();
// send the overlay ref to the root embeddable if it is capable of tracking overlays
const flyoutSession = core.overlays.openFlyout(
const flyoutRef = core.overlays.openFlyout(
toMountPoint(
<Suspense fallback={<EuiLoadingSpinner />}>
<LazyAddPanelFlyout
container={container}
onAddPanel={onAddPanel}
modalTitleId={modalTitleId}
/>
<LazyAddPanelFlyout container={container} modalTitleId={modalTitleId} />
</Suspense>,
core
),
@ -60,5 +53,5 @@ export const openAddPanelFlyout = ({
}
);
return flyoutSession;
return flyoutRef;
};

View file

@ -8,54 +8,43 @@
*/
import { IconType } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { CanAddNewPanel } from '@kbn/presentation-containers';
import { FinderAttributes, SavedObjectCommon } from '@kbn/saved-objects-finder-plugin/common';
import { SavedObjectMetaData } from '@kbn/saved-objects-finder-plugin/public';
import { useMemo } from 'react';
type SOToEmbeddable<TSavedObjectAttributes extends FinderAttributes = FinderAttributes> = (
container: CanAddNewPanel,
savedObject: SavedObjectCommon<TSavedObjectAttributes>
) => void;
export type ReactEmbeddableSavedObject<
TSavedObjectAttributes extends FinderAttributes = FinderAttributes
> = {
onAdd: SOToEmbeddable<TSavedObjectAttributes>;
export type RegistryItem<TSavedObjectAttributes extends FinderAttributes = FinderAttributes> = {
onAdd: (
container: CanAddNewPanel,
savedObject: SavedObjectCommon<TSavedObjectAttributes>
) => void;
savedObjectMetaData: SavedObjectMetaData;
};
const registry: Map<string, ReactEmbeddableSavedObject<any>> = new Map();
const registry: Map<string, RegistryItem<any>> = new Map();
export const registerReactEmbeddableSavedObject = <
TSavedObjectAttributes extends FinderAttributes
>({
export const registerAddFromLibraryType = <TSavedObjectAttributes extends FinderAttributes>({
onAdd,
embeddableType,
savedObjectType,
savedObjectName,
getIconForSavedObject,
getSavedObjectSubType,
getTooltipForSavedObject,
}: {
onAdd: SOToEmbeddable<TSavedObjectAttributes>;
embeddableType: string;
onAdd: RegistryItem['onAdd'];
savedObjectType: string;
savedObjectName: string;
getIconForSavedObject: (savedObject: SavedObjectCommon<TSavedObjectAttributes>) => IconType;
getSavedObjectSubType?: (savedObject: SavedObjectCommon<TSavedObjectAttributes>) => string;
getTooltipForSavedObject?: (savedObject: SavedObjectCommon<TSavedObjectAttributes>) => string;
}) => {
if (registry.has(embeddableType)) {
if (registry.has(savedObjectType)) {
throw new Error(
i18n.translate('embeddableApi.embeddableSavedObjectRegistry.keyAlreadyExistsError', {
defaultMessage: `Embeddable type {embeddableType} already exists in the registry.`,
values: { embeddableType },
})
`Saved object type '${savedObjectType}' already exists in the 'Add from Library' registry.`
);
}
registry.set(embeddableType, {
registry.set(savedObjectType, {
onAdd,
savedObjectMetaData: {
name: savedObjectName,
@ -67,10 +56,14 @@ export const registerReactEmbeddableSavedObject = <
});
};
export const getReactEmbeddableSavedObjects = <
TSavedObjectAttributes extends FinderAttributes
>() => {
return registry.entries() as IterableIterator<
[string, ReactEmbeddableSavedObject<TSavedObjectAttributes>]
>;
export function useAddFromLibraryTypes() {
return useMemo(() => {
return [...registry.entries()]
.map(([type, registryItem]) => registryItem.savedObjectMetaData)
.sort((a, b) => a.type.localeCompare(b.type));
}, []);
}
export const getAddFromLibraryType = (type: string) => {
return registry.get(type);
};

View file

@ -1,196 +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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/
import * as React from 'react';
import { coreMock } from '@kbn/core/public/mocks';
import { fireEvent, render, screen } from '@testing-library/react';
import { SavedObjectCommon } from '@kbn/saved-objects-finder-plugin/common';
import { AddPanelFlyout } from './add_panel_flyout';
import { core, embeddableStart, usageCollection } from '../kibana_services';
import { ContactCardEmbeddableFactory } from '../lib/test_samples';
import { Container, registerReactEmbeddableSavedObject } from '../lib';
import { getMockPresentationContainer } from '@kbn/presentation-containers/mocks';
// Mock saved objects finder component so we can call the onChoose method.
jest.mock('@kbn/saved-objects-finder-plugin/public', () => {
return {
SavedObjectFinder: jest
.fn()
.mockImplementation(
({
onChoose,
}: {
onChoose: (id: string, type: string, name: string, so: unknown) => Promise<void>;
}) => (
<>
<button
id="soFinderAddButton"
data-test-subj="soFinderAddButton"
onClick={() =>
onChoose?.(
'awesomeId',
'AWESOME_EMBEDDABLE',
'Awesome sauce',
{} as unknown as SavedObjectCommon
)
}
>
Add embeddable!
</button>
<button
id="soFinderAddLegacyButton"
data-test-subj="soFinderAddLegacyButton"
onClick={() =>
onChoose?.(
'testId',
'CONTACT_CARD_EMBEDDABLE',
'test name',
{} as unknown as SavedObjectCommon
)
}
>
Add legacy embeddable!
</button>
</>
)
),
};
});
describe('add panel flyout', () => {
describe('registered embeddables', () => {
let container: Container;
const onAdd = jest.fn();
beforeAll(() => {
registerReactEmbeddableSavedObject({
onAdd,
embeddableType: 'AWESOME_EMBEDDABLE',
savedObjectType: 'AWESOME_EMBEDDABLE',
savedObjectName: 'Awesome sauce',
getIconForSavedObject: () => 'happyface',
});
embeddableStart.getEmbeddableFactories = jest.fn().mockReturnValue([]);
});
beforeEach(() => {
onAdd.mockClear();
container = getMockPresentationContainer() as unknown as Container;
// @ts-ignore type is only expected on a dashboard container
container.type = 'DASHBOARD_CONTAINER';
});
test('add panel flyout renders SavedObjectFinder', async () => {
const { container: componentContainer } = render(<AddPanelFlyout container={container} />);
// component should not contain an extra flyout
// https://github.com/elastic/kibana/issues/64789
const flyout = componentContainer.querySelector('.euiFlyout');
expect(flyout).toBeNull();
const dummyButton = screen.queryAllByTestId('soFinderAddButton');
expect(dummyButton).toHaveLength(1);
});
test('add panel calls the registered onAdd method', async () => {
render(<AddPanelFlyout container={container} />);
expect(Object.values(container.children$.value).length).toBe(0);
fireEvent.click(screen.getByTestId('soFinderAddButton'));
// flush promises
await new Promise((r) => setTimeout(r, 1));
expect(onAdd).toHaveBeenCalledWith(container, {});
});
test('runs telemetry function on add embeddable', async () => {
render(<AddPanelFlyout container={container} />);
expect(Object.values(container.children$.value).length).toBe(0);
fireEvent.click(screen.getByTestId('soFinderAddButton'));
// flush promises
await new Promise((r) => setTimeout(r, 1));
expect(usageCollection.reportUiCounter).toHaveBeenCalledWith(
'DASHBOARD_CONTAINER',
'click',
'AWESOME_EMBEDDABLE:add'
);
});
});
describe('legacy embeddables', () => {
let container: Container;
beforeEach(() => {
const coreStart = coreMock.createStart();
const contactCardEmbeddableFactory = new ContactCardEmbeddableFactory(
(() => null) as any,
coreStart
);
embeddableStart.getEmbeddableFactories = jest
.fn()
.mockReturnValue([contactCardEmbeddableFactory]);
container = getMockPresentationContainer() as unknown as Container;
container.addNewEmbeddable = jest.fn().mockResolvedValue({ id: 'foo' });
// @ts-ignore type is only expected on a dashboard container
container.type = 'HELLO_WORLD_CONTAINER';
});
test('add panel flyout renders SavedObjectFinder', async () => {
const { container: componentContainer } = render(<AddPanelFlyout container={container} />);
// component should not contain an extra flyout
// https://github.com/elastic/kibana/issues/64789
const flyout = componentContainer.querySelector('.euiFlyout');
expect(flyout).toBeNull();
const dummyButton = screen.queryAllByTestId('soFinderAddLegacyButton');
expect(dummyButton).toHaveLength(1);
});
test('add panel adds legacy embeddable to container', async () => {
render(<AddPanelFlyout container={container} />);
expect(Object.values(container.children$.value).length).toBe(0);
fireEvent.click(screen.getByTestId('soFinderAddLegacyButton'));
// flush promises
await new Promise((r) => setTimeout(r, 1));
expect(container.addNewEmbeddable).toHaveBeenCalled();
});
test('shows a success toast on add', async () => {
render(<AddPanelFlyout container={container} />);
fireEvent.click(screen.getByTestId('soFinderAddLegacyButton'));
// flush promises
await new Promise((r) => setTimeout(r, 1));
expect(core.notifications.toasts.addSuccess).toHaveBeenCalledWith({
'data-test-subj': 'addObjectToContainerSuccess',
title: 'test name was added',
});
});
test('runs telemetry function on add legacy embeddable', async () => {
render(<AddPanelFlyout container={container} />);
expect(Object.values(container.children$.value).length).toBe(0);
fireEvent.click(screen.getByTestId('soFinderAddLegacyButton'));
// flush promises
await new Promise((r) => setTimeout(r, 1));
expect(usageCollection.reportUiCounter).toHaveBeenCalledWith(
'HELLO_WORLD_CONTAINER',
'click',
'CONTACT_CARD_EMBEDDABLE:add'
);
});
});
});

View file

@ -1,224 +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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/
import React, { useCallback, useMemo } from 'react';
import { i18n } from '@kbn/i18n';
import { EuiFlyoutBody, EuiFlyoutHeader, EuiTitle } from '@elastic/eui';
import { FinderAttributes, SavedObjectCommon } from '@kbn/saved-objects-finder-plugin/common';
import {
SavedObjectFinder,
SavedObjectFinderProps,
type SavedObjectMetaData,
} from '@kbn/saved-objects-finder-plugin/public';
import { METRIC_TYPE } from '@kbn/analytics';
import { apiHasType } from '@kbn/presentation-publishing';
import { Toast } from '@kbn/core/public';
import { CanAddNewPanel } from '@kbn/presentation-containers';
import {
core,
embeddableStart,
savedObjectsTaggingOss,
contentManagement,
usageCollection,
} from '../kibana_services';
import { savedObjectToPanel } from '../registry/saved_object_to_panel_methods';
import {
ReactEmbeddableSavedObject,
EmbeddableFactory,
EmbeddableFactoryNotFoundError,
getReactEmbeddableSavedObjects,
PanelIncompatibleError,
} from '../lib';
type LegacyFactoryMap = { [key: string]: EmbeddableFactory };
type FactoryMap<TSavedObjectAttributes extends FinderAttributes = FinderAttributes> = {
[key: string]: ReactEmbeddableSavedObject<TSavedObjectAttributes> & { type: string };
};
type CanAddNewEmbeddable = {
addNewEmbeddable: (type: string, explicitInput: unknown, attributes: unknown) => { id: string };
};
const apiCanAddNewEmbeddable = (api: unknown): api is CanAddNewEmbeddable => {
return typeof (api as CanAddNewEmbeddable).addNewEmbeddable === 'function';
};
let lastToast: string | Toast;
const showSuccessToast = (name: string) => {
if (lastToast) core.notifications.toasts.remove(lastToast);
lastToast = core.notifications.toasts.addSuccess({
title: i18n.translate('embeddableApi.addPanel.savedObjectAddedToContainerSuccessMessageTitle', {
defaultMessage: '{savedObjectName} was added',
values: {
savedObjectName: name,
},
}),
'data-test-subj': 'addObjectToContainerSuccess',
});
};
const runAddTelemetry = (
parent: unknown,
factoryType: string,
savedObject: SavedObjectCommon,
savedObjectMetaData?: SavedObjectMetaData
) => {
if (!apiHasType(parent)) return;
const type = savedObjectMetaData?.getSavedObjectSubType
? savedObjectMetaData.getSavedObjectSubType(savedObject)
: factoryType;
usageCollection?.reportUiCounter?.(parent.type, METRIC_TYPE.CLICK, `${type}:add`);
};
export const AddPanelFlyout = ({
container,
onAddPanel,
modalTitleId,
}: {
container: CanAddNewPanel;
onAddPanel?: (id: string) => void;
modalTitleId?: string;
}) => {
const legacyFactoriesBySavedObjectType: LegacyFactoryMap = useMemo(() => {
return [...embeddableStart.getEmbeddableFactories()]
.filter(
(embeddableFactory) =>
Boolean(embeddableFactory.savedObjectMetaData?.type) && !embeddableFactory.isContainerType
)
.reduce((acc, factory) => {
acc[factory.savedObjectMetaData!.type] = factory;
return acc;
}, {} as LegacyFactoryMap);
}, []);
const factoriesBySavedObjectType: FactoryMap = useMemo(() => {
return [...getReactEmbeddableSavedObjects()]
.filter(([type, embeddableFactory]) => {
return Boolean(embeddableFactory.savedObjectMetaData?.type);
})
.reduce((acc, [type, factory]) => {
acc[factory.savedObjectMetaData!.type] = {
...factory,
type,
};
return acc;
}, {} as FactoryMap);
}, []);
const metaData = useMemo(
() =>
[
...Object.values(factoriesBySavedObjectType),
...Object.values(legacyFactoriesBySavedObjectType),
]
.filter((embeddableFactory) => Boolean(embeddableFactory.savedObjectMetaData))
.map(({ savedObjectMetaData }) => savedObjectMetaData!)
.sort((a, b) => a.type.localeCompare(b.type)),
[factoriesBySavedObjectType, legacyFactoriesBySavedObjectType]
);
const onChoose: SavedObjectFinderProps['onChoose'] = useCallback(
async (
id: SavedObjectCommon['id'],
type: SavedObjectCommon['type'],
name: string,
savedObject: SavedObjectCommon
) => {
if (factoriesBySavedObjectType[type]) {
const factory = factoriesBySavedObjectType[type];
const { onAdd, savedObjectMetaData } = factory;
onAdd(container, savedObject);
runAddTelemetry(container, factory.type, savedObject, savedObjectMetaData);
return;
}
const legacyFactoryForSavedObjectType = legacyFactoriesBySavedObjectType[type];
if (!legacyFactoryForSavedObjectType) {
throw new EmbeddableFactoryNotFoundError(type);
}
// container.addNewEmbeddable is required for legacy embeddables to support
// panel placement strategies
if (!apiCanAddNewEmbeddable(container)) {
throw new PanelIncompatibleError();
}
let embeddableId: string;
if (savedObjectToPanel[type]) {
// this panel type has a custom method for converting saved objects to panels
const panel = savedObjectToPanel[type](savedObject);
const { id: _embeddableId } = await container.addNewEmbeddable(
legacyFactoryForSavedObjectType.type,
panel,
savedObject.attributes
);
embeddableId = _embeddableId;
} else {
const { id: _embeddableId } = await container.addNewEmbeddable(
legacyFactoryForSavedObjectType.type,
{ savedObjectId: id },
savedObject.attributes
);
embeddableId = _embeddableId;
}
onAddPanel?.(embeddableId);
showSuccessToast(name);
const { savedObjectMetaData, type: factoryType } = legacyFactoryForSavedObjectType;
runAddTelemetry(container, factoryType, savedObject, savedObjectMetaData);
},
[container, factoriesBySavedObjectType, legacyFactoriesBySavedObjectType, onAddPanel]
);
return (
<>
<EuiFlyoutHeader hasBorder>
<EuiTitle size="s">
<h2 id={modalTitleId}>
{i18n.translate('embeddableApi.addPanel.Title', { defaultMessage: 'Add from library' })}
</h2>
</EuiTitle>
</EuiFlyoutHeader>
<EuiFlyoutBody>
<SavedObjectFinder
id="embeddableAddPanel"
services={{
contentClient: contentManagement.client,
savedObjectsTagging: savedObjectsTaggingOss?.getTaggingApi(),
uiSettings: core.uiSettings,
}}
onChoose={onChoose}
savedObjectMetaData={metaData}
showFilter={true}
noItemsMessage={i18n.translate('embeddableApi.addPanel.noMatchingObjectsMessage', {
defaultMessage: 'No matching objects found.',
})}
getTooltipText={(item) => {
return item.managed
? i18n.translate('embeddableApi.addPanel.managedPanelTooltip', {
defaultMessage:
'Elastic manages this panel. Adding it to a dashboard unlinks it from the library.',
})
: undefined;
}}
/>
</EuiFlyoutBody>
</>
);
};

View file

@ -10,7 +10,8 @@
import { PluginInitializerContext } from '@kbn/core/public';
import { EmbeddablePublicPlugin } from './plugin';
export { openAddPanelFlyout } from './add_panel_flyout/open_add_panel_flyout';
export { useAddFromLibraryTypes } from './add_from_library/registry';
export { openAddFromLibraryFlyout } from './add_from_library/open_add_from_library_flyout';
export { EmbeddablePanel } from './embeddable_panel';
export {
cellValueTrigger,
@ -80,7 +81,6 @@ export type {
PanelState,
PropertySpec,
RangeSelectContext,
ReactEmbeddableSavedObject,
ReferenceOrValueEmbeddable,
SavedObjectEmbeddableInput,
SelfStyledEmbeddable,

View file

@ -1,14 +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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/
export {
type ReactEmbeddableSavedObject,
getReactEmbeddableSavedObjects,
registerReactEmbeddableSavedObject,
} from './embeddable_saved_object_registry';

View file

@ -17,4 +17,3 @@ export * from './reference_or_value_embeddable';
export * from './self_styled_embeddable';
export * from './filterable_embeddable';
export * from './factory_migrations/run_factory_migrations';
export * from './embeddable_saved_object_registry';

View file

@ -33,14 +33,13 @@ import {
SelfStyledEmbeddable,
} from '.';
import { setKibanaServices } from './kibana_services';
import { registerReactEmbeddableSavedObject } from './lib';
import { SelfStyledOptions } from './lib/self_styled_embeddable/types';
import { EmbeddablePublicPlugin } from './plugin';
import {
reactEmbeddableRegistryHasKey,
registerReactEmbeddableFactory,
} from './react_embeddable_system';
import { registerSavedObjectToPanelMethod } from './registry/saved_object_to_panel_methods';
import { registerAddFromLibraryType } from './add_from_library/registry';
export { mockAttributeService } from './lib/attribute_service/attribute_service.mock';
export type Setup = jest.Mocked<EmbeddableSetup>;
@ -100,12 +99,7 @@ export function mockFilterableEmbeddable<OriginalEmbeddableType>(
const createSetupContract = (): Setup => {
const setupContract: Setup = {
registerSavedObjectToPanelMethod: jest
.fn()
.mockImplementation(registerSavedObjectToPanelMethod),
registerReactEmbeddableSavedObject: jest
.fn()
.mockImplementation(registerReactEmbeddableSavedObject),
registerAddFromLibraryType: jest.fn().mockImplementation(registerAddFromLibraryType),
registerReactEmbeddableFactory: jest.fn().mockImplementation(registerReactEmbeddableFactory),
registerEmbeddableFactory: jest.fn(),
registerEnhancement: jest.fn(),
@ -117,7 +111,6 @@ const createSetupContract = (): Setup => {
const createStartContract = (): Start => {
const startContract: Start = {
reactEmbeddableRegistryHasKey: jest.fn().mockImplementation(reactEmbeddableRegistryHasKey),
getReactEmbeddableSavedObjects: jest.fn(),
getEmbeddableFactories: jest.fn(),
getEmbeddableFactory: jest.fn(),
telemetry: jest.fn(),

View file

@ -25,7 +25,6 @@ import { migrateToLatest, PersistableStateService } from '@kbn/kibana-utils-plug
import { SavedObjectsManagementPluginStart } from '@kbn/saved-objects-management-plugin/public';
import type { ContentManagementPublicStart } from '@kbn/content-management-plugin/public';
import type { SavedObjectTaggingOssPluginStart } from '@kbn/saved-objects-tagging-oss-plugin/public';
import { FinderAttributes } from '@kbn/saved-objects-finder-plugin/common';
import {
EmbeddableFactoryRegistry,
EmbeddableFactoryProvider,
@ -41,9 +40,6 @@ import {
defaultEmbeddableFactoryProvider,
IEmbeddable,
SavedObjectEmbeddableInput,
registerReactEmbeddableSavedObject,
ReactEmbeddableSavedObject,
getReactEmbeddableSavedObjects,
} from './lib';
import { EmbeddableFactoryDefinition } from './lib/embeddables/embeddable_factory_definition';
import { EmbeddableStateTransfer } from './lib/state_transfer';
@ -62,7 +58,7 @@ import {
reactEmbeddableRegistryHasKey,
registerReactEmbeddableFactory,
} from './react_embeddable_system';
import { registerSavedObjectToPanelMethod } from './registry/saved_object_to_panel_methods';
import { registerAddFromLibraryType } from './add_from_library/registry';
export interface EmbeddableSetupDependencies {
uiActions: UiActionsSetup;
@ -79,17 +75,16 @@ export interface EmbeddableStartDependencies {
export interface EmbeddableSetup {
/**
* Register an embeddable API saved object with the Add from library flyout.
* Register a saved object type with the "Add from library" flyout.
*
* @example
* registerReactEmbeddableSavedObject({
* registerAddFromLibraryType({
* onAdd: (container, savedObject) => {
* container.addNewPanel({
* panelType: CONTENT_ID,
* initialState: savedObject.attributes,
* });
* },
* embeddableType: CONTENT_ID,
* savedObjectType: MAP_SAVED_OBJECT_TYPE,
* savedObjectName: i18n.translate('xpack.maps.mapSavedObjectLabel', {
* defaultMessage: 'Map',
@ -97,12 +92,7 @@ export interface EmbeddableSetup {
* getIconForSavedObject: () => APP_ICON,
* });
*/
registerReactEmbeddableSavedObject: typeof registerReactEmbeddableSavedObject;
/**
* @deprecated React embeddables should register their saved objects with {@link registerReactEmbeddableSavedObject}.
*/
registerSavedObjectToPanelMethod: typeof registerSavedObjectToPanelMethod;
registerAddFromLibraryType: typeof registerAddFromLibraryType;
/**
* Registers an async {@link ReactEmbeddableFactory} getter.
@ -136,14 +126,6 @@ export interface EmbeddableStart extends PersistableStateService<EmbeddableState
*/
reactEmbeddableRegistryHasKey: (type: string) => boolean;
/**
*
* @returns An iterator over all {@link ReactEmbeddableSavedObject}s that have been registered using {@link registerReactEmbeddableSavedObject}.
*/
getReactEmbeddableSavedObjects: <
TSavedObjectAttributes extends FinderAttributes
>() => IterableIterator<[string, ReactEmbeddableSavedObject<TSavedObjectAttributes>]>;
/**
* @deprecated use {@link registerReactEmbeddableFactory} instead.
*/
@ -192,8 +174,7 @@ export class EmbeddablePublicPlugin implements Plugin<EmbeddableSetup, Embeddabl
return {
registerReactEmbeddableFactory,
registerSavedObjectToPanelMethod,
registerReactEmbeddableSavedObject,
registerAddFromLibraryType,
registerEmbeddableFactory: this.registerEmbeddableFactory,
registerEnhancement: this.registerEnhancement,
@ -244,7 +225,6 @@ export class EmbeddablePublicPlugin implements Plugin<EmbeddableSetup, Embeddabl
const embeddableStart: EmbeddableStart = {
reactEmbeddableRegistryHasKey,
getReactEmbeddableSavedObjects,
getEmbeddableFactory: this.getEmbeddableFactory,
getEmbeddableFactories: this.getEmbeddableFactories,

View file

@ -1,28 +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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/
import { SavedObjectCommon } from '@kbn/saved-objects-finder-plugin/common';
type SavedObjectToPanelMethod<TSavedObjectAttributes, TByValueInput> = (
// @ts-expect-error upgrade typescript v4.9.5
savedObject: SavedObjectCommon<TSavedObjectAttributes>
) => { savedObjectId: string } | Partial<TByValueInput>;
export const savedObjectToPanel: Record<string, SavedObjectToPanelMethod<any, any>> = {};
/**
* @deprecated
* React embeddables should register their saved object types with the registerReactEmbeddableSavedObject registry.
*/
export const registerSavedObjectToPanelMethod = <TSavedObjectAttributes, TByValueAttributes>(
savedObjectType: string,
method: SavedObjectToPanelMethod<TSavedObjectAttributes, TByValueAttributes>
) => {
savedObjectToPanel[savedObjectType] = method;
};

View file

@ -61,7 +61,7 @@ export class LinksPlugin
name: APP_NAME,
});
plugins.embeddable.registerReactEmbeddableSavedObject({
plugins.embeddable.registerAddFromLibraryType({
onAdd: async (container, savedObject) => {
const initialState = await deserializeLinksSavedObject(savedObject);
container.addNewPanel<LinksRuntimeState>({
@ -69,7 +69,6 @@ export class LinksPlugin
initialState,
});
},
embeddableType: CONTENT_ID,
savedObjectType: CONTENT_ID,
savedObjectName: APP_NAME,
getIconForSavedObject: () => APP_ICON,

View file

@ -411,14 +411,13 @@ export class VisualizationsPlugin
const getVisualizeEmbeddableFactory = await getVisualizeEmbeddableFactoryLazy();
return getVisualizeEmbeddableFactory({ embeddableStart, embeddableEnhancedStart });
});
embeddable.registerReactEmbeddableSavedObject<VisualizationSavedObjectAttributes>({
embeddable.registerAddFromLibraryType<VisualizationSavedObjectAttributes>({
onAdd: (container, savedObject) => {
container.addNewPanel<VisualizeSerializedState>({
panelType: VISUALIZE_EMBEDDABLE_TYPE,
initialState: { savedObjectId: savedObject.id },
});
},
embeddableType: VISUALIZE_EMBEDDABLE_TYPE,
savedObjectType: VISUALIZE_EMBEDDABLE_TYPE,
savedObjectName: i18n.translate('visualizations.visualizeSavedObjectName', {
defaultMessage: 'Visualization',

View file

@ -9,14 +9,9 @@ import { EuiFlyout, EuiFlyoutBody, EuiFlyoutHeader, EuiTitle } from '@elastic/eu
import { i18n } from '@kbn/i18n';
import React, { FC, useCallback, useMemo } from 'react';
import { EmbeddableFactory, ReactEmbeddableSavedObject } from '@kbn/embeddable-plugin/public';
import { FinderAttributes } from '@kbn/saved-objects-finder-plugin/common';
import { SavedObjectFinder, SavedObjectMetaData } from '@kbn/saved-objects-finder-plugin/public';
import {
contentManagementService,
coreServices,
embeddableService,
} from '../../services/kibana_services';
import { useAddFromLibraryTypes } from '@kbn/embeddable-plugin/public';
import { SavedObjectFinder } from '@kbn/saved-objects-finder-plugin/public';
import { contentManagementService, coreServices } from '../../services/kibana_services';
const strings = {
getNoItemsText: () =>
@ -29,13 +24,6 @@ const strings = {
}),
};
interface LegacyFactoryMap {
[key: string]: EmbeddableFactory;
}
interface FactoryMap<TSavedObjectAttributes extends FinderAttributes = FinderAttributes> {
[key: string]: ReactEmbeddableSavedObject<TSavedObjectAttributes> & { type: string };
}
export interface Props {
onClose: () => void;
onSelect: (id: string, embeddableType: string, isByValueEnabled?: boolean) => void;
@ -49,81 +37,18 @@ export const AddEmbeddableFlyout: FC<Props> = ({
onClose,
isByValueEnabled,
}) => {
const legacyFactoriesBySavedObjectType: LegacyFactoryMap = useMemo(() => {
return [...embeddableService.getEmbeddableFactories()]
.filter(
(embeddableFactory) =>
Boolean(embeddableFactory.savedObjectMetaData?.type) && !embeddableFactory.isContainerType
)
.reduce((acc, factory) => {
acc[factory.savedObjectMetaData!.type] = factory;
return acc;
}, {} as LegacyFactoryMap);
}, []);
const libraryTypes = useAddFromLibraryTypes();
const factoriesBySavedObjectType: FactoryMap = useMemo(() => {
return [...embeddableService.getReactEmbeddableSavedObjects()]
.filter(([type, embeddableFactory]) => {
return Boolean(embeddableFactory.savedObjectMetaData?.type);
})
.reduce((acc, [type, factory]) => {
acc[factory.savedObjectMetaData!.type] = {
...factory,
type,
};
return acc;
}, {} as FactoryMap);
}, []);
const metaData = useMemo(
() =>
[
...Object.values(factoriesBySavedObjectType),
...Object.values(legacyFactoriesBySavedObjectType),
]
.filter((factory) =>
Boolean(
factory.type !== 'links' && // Links panels only exist on Dashboards
(isByValueEnabled || availableEmbeddables.includes(factory.type))
)
)
.map((factory) => factory.savedObjectMetaData)
.filter<SavedObjectMetaData<{}>>(function (
maybeSavedObjectMetaData
): maybeSavedObjectMetaData is SavedObjectMetaData<{}> {
return maybeSavedObjectMetaData !== undefined;
})
.sort((a, b) => a.type.localeCompare(b.type)),
[
availableEmbeddables,
factoriesBySavedObjectType,
isByValueEnabled,
legacyFactoriesBySavedObjectType,
]
);
const canvasOnlyLibraryTypes = useMemo(() => {
// Links panels are not supported in Canvas
return libraryTypes.filter(({ type }) => type !== 'links');
}, [libraryTypes]);
const onAddPanel = useCallback(
(id: string, savedObjectType: string) => {
if (factoriesBySavedObjectType[savedObjectType]) {
const factory = factoriesBySavedObjectType[savedObjectType];
const { type } = factory;
onSelect(id, type, isByValueEnabled);
return;
}
const embeddableFactories = embeddableService.getEmbeddableFactories();
// Find the embeddable type from the saved object type
const found = Array.from(embeddableFactories).find((embeddableFactory) => {
return Boolean(
embeddableFactory.savedObjectMetaData &&
embeddableFactory.savedObjectMetaData.type === savedObjectType
);
});
const foundEmbeddableType = found ? found.type : 'unknown';
onSelect(id, foundEmbeddableType, isByValueEnabled);
onSelect(id, savedObjectType, isByValueEnabled);
},
[isByValueEnabled, onSelect, factoriesBySavedObjectType]
[isByValueEnabled, onSelect]
);
return (
@ -137,7 +62,7 @@ export const AddEmbeddableFlyout: FC<Props> = ({
<SavedObjectFinder
id="canvasEmbeddableFlyout"
onChoose={onAddPanel}
savedObjectMetaData={metaData}
savedObjectMetaData={canvasOnlyLibraryTypes}
showFilter={true}
noItemsMessage={strings.getNoItemsText()}
services={{

View file

@ -395,7 +395,7 @@ export class LensPlugin {
});
// Let Dashboard know about the Lens panel type
embeddable.registerReactEmbeddableSavedObject<LensSavedObjectAttributes>({
embeddable.registerAddFromLibraryType<LensSavedObjectAttributes>({
onAdd: async (container, savedObject) => {
const { attributeService } = await getStartServicesForEmbeddable();
// deserialize the saved object from visualize library
@ -411,7 +411,6 @@ export class LensPlugin {
initialState: state,
});
},
embeddableType: LENS_EMBEDDABLE_TYPE,
savedObjectType: LENS_EMBEDDABLE_TYPE,
savedObjectName: i18n.translate('xpack.lens.mapSavedObjectLabel', {
defaultMessage: 'Lens',

View file

@ -22,14 +22,13 @@ export function setupMapEmbeddable(embeddableSetup: EmbeddableSetup) {
return mapEmbeddableFactory;
});
embeddableSetup.registerReactEmbeddableSavedObject<MapAttributes>({
embeddableSetup.registerAddFromLibraryType<MapAttributes>({
onAdd: (container, savedObject) => {
container.addNewPanel({
panelType: MAP_SAVED_OBJECT_TYPE,
initialState: { savedObjectId: savedObject.id },
});
},
embeddableType: MAP_SAVED_OBJECT_TYPE,
savedObjectType: MAP_SAVED_OBJECT_TYPE,
savedObjectName: i18n.translate('xpack.maps.mapSavedObjectLabel', {
defaultMessage: 'Map',

View file

@ -7,7 +7,7 @@
import type { OverlayRef } from '@kbn/core/public';
import { v4 } from 'uuid';
import { openAddPanelFlyout } from '@kbn/embeddable-plugin/public';
import { openAddFromLibraryFlyout } from '@kbn/embeddable-plugin/public';
import { i18n } from '@kbn/i18n';
import type { FinderAttributes } from '@kbn/saved-objects-finder-plugin/common';
import React, { useMemo, useRef } from 'react';
@ -107,7 +107,7 @@ export function AddFromLibraryButton({ onItemAdd }: AddFromLibraryButtonProps) {
data-test-subj="investigateAppAddFromLibraryButtonImportFromLibraryButton"
iconType="importAction"
onClick={() => {
panelRef.current = openAddPanelFlyout({
panelRef.current = openAddFromLibraryFlyout({
container,
});

View file

@ -2814,7 +2814,6 @@
"domDragDrop.keyboardInstructionsReorder": "Appuyez sur la barre d'espace ou sur Entrée pour commencer à faire glisser. Lors du glissement, utilisez les touches fléchées haut/bas pour réorganiser les éléments dans le groupe et les touches gauche/droite pour choisir les cibles de dépôt à l'extérieur du groupe. Appuyez à nouveau sur la barre d'espace ou sur Entrée pour terminer.",
"embeddableApi.addPanel.managedPanelTooltip": "Elastic gère ce panneau. Le fait de l'ajouter à un tableau de bord le dissocie de la bibliothèque.",
"embeddableApi.addPanel.noMatchingObjectsMessage": "Aucun objet correspondant trouvé.",
"embeddableApi.addPanel.savedObjectAddedToContainerSuccessMessageTitle": "{savedObjectName} a été ajouté.",
"embeddableApi.addPanel.Title": "Ajouter depuis la bibliothèque",
"embeddableApi.attributeService.saveToLibraryError": "Une erreur s'est produite lors de l'enregistrement. Erreur : {errorMessage}.",
"embeddableApi.cellValueTrigger.description": "Les actions apparaissent dans les options de valeur de cellule dans la visualisation",
@ -2825,7 +2824,6 @@
"embeddableApi.compatibility.defaultTypeDisplayName": "graphique",
"embeddableApi.contextMenuTrigger.description": "Une nouvelle action sera ajoutée au menu contextuel du panneau",
"embeddableApi.contextMenuTrigger.title": "Menu contextuel",
"embeddableApi.embeddableSavedObjectRegistry.keyAlreadyExistsError": "Le type incorporable {embeddableType} existe déjà dans le registre.",
"embeddableApi.errors.embeddableFactoryNotFound": "Impossible de charger {type}. Veuillez effectuer une mise à niveau vers la distribution par défaut d'Elasticsearch et de Kibana avec la licence appropriée.",
"embeddableApi.errors.paneldoesNotExist": "Panneau introuvable",
"embeddableApi.errors.panelIncompatibleError": "L'API du panneau n'est pas compatible",

View file

@ -2809,7 +2809,6 @@
"domDragDrop.keyboardInstructionsReorder": "スペースまたはEnterを押してドラッグを開始します。ドラッグするときには、上下矢印キーを使用すると、グループの項目を並べ替えます。左右矢印キーを使用すると、グループの外側でドロップ対象を選択します。もう一度スペースまたはEnterを押すと終了します。",
"embeddableApi.addPanel.managedPanelTooltip": "Elasticはこのパネルを管理します。ダッシュボードに追加すると、ライブラリからリンクが解除されます。",
"embeddableApi.addPanel.noMatchingObjectsMessage": "一致するオブジェクトが見つかりませんでした。",
"embeddableApi.addPanel.savedObjectAddedToContainerSuccessMessageTitle": "{savedObjectName} が追加されました",
"embeddableApi.addPanel.Title": "ライブラリから追加",
"embeddableApi.attributeService.saveToLibraryError": "保存中にエラーが発生しました。エラー:{errorMessage}",
"embeddableApi.cellValueTrigger.description": "アクションはビジュアライゼーションのセル値オプションに表示されます",
@ -2820,7 +2819,6 @@
"embeddableApi.compatibility.defaultTypeDisplayName": "チャート",
"embeddableApi.contextMenuTrigger.description": "新しいアクションがパネルのコンテキストメニューに追加されます",
"embeddableApi.contextMenuTrigger.title": "コンテキストメニュー",
"embeddableApi.embeddableSavedObjectRegistry.keyAlreadyExistsError": "埋め込み可能タイプ\"{embeddableType}\"はすでにレジストリに存在します。",
"embeddableApi.errors.embeddableFactoryNotFound": "{type} を読み込めません。Elasticsearch と Kibanaのデフォルトのディストリビューションを適切なライセンスでアップグレードしてください。",
"embeddableApi.errors.paneldoesNotExist": "パネルが見つかりません",
"embeddableApi.errors.panelIncompatibleError": "パネルAPIに互換性がありません",

View file

@ -2799,7 +2799,6 @@
"domDragDrop.keyboardInstructionsReorder": "按空格键或 enter 键开始拖动。拖动时,请使用上下箭头键重新排列组中的项目,使用左右箭头键在组之外选择拖动目标。再次按空格键或 enter 键结束操作。",
"embeddableApi.addPanel.managedPanelTooltip": "Elastic 将管理此面板。将其添加到仪表板会取消其与库的链接。",
"embeddableApi.addPanel.noMatchingObjectsMessage": "未找到任何匹配对象。",
"embeddableApi.addPanel.savedObjectAddedToContainerSuccessMessageTitle": "{savedObjectName} 已添加",
"embeddableApi.addPanel.Title": "从库中添加",
"embeddableApi.attributeService.saveToLibraryError": "保存时出错。错误:{errorMessage}",
"embeddableApi.cellValueTrigger.description": "操作在可视化上的单元格值选项中显示",
@ -2810,7 +2809,6 @@
"embeddableApi.compatibility.defaultTypeDisplayName": "图表",
"embeddableApi.contextMenuTrigger.description": "会将一个新操作添加到该面板的上下文菜单",
"embeddableApi.contextMenuTrigger.title": "上下文菜单",
"embeddableApi.embeddableSavedObjectRegistry.keyAlreadyExistsError": "注册表中已存在可嵌入对象类型 {embeddableType}。",
"embeddableApi.errors.embeddableFactoryNotFound": "{type} 无法加载。请升级到具有适当许可的默认 Elasticsearch 和 Kibana 分发。",
"embeddableApi.errors.paneldoesNotExist": "未找到面板",
"embeddableApi.errors.panelIncompatibleError": "面板 API 不兼容",