mirror of
https://github.com/elastic/kibana.git
synced 2025-06-27 10:40:07 -04:00
Clean up saved object based embeddable examples (#162987)
## Summary These examples are outdated and don't show recent embeddable best practices. They also use client-side saved object client and block making `SavedObjectFinder` backward compatible https://github.com/elastic/kibana/pull/162904 as the `foobar` saved objects need to be added to content management. We decided that it is better to clean them up, as fixing them is not a small effort and it is not worth it on this point as a large embeddable refactor is coming.
This commit is contained in:
parent
bb68c20d99
commit
65fd7ad260
47 changed files with 24 additions and 2538 deletions
|
@ -1,14 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
|
||||||
* or more contributor license agreements. Licensed under the Elastic License
|
|
||||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
|
||||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
|
||||||
* Side Public License, v 1.
|
|
||||||
*/
|
|
||||||
export const BOOK_SAVED_OBJECT = 'book';
|
|
||||||
|
|
||||||
export interface BookSavedObjectAttributes {
|
|
||||||
title: string;
|
|
||||||
author?: string;
|
|
||||||
readIt?: boolean;
|
|
||||||
}
|
|
|
@ -1,11 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
|
||||||
* or more contributor license agreements. Licensed under the Elastic License
|
|
||||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
|
||||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
|
||||||
* Side Public License, v 1.
|
|
||||||
*/
|
|
||||||
|
|
||||||
export type { TodoSavedObjectAttributes } from './todo_saved_object_attributes';
|
|
||||||
export type { BookSavedObjectAttributes } from './book_saved_object_attributes';
|
|
||||||
export { BOOK_SAVED_OBJECT } from './book_saved_object_attributes';
|
|
|
@ -1,13 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
|
||||||
* or more contributor license agreements. Licensed under the Elastic License
|
|
||||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
|
||||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
|
||||||
* Side Public License, v 1.
|
|
||||||
*/
|
|
||||||
|
|
||||||
export interface TodoSavedObjectAttributes {
|
|
||||||
task: string;
|
|
||||||
icon?: string;
|
|
||||||
title?: string;
|
|
||||||
}
|
|
|
@ -10,17 +10,10 @@
|
||||||
"requiredPlugins": [
|
"requiredPlugins": [
|
||||||
"embeddable",
|
"embeddable",
|
||||||
"uiActions",
|
"uiActions",
|
||||||
"savedObjects",
|
|
||||||
"dashboard",
|
"dashboard",
|
||||||
"kibanaUtils"
|
|
||||||
],
|
|
||||||
"requiredBundles": [
|
|
||||||
"kibanaReact"
|
|
||||||
],
|
],
|
||||||
"extraPublicDirs": [
|
"extraPublicDirs": [
|
||||||
"public/todo",
|
"public/hello_world"
|
||||||
"public/hello_world",
|
|
||||||
"public/todo/todo_ref_embeddable"
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,47 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
|
||||||
* or more contributor license agreements. Licensed under the Elastic License
|
|
||||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
|
||||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
|
||||||
* Side Public License, v 1.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { i18n } from '@kbn/i18n';
|
|
||||||
import { IncompatibleActionError } from '@kbn/ui-actions-plugin/public';
|
|
||||||
import { ViewMode, isReferenceOrValueEmbeddable } from '@kbn/embeddable-plugin/public';
|
|
||||||
import { DASHBOARD_CONTAINER_TYPE } from '@kbn/dashboard-plugin/public';
|
|
||||||
import { BookEmbeddable, BOOK_EMBEDDABLE } from './book_embeddable';
|
|
||||||
|
|
||||||
interface ActionContext {
|
|
||||||
embeddable: BookEmbeddable;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const ACTION_ADD_BOOK_TO_LIBRARY = 'ACTION_ADD_BOOK_TO_LIBRARY';
|
|
||||||
|
|
||||||
export const createAddBookToLibraryActionDefinition = () => ({
|
|
||||||
getDisplayName: () =>
|
|
||||||
i18n.translate('embeddableExamples.book.addToLibrary', {
|
|
||||||
defaultMessage: 'Add Book To Library',
|
|
||||||
}),
|
|
||||||
id: ACTION_ADD_BOOK_TO_LIBRARY,
|
|
||||||
type: ACTION_ADD_BOOK_TO_LIBRARY,
|
|
||||||
order: 100,
|
|
||||||
getIconType: () => 'folderCheck',
|
|
||||||
isCompatible: async ({ embeddable }: ActionContext) => {
|
|
||||||
return (
|
|
||||||
embeddable.type === BOOK_EMBEDDABLE &&
|
|
||||||
embeddable.getInput().viewMode === ViewMode.EDIT &&
|
|
||||||
embeddable.getRoot().isContainer &&
|
|
||||||
embeddable.getRoot().type !== DASHBOARD_CONTAINER_TYPE &&
|
|
||||||
isReferenceOrValueEmbeddable(embeddable) &&
|
|
||||||
!embeddable.inputIsRefType(embeddable.getInput())
|
|
||||||
);
|
|
||||||
},
|
|
||||||
execute: async ({ embeddable }: ActionContext) => {
|
|
||||||
if (!isReferenceOrValueEmbeddable(embeddable)) {
|
|
||||||
throw new IncompatibleActionError();
|
|
||||||
}
|
|
||||||
const newInput = await embeddable.getInputAsRefType();
|
|
||||||
embeddable.updateInput(newInput);
|
|
||||||
},
|
|
||||||
});
|
|
|
@ -1,100 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
|
||||||
* or more contributor license agreements. Licensed under the Elastic License
|
|
||||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
|
||||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
|
||||||
* Side Public License, v 1.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import React from 'react';
|
|
||||||
import { EuiFlexItem, EuiFlexGroup, EuiIcon } from '@elastic/eui';
|
|
||||||
|
|
||||||
import { EuiText } from '@elastic/eui';
|
|
||||||
import { i18n } from '@kbn/i18n';
|
|
||||||
import { withEmbeddableSubscription } from '@kbn/embeddable-plugin/public';
|
|
||||||
import { BookEmbeddableInput, BookEmbeddableOutput, BookEmbeddable } from './book_embeddable';
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
input: BookEmbeddableInput;
|
|
||||||
output: BookEmbeddableOutput;
|
|
||||||
embeddable: BookEmbeddable;
|
|
||||||
}
|
|
||||||
|
|
||||||
function wrapSearchTerms(task?: string, search?: string) {
|
|
||||||
if (!search || !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 BookEmbeddableComponentInner({
|
|
||||||
input: { search },
|
|
||||||
output: { attributes },
|
|
||||||
embeddable,
|
|
||||||
}: Props) {
|
|
||||||
const title = attributes?.title;
|
|
||||||
const author = attributes?.author;
|
|
||||||
const readIt = attributes?.readIt;
|
|
||||||
|
|
||||||
const byReference = embeddable.inputIsRefType(embeddable.getInput());
|
|
||||||
|
|
||||||
return (
|
|
||||||
<EuiFlexGroup gutterSize="s">
|
|
||||||
<EuiFlexItem>
|
|
||||||
<EuiFlexGroup direction="column" gutterSize="s">
|
|
||||||
{title ? (
|
|
||||||
<EuiFlexItem>
|
|
||||||
<EuiText data-test-subj="bookEmbeddableTitle">
|
|
||||||
<h3>{wrapSearchTerms(title, search)}</h3>
|
|
||||||
</EuiText>
|
|
||||||
</EuiFlexItem>
|
|
||||||
) : null}
|
|
||||||
{author ? (
|
|
||||||
<EuiFlexItem>
|
|
||||||
<EuiText data-test-subj="bookEmbeddableAuthor">
|
|
||||||
-{wrapSearchTerms(author, search)}
|
|
||||||
</EuiText>
|
|
||||||
</EuiFlexItem>
|
|
||||||
) : null}
|
|
||||||
{readIt ? (
|
|
||||||
<EuiFlexItem>
|
|
||||||
<EuiIcon type="check" />
|
|
||||||
</EuiFlexItem>
|
|
||||||
) : (
|
|
||||||
<EuiFlexItem>
|
|
||||||
<EuiIcon type="cross" />
|
|
||||||
</EuiFlexItem>
|
|
||||||
)}
|
|
||||||
</EuiFlexGroup>
|
|
||||||
</EuiFlexItem>
|
|
||||||
<EuiFlexItem>
|
|
||||||
<EuiText data-test-subj="bookEmbeddableAuthor">
|
|
||||||
<EuiIcon type={byReference ? 'folderCheck' : 'folderExclamation'} />{' '}
|
|
||||||
<span>
|
|
||||||
{byReference
|
|
||||||
? i18n.translate('embeddableExamples.book.byReferenceLabel', {
|
|
||||||
defaultMessage: 'Book is By Reference',
|
|
||||||
})
|
|
||||||
: i18n.translate('embeddableExamples.book.byValueLabel', {
|
|
||||||
defaultMessage: 'Book is By Value',
|
|
||||||
})}
|
|
||||||
</span>
|
|
||||||
</EuiText>
|
|
||||||
</EuiFlexItem>
|
|
||||||
</EuiFlexGroup>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export const BookEmbeddableComponent = withEmbeddableSubscription<
|
|
||||||
BookEmbeddableInput,
|
|
||||||
BookEmbeddableOutput,
|
|
||||||
BookEmbeddable,
|
|
||||||
{}
|
|
||||||
>(BookEmbeddableComponentInner);
|
|
|
@ -1,134 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
|
||||||
* or more contributor license agreements. Licensed under the Elastic License
|
|
||||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
|
||||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
|
||||||
* Side Public License, v 1.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import React from 'react';
|
|
||||||
import ReactDOM from 'react-dom';
|
|
||||||
import { Subscription } from 'rxjs';
|
|
||||||
import {
|
|
||||||
Embeddable,
|
|
||||||
EmbeddableInput,
|
|
||||||
IContainer,
|
|
||||||
EmbeddableOutput,
|
|
||||||
SavedObjectEmbeddableInput,
|
|
||||||
ReferenceOrValueEmbeddable,
|
|
||||||
AttributeService,
|
|
||||||
} from '@kbn/embeddable-plugin/public';
|
|
||||||
import { BookSavedObjectAttributes } from '../../common';
|
|
||||||
import { BookEmbeddableComponent } from './book_component';
|
|
||||||
|
|
||||||
export const BOOK_EMBEDDABLE = 'book';
|
|
||||||
export type BookEmbeddableInput = BookByValueInput | BookByReferenceInput;
|
|
||||||
export interface BookEmbeddableOutput extends EmbeddableOutput {
|
|
||||||
hasMatch: boolean;
|
|
||||||
attributes: BookSavedObjectAttributes;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface BookInheritedInput extends EmbeddableInput {
|
|
||||||
search?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type BookByValueInput = { attributes: BookSavedObjectAttributes } & BookInheritedInput;
|
|
||||||
export type BookByReferenceInput = SavedObjectEmbeddableInput & BookInheritedInput;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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?: BookSavedObjectAttributes): boolean {
|
|
||||||
if (!search) return true;
|
|
||||||
if (!savedAttributes) return false;
|
|
||||||
return Boolean(
|
|
||||||
(savedAttributes.author && savedAttributes.author.match(search)) ||
|
|
||||||
(savedAttributes.title && savedAttributes.title.match(search))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export class BookEmbeddable
|
|
||||||
extends Embeddable<BookEmbeddableInput, BookEmbeddableOutput>
|
|
||||||
implements ReferenceOrValueEmbeddable<BookByValueInput, BookByReferenceInput>
|
|
||||||
{
|
|
||||||
public readonly type = BOOK_EMBEDDABLE;
|
|
||||||
private subscription: Subscription;
|
|
||||||
private node?: HTMLElement;
|
|
||||||
private savedObjectId?: string;
|
|
||||||
private attributes?: BookSavedObjectAttributes;
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
initialInput: BookEmbeddableInput,
|
|
||||||
private attributeService: AttributeService<BookSavedObjectAttributes>,
|
|
||||||
{
|
|
||||||
parent,
|
|
||||||
}: {
|
|
||||||
parent?: IContainer;
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
super(initialInput, {} as BookEmbeddableOutput, parent);
|
|
||||||
|
|
||||||
this.subscription = this.getInput$().subscribe(async () => {
|
|
||||||
const savedObjectId = (this.getInput() as BookByReferenceInput).savedObjectId;
|
|
||||||
const attributes = (this.getInput() as BookByValueInput).attributes;
|
|
||||||
if (this.attributes !== attributes || this.savedObjectId !== savedObjectId) {
|
|
||||||
this.savedObjectId = savedObjectId;
|
|
||||||
this.reload();
|
|
||||||
} else {
|
|
||||||
this.updateOutput({
|
|
||||||
attributes: this.attributes,
|
|
||||||
defaultTitle: this.attributes.title,
|
|
||||||
hasMatch: getHasMatch(this.input.search, this.attributes),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
readonly inputIsRefType = (input: BookEmbeddableInput): input is BookByReferenceInput => {
|
|
||||||
return this.attributeService.inputIsRefType(input);
|
|
||||||
};
|
|
||||||
|
|
||||||
readonly getInputAsValueType = async (): Promise<BookByValueInput> => {
|
|
||||||
return this.attributeService.getInputAsValueType(this.getExplicitInput());
|
|
||||||
};
|
|
||||||
|
|
||||||
readonly getInputAsRefType = async (): Promise<BookByReferenceInput> => {
|
|
||||||
return this.attributeService.getInputAsRefType(this.getExplicitInput(), {
|
|
||||||
showSaveModal: true,
|
|
||||||
saveModalTitle: this.getTitle(),
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
public render(node: HTMLElement) {
|
|
||||||
if (this.node) {
|
|
||||||
ReactDOM.unmountComponentAtNode(this.node);
|
|
||||||
}
|
|
||||||
this.node = node;
|
|
||||||
ReactDOM.render(<BookEmbeddableComponent embeddable={this} />, node);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async reload() {
|
|
||||||
this.attributes = (await this.attributeService.unwrapAttributes(this.input)).attributes;
|
|
||||||
|
|
||||||
this.updateOutput({
|
|
||||||
attributes: this.attributes,
|
|
||||||
defaultTitle: this.attributes.title,
|
|
||||||
hasMatch: getHasMatch(this.input.search, this.attributes),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public getTitle() {
|
|
||||||
return this.getOutput()?.title || this.getOutput().attributes?.title;
|
|
||||||
}
|
|
||||||
|
|
||||||
public destroy() {
|
|
||||||
super.destroy();
|
|
||||||
this.subscription.unsubscribe();
|
|
||||||
if (this.node) {
|
|
||||||
ReactDOM.unmountComponentAtNode(this.node);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,163 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
|
||||||
* or more contributor license agreements. Licensed under the Elastic License
|
|
||||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
|
||||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
|
||||||
* Side Public License, v 1.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import React from 'react';
|
|
||||||
import { i18n } from '@kbn/i18n';
|
|
||||||
import { toMountPoint } from '@kbn/kibana-react-plugin/public';
|
|
||||||
import {
|
|
||||||
EmbeddableFactoryDefinition,
|
|
||||||
IContainer,
|
|
||||||
EmbeddableFactory,
|
|
||||||
EmbeddableStart,
|
|
||||||
AttributeService,
|
|
||||||
} from '@kbn/embeddable-plugin/public';
|
|
||||||
import { OverlayStart, SavedObjectsClientContract, SimpleSavedObject } from '@kbn/core/public';
|
|
||||||
import { checkForDuplicateTitle, OnSaveProps } from '@kbn/saved-objects-plugin/public';
|
|
||||||
import {
|
|
||||||
BookEmbeddable,
|
|
||||||
BOOK_EMBEDDABLE,
|
|
||||||
BookEmbeddableInput,
|
|
||||||
BookEmbeddableOutput,
|
|
||||||
} from './book_embeddable';
|
|
||||||
import { CreateEditBookComponent } from './create_edit_book_component';
|
|
||||||
import { BookSavedObjectAttributes, BOOK_SAVED_OBJECT } from '../../common';
|
|
||||||
|
|
||||||
interface StartServices {
|
|
||||||
getAttributeService: EmbeddableStart['getAttributeService'];
|
|
||||||
openModal: OverlayStart['openModal'];
|
|
||||||
savedObjectsClient: SavedObjectsClientContract;
|
|
||||||
overlays: OverlayStart;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type BookEmbeddableFactory = EmbeddableFactory<
|
|
||||||
BookEmbeddableInput,
|
|
||||||
BookEmbeddableOutput,
|
|
||||||
BookEmbeddable,
|
|
||||||
BookSavedObjectAttributes
|
|
||||||
>;
|
|
||||||
|
|
||||||
export class BookEmbeddableFactoryDefinition
|
|
||||||
implements
|
|
||||||
EmbeddableFactoryDefinition<
|
|
||||||
BookEmbeddableInput,
|
|
||||||
BookEmbeddableOutput,
|
|
||||||
BookEmbeddable,
|
|
||||||
BookSavedObjectAttributes
|
|
||||||
>
|
|
||||||
{
|
|
||||||
public readonly type = BOOK_EMBEDDABLE;
|
|
||||||
public savedObjectMetaData = {
|
|
||||||
name: 'Book',
|
|
||||||
includeFields: ['title', 'author', 'readIt'],
|
|
||||||
type: BOOK_SAVED_OBJECT,
|
|
||||||
getIconForSavedObject: () => 'pencil',
|
|
||||||
};
|
|
||||||
|
|
||||||
private attributeService?: AttributeService<BookSavedObjectAttributes>;
|
|
||||||
|
|
||||||
constructor(private getStartServices: () => Promise<StartServices>) {}
|
|
||||||
|
|
||||||
public async isEditable() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async create(input: BookEmbeddableInput, parent?: IContainer) {
|
|
||||||
return new BookEmbeddable(input, await this.getAttributeService(), {
|
|
||||||
parent,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// This is currently required due to the distinction in container.ts and the
|
|
||||||
// default error implementation in default_embeddable_factory_provider.ts
|
|
||||||
public async createFromSavedObject(
|
|
||||||
savedObjectId: string,
|
|
||||||
input: BookEmbeddableInput,
|
|
||||||
parent?: IContainer
|
|
||||||
) {
|
|
||||||
return this.create(input, parent);
|
|
||||||
}
|
|
||||||
|
|
||||||
public getDisplayName() {
|
|
||||||
return i18n.translate('embeddableExamples.book.displayName', {
|
|
||||||
defaultMessage: 'Book',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public async getExplicitInput(): Promise<Omit<BookEmbeddableInput, 'id'>> {
|
|
||||||
const { openModal } = await this.getStartServices();
|
|
||||||
return new Promise<Omit<BookEmbeddableInput, 'id'>>((resolve) => {
|
|
||||||
const onSave = async (attributes: BookSavedObjectAttributes, useRefType: boolean) => {
|
|
||||||
const wrappedAttributes = (await this.getAttributeService()).wrapAttributes(
|
|
||||||
attributes,
|
|
||||||
useRefType
|
|
||||||
);
|
|
||||||
resolve(wrappedAttributes);
|
|
||||||
};
|
|
||||||
const overlay = openModal(
|
|
||||||
toMountPoint(
|
|
||||||
<CreateEditBookComponent
|
|
||||||
onSave={(attributes: BookSavedObjectAttributes, useRefType: boolean) => {
|
|
||||||
onSave(attributes, useRefType);
|
|
||||||
overlay.close();
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private async unwrapMethod(
|
|
||||||
savedObjectId: string
|
|
||||||
): Promise<{ attributes: BookSavedObjectAttributes }> {
|
|
||||||
const { savedObjectsClient } = await this.getStartServices();
|
|
||||||
const savedObject: SimpleSavedObject<BookSavedObjectAttributes> =
|
|
||||||
await savedObjectsClient.get<BookSavedObjectAttributes>(this.type, savedObjectId);
|
|
||||||
return { attributes: { ...savedObject.attributes } };
|
|
||||||
}
|
|
||||||
|
|
||||||
private async saveMethod(attributes: BookSavedObjectAttributes, savedObjectId?: string) {
|
|
||||||
const { savedObjectsClient } = await this.getStartServices();
|
|
||||||
if (savedObjectId) {
|
|
||||||
return savedObjectsClient.update(this.type, savedObjectId, attributes);
|
|
||||||
}
|
|
||||||
return savedObjectsClient.create(this.type, attributes);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async checkForDuplicateTitleMethod(props: OnSaveProps): Promise<true> {
|
|
||||||
const start = await this.getStartServices();
|
|
||||||
const { savedObjectsClient, overlays } = start;
|
|
||||||
return checkForDuplicateTitle(
|
|
||||||
{
|
|
||||||
title: props.newTitle,
|
|
||||||
copyOnSave: false,
|
|
||||||
lastSavedTitle: '',
|
|
||||||
getEsType: () => this.type,
|
|
||||||
getDisplayName: this.getDisplayName || (() => this.type),
|
|
||||||
},
|
|
||||||
props.isTitleDuplicateConfirmed,
|
|
||||||
props.onTitleDuplicate,
|
|
||||||
{
|
|
||||||
savedObjectsClient,
|
|
||||||
overlays,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async getAttributeService() {
|
|
||||||
if (!this.attributeService) {
|
|
||||||
this.attributeService = (
|
|
||||||
await this.getStartServices()
|
|
||||||
).getAttributeService<BookSavedObjectAttributes>(this.type, {
|
|
||||||
saveMethod: this.saveMethod.bind(this),
|
|
||||||
unwrapMethod: this.unwrapMethod.bind(this),
|
|
||||||
checkForDuplicateTitle: this.checkForDuplicateTitleMethod.bind(this),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return this.attributeService!;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,78 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
|
||||||
* or more contributor license agreements. Licensed under the Elastic License
|
|
||||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
|
||||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
|
||||||
* Side Public License, v 1.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import React, { useState } from 'react';
|
|
||||||
import { EuiModalBody, EuiCheckbox } from '@elastic/eui';
|
|
||||||
import { EuiFieldText } from '@elastic/eui';
|
|
||||||
import { EuiButton } from '@elastic/eui';
|
|
||||||
import { EuiModalFooter } from '@elastic/eui';
|
|
||||||
import { EuiModalHeader } from '@elastic/eui';
|
|
||||||
import { EuiFormRow } from '@elastic/eui';
|
|
||||||
import { BookSavedObjectAttributes } from '../../common';
|
|
||||||
|
|
||||||
export function CreateEditBookComponent({
|
|
||||||
savedObjectId,
|
|
||||||
attributes,
|
|
||||||
onSave,
|
|
||||||
}: {
|
|
||||||
savedObjectId?: string;
|
|
||||||
attributes?: BookSavedObjectAttributes;
|
|
||||||
onSave: (attributes: BookSavedObjectAttributes, useRefType: boolean) => void;
|
|
||||||
}) {
|
|
||||||
const [title, setTitle] = useState(attributes?.title ?? '');
|
|
||||||
const [author, setAuthor] = useState(attributes?.author ?? '');
|
|
||||||
const [readIt, setReadIt] = useState(attributes?.readIt ?? false);
|
|
||||||
return (
|
|
||||||
<EuiModalBody>
|
|
||||||
<EuiModalHeader>
|
|
||||||
<h1>{`${savedObjectId ? 'Create new ' : 'Edit '}`}</h1>
|
|
||||||
</EuiModalHeader>
|
|
||||||
<EuiModalBody>
|
|
||||||
<EuiFormRow label="Title">
|
|
||||||
<EuiFieldText
|
|
||||||
data-test-subj="titleInputField"
|
|
||||||
value={title}
|
|
||||||
placeholder="Title"
|
|
||||||
onChange={(e) => setTitle(e.target.value)}
|
|
||||||
/>
|
|
||||||
</EuiFormRow>
|
|
||||||
<EuiFormRow label="Author">
|
|
||||||
<EuiFieldText
|
|
||||||
data-test-subj="authorInputField"
|
|
||||||
value={author}
|
|
||||||
placeholder="Author"
|
|
||||||
onChange={(e) => setAuthor(e.target.value)}
|
|
||||||
/>
|
|
||||||
</EuiFormRow>
|
|
||||||
<EuiFormRow label="Read It">
|
|
||||||
<EuiCheckbox
|
|
||||||
id="ReadIt"
|
|
||||||
checked={readIt}
|
|
||||||
onChange={(event) => setReadIt(event.target.checked)}
|
|
||||||
/>
|
|
||||||
</EuiFormRow>
|
|
||||||
</EuiModalBody>
|
|
||||||
<EuiModalFooter>
|
|
||||||
<EuiButton
|
|
||||||
data-test-subj="saveBookEmbeddableByValue"
|
|
||||||
disabled={title === ''}
|
|
||||||
onClick={() => onSave({ title, author, readIt }, false)}
|
|
||||||
>
|
|
||||||
{savedObjectId ? 'Unlink from library item' : 'Save and Return'}
|
|
||||||
</EuiButton>
|
|
||||||
<EuiButton
|
|
||||||
data-test-subj="saveBookEmbeddableByRef"
|
|
||||||
disabled={title === ''}
|
|
||||||
onClick={() => onSave({ title, author, readIt }, true)}
|
|
||||||
>
|
|
||||||
{savedObjectId ? 'Update library item' : 'Save to library'}
|
|
||||||
</EuiButton>
|
|
||||||
</EuiModalFooter>
|
|
||||||
</EuiModalBody>
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -1,95 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
|
||||||
* or more contributor license agreements. Licensed under the Elastic License
|
|
||||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
|
||||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
|
||||||
* Side Public License, v 1.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import React from 'react';
|
|
||||||
import { OverlayStart } from '@kbn/core/public';
|
|
||||||
import { i18n } from '@kbn/i18n';
|
|
||||||
import { toMountPoint } from '@kbn/kibana-react-plugin/public';
|
|
||||||
import {
|
|
||||||
ViewMode,
|
|
||||||
SavedObjectEmbeddableInput,
|
|
||||||
EmbeddableStart,
|
|
||||||
} from '@kbn/embeddable-plugin/public';
|
|
||||||
import { OnSaveProps } from '@kbn/saved-objects-plugin/public';
|
|
||||||
import { SavedObjectsClientContract } from '@kbn/core/public';
|
|
||||||
import {
|
|
||||||
BookEmbeddable,
|
|
||||||
BOOK_EMBEDDABLE,
|
|
||||||
BookByReferenceInput,
|
|
||||||
BookByValueInput,
|
|
||||||
} from './book_embeddable';
|
|
||||||
import { CreateEditBookComponent } from './create_edit_book_component';
|
|
||||||
import { BookSavedObjectAttributes, BOOK_SAVED_OBJECT } from '../../common';
|
|
||||||
|
|
||||||
interface StartServices {
|
|
||||||
openModal: OverlayStart['openModal'];
|
|
||||||
getAttributeService: EmbeddableStart['getAttributeService'];
|
|
||||||
savedObjectsClient: SavedObjectsClientContract;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface ActionContext {
|
|
||||||
embeddable: BookEmbeddable;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const ACTION_EDIT_BOOK = 'ACTION_EDIT_BOOK';
|
|
||||||
|
|
||||||
export const createEditBookActionDefinition = (getStartServices: () => Promise<StartServices>) => ({
|
|
||||||
getDisplayName: () =>
|
|
||||||
i18n.translate('embeddableExamples.book.edit', { defaultMessage: 'Edit Book' }),
|
|
||||||
id: ACTION_EDIT_BOOK,
|
|
||||||
type: ACTION_EDIT_BOOK,
|
|
||||||
order: 100,
|
|
||||||
getIconType: () => 'documents',
|
|
||||||
isCompatible: async ({ embeddable }: ActionContext) => {
|
|
||||||
return embeddable.type === BOOK_EMBEDDABLE && embeddable.getInput().viewMode === ViewMode.EDIT;
|
|
||||||
},
|
|
||||||
execute: async ({ embeddable }: ActionContext) => {
|
|
||||||
const { openModal, getAttributeService, savedObjectsClient } = await getStartServices();
|
|
||||||
const attributeService = getAttributeService<BookSavedObjectAttributes>(BOOK_SAVED_OBJECT, {
|
|
||||||
saveMethod: async (attributes: BookSavedObjectAttributes, savedObjectId?: string) => {
|
|
||||||
if (savedObjectId) {
|
|
||||||
return savedObjectsClient.update(BOOK_EMBEDDABLE, savedObjectId, attributes);
|
|
||||||
}
|
|
||||||
return savedObjectsClient.create(BOOK_EMBEDDABLE, attributes);
|
|
||||||
},
|
|
||||||
checkForDuplicateTitle: (props: OnSaveProps) => {
|
|
||||||
return new Promise(() => {
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
});
|
|
||||||
const onSave = async (attributes: BookSavedObjectAttributes, useRefType: boolean) => {
|
|
||||||
const newInput = await attributeService.wrapAttributes(
|
|
||||||
attributes,
|
|
||||||
useRefType,
|
|
||||||
embeddable.getExplicitInput()
|
|
||||||
);
|
|
||||||
if (!useRefType && (embeddable.getInput() as SavedObjectEmbeddableInput).savedObjectId) {
|
|
||||||
// Set the saved object ID to null so that update input will remove the existing savedObjectId...
|
|
||||||
(newInput as BookByValueInput & { savedObjectId: unknown }).savedObjectId = null;
|
|
||||||
}
|
|
||||||
embeddable.updateInput(newInput);
|
|
||||||
if (useRefType) {
|
|
||||||
// Ensures that any duplicate embeddables also register the changes. This mirrors the behavior of going back and forth between apps
|
|
||||||
embeddable.getRoot().reload();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
const overlay = openModal(
|
|
||||||
toMountPoint(
|
|
||||||
<CreateEditBookComponent
|
|
||||||
savedObjectId={(embeddable.getInput() as BookByReferenceInput).savedObjectId}
|
|
||||||
attributes={embeddable.getOutput().attributes}
|
|
||||||
onSave={(attributes: BookSavedObjectAttributes, useRefType: boolean) => {
|
|
||||||
overlay.close();
|
|
||||||
onSave(attributes, useRefType);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
);
|
|
||||||
},
|
|
||||||
});
|
|
|
@ -1,10 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
|
||||||
* or more contributor license agreements. Licensed under the Elastic License
|
|
||||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
|
||||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
|
||||||
* Side Public License, v 1.
|
|
||||||
*/
|
|
||||||
|
|
||||||
export * from './book_embeddable';
|
|
||||||
export * from './book_embeddable_factory';
|
|
|
@ -1,47 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
|
||||||
* or more contributor license agreements. Licensed under the Elastic License
|
|
||||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
|
||||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
|
||||||
* Side Public License, v 1.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { i18n } from '@kbn/i18n';
|
|
||||||
import { IncompatibleActionError } from '@kbn/ui-actions-plugin/public';
|
|
||||||
import { ViewMode, isReferenceOrValueEmbeddable } from '@kbn/embeddable-plugin/public';
|
|
||||||
import { DASHBOARD_CONTAINER_TYPE } from '@kbn/dashboard-plugin/public';
|
|
||||||
import { BookEmbeddable, BOOK_EMBEDDABLE } from './book_embeddable';
|
|
||||||
|
|
||||||
interface ActionContext {
|
|
||||||
embeddable: BookEmbeddable;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const ACTION_UNLINK_BOOK_FROM_LIBRARY = 'ACTION_UNLINK_BOOK_FROM_LIBRARY';
|
|
||||||
|
|
||||||
export const createUnlinkBookFromLibraryActionDefinition = () => ({
|
|
||||||
getDisplayName: () =>
|
|
||||||
i18n.translate('embeddableExamples.book.unlinkFromLibrary', {
|
|
||||||
defaultMessage: 'Unlink Book from Library Item',
|
|
||||||
}),
|
|
||||||
id: ACTION_UNLINK_BOOK_FROM_LIBRARY,
|
|
||||||
type: ACTION_UNLINK_BOOK_FROM_LIBRARY,
|
|
||||||
order: 100,
|
|
||||||
getIconType: () => 'folderExclamation',
|
|
||||||
isCompatible: async ({ embeddable }: ActionContext) => {
|
|
||||||
return (
|
|
||||||
embeddable.type === BOOK_EMBEDDABLE &&
|
|
||||||
embeddable.getInput().viewMode === ViewMode.EDIT &&
|
|
||||||
embeddable.getRoot().isContainer &&
|
|
||||||
embeddable.getRoot().type !== DASHBOARD_CONTAINER_TYPE &&
|
|
||||||
isReferenceOrValueEmbeddable(embeddable) &&
|
|
||||||
embeddable.inputIsRefType(embeddable.getInput())
|
|
||||||
);
|
|
||||||
},
|
|
||||||
execute: async ({ embeddable }: ActionContext) => {
|
|
||||||
if (!isReferenceOrValueEmbeddable(embeddable)) {
|
|
||||||
throw new IncompatibleActionError();
|
|
||||||
}
|
|
||||||
const newInput = await embeddable.getInputAsValueType();
|
|
||||||
embeddable.updateInput(newInput);
|
|
||||||
},
|
|
||||||
});
|
|
|
@ -1,38 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
|
||||||
* or more contributor license agreements. Licensed under the Elastic License
|
|
||||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
|
||||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
|
||||||
* Side Public License, v 1.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { SavedObjectsClientContract } from '@kbn/core/public';
|
|
||||||
import { TodoSavedObjectAttributes, BookSavedObjectAttributes, BOOK_SAVED_OBJECT } from '../common';
|
|
||||||
|
|
||||||
export async function createSampleData(client: SavedObjectsClientContract, overwrite = true) {
|
|
||||||
await client.create<TodoSavedObjectAttributes>(
|
|
||||||
'todo',
|
|
||||||
{
|
|
||||||
task: 'Take the garbage out',
|
|
||||||
title: 'Garbage',
|
|
||||||
icon: 'trash',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'sample-todo-saved-object',
|
|
||||||
overwrite,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
await client.create<BookSavedObjectAttributes>(
|
|
||||||
BOOK_SAVED_OBJECT,
|
|
||||||
{
|
|
||||||
title: 'Pillars of the Earth',
|
|
||||||
author: 'Ken Follett',
|
|
||||||
readIt: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'sample-book-saved-object',
|
|
||||||
overwrite,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -14,10 +14,6 @@ export {
|
||||||
} from './hello_world';
|
} from './hello_world';
|
||||||
export type { ListContainerFactory } from './list_container';
|
export type { ListContainerFactory } from './list_container';
|
||||||
export { ListContainer, LIST_CONTAINER } from './list_container';
|
export { ListContainer, LIST_CONTAINER } from './list_container';
|
||||||
export type { TodoEmbeddableFactory } from './todo';
|
|
||||||
export { TODO_EMBEDDABLE } from './todo';
|
|
||||||
|
|
||||||
export { BOOK_EMBEDDABLE } from './book';
|
|
||||||
|
|
||||||
export { SIMPLE_EMBEDDABLE } from './migrations';
|
export { SIMPLE_EMBEDDABLE } from './migrations';
|
||||||
export {
|
export {
|
||||||
|
@ -27,9 +23,4 @@ export {
|
||||||
|
|
||||||
import { EmbeddableExamplesPlugin } from './plugin';
|
import { EmbeddableExamplesPlugin } from './plugin';
|
||||||
|
|
||||||
export type { SearchableListContainerFactory } from './searchable_list_container';
|
|
||||||
export { SearchableListContainer, SEARCHABLE_LIST_CONTAINER } from './searchable_list_container';
|
|
||||||
export type { MultiTaskTodoEmbeddableFactory } from './multi_task_todo';
|
|
||||||
export { MULTI_TASK_TODO_EMBEDDABLE } from './multi_task_todo';
|
|
||||||
|
|
||||||
export const plugin = () => new EmbeddableExamplesPlugin();
|
export const plugin = () => new EmbeddableExamplesPlugin();
|
||||||
|
|
|
@ -1,10 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
|
||||||
* or more contributor license agreements. Licensed under the Elastic License
|
|
||||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
|
||||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
|
||||||
* Side Public License, v 1.
|
|
||||||
*/
|
|
||||||
|
|
||||||
export * from './multi_task_todo_embeddable';
|
|
||||||
export * from './multi_task_todo_embeddable_factory';
|
|
|
@ -1,85 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
|
||||||
* or more contributor license agreements. Licensed under the Elastic License
|
|
||||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
|
||||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
|
||||||
* Side Public License, v 1.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import React from 'react';
|
|
||||||
import { EuiFlexItem, EuiFlexGroup } from '@elastic/eui';
|
|
||||||
|
|
||||||
import {
|
|
||||||
EuiText,
|
|
||||||
EuiAvatar,
|
|
||||||
EuiIcon,
|
|
||||||
EuiFlexGrid,
|
|
||||||
EuiListGroup,
|
|
||||||
EuiListGroupItem,
|
|
||||||
} from '@elastic/eui';
|
|
||||||
import { withEmbeddableSubscription } from '@kbn/embeddable-plugin/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: MultiTaskTodoInput['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, tasks },
|
|
||||||
}: Props) {
|
|
||||||
return (
|
|
||||||
<EuiFlexGroup gutterSize="none">
|
|
||||||
<EuiFlexItem grow={false}>
|
|
||||||
{icon ? <EuiIcon type={icon} size="l" /> : <EuiAvatar name={title} size="l" />}
|
|
||||||
</EuiFlexItem>
|
|
||||||
<EuiFlexItem>
|
|
||||||
<EuiFlexGrid columns={1} gutterSize="none">
|
|
||||||
<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<
|
|
||||||
MultiTaskTodoInput,
|
|
||||||
MultiTaskTodoOutput,
|
|
||||||
MultiTaskTodoEmbeddable
|
|
||||||
>(MultiTaskTodoEmbeddableComponentInner);
|
|
|
@ -1,86 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
|
||||||
* or more contributor license agreements. Licensed under the Elastic License
|
|
||||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
|
||||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
|
||||||
* Side Public License, v 1.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import React from 'react';
|
|
||||||
import ReactDOM from 'react-dom';
|
|
||||||
import { Subscription } from 'rxjs';
|
|
||||||
import {
|
|
||||||
Embeddable,
|
|
||||||
EmbeddableInput,
|
|
||||||
IContainer,
|
|
||||||
EmbeddableOutput,
|
|
||||||
} from '@kbn/embeddable-plugin/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! 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 {
|
|
||||||
hasMatch: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getHasMatch(tasks: string[], title?: string, search?: string) {
|
|
||||||
if (search === undefined || search === '') return false;
|
|
||||||
|
|
||||||
if (title && title.match(search)) return true;
|
|
||||||
|
|
||||||
const match = tasks.find((task) => task.match(search));
|
|
||||||
if (match) return true;
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getOutput(input: MultiTaskTodoInput) {
|
|
||||||
const hasMatch = getHasMatch(input.tasks, input.title, input.search);
|
|
||||||
return { hasMatch };
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,56 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
|
||||||
* or more contributor license agreements. Licensed under the Elastic License
|
|
||||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
|
||||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
|
||||||
* Side Public License, v 1.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { i18n } from '@kbn/i18n';
|
|
||||||
import {
|
|
||||||
IContainer,
|
|
||||||
EmbeddableFactoryDefinition,
|
|
||||||
EmbeddableFactory,
|
|
||||||
} from '@kbn/embeddable-plugin/public';
|
|
||||||
import {
|
|
||||||
MultiTaskTodoEmbeddable,
|
|
||||||
MULTI_TASK_TODO_EMBEDDABLE,
|
|
||||||
MultiTaskTodoInput,
|
|
||||||
MultiTaskTodoOutput,
|
|
||||||
} from './multi_task_todo_embeddable';
|
|
||||||
|
|
||||||
export type MultiTaskTodoEmbeddableFactory = EmbeddableFactory<
|
|
||||||
MultiTaskTodoInput,
|
|
||||||
MultiTaskTodoOutput,
|
|
||||||
MultiTaskTodoEmbeddable
|
|
||||||
>;
|
|
||||||
|
|
||||||
export class MultiTaskTodoEmbeddableFactoryDefinition
|
|
||||||
implements
|
|
||||||
EmbeddableFactoryDefinition<MultiTaskTodoInput, MultiTaskTodoOutput, MultiTaskTodoEmbeddable>
|
|
||||||
{
|
|
||||||
public readonly type = MULTI_TASK_TODO_EMBEDDABLE;
|
|
||||||
|
|
||||||
public async isEditable() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async create(initialInput: MultiTaskTodoInput, parent?: IContainer) {
|
|
||||||
return new MultiTaskTodoEmbeddable(initialInput, parent);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check out todo_embeddable_factory for a better example that asks for data from
|
|
||||||
* the user. This just returns default data. That's okay too though, if you want to
|
|
||||||
* start with default data and expose an "edit" action to modify it.
|
|
||||||
*/
|
|
||||||
public async getExplicitInput() {
|
|
||||||
return { title: 'default title', tasks: ['Im default data'] };
|
|
||||||
}
|
|
||||||
|
|
||||||
public getDisplayName() {
|
|
||||||
return i18n.translate('embeddableExamples.multiTaskTodo.displayName', {
|
|
||||||
defaultMessage: 'Multi-task todo item',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -6,49 +6,21 @@
|
||||||
* Side Public License, v 1.
|
* Side Public License, v 1.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {
|
import { EmbeddableSetup, EmbeddableStart } from '@kbn/embeddable-plugin/public';
|
||||||
EmbeddableSetup,
|
import { Plugin, CoreSetup, CoreStart } from '@kbn/core/public';
|
||||||
EmbeddableStart,
|
|
||||||
CONTEXT_MENU_TRIGGER,
|
|
||||||
} from '@kbn/embeddable-plugin/public';
|
|
||||||
import { Plugin, CoreSetup, CoreStart, SavedObjectsClientContract } from '@kbn/core/public';
|
|
||||||
import { UiActionsStart } from '@kbn/ui-actions-plugin/public';
|
import { UiActionsStart } from '@kbn/ui-actions-plugin/public';
|
||||||
import {
|
import {
|
||||||
HelloWorldEmbeddableFactory,
|
HelloWorldEmbeddableFactory,
|
||||||
HELLO_WORLD_EMBEDDABLE,
|
HELLO_WORLD_EMBEDDABLE,
|
||||||
HelloWorldEmbeddableFactoryDefinition,
|
HelloWorldEmbeddableFactoryDefinition,
|
||||||
} from './hello_world';
|
} from './hello_world';
|
||||||
import { TODO_EMBEDDABLE, TodoEmbeddableFactory, TodoEmbeddableFactoryDefinition } from './todo';
|
|
||||||
|
|
||||||
import {
|
|
||||||
MULTI_TASK_TODO_EMBEDDABLE,
|
|
||||||
MultiTaskTodoEmbeddableFactory,
|
|
||||||
MultiTaskTodoEmbeddableFactoryDefinition,
|
|
||||||
} from './multi_task_todo';
|
|
||||||
import {
|
|
||||||
SEARCHABLE_LIST_CONTAINER,
|
|
||||||
SearchableListContainerFactoryDefinition,
|
|
||||||
SearchableListContainerFactory,
|
|
||||||
} from './searchable_list_container';
|
|
||||||
import {
|
import {
|
||||||
LIST_CONTAINER,
|
LIST_CONTAINER,
|
||||||
ListContainerFactoryDefinition,
|
ListContainerFactoryDefinition,
|
||||||
ListContainerFactory,
|
ListContainerFactory,
|
||||||
} from './list_container';
|
} from './list_container';
|
||||||
import { createSampleData } from './create_sample_data';
|
|
||||||
import { TODO_REF_EMBEDDABLE } from './todo/todo_ref_embeddable';
|
|
||||||
import {
|
|
||||||
TodoRefEmbeddableFactory,
|
|
||||||
TodoRefEmbeddableFactoryDefinition,
|
|
||||||
} from './todo/todo_ref_embeddable_factory';
|
|
||||||
import { createEditBookActionDefinition } from './book/edit_book_action';
|
|
||||||
import { BOOK_EMBEDDABLE } from './book/book_embeddable';
|
|
||||||
import {
|
|
||||||
BookEmbeddableFactory,
|
|
||||||
BookEmbeddableFactoryDefinition,
|
|
||||||
} from './book/book_embeddable_factory';
|
|
||||||
import { createAddBookToLibraryActionDefinition } from './book/add_book_to_library_action';
|
|
||||||
import { createUnlinkBookFromLibraryActionDefinition } from './book/unlink_book_from_library_action';
|
|
||||||
import {
|
import {
|
||||||
SIMPLE_EMBEDDABLE,
|
SIMPLE_EMBEDDABLE,
|
||||||
SimpleEmbeddableFactory,
|
SimpleEmbeddableFactory,
|
||||||
|
@ -67,17 +39,11 @@ export interface EmbeddableExamplesSetupDependencies {
|
||||||
|
|
||||||
export interface EmbeddableExamplesStartDependencies {
|
export interface EmbeddableExamplesStartDependencies {
|
||||||
embeddable: EmbeddableStart;
|
embeddable: EmbeddableStart;
|
||||||
savedObjectsClient: SavedObjectsClientContract;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ExampleEmbeddableFactories {
|
interface ExampleEmbeddableFactories {
|
||||||
getHelloWorldEmbeddableFactory: () => HelloWorldEmbeddableFactory;
|
getHelloWorldEmbeddableFactory: () => HelloWorldEmbeddableFactory;
|
||||||
getMultiTaskTodoEmbeddableFactory: () => MultiTaskTodoEmbeddableFactory;
|
|
||||||
getSearchableListContainerEmbeddableFactory: () => SearchableListContainerFactory;
|
|
||||||
getListContainerEmbeddableFactory: () => ListContainerFactory;
|
getListContainerEmbeddableFactory: () => ListContainerFactory;
|
||||||
getTodoEmbeddableFactory: () => TodoEmbeddableFactory;
|
|
||||||
getTodoRefEmbeddableFactory: () => TodoRefEmbeddableFactory;
|
|
||||||
getBookEmbeddableFactory: () => BookEmbeddableFactory;
|
|
||||||
getMigrationsEmbeddableFactory: () => SimpleEmbeddableFactory;
|
getMigrationsEmbeddableFactory: () => SimpleEmbeddableFactory;
|
||||||
getFilterDebuggerEmbeddableFactory: () => FilterDebuggerEmbeddableFactory;
|
getFilterDebuggerEmbeddableFactory: () => FilterDebuggerEmbeddableFactory;
|
||||||
}
|
}
|
||||||
|
@ -114,20 +80,6 @@ export class EmbeddableExamplesPlugin
|
||||||
new SimpleEmbeddableFactoryDefinition()
|
new SimpleEmbeddableFactoryDefinition()
|
||||||
);
|
);
|
||||||
|
|
||||||
this.exampleEmbeddableFactories.getMultiTaskTodoEmbeddableFactory =
|
|
||||||
deps.embeddable.registerEmbeddableFactory(
|
|
||||||
MULTI_TASK_TODO_EMBEDDABLE,
|
|
||||||
new MultiTaskTodoEmbeddableFactoryDefinition()
|
|
||||||
);
|
|
||||||
|
|
||||||
this.exampleEmbeddableFactories.getSearchableListContainerEmbeddableFactory =
|
|
||||||
deps.embeddable.registerEmbeddableFactory(
|
|
||||||
SEARCHABLE_LIST_CONTAINER,
|
|
||||||
new SearchableListContainerFactoryDefinition(async () => ({
|
|
||||||
embeddableServices: (await core.getStartServices())[1].embeddable,
|
|
||||||
}))
|
|
||||||
);
|
|
||||||
|
|
||||||
this.exampleEmbeddableFactories.getListContainerEmbeddableFactory =
|
this.exampleEmbeddableFactories.getListContainerEmbeddableFactory =
|
||||||
deps.embeddable.registerEmbeddableFactory(
|
deps.embeddable.registerEmbeddableFactory(
|
||||||
LIST_CONTAINER,
|
LIST_CONTAINER,
|
||||||
|
@ -136,54 +88,11 @@ export class EmbeddableExamplesPlugin
|
||||||
}))
|
}))
|
||||||
);
|
);
|
||||||
|
|
||||||
this.exampleEmbeddableFactories.getTodoEmbeddableFactory =
|
|
||||||
deps.embeddable.registerEmbeddableFactory(
|
|
||||||
TODO_EMBEDDABLE,
|
|
||||||
new TodoEmbeddableFactoryDefinition(async () => ({
|
|
||||||
openModal: (await core.getStartServices())[0].overlays.openModal,
|
|
||||||
}))
|
|
||||||
);
|
|
||||||
|
|
||||||
this.exampleEmbeddableFactories.getTodoRefEmbeddableFactory =
|
|
||||||
deps.embeddable.registerEmbeddableFactory(
|
|
||||||
TODO_REF_EMBEDDABLE,
|
|
||||||
new TodoRefEmbeddableFactoryDefinition(async () => ({
|
|
||||||
savedObjectsClient: (await core.getStartServices())[0].savedObjects.client,
|
|
||||||
getEmbeddableFactory: (await core.getStartServices())[1].embeddable.getEmbeddableFactory,
|
|
||||||
}))
|
|
||||||
);
|
|
||||||
this.exampleEmbeddableFactories.getBookEmbeddableFactory =
|
|
||||||
deps.embeddable.registerEmbeddableFactory(
|
|
||||||
BOOK_EMBEDDABLE,
|
|
||||||
new BookEmbeddableFactoryDefinition(async () => ({
|
|
||||||
getAttributeService: (await core.getStartServices())[1].embeddable.getAttributeService,
|
|
||||||
openModal: (await core.getStartServices())[0].overlays.openModal,
|
|
||||||
savedObjectsClient: (await core.getStartServices())[0].savedObjects.client,
|
|
||||||
overlays: (await core.getStartServices())[0].overlays,
|
|
||||||
}))
|
|
||||||
);
|
|
||||||
|
|
||||||
this.exampleEmbeddableFactories.getFilterDebuggerEmbeddableFactory =
|
this.exampleEmbeddableFactories.getFilterDebuggerEmbeddableFactory =
|
||||||
deps.embeddable.registerEmbeddableFactory(
|
deps.embeddable.registerEmbeddableFactory(
|
||||||
FILTER_DEBUGGER_EMBEDDABLE,
|
FILTER_DEBUGGER_EMBEDDABLE,
|
||||||
new FilterDebuggerEmbeddableFactoryDefinition()
|
new FilterDebuggerEmbeddableFactoryDefinition()
|
||||||
);
|
);
|
||||||
|
|
||||||
const editBookAction = createEditBookActionDefinition(async () => ({
|
|
||||||
getAttributeService: (await core.getStartServices())[1].embeddable.getAttributeService,
|
|
||||||
openModal: (await core.getStartServices())[0].overlays.openModal,
|
|
||||||
savedObjectsClient: (await core.getStartServices())[0].savedObjects.client,
|
|
||||||
}));
|
|
||||||
deps.uiActions.registerAction(editBookAction);
|
|
||||||
deps.uiActions.attachAction(CONTEXT_MENU_TRIGGER, editBookAction.id);
|
|
||||||
|
|
||||||
const addBookToLibraryAction = createAddBookToLibraryActionDefinition();
|
|
||||||
deps.uiActions.registerAction(addBookToLibraryAction);
|
|
||||||
deps.uiActions.attachAction(CONTEXT_MENU_TRIGGER, addBookToLibraryAction.id);
|
|
||||||
|
|
||||||
const unlinkBookFromLibraryAction = createUnlinkBookFromLibraryActionDefinition();
|
|
||||||
deps.uiActions.registerAction(unlinkBookFromLibraryAction);
|
|
||||||
deps.uiActions.attachAction(CONTEXT_MENU_TRIGGER, unlinkBookFromLibraryAction.id);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public start(
|
public start(
|
||||||
|
@ -191,7 +100,7 @@ export class EmbeddableExamplesPlugin
|
||||||
deps: EmbeddableExamplesStartDependencies
|
deps: EmbeddableExamplesStartDependencies
|
||||||
): EmbeddableExamplesStart {
|
): EmbeddableExamplesStart {
|
||||||
return {
|
return {
|
||||||
createSampleData: () => createSampleData(core.savedObjects.client),
|
createSampleData: async () => {},
|
||||||
factories: this.exampleEmbeddableFactories as ExampleEmbeddableFactories,
|
factories: this.exampleEmbeddableFactories as ExampleEmbeddableFactories,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
|
||||||
* or more contributor license agreements. Licensed under the Elastic License
|
|
||||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
|
||||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
|
||||||
* Side Public License, v 1.
|
|
||||||
*/
|
|
||||||
|
|
||||||
export { SearchableListContainer, SEARCHABLE_LIST_CONTAINER } from './searchable_list_container';
|
|
||||||
export type { SearchableListContainerFactory } from './searchable_list_container_factory';
|
|
||||||
export { SearchableListContainerFactoryDefinition } from './searchable_list_container_factory';
|
|
|
@ -1,67 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
|
||||||
* or more contributor license agreements. Licensed under the Elastic License
|
|
||||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
|
||||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
|
||||||
* Side Public License, v 1.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import React from 'react';
|
|
||||||
import ReactDOM from 'react-dom';
|
|
||||||
import {
|
|
||||||
Container,
|
|
||||||
ContainerInput,
|
|
||||||
EmbeddableStart,
|
|
||||||
EmbeddableInput,
|
|
||||||
} from '@kbn/embeddable-plugin/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, private embeddableServices: EmbeddableStart) {
|
|
||||||
super(input, { embeddableLoaded: {} }, embeddableServices.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,
|
|
||||||
viewMode: this.input.viewMode,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public render(node: HTMLElement) {
|
|
||||||
if (this.node) {
|
|
||||||
ReactDOM.unmountComponentAtNode(this.node);
|
|
||||||
}
|
|
||||||
this.node = node;
|
|
||||||
ReactDOM.render(
|
|
||||||
<SearchableListContainerComponent
|
|
||||||
embeddable={this}
|
|
||||||
embeddableServices={this.embeddableServices}
|
|
||||||
/>,
|
|
||||||
node
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public destroy() {
|
|
||||||
super.destroy();
|
|
||||||
if (this.node) {
|
|
||||||
ReactDOM.unmountComponentAtNode(this.node);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,222 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
|
||||||
* or more contributor license agreements. Licensed under the Elastic License
|
|
||||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
|
||||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
|
||||||
* Side Public License, v 1.
|
|
||||||
*/
|
|
||||||
|
|
||||||
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,
|
|
||||||
EmbeddableStart,
|
|
||||||
EmbeddablePanel,
|
|
||||||
openAddPanelFlyout,
|
|
||||||
} from '@kbn/embeddable-plugin/public';
|
|
||||||
import { SearchableListContainer, SearchableContainerInput } from './searchable_list_container';
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
embeddable: SearchableListContainer;
|
|
||||||
input: SearchableContainerInput;
|
|
||||||
output: ContainerOutput;
|
|
||||||
embeddableServices: EmbeddableStart;
|
|
||||||
}
|
|
||||||
|
|
||||||
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 checkMatching = () => {
|
|
||||||
const { input, embeddable } = this.props;
|
|
||||||
const checked: { [key: string]: boolean } = {};
|
|
||||||
Object.values(input.panels).map((panel) => {
|
|
||||||
const child = embeddable.getChild(panel.explicitInput.id);
|
|
||||||
const output = child.getOutput();
|
|
||||||
if (hasHasMatchOutput(output) && output.hasMatch) {
|
|
||||||
checked[panel.explicitInput.id] = true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
this.setState({ checked });
|
|
||||||
};
|
|
||||||
|
|
||||||
private toggleCheck = (isChecked: boolean, id: string) => {
|
|
||||||
this.setState((prevState) => ({ checked: { ...prevState.checked, [id]: isChecked } }));
|
|
||||||
};
|
|
||||||
|
|
||||||
public renderControls() {
|
|
||||||
const { input, embeddable } = this.props;
|
|
||||||
return (
|
|
||||||
<EuiFlexGroup gutterSize="s">
|
|
||||||
<EuiFlexItem grow={false}>
|
|
||||||
<EuiFormRow hasEmptyLabelSpace>
|
|
||||||
<EuiButton data-test-subj="deleteCheckedTodos" onClick={() => this.deleteChecked()}>
|
|
||||||
Delete checked
|
|
||||||
</EuiButton>
|
|
||||||
</EuiFormRow>
|
|
||||||
</EuiFlexItem>
|
|
||||||
<EuiFlexItem grow={false}>
|
|
||||||
<EuiFormRow hasEmptyLabelSpace>
|
|
||||||
<EuiButton
|
|
||||||
data-test-subj="checkMatchingTodos"
|
|
||||||
disabled={input.search === ''}
|
|
||||||
onClick={() => this.checkMatching()}
|
|
||||||
>
|
|
||||||
Check matching
|
|
||||||
</EuiButton>
|
|
||||||
</EuiFormRow>
|
|
||||||
</EuiFlexItem>
|
|
||||||
<EuiFlexItem>
|
|
||||||
<EuiFormRow label="Filter">
|
|
||||||
<EuiFieldText
|
|
||||||
data-test-subj="filterTodos"
|
|
||||||
value={this.props.input.search || ''}
|
|
||||||
onChange={(ev) => this.updateSearch(ev.target.value)}
|
|
||||||
/>
|
|
||||||
</EuiFormRow>
|
|
||||||
</EuiFlexItem>
|
|
||||||
<EuiFlexItem grow={false}>
|
|
||||||
<EuiFormRow hasEmptyLabelSpace>
|
|
||||||
<EuiButton
|
|
||||||
data-test-subj="addPanelToListContainer"
|
|
||||||
disabled={input.search === ''}
|
|
||||||
onClick={() => openAddPanelFlyout({ container: embeddable })}
|
|
||||||
>
|
|
||||||
Add panel
|
|
||||||
</EuiButton>
|
|
||||||
</EuiFormRow>
|
|
||||||
</EuiFlexItem>
|
|
||||||
<EuiFlexItem />
|
|
||||||
</EuiFlexGroup>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public render() {
|
|
||||||
const { embeddable } = this.props;
|
|
||||||
return (
|
|
||||||
<EuiFlexGroup gutterSize="none">
|
|
||||||
<EuiFlexItem>
|
|
||||||
<h2 data-test-subj="searchableListContainerTitle">{embeddable.getTitle()}</h2>
|
|
||||||
<EuiSpacer size="l" />
|
|
||||||
{this.renderControls()}
|
|
||||||
<EuiSpacer size="l" />
|
|
||||||
{this.renderList()}
|
|
||||||
</EuiFlexItem>
|
|
||||||
</EuiFlexGroup>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private renderList() {
|
|
||||||
const { input, embeddable } = this.props;
|
|
||||||
let id = 0;
|
|
||||||
const list = Object.values(input.panels).map((panel) => {
|
|
||||||
const childEmbeddable = embeddable.getChild(panel.explicitInput.id);
|
|
||||||
id++;
|
|
||||||
return childEmbeddable ? (
|
|
||||||
<EuiPanel key={childEmbeddable.id}>
|
|
||||||
<EuiFlexGroup gutterSize="none">
|
|
||||||
<EuiFlexItem grow={false}>
|
|
||||||
<EuiCheckbox
|
|
||||||
data-test-subj={`todoCheckBox-${childEmbeddable.id}`}
|
|
||||||
disabled={!childEmbeddable}
|
|
||||||
id={childEmbeddable ? childEmbeddable.id : ''}
|
|
||||||
checked={this.state.checked[childEmbeddable.id]}
|
|
||||||
onChange={(e) => this.toggleCheck(e.target.checked, childEmbeddable.id)}
|
|
||||||
/>
|
|
||||||
</EuiFlexItem>
|
|
||||||
<EuiFlexItem>
|
|
||||||
<EmbeddablePanel embeddable={childEmbeddable} />
|
|
||||||
</EuiFlexItem>
|
|
||||||
</EuiFlexGroup>
|
|
||||||
</EuiPanel>
|
|
||||||
) : (
|
|
||||||
<EuiLoadingSpinner size="l" key={id} />
|
|
||||||
);
|
|
||||||
});
|
|
||||||
return list;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const SearchableListContainerComponent = withEmbeddableSubscription<
|
|
||||||
SearchableContainerInput,
|
|
||||||
ContainerOutput,
|
|
||||||
SearchableListContainer,
|
|
||||||
{ embeddableServices: EmbeddableStart }
|
|
||||||
>(SearchableListContainerComponentInner);
|
|
|
@ -1,52 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
|
||||||
* or more contributor license agreements. Licensed under the Elastic License
|
|
||||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
|
||||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
|
||||||
* Side Public License, v 1.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { i18n } from '@kbn/i18n';
|
|
||||||
import {
|
|
||||||
ContainerOutput,
|
|
||||||
EmbeddableFactory,
|
|
||||||
EmbeddableFactoryDefinition,
|
|
||||||
EmbeddableStart,
|
|
||||||
} from '@kbn/embeddable-plugin/public';
|
|
||||||
import {
|
|
||||||
SEARCHABLE_LIST_CONTAINER,
|
|
||||||
SearchableListContainer,
|
|
||||||
SearchableContainerInput,
|
|
||||||
} from './searchable_list_container';
|
|
||||||
|
|
||||||
interface StartServices {
|
|
||||||
embeddableServices: EmbeddableStart;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type SearchableListContainerFactory = EmbeddableFactory<
|
|
||||||
SearchableContainerInput,
|
|
||||||
ContainerOutput
|
|
||||||
>;
|
|
||||||
export class SearchableListContainerFactoryDefinition
|
|
||||||
implements EmbeddableFactoryDefinition<SearchableContainerInput, ContainerOutput>
|
|
||||||
{
|
|
||||||
public readonly type = SEARCHABLE_LIST_CONTAINER;
|
|
||||||
public readonly isContainerType = true;
|
|
||||||
|
|
||||||
constructor(private getStartServices: () => Promise<StartServices>) {}
|
|
||||||
|
|
||||||
public async isEditable() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public create = async (initialInput: SearchableContainerInput) => {
|
|
||||||
const { embeddableServices } = await this.getStartServices();
|
|
||||||
return new SearchableListContainer(initialInput, embeddableServices);
|
|
||||||
};
|
|
||||||
|
|
||||||
public getDisplayName() {
|
|
||||||
return i18n.translate('embeddableExamples.searchableListContainer.displayName', {
|
|
||||||
defaultMessage: 'Searchable list container',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,43 +0,0 @@
|
||||||
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.
|
|
|
@ -1,10 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
|
||||||
* or more contributor license agreements. Licensed under the Elastic License
|
|
||||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
|
||||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
|
||||||
* Side Public License, v 1.
|
|
||||||
*/
|
|
||||||
|
|
||||||
export * from './todo_embeddable';
|
|
||||||
export * from './todo_embeddable_factory';
|
|
|
@ -1,65 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
|
||||||
* or more contributor license agreements. Licensed under the Elastic License
|
|
||||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
|
||||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
|
||||||
* Side Public License, v 1.
|
|
||||||
*/
|
|
||||||
|
|
||||||
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 '@kbn/embeddable-plugin/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 gutterSize="none" data-render-complete="true">
|
|
||||||
<EuiFlexItem grow={false}>
|
|
||||||
{icon ? <EuiIcon type={icon} size="l" /> : <EuiAvatar name={title || task} size="l" />}
|
|
||||||
</EuiFlexItem>
|
|
||||||
<EuiFlexItem>
|
|
||||||
<EuiFlexGrid columns={1} gutterSize="none">
|
|
||||||
<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<
|
|
||||||
TodoInput,
|
|
||||||
EmbeddableOutput,
|
|
||||||
TodoEmbeddable
|
|
||||||
>(TodoEmbeddableComponentInner);
|
|
|
@ -1,78 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
|
||||||
* or more contributor license agreements. Licensed under the Elastic License
|
|
||||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
|
||||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
|
||||||
* Side Public License, v 1.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import React from 'react';
|
|
||||||
import ReactDOM from 'react-dom';
|
|
||||||
import { Subscription } from 'rxjs';
|
|
||||||
import {
|
|
||||||
Embeddable,
|
|
||||||
EmbeddableInput,
|
|
||||||
IContainer,
|
|
||||||
EmbeddableOutput,
|
|
||||||
} from '@kbn/embeddable-plugin/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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,89 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
|
||||||
* or more contributor license agreements. Licensed under the Elastic License
|
|
||||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
|
||||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
|
||||||
* Side Public License, v 1.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import React, { useState } from 'react';
|
|
||||||
import { EuiModalBody } from '@elastic/eui';
|
|
||||||
import { i18n } from '@kbn/i18n';
|
|
||||||
import { OverlayStart } from '@kbn/core/public';
|
|
||||||
import { EuiFieldText } from '@elastic/eui';
|
|
||||||
import { EuiButton } from '@elastic/eui';
|
|
||||||
import { toMountPoint } from '@kbn/kibana-react-plugin/public';
|
|
||||||
import {
|
|
||||||
IContainer,
|
|
||||||
EmbeddableFactoryDefinition,
|
|
||||||
EmbeddableFactory,
|
|
||||||
} from '@kbn/embeddable-plugin/public';
|
|
||||||
import { TodoEmbeddable, TODO_EMBEDDABLE, TodoInput, TodoOutput } from './todo_embeddable';
|
|
||||||
|
|
||||||
function TaskInput({ onSave }: { onSave: (task: string) => void }) {
|
|
||||||
const [task, setTask] = useState('');
|
|
||||||
return (
|
|
||||||
<EuiModalBody>
|
|
||||||
<EuiFieldText
|
|
||||||
data-test-subj="taskInputField"
|
|
||||||
value={task}
|
|
||||||
placeholder="Enter task here"
|
|
||||||
onChange={(e) => setTask(e.target.value)}
|
|
||||||
/>
|
|
||||||
<EuiButton data-test-subj="createTodoEmbeddable" onClick={() => onSave(task)}>
|
|
||||||
Save
|
|
||||||
</EuiButton>
|
|
||||||
</EuiModalBody>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
interface StartServices {
|
|
||||||
openModal: OverlayStart['openModal'];
|
|
||||||
}
|
|
||||||
|
|
||||||
export type TodoEmbeddableFactory = EmbeddableFactory<TodoInput, TodoOutput, TodoEmbeddable>;
|
|
||||||
|
|
||||||
export class TodoEmbeddableFactoryDefinition
|
|
||||||
implements EmbeddableFactoryDefinition<TodoInput, TodoOutput, TodoEmbeddable>
|
|
||||||
{
|
|
||||||
public readonly type = TODO_EMBEDDABLE;
|
|
||||||
|
|
||||||
constructor(private getStartServices: () => Promise<StartServices>) {}
|
|
||||||
|
|
||||||
public async isEditable() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async create(initialInput: TodoInput, parent?: IContainer) {
|
|
||||||
return new TodoEmbeddable(initialInput, parent);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This function is used when dynamically creating a new embeddable to add to a
|
|
||||||
* container. Some input may be inherited from the container, but not all. This can be
|
|
||||||
* used to collect specific embeddable input that the container will not provide, like
|
|
||||||
* in this case, the task string.
|
|
||||||
*/
|
|
||||||
public getExplicitInput = async () => {
|
|
||||||
const { openModal } = await this.getStartServices();
|
|
||||||
return new Promise<{ task: string }>((resolve) => {
|
|
||||||
const onSave = (task: string) => resolve({ task });
|
|
||||||
const overlay = openModal(
|
|
||||||
toMountPoint(
|
|
||||||
<TaskInput
|
|
||||||
onSave={(task: string) => {
|
|
||||||
onSave(task);
|
|
||||||
overlay.close();
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
public getDisplayName() {
|
|
||||||
return i18n.translate('embeddableExamples.todo.displayName', {
|
|
||||||
defaultMessage: 'Todo item',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,76 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
|
||||||
* or more contributor license agreements. Licensed under the Elastic License
|
|
||||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
|
||||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
|
||||||
* Side Public License, v 1.
|
|
||||||
*/
|
|
||||||
|
|
||||||
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 '@kbn/embeddable-plugin/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 data-render-complete="true">
|
|
||||||
<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);
|
|
|
@ -1,143 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
|
||||||
* or more contributor license agreements. Licensed under the Elastic License
|
|
||||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
|
||||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
|
||||||
* Side Public License, v 1.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import React from 'react';
|
|
||||||
import ReactDOM from 'react-dom';
|
|
||||||
import { Subscription } from 'rxjs';
|
|
||||||
import { SavedObjectsClientContract } from '@kbn/core/public';
|
|
||||||
import {
|
|
||||||
Embeddable,
|
|
||||||
IContainer,
|
|
||||||
EmbeddableOutput,
|
|
||||||
SavedObjectEmbeddableInput,
|
|
||||||
} from '@kbn/embeddable-plugin/public';
|
|
||||||
import { TodoSavedObjectAttributes } from '../../common';
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,82 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
|
||||||
* or more contributor license agreements. Licensed under the Elastic License
|
|
||||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
|
||||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
|
||||||
* Side Public License, v 1.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { i18n } from '@kbn/i18n';
|
|
||||||
import { SavedObjectsClientContract } from '@kbn/core/public';
|
|
||||||
import {
|
|
||||||
IContainer,
|
|
||||||
EmbeddableStart,
|
|
||||||
ErrorEmbeddable,
|
|
||||||
EmbeddableFactoryDefinition,
|
|
||||||
EmbeddableFactory,
|
|
||||||
} from '@kbn/embeddable-plugin/public';
|
|
||||||
import { TodoSavedObjectAttributes } from '../../common';
|
|
||||||
import {
|
|
||||||
TodoRefEmbeddable,
|
|
||||||
TODO_REF_EMBEDDABLE,
|
|
||||||
TodoRefInput,
|
|
||||||
TodoRefOutput,
|
|
||||||
} from './todo_ref_embeddable';
|
|
||||||
|
|
||||||
interface StartServices {
|
|
||||||
getEmbeddableFactory: EmbeddableStart['getEmbeddableFactory'];
|
|
||||||
savedObjectsClient: SavedObjectsClientContract;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type TodoRefEmbeddableFactory = EmbeddableFactory<
|
|
||||||
TodoRefInput,
|
|
||||||
TodoRefOutput,
|
|
||||||
TodoRefEmbeddable,
|
|
||||||
TodoSavedObjectAttributes
|
|
||||||
>;
|
|
||||||
|
|
||||||
export class TodoRefEmbeddableFactoryDefinition
|
|
||||||
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)',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,29 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
|
||||||
* or more contributor license agreements. Licensed under the Elastic License
|
|
||||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
|
||||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
|
||||||
* Side Public License, v 1.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { SavedObjectsType } from '@kbn/core/server';
|
|
||||||
|
|
||||||
export const bookSavedObject: SavedObjectsType = {
|
|
||||||
name: 'book',
|
|
||||||
hidden: false,
|
|
||||||
namespaceType: 'agnostic',
|
|
||||||
mappings: {
|
|
||||||
properties: {
|
|
||||||
title: {
|
|
||||||
type: 'keyword',
|
|
||||||
},
|
|
||||||
author: {
|
|
||||||
type: 'keyword',
|
|
||||||
},
|
|
||||||
readIt: {
|
|
||||||
type: 'boolean',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
migrations: {},
|
|
||||||
};
|
|
|
@ -1,25 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
|
||||||
* or more contributor license agreements. Licensed under the Elastic License
|
|
||||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
|
||||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
|
||||||
* Side Public License, v 1.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { mergeWith } from 'lodash';
|
|
||||||
import type { SerializableRecord } from '@kbn/utility-types';
|
|
||||||
import { MigrateFunctionsObject, MigrateFunction } from '@kbn/kibana-utils-plugin/common';
|
|
||||||
|
|
||||||
export const mergeMigrationFunctionMaps = (
|
|
||||||
obj1: MigrateFunctionsObject,
|
|
||||||
obj2: MigrateFunctionsObject
|
|
||||||
) => {
|
|
||||||
const customizer = (objValue: MigrateFunction, srcValue: MigrateFunction) => {
|
|
||||||
if (!srcValue || !objValue) {
|
|
||||||
return srcValue || objValue;
|
|
||||||
}
|
|
||||||
return (state: SerializableRecord) => objValue(srcValue(state));
|
|
||||||
};
|
|
||||||
|
|
||||||
return mergeWith({ ...obj1 }, obj2, customizer);
|
|
||||||
};
|
|
|
@ -8,9 +8,6 @@
|
||||||
|
|
||||||
import { Plugin, CoreSetup, CoreStart } from '@kbn/core/server';
|
import { Plugin, CoreSetup, CoreStart } from '@kbn/core/server';
|
||||||
import { EmbeddableSetup } from '@kbn/embeddable-plugin/server';
|
import { EmbeddableSetup } from '@kbn/embeddable-plugin/server';
|
||||||
import { todoSavedObject } from './todo_saved_object';
|
|
||||||
import { bookSavedObject } from './book_saved_object';
|
|
||||||
import { searchableListSavedObject } from './searchable_list_saved_object';
|
|
||||||
|
|
||||||
export interface EmbeddableExamplesSetupDependencies {
|
export interface EmbeddableExamplesSetupDependencies {
|
||||||
embeddable: EmbeddableSetup;
|
embeddable: EmbeddableSetup;
|
||||||
|
@ -19,11 +16,7 @@ export interface EmbeddableExamplesSetupDependencies {
|
||||||
export class EmbeddableExamplesPlugin
|
export class EmbeddableExamplesPlugin
|
||||||
implements Plugin<void, void, EmbeddableExamplesSetupDependencies>
|
implements Plugin<void, void, EmbeddableExamplesSetupDependencies>
|
||||||
{
|
{
|
||||||
public setup(core: CoreSetup, { embeddable }: EmbeddableExamplesSetupDependencies) {
|
public setup(core: CoreSetup, { embeddable }: EmbeddableExamplesSetupDependencies) {}
|
||||||
core.savedObjects.registerType(todoSavedObject);
|
|
||||||
core.savedObjects.registerType(bookSavedObject);
|
|
||||||
core.savedObjects.registerType(searchableListSavedObject(embeddable));
|
|
||||||
}
|
|
||||||
|
|
||||||
public start(core: CoreStart) {}
|
public start(core: CoreStart) {}
|
||||||
|
|
||||||
|
|
|
@ -1,54 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
|
||||||
* or more contributor license agreements. Licensed under the Elastic License
|
|
||||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
|
||||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
|
||||||
* Side Public License, v 1.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { mapValues } from 'lodash';
|
|
||||||
import { SavedObjectsType, SavedObjectUnsanitizedDoc } from '@kbn/core/server';
|
|
||||||
import { EmbeddableSetup } from '@kbn/embeddable-plugin/server';
|
|
||||||
// NOTE: this should rather be imported from 'plugins/kibana_utils/server' but examples at the moment don't
|
|
||||||
// allow static imports from plugins so this code was duplicated
|
|
||||||
import { mergeMigrationFunctionMaps } from './merge_migration_function_maps';
|
|
||||||
|
|
||||||
export const searchableListSavedObject = (embeddable: EmbeddableSetup) => {
|
|
||||||
const searchableListSO: SavedObjectsType = {
|
|
||||||
name: 'searchableList',
|
|
||||||
hidden: false,
|
|
||||||
namespaceType: 'single',
|
|
||||||
management: {
|
|
||||||
icon: 'visualizeApp',
|
|
||||||
defaultSearchField: 'title',
|
|
||||||
importableAndExportable: true,
|
|
||||||
getTitle(obj: any) {
|
|
||||||
return obj.attributes.title;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
mappings: {
|
|
||||||
properties: {
|
|
||||||
title: { type: 'text' },
|
|
||||||
version: { type: 'integer' },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
migrations: () => {
|
|
||||||
// there are no migrations defined for the saved object at the moment, possibly they would be added in the future
|
|
||||||
const searchableListSavedObjectMigrations = {};
|
|
||||||
|
|
||||||
// we don't know if embeddables have any migrations defined so we need to fetch them and map the received functions so we pass
|
|
||||||
// them the correct input and that we correctly map the response
|
|
||||||
const embeddableMigrations = mapValues(embeddable.getAllMigrations(), (migrate) => {
|
|
||||||
return (state: SavedObjectUnsanitizedDoc) => ({
|
|
||||||
...state,
|
|
||||||
attributes: migrate(state.attributes),
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// we merge our and embeddable migrations and return
|
|
||||||
return mergeMigrationFunctionMaps(searchableListSavedObjectMigrations, embeddableMigrations);
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
return searchableListSO;
|
|
||||||
};
|
|
|
@ -1,29 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
|
||||||
* or more contributor license agreements. Licensed under the Elastic License
|
|
||||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
|
||||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
|
||||||
* Side Public License, v 1.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { SavedObjectsType } from '@kbn/core/server';
|
|
||||||
|
|
||||||
export const todoSavedObject: SavedObjectsType = {
|
|
||||||
name: 'todo',
|
|
||||||
hidden: false,
|
|
||||||
namespaceType: 'agnostic',
|
|
||||||
mappings: {
|
|
||||||
properties: {
|
|
||||||
title: {
|
|
||||||
type: 'keyword',
|
|
||||||
},
|
|
||||||
task: {
|
|
||||||
type: 'text',
|
|
||||||
},
|
|
||||||
icon: {
|
|
||||||
type: 'keyword',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
migrations: {},
|
|
||||||
};
|
|
|
@ -17,13 +17,9 @@
|
||||||
"kbn_references": [
|
"kbn_references": [
|
||||||
"@kbn/core",
|
"@kbn/core",
|
||||||
"@kbn/kibana-utils-plugin",
|
"@kbn/kibana-utils-plugin",
|
||||||
"@kbn/kibana-react-plugin",
|
|
||||||
"@kbn/ui-actions-plugin",
|
"@kbn/ui-actions-plugin",
|
||||||
"@kbn/embeddable-plugin",
|
"@kbn/embeddable-plugin",
|
||||||
"@kbn/dashboard-plugin",
|
|
||||||
"@kbn/saved-objects-plugin",
|
|
||||||
"@kbn/i18n",
|
"@kbn/i18n",
|
||||||
"@kbn/utility-types",
|
|
||||||
"@kbn/es-query",
|
"@kbn/es-query",
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,8 +14,7 @@
|
||||||
"embeddableExamples",
|
"embeddableExamples",
|
||||||
"developerExamples",
|
"developerExamples",
|
||||||
"dashboard",
|
"dashboard",
|
||||||
"kibanaReact",
|
"kibanaReact"
|
||||||
"savedObjects"
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,20 +11,12 @@ import ReactDOM from 'react-dom';
|
||||||
import { BrowserRouter as Router, withRouter, RouteComponentProps } from 'react-router-dom';
|
import { BrowserRouter as Router, withRouter, RouteComponentProps } from 'react-router-dom';
|
||||||
import { Route } from '@kbn/shared-ux-router';
|
import { Route } from '@kbn/shared-ux-router';
|
||||||
import { EuiPageTemplate, EuiSideNav } from '@elastic/eui';
|
import { EuiPageTemplate, EuiSideNav } from '@elastic/eui';
|
||||||
|
|
||||||
import { EmbeddableStart } from '@kbn/embeddable-plugin/public';
|
import { EmbeddableStart } from '@kbn/embeddable-plugin/public';
|
||||||
import { UiActionsStart } from '@kbn/ui-actions-plugin/public';
|
import { UiActionsStart } from '@kbn/ui-actions-plugin/public';
|
||||||
import { Start as InspectorStartContract } from '@kbn/inspector-plugin/public';
|
import { Start as InspectorStartContract } from '@kbn/inspector-plugin/public';
|
||||||
import {
|
import { AppMountParameters, CoreStart, IUiSettingsClient, OverlayStart } from '@kbn/core/public';
|
||||||
AppMountParameters,
|
|
||||||
CoreStart,
|
|
||||||
SavedObjectsStart,
|
|
||||||
IUiSettingsClient,
|
|
||||||
OverlayStart,
|
|
||||||
} from '@kbn/core/public';
|
|
||||||
import { EmbeddableExamplesStart } from '@kbn/embeddable-examples-plugin/public/plugin';
|
import { EmbeddableExamplesStart } from '@kbn/embeddable-examples-plugin/public/plugin';
|
||||||
import { HelloWorldEmbeddableExample } from './hello_world_embeddable_example';
|
import { HelloWorldEmbeddableExample } from './hello_world_embeddable_example';
|
||||||
import { TodoEmbeddableExample } from './todo_embeddable_example';
|
|
||||||
import { ListContainerExample } from './list_container_example';
|
import { ListContainerExample } from './list_container_example';
|
||||||
import { EmbeddablePanelExample } from './embeddable_panel_example';
|
import { EmbeddablePanelExample } from './embeddable_panel_example';
|
||||||
|
|
||||||
|
@ -68,7 +60,6 @@ interface Props {
|
||||||
overlays: OverlayStart;
|
overlays: OverlayStart;
|
||||||
notifications: CoreStart['notifications'];
|
notifications: CoreStart['notifications'];
|
||||||
inspector: InspectorStartContract;
|
inspector: InspectorStartContract;
|
||||||
savedObject: SavedObjectsStart;
|
|
||||||
uiSettingsClient: IUiSettingsClient;
|
uiSettingsClient: IUiSettingsClient;
|
||||||
embeddableExamples: EmbeddableExamplesStart;
|
embeddableExamples: EmbeddableExamplesStart;
|
||||||
}
|
}
|
||||||
|
@ -89,15 +80,6 @@ const EmbeddableExplorerApp = ({
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
|
||||||
title: 'Update embeddable state',
|
|
||||||
id: 'todoEmbeddableSection',
|
|
||||||
component: (
|
|
||||||
<TodoEmbeddableExample
|
|
||||||
todoEmbeddableFactory={embeddableExamples.factories.getTodoEmbeddableFactory()}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
title: 'Groups of embeddables',
|
title: 'Groups of embeddables',
|
||||||
id: 'listContainerSection',
|
id: 'listContainerSection',
|
||||||
|
@ -112,7 +94,7 @@ const EmbeddableExplorerApp = ({
|
||||||
id: 'embeddablePanelExample',
|
id: 'embeddablePanelExample',
|
||||||
component: (
|
component: (
|
||||||
<EmbeddablePanelExample
|
<EmbeddablePanelExample
|
||||||
searchListContainerFactory={embeddableExamples.factories.getSearchableListContainerEmbeddableFactory()}
|
helloWorldFactory={embeddableExamples.factories.getHelloWorldEmbeddableFactory()}
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
|
|
@ -10,80 +10,13 @@ import React, { useState, useEffect, useRef } from 'react';
|
||||||
import { EuiPanel, EuiText, EuiPageTemplate } from '@elastic/eui';
|
import { EuiPanel, EuiText, EuiPageTemplate } from '@elastic/eui';
|
||||||
import { EuiSpacer } from '@elastic/eui';
|
import { EuiSpacer } from '@elastic/eui';
|
||||||
import { IEmbeddable, EmbeddablePanel } from '@kbn/embeddable-plugin/public';
|
import { IEmbeddable, EmbeddablePanel } from '@kbn/embeddable-plugin/public';
|
||||||
import {
|
import { HelloWorldEmbeddableFactory } from '@kbn/embeddable-examples-plugin/public';
|
||||||
HELLO_WORLD_EMBEDDABLE,
|
|
||||||
TODO_EMBEDDABLE,
|
|
||||||
BOOK_EMBEDDABLE,
|
|
||||||
MULTI_TASK_TODO_EMBEDDABLE,
|
|
||||||
SearchableListContainerFactory,
|
|
||||||
} from '@kbn/embeddable-examples-plugin/public';
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
searchListContainerFactory: SearchableListContainerFactory;
|
helloWorldFactory: HelloWorldEmbeddableFactory;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function EmbeddablePanelExample({ searchListContainerFactory }: Props) {
|
export function EmbeddablePanelExample({ helloWorldFactory }: Props) {
|
||||||
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 Wednesdays!',
|
|
||||||
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 encyclopedia'],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
'4': {
|
|
||||||
type: BOOK_EMBEDDABLE,
|
|
||||||
explicitInput: {
|
|
||||||
id: '4',
|
|
||||||
savedObjectId: 'sample-book-saved-object',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
'5': {
|
|
||||||
type: BOOK_EMBEDDABLE,
|
|
||||||
explicitInput: {
|
|
||||||
id: '5',
|
|
||||||
attributes: {
|
|
||||||
title: 'The Sympathizer',
|
|
||||||
author: 'Viet Thanh Nguyen',
|
|
||||||
readIt: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
'6': {
|
|
||||||
type: BOOK_EMBEDDABLE,
|
|
||||||
explicitInput: {
|
|
||||||
id: '6',
|
|
||||||
attributes: {
|
|
||||||
title: 'The Hobbit',
|
|
||||||
author: 'J.R.R. Tolkien',
|
|
||||||
readIt: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const [embeddable, setEmbeddable] = useState<IEmbeddable | undefined>(undefined);
|
const [embeddable, setEmbeddable] = useState<IEmbeddable | undefined>(undefined);
|
||||||
|
|
||||||
const ref = useRef(false);
|
const ref = useRef(false);
|
||||||
|
@ -91,7 +24,7 @@ export function EmbeddablePanelExample({ searchListContainerFactory }: Props) {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
ref.current = true;
|
ref.current = true;
|
||||||
if (!embeddable) {
|
if (!embeddable) {
|
||||||
const promise = searchListContainerFactory.create(searchableInput);
|
const promise = helloWorldFactory.create({ id: '1', title: 'Hello World!' });
|
||||||
if (promise) {
|
if (promise) {
|
||||||
promise.then((e) => {
|
promise.then((e) => {
|
||||||
if (ref.current) {
|
if (ref.current) {
|
||||||
|
|
|
@ -11,10 +11,8 @@ import { EuiPanel, EuiSpacer, EuiText, EuiPageTemplate, EuiCodeBlock } from '@el
|
||||||
import { EmbeddableRenderer, ViewMode } from '@kbn/embeddable-plugin/public';
|
import { EmbeddableRenderer, ViewMode } from '@kbn/embeddable-plugin/public';
|
||||||
import {
|
import {
|
||||||
HELLO_WORLD_EMBEDDABLE,
|
HELLO_WORLD_EMBEDDABLE,
|
||||||
TODO_EMBEDDABLE,
|
|
||||||
ListContainerFactory,
|
ListContainerFactory,
|
||||||
} from '@kbn/embeddable-examples-plugin/public';
|
} from '@kbn/embeddable-examples-plugin/public';
|
||||||
import { TodoInput } from '@kbn/embeddable-examples-plugin/public/todo';
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
listContainerEmbeddableFactory: ListContainerFactory;
|
listContainerEmbeddableFactory: ListContainerFactory;
|
||||||
|
@ -33,7 +31,7 @@ export function ListContainerExample({ listContainerEmbeddableFactory }: Props)
|
||||||
factory={listContainerEmbeddableFactory}
|
factory={listContainerEmbeddableFactory}
|
||||||
input={{
|
input={{
|
||||||
id: 'hello',
|
id: 'hello',
|
||||||
title: 'Todo list',
|
title: 'Hello world list',
|
||||||
viewMode: ViewMode.VIEW,
|
viewMode: ViewMode.VIEW,
|
||||||
panels: {
|
panels: {
|
||||||
'1': {
|
'1': {
|
||||||
|
@ -43,21 +41,10 @@ export function ListContainerExample({ listContainerEmbeddableFactory }: Props)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
'2': {
|
'2': {
|
||||||
type: TODO_EMBEDDABLE,
|
type: HELLO_WORLD_EMBEDDABLE,
|
||||||
explicitInput: {
|
explicitInput: {
|
||||||
id: '2',
|
id: '2',
|
||||||
task: 'Goes out on Wednesdays!',
|
},
|
||||||
icon: 'broom',
|
|
||||||
title: 'Take out the trash',
|
|
||||||
} as TodoInput,
|
|
||||||
},
|
|
||||||
'3': {
|
|
||||||
type: TODO_EMBEDDABLE,
|
|
||||||
explicitInput: {
|
|
||||||
id: '3',
|
|
||||||
icon: 'broom',
|
|
||||||
title: 'Vaccum the floor',
|
|
||||||
} as TodoInput,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
|
@ -77,24 +64,13 @@ export function ListContainerExample({ listContainerEmbeddableFactory }: Props)
|
||||||
explicitInput: {
|
explicitInput: {
|
||||||
id: '1',
|
id: '1',
|
||||||
},
|
},
|
||||||
},
|
|
||||||
'2': {
|
'2': {
|
||||||
type: TODO_EMBEDDABLE,
|
type: HELLO_WORLD_EMBEDDABLE,
|
||||||
explicitInput: {
|
explicitInput: {
|
||||||
id: '2',
|
id: '2',
|
||||||
task: 'Goes out on Wednesdays!',
|
},
|
||||||
icon: 'broom',
|
},
|
||||||
title: 'Take out the trash',
|
}
|
||||||
} as TodoInput,
|
|
||||||
},
|
|
||||||
'3': {
|
|
||||||
type: TODO_EMBEDDABLE,
|
|
||||||
explicitInput: {
|
|
||||||
id: '3',
|
|
||||||
icon: 'broom',
|
|
||||||
title: 'Vaccum the floor',
|
|
||||||
} as TodoInput,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
/>`}
|
/>`}
|
||||||
|
|
|
@ -48,7 +48,6 @@ export class EmbeddableExplorerPlugin implements Plugin<void, void, {}, StartDep
|
||||||
uiActionsApi: depsStart.uiActions,
|
uiActionsApi: depsStart.uiActions,
|
||||||
basename: params.appBasePath,
|
basename: params.appBasePath,
|
||||||
uiSettingsClient: coreStart.uiSettings,
|
uiSettingsClient: coreStart.uiSettings,
|
||||||
savedObject: coreStart.savedObjects,
|
|
||||||
overlays: coreStart.overlays,
|
overlays: coreStart.overlays,
|
||||||
navigateToApp: coreStart.application.navigateToApp,
|
navigateToApp: coreStart.application.navigateToApp,
|
||||||
embeddableExamples: depsStart.embeddableExamples,
|
embeddableExamples: depsStart.embeddableExamples,
|
||||||
|
|
|
@ -1,120 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
|
||||||
* or more contributor license agreements. Licensed under the Elastic License
|
|
||||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
|
||||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
|
||||||
* Side Public License, v 1.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import React from 'react';
|
|
||||||
import {
|
|
||||||
EuiCodeBlock,
|
|
||||||
EuiFieldText,
|
|
||||||
EuiForm,
|
|
||||||
EuiFormRow,
|
|
||||||
EuiPanel,
|
|
||||||
EuiText,
|
|
||||||
EuiTextArea,
|
|
||||||
EuiPageTemplate,
|
|
||||||
EuiSpacer,
|
|
||||||
EuiSelect,
|
|
||||||
} from '@elastic/eui';
|
|
||||||
import { TodoEmbeddableFactory } from '@kbn/embeddable-examples-plugin/public';
|
|
||||||
import { EmbeddableRenderer } from '@kbn/embeddable-plugin/public';
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
todoEmbeddableFactory: TodoEmbeddableFactory;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface State {
|
|
||||||
task: string;
|
|
||||||
title: string;
|
|
||||||
icon: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const ICON_OPTIONS = [
|
|
||||||
{ value: 'beaker', text: 'beaker' },
|
|
||||||
{ value: 'bell', text: 'bell' },
|
|
||||||
{ value: 'bolt', text: 'bolt' },
|
|
||||||
{ value: 'broom', text: 'broom' },
|
|
||||||
{ value: 'bug', text: 'bug' },
|
|
||||||
{ value: 'bullseye', text: 'bullseye' },
|
|
||||||
];
|
|
||||||
|
|
||||||
export class TodoEmbeddableExample extends React.Component<Props, State> {
|
|
||||||
constructor(props: Props) {
|
|
||||||
super(props);
|
|
||||||
|
|
||||||
this.state = {
|
|
||||||
icon: 'broom',
|
|
||||||
task: 'Take out the trash',
|
|
||||||
title: 'Trash',
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public render() {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<EuiPageTemplate.Header pageTitle="Update embeddable state" />
|
|
||||||
<EuiPageTemplate.Section grow={false}>
|
|
||||||
<>
|
|
||||||
<EuiText>
|
|
||||||
Use <strong>input</strong> prop to update embeddable state.
|
|
||||||
</EuiText>
|
|
||||||
<EuiSpacer />
|
|
||||||
<EuiForm>
|
|
||||||
<EuiFormRow label="Title">
|
|
||||||
<EuiFieldText
|
|
||||||
data-test-subj="titleTodo"
|
|
||||||
value={this.state.title}
|
|
||||||
onChange={(ev) => this.setState({ title: ev.target.value })}
|
|
||||||
/>
|
|
||||||
</EuiFormRow>
|
|
||||||
<EuiFormRow label="Icon">
|
|
||||||
<EuiSelect
|
|
||||||
data-test-subj="iconTodo"
|
|
||||||
value={this.state.icon}
|
|
||||||
options={ICON_OPTIONS}
|
|
||||||
onChange={(ev) => this.setState({ icon: ev.target.value })}
|
|
||||||
/>
|
|
||||||
</EuiFormRow>
|
|
||||||
<EuiFormRow label="Task">
|
|
||||||
<EuiTextArea
|
|
||||||
fullWidth
|
|
||||||
resize="horizontal"
|
|
||||||
data-test-subj="taskTodo"
|
|
||||||
value={this.state.task}
|
|
||||||
onChange={(ev) => this.setState({ task: ev.target.value })}
|
|
||||||
/>
|
|
||||||
</EuiFormRow>
|
|
||||||
</EuiForm>
|
|
||||||
<EuiSpacer />
|
|
||||||
<EuiPanel data-test-subj="todoEmbeddable" role="figure">
|
|
||||||
<EmbeddableRenderer
|
|
||||||
factory={this.props.todoEmbeddableFactory}
|
|
||||||
input={{
|
|
||||||
id: '1',
|
|
||||||
task: this.state.task,
|
|
||||||
title: this.state.title,
|
|
||||||
icon: this.state.icon,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</EuiPanel>
|
|
||||||
<EuiSpacer />
|
|
||||||
<EuiCodeBlock language="jsx" fontSize="m" paddingSize="m">
|
|
||||||
{`<EmbeddableRenderer
|
|
||||||
factory={this.props.todoEmbeddableFactory}
|
|
||||||
input={{
|
|
||||||
id: '1',
|
|
||||||
task: this.state.task,
|
|
||||||
title: this.state.title,
|
|
||||||
icon: this.state.icon,
|
|
||||||
}}
|
|
||||||
/>`}
|
|
||||||
</EuiCodeBlock>
|
|
||||||
</>
|
|
||||||
</EuiPageTemplate.Section>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,55 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
|
||||||
* or more contributor license agreements. Licensed under the Elastic License
|
|
||||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
|
||||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
|
||||||
* Side Public License, v 1.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import expect from '@kbn/expect';
|
|
||||||
import { PluginFunctionalProviderContext } from '../../plugin_functional/services';
|
|
||||||
|
|
||||||
// eslint-disable-next-line import/no-default-export
|
|
||||||
export default function ({ getService }: PluginFunctionalProviderContext) {
|
|
||||||
const testSubjects = getService('testSubjects');
|
|
||||||
const find = getService('find');
|
|
||||||
const flyout = getService('flyout');
|
|
||||||
|
|
||||||
const toggleFilterPopover = async () => {
|
|
||||||
const filtersHolder = await find.byClassName('euiSearchBar__filtersHolder');
|
|
||||||
const filtersButton = await filtersHolder.findByCssSelector('button');
|
|
||||||
await filtersButton.click();
|
|
||||||
};
|
|
||||||
|
|
||||||
const clickFilter = async (type: string) => {
|
|
||||||
const list = await testSubjects.find('euiSelectableList');
|
|
||||||
const listItems = await list.findAllByCssSelector('li');
|
|
||||||
for (let i = 0; i < listItems.length; i++) {
|
|
||||||
const listItem = await listItems[i].findByClassName('euiSelectableListItem__text');
|
|
||||||
const text = await listItem.getVisibleText();
|
|
||||||
if (text.includes(type)) {
|
|
||||||
await listItem.click();
|
|
||||||
await toggleFilterPopover();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
describe('adding children', () => {
|
|
||||||
before(async () => {
|
|
||||||
await testSubjects.click('embeddablePanelExample');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Can add a child backed off a saved object', async () => {
|
|
||||||
await testSubjects.click('addPanelToListContainer');
|
|
||||||
await testSubjects.waitForDeleted('savedObjectFinderLoadingIndicator');
|
|
||||||
await toggleFilterPopover();
|
|
||||||
await clickFilter('Todo');
|
|
||||||
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!', 'Take the garbage out']);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
|
@ -24,8 +24,6 @@ export default function ({
|
||||||
});
|
});
|
||||||
|
|
||||||
loadTestFile(require.resolve('./hello_world_embeddable'));
|
loadTestFile(require.resolve('./hello_world_embeddable'));
|
||||||
loadTestFile(require.resolve('./todo_embeddable'));
|
|
||||||
loadTestFile(require.resolve('./list_container'));
|
loadTestFile(require.resolve('./list_container'));
|
||||||
loadTestFile(require.resolve('./adding_children'));
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,13 +22,10 @@ export default function ({ getService }: PluginFunctionalProviderContext) {
|
||||||
it('list containers render', async () => {
|
it('list containers render', async () => {
|
||||||
await retry.try(async () => {
|
await retry.try(async () => {
|
||||||
const title = await testSubjects.getVisibleText('listContainerTitle');
|
const title = await testSubjects.getVisibleText('listContainerTitle');
|
||||||
expect(title).to.be('Todo list');
|
expect(title).to.be('Hello world list');
|
||||||
|
|
||||||
const titles = await testSubjects.getVisibleTextAll('todoEmbeddableTitle');
|
|
||||||
expect(titles).to.eql(['Take out the trash', 'Vaccum the floor']);
|
|
||||||
|
|
||||||
const text = await testSubjects.getVisibleTextAll('helloWorldEmbeddable');
|
const text = await testSubjects.getVisibleTextAll('helloWorldEmbeddable');
|
||||||
expect(text).to.eql(['HELLO WORLD!']);
|
expect(text).to.eql(['HELLO WORLD!', 'HELLO WORLD!']);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,43 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
|
||||||
* or more contributor license agreements. Licensed under the Elastic License
|
|
||||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
|
||||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
|
||||||
* Side Public License, v 1.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import expect from '@kbn/expect';
|
|
||||||
import { PluginFunctionalProviderContext } from '../../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 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');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
Loading…
Add table
Add a link
Reference in a new issue