Embeddable examples on the platform and included with --run-examples flag (#52111)

* Add a new platform embeddable example plugin

* Remove extra hello world test impl.

* cleanup

* code review updates

* Change example to highlight and have parent filter out children

* Fix deep comparison of embeddable prop

* adjust help text
This commit is contained in:
Stacey Gammon 2019-12-16 15:03:46 -05:00 committed by GitHub
parent 3cc513e373
commit 7e67d1f86c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
69 changed files with 2478 additions and 310 deletions

View file

@ -9,6 +9,7 @@
"src/plugins/data"
],
"embeddableApi": "src/plugins/embeddable",
"embeddableExamples": "examples/embeddable_examples",
"share": "src/plugins/share",
"esUi": "src/plugins/es_ui_shared",
"devTools": "src/plugins/dev_tools",

View file

@ -1,7 +1,7 @@
{
"name": "demo_data_search",
"version": "1.0.0",
"main": "target/test/plugin_functional/plugins/demo_data_search",
"main": "target/examples/demo_data_search",
"kibana": {
"version": "kibana",
"templateVersion": "1.0.0"
@ -12,6 +12,6 @@
"build": "rm -rf './target' && tsc"
},
"devDependencies": {
"typescript": "3.5.3"
"typescript": "3.7.2"
}
}

View file

@ -0,0 +1,10 @@
{
"id": "embeddableExamples",
"version": "0.0.1",
"kibanaVersion": "kibana",
"configPath": ["embeddable_examples"],
"server": false,
"ui": true,
"requiredPlugins": ["embeddable"],
"optionalPlugins": []
}

View file

@ -0,0 +1,17 @@
{
"name": "embeddable_examples",
"version": "1.0.0",
"main": "target/examples/embeddable_examples",
"kibana": {
"version": "kibana",
"templateVersion": "1.0.0"
},
"license": "Apache-2.0",
"scripts": {
"kbn": "node ../../scripts/kbn.js",
"build": "rm -rf './target' && tsc"
},
"devDependencies": {
"typescript": "3.7.2"
}
}

View file

