From 672c90a9c126b91b8a94ab9a38fdf5c308593dd0 Mon Sep 17 00:00:00 2001 From: Stratoula Kalafateli Date: Tue, 11 Jul 2023 16:22:57 +0300 Subject: [PATCH 01/61] [Dashboard] Panel settings action improvements (#161616) ## Summary Part of https://github.com/elastic/kibana/issues/160256 - Replaces the Edit panel settings text with the simpler Panel settings - Replaces the documentation - Replaces the icon image ### Checklist - [ ] [Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html) was added for features that require explanation or tutorials --- docs/user/canvas.asciidoc | 2 +- docs/user/dashboard/dashboard.asciidoc | 4 ++-- .../tutorial-create-a-dashboard-of-lens-panels.asciidoc | 8 ++++---- .../customize_panel/customize_panel_action.tsx | 4 ++-- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/docs/user/canvas.asciidoc b/docs/user/canvas.asciidoc index be69c0236cd50..fd11a1162176f 100644 --- a/docs/user/canvas.asciidoc +++ b/docs/user/canvas.asciidoc @@ -139,7 +139,7 @@ Add a panel that you saved in *Visualize Library* to your workpad. * *Edit Visualization* — Opens the visualization editor so that you can edit the panel. -* *Edit panel settings* — Allows you to change the title, description, and time range for the panel. +* *Panel settings* — Allows you to change the title, description, and time range for the panel. * *Inspect* — Allows you to drill down into the panel data. diff --git a/docs/user/dashboard/dashboard.asciidoc b/docs/user/dashboard/dashboard.asciidoc index 5da14bc58572d..948c487485db4 100644 --- a/docs/user/dashboard/dashboard.asciidoc +++ b/docs/user/dashboard/dashboard.asciidoc @@ -295,7 +295,7 @@ To make changes without changing the original version, open the panel menu, then * *Convert to Lens* — Opens *TSVB* and aggregation-based visualizations in *Lens*. -* *Edit panel settings* — Opens the *Panel settings* window to change the *title*, *description*, and *time range*. +* *Panel settings* — Opens the *Panel settings* window to change the *title*, *description*, and *time range*. * *More > Replace panel* — Opens the *Visualize Library* so you can select a new panel to replace the existing panel. @@ -391,7 +391,7 @@ For more information about {kib} and {es} filters, refer to < Edit panel settings*. +. Open the panel menu, then select *More > Panel settings*. . Select *Apply a custom time range*. diff --git a/docs/user/dashboard/tutorial-create-a-dashboard-of-lens-panels.asciidoc b/docs/user/dashboard/tutorial-create-a-dashboard-of-lens-panels.asciidoc index 18b2b0a148154..ecc7b49750eed 100644 --- a/docs/user/dashboard/tutorial-create-a-dashboard-of-lens-panels.asciidoc +++ b/docs/user/dashboard/tutorial-create-a-dashboard-of-lens-panels.asciidoc @@ -141,7 +141,7 @@ image::images/lens_lineChartMetricOverTimeBottomAxis_8.7.png[Bottom axis menu] Since you removed the axis labels, add a panel title: -. Open the panel menu, then select *Edit panel settings*. +. Open the panel menu, then select *Panel settings*. . In the *Title* field, enter `Median of bytes`, then click *Save*. + @@ -245,7 +245,7 @@ image::images/lens_pieChartCompareSubsetOfDocs_7.16.png[Pie chart that compares Add a panel title: -. Open the panel menu, then select *Edit panel settings*. +. Open the panel menu, then select *Panel settings*. . In the *Title* field, enter `Sum of bytes from large requests`, then click *Save*. @@ -278,7 +278,7 @@ image::images/lens_barChartDistributionOfNumberField_7.16.png[Bar chart that dis Add a panel title: -. Open the panel menu, then select *Edit panel settings*. +. Open the panel menu, then select *Panel settings*. . In the *Title* field, enter `Website traffic`, then click *Save*. @@ -342,7 +342,7 @@ image::images/lens_treemapMultiLevelChart_7.16.png[Treemap visualization] Add a panel title: -. Open the panel menu, then select *Edit panel settings*. +. Open the panel menu, then select *Panel settings*. . In the *Title* field, enter `Page views by location and referrer`, then click *Save*. diff --git a/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/customize_panel/customize_panel_action.tsx b/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/customize_panel/customize_panel_action.tsx index fc7a30efbe256..6146199eee24b 100644 --- a/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/customize_panel/customize_panel_action.tsx +++ b/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/customize_panel/customize_panel_action.tsx @@ -87,12 +87,12 @@ export class CustomizePanelAction implements Action public getDisplayName({ embeddable }: CustomizePanelActionContext): string { return i18n.translate('embeddableApi.customizePanel.action.displayName', { - defaultMessage: 'Edit panel settings', + defaultMessage: 'Panel settings', }); } public getIconType() { - return 'pencil'; + return 'gear'; } public async isCompatible({ embeddable }: CustomizePanelActionContext) { From d9d1404119cc4de719eafc98a73716554f4e573b Mon Sep 17 00:00:00 2001 From: Matthew Kime Date: Tue, 11 Jul 2023 08:23:46 -0500 Subject: [PATCH 02/61] [saved search] Remove saved object client from data views plugin for 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> --- .../search/expressions/kibana_context.ts | 143 +----------------- .../search/expressions/kibana_context_type.ts | 30 +--- .../data/public/search/search_service.ts | 9 +- .../data/server/search/search_service.ts | 5 +- src/plugins/data/tsconfig.json | 2 +- .../common/data_views/data_views.test.ts | 10 +- .../common/data_views/data_views.ts | 6 +- src/plugins/data_views/common/index.ts | 2 +- src/plugins/data_views/common/types.ts | 12 +- src/plugins/data_views/common/utils.ts | 4 +- ....ts => content_management_wrapper.test.ts} | 12 +- ...apper.ts => content_management_wrapper.ts} | 20 +-- src/plugins/data_views/public/index.ts | 3 +- src/plugins/data_views/public/plugin.ts | 9 +- .../server/data_views_service_factory.ts | 4 +- src/plugins/data_views/server/index.ts | 2 +- .../saved_objects_client_wrapper.test.ts | 8 +- .../server/saved_objects_client_wrapper.ts | 4 +- .../saved_search/common/expressions/index.ts | 9 ++ .../expressions/kibana_context.test.ts | 46 +++--- .../common/expressions/kibana_context.ts | 139 +++++++++++++++++ .../common/expressions/kibana_context_type.ts | 36 +++++ src/plugins/saved_search/common/index.ts | 1 + .../service}/get_saved_searches.test.ts | 39 +++-- .../service}/get_saved_searches.ts | 43 +++--- .../service}/saved_searches_utils.test.ts | 3 +- .../service}/saved_searches_utils.ts | 7 +- src/plugins/saved_search/common/types.ts | 7 + src/plugins/saved_search/kibana.jsonc | 3 +- .../public}/expressions/kibana_context.ts | 17 ++- src/plugins/saved_search/public/plugin.ts | 22 ++- .../public/services/saved_searches/index.ts | 7 +- .../saved_searches/save_saved_searches.ts | 2 +- .../saved_searches/saved_searches_service.ts | 12 +- .../server}/expressions/kibana_context.ts | 37 +++-- src/plugins/saved_search/server/plugin.ts | 49 ++++-- src/plugins/saved_search/tsconfig.json | 3 + .../fixtures/kbn_archiver/saved_search.json | 27 ++++ .../test_suites/run_pipeline/esaggs.ts | 38 +++++ .../jobs/new_job/utils/new_job_utils.test.ts | 12 +- .../translations/translations/fr-FR.json | 10 +- .../translations/translations/ja-JP.json | 10 +- .../translations/translations/zh-CN.json | 10 +- 43 files changed, 502 insertions(+), 372 deletions(-) rename src/plugins/data_views/public/{saved_objects_client_wrapper.test.ts => content_management_wrapper.test.ts} (75%) rename src/plugins/data_views/public/{saved_objects_client_wrapper.ts => content_management_wrapper.ts} (82%) create mode 100644 src/plugins/saved_search/common/expressions/index.ts rename src/plugins/{data/common/search => saved_search/common}/expressions/kibana_context.test.ts (87%) create mode 100644 src/plugins/saved_search/common/expressions/kibana_context.ts create mode 100644 src/plugins/saved_search/common/expressions/kibana_context_type.ts rename src/plugins/saved_search/{public/services/saved_searches => common/service}/get_saved_searches.test.ts (91%) rename src/plugins/saved_search/{public/services/saved_searches => common/service}/get_saved_searches.ts (65%) rename src/plugins/saved_search/{public/services/saved_searches => common/service}/saved_searches_utils.test.ts (97%) rename src/plugins/saved_search/{public/services/saved_searches => common/service}/saved_searches_utils.ts (92%) rename src/plugins/{data/public/search => saved_search/public}/expressions/kibana_context.ts (71%) rename src/plugins/{data/server/search => saved_search/server}/expressions/kibana_context.ts (53%) create mode 100644 test/functional/fixtures/kbn_archiver/saved_search.json diff --git a/src/plugins/data/common/search/expressions/kibana_context.ts b/src/plugins/data/common/search/expressions/kibana_context.ts index 4fa07676c0152..9f9a7f274ab5b 100644 --- a/src/plugins/data/common/search/expressions/kibana_context.ts +++ b/src/plugins/data/common/search/expressions/kibana_context.ts @@ -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; @@ -37,125 +30,3 @@ export type ExpressionFunctionKibanaContext = ExpressionFunctionDefinition< Promise, ExecutionContext >; - -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( - [...(Array.isArray(first) ? first : [first]), ...(Array.isArray(second) ? second : [second])], - (n: any) => JSON.stringify(n.query) - ); - -export const getKibanaContextFn = ( - getStartDependencies: ( - getKibanaRequest: ExecutionContext['getKibanaRequest'] - ) => Promise -) => { - 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; -}; diff --git a/src/plugins/data/common/search/expressions/kibana_context_type.ts b/src/plugins/data/common/search/expressions/kibana_context_type.ts index 10db5b170ce85..cec788d27d856 100644 --- a/src/plugins/data/common/search/expressions/kibana_context_type.ts +++ b/src/plugins/data/common/search/expressions/kibana_context_type.ts @@ -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 = { @@ -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), - }; - }, - }, -}; diff --git a/src/plugins/data/public/search/search_service.ts b/src/plugins/data/public/search/search_service.ts index c4a135b64ca94..d9033e0eed5f1 100644 --- a/src/plugins/data/public/search/search_service.ts +++ b/src/plugins/data/public/search/search_service.ts @@ -38,7 +38,6 @@ import { ipRangeFunction, ISearchGeneric, kibana, - kibanaContext, kibanaFilterFunction, kibanaTimerangeFunction, kqlFunction, @@ -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'; @@ -143,11 +142,6 @@ export class SearchService implements Plugin { }) ); expressions.registerFunction(kibana); - expressions.registerFunction( - getKibanaContext({ getStartServices } as { - getStartServices: StartServicesAccessor; - }) - ); expressions.registerFunction(cidrFunction); expressions.registerFunction(dateRangeFunction); expressions.registerFunction(extendedBoundsFunction); @@ -167,7 +161,6 @@ export class SearchService implements Plugin { expressions.registerFunction(removeFilterFunction); expressions.registerFunction(selectFilterFunction); expressions.registerFunction(phraseFilterFunction); - expressions.registerType(kibanaContext); expressions.registerFunction( getEsdsl({ getStartServices } as { diff --git a/src/plugins/data/server/search/search_service.ts b/src/plugins/data/server/search/search_service.ts index 8189177c4b58b..f9d1c79390fb5 100644 --- a/src/plugins/data/server/search/search_service.ts +++ b/src/plugins/data/server/search/search_service.ts @@ -67,7 +67,6 @@ import { ipRangeFunction, ISearchOptions, kibana, - kibanaContext, kibanaFilterFunction, kibanaTimerangeFunction, kqlFunction, @@ -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'; @@ -233,7 +231,6 @@ export class SearchService implements Plugin { expressions.registerFunction(luceneFunction); expressions.registerFunction(kqlFunction); expressions.registerFunction(kibanaTimerangeFunction); - expressions.registerFunction(getKibanaContext({ getStartServices: core.getStartServices })); expressions.registerFunction(fieldFunction); expressions.registerFunction(numericalRangeFunction); expressions.registerFunction(rangeFunction); @@ -244,7 +241,7 @@ export class SearchService implements Plugin { expressions.registerFunction(removeFilterFunction); expressions.registerFunction(selectFilterFunction); expressions.registerFunction(phraseFilterFunction); - expressions.registerType(kibanaContext); + expressions.registerType(esRawResponse); expressions.registerType(eqlRawResponse); diff --git a/src/plugins/data/tsconfig.json b/src/plugins/data/tsconfig.json index d98f0da445171..450c858265917 100644 --- a/src/plugins/data/tsconfig.json +++ b/src/plugins/data/tsconfig.json @@ -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/**/*", diff --git a/src/plugins/data_views/common/data_views/data_views.test.ts b/src/plugins/data_views/common/data_views/data_views.test.ts index ec3d9acb8aaf0..e6d09710fe016 100644 --- a/src/plugins/data_views/common/data_views/data_views.test.ts +++ b/src/plugins/data_views/common/data_views/data_views.test.ts @@ -13,7 +13,7 @@ import { fieldFormatsMock } from '@kbn/field-formats-plugin/common/mocks'; import { UiSettingsCommon, - SavedObjectsClientCommon, + PersistenceAPI, SavedObject, DataViewSpec, IDataViewsApiClient, @@ -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 = { @@ -73,7 +73,7 @@ describe('IndexPatterns', () => { beforeEach(() => { jest.clearAllMocks(); - savedObjectsClient = {} as SavedObjectsClientCommon; + savedObjectsClient = {} as PersistenceAPI; savedObjectsClient.find = jest.fn( () => Promise.resolve([indexPatternObj]) as Promise>> ); @@ -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: () => {}, @@ -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: () => {}, diff --git a/src/plugins/data_views/common/data_views/data_views.ts b/src/plugins/data_views/common/data_views/data_views.ts index f51189f2409ea..469fd2a1fa61e 100644 --- a/src/plugins/data_views/common/data_views/data_views.ts +++ b/src/plugins/data_views/common/data_views/data_views.ts @@ -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'; @@ -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 */ @@ -292,7 +292,7 @@ export interface DataViewsServicePublicMethods { */ export class DataViewsService { private config: UiSettingsCommon; - private savedObjectsClient: SavedObjectsClientCommon; + private savedObjectsClient: PersistenceAPI; private savedObjectsCache?: Array> | null; private apiClient: IDataViewsApiClient; private fieldFormats: FieldFormatsStartCommon; diff --git a/src/plugins/data_views/common/index.ts b/src/plugins/data_views/common/index.ts index de167a17c0eca..32b88d48a9798 100644 --- a/src/plugins/data_views/common/index.ts +++ b/src/plugins/data_views/common/index.ts @@ -38,7 +38,7 @@ export type { OnNotification, OnError, UiSettingsCommon, - SavedObjectsClientCommon, + PersistenceAPI, GetFieldsOptions, IDataViewsApiClient, SavedObject, diff --git a/src/plugins/data_views/common/types.ts b/src/plugins/data_views/common/types.ts index aa157d5336685..8e4c42d27b2f8 100644 --- a/src/plugins/data_views/common/types.ts +++ b/src/plugins/data_views/common/types.ts @@ -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 @@ -269,14 +269,6 @@ export interface SavedObjectsClientCommon { * @param id - id of saved object */ get: (id: string) => Promise>; - /** - * 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; /** * Update a saved object by id * @param type - type of saved object diff --git a/src/plugins/data_views/common/utils.ts b/src/plugins/data_views/common/utils.ts index 05822ed363e9f..e63167ff4d3ff 100644 --- a/src/plugins/data_views/common/utils.ts +++ b/src/plugins/data_views/common/utils.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import type { SavedObjectsClientCommon } from './types'; +import type { PersistenceAPI } from './types'; /** * Returns an object matching a given name @@ -15,7 +15,7 @@ import type { SavedObjectsClientCommon } from './types'; * @param name {string} * @returns {SavedObject|undefined} */ -export async function findByName(client: SavedObjectsClientCommon, name: string) { +export async function findByName(client: PersistenceAPI, name: string) { if (name) { const savedObjects = await client.find({ perPage: 10, diff --git a/src/plugins/data_views/public/saved_objects_client_wrapper.test.ts b/src/plugins/data_views/public/content_management_wrapper.test.ts similarity index 75% rename from src/plugins/data_views/public/saved_objects_client_wrapper.test.ts rename to src/plugins/data_views/public/content_management_wrapper.test.ts index 898d22b7d3637..aefbd715fe780 100644 --- a/src/plugins/data_views/public/saved_objects_client_wrapper.test.ts +++ b/src/plugins/data_views/public/content_management_wrapper.test.ts @@ -6,13 +6,11 @@ * Side Public License, v 1. */ -import { SavedObjectsClientPublicToCommon } from './saved_objects_client_wrapper'; +import { ContentMagementWrapper } from './content_management_wrapper'; import { ContentClient } from '@kbn/content-management-plugin/public'; -import { savedObjectsServiceMock } from '@kbn/core/public/mocks'; import { DataViewSavedObjectConflictError } from '../common'; -describe('SavedObjectsClientPublicToCommon', () => { - const soClient = savedObjectsServiceMock.createStartContract().client; +describe('ContentMagementWrapper', () => { const cmClient = {} as ContentClient; test('get saved object - exactMatch', async () => { @@ -22,7 +20,7 @@ describe('SavedObjectsClientPublicToCommon', () => { cmClient.get = jest .fn() .mockResolvedValue({ meta: { outcome: 'exactMatch' }, item: mockedSavedObject }); - const service = new SavedObjectsClientPublicToCommon(cmClient, soClient); + const service = new ContentMagementWrapper(cmClient); const result = await service.get('1'); expect(result).toStrictEqual(mockedSavedObject); }); @@ -34,7 +32,7 @@ describe('SavedObjectsClientPublicToCommon', () => { cmClient.get = jest .fn() .mockResolvedValue({ meta: { outcome: 'aliasMatch' }, item: mockedSavedObject }); - const service = new SavedObjectsClientPublicToCommon(cmClient, soClient); + const service = new ContentMagementWrapper(cmClient); const result = await service.get('1'); expect(result).toStrictEqual(mockedSavedObject); }); @@ -47,7 +45,7 @@ describe('SavedObjectsClientPublicToCommon', () => { cmClient.get = jest .fn() .mockResolvedValue({ meta: { outcome: 'conflict' }, item: mockedSavedObject }); - const service = new SavedObjectsClientPublicToCommon(cmClient, soClient); + const service = new ContentMagementWrapper(cmClient); await expect(service.get('1')).rejects.toThrow(DataViewSavedObjectConflictError); }); diff --git a/src/plugins/data_views/public/saved_objects_client_wrapper.ts b/src/plugins/data_views/public/content_management_wrapper.ts similarity index 82% rename from src/plugins/data_views/public/saved_objects_client_wrapper.ts rename to src/plugins/data_views/public/content_management_wrapper.ts index 2e22ef889b1ac..d4fde29103999 100644 --- a/src/plugins/data_views/public/saved_objects_client_wrapper.ts +++ b/src/plugins/data_views/public/content_management_wrapper.ts @@ -8,12 +8,11 @@ import type { ContentClient } from '@kbn/content-management-plugin/public'; import { SavedObjectNotFound } from '@kbn/kibana-utils-plugin/common'; -import type { SavedObjectsClientContract } from '@kbn/core/public'; import { DataViewSavedObjectConflictError } from '../common/errors'; import { DataViewAttributes, SavedObject, - SavedObjectsClientCommon, + PersistenceAPI, SavedObjectsClientCommonFindArgs, } from '../common/types'; @@ -21,15 +20,11 @@ import type { DataViewCrudTypes } from '../common/content_management'; import { DataViewSOType } from '../common/content_management'; -type SOClient = Pick; - -export class SavedObjectsClientPublicToCommon implements SavedObjectsClientCommon { +export class ContentMagementWrapper implements PersistenceAPI { private contentManagementClient: ContentClient; - private savedObjectClient: SOClient; - constructor(contentManagementClient: ContentClient, savedObjectClient: SOClient) { + constructor(contentManagementClient: ContentClient) { this.contentManagementClient = contentManagementClient; - this.savedObjectClient = savedObjectClient; } async find(options: SavedObjectsClientCommonFindArgs) { @@ -75,15 +70,6 @@ export class SavedObjectsClientPublicToCommon implements SavedObjectsClientCommo return response.item; } - async getSavedSearch(id: string) { - const response = await this.savedObjectClient.resolve('search', id); - - if (response.outcome === 'conflict') { - throw new DataViewSavedObjectConflictError(id); - } - return response.saved_object; - } - async update( id: string, attributes: DataViewAttributes, diff --git a/src/plugins/data_views/public/index.ts b/src/plugins/data_views/public/index.ts index 8ee149622f39f..777e386189d62 100644 --- a/src/plugins/data_views/public/index.ts +++ b/src/plugins/data_views/public/index.ts @@ -19,7 +19,7 @@ export type { DataViewSpec, FieldSpec, DataViewAttributes, - SavedObjectsClientCommon, + PersistenceAPI, RuntimeField, } from '../common'; export { @@ -48,7 +48,6 @@ export type { export { DataViewsApiClient, DataViewsService, DataView } from './data_views'; export type { DataViewListItem } from './data_views'; export { UiSettingsPublicToCommon } from './ui_settings_wrapper'; -export { SavedObjectsClientPublicToCommon } from './saved_objects_client_wrapper'; /* * Plugin setup diff --git a/src/plugins/data_views/public/plugin.ts b/src/plugins/data_views/public/plugin.ts index 929512d70d926..119d785139c90 100644 --- a/src/plugins/data_views/public/plugin.ts +++ b/src/plugins/data_views/public/plugin.ts @@ -17,7 +17,7 @@ import { } from './types'; import { DataViewsApiClient } from '.'; -import { SavedObjectsClientPublicToCommon } from './saved_objects_client_wrapper'; +import { ContentMagementWrapper } from './content_management_wrapper'; import { UiSettingsPublicToCommon } from './ui_settings_wrapper'; @@ -63,7 +63,7 @@ export class DataViewsPublicPlugin core: CoreStart, { fieldFormats, contentManagement }: DataViewsPublicStartDependencies ): DataViewsPublicPluginStart { - const { uiSettings, http, notifications, application, savedObjects } = core; + const { uiSettings, http, notifications, application } = core; const onNotifDebounced = debounceByKey( notifications.toasts.add.bind(notifications.toasts), @@ -77,10 +77,7 @@ export class DataViewsPublicPlugin return new DataViewsServicePublic({ hasData: this.hasData.start(core), uiSettings: new UiSettingsPublicToCommon(uiSettings), - savedObjectsClient: new SavedObjectsClientPublicToCommon( - contentManagement.client, - savedObjects.client - ), + savedObjectsClient: new ContentMagementWrapper(contentManagement.client), apiClient: new DataViewsApiClient(http), fieldFormats, onNotification: (toastInputFields, key) => { diff --git a/src/plugins/data_views/server/data_views_service_factory.ts b/src/plugins/data_views/server/data_views_service_factory.ts index bc910b3822dba..fb5ae2c5afe3a 100644 --- a/src/plugins/data_views/server/data_views_service_factory.ts +++ b/src/plugins/data_views/server/data_views_service_factory.ts @@ -18,7 +18,7 @@ import { FieldFormatsStart } from '@kbn/field-formats-plugin/server'; import { DataViewsService } from '../common'; import { UiSettingsServerToCommon } from './ui_settings_wrapper'; import { IndexPatternsApiServer } from './index_patterns_api_client'; -import { SavedObjectsClientServerToCommon } from './saved_objects_client_wrapper'; +import { SavedObjectsClientWrapper } from './saved_objects_client_wrapper'; interface DataViewsServiceFactoryDeps { logger: Logger; @@ -44,7 +44,7 @@ export const dataViewsServiceFactory = (deps: DataViewsServiceFactoryDeps) => return new DataViewsService({ uiSettings: new UiSettingsServerToCommon(uiSettingsClient), - savedObjectsClient: new SavedObjectsClientServerToCommon(savedObjectsClient), + savedObjectsClient: new SavedObjectsClientWrapper(savedObjectsClient), apiClient: new IndexPatternsApiServer(elasticsearchClient, savedObjectsClient), fieldFormats: formats, onError: (error) => { diff --git a/src/plugins/data_views/server/index.ts b/src/plugins/data_views/server/index.ts index 40b9030e7d180..553f1a48d1d6d 100644 --- a/src/plugins/data_views/server/index.ts +++ b/src/plugins/data_views/server/index.ts @@ -57,5 +57,5 @@ export { export type { SERVICE_KEY_TYPE } from './constants'; -export type { FieldSpec, SavedObjectsClientCommon } from '../common/types'; +export type { FieldSpec } from '../common/types'; export { DataViewsService, DataView } from '../common/data_views'; diff --git a/src/plugins/data_views/server/saved_objects_client_wrapper.test.ts b/src/plugins/data_views/server/saved_objects_client_wrapper.test.ts index e5dc83f7e109b..9fba85e5e76c8 100644 --- a/src/plugins/data_views/server/saved_objects_client_wrapper.test.ts +++ b/src/plugins/data_views/server/saved_objects_client_wrapper.test.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { SavedObjectsClientServerToCommon } from './saved_objects_client_wrapper'; +import { SavedObjectsClientWrapper } from './saved_objects_client_wrapper'; import { SavedObjectsClientContract } from '@kbn/core/server'; import { DataViewSavedObjectConflictError } from '../common'; @@ -21,7 +21,7 @@ describe('SavedObjectsClientPublicToCommon', () => { soClient.resolve = jest .fn() .mockResolvedValue({ outcome: 'exactMatch', saved_object: mockedSavedObject }); - const service = new SavedObjectsClientServerToCommon(soClient); + const service = new SavedObjectsClientWrapper(soClient); const result = await service.get('1'); expect(result).toStrictEqual(mockedSavedObject); }); @@ -33,7 +33,7 @@ describe('SavedObjectsClientPublicToCommon', () => { soClient.resolve = jest .fn() .mockResolvedValue({ outcome: 'aliasMatch', saved_object: mockedSavedObject }); - const service = new SavedObjectsClientServerToCommon(soClient); + const service = new SavedObjectsClientWrapper(soClient); const result = await service.get('1'); expect(result).toStrictEqual(mockedSavedObject); }); @@ -46,7 +46,7 @@ describe('SavedObjectsClientPublicToCommon', () => { soClient.resolve = jest .fn() .mockResolvedValue({ outcome: 'conflict', saved_object: mockedSavedObject }); - const service = new SavedObjectsClientServerToCommon(soClient); + const service = new SavedObjectsClientWrapper(soClient); await expect(service.get('1')).rejects.toThrow(DataViewSavedObjectConflictError); }); diff --git a/src/plugins/data_views/server/saved_objects_client_wrapper.ts b/src/plugins/data_views/server/saved_objects_client_wrapper.ts index a8902904411df..eb85a60c22a6a 100644 --- a/src/plugins/data_views/server/saved_objects_client_wrapper.ts +++ b/src/plugins/data_views/server/saved_objects_client_wrapper.ts @@ -9,7 +9,7 @@ import { SavedObjectsClientContract, SavedObject } from '@kbn/core/server'; import { DataViewAttributes, - SavedObjectsClientCommon, + PersistenceAPI, SavedObjectsClientCommonFindArgs, } from '../common/types'; import { DataViewSavedObjectConflictError } from '../common/errors'; @@ -17,7 +17,7 @@ import { DataViewSavedObjectConflictError } from '../common/errors'; import type { DataViewCrudTypes } from '../common/content_management'; import { DATA_VIEW_SAVED_OBJECT_TYPE } from '../common'; -export class SavedObjectsClientServerToCommon implements SavedObjectsClientCommon { +export class SavedObjectsClientWrapper implements PersistenceAPI { private savedObjectClient: SavedObjectsClientContract; constructor(savedObjectClient: SavedObjectsClientContract) { this.savedObjectClient = savedObjectClient; diff --git a/src/plugins/saved_search/common/expressions/index.ts b/src/plugins/saved_search/common/expressions/index.ts new file mode 100644 index 0000000000000..1818c4c96299e --- /dev/null +++ b/src/plugins/saved_search/common/expressions/index.ts @@ -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 { kibanaContext } from './kibana_context_type'; diff --git a/src/plugins/data/common/search/expressions/kibana_context.test.ts b/src/plugins/saved_search/common/expressions/kibana_context.test.ts similarity index 87% rename from src/plugins/data/common/search/expressions/kibana_context.test.ts rename to src/plugins/saved_search/common/expressions/kibana_context.test.ts index 211327fa152bb..dfb5ccfbf73eb 100644 --- a/src/plugins/data/common/search/expressions/kibana_context.test.ts +++ b/src/plugins/saved_search/common/expressions/kibana_context.test.ts @@ -9,17 +9,16 @@ import { FilterStateStore, buildFilter, FILTERS } from '@kbn/es-query'; import type { DeeplyMockedKeys } from '@kbn/utility-types-jest'; import type { ExecutionContext } from '@kbn/expressions-plugin/common'; -import { KibanaContext } from './kibana_context_type'; +import { KibanaContext, ExpressionFunctionKibanaContext } from '@kbn/data-plugin/common'; +import { fromSavedSearchAttributes } from '../service/saved_searches_utils'; +import type { SavedSearchAttributes, SavedSearch } from '../types'; -import { - getKibanaContextFn, - ExpressionFunctionKibanaContext, - KibanaContextStartDependencies, -} from './kibana_context'; +import { getKibanaContextFn, KibanaContextStartDependencies } from './kibana_context'; type StartServicesMock = DeeplyMockedKeys; const createExecutionContextMock = (): DeeplyMockedKeys => ({ + // eslint-disable-next-line @typescript-eslint/no-explicit-any abortSignal: {} as any, getExecutionContext: jest.fn(), getSearchContext: jest.fn(), @@ -41,23 +40,26 @@ describe('kibanaContextFn', () => { beforeEach(async () => { kibanaContextFn = getKibanaContextFn(getStartServicesMock); startServicesMock = { - savedObjectsClient: { - create: jest.fn(), - delete: jest.fn(), - find: jest.fn(), - get: jest.fn(), - getSavedSearch: jest.fn(), - update: jest.fn(), - }, + getSavedSearch: jest.fn(), }; }); it('merges and deduplicates queries from different sources', async () => { const { fn } = kibanaContextFn; - startServicesMock.savedObjectsClient.getSavedSearch.mockResolvedValue({ - attributes: { - kibanaSavedObjectMeta: { - searchSourceJSON: JSON.stringify({ + startServicesMock.getSavedSearch.mockResolvedValue( + fromSavedSearchAttributes( + 'abc', + { + kibanaSavedObjectMeta: { + searchSourceJSON: JSON.stringify({ + query: [], + }), + }, + } as SavedSearchAttributes, + [], + undefined, + { + getFields: () => ({ query: [ { language: 'kuery', @@ -84,10 +86,12 @@ describe('kibanaContextFn', () => { }, }, ], + filter: [], }), - }, - }, - } as any); + } as unknown as SavedSearch['searchSource'], + {} as SavedSearch['sharingSavedObjectProps'] + ) + ); const args = { ...emptyArgs, q: [ diff --git a/src/plugins/saved_search/common/expressions/kibana_context.ts b/src/plugins/saved_search/common/expressions/kibana_context.ts new file mode 100644 index 0000000000000..35e675783ba7f --- /dev/null +++ b/src/plugins/saved_search/common/expressions/kibana_context.ts @@ -0,0 +1,139 @@ +/* + * 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 { isEqual, uniqBy } from 'lodash'; +import { i18n } from '@kbn/i18n'; +import { ExecutionContext } from '@kbn/expressions-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/server'; +import { ExpressionFunctionKibanaContext } from '@kbn/data-plugin/common'; +import { SavedSearch } from '../types'; + +export interface KibanaContextStartDependencies { + getSavedSearch: (id: string) => Promise; +} + +const mergeQueries = (first: Query | Query[] = [], second: Query | Query[]) => + uniqBy( + [...(Array.isArray(first) ? first : [first]), ...(Array.isArray(second) ? second : [second])], + (n) => JSON.stringify(n.query) + ); + +export const getKibanaContextFn = ( + getStartDependencies: ( + getKibanaRequest: ExecutionContext['getKibanaRequest'] + ) => Promise +) => { + const kibanaContextFunction: ExpressionFunctionKibanaContext = { + name: 'kibana_context', + type: 'kibana_context', + inputTypes: ['kibana_context', 'null'], + help: i18n.translate('savedSearch.kibana_context.help', { + defaultMessage: 'Updates kibana global context', + }), + args: { + q: { + types: ['kibana_query', 'null'], + multi: true, + aliases: ['query', '_'], + help: i18n.translate('savedSearch.kibana_context.q.help', { + defaultMessage: 'Specify Kibana free form text query', + }), + }, + filters: { + types: ['kibana_filter', 'null'], + multi: true, + help: i18n.translate('savedSearch.kibana_context.filters.help', { + defaultMessage: 'Specify Kibana generic filters', + }), + }, + timeRange: { + types: ['timerange', 'null'], + default: null, + help: i18n.translate('savedSearch.kibana_context.timeRange.help', { + defaultMessage: 'Specify Kibana time range filter', + }), + }, + savedSearchId: { + types: ['string', 'null'], + default: null, + help: i18n.translate('savedSearch.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 { getSavedSearch } = 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 getSavedSearch(args.savedSearchId); + const { query, filter } = obj.searchSource.getFields(); + + if (query) { + queries = mergeQueries(queries, query as Query); + } + if (filter) { + filters = [...filters, ...(Array.isArray(filter) ? filter : [filter])] as 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: Filter) => !f.meta?.disabled)), + timeRange, + }; + }, + }; + return kibanaContextFunction; +}; diff --git a/src/plugins/saved_search/common/expressions/kibana_context_type.ts b/src/plugins/saved_search/common/expressions/kibana_context_type.ts new file mode 100644 index 0000000000000..995a438d53526 --- /dev/null +++ b/src/plugins/saved_search/common/expressions/kibana_context_type.ts @@ -0,0 +1,36 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { ExpressionValueFilter } from '@kbn/expressions-plugin/common'; +import { adaptToExpressionValueFilter, KibanaContext } from '@kbn/data-plugin/common'; + +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), + }; + }, + }, +}; diff --git a/src/plugins/saved_search/common/index.ts b/src/plugins/saved_search/common/index.ts index 2a888c3067edd..8915ab582b3e2 100644 --- a/src/plugins/saved_search/common/index.ts +++ b/src/plugins/saved_search/common/index.ts @@ -23,3 +23,4 @@ export enum VIEW_MODE { export { SavedSearchType } from './constants'; export { LATEST_VERSION } from './constants'; +export { getKibanaContextFn } from './expressions/kibana_context'; diff --git a/src/plugins/saved_search/public/services/saved_searches/get_saved_searches.test.ts b/src/plugins/saved_search/common/service/get_saved_searches.test.ts similarity index 91% rename from src/plugins/saved_search/public/services/saved_searches/get_saved_searches.test.ts rename to src/plugins/saved_search/common/service/get_saved_searches.test.ts index e2de4581b15db..05893f5c36e64 100644 --- a/src/plugins/saved_search/public/services/saved_searches/get_saved_searches.test.ts +++ b/src/plugins/saved_search/common/service/get_saved_searches.test.ts @@ -6,26 +6,25 @@ * Side Public License, v 1. */ 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 { dataPluginMock } from '@kbn/data-plugin/public/mocks'; import { getSavedSearch } from './get_saved_searches'; import { SavedObjectsTaggingApi } from '@kbn/saved-objects-tagging-oss-plugin/public'; +import type { GetSavedSearchDependencies } from './get_saved_searches'; describe('getSavedSearch', () => { - let search: DataPublicPluginStart['search']; - let cmClient: ContentManagementPublicStart['client']; + let searchSourceCreate: DataPublicPluginStart['search']['searchSource']['create']; + let getSavedSrch: GetSavedSearchDependencies['getSavedSrch']; beforeEach(() => { - cmClient = contentManagementMock.createStartContract().client; - search = dataPluginMock.createStartContract().search; + getSavedSrch = jest.fn(); + searchSourceCreate = dataPluginMock.createStartContract().search.searchSource.create; }); test('should throw an error if so not found', async () => { let errorMessage = 'No error thrown.'; - cmClient.get = jest.fn().mockReturnValue({ + getSavedSrch = jest.fn().mockReturnValue({ statusCode: 404, error: 'Not Found', message: 'Saved object [ccf1af80-2297-11ec-86e0-1155ffb9c7a7] not found', @@ -33,8 +32,8 @@ describe('getSavedSearch', () => { try { await getSavedSearch('ccf1af80-2297-11ec-86e0-1155ffb9c7a7', { - contentManagement: cmClient, - search, + getSavedSrch, + searchSourceCreate, }); } catch (error) { errorMessage = error.message; @@ -46,7 +45,7 @@ describe('getSavedSearch', () => { }); test('should find saved search', async () => { - cmClient.get = jest.fn().mockReturnValue({ + getSavedSrch = jest.fn().mockReturnValue({ item: { attributes: { kibanaSavedObjectMeta: { @@ -77,11 +76,11 @@ describe('getSavedSearch', () => { }); const savedSearch = await getSavedSearch('ccf1af80-2297-11ec-86e0-1155ffb9c7a7', { - contentManagement: cmClient, - search, + getSavedSrch, + searchSourceCreate, }); - expect(cmClient.get).toHaveBeenCalled(); + expect(getSavedSrch).toHaveBeenCalled(); expect(savedSearch).toMatchInlineSnapshot(` Object { "breakdownField": undefined, @@ -150,7 +149,7 @@ describe('getSavedSearch', () => { }); test('should find saved search with sql mode', async () => { - cmClient.get = jest.fn().mockReturnValue({ + getSavedSrch = jest.fn().mockReturnValue({ item: { attributes: { kibanaSavedObjectMeta: { @@ -182,11 +181,11 @@ describe('getSavedSearch', () => { }); const savedSearch = await getSavedSearch('ccf1af80-2297-11ec-86e0-1155ffb9c7a7', { - contentManagement: cmClient, - search, + getSavedSrch, + searchSourceCreate, }); - expect(cmClient.get).toHaveBeenCalled(); + expect(getSavedSrch).toHaveBeenCalled(); expect(savedSearch).toMatchInlineSnapshot(` Object { "breakdownField": undefined, @@ -255,7 +254,7 @@ describe('getSavedSearch', () => { }); it('should call savedObjectsTagging.ui.getTagIdsFromReferences', async () => { - cmClient.get = jest.fn().mockReturnValue({ + getSavedSrch = jest.fn().mockReturnValue({ item: { attributes: { kibanaSavedObjectMeta: { @@ -296,8 +295,8 @@ describe('getSavedSearch', () => { }, } as unknown as SavedObjectsTaggingApi; await getSavedSearch('ccf1af80-2297-11ec-86e0-1155ffb9c7a7', { - contentManagement: cmClient, - search, + getSavedSrch, + searchSourceCreate, savedObjectsTagging, }); expect(savedObjectsTagging.ui.getTagIdsFromReferences).toHaveBeenCalledWith([ diff --git a/src/plugins/saved_search/public/services/saved_searches/get_saved_searches.ts b/src/plugins/saved_search/common/service/get_saved_searches.ts similarity index 65% rename from src/plugins/saved_search/public/services/saved_searches/get_saved_searches.ts rename to src/plugins/saved_search/common/service/get_saved_searches.ts index a6129d05d07bb..63a41a52b5391 100644 --- a/src/plugins/saved_search/public/services/saved_searches/get_saved_searches.ts +++ b/src/plugins/saved_search/common/service/get_saved_searches.ts @@ -6,20 +6,21 @@ * Side Public License, v 1. */ -import type { DataPublicPluginStart } from '@kbn/data-plugin/public'; -import { injectSearchSourceReferences, parseSearchSourceJSON } from '@kbn/data-plugin/public'; +import type { ISearchStartSearchSource } from '@kbn/data-plugin/common'; +import { injectReferences, parseSearchSourceJSON } from '@kbn/data-plugin/common'; +// these won't exist in on server import type { SpacesApi } from '@kbn/spaces-plugin/public'; import type { SavedObjectsTaggingApi } from '@kbn/saved-objects-tagging-oss-plugin/public'; + 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 type { SavedSearch } from '../types'; +import { SavedSearchType as SAVED_SEARCH_TYPE } from '..'; import { fromSavedSearchAttributes } from './saved_searches_utils'; -import type { SavedSearchCrudTypes } from '../../../common/content_management'; +import type { SavedSearchCrudTypes } from '../content_management'; -interface GetSavedSearchDependencies { - search: DataPublicPluginStart['search']; - contentManagement: ContentManagementPublicStart['client']; +export interface GetSavedSearchDependencies { + searchSourceCreate: ISearchStartSearchSource['create']; + getSavedSrch: (id: string) => Promise; spaces?: SpacesApi; savedObjectsTagging?: SavedObjectsTaggingApi; } @@ -32,15 +33,9 @@ const getSavedSearchUrlConflictMessage = async (json: string) => export const getSavedSearch = async ( savedSearchId: string, - { search, spaces, savedObjectsTagging, contentManagement }: GetSavedSearchDependencies + { searchSourceCreate, spaces, savedObjectsTagging, getSavedSrch }: GetSavedSearchDependencies ) => { - const so = await contentManagement.get< - SavedSearchCrudTypes['GetIn'], - SavedSearchCrudTypes['GetOut'] - >({ - contentTypeId: SAVED_SEARCH_TYPE, - id: savedSearchId, - }); + const so = await getSavedSrch(savedSearchId); // @ts-expect-error if (so.error) { @@ -53,6 +48,7 @@ export const getSavedSearch = async ( JSON.stringify({ targetType: SAVED_SEARCH_TYPE, sourceId: savedSearchId, + // front end only targetSpace: (await spaces?.getActiveSpace())?.id, }) ) @@ -65,11 +61,12 @@ export const getSavedSearch = async ( savedSearch.attributes.kibanaSavedObjectMeta?.searchSourceJSON ?? '{}' ); - const searchSourceValues = injectSearchSourceReferences( - parsedSearchSourceJSON as Parameters[0], + const searchSourceValues = injectReferences( + parsedSearchSourceJSON as Parameters[0], savedSearch.references ); + // front end only const tags = savedObjectsTagging ? savedObjectsTagging.ui.getTagIdsFromReferences(savedSearch.references) : undefined; @@ -79,7 +76,7 @@ export const getSavedSearch = async ( savedSearch.attributes, tags, savedSearch.references, - await search.searchSource.create(searchSourceValues), + await searchSourceCreate(searchSourceValues), so.meta ); @@ -92,9 +89,9 @@ export const getSavedSearch = async ( * @param search */ export const getNewSavedSearch = ({ - search, + searchSource, }: { - search: DataPublicPluginStart['search']; + searchSource: ISearchStartSearchSource; }): SavedSearch => ({ - searchSource: search.searchSource.createEmpty(), + searchSource: searchSource.createEmpty(), }); diff --git a/src/plugins/saved_search/public/services/saved_searches/saved_searches_utils.test.ts b/src/plugins/saved_search/common/service/saved_searches_utils.test.ts similarity index 97% rename from src/plugins/saved_search/public/services/saved_searches/saved_searches_utils.test.ts rename to src/plugins/saved_search/common/service/saved_searches_utils.test.ts index 4ebf62dffb443..8286276bb898e 100644 --- a/src/plugins/saved_search/public/services/saved_searches/saved_searches_utils.test.ts +++ b/src/plugins/saved_search/common/service/saved_searches_utils.test.ts @@ -10,8 +10,7 @@ import { fromSavedSearchAttributes, toSavedSearchAttributes } from './saved_sear import { createSearchSourceMock } from '@kbn/data-plugin/public/mocks'; -import type { SavedSearchAttributes } from '../../../common'; -import type { SavedSearch } from './types'; +import type { SavedSearch, SavedSearchAttributes } from '../types'; describe('saved_searches_utils', () => { describe('fromSavedSearchAttributes', () => { diff --git a/src/plugins/saved_search/public/services/saved_searches/saved_searches_utils.ts b/src/plugins/saved_search/common/service/saved_searches_utils.ts similarity index 92% rename from src/plugins/saved_search/public/services/saved_searches/saved_searches_utils.ts rename to src/plugins/saved_search/common/service/saved_searches_utils.ts index 129e1e5ff6dc6..c4e663e5f6352 100644 --- a/src/plugins/saved_search/public/services/saved_searches/saved_searches_utils.ts +++ b/src/plugins/saved_search/common/service/saved_searches_utils.ts @@ -8,11 +8,10 @@ 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'; +import type { SavedSearchAttributes, SavedSearch } from '..'; +import { fromSavedSearchAttributes as fromSavedSearchAttributesCommon } from '..'; -export { getSavedSearchUrl, getSavedSearchFullPathUrl } from '../../../common'; +export { getSavedSearchUrl, getSavedSearchFullPathUrl } from '..'; export const fromSavedSearchAttributes = ( id: string, diff --git a/src/plugins/saved_search/common/types.ts b/src/plugins/saved_search/common/types.ts index 51c016b7dfbda..3da4276aeb1dd 100644 --- a/src/plugins/saved_search/common/types.ts +++ b/src/plugins/saved_search/common/types.ts @@ -8,6 +8,7 @@ import type { ISearchSource, RefreshInterval, TimeRange } from '@kbn/data-plugin/common'; import type { SavedObjectReference } from '@kbn/core-saved-objects-server'; +import type { SavedObjectsResolveResponse } from '@kbn/core/server'; import { VIEW_MODE } from '.'; export interface DiscoverGridSettings { @@ -75,4 +76,10 @@ export interface SavedSearch { rowsPerPage?: number; breakdownField?: string; references?: SavedObjectReference[]; + sharingSavedObjectProps?: { + outcome?: SavedObjectsResolveResponse['outcome']; + aliasTargetId?: SavedObjectsResolveResponse['alias_target_id']; + aliasPurpose?: SavedObjectsResolveResponse['alias_purpose']; + errorJSON?: string; + }; } diff --git a/src/plugins/saved_search/kibana.jsonc b/src/plugins/saved_search/kibana.jsonc index 908a397bd590d..03df75a7d7924 100644 --- a/src/plugins/saved_search/kibana.jsonc +++ b/src/plugins/saved_search/kibana.jsonc @@ -9,7 +9,8 @@ "browser": true, "requiredPlugins": [ "data", - "contentManagement" + "contentManagement", + "expressions" ], "optionalPlugins": [ "spaces", diff --git a/src/plugins/data/public/search/expressions/kibana_context.ts b/src/plugins/saved_search/public/expressions/kibana_context.ts similarity index 71% rename from src/plugins/data/public/search/expressions/kibana_context.ts rename to src/plugins/saved_search/public/expressions/kibana_context.ts index cfbac7ad8a5ca..89078aa424bde 100644 --- a/src/plugins/data/public/search/expressions/kibana_context.ts +++ b/src/plugins/saved_search/public/expressions/kibana_context.ts @@ -7,9 +7,8 @@ */ import { StartServicesAccessor } from '@kbn/core/public'; -import { SavedObjectsClientCommon } from '@kbn/data-views-plugin/public'; -import { getKibanaContextFn } from '../../../common/search/expressions'; -import { DataPublicPluginStart, DataStartDependencies } from '../../types'; +import { getKibanaContextFn } from '../../common'; +import { SavedSearchPublicPluginStart, SavedSearchPublicStartDependencies } from '../plugin'; /** * This is some glue code that takes in `core.getStartServices`, extracts the dependencies @@ -25,15 +24,17 @@ import { DataPublicPluginStart, DataStartDependencies } from '../../types'; * * @internal */ + export function getKibanaContext({ getStartServices, }: { - getStartServices: StartServicesAccessor; + getStartServices: StartServicesAccessor< + SavedSearchPublicStartDependencies, + SavedSearchPublicPluginStart + >; }) { return getKibanaContextFn(async () => { - const [core] = await getStartServices(); - return { - savedObjectsClient: core.savedObjects.client as unknown as SavedObjectsClientCommon, - }; + const [, , { get: getSavedSearch }] = await getStartServices(); + return { getSavedSearch }; }); } diff --git a/src/plugins/saved_search/public/plugin.ts b/src/plugins/saved_search/public/plugin.ts index 80fe6b6fbc0e7..2d8d53c821ae5 100644 --- a/src/plugins/saved_search/public/plugin.ts +++ b/src/plugins/saved_search/public/plugin.ts @@ -6,10 +6,11 @@ * Side Public License, v 1. */ -import { CoreSetup, CoreStart, Plugin } from '@kbn/core/public'; +import { CoreSetup, CoreStart, Plugin, StartServicesAccessor } 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 { ExpressionsSetup } from '@kbn/expressions-plugin/public'; import { i18n } from '@kbn/i18n'; import type { ContentManagementPublicSetup, @@ -25,6 +26,8 @@ import { import { SavedSearch, SavedSearchAttributes } from '../common/types'; import { SavedSearchType, LATEST_VERSION } from '../common'; import { SavedSearchesService } from './services/saved_searches/saved_searches_service'; +import { kibanaContext } from '../common/expressions'; +import { getKibanaContext } from './expressions/kibana_context'; /** * Saved search plugin public Setup contract @@ -50,6 +53,7 @@ export interface SavedSearchPublicPluginStart { */ export interface SavedSearchPublicSetupDependencies { contentManagement: ContentManagementPublicSetup; + expressions: ExpressionsSetup; } /** @@ -71,7 +75,10 @@ export class SavedSearchPublicPlugin SavedSearchPublicStartDependencies > { - public setup(core: CoreSetup, { contentManagement }: SavedSearchPublicSetupDependencies) { + public setup( + { getStartServices }: CoreSetup, + { contentManagement, expressions }: SavedSearchPublicSetupDependencies + ) { contentManagement.registry.register({ id: SavedSearchType, version: { @@ -82,6 +89,17 @@ export class SavedSearchPublicPlugin }), }); + expressions.registerFunction( + getKibanaContext({ getStartServices } as { + getStartServices: StartServicesAccessor< + SavedSearchPublicStartDependencies, + SavedSearchPublicPluginStart + >; + }) + ); + + expressions.registerType(kibanaContext); + return {}; } diff --git a/src/plugins/saved_search/public/services/saved_searches/index.ts b/src/plugins/saved_search/public/services/saved_searches/index.ts index e09ce932345b3..456e777292778 100644 --- a/src/plugins/saved_search/public/services/saved_searches/index.ts +++ b/src/plugins/saved_search/public/services/saved_searches/index.ts @@ -6,8 +6,11 @@ * Side Public License, v 1. */ -export { getSavedSearch, getNewSavedSearch } from './get_saved_searches'; -export { getSavedSearchUrl, getSavedSearchFullPathUrl } from './saved_searches_utils'; +export { getSavedSearch, getNewSavedSearch } from '../../../common/service/get_saved_searches'; +export { + getSavedSearchUrl, + getSavedSearchFullPathUrl, +} from '../../../common/service/saved_searches_utils'; export type { SaveSavedSearchOptions } from './save_saved_searches'; export { saveSavedSearch } from './save_saved_searches'; export { SAVED_SEARCH_TYPE } from './constants'; diff --git a/src/plugins/saved_search/public/services/saved_searches/save_saved_searches.ts b/src/plugins/saved_search/public/services/saved_searches/save_saved_searches.ts index 9ea35b8389f00..4792680285bfc 100644 --- a/src/plugins/saved_search/public/services/saved_searches/save_saved_searches.ts +++ b/src/plugins/saved_search/public/services/saved_searches/save_saved_searches.ts @@ -10,7 +10,7 @@ import type { SavedObjectsTaggingApi } from '@kbn/saved-objects-tagging-oss-plug 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 { toSavedSearchAttributes } from '../../../common/service/saved_searches_utils'; import type { SavedSearchCrudTypes } from '../../../common/content_management'; export interface SaveSavedSearchOptions { diff --git a/src/plugins/saved_search/public/services/saved_searches/saved_searches_service.ts b/src/plugins/saved_search/public/services/saved_searches/saved_searches_service.ts index 35a7ea5c571af..4b38626e63b21 100644 --- a/src/plugins/saved_search/public/services/saved_searches/saved_searches_service.ts +++ b/src/plugins/saved_search/public/services/saved_searches/saved_searches_service.ts @@ -27,10 +27,16 @@ export class SavedSearchesService { get = (savedSearchId: string) => { const { search, contentManagement, spaces, savedObjectsTaggingOss } = this.deps; + const getViaCm = (id: string) => + contentManagement.get({ + contentTypeId: SavedSearchType, + id, + }); + return getSavedSearch(savedSearchId, { - search, - contentManagement, + getSavedSrch: getViaCm, spaces, + searchSourceCreate: search.searchSource.create, savedObjectsTagging: savedObjectsTaggingOss?.getTaggingApi(), }); }; @@ -45,7 +51,7 @@ export class SavedSearchesService { }); return result.hits; }; - getNew = () => getNewSavedSearch({ search: this.deps.search }); + getNew = () => getNewSavedSearch({ searchSource: this.deps.search.searchSource }); find = async (search: string) => { const { contentManagement } = this.deps; diff --git a/src/plugins/data/server/search/expressions/kibana_context.ts b/src/plugins/saved_search/server/expressions/kibana_context.ts similarity index 53% rename from src/plugins/data/server/search/expressions/kibana_context.ts rename to src/plugins/saved_search/server/expressions/kibana_context.ts index 14fdcdd784f33..2c75dca8a2956 100644 --- a/src/plugins/data/server/search/expressions/kibana_context.ts +++ b/src/plugins/saved_search/server/expressions/kibana_context.ts @@ -7,9 +7,10 @@ */ import { StartServicesAccessor } from '@kbn/core/server'; -import { SavedObjectsClientCommon } from '@kbn/data-views-plugin/server'; -import { getKibanaContextFn } from '../../../common/search/expressions'; -import { DataPluginStart, DataPluginStartDependencies } from '../../plugin'; +import { getKibanaContextFn } from '../../common'; +import { SavedSearchServerStartDeps } from '../plugin'; +import { getSavedSearch } from '../../common/service/get_saved_searches'; +import { SavedSearchAttributes } from '../../common/types'; /** * This is some glue code that takes in `core.getStartServices`, extracts the dependencies @@ -25,20 +26,36 @@ import { DataPluginStart, DataPluginStartDependencies } from '../../plugin'; * * @internal */ -export function getKibanaContext({ - getStartServices, -}: { - getStartServices: StartServicesAccessor; -}) { +export function getKibanaContext( + getStartServices: StartServicesAccessor +) { return getKibanaContextFn(async (getKibanaRequest) => { const request = getKibanaRequest && getKibanaRequest(); if (!request) { throw new Error('KIBANA_CONTEXT_KIBANA_REQUEST_MISSING'); } - const [{ savedObjects }] = await getStartServices(); + const [{ savedObjects }, { data }] = await getStartServices(); return { - savedObjectsClient: savedObjects.getScopedClient(request) as any as SavedObjectsClientCommon, + getSavedSearch: async (id: string) => { + const searchSourceCreate = (await data.search.searchSource.asScoped(request)).create; + const getSavedSrch = async (searchId: string) => { + const so = await savedObjects + .getScopedClient(request) + .resolve('search', searchId); + + return { + item: so.saved_object, + meta: { + outcome: so.outcome, + aliasTargetId: so.alias_target_id, + aliasPurpose: so.alias_purpose, + }, + }; + }; + + return getSavedSearch(id, { searchSourceCreate, getSavedSrch }); + }, }; }); } diff --git a/src/plugins/saved_search/server/plugin.ts b/src/plugins/saved_search/server/plugin.ts index adb6492eae42e..0f3e41894ff22 100644 --- a/src/plugins/saved_search/server/plugin.ts +++ b/src/plugins/saved_search/server/plugin.ts @@ -7,21 +7,41 @@ */ import { CoreSetup, CoreStart, Plugin } from '@kbn/core/server'; -import type { PluginSetup as DataPluginSetup } from '@kbn/data-plugin/server'; +import { StartServicesAccessor } from '@kbn/core/server'; +import type { + PluginSetup as DataPluginSetup, + PluginStart as DataPluginStart, +} from '@kbn/data-plugin/server'; import type { ContentManagementServerSetup } from '@kbn/content-management-plugin/server'; +import { ExpressionsServerSetup } from '@kbn/expressions-plugin/server'; import { getSavedSearchObjectType } from './saved_objects'; import { SavedSearchType, LATEST_VERSION } from '../common'; import { SavedSearchStorage } from './content_management'; +import { kibanaContext } from '../common/expressions'; +import { getKibanaContext } from './expressions/kibana_context'; +import { getSavedSearch } from '../common/service/get_saved_searches'; -export class SavedSearchServerPlugin implements Plugin { +/** + * Saved search plugin server Setup contract + */ +export interface SavedSearchPublicSetupDependencies { + data: DataPluginSetup; + contentManagement: ContentManagementServerSetup; + expressions: ExpressionsServerSetup; +} + +export interface SavedSearchServerStartDeps { + data: DataPluginStart; +} + +export class SavedSearchServerPlugin + implements Plugin +{ public setup( core: CoreSetup, - plugins: { - data: DataPluginSetup; - contentManagement: ContentManagementServerSetup; - } + { data, contentManagement, expressions }: SavedSearchPublicSetupDependencies ) { - plugins.contentManagement.register({ + contentManagement.register({ id: SavedSearchType, storage: new SavedSearchStorage(), version: { @@ -29,16 +49,23 @@ export class SavedSearchServerPlugin implements Plugin { }, }); - const getSearchSourceMigrations = plugins.data.search.searchSource.getAllMigrations.bind( - plugins.data.search.searchSource - ); + const searchSource = data.search.searchSource; + + const getSearchSourceMigrations = searchSource.getAllMigrations.bind(searchSource); core.savedObjects.registerType(getSavedSearchObjectType(getSearchSourceMigrations)); + expressions.registerType(kibanaContext); + expressions.registerFunction( + getKibanaContext(core.getStartServices as StartServicesAccessor) + ); + return {}; } public start(core: CoreStart) { - return {}; + return { + getSavedSearch, + }; } public stop() {} diff --git a/src/plugins/saved_search/tsconfig.json b/src/plugins/saved_search/tsconfig.json index b1c9253b1fd25..468279bbf31cc 100644 --- a/src/plugins/saved_search/tsconfig.json +++ b/src/plugins/saved_search/tsconfig.json @@ -22,6 +22,9 @@ "@kbn/object-versioning", "@kbn/content-management-utils", "@kbn/content-management-plugin", + "@kbn/es-query", + "@kbn/utility-types-jest", + "@kbn/expressions-plugin", ], "exclude": [ "target/**/*", diff --git a/test/functional/fixtures/kbn_archiver/saved_search.json b/test/functional/fixtures/kbn_archiver/saved_search.json new file mode 100644 index 0000000000000..dced24b053ff3 --- /dev/null +++ b/test/functional/fixtures/kbn_archiver/saved_search.json @@ -0,0 +1,27 @@ +{ + "attributes": { + "columns": [ + "_source" + ], + "description": "A Saved Search Description", + "hits": 0, + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{\"query\":{\"query\":\"geo.src :\\\"FR\\\" \",\"language\":\"kuery\"},\"filter\":[]}" + }, + "sort": [], + "title": "A Saved Search", + "version": 1, + "timeRange" : { + "from": "2006-09-21T00:00:00Z", + "to": "2015-09-22T00:00:00Z" + } + }, + "coreMigrationVersion": "7.17.1", + "id": "ab12e3c0-f231-11e6-9486-733b1ac9221a", + "migrationVersion": { + "search": "7.9.3" + }, + "references": [], + "type": "search", + "version": "WzQzLDJd" +} diff --git a/test/interpreter_functional/test_suites/run_pipeline/esaggs.ts b/test/interpreter_functional/test_suites/run_pipeline/esaggs.ts index f71fa58cd7cc5..d29c63b467b64 100644 --- a/test/interpreter_functional/test_suites/run_pipeline/esaggs.ts +++ b/test/interpreter_functional/test_suites/run_pipeline/esaggs.ts @@ -23,6 +23,7 @@ export default function ({ updateBaselines, }: FtrProviderContext & { updateBaselines: boolean }) { const supertest = getService('supertest'); + const kibanaServer = getService('kibanaServer'); let expectExpression: ExpectExpression; const expectClientToMatchServer = async (title: string, expression: string) => { @@ -92,6 +93,43 @@ export default function ({ }); }); + describe('loads a saved search', () => { + before(async () => { + await kibanaServer.importExport.load( + 'test/functional/fixtures/kbn_archiver/saved_search.json' + ); + }); + after(async () => { + await kibanaServer.importExport.unload( + 'test/functional/fixtures/kbn_archiver/saved_search.json' + ); + }); + + const expression = ` + kibana_context savedSearchId="ab12e3c0-f231-11e6-9486-733b1ac9221a" + | esaggs index={indexPatternLoad id='logstash-*'} + aggs={aggCount id="1" enabled=true schema="metric"} + `; + + it('correctly applies filter from saved search', async () => { + const result = await expectExpression('esaggs_saved_searches', expression).getResponse(); + expect(getCell(result, 0, 0)).to.be(119); + }); + + it('correctly applies filter - on the server', async () => { + await supertest + .post('/api/interpreter_functional/run_expression') + .set('kbn-xsrf', 'anything') + .send({ expression, input: undefined }) + .expect(200) + .expect(({ body }) => { + expect(body.columns[0].meta.index).to.be('logstash-*'); + expect(body.columns[0].meta.source).to.be('esaggs'); + expect(getCell(body, 0, 0)).to.be(119); + }); + }); + }); + describe('correctly runs on the server', () => { it('runs the provided agg on the server', async () => { const expression = ` diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/utils/new_job_utils.test.ts b/x-pack/plugins/ml/public/application/jobs/new_job/utils/new_job_utils.test.ts index f932af1143f82..bdec35bfa6054 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/utils/new_job_utils.test.ts +++ b/x-pack/plugins/ml/public/application/jobs/new_job/utils/new_job_utils.test.ts @@ -8,7 +8,7 @@ import type { IUiSettingsClient } from '@kbn/core/public'; import { DataView } from '@kbn/data-views-plugin/common'; import { createSearchItems } from './new_job_utils'; -import { fromSavedSearchAttributes } from '@kbn/saved-search-plugin/public/services/saved_searches/saved_searches_utils'; +import { fromSavedSearchAttributes } from '@kbn/saved-search-plugin/common'; import type { ISearchSource } from '@kbn/data-plugin/public'; describe('createSearchItems', () => { @@ -42,17 +42,9 @@ describe('createSearchItems', () => { isTextBasedQuery: false, }, [], - [ - { - name: 'kibanaSavedObjectMeta.searchSourceJSON.index', - type: 'index-pattern', - id: '7e252840-bd27-11ea-8a6c-75d1a0bd08ab', - }, - ], { getField: getFieldMock(searchSource), - } as unknown as ISearchSource, - {} + } as unknown as ISearchSource ); test('should match data view', () => { diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json index 8527f1d41c74a..ba8376cf9ca10 100644 --- a/x-pack/plugins/translations/translations/fr-FR.json +++ b/x-pack/plugins/translations/translations/fr-FR.json @@ -2004,11 +2004,11 @@ "data.search.functions.ipRange.from.help": "Spécifier l'adresse de début", "data.search.functions.ipRange.help": "Créer une plage d'IP", "data.search.functions.ipRange.to.help": "Spécifier l'adresse de fin", - "data.search.functions.kibana_context.filters.help": "Spécifier des filtres génériques Kibana", - "data.search.functions.kibana_context.help": "Met à jour le contexte général de Kibana.", - "data.search.functions.kibana_context.q.help": "Spécifier une recherche en texte libre Kibana", - "data.search.functions.kibana_context.savedSearchId.help": "Spécifier l'ID de recherche enregistrée à utiliser pour les requêtes et les filtres", - "data.search.functions.kibana_context.timeRange.help": "Spécifier le filtre de plage temporelle Kibana", + "savedSearch.kibana_context.filters.help": "Spécifier des filtres génériques Kibana", + "savedSearch.kibana_context.help": "Met à jour le contexte général de Kibana.", + "savedSearch.kibana_context.q.help": "Spécifier une recherche en texte libre Kibana", + "savedSearch.kibana_context.savedSearchId.help": "Spécifier l'ID de recherche enregistrée à utiliser pour les requêtes et les filtres", + "savedSearch.kibana_context.timeRange.help": "Spécifier le filtre de plage temporelle Kibana", "data.search.functions.kibana.help": "Permet d’obtenir le contexte général de Kibana.", "data.search.functions.kibanaFilter.disabled.help": "Si le filtre doit être désactivé", "data.search.functions.kibanaFilter.field.help": "Spécifier une recherche en texte libre esdsl", diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 2f48fe500ec54..9d30c9d27076c 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -2004,11 +2004,11 @@ "data.search.functions.ipRange.from.help": "開始アドレスを指定", "data.search.functions.ipRange.help": "IP範囲を作成", "data.search.functions.ipRange.to.help": "終了アドレスを指定", - "data.search.functions.kibana_context.filters.help": "Kibana ジェネリックフィルターを指定します", - "data.search.functions.kibana_context.help": "Kibana グローバルコンテキストを更新します", - "data.search.functions.kibana_context.q.help": "自由形式の Kibana テキストクエリを指定します", - "data.search.functions.kibana_context.savedSearchId.help": "クエリとフィルターに使用する保存検索ID を指定します。", - "data.search.functions.kibana_context.timeRange.help": "Kibana 時間範囲フィルターを指定します", + "savedSearch.kibana_context.filters.help": "Kibana ジェネリックフィルターを指定します", + "savedSearch.kibana_context.help": "Kibana グローバルコンテキストを更新します", + "savedSearch.kibana_context.q.help": "自由形式の Kibana テキストクエリを指定します", + "savedSearch.kibana_context.savedSearchId.help": "クエリとフィルターに使用する保存検索ID を指定します。", + "savedSearch.kibana_context.timeRange.help": "Kibana 時間範囲フィルターを指定します", "data.search.functions.kibana.help": "Kibana グローバルコンテキストを取得します", "data.search.functions.kibanaFilter.disabled.help": "フィルターは無効でなければなりません", "data.search.functions.kibanaFilter.field.help": "フリーフォームesdslクエリを指定", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index e43b03a679480..e317ffc9db76a 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -2004,11 +2004,11 @@ "data.search.functions.ipRange.from.help": "指定开始地址", "data.search.functions.ipRange.help": "创建 IP 范围", "data.search.functions.ipRange.to.help": "指定结束地址", - "data.search.functions.kibana_context.filters.help": "指定 Kibana 常规筛选", - "data.search.functions.kibana_context.help": "更新 kibana 全局上下文", - "data.search.functions.kibana_context.q.help": "指定 Kibana 自由格式文本查询", - "data.search.functions.kibana_context.savedSearchId.help": "指定要用于查询和筛选的已保存搜索 ID", - "data.search.functions.kibana_context.timeRange.help": "指定 Kibana 时间范围筛选", + "savedSearch.kibana_context.filters.help": "指定 Kibana 常规筛选", + "savedSearch.kibana_context.help": "更新 kibana 全局上下文", + "savedSearch.kibana_context.q.help": "指定 Kibana 自由格式文本查询", + "savedSearch.kibana_context.savedSearchId.help": "指定要用于查询和筛选的已保存搜索 ID", + "savedSearch.kibana_context.timeRange.help": "指定 Kibana 时间范围筛选", "data.search.functions.kibana.help": "获取 kibana 全局上下文", "data.search.functions.kibanaFilter.disabled.help": "如果禁用该筛选", "data.search.functions.kibanaFilter.field.help": "指定自由格式 esdsl 查询", From f6f5986376fd01a1643fcc15ba71df821eb75890 Mon Sep 17 00:00:00 2001 From: Ashokaditya <1849116+ashokaditya@users.noreply.github.com> Date: Tue, 11 Jul 2023 15:41:35 +0200 Subject: [PATCH 03/61] [Security Solution][Endpoint] Unskip mocked response actions history cypress test (#161533) ## Summary Unskip test that was timing out at indexing test hosts. The timeout has since been increased in elastic/kibana/pull/159518 It was added in elastic/kibana/pull/157777 and skipped in elastic/kibana/pull/156933 thus should be backported to `8.8.1` and `8.9.0` --- .../cypress/e2e/mocked_data/reponse_actions_history.cy.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/x-pack/plugins/security_solution/public/management/cypress/e2e/mocked_data/reponse_actions_history.cy.ts b/x-pack/plugins/security_solution/public/management/cypress/e2e/mocked_data/reponse_actions_history.cy.ts index 741164cc6d433..cb6c7c3a96eee 100644 --- a/x-pack/plugins/security_solution/public/management/cypress/e2e/mocked_data/reponse_actions_history.cy.ts +++ b/x-pack/plugins/security_solution/public/management/cypress/e2e/mocked_data/reponse_actions_history.cy.ts @@ -32,8 +32,7 @@ describe('Response actions history page', () => { } }); - // Flakey, example build failure: https://buildkite.com/elastic/kibana-pull-request/builds/132245 - it.skip('retains expanded action details on page reload', () => { + it('retains expanded action details on page reload', () => { loadPage(`/app/security/administration/response_actions_history`); cy.getByTestSubj('response-actions-list-expand-button').eq(3).click(); // 4th row on 1st page cy.getByTestSubj('response-actions-list-details-tray').should('exist'); From 7e7293a520f3e96440ff3d9f1b3ba438d0731066 Mon Sep 17 00:00:00 2001 From: Marta Bondyra <4283304+mbondyra@users.noreply.github.com> Date: Tue, 11 Jul 2023 15:51:58 +0200 Subject: [PATCH 04/61] [Lens] Fix a11y issues & small styling issues (#161073) ## Summary Fixes the following issues: 1. Suggestions are not reachable by keyboard navigation (it was actually broken when fixing the [accesibility axe warning](https://github.com/elastic/kibana/issues/154787), I ran it now and it doesn't show now) https://github.com/elastic/kibana/assets/4283304/46c9eb70-4df7-462b-a878-b7d190aaf8c9 2. Dimension removal button is not reachable by keyboard navigation before: https://github.com/elastic/kibana/assets/4283304/5011679b-b05a-48ca-a877-bbc940607b56 after: https://github.com/elastic/kibana/assets/4283304/9d056512-a622-4c1c-aa5b-242e2762e9ae 3. The padding for the reordering groups got a distance between the empty dimension button (btw I have more little fixes for drag and drop look but want to submit it in a separate PR to not ping here Discover team): Screenshot 2023-07-03 at 17 42 41 Screenshot 2023-07-03 at 17 39 11 4. The focus outline for dataview selector and layer chart selector got a nicer rectangular shape before: Screenshot 2023-07-03 at 10 50 52 Screenshot 2023-07-03 at 17 41 18 after: Screenshot 2023-07-03 at 11 11 01 Screenshot 2023-07-03 at 17 39 51 5. The focus outline was corrected for dimension buttons. I also refactored code here to make margins, paddings and gaps the property of the containers and not the items themselves. I think this way it's more correct. before: Screenshot 2023-07-03 at 17 40 54 after: https://github.com/elastic/kibana/assets/4283304/712e2ac8-f81d-4896-b384-164fc2854766 --- .../dimension_buttons/dimension_button.tsx | 29 +++++++++---------- .../dimension_button_icon.tsx | 23 ++++----------- .../dimension_buttons/empty_button.tsx | 7 ++--- .../components/dimension_buttons/trigger.tsx | 1 - .../config_panel/layer_panel.scss | 4 +-- .../editor_frame/config_panel/layer_panel.tsx | 1 + .../editor_frame/suggestion_panel.test.tsx | 2 +- .../editor_frame/suggestion_panel.tsx | 2 +- .../dataview_picker/trigger.tsx | 2 +- .../xy/xy_config_panel/layer_header.tsx | 1 + 10 files changed, 29 insertions(+), 43 deletions(-) diff --git a/src/plugins/visualization_ui_components/public/components/dimension_buttons/dimension_button.tsx b/src/plugins/visualization_ui_components/public/components/dimension_buttons/dimension_button.tsx index 44b158f43cf40..1a72a9b612299 100644 --- a/src/plugins/visualization_ui_components/public/components/dimension_buttons/dimension_button.tsx +++ b/src/plugins/visualization_ui_components/public/components/dimension_buttons/dimension_button.tsx @@ -58,20 +58,14 @@ export function DimensionButton({ css={css` ${useEuiFontSize('s')} border-radius: ${euiThemeVars.euiBorderRadius}; + position: relative; + line-height: 1; + overflow: hidden; display: flex; align-items: center; - overflow: hidden; + gap: ${euiThemeVars.euiSizeS}; min-height: ${euiThemeVars.euiSizeXL}; - position: relative; - - &:hover, - &:focus { - .lnsLayerPanel__dimensionRemove { - visibility: visible; - opacity: 1; - transition: opacity ${euiThemeVars.euiAnimSpeedFast} ease-in-out; - } - } + padding: ${euiThemeVars.euiSizeXS} ${euiThemeVars.euiSizeS}; `} > @@ -120,12 +114,17 @@ export function DimensionButton({ })} onClick={() => onRemoveClick(accessorConfig.columnId)} css={css` - margin-right: ${euiThemeVars.euiSizeS}; - visibility: hidden; - opacity: 0; color: ${euiThemeVars.euiTextSubduedColor}; + transition: ${euiThemeVars.euiAnimSpeedFast} ease-in-out; + transition-property: color, opacity, background-color, transform; + opacity: 0; - &:hover { + .domDragDrop:hover &, + .domDragDrop:focus-within & { + opacity: 1; + } + &:hover, + &:focus { color: ${euiThemeVars.euiColorDangerText}; } `} diff --git a/src/plugins/visualization_ui_components/public/components/dimension_buttons/dimension_button_icon.tsx b/src/plugins/visualization_ui_components/public/components/dimension_buttons/dimension_button_icon.tsx index 6294c27254a56..52e0aa665f54a 100644 --- a/src/plugins/visualization_ui_components/public/components/dimension_buttons/dimension_button_icon.tsx +++ b/src/plugins/visualization_ui_components/public/components/dimension_buttons/dimension_button_icon.tsx @@ -9,21 +9,12 @@ import React from 'react'; import { EuiFlexGroup, EuiFlexItem, EuiIcon } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { css } from '@emotion/react'; -import { euiThemeVars } from '@kbn/ui-theme'; import type { AccessorConfig, Message } from './types'; -const baseIconProps = { - css: css` - margin-left: ${euiThemeVars.euiSizeS}; - `, -} as const; - const getIconFromAccessorConfig = (accessorConfig: AccessorConfig) => ( <> {accessorConfig.triggerIconType === 'color' && accessorConfig.color && ( ( )} {accessorConfig.triggerIconType === 'disabled' && ( ( )} {accessorConfig.triggerIconType === 'invisible' && ( ( )} {accessorConfig.triggerIconType === 'aggregate' && ( ( )} {accessorConfig.triggerIconType === 'colorBy' && ( ( )} {accessorConfig.triggerIconType === 'custom' && accessorConfig.customIcon && ( {accessorConfig.triggerIconType !== 'none' && ( @@ -124,7 +113,7 @@ export function DimensionButtonIcon({ )} {severity && ( - + )} @@ -132,7 +121,7 @@ export function DimensionButtonIcon({ } return ( - + {indicatorIcon} {children} diff --git a/src/plugins/visualization_ui_components/public/components/dimension_buttons/empty_button.tsx b/src/plugins/visualization_ui_components/public/components/dimension_buttons/empty_button.tsx index 5b0cf67789879..9cb2586f3030d 100644 --- a/src/plugins/visualization_ui_components/public/components/dimension_buttons/empty_button.tsx +++ b/src/plugins/visualization_ui_components/public/components/dimension_buttons/empty_button.tsx @@ -33,6 +33,7 @@ export const EmptyDimensionButton = ({ width: 100%; border-radius: ${euiThemeVars.euiBorderRadius} !important; border: ${euiThemeVars.euiBorderWidthThin} dashed ${euiThemeVars.euiBorderColor} !important; + padding: ${euiThemeVars.euiSizeXS} ${euiThemeVars.euiSizeS}; `} color="text" // as far as I can tell all this currently adds is the correct active background color size="s" @@ -40,13 +41,9 @@ export const EmptyDimensionButton = ({ contentProps={{ css: css` justify-content: flex-start; + gap: ${euiThemeVars.euiSizeS}; padding: 0 !important; color: ${euiThemeVars.euiTextSubduedColor}; - gap: 0; - - .euiIcon { - margin-left: ${euiThemeVars.euiSizeS}; - } `, }} aria-label={ariaLabel} diff --git a/src/plugins/visualization_ui_components/public/components/dimension_buttons/trigger.tsx b/src/plugins/visualization_ui_components/public/components/dimension_buttons/trigger.tsx index 622faac8f766c..51e8b980fc63b 100644 --- a/src/plugins/visualization_ui_components/public/components/dimension_buttons/trigger.tsx +++ b/src/plugins/visualization_ui_components/public/components/dimension_buttons/trigger.tsx @@ -48,7 +48,6 @@ export const DimensionTrigger = ({ color={color} css={css` width: 100%; - padding: ${euiThemeVars.euiSizeXS} ${euiThemeVars.euiSizeS}; word-break: break-word; font-weight: ${euiThemeVars.euiFontWeightRegular}; `} diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.scss b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.scss index 3afcc0173ca2e..f2e3c26258182 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.scss +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.scss @@ -60,8 +60,8 @@ } .lnsLayerPanel__group { - margin: (-$euiSizeS) (-$euiSize); - padding: $euiSizeS $euiSize; + margin: (-$euiSizeXS) (-$euiSize); + padding: $euiSizeXS $euiSize; } .lnsLayerPanel__styleEditor { diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.tsx index f957886ce033a..683e052513ae2 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.tsx @@ -648,6 +648,7 @@ export function LayerPanel( background-color: ${euiThemeVars.euiColorLightShade} !important; border-color: transparent !important; box-shadow: none !important; + padding: 0 ${euiThemeVars.euiSizeS}; `} > {preview.expression || preview.error ? ( diff --git a/x-pack/plugins/lens/public/shared_components/dataview_picker/trigger.tsx b/x-pack/plugins/lens/public/shared_components/dataview_picker/trigger.tsx index 77bf60fc247c9..0669b7b08f07e 100644 --- a/x-pack/plugins/lens/public/shared_components/dataview_picker/trigger.tsx +++ b/x-pack/plugins/lens/public/shared_components/dataview_picker/trigger.tsx @@ -101,7 +101,7 @@ export function TriggerButton({ fullWidth {...colorProp} {...rest} - textProps={{ style: { width: '100%' } }} + textProps={{ style: { width: '100%', lineHeight: '100%' } }} > diff --git a/x-pack/plugins/lens/public/visualizations/xy/xy_config_panel/layer_header.tsx b/x-pack/plugins/lens/public/visualizations/xy/xy_config_panel/layer_header.tsx index cb37fcbf5edfc..e695477d25d78 100644 --- a/x-pack/plugins/lens/public/visualizations/xy/xy_config_panel/layer_header.tsx +++ b/x-pack/plugins/lens/public/visualizations/xy/xy_config_panel/layer_header.tsx @@ -235,6 +235,7 @@ const DataLayerHeaderTrigger = function ({ onClick={onClick} fullWidth size="s" + textProps={{ style: { lineHeight: '100%' } }} > <> From 6b3a29b4ec0b0da741a32f8152c7b4f42ddbac8d Mon Sep 17 00:00:00 2001 From: Marta Bondyra <4283304+mbondyra@users.noreply.github.com> Date: Tue, 11 Jul 2023 15:56:16 +0200 Subject: [PATCH 05/61] [Lens] fix drag and drop small styling details (#161121) ## Summary Fixes two subtle visual drag and drop thingies: 1. When reordering, the styles should look slightly differently: before: https://github.com/elastic/kibana/assets/4283304/b0f0d053-f6cc-414b-96cb-0b8ad892b613 after: https://github.com/elastic/kibana/assets/4283304/c52f8786-72e2-4ef3-a943-8050d7acf348 2. When picking up a dragging item, the whole dimension panel is jumping very slightly because we add `border`: before: https://github.com/elastic/kibana/assets/4283304/8efa71ec-d449-4dc9-8390-d4c33c62c1e0 after: https://github.com/elastic/kibana/assets/4283304/dbec7e64-5cb3-4b32-afcc-2c376cfda4a2 --- .../kbn-dom-drag-drop/src/sass/drag_drop.scss | 69 ++++++++++++------- .../src/sass/drag_drop_mixins.scss | 28 ++++++-- 2 files changed, 66 insertions(+), 31 deletions(-) diff --git a/packages/kbn-dom-drag-drop/src/sass/drag_drop.scss b/packages/kbn-dom-drag-drop/src/sass/drag_drop.scss index fecd5ee6bfbe0..ffea63cf94cef 100644 --- a/packages/kbn-dom-drag-drop/src/sass/drag_drop.scss +++ b/packages/kbn-dom-drag-drop/src/sass/drag_drop.scss @@ -55,10 +55,6 @@ } } -.domDragDrop-isActiveGroup { - background-color: transparentize($euiColorVis0, .75); -} - // Drop area while hovering with item .domDragDrop-isActiveDropTarget:not(.domDragDrop__dropOverlayWrapper) { z-index: $domDragDropZLevel3; @@ -77,10 +73,14 @@ .domDragDrop-notCompatible:not(.domDragDrop__dropOverlayWrapper) { background-color: $euiColorHighlight !important; - border: $euiBorderWidthThin dashed $euiColorVis5 !important; + &:before { + border: $euiBorderWidthThin dashed $euiColorVis5 !important; + } &.domDragDrop-isActiveDropTarget { background-color: rgba(251, 208, 17, .25) !important; - border-color: $euiColorVis5 !important; + &:before { + border-color: $euiColorVis5 !important; + } } } @@ -138,21 +138,6 @@ $reorderItemMargin: $euiSizeS; } } -.domDragDrop--isDragStarted { - opacity: .5; -} - -// Draggable item when it is moving -.domDragDrop-isHidden { - opacity: 0; - .domDragDrop__keyboardHandler { - &:focus, - &:focus-within { - animation: none; - } - } -} - .domDragDrop__extraDrops { opacity: 0; visibility: hidden; @@ -182,12 +167,20 @@ $reorderItemMargin: $euiSizeS; width: 100%; height: 100%; background: $euiColorLightestShade; - border-radius: 0; - &:first-child, &:first-child .domDragDrop__extraDrop { + + .domDragDrop__extraDrop, + .domDragDrop__extraDrop:before { + border-radius: 0; + } + + &:first-child .domDragDrop__extraDrop, + &:first-child .domDragDrop__extraDrop:before { border-top-left-radius: $euiSizeXS; border-top-right-radius: $euiSizeXS; } - &:last-child, &:last-child .domDragDrop__extraDrop { + + &:last-child .domDragDrop__extraDrop, + &:last-child .domDragDrop__extraDrop:before { border-bottom-left-radius: $euiSizeXS; border-bottom-right-radius: $euiSizeXS; } @@ -233,3 +226,31 @@ $reorderItemMargin: $euiSizeS; @include mixinDomDroppableActiveHover($euiBorderWidthThick); } } + +.domDragDrop-isActiveGroup { + background-color: transparentize($euiColorVis0, .75); + .domDragDrop-isKeyboardReorderInProgress { + .domDragDrop--isDragStarted { + opacity: 1; + } + } + .domDragDrop-isActiveDropTarget, + .domDragDrop-isDropTarget { + background: $euiColorEmptyShade !important; + } +} + +.domDragDrop--isDragStarted { + opacity: .5; +} + +// Draggable item when it is moving +.domDragDrop-isHidden { + opacity: 0; + .domDragDrop__keyboardHandler { + &:focus, + &:focus-within { + animation: none; + } + } +} diff --git a/packages/kbn-dom-drag-drop/src/sass/drag_drop_mixins.scss b/packages/kbn-dom-drag-drop/src/sass/drag_drop_mixins.scss index fb495b4fcfba4..6e753b8b6db32 100644 --- a/packages/kbn-dom-drag-drop/src/sass/drag_drop_mixins.scss +++ b/packages/kbn-dom-drag-drop/src/sass/drag_drop_mixins.scss @@ -16,11 +16,6 @@ $domDragDropZLevel3: 3; cursor: grab; } -// Static styles for a drop area -@mixin mixinDomDroppable($borderWidth: $euiBorderWidthThin) { - border: $borderWidth dashed transparent; -} - // Hovering state for drag item and drop area @mixin mixinDomDragDropHover { &:hover { @@ -29,16 +24,35 @@ $domDragDropZLevel3: 3; } } +// Static styles for a drop area +@mixin mixinDomDroppable($borderWidth: $euiBorderWidthThin) { + &:before { + content: ''; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + pointer-events: none; + border-radius: $euiBorderRadius; + border: $borderWidth dashed transparent; + } +} + // Style for drop area when there's an item being dragged @mixin mixinDomDroppableActive($borderWidth: $euiBorderWidthThin) { background-color: transparentize($euiColorVis0, .9) !important; - border: $borderWidth dashed $euiColorVis0 !important; + &:before { + border-color: $euiColorVis0 !important; + } } // Style for drop area while hovering with item @mixin mixinDomDroppableActiveHover($borderWidth: $euiBorderWidthThin) { background-color: transparentize($euiColorVis0, .75) !important; - border: $borderWidth dashed $euiColorVis0 !important; + &:before { + border-color: $euiColorVis0 !important; + } } // Style for drop area that is not allowed for current item From 811fb7c4fdc2de23ebf3c5c24117fd69d263875d Mon Sep 17 00:00:00 2001 From: amyjtechwriter <61687663+amyjtechwriter@users.noreply.github.com> Date: Tue, 11 Jul 2023 15:08:31 +0100 Subject: [PATCH 06/61] [Docs] Try to make it clear drilldows only available in dashboard (#161469) ## Summary Issue [raised on Slack](https://elastic.slack.com/archives/C0D8P2XK5/p1688661541837369) that it was a bit unclear on our [docs page](https://www.elastic.co/guide/en/kibana/8.8/maps-create-filter-from-map.html#maps-spatial-filters) that to use drilldowns you had to embed your map in Dashboard, and can't be used from the Maps application. Original: Screenshot 2023-07-07 at 15 32 27 Updated: Screenshot 2023-07-07 at 11 47 45 --------- Co-authored-by: David Kilfoyle <41695641+kilfoyle@users.noreply.github.com> --- docs/maps/search.asciidoc | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/docs/maps/search.asciidoc b/docs/maps/search.asciidoc index 8575d9fda01ed..bfd293aa2352f 100644 --- a/docs/maps/search.asciidoc +++ b/docs/maps/search.asciidoc @@ -99,16 +99,18 @@ To filter your dashboard by your map bounds as you pan and zoom your map: A spatial filter narrows search results to documents that either intersect with, are within, or do not intersect with the specified geometry. -You can create spatial filters in two ways: - -* Click the tool icon image:maps/images/tools_icon.png[], and then draw a shape, bounding box, or distance on the map to define the spatial filter. -* Click *Filter by geometry* in a <>, and then use the feature's geometry for the spatial filter. - Spatial filters have the following properties: * *Geometry label* enables you to provide a meaningful name for your spatial filter. * *Spatial relation* determines the {ref}/query-dsl-geo-shape-query.html#geo-shape-spatial-relations[spatial relation operator] to use at search time. -* *Action* specifies whether to apply the filter to the current view or to a drilldown action. Only available when the map is a panel in a {kibana-ref}/dashboard.html[dashboard] with {kibana-ref}/drilldowns.html[drilldowns]. +* *Action* specifies whether to apply the filter to the current view or to a drilldown action. + +NOTE: {kibana-ref}/drilldowns.html[Drilldowns] are available only if the map is a panel in a {kibana-ref}/dashboard.html[dashboard], and not within the Maps application. + +You can create spatial filters in two ways: + +* Click the tool icon image:maps/images/tools_icon.png[tool icon], and then draw a shape, bounding box, or distance on the map to define the spatial filter. +* Click *Filter by geometry* in a <>, and then use the feature's geometry for the spatial filter. [role="screenshot"] image::maps/images/create_spatial_filter.png[] From 8f01d85966496812d8d047e31f78a0adf2d98c6b Mon Sep 17 00:00:00 2001 From: Adam Demjen Date: Tue, 11 Jul 2023 10:23:37 -0400 Subject: [PATCH 07/61] [8.10] [ESRE] Filter already attached pipelines (#161590) ## Summary Filter out pipelines from the existing pipeline selection dropdown that are already attached to the current index. This also updates the evaluation of the "Existing pipeline" option in the configuration panel. Example: 2 pipelines that are already attached are filtered from the dropdown; when attaching the 3rd one, the existing pipelines option is disabled. ![filter_attached_pipelines](https://github.com/elastic/kibana/assets/14224983/b401fcc4-e72a-4f62-ac37-a20dfab106f6) ### Checklist - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios --- .../ml_inference/ml_inference_logic.test.ts | 14 ++----------- .../ml_inference/ml_inference_logic.ts | 15 ++++++------- .../pipelines/ml_inference/utils.ts | 21 ------------------- .../translations/translations/fr-FR.json | 1 - .../translations/translations/ja-JP.json | 1 - .../translations/translations/zh-CN.json | 1 - 6 files changed, 10 insertions(+), 43 deletions(-) diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/ml_inference_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/ml_inference_logic.test.ts index 8f24399b2a9b8..7196749612877 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/ml_inference_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/ml_inference_logic.test.ts @@ -297,7 +297,7 @@ describe('MlInferenceLogic', () => { }, ]); }); - it('returns disabled pipeline option if pipeline already attached', () => { + it('filters pipeline if pipeline already attached', () => { FetchMlInferencePipelineProcessorsApiLogic.actions.apiSuccess([ { modelId: 'test-model', @@ -324,17 +324,7 @@ describe('MlInferenceLogic', () => { }, }); - expect(MLInferenceLogic.values.existingInferencePipelines).toEqual([ - { - disabled: true, - disabledReason: expect.any(String), - pipelineName: 'unit-test', - modelType: '', - modelId: 'test-model', - sourceFields: ['body'], - indexFields: ['body'], - }, - ]); + expect(MLInferenceLogic.values.existingInferencePipelines).toEqual([]); }); }); describe('mlInferencePipeline', () => { diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/ml_inference_logic.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/ml_inference_logic.ts index e7b2cc71770a4..ac6d2f8401b00 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/ml_inference_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/ml_inference_logic.ts @@ -82,7 +82,7 @@ import { } from './types'; import { - getDisabledReason, + EXISTING_PIPELINE_DISABLED_MISSING_SOURCE_FIELDS, validateInferencePipelineConfiguration, validateInferencePipelineFields, validatePipelineNameIsAvailable, @@ -584,7 +584,9 @@ export const MLInferenceLogic = kea< mlInferencePipelinesData ) .map(([pipelineName, pipeline]): MLInferencePipelineOption | undefined => { - if (!pipeline) return undefined; + if (!pipeline || indexProcessorNames.includes(pipelineName)) return undefined; + + // Parse configuration from pipeline definition const pipelineParams = parseMlInferenceParametersFromPipeline(pipelineName, pipeline); if (!pipelineParams) return undefined; const { model_id: modelId, field_mappings: fieldMappings } = pipelineParams; @@ -593,11 +595,10 @@ export const MLInferenceLogic = kea< const missingSourceFields = sourceFields.filter((f) => !indexFields?.includes(f)) ?? []; const mlModel = supportedMLModels.find((model) => model.model_id === modelId); const modelType = mlModel ? getMLType(getMlModelTypesForModelConfig(mlModel)) : ''; - const disabledReason = getDisabledReason( - missingSourceFields, - indexProcessorNames, - pipelineName - ); + const disabledReason = + missingSourceFields.length > 0 + ? EXISTING_PIPELINE_DISABLED_MISSING_SOURCE_FIELDS(missingSourceFields.join(', ')) + : undefined; return { disabled: disabledReason !== undefined, diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/utils.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/utils.ts index 27683135b5d02..332f885a3543d 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/utils.ts +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/utils.ts @@ -95,27 +95,6 @@ export const EXISTING_PIPELINE_DISABLED_MISSING_SOURCE_FIELDS = ( } ); -export const EXISTING_PIPELINE_DISABLED_PIPELINE_EXISTS = i18n.translate( - 'xpack.enterpriseSearch.content.indices.pipelines.addInferencePipelineModal.steps.configure.existingPipeline.disabledPipelineExistsDescription', - { - defaultMessage: 'This pipeline cannot be selected because it is already attached.', - } -); - -export const getDisabledReason = ( - missingSourceFields: string[], - indexProcessorNames: string[], - pipelineName: string -): string | undefined => { - if (missingSourceFields.length > 0) { - return EXISTING_PIPELINE_DISABLED_MISSING_SOURCE_FIELDS(missingSourceFields.join(', ')); - } else if (indexProcessorNames.includes(pipelineName)) { - return EXISTING_PIPELINE_DISABLED_PIPELINE_EXISTS; - } - - return undefined; -}; - export const MODEL_SELECT_PLACEHOLDER = i18n.translate( 'xpack.enterpriseSearch.content.indices.pipelines.addInferencePipelineModal.steps.configure.model.placeholder', { defaultMessage: 'Select a model' } diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json index ba8376cf9ca10..3994a4807487a 100644 --- a/x-pack/plugins/translations/translations/fr-FR.json +++ b/x-pack/plugins/translations/translations/fr-FR.json @@ -13143,7 +13143,6 @@ "xpack.enterpriseSearch.content.indices.pipelines.addInferencePipelineModal.steps.configure.docsLink": "Découvrez l'importation et l'utilisation des modèles de ML dans Enterprise Search", "xpack.enterpriseSearch.content.indices.pipelines.addInferencePipelineModal.steps.configure.emptyValueError": "Champ obligatoire.", "xpack.enterpriseSearch.content.indices.pipelines.addInferencePipelineModal.steps.configure.existingPipeline.chooseLabel": "Choisir", - "xpack.enterpriseSearch.content.indices.pipelines.addInferencePipelineModal.steps.configure.existingPipeline.disabledPipelineExistsDescription": "Ce pipeline ne peut pas être sélectionné car il est déjà attaché.", "xpack.enterpriseSearch.content.indices.pipelines.addInferencePipelineModal.steps.configure.existingPipeline.existingLabel": "Pipeline existant", "xpack.enterpriseSearch.content.indices.pipelines.addInferencePipelineModal.steps.configure.existingPipeline.newLabel": "Nouveau pipeline", "xpack.enterpriseSearch.content.indices.pipelines.addInferencePipelineModal.steps.configure.existingPipeline.placeholder": "Effectuez une sélection", diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 9d30c9d27076c..b04bacbc52971 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -13142,7 +13142,6 @@ "xpack.enterpriseSearch.content.indices.pipelines.addInferencePipelineModal.steps.configure.docsLink": "エンタープライズ サーチでのMLモデルのインポートと使用の詳細", "xpack.enterpriseSearch.content.indices.pipelines.addInferencePipelineModal.steps.configure.emptyValueError": "フィールドが必要です。", "xpack.enterpriseSearch.content.indices.pipelines.addInferencePipelineModal.steps.configure.existingPipeline.chooseLabel": "選択", - "xpack.enterpriseSearch.content.indices.pipelines.addInferencePipelineModal.steps.configure.existingPipeline.disabledPipelineExistsDescription": "このパイプラインはすでにアタッチされているため、選択できません。", "xpack.enterpriseSearch.content.indices.pipelines.addInferencePipelineModal.steps.configure.existingPipeline.existingLabel": "既存のパイプライン", "xpack.enterpriseSearch.content.indices.pipelines.addInferencePipelineModal.steps.configure.existingPipeline.newLabel": "新しいパイプライン", "xpack.enterpriseSearch.content.indices.pipelines.addInferencePipelineModal.steps.configure.existingPipeline.placeholder": "1 つ選択してください", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index e317ffc9db76a..d7efba31bcff9 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -13142,7 +13142,6 @@ "xpack.enterpriseSearch.content.indices.pipelines.addInferencePipelineModal.steps.configure.docsLink": "详细了解如何在 Enterprise Search 中导入并使用 ML 模型", "xpack.enterpriseSearch.content.indices.pipelines.addInferencePipelineModal.steps.configure.emptyValueError": "“字段”必填。", "xpack.enterpriseSearch.content.indices.pipelines.addInferencePipelineModal.steps.configure.existingPipeline.chooseLabel": "选择", - "xpack.enterpriseSearch.content.indices.pipelines.addInferencePipelineModal.steps.configure.existingPipeline.disabledPipelineExistsDescription": "无法选择此管道,因为已附加该管道。", "xpack.enterpriseSearch.content.indices.pipelines.addInferencePipelineModal.steps.configure.existingPipeline.existingLabel": "现有管道", "xpack.enterpriseSearch.content.indices.pipelines.addInferencePipelineModal.steps.configure.existingPipeline.newLabel": "新建管道", "xpack.enterpriseSearch.content.indices.pipelines.addInferencePipelineModal.steps.configure.existingPipeline.placeholder": "选择一个", From 9b6ad7280de61c0ed1471eda54b98a64d485b637 Mon Sep 17 00:00:00 2001 From: Ievgen Sorokopud Date: Tue, 11 Jul 2023 16:27:24 +0200 Subject: [PATCH 08/61] [Security Solution] Rule is created when the conditional logic "If alert matches a query" is left blank (#159690) ## Summary Original ticket: https://github.com/elastic/kibana/issues/156706 These changes prevent user from creating/updating the rule when alert filter is selected and query left blank on the rule's action page. We gonna show an error saying "A custom query is required." in this case. Screenshot 2023-06-14 at 14 36 35 Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../rules/rule_actions_field/index.tsx | 13 +++---- .../validate_rule_actions_field.ts | 4 ++- .../application/lib/value_validators.ts | 18 +++++++++- .../action_connector_form/action_form.tsx | 3 ++ .../action_type_form.tsx | 36 ++++++++++++++----- .../triggers_actions_ui/public/index.ts | 2 ++ 6 files changed, 60 insertions(+), 16 deletions(-) diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/rule_actions_field/index.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/rule_actions_field/index.tsx index a524ad3929ea8..bfdc407fc551e 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/rule_actions_field/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/rule_actions_field/index.tsx @@ -119,7 +119,7 @@ export const RuleActionsField: React.FC = ({ }) => { const [fieldErrors, setFieldErrors] = useState(null); const form = useFormContext(); - const { isSubmitted, isSubmitting, isValid } = form; + const { isValid } = form; const { triggersActionsUi: { getActionForm }, } = useKibana().services; @@ -231,6 +231,7 @@ export const RuleActionsField: React.FC = ({ [field] ); + const isFormValidated = isValid !== undefined; const actionForm = useMemo( () => getActionForm({ @@ -251,6 +252,7 @@ export const RuleActionsField: React.FC = ({ hasSummary: true, notifyWhenSelectOptions: NOTIFY_WHEN_OPTIONS, defaultRuleFrequency: NOTIFICATION_DEFAULT_FREQUENCY, + disableErrorMessages: !isFormValidated, }), [ actions, @@ -262,18 +264,17 @@ export const RuleActionsField: React.FC = ({ setActionParamsProperty, setAlertActionsProperty, setActionAlertsFilterProperty, + isFormValidated, ] ); useEffect(() => { - if (isSubmitting || !field.errors.length) { - return setFieldErrors(null); - } - if (isSubmitted && !isSubmitting && isValid === false && field.errors.length) { + if (isValid === false) { const errorsString = field.errors.map(({ message }) => message).join('\n'); return setFieldErrors(errorsString); } - }, [isSubmitted, isSubmitting, field.isChangingValue, isValid, field.errors, setFieldErrors]); + return setFieldErrors(null); + }, [field.errors, isValid]); return ( diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/validate_rule_actions_field/validate_rule_actions_field.ts b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/validate_rule_actions_field/validate_rule_actions_field.ts index 8d04ed43e0538..b881fd805e597 100644 --- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/validate_rule_actions_field/validate_rule_actions_field.ts +++ b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/validate_rule_actions_field/validate_rule_actions_field.ts @@ -16,6 +16,7 @@ import type { RuleAction, ActionTypeRegistryContract, } from '@kbn/triggers-actions-ui-plugin/public'; +import { validateActionFilterQuery } from '@kbn/triggers-actions-ui-plugin/public'; import type { RuleActionsFormData } from '../../../../../detection_engine/rule_management_ui/components/rules_table/bulk_actions/forms/rule_actions_form'; import type { ActionsStepRule } from '../../../../pages/detection_engine/rules/types'; import type { ValidationFunc, ERROR_CODE } from '../../../../../shared_imports'; @@ -29,8 +30,9 @@ export const validateSingleAction = async ( ): Promise => { const actionParamsErrors = await validateActionParams(actionItem, actionTypeRegistry); const mustacheErrors = validateMustache(actionItem.params); + const queryErrors = validateActionFilterQuery(actionItem); - return [...actionParamsErrors, ...mustacheErrors]; + return [...actionParamsErrors, ...mustacheErrors, ...(queryErrors ? [queryErrors] : [])]; }; export const validateRuleActionsField = diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/value_validators.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/value_validators.ts index e7b799519e1d0..96c01d201b0a0 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/value_validators.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/value_validators.ts @@ -7,7 +7,23 @@ import { set } from '@kbn/safer-lodash-set'; import { constant, get } from 'lodash'; -import { UserConfiguredActionConnector, IErrorObject, Rule } from '../../types'; +import { i18n } from '@kbn/i18n'; +import { UserConfiguredActionConnector, IErrorObject, Rule, RuleAction } from '../../types'; + +const filterQueryRequiredError = i18n.translate( + 'xpack.triggersActionsUI.sections.actionTypeForm.error.requiredFilterQuery', + { + defaultMessage: 'A custom query is required.', + } +); + +export const validateActionFilterQuery = (actionItem: RuleAction): string | null => { + const query = actionItem.alertsFilter?.query; + if (query && !query.kql) { + return filterQueryRequiredError; + } + return null; +}; export function throwIfAbsent(message: string) { return (value: T | undefined): T => { diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.tsx index b7ff043d9727a..2bb623d423429 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.tsx @@ -86,6 +86,7 @@ export interface ActionAccordionFormProps { defaultRuleFrequency?: RuleActionFrequency; ruleTypeId?: string; hasFieldsForAAD?: boolean; + disableErrorMessages?: boolean; } interface ActiveActionConnectorState { @@ -122,6 +123,7 @@ export const ActionForm = ({ ruleTypeId, producerId, hasFieldsForAAD, + disableErrorMessages, }: ActionAccordionFormProps) => { const { http, @@ -499,6 +501,7 @@ export const ActionForm = ({ producerId={producerId} ruleTypeId={ruleTypeId} hasFieldsForAAD={hasFieldsForAAD} + disableErrorMessages={disableErrorMessages} /> ); })} diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_type_form.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_type_form.tsx index d7fbb7f20169e..a506a6fc0ef27 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_type_form.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_type_form.tsx @@ -63,6 +63,7 @@ import { ActionNotifyWhen } from './action_notify_when'; import { validateParamsForWarnings } from '../../lib/validate_params_for_warnings'; import { ActionAlertsFilterTimeframe } from './action_alerts_filter_timeframe'; import { ActionAlertsFilterQuery } from './action_alerts_filter_query'; +import { validateActionFilterQuery } from '../../lib/value_validators'; export type ActionTypeFormProps = { actionItem: RuleAction; @@ -92,6 +93,7 @@ export type ActionTypeFormProps = { producerId: string; ruleTypeId?: string; hasFieldsForAAD?: boolean; + disableErrorMessages?: boolean; } & Pick< ActionAccordionFormProps, | 'defaultActionGroupId' @@ -142,6 +144,7 @@ export const ActionTypeForm = ({ featureId, ruleTypeId, hasFieldsForAAD, + disableErrorMessages, }: ActionTypeFormProps) => { const { application: { capabilities }, @@ -247,13 +250,28 @@ export const ActionTypeForm = ({ useEffect(() => { (async () => { + if (disableErrorMessages) { + setActionParamsErrors({ errors: {} }); + return; + } const res: { errors: IErrorObject } = await actionTypeRegistry .get(actionItem.actionTypeId) ?.validateParams(actionItem.params); setActionParamsErrors(res); })(); // eslint-disable-next-line react-hooks/exhaustive-deps - }, [actionItem]); + }, [actionItem, disableErrorMessages]); + + const [queryError, setQueryError] = useState(null); + useEffect(() => { + (async () => { + if (disableErrorMessages) { + setQueryError(null); + return; + } + setQueryError(validateActionFilterQuery(actionItem)); + })(); + }, [actionItem, disableErrorMessages]); const canSave = hasSaveActionsCapability(capabilities); @@ -424,13 +442,15 @@ export const ActionTypeForm = ({ {showActionAlertsFilter && ( <> {!hideNotifyWhen && } - setActionAlertsFilterProperty('query', query, index)} - featureIds={[producerId as ValidFeatureId]} - appName={featureId!} - ruleTypeId={ruleTypeId} - /> + + setActionAlertsFilterProperty('query', query, index)} + featureIds={[producerId as ValidFeatureId]} + appName={featureId!} + ruleTypeId={ruleTypeId} + /> + { }; export { transformRule } from './application/lib/rule_api/common_transformations'; + +export { validateActionFilterQuery } from './application/lib/value_validators'; From 32b06938be2e182d823ea47e20599eff220805c7 Mon Sep 17 00:00:00 2001 From: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Date: Tue, 11 Jul 2023 10:41:32 -0400 Subject: [PATCH 09/61] skip failing test suite (#161514) --- x-pack/test/functional/apps/infra/hosts_view.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/test/functional/apps/infra/hosts_view.ts b/x-pack/test/functional/apps/infra/hosts_view.ts index aa32e82b711e8..0e417d6306c5b 100644 --- a/x-pack/test/functional/apps/infra/hosts_view.ts +++ b/x-pack/test/functional/apps/infra/hosts_view.ts @@ -154,7 +154,8 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { return !!currentUrl.match(path); }); - describe('Hosts View', function () { + // Failing: See https://github.com/elastic/kibana/issues/161514 + describe.skip('Hosts View', function () { before(async () => { await Promise.all([ esArchiver.load('x-pack/test/functional/es_archives/infra/alerts'), From 9ab807796c2e7002edb575c406c6d7854211fc96 Mon Sep 17 00:00:00 2001 From: Jon Date: Tue, 11 Jul 2023 09:58:24 -0500 Subject: [PATCH 10/61] [ci] Add support for label `ci:build-serverless-image` (#161583) Adding the label `ci:build-serverless-image` will build and push the serverless image to our registry. The url will be available as an annotation at the top of Buildkite Closes https://github.com/elastic/kibana/issues/161562 --- .buildkite/scripts/build_kibana.sh | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/.buildkite/scripts/build_kibana.sh b/.buildkite/scripts/build_kibana.sh index 01810d26758c2..252daa79e0ea8 100755 --- a/.buildkite/scripts/build_kibana.sh +++ b/.buildkite/scripts/build_kibana.sh @@ -41,6 +41,32 @@ if is_pr_with_label "ci:build-cloud-image"; then EOF fi +if is_pr_with_label "ci:build-serverless-image"; then + echo "$KIBANA_DOCKER_PASSWORD" | docker login -u "$KIBANA_DOCKER_USERNAME" --password-stdin docker.elastic.co + GIT_ABBREV_COMMIT=${BUILDKITE_COMMIT:0:12} + node scripts/build \ + --skip-initialize \ + --skip-generic-folders \ + --skip-platform-folders \ + --skip-archives \ + --docker-images \ + --docker-namespace="kibana-ci" \ + --docker-tag="pr-$BUILDKITE_PULL_REQUEST-$GIT_ABBREV_COMMIT" \ + --docker-push \ + --skip-docker-ubi \ + --skip-docker-ubuntu \ + --skip-docker-cloud \ + --skip-docker-contexts + docker logout docker.elastic.co + + SERVERLESS_IMAGE=$(docker images --format "{{.Repository}}:{{.Tag}}" docker.elastic.co/kibana-ci/kibana-serverless) + buildkite-agent meta-data set pr_comment:deploy_cloud:head "* Kibana Serverless Image: \`$SERVERLESS_IMAGE\`" + cat << EOF | buildkite-agent annotate --style "info" --context kibana-serverless-image + + Kibana Serverless Image: \`$SERVERLESS_IMAGE\` +EOF +fi + echo "--- Archive Kibana Distribution" linuxBuild="$(find "$KIBANA_DIR/target" -name 'kibana-*-linux-x86_64.tar.gz')" installDir="$KIBANA_DIR/install/kibana" From a92432869f3c043d0f26719fe19323a10d51c7b0 Mon Sep 17 00:00:00 2001 From: Nathan Reese Date: Tue, 11 Jul 2023 08:58:39 -0600 Subject: [PATCH 11/61] [embeddable] add should_fetch unit test (#161596) While messing around with time slider control and custom time ranges, I noticed shouldFetch$ is triggered (turns out search session id is changing - maybe a separate issue). During investigation, I found it difficult to explore without unit test for shouldFetch$. --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> --- .../should_fetch.test.tsx | 65 +++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 src/plugins/embeddable/public/lib/filterable_embeddable/should_fetch.test.tsx diff --git a/src/plugins/embeddable/public/lib/filterable_embeddable/should_fetch.test.tsx b/src/plugins/embeddable/public/lib/filterable_embeddable/should_fetch.test.tsx new file mode 100644 index 0000000000000..48dcf8f401d85 --- /dev/null +++ b/src/plugins/embeddable/public/lib/filterable_embeddable/should_fetch.test.tsx @@ -0,0 +1,65 @@ +/* + * 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 { BehaviorSubject, Subscription } from 'rxjs'; +import type { FilterableEmbeddableInput } from './types'; +import { shouldFetch$ } from './should_fetch'; + +describe('shouldFetch$', () => { + let shouldFetchCount = 0; + let subscription: Subscription; + let updateInput: (inputFragment: Partial) => void; + beforeAll(() => { + let input: FilterableEmbeddableInput = { + id: '1', + timeRange: { + to: 'now', + from: 'now-15m', + }, + }; + const subject = new BehaviorSubject(input); + updateInput = (inputFragment: Partial) => { + input = { + ...input, + ...inputFragment, + }; + subject.next(input); + }; + + subscription = shouldFetch$(subject, () => { + return input; + }).subscribe(() => { + shouldFetchCount++; + }); + }); + + afterAll(() => { + subscription.unsubscribe(); + }); + + test('should not fire on initial subscription', () => { + expect(shouldFetchCount).toBe(0); + }); + + test('should not fire when there are no changes', () => { + const initialCount = shouldFetchCount; + updateInput({}); + expect(shouldFetchCount).toBe(initialCount); + }); + + test('should fire when timeRange changes', () => { + const initialCount = shouldFetchCount; + updateInput({ + timeRange: { + to: 'now', + from: 'now-25m', + }, + }); + expect(shouldFetchCount).toBe(initialCount + 1); + }); +}); From 787491e2bb0e8c7bcba698effc5e6cacdd9721fe Mon Sep 17 00:00:00 2001 From: Tim Sullivan Date: Tue, 11 Jul 2023 08:16:03 -0700 Subject: [PATCH 12/61] [Reporting] Allow Chromium to work by default in Docker (#149080) ## Summary Closes https://github.com/elastic/kibana/issues/129148 ### Checklist Delete any items that are not applicable to this PR. - [x] [Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html) was added for features that require explanation or tutorials - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios ## Release Note Fixed a bug where Kibana Reporting would not work in Elastic Docker without adding a special setting in kibana.yml. --- ...porting-production-considerations.asciidoc | 9 +++--- .../templates/base/Dockerfile | 1 + .../templates/ironbank/Dockerfile | 1 + .../default_chromium_sandbox_disabled.test.ts | 29 +++++++++++++++++++ .../default_chromium_sandbox_disabled.ts | 17 +++++++---- 5 files changed, 48 insertions(+), 9 deletions(-) diff --git a/docs/user/production-considerations/reporting-production-considerations.asciidoc b/docs/user/production-considerations/reporting-production-considerations.asciidoc index db972e1bbf368..43461d9d9d7ee 100644 --- a/docs/user/production-considerations/reporting-production-considerations.asciidoc +++ b/docs/user/production-considerations/reporting-production-considerations.asciidoc @@ -28,14 +28,15 @@ For an additional layer of security, use the sandbox. The Chromium sandbox uses [[reporting-linux-sandbox]] ==== Linux sandbox The Linux sandbox depends on user namespaces, which were introduced with the 3.8 Linux kernel. However, many -distributions don't have user namespaces enabled by default, or they require the CAP_SYS_ADMIN capability. The {report-features} -automatically disable the sandbox when it is running on Debian because additional steps are required to enable -unprivileged usernamespaces. In these situations, you'll see the following message in your {kib} startup logs: +distributions don't support or allow non-privileged processes to create user namespaces, or they require the CAP_SYS_ADMIN capability. The {report-features} +automatically disable the sandbox when it detects it is running on systems where it is not enabled. +Without explicitly setting `xpack.screenshotting.browser.chromium.disableSandbox: false` in `kibana.yml`, +the {report-features} may detect that it can't be enabled. In the event it is automatically disabled, you'll see the following message in your {kib} startup logs: `Chromium sandbox provides an additional layer of protection, but is not supported for your OS. Automatically setting 'xpack.screenshotting.browser.chromium.disableSandbox: true'.` Reporting automatically enables the Chromium sandbox at startup when a supported OS is detected. However, if your kernel is 3.8 or newer, it's -recommended to set `xpack.screenshotting.browser.chromium.disableSandbox: false` in your `kibana.yml` to explicitly enable usernamespaces. +recommended to set `xpack.screenshotting.browser.chromium.disableSandbox: false` in your `kibana.yml` to explicitly enable the sandbox. [float] [[reporting-docker-sandbox]] diff --git a/src/dev/build/tasks/os_packages/docker_generator/templates/base/Dockerfile b/src/dev/build/tasks/os_packages/docker_generator/templates/base/Dockerfile index beeed1d12dd04..a44fae27c4265 100644 --- a/src/dev/build/tasks/os_packages/docker_generator/templates/base/Dockerfile +++ b/src/dev/build/tasks/os_packages/docker_generator/templates/base/Dockerfile @@ -115,6 +115,7 @@ WORKDIR /usr/share/kibana RUN ln -s /usr/share/kibana /opt/kibana {{! Please notify @elastic/kibana-security if you want to remove or change this environment variable. }} +{{! Kibana applications may depend on the ELASTIC_CONTAINER value. Screenshotting uses this to conditionally disable the Chromium sandbox when launching Puppeteer. }} ENV ELASTIC_CONTAINER true ENV PATH=/usr/share/kibana/bin:$PATH diff --git a/src/dev/build/tasks/os_packages/docker_generator/templates/ironbank/Dockerfile b/src/dev/build/tasks/os_packages/docker_generator/templates/ironbank/Dockerfile index 184d2cfb9414d..a0d3a49a19a89 100644 --- a/src/dev/build/tasks/os_packages/docker_generator/templates/ironbank/Dockerfile +++ b/src/dev/build/tasks/os_packages/docker_generator/templates/ironbank/Dockerfile @@ -52,6 +52,7 @@ WORKDIR /usr/share/kibana RUN ln -s /usr/share/kibana /opt/kibana {{! Please notify @elastic/kibana-security if you want to remove or change this environment variable. }} +{{! Kibana applications may depend on the ELASTIC_CONTAINER value. Screenshotting uses this to conditionally disable the Chromium sandbox when launching Puppeteer. }} ENV ELASTIC_CONTAINER true ENV PATH=/usr/share/kibana/bin:$PATH diff --git a/x-pack/plugins/screenshotting/server/config/default_chromium_sandbox_disabled.test.ts b/x-pack/plugins/screenshotting/server/config/default_chromium_sandbox_disabled.test.ts index 7204230ef5160..0b5f503e86921 100644 --- a/x-pack/plugins/screenshotting/server/config/default_chromium_sandbox_disabled.test.ts +++ b/x-pack/plugins/screenshotting/server/config/default_chromium_sandbox_disabled.test.ts @@ -33,3 +33,32 @@ describe('getDefaultChromiumSandboxDisabled', () => { ); }); }); + +describe('Docker', () => { + const mockOs = { os: 'linux', dist: 'Ubuntu Linux', release: '20.01' }; + + it('Non-Docker', async () => { + (getos as jest.Mock).mockImplementation((cb) => cb(null, mockOs)); + + await expect(getDefaultChromiumSandboxDisabled()).resolves.toHaveProperty( + 'disableSandbox', + false + ); + }); + + it('Elastic Docker container', async () => { + // setup: mock environment variables + const env = { ...process.env }; + process.env.ELASTIC_CONTAINER = 'true'; + + (getos as jest.Mock).mockImplementation((cb) => cb(null, mockOs)); + + await expect(getDefaultChromiumSandboxDisabled()).resolves.toHaveProperty( + 'disableSandbox', + true + ); + + // cleanup: restore the environment variables + process.env = env; + }); +}); diff --git a/x-pack/plugins/screenshotting/server/config/default_chromium_sandbox_disabled.ts b/x-pack/plugins/screenshotting/server/config/default_chromium_sandbox_disabled.ts index 4461b53bf0fcf..34986e804f45a 100644 --- a/x-pack/plugins/screenshotting/server/config/default_chromium_sandbox_disabled.ts +++ b/x-pack/plugins/screenshotting/server/config/default_chromium_sandbox_disabled.ts @@ -11,7 +11,7 @@ import { promisify } from 'util'; const getOs = promisify(getOsSync); const distroSupportsUnprivilegedUsernamespaces = (distro: string) => { - // Debian 7 and 8 don't support usernamespaces by default + // Debian 7 and 8 don't support user namespaces by default // this should be reevaluated when Debian 9 is available if (distro.toLowerCase() === 'debian') { return false; @@ -40,8 +40,15 @@ interface OsSummary { export async function getDefaultChromiumSandboxDisabled(): Promise { const os = await getOs(); - return { - os, - disableSandbox: os.os === 'linux' && !distroSupportsUnprivilegedUsernamespaces(os.dist), - }; + let enableSandbox: boolean; + if (process.env.ELASTIC_CONTAINER) { + // In the Elastic Docker image, user namespaces is not supported. This is relatively safe since Docker + // provides a level of isolation. In addition, Chromium is only used to open Kibana URLs within the + // deployment, which makes it relatively locked down from being able to exploit Chromium. + enableSandbox = false; + } else { + enableSandbox = os.os !== 'linux' || distroSupportsUnprivilegedUsernamespaces(os.dist); + } + + return { os, disableSandbox: !enableSandbox }; } From 70ed2004343561fcc162d287d231b01d8803eab0 Mon Sep 17 00:00:00 2001 From: Shahzad Date: Tue, 11 Jul 2023 17:55:49 +0200 Subject: [PATCH 13/61] [Synthetics] De-dupe overview status request on load (#161627) --- .../journeys/overview_scrolling.journey.ts | 22 +++++++++++++++++++ .../hooks/use_overview_status.ts | 12 +++++----- 2 files changed, 27 insertions(+), 7 deletions(-) diff --git a/x-pack/plugins/synthetics/e2e/synthetics/journeys/overview_scrolling.journey.ts b/x-pack/plugins/synthetics/e2e/synthetics/journeys/overview_scrolling.journey.ts index 2936c33d8c44c..abc9204eb64d1 100644 --- a/x-pack/plugins/synthetics/e2e/synthetics/journeys/overview_scrolling.journey.ts +++ b/x-pack/plugins/synthetics/e2e/synthetics/journeys/overview_scrolling.journey.ts @@ -21,7 +21,15 @@ journey('OverviewScrolling', async ({ page, params }) => { const syntheticsApp = syntheticsAppPageProvider({ page, kibanaUrl: params.kibanaUrl }); const retry: RetryService = params.getService('retry'); + const listOfRequests: string[] = []; + before(async () => { + page.on('request', (request) => { + const url = request.url(); + if (url.includes('/synthetics/') || url.includes('/uptime/')) { + listOfRequests.push(request.url()); + } + }); await enableMonitorManagedViaApi(params.kibanaUrl); await cleanTestMonitors(params); @@ -48,6 +56,20 @@ journey('OverviewScrolling', async ({ page, params }) => { expect(await invalid.isVisible()).toBeFalsy(); }); + step('validates de-duplicate requests', async () => { + await page.waitForSelector(`text="test monitor 0"`); + + const assertUnique = (value: string) => { + expect(listOfRequests.filter((req) => req.includes(value)).length).toBe(1); + }; + assertUnique('/overview_status'); + assertUnique('/overview?'); + assertUnique('/service/monitors'); + assertUnique('/monitor/filters'); + + expect(listOfRequests.length).toBe(16); + }); + step('scroll until you see showing all monitors', async () => { const gridItems = await page.locator(`[data-test-subj="syntheticsOverviewGridItem"]`); await page.waitForSelector(`text="test monitor 0"`); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/hooks/use_overview_status.ts b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/hooks/use_overview_status.ts index ed99ec8161765..b06b66f8a5a73 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/hooks/use_overview_status.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/hooks/use_overview_status.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { useEffect, useCallback } from 'react'; +import { useEffect } from 'react'; import { useDispatch, useSelector } from 'react-redux'; import { useSyntheticsRefreshContext } from '../../../contexts/synthetics_refresh_context'; import { selectOverviewPageState } from '../../../state'; @@ -23,21 +23,19 @@ export function useOverviewStatus({ scopeStatusByLocation }: { scopeStatusByLoca const { lastRefresh } = useSyntheticsRefreshContext(); const dispatch = useDispatch(); - const reload = useCallback(() => { - dispatch(fetchOverviewStatusAction.get({ pageState, scopeStatusByLocation })); - }, [dispatch, pageState, scopeStatusByLocation]); useEffect(() => { if (loaded) { dispatch(quietFetchOverviewStatusAction.get({ pageState, scopeStatusByLocation })); } else { - reload(); + dispatch(fetchOverviewStatusAction.get({ pageState, scopeStatusByLocation })); } - }, [dispatch, reload, lastRefresh, pageState, loaded, scopeStatusByLocation]); + // loaded is omitted from the dependency array because it is not used in the callback + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [dispatch, lastRefresh, pageState, scopeStatusByLocation]); return { status, error, - reload, }; } From 08a57b9e3a570329c4f8051dcf195fe80e691eb3 Mon Sep 17 00:00:00 2001 From: christineweng <18648970+christineweng@users.noreply.github.com> Date: Tue, 11 Jul 2023 11:13:02 -0500 Subject: [PATCH 14/61] [Security Solution] Add validations for insight form in timeline and rules (#161034) ## Summary Currently user can add a note in timeline with invalid markdown syntax in insight forms. Same goes to the investigation guide in rule creation -> About. ### Before **In timeline -> Notes** ![image](https://github.com/elastic/kibana/assets/18648970/33b05592-f97a-41d8-a394-fc111f4cc039) **On rules -> about** ![image](https://github.com/elastic/kibana/assets/18648970/ca09ac1a-7798-4b13-820b-85e5cf0f61c7) ### After **Timeline -> Notes** Add note button should be disabled if markdown is invalid ![image](https://github.com/elastic/kibana/assets/18648970/d34b0ab1-c39b-443a-a309-00aba251731e) **On rules -> about** Field is red if markdown is invalid ![image](https://github.com/elastic/kibana/assets/18648970/850f9f4a-71a7-44f8-a9e1-bbfbdb07b0dd) ### Checklist - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios --------- Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../components/markdown_editor/editor.tsx | 25 ++++++++++++++++--- .../components/markdown_editor/eui_form.tsx | 6 +++-- .../__snapshots__/new_note.test.tsx.snap | 1 + .../components/notes/add_note/index.test.tsx | 1 + .../components/notes/add_note/index.tsx | 8 +++--- .../notes/add_note/new_note.test.tsx | 16 ++++++++++-- .../components/notes/add_note/new_note.tsx | 4 ++- 7 files changed, 49 insertions(+), 12 deletions(-) diff --git a/x-pack/plugins/security_solution/public/common/components/markdown_editor/editor.tsx b/x-pack/plugins/security_solution/public/common/components/markdown_editor/editor.tsx index 79802dae16049..0137e6dedc536 100644 --- a/x-pack/plugins/security_solution/public/common/components/markdown_editor/editor.tsx +++ b/x-pack/plugins/security_solution/public/common/components/markdown_editor/editor.tsx @@ -30,6 +30,7 @@ interface MarkdownEditorProps { dataTestSubj?: string; height?: number; autoFocusDisabled?: boolean; + setIsMarkdownInvalid: (value: boolean) => void; } type EuiMarkdownEditorRef = ElementRef; @@ -41,11 +42,27 @@ export interface MarkdownEditorRef { } const MarkdownEditorComponent = forwardRef( - ({ onChange, value, ariaLabel, editorId, dataTestSubj, height, autoFocusDisabled }, ref) => { + ( + { + onChange, + value, + ariaLabel, + editorId, + dataTestSubj, + height, + autoFocusDisabled, + setIsMarkdownInvalid, + }, + ref + ) => { const [markdownErrorMessages, setMarkdownErrorMessages] = useState([]); - const onParse = useCallback((err, { messages }) => { - setMarkdownErrorMessages(err ? [err] : messages); - }, []); + const onParse = useCallback( + (err, { messages }) => { + setMarkdownErrorMessages(err ? [err] : messages); + setIsMarkdownInvalid(err ? true : false); + }, + [setIsMarkdownInvalid] + ); const editorRef = useRef(null); useEffect(() => { diff --git a/x-pack/plugins/security_solution/public/common/components/markdown_editor/eui_form.tsx b/x-pack/plugins/security_solution/public/common/components/markdown_editor/eui_form.tsx index 9554b5bba4e5b..c44ff696f2020 100644 --- a/x-pack/plugins/security_solution/public/common/components/markdown_editor/eui_form.tsx +++ b/x-pack/plugins/security_solution/public/common/components/markdown_editor/eui_form.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { forwardRef } from 'react'; +import React, { forwardRef, useState } from 'react'; import styled from 'styled-components'; import type { EuiMarkdownEditorProps } from '@elastic/eui'; import { EuiFormRow, EuiFlexItem, EuiFlexGroup } from '@elastic/eui'; @@ -34,6 +34,7 @@ export const MarkdownEditorForm = React.memo( forwardRef( ({ id, field, dataTestSubj, idAria, bottomRightContent }, ref) => { const { isInvalid, errorMessage } = getFieldValidityAndErrorMessage(field); + const [isMarkdownInvalid, setIsMarkdownInvalid] = useState(false); return ( @@ -54,6 +55,7 @@ export const MarkdownEditorForm = React.memo( onChange={field.setValue} value={field.value as string} data-test-subj={`${dataTestSubj}-markdown-editor`} + setIsMarkdownInvalid={setIsMarkdownInvalid} /> {bottomRightContent && ( diff --git a/x-pack/plugins/security_solution/public/timelines/components/notes/add_note/__snapshots__/new_note.test.tsx.snap b/x-pack/plugins/security_solution/public/timelines/components/notes/add_note/__snapshots__/new_note.test.tsx.snap index 32e17a19045b1..3e3f4bd1e99cf 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/notes/add_note/__snapshots__/new_note.test.tsx.snap +++ b/x-pack/plugins/security_solution/public/timelines/components/notes/add_note/__snapshots__/new_note.test.tsx.snap @@ -10,6 +10,7 @@ exports[`NewNote renders correctly 1`] = ` dataTestSubj="add-a-note" height={200} onChange={[MockFunction]} + setIsMarkdownInvalid={[MockFunction]} value="The contents of a new note" /> diff --git a/x-pack/plugins/security_solution/public/timelines/components/notes/add_note/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/notes/add_note/index.test.tsx index e883ea10b0a6c..fe1f23c29028b 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/notes/add_note/index.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/notes/add_note/index.test.tsx @@ -41,6 +41,7 @@ describe('AddNote', () => { newNote: note, onCancelAddNote: jest.fn(), updateNewNote: jest.fn(), + setIsMarkdownInvalid: jest.fn(), }; test('renders correctly', () => { diff --git a/x-pack/plugins/security_solution/public/timelines/components/notes/add_note/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/notes/add_note/index.tsx index 9e216b24f7843..31df17fa40046 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/notes/add_note/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/notes/add_note/index.tsx @@ -12,7 +12,7 @@ import { EuiFlexItem, EuiScreenReaderOnly, } from '@elastic/eui'; -import React, { useCallback, useMemo } from 'react'; +import React, { useCallback, useMemo, useState } from 'react'; import styled from 'styled-components'; import { useDispatch } from 'react-redux'; @@ -56,6 +56,7 @@ export const AddNote = React.memo<{ }>(({ associateNote, newNote, onCancelAddNote, updateNewNote, autoFocusDisabled = false }) => { const dispatch = useDispatch(); const authenticatedUser = useCurrentUser(); + const [isMarkdownInvalid, setIsMarkdownInvalid] = useState(false); const updateNote = useCallback( (note: Note) => dispatch(appActions.updateNote({ note })), [dispatch] @@ -88,8 +89,8 @@ export const AddNote = React.memo<{ ); const isAddNoteDisabled = useMemo(() => { - return newNote.trim().length === 0; - }, [newNote]); + return newNote.trim().length === 0 || isMarkdownInvalid; + }, [newNote, isMarkdownInvalid]); return ( @@ -102,6 +103,7 @@ export const AddNote = React.memo<{ noteInputHeight={200} updateNewNote={updateNewNote} autoFocusDisabled={autoFocusDisabled} + setIsMarkdownInvalid={setIsMarkdownInvalid} /> {onCancelAddNote != null ? ( diff --git a/x-pack/plugins/security_solution/public/timelines/components/notes/add_note/new_note.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/notes/add_note/new_note.test.tsx index 4dd90b877e962..0a4659b8d18b9 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/notes/add_note/new_note.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/notes/add_note/new_note.test.tsx @@ -15,13 +15,25 @@ describe('NewNote', () => { test('renders correctly', () => { const wrapper = shallow( - + ); expect(wrapper).toMatchSnapshot(); }); test('it renders a text area containing the contents of a new (raw) note', () => { - const wrapper = mount(); + const wrapper = mount( + + ); expect( wrapper.find('[data-test-subj="add-a-note"] .euiMarkdownEditorDropZone').first().text() diff --git a/x-pack/plugins/security_solution/public/timelines/components/notes/add_note/new_note.tsx b/x-pack/plugins/security_solution/public/timelines/components/notes/add_note/new_note.tsx index 4d02a1e8c74c1..a6a3c5c173ef6 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/notes/add_note/new_note.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/notes/add_note/new_note.tsx @@ -25,7 +25,8 @@ export const NewNote = React.memo<{ note: string; updateNewNote: UpdateInternalNewNote; autoFocusDisabled?: boolean; -}>(({ note, noteInputHeight, updateNewNote, autoFocusDisabled = false }) => { + setIsMarkdownInvalid: (value: boolean) => void; +}>(({ note, noteInputHeight, updateNewNote, autoFocusDisabled = false, setIsMarkdownInvalid }) => { return ( ); From 586e8db0d2e9cac3c774de277c07c93cace060eb Mon Sep 17 00:00:00 2001 From: Kevin Delemme Date: Tue, 11 Jul 2023 12:26:00 -0400 Subject: [PATCH 15/61] chore(slo): update open api spec (#161670) --- .../docs/openapi/slo/bundled.yaml | 990 +++++------------- .../schemas/base_composite_slo_response.yaml | 10 +- .../schemas/composite_slo_response.yaml | 12 +- .../schemas/create_composite_slo_request.yaml | 11 +- .../schemas/create_slo_request.yaml | 18 +- .../indicator_properties_custom_metric.yaml | 8 +- .../slo/components/schemas/slo_response.yaml | 20 +- .../slo/components/schemas/time_window.yaml | 16 + .../schemas/time_window_calendar_aligned.yaml | 15 - .../schemas/time_window_rolling.yaml | 15 - .../schemas/update_composite_slo_request.yaml | 11 +- .../schemas/update_slo_request.yaml | 18 +- .../docs/openapi/slo/entrypoint.yaml | 32 +- 13 files changed, 354 insertions(+), 822 deletions(-) create mode 100644 x-pack/plugins/observability/docs/openapi/slo/components/schemas/time_window.yaml delete mode 100644 x-pack/plugins/observability/docs/openapi/slo/components/schemas/time_window_calendar_aligned.yaml delete mode 100644 x-pack/plugins/observability/docs/openapi/slo/components/schemas/time_window_rolling.yaml diff --git a/x-pack/plugins/observability/docs/openapi/slo/bundled.yaml b/x-pack/plugins/observability/docs/openapi/slo/bundled.yaml index f4ef3a5991387..533d06a67e54b 100644 --- a/x-pack/plugins/observability/docs/openapi/slo/bundled.yaml +++ b/x-pack/plugins/observability/docs/openapi/slo/bundled.yaml @@ -8,269 +8,15 @@ info: license: name: Elastic License 2.0 url: https://www.elastic.co/licensing/elastic-license -servers: - - url: http://localhost:5601 - description: local -security: - - basicAuth: [] - - apiKeyAuth: [] tags: - name: slo description: SLO APIs enable you to define, manage and track service-level objectives - name: composite slo description: Composite SLO APIs enable you to define, manage and track a group of SLOs. +servers: + - url: http://localhost:5601 + description: local paths: - /s/{spaceId}/api/observability/composite_slos: - post: - summary: Creates a Composite SLO - operationId: createCompositeSlo - description: | - You must have `all` privileges for the **SLOs** feature in the **Observability** section of the Kibana feature privileges. - tags: - - composite slo - parameters: - - $ref: '#/components/parameters/kbn_xsrf' - - $ref: '#/components/parameters/space_id' - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/create_composite_slo_request' - responses: - '200': - description: Successful request - content: - application/json: - schema: - $ref: '#/components/schemas/create_composite_slo_response' - '400': - description: Bad request - content: - application/json: - schema: - $ref: '#/components/schemas/400_response' - '401': - description: Unauthorized response - content: - application/json: - schema: - $ref: '#/components/schemas/401_response' - '403': - description: Unauthorized response - content: - application/json: - schema: - $ref: '#/components/schemas/403_response' - '409': - description: Conflict - The Composite SLO id already exists - content: - application/json: - schema: - $ref: '#/components/schemas/409_response' - get: - summary: Retrieves a paginated list of composite SLOs with summary - operationId: findCompositeSlo - description: | - You must have the `read` privileges for the **SLOs** feature in the **Observability** section of the Kibana feature privileges. - tags: - - composite slo - parameters: - - $ref: '#/components/parameters/kbn_xsrf' - - $ref: '#/components/parameters/space_id' - - name: page - in: query - description: The page number to return - schema: - type: integer - default: 1 - example: 1 - - name: perPage - in: query - description: The number of SLOs to return per page - schema: - type: integer - default: 25 - example: 20 - - name: sortBy - in: query - description: Sort by field - schema: - type: string - enum: - - creationTime - default: creationTime - example: creationTime - - name: sortDirection - in: query - description: Sort order - schema: - type: string - enum: - - asc - - desc - default: asc - example: asc - responses: - '200': - description: Successful request - content: - application/json: - schema: - $ref: '#/components/schemas/find_composite_slo_response' - '400': - description: Bad request - content: - application/json: - schema: - $ref: '#/components/schemas/400_response' - '401': - description: Unauthorized response - content: - application/json: - schema: - $ref: '#/components/schemas/401_response' - '403': - description: Unauthorized response - content: - application/json: - schema: - $ref: '#/components/schemas/403_response' - '404': - description: Not found response - content: - application/json: - schema: - $ref: '#/components/schemas/404_response' - /s/{spaceId}/api/observability/composite_slos/{compositeSloId}: - get: - summary: Retrieves a composite SLO - operationId: getCompositeSlo - description: | - You must have the `read` privileges for the **SLOs** feature in the **Observability** section of the Kibana feature privileges. - tags: - - composite slo - parameters: - - $ref: '#/components/parameters/kbn_xsrf' - - $ref: '#/components/parameters/space_id' - - $ref: '#/components/parameters/composite_slo_id' - responses: - '200': - description: Successful request - content: - application/json: - schema: - $ref: '#/components/schemas/composite_slo_response' - '400': - description: Bad request - content: - application/json: - schema: - $ref: '#/components/schemas/400_response' - '401': - description: Unauthorized response - content: - application/json: - schema: - $ref: '#/components/schemas/401_response' - '403': - description: Unauthorized response - content: - application/json: - schema: - $ref: '#/components/schemas/403_response' - '404': - description: Not found response - content: - application/json: - schema: - $ref: '#/components/schemas/404_response' - put: - summary: Updates a composite SLO - operationId: updateCompositeSlo - description: | - You must have the `write` privileges for the **SLOs** feature in the **Observability** section of the Kibana feature privileges. - tags: - - composite slo - parameters: - - $ref: '#/components/parameters/kbn_xsrf' - - $ref: '#/components/parameters/space_id' - - $ref: '#/components/parameters/composite_slo_id' - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/update_composite_slo_request' - responses: - '200': - description: Successful request - content: - application/json: - schema: - $ref: '#/components/schemas/base_composite_slo_response' - '400': - description: Bad request - content: - application/json: - schema: - $ref: '#/components/schemas/400_response' - '401': - description: Unauthorized response - content: - application/json: - schema: - $ref: '#/components/schemas/401_response' - '403': - description: Unauthorized response - content: - application/json: - schema: - $ref: '#/components/schemas/403_response' - '404': - description: Not found response - content: - application/json: - schema: - $ref: '#/components/schemas/404_response' - delete: - summary: Deletes a composite SLO - operationId: deleteCompositeSlo - description: | - You must have the `write` privileges for the **SLOs** feature in the **Observability** section of the Kibana feature privileges. - tags: - - composite slo - parameters: - - $ref: '#/components/parameters/kbn_xsrf' - - $ref: '#/components/parameters/space_id' - - $ref: '#/components/parameters/composite_slo_id' - responses: - '204': - description: Successful request - '400': - description: Bad request - content: - application/json: - schema: - $ref: '#/components/schemas/400_response' - '401': - description: Unauthorized response - content: - application/json: - schema: - $ref: '#/components/schemas/401_response' - '403': - description: Unauthorized response - content: - application/json: - schema: - $ref: '#/components/schemas/403_response' - '404': - description: Not found response - content: - application/json: - schema: - $ref: '#/components/schemas/404_response' /s/{spaceId}/api/observability/slos: post: summary: Creates an SLO. @@ -602,448 +348,98 @@ paths: $ref: '#/components/schemas/400_response' '401': description: Unauthorized response - content: - application/json: - schema: - $ref: '#/components/schemas/401_response' - '403': - description: Unauthorized response - content: - application/json: - schema: - $ref: '#/components/schemas/403_response' - '404': - description: Not found response - content: - application/json: - schema: - $ref: '#/components/schemas/404_response' - /s/{spaceId}/internal/observability/slos/_historical_summary: - post: - summary: Retrieves the historical summary for a list of SLOs - operationId: historicalSummary - description: | - You must have the `read` privileges for the **SLOs** feature in the **Observability** section of the Kibana feature privileges. - tags: - - slo - parameters: - - $ref: '#/components/parameters/kbn_xsrf' - - $ref: '#/components/parameters/space_id' - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/historical_summary_request' - responses: - '200': - description: Successful request - content: - application/json: - schema: - $ref: '#/components/schemas/historical_summary_response' - '400': - description: Bad request - content: - application/json: - schema: - $ref: '#/components/schemas/400_response' - '401': - description: Unauthorized response - content: - application/json: - schema: - $ref: '#/components/schemas/401_response' - '403': - description: Unauthorized response - content: - application/json: - schema: - $ref: '#/components/schemas/403_response' -components: - securitySchemes: - basicAuth: - type: http - scheme: basic - apiKeyAuth: - type: apiKey - in: header - name: ApiKey - parameters: - kbn_xsrf: - schema: - type: string - in: header - name: kbn-xsrf - description: Cross-site request forgery protection - required: true - space_id: - in: path - name: spaceId - description: An identifier for the space. If `/s/` and the identifier are omitted from the path, the default space is used. - required: true - schema: - type: string - example: default - composite_slo_id: - in: path - name: compositeSloId - description: An identifier for the composite slo. - required: true - schema: - type: string - example: 9c235211-6834-11ea-a78c-6feb38a34414 - slo_id: - in: path - name: sloId - description: An identifier for the slo. - required: true - schema: - type: string - example: 9c235211-6834-11ea-a78c-6feb38a34414 - schemas: - time_window_rolling: - title: Rolling - required: - - duration - - isRolling - description: Defines properties for rolling time window - type: object - properties: - duration: - description: the duration formatted as {duration}{unit} - type: string - example: 28d - isRolling: - description: Indicates a rolling time window - type: boolean - example: true - budgeting_method: - title: Budgeting method - type: string - description: The budgeting method to use when computing the rollup data. - enum: - - occurrences - - timeslices - example: occurrences - composite_method: - title: Composite method - type: string - description: The composite method to use for the composite SLO. - enum: - - weightedAverage - example: weightedAverage - objective: - title: Objective - required: - - target - description: Defines properties for the SLO objective - type: object - properties: - target: - description: the target objective between 0 and 1 excluded - type: number - example: 0.99 - timeslicesTarget: - description: the target objective for each slice when using a timeslices budgeting method - type: number - example: 0.995 - timeslicesWindow: - description: the duration of each slice when using a timeslices budgeting method, as {duraton}{unit} - type: string - example: 5m - weighted_composite_sources: - title: Weighted sources - description: An array of source SLO to use for the weighted average composite. - type: array - items: - type: object - required: - - id - - revision - - weight - properties: - id: - description: The id of the SLO. - type: string - example: 8853df00-ae2e-11ed-90af-09bb6422b258 - revision: - description: The revision number of the SLO. - type: number - example: 2 - weight: - description: The weight to apply to this SLO. - type: number - example: 3 - error_budget: - title: Error budget - type: object - properties: - initial: - type: number - description: The initial error budget, as 1 - objective - example: 0.02 - consumed: - type: number - description: The error budget consummed, as a percentage of the initial value. - example: 0.8 - remaining: - type: number - description: The error budget remaining, as a percentage of the initial value. - example: 0.2 - isEstimated: - type: boolean - description: Only for SLO defined with occurrences budgeting method and calendar aligned time window. - example: true - summary: - title: Summary - type: object - description: The SLO computed data - properties: - status: - type: string - enum: - - NO_DATA - - HEALTHY - - DEGRADING - - VIOLATED - example: HEALTHY - sliValue: - type: number - example: 0.9836 - errorBudget: - $ref: '#/components/schemas/error_budget' - composite_slo_response: - title: Composite SLO with summary response - type: object - properties: - id: - description: The identifier of the composite SLO. - type: string - example: 8853df00-ae2e-11ed-90af-09bb6422b258 - name: - description: The name of the composite SLO. - type: string - example: My Service SLO - timeWindow: - $ref: '#/components/schemas/time_window_rolling' - budgetingMethod: - $ref: '#/components/schemas/budgeting_method' - compositeMethod: - $ref: '#/components/schemas/composite_method' - objective: - $ref: '#/components/schemas/objective' - sources: - oneOf: - - $ref: '#/components/schemas/weighted_composite_sources' - summary: - $ref: '#/components/schemas/summary' - createdAt: - description: The creation date - type: string - example: '2023-01-12T10:03:19.000Z' - updatedAt: - description: The last update date - type: string - example: '2023-01-12T10:03:19.000Z' - find_composite_slo_response: - title: Find composite SLO response - description: A paginated response of composite SLOs matching the query. - type: object - properties: - page: - type: number - example: 1 - perPage: - type: number - example: 25 - total: - type: number - example: 34 - results: - type: array - items: - $ref: '#/components/schemas/composite_slo_response' - 400_response: - title: Bad request - type: object - required: - - statusCode - - error - - message - properties: - statusCode: - type: number - example: 400 - error: - type: string - example: Bad Request - message: - type: string - example: 'Invalid value ''foo'' supplied to: [...]' - 401_response: - title: Unauthorized - type: object - required: - - statusCode - - error - - message - properties: - statusCode: - type: number - example: 401 - error: - type: string - example: Unauthorized - message: - type: string - example: "[security_exception\n\tRoot causes:\n\t\tsecurity_exception: unable to authenticate user [elastics] for REST request [/_security/_authenticate]]: unable to authenticate user [elastics] for REST request [/_security/_authenticate]" - 403_response: - title: Unauthorized - type: object - required: - - statusCode - - error - - message - properties: - statusCode: - type: number - example: 403 - error: - type: string - example: Unauthorized - message: - type: string - example: "[security_exception\n\tRoot causes:\n\t\tsecurity_exception: unable to authenticate user [elastics] for REST request [/_security/_authenticate]]: unable to authenticate user [elastics] for REST request [/_security/_authenticate]" - 404_response: - title: Not found - type: object - required: - - statusCode - - error - - message - properties: - statusCode: - type: number - example: 404 - error: - type: string - example: Not Found - message: - type: string - example: SLO [3749f390-03a3-11ee-8139-c7ff60a1692d] not found - create_composite_slo_request: - title: Create composite SLO request - description: | - The create Composite SLO API request body. The provided source SLOs must exists and their budgeting method and time window must match the one from the composite SLO. - type: object - required: - - name - - timeWindow - - budgetingMethod - - compositeMethod - - objective - - sources - properties: - id: - description: A unique identifier for the composite SLO. Must be between 8 and 36 chars - type: string - example: my-super-composite-slo-id - name: - description: A name for the composite SLO. - type: string - timeWindow: - $ref: '#/components/schemas/time_window_rolling' - budgetingMethod: - $ref: '#/components/schemas/budgeting_method' - compositeMethod: - $ref: '#/components/schemas/composite_method' - objective: - $ref: '#/components/schemas/objective' - sources: - oneOf: - - $ref: '#/components/schemas/weighted_composite_sources' - create_composite_slo_response: - title: Create composite SLO response - type: object - required: - - id - properties: - id: - type: string - example: 8853df00-ae2e-11ed-90af-09bb6422b258 - 409_response: - title: Conflict - type: object - required: - - statusCode - - error - - message - properties: - statusCode: - type: number - example: 409 - error: - type: string - example: Conflict - message: - type: string - example: SLO [d077e940-1515-11ee-9c50-9d096392f520] already exists - update_composite_slo_request: - title: Update composite SLO request - description: | - The update composite SLO API request body. The provided source SLOs must exists and their budgeting method and time window must match the one from the composite SLO. - type: object - properties: - id: - description: A unique identifier for the composite SLO. Must be between 8 and 36 chars - type: string - example: my-super-composite-slo-id - name: - description: A name for the composite SLO. - type: string - timeWindow: - $ref: '#/components/schemas/time_window_rolling' - budgetingMethod: - $ref: '#/components/schemas/budgeting_method' - compositeMethod: - $ref: '#/components/schemas/composite_method' - objective: - $ref: '#/components/schemas/objective' - sources: - oneOf: - - $ref: '#/components/schemas/weighted_composite_sources' - base_composite_slo_response: - title: Composite SLO response - type: object - properties: - id: - description: The identifier of the composite SLO. - type: string - example: 8853df00-ae2e-11ed-90af-09bb6422b258 - name: - description: The name of the composite SLO. - type: string - example: My Service SLO - timeWindow: - $ref: '#/components/schemas/time_window_rolling' - budgetingMethod: - $ref: '#/components/schemas/budgeting_method' - compositeMethod: - $ref: '#/components/schemas/composite_method' - objective: - $ref: '#/components/schemas/objective' - sources: - oneOf: - - $ref: '#/components/schemas/weighted_composite_sources' - createdAt: - description: The creation date - type: string - example: '2023-01-12T10:03:19.000Z' - updatedAt: - description: The last update date - type: string - example: '2023-01-12T10:03:19.000Z' + content: + application/json: + schema: + $ref: '#/components/schemas/401_response' + '403': + description: Unauthorized response + content: + application/json: + schema: + $ref: '#/components/schemas/403_response' + '404': + description: Not found response + content: + application/json: + schema: + $ref: '#/components/schemas/404_response' + /s/{spaceId}/internal/observability/slos/_historical_summary: + post: + summary: Retrieves the historical summary for a list of SLOs + operationId: historicalSummary + description: | + You must have the `read` privileges for the **SLOs** feature in the **Observability** section of the Kibana feature privileges. + tags: + - slo + parameters: + - $ref: '#/components/parameters/kbn_xsrf' + - $ref: '#/components/parameters/space_id' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/historical_summary_request' + responses: + '200': + description: Successful request + content: + application/json: + schema: + $ref: '#/components/schemas/historical_summary_response' + '400': + description: Bad request + content: + application/json: + schema: + $ref: '#/components/schemas/400_response' + '401': + description: Unauthorized response + content: + application/json: + schema: + $ref: '#/components/schemas/401_response' + '403': + description: Unauthorized response + content: + application/json: + schema: + $ref: '#/components/schemas/403_response' +components: + securitySchemes: + basicAuth: + type: http + scheme: basic + apiKeyAuth: + type: apiKey + in: header + name: ApiKey + parameters: + kbn_xsrf: + schema: + type: string + in: header + name: kbn-xsrf + description: Cross-site request forgery protection + required: true + space_id: + in: path + name: spaceId + description: An identifier for the space. If `/s/` and the identifier are omitted from the path, the default space is used. + required: true + schema: + type: string + example: default + slo_id: + in: path + name: sloId + description: An identifier for the slo. + required: true + schema: + type: string + example: 9c235211-6834-11ea-a78c-6feb38a34414 + schemas: indicator_properties_custom_kql: title: Custom KQL required: @@ -1248,6 +644,10 @@ components: description: The field of the metric. type: string example: processor.processed + filter: + description: The filter to apply to the metric. + type: string + example: 'processor.outcome: "success"' equation: description: The equation to calculate the "good" metric. type: string @@ -1285,6 +685,10 @@ components: description: The field of the metric. type: string example: processor.processed + filter: + description: The filter to apply to the metric. + type: string + example: 'processor.outcome: *' equation: description: The equation to calculate the "total" metric. type: string @@ -1293,22 +697,52 @@ components: description: The type of indicator. type: string example: sli.metric.custom - time_window_calendar_aligned: - title: Calendar aligned + time_window: + title: Time window required: - duration - - isCalendar - description: Defines properties for calendar aligned time window + - type + description: Defines properties for the SLO time window type: object properties: duration: - description: the duration formatted as {duration}{unit}, accept '1w' (weekly calendar) or '1M' (monthly calendar) only + description: 'the duration formatted as {duration}{unit}. Accepted values for rolling: 7d, 30d, 90d. Accepted values for calendar aligned: 1w (weekly) or 1M (monthly)' type: string - example: 1M - isCalendar: - description: Indicates a calendar aligned time window - type: boolean - example: true + example: 30d + type: + description: Indicates weither the time window is a rolling or a calendar aligned time window. + type: string + example: rolling + enum: + - rolling + - calendarAligned + budgeting_method: + title: Budgeting method + type: string + description: The budgeting method to use when computing the rollup data. + enum: + - occurrences + - timeslices + example: occurrences + objective: + title: Objective + required: + - target + description: Defines properties for the SLO objective + type: object + properties: + target: + description: the target objective between 0 and 1 excluded + type: number + example: 0.99 + timeslicesTarget: + description: the target objective for each slice when using a timeslices budgeting method + type: number + example: 0.995 + timeslicesWindow: + description: the duration of each slice when using a timeslices budgeting method, as {duraton}{unit} + type: string + example: 5m settings: title: Settings description: Defines properties for SLO settings. @@ -1322,6 +756,44 @@ components: description: Configure how often the transform runs, default 1m type: string example: 5m + error_budget: + title: Error budget + type: object + properties: + initial: + type: number + description: The initial error budget, as 1 - objective + example: 0.02 + consumed: + type: number + description: The error budget consummed, as a percentage of the initial value. + example: 0.8 + remaining: + type: number + description: The error budget remaining, as a percentage of the initial value. + example: 0.2 + isEstimated: + type: boolean + description: Only for SLO defined with occurrences budgeting method and calendar aligned time window. + example: true + summary: + title: Summary + type: object + description: The SLO computed data + properties: + status: + type: string + enum: + - NO_DATA + - HEALTHY + - DEGRADING + - VIOLATED + example: HEALTHY + sliValue: + type: number + example: 0.9836 + errorBudget: + $ref: '#/components/schemas/error_budget' slo_response: title: SLO response type: object @@ -1345,9 +817,7 @@ components: - $ref: '#/components/schemas/indicator_properties_apm_latency' - $ref: '#/components/schemas/indicator_properties_custom_metric' timeWindow: - oneOf: - - $ref: '#/components/schemas/time_window_rolling' - - $ref: '#/components/schemas/time_window_calendar_aligned' + $ref: '#/components/schemas/time_window' budgetingMethod: $ref: '#/components/schemas/budgeting_method' objective: @@ -1391,6 +861,74 @@ components: type: array items: $ref: '#/components/schemas/slo_response' + 400_response: + title: Bad request + type: object + required: + - statusCode + - error + - message + properties: + statusCode: + type: number + example: 400 + error: + type: string + example: Bad Request + message: + type: string + example: 'Invalid value ''foo'' supplied to: [...]' + 401_response: + title: Unauthorized + type: object + required: + - statusCode + - error + - message + properties: + statusCode: + type: number + example: 401 + error: + type: string + example: Unauthorized + message: + type: string + example: "[security_exception\n\tRoot causes:\n\t\tsecurity_exception: unable to authenticate user [elastics] for REST request [/_security/_authenticate]]: unable to authenticate user [elastics] for REST request [/_security/_authenticate]" + 403_response: + title: Unauthorized + type: object + required: + - statusCode + - error + - message + properties: + statusCode: + type: number + example: 403 + error: + type: string + example: Unauthorized + message: + type: string + example: "[security_exception\n\tRoot causes:\n\t\tsecurity_exception: unable to authenticate user [elastics] for REST request [/_security/_authenticate]]: unable to authenticate user [elastics] for REST request [/_security/_authenticate]" + 404_response: + title: Not found + type: object + required: + - statusCode + - error + - message + properties: + statusCode: + type: number + example: 404 + error: + type: string + example: Not Found + message: + type: string + example: SLO [3749f390-03a3-11ee-8139-c7ff60a1692d] not found create_slo_request: title: Create SLO request description: | @@ -1421,9 +959,7 @@ components: - $ref: '#/components/schemas/indicator_properties_apm_latency' - $ref: '#/components/schemas/indicator_properties_custom_metric' timeWindow: - oneOf: - - $ref: '#/components/schemas/time_window_rolling' - - $ref: '#/components/schemas/time_window_calendar_aligned' + $ref: '#/components/schemas/time_window' budgetingMethod: $ref: '#/components/schemas/budgeting_method' objective: @@ -1439,6 +975,23 @@ components: id: type: string example: 8853df00-ae2e-11ed-90af-09bb6422b258 + 409_response: + title: Conflict + type: object + required: + - statusCode + - error + - message + properties: + statusCode: + type: number + example: 409 + error: + type: string + example: Conflict + message: + type: string + example: SLO [d077e940-1515-11ee-9c50-9d096392f520] already exists update_slo_request: title: Update SLO request description: | @@ -1458,9 +1011,7 @@ components: - $ref: '#/components/schemas/indicator_properties_apm_latency' - $ref: '#/components/schemas/indicator_properties_custom_metric' timeWindow: - oneOf: - - $ref: '#/components/schemas/time_window_rolling' - - $ref: '#/components/schemas/time_window_calendar_aligned' + $ref: '#/components/schemas/time_window' budgetingMethod: $ref: '#/components/schemas/budgeting_method' objective: @@ -1503,3 +1054,6 @@ components: example: 0.9836 errorBudget: $ref: '#/components/schemas/error_budget' +security: + - basicAuth: [] + - apiKeyAuth: [] diff --git a/x-pack/plugins/observability/docs/openapi/slo/components/schemas/base_composite_slo_response.yaml b/x-pack/plugins/observability/docs/openapi/slo/components/schemas/base_composite_slo_response.yaml index 08c5b7c7eba6e..8fce7e38f46de 100644 --- a/x-pack/plugins/observability/docs/openapi/slo/components/schemas/base_composite_slo_response.yaml +++ b/x-pack/plugins/observability/docs/openapi/slo/components/schemas/base_composite_slo_response.yaml @@ -10,16 +10,16 @@ properties: type: string example: My Service SLO timeWindow: - $ref: 'time_window_rolling.yaml' + $ref: "time_window.yaml" budgetingMethod: - $ref: 'budgeting_method.yaml' + $ref: "budgeting_method.yaml" compositeMethod: - $ref: 'composite_method.yaml' + $ref: "composite_method.yaml" objective: - $ref: 'objective.yaml' + $ref: "objective.yaml" sources: oneOf: - - $ref: 'weighted_composite_sources.yaml' + - $ref: "weighted_composite_sources.yaml" createdAt: description: The creation date type: string diff --git a/x-pack/plugins/observability/docs/openapi/slo/components/schemas/composite_slo_response.yaml b/x-pack/plugins/observability/docs/openapi/slo/components/schemas/composite_slo_response.yaml index 305377500a932..e4c66379ef554 100644 --- a/x-pack/plugins/observability/docs/openapi/slo/components/schemas/composite_slo_response.yaml +++ b/x-pack/plugins/observability/docs/openapi/slo/components/schemas/composite_slo_response.yaml @@ -10,18 +10,18 @@ properties: type: string example: My Service SLO timeWindow: - $ref: 'time_window_rolling.yaml' + $ref: "time_window.yaml" budgetingMethod: - $ref: 'budgeting_method.yaml' + $ref: "budgeting_method.yaml" compositeMethod: - $ref: 'composite_method.yaml' + $ref: "composite_method.yaml" objective: - $ref: 'objective.yaml' + $ref: "objective.yaml" sources: oneOf: - - $ref: 'weighted_composite_sources.yaml' + - $ref: "weighted_composite_sources.yaml" summary: - $ref: 'summary.yaml' + $ref: "summary.yaml" createdAt: description: The creation date type: string diff --git a/x-pack/plugins/observability/docs/openapi/slo/components/schemas/create_composite_slo_request.yaml b/x-pack/plugins/observability/docs/openapi/slo/components/schemas/create_composite_slo_request.yaml index 3db2a3ff738fc..46a801c75bba2 100644 --- a/x-pack/plugins/observability/docs/openapi/slo/components/schemas/create_composite_slo_request.yaml +++ b/x-pack/plugins/observability/docs/openapi/slo/components/schemas/create_composite_slo_request.yaml @@ -18,14 +18,13 @@ properties: description: A name for the composite SLO. type: string timeWindow: - $ref: 'time_window_rolling.yaml' + $ref: "time_window.yaml" budgetingMethod: - $ref: 'budgeting_method.yaml' + $ref: "budgeting_method.yaml" compositeMethod: - $ref: 'composite_method.yaml' + $ref: "composite_method.yaml" objective: - $ref: 'objective.yaml' + $ref: "objective.yaml" sources: oneOf: - - $ref: 'weighted_composite_sources.yaml' - + - $ref: "weighted_composite_sources.yaml" diff --git a/x-pack/plugins/observability/docs/openapi/slo/components/schemas/create_slo_request.yaml b/x-pack/plugins/observability/docs/openapi/slo/components/schemas/create_slo_request.yaml index b9bf929611fc8..86394ff6f210c 100644 --- a/x-pack/plugins/observability/docs/openapi/slo/components/schemas/create_slo_request.yaml +++ b/x-pack/plugins/observability/docs/openapi/slo/components/schemas/create_slo_request.yaml @@ -22,17 +22,15 @@ properties: type: string indicator: oneOf: - - $ref: 'indicator_properties_custom_kql.yaml' - - $ref: 'indicator_properties_apm_availability.yaml' - - $ref: 'indicator_properties_apm_latency.yaml' - - $ref: 'indicator_properties_custom_metric.yaml' + - $ref: "indicator_properties_custom_kql.yaml" + - $ref: "indicator_properties_apm_availability.yaml" + - $ref: "indicator_properties_apm_latency.yaml" + - $ref: "indicator_properties_custom_metric.yaml" timeWindow: - oneOf: - - $ref: 'time_window_rolling.yaml' - - $ref: 'time_window_calendar_aligned.yaml' + $ref: "time_window.yaml" budgetingMethod: - $ref: 'budgeting_method.yaml' + $ref: "budgeting_method.yaml" objective: - $ref: 'objective.yaml' + $ref: "objective.yaml" settings: - $ref: 'settings.yaml' + $ref: "settings.yaml" diff --git a/x-pack/plugins/observability/docs/openapi/slo/components/schemas/indicator_properties_custom_metric.yaml b/x-pack/plugins/observability/docs/openapi/slo/components/schemas/indicator_properties_custom_metric.yaml index 6746bf400e8d0..d97dc32fe0d11 100644 --- a/x-pack/plugins/observability/docs/openapi/slo/components/schemas/indicator_properties_custom_metric.yaml +++ b/x-pack/plugins/observability/docs/openapi/slo/components/schemas/indicator_properties_custom_metric.yaml @@ -50,7 +50,7 @@ properties: description: The name of the metric. Only valid options are A-Z type: string example: A - pattern: '^[A-Z]$' + pattern: "^[A-Z]$" aggregation: description: The aggregation type of the metric. Only valid option is "sum" type: string @@ -63,7 +63,7 @@ properties: filter: description: The filter to apply to the metric. type: string - example: processor.outcome: "success" + example: 'processor.outcome: "success"' equation: description: The equation to calculate the "good" metric. type: string @@ -90,7 +90,7 @@ properties: description: The name of the metric. Only valid options are A-Z type: string example: A - pattern: '^[A-Z]$' + pattern: "^[A-Z]$" aggregation: description: The aggregation type of the metric. Only valid option is "sum" type: string @@ -103,7 +103,7 @@ properties: filter: description: The filter to apply to the metric. type: string - example: processor.outcome: * + example: "processor.outcome: *" equation: description: The equation to calculate the "total" metric. type: string diff --git a/x-pack/plugins/observability/docs/openapi/slo/components/schemas/slo_response.yaml b/x-pack/plugins/observability/docs/openapi/slo/components/schemas/slo_response.yaml index 93e762ec3e6cf..afe03d12d676f 100644 --- a/x-pack/plugins/observability/docs/openapi/slo/components/schemas/slo_response.yaml +++ b/x-pack/plugins/observability/docs/openapi/slo/components/schemas/slo_response.yaml @@ -15,26 +15,24 @@ properties: example: My SLO description indicator: oneOf: - - $ref: 'indicator_properties_custom_kql.yaml' - - $ref: 'indicator_properties_apm_availability.yaml' - - $ref: 'indicator_properties_apm_latency.yaml' - - $ref: 'indicator_properties_custom_metric.yaml' + - $ref: "indicator_properties_custom_kql.yaml" + - $ref: "indicator_properties_apm_availability.yaml" + - $ref: "indicator_properties_apm_latency.yaml" + - $ref: "indicator_properties_custom_metric.yaml" timeWindow: - oneOf: - - $ref: 'time_window_rolling.yaml' - - $ref: 'time_window_calendar_aligned.yaml' + $ref: "time_window.yaml" budgetingMethod: - $ref: 'budgeting_method.yaml' + $ref: "budgeting_method.yaml" objective: - $ref: 'objective.yaml' + $ref: "objective.yaml" settings: - $ref: 'settings.yaml' + $ref: "settings.yaml" revision: description: The SLO revision type: number example: 2 summary: - $ref: 'summary.yaml' + $ref: "summary.yaml" enabled: description: Indicate if the SLO is enabled type: boolean diff --git a/x-pack/plugins/observability/docs/openapi/slo/components/schemas/time_window.yaml b/x-pack/plugins/observability/docs/openapi/slo/components/schemas/time_window.yaml new file mode 100644 index 0000000000000..39ee742bcc07f --- /dev/null +++ b/x-pack/plugins/observability/docs/openapi/slo/components/schemas/time_window.yaml @@ -0,0 +1,16 @@ +title: Time window +required: + - duration + - type +description: Defines properties for the SLO time window +type: object +properties: + duration: + description: "the duration formatted as {duration}{unit}. Accepted values for rolling: 7d, 30d, 90d. Accepted values for calendar aligned: 1w (weekly) or 1M (monthly)" + type: string + example: 30d + type: + description: Indicates weither the time window is a rolling or a calendar aligned time window. + type: string + example: "rolling" + enum: ["rolling", "calendarAligned"] diff --git a/x-pack/plugins/observability/docs/openapi/slo/components/schemas/time_window_calendar_aligned.yaml b/x-pack/plugins/observability/docs/openapi/slo/components/schemas/time_window_calendar_aligned.yaml deleted file mode 100644 index 7b914e114b268..0000000000000 --- a/x-pack/plugins/observability/docs/openapi/slo/components/schemas/time_window_calendar_aligned.yaml +++ /dev/null @@ -1,15 +0,0 @@ -title: Calendar aligned -required: - - duration - - isCalendar -description: Defines properties for calendar aligned time window -type: object -properties: - duration: - description: the duration formatted as {duration}{unit}, accept '1w' (weekly calendar) or '1M' (monthly calendar) only - type: string - example: 1M - isCalendar: - description: Indicates a calendar aligned time window - type: boolean - example: true \ No newline at end of file diff --git a/x-pack/plugins/observability/docs/openapi/slo/components/schemas/time_window_rolling.yaml b/x-pack/plugins/observability/docs/openapi/slo/components/schemas/time_window_rolling.yaml deleted file mode 100644 index bf949153ee3f9..0000000000000 --- a/x-pack/plugins/observability/docs/openapi/slo/components/schemas/time_window_rolling.yaml +++ /dev/null @@ -1,15 +0,0 @@ -title: Rolling -required: - - duration - - isRolling -description: Defines properties for rolling time window -type: object -properties: - duration: - description: the duration formatted as {duration}{unit} - type: string - example: 28d - isRolling: - description: Indicates a rolling time window - type: boolean - example: true diff --git a/x-pack/plugins/observability/docs/openapi/slo/components/schemas/update_composite_slo_request.yaml b/x-pack/plugins/observability/docs/openapi/slo/components/schemas/update_composite_slo_request.yaml index 2095ca6a1d0ea..6368b62badf6e 100644 --- a/x-pack/plugins/observability/docs/openapi/slo/components/schemas/update_composite_slo_request.yaml +++ b/x-pack/plugins/observability/docs/openapi/slo/components/schemas/update_composite_slo_request.yaml @@ -11,14 +11,13 @@ properties: description: A name for the composite SLO. type: string timeWindow: - $ref: 'time_window_rolling.yaml' + $ref: "time_window.yaml" budgetingMethod: - $ref: 'budgeting_method.yaml' + $ref: "budgeting_method.yaml" compositeMethod: - $ref: 'composite_method.yaml' + $ref: "composite_method.yaml" objective: - $ref: 'objective.yaml' + $ref: "objective.yaml" sources: oneOf: - - $ref: 'weighted_composite_sources.yaml' - + - $ref: "weighted_composite_sources.yaml" diff --git a/x-pack/plugins/observability/docs/openapi/slo/components/schemas/update_slo_request.yaml b/x-pack/plugins/observability/docs/openapi/slo/components/schemas/update_slo_request.yaml index aae12b175d782..0587ddf0abb2b 100644 --- a/x-pack/plugins/observability/docs/openapi/slo/components/schemas/update_slo_request.yaml +++ b/x-pack/plugins/observability/docs/openapi/slo/components/schemas/update_slo_request.yaml @@ -12,17 +12,15 @@ properties: type: string indicator: oneOf: - - $ref: 'indicator_properties_custom_kql.yaml' - - $ref: 'indicator_properties_apm_availability.yaml' - - $ref: 'indicator_properties_apm_latency.yaml' - - $ref: 'indicator_properties_custom_metric.yaml' + - $ref: "indicator_properties_custom_kql.yaml" + - $ref: "indicator_properties_apm_availability.yaml" + - $ref: "indicator_properties_apm_latency.yaml" + - $ref: "indicator_properties_custom_metric.yaml" timeWindow: - oneOf: - - $ref: 'time_window_rolling.yaml' - - $ref: 'time_window_calendar_aligned.yaml' + $ref: "time_window.yaml" budgetingMethod: - $ref: 'budgeting_method.yaml' + $ref: "budgeting_method.yaml" objective: - $ref: 'objective.yaml' + $ref: "objective.yaml" settings: - $ref: 'settings.yaml' + $ref: "settings.yaml" diff --git a/x-pack/plugins/observability/docs/openapi/slo/entrypoint.yaml b/x-pack/plugins/observability/docs/openapi/slo/entrypoint.yaml index 94f6416103ade..44f16ed4585e0 100644 --- a/x-pack/plugins/observability/docs/openapi/slo/entrypoint.yaml +++ b/x-pack/plugins/observability/docs/openapi/slo/entrypoint.yaml @@ -2,7 +2,7 @@ openapi: 3.0.1 info: title: SLOs description: OpenAPI schema for SLOs endpoints - version: '1.0' + version: "1.0" contact: name: Actionable Observability Team license: @@ -14,23 +14,23 @@ tags: - name: composite slo description: Composite SLO APIs enable you to define, manage and track a group of SLOs. servers: - - url: 'http://localhost:5601' + - url: "http://localhost:5601" description: local paths: - '/s/{spaceId}/api/observability/composite_slos': - $ref: 'paths/s@{spaceid}@api@composite_slos.yaml' - '/s/{spaceId}/api/observability/composite_slos/{compositeSloId}': - $ref: 'paths/s@{spaceid}@api@composite_slos@{compositesloid}.yaml' - '/s/{spaceId}/api/observability/slos': - $ref: 'paths/s@{spaceid}@api@slos.yaml' - '/s/{spaceId}/api/observability/slos/{sloId}': - $ref: 'paths/s@{spaceid}@api@slos@{sloid}.yaml' - '/s/{spaceId}/api/observability/slos/{sloId}/enable': - $ref: 'paths/s@{spaceid}@api@slos@{sloid}@{enable}.yaml' - '/s/{spaceId}/api/observability/slos/{sloId}/disable': - $ref: 'paths/s@{spaceid}@api@slos@{sloid}@{disable}.yaml' - '/s/{spaceId}/internal/observability/slos/_historical_summary': - $ref: 'paths/s@{spaceid}@api@slos@_historical_summary.yaml' + #'/s/{spaceId}/api/observability/composite_slos': + # $ref: 'paths/s@{spaceid}@api@composite_slos.yaml' + #'/s/{spaceId}/api/observability/composite_slos/{compositeSloId}': + # $ref: 'paths/s@{spaceid}@api@composite_slos@{compositesloid}.yaml' + "/s/{spaceId}/api/observability/slos": + $ref: "paths/s@{spaceid}@api@slos.yaml" + "/s/{spaceId}/api/observability/slos/{sloId}": + $ref: "paths/s@{spaceid}@api@slos@{sloid}.yaml" + "/s/{spaceId}/api/observability/slos/{sloId}/enable": + $ref: "paths/s@{spaceid}@api@slos@{sloid}@{enable}.yaml" + "/s/{spaceId}/api/observability/slos/{sloId}/disable": + $ref: "paths/s@{spaceid}@api@slos@{sloid}@{disable}.yaml" + "/s/{spaceId}/internal/observability/slos/_historical_summary": + $ref: "paths/s@{spaceid}@api@slos@_historical_summary.yaml" components: securitySchemes: basicAuth: From b83f47560fd48dfe6d07268e420efac861223242 Mon Sep 17 00:00:00 2001 From: Alexi Doak <109488926+doakalexi@users.noreply.github.com> Date: Tue, 11 Jul 2023 12:31:30 -0400 Subject: [PATCH 16/61] [Response Ops][Alerting] Allow runtime fields to be selected for Elasticsearch query rule type group by or aggregate over options (#160319) Resolves https://github.com/elastic/kibana/issues/157258 ## Summary Gets the runtime_mappings from the es query, and includes them in the query to retrieve the fields. Also gets runtime mappings from data views. ### Checklist - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios ### To verify - Create a new Es Query rule using DSL - Include runtime mappings in your query and verify that the runtime fields are listed in the group by or aggregate over options below the query - Create a dataview and include runtime fields - Create a new Es Query rule using KQL - Verify that the runtime fields are listed in the group by or aggregate over options below the query --- .../expression/es_query_expression.tsx | 23 ++++- .../public/rule_types/es_query/util.test.ts | 85 ++++++++++++++++++- .../public/rule_types/es_query/util.ts | 32 ++++++- .../triggers_actions_ui/common/index.ts | 1 + .../common/normalized_field_types.ts | 18 ++++ .../public/common/index.ts | 1 + .../server/data/routes/fields.ts | 15 +--- 7 files changed, 156 insertions(+), 19 deletions(-) create mode 100644 x-pack/plugins/triggers_actions_ui/common/normalized_field_types.ts diff --git a/x-pack/plugins/stack_alerts/public/rule_types/es_query/expression/es_query_expression.tsx b/x-pack/plugins/stack_alerts/public/rule_types/es_query/expression/es_query_expression.tsx index bf0af46912c62..f3a0c0175b300 100644 --- a/x-pack/plugins/stack_alerts/public/rule_types/es_query/expression/es_query_expression.tsx +++ b/x-pack/plugins/stack_alerts/public/rule_types/es_query/expression/es_query_expression.tsx @@ -6,6 +6,7 @@ */ import React, { useState, Fragment, useEffect, useCallback } from 'react'; +import { get, sortBy } from 'lodash'; import { lastValueFrom } from 'rxjs'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; @@ -32,7 +33,7 @@ import { EsQueryRuleParams, EsQueryRuleMetaData, SearchType } from '../types'; import { IndexSelectPopover } from '../../components/index_select_popover'; import { DEFAULT_VALUES } from '../constants'; import { RuleCommonExpressions } from '../rule_common_expressions'; -import { useTriggerUiActionServices } from '../util'; +import { convertRawRuntimeFieldtoFieldOption, useTriggerUiActionServices } from '../util'; const { useXJsonMode } = XJson; @@ -89,6 +90,8 @@ export const EsQueryExpression: React.FC< const { http, docLinks } = services; const [esFields, setEsFields] = useState([]); + const [runtimeFields, setRuntimeFields] = useState([]); + const [combinedFields, setCombinedFields] = useState([]); const { convertToJson, setXJson, xJson } = useXJsonMode(DEFAULT_VALUES.QUERY); const setDefaultExpressionValues = async () => { @@ -108,6 +111,21 @@ export const EsQueryExpression: React.FC< const refreshEsFields = async (indices: string[]) => { const currentEsFields = await getFields(http, indices); setEsFields(currentEsFields); + setCombinedFields(sortBy(currentEsFields.concat(runtimeFields), 'name')); + }; + + const getRuntimeFields = () => { + let runtimeMappings; + try { + runtimeMappings = get(JSON.parse(xJson), 'runtime_mappings'); + } catch (e) { + // ignore error + } + if (runtimeMappings) { + const currentRuntimeFields = convertRawRuntimeFieldtoFieldOption(runtimeMappings); + setRuntimeFields(currentRuntimeFields); + setCombinedFields(sortBy(esFields.concat(currentRuntimeFields), 'name')); + } }; const onTestQuery = useCallback(async () => { @@ -252,6 +270,7 @@ export const EsQueryExpression: React.FC< onChange={(xjson: string) => { setXJson(xjson); setParam('esQuery', convertToJson(xjson)); + getRuntimeFields(); }} options={{ ariaLabel: i18n.translate('xpack.stackAlerts.esQuery.ui.queryEditor', { @@ -276,7 +295,7 @@ export const EsQueryExpression: React.FC< timeWindowSize={timeWindowSize} timeWindowUnit={timeWindowUnit} size={size} - esFields={esFields} + esFields={combinedFields} aggType={aggType} aggField={aggField} groupBy={groupBy} diff --git a/x-pack/plugins/stack_alerts/public/rule_types/es_query/util.test.ts b/x-pack/plugins/stack_alerts/public/rule_types/es_query/util.test.ts index 355c3e19516d6..f2d6e6abe250f 100644 --- a/x-pack/plugins/stack_alerts/public/rule_types/es_query/util.test.ts +++ b/x-pack/plugins/stack_alerts/public/rule_types/es_query/util.test.ts @@ -5,12 +5,30 @@ * 2.0. */ -import { convertFieldSpecToFieldOption } from './util'; +import { convertFieldSpecToFieldOption, convertRawRuntimeFieldtoFieldOption } from './util'; -describe('convertFieldSpecToFieldOption', () => { +describe('Es Query utils', () => { test('should correctly convert FieldSpec to FieldOption', () => { expect( convertFieldSpecToFieldOption([ + { + count: 0, + name: 'day_of_week', + type: 'string', + esTypes: ['keyword'], + scripted: false, + searchable: true, + aggregatable: true, + readFromDocValues: false, + shortDotsEnable: false, + runtimeField: { + type: 'keyword', + script: { + source: + "emit(doc['@timestamp'].value.dayOfWeekEnum.getDisplayName(TextStyle.FULL, Locale.ROOT))", + }, + }, + }, { count: 0, name: '@timestamp', @@ -85,6 +103,13 @@ describe('convertFieldSpecToFieldOption', () => { }, ]) ).toEqual([ + { + name: 'day_of_week', + type: 'keyword', + normalizedType: 'keyword', + aggregatable: true, + searchable: true, + }, { name: '@timestamp', type: 'date', @@ -122,4 +147,60 @@ describe('convertFieldSpecToFieldOption', () => { }, ]); }); + + test('should correctly convert raw runtime field to FieldOption', () => { + expect( + convertRawRuntimeFieldtoFieldOption({ + day_of_week: { + type: 'keyword', + script: { + source: + "emit(doc['@timestamp'].value.dayOfWeekEnum.getDisplayName(TextStyle.FULL, Locale.ROOT))", + }, + }, + location: { + type: 'lookup', + target_index: 'ip_location', + input_field: 'host', + target_field: 'ip', + fetch_fields: ['country', 'city'], + }, + }) + ).toEqual([ + { + name: 'day_of_week', + type: 'keyword', + normalizedType: 'keyword', + aggregatable: true, + searchable: true, + }, + { + name: 'location', + type: 'lookup', + normalizedType: 'lookup', + aggregatable: false, + searchable: false, + }, + ]); + }); + + test('should return an empty array if raw runtime fields are malformed JSON', () => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const rawFields: any = null; + expect(convertRawRuntimeFieldtoFieldOption(rawFields)).toEqual([]); + }); + + test('should not return FieldOption if raw runtime fields do not include the type', () => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const rawFields: any = { + day_of_week: { + test: 'keyword', + script: { + source: + "emit(doc['@timestamp'].value.dayOfWeekEnum.getDisplayName(TextStyle.FULL, Locale.ROOT))", + }, + }, + }; + expect(convertRawRuntimeFieldtoFieldOption(rawFields)).toEqual([]); + }); }); diff --git a/x-pack/plugins/stack_alerts/public/rule_types/es_query/util.ts b/x-pack/plugins/stack_alerts/public/rule_types/es_query/util.ts index c4ec66a21c4e8..5568924e845e4 100644 --- a/x-pack/plugins/stack_alerts/public/rule_types/es_query/util.ts +++ b/x-pack/plugins/stack_alerts/public/rule_types/es_query/util.ts @@ -7,7 +7,8 @@ import { FieldSpec } from '@kbn/data-views-plugin/common'; import { useKibana } from '@kbn/triggers-actions-ui-plugin/public'; -import { FieldOption } from '@kbn/triggers-actions-ui-plugin/public/common'; +import { FieldOption, NORMALIZED_FIELD_TYPES } from '@kbn/triggers-actions-ui-plugin/public/common'; +import { estypes } from '@elastic/elasticsearch'; import { EsQueryRuleParams, SearchType } from './types'; export const isSearchSourceRule = ( @@ -18,7 +19,7 @@ export const isSearchSourceRule = ( export const convertFieldSpecToFieldOption = (fieldSpec: FieldSpec[]): FieldOption[] => { return (fieldSpec ?? []) - .filter((spec: FieldSpec) => spec.isMapped) + .filter((spec: FieldSpec) => spec.isMapped || spec.runtimeField) .map((spec: FieldSpec) => { const converted = { name: spec.name, @@ -41,4 +42,31 @@ export const convertFieldSpecToFieldOption = (fieldSpec: FieldSpec[]): FieldOpti }); }; +export const convertRawRuntimeFieldtoFieldOption = ( + rawFields: Record +): FieldOption[] => { + const result: FieldOption[] = []; + + // verifying that the raw fields are an object + let keys; + try { + keys = Object.keys(rawFields); + } catch (e) { + return result; + } + + for (const name of keys) { + const rawField = rawFields[name]; + const type = rawField.type; + + const normalizedType = NORMALIZED_FIELD_TYPES[type] || type; + const isAggregatableAndSearchable = type !== 'lookup'; + const aggregatable = isAggregatableAndSearchable; + const searchable = isAggregatableAndSearchable; + + if (type) result.push({ name, type, normalizedType, aggregatable, searchable }); + } + return result; +}; + export const useTriggerUiActionServices = () => useKibana().services; diff --git a/x-pack/plugins/triggers_actions_ui/common/index.ts b/x-pack/plugins/triggers_actions_ui/common/index.ts index 1675dee6129d9..142f6e8ac9c81 100644 --- a/x-pack/plugins/triggers_actions_ui/common/index.ts +++ b/x-pack/plugins/triggers_actions_ui/common/index.ts @@ -12,3 +12,4 @@ export * from './data'; export const BASE_TRIGGERS_ACTIONS_UI_API_PATH = '/internal/triggers_actions_ui'; export * from './parse_interval'; export * from './experimental_features'; +export * from './normalized_field_types'; diff --git a/x-pack/plugins/triggers_actions_ui/common/normalized_field_types.ts b/x-pack/plugins/triggers_actions_ui/common/normalized_field_types.ts new file mode 100644 index 0000000000000..7f69685111f4f --- /dev/null +++ b/x-pack/plugins/triggers_actions_ui/common/normalized_field_types.ts @@ -0,0 +1,18 @@ +/* + * 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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export const NORMALIZED_FIELD_TYPES: Record = { + long: 'number', + integer: 'number', + short: 'number', + byte: 'number', + double: 'number', + float: 'number', + half_float: 'number', + scaled_float: 'number', + unsigned_long: 'number', +}; diff --git a/x-pack/plugins/triggers_actions_ui/public/common/index.ts b/x-pack/plugins/triggers_actions_ui/public/common/index.ts index 4cf4f8c855d4e..f9b3cc7c8654c 100644 --- a/x-pack/plugins/triggers_actions_ui/public/common/index.ts +++ b/x-pack/plugins/triggers_actions_ui/public/common/index.ts @@ -39,5 +39,6 @@ export { isCountAggregation, isGroupAggregation, parseAggregationResults, + NORMALIZED_FIELD_TYPES, } from '../../common'; export type { ParsedAggregationGroup } from '../../common'; diff --git a/x-pack/plugins/triggers_actions_ui/server/data/routes/fields.ts b/x-pack/plugins/triggers_actions_ui/server/data/routes/fields.ts index e2c4302afe352..bf45b702a84ec 100644 --- a/x-pack/plugins/triggers_actions_ui/server/data/routes/fields.ts +++ b/x-pack/plugins/triggers_actions_ui/server/data/routes/fields.ts @@ -15,6 +15,7 @@ import { ElasticsearchClient, } from '@kbn/core/server'; import { Logger } from '@kbn/core/server'; +import { NORMALIZED_FIELD_TYPES } from '../../../common'; const bodySchema = schema.object({ indexPatterns: schema.arrayOf(schema.string()), @@ -118,7 +119,7 @@ function getFieldsFromRawFields(rawFields: RawFields): Field[] { if (!type || type.startsWith('_')) continue; if (!values) continue; - const normalizedType = normalizedFieldTypes[type] || type; + const normalizedType = NORMALIZED_FIELD_TYPES[type] || type; const aggregatable = values.aggregatable; const searchable = values.searchable; @@ -128,15 +129,3 @@ function getFieldsFromRawFields(rawFields: RawFields): Field[] { result.sort((a, b) => a.name.localeCompare(b.name)); return result; } - -const normalizedFieldTypes: Record = { - long: 'number', - integer: 'number', - short: 'number', - byte: 'number', - double: 'number', - float: 'number', - half_float: 'number', - scaled_float: 'number', - unsigned_long: 'number', -}; From f4e9cd15d5318e6a3b3432d2e3039bae4a229250 Mon Sep 17 00:00:00 2001 From: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Date: Tue, 11 Jul 2023 12:35:49 -0400 Subject: [PATCH 17/61] skip failing test suite (#157711) --- x-pack/test/functional/apps/infra/home_page.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/test/functional/apps/infra/home_page.ts b/x-pack/test/functional/apps/infra/home_page.ts index 88d7538043e32..842f32efc9933 100644 --- a/x-pack/test/functional/apps/infra/home_page.ts +++ b/x-pack/test/functional/apps/infra/home_page.ts @@ -21,7 +21,8 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { const kibanaServer = getService('kibanaServer'); const testSubjects = getService('testSubjects'); - describe('Home page', function () { + // Failing: See https://github.com/elastic/kibana/issues/157711 + describe.skip('Home page', function () { this.tags('includeFirefox'); before(async () => { await kibanaServer.savedObjects.cleanStandardList(); From dd292b70b7fc121dcef4fad84b979d730b6c6f6e Mon Sep 17 00:00:00 2001 From: Xavier Mouligneau Date: Tue, 11 Jul 2023 13:25:20 -0400 Subject: [PATCH 18/61] [RAM] Remove allow slack channels (#161674) ## Summary Remove allow slack channels feature for 8.9 until we have a better way to deal with channels ### Checklist - [X] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios --- .../common/slack_api/schema.ts | 4 +- .../slack_api/slack_connectors.test.tsx | 78 +------------ .../slack_api/slack_connectors.tsx | 62 +--------- .../connector_types/slack_api/index.test.ts | 14 --- .../server/connector_types/slack_api/index.ts | 1 - .../connector_types/slack_api/service.test.ts | 45 -------- .../connector_types/slack_api/service.ts | 33 +----- .../plugins/stack_connectors/server/plugin.ts | 11 +- .../server/routes/get_slack_api_channels.ts | 109 ------------------ .../stack_connectors/server/routes/index.ts | 1 - 10 files changed, 9 insertions(+), 349 deletions(-) delete mode 100644 x-pack/plugins/stack_connectors/server/routes/get_slack_api_channels.ts diff --git a/x-pack/plugins/stack_connectors/common/slack_api/schema.ts b/x-pack/plugins/stack_connectors/common/slack_api/schema.ts index 3a96528ba2801..4f121fd92389a 100644 --- a/x-pack/plugins/stack_connectors/common/slack_api/schema.ts +++ b/x-pack/plugins/stack_connectors/common/slack_api/schema.ts @@ -11,9 +11,7 @@ export const SlackApiSecretsSchema = schema.object({ token: schema.string({ minLength: 1 }), }); -export const SlackApiConfigSchema = schema.object({ - allowedChannels: schema.maybe(schema.arrayOf(schema.string())), -}); +export const SlackApiConfigSchema = schema.object({}, { defaultValue: {} }); export const GetChannelsParamsSchema = schema.object({ subAction: schema.literal('getChannels'), diff --git a/x-pack/plugins/stack_connectors/public/connector_types/slack_api/slack_connectors.test.tsx b/x-pack/plugins/stack_connectors/public/connector_types/slack_api/slack_connectors.test.tsx index 8346e4b07c697..8c255bce003fe 100644 --- a/x-pack/plugins/stack_connectors/public/connector_types/slack_api/slack_connectors.test.tsx +++ b/x-pack/plugins/stack_connectors/public/connector_types/slack_api/slack_connectors.test.tsx @@ -48,16 +48,14 @@ describe('SlackActionFields renders', () => { secrets: { token: 'some token', }, - config: { - allowedChannels: ['foo', 'bar'], - }, + config: {}, id: 'test', actionTypeId: '.slack', name: 'slack', isDeprecated: false, }; - const { container } = render( + render( {}} /> @@ -65,17 +63,6 @@ describe('SlackActionFields renders', () => { expect(screen.getByTestId('secrets.token-input')).toBeInTheDocument(); expect(screen.getByTestId('secrets.token-input')).toHaveValue('some token'); - expect(screen.getByTestId('config.allowedChannels-input')).toBeInTheDocument(); - const allowedChannels: string[] = []; - container - .querySelectorAll('[data-test-subj="config.allowedChannels-input"] .euiBadge') - .forEach((node) => { - const channel = node.getAttribute('title'); - if (channel) { - allowedChannels.push(channel); - } - }); - expect(allowedChannels).toEqual(['foo', 'bar']); }); it('connector validation succeeds when connector config is valid for Web API type', async () => { @@ -105,9 +92,6 @@ describe('SlackActionFields renders', () => { secrets: { token: 'some token', }, - config: { - allowedChannels: [], - }, id: 'test', actionTypeId: '.slack', name: 'slack', @@ -116,62 +100,4 @@ describe('SlackActionFields renders', () => { isValid: true, }); }); - - it('Allowed Channels combobox should be disable when there is NO token', async () => { - const actionConnector = { - secrets: { - token: '', - }, - config: { - allowedChannels: ['foo', 'bar'], - }, - id: 'test', - actionTypeId: '.slack', - name: 'slack', - isDeprecated: false, - }; - - const { container } = render( - - {}} /> - - ); - expect( - container.querySelector( - '[data-test-subj="config.allowedChannels-input"].euiComboBox-isDisabled' - ) - ).toBeInTheDocument(); - }); - - it('Allowed Channels combobox should NOT be disable when there is token', async () => { - const actionConnector = { - secrets: { - token: 'qwertyuiopasdfghjklzxcvbnm', - }, - config: { - allowedChannels: ['foo', 'bar'], - }, - id: 'test', - actionTypeId: '.slack', - name: 'slack', - isDeprecated: false, - }; - - (useFetchChannels as jest.Mock).mockImplementation(() => ({ - channels: [{ label: 'foo' }, { label: 'bar' }, { label: 'hello' }, { label: 'world' }], - isLoading: false, - })); - - const { container } = render( - - {}} /> - - ); - - expect( - container.querySelector( - '[data-test-subj="config.allowedChannels-input"].euiComboBox-isDisabled' - ) - ).not.toBeInTheDocument(); - }); }); diff --git a/x-pack/plugins/stack_connectors/public/connector_types/slack_api/slack_connectors.tsx b/x-pack/plugins/stack_connectors/public/connector_types/slack_api/slack_connectors.tsx index 2caf8bff0b611..71a262954d2d1 100644 --- a/x-pack/plugins/stack_connectors/public/connector_types/slack_api/slack_connectors.tsx +++ b/x-pack/plugins/stack_connectors/public/connector_types/slack_api/slack_connectors.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { useEffect, useMemo, useState } from 'react'; +import React from 'react'; import { ActionConnectorFieldsProps, ConfigFieldSchema, @@ -13,18 +13,12 @@ import { SimpleConnectorForm, useKibana, } from '@kbn/triggers-actions-ui-plugin/public'; -import { EuiComboBoxOptionOption, EuiLink } from '@elastic/eui'; +import { EuiLink } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; import { DocLinksStart } from '@kbn/core/public'; -import { useFormContext, useFormData } from '@kbn/es-ui-shared-plugin/static/forms/hook_form_lib'; -import { debounce, isEmpty } from 'lodash'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import * as i18n from './translations'; -import { useFetchChannels } from './use_fetch_channels'; - -/** wait this many ms after the user completes typing before applying the filter input */ -const INPUT_TIMEOUT = 250; const getSecretsFormSchema = (docLinks: DocLinksStart): SecretsFieldSchema[] => [ { @@ -42,31 +36,6 @@ const getSecretsFormSchema = (docLinks: DocLinksStart): SecretsFieldSchema[] => }, ]; -const getConfigFormSchemaAfterSecrets = ( - options: EuiComboBoxOptionOption[], - isLoading: boolean, - isDisabled: boolean -): ConfigFieldSchema[] => [ - { - id: 'allowedChannels', - isRequired: false, - label: i18n.ALLOWED_CHANNELS, - helpText: ( - - ), - type: 'COMBO_BOX', - euiFieldProps: { - isDisabled, - isLoading, - noSuggestions: false, - options, - }, - }, -]; - const NO_SCHEMA: ConfigFieldSchema[] = []; export const SlackActionFieldsComponents: React.FC = ({ @@ -75,39 +44,12 @@ export const SlackActionFieldsComponents: React.FC = }) => { const { docLinks } = useKibana().services; - const form = useFormContext(); - const { setFieldValue } = form; - const [formData] = useFormData({ form }); - const [authToken, setAuthToken] = useState(''); - - const { channels, isLoading } = useFetchChannels({ authToken }); - const configFormSchemaAfterSecrets = useMemo( - () => getConfigFormSchemaAfterSecrets(channels, isLoading, channels.length === 0), - [channels, isLoading] - ); - - const debounceSetToken = debounce(setAuthToken, INPUT_TIMEOUT); - useEffect(() => { - if (formData.secrets && formData.secrets.token !== authToken) { - debounceSetToken(formData.secrets.token); - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [formData.secrets]); - - useEffect(() => { - if (isEmpty(authToken) && channels.length > 0) { - setFieldValue('config.allowedChannels', []); - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [authToken]); - return ( ); }; diff --git a/x-pack/plugins/stack_connectors/server/connector_types/slack_api/index.test.ts b/x-pack/plugins/stack_connectors/server/connector_types/slack_api/index.test.ts index 2b4022285dea8..c4922020cdce7 100644 --- a/x-pack/plugins/stack_connectors/server/connector_types/slack_api/index.test.ts +++ b/x-pack/plugins/stack_connectors/server/connector_types/slack_api/index.test.ts @@ -55,23 +55,9 @@ describe('validate config', () => { }).toThrowErrorMatchingInlineSnapshot( `"error validating action type config: [message]: definition for this key is missing"` ); - - expect(() => { - validateConfig(connectorType, { allowedChannels: 'foo' }, { configurationUtilities }); - }).toThrowErrorMatchingInlineSnapshot( - `"error validating action type config: [allowedChannels]: could not parse array value from json input"` - ); }); test('should validate when config are valid', () => { - expect(() => { - validateConfig( - connectorType, - { allowedChannels: ['foo', 'bar'] }, - { configurationUtilities } - ); - }).not.toThrow(); - expect(() => { validateConfig(connectorType, {}, { configurationUtilities }); }).not.toThrow(); diff --git a/x-pack/plugins/stack_connectors/server/connector_types/slack_api/index.ts b/x-pack/plugins/stack_connectors/server/connector_types/slack_api/index.ts index bc3128dc666b8..ffb952f457956 100644 --- a/x-pack/plugins/stack_connectors/server/connector_types/slack_api/index.ts +++ b/x-pack/plugins/stack_connectors/server/connector_types/slack_api/index.ts @@ -105,7 +105,6 @@ const slackApiExecutor = async ({ const externalService = createExternalService( { - config, secrets, }, logger, diff --git a/x-pack/plugins/stack_connectors/server/connector_types/slack_api/service.test.ts b/x-pack/plugins/stack_connectors/server/connector_types/slack_api/service.test.ts index 2f22944ff6480..85df189c72d29 100644 --- a/x-pack/plugins/stack_connectors/server/connector_types/slack_api/service.test.ts +++ b/x-pack/plugins/stack_connectors/server/connector_types/slack_api/service.test.ts @@ -177,50 +177,5 @@ describe('Slack API service', () => { status: 'error', }); }); - - test('should NOT by pass allowed channels when present', async () => { - service = createExternalService( - { - secrets: { token: 'token' }, - config: { allowedChannels: ['foo', 'bar'] }, - }, - logger, - configurationUtilities - ); - - expect( - await service.postMessage({ channels: ['general', 'privat'], text: 'a message' }) - ).toEqual({ - actionId: SLACK_API_CONNECTOR_ID, - serviceMessage: - 'The channel "general,privat" is not included in the allowed channels list "foo,bar"', - message: 'error posting slack message', - status: 'error', - }); - }); - - test('should allowed channels to be persisted', async () => { - service = createExternalService( - { - secrets: { token: 'token' }, - config: { allowedChannels: ['foo', 'bar', 'general', 'privat'] }, - }, - logger, - configurationUtilities - ); - requestMock.mockImplementation(() => postMessageResponse); - - await service.postMessage({ channels: ['general', 'privat'], text: 'a message' }); - - expect(requestMock).toHaveBeenCalledTimes(1); - expect(requestMock).toHaveBeenNthCalledWith(1, { - axios, - logger, - configurationUtilities, - method: 'post', - url: 'chat.postMessage', - data: { channel: 'general', text: 'a message' }, - }); - }); }); }); diff --git a/x-pack/plugins/stack_connectors/server/connector_types/slack_api/service.ts b/x-pack/plugins/stack_connectors/server/connector_types/slack_api/service.ts index a2b6ff8989880..2db25dc52f223 100644 --- a/x-pack/plugins/stack_connectors/server/connector_types/slack_api/service.ts +++ b/x-pack/plugins/stack_connectors/server/connector_types/slack_api/service.ts @@ -106,12 +106,11 @@ const buildSlackExecutorSuccessResponse = ({ }; export const createExternalService = ( - { config, secrets }: { config?: { allowedChannels?: string[] }; secrets: { token: string } }, + { secrets }: { secrets: { token: string } }, logger: Logger, configurationUtilities: ActionsConfigurationUtilities ): SlackApiService => { const { token } = secrets; - const { allowedChannels } = config || { allowedChannels: [] }; if (!token) { throw Error(`[Action][${SLACK_CONNECTOR_NAME}]: Wrong configuration.`); } @@ -170,23 +169,6 @@ export const createExternalService = ( } result.data.channels = channels; const responseData = result.data; - if ((allowedChannels ?? []).length > 0) { - const allowedChannelsList = channels.filter((channel: ChannelsResponse) => - allowedChannels?.includes(channel.name) - ); - allowedChannels?.forEach((ac) => { - if (!allowedChannelsList.find((c: ChannelsResponse) => c.name === ac)) { - allowedChannelsList.push({ - id: '-1', - name: ac, - is_channel: true, - is_archived: false, - is_private: false, - }); - } - }); - responseData.channels = allowedChannelsList; - } return buildSlackExecutorSuccessResponse({ slackApiResponseData: responseData, @@ -201,19 +183,6 @@ export const createExternalService = ( text, }: PostMessageSubActionParams): Promise> => { try { - if ( - allowedChannels && - allowedChannels.length > 0 && - !channels.every((c) => allowedChannels?.includes(c)) - ) { - return buildSlackExecutorErrorResponse({ - slackApiError: { - message: `The channel "${channels.join()}" is not included in the allowed channels list "${allowedChannels.join()}"`, - }, - logger, - }); - } - const result: AxiosResponse = await request({ axios: axiosInstance, method: 'post', diff --git a/x-pack/plugins/stack_connectors/server/plugin.ts b/x-pack/plugins/stack_connectors/server/plugin.ts index 3e76b9adbb083..ce1795b4eb7fb 100644 --- a/x-pack/plugins/stack_connectors/server/plugin.ts +++ b/x-pack/plugins/stack_connectors/server/plugin.ts @@ -5,10 +5,10 @@ * 2.0. */ -import { PluginInitializerContext, Plugin, CoreSetup, Logger } from '@kbn/core/server'; +import { PluginInitializerContext, Plugin, CoreSetup } from '@kbn/core/server'; import { PluginSetupContract as ActionsPluginSetupContract } from '@kbn/actions-plugin/server'; import { registerConnectorTypes } from './connector_types'; -import { getSlackApiChannelsRoute, getWellKnownEmailServiceRoute } from './routes'; +import { getWellKnownEmailServiceRoute } from './routes'; export interface ConnectorsPluginsSetup { actions: ActionsPluginSetupContract; } @@ -18,18 +18,13 @@ export interface ConnectorsPluginsStart { } export class StackConnectorsPlugin implements Plugin { - private readonly logger: Logger; - - constructor(context: PluginInitializerContext) { - this.logger = context.logger.get(); - } + constructor(context: PluginInitializerContext) {} public setup(core: CoreSetup, plugins: ConnectorsPluginsSetup) { const router = core.http.createRouter(); const { actions } = plugins; getWellKnownEmailServiceRoute(router); - getSlackApiChannelsRoute(router, actions.getActionsConfigurationUtilities(), this.logger); registerConnectorTypes({ actions, diff --git a/x-pack/plugins/stack_connectors/server/routes/get_slack_api_channels.ts b/x-pack/plugins/stack_connectors/server/routes/get_slack_api_channels.ts deleted file mode 100644 index dac35c0503cbe..0000000000000 --- a/x-pack/plugins/stack_connectors/server/routes/get_slack_api_channels.ts +++ /dev/null @@ -1,109 +0,0 @@ -/* - * 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; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { schema } from '@kbn/config-schema'; -import { - IRouter, - RequestHandlerContext, - KibanaRequest, - IKibanaResponse, - KibanaResponseFactory, - Logger, -} from '@kbn/core/server'; -import axios, { AxiosResponse } from 'axios'; -import { request } from '@kbn/actions-plugin/server/lib/axios_utils'; -import { ActionsConfigurationUtilities } from '@kbn/actions-plugin/server/actions_config'; -import { INTERNAL_BASE_STACK_CONNECTORS_API_PATH } from '../../common'; -import { SLACK_URL } from '../../common/slack_api/constants'; -import { ChannelsResponse, GetChannelsResponse } from '../../common/slack_api/types'; - -const bodySchema = schema.object({ - authToken: schema.string(), -}); - -const RE_TRY = 5; -const LIMIT = 1000; - -export const getSlackApiChannelsRoute = ( - router: IRouter, - configurationUtilities: ActionsConfigurationUtilities, - logger: Logger -) => { - router.post( - { - path: `${INTERNAL_BASE_STACK_CONNECTORS_API_PATH}/_slack_api/channels`, - validate: { - body: bodySchema, - }, - }, - handler - ); - - async function handler( - ctx: RequestHandlerContext, - req: KibanaRequest, - res: KibanaResponseFactory - ): Promise { - const { authToken } = req.body; - - const axiosInstance = axios.create({ - baseURL: SLACK_URL, - headers: { - Authorization: `Bearer ${authToken}`, - 'Content-type': 'application/json; charset=UTF-8', - }, - }); - - const fetchChannels = (cursor: string = ''): Promise> => { - return request({ - axios: axiosInstance, - configurationUtilities, - logger, - method: 'get', - url: `conversations.list?exclude_archived=true&types=public_channel,private_channel&limit=${LIMIT}${ - cursor.length > 0 ? `&cursor=${cursor}` : '' - }`, - }); - }; - - let numberOfFetch = 0; - let cursor = ''; - const channels: ChannelsResponse[] = []; - let result: AxiosResponse = { - data: { ok: false, channels }, - status: 0, - statusText: '', - headers: {}, - config: {}, - }; - - while (numberOfFetch < RE_TRY) { - result = await fetchChannels(cursor); - if (result.data.ok && (result.data?.channels ?? []).length > 0) { - channels.push(...(result.data?.channels ?? [])); - } - if ( - result.data.ok && - result.data.response_metadata && - result.data.response_metadata.next_cursor && - result.data.response_metadata.next_cursor.length > 0 - ) { - numberOfFetch += 1; - cursor = result.data.response_metadata.next_cursor; - } else { - break; - } - } - - return res.ok({ - body: { - ...result.data, - channels, - }, - }); - } -}; diff --git a/x-pack/plugins/stack_connectors/server/routes/index.ts b/x-pack/plugins/stack_connectors/server/routes/index.ts index df48f18480252..2766b99679845 100644 --- a/x-pack/plugins/stack_connectors/server/routes/index.ts +++ b/x-pack/plugins/stack_connectors/server/routes/index.ts @@ -6,4 +6,3 @@ */ export { getWellKnownEmailServiceRoute } from './get_well_known_email_service'; -export { getSlackApiChannelsRoute } from './get_slack_api_channels'; From 4ce8b3f4ebb602030ad1a7dbbc999e5128ad3daa Mon Sep 17 00:00:00 2001 From: Lisa Cawley Date: Tue, 11 Jul 2023 10:39:30 -0700 Subject: [PATCH 19/61] [DOCS] Edits FormatNumber examples for rule action variables (#161678) --- docs/user/alerting/action-variables.asciidoc | 44 ++++++++++---------- 1 file changed, 21 insertions(+), 23 deletions(-) diff --git a/docs/user/alerting/action-variables.asciidoc b/docs/user/alerting/action-variables.asciidoc index b3a2854642029..5e26dd874f67e 100644 --- a/docs/user/alerting/action-variables.asciidoc +++ b/docs/user/alerting/action-variables.asciidoc @@ -116,14 +116,14 @@ For diagnostic or exploratory purposes, action variables whose values are object For situations where your rule response returns arrays of data, you can loop through the `context`: -[source] +[source,sh] -------------------------------------------------- {{#context}}{{.}}{{/context}} -------------------------------------------------- For example, looping through search result hits: -[source] +[source,sh] -------------------------------------------------- triggering data was: {{#context.hits}} - {{_source.message}} @@ -154,7 +154,7 @@ The following capabilities are available: Mustache lambdas provide additional rendering capabilities for Mustache templates. A Mustache lambda is formatted like a Mustache section. For example: -[source] +[source,sh] ---- {{#EvalMath}} round(context.value, 1) {{/EvalMath}} ---- @@ -169,7 +169,7 @@ The EvalMath lambda will evaluate the text passed to it as <` parameter is required; the `