Skip to content

Commit

Permalink
[GS] add savedObjects result provider (#68619)
Browse files Browse the repository at this point in the history
* create server-side skeleton

* add base implementation & tests

* add unit test for provider

* remove useless contracts

* add preference search option

* implement score from find results

* fix types

* add FTR test

* fix test plugin types

* address ome review comments

* add multi results test

* use `getVisibleTypes`
  • Loading branch information
pgayvallet authored Jul 6, 2020
1 parent 2a43b48 commit 04aaba8
Show file tree
Hide file tree
Showing 18 changed files with 1,382 additions and 7 deletions.
8 changes: 5 additions & 3 deletions x-pack/plugins/global_search/server/mocks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
RouteHandlerGlobalSearchContext,
} from './types';
import { searchServiceMock } from './services/search_service.mock';
import { contextMock } from './services/context.mock';

const createSetupMock = (): jest.Mocked<GlobalSearchPluginSetup> => {
const searchMock = searchServiceMock.createSetupContract();
Expand All @@ -29,17 +30,18 @@ const createStartMock = (): jest.Mocked<GlobalSearchPluginStart> => {
};

const createRouteHandlerContextMock = (): jest.Mocked<RouteHandlerGlobalSearchContext> => {
const contextMock = {
const handlerContextMock = {
find: jest.fn(),
};

contextMock.find.mockReturnValue(of([]));
handlerContextMock.find.mockReturnValue(of([]));

return contextMock;
return handlerContextMock;
};

export const globalSearchPluginMock = {
createSetupContract: createSetupMock,
createStartContract: createStartMock,
createRouteHandlerContext: createRouteHandlerContextMock,
createProviderContext: contextMock.create,
};
38 changes: 38 additions & 0 deletions x-pack/plugins/global_search/server/services/context.mock.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

import {
savedObjectsTypeRegistryMock,
savedObjectsClientMock,
elasticsearchServiceMock,
uiSettingsServiceMock,
} from '../../../../../src/core/server/mocks';

const createContextMock = () => {
return {
core: {
savedObjects: {
client: savedObjectsClientMock.create(),
typeRegistry: savedObjectsTypeRegistryMock.create(),
},
elasticsearch: {
legacy: {
client: elasticsearchServiceMock.createScopedClusterClient(),
},
},
uiSettings: {
client: uiSettingsServiceMock.createClient(),
},
},
};
};

const createFactoryMock = () => () => () => createContextMock();

export const contextMock = {
create: createContextMock,
createFactory: createFactoryMock,
};
2 changes: 1 addition & 1 deletion x-pack/plugins/global_search_providers/kibana.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"id": "globalSearchProviders",
"version": "8.0.0",
"kibanaVersion": "kibana",
"server": false,
"server": true,
"ui": true,
"requiredPlugins": ["globalSearch"],
"optionalPlugins": [],
Expand Down
11 changes: 11 additions & 0 deletions x-pack/plugins/global_search_providers/server/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

import { PluginInitializer } from 'src/core/server';
import { GlobalSearchProvidersPlugin, GlobalSearchProvidersPluginSetupDeps } from './plugin';

export const plugin: PluginInitializer<{}, {}, GlobalSearchProvidersPluginSetupDeps, {}> = () =>
new GlobalSearchProvidersPlugin();
33 changes: 33 additions & 0 deletions x-pack/plugins/global_search_providers/server/plugin.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

import { coreMock } from '../../../../src/core/server/mocks';
import { globalSearchPluginMock } from '../../global_search/server/mocks';
import { GlobalSearchProvidersPlugin } from './plugin';

describe('GlobalSearchProvidersPlugin', () => {
let plugin: GlobalSearchProvidersPlugin;
let globalSearchSetup: ReturnType<typeof globalSearchPluginMock.createSetupContract>;

beforeEach(() => {
plugin = new GlobalSearchProvidersPlugin();
globalSearchSetup = globalSearchPluginMock.createSetupContract();
});

describe('#setup', () => {
it('registers the `savedObjects` result provider', () => {
const coreSetup = coreMock.createSetup();
plugin.setup(coreSetup, { globalSearch: globalSearchSetup });

expect(globalSearchSetup.registerResultProvider).toHaveBeenCalledTimes(1);
expect(globalSearchSetup.registerResultProvider).toHaveBeenCalledWith(
expect.objectContaining({
id: 'savedObjects',
})
);
});
});
});
28 changes: 28 additions & 0 deletions x-pack/plugins/global_search_providers/server/plugin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

import { CoreSetup, Plugin } from 'src/core/server';
import { GlobalSearchPluginSetup } from '../../global_search/server';
import { createSavedObjectsResultProvider } from './providers';

export interface GlobalSearchProvidersPluginSetupDeps {
globalSearch: GlobalSearchPluginSetup;
}

export class GlobalSearchProvidersPlugin
implements Plugin<{}, {}, GlobalSearchProvidersPluginSetupDeps, {}> {
setup(
{ getStartServices }: CoreSetup<{}, {}>,
{ globalSearch }: GlobalSearchProvidersPluginSetupDeps
) {
globalSearch.registerResultProvider(createSavedObjectsResultProvider());
return {};
}

start() {
return {};
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

export { createSavedObjectsResultProvider } from './saved_objects';
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

export { createSavedObjectsResultProvider } from './provider';
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

import { SavedObjectsFindResult, SavedObjectsType, SavedObjectTypeRegistry } from 'src/core/server';
import { mapToResult, mapToResults } from './map_object_to_result';

const createType = (props: Partial<SavedObjectsType>): SavedObjectsType => {
return {
name: 'type',
hidden: false,
namespaceType: 'single',
mappings: { properties: {} },
...props,
};
};

const createObject = <T>(
props: Partial<SavedObjectsFindResult>,
attributes: T
): SavedObjectsFindResult<T> => {
return {
id: 'id',
type: 'dashboard',
references: [],
score: 100,
...props,
attributes,
};
};

describe('mapToResult', () => {
it('converts a savedObject to a result', () => {
const type = createType({
name: 'dashboard',
management: {
defaultSearchField: 'title',
getInAppUrl: (obj) => ({ path: `/dashboard/${obj.id}`, uiCapabilitiesPath: '' }),
},
});

const obj = createObject(
{
id: 'dash1',
type: 'dashboard',
score: 42,
},
{
title: 'My dashboard',
}
);

expect(mapToResult(obj, type)).toEqual({
id: 'dash1',
title: 'My dashboard',
type: 'dashboard',
url: '/dashboard/dash1',
score: 42,
});
});

it('throws if the type do not have management information', () => {
const object = createObject(
{ id: 'dash1', type: 'dashboard', score: 42 },
{ title: 'My dashboard' }
);

expect(() => {
mapToResult(
object,
createType({
name: 'dashboard',
management: {
getInAppUrl: (obj) => ({ path: `/dashboard/${obj.id}`, uiCapabilitiesPath: '' }),
},
})
);
}).toThrowErrorMatchingInlineSnapshot(
`"Trying to map an object from a type without management metadata"`
);

expect(() => {
mapToResult(
object,
createType({
name: 'dashboard',
management: {
defaultSearchField: 'title',
},
})
);
}).toThrowErrorMatchingInlineSnapshot(
`"Trying to map an object from a type without management metadata"`
);

expect(() => {
mapToResult(
object,
createType({
name: 'dashboard',
management: undefined,
})
);
}).toThrowErrorMatchingInlineSnapshot(
`"Trying to map an object from a type without management metadata"`
);
});
});

describe('mapToResults', () => {
let typeRegistry: SavedObjectTypeRegistry;

beforeEach(() => {
typeRegistry = new SavedObjectTypeRegistry();
});

it('converts savedObjects to results', () => {
typeRegistry.registerType(
createType({
name: 'typeA',
management: {
defaultSearchField: 'title',
getInAppUrl: (obj) => ({ path: `/type-a/${obj.id}`, uiCapabilitiesPath: '' }),
},
})
);
typeRegistry.registerType(
createType({
name: 'typeB',
management: {
defaultSearchField: 'description',
getInAppUrl: (obj) => ({ path: `/type-b/${obj.id}`, uiCapabilitiesPath: 'foo' }),
},
})
);
typeRegistry.registerType(
createType({
name: 'typeC',
management: {
defaultSearchField: 'excerpt',
getInAppUrl: (obj) => ({ path: `/type-c/${obj.id}`, uiCapabilitiesPath: 'bar' }),
},
})
);

const results = [
createObject(
{
id: 'resultA',
type: 'typeA',
score: 100,
},
{
title: 'titleA',
field: 'noise',
}
),
createObject(
{
id: 'resultC',
type: 'typeC',
score: 42,
},
{
excerpt: 'titleC',
title: 'foo',
}
),
createObject(
{
id: 'resultB',
type: 'typeB',
score: 69,
},
{
description: 'titleB',
bar: 'baz',
}
),
];

expect(mapToResults(results, typeRegistry)).toEqual([
{
id: 'resultA',
title: 'titleA',
type: 'typeA',
url: '/type-a/resultA',
score: 100,
},
{
id: 'resultC',
title: 'titleC',
type: 'typeC',
url: '/type-c/resultC',
score: 42,
},
{
id: 'resultB',
title: 'titleB',
type: 'typeB',
url: '/type-b/resultB',
score: 69,
},
]);
});
});
Loading

0 comments on commit 04aaba8

Please sign in to comment.