Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Dashboard First] Decouple Attribute Service and By Value Embeddables #74302

Merged
2 changes: 1 addition & 1 deletion examples/embeddable_examples/kibana.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"kibanaVersion": "kibana",
"server": true,
"ui": true,
"requiredPlugins": ["embeddable", "uiActions"],
"requiredPlugins": ["embeddable", "uiActions", "dashboard"],
"optionalPlugins": [],
"extraPublicDirs": ["public/todo", "public/hello_world", "public/todo/todo_ref_embeddable"],
"requiredBundles": ["kibanaReact"]
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

import { i18n } from '@kbn/i18n';
import { createAction, IncompatibleActionError } from '../../../../src/plugins/ui_actions/public';
import { BookEmbeddable, BOOK_EMBEDDABLE } from './book_embeddable';
import { ViewMode, isReferenceOrValueEmbeddable } from '../../../../src/plugins/embeddable/public';

interface ActionContext {
embeddable: BookEmbeddable;
}

export const ACTION_ADD_BOOK_TO_LIBRARY = 'ACTION_ADD_BOOK_TO_LIBRARY';

export const createAddBookToLibraryAction = () =>
createAction({
getDisplayName: () =>
i18n.translate('embeddableExamples.book.addToLibrary', {
defaultMessage: '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 &&
isReferenceOrValueEmbeddable(embeddable) &&
!embeddable.inputIsRefType(embeddable.getInput())
);
},
execute: async ({ embeddable }: ActionContext) => {
if (!isReferenceOrValueEmbeddable(embeddable)) {
throw new IncompatibleActionError();
}
const newInput = await embeddable.getInputAsRefType();
embeddable.updateInput(newInput);
},
});
32 changes: 26 additions & 6 deletions examples/embeddable_examples/public/book/book_component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import React from 'react';
import { EuiFlexItem, EuiFlexGroup, EuiIcon } from '@elastic/eui';

import { EuiText } from '@elastic/eui';
import { EuiFlexGrid } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { withEmbeddableSubscription } from '../../../../src/plugins/embeddable/public';
import { BookEmbeddableInput, BookEmbeddableOutput, BookEmbeddable } from './book_embeddable';

Expand All @@ -44,26 +44,32 @@ function wrapSearchTerms(task?: string, search?: string) {
);
}

export function BookEmbeddableComponentInner({ input: { search }, output: { attributes } }: Props) {
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>
<EuiFlexGrid columns={1} gutterSize="none">
<EuiFlexGroup direction="column" gutterSize="s">
{title ? (
<EuiFlexItem>
<EuiText data-test-subj="bookEmbeddableTitle">
<h3>{wrapSearchTerms(title, search)},</h3>
<h3>{wrapSearchTerms(title, search)}</h3>
</EuiText>
</EuiFlexItem>
) : null}
{author ? (
<EuiFlexItem>
<EuiText data-test-subj="bookEmbeddableAuthor">
<h5>-{wrapSearchTerms(author, search)}</h5>
-{wrapSearchTerms(author, search)}
</EuiText>
</EuiFlexItem>
) : null}
Expand All @@ -76,7 +82,21 @@ export function BookEmbeddableComponentInner({ input: { search }, output: { attr
<EuiIcon type="cross" />
</EuiFlexItem>
)}
</EuiFlexGrid>
</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>
);
Expand Down
22 changes: 20 additions & 2 deletions examples/embeddable_examples/public/book/book_embeddable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,11 @@ import {
IContainer,
EmbeddableOutput,
SavedObjectEmbeddableInput,
AttributeService,
ReferenceOrValueEmbeddable,
} from '../../../../src/plugins/embeddable/public';
import { BookSavedObjectAttributes } from '../../common';
import { BookEmbeddableComponent } from './book_component';
import { AttributeService } from '../../../../src/plugins/dashboard/public';

export const BOOK_EMBEDDABLE = 'book';
export type BookEmbeddableInput = BookByValueInput | BookByReferenceInput;
Expand Down Expand Up @@ -59,7 +60,8 @@ function getHasMatch(search?: string, savedAttributes?: BookSavedObjectAttribute
);
}

