Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Refactor attribute service #78414

Merged
merged 13 commits into from
Oct 6, 2020
2 changes: 1 addition & 1 deletion examples/embeddable_examples/kibana.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"kibanaVersion": "kibana",
"server": true,
"ui": true,
"requiredPlugins": ["embeddable", "uiActions", "dashboard"],
"requiredPlugins": ["embeddable", "uiActions", "dashboard", "savedObjects"],
"optionalPlugins": [],
"extraPublicDirs": ["public/todo", "public/hello_world", "public/todo/todo_ref_embeddable"],
"requiredBundles": ["kibanaReact"]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<
Expand Down Expand Up @@ -117,11 +124,55 @@ export class BookEmbeddableFactoryDefinition
});
}

private async unwrapMethod(savedObjectId: string): Promise<BookSavedObjectAttributes> {
const { savedObjectsClient } = await this.getStartServices();
const savedObject: SimpleSavedObject<BookSavedObjectAttributes> = 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<true> {
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!;
}
Expand Down
23 changes: 21 additions & 2 deletions examples/embeddable_examples/public/book/edit_book_action.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -56,8 +59,24 @@ export const createEditBookAction = (getStartServices: () => Promise<StartServic
);
},
execute: async ({ embeddable }: ActionContext) => {
const { openModal, getAttributeService } = await getStartServices();
const attributeService = getAttributeService<BookSavedObjectAttributes>(BOOK_SAVED_OBJECT);
const { openModal, getAttributeService, savedObjectsClient } = await getStartServices();
const attributeService = getAttributeService<BookSavedObjectAttributes>(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,
Expand Down
6 changes: 5 additions & 1 deletion examples/embeddable_examples/public/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -76,6 +76,7 @@ export interface EmbeddableExamplesSetupDependencies {
export interface EmbeddableExamplesStartDependencies {
embeddable: EmbeddableStart;
dashboard: DashboardStart;
savedObjectsClient: SavedObjectsClient;
}

interface ExampleEmbeddableFactories {
Expand Down Expand Up @@ -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);
Expand Down
2 changes: 1 addition & 1 deletion src/plugins/dashboard/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
import { schema, TypeOf } from '@kbn/config-schema';

export const configSchema = schema.object({
allowByValueEmbeddables: schema.boolean({ defaultValue: false }),
allowByValueEmbeddables: schema.boolean({ defaultValue: true }),
});

export type ConfigSchema = TypeOf<typeof configSchema>;
Original file line number Diff line number Diff line change
Expand Up @@ -31,19 +31,16 @@ export const mockAttributeService = <
R extends SavedObjectEmbeddableInput = SavedObjectEmbeddableInput
>(
type: string,
options?: AttributeServiceOptions<A>,
options: AttributeServiceOptions<A>,
customCore?: jest.Mocked<CoreStart>
): AttributeService<A, V, R> => {
const core = customCore ? customCore : coreMock.createStart();
const service = new AttributeService<A, V, R>(
return new AttributeService<A, V, R>(
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;
};
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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<TestAttributes> => {
return new Promise(() => {
return { ...attributes };
});
};
const defaultCheckForDuplicateTitle = (props: OnSaveProps): Promise<true> => {
return new Promise(() => {
return true;
});
};
const options = {
saveMethod: defaultSaveMethod,
unwrapMethod: defaultUnwrapMethod,
checkForDuplicateTitle: defaultCheckForDuplicateTitle,
};

beforeEach(() => {
attributes = {
Expand All @@ -55,9 +80,10 @@ describe('attributeService', () => {
});

describe('determining input type', () => {
const defaultAttributeService = mockAttributeService<TestAttributes>(defaultTestType);
const defaultAttributeService = mockAttributeService<TestAttributes>(defaultTestType, options);
const customAttributeService = mockAttributeService<TestAttributes, TestByValueInput>(
defaultTestType
defaultTestType,
options
);

it('can determine input type given default types', () => {
Expand Down Expand Up @@ -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<TestAttributes>(defaultTestType, {
saveMethod: defaultSaveMethod,
checkForDuplicateTitle: jest.fn(),
});
const attributeService = mockAttributeService<TestAttributes>(
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<TestAttributes>(defaultTestType);
const attributeService = mockAttributeService<TestAttributes>(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<TestAttributes>(
defaultTestType,
{
customUnwrapMethod: (savedObject) => ({
...savedObject.attributes,
testAttr2: { array: [1, 2, 3, 4, 5], testAttr3: 'kibanana' },
}),
const attributeService = mockAttributeService<TestAttributes>(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' },
Expand All @@ -127,67 +146,57 @@ describe('attributeService', () => {

describe('wrapping attributes', () => {
it('returns given attributes when use ref type is false', async () => {
const attributeService = mockAttributeService<TestAttributes>(defaultTestType);
const attributeService = mockAttributeService<TestAttributes>(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<TestAttributes>(
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<TestAttributes>(
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<TestAttributes>(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
);
});

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<TestAttributes>(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);
});
});
});
Loading