@ -16,14 +16,14 @@
* specific language governing permissions and limitations
* under the License.
*/
import { Embeddable, EmbeddableInput, IContainer } from '../../..';
import { Embeddable, EmbeddableInput, IContainer } from '../../../../src/plugins/embeddable/public';
export const HELLO_WORLD_EMBEDDABLE_TYPE = 'HELLO_WORLD_EMBEDDABLE_TYPE';
export const HELLO_WORLD_EMBEDDABLE = 'HELLO_WORLD_EMBEDDABLE';
export class HelloWorldEmbeddable extends Embeddable {
// The type of this embeddable. This will be used to find the appropriate factory
// to instantiate this kind of embeddable.
public readonly type = HELLO_WORLD_EMBEDDABLE_TYPE;
public readonly type = HELLO_WORLD_EMBEDDABLE;
constructor(initialInput: EmbeddableInput, parent?: IContainer) {
super(

View file

@ -18,11 +18,15 @@
*/
import { i18n } from '@kbn/i18n';
import { IContainer, EmbeddableInput, EmbeddableFactory } from '../../..';
import { HelloWorldEmbeddable, HELLO_WORLD_EMBEDDABLE_TYPE } from './hello_world_embeddable';
import {
IContainer,
EmbeddableInput,
EmbeddableFactory,
} from '../../../../src/plugins/embeddable/public';
import { HelloWorldEmbeddable, HELLO_WORLD_EMBEDDABLE } from './hello_world_embeddable';
export class HelloWorldEmbeddableFactory extends EmbeddableFactory {
public readonly type = HELLO_WORLD_EMBEDDABLE_TYPE;
public readonly type = HELLO_WORLD_EMBEDDABLE;
/**
* In our simple example, we let everyone have permissions to edit this. Most
@ -38,7 +42,7 @@ export class HelloWorldEmbeddableFactory extends EmbeddableFactory {
}
public getDisplayName() {
return i18n.translate('embeddableApi.samples.helloworld.displayName', {
return i18n.translate('embeddableExamples.helloworld.displayName', {
defaultMessage: 'hello world',
});
}

View file

@ -0,0 +1,34 @@
/*
* 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 { PluginInitializer } from 'kibana/public';
export {
HELLO_WORLD_EMBEDDABLE,
HelloWorldEmbeddable,
HelloWorldEmbeddableFactory,
} from './hello_world';
export { ListContainer, LIST_CONTAINER } from './list_container';
export { TODO_EMBEDDABLE } from './todo';
import { EmbeddableExamplesPlugin } from './plugin';
export { SearchableListContainer, SEARCHABLE_LIST_CONTAINER } from './searchable_list_container';
export { MULTI_TASK_TODO_EMBEDDABLE } from './multi_task_todo';
export const plugin: PluginInitializer<void, void> = () => new EmbeddableExamplesPlugin();

View file

@ -0,0 +1,64 @@
/*
* 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

@ -17,22 +17,5 @@
* under the License.
*/
import expect from '@kbn/expect';
export default function({ getService }) {
const testSubjects = getService('testSubjects');
const retry = getService('retry');
describe('hello world container', () => {
before(async () => {
await testSubjects.click('embedExplorerTab-helloWorldContainer');
});
it('hello world embeddable renders', async () => {
await retry.try(async () => {
const text = await testSubjects.getVisibleText('helloWorldEmbeddable');
expect(text).to.be('HELLO WORLD!');
});
});
});
}
export { ListContainer, LIST_CONTAINER } from './list_container';
export { ListContainerFactory } from './list_container_factory';

View file

@ -0,0 +1,57 @@
/*
* 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 ReactDOM from 'react-dom';
import {
Container,
ContainerInput,
GetEmbeddableFactory,
} from '../../../../src/plugins/embeddable/public';
import { ListContainerComponent } from './list_container_component';
export const LIST_CONTAINER = 'LIST_CONTAINER';
export class ListContainer extends Container<{}, ContainerInput> {
public readonly type = LIST_CONTAINER;
private node?: HTMLElement;
constructor(input: ContainerInput, getEmbeddableFactory: GetEmbeddableFactory) {
super(input, { embeddableLoaded: {} }, getEmbeddableFactory);
}
// This container has no input itself.
getInheritedInput(id: string) {
return {};
}
public render(node: HTMLElement) {
this.node = node;
if (this.node) {
ReactDOM.unmountComponentAtNode(this.node);
}
ReactDOM.render(<ListContainerComponent embeddable={this} />, node);
}
public destroy() {
super.destroy();
if (this.node) {
ReactDOM.unmountComponentAtNode(this.node);
}
}
}

View file

@ -0,0 +1,74 @@
/*
* 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 { EuiFlexGroup, EuiSpacer, EuiFlexItem, EuiText, EuiPanel } from '@elastic/eui';
import {
IContainer,
withEmbeddableSubscription,
ContainerInput,
ContainerOutput,
} from '../../../../src/plugins/embeddable/public';
import { EmbeddableListItem } from './embeddable_list_item';
interface Props {
embeddable: IContainer;
input: ContainerInput;
output: ContainerOutput;
}
function renderList(embeddable: IContainer, panels: ContainerInput['panels']) {
let number = 0;
const list = Object.values(panels).map(panel => {
const child = embeddable.getChild(panel.explicitInput.id);
number++;
return (
<EuiPanel key={number.toString()}>
<EuiFlexGroup>
<EuiFlexItem grow={false}>
<EuiText>
<h3>{number}</h3>
</EuiText>
</EuiFlexItem>
<EuiFlexItem>
<EmbeddableListItem embeddable={child} />
</EuiFlexItem>
</EuiFlexGroup>
</EuiPanel>
);
});
return list;
}
export function ListContainerComponentInner(props: Props) {
return (
<div>
<h2 data-test-subj="listContainerTitle">{props.embeddable.getTitle()}</h2>
<EuiSpacer size="l" />
{renderList(props.embeddable, props.input.panels)}
</div>
);
}
// You don't have to use this helper wrapper, but it handles a lot of the React boilerplate for
// embeddables, like setting up the subscriptions to cause the component to refresh whenever
// 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);

View file

@ -0,0 +1,49 @@
/*
* 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 { i18n } from '@kbn/i18n';
import {
EmbeddableFactory,
GetEmbeddableFactory,
ContainerInput,
} from '../../../../src/plugins/embeddable/public';
import { LIST_CONTAINER, ListContainer } from './list_container';
export class ListContainerFactory extends EmbeddableFactory {
public readonly type = LIST_CONTAINER;
public readonly isContainerType = true;
constructor(private getEmbeddableFactory: GetEmbeddableFactory) {
super();
}
public isEditable() {
return true;
}
public async create(initialInput: ContainerInput) {
return new ListContainer(initialInput, this.getEmbeddableFactory);
}
public getDisplayName() {
return i18n.translate('embeddableExamples.searchableListContainer.displayName', {
defaultMessage: 'List container',
});
}
}

View file

@ -0,0 +1,21 @@
/*
* 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.
*/
export * from './multi_task_todo_embeddable';
export * from './multi_task_todo_embeddable_factory';

View file

@ -0,0 +1,94 @@
/*
* 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 { EuiFlexItem, EuiFlexGroup } from '@elastic/eui';
import {
EuiText,
EuiAvatar,
EuiIcon,
EuiFlexGrid,
EuiListGroup,
EuiListGroupItem,
} from '@elastic/eui';
import { withEmbeddableSubscription } from '../../../../src/plugins/embeddable/public';
import {
MultiTaskTodoEmbeddable,
MultiTaskTodoOutput,
MultiTaskTodoInput,
} from './multi_task_todo_embeddable';
interface Props {
embeddable: MultiTaskTodoEmbeddable;
input: MultiTaskTodoInput;
output: MultiTaskTodoOutput;
}
function wrapSearchTerms(task: string, search?: string) {
if (!search) return task;
const parts = task.split(new RegExp(`(${search})`, 'g'));
return parts.map((part, i) =>
part === search ? (
<span key={i} style={{ backgroundColor: 'yellow' }}>
{part}
</span>
) : (
part
)
);
}
function renderTasks(tasks: MultiTaskTodoOutput['tasks'], search?: string) {
return tasks.map(task => (
<EuiListGroupItem
key={task}
data-test-subj="multiTaskTodoTask"
label={wrapSearchTerms(task, search)}
/>
));
}
export function MultiTaskTodoEmbeddableComponentInner({
input: { title, icon, search },
output: { tasks },
}: Props) {
return (
<EuiFlexGroup>
<EuiFlexItem grow={false}>
{icon ? <EuiIcon type={icon} size="l" /> : <EuiAvatar name={title} size="l" />}
</EuiFlexItem>
<EuiFlexItem>
<EuiFlexGrid columns={1}>
<EuiFlexItem>
<EuiText data-test-subj="multiTaskTodoTitle">
<h3>{wrapSearchTerms(title, search)}</h3>
</EuiText>
</EuiFlexItem>
<EuiFlexItem>
<EuiListGroup bordered={true}>{renderTasks(tasks, search)}</EuiListGroup>
</EuiFlexItem>
</EuiFlexGrid>
</EuiFlexItem>
</EuiFlexGroup>
);
}
export const MultiTaskTodoEmbeddableComponent = withEmbeddableSubscription(
MultiTaskTodoEmbeddableComponentInner
);

View file

@ -0,0 +1,99 @@
/*
* 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 ReactDOM from 'react-dom';
import { Subscription } from 'rxjs';
import {
Embeddable,
EmbeddableInput,
IContainer,
EmbeddableOutput,
} from '../../../../src/plugins/embeddable/public';
import { MultiTaskTodoEmbeddableComponent } from './multi_task_todo_component';
export const MULTI_TASK_TODO_EMBEDDABLE = 'MULTI_TASK_TODO_EMBEDDABLE';
export interface MultiTaskTodoInput extends EmbeddableInput {
tasks: string[];
icon?: string;
search?: string;
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
// derived from input state and updates when input does.
export interface MultiTaskTodoOutput extends EmbeddableOutput {
tasks: string[];
}
function getFilteredTasks(tasks: string[], search?: string) {
const filteredTasks: string[] = [];
if (search === undefined) return tasks;
tasks.forEach(task => {
if (task.match(search)) {
filteredTasks.push(task);
}
});
return filteredTasks;
}
function getOutput(input: MultiTaskTodoInput) {
const tasks = getFilteredTasks(input.tasks, input.search);
return { tasks, hasMatch: tasks.length > 0 || (input.search && input.title.match(input.search)) };
}
export class MultiTaskTodoEmbeddable extends Embeddable<MultiTaskTodoInput, MultiTaskTodoOutput> {
public readonly type = MULTI_TASK_TODO_EMBEDDABLE;
private subscription: Subscription;
private node?: HTMLElement;
constructor(initialInput: MultiTaskTodoInput, parent?: IContainer) {
super(initialInput, getOutput(initialInput), parent);
// If you have any output state that changes as a result of input state changes, you
// should use an subcription. Here, any time input tasks list, or the input filter
// changes, we want to update the output tasks list as well as whether a match has
// been found.
this.subscription = this.getInput$().subscribe(() => {
this.updateOutput(getOutput(this.input));
});
}
public render(node: HTMLElement) {
if (this.node) {
ReactDOM.unmountComponentAtNode(this.node);
}
this.node = node;
ReactDOM.render(<MultiTaskTodoEmbeddableComponent embeddable={this} />, node);
}
public reload() {}
public destroy() {
super.destroy();
this.subscription.unsubscribe();
if (this.node) {
ReactDOM.unmountComponentAtNode(this.node);
}
}
}

View file

@ -0,0 +1,44 @@
/*
* 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 { i18n } from '@kbn/i18n';
import { IContainer, EmbeddableFactory } from '../../../../src/plugins/embeddable/public';
import {
MultiTaskTodoEmbeddable,
MULTI_TASK_TODO_EMBEDDABLE,
MultiTaskTodoInput,
} from './multi_task_todo_embeddable';
export class MultiTaskTodoEmbeddableFactory extends EmbeddableFactory {
public readonly type = MULTI_TASK_TODO_EMBEDDABLE;
public isEditable() {
return true;
}
public async create(initialInput: MultiTaskTodoInput, parent?: IContainer) {
return new MultiTaskTodoEmbeddable(initialInput, parent);
}
public getDisplayName() {
return i18n.translate('embeddableExamples.multiTaskTodo.displayName', {
defaultMessage: 'Multi-task todo item',
});
}
}

View file

@ -0,0 +1,72 @@
/*
* 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 { IEmbeddableSetup, IEmbeddableStart } from '../../../src/plugins/embeddable/public';
import { Plugin, CoreSetup, CoreStart } from '../../../src/core/public';
import { HelloWorldEmbeddableFactory, HELLO_WORLD_EMBEDDABLE } from './hello_world';
import { TODO_EMBEDDABLE, TodoEmbeddableFactory } from './todo';
import { MULTI_TASK_TODO_EMBEDDABLE, MultiTaskTodoEmbeddableFactory } from './multi_task_todo';
import {
SEARCHABLE_LIST_CONTAINER,
SearchableListContainerFactory,
} from './searchable_list_container';
import { LIST_CONTAINER, ListContainerFactory } from './list_container';
interface EmbeddableExamplesSetupDependencies {
embeddable: IEmbeddableSetup;
}
interface EmbeddableExamplesStartDependencies {
embeddable: IEmbeddableStart;
}
export class EmbeddableExamplesPlugin
implements
Plugin<void, void, EmbeddableExamplesSetupDependencies, EmbeddableExamplesStartDependencies> {
public setup(core: CoreSetup, deps: EmbeddableExamplesSetupDependencies) {
deps.embeddable.registerEmbeddableFactory(
HELLO_WORLD_EMBEDDABLE,
new HelloWorldEmbeddableFactory()
);
deps.embeddable.registerEmbeddableFactory(TODO_EMBEDDABLE, new TodoEmbeddableFactory());
deps.embeddable.registerEmbeddableFactory(
MULTI_TASK_TODO_EMBEDDABLE,
new MultiTaskTodoEmbeddableFactory()
);
}
public start(core: CoreStart, deps: EmbeddableExamplesStartDependencies) {
// 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(deps.embeddable.getEmbeddableFactory)
);
deps.embeddable.registerEmbeddableFactory(
LIST_CONTAINER,
new ListContainerFactory(deps.embeddable.getEmbeddableFactory)
);
}
public stop() {}
}

View file

@ -0,0 +1,21 @@
/*
* 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.
*/
export { SearchableListContainer, SEARCHABLE_LIST_CONTAINER } from './searchable_list_container';
export { SearchableListContainerFactory } from './searchable_list_container_factory';

View file

@ -0,0 +1,70 @@
/*
* 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 ReactDOM from 'react-dom';
import {
Container,
ContainerInput,
GetEmbeddableFactory,
EmbeddableInput,
} from '../../../../src/plugins/embeddable/public';
import { SearchableListContainerComponent } from './searchable_list_container_component';
export const SEARCHABLE_LIST_CONTAINER = 'SEARCHABLE_LIST_CONTAINER';
export interface SearchableContainerInput extends ContainerInput {
search?: string;
}
interface ChildInput extends EmbeddableInput {
search?: string;
}
export class SearchableListContainer extends Container<ChildInput, SearchableContainerInput> {
public readonly type = SEARCHABLE_LIST_CONTAINER;
private node?: HTMLElement;
constructor(input: SearchableContainerInput, getEmbeddableFactory: GetEmbeddableFactory) {
super(input, { embeddableLoaded: {} }, getEmbeddableFactory);
}
// TODO: add a more advanced example here where inherited child input is derived from container
// input and not just an exact pass through.
getInheritedInput(id: string) {
return {
id,
search: this.getInput().search,
};
}
public render(node: HTMLElement) {
if (this.node) {
ReactDOM.unmountComponentAtNode(this.node);
}
this.node = node;
ReactDOM.render(<SearchableListContainerComponent embeddable={this} />, node);
}
public destroy() {
super.destroy();
if (this.node) {
ReactDOM.unmountComponentAtNode(this.node);
}
}
}

View file

@ -0,0 +1,188 @@
/*
* 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, { Component } from 'react';
import {
EuiLoadingSpinner,
EuiButton,
EuiFormRow,
EuiFlexGroup,
EuiSpacer,
EuiFlexItem,
EuiFieldText,
EuiPanel,
EuiCheckbox,
} from '@elastic/eui';
import * as Rx from 'rxjs';
import {
withEmbeddableSubscription,
ContainerOutput,
EmbeddableOutput,
} 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;
}
interface State {
checked: { [key: string]: boolean };
hasMatch: { [key: string]: boolean };
}
interface HasMatchOutput {
hasMatch: boolean;
}
function hasHasMatchOutput(output: EmbeddableOutput | HasMatchOutput): output is HasMatchOutput {
return (output as HasMatchOutput).hasMatch !== undefined;
}
export class SearchableListContainerComponentInner extends Component<Props, State> {
private subscriptions: { [id: string]: Rx.Subscription } = {};
constructor(props: Props) {
super(props);
const checked: { [id: string]: boolean } = {};
const hasMatch: { [id: string]: boolean } = {};
props.embeddable.getChildIds().forEach(id => {
checked[id] = false;
const output = props.embeddable.getChild(id).getOutput();
hasMatch[id] = hasHasMatchOutput(output) && output.hasMatch;
});
props.embeddable.getChildIds().forEach(id => (checked[id] = false));
this.state = {
checked,
hasMatch,
};
}
componentDidMount() {
this.props.embeddable.getChildIds().forEach(id => {
this.subscriptions[id] = this.props.embeddable
.getChild(id)
.getOutput$()
.subscribe(output => {
if (hasHasMatchOutput(output)) {
this.setState(prevState => ({
hasMatch: {
...prevState.hasMatch,
[id]: output.hasMatch,
},
}));
}
});
});
}
componentWillUnmount() {
Object.values(this.subscriptions).forEach(sub => sub.unsubscribe());
}
private updateSearch = (search: string) => {
this.props.embeddable.updateInput({ search });
};
private deleteChecked = () => {
Object.values(this.props.input.panels).map(panel => {
if (this.state.checked[panel.explicitInput.id]) {
this.props.embeddable.removeEmbeddable(panel.explicitInput.id);
this.subscriptions[panel.explicitInput.id].unsubscribe();
}
});
};
private toggleCheck = (isChecked: boolean, id: string) => {
this.setState(prevState => ({ checked: { ...prevState.checked, [id]: isChecked } }));
};
public renderControls() {
return (
<EuiFlexGroup>
<EuiFlexItem grow={false}>
<EuiFormRow hasEmptyLabelSpace>
<EuiButton data-test-subj="deleteCheckedTodos" onClick={() => this.deleteChecked()}>
Delete checked
</EuiButton>
</EuiFormRow>
</EuiFlexItem>
<EuiFlexItem>
<EuiFormRow label="Filter">
<EuiFieldText
data-test-subj="filterTodos"
value={this.props.input.search || ''}
onChange={ev => this.updateSearch(ev.target.value)}
/>
</EuiFormRow>
</EuiFlexItem>
<EuiFlexItem />
</EuiFlexGroup>
);
}
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>
);
}
private renderList() {
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;
id++;
return embeddable ? (
<EuiPanel key={embeddable.id}>
<EuiFlexGroup>
<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)}
/>
</EuiFlexItem>
<EuiFlexItem>
<EmbeddableListItem embeddable={embeddable} />
</EuiFlexItem>
</EuiFlexGroup>
</EuiPanel>
) : (
<EuiLoadingSpinner size="l" key={id} />
);
});
return list;
}
}
export const SearchableListContainerComponent = withEmbeddableSubscription(
SearchableListContainerComponentInner
);

View file

@ -0,0 +1,49 @@
/*
* 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 { i18n } from '@kbn/i18n';
import { EmbeddableFactory, GetEmbeddableFactory } from '../../../../src/plugins/embeddable/public';
import {
SEARCHABLE_LIST_CONTAINER,
SearchableListContainer,
SearchableContainerInput,
} from './searchable_list_container';
export class SearchableListContainerFactory extends EmbeddableFactory {
public readonly type = SEARCHABLE_LIST_CONTAINER;
public readonly isContainerType = true;
constructor(private getEmbeddableFactory: GetEmbeddableFactory) {
super();
}
public isEditable() {
return true;
}
public async create(initialInput: SearchableContainerInput) {
return new SearchableListContainer(initialInput, this.getEmbeddableFactory);
}
public getDisplayName() {
return i18n.translate('embeddableExamples.searchableListContainer.displayName', {
defaultMessage: 'Searchable list container',
});
}
}

View file

@ -0,0 +1,21 @@
/*
* 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.
*/
export * from './todo_embeddable';
export * from './todo_embeddable_factory';

View file

@ -0,0 +1,74 @@
/*
* 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 { EuiFlexItem, EuiFlexGroup } from '@elastic/eui';
import { EuiText } from '@elastic/eui';
import { EuiAvatar } from '@elastic/eui';
import { EuiIcon } from '@elastic/eui';
import { EuiFlexGrid } from '@elastic/eui';
import {
withEmbeddableSubscription,
EmbeddableOutput,
} from '../../../../src/plugins/embeddable/public';
import { TodoEmbeddable, TodoInput } from './todo_embeddable';
interface Props {
embeddable: TodoEmbeddable;
input: TodoInput;
output: EmbeddableOutput;
}
function wrapSearchTerms(task: string, search?: string) {
if (!search) return task;
const parts = task.split(new RegExp(`(${search})`, 'g'));
return parts.map((part, i) =>
part === search ? (
<span key={i} style={{ backgroundColor: 'yellow' }}>
{part}
</span>
) : (
part
)
);
}
export function TodoEmbeddableComponentInner({ input: { icon, title, task, search } }: Props) {
return (
<EuiFlexGroup>
<EuiFlexItem grow={false}>
{icon ? <EuiIcon type={icon} size="l" /> : <EuiAvatar name={title || task} size="l" />}
</EuiFlexItem>
<EuiFlexItem>
<EuiFlexGrid columns={1}>
<EuiFlexItem>
<EuiText data-test-subj="todoEmbeddableTitle">
<h3>{wrapSearchTerms(title || '', search)}</h3>
</EuiText>
</EuiFlexItem>
<EuiFlexItem>
<EuiText data-test-subj="todoEmbeddableTask">{wrapSearchTerms(task, search)}</EuiText>
</EuiFlexItem>
</EuiFlexGrid>
</EuiFlexItem>
</EuiFlexGroup>
);
}
export const TodoEmbeddableComponent = withEmbeddableSubscription(TodoEmbeddableComponentInner);

View file

@ -0,0 +1,88 @@
/*
* 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 ReactDOM from 'react-dom';
import { Subscription } from 'rxjs';
import {
Embeddable,
EmbeddableInput,
IContainer,
EmbeddableOutput,
} from '../../../../src/plugins/embeddable/public';
import { TodoEmbeddableComponent } from './todo_component';
export const TODO_EMBEDDABLE = 'TODO_EMBEDDABLE';
export interface TodoInput extends EmbeddableInput {
task: string;
icon?: string;
search?: string;
}
export interface TodoOutput extends EmbeddableOutput {
hasMatch: boolean;
}
function getOutput(input: TodoInput): TodoOutput {
return {
hasMatch: input.search
? Boolean(input.task.match(input.search) || (input.title && input.title.match(input.search)))
: true,
};
}
export class TodoEmbeddable extends Embeddable<TodoInput, TodoOutput> {
// The type of this embeddable. This will be used to find the appropriate factory
// to instantiate this kind of embeddable.
public readonly type = TODO_EMBEDDABLE;
private subscription: Subscription;
private node?: HTMLElement;
constructor(initialInput: TodoInput, parent?: IContainer) {
super(initialInput, getOutput(initialInput), parent);
// If you have any output state that changes as a result of input state changes, you
// should use an subcription. Here, we use output to indicate whether this task
// matches the search string.
this.subscription = this.getInput$().subscribe(() => {
this.updateOutput(getOutput(this.input));
});
}
public render(node: HTMLElement) {
this.node = node;
if (this.node) {
ReactDOM.unmountComponentAtNode(this.node);
}
ReactDOM.render(<TodoEmbeddableComponent embeddable={this} />, node);
}
/**
* Not relevant.
*/
public reload() {}
public destroy() {
super.destroy();
this.subscription.unsubscribe();
if (this.node) {
ReactDOM.unmountComponentAtNode(this.node);
}
}
}

View file

@ -0,0 +1,40 @@
/*
* 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 { i18n } from '@kbn/i18n';
import { IContainer, EmbeddableFactory } from '../../../../src/plugins/embeddable/public';
import { TodoEmbeddable, TODO_EMBEDDABLE, TodoInput } from './todo_embeddable';
export class TodoEmbeddableFactory extends EmbeddableFactory {
public readonly type = TODO_EMBEDDABLE;
public isEditable() {
return true;
}
public async create(initialInput: TodoInput, parent?: IContainer) {
return new TodoEmbeddable(initialInput, parent);
}
public getDisplayName() {
return i18n.translate('embeddableExamples.todo.displayName', {
defaultMessage: 'Todo item',
});
}
}

View file

@ -0,0 +1,15 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"outDir": "./target",
"skipLibCheck": true
},
"include": [
"index.ts",
"public/**/*.ts",
"public/**/*.tsx",
"server/**/*.ts",
"../../typings/**/*"
],
"exclude": []
}

View file

@ -0,0 +1,10 @@
{
"id": "embeddableExplorer",
"version": "0.0.1",
"kibanaVersion": "kibana",
"configPath": ["embeddable_explorer"],
"server": false,
"ui": true,
"requiredPlugins": ["embeddable", "embeddableExamples"],
"optionalPlugins": []
}

View file

@ -0,0 +1,17 @@
{
"name": "embeddable_explorer",
"version": "1.0.0",
"main": "target/examples/embeddable_explorer",
"kibana": {
"version": "kibana",
"templateVersion": "1.0.0"
},
"license": "Apache-2.0",
"scripts": {
"kbn": "node ../../scripts/kbn.js",
"build": "rm -rf './target' && tsc"
},
"devDependencies": {
"typescript": "3.7.2"
}
}

View file

@ -0,0 +1,126 @@
/*
* 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 ReactDOM from 'react-dom';
import { BrowserRouter as Router, Route, withRouter, RouteComponentProps } from 'react-router-dom';
import { EuiPage, EuiPageSideBar, EuiSideNav } from '@elastic/eui';
import { IEmbeddableStart } from 'src/plugins/embeddable/public';
import { AppMountContext, AppMountParameters, CoreStart } from '../../../src/core/public';
import { HelloWorldEmbeddableExample } from './hello_world_embeddable_example';
import { TodoEmbeddableExample } from './todo_embeddable_example';
import { ListContainerExample } from './list_container_example';
interface PageDef {
title: string;
id: string;
component: React.ReactNode;
}
type NavProps = RouteComponentProps & {
navigateToApp: AppMountContext['core']['application']['navigateToApp'];
pages: PageDef[];
};
const Nav = withRouter(({ history, navigateToApp, pages }: NavProps) => {
const navItems = pages.map(page => ({
id: page.id,
name: page.title,
onClick: () => history.push(`/${page.id}`),
'data-test-subj': page.id,
}));
return (
<EuiSideNav
items={[
{
name: 'Embeddable explorer',
id: 'home',
items: [...navItems],
},
]}
/>
);
});
const EmbeddableExplorerApp = ({
basename,
navigateToApp,
embeddableApi,
}: {
basename: string;
navigateToApp: CoreStart['application']['navigateToApp'];
embeddableApi: IEmbeddableStart;
}) => {
const pages: PageDef[] = [
{
title: 'Hello world embeddable',
id: 'helloWorldEmbeddableSection',
component: (
<HelloWorldEmbeddableExample getEmbeddableFactory={embeddableApi.getEmbeddableFactory} />
),
},
{
title: 'Todo embeddable',
id: 'todoEmbeddableSection',
component: (
<TodoEmbeddableExample getEmbeddableFactory={embeddableApi.getEmbeddableFactory} />
),
},
{
title: 'List container embeddable',
id: 'listContainerSection',
component: <ListContainerExample getEmbeddableFactory={embeddableApi.getEmbeddableFactory} />,
},
];
const routes = pages.map((page, i) => (
<Route key={i} path={`/${page.id}`} render={props => page.component} />
));
return (
<Router basename={basename}>
<EuiPage>
<EuiPageSideBar>
<Nav navigateToApp={navigateToApp} pages={pages} />
</EuiPageSideBar>
{routes}
</EuiPage>
</Router>
);
};
export const renderApp = (
core: CoreStart,
embeddableApi: IEmbeddableStart,
{ appBasePath, element }: AppMountParameters
) => {
ReactDOM.render(
<EmbeddableExplorerApp
basename={appBasePath}
navigateToApp={core.application.navigateToApp}
embeddableApi={embeddableApi}
/>,
element
);
return () => ReactDOM.unmountComponentAtNode(element);
};

View file

@ -0,0 +1,83 @@
/*
* 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,
EuiPageBody,
EuiPageContent,
EuiPageContentBody,
EuiPageHeader,
EuiPageHeaderSection,
EuiTitle,
EuiText,
} from '@elastic/eui';
import {
GetEmbeddableFactory,
EmbeddableFactoryRenderer,
EmbeddableRoot,
} from '../../../src/plugins/embeddable/public';
import { HelloWorldEmbeddable, HELLO_WORLD_EMBEDDABLE } from '../../embeddable_examples/public';
interface Props {
getEmbeddableFactory: GetEmbeddableFactory;
}
export function HelloWorldEmbeddableExample({ getEmbeddableFactory }: Props) {
return (
<EuiPageBody>
<EuiPageHeader>
<EuiPageHeaderSection>
<EuiTitle size="l">
<h1>Hello world example</h1>
</EuiTitle>
</EuiPageHeaderSection>
</EuiPageHeader>
<EuiPageContent>
<EuiPageContentBody>
<EuiText>
Here the embeddable is rendered without the factory. A developer may use this method if
they want to statically embed a single embeddable into their application or page.
</EuiText>
<EuiPanel data-test-subj="helloWorldEmbeddablePanel" paddingSize="none" role="figure">
<EmbeddableRoot embeddable={new HelloWorldEmbeddable({ id: 'hello' })} />
</EuiPanel>
<EuiText>
Here the embeddable is rendered using the factory.create method. This method is used
programatically when a container embeddable attempts to initialize it&#39;s children
embeddables. This method can be used when you only have a string representing the type
of embeddable, and input data.
</EuiText>
<EuiPanel
data-test-subj="helloWorldEmbeddableFromFactory"
paddingSize="none"
role="figure"
>
<EmbeddableFactoryRenderer
getEmbeddableFactory={getEmbeddableFactory}
type={HELLO_WORLD_EMBEDDABLE}
input={{ id: '1234' }}
/>
</EuiPanel>
</EuiPageContentBody>
</EuiPageContent>
</EuiPageBody>
);
}

View file

@ -0,0 +1,22 @@
/*
* 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 { EmbeddableExplorerPlugin } from './plugin';
export const plugin = () => new EmbeddableExplorerPlugin();

View file

@ -0,0 +1,182 @@
/*
* 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,
EuiPageBody,
EuiPageContent,
EuiPageContentBody,
EuiPageHeader,
EuiPageHeaderSection,
EuiTitle,
EuiText,
} from '@elastic/eui';
import { EuiSpacer } from '@elastic/eui';
import {
GetEmbeddableFactory,
EmbeddableFactoryRenderer,
} from '../../../src/plugins/embeddable/public';
import {
HELLO_WORLD_EMBEDDABLE,
TODO_EMBEDDABLE,
MULTI_TASK_TODO_EMBEDDABLE,
SEARCHABLE_LIST_CONTAINER,
LIST_CONTAINER,
} from '../../embeddable_examples/public';
interface Props {
getEmbeddableFactory: GetEmbeddableFactory;
}
export function ListContainerExample({ getEmbeddableFactory }: Props) {
const listInput = {
id: 'hello',
title: 'My todo list',
panels: {
'1': {
type: HELLO_WORLD_EMBEDDABLE,
explicitInput: {
id: '1',
},
},
'2': {
type: TODO_EMBEDDABLE,
explicitInput: {
id: '2',
task: 'Goes out on Wenesdays!',
icon: 'broom',
title: 'Take out the trash',
},
},
'3': {
type: TODO_EMBEDDABLE,
explicitInput: {
id: '3',
icon: 'broom',
title: 'Vaccum the floor',
},
},
},
};
const searchableInput = {
id: '1',
title: 'My searchable todo list',
panels: {
'1': {
type: HELLO_WORLD_EMBEDDABLE,
explicitInput: {
id: '1',
title: 'Hello',
},
},
'2': {
type: TODO_EMBEDDABLE,
explicitInput: {
id: '2',
task: 'Goes out on Wenesdays!',
icon: 'broom',
title: 'Take out the trash',
},
},
'3': {
type: MULTI_TASK_TODO_EMBEDDABLE,
explicitInput: {
id: '3',
icon: 'searchProfilerApp',
title: 'Learn more',
tasks: ['Go to school', 'Watch planet earth', 'Read the encylopedia'],
},
},
},
};
return (
<EuiPageBody>
<EuiPageHeader>
<EuiPageHeaderSection>
<EuiTitle size="l">
<h1>List container example</h1>
</EuiTitle>
</EuiPageHeaderSection>
</EuiPageHeader>
<EuiPageContent>
<EuiPageContentBody>
<EuiText>
Here is a container embeddable that contains other embeddables and displays them in a
list.
</EuiText>
<EuiPanel data-test-subj="listContainerEmbeddablePanel" paddingSize="none" role="figure">
<EmbeddableFactoryRenderer
getEmbeddableFactory={getEmbeddableFactory}
type={LIST_CONTAINER}
input={listInput}
/>
</EuiPanel>
<EuiSpacer />
<EuiText>
<p>
The reason to use a container embeddable instead of just a custom react component is
because it comes with helpful methods to store the state of all its children
embeddables, listeners to clean up the input state when an embeddable is added or
removed, and a way to pass down the container embeddable&#39;s own input to its
children. In the above example, the container did not take any input. Let&#39;s modify
it so it does.
</p>
<p>
In this Searchable List Container, the container takes in a search string as input and
passes that down to all its children. It also listens to its children that output
`hasMatch`, and removes them from the list when there is a search string and the child
doesn&#39;t match.
</p>
<p>
The first HelloWorldEmbeddable does not emit the hasMatch output variable, so the
container chooses to hide it.
</p>
</EuiText>
<EuiSpacer />
<EuiPanel
data-test-subj="searchableListContainerEmbeddablePanel"
paddingSize="none"
role="figure"
>
<EmbeddableFactoryRenderer
getEmbeddableFactory={getEmbeddableFactory}
type={SEARCHABLE_LIST_CONTAINER}
input={searchableInput}
/>{' '}
</EuiPanel>
<EuiSpacer />
<EuiText>
<p>
There currently is no formal way to limit what children can be added to a container.
If the use case arose, it wouldn&#39;t be difficult. In the mean time, it&#39;s good
to understand that chilren may ignore input they don&#39;t care about. Likewise the
container will have to choose what to do when it encounters children that are missing
certain output variables.
</p>
</EuiText>
</EuiPageContentBody>
</EuiPageContent>
</EuiPageBody>
);
}

View file

@ -0,0 +1,38 @@
/*
* 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 { Plugin, CoreSetup, AppMountParameters } from 'kibana/public';
import { IEmbeddableStart } from '../../../src/plugins/embeddable/public';
export class EmbeddableExplorerPlugin implements Plugin {
public setup(core: CoreSetup<{ embeddable: IEmbeddableStart }>) {
core.application.register({
id: 'embeddableExplorer',
title: 'Embeddable explorer',
async mount(params: AppMountParameters) {
const [coreStart, depsStart] = await core.getStartServices();
const { renderApp } = await import('./app');
return renderApp(coreStart, depsStart.embeddable, params);
},
});
}
public start() {}
public stop() {}
}

View file

@ -0,0 +1,172 @@
/*
* 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 {
EuiFormRow,
EuiFieldText,
EuiPanel,
EuiTextArea,
EuiPageBody,
EuiPageContent,
EuiPageContentBody,
EuiPageHeader,
EuiPageHeaderSection,
EuiTitle,
EuiText,
EuiButton,
EuiFlexGroup,
EuiFlexItem,
} from '@elastic/eui';
import {
TodoEmbeddable,
TODO_EMBEDDABLE,
TodoEmbeddableFactory,
} from '../../../examples/embeddable_examples/public/todo';
import { GetEmbeddableFactory, EmbeddableRoot } from '../../../src/plugins/embeddable/public';
interface Props {
getEmbeddableFactory: GetEmbeddableFactory;
}
interface State {
task?: string;
title?: string;
icon?: string;
loading: boolean;
}
export class TodoEmbeddableExample extends React.Component<Props, State> {
private embeddable?: TodoEmbeddable;
constructor(props: Props) {
super(props);
this.state = { loading: true };
}
public componentDidMount() {
const factory = this.props.getEmbeddableFactory(TODO_EMBEDDABLE) as TodoEmbeddableFactory;
if (factory === undefined) {
throw new Error('Embeddable factory is undefined!');
}
factory
.create({
id: '1',
task: 'Take out the trash',
icon: 'broom',
title: 'Trash',
})
.then(embeddable => {
this.embeddable = embeddable;
this.setState({ loading: false });
});
}
public componentWillUnmount() {
if (this.embeddable) {
this.embeddable.destroy();
}
}
private onUpdateEmbeddableInput = () => {
if (this.embeddable) {
const { task, title, icon } = this.state;
this.embeddable.updateInput({ task, title, icon });
}
};
public render() {
return (
<EuiPageBody>
<EuiPageHeader>
<EuiPageHeaderSection>
<EuiTitle size="l">
<h1>Todo example</h1>
</EuiTitle>
</EuiPageHeaderSection>
</EuiPageHeader>
<EuiPageContent>
<EuiPageContentBody>
<EuiText>
This embeddable takes input parameters, task, title and icon. You can update them
using this form. In the code, pressing update will call
<pre>
<code>
const &#123; task, title, icon &#125; = this.state;
<br />
this.embeddable.updateInput(&#123; task, title, icon &#125;);
</code>
</pre>
<p>
You may also notice this example uses `EmbeddableRoot` instead of the
`EmbeddableFactoryRenderer` helper component. This is because it needs a reference
to the embeddable to update it, and `EmbeddableFactoryRenderer` creates and holds
the embeddable instance internally.
</p>
</EuiText>
<EuiFlexGroup>
<EuiFlexItem grow={true}>
<EuiFormRow label="Title">
<EuiFieldText
data-test-subj="titleTodo"
onChange={ev => this.setState({ title: ev.target.value })}
/>
</EuiFormRow>
</EuiFlexItem>
<EuiFlexItem grow={true}>
<EuiFormRow label="Icon">
<EuiFieldText
data-test-subj="iconTodo"
onChange={ev => this.setState({ icon: ev.target.value })}
/>
</EuiFormRow>
</EuiFlexItem>
<EuiFlexItem>
<EuiFormRow label="Task">
<EuiTextArea
fullWidth
resize="horizontal"
data-test-subj="taskTodo"
onChange={ev => this.setState({ task: ev.target.value })}
/>
</EuiFormRow>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiFormRow hasEmptyLabelSpace>
<EuiButton
data-test-subj="updateTodoButton"
onClick={this.onUpdateEmbeddableInput}
>
Update
</EuiButton>
</EuiFormRow>
</EuiFlexItem>
</EuiFlexGroup>
<EuiPanel data-test-subj="todoEmbeddable" paddingSize="none" role="figure">
<EmbeddableRoot embeddable={this.embeddable} loading={this.state.loading} />
</EuiPanel>
</EuiPageContentBody>
</EuiPageContent>
</EuiPageBody>
);
}
}

View file

@ -0,0 +1,15 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"outDir": "./target",
"skipLibCheck": true
},
"include": [
"index.ts",
"public/**/*.ts",
"public/**/*.tsx",
"server/**/*.ts",
"../../typings/**/*",
],
"exclude": []
}

