Skip to content

Commit cb72fc0

Browse files
committed
[Dashboard First] Decouple Attribute Service and By Value Embeddables (#74302)
* Added an interface that determines if an embeddable can be treated as either by reference or by value
1 parent a9771ab commit cb72fc0

19 files changed

Lines changed: 453 additions & 104 deletions

File tree

examples/embeddable_examples/kibana.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
"kibanaVersion": "kibana",
55
"server": true,
66
"ui": true,
7-
"requiredPlugins": ["embeddable", "uiActions"],
7+
"requiredPlugins": ["embeddable", "uiActions", "dashboard"],
88
"optionalPlugins": [],
99
"extraPublicDirs": ["public/todo", "public/hello_world", "public/todo/todo_ref_embeddable"],
1010
"requiredBundles": ["kibanaReact"]
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
/*
2+
* Licensed to Elasticsearch B.V. under one or more contributor
3+
* license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright
5+
* ownership. Elasticsearch B.V. licenses this file to you under
6+
* the Apache License, Version 2.0 (the "License"); you may
7+
* not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
import { i18n } from '@kbn/i18n';
21+
import { createAction, IncompatibleActionError } from '../../../../src/plugins/ui_actions/public';
22+
import { BookEmbeddable, BOOK_EMBEDDABLE } from './book_embeddable';
23+
import { ViewMode, isReferenceOrValueEmbeddable } from '../../../../src/plugins/embeddable/public';
24+
25+
interface ActionContext {
26+
embeddable: BookEmbeddable;
27+
}
28+
29+
export const ACTION_ADD_BOOK_TO_LIBRARY = 'ACTION_ADD_BOOK_TO_LIBRARY';
30+
31+
export const createAddBookToLibraryAction = () =>
32+
createAction({
33+
getDisplayName: () =>
34+
i18n.translate('embeddableExamples.book.addToLibrary', {
35+
defaultMessage: 'Add Book To Library',
36+
}),
37+
type: ACTION_ADD_BOOK_TO_LIBRARY,
38+
order: 100,
39+
getIconType: () => 'folderCheck',
40+
isCompatible: async ({ embeddable }: ActionContext) => {
41+
return (
42+
embeddable.type === BOOK_EMBEDDABLE &&
43+
embeddable.getInput().viewMode === ViewMode.EDIT &&
44+
isReferenceOrValueEmbeddable(embeddable) &&
45+
!embeddable.inputIsRefType(embeddable.getInput())
46+
);
47+
},
48+
execute: async ({ embeddable }: ActionContext) => {
49+
if (!isReferenceOrValueEmbeddable(embeddable)) {
50+
throw new IncompatibleActionError();
51+
}
52+
const newInput = await embeddable.getInputAsRefType();
53+
embeddable.updateInput(newInput);
54+
},
55+
});

examples/embeddable_examples/public/book/book_component.tsx

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import React from 'react';
2020
import { EuiFlexItem, EuiFlexGroup, EuiIcon } from '@elastic/eui';
2121

2222
import { EuiText } from '@elastic/eui';
23-
import { EuiFlexGrid } from '@elastic/eui';
23+
import { i18n } from '@kbn/i18n';
2424
import { withEmbeddableSubscription } from '../../../../src/plugins/embeddable/public';
2525
import { BookEmbeddableInput, BookEmbeddableOutput, BookEmbeddable } from './book_embeddable';
2626

@@ -44,26 +44,32 @@ function wrapSearchTerms(task?: string, search?: string) {
4444
);
4545
}
4646

47-
export function BookEmbeddableComponentInner({ input: { search }, output: { attributes } }: Props) {
47+
export function BookEmbeddableComponentInner({
48+
input: { search },
49+
output: { attributes },
50+
embeddable,
51+
}: Props) {
4852
const title = attributes?.title;
4953
const author = attributes?.author;
5054
const readIt = attributes?.readIt;
5155

56+
const byReference = embeddable.inputIsRefType(embeddable.getInput());
57+
5258
return (
5359
<EuiFlexGroup gutterSize="s">
5460
<EuiFlexItem>
55-
<EuiFlexGrid columns={1} gutterSize="none">
61+
<EuiFlexGroup direction="column" gutterSize="s">
5662
{title ? (
5763
<EuiFlexItem>
5864
<EuiText data-test-subj="bookEmbeddableTitle">
59-
<h3>{wrapSearchTerms(title, search)},</h3>
65+
<h3>{wrapSearchTerms(title, search)}</h3>
6066
</EuiText>
6167
</EuiFlexItem>
6268
) : null}
6369
{author ? (
6470
<EuiFlexItem>
6571
<EuiText data-test-subj="bookEmbeddableAuthor">
66-
<h5>-{wrapSearchTerms(author, search)}</h5>
72+
-{wrapSearchTerms(author, search)}
6773
</EuiText>
6874
</EuiFlexItem>
6975
) : null}
@@ -76,7 +82,21 @@ export function BookEmbeddableComponentInner({ input: { search }, output: { attr
7682
<EuiIcon type="cross" />
7783
</EuiFlexItem>
7884
)}
79-
</EuiFlexGrid>
85+
</EuiFlexGroup>
86+
</EuiFlexItem>
87+
<EuiFlexItem>
88+
<EuiText data-test-subj="bookEmbeddableAuthor">
89+
<EuiIcon type={byReference ? 'folderCheck' : 'folderExclamation'} />{' '}
90+
<span>
91+
{byReference
92+
? i18n.translate('embeddableExamples.book.byReferenceLabel', {
93+
defaultMessage: 'Book is By Reference',
94+
})
95+
: i18n.translate('embeddableExamples.book.byValueLabel', {
96+
defaultMessage: 'Book is By Value',
97+
})}
98+
</span>
99+
</EuiText>
80100
</EuiFlexItem>
81101
</EuiFlexGroup>
82102
);

examples/embeddable_examples/public/book/book_embeddable.tsx

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,11 @@ import {
2525
IContainer,
2626
EmbeddableOutput,
2727
SavedObjectEmbeddableInput,
28-
AttributeService,
28+
ReferenceOrValueEmbeddable,
2929
} from '../../../../src/plugins/embeddable/public';
3030
import { BookSavedObjectAttributes } from '../../common';
3131
import { BookEmbeddableComponent } from './book_component';
32+
import { AttributeService } from '../../../../src/plugins/dashboard/public';
3233

3334
export const BOOK_EMBEDDABLE = 'book';
3435
export type BookEmbeddableInput = BookByValueInput | BookByReferenceInput;
@@ -59,7 +60,8 @@ function getHasMatch(search?: string, savedAttributes?: BookSavedObjectAttribute
5960
);
6061
}
6162

62-
export class BookEmbeddable extends Embeddable<BookEmbeddableInput, BookEmbeddableOutput> {
63+
export class BookEmbeddable extends Embeddable<BookEmbeddableInput, BookEmbeddableOutput>
64+
implements ReferenceOrValueEmbeddable<BookByValueInput, BookByReferenceInput> {
6365
public readonly type = BOOK_EMBEDDABLE;
6466
private subscription: Subscription;
6567
private node?: HTMLElement;
@@ -96,6 +98,18 @@ export class BookEmbeddable extends Embeddable<BookEmbeddableInput, BookEmbeddab
9698
});
9799
}
98100

101+
inputIsRefType = (input: BookEmbeddableInput): input is BookByReferenceInput => {
102+
return this.attributeService.inputIsRefType(input);
103+
};
104+
105+
getInputAsValueType = async (): Promise<BookByValueInput> => {
106+
return this.attributeService.getInputAsValueType(this.input);
107+
};
108+
109+
getInputAsRefType = async (): Promise<BookByReferenceInput> => {
110+
return this.attributeService.getInputAsRefType(this.input, { showSaveModal: true });
111+
};
112+
99113
public render(node: HTMLElement) {
100114
if (this.node) {
101115
ReactDOM.unmountComponentAtNode(this.node);
@@ -113,6 +127,10 @@ export class BookEmbeddable extends Embeddable<BookEmbeddableInput, BookEmbeddab
113127
});
114128
}
115129

130+
public getTitle() {
131+
return this.getOutput()?.title || this.getOutput().attributes?.title;
132+
}
133+
116134
public destroy() {
117135
super.destroy();
118136
this.subscription.unsubscribe();

examples/embeddable_examples/public/book/book_embeddable_factory.tsx

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,7 @@ import { BookSavedObjectAttributes, BOOK_SAVED_OBJECT } from '../../common';
2323
import { toMountPoint } from '../../../../src/plugins/kibana_react/public';
2424
import {
2525
EmbeddableFactoryDefinition,
26-
EmbeddableStart,
2726
IContainer,
28-
AttributeService,
2927
EmbeddableFactory,
3028
} from '../../../../src/plugins/embeddable/public';
3129
import {
@@ -38,9 +36,10 @@ import {
3836
} from './book_embeddable';
3937
import { CreateEditBookComponent } from './create_edit_book_component';
4038
import { OverlayStart } from '../../../../src/core/public';
39+
import { DashboardStart, AttributeService } from '../../../../src/plugins/dashboard/public';
4140

4241
interface StartServices {
43-
getAttributeService: EmbeddableStart['getAttributeService'];
42+
getAttributeService: DashboardStart['getAttributeService'];
4443
openModal: OverlayStart['openModal'];
4544
}
4645

@@ -85,6 +84,16 @@ export class BookEmbeddableFactoryDefinition
8584
});
8685
}
8786

87+
// This is currently required due to the distinction in container.ts and the
88+
// default error implementation in default_embeddable_factory_provider.ts
89+
public async createFromSavedObject(
90+
savedObjectId: string,
91+
input: BookEmbeddableInput,
92+
parent?: IContainer
93+
) {
94+
return this.create(input, parent);
95+
}
96+
8897
public getDisplayName() {
8998
return i18n.translate('embeddableExamples.book.displayName', {
9099
defaultMessage: 'Book',
@@ -122,6 +131,6 @@ export class BookEmbeddableFactoryDefinition
122131
BookByReferenceInput
123132
>(this.type);
124133
}
125-
return this.attributeService;
134+
return this.attributeService!;
126135
}
127136
}

examples/embeddable_examples/public/book/edit_book_action.tsx

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,22 +22,19 @@ import { i18n } from '@kbn/i18n';
2222
import { BookSavedObjectAttributes, BOOK_SAVED_OBJECT } from '../../common';
2323
import { createAction } from '../../../../src/plugins/ui_actions/public';
2424
import { toMountPoint } from '../../../../src/plugins/kibana_react/public';
25-
import {
26-
ViewMode,
27-
EmbeddableStart,
28-
SavedObjectEmbeddableInput,
29-
} from '../../../../src/plugins/embeddable/public';
25+
import { ViewMode, SavedObjectEmbeddableInput } from '../../../../src/plugins/embeddable/public';
3026
import {
3127
BookEmbeddable,
3228
BOOK_EMBEDDABLE,
3329
BookByReferenceInput,
3430
BookByValueInput,
3531
} from './book_embeddable';
3632
import { CreateEditBookComponent } from './create_edit_book_component';
33+
import { DashboardStart } from '../../../../src/plugins/dashboard/public';
3734

3835
interface StartServices {
3936
openModal: OverlayStart['openModal'];
40-
getAttributeService: EmbeddableStart['getAttributeService'];
37+
getAttributeService: DashboardStart['getAttributeService'];
4138
}
4239

4340
interface ActionContext {
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
/*
2+
* Licensed to Elasticsearch B.V. under one or more contributor
3+
* license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright
5+
* ownership. Elasticsearch B.V. licenses this file to you under
6+
* the Apache License, Version 2.0 (the "License"); you may
7+
* not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
import { i18n } from '@kbn/i18n';
21+
import { createAction, IncompatibleActionError } from '../../../../src/plugins/ui_actions/public';
22+
import { BookEmbeddable, BOOK_EMBEDDABLE } from './book_embeddable';
23+
import { ViewMode, isReferenceOrValueEmbeddable } from '../../../../src/plugins/embeddable/public';
24+
25+
interface ActionContext {
26+
embeddable: BookEmbeddable;
27+
}
28+
29+
export const ACTION_UNLINK_BOOK_FROM_LIBRARY = 'ACTION_UNLINK_BOOK_FROM_LIBRARY';
30+
31+
export const createUnlinkBookFromLibraryAction = () =>
32+
createAction({
33+
getDisplayName: () =>
34+
i18n.translate('embeddableExamples.book.unlinkFromLibrary', {
35+
defaultMessage: 'Unlink Book from Library Item',
36+
}),
37+
type: ACTION_UNLINK_BOOK_FROM_LIBRARY,
38+
order: 100,
39+
getIconType: () => 'folderExclamation',
40+
isCompatible: async ({ embeddable }: ActionContext) => {
41+
return (
42+
embeddable.type === BOOK_EMBEDDABLE &&
43+
embeddable.getInput().viewMode === ViewMode.EDIT &&
44+
isReferenceOrValueEmbeddable(embeddable) &&
45+
embeddable.inputIsRefType(embeddable.getInput())
46+
);
47+
},
48+
execute: async ({ embeddable }: ActionContext) => {
49+
if (!isReferenceOrValueEmbeddable(embeddable)) {
50+
throw new IncompatibleActionError();
51+
}
52+
const newInput = await embeddable.getInputAsValueType();
53+
embeddable.updateInput(newInput);
54+
},
55+
});

examples/embeddable_examples/public/plugin.ts

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,15 @@ import {
5858
BookEmbeddableFactoryDefinition,
5959
} from './book/book_embeddable_factory';
6060
import { UiActionsStart } from '../../../src/plugins/ui_actions/public';
61+
import {
62+
ACTION_ADD_BOOK_TO_LIBRARY,
63+
createAddBookToLibraryAction,
64+
} from './book/add_book_to_library_action';
65+
import { DashboardStart } from '../../../src/plugins/dashboard/public';
66+
import {
67+
ACTION_UNLINK_BOOK_FROM_LIBRARY,
68+
createUnlinkBookFromLibraryAction,
69+
} from './book/unlink_book_from_library_action';
6170

6271
export interface EmbeddableExamplesSetupDependencies {
6372
embeddable: EmbeddableSetup;
@@ -66,6 +75,7 @@ export interface EmbeddableExamplesSetupDependencies {
6675

6776
export interface EmbeddableExamplesStartDependencies {
6877
embeddable: EmbeddableStart;
78+
dashboard: DashboardStart;
6979
}
7080

7181
interface ExampleEmbeddableFactories {
@@ -86,6 +96,8 @@ export interface EmbeddableExamplesStart {
8696
declare module '../../../src/plugins/ui_actions/public' {
8797
export interface ActionContextMapping {
8898
[ACTION_EDIT_BOOK]: { embeddable: BookEmbeddable };
99+
[ACTION_ADD_BOOK_TO_LIBRARY]: { embeddable: BookEmbeddable };
100+
[ACTION_UNLINK_BOOK_FROM_LIBRARY]: { embeddable: BookEmbeddable };
89101
}
90102
}
91103

@@ -144,17 +156,25 @@ export class EmbeddableExamplesPlugin
144156
this.exampleEmbeddableFactories.getBookEmbeddableFactory = deps.embeddable.registerEmbeddableFactory(
145157
BOOK_EMBEDDABLE,
146158
new BookEmbeddableFactoryDefinition(async () => ({
147-
getAttributeService: (await core.getStartServices())[1].embeddable.getAttributeService,
159+
getAttributeService: (await core.getStartServices())[1].dashboard.getAttributeService,
148160
openModal: (await core.getStartServices())[0].overlays.openModal,
149161
}))
150162
);
151163

152164
const editBookAction = createEditBookAction(async () => ({
153-
getAttributeService: (await core.getStartServices())[1].embeddable.getAttributeService,
165+
getAttributeService: (await core.getStartServices())[1].dashboard.getAttributeService,
154166
openModal: (await core.getStartServices())[0].overlays.openModal,
155167
}));
156168
deps.uiActions.registerAction(editBookAction);
157169
deps.uiActions.attachAction(CONTEXT_MENU_TRIGGER, editBookAction.id);
170+
171+
const addBookToLibraryAction = createAddBookToLibraryAction();
172+
deps.uiActions.registerAction(addBookToLibraryAction);
173+
deps.uiActions.attachAction(CONTEXT_MENU_TRIGGER, addBookToLibraryAction.id);
174+
175+
const unlinkBookFromLibraryAction = createUnlinkBookFromLibraryAction();
176+
deps.uiActions.registerAction(unlinkBookFromLibraryAction);
177+
deps.uiActions.attachAction(CONTEXT_MENU_TRIGGER, unlinkBookFromLibraryAction.id);
158178
}
159179

160180
public start(

0 commit comments

Comments
 (0)