export class BookEmbeddable extends Embeddable<BookEmbeddableInput, BookEmbeddableOutput> {
export class BookEmbeddable extends Embeddable<BookEmbeddableInput, BookEmbeddableOutput>
implements ReferenceOrValueEmbeddable<BookByValueInput, BookByReferenceInput> {
public readonly type = BOOK_EMBEDDABLE;
private subscription: Subscription;
private node?: HTMLElement;
Expand Down Expand Up @@ -96,6 +98,18 @@ export class BookEmbeddable extends Embeddable<BookEmbeddableInput, BookEmbeddab
});
}

inputIsRefType = (input: BookEmbeddableInput): input is BookByReferenceInput => {
return this.attributeService.inputIsRefType(input);
};

getInputAsValueType = async (): Promise<BookByValueInput> => {
return this.attributeService.getInputAsValueType(this.input);
};

getInputAsRefType = async (): Promise<BookByReferenceInput> => {
return this.attributeService.getInputAsRefType(this.input, { showSaveModal: true });
};

public render(node: HTMLElement) {
if (this.node) {
ReactDOM.unmountComponentAtNode(this.node);
Expand All @@ -113,6 +127,10 @@ export class BookEmbeddable extends Embeddable<BookEmbeddableInput, BookEmbeddab
});
}

public getTitle() {
return this.getOutput()?.title || this.getOutput().attributes?.title;
}

public destroy() {
super.destroy();
this.subscription.unsubscribe();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,7 @@ import { BookSavedObjectAttributes, BOOK_SAVED_OBJECT } from '../../common';
import { toMountPoint } from '../../../../src/plugins/kibana_react/public';
import {
EmbeddableFactoryDefinition,
EmbeddableStart,
IContainer,
AttributeService,
EmbeddableFactory,
} from '../../../../src/plugins/embeddable/public';
import {
Expand All @@ -38,9 +36,10 @@ import {
} from './book_embeddable';
import { CreateEditBookComponent } from './create_edit_book_component';
import { OverlayStart } from '../../../../src/core/public';
import { DashboardStart, AttributeService } from '../../../../src/plugins/dashboard/public';

interface StartServices {
getAttributeService: EmbeddableStart['getAttributeService'];
getAttributeService: DashboardStart['getAttributeService'];
openModal: OverlayStart['openModal'];
}

Expand Down Expand Up @@ -85,6 +84,16 @@ export class BookEmbeddableFactoryDefinition
});
}

// 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',
Expand Down Expand Up @@ -122,6 +131,6 @@ export class BookEmbeddableFactoryDefinition
BookByReferenceInput
>(this.type);
}
return this.attributeService;
return this.attributeService!;
}
}
9 changes: 3 additions & 6 deletions examples/embeddable_examples/public/book/edit_book_action.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,22 +22,19 @@ import { i18n } from '@kbn/i18n';
import { BookSavedObjectAttributes, BOOK_SAVED_OBJECT } from '../../common';
import { createAction } from '../../../../src/plugins/ui_actions/public';
import { toMountPoint } from '../../../../src/plugins/kibana_react/public';
import {
ViewMode,
EmbeddableStart,
SavedObjectEmbeddableInput,
} from '../../../../src/plugins/embeddable/public';
import { ViewMode, SavedObjectEmbeddableInput } from '../../../../src/plugins/embeddable/public';
import {
BookEmbeddable,
BOOK_EMBEDDABLE,
BookByReferenceInput,
BookByValueInput,
} from './book_embeddable';
import { CreateEditBookComponent } from './create_edit_book_component';
import { DashboardStart } from '../../../../src/plugins/dashboard/public';

interface StartServices {
openModal: OverlayStart['openModal'];
getAttributeService: EmbeddableStart['getAttributeService'];
getAttributeService: DashboardStart['getAttributeService'];
}

