diff --git a/examples/embeddable_examples/kibana.json b/examples/embeddable_examples/kibana.json index 0ac40ae1889de..223b8c55a5fde 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", "dashboard"], + "requiredPlugins": ["embeddable", "uiActions", "dashboard", "savedObjects"], "optionalPlugins": [], "extraPublicDirs": ["public/todo", "public/hello_world", "public/todo/todo_ref_embeddable"], "requiredBundles": ["kibanaReact"] diff --git a/examples/embeddable_examples/public/book/book_embeddable_factory.tsx b/examples/embeddable_examples/public/book/book_embeddable_factory.tsx index 292261ee16c59..a535552282150 100644 --- a/examples/embeddable_examples/public/book/book_embeddable_factory.tsx +++ b/examples/embeddable_examples/public/book/book_embeddable_factory.tsx @@ -33,12 +33,19 @@ import { BookEmbeddableOutput, } from './book_embeddable'; import { CreateEditBookComponent } from './create_edit_book_component'; -import { OverlayStart } from '../../../../src/core/public'; +import { + OverlayStart, + SavedObjectsClientContract, + SimpleSavedObject, +} from '../../../../src/core/public'; import { DashboardStart, AttributeService } from '../../../../src/plugins/dashboard/public'; +import { checkForDuplicateTitle, OnSaveProps } from '../../../../src/plugins/saved_objects/public'; interface StartServices { getAttributeService: DashboardStart['getAttributeService']; openModal: OverlayStart['openModal']; + savedObjectsClient: SavedObjectsClientContract; + overlays: OverlayStart; } export type BookEmbeddableFactory = EmbeddableFactory< @@ -117,11 +124,55 @@ export class BookEmbeddableFactoryDefinition }); } + private async unwrapMethod(savedObjectId: string): Promise { + const { savedObjectsClient } = await this.getStartServices(); + const savedObject: SimpleSavedObject = await savedObjectsClient.get< + BookSavedObjectAttributes + >(this.type, savedObjectId); + return { ...savedObject.attributes }; + } + + private async saveMethod( + type: string, + attributes: BookSavedObjectAttributes, + savedObjectId?: string + ) { + const { savedObjectsClient } = await this.getStartServices(); + if (savedObjectId) { + return savedObjectsClient.update(type, savedObjectId, attributes); + } + return savedObjectsClient.create(type, attributes); + } + + private async checkForDuplicateTitleMethod(props: OnSaveProps): Promise { + const start = await this.getStartServices(); + const { savedObjectsClient, overlays } = start; + return checkForDuplicateTitle( + { + title: props.newTitle, + copyOnSave: false, + lastSavedTitle: '', + getEsType: () => this.type, + getDisplayName: this.getDisplayName || (() => this.type), + }, + props.isTitleDuplicateConfirmed, + props.onTitleDuplicate, + { + savedObjectsClient, + overlays, + } + ); + } + private async getAttributeService() { if (!this.attributeService) { - this.attributeService = await (await this.getStartServices()).getAttributeService< + this.attributeService = (await this.getStartServices()).getAttributeService< BookSavedObjectAttributes - >(this.type); + >(this.type, { + saveMethod: this.saveMethod.bind(this), + unwrapMethod: this.unwrapMethod.bind(this), + checkForDuplicateTitle: this.checkForDuplicateTitleMethod.bind(this), + }); } 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 3541ace1e5e7e..77035b6887734 100644 --- a/examples/embeddable_examples/public/book/edit_book_action.tsx +++ b/examples/embeddable_examples/public/book/edit_book_action.tsx @@ -31,10 +31,13 @@ import { } from './book_embeddable'; import { CreateEditBookComponent } from './create_edit_book_component'; import { DashboardStart } from '../../../../src/plugins/dashboard/public'; +import { OnSaveProps } from '../../../../src/plugins/saved_objects/public'; +import { SavedObjectsClientContract } from '../../../../src/core/target/types/public/saved_objects'; interface StartServices { openModal: OverlayStart['openModal']; getAttributeService: DashboardStart['getAttributeService']; + savedObjectsClient: SavedObjectsClientContract; } interface ActionContext { @@ -56,8 +59,24 @@ export const createEditBookAction = (getStartServices: () => Promise { - const { openModal, getAttributeService } = await getStartServices(); - const attributeService = getAttributeService(BOOK_SAVED_OBJECT); + const { openModal, getAttributeService, savedObjectsClient } = await getStartServices(); + const attributeService = getAttributeService(BOOK_SAVED_OBJECT, { + saveMethod: async ( + type: string, + attributes: BookSavedObjectAttributes, + savedObjectId?: string + ) => { + if (savedObjectId) { + return savedObjectsClient.update(type, savedObjectId, attributes); + } + return savedObjectsClient.create(type, attributes); + }, + checkForDuplicateTitle: (props: OnSaveProps) => { + return new Promise(() => { + return true; + }); + }, + }); const onSave = async (attributes: BookSavedObjectAttributes, useRefType: boolean) => { const newInput = await attributeService.wrapAttributes( attributes, diff --git a/examples/embeddable_examples/public/plugin.ts b/examples/embeddable_examples/public/plugin.ts index 0c6ed1eb3be48..6d1b119e741bd 100644 --- a/examples/embeddable_examples/public/plugin.ts +++ b/examples/embeddable_examples/public/plugin.ts @@ -22,7 +22,7 @@ import { EmbeddableStart, CONTEXT_MENU_TRIGGER, } from '../../../src/plugins/embeddable/public'; -import { Plugin, CoreSetup, CoreStart } from '../../../src/core/public'; +import { Plugin, CoreSetup, CoreStart, SavedObjectsClient } from '../../../src/core/public'; import { HelloWorldEmbeddableFactory, HELLO_WORLD_EMBEDDABLE, @@ -76,6 +76,7 @@ export interface EmbeddableExamplesSetupDependencies { export interface EmbeddableExamplesStartDependencies { embeddable: EmbeddableStart; dashboard: DashboardStart; + savedObjectsClient: SavedObjectsClient; } interface ExampleEmbeddableFactories { @@ -158,12 +159,15 @@ export class EmbeddableExamplesPlugin new BookEmbeddableFactoryDefinition(async () => ({ getAttributeService: (await core.getStartServices())[1].dashboard.getAttributeService, openModal: (await core.getStartServices())[0].overlays.openModal, + savedObjectsClient: (await core.getStartServices())[0].savedObjects.client, + overlays: (await core.getStartServices())[0].overlays, })) ); const editBookAction = createEditBookAction(async () => ({ getAttributeService: (await core.getStartServices())[1].dashboard.getAttributeService, openModal: (await core.getStartServices())[0].overlays.openModal, + savedObjectsClient: (await core.getStartServices())[0].savedObjects.client, })); deps.uiActions.registerAction(editBookAction); deps.uiActions.attachAction(CONTEXT_MENU_TRIGGER, editBookAction.id); diff --git a/src/plugins/dashboard/public/attribute_service/attribute_service.mock.tsx b/src/plugins/dashboard/public/attribute_service/attribute_service.mock.tsx index 321a53361fc7a..09d6f5b4f1e0d 100644 --- a/src/plugins/dashboard/public/attribute_service/attribute_service.mock.tsx +++ b/src/plugins/dashboard/public/attribute_service/attribute_service.mock.tsx @@ -31,19 +31,16 @@ export const mockAttributeService = < R extends SavedObjectEmbeddableInput = SavedObjectEmbeddableInput >( type: string, - options?: AttributeServiceOptions, + options: AttributeServiceOptions, customCore?: jest.Mocked ): AttributeService => { const core = customCore ? customCore : coreMock.createStart(); - const service = new AttributeService( + return new AttributeService( type, jest.fn(), - core.savedObjects.client, - core.overlays, core.i18n.Context, core.notifications.toasts, - jest.fn().mockReturnValue(() => ({ getDisplayName: () => type })), - options + options, + jest.fn().mockReturnValue(() => ({ getDisplayName: () => type })) ); - return service; }; diff --git a/src/plugins/dashboard/public/attribute_service/attribute_service.test.ts b/src/plugins/dashboard/public/attribute_service/attribute_service.test.ts index ae8f034aec687..d7368b299c411 100644 --- a/src/plugins/dashboard/public/attribute_service/attribute_service.test.ts +++ b/src/plugins/dashboard/public/attribute_service/attribute_service.test.ts @@ -20,6 +20,7 @@ import { ATTRIBUTE_SERVICE_KEY } from './attribute_service'; import { mockAttributeService } from './attribute_service.mock'; import { coreMock } from '../../../../core/public/mocks'; +import { OnSaveProps } from '../../../saved_objects/public/save_modal'; interface TestAttributes { title: string; @@ -37,6 +38,30 @@ describe('attributeService', () => { let attributes: TestAttributes; let byValueInput: TestByValueInput; let byReferenceInput: { id: string; savedObjectId: string }; + const defaultSaveMethod = ( + type: string, + testAttributes: TestAttributes, + savedObjectId?: string + ): Promise<{ id: string }> => { + return new Promise(() => { + return { id: '123' }; + }); + }; + const defaultUnwrapMethod = (savedObjectId: string): Promise => { + return new Promise(() => { + return { ...attributes }; + }); + }; + const defaultCheckForDuplicateTitle = (props: OnSaveProps): Promise => { + return new Promise(() => { + return true; + }); + }; + const options = { + saveMethod: defaultSaveMethod, + unwrapMethod: defaultUnwrapMethod, + checkForDuplicateTitle: defaultCheckForDuplicateTitle, + }; beforeEach(() => { attributes = { @@ -55,9 +80,10 @@ describe('attributeService', () => { }); describe('determining input type', () => { - const defaultAttributeService = mockAttributeService(defaultTestType); + const defaultAttributeService = mockAttributeService(defaultTestType, options); const customAttributeService = mockAttributeService( - defaultTestType + defaultTestType, + options ); it('can determine input type given default types', () => { @@ -85,39 +111,32 @@ describe('attributeService', () => { }); describe('unwrapping attributes', () => { - it('can unwrap all default attributes when given reference type input', async () => { - const core = coreMock.createStart(); - core.savedObjects.client.get = jest.fn().mockResolvedValueOnce({ - attributes, + it('does not throw error when given reference type input with no unwrap method', async () => { + const attributeService = mockAttributeService(defaultTestType, { + saveMethod: defaultSaveMethod, + checkForDuplicateTitle: jest.fn(), }); - const attributeService = mockAttributeService( - defaultTestType, - undefined, - core - ); - expect(await attributeService.unwrapAttributes(byReferenceInput)).toEqual(attributes); + expect(await attributeService.unwrapAttributes(byReferenceInput)).toEqual(byReferenceInput); }); it('returns attributes when when given value type input', async () => { - const attributeService = mockAttributeService(defaultTestType); + const attributeService = mockAttributeService(defaultTestType, options); expect(await attributeService.unwrapAttributes(byValueInput)).toEqual(attributes); }); it('runs attributes through a custom unwrap method', async () => { - const core = coreMock.createStart(); - core.savedObjects.client.get = jest.fn().mockResolvedValueOnce({ - attributes, - }); - const attributeService = mockAttributeService( - defaultTestType, - { - customUnwrapMethod: (savedObject) => ({ - ...savedObject.attributes, - testAttr2: { array: [1, 2, 3, 4, 5], testAttr3: 'kibanana' }, - }), + const attributeService = mockAttributeService(defaultTestType, { + saveMethod: defaultSaveMethod, + unwrapMethod: (savedObjectId) => { + return new Promise((resolve) => { + return resolve({ + ...attributes, + testAttr2: { array: [1, 2, 3, 4, 5], testAttr3: 'kibanana' }, + }); + }); }, - core - ); + checkForDuplicateTitle: jest.fn(), + }); expect(await attributeService.unwrapAttributes(byReferenceInput)).toEqual({ ...attributes, testAttr2: { array: [1, 2, 3, 4, 5], testAttr3: 'kibanana' }, @@ -127,52 +146,40 @@ describe('attributeService', () => { describe('wrapping attributes', () => { it('returns given attributes when use ref type is false', async () => { - const attributeService = mockAttributeService(defaultTestType); + const attributeService = mockAttributeService(defaultTestType, options); expect(await attributeService.wrapAttributes(attributes, false)).toEqual({ attributes }); }); - it('updates existing saved object with new attributes when given id', async () => { + it('calls saveMethod with appropriate parameters', async () => { const core = coreMock.createStart(); + const saveMethod = jest.fn(); + saveMethod.mockReturnValueOnce({}); const attributeService = mockAttributeService( defaultTestType, - undefined, + { + saveMethod, + unwrapMethod: defaultUnwrapMethod, + checkForDuplicateTitle: defaultCheckForDuplicateTitle, + }, core ); expect(await attributeService.wrapAttributes(attributes, true, byReferenceInput)).toEqual( byReferenceInput ); - expect(core.savedObjects.client.update).toHaveBeenCalledWith( - defaultTestType, - '123', - attributes - ); - }); - - it('creates new saved object with attributes when given no id', async () => { - const core = coreMock.createStart(); - core.savedObjects.client.create = jest.fn().mockResolvedValueOnce({ - id: '678', - }); - const attributeService = mockAttributeService( - defaultTestType, - undefined, - core - ); - expect(await attributeService.wrapAttributes(attributes, true)).toEqual({ - savedObjectId: '678', - }); - expect(core.savedObjects.client.create).toHaveBeenCalledWith(defaultTestType, attributes); + expect(saveMethod).toHaveBeenCalledWith(defaultTestType, attributes, '123'); }); it('uses custom save method when given an id', async () => { - const customSaveMethod = jest.fn().mockReturnValue({ id: '123' }); + const saveMethod = jest.fn().mockReturnValue({ id: '123' }); const attributeService = mockAttributeService(defaultTestType, { - customSaveMethod, + saveMethod, + unwrapMethod: defaultUnwrapMethod, + checkForDuplicateTitle: defaultCheckForDuplicateTitle, }); expect(await attributeService.wrapAttributes(attributes, true, byReferenceInput)).toEqual( byReferenceInput ); - expect(customSaveMethod).toHaveBeenCalledWith( + expect(saveMethod).toHaveBeenCalledWith( defaultTestType, attributes, byReferenceInput.savedObjectId @@ -180,14 +187,16 @@ describe('attributeService', () => { }); it('uses custom save method given no id', async () => { - const customSaveMethod = jest.fn().mockReturnValue({ id: '678' }); + const saveMethod = jest.fn().mockReturnValue({ id: '678' }); const attributeService = mockAttributeService(defaultTestType, { - customSaveMethod, + saveMethod, + unwrapMethod: defaultUnwrapMethod, + checkForDuplicateTitle: defaultCheckForDuplicateTitle, }); expect(await attributeService.wrapAttributes(attributes, true)).toEqual({ savedObjectId: '678', }); - expect(customSaveMethod).toHaveBeenCalledWith(defaultTestType, attributes, undefined); + expect(saveMethod).toHaveBeenCalledWith(defaultTestType, attributes, undefined); }); }); }); diff --git a/src/plugins/dashboard/public/attribute_service/attribute_service.tsx b/src/plugins/dashboard/public/attribute_service/attribute_service.tsx index 7499a6fced72a..b46226ec4ab02 100644 --- a/src/plugins/dashboard/public/attribute_service/attribute_service.tsx +++ b/src/plugins/dashboard/public/attribute_service/attribute_service.tsx @@ -27,22 +27,10 @@ import { IEmbeddable, Container, EmbeddableStart, - EmbeddableFactory, EmbeddableFactoryNotFoundError, } from '../embeddable_plugin'; -import { - SavedObjectsClientContract, - SimpleSavedObject, - I18nStart, - NotificationsStart, - OverlayStart, -} from '../../../../core/public'; -import { - SavedObjectSaveModal, - OnSaveProps, - SaveResult, - checkForDuplicateTitle, -} from '../../../saved_objects/public'; +import { I18nStart, NotificationsStart } from '../../../../core/public'; +import { SavedObjectSaveModal, OnSaveProps, SaveResult } from '../../../saved_objects/public'; /** * The attribute service is a shared, generic service that embeddables can use to provide the functionality @@ -53,12 +41,13 @@ import { export const ATTRIBUTE_SERVICE_KEY = 'attributes'; export interface AttributeServiceOptions { - customSaveMethod?: ( + saveMethod: ( type: string, attributes: A, savedObjectId?: string ) => Promise<{ id?: string } | { error: Error }>; - customUnwrapMethod?: (savedObject: SimpleSavedObject) => A; + checkForDuplicateTitle: (props: OnSaveProps) => Promise; + unwrapMethod?: (savedObjectId: string) => Promise; } export class AttributeService< @@ -68,38 +57,37 @@ export class AttributeService< } = EmbeddableInput & { [ATTRIBUTE_SERVICE_KEY]: SavedObjectAttributes }, RefType extends SavedObjectEmbeddableInput = SavedObjectEmbeddableInput > { - private embeddableFactory?: EmbeddableFactory; - constructor( private type: string, private showSaveModal: ( saveModal: React.ReactElement, I18nContext: I18nStart['Context'] ) => void, - private savedObjectsClient: SavedObjectsClientContract, - private overlays: OverlayStart, private i18nContext: I18nStart['Context'], private toasts: NotificationsStart['toasts'], - getEmbeddableFactory?: EmbeddableStart['getEmbeddableFactory'], - private options?: AttributeServiceOptions + private options: AttributeServiceOptions, + getEmbeddableFactory?: EmbeddableStart['getEmbeddableFactory'] ) { if (getEmbeddableFactory) { const factory = getEmbeddableFactory(this.type); if (!factory) { throw new EmbeddableFactoryNotFoundError(this.type); } - this.embeddableFactory = factory; } } + private async defaultUnwrapMethod(input: RefType): Promise { + return new Promise((resolve) => { + // @ts-ignore + return resolve({ ...input }); + }); + } + public async unwrapAttributes(input: RefType | ValType): Promise { if (this.inputIsRefType(input)) { - const savedObject: SimpleSavedObject = await this.savedObjectsClient.get< - SavedObjectAttributes - >(this.type, input.savedObjectId); - return this.options?.customUnwrapMethod - ? this.options?.customUnwrapMethod(savedObject) - : { ...savedObject.attributes }; + return this.options.unwrapMethod + ? await this.options.unwrapMethod(input.savedObjectId) + : await this.defaultUnwrapMethod(input); } return input[ATTRIBUTE_SERVICE_KEY]; } @@ -118,25 +106,11 @@ export class AttributeService< return { [ATTRIBUTE_SERVICE_KEY]: newAttributes } as ValType; } try { - if (this.options?.customSaveMethod) { - const savedItem = await this.options.customSaveMethod( - this.type, - newAttributes, - savedObjectId - ); - if ('id' in savedItem) { - return { ...originalInput, savedObjectId: savedItem.id } as RefType; - } - return { ...originalInput } as RefType; - } - - if (savedObjectId) { - await this.savedObjectsClient.update(this.type, savedObjectId, newAttributes); - return { ...originalInput, savedObjectId } as RefType; + const savedItem = await this.options.saveMethod(this.type, newAttributes, savedObjectId); + if ('id' in savedItem) { + return { ...originalInput, savedObjectId: savedItem.id } as RefType; } - - const savedItem = await this.savedObjectsClient.create(this.type, newAttributes); - return { ...originalInput, savedObjectId: savedItem.id } as RefType; + return { ...originalInput } as RefType; } catch (error) { this.toasts.addDanger({ title: i18n.translate('dashboard.attributeService.saveToLibraryError', { @@ -181,21 +155,7 @@ export class AttributeService< } return new Promise((resolve, reject) => { const onSave = async (props: OnSaveProps): Promise => { - await checkForDuplicateTitle( - { - title: props.newTitle, - copyOnSave: false, - lastSavedTitle: '', - getEsType: () => this.type, - getDisplayName: this.embeddableFactory?.getDisplayName || (() => this.type), - }, - props.isTitleDuplicateConfirmed, - props.onTitleDuplicate, - { - savedObjectsClient: this.savedObjectsClient, - overlays: this.overlays, - } - ); + await this.options.checkForDuplicateTitle(props); try { const newAttributes = { ...input[ATTRIBUTE_SERVICE_KEY] }; newAttributes.title = props.newTitle; diff --git a/src/plugins/dashboard/public/plugin.tsx b/src/plugins/dashboard/public/plugin.tsx index eadb3cd207e4d..b1437aeb3471f 100644 --- a/src/plugins/dashboard/public/plugin.tsx +++ b/src/plugins/dashboard/public/plugin.tsx @@ -164,7 +164,7 @@ export interface DashboardStart { R extends SavedObjectEmbeddableInput = SavedObjectEmbeddableInput >( type: string, - options?: AttributeServiceOptions + options: AttributeServiceOptions ) => AttributeService; } @@ -491,12 +491,10 @@ export class DashboardPlugin new AttributeService( type, showSaveModal, - core.savedObjects.client, - core.overlays, core.i18n.Context, core.notifications.toasts, - embeddable.getEmbeddableFactory, - options + options, + embeddable.getEmbeddableFactory ), }; } diff --git a/src/plugins/visualizations/public/embeddable/visualize_embeddable_factory.tsx b/src/plugins/visualizations/public/embeddable/visualize_embeddable_factory.tsx index 75e53e8e92dbe..87f78f5639ff0 100644 --- a/src/plugins/visualizations/public/embeddable/visualize_embeddable_factory.tsx +++ b/src/plugins/visualizations/public/embeddable/visualize_embeddable_factory.tsx @@ -18,7 +18,7 @@ */ import { i18n } from '@kbn/i18n'; -import { SavedObjectMetaData } from 'src/plugins/saved_objects/public'; +import { SavedObjectMetaData, OnSaveProps } from 'src/plugins/saved_objects/public'; import { first } from 'rxjs/operators'; import { SavedObjectAttributes } from '../../../../core/public'; import { @@ -51,6 +51,7 @@ import { StartServicesGetter } from '../../../kibana_utils/public'; import { VisualizationsStartDeps } from '../plugin'; import { VISUALIZE_ENABLE_LABS_SETTING } from '../../common/constants'; import { AttributeService } from '../../../dashboard/public'; +import { checkForDuplicateTitle } from '../../../saved_objects/public'; interface VisualizationAttributes extends SavedObjectAttributes { visState: string; @@ -58,7 +59,7 @@ interface VisualizationAttributes extends SavedObjectAttributes { export interface VisualizeEmbeddableFactoryDeps { start: StartServicesGetter< - Pick + Pick >; } @@ -129,7 +130,10 @@ export class VisualizeEmbeddableFactory VisualizeSavedObjectAttributes, VisualizeByValueInput, VisualizeByReferenceInput - >(this.type, { customSaveMethod: this.onSave }); + >(this.type, { + saveMethod: this.saveMethod.bind(this), + checkForDuplicateTitle: this.checkTitle.bind(this), + }); } return this.attributeService!; } @@ -183,7 +187,7 @@ export class VisualizeEmbeddableFactory } } - private async onSave( + private async saveMethod( type: string, attributes: VisualizeSavedObjectAttributes ): Promise<{ id: string }> { @@ -225,4 +229,24 @@ export class VisualizeEmbeddableFactory throw error; } } + + public async checkTitle(props: OnSaveProps): Promise { + const savedObjectsClient = await this.deps.start().core.savedObjects.client; + const overlays = await this.deps.start().core.overlays; + return checkForDuplicateTitle( + { + title: props.newTitle, + copyOnSave: false, + lastSavedTitle: '', + getEsType: () => this.type, + getDisplayName: this.getDisplayName || (() => this.type), + }, + props.isTitleDuplicateConfirmed, + props.onTitleDuplicate, + { + savedObjectsClient, + overlays, + } + ); + } } diff --git a/src/plugins/visualizations/public/mocks.ts b/src/plugins/visualizations/public/mocks.ts index 646acc49a6a83..90e4936a58b45 100644 --- a/src/plugins/visualizations/public/mocks.ts +++ b/src/plugins/visualizations/public/mocks.ts @@ -72,6 +72,7 @@ const createInstance = async () => { embeddable: embeddablePluginMock.createStartContract(), dashboard: dashboardPluginMock.createStartContract(), getAttributeService: jest.fn(), + savedObjectsClient: coreMock.createStart().savedObjects.client, }); return { diff --git a/src/plugins/visualizations/public/plugin.ts b/src/plugins/visualizations/public/plugin.ts index 0ba80887b513f..37a9972983421 100644 --- a/src/plugins/visualizations/public/plugin.ts +++ b/src/plugins/visualizations/public/plugin.ts @@ -25,6 +25,7 @@ import { CoreStart, Plugin, ApplicationStart, + SavedObjectsClientContract, } from '../../../core/public'; import { TypesService, TypesSetup, TypesStart } from './vis_types'; import { @@ -112,6 +113,7 @@ export interface VisualizationsStartDeps { application: ApplicationStart; dashboard: DashboardStart; getAttributeService: DashboardStart['getAttributeService']; + savedObjectsClient: SavedObjectsClientContract; } /** diff --git a/x-pack/plugins/lens/public/app_plugin/app.test.tsx b/x-pack/plugins/lens/public/app_plugin/app.test.tsx index 24114e2b31518..14e8b33b96ad1 100644 --- a/x-pack/plugins/lens/public/app_plugin/app.test.tsx +++ b/x-pack/plugins/lens/public/app_plugin/app.test.tsx @@ -145,8 +145,9 @@ describe('Lens App', () => { >( DOC_TYPE, { - customSaveMethod: jest.fn(), - customUnwrapMethod: jest.fn(), + saveMethod: jest.fn(), + unwrapMethod: jest.fn(), + checkForDuplicateTitle: jest.fn(), }, core ); diff --git a/x-pack/plugins/lens/public/editor_frame_service/embeddable/embeddable.test.tsx b/x-pack/plugins/lens/public/editor_frame_service/embeddable/embeddable.test.tsx index 151f85e817c70..4966fce590542 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/embeddable/embeddable.test.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/embeddable/embeddable.test.tsx @@ -27,6 +27,7 @@ import { coreMock, httpServiceMock } from '../../../../../../src/core/public/moc import { IBasePath } from '../../../../../../src/core/public'; import { AttributeService } from '../../../../../../src/plugins/dashboard/public'; import { LensAttributeService } from '../../lens_attribute_service'; +import { OnSaveProps } from '../../../../../../src/plugins/saved_objects/public/save_modal'; jest.mock('../../../../../../src/plugins/inspector/public/', () => ({ isAvailable: false, @@ -44,6 +45,30 @@ const savedVis: Document = { title: 'My title', visualizationType: '', }; +const defaultSaveMethod = ( + type: string, + testAttributes: LensSavedObjectAttributes, + savedObjectId?: string +): Promise<{ id: string }> => { + return new Promise(() => { + return { id: '123' }; + }); +}; +const defaultUnwrapMethod = (savedObjectId: string): Promise => { + return new Promise(() => { + return { ...savedVis }; + }); +}; +const defaultCheckForDuplicateTitle = (props: OnSaveProps): Promise => { + return new Promise(() => { + return true; + }); +}; +const options = { + saveMethod: defaultSaveMethod, + unwrapMethod: defaultUnwrapMethod, + checkForDuplicateTitle: defaultCheckForDuplicateTitle, +}; const attributeServiceMockFromSavedVis = (document: Document): LensAttributeService => { const core = coreMock.createStart(); @@ -51,14 +76,7 @@ const attributeServiceMockFromSavedVis = (document: Document): LensAttributeServ LensSavedObjectAttributes, LensByValueInput, LensByReferenceInput - >( - 'lens', - jest.fn(), - core.savedObjects.client, - core.overlays, - core.i18n.Context, - core.notifications.toasts - ); + >('lens', jest.fn(), core.i18n.Context, core.notifications.toasts, options); service.unwrapAttributes = jest.fn((input: LensByValueInput | LensByReferenceInput) => { return Promise.resolve({ ...document } as LensSavedObjectAttributes); }); diff --git a/x-pack/plugins/lens/public/lens_attribute_service.ts b/x-pack/plugins/lens/public/lens_attribute_service.ts index 3c43fd98cceb4..9e1ce535d6675 100644 --- a/x-pack/plugins/lens/public/lens_attribute_service.ts +++ b/x-pack/plugins/lens/public/lens_attribute_service.ts @@ -13,6 +13,7 @@ import { LensByReferenceInput, } from './editor_frame_service/embeddable/embeddable'; import { SavedObjectIndexStore, DOC_TYPE } from './persistence'; +import { checkForDuplicateTitle, OnSaveProps } from '../../../../src/plugins/saved_objects/public'; export type LensAttributeService = AttributeService< LensSavedObjectAttributes, @@ -30,7 +31,7 @@ export function getLensAttributeService( LensByValueInput, LensByReferenceInput >(DOC_TYPE, { - customSaveMethod: async ( + saveMethod: async ( type: string, attributes: LensSavedObjectAttributes, savedObjectId?: string @@ -42,11 +43,34 @@ export function getLensAttributeService( }); return { id: savedDoc.savedObjectId }; }, - customUnwrapMethod: (savedObject) => { + unwrapMethod: async (savedObjectId: string): Promise => { + const savedObject = await core.savedObjects.client.get( + DOC_TYPE, + savedObjectId + ); return { ...savedObject.attributes, references: savedObject.references, }; }, + checkForDuplicateTitle: (props: OnSaveProps) => { + const savedObjectsClient = core.savedObjects.client; + const overlays = core.overlays; + return checkForDuplicateTitle( + { + title: props.newTitle, + copyOnSave: false, + lastSavedTitle: '', + getEsType: () => DOC_TYPE, + getDisplayName: () => DOC_TYPE, + }, + props.isTitleDuplicateConfirmed, + props.onTitleDuplicate, + { + savedObjectsClient, + overlays, + } + ); + }, }); }