mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 09:19:04 -04:00
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Marco Liberati <dej611@users.noreply.github.com>
This commit is contained in:
parent
8bc10abb95
commit
4ee24294a7
17 changed files with 333 additions and 100 deletions
|
@ -36,6 +36,7 @@
|
|||
| [isSavedObjectEmbeddableInput(input)](./kibana-plugin-plugins-embeddable-public.issavedobjectembeddableinput.md) | |
|
||||
| [openAddPanelFlyout(options)](./kibana-plugin-plugins-embeddable-public.openaddpanelflyout.md) | |
|
||||
| [plugin(initializerContext)](./kibana-plugin-plugins-embeddable-public.plugin.md) | |
|
||||
| [useEmbeddableFactory({ input, factory, onInputUpdated, })](./kibana-plugin-plugins-embeddable-public.useembeddablefactory.md) | |
|
||||
|
||||
## Interfaces
|
||||
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-plugins-embeddable-public](./kibana-plugin-plugins-embeddable-public.md) > [useEmbeddableFactory](./kibana-plugin-plugins-embeddable-public.useembeddablefactory.md)
|
||||
|
||||
## useEmbeddableFactory() function
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
export declare function useEmbeddableFactory<I extends EmbeddableInput>({ input, factory, onInputUpdated, }: EmbeddableRendererWithFactory<I>): readonly [ErrorEmbeddable | IEmbeddable<I, import("./i_embeddable").EmbeddableOutput> | undefined, boolean, string | undefined];
|
||||
```
|
||||
|
||||
## Parameters
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| { input, factory, onInputUpdated, } | <code>EmbeddableRendererWithFactory<I></code> | |
|
||||
|
||||
<b>Returns:</b>
|
||||
|
||||
`readonly [ErrorEmbeddable | IEmbeddable<I, import("./i_embeddable").EmbeddableOutput> | undefined, boolean, string | undefined]`
|
||||
|
|
@ -69,6 +69,7 @@ export {
|
|||
EmbeddablePackageState,
|
||||
EmbeddableRenderer,
|
||||
EmbeddableRendererProps,
|
||||
useEmbeddableFactory,
|
||||
} from './lib';
|
||||
|
||||
export { AttributeService, ATTRIBUTE_SERVICE_KEY } from './lib/attribute_service';
|
||||
|
|
|
@ -9,14 +9,39 @@
|
|||
import React from 'react';
|
||||
import { waitFor } from '@testing-library/dom';
|
||||
import { render } from '@testing-library/react';
|
||||
import { renderHook } from '@testing-library/react-hooks';
|
||||
import {
|
||||
HelloWorldEmbeddable,
|
||||
HelloWorldEmbeddableFactoryDefinition,
|
||||
HELLO_WORLD_EMBEDDABLE,
|
||||
} from '../../tests/fixtures';
|
||||
import { EmbeddableRenderer } from './embeddable_renderer';
|
||||
import { EmbeddableRenderer, useEmbeddableFactory } from './embeddable_renderer';
|
||||
import { embeddablePluginMock } from '../../mocks';
|
||||
|
||||
describe('useEmbeddableFactory', () => {
|
||||
it('should update upstream value changes', async () => {
|
||||
const { setup, doStart } = embeddablePluginMock.createInstance();
|
||||
const getFactory = setup.registerEmbeddableFactory(
|
||||
HELLO_WORLD_EMBEDDABLE,
|
||||
new HelloWorldEmbeddableFactoryDefinition()
|
||||
);
|
||||
doStart();
|
||||
|
||||
const { result, waitForNextUpdate } = renderHook(() =>
|
||||
useEmbeddableFactory({ factory: getFactory(), input: { id: 'hello' } })
|
||||
);
|
||||
|
||||
const [, loading] = result.current;
|
||||
|
||||
expect(loading).toBe(true);
|
||||
|
||||
await waitForNextUpdate();
|
||||
|
||||
const [embeddable] = result.current;
|
||||
expect(embeddable).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('<EmbeddableRenderer/>', () => {
|
||||
test('Render embeddable', () => {
|
||||
const embeddable = new HelloWorldEmbeddable({ id: 'hello' });
|
||||
|
|
|
@ -28,12 +28,6 @@ interface EmbeddableRendererPropsWithEmbeddable<I extends EmbeddableInput> {
|
|||
embeddable: IEmbeddable<I>;
|
||||
}
|
||||
|
||||
function isWithEmbeddable<I extends EmbeddableInput>(
|
||||
props: EmbeddableRendererProps<I>
|
||||
): props is EmbeddableRendererPropsWithEmbeddable<I> {
|
||||
return 'embeddable' in props;
|
||||
}
|
||||
|
||||
interface EmbeddableRendererWithFactory<I extends EmbeddableInput> {
|
||||
input: I;
|
||||
onInputUpdated?: (newInput: I) => void;
|
||||
|
@ -46,6 +40,72 @@ function isWithFactory<I extends EmbeddableInput>(
|
|||
return 'factory' in props;
|
||||
}
|
||||
|
||||
export function useEmbeddableFactory<I extends EmbeddableInput>({
|
||||
input,
|
||||
factory,
|
||||
onInputUpdated,
|
||||
}: EmbeddableRendererWithFactory<I>) {
|
||||
const [embeddable, setEmbeddable] = useState<IEmbeddable<I> | ErrorEmbeddable | undefined>(
|
||||
undefined
|
||||
);
|
||||
const [loading, setLoading] = useState<boolean>(false);
|
||||
const [error, setError] = useState<string | undefined>();
|
||||
const latestInput = React.useRef(input);
|
||||
useEffect(() => {
|
||||
latestInput.current = input;
|
||||
}, [input]);
|
||||
|
||||
useEffect(() => {
|
||||
let canceled = false;
|
||||
|
||||
// keeping track of embeddables created by this component to be able to destroy them
|
||||
let createdEmbeddableRef: IEmbeddable | ErrorEmbeddable | undefined;
|
||||
setEmbeddable(undefined);
|
||||
setLoading(true);
|
||||
factory
|
||||
.create(latestInput.current!)
|
||||
.then((createdEmbeddable) => {
|
||||
if (canceled) {
|
||||
if (createdEmbeddable) {
|
||||
createdEmbeddable.destroy();
|
||||
}
|
||||
} else {
|
||||
createdEmbeddableRef = createdEmbeddable;
|
||||
setEmbeddable(createdEmbeddable);
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
if (canceled) return;
|
||||
setError(err?.message);
|
||||
})
|
||||
.finally(() => {
|
||||
if (canceled) return;
|
||||
setLoading(false);
|
||||
});
|
||||
|
||||
return () => {
|
||||
canceled = true;
|
||||
if (createdEmbeddableRef) {
|
||||
createdEmbeddableRef.destroy();
|
||||
}
|
||||
};
|
||||
}, [factory]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!embeddable) return;
|
||||
if (isErrorEmbeddable(embeddable)) return;
|
||||
if (!onInputUpdated) return;
|
||||
const sub = embeddable.getInput$().subscribe((newInput) => {
|
||||
onInputUpdated(newInput);
|
||||
});
|
||||
return () => {
|
||||
sub.unsubscribe();
|
||||
};
|
||||
}, [embeddable, onInputUpdated]);
|
||||
|
||||
return [embeddable, loading, error] as const;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper react component to render an embeddable
|
||||
* Can be used if you have an embeddable object or an embeddable factory
|
||||
|
@ -82,72 +142,22 @@ function isWithFactory<I extends EmbeddableInput>(
|
|||
export const EmbeddableRenderer = <I extends EmbeddableInput>(
|
||||
props: EmbeddableRendererProps<I>
|
||||
) => {
|
||||
const { input, onInputUpdated } = props;
|
||||
const [embeddable, setEmbeddable] = useState<IEmbeddable<I> | ErrorEmbeddable | undefined>(
|
||||
isWithEmbeddable(props) ? props.embeddable : undefined
|
||||
);
|
||||
const [loading, setLoading] = useState<boolean>(!isWithEmbeddable(props));
|
||||
const [error, setError] = useState<string | undefined>();
|
||||
const latestInput = React.useRef(props.input);
|
||||
useEffect(() => {
|
||||
latestInput.current = input;
|
||||
}, [input]);
|
||||
|
||||
const factoryFromProps = isWithFactory(props) ? props.factory : undefined;
|
||||
const embeddableFromProps = isWithEmbeddable(props) ? props.embeddable : undefined;
|
||||
useEffect(() => {
|
||||
let canceled = false;
|
||||
if (embeddableFromProps) {
|
||||
setEmbeddable(embeddableFromProps);
|
||||
return;
|
||||
}
|
||||
|
||||
// keeping track of embeddables created by this component to be able to destroy them
|
||||
let createdEmbeddableRef: IEmbeddable | ErrorEmbeddable | undefined;
|
||||
if (factoryFromProps) {
|
||||
setEmbeddable(undefined);
|
||||
setLoading(true);
|
||||
factoryFromProps
|
||||
.create(latestInput.current!)
|
||||
.then((createdEmbeddable) => {
|
||||
if (canceled) {
|
||||
if (createdEmbeddable) {
|
||||
createdEmbeddable.destroy();
|
||||
}
|
||||
} else {
|
||||
createdEmbeddableRef = createdEmbeddable;
|
||||
setEmbeddable(createdEmbeddable);
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
if (canceled) return;
|
||||
setError(err?.message);
|
||||
})
|
||||
.finally(() => {
|
||||
if (canceled) return;
|
||||
setLoading(false);
|
||||
});
|
||||
}
|
||||
|
||||
return () => {
|
||||
canceled = true;
|
||||
if (createdEmbeddableRef) {
|
||||
createdEmbeddableRef.destroy();
|
||||
}
|
||||
};
|
||||
}, [factoryFromProps, embeddableFromProps]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!embeddable) return;
|
||||
if (isErrorEmbeddable(embeddable)) return;
|
||||
if (!onInputUpdated) return;
|
||||
const sub = embeddable.getInput$().subscribe((newInput) => {
|
||||
onInputUpdated(newInput);
|
||||
});
|
||||
return () => {
|
||||
sub.unsubscribe();
|
||||
};
|
||||
}, [embeddable, onInputUpdated]);
|
||||
if (isWithFactory(props)) {
|
||||
return <EmbeddableByFactory {...props} />;
|
||||
}
|
||||
return <EmbeddableRoot embeddable={props.embeddable} input={props.input} />;
|
||||
};
|
||||
|
||||
//
|
||||
const EmbeddableByFactory = <I extends EmbeddableInput>({
|
||||
factory,
|
||||
input,
|
||||
onInputUpdated,
|
||||
}: EmbeddableRendererWithFactory<I>) => {
|
||||
const [embeddable, loading, error] = useEmbeddableFactory({
|
||||
factory,
|
||||
input,
|
||||
onInputUpdated,
|
||||
});
|
||||
return <EmbeddableRoot embeddable={embeddable} loading={loading} error={error} input={input} />;
|
||||
};
|
||||
|
|
|
@ -16,4 +16,8 @@ export { ErrorEmbeddable, isErrorEmbeddable } from './error_embeddable';
|
|||
export { withEmbeddableSubscription } from './with_subscription';
|
||||
export { EmbeddableRoot } from './embeddable_root';
|
||||
export * from '../../../common/lib/saved_object_embeddable';
|
||||
export { EmbeddableRenderer, EmbeddableRendererProps } from './embeddable_renderer';
|
||||
export {
|
||||
EmbeddableRenderer,
|
||||
EmbeddableRendererProps,
|
||||
useEmbeddableFactory,
|
||||
} from './embeddable_renderer';
|
||||
|
|
|
@ -542,3 +542,40 @@ test('Check when hide header option is true', async () => {
|
|||
const title = findTestSubject(component, `embeddablePanelHeading-HelloAryaStark`);
|
||||
expect(title.length).toBe(0);
|
||||
});
|
||||
|
||||
test('Should work in minimal way rendering only the inspector action', async () => {
|
||||
const inspector = inspectorPluginMock.createStartContract();
|
||||
inspector.isAvailable = jest.fn(() => true);
|
||||
|
||||
const container = new HelloWorldContainer({ id: '123', panels: {}, viewMode: ViewMode.VIEW }, {
|
||||
getEmbeddableFactory,
|
||||
} as any);
|
||||
|
||||
const embeddable = await container.addNewEmbeddable<
|
||||
ContactCardEmbeddableInput,
|
||||
ContactCardEmbeddableOutput,
|
||||
ContactCardEmbeddable
|
||||
>(CONTACT_CARD_EMBEDDABLE, {
|
||||
firstName: 'Arya',
|
||||
lastName: 'Stark',
|
||||
});
|
||||
|
||||
const component = mount(
|
||||
<I18nProvider>
|
||||
<EmbeddablePanel
|
||||
embeddable={embeddable}
|
||||
getActions={() => Promise.resolve([])}
|
||||
inspector={inspector}
|
||||
hideHeader={false}
|
||||
/>
|
||||
</I18nProvider>
|
||||
);
|
||||
|
||||
findTestSubject(component, 'embeddablePanelToggleMenuIcon').simulate('click');
|
||||
expect(findTestSubject(component, `embeddablePanelContextMenuOpen`).length).toBe(1);
|
||||
await nextTick();
|
||||
component.update();
|
||||
expect(findTestSubject(component, `embeddablePanelAction-openInspector`).length).toBe(1);
|
||||
const action = findTestSubject(component, `embeddablePanelAction-ACTION_CUSTOMIZE_PANEL`);
|
||||
expect(action.length).toBe(0);
|
||||
});
|
||||
|
|
|
@ -54,16 +54,20 @@ const removeById = (disabledActions: string[]) => ({ id }: { id: string }) =>
|
|||
interface Props {
|
||||
embeddable: IEmbeddable<EmbeddableInput, EmbeddableOutput>;
|
||||
getActions: UiActionsService['getTriggerCompatibleActions'];
|
||||
getEmbeddableFactory: EmbeddableStart['getEmbeddableFactory'];
|
||||
getAllEmbeddableFactories: EmbeddableStart['getEmbeddableFactories'];
|
||||
overlays: CoreStart['overlays'];
|
||||
notifications: CoreStart['notifications'];
|
||||
application: CoreStart['application'];
|
||||
inspector: InspectorStartContract;
|
||||
SavedObjectFinder: React.ComponentType<any>;
|
||||
getEmbeddableFactory?: EmbeddableStart['getEmbeddableFactory'];
|
||||
getAllEmbeddableFactories?: EmbeddableStart['getEmbeddableFactories'];
|
||||
overlays?: CoreStart['overlays'];
|
||||
notifications?: CoreStart['notifications'];
|
||||
application?: CoreStart['application'];
|
||||
inspector?: InspectorStartContract;
|
||||
SavedObjectFinder?: React.ComponentType<any>;
|
||||
stateTransfer?: EmbeddableStateTransfer;
|
||||
hideHeader?: boolean;
|
||||
actionPredicate?: (actionId: string) => boolean;
|
||||
reportUiCounter?: UsageCollectionStart['reportUiCounter'];
|
||||
showShadow?: boolean;
|
||||
showBadges?: boolean;
|
||||
showNotifications?: boolean;
|
||||
}
|
||||
|
||||
interface State {
|
||||
|
@ -80,7 +84,11 @@ interface State {
|
|||
errorEmbeddable?: ErrorEmbeddable;
|
||||
}
|
||||
|
||||
interface PanelUniversalActions {
|
||||
interface InspectorPanelAction {
|
||||
inspectPanel: InspectPanelAction;
|
||||
}
|
||||
|
||||
interface BasePanelActions {
|
||||
customizePanelTitle: CustomizePanelTitleAction;
|
||||
addPanel: AddPanelAction;
|
||||
inspectPanel: InspectPanelAction;
|
||||
|
@ -88,6 +96,15 @@ interface PanelUniversalActions {
|
|||
editPanel: EditPanelAction;
|
||||
}
|
||||
|
||||
const emptyObject = {};
|
||||
type EmptyObject = typeof emptyObject;
|
||||
|
||||
type PanelUniversalActions =
|
||||
| BasePanelActions
|
||||
| InspectorPanelAction
|
||||
| (BasePanelActions & InspectorPanelAction)
|
||||
| EmptyObject;
|
||||
|
||||
export class EmbeddablePanel extends React.Component<Props, State> {
|
||||
private embeddableRoot: React.RefObject<HTMLDivElement>;
|
||||
private parentSubscription?: Subscription;
|
||||
|
@ -117,10 +134,15 @@ export class EmbeddablePanel extends React.Component<Props, State> {
|
|||
}
|
||||
|
||||
private async refreshBadges() {
|
||||
if (!this.mounted) {
|
||||
return;
|
||||
}
|
||||
if (this.props.showBadges === false) {
|
||||
return;
|
||||
}
|
||||
let badges = await this.props.getActions(PANEL_BADGE_TRIGGER, {
|
||||
embeddable: this.props.embeddable,
|
||||
});
|
||||
if (!this.mounted) return;
|
||||
|
||||
const { disabledActions } = this.props.embeddable.getInput();
|
||||
if (disabledActions) {
|
||||
|
@ -135,10 +157,15 @@ export class EmbeddablePanel extends React.Component<Props, State> {
|
|||
}
|
||||
|
||||
private async refreshNotifications() {
|
||||
if (!this.mounted) {
|
||||
return;
|
||||
}
|
||||
if (this.props.showNotifications === false) {
|
||||
return;
|
||||
}
|
||||
let notifications = await this.props.getActions(PANEL_NOTIFICATION_TRIGGER, {
|
||||
embeddable: this.props.embeddable,
|
||||
});
|
||||
if (!this.mounted) return;
|
||||
|
||||
const { disabledActions } = this.props.embeddable.getInput();
|
||||
if (disabledActions) {
|
||||
|
@ -229,13 +256,18 @@ export class EmbeddablePanel extends React.Component<Props, State> {
|
|||
paddingSize="none"
|
||||
role="figure"
|
||||
aria-labelledby={headerId}
|
||||
hasShadow={this.props.showShadow}
|
||||
>
|
||||
{!this.props.hideHeader && (
|
||||
<PanelHeader
|
||||
getActionContextMenuPanel={this.getActionContextMenuPanel}
|
||||
hidePanelTitle={this.state.hidePanelTitle}
|
||||
isViewMode={viewOnlyMode}
|
||||
customizeTitle={this.state.universalActions.customizePanelTitle}
|
||||
customizeTitle={
|
||||
'customizePanelTitle' in this.state.universalActions
|
||||
? this.state.universalActions.customizePanelTitle
|
||||
: undefined
|
||||
}
|
||||
closeContextMenu={this.state.closeContextMenu}
|
||||
title={title}
|
||||
badges={this.state.badges}
|
||||
|
@ -284,6 +316,23 @@ export class EmbeddablePanel extends React.Component<Props, State> {
|
|||
};
|
||||
|
||||
private getUniversalActions = (): PanelUniversalActions => {
|
||||
let actions = {};
|
||||
if (this.props.inspector) {
|
||||
actions = {
|
||||
inspectPanel: new InspectPanelAction(this.props.inspector),
|
||||
};
|
||||
}
|
||||
if (
|
||||
!this.props.getEmbeddableFactory ||
|
||||
!this.props.getAllEmbeddableFactories ||
|
||||
!this.props.overlays ||
|
||||
!this.props.notifications ||
|
||||
!this.props.SavedObjectFinder ||
|
||||
!this.props.application
|
||||
) {
|
||||
return actions;
|
||||
}
|
||||
|
||||
const createGetUserData = (overlays: OverlayStart) =>
|
||||
async function getUserData(context: { embeddable: IEmbeddable }) {
|
||||
return new Promise<{ title: string | undefined; hideTitle?: boolean }>((resolve) => {
|
||||
|
@ -308,6 +357,7 @@ export class EmbeddablePanel extends React.Component<Props, State> {
|
|||
// Universal actions are exposed on the context menu for every embeddable, they bypass the trigger
|
||||
// registry.
|
||||
return {
|
||||
...actions,
|
||||
customizePanelTitle: new CustomizePanelTitleAction(createGetUserData(this.props.overlays)),
|
||||
addPanel: new AddPanelAction(
|
||||
this.props.getEmbeddableFactory,
|
||||
|
@ -317,7 +367,6 @@ export class EmbeddablePanel extends React.Component<Props, State> {
|
|||
this.props.SavedObjectFinder,
|
||||
this.props.reportUiCounter
|
||||
),
|
||||
inspectPanel: new InspectPanelAction(this.props.inspector),
|
||||
removePanel: new RemovePanelAction(),
|
||||
editPanel: new EditPanelAction(
|
||||
this.props.getEmbeddableFactory,
|
||||
|
@ -338,9 +387,13 @@ export class EmbeddablePanel extends React.Component<Props, State> {
|
|||
regularActions = regularActions.filter(removeDisabledActions);
|
||||
}
|
||||
|
||||
const sortedActions = [...regularActions, ...Object.values(this.state.universalActions)].sort(
|
||||
sortByOrderField
|
||||
);
|
||||
let sortedActions = regularActions
|
||||
.concat(Object.values(this.state.universalActions || {}) as Array<Action<object>>)
|
||||
.sort(sortByOrderField);
|
||||
|
||||
if (this.props.actionPredicate) {
|
||||
sortedActions = sortedActions.filter(({ id }) => this.props.actionPredicate!(id));
|
||||
}
|
||||
|
||||
return await buildContextMenuForActions({
|
||||
actions: sortedActions.map((action) => ({
|
||||
|
|
|
@ -36,7 +36,7 @@ export interface PanelHeaderProps {
|
|||
embeddable: IEmbeddable;
|
||||
headerId?: string;
|
||||
showPlaceholderTitle?: boolean;
|
||||
customizeTitle: CustomizePanelTitleAction;
|
||||
customizeTitle?: CustomizePanelTitleAction;
|
||||
}
|
||||
|
||||
function renderBadges(badges: Array<Action<EmbeddableContext>>, embeddable: IEmbeddable) {
|
||||
|
@ -177,7 +177,7 @@ export function PanelHeader({
|
|||
>
|
||||
{title || placeholderTitle}
|
||||
</span>
|
||||
) : (
|
||||
) : customizeTitle ? (
|
||||
<EuiLink
|
||||
color="text"
|
||||
data-test-subj={'embeddablePanelTitleLink'}
|
||||
|
@ -193,7 +193,7 @@ export function PanelHeader({
|
|||
>
|
||||
{title || placeholderTitle}
|
||||
</EuiLink>
|
||||
);
|
||||
) : null;
|
||||
}
|
||||
return description ? (
|
||||
<EuiToolTip
|
||||
|
|
|
@ -843,6 +843,11 @@ export interface SavedObjectEmbeddableInput extends EmbeddableInput {
|
|||
// @public (undocumented)
|
||||
export const SELECT_RANGE_TRIGGER = "SELECT_RANGE_TRIGGER";
|
||||
|
||||
// Warning: (ae-missing-release-tag) "useEmbeddableFactory" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
export function useEmbeddableFactory<I extends EmbeddableInput>({ input, factory, onInputUpdated, }: EmbeddableRendererWithFactory<I>): readonly [ErrorEmbeddable | IEmbeddable<I, import("./i_embeddable").EmbeddableOutput> | undefined, boolean, string | undefined];
|
||||
|
||||
// Warning: (ae-missing-release-tag) "VALUE_CLICK_TRIGGER" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
"requiredPlugins": [
|
||||
"lens",
|
||||
"data",
|
||||
"embeddable",
|
||||
"developerExamples"
|
||||
],
|
||||
"optionalPlugins": [],
|
||||
|
|
|
@ -20,6 +20,7 @@ import {
|
|||
} from '@elastic/eui';
|
||||
import { IndexPattern } from 'src/plugins/data/public';
|
||||
import { CoreStart } from 'kibana/public';
|
||||
import { ViewMode } from '../../../../src/plugins/embeddable/public';
|
||||
import {
|
||||
TypedLensByValueInput,
|
||||
PersistedIndexPatternLayer,
|
||||
|
@ -193,6 +194,7 @@ export const App = (props: {
|
|||
</EuiFlexGroup>
|
||||
<LensComponent
|
||||
id=""
|
||||
withActions
|
||||
style={{ height: 500 }}
|
||||
timeRange={time}
|
||||
attributes={getLensAttributes(props.defaultIndexPattern, color)}
|
||||
|
@ -211,6 +213,7 @@ export const App = (props: {
|
|||
onTableRowClick={(_data) => {
|
||||
// call back event for on table row click event
|
||||
}}
|
||||
viewMode={ViewMode.VIEW}
|
||||
/>
|
||||
{isSaveModalVisible && (
|
||||
<LensSaveModalComponent
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
"data",
|
||||
"charts",
|
||||
"expressions",
|
||||
"inspector",
|
||||
"navigation",
|
||||
"urlForwarding",
|
||||
"visualizations",
|
||||
|
|
|
@ -5,10 +5,18 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import React, { FC, useEffect } from 'react';
|
||||
import { CoreStart } from 'kibana/public';
|
||||
import { UiActionsStart } from 'src/plugins/ui_actions/public';
|
||||
import type { Start as InspectorStartContract } from 'src/plugins/inspector/public';
|
||||
import {
|
||||
EmbeddableRenderer,
|
||||
EmbeddableInput,
|
||||
EmbeddableOutput,
|
||||
EmbeddablePanel,
|
||||
EmbeddableRoot,
|
||||
EmbeddableStart,
|
||||
IEmbeddable,
|
||||
useEmbeddableFactory,
|
||||
} from '../../../../../../src/plugins/embeddable/public';
|
||||
import type { LensByReferenceInput, LensByValueInput } from './embeddable';
|
||||
import type { Document } from '../../persistence';
|
||||
|
@ -43,11 +51,69 @@ export type TypedLensByValueInput = Omit<LensByValueInput, 'attributes'> & {
|
|||
| LensAttributes<'lnsMetric', MetricState>;
|
||||
};
|
||||
|
||||
export type EmbeddableComponentProps = TypedLensByValueInput | LensByReferenceInput;
|
||||
export type EmbeddableComponentProps = (TypedLensByValueInput | LensByReferenceInput) & {
|
||||
withActions?: boolean;
|
||||
};
|
||||
|
||||
export function getEmbeddableComponent(embeddableStart: EmbeddableStart) {
|
||||
interface PluginsStartDependencies {
|
||||
uiActions: UiActionsStart;
|
||||
embeddable: EmbeddableStart;
|
||||
inspector: InspectorStartContract;
|
||||
}
|
||||
|
||||
export function getEmbeddableComponent(core: CoreStart, plugins: PluginsStartDependencies) {
|
||||
return (props: EmbeddableComponentProps) => {
|
||||
const { embeddable: embeddableStart, uiActions, inspector } = plugins;
|
||||
const factory = embeddableStart.getEmbeddableFactory('lens')!;
|
||||
return <EmbeddableRenderer factory={factory} input={props} />;
|
||||
const input = { ...props };
|
||||
const [embeddable, loading, error] = useEmbeddableFactory({ factory, input });
|
||||
const hasActions = props.withActions === true;
|
||||
|
||||
if (embeddable && hasActions) {
|
||||
return (
|
||||
<EmbeddablePanelWrapper
|
||||
embeddable={embeddable as IEmbeddable<EmbeddableInput, EmbeddableOutput>}
|
||||
uiActions={uiActions}
|
||||
inspector={inspector}
|
||||
actionPredicate={() => hasActions}
|
||||
input={input}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return <EmbeddableRoot embeddable={embeddable} loading={loading} error={error} input={input} />;
|
||||
};
|
||||
}
|
||||
|
||||
interface EmbeddablePanelWrapperProps {
|
||||
embeddable: IEmbeddable<EmbeddableInput, EmbeddableOutput>;
|
||||
uiActions: PluginsStartDependencies['uiActions'];
|
||||
inspector: PluginsStartDependencies['inspector'];
|
||||
actionPredicate: (id: string) => boolean;
|
||||
input: EmbeddableComponentProps;
|
||||
}
|
||||
|
||||
const EmbeddablePanelWrapper: FC<EmbeddablePanelWrapperProps> = ({
|
||||
embeddable,
|
||||
uiActions,
|
||||
actionPredicate,
|
||||
inspector,
|
||||
input,
|
||||
}) => {
|
||||
useEffect(() => {
|
||||
embeddable.updateInput(input);
|
||||
}, [embeddable, input]);
|
||||
|
||||
return (
|
||||
<EmbeddablePanel
|
||||
hideHeader={false}
|
||||
embeddable={embeddable as IEmbeddable<EmbeddableInput, EmbeddableOutput>}
|
||||
getActions={uiActions.getTriggerCompatibleActions}
|
||||
inspector={inspector}
|
||||
actionPredicate={actionPredicate}
|
||||
showShadow={false}
|
||||
showBadges={false}
|
||||
showNotifications={false}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
import { LensPlugin } from './plugin';
|
||||
|
||||
export {
|
||||
export type {
|
||||
EmbeddableComponentProps,
|
||||
TypedLensByValueInput,
|
||||
} from './editor_frame_service/embeddable/embeddable_component';
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
*/
|
||||
|
||||
import { AppMountParameters, CoreSetup, CoreStart } from 'kibana/public';
|
||||
import type { Start as InspectorStartContract } from 'src/plugins/inspector/public';
|
||||
import { UsageCollectionSetup, UsageCollectionStart } from 'src/plugins/usage_collection/public';
|
||||
import { DataPublicPluginSetup, DataPublicPluginStart } from '../../../../src/plugins/data/public';
|
||||
import { EmbeddableSetup, EmbeddableStart } from '../../../../src/plugins/embeddable/public';
|
||||
|
@ -81,6 +82,7 @@ export interface LensPluginStartDependencies {
|
|||
savedObjectsTagging?: SavedObjectTaggingPluginStart;
|
||||
presentationUtil: PresentationUtilPluginStart;
|
||||
indexPatternFieldEditor: IndexPatternFieldEditorStart;
|
||||
inspector: InspectorStartContract;
|
||||
usageCollection?: UsageCollectionStart;
|
||||
}
|
||||
|
||||
|
@ -256,7 +258,7 @@ export class LensPlugin {
|
|||
);
|
||||
|
||||
return {
|
||||
EmbeddableComponent: getEmbeddableComponent(startDependencies.embeddable),
|
||||
EmbeddableComponent: getEmbeddableComponent(core, startDependencies),
|
||||
SaveModalComponent: getSaveModalComponent(core, startDependencies, this.attributeService!),
|
||||
navigateToPrefilledEditor: (input: LensEmbeddableInput, openInNewTab?: boolean) => {
|
||||
// for openInNewTab, we set the time range in url via getEditPath below
|
||||
|
|
|
@ -78,7 +78,9 @@ export class CustomTimeRangeAction implements Action<TimeRangeActionContext> {
|
|||
const isMarkdown =
|
||||
isVisualizeEmbeddable(embeddable) &&
|
||||
(embeddable as VisualizeEmbeddable).getOutput().visTypeName === 'markdown';
|
||||
return Boolean(embeddable && hasTimeRange(embeddable) && !isInputControl && !isMarkdown);
|
||||
return Boolean(
|
||||
embeddable && embeddable.parent && hasTimeRange(embeddable) && !isInputControl && !isMarkdown
|
||||
);
|
||||
}
|
||||
|
||||
public async execute({ embeddable }: TimeRangeActionContext) {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue