mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
* 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:
parent
60a289b8ca
commit
cf21b2dd15
69 changed files with 2478 additions and 310 deletions
|
@ -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",
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
10
examples/embeddable_examples/kibana.json
Normal file
10
examples/embeddable_examples/kibana.json
Normal file
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"id": "embeddableExamples",
|
||||
"version": "0.0.1",
|
||||
"kibanaVersion": "kibana",
|
||||
"configPath": ["embeddable_examples"],
|
||||
"server": false,
|
||||
"ui": true,
|
||||
"requiredPlugins": ["embeddable"],
|
||||
"optionalPlugins": []
|
||||
}
|
17
examples/embeddable_examples/package.json
Normal file
17
examples/embeddable_examples/package.json
Normal 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"
|
||||
}
|
||||
}
|
|
@ -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(
|
|
@ -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',
|
||||
});
|
||||
}
|
34
examples/embeddable_examples/public/index.ts
Normal file
34
examples/embeddable_examples/public/index.ts
Normal 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();
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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';
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
|
@ -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',
|
||||
});
|
||||
}
|
||||
}
|
21
examples/embeddable_examples/public/multi_task_todo/index.ts
Normal file
21
examples/embeddable_examples/public/multi_task_todo/index.ts
Normal 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';
|
|
@ -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
|
||||
);
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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',
|
||||
});
|
||||
}
|
||||
}
|
72
examples/embeddable_examples/public/plugin.ts
Normal file
72
examples/embeddable_examples/public/plugin.ts
Normal 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() {}
|
||||
}
|
|
@ -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';
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
);
|
|
@ -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',
|
||||
});
|
||||
}
|
||||
}
|
21
examples/embeddable_examples/public/todo/index.ts
Normal file
21
examples/embeddable_examples/public/todo/index.ts
Normal 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';
|
74
examples/embeddable_examples/public/todo/todo_component.tsx
Normal file
74
examples/embeddable_examples/public/todo/todo_component.tsx
Normal 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);
|
88
examples/embeddable_examples/public/todo/todo_embeddable.tsx
Normal file
88
examples/embeddable_examples/public/todo/todo_embeddable.tsx
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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',
|
||||
});
|
||||
}
|
||||
}
|
15
examples/embeddable_examples/tsconfig.json
Normal file
15
examples/embeddable_examples/tsconfig.json
Normal file
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "./target",
|
||||
"skipLibCheck": true
|
||||
},
|
||||
"include": [
|
||||
"index.ts",
|
||||
"public/**/*.ts",
|
||||
"public/**/*.tsx",
|
||||
"server/**/*.ts",
|
||||
"../../typings/**/*"
|
||||
],
|
||||
"exclude": []
|
||||
}
|
10
examples/embeddable_explorer/kibana.json
Normal file
10
examples/embeddable_explorer/kibana.json
Normal file
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"id": "embeddableExplorer",
|
||||
"version": "0.0.1",
|
||||
"kibanaVersion": "kibana",
|
||||
"configPath": ["embeddable_explorer"],
|
||||
"server": false,
|
||||
"ui": true,
|
||||
"requiredPlugins": ["embeddable", "embeddableExamples"],
|
||||
"optionalPlugins": []
|
||||
}
|
17
examples/embeddable_explorer/package.json
Normal file
17
examples/embeddable_explorer/package.json
Normal 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"
|
||||
}
|
||||
}
|
126
examples/embeddable_explorer/public/app.tsx
Normal file
126
examples/embeddable_explorer/public/app.tsx
Normal 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);
|
||||
};
|
|
@ -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'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>
|
||||
);
|
||||
}
|
22
examples/embeddable_explorer/public/index.ts
Normal file
22
examples/embeddable_explorer/public/index.ts
Normal 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();
|
182
examples/embeddable_explorer/public/list_container_example.tsx
Normal file
182
examples/embeddable_explorer/public/list_container_example.tsx
Normal 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's own input to its
|
||||
children. In the above example, the container did not take any input. Let'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'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't be difficult. In the mean time, it's good
|
||||
to understand that chilren may ignore input they don'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>
|
||||
);
|
||||
}
|
38
examples/embeddable_explorer/public/plugin.tsx
Normal file
38
examples/embeddable_explorer/public/plugin.tsx
Normal 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() {}
|
||||
}
|
172
examples/embeddable_explorer/public/todo_embeddable_example.tsx
Normal file
172
examples/embeddable_explorer/public/todo_embeddable_example.tsx
Normal 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 { task, title, icon } = this.state;
|
||||
<br />
|
||||
this.embeddable.updateInput({ task, title, icon });
|
||||
</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>
|
||||
);
|
||||
}
|
||||
}
|
15
examples/embeddable_explorer/tsconfig.json
Normal file
15
examples/embeddable_explorer/tsconfig.json
Normal file
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "./target",
|
||||
"skipLibCheck": true
|
||||
},
|
||||
"include": [
|
||||
"index.ts",
|
||||
"public/**/*.ts",
|
||||
"public/**/*.tsx",
|
||||
"server/**/*.ts",
|
||||
"../../typings/**/*",
|
||||
],
|
||||
"exclude": []
|
||||
}
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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'),
|
||||
]
|
||||
: [],
|
||||
|
||||
|
|
|
@ -55,6 +55,9 @@ export {
|
|||
ViewMode,
|
||||
isErrorEmbeddable,
|
||||
openAddPanelFlyout,
|
||||
withEmbeddableSubscription,
|
||||
EmbeddableFactoryRenderer,
|
||||
EmbeddableRoot,
|
||||
} from './lib';
|
||||
|
||||
export function plugin(initializerContext: PluginInitializerContext) {
|
||||
|
|
|
@ -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 }>;
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
});
|
|
@ -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}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
});
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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';
|
||||
|
|
|
@ -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}
|
||||
/>
|
||||
);
|
||||
}
|
||||
};
|
|
@ -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';
|
||||
|
|
|
@ -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();
|
||||
});
|
||||
|
||||
|
|
|
@ -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 = {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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!');
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
43
test/examples/embeddables/index.ts
Normal file
43
test/examples/embeddables/index.ts
Normal 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'));
|
||||
});
|
||||
}
|
69
test/examples/embeddables/list_container.ts
Normal file
69
test/examples/embeddables/list_container.ts
Normal 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']);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
55
test/examples/embeddables/todo_embeddable.ts
Normal file
55
test/examples/embeddables/todo_embeddable.ts
Normal 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');
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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',
|
||||
},
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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';
|
||||
|
|
|
@ -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()
|
||||
);
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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'));
|
||||
});
|
||||
}
|
||||
|
|
|
@ -24,5 +24,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
|
|
@ -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';
|
||||
|
||||
|
|
|
@ -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',
|
||||
},
|
||||
|
|
|
@ -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": "ダッシュボードパネル",
|
||||
|
|
|
@ -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": "仪表板面板",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue