mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -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": [
|
||||
"embeddable",
|
||||
"uiActions",
|
||||
"savedObjects",
|
||||
"dashboard",
|
||||
"kibanaUtils"
|
||||
],
|
||||
"requiredBundles": [
|
||||
"kibanaReact"
|
||||
],
|
||||
"extraPublicDirs": [
|
||||
"public/todo",
|
||||
"public/hello_world",
|
||||
"public/todo/todo_ref_embeddable"
|
||||
"public/hello_world"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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';
|
||||
export type { ListContainerFactory } 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 {
|
||||
|
@ -27,9 +23,4 @@ export {
|
|||
|
||||
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();
|
||||
|
|
|
@ -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.
|
||||
*/
|
||||
|
||||
import {
|
||||
EmbeddableSetup,
|
||||
EmbeddableStart,
|
||||
CONTEXT_MENU_TRIGGER,
|
||||
} from '@kbn/embeddable-plugin/public';
|
||||
import { Plugin, CoreSetup, CoreStart, SavedObjectsClientContract } from '@kbn/core/public';
|
||||
import { EmbeddableSetup, EmbeddableStart } from '@kbn/embeddable-plugin/public';
|
||||
import { Plugin, CoreSetup, CoreStart } from '@kbn/core/public';
|
||||
import { UiActionsStart } from '@kbn/ui-actions-plugin/public';
|
||||
import {
|
||||
HelloWorldEmbeddableFactory,
|
||||
HELLO_WORLD_EMBEDDABLE,
|
||||
HelloWorldEmbeddableFactoryDefinition,
|
||||
} 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 {
|
||||
LIST_CONTAINER,
|
||||
ListContainerFactoryDefinition,
|
||||
ListContainerFactory,
|
||||
} 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 {
|
||||
SIMPLE_EMBEDDABLE,
|
||||
SimpleEmbeddableFactory,
|
||||
|
@ -67,17 +39,11 @@ export interface EmbeddableExamplesSetupDependencies {
|
|||
|
||||
export interface EmbeddableExamplesStartDependencies {
|
||||
embeddable: EmbeddableStart;
|
||||
savedObjectsClient: SavedObjectsClientContract;
|
||||
}
|
||||
|
||||
interface ExampleEmbeddableFactories {
|
||||
getHelloWorldEmbeddableFactory: () => HelloWorldEmbeddableFactory;
|
||||
getMultiTaskTodoEmbeddableFactory: () => MultiTaskTodoEmbeddableFactory;
|
||||
getSearchableListContainerEmbeddableFactory: () => SearchableListContainerFactory;
|
||||
getListContainerEmbeddableFactory: () => ListContainerFactory;
|
||||
getTodoEmbeddableFactory: () => TodoEmbeddableFactory;
|
||||
getTodoRefEmbeddableFactory: () => TodoRefEmbeddableFactory;
|
||||
getBookEmbeddableFactory: () => BookEmbeddableFactory;
|
||||
getMigrationsEmbeddableFactory: () => SimpleEmbeddableFactory;
|
||||
getFilterDebuggerEmbeddableFactory: () => FilterDebuggerEmbeddableFactory;
|
||||
}
|
||||
|
@ -114,20 +80,6 @@ export class EmbeddableExamplesPlugin
|
|||
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 =
|
||||
deps.embeddable.registerEmbeddableFactory(
|
||||
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 =
|
||||
deps.embeddable.registerEmbeddableFactory(
|
||||
FILTER_DEBUGGER_EMBEDDABLE,
|
||||
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(
|
||||
|
@ -191,7 +100,7 @@ export class EmbeddableExamplesPlugin
|
|||
deps: EmbeddableExamplesStartDependencies
|
||||
): EmbeddableExamplesStart {
|
||||
return {
|
||||
createSampleData: () => createSampleData(core.savedObjects.client),
|
||||
createSampleData: async () => {},
|
||||
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 { 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 {
|
||||
embeddable: EmbeddableSetup;
|
||||
|
@ -19,11 +16,7 @@ export interface EmbeddableExamplesSetupDependencies {
|
|||
export class EmbeddableExamplesPlugin
|
||||
implements Plugin<void, void, EmbeddableExamplesSetupDependencies>
|
||||
{
|
||||
public setup(core: CoreSetup, { embeddable }: EmbeddableExamplesSetupDependencies) {
|
||||
core.savedObjects.registerType(todoSavedObject);
|
||||
core.savedObjects.registerType(bookSavedObject);
|
||||
core.savedObjects.registerType(searchableListSavedObject(embeddable));
|
||||
}
|
||||
public setup(core: CoreSetup, { embeddable }: EmbeddableExamplesSetupDependencies) {}
|
||||
|
||||
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/core",
|
||||
"@kbn/kibana-utils-plugin",
|
||||
"@kbn/kibana-react-plugin",
|
||||
"@kbn/ui-actions-plugin",
|
||||
"@kbn/embeddable-plugin",
|
||||
"@kbn/dashboard-plugin",
|
||||
"@kbn/saved-objects-plugin",
|
||||
"@kbn/i18n",
|
||||
"@kbn/utility-types",
|
||||
"@kbn/es-query",
|
||||
]
|
||||
}
|
||||
|
|
|
@ -14,8 +14,7 @@
|
|||
"embeddableExamples",
|
||||
"developerExamples",
|
||||
"dashboard",
|
||||
"kibanaReact",
|
||||
"savedObjects"
|
||||
"kibanaReact"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,20 +11,12 @@ import ReactDOM from 'react-dom';
|
|||
import { BrowserRouter as Router, withRouter, RouteComponentProps } from 'react-router-dom';
|
||||
import { Route } from '@kbn/shared-ux-router';
|
||||
import { EuiPageTemplate, EuiSideNav } from '@elastic/eui';
|
||||
|
||||
import { EmbeddableStart } from '@kbn/embeddable-plugin/public';
|
||||
import { UiActionsStart } from '@kbn/ui-actions-plugin/public';
|
||||
import { Start as InspectorStartContract } from '@kbn/inspector-plugin/public';
|
||||
import {
|
||||
AppMountParameters,
|
||||
CoreStart,
|
||||
SavedObjectsStart,
|
||||
IUiSettingsClient,
|
||||
OverlayStart,
|
||||
} from '@kbn/core/public';
|
||||
import { AppMountParameters, CoreStart, IUiSettingsClient, OverlayStart } from '@kbn/core/public';
|
||||
import { EmbeddableExamplesStart } from '@kbn/embeddable-examples-plugin/public/plugin';
|
||||
import { HelloWorldEmbeddableExample } from './hello_world_embeddable_example';
|
||||
import { TodoEmbeddableExample } from './todo_embeddable_example';
|
||||
import { ListContainerExample } from './list_container_example';
|
||||
import { EmbeddablePanelExample } from './embeddable_panel_example';
|
||||
|
||||
|
@ -68,7 +60,6 @@ interface Props {
|
|||
overlays: OverlayStart;
|
||||
notifications: CoreStart['notifications'];
|
||||
inspector: InspectorStartContract;
|
||||
savedObject: SavedObjectsStart;
|
||||
uiSettingsClient: IUiSettingsClient;
|
||||
embeddableExamples: EmbeddableExamplesStart;
|
||||
}
|
||||
|
@ -89,15 +80,6 @@ const EmbeddableExplorerApp = ({
|
|||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: 'Update embeddable state',
|
||||
id: 'todoEmbeddableSection',
|
||||
component: (
|
||||
<TodoEmbeddableExample
|
||||
todoEmbeddableFactory={embeddableExamples.factories.getTodoEmbeddableFactory()}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: 'Groups of embeddables',
|
||||
id: 'listContainerSection',
|
||||
|
@ -112,7 +94,7 @@ const EmbeddableExplorerApp = ({
|
|||
id: 'embeddablePanelExample',
|
||||
component: (
|
||||
<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 { EuiSpacer } from '@elastic/eui';
|
||||
import { IEmbeddable, EmbeddablePanel } from '@kbn/embeddable-plugin/public';
|
||||
import {
|
||||
HELLO_WORLD_EMBEDDABLE,
|
||||
TODO_EMBEDDABLE,
|
||||
BOOK_EMBEDDABLE,
|
||||
MULTI_TASK_TODO_EMBEDDABLE,
|
||||
SearchableListContainerFactory,
|
||||
} from '@kbn/embeddable-examples-plugin/public';
|
||||
import { HelloWorldEmbeddableFactory } from '@kbn/embeddable-examples-plugin/public';
|
||||
|
||||
interface Props {
|
||||
searchListContainerFactory: SearchableListContainerFactory;
|
||||
helloWorldFactory: HelloWorldEmbeddableFactory;
|
||||
}
|
||||
|
||||
export function EmbeddablePanelExample({ searchListContainerFactory }: 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,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export function EmbeddablePanelExample({ helloWorldFactory }: Props) {
|
||||
const [embeddable, setEmbeddable] = useState<IEmbeddable | undefined>(undefined);
|
||||
|
||||
const ref = useRef(false);
|
||||
|
@ -91,7 +24,7 @@ export function EmbeddablePanelExample({ searchListContainerFactory }: Props) {
|
|||
useEffect(() => {
|
||||
ref.current = true;
|
||||
if (!embeddable) {
|
||||
const promise = searchListContainerFactory.create(searchableInput);
|
||||
const promise = helloWorldFactory.create({ id: '1', title: 'Hello World!' });
|
||||
if (promise) {
|
||||
promise.then((e) => {
|
||||
if (ref.current) {
|
||||
|
|
|
@ -11,10 +11,8 @@ import { EuiPanel, EuiSpacer, EuiText, EuiPageTemplate, EuiCodeBlock } from '@el
|
|||
import { EmbeddableRenderer, ViewMode } from '@kbn/embeddable-plugin/public';
|
||||
import {
|
||||
HELLO_WORLD_EMBEDDABLE,
|
||||
TODO_EMBEDDABLE,
|
||||
ListContainerFactory,
|
||||
} from '@kbn/embeddable-examples-plugin/public';
|
||||
import { TodoInput } from '@kbn/embeddable-examples-plugin/public/todo';
|
||||
|
||||
interface Props {
|
||||
listContainerEmbeddableFactory: ListContainerFactory;
|
||||
|
@ -33,7 +31,7 @@ export function ListContainerExample({ listContainerEmbeddableFactory }: Props)
|
|||
factory={listContainerEmbeddableFactory}
|
||||
input={{
|
||||
id: 'hello',
|
||||
title: 'Todo list',
|
||||
title: 'Hello world list',
|
||||
viewMode: ViewMode.VIEW,
|
||||
panels: {
|
||||
'1': {
|
||||
|
@ -43,21 +41,10 @@ export function ListContainerExample({ listContainerEmbeddableFactory }: Props)
|
|||
},
|
||||
},
|
||||
'2': {
|
||||
type: TODO_EMBEDDABLE,
|
||||
type: HELLO_WORLD_EMBEDDABLE,
|
||||
explicitInput: {
|
||||
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: {
|
||||
id: '1',
|
||||
},
|
||||
},
|
||||
'2': {
|
||||
type: TODO_EMBEDDABLE,
|
||||
explicitInput: {
|
||||
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,
|
||||
},
|
||||
type: HELLO_WORLD_EMBEDDABLE,
|
||||
explicitInput: {
|
||||
id: '2',
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
}}
|
||||
/>`}
|
||||
|
|
|
@ -48,7 +48,6 @@ export class EmbeddableExplorerPlugin implements Plugin<void, void, {}, StartDep
|
|||
uiActionsApi: depsStart.uiActions,
|
||||
basename: params.appBasePath,
|
||||
uiSettingsClient: coreStart.uiSettings,
|
||||
savedObject: coreStart.savedObjects,
|
||||
overlays: coreStart.overlays,
|
||||
navigateToApp: coreStart.application.navigateToApp,
|
||||
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('./todo_embeddable'));
|
||||
loadTestFile(require.resolve('./list_container'));
|
||||
loadTestFile(require.resolve('./adding_children'));
|
||||
});
|
||||
}
|
||||
|
|
|
@ -22,13 +22,10 @@ export default function ({ getService }: PluginFunctionalProviderContext) {
|
|||
it('list containers render', async () => {
|
||||
await retry.try(async () => {
|
||||
const title = await testSubjects.getVisibleText('listContainerTitle');
|
||||
expect(title).to.be('Todo list');
|
||||
|
||||
const titles = await testSubjects.getVisibleTextAll('todoEmbeddableTitle');
|
||||
expect(titles).to.eql(['Take out the trash', 'Vaccum the floor']);
|
||||
expect(title).to.be('Hello world list');
|
||||
|
||||
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