Prep for embed saved object refactor + helper (#62486)

This commit is contained in:
Stacey Gammon 2020-04-06 13:45:46 -04:00 committed by GitHub
parent 0ebfe76b3f
commit dfa083dc60
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
26 changed files with 234 additions and 303 deletions

View file

@ -1,64 +0,0 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import React from 'react';
import { EuiPanel, EuiLoadingSpinner, EuiFlexItem } from '@elastic/eui';
import { IEmbeddable } from '../../../../src/plugins/embeddable/public';
interface Props {
embeddable: IEmbeddable;
}
export class EmbeddableListItem extends React.Component<Props> {
private embeddableRoot: React.RefObject<HTMLDivElement>;
private rendered = false;
constructor(props: Props) {
super(props);
this.embeddableRoot = React.createRef();
}
public componentDidMount() {
if (this.embeddableRoot.current && this.props.embeddable) {
this.props.embeddable.render(this.embeddableRoot.current);
this.rendered = true;
}
}
public componentDidUpdate() {
if (this.embeddableRoot.current && this.props.embeddable && !this.rendered) {
this.props.embeddable.render(this.embeddableRoot.current);
this.rendered = true;
}
}
public render() {
return (
<EuiFlexItem>
<EuiPanel>
{this.props.embeddable ? (
<div ref={this.embeddableRoot} />
) : (
<EuiLoadingSpinner size="s" />
)}
</EuiPanel>
</EuiFlexItem>
);
}
}

View file

@ -31,16 +31,14 @@ export class ListContainer extends Container<{}, ContainerInput> {
public readonly type = LIST_CONTAINER;
private node?: HTMLElement;
constructor(
input: ContainerInput,
getEmbeddableFactory: EmbeddableStart['getEmbeddableFactory']
) {
super(input, { embeddableLoaded: {} }, getEmbeddableFactory);
constructor(input: ContainerInput, private embeddableServices: EmbeddableStart) {
super(input, { embeddableLoaded: {} }, embeddableServices.getEmbeddableFactory);
}
// This container has no input itself.
getInheritedInput(id: string) {
return {};
getInheritedInput() {
return {
viewMode: this.input.viewMode,
};
}
public render(node: HTMLElement) {
@ -48,7 +46,10 @@ export class ListContainer extends Container<{}, ContainerInput> {
if (this.node) {
ReactDOM.unmountComponentAtNode(this.node);
}
ReactDOM.render(<ListContainerComponent embeddable={this} />, node);
ReactDOM.render(
<ListContainerComponent embeddable={this} embeddableServices={this.embeddableServices} />,
node
);
}
public destroy() {

View file

@ -24,30 +24,35 @@ import {
withEmbeddableSubscription,
ContainerInput,
ContainerOutput,
EmbeddableStart,
} from '../../../../src/plugins/embeddable/public';
import { EmbeddableListItem } from './embeddable_list_item';
interface Props {
embeddable: IContainer;
input: ContainerInput;
output: ContainerOutput;
embeddableServices: EmbeddableStart;
}
function renderList(embeddable: IContainer, panels: ContainerInput['panels']) {
function renderList(
embeddable: IContainer,
panels: ContainerInput['panels'],
embeddableServices: EmbeddableStart
) {
let number = 0;
const list = Object.values(panels).map(panel => {
const child = embeddable.getChild(panel.explicitInput.id);
number++;
return (
<EuiPanel key={number.toString()}>
<EuiFlexGroup>
<EuiFlexGroup gutterSize="none">
<EuiFlexItem grow={false}>
<EuiText>
<h3>{number}</h3>
</EuiText>
</EuiFlexItem>
<EuiFlexItem>
<EmbeddableListItem embeddable={child} />
<embeddableServices.EmbeddablePanel embeddable={child} />
</EuiFlexItem>
</EuiFlexGroup>
</EuiPanel>
@ -56,12 +61,12 @@ function renderList(embeddable: IContainer, panels: ContainerInput['panels']) {
return list;
}
export function ListContainerComponentInner(props: Props) {
export function ListContainerComponentInner({ embeddable, input, embeddableServices }: Props) {
return (
<div>
<h2 data-test-subj="listContainerTitle">{props.embeddable.getTitle()}</h2>
<h2 data-test-subj="listContainerTitle">{embeddable.getTitle()}</h2>
<EuiSpacer size="l" />
{renderList(props.embeddable, props.input.panels)}
{renderList(embeddable, input.panels, embeddableServices)}
</div>
);
}
@ -71,4 +76,9 @@ export function ListContainerComponentInner(props: Props) {
// anything on input or output state changes. If you don't want that to happen (for example
// if you expect something on input or output state to change frequently that your react
// component does not care about, then you should probably hook this up manually).
export const ListContainerComponent = withEmbeddableSubscription(ListContainerComponentInner);
export const ListContainerComponent = withEmbeddableSubscription<
ContainerInput,
ContainerOutput,
IContainer,
{ embeddableServices: EmbeddableStart }
>(ListContainerComponentInner);

View file

@ -26,7 +26,7 @@ import {
import { LIST_CONTAINER, ListContainer } from './list_container';
interface StartServices {
getEmbeddableFactory: EmbeddableStart['getEmbeddableFactory'];
embeddableServices: EmbeddableStart;
}
export class ListContainerFactory implements EmbeddableFactoryDefinition {
@ -40,8 +40,8 @@ export class ListContainerFactory implements EmbeddableFactoryDefinition {
}
public create = async (initialInput: ContainerInput) => {
const { getEmbeddableFactory } = await this.getStartServices();
return new ListContainer(initialInput, getEmbeddableFactory);
const { embeddableServices } = await this.getStartServices();
return new ListContainer(initialInput, embeddableServices);
};
public getDisplayName() {

View file

@ -54,7 +54,7 @@ function wrapSearchTerms(task: string, search?: string) {
);
}
function renderTasks(tasks: MultiTaskTodoOutput['tasks'], search?: string) {
function renderTasks(tasks: MultiTaskTodoInput['tasks'], search?: string) {
return tasks.map(task => (
<EuiListGroupItem
key={task}
@ -65,16 +65,15 @@ function renderTasks(tasks: MultiTaskTodoOutput['tasks'], search?: string) {
}
export function MultiTaskTodoEmbeddableComponentInner({
input: { title, icon, search },
output: { tasks },
input: { title, icon, search, tasks },
}: Props) {
return (
<EuiFlexGroup>
<EuiFlexGroup gutterSize="none">
<EuiFlexItem grow={false}>
{icon ? <EuiIcon type={icon} size="l" /> : <EuiAvatar name={title} size="l" />}
</EuiFlexItem>
<EuiFlexItem>
<EuiFlexGrid columns={1}>
<EuiFlexGrid columns={1} gutterSize="none">
<EuiFlexItem>
<EuiText data-test-subj="multiTaskTodoTitle">
<h3>{wrapSearchTerms(title, search)}</h3>
@ -89,6 +88,8 @@ export function MultiTaskTodoEmbeddableComponentInner({
);
}
export const MultiTaskTodoEmbeddableComponent = withEmbeddableSubscription(
MultiTaskTodoEmbeddableComponentInner
);
export const MultiTaskTodoEmbeddableComponent = withEmbeddableSubscription<
MultiTaskTodoInput,
MultiTaskTodoOutput,
MultiTaskTodoEmbeddable
>(MultiTaskTodoEmbeddableComponentInner);

View file

@ -36,30 +36,27 @@ export interface MultiTaskTodoInput extends EmbeddableInput {
title: string;
}
// This embeddable has output! It's the tasks list that is filtered.
// Output state is something only the embeddable itself can update. It
// can be something completely internal, or it can be state that is
// This embeddable has output! Output state is something only the embeddable itself
// can update. It can be something completely internal, or it can be state that is
// derived from input state and updates when input does.
export interface MultiTaskTodoOutput extends EmbeddableOutput {
tasks: string[];
hasMatch: boolean;
}
function getFilteredTasks(tasks: string[], search?: string) {
const filteredTasks: string[] = [];
if (search === undefined) return tasks;
function getHasMatch(tasks: string[], title?: string, search?: string) {
if (search === undefined || search === '') return false;
tasks.forEach(task => {
if (task.match(search)) {
filteredTasks.push(task);
}
});
if (title && title.match(search)) return true;
return filteredTasks;
const match = tasks.find(task => task.match(search));
if (match) return true;
return false;
}
function getOutput(input: MultiTaskTodoInput) {
const tasks = getFilteredTasks(input.tasks, input.search);
return { tasks, hasMatch: tasks.length > 0 || (input.search && input.title.match(input.search)) };
const hasMatch = getHasMatch(input.tasks, input.title, input.search);
return { hasMatch };
}
export class MultiTaskTodoEmbeddable extends Embeddable<MultiTaskTodoInput, MultiTaskTodoOutput> {

View file

@ -53,20 +53,17 @@ export class EmbeddableExamplesPlugin
new MultiTaskTodoEmbeddableFactory()
);
// These are registered in the start method because `getEmbeddableFactory `
// is only available in start. We could reconsider this I think and make it
// available in both.
deps.embeddable.registerEmbeddableFactory(
SEARCHABLE_LIST_CONTAINER,
new SearchableListContainerFactory(async () => ({
getEmbeddableFactory: (await core.getStartServices())[1].embeddable.getEmbeddableFactory,
embeddableServices: (await core.getStartServices())[1].embeddable,
}))
);
deps.embeddable.registerEmbeddableFactory(
LIST_CONTAINER,
new ListContainerFactory(async () => ({
getEmbeddableFactory: (await core.getStartServices())[1].embeddable.getEmbeddableFactory,
embeddableServices: (await core.getStartServices())[1].embeddable,
}))
);

View file

@ -40,11 +40,8 @@ export class SearchableListContainer extends Container<ChildInput, SearchableCon
public readonly type = SEARCHABLE_LIST_CONTAINER;
private node?: HTMLElement;
constructor(
input: SearchableContainerInput,
getEmbeddableFactory: EmbeddableStart['getEmbeddableFactory']
) {
super(input, { embeddableLoaded: {} }, getEmbeddableFactory);
constructor(input: SearchableContainerInput, private embeddableServices: EmbeddableStart) {
super(input, { embeddableLoaded: {} }, embeddableServices.getEmbeddableFactory);
}
// TODO: add a more advanced example here where inherited child input is derived from container
@ -53,6 +50,7 @@ export class SearchableListContainer extends Container<ChildInput, SearchableCon
return {
id,
search: this.getInput().search,
viewMode: this.input.viewMode,
};
}
@ -61,7 +59,13 @@ export class SearchableListContainer extends Container<ChildInput, SearchableCon
ReactDOM.unmountComponentAtNode(this.node);
}
this.node = node;
ReactDOM.render(<SearchableListContainerComponent embeddable={this} />, node);
ReactDOM.render(
<SearchableListContainerComponent
embeddable={this}
embeddableServices={this.embeddableServices}
/>,
node
);
}
public destroy() {

View file

@ -34,14 +34,15 @@ import {
withEmbeddableSubscription,
ContainerOutput,
EmbeddableOutput,
EmbeddableStart,
} from '../../../../src/plugins/embeddable/public';
import { EmbeddableListItem } from '../list_container/embeddable_list_item';
import { SearchableListContainer, SearchableContainerInput } from './searchable_list_container';
interface Props {
embeddable: SearchableListContainer;
input: SearchableContainerInput;
output: ContainerOutput;
embeddableServices: EmbeddableStart;
}
interface State {
@ -111,13 +112,27 @@ export class SearchableListContainerComponentInner extends Component<Props, Stat
});
};
private checkMatching = () => {
const { input, embeddable } = this.props;
const checked: { [key: string]: boolean } = {};
Object.values(input.panels).map(panel => {
const child = embeddable.getChild(panel.explicitInput.id);
const output = child.getOutput();
if (hasHasMatchOutput(output) && output.hasMatch) {
checked[panel.explicitInput.id] = true;
}
});
this.setState({ checked });
};
private toggleCheck = (isChecked: boolean, id: string) => {
this.setState(prevState => ({ checked: { ...prevState.checked, [id]: isChecked } }));
};
public renderControls() {
const { input } = this.props;
return (
<EuiFlexGroup>
<EuiFlexGroup gutterSize="s">
<EuiFlexItem grow={false}>
<EuiFormRow hasEmptyLabelSpace>
<EuiButton data-test-subj="deleteCheckedTodos" onClick={() => this.deleteChecked()}>
@ -125,6 +140,17 @@ export class SearchableListContainerComponentInner extends Component<Props, Stat
</EuiButton>
</EuiFormRow>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiFormRow hasEmptyLabelSpace>
<EuiButton
data-test-subj="checkMatchingTodos"
disabled={input.search === ''}
onClick={() => this.checkMatching()}
>
Check matching
</EuiButton>
</EuiFormRow>
</EuiFlexItem>
<EuiFlexItem>
<EuiFormRow label="Filter">
<EuiFieldText
@ -142,36 +168,38 @@ export class SearchableListContainerComponentInner extends Component<Props, Stat
public render() {
const { embeddable } = this.props;
return (
<div>
<h2 data-test-subj="searchableListContainerTitle">{embeddable.getTitle()}</h2>
<EuiSpacer size="l" />
{this.renderControls()}
<EuiSpacer size="l" />
{this.renderList()}
</div>
<EuiFlexGroup gutterSize="none">
<EuiFlexItem>
<h2 data-test-subj="searchableListContainerTitle">{embeddable.getTitle()}</h2>
<EuiSpacer size="l" />
{this.renderControls()}
<EuiSpacer size="l" />
{this.renderList()}
</EuiFlexItem>
</EuiFlexGroup>
);
}
private renderList() {
const { embeddableServices, input, embeddable } = this.props;
let id = 0;
const list = Object.values(this.props.input.panels).map(panel => {
const embeddable = this.props.embeddable.getChild(panel.explicitInput.id);
if (this.props.input.search && !this.state.hasMatch[panel.explicitInput.id]) return;
const list = Object.values(input.panels).map(panel => {
const childEmbeddable = embeddable.getChild(panel.explicitInput.id);
id++;
return embeddable ? (
<EuiPanel key={embeddable.id}>
<EuiFlexGroup>
return childEmbeddable ? (
<EuiPanel key={childEmbeddable.id}>
<EuiFlexGroup gutterSize="none">
<EuiFlexItem grow={false}>
<EuiCheckbox
data-test-subj={`todoCheckBox-${embeddable.id}`}
disabled={!embeddable}
id={embeddable ? embeddable.id : ''}
checked={this.state.checked[embeddable.id]}
onChange={e => this.toggleCheck(e.target.checked, embeddable.id)}
data-test-subj={`todoCheckBox-${childEmbeddable.id}`}
disabled={!childEmbeddable}
id={childEmbeddable ? childEmbeddable.id : ''}
checked={this.state.checked[childEmbeddable.id]}
onChange={e => this.toggleCheck(e.target.checked, childEmbeddable.id)}
/>
</EuiFlexItem>
<EuiFlexItem>
<EmbeddableListItem embeddable={embeddable} />
<embeddableServices.EmbeddablePanel embeddable={childEmbeddable} />
</EuiFlexItem>
</EuiFlexGroup>
</EuiPanel>
@ -183,6 +211,9 @@ export class SearchableListContainerComponentInner extends Component<Props, Stat
}
}
export const SearchableListContainerComponent = withEmbeddableSubscription(
SearchableListContainerComponentInner
);
export const SearchableListContainerComponent = withEmbeddableSubscription<
SearchableContainerInput,
ContainerOutput,
SearchableListContainer,
{ embeddableServices: EmbeddableStart }
>(SearchableListContainerComponentInner);

View file

@ -29,7 +29,7 @@ import {
} from './searchable_list_container';
interface StartServices {
getEmbeddableFactory: EmbeddableStart['getEmbeddableFactory'];
embeddableServices: EmbeddableStart;
}
export class SearchableListContainerFactory implements EmbeddableFactoryDefinition {
@ -43,8 +43,8 @@ export class SearchableListContainerFactory implements EmbeddableFactoryDefiniti
}
public create = async (initialInput: SearchableContainerInput) => {
const { getEmbeddableFactory } = await this.getStartServices();
return new SearchableListContainer(initialInput, getEmbeddableFactory);
const { embeddableServices } = await this.getStartServices();
return new SearchableListContainer(initialInput, embeddableServices);
};
public getDisplayName() {

View file

@ -51,12 +51,12 @@ function wrapSearchTerms(task: string, search?: string) {
export function TodoEmbeddableComponentInner({ input: { icon, title, task, search } }: Props) {
return (
<EuiFlexGroup>
<EuiFlexGroup gutterSize="none">
<EuiFlexItem grow={false}>
{icon ? <EuiIcon type={icon} size="l" /> : <EuiAvatar name={title || task} size="l" />}
</EuiFlexItem>
<EuiFlexItem>
<EuiFlexGrid columns={1}>
<EuiFlexGrid columns={1} gutterSize="none">
<EuiFlexItem>
<EuiText data-test-subj="todoEmbeddableTitle">
<h3>{wrapSearchTerms(title || '', search)}</h3>
@ -71,4 +71,8 @@ export function TodoEmbeddableComponentInner({ input: { icon, title, task, searc
);
}
export const TodoEmbeddableComponent = withEmbeddableSubscription(TodoEmbeddableComponentInner);
export const TodoEmbeddableComponent = withEmbeddableSubscription<
TodoInput,
EmbeddableOutput,
TodoEmbeddable
>(TodoEmbeddableComponentInner);

View file

@ -117,18 +117,7 @@ const EmbeddableExplorerApp = ({
{
title: 'Dynamically adding children to a container',
id: 'embeddablePanelExamplae',
component: (
<EmbeddablePanelExample
uiActionsApi={uiActionsApi}
getAllEmbeddableFactories={embeddableApi.getEmbeddableFactories}
getEmbeddableFactory={embeddableApi.getEmbeddableFactory}
overlays={overlays}
uiSettingsClient={uiSettingsClient}
savedObject={savedObject}
notifications={notifications}
inspector={inspector}
/>
),
component: <EmbeddablePanelExample embeddableServices={embeddableApi} />,
},
];

View file

@ -29,43 +29,19 @@ import {
EuiText,
} from '@elastic/eui';
import { EuiSpacer } from '@elastic/eui';
import { OverlayStart, CoreStart, SavedObjectsStart, IUiSettingsClient } from 'kibana/public';
import {
EmbeddablePanel,
EmbeddableStart,
IEmbeddable,
} from '../../../src/plugins/embeddable/public';
import { EmbeddableStart, IEmbeddable } from '../../../src/plugins/embeddable/public';
import {
HELLO_WORLD_EMBEDDABLE,
TODO_EMBEDDABLE,
MULTI_TASK_TODO_EMBEDDABLE,
SEARCHABLE_LIST_CONTAINER,
} from '../../embeddable_examples/public';
import { UiActionsStart } from '../../../src/plugins/ui_actions/public';
import { Start as InspectorStartContract } from '../../../src/plugins/inspector/public';
import { getSavedObjectFinder } from '../../../src/plugins/saved_objects/public';
interface Props {
getAllEmbeddableFactories: EmbeddableStart['getEmbeddableFactories'];
getEmbeddableFactory: EmbeddableStart['getEmbeddableFactory'];
uiActionsApi: UiActionsStart;
overlays: OverlayStart;
notifications: CoreStart['notifications'];
inspector: InspectorStartContract;
savedObject: SavedObjectsStart;
uiSettingsClient: IUiSettingsClient;
embeddableServices: EmbeddableStart;
}
export function EmbeddablePanelExample({
inspector,
notifications,
overlays,
getAllEmbeddableFactories,
getEmbeddableFactory,
uiActionsApi,
savedObject,
uiSettingsClient,
}: Props) {
export function EmbeddablePanelExample({ embeddableServices }: Props) {
const searchableInput = {
id: '1',
title: 'My searchable todo list',
@ -105,7 +81,7 @@ export function EmbeddablePanelExample({
useEffect(() => {
ref.current = true;
if (!embeddable) {
const factory = getEmbeddableFactory(SEARCHABLE_LIST_CONTAINER);
const factory = embeddableServices.getEmbeddableFactory(SEARCHABLE_LIST_CONTAINER);
const promise = factory?.create(searchableInput);
if (promise) {
promise.then(e => {
@ -134,22 +110,13 @@ export function EmbeddablePanelExample({
<EuiText>
You can render your embeddable inside the EmbeddablePanel component. This adds some
extra rendering and offers a context menu with pluggable actions. Using EmbeddablePanel
to render your embeddable means you get access to the &quote;Add panel flyout&quote;.
Now you can see how to add embeddables to your container, and how
&quote;getExplicitInput&quote; is used to grab input not provided by the container.
to render your embeddable means you get access to the &quot;Add panel flyout&quot;. Now
you can see how to add embeddables to your container, and how
&quot;getExplicitInput&quot; is used to grab input not provided by the container.
</EuiText>
<EuiPanel data-test-subj="embeddedPanelExample" paddingSize="none" role="figure">
{embeddable ? (
<EmbeddablePanel
embeddable={embeddable}
getActions={uiActionsApi.getTriggerCompatibleActions}
getEmbeddableFactory={getEmbeddableFactory}
getAllEmbeddableFactories={getAllEmbeddableFactories}
overlays={overlays}
notifications={notifications}
inspector={inspector}
SavedObjectFinder={getSavedObjectFinder(savedObject, uiSettingsClient)}
/>
<embeddableServices.EmbeddablePanel embeddable={embeddable} />
) : (
<EuiText>Loading...</EuiText>
)}

View file

@ -29,7 +29,11 @@ import {
EuiText,
} from '@elastic/eui';
import { EuiSpacer } from '@elastic/eui';
import { EmbeddableFactoryRenderer, EmbeddableStart } from '../../../src/plugins/embeddable/public';
import {
EmbeddableFactoryRenderer,
EmbeddableStart,
ViewMode,
} from '../../../src/plugins/embeddable/public';
import {
HELLO_WORLD_EMBEDDABLE,
TODO_EMBEDDABLE,
@ -46,6 +50,7 @@ export function ListContainerExample({ getEmbeddableFactory }: Props) {
const listInput = {
id: 'hello',
title: 'My todo list',
viewMode: ViewMode.VIEW,
panels: {
'1': {
type: HELLO_WORLD_EMBEDDABLE,
@ -76,6 +81,7 @@ export function ListContainerExample({ getEmbeddableFactory }: Props) {
const searchableInput = {
id: '1',
title: 'My searchable todo list',
viewMode: ViewMode.VIEW,
panels: {
'1': {
type: HELLO_WORLD_EMBEDDABLE,
@ -150,7 +156,7 @@ export function ListContainerExample({ getEmbeddableFactory }: Props) {
</p>
<p>
Check out the &quote;Dynamically adding children&quote; section, to see how to add
Check out the &quot;Dynamically adding children&quot; section, to see how to add
children to this container, and see it rendered inside an `EmbeddablePanel` component.
</p>
</EuiText>

View file

@ -29,7 +29,7 @@ import {
ContactCardEmbeddable,
} from '../test_samples/embeddables/contact_card/contact_card_embeddable';
// eslint-disable-next-line
import { inspectorPluginMock } from 'src/plugins/inspector/public/mocks';
import { inspectorPluginMock } from '../../../../inspector/public/mocks';
import { mount } from 'enzyme';
import { embeddablePluginMock } from '../../mocks';

View file

@ -23,18 +23,19 @@ import { IEmbeddable, EmbeddableInput, EmbeddableOutput } from './i_embeddable';
export const withEmbeddableSubscription = <
I extends EmbeddableInput,
O extends EmbeddableOutput,
E extends IEmbeddable<I, O> = IEmbeddable<I, O>
E extends IEmbeddable<I, O> = IEmbeddable<I, O>,
ExtraProps = {}
>(
WrappedComponent: React.ComponentType<{ input: I; output: O; embeddable: E }>
): React.ComponentType<{ embeddable: E }> =>
WrappedComponent: React.ComponentType<{ input: I; output: O; embeddable: E } & ExtraProps>
): React.ComponentType<{ embeddable: E } & ExtraProps> =>
class WithEmbeddableSubscription extends React.Component<
{ embeddable: E },
{ embeddable: E } & ExtraProps,
{ input: I; output: O }
> {
private subscription?: Rx.Subscription;
private mounted: boolean = false;
constructor(props: { embeddable: E }) {
constructor(props: { embeddable: E } & ExtraProps) {
super(props);
this.state = {
input: this.props.embeddable.getInput(),
@ -71,6 +72,7 @@ export const withEmbeddableSubscription = <
input={this.state.input}
output={this.state.output}
embeddable={this.props.embeddable}
{...this.props}
/>
);
}

View file

@ -25,7 +25,7 @@ import { nextTick } from 'test_utils/enzyme_helpers';
import { findTestSubject } from '@elastic/eui/lib/test';
import { I18nProvider } from '@kbn/i18n/react';
import { CONTEXT_MENU_TRIGGER } from '../triggers';
import { Action, UiActionsStart, ActionType } from 'src/plugins/ui_actions/public';
import { Action, UiActionsStart, ActionType } from '../../../../ui_actions/public';
import { Trigger, ViewMode } from '../types';
import { isErrorEmbeddable } from '../embeddables';
import { EmbeddablePanel } from './embeddable_panel';
@ -41,7 +41,7 @@ import {
ContactCardEmbeddableOutput,
} from '../test_samples/embeddables/contact_card/contact_card_embeddable';
// eslint-disable-next-line
import { inspectorPluginMock } from 'src/plugins/inspector/public/mocks';
import { inspectorPluginMock } from '../../../../inspector/public/mocks';
import { EuiBadge } from '@elastic/eui';
import { embeddablePluginMock } from '../../mocks';

View file

@ -27,7 +27,7 @@ import {
ContactCardEmbeddable,
} from '../../../test_samples';
// eslint-disable-next-line
import { inspectorPluginMock } from 'src/plugins/inspector/public/mocks';
import { inspectorPluginMock } from '../../../../../../../plugins/inspector/public/mocks';
import { EmbeddableOutput, isErrorEmbeddable, ErrorEmbeddable } from '../../../embeddables';
import { of } from '../../../../tests/helpers';
import { esFilters } from '../../../../../../../plugins/data/public';

View file

@ -16,11 +16,12 @@
* specific language governing permissions and limitations
* under the License.
*/
import { EmbeddableStart, EmbeddableSetup } from '.';
import { EmbeddablePublicPlugin } from './plugin';
import { coreMock } from '../../../core/public/mocks';
// eslint-disable-next-line
import { inspectorPluginMock } from '../../inspector/public/mocks';
// eslint-disable-next-line
import { uiActionsPluginMock } from '../../ui_actions/public/mocks';
@ -39,6 +40,7 @@ const createStartContract = (): Start => {
const startContract: Start = {
getEmbeddableFactories: jest.fn(),
getEmbeddableFactory: jest.fn(),
EmbeddablePanel: jest.fn(),
};
return startContract;
};
@ -48,7 +50,11 @@ const createInstance = () => {
const setup = plugin.setup(coreMock.createSetup(), {
uiActions: uiActionsPluginMock.createSetupContract(),
});
const doStart = () => plugin.start(coreMock.createStart());
const doStart = () =>
plugin.start(coreMock.createStart(), {
uiActions: uiActionsPluginMock.createStartContract(),
inspector: inspectorPluginMock.createStartContract(),
});
return {
plugin,
setup,

View file

@ -16,7 +16,10 @@
* specific language governing permissions and limitations
* under the License.
*/
import { UiActionsSetup } from 'src/plugins/ui_actions/public';
import React from 'react';
import { getSavedObjectFinder } from '../../saved_objects/public';
import { UiActionsSetup, UiActionsStart } from '../../ui_actions/public';
import { Start as InspectorStart } from '../../inspector/public';
import { PluginInitializerContext, CoreSetup, CoreStart, Plugin } from '../../../core/public';
import { EmbeddableFactoryRegistry, EmbeddableFactoryProvider } from './types';
import { bootstrap } from './bootstrap';
@ -26,6 +29,7 @@ import {
EmbeddableOutput,
defaultEmbeddableFactoryProvider,
IEmbeddable,
EmbeddablePanel,
} from './lib';
import { EmbeddableFactoryDefinition } from './lib/embeddables/embeddable_factory_definition';
@ -33,6 +37,11 @@ export interface EmbeddableSetupDependencies {
uiActions: UiActionsSetup;
}
export interface EmbeddableStartDependencies {
uiActions: UiActionsStart;
inspector: InspectorStart;
}
export interface EmbeddableSetup {
registerEmbeddableFactory: <I extends EmbeddableInput, O extends EmbeddableOutput>(
id: string,
@ -50,6 +59,7 @@ export interface EmbeddableStart {
embeddableFactoryId: string
) => EmbeddableFactory<I, O, E> | undefined;
getEmbeddableFactories: () => IterableIterator<EmbeddableFactory>;
EmbeddablePanel: React.FC<{ embeddable: IEmbeddable; hideHeader?: boolean }>;
}
export class EmbeddablePublicPlugin implements Plugin<EmbeddableSetup, EmbeddableStart> {
@ -78,7 +88,10 @@ export class EmbeddablePublicPlugin implements Plugin<EmbeddableSetup, Embeddabl
};
}
public start(core: CoreStart): EmbeddableStart {
public start(
core: CoreStart,
{ uiActions, inspector }: EmbeddableStartDependencies
): EmbeddableStart {
this.embeddableFactoryDefinitions.forEach(def => {
this.embeddableFactories.set(
def.type,
@ -89,15 +102,36 @@ export class EmbeddablePublicPlugin implements Plugin<EmbeddableSetup, Embeddabl
});
return {
getEmbeddableFactory: this.getEmbeddableFactory,
getEmbeddableFactories: () => {
this.ensureFactoriesExist();
return this.embeddableFactories.values();
},
getEmbeddableFactories: this.getEmbeddableFactories,
EmbeddablePanel: ({
embeddable,
hideHeader,
}: {
embeddable: IEmbeddable;
hideHeader?: boolean;
}) => (
<EmbeddablePanel
hideHeader={hideHeader}
embeddable={embeddable}
getActions={uiActions.getTriggerCompatibleActions}
getEmbeddableFactory={this.getEmbeddableFactory}
getAllEmbeddableFactories={this.getEmbeddableFactories}
overlays={core.overlays}
notifications={core.notifications}
inspector={inspector}
SavedObjectFinder={getSavedObjectFinder(core.savedObjects, core.uiSettings)}
/>
),
};
}
public stop() {}
private getEmbeddableFactories = () => {
this.ensureFactoriesExist();
return this.embeddableFactories.values();
};
private registerEmbeddableFactory = (
embeddableFactoryId: string,
factory: EmbeddableFactoryDefinition
@ -130,11 +164,11 @@ export class EmbeddablePublicPlugin implements Plugin<EmbeddableSetup, Embeddabl
};
// These two functions are only to support legacy plugins registering factories after the start lifecycle.
private ensureFactoriesExist() {
private ensureFactoriesExist = () => {
this.embeddableFactoryDefinitions.forEach(def => this.ensureFactoryExists(def.type));
}
};
private ensureFactoryExists(type: string) {
private ensureFactoryExists = (type: string) => {
if (!this.embeddableFactories.get(type)) {
const def = this.embeddableFactoryDefinitions.get(type);
if (!def) return;
@ -145,5 +179,5 @@ export class EmbeddablePublicPlugin implements Plugin<EmbeddableSetup, Embeddabl
: defaultEmbeddableFactoryProvider(def)
);
}
}
};
}

View file

@ -31,7 +31,7 @@ import {
FilterableEmbeddableInput,
} from '../lib/test_samples';
// eslint-disable-next-line
import { inspectorPluginMock } from 'src/plugins/inspector/public/mocks';
import { inspectorPluginMock } from '../../../../plugins/inspector/public/mocks';
import { esFilters } from '../../../../plugins/data/public';
test('ApplyFilterAction applies the filter to the root of the container tree', async () => {

View file

@ -18,9 +18,11 @@
*/
import { CoreSetup, CoreStart } from 'src/core/public';
import { UiActionsStart } from '../../../ui_actions/public';
// eslint-disable-next-line
import { uiActionsPluginMock } from 'src/plugins/ui_actions/public/mocks';
import { UiActionsStart } from 'src/plugins/ui_actions/public';
import { uiActionsPluginMock } from '../../../ui_actions/public/mocks';
// eslint-disable-next-line
import { inspectorPluginMock } from '../../../inspector/public/mocks';
import { coreMock } from '../../../../core/public/mocks';
import { EmbeddablePublicPlugin, EmbeddableSetup, EmbeddableStart } from '../plugin';
@ -48,7 +50,10 @@ export const testPlugin = (
coreStart,
setup,
doStart: (anotherCoreStart: CoreStart = coreStart) => {
const start = plugin.start(anotherCoreStart);
const start = plugin.start(anotherCoreStart, {
uiActions: uiActionsPluginMock.createStartContract(),
inspector: inspectorPluginMock.createStartContract(),
});
return start;
},
uiActions: uiActions.doStart(coreStart),

View file

@ -57,13 +57,12 @@ export default function({ getService }: PluginFunctionalProviderContext) {
expect(text).to.eql(['HELLO WORLD!']);
});
it('searchable container filters multi-task children', async () => {
it('searchable container finds matches in multi-task children', async () => {
await testSubjects.setValue('filterTodos', 'earth');
await testSubjects.click('checkMatchingTodos');
await testSubjects.click('deleteCheckedTodos');
await retry.try(async () => {
const tasks = await testSubjects.getVisibleTextAll('multiTaskTodoTask');
expect(tasks).to.eql(['Watch planet earth']);
});
await testSubjects.missingOrFail('multiTaskTodoTask');
});
});
}

View file

@ -18,21 +18,11 @@
*/
import { EuiTab } from '@elastic/eui';
import React, { Component } from 'react';
import { CoreStart } from 'src/core/public';
import { EmbeddableStart } from 'src/plugins/embeddable/public';
import { UiActionsService } from '../../../../../../../../src/plugins/ui_actions/public';
import { DashboardContainerExample } from './dashboard_container_example';
import { Start as InspectorStartContract } from '../../../../../../../../src/plugins/inspector/public';
export interface AppProps {
getActions: UiActionsService['getTriggerCompatibleActions'];
getEmbeddableFactory: EmbeddableStart['getEmbeddableFactory'];
getAllEmbeddableFactories: EmbeddableStart['getEmbeddableFactories'];
overlays: CoreStart['overlays'];
notifications: CoreStart['notifications'];
inspector: InspectorStartContract;
SavedObjectFinder: React.ComponentType<any>;
I18nContext: CoreStart['i18n']['Context'];
embeddableServices: EmbeddableStart;
}
export class App extends Component<AppProps, { selectedTabId: string }> {
@ -72,29 +62,17 @@ export class App extends Component<AppProps, { selectedTabId: string }> {
public render() {
return (
<this.props.I18nContext>
<div id="dashboardViewport" style={{ flex: '1', display: 'flex', flexDirection: 'column' }}>
<div>{this.renderTabs()}</div>
{this.getContentsForTab()}
</div>
</this.props.I18nContext>
<div id="dashboardViewport" style={{ flex: '1', display: 'flex', flexDirection: 'column' }}>
<div>{this.renderTabs()}</div>
{this.getContentsForTab()}
</div>
);
}
private getContentsForTab() {
switch (this.state.selectedTabId) {
case 'dashboardContainer': {
return (
<DashboardContainerExample
getActions={this.props.getActions}
getEmbeddableFactory={this.props.getEmbeddableFactory}
getAllEmbeddableFactories={this.props.getAllEmbeddableFactories}
overlays={this.props.overlays}
notifications={this.props.notifications}
inspector={this.props.inspector}
SavedObjectFinder={this.props.SavedObjectFinder}
/>
);
return <DashboardContainerExample embeddableServices={this.props.embeddableServices} />;
}
}
}

View file

@ -19,32 +19,17 @@
import React from 'react';
import { EuiButton, EuiLoadingChart } from '@elastic/eui';
import { ContainerOutput } from 'src/plugins/embeddable/public';
import {
ErrorEmbeddable,
ViewMode,
isErrorEmbeddable,
EmbeddablePanel,
EmbeddableStart,
} from '../embeddable_api';
import { ErrorEmbeddable, ViewMode, isErrorEmbeddable, EmbeddableStart } from '../embeddable_api';
import {
DASHBOARD_CONTAINER_TYPE,
DashboardContainer,
DashboardContainerInput,
} from '../../../../../../../../src/plugins/dashboard/public';
import { CoreStart } from '../../../../../../../../src/core/public';
import { dashboardInput } from './dashboard_input';
import { Start as InspectorStartContract } from '../../../../../../../../src/plugins/inspector/public';
import { UiActionsService } from '../../../../../../../../src/plugins/ui_actions/public';
interface Props {
getActions: UiActionsService['getTriggerCompatibleActions'];
getEmbeddableFactory: EmbeddableStart['getEmbeddableFactory'];
getAllEmbeddableFactories: EmbeddableStart['getEmbeddableFactories'];
overlays: CoreStart['overlays'];
notifications: CoreStart['notifications'];
inspector: InspectorStartContract;
SavedObjectFinder: React.ComponentType<any>;
embeddableServices: EmbeddableStart;
}
interface State {
@ -67,7 +52,7 @@ export class DashboardContainerExample extends React.Component<Props, State> {
public async componentDidMount() {
this.mounted = true;
const dashboardFactory = this.props.getEmbeddableFactory<
const dashboardFactory = this.props.embeddableServices.getEmbeddableFactory<
DashboardContainerInput,
ContainerOutput,
DashboardContainer
@ -99,6 +84,7 @@ export class DashboardContainerExample extends React.Component<Props, State> {
};
public render() {
const { embeddableServices } = this.props;
return (
<div className="app-container dshAppContainer">
<h1>Dashboard Container</h1>
@ -108,16 +94,7 @@ export class DashboardContainerExample extends React.Component<Props, State> {
{!this.state.loaded || !this.container ? (
<EuiLoadingChart size="l" mono />
) : (
<EmbeddablePanel
embeddable={this.container}
getActions={this.props.getActions}
getEmbeddableFactory={this.props.getEmbeddableFactory}
getAllEmbeddableFactories={this.props.getAllEmbeddableFactories}
overlays={this.props.overlays}
notifications={this.props.notifications}
inspector={this.props.inspector}
SavedObjectFinder={this.props.SavedObjectFinder}
/>
<embeddableServices.EmbeddablePanel embeddable={this.container} />
)}
</div>
);

View file

@ -33,7 +33,6 @@ const REACT_ROOT_ID = 'embeddableExplorerRoot';
import { SayHelloAction, createSendMessageAction } from './embeddable_api';
import { App } from './app';
import { getSavedObjectFinder } from '../../../../../../../src/plugins/saved_objects/public';
import {
EmbeddableStart,
EmbeddableSetup,
@ -78,19 +77,7 @@ export class EmbeddableExplorerPublicPlugin
plugins.__LEGACY.onRenderComplete(() => {
const root = document.getElementById(REACT_ROOT_ID);
ReactDOM.render(
<App
getActions={plugins.uiActions.getTriggerCompatibleActions}
getAllEmbeddableFactories={plugins.embeddable.getEmbeddableFactories}
getEmbeddableFactory={plugins.embeddable.getEmbeddableFactory}
notifications={core.notifications}
overlays={core.overlays}
inspector={plugins.inspector}
SavedObjectFinder={getSavedObjectFinder(core.savedObjects, core.uiSettings)}
I18nContext={core.i18n.Context}
/>,
root
);
ReactDOM.render(<App embeddableServices={plugins.embeddable} />, root);
});
}