diff --git a/x-pack/plugins/infra/kibana.json b/x-pack/plugins/infra/kibana.json index 08ffe520e53d5..8da560d458d5e 100644 --- a/x-pack/plugins/infra/kibana.json +++ b/x-pack/plugins/infra/kibana.json @@ -6,7 +6,6 @@ "features", "usageCollection", "spaces", - "data", "dataEnhanced", "visTypeTimeseries", diff --git a/x-pack/plugins/infra/public/pages/logs/page_content.tsx b/x-pack/plugins/infra/public/pages/logs/page_content.tsx index 426ae8e9d05a8..70e02f1a414b4 100644 --- a/x-pack/plugins/infra/public/pages/logs/page_content.tsx +++ b/x-pack/plugins/infra/public/pages/logs/page_content.tsx @@ -6,10 +6,9 @@ import { EuiFlexGroup, EuiFlexItem, EuiButtonEmpty } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import React from 'react'; +import React, { useEffect, useState, useCallback} from 'react'; import { Route, Switch } from 'react-router-dom'; import { useMount } from 'react-use'; - import { useKibana } from '../../../../../../src/plugins/kibana_react/public'; import { DocumentTitle } from '../../components/document_title'; import { Header } from '../../components/header'; @@ -24,6 +23,45 @@ import { LogEntryRatePage } from './log_entry_rate'; import { LogsSettingsPage } from './settings'; import { StreamPage } from './stream'; import { AlertDropdown } from '../../components/alerting/logs/alert_dropdown'; +import { LogEntriesQueries } from '../../../server/search_strategy/provider'; + +const SearchStrategyTest = () => { + const abortController = new AbortController(); + const { services: { data } } = useKibana(); + const [response, setResponse] = useState({}); + const [request, setRequest] = useState({queryType: LogEntriesQueries.item}); + + // NOTE: This is just a rough example. Cancellation and abort signals are not handled properly here, + // we'd need to handle getData being called more than once, we'd also need to make this all hook-centric. + // This is primarily to show how we'd use our search strategy, and how we'd handle partial results and errors etc. + const getData = useCallback((request) => { + const searchSubscription$ = data.search.search({queryType: LogEntriesQueries.item}, { + strategy: 'infraSearchStrategy', + signal: abortController.signal, + }) + .subscribe({ + next: (response) => { + const { isPartial, isRunning } = response; + // Set partial results + setResponse(response); + if (!isPartial && !isRunning) { + // We have all results + searchSubscription$.unsubscribe(); + } else if (isPartial && !isRunning) { + // Something went wrong + } + } + }); + }, [data.search]); + + useEffect(() => { + getData(request); + }, [request]) + + return ( +
{JSON.stringify(response,null,'\t')}
+ ) +}; export const LogsPageContent: React.FunctionComponent = () => { const uiCapabilities = useKibana().services.application?.capabilities; @@ -77,7 +115,11 @@ export const LogsPageContent: React.FunctionComponent = () => { - + @@ -99,6 +141,7 @@ export const LogsPageContent: React.FunctionComponent = () => { + diff --git a/x-pack/plugins/infra/server/lib/adapters/framework/adapter_types.ts b/x-pack/plugins/infra/server/lib/adapters/framework/adapter_types.ts index 1ecae84c54ffb..ac66a3da8df56 100644 --- a/x-pack/plugins/infra/server/lib/adapters/framework/adapter_types.ts +++ b/x-pack/plugins/infra/server/lib/adapters/framework/adapter_types.ts @@ -15,6 +15,7 @@ import { PluginSetupContract as FeaturesPluginSetup } from '../../../../../../pl import { SpacesPluginSetup } from '../../../../../../plugins/spaces/server'; import { PluginSetupContract as AlertingPluginContract } from '../../../../../alerts/server'; import { MlPluginSetup } from '../../../../../ml/server'; +import { DataPluginSetup, DataPluginStart } from '../../../../../../../src/plugins/data/server/plugin'; export interface InfraServerPluginDeps { home: HomeServerPluginSetup; @@ -25,6 +26,7 @@ export interface InfraServerPluginDeps { apm: APMPluginSetup; alerts: AlertingPluginContract; ml?: MlPluginSetup; + data: DataPluginSetup; } export interface CallWithRequestParams extends GenericParams { diff --git a/x-pack/plugins/infra/server/plugin.ts b/x-pack/plugins/infra/server/plugin.ts index 51f91d7189db7..ae5f2e8d889aa 100644 --- a/x-pack/plugins/infra/server/plugin.ts +++ b/x-pack/plugins/infra/server/plugin.ts @@ -31,6 +31,7 @@ import { infraSourceConfigurationSavedObjectType } from './lib/sources'; import { metricsExplorerViewSavedObjectType } from '../common/saved_objects/metrics_explorer_view'; import { inventoryViewSavedObjectType } from '../common/saved_objects/inventory_view'; import { InfraRequestHandlerContext } from './types'; +import { infraSearchStrategyProvider } from './search_strategy/provider'; export const config = { schema: schema.object({ @@ -166,7 +167,15 @@ export class InfraServerPlugin { // Telemetry UsageCollector.registerUsageCollector(plugins.usageCollection); - + + // Register custom search strategy + core.getStartServices().then(([_, startPlugins]) => { + const infraSearchStrategy = infraSearchStrategyProvider(startPlugins.data); + plugins.data.search.registerSearchStrategy( + 'infraSearchStrategy', + infraSearchStrategy + ); + }); return { defineInternalSourceConfiguration(sourceId, sourceProperties) { sources.defineInternalSourceConfiguration(sourceId, sourceProperties); diff --git a/x-pack/plugins/infra/server/search_strategy/provider.ts b/x-pack/plugins/infra/server/search_strategy/provider.ts new file mode 100644 index 0000000000000..09caa88aa32bb --- /dev/null +++ b/x-pack/plugins/infra/server/search_strategy/provider.ts @@ -0,0 +1,122 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { ISearchStrategy, PluginStart } from '../../../../../src/plugins/data/server'; +import { IEsSearchResponse, ISearchRequestParams } from '../../../../../src/plugins/data/common'; + +// NOTE: Types are shown as super rough examples here. We usually stay away from enums, so they'd likely be +// keyof via io-ts. And 'any' is used in some places as it just sped up putting the example together. +interface ExampleRequestOptions { + queryType: FactoryQueryTypes, + params?: { + size: 10, + body: { + query: {} + } + } +} + +type LogEntriesEntriesReqestOptions = ExampleRequestOptions; +type LogEntriesItemReqestOptions = ExampleRequestOptions; + +interface ExampleResponse extends IEsSearchResponse { + results: { + name: string + }[] +}; + +type LogEntriesEntriesStrategyResponse = ExampleResponse; +type LogEntriesItemStrategyResponse = ExampleResponse; + +export enum LogEntriesQueries { + entries = 'entries', + highlights = 'highlights', + summaryBuckets = 'summaryBuckets', + item = 'item', +} + +type FactoryQueryTypes = LogEntriesQueries; + +type StrategyRequestType = T extends LogEntriesQueries.entries + ? LogEntriesEntriesReqestOptions + : T extends LogEntriesQueries.item + ? LogEntriesItemReqestOptions + : never; + + export type StrategyResponseType = T extends LogEntriesQueries.entries + ? LogEntriesEntriesStrategyResponse + : T extends LogEntriesQueries.item + ? LogEntriesItemStrategyResponse + : never; + + + +export interface InfraQueryTypeFactory { + buildDsl: (options: StrategyRequestType) => ISearchRequestParams; + parse: ( + options: StrategyRequestType, + response: IEsSearchResponse + ) => Promise>; +} + + +// NOTE: Switch harcoded ID to something you know exists to try this out +const queryTypeFactories = { + [LogEntriesQueries.item]: { + buildDsl: (options: any) => { + return { + "index":"logs-*,filebeat-*,kibana_sample_data_logs*", + "terminate_after":1, + "body":{ + "size":1, + "sort":[ + { + "@timestamp":"desc" + }, + { + "_doc":"desc" + } + ], + "query":{ + "ids":{ + "values":[ + "ww5WbXQBHyE_1lJ-g4vn" + ] + } + } + } + } + }, + parse: async ( + options: any, + response: any, + ): Promise => { + return { ...response } + }, + } +} +export const infraSearchStrategyProvider = ( + data: PluginStart +): ISearchStrategy, StrategyResponseType> => { + const es = data.search.getSearchStrategy('es'); + + return { + search: async (context, request, options) => { + if (request.queryType == null) { + throw new Error('queryType is required'); + } + const queryFactory: InfraQueryTypeFactory = queryTypeFactories[request.queryType]; + const dsl = queryFactory.buildDsl(request); + const esSearchRes = await es.search(context, { ...request, params: dsl }, options); + return queryFactory.parse(request, esSearchRes); + }, + cancel: async (context, id) => { + if (es.cancel) { + es.cancel(context, id); + } + }, + }; +}; \ No newline at end of file