diff --git a/src/plugins/vis_type_script/public/kibana_api/kibana_api.ts b/src/plugins/vis_type_script/public/kibana_api/kibana_api.ts index 22269b20f1d46..f376eb0236aa7 100644 --- a/src/plugins/vis_type_script/public/kibana_api/kibana_api.ts +++ b/src/plugins/vis_type_script/public/kibana_api/kibana_api.ts @@ -8,6 +8,12 @@ import type * as estypes from '@elastic/elasticsearch/lib/api/types'; import { lastValueFrom } from 'rxjs'; +import { + ENHANCED_ES_SEARCH_STRATEGY, + SQL_SEARCH_STRATEGY, + SqlSearchStrategyRequest, + SqlSearchStrategyResponse, +} from '@kbn/data-plugin/common'; import { DataPublicPluginStart } from '@kbn/data-plugin/public'; import { VisSearchContext } from '../types'; @@ -15,12 +21,56 @@ export interface VisTypeScriptKibanaApiDeps { data: DataPublicPluginStart; } -export interface EsSearchOptions { +export interface ESSearchOptions { useKibanaContext: boolean; + timeField?: string; } -export type ESSearchRequest = estypes.SearchRequest; + +// disallow transport properties that are handled by data_plugin and allow only query related properties +const allowedESSearchRequestKeys = [ + 'index', + 'aggregations', + 'aggs', + 'query', + 'q', + 'sort', + 'from', +] as const; +export type ESSearchRequest = Pick< + estypes.SearchRequest, + typeof allowedESSearchRequestKeys[number] +>; export type ESSearchResponse = estypes.SearchResponse; +export interface SQLSearchOptions { + useKibanaContext: boolean; + timeField?: string; +} + +const allowedSQLSearchRequestKeys = [ + 'query', + 'columnar', + 'cursor', + 'fetch_size', + 'filter', + 'time_zone', +] as const; +export type SQLSearchRequest = Pick< + estypes.SqlQueryRequest, + typeof allowedSQLSearchRequestKeys[number] +>; +export type SQLSearchResponse = estypes.SqlQueryResponse; + +function sanitizeRequest>(payload: T, allowedKeys: string[]): T { + if (payload == null || typeof payload !== 'object') return payload; + return allowedKeys.reduce((sanitized, allowedKey) => { + if (allowedKey in payload) { + sanitized[allowedKey] = payload[allowedKey]; + } + return sanitized; + }, {} as Record) as T; +} + export class VisTypeScriptKibanaApi { constructor( private readonly deps: VisTypeScriptKibanaApiDeps, @@ -29,15 +79,41 @@ export class VisTypeScriptKibanaApi { async esSearch( payload: ESSearchRequest, - { useKibanaContext = true }: EsSearchOptions = { useKibanaContext: true } + { useKibanaContext = true }: ESSearchOptions = { useKibanaContext: true } ): Promise { + payload = sanitizeRequest(payload, [...allowedESSearchRequestKeys]); + + if (useKibanaContext) { + // TODO: adjust request based on this.visSearchContext + // eslint-disable-next-line no-console + console.log(this.visSearchContext); + } + + const response = await lastValueFrom( + this.deps.data.search.search({ params: payload }, { strategy: ENHANCED_ES_SEARCH_STRATEGY }) + ); + return response.rawResponse; + } + + async sqlSearch( + payload: SQLSearchRequest, + { useKibanaContext = true }: ESSearchOptions = { useKibanaContext: true } + ): Promise { + payload = sanitizeRequest(payload, [...allowedSQLSearchRequestKeys]); + if (useKibanaContext) { // TODO: adjust request based on this.visSearchContext // eslint-disable-next-line no-console console.log(this.visSearchContext); } - const response = await lastValueFrom(this.deps.data.search.search({ params: payload })); + const response = await lastValueFrom( + this.deps.data.search.search( + { params: payload }, + { strategy: SQL_SEARCH_STRATEGY } + ) + ); + return response.rawResponse; } } diff --git a/src/plugins/vis_type_script/public/renderer/index.tsx b/src/plugins/vis_type_script/public/renderer/index.tsx index 85ef0af41e884..d8dadbf87875d 100644 --- a/src/plugins/vis_type_script/public/renderer/index.tsx +++ b/src/plugins/vis_type_script/public/renderer/index.tsx @@ -12,10 +12,13 @@ import { createEndpoint, fromIframe } from '@remote-ui/rpc'; import './index.scss'; import { IExternalUrl } from '@kbn/core/public'; import { - EsSearchOptions, + ESSearchOptions, VisTypeScriptKibanaApi, ESSearchResponse, ESSearchRequest, + SQLSearchRequest, + SQLSearchOptions, + SQLSearchResponse, } from '../kibana_api'; export const KIBANA_API_CONSTANT_NAME = 'KIBANA'; @@ -41,6 +44,9 @@ const getSandboxDocument = (script: string, dependencies: string[], nonce: strin searchEs: (payload, options) => { return endpoint.call.esSearch(payload, options); }, + searchSql: (payload, options) => { + return endpoint.call.sqlSearch(payload, options); + }, subscribeToResize: (fn) => { onResize = fn; }, @@ -102,10 +108,16 @@ export const ScriptRenderer: React.FunctionComponent<{ endpoint.expose({ esSearch: async ( payload: ESSearchRequest, - options?: EsSearchOptions + options?: ESSearchOptions ): Promise => { return kibanaApi.esSearch(payload, options); }, + sqlSearch: async ( + payload: SQLSearchRequest, + options?: SQLSearchOptions + ): Promise => { + return kibanaApi.sqlSearch(payload, options); + }, }); return () => { diff --git a/src/plugins/vis_type_script/public/vis_definition/editor_options/script.tsx b/src/plugins/vis_type_script/public/vis_definition/editor_options/script.tsx index 2613756f38c6a..e0797e4119a43 100644 --- a/src/plugins/vis_type_script/public/vis_definition/editor_options/script.tsx +++ b/src/plugins/vis_type_script/public/vis_definition/editor_options/script.tsx @@ -71,6 +71,17 @@ const provideSuggestions = ( insertTextRules: monacoEditor.languages.CompletionItemInsertTextRule.InsertAsSnippet, range: wordRange, }, + { + label: 'searchSql', + kind: monacoEditor.languages.CompletionItemKind.Method, + documentation: { + value: 'Runs an Elasticsearch SQL query', + isTrusted: true, + }, + insertText: 'searchSql({\n\tquery: "$0"\n})', + insertTextRules: monacoEditor.languages.CompletionItemInsertTextRule.InsertAsSnippet, + range: wordRange, + }, { label: 'subscribeToResize', kind: monacoEditor.languages.CompletionItemKind.Method,