interface ActionContext {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

import { i18n } from '@kbn/i18n';
import { createAction, IncompatibleActionError } from '../../../../src/plugins/ui_actions/public';
import { BookEmbeddable, BOOK_EMBEDDABLE } from './book_embeddable';
import { ViewMode, isReferenceOrValueEmbeddable } from '../../../../src/plugins/embeddable/public';

interface ActionContext {
embeddable: BookEmbeddable;
}

export const ACTION_UNLINK_BOOK_FROM_LIBRARY = 'ACTION_UNLINK_BOOK_FROM_LIBRARY';

export const createUnlinkBookFromLibraryAction = () =>
createAction({
getDisplayName: () =>
i18n.translate('embeddableExamples.book.unlinkFromLibrary', {
defaultMessage: 'Unlink Book from Library Item',
}),
type: ACTION_UNLINK_BOOK_FROM_LIBRARY,
order: 100,
getIconType: () => 'folderExclamation',
isCompatible: async ({ embeddable }: ActionContext) => {
return (
embeddable.type === BOOK_EMBEDDABLE &&
embeddable.getInput().viewMode === ViewMode.EDIT &&
isReferenceOrValueEmbeddable(embeddable) &&
embeddable.inputIsRefType(embeddable.getInput())
);
},
execute: async ({ embeddable }: ActionContext) => {
if (!isReferenceOrValueEmbeddable(embeddable)) {
throw new IncompatibleActionError();
}
const newInput = await embeddable.getInputAsValueType();
embeddable.updateInput(newInput);
},
});
24 changes: 22 additions & 2 deletions examples/embeddable_examples/public/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,15 @@ import {
BookEmbeddableFactoryDefinition,
} from './book/book_embeddable_factory';
import { UiActionsStart } from '../../../src/plugins/ui_actions/public';
import {
ACTION_ADD_BOOK_TO_LIBRARY,
createAddBookToLibraryAction,
} from './book/add_book_to_library_action';
import { DashboardStart } from '../../../src/plugins/dashboard/public';
import {
ACTION_UNLINK_BOOK_FROM_LIBRARY,
createUnlinkBookFromLibraryAction,
} from './book/unlink_book_from_library_action';

export interface EmbeddableExamplesSetupDependencies {
embeddable: EmbeddableSetup;
Expand All @@ -66,6 +75,7 @@ export interface EmbeddableExamplesSetupDependencies {

export interface EmbeddableExamplesStartDependencies {
embeddable: EmbeddableStart;
dashboard: DashboardStart;
}

interface ExampleEmbeddableFactories {
Expand All @@ -86,6 +96,8 @@ export interface EmbeddableExamplesStart {
declare module '../../../src/plugins/ui_actions/public' {
export interface ActionContextMapping {
[ACTION_EDIT_BOOK]: { embeddable: BookEmbeddable };
[ACTION_ADD_BOOK_TO_LIBRARY]: { embeddable: BookEmbeddable };
[ACTION_UNLINK_BOOK_FROM_LIBRARY]: { embeddable: BookEmbeddable };
}
}

Expand Down Expand Up @@ -144,17 +156,25 @@ export class EmbeddableExamplesPlugin
this.exampleEmbeddableFactories.getBookEmbeddableFactory = deps.embeddable.registerEmbeddableFactory(
BOOK_EMBEDDABLE,
new BookEmbeddableFactoryDefinition(async () => ({
getAttributeService: (await core.getStartServices())[1].embeddable.getAttributeService,
getAttributeService: (await core.getStartServices())[1].dashboard.getAttributeService,
openModal: (await core.getStartServices())[0].overlays.openModal,
}))
);

const editBookAction = createEditBookAction(async () => ({
getAttributeService: (await core.getStartServices())[1].embeddable.getAttributeService,
getAttributeService: (await core.getStartServices())[1].dashboard.getAttributeService,
openModal: (await core.getStartServices())[0].overlays.openModal,
}));
deps.uiActions.registerAction(editBookAction);
deps.uiActions.attachAction(CONTEXT_MENU_TRIGGER, editBookAction.id);

const addBookToLibraryAction = createAddBookToLibraryAction();
deps.uiActions.registerAction(addBookToLibraryAction);
deps.uiActions.attachAction(CONTEXT_MENU_TRIGGER, addBookToLibraryAction.id);

const unlinkBookFromLibraryAction = createUnlinkBookFromLibraryAction();
deps.uiActions.registerAction(unlinkBookFromLibraryAction);
deps.uiActions.attachAction(CONTEXT_MENU_TRIGGER, unlinkBookFromLibraryAction.id);
}

public start(
Expand Down
Loading