diff --git a/examples/embeddable_examples/kibana.json b/examples/embeddable_examples/kibana.json
index 771c19cfdbd3d..0ac40ae1889de 100644
--- a/examples/embeddable_examples/kibana.json
+++ b/examples/embeddable_examples/kibana.json
@@ -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"]
diff --git a/examples/embeddable_examples/public/book/add_book_to_library_action.tsx b/examples/embeddable_examples/public/book/add_book_to_library_action.tsx
new file mode 100644
index 0000000000000..b74a1d5642982
--- /dev/null
+++ b/examples/embeddable_examples/public/book/add_book_to_library_action.tsx
@@ -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);
+ },
+ });
diff --git a/examples/embeddable_examples/public/book/book_component.tsx b/examples/embeddable_examples/public/book/book_component.tsx
index 064e13c131a0a..e46487641b913 100644
--- a/examples/embeddable_examples/public/book/book_component.tsx
+++ b/examples/embeddable_examples/public/book/book_component.tsx
@@ -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';
@@ -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 (
-
+
{title ? (
- {wrapSearchTerms(title, search)},
+ {wrapSearchTerms(title, search)}
) : null}
{author ? (
- -{wrapSearchTerms(author, search)}
+ -{wrapSearchTerms(author, search)}
) : null}
@@ -76,7 +82,21 @@ export function BookEmbeddableComponentInner({ input: { search }, output: { attr
)}
-
+
+
+
+
+ {' '}
+
+ {byReference
+ ? i18n.translate('embeddableExamples.book.byReferenceLabel', {
+ defaultMessage: 'Book is By Reference',
+ })
+ : i18n.translate('embeddableExamples.book.byValueLabel', {
+ defaultMessage: 'Book is By Value',
+ })}
+
+
);
diff --git a/examples/embeddable_examples/public/book/book_embeddable.tsx b/examples/embeddable_examples/public/book/book_embeddable.tsx
index d49bd3280d97d..dd9418c0e8596 100644
--- a/examples/embeddable_examples/public/book/book_embeddable.tsx
+++ b/examples/embeddable_examples/public/book/book_embeddable.tsx
@@ -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;
@@ -59,7 +60,8 @@ function getHasMatch(search?: string, savedAttributes?: BookSavedObjectAttribute
);
}
-export class BookEmbeddable extends Embeddable {
+export class BookEmbeddable extends Embeddable
+ implements ReferenceOrValueEmbeddable {
public readonly type = BOOK_EMBEDDABLE;
private subscription: Subscription;
private node?: HTMLElement;
@@ -96,6 +98,18 @@ export class BookEmbeddable extends Embeddable {
+ return this.attributeService.inputIsRefType(input);
+ };
+
+ getInputAsValueType = async (): Promise => {
+ return this.attributeService.getInputAsValueType(this.input);
+ };
+
+ getInputAsRefType = async (): Promise => {
+ return this.attributeService.getInputAsRefType(this.input, { showSaveModal: true });
+ };
+
public render(node: HTMLElement) {
if (this.node) {
ReactDOM.unmountComponentAtNode(this.node);
@@ -113,6 +127,10 @@ export class BookEmbeddable extends Embeddable(this.type);
}
- return this.attributeService;
+ return this.attributeService!;
}
}
diff --git a/examples/embeddable_examples/public/book/edit_book_action.tsx b/examples/embeddable_examples/public/book/edit_book_action.tsx
index 222f70e0be60f..b31d69696598e 100644
--- a/examples/embeddable_examples/public/book/edit_book_action.tsx
+++ b/examples/embeddable_examples/public/book/edit_book_action.tsx
@@ -22,11 +22,7 @@ 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,
@@ -34,10 +30,11 @@ import {
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 {
diff --git a/examples/embeddable_examples/public/book/unlink_book_from_library_action.tsx b/examples/embeddable_examples/public/book/unlink_book_from_library_action.tsx
new file mode 100644
index 0000000000000..cef77092a642a
--- /dev/null
+++ b/examples/embeddable_examples/public/book/unlink_book_from_library_action.tsx
@@ -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);
+ },
+ });
diff --git a/examples/embeddable_examples/public/plugin.ts b/examples/embeddable_examples/public/plugin.ts
index 95f4f5b41e198..0c6ed1eb3be48 100644
--- a/examples/embeddable_examples/public/plugin.ts
+++ b/examples/embeddable_examples/public/plugin.ts
@@ -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;
@@ -66,6 +75,7 @@ export interface EmbeddableExamplesSetupDependencies {
export interface EmbeddableExamplesStartDependencies {
embeddable: EmbeddableStart;
+ dashboard: DashboardStart;
}
interface ExampleEmbeddableFactories {
@@ -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 };
}
}
@@ -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(
diff --git a/src/plugins/dashboard/public/attribute_service/attribute_service.tsx b/src/plugins/dashboard/public/attribute_service/attribute_service.tsx
new file mode 100644
index 0000000000000..c2f529fe399f3
--- /dev/null
+++ b/src/plugins/dashboard/public/attribute_service/attribute_service.tsx
@@ -0,0 +1,156 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import React from 'react';
+import { i18n } from '@kbn/i18n';
+import {
+ EmbeddableInput,
+ SavedObjectEmbeddableInput,
+ isSavedObjectEmbeddableInput,
+ IEmbeddable,
+} from '../embeddable_plugin';
+import {
+ SavedObjectsClientContract,
+ SimpleSavedObject,
+ I18nStart,
+ NotificationsStart,
+} from '../../../../core/public';
+import {
+ SavedObjectSaveModal,
+ showSaveModal,
+ OnSaveProps,
+ SaveResult,
+} from '../../../saved_objects/public';
+
+/**
+ * The attribute service is a shared, generic service that embeddables can use to provide the functionality
+ * required to fulfill the requirements of the ReferenceOrValueEmbeddable interface. The attribute_service
+ * can also be used as a higher level wrapper to transform an embeddable input shape that references a saved object
+ * into an embeddable input shape that contains that saved object's attributes by value.
+ */
+export class AttributeService<
+ SavedObjectAttributes extends { title: string },
+ ValType extends EmbeddableInput & { attributes: SavedObjectAttributes },
+ RefType extends SavedObjectEmbeddableInput
+> {
+ constructor(
+ private type: string,
+ private savedObjectsClient: SavedObjectsClientContract,
+ private i18nContext: I18nStart['Context'],
+ private toasts: NotificationsStart['toasts']
+ ) {}
+
+ public async unwrapAttributes(input: RefType | ValType): Promise {
+ if (this.inputIsRefType(input)) {
+ const savedObject: SimpleSavedObject = await this.savedObjectsClient.get<
+ SavedObjectAttributes
+ >(this.type, input.savedObjectId);
+ return savedObject.attributes;
+ }
+ return input.attributes;
+ }
+
+ public async wrapAttributes(
+ newAttributes: SavedObjectAttributes,
+ useRefType: boolean,
+ embeddable?: IEmbeddable
+ ): Promise> {
+ const savedObjectId =
+ embeddable && isSavedObjectEmbeddableInput(embeddable.getInput())
+ ? (embeddable.getInput() as SavedObjectEmbeddableInput).savedObjectId
+ : undefined;
+ if (!useRefType) {
+ return { attributes: newAttributes } as ValType;
+ } else {
+ try {
+ if (savedObjectId) {
+ await this.savedObjectsClient.update(this.type, savedObjectId, newAttributes);
+ return { savedObjectId } as RefType;
+ } else {
+ const savedItem = await this.savedObjectsClient.create(this.type, newAttributes);
+ return { savedObjectId: savedItem.id } as RefType;
+ }
+ } catch (error) {
+ this.toasts.addDanger({
+ title: i18n.translate('dashboard.attributeService.saveToLibraryError', {
+ defaultMessage: `Panel was not saved to the library. Error: {errorMessage}`,
+ values: {
+ errorMessage: error.message,
+ },
+ }),
+ 'data-test-subj': 'saveDashboardFailure',
+ });
+ return Promise.reject({ error });
+ }
+ }
+ }
+
+ inputIsRefType = (input: ValType | RefType): input is RefType => {
+ return isSavedObjectEmbeddableInput(input);
+ };
+
+ getInputAsValueType = async (input: ValType | RefType): Promise => {
+ if (!this.inputIsRefType(input)) {
+ return input;
+ }
+ const attributes = await this.unwrapAttributes(input);
+ return {
+ ...input,
+ savedObjectId: undefined,
+ attributes,
+ };
+ };
+
+ getInputAsRefType = async (
+ input: ValType | RefType,
+ saveOptions?: { showSaveModal: boolean } | { title: string }
+ ): Promise => {
+ if (this.inputIsRefType(input)) {
+ return input;
+ }
+
+ return new Promise((resolve, reject) => {
+ const onSave = async (props: OnSaveProps): Promise => {
+ try {
+ input.attributes.title = props.newTitle;
+ const wrappedInput = (await this.wrapAttributes(input.attributes, true)) as RefType;
+ resolve(wrappedInput);
+ return { id: wrappedInput.savedObjectId };
+ } catch (error) {
+ reject();
+ return { error };
+ }
+ };
+
+ if (saveOptions && (saveOptions as { showSaveModal: boolean }).showSaveModal) {
+ showSaveModal(
+ reject()}
+ title={input.attributes.title}
+ showCopyOnSave={false}
+ objectType={this.type}
+ showDescription={false}
+ />,
+ this.i18nContext
+ );
+ }
+ });
+ };
+}
diff --git a/src/plugins/dashboard/public/index.ts b/src/plugins/dashboard/public/index.ts
index dcfde67cd9f13..8a9954cc77a2e 100644
--- a/src/plugins/dashboard/public/index.ts
+++ b/src/plugins/dashboard/public/index.ts
@@ -40,6 +40,7 @@ export {
export { addEmbeddableToDashboardUrl } from './url_utils/url_helper';
export { SavedObjectDashboard } from './saved_dashboards';
export { SavedDashboardPanel } from './types';
+export { AttributeService } from './attribute_service/attribute_service';
export function plugin(initializerContext: PluginInitializerContext) {
return new DashboardPlugin(initializerContext);
diff --git a/src/plugins/dashboard/public/plugin.tsx b/src/plugins/dashboard/public/plugin.tsx
index f1319665d258b..3b0863a9f4651 100644
--- a/src/plugins/dashboard/public/plugin.tsx
+++ b/src/plugins/dashboard/public/plugin.tsx
@@ -34,7 +34,13 @@ import {
ScopedHistory,
} from 'src/core/public';
import { UsageCollectionSetup } from '../../usage_collection/public';
-import { CONTEXT_MENU_TRIGGER, EmbeddableSetup, EmbeddableStart } from '../../embeddable/public';
+import {
+ CONTEXT_MENU_TRIGGER,
+ EmbeddableSetup,
+ EmbeddableStart,
+ SavedObjectEmbeddableInput,
+ EmbeddableInput,
+} from '../../embeddable/public';
import { DataPublicPluginSetup, DataPublicPluginStart, esFilters } from '../../data/public';
import { SharePluginSetup, SharePluginStart, UrlGeneratorContract } from '../../share/public';
import { UiActionsSetup, UiActionsStart } from '../../ui_actions/public';
@@ -85,6 +91,7 @@ import { DashboardConstants } from './dashboard_constants';
import { addEmbeddableToDashboardUrl } from './url_utils/url_helper';
import { PlaceholderEmbeddableFactory } from './application/embeddable/placeholder';
import { UrlGeneratorState } from '../../share/public';
+import { AttributeService } from '.';
declare module '../../share/public' {
export interface UrlGeneratorStateMapping {
@@ -131,6 +138,13 @@ export interface DashboardStart {
dashboardUrlGenerator?: DashboardUrlGenerator;
dashboardFeatureFlagConfig: DashboardFeatureFlagConfig;
DashboardContainerByValueRenderer: ReturnType;
+ getAttributeService: <
+ A extends { title: string },
+ V extends EmbeddableInput & { attributes: A },
+ R extends SavedObjectEmbeddableInput
+ >(
+ type: string
+ ) => AttributeService;
}
declare module '../../../plugins/ui_actions/public' {
@@ -420,6 +434,13 @@ export class DashboardPlugin
DashboardContainerByValueRenderer: createDashboardContainerByValueRenderer({
factory: dashboardContainerFactory,
}),
+ getAttributeService: (type: string) =>
+ new AttributeService(
+ type,
+ core.savedObjects.client,
+ core.i18n.Context,
+ core.notifications.toasts
+ ),
};
}
diff --git a/src/plugins/embeddable/public/index.ts b/src/plugins/embeddable/public/index.ts
index fafbdda148de8..57253c1f741ab 100644
--- a/src/plugins/embeddable/public/index.ts
+++ b/src/plugins/embeddable/public/index.ts
@@ -28,7 +28,8 @@ export {
ACTION_EDIT_PANEL,
Adapters,
AddPanelAction,
- AttributeService,
+ ReferenceOrValueEmbeddable,
+ isReferenceOrValueEmbeddable,
ChartActionContext,
Container,
ContainerInput,
diff --git a/src/plugins/embeddable/public/lib/embeddables/attribute_service.ts b/src/plugins/embeddable/public/lib/embeddables/attribute_service.ts
deleted file mode 100644
index a33f592350d9a..0000000000000
--- a/src/plugins/embeddable/public/lib/embeddables/attribute_service.ts
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import { SavedObjectsClientContract } from '../../../../../core/public';
-import {
- SavedObjectEmbeddableInput,
- isSavedObjectEmbeddableInput,
- EmbeddableInput,
- IEmbeddable,
-} from '.';
-import { SimpleSavedObject } from '../../../../../core/public';
-
-export class AttributeService<
- SavedObjectAttributes,
- ValType extends EmbeddableInput & { attributes: SavedObjectAttributes },
- RefType extends SavedObjectEmbeddableInput
-> {
- constructor(private type: string, private savedObjectsClient: SavedObjectsClientContract) {}
-
- public async unwrapAttributes(input: RefType | ValType): Promise {
- if (isSavedObjectEmbeddableInput(input)) {
- const savedObject: SimpleSavedObject = await this.savedObjectsClient.get<
- SavedObjectAttributes
- >(this.type, input.savedObjectId);
- return savedObject.attributes;
- }
- return input.attributes;
- }
-
- public async wrapAttributes(
- newAttributes: SavedObjectAttributes,
- useRefType: boolean,
- embeddable?: IEmbeddable
- ): Promise> {
- const savedObjectId =
- embeddable && isSavedObjectEmbeddableInput(embeddable.getInput())
- ? (embeddable.getInput() as SavedObjectEmbeddableInput).savedObjectId
- : undefined;
-
- if (useRefType) {
- if (savedObjectId) {
- await this.savedObjectsClient.update(this.type, savedObjectId, newAttributes);
- return { savedObjectId } as RefType;
- } else {
- const savedItem = await this.savedObjectsClient.create(this.type, newAttributes);
- return { savedObjectId: savedItem.id } as RefType;
- }
- } else {
- return { attributes: newAttributes } as ValType;
- }
- }
-}
diff --git a/src/plugins/embeddable/public/lib/embeddables/index.ts b/src/plugins/embeddable/public/lib/embeddables/index.ts
index 06cb6e322acf3..5bab5ac27f3cc 100644
--- a/src/plugins/embeddable/public/lib/embeddables/index.ts
+++ b/src/plugins/embeddable/public/lib/embeddables/index.ts
@@ -25,5 +25,4 @@ export { ErrorEmbeddable, isErrorEmbeddable } from './error_embeddable';
export { withEmbeddableSubscription } from './with_subscription';
export { EmbeddableRoot } from './embeddable_root';
export * from './saved_object_embeddable';
-export { AttributeService } from './attribute_service';
export { EmbeddableRenderer, EmbeddableRendererProps } from './embeddable_renderer';
diff --git a/src/plugins/embeddable/public/lib/index.ts b/src/plugins/embeddable/public/lib/index.ts
index b757fa59a7f3a..aef4c33ee1078 100644
--- a/src/plugins/embeddable/public/lib/index.ts
+++ b/src/plugins/embeddable/public/lib/index.ts
@@ -25,3 +25,4 @@ export * from './triggers';
export * from './containers';
export * from './panel';
export * from './state_transfer';
+export * from './reference_or_value_embeddable';
diff --git a/src/plugins/embeddable/public/lib/reference_or_value_embeddable/index.ts b/src/plugins/embeddable/public/lib/reference_or_value_embeddable/index.ts
new file mode 100644
index 0000000000000..e9b8521a35ba5
--- /dev/null
+++ b/src/plugins/embeddable/public/lib/reference_or_value_embeddable/index.ts
@@ -0,0 +1,20 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+export { ReferenceOrValueEmbeddable, isReferenceOrValueEmbeddable } from './types';
diff --git a/src/plugins/embeddable/public/lib/reference_or_value_embeddable/types.ts b/src/plugins/embeddable/public/lib/reference_or_value_embeddable/types.ts
new file mode 100644
index 0000000000000..eaf5c94a09138
--- /dev/null
+++ b/src/plugins/embeddable/public/lib/reference_or_value_embeddable/types.ts
@@ -0,0 +1,56 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { EmbeddableInput, SavedObjectEmbeddableInput } from '..';
+
+/**
+ * Any embeddable that implements this interface will be able to use input that is
+ * either by reference (backed by a saved object) OR by value, (provided
+ * by the container).
+ * @public
+ */
+export interface ReferenceOrValueEmbeddable<
+ ValTypeInput extends EmbeddableInput = EmbeddableInput,
+ RefTypeInput extends SavedObjectEmbeddableInput = SavedObjectEmbeddableInput
+> {
+ /**
+ * determines whether the input is by value or by reference.
+ */
+ inputIsRefType: (input: ValTypeInput | RefTypeInput) => input is RefTypeInput;
+
+ /**
+ * Gets the embeddable's current input as its Value type
+ */
+ getInputAsValueType: () => Promise;
+
+ /**
+ * Gets the embeddable's current input as its Reference type
+ */
+ getInputAsRefType: () => Promise;
+}
+
+export function isReferenceOrValueEmbeddable(
+ incoming: unknown
+): incoming is ReferenceOrValueEmbeddable {
+ return (
+ !!(incoming as ReferenceOrValueEmbeddable).inputIsRefType &&
+ !!(incoming as ReferenceOrValueEmbeddable).getInputAsValueType &&
+ !!(incoming as ReferenceOrValueEmbeddable).getInputAsRefType
+ );
+}
diff --git a/src/plugins/embeddable/public/mocks.tsx b/src/plugins/embeddable/public/mocks.tsx
index 94aa980e446ca..fa79af909a427 100644
--- a/src/plugins/embeddable/public/mocks.tsx
+++ b/src/plugins/embeddable/public/mocks.tsx
@@ -97,7 +97,6 @@ const createStartContract = (): Start => {
getEmbeddableFactories: jest.fn(),
getEmbeddableFactory: jest.fn(),
EmbeddablePanel: jest.fn(),
- getAttributeService: jest.fn(),
getEmbeddablePanel: jest.fn(),
getStateTransfer: jest.fn(() => createEmbeddableStateTransferMock() as EmbeddableStateTransfer),
};
diff --git a/src/plugins/embeddable/public/plugin.tsx b/src/plugins/embeddable/public/plugin.tsx
index 319cbf8ec44b4..3cbd49279564f 100644
--- a/src/plugins/embeddable/public/plugin.tsx
+++ b/src/plugins/embeddable/public/plugin.tsx
@@ -37,10 +37,8 @@ import {
defaultEmbeddableFactoryProvider,
IEmbeddable,
EmbeddablePanel,
- SavedObjectEmbeddableInput,
} from './lib';
import { EmbeddableFactoryDefinition } from './lib/embeddables/embeddable_factory_definition';
-import { AttributeService } from './lib/embeddables/attribute_service';
import { EmbeddableStateTransfer } from './lib/state_transfer';
export interface EmbeddableSetupDependencies {
@@ -75,14 +73,6 @@ export interface EmbeddableStart {
embeddableFactoryId: string
) => EmbeddableFactory | undefined;
getEmbeddableFactories: () => IterableIterator;
- getAttributeService: <
- A,
- V extends EmbeddableInput & { attributes: A },
- R extends SavedObjectEmbeddableInput
- >(
- type: string
- ) => AttributeService;
-
EmbeddablePanel: EmbeddablePanelHOC;
getEmbeddablePanel: (stateTransfer?: EmbeddableStateTransfer) => EmbeddablePanelHOC;
getStateTransfer: (history?: ScopedHistory) => EmbeddableStateTransfer;
@@ -159,7 +149,6 @@ export class EmbeddablePublicPlugin implements Plugin new AttributeService(type, core.savedObjects.client),
getStateTransfer: (history?: ScopedHistory) => {
return history
? new EmbeddableStateTransfer(core.application.navigateToApp, history)