mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
Add embeddable via saved object example (#61692)
* Add embeddable via saved object example * give todoRefEmbed a different name from the by value one * fix types * fix order of unmounting Co-authored-by: Christos Nasikas <christos.nasikas@elastic.co>
This commit is contained in:
parent
00a1144ae2
commit
d0404487f6
31 changed files with 679 additions and 90 deletions
20
examples/embeddable_examples/common/index.ts
Normal file
20
examples/embeddable_examples/common/index.ts
Normal file
|
@ -0,0 +1,20 @@
|
|||
/*
|
||||
* 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 { TodoSavedObjectAttributes } from './todo_saved_object_attributes';
|
|
@ -0,0 +1,26 @@
|
|||
/*
|
||||
* 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 { SavedObjectAttributes } from '../../../src/core/types';
|
||||
|
||||
export interface TodoSavedObjectAttributes extends SavedObjectAttributes {
|
||||
task: string;
|
||||
icon?: string;
|
||||
title?: string;
|
||||
}
|
|
@ -3,7 +3,7 @@
|
|||
"version": "0.0.1",
|
||||
"kibanaVersion": "kibana",
|
||||
"configPath": ["embeddable_examples"],
|
||||
"server": false,
|
||||
"server": true,
|
||||
"ui": true,
|
||||
"requiredPlugins": ["embeddable"],
|
||||
"optionalPlugins": []
|
||||
|
|
36
examples/embeddable_examples/public/create_sample_data.ts
Normal file
36
examples/embeddable_examples/public/create_sample_data.ts
Normal file
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
* 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 { SavedObjectsClientContract } from 'kibana/public';
|
||||
import { TodoSavedObjectAttributes } from '../common';
|
||||
|
||||
export async function createSampleData(client: SavedObjectsClientContract) {
|
||||
await client.create<TodoSavedObjectAttributes>(
|
||||
'todo',
|
||||
{
|
||||
task: 'Take the garbage out',
|
||||
title: 'Garbage',
|
||||
icon: 'trash',
|
||||
},
|
||||
{
|
||||
id: 'sample-todo-saved-object',
|
||||
overwrite: true,
|
||||
}
|
||||
);
|
||||
}
|
|
@ -17,7 +17,6 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { PluginInitializer } from 'kibana/public';
|
||||
export {
|
||||
HELLO_WORLD_EMBEDDABLE,
|
||||
HelloWorldEmbeddable,
|
||||
|
@ -26,18 +25,8 @@ export {
|
|||
export { ListContainer, LIST_CONTAINER } from './list_container';
|
||||
export { TODO_EMBEDDABLE } from './todo';
|
||||
|
||||
import {
|
||||
EmbeddableExamplesPlugin,
|
||||
EmbeddableExamplesSetupDependencies,
|
||||
EmbeddableExamplesStartDependencies,
|
||||
} from './plugin';
|
||||
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,
|
||||
EmbeddableExamplesSetupDependencies,
|
||||
EmbeddableExamplesStartDependencies
|
||||
> = () => new EmbeddableExamplesPlugin();
|
||||
export const plugin = () => new EmbeddableExamplesPlugin();
|
||||
|
|
|
@ -21,12 +21,20 @@ import { EmbeddableSetup, EmbeddableStart } from '../../../src/plugins/embeddabl
|
|||
import { Plugin, CoreSetup, CoreStart } from '../../../src/core/public';
|
||||
import { HelloWorldEmbeddableFactory, HELLO_WORLD_EMBEDDABLE } from './hello_world';
|
||||
import { TODO_EMBEDDABLE, TodoEmbeddableFactory, TodoInput, TodoOutput } from './todo';
|
||||
import { MULTI_TASK_TODO_EMBEDDABLE, MultiTaskTodoEmbeddableFactory } from './multi_task_todo';
|
||||
import {
|
||||
MULTI_TASK_TODO_EMBEDDABLE,
|
||||
MultiTaskTodoEmbeddableFactory,
|
||||
MultiTaskTodoInput,
|
||||
MultiTaskTodoOutput,
|
||||
} from './multi_task_todo';
|
||||
import {
|
||||
SEARCHABLE_LIST_CONTAINER,
|
||||
SearchableListContainerFactory,
|
||||
} from './searchable_list_container';
|
||||
import { LIST_CONTAINER, ListContainerFactory } from './list_container';
|
||||
import { createSampleData } from './create_sample_data';
|
||||
import { TodoRefInput, TodoRefOutput, TODO_REF_EMBEDDABLE } from './todo/todo_ref_embeddable';
|
||||
import { TodoRefEmbeddableFactory } from './todo/todo_ref_embeddable_factory';
|
||||
|
||||
export interface EmbeddableExamplesSetupDependencies {
|
||||
embeddable: EmbeddableSetup;
|
||||
|
@ -36,9 +44,18 @@ export interface EmbeddableExamplesStartDependencies {
|
|||
embeddable: EmbeddableStart;
|
||||
}
|
||||
|
||||
export interface EmbeddableExamplesStart {
|
||||
createSampleData: () => Promise<void>;
|
||||
}
|
||||
|
||||
export class EmbeddableExamplesPlugin
|
||||
implements
|
||||
Plugin<void, void, EmbeddableExamplesSetupDependencies, EmbeddableExamplesStartDependencies> {
|
||||
Plugin<
|
||||
void,
|
||||
EmbeddableExamplesStart,
|
||||
EmbeddableExamplesSetupDependencies,
|
||||
EmbeddableExamplesStartDependencies
|
||||
> {
|
||||
public setup(
|
||||
core: CoreSetup<EmbeddableExamplesStartDependencies>,
|
||||
deps: EmbeddableExamplesSetupDependencies
|
||||
|
@ -48,7 +65,7 @@ export class EmbeddableExamplesPlugin
|
|||
new HelloWorldEmbeddableFactory()
|
||||
);
|
||||
|
||||
deps.embeddable.registerEmbeddableFactory(
|
||||
deps.embeddable.registerEmbeddableFactory<MultiTaskTodoInput, MultiTaskTodoOutput>(
|
||||
MULTI_TASK_TODO_EMBEDDABLE,
|
||||
new MultiTaskTodoEmbeddableFactory()
|
||||
);
|
||||
|
@ -73,9 +90,21 @@ export class EmbeddableExamplesPlugin
|
|||
openModal: (await core.getStartServices())[0].overlays.openModal,
|
||||
}))
|
||||
);
|
||||
|
||||
deps.embeddable.registerEmbeddableFactory<TodoRefInput, TodoRefOutput>(
|
||||
TODO_REF_EMBEDDABLE,
|
||||
new TodoRefEmbeddableFactory(async () => ({
|
||||
savedObjectsClient: (await core.getStartServices())[0].savedObjects.client,
|
||||
getEmbeddableFactory: (await core.getStartServices())[1].embeddable.getEmbeddableFactory,
|
||||
}))
|
||||
);
|
||||
}
|
||||
|
||||
public start(core: CoreStart, deps: EmbeddableExamplesStartDependencies) {}
|
||||
public start(core: CoreStart, deps: EmbeddableExamplesStartDependencies) {
|
||||
return {
|
||||
createSampleData: () => createSampleData(core.savedObjects.client),
|
||||
};
|
||||
}
|
||||
|
||||
public stop() {}
|
||||
}
|
||||
|
|
43
examples/embeddable_examples/public/todo/README.md
Normal file
43
examples/embeddable_examples/public/todo/README.md
Normal file
|
@ -0,0 +1,43 @@
|
|||
There are two examples in here:
|
||||
- TodoEmbeddable
|
||||
- TodoRefEmbeddable
|
||||
|
||||
# TodoEmbeddable
|
||||
|
||||
The first example you should review is the HelloWorldEmbeddable. That is as basic an embeddable as you can get.
|
||||
This embeddable is the next step up - an embeddable that renders dynamic input data. The data is simple:
|
||||
- a required task string
|
||||
- an optional title
|
||||
- an optional icon string
|
||||
- an optional search string
|
||||
|
||||
It also has output data, which is `hasMatch` - whether or not the search string has matched any input data.
|
||||
|
||||
`hasMatch` is a better fit for output data than input data, because it's state that is _derived_ from input data.
|
||||
|
||||
For example, if it was input data, you could create a TodoEmbeddable with input like this:
|
||||
|
||||
```ts
|
||||
todoEmbeddableFactory.create({ task: 'take out the garabage', search: 'garbage', hasMatch: false });
|
||||
```
|
||||
|
||||
That's wrong because there is actually a match from the search string inside the task.
|
||||
|
||||
The TodoEmbeddable component itself doesn't do anything with the `hasMatch` variable other than set it, but
|
||||
if you check out `SearchableListContainer`, you can see an example where this output data is being used.
|
||||
|
||||
## TodoRefEmbeddable
|
||||
|
||||
This is an example of an embeddable based off of a saved object. The input is just the `savedObjectId` and
|
||||
the `search` string. It has even more output parameters, and this time, it does read it's own output parameters in
|
||||
order to calculate `hasMatch`.
|
||||
|
||||
Output:
|
||||
```ts
|
||||
{
|
||||
hasMatch: boolean,
|
||||
savedAttributes?: TodoSavedAttributes
|
||||
}
|
||||
```
|
||||
|
||||
`savedAttributes` is optional because it's possible a TodoSavedObject could not be found with the given savedObjectId.
|
|
@ -0,0 +1,86 @@
|
|||
/*
|
||||
* 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 } from '../../../../src/plugins/embeddable/public';
|
||||
import { TodoRefInput, TodoRefOutput, TodoRefEmbeddable } from './todo_ref_embeddable';
|
||||
|
||||
interface Props {
|
||||
embeddable: TodoRefEmbeddable;
|
||||
input: TodoRefInput;
|
||||
output: TodoRefOutput;
|
||||
}
|
||||
|
||||
function wrapSearchTerms(task?: string, search?: string) {
|
||||
if (!search) return task;
|
||||
if (!task) 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 TodoRefEmbeddableComponentInner({
|
||||
input: { search },
|
||||
output: { savedAttributes },
|
||||
}: Props) {
|
||||
const icon = savedAttributes?.icon;
|
||||
const title = savedAttributes?.title;
|
||||
const task = savedAttributes?.task;
|
||||
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 TodoRefEmbeddableComponent = withEmbeddableSubscription<
|
||||
TodoRefInput,
|
||||
TodoRefOutput,
|
||||
TodoRefEmbeddable
|
||||
>(TodoRefEmbeddableComponentInner);
|
153
examples/embeddable_examples/public/todo/todo_ref_embeddable.tsx
Normal file
153
examples/embeddable_examples/public/todo/todo_ref_embeddable.tsx
Normal file
|
@ -0,0 +1,153 @@
|
|||
/*
|
||||
* 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 { TodoSavedObjectAttributes } from 'examples/embeddable_examples/common';
|
||||
import { SavedObjectsClientContract } from 'kibana/public';
|
||||
import {
|
||||
Embeddable,
|
||||
IContainer,
|
||||
EmbeddableOutput,
|
||||
SavedObjectEmbeddableInput,
|
||||
} from '../../../../src/plugins/embeddable/public';
|
||||
import { TodoRefEmbeddableComponent } from './todo_ref_component';
|
||||
|
||||
// Notice this is not the same value as the 'todo' saved object type. Many of our
|
||||
// cases in prod today use the same value, but this is unnecessary.
|
||||
export const TODO_REF_EMBEDDABLE = 'TODO_REF_EMBEDDABLE';
|
||||
|
||||
export interface TodoRefInput extends SavedObjectEmbeddableInput {
|
||||
/**
|
||||
* Optional search string which will be used to highlight search terms as
|
||||
* well as calculate `output.hasMatch`.
|
||||
*/
|
||||
search?: string;
|
||||
}
|
||||
|
||||
export interface TodoRefOutput extends EmbeddableOutput {
|
||||
/**
|
||||
* Should be true if input.search is defined and the task or title contain
|
||||
* search as a substring.
|
||||
*/
|
||||
hasMatch: boolean;
|
||||
/**
|
||||
* Will contain the saved object attributes of the Todo Saved Object that matches
|
||||
* `input.savedObjectId`. If the id is invalid, this may be undefined.
|
||||
*/
|
||||
savedAttributes?: TodoSavedObjectAttributes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether any attributes contain the search string. If search is empty, true is returned. If
|
||||
* there are no savedAttributes, false is returned.
|
||||
* @param search - the search string
|
||||
* @param savedAttributes - the saved object attributes for the saved object with id `input.savedObjectId`
|
||||
*/
|
||||
function getHasMatch(search?: string, savedAttributes?: TodoSavedObjectAttributes): boolean {
|
||||
if (!search) return true;
|
||||
if (!savedAttributes) return false;
|
||||
return Boolean(
|
||||
(savedAttributes.task && savedAttributes.task.match(search)) ||
|
||||
(savedAttributes.title && savedAttributes.title.match(search))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* This is an example of an embeddable that is backed by a saved object. It's essentially the
|
||||
* same as `TodoEmbeddable` but that is "by value", while this is "by reference".
|
||||
*/
|
||||
export class TodoRefEmbeddable extends Embeddable<TodoRefInput, TodoRefOutput> {
|
||||
public readonly type = TODO_REF_EMBEDDABLE;
|
||||
private subscription: Subscription;
|
||||
private node?: HTMLElement;
|
||||
private savedObjectsClient: SavedObjectsClientContract;
|
||||
private savedObjectId?: string;
|
||||
|
||||
constructor(
|
||||
initialInput: TodoRefInput,
|
||||
{
|
||||
parent,
|
||||
savedObjectsClient,
|
||||
}: {
|
||||
parent?: IContainer;
|
||||
savedObjectsClient: SavedObjectsClientContract;
|
||||
}
|
||||
) {
|
||||
super(initialInput, { hasMatch: false }, parent);
|
||||
this.savedObjectsClient = savedObjectsClient;
|
||||
|
||||
this.subscription = this.getInput$().subscribe(async () => {
|
||||
// There is a little more work today for this embeddable because it has
|
||||
// more output it needs to update in response to input state changes.
|
||||
let savedAttributes: TodoSavedObjectAttributes | undefined;
|
||||
|
||||
// Since this is an expensive task, we save a local copy of the previous
|
||||
// savedObjectId locally and only retrieve the new saved object if the id
|
||||
// actually changed.
|
||||
if (this.savedObjectId !== this.input.savedObjectId) {
|
||||
this.savedObjectId = this.input.savedObjectId;
|
||||
const todoSavedObject = await this.savedObjectsClient.get<TodoSavedObjectAttributes>(
|
||||
'todo',
|
||||
this.input.savedObjectId
|
||||
);
|
||||
savedAttributes = todoSavedObject?.attributes;
|
||||
}
|
||||
|
||||
// The search string might have changed as well so we need to make sure we recalculate
|
||||
// hasMatch.
|
||||
this.updateOutput({
|
||||
hasMatch: getHasMatch(this.input.search, savedAttributes),
|
||||
savedAttributes,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public render(node: HTMLElement) {
|
||||
if (this.node) {
|
||||
ReactDOM.unmountComponentAtNode(this.node);
|
||||
}
|
||||
this.node = node;
|
||||
ReactDOM.render(<TodoRefEmbeddableComponent embeddable={this} />, node);
|
||||
}
|
||||
|
||||
/**
|
||||
* Lets re-sync our saved object to make sure it's up to date!
|
||||
*/
|
||||
public async reload() {
|
||||
this.savedObjectId = this.input.savedObjectId;
|
||||
const todoSavedObject = await this.savedObjectsClient.get<TodoSavedObjectAttributes>(
|
||||
'todo',
|
||||
this.input.savedObjectId
|
||||
);
|
||||
const savedAttributes = todoSavedObject?.attributes;
|
||||
this.updateOutput({
|
||||
hasMatch: getHasMatch(this.input.search, savedAttributes),
|
||||
savedAttributes,
|
||||
});
|
||||
}
|
||||
|
||||
public destroy() {
|
||||
super.destroy();
|
||||
this.subscription.unsubscribe();
|
||||
if (this.node) {
|
||||
ReactDOM.unmountComponentAtNode(this.node);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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 { i18n } from '@kbn/i18n';
|
||||
import { SavedObjectsClientContract } from 'kibana/public';
|
||||
import { TodoSavedObjectAttributes } from 'examples/embeddable_examples/common';
|
||||
import {
|
||||
IContainer,
|
||||
EmbeddableStart,
|
||||
ErrorEmbeddable,
|
||||
EmbeddableFactoryDefinition,
|
||||
} from '../../../../src/plugins/embeddable/public';
|
||||
import {
|
||||
TodoRefEmbeddable,
|
||||
TODO_REF_EMBEDDABLE,
|
||||
TodoRefInput,
|
||||
TodoRefOutput,
|
||||
} from './todo_ref_embeddable';
|
||||
|
||||
interface StartServices {
|
||||
getEmbeddableFactory: EmbeddableStart['getEmbeddableFactory'];
|
||||
savedObjectsClient: SavedObjectsClientContract;
|
||||
}
|
||||
|
||||
export class TodoRefEmbeddableFactory
|
||||
implements
|
||||
EmbeddableFactoryDefinition<
|
||||
TodoRefInput,
|
||||
TodoRefOutput,
|
||||
TodoRefEmbeddable,
|
||||
TodoSavedObjectAttributes
|
||||
> {
|
||||
public readonly type = TODO_REF_EMBEDDABLE;
|
||||
public readonly savedObjectMetaData = {
|
||||
name: 'Todo',
|
||||
includeFields: ['task', 'icon', 'title'],
|
||||
type: 'todo',
|
||||
getIconForSavedObject: () => 'pencil',
|
||||
};
|
||||
|
||||
constructor(private getStartServices: () => Promise<StartServices>) {}
|
||||
|
||||
public async isEditable() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public createFromSavedObject = (
|
||||
savedObjectId: string,
|
||||
input: Partial<TodoRefInput> & { id: string },
|
||||
parent?: IContainer
|
||||
): Promise<TodoRefEmbeddable | ErrorEmbeddable> => {
|
||||
return this.create({ ...input, savedObjectId }, parent);
|
||||
};
|
||||
|
||||
public async create(input: TodoRefInput, parent?: IContainer) {
|
||||
const { savedObjectsClient } = await this.getStartServices();
|
||||
return new TodoRefEmbeddable(input, {
|
||||
parent,
|
||||
savedObjectsClient,
|
||||
});
|
||||
}
|
||||
|
||||
public getDisplayName() {
|
||||
return i18n.translate('embeddableExamples.todo.displayName', {
|
||||
defaultMessage: 'Todo (by reference)',
|
||||
});
|
||||
}
|
||||
}
|
24
examples/embeddable_examples/server/index.ts
Normal file
24
examples/embeddable_examples/server/index.ts
Normal file
|
@ -0,0 +1,24 @@
|
|||
/*
|
||||
* 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/server';
|
||||
|
||||
import { EmbeddableExamplesPlugin } from './plugin';
|
||||
|
||||
export const plugin: PluginInitializer<void, void> = () => new EmbeddableExamplesPlugin();
|
31
examples/embeddable_examples/server/plugin.ts
Normal file
31
examples/embeddable_examples/server/plugin.ts
Normal file
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* 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, CoreStart } from 'kibana/server';
|
||||
import { todoSavedObject } from './todo_saved_object';
|
||||
|
||||
export class EmbeddableExamplesPlugin implements Plugin {
|
||||
public setup(core: CoreSetup) {
|
||||
core.savedObjects.registerType(todoSavedObject);
|
||||
}
|
||||
|
||||
public start(core: CoreStart) {}
|
||||
|
||||
public stop() {}
|
||||
}
|
40
examples/embeddable_examples/server/todo_saved_object.ts
Normal file
40
examples/embeddable_examples/server/todo_saved_object.ts
Normal file
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import { SavedObjectsType } from 'kibana/server';
|
||||
|
||||
export const todoSavedObject: SavedObjectsType = {
|
||||
name: 'todo',
|
||||
hidden: false,
|
||||
namespaceAgnostic: true,
|
||||
mappings: {
|
||||
properties: {
|
||||
title: {
|
||||
type: 'keyword',
|
||||
},
|
||||
task: {
|
||||
type: 'text',
|
||||
},
|
||||
icon: {
|
||||
type: 'keyword',
|
||||
},
|
||||
},
|
||||
},
|
||||
migrations: {},
|
||||
};
|
|
@ -6,6 +6,7 @@
|
|||
},
|
||||
"include": [
|
||||
"index.ts",
|
||||
"common/**/*.ts",
|
||||
"public/**/*.ts",
|
||||
"public/**/*.tsx",
|
||||
"server/**/*.ts",
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
*/
|
||||
|
||||
import { Plugin, CoreSetup, AppMountParameters } from 'kibana/public';
|
||||
import { EmbeddableExamplesStart } from 'examples/embeddable_examples/public/plugin';
|
||||
import { UiActionsService } from '../../../src/plugins/ui_actions/public';
|
||||
import { EmbeddableStart } from '../../../src/plugins/embeddable/public';
|
||||
import { Start as InspectorStart } from '../../../src/plugins/inspector/public';
|
||||
|
@ -26,6 +27,7 @@ interface StartDeps {
|
|||
uiActions: UiActionsService;
|
||||
embeddable: EmbeddableStart;
|
||||
inspector: InspectorStart;
|
||||
embeddableExamples: EmbeddableExamplesStart;
|
||||
}
|
||||
|
||||
export class EmbeddableExplorerPlugin implements Plugin<void, void, {}, StartDeps> {
|
||||
|
@ -36,6 +38,7 @@ export class EmbeddableExplorerPlugin implements Plugin<void, void, {}, StartDep
|
|||
async mount(params: AppMountParameters) {
|
||||
const [coreStart, depsStart] = await core.getStartServices();
|
||||
const { renderApp } = await import('./app');
|
||||
await depsStart.embeddableExamples.createSampleData();
|
||||
return renderApp(
|
||||
{
|
||||
notifications: coreStart.notifications,
|
||||
|
|
|
@ -28,6 +28,7 @@ import {
|
|||
EmbeddableInput,
|
||||
EmbeddableOutput,
|
||||
EmbeddableStart,
|
||||
SavedObjectEmbeddableInput,
|
||||
} from '../../embeddable_plugin';
|
||||
|
||||
interface Props {
|
||||
|
@ -66,7 +67,7 @@ export class ReplacePanelFlyout extends React.Component<Props> {
|
|||
});
|
||||
};
|
||||
|
||||
public onReplacePanel = async (id: string, type: string, name: string) => {
|
||||
public onReplacePanel = async (savedObjectId: string, type: string, name: string) => {
|
||||
const originalPanels = this.props.container.getInput().panels;
|
||||
const filteredPanels = { ...originalPanels };
|
||||
|
||||
|
@ -76,7 +77,9 @@ export class ReplacePanelFlyout extends React.Component<Props> {
|
|||
const nny = (filteredPanels[this.props.panelToRemove.id] as DashboardPanelState).gridData.y;
|
||||
|
||||
// add the new view
|
||||
const newObj = await this.props.container.addSavedObjectEmbeddable(type, id);
|
||||
const newObj = await this.props.container.addNewEmbeddable<SavedObjectEmbeddableInput>(type, {
|
||||
savedObjectId,
|
||||
});
|
||||
|
||||
const finalPanels = _.cloneDeep(this.props.container.getInput().panels);
|
||||
(finalPanels[newObj.id] as DashboardPanelState).gridData.w = nnw;
|
||||
|
|
|
@ -53,6 +53,7 @@ import {
|
|||
isErrorEmbeddable,
|
||||
openAddPanelFlyout,
|
||||
ViewMode,
|
||||
SavedObjectEmbeddableInput,
|
||||
ContainerOutput,
|
||||
} from '../../../embeddable/public';
|
||||
import { NavAction, SavedDashboardPanel } from '../types';
|
||||
|
@ -394,7 +395,7 @@ export class DashboardAppController {
|
|||
if ($routeParams[DashboardConstants.ADD_EMBEDDABLE_TYPE]) {
|
||||
const type = $routeParams[DashboardConstants.ADD_EMBEDDABLE_TYPE];
|
||||
const id = $routeParams[DashboardConstants.ADD_EMBEDDABLE_ID];
|
||||
container.addSavedObjectEmbeddable(type, id);
|
||||
container.addNewEmbeddable<SavedObjectEmbeddableInput>(type, { savedObjectId: id });
|
||||
removeQueryParam(history, DashboardConstants.ADD_EMBEDDABLE_TYPE);
|
||||
removeQueryParam(history, DashboardConstants.ADD_EMBEDDABLE_ID);
|
||||
}
|
||||
|
|
|
@ -55,7 +55,7 @@ export interface DashboardContainerInput extends ContainerInput {
|
|||
description?: string;
|
||||
isFullScreenMode: boolean;
|
||||
panels: {
|
||||
[panelId: string]: DashboardPanelState;
|
||||
[panelId: string]: DashboardPanelState<EmbeddableInput & { [k: string]: unknown }>;
|
||||
};
|
||||
isEmptyState?: boolean;
|
||||
}
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import { SavedObjectEmbeddableInput } from 'src/plugins/embeddable/public';
|
||||
import { PanelState, EmbeddableInput } from '../../embeddable_plugin';
|
||||
export type PanelId = string;
|
||||
export type SavedObjectId = string;
|
||||
|
@ -28,7 +29,8 @@ export interface GridData {
|
|||
i: string;
|
||||
}
|
||||
|
||||
export interface DashboardPanelState<TEmbeddableInput extends EmbeddableInput = EmbeddableInput>
|
||||
extends PanelState<TEmbeddableInput> {
|
||||
export interface DashboardPanelState<
|
||||
TEmbeddableInput extends EmbeddableInput | SavedObjectEmbeddableInput = SavedObjectEmbeddableInput
|
||||
> extends PanelState<TEmbeddableInput> {
|
||||
readonly gridData: GridData;
|
||||
}
|
||||
|
|
|
@ -23,11 +23,6 @@ import {
|
|||
} from './embeddable_saved_object_converters';
|
||||
import { SavedDashboardPanel } from '../../types';
|
||||
import { DashboardPanelState } from '../embeddable';
|
||||
import { EmbeddableInput } from 'src/plugins/embeddable/public';
|
||||
|
||||
interface CustomInput extends EmbeddableInput {
|
||||
something: string;
|
||||
}
|
||||
|
||||
test('convertSavedDashboardPanelToPanelState', () => {
|
||||
const savedDashboardPanel: SavedDashboardPanel = {
|
||||
|
@ -58,8 +53,8 @@ test('convertSavedDashboardPanelToPanelState', () => {
|
|||
explicitInput: {
|
||||
something: 'hi!',
|
||||
id: '123',
|
||||
savedObjectId: 'savedObjectId',
|
||||
},
|
||||
savedObjectId: 'savedObjectId',
|
||||
type: 'search',
|
||||
});
|
||||
});
|
||||
|
@ -86,7 +81,7 @@ test('convertSavedDashboardPanelToPanelState does not include undefined id', ()
|
|||
});
|
||||
|
||||
test('convertPanelStateToSavedDashboardPanel', () => {
|
||||
const dashboardPanel: DashboardPanelState<CustomInput> = {
|
||||
const dashboardPanel: DashboardPanelState = {
|
||||
gridData: {
|
||||
x: 0,
|
||||
y: 0,
|
||||
|
@ -94,10 +89,10 @@ test('convertPanelStateToSavedDashboardPanel', () => {
|
|||
w: 15,
|
||||
i: '123',
|
||||
},
|
||||
savedObjectId: 'savedObjectId',
|
||||
explicitInput: {
|
||||
something: 'hi!',
|
||||
id: '123',
|
||||
savedObjectId: 'savedObjectId',
|
||||
},
|
||||
type: 'search',
|
||||
};
|
||||
|
@ -121,7 +116,7 @@ test('convertPanelStateToSavedDashboardPanel', () => {
|
|||
});
|
||||
|
||||
test('convertPanelStateToSavedDashboardPanel will not add an undefined id when not needed', () => {
|
||||
const dashboardPanel: DashboardPanelState<CustomInput> = {
|
||||
const dashboardPanel: DashboardPanelState = {
|
||||
gridData: {
|
||||
x: 0,
|
||||
y: 0,
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
import { omit } from 'lodash';
|
||||
import { SavedDashboardPanel } from '../../types';
|
||||
import { DashboardPanelState } from '../embeddable';
|
||||
import { SavedObjectEmbeddableInput } from '../../embeddable_plugin';
|
||||
|
||||
export function convertSavedDashboardPanelToPanelState(
|
||||
savedDashboardPanel: SavedDashboardPanel
|
||||
|
@ -26,9 +27,9 @@ export function convertSavedDashboardPanelToPanelState(
|
|||
return {
|
||||
type: savedDashboardPanel.type,
|
||||
gridData: savedDashboardPanel.gridData,
|
||||
...(savedDashboardPanel.id !== undefined && { savedObjectId: savedDashboardPanel.id }),
|
||||
explicitInput: {
|
||||
id: savedDashboardPanel.panelIndex,
|
||||
...(savedDashboardPanel.id !== undefined && { savedObjectId: savedDashboardPanel.id }),
|
||||
...(savedDashboardPanel.title !== undefined && { title: savedDashboardPanel.title }),
|
||||
...savedDashboardPanel.embeddableConfig,
|
||||
},
|
||||
|
@ -42,13 +43,14 @@ export function convertPanelStateToSavedDashboardPanel(
|
|||
const customTitle: string | undefined = panelState.explicitInput.title
|
||||
? (panelState.explicitInput.title as string)
|
||||
: undefined;
|
||||
const savedObjectId = (panelState.explicitInput as SavedObjectEmbeddableInput).savedObjectId;
|
||||
return {
|
||||
version,
|
||||
type: panelState.type,
|
||||
gridData: panelState.gridData,
|
||||
panelIndex: panelState.explicitInput.id,
|
||||
embeddableConfig: omit(panelState.explicitInput, 'id'),
|
||||
embeddableConfig: omit(panelState.explicitInput, ['id', 'savedObjectId']),
|
||||
...(customTitle && { title: customTitle }),
|
||||
...(panelState.savedObjectId !== undefined && { id: panelState.savedObjectId }),
|
||||
...(savedObjectId !== undefined && { id: savedObjectId }),
|
||||
};
|
||||
}
|
||||
|
|
|
@ -61,6 +61,8 @@ export {
|
|||
PropertySpec,
|
||||
ViewMode,
|
||||
withEmbeddableSubscription,
|
||||
SavedObjectEmbeddableInput,
|
||||
isSavedObjectEmbeddableInput,
|
||||
} from './lib';
|
||||
|
||||
export function plugin(initializerContext: PluginInitializerContext) {
|
||||
|
|
|
@ -30,6 +30,7 @@ import {
|
|||
import { IContainer, ContainerInput, ContainerOutput, PanelState } from './i_container';
|
||||
import { PanelNotFoundError, EmbeddableFactoryNotFoundError } from '../errors';
|
||||
import { EmbeddableStart } from '../../plugin';
|
||||
import { isSavedObjectEmbeddableInput } from '../embeddables/saved_object_embeddable';
|
||||
|
||||
const getKeys = <T extends {}>(o: T): Array<keyof T> => Object.keys(o) as Array<keyof T>;
|
||||
|
||||
|
@ -98,17 +99,6 @@ export abstract class Container<
|
|||
return this.createAndSaveEmbeddable(type, panelState);
|
||||
}
|
||||
|
||||
public async addSavedObjectEmbeddable<
|
||||
TEmbeddableInput extends EmbeddableInput = EmbeddableInput,
|
||||
TEmbeddable extends IEmbeddable<TEmbeddableInput> = IEmbeddable<TEmbeddableInput>
|
||||
>(type: string, savedObjectId: string): Promise<TEmbeddable | ErrorEmbeddable> {
|
||||
const factory = this.getFactory(type) as EmbeddableFactory<TEmbeddableInput, any, TEmbeddable>;
|
||||
const panelState = this.createNewPanelState(factory);
|
||||
panelState.savedObjectId = savedObjectId;
|
||||
|
||||
return this.createAndSaveEmbeddable(type, panelState);
|
||||
}
|
||||
|
||||
public removeEmbeddable(embeddableId: string) {
|
||||
// Just a shortcut for removing the panel from input state, all internal state will get cleaned up naturally
|
||||
// by the listener.
|
||||
|
@ -304,8 +294,10 @@ export abstract class Container<
|
|||
throw new EmbeddableFactoryNotFoundError(panel.type);
|
||||
}
|
||||
|
||||
embeddable = panel.savedObjectId
|
||||
? await factory.createFromSavedObject(panel.savedObjectId, inputForChild, this)
|
||||
// TODO: lets get rid of this distinction with factories, I don't think it will be needed
|
||||
// anymore after this change.
|
||||
embeddable = isSavedObjectEmbeddableInput(inputForChild)
|
||||
? await factory.createFromSavedObject(inputForChild.savedObjectId, inputForChild, this)
|
||||
: await factory.create(inputForChild, this);
|
||||
} catch (e) {
|
||||
embeddable = new ErrorEmbeddable(e, { id: panel.explicitInput.id }, this);
|
||||
|
@ -323,23 +315,6 @@ export abstract class Container<
|
|||
return;
|
||||
}
|
||||
|
||||
if (embeddable.getOutput().savedObjectId) {
|
||||
this.updateInput({
|
||||
panels: {
|
||||
...this.input.panels,
|
||||
[panel.explicitInput.id]: {
|
||||
...this.input.panels[panel.explicitInput.id],
|
||||
...(embeddable.getOutput().savedObjectId
|
||||
? { savedObjectId: embeddable.getOutput().savedObjectId }
|
||||
: undefined),
|
||||
explicitInput: {
|
||||
...this.input.panels[panel.explicitInput.id].explicitInput,
|
||||
},
|
||||
},
|
||||
},
|
||||
} as Partial<TContainerInput>);
|
||||
}
|
||||
|
||||
this.children[embeddable.id] = embeddable;
|
||||
this.updateOutput({
|
||||
embeddableLoaded: {
|
||||
|
|
|
@ -25,9 +25,7 @@ import {
|
|||
IEmbeddable,
|
||||
} from '../embeddables';
|
||||
|
||||
export interface PanelState<E extends { id: string } = { id: string }> {
|
||||
savedObjectId?: string;
|
||||
|
||||
export interface PanelState<E extends { id: string; [key: string]: unknown } = { id: string }> {
|
||||
// The type of embeddable in this panel. Will be used to find the factory in which to
|
||||
// load the embeddable.
|
||||
type: string;
|
||||
|
@ -89,17 +87,6 @@ export interface IContainer<
|
|||
*/
|
||||
removeEmbeddable(embeddableId: string): void;
|
||||
|
||||
/**
|
||||
* Adds a new embeddable that is backed off of a saved object.
|
||||
*/
|
||||
addSavedObjectEmbeddable<
|
||||
EEI extends EmbeddableInput = EmbeddableInput,
|
||||
E extends Embeddable<EEI> = Embeddable<EEI>
|
||||
>(
|
||||
type: string,
|
||||
savedObjectId: string
|
||||
): Promise<E | ErrorEmbeddable>;
|
||||
|
||||
/**
|
||||
* Adds a new embeddable to the container. `explicitInput` may partially specify the required embeddable input,
|
||||
* but the remainder must come from inherited container state.
|
||||
|
|
|
@ -26,6 +26,11 @@ import { TriggerContextMapping } from '../../../../ui_actions/public';
|
|||
export interface EmbeddableInput {
|
||||
viewMode?: ViewMode;
|
||||
title?: string;
|
||||
/**
|
||||
* Note this is not a saved object id. It is used to uniquely identify this
|
||||
* Embeddable instance from others (e.g. inside a container). It's possible to
|
||||
* have two Embeddables where everything else is the same but the id.
|
||||
*/
|
||||
id: string;
|
||||
lastReloadRequestTime?: number;
|
||||
hidePanelTitles?: boolean;
|
||||
|
@ -44,6 +49,8 @@ export interface EmbeddableInput {
|
|||
* Whether this embeddable should not execute triggers.
|
||||
*/
|
||||
disableTriggers?: boolean;
|
||||
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
export interface EmbeddableOutput {
|
||||
|
|
|
@ -25,3 +25,4 @@ export { ErrorEmbeddable, isErrorEmbeddable } from './error_embeddable';
|
|||
export { withEmbeddableSubscription } from './with_subscription';
|
||||
export { EmbeddableFactoryRenderer } from './embeddable_factory_renderer';
|
||||
export { EmbeddableRoot } from './embeddable_root';
|
||||
export * from './saved_object_embeddable';
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
/*
|
||||
* 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 { EmbeddableInput } from '..';
|
||||
|
||||
export interface SavedObjectEmbeddableInput extends EmbeddableInput {
|
||||
savedObjectId: string;
|
||||
}
|
||||
|
||||
export function isSavedObjectEmbeddableInput(
|
||||
input: EmbeddableInput | SavedObjectEmbeddableInput
|
||||
): input is SavedObjectEmbeddableInput {
|
||||
return (input as SavedObjectEmbeddableInput).savedObjectId !== undefined;
|
||||
}
|
|
@ -33,6 +33,7 @@ import { EmbeddableStart } from 'src/plugins/embeddable/public';
|
|||
import { IContainer } from '../../../../containers';
|
||||
import { EmbeddableFactoryNotFoundError } from '../../../../errors';
|
||||
import { SavedObjectFinderCreateNew } from './saved_object_finder_create_new';
|
||||
import { SavedObjectEmbeddableInput } from '../../../../embeddables';
|
||||
|
||||
interface Props {
|
||||
onClose: () => void;
|
||||
|
@ -98,8 +99,18 @@ export class AddPanelFlyout extends React.Component<Props, State> {
|
|||
}
|
||||
};
|
||||
|
||||
public onAddPanel = async (id: string, type: string, name: string) => {
|
||||
this.props.container.addSavedObjectEmbeddable(type, id);
|
||||
public onAddPanel = async (savedObjectId: string, savedObjectType: string, name: string) => {
|
||||
const factoryForSavedObjectType = [...this.props.getAllFactories()].find(
|
||||
factory => factory.savedObjectMetaData && factory.savedObjectMetaData.type === savedObjectType
|
||||
);
|
||||
if (!factoryForSavedObjectType) {
|
||||
throw new EmbeddableFactoryNotFoundError(savedObjectType);
|
||||
}
|
||||
|
||||
this.props.container.addNewEmbeddable<SavedObjectEmbeddableInput>(
|
||||
factoryForSavedObjectType.type,
|
||||
{ savedObjectId }
|
||||
);
|
||||
|
||||
this.showToast(name);
|
||||
};
|
||||
|
|
|
@ -641,8 +641,7 @@ test('container stores ErrorEmbeddables when a saved object cannot be found', as
|
|||
panels: {
|
||||
'123': {
|
||||
type: 'vis',
|
||||
explicitInput: { id: '123' },
|
||||
savedObjectId: '456',
|
||||
explicitInput: { id: '123', savedObjectId: '456' },
|
||||
},
|
||||
},
|
||||
viewMode: ViewMode.EDIT,
|
||||
|
@ -663,8 +662,7 @@ test('ErrorEmbeddables get updated when parent does', async done => {
|
|||
panels: {
|
||||
'123': {
|
||||
type: 'vis',
|
||||
explicitInput: { id: '123' },
|
||||
savedObjectId: '456',
|
||||
explicitInput: { id: '123', savedObjectId: '456' },
|
||||
},
|
||||
},
|
||||
viewMode: ViewMode.EDIT,
|
||||
|
|
|
@ -23,6 +23,7 @@ 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 flyout = getService('flyout');
|
||||
|
||||
describe('creating and adding children', () => {
|
||||
before(async () => {
|
||||
|
@ -39,5 +40,15 @@ export default function({ getService }: PluginFunctionalProviderContext) {
|
|||
const tasks = await testSubjects.getVisibleTextAll('todoEmbeddableTask');
|
||||
expect(tasks).to.eql(['Goes out on Wednesdays!', 'new task']);
|
||||
});
|
||||
|
||||
it('Can add a child backed off a saved object', async () => {
|
||||
await testSubjects.click('embeddablePanelToggleMenuIcon');
|
||||
await testSubjects.click('embeddablePanelAction-ACTION_ADD_PANEL');
|
||||
await testSubjects.click('savedObjectTitleGarbage');
|
||||
await testSubjects.moveMouseTo('euiFlyoutCloseButton');
|
||||
await flyout.ensureClosed('dashboardAddPanel');
|
||||
const tasks = await testSubjects.getVisibleTextAll('todoEmbeddableTask');
|
||||
expect(tasks).to.eql(['Goes out on Wednesdays!', 'new task', 'Take the garbage out']);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -47,7 +47,7 @@ export const dashboardInput: DashboardContainerInput = {
|
|||
explicitInput: {
|
||||
id: '2',
|
||||
firstName: 'Sue',
|
||||
} as any,
|
||||
},
|
||||
},
|
||||
'822cd0f0-ce7c-419d-aeaa-1171cf452745': {
|
||||
gridData: {
|
||||
|
@ -60,8 +60,8 @@ export const dashboardInput: DashboardContainerInput = {
|
|||
type: 'visualization',
|
||||
explicitInput: {
|
||||
id: '822cd0f0-ce7c-419d-aeaa-1171cf452745',
|
||||
savedObjectId: '3fe22200-3dcb-11e8-8660-4d65aa086b3c',
|
||||
},
|
||||
savedObjectId: '3fe22200-3dcb-11e8-8660-4d65aa086b3c',
|
||||
},
|
||||
'66f0a265-7b06-4974-accd-d05f74f7aa82': {
|
||||
gridData: {
|
||||
|
@ -74,8 +74,8 @@ export const dashboardInput: DashboardContainerInput = {
|
|||
type: 'visualization',
|
||||
explicitInput: {
|
||||
id: '66f0a265-7b06-4974-accd-d05f74f7aa82',
|
||||
savedObjectId: '4c0f47e0-3dcd-11e8-8660-4d65aa086b3c',
|
||||
},
|
||||
savedObjectId: '4c0f47e0-3dcd-11e8-8660-4d65aa086b3c',
|
||||
},
|
||||
'b2861741-40b9-4dc8-b82b-080c6e29a551': {
|
||||
gridData: {
|
||||
|
@ -88,8 +88,8 @@ export const dashboardInput: DashboardContainerInput = {
|
|||
type: 'search',
|
||||
explicitInput: {
|
||||
id: 'b2861741-40b9-4dc8-b82b-080c6e29a551',
|
||||
savedObjectId: 'be5accf0-3dca-11e8-8660-4d65aa086b3c',
|
||||
},
|
||||
savedObjectId: 'be5accf0-3dca-11e8-8660-4d65aa086b3c',
|
||||
},
|
||||
},
|
||||
isFullScreenMode: false,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue