[SavedObjectsFinder] Replace savedObjects.client.find (#151549)

## Summary

This PR is a part of standardizing the `SavedObjectsFinder` component
across our codebase.
This PR moves the server-side logic from `saved_objects` plugin to
`saved_objects_finder` plugin and removes the deprecated
`savedObjects.client.find` from the `saved_objects_finder` component.
The component in `saved_objects_finder` plugin supports tagging, while
the one in `saved_objects` does not. Ideally, we'd move to using that
component everywhere. I started working on moving to this component in
the follow-up [PR](https://github.com/elastic/kibana/pull/151764).


### Checklist

Delete any items that are not applicable to this PR.

- [X] Any text added follows [EUI's writing
guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses
sentence case text and includes [i18n
support](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md)
~- []
[Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html)
was added for features that require explanation or tutorials~
- [X] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or added to match the most common scenarios
~- [ ] Any UI touched in this PR is usable by keyboard only (learn more
about [keyboard
accessibility](https://webaim.org/techniques/keyboard/))~
~- [ ] Any UI touched in this PR does not create any new axe failures
(run axe in browser:
[FF](https://addons.mozilla.org/en-US/firefox/addon/axe-devtools/),
[Chrome](https://chrome.google.com/webstore/detail/axe-web-accessibility-tes/lhdoppojpmngadmnindnejefpokejbdd?hl=en-US))~
~- [ ] If a plugin configuration key changed, check if it needs to be
allowlisted in the cloud and added to the [docker
list](https://github.com/elastic/kibana/blob/main/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker)~
~- [ ] This renders correctly on smaller devices using a responsive
layout. (You can test this [in your
browser](https://www.browserstack.com/guide/responsive-testing-on-local-server))~
~- [ ] This was checked for [cross-browser
compatibility](https://www.elastic.co/support/matrix#matrix_browsers)~


### For maintainers

- [ ] This was checked for breaking API changes and was [labeled
appropriately](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)

---------

Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Maja Grubic 2023-02-22 15:53:12 +01:00 committed by GitHub
parent 6943797fbf
commit 20ee30207f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
21 changed files with 269 additions and 215 deletions

View file

@ -42,7 +42,7 @@ exports[`OpenSearchPanel render 1`] = `
}
services={
Object {
"savedObjects": undefined,
"http": undefined,
"savedObjectsManagement": undefined,
"savedObjectsTagging": undefined,
"uiSettings": undefined,

View file

@ -57,7 +57,7 @@ export function OpenSearchPanel(props: OpenSearchPanelProps) {
<EuiFlyoutBody>
<SavedObjectFinder
services={{
savedObjects: core.savedObjects,
http: core.http,
uiSettings,
savedObjectsManagement,
savedObjectsTagging,

View file

@ -4,7 +4,7 @@
"owner": "@elastic/kibana-core",
"plugin": {
"id": "savedObjects",
"server": true,
"server": false,
"browser": true,
"requiredPlugins": [
"data",

View file

@ -14,7 +14,6 @@
"@kbn/i18n-react",
"@kbn/test-jest-helpers",
"@kbn/utility-types",
"@kbn/config-schema",
"@kbn/core-saved-objects-server",
],
"exclude": [

View file

@ -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
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
export const PER_PAGE_SETTING = 'savedObjects:perPage';
export const LISTING_LIMIT_SETTING = 'savedObjects:listingLimit';
export type { SavedObjectCommon, FindQueryHTTP, FindResponseHTTP, FinderAttributes } from './types';

View file

@ -0,0 +1,36 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import { SavedObject } from '@kbn/core-saved-objects-server';
export type SavedObjectCommon<T = unknown> = SavedObject<T>;
export interface FindQueryHTTP {
perPage?: number;
page?: number;
type: string | string[];
search?: string;
searchFields?: string[];
defaultSearchOperator?: 'AND' | 'OR';
sortField?: string;
sortOrder?: 'asc' | 'desc';
fields?: string | string[];
hasReference?: string;
}
export interface FinderAttributes {
title?: string;
name?: string;
type: string;
}
export interface FindResponseHTTP<T> {
saved_objects: Array<SavedObjectCommon<T>>;
total: number;
page: number;
per_page: number;
}

View file

@ -4,7 +4,7 @@
"owner": "@elastic/kibana-data-discovery",
"plugin": {
"id": "savedObjectsFinder",
"server": false,
"server": true,
"browser": true,
"requiredBundles": [
"savedObjects"

View file

@ -98,17 +98,17 @@ describe('SavedObjectsFinder', () => {
},
} as any as SavedObjectsTaggingApi;
it('should call saved object client on startup', async () => {
it('should call API on startup', async () => {
const core = coreMock.createStart();
(core.savedObjects.client.find as any as jest.SpyInstance).mockImplementation(() =>
Promise.resolve({ savedObjects: [doc] })
(core.http.get as any as jest.SpyInstance).mockImplementation(() =>
Promise.resolve({ saved_objects: [doc] })
);
core.uiSettings.get.mockImplementation(() => 10);
const wrapper = shallow(
<SavedObjectFinder
services={{
savedObjects: core.savedObjects,
http: core.http,
uiSettings: core.uiSettings,
savedObjectsManagement,
savedObjectsTagging,
@ -119,29 +119,31 @@ describe('SavedObjectsFinder', () => {
wrapper.instance().componentDidMount!();
await nextTick();
expect(core.savedObjects.client.find).toHaveBeenCalledWith({
type: ['search'],
fields: ['title', 'name'],
search: undefined,
hasReference: undefined,
page: 1,
perPage: 10,
searchFields: ['title^3', 'description', 'name'],
defaultSearchOperator: 'AND',
expect(core.http.get).toHaveBeenCalledWith('/internal/saved-objects-finder/find', {
query: {
type: ['search'],
fields: ['title', 'name'],
search: undefined,
hasReference: undefined,
page: 1,
perPage: 10,
searchFields: ['title^3', 'description', 'name'],
defaultSearchOperator: 'AND',
},
});
});
it('should list initial items', async () => {
const core = coreMock.createStart();
(core.savedObjects.client.find as any as jest.SpyInstance).mockImplementation(() =>
Promise.resolve({ savedObjects: [doc] })
(core.http.get as any as jest.SpyInstance).mockImplementation(() =>
Promise.resolve({ saved_objects: [doc] })
);
core.uiSettings.get.mockImplementation(() => 10);
const wrapper = shallow(
<SavedObjectFinder
services={{
savedObjects: core.savedObjects,
http: core.http,
uiSettings: core.uiSettings,
savedObjectsManagement,
savedObjectsTagging,
@ -163,15 +165,15 @@ describe('SavedObjectsFinder', () => {
it('should call onChoose on item click', async () => {
const chooseStub = sinon.stub();
const core = coreMock.createStart();
(core.savedObjects.client.find as any as jest.SpyInstance).mockImplementation(() =>
Promise.resolve({ savedObjects: [doc] })
(core.http.get as any as jest.SpyInstance).mockImplementation(() =>
Promise.resolve({ saved_objects: [doc] })
);
core.uiSettings.get.mockImplementation(() => 10);
const wrapper = mount(
<SavedObjectFinder
services={{
savedObjects: core.savedObjects,
http: core.http,
uiSettings: core.uiSettings,
savedObjectsManagement,
savedObjectsTagging,
@ -193,15 +195,15 @@ describe('SavedObjectsFinder', () => {
describe('sorting', () => {
it('should list items by type ascending', async () => {
const core = coreMock.createStart();
(core.savedObjects.client.find as any as jest.SpyInstance).mockImplementation(() =>
Promise.resolve({ savedObjects: [doc, doc3, doc2] })
(core.http.get as any as jest.SpyInstance).mockImplementation(() =>
Promise.resolve({ saved_objects: [doc, doc3, doc2] })
);
core.uiSettings.get.mockImplementation(() => 10);
const wrapper = mount(
<SavedObjectFinder
services={{
savedObjects: core.savedObjects,
http: core.http,
uiSettings: core.uiSettings,
savedObjectsManagement,
savedObjectsTagging,
@ -224,15 +226,15 @@ describe('SavedObjectsFinder', () => {
it('should list items by type descending', async () => {
const core = coreMock.createStart();
(core.savedObjects.client.find as any as jest.SpyInstance).mockImplementation(() =>
Promise.resolve({ savedObjects: [doc, doc3, doc2] })
(core.http.get as any as jest.SpyInstance).mockImplementation(() =>
Promise.resolve({ saved_objects: [doc, doc3, doc2] })
);
core.uiSettings.get.mockImplementation(() => 10);
const wrapper = mount(
<SavedObjectFinder
services={{
savedObjects: core.savedObjects,
http: core.http,
uiSettings: core.uiSettings,
savedObjectsManagement,
savedObjectsTagging,
@ -259,15 +261,15 @@ describe('SavedObjectsFinder', () => {
it('should list items by title ascending', async () => {
const core = coreMock.createStart();
(core.savedObjects.client.find as any as jest.SpyInstance).mockImplementation(() =>
Promise.resolve({ savedObjects: [doc, doc2] })
(core.http.get as any as jest.SpyInstance).mockImplementation(() =>
Promise.resolve({ saved_objects: [doc, doc2] })
);
core.uiSettings.get.mockImplementation(() => 10);
const wrapper = mount(
<SavedObjectFinder
services={{
savedObjects: core.savedObjects,
http: core.http,
uiSettings: core.uiSettings,
savedObjectsManagement,
savedObjectsTagging,
@ -286,15 +288,15 @@ describe('SavedObjectsFinder', () => {
it('should list items by title descending', async () => {
const core = coreMock.createStart();
(core.savedObjects.client.find as any as jest.SpyInstance).mockImplementation(() =>
Promise.resolve({ savedObjects: [doc, doc2] })
(core.http.get as any as jest.SpyInstance).mockImplementation(() =>
Promise.resolve({ saved_objects: [doc, doc2] })
);
core.uiSettings.get.mockImplementation(() => 10);
const wrapper = mount(
<SavedObjectFinder
services={{
savedObjects: core.savedObjects,
http: core.http,
uiSettings: core.uiSettings,
savedObjectsManagement,
savedObjectsTagging,
@ -316,15 +318,15 @@ describe('SavedObjectsFinder', () => {
it('should list items by tag ascending', async () => {
const core = coreMock.createStart();
(core.savedObjects.client.find as any as jest.SpyInstance).mockImplementation(() =>
Promise.resolve({ savedObjects: [doc, doc3, doc2] })
(core.http.get as any as jest.SpyInstance).mockImplementation(() =>
Promise.resolve({ saved_objects: [doc, doc3, doc2] })
);
core.uiSettings.get.mockImplementation(() => 10);
const wrapper = mount(
<SavedObjectFinder
services={{
savedObjects: core.savedObjects,
http: core.http,
uiSettings: core.uiSettings,
savedObjectsManagement,
savedObjectsTagging,
@ -347,15 +349,15 @@ describe('SavedObjectsFinder', () => {
it('should list items by tag descending', async () => {
const core = coreMock.createStart();
(core.savedObjects.client.find as any as jest.SpyInstance).mockImplementation(() =>
Promise.resolve({ savedObjects: [doc, doc3, doc2] })
(core.http.get as any as jest.SpyInstance).mockImplementation(() =>
Promise.resolve({ saved_objects: [doc, doc3, doc2] })
);
core.uiSettings.get.mockImplementation(() => 10);
const wrapper = mount(
<SavedObjectFinder
services={{
savedObjects: core.savedObjects,
http: core.http,
uiSettings: core.uiSettings,
savedObjectsManagement,
savedObjectsTagging,
@ -383,15 +385,15 @@ describe('SavedObjectsFinder', () => {
it('should not show the saved objects which get filtered by showSavedObject', async () => {
const core = coreMock.createStart();
(core.savedObjects.client.find as any as jest.SpyInstance).mockImplementation(() =>
Promise.resolve({ savedObjects: [doc, doc2] })
(core.http.get as any as jest.SpyInstance).mockImplementation(() =>
Promise.resolve({ saved_objects: [doc, doc2] })
);
core.uiSettings.get.mockImplementation(() => 10);
const wrapper = shallow(
<SavedObjectFinder
services={{
savedObjects: core.savedObjects,
http: core.http,
uiSettings: core.uiSettings,
savedObjectsManagement,
savedObjectsTagging,
@ -417,15 +419,15 @@ describe('SavedObjectsFinder', () => {
describe('search', () => {
it('should request filtered list on search input', async () => {
const core = coreMock.createStart();
(core.savedObjects.client.find as any as jest.SpyInstance).mockImplementation(() =>
Promise.resolve({ savedObjects: [doc, doc2] })
(core.http.get as any as jest.SpyInstance).mockImplementation(() =>
Promise.resolve({ saved_objects: [doc, doc2] })
);
core.uiSettings.get.mockImplementation(() => 10);
const wrapper = mount(
<SavedObjectFinder
services={{
savedObjects: core.savedObjects,
http: core.http,
uiSettings: core.uiSettings,
savedObjectsManagement,
savedObjectsTagging,
@ -439,27 +441,29 @@ describe('SavedObjectsFinder', () => {
wrapper
.find('[data-test-subj="savedObjectFinderSearchInput"] input')
.simulate('keyup', { key: 'Enter', target: { value: 'abc' } });
expect(core.savedObjects.client.find).toHaveBeenCalledWith({
type: ['search'],
fields: ['title', 'name'],
search: 'abc*',
hasReference: undefined,
page: 1,
perPage: 10,
searchFields: ['title^3', 'description', 'name'],
defaultSearchOperator: 'AND',
expect(core.http.get).toHaveBeenCalledWith('/internal/saved-objects-finder/find', {
query: {
type: ['search'],
fields: ['title', 'name'],
search: 'abc*',
hasReference: undefined,
page: 1,
perPage: 10,
searchFields: ['title^3', 'description', 'name'],
defaultSearchOperator: 'AND',
},
});
});
it('should include additional fields in search if listed in meta data', async () => {
const core = coreMock.createStart();
(core.savedObjects.client.find as jest.Mock).mockResolvedValue({ savedObjects: [] });
(core.http.get as jest.Mock).mockResolvedValue({ saved_objects: [] });
core.uiSettings.get.mockImplementation(() => 10);
const wrapper = mount(
<SavedObjectFinder
services={{
savedObjects: core.savedObjects,
http: core.http,
uiSettings: core.uiSettings,
savedObjectsManagement,
savedObjectsTagging,
@ -486,29 +490,31 @@ describe('SavedObjectsFinder', () => {
wrapper
.find('[data-test-subj="savedObjectFinderSearchInput"] input')
.simulate('keyup', { key: 'Enter', target: { value: 'abc' } });
expect(core.savedObjects.client.find).toHaveBeenCalledWith({
type: ['type1', 'type2'],
fields: ['title', 'name', 'field1', 'field2', 'field3'],
search: 'abc*',
hasReference: undefined,
page: 1,
perPage: 10,
searchFields: ['title^3', 'description'],
defaultSearchOperator: 'AND',
expect(core.http.get).toHaveBeenCalledWith('/internal/saved-objects-finder/find', {
query: {
type: ['type1', 'type2'],
fields: ['title', 'name', 'field1', 'field2', 'field3'],
search: 'abc*',
hasReference: undefined,
page: 1,
perPage: 10,
searchFields: ['title^3', 'description'],
defaultSearchOperator: 'AND',
},
});
});
it('should respect response order on search input', async () => {
const core = coreMock.createStart();
(core.savedObjects.client.find as any as jest.SpyInstance).mockImplementation(() =>
Promise.resolve({ savedObjects: [doc, doc2] })
(core.http.get as any as jest.SpyInstance).mockImplementation(() =>
Promise.resolve({ saved_objects: [doc, doc2] })
);
core.uiSettings.get.mockImplementation(() => 10);
const wrapper = mount(
<SavedObjectFinder
services={{
savedObjects: core.savedObjects,
http: core.http,
uiSettings: core.uiSettings,
savedObjectsManagement,
savedObjectsTagging,
@ -530,15 +536,15 @@ describe('SavedObjectsFinder', () => {
it('should request multiple saved object types at once', async () => {
const core = coreMock.createStart();
(core.savedObjects.client.find as any as jest.SpyInstance).mockImplementation(() =>
Promise.resolve({ savedObjects: [doc, doc2] })
(core.http.get as any as jest.SpyInstance).mockImplementation(() =>
Promise.resolve({ saved_objects: [doc, doc2] })
);
core.uiSettings.get.mockImplementation(() => 10);
const wrapper = shallow(
<SavedObjectFinder
services={{
savedObjects: core.savedObjects,
http: core.http,
uiSettings: core.uiSettings,
savedObjectsManagement,
savedObjectsTagging,
@ -560,23 +566,25 @@ describe('SavedObjectsFinder', () => {
wrapper.instance().componentDidMount!();
expect(core.savedObjects.client.find).toHaveBeenCalledWith({
type: ['search', 'vis'],
fields: ['title', 'name'],
search: undefined,
page: 1,
perPage: 10,
searchFields: ['title^3', 'description'],
defaultSearchOperator: 'AND',
expect(core.http.get).toHaveBeenCalledWith('/internal/saved-objects-finder/find', {
query: {
type: ['search', 'vis'],
fields: ['title', 'name'],
search: undefined,
page: 1,
perPage: 10,
searchFields: ['title^3', 'description'],
defaultSearchOperator: 'AND',
},
});
});
describe('filter', () => {
it('should render filter buttons if enabled', async () => {
const core = coreMock.createStart();
(core.savedObjects.client.find as any as jest.SpyInstance).mockImplementation(() =>
(core.http.get as any as jest.SpyInstance).mockImplementation(() =>
Promise.resolve({
savedObjects: [doc, doc2, doc3],
saved_objects: [doc, doc2, doc3],
})
);
core.uiSettings.get.mockImplementation(() => 10);
@ -584,7 +592,7 @@ describe('SavedObjectsFinder', () => {
const wrapper = mount(
<SavedObjectFinder
services={{
savedObjects: core.savedObjects,
http: core.http,
uiSettings: core.uiSettings,
savedObjectsManagement,
savedObjectsTagging,
@ -603,9 +611,9 @@ describe('SavedObjectsFinder', () => {
it('should not render filter buttons if disabled', async () => {
const core = coreMock.createStart();
(core.savedObjects.client.find as any as jest.SpyInstance).mockImplementation(() =>
(core.http.get as any as jest.SpyInstance).mockImplementation(() =>
Promise.resolve({
savedObjects: [doc, doc2, doc3],
saved_objects: [doc, doc2, doc3],
})
);
core.uiSettings.get.mockImplementation(() => 10);
@ -613,7 +621,7 @@ describe('SavedObjectsFinder', () => {
const wrapper = mount(
<SavedObjectFinder
services={{
savedObjects: core.savedObjects,
http: core.http,
uiSettings: core.uiSettings,
savedObjectsManagement,
savedObjectsTagging,
@ -630,9 +638,9 @@ describe('SavedObjectsFinder', () => {
it('should not render types filter button if there is only one type in the metadata list', async () => {
const core = coreMock.createStart();
(core.savedObjects.client.find as any as jest.SpyInstance).mockImplementation(() =>
(core.http.get as any as jest.SpyInstance).mockImplementation(() =>
Promise.resolve({
savedObjects: [doc, doc2],
saved_objects: [doc, doc2],
})
);
core.uiSettings.get.mockImplementation(() => 10);
@ -640,7 +648,7 @@ describe('SavedObjectsFinder', () => {
const wrapper = mount(
<SavedObjectFinder
services={{
savedObjects: core.savedObjects,
http: core.http,
uiSettings: core.uiSettings,
savedObjectsManagement,
savedObjectsTagging,
@ -658,9 +666,9 @@ describe('SavedObjectsFinder', () => {
it('should not render tags filter button if savedObjectsTagging is undefined', async () => {
const core = coreMock.createStart();
(core.savedObjects.client.find as any as jest.SpyInstance).mockImplementation(() =>
(core.http.get as any as jest.SpyInstance).mockImplementation(() =>
Promise.resolve({
savedObjects: [doc, doc2, doc3],
saved_objects: [doc, doc2, doc3],
})
);
core.uiSettings.get.mockImplementation(() => 10);
@ -668,7 +676,7 @@ describe('SavedObjectsFinder', () => {
const wrapper = mount(
<SavedObjectFinder
services={{
savedObjects: core.savedObjects,
http: core.http,
uiSettings: core.uiSettings,
savedObjectsManagement,
savedObjectsTagging: undefined,
@ -686,9 +694,9 @@ describe('SavedObjectsFinder', () => {
it('should apply types filter if selected', async () => {
const core = coreMock.createStart();
(core.savedObjects.client.find as any as jest.SpyInstance).mockImplementation(() =>
(core.http.get as any as jest.SpyInstance).mockImplementation(() =>
Promise.resolve({
savedObjects: [doc, doc2, doc3],
saved_objects: [doc, doc2, doc3],
})
);
core.uiSettings.get.mockImplementation(() => 10);
@ -696,7 +704,7 @@ describe('SavedObjectsFinder', () => {
const wrapper = mount(
<SavedObjectFinder
services={{
savedObjects: core.savedObjects,
http: core.http,
uiSettings: core.uiSettings,
savedObjectsManagement,
savedObjectsTagging,
@ -711,34 +719,38 @@ describe('SavedObjectsFinder', () => {
const table = wrapper.find<EuiInMemoryTable<any>>(EuiInMemoryTable);
const search = table.prop('search') as EuiSearchBarProps;
search.onChange?.({ query: Query.parse('type:(vis)'), queryText: '', error: null });
expect(core.savedObjects.client.find).toHaveBeenLastCalledWith({
type: ['vis'],
fields: ['title', 'name'],
search: undefined,
hasReference: undefined,
page: 1,
perPage: 10,
searchFields: ['title^3', 'description'],
defaultSearchOperator: 'AND',
expect(core.http.get).toHaveBeenLastCalledWith('/internal/saved-objects-finder/find', {
query: {
type: ['vis'],
fields: ['title', 'name'],
search: undefined,
hasReference: undefined,
page: 1,
perPage: 10,
searchFields: ['title^3', 'description'],
defaultSearchOperator: 'AND',
},
});
search.onChange?.({ query: Query.parse('type:(search or vis)'), queryText: '', error: null });
expect(core.savedObjects.client.find).toHaveBeenLastCalledWith({
type: ['search', 'vis'],
fields: ['title', 'name'],
search: undefined,
hasReference: undefined,
page: 1,
perPage: 10,
searchFields: ['title^3', 'description'],
defaultSearchOperator: 'AND',
expect(core.http.get).toHaveBeenLastCalledWith('/internal/saved-objects-finder/find', {
query: {
type: ['search', 'vis'],
fields: ['title', 'name'],
search: undefined,
hasReference: undefined,
page: 1,
perPage: 10,
searchFields: ['title^3', 'description'],
defaultSearchOperator: 'AND',
},
});
});
it('should apply tags filter if selected', async () => {
const core = coreMock.createStart();
(core.savedObjects.client.find as any as jest.SpyInstance).mockImplementation(() =>
(core.http.get as any as jest.SpyInstance).mockImplementation(() =>
Promise.resolve({
savedObjects: [doc, doc2, doc3],
saved_objects: [doc, doc2, doc3],
})
);
core.uiSettings.get.mockImplementation(() => 10);
@ -746,7 +758,7 @@ describe('SavedObjectsFinder', () => {
const wrapper = mount(
<SavedObjectFinder
services={{
savedObjects: core.savedObjects,
http: core.http,
uiSettings: core.uiSettings,
savedObjectsManagement,
savedObjectsTagging,
@ -761,34 +773,38 @@ describe('SavedObjectsFinder', () => {
const table = wrapper.find<EuiInMemoryTable<any>>(EuiInMemoryTable);
const search = table.prop('search') as EuiSearchBarProps;
search.onChange?.({ query: Query.parse('tag:(tag1)'), queryText: '', error: null });
expect(core.savedObjects.client.find).toHaveBeenLastCalledWith({
type: ['search', 'vis'],
fields: ['title', 'name'],
search: undefined,
hasReference: ['tag1'],
page: 1,
perPage: 10,
searchFields: ['title^3', 'description'],
defaultSearchOperator: 'AND',
expect(core.http.get).toHaveBeenLastCalledWith('/internal/saved-objects-finder/find', {
query: {
type: ['search', 'vis'],
fields: ['title', 'name'],
search: undefined,
hasReference: JSON.stringify(['tag1']),
page: 1,
perPage: 10,
searchFields: ['title^3', 'description'],
defaultSearchOperator: 'AND',
},
});
search.onChange?.({ query: Query.parse('tag:(tag1 or tag2)'), queryText: '', error: null });
expect(core.savedObjects.client.find).toHaveBeenLastCalledWith({
type: ['search', 'vis'],
fields: ['title', 'name'],
search: undefined,
hasReference: ['tag1', 'tag2'],
page: 1,
perPage: 10,
searchFields: ['title^3', 'description'],
defaultSearchOperator: 'AND',
expect(core.http.get).toHaveBeenLastCalledWith('/internal/saved-objects-finder/find', {
query: {
type: ['search', 'vis'],
fields: ['title', 'name'],
search: undefined,
hasReference: JSON.stringify(['tag1', 'tag2']),
page: 1,
perPage: 10,
searchFields: ['title^3', 'description'],
defaultSearchOperator: 'AND',
},
});
});
});
it('should display no items message if there are no items', async () => {
const core = coreMock.createStart();
(core.savedObjects.client.find as any as jest.SpyInstance).mockImplementation(() =>
Promise.resolve({ savedObjects: [] })
(core.http.get as any as jest.SpyInstance).mockImplementation(() =>
Promise.resolve({ saved_objects: [] })
);
core.uiSettings.get.mockImplementation(() => 10);
@ -796,7 +812,7 @@ describe('SavedObjectsFinder', () => {
const wrapper = mount(
<SavedObjectFinder
services={{
savedObjects: core.savedObjects,
http: core.http,
uiSettings: core.uiSettings,
savedObjectsManagement,
savedObjectsTagging,
@ -823,15 +839,15 @@ describe('SavedObjectsFinder', () => {
it('should show a table pagination with initial per page', async () => {
const core = coreMock.createStart();
(core.savedObjects.client.find as any as jest.SpyInstance).mockImplementation(() =>
Promise.resolve({ savedObjects: longItemList })
(core.http.get as any as jest.SpyInstance).mockImplementation(() =>
Promise.resolve({ saved_objects: longItemList })
);
core.uiSettings.get.mockImplementation(() => 10);
const wrapper = mount(
<SavedObjectFinder
services={{
savedObjects: core.savedObjects,
http: core.http,
uiSettings: core.uiSettings,
savedObjectsManagement,
savedObjectsTagging,
@ -852,15 +868,15 @@ describe('SavedObjectsFinder', () => {
it('should allow switching the page size', async () => {
const core = coreMock.createStart();
(core.savedObjects.client.find as any as jest.SpyInstance).mockImplementation(() =>
Promise.resolve({ savedObjects: longItemList })
(core.http.get as any as jest.SpyInstance).mockImplementation(() =>
Promise.resolve({ saved_objects: longItemList })
);
core.uiSettings.get.mockImplementation(() => 10);
const wrapper = mount(
<SavedObjectFinder
services={{
savedObjects: core.savedObjects,
http: core.http,
uiSettings: core.uiSettings,
savedObjectsManagement,
savedObjectsTagging,
@ -887,15 +903,15 @@ describe('SavedObjectsFinder', () => {
it('should switch page correctly', async () => {
const core = coreMock.createStart();
(core.savedObjects.client.find as any as jest.SpyInstance).mockImplementation(() =>
Promise.resolve({ savedObjects: longItemList })
(core.http.get as any as jest.SpyInstance).mockImplementation(() =>
Promise.resolve({ saved_objects: longItemList })
);
core.uiSettings.get.mockImplementation(() => 10);
const wrapper = mount(
<SavedObjectFinder
services={{
savedObjects: core.savedObjects,
http: core.http,
uiSettings: core.uiSettings,
savedObjectsManagement,
savedObjectsTagging,
@ -925,15 +941,15 @@ describe('SavedObjectsFinder', () => {
it('should show an ordinary pagination for fixed page sizes', async () => {
const core = coreMock.createStart();
(core.savedObjects.client.find as any as jest.SpyInstance).mockImplementation(() =>
Promise.resolve({ savedObjects: longItemList })
(core.http.get as any as jest.SpyInstance).mockImplementation(() =>
Promise.resolve({ saved_objects: longItemList })
);
core.uiSettings.get.mockImplementation(() => 10);
const wrapper = mount(
<SavedObjectFinder
services={{
savedObjects: core.savedObjects,
http: core.http,
uiSettings: core.uiSettings,
savedObjectsManagement,
savedObjectsTagging,
@ -954,15 +970,15 @@ describe('SavedObjectsFinder', () => {
it('should switch page correctly for fixed page sizes', async () => {
const core = coreMock.createStart();
(core.savedObjects.client.find as any as jest.SpyInstance).mockImplementation(() =>
Promise.resolve({ savedObjects: longItemList })
(core.http.get as any as jest.SpyInstance).mockImplementation(() =>
Promise.resolve({ saved_objects: longItemList })
);
core.uiSettings.get.mockImplementation(() => 10);
const wrapper = mount(
<SavedObjectFinder
services={{
savedObjects: core.savedObjects,
http: core.http,
uiSettings: core.uiSettings,
savedObjectsManagement,
savedObjectsTagging,
@ -994,13 +1010,13 @@ describe('SavedObjectsFinder', () => {
describe('loading state', () => {
it('should display a loading indicator during initial loading', () => {
const core = coreMock.createStart();
(core.savedObjects.client.find as jest.Mock).mockResolvedValue({ savedObjects: [] });
(core.http.get as jest.Mock).mockResolvedValue({ saved_objects: [] });
core.uiSettings.get.mockImplementation(() => 10);
const wrapper = mount(
<SavedObjectFinder
services={{
savedObjects: core.savedObjects,
http: core.http,
uiSettings: core.uiSettings,
savedObjectsManagement,
savedObjectsTagging,
@ -1014,15 +1030,15 @@ describe('SavedObjectsFinder', () => {
it('should hide the loading indicator if data is shown', async () => {
const core = coreMock.createStart();
(core.savedObjects.client.find as any as jest.SpyInstance).mockImplementation(() =>
Promise.resolve({ savedObjects: [doc] })
(core.http.get as any as jest.SpyInstance).mockImplementation(() =>
Promise.resolve({ saved_objects: [doc] })
);
core.uiSettings.get.mockImplementation(() => 10);
const wrapper = mount(
<SavedObjectFinder
services={{
savedObjects: core.savedObjects,
http: core.http,
uiSettings: core.uiSettings,
savedObjectsManagement,
savedObjectsTagging,
@ -1045,15 +1061,15 @@ describe('SavedObjectsFinder', () => {
it('should show the loading indicator if there are already items and the search is updated', async () => {
const core = coreMock.createStart();
(core.savedObjects.client.find as any as jest.SpyInstance).mockImplementation(() =>
Promise.resolve({ savedObjects: [doc] })
(core.http.get as any as jest.SpyInstance).mockImplementation(() =>
Promise.resolve({ saved_objects: [doc] })
);
core.uiSettings.get.mockImplementation(() => 10);
const wrapper = mount(
<SavedObjectFinder
services={{
savedObjects: core.savedObjects,
http: core.http,
uiSettings: core.uiSettings,
savedObjectsManagement,
savedObjectsTagging,
@ -1076,15 +1092,15 @@ describe('SavedObjectsFinder', () => {
it('should render with children', async () => {
const core = coreMock.createStart();
(core.savedObjects.client.find as any as jest.SpyInstance).mockImplementation(() =>
Promise.resolve({ savedObjects: [doc, doc2] })
(core.http.get as any as jest.SpyInstance).mockImplementation(() =>
Promise.resolve({ saved_objects: [doc, doc2] })
);
core.uiSettings.get.mockImplementation(() => 10);
const wrapper = mount(
<SavedObjectFinder
services={{
savedObjects: core.savedObjects,
http: core.http,
uiSettings: core.uiSettings,
savedObjectsManagement,
savedObjectsTagging,
@ -1111,15 +1127,15 @@ describe('SavedObjectsFinder', () => {
describe('columns', () => {
it('should show all columns', async () => {
const core = coreMock.createStart();
(core.savedObjects.client.find as any as jest.SpyInstance).mockImplementation(() =>
Promise.resolve({ savedObjects: [doc, doc2, doc3] })
(core.http.get as any as jest.SpyInstance).mockImplementation(() =>
Promise.resolve({ saved_objects: [doc, doc2, doc3] })
);
core.uiSettings.get.mockImplementation(() => 10);
const wrapper = mount(
<SavedObjectFinder
services={{
savedObjects: core.savedObjects,
http: core.http,
uiSettings: core.uiSettings,
savedObjectsManagement,
savedObjectsTagging,
@ -1139,15 +1155,15 @@ describe('SavedObjectsFinder', () => {
it('should hide the type column if there is only one type in the metadata list', async () => {
const core = coreMock.createStart();
(core.savedObjects.client.find as any as jest.SpyInstance).mockImplementation(() =>
Promise.resolve({ savedObjects: [doc, doc2] })
(core.http.get as any as jest.SpyInstance).mockImplementation(() =>
Promise.resolve({ saved_objects: [doc, doc2] })
);
core.uiSettings.get.mockImplementation(() => 10);
const wrapper = mount(
<SavedObjectFinder
services={{
savedObjects: core.savedObjects,
http: core.http,
uiSettings: core.uiSettings,
savedObjectsManagement,
savedObjectsTagging,
@ -1167,15 +1183,15 @@ describe('SavedObjectsFinder', () => {
it('should hide the tags column if savedObjectsTagging is undefined', async () => {
const core = coreMock.createStart();
(core.savedObjects.client.find as any as jest.SpyInstance).mockImplementation(() =>
Promise.resolve({ savedObjects: [doc, doc2, doc3] })
(core.http.get as any as jest.SpyInstance).mockImplementation(() =>
Promise.resolve({ saved_objects: [doc, doc2, doc3] })
);
core.uiSettings.get.mockImplementation(() => 10);
const wrapper = mount(
<SavedObjectFinder
services={{
savedObjects: core.savedObjects,
http: core.http,
uiSettings: core.uiSettings,
savedObjectsManagement,
savedObjectsTagging: undefined,

View file

@ -25,22 +25,18 @@ import {
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import type {
SimpleSavedObject,
SavedObject,
SavedObjectsStart,
IUiSettingsClient,
} from '@kbn/core/public';
import type { IUiSettingsClient, HttpStart } from '@kbn/core/public';
import type { SavedObjectsTaggingApi } from '@kbn/saved-objects-tagging-oss-plugin/public';
import { LISTING_LIMIT_SETTING } from '@kbn/saved-objects-plugin/public';
import { SavedObjectCommon, FindQueryHTTP, FindResponseHTTP } from '../../common';
export interface SavedObjectMetaData<T = unknown> {
type: string;
name: string;
getIconForSavedObject(savedObject: SimpleSavedObject<T>): IconType;
getTooltipForSavedObject?(savedObject: SimpleSavedObject<T>): string;
showSavedObject?(savedObject: SimpleSavedObject<T>): boolean;
getSavedObjectSubType?(savedObject: SimpleSavedObject<T>): string;
getIconForSavedObject(savedObject: SavedObjectCommon<T>): IconType;
getTooltipForSavedObject?(savedObject: SavedObjectCommon<T>): string;
showSavedObject?(savedObject: SavedObjectCommon<T>): boolean;
getSavedObjectSubType?(savedObject: SavedObjectCommon<T>): string;
includeFields?: string[];
defaultSearchField?: string;
}
@ -51,10 +47,10 @@ interface FinderAttributes {
type: string;
}
interface SavedObjectFinderItem extends SavedObject {
interface SavedObjectFinderItem extends SavedObjectCommon {
title: string | null;
name: string | null;
simple: SimpleSavedObject<FinderAttributes>;
simple: SavedObjectCommon<FinderAttributes>;
}
interface SavedObjectFinderState {
@ -65,7 +61,7 @@ interface SavedObjectFinderState {
}
interface SavedObjectFinderServices {
savedObjects: SavedObjectsStart;
http: HttpStart;
uiSettings: IUiSettingsClient;
savedObjectsManagement: SavedObjectsManagementPluginStart;
savedObjectsTagging: SavedObjectsTaggingApi | undefined;
@ -74,10 +70,10 @@ interface SavedObjectFinderServices {
interface BaseSavedObjectFinder {
services: SavedObjectFinderServices;
onChoose?: (
id: SimpleSavedObject['id'],
type: SimpleSavedObject['type'],
id: SavedObjectCommon['id'],
type: SavedObjectCommon['type'],
name: string,
savedObject: SimpleSavedObject
savedObject: SavedObjectCommon
) => void;
noItemsMessage?: React.ReactNode;
savedObjectMetaData: Array<SavedObjectMetaData<FinderAttributes>>;
@ -136,7 +132,11 @@ export class SavedObjectFinderUi extends React.Component<
}, []);
const perPage = this.props.services.uiSettings.get(LISTING_LIMIT_SETTING);
const response = await this.props.services.savedObjects.client.find<FinderAttributes>({
const hasReference = this.props.services.savedObjectsManagement.getTagFindReferences({
selectedTags,
taggingApi: this.props.services.savedObjectsTagging,
});
const params: FindQueryHTTP = {
type: visibleTypes ?? Object.keys(metaDataMap),
fields: [...new Set(fields)],
search: queryText ? `${queryText}*` : undefined,
@ -144,13 +144,13 @@ export class SavedObjectFinderUi extends React.Component<
perPage,
searchFields: ['title^3', 'description', ...additionalSearchFields],
defaultSearchOperator: 'AND',
hasReference: this.props.services.savedObjectsManagement.getTagFindReferences({
selectedTags,
taggingApi: this.props.services.savedObjectsTagging,
}),
});
hasReference: hasReference ? JSON.stringify(hasReference) : undefined,
};
const response = (await this.props.services.http.get('/internal/saved-objects-finder/find', {
query: params as Record<string, any>,
})) as FindResponseHTTP<FinderAttributes>;
const savedObjects = response.savedObjects
const savedObjects = response.saved_objects
.map((savedObject) => {
const {
attributes: { name, title },
@ -159,7 +159,7 @@ export class SavedObjectFinderUi extends React.Component<
const nameToUse = name && typeof name === 'string' ? name : titleToUse;
return {
...savedObject,
version: savedObject._version,
version: savedObject.version,
title: titleToUse,
name: nameToUse,
simple: savedObject,
@ -228,7 +228,7 @@ export class SavedObjectFinderUi extends React.Component<
const { onChoose, savedObjectMetaData } = this.props;
const taggingApi = this.props.services.savedObjectsTagging;
const originalTagColumn = taggingApi?.ui.getTableColumnDefinition();
const tagColumn: EuiTableFieldDataColumnType<SavedObject> | undefined = originalTagColumn
const tagColumn: EuiTableFieldDataColumnType<SavedObjectCommon> | undefined = originalTagColumn
? {
...originalTagColumn,
sortable: (item) =>

View file

@ -27,6 +27,7 @@ export const registerFindRoute = (router: SavedObjectsRouter) => {
defaultValue: [],
}),
searchFields: schema.maybe(schema.arrayOf(schema.string())),
hasReference: schema.maybe(schema.string()),
}),
},
options: {
@ -44,6 +45,7 @@ export const registerFindRoute = (router: SavedObjectsRouter) => {
...query,
type: searchTypes,
fields: includedFields,
hasReference: query.hasReference ? JSON.parse(query.hasReference) : undefined,
});
const savedObjects = findResponse.saved_objects;

View file

@ -14,23 +14,23 @@ import { PER_PAGE_SETTING, LISTING_LIMIT_SETTING } from '../common';
export const uiSettings: Record<string, UiSettingsParams> = {
[PER_PAGE_SETTING]: {
name: i18n.translate('savedObjects.advancedSettings.perPageTitle', {
name: i18n.translate('savedObjectsFinder.advancedSettings.perPageTitle', {
defaultMessage: 'Objects per page',
}),
value: 20,
type: 'number',
description: i18n.translate('savedObjects.advancedSettings.perPageText', {
description: i18n.translate('savedObjectsFinder.advancedSettings.perPageText', {
defaultMessage: 'Number of objects to show per page in the load dialog',
}),
schema: schema.number(),
},
[LISTING_LIMIT_SETTING]: {
name: i18n.translate('savedObjects.advancedSettings.listingLimitTitle', {
name: i18n.translate('savedObjectsFinder.advancedSettings.listingLimitTitle', {
defaultMessage: 'Objects listing limit',
}),
type: 'number',
value: 1000,
description: i18n.translate('savedObjects.advancedSettings.listingLimitText', {
description: i18n.translate('savedObjectsFinder.advancedSettings.listingLimitText', {
defaultMessage: 'Number of objects to fetch for the listing pages',
}),
schema: schema.number(),

View file

@ -11,6 +11,8 @@
"@kbn/saved-objects-tagging-oss-plugin",
"@kbn/i18n",
"@kbn/saved-objects-plugin",
"@kbn/core-saved-objects-server",
"@kbn/config-schema",
],
"exclude": [
"target/**/*",

View file

@ -4529,10 +4529,6 @@
"savedObjects.saveModal.saveAsNewLabel": "Enregistrer en tant que nouveau {objectType}",
"savedObjects.saveModal.saveTitle": "Enregistrer {objectType}",
"savedObjects.saveModalOrigin.originAfterSavingSwitchLabel": "{originVerb} à {origin} après l'enregistrement",
"savedObjects.advancedSettings.listingLimitText": "Nombre d'objets à récupérer pour les pages de listing",
"savedObjects.advancedSettings.listingLimitTitle": "Limite de listing dobjets",
"savedObjects.advancedSettings.perPageText": "Nombre d'objets à afficher par page dans la boîte de dialogue de chargement",
"savedObjects.advancedSettings.perPageTitle": "Objets par page",
"savedObjects.confirmModal.cancelButtonLabel": "Annuler",
"savedObjects.confirmModal.overwriteButtonLabel": "Écraser",
"savedObjects.finder.filterButtonLabel": "Types",

View file

@ -4527,10 +4527,6 @@
"savedObjects.saveModal.saveAsNewLabel": "新しい {objectType} として保存",
"savedObjects.saveModal.saveTitle": "{objectType} を保存",
"savedObjects.saveModalOrigin.originAfterSavingSwitchLabel": "保存後に{originVerb}から{origin}",
"savedObjects.advancedSettings.listingLimitText": "一覧ページ用に取得するオブジェクトの数です",
"savedObjects.advancedSettings.listingLimitTitle": "オブジェクト取得制限",
"savedObjects.advancedSettings.perPageText": "読み込みダイアログで表示されるページごとのオブジェクトの数です",
"savedObjects.advancedSettings.perPageTitle": "ページごとのオブジェクト数",
"savedObjects.confirmModal.cancelButtonLabel": "キャンセル",
"savedObjects.confirmModal.overwriteButtonLabel": "上書き",
"savedObjects.finder.filterButtonLabel": "タイプ",

View file

@ -4532,10 +4532,6 @@
"savedObjects.saveModal.saveAsNewLabel": "另存为新的 {objectType}",
"savedObjects.saveModal.saveTitle": "保存 {objectType}",
"savedObjects.saveModalOrigin.originAfterSavingSwitchLabel": "保存后{originVerb}至{origin}",
"savedObjects.advancedSettings.listingLimitText": "要为列表页面提取的对象数目",
"savedObjects.advancedSettings.listingLimitTitle": "对象列表限制",
"savedObjects.advancedSettings.perPageText": "加载对话框中每页要显示的对象数目",
"savedObjects.advancedSettings.perPageTitle": "每页对象数",
"savedObjects.confirmModal.cancelButtonLabel": "取消",
"savedObjects.confirmModal.overwriteButtonLabel": "覆盖",
"savedObjects.finder.filterButtonLabel": "类型",