View file

@ -1,7 +1,7 @@
{
"name": "search_explorer",
"version": "1.0.0",
"main": "target/test/plugin_functional/plugins/search_explorer",
"main": "target/examples/search_explorer",
"kibana": {
"version": "kibana",
"templateVersion": "1.0.0"
@ -12,6 +12,6 @@
"build": "rm -rf './target' && tsc"
},
"devDependencies": {
"typescript": "3.5.3"
"typescript": "3.7.2"
}
}

View file

@ -152,6 +152,8 @@ function applyConfigOverrides(rawConfig, opts, extraCliOptions) {
// Ideally this would automatically include all plugins in the examples dir
fromRoot('examples/demo_search'),
fromRoot('examples/search_explorer'),
fromRoot('examples/embeddable_examples'),
fromRoot('examples/embeddable_explorer'),
]
: [],

View file

@ -55,6 +55,9 @@ export {
ViewMode,
isErrorEmbeddable,
openAddPanelFlyout,
withEmbeddableSubscription,
EmbeddableFactoryRenderer,
EmbeddableRoot,
} from './lib';
export function plugin(initializerContext: PluginInitializerContext) {

View file

@ -45,7 +45,7 @@ export interface ContainerOutput extends EmbeddableOutput {
export interface ContainerInput<PanelExplicitInput = {}> extends EmbeddableInput {
hidePanelTitles?: boolean;
panels: {
[key: string]: PanelState<PanelExplicitInput & { id: string }>;
[key: string]: PanelState<PanelExplicitInput & { [id: string]: unknown } & { id: string }>;
};
}

View file

@ -0,0 +1,71 @@
/*
* 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 {
HELLO_WORLD_EMBEDDABLE,
HelloWorldEmbeddableFactory,
} from '../../../../../../examples/embeddable_examples/public';
import { EmbeddableFactory } from './embeddable_factory';
import { GetEmbeddableFactory } from '../types';
import { EmbeddableFactoryRenderer } from './embeddable_factory_renderer';
import { mount } from 'enzyme';
import { nextTick } from 'test_utils/enzyme_helpers';
// @ts-ignore
import { findTestSubject } from '@elastic/eui/lib/test';
test('EmbeddableFactoryRenderer renders an embeddable', async () => {
const embeddableFactories = new Map<string, EmbeddableFactory>();
embeddableFactories.set(HELLO_WORLD_EMBEDDABLE, new HelloWorldEmbeddableFactory());
const getEmbeddableFactory: GetEmbeddableFactory = (id: string) => embeddableFactories.get(id);
const component = mount(
<EmbeddableFactoryRenderer
getEmbeddableFactory={getEmbeddableFactory}
type={HELLO_WORLD_EMBEDDABLE}
input={{ id: '123' }}
/>
);
await nextTick();
component.update();
// Due to the way embeddables mount themselves on the dom node, they are not forced to be
// react components, and hence, we can't use the usual
// findTestSubject(component, 'subjIdHere');
expect(
component.getDOMNode().querySelectorAll('[data-test-subj="helloWorldEmbeddable"]').length
).toBe(1);
});
test('EmbeddableRoot renders an error if the type does not exist', async () => {
const getEmbeddableFactory: GetEmbeddableFactory = (id: string) => undefined;
const component = mount(
<EmbeddableFactoryRenderer
getEmbeddableFactory={getEmbeddableFactory}
type={HELLO_WORLD_EMBEDDABLE}
input={{ id: '123' }}
/>
);
await nextTick();
component.update();
expect(findTestSubject(component, 'embedSpinner').length).toBe(0);
expect(findTestSubject(component, 'embedError').length).toBe(1);
});

View file

@ -0,0 +1,80 @@
/*
* 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 { i18n } from '@kbn/i18n';
import { IEmbeddable, EmbeddableInput } from './i_embeddable';
import { EmbeddableRoot } from './embeddable_root';
import { GetEmbeddableFactory } from '../types';
interface Props {
type: string;
getEmbeddableFactory: GetEmbeddableFactory;
input: EmbeddableInput;
}
interface State {
loading: boolean;
error?: string;
}
export class EmbeddableFactoryRenderer extends React.Component<Props, State> {
private embeddable?: IEmbeddable;
constructor(props: Props) {
super(props);
this.state = {
loading: true,
error: undefined,
};
}
public componentDidMount() {
const factory = this.props.getEmbeddableFactory(this.props.type);
if (factory === undefined) {
this.setState({
loading: false,
error: i18n.translate('embeddableApi.errors.factoryDoesNotExist', {
defaultMessage:
'Embeddable factory of {type} does not exist. Ensure all neccessary plugins are installed and enabled.',
values: {
type: this.props.type,
},
}),
});
return;
}
factory.create(this.props.input).then(embeddable => {
this.embeddable = embeddable;
this.setState({ loading: false });
});
}
public render() {
return (
<EmbeddableRoot
embeddable={this.embeddable}
loading={this.state.loading}
error={this.state.error}
/>
);
}
}

View file

@ -0,0 +1,55 @@
/*
* 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 { HelloWorldEmbeddable } from '../../../../../../examples/embeddable_examples/public';
import { EmbeddableRoot } from './embeddable_root';
import { mount } from 'enzyme';
// @ts-ignore
import { findTestSubject } from '@elastic/eui/lib/test';
test('EmbeddableRoot renders an embeddable', async () => {
const embeddable = new HelloWorldEmbeddable({ id: 'hello' });
const component = mount(<EmbeddableRoot embeddable={embeddable} />);
// Due to the way embeddables mount themselves on the dom node, they are not forced to be
// react components, and hence, we can't use the usual
// findTestSubject.
expect(
component.getDOMNode().querySelectorAll('[data-test-subj="helloWorldEmbeddable"]').length
).toBe(1);
expect(findTestSubject(component, 'embedSpinner').length).toBe(0);
expect(findTestSubject(component, 'embedError').length).toBe(0);
});
test('EmbeddableRoot renders a spinner if loading an no embeddable given', async () => {
const component = mount(<EmbeddableRoot loading={true} />);
// Due to the way embeddables mount themselves on the dom node, they are not forced to be
// react components, and hence, we can't use the usual
// findTestSubject.
expect(findTestSubject(component, 'embedSpinner').length).toBe(1);
expect(findTestSubject(component, 'embedError').length).toBe(0);
});
test('EmbeddableRoot renders an error if given with no embeddable', async () => {
const component = mount(<EmbeddableRoot error="bad" />);
// Due to the way embeddables mount themselves on the dom node, they are not forced to be
// react components, and hence, we can't use the usual
// findTestSubject.
expect(findTestSubject(component, 'embedError').length).toBe(1);
expect(findTestSubject(component, 'embedSpinner').length).toBe(0);
});

View file

@ -0,0 +1,73 @@
/*
* 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 { EuiLoadingSpinner } from '@elastic/eui';
import { EuiText } from '@elastic/eui';
import { IEmbeddable } from './i_embeddable';
interface Props {
embeddable?: IEmbeddable;
loading?: boolean;
error?: string;
}
export class EmbeddableRoot extends React.Component<Props> {
private root?: React.RefObject<HTMLDivElement>;
private alreadyMounted: boolean = false;
constructor(props: Props) {
super(props);
this.root = React.createRef();
}
public componentDidMount() {
if (this.root && this.root.current && this.props.embeddable) {
this.alreadyMounted = true;
this.props.embeddable.render(this.root.current);
}
}
public componentDidUpdate() {
if (this.root && this.root.current && this.props.embeddable && !this.alreadyMounted) {
this.alreadyMounted = true;
this.props.embeddable.render(this.root.current);
}
}
public shouldComponentUpdate(newProps: Props) {
return Boolean(
newProps.error !== this.props.error ||
newProps.loading !== this.props.loading ||
newProps.embeddable !== this.props.embeddable ||
(this.root && this.root.current && newProps.embeddable && !this.alreadyMounted)
);
}
public render() {
return (
<React.Fragment>
<div ref={this.root} />
{this.props.loading && <EuiLoadingSpinner data-test-subj="embedSpinner" />}
{this.props.error && <EuiText data-test-subj="embedError">{this.props.error}</EuiText>}
</React.Fragment>
);
}
}

View file

@ -24,3 +24,6 @@ export {
OutputSpec,
} from './embeddable_factory';
export { ErrorEmbeddable, isErrorEmbeddable } from './error_embeddable';
export { withEmbeddableSubscription } from './with_subscription';
export { EmbeddableFactoryRenderer } from './embeddable_factory_renderer';
export { EmbeddableRoot } from './embeddable_root';

View file

@ -0,0 +1,77 @@
/*
* 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 * as Rx from 'rxjs';
import { IEmbeddable, EmbeddableInput, EmbeddableOutput } from './i_embeddable';
export const withEmbeddableSubscription = <
I extends EmbeddableInput,
O extends EmbeddableOutput,
E extends IEmbeddable<I, O> = IEmbeddable<I, O>
>(
WrappedComponent: React.ComponentType<{ input: I; output: O; embeddable: E }>
): React.ComponentType<{ embeddable: E }> =>
class WithEmbeddableSubscription extends React.Component<
{ embeddable: E },
{ input: I; output: O }
> {
private subscription?: Rx.Subscription;
private mounted: boolean = false;
constructor(props: { embeddable: E }) {
super(props);
this.state = {
input: this.props.embeddable.getInput(),
output: this.props.embeddable.getOutput(),
};
}
componentDidMount() {
this.mounted = true;
this.subscription = Rx.merge(
this.props.embeddable.getOutput$(),
this.props.embeddable.getInput$()
).subscribe(() => {
if (this.mounted) {
this.setState({
input: this.props.embeddable.getInput(),
output: this.props.embeddable.getOutput(),
});
}
});
}
componentWillUnmount() {
this.mounted = false;
if (this.subscription) {
this.subscription.unsubscribe();
}
}
render() {
return (
<WrappedComponent
input={this.state.input}
output={this.state.output}
embeddable={this.props.embeddable}
/>
);
}
};

View file

@ -23,6 +23,5 @@ export * from './filterable_container';
export * from './filterable_container_factory';
export * from './filterable_embeddable';
export * from './filterable_embeddable_factory';
export * from './hello_world';
export * from './hello_world_container';
export * from './hello_world_container_component';

View file

@ -29,8 +29,10 @@ import { ERROR_EMBEDDABLE_TYPE } from '../lib/embeddables/error_embeddable';
import { FilterableEmbeddableFactory } from '../lib/test_samples/embeddables/filterable_embeddable_factory';
import { CONTACT_CARD_EMBEDDABLE } from '../lib/test_samples/embeddables/contact_card/contact_card_embeddable_factory';
import { SlowContactCardEmbeddableFactory } from '../lib/test_samples/embeddables/contact_card/slow_contact_card_embeddable_factory';
import { HELLO_WORLD_EMBEDDABLE_TYPE } from '../lib/test_samples/embeddables/hello_world/hello_world_embeddable';
import { HelloWorldEmbeddableFactory } from '../lib/test_samples/embeddables/hello_world/hello_world_embeddable_factory';
import {
HELLO_WORLD_EMBEDDABLE,
HelloWorldEmbeddableFactory,
} from '../../../../../examples/embeddable_examples/public';
import { HelloWorldContainer } from '../lib/test_samples/embeddables/hello_world_container';
import {
ContactCardEmbeddableInput,
@ -730,7 +732,7 @@ test('untilEmbeddableLoaded() resolves if child is loaded in the container', asy
id: 'hello',
panels: {
'123': {
type: HELLO_WORLD_EMBEDDABLE_TYPE,
type: HELLO_WORLD_EMBEDDABLE,
explicitInput: { id: '123' },
},
},
@ -748,7 +750,7 @@ test('untilEmbeddableLoaded() resolves if child is loaded in the container', asy
const child = await container.untilEmbeddableLoaded('123');
expect(child).toBeDefined();
expect(child.type).toBe(HELLO_WORLD_EMBEDDABLE_TYPE);
expect(child.type).toBe(HELLO_WORLD_EMBEDDABLE);
done();
});

View file

@ -26,8 +26,10 @@ import {
import { FilterableEmbeddableFactory } from '../lib/test_samples/embeddables/filterable_embeddable_factory';
import { CONTACT_CARD_EMBEDDABLE } from '../lib/test_samples/embeddables/contact_card/contact_card_embeddable_factory';
import { SlowContactCardEmbeddableFactory } from '../lib/test_samples/embeddables/contact_card/slow_contact_card_embeddable_factory';
import { HELLO_WORLD_EMBEDDABLE_TYPE } from '../lib/test_samples/embeddables/hello_world/hello_world_embeddable';
import { HelloWorldEmbeddableFactory } from '../lib/test_samples/embeddables/hello_world/hello_world_embeddable_factory';
import {
HELLO_WORLD_EMBEDDABLE,
HelloWorldEmbeddableFactory,
} from '../../../../../examples/embeddable_examples/public';
import { FilterableContainer } from '../lib/test_samples/embeddables/filterable_container';
import { isErrorEmbeddable } from '../lib';
import { HelloWorldContainer } from '../lib/test_samples/embeddables/hello_world_container';
@ -47,7 +49,7 @@ const factory = new SlowContactCardEmbeddableFactory({
execAction: uiActions.executeTriggerActions,
});
setup.registerEmbeddableFactory(CONTACT_CARD_EMBEDDABLE, factory);
setup.registerEmbeddableFactory(HELLO_WORLD_EMBEDDABLE_TYPE, new HelloWorldEmbeddableFactory());
setup.registerEmbeddableFactory(HELLO_WORLD_EMBEDDABLE, new HelloWorldEmbeddableFactory());
test('Explicit embeddable input mapped to undefined will default to inherited', async () => {
const derivedFilter: esFilters.Filter = {

View file

@ -21,6 +21,7 @@ import { CoreSetup, CoreStart } from 'src/core/public';
// eslint-disable-next-line
import { uiActionsTestPlugin } from 'src/plugins/ui_actions/public/tests';
import { IUiActionsApi } from 'src/plugins/ui_actions/public';
import { coreMock } from '../../../../core/public/mocks';
import { EmbeddablePublicPlugin, IEmbeddableSetup, IEmbeddableStart } from '../plugin';
export interface TestPluginReturn {
@ -33,8 +34,8 @@ export interface TestPluginReturn {
}
export const testPlugin = (
coreSetup: CoreSetup = {} as CoreSetup,
coreStart: CoreStart = {} as CoreStart
coreSetup: CoreSetup = coreMock.createSetup(),
coreStart: CoreStart = coreMock.createStart()
): TestPluginReturn => {
const uiActions = uiActionsTestPlugin(coreSetup, coreStart);
const initializerContext = {} as any;

View file

@ -252,6 +252,18 @@ module.exports = function(grunt) {
],
}),
exampleFunctionalTestsRelease: scriptWithGithubChecks({
title: 'Example functional tests',
cmd: NODE,
args: [
'scripts/functional_tests',
'--config',
'test/examples/config.js',
'--bail',
'--debug',
],
}),
functionalTests: scriptWithGithubChecks({
title: 'Functional tests',
cmd: NODE,

View file

@ -24,7 +24,7 @@ export default async function({ readConfigFile }) {
const functionalConfig = await readConfigFile(require.resolve('../functional/config'));
return {
testFiles: [require.resolve('./search')],
testFiles: [require.resolve('./search'), require.resolve('./embeddables')],
services: {
...functionalConfig.get('services'),
...services,

View file

@ -19,20 +19,30 @@
import expect from '@kbn/expect';
export default function({ getService }) {
import { PluginFunctionalProviderContext } from 'test/plugin_functional/services';
// eslint-disable-next-line import/no-default-export
export default function({ getService }: PluginFunctionalProviderContext) {
const testSubjects = getService('testSubjects');
const retry = getService('retry');
describe('hello world embeddable', () => {
before(async () => {
await testSubjects.click('embedExplorerTab-helloWorldEmbeddable');
await testSubjects.click('helloWorldEmbeddableSection');
});
it('hello world embeddable renders', async () => {
it('hello world embeddable render', async () => {
await retry.try(async () => {
const text = await testSubjects.getVisibleText('helloWorldEmbeddable');
expect(text).to.be('HELLO WORLD!');
});
});
it('hello world embeddable from factory renders', async () => {
await retry.try(async () => {
const text = await testSubjects.getVisibleText('helloWorldEmbeddableFromFactory');
expect(text).to.be('HELLO WORLD!');
});
});
});
}

View file

@ -0,0 +1,43 @@
/*
* 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 { PluginFunctionalProviderContext } from 'test/plugin_functional/services';
// eslint-disable-next-line import/no-default-export
export default function({
getService,
getPageObjects,
loadTestFile,
}: PluginFunctionalProviderContext) {
const browser = getService('browser');
const appsMenu = getService('appsMenu');
const PageObjects = getPageObjects(['common', 'header']);
describe('embeddable explorer', function() {
before(async () => {
await browser.setWindowSize(1300, 900);
await PageObjects.common.navigateToApp('settings');
await appsMenu.clickLink('Embeddable explorer');
});
loadTestFile(require.resolve('./hello_world_embeddable'));
loadTestFile(require.resolve('./todo_embeddable'));
loadTestFile(require.resolve('./list_container'));
});
}

View file

@ -0,0 +1,69 @@
/*
* 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 expect from '@kbn/expect';
import { PluginFunctionalProviderContext } from 'test/plugin_functional/services';
// eslint-disable-next-line import/no-default-export
export default function({ getService }: PluginFunctionalProviderContext) {
const testSubjects = getService('testSubjects');
const retry = getService('retry');
describe('list container', () => {
before(async () => {
await testSubjects.click('listContainerSection');
});
it('list containers render', async () => {
await retry.try(async () => {
const title = await testSubjects.getVisibleText('listContainerTitle');
expect(title).to.be('My todo list');
const titles = await testSubjects.getVisibleTextAll('todoEmbeddableTitle');
expect(titles).to.eql(['Take out the trash', 'Vaccum the floor', 'Take out the trash']);
const searchableTitle = await testSubjects.getVisibleText('searchableListContainerTitle');
expect(searchableTitle).to.be('My searchable todo list');
const text = await testSubjects.getVisibleTextAll('helloWorldEmbeddable');
expect(text).to.eql(['HELLO WORLD!', 'HELLO WORLD!']);
const tasks = await testSubjects.getVisibleTextAll('multiTaskTodoTask');
expect(tasks).to.eql(['Go to school', 'Watch planet earth', 'Read the encylopedia']);
});
});
it('searchable container deletes children', async () => {
await testSubjects.click('todoCheckBox-1');
await testSubjects.click('deleteCheckedTodos');
const text = await testSubjects.getVisibleTextAll('helloWorldEmbeddable');
expect(text).to.eql(['HELLO WORLD!']);
});
it('searchable container filters multi-task children', async () => {
await testSubjects.setValue('filterTodos', 'earth');
await retry.try(async () => {
const tasks = await testSubjects.getVisibleTextAll('multiTaskTodoTask');
expect(tasks).to.eql(['Watch planet earth']);
});
});
});
}

View file

@ -0,0 +1,55 @@
/*
* 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 expect from '@kbn/expect';
import { PluginFunctionalProviderContext } from 'test/plugin_functional/services';
// eslint-disable-next-line import/no-default-export
export default function({ getService }: PluginFunctionalProviderContext) {
const testSubjects = getService('testSubjects');
const retry = getService('retry');
describe('todo embeddable', () => {
before(async () => {
await testSubjects.click('todoEmbeddableSection');
});
it('todo embeddable renders', async () => {
await retry.try(async () => {
const title = await testSubjects.getVisibleText('todoEmbeddableTitle');
expect(title).to.be('Trash');
const task = await testSubjects.getVisibleText('todoEmbeddableTask');
expect(task).to.be('Take out the trash');
});
});
it('todo embeddable updates', async () => {
await testSubjects.setValue('taskTodo', 'read a book');
await testSubjects.setValue('titleTodo', 'Learn');
await testSubjects.click('updateTodoButton');
await retry.try(async () => {
const title = await testSubjects.getVisibleText('todoEmbeddableTitle');
expect(title).to.be('Learn');
const task = await testSubjects.getVisibleText('todoEmbeddableTask');
expect(task).to.be('read a book');
});
});
});
}

View file

@ -24,8 +24,6 @@ import {
GetEmbeddableFactories,
} from 'src/legacy/core_plugins/embeddable_api/public/np_ready/public';
import { TGetActionsCompatibleWithTrigger } from '../../../../../../../../src/plugins/ui_actions/public';
import { ContactCardEmbeddableExample } from './hello_world_embeddable_example';
import { HelloWorldContainerExample } from './hello_world_container_example';
import { DashboardContainerExample } from './dashboard_container_example';
import { Start as InspectorStartContract } from '../../../../../../../../src/plugins/inspector/public';
@ -45,14 +43,6 @@ export class App extends Component<AppProps, { selectedTabId: string }> {
constructor(props: AppProps) {
super(props);
this.tabs = [
{
id: 'helloWorldContainer',
name: 'Hello World Container',
},
{
id: 'helloWorldEmbeddable',
name: 'Hello World Embeddable',
},
{
id: 'dashboardContainer',
name: 'Dashboard Container',
@ -96,32 +86,6 @@ export class App extends Component<AppProps, { selectedTabId: string }> {
private getContentsForTab() {
switch (this.state.selectedTabId) {
case 'helloWorldContainer': {
return (
<HelloWorldContainerExample
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}
/>
);
}
case 'helloWorldEmbeddable': {
return (
<ContactCardEmbeddableExample
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}
/>
);
}
case 'dashboardContainer': {
return (
<DashboardContainerExample

View file

@ -17,7 +17,7 @@
* under the License.
*/
import { ViewMode, CONTACT_CARD_EMBEDDABLE, HELLO_WORLD_EMBEDDABLE_TYPE } from '../embeddable_api';
import { ViewMode, CONTACT_CARD_EMBEDDABLE, HELLO_WORLD_EMBEDDABLE } from '../embeddable_api';
import { DashboardContainerInput } from '../../../../../../../../src/legacy/core_plugins/dashboard_embeddable_container/public/np_ready/public';
export const dashboardInput: DashboardContainerInput = {
@ -30,7 +30,7 @@ export const dashboardInput: DashboardContainerInput = {
y: 15,
i: '1',
},
type: HELLO_WORLD_EMBEDDABLE_TYPE,
type: HELLO_WORLD_EMBEDDABLE,
explicitInput: {
id: '1',
},

View file

@ -1,136 +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 { Subscription } from 'rxjs';
import { EuiFieldText, EuiFormRow } from '@elastic/eui';
import {
EmbeddablePanel,
GetEmbeddableFactory,
GetEmbeddableFactories,
} from '../../../../../../../../src/plugins/embeddable/public';
import {
HelloWorldContainer,
CONTACT_CARD_EMBEDDABLE,
HELLO_WORLD_EMBEDDABLE_TYPE,
} from '../../../../../../../../src/plugins/embeddable/public/lib/test_samples';
import { TGetActionsCompatibleWithTrigger } from '../../../../../../../../src/plugins/ui_actions/public';
import { CoreStart } from '../../../../../../../../src/core/public';
import { Start as InspectorStartContract } from '../../../../../../../../src/plugins/inspector/public';
interface Props {
getActions: TGetActionsCompatibleWithTrigger;
getEmbeddableFactory: GetEmbeddableFactory;
getAllEmbeddableFactories: GetEmbeddableFactories;
overlays: CoreStart['overlays'];
notifications: CoreStart['notifications'];
inspector: InspectorStartContract;
SavedObjectFinder: React.ComponentType<any>;
}
export class HelloWorldContainerExample extends React.Component<Props, { lastName?: string }> {
private container: HelloWorldContainer;
private mounted: boolean = false;
private subscription?: Subscription;
constructor(props: Props) {
super(props);
this.container = new HelloWorldContainer(
{
id: 'helloWorldContainerExample',
panels: {
'1': {
type: CONTACT_CARD_EMBEDDABLE,
explicitInput: {
firstName: 'Joe',
id: '1',
},
},
'2': {
type: HELLO_WORLD_EMBEDDABLE_TYPE,
explicitInput: { id: '2' },
},
'3': {
type: 'idontexist',
explicitInput: { id: '3' },
},
},
},
{
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,
}
);
this.state = {
lastName: this.container.getInput().lastName,
};
}
public componentDidMount() {
this.mounted = true;
this.subscription = this.container.getInput$().subscribe(() => {
const { lastName } = this.container.getInput();
if (this.mounted) {
this.setState({
lastName,
});
}
});
}
public componentWillUnmount() {
this.mounted = false;
if (this.subscription) {
this.subscription.unsubscribe();
}
this.container.destroy();
}
public render() {
return (
<div className="app-container dshAppContainer">
<h1>Hello World Container</h1>
<EuiFormRow label="Last name">
<EuiFieldText
name="popfirst"
value={this.state.lastName}
placeholder="optional"
onChange={e => this.container.updateInput({ lastName: e.target.value })}
/>
</EuiFormRow>
<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}
/>
</div>
);
}
}

View file

@ -1,72 +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 {
EmbeddablePanel,
GetEmbeddableFactory,
GetEmbeddableFactories,
} from '../../../../../../../../src/plugins/embeddable/public';
import { HelloWorldEmbeddable } from '../../../../../../../../src/plugins/embeddable/public/lib/test_samples';
import { TGetActionsCompatibleWithTrigger } from '../../../../../../../../src/plugins/ui_actions/public';
import { CoreStart } from '../../../../../../../../src/core/public';
import { Start as InspectorStartContract } from '../../../../../../../../src/plugins/inspector/public';
interface Props {
getActions: TGetActionsCompatibleWithTrigger;
getEmbeddableFactory: GetEmbeddableFactory;
getAllEmbeddableFactories: GetEmbeddableFactories;
overlays: CoreStart['overlays'];
notifications: CoreStart['notifications'];
inspector: InspectorStartContract;
SavedObjectFinder: React.ComponentType<any>;
}
export class ContactCardEmbeddableExample extends React.Component<Props> {
private embeddable: HelloWorldEmbeddable;
constructor(props: Props) {
super(props);
this.embeddable = new HelloWorldEmbeddable({ id: 'hello' });
}
public componentWillUnmount() {
if (this.embeddable) {
this.embeddable.destroy();
}
}
public render() {
return (
<div className="app-container dshAppContainer">
<EmbeddablePanel
embeddable={this.embeddable}
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}
/>
</div>
);
}
}

View file

@ -19,3 +19,4 @@
export * from '../../../../../../../src/plugins/embeddable/public';
export * from '../../../../../../../src/plugins/embeddable/public/lib/test_samples';
export { HELLO_WORLD_EMBEDDABLE } from '../../../../../../../examples/embeddable_examples/public';

View file

@ -20,7 +20,9 @@
// eslint-disable-next-line
import { npSetup } from '../../../../../../../../src/legacy/ui/public/new_platform';
// eslint-disable-next-line
import { HelloWorldEmbeddableFactory } from '../../../../../../../../src/plugins/embeddable/public/lib/test_samples';
import { HelloWorldEmbeddableFactory, HELLO_WORLD_EMBEDDABLE } from '../../../../../../../../examples/embeddable_examples/public';
const factory = new HelloWorldEmbeddableFactory();
npSetup.plugins.embeddable.registerEmbeddableFactory(factory.type, factory);
npSetup.plugins.embeddable.registerEmbeddableFactory(
HELLO_WORLD_EMBEDDABLE,
new HelloWorldEmbeddableFactory()
);

View file

@ -34,7 +34,6 @@ const REACT_ROOT_ID = 'embeddableExplorerRoot';
import {
SayHelloAction,
createSendMessageAction,
HelloWorldEmbeddableFactory,
ContactCardEmbeddableFactory,
} from './embeddable_api';
import { App } from './app';
@ -42,6 +41,7 @@ import {
SavedObjectFinderProps,
SavedObjectFinderUi,
} from '../../../../../../../src/plugins/kibana_react/public/saved_objects';
import { HelloWorldEmbeddableFactory } from '../../../../../../../examples/embeddable_examples/public';
import {
IEmbeddableStart,
IEmbeddableSetup,

View file

@ -37,8 +37,6 @@ export default function({ getService, getPageObjects, loadTestFile }) {
await appsMenu.clickLink('Embeddable Explorer');
});
loadTestFile(require.resolve('./hello_world_container'));
loadTestFile(require.resolve('./hello_world_embeddable'));
loadTestFile(require.resolve('./dashboard_container'));
});
}

