Skip to content

Commit

Permalink
[saved search] Remove saved object client from data views plugin for …
Browse files Browse the repository at this point in the history
…saved search usage (#159109)

## Summary

Previously the data plugin relied on the data view plugin to load saved
searches since the saved searches depend upon the data plugin and
circular dependencies needed to be avoided. This is innovative and
perhaps a bit crazy.

What this PR does
- Data view api no longer loads saved searches, removing browser saved
object client usage
- Moves `kibana_context` expression and getKibanaContext function from
data plugin to saved search plugin since it loads saved searches
- Rename data views `SavedObjectsClientCommon` to `PersistenceAPI` -
this is the abstraction around saved object loading that no longer is
exclusive to the saved objects api.
- Adds saved search server api (plugin contract) for loading saved
searches.
- Functional tests on browser and server for kibana_context expression
when loading saved searches

---------

Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
  • Loading branch information
mattkime and kibanamachine committed Jul 11, 2023
1 parent 672c90a commit d9d1404
Show file tree
Hide file tree
Showing 43 changed files with 502 additions and 372 deletions.
143 changes: 7 additions & 136 deletions src/plugins/data/common/search/expressions/kibana_context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,15 @@
* Side Public License, v 1.
*/

import { isEqual, uniqBy } from 'lodash';
import { i18n } from '@kbn/i18n';
import { ExpressionFunctionDefinition, ExecutionContext } from '@kbn/expressions-plugin/common';
import { Adapters } from '@kbn/inspector-plugin/common';
import { Filter, fromCombinedFilter } from '@kbn/es-query';
import { Query, uniqFilters } from '@kbn/es-query';
import { unboxExpressionValue } from '@kbn/expressions-plugin/common';
import { SavedObjectReference } from '@kbn/core/types';
import { SavedObjectsClientCommon } from '@kbn/data-views-plugin/common';
import { ExecutionContextSearch, KibanaContext, KibanaFilter } from './kibana_context_type';
import { KibanaQueryOutput } from './kibana_context_type';
import { KibanaTimerangeOutput } from './timerange';

export interface KibanaContextStartDependencies {
savedObjectsClient: SavedObjectsClientCommon;
}
import {
KibanaTimerangeOutput,
ExecutionContextSearch,
KibanaContext,
KibanaFilter,
KibanaQueryOutput,
} from '../..';

interface Arguments {
q?: KibanaQueryOutput[] | null;
Expand All @@ -37,125 +30,3 @@ export type ExpressionFunctionKibanaContext = ExpressionFunctionDefinition<
Promise<KibanaContext>,
ExecutionContext<Adapters, ExecutionContextSearch>
>;

const getParsedValue = (data: any, defaultValue: any) =>
typeof data === 'string' && data.length ? JSON.parse(data) || defaultValue : defaultValue;

const mergeQueries = (first: Query | Query[] = [], second: Query | Query[]) =>
uniqBy<Query>(
[...(Array.isArray(first) ? first : [first]), ...(Array.isArray(second) ? second : [second])],
(n: any) => JSON.stringify(n.query)
);

export const getKibanaContextFn = (
getStartDependencies: (
getKibanaRequest: ExecutionContext['getKibanaRequest']
) => Promise<KibanaContextStartDependencies>
) => {
const kibanaContextFunction: ExpressionFunctionKibanaContext = {
name: 'kibana_context',
type: 'kibana_context',
inputTypes: ['kibana_context', 'null'],
help: i18n.translate('data.search.functions.kibana_context.help', {
defaultMessage: 'Updates kibana global context',
}),
args: {
q: {
types: ['kibana_query', 'null'],
multi: true,
aliases: ['query', '_'],
help: i18n.translate('data.search.functions.kibana_context.q.help', {
defaultMessage: 'Specify Kibana free form text query',
}),
},
filters: {
types: ['kibana_filter', 'null'],
multi: true,
help: i18n.translate('data.search.functions.kibana_context.filters.help', {
defaultMessage: 'Specify Kibana generic filters',
}),
},
timeRange: {
types: ['timerange', 'null'],
default: null,
help: i18n.translate('data.search.functions.kibana_context.timeRange.help', {
defaultMessage: 'Specify Kibana time range filter',
}),
},
savedSearchId: {
types: ['string', 'null'],
default: null,
help: i18n.translate('data.search.functions.kibana_context.savedSearchId.help', {
defaultMessage: 'Specify saved search ID to be used for queries and filters',
}),
},
},

extract(state) {
const references: SavedObjectReference[] = [];
if (state.savedSearchId.length && typeof state.savedSearchId[0] === 'string') {
const refName = 'kibana_context.savedSearchId';
references.push({
name: refName,
type: 'search',
id: state.savedSearchId[0] as string,
});
return {
state: {
...state,
savedSearchId: [refName],
},
references,
};
}
return { state, references };
},

inject(state, references) {
const reference = references.find((r) => r.name === 'kibana_context.savedSearchId');
if (reference) {
state.savedSearchId[0] = reference.id;
}
return state;
},

async fn(input, args, { getKibanaRequest }) {
const { savedObjectsClient } = await getStartDependencies(getKibanaRequest);

const timeRange = args.timeRange || input?.timeRange;
let queries = mergeQueries(input?.query, args?.q?.filter(Boolean) || []);
const filterFromArgs = (args?.filters?.map(unboxExpressionValue) || []) as Filter[];

let filters = [...(input?.filters || [])];

if (args.savedSearchId) {
const obj = await savedObjectsClient.getSavedSearch(args.savedSearchId);
const search = (obj.attributes as any).kibanaSavedObjectMeta.searchSourceJSON as string;
const { query, filter } = getParsedValue(search, {});

if (query) {
queries = mergeQueries(queries, query);
}
if (filter) {
filters = [...filters, ...(Array.isArray(filter) ? filter : [filter])];
}
}
const uniqueArgFilters = filterFromArgs.filter(
(argF) =>
!filters.some((f) => {
return isEqual(fromCombinedFilter(f).query, argF.query);
})
);

filters = [...filters, ...uniqueArgFilters];

return {
type: 'kibana_context',
query: queries,
filters: uniqFilters(filters.filter((f: any) => !f.meta?.disabled)),
timeRange,
};
},
};
return kibanaContextFunction;
};
30 changes: 2 additions & 28 deletions src/plugins/data/common/search/expressions/kibana_context_type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@
* Side Public License, v 1.
*/
import { Filter } from '@kbn/es-query';
import { ExpressionValueBoxed, ExpressionValueFilter } from '@kbn/expressions-plugin/common';
import { ExpressionValueBoxed } from '@kbn/expressions-plugin/common';
import { Query, TimeRange } from '../../query';
import { adaptToExpressionValueFilter, DataViewField } from '../..';
import { DataViewField } from '../..';

// eslint-disable-next-line @typescript-eslint/consistent-type-definitions
export type ExecutionContextSearch = {
Expand All @@ -30,29 +30,3 @@ export type KibanaField = ExpressionValueBoxed<'kibana_field', DataViewField>;
// TODO: These two are exported for legacy reasons - remove them eventually.
export type KIBANA_CONTEXT_NAME = 'kibana_context';
export type KibanaContext = ExpressionValueSearchContext;

export const kibanaContext = {
name: 'kibana_context',
from: {
null: () => {
return {
type: 'kibana_context',
};
},
},
to: {
null: () => {
return {
type: 'null',
};
},
filter: (input: KibanaContext): ExpressionValueFilter => {
const { filters = [] } = input;
return {
type: 'filter',
filterType: 'filter',
and: filters.map(adaptToExpressionValueFilter),
};
},
},
};
9 changes: 1 addition & 8 deletions src/plugins/data/public/search/search_service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@ import {
ipRangeFunction,
ISearchGeneric,
kibana,
kibanaContext,
kibanaFilterFunction,
kibanaTimerangeFunction,
kqlFunction,
Expand All @@ -65,7 +64,7 @@ import { DataPublicPluginStart, DataStartDependencies } from '../types';
import { AggsService } from './aggs';
import { createUsageCollector, SearchUsageCollector } from './collectors';
import { getEql, getEsaggs, getEsdsl, getEssql } from './expressions';
import { getKibanaContext } from './expressions/kibana_context';

import { handleWarnings } from './fetch/handle_warnings';
import { ISearchInterceptor, SearchInterceptor } from './search_interceptor';
import { ISessionsClient, ISessionService, SessionsClient, SessionService } from './session';
Expand Down Expand Up @@ -143,11 +142,6 @@ export class SearchService implements Plugin<ISearchSetup, ISearchStart> {
})
);
expressions.registerFunction(kibana);
expressions.registerFunction(
getKibanaContext({ getStartServices } as {
getStartServices: StartServicesAccessor<DataStartDependencies, DataPublicPluginStart>;
})
);
expressions.registerFunction(cidrFunction);
expressions.registerFunction(dateRangeFunction);
expressions.registerFunction(extendedBoundsFunction);
Expand All @@ -167,7 +161,6 @@ export class SearchService implements Plugin<ISearchSetup, ISearchStart> {
expressions.registerFunction(removeFilterFunction);
expressions.registerFunction(selectFilterFunction);
expressions.registerFunction(phraseFilterFunction);
expressions.registerType(kibanaContext);

expressions.registerFunction(
getEsdsl({ getStartServices } as {
Expand Down
5 changes: 1 addition & 4 deletions src/plugins/data/server/search/search_service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,6 @@ import {
ipRangeFunction,
ISearchOptions,
kibana,
kibanaContext,
kibanaFilterFunction,
kibanaTimerangeFunction,
kqlFunction,
Expand All @@ -94,7 +93,6 @@ import { aggShardDelay } from '../../common/search/aggs/buckets/shard_delay_fn';
import { ConfigSchema } from '../../config';
import { SearchSessionService } from './session';
import { registerBsearchRoute } from './routes/bsearch';
import { getKibanaContext } from './expressions/kibana_context';
import { enhancedEsSearchStrategyProvider } from './strategies/ese_search';
import { eqlSearchStrategyProvider } from './strategies/eql_search';
import { NoSearchIdInSessionError } from './errors/no_search_id_in_session';
Expand Down Expand Up @@ -233,7 +231,6 @@ export class SearchService implements Plugin<ISearchSetup, ISearchStart> {
expressions.registerFunction(luceneFunction);
expressions.registerFunction(kqlFunction);
expressions.registerFunction(kibanaTimerangeFunction);
expressions.registerFunction(getKibanaContext({ getStartServices: core.getStartServices }));
expressions.registerFunction(fieldFunction);
expressions.registerFunction(numericalRangeFunction);
expressions.registerFunction(rangeFunction);
Expand All @@ -244,7 +241,7 @@ export class SearchService implements Plugin<ISearchSetup, ISearchStart> {
expressions.registerFunction(removeFilterFunction);
expressions.registerFunction(selectFilterFunction);
expressions.registerFunction(phraseFilterFunction);
expressions.registerType(kibanaContext);

expressions.registerType(esRawResponse);
expressions.registerType(eqlRawResponse);

Expand Down
2 changes: 1 addition & 1 deletion src/plugins/data/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@
"@kbn/core-application-browser",
"@kbn/core-saved-objects-server",
"@kbn/core-saved-objects-utils-server",
"@kbn/data-service",
"@kbn/data-service"
],
"exclude": [
"target/**/*",
Expand Down
10 changes: 5 additions & 5 deletions src/plugins/data_views/common/data_views/data_views.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { fieldFormatsMock } from '@kbn/field-formats-plugin/common/mocks';

import {
UiSettingsCommon,
SavedObjectsClientCommon,
PersistenceAPI,
SavedObject,
DataViewSpec,
IDataViewsApiClient,
Expand Down Expand Up @@ -60,7 +60,7 @@ const savedObject = {
describe('IndexPatterns', () => {
let indexPatterns: DataViewsService;
let indexPatternsNoAccess: DataViewsService;
let savedObjectsClient: SavedObjectsClientCommon;
let savedObjectsClient: PersistenceAPI;
let SOClientGetDelay = 0;
let apiClient: IDataViewsApiClient;
const uiSettings = {
Expand All @@ -73,7 +73,7 @@ describe('IndexPatterns', () => {

beforeEach(() => {
jest.clearAllMocks();
savedObjectsClient = {} as SavedObjectsClientCommon;
savedObjectsClient = {} as PersistenceAPI;
savedObjectsClient.find = jest.fn(
() => Promise.resolve([indexPatternObj]) as Promise<Array<SavedObject<any>>>
);
Expand Down Expand Up @@ -107,7 +107,7 @@ describe('IndexPatterns', () => {

indexPatterns = new DataViewsService({
uiSettings,
savedObjectsClient: savedObjectsClient as unknown as SavedObjectsClientCommon,
savedObjectsClient: savedObjectsClient as unknown as PersistenceAPI,
apiClient,
fieldFormats,
onNotification: () => {},
Expand All @@ -119,7 +119,7 @@ describe('IndexPatterns', () => {

indexPatternsNoAccess = new DataViewsService({
uiSettings,
savedObjectsClient: savedObjectsClient as unknown as SavedObjectsClientCommon,
savedObjectsClient: savedObjectsClient as unknown as PersistenceAPI,
apiClient,
fieldFormats,
onNotification: () => {},
Expand Down
6 changes: 3 additions & 3 deletions src/plugins/data_views/common/data_views/data_views.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import type { PublicMethodsOf } from '@kbn/utility-types';
import { castEsToKbnFieldTypeName } from '@kbn/field-types';
import { FieldFormatsStartCommon, FORMATS_UI_SETTINGS } from '@kbn/field-formats-plugin/common';
import { v4 as uuidv4 } from 'uuid';
import { SavedObjectsClientCommon } from '../types';
import { PersistenceAPI } from '../types';

import { createDataViewCache } from '.';
import type { RuntimeField, RuntimeFieldSpec, RuntimeType } from '../types';
Expand Down Expand Up @@ -89,7 +89,7 @@ export interface DataViewsServiceDeps {
/**
* Saved objects client interface wrapped in a common interface
*/
savedObjectsClient: SavedObjectsClientCommon;
savedObjectsClient: PersistenceAPI;
/**
* Wrapper around http call functionality so it can be used on client or server
*/
Expand Down Expand Up @@ -292,7 +292,7 @@ export interface DataViewsServicePublicMethods {
*/
export class DataViewsService {
private config: UiSettingsCommon;
private savedObjectsClient: SavedObjectsClientCommon;
private savedObjectsClient: PersistenceAPI;
private savedObjectsCache?: Array<SavedObject<DataViewSavedObjectAttrs>> | null;
private apiClient: IDataViewsApiClient;
private fieldFormats: FieldFormatsStartCommon;
Expand Down
2 changes: 1 addition & 1 deletion src/plugins/data_views/common/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ export type {
OnNotification,
OnError,
UiSettingsCommon,
SavedObjectsClientCommon,
PersistenceAPI,
GetFieldsOptions,
IDataViewsApiClient,
SavedObject,
Expand Down
12 changes: 2 additions & 10 deletions src/plugins/data_views/common/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -252,10 +252,10 @@ export interface SavedObjectsClientCommonFindArgs {
}

/**
* Common interface for the saved objects client
* Common interface for the saved objects client on server and content management in browser
* @public
*/
export interface SavedObjectsClientCommon {
export interface PersistenceAPI {
/**
* Search for saved objects
* @param options - options for search
Expand All @@ -269,14 +269,6 @@ export interface SavedObjectsClientCommon {
* @param id - id of saved object
*/
get: (id: string) => Promise<SavedObject<DataViewAttributes>>;
/**
* Update a saved object by id
* @param type - type of saved object
* @param id - id of saved object
* @param attributes - attributes to update
* @param options - client options
*/
getSavedSearch: (id: string) => Promise<SavedObject>;
/**
* Update a saved object by id
* @param type - type of saved object
Expand Down
Loading

0 comments on commit d9d1404

Please sign in to comment.