[saved search] Content management integration (#158006)

## Summary

- Saved searches use content management api
- Saved search plugin provides proper plugin api instead of static
exports
- Discover no longer re-exports static content from saved search plugin
- ML no longer works directly with the saved search saved object
- saved object conflicts are thrown via the api, rather than the api
consumer
- Content management api logs failed requests a bit better, helpful for
tests failing in CI

Closes https://github.com/elastic/kibana/issues/157078

---------

Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Matthew Kime 2023-06-02 20:55:52 -05:00 committed by GitHub
parent 4e38817a4d
commit 22ccf83d66
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
123 changed files with 1100 additions and 770 deletions

View file

@ -0,0 +1,37 @@
/*
* 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 { ContentManagementPublicStart, ContentManagementPublicSetup } from './types';
const createSetupContract = (): ContentManagementPublicSetup => {
return {
registry: {
register: jest.fn(),
},
};
};
const createStartContract = (): ContentManagementPublicStart => {
return {
client: {
get: jest.fn(),
create: jest.fn(),
update: jest.fn(),
delete: jest.fn(),
search: jest.fn(),
} as unknown as ContentManagementPublicStart['client'],
registry: {
get: jest.fn(),
getAll: jest.fn(),
},
};
};
export const contentManagementMock = {
createSetupContract,
createStartContract,
};

View file

@ -62,9 +62,15 @@ export class RpcClient implements CrudClient {
}
private sendMessage = async <O = unknown>(name: ProcedureName, input: any): Promise<O> => {
const { result } = await this.http.post<{ result: O }>(`${API_ENDPOINT}/${name}`, {
body: JSON.stringify(input),
});
return result;
try {
const response = await this.http.post<{ result: O }>(`${API_ENDPOINT}/${name}`, {
body: JSON.stringify(input),
});
return response.result;
} catch (e) {
// eslint-disable-next-line no-console
console.log(`Content management client error: ${e.body.message}`);
throw e;
}
};
}

View file

@ -20,6 +20,7 @@
"savedObjects",
"savedObjectsFinder",
"savedObjectsManagement",
"savedSearch",
"dataViewFieldEditor",
"dataViewEditor",
"expressions",
@ -39,8 +40,7 @@
"requiredBundles": [
"kibanaUtils",
"kibanaReact",
"unifiedSearch",
"savedSearch"
"unifiedSearch"
],
"extraPublicDirs": [
"common"

View file

@ -10,6 +10,7 @@ import { EUI_CHARTS_THEME_LIGHT } from '@elastic/eui/dist/eui_charts_theme';
import { DiscoverServices } from '../build_services';
import { dataPluginMock } from '@kbn/data-plugin/public/mocks';
import { expressionsPluginMock } from '@kbn/expressions-plugin/public/mocks';
import { savedSearchPluginMock } from '@kbn/saved-search-plugin/public/mocks';
import { chromeServiceMock, coreMock, docLinksServiceMock } from '@kbn/core/public/mocks';
import {
CONTEXT_STEP_SETTING,
@ -190,6 +191,7 @@ export function createDiscoverServicesMock(): DiscoverServices {
updateTagsReferences: jest.fn(),
},
},
savedSearch: savedSearchPluginMock.createStartContract(),
dataViews: dataPlugin.dataViews,
timefilter: dataPlugin.query.timefilter.timefilter,
lens: {

View file

@ -12,6 +12,7 @@ import { BehaviorSubject, Observable, Subject } from 'rxjs';
import { RequestAdapter } from '@kbn/inspector-plugin/common';
import { action } from '@storybook/addon-actions';
import { createHashHistory } from 'history';
import { SavedSearch } from '@kbn/saved-search-plugin/public';
import { FetchStatus } from '../../../../types';
import {
AvailableFields$,
@ -22,7 +23,6 @@ import {
} from '../../../services/discover_data_state_container';
import { buildDataTableRecordList } from '../../../../../utils/build_data_record';
import { esHits } from '../../../../../__mocks__/es_hits';
import { SavedSearch } from '../../../../..';
import { DiscoverLayoutProps } from '../discover_layout';
import {
DiscoverStateContainer,

View file

@ -6,17 +6,6 @@
* Side Public License, v 1.
*/
import { SavedObjectSaveOpts } from '@kbn/saved-objects-plugin/public';
const mockSaveSavedSearch = jest.fn().mockResolvedValue('123');
jest.mock('@kbn/saved-search-plugin/public', () => {
const actualPlugin = jest.requireActual('@kbn/saved-search-plugin/public');
return {
...actualPlugin,
saveSavedSearch: (val: SavedSearch, opts?: SavedObjectSaveOpts) =>
mockSaveSavedSearch(val, opts),
};
});
import { getSavedSearchContainer, isEqualSavedSearch } from './discover_saved_search_container';
import type { SavedSearch } from '@kbn/saved-search-plugin/public';
import { discoverServiceMock } from '../../../__mocks__/services';
@ -92,32 +81,7 @@ describe('DiscoverSavedSearchContainer', () => {
discoverServiceMock.data.search.searchSource.create = jest
.fn()
.mockReturnValue(savedSearchMock.searchSource);
discoverServiceMock.core.savedObjects.client.resolve = jest.fn().mockReturnValue({
saved_object: {
attributes: {
kibanaSavedObjectMeta: {
searchSourceJSON:
'{"query":{"query":"","language":"kuery"},"filter":[],"indexRefName":"kibanaSavedObjectMeta.searchSourceJSON.index"}',
},
title: 'The saved search that will save the world',
sort: [],
columns: ['test123'],
description: 'description',
hideChart: false,
},
id: 'the-saved-search-id',
type: 'search',
references: [
{
name: 'kibanaSavedObjectMeta.searchSourceJSON.index',
id: 'the-data-view-id',
type: 'index-pattern',
},
],
namespaces: ['default'],
},
outcome: 'exactMatch',
});
discoverServiceMock.savedSearch.get = jest.fn().mockReturnValue(savedSearchMock);
it('loads a saved search', async () => {
const savedSearchContainer = getSavedSearchContainer({
@ -143,7 +107,10 @@ describe('DiscoverSavedSearchContainer', () => {
};
await savedSearchContainer.persist(savedSearchToPersist, saveOptions);
expect(mockSaveSavedSearch).toHaveBeenCalledWith(savedSearchToPersist, saveOptions);
expect(discoverServiceMock.savedSearch.save).toHaveBeenCalledWith(
savedSearchToPersist,
saveOptions
);
});
it('sets the initial and current saved search to the persisted saved search', async () => {
@ -152,6 +119,9 @@ describe('DiscoverSavedSearchContainer', () => {
...savedSearch,
title,
};
discoverServiceMock.savedSearch.save = jest.fn().mockResolvedValue('123');
const savedSearchContainer = getSavedSearchContainer({
services: discoverServiceMock,
});
@ -200,7 +170,7 @@ describe('DiscoverSavedSearchContainer', () => {
});
it('Error thrown on persistence layer bubbling up, no changes to the initial saved search ', async () => {
mockSaveSavedSearch.mockImplementation(() => {
discoverServiceMock.savedSearch.save = jest.fn().mockImplementation(() => {
throw new Error('oh-noes');
});

View file

@ -6,12 +6,7 @@
* Side Public License, v 1.
*/
import {
getNewSavedSearch,
getSavedSearch,
SavedSearch,
saveSavedSearch,
} from '@kbn/saved-search-plugin/public';
import { SavedSearch } from '@kbn/saved-search-plugin/public';
import { BehaviorSubject } from 'rxjs';
import type { DataView } from '@kbn/data-views-plugin/common';
import { SavedObjectSaveOpts } from '@kbn/saved-objects-plugin/public';
@ -114,7 +109,7 @@ export function getSavedSearchContainer({
}: {
services: DiscoverServices;
}): DiscoverSavedSearchContainer {
const initialSavedSearch = getNewSavedSearch(services.data);
const initialSavedSearch = services.savedSearch.getNew();
const savedSearchInitial$ = new BehaviorSubject(initialSavedSearch);
const savedSearchCurrent$ = new BehaviorSubject(copySavedSearch(initialSavedSearch));
const hasChanged$ = new BehaviorSubject(false);
@ -135,7 +130,7 @@ export function getSavedSearchContainer({
const newSavedSearch = async (nextDataView: DataView | undefined) => {
addLog('[savedSearch] new', { nextDataView });
const dataView = nextDataView ?? getState().searchSource.getField('index');
const nextSavedSearch = await getNewSavedSearch(services.data);
const nextSavedSearch = services.savedSearch.getNew();
nextSavedSearch.searchSource.setField('index', dataView);
const newAppState = getDefaultAppState(nextSavedSearch, services);
const nextSavedSearchToSet = updateSavedSearch({
@ -151,12 +146,7 @@ export function getSavedSearchContainer({
addLog('[savedSearch] persist', { nextSavedSearch, saveOptions });
updateSavedSearch({ savedSearch: nextSavedSearch, services }, true);
const id = await saveSavedSearch(
nextSavedSearch,
saveOptions || {},
services.core.savedObjects.client,
services.savedObjectsTagging
);
const id = await services.savedSearch.save(nextSavedSearch, saveOptions || {});
if (id) {
set(nextSavedSearch);
@ -191,12 +181,9 @@ export function getSavedSearchContainer({
const load = async (id: string, dataView: DataView | undefined): Promise<SavedSearch> => {
addLog('[savedSearch] load', { id, dataView });
const loadedSavedSearch = await getSavedSearch(id, {
search: services.data.search,
savedObjectsClient: services.core.savedObjects.client,
spaces: services.spaces,
savedObjectsTagging: services.savedObjectsTagging,
});
const loadedSavedSearch = await services.savedSearch.get(id);
if (!loadedSavedSearch.searchSource.getField('index') && dataView) {
loadedSavedSearch.searchSource.setField('index', dataView);
}

View file

@ -39,6 +39,7 @@ import { NavigationPublicPluginStart } from '@kbn/navigation-plugin/public';
import { IndexPatternFieldEditorStart } from '@kbn/data-view-field-editor-plugin/public';
import { FieldFormatsStart } from '@kbn/field-formats-plugin/public';
import { EmbeddableStart } from '@kbn/embeddable-plugin/public';
import { SavedSearchPublicPluginStart } from '@kbn/saved-search-plugin/public';
import type { SpacesApi } from '@kbn/spaces-plugin/public';
import { DataViewEditorStart } from '@kbn/data-view-editor-plugin/public';
@ -99,6 +100,7 @@ export interface DiscoverServices {
charts: ChartsPluginStart;
savedObjectsManagement: SavedObjectsManagementPluginStart;
savedObjectsTagging?: SavedObjectsTaggingApi;
savedSearch: SavedSearchPublicPluginStart;
unifiedSearch: UnifiedSearchPublicPluginStart;
lens: LensPublicStart;
}
@ -154,6 +156,7 @@ export const buildServices = memoize(function (
charts: plugins.charts,
savedObjectsTagging: plugins.savedObjectsTaggingOss?.getTaggingApi(),
savedObjectsManagement: plugins.savedObjectsManagement,
savedSearch: plugins.savedSearch,
unifiedSearch: plugins.unifiedSearch,
lens: plugins.lens,
};

View file

@ -9,7 +9,8 @@
import { ReactElement } from 'react';
import { FilterManager } from '@kbn/data-plugin/public';
import { createFilterManagerMock } from '@kbn/data-plugin/public/query/filter_manager/filter_manager.mock';
import { getSavedSearchUrl, SearchInput } from '..';
import { SearchInput } from '..';
import { getSavedSearchUrl } from '@kbn/saved-search-plugin/public';
import { DiscoverServices } from '../build_services';
import { dataViewMock } from '../__mocks__/data_view';
import { discoverServiceMock } from '../__mocks__/services';

View file

@ -8,18 +8,10 @@
import { discoverServiceMock } from '../__mocks__/services';
import { SearchEmbeddableFactory, type StartServices } from './search_embeddable_factory';
import { getSavedSearch } from '@kbn/saved-search-plugin/public';
import { createSearchSourceMock } from '@kbn/data-plugin/public/mocks';
import { dataViewMock } from '../__mocks__/data_view';
import { ErrorEmbeddable } from '@kbn/embeddable-plugin/public';
jest.mock('@kbn/saved-search-plugin/public', () => {
return {
...jest.requireActual('@kbn/saved-search-plugin/public'),
getSavedSearch: jest.fn(),
};
});
jest.mock('@kbn/embeddable-plugin/public', () => {
return {
...jest.requireActual('@kbn/embeddable-plugin/public'),
@ -35,7 +27,6 @@ const input = {
rowsPerPage: 50,
};
const getSavedSearchMock = getSavedSearch as unknown as jest.Mock;
const ErrorEmbeddableMock = ErrorEmbeddable as unknown as jest.Mock;
describe('SearchEmbeddableFactory', () => {
@ -45,7 +36,9 @@ describe('SearchEmbeddableFactory', () => {
sort: [['message', 'asc']] as Array<[string, string]>,
searchSource: createSearchSourceMock({ index: dataViewMock }, undefined),
};
getSavedSearchMock.mockResolvedValue(savedSearchMock);
const mockGet = jest.fn().mockResolvedValue(savedSearchMock);
discoverServiceMock.savedSearch.get = mockGet;
const factory = new SearchEmbeddableFactory(
() => Promise.resolve({ executeTriggerActions: jest.fn() } as unknown as StartServices),
@ -53,12 +46,13 @@ describe('SearchEmbeddableFactory', () => {
);
const embeddable = await factory.createFromSavedObject('saved-object-id', input);
expect(getSavedSearchMock.mock.calls[0][0]).toEqual('saved-object-id');
expect(mockGet.mock.calls[0][0]).toEqual('saved-object-id');
expect(embeddable).toBeDefined();
});
it('should throw an error when saved search could not be found', async () => {
getSavedSearchMock.mockRejectedValue('Could not find saved search');
const mockGet = jest.fn().mockRejectedValue('Could not find saved search');
discoverServiceMock.savedSearch.get = mockGet;
const factory = new SearchEmbeddableFactory(
() => Promise.resolve({ executeTriggerActions: jest.fn() } as unknown as StartServices),

View file

@ -16,11 +16,7 @@ import {
import type { TimeRange } from '@kbn/es-query';
import {
getSavedSearch,
getSavedSearchUrl,
throwErrorOnSavedSearchUrlConflict,
} from '@kbn/saved-search-plugin/public';
import { getSavedSearchUrl } from '@kbn/saved-search-plugin/public';
import { SearchInput, SearchOutput } from './types';
import { SEARCH_EMBEDDABLE_TYPE } from './constants';
import { SavedSearchEmbeddable } from './saved_search_embeddable';
@ -72,14 +68,7 @@ export class SearchEmbeddableFactory
const url = getSavedSearchUrl(savedObjectId);
const editUrl = services.addBasePath(`/app/discover${url}`);
try {
const savedSearch = await getSavedSearch(savedObjectId, {
search: services.data.search,
savedObjectsClient: services.core.savedObjects.client,
spaces: services.spaces,
savedObjectsTagging: services.savedObjectsTagging,
});
await throwErrorOnSavedSearchUrlConflict(savedSearch);
const savedSearch = await services.savedSearch.get(savedObjectId);
const dataView = savedSearch.searchSource.getField('index');
const { executeTriggerActions } = await this.getStartServices();

View file

@ -17,13 +17,3 @@ export function plugin(initializerContext: PluginInitializerContext) {
export type { ISearchEmbeddable, SearchInput } from './embeddable';
export { SEARCH_EMBEDDABLE_TYPE } from './embeddable';
export { loadSharingDataHelpers } from './utils';
// re-export types and static functions to give other plugins time to migrate away
export {
type SavedSearch,
getSavedSearch,
getSavedSearchFullPathUrl,
getSavedSearchUrl,
getSavedSearchUrlConflictMessage,
throwErrorOnSavedSearchUrlConflict,
} from '@kbn/saved-search-plugin/public';

View file

@ -39,6 +39,7 @@ import { DataViewEditorStart } from '@kbn/data-view-editor-plugin/public';
import { TriggersAndActionsUIPublicPluginStart } from '@kbn/triggers-actions-ui-plugin/public';
import type { SavedObjectTaggingOssPluginStart } from '@kbn/saved-objects-tagging-oss-plugin/public';
import type { SavedObjectsManagementPluginStart } from '@kbn/saved-objects-management-plugin/public';
import type { SavedSearchPublicPluginStart } from '@kbn/saved-search-plugin/public';
import type { UnifiedSearchPublicPluginStart } from '@kbn/unified-search-plugin/public';
import { setStateToKbnUrl } from '@kbn/kibana-utils-plugin/public';
import type { LensPublicStart } from '@kbn/lens-plugin/public';
@ -193,6 +194,7 @@ export interface DiscoverStartPlugins {
expressions: ExpressionsStart;
savedObjectsTaggingOss?: SavedObjectTaggingOssPluginStart;
savedObjectsManagement: SavedObjectsManagementPluginStart;
savedSearch: SavedSearchPublicPluginStart;
unifiedSearch: UnifiedSearchPublicPluginStart;
lens: LensPublicStart;
}

View file

@ -0,0 +1,13 @@
/*
* 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 SavedSearchType = 'search';
export const LATEST_VERSION = 1;
export type SavedSearchContentType = typeof SavedSearchType;

View file

@ -0,0 +1,21 @@
/*
* 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 type {
ContentManagementServicesDefinition as ServicesDefinition,
Version,
} from '@kbn/object-versioning';
// We export the versionned service definition from this file and not the barrel to avoid adding
// the schemas in the "public" js bundle
import { serviceDefinition as v1 } from './v1/cm_services';
export const cmServicesDefinition: { [version: Version]: ServicesDefinition } = {
1: v1,
};

View file

@ -0,0 +1,9 @@
/*
* 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 * from './v1';

View file

@ -0,0 +1,140 @@
/*
* 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 { schema } from '@kbn/config-schema';
import type { ContentManagementServicesDefinition as ServicesDefinition } from '@kbn/object-versioning';
import {
savedObjectSchema,
objectTypeToGetResultSchema,
createOptionsSchemas,
updateOptionsSchema,
createResultSchema,
} from '@kbn/content-management-utils';
const sortSchema = schema.arrayOf(schema.string(), { minSize: 2, maxSize: 2 });
const savedSearchAttributesSchema = schema.object(
{
title: schema.string(),
sort: schema.oneOf([sortSchema, schema.arrayOf(sortSchema)]),
columns: schema.arrayOf(schema.string()),
description: schema.string(),
grid: schema.object({
columns: schema.maybe(
schema.recordOf(
schema.string(),
schema.object({
width: schema.maybe(schema.number()),
})
)
),
}),
hideChart: schema.maybe(schema.boolean()),
isTextBasedQuery: schema.maybe(schema.boolean()),
usesAdHocDataView: schema.maybe(schema.boolean()),
kibanaSavedObjectMeta: schema.object({
searchSourceJSON: schema.string(),
}),
viewMode: schema.maybe(
schema.oneOf([schema.literal('documents'), schema.literal('aggregated')])
),
hideAggregatedPreview: schema.maybe(schema.boolean()),
rowHeight: schema.maybe(schema.number()),
hits: schema.maybe(schema.number()),
timeRestore: schema.maybe(schema.boolean()),
timeRange: schema.maybe(
schema.object({
from: schema.string(),
to: schema.string(),
})
),
refreshInterval: schema.maybe(
schema.object({
pause: schema.boolean(),
value: schema.number(),
})
),
rowsPerPage: schema.maybe(schema.number()),
breakdownField: schema.maybe(schema.string()),
version: schema.maybe(schema.number()),
},
{ unknowns: 'forbid' }
);
const savedSearchSavedObjectSchema = savedObjectSchema(savedSearchAttributesSchema);
const savedSearchCreateOptionsSchema = schema.maybe(
schema.object({
id: createOptionsSchemas.id,
references: createOptionsSchemas.references,
overwrite: createOptionsSchemas.overwrite,
})
);
const savedSearchUpdateOptionsSchema = schema.maybe(
schema.object({
references: updateOptionsSchema.references,
})
);
const savedSearchSearchOptionsSchema = schema.maybe(
schema.object({
searchFields: schema.maybe(schema.arrayOf(schema.string())),
fields: schema.maybe(schema.arrayOf(schema.string())),
})
);
// Content management service definition.
// We need it for BWC support between different versions of the content
export const serviceDefinition: ServicesDefinition = {
get: {
out: {
result: {
schema: objectTypeToGetResultSchema(savedSearchSavedObjectSchema),
},
},
},
create: {
in: {
options: {
schema: savedSearchCreateOptionsSchema,
},
data: {
schema: savedSearchAttributesSchema,
},
},
out: {
result: {
schema: createResultSchema(savedSearchSavedObjectSchema),
},
},
},
update: {
in: {
options: {
schema: savedSearchUpdateOptionsSchema,
},
data: {
schema: savedSearchAttributesSchema,
},
},
},
search: {
in: {
options: {
schema: savedSearchSearchOptionsSchema,
},
},
},
mSearch: {
out: {
result: {
schema: savedSearchSavedObjectSchema,
},
},
},
};

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 { LATEST_VERSION, type SavedSearchContentType, SavedSearchType } from '../../constants';
export type { SavedSearchCrudTypes } from './types';

View file

@ -0,0 +1,39 @@
/*
* 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 type {
ContentManagementCrudTypes,
SavedObjectCreateOptions,
SavedObjectSearchOptions,
SavedObjectUpdateOptions,
} from '@kbn/content-management-utils';
import { SavedSearchAttributes } from '../../types';
import { SavedSearchContentType } from '../../constants';
interface SavedSearchCreateOptions {
id?: SavedObjectCreateOptions['id'];
overwrite?: SavedObjectCreateOptions['overwrite'];
references?: SavedObjectCreateOptions['references'];
}
interface SavedSearchUpdateOptions {
references?: SavedObjectUpdateOptions['references'];
}
interface SavedSearchSearchOptions {
searchFields?: SavedObjectSearchOptions['searchFields'];
fields?: SavedObjectSearchOptions['fields'];
}
export type SavedSearchCrudTypes = ContentManagementCrudTypes<
SavedSearchContentType,
SavedSearchAttributes,
SavedSearchCreateOptions,
SavedSearchUpdateOptions,
SavedSearchSearchOptions
>;

View file

@ -20,3 +20,6 @@ export enum VIEW_MODE {
DOCUMENT_LEVEL = 'documents',
AGGREGATED_LEVEL = 'aggregated',
}
export { SavedSearchType } from './constants';
export { LATEST_VERSION } from './constants';

View file

@ -7,6 +7,7 @@
*/
import type { ISearchSource, RefreshInterval, TimeRange } from '@kbn/data-plugin/common';
import type { SavedObjectReference } from '@kbn/core-saved-objects-server';
import { VIEW_MODE } from '.';
export interface DiscoverGridSettings {
@ -73,4 +74,5 @@ export interface SavedSearch {
rowsPerPage?: number;
breakdownField?: string;
references?: SavedObjectReference[];
}

View file

@ -8,10 +8,14 @@
"server": true,
"browser": true,
"requiredPlugins": [
"data"
"data",
"contentManagement"
],
"optionalPlugins": [
"spaces",
"savedObjectsTaggingOss"
],
"requiredBundles": [
"kibanaUtils"
],
"extraPublicDirs": [
"common"

View file

@ -8,20 +8,13 @@
export type { SortOrder } from '../common/types';
export type { SavedSearch, SaveSavedSearchOptions } from './services/saved_searches';
export {
getSavedSearch,
getSavedSearchFullPathUrl,
getSavedSearchUrl,
getSavedSearchUrlConflictMessage,
throwErrorOnSavedSearchUrlConflict,
saveSavedSearch,
getNewSavedSearch,
} from './services/saved_searches';
export { getSavedSearchFullPathUrl, getSavedSearchUrl } from './services/saved_searches';
export { VIEW_MODE } from '../common';
import { SavedSearchPublicPlugin } from './plugin';
export type { SavedSearchPublicPluginStart } from './plugin';
export function plugin() {
return {
setup: () => {},
start: () => {},
};
return new SavedSearchPublicPlugin();
}

View file

@ -0,0 +1,48 @@
/*
* 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 { of } from 'rxjs';
import { SearchSource, IKibanaSearchResponse } from '@kbn/data-plugin/public';
import { SearchSourceDependencies } from '@kbn/data-plugin/common/search';
import type { SearchResponse } from '@elastic/elasticsearch/lib/api/types';
const createEmptySearchSource = jest.fn(() => {
const deps = {
getConfig: jest.fn(),
} as unknown as SearchSourceDependencies;
const searchSource = new SearchSource({}, deps);
searchSource.fetch$ = jest.fn().mockReturnValue(of({ rawResponse: { hits: { total: 2 } } }));
searchSource.createChild = jest.fn((options = {}) => {
const childSearchSource = new SearchSource({}, deps);
childSearchSource.setParent(searchSource, options);
childSearchSource.fetch$ = <T>() =>
of({ rawResponse: { hits: { hits: [] } } } as unknown as IKibanaSearchResponse<
SearchResponse<T>
>);
return childSearchSource;
});
return searchSource;
});
const savedSearchStartMock = () => ({
get: jest.fn().mockImplementation(() => ({
id: 'savedSearch',
title: 'savedSearchTitle',
searchSource: createEmptySearchSource(),
})),
getAll: jest.fn(),
getNew: jest.fn().mockImplementation(() => ({
searchSource: createEmptySearchSource(),
})),
save: jest.fn(),
find: jest.fn(),
});
export const savedSearchPluginMock = {
createStartContract: savedSearchStartMock,
};

View file

@ -0,0 +1,99 @@
/*
* 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 { CoreSetup, CoreStart, Plugin } from '@kbn/core/public';
import type { DataPublicPluginStart } from '@kbn/data-plugin/public';
import type { SpacesApi } from '@kbn/spaces-plugin/public';
import type { SavedObjectTaggingOssPluginStart } from '@kbn/saved-objects-tagging-oss-plugin/public';
import { i18n } from '@kbn/i18n';
import type {
ContentManagementPublicSetup,
ContentManagementPublicStart,
} from '@kbn/content-management-plugin/public';
import type { SOWithMetadata } from '@kbn/content-management-utils';
import {
getSavedSearch,
saveSavedSearch,
SaveSavedSearchOptions,
getNewSavedSearch,
} from './services/saved_searches';
import { SavedSearch, SavedSearchAttributes } from '../common/types';
import { SavedSearchType, LATEST_VERSION } from '../common';
import { SavedSearchesService } from './services/saved_searches/saved_searches_service';
/**
* Saved search plugin public Setup contract
*/
// eslint-disable-next-line @typescript-eslint/no-empty-interface
export interface SavedSearchPublicPluginSetup {}
/**
* Saved search plugin public Setup contract
*/
export interface SavedSearchPublicPluginStart {
get: (savedSearchId: string) => ReturnType<typeof getSavedSearch>;
getNew: () => ReturnType<typeof getNewSavedSearch>;
getAll: () => Promise<Array<SOWithMetadata<SavedSearchAttributes>>>;
save: (
savedSearch: SavedSearch,
options?: SaveSavedSearchOptions
) => ReturnType<typeof saveSavedSearch>;
}
/**
* Saved search plugin public Setup contract
*/
export interface SavedSearchPublicSetupDependencies {
contentManagement: ContentManagementPublicSetup;
}
/**
* Saved search plugin public Setup contract
*/
export interface SavedSearchPublicStartDependencies {
data: DataPublicPluginStart;
spaces?: SpacesApi;
savedObjectsTaggingOss?: SavedObjectTaggingOssPluginStart;
contentManagement: ContentManagementPublicStart;
}
export class SavedSearchPublicPlugin
implements
Plugin<
SavedSearchPublicPluginSetup,
SavedSearchPublicPluginStart,
SavedSearchPublicSetupDependencies,
SavedSearchPublicStartDependencies
>
{
public setup(core: CoreSetup, { contentManagement }: SavedSearchPublicSetupDependencies) {
contentManagement.registry.register({
id: SavedSearchType,
version: {
latest: LATEST_VERSION,
},
name: i18n.translate('savedSearch.contentManagementType', {
defaultMessage: 'Saved search',
}),
});
return {};
}
public start(
core: CoreStart,
{
data: { search },
spaces,
savedObjectsTaggingOss,
contentManagement: { client: contentManagement },
}: SavedSearchPublicStartDependencies
): SavedSearchPublicPluginStart {
return new SavedSearchesService({ search, spaces, savedObjectsTaggingOss, contentManagement });
}
}

View file

@ -5,10 +5,10 @@
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import type { SavedObjectsStart } from '@kbn/core/public';
import type { DataPublicPluginStart } from '@kbn/data-plugin/public';
import { contentManagementMock } from '@kbn/content-management-plugin/public/mocks';
import type { ContentManagementPublicStart } from '@kbn/content-management-plugin/public';
import { savedObjectsServiceMock } from '@kbn/core/public/mocks';
import { dataPluginMock } from '@kbn/data-plugin/public/mocks';
import { getSavedSearch } from './get_saved_searches';
@ -16,42 +16,24 @@ import { SavedObjectsTaggingApi } from '@kbn/saved-objects-tagging-oss-plugin/pu
describe('getSavedSearch', () => {
let search: DataPublicPluginStart['search'];
let savedObjectsClient: SavedObjectsStart['client'];
let cmClient: ContentManagementPublicStart['client'];
beforeEach(() => {
savedObjectsClient = savedObjectsServiceMock.createStartContract().client;
cmClient = contentManagementMock.createStartContract().client;
search = dataPluginMock.createStartContract().search;
});
test('should return empty saved search in case of no id', async () => {
const savedSearch = await getSavedSearch(undefined, {
savedObjectsClient,
search,
});
expect(search.searchSource.createEmpty).toHaveBeenCalled();
expect(savedSearch).toHaveProperty('searchSource');
});
test('should throw an error if so not found', async () => {
let errorMessage = 'No error thrown.';
savedObjectsClient.resolve = jest.fn().mockReturnValue({
saved_object: {
attributes: {},
error: {
statusCode: 404,
error: 'Not Found',
message: 'Saved object [search/ccf1af80-2297-11ec-86e0-1155ffb9c7a7] not found',
},
id: 'ccf1af80-2297-11ec-86e0-1155ffb9c7a7',
type: 'search',
references: [],
},
cmClient.get = jest.fn().mockReturnValue({
statusCode: 404,
error: 'Not Found',
message: 'Saved object [ccf1af80-2297-11ec-86e0-1155ffb9c7a7] not found',
});
try {
await getSavedSearch('ccf1af80-2297-11ec-86e0-1155ffb9c7a7', {
savedObjectsClient,
contentManagement: cmClient,
search,
});
} catch (error) {
@ -64,8 +46,8 @@ describe('getSavedSearch', () => {
});
test('should find saved search', async () => {
savedObjectsClient.resolve = jest.fn().mockReturnValue({
saved_object: {
cmClient.get = jest.fn().mockReturnValue({
item: {
attributes: {
kibanaSavedObjectMeta: {
searchSourceJSON:
@ -89,15 +71,17 @@ describe('getSavedSearch', () => {
],
namespaces: ['default'],
},
outcome: 'exactMatch',
meta: {
outcome: 'exactMatch',
},
});
const savedSearch = await getSavedSearch('ccf1af80-2297-11ec-86e0-1155ffb9c7a7', {
savedObjectsClient,
contentManagement: cmClient,
search,
});
expect(savedObjectsClient.resolve).toHaveBeenCalled();
expect(cmClient.get).toHaveBeenCalled();
expect(savedSearch).toMatchInlineSnapshot(`
Object {
"breakdownField": undefined,
@ -110,6 +94,13 @@ describe('getSavedSearch', () => {
"hideChart": false,
"id": "ccf1af80-2297-11ec-86e0-1155ffb9c7a7",
"isTextBasedQuery": undefined,
"references": Array [
Object {
"id": "ff959d40-b880-11e8-a6d9-e546fe2bba5f",
"name": "kibanaSavedObjectMeta.searchSourceJSON.index",
"type": "index-pattern",
},
],
"refreshInterval": undefined,
"rowHeight": undefined,
"rowsPerPage": undefined,
@ -140,9 +131,6 @@ describe('getSavedSearch', () => {
"toExpressionAst": [MockFunction],
},
"sharingSavedObjectProps": Object {
"aliasPurpose": undefined,
"aliasTargetId": undefined,
"errorJSON": undefined,
"outcome": "exactMatch",
},
"sort": Array [
@ -162,8 +150,8 @@ describe('getSavedSearch', () => {
});
test('should find saved search with sql mode', async () => {
savedObjectsClient.resolve = jest.fn().mockReturnValue({
saved_object: {
cmClient.get = jest.fn().mockReturnValue({
item: {
attributes: {
kibanaSavedObjectMeta: {
searchSourceJSON:
@ -188,15 +176,17 @@ describe('getSavedSearch', () => {
],
namespaces: ['default'],
},
outcome: 'exactMatch',
meta: {
outcome: 'exactMatch',
},
});
const savedSearch = await getSavedSearch('ccf1af80-2297-11ec-86e0-1155ffb9c7a7', {
savedObjectsClient,
contentManagement: cmClient,
search,
});
expect(savedObjectsClient.resolve).toHaveBeenCalled();
expect(cmClient.get).toHaveBeenCalled();
expect(savedSearch).toMatchInlineSnapshot(`
Object {
"breakdownField": undefined,
@ -209,6 +199,13 @@ describe('getSavedSearch', () => {
"hideChart": true,
"id": "ccf1af80-2297-11ec-86e0-1155ffb9c7a7",
"isTextBasedQuery": true,
"references": Array [
Object {
"id": "ff959d40-b880-11e8-a6d9-e546fe2bba5f",
"name": "kibanaSavedObjectMeta.searchSourceJSON.index",
"type": "index-pattern",
},
],
"refreshInterval": undefined,
"rowHeight": undefined,
"rowsPerPage": undefined,
@ -239,9 +236,6 @@ describe('getSavedSearch', () => {
"toExpressionAst": [MockFunction],
},
"sharingSavedObjectProps": Object {
"aliasPurpose": undefined,
"aliasTargetId": undefined,
"errorJSON": undefined,
"outcome": "exactMatch",
},
"sort": Array [
@ -261,8 +255,8 @@ describe('getSavedSearch', () => {
});
it('should call savedObjectsTagging.ui.getTagIdsFromReferences', async () => {
savedObjectsClient.resolve = jest.fn().mockReturnValue({
saved_object: {
cmClient.get = jest.fn().mockReturnValue({
item: {
attributes: {
kibanaSavedObjectMeta: {
searchSourceJSON:
@ -292,7 +286,9 @@ describe('getSavedSearch', () => {
],
namespaces: ['default'],
},
outcome: 'exactMatch',
meta: {
outcome: 'exactMatch',
},
});
const savedObjectsTagging = {
ui: {
@ -300,7 +296,7 @@ describe('getSavedSearch', () => {
},
} as unknown as SavedObjectsTaggingApi;
await getSavedSearch('ccf1af80-2297-11ec-86e0-1155ffb9c7a7', {
savedObjectsClient,
contentManagement: cmClient,
search,
savedObjectsTagging,
});

View file

@ -6,38 +6,60 @@
* Side Public License, v 1.
*/
import type { SavedObjectsClientContract } from '@kbn/core/public';
import type { DataPublicPluginStart } from '@kbn/data-plugin/public';
import { injectSearchSourceReferences, parseSearchSourceJSON } from '@kbn/data-plugin/public';
import { SavedObjectNotFound } from '@kbn/kibana-utils-plugin/public';
import type { SpacesApi } from '@kbn/spaces-plugin/public';
import type { SavedObjectsTaggingApi } from '@kbn/saved-objects-tagging-oss-plugin/public';
import type { SavedSearchAttributes } from '../../../common';
import { i18n } from '@kbn/i18n';
import type { ContentManagementPublicStart } from '@kbn/content-management-plugin/public';
import type { SavedSearch } from './types';
import { SAVED_SEARCH_TYPE } from './constants';
import { fromSavedSearchAttributes } from './saved_searches_utils';
import type { SavedSearchCrudTypes } from '../../../common/content_management';
interface GetSavedSearchDependencies {
search: DataPublicPluginStart['search'];
savedObjectsClient: SavedObjectsClientContract;
contentManagement: ContentManagementPublicStart['client'];
spaces?: SpacesApi;
savedObjectsTagging?: SavedObjectsTaggingApi;
}
const findSavedSearch = async (
savedSearchId: string,
{ search, savedObjectsClient, spaces, savedObjectsTagging }: GetSavedSearchDependencies
) => {
const so = await savedObjectsClient.resolve<SavedSearchAttributes>(
SAVED_SEARCH_TYPE,
savedSearchId
);
const getSavedSearchUrlConflictMessage = async (json: string) =>
i18n.translate('savedSearch.legacyURLConflict.errorMessage', {
defaultMessage: `This search has the same URL as a legacy alias. Disable the alias to resolve this error : {json}`,
values: { json },
});
if (!so.saved_object || so.saved_object.error) {
throw new SavedObjectNotFound(SAVED_SEARCH_TYPE, savedSearchId);
export const getSavedSearch = async (
savedSearchId: string,
{ search, spaces, savedObjectsTagging, contentManagement }: GetSavedSearchDependencies
) => {
const so = await contentManagement.get<
SavedSearchCrudTypes['GetIn'],
SavedSearchCrudTypes['GetOut']
>({
contentTypeId: SAVED_SEARCH_TYPE,
id: savedSearchId,
});
// @ts-expect-error
if (so.error) {
throw new Error(`Could not locate that search (id: ${savedSearchId})`);
}
const savedSearch = so.saved_object;
if (so.meta.outcome === 'conflict') {
throw new Error(
await getSavedSearchUrlConflictMessage(
JSON.stringify({
targetType: SAVED_SEARCH_TYPE,
sourceId: savedSearchId,
targetSpace: (await spaces?.getActiveSpace())?.id,
})
)
);
}
const savedSearch = so.item;
const parsedSearchSourceJSON = parseSearchSourceJSON(
savedSearch.attributes.kibanaSavedObjectMeta?.searchSourceJSON ?? '{}'
@ -52,28 +74,17 @@ const findSavedSearch = async (
? savedObjectsTagging.ui.getTagIdsFromReferences(savedSearch.references)
: undefined;
return fromSavedSearchAttributes(
const returnVal = fromSavedSearchAttributes(
savedSearchId,
savedSearch.attributes,
tags,
savedSearch.references,
await search.searchSource.create(searchSourceValues),
{
outcome: so.outcome,
aliasTargetId: so.alias_target_id,
aliasPurpose: so.alias_purpose,
errorJSON:
so.outcome === 'conflict' && spaces
? JSON.stringify({
targetType: SAVED_SEARCH_TYPE,
sourceId: savedSearchId,
targetSpace: (await spaces.getActiveSpace()).id,
})
: undefined,
}
so.meta
);
};
/** @public **/
return returnVal;
};
/**
* Returns a new saved search
@ -87,16 +98,3 @@ export const getNewSavedSearch = ({
}): SavedSearch => ({
searchSource: search.searchSource.createEmpty(),
});
/**
* Returns a persisted or a new saved search
* @param savedSearchId - when undefined a new saved search is returned
* @param dependencies
*/
export const getSavedSearch = async (
savedSearchId: string | undefined,
dependencies: GetSavedSearchDependencies
) => {
return savedSearchId
? findSavedSearch(savedSearchId, dependencies)
: getNewSavedSearch(dependencies);
};

View file

@ -7,12 +7,7 @@
*/
export { getSavedSearch, getNewSavedSearch } from './get_saved_searches';
export {
getSavedSearchUrl,
getSavedSearchFullPathUrl,
getSavedSearchUrlConflictMessage,
throwErrorOnSavedSearchUrlConflict,
} from './saved_searches_utils';
export { getSavedSearchUrl, getSavedSearchFullPathUrl } from './saved_searches_utils';
export type { SaveSavedSearchOptions } from './save_saved_searches';
export { saveSavedSearch } from './save_saved_searches';
export { SAVED_SEARCH_TYPE } from './constants';

View file

@ -6,23 +6,21 @@
* Side Public License, v 1.
*/
import type { SavedObjectsStart } from '@kbn/core/public';
import { savedObjectsServiceMock } from '@kbn/core/public/mocks';
import { dataPluginMock } from '@kbn/data-plugin/public/mocks';
import { saveSavedSearch } from './save_saved_searches';
import type { SavedSearch } from './types';
import type { SavedObjectsTaggingApi } from '@kbn/saved-objects-tagging-oss-plugin/public';
import type { ContentManagementPublicStart } from '@kbn/content-management-plugin/public';
import { contentManagementMock } from '@kbn/content-management-plugin/public/mocks';
describe('saveSavedSearch', () => {
let savedObjectsClient: SavedObjectsStart['client'];
let cmApi: ContentManagementPublicStart['client'];
let savedSearch: SavedSearch;
beforeEach(() => {
savedObjectsClient = savedObjectsServiceMock.createStartContract().client;
cmApi = contentManagementMock.createStartContract().client;
const searchSource = dataPluginMock.createStartContract().search.searchSource.createEmpty();
savedSearch = {
id: 'id',
title: 'title',
@ -41,8 +39,14 @@ describe('saveSavedSearch', () => {
describe('onTitleDuplicate', () => {
test('should check for title duplicating', async () => {
savedObjectsClient.find = jest.fn().mockReturnValue({
savedObjects: [{ get: () => 'title' }],
cmApi.search = jest.fn().mockReturnValue({
hits: [
{
attributes: {
title: 'title',
},
},
],
});
const onTitleDuplicate = jest.fn();
@ -52,7 +56,7 @@ describe('saveSavedSearch', () => {
onTitleDuplicate,
copyOnSave: true,
},
savedObjectsClient,
cmApi,
undefined
);
@ -60,8 +64,19 @@ describe('saveSavedSearch', () => {
});
test('should not check for title duplicating for saving existing search', async () => {
savedObjectsClient.find = jest.fn().mockReturnValue({
savedObjects: [{ get: () => 'title' }],
cmApi.search = jest.fn().mockReturnValue({
hits: [
{
attributes: {
title: 'title',
},
},
],
});
cmApi.update = jest.fn().mockReturnValue({
item: {
id: 'id',
},
});
const onTitleDuplicate = jest.fn();
@ -71,7 +86,7 @@ describe('saveSavedSearch', () => {
onTitleDuplicate,
copyOnSave: false,
},
savedObjectsClient,
cmApi,
undefined
);
@ -80,49 +95,92 @@ describe('saveSavedSearch', () => {
});
test('should call savedObjectsClient.create for saving new search', async () => {
cmApi.search = jest.fn().mockReturnValue({
hits: [
{
attributes: {
title: 'title',
},
},
],
});
cmApi.create = jest.fn().mockReturnValue({
item: {
id: 'id',
},
});
delete savedSearch.id;
await saveSavedSearch(savedSearch, {}, savedObjectsClient, undefined);
await saveSavedSearch(savedSearch, {}, cmApi, undefined);
expect(savedObjectsClient.create).toHaveBeenCalledWith(
'search',
{
expect(cmApi.create).toHaveBeenCalledWith({
contentTypeId: 'search',
data: {
breakdownField: undefined,
columns: [],
description: '',
grid: {},
isTextBasedQuery: false,
hideAggregatedPreview: undefined,
hideChart: false,
isTextBasedQuery: false,
kibanaSavedObjectMeta: { searchSourceJSON: '{}' },
refreshInterval: undefined,
rowHeight: undefined,
rowsPerPage: undefined,
sort: [],
title: 'title',
timeRange: undefined,
timeRestore: false,
title: 'title',
usesAdHocDataView: undefined,
viewMode: undefined,
},
{ references: [] }
);
options: { references: [] },
});
});
test('should call savedObjectsClient.update for saving existing search', async () => {
await saveSavedSearch(savedSearch, {}, savedObjectsClient, undefined);
cmApi.update = jest.fn().mockReturnValue({
item: {
id: 'id',
},
});
expect(savedObjectsClient.update).toHaveBeenCalledWith(
'search',
'id',
{
await saveSavedSearch(savedSearch, {}, cmApi, undefined);
expect(cmApi.update).toHaveBeenCalledWith({
contentTypeId: 'search',
data: {
breakdownField: undefined,
columns: [],
description: '',
grid: {},
hideAggregatedPreview: undefined,
isTextBasedQuery: false,
hideChart: false,
kibanaSavedObjectMeta: { searchSourceJSON: '{}' },
refreshInterval: undefined,
rowHeight: undefined,
rowsPerPage: undefined,
timeRange: undefined,
sort: [],
title: 'title',
timeRestore: false,
usesAdHocDataView: undefined,
viewMode: undefined,
},
{ references: [] }
);
id: 'id',
options: { references: [] },
});
});
test('should call savedObjectsTagging.ui.updateTagsReferences', async () => {
cmApi.update = jest.fn().mockReturnValue({
item: {
id: 'id',
},
});
const savedObjectsTagging = {
ui: {
updateTagsReferences: jest.fn((_, tags) => tags),
@ -131,7 +189,7 @@ describe('saveSavedSearch', () => {
await saveSavedSearch(
{ ...savedSearch, tags: ['tag-1', 'tag-2'] },
{},
savedObjectsClient,
cmApi,
savedObjectsTagging
);
@ -139,21 +197,29 @@ describe('saveSavedSearch', () => {
[],
['tag-1', 'tag-2']
);
expect(savedObjectsClient.update).toHaveBeenCalledWith(
'search',
'id',
{
expect(cmApi.update).toHaveBeenCalledWith({
contentTypeId: 'search',
data: {
breakdownField: undefined,
columns: [],
description: '',
grid: {},
isTextBasedQuery: false,
hideAggregatedPreview: undefined,
hideChart: false,
isTextBasedQuery: false,
kibanaSavedObjectMeta: { searchSourceJSON: '{}' },
refreshInterval: undefined,
rowHeight: undefined,
rowsPerPage: undefined,
sort: [],
title: 'title',
timeRange: undefined,
timeRestore: false,
title: 'title',
usesAdHocDataView: undefined,
viewMode: undefined,
},
{ references: ['tag-1', 'tag-2'] }
);
id: 'id',
options: { references: ['tag-1', 'tag-2'] },
});
});
});

View file

@ -5,12 +5,13 @@
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import type { SavedObjectsClientContract, SavedObjectsStart } from '@kbn/core/public';
import type { SavedObjectsTaggingApi } from '@kbn/saved-objects-tagging-oss-plugin/public';
import type { SavedSearchAttributes } from '../../../common';
import type { ContentManagementPublicStart } from '@kbn/content-management-plugin/public';
import type { SavedSearch } from './types';
import { SAVED_SEARCH_TYPE } from './constants';
import { toSavedSearchAttributes } from './saved_searches_utils';
import type { SavedSearchCrudTypes } from '../../../common/content_management';
export interface SaveSavedSearchOptions {
onTitleDuplicate?: () => void;
@ -20,30 +21,34 @@ export interface SaveSavedSearchOptions {
const hasDuplicatedTitle = async (
title: string,
savedObjectsClient: SavedObjectsStart['client']
contentManagement: ContentManagementPublicStart['client']
): Promise<boolean | void> => {
if (!title) {
return;
}
const response = await savedObjectsClient.find({
type: SAVED_SEARCH_TYPE,
perPage: 10,
search: `"${title}"`,
searchFields: ['title'],
fields: ['title'],
const response = await contentManagement.search<
SavedSearchCrudTypes['SearchIn'],
SavedSearchCrudTypes['SearchOut']
>({
contentTypeId: SAVED_SEARCH_TYPE,
query: {
text: `"${title}"`,
},
options: {
searchFields: ['title'],
fields: ['title'],
},
});
return response.savedObjects.some(
(obj) => obj.get('title').toLowerCase() === title.toLowerCase()
);
return response.hits.some((obj) => obj.attributes.title.toLowerCase() === title.toLowerCase());
};
/** @internal **/
export const saveSavedSearch = async (
savedSearch: SavedSearch,
options: SaveSavedSearchOptions,
savedObjectsClient: SavedObjectsClientContract,
contentManagement: ContentManagementPublicStart['client'],
savedObjectsTagging: SavedObjectsTaggingApi | undefined
): Promise<string | undefined> => {
const isNew = options.copyOnSave || !savedSearch.id;
@ -53,7 +58,7 @@ export const saveSavedSearch = async (
isNew &&
!options.isTitleDuplicateConfirmed &&
options.onTitleDuplicate &&
(await hasDuplicatedTitle(savedSearch.title, savedObjectsClient))
(await hasDuplicatedTitle(savedSearch.title, contentManagement))
) {
options.onTitleDuplicate();
return;
@ -65,21 +70,27 @@ export const saveSavedSearch = async (
? savedObjectsTagging.ui.updateTagsReferences(originalReferences, savedSearch.tags ?? [])
: originalReferences;
const resp = isNew
? await savedObjectsClient.create<SavedSearchAttributes>(
SAVED_SEARCH_TYPE,
toSavedSearchAttributes(savedSearch, searchSourceJSON),
{
? await contentManagement.create<
SavedSearchCrudTypes['CreateIn'],
SavedSearchCrudTypes['CreateOut']
>({
contentTypeId: SAVED_SEARCH_TYPE,
data: toSavedSearchAttributes(savedSearch, searchSourceJSON),
options: {
references,
}
)
: await savedObjectsClient.update<SavedSearchAttributes>(
SAVED_SEARCH_TYPE,
savedSearch.id!,
toSavedSearchAttributes(savedSearch, searchSourceJSON),
{
},
})
: await contentManagement.update<
SavedSearchCrudTypes['UpdateIn'],
SavedSearchCrudTypes['UpdateOut']
>({
contentTypeId: SAVED_SEARCH_TYPE,
id: savedSearch.id!,
data: toSavedSearchAttributes(savedSearch, searchSourceJSON),
options: {
references,
}
);
},
});
return resp?.id;
return resp.item.id;
};

View file

@ -0,0 +1,77 @@
/*
* 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 type { DataPublicPluginStart } from '@kbn/data-plugin/public';
import type { ContentManagementPublicStart } from '@kbn/content-management-plugin/public';
import type { SpacesApi } from '@kbn/spaces-plugin/public';
import type { SavedObjectTaggingOssPluginStart } from '@kbn/saved-objects-tagging-oss-plugin/public';
import { getSavedSearch, saveSavedSearch, SaveSavedSearchOptions, getNewSavedSearch } from '.';
import type { SavedSearchCrudTypes } from '../../../common/content_management';
import { SavedSearchType } from '../../../common';
import type { SavedSearch } from '../../../common/types';
interface SavedSearchesServiceDeps {
search: DataPublicPluginStart['search'];
contentManagement: ContentManagementPublicStart['client'];
spaces?: SpacesApi;
savedObjectsTaggingOss?: SavedObjectTaggingOssPluginStart;
}
export class SavedSearchesService {
constructor(private deps: SavedSearchesServiceDeps) {}
get = (savedSearchId: string) => {
const { search, contentManagement, spaces, savedObjectsTaggingOss } = this.deps;
return getSavedSearch(savedSearchId, {
search,
contentManagement,
spaces,
savedObjectsTagging: savedObjectsTaggingOss?.getTaggingApi(),
});
};
getAll = async () => {
const { contentManagement } = this.deps;
const result = await contentManagement.search<
SavedSearchCrudTypes['SearchIn'],
SavedSearchCrudTypes['SearchOut']
>({
contentTypeId: SavedSearchType,
query: {},
});
return result.hits;
};
getNew = () => getNewSavedSearch({ search: this.deps.search });
find = async (search: string) => {
const { contentManagement } = this.deps;
const result = await contentManagement.search<
SavedSearchCrudTypes['SearchIn'],
SavedSearchCrudTypes['SearchOut']
>({
contentTypeId: SavedSearchType,
query: {
text: search,
},
options: {
searchFields: ['title'],
fields: ['title'],
},
});
return result.hits;
};
save = (savedSearch: SavedSearch, options: SaveSavedSearchOptions = {}) => {
const { contentManagement, savedObjectsTaggingOss } = this.deps;
return saveSavedSearch(
savedSearch,
options,
contentManagement,
savedObjectsTaggingOss?.getTaggingApi()
);
};
}

View file

@ -6,11 +6,7 @@
* Side Public License, v 1.
*/
import {
fromSavedSearchAttributes,
toSavedSearchAttributes,
throwErrorOnSavedSearchUrlConflict,
} from './saved_searches_utils';
import { fromSavedSearchAttributes, toSavedSearchAttributes } from './saved_searches_utils';
import { createSearchSourceMock } from '@kbn/data-plugin/public/mocks';
@ -37,6 +33,7 @@ describe('saved_searches_utils', () => {
'id',
attributes,
['tags-1', 'tags-2'],
[],
createSearchSourceMock(),
{}
)
@ -53,6 +50,7 @@ describe('saved_searches_utils', () => {
"hideChart": true,
"id": "id",
"isTextBasedQuery": false,
"references": Array [],
"refreshInterval": undefined,
"rowHeight": undefined,
"rowsPerPage": undefined,
@ -91,28 +89,6 @@ describe('saved_searches_utils', () => {
});
});
describe('throwErrorOnSavedSearchUrlConflict', () => {
test('should throw an error on url conflict', async () => {
let error = 'no error';
try {
await throwErrorOnSavedSearchUrlConflict({
id: 'id',
sharingSavedObjectProps: {
outcome: 'conflict',
errorJSON: '{}',
},
} as SavedSearch);
} catch (e) {
error = e.message;
}
expect(error).toBe(
'This search has the same URL as a legacy alias. Disable the alias to resolve this error : {}'
);
});
});
describe('toSavedSearchAttributes', () => {
test('should serialize SavedSearch attributes', () => {
const savedSearch: SavedSearch = {

View file

@ -5,37 +5,26 @@
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import { i18n } from '@kbn/i18n';
import { pick } from 'lodash';
import type { SavedObjectReference } from '@kbn/core-saved-objects-server';
import type { SavedSearchAttributes } from '../../../common';
import { fromSavedSearchAttributes as fromSavedSearchAttributesCommon } from '../../../common';
import type { SavedSearch } from './types';
export { getSavedSearchUrl, getSavedSearchFullPathUrl } from '../../../common';
export const getSavedSearchUrlConflictMessage = async (savedSearch: SavedSearch) =>
i18n.translate('savedSearch.legacyURLConflict.errorMessage', {
defaultMessage: `This search has the same URL as a legacy alias. Disable the alias to resolve this error : {json}`,
values: {
json: savedSearch.sharingSavedObjectProps?.errorJSON,
},
});
export const throwErrorOnSavedSearchUrlConflict = async (savedSearch: SavedSearch) => {
if (savedSearch.sharingSavedObjectProps?.errorJSON) {
throw new Error(await getSavedSearchUrlConflictMessage(savedSearch));
}
};
export const fromSavedSearchAttributes = (
id: string,
attributes: SavedSearchAttributes,
tags: string[] | undefined,
references: SavedObjectReference[] | undefined,
searchSource: SavedSearch['searchSource'],
sharingSavedObjectProps: SavedSearch['sharingSavedObjectProps']
): SavedSearch => ({
...fromSavedSearchAttributesCommon(id, attributes, tags, searchSource),
sharingSavedObjectProps,
references,
});
export const toSavedSearchAttributes = (

View file

@ -0,0 +1,9 @@
/*
* 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 { SavedSearchStorage } from './saved_search_storage';

View file

@ -0,0 +1,42 @@
/*
* 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 { SOContentStorage } from '@kbn/content-management-utils';
import type { SavedSearchCrudTypes } from '../../common/content_management';
import { SavedSearchType } from '../../common/content_management';
import { cmServicesDefinition } from '../../common/content_management/cm_services';
export class SavedSearchStorage extends SOContentStorage<SavedSearchCrudTypes> {
constructor() {
super({
savedObjectType: SavedSearchType,
cmServicesDefinition,
enableMSearch: true,
allowedSavedObjectAttributes: [
'title',
'sort',
'columns',
'description',
'grid',
'hideChart',
'isTextBasedQuery',
'usesAdHocDataView',
'kibanaSavedObjectMeta',
'viewMode',
'hideAggregatedPreview',
'rowHeight',
'timeRestore',
'timeRange',
'refreshInterval',
'rowsPerPage',
'breakdownField',
],
});
}
}

View file

@ -8,15 +8,27 @@
import { CoreSetup, CoreStart, Plugin } from '@kbn/core/server';
import type { PluginSetup as DataPluginSetup } from '@kbn/data-plugin/server';
import type { ContentManagementServerSetup } from '@kbn/content-management-plugin/server';
import { getSavedSearchObjectType } from './saved_objects';
import { SavedSearchType, LATEST_VERSION } from '../common';
import { SavedSearchStorage } from './content_management';
export class SavedSearchServerPlugin implements Plugin<object, object> {
public setup(
core: CoreSetup,
plugins: {
data: DataPluginSetup;
contentManagement: ContentManagementServerSetup;
}
) {
plugins.contentManagement.register({
id: SavedSearchType,
storage: new SavedSearchStorage(),
version: {
latest: LATEST_VERSION,
},
});
const getSearchSourceMigrations = plugins.data.search.searchSource.getAllMigrations.bind(
plugins.data.search.searchSource
);

View file

@ -19,6 +19,9 @@
"@kbn/config-schema",
"@kbn/core-saved-objects-server",
"@kbn/core-saved-objects-utils-server",
"@kbn/object-versioning",
"@kbn/content-management-utils",
"@kbn/content-management-plugin",
],
"exclude": [
"target/**/*",

View file

@ -19,9 +19,9 @@
"kibanaReact",
"data",
"fieldFormats",
"discover",
"esUiShared",
"visualizations"
"visualizations",
"savedSearch"
]
}
}

View file

@ -26,7 +26,7 @@ import {
} from '@kbn/visualizations-plugin/public';
import type { Schema } from '@kbn/visualizations-plugin/public';
import type { TimeRange } from '@kbn/es-query';
import { SavedSearch } from '@kbn/discover-plugin/public';
import { SavedSearch } from '@kbn/saved-search-plugin/public';
import { DefaultEditorNavBar } from './navbar';
import { DefaultEditorControls } from './controls';
import { setStateParamValue, useEditorReducer, useEditorFormState, discardChanges } from './state';

View file

@ -25,7 +25,7 @@ import { FormattedMessage } from '@kbn/i18n-react';
import { i18n } from '@kbn/i18n';
import { Vis } from '@kbn/visualizations-plugin/public';
import { SavedSearch, getSavedSearchUrl } from '@kbn/discover-plugin/public';
import { SavedSearch, getSavedSearchUrl } from '@kbn/saved-search-plugin/public';
import { ApplicationStart } from '@kbn/core/public';
import { useKibana } from '@kbn/kibana-react-plugin/public';

View file

@ -10,7 +10,6 @@
"@kbn/core",
"@kbn/data-plugin",
"@kbn/visualizations-plugin",
"@kbn/discover-plugin",
"@kbn/kibana-utils-plugin",
"@kbn/kibana-react-plugin",
"@kbn/field-formats-plugin",
@ -27,6 +26,7 @@
"@kbn/monaco",
"@kbn/es-ui-shared-plugin",
"@kbn/utility-types",
"@kbn/saved-search-plugin",
],
"exclude": [
"target/**/*",

View file

@ -26,6 +26,7 @@
"usageCollection",
"savedObjectsFinder",
"savedObjectsManagement",
"savedSearch",
"contentManagement",
],
"optionalPlugins": [
@ -36,7 +37,6 @@
],
"requiredBundles": [
"kibanaUtils",
"savedSearch",
"kibanaReact",
"charts"
],

View file

@ -25,6 +25,7 @@ import { screenshotModePluginMock } from '@kbn/screenshot-mode-plugin/public/moc
import { fieldFormatsServiceMock } from '@kbn/field-formats-plugin/public/mocks';
import { unifiedSearchPluginMock } from '@kbn/unified-search-plugin/public/mocks';
import { savedObjectsManagementPluginMock } from '@kbn/saved-objects-management-plugin/public/mocks';
import { savedSearchPluginMock } from '@kbn/saved-search-plugin/public/mocks';
import { ContentManagementPublicStart } from '@kbn/content-management-plugin/public';
import { VisualizationsPlugin } from './plugin';
import { Schemas } from './vis_types';
@ -77,6 +78,7 @@ const createInstance = async () => {
savedObjectsClient: coreMock.createStart().savedObjects.client,
savedObjects: savedObjectsPluginMock.createStartContract(),
savedObjectsTaggingOss: savedObjectTaggingOssPluginMock.createStart(),
savedSearch: savedSearchPluginMock.createStartContract(),
navigation: navigationPluginMock.createStartContract(),
presentationUtil: presentationUtilPluginMock.createStartContract(coreMock.createStart()),
urlForwarding: urlForwardingPluginMock.createStartContract(),

View file

@ -58,6 +58,7 @@ import type { HomePublicPluginSetup } from '@kbn/home-plugin/public';
import type { SpacesPluginStart } from '@kbn/spaces-plugin/public';
import type { DataViewEditorStart } from '@kbn/data-view-editor-plugin/public';
import { SavedObjectsManagementPluginStart } from '@kbn/saved-objects-management-plugin/public';
import type { SavedSearchPublicPluginStart } from '@kbn/saved-search-plugin/public';
import {
ContentManagementPublicSetup,
ContentManagementPublicStart,
@ -105,6 +106,7 @@ import {
setUsageCollection,
setSavedObjectsManagement,
setContentManagement,
setSavedSearch,
} from './services';
import { VisualizeConstants } from '../common/constants';
import { EditInLensAction } from './actions/edit_in_lens_action';
@ -148,6 +150,7 @@ export interface VisualizationsStartDeps {
presentationUtil: PresentationUtilPluginStart;
savedObjects: SavedObjectsStart;
savedObjectsClient: SavedObjectsClientContract;
savedSearch: SavedSearchPublicPluginStart;
spaces?: SpacesPluginStart;
savedObjectsTaggingOss?: SavedObjectTaggingOssPluginStart;
share?: SharePluginStart;
@ -313,6 +316,7 @@ export class VisualizationsPlugin
restorePreviousUrl,
setHeaderActionMenu: params.setHeaderActionMenu,
savedObjectsTagging: pluginsStart.savedObjectsTaggingOss?.getTaggingApi(),
savedSearch: pluginsStart.savedSearch,
presentationUtil: pluginsStart.presentationUtil,
getKibanaVersion: () => this.initializerContext.env.packageInfo.version,
spaces: pluginsStart.spaces,
@ -401,6 +405,7 @@ export class VisualizationsPlugin
usageCollection,
savedObjectsManagement,
contentManagement,
savedSearch,
}: VisualizationsStartDeps
): VisualizationsStart {
const types = this.types.start();
@ -423,6 +428,7 @@ export class VisualizationsPlugin
setUsageCollection(usageCollection);
setSavedObjectsManagement(savedObjectsManagement);
setContentManagement(contentManagement);
setSavedSearch(savedSearch);
if (spaces) {
setSpaces(spaces);

View file

@ -29,6 +29,7 @@ import type { SpacesPluginStart } from '@kbn/spaces-plugin/public';
import type { SavedObjectTaggingOssPluginStart } from '@kbn/saved-objects-tagging-oss-plugin/public';
import type { UsageCollectionStart } from '@kbn/usage-collection-plugin/public';
import { SavedObjectsManagementPluginStart } from '@kbn/saved-objects-management-plugin/public';
import type { SavedSearchPublicPluginStart } from '@kbn/saved-search-plugin/public';
import type { TypesStart } from './vis_types';
export const [getUISettings, setUISettings] = createGetterSetter<IUiSettingsClient>('UISettings');
@ -84,3 +85,6 @@ export const [getSavedObjectsManagement, setSavedObjectsManagement] =
export const [getContentManagement, setContentManagement] =
createGetterSetter<ContentManagementPublicStart>('SavedObjectsManagement');
export const [getSavedSearch, setSavedSearch] =
createGetterSetter<SavedSearchPublicPluginStart>('SavedSearch');

View file

@ -22,21 +22,9 @@ import { i18n } from '@kbn/i18n';
import { IAggConfigs, ISearchSource, AggConfigSerialized } from '@kbn/data-plugin/public';
import { DataView } from '@kbn/data-views-plugin/public';
import {
getSavedSearch,
SavedSearch,
throwErrorOnSavedSearchUrlConflict,
} from '@kbn/saved-search-plugin/public';
import { SavedSearch } from '@kbn/saved-search-plugin/public';
import { PersistedState } from './persisted_state';
import {
getTypes,
getAggs,
getSearch,
getSavedObjects,
getSpaces,
getFieldsFormats,
getSavedObjectTagging,
} from './services';
import { getTypes, getAggs, getSearch, getFieldsFormats, getSavedSearch } from './services';
import { BaseVisType } from './vis_types';
import { SerializedVis, SerializedVisData, VisParams } from '../common/types';
@ -55,18 +43,11 @@ const getSearchSource = async (inputSearchSource: ISearchSource, savedSearchId?:
let savedSearch: SavedSearch;
try {
savedSearch = await getSavedSearch(savedSearchId, {
search: getSearch(),
savedObjectsClient: getSavedObjects().client,
spaces: getSpaces(),
savedObjectsTagging: getSavedObjectTagging()?.getTaggingApi(),
});
savedSearch = await getSavedSearch().get(savedSearchId);
} catch (e) {
return inputSearchSource;
}
await throwErrorOnSavedSearchUrlConflict(savedSearch);
if (savedSearch?.searchSource) {
inputSearchSource.setParent(savedSearch.searchSource);
}

View file

@ -39,7 +39,7 @@ import type { UrlForwardingStart } from '@kbn/url-forwarding-plugin/public';
import type { PresentationUtilPluginStart } from '@kbn/presentation-util-plugin/public';
import type { SpacesPluginStart } from '@kbn/spaces-plugin/public';
import type { SavedObjectsTaggingApi } from '@kbn/saved-objects-tagging-oss-plugin/public';
import type { SavedSearch } from '@kbn/saved-search-plugin/public';
import type { SavedSearch, SavedSearchPublicPluginStart } from '@kbn/saved-search-plugin/public';
import type {
Vis,
VisualizeEmbeddableContract,
@ -107,6 +107,7 @@ export interface VisualizeServices extends CoreStart {
scopedHistory: ScopedHistory;
setHeaderActionMenu: AppMountParameters['setHeaderActionMenu'];
savedObjectsTagging?: SavedObjectsTaggingApi;
savedSearch: SavedSearchPublicPluginStart;
presentationUtil: PresentationUtilPluginStart;
getKibanaVersion: () => string;
spaces?: SpacesPluginStart;

View file

@ -6,7 +6,6 @@
* Side Public License, v 1.
*/
import { getSavedSearch } from '@kbn/saved-search-plugin/public';
import type { VisualizeInput, VisSavedObject, Vis, VisParams } from '../..';
import {
getVisualizationInstance,
@ -38,15 +37,6 @@ jest.mock('../../vis_async', () => ({
}));
const { createVisAsync } = jest.requireMock('../../vis_async');
jest.mock('@kbn/saved-search-plugin/public', () => ({
getSavedSearch: jest.fn().mockResolvedValue({
id: 'savedSearch',
title: 'savedSearchTitle',
searchSource: {},
}),
throwErrorOnSavedSearchUrlConflict: jest.fn(),
}));
let savedVisMock: VisSavedObject;
describe('getVisualizationInstance', () => {
@ -69,6 +59,16 @@ describe('getVisualizationInstance', () => {
mockServices.createVisEmbeddableFromObject = jest.fn().mockImplementation(() => ({
getOutput$: jest.fn(() => subj.asObservable()),
}));
mockServices.savedSearch = {
get: jest.fn().mockImplementation(() => ({
id: 'savedSearch',
searchSource: {},
title: 'savedSearchTitle',
})),
getAll: jest.fn(),
getNew: jest.fn().mockImplementation(() => ({})),
save: jest.fn().mockImplementation(() => ({})),
};
});
test('should create new instances of savedVis, vis and embeddableHandler', async () => {
@ -120,7 +120,6 @@ describe('getVisualizationInstance', () => {
visMock.data.savedSearchId = 'saved_search_id';
const { savedSearch } = await getVisualizationInstance(mockServices, 'saved_vis_id');
expect(getSavedSearch).toHaveBeenCalled();
expect(savedSearch).toMatchInlineSnapshot(`
Object {
"id": "savedSearch",

View file

@ -9,11 +9,7 @@ import { cloneDeep } from 'lodash';
import type { SerializedSearchSourceFields } from '@kbn/data-plugin/public';
import type { ExpressionValueError } from '@kbn/expressions-plugin/public';
import { SavedFieldNotFound, SavedFieldTypeInvalidForAgg } from '@kbn/kibana-utils-plugin/common';
import {
getSavedSearch,
SavedSearch,
throwErrorOnSavedSearchUrlConflict,
} from '@kbn/saved-search-plugin/public';
import { SavedSearch } from '@kbn/saved-search-plugin/public';
import { createVisAsync } from '../../vis_async';
import { convertToSerializedVis, getSavedVisualization } from '../../utils/saved_visualize_utils';
import {
@ -37,26 +33,18 @@ const createVisualizeEmbeddableAndLinkSavedSearch = async (
vis: Vis,
visualizeServices: VisualizeServices
) => {
const { data, createVisEmbeddableFromObject, savedObjects, spaces, savedObjectsTagging } =
visualizeServices;
const { data, createVisEmbeddableFromObject, savedSearch: savedSearchApi } = visualizeServices;
let savedSearch: SavedSearch | undefined;
if (vis.data.savedSearchId) {
try {
savedSearch = await getSavedSearch(vis.data.savedSearchId, {
search: data.search,
savedObjectsClient: savedObjects.client,
spaces,
savedObjectsTagging,
});
savedSearch = vis.data.savedSearchId
? await savedSearchApi.get(vis.data.savedSearchId)
: await savedSearchApi.getNew();
} catch (e) {
// skip this catch block
}
if (savedSearch) {
await throwErrorOnSavedSearchUrlConflict(savedSearch);
}
}
const embeddableHandler = (await createVisEmbeddableFromObject(vis, {

View file

@ -11,7 +11,7 @@
import { cloneDeep } from 'lodash';
import { IUiSettingsClient } from '@kbn/core/public';
import { getEsQueryConfig, SearchSource } from '@kbn/data-plugin/common';
import type { SavedSearch } from '@kbn/discover-plugin/public';
import type { SavedSearch } from '@kbn/saved-search-plugin/public';
import { FilterManager, isQuery, mapAndFlattenFilters } from '@kbn/data-plugin/public';
import {
fromKueryExpression,

View file

@ -11,7 +11,7 @@ import { pick } from 'lodash';
import { EuiCallOut } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import type { SavedSearch } from '@kbn/discover-plugin/public';
import type { SavedSearch } from '@kbn/saved-search-plugin/public';
import type { DataView } from '@kbn/data-views-plugin/public';
import { StorageContextProvider } from '@kbn/ml-local-storage';
import { UrlStateProvider } from '@kbn/ml-url-state';

View file

@ -7,7 +7,7 @@
import React, { FC } from 'react';
import { pick } from 'lodash';
import type { SavedSearch } from '@kbn/discover-plugin/public';
import type { SavedSearch } from '@kbn/saved-search-plugin/public';
import type { DataView } from '@kbn/data-views-plugin/public';
import { StorageContextProvider } from '@kbn/ml-local-storage';
import { UrlStateProvider } from '@kbn/ml-url-state';

View file

@ -5,7 +5,7 @@
* 2.0.
*/
import React, { FC, useState, useEffect, useCallback, useRef, useMemo } from 'react';
import type { SavedSearch } from '@kbn/discover-plugin/public';
import type { SavedSearch } from '@kbn/saved-search-plugin/public';
import type { DataView, DataViewField } from '@kbn/data-views-plugin/public';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n-react';

View file

@ -12,6 +12,7 @@ import type { Moment } from 'moment';
import { useExecutionContext } from '@kbn/kibana-react-plugin/public';
import type { DataView } from '@kbn/data-views-plugin/public';
import type { SignificantTerm } from '@kbn/ml-agg-utils';
import type { Dictionary } from '@kbn/ml-url-state';
import { mlTimefilterRefresh$, useTimefilter } from '@kbn/ml-date-picker';
import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey';

View file

@ -6,7 +6,7 @@
*/
import type { DataView } from '@kbn/data-views-plugin/public';
import type { SavedSearch } from '@kbn/discover-plugin/public';
import type { SavedSearch } from '@kbn/saved-search-plugin/public';
import { getEsQueryFromSavedSearch } from '../application/utils/search_utils';
import type { AiOpsIndexBasedAppState } from '../application/utils/url_state';

View file

@ -22,7 +22,6 @@
"@kbn/data-plugin",
"@kbn/data-views-plugin",
"@kbn/datemath",
"@kbn/discover-plugin",
"@kbn/es-query",
"@kbn/es-types",
"@kbn/field-formats-plugin",

View file

@ -16,7 +16,8 @@
"fileUpload",
"uiActions",
"charts",
"unifiedSearch"
"unifiedSearch",
"savedSearch"
],
"optionalPlugins": [
"security",
@ -37,7 +38,6 @@
"unifiedFieldList",
"lens",
"cloudChat",
"savedSearch"
]
}
}

View file

@ -27,7 +27,7 @@ import { KibanaContextProvider, KibanaThemeProvider } from '@kbn/kibana-react-pl
import type { Query } from '@kbn/es-query';
import { DataView, DataViewField } from '@kbn/data-views-plugin/public';
import { DatePickerContextProvider } from '@kbn/ml-date-picker';
import type { SavedSearch } from '@kbn/discover-plugin/public';
import type { SavedSearch } from '@kbn/saved-search-plugin/public';
import type { SamplingOption } from '../../../../../common/types/field_stats';
import { DATA_VISUALIZER_GRID_EMBEDDABLE_TYPE } from './constants';
import { EmbeddableLoading } from './embeddable_loading_fallback';

View file

@ -32,7 +32,7 @@ import {
type Dictionary,
type SetUrlState,
} from '@kbn/ml-url-state';
import { getSavedSearch, type SavedSearch } from '@kbn/saved-search-plugin/public';
import type { SavedSearch } from '@kbn/saved-search-plugin/public';
import { getCoreStart, getPluginsStart } from '../../kibana_services';
import {
type IndexDataVisualizerViewProps,
@ -94,6 +94,7 @@ export const DataVisualizerStateContextProvider: FC<DataVisualizerStateContextPr
data: { dataViews, search },
savedObjects: { client: savedObjectsClient },
notifications: { toasts },
savedSearch: savedSearchService,
} = services;
const history = useHistory();
@ -159,10 +160,7 @@ export const DataVisualizerStateContextProvider: FC<DataVisualizerStateContextPr
if (typeof parsedQueryString?.savedSearchId === 'string') {
const savedSearchId = parsedQueryString.savedSearchId;
try {
const savedSearch = await getSavedSearch(savedSearchId, {
search,
savedObjectsClient,
});
const savedSearch = await savedSearchService.get(savedSearchId);
const dataView = savedSearch.searchSource.getField('index');
if (!dataView) {
@ -190,7 +188,7 @@ export const DataVisualizerStateContextProvider: FC<DataVisualizerStateContextPr
}
};
getDataView();
}, [savedObjectsClient, toasts, dataViews, urlSearchString, search]);
}, [savedObjectsClient, toasts, dataViews, urlSearchString, search, savedSearchService]);
const setUrlState: SetUrlState = useCallback(
(

View file

@ -11,7 +11,7 @@ import {
getEsQueryFromSavedSearch,
} from './saved_search_utils';
import type { SavedSearchSavedObject } from '../../../../common/types';
import type { SavedSearch } from '@kbn/discover-plugin/public';
import type { SavedSearch } from '@kbn/saved-search-plugin/public';
import { type Filter, FilterStateStore } from '@kbn/es-query';
import { stubbedSavedObjectIndexPattern } from '@kbn/data-views-plugin/common/data_view.stub';
import { DataView } from '@kbn/data-views-plugin/public';

View file

@ -15,6 +15,7 @@ import { Plugin } from '@kbn/core/public';
import type { DataPublicPluginStart } from '@kbn/data-plugin/public';
import type { HomePublicPluginSetup } from '@kbn/home-plugin/public';
import type { SavedSearchPublicPluginStart } from '@kbn/saved-search-plugin/public';
import type { FileUploadPluginStart } from '@kbn/file-upload-plugin/public';
import type { UnifiedSearchPublicPluginStart } from '@kbn/unified-search-plugin/public';
import type { MapsStartApi } from '@kbn/maps-plugin/public';
@ -51,6 +52,7 @@ export interface DataVisualizerStartDependencies {
fieldFormats: FieldFormatsStart;
uiActions?: UiActionsStart;
cloud?: CloudStart;
savedSearch: SavedSearchPublicPluginStart;
}
export type DataVisualizerPluginSetup = ReturnType<DataVisualizerPlugin['setup']>;

View file

@ -20,10 +20,4 @@ export interface Route {
// TODO define saved object type
export type SavedSearchSavedObject = SimpleSavedObject<any>;
export function isSavedSearchSavedObject(
ss: SavedSearchSavedObject | null
): ss is SavedSearchSavedObject {
return ss !== null;
}
export type FieldFormatsRegistryProvider = () => Promise<FieldFormatsRegistry>;

View file

@ -30,7 +30,8 @@
"uiActions",
"unifiedSearch",
"savedObjectsFinder",
"savedObjectsManagement"
"savedObjectsManagement",
"savedSearch"
],
"optionalPlugins": [
"alerting",
@ -54,7 +55,6 @@
"kibanaUtils",
"lens",
"maps",
"savedSearch",
"usageCollection",
"unifiedFieldList",
"unifiedSearch"

View file

@ -97,10 +97,6 @@ const App: FC<AppProps> = ({ coreStart, deps, appMountParams }) => {
config: coreStart.uiSettings!,
setBreadcrumbs: coreStart.chrome!.setBreadcrumbs,
redirectToMlAccessDeniedPage,
getSavedSearchDeps: {
search: deps.data.search,
savedObjectsClient: coreStart.savedObjects.client,
},
};
const services = {
@ -123,6 +119,7 @@ const App: FC<AppProps> = ({ coreStart, deps, appMountParams }) => {
licensing: deps.licensing,
lens: deps.lens,
savedObjectsManagement: deps.savedObjectsManagement,
savedSearch: deps.savedSearch,
...coreStart,
};
@ -177,6 +174,7 @@ export const renderApp = (
recentlyAccessed: coreStart.chrome!.recentlyAccessed,
basePath: coreStart.http.basePath,
savedObjectsClient: coreStart.savedObjects.client,
savedSearch: deps.savedSearch,
application: coreStart.application,
http: coreStart.http,
security: deps.security,

View file

@ -7,8 +7,7 @@
import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
import type { DataView } from '@kbn/data-views-plugin/public';
import { SavedSearchSavedObject } from '../../../../../../common/types/kibana';
import type { SavedSearch } from '@kbn/saved-search-plugin/public';
import { JobCreator } from './job_creator';
import { Field, Aggregation, SplitField } from '../../../../../../common/types/fields';
import {
@ -39,7 +38,7 @@ export class AdvancedJobCreator extends JobCreator {
private _richDetectors: RichDetector[] = [];
private _queryString: string;
constructor(indexPattern: DataView, savedSearch: SavedSearchSavedObject | null, query: object) {
constructor(indexPattern: DataView, savedSearch: SavedSearch | null, query: object) {
super(indexPattern, savedSearch, query);
this._queryString = JSON.stringify(this._datafeed_config.query);

View file

@ -7,7 +7,7 @@
import { isEqual } from 'lodash';
import type { DataView } from '@kbn/data-views-plugin/public';
import { SavedSearchSavedObject } from '../../../../../../common/types/kibana';
import type { SavedSearch } from '@kbn/saved-search-plugin/public';
import { JobCreator } from './job_creator';
import { Field, Aggregation, mlCategory } from '../../../../../../common/types/fields';
import { Job, Datafeed, Detector } from '../../../../../../common/types/anomaly_detection_jobs';
@ -47,7 +47,7 @@ export class CategorizationJobCreator extends JobCreator {
private _partitionFieldName: string | null = null;
private _ccsVersionFailure: boolean = false;
constructor(indexPattern: DataView, savedSearch: SavedSearchSavedObject | null, query: object) {
constructor(indexPattern: DataView, savedSearch: SavedSearch | null, query: object) {
super(indexPattern, savedSearch, query);
this.createdBy = CREATED_BY_LABEL.CATEGORIZATION;
this._examplesLoader = new CategorizationExamplesLoader(this, indexPattern, query);

View file

@ -6,7 +6,7 @@
*/
import type { DataView } from '@kbn/data-views-plugin/public';
import { SavedSearchSavedObject } from '../../../../../../common/types/kibana';
import type { SavedSearch } from '@kbn/saved-search-plugin/public';
import { JobCreator } from './job_creator';
import {
Field,
@ -28,7 +28,7 @@ export class GeoJobCreator extends JobCreator {
protected _type: JOB_TYPE = JOB_TYPE.GEO;
constructor(indexPattern: DataView, savedSearch: SavedSearchSavedObject | null, query: object) {
constructor(indexPattern: DataView, savedSearch: SavedSearch | null, query: object) {
super(indexPattern, savedSearch, query);
this.createdBy = CREATED_BY_LABEL.GEO;
this._wizardInitialized$.next(true);

View file

@ -12,7 +12,7 @@ import type { Query } from '@kbn/es-query';
import type { DataView } from '@kbn/data-views-plugin/public';
import { addExcludeFrozenToQuery } from '@kbn/ml-query-utils';
import { MlUrlConfig } from '@kbn/ml-anomaly-utils';
import { SavedSearchSavedObject } from '../../../../../../common/types/kibana';
import type { SavedSearch } from '@kbn/saved-search-plugin/public';
import { IndexPatternTitle } from '../../../../../../common/types/kibana';
import {
ML_JOB_AGGREGATION,
@ -51,7 +51,7 @@ import { ml } from '../../../../services/ml_api_service';
export class JobCreator {
protected _type: JOB_TYPE = JOB_TYPE.SINGLE_METRIC;
protected _indexPattern: DataView;
protected _savedSearch: SavedSearchSavedObject | null;
protected _savedSearch: SavedSearch | null;
protected _indexPatternTitle: IndexPatternTitle = '';
protected _indexPatternDisplayName: string = '';
protected _job_config: Job;
@ -79,7 +79,7 @@ export class JobCreator {
protected _wizardInitialized$ = new BehaviorSubject<boolean>(false);
public wizardInitialized$ = this._wizardInitialized$.asObservable();
constructor(indexPattern: DataView, savedSearch: SavedSearchSavedObject | null, query: object) {
constructor(indexPattern: DataView, savedSearch: SavedSearch | null, query: object) {
this._indexPattern = indexPattern;
this._savedSearch = savedSearch;
@ -107,7 +107,7 @@ export class JobCreator {
return this._type;
}
public get savedSearch(): SavedSearchSavedObject | null {
public get savedSearch(): SavedSearch | null {
return this._savedSearch;
}

View file

@ -6,7 +6,7 @@
*/
import type { DataView } from '@kbn/data-views-plugin/public';
import { SavedSearchSavedObject } from '../../../../../../common/types/kibana';
import type { SavedSearch } from '@kbn/saved-search-plugin/public';
import { SingleMetricJobCreator } from './single_metric_job_creator';
import { MultiMetricJobCreator } from './multi_metric_job_creator';
import { PopulationJobCreator } from './population_job_creator';
@ -19,7 +19,7 @@ import { JOB_TYPE } from '../../../../../../common/constants/new_job';
export const jobCreatorFactory =
(jobType: JOB_TYPE) =>
(indexPattern: DataView, savedSearch: SavedSearchSavedObject | null, query: object) => {
(indexPattern: DataView, savedSearch: SavedSearch | null, query: object) => {
let jc;
switch (jobType) {
case JOB_TYPE.SINGLE_METRIC:

View file

@ -6,7 +6,7 @@
*/
import type { DataView } from '@kbn/data-views-plugin/public';
import { SavedSearchSavedObject } from '../../../../../../common/types/kibana';
import type { SavedSearch } from '@kbn/saved-search-plugin/public';
import { JobCreator } from './job_creator';
import {
Field,
@ -27,7 +27,7 @@ export class MultiMetricJobCreator extends JobCreator {
protected _type: JOB_TYPE = JOB_TYPE.MULTI_METRIC;
constructor(indexPattern: DataView, savedSearch: SavedSearchSavedObject | null, query: object) {
constructor(indexPattern: DataView, savedSearch: SavedSearch | null, query: object) {
super(indexPattern, savedSearch, query);
this.createdBy = CREATED_BY_LABEL.MULTI_METRIC;
this._wizardInitialized$.next(true);

View file

@ -6,7 +6,7 @@
*/
import type { DataView } from '@kbn/data-views-plugin/public';
import { SavedSearchSavedObject } from '../../../../../../common/types/kibana';
import type { SavedSearch } from '@kbn/saved-search-plugin/public';
import { JobCreator } from './job_creator';
import {
Field,
@ -26,7 +26,7 @@ export class PopulationJobCreator extends JobCreator {
private _byFields: SplitField[] = [];
protected _type: JOB_TYPE = JOB_TYPE.POPULATION;
constructor(indexPattern: DataView, savedSearch: SavedSearchSavedObject | null, query: object) {
constructor(indexPattern: DataView, savedSearch: SavedSearch | null, query: object) {
super(indexPattern, savedSearch, query);
this.createdBy = CREATED_BY_LABEL.POPULATION;
this._wizardInitialized$.next(true);

View file

@ -6,7 +6,7 @@
*/
import type { DataView } from '@kbn/data-views-plugin/public';
import { SavedSearchSavedObject } from '../../../../../../common/types/kibana';
import type { SavedSearch } from '@kbn/saved-search-plugin/public';
import { JobCreator } from './job_creator';
import { Field, SplitField, Aggregation } from '../../../../../../common/types/fields';
import { Job, Datafeed, Detector } from '../../../../../../common/types/anomaly_detection_jobs';
@ -26,7 +26,7 @@ export class RareJobCreator extends JobCreator {
private _rareAgg: Aggregation;
private _freqRareAgg: Aggregation;
constructor(indexPattern: DataView, savedSearch: SavedSearchSavedObject | null, query: object) {
constructor(indexPattern: DataView, savedSearch: SavedSearch | null, query: object) {
super(indexPattern, savedSearch, query);
this.createdBy = CREATED_BY_LABEL.RARE;
this._wizardInitialized$.next(true);

View file

@ -6,7 +6,7 @@
*/
import type { DataView } from '@kbn/data-views-plugin/public';
import { SavedSearchSavedObject } from '../../../../../../common/types/kibana';
import type { SavedSearch } from '@kbn/saved-search-plugin/public';
import { parseInterval } from '../../../../../../common/util/parse_interval';
import { JobCreator } from './job_creator';
import { Field, Aggregation, AggFieldPair } from '../../../../../../common/types/fields';
@ -29,7 +29,7 @@ import { isSparseDataJob } from './util/general';
export class SingleMetricJobCreator extends JobCreator {
protected _type: JOB_TYPE = JOB_TYPE.SINGLE_METRIC;
constructor(indexPattern: DataView, savedSearch: SavedSearchSavedObject | null, query: object) {
constructor(indexPattern: DataView, savedSearch: SavedSearch | null, query: object) {
super(indexPattern, savedSearch, query);
this.createdBy = CREATED_BY_LABEL.SINGLE_METRIC;
this._wizardInitialized$.next(true);

View file

@ -64,7 +64,7 @@ export const Page: FC<PageProps> = ({ existingJobsAndGroups, jobType }) => {
() =>
jobCreatorFactory(jobType)(
mlContext.currentDataView,
mlContext.deprecatedSavedSearchObj,
mlContext.selectedSavedSearch,
mlContext.combinedQuery
),
// eslint-disable-next-line react-hooks/exhaustive-deps

View file

@ -7,8 +7,9 @@
import { IUiSettingsClient } from '@kbn/core/public';
import { DataView } from '@kbn/data-views-plugin/common';
import { SavedSearchSavedObject } from '../../../../../common/types/kibana';
import { createSearchItems } from './new_job_utils';
import { fromSavedSearchAttributes } from '@kbn/saved-search-plugin/public/services/saved_searches/saved_searches_utils';
import type { ISearchSource } from '@kbn/data-plugin/public';
describe('createSearchItems', () => {
const kibanaConfig = {} as IUiSettingsClient;
@ -16,45 +17,43 @@ describe('createSearchItems', () => {
fields: [],
} as unknown as DataView;
let savedSearch = {} as unknown as SavedSearchSavedObject;
beforeEach(() => {
savedSearch = {
client: {
http: {
basePath: {
basePath: '/abc',
serverBasePath: '/abc',
},
anonymousPaths: {},
},
batchQueue: [],
},
attributes: {
const getFieldMock = (searchSource: any) =>
jest.fn().mockImplementation((name: string) => {
if (name === 'query') {
return searchSource.query;
} else {
return searchSource.filter;
}
});
const getSavedSearchMock = (searchSource: any = {}) =>
fromSavedSearchAttributes(
'4b9b1010-c678-11ea-b6e6-e942978da29c',
{
title: 'not test',
description: '',
hits: 0,
columns: ['_source'],
sort: [],
version: 1,
kibanaSavedObjectMeta: {
searchSourceJSON: '',
},
grid: {},
hideChart: false,
isTextBasedQuery: false,
},
_version: 'WzI0OSw0XQ==',
id: '4b9b1010-c678-11ea-b6e6-e942978da29c',
type: 'search',
migrationVersion: {
search: '7.4.0',
},
references: [
[],
[
{
name: 'kibanaSavedObjectMeta.searchSourceJSON.index',
type: 'index-pattern',
id: '7e252840-bd27-11ea-8a6c-75d1a0bd08ab',
},
],
} as unknown as SavedSearchSavedObject;
});
{
getField: getFieldMock(searchSource),
} as unknown as ISearchSource,
{}
);
test('should match data view', () => {
const resp = createSearchItems(kibanaConfig, indexPattern, null);
@ -65,14 +64,13 @@ describe('createSearchItems', () => {
});
test('should match saved search with kuery and condition', () => {
const searchSource = {
const savedSearch = getSavedSearchMock({
highlightAll: true,
version: true,
query: { query: 'airline : "AAL" ', language: 'kuery' },
filter: [],
indexRefName: 'kibanaSavedObjectMeta.searchSourceJSON.index',
};
savedSearch.attributes.kibanaSavedObjectMeta.searchSourceJSON = JSON.stringify(searchSource);
});
const resp = createSearchItems(kibanaConfig, indexPattern, savedSearch);
expect(resp).toStrictEqual({
@ -92,14 +90,13 @@ describe('createSearchItems', () => {
});
test('should match saved search with kuery and not condition', () => {
const searchSource = {
const savedSearch = getSavedSearchMock({
highlightAll: true,
version: true,
query: { query: 'NOT airline : "AAL" ', language: 'kuery' },
filter: [],
indexRefName: 'kibanaSavedObjectMeta.searchSourceJSON.index',
};
savedSearch.attributes.kibanaSavedObjectMeta.searchSourceJSON = JSON.stringify(searchSource);
});
const resp = createSearchItems(kibanaConfig, indexPattern, savedSearch);
expect(resp).toStrictEqual({
@ -130,14 +127,13 @@ describe('createSearchItems', () => {
});
test('should match saved search with kuery and condition and not condition', () => {
const searchSource = {
const savedSearch = getSavedSearchMock({
highlightAll: true,
version: true,
query: { query: 'airline : "AAL" and NOT airline : "AWE" ', language: 'kuery' },
filter: [],
indexRefName: 'kibanaSavedObjectMeta.searchSourceJSON.index',
};
savedSearch.attributes.kibanaSavedObjectMeta.searchSourceJSON = JSON.stringify(searchSource);
});
const resp = createSearchItems(kibanaConfig, indexPattern, savedSearch);
expect(resp).toStrictEqual({
@ -161,7 +157,7 @@ describe('createSearchItems', () => {
});
test('should match saved search with kuery and filter', () => {
const searchSource = {
const savedSearch = getSavedSearchMock({
highlightAll: true,
version: true,
query: {
@ -192,8 +188,7 @@ describe('createSearchItems', () => {
},
],
indexRefName: 'kibanaSavedObjectMeta.searchSourceJSON.index',
};
savedSearch.attributes.kibanaSavedObjectMeta.searchSourceJSON = JSON.stringify(searchSource);
});
const resp = createSearchItems(kibanaConfig, indexPattern, savedSearch);
expect(resp).toStrictEqual({

View file

@ -18,8 +18,8 @@ import {
import { Filter } from '@kbn/es-query';
import { IUiSettingsClient } from '@kbn/core/public';
import { getEsQueryConfig } from '@kbn/data-plugin/public';
import type { SavedSearch } from '@kbn/saved-search-plugin/public';
import { SEARCH_QUERY_LANGUAGE } from '../../../../../common/constants/search';
import { SavedSearchSavedObject } from '../../../../../common/types/kibana';
import { getQueryFromSavedSearchObject } from '../../../util/index_utils';
// Provider for creating the items used for searching and job creation.
@ -50,7 +50,7 @@ export function getDefaultQuery() {
export function createSearchItems(
kibanaConfig: IUiSettingsClient,
indexPattern: DataViewBase | undefined,
savedSearch: SavedSearchSavedObject | null
savedSearch: SavedSearch | null
) {
// query is only used by the data visualizer as it needs
// a lucene query_string.

View file

@ -19,8 +19,6 @@ import type { DataViewsContract } from '@kbn/data-views-plugin/public';
import { EuiSkeletonText } from '@elastic/eui';
import { UrlStateProvider } from '@kbn/ml-url-state';
import type { DataPublicPluginStart } from '@kbn/data-plugin/public';
import { SavedObjectsClientContract } from '@kbn/core/public';
import { MlNotificationsContextProvider } from '../contexts/ml/ml_notifications_context';
import { MlContext, MlContextValue } from '../contexts/ml';
@ -67,10 +65,6 @@ export interface PageDependencies {
dataViewsContract: DataViewsContract;
setBreadcrumbs: ChromeStart['setBreadcrumbs'];
redirectToMlAccessDeniedPage: () => Promise<void>;
getSavedSearchDeps: {
search: DataPublicPluginStart['search'];
savedObjectsClient: SavedObjectsClientContract;
};
}
export const PageLoader: FC<{ context: MlContextValue }> = ({ context, children }) => {

View file

@ -30,14 +30,7 @@ export const accessDeniedRouteFactory = (): MlRoute => ({
});
const PageWrapper: FC<PageProps> = ({ deps }) => {
const { context } = useResolver(
undefined,
undefined,
deps.config,
deps.dataViewsContract,
deps.getSavedSearchDeps,
{}
);
const { context } = useResolver(undefined, undefined, deps.config, deps.dataViewsContract, {});
return (
<PageLoader context={context}>

View file

@ -43,17 +43,10 @@ export const changePointDetectionRouteFactory = (
const PageWrapper: FC<PageProps> = ({ location, deps }) => {
const { index, savedSearchId }: Record<string, any> = parse(location.search, { sort: false });
const { context } = useResolver(
index,
savedSearchId,
deps.config,
deps.dataViewsContract,
deps.getSavedSearchDeps,
{
checkBasicLicense,
cacheDataViewsContract: () => cacheDataViewsContract(deps.dataViewsContract),
}
);
const { context } = useResolver(index, savedSearchId, deps.config, deps.dataViewsContract, {
checkBasicLicense,
cacheDataViewsContract: () => cacheDataViewsContract(deps.dataViewsContract),
});
return (
<PageLoader context={context}>

View file

@ -54,19 +54,11 @@ const PageWrapper: FC<PageProps> = ({ location, deps, ...restProps }) => {
const { redirectToMlAccessDeniedPage } = deps;
const { index, savedSearchId }: Record<string, any> = parse(location.search, { sort: false });
const { context } = useResolver(
index,
savedSearchId,
deps.config,
deps.dataViewsContract,
deps.getSavedSearchDeps,
{
checkBasicLicense,
cacheDataViewsContract: () => cacheDataViewsContract(deps.dataViewsContract),
checkGetJobsCapabilities: () =>
checkGetJobsCapabilitiesResolver(redirectToMlAccessDeniedPage),
}
);
const { context } = useResolver(index, savedSearchId, deps.config, deps.dataViewsContract, {
checkBasicLicense,
cacheDataViewsContract: () => cacheDataViewsContract(deps.dataViewsContract),
checkGetJobsCapabilities: () => checkGetJobsCapabilitiesResolver(redirectToMlAccessDeniedPage),
});
return (
<PageLoader context={context}>

View file

@ -50,19 +50,11 @@ const PageWrapper: FC<PageProps> = ({ location, deps }) => {
const { redirectToMlAccessDeniedPage } = deps;
const { index, savedSearchId }: Record<string, any> = parse(location.search, { sort: false });
const { context } = useResolver(
index,
savedSearchId,
deps.config,
deps.dataViewsContract,
deps.getSavedSearchDeps,
{
checkBasicLicense,
cacheDataViewsContract: () => cacheDataViewsContract(deps.dataViewsContract),
checkGetJobsCapabilities: () =>
checkGetJobsCapabilitiesResolver(redirectToMlAccessDeniedPage),
}
);
const { context } = useResolver(index, savedSearchId, deps.config, deps.dataViewsContract, {
checkBasicLicense,
cacheDataViewsContract: () => cacheDataViewsContract(deps.dataViewsContract),
checkGetJobsCapabilities: () => checkGetJobsCapabilitiesResolver(redirectToMlAccessDeniedPage),
});
return (
<PageLoader context={context}>

View file

@ -48,18 +48,11 @@ const PageWrapper: FC<PageProps> = ({ location, deps }) => {
sort: false,
});
const { context } = useResolver(
index,
savedSearchId,
deps.config,
deps.dataViewsContract,
deps.getSavedSearchDeps,
{
...basicResolvers(deps),
analyticsFields: () =>
loadNewJobCapabilities(index, savedSearchId, deps.dataViewsContract, DATA_FRAME_ANALYTICS),
}
);
const { context } = useResolver(index, savedSearchId, deps.config, deps.dataViewsContract, {
...basicResolvers(deps),
analyticsFields: () =>
loadNewJobCapabilities(index, savedSearchId, deps.dataViewsContract, DATA_FRAME_ANALYTICS),
});
return (
<PageLoader context={context}>

View file

@ -46,7 +46,6 @@ const PageWrapper: FC<PageProps> = ({ deps }) => {
undefined,
deps.config,
deps.dataViewsContract,
deps.getSavedSearchDeps,
basicResolvers(deps)
);

View file

@ -44,7 +44,6 @@ const PageWrapper: FC<PageProps> = ({ location, deps }) => {
undefined,
deps.config,
deps.dataViewsContract,
deps.getSavedSearchDeps,
basicResolvers(deps)
);
return (

View file

@ -45,7 +45,6 @@ const PageWrapper: FC<PageProps> = ({ deps }) => {
undefined,
deps.config,
deps.dataViewsContract,
deps.getSavedSearchDeps,
basicResolvers(deps)
);

View file

@ -44,7 +44,6 @@ const PageWrapper: FC<PageProps> = ({ deps }) => {
undefined,
deps.config,
deps.dataViewsContract,
deps.getSavedSearchDeps,
basicResolvers(deps)
);

View file

@ -35,18 +35,11 @@ export const selectorRouteFactory = (
const PageWrapper: FC<PageProps> = ({ location, deps }) => {
const { redirectToMlAccessDeniedPage } = deps;
const { context } = useResolver(
undefined,
undefined,
deps.config,
deps.dataViewsContract,
deps.getSavedSearchDeps,
{
checkBasicLicense,
checkFindFileStructurePrivilege: () =>
checkFindFileStructurePrivilegeResolver(redirectToMlAccessDeniedPage),
}
);
const { context } = useResolver(undefined, undefined, deps.config, deps.dataViewsContract, {
checkBasicLicense,
checkFindFileStructurePrivilege: () =>
checkFindFileStructurePrivilegeResolver(redirectToMlAccessDeniedPage),
});
return (
<PageLoader context={context}>
<DatavisualizerSelector />

View file

@ -45,19 +45,12 @@ export const fileBasedRouteFactory = (
const PageWrapper: FC<PageProps> = ({ deps }) => {
const { redirectToMlAccessDeniedPage } = deps;
const { context } = useResolver(
undefined,
undefined,
deps.config,
deps.dataViewsContract,
deps.getSavedSearchDeps,
{
checkBasicLicense,
cacheDataViewsContract: () => cacheDataViewsContract(deps.dataViewsContract),
checkFindFileStructurePrivilege: () =>
checkFindFileStructurePrivilegeResolver(redirectToMlAccessDeniedPage),
}
);
const { context } = useResolver(undefined, undefined, deps.config, deps.dataViewsContract, {
checkBasicLicense,
cacheDataViewsContract: () => cacheDataViewsContract(deps.dataViewsContract),
checkFindFileStructurePrivilege: () =>
checkFindFileStructurePrivilegeResolver(redirectToMlAccessDeniedPage),
});
return (
<PageLoader context={context}>

View file

@ -47,19 +47,11 @@ const PageWrapper: FC<PageProps> = ({ location, deps }) => {
const { redirectToMlAccessDeniedPage } = deps;
const { index, savedSearchId }: Record<string, any> = parse(location.search, { sort: false });
const { context } = useResolver(
index,
savedSearchId,
deps.config,
deps.dataViewsContract,
deps.getSavedSearchDeps,
{
checkBasicLicense,
cacheDataViewsContract: () => cacheDataViewsContract(deps.dataViewsContract),
checkGetJobsCapabilities: () =>
checkGetJobsCapabilitiesResolver(redirectToMlAccessDeniedPage),
}
);
const { context } = useResolver(index, savedSearchId, deps.config, deps.dataViewsContract, {
checkBasicLicense,
cacheDataViewsContract: () => cacheDataViewsContract(deps.dataViewsContract),
checkGetJobsCapabilities: () => checkGetJobsCapabilitiesResolver(redirectToMlAccessDeniedPage),
});
return (
<PageLoader context={context}>

View file

@ -75,7 +75,6 @@ const PageWrapper: FC<PageProps> = ({ deps }) => {
undefined,
deps.config,
deps.dataViewsContract,
deps.getSavedSearchDeps,
{
...basicResolvers(deps),
jobs: mlJobService.loadJobsWrapper,

View file

@ -50,7 +50,6 @@ const PageWrapper: FC<PageProps> = ({ deps }) => {
undefined,
deps.config,
deps.dataViewsContract,
deps.getSavedSearchDeps,
basicResolvers(deps)
);
const timefilter = useTimefilter({ timeRangeSelector: false, autoRefreshSelector: true });

View file

@ -41,7 +41,6 @@ const PageWrapper: FC<PageProps> = ({ location, deps }) => {
undefined,
deps.config,
deps.dataViewsContract,
deps.getSavedSearchDeps,
basicResolvers(deps)
);

View file

@ -30,16 +30,9 @@ const PageWrapper: FC<PageProps> = ({ location, deps }) => {
}
);
const { context } = useResolver(
undefined,
undefined,
deps.config,
deps.dataViewsContract,
deps.getSavedSearchDeps,
{
redirect: () => resolver(lensId, vis, from, to, query, filters, layerIndex),
}
);
const { context } = useResolver(undefined, undefined, deps.config, deps.dataViewsContract, {
redirect: () => resolver(lensId, vis, from, to, query, filters, layerIndex),
});
return (
<PageLoader context={context}>
{<Redirect to={createPath(ML_PAGES.ANOMALY_DETECTION_CREATE_JOB)} />}

View file

@ -36,17 +36,10 @@ const PageWrapper: FC<PageProps> = ({ location, deps }) => {
sort: false,
});
const { context } = useResolver(
undefined,
undefined,
deps.config,
deps.dataViewsContract,
deps.getSavedSearchDeps,
{
redirect: () =>
resolver(dashboard, dataViewId, embeddable, geoField, splitField, from, to, layer),
}
);
const { context } = useResolver(undefined, undefined, deps.config, deps.dataViewsContract, {
redirect: () =>
resolver(dashboard, dataViewId, embeddable, geoField, splitField, from, to, layer),
});
return (
<PageLoader context={context}>
{<Redirect to={createPath(ML_PAGES.ANOMALY_DETECTION_CREATE_JOB)} />}

View file

@ -206,7 +206,6 @@ const PageWrapper: FC<IndexOrSearchPageProps> = ({ nextStepPath, deps, mode }) =
undefined,
deps.config,
deps.dataViewsContract,
deps.getSavedSearchDeps,
mode === MODE.NEW_JOB ? newJobResolvers : dataVizResolvers
);
return (

View file

@ -41,7 +41,6 @@ const PageWrapper: FC<PageProps> = ({ location, deps }) => {
savedSearchId,
deps.config,
deps.dataViewsContract,
deps.getSavedSearchDeps,
basicResolvers(deps)
);
return (

View file

@ -55,7 +55,6 @@ const PageWrapper: FC<PageProps> = ({ location, deps }) => {
savedSearchId,
deps.config,
deps.dataViewsContract,
deps.getSavedSearchDeps,
{
...basicResolvers(deps),
@ -79,7 +78,7 @@ const CheckViewOrCreateWrapper: FC<PageProps> = ({ location, deps }) => {
const navigateToPath = useNavigateToPath();
// the single resolver checkViewOrCreateJobs redirects only. so will always reject
useResolver(undefined, undefined, deps.config, deps.dataViewsContract, deps.getSavedSearchDeps, {
useResolver(undefined, undefined, deps.config, deps.dataViewsContract, {
checkViewOrCreateJobs: () =>
checkViewOrCreateJobs(moduleId, dataViewId, createLinkWithUserDefaults, navigateToPath),
});

View file

@ -199,7 +199,6 @@ const PageWrapper: FC<WizardPageProps> = ({ location, jobType, deps }) => {
savedSearchId,
deps.config,
deps.dataViewsContract,
deps.getSavedSearchDeps,
{
...basicResolvers(deps),
privileges: () => checkCreateJobsCapabilitiesResolver(redirectToJobsManagementPage),

View file

@ -46,20 +46,12 @@ export const notificationsRouteFactory = (
const PageWrapper: FC<PageProps> = ({ deps }) => {
const { redirectToMlAccessDeniedPage } = deps;
const { context } = useResolver(
undefined,
undefined,
deps.config,
deps.dataViewsContract,
deps.getSavedSearchDeps,
{
checkFullLicense,
checkGetJobsCapabilities: () =>
checkGetJobsCapabilitiesResolver(redirectToMlAccessDeniedPage),
getMlNodeCount,
loadMlServerInfo,
}
);
const { context } = useResolver(undefined, undefined, deps.config, deps.dataViewsContract, {
checkFullLicense,
checkGetJobsCapabilities: () => checkGetJobsCapabilitiesResolver(redirectToMlAccessDeniedPage),
getMlNodeCount,
loadMlServerInfo,
});
useTimefilter({ timeRangeSelector: false, autoRefreshSelector: false });
return (

View file

@ -50,20 +50,12 @@ export const overviewRouteFactory = (
const PageWrapper: FC<PageProps> = ({ deps }) => {
const { redirectToMlAccessDeniedPage } = deps;
const { context } = useResolver(
undefined,
undefined,
deps.config,
deps.dataViewsContract,
deps.getSavedSearchDeps,
{
checkFullLicense,
checkGetJobsCapabilities: () =>
checkGetJobsCapabilitiesResolver(redirectToMlAccessDeniedPage),
getMlNodeCount,
loadMlServerInfo,
}
);
const { context } = useResolver(undefined, undefined, deps.config, deps.dataViewsContract, {
checkFullLicense,
checkGetJobsCapabilities: () => checkGetJobsCapabilitiesResolver(redirectToMlAccessDeniedPage),
getMlNodeCount,
loadMlServerInfo,
});
useTimefilter({ timeRangeSelector: false, autoRefreshSelector: false });
return (

Some files were not shown because too many files have changed in this diff Show more