[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
This commit is contained in:
parent
5d82ac15cb
commit
2e5140d67b
|
@ -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"]
|
||||
|
|
|
@ -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);
|
||||
},
|
||||
});
|
|
@ -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 (
|
||||
<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}
|
||||
|
@ -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>
|
||||
);
|
||||
|
|
|
@ -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<BookEmbeddableInput, BookEmbeddableOutput> {
|
||||
export class BookEmbeddable extends Embeddable<BookEmbeddableInput, BookEmbeddableOutput>
|
||||
implements ReferenceOrValueEmbeddable<BookByValueInput, BookByReferenceInput> {
|
||||
public readonly type = BOOK_EMBEDDABLE;
|
||||
private subscription: Subscription;
|
||||
private node?: HTMLElement;
|
||||
|
@ -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);
|
||||
|
@ -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();
|
||||
|
|
|
@ -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 {
|
||||
|
@ -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'];
|
||||
}
|
||||
|
||||
|
@ -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',
|
||||
|
@ -122,6 +131,6 @@ export class BookEmbeddableFactoryDefinition
|
|||
BookByReferenceInput
|
||||
>(this.type);
|
||||
}
|
||||
return this.attributeService;
|
||||
return this.attributeService!;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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);
|
||||
},
|
||||
});
|
|
@ -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(
|
||||
|
|
|
@ -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<SavedObjectAttributes> {
|
||||
if (this.inputIsRefType(input)) {
|
||||
const savedObject: SimpleSavedObject<SavedObjectAttributes> = 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<Omit<ValType | RefType, 'id'>> {
|
||||
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<ValType> => {
|
||||
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<RefType> => {
|
||||
if (this.inputIsRefType(input)) {
|
||||
return input;
|
||||
}
|
||||
|
||||
return new Promise<RefType>((resolve, reject) => {
|
||||
const onSave = async (props: OnSaveProps): Promise<SaveResult> => {
|
||||
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(
|
||||
<SavedObjectSaveModal
|
||||
onSave={onSave}
|
||||
onClose={() => reject()}
|
||||
title={input.attributes.title}
|
||||
showCopyOnSave={false}
|
||||
objectType={this.type}
|
||||
showDescription={false}
|
||||
/>,
|
||||
this.i18nContext
|
||||
);
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -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<typeof createDashboardContainerByValueRenderer>;
|
||||
getAttributeService: <
|
||||
A extends { title: string },
|
||||
V extends EmbeddableInput & { attributes: A },
|
||||
R extends SavedObjectEmbeddableInput
|
||||
>(
|
||||
type: string
|
||||
) => AttributeService<A, V, R>;
|
||||
}
|
||||
|
||||
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
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -28,7 +28,8 @@ export {
|
|||
ACTION_EDIT_PANEL,
|
||||
Adapters,
|
||||
AddPanelAction,
|
||||
AttributeService,
|
||||
ReferenceOrValueEmbeddable,
|
||||
isReferenceOrValueEmbeddable,
|
||||
ChartActionContext,
|
||||
Container,
|
||||
ContainerInput,
|
||||
|
|
|
@ -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<SavedObjectAttributes> {
|
||||
if (isSavedObjectEmbeddableInput(input)) {
|
||||
const savedObject: SimpleSavedObject<SavedObjectAttributes> = 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<Omit<ValType | RefType, 'id'>> {
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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';
|
||||
|
|
|
@ -25,3 +25,4 @@ export * from './triggers';
|
|||
export * from './containers';
|
||||
export * from './panel';
|
||||
export * from './state_transfer';
|
||||
export * from './reference_or_value_embeddable';
|
||||
|
|
|
@ -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';
|
|
@ -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<ValTypeInput>;
|
||||
|
||||
/**
|
||||
* Gets the embeddable's current input as its Reference type
|
||||
*/
|
||||
getInputAsRefType: () => Promise<RefTypeInput>;
|
||||
}
|
||||
|
||||
export function isReferenceOrValueEmbeddable(
|
||||
incoming: unknown
|
||||
): incoming is ReferenceOrValueEmbeddable {
|
||||
return (
|
||||
!!(incoming as ReferenceOrValueEmbeddable).inputIsRefType &&
|
||||
!!(incoming as ReferenceOrValueEmbeddable).getInputAsValueType &&
|
||||
!!(incoming as ReferenceOrValueEmbeddable).getInputAsRefType
|
||||
);
|
||||
}
|
|
@ -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),
|
||||
};
|
||||
|
|
|
@ -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<I, O, E> | undefined;
|
||||
getEmbeddableFactories: () => IterableIterator<EmbeddableFactory>;
|
||||
getAttributeService: <
|
||||
A,
|
||||
V extends EmbeddableInput & { attributes: A },
|
||||
R extends SavedObjectEmbeddableInput
|
||||
>(
|
||||
type: string
|
||||
) => AttributeService<A, V, R>;
|
||||
|
||||
EmbeddablePanel: EmbeddablePanelHOC;
|
||||
getEmbeddablePanel: (stateTransfer?: EmbeddableStateTransfer) => EmbeddablePanelHOC;
|
||||
getStateTransfer: (history?: ScopedHistory) => EmbeddableStateTransfer;
|
||||
|
@ -159,7 +149,6 @@ export class EmbeddablePublicPlugin implements Plugin<EmbeddableSetup, Embeddabl
|
|||
return {
|
||||
getEmbeddableFactory: this.getEmbeddableFactory,
|
||||
getEmbeddableFactories: this.getEmbeddableFactories,
|
||||
getAttributeService: (type: string) => new AttributeService(type, core.savedObjects.client),
|
||||
getStateTransfer: (history?: ScopedHistory) => {
|
||||
return history
|
||||
? new EmbeddableStateTransfer(core.application.navigateToApp, history)
|
||||
|
|
Loading…
Reference in a new issue