View file

@ -18,5 +18,6 @@ checks-reporter-with-killswitch "Functional tests / Group ${CI_GROUP}" yarn run
if [ "$CI_GROUP" == "1" ]; then
source test/scripts/jenkins_build_kbn_tp_sample_panel_action.sh
yarn run grunt run:pluginFunctionalTestsRelease --from=source;
yarn run grunt run:exampleFunctionalTestsRelease --from=source;
yarn run grunt run:interpreterFunctionalTestsRelease;
fi

View file

@ -5,10 +5,8 @@
*/
import { canInheritTimeRange } from './can_inherit_time_range';
/** eslint-disable */
import {
HelloWorldEmbeddable,
HelloWorldContainer,
} from '../../../../src/plugins/embeddable/public/lib/test_samples';
import { HelloWorldContainer } from '../../../../src/plugins/embeddable/public/lib/test_samples';
import { HelloWorldEmbeddable } from '../../../../examples/embeddable_examples/public';
/** eslint-enable */
import { TimeRangeEmbeddable, TimeRangeContainer } from './test_helpers';

View file

@ -16,13 +16,16 @@ import { TimeRangeEmbeddableFactory } from './test_helpers/time_range_embeddable
import { CustomTimeRangeAction } from './custom_time_range_action';
/* eslint-disable */
import {
HelloWorldEmbeddableFactory,
HELLO_WORLD_EMBEDDABLE_TYPE,
HelloWorldEmbeddable,
HelloWorldContainer,
} from '../../../../src/plugins/embeddable/public/lib/test_samples';
/* eslint-enable */
import {
HelloWorldEmbeddableFactory,
HelloWorldEmbeddable,
HELLO_WORLD_EMBEDDABLE,
} from '../../../../examples/embeddable_examples/public';
import { nextTick } from 'test_utils/enzyme_helpers';
import { ReactElement } from 'react';
@ -277,13 +280,13 @@ test(`badge is compatible with embeddable that inherits from parent`, async () =
// TODO: uncomment when https://github.com/elastic/kibana/issues/43271 is fixed.
// test('Embeddable that does not use time range in a container that has time range is incompatible', async () => {
// const embeddableFactories = new Map<string, EmbeddableFactory>();
// embeddableFactories.set(HELLO_WORLD_EMBEDDABLE_TYPE, new HelloWorldEmbeddableFactory());
// embeddableFactories.set(HELLO_WORLD_EMBEDDABLE, new HelloWorldEmbeddableFactory());
// const container = new TimeRangeContainer(
// {
// timeRange: { from: 'now-15m', to: 'now' },
// panels: {
// '1': {
// type: HELLO_WORLD_EMBEDDABLE_TYPE,
// type: HELLO_WORLD_EMBEDDABLE,
// explicitInput: {
// id: '1',
// },
@ -313,12 +316,12 @@ test(`badge is compatible with embeddable that inherits from parent`, async () =
test('Attempting to execute on incompatible embeddable throws an error', async () => {
const embeddableFactories = new Map<string, EmbeddableFactory>();
embeddableFactories.set(HELLO_WORLD_EMBEDDABLE_TYPE, new HelloWorldEmbeddableFactory());
embeddableFactories.set(HELLO_WORLD_EMBEDDABLE, new HelloWorldEmbeddableFactory());
const container = new HelloWorldContainer(
{
panels: {
'1': {
type: HELLO_WORLD_EMBEDDABLE_TYPE,
type: HELLO_WORLD_EMBEDDABLE,
explicitInput: {
id: '1',
},

View file

@ -916,7 +916,6 @@
"embeddableApi.samples.contactCard.displayName": "連絡先カード",
"embeddableApi.samples.filterableContainer.displayName": "フィルター可能なダッシュボード",
"embeddableApi.samples.filterableEmbeddable.displayName": "フィルター可能",
"embeddableApi.samples.helloworld.displayName": "こんにちは",
"embeddableApi.panel.enhancedDashboardPanelAriaLabel": "ダッシュボードパネル: {title}",
"embeddableApi.panel.optionsMenu.panelOptionsButtonEnhancedAriaLabel": "{title} のパネルオプション",
"embeddableApi.panel.dashboardPanelAriaLabel": "ダッシュボードパネル",

View file

@ -917,7 +917,6 @@
"embeddableApi.samples.contactCard.displayName": "联系卡片",
"embeddableApi.samples.filterableContainer.displayName": "可筛选仪表板",
"embeddableApi.samples.filterableEmbeddable.displayName": "可筛选",
"embeddableApi.samples.helloworld.displayName": "hello world",
"embeddableApi.panel.enhancedDashboardPanelAriaLabel": "仪表板面板:{title}",
"embeddableApi.panel.optionsMenu.panelOptionsButtonEnhancedAriaLabel": "{title} 的面板选项",
"embeddableApi.panel.dashboardPanelAriaLabel": "仪表板面板",