From a19844c6a3e2ec3b70d80625a94863c03d043024 Mon Sep 17 00:00:00 2001 From: Anton Dosov Date: Tue, 16 Feb 2021 14:12:39 +0100 Subject: [PATCH 01/53] [Search Sessions] Search session UI telemetry (#89950) (#91461) Co-authored-by: Liza K Co-authored-by: Lukas Olson --- ...n-plugins-data-public.searchinterceptor.md | 2 +- ...ns-data-public.searchinterceptor.search.md | 2 +- src/plugins/data/public/index.ts | 1 + src/plugins/data/public/public.api.md | 66 ++++++++++++---- .../collectors/create_usage_collector.test.ts | 62 ++++++++++++++- .../collectors/create_usage_collector.ts | 59 ++++++++++---- .../data/public/search/collectors/mocks.ts | 28 +++++++ .../data/public/search/collectors/types.ts | 76 ++++++++++++++++++- src/plugins/data/public/search/index.ts | 8 +- src/plugins/data/public/search/mocks.ts | 2 + .../data/public/search/search_interceptor.ts | 7 +- src/plugins/data/public/search/types.ts | 2 +- x-pack/plugins/data_enhanced/public/plugin.ts | 12 ++- .../public/search/search_interceptor.test.ts | 44 +---------- .../public/search/search_interceptor.ts | 9 --- .../sessions_mgmt/application/index.tsx | 1 + .../sessions_mgmt/components/main.test.tsx | 10 ++- .../search/sessions_mgmt/components/main.tsx | 2 + .../components/table/table.test.tsx | 13 +++- .../sessions_mgmt/components/table/table.tsx | 10 ++- .../public/search/sessions_mgmt/index.ts | 3 +- .../public/search/sessions_mgmt/lib/api.ts | 13 +++- .../sessions_mgmt/lib/get_columns.test.tsx | 47 +++++++++--- .../search/sessions_mgmt/lib/get_columns.tsx | 11 ++- ...onnected_search_session_indicator.test.tsx | 30 ++++++++ .../connected_search_session_indicator.tsx | 31 ++++++-- .../search_session_tour.tsx | 34 ++++++++- .../search_session_indicator.tsx | 4 + 28 files changed, 463 insertions(+), 126 deletions(-) create mode 100644 src/plugins/data/public/search/collectors/mocks.ts diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchinterceptor.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchinterceptor.md index 5f266e7d8bd8c..2247813562dc7 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchinterceptor.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchinterceptor.md @@ -28,6 +28,6 @@ export declare class SearchInterceptor | --- | --- | --- | | [getTimeoutMode()](./kibana-plugin-plugins-data-public.searchinterceptor.gettimeoutmode.md) | | | | [handleSearchError(e, timeoutSignal, options)](./kibana-plugin-plugins-data-public.searchinterceptor.handlesearcherror.md) | | | -| [search(request, options)](./kibana-plugin-plugins-data-public.searchinterceptor.search.md) | | Searches using the given search method. Overrides the AbortSignal with one that will abort either when cancelPending is called, when the request times out, or when the original AbortSignal is aborted. Updates pendingCount$ when the request is started/finalized. | +| [search(request, options)](./kibana-plugin-plugins-data-public.searchinterceptor.search.md) | | Searches using the given search method. Overrides the AbortSignal with one that will abort either when the request times out, or when the original AbortSignal is aborted. Updates pendingCount$ when the request is started/finalized. | | [showError(e)](./kibana-plugin-plugins-data-public.searchinterceptor.showerror.md) | | | diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchinterceptor.search.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchinterceptor.search.md index 61f8eeb973f4c..a54b43da4add8 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchinterceptor.search.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchinterceptor.search.md @@ -4,7 +4,7 @@ ## SearchInterceptor.search() method -Searches using the given `search` method. Overrides the `AbortSignal` with one that will abort either when `cancelPending` is called, when the request times out, or when the original `AbortSignal` is aborted. Updates `pendingCount$` when the request is started/finalized. +Searches using the given `search` method. Overrides the `AbortSignal` with one that will abort either when the request times out, or when the original `AbortSignal` is aborted. Updates `pendingCount$` when the request is started/finalized. Signature: diff --git a/src/plugins/data/public/index.ts b/src/plugins/data/public/index.ts index 83a248ee2c3de..df799ede08a31 100644 --- a/src/plugins/data/public/index.ts +++ b/src/plugins/data/public/index.ts @@ -389,6 +389,7 @@ export type { ISessionService, SearchSessionInfoProvider, ISessionsClient, + SearchUsageCollector, } from './search'; export { ISearchOptions, isErrorResponse, isCompleteResponse, isPartialResponse } from '../common'; diff --git a/src/plugins/data/public/public.api.md b/src/plugins/data/public/public.api.md index 782f6f45eadc7..e904bbece7b19 100644 --- a/src/plugins/data/public/public.api.md +++ b/src/plugins/data/public/public.api.md @@ -1669,7 +1669,7 @@ export interface ISearchSetup { aggs: AggsSetup; session: ISessionService; sessionsClient: ISessionsClient; - // Warning: (ae-forgotten-export) The symbol "SearchUsageCollector" needs to be exported by the entry point index.d.ts + // Warning: (ae-incompatible-release-tags) The symbol "usageCollector" is marked as @public, but its signature references "SearchUsageCollector" which is marked as @internal // // (undocumented) usageCollector?: SearchUsageCollector; @@ -2337,6 +2337,8 @@ export interface SearchInterceptorDeps { toasts: ToastsSetup; // (undocumented) uiSettings: CoreSetup_2['uiSettings']; + // Warning: (ae-incompatible-release-tags) The symbol "usageCollector" is marked as @public, but its signature references "SearchUsageCollector" which is marked as @internal + // // (undocumented) usageCollector?: SearchUsageCollector; } @@ -2461,6 +2463,38 @@ export class SearchTimeoutError extends KbnError { mode: TimeoutErrorMode; } +// @internal (undocumented) +export interface SearchUsageCollector { + // (undocumented) + trackQueryTimedOut: () => Promise; + // (undocumented) + trackSessionCancelled: () => Promise; + // (undocumented) + trackSessionDeleted: () => Promise; + // (undocumented) + trackSessionExtended: () => Promise; + // (undocumented) + trackSessionIndicatorSaveDisabled: () => Promise; + // (undocumented) + trackSessionIndicatorTourLoading: () => Promise; + // (undocumented) + trackSessionIndicatorTourRestored: () => Promise; + // (undocumented) + trackSessionIsRestored: () => Promise; + // (undocumented) + trackSessionReloaded: () => Promise; + // (undocumented) + trackSessionSavedResults: () => Promise; + // (undocumented) + trackSessionSentToBackground: () => Promise; + // (undocumented) + trackSessionsListLoaded: () => Promise; + // (undocumented) + trackSessionViewRestored: () => Promise; + // (undocumented) + trackViewSessionsList: () => Promise; +} + // Warning: (ae-missing-release-tag) "SortDirection" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) @@ -2615,21 +2649,21 @@ export const UI_SETTINGS: { // src/plugins/data/public/index.ts:234:27 - (ae-forgotten-export) The symbol "validateIndexPattern" needs to be exported by the entry point index.d.ts // src/plugins/data/public/index.ts:234:27 - (ae-forgotten-export) The symbol "flattenHitWrapper" needs to be exported by the entry point index.d.ts // src/plugins/data/public/index.ts:234:27 - (ae-forgotten-export) The symbol "formatHitProvider" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:397:20 - (ae-forgotten-export) The symbol "getRequestInspectorStats" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:397:20 - (ae-forgotten-export) The symbol "getResponseInspectorStats" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:397:20 - (ae-forgotten-export) The symbol "tabifyAggResponse" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:397:20 - (ae-forgotten-export) The symbol "tabifyGetColumns" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:399:1 - (ae-forgotten-export) The symbol "CidrMask" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:400:1 - (ae-forgotten-export) The symbol "dateHistogramInterval" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:409:1 - (ae-forgotten-export) The symbol "InvalidEsCalendarIntervalError" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:410:1 - (ae-forgotten-export) The symbol "InvalidEsIntervalFormatError" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:411:1 - (ae-forgotten-export) The symbol "Ipv4Address" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:412:1 - (ae-forgotten-export) The symbol "isDateHistogramBucketAggConfig" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:416:1 - (ae-forgotten-export) The symbol "isValidEsInterval" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:417:1 - (ae-forgotten-export) The symbol "isValidInterval" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:420:1 - (ae-forgotten-export) The symbol "parseInterval" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:421:1 - (ae-forgotten-export) The symbol "propFilter" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:424:1 - (ae-forgotten-export) The symbol "toAbsoluteDates" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:398:20 - (ae-forgotten-export) The symbol "getRequestInspectorStats" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:398:20 - (ae-forgotten-export) The symbol "getResponseInspectorStats" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:398:20 - (ae-forgotten-export) The symbol "tabifyAggResponse" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:398:20 - (ae-forgotten-export) The symbol "tabifyGetColumns" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:400:1 - (ae-forgotten-export) The symbol "CidrMask" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:401:1 - (ae-forgotten-export) The symbol "dateHistogramInterval" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:410:1 - (ae-forgotten-export) The symbol "InvalidEsCalendarIntervalError" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:411:1 - (ae-forgotten-export) The symbol "InvalidEsIntervalFormatError" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:412:1 - (ae-forgotten-export) The symbol "Ipv4Address" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:413:1 - (ae-forgotten-export) The symbol "isDateHistogramBucketAggConfig" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:417:1 - (ae-forgotten-export) The symbol "isValidEsInterval" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:418:1 - (ae-forgotten-export) The symbol "isValidInterval" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:421:1 - (ae-forgotten-export) The symbol "parseInterval" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:422:1 - (ae-forgotten-export) The symbol "propFilter" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:425:1 - (ae-forgotten-export) The symbol "toAbsoluteDates" needs to be exported by the entry point index.d.ts // src/plugins/data/public/query/state_sync/connect_to_query_state.ts:34:5 - (ae-forgotten-export) The symbol "FilterStateStore" needs to be exported by the entry point index.d.ts // src/plugins/data/public/search/session/session_service.ts:42:5 - (ae-forgotten-export) The symbol "UrlGeneratorStateMapping" needs to be exported by the entry point index.d.ts diff --git a/src/plugins/data/public/search/collectors/create_usage_collector.test.ts b/src/plugins/data/public/search/collectors/create_usage_collector.test.ts index df9903a4683e1..145bb191fde11 100644 --- a/src/plugins/data/public/search/collectors/create_usage_collector.test.ts +++ b/src/plugins/data/public/search/collectors/create_usage_collector.test.ts @@ -45,12 +45,66 @@ describe('Search Usage Collector', () => { ); }); - test('tracks query cancellation', async () => { - await usageCollector.trackQueriesCancelled(); + test('tracks session sent to background', async () => { + await usageCollector.trackSessionSentToBackground(); expect(mockUsageCollectionSetup.reportUiCounter).toHaveBeenCalled(); - expect(mockUsageCollectionSetup.reportUiCounter.mock.calls[0][1]).toBe(METRIC_TYPE.LOADED); + expect(mockUsageCollectionSetup.reportUiCounter.mock.calls[0][1]).toBe(METRIC_TYPE.CLICK); + expect(mockUsageCollectionSetup.reportUiCounter.mock.calls[0][2]).toBe( + SEARCH_EVENT_TYPE.SESSION_SENT_TO_BACKGROUND + ); + }); + + test('tracks session saved results', async () => { + await usageCollector.trackSessionSavedResults(); + expect(mockUsageCollectionSetup.reportUiCounter).toHaveBeenCalled(); + expect(mockUsageCollectionSetup.reportUiCounter.mock.calls[0][1]).toBe(METRIC_TYPE.CLICK); + expect(mockUsageCollectionSetup.reportUiCounter.mock.calls[0][2]).toBe( + SEARCH_EVENT_TYPE.SESSION_SAVED_RESULTS + ); + }); + + test('tracks session view restored', async () => { + await usageCollector.trackSessionViewRestored(); + expect(mockUsageCollectionSetup.reportUiCounter).toHaveBeenCalled(); + expect(mockUsageCollectionSetup.reportUiCounter.mock.calls[0][1]).toBe(METRIC_TYPE.CLICK); + expect(mockUsageCollectionSetup.reportUiCounter.mock.calls[0][2]).toBe( + SEARCH_EVENT_TYPE.SESSION_VIEW_RESTORED + ); + }); + + test('tracks session is restored', async () => { + await usageCollector.trackSessionIsRestored(); + expect(mockUsageCollectionSetup.reportUiCounter).toHaveBeenCalled(); + expect(mockUsageCollectionSetup.reportUiCounter.mock.calls[0][1]).toBe(METRIC_TYPE.CLICK); + expect(mockUsageCollectionSetup.reportUiCounter.mock.calls[0][2]).toBe( + SEARCH_EVENT_TYPE.SESSION_IS_RESTORED + ); + }); + + test('tracks session reloaded', async () => { + await usageCollector.trackSessionReloaded(); + expect(mockUsageCollectionSetup.reportUiCounter).toHaveBeenCalled(); + expect(mockUsageCollectionSetup.reportUiCounter.mock.calls[0][1]).toBe(METRIC_TYPE.CLICK); + expect(mockUsageCollectionSetup.reportUiCounter.mock.calls[0][2]).toBe( + SEARCH_EVENT_TYPE.SESSION_RELOADED + ); + }); + + test('tracks session extended', async () => { + await usageCollector.trackSessionExtended(); + expect(mockUsageCollectionSetup.reportUiCounter).toHaveBeenCalled(); + expect(mockUsageCollectionSetup.reportUiCounter.mock.calls[0][1]).toBe(METRIC_TYPE.CLICK); + expect(mockUsageCollectionSetup.reportUiCounter.mock.calls[0][2]).toBe( + SEARCH_EVENT_TYPE.SESSION_EXTENDED + ); + }); + + test('tracks session cancelled', async () => { + await usageCollector.trackSessionCancelled(); + expect(mockUsageCollectionSetup.reportUiCounter).toHaveBeenCalled(); + expect(mockUsageCollectionSetup.reportUiCounter.mock.calls[0][1]).toBe(METRIC_TYPE.CLICK); expect(mockUsageCollectionSetup.reportUiCounter.mock.calls[0][2]).toBe( - SEARCH_EVENT_TYPE.QUERIES_CANCELLED + SEARCH_EVENT_TYPE.SESSION_CANCELLED ); }); }); diff --git a/src/plugins/data/public/search/collectors/create_usage_collector.ts b/src/plugins/data/public/search/collectors/create_usage_collector.ts index e9a192a2710c4..3fe135ea29152 100644 --- a/src/plugins/data/public/search/collectors/create_usage_collector.ts +++ b/src/plugins/data/public/search/collectors/create_usage_collector.ts @@ -7,6 +7,7 @@ */ import { first } from 'rxjs/operators'; +import { UiCounterMetricType } from '@kbn/analytics'; import { StartServicesAccessor } from '../../../../../core/public'; import { METRIC_TYPE, UsageCollectionSetup } from '../../../../usage_collection/public'; import { SEARCH_EVENT_TYPE, SearchUsageCollector } from './types'; @@ -20,22 +21,48 @@ export const createUsageCollector = ( return application.currentAppId$.pipe(first()).toPromise(); }; - return { - trackQueryTimedOut: async () => { - const currentApp = await getCurrentApp(); - return usageCollection?.reportUiCounter( - currentApp!, - METRIC_TYPE.LOADED, - SEARCH_EVENT_TYPE.QUERY_TIMED_OUT - ); - }, - trackQueriesCancelled: async () => { + const getCollector = (metricType: UiCounterMetricType, eventType: SEARCH_EVENT_TYPE) => { + return async () => { const currentApp = await getCurrentApp(); - return usageCollection?.reportUiCounter( - currentApp!, - METRIC_TYPE.LOADED, - SEARCH_EVENT_TYPE.QUERIES_CANCELLED - ); - }, + return usageCollection?.reportUiCounter(currentApp!, metricType, eventType); + }; + }; + + return { + trackQueryTimedOut: getCollector(METRIC_TYPE.LOADED, SEARCH_EVENT_TYPE.QUERY_TIMED_OUT), + trackSessionIndicatorTourLoading: getCollector( + METRIC_TYPE.LOADED, + SEARCH_EVENT_TYPE.SESSION_INDICATOR_TOUR_LOADING + ), + trackSessionIndicatorTourRestored: getCollector( + METRIC_TYPE.LOADED, + SEARCH_EVENT_TYPE.SESSION_INDICATOR_TOUR_RESTORED + ), + trackSessionIndicatorSaveDisabled: getCollector( + METRIC_TYPE.LOADED, + SEARCH_EVENT_TYPE.SESSION_INDICATOR_SAVE_DISABLED + ), + trackSessionSentToBackground: getCollector( + METRIC_TYPE.CLICK, + SEARCH_EVENT_TYPE.SESSION_SENT_TO_BACKGROUND + ), + trackSessionSavedResults: getCollector( + METRIC_TYPE.CLICK, + SEARCH_EVENT_TYPE.SESSION_SAVED_RESULTS + ), + trackSessionViewRestored: getCollector( + METRIC_TYPE.CLICK, + SEARCH_EVENT_TYPE.SESSION_VIEW_RESTORED + ), + trackSessionIsRestored: getCollector(METRIC_TYPE.CLICK, SEARCH_EVENT_TYPE.SESSION_IS_RESTORED), + trackSessionReloaded: getCollector(METRIC_TYPE.CLICK, SEARCH_EVENT_TYPE.SESSION_RELOADED), + trackSessionExtended: getCollector(METRIC_TYPE.CLICK, SEARCH_EVENT_TYPE.SESSION_EXTENDED), + trackSessionCancelled: getCollector(METRIC_TYPE.CLICK, SEARCH_EVENT_TYPE.SESSION_CANCELLED), + trackSessionDeleted: getCollector(METRIC_TYPE.CLICK, SEARCH_EVENT_TYPE.SESSION_DELETED), + trackViewSessionsList: getCollector(METRIC_TYPE.CLICK, SEARCH_EVENT_TYPE.SESSION_VIEW_LIST), + trackSessionsListLoaded: getCollector( + METRIC_TYPE.LOADED, + SEARCH_EVENT_TYPE.SESSIONS_LIST_LOADED + ), }; }; diff --git a/src/plugins/data/public/search/collectors/mocks.ts b/src/plugins/data/public/search/collectors/mocks.ts new file mode 100644 index 0000000000000..2a546d6310d7f --- /dev/null +++ b/src/plugins/data/public/search/collectors/mocks.ts @@ -0,0 +1,28 @@ +/* + * 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 { SearchUsageCollector } from './types'; + +export function createSearchUsageCollectorMock(): jest.Mocked { + return { + trackQueryTimedOut: jest.fn(), + trackSessionIndicatorTourLoading: jest.fn(), + trackSessionIndicatorTourRestored: jest.fn(), + trackSessionIndicatorSaveDisabled: jest.fn(), + trackSessionSentToBackground: jest.fn(), + trackSessionSavedResults: jest.fn(), + trackSessionViewRestored: jest.fn(), + trackSessionIsRestored: jest.fn(), + trackSessionReloaded: jest.fn(), + trackSessionExtended: jest.fn(), + trackSessionCancelled: jest.fn(), + trackSessionDeleted: jest.fn(), + trackViewSessionsList: jest.fn(), + trackSessionsListLoaded: jest.fn(), + }; +} diff --git a/src/plugins/data/public/search/collectors/types.ts b/src/plugins/data/public/search/collectors/types.ts index 9668b4dcbefa2..49c240d1ccb16 100644 --- a/src/plugins/data/public/search/collectors/types.ts +++ b/src/plugins/data/public/search/collectors/types.ts @@ -6,12 +6,84 @@ * Side Public License, v 1. */ +/** + * @internal + */ export enum SEARCH_EVENT_TYPE { + /** + * A search reached the timeout configured in UI setting search:timeout + */ QUERY_TIMED_OUT = 'queryTimedOut', - QUERIES_CANCELLED = 'queriesCancelled', + /** + * The session indicator was automatically brought up because of a long running query + */ + SESSION_INDICATOR_TOUR_LOADING = 'sessionIndicatorTourLoading', + /** + * The session indicator was automatically brought up because of a restored session + */ + SESSION_INDICATOR_TOUR_RESTORED = 'sessionIndicatorTourRestored', + /** + * The session indicator was disabled because of a completion timeout + */ + SESSION_INDICATOR_SAVE_DISABLED = 'sessionIndicatorSaveDisabled', + /** + * The user clicked to continue a session in the background (prior to results completing) + */ + SESSION_SENT_TO_BACKGROUND = 'sessionSentToBackground', + /** + * The user clicked to save the session (after results completing) + */ + SESSION_SAVED_RESULTS = 'sessionSavedResults', + /** + * The user clicked to view a completed session + */ + SESSION_VIEW_RESTORED = 'sessionViewRestored', + /** + * The session was successfully restored upon a user navigating + */ + SESSION_IS_RESTORED = 'sessionIsRestored', + /** + * The user clicked to reload an expired/cancelled session + */ + SESSION_RELOADED = 'sessionReloaded', + /** + * The user clicked to extend the expiration of a session + */ + SESSION_EXTENDED = 'sessionExtended', + /** + * The user clicked to cancel a session + */ + SESSION_CANCELLED = 'sessionCancelled', + /** + * The user clicked to delete a session + */ + SESSION_DELETED = 'sessionDeleted', + /** + * The user clicked a link to view the list of sessions + */ + SESSION_VIEW_LIST = 'sessionViewList', + /** + * The user landed on the sessions management page + */ + SESSIONS_LIST_LOADED = 'sessionsListLoaded', } +/** + * @internal + */ export interface SearchUsageCollector { trackQueryTimedOut: () => Promise; - trackQueriesCancelled: () => Promise; + trackSessionIndicatorTourLoading: () => Promise; + trackSessionIndicatorTourRestored: () => Promise; + trackSessionIndicatorSaveDisabled: () => Promise; + trackSessionSentToBackground: () => Promise; + trackSessionSavedResults: () => Promise; + trackSessionViewRestored: () => Promise; + trackSessionIsRestored: () => Promise; + trackSessionReloaded: () => Promise; + trackSessionExtended: () => Promise; + trackSessionCancelled: () => Promise; + trackSessionDeleted: () => Promise; + trackViewSessionsList: () => Promise; + trackSessionsListLoaded: () => Promise; } diff --git a/src/plugins/data/public/search/index.ts b/src/plugins/data/public/search/index.ts index b1e0bc490823a..fded4c46992c0 100644 --- a/src/plugins/data/public/search/index.ts +++ b/src/plugins/data/public/search/index.ts @@ -8,7 +8,13 @@ export * from './expressions'; -export { ISearchSetup, ISearchStart, ISearchStartSearchSource, SearchEnhancements } from './types'; +export { + ISearchSetup, + ISearchStart, + ISearchStartSearchSource, + SearchEnhancements, + SearchUsageCollector, +} from './types'; export { ES_SEARCH_STRATEGY, diff --git a/src/plugins/data/public/search/mocks.ts b/src/plugins/data/public/search/mocks.ts index b16468120d95a..273bbfe9e7b08 100644 --- a/src/plugins/data/public/search/mocks.ts +++ b/src/plugins/data/public/search/mocks.ts @@ -10,6 +10,7 @@ import { searchAggsSetupMock, searchAggsStartMock } from './aggs/mocks'; import { searchSourceMock } from './search_source/mocks'; import { ISearchSetup, ISearchStart } from './types'; import { getSessionsClientMock, getSessionServiceMock } from './session/mocks'; +import { createSearchUsageCollectorMock } from './collectors/mocks'; function createSetupContract(): jest.Mocked { return { @@ -17,6 +18,7 @@ function createSetupContract(): jest.Mocked { __enhance: jest.fn(), session: getSessionServiceMock(), sessionsClient: getSessionsClientMock(), + usageCollector: createSearchUsageCollectorMock(), }; } diff --git a/src/plugins/data/public/search/search_interceptor.ts b/src/plugins/data/public/search/search_interceptor.ts index f33740cc45bf9..f46a3d258f948 100644 --- a/src/plugins/data/public/search/search_interceptor.ts +++ b/src/plugins/data/public/search/search_interceptor.ts @@ -155,13 +155,14 @@ export class SearchInterceptor { const { signal: timeoutSignal } = timeoutController; const timeout$ = timeout ? timer(timeout) : NEVER; const subscription = timeout$.subscribe(() => { + this.deps.usageCollector?.trackQueryTimedOut(); timeoutController.abort(); }); const selfAbortController = new AbortController(); // Get a combined `AbortSignal` that will be aborted whenever the first of the following occurs: - // 1. The user manually aborts (via `cancelPending`) + // 1. The internal abort controller aborts // 2. The request times out // 3. abort() is called on `selfAbortController`. This is used by session service to abort all pending searches that it tracks // in the current session @@ -221,8 +222,8 @@ export class SearchInterceptor { /** * Searches using the given `search` method. Overrides the `AbortSignal` with one that will abort - * either when `cancelPending` is called, when the request times out, or when the original - * `AbortSignal` is aborted. Updates `pendingCount$` when the request is started/finalized. + * either when the request times out, or when the original `AbortSignal` is aborted. Updates + * `pendingCount$` when the request is started/finalized. * * @param request * @options diff --git a/src/plugins/data/public/search/types.ts b/src/plugins/data/public/search/types.ts index 01f5cf3de38bd..391be8e053746 100644 --- a/src/plugins/data/public/search/types.ts +++ b/src/plugins/data/public/search/types.ts @@ -15,7 +15,7 @@ import { IndexPatternsContract } from '../../common/index_patterns/index_pattern import { UsageCollectionSetup } from '../../../usage_collection/public'; import { ISessionsClient, ISessionService } from './session'; -export { ISearchStartSearchSource }; +export { ISearchStartSearchSource, SearchUsageCollector }; export interface SearchEnhancements { searchInterceptor: ISearchInterceptor; diff --git a/x-pack/plugins/data_enhanced/public/plugin.ts b/x-pack/plugins/data_enhanced/public/plugin.ts index 0a116545e6e36..29f3494433bef 100644 --- a/x-pack/plugins/data_enhanced/public/plugin.ts +++ b/x-pack/plugins/data_enhanced/public/plugin.ts @@ -8,7 +8,11 @@ import React from 'react'; import moment from 'moment'; import { CoreSetup, CoreStart, Plugin, PluginInitializerContext } from 'src/core/public'; -import { DataPublicPluginSetup, DataPublicPluginStart } from '../../../../src/plugins/data/public'; +import { + DataPublicPluginSetup, + DataPublicPluginStart, + SearchUsageCollector, +} from '../../../../src/plugins/data/public'; import { BfetchPublicSetup } from '../../../../src/plugins/bfetch/public'; import { ManagementSetup } from '../../../../src/plugins/management/public'; import { SharePluginStart } from '../../../../src/plugins/share/public'; @@ -40,6 +44,7 @@ export class DataEnhancedPlugin private enhancedSearchInterceptor!: EnhancedSearchInterceptor; private config!: ConfigSchema; private readonly storage = new Storage(window.localStorage); + private usageCollector?: SearchUsageCollector; constructor(private initializerContext: PluginInitializerContext) {} @@ -71,8 +76,10 @@ export class DataEnhancedPlugin this.config = this.initializerContext.config.get(); if (this.config.search.sessions.enabled) { const sessionsConfig = this.config.search.sessions; - registerSearchSessionsMgmt(core, sessionsConfig, { management }); + registerSearchSessionsMgmt(core, sessionsConfig, { data, management }); } + + this.usageCollector = data.search.usageCollector; } public start(core: CoreStart, plugins: DataEnhancedStartDependencies) { @@ -90,6 +97,7 @@ export class DataEnhancedPlugin disableSaveAfterSessionCompletesTimeout: moment .duration(this.config.search.sessions.notTouchedTimeout) .asMilliseconds(), + usageCollector: this.usageCollector, }) ) ), diff --git a/x-pack/plugins/data_enhanced/public/search/search_interceptor.test.ts b/x-pack/plugins/data_enhanced/public/search/search_interceptor.test.ts index 04a777b9b6897..02671974e5053 100644 --- a/x-pack/plugins/data_enhanced/public/search/search_interceptor.test.ts +++ b/x-pack/plugins/data_enhanced/public/search/search_interceptor.test.ts @@ -16,6 +16,7 @@ import { SearchTimeoutError, SearchSessionState, PainlessError, + DataPublicPluginSetup, } from 'src/plugins/data/public'; import { dataPluginMock } from '../../../../../src/plugins/data/public/mocks'; import { bfetchPluginMock } from '../../../../../src/plugins/bfetch/public/mocks'; @@ -51,14 +52,15 @@ function mockFetchImplementation(responses: any[]) { } describe('EnhancedSearchInterceptor', () => { - let mockUsageCollector: any; let sessionService: jest.Mocked; let sessionState$: BehaviorSubject; + let dataPluginMockSetup: DataPublicPluginSetup; beforeEach(() => { mockCoreSetup = coreMock.createSetup(); mockCoreStart = coreMock.createStart(); sessionState$ = new BehaviorSubject(SearchSessionState.None); + dataPluginMockSetup = dataPluginMock.createSetupContract(); const dataPluginMockStart = dataPluginMock.createStartContract(); sessionService = { ...(dataPluginMockStart.search.session as jest.Mocked), @@ -80,11 +82,6 @@ describe('EnhancedSearchInterceptor', () => { complete.mockClear(); jest.clearAllTimers(); - mockUsageCollector = { - trackQueryTimedOut: jest.fn(), - trackQueriesCancelled: jest.fn(), - }; - const mockPromise = new Promise((resolve) => { resolve([ { @@ -102,7 +99,7 @@ describe('EnhancedSearchInterceptor', () => { startServices: mockPromise as any, http: mockCoreSetup.http, uiSettings: mockCoreSetup.uiSettings, - usageCollector: mockUsageCollector, + usageCollector: dataPluginMockSetup.search.usageCollector, session: sessionService, }); }); @@ -455,39 +452,6 @@ describe('EnhancedSearchInterceptor', () => { }); }); - describe('cancelPending', () => { - test('should abort all pending requests', async () => { - mockFetchImplementation([ - { - time: 10, - value: { - isPartial: false, - isRunning: false, - id: 1, - }, - }, - { - time: 20, - value: { - isPartial: false, - isRunning: false, - id: 1, - }, - }, - ]); - - searchInterceptor.search({}).subscribe({ next, error }); - searchInterceptor.search({}).subscribe({ next, error }); - searchInterceptor.cancelPending(); - - await timeTravel(); - - const areAllRequestsAborted = fetchMock.mock.calls.every(([_, signal]) => signal?.aborted); - expect(areAllRequestsAborted).toBe(true); - expect(mockUsageCollector.trackQueriesCancelled).toBeCalledTimes(1); - }); - }); - describe('session', () => { beforeEach(() => { const responses = [ diff --git a/x-pack/plugins/data_enhanced/public/search/search_interceptor.ts b/x-pack/plugins/data_enhanced/public/search/search_interceptor.ts index f211021e45773..0dfec1a35d900 100644 --- a/x-pack/plugins/data_enhanced/public/search/search_interceptor.ts +++ b/x-pack/plugins/data_enhanced/public/search/search_interceptor.ts @@ -46,15 +46,6 @@ export class EnhancedSearchInterceptor extends SearchInterceptor { : TimeoutErrorMode.CONTACT; } - /** - * Abort our `AbortController`, which in turn aborts any intercepted searches. - */ - public cancelPending = () => { - this.abortController.abort(); - this.abortController = new AbortController(); - if (this.deps.usageCollector) this.deps.usageCollector.trackQueriesCancelled(); - }; - public search({ id, ...request }: IKibanaSearchRequest, options: IAsyncSearchOptions = {}) { const { combinedSignal, timeoutSignal, cleanup, abort } = this.setupAbortSignal({ abortSignal: options.abortSignal, diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/application/index.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/application/index.tsx index 177cfbbb4fd7e..2dfca534c20b5 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/application/index.tsx +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/application/index.tsx @@ -50,6 +50,7 @@ export class SearchSessionsMgmtApp { notifications, urls: share.urlGenerators, application, + usageCollector: pluginsSetup.data.search.usageCollector, }); const documentation = new AsyncSearchIntroDocumentation(docLinks); diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/main.test.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/main.test.tsx index 1f8f603400c9f..6b94eccc4e707 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/main.test.tsx +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/main.test.tsx @@ -13,14 +13,17 @@ import React from 'react'; import { act } from 'react-dom/test-utils'; import { coreMock } from 'src/core/public/mocks'; import { SessionsClient } from 'src/plugins/data/public/search'; -import { SessionsConfigSchema } from '..'; +import { IManagementSectionsPluginsSetup, SessionsConfigSchema } from '..'; import { SearchSessionsMgmtAPI } from '../lib/api'; import { AsyncSearchIntroDocumentation } from '../lib/documentation'; import { LocaleWrapper, mockUrls } from '../__mocks__'; import { SearchSessionsMgmtMain } from './main'; +import { dataPluginMock } from '../../../../../../../src/plugins/data/public/mocks'; +import { managementPluginMock } from '../../../../../../../src/plugins/management/public/mocks'; let mockCoreSetup: MockedKeys; let mockCoreStart: MockedKeys; +let mockPluginsSetup: IManagementSectionsPluginsSetup; let mockConfig: SessionsConfigSchema; let sessionsClient: SessionsClient; let api: SearchSessionsMgmtAPI; @@ -29,6 +32,10 @@ describe('Background Search Session Management Main', () => { beforeEach(() => { mockCoreSetup = coreMock.createSetup(); mockCoreStart = coreMock.createStart(); + mockPluginsSetup = { + data: dataPluginMock.createSetupContract(), + management: managementPluginMock.createSetupContract(), + }; mockConfig = { defaultExpiration: moment.duration('7d'), management: { @@ -67,6 +74,7 @@ describe('Background Search Session Management Main', () => { ; let mockCoreStart: CoreStart; +let mockPluginsSetup: IManagementSectionsPluginsSetup; let mockConfig: SessionsConfigSchema; let sessionsClient: SessionsClient; let api: SearchSessionsMgmtAPI; @@ -29,6 +32,10 @@ describe('Background Search Session Management Table', () => { beforeEach(async () => { mockCoreSetup = coreMock.createSetup(); mockCoreStart = coreMock.createStart(); + mockPluginsSetup = { + data: dataPluginMock.createSetupContract(), + management: managementPluginMock.createSetupContract(), + }; mockConfig = { defaultExpiration: moment.duration('7d'), management: { @@ -79,6 +86,7 @@ describe('Background Search Session Management Table', () => { { { { ([]); const [isLoading, setIsLoading] = useState(false); const [debouncedIsLoading, setDebouncedIsLoading] = useState(false); @@ -71,7 +72,8 @@ export function SearchSessionsMgmtTable({ core, api, timezone, config, ...props // initial data load useEffect(() => { doRefresh(); - }, [doRefresh]); + plugins.data.search.usageCollector?.trackSessionsListLoaded(); + }, [doRefresh, plugins]); useInterval(doRefresh, refreshInterval); @@ -110,7 +112,7 @@ export function SearchSessionsMgmtTable({ core, api, timezone, config, ...props rowProps={() => ({ 'data-test-subj': 'searchSessionsRow', })} - columns={getColumns(core, api, config, timezone, onActionComplete)} + columns={getColumns(core, plugins, api, config, timezone, onActionComplete)} items={tableData} pagination={pagination} search={search} diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/index.ts b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/index.ts index e916eed6bcbc4..0ac8fa798cc92 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/index.ts +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/index.ts @@ -8,7 +8,7 @@ import { i18n } from '@kbn/i18n'; import type { CoreStart, HttpStart, I18nStart, IUiSettingsClient } from 'kibana/public'; import { CoreSetup } from 'kibana/public'; -import type { DataPublicPluginStart } from 'src/plugins/data/public'; +import type { DataPublicPluginSetup, DataPublicPluginStart } from 'src/plugins/data/public'; import type { ManagementSetup } from 'src/plugins/management/public'; import type { SharePluginStart } from 'src/plugins/share/public'; import type { ConfigSchema } from '../../../config'; @@ -18,6 +18,7 @@ import type { AsyncSearchIntroDocumentation } from './lib/documentation'; import { SEARCH_SESSIONS_MANAGEMENT_ID } from '../../../../../../src/plugins/data/public'; export interface IManagementSectionsPluginsSetup { + data: DataPublicPluginSetup; management: ManagementSetup; } diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/api.ts b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/api.ts index 39da58cb76918..838b51994aa71 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/api.ts +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/api.ts @@ -11,7 +11,10 @@ import moment from 'moment'; import { from, race, timer } from 'rxjs'; import { mapTo, tap } from 'rxjs/operators'; import type { SharePluginStart } from 'src/plugins/share/public'; -import { ISessionsClient } from '../../../../../../../src/plugins/data/public'; +import { + ISessionsClient, + SearchUsageCollector, +} from '../../../../../../../src/plugins/data/public'; import { SearchSessionStatus } from '../../../../common/search'; import { ACTION } from '../components/actions'; import { PersistedSearchSessionSavedObjectAttributes, UISession } from '../types'; @@ -84,17 +87,18 @@ const mapToUISession = (urls: UrlGeneratorsStart, config: SessionsConfigSchema) }; }; -interface SearcgSessuibManagementDeps { +interface SearchSessionManagementDeps { urls: UrlGeneratorsStart; notifications: NotificationsStart; application: ApplicationStart; + usageCollector?: SearchUsageCollector; } export class SearchSessionsMgmtAPI { constructor( private sessionsClient: ISessionsClient, private config: SessionsConfigSchema, - private deps: SearcgSessuibManagementDeps + private deps: SearchSessionManagementDeps ) {} public async fetchTableData(): Promise { @@ -151,6 +155,7 @@ export class SearchSessionsMgmtAPI { } public reloadSearchSession(reloadUrl: string) { + this.deps.usageCollector?.trackSessionReloaded(); this.deps.application.navigateToUrl(reloadUrl); } @@ -160,6 +165,7 @@ export class SearchSessionsMgmtAPI { // Cancel and expire public async sendCancel(id: string): Promise { + this.deps.usageCollector?.trackSessionDeleted(); try { await this.sessionsClient.delete(id); @@ -179,6 +185,7 @@ export class SearchSessionsMgmtAPI { // Extend public async sendExtend(id: string, expires: string): Promise { + this.deps.usageCollector?.trackSessionExtended(); try { await this.sessionsClient.extend(id, expires); diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_columns.test.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_columns.test.tsx index fc0a8849006d3..29f0033aaf012 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_columns.test.tsx +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_columns.test.tsx @@ -13,16 +13,19 @@ import moment from 'moment'; import { ReactElement } from 'react'; import { coreMock } from 'src/core/public/mocks'; import { SessionsClient } from 'src/plugins/data/public/search'; -import { SessionsConfigSchema } from '../'; +import { IManagementSectionsPluginsSetup, SessionsConfigSchema } from '../'; import { SearchSessionStatus } from '../../../../common/search'; import { OnActionComplete } from '../components'; import { UISession } from '../types'; import { mockUrls } from '../__mocks__'; import { SearchSessionsMgmtAPI } from './api'; import { getColumns } from './get_columns'; +import { dataPluginMock } from '../../../../../../../src/plugins/data/public/mocks'; +import { managementPluginMock } from '../../../../../../../src/plugins/management/public/mocks'; let mockCoreSetup: MockedKeys; let mockCoreStart: CoreStart; +let mockPluginsSetup: IManagementSectionsPluginsSetup; let mockConfig: SessionsConfigSchema; let api: SearchSessionsMgmtAPI; let sessionsClient: SessionsClient; @@ -35,6 +38,10 @@ describe('Search Sessions Management table column factory', () => { beforeEach(async () => { mockCoreSetup = coreMock.createSetup(); mockCoreStart = coreMock.createStart(); + mockPluginsSetup = { + data: dataPluginMock.createSetupContract(), + management: managementPluginMock.createSetupContract(), + }; mockConfig = { defaultExpiration: moment.duration('7d'), management: { @@ -72,7 +79,7 @@ describe('Search Sessions Management table column factory', () => { }); test('returns columns', () => { - const columns = getColumns(mockCoreStart, api, mockConfig, tz, handleAction); + const columns = getColumns(mockCoreStart, mockPluginsSetup, api, mockConfig, tz, handleAction); expect(columns).toMatchInlineSnapshot(` Array [ Object { @@ -124,9 +131,14 @@ describe('Search Sessions Management table column factory', () => { describe('name', () => { test('rendering', () => { - const [, nameColumn] = getColumns(mockCoreStart, api, mockConfig, tz, handleAction) as Array< - EuiTableFieldDataColumnType - >; + const [, nameColumn] = getColumns( + mockCoreStart, + mockPluginsSetup, + api, + mockConfig, + tz, + handleAction + ) as Array>; const name = mount(nameColumn.render!(mockSession.name, mockSession) as ReactElement); @@ -137,9 +149,14 @@ describe('Search Sessions Management table column factory', () => { // Status column describe('status', () => { test('render in_progress', () => { - const [, , status] = getColumns(mockCoreStart, api, mockConfig, tz, handleAction) as Array< - EuiTableFieldDataColumnType - >; + const [, , status] = getColumns( + mockCoreStart, + mockPluginsSetup, + api, + mockConfig, + tz, + handleAction + ) as Array>; const statusLine = mount(status.render!(mockSession.status, mockSession) as ReactElement); expect( @@ -148,9 +165,14 @@ describe('Search Sessions Management table column factory', () => { }); test('error handling', () => { - const [, , status] = getColumns(mockCoreStart, api, mockConfig, tz, handleAction) as Array< - EuiTableFieldDataColumnType - >; + const [, , status] = getColumns( + mockCoreStart, + mockPluginsSetup, + api, + mockConfig, + tz, + handleAction + ) as Array>; mockSession.status = 'INVALID' as SearchSessionStatus; const statusLine = mount(status.render!(mockSession.status, mockSession) as ReactElement); @@ -168,6 +190,7 @@ describe('Search Sessions Management table column factory', () => { const [, , , createdDateCol] = getColumns( mockCoreStart, + mockPluginsSetup, api, mockConfig, tz, @@ -184,6 +207,7 @@ describe('Search Sessions Management table column factory', () => { const [, , , createdDateCol] = getColumns( mockCoreStart, + mockPluginsSetup, api, mockConfig, tz, @@ -198,6 +222,7 @@ describe('Search Sessions Management table column factory', () => { test('error handling', () => { const [, , , createdDateCol] = getColumns( mockCoreStart, + mockPluginsSetup, api, mockConfig, tz, diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_columns.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_columns.tsx index cbd42ec56bb8b..d34998d023178 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_columns.tsx +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_columns.tsx @@ -21,7 +21,7 @@ import { capitalize } from 'lodash'; import React from 'react'; import { FormattedMessage } from 'react-intl'; import { RedirectAppLinks } from '../../../../../../../src/plugins/kibana_react/public'; -import { SessionsConfigSchema } from '../'; +import { IManagementSectionsPluginsSetup, SessionsConfigSchema } from '../'; import { SearchSessionStatus } from '../../../../common/search'; import { TableText } from '../components'; import { OnActionComplete, PopoverActionsMenu } from '../components'; @@ -45,6 +45,7 @@ function isSessionRestorable(status: SearchSessionStatus) { export const getColumns = ( core: CoreStart, + plugins: IManagementSectionsPluginsSetup, api: SearchSessionsMgmtAPI, config: SessionsConfigSchema, timezone: string, @@ -83,6 +84,10 @@ export const getColumns = ( width: '20%', render: (name: UISession['name'], { restoreUrl, reloadUrl, status }) => { const isRestorable = isSessionRestorable(status); + const href = isRestorable ? restoreUrl : reloadUrl; + const trackAction = isRestorable + ? plugins.data.search.usageCollector?.trackSessionViewRestored + : plugins.data.search.usageCollector?.trackSessionReloaded; const notRestorableWarning = isRestorable ? null : ( <> {' '} @@ -99,8 +104,10 @@ export const getColumns = ( ); return ( + {/* eslint-disable-next-line @elastic/eui/href-or-on-click */} trackAction?.()} data-test-subj="sessionManagementNameCol" > diff --git a/x-pack/plugins/data_enhanced/public/search/ui/connected_search_session_indicator/connected_search_session_indicator.test.tsx b/x-pack/plugins/data_enhanced/public/search/ui/connected_search_session_indicator/connected_search_session_indicator.test.tsx index aacb86f269727..0aef27310e090 100644 --- a/x-pack/plugins/data_enhanced/public/search/ui/connected_search_session_indicator/connected_search_session_indicator.test.tsx +++ b/x-pack/plugins/data_enhanced/public/search/ui/connected_search_session_indicator/connected_search_session_indicator.test.tsx @@ -16,18 +16,22 @@ import { ISessionService, RefreshInterval, SearchSessionState, + SearchUsageCollector, TimefilterContract, } from '../../../../../../../src/plugins/data/public'; import { coreMock } from '../../../../../../../src/core/public/mocks'; import { TOUR_RESTORE_STEP_KEY, TOUR_TAKING_TOO_LONG_STEP_KEY } from './search_session_tour'; import userEvent from '@testing-library/user-event'; import { IntlProvider } from 'react-intl'; +import { createSearchUsageCollectorMock } from '../../../../../../../src/plugins/data/public/search/collectors/mocks'; const coreStart = coreMock.createStart(); const application = coreStart.application; const dataStart = dataPluginMock.createStartContract(); const sessionService = dataStart.search.session as jest.Mocked; let storage: Storage; +let usageCollector: jest.Mocked; + const refreshInterval$ = new BehaviorSubject({ value: 0, pause: true }); const timeFilter = dataStart.query.timefilter.timefilter as jest.Mocked; timeFilter.getRefreshIntervalUpdate$.mockImplementation(() => refreshInterval$); @@ -41,6 +45,7 @@ function Container({ children }: { children?: ReactNode }) { beforeEach(() => { storage = new Storage(new StubBrowserStorage()); + usageCollector = createSearchUsageCollectorMock(); refreshInterval$.next({ value: 0, pause: true }); sessionService.isSessionStorageReady.mockImplementation(() => true); sessionService.getSearchSessionIndicatorUiConfig.mockImplementation(() => ({ @@ -57,6 +62,7 @@ test("shouldn't show indicator in case no active search session", async () => { timeFilter, storage, disableSaveAfterSessionCompletesTimeout, + usageCollector, }); const { getByTestId, container } = render( @@ -84,6 +90,7 @@ test("shouldn't show indicator in case app hasn't opt-in", async () => { timeFilter, storage, disableSaveAfterSessionCompletesTimeout, + usageCollector, }); const { getByTestId, container } = render( @@ -113,6 +120,7 @@ test('should show indicator in case there is an active search session', async () timeFilter, storage, disableSaveAfterSessionCompletesTimeout, + usageCollector, }); const { getByTestId } = render( @@ -137,6 +145,7 @@ test('should be disabled in case uiConfig says so ', async () => { timeFilter, storage, disableSaveAfterSessionCompletesTimeout, + usageCollector, }); render( @@ -185,6 +194,7 @@ test('should be disabled during auto-refresh', async () => { timeFilter, storage, disableSaveAfterSessionCompletesTimeout, + usageCollector, }); render( @@ -222,6 +232,7 @@ describe('Completed inactivity', () => { timeFilter, storage, disableSaveAfterSessionCompletesTimeout, + usageCollector, }); render( @@ -253,12 +264,14 @@ describe('Completed inactivity', () => { }); expect(screen.getByRole('button', { name: 'Save session' })).not.toBeDisabled(); + expect(usageCollector.trackSessionIndicatorSaveDisabled).toHaveBeenCalledTimes(0); act(() => { jest.advanceTimersByTime(2.5 * 60 * 1000); }); expect(screen.getByRole('button', { name: 'Save session' })).toBeDisabled(); + expect(usageCollector.trackSessionIndicatorSaveDisabled).toHaveBeenCalledTimes(1); }); }); @@ -280,6 +293,7 @@ describe('tour steps', () => { timeFilter, storage, disableSaveAfterSessionCompletesTimeout, + usageCollector, }); const rendered = render( @@ -307,6 +321,9 @@ describe('tour steps', () => { expect(storage.get(TOUR_RESTORE_STEP_KEY)).toBeFalsy(); expect(storage.get(TOUR_TAKING_TOO_LONG_STEP_KEY)).toBeTruthy(); + + expect(usageCollector.trackSessionIndicatorTourLoading).toHaveBeenCalledTimes(1); + expect(usageCollector.trackSessionIndicatorTourRestored).toHaveBeenCalledTimes(0); }); test("doesn't show tour step if state changed before delay", async () => { @@ -317,6 +334,7 @@ describe('tour steps', () => { timeFilter, storage, disableSaveAfterSessionCompletesTimeout, + usageCollector, }); const rendered = render( @@ -337,6 +355,9 @@ describe('tour steps', () => { expect(storage.get(TOUR_RESTORE_STEP_KEY)).toBeFalsy(); expect(storage.get(TOUR_TAKING_TOO_LONG_STEP_KEY)).toBeFalsy(); + + expect(usageCollector.trackSessionIndicatorTourLoading).toHaveBeenCalledTimes(0); + expect(usageCollector.trackSessionIndicatorTourRestored).toHaveBeenCalledTimes(0); }); }); @@ -348,6 +369,7 @@ describe('tour steps', () => { timeFilter, storage, disableSaveAfterSessionCompletesTimeout, + usageCollector, }); const rendered = render( @@ -360,6 +382,10 @@ describe('tour steps', () => { expect(storage.get(TOUR_RESTORE_STEP_KEY)).toBeTruthy(); expect(storage.get(TOUR_TAKING_TOO_LONG_STEP_KEY)).toBeTruthy(); + + expect(usageCollector.trackSessionIndicatorTourLoading).toHaveBeenCalledTimes(0); + expect(usageCollector.trackSessionIsRestored).toHaveBeenCalledTimes(1); + expect(usageCollector.trackSessionIndicatorTourRestored).toHaveBeenCalledTimes(1); }); test("doesn't show tour for irrelevant state", async () => { @@ -370,6 +396,7 @@ describe('tour steps', () => { timeFilter, storage, disableSaveAfterSessionCompletesTimeout, + usageCollector, }); const rendered = render( @@ -383,5 +410,8 @@ describe('tour steps', () => { expect(storage.get(TOUR_RESTORE_STEP_KEY)).toBeFalsy(); expect(storage.get(TOUR_TAKING_TOO_LONG_STEP_KEY)).toBeFalsy(); + + expect(usageCollector.trackSessionIndicatorTourLoading).toHaveBeenCalledTimes(0); + expect(usageCollector.trackSessionIndicatorTourRestored).toHaveBeenCalledTimes(0); }); }); diff --git a/x-pack/plugins/data_enhanced/public/search/ui/connected_search_session_indicator/connected_search_session_indicator.tsx b/x-pack/plugins/data_enhanced/public/search/ui/connected_search_session_indicator/connected_search_session_indicator.tsx index 81769e5a25544..7c70a270bd30a 100644 --- a/x-pack/plugins/data_enhanced/public/search/ui/connected_search_session_indicator/connected_search_session_indicator.tsx +++ b/x-pack/plugins/data_enhanced/public/search/ui/connected_search_session_indicator/connected_search_session_indicator.tsx @@ -5,8 +5,8 @@ * 2.0. */ -import React, { useCallback, useState } from 'react'; -import { debounce, distinctUntilChanged, map, mapTo, switchMap } from 'rxjs/operators'; +import React, { useCallback, useEffect, useState } from 'react'; +import { debounce, distinctUntilChanged, map, mapTo, switchMap, tap } from 'rxjs/operators'; import { merge, of, timer } from 'rxjs'; import useObservable from 'react-use/lib/useObservable'; import { i18n } from '@kbn/i18n'; @@ -15,7 +15,8 @@ import { ISessionService, SearchSessionState, TimefilterContract, -} from '../../../../../../../src/plugins/data/public/'; + SearchUsageCollector, +} from '../../../../../../../src/plugins/data/public'; import { RedirectAppLinks } from '../../../../../../../src/plugins/kibana_react/public'; import { ApplicationStart } from '../../../../../../../src/core/public'; import { IStorageWrapper } from '../../../../../../../src/plugins/kibana_utils/public'; @@ -31,6 +32,7 @@ export interface SearchSessionIndicatorDeps { * after the last search in the session has completed */ disableSaveAfterSessionCompletesTimeout: number; + usageCollector?: SearchUsageCollector; } export const createConnectedSearchSessionIndicator = ({ @@ -39,6 +41,7 @@ export const createConnectedSearchSessionIndicator = ({ timeFilter, storage, disableSaveAfterSessionCompletesTimeout, + usageCollector, }: SearchSessionIndicatorDeps): React.FC => { const isAutoRefreshEnabled = () => !timeFilter.getRefreshInterval().pause; const isAutoRefreshEnabled$ = timeFilter @@ -55,7 +58,10 @@ export const createConnectedSearchSessionIndicator = ({ ? merge(of(false), timer(disableSaveAfterSessionCompletesTimeout).pipe(mapTo(true))) : of(false) ), - distinctUntilChanged() + distinctUntilChanged(), + tap((value) => { + if (value) usageCollector?.trackSessionIndicatorSaveDisabled(); + }) ); return () => { @@ -123,7 +129,8 @@ export const createConnectedSearchSessionIndicator = ({ storage, searchSessionIndicator, state, - saveDisabled + saveDisabled, + usageCollector ); const onOpened = useCallback( @@ -138,18 +145,31 @@ export const createConnectedSearchSessionIndicator = ({ const onContinueInBackground = useCallback(() => { if (saveDisabled) return; + usageCollector?.trackSessionSentToBackground(); sessionService.save(); }, [saveDisabled]); const onSaveResults = useCallback(() => { if (saveDisabled) return; + usageCollector?.trackSessionSavedResults(); sessionService.save(); }, [saveDisabled]); const onCancel = useCallback(() => { + usageCollector?.trackSessionCancelled(); sessionService.cancel(); }, []); + const onViewSearchSessions = useCallback(() => { + usageCollector?.trackViewSessionsList(); + }, []); + + useEffect(() => { + if (state === SearchSessionState.Restored) { + usageCollector?.trackSessionIsRestored(); + } + }, [state]); + if (!sessionService.isSessionStorageReady()) return null; return ( @@ -164,6 +184,7 @@ export const createConnectedSearchSessionIndicator = ({ onSaveResults={onSaveResults} onCancel={onCancel} onOpened={onOpened} + onViewSearchSessions={onViewSearchSessions} /> ); diff --git a/x-pack/plugins/data_enhanced/public/search/ui/connected_search_session_indicator/search_session_tour.tsx b/x-pack/plugins/data_enhanced/public/search/ui/connected_search_session_indicator/search_session_tour.tsx index 7987278f400ff..1568d54962eca 100644 --- a/x-pack/plugins/data_enhanced/public/search/ui/connected_search_session_indicator/search_session_tour.tsx +++ b/x-pack/plugins/data_enhanced/public/search/ui/connected_search_session_indicator/search_session_tour.tsx @@ -6,9 +6,13 @@ */ import { useCallback, useEffect } from 'react'; +import { once } from 'lodash'; import { IStorageWrapper } from '../../../../../../../src/plugins/kibana_utils/public'; import { SearchSessionIndicatorRef } from '../search_session_indicator'; -import { SearchSessionState } from '../../../../../../../src/plugins/data/public'; +import { + SearchSessionState, + SearchUsageCollector, +} from '../../../../../../../src/plugins/data/public'; const TOUR_TAKING_TOO_LONG_TIMEOUT = 10000; export const TOUR_TAKING_TOO_LONG_STEP_KEY = `data.searchSession.tour.takingTooLong`; @@ -18,7 +22,8 @@ export function useSearchSessionTour( storage: IStorageWrapper, searchSessionIndicatorRef: SearchSessionIndicatorRef | null, state: SearchSessionState, - searchSessionsDisabled: boolean + searchSessionsDisabled: boolean, + usageCollector?: SearchUsageCollector ) { const markOpenedDone = useCallback(() => { safeSet(storage, TOUR_TAKING_TOO_LONG_STEP_KEY); @@ -28,6 +33,26 @@ export function useSearchSessionTour( safeSet(storage, TOUR_RESTORE_STEP_KEY); }, [storage]); + // Makes sure `trackSessionIndicatorTourLoading` is called only once per sessionId + // if to call `usageCollector?.trackSessionIndicatorTourLoading()` directly inside the `useEffect` below + // it might happen that we cause excessive logging + // ESLint: React Hook useCallback received a function whose dependencies are unknown. Pass an inline function instead. + // eslint-disable-next-line react-hooks/exhaustive-deps + const trackSessionIndicatorTourLoading = useCallback( + once(() => usageCollector?.trackSessionIndicatorTourLoading()), + [usageCollector, state] + ); + + // Makes sure `trackSessionIndicatorTourRestored` is called only once per sessionId + // if to call `usageCollector?.trackSessionIndicatorTourRestored()` directly inside the `useEffect` below + // it might happen that we cause excessive logging + // ESLint: React Hook useCallback received a function whose dependencies are unknown. Pass an inline function instead. + // eslint-disable-next-line react-hooks/exhaustive-deps + const trackSessionIndicatorTourRestored = useCallback( + once(() => usageCollector?.trackSessionIndicatorTourRestored()), + [usageCollector, state] + ); + useEffect(() => { if (searchSessionsDisabled) return; if (!searchSessionIndicatorRef) return; @@ -36,6 +61,7 @@ export function useSearchSessionTour( if (state === SearchSessionState.Loading) { if (!safeHas(storage, TOUR_TAKING_TOO_LONG_STEP_KEY)) { timeoutHandle = window.setTimeout(() => { + trackSessionIndicatorTourLoading(); searchSessionIndicatorRef.openPopover(); }, TOUR_TAKING_TOO_LONG_TIMEOUT); } @@ -43,6 +69,7 @@ export function useSearchSessionTour( if (state === SearchSessionState.Restored) { if (!safeHas(storage, TOUR_RESTORE_STEP_KEY)) { + trackSessionIndicatorTourRestored(); searchSessionIndicatorRef.openPopover(); } } @@ -57,6 +84,9 @@ export function useSearchSessionTour( searchSessionsDisabled, markOpenedDone, markRestoredDone, + usageCollector, + trackSessionIndicatorTourRestored, + trackSessionIndicatorTourLoading, ]); return { diff --git a/x-pack/plugins/data_enhanced/public/search/ui/search_session_indicator/search_session_indicator.tsx b/x-pack/plugins/data_enhanced/public/search/ui/search_session_indicator/search_session_indicator.tsx index 0d31ce0c98f19..24ffc1359acae 100644 --- a/x-pack/plugins/data_enhanced/public/search/ui/search_session_indicator/search_session_indicator.tsx +++ b/x-pack/plugins/data_enhanced/public/search/ui/search_session_indicator/search_session_indicator.tsx @@ -30,6 +30,7 @@ export interface SearchSessionIndicatorProps { onContinueInBackground?: () => void; onCancel?: () => void; viewSearchSessionsLink?: string; + onViewSearchSessions?: () => void; onSaveResults?: () => void; managementDisabled?: boolean; managementDisabledReasonText?: string; @@ -78,13 +79,16 @@ const ContinueInBackgroundButton = ({ const ViewAllSearchSessionsButton = ({ viewSearchSessionsLink = 'management/kibana/search_sessions', + onViewSearchSessions = () => {}, buttonProps = {}, managementDisabled, managementDisabledReasonText, }: ActionButtonProps) => ( + {/* eslint-disable-next-line @elastic/eui/href-or-on-click */} Date: Tue, 16 Feb 2021 16:18:33 +0200 Subject: [PATCH 02/53] [TSVB] Fixes the timeseries legend, renders the metric, gauge charts for series with empty strings (#90760) (#91468) * [TSVB] Fixes the legend for empty values and renders the metric, gauge charts * Change i18n id Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../application/components/vis_types/table/vis.js | 8 +++++++- .../application/components/vis_with_splits.js | 10 ++++++++-- .../visualizations/views/timeseries/index.js | 15 +++++++++++++-- .../application/visualizations/views/top_n.js | 7 ++++++- 4 files changed, 34 insertions(+), 6 deletions(-) diff --git a/src/plugins/vis_type_timeseries/public/application/components/vis_types/table/vis.js b/src/plugins/vis_type_timeseries/public/application/components/vis_types/table/vis.js index e4ab4eaa0a671..24d0ca1b588f7 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/vis_types/table/vis.js +++ b/src/plugins/vis_type_timeseries/public/application/components/vis_types/table/vis.js @@ -8,6 +8,7 @@ import _, { isArray, last, get } from 'lodash'; import React, { Component } from 'react'; +import { i18n } from '@kbn/i18n'; import PropTypes from 'prop-types'; import { RedirectAppLinks } from '../../../../../../kibana_react/public'; import { createTickFormatter } from '../../lib/tick_formatter'; @@ -88,7 +89,12 @@ class TableVis extends Component { }); return ( - {rowDisplay} + + {rowDisplay || + i18n.translate('visTypeTimeseries.emptyTextValue', { + defaultMessage: '(empty)', + })} + {columns} ); diff --git a/src/plugins/vis_type_timeseries/public/application/components/vis_with_splits.js b/src/plugins/vis_type_timeseries/public/application/components/vis_with_splits.js index cb9baaa4112bb..fbec412c30f48 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/vis_with_splits.js +++ b/src/plugins/vis_type_timeseries/public/application/components/vis_with_splits.js @@ -7,6 +7,7 @@ */ import React from 'react'; +import { i18n } from '@kbn/i18n'; import { getDisplayName } from './lib/get_display_name'; import { labelDateFormatter } from './lib/label_date_formatter'; import { findIndex, first } from 'lodash'; @@ -22,7 +23,7 @@ export function visWithSplits(WrappedComponent) { const splitsVisData = visData[model.id].series.reduce((acc, series) => { const [seriesId, splitId] = series.id.split(':'); const seriesModel = model.series.find((s) => s.id === seriesId); - if (!seriesModel || !splitId) return acc; + if (!seriesModel) return acc; const label = series.splitByLabel; @@ -80,7 +81,12 @@ export function visWithSplits(WrappedComponent) { model={model} visData={newVisData} onBrush={props.onBrush} - additionalLabel={additionalLabel} + additionalLabel={ + additionalLabel || + i18n.translate('visTypeTimeseries.emptyTextValue', { + defaultMessage: '(empty)', + }) + } backgroundColor={props.backgroundColor} getConfig={props.getConfig} /> diff --git a/src/plugins/vis_type_timeseries/public/application/visualizations/views/timeseries/index.js b/src/plugins/vis_type_timeseries/public/application/visualizations/views/timeseries/index.js index 9ca53edc50ce9..4ec60661ffed2 100644 --- a/src/plugins/vis_type_timeseries/public/application/visualizations/views/timeseries/index.js +++ b/src/plugins/vis_type_timeseries/public/application/visualizations/views/timeseries/index.js @@ -9,6 +9,7 @@ import React, { useEffect, useRef } from 'react'; import PropTypes from 'prop-types'; import classNames from 'classnames'; +import { i18n } from '@kbn/i18n'; import { labelDateFormatter } from '../../../components/lib/label_date_formatter'; import { @@ -188,7 +189,12 @@ export const TimeSeries = ({ key={key} seriesId={id} seriesGroupId={groupId} - name={seriesName} + name={ + seriesName || + i18n.translate('visTypeTimeseries.emptyTextValue', { + defaultMessage: '(empty)', + }) + } data={data} hideInLegend={hideInLegend} bars={bars} @@ -213,7 +219,12 @@ export const TimeSeries = ({ key={key} seriesId={id} seriesGroupId={groupId} - name={seriesName} + name={ + seriesName || + i18n.translate('visTypeTimeseries.emptyTextValue', { + defaultMessage: '(empty)', + }) + } data={data} hideInLegend={hideInLegend} lines={lines} diff --git a/src/plugins/vis_type_timeseries/public/application/visualizations/views/top_n.js b/src/plugins/vis_type_timeseries/public/application/visualizations/views/top_n.js index 42d6d095d0648..542ee0871fdcb 100644 --- a/src/plugins/vis_type_timeseries/public/application/visualizations/views/top_n.js +++ b/src/plugins/vis_type_timeseries/public/application/visualizations/views/top_n.js @@ -8,6 +8,7 @@ import PropTypes from 'prop-types'; import React, { Component } from 'react'; +import { i18n } from '@kbn/i18n'; import { getLastValue } from '../../../../common/get_last_value'; import { labelDateFormatter } from '../../components/lib/label_date_formatter'; import reactcss from 'reactcss'; @@ -103,6 +104,7 @@ export class TopN extends Component { // if both are 0, the division returns NaN causing unexpected behavior. // For this it defaults to 0 const width = 100 * (Math.abs(lastValue) / intervalLength) || 0; + const label = item.labelFormatted ? labelDateFormatter(item.labelFormatted) : item.label; const styles = reactcss( { @@ -128,7 +130,10 @@ export class TopN extends Component { return ( - {item.labelFormatted ? labelDateFormatter(item.labelFormatted) : item.label} + {label || + i18n.translate('visTypeTimeseries.emptyTextValue', { + defaultMessage: '(empty)', + })}
From 01e82874b49af82a695b57af3ca91782d146329b Mon Sep 17 00:00:00 2001 From: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Date: Tue, 16 Feb 2021 09:27:38 -0500 Subject: [PATCH 03/53] Fixes session idle timeout (#91070) (#91472) * Fix calls to `/api/saved_objects_tagging/tags` Seen on all pages. * Fix calls to `/api/ui_counters/_report` Seen on all pages. * Fix calls to `/api/index_management/indices/reload` Seen on page: /app/management/data/index_management * Fix calls to `/api/watcher/watches` Seen on page: /app/management/insightsAndAlerting/watcher/watches * Fix calls to `/api/rollup/jobs` Seen on page: /app/management/data/rollup_jobs/job_list * Fix calls to `/api/cross_cluster_replication/follower_indices` Seen on page: /app/management/data/cross_cluster_replication/follower_indices * Fix calls to `/api/cross_cluster_replication/auto_follow_patterns` Seen on page: /app/management/data/cross_cluster_replication/auto_follow_patterns * Fix calls to `/api/transform/transforms` and `/api/transform/transforms/_stats` Seen on page: /app/management/data/transform * Fix calls to `/api/console/proxy` Seen on page: /app/dev_tools#/console * Fix calls to `/api/monitoring/v1/clusters` and `/api/monitoring/v1/elasticsearch_settings/check/cluster` Seen on page: /app/monitoring Co-authored-by: Joe Portner <5295965+jportner@users.noreply.github.com> --- src/plugins/console/public/lib/es/es.ts | 12 ++- .../console/public/lib/mappings/mappings.js | 2 +- .../request/send_request.test.helpers.ts | 22 +++-- .../public/request/send_request.ts | 13 ++- .../request/use_request.test.helpers.tsx | 33 ++++--- .../public/request/use_request.ts | 88 +++++++++++-------- .../saved_objects_tagging_oss/common/index.ts | 2 +- .../saved_objects_tagging_oss/common/types.ts | 6 +- .../public/services/create_reporter.ts | 1 + .../public/app/services/api.js | 6 +- .../app/store/actions/auto_follow_pattern.js | 2 +- .../app/store/actions/follower_index.js | 2 +- .../index_table/index_table.container.js | 4 +- .../index_list/index_table/index_table.js | 6 +- .../public/application/services/api.ts | 14 ++- .../store/actions/reload_indices.js | 4 +- .../checkers/settings_checker.js | 4 +- .../monitoring/public/services/clusters.js | 18 ++-- .../public/application/services/api.js | 4 +- .../public/application/services/http.ts | 13 ++- .../store/actions/refresh_clusters.js | 2 +- .../sections/job_list/job_list.container.js | 4 +- .../crud_app/sections/job_list/job_list.js | 5 +- .../rollup/public/crud_app/services/api.js | 5 +- .../crud_app/store/actions/refresh_jobs.js | 4 +- .../saved_objects_tagging/common/types.ts | 1 + .../saved_objects_tagging/public/plugin.ts | 2 +- .../public/services/tags/tags_client.test.ts | 12 ++- .../public/services/tags/tags_client.ts | 16 +++- .../transform/public/app/hooks/use_api.ts | 16 +++- .../public/app/hooks/use_get_transforms.ts | 5 +- 31 files changed, 222 insertions(+), 106 deletions(-) diff --git a/src/plugins/console/public/lib/es/es.ts b/src/plugins/console/public/lib/es/es.ts index 8053fca91b7d1..03ee218fa2e1d 100644 --- a/src/plugins/console/public/lib/es/es.ts +++ b/src/plugins/console/public/lib/es/es.ts @@ -9,6 +9,10 @@ import $ from 'jquery'; import { stringify } from 'query-string'; +interface SendOptions { + asSystemRequest?: boolean; +} + const esVersion: string[] = []; export function getVersion() { @@ -20,13 +24,19 @@ export function getContentType(body: any) { return 'application/json'; } -export function send(method: string, path: string, data: any) { +export function send( + method: string, + path: string, + data: any, + { asSystemRequest }: SendOptions = {} +) { const wrappedDfd = $.Deferred(); const options: JQuery.AjaxSettings = { url: '../api/console/proxy?' + stringify({ path, method }, { sort: false }), headers: { 'kbn-xsrf': 'kibana', + ...(asSystemRequest && { 'kbn-system-request': 'true' }), }, data, contentType: getContentType(data), diff --git a/src/plugins/console/public/lib/mappings/mappings.js b/src/plugins/console/public/lib/mappings/mappings.js index 244cc781498a7..d4996f9fd8862 100644 --- a/src/plugins/console/public/lib/mappings/mappings.js +++ b/src/plugins/console/public/lib/mappings/mappings.js @@ -250,7 +250,7 @@ function retrieveSettings(settingsKey, settingsToRetrieve) { // Fetch autocomplete info if setting is set to true, and if user has made changes. if (settingsToRetrieve[settingsKey] === true) { - return es.send('GET', settingKeyToPathMap[settingsKey], null); + return es.send('GET', settingKeyToPathMap[settingsKey], null, true); } else { const settingsPromise = new $.Deferred(); if (settingsToRetrieve[settingsKey] === false) { diff --git a/src/plugins/es_ui_shared/public/request/send_request.test.helpers.ts b/src/plugins/es_ui_shared/public/request/send_request.test.helpers.ts index 5244a6c1e8bf1..3ef33b651f4d2 100644 --- a/src/plugins/es_ui_shared/public/request/send_request.test.helpers.ts +++ b/src/plugins/es_ui_shared/public/request/send_request.test.helpers.ts @@ -41,20 +41,26 @@ export const createSendRequestHelpers = (): SendRequestHelpers => { // Set up successful request helpers. sendRequestSpy - .withArgs(successRequest.path, { - body: JSON.stringify(successRequest.body), - query: undefined, - }) + .withArgs( + successRequest.path, + sinon.match({ + body: JSON.stringify(successRequest.body), + query: undefined, + }) + ) .resolves(successResponse); const sendSuccessRequest = () => sendRequest({ ...successRequest }); const getSuccessResponse = () => ({ data: successResponse.data, error: null }); // Set up failed request helpers. sendRequestSpy - .withArgs(errorRequest.path, { - body: JSON.stringify(errorRequest.body), - query: undefined, - }) + .withArgs( + errorRequest.path, + sinon.match({ + body: JSON.stringify(errorRequest.body), + query: undefined, + }) + ) .rejects(errorResponse); const sendErrorRequest = () => sendRequest({ ...errorRequest }); const getErrorResponse = () => ({ diff --git a/src/plugins/es_ui_shared/public/request/send_request.ts b/src/plugins/es_ui_shared/public/request/send_request.ts index 32703f21a4668..11ab99cfb6978 100644 --- a/src/plugins/es_ui_shared/public/request/send_request.ts +++ b/src/plugins/es_ui_shared/public/request/send_request.ts @@ -13,6 +13,11 @@ export interface SendRequestConfig { method: 'get' | 'post' | 'put' | 'delete' | 'patch' | 'head'; query?: HttpFetchQuery; body?: any; + /** + * If set, flags this as a "system request" to indicate that this is not a user-initiated request. For more information, see + * HttpFetchOptions#asSystemRequest. + */ + asSystemRequest?: boolean; } export interface SendRequestResponse { @@ -22,11 +27,15 @@ export interface SendRequestResponse { export const sendRequest = async ( httpClient: HttpSetup, - { path, method, body, query }: SendRequestConfig + { path, method, body, query, asSystemRequest }: SendRequestConfig ): Promise> => { try { const stringifiedBody = typeof body === 'string' ? body : JSON.stringify(body); - const response = await httpClient[method](path, { body: stringifiedBody, query }); + const response = await httpClient[method](path, { + body: stringifiedBody, + query, + asSystemRequest, + }); return { data: response.data ? response.data : response, diff --git a/src/plugins/es_ui_shared/public/request/use_request.test.helpers.tsx b/src/plugins/es_ui_shared/public/request/use_request.test.helpers.tsx index 9f41d13112bc8..82d3764dbf72a 100644 --- a/src/plugins/es_ui_shared/public/request/use_request.test.helpers.tsx +++ b/src/plugins/es_ui_shared/public/request/use_request.test.helpers.tsx @@ -123,10 +123,13 @@ export const createUseRequestHelpers = (): UseRequestHelpers => { // Set up successful request helpers. sendRequestSpy - .withArgs(successRequest.path, { - body: JSON.stringify(successRequest.body), - query: undefined, - }) + .withArgs( + successRequest.path, + sinon.match({ + body: JSON.stringify(successRequest.body), + query: undefined, + }) + ) .resolves(successResponse); const setupSuccessRequest = (overrides = {}, requestTimings?: number[]) => setupUseRequest({ ...successRequest, ...overrides }, requestTimings); @@ -134,10 +137,13 @@ export const createUseRequestHelpers = (): UseRequestHelpers => { // Set up failed request helpers. sendRequestSpy - .withArgs(errorRequest.path, { - body: JSON.stringify(errorRequest.body), - query: undefined, - }) + .withArgs( + errorRequest.path, + sinon.match({ + body: JSON.stringify(errorRequest.body), + query: undefined, + }) + ) .rejects(errorResponse); const setupErrorRequest = (overrides = {}, requestTimings?: number[]) => setupUseRequest({ ...errorRequest, ...overrides }, requestTimings); @@ -152,10 +158,13 @@ export const createUseRequestHelpers = (): UseRequestHelpers => { // Set up failed request helpers with the alternative error shape. sendRequestSpy - .withArgs(errorWithBodyRequest.path, { - body: JSON.stringify(errorWithBodyRequest.body), - query: undefined, - }) + .withArgs( + errorWithBodyRequest.path, + sinon.match({ + body: JSON.stringify(errorWithBodyRequest.body), + query: undefined, + }) + ) .rejects(errorWithBodyResponse); const setupErrorWithBodyRequest = (overrides = {}) => setupUseRequest({ ...errorWithBodyRequest, ...overrides }); diff --git a/src/plugins/es_ui_shared/public/request/use_request.ts b/src/plugins/es_ui_shared/public/request/use_request.ts index 99eb38ff6023f..33085bdbf4478 100644 --- a/src/plugins/es_ui_shared/public/request/use_request.ts +++ b/src/plugins/es_ui_shared/public/request/use_request.ts @@ -65,49 +65,59 @@ export const useRequest = ( /* eslint-disable-next-line react-hooks/exhaustive-deps */ }, [path, method, queryStringified, bodyStringified]); - const resendRequest = useCallback(async () => { - // If we're on an interval, this allows us to reset it if the user has manually requested the - // data, to avoid doubled-up requests. - clearPollInterval(); - - const requestId = ++requestCountRef.current; - - // We don't clear error or data, so it's up to the consumer to decide whether to display the - // "old" error/data or loading state when a new request is in-flight. - setIsLoading(true); - - const response = await sendRequest(httpClient, requestBody); - const { data: serializedResponseData, error: responseError } = response; - - const isOutdatedRequest = requestId !== requestCountRef.current; - const isUnmounted = isMounted.current === false; - - // Ignore outdated or irrelevant data. - if (isOutdatedRequest || isUnmounted) { - return; - } - - // Surface to consumers that at least one request has resolved. - isInitialRequestRef.current = false; + const resendRequest = useCallback( + async (asSystemRequest?: boolean) => { + // If we're on an interval, this allows us to reset it if the user has manually requested the + // data, to avoid doubled-up requests. + clearPollInterval(); - setError(responseError); - // If there's an error, keep the data from the last request in case it's still useful to the user. - if (!responseError) { - const responseData = deserializer - ? deserializer(serializedResponseData) - : serializedResponseData; - setData(responseData); - } - // Setting isLoading to false also acts as a signal for scheduling the next poll request. - setIsLoading(false); - }, [requestBody, httpClient, deserializer, clearPollInterval]); + const requestId = ++requestCountRef.current; + + // We don't clear error or data, so it's up to the consumer to decide whether to display the + // "old" error/data or loading state when a new request is in-flight. + setIsLoading(true); + + // Any requests that are sent in the background (without user interaction) should be flagged as "system requests". This should not be + // confused with any terminology in Elasticsearch. This is a Kibana-specific construct that allows the server to differentiate between + // user-initiated and requests "system"-initiated requests, for purposes like security features. + const requestPayload = { ...requestBody, asSystemRequest }; + const response = await sendRequest(httpClient, requestPayload); + const { data: serializedResponseData, error: responseError } = response; + + const isOutdatedRequest = requestId !== requestCountRef.current; + const isUnmounted = isMounted.current === false; + + // Ignore outdated or irrelevant data. + if (isOutdatedRequest || isUnmounted) { + return; + } + + // Surface to consumers that at least one request has resolved. + isInitialRequestRef.current = false; + + setError(responseError); + // If there's an error, keep the data from the last request in case it's still useful to the user. + if (!responseError) { + const responseData = deserializer + ? deserializer(serializedResponseData) + : serializedResponseData; + setData(responseData); + } + // Setting isLoading to false also acts as a signal for scheduling the next poll request. + setIsLoading(false); + }, + [requestBody, httpClient, deserializer, clearPollInterval] + ); const scheduleRequest = useCallback(() => { // If there's a scheduled poll request, this new one will supersede it. clearPollInterval(); if (pollIntervalMs) { - pollIntervalIdRef.current = setTimeout(resendRequest, pollIntervalMs); + pollIntervalIdRef.current = setTimeout( + () => resendRequest(true), // This is happening on an interval in the background, so we flag it as a "system request". + pollIntervalMs + ); } }, [pollIntervalMs, resendRequest, clearPollInterval]); @@ -137,11 +147,15 @@ export const useRequest = ( }; }, [clearPollInterval]); + const resendRequestForConsumer = useCallback(() => { + return resendRequest(); + }, [resendRequest]); + return { isInitialRequest: isInitialRequestRef.current, isLoading, error, data, - resendRequest, // Gives the user the ability to manually request data + resendRequest: resendRequestForConsumer, // Gives the user the ability to manually request data }; }; diff --git a/src/plugins/saved_objects_tagging_oss/common/index.ts b/src/plugins/saved_objects_tagging_oss/common/index.ts index 231bec46f57ab..a892f41c69314 100644 --- a/src/plugins/saved_objects_tagging_oss/common/index.ts +++ b/src/plugins/saved_objects_tagging_oss/common/index.ts @@ -6,4 +6,4 @@ * Side Public License, v 1. */ -export { Tag, TagAttributes, ITagsClient } from './types'; +export { Tag, TagAttributes, GetAllTagsOptions, ITagsClient } from './types'; diff --git a/src/plugins/saved_objects_tagging_oss/common/types.ts b/src/plugins/saved_objects_tagging_oss/common/types.ts index 344e18a5fd76d..205f6984ed618 100644 --- a/src/plugins/saved_objects_tagging_oss/common/types.ts +++ b/src/plugins/saved_objects_tagging_oss/common/types.ts @@ -19,10 +19,14 @@ export interface TagAttributes { color: string; } +export interface GetAllTagsOptions { + asSystemRequest?: boolean; +} + export interface ITagsClient { create(attributes: TagAttributes): Promise; get(id: string): Promise; - getAll(): Promise; + getAll(options?: GetAllTagsOptions): Promise; delete(id: string): Promise; update(id: string, attributes: TagAttributes): Promise; } diff --git a/src/plugins/usage_collection/public/services/create_reporter.ts b/src/plugins/usage_collection/public/services/create_reporter.ts index ef4c007735ff4..e5006646fe368 100644 --- a/src/plugins/usage_collection/public/services/create_reporter.ts +++ b/src/plugins/usage_collection/public/services/create_reporter.ts @@ -24,6 +24,7 @@ export function createReporter(config: AnalyicsReporterConfig): Reporter { async http(report) { const response = await fetch.post('/api/ui_counters/_report', { body: JSON.stringify({ report }), + asSystemRequest: true, }); if (response.status !== 'ok') { diff --git a/x-pack/plugins/cross_cluster_replication/public/app/services/api.js b/x-pack/plugins/cross_cluster_replication/public/app/services/api.js index e1be717db221c..8067b2cc11b9a 100644 --- a/x-pack/plugins/cross_cluster_replication/public/app/services/api.js +++ b/x-pack/plugins/cross_cluster_replication/public/app/services/api.js @@ -48,7 +48,8 @@ export const getHttpClient = () => { const createIdString = (ids) => ids.map((id) => encodeURIComponent(id)).join(','); /* Auto Follow Pattern */ -export const loadAutoFollowPatterns = () => httpClient.get(`${API_BASE_PATH}/auto_follow_patterns`); +export const loadAutoFollowPatterns = (asSystemRequest) => + httpClient.get(`${API_BASE_PATH}/auto_follow_patterns`, { asSystemRequest }); export const getAutoFollowPattern = (id) => httpClient.get(`${API_BASE_PATH}/auto_follow_patterns/${encodeURIComponent(id)}`); @@ -100,7 +101,8 @@ export const resumeAutoFollowPattern = (id) => { }; /* Follower Index */ -export const loadFollowerIndices = () => httpClient.get(`${API_BASE_PATH}/follower_indices`); +export const loadFollowerIndices = (asSystemRequest) => + httpClient.get(`${API_BASE_PATH}/follower_indices`, { asSystemRequest }); export const getFollowerIndex = (id) => httpClient.get(`${API_BASE_PATH}/follower_indices/${encodeURIComponent(id)}`); diff --git a/x-pack/plugins/cross_cluster_replication/public/app/store/actions/auto_follow_pattern.js b/x-pack/plugins/cross_cluster_replication/public/app/store/actions/auto_follow_pattern.js index e6a9f02b913ca..79d0eeabb817d 100644 --- a/x-pack/plugins/cross_cluster_replication/public/app/store/actions/auto_follow_pattern.js +++ b/x-pack/plugins/cross_cluster_replication/public/app/store/actions/auto_follow_pattern.js @@ -39,7 +39,7 @@ export const loadAutoFollowPatterns = (isUpdating = false) => label: t.AUTO_FOLLOW_PATTERN_LOAD, scope, status: isUpdating ? API_STATUS.UPDATING : API_STATUS.LOADING, - handler: async () => await loadAutoFollowPatternsRequest(), + handler: async () => await loadAutoFollowPatternsRequest(isUpdating), }); export const getAutoFollowPattern = (id) => diff --git a/x-pack/plugins/cross_cluster_replication/public/app/store/actions/follower_index.js b/x-pack/plugins/cross_cluster_replication/public/app/store/actions/follower_index.js index 9f8b20622d6ec..7422ba6c84491 100644 --- a/x-pack/plugins/cross_cluster_replication/public/app/store/actions/follower_index.js +++ b/x-pack/plugins/cross_cluster_replication/public/app/store/actions/follower_index.js @@ -40,7 +40,7 @@ export const loadFollowerIndices = (isUpdating = false) => label: t.FOLLOWER_INDEX_LOAD, scope, status: isUpdating ? API_STATUS.UPDATING : API_STATUS.LOADING, - handler: async () => await loadFollowerIndicesRequest(), + handler: async () => await loadFollowerIndicesRequest(isUpdating), }); export const getFollowerIndex = (id) => diff --git a/x-pack/plugins/index_management/public/application/sections/home/index_list/index_table/index_table.container.js b/x-pack/plugins/index_management/public/application/sections/home/index_list/index_table/index_table.container.js index a435d9be54864..93ad0e0dc3be5 100644 --- a/x-pack/plugins/index_management/public/application/sections/home/index_list/index_table/index_table.container.js +++ b/x-pack/plugins/index_management/public/application/sections/home/index_list/index_table/index_table.container.js @@ -76,8 +76,8 @@ const mapDispatchToProps = (dispatch) => { loadIndices: () => { dispatch(loadIndices()); }, - reloadIndices: (indexNames) => { - dispatch(reloadIndices(indexNames)); + reloadIndices: (indexNames, options) => { + dispatch(reloadIndices(indexNames, options)); }, }; }; diff --git a/x-pack/plugins/index_management/public/application/sections/home/index_list/index_table/index_table.js b/x-pack/plugins/index_management/public/application/sections/home/index_list/index_table/index_table.js index d966c39b76c17..f488290692e7e 100644 --- a/x-pack/plugins/index_management/public/application/sections/home/index_list/index_table/index_table.js +++ b/x-pack/plugins/index_management/public/application/sections/home/index_list/index_table/index_table.js @@ -103,7 +103,11 @@ export class IndexTable extends Component { componentDidMount() { this.props.loadIndices(); this.interval = setInterval( - () => this.props.reloadIndices(this.props.indices.map((i) => i.name)), + () => + this.props.reloadIndices( + this.props.indices.map((i) => i.name), + { asSystemRequest: true } + ), REFRESH_RATE_INDEX_LIST ); const { location, filterChanged } = this.props; diff --git a/x-pack/plugins/index_management/public/application/services/api.ts b/x-pack/plugins/index_management/public/application/services/api.ts index 912b1cc64fa78..aea487d601740 100644 --- a/x-pack/plugins/index_management/public/application/services/api.ts +++ b/x-pack/plugins/index_management/public/application/services/api.ts @@ -41,6 +41,10 @@ import { useRequest, sendRequest } from './use_request'; import { httpService } from './http'; import { UiMetricService } from './ui_metric'; +interface ReloadIndicesOptions { + asSystemRequest?: boolean; +} + // Temporary hack to provide the uiMetricService instance to this file. // TODO: Refactor and export an ApiService instance through the app dependencies context let uiMetricService: UiMetricService; @@ -79,11 +83,17 @@ export async function loadIndices() { return response.data ? response.data : response; } -export async function reloadIndices(indexNames: string[]) { +export async function reloadIndices( + indexNames: string[], + { asSystemRequest }: ReloadIndicesOptions = {} +) { const body = JSON.stringify({ indexNames, }); - const response = await httpService.httpClient.post(`${API_BASE_PATH}/indices/reload`, { body }); + const response = await httpService.httpClient.post(`${API_BASE_PATH}/indices/reload`, { + body, + asSystemRequest, + }); return response.data ? response.data : response; } diff --git a/x-pack/plugins/index_management/public/application/store/actions/reload_indices.js b/x-pack/plugins/index_management/public/application/store/actions/reload_indices.js index 71838d61c20f8..9498e55154839 100644 --- a/x-pack/plugins/index_management/public/application/store/actions/reload_indices.js +++ b/x-pack/plugins/index_management/public/application/store/actions/reload_indices.js @@ -12,10 +12,10 @@ import { loadIndices } from './load_indices'; import { notificationService } from '../../services/notification'; export const reloadIndicesSuccess = createAction('INDEX_MANAGEMENT_RELOAD_INDICES_SUCCESS'); -export const reloadIndices = (indexNames) => async (dispatch) => { +export const reloadIndices = (indexNames, options) => async (dispatch) => { let indices; try { - indices = await request(indexNames); + indices = await request(indexNames, options); } catch (error) { // an index has been deleted // or the user does not have privileges for one of the indices on the current page, diff --git a/x-pack/plugins/monitoring/public/lib/elasticsearch_settings/checkers/settings_checker.js b/x-pack/plugins/monitoring/public/lib/elasticsearch_settings/checkers/settings_checker.js index 8f19fb6ab87be..92a172f4ef3df 100644 --- a/x-pack/plugins/monitoring/public/lib/elasticsearch_settings/checkers/settings_checker.js +++ b/x-pack/plugins/monitoring/public/lib/elasticsearch_settings/checkers/settings_checker.js @@ -44,7 +44,9 @@ export class SettingsChecker { async executeCheck() { try { - const { data } = await this.$http.get(this.getApi()); + const { data } = await this.$http.get(this.getApi(), { + headers: { 'kbn-system-request': 'true' }, + }); const { found, reason } = data; return { found, reason }; diff --git a/x-pack/plugins/monitoring/public/services/clusters.js b/x-pack/plugins/monitoring/public/services/clusters.js index 638b3a91b9874..71ae128072b7f 100644 --- a/x-pack/plugins/monitoring/public/services/clusters.js +++ b/x-pack/plugins/monitoring/public/services/clusters.js @@ -38,14 +38,18 @@ export function monitoringClustersProvider($injector) { async function getClusters() { try { - const response = await $http.post(url, { - ccs, - timeRange: { - min: min.toISOString(), - max: max.toISOString(), + const response = await $http.post( + url, + { + ccs, + timeRange: { + min: min.toISOString(), + max: max.toISOString(), + }, + codePaths, }, - codePaths, - }); + { headers: { 'kbn-system-request': 'true' } } + ); return formatClusters(response.data); } catch (err) { const Private = $injector.get('Private'); diff --git a/x-pack/plugins/remote_clusters/public/application/services/api.js b/x-pack/plugins/remote_clusters/public/application/services/api.js index c0d21f577dae8..6dd04b7090283 100644 --- a/x-pack/plugins/remote_clusters/public/application/services/api.js +++ b/x-pack/plugins/remote_clusters/public/application/services/api.js @@ -9,8 +9,8 @@ import { UIM_CLUSTER_ADD, UIM_CLUSTER_UPDATE } from '../constants'; import { trackUserRequest } from './ui_metric'; import { sendGet, sendPost, sendPut, sendDelete } from './http'; -export async function loadClusters() { - return await sendGet(); +export async function loadClusters(options) { + return await sendGet(undefined, options); } export async function addCluster(cluster) { diff --git a/x-pack/plugins/remote_clusters/public/application/services/http.ts b/x-pack/plugins/remote_clusters/public/application/services/http.ts index 7f205023dfa8a..831e706b5fa08 100644 --- a/x-pack/plugins/remote_clusters/public/application/services/http.ts +++ b/x-pack/plugins/remote_clusters/public/application/services/http.ts @@ -10,11 +10,15 @@ import { API_BASE_PATH } from '../../../common/constants'; let _httpClient: HttpSetup; +interface SendGetOptions { + asSystemRequest?: boolean; +} + export function init(httpClient: HttpSetup): void { _httpClient = httpClient; } -export function getFullPath(path: string): string { +export function getFullPath(path?: string): string { if (path) { return `${API_BASE_PATH}/${path}`; } @@ -35,8 +39,11 @@ export function sendPost( }); } -export function sendGet(path: string): Promise { - return _httpClient.get(getFullPath(path)); +export function sendGet( + path?: string, + { asSystemRequest }: SendGetOptions = {} +): Promise { + return _httpClient.get(getFullPath(path), { asSystemRequest }); } export function sendPut( diff --git a/x-pack/plugins/remote_clusters/public/application/store/actions/refresh_clusters.js b/x-pack/plugins/remote_clusters/public/application/store/actions/refresh_clusters.js index 8a765e171a8af..3dae779f0dc78 100644 --- a/x-pack/plugins/remote_clusters/public/application/store/actions/refresh_clusters.js +++ b/x-pack/plugins/remote_clusters/public/application/store/actions/refresh_clusters.js @@ -14,7 +14,7 @@ import { REFRESH_CLUSTERS_SUCCESS } from '../action_types'; export const refreshClusters = () => async (dispatch) => { let clusters; try { - clusters = await sendLoadClustersRequest(); + clusters = await sendLoadClustersRequest({ asSystemRequest: true }); } catch (error) { return showApiWarning( error, diff --git a/x-pack/plugins/rollup/public/crud_app/sections/job_list/job_list.container.js b/x-pack/plugins/rollup/public/crud_app/sections/job_list/job_list.container.js index e71b3b6870267..ce7e29af8323d 100644 --- a/x-pack/plugins/rollup/public/crud_app/sections/job_list/job_list.container.js +++ b/x-pack/plugins/rollup/public/crud_app/sections/job_list/job_list.container.js @@ -32,8 +32,8 @@ const mapDispatchToProps = (dispatch) => { loadJobs: () => { dispatch(loadJobs()); }, - refreshJobs: () => { - dispatch(refreshJobs()); + refreshJobs: (options) => { + dispatch(refreshJobs(options)); }, openDetailPanel: (jobId) => { dispatch(openDetailPanel({ jobId: jobId })); diff --git a/x-pack/plugins/rollup/public/crud_app/sections/job_list/job_list.js b/x-pack/plugins/rollup/public/crud_app/sections/job_list/job_list.js index d5038f40a686b..589546a11ef38 100644 --- a/x-pack/plugins/rollup/public/crud_app/sections/job_list/job_list.js +++ b/x-pack/plugins/rollup/public/crud_app/sections/job_list/job_list.js @@ -73,7 +73,10 @@ export class JobListUi extends Component { } componentDidMount() { - this.interval = setInterval(this.props.refreshJobs, REFRESH_RATE_MS); + this.interval = setInterval( + () => this.props.refreshJobs({ asSystemRequest: true }), + REFRESH_RATE_MS + ); } componentWillUnmount() { diff --git a/x-pack/plugins/rollup/public/crud_app/services/api.js b/x-pack/plugins/rollup/public/crud_app/services/api.js index 66efb6c2f09a0..b12cc62c9daa8 100644 --- a/x-pack/plugins/rollup/public/crud_app/services/api.js +++ b/x-pack/plugins/rollup/public/crud_app/services/api.js @@ -19,8 +19,9 @@ import { trackUserRequest } from './track_ui_metric'; const apiPrefix = '/api/rollup'; -export async function loadJobs() { - const { jobs } = await getHttp().get(`${apiPrefix}/jobs`); +export async function loadJobs({ asSystemRequest } = {}) { + const fetchOptions = { asSystemRequest }; + const { jobs } = await getHttp().get(`${apiPrefix}/jobs`, fetchOptions); return jobs; } diff --git a/x-pack/plugins/rollup/public/crud_app/store/actions/refresh_jobs.js b/x-pack/plugins/rollup/public/crud_app/store/actions/refresh_jobs.js index 37b6e7a893fbe..562341a020523 100644 --- a/x-pack/plugins/rollup/public/crud_app/store/actions/refresh_jobs.js +++ b/x-pack/plugins/rollup/public/crud_app/store/actions/refresh_jobs.js @@ -10,10 +10,10 @@ import { i18n } from '@kbn/i18n'; import { loadJobs as sendLoadJobsRequest, deserializeJobs, showApiWarning } from '../../services'; import { REFRESH_JOBS_SUCCESS } from '../action_types'; -export const refreshJobs = () => async (dispatch) => { +export const refreshJobs = (options) => async (dispatch) => { let jobs; try { - jobs = await sendLoadJobsRequest(); + jobs = await sendLoadJobsRequest(options); } catch (error) { return showApiWarning( error, diff --git a/x-pack/plugins/saved_objects_tagging/common/types.ts b/x-pack/plugins/saved_objects_tagging/common/types.ts index bd65f74044bc1..c0b92a71a3d1b 100644 --- a/x-pack/plugins/saved_objects_tagging/common/types.ts +++ b/x-pack/plugins/saved_objects_tagging/common/types.ts @@ -21,5 +21,6 @@ export type TagWithRelations = Tag & { export type { Tag, TagAttributes, + GetAllTagsOptions, ITagsClient, } from '../../../../src/plugins/saved_objects_tagging_oss/common'; diff --git a/x-pack/plugins/saved_objects_tagging/public/plugin.ts b/x-pack/plugins/saved_objects_tagging/public/plugin.ts index 9821bfb397802..d4e3f8678fe1f 100644 --- a/x-pack/plugins/saved_objects_tagging/public/plugin.ts +++ b/x-pack/plugins/saved_objects_tagging/public/plugin.ts @@ -66,7 +66,7 @@ export class SavedObjectTaggingPlugin public start({ http, application, overlays }: CoreStart) { this.tagCache = new TagsCache({ - refreshHandler: () => this.tagClient!.getAll(), + refreshHandler: () => this.tagClient!.getAll({ asSystemRequest: true }), refreshInterval: this.config.cacheRefreshInterval, }); this.tagClient = new TagsClient({ http, changeListener: this.tagCache }); diff --git a/x-pack/plugins/saved_objects_tagging/public/services/tags/tags_client.test.ts b/x-pack/plugins/saved_objects_tagging/public/services/tags/tags_client.test.ts index 39e2df073591e..24409e8596265 100644 --- a/x-pack/plugins/saved_objects_tagging/public/services/tags/tags_client.test.ts +++ b/x-pack/plugins/saved_objects_tagging/public/services/tags/tags_client.test.ts @@ -156,7 +156,17 @@ describe('TagsClient', () => { await tagsClient.getAll(); expect(http.get).toHaveBeenCalledTimes(1); - expect(http.get).toHaveBeenCalledWith(`/api/saved_objects_tagging/tags`); + expect(http.get).toHaveBeenCalledWith(`/api/saved_objects_tagging/tags`, { + asSystemRequest: undefined, + }); + }); + it('allows `asSystemRequest` option to be set', async () => { + await tagsClient.getAll({ asSystemRequest: true }); + + expect(http.get).toHaveBeenCalledTimes(1); + expect(http.get).toHaveBeenCalledWith(`/api/saved_objects_tagging/tags`, { + asSystemRequest: true, + }); }); it('returns the tag objects from the response', async () => { const tags = await tagsClient.getAll(); diff --git a/x-pack/plugins/saved_objects_tagging/public/services/tags/tags_client.ts b/x-pack/plugins/saved_objects_tagging/public/services/tags/tags_client.ts index 8a99af7af6d02..ef484f0a550b1 100644 --- a/x-pack/plugins/saved_objects_tagging/public/services/tags/tags_client.ts +++ b/x-pack/plugins/saved_objects_tagging/public/services/tags/tags_client.ts @@ -6,7 +6,13 @@ */ import { HttpSetup } from 'src/core/public'; -import { Tag, TagAttributes, ITagsClient, TagWithRelations } from '../../../common/types'; +import { + Tag, + TagAttributes, + GetAllTagsOptions, + ITagsClient, + TagWithRelations, +} from '../../../common/types'; import { ITagsChangeListener } from './tags_cache'; export interface TagsClientOptions { @@ -83,8 +89,12 @@ export class TagsClient implements ITagInternalClient { return tag; } - public async getAll() { - const { tags } = await this.http.get<{ tags: Tag[] }>('/api/saved_objects_tagging/tags'); + public async getAll({ asSystemRequest }: GetAllTagsOptions = {}) { + const fetchOptions = { asSystemRequest }; + const { tags } = await this.http.get<{ tags: Tag[] }>( + '/api/saved_objects_tagging/tags', + fetchOptions + ); trapErrors(() => { if (this.changeListener) { diff --git a/x-pack/plugins/transform/public/app/hooks/use_api.ts b/x-pack/plugins/transform/public/app/hooks/use_api.ts index 580641cb86bc2..388bc8b432fc4 100644 --- a/x-pack/plugins/transform/public/app/hooks/use_api.ts +++ b/x-pack/plugins/transform/public/app/hooks/use_api.ts @@ -54,6 +54,10 @@ export interface FieldHistogramRequestConfig { type?: KBN_FIELD_TYPES; } +interface FetchOptions { + asSystemRequest?: boolean; +} + export const useApi = () => { const { http } = useAppDependencies(); @@ -68,9 +72,11 @@ export const useApi = () => { return e; } }, - async getTransforms(): Promise { + async getTransforms( + fetchOptions: FetchOptions = {} + ): Promise { try { - return await http.get(`${API_BASE_PATH}transforms`); + return await http.get(`${API_BASE_PATH}transforms`, fetchOptions); } catch (e) { return e; } @@ -84,9 +90,11 @@ export const useApi = () => { return e; } }, - async getTransformsStats(): Promise { + async getTransformsStats( + fetchOptions: FetchOptions = {} + ): Promise { try { - return await http.get(`${API_BASE_PATH}transforms/_stats`); + return await http.get(`${API_BASE_PATH}transforms/_stats`, fetchOptions); } catch (e) { return e; } diff --git a/x-pack/plugins/transform/public/app/hooks/use_get_transforms.ts b/x-pack/plugins/transform/public/app/hooks/use_get_transforms.ts index 919131341cd5b..dbb268b44cfd2 100644 --- a/x-pack/plugins/transform/public/app/hooks/use_get_transforms.ts +++ b/x-pack/plugins/transform/public/app/hooks/use_get_transforms.ts @@ -39,8 +39,9 @@ export const useGetTransforms = ( return; } - const transformConfigs = await api.getTransforms(); - const transformStats = await api.getTransformsStats(); + const fetchOptions = { asSystemRequest: true }; + const transformConfigs = await api.getTransforms(fetchOptions); + const transformStats = await api.getTransformsStats(fetchOptions); if ( !isGetTransformsResponseSchema(transformConfigs) || From da7ab81bdbdad9e74288e1975f8422ac462c5730 Mon Sep 17 00:00:00 2001 From: Gidi Meir Morris Date: Tue, 16 Feb 2021 15:00:32 +0000 Subject: [PATCH 04/53] [Docs][Alerting] fixed link to TM settings (#91368) (#91488) Fixed link to TM settings doc --- docs/setup/settings.asciidoc | 1 + docs/user/alerting/alerting-production-considerations.asciidoc | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/setup/settings.asciidoc b/docs/setup/settings.asciidoc index b57152646dda1..52966bf5ac8c9 100644 --- a/docs/setup/settings.asciidoc +++ b/docs/setup/settings.asciidoc @@ -684,3 +684,4 @@ include::secure-settings.asciidoc[] include::{kib-repo-dir}/settings/security-settings.asciidoc[] include::{kib-repo-dir}/settings/spaces-settings.asciidoc[] include::{kib-repo-dir}/settings/telemetry-settings.asciidoc[] +include::{kib-repo-dir}/settings/task-manager-settings.asciidoc[] diff --git a/docs/user/alerting/alerting-production-considerations.asciidoc b/docs/user/alerting/alerting-production-considerations.asciidoc index cc7adc87b150e..0442b760669cc 100644 --- a/docs/user/alerting/alerting-production-considerations.asciidoc +++ b/docs/user/alerting/alerting-production-considerations.asciidoc @@ -25,7 +25,7 @@ Because by default tasks are polled at 3 second intervals and only 10 tasks can * Many alerts or actions must be *run at once*. In this case pending tasks will queue in {es}, and be pulled 10 at a time from the queue at 3 second intervals. * *Long running tasks* occupy slots for an extended time, leaving fewer slots for other tasks. -For details on the settings that can influence the performance and throughput of Task Manager, see {task-manager-settings}. +For details on the settings that can influence the performance and throughput of Task Manager, see <>. ============================================== From 47f0d668704773b4964bdc2deb56183e75a70f21 Mon Sep 17 00:00:00 2001 From: Joe Reuter Date: Tue, 16 Feb 2021 16:07:44 +0100 Subject: [PATCH 05/53] fix readonly error (#91104) (#91476) --- x-pack/plugins/lens/public/lens_ui_telemetry/factory.ts | 6 ++++++ x-pack/plugins/lens/server/routes/telemetry.ts | 4 ++++ 2 files changed, 10 insertions(+) diff --git a/x-pack/plugins/lens/public/lens_ui_telemetry/factory.ts b/x-pack/plugins/lens/public/lens_ui_telemetry/factory.ts index b09d757b37141..f010c0b8114b5 100644 --- a/x-pack/plugins/lens/public/lens_ui_telemetry/factory.ts +++ b/x-pack/plugins/lens/public/lens_ui_telemetry/factory.ts @@ -98,6 +98,12 @@ export class LensReportManager { this.write(); } catch (e) { // Silent error because events will be reported during the next timer + + // If posting stats is forbidden for the current user, stop attempting to send them, + // but keep them in storage to push in case the user logs in with sufficient permissions at some point. + if (e.response && e.response.status === 403) { + this.stop(); + } } } } diff --git a/x-pack/plugins/lens/server/routes/telemetry.ts b/x-pack/plugins/lens/server/routes/telemetry.ts index d4eec5beaba90..cb8cf4b15f8d9 100644 --- a/x-pack/plugins/lens/server/routes/telemetry.ts +++ b/x-pack/plugins/lens/server/routes/telemetry.ts @@ -9,6 +9,7 @@ import Boom from '@hapi/boom'; import { errors } from '@elastic/elasticsearch'; import { CoreSetup } from 'src/core/server'; import { schema } from '@kbn/config-schema'; +import { SavedObjectsErrorHelpers } from '../../../../../src/core/server'; import { BASE_API_URL } from '../../common'; import { PluginStartContract } from '../plugin'; @@ -73,6 +74,9 @@ export async function initLensUsageRoute(setup: CoreSetup) return res.ok({ body: {} }); } catch (e) { + if (SavedObjectsErrorHelpers.isForbiddenError(e)) { + return res.forbidden(); + } if (e instanceof errors.ResponseError && e.statusCode === 404) { return res.notFound(); } From 57fbcd1c0c2d47efc475d2f293caf0431f665b71 Mon Sep 17 00:00:00 2001 From: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Date: Tue, 16 Feb 2021 11:02:14 -0500 Subject: [PATCH 06/53] [ML] Adding index pattern runtime fields to anomaly detection wizards (#91168) (#91480) * [ML] Adding index pattern runtime fields to anomaly detection wizards * hook refactor * small refactor of search json * fixing mml estimation error * changes based on review * sorting fields in metric selection * using useMemo rather than useState Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: James Gowdy --- .../ml/common/constants/aggregation_types.ts | 314 +++++++++++++++++ .../types/anomaly_detection_jobs/datafeed.ts | 3 +- x-pack/plugins/ml/common/types/fields.ts | 6 +- x-pack/plugins/ml/common/util/fields_utils.ts | 144 ++++++++ .../common/chart_loader/chart_loader.ts | 29 +- .../new_job/common/chart_loader/searches.ts | 5 +- .../new_job/common/job_creator/job_creator.ts | 90 +++-- .../util/model_memory_estimator.ts | 9 +- .../categorization_examples_loader.ts | 3 +- .../common/results_loader/results_loader.ts | 3 +- .../estimate_bucket_span.ts | 1 + .../multi_metric_view/metric_selection.tsx | 13 +- .../metric_selection_summary.tsx | 8 +- .../population_view/metric_selection.tsx | 13 +- .../metric_selection_summary.tsx | 5 +- .../single_metric_view/metric_selection.tsx | 11 +- .../metric_selection_summary.tsx | 3 +- .../components/split_field/by_field.tsx | 13 +- .../components/split_field/split_field.tsx | 13 +- .../services/ml_api_service/index.ts | 2 + .../services/ml_api_service/jobs.ts | 14 +- .../services/new_job_capabilities_service.ts | 6 +- .../bucket_span_estimator.d.ts | 2 + .../bucket_span_estimator.js | 24 +- .../bucket_span_estimator.test.ts | 1 + .../single_series_checker.js | 5 +- .../calculate_model_memory_limit.ts | 3 +- .../models/fields_service/fields_service.ts | 5 +- .../new_job/categorization/examples.ts | 11 +- .../models/job_service/new_job/line_chart.ts | 16 +- .../job_service/new_job/population_chart.ts | 17 +- .../job_service/new_job_caps/aggregations.ts | 325 ------------------ .../job_service/new_job_caps/field_service.ts | 126 +------ .../models/job_service/new_job_caps/rollup.ts | 5 +- .../plugins/ml/server/routes/job_service.ts | 12 +- .../routes/schemas/job_service_schema.ts | 2 + .../routes/schemas/job_validation_schema.ts | 1 + 37 files changed, 722 insertions(+), 541 deletions(-) create mode 100644 x-pack/plugins/ml/common/util/fields_utils.ts delete mode 100644 x-pack/plugins/ml/server/models/job_service/new_job_caps/aggregations.ts diff --git a/x-pack/plugins/ml/common/constants/aggregation_types.ts b/x-pack/plugins/ml/common/constants/aggregation_types.ts index 7278f1de8b9a7..e5e1543c555f3 100644 --- a/x-pack/plugins/ml/common/constants/aggregation_types.ts +++ b/x-pack/plugins/ml/common/constants/aggregation_types.ts @@ -5,6 +5,8 @@ * 2.0. */ +import { Aggregation, METRIC_AGG_TYPE } from '../types/fields'; + export enum ML_JOB_AGGREGATION { // count COUNT = 'count', @@ -84,3 +86,315 @@ export enum ES_AGGREGATION { PERCENTILES = 'percentiles', CARDINALITY = 'cardinality', } + +// aggregation object missing id, title and fields and has null for kibana and dsl aggregation names. +// this is used as the basis for the ML only aggregations +function getBasicMlOnlyAggregation(): Omit { + return { + kibanaName: null, + dslName: null, + type: METRIC_AGG_TYPE, + mlModelPlotAgg: { + max: KIBANA_AGGREGATION.MAX, + min: KIBANA_AGGREGATION.MIN, + }, + }; +} + +// list of aggregations only support by ML and which don't have an equivalent ES aggregation +// note, not all aggs have a field list. Some aggs cannot be used with a field. +export const mlOnlyAggregations: Aggregation[] = [ + { + id: ML_JOB_AGGREGATION.NON_ZERO_COUNT, + title: 'Non zero count', + ...getBasicMlOnlyAggregation(), + }, + { + id: ML_JOB_AGGREGATION.HIGH_NON_ZERO_COUNT, + title: 'High non zero count', + ...getBasicMlOnlyAggregation(), + }, + { + id: ML_JOB_AGGREGATION.LOW_NON_ZERO_COUNT, + title: 'Low non zero count', + ...getBasicMlOnlyAggregation(), + }, + { + id: ML_JOB_AGGREGATION.HIGH_DISTINCT_COUNT, + title: 'High distinct count', + fields: [], + ...getBasicMlOnlyAggregation(), + }, + { + id: ML_JOB_AGGREGATION.LOW_DISTINCT_COUNT, + title: 'Low distinct count', + fields: [], + ...getBasicMlOnlyAggregation(), + }, + { + id: ML_JOB_AGGREGATION.METRIC, + title: 'Metric', + fields: [], + ...getBasicMlOnlyAggregation(), + }, + { + id: ML_JOB_AGGREGATION.VARP, + title: 'varp', + fields: [], + ...getBasicMlOnlyAggregation(), + }, + { + id: ML_JOB_AGGREGATION.HIGH_VARP, + title: 'High varp', + fields: [], + ...getBasicMlOnlyAggregation(), + }, + { + id: ML_JOB_AGGREGATION.LOW_VARP, + title: 'Low varp', + fields: [], + ...getBasicMlOnlyAggregation(), + }, + { + id: ML_JOB_AGGREGATION.NON_NULL_SUM, + title: 'Non null sum', + fields: [], + ...getBasicMlOnlyAggregation(), + }, + { + id: ML_JOB_AGGREGATION.HIGH_NON_NULL_SUM, + title: 'High non null sum', + fields: [], + ...getBasicMlOnlyAggregation(), + }, + { + id: ML_JOB_AGGREGATION.LOW_NON_NULL_SUM, + title: 'Low non null sum', + fields: [], + ...getBasicMlOnlyAggregation(), + }, + { + id: ML_JOB_AGGREGATION.RARE, + title: 'Rare', + ...getBasicMlOnlyAggregation(), + }, + { + id: ML_JOB_AGGREGATION.FREQ_RARE, + title: 'Freq rare', + ...getBasicMlOnlyAggregation(), + }, + { + id: ML_JOB_AGGREGATION.INFO_CONTENT, + title: 'Info content', + fields: [], + ...getBasicMlOnlyAggregation(), + }, + { + id: ML_JOB_AGGREGATION.HIGH_INFO_CONTENT, + title: 'High info content', + fields: [], + ...getBasicMlOnlyAggregation(), + }, + { + id: ML_JOB_AGGREGATION.LOW_INFO_CONTENT, + title: 'Low info content', + fields: [], + ...getBasicMlOnlyAggregation(), + }, + { + id: ML_JOB_AGGREGATION.TIME_OF_DAY, + title: 'Time of day', + ...getBasicMlOnlyAggregation(), + }, + { + id: ML_JOB_AGGREGATION.TIME_OF_WEEK, + title: 'Time of week', + ...getBasicMlOnlyAggregation(), + }, + { + id: ML_JOB_AGGREGATION.LAT_LONG, + title: 'Lat long', + fields: [], + ...getBasicMlOnlyAggregation(), + }, +]; + +export const aggregations: Aggregation[] = [ + { + id: ML_JOB_AGGREGATION.COUNT, + title: 'Count', + kibanaName: KIBANA_AGGREGATION.COUNT, + dslName: ES_AGGREGATION.COUNT, + type: METRIC_AGG_TYPE, + mlModelPlotAgg: { + max: KIBANA_AGGREGATION.MAX, + min: KIBANA_AGGREGATION.MIN, + }, + }, + { + id: ML_JOB_AGGREGATION.HIGH_COUNT, + title: 'High count', + kibanaName: KIBANA_AGGREGATION.COUNT, + dslName: ES_AGGREGATION.COUNT, + type: METRIC_AGG_TYPE, + mlModelPlotAgg: { + max: KIBANA_AGGREGATION.MAX, + min: KIBANA_AGGREGATION.MIN, + }, + }, + { + id: ML_JOB_AGGREGATION.LOW_COUNT, + title: 'Low count', + kibanaName: KIBANA_AGGREGATION.COUNT, + dslName: ES_AGGREGATION.COUNT, + type: METRIC_AGG_TYPE, + mlModelPlotAgg: { + max: KIBANA_AGGREGATION.MAX, + min: KIBANA_AGGREGATION.MIN, + }, + }, + { + id: ML_JOB_AGGREGATION.MEAN, + title: 'Mean', + kibanaName: KIBANA_AGGREGATION.AVG, + dslName: ES_AGGREGATION.AVG, + type: METRIC_AGG_TYPE, + mlModelPlotAgg: { + max: KIBANA_AGGREGATION.AVG, + min: KIBANA_AGGREGATION.AVG, + }, + fields: [], + }, + { + id: ML_JOB_AGGREGATION.HIGH_MEAN, + title: 'High mean', + kibanaName: KIBANA_AGGREGATION.AVG, + dslName: ES_AGGREGATION.AVG, + type: METRIC_AGG_TYPE, + mlModelPlotAgg: { + max: KIBANA_AGGREGATION.AVG, + min: KIBANA_AGGREGATION.AVG, + }, + fields: [], + }, + { + id: ML_JOB_AGGREGATION.LOW_MEAN, + title: 'Low mean', + kibanaName: KIBANA_AGGREGATION.AVG, + dslName: ES_AGGREGATION.AVG, + type: METRIC_AGG_TYPE, + mlModelPlotAgg: { + max: KIBANA_AGGREGATION.AVG, + min: KIBANA_AGGREGATION.AVG, + }, + fields: [], + }, + { + id: ML_JOB_AGGREGATION.SUM, + title: 'Sum', + kibanaName: KIBANA_AGGREGATION.SUM, + dslName: ES_AGGREGATION.SUM, + type: METRIC_AGG_TYPE, + mlModelPlotAgg: { + max: KIBANA_AGGREGATION.SUM, + min: KIBANA_AGGREGATION.SUM, + }, + fields: [], + }, + { + id: ML_JOB_AGGREGATION.HIGH_SUM, + title: 'High sum', + kibanaName: KIBANA_AGGREGATION.SUM, + dslName: ES_AGGREGATION.SUM, + type: METRIC_AGG_TYPE, + mlModelPlotAgg: { + max: KIBANA_AGGREGATION.SUM, + min: KIBANA_AGGREGATION.SUM, + }, + fields: [], + }, + { + id: ML_JOB_AGGREGATION.LOW_SUM, + title: 'Low sum', + kibanaName: KIBANA_AGGREGATION.SUM, + dslName: ES_AGGREGATION.SUM, + type: METRIC_AGG_TYPE, + mlModelPlotAgg: { + max: KIBANA_AGGREGATION.SUM, + min: KIBANA_AGGREGATION.SUM, + }, + fields: [], + }, + { + id: ML_JOB_AGGREGATION.MEDIAN, + title: 'Median', + kibanaName: KIBANA_AGGREGATION.MEDIAN, + dslName: ES_AGGREGATION.PERCENTILES, + type: METRIC_AGG_TYPE, + mlModelPlotAgg: { + max: KIBANA_AGGREGATION.MAX, + min: KIBANA_AGGREGATION.MIN, + }, + fields: [], + }, + { + id: ML_JOB_AGGREGATION.HIGH_MEDIAN, + title: 'High median', + kibanaName: KIBANA_AGGREGATION.MEDIAN, + dslName: ES_AGGREGATION.PERCENTILES, + type: METRIC_AGG_TYPE, + mlModelPlotAgg: { + max: KIBANA_AGGREGATION.MAX, + min: KIBANA_AGGREGATION.MIN, + }, + fields: [], + }, + { + id: ML_JOB_AGGREGATION.LOW_MEDIAN, + title: 'Low median', + kibanaName: KIBANA_AGGREGATION.MEDIAN, + dslName: ES_AGGREGATION.PERCENTILES, + type: METRIC_AGG_TYPE, + mlModelPlotAgg: { + max: KIBANA_AGGREGATION.MAX, + min: KIBANA_AGGREGATION.MIN, + }, + fields: [], + }, + { + id: ML_JOB_AGGREGATION.MIN, + title: 'Min', + kibanaName: KIBANA_AGGREGATION.MIN, + dslName: ES_AGGREGATION.MIN, + type: METRIC_AGG_TYPE, + mlModelPlotAgg: { + max: KIBANA_AGGREGATION.MIN, + min: KIBANA_AGGREGATION.MIN, + }, + fields: [], + }, + { + id: ML_JOB_AGGREGATION.MAX, + title: 'Max', + kibanaName: KIBANA_AGGREGATION.MAX, + dslName: ES_AGGREGATION.MAX, + type: METRIC_AGG_TYPE, + mlModelPlotAgg: { + max: KIBANA_AGGREGATION.MAX, + min: KIBANA_AGGREGATION.MAX, + }, + fields: [], + }, + { + id: ML_JOB_AGGREGATION.DISTINCT_COUNT, + title: 'Distinct count', + kibanaName: KIBANA_AGGREGATION.CARDINALITY, + dslName: ES_AGGREGATION.CARDINALITY, + type: METRIC_AGG_TYPE, + mlModelPlotAgg: { + max: KIBANA_AGGREGATION.MAX, + min: KIBANA_AGGREGATION.MIN, + }, + fields: [], + }, +]; diff --git a/x-pack/plugins/ml/common/types/anomaly_detection_jobs/datafeed.ts b/x-pack/plugins/ml/common/types/anomaly_detection_jobs/datafeed.ts index 77466d2741536..06938485649fb 100644 --- a/x-pack/plugins/ml/common/types/anomaly_detection_jobs/datafeed.ts +++ b/x-pack/plugins/ml/common/types/anomaly_detection_jobs/datafeed.ts @@ -6,6 +6,7 @@ */ import { IndexPatternTitle } from '../kibana'; +import { RuntimeMappings } from '../fields'; import { JobId } from './job'; export type DatafeedId = string; @@ -21,7 +22,7 @@ export interface Datafeed { query: object; query_delay?: string; script_fields?: Record; - runtime_mappings?: Record; + runtime_mappings?: RuntimeMappings; scroll_size?: number; delayed_data_check_config?: object; indices_options?: IndicesOptions; diff --git a/x-pack/plugins/ml/common/types/fields.ts b/x-pack/plugins/ml/common/types/fields.ts index f12ed5b23542a..ae157cef5735f 100644 --- a/x-pack/plugins/ml/common/types/fields.ts +++ b/x-pack/plugins/ml/common/types/fields.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { ES_FIELD_TYPES } from '../../../../../src/plugins/data/common'; +import { ES_FIELD_TYPES, RuntimeField } from '../../../../../src/plugins/data/common'; import { ML_JOB_AGGREGATION, KIBANA_AGGREGATION, @@ -27,6 +27,7 @@ export interface Field { aggregatable?: boolean; aggIds?: AggId[]; aggs?: Aggregation[]; + runtimeField?: RuntimeField; } export interface Aggregation { @@ -103,3 +104,6 @@ export interface ScriptAggCardinality { export interface AggCardinality { cardinality: FieldAggCardinality | ScriptAggCardinality; } + +export type RollupFields = Record]>; +export type RuntimeMappings = Record; diff --git a/x-pack/plugins/ml/common/util/fields_utils.ts b/x-pack/plugins/ml/common/util/fields_utils.ts new file mode 100644 index 0000000000000..98b0fcd6efd80 --- /dev/null +++ b/x-pack/plugins/ml/common/util/fields_utils.ts @@ -0,0 +1,144 @@ +/* + * 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 { + Field, + Aggregation, + NewJobCaps, + METRIC_AGG_TYPE, + RollupFields, + EVENT_RATE_FIELD_ID, +} from '../types/fields'; +import { ES_FIELD_TYPES } from '../../../../../src/plugins/data/common'; +import { ML_JOB_AGGREGATION } from '../constants/aggregation_types'; + +// cross reference fields and aggs. +// fields contain a list of aggs that are compatible, and vice versa. +export function combineFieldsAndAggs( + fields: Field[], + aggs: Aggregation[], + rollupFields: RollupFields +): NewJobCaps { + const keywordFields = getKeywordFields(fields); + const textFields = getTextFields(fields); + const numericalFields = getNumericalFields(fields); + const ipFields = getIpFields(fields); + const geoFields = getGeoFields(fields); + + const isRollup = Object.keys(rollupFields).length > 0; + const mix = mixFactory(isRollup, rollupFields); + + aggs.forEach((a) => { + if (a.type === METRIC_AGG_TYPE && a.fields !== undefined) { + switch (a.id) { + case ML_JOB_AGGREGATION.LAT_LONG: + geoFields.forEach((f) => mix(f, a)); + break; + case ML_JOB_AGGREGATION.INFO_CONTENT: + case ML_JOB_AGGREGATION.HIGH_INFO_CONTENT: + case ML_JOB_AGGREGATION.LOW_INFO_CONTENT: + textFields.forEach((f) => mix(f, a)); + case ML_JOB_AGGREGATION.DISTINCT_COUNT: + case ML_JOB_AGGREGATION.HIGH_DISTINCT_COUNT: + case ML_JOB_AGGREGATION.LOW_DISTINCT_COUNT: + // distinct count (i.e. cardinality) takes keywords, ips + // as well as numerical fields + keywordFields.forEach((f) => mix(f, a)); + ipFields.forEach((f) => mix(f, a)); + // note, no break to fall through to add numerical fields. + default: + // all other aggs take numerical fields + numericalFields.forEach((f) => { + mix(f, a); + }); + break; + } + } + }); + + return { + aggs, + fields: isRollup ? filterFields(fields) : fields, + }; +} + +// remove fields that have no aggs associated to them, unless they are date fields +function filterFields(fields: Field[]): Field[] { + return fields.filter( + (f) => f.aggs && (f.aggs.length > 0 || (f.aggs.length === 0 && f.type === ES_FIELD_TYPES.DATE)) + ); +} + +// returns a mix function that is used to cross-reference aggs and fields. +// wrapped in a provider to allow filtering based on rollup job capabilities +function mixFactory(isRollup: boolean, rollupFields: RollupFields) { + return function mix(field: Field, agg: Aggregation): void { + if ( + isRollup === false || + (rollupFields[field.id] && rollupFields[field.id].find((f) => f.agg === agg.dslName)) + ) { + if (field.aggs !== undefined) { + field.aggs.push(agg); + } + if (agg.fields !== undefined) { + agg.fields.push(field); + } + } + }; +} + +function getKeywordFields(fields: Field[]): Field[] { + return fields.filter((f) => f.type === ES_FIELD_TYPES.KEYWORD); +} + +function getTextFields(fields: Field[]): Field[] { + return fields.filter((f) => f.type === ES_FIELD_TYPES.TEXT); +} + +function getIpFields(fields: Field[]): Field[] { + return fields.filter((f) => f.type === ES_FIELD_TYPES.IP); +} + +function getNumericalFields(fields: Field[]): Field[] { + return fields.filter( + (f) => + f.type === ES_FIELD_TYPES.LONG || + f.type === ES_FIELD_TYPES.UNSIGNED_LONG || + f.type === ES_FIELD_TYPES.INTEGER || + f.type === ES_FIELD_TYPES.SHORT || + f.type === ES_FIELD_TYPES.BYTE || + f.type === ES_FIELD_TYPES.DOUBLE || + f.type === ES_FIELD_TYPES.FLOAT || + f.type === ES_FIELD_TYPES.HALF_FLOAT || + f.type === ES_FIELD_TYPES.SCALED_FLOAT + ); +} + +function getGeoFields(fields: Field[]): Field[] { + return fields.filter( + (f) => f.type === ES_FIELD_TYPES.GEO_POINT || f.type === ES_FIELD_TYPES.GEO_SHAPE + ); +} + +/** + * Sort fields by name, keeping event rate at the beginning + */ +export function sortFields(fields: Field[]) { + if (fields.length === 0) { + return fields; + } + + let eventRate: Field | undefined; + if (fields[0].id === EVENT_RATE_FIELD_ID) { + [eventRate] = fields.splice(0, 1); + } + fields.sort((a, b) => a.name.localeCompare(b.name)); + if (eventRate !== undefined) { + fields.splice(0, 0, eventRate); + } + return fields; +} diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/common/chart_loader/chart_loader.ts b/x-pack/plugins/ml/public/application/jobs/new_job/common/chart_loader/chart_loader.ts index 1c56be94d5891..a36e52f4e863b 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/common/chart_loader/chart_loader.ts +++ b/x-pack/plugins/ml/public/application/jobs/new_job/common/chart_loader/chart_loader.ts @@ -8,12 +8,17 @@ import memoizeOne from 'memoize-one'; import { isEqual } from 'lodash'; import { IndexPatternTitle } from '../../../../../../common/types/kibana'; -import { Field, SplitField, AggFieldPair } from '../../../../../../common/types/fields'; +import { + Field, + SplitField, + AggFieldPair, + RuntimeMappings, +} from '../../../../../../common/types/fields'; import { ml } from '../../../../services/ml_api_service'; import { mlResultsService } from '../../../../services/results_service'; import { getCategoryFields as getCategoryFieldsOrig } from './searches'; import { aggFieldPairsCanBeCharted } from '../job_creator/util/general'; -import { IndexPattern } from '../../../../../../../../../src/plugins/data/public'; +import { IndexPattern } from '../../../../../../../../../src/plugins/data/common'; type DetectorIndex = number; export interface LineChartPoint { @@ -50,7 +55,8 @@ export class ChartLoader { aggFieldPairs: AggFieldPair[], splitField: SplitField, splitFieldValue: SplitFieldValue, - intervalMs: number + intervalMs: number, + runtimeMappings: RuntimeMappings | null ): Promise { if (this._timeFieldName !== '') { if (aggFieldPairsCanBeCharted(aggFieldPairs) === false) { @@ -70,7 +76,8 @@ export class ChartLoader { this._query, aggFieldPairNames, splitFieldName, - splitFieldValue + splitFieldValue, + runtimeMappings ?? undefined ); return resp.results; @@ -83,7 +90,8 @@ export class ChartLoader { end: number, aggFieldPairs: AggFieldPair[], splitField: SplitField, - intervalMs: number + intervalMs: number, + runtimeMappings: RuntimeMappings | null ): Promise { if (this._timeFieldName !== '') { if (aggFieldPairsCanBeCharted(aggFieldPairs) === false) { @@ -102,7 +110,8 @@ export class ChartLoader { intervalMs, this._query, aggFieldPairNames, - splitFieldName + splitFieldName, + runtimeMappings ?? undefined ); return resp.results; @@ -136,12 +145,16 @@ export class ChartLoader { return []; } - async loadFieldExampleValues(field: Field): Promise { + async loadFieldExampleValues( + field: Field, + runtimeMappings: RuntimeMappings | null + ): Promise { const { results } = await getCategoryFields( this._indexPatternTitle, field.name, 10, - this._query + this._query, + runtimeMappings ?? undefined ); return results; } diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/common/chart_loader/searches.ts b/x-pack/plugins/ml/public/application/jobs/new_job/common/chart_loader/searches.ts index 1e7ee9ca45bf1..54917c4884f22 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/common/chart_loader/searches.ts +++ b/x-pack/plugins/ml/public/application/jobs/new_job/common/chart_loader/searches.ts @@ -8,6 +8,7 @@ import { get } from 'lodash'; import { ml } from '../../../../services/ml_api_service'; +import { RuntimeMappings } from '../../../../../../common/types/fields'; interface CategoryResults { success: boolean; @@ -18,7 +19,8 @@ export function getCategoryFields( indexPatternName: string, fieldName: string, size: number, - query: any + query: any, + runtimeMappings?: RuntimeMappings ): Promise { return new Promise((resolve, reject) => { ml.esSearch({ @@ -34,6 +36,7 @@ export function getCategoryFields( }, }, }, + ...(runtimeMappings !== undefined ? { runtime_mappings: runtimeMappings } : {}), }, }) .then((resp: any) => { diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/common/job_creator/job_creator.ts b/x-pack/plugins/ml/public/application/jobs/new_job/common/job_creator/job_creator.ts index 913832e2fb8a3..ca2c2204fb0c1 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/common/job_creator/job_creator.ts +++ b/x-pack/plugins/ml/public/application/jobs/new_job/common/job_creator/job_creator.ts @@ -6,10 +6,15 @@ */ import { BehaviorSubject } from 'rxjs'; +import { cloneDeep } from 'lodash'; import { SavedSearchSavedObject } from '../../../../../../common/types/kibana'; import { UrlConfig } from '../../../../../../common/types/custom_urls'; import { IndexPatternTitle } from '../../../../../../common/types/kibana'; -import { ML_JOB_AGGREGATION } from '../../../../../../common/constants/aggregation_types'; +import { + ML_JOB_AGGREGATION, + aggregations, + mlOnlyAggregations, +} from '../../../../../../common/constants/aggregation_types'; import { ES_FIELD_TYPES } from '../../../../../../../../../src/plugins/data/public'; import { Job, @@ -20,7 +25,8 @@ import { BucketSpan, CustomSettings, } from '../../../../../../common/types/anomaly_detection_jobs'; -import { Aggregation, Field } from '../../../../../../common/types/fields'; +import { Aggregation, Field, RuntimeMappings } from '../../../../../../common/types/fields'; +import { combineFieldsAndAggs } from '../../../../../../common/util/fields_utils'; import { createEmptyJob, createEmptyDatafeed } from './util/default_configs'; import { mlJobService } from '../../../../services/job_service'; import { JobRunner, ProgressSubscriber } from '../job_runner'; @@ -57,7 +63,8 @@ export class JobCreator { protected _aggs: Aggregation[] = []; protected _fields: Field[] = []; protected _scriptFields: Field[] = []; - protected _runtimeMappings: Field[] = []; + protected _runtimeFields: Field[] = []; + protected _runtimeMappings: RuntimeMappings | null = null; protected _aggregationFields: Field[] = []; protected _sparseData: boolean = false; private _stopAllRefreshPolls: { @@ -86,6 +93,8 @@ export class JobCreator { this._job_config.data_description.time_field = indexPattern.timeFieldName; } + this._extractRuntimeMappings(); + this._datafeed_config.query = query; } @@ -489,16 +498,20 @@ export class JobCreator { return this._scriptFields; } - public get runtimeMappings(): Field[] { + public get runtimeMappings(): RuntimeMappings | null { return this._runtimeMappings; } + public get runtimeFields(): Field[] { + return this._runtimeFields; + } + public get aggregationFields(): Field[] { return this._aggregationFields; } public get additionalFields(): Field[] { - return [...this._scriptFields, ...this._runtimeMappings, ...this._aggregationFields]; + return [...this._scriptFields, ...this._runtimeFields, ...this._aggregationFields]; } public get subscribers(): ProgressSubscriber[] { @@ -662,6 +675,52 @@ export class JobCreator { this._job_config.analysis_config.per_partition_categorization!.stop_on_warn = enabled; } + private _extractRuntimeMappings() { + const runtimeFieldMap = this._indexPattern.toSpec().runtimeFieldMap; + if (runtimeFieldMap !== undefined) { + if (this._datafeed_config.runtime_mappings === undefined) { + this._datafeed_config.runtime_mappings = {}; + } + Object.entries(runtimeFieldMap).forEach(([key, val]) => { + this._datafeed_config.runtime_mappings![key] = val; + }); + } + this._populateRuntimeFields(); + } + + private _populateRuntimeFields() { + this._runtimeFields = []; + this._runtimeMappings = this._datafeed_config.runtime_mappings ?? null; + if (this._runtimeMappings !== null) { + const tempRuntimeFields = Object.entries(this._runtimeMappings).map( + ([id, runtimeField]) => + ({ + id, + name: id, + type: runtimeField.type, + aggregatable: true, + aggs: [], + runtimeField, + } as Field) + ); + + const aggs = cloneDeep([...aggregations, ...mlOnlyAggregations]); + this._runtimeFields = combineFieldsAndAggs(tempRuntimeFields, aggs, {}).fields; + } + } + + private _populateScriptFields() { + this._scriptFields = []; + if (this._datafeed_config.script_fields !== undefined) { + this._scriptFields = Object.keys(this._datafeed_config.script_fields).map((f) => ({ + id: f, + name: f, + type: ES_FIELD_TYPES.KEYWORD, + aggregatable: true, + })); + } + } + protected _overrideConfigs(job: Job, datafeed: Datafeed) { this._job_config = job; this._datafeed_config = datafeed; @@ -683,25 +742,8 @@ export class JobCreator { this.useDedicatedIndex = true; } - this._scriptFields = []; - if (this._datafeed_config.script_fields !== undefined) { - this._scriptFields = Object.keys(this._datafeed_config.script_fields).map((f) => ({ - id: f, - name: f, - type: ES_FIELD_TYPES.KEYWORD, - aggregatable: true, - })); - } - - this._runtimeMappings = []; - if (this._datafeed_config.runtime_mappings !== undefined) { - this._runtimeMappings = Object.keys(this._datafeed_config.runtime_mappings).map((f) => ({ - id: f, - name: f, - type: ES_FIELD_TYPES.KEYWORD, - aggregatable: true, - })); - } + this._populateScriptFields(); + this._populateRuntimeFields(); this._aggregationFields = []; const aggs = getDatafeedAggregations(this._datafeed_config); diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/common/job_creator/util/model_memory_estimator.ts b/x-pack/plugins/ml/public/application/jobs/new_job/common/job_creator/util/model_memory_estimator.ts index 289bfb54b2855..1f0acfcbec5c8 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/common/job_creator/util/model_memory_estimator.ts +++ b/x-pack/plugins/ml/public/application/jobs/new_job/common/job_creator/util/model_memory_estimator.ts @@ -24,7 +24,11 @@ import { useEffect, useMemo } from 'react'; import { DEFAULT_MODEL_MEMORY_LIMIT } from '../../../../../../../common/constants/new_job'; import { ml } from '../../../../../services/ml_api_service'; import { JobValidator, VALIDATION_DELAY_MS } from '../../job_validator/job_validator'; -import { MLHttpFetchError, MLResponseError } from '../../../../../../../common/util/errors'; +import { + MLHttpFetchError, + MLResponseError, + extractErrorMessage, +} from '../../../../../../../common/util/errors'; import { useMlKibana } from '../../../../../contexts/kibana'; import { JobCreator } from '../job_creator'; @@ -121,8 +125,7 @@ export const useModelMemoryEstimator = ( title: i18n.translate('xpack.ml.newJob.wizard.estimateModelMemoryError', { defaultMessage: 'Model memory limit could not be calculated', }), - text: - error.body.attributes?.body.error.caused_by?.reason || error.body.message || undefined, + text: extractErrorMessage(error), }); }) ); diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/common/results_loader/categorization_examples_loader.ts b/x-pack/plugins/ml/public/application/jobs/new_job/common/results_loader/categorization_examples_loader.ts index b240d6f230b89..06d489ee5a437 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/common/results_loader/categorization_examples_loader.ts +++ b/x-pack/plugins/ml/public/application/jobs/new_job/common/results_loader/categorization_examples_loader.ts @@ -50,7 +50,8 @@ export class CategorizationExamplesLoader { this._timeFieldName, this._jobCreator.start, this._jobCreator.end, - analyzer + analyzer, + this._jobCreator.runtimeMappings ?? undefined ); return resp; } diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/common/results_loader/results_loader.ts b/x-pack/plugins/ml/public/application/jobs/new_job/common/results_loader/results_loader.ts index 86f7e494870bb..c4365bd656f9e 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/common/results_loader/results_loader.ts +++ b/x-pack/plugins/ml/public/application/jobs/new_job/common/results_loader/results_loader.ts @@ -255,7 +255,8 @@ export class ResultsLoader { if (isMultiMetricJobCreator(this._jobCreator)) { if (this._jobCreator.splitField !== null) { const fieldValues = await this._chartLoader.loadFieldExampleValues( - this._jobCreator.splitField + this._jobCreator.splitField, + this._jobCreator.runtimeMappings ); if (fieldValues.length > 0) { this._detectorSplitFieldFilters = { diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/bucket_span_estimator/estimate_bucket_span.ts b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/bucket_span_estimator/estimate_bucket_span.ts index 1e5487057bfb5..f0932b09af46b 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/bucket_span_estimator/estimate_bucket_span.ts +++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/bucket_span_estimator/estimate_bucket_span.ts @@ -40,6 +40,7 @@ export function useEstimateBucketSpan() { query: mlContext.combinedQuery, splitField: undefined, timeField: mlContext.currentIndexPattern.timeFieldName, + runtimeMappings: jobCreator.runtimeMappings ?? undefined, }; if ( diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/multi_metric_view/metric_selection.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/multi_metric_view/metric_selection.tsx index 9497114d056ba..5bf4beacc1593 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/multi_metric_view/metric_selection.tsx +++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/multi_metric_view/metric_selection.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { Fragment, FC, useContext, useEffect, useState } from 'react'; +import React, { Fragment, FC, useContext, useEffect, useState, useMemo } from 'react'; import { JobCreatorContext } from '../../../job_creator_context'; import { MultiMetricJobCreator } from '../../../../../common/job_creator'; @@ -13,6 +13,7 @@ import { LineChartData } from '../../../../../common/chart_loader'; import { DropDownLabel, DropDownProps } from '../agg_select'; import { newJobCapsService } from '../../../../../../../services/new_job_capabilities_service'; import { AggFieldPair } from '../../../../../../../../../common/types/fields'; +import { sortFields } from '../../../../../../../../../common/util/fields_utils'; import { getChartSettings, defaultChartSettings } from '../../../charts/common/settings'; import { MetricSelector } from './metric_selector'; import { ChartGrid } from './chart_grid'; @@ -33,7 +34,10 @@ export const MultiMetricDetectors: FC = ({ setIsValid }) => { const jobCreator = jc as MultiMetricJobCreator; - const { fields } = newJobCapsService; + const fields = useMemo( + () => sortFields([...newJobCapsService.fields, ...jobCreator.runtimeFields]), + [] + ); const [selectedOptions, setSelectedOptions] = useState([]); const [aggFieldPairList, setAggFieldPairList] = useState( jobCreator.aggFieldPairs @@ -107,7 +111,7 @@ export const MultiMetricDetectors: FC = ({ setIsValid }) => { useEffect(() => { if (splitField !== null) { chartLoader - .loadFieldExampleValues(splitField) + .loadFieldExampleValues(splitField, jobCreator.runtimeMappings) .then(setFieldValues) .catch((error) => { getToastNotificationService().displayErrorToast(error); @@ -135,7 +139,8 @@ export const MultiMetricDetectors: FC = ({ setIsValid }) => { aggFieldPairList, jobCreator.splitField, fieldValues.length > 0 ? fieldValues[0] : null, - cs.intervalMs + cs.intervalMs, + jobCreator.runtimeMappings ); setLineChartsData(resp); } catch (error) { diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/multi_metric_view/metric_selection_summary.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/multi_metric_view/metric_selection_summary.tsx index fc94d2b096012..11f2f60e17d3d 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/multi_metric_view/metric_selection_summary.tsx +++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/multi_metric_view/metric_selection_summary.tsx @@ -41,7 +41,10 @@ export const MultiMetricDetectorsSummary: FC = () => { (async () => { if (jobCreator.splitField !== null) { try { - const tempFieldValues = await chartLoader.loadFieldExampleValues(jobCreator.splitField); + const tempFieldValues = await chartLoader.loadFieldExampleValues( + jobCreator.splitField, + jobCreator.runtimeMappings + ); setFieldValues(tempFieldValues); } catch (error) { getToastNotificationService().displayErrorToast(error); @@ -72,7 +75,8 @@ export const MultiMetricDetectorsSummary: FC = () => { jobCreator.aggFieldPairs, jobCreator.splitField, fieldValues.length > 0 ? fieldValues[0] : null, - cs.intervalMs + cs.intervalMs, + jobCreator.runtimeMappings ); setLineChartsData(resp); } catch (error) { diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/population_view/metric_selection.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/population_view/metric_selection.tsx index c1e291567ddc7..aba2acfa41a85 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/population_view/metric_selection.tsx +++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/population_view/metric_selection.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { Fragment, FC, useContext, useEffect, useState, useReducer } from 'react'; +import React, { Fragment, FC, useContext, useEffect, useState, useReducer, useMemo } from 'react'; import { EuiHorizontalRule } from '@elastic/eui'; import { JobCreatorContext } from '../../../job_creator_context'; @@ -14,6 +14,7 @@ import { LineChartData } from '../../../../../common/chart_loader'; import { DropDownLabel, DropDownProps } from '../agg_select'; import { newJobCapsService } from '../../../../../../../services/new_job_capabilities_service'; import { Field, AggFieldPair } from '../../../../../../../../../common/types/fields'; +import { sortFields } from '../../../../../../../../../common/util/fields_utils'; import { getChartSettings, defaultChartSettings } from '../../../charts/common/settings'; import { MetricSelector } from './metric_selector'; import { SplitFieldSelector } from '../split_field'; @@ -36,7 +37,10 @@ export const PopulationDetectors: FC = ({ setIsValid }) => { } = useContext(JobCreatorContext); const jobCreator = jc as PopulationJobCreator; - const { fields } = newJobCapsService; + const fields = useMemo( + () => sortFields([...newJobCapsService.fields, ...jobCreator.runtimeFields]), + [] + ); const [selectedOptions, setSelectedOptions] = useState([]); const [aggFieldPairList, setAggFieldPairList] = useState( jobCreator.aggFieldPairs @@ -155,7 +159,8 @@ export const PopulationDetectors: FC = ({ setIsValid }) => { jobCreator.end, aggFieldPairList, jobCreator.splitField, - cs.intervalMs + cs.intervalMs, + jobCreator.runtimeMappings ); setLineChartsData(resp); @@ -175,7 +180,7 @@ export const PopulationDetectors: FC = ({ setIsValid }) => { (async (index: number, field: Field) => { return { index, - fields: await chartLoader.loadFieldExampleValues(field), + fields: await chartLoader.loadFieldExampleValues(field, jobCreator.runtimeMappings), }; })(i, af.by.field) ); diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/population_view/metric_selection_summary.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/population_view/metric_selection_summary.tsx index 0057b50e6de57..c615010891101 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/population_view/metric_selection_summary.tsx +++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/population_view/metric_selection_summary.tsx @@ -77,7 +77,8 @@ export const PopulationDetectorsSummary: FC = () => { jobCreator.end, aggFieldPairList, jobCreator.splitField, - cs.intervalMs + cs.intervalMs, + jobCreator.runtimeMappings ); setLineChartsData(resp); @@ -97,7 +98,7 @@ export const PopulationDetectorsSummary: FC = () => { (async (index: number, field: Field) => { return { index, - fields: await chartLoader.loadFieldExampleValues(field), + fields: await chartLoader.loadFieldExampleValues(field, jobCreator.runtimeMappings), }; })(i, af.by.field) ); diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/single_metric_view/metric_selection.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/single_metric_view/metric_selection.tsx index 56a81d7351ec5..f4a907dcc6a49 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/single_metric_view/metric_selection.tsx +++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/single_metric_view/metric_selection.tsx @@ -5,13 +5,14 @@ * 2.0. */ -import React, { Fragment, FC, useContext, useEffect, useState } from 'react'; +import React, { Fragment, FC, useContext, useEffect, useState, useMemo } from 'react'; import { JobCreatorContext } from '../../../job_creator_context'; import { SingleMetricJobCreator } from '../../../../../common/job_creator'; import { LineChartData } from '../../../../../common/chart_loader'; import { AggSelect, DropDownLabel, DropDownProps, createLabel } from '../agg_select'; import { newJobCapsService } from '../../../../../../../services/new_job_capabilities_service'; import { AggFieldPair } from '../../../../../../../../../common/types/fields'; +import { sortFields } from '../../../../../../../../../common/util/fields_utils'; import { AnomalyChart, CHART_TYPE } from '../../../charts/anomaly_chart'; import { getChartSettings } from '../../../charts/common/settings'; import { getToastNotificationService } from '../../../../../../../services/toast_notification_service'; @@ -32,7 +33,10 @@ export const SingleMetricDetectors: FC = ({ setIsValid }) => { } = useContext(JobCreatorContext); const jobCreator = jc as SingleMetricJobCreator; - const { fields } = newJobCapsService; + const fields = useMemo( + () => sortFields([...newJobCapsService.fields, ...jobCreator.runtimeFields]), + [] + ); const [selectedOptions, setSelectedOptions] = useState( jobCreator.aggFieldPair !== null ? [{ label: createLabel(jobCreator.aggFieldPair) }] : [] ); @@ -88,7 +92,8 @@ export const SingleMetricDetectors: FC = ({ setIsValid }) => { [aggFieldPair], null, null, - cs.intervalMs + cs.intervalMs, + jobCreator.runtimeMappings ); if (resp[DTR_IDX] !== undefined) { setLineChartData(resp); diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/single_metric_view/metric_selection_summary.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/single_metric_view/metric_selection_summary.tsx index 66209c31427e7..4d8fc5ef76084 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/single_metric_view/metric_selection_summary.tsx +++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/single_metric_view/metric_selection_summary.tsx @@ -58,7 +58,8 @@ export const SingleMetricDetectorsSummary: FC = () => { [jobCreator.aggFieldPair], null, null, - cs.intervalMs + cs.intervalMs, + jobCreator.runtimeMappings ); if (resp[DTR_IDX] !== undefined) { setLineChartData(resp); diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/split_field/by_field.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/split_field/by_field.tsx index f34695c8c4998..01c538f7ceb01 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/split_field/by_field.tsx +++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/split_field/by_field.tsx @@ -5,13 +5,16 @@ * 2.0. */ -import React, { FC, useContext, useEffect, useState } from 'react'; +import React, { FC, useContext, useEffect, useState, useMemo } from 'react'; import { i18n } from '@kbn/i18n'; import { SplitFieldSelect } from './split_field_select'; import { JobCreatorContext } from '../../../job_creator_context'; import { Field } from '../../../../../../../../../common/types/fields'; -import { newJobCapsService } from '../../../../../../../services/new_job_capabilities_service'; +import { + newJobCapsService, + filterCategoryFields, +} from '../../../../../../../services/new_job_capabilities_service'; import { MultiMetricJobCreator, PopulationJobCreator } from '../../../../../common/job_creator'; interface Props { @@ -22,7 +25,11 @@ export const ByFieldSelector: FC = ({ detectorIndex }) => { const { jobCreator: jc, jobCreatorUpdate, jobCreatorUpdated } = useContext(JobCreatorContext); const jobCreator = jc as PopulationJobCreator; - const { categoryFields: allCategoryFields } = newJobCapsService; + const runtimeCategoryFields = useMemo(() => filterCategoryFields(jobCreator.runtimeFields), []); + const allCategoryFields = useMemo( + () => [...newJobCapsService.categoryFields, ...runtimeCategoryFields], + [] + ); const [byField, setByField] = useState(jobCreator.getByField(detectorIndex)); const categoryFields = useFilteredCategoryFields( diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/split_field/split_field.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/split_field/split_field.tsx index d20f186b1d88c..7a99d4da13185 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/split_field/split_field.tsx +++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/split_field/split_field.tsx @@ -5,11 +5,14 @@ * 2.0. */ -import React, { FC, useContext, useEffect, useState } from 'react'; +import React, { FC, useContext, useEffect, useState, useMemo } from 'react'; import { SplitFieldSelect } from './split_field_select'; import { JobCreatorContext } from '../../../job_creator_context'; -import { newJobCapsService } from '../../../../../../../services/new_job_capabilities_service'; +import { + newJobCapsService, + filterCategoryFields, +} from '../../../../../../../services/new_job_capabilities_service'; import { Description } from './description'; import { MultiMetricJobCreator, @@ -23,7 +26,11 @@ export const SplitFieldSelector: FC = () => { const jobCreator = jc as MultiMetricJobCreator | PopulationJobCreator; const canClearSelection = isMultiMetricJobCreator(jc); - const { categoryFields } = newJobCapsService; + const runtimeCategoryFields = useMemo(() => filterCategoryFields(jobCreator.runtimeFields), []); + const categoryFields = useMemo( + () => [...newJobCapsService.categoryFields, ...runtimeCategoryFields], + [] + ); const [splitField, setSplitField] = useState(jobCreator.splitField); useEffect(() => { diff --git a/x-pack/plugins/ml/public/application/services/ml_api_service/index.ts b/x-pack/plugins/ml/public/application/services/ml_api_service/index.ts index befc1cff6e9fe..8d0ecddaa97b8 100644 --- a/x-pack/plugins/ml/public/application/services/ml_api_service/index.ts +++ b/x-pack/plugins/ml/public/application/services/ml_api_service/index.ts @@ -24,6 +24,7 @@ import { import { MlCapabilitiesResponse } from '../../../../common/types/capabilities'; import { Calendar, CalendarId, UpdateCalendar } from '../../../../common/types/calendars'; +import { RuntimeMappings } from '../../../../common/types/fields'; import { Job, JobStats, @@ -63,6 +64,7 @@ export interface BucketSpanEstimatorData { query: any; splitField: string | undefined; timeField: string | undefined; + runtimeMappings: RuntimeMappings | undefined; } export interface BucketSpanEstimatorResponse { diff --git a/x-pack/plugins/ml/public/application/services/ml_api_service/jobs.ts b/x-pack/plugins/ml/public/application/services/ml_api_service/jobs.ts index 400841587bf8c..df72bd25c6bcd 100644 --- a/x-pack/plugins/ml/public/application/services/ml_api_service/jobs.ts +++ b/x-pack/plugins/ml/public/application/services/ml_api_service/jobs.ts @@ -17,7 +17,7 @@ import type { Datafeed, } from '../../../../common/types/anomaly_detection_jobs'; import type { JobMessage } from '../../../../common/types/audit_message'; -import type { AggFieldNamePair } from '../../../../common/types/fields'; +import type { AggFieldNamePair, RuntimeMappings } from '../../../../common/types/fields'; import type { ExistingJobsAndGroups } from '../job_service'; import type { CategorizationAnalyzer, @@ -188,7 +188,8 @@ export const jobsApiProvider = (httpService: HttpService) => ({ query: any, aggFieldNamePairs: AggFieldNamePair[], splitFieldName: string | null, - splitFieldValue: string | null + splitFieldValue: string | null, + runtimeMappings?: RuntimeMappings ) { const body = JSON.stringify({ indexPatternTitle, @@ -200,6 +201,7 @@ export const jobsApiProvider = (httpService: HttpService) => ({ aggFieldNamePairs, splitFieldName, splitFieldValue, + runtimeMappings, }); return httpService.http({ path: `${ML_BASE_PATH}/jobs/new_job_line_chart`, @@ -216,7 +218,8 @@ export const jobsApiProvider = (httpService: HttpService) => ({ intervalMs: number, query: any, aggFieldNamePairs: AggFieldNamePair[], - splitFieldName: string + splitFieldName: string, + runtimeMappings?: RuntimeMappings ) { const body = JSON.stringify({ indexPatternTitle, @@ -227,6 +230,7 @@ export const jobsApiProvider = (httpService: HttpService) => ({ query, aggFieldNamePairs, splitFieldName, + runtimeMappings, }); return httpService.http({ path: `${ML_BASE_PATH}/jobs/new_job_population_chart`, @@ -263,7 +267,8 @@ export const jobsApiProvider = (httpService: HttpService) => ({ timeField: string, start: number, end: number, - analyzer: CategorizationAnalyzer + analyzer: CategorizationAnalyzer, + runtimeMappings?: RuntimeMappings ) { const body = JSON.stringify({ indexPatternTitle, @@ -274,6 +279,7 @@ export const jobsApiProvider = (httpService: HttpService) => ({ start, end, analyzer, + runtimeMappings, }); return httpService.http<{ examples: CategoryFieldExample[]; diff --git a/x-pack/plugins/ml/public/application/services/new_job_capabilities_service.ts b/x-pack/plugins/ml/public/application/services/new_job_capabilities_service.ts index bd5dfbd6160f3..b9520df4e710f 100644 --- a/x-pack/plugins/ml/public/application/services/new_job_capabilities_service.ts +++ b/x-pack/plugins/ml/public/application/services/new_job_capabilities_service.ts @@ -86,7 +86,7 @@ class NewJobCapsService { } public get categoryFields(): Field[] { - return this._fields.filter((f) => categoryFieldTypes.includes(f.type)); + return filterCategoryFields(this._fields); } public async initializeFromIndexPattern( @@ -252,4 +252,8 @@ function processTextAndKeywordFields(fields: Field[]) { return { fieldsPreferringKeyword, fieldsPreferringText }; } +export function filterCategoryFields(fields: Field[]) { + return fields.filter((f) => categoryFieldTypes.includes(f.type)); +} + export const newJobCapsService = new NewJobCapsService(); diff --git a/x-pack/plugins/ml/server/models/bucket_span_estimator/bucket_span_estimator.d.ts b/x-pack/plugins/ml/server/models/bucket_span_estimator/bucket_span_estimator.d.ts index 24743d3bc0874..40a6bd1decd97 100644 --- a/x-pack/plugins/ml/server/models/bucket_span_estimator/bucket_span_estimator.d.ts +++ b/x-pack/plugins/ml/server/models/bucket_span_estimator/bucket_span_estimator.d.ts @@ -7,6 +7,7 @@ import { IScopedClusterClient } from 'kibana/server'; import { ES_AGGREGATION } from '../../../common/constants/aggregation_types'; +import { RuntimeMappings } from '../../../common/types/fields'; export interface BucketSpanEstimatorData { aggTypes: Array; @@ -19,6 +20,7 @@ export interface BucketSpanEstimatorData { query: any; splitField: string | undefined; timeField: string | undefined; + runtimeMappings: RuntimeMappings | undefined; } export function estimateBucketSpanFactory({ diff --git a/x-pack/plugins/ml/server/models/bucket_span_estimator/bucket_span_estimator.js b/x-pack/plugins/ml/server/models/bucket_span_estimator/bucket_span_estimator.js index 9639a6e1e1317..79f48645d52f2 100644 --- a/x-pack/plugins/ml/server/models/bucket_span_estimator/bucket_span_estimator.js +++ b/x-pack/plugins/ml/server/models/bucket_span_estimator/bucket_span_estimator.js @@ -20,7 +20,7 @@ export function estimateBucketSpanFactory(client) { class BucketSpanEstimator { constructor( - { index, timeField, aggTypes, fields, duration, query, splitField }, + { index, timeField, aggTypes, fields, duration, query, splitField, runtimeMappings }, splitFieldValues, maxBuckets ) { @@ -38,6 +38,9 @@ export function estimateBucketSpanFactory(client) { minimumBucketSpanMS: 0, }; + this.runtimeMappings = + runtimeMappings !== undefined ? { runtime_mappings: runtimeMappings } : {}; + // determine durations for bucket span estimation // taking into account the clusters' search.max_buckets settings // the polled_data_checker uses an aggregation interval of 1 minute @@ -85,7 +88,8 @@ export function estimateBucketSpanFactory(client) { this.fields[i], this.duration, this.query, - this.thresholds + this.thresholds, + this.runtimeMappings ), result: null, }); @@ -107,7 +111,8 @@ export function estimateBucketSpanFactory(client) { this.fields[i], this.duration, queryCopy, - this.thresholds + this.thresholds, + this.runtimeMappings ), result: null, }); @@ -241,7 +246,7 @@ export function estimateBucketSpanFactory(client) { } } - const getFieldCardinality = function (index, field) { + const getFieldCardinality = function (index, field, runtimeMappings) { return new Promise((resolve, reject) => { asCurrentUser .search({ @@ -255,6 +260,7 @@ export function estimateBucketSpanFactory(client) { }, }, }, + ...(runtimeMappings !== undefined ? { runtime_mappings: runtimeMappings } : {}), }, }) .then(({ body }) => { @@ -267,7 +273,7 @@ export function estimateBucketSpanFactory(client) { }); }; - const getRandomFieldValues = function (index, field, query) { + const getRandomFieldValues = function (index, field, query, runtimeMappings) { let fieldValues = []; return new Promise((resolve, reject) => { const NUM_PARTITIONS = 10; @@ -293,6 +299,7 @@ export function estimateBucketSpanFactory(client) { }, }, }, + ...(runtimeMappings !== undefined ? { runtime_mappings: runtimeMappings } : {}), }, }) .then(({ body }) => { @@ -379,7 +386,12 @@ export function estimateBucketSpanFactory(client) { // a partition has been selected, so we need to load some field values to use in the // bucket span tests. if (formConfig.splitField !== undefined) { - getRandomFieldValues(formConfig.index, formConfig.splitField, formConfig.query) + getRandomFieldValues( + formConfig.index, + formConfig.splitField, + formConfig.query, + formConfig.runtimeMappings + ) .then((splitFieldValues) => { runEstimator(splitFieldValues); }) diff --git a/x-pack/plugins/ml/server/models/bucket_span_estimator/bucket_span_estimator.test.ts b/x-pack/plugins/ml/server/models/bucket_span_estimator/bucket_span_estimator.test.ts index 05a6ae85696d8..aa576d1f69915 100644 --- a/x-pack/plugins/ml/server/models/bucket_span_estimator/bucket_span_estimator.test.ts +++ b/x-pack/plugins/ml/server/models/bucket_span_estimator/bucket_span_estimator.test.ts @@ -35,6 +35,7 @@ const formConfig: BucketSpanEstimatorData = { }, splitField: undefined, timeField: undefined, + runtimeMappings: undefined, }; describe('ML - BucketSpanEstimator', () => { diff --git a/x-pack/plugins/ml/server/models/bucket_span_estimator/single_series_checker.js b/x-pack/plugins/ml/server/models/bucket_span_estimator/single_series_checker.js index 8564ddc72770d..25c87c5c2acbf 100644 --- a/x-pack/plugins/ml/server/models/bucket_span_estimator/single_series_checker.js +++ b/x-pack/plugins/ml/server/models/bucket_span_estimator/single_series_checker.js @@ -18,7 +18,7 @@ export function singleSeriesCheckerFactory({ asCurrentUser }) { const REF_DATA_INTERVAL = { name: '1h', ms: 3600000 }; class SingleSeriesChecker { - constructor(index, timeField, aggType, field, duration, query, thresholds) { + constructor(index, timeField, aggType, field, duration, query, thresholds, runtimeMappings) { this.index = index; this.timeField = timeField; this.aggType = aggType; @@ -31,7 +31,7 @@ export function singleSeriesCheckerFactory({ asCurrentUser }) { varDiff: 0, created: false, }; - + this.runtimeMappings = runtimeMappings; this.interval = null; } @@ -171,6 +171,7 @@ export function singleSeriesCheckerFactory({ asCurrentUser }) { }, }, }, + ...this.runtimeMappings, }; if (this.field !== null) { diff --git a/x-pack/plugins/ml/server/models/calculate_model_memory_limit/calculate_model_memory_limit.ts b/x-pack/plugins/ml/server/models/calculate_model_memory_limit/calculate_model_memory_limit.ts index 7a022c0c1805c..2efc2f905d9bb 100644 --- a/x-pack/plugins/ml/server/models/calculate_model_memory_limit/calculate_model_memory_limit.ts +++ b/x-pack/plugins/ml/server/models/calculate_model_memory_limit/calculate_model_memory_limit.ts @@ -116,7 +116,8 @@ const cardinalityCheckProvider = (client: IScopedClusterClient) => { timeFieldName, earliestMs, latestMs, - bucketSpan + bucketSpan, + datafeedConfig ); } diff --git a/x-pack/plugins/ml/server/models/fields_service/fields_service.ts b/x-pack/plugins/ml/server/models/fields_service/fields_service.ts index 3c12aa8c75c03..56eddf9df2e04 100644 --- a/x-pack/plugins/ml/server/models/fields_service/fields_service.ts +++ b/x-pack/plugins/ml/server/models/fields_service/fields_service.ts @@ -298,13 +298,14 @@ export function fieldsServiceProvider({ asCurrentUser }: IScopedClusterClient) { timeFieldName: string, earliestMs: number, latestMs: number, - interval: string | undefined + interval: string | undefined, + datafeedConfig?: Datafeed ): Promise<{ [key: string]: number }> { if (!interval) { throw Boom.badRequest('Interval is required to retrieve max bucket cardinalities.'); } - const aggregatableFields = await getAggregatableFields(index, fieldNames); + const aggregatableFields = await getAggregatableFields(index, fieldNames, datafeedConfig); if (aggregatableFields.length === 0) { return {}; diff --git a/x-pack/plugins/ml/server/models/job_service/new_job/categorization/examples.ts b/x-pack/plugins/ml/server/models/job_service/new_job/categorization/examples.ts index 41837dda29e3f..63df425791e85 100644 --- a/x-pack/plugins/ml/server/models/job_service/new_job/categorization/examples.ts +++ b/x-pack/plugins/ml/server/models/job_service/new_job/categorization/examples.ts @@ -14,6 +14,7 @@ import { CategorizationAnalyzer, CategoryFieldExample, } from '../../../../../common/types/categories'; +import { RuntimeMappings } from '../../../../../common/types/fields'; import { ValidationResults } from './validation_results'; const CHUNK_SIZE = 100; @@ -32,7 +33,8 @@ export function categorizationExamplesProvider({ timeField: string | undefined, start: number, end: number, - analyzer: CategorizationAnalyzer + analyzer: CategorizationAnalyzer, + runtimeMappings: RuntimeMappings | undefined ): Promise<{ examples: CategoryFieldExample[]; error?: any }> { if (timeField !== undefined) { const range = { @@ -65,6 +67,7 @@ export function categorizationExamplesProvider({ _source: false, query, sort: ['_doc'], + ...(runtimeMappings !== undefined ? { runtime_mappings: runtimeMappings } : {}), }, }); @@ -165,7 +168,8 @@ export function categorizationExamplesProvider({ timeField: string | undefined, start: number, end: number, - analyzer: CategorizationAnalyzer + analyzer: CategorizationAnalyzer, + runtimeMappings: RuntimeMappings | undefined ) { const resp = await categorizationExamples( indexPatternTitle, @@ -175,7 +179,8 @@ export function categorizationExamplesProvider({ timeField, start, end, - analyzer + analyzer, + runtimeMappings ); const { examples } = resp; diff --git a/x-pack/plugins/ml/server/models/job_service/new_job/line_chart.ts b/x-pack/plugins/ml/server/models/job_service/new_job/line_chart.ts index 4b367c1430f50..c83485211b455 100644 --- a/x-pack/plugins/ml/server/models/job_service/new_job/line_chart.ts +++ b/x-pack/plugins/ml/server/models/job_service/new_job/line_chart.ts @@ -7,7 +7,11 @@ import { get } from 'lodash'; import { IScopedClusterClient } from 'kibana/server'; -import { AggFieldNamePair, EVENT_RATE_FIELD_ID } from '../../../../common/types/fields'; +import { + AggFieldNamePair, + EVENT_RATE_FIELD_ID, + RuntimeMappings, +} from '../../../../common/types/fields'; import { ML_MEDIAN_PERCENTS } from '../../../../common/util/job_utils'; type DtrIndex = number; @@ -34,7 +38,8 @@ export function newJobLineChartProvider({ asCurrentUser }: IScopedClusterClient) query: object, aggFieldNamePairs: AggFieldNamePair[], splitFieldName: string | null, - splitFieldValue: string | null + splitFieldValue: string | null, + runtimeMappings: RuntimeMappings | undefined ) { const json: object = getSearchJsonFromConfig( indexPatternTitle, @@ -45,7 +50,8 @@ export function newJobLineChartProvider({ asCurrentUser }: IScopedClusterClient) query, aggFieldNamePairs, splitFieldName, - splitFieldValue + splitFieldValue, + runtimeMappings ); const { body } = await asCurrentUser.search(json); @@ -103,7 +109,8 @@ function getSearchJsonFromConfig( query: any, aggFieldNamePairs: AggFieldNamePair[], splitFieldName: string | null, - splitFieldValue: string | null + splitFieldValue: string | null, + runtimeMappings: RuntimeMappings | undefined ): object { const json = { index: indexPatternTitle, @@ -125,6 +132,7 @@ function getSearchJsonFromConfig( aggs: {}, }, }, + ...(runtimeMappings !== undefined ? { runtime_mappings: runtimeMappings } : {}), }, }; diff --git a/x-pack/plugins/ml/server/models/job_service/new_job/population_chart.ts b/x-pack/plugins/ml/server/models/job_service/new_job/population_chart.ts index 469ae39296f12..10f6d94e764ac 100644 --- a/x-pack/plugins/ml/server/models/job_service/new_job/population_chart.ts +++ b/x-pack/plugins/ml/server/models/job_service/new_job/population_chart.ts @@ -7,7 +7,11 @@ import { get } from 'lodash'; import { IScopedClusterClient } from 'kibana/server'; -import { AggFieldNamePair, EVENT_RATE_FIELD_ID } from '../../../../common/types/fields'; +import { + AggFieldNamePair, + EVENT_RATE_FIELD_ID, + RuntimeMappings, +} from '../../../../common/types/fields'; import { ML_MEDIAN_PERCENTS } from '../../../../common/util/job_utils'; const OVER_FIELD_EXAMPLES_COUNT = 40; @@ -39,7 +43,8 @@ export function newJobPopulationChartProvider({ asCurrentUser }: IScopedClusterC intervalMs: number, query: object, aggFieldNamePairs: AggFieldNamePair[], - splitFieldName: string | null + splitFieldName: string | null, + runtimeMappings: RuntimeMappings | undefined ) { const json: object = getPopulationSearchJsonFromConfig( indexPatternTitle, @@ -49,7 +54,8 @@ export function newJobPopulationChartProvider({ asCurrentUser }: IScopedClusterC intervalMs, query, aggFieldNamePairs, - splitFieldName + splitFieldName, + runtimeMappings ); const { body } = await asCurrentUser.search(json); @@ -131,7 +137,8 @@ function getPopulationSearchJsonFromConfig( intervalMs: number, query: any, aggFieldNamePairs: AggFieldNamePair[], - splitFieldName: string | null + splitFieldName: string | null, + runtimeMappings: RuntimeMappings | undefined ): object { const json = { index: indexPatternTitle, @@ -153,6 +160,7 @@ function getPopulationSearchJsonFromConfig( aggs: {}, }, }, + ...(runtimeMappings !== undefined ? { runtime_mappings: runtimeMappings } : {}), }, }; @@ -237,5 +245,6 @@ function getPopulationSearchJsonFromConfig( } else { json.body.aggs.times.aggs = aggs; } + return json; } diff --git a/x-pack/plugins/ml/server/models/job_service/new_job_caps/aggregations.ts b/x-pack/plugins/ml/server/models/job_service/new_job_caps/aggregations.ts deleted file mode 100644 index eb407357bcda3..0000000000000 --- a/x-pack/plugins/ml/server/models/job_service/new_job_caps/aggregations.ts +++ /dev/null @@ -1,325 +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 { Aggregation, METRIC_AGG_TYPE } from '../../../../common/types/fields'; -import { - ML_JOB_AGGREGATION, - KIBANA_AGGREGATION, - ES_AGGREGATION, -} from '../../../../common/constants/aggregation_types'; - -// aggregation object missing id, title and fields and has null for kibana and dsl aggregation names. -// this is used as the basis for the ML only aggregations -function getBasicMlOnlyAggregation(): Omit { - return { - kibanaName: null, - dslName: null, - type: METRIC_AGG_TYPE, - mlModelPlotAgg: { - max: KIBANA_AGGREGATION.MAX, - min: KIBANA_AGGREGATION.MIN, - }, - }; -} - -// list of aggregations only support by ML and which don't have an equivalent ES aggregation -// note, not all aggs have a field list. Some aggs cannot be used with a field. -export const mlOnlyAggregations: Aggregation[] = [ - { - id: ML_JOB_AGGREGATION.NON_ZERO_COUNT, - title: 'Non zero count', - ...getBasicMlOnlyAggregation(), - }, - { - id: ML_JOB_AGGREGATION.HIGH_NON_ZERO_COUNT, - title: 'High non zero count', - ...getBasicMlOnlyAggregation(), - }, - { - id: ML_JOB_AGGREGATION.LOW_NON_ZERO_COUNT, - title: 'Low non zero count', - ...getBasicMlOnlyAggregation(), - }, - { - id: ML_JOB_AGGREGATION.HIGH_DISTINCT_COUNT, - title: 'High distinct count', - fields: [], - ...getBasicMlOnlyAggregation(), - }, - { - id: ML_JOB_AGGREGATION.LOW_DISTINCT_COUNT, - title: 'Low distinct count', - fields: [], - ...getBasicMlOnlyAggregation(), - }, - { - id: ML_JOB_AGGREGATION.METRIC, - title: 'Metric', - fields: [], - ...getBasicMlOnlyAggregation(), - }, - { - id: ML_JOB_AGGREGATION.VARP, - title: 'varp', - fields: [], - ...getBasicMlOnlyAggregation(), - }, - { - id: ML_JOB_AGGREGATION.HIGH_VARP, - title: 'High varp', - fields: [], - ...getBasicMlOnlyAggregation(), - }, - { - id: ML_JOB_AGGREGATION.LOW_VARP, - title: 'Low varp', - fields: [], - ...getBasicMlOnlyAggregation(), - }, - { - id: ML_JOB_AGGREGATION.NON_NULL_SUM, - title: 'Non null sum', - fields: [], - ...getBasicMlOnlyAggregation(), - }, - { - id: ML_JOB_AGGREGATION.HIGH_NON_NULL_SUM, - title: 'High non null sum', - fields: [], - ...getBasicMlOnlyAggregation(), - }, - { - id: ML_JOB_AGGREGATION.LOW_NON_NULL_SUM, - title: 'Low non null sum', - fields: [], - ...getBasicMlOnlyAggregation(), - }, - { - id: ML_JOB_AGGREGATION.RARE, - title: 'Rare', - ...getBasicMlOnlyAggregation(), - }, - { - id: ML_JOB_AGGREGATION.FREQ_RARE, - title: 'Freq rare', - ...getBasicMlOnlyAggregation(), - }, - { - id: ML_JOB_AGGREGATION.INFO_CONTENT, - title: 'Info content', - fields: [], - ...getBasicMlOnlyAggregation(), - }, - { - id: ML_JOB_AGGREGATION.HIGH_INFO_CONTENT, - title: 'High info content', - fields: [], - ...getBasicMlOnlyAggregation(), - }, - { - id: ML_JOB_AGGREGATION.LOW_INFO_CONTENT, - title: 'Low info content', - fields: [], - ...getBasicMlOnlyAggregation(), - }, - { - id: ML_JOB_AGGREGATION.TIME_OF_DAY, - title: 'Time of day', - ...getBasicMlOnlyAggregation(), - }, - { - id: ML_JOB_AGGREGATION.TIME_OF_WEEK, - title: 'Time of week', - ...getBasicMlOnlyAggregation(), - }, - { - id: ML_JOB_AGGREGATION.LAT_LONG, - title: 'Lat long', - fields: [], - ...getBasicMlOnlyAggregation(), - }, -]; - -export const aggregations: Aggregation[] = [ - { - id: ML_JOB_AGGREGATION.COUNT, - title: 'Count', - kibanaName: KIBANA_AGGREGATION.COUNT, - dslName: ES_AGGREGATION.COUNT, - type: METRIC_AGG_TYPE, - mlModelPlotAgg: { - max: KIBANA_AGGREGATION.MAX, - min: KIBANA_AGGREGATION.MIN, - }, - }, - { - id: ML_JOB_AGGREGATION.HIGH_COUNT, - title: 'High count', - kibanaName: KIBANA_AGGREGATION.COUNT, - dslName: ES_AGGREGATION.COUNT, - type: METRIC_AGG_TYPE, - mlModelPlotAgg: { - max: KIBANA_AGGREGATION.MAX, - min: KIBANA_AGGREGATION.MIN, - }, - }, - { - id: ML_JOB_AGGREGATION.LOW_COUNT, - title: 'Low count', - kibanaName: KIBANA_AGGREGATION.COUNT, - dslName: ES_AGGREGATION.COUNT, - type: METRIC_AGG_TYPE, - mlModelPlotAgg: { - max: KIBANA_AGGREGATION.MAX, - min: KIBANA_AGGREGATION.MIN, - }, - }, - { - id: ML_JOB_AGGREGATION.MEAN, - title: 'Mean', - kibanaName: KIBANA_AGGREGATION.AVG, - dslName: ES_AGGREGATION.AVG, - type: METRIC_AGG_TYPE, - mlModelPlotAgg: { - max: KIBANA_AGGREGATION.AVG, - min: KIBANA_AGGREGATION.AVG, - }, - fields: [], - }, - { - id: ML_JOB_AGGREGATION.HIGH_MEAN, - title: 'High mean', - kibanaName: KIBANA_AGGREGATION.AVG, - dslName: ES_AGGREGATION.AVG, - type: METRIC_AGG_TYPE, - mlModelPlotAgg: { - max: KIBANA_AGGREGATION.AVG, - min: KIBANA_AGGREGATION.AVG, - }, - fields: [], - }, - { - id: ML_JOB_AGGREGATION.LOW_MEAN, - title: 'Low mean', - kibanaName: KIBANA_AGGREGATION.AVG, - dslName: ES_AGGREGATION.AVG, - type: METRIC_AGG_TYPE, - mlModelPlotAgg: { - max: KIBANA_AGGREGATION.AVG, - min: KIBANA_AGGREGATION.AVG, - }, - fields: [], - }, - { - id: ML_JOB_AGGREGATION.SUM, - title: 'Sum', - kibanaName: KIBANA_AGGREGATION.SUM, - dslName: ES_AGGREGATION.SUM, - type: METRIC_AGG_TYPE, - mlModelPlotAgg: { - max: KIBANA_AGGREGATION.SUM, - min: KIBANA_AGGREGATION.SUM, - }, - fields: [], - }, - { - id: ML_JOB_AGGREGATION.HIGH_SUM, - title: 'High sum', - kibanaName: KIBANA_AGGREGATION.SUM, - dslName: ES_AGGREGATION.SUM, - type: METRIC_AGG_TYPE, - mlModelPlotAgg: { - max: KIBANA_AGGREGATION.SUM, - min: KIBANA_AGGREGATION.SUM, - }, - fields: [], - }, - { - id: ML_JOB_AGGREGATION.LOW_SUM, - title: 'Low sum', - kibanaName: KIBANA_AGGREGATION.SUM, - dslName: ES_AGGREGATION.SUM, - type: METRIC_AGG_TYPE, - mlModelPlotAgg: { - max: KIBANA_AGGREGATION.SUM, - min: KIBANA_AGGREGATION.SUM, - }, - fields: [], - }, - { - id: ML_JOB_AGGREGATION.MEDIAN, - title: 'Median', - kibanaName: KIBANA_AGGREGATION.MEDIAN, - dslName: ES_AGGREGATION.PERCENTILES, - type: METRIC_AGG_TYPE, - mlModelPlotAgg: { - max: KIBANA_AGGREGATION.MAX, - min: KIBANA_AGGREGATION.MIN, - }, - fields: [], - }, - { - id: ML_JOB_AGGREGATION.HIGH_MEDIAN, - title: 'High median', - kibanaName: KIBANA_AGGREGATION.MEDIAN, - dslName: ES_AGGREGATION.PERCENTILES, - type: METRIC_AGG_TYPE, - mlModelPlotAgg: { - max: KIBANA_AGGREGATION.MAX, - min: KIBANA_AGGREGATION.MIN, - }, - fields: [], - }, - { - id: ML_JOB_AGGREGATION.LOW_MEDIAN, - title: 'Low median', - kibanaName: KIBANA_AGGREGATION.MEDIAN, - dslName: ES_AGGREGATION.PERCENTILES, - type: METRIC_AGG_TYPE, - mlModelPlotAgg: { - max: KIBANA_AGGREGATION.MAX, - min: KIBANA_AGGREGATION.MIN, - }, - fields: [], - }, - { - id: ML_JOB_AGGREGATION.MIN, - title: 'Min', - kibanaName: KIBANA_AGGREGATION.MIN, - dslName: ES_AGGREGATION.MIN, - type: METRIC_AGG_TYPE, - mlModelPlotAgg: { - max: KIBANA_AGGREGATION.MIN, - min: KIBANA_AGGREGATION.MIN, - }, - fields: [], - }, - { - id: ML_JOB_AGGREGATION.MAX, - title: 'Max', - kibanaName: KIBANA_AGGREGATION.MAX, - dslName: ES_AGGREGATION.MAX, - type: METRIC_AGG_TYPE, - mlModelPlotAgg: { - max: KIBANA_AGGREGATION.MAX, - min: KIBANA_AGGREGATION.MAX, - }, - fields: [], - }, - { - id: ML_JOB_AGGREGATION.DISTINCT_COUNT, - title: 'Distinct count', - kibanaName: KIBANA_AGGREGATION.CARDINALITY, - dslName: ES_AGGREGATION.CARDINALITY, - type: METRIC_AGG_TYPE, - mlModelPlotAgg: { - max: KIBANA_AGGREGATION.MAX, - min: KIBANA_AGGREGATION.MIN, - }, - fields: [], - }, -]; diff --git a/x-pack/plugins/ml/server/models/job_service/new_job_caps/field_service.ts b/x-pack/plugins/ml/server/models/job_service/new_job_caps/field_service.ts index 289c0118dce4e..7ce54cd2f9c5e 100644 --- a/x-pack/plugins/ml/server/models/job_service/new_job_caps/field_service.ts +++ b/x-pack/plugins/ml/server/models/job_service/new_job_caps/field_service.ts @@ -8,17 +8,11 @@ import { IScopedClusterClient } from 'kibana/server'; import { cloneDeep } from 'lodash'; import { SavedObjectsClientContract } from 'kibana/server'; -import { - Field, - Aggregation, - FieldId, - NewJobCaps, - METRIC_AGG_TYPE, -} from '../../../../common/types/fields'; -import { ES_FIELD_TYPES } from '../../../../../../../src/plugins/data/server'; -import { ML_JOB_AGGREGATION } from '../../../../common/constants/aggregation_types'; -import { rollupServiceProvider, RollupJob, RollupFields } from './rollup'; -import { aggregations, mlOnlyAggregations } from './aggregations'; +import { Field, FieldId, NewJobCaps, RollupFields } from '../../../../common/types/fields'; +import { ES_FIELD_TYPES } from '../../../../../../../src/plugins/data/common'; +import { combineFieldsAndAggs } from '../../../../common/util/fields_utils'; +import { rollupServiceProvider, RollupJob } from './rollup'; +import { aggregations, mlOnlyAggregations } from '../../../../common/constants/aggregation_types'; const supportedTypes: string[] = [ ES_FIELD_TYPES.DATE, @@ -133,85 +127,10 @@ class FieldsService { const aggs = cloneDeep([...aggregations, ...mlOnlyAggregations]); const fields: Field[] = await this.createFields(); - return await combineFieldsAndAggs(fields, aggs, rollupFields); + return combineFieldsAndAggs(fields, aggs, rollupFields); } } -// cross reference fields and aggs. -// fields contain a list of aggs that are compatible, and vice versa. -async function combineFieldsAndAggs( - fields: Field[], - aggs: Aggregation[], - rollupFields: RollupFields -): Promise { - const keywordFields = getKeywordFields(fields); - const textFields = getTextFields(fields); - const numericalFields = getNumericalFields(fields); - const ipFields = getIpFields(fields); - const geoFields = getGeoFields(fields); - - const isRollup = Object.keys(rollupFields).length > 0; - const mix = mixFactory(isRollup, rollupFields); - - aggs.forEach((a) => { - if (a.type === METRIC_AGG_TYPE && a.fields !== undefined) { - switch (a.id) { - case ML_JOB_AGGREGATION.LAT_LONG: - geoFields.forEach((f) => mix(f, a)); - break; - case ML_JOB_AGGREGATION.INFO_CONTENT: - case ML_JOB_AGGREGATION.HIGH_INFO_CONTENT: - case ML_JOB_AGGREGATION.LOW_INFO_CONTENT: - textFields.forEach((f) => mix(f, a)); - case ML_JOB_AGGREGATION.DISTINCT_COUNT: - case ML_JOB_AGGREGATION.HIGH_DISTINCT_COUNT: - case ML_JOB_AGGREGATION.LOW_DISTINCT_COUNT: - // distinct count (i.e. cardinality) takes keywords, ips - // as well as numerical fields - keywordFields.forEach((f) => mix(f, a)); - ipFields.forEach((f) => mix(f, a)); - // note, no break to fall through to add numerical fields. - default: - // all other aggs take numerical fields - numericalFields.forEach((f) => { - mix(f, a); - }); - break; - } - } - }); - - return { - aggs, - fields: isRollup ? filterFields(fields) : fields, - }; -} - -// remove fields that have no aggs associated to them, unless they are date fields -function filterFields(fields: Field[]): Field[] { - return fields.filter( - (f) => f.aggs && (f.aggs.length > 0 || (f.aggs.length === 0 && f.type === ES_FIELD_TYPES.DATE)) - ); -} - -// returns a mix function that is used to cross-reference aggs and fields. -// wrapped in a provider to allow filtering based on rollup job capabilities -function mixFactory(isRollup: boolean, rollupFields: RollupFields) { - return function mix(field: Field, agg: Aggregation): void { - if ( - isRollup === false || - (rollupFields[field.id] && rollupFields[field.id].find((f) => f.agg === agg.dslName)) - ) { - if (field.aggs !== undefined) { - field.aggs.push(agg); - } - if (agg.fields !== undefined) { - agg.fields.push(field); - } - } - }; -} - function combineAllRollupFields(rollupConfigs: RollupJob[]): RollupFields { const rollupFields: RollupFields = {}; rollupConfigs.forEach((conf) => { @@ -230,36 +149,3 @@ function combineAllRollupFields(rollupConfigs: RollupJob[]): RollupFields { }); return rollupFields; } - -function getKeywordFields(fields: Field[]): Field[] { - return fields.filter((f) => f.type === ES_FIELD_TYPES.KEYWORD); -} - -function getTextFields(fields: Field[]): Field[] { - return fields.filter((f) => f.type === ES_FIELD_TYPES.TEXT); -} - -function getIpFields(fields: Field[]): Field[] { - return fields.filter((f) => f.type === ES_FIELD_TYPES.IP); -} - -function getNumericalFields(fields: Field[]): Field[] { - return fields.filter( - (f) => - f.type === ES_FIELD_TYPES.LONG || - f.type === ES_FIELD_TYPES.UNSIGNED_LONG || - f.type === ES_FIELD_TYPES.INTEGER || - f.type === ES_FIELD_TYPES.SHORT || - f.type === ES_FIELD_TYPES.BYTE || - f.type === ES_FIELD_TYPES.DOUBLE || - f.type === ES_FIELD_TYPES.FLOAT || - f.type === ES_FIELD_TYPES.HALF_FLOAT || - f.type === ES_FIELD_TYPES.SCALED_FLOAT - ); -} - -function getGeoFields(fields: Field[]): Field[] { - return fields.filter( - (f) => f.type === ES_FIELD_TYPES.GEO_POINT || f.type === ES_FIELD_TYPES.GEO_SHAPE - ); -} diff --git a/x-pack/plugins/ml/server/models/job_service/new_job_caps/rollup.ts b/x-pack/plugins/ml/server/models/job_service/new_job_caps/rollup.ts index e3f1b5939a907..3b480bae2199e 100644 --- a/x-pack/plugins/ml/server/models/job_service/new_job_caps/rollup.ts +++ b/x-pack/plugins/ml/server/models/job_service/new_job_caps/rollup.ts @@ -9,10 +9,7 @@ import { IScopedClusterClient } from 'kibana/server'; import { SavedObject } from 'kibana/server'; import { IndexPatternAttributes } from 'src/plugins/data/server'; import { SavedObjectsClientContract } from 'kibana/server'; -import { FieldId } from '../../../../common/types/fields'; -import { ES_AGGREGATION } from '../../../../common/constants/aggregation_types'; - -export type RollupFields = Record]>; +import { RollupFields } from '../../../../common/types/fields'; export interface RollupJob { job_id: string; diff --git a/x-pack/plugins/ml/server/routes/job_service.ts b/x-pack/plugins/ml/server/routes/job_service.ts index eb142542944ad..1e028dfb20b4d 100644 --- a/x-pack/plugins/ml/server/routes/job_service.ts +++ b/x-pack/plugins/ml/server/routes/job_service.ts @@ -534,6 +534,7 @@ export function jobServiceRoutes({ router, routeGuard }: RouteInitialization) { aggFieldNamePairs, splitFieldName, splitFieldValue, + runtimeMappings, } = request.body; const { newJobLineChart } = jobServiceProvider(client, mlClient); @@ -546,7 +547,8 @@ export function jobServiceRoutes({ router, routeGuard }: RouteInitialization) { query, aggFieldNamePairs, splitFieldName, - splitFieldValue + splitFieldValue, + runtimeMappings ); return response.ok({ @@ -588,6 +590,7 @@ export function jobServiceRoutes({ router, routeGuard }: RouteInitialization) { query, aggFieldNamePairs, splitFieldName, + runtimeMappings, } = request.body; const { newJobPopulationChart } = jobServiceProvider(client, mlClient); @@ -599,7 +602,8 @@ export function jobServiceRoutes({ router, routeGuard }: RouteInitialization) { intervalMs, query, aggFieldNamePairs, - splitFieldName + splitFieldName, + runtimeMappings ); return response.ok({ @@ -705,6 +709,7 @@ export function jobServiceRoutes({ router, routeGuard }: RouteInitialization) { start, end, analyzer, + runtimeMappings, } = request.body; const resp = await validateCategoryExamples( @@ -715,7 +720,8 @@ export function jobServiceRoutes({ router, routeGuard }: RouteInitialization) { timeField, start, end, - analyzer + analyzer, + runtimeMappings ); return response.ok({ diff --git a/x-pack/plugins/ml/server/routes/schemas/job_service_schema.ts b/x-pack/plugins/ml/server/routes/schemas/job_service_schema.ts index afd56c0067e4d..65955fbc47a37 100644 --- a/x-pack/plugins/ml/server/routes/schemas/job_service_schema.ts +++ b/x-pack/plugins/ml/server/routes/schemas/job_service_schema.ts @@ -16,6 +16,7 @@ export const categorizationFieldExamplesSchema = { start: schema.number(), end: schema.number(), analyzer: schema.any(), + runtimeMappings: schema.maybe(schema.any()), }; export const chartSchema = { @@ -28,6 +29,7 @@ export const chartSchema = { aggFieldNamePairs: schema.arrayOf(schema.any()), splitFieldName: schema.maybe(schema.nullable(schema.string())), splitFieldValue: schema.maybe(schema.nullable(schema.string())), + runtimeMappings: schema.maybe(schema.any()), }; export const datafeedIdsSchema = schema.object({ diff --git a/x-pack/plugins/ml/server/routes/schemas/job_validation_schema.ts b/x-pack/plugins/ml/server/routes/schemas/job_validation_schema.ts index feee5a49ed2ca..8c054d54e0589 100644 --- a/x-pack/plugins/ml/server/routes/schemas/job_validation_schema.ts +++ b/x-pack/plugins/ml/server/routes/schemas/job_validation_schema.ts @@ -18,6 +18,7 @@ export const estimateBucketSpanSchema = schema.object({ query: schema.any(), splitField: schema.maybe(schema.string()), timeField: schema.maybe(schema.string()), + runtimeMappings: schema.maybe(schema.any()), }); export const modelMemoryLimitSchema = schema.object({ From aeb133e0c84a8a88a395b6e828a69ef03e1e5af9 Mon Sep 17 00:00:00 2001 From: Kaarina Tungseth Date: Tue, 16 Feb 2021 10:27:11 -0600 Subject: [PATCH 07/53] [7.x] [DOCS] Adds security update to Release Notes (#91333) (#91513) --- docs/CHANGELOG.asciidoc | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/docs/CHANGELOG.asciidoc b/docs/CHANGELOG.asciidoc index 866a74774b6ed..51344fdae680c 100644 --- a/docs/CHANGELOG.asciidoc +++ b/docs/CHANGELOG.asciidoc @@ -458,13 +458,28 @@ Uptime:: * UX use replace history instead of push on first load {kibana-pull}88586[#88586] * Fixes impacted page load errors {kibana-pull}88597[#88597] - [[release-notes-7.10.2]] == {kib} 7.10.2 -For detailed information about the 7.10.2 release, review the following bug fixes. +For detailed information about the 7.10.2 release, review the following bug fixes. For the breaking changes, refer to the <>. -For the breaking changes, refer to the <>. +[float] +[[security-update-v7.10.2]] +=== Security update +*Vega* visualizations are susceptible to stored and reflected XSS via a vulnerable version of the Vega library. When you create *Vega* visualizations or create a vulnerable URL that describes the visualization, an arbitrary JavaScript can execute in your browser. + +[float] +[[affected-versions-v7.10.2]] +==== Affected versions +Affected versions include 7.10.1 and earlier. + +[float] +[[solution-v7.10.2]] +==== Solution +Verify if you use *Vega* visualizations, then complete the following: + +* If you use *Vega* visualizations, upgrade to 7.10.2. +* If you do not use *Vega* visualizations, open your kibana.yml file, then change `vega.enabled: true` to `vega.enabled: false`. [float] [[bug-v7.10.2]] From 3a2b2a3b89f13111692e0ddcb68238cc26bc0be3 Mon Sep 17 00:00:00 2001 From: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Date: Tue, 16 Feb 2021 12:00:02 -0500 Subject: [PATCH 08/53] chore(NA): assure bazel bin is available on kbn clean and reset commands (#91406) (#91491) Co-authored-by: Tiago Costa --- packages/kbn-pm/dist/index.js | 26 ++++++++++++------- packages/kbn-pm/src/commands/clean.ts | 8 +++--- packages/kbn-pm/src/commands/reset.ts | 26 ++++++++++++------- .../kbn-pm/src/utils/bazel/install_tools.ts | 2 +- 4 files changed, 40 insertions(+), 22 deletions(-) diff --git a/packages/kbn-pm/dist/index.js b/packages/kbn-pm/dist/index.js index 375ad634cbc15..1b8bd4784e583 100644 --- a/packages/kbn-pm/dist/index.js +++ b/packages/kbn-pm/dist/index.js @@ -48005,6 +48005,8 @@ __webpack_require__.r(__webpack_exports__); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "getBazelRepositoryCacheFolder", function() { return _get_cache_folders__WEBPACK_IMPORTED_MODULE_0__["getBazelRepositoryCacheFolder"]; }); /* harmony import */ var _install_tools__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(373); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "isBazelBinAvailable", function() { return _install_tools__WEBPACK_IMPORTED_MODULE_1__["isBazelBinAvailable"]; }); + /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "installBazelTools", function() { return _install_tools__WEBPACK_IMPORTED_MODULE_1__["installBazelTools"]; }); /* harmony import */ var _run__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(374); @@ -48064,6 +48066,7 @@ async function getBazelRepositoryCacheFolder() { "use strict"; __webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "isBazelBinAvailable", function() { return isBazelBinAvailable; }); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "installBazelTools", function() { return installBazelTools; }); /* harmony import */ var dedent__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(2); /* harmony import */ var dedent__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(dedent__WEBPACK_IMPORTED_MODULE_0__); @@ -54434,8 +54437,10 @@ const CleanCommand = { } // Runs Bazel soft clean - await Object(_utils_bazel__WEBPACK_IMPORTED_MODULE_4__["runBazel"])(['clean']); - _utils_log__WEBPACK_IMPORTED_MODULE_6__["log"].success('Soft cleaned bazel'); + if (await Object(_utils_bazel__WEBPACK_IMPORTED_MODULE_4__["isBazelBinAvailable"])()) { + await Object(_utils_bazel__WEBPACK_IMPORTED_MODULE_4__["runBazel"])(['clean']); + _utils_log__WEBPACK_IMPORTED_MODULE_6__["log"].success('Soft cleaned bazel'); + } if (toDelete.length === 0) { _utils_log__WEBPACK_IMPORTED_MODULE_6__["log"].success('Nothing to delete'); @@ -59124,16 +59129,19 @@ const ResetCommand = { pattern: extraPatterns }); } - } // Runs Bazel hard clean + } // Runs Bazel hard clean and deletes Bazel Cache Folders - await Object(_utils_bazel__WEBPACK_IMPORTED_MODULE_4__["runBazel"])(['clean', '--expunge']); - _utils_log__WEBPACK_IMPORTED_MODULE_6__["log"].success('Hard cleaned bazel'); // Deletes Bazel Cache Folders + if (await Object(_utils_bazel__WEBPACK_IMPORTED_MODULE_4__["isBazelBinAvailable"])()) { + // Hard cleaning bazel + await Object(_utils_bazel__WEBPACK_IMPORTED_MODULE_4__["runBazel"])(['clean', '--expunge']); + _utils_log__WEBPACK_IMPORTED_MODULE_6__["log"].success('Hard cleaned bazel'); // Deletes Bazel Cache Folders - await del__WEBPACK_IMPORTED_MODULE_1___default()([await Object(_utils_bazel__WEBPACK_IMPORTED_MODULE_4__["getBazelDiskCacheFolder"])(), await Object(_utils_bazel__WEBPACK_IMPORTED_MODULE_4__["getBazelRepositoryCacheFolder"])()], { - force: true - }); - _utils_log__WEBPACK_IMPORTED_MODULE_6__["log"].success('Removed disk caches'); + await del__WEBPACK_IMPORTED_MODULE_1___default()([await Object(_utils_bazel__WEBPACK_IMPORTED_MODULE_4__["getBazelDiskCacheFolder"])(), await Object(_utils_bazel__WEBPACK_IMPORTED_MODULE_4__["getBazelRepositoryCacheFolder"])()], { + force: true + }); + _utils_log__WEBPACK_IMPORTED_MODULE_6__["log"].success('Removed disk caches'); + } if (toDelete.length === 0) { return; diff --git a/packages/kbn-pm/src/commands/clean.ts b/packages/kbn-pm/src/commands/clean.ts index 23a2c382fbccc..a742d6d4e68df 100644 --- a/packages/kbn-pm/src/commands/clean.ts +++ b/packages/kbn-pm/src/commands/clean.ts @@ -11,7 +11,7 @@ import del from 'del'; import ora from 'ora'; import { join, relative } from 'path'; -import { runBazel } from '../utils/bazel'; +import { isBazelBinAvailable, runBazel } from '../utils/bazel'; import { isDirectory } from '../utils/fs'; import { log } from '../utils/log'; import { ICommand } from './'; @@ -53,8 +53,10 @@ export const CleanCommand: ICommand = { } // Runs Bazel soft clean - await runBazel(['clean']); - log.success('Soft cleaned bazel'); + if (await isBazelBinAvailable()) { + await runBazel(['clean']); + log.success('Soft cleaned bazel'); + } if (toDelete.length === 0) { log.success('Nothing to delete'); diff --git a/packages/kbn-pm/src/commands/reset.ts b/packages/kbn-pm/src/commands/reset.ts index 71fe7a8c7a6e3..9eae12a15b164 100644 --- a/packages/kbn-pm/src/commands/reset.ts +++ b/packages/kbn-pm/src/commands/reset.ts @@ -11,7 +11,12 @@ import del from 'del'; import ora from 'ora'; import { join, relative } from 'path'; -import { getBazelDiskCacheFolder, getBazelRepositoryCacheFolder, runBazel } from '../utils/bazel'; +import { + getBazelDiskCacheFolder, + getBazelRepositoryCacheFolder, + isBazelBinAvailable, + runBazel, +} from '../utils/bazel'; import { isDirectory } from '../utils/fs'; import { log } from '../utils/log'; import { ICommand } from './'; @@ -52,15 +57,18 @@ export const ResetCommand: ICommand = { } } - // Runs Bazel hard clean - await runBazel(['clean', '--expunge']); - log.success('Hard cleaned bazel'); + // Runs Bazel hard clean and deletes Bazel Cache Folders + if (await isBazelBinAvailable()) { + // Hard cleaning bazel + await runBazel(['clean', '--expunge']); + log.success('Hard cleaned bazel'); - // Deletes Bazel Cache Folders - await del([await getBazelDiskCacheFolder(), await getBazelRepositoryCacheFolder()], { - force: true, - }); - log.success('Removed disk caches'); + // Deletes Bazel Cache Folders + await del([await getBazelDiskCacheFolder(), await getBazelRepositoryCacheFolder()], { + force: true, + }); + log.success('Removed disk caches'); + } if (toDelete.length === 0) { return; diff --git a/packages/kbn-pm/src/utils/bazel/install_tools.ts b/packages/kbn-pm/src/utils/bazel/install_tools.ts index 93acbe09b4eab..8f634726b5ab2 100644 --- a/packages/kbn-pm/src/utils/bazel/install_tools.ts +++ b/packages/kbn-pm/src/utils/bazel/install_tools.ts @@ -26,7 +26,7 @@ async function readBazelToolsVersionFile(repoRootPath: string, versionFilename: return version; } -async function isBazelBinAvailable() { +export async function isBazelBinAvailable() { try { await spawn('bazel', ['--version'], { stdio: 'pipe' }); From a33734dfb807adc0bbf680ed5d51cb60e410bd7c Mon Sep 17 00:00:00 2001 From: Wylie Conlon Date: Tue, 16 Feb 2021 12:14:14 -0500 Subject: [PATCH 09/53] [TSVB] Add a new "Series Agg" to count the number of series (#91225) (#91495) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../application/components/aggs/series_agg.js | 7 ++++ .../response_processors/series/_series_agg.js | 10 +++++ .../series/_series_agg.test.js | 38 +++++++++++++++++++ 3 files changed, 55 insertions(+) diff --git a/src/plugins/vis_type_timeseries/public/application/components/aggs/series_agg.js b/src/plugins/vis_type_timeseries/public/application/components/aggs/series_agg.js index 0d0a964f3c1b6..c6afbaaee47da 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/aggs/series_agg.js +++ b/src/plugins/vis_type_timeseries/public/application/components/aggs/series_agg.js @@ -61,6 +61,13 @@ function SeriesAggUi(props) { }), value: 'mean', }, + { + label: intl.formatMessage({ + id: 'visTypeTimeseries.seriesAgg.functionOptions.countLabel', + defaultMessage: 'Series count', + }), + value: 'count', + }, { label: intl.formatMessage({ id: 'visTypeTimeseries.seriesAgg.functionOptions.overallSumLabel', diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/response_processors/series/_series_agg.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/response_processors/series/_series_agg.js index fd8ec0d5a439a..9ca5ffdfd1c27 100644 --- a/src/plugins/vis_type_timeseries/server/lib/vis_data/response_processors/series/_series_agg.js +++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/response_processors/series/_series_agg.js @@ -49,6 +49,16 @@ export const SeriesAgg = { }); return [data]; }, + count(targetSeries) { + const data = []; + _.zip(...targetSeries).forEach((row) => { + const key = row[0][0]; + // Filter out undefined or null values + const values = row.map((r) => r && r[1]).filter((v) => v || typeof v === 'number'); + data.push([key, values.length]); + }); + return [data]; + }, overall_max: overall('max'), overall_min: overall('min'), diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/response_processors/series/_series_agg.test.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/response_processors/series/_series_agg.test.js index 3952ecd3edd61..6201e718d4244 100644 --- a/src/plugins/vis_type_timeseries/server/lib/vis_data/response_processors/series/_series_agg.test.js +++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/response_processors/series/_series_agg.test.js @@ -67,6 +67,44 @@ describe('seriesAgg', () => { ], ]); }); + + test('returns the count of series', () => { + expect(seriesAgg.count(series)).toEqual([ + [ + [0, 3], + [1, 3], + [2, 3], + ], + ]); + }); + + test('returns the count of missing series', () => { + expect( + seriesAgg.count([ + [ + [0, null], + [1, null], + [2, 0], + ], + [ + [0, 0], + [1, null], + [2, 3], + ], + [ + [0, 2], + [1, null], + [2, 3], + ], + ]) + ).toEqual([ + [ + [0, 2], + [1, 0], + [2, 3], + ], + ]); + }); }); describe('overall', () => { From e6435e89db37f151b81ff3c967bb86513cba671b Mon Sep 17 00:00:00 2001 From: Robert Oskamp Date: Tue, 16 Feb 2021 18:19:45 +0100 Subject: [PATCH 10/53] [ML] Fix import missing range for File Data Visualizer, Discover card visible when disabled, texts (#91352) (#91496) This PR fixes several issues related to the Data Visualizer Co-authored-by: Quynh Nguyen <43350163+qn895@users.noreply.github.com> --- .../components/import_view/import_view.js | 1 + .../results_links/results_links.tsx | 28 ++++++++++- .../actions_panel/actions_panel.tsx | 47 ++++++------------- .../translations/translations/ja-JP.json | 2 - .../translations/translations/zh-CN.json | 2 - .../apps/ml/permissions/full_ml_access.ts | 23 +++++---- .../apps/ml/permissions/no_ml_access.ts | 11 +++-- .../apps/ml/permissions/read_ml_access.ts | 23 +++++---- .../ml/data_visualizer_index_based.ts | 8 ++++ .../apps/ml/permissions/full_ml_access.ts | 19 +++++--- .../apps/ml/permissions/no_ml_access.ts | 11 +++-- .../apps/ml/permissions/read_ml_access.ts | 19 +++++--- 12 files changed, 118 insertions(+), 76 deletions(-) diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_view/import_view.js b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_view/import_view.js index e22cca2746f99..0aadf9e17f30d 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_view/import_view.js +++ b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_view/import_view.js @@ -582,6 +582,7 @@ export class ImportView extends Component { = ({ + fieldStats, index, indexPatternId, timeFieldName, @@ -55,7 +58,7 @@ export const ResultsLinks: FC = ({ const { services: { - application: { getUrlForApp }, + application: { getUrlForApp, capabilities }, share: { urlGenerators: { getUrlGenerator }, }, @@ -66,6 +69,11 @@ export const ResultsLinks: FC = ({ let unmounted = false; const getDiscoverUrl = async (): Promise => { + const isDiscoverAvailable = capabilities.discover?.show ?? false; + if (!isDiscoverAvailable) { + return; + } + const state: DiscoverUrlGeneratorState = { indexPatternId, }; @@ -133,7 +141,7 @@ export const ResultsLinks: FC = ({ return () => { unmounted = true; }; - }, [indexPatternId, getUrlGenerator]); + }, [indexPatternId, getUrlGenerator, JSON.stringify(globalState)]); useEffect(() => { setShowCreateJobLink(checkPermission('canCreateJob') && mlNodesAvailable()); @@ -150,6 +158,22 @@ export const ResultsLinks: FC = ({ setGlobalState(_globalState); }, [duration]); + useEffect(() => { + // Update the global time range from known timeFieldName if stats is available + if ( + fieldStats && + typeof fieldStats === 'object' && + timeFieldName !== undefined && + fieldStats.hasOwnProperty(timeFieldName) && + fieldStats[timeFieldName].earliest !== undefined && + fieldStats[timeFieldName].latest !== undefined + ) { + setGlobalState({ + time: { from: fieldStats[timeFieldName].earliest!, to: fieldStats[timeFieldName].latest! }, + }); + } + }, [timeFieldName, fieldStats]); + async function updateTimeValues(recheck = true) { if (timeFieldName !== undefined) { const { from, to } = await getFullTimeRange(index, timeFieldName); diff --git a/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/actions_panel/actions_panel.tsx b/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/actions_panel/actions_panel.tsx index 850367fc1a65a..ca393c2d8ce72 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/actions_panel/actions_panel.tsx +++ b/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/actions_panel/actions_panel.tsx @@ -9,7 +9,7 @@ import React, { FC, useState, useEffect } from 'react'; import { FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; -import { EuiSpacer, EuiText, EuiTitle, EuiFlexGroup } from '@elastic/eui'; +import { EuiSpacer, EuiTitle, EuiFlexGroup } from '@elastic/eui'; import { LinkCard } from '../../../../components/link_card'; import { DataRecognizer } from '../../../../components/data_recognizer'; import { ML_PAGES } from '../../../../../../common/constants/ml_url_generator'; @@ -35,6 +35,7 @@ export const ActionsPanel: FC = ({ indexPattern, searchString, searchQuer const [discoverLink, setDiscoverLink] = useState(''); const { services: { + application: { capabilities }, share: { urlGenerators: { getUrlGenerator }, }, @@ -66,6 +67,11 @@ export const ActionsPanel: FC = ({ indexPattern, searchString, searchQuer const indexPatternId = indexPattern.id; const getDiscoverUrl = async (): Promise => { + const isDiscoverAvailable = capabilities.discover?.show ?? false; + if (!isDiscoverAvailable) { + return; + } + const state: DiscoverUrlGeneratorState = { indexPatternId, }; @@ -110,7 +116,7 @@ export const ActionsPanel: FC = ({ indexPattern, searchString, searchQuer

@@ -118,14 +124,6 @@ export const ActionsPanel: FC = ({ indexPattern, searchString, searchQuer {showCreateAnomalyDetectionJob && ( <> - -

- -

-
= ({ indexPattern, searchString, searchQuer )} {mlAvailable && indexPattern.id !== undefined && createDataFrameAnalyticsLink && ( <> - -

- -

-
= ({ indexPattern, searchString, searchQuer description={i18n.translate( 'xpack.ml.datavisualizer.actionsPanel.dataframeTypesDescription', { - defaultMessage: 'Create outlier detection, regression, or classification analytics', + defaultMessage: + 'Create outlier detection, regression, or classification analytics.', } )} title={ } data-test-subj="mlDataVisualizerCreateDataFrameAnalyticsCard" @@ -203,7 +186,7 @@ export const ActionsPanel: FC = ({ indexPattern, searchString, searchQuer

@@ -214,7 +197,7 @@ export const ActionsPanel: FC = ({ indexPattern, searchString, searchQuer description={i18n.translate( 'xpack.ml.datavisualizer.actionsPanel.viewIndexInDiscoverDescription', { - defaultMessage: 'Explore index in Discover', + defaultMessage: 'Explore the documents in your index.', } )} title={ diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 83d33c42baede..8db16fe58cca9 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -12882,9 +12882,7 @@ "xpack.ml.dataGridChart.topCategoriesLegend": "上位 {maxChartColumns}/{cardinality} カテゴリ", "xpack.ml.datavisualizer.actionsPanel.advancedDescription": "より高度なユースケースでは、ジョブの作成にすべてのオプションを使用します", "xpack.ml.datavisualizer.actionsPanel.advancedTitle": "高度な設定", - "xpack.ml.datavisualizer.actionsPanel.createJobDescription": "高度なジョブウィザードでジョブを作成し、このデータの異常を検出します:", "xpack.ml.datavisualizer.actionsPanel.createJobTitle": "ジョブの作成", - "xpack.ml.datavisualizer.actionsPanel.selectKnownConfigurationDescription": "認識されたデータの既知の構成を選択します:", "xpack.ml.datavisualizer.dataGrid.collapseDetailsForAllAriaLabel": "すべてのフィールドの詳細を折りたたむ", "xpack.ml.datavisualizer.dataGrid.distinctValuesColumnName": "固有の値", "xpack.ml.datavisualizer.dataGrid.distributionsColumnName": "分布", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index c6f880e1c664f..be595d0dd436d 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -12911,9 +12911,7 @@ "xpack.ml.dataGridChart.topCategoriesLegend": "{cardinality} 个类别中的排名前 {maxChartColumns} 个", "xpack.ml.datavisualizer.actionsPanel.advancedDescription": "使用全部选项为更高级的用例创建作业", "xpack.ml.datavisualizer.actionsPanel.advancedTitle": "高级", - "xpack.ml.datavisualizer.actionsPanel.createJobDescription": "使用“高级作业”向导创建作业,以查找此数据中的异常:", "xpack.ml.datavisualizer.actionsPanel.createJobTitle": "创建作业", - "xpack.ml.datavisualizer.actionsPanel.selectKnownConfigurationDescription": "选择已识别数据的已知配置:", "xpack.ml.datavisualizer.dataGrid.collapseDetailsForAllAriaLabel": "收起所有字段的详细信息", "xpack.ml.datavisualizer.dataGrid.distinctValuesColumnName": "不同值", "xpack.ml.datavisualizer.dataGrid.distributionsColumnName": "分布", diff --git a/x-pack/test/functional/apps/ml/permissions/full_ml_access.ts b/x-pack/test/functional/apps/ml/permissions/full_ml_access.ts index ad11acb3a6cbb..8d29b611c0bf5 100644 --- a/x-pack/test/functional/apps/ml/permissions/full_ml_access.ts +++ b/x-pack/test/functional/apps/ml/permissions/full_ml_access.ts @@ -15,16 +15,19 @@ export default function ({ getService }: FtrProviderContext) { const esArchiver = getService('esArchiver'); const ml = getService('ml'); - const testUsers = [USER.ML_POWERUSER, USER.ML_POWERUSER_SPACES]; + const testUsers = [ + { user: USER.ML_POWERUSER, discoverAvailable: true }, + { user: USER.ML_POWERUSER_SPACES, discoverAvailable: false }, + ]; describe('for user with full ML access', function () { this.tags(['skipFirefox', 'mlqa']); describe('with no data loaded', function () { - for (const user of testUsers) { - describe(`(${user})`, function () { + for (const testUser of testUsers) { + describe(`(${testUser.user})`, function () { before(async () => { - await ml.securityUI.loginAs(user); + await ml.securityUI.loginAs(testUser.user); await ml.api.cleanMlIndices(); }); @@ -153,10 +156,10 @@ export default function ({ getService }: FtrProviderContext) { await ml.api.deleteFilter(filterId); }); - for (const user of testUsers) { - describe(`(${user})`, function () { + for (const testUser of testUsers) { + describe(`(${testUser.user})`, function () { before(async () => { - await ml.securityUI.loginAs(user); + await ml.securityUI.loginAs(testUser.user); }); after(async () => { @@ -358,10 +361,12 @@ export default function ({ getService }: FtrProviderContext) { await ml.dataVisualizerIndexBased.assertDataVisualizerTableExist(); await ml.testExecution.logTestStep( - 'should display the actions panel with Discover card' + `should display the actions panel ${ + testUser.discoverAvailable ? 'with' : 'without' + } Discover card` ); await ml.dataVisualizerIndexBased.assertActionsPanelExists(); - await ml.dataVisualizerIndexBased.assertViewInDiscoverCardExists(); + await ml.dataVisualizerIndexBased.assertViewInDiscoverCard(testUser.discoverAvailable); await ml.testExecution.logTestStep('should display job cards'); await ml.dataVisualizerIndexBased.assertCreateAdvancedJobCardExists(); diff --git a/x-pack/test/functional/apps/ml/permissions/no_ml_access.ts b/x-pack/test/functional/apps/ml/permissions/no_ml_access.ts index ef26530edb271..280801d1becb5 100644 --- a/x-pack/test/functional/apps/ml/permissions/no_ml_access.ts +++ b/x-pack/test/functional/apps/ml/permissions/no_ml_access.ts @@ -13,15 +13,18 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { const PageObjects = getPageObjects(['common', 'error']); const ml = getService('ml'); - const testUsers = [USER.ML_UNAUTHORIZED, USER.ML_UNAUTHORIZED_SPACES]; + const testUsers = [ + { user: USER.ML_UNAUTHORIZED, discoverAvailable: true }, + { user: USER.ML_UNAUTHORIZED_SPACES, discoverAvailable: true }, + ]; describe('for user with no ML access', function () { this.tags(['skipFirefox', 'mlqa']); - for (const user of testUsers) { - describe(`(${user})`, function () { + for (const testUser of testUsers) { + describe(`(${testUser.user})`, function () { before(async () => { - await ml.securityUI.loginAs(user); + await ml.securityUI.loginAs(testUser.user); }); after(async () => { diff --git a/x-pack/test/functional/apps/ml/permissions/read_ml_access.ts b/x-pack/test/functional/apps/ml/permissions/read_ml_access.ts index 00cda88e0dc58..71ac9c00032dc 100644 --- a/x-pack/test/functional/apps/ml/permissions/read_ml_access.ts +++ b/x-pack/test/functional/apps/ml/permissions/read_ml_access.ts @@ -15,16 +15,19 @@ export default function ({ getService }: FtrProviderContext) { const esArchiver = getService('esArchiver'); const ml = getService('ml'); - const testUsers = [USER.ML_VIEWER, USER.ML_VIEWER_SPACES]; + const testUsers = [ + { user: USER.ML_VIEWER, discoverAvailable: true }, + { user: USER.ML_VIEWER_SPACES, discoverAvailable: false }, + ]; describe('for user with read ML access', function () { this.tags(['skipFirefox', 'mlqa']); describe('with no data loaded', function () { - for (const user of testUsers) { - describe(`(${user})`, function () { + for (const testUser of testUsers) { + describe(`(${testUser.user})`, function () { before(async () => { - await ml.securityUI.loginAs(user); + await ml.securityUI.loginAs(testUser.user); await ml.api.cleanMlIndices(); }); @@ -154,10 +157,10 @@ export default function ({ getService }: FtrProviderContext) { await ml.api.deleteFilter(filterId); }); - for (const user of testUsers) { - describe(`(${user})`, function () { + for (const testUser of testUsers) { + describe(`(${testUser.user})`, function () { before(async () => { - await ml.securityUI.loginAs(user); + await ml.securityUI.loginAs(testUser.user); }); after(async () => { @@ -351,10 +354,12 @@ export default function ({ getService }: FtrProviderContext) { await ml.dataVisualizerIndexBased.assertDataVisualizerTableExist(); await ml.testExecution.logTestStep( - 'should display the actions panel with Discover card' + `should display the actions panel ${ + testUser.discoverAvailable ? 'with' : 'without' + } Discover card` ); await ml.dataVisualizerIndexBased.assertActionsPanelExists(); - await ml.dataVisualizerIndexBased.assertViewInDiscoverCardExists(); + await ml.dataVisualizerIndexBased.assertViewInDiscoverCard(testUser.discoverAvailable); await ml.testExecution.logTestStep('should not display job cards'); await ml.dataVisualizerIndexBased.assertCreateAdvancedJobCardNotExists(); diff --git a/x-pack/test/functional/services/ml/data_visualizer_index_based.ts b/x-pack/test/functional/services/ml/data_visualizer_index_based.ts index 53b87042d48da..4beaa78d0189b 100644 --- a/x-pack/test/functional/services/ml/data_visualizer_index_based.ts +++ b/x-pack/test/functional/services/ml/data_visualizer_index_based.ts @@ -161,6 +161,14 @@ export function MachineLearningDataVisualizerIndexBasedProvider({ await testSubjects.missingOrFail('mlDataVisualizerCreateDataFrameAnalyticsCard'); }, + async assertViewInDiscoverCard(shouldExist: boolean) { + if (shouldExist) { + await this.assertViewInDiscoverCardExists(); + } else { + await this.assertViewInDiscoverCardNotExists(); + } + }, + async assertViewInDiscoverCardExists() { await testSubjects.existOrFail('mlDataVisualizerViewInDiscoverCard'); }, diff --git a/x-pack/test/functional_basic/apps/ml/permissions/full_ml_access.ts b/x-pack/test/functional_basic/apps/ml/permissions/full_ml_access.ts index 9806c186914a3..ced46f1f92d30 100644 --- a/x-pack/test/functional_basic/apps/ml/permissions/full_ml_access.ts +++ b/x-pack/test/functional_basic/apps/ml/permissions/full_ml_access.ts @@ -15,11 +15,14 @@ export default function ({ getService }: FtrProviderContext) { const esArchiver = getService('esArchiver'); const ml = getService('ml'); - const testUsers = [USER.ML_POWERUSER, USER.ML_POWERUSER_SPACES]; + const testUsers = [ + { user: USER.ML_POWERUSER, discoverAvailable: true }, + { user: USER.ML_POWERUSER_SPACES, discoverAvailable: false }, + ]; describe('for user with full ML access', function () { - for (const user of testUsers) { - describe(`(${user})`, function () { + for (const testUser of testUsers) { + describe(`(${testUser.user})`, function () { const ecIndexPattern = 'ft_module_sample_ecommerce'; const ecExpectedTotalCount = '287'; const ecExpectedModuleId = 'sample_data_ecommerce'; @@ -45,7 +48,7 @@ export default function ({ getService }: FtrProviderContext) { await esArchiver.loadIfNeeded('ml/module_sample_ecommerce'); await ml.testResources.createIndexPatternIfNeeded(ecIndexPattern, 'order_date'); - await ml.securityUI.loginAs(user); + await ml.securityUI.loginAs(testUser.user); }); after(async () => { @@ -127,9 +130,13 @@ export default function ({ getService }: FtrProviderContext) { await ml.testExecution.logTestStep('should display the data visualizer table'); await ml.dataVisualizerIndexBased.assertDataVisualizerTableExist(); - await ml.testExecution.logTestStep('should display the actions panel with Discover card'); + await ml.testExecution.logTestStep( + `should display the actions panel ${ + testUser.discoverAvailable ? 'with' : 'without' + } Discover card` + ); await ml.dataVisualizerIndexBased.assertActionsPanelExists(); - await ml.dataVisualizerIndexBased.assertViewInDiscoverCardExists(); + await ml.dataVisualizerIndexBased.assertViewInDiscoverCard(testUser.discoverAvailable); await ml.testExecution.logTestStep('should not display job cards'); await ml.dataVisualizerIndexBased.assertCreateAdvancedJobCardNotExists(); diff --git a/x-pack/test/functional_basic/apps/ml/permissions/no_ml_access.ts b/x-pack/test/functional_basic/apps/ml/permissions/no_ml_access.ts index 24dbee356416a..91a37d0d98cda 100644 --- a/x-pack/test/functional_basic/apps/ml/permissions/no_ml_access.ts +++ b/x-pack/test/functional_basic/apps/ml/permissions/no_ml_access.ts @@ -13,13 +13,16 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { const PageObjects = getPageObjects(['common', 'error']); const ml = getService('ml'); - const testUsers = [USER.ML_UNAUTHORIZED, USER.ML_UNAUTHORIZED_SPACES]; + const testUsers = [ + { user: USER.ML_UNAUTHORIZED, discoverAvailable: true }, + { user: USER.ML_UNAUTHORIZED_SPACES, discoverAvailable: true }, + ]; describe('for user with no ML access', function () { - for (const user of testUsers) { - describe(`(${user})`, function () { + for (const testUser of testUsers) { + describe(`(${testUser.user})`, function () { before(async () => { - await ml.securityUI.loginAs(user); + await ml.securityUI.loginAs(testUser.user); }); after(async () => { diff --git a/x-pack/test/functional_basic/apps/ml/permissions/read_ml_access.ts b/x-pack/test/functional_basic/apps/ml/permissions/read_ml_access.ts index 632922a353b33..f207b79582004 100644 --- a/x-pack/test/functional_basic/apps/ml/permissions/read_ml_access.ts +++ b/x-pack/test/functional_basic/apps/ml/permissions/read_ml_access.ts @@ -15,11 +15,14 @@ export default function ({ getService }: FtrProviderContext) { const esArchiver = getService('esArchiver'); const ml = getService('ml'); - const testUsers = [USER.ML_VIEWER, USER.ML_VIEWER_SPACES]; + const testUsers = [ + { user: USER.ML_VIEWER, discoverAvailable: true }, + { user: USER.ML_VIEWER_SPACES, discoverAvailable: false }, + ]; describe('for user with read ML access', function () { - for (const user of testUsers) { - describe(`(${user})`, function () { + for (const testUser of testUsers) { + describe(`(${testUser.user})`, function () { const ecIndexPattern = 'ft_module_sample_ecommerce'; const ecExpectedTotalCount = '287'; const ecExpectedModuleId = 'sample_data_ecommerce'; @@ -45,7 +48,7 @@ export default function ({ getService }: FtrProviderContext) { await esArchiver.loadIfNeeded('ml/module_sample_ecommerce'); await ml.testResources.createIndexPatternIfNeeded(ecIndexPattern, 'order_date'); - await ml.securityUI.loginAs(user); + await ml.securityUI.loginAs(testUser.user); }); after(async () => { @@ -127,9 +130,13 @@ export default function ({ getService }: FtrProviderContext) { await ml.testExecution.logTestStep('should display the data visualizer table'); await ml.dataVisualizerIndexBased.assertDataVisualizerTableExist(); - await ml.testExecution.logTestStep('should display the actions panel with Discover card'); + await ml.testExecution.logTestStep( + `should display the actions panel ${ + testUser.discoverAvailable ? 'with' : 'without' + } Discover card` + ); await ml.dataVisualizerIndexBased.assertActionsPanelExists(); - await ml.dataVisualizerIndexBased.assertViewInDiscoverCardExists(); + await ml.dataVisualizerIndexBased.assertViewInDiscoverCard(testUser.discoverAvailable); await ml.testExecution.logTestStep('should not display job cards'); await ml.dataVisualizerIndexBased.assertCreateAdvancedJobCardNotExists(); From 8485edc50adbe91705b7a11bc49789711bb0cb3e Mon Sep 17 00:00:00 2001 From: Maja Grubic Date: Tue, 16 Feb 2021 17:38:09 +0000 Subject: [PATCH 11/53] [Discover] Making source filters test run with fields API (#91404) (#91482) --- test/functional/apps/discover/_source_filters.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/functional/apps/discover/_source_filters.ts b/test/functional/apps/discover/_source_filters.ts index 273ccbd8bf5af..4161f7f289dbf 100644 --- a/test/functional/apps/discover/_source_filters.ts +++ b/test/functional/apps/discover/_source_filters.ts @@ -30,7 +30,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await esArchiver.loadIfNeeded('logstash_functional'); await kibanaServer.uiSettings.update({ - 'discover:searchFieldsFromSource': true, + 'discover:searchFieldsFromSource': false, }); log.debug('discover'); From 078ac3e03237c803d36d5e431567e964e5b6a453 Mon Sep 17 00:00:00 2001 From: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Date: Tue, 16 Feb 2021 13:08:43 -0500 Subject: [PATCH 12/53] [7.x] [User Experience app] fix e2e tests (#91423) (#91510) Co-authored-by: Shahzad --- .../support/step_definitions/csm/breakdown_filter.ts | 6 +++--- .../cypress/support/step_definitions/csm/csm_dashboard.ts | 6 ++++-- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/x-pack/plugins/apm/e2e/cypress/support/step_definitions/csm/breakdown_filter.ts b/x-pack/plugins/apm/e2e/cypress/support/step_definitions/csm/breakdown_filter.ts index b7e16f71ce0a4..5b4934eac1f71 100644 --- a/x-pack/plugins/apm/e2e/cypress/support/step_definitions/csm/breakdown_filter.ts +++ b/x-pack/plugins/apm/e2e/cypress/support/step_definitions/csm/breakdown_filter.ts @@ -31,13 +31,13 @@ Then(`breakdown series should appear in chart`, () => { cy.get('.euiLoadingChart').should('not.exist'); cy.get('[data-cy=pageLoadDist]').within(() => { - cy.get('div.echLegendItem__label[title=Chrome] ', DEFAULT_TIMEOUT) + cy.get('button.echLegendItem__label[title=Chrome] ', DEFAULT_TIMEOUT) .invoke('text') .should('eq', 'Chrome'); - cy.get('div.echLegendItem__label', DEFAULT_TIMEOUT).should( + cy.get('button.echLegendItem__label', DEFAULT_TIMEOUT).should( 'have.text', - 'OverallChromeChrome Mobile WebViewSafariFirefoxMobile SafariChrome MobileChrome Mobile iOS' + 'ChromeChrome Mobile WebViewSafariFirefoxMobile SafariChrome MobileChrome Mobile iOSOverall' ); }); }); diff --git a/x-pack/plugins/apm/e2e/cypress/support/step_definitions/csm/csm_dashboard.ts b/x-pack/plugins/apm/e2e/cypress/support/step_definitions/csm/csm_dashboard.ts index 8d01bfa70bc49..47154ee214dc4 100644 --- a/x-pack/plugins/apm/e2e/cypress/support/step_definitions/csm/csm_dashboard.ts +++ b/x-pack/plugins/apm/e2e/cypress/support/step_definitions/csm/csm_dashboard.ts @@ -52,12 +52,14 @@ Then(`should display percentile for page load chart`, () => { }); Then(`should display chart legend`, () => { - const chartLegend = 'div.echLegendItem__label'; + const chartLegend = 'button.echLegendItem__label'; waitForLoadingToFinish(); cy.get('.euiLoadingChart').should('not.exist'); - cy.get(chartLegend, DEFAULT_TIMEOUT).eq(0).should('have.text', 'Overall'); + cy.get('[data-cy=pageLoadDist]').within(() => { + cy.get(chartLegend, DEFAULT_TIMEOUT).eq(0).should('have.text', 'Overall'); + }); }); Then(`should display tooltip on hover`, () => { From d0cb01961948ce87f9b0233552657796914e0bde Mon Sep 17 00:00:00 2001 From: Nicolas Chaulet Date: Tue, 16 Feb 2021 13:17:58 -0500 Subject: [PATCH 13/53] [Fleet] Create default Fleet Server policy with fleet server package (#90973) (#91512) --- .../fleet/common/constants/agent_policy.ts | 15 ++++++ .../fleet/common/types/models/agent_policy.ts | 1 + .../fleet/server/saved_objects/index.ts | 1 + .../saved_objects/migrations/to_v7_12_0.ts | 9 ++-- .../fleet/server/services/agent_policy.ts | 47 ++++++++++++++++--- x-pack/plugins/fleet/server/services/setup.ts | 22 ++++++++- 6 files changed, 82 insertions(+), 13 deletions(-) diff --git a/x-pack/plugins/fleet/common/constants/agent_policy.ts b/x-pack/plugins/fleet/common/constants/agent_policy.ts index 96b6249585bfc..bed9b6e8390b8 100644 --- a/x-pack/plugins/fleet/common/constants/agent_policy.ts +++ b/x-pack/plugins/fleet/common/constants/agent_policy.ts @@ -28,4 +28,19 @@ export const DEFAULT_AGENT_POLICY: Omit< monitoring_enabled: ['logs', 'metrics'] as Array<'logs' | 'metrics'>, }; +export const DEFAULT_FLEET_SERVER_AGENT_POLICY: Omit< + AgentPolicy, + 'id' | 'updated_at' | 'updated_by' | 'revision' +> = { + name: 'Default Fleet Server policy', + namespace: 'default', + description: 'Default Fleet Server agent policy created by Kibana', + status: agentPolicyStatuses.Active, + package_policies: [], + is_default: false, + is_default_fleet_server: true, + is_managed: false, + monitoring_enabled: ['logs', 'metrics'] as Array<'logs' | 'metrics'>, +}; + export const DEFAULT_AGENT_POLICIES_PACKAGES = [defaultPackages.System]; diff --git a/x-pack/plugins/fleet/common/types/models/agent_policy.ts b/x-pack/plugins/fleet/common/types/models/agent_policy.ts index 5f41b0f70ca74..bc139537400cc 100644 --- a/x-pack/plugins/fleet/common/types/models/agent_policy.ts +++ b/x-pack/plugins/fleet/common/types/models/agent_policy.ts @@ -17,6 +17,7 @@ export interface NewAgentPolicy { namespace: string; description?: string; is_default?: boolean; + is_default_fleet_server?: boolean; // Optional when creating a policy is_managed?: boolean; // Optional when creating a policy monitoring_enabled?: Array>; } diff --git a/x-pack/plugins/fleet/server/saved_objects/index.ts b/x-pack/plugins/fleet/server/saved_objects/index.ts index f2eb8be5c030c..5b851c692ad3f 100644 --- a/x-pack/plugins/fleet/server/saved_objects/index.ts +++ b/x-pack/plugins/fleet/server/saved_objects/index.ts @@ -162,6 +162,7 @@ const getSavedObjectTypes = ( description: { type: 'text' }, namespace: { type: 'keyword' }, is_default: { type: 'boolean' }, + is_default_fleet_server: { type: 'boolean' }, is_managed: { type: 'boolean' }, status: { type: 'keyword' }, package_policies: { type: 'keyword' }, diff --git a/x-pack/plugins/fleet/server/saved_objects/migrations/to_v7_12_0.ts b/x-pack/plugins/fleet/server/saved_objects/migrations/to_v7_12_0.ts index 49a0d6fc7737f..15e68ace987b9 100644 --- a/x-pack/plugins/fleet/server/saved_objects/migrations/to_v7_12_0.ts +++ b/x-pack/plugins/fleet/server/saved_objects/migrations/to_v7_12_0.ts @@ -17,12 +17,11 @@ export const migrateAgentToV7120: SavedObjectMigrationFn, + Exclude, AgentPolicy > = (agentPolicyDoc) => { - const isV12 = 'is_managed' in agentPolicyDoc.attributes; - if (!isV12) { - agentPolicyDoc.attributes.is_managed = false; - } + agentPolicyDoc.attributes.is_managed = false; + agentPolicyDoc.attributes.is_default_fleet_server = false; + return agentPolicyDoc; }; diff --git a/x-pack/plugins/fleet/server/services/agent_policy.ts b/x-pack/plugins/fleet/server/services/agent_policy.ts index f31f38796055c..44962ea31c56c 100644 --- a/x-pack/plugins/fleet/server/services/agent_policy.ts +++ b/x-pack/plugins/fleet/server/services/agent_policy.ts @@ -35,6 +35,7 @@ import { dataTypes, FleetServerPolicy, AGENT_POLICY_INDEX, + DEFAULT_FLEET_SERVER_AGENT_POLICY, } from '../../common'; import { AgentPolicyNameExistsError, @@ -133,6 +134,39 @@ class AgentPolicyService { }; } + public async ensureDefaultFleetServerAgentPolicy( + soClient: SavedObjectsClientContract, + esClient: ElasticsearchClient + ): Promise<{ + created: boolean; + policy: AgentPolicy; + }> { + const agentPolicies = await soClient.find({ + type: AGENT_POLICY_SAVED_OBJECT_TYPE, + searchFields: ['is_default_fleet_server'], + search: 'true', + }); + + if (agentPolicies.total === 0) { + const newDefaultAgentPolicy: NewAgentPolicy = { + ...DEFAULT_FLEET_SERVER_AGENT_POLICY, + }; + + return { + created: true, + policy: await this.create(soClient, esClient, newDefaultAgentPolicy), + }; + } + + return { + created: false, + policy: { + id: agentPolicies.saved_objects[0].id, + ...agentPolicies.saved_objects[0].attributes, + }, + }; + } + public async create( soClient: SavedObjectsClientContract, esClient: ElasticsearchClient, @@ -569,18 +603,19 @@ class AgentPolicyService { if (!(await isAgentsSetup(soClient))) { return; } - const policy = await agentPolicyService.getFullAgentPolicy(soClient, agentPolicyId); - if (!policy || !policy.revision) { + const policy = await agentPolicyService.get(soClient, agentPolicyId); + const fullPolicy = await agentPolicyService.getFullAgentPolicy(soClient, agentPolicyId); + if (!policy || !fullPolicy || !fullPolicy.revision) { return; } const fleetServerPolicy: FleetServerPolicy = { '@timestamp': new Date().toISOString(), - revision_idx: policy.revision, + revision_idx: fullPolicy.revision, coordinator_idx: 0, - data: (policy as unknown) as FleetServerPolicy['data'], - policy_id: policy.id, - default_fleet_server: false, + data: (fullPolicy as unknown) as FleetServerPolicy['data'], + policy_id: fullPolicy.id, + default_fleet_server: policy.is_default_fleet_server === true, }; await esClient.create({ diff --git a/x-pack/plugins/fleet/server/services/setup.ts b/x-pack/plugins/fleet/server/services/setup.ts index 6c8f24e799574..94c3c606f9f8f 100644 --- a/x-pack/plugins/fleet/server/services/setup.ts +++ b/x-pack/plugins/fleet/server/services/setup.ts @@ -56,15 +56,20 @@ async function createSetupSideEffects( esClient: ElasticsearchClient, callCluster: CallESAsCurrentUser ): Promise { + const isFleetServerEnabled = appContextService.getConfig()?.agents.fleetServerEnabled; const [ installedPackages, defaultOutput, { created: defaultAgentPolicyCreated, defaultAgentPolicy }, + { created: defaultFleetServerPolicyCreated, policy: defaultFleetServerPolicy }, ] = await Promise.all([ // packages installed by default ensureInstalledDefaultPackages(soClient, callCluster), outputService.ensureDefaultOutput(soClient), agentPolicyService.ensureDefaultAgentPolicy(soClient, esClient), + isFleetServerEnabled + ? agentPolicyService.ensureDefaultFleetServerAgentPolicy(soClient, esClient) + : {}, updateFleetRoleIfExists(callCluster), settingsService.getSettings(soClient).catch((e: any) => { if (e.isBoom && e.output.statusCode === 404) { @@ -83,7 +88,7 @@ async function createSetupSideEffects( // By moving this outside of the Promise.all, the upgrade will occur first, and then we'll attempt to reinstall any // packages that are stuck in the installing state. await ensurePackagesCompletedInstall(soClient, callCluster); - if (appContextService.getConfig()?.agents.fleetServerEnabled) { + if (isFleetServerEnabled) { await ensureInstalledPackage({ savedObjectsClient: soClient, pkgName: FLEET_SERVER_PACKAGE, @@ -94,15 +99,28 @@ async function createSetupSideEffects( } if (appContextService.getConfig()?.agents?.fleetServerEnabled) { - await ensureInstalledPackage({ + const fleetServerPackage = await ensureInstalledPackage({ savedObjectsClient: soClient, pkgName: FLEET_SERVER_PACKAGE, callCluster, }); await ensureFleetServerIndicesCreated(esClient); await runFleetServerMigration(); + + if (defaultFleetServerPolicyCreated) { + await addPackageToAgentPolicy( + soClient, + esClient, + callCluster, + fleetServerPackage, + defaultFleetServerPolicy, + defaultOutput + ); + } } + // If we just created the default fleet server policy add the fleet server package + // If we just created the default policy, ensure default packages are added to it if (defaultAgentPolicyCreated) { const agentPolicyWithPackagePolicies = await agentPolicyService.get( From 3a06071634c1d56029259dd08260dd738fd5d10e Mon Sep 17 00:00:00 2001 From: Marco Vettorello Date: Tue, 16 Feb 2021 19:23:03 +0100 Subject: [PATCH 14/53] Update dependency @elastic/charts to v24.6.0 (#91382) (#91514) * Update dependency @elastic/charts to v24.6.0 * Update donut chart snapshot Co-authored-by: Renovate Bot Co-authored-by: Marco Vettorello Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Renovate Bot --- package.json | 2 +- .../charts/__snapshots__/donut_chart.test.tsx.snap | 11 +++++++++++ yarn.lock | 8 ++++---- 3 files changed, 16 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 08e3fbb16bb1a..695f34e49d5f0 100644 --- a/package.json +++ b/package.json @@ -349,7 +349,7 @@ "@cypress/webpack-preprocessor": "^5.5.0", "@elastic/apm-rum": "^5.6.1", "@elastic/apm-rum-react": "^1.2.5", - "@elastic/charts": "24.5.1", + "@elastic/charts": "24.6.0", "@elastic/eslint-config-kibana": "link:packages/elastic-eslint-config-kibana", "@elastic/eslint-plugin-eui": "0.0.2", "@elastic/github-checks-reporter": "0.0.20b3", diff --git a/x-pack/plugins/uptime/public/components/common/charts/__snapshots__/donut_chart.test.tsx.snap b/x-pack/plugins/uptime/public/components/common/charts/__snapshots__/donut_chart.test.tsx.snap index 238ce6c3f9cee..31d4322210e2b 100644 --- a/x-pack/plugins/uptime/public/components/common/charts/__snapshots__/donut_chart.test.tsx.snap +++ b/x-pack/plugins/uptime/public/components/common/charts/__snapshots__/donut_chart.test.tsx.snap @@ -50,6 +50,17 @@ exports[`DonutChart component passes correct props without errors for valid prop "strokeWidth": 1, "visible": true, }, + "axisPanelTitle": Object { + "fill": "#333", + "fontFamily": "sans-serif", + "fontSize": 10, + "fontStyle": "bold", + "padding": Object { + "inner": 8, + "outer": 0, + }, + "visible": true, + }, "axisTitle": Object { "fill": "#333", "fontFamily": "sans-serif", diff --git a/yarn.lock b/yarn.lock index 454146b6bd10b..3e275ed135b38 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2146,10 +2146,10 @@ dependencies: object-hash "^1.3.0" -"@elastic/charts@24.5.1": - version "24.5.1" - resolved "https://registry.yarnpkg.com/@elastic/charts/-/charts-24.5.1.tgz#4757721b0323b15412c92d696dd76fdef9b963f8" - integrity sha512-eHJna3xyHREaSfTRb+3/34EmyoINopH6yP9KReakXRb0jW8DD4n9IkbPFwpVN3uXQ6ND2x1ObA0ZzLPSLCPozg== +"@elastic/charts@24.6.0": + version "24.6.0" + resolved "https://registry.yarnpkg.com/@elastic/charts/-/charts-24.6.0.tgz#2123c72e69e1e4557be41ae55c085a5a9f75d3b6" + integrity sha512-fL0301EcHxJEYRzdlD4JIA3VXY4qwRPSkRrk8hvJNryTlQWEdyXZF3HNczk0IrgST5cfCOGAWG8IVtO59HxUJw== dependencies: "@popperjs/core" "^2.4.0" chroma-js "^2.1.0" From 182482b873f008936a3627e96f0405b883eabc86 Mon Sep 17 00:00:00 2001 From: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Date: Tue, 16 Feb 2021 13:29:40 -0500 Subject: [PATCH 15/53] [APM] Add setup instructions for PHP agent (#91381) (#91515) Co-authored-by: Sergey Kleyman Co-authored-by: Sergey Kleyman Co-authored-by: Dario Gieselaar Co-authored-by: Sergey Kleyman Co-authored-by: Sergey Kleyman --- src/plugins/apm_oss/server/index.ts | 1 + .../apm_oss/server/tutorial/envs/on_prem.ts | 5 ++ .../instructions/apm_agent_instructions.ts | 51 +++++++++++++++++++ .../home/common/instruction_variant.ts | 2 + .../apm/server/tutorial/elastic_cloud.ts | 5 ++ 5 files changed, 64 insertions(+) diff --git a/src/plugins/apm_oss/server/index.ts b/src/plugins/apm_oss/server/index.ts index bea9965748f27..a02e28201a1b9 100644 --- a/src/plugins/apm_oss/server/index.ts +++ b/src/plugins/apm_oss/server/index.ts @@ -47,4 +47,5 @@ export { createGoAgentInstructions, createJavaAgentInstructions, createDotNetAgentInstructions, + createPhpAgentInstructions, } from './tutorial/instructions/apm_agent_instructions'; diff --git a/src/plugins/apm_oss/server/tutorial/envs/on_prem.ts b/src/plugins/apm_oss/server/tutorial/envs/on_prem.ts index 7f391c0159d38..d0df02bc43ad1 100644 --- a/src/plugins/apm_oss/server/tutorial/envs/on_prem.ts +++ b/src/plugins/apm_oss/server/tutorial/envs/on_prem.ts @@ -27,6 +27,7 @@ import { createGoAgentInstructions, createJavaAgentInstructions, createDotNetAgentInstructions, + createPhpAgentInstructions, } from '../instructions/apm_agent_instructions'; export function onPremInstructions({ @@ -152,6 +153,10 @@ export function onPremInstructions({ id: INSTRUCTION_VARIANT.DOTNET, instructions: createDotNetAgentInstructions(), }, + { + id: INSTRUCTION_VARIANT.PHP, + instructions: createPhpAgentInstructions(), + }, ], statusCheck: { title: i18n.translate('apmOss.tutorial.apmAgents.statusCheck.title', { diff --git a/src/plugins/apm_oss/server/tutorial/instructions/apm_agent_instructions.ts b/src/plugins/apm_oss/server/tutorial/instructions/apm_agent_instructions.ts index 60bc95e0bd006..20be2cc196a94 100644 --- a/src/plugins/apm_oss/server/tutorial/instructions/apm_agent_instructions.ts +++ b/src/plugins/apm_oss/server/tutorial/instructions/apm_agent_instructions.ts @@ -701,3 +701,54 @@ export const createDotNetAgentInstructions = (apmServerUrl = '', secretToken = ' }), }, ]; + +export const createPhpAgentInstructions = (apmServerUrl = '', secretToken = '') => [ + { + title: i18n.translate('apmOss.tutorial.phpClient.download.title', { + defaultMessage: 'Download the APM agent', + }), + textPre: i18n.translate('apmOss.tutorial.phpClient.download.textPre', { + defaultMessage: + 'Download the package corresponding to your platform from [GitHub releases]({githubReleasesLink}).', + values: { + githubReleasesLink: 'https://github.com/elastic/apm-agent-php/releases', + }, + }), + }, + { + title: i18n.translate('apmOss.tutorial.phpClient.installPackage.title', { + defaultMessage: 'Install the downloaded package', + }), + textPre: i18n.translate('apmOss.tutorial.phpClient.installPackage.textPre', { + defaultMessage: 'For example on Alpine Linux using APK package:', + }), + commands: ['apk add --allow-untrusted .apk'], + textPost: i18n.translate('apmOss.tutorial.phpClient.installPackage.textPost', { + defaultMessage: + 'See the [documentation]({documentationLink}) for installation commands on other supported platforms and advanced installation.', + values: { + documentationLink: '{config.docs.base_url}guide/en/apm/agent/php/current/setup.html', + }, + }), + }, + { + title: i18n.translate('apmOss.tutorial.phpClient.configureAgent.title', { + defaultMessage: 'Configure the agent', + }), + textPre: i18n.translate('apmOss.tutorial.phpClient.configureAgent.textPre', { + defaultMessage: + 'APM is automatically started when your app boots. Configure the agent either via `php.ini` file:', + }), + commands: `elastic_apm.server_url=http://localhost:8200 +elastic_apm.service_name="My service" +`.split('\n'), + textPost: i18n.translate('apmOss.tutorial.phpClient.configure.textPost', { + defaultMessage: + 'See the [documentation]({documentationLink}) for configuration options and advanced usage.\n\n', + values: { + documentationLink: + '{config.docs.base_url}guide/en/apm/agent/php/current/configuration.html', + }, + }), + }, +]; diff --git a/src/plugins/home/common/instruction_variant.ts b/src/plugins/home/common/instruction_variant.ts index ae6735568851c..310ee23460a08 100644 --- a/src/plugins/home/common/instruction_variant.ts +++ b/src/plugins/home/common/instruction_variant.ts @@ -23,6 +23,7 @@ export const INSTRUCTION_VARIANT = { JAVA: 'java', DOTNET: 'dotnet', LINUX: 'linux', + PHP: 'php', }; const DISPLAY_MAP = { @@ -42,6 +43,7 @@ const DISPLAY_MAP = { [INSTRUCTION_VARIANT.JAVA]: 'Java', [INSTRUCTION_VARIANT.DOTNET]: '.NET', [INSTRUCTION_VARIANT.LINUX]: 'Linux', + [INSTRUCTION_VARIANT.PHP]: 'PHP', }; /** diff --git a/x-pack/plugins/apm/server/tutorial/elastic_cloud.ts b/x-pack/plugins/apm/server/tutorial/elastic_cloud.ts index fac38027e1b82..08e1ff75d4324 100644 --- a/x-pack/plugins/apm/server/tutorial/elastic_cloud.ts +++ b/x-pack/plugins/apm/server/tutorial/elastic_cloud.ts @@ -18,6 +18,7 @@ import { createGoAgentInstructions, createJavaAgentInstructions, createDotNetAgentInstructions, + createPhpAgentInstructions, } from '../../../../../src/plugins/apm_oss/server'; import { CloudSetup } from '../../../cloud/server'; @@ -105,6 +106,10 @@ function getApmAgentInstructionSet(cloudSetup?: CloudSetup) { id: INSTRUCTION_VARIANT.DOTNET, instructions: createDotNetAgentInstructions(apmServerUrl, secretToken), }, + { + id: INSTRUCTION_VARIANT.PHP, + instructions: createPhpAgentInstructions(apmServerUrl, secretToken), + }, ], }; } From bc05bea084556c560136f1d68e33cebb9c580e56 Mon Sep 17 00:00:00 2001 From: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Date: Tue, 16 Feb 2021 13:56:57 -0500 Subject: [PATCH 16/53] [7.x] [Uptime] Fix alert loading on error (#91453) (#91520) Co-authored-by: Shahzad --- .../components/overview/monitor_list/columns/enable_alert.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/uptime/public/components/overview/monitor_list/columns/enable_alert.tsx b/x-pack/plugins/uptime/public/components/overview/monitor_list/columns/enable_alert.tsx index d1401c64a8925..444198bfa9412 100644 --- a/x-pack/plugins/uptime/public/components/overview/monitor_list/columns/enable_alert.tsx +++ b/x-pack/plugins/uptime/public/components/overview/monitor_list/columns/enable_alert.tsx @@ -42,7 +42,7 @@ export const EnableMonitorAlert = ({ monitorId, monitorName }: Props) => { const { data: deletedAlertId } = useSelector(isAlertDeletedSelector); - const { data: newAlert } = useSelector(newAlertSelector); + const { data: newAlert, error: newAlertError } = useSelector(newAlertSelector); const isNewAlert = newAlert?.params.search.includes(monitorId); @@ -85,7 +85,7 @@ export const EnableMonitorAlert = ({ monitorId, monitorName }: Props) => { useEffect(() => { setIsLoading(false); - }, [hasAlert, deletedAlertId]); + }, [hasAlert, deletedAlertId, newAlertError]); const hasDefaultConnectors = (settings?.defaultConnectors ?? []).length > 0; From 55c873e30078a64315405396c79d26fca338e0a6 Mon Sep 17 00:00:00 2001 From: Anton Dosov Date: Tue, 16 Feb 2021 19:57:59 +0100 Subject: [PATCH 17/53] [Expressions] Cancel nested executions when main execution is canceled (#91486) (#91522) --- .../execution/execution.abortion.test.ts | 71 +++++++++++++++++++ .../expressions/common/execution/execution.ts | 14 +++- 2 files changed, 83 insertions(+), 2 deletions(-) diff --git a/src/plugins/expressions/common/execution/execution.abortion.test.ts b/src/plugins/expressions/common/execution/execution.abortion.test.ts index 7f6141b60e9a7..33bb782691747 100644 --- a/src/plugins/expressions/common/execution/execution.abortion.test.ts +++ b/src/plugins/expressions/common/execution/execution.abortion.test.ts @@ -6,9 +6,11 @@ * Side Public License, v 1. */ +import { waitFor } from '@testing-library/react'; import { Execution } from './execution'; import { parseExpression } from '../ast'; import { createUnitTestExecutor } from '../test_helpers'; +import { ExpressionFunctionDefinition } from '../expression_functions'; jest.useFakeTimers(); @@ -81,4 +83,73 @@ describe('Execution abortion tests', () => { jest.useFakeTimers(); }); + + test('nested expressions are aborted when parent aborted', async () => { + jest.useRealTimers(); + const started = jest.fn(); + const completed = jest.fn(); + const aborted = jest.fn(); + + const defer: ExpressionFunctionDefinition<'defer', any, { time: number }, any> = { + name: 'defer', + args: { + time: { + aliases: ['_'], + help: 'Calls function from a context after delay unless aborted', + types: ['number'], + }, + }, + help: '', + fn: async (input, args, { abortSignal }) => { + started(); + await new Promise((r) => { + const timeout = setTimeout(() => { + if (!abortSignal.aborted) { + completed(); + } + r(undefined); + }, args.time); + + abortSignal.addEventListener('abort', () => { + aborted(); + clearTimeout(timeout); + r(undefined); + }); + }); + + return args.time; + }, + }; + + const expression = 'defer time={defer time={defer time=300}}'; + const executor = createUnitTestExecutor(); + executor.registerFunction(defer); + const execution = new Execution({ + executor, + ast: parseExpression(expression), + params: {}, + }); + + execution.start(); + + await waitFor(() => expect(started).toHaveBeenCalledTimes(1)); + + execution.cancel(); + const result = await execution.result; + expect(result).toMatchObject({ + type: 'error', + error: { + message: 'The expression was aborted.', + name: 'AbortError', + }, + }); + + await waitFor(() => expect(aborted).toHaveBeenCalledTimes(1)); + + expect(started).toHaveBeenCalledTimes(1); + expect(aborted).toHaveBeenCalledTimes(1); + expect(completed).toHaveBeenCalledTimes(0); + + jest.useFakeTimers(); + }); }); diff --git a/src/plugins/expressions/common/execution/execution.ts b/src/plugins/expressions/common/execution/execution.ts index e555258a0cf99..bf545a0075bed 100644 --- a/src/plugins/expressions/common/execution/execution.ts +++ b/src/plugins/expressions/common/execution/execution.ts @@ -120,6 +120,13 @@ export class Execution< */ private readonly firstResultFuture = new Defer(); + /** + * Keeping track of any child executions + * Needed to cancel child executions in case parent execution is canceled + * @private + */ + private readonly childExecutions: Execution[] = []; + /** * Contract is a public representation of `Execution` instances. Contract we * can return to other plugins for their consumption. @@ -203,8 +210,10 @@ export class Execution< const chainPromise = this.invokeChain(this.state.get().ast.chain, input); this.race(chainPromise).then(resolve, (error) => { - if (this.abortController.signal.aborted) resolve(createAbortErrorValue()); - else reject(error); + if (this.abortController.signal.aborted) { + this.childExecutions.forEach((ex) => ex.cancel()); + resolve(createAbortErrorValue()); + } else reject(error); }); this.firstResultFuture.promise @@ -460,6 +469,7 @@ export class Execution< ast as ExpressionAstExpression, this.execution.params ); + this.childExecutions.push(execution); execution.start(input); return await execution.result; case 'string': From 1fb387ea8d134f9ff737807919b5eef6e3890606 Mon Sep 17 00:00:00 2001 From: Wylie Conlon Date: Tue, 16 Feb 2021 14:06:02 -0500 Subject: [PATCH 18/53] [Lens] Support histogram mapping type for all numeric functions (#90357) (#91521) * [Lens] Support histogram mapping type * Fix field stats and allow max/min * Fix types * Revert to regular sample data * Simplify server code * Add test for edge case Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../data/common/search/aggs/metrics/max.ts | 2 +- .../data/common/search/aggs/metrics/min.ts | 2 +- .../indexpattern_datasource/datapanel.tsx | 11 ++- .../operations/definitions/metrics.tsx | 6 +- .../definitions/percentile.test.tsx | 74 ++++++++++++++ .../operations/definitions/percentile.tsx | 6 +- .../public/indexpattern_datasource/utils.ts | 1 + x-pack/plugins/lens/public/types.ts | 3 +- .../public/xy_visualization/xy_suggestions.ts | 1 + .../plugins/lens/server/routes/field_stats.ts | 55 ++++++++--- .../api_integration/apis/lens/field_stats.ts | 97 +++++++++++++++++++ .../apps/visualize/precalculated_histogram.ts | 4 +- .../pre_calculated_histogram/data.json | 22 ++++- 13 files changed, 256 insertions(+), 28 deletions(-) diff --git a/src/plugins/data/common/search/aggs/metrics/max.ts b/src/plugins/data/common/search/aggs/metrics/max.ts index ee2d5ad03ce3a..5a41cdbb256c8 100644 --- a/src/plugins/data/common/search/aggs/metrics/max.ts +++ b/src/plugins/data/common/search/aggs/metrics/max.ts @@ -36,7 +36,7 @@ export const getMaxMetricAgg = () => { { name: 'field', type: 'field', - filterFieldTypes: [KBN_FIELD_TYPES.NUMBER, KBN_FIELD_TYPES.DATE], + filterFieldTypes: [KBN_FIELD_TYPES.NUMBER, KBN_FIELD_TYPES.DATE, KBN_FIELD_TYPES.HISTOGRAM], }, ], }); diff --git a/src/plugins/data/common/search/aggs/metrics/min.ts b/src/plugins/data/common/search/aggs/metrics/min.ts index f9e3c5b59d586..1805546a9fa34 100644 --- a/src/plugins/data/common/search/aggs/metrics/min.ts +++ b/src/plugins/data/common/search/aggs/metrics/min.ts @@ -36,7 +36,7 @@ export const getMinMetricAgg = () => { { name: 'field', type: 'field', - filterFieldTypes: [KBN_FIELD_TYPES.NUMBER, KBN_FIELD_TYPES.DATE], + filterFieldTypes: [KBN_FIELD_TYPES.NUMBER, KBN_FIELD_TYPES.DATE, KBN_FIELD_TYPES.HISTOGRAM], }, ], }); diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/datapanel.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/datapanel.tsx index 8047807093eef..e487e185a8c8f 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/datapanel.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/datapanel.tsx @@ -57,7 +57,15 @@ function sortFields(fieldA: IndexPatternField, fieldB: IndexPatternField) { return fieldA.displayName.localeCompare(fieldB.displayName, undefined, { sensitivity: 'base' }); } -const supportedFieldTypes = new Set(['string', 'number', 'boolean', 'date', 'ip', 'document']); +const supportedFieldTypes = new Set([ + 'string', + 'number', + 'boolean', + 'date', + 'ip', + 'histogram', + 'document', +]); const fieldTypeNames: Record = { document: i18n.translate('xpack.lens.datatypes.record', { defaultMessage: 'record' }), @@ -66,6 +74,7 @@ const fieldTypeNames: Record = { boolean: i18n.translate('xpack.lens.datatypes.boolean', { defaultMessage: 'boolean' }), date: i18n.translate('xpack.lens.datatypes.date', { defaultMessage: 'date' }), ip: i18n.translate('xpack.lens.datatypes.ipAddress', { defaultMessage: 'IP' }), + histogram: i18n.translate('xpack.lens.datatypes.histogram', { defaultMessage: 'histogram' }), }; // Wrapper around esQuery.buildEsQuery, handling errors (e.g. because a query can't be parsed) by diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/metrics.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/metrics.tsx index e11ee580deb9b..e724a34be20e8 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/metrics.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/metrics.tsx @@ -32,6 +32,8 @@ const typeToFn: Record = { median: 'aggMedian', }; +const supportedTypes = ['number', 'histogram']; + function buildMetricOperation>({ type, displayName, @@ -61,7 +63,7 @@ function buildMetricOperation>({ timeScalingMode: optionalTimeScaling ? 'optional' : undefined, getPossibleOperationForField: ({ aggregationRestrictions, aggregatable, type: fieldType }) => { if ( - fieldType === 'number' && + supportedTypes.includes(fieldType) && aggregatable && (!aggregationRestrictions || aggregationRestrictions[type]) ) { @@ -77,7 +79,7 @@ function buildMetricOperation>({ return Boolean( newField && - newField.type === 'number' && + supportedTypes.includes(newField.type) && newField.aggregatable && (!newField.aggregationRestrictions || newField.aggregationRestrictions![type]) ); diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/percentile.test.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/percentile.test.tsx index 07bab16b7096f..9ac91be5a17ec 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/percentile.test.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/percentile.test.tsx @@ -68,6 +68,52 @@ describe('percentile', () => { }; }); + describe('getPossibleOperationForField', () => { + it('should accept number', () => { + expect( + percentileOperation.getPossibleOperationForField({ + name: 'bytes', + displayName: 'bytes', + type: 'number', + esTypes: ['long'], + aggregatable: true, + }) + ).toEqual({ + dataType: 'number', + isBucketed: false, + scale: 'ratio', + }); + }); + + it('should accept histogram', () => { + expect( + percentileOperation.getPossibleOperationForField({ + name: 'response_time', + displayName: 'response_time', + type: 'histogram', + esTypes: ['histogram'], + aggregatable: true, + }) + ).toEqual({ + dataType: 'number', + isBucketed: false, + scale: 'ratio', + }); + }); + + it('should reject keywords', () => { + expect( + percentileOperation.getPossibleOperationForField({ + name: 'origin', + displayName: 'origin', + type: 'string', + esTypes: ['keyword'], + aggregatable: true, + }) + ).toBeUndefined(); + }); + }); + describe('toEsAggsFn', () => { it('should reflect params correctly', () => { const percentileColumn = layer.columns.col2 as PercentileIndexPatternColumn; @@ -134,6 +180,34 @@ describe('percentile', () => { }); }); + describe('isTransferable', () => { + it('should transfer from number to histogram', () => { + const indexPattern = createMockedIndexPattern(); + indexPattern.getFieldByName = jest.fn().mockReturnValue({ + name: 'response_time', + displayName: 'response_time', + type: 'histogram', + esTypes: ['histogram'], + aggregatable: true, + }); + expect( + percentileOperation.isTransferable( + { + label: '', + sourceField: 'response_time', + isBucketed: false, + dataType: 'number', + operationType: 'percentile', + params: { + percentile: 95, + }, + }, + indexPattern + ) + ).toBeTruthy(); + }); + }); + describe('param editor', () => { it('should render current percentile', () => { const updateLayerSpy = jest.fn(); diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/percentile.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/percentile.tsx index f236b2932b2d3..e7654380bd85f 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/percentile.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/percentile.tsx @@ -42,6 +42,8 @@ function ofName(name: string, percentile: number) { const DEFAULT_PERCENTILE_VALUE = 95; +const supportedFieldTypes = ['number', 'histogram']; + export const percentileOperation: OperationDefinition = { type: 'percentile', displayName: i18n.translate('xpack.lens.indexPattern.percentile', { @@ -49,7 +51,7 @@ export const percentileOperation: OperationDefinition { - if (fieldType === 'number' && aggregatable && !aggregationRestrictions) { + if (supportedFieldTypes.includes(fieldType) && aggregatable && !aggregationRestrictions) { return { dataType: 'number', isBucketed: false, @@ -62,7 +64,7 @@ export const percentileOperation: OperationDefinition = DatasourceDimensionDropProp dropType: DropType; }; -export type DataType = 'document' | 'string' | 'number' | 'date' | 'boolean' | 'ip'; +export type FieldOnlyDataType = 'document' | 'ip' | 'histogram'; +export type DataType = 'string' | 'number' | 'date' | 'boolean' | FieldOnlyDataType; // An operation represents a column in a table, not any information // about how the column was created such as whether it is a sum or average. diff --git a/x-pack/plugins/lens/public/xy_visualization/xy_suggestions.ts b/x-pack/plugins/lens/public/xy_visualization/xy_suggestions.ts index 8b121232162aa..772934160a058 100644 --- a/x-pack/plugins/lens/public/xy_visualization/xy_suggestions.ts +++ b/x-pack/plugins/lens/public/xy_visualization/xy_suggestions.ts @@ -26,6 +26,7 @@ const columnSortOrder = { ip: 3, boolean: 4, number: 5, + histogram: 6, }; /** diff --git a/x-pack/plugins/lens/server/routes/field_stats.ts b/x-pack/plugins/lens/server/routes/field_stats.ts index e1681a74c2951..7fd884755d86d 100644 --- a/x-pack/plugins/lens/server/routes/field_stats.ts +++ b/x-pack/plugins/lens/server/routes/field_stats.ts @@ -86,7 +86,11 @@ export async function initFieldsRoute(setup: CoreSetup) { return result; }; - if (field.type === 'number') { + if (field.type === 'histogram') { + return res.ok({ + body: await getNumberHistogram(search, field, false), + }); + } else if (field.type === 'number') { return res.ok({ body: await getNumberHistogram(search, field), }); @@ -120,21 +124,31 @@ export async function initFieldsRoute(setup: CoreSetup) { export async function getNumberHistogram( aggSearchWithBody: (body: unknown) => Promise, - field: IFieldType + field: IFieldType, + useTopHits = true ): Promise { const fieldRef = getFieldRef(field); - const searchBody = { + const baseAggs = { + min_value: { + min: { field: field.name }, + }, + max_value: { + max: { field: field.name }, + }, + sample_count: { value_count: { ...fieldRef } }, + }; + const searchWithoutHits = { + sample: { + sampler: { shard_size: SHARD_SIZE }, + aggs: { ...baseAggs }, + }, + }; + const searchWithHits = { sample: { sampler: { shard_size: SHARD_SIZE }, aggs: { - min_value: { - min: { field: field.name }, - }, - max_value: { - max: { field: field.name }, - }, - sample_count: { value_count: { ...fieldRef } }, + ...baseAggs, top_values: { terms: { ...fieldRef, size: 10 }, }, @@ -142,14 +156,18 @@ export async function getNumberHistogram( }, }; - const minMaxResult = (await aggSearchWithBody(searchBody)) as ESSearchResponse< - unknown, - { body: { aggs: typeof searchBody } } - >; + const minMaxResult = (await aggSearchWithBody( + useTopHits ? searchWithHits : searchWithoutHits + )) as + | ESSearchResponse + | ESSearchResponse; const minValue = minMaxResult.aggregations!.sample.min_value.value; const maxValue = minMaxResult.aggregations!.sample.max_value.value; - const terms = minMaxResult.aggregations!.sample.top_values; + const terms = + 'top_values' in minMaxResult.aggregations!.sample + ? minMaxResult.aggregations!.sample.top_values + : { buckets: [] }; const topValuesBuckets = { buckets: terms.buckets.map((bucket) => ({ count: bucket.doc_count, @@ -169,7 +187,12 @@ export async function getNumberHistogram( sampledValues: minMaxResult.aggregations!.sample.sample_count.value!, sampledDocuments: minMaxResult.aggregations!.sample.doc_count, topValues: topValuesBuckets, - histogram: { buckets: [] }, + histogram: useTopHits + ? { buckets: [] } + : { + // Insert a fake bucket for a single-value histogram + buckets: [{ count: minMaxResult.aggregations!.sample.doc_count, key: minValue }], + }, }; } diff --git a/x-pack/test/api_integration/apis/lens/field_stats.ts b/x-pack/test/api_integration/apis/lens/field_stats.ts index 2cfce5ef31305..ac4ebb4e5b02c 100644 --- a/x-pack/test/api_integration/apis/lens/field_stats.ts +++ b/x-pack/test/api_integration/apis/lens/field_stats.ts @@ -23,10 +23,12 @@ export default ({ getService }: FtrProviderContext) => { before(async () => { await esArchiver.loadIfNeeded('logstash_functional'); await esArchiver.loadIfNeeded('visualize/default'); + await esArchiver.loadIfNeeded('pre_calculated_histogram'); }); after(async () => { await esArchiver.unload('logstash_functional'); await esArchiver.unload('visualize/default'); + await esArchiver.unload('pre_calculated_histogram'); }); describe('field distribution', () => { @@ -347,6 +349,101 @@ export default ({ getService }: FtrProviderContext) => { }); }); + it('should return an auto histogram for precalculated histograms', async () => { + const { body } = await supertest + .post('/api/lens/index_stats/histogram-test/field') + .set(COMMON_HEADERS) + .send({ + dslQuery: { match_all: {} }, + fromDate: TEST_START_TIME, + toDate: TEST_END_TIME, + field: { + name: 'histogram-content', + type: 'histogram', + }, + }) + .expect(200); + + expect(body).to.eql({ + histogram: { + buckets: [ + { + count: 237, + key: 0, + }, + { + count: 323, + key: 0.47000000000000003, + }, + { + count: 454, + key: 0.9400000000000001, + }, + { + count: 166, + key: 1.4100000000000001, + }, + { + count: 168, + key: 1.8800000000000001, + }, + { + count: 425, + key: 2.35, + }, + { + count: 311, + key: 2.8200000000000003, + }, + { + count: 391, + key: 3.29, + }, + { + count: 406, + key: 3.7600000000000002, + }, + { + count: 324, + key: 4.23, + }, + { + count: 628, + key: 4.7, + }, + ], + }, + sampledDocuments: 7, + sampledValues: 3833, + totalDocuments: 7, + topValues: { buckets: [] }, + }); + }); + + it('should return a single-value histogram when filtering a precalculated histogram', async () => { + const { body } = await supertest + .post('/api/lens/index_stats/histogram-test/field') + .set(COMMON_HEADERS) + .send({ + dslQuery: { match: { 'histogram-title': 'single value' } }, + fromDate: TEST_START_TIME, + toDate: TEST_END_TIME, + field: { + name: 'histogram-content', + type: 'histogram', + }, + }) + .expect(200); + + expect(body).to.eql({ + histogram: { buckets: [{ count: 1, key: 1 }] }, + sampledDocuments: 1, + sampledValues: 1, + totalDocuments: 1, + topValues: { buckets: [] }, + }); + }); + it('should return histograms for scripted date fields', async () => { const { body } = await supertest .post('/api/lens/index_stats/logstash-2015.09.22/field') diff --git a/x-pack/test/functional/apps/visualize/precalculated_histogram.ts b/x-pack/test/functional/apps/visualize/precalculated_histogram.ts index a0c8fa3b21ffd..151c9e981250f 100644 --- a/x-pack/test/functional/apps/visualize/precalculated_histogram.ts +++ b/x-pack/test/functional/apps/visualize/precalculated_histogram.ts @@ -64,7 +64,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('with average aggregation', async () => { const data = await renderTableForAggregation('Average'); - expect(data).to.eql([['2.8510720308359434']]); + expect(data).to.eql([['2.8653795982259327']]); }); it('with median aggregation', async () => { @@ -79,7 +79,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('with sum aggregation', async () => { const data = await renderTableForAggregation('Sum'); - expect(data).to.eql([['11834.800000000001']]); + expect(data).to.eql([['10983']]); }); }); }); diff --git a/x-pack/test/functional/es_archives/pre_calculated_histogram/data.json b/x-pack/test/functional/es_archives/pre_calculated_histogram/data.json index cab1dbdf84483..121a4036aaacd 100644 --- a/x-pack/test/functional/es_archives/pre_calculated_histogram/data.json +++ b/x-pack/test/functional/es_archives/pre_calculated_histogram/data.json @@ -5,8 +5,7 @@ "index": ".kibana", "source": { "index-pattern": { - "title": "histogram-test", - "fields": "[{\"name\":\"_id\",\"type\":\"string\",\"esTypes\":[\"_id\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"_index\",\"type\":\"string\",\"esTypes\":[\"_index\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"_score\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":false,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"_source\",\"type\":\"_source\",\"esTypes\":[\"_source\"],\"count\":0,\"scripted\":false,\"searchable\":false,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"_type\",\"type\":\"string\",\"esTypes\":[\"_type\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"histogram-content\",\"type\":\"histogram\",\"esTypes\":[\"histogram\"],\"count\":0,\"scripted\":false,\"searchable\":false,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"histogram-title\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true}]" + "title": "histogram-test" }, "type": "index-pattern" } @@ -195,3 +194,22 @@ } } } + +{ + "type": "doc", + "value": { + "id": "5e694159d909d9d99b5e12d1", + "index": "histogram-test", + "source": { + "histogram-title": "single value", + "histogram-content": { + "values": [ + 1 + ], + "counts": [ + 1 + ] + } + } + } +} From 1bfdc14fc4f21ce19c100586713372d0ff38fe4e Mon Sep 17 00:00:00 2001 From: Jonathan Budzenski Date: Tue, 16 Feb 2021 13:21:16 -0600 Subject: [PATCH 19/53] [CI] Detect architecture when determining node download url (#91497) Currently CI is setup to always download the x64 Node.js architecture. When runing builds on ARM machines we'll want to make sure the architecture matches the machine. --- src/dev/ci_setup/setup_env.sh | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/dev/ci_setup/setup_env.sh b/src/dev/ci_setup/setup_env.sh index 2deafaaf35a94..b9898960135fc 100644 --- a/src/dev/ci_setup/setup_env.sh +++ b/src/dev/ci_setup/setup_env.sh @@ -56,7 +56,13 @@ export WORKSPACE="${WORKSPACE:-$PARENT_DIR}" nodeVersion="$(cat "$dir/.node-version")" nodeDir="$cacheDir/node/$nodeVersion" nodeBin="$nodeDir/bin" -classifier="x64.tar.gz" +hostArch="$(command uname -m)" +case "${hostArch}" in + x86_64 | amd64) nodeArch="x64" ;; + aarch64) nodeArch="arm64" ;; + *) nodeArch="${hostArch}" ;; +esac +classifier="$nodeArch.tar.gz" UNAME=$(uname) OS="linux" From 0c44f5fd41be17ec048b33d8e47ebee64f1d35ce Mon Sep 17 00:00:00 2001 From: Kerry Gallagher Date: Tue, 16 Feb 2021 19:37:25 +0000 Subject: [PATCH 20/53] [Logs UI] Check for privileges with user management links (#91134) (#91490) * Wrap users management link with privileges check Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../log_analysis_setup/user_management_link.tsx | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/x-pack/plugins/infra/public/components/logging/log_analysis_setup/user_management_link.tsx b/x-pack/plugins/infra/public/components/logging/log_analysis_setup/user_management_link.tsx index 24179768604c4..3b0eb6fa89856 100644 --- a/x-pack/plugins/infra/public/components/logging/log_analysis_setup/user_management_link.tsx +++ b/x-pack/plugins/infra/public/components/logging/log_analysis_setup/user_management_link.tsx @@ -9,12 +9,23 @@ import { EuiButton, EuiButtonProps } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import React from 'react'; import { useLinkProps } from '../../../hooks/use_link_props'; +import { useKibanaContextForPlugin } from '../../../hooks/use_kibana'; export const UserManagementLink: React.FunctionComponent = (props) => { + const { + services: { + application: { capabilities }, + }, + } = useKibanaContextForPlugin(); + const canAccessUserManagement = capabilities?.management?.security?.users ?? false; + const linkProps = useLinkProps({ app: 'management', pathname: '/security/users', }); + + if (!canAccessUserManagement) return null; + return ( Date: Tue, 16 Feb 2021 15:17:31 -0500 Subject: [PATCH 21/53] [Time to Visualize] Combine Discard & Cancel (#91267) (#91533) * recombined discard and cancel button functionality # Conflicts: # test/functional/apps/dashboard/view_edit.ts --- .../application/listing/confirm_overlays.tsx | 54 +++++++++++++++ .../application/top_nav/dashboard_top_nav.tsx | 67 ++++++++++++------- .../application/top_nav/get_top_nav_config.ts | 19 ------ .../public/application/top_nav/top_nav_ids.ts | 1 - .../dashboard/public/dashboard_strings.ts | 12 ++++ test/accessibility/apps/dashboard.ts | 5 +- .../apps/dashboard/dashboard_unsaved_state.ts | 1 + test/functional/apps/dashboard/view_edit.ts | 36 +++++----- .../functional/page_objects/dashboard_page.ts | 18 ++++- 9 files changed, 143 insertions(+), 70 deletions(-) diff --git a/src/plugins/dashboard/public/application/listing/confirm_overlays.tsx b/src/plugins/dashboard/public/application/listing/confirm_overlays.tsx index 41b27b4fd6926..d302bb4216bc4 100644 --- a/src/plugins/dashboard/public/application/listing/confirm_overlays.tsx +++ b/src/plugins/dashboard/public/application/listing/confirm_overlays.tsx @@ -40,6 +40,60 @@ export const confirmDiscardUnsavedChanges = ( } }); +export type DiscardOrKeepSelection = 'cancel' | 'discard' | 'keep'; + +export const confirmDiscardOrKeepUnsavedChanges = ( + overlays: OverlayStart +): Promise => { + return new Promise((resolve) => { + const session = overlays.openModal( + toMountPoint( + <> + + {leaveConfirmStrings.getLeaveEditModeTitle()} + + + + {leaveConfirmStrings.getLeaveEditModeSubtitle()} + + + + session.close()} + > + {leaveConfirmStrings.getCancelButtonText()} + + { + session.close(); + resolve('keep'); + }} + > + {leaveConfirmStrings.getKeepChangesText()} + + { + session.close(); + resolve('discard'); + }} + > + {leaveConfirmStrings.getConfirmButtonText()} + + + + ), + { + 'data-test-subj': 'dashboardDiscardConfirmModal', + } + ); + }); +}; + export const confirmCreateWithUnsaved = ( overlays: OverlayStart, startBlankCallback: () => void, diff --git a/src/plugins/dashboard/public/application/top_nav/dashboard_top_nav.tsx b/src/plugins/dashboard/public/application/top_nav/dashboard_top_nav.tsx index 786afc81c400c..e68d371ebb270 100644 --- a/src/plugins/dashboard/public/application/top_nav/dashboard_top_nav.tsx +++ b/src/plugins/dashboard/public/application/top_nav/dashboard_top_nav.tsx @@ -43,7 +43,7 @@ import { showOptionsPopover } from './show_options_popover'; import { TopNavIds } from './top_nav_ids'; import { ShowShareModal } from './show_share_modal'; import { PanelToolbar } from './panel_toolbar'; -import { confirmDiscardUnsavedChanges } from '../listing/confirm_overlays'; +import { confirmDiscardOrKeepUnsavedChanges } from '../listing/confirm_overlays'; import { OverlayRef } from '../../../../../core/public'; import { getNewDashboardTitle } from '../../dashboard_strings'; import { DASHBOARD_PANELS_UNSAVED_ID } from '../lib/dashboard_panel_storage'; @@ -152,34 +152,53 @@ export function DashboardTopNav({ } }, [state.addPanelOverlay]); - const onDiscardChanges = useCallback(() => { - function revertChangesAndExitEditMode() { - dashboardStateManager.resetState(); - dashboardStateManager.clearUnsavedPanels(); - - // We need to do a hard reset of the timepicker. appState will not reload like - // it does on 'open' because it's been saved to the url and the getAppState.previouslyStored() check on - // reload will cause it not to sync. - if (dashboardStateManager.getIsTimeSavedWithDashboard()) { - dashboardStateManager.syncTimefilterWithDashboardTime(timefilter); - dashboardStateManager.syncTimefilterWithDashboardRefreshInterval(timefilter); - } - dashboardStateManager.switchViewMode(ViewMode.VIEW); - } - confirmDiscardUnsavedChanges(core.overlays, revertChangesAndExitEditMode); - }, [core.overlays, dashboardStateManager, timefilter]); - const onChangeViewMode = useCallback( (newMode: ViewMode) => { clearAddPanel(); - if (savedDashboard?.id && allowByValueEmbeddables) { - const { getFullEditPath, title, id } = savedDashboard; - chrome.recentlyAccessed.add(getFullEditPath(newMode === ViewMode.EDIT), title, id); + const isPageRefresh = newMode === dashboardStateManager.getViewMode(); + const isLeavingEditMode = !isPageRefresh && newMode === ViewMode.VIEW; + const willLoseChanges = isLeavingEditMode && dashboardStateManager.getIsDirty(timefilter); + + function switchViewMode() { + dashboardStateManager.switchViewMode(newMode); + dashboardStateManager.restorePanels(); + + if (savedDashboard?.id && allowByValueEmbeddables) { + const { getFullEditPath, title, id } = savedDashboard; + chrome.recentlyAccessed.add(getFullEditPath(newMode === ViewMode.EDIT), title, id); + } + } + + if (!willLoseChanges) { + switchViewMode(); + return; } - dashboardStateManager.switchViewMode(newMode); - dashboardStateManager.restorePanels(); + + function discardChanges() { + dashboardStateManager.resetState(); + dashboardStateManager.clearUnsavedPanels(); + + // We need to do a hard reset of the timepicker. appState will not reload like + // it does on 'open' because it's been saved to the url and the getAppState.previouslyStored() check on + // reload will cause it not to sync. + if (dashboardStateManager.getIsTimeSavedWithDashboard()) { + dashboardStateManager.syncTimefilterWithDashboardTime(timefilter); + dashboardStateManager.syncTimefilterWithDashboardRefreshInterval(timefilter); + } + dashboardStateManager.switchViewMode(ViewMode.VIEW); + } + confirmDiscardOrKeepUnsavedChanges(core.overlays).then((selection) => { + if (selection === 'discard') { + discardChanges(); + } + if (selection !== 'cancel') { + switchViewMode(); + } + }); }, [ + timefilter, + core.overlays, clearAddPanel, savedDashboard, dashboardStateManager, @@ -381,7 +400,6 @@ export function DashboardTopNav({ }, [TopNavIds.EXIT_EDIT_MODE]: () => onChangeViewMode(ViewMode.VIEW), [TopNavIds.ENTER_EDIT_MODE]: () => onChangeViewMode(ViewMode.EDIT), - [TopNavIds.DISCARD_CHANGES]: onDiscardChanges, [TopNavIds.SAVE]: runSave, [TopNavIds.QUICK_SAVE]: runQuickSave, [TopNavIds.CLONE]: runClone, @@ -417,7 +435,6 @@ export function DashboardTopNav({ }, [ dashboardCapabilities, dashboardStateManager, - onDiscardChanges, onChangeViewMode, savedDashboard, runClone, diff --git a/src/plugins/dashboard/public/application/top_nav/get_top_nav_config.ts b/src/plugins/dashboard/public/application/top_nav/get_top_nav_config.ts index abc128369017c..26eea1b5f718d 100644 --- a/src/plugins/dashboard/public/application/top_nav/get_top_nav_config.ts +++ b/src/plugins/dashboard/public/application/top_nav/get_top_nav_config.ts @@ -41,14 +41,12 @@ export function getTopNavConfig( getOptionsConfig(actions[TopNavIds.OPTIONS]), getShareConfig(actions[TopNavIds.SHARE]), getViewConfig(actions[TopNavIds.EXIT_EDIT_MODE]), - getDiscardConfig(actions[TopNavIds.DISCARD_CHANGES]), getSaveConfig(actions[TopNavIds.SAVE], options.isNewDashboard), ] : [ getOptionsConfig(actions[TopNavIds.OPTIONS]), getShareConfig(actions[TopNavIds.SHARE]), getViewConfig(actions[TopNavIds.EXIT_EDIT_MODE]), - getDiscardConfig(actions[TopNavIds.DISCARD_CHANGES]), getSaveConfig(actions[TopNavIds.SAVE]), getQuickSave(actions[TopNavIds.QUICK_SAVE]), ]; @@ -154,23 +152,6 @@ function getViewConfig(action: NavAction) { }; } -/** - * @returns {kbnTopNavConfig} - */ -function getDiscardConfig(action: NavAction) { - return { - id: 'discard', - label: i18n.translate('dashboard.topNave.discardlButtonAriaLabel', { - defaultMessage: 'discard', - }), - description: i18n.translate('dashboard.topNave.discardConfigDescription', { - defaultMessage: 'Discard unsaved changes', - }), - testId: 'dashboardDiscardChanges', - run: action, - }; -} - /** * @returns {kbnTopNavConfig} */ diff --git a/src/plugins/dashboard/public/application/top_nav/top_nav_ids.ts b/src/plugins/dashboard/public/application/top_nav/top_nav_ids.ts index 92a0db6bd0ba2..ee3d08e2330ae 100644 --- a/src/plugins/dashboard/public/application/top_nav/top_nav_ids.ts +++ b/src/plugins/dashboard/public/application/top_nav/top_nav_ids.ts @@ -13,7 +13,6 @@ export const TopNavIds = { SAVE: 'save', EXIT_EDIT_MODE: 'exitEditMode', ENTER_EDIT_MODE: 'enterEditMode', - DISCARD_CHANGES: 'discard', CLONE: 'clone', FULL_SCREEN: 'fullScreenMode', }; diff --git a/src/plugins/dashboard/public/dashboard_strings.ts b/src/plugins/dashboard/public/dashboard_strings.ts index 96bd32088ec38..8588fbc1bbdc7 100644 --- a/src/plugins/dashboard/public/dashboard_strings.ts +++ b/src/plugins/dashboard/public/dashboard_strings.ts @@ -253,6 +253,18 @@ export const leaveConfirmStrings = { i18n.translate('dashboard.appLeaveConfirmModal.unsavedChangesSubtitle', { defaultMessage: 'Leave Dashboard with unsaved work?', }), + getKeepChangesText: () => + i18n.translate('dashboard.appLeaveConfirmModal.keepUnsavedChangesButtonLabel', { + defaultMessage: 'Keep unsaved changes', + }), + getLeaveEditModeTitle: () => + i18n.translate('dashboard.changeViewModeConfirmModal.leaveEditMode', { + defaultMessage: 'Leave edit mode with unsaved work?', + }), + getLeaveEditModeSubtitle: () => + i18n.translate('dashboard.changeViewModeConfirmModal.discardChangesOptionalDescription', { + defaultMessage: `If you discard your changes, there's no getting them back.`, + }), getDiscardTitle: () => i18n.translate('dashboard.changeViewModeConfirmModal.discardChangesTitle', { defaultMessage: 'Discard changes to dashboard?', diff --git a/test/accessibility/apps/dashboard.ts b/test/accessibility/apps/dashboard.ts index 0171a462b1368..08d577b3df08c 100644 --- a/test/accessibility/apps/dashboard.ts +++ b/test/accessibility/apps/dashboard.ts @@ -110,12 +110,13 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); it('Exit out of edit mode', async () => { - await PageObjects.dashboard.clickDiscardChanges(); + await PageObjects.dashboard.clickDiscardChanges(false); await a11y.testAppSnapshot(); }); it('Discard changes', async () => { - await PageObjects.common.clickConfirmOnModal(); + await testSubjects.exists('dashboardDiscardConfirmDiscard'); + await testSubjects.click('dashboardDiscardConfirmDiscard'); await PageObjects.dashboard.getIsInViewMode(); await a11y.testAppSnapshot(); }); diff --git a/test/functional/apps/dashboard/dashboard_unsaved_state.ts b/test/functional/apps/dashboard/dashboard_unsaved_state.ts index 851d7ab7461ed..eaf0d2f2a97df 100644 --- a/test/functional/apps/dashboard/dashboard_unsaved_state.ts +++ b/test/functional/apps/dashboard/dashboard_unsaved_state.ts @@ -80,6 +80,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('retains unsaved panel count after returning to edit mode', async () => { await PageObjects.header.waitUntilLoadingHasFinished(); await PageObjects.dashboard.switchToEditMode(); + await PageObjects.header.waitUntilLoadingHasFinished(); const currentPanelCount = await PageObjects.dashboard.getPanelCount(); expect(currentPanelCount).to.eql(unsavedPanelCount); }); diff --git a/test/functional/apps/dashboard/view_edit.ts b/test/functional/apps/dashboard/view_edit.ts index 4a35b2fd1710a..6c7d60c9a15aa 100644 --- a/test/functional/apps/dashboard/view_edit.ts +++ b/test/functional/apps/dashboard/view_edit.ts @@ -15,6 +15,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const esArchiver = getService('esArchiver'); const kibanaServer = getService('kibanaServer'); const dashboardAddPanel = getService('dashboardAddPanel'); + const testSubjects = getService('testSubjects'); const PageObjects = getPageObjects(['dashboard', 'header', 'common', 'visualize', 'timePicker']); const dashboardName = 'dashboard with filter'; const filterBar = getService('filterBar'); @@ -74,9 +75,6 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { ); await PageObjects.dashboard.clickDiscardChanges(); - // confirm lose changes - await PageObjects.common.clickConfirmOnModal(); - const newTime = await PageObjects.timePicker.getTimeConfig(); expect(newTime.start).to.equal(originalTime.start); @@ -90,9 +88,6 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.dashboard.clickDiscardChanges(); - // confirm lose changes - await PageObjects.common.clickConfirmOnModal(); - const query = await queryBar.getQueryString(); expect(query).to.equal(originalQuery); }); @@ -113,9 +108,6 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.dashboard.clickDiscardChanges(); - // confirm lose changes - await PageObjects.common.clickConfirmOnModal(); - hasFilter = await filterBar.hasFilter('animal', 'dog'); expect(hasFilter).to.be(true); }); @@ -133,8 +125,13 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { redirectToOrigin: true, }); - await PageObjects.dashboard.clickDiscardChanges(); - await PageObjects.common.clickConfirmOnModal(); + await PageObjects.dashboard.clickDiscardChanges(false); + // for this sleep see https://github.com/elastic/kibana/issues/22299 + await PageObjects.common.sleep(500); + + // confirm lose changes + await testSubjects.exists('dashboardDiscardConfirmDiscard'); + await testSubjects.click('dashboardDiscardConfirmDiscard'); const panelCount = await PageObjects.dashboard.getPanelCount(); expect(panelCount).to.eql(originalPanelCount); @@ -146,9 +143,6 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await dashboardAddPanel.addVisualization('new viz panel'); await PageObjects.dashboard.clickDiscardChanges(); - // confirm lose changes - await PageObjects.common.clickConfirmOnModal(); - const panelCount = await PageObjects.dashboard.getPanelCount(); expect(panelCount).to.eql(originalPanelCount); }); @@ -167,9 +161,10 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { 'Sep 19, 2015 @ 06:31:44.000', 'Sep 19, 2015 @ 06:31:44.000' ); - await PageObjects.dashboard.clickDiscardChanges(); + await PageObjects.dashboard.clickDiscardChanges(false); - await PageObjects.common.clickCancelOnModal(); + await testSubjects.exists('dashboardDiscardConfirmCancel'); + await testSubjects.click('dashboardDiscardConfirmCancel'); await PageObjects.dashboard.saveDashboard(dashboardName, { storeTimeWithDashboard: true, }); @@ -196,9 +191,10 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { ); const newTime = await PageObjects.timePicker.getTimeConfig(); - await PageObjects.dashboard.clickDiscardChanges(); + await PageObjects.dashboard.clickDiscardChanges(false); - await PageObjects.common.clickCancelOnModal(); + await testSubjects.exists('dashboardDiscardConfirmCancel'); + await testSubjects.click('dashboardDiscardConfirmCancel'); await PageObjects.dashboard.saveDashboard(dashboardName, { storeTimeWithDashboard: true }); await PageObjects.dashboard.loadSavedDashboard(dashboardName); @@ -219,7 +215,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { 'Oct 19, 2014 @ 06:31:44.000', 'Dec 19, 2014 @ 06:31:44.000' ); - await PageObjects.dashboard.clickCancelOutOfEditMode(); + await PageObjects.dashboard.clickCancelOutOfEditMode(false); await PageObjects.common.expectConfirmModalOpenState(false); }); @@ -231,7 +227,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const originalQuery = await queryBar.getQueryString(); await queryBar.setQuery(`${originalQuery}extra stuff`); - await PageObjects.dashboard.clickCancelOutOfEditMode(); + await PageObjects.dashboard.clickCancelOutOfEditMode(false); await PageObjects.common.expectConfirmModalOpenState(false); diff --git a/test/functional/page_objects/dashboard_page.ts b/test/functional/page_objects/dashboard_page.ts index 4291d67a6bc08..9c571f0f0ef86 100644 --- a/test/functional/page_objects/dashboard_page.ts +++ b/test/functional/page_objects/dashboard_page.ts @@ -246,14 +246,26 @@ export function DashboardPageProvider({ getService, getPageObjects }: FtrProvide return await testSubjects.exists('dashboardEditMode'); } - public async clickCancelOutOfEditMode() { + public async clickCancelOutOfEditMode(accept = true) { log.debug('clickCancelOutOfEditMode'); await testSubjects.click('dashboardViewOnlyMode'); + if (accept) { + const confirmation = await testSubjects.exists('dashboardDiscardConfirmKeep'); + if (confirmation) { + await testSubjects.click('dashboardDiscardConfirmKeep'); + } + } } - public async clickDiscardChanges() { + public async clickDiscardChanges(accept = true) { log.debug('clickDiscardChanges'); - await testSubjects.click('dashboardDiscardChanges'); + await testSubjects.click('dashboardViewOnlyMode'); + if (accept) { + const confirmation = await testSubjects.exists('dashboardDiscardConfirmDiscard'); + if (confirmation) { + await testSubjects.click('dashboardDiscardConfirmDiscard'); + } + } } public async clickQuickSave() { From 944476b8329dbb42bdc21dcd027f93924f920209 Mon Sep 17 00:00:00 2001 From: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Date: Tue, 16 Feb 2021 15:17:51 -0500 Subject: [PATCH 22/53] [App Search] Set up Curations routes & complete 'Edit Query' action in Analytics tables (#91052) (#91528) * Set up Curations routes * Update EngineRouter/Nav with Curations * Set up Curations find_or_create API * [bug] Fix view action not working correctly for "" query * Add Edit query action - to call find_or_create curation API & navigate to curation page + fix copy string, only just noticed this :doh: * Add/update unit tests for action column - Refactor out into a single shared test helper file that both AnalyticsTable and RecentQueriesTable simply calls & runs (instead of copying and pasting the same tests twice into 2 diff files) - note: test file can't be `.test.tsx` or Jest tries to automatically run it, which we don't want Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Constance --- .../analytics_tables/analytics_table.test.tsx | 19 ++--- .../recent_queries_table.test.tsx | 19 ++--- .../analytics_tables/shared_columns.tsx | 27 ++++-- .../analytics_tables/shared_columns_tests.tsx | 82 +++++++++++++++++++ .../curations/curations_router.test.tsx | 22 +++++ .../components/curations/curations_router.tsx | 55 +++++++++++++ .../app_search/components/curations/index.ts | 1 + .../components/engine/engine_nav.tsx | 4 +- .../components/engine/engine_router.test.tsx | 10 ++- .../components/engine/engine_router.tsx | 12 ++- .../public/applications/app_search/routes.ts | 7 +- .../routes/app_search/curations.test.ts | 47 +++++++++++ .../server/routes/app_search/curations.ts | 32 ++++++++ .../server/routes/app_search/index.ts | 2 + 14 files changed, 297 insertions(+), 42 deletions(-) create mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/components/analytics_tables/shared_columns_tests.tsx create mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curations_router.test.tsx create mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curations_router.tsx create mode 100644 x-pack/plugins/enterprise_search/server/routes/app_search/curations.test.ts create mode 100644 x-pack/plugins/enterprise_search/server/routes/app_search/curations.ts diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/components/analytics_tables/analytics_table.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/components/analytics_tables/analytics_table.test.tsx index 2eac65fc21091..593f70cda404c 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/components/analytics_tables/analytics_table.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/components/analytics_tables/analytics_table.test.tsx @@ -5,18 +5,18 @@ * 2.0. */ -import { mountWithIntl, mockKibanaValues } from '../../../../../__mocks__'; +import { mountWithIntl } from '../../../../../__mocks__'; import '../../../../__mocks__/engine_logic.mock'; import React from 'react'; import { EuiBasicTable, EuiBadge, EuiEmptyPrompt } from '@elastic/eui'; +import { runActionColumnTests } from './shared_columns_tests'; + import { AnalyticsTable } from './'; describe('AnalyticsTable', () => { - const { navigateToUrl } = mockKibanaValues; - const items = [ { key: 'some search', @@ -69,18 +69,9 @@ describe('AnalyticsTable', () => { expect(tableContent).toContain('0'); }); - it('renders an action column', () => { + describe('renders an action column', () => { const wrapper = mountWithIntl(); - const viewQuery = wrapper.find('[data-test-subj="AnalyticsTableViewQueryButton"]').first(); - const editQuery = wrapper.find('[data-test-subj="AnalyticsTableEditQueryButton"]').first(); - - viewQuery.simulate('click'); - expect(navigateToUrl).toHaveBeenCalledWith( - '/engines/some-engine/analytics/query_detail/some%20search' - ); - - editQuery.simulate('click'); - // TODO + runActionColumnTests(wrapper); }); it('renders an empty prompt if no items are passed', () => { diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/components/analytics_tables/recent_queries_table.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/components/analytics_tables/recent_queries_table.test.tsx index a5a582d3747bc..f90d86908d470 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/components/analytics_tables/recent_queries_table.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/components/analytics_tables/recent_queries_table.test.tsx @@ -5,18 +5,18 @@ * 2.0. */ -import { mountWithIntl, mockKibanaValues } from '../../../../../__mocks__'; +import { mountWithIntl } from '../../../../../__mocks__'; import '../../../../__mocks__/engine_logic.mock'; import React from 'react'; import { EuiBasicTable, EuiBadge, EuiEmptyPrompt } from '@elastic/eui'; +import { runActionColumnTests } from './shared_columns_tests'; + import { RecentQueriesTable } from './'; describe('RecentQueriesTable', () => { - const { navigateToUrl } = mockKibanaValues; - const items = [ { query_string: 'some search', @@ -63,18 +63,9 @@ describe('RecentQueriesTable', () => { expect(tableContent).toContain('3'); }); - it('renders an action column', () => { + describe('renders an action column', () => { const wrapper = mountWithIntl(); - const viewQuery = wrapper.find('[data-test-subj="AnalyticsTableViewQueryButton"]').first(); - const editQuery = wrapper.find('[data-test-subj="AnalyticsTableEditQueryButton"]').first(); - - viewQuery.simulate('click'); - expect(navigateToUrl).toHaveBeenCalledWith( - '/engines/some-engine/analytics/query_detail/some%20search' - ); - - editQuery.simulate('click'); - // TODO + runActionColumnTests(wrapper); }); it('renders an empty prompt if no items are passed', () => { diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/components/analytics_tables/shared_columns.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/components/analytics_tables/shared_columns.tsx index 9d8365a2f7af1..6c3d2539035ae 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/components/analytics_tables/shared_columns.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/components/analytics_tables/shared_columns.tsx @@ -9,10 +9,12 @@ import React from 'react'; import { i18n } from '@kbn/i18n'; +import { flashAPIErrors } from '../../../../../shared/flash_messages'; +import { HttpLogic } from '../../../../../shared/http'; import { KibanaLogic } from '../../../../../shared/kibana'; import { EuiLinkTo } from '../../../../../shared/react_router_helpers'; -import { ENGINE_ANALYTICS_QUERY_DETAIL_PATH } from '../../../../routes'; -import { generateEnginePath } from '../../../engine'; +import { ENGINE_ANALYTICS_QUERY_DETAIL_PATH, ENGINE_CURATION_PATH } from '../../../../routes'; +import { generateEnginePath, EngineLogic } from '../../../engine'; import { Query, RecentQuery } from '../../types'; import { InlineTagsList } from './inline_tags_list'; @@ -63,7 +65,7 @@ export const ACTIONS_COLUMN = { onClick: (item: Query | RecentQuery) => { const { navigateToUrl } = KibanaLogic.values; - const query = (item as Query).key || (item as RecentQuery).query_string; + const query = (item as Query).key || (item as RecentQuery).query_string || '""'; navigateToUrl(generateEnginePath(ENGINE_ANALYTICS_QUERY_DETAIL_PATH, { query })); }, 'data-test-subj': 'AnalyticsTableViewQueryButton', @@ -74,12 +76,25 @@ export const ACTIONS_COLUMN = { }), description: i18n.translate( 'xpack.enterpriseSearch.appSearch.engine.analytics.table.editTooltip', - { defaultMessage: 'Edit query analytics' } + { defaultMessage: 'Edit query' } ), type: 'icon', icon: 'pencil', - onClick: () => { - // TODO: CurationsLogic + onClick: async (item: Query | RecentQuery) => { + const { http } = HttpLogic.values; + const { navigateToUrl } = KibanaLogic.values; + const { engineName } = EngineLogic.values; + + try { + const query = (item as Query).key || (item as RecentQuery).query_string || '""'; + const response = await http.get( + `/api/app_search/engines/${engineName}/curations/find_or_create`, + { query: { query } } + ); + navigateToUrl(generateEnginePath(ENGINE_CURATION_PATH, { curationId: response.id })); + } catch (e) { + flashAPIErrors(e); + } }, 'data-test-subj': 'AnalyticsTableEditQueryButton', }, diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/components/analytics_tables/shared_columns_tests.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/components/analytics_tables/shared_columns_tests.tsx new file mode 100644 index 0000000000000..cb78a6585e43c --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/components/analytics_tables/shared_columns_tests.tsx @@ -0,0 +1,82 @@ +/* + * 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 { + mockHttpValues, + mockKibanaValues, + mockFlashMessageHelpers, +} from '../../../../../__mocks__'; +import '../../../../__mocks__/engine_logic.mock'; + +import { ReactWrapper } from 'enzyme'; + +import { nextTick } from '@kbn/test/jest'; + +export const runActionColumnTests = (wrapper: ReactWrapper) => { + const { http } = mockHttpValues; + const { navigateToUrl } = mockKibanaValues; + const { flashAPIErrors } = mockFlashMessageHelpers; + + beforeEach(() => { + jest.clearAllMocks(); + }); + + describe('view action', () => { + it('navigates to the query detail view', () => { + wrapper.find('[data-test-subj="AnalyticsTableViewQueryButton"]').first().simulate('click'); + + expect(navigateToUrl).toHaveBeenCalledWith( + '/engines/some-engine/analytics/query_detail/some%20search' + ); + }); + + it('falls back to "" for the empty query', () => { + wrapper.find('[data-test-subj="AnalyticsTableViewQueryButton"]').last().simulate('click'); + expect(navigateToUrl).toHaveBeenCalledWith( + '/engines/some-engine/analytics/query_detail/%22%22' + ); + }); + }); + + describe('edit action', () => { + it('calls the find_or_create curation API, then navigates the user to the curation', async () => { + http.get.mockReturnValue(Promise.resolve({ id: 'cur-123456789' })); + wrapper.find('[data-test-subj="AnalyticsTableEditQueryButton"]').first().simulate('click'); + await nextTick(); + + expect(http.get).toHaveBeenCalledWith( + '/api/app_search/engines/some-engine/curations/find_or_create', + { + query: { query: 'some search' }, + } + ); + expect(navigateToUrl).toHaveBeenCalledWith('/engines/some-engine/curations/cur-123456789'); + }); + + it('falls back to "" for the empty query', async () => { + http.get.mockReturnValue(Promise.resolve({ id: 'cur-987654321' })); + wrapper.find('[data-test-subj="AnalyticsTableEditQueryButton"]').last().simulate('click'); + await nextTick(); + + expect(http.get).toHaveBeenCalledWith( + '/api/app_search/engines/some-engine/curations/find_or_create', + { + query: { query: '""' }, + } + ); + expect(navigateToUrl).toHaveBeenCalledWith('/engines/some-engine/curations/cur-987654321'); + }); + + it('handles API errors', async () => { + http.get.mockReturnValue(Promise.reject()); + wrapper.find('[data-test-subj="AnalyticsTableEditQueryButton"]').first().simulate('click'); + await nextTick(); + + expect(flashAPIErrors).toHaveBeenCalled(); + }); + }); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curations_router.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curations_router.test.tsx new file mode 100644 index 0000000000000..047d00ad98a0d --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curations_router.test.tsx @@ -0,0 +1,22 @@ +/* + * 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 React from 'react'; +import { Route, Switch } from 'react-router-dom'; + +import { shallow } from 'enzyme'; + +import { CurationsRouter } from './'; + +describe('CurationsRouter', () => { + it('renders', () => { + const wrapper = shallow(); + + expect(wrapper.find(Switch)).toHaveLength(1); + expect(wrapper.find(Route)).toHaveLength(5); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curations_router.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curations_router.tsx new file mode 100644 index 0000000000000..a7f99044cc1c3 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curations_router.tsx @@ -0,0 +1,55 @@ +/* + * 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 React from 'react'; +import { Route, Switch } from 'react-router-dom'; + +import { APP_SEARCH_PLUGIN } from '../../../../../common/constants'; +import { SetAppSearchChrome as SetPageChrome } from '../../../shared/kibana_chrome'; +import { BreadcrumbTrail } from '../../../shared/kibana_chrome/generate_breadcrumbs'; +import { NotFound } from '../../../shared/not_found'; +import { + ENGINE_CURATIONS_PATH, + ENGINE_CURATIONS_NEW_PATH, + ENGINE_CURATION_PATH, + ENGINE_CURATION_ADD_RESULT_PATH, +} from '../../routes'; + +import { CURATIONS_TITLE } from './constants'; + +interface Props { + engineBreadcrumb: BreadcrumbTrail; +} +export const CurationsRouter: React.FC = ({ engineBreadcrumb }) => { + const CURATIONS_BREADCRUMB = [...engineBreadcrumb, CURATIONS_TITLE]; + + return ( + + + + TODO: Curations overview + + + + TODO: Curation creation view + + + + TODO: Curation view (+ show a NotFound view if ID is invalid) + + + + TODO: Curation Add Result view + + + + + + ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/index.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/index.ts index f1eb95a0c878c..075bc1368b300 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/index.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/index.ts @@ -6,3 +6,4 @@ */ export { CURATIONS_TITLE } from './constants'; +export { CurationsRouter } from './curations_router'; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_nav.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_nav.tsx index 447e4d678bcdb..a4ce724fdb097 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_nav.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_nav.tsx @@ -220,8 +220,8 @@ export const EngineNav: React.FC = () => { )} {canManageEngineCurations && ( {CURATIONS_TITLE} diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_router.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_router.test.tsx index 3740882dee3db..e6b829a43dcc1 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_router.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_router.test.tsx @@ -17,6 +17,7 @@ import { shallow } from 'enzyme'; import { Loading } from '../../../shared/loading'; import { AnalyticsRouter } from '../analytics'; +import { CurationsRouter } from '../curations'; import { EngineOverview } from '../engine_overview'; import { RelevanceTuning } from '../relevance_tuning'; @@ -97,7 +98,14 @@ describe('EngineRouter', () => { expect(wrapper.find(AnalyticsRouter)).toHaveLength(1); }); - it('renders an relevance tuning view', () => { + it('renders a curations view', () => { + setMockValues({ ...values, myRole: { canManageEngineCurations: true } }); + const wrapper = shallow(); + + expect(wrapper.find(CurationsRouter)).toHaveLength(1); + }); + + it('renders a relevance tuning view', () => { setMockValues({ ...values, myRole: { canManageEngineRelevanceTuning: true } }); const wrapper = shallow(); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_router.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_router.tsx index 2f1c3bc57d331..305bdf74ae501 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_router.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_router.tsx @@ -28,12 +28,13 @@ import { // META_ENGINE_SOURCE_ENGINES_PATH, ENGINE_RELEVANCE_TUNING_PATH, // ENGINE_SYNONYMS_PATH, - // ENGINE_CURATIONS_PATH, + ENGINE_CURATIONS_PATH, // ENGINE_RESULT_SETTINGS_PATH, // ENGINE_SEARCH_UI_PATH, // ENGINE_API_LOGS_PATH, } from '../../routes'; import { AnalyticsRouter } from '../analytics'; +import { CurationsRouter } from '../curations'; import { DocumentDetail, Documents } from '../documents'; import { OVERVIEW_TITLE } from '../engine_overview'; import { EngineOverview } from '../engine_overview'; @@ -46,13 +47,13 @@ export const EngineRouter: React.FC = () => { const { myRole: { canViewEngineAnalytics, - canManageEngineRelevanceTuning, // canViewEngineDocuments, // canViewEngineSchema, // canViewEngineCrawler, // canViewMetaEngineSourceEngines, + canManageEngineRelevanceTuning, // canManageEngineSynonyms, - // canManageEngineCurations, + canManageEngineCurations, // canManageEngineResultSettings, // canManageEngineSearchUi, // canViewEngineApiLogs, @@ -97,6 +98,11 @@ export const EngineRouter: React.FC = () => { + {canManageEngineCurations && ( + + + + )} {canManageEngineRelevanceTuning && ( diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/routes.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/routes.ts index 962efbb7ece3a..191b583afbe4d 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/routes.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/routes.ts @@ -44,9 +44,12 @@ export const META_ENGINE_SOURCE_ENGINES_PATH = `${ENGINE_PATH}/engines`; export const ENGINE_RELEVANCE_TUNING_PATH = `${ENGINE_PATH}/relevance_tuning`; export const ENGINE_SYNONYMS_PATH = `${ENGINE_PATH}/synonyms`; -export const ENGINE_CURATIONS_PATH = `${ENGINE_PATH}/curations`; -// TODO: Curations sub-pages export const ENGINE_RESULT_SETTINGS_PATH = `${ENGINE_PATH}/result-settings`; +export const ENGINE_CURATIONS_PATH = `${ENGINE_PATH}/curations`; +export const ENGINE_CURATIONS_NEW_PATH = `${ENGINE_CURATIONS_PATH}/new`; +export const ENGINE_CURATION_PATH = `${ENGINE_CURATIONS_PATH}/:curationId`; +export const ENGINE_CURATION_ADD_RESULT_PATH = `${ENGINE_CURATIONS_PATH}/:curationId/add_result`; + export const ENGINE_SEARCH_UI_PATH = `${ENGINE_PATH}/reference_application/new`; export const ENGINE_API_LOGS_PATH = `${ENGINE_PATH}/api-logs`; diff --git a/x-pack/plugins/enterprise_search/server/routes/app_search/curations.test.ts b/x-pack/plugins/enterprise_search/server/routes/app_search/curations.test.ts new file mode 100644 index 0000000000000..5b5d132591f4e --- /dev/null +++ b/x-pack/plugins/enterprise_search/server/routes/app_search/curations.test.ts @@ -0,0 +1,47 @@ +/* + * 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 { MockRouter, mockRequestHandler, mockDependencies } from '../../__mocks__'; + +import { registerCurationsRoutes } from './curations'; + +describe('curations routes', () => { + describe('GET /api/app_search/engines/{engineName}/curations/find_or_create', () => { + let mockRouter: MockRouter; + + beforeEach(() => { + jest.clearAllMocks(); + mockRouter = new MockRouter({ + method: 'get', + path: '/api/app_search/engines/{engineName}/curations/find_or_create', + }); + + registerCurationsRoutes({ + ...mockDependencies, + router: mockRouter.router, + }); + }); + + it('creates a request handler', () => { + expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({ + path: '/as/engines/:engineName/curations/find_or_create', + }); + }); + + describe('validates', () => { + it('required query param', () => { + const request = { query: { query: 'some query' } }; + mockRouter.shouldValidate(request); + }); + + it('missing query', () => { + const request = { query: {} }; + mockRouter.shouldThrow(request); + }); + }); + }); +}); diff --git a/x-pack/plugins/enterprise_search/server/routes/app_search/curations.ts b/x-pack/plugins/enterprise_search/server/routes/app_search/curations.ts new file mode 100644 index 0000000000000..a4addb3ad0d3a --- /dev/null +++ b/x-pack/plugins/enterprise_search/server/routes/app_search/curations.ts @@ -0,0 +1,32 @@ +/* + * 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 { RouteDependencies } from '../../plugin'; + +export function registerCurationsRoutes({ + router, + enterpriseSearchRequestHandler, +}: RouteDependencies) { + router.get( + { + path: '/api/app_search/engines/{engineName}/curations/find_or_create', + validate: { + params: schema.object({ + engineName: schema.string(), + }), + query: schema.object({ + query: schema.string(), + }), + }, + }, + enterpriseSearchRequestHandler.createRequest({ + path: '/as/engines/:engineName/curations/find_or_create', + }) + ); +} diff --git a/x-pack/plugins/enterprise_search/server/routes/app_search/index.ts b/x-pack/plugins/enterprise_search/server/routes/app_search/index.ts index 92fdcb689db1d..90b86138a4a6d 100644 --- a/x-pack/plugins/enterprise_search/server/routes/app_search/index.ts +++ b/x-pack/plugins/enterprise_search/server/routes/app_search/index.ts @@ -9,6 +9,7 @@ import { RouteDependencies } from '../../plugin'; import { registerAnalyticsRoutes } from './analytics'; import { registerCredentialsRoutes } from './credentials'; +import { registerCurationsRoutes } from './curations'; import { registerDocumentsRoutes, registerDocumentRoutes } from './documents'; import { registerEnginesRoutes } from './engines'; import { registerSearchSettingsRoutes } from './search_settings'; @@ -21,5 +22,6 @@ export const registerAppSearchRoutes = (dependencies: RouteDependencies) => { registerAnalyticsRoutes(dependencies); registerDocumentsRoutes(dependencies); registerDocumentRoutes(dependencies); + registerCurationsRoutes(dependencies); registerSearchSettingsRoutes(dependencies); }; From 2a6442cded88811d76d102fa4f7e4f115ce0e1f3 Mon Sep 17 00:00:00 2001 From: Sonja Krause-Harder Date: Tue, 16 Feb 2021 21:26:53 +0100 Subject: [PATCH 23/53] [Fleet] Escape YAML string values if necessary (#91418) (#91500) * Use js-yaml.safeDump() to escape string values. * Add unit test. * Explicitly check for YAML special characters. * Remove unnecessary imports. * Use RegExp.prototype.test() for speed. Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../server/services/epm/agent/agent.test.ts | 75 +++++++++++++++++++ .../fleet/server/services/epm/agent/agent.ts | 26 +++++-- 2 files changed, 93 insertions(+), 8 deletions(-) diff --git a/x-pack/plugins/fleet/server/services/epm/agent/agent.test.ts b/x-pack/plugins/fleet/server/services/epm/agent/agent.test.ts index 7ab904b2f15e1..4509deee0d00f 100644 --- a/x-pack/plugins/fleet/server/services/epm/agent/agent.test.ts +++ b/x-pack/plugins/fleet/server/services/epm/agent/agent.test.ts @@ -179,4 +179,79 @@ input: logs input: 'logs', }); }); + + it('should escape string values when necessary', () => { + const stringTemplate = ` +my-package: + opencurly: {{opencurly}} + closecurly: {{closecurly}} + opensquare: {{opensquare}} + closesquare: {{closesquare}} + ampersand: {{ampersand}} + asterisk: {{asterisk}} + question: {{question}} + pipe: {{pipe}} + hyphen: {{hyphen}} + openangle: {{openangle}} + closeangle: {{closeangle}} + equals: {{equals}} + exclamation: {{exclamation}} + percent: {{percent}} + at: {{at}} + colon: {{colon}} + numeric: {{numeric}} + mixed: {{mixed}}`; + + // List of special chars that may lead to YAML parsing errors when not quoted. + // See YAML specification section 5.3 Indicator characters + // https://yaml.org/spec/1.2/spec.html#id2772075 + // {,},[,],&,*,?,|,-,<,>,=,!,%,@,: + const vars = { + opencurly: { value: '{', type: 'string' }, + closecurly: { value: '}', type: 'string' }, + opensquare: { value: '[', type: 'string' }, + closesquare: { value: ']', type: 'string' }, + comma: { value: ',', type: 'string' }, + ampersand: { value: '&', type: 'string' }, + asterisk: { value: '*', type: 'string' }, + question: { value: '?', type: 'string' }, + pipe: { value: '|', type: 'string' }, + hyphen: { value: '-', type: 'string' }, + openangle: { value: '<', type: 'string' }, + closeangle: { value: '>', type: 'string' }, + equals: { value: '=', type: 'string' }, + exclamation: { value: '!', type: 'string' }, + percent: { value: '%', type: 'string' }, + at: { value: '@', type: 'string' }, + colon: { value: ':', type: 'string' }, + numeric: { value: '100', type: 'string' }, + mixed: { value: '1s', type: 'string' }, + }; + + const targetOutput = { + 'my-package': { + opencurly: '{', + closecurly: '}', + opensquare: '[', + closesquare: ']', + ampersand: '&', + asterisk: '*', + question: '?', + pipe: '|', + hyphen: '-', + openangle: '<', + closeangle: '>', + equals: '=', + exclamation: '!', + percent: '%', + at: '@', + colon: ':', + numeric: '100', + mixed: '1s', + }, + }; + + const output = compileTemplate(vars, stringTemplate); + expect(output).toEqual(targetOutput); + }); }); diff --git a/x-pack/plugins/fleet/server/services/epm/agent/agent.ts b/x-pack/plugins/fleet/server/services/epm/agent/agent.ts index 4f39da5b0b70d..a71776af245f7 100644 --- a/x-pack/plugins/fleet/server/services/epm/agent/agent.ts +++ b/x-pack/plugins/fleet/server/services/epm/agent/agent.ts @@ -13,7 +13,6 @@ const handlebars = Handlebars.create(); export function compileTemplate(variables: PackagePolicyConfigRecord, templateStr: string) { const { vars, yamlValues } = buildTemplateVariables(variables, templateStr); - const template = handlebars.compile(templateStr, { noEscape: true }); let compiledTemplate = template(vars); compiledTemplate = replaceRootLevelYamlVariables(yamlValues, compiledTemplate); @@ -58,8 +57,17 @@ function replaceVariablesInYaml(yamlVariables: { [k: string]: any }, yaml: any) return yaml; } -const maybeEscapeNumericString = (value: string) => { - return value.length && !isNaN(+value) ? `"${value}"` : value; +const maybeEscapeString = (value: string) => { + // List of special chars that may lead to YAML parsing errors when not quoted. + // See YAML specification section 5.3 Indicator characters + // https://yaml.org/spec/1.2/spec.html#id2772075 + const yamlSpecialCharsRegex = /[{}\[\],&*?|\-<>=!%@:]/; + + // In addition, numeric strings need to be quoted to stay strings. + if ((value.length && !isNaN(+value)) || yamlSpecialCharsRegex.test(value)) { + return `"${value}"`; + } + return value; }; function buildTemplateVariables(variables: PackagePolicyConfigRecord, templateStr: string) { @@ -88,13 +96,15 @@ function buildTemplateVariables(variables: PackagePolicyConfigRecord, templateSt const yamlKeyPlaceholder = `##${key}##`; varPart[lastKeyPart] = `"${yamlKeyPlaceholder}"`; yamlValues[yamlKeyPlaceholder] = recordEntry.value ? safeLoad(recordEntry.value) : null; - } else if (recordEntry.type && recordEntry.type === 'text' && recordEntry.value?.length) { + } else if ( + recordEntry.type && + (recordEntry.type === 'text' || recordEntry.type === 'string') && + recordEntry.value?.length + ) { if (Array.isArray(recordEntry.value)) { - varPart[lastKeyPart] = recordEntry.value.map((value: string) => - maybeEscapeNumericString(value) - ); + varPart[lastKeyPart] = recordEntry.value.map((value: string) => maybeEscapeString(value)); } else { - varPart[lastKeyPart] = maybeEscapeNumericString(recordEntry.value); + varPart[lastKeyPart] = maybeEscapeString(recordEntry.value); } } else { varPart[lastKeyPart] = recordEntry.value; From d41df11804ed8401f226cf886aa429bbb59c01df Mon Sep 17 00:00:00 2001 From: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Date: Tue, 16 Feb 2021 15:38:07 -0500 Subject: [PATCH 24/53] Further optimize check privileges response validation (#90631) (#91530) Co-authored-by: Larry Gregory --- .../validate_es_response.test.ts.snap | 6 +-- .../authorization/check_privileges.test.ts | 12 ++--- .../authorization/validate_es_response.ts | 45 ++++++++++++------- 3 files changed, 38 insertions(+), 25 deletions(-) diff --git a/x-pack/plugins/security/server/authorization/__snapshots__/validate_es_response.test.ts.snap b/x-pack/plugins/security/server/authorization/__snapshots__/validate_es_response.test.ts.snap index 76d284a21984e..04190fbf5eacd 100644 --- a/x-pack/plugins/security/server/authorization/__snapshots__/validate_es_response.test.ts.snap +++ b/x-pack/plugins/security/server/authorization/__snapshots__/validate_es_response.test.ts.snap @@ -1,12 +1,12 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`validateEsPrivilegeResponse fails validation when an action is malformed in the response 1`] = `"Invalid response received from Elasticsearch has_privilege endpoint. Error: [application.foo-application]: [action3]: expected value of type [boolean] but got [string]"`; +exports[`validateEsPrivilegeResponse fails validation when an action is malformed in the response 1`] = `"Invalid response received from Elasticsearch has_privilege endpoint. Error: [application.foo-application]: expected value of type [boolean] but got [string]"`; -exports[`validateEsPrivilegeResponse fails validation when an action is missing in the response 1`] = `"Invalid response received from Elasticsearch has_privilege endpoint. Error: [application.foo-application]: [action2]: expected value of type [boolean] but got [undefined]"`; +exports[`validateEsPrivilegeResponse fails validation when an action is missing in the response 1`] = `"Invalid response received from Elasticsearch has_privilege endpoint. Error: [application.foo-application]: Payload did not match expected actions"`; exports[`validateEsPrivilegeResponse fails validation when an expected resource property is missing from the response 1`] = `"Invalid response received from Elasticsearch has_privilege endpoint. Error: [application.foo-application]: Payload did not match expected resources"`; -exports[`validateEsPrivilegeResponse fails validation when an extra action is present in the response 1`] = `"Invalid response received from Elasticsearch has_privilege endpoint. Error: [application.foo-application]: [action4]: definition for this key is missing"`; +exports[`validateEsPrivilegeResponse fails validation when an extra action is present in the response 1`] = `"Invalid response received from Elasticsearch has_privilege endpoint. Error: [application.foo-application]: Payload did not match expected actions"`; exports[`validateEsPrivilegeResponse fails validation when an extra application is present in the response 1`] = `"Invalid response received from Elasticsearch has_privilege endpoint. Error: [application.otherApplication]: definition for this key is missing"`; diff --git a/x-pack/plugins/security/server/authorization/check_privileges.test.ts b/x-pack/plugins/security/server/authorization/check_privileges.test.ts index 93f5efed58fb8..5bca46f22a512 100644 --- a/x-pack/plugins/security/server/authorization/check_privileges.test.ts +++ b/x-pack/plugins/security/server/authorization/check_privileges.test.ts @@ -316,7 +316,7 @@ describe('#atSpace', () => { }, }); expect(result).toMatchInlineSnapshot( - `[Error: Invalid response received from Elasticsearch has_privilege endpoint. Error: [application.kibana-our_application]: [saved_object:bar-type/get]: definition for this key is missing]` + `[Error: Invalid response received from Elasticsearch has_privilege endpoint. Error: [application.kibana-our_application]: Payload did not match expected actions]` ); }); @@ -338,7 +338,7 @@ describe('#atSpace', () => { }, }); expect(result).toMatchInlineSnapshot( - `[Error: Invalid response received from Elasticsearch has_privilege endpoint. Error: [application.kibana-our_application]: [saved_object:foo-type/get]: expected value of type [boolean] but got [undefined]]` + `[Error: Invalid response received from Elasticsearch has_privilege endpoint. Error: [application.kibana-our_application]: Payload did not match expected actions]` ); }); }); @@ -1092,7 +1092,7 @@ describe('#atSpaces', () => { }, }); expect(result).toMatchInlineSnapshot( - `[Error: Invalid response received from Elasticsearch has_privilege endpoint. Error: [application.kibana-our_application]: [mock-action:version]: expected value of type [boolean] but got [undefined]]` + `[Error: Invalid response received from Elasticsearch has_privilege endpoint. Error: [application.kibana-our_application]: Payload did not match expected actions]` ); }); @@ -2266,7 +2266,7 @@ describe('#globally', () => { }, }); expect(result).toMatchInlineSnapshot( - `[Error: Invalid response received from Elasticsearch has_privilege endpoint. Error: [application.kibana-our_application]: [mock-action:version]: expected value of type [boolean] but got [undefined]]` + `[Error: Invalid response received from Elasticsearch has_privilege endpoint. Error: [application.kibana-our_application]: Payload did not match expected actions]` ); }); @@ -2384,7 +2384,7 @@ describe('#globally', () => { }, }); expect(result).toMatchInlineSnapshot( - `[Error: Invalid response received from Elasticsearch has_privilege endpoint. Error: [application.kibana-our_application]: [saved_object:bar-type/get]: definition for this key is missing]` + `[Error: Invalid response received from Elasticsearch has_privilege endpoint. Error: [application.kibana-our_application]: Payload did not match expected actions]` ); }); @@ -2405,7 +2405,7 @@ describe('#globally', () => { }, }); expect(result).toMatchInlineSnapshot( - `[Error: Invalid response received from Elasticsearch has_privilege endpoint. Error: [application.kibana-our_application]: [saved_object:foo-type/get]: expected value of type [boolean] but got [undefined]]` + `[Error: Invalid response received from Elasticsearch has_privilege endpoint. Error: [application.kibana-our_application]: Payload did not match expected actions]` ); }); }); diff --git a/x-pack/plugins/security/server/authorization/validate_es_response.ts b/x-pack/plugins/security/server/authorization/validate_es_response.ts index 19afaaf035c15..270ff26716e3f 100644 --- a/x-pack/plugins/security/server/authorization/validate_es_response.ts +++ b/x-pack/plugins/security/server/authorization/validate_es_response.ts @@ -8,6 +8,11 @@ import { schema } from '@kbn/config-schema'; import { HasPrivilegesResponse } from './types'; +/** + * Validates an Elasticsearch "Has privileges" response against the expected application, actions, and resources. + * + * Note: the `actions` and `resources` parameters must be unique string arrays; any duplicates will cause validation to fail. + */ export function validateEsPrivilegeResponse( response: HasPrivilegesResponse, application: string, @@ -24,21 +29,29 @@ export function validateEsPrivilegeResponse( return response; } -function buildActionsValidationSchema(actions: string[]) { - return schema.object({ - ...actions.reduce>((acc, action) => { - return { - ...acc, - [action]: schema.boolean(), - }; - }, {}), - }); -} - function buildValidationSchema(application: string, actions: string[], resources: string[]) { - const actionValidationSchema = buildActionsValidationSchema(actions); + const actionValidationSchema = schema.boolean(); + const actionsValidationSchema = schema.object( + {}, + { + unknowns: 'allow', + validate: (value) => { + const actualActions = Object.keys(value).sort(); + if ( + actions.length !== actualActions.length || + ![...actions].sort().every((x, i) => x === actualActions[i]) + ) { + throw new Error('Payload did not match expected actions'); + } + + Object.values(value).forEach((actionResult) => { + actionValidationSchema.validate(actionResult); + }); + }, + } + ); - const resourceValidationSchema = schema.object( + const resourcesValidationSchema = schema.object( {}, { unknowns: 'allow', @@ -46,13 +59,13 @@ function buildValidationSchema(application: string, actions: string[], resources const actualResources = Object.keys(value).sort(); if ( resources.length !== actualResources.length || - !resources.sort().every((x, i) => x === actualResources[i]) + ![...resources].sort().every((x, i) => x === actualResources[i]) ) { throw new Error('Payload did not match expected resources'); } Object.values(value).forEach((actionResult) => { - actionValidationSchema.validate(actionResult); + actionsValidationSchema.validate(actionResult); }); }, } @@ -63,7 +76,7 @@ function buildValidationSchema(application: string, actions: string[], resources has_all_requested: schema.boolean(), cluster: schema.object({}, { unknowns: 'allow' }), application: schema.object({ - [application]: resourceValidationSchema, + [application]: resourcesValidationSchema, }), index: schema.object({}, { unknowns: 'allow' }), }); From f81cdc3ff7c64825c4c45fceec1a31996e3f4e6f Mon Sep 17 00:00:00 2001 From: Devon Thomson Date: Tue, 16 Feb 2021 16:01:01 -0500 Subject: [PATCH 25/53] [Time to Visualize] Unsaved Changes Badge (#91073) (#91540) * Added unsaved changes badge to dashboards. Removed (unsaved) from the dashboard title --- .../public/application/dashboard_app.tsx | 10 ++++- .../application/dashboard_state.test.ts | 2 + .../application/dashboard_state_manager.ts | 16 +++++++ .../hooks/use_dashboard_breadcrumbs.ts | 1 - .../hooks/use_dashboard_container.test.tsx | 14 ++++--- .../hooks/use_dashboard_container.ts | 30 +++++++++---- .../hooks/use_dashboard_state_manager.ts | 3 +- .../application/top_nav/dashboard_top_nav.tsx | 15 ++++++- .../dashboard/public/dashboard_strings.ts | 42 ++++++++----------- .../public/top_nav_menu/_index.scss | 9 ++++ .../public/top_nav_menu/top_nav_menu.tsx | 26 ++++++++++-- .../apps/dashboard/copy_panel_to.ts | 2 +- .../apps/dashboard/dashboard_unsaved_state.ts | 25 ++++++++++- .../translations/translations/ja-JP.json | 1 - .../translations/translations/zh-CN.json | 1 - 15 files changed, 148 insertions(+), 49 deletions(-) diff --git a/src/plugins/dashboard/public/application/dashboard_app.tsx b/src/plugins/dashboard/public/application/dashboard_app.tsx index d060327563b25..f659fa002e922 100644 --- a/src/plugins/dashboard/public/application/dashboard_app.tsx +++ b/src/plugins/dashboard/public/application/dashboard_app.tsx @@ -67,7 +67,13 @@ export function DashboardApp({ savedDashboard, history ); - const dashboardContainer = useDashboardContainer(dashboardStateManager, history, false); + const [unsavedChanges, setUnsavedChanges] = useState(false); + const dashboardContainer = useDashboardContainer({ + timeFilter: data.query.timefilter.timefilter, + dashboardStateManager, + setUnsavedChanges, + history, + }); const searchSessionIdQuery$ = useMemo( () => createQueryParamObservable(history, DashboardConstants.SEARCH_SESSION_ID), [history] @@ -200,6 +206,7 @@ export function DashboardApp({ ); dashboardStateManager.registerChangeListener(() => { + setUnsavedChanges(dashboardStateManager?.hasUnsavedPanelState()); // we aren't checking dirty state because there are changes the container needs to know about // that won't make the dashboard "dirty" - like a view mode change. triggerRefresh$.next(); @@ -281,6 +288,7 @@ export function DashboardApp({ embedSettings, indexPatterns, savedDashboard, + unsavedChanges, dashboardContainer, dashboardStateManager, }} diff --git a/src/plugins/dashboard/public/application/dashboard_state.test.ts b/src/plugins/dashboard/public/application/dashboard_state.test.ts index 04112d10ae7e3..c5bda98c31b70 100644 --- a/src/plugins/dashboard/public/application/dashboard_state.test.ts +++ b/src/plugins/dashboard/public/application/dashboard_state.test.ts @@ -17,6 +17,7 @@ import { createKbnUrlStateStorage } from '../services/kibana_utils'; import { InputTimeRange, TimefilterContract, TimeRange } from '../services/data'; import { embeddablePluginMock } from 'src/plugins/embeddable/public/mocks'; +import { coreMock } from '../../../../core/public/mocks'; describe('DashboardState', function () { let dashboardState: DashboardStateManager; @@ -45,6 +46,7 @@ describe('DashboardState', function () { kibanaVersion: '7.0.0', kbnUrlStateStorage: createKbnUrlStateStorage(), history: createBrowserHistory(), + toasts: coreMock.createStart().notifications.toasts, hasTaggingCapabilities: mockHasTaggingCapabilities, }); } diff --git a/src/plugins/dashboard/public/application/dashboard_state_manager.ts b/src/plugins/dashboard/public/application/dashboard_state_manager.ts index 8494900ea79c7..e4b2afa8a46ea 100644 --- a/src/plugins/dashboard/public/application/dashboard_state_manager.ts +++ b/src/plugins/dashboard/public/application/dashboard_state_manager.ts @@ -43,6 +43,8 @@ import { syncState, } from '../services/kibana_utils'; import { STATE_STORAGE_KEY } from '../url_generator'; +import { NotificationsStart } from '../services/core'; +import { getMigratedToastText } from '../dashboard_strings'; /** * Dashboard state manager handles connecting angular and redux state between the angular and react portions of the @@ -59,10 +61,12 @@ export class DashboardStateManager { query: Query; }; private stateDefaults: DashboardAppStateDefaults; + private toasts: NotificationsStart['toasts']; private hideWriteControls: boolean; private kibanaVersion: string; public isDirty: boolean; private changeListeners: Array<(status: { dirty: boolean }) => void>; + private hasShownMigrationToast = false; public get appState(): DashboardAppState { return this.stateContainer.get(); @@ -93,6 +97,7 @@ export class DashboardStateManager { * @param */ constructor({ + toasts, history, kibanaVersion, savedDashboard, @@ -108,11 +113,13 @@ export class DashboardStateManager { hideWriteControls: boolean; allowByValueEmbeddables: boolean; savedDashboard: DashboardSavedObject; + toasts: NotificationsStart['toasts']; usageCollection?: UsageCollectionSetup; kbnUrlStateStorage: IKbnUrlStateStorage; dashboardPanelStorage?: DashboardPanelStorage; hasTaggingCapabilities: SavedObjectTagDecoratorTypeGuard; }) { + this.toasts = toasts; this.kibanaVersion = kibanaVersion; this.savedDashboard = savedDashboard; this.hideWriteControls = hideWriteControls; @@ -283,6 +290,10 @@ export class DashboardStateManager { if (dirty) { this.stateContainer.transitions.set('panels', Object.values(convertedPanelStateMap)); if (dirtyBecauseOfInitialStateMigration) { + if (this.getIsEditMode() && !this.hasShownMigrationToast) { + this.toasts.addSuccess(getMigratedToastText()); + this.hasShownMigrationToast = true; + } this.saveState({ replace: true }); } @@ -693,6 +704,11 @@ export class DashboardStateManager { this.dashboardPanelStorage.clearPanels(this.savedDashboard?.id); } + public hasUnsavedPanelState(): boolean { + const panels = this.dashboardPanelStorage?.getPanels(this.savedDashboard?.id); + return panels !== undefined && panels.length > 0; + } + private getUnsavedPanelState(): { panels?: SavedDashboardPanel[] } { if (!this.allowByValueEmbeddables || this.getIsViewMode() || !this.dashboardPanelStorage) { return {}; diff --git a/src/plugins/dashboard/public/application/hooks/use_dashboard_breadcrumbs.ts b/src/plugins/dashboard/public/application/hooks/use_dashboard_breadcrumbs.ts index 6eb1c0bf75b24..50465cc4ab58b 100644 --- a/src/plugins/dashboard/public/application/hooks/use_dashboard_breadcrumbs.ts +++ b/src/plugins/dashboard/public/application/hooks/use_dashboard_breadcrumbs.ts @@ -45,7 +45,6 @@ export const useDashboardBreadcrumbs = ( text: getDashboardTitle( dashboardStateManager.getTitle(), dashboardStateManager.getViewMode(), - dashboardStateManager.getIsDirty(timefilter), dashboardStateManager.isNew() ), }, diff --git a/src/plugins/dashboard/public/application/hooks/use_dashboard_container.test.tsx b/src/plugins/dashboard/public/application/hooks/use_dashboard_container.test.tsx index d14b4056a64c6..6a6dc58db7815 100644 --- a/src/plugins/dashboard/public/application/hooks/use_dashboard_container.test.tsx +++ b/src/plugins/dashboard/public/application/hooks/use_dashboard_container.test.tsx @@ -20,6 +20,7 @@ import { DashboardCapabilities } from '../types'; import { EmbeddableFactory } from '../../../../embeddable/public'; import { HelloWorldEmbeddable } from '../../../../embeddable/public/tests/fixtures'; import { DashboardContainer } from '../embeddable'; +import { coreMock } from 'src/core/public/mocks'; const savedDashboard = getSavedDashboardMock(); @@ -32,12 +33,13 @@ const history = createBrowserHistory(); const createDashboardState = () => new DashboardStateManager({ savedDashboard, + kibanaVersion: '7.0.0', hideWriteControls: false, allowByValueEmbeddables: false, - kibanaVersion: '7.0.0', - kbnUrlStateStorage: createKbnUrlStateStorage(), history: createBrowserHistory(), + kbnUrlStateStorage: createKbnUrlStateStorage(), hasTaggingCapabilities: mockHasTaggingCapabilities, + toasts: coreMock.createStart().notifications.toasts, }); const defaultCapabilities: DashboardCapabilities = { @@ -83,9 +85,9 @@ const setupEmbeddableFactory = () => { test('container is destroyed on unmount', async () => { const { createEmbeddable, destroySpy, embeddable } = setupEmbeddableFactory(); - const state = createDashboardState(); + const dashboardStateManager = createDashboardState(); const { result, unmount, waitForNextUpdate } = renderHook( - () => useDashboardContainer(state, history, false), + () => useDashboardContainer({ dashboardStateManager, history }), { wrapper: ({ children }) => ( {children} @@ -113,7 +115,7 @@ test('old container is destroyed on new dashboardStateManager', async () => { const { result, waitForNextUpdate, rerender } = renderHook< DashboardStateManager, DashboardContainer | null - >((dashboardState) => useDashboardContainer(dashboardState, history, false), { + >((dashboardStateManager) => useDashboardContainer({ dashboardStateManager, history }), { wrapper: ({ children }) => ( {children} ), @@ -148,7 +150,7 @@ test('destroyed if rerendered before resolved', async () => { const { result, waitForNextUpdate, rerender } = renderHook< DashboardStateManager, DashboardContainer | null - >((dashboardState) => useDashboardContainer(dashboardState, history, false), { + >((dashboardStateManager) => useDashboardContainer({ dashboardStateManager, history }), { wrapper: ({ children }) => ( {children} ), diff --git a/src/plugins/dashboard/public/application/hooks/use_dashboard_container.ts b/src/plugins/dashboard/public/application/hooks/use_dashboard_container.ts index d12fea07bdd41..f4fe55f877400 100644 --- a/src/plugins/dashboard/public/application/hooks/use_dashboard_container.ts +++ b/src/plugins/dashboard/public/application/hooks/use_dashboard_container.ts @@ -24,12 +24,21 @@ import { getDashboardContainerInput, getSearchSessionIdFromURL } from '../dashbo import { DashboardConstants, DashboardContainer, DashboardContainerInput } from '../..'; import { DashboardAppServices } from '../types'; import { DASHBOARD_CONTAINER_TYPE } from '..'; - -export const useDashboardContainer = ( - dashboardStateManager: DashboardStateManager | null, - history: History, - isEmbeddedExternally: boolean -) => { +import { TimefilterContract } from '../../services/data'; + +export const useDashboardContainer = ({ + history, + timeFilter, + setUnsavedChanges, + dashboardStateManager, + isEmbeddedExternally, +}: { + history: History; + isEmbeddedExternally?: boolean; + timeFilter?: TimefilterContract; + setUnsavedChanges?: (dirty: boolean) => void; + dashboardStateManager: DashboardStateManager | null; +}) => { const { dashboardCapabilities, data, @@ -72,15 +81,20 @@ export const useDashboardContainer = ( .getStateTransfer() .getIncomingEmbeddablePackage(DashboardConstants.DASHBOARDS_ID, true); + // when dashboard state manager initially loads, determine whether or not there are unsaved changes + setUnsavedChanges?.( + Boolean(incomingEmbeddable) || dashboardStateManager.hasUnsavedPanelState() + ); + let canceled = false; let pendingContainer: DashboardContainer | ErrorEmbeddable | null | undefined; (async function createContainer() { pendingContainer = await dashboardFactory.create( getDashboardContainerInput({ + isEmbeddedExternally: Boolean(isEmbeddedExternally), dashboardCapabilities, dashboardStateManager, incomingEmbeddable, - isEmbeddedExternally, query, searchSessionId: searchSessionIdFromURL ?? searchSession.start(), }) @@ -141,8 +155,10 @@ export const useDashboardContainer = ( dashboardCapabilities, dashboardStateManager, isEmbeddedExternally, + setUnsavedChanges, searchSession, scopedHistory, + timeFilter, embeddable, history, query, diff --git a/src/plugins/dashboard/public/application/hooks/use_dashboard_state_manager.ts b/src/plugins/dashboard/public/application/hooks/use_dashboard_state_manager.ts index ed14223bb0a83..effd598cc3ee8 100644 --- a/src/plugins/dashboard/public/application/hooks/use_dashboard_state_manager.ts +++ b/src/plugins/dashboard/public/application/hooks/use_dashboard_state_manager.ts @@ -87,6 +87,7 @@ export const useDashboardStateManager = ( }); const stateManager = new DashboardStateManager({ + toasts: core.notifications.toasts, hasTaggingCapabilities, dashboardPanelStorage, hideWriteControls, @@ -160,7 +161,6 @@ export const useDashboardStateManager = ( const dashboardTitle = getDashboardTitle( stateManager.getTitle(), stateManager.getViewMode(), - stateManager.getIsDirty(timefilter), stateManager.isNew() ); @@ -213,6 +213,7 @@ export const useDashboardStateManager = ( uiSettings, usageCollection, allowByValueEmbeddables, + core.notifications.toasts, dashboardCapabilities.storeSearchSession, ]); diff --git a/src/plugins/dashboard/public/application/top_nav/dashboard_top_nav.tsx b/src/plugins/dashboard/public/application/top_nav/dashboard_top_nav.tsx index e68d371ebb270..11fb7f0cb56ff 100644 --- a/src/plugins/dashboard/public/application/top_nav/dashboard_top_nav.tsx +++ b/src/plugins/dashboard/public/application/top_nav/dashboard_top_nav.tsx @@ -45,7 +45,7 @@ import { ShowShareModal } from './show_share_modal'; import { PanelToolbar } from './panel_toolbar'; import { confirmDiscardOrKeepUnsavedChanges } from '../listing/confirm_overlays'; import { OverlayRef } from '../../../../../core/public'; -import { getNewDashboardTitle } from '../../dashboard_strings'; +import { getNewDashboardTitle, unsavedChangesBadge } from '../../dashboard_strings'; import { DASHBOARD_PANELS_UNSAVED_ID } from '../lib/dashboard_panel_storage'; import { DashboardContainer } from '..'; @@ -64,6 +64,7 @@ export interface DashboardTopNavProps { timefilter: TimefilterContract; indexPatterns: IndexPattern[]; redirectTo: DashboardRedirect; + unsavedChanges?: boolean; lastDashboardId?: string; viewMode: ViewMode; } @@ -72,6 +73,7 @@ export function DashboardTopNav({ dashboardStateManager, dashboardContainer, lastDashboardId, + unsavedChanges, savedDashboard, onQuerySubmit, embedSettings, @@ -467,7 +469,18 @@ export function DashboardTopNav({ isDirty: dashboardStateManager.isDirty, }); + const badges = unsavedChanges + ? [ + { + 'data-test-subj': 'dashboardUnsavedChangesBadge', + badgeText: unsavedChangesBadge.getUnsavedChangedBadgeText(), + color: 'secondary', + }, + ] + : undefined; + return { + badges, appName: 'dashboard', config: showTopNavMenu ? topNav : undefined, className: isFullScreenMode ? 'kbnTopNavMenu-isFullScreen' : undefined, diff --git a/src/plugins/dashboard/public/dashboard_strings.ts b/src/plugins/dashboard/public/dashboard_strings.ts index 8588fbc1bbdc7..dad347b176c7e 100644 --- a/src/plugins/dashboard/public/dashboard_strings.ts +++ b/src/plugins/dashboard/public/dashboard_strings.ts @@ -12,36 +12,30 @@ import { ViewMode } from './services/embeddable'; /** * @param title {string} the current title of the dashboard * @param viewMode {DashboardViewMode} the current mode. If in editing state, prepends 'Editing ' to the title. - * @param isDirty {boolean} if the dashboard is in a dirty state. If in dirty state, adds (unsaved) to the - * end of the title. * @returns {string} A title to display to the user based on the above parameters. */ -export function getDashboardTitle( - title: string, - viewMode: ViewMode, - isDirty: boolean, - isNew: boolean -): string { +export function getDashboardTitle(title: string, viewMode: ViewMode, isNew: boolean): string { const isEditMode = viewMode === ViewMode.EDIT; - let displayTitle: string; const dashboardTitle = isNew ? getNewDashboardTitle() : title; + return isEditMode + ? i18n.translate('dashboard.strings.dashboardEditTitle', { + defaultMessage: 'Editing {title}', + values: { title: dashboardTitle }, + }) + : dashboardTitle; +} - if (isEditMode && isDirty) { - displayTitle = i18n.translate('dashboard.strings.dashboardUnsavedEditTitle', { - defaultMessage: 'Editing {title} (unsaved)', - values: { title: dashboardTitle }, - }); - } else if (isEditMode) { - displayTitle = i18n.translate('dashboard.strings.dashboardEditTitle', { - defaultMessage: 'Editing {title}', - values: { title: dashboardTitle }, - }); - } else { - displayTitle = dashboardTitle; - } +export const unsavedChangesBadge = { + getUnsavedChangedBadgeText: () => + i18n.translate('dashboard.unsavedChangesBadge', { + defaultMessage: 'Unsaved changes', + }), +}; - return displayTitle; -} +export const getMigratedToastText = () => + i18n.translate('dashboard.migratedChanges', { + defaultMessage: 'Some panels have been successfully updated to the latest version.', + }); /* Plugin diff --git a/src/plugins/navigation/public/top_nav_menu/_index.scss b/src/plugins/navigation/public/top_nav_menu/_index.scss index 230be399febda..bc27cf061eb68 100644 --- a/src/plugins/navigation/public/top_nav_menu/_index.scss +++ b/src/plugins/navigation/public/top_nav_menu/_index.scss @@ -1,3 +1,12 @@ .kbnTopNavMenu { margin-right: $euiSizeXS; } + +.kbnTopNavMenu__badgeWrapper { + display: flex; + align-items: baseline; +} + +.kbnTopNavMenu__badgeGroup { + margin-right: $euiSizeM; +} diff --git a/src/plugins/navigation/public/top_nav_menu/top_nav_menu.tsx b/src/plugins/navigation/public/top_nav_menu/top_nav_menu.tsx index 70bc3b10b30ad..22edf9c454466 100644 --- a/src/plugins/navigation/public/top_nav_menu/top_nav_menu.tsx +++ b/src/plugins/navigation/public/top_nav_menu/top_nav_menu.tsx @@ -7,7 +7,7 @@ */ import React, { ReactElement } from 'react'; -import { EuiHeaderLinks } from '@elastic/eui'; +import { EuiBadge, EuiBadgeGroup, EuiBadgeProps, EuiHeaderLinks } from '@elastic/eui'; import classNames from 'classnames'; import { MountPoint } from '../../../../core/public'; @@ -23,6 +23,7 @@ import { TopNavMenuItem } from './top_nav_menu_item'; export type TopNavMenuProps = StatefulSearchBarProps & Omit & { config?: TopNavMenuData[]; + badges?: Array; showSearchBar?: boolean; showQueryBar?: boolean; showQueryInput?: boolean; @@ -61,12 +62,28 @@ export type TopNavMenuProps = StatefulSearchBarProps & **/ export function TopNavMenu(props: TopNavMenuProps): ReactElement | null { - const { config, showSearchBar, ...searchBarProps } = props; + const { config, badges, showSearchBar, ...searchBarProps } = props; if ((!config || config.length === 0) && (!showSearchBar || !props.data)) { return null; } + function renderBadges(): ReactElement | null { + if (!badges || badges.length === 0) return null; + return ( + + {badges.map((badge: EuiBadgeProps & { badgeText: string }, i: number) => { + const { badgeText, ...badgeProps } = badge; + return ( + + {badgeText} + + ); + })} + + ); + } + function renderItems(): ReactElement[] | null { if (!config || config.length === 0) return null; return config.map((menuItem: TopNavMenuData, i: number) => { @@ -98,7 +115,10 @@ export function TopNavMenu(props: TopNavMenuProps): ReactElement | null { return ( <> - {renderMenu(menuClassName)} + + {renderBadges()} + {renderMenu(menuClassName)} + {renderSearchBar()} diff --git a/test/functional/apps/dashboard/copy_panel_to.ts b/test/functional/apps/dashboard/copy_panel_to.ts index bb02bfee49f00..9abdc2ceffc01 100644 --- a/test/functional/apps/dashboard/copy_panel_to.ts +++ b/test/functional/apps/dashboard/copy_panel_to.ts @@ -115,7 +115,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await testSubjects.click('confirmCopyToButton'); await PageObjects.dashboard.waitForRenderComplete(); - await PageObjects.dashboard.expectOnDashboard(`Editing New Dashboard (unsaved)`); + await PageObjects.dashboard.expectOnDashboard(`Editing New Dashboard`); }); it('it always appends new panels instead of overwriting', async () => { diff --git a/test/functional/apps/dashboard/dashboard_unsaved_state.ts b/test/functional/apps/dashboard/dashboard_unsaved_state.ts index eaf0d2f2a97df..e6cc91880010a 100644 --- a/test/functional/apps/dashboard/dashboard_unsaved_state.ts +++ b/test/functional/apps/dashboard/dashboard_unsaved_state.ts @@ -13,6 +13,7 @@ import { FtrProviderContext } from '../../ftr_provider_context'; export default function ({ getService, getPageObjects }: FtrProviderContext) { const PageObjects = getPageObjects(['dashboard', 'header', 'visualize', 'settings', 'common']); const esArchiver = getService('esArchiver'); + const testSubjects = getService('testSubjects'); const kibanaServer = getService('kibanaServer'); const dashboardAddPanel = getService('dashboardAddPanel'); @@ -29,10 +30,16 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.common.navigateToApp('dashboard'); await PageObjects.dashboard.preserveCrossAppState(); await PageObjects.dashboard.loadSavedDashboard('few panels'); - await PageObjects.dashboard.switchToEditMode(); - + await PageObjects.header.waitUntilLoadingHasFinished(); originalPanelCount = await PageObjects.dashboard.getPanelCount(); + }); + it('does not show unsaved changes badge when there are no unsaved changes', async () => { + await testSubjects.missingOrFail('dashboardUnsavedChangesBadge'); + }); + + it('shows the unsaved changes badge after adding panels', async () => { + await PageObjects.dashboard.switchToEditMode(); // add an area chart by value await dashboardAddPanel.clickCreateNewLink(); await PageObjects.visualize.clickAggBasedVisualizations(); @@ -42,6 +49,9 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { // add a metric by reference await dashboardAddPanel.addVisualization('Rendering-Test: metric'); + + await PageObjects.header.waitUntilLoadingHasFinished(); + await testSubjects.existOrFail('dashboardUnsavedChangesBadge'); }); it('has correct number of panels', async () => { @@ -73,10 +83,15 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('resets to original panel count upon entering view mode', async () => { await PageObjects.header.waitUntilLoadingHasFinished(); await PageObjects.dashboard.clickCancelOutOfEditMode(); + await PageObjects.header.waitUntilLoadingHasFinished(); const currentPanelCount = await PageObjects.dashboard.getPanelCount(); expect(currentPanelCount).to.eql(originalPanelCount); }); + it('shows unsaved changes badge in view mode if changes have not been discarded', async () => { + await testSubjects.existOrFail('dashboardUnsavedChangesBadge'); + }); + it('retains unsaved panel count after returning to edit mode', async () => { await PageObjects.header.waitUntilLoadingHasFinished(); await PageObjects.dashboard.switchToEditMode(); @@ -84,5 +99,11 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const currentPanelCount = await PageObjects.dashboard.getPanelCount(); expect(currentPanelCount).to.eql(unsavedPanelCount); }); + + it('does not show unsaved changes badge after saving', async () => { + await PageObjects.dashboard.saveDashboard('Unsaved State Test'); + await PageObjects.header.waitUntilLoadingHasFinished(); + await testSubjects.missingOrFail('dashboardUnsavedChangesBadge'); + }); }); } diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 8db16fe58cca9..1dc7e1a9cac94 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -646,7 +646,6 @@ "dashboard.savedDashboard.newDashboardTitle": "新規ダッシュボード", "dashboard.stateManager.timeNotSavedWithDashboardErrorMessage": "このダッシュボードに時刻が保存されていないため、同期できません。", "dashboard.strings.dashboardEditTitle": "{title}を編集中", - "dashboard.strings.dashboardUnsavedEditTitle": "{title}を編集中(未保存)", "dashboard.topNav.cloneModal.cancelButtonLabel": "キャンセル", "dashboard.topNav.cloneModal.cloneDashboardModalHeaderTitle": "ダッシュボードのクローンを作成", "dashboard.topNav.cloneModal.confirmButtonLabel": "クローンの確認", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index be595d0dd436d..5343cfe5cbe6f 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -646,7 +646,6 @@ "dashboard.savedDashboard.newDashboardTitle": "新建仪表板", "dashboard.stateManager.timeNotSavedWithDashboardErrorMessage": "时间未随此仪表板保存,因此无法同步。", "dashboard.strings.dashboardEditTitle": "正在编辑 {title}", - "dashboard.strings.dashboardUnsavedEditTitle": "正在编辑 {title}(未保存)", "dashboard.topNav.cloneModal.cancelButtonLabel": "取消", "dashboard.topNav.cloneModal.cloneDashboardModalHeaderTitle": "克隆仪表板", "dashboard.topNav.cloneModal.confirmButtonLabel": "确认克隆", From fedc100d4c53adaf0fabf4b448d5e0fd2ded3514 Mon Sep 17 00:00:00 2001 From: Spencer Date: Tue, 16 Feb 2021 13:01:26 -0800 Subject: [PATCH 26/53] [7.x] ship @kbn/ui-shared-deps metrics in separate step (#91504) (#91539) Co-authored-by: spalger --- packages/kbn-ui-shared-deps/scripts/build.js | 32 ++----------------- packages/kbn-ui-shared-deps/webpack.config.js | 31 ++++++++++++++++++ test/scripts/jenkins_baseline.sh | 4 ++- test/scripts/jenkins_build_kibana.sh | 4 ++- test/scripts/jenkins_xpack_baseline.sh | 4 ++- test/scripts/jenkins_xpack_build_kibana.sh | 4 ++- 6 files changed, 45 insertions(+), 34 deletions(-) diff --git a/packages/kbn-ui-shared-deps/scripts/build.js b/packages/kbn-ui-shared-deps/scripts/build.js index 9e1e755b3077a..0993f78590246 100644 --- a/packages/kbn-ui-shared-deps/scripts/build.js +++ b/packages/kbn-ui-shared-deps/scripts/build.js @@ -7,9 +7,8 @@ */ const Path = require('path'); -const Fs = require('fs'); -const { run, createFailError, CiStatsReporter } = require('@kbn/dev-utils'); +const { run, createFailError } = require('@kbn/dev-utils'); const webpack = require('webpack'); const Stats = require('webpack/lib/Stats'); const del = require('del'); @@ -34,34 +33,6 @@ run( const took = Math.round((stats.endTime - stats.startTime) / 1000); if (!stats.hasErrors() && !stats.hasWarnings()) { - if (!flags.dev) { - const reporter = CiStatsReporter.fromEnv(log); - - const metrics = [ - { - group: '@kbn/ui-shared-deps asset size', - id: 'kbn-ui-shared-deps.js', - value: Fs.statSync(Path.resolve(DIST_DIR, 'kbn-ui-shared-deps.js')).size, - }, - { - group: '@kbn/ui-shared-deps asset size', - id: 'kbn-ui-shared-deps.@elastic.js', - value: Fs.statSync(Path.resolve(DIST_DIR, 'kbn-ui-shared-deps.@elastic.js')).size, - }, - { - group: '@kbn/ui-shared-deps asset size', - id: 'css', - value: - Fs.statSync(Path.resolve(DIST_DIR, 'kbn-ui-shared-deps.css')).size + - Fs.statSync(Path.resolve(DIST_DIR, 'kbn-ui-shared-deps.v7.light.css')).size, - }, - ]; - - log.debug('metrics:', metrics); - - await reporter.metrics(metrics); - } - log.success(`webpack completed in about ${took} seconds`); return; } @@ -101,6 +72,7 @@ run( return; } + log.info('running webpack'); await onCompilationComplete( await new Promise((resolve, reject) => { compiler.run((error, stats) => { diff --git a/packages/kbn-ui-shared-deps/webpack.config.js b/packages/kbn-ui-shared-deps/webpack.config.js index 7ff5978e1f2ea..cc761dae3bfe9 100644 --- a/packages/kbn-ui-shared-deps/webpack.config.js +++ b/packages/kbn-ui-shared-deps/webpack.config.js @@ -12,6 +12,7 @@ const MiniCssExtractPlugin = require('mini-css-extract-plugin'); const CompressionPlugin = require('compression-webpack-plugin'); const { REPO_ROOT } = require('@kbn/utils'); const webpack = require('webpack'); +const { RawSource } = require('webpack-sources'); const UiSharedDeps = require('./index'); @@ -145,6 +146,36 @@ exports.getWebpackConfig = ({ dev = false } = {}) => ({ test: /\.(js|css)$/, cache: false, }), + new (class MetricsPlugin { + apply(compiler) { + compiler.hooks.emit.tap('MetricsPlugin', (compilation) => { + const metrics = [ + { + group: '@kbn/ui-shared-deps asset size', + id: 'kbn-ui-shared-deps.js', + value: compilation.assets['kbn-ui-shared-deps.js'].size(), + }, + { + group: '@kbn/ui-shared-deps asset size', + id: 'kbn-ui-shared-deps.@elastic.js', + value: compilation.assets['kbn-ui-shared-deps.@elastic.js'].size(), + }, + { + group: '@kbn/ui-shared-deps asset size', + id: 'css', + value: + compilation.assets['kbn-ui-shared-deps.css'].size() + + compilation.assets['kbn-ui-shared-deps.v7.light.css'].size(), + }, + ]; + + compilation.emitAsset( + 'metrics.json', + new RawSource(JSON.stringify(metrics, null, 2)) + ); + }); + } + })(), ]), ], }); diff --git a/test/scripts/jenkins_baseline.sh b/test/scripts/jenkins_baseline.sh index 60926238576c7..58d86cddf65fa 100755 --- a/test/scripts/jenkins_baseline.sh +++ b/test/scripts/jenkins_baseline.sh @@ -7,7 +7,9 @@ echo " -> building and extracting OSS Kibana distributable for use in functional node scripts/build --debug --oss echo " -> shipping metrics from build to ci-stats" -node scripts/ship_ci_stats --metrics target/optimizer_bundle_metrics.json +node scripts/ship_ci_stats \ + --metrics target/optimizer_bundle_metrics.json \ + --metrics packages/kbn-ui-shared-deps/target/metrics.json linuxBuild="$(find "$KIBANA_DIR/target" -name 'kibana-*-linux-x86_64.tar.gz')" installDir="$PARENT_DIR/install/kibana" diff --git a/test/scripts/jenkins_build_kibana.sh b/test/scripts/jenkins_build_kibana.sh index 330d529a8a884..95c619423d313 100755 --- a/test/scripts/jenkins_build_kibana.sh +++ b/test/scripts/jenkins_build_kibana.sh @@ -16,7 +16,9 @@ echo " -> building and extracting OSS Kibana distributable for use in functional node scripts/build --debug --oss echo " -> shipping metrics from build to ci-stats" -node scripts/ship_ci_stats --metrics target/optimizer_bundle_metrics.json +node scripts/ship_ci_stats \ + --metrics target/optimizer_bundle_metrics.json \ + --metrics packages/kbn-ui-shared-deps/target/metrics.json mkdir -p "$WORKSPACE/kibana-build-oss" cp -pR build/oss/kibana-*-SNAPSHOT-linux-x86_64/. $WORKSPACE/kibana-build-oss/ diff --git a/test/scripts/jenkins_xpack_baseline.sh b/test/scripts/jenkins_xpack_baseline.sh index aaacdd4ea3aae..2755a6e0a705d 100755 --- a/test/scripts/jenkins_xpack_baseline.sh +++ b/test/scripts/jenkins_xpack_baseline.sh @@ -8,7 +8,9 @@ cd "$KIBANA_DIR" node scripts/build --debug --no-oss echo " -> shipping metrics from build to ci-stats" -node scripts/ship_ci_stats --metrics target/optimizer_bundle_metrics.json +node scripts/ship_ci_stats \ + --metrics target/optimizer_bundle_metrics.json \ + --metrics packages/kbn-ui-shared-deps/target/metrics.json linuxBuild="$(find "$KIBANA_DIR/target" -name 'kibana-*-linux-x86_64.tar.gz')" installDir="$KIBANA_DIR/install/kibana" diff --git a/test/scripts/jenkins_xpack_build_kibana.sh b/test/scripts/jenkins_xpack_build_kibana.sh index b2973f68a7775..abe0990b9044c 100755 --- a/test/scripts/jenkins_xpack_build_kibana.sh +++ b/test/scripts/jenkins_xpack_build_kibana.sh @@ -32,7 +32,9 @@ cd "$KIBANA_DIR" node scripts/build --debug --no-oss echo " -> shipping metrics from build to ci-stats" -node scripts/ship_ci_stats --metrics target/optimizer_bundle_metrics.json +node scripts/ship_ci_stats \ + --metrics target/optimizer_bundle_metrics.json \ + --metrics packages/kbn-ui-shared-deps/target/metrics.json linuxBuild="$(find "$KIBANA_DIR/target" -name 'kibana-*-linux-x86_64.tar.gz')" installDir="$KIBANA_DIR/install/kibana" From 59585a38ad4d083806b49936ca2d2592db75b308 Mon Sep 17 00:00:00 2001 From: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Date: Tue, 16 Feb 2021 16:02:49 -0500 Subject: [PATCH 27/53] TS project references for monitoring plugin (#91498) (#91538) Fixes #89293. Co-authored-by: Nathan L Smith --- tsconfig.refs.json | 1 + x-pack/plugins/monitoring/tsconfig.json | 31 +++++++++++++++++++++++++ x-pack/test/tsconfig.json | 3 ++- x-pack/tsconfig.json | 6 ++--- 4 files changed, 37 insertions(+), 4 deletions(-) create mode 100644 x-pack/plugins/monitoring/tsconfig.json diff --git a/tsconfig.refs.json b/tsconfig.refs.json index 39f3057ec9b2a..7806cf93756e5 100644 --- a/tsconfig.refs.json +++ b/tsconfig.refs.json @@ -87,6 +87,7 @@ { "path": "./x-pack/plugins/maps_legacy_licensing/tsconfig.json" }, { "path": "./x-pack/plugins/maps/tsconfig.json" }, { "path": "./x-pack/plugins/ml/tsconfig.json" }, + { "path": "./x-pack/plugins/monitoring/tsconfig.json" }, { "path": "./x-pack/plugins/observability/tsconfig.json" }, { "path": "./x-pack/plugins/osquery/tsconfig.json" }, { "path": "./x-pack/plugins/painless_lab/tsconfig.json" }, diff --git a/x-pack/plugins/monitoring/tsconfig.json b/x-pack/plugins/monitoring/tsconfig.json new file mode 100644 index 0000000000000..760ff188aacfc --- /dev/null +++ b/x-pack/plugins/monitoring/tsconfig.json @@ -0,0 +1,31 @@ +{ + "extends": "../../../tsconfig.base.json", + "compilerOptions": { + "composite": true, + "outDir": "./target/types", + "emitDeclarationOnly": true, + "declaration": true, + "declarationMap": true + }, + "include": ["common/**/*", "public/**/*", "server/**/*"], + "references": [ + { "path": "../../../src/core/tsconfig.json" }, + { "path": "../../../src/plugins/data/tsconfig.json" }, + { "path": "../../../src/plugins/home/tsconfig.json" }, + { "path": "../../../src/plugins/kibana_legacy/tsconfig.json" }, + { "path": "../../../src/plugins/kibana_utils/tsconfig.json" }, + { "path": "../../../src/plugins/navigation/tsconfig.json" }, + { "path": "../../../src/plugins/usage_collection/tsconfig.json" }, + { "path": "../actions/tsconfig.json" }, + { "path": "../alerts/tsconfig.json" }, + { "path": "../cloud/tsconfig.json" }, + { "path": "../encrypted_saved_objects/tsconfig.json" }, + { "path": "../features/tsconfig.json" }, + { "path": "../infra/tsconfig.json" }, + { "path": "../license_management/tsconfig.json" }, + { "path": "../licensing/tsconfig.json" }, + { "path": "../observability/tsconfig.json" }, + { "path": "../telemetry_collection_xpack/tsconfig.json" }, + { "path": "../triggers_actions_ui/tsconfig.json" } + ] +} diff --git a/x-pack/test/tsconfig.json b/x-pack/test/tsconfig.json index 1c2e0aeecd247..ff3fec1c5aaee 100644 --- a/x-pack/test/tsconfig.json +++ b/x-pack/test/tsconfig.json @@ -69,6 +69,7 @@ { "path": "../plugins/license_management/tsconfig.json" }, { "path": "../plugins/licensing/tsconfig.json" }, { "path": "../plugins/ml/tsconfig.json" }, + { "path": "../plugins/monitoring/tsconfig.json" }, { "path": "../plugins/observability/tsconfig.json" }, { "path": "../plugins/osquery/tsconfig.json" }, { "path": "../plugins/painless_lab/tsconfig.json" }, @@ -88,7 +89,7 @@ { "path": "../plugins/rollup/tsconfig.json" }, { "path": "../plugins/remote_clusters/tsconfig.json" }, { "path": "../plugins/cross_cluster_replication/tsconfig.json" }, - { "path": "../plugins/index_lifecycle_management/tsconfig.json"}, + { "path": "../plugins/index_lifecycle_management/tsconfig.json" }, { "path": "../plugins/uptime/tsconfig.json" } ] } diff --git a/x-pack/tsconfig.json b/x-pack/tsconfig.json index 813811d4a9ce4..b3e47136977e2 100644 --- a/x-pack/tsconfig.json +++ b/x-pack/tsconfig.json @@ -7,7 +7,6 @@ "plugins/case/**/*", "plugins/lists/**/*", "plugins/logstash/**/*", - "plugins/monitoring/**/*", "plugins/security_solution/**/*", "plugins/xpack_legacy/**/*", "plugins/drilldowns/url_drilldown/**/*" @@ -90,6 +89,7 @@ { "path": "./plugins/maps_legacy_licensing/tsconfig.json" }, { "path": "./plugins/maps/tsconfig.json" }, { "path": "./plugins/ml/tsconfig.json" }, + { "path": "./plugins/monitoring/tsconfig.json" }, { "path": "./plugins/observability/tsconfig.json" }, { "path": "./plugins/osquery/tsconfig.json" }, { "path": "./plugins/painless_lab/tsconfig.json" }, @@ -111,8 +111,8 @@ { "path": "./plugins/watcher/tsconfig.json" }, { "path": "./plugins/rollup/tsconfig.json" }, { "path": "./plugins/remote_clusters/tsconfig.json" }, - { "path": "./plugins/cross_cluster_replication/tsconfig.json"}, - { "path": "./plugins/index_lifecycle_management/tsconfig.json"}, + { "path": "./plugins/cross_cluster_replication/tsconfig.json" }, + { "path": "./plugins/index_lifecycle_management/tsconfig.json" }, { "path": "./plugins/uptime/tsconfig.json" } ] } From f8b3353e81ca4e7dd8dd6e86dce5aea4d30157e9 Mon Sep 17 00:00:00 2001 From: Nathan Reese Date: Tue, 16 Feb 2021 14:11:13 -0700 Subject: [PATCH 28/53] [Maps] fix reporting jobs fail when Elastic Maps Service (EMS) is unavailable (#90834) (#91541) * [Maps] fix Reporting jobs fail when Elastic Maps Service (EMS) is unavailable * clean up test case names * make tests more explicit Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../public/selectors/map_selectors.test.ts | 79 ++++++++++++++++++- .../maps/public/selectors/map_selectors.ts | 7 +- 2 files changed, 82 insertions(+), 4 deletions(-) diff --git a/x-pack/plugins/maps/public/selectors/map_selectors.test.ts b/x-pack/plugins/maps/public/selectors/map_selectors.test.ts index c2f5fc02c5df2..89cd80f4daab5 100644 --- a/x-pack/plugins/maps/public/selectors/map_selectors.test.ts +++ b/x-pack/plugins/maps/public/selectors/map_selectors.test.ts @@ -26,10 +26,12 @@ jest.mock('../kibana_services', () => ({ })); import { DEFAULT_MAP_STORE_STATE } from '../reducers/store'; -import { getTimeFilters } from './map_selectors'; +import { areLayersLoaded, getTimeFilters } from './map_selectors'; +import { LayerDescriptor } from '../../common/descriptor_types'; +import { ILayer } from '../classes/layers/layer'; describe('getTimeFilters', () => { - it('should return timeFilters when contained in state', () => { + test('should return timeFilters when contained in state', () => { const state = { ...DEFAULT_MAP_STORE_STATE, map: { @@ -46,7 +48,7 @@ describe('getTimeFilters', () => { expect(getTimeFilters(state)).toEqual({ to: '2001-01-01', from: '2001-12-31' }); }); - it('should return kibana time filters when not contained in state', () => { + test('should return kibana time filters when not contained in state', () => { const state = { ...DEFAULT_MAP_STORE_STATE, map: { @@ -60,3 +62,74 @@ describe('getTimeFilters', () => { expect(getTimeFilters(state)).toEqual({ to: 'now', from: 'now-15m' }); }); }); + +describe('areLayersLoaded', () => { + function createLayerMock({ + hasErrors = false, + isDataLoaded = false, + isVisible = true, + showAtZoomLevel = true, + }: { + hasErrors?: boolean; + isDataLoaded?: boolean; + isVisible?: boolean; + showAtZoomLevel?: boolean; + }) { + return ({ + hasErrors: () => { + return hasErrors; + }, + isDataLoaded: () => { + return isDataLoaded; + }, + isVisible: () => { + return isVisible; + }, + showAtZoomLevel: () => { + return showAtZoomLevel; + }, + } as unknown) as ILayer; + } + + test('layers waiting for map to load should not be counted loaded', () => { + const layerList: ILayer[] = []; + const waitingForMapReadyLayerList: LayerDescriptor[] = [({} as unknown) as LayerDescriptor]; + const zoom = 4; + expect(areLayersLoaded.resultFunc(layerList, waitingForMapReadyLayerList, zoom)).toBe(false); + }); + + test('layer should not be counted as loaded if it has not loaded', () => { + const layerList = [createLayerMock({ isDataLoaded: false })]; + const waitingForMapReadyLayerList: LayerDescriptor[] = []; + const zoom = 4; + expect(areLayersLoaded.resultFunc(layerList, waitingForMapReadyLayerList, zoom)).toBe(false); + }); + + test('layer should be counted as loaded if its not visible', () => { + const layerList = [createLayerMock({ isVisible: false, isDataLoaded: false })]; + const waitingForMapReadyLayerList: LayerDescriptor[] = []; + const zoom = 4; + expect(areLayersLoaded.resultFunc(layerList, waitingForMapReadyLayerList, zoom)).toBe(true); + }); + + test('layer should be counted as loaded if its not shown at zoom level', () => { + const layerList = [createLayerMock({ showAtZoomLevel: false, isDataLoaded: false })]; + const waitingForMapReadyLayerList: LayerDescriptor[] = []; + const zoom = 4; + expect(areLayersLoaded.resultFunc(layerList, waitingForMapReadyLayerList, zoom)).toBe(true); + }); + + test('layer should be counted as loaded if it has a loading error', () => { + const layerList = [createLayerMock({ hasErrors: true, isDataLoaded: false })]; + const waitingForMapReadyLayerList: LayerDescriptor[] = []; + const zoom = 4; + expect(areLayersLoaded.resultFunc(layerList, waitingForMapReadyLayerList, zoom)).toBe(true); + }); + + test('layer should be counted as loaded if its loaded', () => { + const layerList = [createLayerMock({ isDataLoaded: true })]; + const waitingForMapReadyLayerList: LayerDescriptor[] = []; + const zoom = 4; + expect(areLayersLoaded.resultFunc(layerList, waitingForMapReadyLayerList, zoom)).toBe(true); + }); +}); diff --git a/x-pack/plugins/maps/public/selectors/map_selectors.ts b/x-pack/plugins/maps/public/selectors/map_selectors.ts index f53f39ad2fc0c..b16ac704c3715 100644 --- a/x-pack/plugins/maps/public/selectors/map_selectors.ts +++ b/x-pack/plugins/maps/public/selectors/map_selectors.ts @@ -428,7 +428,12 @@ export const areLayersLoaded = createSelector( for (let i = 0; i < layerList.length; i++) { const layer = layerList[i]; - if (layer.isVisible() && layer.showAtZoomLevel(zoom) && !layer.isDataLoaded()) { + if ( + layer.isVisible() && + layer.showAtZoomLevel(zoom) && + !layer.hasErrors() && + !layer.isDataLoaded() + ) { return false; } } From e294f430e6a28d67721336a589b4d0483f169319 Mon Sep 17 00:00:00 2001 From: Wylie Conlon Date: Tue, 16 Feb 2021 16:11:28 -0500 Subject: [PATCH 29/53] [Docs] Add note about TSVB axis scaling (#91211) (#91516) * [Docs] Add note about TSVB axis scaling * Apply suggestions --- docs/user/dashboard/tsvb.asciidoc | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/user/dashboard/tsvb.asciidoc b/docs/user/dashboard/tsvb.asciidoc index 8a98ef6fa97d3..e89678afec170 100644 --- a/docs/user/dashboard/tsvb.asciidoc +++ b/docs/user/dashboard/tsvb.asciidoc @@ -21,7 +21,9 @@ When you open *TSVB*, click *Panel options*, then verify the following: ==== Visualization options Time series:: - Supports annotations based on timestamped documents in a separate {es} index. + By default, the Y axis shows the full range of data, including zero. To scale the axis from + the minimum to maximum values of the data automatically, go to *Series > Options > Fill* and set *Fill to 0*. + You can add annotations to the x-axis based on timestamped documents in a separate {es} index. All other chart types:: *Panel options > Data timerange mode* controls the timespan used for matching documents. From 38b52b409c9fa99fd8132f6a2b870d4ea94b9d99 Mon Sep 17 00:00:00 2001 From: Nicolas Chaulet Date: Tue, 16 Feb 2021 16:13:36 -0500 Subject: [PATCH 30/53] [Fleet] Setup fleet server indices in Kibana without packages (#90658) (#91543) --- .../plugins/fleet/common/constants/index.ts | 2 + .../server/collectors/agent_collectors.ts | 3 +- x-pack/plugins/fleet/server/plugin.ts | 17 +- .../fleet/server/services/app_context.ts | 4 + .../fleet_server/elastic_index.test.ts | 156 +++++++++++++ .../services/fleet_server/elastic_index.ts | 117 ++++++++++ .../elasticsearch/fleet_actions.json | 30 +++ .../elasticsearch/fleet_agents.json | 220 ++++++++++++++++++ .../fleet_enrollment_api_keys.json | 32 +++ .../elasticsearch/fleet_policies.json | 27 +++ .../elasticsearch/fleet_policies_leader.json | 21 ++ .../elasticsearch/fleet_servers.json | 47 ++++ .../server/services/fleet_server/index.ts | 57 +++++ .../saved_object_migrations.ts} | 36 +-- x-pack/plugins/fleet/server/services/setup.ts | 31 +-- x-pack/plugins/fleet/tsconfig.json | 3 +- 16 files changed, 729 insertions(+), 74 deletions(-) create mode 100644 x-pack/plugins/fleet/server/services/fleet_server/elastic_index.test.ts create mode 100644 x-pack/plugins/fleet/server/services/fleet_server/elastic_index.ts create mode 100644 x-pack/plugins/fleet/server/services/fleet_server/elasticsearch/fleet_actions.json create mode 100644 x-pack/plugins/fleet/server/services/fleet_server/elasticsearch/fleet_agents.json create mode 100644 x-pack/plugins/fleet/server/services/fleet_server/elasticsearch/fleet_enrollment_api_keys.json create mode 100644 x-pack/plugins/fleet/server/services/fleet_server/elasticsearch/fleet_policies.json create mode 100644 x-pack/plugins/fleet/server/services/fleet_server/elasticsearch/fleet_policies_leader.json create mode 100644 x-pack/plugins/fleet/server/services/fleet_server/elasticsearch/fleet_servers.json create mode 100644 x-pack/plugins/fleet/server/services/fleet_server/index.ts rename x-pack/plugins/fleet/server/services/{fleet_server_migration.ts => fleet_server/saved_object_migrations.ts} (84%) diff --git a/x-pack/plugins/fleet/common/constants/index.ts b/x-pack/plugins/fleet/common/constants/index.ts index dcddbe3539abd..d95bc9cf736a6 100644 --- a/x-pack/plugins/fleet/common/constants/index.ts +++ b/x-pack/plugins/fleet/common/constants/index.ts @@ -22,6 +22,8 @@ export * from './settings'; // setting in the future? export const SO_SEARCH_LIMIT = 10000; +export const FLEET_SERVER_INDICES_VERSION = 1; + export const FLEET_SERVER_INDICES = [ '.fleet-actions', '.fleet-agents', diff --git a/x-pack/plugins/fleet/server/collectors/agent_collectors.ts b/x-pack/plugins/fleet/server/collectors/agent_collectors.ts index 8aa66c4ae5f4a..154e78feae283 100644 --- a/x-pack/plugins/fleet/server/collectors/agent_collectors.ts +++ b/x-pack/plugins/fleet/server/collectors/agent_collectors.ts @@ -7,7 +7,8 @@ import { ElasticsearchClient, SavedObjectsClient } from 'kibana/server'; import * as AgentService from '../services/agents'; -import { isFleetServerSetup } from '../services/fleet_server_migration'; +import { isFleetServerSetup } from '../services/fleet_server'; + export interface AgentUsage { total: number; online: number; diff --git a/x-pack/plugins/fleet/server/plugin.ts b/x-pack/plugins/fleet/server/plugin.ts index d89db7f1ac341..d4cd39b274f05 100644 --- a/x-pack/plugins/fleet/server/plugin.ts +++ b/x-pack/plugins/fleet/server/plugin.ts @@ -83,7 +83,7 @@ import { agentCheckinState } from './services/agents/checkin/state'; import { registerFleetUsageCollector } from './collectors/register'; import { getInstallation } from './services/epm/packages'; import { makeRouterEnforcingSuperuser } from './routes/security'; -import { isFleetServerSetup } from './services/fleet_server_migration'; +import { startFleetServerSetup } from './services/fleet_server'; export interface FleetSetupDeps { licensing: LicensingPluginSetup; @@ -297,18 +297,9 @@ export class FleetPlugin licenseService.start(this.licensing$); agentCheckinState.start(); - const fleetServerEnabled = appContextService.getConfig()?.agents?.fleetServerEnabled; - if (fleetServerEnabled) { - // We need licence to be initialized before using the SO service. - await this.licensing$.pipe(first()).toPromise(); - - const fleetSetup = await isFleetServerSetup(); - - if (!fleetSetup) { - this.logger?.warn( - 'Extra setup is needed to be able to use central management for agent, please visit the Fleet app in Kibana.' - ); - } + if (appContextService.getConfig()?.agents?.fleetServerEnabled) { + // Break the promise chain, the error handling is done in startFleetServerSetup + startFleetServerSetup(); } return { diff --git a/x-pack/plugins/fleet/server/services/app_context.ts b/x-pack/plugins/fleet/server/services/app_context.ts index cc4be6b31734a..1ada940dd793c 100644 --- a/x-pack/plugins/fleet/server/services/app_context.ts +++ b/x-pack/plugins/fleet/server/services/app_context.ts @@ -80,6 +80,10 @@ class AppContextService { return this.security; } + public hasSecurity() { + return !!this.security; + } + public getCloud() { return this.cloud; } diff --git a/x-pack/plugins/fleet/server/services/fleet_server/elastic_index.test.ts b/x-pack/plugins/fleet/server/services/fleet_server/elastic_index.test.ts new file mode 100644 index 0000000000000..96e642ba9884e --- /dev/null +++ b/x-pack/plugins/fleet/server/services/fleet_server/elastic_index.test.ts @@ -0,0 +1,156 @@ +/* + * 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 { elasticsearchServiceMock } from 'src/core/server/mocks'; +import hash from 'object-hash'; +import { setupFleetServerIndexes } from './elastic_index'; +import ESFleetAgentIndex from './elasticsearch/fleet_agents.json'; +import ESFleetPoliciesIndex from './elasticsearch/fleet_policies.json'; +import ESFleetPoliciesLeaderIndex from './elasticsearch/fleet_policies_leader.json'; +import ESFleetServersIndex from './elasticsearch/fleet_servers.json'; +import ESFleetEnrollmentApiKeysIndex from './elasticsearch/fleet_enrollment_api_keys.json'; +import EsFleetActionsIndex from './elasticsearch/fleet_actions.json'; + +const FLEET_INDEXES_MIGRATION_HASH = { + '.fleet-actions': hash(EsFleetActionsIndex), + '.fleet-agents': hash(ESFleetAgentIndex), + '.fleet-enrollment-apy-keys': hash(ESFleetEnrollmentApiKeysIndex), + '.fleet-policies': hash(ESFleetPoliciesIndex), + '.fleet-policies-leader': hash(ESFleetPoliciesLeaderIndex), + '.fleet-servers': hash(ESFleetServersIndex), +}; + +describe('setupFleetServerIndexes ', () => { + it('should create all the indices and aliases if nothings exists', async () => { + const esMock = elasticsearchServiceMock.createInternalClient(); + await setupFleetServerIndexes(esMock); + + const indexesCreated = esMock.indices.create.mock.calls.map((call) => call[0].index).sort(); + expect(indexesCreated).toEqual([ + '.fleet-actions_1', + '.fleet-agents_1', + '.fleet-enrollment-api-keys_1', + '.fleet-policies-leader_1', + '.fleet-policies_1', + '.fleet-servers_1', + ]); + const aliasesCreated = esMock.indices.updateAliases.mock.calls + .map((call) => (call[0].body as any)?.actions[0].add.alias) + .sort(); + + expect(aliasesCreated).toEqual([ + '.fleet-actions', + '.fleet-agents', + '.fleet-enrollment-api-keys', + '.fleet-policies', + '.fleet-policies-leader', + '.fleet-servers', + ]); + }); + + it('should not create any indices and create aliases if indices exists but not the aliases', async () => { + const esMock = elasticsearchServiceMock.createInternalClient(); + // @ts-expect-error + esMock.indices.exists.mockResolvedValue({ body: true }); + // @ts-expect-error + esMock.indices.getMapping.mockImplementation((params: { index: string }) => { + return { + body: { + [params.index]: { + mappings: { + _meta: { + // @ts-expect-error + migrationHash: FLEET_INDEXES_MIGRATION_HASH[params.index.replace(/_1$/, '')], + }, + }, + }, + }, + }; + }); + + await setupFleetServerIndexes(esMock); + + expect(esMock.indices.create).not.toBeCalled(); + const aliasesCreated = esMock.indices.updateAliases.mock.calls + .map((call) => (call[0].body as any)?.actions[0].add.alias) + .sort(); + + expect(aliasesCreated).toEqual([ + '.fleet-actions', + '.fleet-agents', + '.fleet-enrollment-api-keys', + '.fleet-policies', + '.fleet-policies-leader', + '.fleet-servers', + ]); + }); + + it('should put new indices mapping if the mapping has been updated ', async () => { + const esMock = elasticsearchServiceMock.createInternalClient(); + // @ts-expect-error + esMock.indices.exists.mockResolvedValue({ body: true }); + // @ts-expect-error + esMock.indices.getMapping.mockImplementation((params: { index: string }) => { + return { + body: { + [params.index]: { + mappings: { + _meta: { + migrationHash: 'NOT_VALID_HASH', + }, + }, + }, + }, + }; + }); + + await setupFleetServerIndexes(esMock); + + expect(esMock.indices.create).not.toBeCalled(); + const indexesMappingUpdated = esMock.indices.putMapping.mock.calls + .map((call) => call[0].index) + .sort(); + + expect(indexesMappingUpdated).toEqual([ + '.fleet-actions_1', + '.fleet-agents_1', + '.fleet-enrollment-api-keys_1', + '.fleet-policies-leader_1', + '.fleet-policies_1', + '.fleet-servers_1', + ]); + }); + + it('should not create any indices or aliases if indices and aliases already exists', async () => { + const esMock = elasticsearchServiceMock.createInternalClient(); + + // @ts-expect-error + esMock.indices.exists.mockResolvedValue({ body: true }); + // @ts-expect-error + esMock.indices.getMapping.mockImplementation((params: { index: string }) => { + return { + body: { + [params.index]: { + mappings: { + _meta: { + // @ts-expect-error + migrationHash: FLEET_INDEXES_MIGRATION_HASH[params.index.replace(/_1$/, '')], + }, + }, + }, + }, + }; + }); + // @ts-expect-error + esMock.indices.existsAlias.mockResolvedValue({ body: true }); + + await setupFleetServerIndexes(esMock); + + expect(esMock.indices.create).not.toBeCalled(); + expect(esMock.indices.updateAliases).not.toBeCalled(); + }); +}); diff --git a/x-pack/plugins/fleet/server/services/fleet_server/elastic_index.ts b/x-pack/plugins/fleet/server/services/fleet_server/elastic_index.ts new file mode 100644 index 0000000000000..15672be756fe2 --- /dev/null +++ b/x-pack/plugins/fleet/server/services/fleet_server/elastic_index.ts @@ -0,0 +1,117 @@ +/* + * 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 { ElasticsearchClient } from 'kibana/server'; +import hash from 'object-hash'; + +import { FLEET_SERVER_INDICES, FLEET_SERVER_INDICES_VERSION } from '../../../common'; +import { appContextService } from '../app_context'; +import ESFleetAgentIndex from './elasticsearch/fleet_agents.json'; +import ESFleetPoliciesIndex from './elasticsearch/fleet_policies.json'; +import ESFleetPoliciesLeaderIndex from './elasticsearch/fleet_policies_leader.json'; +import ESFleetServersIndex from './elasticsearch/fleet_servers.json'; +import ESFleetEnrollmentApiKeysIndex from './elasticsearch/fleet_enrollment_api_keys.json'; +import EsFleetActionsIndex from './elasticsearch/fleet_actions.json'; + +const FLEET_INDEXES: Array<[typeof FLEET_SERVER_INDICES[number], any]> = [ + ['.fleet-actions', EsFleetActionsIndex], + ['.fleet-agents', ESFleetAgentIndex], + ['.fleet-enrollment-api-keys', ESFleetEnrollmentApiKeysIndex], + ['.fleet-policies', ESFleetPoliciesIndex], + ['.fleet-policies-leader', ESFleetPoliciesLeaderIndex], + ['.fleet-servers', ESFleetServersIndex], +]; + +export async function setupFleetServerIndexes( + esClient = appContextService.getInternalUserESClient() +) { + await Promise.all( + FLEET_INDEXES.map(async ([indexAlias, indexData]) => { + const index = `${indexAlias}_${FLEET_SERVER_INDICES_VERSION}`; + await createOrUpdateIndex(esClient, index, indexData); + await createAliasIfDoNotExists(esClient, indexAlias, index); + }) + ); +} + +export async function createAliasIfDoNotExists( + esClient: ElasticsearchClient, + alias: string, + index: string +) { + const { body: exists } = await esClient.indices.existsAlias({ + name: alias, + }); + + if (exists === true) { + return; + } + await esClient.indices.updateAliases({ + body: { + actions: [ + { + add: { index, alias }, + }, + ], + }, + }); +} + +async function createOrUpdateIndex( + esClient: ElasticsearchClient, + indexName: string, + indexData: any +) { + const resExists = await esClient.indices.exists({ + index: indexName, + }); + + // Support non destructive migration only (adding new field) + if (resExists.body === true) { + return updateIndex(esClient, indexName, indexData); + } + + return createIndex(esClient, indexName, indexData); +} + +async function updateIndex(esClient: ElasticsearchClient, indexName: string, indexData: any) { + const res = await esClient.indices.getMapping({ + index: indexName, + }); + + const migrationHash = hash(indexData); + if (res.body[indexName].mappings?._meta?.migrationHash !== migrationHash) { + await esClient.indices.putMapping({ + index: indexName, + body: Object.assign({ + ...indexData.mappings, + _meta: { ...(indexData.mappings._meta || {}), migrationHash }, + }), + }); + } +} + +async function createIndex(esClient: ElasticsearchClient, indexName: string, indexData: any) { + try { + const migrationHash = hash(indexData); + await esClient.indices.create({ + index: indexName, + body: { + ...indexData, + mappings: Object.assign({ + ...indexData.mappings, + _meta: { ...(indexData.mappings._meta || {}), migrationHash }, + }), + }, + }); + } catch (err) { + // Swallow already exists errors as concurent Kibana can try to create that indice + if (err?.body?.error?.type !== 'resource_already_exists_exception') { + throw err; + } + } +} diff --git a/x-pack/plugins/fleet/server/services/fleet_server/elasticsearch/fleet_actions.json b/x-pack/plugins/fleet/server/services/fleet_server/elasticsearch/fleet_actions.json new file mode 100644 index 0000000000000..3008ee74ab50c --- /dev/null +++ b/x-pack/plugins/fleet/server/services/fleet_server/elasticsearch/fleet_actions.json @@ -0,0 +1,30 @@ +{ + "settings": {}, + "mappings": { + "dynamic": false, + "properties": { + "action_id": { + "type": "keyword" + }, + "agents": { + "type": "keyword" + }, + "data": { + "enabled": false, + "type": "object" + }, + "expiration": { + "type": "date" + }, + "input_type": { + "type": "keyword" + }, + "@timestamp": { + "type": "date" + }, + "type": { + "type": "keyword" + } + } + } +} diff --git a/x-pack/plugins/fleet/server/services/fleet_server/elasticsearch/fleet_agents.json b/x-pack/plugins/fleet/server/services/fleet_server/elasticsearch/fleet_agents.json new file mode 100644 index 0000000000000..9937e9ad66e56 --- /dev/null +++ b/x-pack/plugins/fleet/server/services/fleet_server/elasticsearch/fleet_agents.json @@ -0,0 +1,220 @@ +{ + "settings": {}, + "mappings": { + "dynamic": false, + "properties": { + "access_api_key_id": { + "type": "keyword" + }, + "action_seq_no": { + "type": "integer" + }, + "active": { + "type": "boolean" + }, + "agent": { + "properties": { + "id": { + "type": "keyword" + }, + "version": { + "type": "keyword" + } + } + }, + "default_api_key": { + "type": "keyword" + }, + "default_api_key_id": { + "type": "keyword" + }, + "enrolled_at": { + "type": "date" + }, + "last_checkin": { + "type": "date" + }, + "last_checkin_status": { + "type": "keyword" + }, + "last_updated": { + "type": "date" + }, + "local_metadata": { + "properties": { + "elastic": { + "properties": { + "agent": { + "properties": { + "build": { + "properties": { + "original": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + } + } + }, + "id": { + "type": "keyword" + }, + "log_level": { + "type": "keyword" + }, + "snapshot": { + "type": "boolean" + }, + "upgradeable": { + "type": "boolean" + }, + "version": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 16 + } + } + } + } + } + } + }, + "host": { + "properties": { + "architecture": { + "type": "keyword" + }, + "hostname": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + }, + "id": { + "type": "keyword" + }, + "ip": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 64 + } + } + }, + "mac": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 17 + } + } + }, + "name": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + } + } + }, + "os": { + "properties": { + "family": { + "type": "keyword" + }, + "full": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 128 + } + } + }, + "kernel": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 128 + } + } + }, + "name": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + }, + "platform": { + "type": "keyword" + }, + "version": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 32 + } + } + } + } + } + } + }, + "packages": { + "type": "keyword" + }, + "policy_coordinator_idx": { + "type": "integer" + }, + "policy_id": { + "type": "keyword" + }, + "policy_revision_idx": { + "type": "integer" + }, + "shared_id": { + "type": "keyword" + }, + "type": { + "type": "keyword" + }, + "unenrolled_at": { + "type": "date" + }, + "unenrollment_started_at": { + "type": "date" + }, + "updated_at": { + "type": "date" + }, + "upgrade_started_at": { + "type": "date" + }, + "upgraded_at": { + "type": "date" + }, + "user_provided_metadata": { + "type": "object", + "enabled": false + } + } + } +} diff --git a/x-pack/plugins/fleet/server/services/fleet_server/elasticsearch/fleet_enrollment_api_keys.json b/x-pack/plugins/fleet/server/services/fleet_server/elasticsearch/fleet_enrollment_api_keys.json new file mode 100644 index 0000000000000..fc3898aff55c6 --- /dev/null +++ b/x-pack/plugins/fleet/server/services/fleet_server/elasticsearch/fleet_enrollment_api_keys.json @@ -0,0 +1,32 @@ +{ + "settings": {}, + "mappings": { + "dynamic": false, + "properties": { + "active": { + "type": "boolean" + }, + "api_key": { + "type": "keyword" + }, + "api_key_id": { + "type": "keyword" + }, + "created_at": { + "type": "date" + }, + "expire_at": { + "type": "date" + }, + "name": { + "type": "keyword" + }, + "policy_id": { + "type": "keyword" + }, + "updated_at": { + "type": "date" + } + } + } +} diff --git a/x-pack/plugins/fleet/server/services/fleet_server/elasticsearch/fleet_policies.json b/x-pack/plugins/fleet/server/services/fleet_server/elasticsearch/fleet_policies.json new file mode 100644 index 0000000000000..50078aaa5ea98 --- /dev/null +++ b/x-pack/plugins/fleet/server/services/fleet_server/elasticsearch/fleet_policies.json @@ -0,0 +1,27 @@ +{ + "settings": {}, + "mappings": { + "dynamic": false, + "properties": { + "coordinator_idx": { + "type": "integer" + }, + "data": { + "enabled": false, + "type": "object" + }, + "default_fleet_server": { + "type": "boolean" + }, + "policy_id": { + "type": "keyword" + }, + "revision_idx": { + "type": "integer" + }, + "@timestamp": { + "type": "date" + } + } + } +} diff --git a/x-pack/plugins/fleet/server/services/fleet_server/elasticsearch/fleet_policies_leader.json b/x-pack/plugins/fleet/server/services/fleet_server/elasticsearch/fleet_policies_leader.json new file mode 100644 index 0000000000000..ad3dfe64df57c --- /dev/null +++ b/x-pack/plugins/fleet/server/services/fleet_server/elasticsearch/fleet_policies_leader.json @@ -0,0 +1,21 @@ +{ + "settings": {}, + "mappings": { + "dynamic": false, + "properties": { + "server": { + "properties": { + "id": { + "type": "keyword" + }, + "version": { + "type": "keyword" + } + } + }, + "@timestamp": { + "type": "date" + } + } + } +} diff --git a/x-pack/plugins/fleet/server/services/fleet_server/elasticsearch/fleet_servers.json b/x-pack/plugins/fleet/server/services/fleet_server/elasticsearch/fleet_servers.json new file mode 100644 index 0000000000000..9ee68735d5b6f --- /dev/null +++ b/x-pack/plugins/fleet/server/services/fleet_server/elasticsearch/fleet_servers.json @@ -0,0 +1,47 @@ +{ + "settings": {}, + "mappings": { + "dynamic": false, + "properties": { + "agent": { + "properties": { + "id": { + "type": "keyword" + }, + "version": { + "type": "keyword" + } + } + }, + "host": { + "properties": { + "architecture": { + "type": "keyword" + }, + "id": { + "type": "keyword" + }, + "ip": { + "type": "keyword" + }, + "name": { + "type": "keyword" + } + } + }, + "server": { + "properties": { + "id": { + "type": "keyword" + }, + "version": { + "type": "keyword" + } + } + }, + "@timestamp": { + "type": "date" + } + } + } +} diff --git a/x-pack/plugins/fleet/server/services/fleet_server/index.ts b/x-pack/plugins/fleet/server/services/fleet_server/index.ts new file mode 100644 index 0000000000000..0b54dc0d168b4 --- /dev/null +++ b/x-pack/plugins/fleet/server/services/fleet_server/index.ts @@ -0,0 +1,57 @@ +/* + * 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 { first } from 'rxjs/operators'; +import { appContextService } from '../app_context'; +import { licenseService } from '../license'; +import { setupFleetServerIndexes } from './elastic_index'; +import { runFleetServerMigration } from './saved_object_migrations'; + +let _isFleetServerSetup = false; +let _isPending = false; +let _status: Promise | undefined; +let _onResolve: (arg?: any) => void; + +export function isFleetServerSetup() { + return _isFleetServerSetup; +} + +export function awaitIfFleetServerSetupPending() { + if (!_isPending) { + return; + } + + return _status; +} + +export async function startFleetServerSetup() { + _isPending = true; + _status = new Promise((resolve) => { + _onResolve = resolve; + }); + const logger = appContextService.getLogger(); + if (!appContextService.hasSecurity()) { + // Fleet will not work if security is not enabled + logger?.warn('Fleet requires the security plugin to be enabled.'); + return; + } + + try { + // We need licence to be initialized before using the SO service. + await licenseService.getLicenseInformation$()?.pipe(first())?.toPromise(); + await setupFleetServerIndexes(); + await runFleetServerMigration(); + _isFleetServerSetup = true; + } catch (err) { + logger?.error('Setup for central management of agents failed.'); + logger?.error(err); + } + _isPending = false; + if (_onResolve) { + _onResolve(); + } +} diff --git a/x-pack/plugins/fleet/server/services/fleet_server_migration.ts b/x-pack/plugins/fleet/server/services/fleet_server/saved_object_migrations.ts similarity index 84% rename from x-pack/plugins/fleet/server/services/fleet_server_migration.ts rename to x-pack/plugins/fleet/server/services/fleet_server/saved_object_migrations.ts index 170bec54983c0..84e6b06e59844 100644 --- a/x-pack/plugins/fleet/server/services/fleet_server_migration.ts +++ b/x-pack/plugins/fleet/server/services/fleet_server/saved_object_migrations.ts @@ -17,38 +17,12 @@ import { AgentSOAttributes, FleetServerAgent, SO_SEARCH_LIMIT, - FLEET_SERVER_PACKAGE, - FLEET_SERVER_INDICES, -} from '../../common'; -import { listEnrollmentApiKeys, getEnrollmentAPIKey } from './api_keys/enrollment_api_key_so'; -import { appContextService } from './app_context'; -import { getInstallation } from './epm/packages'; - -import { isAgentsSetup } from './agents'; -import { agentPolicyService } from './agent_policy'; - -export async function isFleetServerSetup() { - const pkgInstall = await getInstallation({ - savedObjectsClient: getInternalUserSOClient(), - pkgName: FLEET_SERVER_PACKAGE, - }); - - if (!pkgInstall) { - return false; - } +} from '../../../common'; +import { listEnrollmentApiKeys, getEnrollmentAPIKey } from '../api_keys/enrollment_api_key_so'; +import { appContextService } from '../app_context'; - const esClient = appContextService.getInternalUserESClient(); - const exists = await Promise.all( - FLEET_SERVER_INDICES.map(async (index) => { - const res = await esClient.indices.exists({ - index, - }); - return res.statusCode !== 404; - }) - ); - - return exists.every((exist) => exist === true); -} +import { isAgentsSetup } from '../agents'; +import { agentPolicyService } from '../agent_policy'; export async function runFleetServerMigration() { // If Agents are not setup skip as there is nothing to migrate diff --git a/x-pack/plugins/fleet/server/services/setup.ts b/x-pack/plugins/fleet/server/services/setup.ts index 94c3c606f9f8f..2a3166e9dc729 100644 --- a/x-pack/plugins/fleet/server/services/setup.ts +++ b/x-pack/plugins/fleet/server/services/setup.ts @@ -23,7 +23,6 @@ import { Output, DEFAULT_AGENT_POLICIES_PACKAGES, FLEET_SERVER_PACKAGE, - FLEET_SERVER_INDICES, } from '../../common'; import { SO_SEARCH_LIMIT } from '../constants'; import { getPackageInfo } from './epm/packages'; @@ -34,7 +33,7 @@ import { awaitIfPending } from './setup_utils'; import { createDefaultSettings } from './settings'; import { ensureAgentActionPolicyChangeExists } from './agents'; import { appContextService } from './app_context'; -import { runFleetServerMigration } from './fleet_server_migration'; +import { awaitIfFleetServerSetupPending } from './fleet_server'; const FLEET_ENROLL_USERNAME = 'fleet_enroll'; const FLEET_ENROLL_ROLE = 'fleet_enroll'; @@ -88,24 +87,15 @@ async function createSetupSideEffects( // By moving this outside of the Promise.all, the upgrade will occur first, and then we'll attempt to reinstall any // packages that are stuck in the installing state. await ensurePackagesCompletedInstall(soClient, callCluster); + if (isFleetServerEnabled) { - await ensureInstalledPackage({ - savedObjectsClient: soClient, - pkgName: FLEET_SERVER_PACKAGE, - callCluster, - }); - await ensureFleetServerIndicesCreated(esClient); - await runFleetServerMigration(); - } + await awaitIfFleetServerSetupPending(); - if (appContextService.getConfig()?.agents?.fleetServerEnabled) { const fleetServerPackage = await ensureInstalledPackage({ savedObjectsClient: soClient, pkgName: FLEET_SERVER_PACKAGE, callCluster, }); - await ensureFleetServerIndicesCreated(esClient); - await runFleetServerMigration(); if (defaultFleetServerPolicyCreated) { await addPackageToAgentPolicy( @@ -187,21 +177,6 @@ async function updateFleetRoleIfExists(callCluster: CallESAsCurrentUser) { return putFleetRole(callCluster); } -async function ensureFleetServerIndicesCreated(esClient: ElasticsearchClient) { - await Promise.all( - FLEET_SERVER_INDICES.map(async (index) => { - const res = await esClient.indices.exists({ - index, - }); - if (res.statusCode === 404) { - await esClient.indices.create({ - index, - }); - } - }) - ); -} - async function putFleetRole(callCluster: CallESAsCurrentUser) { return callCluster('transport.request', { method: 'PUT', diff --git a/x-pack/plugins/fleet/tsconfig.json b/x-pack/plugins/fleet/tsconfig.json index 152fb2e132f62..e6dc206912c4b 100644 --- a/x-pack/plugins/fleet/tsconfig.json +++ b/x-pack/plugins/fleet/tsconfig.json @@ -5,13 +5,14 @@ "outDir": "./target/types", "emitDeclarationOnly": true, "declaration": true, - "declarationMap": true + "declarationMap": true, }, "include": [ // add all the folders containg files to be compiled "common/**/*", "public/**/*", "server/**/*", + "server/**/*.json", "scripts/**/*", "package.json", "../../typings/**/*" From 56f9647c359dded83ee340721bae3047a790009f Mon Sep 17 00:00:00 2001 From: Wylie Conlon Date: Tue, 16 Feb 2021 16:14:28 -0500 Subject: [PATCH 31/53] [Docs] Clarify KQL and Lucene docs (#91065) (#91531) * [Docs] Clarify KQL and Lucene docs * Code review suggestions --- docs/discover/kuery.asciidoc | 64 +++++++++++++++++++++-------------- docs/discover/search.asciidoc | 59 +++++++++++++++++++++----------- 2 files changed, 78 insertions(+), 45 deletions(-) diff --git a/docs/discover/kuery.asciidoc b/docs/discover/kuery.asciidoc index 8c0012fb6c6bf..a92fc182f388c 100644 --- a/docs/discover/kuery.asciidoc +++ b/docs/discover/kuery.asciidoc @@ -1,55 +1,63 @@ [[kuery-query]] === Kibana Query Language -The Kibana Query Language (KQL) makes it easy to find -the fields and syntax for your {es} query. If you have the -https://www.elastic.co/subscriptions[Basic tier] or above, -simply place your cursor in the *Search* field. As you type, you’ll get suggestions for fields, -values, and operators. +The Kibana Query Language (KQL) is a simple syntax for filtering {es} data using +free text search or field-based search. KQL is only used for filtering data, and has +no role in sorting or aggregating the data. + +KQL is able to suggest field names, values, and operators as you type. +The performance of the suggestions is controlled by <>: [role="screenshot"] image::images/kql-autocomplete.png[Autocomplete in Search bar] -If you prefer to use Kibana’s legacy query language, based on the -<>, click *KQL* next to the *Search* field, and then turn off KQL. +KQL has a different set of features than the <>. KQL is able to query +nested fields and <>. KQL does not support regular expressions +or searching with fuzzy terms. To use the legacy Lucene syntax, click *KQL* next to the *Search* field, +and then turn off KQL. [discrete] === Terms query -A terms query matches documents that contain one or more *exact* terms in a field. +A terms query uses *exact search terms*. Spaces separate each search term, and only one term +is required to match the document. Use quotation marks to indicate a *phrase match*. -To match documents where the response field is `200`: +To query using *exact search terms*, enter the field name followed by `:` and +then the values separated by spaces: [source,yaml] ------------------- -response:200 +http.response.status_code:400 401 404 ------------------- -To match documents with the phrase "quick brown fox" in the `message` field. +For text fields, this will match any value regardless of order: [source,yaml] ------------------- -message:"quick brown fox" +http.response.body.content.text:quick brown fox ------------------- -Without the quotes, -the query matches documents regardless of the order in which -they appear. Documents with "quick brown fox" match, -and so does "quick fox brown". +To query for an *exact phrase*, use quotation marks around the values: + +[source,yaml] +------------------- +http.response.body.content.text:"quick brown fox" +------------------- -NOTE: Terms without fields are matched against the default field in your index settings. -If a default field is not -set, terms are matched against all fields. For example, a query -for `response:200` searches for the value 200 -in the response field, but a query for just `200` searches for 200 -across all fields in your index. +Field names are not required by KQL. When a field name is not provided, terms +will be matched by the default fields in your index settings. To search across fields: +[source,yaml] +------------------- +"quick brown fox" +------------------- [discrete] === Boolean queries KQL supports `or`, `and`, and `not`. By default, `and` has a higher precedence than `or`. -To override the default precedence, group operators in parentheses. +To override the default precedence, group operators in parentheses. These operators can +be upper or lower case. To match documents where response is `200`, extension is `php`, or both: @@ -143,7 +151,7 @@ but in some cases you might need to search on dates. Include the date range in q [discrete] === Exist queries -An exist query matches documents that contain a value for a field, in this case, +An exist query matches documents that contain any value for a field, in this case, response: [source,yaml] @@ -151,10 +159,16 @@ response: response:* ------------------- +Existence is defined by {es} and includes all values, including empty text. + [discrete] === Wildcard queries -To match documents where machine.os starts with `win`, such +Wildcards queries can be used to *search by a term prefix* or to *search multiple fields*. +The default settings of {kib} *prevent leading wildcards* for performance reasons, +but this can be allowed with an <>. + +To match documents where `machine.os` starts with `win`, such as "windows 7" and "windows 10": [source,yaml] diff --git a/docs/discover/search.asciidoc b/docs/discover/search.asciidoc index 45f0df5bd773f..e8faccd50661a 100644 --- a/docs/discover/search.asciidoc +++ b/docs/discover/search.asciidoc @@ -53,36 +53,55 @@ include::kuery.asciidoc[] [[lucene-query]] === Lucene query syntax -Kibana's legacy query language was based on the Lucene query syntax. For the time being this syntax -is still available under the options menu in the Query Bar and in Advanced Settings. The following -are some tips that can help get you started. +Lucene query syntax is available to {kib} users who opt out of the <>. +Full documentation for this syntax is available as part of {es} +{ref}/query-dsl-query-string-query.html#query-string-syntax[query string syntax]. -* To perform a free text search, simply enter a text string. For example, if +The main reason to use the Lucene query syntax in {kib} is for advanced +Lucene features, such as regular expressions or fuzzy term matching. However, +Lucene syntax is not able to search nested objects or scripted fields. + +To perform a free text search, simply enter a text string. For example, if you're searching web server logs, you could enter `safari` to search all -fields for the term `safari`. +fields: + +[source,yaml] +------------------- +safari +------------------- + +To search for a value in a specific field, prefix the value with the name +of the field: -* To search for a value in a specific field, prefix the value with the name -of the field. For example, you could enter `status:200` to find all of -the entries that contain the value `200` in the `status` field. +[source,yaml] +------------------- +status:200 +------------------- -* To search for a range of values, you can use the bracketed range syntax, +To search for a range of values, use the bracketed range syntax, `[START_VALUE TO END_VALUE]`. For example, to find entries that have 4xx status codes, you could enter `status:[400 TO 499]`. -* To specify more complex search criteria, you can use the Boolean operators -`AND`, `OR`, and `NOT`. For example, to find entries that have 4xx status -codes and have an extension of `php` or `html`, you could enter `status:[400 TO -499] AND (extension:php OR extension:html)`. +[source,yaml] +------------------- +status:[400 TO 499] +------------------- + +For an open range, use a wildcard: -IMPORTANT: When you use the Lucene Query Syntax in the *KQL* search bar, {kib} is unable to search on nested objects and perform aggregations across fields that contain nested objects. -Using `include_in_parent` or `copy_to` as a workaround can cause {kib} to fail. +[source,yaml] +------------------- +status:[400 TO *] +------------------- -For more detailed information about the Lucene query syntax, see the -{ref}/query-dsl-query-string-query.html#query-string-syntax[Query String Query] -docs. +To specify more complex search criteria, use the boolean operators +`AND`, `OR`, and `NOT`. For example, to find entries that have 4xx status +codes and have an extension of `php` or `html`: -NOTE: These examples use the Lucene query syntax. When lucene is selected as your -query language you can also submit queries using the {ref}/query-dsl.html[Elasticsearch Query DSL]. +[source,yaml] +------------------- +status:[400 TO 499] AND (extension:php OR extension:html) +------------------- [[save-open-search]] From a5196fffc6150c47d8cfce17e3537424754f9862 Mon Sep 17 00:00:00 2001 From: Kevin Qualters <56408403+kqualters-elastic@users.noreply.github.com> Date: Tue, 16 Feb 2021 16:18:38 -0500 Subject: [PATCH 32/53] [Security Solution] Format large numbers in timeline kpis and display value in a tooltip (#91263) (#91544) * Format large numbers in timeline kpis and display actual value in a tooltip * Use more descriptive timelineId for additional test id Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../common/types/timeline/index.ts | 2 +- .../events_viewer/events_viewer.test.tsx | 2 +- .../components/flyout/header/index.test.tsx | 40 ++++++- .../components/flyout/header/kpis.tsx | 108 ++++++++++++------ 4 files changed, 110 insertions(+), 42 deletions(-) diff --git a/x-pack/plugins/security_solution/common/types/timeline/index.ts b/x-pack/plugins/security_solution/common/types/timeline/index.ts index cee8ccdea3e9e..58e3b9824d8fd 100644 --- a/x-pack/plugins/security_solution/common/types/timeline/index.ts +++ b/x-pack/plugins/security_solution/common/types/timeline/index.ts @@ -281,7 +281,7 @@ export enum TimelineId { active = 'timeline-1', casePage = 'timeline-case', test = 'test', // Reserved for testing purposes - test2 = 'test2', + alternateTest = 'alternateTest', } export const TimelineIdLiteralRt = runtimeTypes.union([ diff --git a/x-pack/plugins/security_solution/public/common/components/events_viewer/events_viewer.test.tsx b/x-pack/plugins/security_solution/public/common/components/events_viewer/events_viewer.test.tsx index a37528fcb24d7..3ecc17589fe08 100644 --- a/x-pack/plugins/security_solution/public/common/components/events_viewer/events_viewer.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/events_viewer/events_viewer.test.tsx @@ -201,7 +201,7 @@ describe('EventsViewer', () => { testProps = { ...testProps, // Update with a new id, to force columns back to default. - id: TimelineId.test2, + id: TimelineId.alternateTest, }; const wrapper = mount( diff --git a/x-pack/plugins/security_solution/public/timelines/components/flyout/header/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/flyout/header/index.test.tsx index 35dcd88b77e04..6713be176586c 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/flyout/header/index.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/flyout/header/index.test.tsx @@ -9,6 +9,7 @@ import React from 'react'; import { useKibana } from '../../../../common/lib/kibana'; import { TestProviders, mockIndexNames, mockIndexPattern } from '../../../../common/mock'; +import { TimelineId } from '../../../../../common/types/timeline'; import { useTimelineKpis } from '../../../containers/kpis'; import { FlyoutHeader } from '.'; import { useSourcererScope } from '../../../../common/containers/sourcerer'; @@ -33,6 +34,14 @@ const mockUseTimelineKpiResponse = { hostCount: 1, destinationIpCount: 1, }; + +const mockUseTimelineLargeKpiResponse = { + processCount: 1000, + userCount: 1000000, + sourceIpCount: 1000000000, + hostCount: 999, + destinationIpCount: 1, +}; const defaultMocks = { browserFields: mockBrowserFields, docValueFields: mockDocValueFields, @@ -65,7 +74,7 @@ describe('Timeline KPIs', () => { it('renders the component, labels and values succesfully', async () => { const wrapper = mount( - + ); expect(wrapper.find('[data-test-subj="siem-timeline-kpis"]').exists()).toEqual(true); @@ -87,7 +96,7 @@ describe('Timeline KPIs', () => { it('renders a loading indicator for values', async () => { const wrapper = mount( - + ); expect(wrapper.find('[data-test-subj="siem-timeline-process-kpi"]').first().text()).toEqual( @@ -103,7 +112,7 @@ describe('Timeline KPIs', () => { it('renders labels and the default empty string', async () => { const wrapper = mount( - + ); @@ -115,4 +124,29 @@ describe('Timeline KPIs', () => { ); }); }); + + describe('when the response contains numbers larger than one thousand', () => { + beforeEach(() => { + mockUseTimelineKpis.mockReturnValue([false, mockUseTimelineLargeKpiResponse]); + }); + it('formats the numbers correctly', async () => { + const wrapper = mount( + + + + ); + expect(wrapper.find('[data-test-subj="siem-timeline-process-kpi"]').first().text()).toEqual( + expect.stringContaining('1k') + ); + expect(wrapper.find('[data-test-subj="siem-timeline-user-kpi"]').first().text()).toEqual( + expect.stringContaining('1m') + ); + expect(wrapper.find('[data-test-subj="siem-timeline-source-ip-kpi"]').first().text()).toEqual( + expect.stringContaining('1b') + ); + expect(wrapper.find('[data-test-subj="siem-timeline-host-kpi"]').first().text()).toEqual( + expect.stringContaining('999') + ); + }); + }); }); diff --git a/x-pack/plugins/security_solution/public/timelines/components/flyout/header/kpis.tsx b/x-pack/plugins/security_solution/public/timelines/components/flyout/header/kpis.tsx index 3b0a86432aa96..e487fe70fdc94 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/flyout/header/kpis.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/flyout/header/kpis.tsx @@ -5,61 +5,95 @@ * 2.0. */ -import React from 'react'; +import React, { useMemo } from 'react'; -import { EuiStat, EuiFlexItem, EuiFlexGroup } from '@elastic/eui'; +import { EuiStat, EuiFlexItem, EuiFlexGroup, EuiToolTip } from '@elastic/eui'; +import numeral from '@elastic/numeral'; +import { DEFAULT_NUMBER_FORMAT } from '../../../../../common/constants'; +import { useUiSetting$ } from '../../../../common/lib/kibana'; import { TimelineKpiStrategyResponse } from '../../../../../common/search_strategy'; import { getEmptyValue } from '../../../../common/components/empty_value'; import * as i18n from './translations'; export const TimelineKPIs = React.memo( ({ kpis, isLoading }: { kpis: TimelineKpiStrategyResponse | null; isLoading: boolean }) => { + const kpiFormat = '0,0.[000]a'; + const [defaultNumberFormat] = useUiSetting$(DEFAULT_NUMBER_FORMAT); + const formattedKpis = useMemo(() => { + return { + process: kpis === null ? getEmptyValue() : numeral(kpis.processCount).format(kpiFormat), + user: kpis === null ? getEmptyValue() : numeral(kpis.userCount).format(kpiFormat), + host: kpis === null ? getEmptyValue() : numeral(kpis.hostCount).format(kpiFormat), + sourceIp: kpis === null ? getEmptyValue() : numeral(kpis.sourceIpCount).format(kpiFormat), + destinationIp: + kpis === null ? getEmptyValue() : numeral(kpis.destinationIpCount).format(kpiFormat), + }; + }, [kpis]); + const formattedKpiToolTips = useMemo(() => { + return { + process: numeral(kpis?.processCount).format(defaultNumberFormat), + user: numeral(kpis?.userCount).format(defaultNumberFormat), + host: numeral(kpis?.hostCount).format(defaultNumberFormat), + sourceIp: numeral(kpis?.sourceIpCount).format(defaultNumberFormat), + destinationIp: numeral(kpis?.destinationIpCount).format(defaultNumberFormat), + }; + }, [kpis, defaultNumberFormat]); return ( - + + + - + + + - + + + - + + + - + + + ); From 554eb2e15d04266c8f2c259f544012a4089e5aef Mon Sep 17 00:00:00 2001 From: Kevin Logan <56395104+kevinlog@users.noreply.github.com> Date: Tue, 16 Feb 2021 16:41:03 -0500 Subject: [PATCH 33/53] [Security Solution] add unsupported type to Endpoint Policy response (#91295) (#91537) * add unsupported type * update types Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../security_solution/common/endpoint/generate_data.ts | 4 +++- .../plugins/security_solution/common/endpoint/types/index.ts | 1 + .../search_strategy/security_solution/hosts/common/index.ts | 1 + x-pack/plugins/security_solution/public/graphql/types.ts | 1 + .../management/pages/endpoint_hosts/view/host_constants.ts | 4 ++++ .../security_solution/server/graphql/hosts/schema.gql.ts | 1 + x-pack/plugins/security_solution/server/graphql/types.ts | 1 + 7 files changed, 12 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/security_solution/common/endpoint/generate_data.ts b/x-pack/plugins/security_solution/common/endpoint/generate_data.ts index ffeaf853828f1..8aec9768dd50d 100644 --- a/x-pack/plugins/security_solution/common/endpoint/generate_data.ts +++ b/x-pack/plugins/security_solution/common/endpoint/generate_data.ts @@ -101,6 +101,7 @@ const POLICY_RESPONSE_STATUSES: HostPolicyResponseActionStatus[] = [ HostPolicyResponseActionStatus.success, HostPolicyResponseActionStatus.failure, HostPolicyResponseActionStatus.warning, + HostPolicyResponseActionStatus.unsupported, ]; const APPLIED_POLICIES: Array<{ @@ -1492,7 +1493,7 @@ export class EndpointDocGenerator { { name: 'workflow', message: 'Failed to apply a portion of the configuration (kernel)', - status: HostPolicyResponseActionStatus.success, + status: HostPolicyResponseActionStatus.unsupported, }, { name: 'download_model', @@ -1637,6 +1638,7 @@ export class EndpointDocGenerator { HostPolicyResponseActionStatus.failure, HostPolicyResponseActionStatus.success, HostPolicyResponseActionStatus.warning, + HostPolicyResponseActionStatus.unsupported, ]); } diff --git a/x-pack/plugins/security_solution/common/endpoint/types/index.ts b/x-pack/plugins/security_solution/common/endpoint/types/index.ts index d361c0d6282a3..94a09b385a08c 100644 --- a/x-pack/plugins/security_solution/common/endpoint/types/index.ts +++ b/x-pack/plugins/security_solution/common/endpoint/types/index.ts @@ -933,6 +933,7 @@ export enum HostPolicyResponseActionStatus { success = 'success', failure = 'failure', warning = 'warning', + unsupported = 'unsupported', } /** diff --git a/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/common/index.ts b/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/common/index.ts index 40e353263bcc8..7e19944ea5856 100644 --- a/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/common/index.ts +++ b/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/common/index.ts @@ -13,6 +13,7 @@ export enum HostPolicyResponseActionStatus { success = 'success', failure = 'failure', warning = 'warning', + unsupported = 'unsupported', } export enum HostsFields { diff --git a/x-pack/plugins/security_solution/public/graphql/types.ts b/x-pack/plugins/security_solution/public/graphql/types.ts index 4397573217312..f70cd37b8da94 100644 --- a/x-pack/plugins/security_solution/public/graphql/types.ts +++ b/x-pack/plugins/security_solution/public/graphql/types.ts @@ -273,6 +273,7 @@ export enum HostPolicyResponseActionStatus { success = 'success', failure = 'failure', warning = 'warning', + unsupported = 'unsupported', } export enum TimelineType { diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/host_constants.ts b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/host_constants.ts index 37a26d8805352..4745cd9de249d 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/host_constants.ts +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/host_constants.ts @@ -25,6 +25,7 @@ export const POLICY_STATUS_TO_HEALTH_COLOR = Object.freeze< success: 'success', warning: 'warning', failure: 'danger', + unsupported: 'subdued', }); export const POLICY_STATUS_TO_TEXT = Object.freeze< @@ -39,4 +40,7 @@ export const POLICY_STATUS_TO_TEXT = Object.freeze< failure: i18n.translate('xpack.securitySolution.policyStatusText.failure', { defaultMessage: 'Failure', }), + unsupported: i18n.translate('xpack.securitySolution.policyStatusText.unsupported', { + defaultMessage: 'Unsupported', + }), }); diff --git a/x-pack/plugins/security_solution/server/graphql/hosts/schema.gql.ts b/x-pack/plugins/security_solution/server/graphql/hosts/schema.gql.ts index f3d553936bac5..c3a5c4e3b23cf 100644 --- a/x-pack/plugins/security_solution/server/graphql/hosts/schema.gql.ts +++ b/x-pack/plugins/security_solution/server/graphql/hosts/schema.gql.ts @@ -50,6 +50,7 @@ export const hostsSchema = gql` success failure warning + unsupported } type EndpointFields { diff --git a/x-pack/plugins/security_solution/server/graphql/types.ts b/x-pack/plugins/security_solution/server/graphql/types.ts index d1e646e73cf12..0d6a0e63455b0 100644 --- a/x-pack/plugins/security_solution/server/graphql/types.ts +++ b/x-pack/plugins/security_solution/server/graphql/types.ts @@ -275,6 +275,7 @@ export enum HostPolicyResponseActionStatus { success = 'success', failure = 'failure', warning = 'warning', + unsupported = 'unsupported', } export enum TimelineType { From 9d50e776d08483830e131e623227fd1b132374cb Mon Sep 17 00:00:00 2001 From: Paul Tavares <56442535+paul-tavares@users.noreply.github.com> Date: Tue, 16 Feb 2021 16:45:10 -0500 Subject: [PATCH 34/53] [Security Solution][Artifacts] implemented policy specific trusted apps support in the manifest manager (#90991) (#91549) * Implemented policy specific trusted apps support in the manifest manager. Co-authored-by: Bohdan Tsymbala --- .../fleet/common/types/rest_spec/common.ts | 7 + x-pack/plugins/fleet/server/mocks.ts | 1 + .../routes/package_policy/handlers.test.ts | 1 + .../fleet/server/services/package_policy.ts | 27 +- x-pack/plugins/lists/common/shared_exports.ts | 2 +- .../server/saved_objects/migrations.test.ts | 249 +++-- .../lists/server/saved_objects/migrations.ts | 24 + .../common/shared_imports.ts | 1 + .../endpoint/lib/artifacts/lists.test.ts | 918 ++++++++++-------- .../server/endpoint/lib/artifacts/lists.ts | 40 +- .../routes/trusted_apps/mapping.test.ts | 4 +- .../endpoint/routes/trusted_apps/mapping.ts | 4 +- .../endpoint/routes/trusted_apps/service.ts | 2 +- .../schemas/artifacts/saved_objects.mock.ts | 6 +- .../manifest_manager/manifest_manager.mock.ts | 21 +- .../manifest_manager/manifest_manager.test.ts | 139 ++- .../manifest_manager/manifest_manager.ts | 273 +++--- 17 files changed, 1084 insertions(+), 635 deletions(-) diff --git a/x-pack/plugins/fleet/common/types/rest_spec/common.ts b/x-pack/plugins/fleet/common/types/rest_spec/common.ts index d03129efd8fad..de5e87d2e59a5 100644 --- a/x-pack/plugins/fleet/common/types/rest_spec/common.ts +++ b/x-pack/plugins/fleet/common/types/rest_spec/common.ts @@ -14,3 +14,10 @@ export interface ListWithKuery extends HttpFetchQuery { sortOrder?: 'desc' | 'asc'; kuery?: string; } + +export interface ListResult { + items: T[]; + total: number; + page: number; + perPage: number; +} diff --git a/x-pack/plugins/fleet/server/mocks.ts b/x-pack/plugins/fleet/server/mocks.ts index c650995c809cb..430e38bd1bc3e 100644 --- a/x-pack/plugins/fleet/server/mocks.ts +++ b/x-pack/plugins/fleet/server/mocks.ts @@ -53,6 +53,7 @@ export const createPackagePolicyServiceMock = () => { get: jest.fn(), getByIDs: jest.fn(), list: jest.fn(), + listIds: jest.fn(), update: jest.fn(), runExternalCallbacks: jest.fn(), } as jest.Mocked; diff --git a/x-pack/plugins/fleet/server/routes/package_policy/handlers.test.ts b/x-pack/plugins/fleet/server/routes/package_policy/handlers.test.ts index 2b44975cc3b4d..813279f2a800f 100644 --- a/x-pack/plugins/fleet/server/routes/package_policy/handlers.test.ts +++ b/x-pack/plugins/fleet/server/routes/package_policy/handlers.test.ts @@ -47,6 +47,7 @@ jest.mock('../../services/package_policy', (): { get: jest.fn(), getByIDs: jest.fn(), list: jest.fn(), + listIds: jest.fn(), update: jest.fn(), runExternalCallbacks: jest.fn((callbackType, newPackagePolicy, context, request) => Promise.resolve(newPackagePolicy) diff --git a/x-pack/plugins/fleet/server/services/package_policy.ts b/x-pack/plugins/fleet/server/services/package_policy.ts index a882ceb0037f2..335cd7c956faf 100644 --- a/x-pack/plugins/fleet/server/services/package_policy.ts +++ b/x-pack/plugins/fleet/server/services/package_policy.ts @@ -20,6 +20,7 @@ import { PackagePolicyInputStream, PackageInfo, ListWithKuery, + ListResult, packageToPackagePolicy, isPackageLimited, doesAgentPolicyAlreadyIncludePackage, @@ -248,7 +249,7 @@ class PackagePolicyService { public async list( soClient: SavedObjectsClientContract, options: ListWithKuery - ): Promise<{ items: PackagePolicy[]; total: number; page: number; perPage: number }> { + ): Promise> { const { page = 1, perPage = 20, sortField = 'updated_at', sortOrder = 'desc', kuery } = options; const packagePolicies = await soClient.find({ @@ -272,6 +273,30 @@ class PackagePolicyService { }; } + public async listIds( + soClient: SavedObjectsClientContract, + options: ListWithKuery + ): Promise> { + const { page = 1, perPage = 20, sortField = 'updated_at', sortOrder = 'desc', kuery } = options; + + const packagePolicies = await soClient.find<{}>({ + type: SAVED_OBJECT_TYPE, + sortField, + sortOrder, + page, + perPage, + fields: [], + filter: kuery ? normalizeKuery(SAVED_OBJECT_TYPE, kuery) : undefined, + }); + + return { + items: packagePolicies.saved_objects.map((packagePolicySO) => packagePolicySO.id), + total: packagePolicies.total, + page, + perPage, + }; + } + public async update( soClient: SavedObjectsClientContract, esClient: ElasticsearchClient, diff --git a/x-pack/plugins/lists/common/shared_exports.ts b/x-pack/plugins/lists/common/shared_exports.ts index ba5891092fe12..6dcda5d1f8c24 100644 --- a/x-pack/plugins/lists/common/shared_exports.ts +++ b/x-pack/plugins/lists/common/shared_exports.ts @@ -47,4 +47,4 @@ export { OsTypeArray, } from './schemas'; -export { ENDPOINT_LIST_ID } from './constants'; +export { ENDPOINT_LIST_ID, ENDPOINT_TRUSTED_APPS_LIST_ID } from './constants'; diff --git a/x-pack/plugins/lists/server/saved_objects/migrations.test.ts b/x-pack/plugins/lists/server/saved_objects/migrations.test.ts index 143443b932092..f71109b9bb85d 100644 --- a/x-pack/plugins/lists/server/saved_objects/migrations.test.ts +++ b/x-pack/plugins/lists/server/saved_objects/migrations.test.ts @@ -6,61 +6,102 @@ */ import { SavedObjectUnsanitizedDoc } from 'kibana/server'; +import uuid from 'uuid'; -import { ENDPOINT_LIST_ID } from '../../common/constants'; +import { ENDPOINT_LIST_ID, ENDPOINT_TRUSTED_APPS_LIST_ID } from '../../common/constants'; +import { ExceptionListSoSchema } from '../../common/schemas/saved_objects'; import { OldExceptionListSoSchema, migrations } from './migrations'; +const DEFAULT_EXCEPTION_LIST_SO: ExceptionListSoSchema = { + comments: undefined, + created_at: '2020-06-09T20:18:20.349Z', + created_by: 'user', + description: 'description', + entries: undefined, + immutable: false, + item_id: undefined, + list_id: 'some_list', + list_type: 'list', + meta: undefined, + name: 'name', + os_types: [], + tags: [], + tie_breaker_id: uuid.v4(), + type: 'endpoint', + updated_by: 'user', + version: undefined, +}; + +const DEFAULT_OLD_EXCEPTION_LIST_SO: OldExceptionListSoSchema = { + ...DEFAULT_EXCEPTION_LIST_SO, + _tags: [], +}; + +const createOldExceptionListSoSchemaSavedObject = ( + attributes: Partial +): SavedObjectUnsanitizedDoc => ({ + attributes: { ...DEFAULT_OLD_EXCEPTION_LIST_SO, ...attributes }, + id: 'abcd', + migrationVersion: {}, + references: [], + type: 'so-type', + updated_at: '2020-06-09T20:18:20.349Z', +}); + +const createExceptionListSoSchemaSavedObject = ( + attributes: Partial +): SavedObjectUnsanitizedDoc => ({ + attributes: { ...DEFAULT_EXCEPTION_LIST_SO, ...attributes }, + id: 'abcd', + migrationVersion: {}, + references: [], + type: 'so-type', + updated_at: '2020-06-09T20:18:20.349Z', +}); + describe('7.10.0 lists migrations', () => { const migration = migrations['7.10.0']; test('properly converts .text fields to .caseless', () => { - const doc = { - attributes: { - entries: [ - { - field: 'file.path.text', - operator: 'included', - type: 'match', - value: 'C:\\Windows\\explorer.exe', - }, - { - field: 'host.os.name', - operator: 'included', - type: 'match', - value: 'my-host', - }, - { - entries: [ - { - field: 'process.command_line.text', - operator: 'included', - type: 'match', - value: '/usr/bin/bash', - }, - { - field: 'process.parent.command_line.text', - operator: 'included', - type: 'match', - value: '/usr/bin/bash', - }, - ], - field: 'nested.field', - type: 'nested', - }, - ], - list_id: ENDPOINT_LIST_ID, - }, - id: 'abcd', - migrationVersion: {}, - references: [], - type: 'so-type', - updated_at: '2020-06-09T20:18:20.349Z', - }; - expect( - migration((doc as unknown) as SavedObjectUnsanitizedDoc) - ).toEqual({ - attributes: { + const doc = createOldExceptionListSoSchemaSavedObject({ + entries: [ + { + field: 'file.path.text', + operator: 'included', + type: 'match', + value: 'C:\\Windows\\explorer.exe', + }, + { + field: 'host.os.name', + operator: 'included', + type: 'match', + value: 'my-host', + }, + { + entries: [ + { + field: 'process.command_line.text', + operator: 'included', + type: 'match', + value: '/usr/bin/bash', + }, + { + field: 'process.parent.command_line.text', + operator: 'included', + type: 'match', + value: '/usr/bin/bash', + }, + ], + field: 'nested.field', + type: 'nested', + }, + ], + list_id: ENDPOINT_LIST_ID, + }); + + expect(migration(doc)).toEqual( + createOldExceptionListSoSchemaSavedObject({ entries: [ { field: 'file.path.caseless', @@ -94,40 +135,98 @@ describe('7.10.0 lists migrations', () => { }, ], list_id: ENDPOINT_LIST_ID, - }, - id: 'abcd', - migrationVersion: {}, - references: [], - type: 'so-type', - updated_at: '2020-06-09T20:18:20.349Z', - }); + }) + ); }); test('properly copies os tags to os_types', () => { - const doc = { - attributes: { - _tags: ['1234', 'os:windows'], - comments: [], - }, - id: 'abcd', - migrationVersion: {}, - references: [], - type: 'so-type', - updated_at: '2020-06-09T20:18:20.349Z', - }; - expect( - migration((doc as unknown) as SavedObjectUnsanitizedDoc) - ).toEqual({ - attributes: { + const doc = createOldExceptionListSoSchemaSavedObject({ + _tags: ['1234', 'os:windows'], + comments: [], + }); + + expect(migration(doc)).toEqual( + createOldExceptionListSoSchemaSavedObject({ _tags: ['1234', 'os:windows'], comments: [], os_types: ['windows'], - }, - id: 'abcd', - migrationVersion: {}, - references: [], - type: 'so-type', - updated_at: '2020-06-09T20:18:20.349Z', + }) + ); + }); +}); + +describe('7.12.0 lists migrations', () => { + const migration = migrations['7.12.0']; + + test('should not convert non trusted apps lists', () => { + const doc = createExceptionListSoSchemaSavedObject({ list_id: ENDPOINT_LIST_ID, tags: [] }); + + expect(migration(doc)).toEqual( + createExceptionListSoSchemaSavedObject({ + list_id: ENDPOINT_LIST_ID, + tags: [], + tie_breaker_id: expect.anything(), + }) + ); + }); + + test('converts empty tags to contain list containing "policy:all" tag', () => { + const doc = createExceptionListSoSchemaSavedObject({ + list_id: ENDPOINT_TRUSTED_APPS_LIST_ID, + tags: [], + }); + + expect(migration(doc)).toEqual( + createExceptionListSoSchemaSavedObject({ + list_id: ENDPOINT_TRUSTED_APPS_LIST_ID, + tags: ['policy:all'], + tie_breaker_id: expect.anything(), + }) + ); + }); + + test('preserves existing non policy related tags', () => { + const doc = createExceptionListSoSchemaSavedObject({ + list_id: ENDPOINT_TRUSTED_APPS_LIST_ID, + tags: ['tag1', 'tag2'], + }); + + expect(migration(doc)).toEqual( + createExceptionListSoSchemaSavedObject({ + list_id: ENDPOINT_TRUSTED_APPS_LIST_ID, + tags: ['tag1', 'tag2', 'policy:all'], + tie_breaker_id: expect.anything(), + }) + ); + }); + + test('preserves existing "policy:all" tag and does not add another one', () => { + const doc = createExceptionListSoSchemaSavedObject({ + list_id: ENDPOINT_TRUSTED_APPS_LIST_ID, + tags: ['policy:all', 'tag1', 'tag2'], + }); + + expect(migration(doc)).toEqual( + createExceptionListSoSchemaSavedObject({ + list_id: ENDPOINT_TRUSTED_APPS_LIST_ID, + tags: ['policy:all', 'tag1', 'tag2'], + tie_breaker_id: expect.anything(), + }) + ); + }); + + test('preserves existing policy reference tag and does not add "policy:all" tag', () => { + const doc = createExceptionListSoSchemaSavedObject({ + list_id: ENDPOINT_TRUSTED_APPS_LIST_ID, + tags: ['policy:056d2d4645421fb92e5cd39f33d70856', 'tag1', 'tag2'], }); + + expect(migration(doc)).toEqual( + createExceptionListSoSchemaSavedObject({ + list_id: ENDPOINT_TRUSTED_APPS_LIST_ID, + tags: ['policy:056d2d4645421fb92e5cd39f33d70856', 'tag1', 'tag2'], + tie_breaker_id: expect.anything(), + }) + ); }); }); diff --git a/x-pack/plugins/lists/server/saved_objects/migrations.ts b/x-pack/plugins/lists/server/saved_objects/migrations.ts index 43faa7a5e8fb6..2fa19a6810a8a 100644 --- a/x-pack/plugins/lists/server/saved_objects/migrations.ts +++ b/x-pack/plugins/lists/server/saved_objects/migrations.ts @@ -40,6 +40,9 @@ const reduceOsTypes = (acc: string[], tag: string): string[] => { return [...acc]; }; +const containsPolicyTags = (tags: string[]): boolean => + tags.some((tag) => tag.startsWith('policy:')); + export type OldExceptionListSoSchema = ExceptionListSoSchema & { _tags: string[]; }; @@ -64,4 +67,25 @@ export const migrations = { }, references: doc.references || [], }), + '7.12.0': ( + doc: SavedObjectUnsanitizedDoc + ): SavedObjectSanitizedDoc => { + if (doc.attributes.list_id === ENDPOINT_TRUSTED_APPS_LIST_ID) { + return { + ...doc, + ...{ + attributes: { + ...doc.attributes, + tags: [ + ...(doc.attributes.tags || []), + ...(containsPolicyTags(doc.attributes.tags) ? [] : ['policy:all']), + ], + }, + }, + references: doc.references || [], + }; + } else { + return { ...doc, references: doc.references || [] }; + } + }, }; diff --git a/x-pack/plugins/security_solution/common/shared_imports.ts b/x-pack/plugins/security_solution/common/shared_imports.ts index d6ec668e1b0f9..988f0ad0c125d 100644 --- a/x-pack/plugins/security_solution/common/shared_imports.ts +++ b/x-pack/plugins/security_solution/common/shared_imports.ts @@ -43,6 +43,7 @@ export { ExceptionListType, Type, ENDPOINT_LIST_ID, + ENDPOINT_TRUSTED_APPS_LIST_ID, osTypeArray, OsTypeArray, } from '../../lists/common'; diff --git a/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/lists.test.ts b/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/lists.test.ts index 2833a5ad24f2a..88bf7941c8464 100644 --- a/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/lists.test.ts +++ b/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/lists.test.ts @@ -10,12 +10,17 @@ import { listMock } from '../../../../../lists/server/mocks'; import { getFoundExceptionListItemSchemaMock } from '../../../../../lists/common/schemas/response/found_exception_list_item_schema.mock'; import { getExceptionListItemSchemaMock } from '../../../../../lists/common/schemas/response/exception_list_item_schema.mock'; import { EntriesArray, EntryList } from '../../../../../lists/common/schemas/types'; -import { buildArtifact, getFullEndpointExceptionList } from './lists'; +import { + buildArtifact, + getEndpointExceptionList, + getEndpointTrustedAppsList, + getFilteredEndpointExceptionList, +} from './lists'; import { TranslatedEntry, TranslatedExceptionListItem } from '../../schemas/artifacts'; import { ArtifactConstants } from './common'; -import { ENDPOINT_LIST_ID } from '../../../../../lists/common'; +import { ENDPOINT_LIST_ID, ENDPOINT_TRUSTED_APPS_LIST_ID } from '../../../../../lists/common'; -describe('buildEventTypeSignal', () => { +describe('artifacts lists', () => { let mockExceptionClient: ExceptionListClient; beforeEach(() => { @@ -23,214 +28,384 @@ describe('buildEventTypeSignal', () => { mockExceptionClient = listMock.getExceptionListClient(); }); - test('it should convert the exception lists response to the proper endpoint format', async () => { - const expectedEndpointExceptions = { - type: 'simple', - entries: [ - { - entries: [ - { - field: 'nested.field', - operator: 'included', - type: 'exact_cased', - value: 'some value', - }, - ], - field: 'some.parentField', - type: 'nested', - }, - { - field: 'some.not.nested.field', - operator: 'included', - type: 'exact_cased', - value: 'some value', - }, - ], - }; - - const first = getFoundExceptionListItemSchemaMock(); - mockExceptionClient.findExceptionListItem = jest.fn().mockReturnValueOnce(first); - const resp = await getFullEndpointExceptionList( - mockExceptionClient, - 'linux', - 'v1', - ENDPOINT_LIST_ID - ); - expect(resp).toEqual({ - entries: [expectedEndpointExceptions], + describe('getFilteredEndpointExceptionList', () => { + const TEST_FILTER = 'exception-list-agnostic.attributes.os_types:"linux"'; + + test('it should convert the exception lists response to the proper endpoint format', async () => { + const expectedEndpointExceptions = { + type: 'simple', + entries: [ + { + entries: [ + { + field: 'nested.field', + operator: 'included', + type: 'exact_cased', + value: 'some value', + }, + ], + field: 'some.parentField', + type: 'nested', + }, + { + field: 'some.not.nested.field', + operator: 'included', + type: 'exact_cased', + value: 'some value', + }, + ], + }; + + const first = getFoundExceptionListItemSchemaMock(); + mockExceptionClient.findExceptionListItem = jest.fn().mockReturnValueOnce(first); + const resp = await getFilteredEndpointExceptionList( + mockExceptionClient, + 'v1', + TEST_FILTER, + ENDPOINT_LIST_ID + ); + expect(resp).toEqual({ + entries: [expectedEndpointExceptions], + }); }); - }); - test('it should convert simple fields', async () => { - const testEntries: EntriesArray = [ - { field: 'host.os.full', operator: 'included', type: 'match', value: 'windows' }, - { field: 'server.ip', operator: 'included', type: 'match', value: '192.168.1.1' }, - { field: 'host.hostname', operator: 'included', type: 'match', value: 'estc' }, - ]; + test('it should convert simple fields', async () => { + const testEntries: EntriesArray = [ + { field: 'host.os.full', operator: 'included', type: 'match', value: 'windows' }, + { field: 'server.ip', operator: 'included', type: 'match', value: '192.168.1.1' }, + { field: 'host.hostname', operator: 'included', type: 'match', value: 'estc' }, + ]; - const expectedEndpointExceptions = { - type: 'simple', - entries: [ - { - field: 'host.os.full', - operator: 'included', - type: 'exact_cased', - value: 'windows', - }, + const expectedEndpointExceptions = { + type: 'simple', + entries: [ + { + field: 'host.os.full', + operator: 'included', + type: 'exact_cased', + value: 'windows', + }, + { + field: 'server.ip', + operator: 'included', + type: 'exact_cased', + value: '192.168.1.1', + }, + { + field: 'host.hostname', + operator: 'included', + type: 'exact_cased', + value: 'estc', + }, + ], + }; + + const first = getFoundExceptionListItemSchemaMock(); + first.data[0].entries = testEntries; + mockExceptionClient.findExceptionListItem = jest.fn().mockReturnValueOnce(first); + + const resp = await getFilteredEndpointExceptionList( + mockExceptionClient, + 'v1', + TEST_FILTER, + ENDPOINT_LIST_ID + ); + expect(resp).toEqual({ + entries: [expectedEndpointExceptions], + }); + }); + + test('it should convert fields case sensitive', async () => { + const testEntries: EntriesArray = [ + { field: 'host.os.full.caseless', operator: 'included', type: 'match', value: 'windows' }, + { field: 'server.ip', operator: 'included', type: 'match', value: '192.168.1.1' }, { - field: 'server.ip', + field: 'host.hostname.caseless', operator: 'included', - type: 'exact_cased', - value: '192.168.1.1', + type: 'match_any', + value: ['estc', 'kibana'], }, + ]; + + const expectedEndpointExceptions = { + type: 'simple', + entries: [ + { + field: 'host.os.full', + operator: 'included', + type: 'exact_caseless', + value: 'windows', + }, + { + field: 'server.ip', + operator: 'included', + type: 'exact_cased', + value: '192.168.1.1', + }, + { + field: 'host.hostname', + operator: 'included', + type: 'exact_caseless_any', + value: ['estc', 'kibana'], + }, + ], + }; + + const first = getFoundExceptionListItemSchemaMock(); + first.data[0].entries = testEntries; + mockExceptionClient.findExceptionListItem = jest.fn().mockReturnValueOnce(first); + + const resp = await getFilteredEndpointExceptionList( + mockExceptionClient, + 'v1', + TEST_FILTER, + ENDPOINT_LIST_ID + ); + expect(resp).toEqual({ + entries: [expectedEndpointExceptions], + }); + }); + + test('it should deduplicate exception entries', async () => { + const testEntries: EntriesArray = [ + { field: 'host.os.full.caseless', operator: 'included', type: 'match', value: 'windows' }, + { field: 'host.os.full.caseless', operator: 'included', type: 'match', value: 'windows' }, + { field: 'host.os.full.caseless', operator: 'included', type: 'match', value: 'windows' }, + { field: 'server.ip', operator: 'included', type: 'match', value: '192.168.1.1' }, { field: 'host.hostname', operator: 'included', - type: 'exact_cased', - value: 'estc', + type: 'match_any', + value: ['estc', 'kibana'], }, - ], - }; - - const first = getFoundExceptionListItemSchemaMock(); - first.data[0].entries = testEntries; - mockExceptionClient.findExceptionListItem = jest.fn().mockReturnValueOnce(first); - - const resp = await getFullEndpointExceptionList( - mockExceptionClient, - 'linux', - 'v1', - ENDPOINT_LIST_ID - ); - expect(resp).toEqual({ - entries: [expectedEndpointExceptions], - }); - }); + ]; - test('it should convert fields case sensitive', async () => { - const testEntries: EntriesArray = [ - { field: 'host.os.full.caseless', operator: 'included', type: 'match', value: 'windows' }, - { field: 'server.ip', operator: 'included', type: 'match', value: '192.168.1.1' }, - { - field: 'host.hostname.caseless', - operator: 'included', - type: 'match_any', - value: ['estc', 'kibana'], - }, - ]; + const expectedEndpointExceptions = { + type: 'simple', + entries: [ + { + field: 'host.os.full', + operator: 'included', + type: 'exact_caseless', + value: 'windows', + }, + { + field: 'server.ip', + operator: 'included', + type: 'exact_cased', + value: '192.168.1.1', + }, + { + field: 'host.hostname', + operator: 'included', + type: 'exact_cased_any', + value: ['estc', 'kibana'], + }, + ], + }; + + const first = getFoundExceptionListItemSchemaMock(); + first.data[0].entries = testEntries; + mockExceptionClient.findExceptionListItem = jest.fn().mockReturnValueOnce(first); + + const resp = await getFilteredEndpointExceptionList( + mockExceptionClient, + 'v1', + TEST_FILTER, + ENDPOINT_LIST_ID + ); + expect(resp).toEqual({ + entries: [expectedEndpointExceptions], + }); + }); - const expectedEndpointExceptions = { - type: 'simple', - entries: [ - { - field: 'host.os.full', - operator: 'included', - type: 'exact_caseless', - value: 'windows', - }, - { - field: 'server.ip', - operator: 'included', - type: 'exact_cased', - value: '192.168.1.1', - }, + test('it should not deduplicate exception entries across nested boundaries', async () => { + const testEntries: EntriesArray = [ { - field: 'host.hostname', - operator: 'included', - type: 'exact_caseless_any', - value: ['estc', 'kibana'], + entries: [ + { field: 'nested.field', operator: 'included', type: 'match', value: 'some value' }, + ], + field: 'some.parentField', + type: 'nested', }, - ], - }; - - const first = getFoundExceptionListItemSchemaMock(); - first.data[0].entries = testEntries; - mockExceptionClient.findExceptionListItem = jest.fn().mockReturnValueOnce(first); - - const resp = await getFullEndpointExceptionList( - mockExceptionClient, - 'linux', - 'v1', - ENDPOINT_LIST_ID - ); - expect(resp).toEqual({ - entries: [expectedEndpointExceptions], + // Same as above but not inside the nest + { field: 'nested.field', operator: 'included', type: 'match', value: 'some value' }, + ]; + + const expectedEndpointExceptions = { + type: 'simple', + entries: [ + { + entries: [ + { + field: 'nested.field', + operator: 'included', + type: 'exact_cased', + value: 'some value', + }, + ], + field: 'some.parentField', + type: 'nested', + }, + { + field: 'nested.field', + operator: 'included', + type: 'exact_cased', + value: 'some value', + }, + ], + }; + + const first = getFoundExceptionListItemSchemaMock(); + first.data[0].entries = testEntries; + mockExceptionClient.findExceptionListItem = jest.fn().mockReturnValueOnce(first); + + const resp = await getFilteredEndpointExceptionList( + mockExceptionClient, + 'v1', + TEST_FILTER, + ENDPOINT_LIST_ID + ); + expect(resp).toEqual({ + entries: [expectedEndpointExceptions], + }); }); - }); - test('it should deduplicate exception entries', async () => { - const testEntries: EntriesArray = [ - { field: 'host.os.full.caseless', operator: 'included', type: 'match', value: 'windows' }, - { field: 'host.os.full.caseless', operator: 'included', type: 'match', value: 'windows' }, - { field: 'host.os.full.caseless', operator: 'included', type: 'match', value: 'windows' }, - { field: 'server.ip', operator: 'included', type: 'match', value: '192.168.1.1' }, - { - field: 'host.hostname', - operator: 'included', - type: 'match_any', - value: ['estc', 'kibana'], - }, - ]; + test('it should deduplicate exception items', async () => { + const testEntries: EntriesArray = [ + { field: 'host.os.full.caseless', operator: 'included', type: 'match', value: 'windows' }, + { field: 'server.ip', operator: 'included', type: 'match', value: '192.168.1.1' }, + ]; - const expectedEndpointExceptions = { - type: 'simple', - entries: [ + const expectedEndpointExceptions = { + type: 'simple', + entries: [ + { + field: 'host.os.full', + operator: 'included', + type: 'exact_caseless', + value: 'windows', + }, + { + field: 'server.ip', + operator: 'included', + type: 'exact_cased', + value: '192.168.1.1', + }, + ], + }; + + const first = getFoundExceptionListItemSchemaMock(); + first.data[0].entries = testEntries; + + // Create a second exception item with the same entries + first.data[1] = getExceptionListItemSchemaMock(); + first.data[1].entries = testEntries; + mockExceptionClient.findExceptionListItem = jest.fn().mockReturnValueOnce(first); + + const resp = await getFilteredEndpointExceptionList( + mockExceptionClient, + 'v1', + TEST_FILTER, + ENDPOINT_LIST_ID + ); + expect(resp).toEqual({ + entries: [expectedEndpointExceptions], + }); + }); + + test('it should ignore unsupported entries', async () => { + // Lists and exists are not supported by the Endpoint + const testEntries: EntriesArray = [ + { field: 'host.os.full', operator: 'included', type: 'match', value: 'windows' }, { field: 'host.os.full', operator: 'included', - type: 'exact_caseless', - value: 'windows', - }, - { - field: 'server.ip', - operator: 'included', - type: 'exact_cased', - value: '192.168.1.1', - }, - { - field: 'host.hostname', - operator: 'included', - type: 'exact_cased_any', - value: ['estc', 'kibana'], - }, - ], - }; - - const first = getFoundExceptionListItemSchemaMock(); - first.data[0].entries = testEntries; - mockExceptionClient.findExceptionListItem = jest.fn().mockReturnValueOnce(first); - - const resp = await getFullEndpointExceptionList( - mockExceptionClient, - 'linux', - 'v1', - ENDPOINT_LIST_ID - ); - expect(resp).toEqual({ - entries: [expectedEndpointExceptions], - }); - }); + type: 'list', + list: { + id: 'lists_not_supported', + type: 'keyword', + }, + } as EntryList, + { field: 'server.ip', operator: 'included', type: 'exists' }, + ]; - test('it should not deduplicate exception entries across nested boundaries', async () => { - const testEntries: EntriesArray = [ - { + const expectedEndpointExceptions = { + type: 'simple', entries: [ - { field: 'nested.field', operator: 'included', type: 'match', value: 'some value' }, + { + field: 'host.os.full', + operator: 'included', + type: 'exact_cased', + value: 'windows', + }, ], - field: 'some.parentField', - type: 'nested', - }, - // Same as above but not inside the nest - { field: 'nested.field', operator: 'included', type: 'match', value: 'some value' }, - ]; + }; + + const first = getFoundExceptionListItemSchemaMock(); + first.data[0].entries = testEntries; + mockExceptionClient.findExceptionListItem = jest.fn().mockReturnValueOnce(first); + + const resp = await getFilteredEndpointExceptionList( + mockExceptionClient, + 'v1', + TEST_FILTER, + ENDPOINT_LIST_ID + ); + expect(resp).toEqual({ + entries: [expectedEndpointExceptions], + }); + }); + + test('it should convert the exception lists response to the proper endpoint format while paging', async () => { + // The first call returns two exceptions + const first = getFoundExceptionListItemSchemaMock(); + first.per_page = 2; + first.total = 4; + first.data.push(getExceptionListItemSchemaMock()); + + // The second call returns two exceptions + const second = getFoundExceptionListItemSchemaMock(); + second.per_page = 2; + second.total = 4; + second.data.push(getExceptionListItemSchemaMock()); + + mockExceptionClient.findExceptionListItem = jest + .fn() + .mockReturnValueOnce(first) + .mockReturnValueOnce(second); + + const resp = await getFilteredEndpointExceptionList( + mockExceptionClient, + 'v1', + TEST_FILTER, + ENDPOINT_LIST_ID + ); + + // Expect 2 exceptions, the first two calls returned the same exception list items + expect(resp.entries.length).toEqual(2); + }); + + test('it should handle no exceptions', async () => { + const exceptionsResponse = getFoundExceptionListItemSchemaMock(); + exceptionsResponse.data = []; + exceptionsResponse.total = 0; + mockExceptionClient.findExceptionListItem = jest.fn().mockReturnValueOnce(exceptionsResponse); + const resp = await getFilteredEndpointExceptionList( + mockExceptionClient, + 'v1', + TEST_FILTER, + ENDPOINT_LIST_ID + ); + expect(resp.entries.length).toEqual(0); + }); - const expectedEndpointExceptions = { - type: 'simple', - entries: [ + test('it should return a stable hash regardless of order of entries', async () => { + const translatedEntries: TranslatedEntry[] = [ { entries: [ { - field: 'nested.field', + field: 'some.nested.field', operator: 'included', type: 'exact_cased', value: 'some value', @@ -245,218 +420,107 @@ describe('buildEventTypeSignal', () => { type: 'exact_cased', value: 'some value', }, - ], - }; - - const first = getFoundExceptionListItemSchemaMock(); - first.data[0].entries = testEntries; - mockExceptionClient.findExceptionListItem = jest.fn().mockReturnValueOnce(first); - - const resp = await getFullEndpointExceptionList( - mockExceptionClient, - 'linux', - 'v1', - ENDPOINT_LIST_ID - ); - expect(resp).toEqual({ - entries: [expectedEndpointExceptions], - }); - }); - - test('it should deduplicate exception items', async () => { - const testEntries: EntriesArray = [ - { field: 'host.os.full.caseless', operator: 'included', type: 'match', value: 'windows' }, - { field: 'server.ip', operator: 'included', type: 'match', value: '192.168.1.1' }, - ]; - - const expectedEndpointExceptions = { - type: 'simple', - entries: [ - { - field: 'host.os.full', - operator: 'included', - type: 'exact_caseless', - value: 'windows', - }, - { - field: 'server.ip', - operator: 'included', - type: 'exact_cased', - value: '192.168.1.1', - }, - ], - }; - - const first = getFoundExceptionListItemSchemaMock(); - first.data[0].entries = testEntries; - - // Create a second exception item with the same entries - first.data[1] = getExceptionListItemSchemaMock(); - first.data[1].entries = testEntries; - mockExceptionClient.findExceptionListItem = jest.fn().mockReturnValueOnce(first); - - const resp = await getFullEndpointExceptionList( - mockExceptionClient, - 'linux', - 'v1', - ENDPOINT_LIST_ID - ); - expect(resp).toEqual({ - entries: [expectedEndpointExceptions], - }); - }); - - test('it should ignore unsupported entries', async () => { - // Lists and exists are not supported by the Endpoint - const testEntries: EntriesArray = [ - { field: 'host.os.full', operator: 'included', type: 'match', value: 'windows' }, - { - field: 'host.os.full', - operator: 'included', - type: 'list', - list: { - id: 'lists_not_supported', - type: 'keyword', - }, - } as EntryList, - { field: 'server.ip', operator: 'included', type: 'exists' }, - ]; - - const expectedEndpointExceptions = { - type: 'simple', - entries: [ - { - field: 'host.os.full', - operator: 'included', - type: 'exact_cased', - value: 'windows', - }, - ], - }; - - const first = getFoundExceptionListItemSchemaMock(); - first.data[0].entries = testEntries; - mockExceptionClient.findExceptionListItem = jest.fn().mockReturnValueOnce(first); - - const resp = await getFullEndpointExceptionList( - mockExceptionClient, - 'linux', - 'v1', - ENDPOINT_LIST_ID - ); - expect(resp).toEqual({ - entries: [expectedEndpointExceptions], - }); - }); + ]; + const translatedEntriesReversed = translatedEntries.reverse(); - test('it should convert the exception lists response to the proper endpoint format while paging', async () => { - // The first call returns two exceptions - const first = getFoundExceptionListItemSchemaMock(); - first.per_page = 2; - first.total = 4; - first.data.push(getExceptionListItemSchemaMock()); - - // The second call returns two exceptions - const second = getFoundExceptionListItemSchemaMock(); - second.per_page = 2; - second.total = 4; - second.data.push(getExceptionListItemSchemaMock()); - - mockExceptionClient.findExceptionListItem = jest - .fn() - .mockReturnValueOnce(first) - .mockReturnValueOnce(second); - - const resp = await getFullEndpointExceptionList( - mockExceptionClient, - 'linux', - 'v1', - ENDPOINT_LIST_ID - ); - - // Expect 2 exceptions, the first two calls returned the same exception list items - expect(resp.entries.length).toEqual(2); - }); - - test('it should handle no exceptions', async () => { - const exceptionsResponse = getFoundExceptionListItemSchemaMock(); - exceptionsResponse.data = []; - exceptionsResponse.total = 0; - mockExceptionClient.findExceptionListItem = jest.fn().mockReturnValueOnce(exceptionsResponse); - const resp = await getFullEndpointExceptionList( - mockExceptionClient, - 'linux', - 'v1', - ENDPOINT_LIST_ID - ); - expect(resp.entries.length).toEqual(0); - }); + const translatedExceptionList = { + entries: [ + { + type: 'simple', + entries: translatedEntries, + }, + ], + }; - test('it should return a stable hash regardless of order of entries', async () => { - const translatedEntries: TranslatedEntry[] = [ - { + const translatedExceptionListReversed = { entries: [ { - field: 'some.nested.field', - operator: 'included', - type: 'exact_cased', - value: 'some value', + type: 'simple', + entries: translatedEntriesReversed, }, ], - field: 'some.parentField', - type: 'nested', - }, - { - field: 'nested.field', - operator: 'included', - type: 'exact_cased', - value: 'some value', - }, - ]; - const translatedEntriesReversed = translatedEntries.reverse(); + }; + + const artifact1 = await buildArtifact( + translatedExceptionList, + 'v1', + 'linux', + ArtifactConstants.GLOBAL_ALLOWLIST_NAME + ); + const artifact2 = await buildArtifact( + translatedExceptionListReversed, + 'v1', + 'linux', + ArtifactConstants.GLOBAL_ALLOWLIST_NAME + ); + expect(artifact1.decodedSha256).toEqual(artifact2.decodedSha256); + }); - const translatedExceptionList = { - entries: [ + test('it should return a stable hash regardless of order of items', async () => { + const translatedItems: TranslatedExceptionListItem[] = [ { type: 'simple', - entries: translatedEntries, + entries: [ + { + entries: [ + { + field: 'some.nested.field', + operator: 'included', + type: 'exact_cased', + value: 'some value', + }, + ], + field: 'some.parentField', + type: 'nested', + }, + ], }, - ], - }; - - const translatedExceptionListReversed = { - entries: [ { type: 'simple', - entries: translatedEntriesReversed, + entries: [ + { + field: 'nested.field', + operator: 'included', + type: 'exact_cased', + value: 'some value', + }, + ], }, - ], - }; - - const artifact1 = await buildArtifact( - translatedExceptionList, - 'linux', - 'v1', - ArtifactConstants.GLOBAL_ALLOWLIST_NAME - ); - const artifact2 = await buildArtifact( - translatedExceptionListReversed, - 'linux', - 'v1', - ArtifactConstants.GLOBAL_ALLOWLIST_NAME - ); - expect(artifact1.decodedSha256).toEqual(artifact2.decodedSha256); + ]; + + const translatedExceptionList = { + entries: translatedItems, + }; + + const translatedExceptionListReversed = { + entries: translatedItems.reverse(), + }; + + const artifact1 = await buildArtifact( + translatedExceptionList, + 'v1', + 'linux', + ArtifactConstants.GLOBAL_ALLOWLIST_NAME + ); + const artifact2 = await buildArtifact( + translatedExceptionListReversed, + 'v1', + 'linux', + ArtifactConstants.GLOBAL_ALLOWLIST_NAME + ); + expect(artifact1.decodedSha256).toEqual(artifact2.decodedSha256); + }); }); - test('it should return a stable hash regardless of order of items', async () => { - const translatedItems: TranslatedExceptionListItem[] = [ + const TEST_EXCEPTION_LIST_ITEM = { + entries: [ { type: 'simple', entries: [ { entries: [ { - field: 'some.nested.field', + field: 'nested.field', operator: 'included', type: 'exact_cased', value: 'some value', @@ -465,41 +529,87 @@ describe('buildEventTypeSignal', () => { field: 'some.parentField', type: 'nested', }, - ], - }, - { - type: 'simple', - entries: [ { - field: 'nested.field', + field: 'some.not.nested.field', operator: 'included', type: 'exact_cased', value: 'some value', }, ], }, - ]; - - const translatedExceptionList = { - entries: translatedItems, - }; - - const translatedExceptionListReversed = { - entries: translatedItems.reverse(), - }; - - const artifact1 = await buildArtifact( - translatedExceptionList, - 'linux', - 'v1', - ArtifactConstants.GLOBAL_ALLOWLIST_NAME - ); - const artifact2 = await buildArtifact( - translatedExceptionListReversed, - 'linux', - 'v1', - ArtifactConstants.GLOBAL_ALLOWLIST_NAME - ); - expect(artifact1.decodedSha256).toEqual(artifact2.decodedSha256); + ], + }; + + describe('getEndpointExceptionList', () => { + test('it should build proper kuery', async () => { + mockExceptionClient.findExceptionListItem = jest + .fn() + .mockReturnValueOnce(getFoundExceptionListItemSchemaMock()); + + const resp = await getEndpointExceptionList(mockExceptionClient, 'v1', 'windows'); + + expect(resp).toEqual(TEST_EXCEPTION_LIST_ITEM); + + expect(mockExceptionClient.findExceptionListItem).toHaveBeenCalledWith({ + listId: ENDPOINT_LIST_ID, + namespaceType: 'agnostic', + filter: 'exception-list-agnostic.attributes.os_types:"windows"', + perPage: 100, + page: 1, + sortField: 'created_at', + sortOrder: 'desc', + }); + }); + }); + + describe('getEndpointTrustedAppsList', () => { + test('it should build proper kuery without policy', async () => { + mockExceptionClient.findExceptionListItem = jest + .fn() + .mockReturnValueOnce(getFoundExceptionListItemSchemaMock()); + + const resp = await getEndpointTrustedAppsList(mockExceptionClient, 'v1', 'macos'); + + expect(resp).toEqual(TEST_EXCEPTION_LIST_ITEM); + + expect(mockExceptionClient.findExceptionListItem).toHaveBeenCalledWith({ + listId: ENDPOINT_TRUSTED_APPS_LIST_ID, + namespaceType: 'agnostic', + filter: + 'exception-list-agnostic.attributes.os_types:"macos" and (exception-list-agnostic.attributes.tags:"policy:all")', + perPage: 100, + page: 1, + sortField: 'created_at', + sortOrder: 'desc', + }); + }); + + test('it should build proper kuery with policy', async () => { + mockExceptionClient.findExceptionListItem = jest + .fn() + .mockReturnValueOnce(getFoundExceptionListItemSchemaMock()); + + const resp = await getEndpointTrustedAppsList( + mockExceptionClient, + 'v1', + 'macos', + 'c6d16e42-c32d-4dce-8a88-113cfe276ad1' + ); + + expect(resp).toEqual(TEST_EXCEPTION_LIST_ITEM); + + expect(mockExceptionClient.findExceptionListItem).toHaveBeenCalledWith({ + listId: ENDPOINT_TRUSTED_APPS_LIST_ID, + namespaceType: 'agnostic', + filter: + 'exception-list-agnostic.attributes.os_types:"macos" and ' + + '(exception-list-agnostic.attributes.tags:"policy:all" or ' + + 'exception-list-agnostic.attributes.tags:"policy:c6d16e42-c32d-4dce-8a88-113cfe276ad1")', + perPage: 100, + page: 1, + sortField: 'created_at', + sortOrder: 'desc', + }); + }); }); }); diff --git a/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/lists.ts b/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/lists.ts index 6cc6a821eba33..322bb2ca47a45 100644 --- a/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/lists.ts +++ b/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/lists.ts @@ -12,7 +12,7 @@ import { validate } from '../../../../common/validate'; import { Entry, EntryNested } from '../../../../../lists/common/schemas/types'; import { ExceptionListClient } from '../../../../../lists/server'; -import { ENDPOINT_LIST_ID } from '../../../../common/shared_imports'; +import { ENDPOINT_LIST_ID, ENDPOINT_TRUSTED_APPS_LIST_ID } from '../../../../common/shared_imports'; import { InternalArtifactSchema, TranslatedEntry, @@ -28,12 +28,11 @@ import { internalArtifactCompleteSchema, InternalArtifactCompleteSchema, } from '../../schemas'; -import { ENDPOINT_TRUSTED_APPS_LIST_ID } from '../../../../../lists/common/constants'; export async function buildArtifact( exceptions: WrappedTranslatedExceptionList, - os: string, schemaVersion: string, + os: string, name: string ): Promise { const exceptionsBuffer = Buffer.from(JSON.stringify(exceptions)); @@ -74,10 +73,10 @@ export function isCompressed(artifact: InternalArtifactSchema) { return artifact.compressionAlgorithm === 'zlib'; } -export async function getFullEndpointExceptionList( +export async function getFilteredEndpointExceptionList( eClient: ExceptionListClient, - os: string, schemaVersion: string, + filter: string, listId: typeof ENDPOINT_LIST_ID | typeof ENDPOINT_TRUSTED_APPS_LIST_ID ): Promise { const exceptions: WrappedTranslatedExceptionList = { entries: [] }; @@ -88,7 +87,7 @@ export async function getFullEndpointExceptionList( const response = await eClient.findExceptionListItem({ listId, namespaceType: 'agnostic', - filter: `exception-list-agnostic.attributes.os_types:\"${os}\"`, + filter, perPage: 100, page, sortField: 'created_at', @@ -114,6 +113,35 @@ export async function getFullEndpointExceptionList( return validated as WrappedTranslatedExceptionList; } +export async function getEndpointExceptionList( + eClient: ExceptionListClient, + schemaVersion: string, + os: string +): Promise { + const filter = `exception-list-agnostic.attributes.os_types:\"${os}\"`; + + return getFilteredEndpointExceptionList(eClient, schemaVersion, filter, ENDPOINT_LIST_ID); +} + +export async function getEndpointTrustedAppsList( + eClient: ExceptionListClient, + schemaVersion: string, + os: string, + policyId?: string +): Promise { + const osFilter = `exception-list-agnostic.attributes.os_types:\"${os}\"`; + const policyFilter = `(exception-list-agnostic.attributes.tags:\"policy:all\"${ + policyId ? ` or exception-list-agnostic.attributes.tags:\"policy:${policyId}\"` : '' + })`; + + return getFilteredEndpointExceptionList( + eClient, + schemaVersion, + `${osFilter} and ${policyFilter}`, + ENDPOINT_TRUSTED_APPS_LIST_ID + ); +} + /** * Translates Exception list items to Exceptions the endpoint can understand * @param exceptions diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/trusted_apps/mapping.test.ts b/x-pack/plugins/security_solution/server/endpoint/routes/trusted_apps/mapping.test.ts index 972c4f3153a1c..b8b1e13f2052b 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/trusted_apps/mapping.test.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/trusted_apps/mapping.test.ts @@ -35,7 +35,7 @@ const createExceptionListItemOptions = ( name: '', namespaceType: 'agnostic', osTypes: [], - tags: [], + tags: ['policy:all'], type: 'simple', ...options, }); @@ -56,7 +56,7 @@ const exceptionListItemSchema = ( name: '', namespace_type: 'agnostic', os_types: [], - tags: [], + tags: ['policy:all'], type: 'simple', tie_breaker_id: '123', updated_at: '11/11/2011T11:11:11.111', diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/trusted_apps/mapping.ts b/x-pack/plugins/security_solution/server/endpoint/routes/trusted_apps/mapping.ts index 4d2238ea96ee1..41b4b7b1d55fd 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/trusted_apps/mapping.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/trusted_apps/mapping.ts @@ -15,7 +15,7 @@ import { ExceptionListItemSchema, NestedEntriesArray, } from '../../../../../lists/common/shared_exports'; -import { ENDPOINT_TRUSTED_APPS_LIST_ID } from '../../../../../lists/common/constants'; +import { ENDPOINT_TRUSTED_APPS_LIST_ID } from '../../../../../lists/common'; import { CreateExceptionListItemOptions } from '../../../../../lists/server'; import { ConditionEntry, @@ -184,7 +184,7 @@ export const newTrustedAppToCreateExceptionListItemOptions = ({ name, namespaceType: 'agnostic', osTypes: [OPERATING_SYSTEM_TO_OS_TYPE[os]], - tags: [], + tags: ['policy:all'], type: 'simple', }; }; diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/trusted_apps/service.ts b/x-pack/plugins/security_solution/server/endpoint/routes/trusted_apps/service.ts index dc3c369494d4e..97a8451bf25d8 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/trusted_apps/service.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/trusted_apps/service.ts @@ -6,7 +6,7 @@ */ import { ExceptionListClient } from '../../../../../lists/server'; -import { ENDPOINT_TRUSTED_APPS_LIST_ID } from '../../../../../lists/common/constants'; +import { ENDPOINT_TRUSTED_APPS_LIST_ID } from '../../../../../lists/common'; import { DeleteTrustedAppsRequestParams, diff --git a/x-pack/plugins/security_solution/server/endpoint/schemas/artifacts/saved_objects.mock.ts b/x-pack/plugins/security_solution/server/endpoint/schemas/artifacts/saved_objects.mock.ts index dedbcc25e2373..1975c2a92cc16 100644 --- a/x-pack/plugins/security_solution/server/endpoint/schemas/artifacts/saved_objects.mock.ts +++ b/x-pack/plugins/security_solution/server/endpoint/schemas/artifacts/saved_objects.mock.ts @@ -36,8 +36,8 @@ export const getInternalArtifactMock = async ( ): Promise => { const artifact = await buildArtifact( getTranslatedExceptionListMock(), - os, schemaVersion, + os, artifactName ); return opts?.compress ? compressArtifact(artifact) : artifact; @@ -49,7 +49,7 @@ export const getEmptyInternalArtifactMock = async ( opts?: { compress: boolean }, artifactName: string = ArtifactConstants.GLOBAL_ALLOWLIST_NAME ): Promise => { - const artifact = await buildArtifact({ entries: [] }, os, schemaVersion, artifactName); + const artifact = await buildArtifact({ entries: [] }, schemaVersion, os, artifactName); return opts?.compress ? compressArtifact(artifact) : artifact; }; @@ -62,8 +62,8 @@ export const getInternalArtifactMockWithDiffs = async ( mock.entries.pop(); const artifact = await buildArtifact( mock, - os, schemaVersion, + os, ArtifactConstants.GLOBAL_ALLOWLIST_NAME ); return opts?.compress ? compressArtifact(artifact) : artifact; diff --git a/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.mock.ts b/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.mock.ts index a8bbfca0d41e5..b0e0d5d8ebfbe 100644 --- a/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.mock.ts +++ b/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.mock.ts @@ -33,17 +33,24 @@ export const createExceptionListResponse = (data: ExceptionListItemSchema[], tot type FindExceptionListItemOptions = Parameters[0]; -const FILTER_REGEXP = /^exception-list-agnostic\.attributes\.os_types:"(\w+)"$/; +const FILTER_PROPERTY_PREFIX = 'exception-list-agnostic\\.attributes'; +const FILTER_REGEXP = new RegExp( + `^${FILTER_PROPERTY_PREFIX}\.os_types:"([^"]+)"( and \\(${FILTER_PROPERTY_PREFIX}\.tags:"policy:all"( or ${FILTER_PROPERTY_PREFIX}\.tags:"policy:([^"]+)")?\\))?$` +); export const mockFindExceptionListItemResponses = ( responses: Record> ) => { return jest.fn().mockImplementation((options: FindExceptionListItemOptions) => { - const os = FILTER_REGEXP.test(options.filter || '') - ? options.filter!.match(FILTER_REGEXP)![1] - : ''; - - return createExceptionListResponse(responses[options.listId]?.[os] || []); + const matches = options.filter!.match(FILTER_REGEXP) || []; + + if (matches[4] && responses[options.listId]?.[`${matches![1]}-${matches[4]}`]) { + return createExceptionListResponse( + responses[options.listId]?.[`${matches![1]}-${matches[4]}`] || [] + ); + } else { + return createExceptionListResponse(responses[options.listId]?.[matches![1] || ''] || []); + } }); }; @@ -118,7 +125,7 @@ export const getManifestManagerMock = ( context.exceptionListClient.findExceptionListItem = jest .fn() .mockRejectedValue(new Error('unexpected thing happened')); - return super.buildExceptionListArtifacts('v1'); + return super.buildExceptionListArtifacts(); case ManifestManagerMockType.NormalFlow: return getMockArtifactsWithDiff(); } diff --git a/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.test.ts b/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.test.ts index 52897f473189f..26db49be459fa 100644 --- a/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.test.ts +++ b/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.test.ts @@ -8,8 +8,7 @@ import { inflateSync } from 'zlib'; import { SavedObjectsErrorHelpers } from 'src/core/server'; import { savedObjectsClientMock } from 'src/core/server/mocks'; -import { ENDPOINT_LIST_ID } from '../../../../../../lists/common'; -import { ENDPOINT_TRUSTED_APPS_LIST_ID } from '../../../../../../lists/common/constants'; +import { ENDPOINT_LIST_ID, ENDPOINT_TRUSTED_APPS_LIST_ID } from '../../../../../../lists/common'; import { getExceptionListItemSchemaMock } from '../../../../../../lists/common/schemas/response/exception_list_item_schema.mock'; import { PackagePolicy } from '../../../../../../fleet/common/types/models'; import { getEmptyInternalArtifactMock } from '../../../schemas/artifacts/saved_objects.mock'; @@ -211,10 +210,19 @@ describe('ManifestManager', () => { ARTIFACT_NAME_TRUSTED_APPS_LINUX, ]; - const getArtifactIds = (artifacts: InternalArtifactSchema[]) => - artifacts.map((artifact) => artifact.identifier); + const getArtifactIds = (artifacts: InternalArtifactSchema[]) => [ + ...new Set(artifacts.map((artifact) => artifact.identifier)).values(), + ]; + + const mockPolicyListIdsResponse = (items: string[]) => + jest.fn().mockResolvedValue({ + items, + page: 1, + per_page: 100, + total: items.length, + }); - test('Fails when exception list list client fails', async () => { + test('Fails when exception list client fails', async () => { const context = buildManifestManagerContextMock({}); const manifestManager = new ManifestManager(context); @@ -228,6 +236,7 @@ describe('ManifestManager', () => { const manifestManager = new ManifestManager(context); context.exceptionListClient.findExceptionListItem = mockFindExceptionListItemResponses({}); + context.packagePolicyService.listIds = mockPolicyListIdsResponse([TEST_POLICY_ID_1]); const manifest = await manifestManager.buildNewManifest(); @@ -237,11 +246,16 @@ describe('ManifestManager', () => { const artifacts = manifest.getAllArtifacts(); + expect(artifacts.length).toBe(5); expect(getArtifactIds(artifacts)).toStrictEqual(SUPPORTED_ARTIFACT_NAMES); expect(artifacts.every(isCompressed)).toBe(true); for (const artifact of artifacts) { expect(await uncompressArtifact(artifact)).toStrictEqual({ entries: [] }); + expect(manifest.isDefaultArtifact(artifact)).toBe(true); + expect(manifest.getArtifactTargetPolicies(artifact)).toStrictEqual( + new Set([TEST_POLICY_ID_1]) + ); } }); @@ -255,6 +269,7 @@ describe('ManifestManager', () => { [ENDPOINT_LIST_ID]: { macos: [exceptionListItem] }, [ENDPOINT_TRUSTED_APPS_LIST_ID]: { linux: [trustedAppListItem] }, }); + context.packagePolicyService.listIds = mockPolicyListIdsResponse([TEST_POLICY_ID_1]); const manifest = await manifestManager.buildNewManifest(); @@ -264,21 +279,25 @@ describe('ManifestManager', () => { const artifacts = manifest.getAllArtifacts(); + expect(artifacts.length).toBe(5); expect(getArtifactIds(artifacts)).toStrictEqual(SUPPORTED_ARTIFACT_NAMES); expect(artifacts.every(isCompressed)).toBe(true); + expect(await uncompressArtifact(artifacts[0])).toStrictEqual({ + entries: translateToEndpointExceptions([exceptionListItem], 'v1'), + }); + expect(await uncompressArtifact(artifacts[1])).toStrictEqual({ entries: [] }); + expect(await uncompressArtifact(artifacts[2])).toStrictEqual({ entries: [] }); + expect(await uncompressArtifact(artifacts[3])).toStrictEqual({ entries: [] }); + expect(await uncompressArtifact(artifacts[4])).toStrictEqual({ + entries: translateToEndpointExceptions([trustedAppListItem], 'v1'), + }); + for (const artifact of artifacts) { - if (artifact.identifier === ARTIFACT_NAME_EXCEPTIONS_MACOS) { - expect(await uncompressArtifact(artifact)).toStrictEqual({ - entries: translateToEndpointExceptions([exceptionListItem], 'v1'), - }); - } else if (artifact.identifier === 'endpoint-trustlist-linux-v1') { - expect(await uncompressArtifact(artifact)).toStrictEqual({ - entries: translateToEndpointExceptions([trustedAppListItem], 'v1'), - }); - } else { - expect(await uncompressArtifact(artifact)).toStrictEqual({ entries: [] }); - } + expect(manifest.isDefaultArtifact(artifact)).toBe(true); + expect(manifest.getArtifactTargetPolicies(artifact)).toStrictEqual( + new Set([TEST_POLICY_ID_1]) + ); } }); @@ -291,6 +310,7 @@ describe('ManifestManager', () => { context.exceptionListClient.findExceptionListItem = mockFindExceptionListItemResponses({ [ENDPOINT_LIST_ID]: { macos: [exceptionListItem] }, }); + context.packagePolicyService.listIds = mockPolicyListIdsResponse([TEST_POLICY_ID_1]); const oldManifest = await manifestManager.buildNewManifest(); @@ -307,20 +327,89 @@ describe('ManifestManager', () => { const artifacts = manifest.getAllArtifacts(); + expect(artifacts.length).toBe(5); expect(getArtifactIds(artifacts)).toStrictEqual(SUPPORTED_ARTIFACT_NAMES); expect(artifacts.every(isCompressed)).toBe(true); + expect(artifacts[0]).toStrictEqual(oldManifest.getAllArtifacts()[0]); + expect(await uncompressArtifact(artifacts[1])).toStrictEqual({ entries: [] }); + expect(await uncompressArtifact(artifacts[2])).toStrictEqual({ entries: [] }); + expect(await uncompressArtifact(artifacts[3])).toStrictEqual({ entries: [] }); + expect(await uncompressArtifact(artifacts[4])).toStrictEqual({ + entries: translateToEndpointExceptions([trustedAppListItem], 'v1'), + }); + for (const artifact of artifacts) { - if (artifact.identifier === ARTIFACT_NAME_EXCEPTIONS_MACOS) { - expect(artifact).toStrictEqual(oldManifest.getAllArtifacts()[0]); - } else if (artifact.identifier === 'endpoint-trustlist-linux-v1') { - expect(await uncompressArtifact(artifact)).toStrictEqual({ - entries: translateToEndpointExceptions([trustedAppListItem], 'v1'), - }); - } else { - expect(await uncompressArtifact(artifact)).toStrictEqual({ entries: [] }); - } + expect(manifest.isDefaultArtifact(artifact)).toBe(true); + expect(manifest.getArtifactTargetPolicies(artifact)).toStrictEqual( + new Set([TEST_POLICY_ID_1]) + ); + } + }); + + test('Builds manifest with policy specific exception list items for trusted apps', async () => { + const exceptionListItem = getExceptionListItemSchemaMock({ os_types: ['macos'] }); + const trustedAppListItem = getExceptionListItemSchemaMock({ os_types: ['linux'] }); + const trustedAppListItemPolicy2 = getExceptionListItemSchemaMock({ + os_types: ['linux'], + entries: [ + { field: 'other.field', operator: 'included', type: 'match', value: 'other value' }, + ], + }); + const context = buildManifestManagerContextMock({}); + const manifestManager = new ManifestManager(context); + + context.exceptionListClient.findExceptionListItem = mockFindExceptionListItemResponses({ + [ENDPOINT_LIST_ID]: { macos: [exceptionListItem] }, + [ENDPOINT_TRUSTED_APPS_LIST_ID]: { + linux: [trustedAppListItem], + [`linux-${TEST_POLICY_ID_2}`]: [trustedAppListItem, trustedAppListItemPolicy2], + }, + }); + context.packagePolicyService.listIds = mockPolicyListIdsResponse([ + TEST_POLICY_ID_1, + TEST_POLICY_ID_2, + ]); + + const manifest = await manifestManager.buildNewManifest(); + + expect(manifest?.getSchemaVersion()).toStrictEqual('v1'); + expect(manifest?.getSemanticVersion()).toStrictEqual('1.0.0'); + expect(manifest?.getSavedObjectVersion()).toBeUndefined(); + + const artifacts = manifest.getAllArtifacts(); + + expect(artifacts.length).toBe(6); + expect(getArtifactIds(artifacts)).toStrictEqual(SUPPORTED_ARTIFACT_NAMES); + expect(artifacts.every(isCompressed)).toBe(true); + + expect(await uncompressArtifact(artifacts[0])).toStrictEqual({ + entries: translateToEndpointExceptions([exceptionListItem], 'v1'), + }); + expect(await uncompressArtifact(artifacts[1])).toStrictEqual({ entries: [] }); + expect(await uncompressArtifact(artifacts[2])).toStrictEqual({ entries: [] }); + expect(await uncompressArtifact(artifacts[3])).toStrictEqual({ entries: [] }); + expect(await uncompressArtifact(artifacts[4])).toStrictEqual({ + entries: translateToEndpointExceptions([trustedAppListItem], 'v1'), + }); + expect(await uncompressArtifact(artifacts[5])).toStrictEqual({ + entries: translateToEndpointExceptions( + [trustedAppListItem, trustedAppListItemPolicy2], + 'v1' + ), + }); + + for (const artifact of artifacts.slice(0, 4)) { + expect(manifest.isDefaultArtifact(artifact)).toBe(true); + expect(manifest.getArtifactTargetPolicies(artifact)).toStrictEqual( + new Set([TEST_POLICY_ID_1, TEST_POLICY_ID_2]) + ); } + + expect(manifest.isDefaultArtifact(artifacts[5])).toBe(false); + expect(manifest.getArtifactTargetPolicies(artifacts[5])).toStrictEqual( + new Set([TEST_POLICY_ID_2]) + ); }); }); diff --git a/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.ts b/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.ts index 6b9cbb55415a0..f49f2a3e226ee 100644 --- a/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.ts +++ b/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.ts @@ -9,6 +9,7 @@ import semver from 'semver'; import LRU from 'lru-cache'; import { isEqual } from 'lodash'; import { Logger, SavedObjectsClientContract } from 'src/core/server'; +import { ListResult } from '../../../../../../fleet/common'; import { PackagePolicyServiceInterface } from '../../../../../../fleet/server'; import { ExceptionListClient } from '../../../../../../lists/server'; import { ManifestSchemaVersion } from '../../../../../common/endpoint/schema/common'; @@ -21,7 +22,8 @@ import { ArtifactConstants, buildArtifact, getArtifactId, - getFullEndpointExceptionList, + getEndpointExceptionList, + getEndpointTrustedAppsList, isCompressed, Manifest, maybeCompressArtifact, @@ -32,9 +34,45 @@ import { } from '../../../schemas/artifacts'; import { ArtifactClient } from '../artifact_client'; import { ManifestClient } from '../manifest_client'; -import { ENDPOINT_LIST_ID } from '../../../../../../lists/common'; -import { ENDPOINT_TRUSTED_APPS_LIST_ID } from '../../../../../../lists/common/constants'; -import { PackagePolicy } from '../../../../../../fleet/common/types/models'; + +interface ArtifactsBuildResult { + defaultArtifacts: InternalArtifactCompleteSchema[]; + policySpecificArtifacts: Record; +} + +const iterateArtifactsBuildResult = async ( + result: ArtifactsBuildResult, + callback: (artifact: InternalArtifactCompleteSchema, policyId?: string) => Promise +) => { + for (const artifact of result.defaultArtifacts) { + await callback(artifact); + } + + for (const policyId of Object.keys(result.policySpecificArtifacts)) { + for (const artifact of result.policySpecificArtifacts[policyId]) { + await callback(artifact, policyId); + } + } +}; + +const iterateAllListItems = async ( + pageSupplier: (page: number) => Promise>, + itemCallback: (item: T) => void +) => { + let paging = true; + let page = 1; + + while (paging) { + const { items, total } = await pageSupplier(page); + + for (const item of items) { + await itemCallback(item); + } + + paging = (page - 1) * 20 + items.length < total; + page++; + } +}; export interface ManifestManagerContext { savedObjectsClient: SavedObjectsClientContract; @@ -81,6 +119,19 @@ export class ManifestManager { return new ManifestClient(this.savedObjectsClient, this.schemaVersion); } + /** + * Builds an artifact (one per supported OS) based on the current + * state of exception-list-agnostic SOs. + */ + protected async buildExceptionListArtifact(os: string): Promise { + return buildArtifact( + await getEndpointExceptionList(this.exceptionListClient, this.schemaVersion, os), + this.schemaVersion, + os, + ArtifactConstants.GLOBAL_ALLOWLIST_NAME + ); + } + /** * Builds an array of artifacts (one per supported OS) based on the current * state of exception-list-agnostic SOs. @@ -88,54 +139,60 @@ export class ManifestManager { * @returns {Promise} An array of uncompressed artifacts built from exception-list-agnostic SOs. * @throws Throws/rejects if there are errors building the list. */ - protected async buildExceptionListArtifacts( - artifactSchemaVersion?: string - ): Promise { - const artifacts: InternalArtifactCompleteSchema[] = []; + protected async buildExceptionListArtifacts(): Promise { + const defaultArtifacts: InternalArtifactCompleteSchema[] = []; + const policySpecificArtifacts: Record = {}; + for (const os of ArtifactConstants.SUPPORTED_OPERATING_SYSTEMS) { - const exceptionList = await getFullEndpointExceptionList( - this.exceptionListClient, - os, - artifactSchemaVersion ?? 'v1', - ENDPOINT_LIST_ID - ); - const artifact = await buildArtifact( - exceptionList, - os, - artifactSchemaVersion ?? 'v1', - ArtifactConstants.GLOBAL_ALLOWLIST_NAME - ); - artifacts.push(artifact); + defaultArtifacts.push(await this.buildExceptionListArtifact(os)); } - return artifacts; + + await iterateAllListItems( + (page) => this.listEndpointPolicyIds(page), + async (policyId) => { + policySpecificArtifacts[policyId] = defaultArtifacts; + } + ); + + return { defaultArtifacts, policySpecificArtifacts }; + } + + /** + * Builds an artifact (one per supported OS) based on the current state of the + * Trusted Apps list (which uses the `exception-list-agnostic` SO type) + */ + protected async buildTrustedAppsArtifact(os: string, policyId?: string) { + return buildArtifact( + await getEndpointTrustedAppsList(this.exceptionListClient, this.schemaVersion, os, policyId), + this.schemaVersion, + os, + ArtifactConstants.GLOBAL_TRUSTED_APPS_NAME + ); } /** * Builds an array of artifacts (one per supported OS) based on the current state of the * Trusted Apps list (which uses the `exception-list-agnostic` SO type) - * @param artifactSchemaVersion */ - protected async buildTrustedAppsArtifacts( - artifactSchemaVersion?: string - ): Promise { - const artifacts: InternalArtifactCompleteSchema[] = []; + protected async buildTrustedAppsArtifacts(): Promise { + const defaultArtifacts: InternalArtifactCompleteSchema[] = []; + const policySpecificArtifacts: Record = {}; for (const os of ArtifactConstants.SUPPORTED_TRUSTED_APPS_OPERATING_SYSTEMS) { - const trustedApps = await getFullEndpointExceptionList( - this.exceptionListClient, - os, - artifactSchemaVersion ?? 'v1', - ENDPOINT_TRUSTED_APPS_LIST_ID - ); - const artifact = await buildArtifact( - trustedApps, - os, - 'v1', - ArtifactConstants.GLOBAL_TRUSTED_APPS_NAME - ); - artifacts.push(artifact); + defaultArtifacts.push(await this.buildTrustedAppsArtifact(os)); } - return artifacts; + + await iterateAllListItems( + (page) => this.listEndpointPolicyIds(page), + async (policyId) => { + for (const os of ArtifactConstants.SUPPORTED_TRUSTED_APPS_OPERATING_SYSTEMS) { + policySpecificArtifacts[policyId] = policySpecificArtifacts[policyId] || []; + policySpecificArtifacts[policyId].push(await this.buildTrustedAppsArtifact(os, policyId)); + } + } + ); + + return { defaultArtifacts, policySpecificArtifacts }; } /** @@ -251,32 +308,33 @@ export class ManifestManager { public async buildNewManifest( baselineManifest: Manifest = Manifest.getDefault(this.schemaVersion) ): Promise { - // Build new exception list artifacts - const artifacts = ( - await Promise.all([this.buildExceptionListArtifacts(), this.buildTrustedAppsArtifacts()]) - ).flat(); + const results = await Promise.all([ + this.buildExceptionListArtifacts(), + this.buildTrustedAppsArtifacts(), + ]); - // Build new manifest const manifest = new Manifest({ schemaVersion: this.schemaVersion, semanticVersion: baselineManifest.getSemanticVersion(), soVersion: baselineManifest.getSavedObjectVersion(), }); - for (const artifact of artifacts) { - let artifactToAdd = baselineManifest.getArtifact(getArtifactId(artifact)) || artifact; - - if (!isCompressed(artifactToAdd)) { - artifactToAdd = await maybeCompressArtifact(artifactToAdd); + for (const result of results) { + await iterateArtifactsBuildResult(result, async (artifact, policyId) => { + let artifactToAdd = baselineManifest.getArtifact(getArtifactId(artifact)) || artifact; if (!isCompressed(artifactToAdd)) { - throw new Error(`Unable to compress artifact: ${getArtifactId(artifactToAdd)}`); - } else if (!internalArtifactCompleteSchema.is(artifactToAdd)) { - throw new Error(`Incomplete artifact detected: ${getArtifactId(artifactToAdd)}`); + artifactToAdd = await maybeCompressArtifact(artifactToAdd); + + if (!isCompressed(artifactToAdd)) { + throw new Error(`Unable to compress artifact: ${getArtifactId(artifactToAdd)}`); + } else if (!internalArtifactCompleteSchema.is(artifactToAdd)) { + throw new Error(`Incomplete artifact detected: ${getArtifactId(artifactToAdd)}`); + } } - } - manifest.addEntry(artifactToAdd); + manifest.addEntry(artifactToAdd, policyId); + }); } return manifest; @@ -292,49 +350,52 @@ export class ManifestManager { public async tryDispatch(manifest: Manifest): Promise { const errors: Error[] = []; - await this.forEachPolicy(async (packagePolicy) => { - // eslint-disable-next-line @typescript-eslint/naming-convention - const { id, revision, updated_at, updated_by, ...newPackagePolicy } = packagePolicy; - if (newPackagePolicy.inputs.length > 0 && newPackagePolicy.inputs[0].config !== undefined) { - const oldManifest = newPackagePolicy.inputs[0].config.artifact_manifest ?? { - value: {}, - }; - - const newManifestVersion = manifest.getSemanticVersion(); - if (semver.gt(newManifestVersion, oldManifest.value.manifest_version)) { - const serializedManifest = manifest.toPackagePolicyManifest(packagePolicy.id); - - if (!manifestDispatchSchema.is(serializedManifest)) { - errors.push(new Error(`Invalid manifest for policy ${packagePolicy.id}`)); - } else if (!manifestsEqual(serializedManifest, oldManifest.value)) { - newPackagePolicy.inputs[0].config.artifact_manifest = { value: serializedManifest }; - - try { - await this.packagePolicyService.update( - this.savedObjectsClient, - // @ts-ignore - undefined, - id, - newPackagePolicy - ); + await iterateAllListItems( + (page) => this.listEndpointPolicies(page), + async (packagePolicy) => { + // eslint-disable-next-line @typescript-eslint/naming-convention + const { id, revision, updated_at, updated_by, ...newPackagePolicy } = packagePolicy; + if (newPackagePolicy.inputs.length > 0 && newPackagePolicy.inputs[0].config !== undefined) { + const oldManifest = newPackagePolicy.inputs[0].config.artifact_manifest ?? { + value: {}, + }; + + const newManifestVersion = manifest.getSemanticVersion(); + if (semver.gt(newManifestVersion, oldManifest.value.manifest_version)) { + const serializedManifest = manifest.toPackagePolicyManifest(packagePolicy.id); + + if (!manifestDispatchSchema.is(serializedManifest)) { + errors.push(new Error(`Invalid manifest for policy ${packagePolicy.id}`)); + } else if (!manifestsEqual(serializedManifest, oldManifest.value)) { + newPackagePolicy.inputs[0].config.artifact_manifest = { value: serializedManifest }; + + try { + await this.packagePolicyService.update( + this.savedObjectsClient, + // @ts-ignore + undefined, + id, + newPackagePolicy + ); + this.logger.debug( + `Updated package policy ${id} with manifest version ${manifest.getSemanticVersion()}` + ); + } catch (err) { + errors.push(err); + } + } else { this.logger.debug( - `Updated package policy ${id} with manifest version ${manifest.getSemanticVersion()}` + `No change in manifest content for package policy: ${id}. Staying on old version` ); - } catch (err) { - errors.push(err); } } else { - this.logger.debug( - `No change in manifest content for package policy: ${id}. Staying on old version` - ); + this.logger.debug(`No change in manifest version for package policy: ${id}`); } } else { - this.logger.debug(`No change in manifest version for package policy: ${id}`); + errors.push(new Error(`Package Policy ${id} has no config.`)); } - } else { - errors.push(new Error(`Package Policy ${id} has no config.`)); } - }); + ); return errors; } @@ -363,23 +424,19 @@ export class ManifestManager { this.logger.info(`Committed manifest ${manifest.getSemanticVersion()}`); } - private async forEachPolicy(callback: (policy: PackagePolicy) => Promise) { - let paging = true; - let page = 1; - - while (paging) { - const { items, total } = await this.packagePolicyService.list(this.savedObjectsClient, { - page, - perPage: 20, - kuery: 'ingest-package-policies.package.name:endpoint', - }); - - for (const packagePolicy of items) { - await callback(packagePolicy); - } + private async listEndpointPolicies(page: number) { + return this.packagePolicyService.list(this.savedObjectsClient, { + page, + perPage: 20, + kuery: 'ingest-package-policies.package.name:endpoint', + }); + } - paging = (page - 1) * 20 + items.length < total; - page++; - } + private async listEndpointPolicyIds(page: number) { + return this.packagePolicyService.listIds(this.savedObjectsClient, { + page, + perPage: 20, + kuery: 'ingest-package-policies.package.name:endpoint', + }); } } From eeaaeb762736f2d6ce1dc4e1627103500b76cfbd Mon Sep 17 00:00:00 2001 From: Melissa Alvarez Date: Tue, 16 Feb 2021 17:15:20 -0500 Subject: [PATCH 35/53] [ML] Anomaly Detection: when no anomalies present for time range show no results message (#91151) (#91554) * single metric viewer callout color to blue.show empty results in explorer. * update snapshot for empty results view * check if selected job still running * update resultsWithAnomalies check * update no overall data message. remove unnecessary component prop --- .../anomaly_detection_jobs/summary_job.ts | 1 + .../explorer_no_results_found.test.js.snap | 10 +-- .../explorer_no_results_found.js | 68 ++++++++++++------- .../explorer/components/no_overall_data.tsx | 2 +- .../public/application/explorer/explorer.js | 13 +++- .../application/routing/routes/explorer.tsx | 4 ++ .../timeseriesexplorer/timeseriesexplorer.js | 1 - 7 files changed, 62 insertions(+), 37 deletions(-) diff --git a/x-pack/plugins/ml/common/types/anomaly_detection_jobs/summary_job.ts b/x-pack/plugins/ml/common/types/anomaly_detection_jobs/summary_job.ts index bb6b331c10fc1..09f5c37ac9aea 100644 --- a/x-pack/plugins/ml/common/types/anomaly_detection_jobs/summary_job.ts +++ b/x-pack/plugins/ml/common/types/anomaly_detection_jobs/summary_job.ts @@ -49,6 +49,7 @@ export type MlSummaryJobs = MlSummaryJob[]; export interface MlJobWithTimeRange extends CombinedJobWithStats { id: string; + isRunning?: boolean; isNotSingleMetricViewerJobMessage?: string; timeRange: { from: number; diff --git a/x-pack/plugins/ml/public/application/explorer/components/explorer_no_results_found/__snapshots__/explorer_no_results_found.test.js.snap b/x-pack/plugins/ml/public/application/explorer/components/explorer_no_results_found/__snapshots__/explorer_no_results_found.test.js.snap index dc7e567380fdf..388e2f590edf2 100644 --- a/x-pack/plugins/ml/public/application/explorer/components/explorer_no_results_found/__snapshots__/explorer_no_results_found.test.js.snap +++ b/x-pack/plugins/ml/public/application/explorer/components/explorer_no_results_found/__snapshots__/explorer_no_results_found.test.js.snap @@ -14,14 +14,6 @@ exports[`ExplorerNoInfluencersFound snapshot 1`] = ` } iconType="iInCircle" - title={ -

- -

- } + title={

} /> `; diff --git a/x-pack/plugins/ml/public/application/explorer/components/explorer_no_results_found/explorer_no_results_found.js b/x-pack/plugins/ml/public/application/explorer/components/explorer_no_results_found/explorer_no_results_found.js index 6e058a8fc8c61..799437e1799f0 100644 --- a/x-pack/plugins/ml/public/application/explorer/components/explorer_no_results_found/explorer_no_results_found.js +++ b/x-pack/plugins/ml/public/application/explorer/components/explorer_no_results_found/explorer_no_results_found.js @@ -14,26 +14,48 @@ import { FormattedMessage } from '@kbn/i18n/react'; import { EuiEmptyPrompt } from '@elastic/eui'; -export const ExplorerNoResultsFound = () => ( - - -

- } - body={ - -

- -

-
- } - /> -); +export const ExplorerNoResultsFound = ({ hasResults, selectedJobsRunning }) => { + const resultsHaveNoAnomalies = hasResults === true; + const noResults = hasResults === false; + return ( + + {resultsHaveNoAnomalies && ( + + )} + {noResults && ( + + )} + + } + body={ + + {selectedJobsRunning && noResults && ( +

+ +

+ )} + {!selectedJobsRunning && ( +

+ +

+ )} +
+ } + /> + ); +}; diff --git a/x-pack/plugins/ml/public/application/explorer/components/no_overall_data.tsx b/x-pack/plugins/ml/public/application/explorer/components/no_overall_data.tsx index fe77fdf235b58..65935050ee218 100644 --- a/x-pack/plugins/ml/public/application/explorer/components/no_overall_data.tsx +++ b/x-pack/plugins/ml/public/application/explorer/components/no_overall_data.tsx @@ -12,7 +12,7 @@ export const NoOverallData: FC = () => { return ( ); }; diff --git a/x-pack/plugins/ml/public/application/explorer/explorer.js b/x-pack/plugins/ml/public/application/explorer/explorer.js index 9f77260ab3320..abf8197f51634 100644 --- a/x-pack/plugins/ml/public/application/explorer/explorer.js +++ b/x-pack/plugins/ml/public/application/explorer/explorer.js @@ -142,6 +142,7 @@ export class Explorer extends React.Component { setSelectedCells: PropTypes.func.isRequired, severity: PropTypes.number.isRequired, showCharts: PropTypes.bool.isRequired, + selectedJobsRunning: PropTypes.bool.isRequired, }; state = { filterIconTriggeredQuery: undefined, language: DEFAULT_QUERY_LANG }; @@ -223,7 +224,7 @@ export class Explorer extends React.Component { updateLanguage = (language) => this.setState({ language }); render() { - const { showCharts, severity, stoppedPartitions } = this.props; + const { showCharts, severity, stoppedPartitions, selectedJobsRunning } = this.props; const { annotations, @@ -248,6 +249,9 @@ export class Explorer extends React.Component { const noJobsFound = selectedJobs === null || selectedJobs.length === 0; const hasResults = overallSwimlaneData.points && overallSwimlaneData.points.length > 0; + const hasResultsWithAnomalies = + (hasResults && overallSwimlaneData.points.some((v) => v.value > 0)) || + tableData.anomalies?.length > 0; if (noJobsFound && !loading) { return ( @@ -257,10 +261,13 @@ export class Explorer extends React.Component { ); } - if (noJobsFound && hasResults === false && !loading) { + if (hasResultsWithAnomalies === false && !loading) { return ( - + ); } diff --git a/x-pack/plugins/ml/public/application/routing/routes/explorer.tsx b/x-pack/plugins/ml/public/application/routing/routes/explorer.tsx index 052be41ca1eb7..e65ca22effd76 100644 --- a/x-pack/plugins/ml/public/application/routing/routes/explorer.tsx +++ b/x-pack/plugins/ml/public/application/routing/routes/explorer.tsx @@ -87,6 +87,9 @@ const ExplorerUrlStateManager: FC = ({ jobsWithTim const timefilter = useTimefilter({ timeRangeSelector: true, autoRefreshSelector: true }); const { jobIds } = useJobSelection(jobsWithTimeRange); + const selectedJobsRunning = jobsWithTimeRange.some( + (job) => jobIds.includes(job.id) && job.isRunning === true + ); const explorerAppState = useObservable(explorerService.appState$); const explorerState = useObservable(explorerService.state$); @@ -261,6 +264,7 @@ const ExplorerUrlStateManager: FC = ({ jobsWithTim severity: tableSeverity.val, stoppedPartitions, invalidTimeRangeError, + selectedJobsRunning, }} />
diff --git a/x-pack/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer.js b/x-pack/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer.js index 33e5183fa7949..06a0f7e17e164 100644 --- a/x-pack/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer.js +++ b/x-pack/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer.js @@ -1000,7 +1000,6 @@ export class TimeSeriesExplorer extends React.Component { }} /> } - color="warning" iconType="help" size="s" /> From 957ef447b5adad8104adb8fa8e6f4b3e2b877a37 Mon Sep 17 00:00:00 2001 From: Melissa Alvarez Date: Tue, 16 Feb 2021 17:22:58 -0500 Subject: [PATCH 36/53] ensure class_assignment_objective parameter is cloned correctly (#91507) (#91556) --- .../ml/common/types/data_frame_analytics.ts | 3 ++- .../hooks/use_create_analytics_form/state.ts | 18 +++++++++++------- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/x-pack/plugins/ml/common/types/data_frame_analytics.ts b/x-pack/plugins/ml/common/types/data_frame_analytics.ts index cacc5acb9768f..95d82932a1212 100644 --- a/x-pack/plugins/ml/common/types/data_frame_analytics.ts +++ b/x-pack/plugins/ml/common/types/data_frame_analytics.ts @@ -34,9 +34,10 @@ interface Regression { } interface Classification { + class_assignment_objective?: string; dependent_variable: string; training_percent?: number; - num_top_classes?: string; + num_top_classes?: number; num_top_feature_importance_values?: number; prediction_field_name?: string; } diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/state.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/state.ts index 131da93a2328a..40e13ea0e6867 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/state.ts +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/state.ts @@ -16,6 +16,7 @@ import { DataFrameAnalyticsId, DataFrameAnalysisConfigType, } from '../../../../../../../common/types/data_frame_analytics'; +import { isClassificationAnalysis } from '../../../../../../../common/util/analytics_utils'; import { ANALYSIS_CONFIG_TYPE } from '../../../../../../../common/constants/data_frame_analytics'; export enum DEFAULT_MODEL_MEMORY_LIMIT { regression = '100mb', @@ -50,6 +51,7 @@ export interface State { alpha: undefined | number; computeFeatureInfluence: string; createIndexPattern: boolean; + classAssignmentObjective: undefined | string; dependentVariable: DependentVariable; description: string; destinationIndex: EsIndexName; @@ -126,6 +128,7 @@ export const getInitialState = (): State => ({ alpha: undefined, computeFeatureInfluence: 'true', createIndexPattern: true, + classAssignmentObjective: undefined, dependentVariable: '', description: '', destinationIndex: '', @@ -278,13 +281,14 @@ export const getJobConfigFromFormState = ( }; } - if ( - formState.jobType === ANALYSIS_CONFIG_TYPE.CLASSIFICATION && - jobConfig?.analysis?.classification !== undefined && - formState.numTopClasses !== undefined - ) { - // @ts-ignore - jobConfig.analysis.classification.num_top_classes = formState.numTopClasses; + if (jobConfig?.analysis !== undefined && isClassificationAnalysis(jobConfig?.analysis)) { + if (formState.numTopClasses !== undefined) { + jobConfig.analysis.classification.num_top_classes = formState.numTopClasses; + } + if (formState.classAssignmentObjective !== undefined) { + jobConfig.analysis.classification.class_assignment_objective = + formState.classAssignmentObjective; + } } if (formState.jobType === ANALYSIS_CONFIG_TYPE.OUTLIER_DETECTION) { From d94108d43358b92530f6b47972d5d3f80a536ba0 Mon Sep 17 00:00:00 2001 From: Greg Thompson Date: Tue, 16 Feb 2021 16:25:40 -0600 Subject: [PATCH 37/53] Upgrade EUI to v31.7.0 (#91210) (#91558) * eui to 31.6.0 * flyout, collapsible snapshot updates * initial overlaymask removal * undo jest * overlaymask src snapshot updates * more overlaymask removals * overlaymask removal xpack test updates * saved objects modal form * eui to 31.7.0 * code, codeblock types * snapshot update * tooltip * remove ownFocus from ConfirmModal * remove fragments # Conflicts: # x-pack/plugins/canvas/public/components/asset_manager/asset_manager.component.tsx --- package.json | 2 +- .../collapsible_nav.test.tsx.snap | 18 + .../header/__snapshots__/header.test.tsx.snap | 3 + .../flyout_service.test.tsx.snap | 4 +- .../__snapshots__/modal_service.test.tsx.snap | 248 +++--- .../public/overlays/modal/modal_service.tsx | 22 +- .../application/components/settings_modal.tsx | 181 +++-- .../__snapshots__/clone_modal.test.js.snap | 112 ++- .../application/top_nav/clone_modal.tsx | 117 ++- .../ui/saved_query_form/save_query_form.tsx | 63 +- .../saved_query_list_item.tsx | 60 +- .../confirmation_modal.test.tsx.snap | 18 +- .../confirmation_modal/confirmation_modal.tsx | 20 +- .../confirmation_modal.test.tsx.snap | 62 +- .../confirmation_modal/confirmation_modal.tsx | 58 +- .../components/field_editor/field_editor.tsx | 71 +- .../table_list_view/table_list_view.tsx | 71 +- .../saved_object_save_modal.test.tsx.snap | 704 +++++++++--------- .../save_modal/saved_object_save_modal.tsx | 85 +-- .../components/delete_confirm_modal.tsx | 166 ++--- .../objects_table/components/export_modal.tsx | 143 ++-- .../components/overwrite_modal.tsx | 54 +- .../public/wizard/new_vis_modal.tsx | 4 +- .../List/ConfirmDeleteModal.tsx | 68 +- .../table/controls/action_control.tsx | 62 +- .../public/pages/overview/enrolled_beats.tsx | 101 ++- .../asset_manager/asset_manager.component.tsx | 127 ++-- .../confirm_modal/confirm_modal.tsx | 30 +- .../datasource_preview/datasource_preview.js | 81 +- .../keyboard_shortcuts_doc.stories.storyshot | 2 +- .../saved_elements_modal.component.tsx | 81 +- .../components/toolbar/toolbar.component.tsx | 19 +- .../var_config.stories.storyshot | 3 + .../edit_menu/edit_menu.component.tsx | 14 +- .../__snapshots__/pdf_panel.stories.storyshot | 3 + .../auto_follow_pattern_delete_provider.js | 78 +- .../follower_index_pause_provider.js | 110 ++- .../follower_index_resume_provider.js | 130 ++-- .../follower_index_unfollow_provider.js | 96 ++- .../follower_index_edit.js | 77 +- .../components/actions/delete_button.tsx | 36 +- .../components/actions/extend_button.tsx | 40 +- .../search_experience/customization_modal.tsx | 162 ++-- .../log_retention_confirmation_modal.tsx | 6 +- .../shared/schema/schema_add_field_modal.tsx | 123 ++- .../display_settings/field_editor_modal.tsx | 81 +- .../components/source_settings.tsx | 39 +- .../views/content_sources/sources_view.tsx | 122 ++- .../components/add_group_modal.test.tsx | 3 +- .../groups/components/add_group_modal.tsx | 59 +- .../components/group_manager_modal.test.tsx | 3 +- .../groups/components/group_manager_modal.tsx | 17 +- .../groups/components/group_overview.tsx | 23 +- .../views/security/security.tsx | 23 +- .../settings/components/oauth_application.tsx | 42 +- .../settings/components/source_config.tsx | 31 +- .../components/agent_policy_copy_provider.tsx | 118 ++- .../agent_policy_delete_provider.tsx | 114 ++- .../components/agent_policy_yaml_flyout.tsx | 2 - .../components/confirm_deploy_modal.tsx | 100 ++- .../package_policy_delete_provider.tsx | 128 ++-- .../components/agent_unenroll_modal/index.tsx | 154 ++-- .../components/agent_upgrade_modal/index.tsx | 154 ++-- .../components/confirm_delete_modal.tsx | 54 +- .../settings/confirm_package_install.tsx | 80 +- .../settings/confirm_package_uninstall.tsx | 88 ++- .../add_policy_to_template_confirm_modal.tsx | 79 +- .../components/confirm_delete.tsx | 54 +- .../add_lifecycle_confirm_modal.tsx | 99 ++- .../remove_lifecycle_confirm_modal.tsx | 84 +-- .../component_template_list/delete_modal.tsx | 80 +- .../modal_confirmation_delete_fields.tsx | 98 ++- .../load_mappings/load_mappings_provider.tsx | 109 ++- .../runtime_fields/delete_field_provider.tsx | 32 +- .../constants/data_types_definition.tsx | 4 +- .../components/template_delete_modal.tsx | 166 ++--- .../delete_data_stream_confirmation_modal.tsx | 118 ++- .../index_actions_context_menu.js | 483 ++++++------ .../components/saved_views/create_modal.tsx | 119 ++- .../components/saved_views/update_modal.tsx | 119 ++- .../saved_views/view_list_modal.tsx | 98 ++- .../logs/stream/page_view_log_in_context.tsx | 48 +- .../load_from_json/modal_provider.tsx | 108 ++- .../processor_form/processors/csv.tsx | 4 +- .../processor_form/processors/date.tsx | 6 +- .../processors/date_index_name.tsx | 8 +- .../processor_form/processors/dissect.tsx | 2 +- .../processor_form/processors/geoip.tsx | 4 +- .../processor_form/processors/inference.tsx | 2 +- .../processor_form/processors/user_agent.tsx | 2 +- .../components/processor_remove_modal.tsx | 68 +- .../shared/map_processor_type_to_form.tsx | 2 +- .../tab_documents/reset_documents_modal.tsx | 26 +- .../sections/pipelines_list/delete_modal.tsx | 80 +- .../upload_license.test.tsx.snap | 230 +++--- .../revert_to_basic/revert_to_basic.js | 78 +- .../start_trial/start_trial.tsx | 277 ++++--- .../sections/upload_license/upload_license.js | 69 +- ...confirm_delete_pipeline_modal.test.js.snap | 70 +- .../confirm_delete_pipeline_modal.js | 58 +- .../confirm_delete_modal.test.js.snap | 162 ++-- .../pipeline_list/confirm_delete_modal.js | 36 +- .../layer_control/layer_toc/toc_entry/view.js | 28 +- .../delete_annotation_modal/index.tsx | 54 +- .../delete_job_check_modal.tsx | 119 ++- .../close_job_confirm/close_job_confirm.tsx | 93 ++- .../edit_model_snapshot_flyout.tsx | 33 +- .../revert_model_snapshot_flyout.tsx | 55 +- .../delete_rule_modal.test.js.snap | 56 +- .../select_rule_action/delete_rule_modal.js | 52 +- .../validate_job/validate_job_view.js | 32 +- .../action_delete/delete_action_modal.tsx | 120 ++- .../action_start/start_action_modal.tsx | 60 +- .../action_stop/stop_action_modal.tsx | 62 +- .../models_management/delete_models_modal.tsx | 91 ++- .../source_selection/source_selection.tsx | 120 ++- .../explorer/add_to_dashboard_control.tsx | 181 +++-- .../delete_job_modal/delete_job_modal.tsx | 125 ++-- .../edit_job_flyout/edit_job_flyout.js | 63 +- .../edit_job_flyout/tabs/custom_urls.tsx | 43 +- .../start_datafeed_modal.js | 139 ++-- .../components/reset_query/reset_query.tsx | 60 +- .../advanced_detector_modal/modal_wrapper.tsx | 71 +- .../settings/calendars/edit/new_calendar.js | 18 +- .../settings/calendars/list/calendars_list.js | 61 +- .../delete_filter_list_modal.test.js.snap | 68 +- .../delete_filter_list_modal.js | 46 +- .../components/forecasting_modal/modal.js | 73 +- .../remove_cluster_button_provider.js | 6 +- .../report_info_button.test.tsx.snap | 8 +- .../buttons/report_delete_button.tsx | 26 +- .../confirm_delete_modal.js | 44 +- .../public/components/confirm_modal.tsx | 88 +-- .../invalidate_provider.tsx | 102 ++- .../delete_provider/delete_provider.tsx | 104 ++- .../rule_editor_panel/rule_editor_panel.tsx | 67 +- .../rule_editor_panel/rule_group_title.tsx | 77 +- .../roles/edit_role/delete_role_button.tsx | 76 +- .../confirm_delete/confirm_delete.tsx | 109 ++- .../confirm_delete_users.tsx | 80 +- .../users/edit_user/confirm_delete_users.tsx | 1 - .../users/edit_user/confirm_disable_users.tsx | 1 - .../users/edit_user/confirm_enable_users.tsx | 1 - .../cypress/tasks/timeline.ts | 2 +- .../components/confirm_delete_case/index.tsx | 28 +- .../use_all_cases_modal/all_cases_modal.tsx | 26 +- .../create_case_modal.tsx | 36 +- .../exceptions/add_exception_modal/index.tsx | 241 +++--- .../exceptions/edit_exception_modal/index.tsx | 233 +++--- .../__snapshots__/index.test.tsx.snap | 116 ++- .../components/import_data_modal/index.tsx | 87 ++- .../common/components/inspect/modal.tsx | 37 +- .../__snapshots__/index.test.tsx.snap | 8 + .../modal_all_errors.test.tsx.snap | 90 ++- .../components/toasters/modal_all_errors.tsx | 55 +- .../value_lists_management_modal/modal.tsx | 5 +- .../reference_error_modal.tsx | 44 +- .../rules/all/rules_tables.tsx | 23 +- .../pages/policy/view/policy_details.tsx | 102 ++- .../view/trusted_app_deletion_dialog.tsx | 53 +- .../delete_timeline_modal/index.tsx | 20 +- .../open_timeline_modal/index.tsx | 32 +- .../__snapshots__/index.test.tsx.snap | 6 +- .../timeline/header/title_and_description.tsx | 146 ++-- .../components/policy_delete_provider.tsx | 94 ++- .../components/policy_execute_provider.tsx | 52 +- .../components/repository_delete_provider.tsx | 130 ++-- .../retention_execute_modal_provider.tsx | 50 +- .../retention_update_modal_provider.tsx | 295 ++++---- .../components/snapshot_delete_provider.tsx | 159 ++-- .../repository_details/repository_details.tsx | 4 +- .../confirm_delete_modal.test.tsx.snap | 164 ++-- .../confirm_delete_modal.tsx | 151 ++-- ...irm_alter_active_space_modal.test.tsx.snap | 46 +- .../confirm_alter_active_space_modal.tsx | 56 +- .../components/switch_modal/switch_modal.tsx | 30 +- .../action_delete/delete_action_modal.tsx | 33 +- .../action_start/start_action_modal.tsx | 42 +- .../transform_management_section.tsx | 17 +- .../components/delete_modal_confirmation.tsx | 98 ++- .../connector_add_modal.tsx | 172 ++--- .../alert_form/confirm_alert_close.tsx | 66 +- .../alert_form/confirm_alert_save.tsx | 66 +- .../components/manage_license_modal.tsx | 64 +- .../__snapshots__/donut_chart.test.tsx.snap | 8 + .../confirm_delete.test.tsx.snap | 96 ++- .../ml/__snapshots__/ml_flyout.test.tsx.snap | 2 +- .../monitor/ml/confirm_alert_delete.tsx | 38 +- .../components/monitor/ml/confirm_delete.tsx | 78 +- .../components/confirm_watches_modal.tsx | 48 +- .../components/delete_watches_modal.tsx | 84 +-- yarn.lock | 8 +- 192 files changed, 6996 insertions(+), 7510 deletions(-) diff --git a/package.json b/package.json index 695f34e49d5f0..be099c165917e 100644 --- a/package.json +++ b/package.json @@ -95,7 +95,7 @@ "@elastic/datemath": "link:packages/elastic-datemath", "@elastic/elasticsearch": "npm:@elastic/elasticsearch-canary@^7.12.0-canary", "@elastic/ems-client": "7.12.0", - "@elastic/eui": "31.4.0", + "@elastic/eui": "31.7.0", "@elastic/filesaver": "1.1.2", "@elastic/good": "^9.0.1-kibana3", "@elastic/node-crypto": "1.2.1", diff --git a/src/core/public/chrome/ui/header/__snapshots__/collapsible_nav.test.tsx.snap b/src/core/public/chrome/ui/header/__snapshots__/collapsible_nav.test.tsx.snap index 80e23a32ca557..575a247ffeccb 100644 --- a/src/core/public/chrome/ui/header/__snapshots__/collapsible_nav.test.tsx.snap +++ b/src/core/public/chrome/ui/header/__snapshots__/collapsible_nav.test.tsx.snap @@ -715,8 +715,11 @@ exports[`CollapsibleNav renders links grouped by category 1`] = `
Flyout content
"`; +exports[`FlyoutService openFlyout() renders a flyout to the DOM 2`] = `"
Flyout content
"`; exports[`FlyoutService openFlyout() with a currently active flyout replaces the current flyout with a new one 1`] = ` Array [ @@ -59,4 +59,4 @@ Array [ ] `; -exports[`FlyoutService openFlyout() with a currently active flyout replaces the current flyout with a new one 2`] = `"
Flyout content 2
"`; +exports[`FlyoutService openFlyout() with a currently active flyout replaces the current flyout with a new one 2`] = `"
Flyout content 2
"`; diff --git a/src/core/public/overlays/modal/__snapshots__/modal_service.test.tsx.snap b/src/core/public/overlays/modal/__snapshots__/modal_service.test.tsx.snap index 7e79725c20307..d52cc090d5d19 100644 --- a/src/core/public/overlays/modal/__snapshots__/modal_service.test.tsx.snap +++ b/src/core/public/overlays/modal/__snapshots__/modal_service.test.tsx.snap @@ -11,21 +11,19 @@ Array [ exports[`ModalService openConfirm() renders a mountpoint confirm message 1`] = ` Array [ Array [ - - - - - - - , + + + + + ,
, ], ] @@ -36,18 +34,16 @@ exports[`ModalService openConfirm() renders a mountpoint confirm message 2`] = ` exports[`ModalService openConfirm() renders a string confirm message 1`] = ` Array [ Array [ - - - - Some message - - - , + + + Some message + + ,
, ], ] @@ -58,33 +54,29 @@ exports[`ModalService openConfirm() renders a string confirm message 2`] = `" - - - confirm 1 - - - , + + + confirm 1 + + ,
, ], Array [ - - - - some confirm - - - , + + + some confirm + + ,
, ], ] @@ -93,33 +85,29 @@ Array [ exports[`ModalService openConfirm() with a currently active modal replaces the current modal with the new confirm 1`] = ` Array [ Array [ - - - - - - - , + + + + + ,
, ], Array [ - - - - some confirm - - - , + + + some confirm + + ,
, ], ] @@ -128,18 +116,16 @@ Array [ exports[`ModalService openModal() renders a modal to the DOM 1`] = ` Array [ Array [ - - - - - - - , + + + + + ,
, ], ] @@ -150,33 +136,29 @@ exports[`ModalService openModal() renders a modal to the DOM 2`] = `"
- - - confirm 1 - - - , + + + confirm 1 + + ,
, ], Array [ - - - - some confirm - - - , + + + some confirm + + ,
, ], ] @@ -185,33 +167,29 @@ Array [ exports[`ModalService openModal() with a currently active modal replaces the current modal with a new one 1`] = ` Array [ Array [ - - - - - - - , + + + + + ,
, ], Array [ - - - - - - - , + + + + + ,
, ], ] diff --git a/src/core/public/overlays/modal/modal_service.tsx b/src/core/public/overlays/modal/modal_service.tsx index 1f96e00fef0f8..7e4aee94c958e 100644 --- a/src/core/public/overlays/modal/modal_service.tsx +++ b/src/core/public/overlays/modal/modal_service.tsx @@ -9,7 +9,7 @@ /* eslint-disable max-classes-per-file */ import { i18n as t } from '@kbn/i18n'; -import { EuiModal, EuiConfirmModal, EuiOverlayMask, EuiConfirmModalProps } from '@elastic/eui'; +import { EuiModal, EuiConfirmModal, EuiConfirmModalProps } from '@elastic/eui'; import React from 'react'; import { render, unmountComponentAtNode } from 'react-dom'; import { Subject } from 'rxjs'; @@ -137,13 +137,11 @@ export class ModalService { this.activeModal = modal; render( - - - modal.close()}> - - - - , + + modal.close()}> + + + , targetDomElement ); @@ -199,11 +197,9 @@ export class ModalService { }; render( - - - - - , + + + , targetDomElement ); }); diff --git a/src/plugins/console/public/application/components/settings_modal.tsx b/src/plugins/console/public/application/components/settings_modal.tsx index f3c8954d01254..161b67500b47c 100644 --- a/src/plugins/console/public/application/components/settings_modal.tsx +++ b/src/plugins/console/public/application/components/settings_modal.tsx @@ -22,7 +22,6 @@ import { EuiModalFooter, EuiModalHeader, EuiModalHeaderTitle, - EuiOverlayMask, EuiSwitch, } from '@elastic/eui'; @@ -151,115 +150,107 @@ export function DevToolsSettingsModal(props: Props) { ) : undefined; return ( - - - - - - - + + + + + + + + + + } + > + { + const val = parseInt(e.target.value, 10); + if (!val) return; + setFontSize(val); + }} + /> + - - + } - > - { - const val = parseInt(e.target.value, 10); - if (!val) return; - setFontSize(val); - }} - /> - + onChange={(e) => setWrapMode(e.target.checked)} + /> + - - - } - onChange={(e) => setWrapMode(e.target.checked)} + - - - + } - > - - } - onChange={(e) => setTripleQuotes(e.target.checked)} - /> - + onChange={(e) => setTripleQuotes(e.target.checked)} + /> + - - } - > - { - const { stateSetter, ...rest } = opts; - return rest; - })} - idToSelectedMap={checkboxIdToSelectedMap} - onChange={(e: any) => { - onAutocompleteChange(e as AutocompleteOptions); - }} + - + } + > + { + const { stateSetter, ...rest } = opts; + return rest; + })} + idToSelectedMap={checkboxIdToSelectedMap} + onChange={(e: any) => { + onAutocompleteChange(e as AutocompleteOptions); + }} + /> + - {pollingFields} - + {pollingFields} + - - - - + + + + - - - - - - + + + + + ); } diff --git a/src/plugins/dashboard/public/application/top_nav/__snapshots__/clone_modal.test.js.snap b/src/plugins/dashboard/public/application/top_nav/__snapshots__/clone_modal.test.js.snap index d289d267a2fd6..1e029e6960cdf 100644 --- a/src/plugins/dashboard/public/application/top_nav/__snapshots__/clone_modal.test.js.snap +++ b/src/plugins/dashboard/public/application/top_nav/__snapshots__/clone_modal.test.js.snap @@ -1,65 +1,63 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`renders DashboardCloneModal 1`] = ` - - - - - - - - - -

- -

-
- - + + + -
- - - - - + + + + +

- - - - +

+
+ + +
+ + + + + + + + +
`; diff --git a/src/plugins/dashboard/public/application/top_nav/clone_modal.tsx b/src/plugins/dashboard/public/application/top_nav/clone_modal.tsx index c1bcad51babf9..3af186f841a5d 100644 --- a/src/plugins/dashboard/public/application/top_nav/clone_modal.tsx +++ b/src/plugins/dashboard/public/application/top_nav/clone_modal.tsx @@ -19,7 +19,6 @@ import { EuiModalFooter, EuiModalHeader, EuiModalHeaderTitle, - EuiOverlayMask, EuiSpacer, EuiText, EuiCallOut, @@ -138,69 +137,67 @@ export class DashboardCloneModal extends React.Component { render() { return ( - - - - - - - - - - -

- -

-
- - - - + + + + + - {this.renderDuplicateTitleCallout()} -
- - - + + +

- - - - - - - - +

+
+ + + + + + {this.renderDuplicateTitleCallout()} +
+ + + + + + + + + + +
); } } diff --git a/src/plugins/data/public/ui/saved_query_form/save_query_form.tsx b/src/plugins/data/public/ui/saved_query_form/save_query_form.tsx index 7873886432cbe..077b9ac47286d 100644 --- a/src/plugins/data/public/ui/saved_query_form/save_query_form.tsx +++ b/src/plugins/data/public/ui/saved_query_form/save_query_form.tsx @@ -9,7 +9,6 @@ import React, { useEffect, useState, useCallback } from 'react'; import { EuiButtonEmpty, - EuiOverlayMask, EuiModal, EuiButton, EuiModalHeader, @@ -208,37 +207,35 @@ export function SaveQueryForm({ ); return ( - - - - - {i18n.translate('data.search.searchBar.savedQueryFormTitle', { - defaultMessage: 'Save query', - })} - - - - {saveQueryForm} - - - - {i18n.translate('data.search.searchBar.savedQueryFormCancelButtonText', { - defaultMessage: 'Cancel', - })} - - - - {i18n.translate('data.search.searchBar.savedQueryFormSaveButtonText', { - defaultMessage: 'Save', - })} - - - - + + + + {i18n.translate('data.search.searchBar.savedQueryFormTitle', { + defaultMessage: 'Save query', + })} + + + + {saveQueryForm} + + + + {i18n.translate('data.search.searchBar.savedQueryFormCancelButtonText', { + defaultMessage: 'Cancel', + })} + + + + {i18n.translate('data.search.searchBar.savedQueryFormSaveButtonText', { + defaultMessage: 'Save', + })} + + + ); } diff --git a/src/plugins/data/public/ui/saved_query_management/saved_query_list_item.tsx b/src/plugins/data/public/ui/saved_query_management/saved_query_list_item.tsx index 47a2d050a9bfa..b7ba3215eb5aa 100644 --- a/src/plugins/data/public/ui/saved_query_management/saved_query_list_item.tsx +++ b/src/plugins/data/public/ui/saved_query_management/saved_query_list_item.tsx @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { EuiListGroupItem, EuiConfirmModal, EuiOverlayMask, EuiIconTip } from '@elastic/eui'; +import { EuiListGroupItem, EuiConfirmModal, EuiIconTip } from '@elastic/eui'; import React, { Fragment, useState } from 'react'; import classNames from 'classnames'; @@ -114,36 +114,34 @@ export const SavedQueryListItem = ({ /> {showDeletionConfirmationModal && ( - - { - onDelete(savedQuery); - setShowDeletionConfirmationModal(false); - }} - buttonColor="danger" - onCancel={() => { - setShowDeletionConfirmationModal(false); - }} - /> - + { + onDelete(savedQuery); + setShowDeletionConfirmationModal(false); + }} + buttonColor="danger" + onCancel={() => { + setShowDeletionConfirmationModal(false); + }} + /> )} ); diff --git a/src/plugins/index_pattern_management/public/components/edit_index_pattern/scripted_fields_table/components/confirmation_modal/__snapshots__/confirmation_modal.test.tsx.snap b/src/plugins/index_pattern_management/public/components/edit_index_pattern/scripted_fields_table/components/confirmation_modal/__snapshots__/confirmation_modal.test.tsx.snap index 2b320782cb163..eaaccdb499b0b 100644 --- a/src/plugins/index_pattern_management/public/components/edit_index_pattern/scripted_fields_table/components/confirmation_modal/__snapshots__/confirmation_modal.test.tsx.snap +++ b/src/plugins/index_pattern_management/public/components/edit_index_pattern/scripted_fields_table/components/confirmation_modal/__snapshots__/confirmation_modal.test.tsx.snap @@ -1,14 +1,12 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`DeleteScritpedFieldConfirmationModal should render normally 1`] = ` - - - + `; diff --git a/src/plugins/index_pattern_management/public/components/edit_index_pattern/scripted_fields_table/components/confirmation_modal/confirmation_modal.tsx b/src/plugins/index_pattern_management/public/components/edit_index_pattern/scripted_fields_table/components/confirmation_modal/confirmation_modal.tsx index 5fbd3118b800b..36069f408f354 100644 --- a/src/plugins/index_pattern_management/public/components/edit_index_pattern/scripted_fields_table/components/confirmation_modal/confirmation_modal.tsx +++ b/src/plugins/index_pattern_management/public/components/edit_index_pattern/scripted_fields_table/components/confirmation_modal/confirmation_modal.tsx @@ -8,7 +8,7 @@ import React from 'react'; import { i18n } from '@kbn/i18n'; -import { EUI_MODAL_CONFIRM_BUTTON, EuiConfirmModal, EuiOverlayMask } from '@elastic/eui'; +import { EUI_MODAL_CONFIRM_BUTTON, EuiConfirmModal } from '@elastic/eui'; import { ScriptedFieldItem } from '../../types'; @@ -42,15 +42,13 @@ export const DeleteScritpedFieldConfirmationModal = ({ ); return ( - - - + ); }; diff --git a/src/plugins/index_pattern_management/public/components/edit_index_pattern/source_filters_table/components/confirmation_modal/__snapshots__/confirmation_modal.test.tsx.snap b/src/plugins/index_pattern_management/public/components/edit_index_pattern/source_filters_table/components/confirmation_modal/__snapshots__/confirmation_modal.test.tsx.snap index 9d92a3689b698..736dbb611dbbd 100644 --- a/src/plugins/index_pattern_management/public/components/edit_index_pattern/source_filters_table/components/confirmation_modal/__snapshots__/confirmation_modal.test.tsx.snap +++ b/src/plugins/index_pattern_management/public/components/edit_index_pattern/source_filters_table/components/confirmation_modal/__snapshots__/confirmation_modal.test.tsx.snap @@ -1,37 +1,35 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`Header should render normally 1`] = ` - - - } - confirmButtonText={ - - } - defaultFocusedButton="confirm" - onCancel={[Function]} - onConfirm={[Function]} - title={ - + } + confirmButtonText={ + + } + defaultFocusedButton="confirm" + onCancel={[Function]} + onConfirm={[Function]} + title={ + - } - /> - + } + /> + } +/> `; diff --git a/src/plugins/index_pattern_management/public/components/edit_index_pattern/source_filters_table/components/confirmation_modal/confirmation_modal.tsx b/src/plugins/index_pattern_management/public/components/edit_index_pattern/source_filters_table/components/confirmation_modal/confirmation_modal.tsx index 6715d7a6780ae..fb8d4a38bfe63 100644 --- a/src/plugins/index_pattern_management/public/components/edit_index_pattern/source_filters_table/components/confirmation_modal/confirmation_modal.tsx +++ b/src/plugins/index_pattern_management/public/components/edit_index_pattern/source_filters_table/components/confirmation_modal/confirmation_modal.tsx @@ -10,7 +10,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import { FormattedMessage } from '@kbn/i18n/react'; -import { EuiOverlayMask, EuiConfirmModal, EUI_MODAL_CONFIRM_BUTTON } from '@elastic/eui'; +import { EuiConfirmModal, EUI_MODAL_CONFIRM_BUTTON } from '@elastic/eui'; interface DeleteFilterConfirmationModalProps { filterToDeleteValue: string; @@ -26,35 +26,33 @@ export const DeleteFilterConfirmationModal = ({ onDeleteFilter, }: DeleteFilterConfirmationModalProps) => { return ( - - - } - onCancel={onCancelConfirmationModal} - onConfirm={onDeleteFilter} - cancelButtonText={ - - } - buttonColor="danger" - confirmButtonText={ - - } - defaultFocusedButton={EUI_MODAL_CONFIRM_BUTTON} - /> - + + } + onCancel={onCancelConfirmationModal} + onConfirm={onDeleteFilter} + cancelButtonText={ + + } + buttonColor="danger" + confirmButtonText={ + + } + defaultFocusedButton={EUI_MODAL_CONFIRM_BUTTON} + /> ); }; diff --git a/src/plugins/index_pattern_management/public/components/field_editor/field_editor.tsx b/src/plugins/index_pattern_management/public/components/field_editor/field_editor.tsx index f22981de85749..829536063a26c 100644 --- a/src/plugins/index_pattern_management/public/components/field_editor/field_editor.tsx +++ b/src/plugins/index_pattern_management/public/components/field_editor/field_editor.tsx @@ -25,7 +25,6 @@ import { EuiFormRow, EuiIcon, EuiLink, - EuiOverlayMask, EuiSelect, EuiSpacer, EuiText, @@ -643,42 +642,40 @@ export class FieldEditor extends PureComponent - { - this.hideDeleteModal(); - this.deleteField(); - }} - cancelButtonText={i18n.translate('indexPatternManagement.deleteField.cancelButton', { - defaultMessage: 'Cancel', - })} - confirmButtonText={i18n.translate('indexPatternManagement.deleteField.deleteButton', { - defaultMessage: 'Delete', - })} - buttonColor="danger" - defaultFocusedButton={EUI_MODAL_CONFIRM_BUTTON} - > -

- -
-
- - ), - }} - /> -

-
-
+ { + this.hideDeleteModal(); + this.deleteField(); + }} + cancelButtonText={i18n.translate('indexPatternManagement.deleteField.cancelButton', { + defaultMessage: 'Cancel', + })} + confirmButtonText={i18n.translate('indexPatternManagement.deleteField.deleteButton', { + defaultMessage: 'Delete', + })} + buttonColor="danger" + defaultFocusedButton={EUI_MODAL_CONFIRM_BUTTON} + > +

+ +
+
+ + ), + }} + /> +

+
) : null; }; diff --git a/src/plugins/kibana_react/public/table_list_view/table_list_view.tsx b/src/plugins/kibana_react/public/table_list_view/table_list_view.tsx index 7b3322f5f6c2d..fa0a32fc3d542 100644 --- a/src/plugins/kibana_react/public/table_list_view/table_list_view.tsx +++ b/src/plugins/kibana_react/public/table_list_view/table_list_view.tsx @@ -21,7 +21,6 @@ import { EuiFlexItem, EuiButton, EuiSpacer, - EuiOverlayMask, EuiConfirmModal, EuiCallOut, EuiBasicTableColumn, @@ -238,42 +237,40 @@ class TableListView extends React.Component - - } - buttonColor="danger" - onCancel={this.closeDeleteModal} - onConfirm={this.deleteSelectedItems} - cancelButtonText={ - - } - confirmButtonText={deleteButton} - defaultFocusedButton="cancel" - > -

- -

-
-
+ + } + buttonColor="danger" + onCancel={this.closeDeleteModal} + onConfirm={this.deleteSelectedItems} + cancelButtonText={ + + } + confirmButtonText={deleteButton} + defaultFocusedButton="cancel" + > +

+ +

+
); } diff --git a/src/plugins/saved_objects/public/save_modal/__snapshots__/saved_object_save_modal.test.tsx.snap b/src/plugins/saved_objects/public/save_modal/__snapshots__/saved_object_save_modal.test.tsx.snap index f88039fbda9ba..1f05ed6b94405 100644 --- a/src/plugins/saved_objects/public/save_modal/__snapshots__/saved_object_save_modal.test.tsx.snap +++ b/src/plugins/saved_objects/public/save_modal/__snapshots__/saved_object_save_modal.test.tsx.snap @@ -1,407 +1,399 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`SavedObjectSaveModal should render matching snapshot 1`] = ` - +
- - - - - - - - - + + + - + + + + + - - + - } - labelType="label" - > - + + - - - - - - - - - Save - - - + + + + + + + + + Save + +
-
+ `; exports[`SavedObjectSaveModal should render matching snapshot when custom isValid is set 1`] = ` - +
- - - - + + - - - - - - } - labelType="label" - > - + + + + + - - + - } - labelType="label" - > - + + - - - - - - - - - Save - - - + + + + + + + + + Save + +
-
+ `; exports[`SavedObjectSaveModal should render matching snapshot when custom isValid is set 2`] = ` - +
- - - - + + - - - - - - } - labelType="label" - > - + + + + + - - + - } - labelType="label" - > - + + - - - - - - - - - Save - - - + + + + + + + + + Save + +
-
+ `; exports[`SavedObjectSaveModal should render matching snapshot when given options 1`] = ` - +
- - - - + + - - - - - - - - } - labelType="label" - > - + + + + + + + - - + - } - labelType="label" - > - + + - -
- Hello! Main options -
-
- -
- Hey there! Options on the right -
-
-
-
-
- - - - - - Save - - -
+ } + labelType="label" + > + + +
+ Hello! Main options +
+ + +
+ Hey there! Options on the right +
+
+ + + + + + + + + Save + + -
+ `; diff --git a/src/plugins/saved_objects/public/save_modal/saved_object_save_modal.tsx b/src/plugins/saved_objects/public/save_modal/saved_object_save_modal.tsx index 39c87c9da60c2..e476d62a0e793 100644 --- a/src/plugins/saved_objects/public/save_modal/saved_object_save_modal.tsx +++ b/src/plugins/saved_objects/public/save_modal/saved_object_save_modal.tsx @@ -21,7 +21,6 @@ import { EuiModalFooter, EuiModalHeader, EuiModalHeaderTitle, - EuiOverlayMask, EuiSpacer, EuiSwitch, EuiSwitchEvent, @@ -123,52 +122,48 @@ export class SavedObjectSaveModal extends React.Component ); return ( - +
- - - - - - - - - {this.renderDuplicateTitleCallout(duplicateWarningId)} - - - {!this.props.showDescription && this.props.description && ( - - {this.props.description} - - )} - {formBody} - {this.renderCopyOnSave()} - - - - - - - - - {this.renderConfirmButton()} - - + + + + + + + + {this.renderDuplicateTitleCallout(duplicateWarningId)} + + + {!this.props.showDescription && this.props.description && ( + + {this.props.description} + + )} + {formBody} + {this.renderCopyOnSave()} + + + + + + + + + {this.renderConfirmButton()} +
-
+ ); } diff --git a/src/plugins/saved_objects_management/public/management_section/objects_table/components/delete_confirm_modal.tsx b/src/plugins/saved_objects_management/public/management_section/objects_table/components/delete_confirm_modal.tsx index f67e7bd0b568c..f6f00c95d9bf1 100644 --- a/src/plugins/saved_objects_management/public/management_section/objects_table/components/delete_confirm_modal.tsx +++ b/src/plugins/saved_objects_management/public/management_section/objects_table/components/delete_confirm_modal.tsx @@ -53,90 +53,88 @@ export const DeleteConfirmModal: FC = ({ // can't use `EuiConfirmModal` here as the confirm modal body is wrapped // inside a `

` element, causing UI glitches with the table. return ( - - - - - - - - -

- -

- - ( - - - - ), - }, - { - field: 'id', - name: i18n.translate( - 'savedObjectsManagement.objectsTable.deleteSavedObjectsConfirmModal.idColumnName', - { defaultMessage: 'Id' } - ), - }, - { - field: 'meta.title', - name: i18n.translate( - 'savedObjectsManagement.objectsTable.deleteSavedObjectsConfirmModal.titleColumnName', - { defaultMessage: 'Title' } - ), - }, - ]} - pagination={true} - sorting={false} + + + + - - - - - - - - - - - - - - - - - - - - - + + + +

+ +

+ + ( + + + + ), + }, + { + field: 'id', + name: i18n.translate( + 'savedObjectsManagement.objectsTable.deleteSavedObjectsConfirmModal.idColumnName', + { defaultMessage: 'Id' } + ), + }, + { + field: 'meta.title', + name: i18n.translate( + 'savedObjectsManagement.objectsTable.deleteSavedObjectsConfirmModal.titleColumnName', + { defaultMessage: 'Title' } + ), + }, + ]} + pagination={true} + sorting={false} + /> +
+ + + + + + + + + + + + + + + + + + + ); }; diff --git a/src/plugins/saved_objects_management/public/management_section/objects_table/components/export_modal.tsx b/src/plugins/saved_objects_management/public/management_section/objects_table/components/export_modal.tsx index 693fe00ffedcc..0699f77f57521 100644 --- a/src/plugins/saved_objects_management/public/management_section/objects_table/components/export_modal.tsx +++ b/src/plugins/saved_objects_management/public/management_section/objects_table/components/export_modal.tsx @@ -8,7 +8,6 @@ import React, { FC } from 'react'; import { - EuiOverlayMask, EuiModal, EuiModalHeader, EuiModalHeaderTitle, @@ -47,80 +46,78 @@ export const ExportModal: FC = ({ onIncludeReferenceChange, }) => { return ( - - - - + + + + + + + + - - - - - } - labelType="legend" - > - { - onSelectedOptionsChange({ - ...selectedOptions, - ...{ - [optionId]: !selectedOptions[optionId], - }, - }); - }} + id="savedObjectsManagement.objectsTable.exportObjectsConfirmModalDescription" + defaultMessage="Select which types to export" /> - - - - } - checked={includeReferences} - onChange={() => onIncludeReferenceChange(!includeReferences)} + } + labelType="legend" + > + { + onSelectedOptionsChange({ + ...selectedOptions, + ...{ + [optionId]: !selectedOptions[optionId], + }, + }); + }} /> - - - - - - - - - - - - - - - - - - - - - + + + + } + checked={includeReferences} + onChange={() => onIncludeReferenceChange(!includeReferences)} + /> + + + + + + + + + + + + + + + + + + + + ); }; diff --git a/src/plugins/saved_objects_management/public/management_section/objects_table/components/overwrite_modal.tsx b/src/plugins/saved_objects_management/public/management_section/objects_table/components/overwrite_modal.tsx index 66753e81ccd3f..cfe0b2be1d3c0 100644 --- a/src/plugins/saved_objects_management/public/management_section/objects_table/components/overwrite_modal.tsx +++ b/src/plugins/saved_objects_management/public/management_section/objects_table/components/overwrite_modal.tsx @@ -7,13 +7,7 @@ */ import React, { useState, Fragment, ReactNode } from 'react'; -import { - EuiOverlayMask, - EuiConfirmModal, - EUI_MODAL_CONFIRM_BUTTON, - EuiText, - EuiSuperSelect, -} from '@elastic/eui'; +import { EuiConfirmModal, EUI_MODAL_CONFIRM_BUTTON, EuiText, EuiSuperSelect } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import moment from 'moment'; import { FailedImportConflict } from '../../../lib/resolve_import_errors'; @@ -98,29 +92,27 @@ export const OverwriteModal = ({ conflict, onFinish }: OverwriteModalProps) => { } ); return ( - - onFinish(false)} - onConfirm={() => onFinish(true, destinationId)} - defaultFocusedButton={EUI_MODAL_CONFIRM_BUTTON} - maxWidth="500px" - > -

{bodyText}

- {selectControl} -
-
+ onFinish(false)} + onConfirm={() => onFinish(true, destinationId)} + defaultFocusedButton={EUI_MODAL_CONFIRM_BUTTON} + maxWidth="500px" + > +

{bodyText}

+ {selectControl} +
); }; diff --git a/src/plugins/visualizations/public/wizard/new_vis_modal.tsx b/src/plugins/visualizations/public/wizard/new_vis_modal.tsx index ff352003609a9..d36b734f75be2 100644 --- a/src/plugins/visualizations/public/wizard/new_vis_modal.tsx +++ b/src/plugins/visualizations/public/wizard/new_vis_modal.tsx @@ -8,7 +8,7 @@ import React from 'react'; -import { EuiModal, EuiOverlayMask } from '@elastic/eui'; +import { EuiModal } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { METRIC_TYPE, UiCounterMetricType } from '@kbn/analytics'; @@ -121,7 +121,7 @@ class NewVisModal extends React.Component ); - return {selectionModal}; + return selectionModal; } private onCloseModal = () => { diff --git a/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/List/ConfirmDeleteModal.tsx b/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/List/ConfirmDeleteModal.tsx index 6554f48ea3c2b..081a3dbc907c5 100644 --- a/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/List/ConfirmDeleteModal.tsx +++ b/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/List/ConfirmDeleteModal.tsx @@ -6,7 +6,7 @@ */ import React, { useState } from 'react'; -import { EuiConfirmModal, EuiOverlayMask } from '@elastic/eui'; +import { EuiConfirmModal } from '@elastic/eui'; import { NotificationsStart } from 'kibana/public'; import { i18n } from '@kbn/i18n'; import { getOptionLabel } from '../../../../../../common/agent_configuration/all_option'; @@ -29,41 +29,39 @@ export function ConfirmDeleteModal({ config, onCancel, onConfirm }: Props) { const { toasts } = useApmPluginContext().core.notifications; return ( - - { + setIsDeleting(true); + await deleteConfig(config, toasts); + setIsDeleting(false); + onConfirm(); + }} + cancelButtonText={i18n.translate( + 'xpack.apm.agentConfig.deleteModal.cancel', + { defaultMessage: `Cancel` } + )} + confirmButtonText={i18n.translate( + 'xpack.apm.agentConfig.deleteModal.confirm', + { defaultMessage: `Delete` } + )} + confirmButtonDisabled={isDeleting} + buttonColor="danger" + defaultFocusedButton="confirm" + > +

+ {i18n.translate('xpack.apm.agentConfig.deleteModal.text', { + defaultMessage: `You are about to delete the configuration for service "{serviceName}" and environment "{environment}".`, + values: { + serviceName: getOptionLabel(config.service.name), + environment: getOptionLabel(config.service.environment), + }, })} - onCancel={onCancel} - onConfirm={async () => { - setIsDeleting(true); - await deleteConfig(config, toasts); - setIsDeleting(false); - onConfirm(); - }} - cancelButtonText={i18n.translate( - 'xpack.apm.agentConfig.deleteModal.cancel', - { defaultMessage: `Cancel` } - )} - confirmButtonText={i18n.translate( - 'xpack.apm.agentConfig.deleteModal.confirm', - { defaultMessage: `Delete` } - )} - confirmButtonDisabled={isDeleting} - buttonColor="danger" - defaultFocusedButton="confirm" - > -

- {i18n.translate('xpack.apm.agentConfig.deleteModal.text', { - defaultMessage: `You are about to delete the configuration for service "{serviceName}" and environment "{environment}".`, - values: { - serviceName: getOptionLabel(config.service.name), - environment: getOptionLabel(config.service.environment), - }, - })} -

-
-
+

+ ); } diff --git a/x-pack/plugins/beats_management/public/components/table/controls/action_control.tsx b/x-pack/plugins/beats_management/public/components/table/controls/action_control.tsx index c498f9e06e1d3..5badef9a71fe1 100644 --- a/x-pack/plugins/beats_management/public/components/table/controls/action_control.tsx +++ b/x-pack/plugins/beats_management/public/components/table/controls/action_control.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { EuiButton, EuiConfirmModal, EuiOverlayMask } from '@elastic/eui'; +import { EuiButton, EuiConfirmModal } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import React from 'react'; import { AssignmentActionType } from '../table'; @@ -58,40 +58,38 @@ export class ActionControl extends React.PureComponent {this.state.showModal && ( - - + } + confirmButtonText={ + + } + onConfirm={() => { + actionHandler(action); + this.setState({ showModal: false }); + }} + onCancel={() => this.setState({ showModal: false })} + title={ + warningHeading ? ( + warningHeading + ) : ( - } - confirmButtonText={ - - } - onConfirm={() => { - actionHandler(action); - this.setState({ showModal: false }); - }} - onCancel={() => this.setState({ showModal: false })} - title={ - warningHeading ? ( - warningHeading - ) : ( - - ) - } - > - {warningMessage} - - + ) + } + > + {warningMessage} + )}
); diff --git a/x-pack/plugins/beats_management/public/pages/overview/enrolled_beats.tsx b/x-pack/plugins/beats_management/public/pages/overview/enrolled_beats.tsx index f09d34eaa6e61..0ab02430e90e6 100644 --- a/x-pack/plugins/beats_management/public/pages/overview/enrolled_beats.tsx +++ b/x-pack/plugins/beats_management/public/pages/overview/enrolled_beats.tsx @@ -13,7 +13,6 @@ import { EuiModalBody, EuiModalHeader, EuiModalHeaderTitle, - EuiOverlayMask, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage, InjectedIntl, injectI18n } from '@kbn/i18n/react'; @@ -104,58 +103,56 @@ class BeatsPageComponent extends React.PureComponent { {this.props.location.pathname === '/overview/enrolled_beats/enroll' && ( - - { - this.props.setUrlState({ - enrollmentToken: '', - }); - this.props.goTo(`/overview/enrolled_beats`); - }} - style={{ width: '640px' }} - > - - - - - - - { - const enrollmentTokens = await this.props.libs.tokens.createEnrollmentTokens(); - this.props.setUrlState({ - enrollmentToken: enrollmentTokens[0], - }); - }} - onBeatEnrolled={() => { - this.props.setUrlState({ - enrollmentToken: '', - }); - }} + { + this.props.setUrlState({ + enrollmentToken: '', + }); + this.props.goTo(`/overview/enrolled_beats`); + }} + style={{ width: '640px' }} + > + + + - {!this.props.urlState.enrollmentToken && ( - - { - this.props.goTo('/overview/enrolled_beats'); - }} - > - Done - - - )} - - - + + + + { + const enrollmentTokens = await this.props.libs.tokens.createEnrollmentTokens(); + this.props.setUrlState({ + enrollmentToken: enrollmentTokens[0], + }); + }} + onBeatEnrolled={() => { + this.props.setUrlState({ + enrollmentToken: '', + }); + }} + /> + {!this.props.urlState.enrollmentToken && ( + + { + this.props.goTo('/overview/enrolled_beats'); + }} + > + Done + + + )} + + )} ); diff --git a/x-pack/plugins/canvas/public/components/asset_manager/asset_manager.component.tsx b/x-pack/plugins/canvas/public/components/asset_manager/asset_manager.component.tsx index fcd34a8972744..2a4452bc5101b 100644 --- a/x-pack/plugins/canvas/public/components/asset_manager/asset_manager.component.tsx +++ b/x-pack/plugins/canvas/public/components/asset_manager/asset_manager.component.tsx @@ -19,7 +19,6 @@ import { EuiModalFooter, EuiModalHeader, EuiModalHeaderTitle, - EuiOverlayMask, EuiProgress, EuiSpacer, EuiText, @@ -74,71 +73,69 @@ export const AssetManager: FC = (props) => { }; return ( - - onClose()} - className="canvasAssetManager canvasModal--fixedSize" - maxWidth="1000px" - > - - - {strings.getModalTitle()} - - - - {isLoading ? ( - - ) : ( - - )} - - - - - -

{strings.getDescription()}

-
- - {assets.length ? ( - - {assets.map((asset) => ( - - ))} - - ) : ( - emptyAssets - )} -
- - - - onClose()} + className="canvasAssetManager canvasModal--fixedSize" + maxWidth="1000px" + > + + + {strings.getModalTitle()} + + + + {isLoading ? ( + + ) : ( + - - - - {strings.getSpaceUsedText(percentageUsed)} - - - - onClose()}> - {strings.getModalCloseButtonLabel()} - - -
-
+ )} + + + + + +

{strings.getDescription()}

+
+ + {assets.length ? ( + + {assets.map((asset) => ( + + ))} + + ) : ( + emptyAssets + )} +
+ + + + + + + + {strings.getSpaceUsedText(percentageUsed)} + + + + onClose()}> + {strings.getModalCloseButtonLabel()} + + + ); }; diff --git a/x-pack/plugins/canvas/public/components/confirm_modal/confirm_modal.tsx b/x-pack/plugins/canvas/public/components/confirm_modal/confirm_modal.tsx index 38be3b8559af2..521ced0d731f2 100644 --- a/x-pack/plugins/canvas/public/components/confirm_modal/confirm_modal.tsx +++ b/x-pack/plugins/canvas/public/components/confirm_modal/confirm_modal.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { EuiConfirmModal, EuiOverlayMask } from '@elastic/eui'; +import { EuiConfirmModal } from '@elastic/eui'; import PropTypes from 'prop-types'; import React, { FunctionComponent } from 'react'; @@ -39,21 +39,19 @@ export const ConfirmModal: FunctionComponent = (props) => { } return ( - - - {message} - - + + {message} + ); }; diff --git a/x-pack/plugins/canvas/public/components/datasource/datasource_preview/datasource_preview.js b/x-pack/plugins/canvas/public/components/datasource/datasource_preview/datasource_preview.js index 4a5861b41d06c..a55f73a087467 100644 --- a/x-pack/plugins/canvas/public/components/datasource/datasource_preview/datasource_preview.js +++ b/x-pack/plugins/canvas/public/components/datasource/datasource_preview/datasource_preview.js @@ -8,7 +8,6 @@ import React from 'react'; import PropTypes from 'prop-types'; import { - EuiOverlayMask, EuiModal, EuiModalBody, EuiModalHeader, @@ -27,48 +26,46 @@ const { DatasourceDatasourcePreview: strings } = ComponentStrings; const { DatasourceDatasourceComponent: datasourceStrings } = ComponentStrings; export const DatasourcePreview = ({ done, datatable }) => ( - - - - {strings.getModalTitle()} - - - -

- {datasourceStrings.getSaveButtonLabel()}, - }} + + + {strings.getModalTitle()} + + + +

+ {datasourceStrings.getSaveButtonLabel()}, + }} + /> +

+
+ + {datatable.type === 'error' ? ( + + ) : ( + + {datatable.rows.length > 0 ? ( + + ) : ( + {strings.getEmptyTitle()}} + titleSize="s" + body={ +

+ {strings.getEmptyFirstLineDescription()} +
+ {strings.getEmptySecondLineDescription()} +

+ } /> -

- - - {datatable.type === 'error' ? ( - - ) : ( - - {datatable.rows.length > 0 ? ( - - ) : ( - {strings.getEmptyTitle()}} - titleSize="s" - body={ -

- {strings.getEmptyFirstLineDescription()} -
- {strings.getEmptySecondLineDescription()} -

- } - /> - )} -
- )} -
-
-
+ )} + + )} + + ); DatasourcePreview.propTypes = { diff --git a/x-pack/plugins/canvas/public/components/keyboard_shortcuts_doc/__stories__/__snapshots__/keyboard_shortcuts_doc.stories.storyshot b/x-pack/plugins/canvas/public/components/keyboard_shortcuts_doc/__stories__/__snapshots__/keyboard_shortcuts_doc.stories.storyshot index aea9626d7b57a..a28986c0418a2 100644 --- a/x-pack/plugins/canvas/public/components/keyboard_shortcuts_doc/__stories__/__snapshots__/keyboard_shortcuts_doc.stories.storyshot +++ b/x-pack/plugins/canvas/public/components/keyboard_shortcuts_doc/__stories__/__snapshots__/keyboard_shortcuts_doc.stories.storyshot @@ -5,7 +5,7 @@ exports[`Storyshots components/KeyboardShortcutsDoc default 1`] = ` data-eui="EuiFocusTrap" >
diff --git a/x-pack/plugins/canvas/public/components/saved_elements_modal/saved_elements_modal.component.tsx b/x-pack/plugins/canvas/public/components/saved_elements_modal/saved_elements_modal.component.tsx index e99cc60dfcaa4..bc0039245f432 100644 --- a/x-pack/plugins/canvas/public/components/saved_elements_modal/saved_elements_modal.component.tsx +++ b/x-pack/plugins/canvas/public/components/saved_elements_modal/saved_elements_modal.component.tsx @@ -23,7 +23,6 @@ import { EuiEmptyPrompt, EuiFieldSearch, EuiSpacer, - EuiOverlayMask, EuiButton, } from '@elastic/eui'; import { sortBy } from 'lodash'; @@ -117,16 +116,14 @@ export const SavedElementsModal: FunctionComponent = ({ } return ( - - - + ); }; @@ -176,40 +173,34 @@ export const SavedElementsModal: FunctionComponent = ({ return ( - - - - - {strings.getModalTitle()} - - - - - - - {customElementContent} - - - - {strings.getSavedElementsModalCloseButtonLabel()} - - - - + + + + {strings.getModalTitle()} + + + + + + + {customElementContent} + + + + {strings.getSavedElementsModalCloseButtonLabel()} + + + {renderDeleteModal()} {renderEditModal()} diff --git a/x-pack/plugins/canvas/public/components/toolbar/toolbar.component.tsx b/x-pack/plugins/canvas/public/components/toolbar/toolbar.component.tsx index edf7d33eff79c..6e5c936a113bf 100644 --- a/x-pack/plugins/canvas/public/components/toolbar/toolbar.component.tsx +++ b/x-pack/plugins/canvas/public/components/toolbar/toolbar.component.tsx @@ -12,7 +12,6 @@ import { EuiButtonIcon, EuiFlexGroup, EuiFlexItem, - EuiOverlayMask, EuiModal, EuiModalFooter, EuiButton, @@ -93,16 +92,14 @@ export const Toolbar: FC = ({ const openWorkpadManager = () => setShowWorkpadManager(true); const workpadManager = ( - - - - - - {strings.getWorkpadManagerCloseButtonLabel()} - - - - + + + + + {strings.getWorkpadManagerCloseButtonLabel()} + + + ); const trays = { diff --git a/x-pack/plugins/canvas/public/components/var_config/__stories__/__snapshots__/var_config.stories.storyshot b/x-pack/plugins/canvas/public/components/var_config/__stories__/__snapshots__/var_config.stories.storyshot index bf5629596d6b6..6277c599032c1 100644 --- a/x-pack/plugins/canvas/public/components/var_config/__stories__/__snapshots__/var_config.stories.storyshot +++ b/x-pack/plugins/canvas/public/components/var_config/__stories__/__snapshots__/var_config.stories.storyshot @@ -77,8 +77,11 @@ exports[`Storyshots components/Variables/VarConfig default 1`] = `
= ({ )} {isModalVisible ? ( - - - + ) : null} ); diff --git a/x-pack/plugins/canvas/public/components/workpad_header/share_menu/__stories__/__snapshots__/pdf_panel.stories.storyshot b/x-pack/plugins/canvas/public/components/workpad_header/share_menu/__stories__/__snapshots__/pdf_panel.stories.storyshot index 4e84a9d5a0d21..010037bee4a0f 100644 --- a/x-pack/plugins/canvas/public/components/workpad_header/share_menu/__stories__/__snapshots__/pdf_panel.stories.storyshot +++ b/x-pack/plugins/canvas/public/components/workpad_header/share_menu/__stories__/__snapshots__/pdf_panel.stories.storyshot @@ -150,8 +150,11 @@ exports[`Storyshots components/WorkpadHeader/ShareMenu/PDFPanel default 1`] = `
diff --git a/x-pack/plugins/cross_cluster_replication/public/app/components/auto_follow_pattern_delete_provider.js b/x-pack/plugins/cross_cluster_replication/public/app/components/auto_follow_pattern_delete_provider.js index b07583837636e..034e08b5c6ab8 100644 --- a/x-pack/plugins/cross_cluster_replication/public/app/components/auto_follow_pattern_delete_provider.js +++ b/x-pack/plugins/cross_cluster_replication/public/app/components/auto_follow_pattern_delete_provider.js @@ -9,7 +9,7 @@ import React, { PureComponent, Fragment } from 'react'; import { connect } from 'react-redux'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; -import { EuiConfirmModal, EuiOverlayMask } from '@elastic/eui'; +import { EuiConfirmModal } from '@elastic/eui'; import { deleteAutoFollowPattern } from '../store/actions'; import { arrify } from '../../../common/services/utils'; @@ -61,45 +61,43 @@ class AutoFollowPatternDeleteProviderUi extends PureComponent { ); return ( - - {/* eslint-disable-next-line jsx-a11y/mouse-events-have-key-events */} - - {!isSingle && ( - -

- -

-
    - {ids.map((id) => ( -
  • {id}
  • - ))} -
-
- )} -
-
+ // eslint-disable-next-line jsx-a11y/mouse-events-have-key-events + + {!isSingle && ( + +

+ +

+
    + {ids.map((id) => ( +
  • {id}
  • + ))} +
+
+ )} +
); }; diff --git a/x-pack/plugins/cross_cluster_replication/public/app/components/follower_index_actions_providers/follower_index_pause_provider.js b/x-pack/plugins/cross_cluster_replication/public/app/components/follower_index_actions_providers/follower_index_pause_provider.js index e84816d0d71af..34697a80121cc 100644 --- a/x-pack/plugins/cross_cluster_replication/public/app/components/follower_index_actions_providers/follower_index_pause_provider.js +++ b/x-pack/plugins/cross_cluster_replication/public/app/components/follower_index_actions_providers/follower_index_pause_provider.js @@ -10,7 +10,7 @@ import PropTypes from 'prop-types'; import { connect } from 'react-redux'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; -import { EuiConfirmModal, EuiOverlayMask } from '@elastic/eui'; +import { EuiConfirmModal } from '@elastic/eui'; import { pauseFollowerIndex } from '../../store/actions'; import { arrify } from '../../../../common/services/utils'; @@ -69,64 +69,62 @@ class FollowerIndexPauseProviderUi extends PureComponent { const hasCustomSettings = indices.some((index) => !areAllSettingsDefault(index)); return ( - - {/* eslint-disable-next-line jsx-a11y/mouse-events-have-key-events */} - - {hasCustomSettings && ( -

- {isSingle ? ( - + {hasCustomSettings && ( +

+ {isSingle ? ( + - ) : ( - - )} + /> + )} +

+ )} + + {!isSingle && ( + +

+

- )} - - {!isSingle && ( - -

- -

- -
    - {indices.map((index) => ( -
  • {index.name}
  • - ))} -
-
- )} -
-
+ +
    + {indices.map((index) => ( +
  • {index.name}
  • + ))} +
+ + )} + ); }; diff --git a/x-pack/plugins/cross_cluster_replication/public/app/components/follower_index_actions_providers/follower_index_resume_provider.js b/x-pack/plugins/cross_cluster_replication/public/app/components/follower_index_actions_providers/follower_index_resume_provider.js index 0517f84159912..91c6cb6e243ac 100644 --- a/x-pack/plugins/cross_cluster_replication/public/app/components/follower_index_actions_providers/follower_index_resume_provider.js +++ b/x-pack/plugins/cross_cluster_replication/public/app/components/follower_index_actions_providers/follower_index_resume_provider.js @@ -10,7 +10,7 @@ import PropTypes from 'prop-types'; import { connect } from 'react-redux'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; -import { EuiConfirmModal, EuiLink, EuiOverlayMask } from '@elastic/eui'; +import { EuiConfirmModal, EuiLink } from '@elastic/eui'; import { reactRouterNavigate } from '../../../../../../../src/plugins/kibana_react/public'; import { routing } from '../../services/routing'; import { resumeFollowerIndex } from '../../store/actions'; @@ -68,77 +68,75 @@ class FollowerIndexResumeProviderUi extends PureComponent { ); return ( - - {/* eslint-disable-next-line jsx-a11y/mouse-events-have-key-events */} - - {isSingle ? ( + // eslint-disable-next-line jsx-a11y/mouse-events-have-key-events + + {isSingle ? ( +

+ + + + ), + }} + /> +

+ ) : ( +

- - - ), - }} + id="xpack.crossClusterReplication.resumeFollowerIndex.confirmModal.multipleResumeDescriptionWithSettingWarning" + defaultMessage="Replication resumes using the default advanced settings." />

- ) : ( - -

- -

-

- -

+

+ +

-
    - {ids.map((id) => ( -
  • {id}
  • - ))} -
-
- )} -
-
+
    + {ids.map((id) => ( +
  • {id}
  • + ))} +
+ + )} + ); }; diff --git a/x-pack/plugins/cross_cluster_replication/public/app/components/follower_index_actions_providers/follower_index_unfollow_provider.js b/x-pack/plugins/cross_cluster_replication/public/app/components/follower_index_actions_providers/follower_index_unfollow_provider.js index 9b0f0ad3111e0..72d262bcf7af3 100644 --- a/x-pack/plugins/cross_cluster_replication/public/app/components/follower_index_actions_providers/follower_index_unfollow_provider.js +++ b/x-pack/plugins/cross_cluster_replication/public/app/components/follower_index_actions_providers/follower_index_unfollow_provider.js @@ -10,7 +10,7 @@ import PropTypes from 'prop-types'; import { connect } from 'react-redux'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; -import { EuiConfirmModal, EuiOverlayMask } from '@elastic/eui'; +import { EuiConfirmModal } from '@elastic/eui'; import { unfollowLeaderIndex } from '../../store/actions'; import { arrify } from '../../../../common/services/utils'; @@ -67,58 +67,56 @@ class FollowerIndexUnfollowProviderUi extends PureComponent { ); return ( - - {/* eslint-disable-next-line jsx-a11y/mouse-events-have-key-events */} - - {isSingle ? ( - -

- + {isSingle ? ( + +

+ -

-
- ) : ( - -

- -

-
    - {ids.map((id) => ( -
  • {id}
  • - ))} -
-
- )} -
-
+ /> +

+
    + {ids.map((id) => ( +
  • {id}
  • + ))} +
+ + )} + ); }; diff --git a/x-pack/plugins/cross_cluster_replication/public/app/sections/follower_index_edit/follower_index_edit.js b/x-pack/plugins/cross_cluster_replication/public/app/sections/follower_index_edit/follower_index_edit.js index 8fb4fb27006cb..8d6e47d4004b6 100644 --- a/x-pack/plugins/cross_cluster_replication/public/app/sections/follower_index_edit/follower_index_edit.js +++ b/x-pack/plugins/cross_cluster_replication/public/app/sections/follower_index_edit/follower_index_edit.js @@ -15,7 +15,6 @@ import { EuiConfirmModal, EuiFlexGroup, EuiFlexItem, - EuiOverlayMask, EuiPageContent, EuiSpacer, } from '@elastic/eui'; @@ -182,47 +181,45 @@ export class FollowerIndexEdit extends PureComponent { ); return ( - - - ) : ( - - ) + -

- {isPaused ? ( - - ) : ( - + ) : ( + + ) + } + > +

+ {isPaused ? ( + + ) : ( + - )} -

-
-
+ /> + )} +

+ ); }; diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions/delete_button.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions/delete_button.tsx index d505752ec3fad..6a952d2f8d9d7 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions/delete_button.tsx +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions/delete_button.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { EuiConfirmModal, EuiOverlayMask } from '@elastic/eui'; +import { EuiConfirmModal } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import React, { useState } from 'react'; @@ -44,24 +44,22 @@ const DeleteConfirm = ({ }); return ( - - { - setIsLoading(true); - await api.sendCancel(id); - onActionComplete(); - }} - confirmButtonText={confirm} - confirmButtonDisabled={isLoading} - cancelButtonText={cancel} - defaultFocusedButton="confirm" - buttonColor="danger" - > - {message} - - + { + setIsLoading(true); + await api.sendCancel(id); + onActionComplete(); + }} + confirmButtonText={confirm} + confirmButtonDisabled={isLoading} + cancelButtonText={cancel} + defaultFocusedButton="confirm" + buttonColor="danger" + > + {message} + ); }; diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions/extend_button.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions/extend_button.tsx index 381c44b1bf7be..856e7c8d43483 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions/extend_button.tsx +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions/extend_button.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { EuiConfirmModal, EuiOverlayMask } from '@elastic/eui'; +import { EuiConfirmModal } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import React, { useState } from 'react'; @@ -52,26 +52,24 @@ const ExtendConfirm = ({ }); return ( - - { - setIsLoading(true); - await api.sendExtend(id, `${newExpiration.toISOString()}`); - setIsLoading(false); - onConfirmDismiss(); - onActionComplete(); - }} - confirmButtonText={confirm} - confirmButtonDisabled={isLoading} - cancelButtonText={extend} - defaultFocusedButton="confirm" - buttonColor="primary" - > - {message} - - + { + setIsLoading(true); + await api.sendExtend(id, `${newExpiration.toISOString()}`); + setIsLoading(false); + onConfirmDismiss(); + onActionComplete(); + }} + confirmButtonText={confirm} + confirmButtonDisabled={isLoading} + cancelButtonText={extend} + defaultFocusedButton="confirm" + buttonColor="primary" + > + {message} + ); }; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/customization_modal.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/customization_modal.tsx index e05fc10053ff1..9bc838c01f636 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/customization_modal.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/customization_modal.tsx @@ -20,7 +20,6 @@ import { EuiModalFooter, EuiModalHeader, EuiModalHeaderTitle, - EuiOverlayMask, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; @@ -62,97 +61,94 @@ export const CustomizationModal: React.FC = ({ ); return ( - - - - - {i18n.translate( - 'xpack.enterpriseSearch.appSearch.documents.search.customizationModal.title', + + + + {i18n.translate( + 'xpack.enterpriseSearch.appSearch.documents.search.customizationModal.title', + { + defaultMessage: 'Customize document search', + } + )} + + + + + - - - - - - - - - - - - - - {i18n.translate( - 'xpack.enterpriseSearch.appSearch.documents.search.customizationModal.cancel', + fullWidth + helpText={i18n.translate( + 'xpack.enterpriseSearch.appSearch.documents.search.customizationModal.filterFields', { - defaultMessage: 'Cancel', + defaultMessage: + 'Faceted values rendered as filters and available as query refinement', } )} - - { - onSave({ - filterFields: selectedFilterFields.map(comboBoxOptionToFieldName), - sortFields: selectedSortFields.map(comboBoxOptionToFieldName), - }); - }} > - {i18n.translate( - 'xpack.enterpriseSearch.appSearch.documents.search.customizationModal.save', + + + - - - + > + + + + + + + {i18n.translate( + 'xpack.enterpriseSearch.appSearch.documents.search.customizationModal.cancel', + { + defaultMessage: 'Cancel', + } + )} + + { + onSave({ + filterFields: selectedFilterFields.map(comboBoxOptionToFieldName), + sortFields: selectedSortFields.map(comboBoxOptionToFieldName), + }); + }} + > + {i18n.translate( + 'xpack.enterpriseSearch.appSearch.documents.search.customizationModal.save', + { + defaultMessage: 'Save', + } + )} + + + ); }; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/settings/log_retention/log_retention_confirmation_modal.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/settings/log_retention/log_retention_confirmation_modal.tsx index ca1fa9a8d0737..ba79d62cfe615 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/settings/log_retention/log_retention_confirmation_modal.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/settings/log_retention/log_retention_confirmation_modal.tsx @@ -9,7 +9,7 @@ import React from 'react'; import { useActions, useValues } from 'kea'; -import { EuiTextColor, EuiOverlayMask } from '@elastic/eui'; +import { EuiTextColor } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { LogRetentionLogic, LogRetentionOptions } from '../../log_retention'; @@ -40,7 +40,7 @@ export const LogRetentionConfirmationModal: React.FC = () => { } return ( - + <> {openedModal === LogRetentionOptions.Analytics && ( { onSave={() => saveLogRetention(LogRetentionOptions.API, false)} /> )} - + ); }; diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/schema/schema_add_field_modal.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/schema/schema_add_field_modal.tsx index bbde6c5d3b55d..bd9b6b51a43b1 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/schema/schema_add_field_modal.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/shared/schema/schema_add_field_modal.tsx @@ -20,7 +20,6 @@ import { EuiModalFooter, EuiModalHeader, EuiModalHeaderTitle, - EuiOverlayMask, EuiSelect, EuiSpacer, } from '@elastic/eui'; @@ -79,71 +78,69 @@ export const SchemaAddFieldModal: React.FC = ({ ); return ( - +
- - - {FIELD_NAME_MODAL_TITLE} - - -

{FIELD_NAME_MODAL_DESCRIPTION}

- - - - - + {FIELD_NAME_MODAL_TITLE} + + +

{FIELD_NAME_MODAL_DESCRIPTION}

+ + + + + + - - - - - - updateNewFieldType(e.target.value)} - data-test-subj="SchemaSelect" - /> - - - - -
- - {FIELD_NAME_MODAL_CANCEL} - - {FIELD_NAME_MODAL_ADD_FIELD} - - -
+ autoFocus + isLoading={loading} + data-test-subj="SchemaAddFieldNameField" + /> + + + + + updateNewFieldType(e.target.value)} + data-test-subj="SchemaSelect" + /> + + + + + + + {FIELD_NAME_MODAL_CANCEL} + + {FIELD_NAME_MODAL_ADD_FIELD} + +
-
+ ); }; diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/field_editor_modal.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/field_editor_modal.tsx index 9a6af035c1c8d..717eebf5cf873 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/field_editor_modal.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/field_editor_modal.tsx @@ -20,7 +20,6 @@ import { EuiModalFooter, EuiModalHeader, EuiModalHeaderTitle, - EuiOverlayMask, EuiSelect, } from '@elastic/eui'; @@ -59,48 +58,46 @@ export const FieldEditorModal: React.FC = () => { const ACTION_LABEL = isEditing ? UPDATE_LABEL : ADD_LABEL; return ( - +
- - - - {ACTION_LABEL} {FIELD_LABEL} - - - - - - setName(e.target.value)} - /> - - - setLabel(e.target.value)} - /> - - - - - {CANCEL_BUTTON} - - {ACTION_LABEL} {FIELD_LABEL} - - - + + + {ACTION_LABEL} {FIELD_LABEL} + + + + + + setName(e.target.value)} + /> + + + setLabel(e.target.value)} + /> + + + + + {CANCEL_BUTTON} + + {ACTION_LABEL} {FIELD_LABEL} + +
-
+ ); }; diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/source_settings.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/source_settings.tsx index ee20446176dc1..14884a87b45c4 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/source_settings.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/source_settings.tsx @@ -15,7 +15,6 @@ import { EuiButton, EuiButtonEmpty, EuiConfirmModal, - EuiOverlayMask, EuiFieldText, EuiFlexGroup, EuiFlexItem, @@ -102,26 +101,24 @@ export const SourceSettings: React.FC = () => { }; const confirmModal = ( - - - , - }} - /> - - + + , + }} + /> + ); return ( diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/sources_view.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/sources_view.tsx index c62f0b00258d6..247df5556ada0 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/sources_view.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/sources_view.tsx @@ -19,7 +19,6 @@ import { EuiModalFooter, EuiModalHeader, EuiModalHeaderTitle, - EuiOverlayMask, EuiText, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; @@ -53,70 +52,65 @@ export const SourcesView: React.FC = ({ children }) => { addedSourceName: string; serviceType: string; }) => ( - - - - - - - - - - {i18n.translate( - 'xpack.enterpriseSearch.workplaceSearch.sourcesView.modal.heading', - { - defaultMessage: '{addedSourceName} requires additional configuration', - values: { addedSourceName }, - } - )} - - - - - - -

- - {EXTERNAL_IDENTITIES_LINK} - - ), - }} - /> -

+ + + + + + + + + {i18n.translate('xpack.enterpriseSearch.workplaceSearch.sourcesView.modal.heading', { + defaultMessage: '{addedSourceName} requires additional configuration', + values: { addedSourceName }, + })} + + + + + + +

+ + {EXTERNAL_IDENTITIES_LINK} + + ), + }} + /> +

-

- - {DOCUMENT_PERMISSIONS_LINK} - - ), - }} - /> -

-
-
- - - {UNDERSTAND_BUTTON} - - -
-
+

+ + {DOCUMENT_PERMISSIONS_LINK} + + ), + }} + /> +

+ + + + + {UNDERSTAND_BUTTON} + + + ); return ( diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/add_group_modal.test.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/add_group_modal.test.tsx index 26ac5e484f0d7..784544b0001fa 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/add_group_modal.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/add_group_modal.test.tsx @@ -11,7 +11,7 @@ import React from 'react'; import { shallow } from 'enzyme'; -import { EuiModal, EuiOverlayMask } from '@elastic/eui'; +import { EuiModal } from '@elastic/eui'; import { AddGroupModal } from './add_group_modal'; @@ -36,7 +36,6 @@ describe('AddGroupModal', () => { const wrapper = shallow(); expect(wrapper.find(EuiModal)).toHaveLength(1); - expect(wrapper.find(EuiOverlayMask)).toHaveLength(1); }); it('updates the input value', () => { diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/add_group_modal.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/add_group_modal.tsx index fb82e9393f2a2..2c5732b4b7157 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/add_group_modal.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/add_group_modal.tsx @@ -19,7 +19,6 @@ import { EuiModalFooter, EuiModalHeader, EuiModalHeaderTitle, - EuiOverlayMask, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; @@ -49,37 +48,35 @@ export const AddGroupModal: React.FC<{}> = () => { }; return ( - - -
- - {ADD_GROUP_HEADER} - + + + + {ADD_GROUP_HEADER} + - - - setNewGroupName(e.target.value)} - /> - - + + + setNewGroupName(e.target.value)} + /> + + - - {CANCEL_BUTTON} - - {ADD_GROUP_SUBMIT} - - - -
-
+ + {CANCEL_BUTTON} + + {ADD_GROUP_SUBMIT} + + + + ); }; diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/group_manager_modal.test.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/group_manager_modal.test.tsx index 949ae9d502e73..7c39414f158ef 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/group_manager_modal.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/group_manager_modal.test.tsx @@ -13,7 +13,7 @@ import React from 'react'; import { shallow } from 'enzyme'; -import { EuiOverlayMask, EuiModal, EuiEmptyPrompt } from '@elastic/eui'; +import { EuiModal, EuiEmptyPrompt } from '@elastic/eui'; import { GroupManagerModal } from './group_manager_modal'; @@ -46,7 +46,6 @@ describe('GroupManagerModal', () => { const wrapper = shallow(); expect(wrapper.find(EuiModal)).toHaveLength(1); - expect(wrapper.find(EuiOverlayMask)).toHaveLength(1); }); it('renders empty state', () => { diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/group_manager_modal.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/group_manager_modal.tsx index b4317ed9bd417..1b051394dcdcf 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/group_manager_modal.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/group_manager_modal.tsx @@ -21,7 +21,6 @@ import { EuiModalFooter, EuiModalHeader, EuiModalHeaderTitle, - EuiOverlayMask, EuiSpacer, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; @@ -161,14 +160,12 @@ export const GroupManagerModal: React.FC = ({ ); return ( - - - {showEmptyState ? emptyState : modalContent} - - + + {showEmptyState ? emptyState : modalContent} + ); }; diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/group_overview.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/group_overview.tsx index df9c0b5db9b7d..375ac7476f9b6 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/group_overview.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/group_overview.tsx @@ -12,7 +12,6 @@ import { useActions, useValues } from 'kea'; import { EuiButton, EuiConfirmModal, - EuiOverlayMask, EuiFieldText, EuiFlexGroup, EuiFlexItem, @@ -226,18 +225,16 @@ export const GroupOverview: React.FC = () => { {confirmDeleteModalVisible && ( - - - {CONFIRM_REMOVE_DESCRIPTION} - - + + {CONFIRM_REMOVE_DESCRIPTION} + )} { ); const confirmModal = ( - - - {PRIVATE_SOURCES_UPDATE_CONFIRMATION_TEXT} - - + + {PRIVATE_SOURCES_UPDATE_CONFIRMATION_TEXT} + ); return ( diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/settings/components/oauth_application.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/settings/components/oauth_application.tsx index 28e7e2a33eaa1..3f2e55d23722c 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/settings/components/oauth_application.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/settings/components/oauth_application.tsx @@ -18,7 +18,6 @@ import { EuiSwitch, EuiCode, EuiSpacer, - EuiOverlayMask, EuiLink, EuiModal, EuiModalBody, @@ -93,25 +92,28 @@ export const OauthApplication: React.FC = () => { }; const licenseModal = ( - - - - - - - -

{LICENSE_MODAL_TITLE}

-
- - {LICENSE_MODAL_DESCRIPTION} - - - {LICENSE_MODAL_LINK} - - -
-
-
+ + + + + + +

{LICENSE_MODAL_TITLE}

+
+ + {LICENSE_MODAL_DESCRIPTION} + + + {LICENSE_MODAL_LINK} + + +
+
); return ( diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/settings/components/source_config.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/settings/components/source_config.tsx index 4ed223931d6a4..47a24e7912c3c 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/settings/components/source_config.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/settings/components/source_config.tsx @@ -9,7 +9,7 @@ import React, { useEffect, useState } from 'react'; import { useActions, useValues } from 'kea'; -import { EuiConfirmModal, EuiOverlayMask } from '@elastic/eui'; +import { EuiConfirmModal } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { Loading } from '../../../../shared/loading'; @@ -56,22 +56,19 @@ export const SourceConfig: React.FC = ({ sourceIndex }) => { header={header} /> {confirmModalVisible && ( - - deleteSourceConfig(serviceType, name)} - onCancel={hideConfirmModal} - buttonColor="danger" - > - {i18n.translate( - 'xpack.enterpriseSearch.workplaceSearch.settings.confirmRemoveConfig.message', - { - defaultMessage: - 'Are you sure you want to remove the OAuth configuration for {name}?', - values: { name }, - } - )} - - + deleteSourceConfig(serviceType, name)} + onCancel={hideConfirmModal} + buttonColor="danger" + > + {i18n.translate( + 'xpack.enterpriseSearch.workplaceSearch.settings.confirmRemoveConfig.message', + { + defaultMessage: 'Are you sure you want to remove the OAuth configuration for {name}?', + values: { name }, + } + )} + )} ); diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_copy_provider.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_copy_provider.tsx index d161dfcc5894c..2b7ecc75195b0 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_copy_provider.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_copy_provider.tsx @@ -6,7 +6,7 @@ */ import React, { Fragment, useRef, useState } from 'react'; -import { EuiConfirmModal, EuiOverlayMask, EuiFormRow, EuiFieldText } from '@elastic/eui'; +import { EuiConfirmModal, EuiFormRow, EuiFieldText } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import { AgentPolicy } from '../../../types'; @@ -92,75 +92,71 @@ export const AgentPolicyCopyProvider: React.FunctionComponent = ({ childr } return ( - - - - - } - onCancel={closeModal} - onConfirm={copyAgentPolicy} - cancelButtonText={ + - } - confirmButtonText={ + + } + onCancel={closeModal} + onConfirm={copyAgentPolicy} + cancelButtonText={ + + } + confirmButtonText={ + + } + confirmButtonDisabled={isLoading || !newAgentPolicy.name.trim()} + > +

+ +

+ } - confirmButtonDisabled={isLoading || !newAgentPolicy.name.trim()} + fullWidth > -

- -

- - } + - setNewAgentPolicy({ ...newAgentPolicy, name: e.target.value })} + value={newAgentPolicy.name} + onChange={(e) => setNewAgentPolicy({ ...newAgentPolicy, name: e.target.value })} + /> + + - - - } + } + fullWidth + > + - - setNewAgentPolicy({ ...newAgentPolicy, description: e.target.value }) - } - /> - -
-
+ value={newAgentPolicy.description} + onChange={(e) => setNewAgentPolicy({ ...newAgentPolicy, description: e.target.value })} + /> + + ); }; diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_delete_provider.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_delete_provider.tsx index b03d70a78c51a..014af7f54d020 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_delete_provider.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_delete_provider.tsx @@ -6,7 +6,7 @@ */ import React, { Fragment, useRef, useState } from 'react'; -import { EuiConfirmModal, EuiOverlayMask, EuiCallOut } from '@elastic/eui'; +import { EuiConfirmModal, EuiCallOut } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import { AGENT_SAVED_OBJECT_TYPE } from '../../../constants'; @@ -110,69 +110,67 @@ export const AgentPolicyDeleteProvider: React.FunctionComponent = ({ chil } return ( - - - } - onCancel={closeModal} - onConfirm={deleteAgentPolicy} - cancelButtonText={ + + } + onCancel={closeModal} + onConfirm={deleteAgentPolicy} + cancelButtonText={ + + } + confirmButtonText={ + isLoading || isLoadingAgentsCount ? ( - } - confirmButtonText={ - isLoading || isLoadingAgentsCount ? ( - - ) : ( - - ) - } - buttonColor="danger" - confirmButtonDisabled={isLoading || isLoadingAgentsCount || !!agentsCount} - > - {isLoadingAgentsCount ? ( + ) : ( - ) : agentsCount ? ( - - - - ) : ( + ) + } + buttonColor="danger" + confirmButtonDisabled={isLoading || isLoadingAgentsCount || !!agentsCount} + > + {isLoadingAgentsCount ? ( + + ) : agentsCount ? ( + - )} - - + + ) : ( + + )} + ); }; diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_yaml_flyout.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_yaml_flyout.tsx index 63fb1f5b4b638..9ed4bb6ff6ff4 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_yaml_flyout.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_yaml_flyout.tsx @@ -52,8 +52,6 @@ export const AgentPolicyYamlFlyout = memo<{ policyId: string; onClose: () => voi {error.message} ) : ( - // Property 'whiteSpace' does not exist on type 'IntrinsicAttributes & CommonProps & OwnProps & HTMLAttributes & { children?: ReactNode; }'. - // @ts-expect-error linter complains whiteSpace isn't available but docs show it on EuiCodeBlockImpl {fullAgentPolicyToYaml(yamlData!.item)} diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/confirm_deploy_modal.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/confirm_deploy_modal.tsx index 02d7a6423edc8..f3d01e6b528ca 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/confirm_deploy_modal.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/confirm_deploy_modal.tsx @@ -6,7 +6,7 @@ */ import React from 'react'; -import { EuiCallOut, EuiOverlayMask, EuiConfirmModal, EuiSpacer } from '@elastic/eui'; +import { EuiCallOut, EuiConfirmModal, EuiSpacer } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; import { AgentPolicy } from '../../../types'; @@ -18,58 +18,56 @@ export const ConfirmDeployAgentPolicyModal: React.FunctionComponent<{ agentPolicy: AgentPolicy; }> = ({ onConfirm, onCancel, agentCount, agentPolicy }) => { return ( - - - } - onCancel={onCancel} - onConfirm={onConfirm} - cancelButtonText={ - - } - confirmButtonText={ - - } - buttonColor="primary" + + } + onCancel={onCancel} + onConfirm={onConfirm} + cancelButtonText={ + + } + confirmButtonText={ + + } + buttonColor="primary" + > + - -
- + {agentPolicy.name}, - }} - /> -
-
- - -
-
+ values={{ + policyName: {agentPolicy.name}, + }} + /> +
+ + + + ); }; diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/package_policy_delete_provider.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/package_policy_delete_provider.tsx index 2ea94e88ed8c6..80952fee05bb4 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/package_policy_delete_provider.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/package_policy_delete_provider.tsx @@ -6,7 +6,7 @@ */ import React, { Fragment, useMemo, useRef, useState } from 'react'; -import { EuiCallOut, EuiConfirmModal, EuiOverlayMask, EuiSpacer } from '@elastic/eui'; +import { EuiCallOut, EuiConfirmModal, EuiSpacer } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import { useStartServices, sendRequest, sendDeletePackagePolicy, useConfig } from '../../../hooks'; @@ -142,78 +142,76 @@ export const PackagePolicyDeleteProvider: React.FunctionComponent = ({ } return ( - - + } + onCancel={closeModal} + onConfirm={deletePackagePolicies} + cancelButtonText={ + + } + confirmButtonText={ + isLoading || isLoadingAgentsCount ? ( - } - onCancel={closeModal} - onConfirm={deletePackagePolicies} - cancelButtonText={ + ) : ( - } - confirmButtonText={ - isLoading || isLoadingAgentsCount ? ( - - ) : ( + ) + } + buttonColor="danger" + confirmButtonDisabled={isLoading || isLoadingAgentsCount} + > + {isLoadingAgentsCount ? ( + + ) : agentsCount ? ( + <> + + } + > {agentPolicy.name}, }} /> - ) - } - buttonColor="danger" - confirmButtonDisabled={isLoading || isLoadingAgentsCount} - > - {isLoadingAgentsCount ? ( - - ) : agentsCount ? ( - <> - - } - > - {agentPolicy.name}, - }} - /> - - - - ) : null} - {!isLoadingAgentsCount && ( - - )} - - + + + + ) : null} + {!isLoadingAgentsCount && ( + + )} + ); }; diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_unenroll_modal/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_unenroll_modal/index.tsx index 69bff78d60413..a50cc18d46f55 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_unenroll_modal/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_unenroll_modal/index.tsx @@ -7,7 +7,7 @@ import React, { useState } from 'react'; import { i18n } from '@kbn/i18n'; -import { EuiConfirmModal, EuiOverlayMask, EuiFormFieldset, EuiCheckbox } from '@elastic/eui'; +import { EuiConfirmModal, EuiFormFieldset, EuiCheckbox } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import { Agent } from '../../../../types'; import { @@ -81,90 +81,88 @@ export const AgentUnenrollAgentModal: React.FunctionComponent = ({ } return ( - - - ) : ( - - ) - } - onCancel={onClose} - onConfirm={onSubmit} - cancelButtonText={ + - } - confirmButtonDisabled={isSubmitting} - confirmButtonText={ - isSingleAgent ? ( - - ) : ( + ) : ( + + ) + } + onCancel={onClose} + onConfirm={onSubmit} + cancelButtonText={ + + } + confirmButtonDisabled={isSubmitting} + confirmButtonText={ + isSingleAgent ? ( + + ) : ( + + ) + } + buttonColor="danger" + > +

+ {isSingleAgent ? ( + + ) : ( + + )} +

+ - ) - } - buttonColor="danger" + ), + }} > -

- {isSingleAgent ? ( + - ) : ( - - )} -

- - ), - }} - > - - } - checked={forceUnenroll} - onChange={(e) => setForceUnenroll(e.target.checked)} - disabled={useForceUnenroll} - /> - -
-
+ values={{ count: agentCount }} + /> + } + checked={forceUnenroll} + onChange={(e) => setForceUnenroll(e.target.checked)} + disabled={useForceUnenroll} + /> + + ); }; diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_upgrade_modal/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_upgrade_modal/index.tsx index a836e3ec3149b..57f4007a00274 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_upgrade_modal/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_upgrade_modal/index.tsx @@ -7,13 +7,7 @@ import React, { useState } from 'react'; import { i18n } from '@kbn/i18n'; -import { - EuiConfirmModal, - EuiOverlayMask, - EuiBetaBadge, - EuiFlexGroup, - EuiFlexItem, -} from '@elastic/eui'; +import { EuiConfirmModal, EuiBetaBadge, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import { Agent } from '../../../../types'; import { @@ -74,85 +68,83 @@ export const AgentUpgradeAgentModal: React.FunctionComponent = ({ } return ( - - - - {isSingleAgent ? ( + + + {isSingleAgent ? ( + + ) : ( + + )} + + + - ) : ( + } + tooltipContent={ - )} - - - - } - tooltipContent={ - - } - /> - - - } - onCancel={onClose} - onConfirm={onSubmit} - cancelButtonText={ + } + /> + + + } + onCancel={onClose} + onConfirm={onSubmit} + cancelButtonText={ + + } + confirmButtonDisabled={isSubmitting} + confirmButtonText={ + isSingleAgent ? ( - } - confirmButtonDisabled={isSubmitting} - confirmButtonText={ - isSingleAgent ? ( - - ) : ( - - ) - } - > -

- {isSingleAgent ? ( - - ) : ( - - )} -

-
-
+ ) : ( + + ) + } + > +

+ {isSingleAgent ? ( + + ) : ( + + )} +

+ ); }; diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/enrollment_token_list_page/components/confirm_delete_modal.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/enrollment_token_list_page/components/confirm_delete_modal.tsx index 22e2e68e6d83e..565657c70e17f 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/enrollment_token_list_page/components/confirm_delete_modal.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/enrollment_token_list_page/components/confirm_delete_modal.tsx @@ -7,7 +7,7 @@ import React from 'react'; import { i18n } from '@kbn/i18n'; -import { EuiConfirmModal, EuiCallOut, EuiOverlayMask } from '@elastic/eui'; +import { EuiConfirmModal, EuiCallOut } from '@elastic/eui'; import { EnrollmentAPIKey } from '../../../../types'; interface Props { @@ -19,33 +19,31 @@ interface Props { export const ConfirmEnrollmentTokenDelete = (props: Props) => { const { onCancel, onConfirm, enrollmentKey } = props; return ( - - + - - - + color="danger" + /> + ); }; diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/epm/screens/detail/settings/confirm_package_install.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/epm/screens/detail/settings/confirm_package_install.tsx index ef3ca3ce664c1..5144b2a648786 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/epm/screens/detail/settings/confirm_package_install.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/epm/screens/detail/settings/confirm_package_install.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { EuiCallOut, EuiConfirmModal, EuiOverlayMask, EuiSpacer } from '@elastic/eui'; +import { EuiCallOut, EuiConfirmModal, EuiSpacer } from '@elastic/eui'; import React from 'react'; import { FormattedMessage } from '@kbn/i18n/react'; @@ -18,50 +18,48 @@ interface ConfirmPackageInstallProps { export const ConfirmPackageInstall = (props: ConfirmPackageInstallProps) => { const { onCancel, onConfirm, packageName, numOfAssets } = props; return ( - - + } + onCancel={onCancel} + onConfirm={onConfirm} + cancelButtonText={ + + } + confirmButtonText={ + + } + defaultFocusedButton="confirm" + > + - } - onCancel={onCancel} - onConfirm={onConfirm} - cancelButtonText={ - } - confirmButtonText={ - - } - defaultFocusedButton="confirm" - > - - } + /> + +

+ - -

- -

-
-
+

+ ); }; diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/epm/screens/detail/settings/confirm_package_uninstall.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/epm/screens/detail/settings/confirm_package_uninstall.tsx index 7688c0269d358..2def57b040944 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/epm/screens/detail/settings/confirm_package_uninstall.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/epm/screens/detail/settings/confirm_package_uninstall.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { EuiCallOut, EuiConfirmModal, EuiOverlayMask, EuiSpacer } from '@elastic/eui'; +import { EuiCallOut, EuiConfirmModal, EuiSpacer } from '@elastic/eui'; import React from 'react'; import { FormattedMessage } from '@kbn/i18n/react'; @@ -18,58 +18,56 @@ interface ConfirmPackageUninstallProps { export const ConfirmPackageUninstall = (props: ConfirmPackageUninstallProps) => { const { onCancel, onConfirm, packageName, numOfAssets } = props; return ( - - + } + onCancel={onCancel} + onConfirm={onConfirm} + cancelButtonText={ + + } + confirmButtonText={ + + } + defaultFocusedButton="confirm" + buttonColor="danger" + > + } - onCancel={onCancel} - onConfirm={onConfirm} - cancelButtonText={ - - } - confirmButtonText={ - - } - defaultFocusedButton="confirm" - buttonColor="danger" > - - } - > -

- -

-
-

-
-
+ + +

+ +

+ ); }; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_table/components/add_policy_to_template_confirm_modal.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_table/components/add_policy_to_template_confirm_modal.tsx index f20ea0f5d1bf4..8971f18ef8e5f 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_table/components/add_policy_to_template_confirm_modal.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_table/components/add_policy_to_template_confirm_modal.tsx @@ -13,7 +13,6 @@ import { EuiComboBox, EuiForm, EuiFormRow, - EuiOverlayMask, EuiConfirmModal, EuiFieldText, EuiSpacer, @@ -257,46 +256,44 @@ export const AddPolicyToTemplateConfirmModal: React.FunctionComponent = ( ); return ( - - - -

- {' '} - - } - /> -

-
- - {renderForm()} -
-
+ />{' '} + + } + /> +

+ + + {renderForm()} + ); }; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_table/components/confirm_delete.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_table/components/confirm_delete.tsx index 80039a18ef17a..e42aa97a10d4f 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_table/components/confirm_delete.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_table/components/confirm_delete.tsx @@ -8,7 +8,7 @@ import React, { Component } from 'react'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; -import { EuiOverlayMask, EuiConfirmModal } from '@elastic/eui'; +import { EuiConfirmModal } from '@elastic/eui'; import { PolicyFromES } from '../../../../../common/types'; import { toasts } from '../../../services/notification'; @@ -50,33 +50,31 @@ export class ConfirmDelete extends Component { values: { name: policyToDelete.name }, }); return ( - - - } - confirmButtonText={ - - } - buttonColor="danger" - > -
- -
-
-
+ + } + confirmButtonText={ + + } + buttonColor="danger" + > +
+ +
+
); } } diff --git a/x-pack/plugins/index_lifecycle_management/public/extend_index_management/components/add_lifecycle_confirm_modal.tsx b/x-pack/plugins/index_lifecycle_management/public/extend_index_management/components/add_lifecycle_confirm_modal.tsx index 550cefb488f66..36df4d9527a5c 100644 --- a/x-pack/plugins/index_lifecycle_management/public/extend_index_management/components/add_lifecycle_confirm_modal.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/extend_index_management/components/add_lifecycle_confirm_modal.tsx @@ -16,7 +16,6 @@ import { EuiSelect, EuiForm, EuiFormRow, - EuiOverlayMask, EuiConfirmModal, EuiModal, EuiModalBody, @@ -246,63 +245,59 @@ export class AddLifecyclePolicyConfirmModal extends Component { ); if (!policies.length) { return ( - - - - {title} - + + + {title} + - - + + } + color="warning" + > +

+ - } - color="warning" - > -

- - - -

-
-
-
-
+ +

+ + + ); } return ( - - - } - confirmButtonText={ - - } - > - {this.renderForm()} - - + + } + confirmButtonText={ + + } + > + {this.renderForm()} + ); } } diff --git a/x-pack/plugins/index_lifecycle_management/public/extend_index_management/components/remove_lifecycle_confirm_modal.tsx b/x-pack/plugins/index_lifecycle_management/public/extend_index_management/components/remove_lifecycle_confirm_modal.tsx index 8ce4ac052fce2..2f22a0b347db9 100644 --- a/x-pack/plugins/index_lifecycle_management/public/extend_index_management/components/remove_lifecycle_confirm_modal.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/extend_index_management/components/remove_lifecycle_confirm_modal.tsx @@ -8,7 +8,7 @@ import React, { Component, Fragment } from 'react'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; -import { EuiOverlayMask, EuiConfirmModal } from '@elastic/eui'; +import { EuiConfirmModal } from '@elastic/eui'; import { removeLifecycleForIndex } from '../../application/services/api'; import { showApiError } from '../../application/services/api_errors'; @@ -57,54 +57,52 @@ export class RemoveLifecyclePolicyConfirmModal extends Component { const { closeModal, indexNames } = this.props; return ( - - + } + onCancel={closeModal} + onConfirm={this.removePolicy} + cancelButtonText={ + + } + buttonColor="danger" + confirmButtonText={ + + } + > + +

- } - onCancel={closeModal} - onConfirm={this.removePolicy} - cancelButtonText={ - - } - buttonColor="danger" - confirmButtonText={ - - } - > - -

- -

+

-
    - {indexNames.map((indexName) => ( -
  • {indexName}
  • - ))} -
-
-
-
+
    + {indexNames.map((indexName) => ( +
  • {indexName}
  • + ))} +
+ + ); } } diff --git a/x-pack/plugins/index_management/public/application/components/component_templates/component_template_list/delete_modal.tsx b/x-pack/plugins/index_management/public/application/components/component_templates/component_template_list/delete_modal.tsx index cac26d948b11a..0b20bebf43143 100644 --- a/x-pack/plugins/index_management/public/application/components/component_templates/component_template_list/delete_modal.tsx +++ b/x-pack/plugins/index_management/public/application/components/component_templates/component_template_list/delete_modal.tsx @@ -6,7 +6,7 @@ */ import React from 'react'; -import { EuiConfirmModal, EuiOverlayMask } from '@elastic/eui'; +import { EuiConfirmModal } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; @@ -81,49 +81,47 @@ export const ComponentTemplatesDeleteModal = ({ }; return ( - - + } + onCancel={handleOnCancel} + onConfirm={handleDeleteComponentTemplates} + cancelButtonText={ + + } + confirmButtonText={ + + } + > + <> +

- } - onCancel={handleOnCancel} - onConfirm={handleDeleteComponentTemplates} - cancelButtonText={ - - } - confirmButtonText={ - - } - > - <> -

- -

+

-
    - {componentTemplatesToDelete.map((name) => ( -
  • {name}
  • - ))} -
- -
-
+
    + {componentTemplatesToDelete.map((name) => ( +
  • {name}
  • + ))} +
+ + ); }; diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/modal_confirmation_delete_fields.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/modal_confirmation_delete_fields.tsx index e6a7e42c08936..2a65906ea56b4 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/modal_confirmation_delete_fields.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/modal_confirmation_delete_fields.tsx @@ -6,7 +6,7 @@ */ import React from 'react'; -import { EuiConfirmModal, EuiOverlayMask, EuiBadge, EuiCode } from '@elastic/eui'; +import { EuiConfirmModal, EuiBadge, EuiCode } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { NormalizedFields, NormalizedField } from '../../../types'; @@ -59,55 +59,53 @@ export const ModalConfirmationDeleteFields = ({ : null; return ( - - + <> + {fieldsTree && ( + <> +

+ {i18n.translate( + 'xpack.idxMgmt.mappingsEditor.confirmationModal.deleteFieldsDescription', + { + defaultMessage: 'This will also delete the following fields.', + } + )} +

+ + + )} + {aliases && ( + <> +

+ {i18n.translate( + 'xpack.idxMgmt.mappingsEditor.confirmationModal.deleteAliasesDescription', + { + defaultMessage: 'The following aliases will also be deleted.', + } + )} +

+
    + {aliases.map((aliasPath) => ( +
  • + {aliasPath} +
  • + ))} +
+ )} - buttonColor="danger" - confirmButtonText={confirmButtonText} - > - <> - {fieldsTree && ( - <> -

- {i18n.translate( - 'xpack.idxMgmt.mappingsEditor.confirmationModal.deleteFieldsDescription', - { - defaultMessage: 'This will also delete the following fields.', - } - )} -

- - - )} - {aliases && ( - <> -

- {i18n.translate( - 'xpack.idxMgmt.mappingsEditor.confirmationModal.deleteAliasesDescription', - { - defaultMessage: 'The following aliases will also be deleted.', - } - )} -

-
    - {aliases.map((aliasPath) => ( -
  • - {aliasPath} -
  • - ))} -
- - )} - -
-
+ + ); }; diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/load_mappings/load_mappings_provider.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/load_mappings/load_mappings_provider.tsx index 0200572a3e52c..51e2f19a1e4e5 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/load_mappings/load_mappings_provider.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/load_mappings/load_mappings_provider.tsx @@ -9,14 +9,7 @@ import React, { useState, useRef, useCallback } from 'react'; import { isPlainObject } from 'lodash'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; -import { - EuiConfirmModal, - EuiOverlayMask, - EuiCallOut, - EuiText, - EuiSpacer, - EuiButtonEmpty, -} from '@elastic/eui'; +import { EuiConfirmModal, EuiCallOut, EuiText, EuiSpacer, EuiButtonEmpty } from '@elastic/eui'; import { JsonEditor, OnJsonEditorUpdateHandler } from '../../shared_imports'; import { validateMappings, MappingsValidationError, VALID_MAPPINGS_PARAMETERS } from '../../lib'; @@ -289,61 +282,55 @@ export const LoadMappingsProvider = ({ onJson, children }: Props) => { {children(openModal)} {state.isModalOpen && ( - - - {view === 'json' ? ( - // The CSS override for the EuiCodeEditor requires a parent .application css class -
- - mappings, - }} - /> - - - - - + {view === 'json' ? ( + // The CSS override for the EuiCodeEditor requires a parent .application css class +
+ + mappings, }} /> -
- ) : ( - <> - - -

{i18nTexts.validationErrors.description}

-
- -
    - {state.errors!.slice(0, totalErrorsToDisplay).map((error, i) => ( -
  1. {getErrorMessage(error)}
  2. - ))} -
- {state.errors!.length > MAX_ERRORS_TO_DISPLAY && renderErrorsFilterButton()} -
- - )} - - + + + + + +
+ ) : ( + <> + + +

{i18nTexts.validationErrors.description}

+
+ +
    + {state.errors!.slice(0, totalErrorsToDisplay).map((error, i) => ( +
  1. {getErrorMessage(error)}
  2. + ))} +
+ {state.errors!.length > MAX_ERRORS_TO_DISPLAY && renderErrorsFilterButton()} +
+ + )} +
)} ); diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/runtime_fields/delete_field_provider.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/runtime_fields/delete_field_provider.tsx index e48172c417d0a..f9ecca1f8cb61 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/runtime_fields/delete_field_provider.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/runtime_fields/delete_field_provider.tsx @@ -7,7 +7,7 @@ import React, { useState } from 'react'; import { i18n } from '@kbn/i18n'; -import { EuiConfirmModal, EuiOverlayMask } from '@elastic/eui'; +import { EuiConfirmModal } from '@elastic/eui'; import { useDispatch } from '../../mappings_state_context'; import { NormalizedRuntimeField } from '../../types'; @@ -68,22 +68,20 @@ export const DeleteRuntimeFieldProvider = ({ children }: Props) => { {children(deleteField)} {state.isModalOpen && ( - - - + )} ); diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/constants/data_types_definition.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/constants/data_types_definition.tsx index 2f49e95a1bd62..d7db98731427d 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/constants/data_types_definition.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/constants/data_types_definition.tsx @@ -86,7 +86,7 @@ export const TYPE_DEFINITION: { [key in DataType]: DataTypeDefinition } = { id="xpack.idxMgmt.mappingsEditor.dataType.constantKeywordLongDescription" defaultMessage="Constant keyword fields are a special type of keyword fields for fields that contain the same keyword across all documents in the index. Supports the same queries and aggregations as {keyword} fields." values={{ - keyword: {'keyword'}, + keyword: {'keyword'}, }} />

@@ -836,7 +836,7 @@ export const TYPE_DEFINITION: { [key in DataType]: DataTypeDefinition } = { id="xpack.idxMgmt.mappingsEditor.dataType.pointLongDescription" defaultMessage="Point fields enable searching of {code} pairs that fall in a 2-dimensional planar coordinate system." values={{ - code: {'x,y'}, + code: {'x,y'}, }} />

diff --git a/x-pack/plugins/index_management/public/application/components/template_delete_modal.tsx b/x-pack/plugins/index_management/public/application/components/template_delete_modal.tsx index 0dc2407d22c29..f22fa2a3b4f8a 100644 --- a/x-pack/plugins/index_management/public/application/components/template_delete_modal.tsx +++ b/x-pack/plugins/index_management/public/application/components/template_delete_modal.tsx @@ -6,7 +6,7 @@ */ import React, { Fragment, useState } from 'react'; -import { EuiConfirmModal, EuiOverlayMask, EuiCallOut, EuiCheckbox, EuiBadge } from '@elastic/eui'; +import { EuiConfirmModal, EuiCallOut, EuiCheckbox, EuiBadge } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; @@ -81,95 +81,93 @@ export const TemplateDeleteModal = ({ }; return ( - - - } - onCancel={handleOnCancel} - onConfirm={handleDeleteTemplates} - cancelButtonText={ - - } - confirmButtonText={ + + } + onCancel={handleOnCancel} + onConfirm={handleDeleteTemplates} + cancelButtonText={ + + } + confirmButtonText={ + + } + confirmButtonDisabled={hasSystemTemplate ? !isDeleteConfirmed : false} + > + +

- } - confirmButtonDisabled={hasSystemTemplate ? !isDeleteConfirmed : false} - > - -

- -

+

-
    - {templatesToDelete.map(({ name }) => ( -
  • - {name} - {name.startsWith('.') ? ( - - {' '} - - - - - ) : null} -
  • - ))} -
- {hasSystemTemplate && ( - + {templatesToDelete.map(({ name }) => ( +
  • + {name} + {name.startsWith('.') ? ( + + {' '} + + + + + ) : null} +
  • + ))} + + {hasSystemTemplate && ( + + } + color="danger" + iconType="alert" + data-test-subj="deleteSystemTemplateCallOut" + > +

    + +

    + } - color="danger" - iconType="alert" - data-test-subj="deleteSystemTemplateCallOut" - > -

    - -

    - - } - checked={isDeleteConfirmed} - onChange={(e) => setIsDeleteConfirmed(e.target.checked)} - /> -
    - )} -
    -
    -
    + checked={isDeleteConfirmed} + onChange={(e) => setIsDeleteConfirmed(e.target.checked)} + /> + + )} + +
    ); }; diff --git a/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/delete_data_stream_confirmation_modal/delete_data_stream_confirmation_modal.tsx b/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/delete_data_stream_confirmation_modal/delete_data_stream_confirmation_modal.tsx index 7475a87ca24d9..f555706a28cdd 100644 --- a/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/delete_data_stream_confirmation_modal/delete_data_stream_confirmation_modal.tsx +++ b/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/delete_data_stream_confirmation_modal/delete_data_stream_confirmation_modal.tsx @@ -6,7 +6,7 @@ */ import React, { Fragment } from 'react'; -import { EuiCallOut, EuiConfirmModal, EuiOverlayMask, EuiSpacer } from '@elastic/eui'; +import { EuiCallOut, EuiConfirmModal, EuiSpacer } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; @@ -82,69 +82,67 @@ export const DeleteDataStreamConfirmationModal: React.FunctionComponent = }; return ( - - - } - onCancel={() => onClose()} - onConfirm={handleDeleteDataStreams} - cancelButtonText={ - - } - confirmButtonText={ - - } - > - - - } - color="danger" - iconType="alert" - > -

    - -

    -
    - - - + + } + onCancel={() => onClose()} + onConfirm={handleDeleteDataStreams} + cancelButtonText={ + + } + confirmButtonText={ + + } + > + + + } + color="danger" + iconType="alert" + >

    +
    + + + +

    + +

    -
      - {dataStreams.map((name) => ( -
    • {name}
    • - ))} -
    -
    -
    -
    +
      + {dataStreams.map((name) => ( +
    • {name}
    • + ))} +
    + + ); }; diff --git a/x-pack/plugins/index_management/public/application/sections/home/index_list/index_actions_context_menu/index_actions_context_menu.js b/x-pack/plugins/index_management/public/application/sections/home/index_list/index_actions_context_menu/index_actions_context_menu.js index 6282469b09266..20a4af59bab11 100644 --- a/x-pack/plugins/index_management/public/application/sections/home/index_list/index_actions_context_menu/index_actions_context_menu.js +++ b/x-pack/plugins/index_management/public/application/sections/home/index_list/index_actions_context_menu/index_actions_context_menu.js @@ -20,7 +20,6 @@ import { EuiPopover, EuiSpacer, EuiConfirmModal, - EuiOverlayMask, EuiCheckbox, } from '@elastic/eui'; @@ -301,102 +300,97 @@ export class IndexActionsContextMenu extends Component { const selectedIndexCount = indexNames.length; return ( - - { - if (!this.forcemergeSegmentsError()) { - this.closePopoverAndExecute(() => { - forcemergeIndices(this.state.forcemergeSegments); - this.setState({ - forcemergeSegments: null, - showForcemergeSegmentsModal: null, - }); + { + if (!this.forcemergeSegmentsError()) { + this.closePopoverAndExecute(() => { + forcemergeIndices(this.state.forcemergeSegments); + this.setState({ + forcemergeSegments: null, + showForcemergeSegmentsModal: null, }); - } - }} - cancelButtonText={i18n.translate( - 'xpack.idxMgmt.indexActionsMenu.forceMerge.confirmModal.cancelButtonText', - { - defaultMessage: 'Cancel', - } - )} - confirmButtonText={i18n.translate( - 'xpack.idxMgmt.indexActionsMenu.forceMerge.confirmModal.confirmButtonText', + }); + } + }} + cancelButtonText={i18n.translate( + 'xpack.idxMgmt.indexActionsMenu.forceMerge.confirmModal.cancelButtonText', + { + defaultMessage: 'Cancel', + } + )} + confirmButtonText={i18n.translate( + 'xpack.idxMgmt.indexActionsMenu.forceMerge.confirmModal.confirmButtonText', + { + defaultMessage: 'Force merge', + } + )} + > +

    + +

    + +
      + {indexNames.map((indexName) => ( +
    • {indexName}
    • + ))} +
    + +

    -

    - -
      - {indexNames.map((indexName) => ( -
    • {indexName}
    • - ))} -
    - - -

    - -

    -
    + /> +

    +
    - + - + - - { - this.setState({ forcemergeSegments: event.target.value }); - }} - min={1} - name="maxNumberSegments" - /> - - -
    -
    + { + this.setState({ forcemergeSegments: event.target.value }); + }} + min={1} + name="maxNumberSegments" + /> + + + ); }; @@ -494,39 +488,37 @@ export class IndexActionsContextMenu extends Component { ); return ( - - { - this.confirmAction(false); - this.closeConfirmModal(); - }} - onConfirm={() => this.closePopoverAndExecute(deleteIndices)} - buttonColor="danger" - confirmButtonDisabled={hasSystemIndex ? !isActionConfirmed : false} - cancelButtonText={i18n.translate( - 'xpack.idxMgmt.indexActionsMenu.deleteIndex.confirmModal.cancelButtonText', - { - defaultMessage: 'Cancel', - } - )} - confirmButtonText={i18n.translate( - 'xpack.idxMgmt.indexActionsMenu.deleteIndex.confirmModal.confirmButtonText', - { - defaultMessage: 'Delete {selectedIndexCount, plural, one {index} other {indices} }', - values: { selectedIndexCount }, - } - )} - > - {hasSystemIndex ? systemIndexModalBody : standardIndexModalBody} - - + { + this.confirmAction(false); + this.closeConfirmModal(); + }} + onConfirm={() => this.closePopoverAndExecute(deleteIndices)} + buttonColor="danger" + confirmButtonDisabled={hasSystemIndex ? !isActionConfirmed : false} + cancelButtonText={i18n.translate( + 'xpack.idxMgmt.indexActionsMenu.deleteIndex.confirmModal.cancelButtonText', + { + defaultMessage: 'Cancel', + } + )} + confirmButtonText={i18n.translate( + 'xpack.idxMgmt.indexActionsMenu.deleteIndex.confirmModal.confirmButtonText', + { + defaultMessage: 'Delete {selectedIndexCount, plural, one {index} other {indices} }', + values: { selectedIndexCount }, + } + )} + > + {hasSystemIndex ? systemIndexModalBody : standardIndexModalBody} + ); }; @@ -536,96 +528,91 @@ export class IndexActionsContextMenu extends Component { const selectedIndexCount = indexNames.length; return ( - - { + this.confirmAction(false); + this.closeConfirmModal(); + }} + onConfirm={() => this.closePopoverAndExecute(closeIndices)} + buttonColor="danger" + confirmButtonDisabled={!isActionConfirmed} + cancelButtonText={i18n.translate( + 'xpack.idxMgmt.indexActionsMenu.deleteIndex.confirmModal.cancelButtonText', + { + defaultMessage: 'Cancel', + } + )} + confirmButtonText={i18n.translate( + 'xpack.idxMgmt.indexActionsMenu.closeIndex.confirmModal.confirmButtonText', + { + defaultMessage: 'Close {selectedIndexCount, plural, one {index} other {indices} }', + values: { selectedIndexCount }, + } + )} + > +

    + +

    + +
      + {indexNames.map((indexName) => ( +
    • + {indexName} + {isSystemIndexByName[indexName] ? ( + + {' '} + + + + + ) : ( + '' + )} +
    • + ))} +
    + + { - this.confirmAction(false); - this.closeConfirmModal(); - }} - onConfirm={() => this.closePopoverAndExecute(closeIndices)} - buttonColor="danger" - confirmButtonDisabled={!isActionConfirmed} - cancelButtonText={i18n.translate( - 'xpack.idxMgmt.indexActionsMenu.deleteIndex.confirmModal.cancelButtonText', + 'xpack.idxMgmt.indexActionsMenu.closeIndex.proceedWithCautionCallOutTitle', { - defaultMessage: 'Cancel', - } - )} - confirmButtonText={i18n.translate( - 'xpack.idxMgmt.indexActionsMenu.closeIndex.confirmModal.confirmButtonText', - { - defaultMessage: 'Close {selectedIndexCount, plural, one {index} other {indices} }', - values: { selectedIndexCount }, + defaultMessage: 'Closing a system index can break Kibana', } )} + color="danger" + iconType="alert" >

    - -
      - {indexNames.map((indexName) => ( -
    • - {indexName} - {isSystemIndexByName[indexName] ? ( - - {' '} - - - - - ) : ( - '' - )} -
    • - ))} -
    - - -

    + -

    - - } - checked={isActionConfirmed} - onChange={(e) => this.confirmAction(e.target.checked)} - /> -
    -
    -
    + } + checked={isActionConfirmed} + onChange={(e) => this.confirmAction(e.target.checked)} + /> + + ); }; @@ -633,71 +620,69 @@ export class IndexActionsContextMenu extends Component { const { freezeIndices, indexNames } = this.props; return ( - - this.closePopoverAndExecute(freezeIndices)} + cancelButtonText={i18n.translate( + 'xpack.idxMgmt.indexActionsMenu.freezeEntity.confirmModal.cancelButtonText', + { + defaultMessage: 'Cancel', + } + )} + confirmButtonText={i18n.translate( + 'xpack.idxMgmt.indexActionsMenu.freezeEntity.confirmModal.confirmButtonText', + { + defaultMessage: 'Freeze {count, plural, one {index} other {indices}}', + values: { + count: indexNames.length, + }, + } + )} + > +

    + +

    + +
      + {indexNames.map((indexName) => ( +
    • {indexName}
    • + ))} +
    + + this.closePopoverAndExecute(freezeIndices)} - cancelButtonText={i18n.translate( - 'xpack.idxMgmt.indexActionsMenu.freezeEntity.confirmModal.cancelButtonText', - { - defaultMessage: 'Cancel', - } - )} - confirmButtonText={i18n.translate( - 'xpack.idxMgmt.indexActionsMenu.freezeEntity.confirmModal.confirmButtonText', + 'xpack.idxMgmt.indexActionsMenu.freezeEntity.proceedWithCautionCallOutTitle', { - defaultMessage: 'Freeze {count, plural, one {index} other {indices}}', - values: { - count: indexNames.length, - }, + defaultMessage: 'Proceed with caution', } )} + color="warning" + iconType="help" >

    -

    - -
      - {indexNames.map((indexName) => ( -
    • {indexName}
    • - ))} -
    - - -

    - -

    -
    -
    -
    + /> +

    + + ); }; diff --git a/x-pack/plugins/infra/public/components/saved_views/create_modal.tsx b/x-pack/plugins/infra/public/components/saved_views/create_modal.tsx index 985db9872ef3f..654cba0721bb8 100644 --- a/x-pack/plugins/infra/public/components/saved_views/create_modal.tsx +++ b/x-pack/plugins/infra/public/components/saved_views/create_modal.tsx @@ -16,7 +16,6 @@ import { EuiModalFooter, EuiModalHeader, EuiModalHeaderTitle, - EuiOverlayMask, EuiFieldText, EuiSpacer, EuiSwitch, @@ -40,69 +39,67 @@ export const SavedViewCreateModal = ({ close, save, isInvalid }: Props) => { }, [includeTime, save, viewName]); return ( - - - - - - - - - - + + + - - - } - checked={includeTime} - onChange={onCheckChange} - /> - - - - - + + - - + + + + - - - - - - - + } + checked={includeTime} + onChange={onCheckChange} + /> + + + + + + + + + + + + + + + ); }; diff --git a/x-pack/plugins/infra/public/components/saved_views/update_modal.tsx b/x-pack/plugins/infra/public/components/saved_views/update_modal.tsx index 15d0d162604a4..c6d87d9a8ca15 100644 --- a/x-pack/plugins/infra/public/components/saved_views/update_modal.tsx +++ b/x-pack/plugins/infra/public/components/saved_views/update_modal.tsx @@ -16,7 +16,6 @@ import { EuiModalFooter, EuiModalHeader, EuiModalHeaderTitle, - EuiOverlayMask, EuiFieldText, EuiSpacer, EuiSwitch, @@ -46,69 +45,67 @@ export function SavedViewUpdateModal - - - - - - - - - + + + - - - } - checked={includeTime} - onChange={onCheckChange} - /> - - - - - + + - - + + + + - - - - - - -
    + } + checked={includeTime} + onChange={onCheckChange} + /> + + + + + + + + + + + + + + + ); } diff --git a/x-pack/plugins/infra/public/components/saved_views/view_list_modal.tsx b/x-pack/plugins/infra/public/components/saved_views/view_list_modal.tsx index 9ab742d720eb5..aad50c4dcb45d 100644 --- a/x-pack/plugins/infra/public/components/saved_views/view_list_modal.tsx +++ b/x-pack/plugins/infra/public/components/saved_views/view_list_modal.tsx @@ -9,13 +9,7 @@ import React, { useCallback, useState, useMemo } from 'react'; import { EuiButtonEmpty, EuiModalFooter, EuiButton } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; -import { - EuiOverlayMask, - EuiModal, - EuiModalHeader, - EuiModalHeaderTitle, - EuiModalBody, -} from '@elastic/eui'; +import { EuiModal, EuiModalHeader, EuiModalHeaderTitle, EuiModalBody } from '@elastic/eui'; import { EuiSelectable } from '@elastic/eui'; import { EuiSelectableOption } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; @@ -64,51 +58,49 @@ export function SavedViewListModal - - - - - - - - - {(list, search) => ( - <> - {search} -
    {list}
    - - )} -
    -
    - - - - - - - - -
    - + + + + + + + + + {(list, search) => ( + <> + {search} +
    {list}
    + + )} +
    +
    + + + + + + + + +
    ); } diff --git a/x-pack/plugins/infra/public/pages/logs/stream/page_view_log_in_context.tsx b/x-pack/plugins/infra/public/pages/logs/stream/page_view_log_in_context.tsx index df21a66a5226f..5537ef9541f89 100644 --- a/x-pack/plugins/infra/public/pages/logs/stream/page_view_log_in_context.tsx +++ b/x-pack/plugins/infra/public/pages/logs/stream/page_view_log_in_context.tsx @@ -9,7 +9,6 @@ import { EuiFlexGroup, EuiFlexItem, EuiModal, - EuiOverlayMask, EuiText, EuiTextColor, EuiToolTip, @@ -51,33 +50,26 @@ export const PageViewLogInContext: React.FC = () => { } return ( - - - - - - - - - - - - - - + + + + + + + + + + + + ); }; diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/load_from_json/modal_provider.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/load_from_json/modal_provider.tsx index 10da326322ad4..c0f9c758fc278 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/load_from_json/modal_provider.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/load_from_json/modal_provider.tsx @@ -8,7 +8,7 @@ import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import React, { FunctionComponent, useRef, useState, useCallback } from 'react'; -import { EuiConfirmModal, EuiOverlayMask, EuiSpacer, EuiText, EuiCallOut } from '@elastic/eui'; +import { EuiConfirmModal, EuiSpacer, EuiText, EuiCallOut } from '@elastic/eui'; import { JsonEditor, OnJsonEditorUpdateHandler } from '../../../../../shared_imports'; @@ -78,64 +78,62 @@ export const ModalProvider: FunctionComponent = ({ onDone, children }) => <> {children(() => setIsModalVisible(true))} {isModalVisible ? ( - - { + { + setIsModalVisible(false); + }} + onConfirm={async () => { + try { + const json = jsonContent.current.data.format(); + const { processors, on_failure: onFailure } = json; + // This function will throw if it cannot parse the pipeline object + deserialize({ processors, onFailure }); + onDone(json as any); setIsModalVisible(false); - }} - onConfirm={async () => { - try { - const json = jsonContent.current.data.format(); - const { processors, on_failure: onFailure } = json; - // This function will throw if it cannot parse the pipeline object - deserialize({ processors, onFailure }); - onDone(json as any); - setIsModalVisible(false); - } catch (e) { - setError(e); - } - }} - cancelButtonText={i18nTexts.buttons.cancel} - confirmButtonDisabled={!isValidJson} - confirmButtonText={i18nTexts.buttons.confirm} - maxWidth={600} - > -
    - - - + } catch (e) { + setError(e); + } + }} + cancelButtonText={i18nTexts.buttons.cancel} + confirmButtonDisabled={!isValidJson} + confirmButtonText={i18nTexts.buttons.confirm} + maxWidth={600} + > +
    + + + - + - {error && ( - <> - - {i18nTexts.error.body} - - - - )} + {error && ( + <> + + {i18nTexts.error.body} + + + + )} - -
    - - + +
    +
    ) : undefined} ); diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/processors/csv.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/processors/csv.tsx index d6cf2d0ae05e8..19176a27a0778 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/processors/csv.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/processors/csv.tsx @@ -85,7 +85,7 @@ const fieldsConfig: FieldsConfig = { {','} }} + values={{ value: {','} }} /> ), }, @@ -104,7 +104,7 @@ const fieldsConfig: FieldsConfig = { {'"'} }} + values={{ value: {'"'} }} /> ), }, diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/processors/date.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/processors/date.tsx index 17af47f1569e0..e8e956daff207 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/processors/date.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/processors/date.tsx @@ -61,7 +61,7 @@ const fieldsConfig: FieldsConfig = { {'UTC'} }} + values={{ timezone: {'UTC'} }} /> ), }, @@ -75,7 +75,7 @@ const fieldsConfig: FieldsConfig = { {'ENGLISH'} }} + values={{ timezone: {'ENGLISH'} }} /> ), }, @@ -102,7 +102,7 @@ export const DateProcessor: FunctionComponent = () => { id="xpack.ingestPipelines.pipelineEditor.dateForm.targetFieldHelpText" defaultMessage="Output field. If empty, the input field is updated in place. Defaults to {defaultField}." values={{ - defaultField: {'@timestamp'}, + defaultField: {'@timestamp'}, }} /> } diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/processors/date_index_name.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/processors/date_index_name.tsx index d4b1ec876bfd5..182b9ecd845e9 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/processors/date_index_name.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/processors/date_index_name.tsx @@ -82,7 +82,7 @@ const fieldsConfig: FieldsConfig = { {'yyyy-MM-dd'} }} + values={{ value: {'yyyy-MM-dd'} }} /> ), }, @@ -102,7 +102,7 @@ const fieldsConfig: FieldsConfig = { {"yyyy-MM-dd'T'HH:mm:ss.SSSXX"} }} + values={{ value: {"yyyy-MM-dd'T'HH:mm:ss.SSSXX"} }} /> ), }, @@ -119,7 +119,7 @@ const fieldsConfig: FieldsConfig = { {'UTC'} }} + values={{ timezone: {'UTC'} }} /> ), }, @@ -136,7 +136,7 @@ const fieldsConfig: FieldsConfig = { {'ENGLISH'} }} + values={{ locale: {'ENGLISH'} }} /> ), }, diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/processors/dissect.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/processors/dissect.tsx index 609ce8a1f8ae6..641a6e73d9025 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/processors/dissect.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/processors/dissect.tsx @@ -82,7 +82,7 @@ const getFieldsConfig = (esDocUrl: string): Record => { {'""'} }} + values={{ value: {'""'} }} /> ), }, diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/processors/geoip.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/processors/geoip.tsx index 2f0699fac729d..7848872800df4 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/processors/geoip.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/processors/geoip.tsx @@ -31,8 +31,8 @@ const fieldsConfig: FieldsConfig = { id="xpack.ingestPipelines.pipelineEditor.geoIPForm.databaseFileHelpText" defaultMessage="GeoIP2 database file in the {ingestGeoIP} configuration directory. Defaults to {databaseFile}." values={{ - databaseFile: {'GeoLite2-City.mmdb'}, - ingestGeoIP: {'ingest-geoip'}, + databaseFile: {'GeoLite2-City.mmdb'}, + ingestGeoIP: {'ingest-geoip'}, }} /> ), diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/processors/inference.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/processors/inference.tsx index 2b14a79afb8df..9575e6d690e00 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/processors/inference.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/processors/inference.tsx @@ -168,7 +168,7 @@ export const Inference: FunctionComponent = () => { {'ml.inference.'} }} + values={{ targetField: {'ml.inference.'} }} /> } /> diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/processors/user_agent.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/processors/user_agent.tsx index 4309df214410b..d14048c4e00dc 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/processors/user_agent.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/processors/user_agent.tsx @@ -66,7 +66,7 @@ export const UserAgent: FunctionComponent = () => { id="xpack.ingestPipelines.pipelineEditor.userAgentForm.targetFieldHelpText" defaultMessage="Output field. Defaults to {defaultField}." values={{ - defaultField: {'user_agent'}, + defaultField: {'user_agent'}, }} /> } diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_remove_modal.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_remove_modal.tsx index eb5a1baac78fb..26ae69ead3b5b 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_remove_modal.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_remove_modal.tsx @@ -7,7 +7,7 @@ import { FormattedMessage } from '@kbn/i18n/react'; import React from 'react'; -import { EuiConfirmModal, EuiOverlayMask } from '@elastic/eui'; +import { EuiConfirmModal } from '@elastic/eui'; import { ProcessorInternal, ProcessorSelector } from '../types'; interface Props { @@ -18,39 +18,37 @@ interface Props { export const ProcessorRemoveModal = ({ processor, onResult, selector }: Props) => { return ( - - - } - onCancel={() => onResult({ confirmed: false, selector })} - onConfirm={() => onResult({ confirmed: true, selector })} - cancelButtonText={ - - } - confirmButtonText={ - - } - > -

    - -

    -
    -
    + + } + onCancel={() => onResult({ confirmed: false, selector })} + onConfirm={() => onResult({ confirmed: true, selector })} + cancelButtonText={ + + } + confirmButtonText={ + + } + > +

    + +

    +
    ); }; diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/shared/map_processor_type_to_form.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/shared/map_processor_type_to_form.tsx index aac4da7c16bbf..9095ab1927cb9 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/shared/map_processor_type_to_form.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/shared/map_processor_type_to_form.tsx @@ -135,7 +135,7 @@ export const mapProcessorTypeToDescriptor: MapProcessorTypeToDescriptor = { {'my-index-yyyy-MM-dd'} }} + values={{ value: {'my-index-yyyy-MM-dd'} }} /> ), }, diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/test_pipeline/test_pipeline_tabs/tab_documents/reset_documents_modal.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/test_pipeline/test_pipeline_tabs/tab_documents/reset_documents_modal.tsx index 305ccce4e31b5..d71a6fb80bde1 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/test_pipeline/test_pipeline_tabs/tab_documents/reset_documents_modal.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/test_pipeline/test_pipeline_tabs/tab_documents/reset_documents_modal.tsx @@ -7,7 +7,7 @@ import { i18n } from '@kbn/i18n'; import React, { FunctionComponent } from 'react'; -import { EuiConfirmModal, EuiOverlayMask } from '@elastic/eui'; +import { EuiConfirmModal } from '@elastic/eui'; interface Props { confirmResetTestOutput: () => void; @@ -46,18 +46,16 @@ export const ResetDocumentsModal: FunctionComponent = ({ closeModal, }) => { return ( - - -

    {i18nTexts.modalDescription}

    -
    -
    + +

    {i18nTexts.modalDescription}

    +
    ); }; diff --git a/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_list/delete_modal.tsx b/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_list/delete_modal.tsx index 230cc52a1c169..63cf7af2737aa 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_list/delete_modal.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_list/delete_modal.tsx @@ -6,7 +6,7 @@ */ import React from 'react'; -import { EuiConfirmModal, EuiOverlayMask } from '@elastic/eui'; +import { EuiConfirmModal } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; @@ -78,49 +78,47 @@ export const PipelineDeleteModal = ({ }; return ( - - + } + onCancel={handleOnCancel} + onConfirm={handleDeletePipelines} + cancelButtonText={ + + } + confirmButtonText={ + + } + > + <> +

    - } - onCancel={handleOnCancel} - onConfirm={handleDeletePipelines} - cancelButtonText={ - - } - confirmButtonText={ - - } - > - <> -

    - -

    +

    -
      - {pipelinesToDelete.map((name) => ( -
    • {name}
    • - ))} -
    - -
    -
    +
      + {pipelinesToDelete.map((name) => ( +
    • {name}
    • + ))} +
    + +
    ); }; diff --git a/x-pack/plugins/license_management/__jest__/__snapshots__/upload_license.test.tsx.snap b/x-pack/plugins/license_management/__jest__/__snapshots__/upload_license.test.tsx.snap index 9db44bd8225ea..bc69ab5352a4f 100644 --- a/x-pack/plugins/license_management/__jest__/__snapshots__/upload_license.test.tsx.snap +++ b/x-pack/plugins/license_management/__jest__/__snapshots__/upload_license.test.tsx.snap @@ -296,140 +296,140 @@ exports[`UploadLicense should display a modal when license requires acknowledgem className="euiSpacer euiSpacer--l" />
    - - -
    + + } + confirmButtonText={ + + } + onCancel={[Function]} + onConfirm={[Function]} + title={ + + } + > + + + -
    -
    - Confirm License Upload -
    -
    -
    +
    -
    -
    - } - > - - } - confirmButtonText={ - - } - onCancel={[Function]} - onConfirm={[Function]} - title={ - - } - > -
    -
    -
    - - + + + +
    - - } - onCancel={cancelStartBasicLicense} - onConfirm={() => startBasicLicense(licenseType, true)} - cancelButtonText={ - - } - confirmButtonText={ - - } - > -
    - {firstLine} - -
      - {messages.map((message) => ( -
    • {message}
    • - ))} -
    -
    -
    -
    - + + } + onCancel={cancelStartBasicLicense} + onConfirm={() => startBasicLicense(licenseType, true)} + cancelButtonText={ + + } + confirmButtonText={ + + } + > +
    + {firstLine} + +
      + {messages.map((message) => ( +
    • {message}
    • + ))} +
    +
    +
    +
    ); } render() { diff --git a/x-pack/plugins/license_management/public/application/sections/license_dashboard/start_trial/start_trial.tsx b/x-pack/plugins/license_management/public/application/sections/license_dashboard/start_trial/start_trial.tsx index 4f7d1ab4365b6..36af5c3b9c7ad 100644 --- a/x-pack/plugins/license_management/public/application/sections/license_dashboard/start_trial/start_trial.tsx +++ b/x-pack/plugins/license_management/public/application/sections/license_dashboard/start_trial/start_trial.tsx @@ -14,7 +14,6 @@ import { EuiFlexItem, EuiCard, EuiLink, - EuiOverlayMask, EuiText, EuiModal, EuiModalFooter, @@ -78,154 +77,152 @@ export class StartTrial extends Component { } return ( - - - - - - - - - -
    - -

    - + + + + + + + +

    + +

    + - - - ), - }} + values={{ + subscriptionFeaturesLinkText: ( + + + + ), + }} + /> +

    +
      +
    • + -

      -
        -
      • - -
      • -
      • - -
      • -
      • - -
      • -
      • - -
      • -
      -

      +

    • +
    • - - - ), - }} + id="xpack.licenseMgmt.licenseDashboard.startTrial.confirmModalDescription.alertingFeatureTitle" + defaultMessage="Alerting" + /> +
    • +
    • + -

      -

      +

    • +
    • - - - ), + jdbcStandard: 'JDBC', + odbcStandard: 'ODBC', + sqlDataBase: 'SQL', }} /> -

      - -
    -
    - - - - - - {shouldShowTelemetryOptIn(telemetry) && ( - + +

    + + + + ), + }} + /> +

    +

    + + + + ), + }} /> - )} - - - - - - - - - - - - - - - - - - - +

    + +
    +
    +
    + + + + + {shouldShowTelemetryOptIn(telemetry) && ( + + )} + + + + + + + + + + + + + + + + + +
    ); } diff --git a/x-pack/plugins/license_management/public/application/sections/upload_license/upload_license.js b/x-pack/plugins/license_management/public/application/sections/upload_license/upload_license.js index 77efe30bbb71e..4d639ec3123df 100644 --- a/x-pack/plugins/license_management/public/application/sections/upload_license/upload_license.js +++ b/x-pack/plugins/license_management/public/application/sections/upload_license/upload_license.js @@ -13,7 +13,6 @@ import { EuiForm, EuiSpacer, EuiConfirmModal, - EuiOverlayMask, EuiText, EuiTitle, EuiFlexGroup, @@ -62,41 +61,39 @@ export class UploadLicense extends React.PureComponent { return null; } return ( - - - } - onCancel={this.cancel} - onConfirm={() => this.send(true)} - cancelButtonText={ - - } - confirmButtonText={ - - } - > -
    - {firstLine} - -
      - {messages.map((message) => ( -
    • {message}
    • - ))} -
    -
    -
    -
    -
    + + } + onCancel={this.cancel} + onConfirm={() => this.send(true)} + cancelButtonText={ + + } + confirmButtonText={ + + } + > +
    + {firstLine} + +
      + {messages.map((message) => ( +
    • {message}
    • + ))} +
    +
    +
    +
    ); } errorMessage() { diff --git a/x-pack/plugins/logstash/public/application/components/pipeline_editor/__snapshots__/confirm_delete_pipeline_modal.test.js.snap b/x-pack/plugins/logstash/public/application/components/pipeline_editor/__snapshots__/confirm_delete_pipeline_modal.test.js.snap index 8fc0ecacd4a3c..31b8be8aab9ce 100644 --- a/x-pack/plugins/logstash/public/application/components/pipeline_editor/__snapshots__/confirm_delete_pipeline_modal.test.js.snap +++ b/x-pack/plugins/logstash/public/application/components/pipeline_editor/__snapshots__/confirm_delete_pipeline_modal.test.js.snap @@ -1,41 +1,39 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`ConfirmDeletePipelineModal component renders as expected 1`] = ` - - - } - confirmButtonText={ - - } - defaultFocusedButton="cancel" - onCancel={[MockFunction]} - onConfirm={[MockFunction]} - title={ - + } + confirmButtonText={ + + } + defaultFocusedButton="cancel" + onCancel={[MockFunction]} + onConfirm={[MockFunction]} + title={ + - } - > -

    - You cannot recover a deleted pipeline. -

    -
    -
    + } + /> + } +> +

    + You cannot recover a deleted pipeline. +

    + `; diff --git a/x-pack/plugins/logstash/public/application/components/pipeline_editor/confirm_delete_pipeline_modal.js b/x-pack/plugins/logstash/public/application/components/pipeline_editor/confirm_delete_pipeline_modal.js index 37ce05f42073a..d8cf85919bd42 100644 --- a/x-pack/plugins/logstash/public/application/components/pipeline_editor/confirm_delete_pipeline_modal.js +++ b/x-pack/plugins/logstash/public/application/components/pipeline_editor/confirm_delete_pipeline_modal.js @@ -7,41 +7,39 @@ import React from 'react'; import PropTypes from 'prop-types'; -import { EuiConfirmModal, EUI_MODAL_CANCEL_BUTTON, EuiOverlayMask } from '@elastic/eui'; +import { EuiConfirmModal, EUI_MODAL_CANCEL_BUTTON } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import { PIPELINE_EDITOR } from './constants'; export function ConfirmDeletePipelineModal({ id, cancelDeleteModal, confirmDeletePipeline }) { return ( - - - } - confirmButtonText={ - - } - defaultFocusedButton={EUI_MODAL_CANCEL_BUTTON} - onCancel={cancelDeleteModal} - onConfirm={confirmDeletePipeline} - title={ - - } - > -

    {PIPELINE_EDITOR.DELETE_PIPELINE_MODAL_MESSAGE}

    -
    -
    + + } + confirmButtonText={ + + } + defaultFocusedButton={EUI_MODAL_CANCEL_BUTTON} + onCancel={cancelDeleteModal} + onConfirm={confirmDeletePipeline} + title={ + + } + > +

    {PIPELINE_EDITOR.DELETE_PIPELINE_MODAL_MESSAGE}

    +
    ); } diff --git a/x-pack/plugins/logstash/public/application/components/pipeline_list/__snapshots__/confirm_delete_modal.test.js.snap b/x-pack/plugins/logstash/public/application/components/pipeline_list/__snapshots__/confirm_delete_modal.test.js.snap index c58337612f287..9eabf4120ef23 100644 --- a/x-pack/plugins/logstash/public/application/components/pipeline_list/__snapshots__/confirm_delete_modal.test.js.snap +++ b/x-pack/plugins/logstash/public/application/components/pipeline_list/__snapshots__/confirm_delete_modal.test.js.snap @@ -1,93 +1,89 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`ConfirmDeleteModal component confirms delete for multiple pipelines 1`] = ` - - - } - confirmButtonText={ - + } + confirmButtonText={ + - } - defaultFocusedButton="cancel" - onCancel={[MockFunction]} - onConfirm={[MockFunction]} - title={ - + } + defaultFocusedButton="cancel" + onCancel={[MockFunction]} + onConfirm={[MockFunction]} + title={ + - } - > -

    - -

    -
    -
    + } + /> + } +> +

    + +

    + `; exports[`ConfirmDeleteModal component confirms delete for single pipeline 1`] = ` - - - } - confirmButtonText={ - - } - defaultFocusedButton="cancel" - onCancel={[MockFunction]} - onConfirm={[MockFunction]} - title={ - + } + confirmButtonText={ + + } + defaultFocusedButton="cancel" + onCancel={[MockFunction]} + onConfirm={[MockFunction]} + title={ + - } - > -

    - -

    -
    -
    + } + /> + } +> +

    + +

    + `; diff --git a/x-pack/plugins/logstash/public/application/components/pipeline_list/confirm_delete_modal.js b/x-pack/plugins/logstash/public/application/components/pipeline_list/confirm_delete_modal.js index c20db3d3fc579..5dbefd2ae58e8 100644 --- a/x-pack/plugins/logstash/public/application/components/pipeline_list/confirm_delete_modal.js +++ b/x-pack/plugins/logstash/public/application/components/pipeline_list/confirm_delete_modal.js @@ -6,7 +6,7 @@ */ import React from 'react'; -import { EuiConfirmModal, EUI_MODAL_CANCEL_BUTTON, EuiOverlayMask } from '@elastic/eui'; +import { EuiConfirmModal, EUI_MODAL_CANCEL_BUTTON } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; export function ConfirmDeleteModal({ @@ -67,23 +67,21 @@ export function ConfirmDeleteModal({ }; return ( - - - } - confirmButtonText={confirmText.button} - defaultFocusedButton={EUI_MODAL_CANCEL_BUTTON} - onCancel={cancelDeletePipelines} - onConfirm={deleteSelectedPipelines} - title={confirmText.title} - > -

    {confirmText.message}

    -
    -
    + + } + confirmButtonText={confirmText.button} + defaultFocusedButton={EUI_MODAL_CANCEL_BUTTON} + onCancel={cancelDeletePipelines} + onConfirm={deleteSelectedPipelines} + title={confirmText.title} + > +

    {confirmText.message}

    +
    ); } diff --git a/x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/toc_entry/view.js b/x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/toc_entry/view.js index 89eef907b2259..9e5a6080c830d 100644 --- a/x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/toc_entry/view.js +++ b/x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/toc_entry/view.js @@ -8,7 +8,7 @@ import React from 'react'; import classNames from 'classnames'; -import { EuiIcon, EuiOverlayMask, EuiButtonIcon, EuiConfirmModal } from '@elastic/eui'; +import { EuiIcon, EuiButtonIcon, EuiConfirmModal } from '@elastic/eui'; import { TOCEntryActionsPopover } from './toc_entry_actions_popover'; import { i18n } from '@kbn/i18n'; @@ -100,20 +100,18 @@ export class TOCEntry extends React.Component { }; return ( - - -

    There are unsaved changes to your layer.

    -

    Are you sure you want to proceed?

    -
    -
    + +

    There are unsaved changes to your layer.

    +

    Are you sure you want to proceed?

    +
    ); } diff --git a/x-pack/plugins/ml/public/application/components/annotations/delete_annotation_modal/index.tsx b/x-pack/plugins/ml/public/application/components/annotations/delete_annotation_modal/index.tsx index 8469d42c16c51..9999fad89d0e1 100644 --- a/x-pack/plugins/ml/public/application/components/annotations/delete_annotation_modal/index.tsx +++ b/x-pack/plugins/ml/public/application/components/annotations/delete_annotation_modal/index.tsx @@ -8,7 +8,7 @@ import PropTypes from 'prop-types'; import React, { Fragment } from 'react'; -import { EUI_MODAL_CONFIRM_BUTTON, EuiConfirmModal, EuiOverlayMask } from '@elastic/eui'; +import { EUI_MODAL_CONFIRM_BUTTON, EuiConfirmModal } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; @@ -26,33 +26,31 @@ export const DeleteAnnotationModal: React.FC = ({ return ( {isVisible === true && ( - - - } - onCancel={cancelAction} - onConfirm={deleteAction} - cancelButtonText={ - - } - confirmButtonText={ - - } - buttonColor="danger" - defaultFocusedButton={EUI_MODAL_CONFIRM_BUTTON} - className="eui-textBreakWord" - /> - + + } + onCancel={cancelAction} + onConfirm={deleteAction} + cancelButtonText={ + + } + confirmButtonText={ + + } + buttonColor="danger" + defaultFocusedButton={EUI_MODAL_CONFIRM_BUTTON} + className="eui-textBreakWord" + /> )} ); diff --git a/x-pack/plugins/ml/public/application/components/delete_job_check_modal/delete_job_check_modal.tsx b/x-pack/plugins/ml/public/application/components/delete_job_check_modal/delete_job_check_modal.tsx index eda0509d417ca..972ed06ba1385 100644 --- a/x-pack/plugins/ml/public/application/components/delete_job_check_modal/delete_job_check_modal.tsx +++ b/x-pack/plugins/ml/public/application/components/delete_job_check_modal/delete_job_check_modal.tsx @@ -13,7 +13,6 @@ import { EuiFlexGroup, EuiFlexItem, EuiModal, - EuiOverlayMask, EuiModalHeader, EuiModalHeaderTitle, EuiModalBody, @@ -230,69 +229,67 @@ export const DeleteJobCheckModal: FC = ({ }; return ( - - - {isLoading === true && ( - <> - - - - - - - - - )} - {isLoading === false && ( - <> - - - - - + + {isLoading === true && ( + <> + + + + + + + + + )} + {isLoading === false && ( + <> + + + + + - {modalContent} + {modalContent} - - - - {!hasUntagged && + + + + {!hasUntagged && + jobCheckRespSummary?.canTakeAnyAction && + jobCheckRespSummary?.canRemoveFromSpace && + jobCheckRespSummary?.canDelete && ( + + {shouldUnTagLabel} + + )} + + + - {shouldUnTagLabel} - - )} - - - - {buttonContent} - - - - - - )} - - + !jobCheckRespSummary?.canDelete + ? onUntagClick + : onClick + } + fill + > + {buttonContent} + + + + + + )} + ); }; diff --git a/x-pack/plugins/ml/public/application/components/model_snapshots/close_job_confirm/close_job_confirm.tsx b/x-pack/plugins/ml/public/application/components/model_snapshots/close_job_confirm/close_job_confirm.tsx index 1d2bda90516b9..8fc4a0d636bce 100644 --- a/x-pack/plugins/ml/public/application/components/model_snapshots/close_job_confirm/close_job_confirm.tsx +++ b/x-pack/plugins/ml/public/application/components/model_snapshots/close_job_confirm/close_job_confirm.tsx @@ -10,7 +10,7 @@ import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; -import { EuiOverlayMask, EuiConfirmModal } from '@elastic/eui'; +import { EuiConfirmModal } from '@elastic/eui'; import { COMBINED_JOB_STATE } from '../model_snapshots_table'; @@ -25,56 +25,51 @@ export const CloseJobConfirm: FC = ({ forceCloseJob, }) => { return ( - - +

    + {combinedJobState === COMBINED_JOB_STATE.OPEN_AND_RUNNING && ( + )} - confirmButtonText={ - combinedJobState === COMBINED_JOB_STATE.OPEN_AND_RUNNING - ? i18n.translate('xpack.ml.modelSnapshotTable.closeJobConfirm.stopAndClose.button', { - defaultMessage: 'Force stop and close', - }) - : i18n.translate('xpack.ml.modelSnapshotTable.closeJobConfirm.close.button', { - defaultMessage: 'Force close', - }) - } - defaultFocusedButton="confirm" - > -

    - {combinedJobState === COMBINED_JOB_STATE.OPEN_AND_RUNNING && ( - - )} - {combinedJobState === COMBINED_JOB_STATE.OPEN_AND_STOPPED && ( - - )} -
    + {combinedJobState === COMBINED_JOB_STATE.OPEN_AND_STOPPED && ( -

    -
    -
    + )} +
    + +

    + ); }; diff --git a/x-pack/plugins/ml/public/application/components/model_snapshots/edit_model_snapshot_flyout/edit_model_snapshot_flyout.tsx b/x-pack/plugins/ml/public/application/components/model_snapshots/edit_model_snapshot_flyout/edit_model_snapshot_flyout.tsx index 833e70fc86f4c..20c98255930b4 100644 --- a/x-pack/plugins/ml/public/application/components/model_snapshots/edit_model_snapshot_flyout/edit_model_snapshot_flyout.tsx +++ b/x-pack/plugins/ml/public/application/components/model_snapshots/edit_model_snapshot_flyout/edit_model_snapshot_flyout.tsx @@ -22,7 +22,6 @@ import { EuiFormRow, EuiSwitch, EuiConfirmModal, - EuiOverlayMask, EuiCallOut, } from '@elastic/eui'; @@ -190,23 +189,21 @@ export const EditModelSnapshotFlyout: FC = ({ snapshot, job, closeFlyout {deleteModalVisible && ( - - - + )} ); diff --git a/x-pack/plugins/ml/public/application/components/model_snapshots/revert_model_snapshot_flyout/revert_model_snapshot_flyout.tsx b/x-pack/plugins/ml/public/application/components/model_snapshots/revert_model_snapshot_flyout/revert_model_snapshot_flyout.tsx index 1929cddaca6b5..6dd4e6c14589b 100644 --- a/x-pack/plugins/ml/public/application/components/model_snapshots/revert_model_snapshot_flyout/revert_model_snapshot_flyout.tsx +++ b/x-pack/plugins/ml/public/application/components/model_snapshots/revert_model_snapshot_flyout/revert_model_snapshot_flyout.tsx @@ -22,7 +22,6 @@ import { EuiFormRow, EuiSwitch, EuiConfirmModal, - EuiOverlayMask, EuiCallOut, EuiHorizontalRule, EuiSuperSelect, @@ -368,34 +367,32 @@ export const RevertModelSnapshotFlyout: FC = ({ {revertModalVisible && ( - - - - - + + + )} ); diff --git a/x-pack/plugins/ml/public/application/components/rule_editor/select_rule_action/__snapshots__/delete_rule_modal.test.js.snap b/x-pack/plugins/ml/public/application/components/rule_editor/select_rule_action/__snapshots__/delete_rule_modal.test.js.snap index a132e6682ee25..3a11531f6c4bc 100644 --- a/x-pack/plugins/ml/public/application/components/rule_editor/select_rule_action/__snapshots__/delete_rule_modal.test.js.snap +++ b/x-pack/plugins/ml/public/application/components/rule_editor/select_rule_action/__snapshots__/delete_rule_modal.test.js.snap @@ -42,34 +42,32 @@ exports[`DeleteRuleModal renders modal after clicking delete rule link 1`] = ` values={Object {}} /> - - - } - confirmButtonText={ - - } - defaultFocusedButton="confirm" - onCancel={[Function]} - onConfirm={[Function]} - title={ - - } - /> - + + } + confirmButtonText={ + + } + defaultFocusedButton="confirm" + onCancel={[Function]} + onConfirm={[Function]} + title={ + + } + /> `; diff --git a/x-pack/plugins/ml/public/application/components/rule_editor/select_rule_action/delete_rule_modal.js b/x-pack/plugins/ml/public/application/components/rule_editor/select_rule_action/delete_rule_modal.js index 809bb780c3323..6caa6592e96c1 100644 --- a/x-pack/plugins/ml/public/application/components/rule_editor/select_rule_action/delete_rule_modal.js +++ b/x-pack/plugins/ml/public/application/components/rule_editor/select_rule_action/delete_rule_modal.js @@ -12,7 +12,7 @@ import PropTypes from 'prop-types'; import React, { Component } from 'react'; -import { EuiConfirmModal, EuiLink, EuiOverlayMask, EUI_MODAL_CONFIRM_BUTTON } from '@elastic/eui'; +import { EuiConfirmModal, EuiLink, EUI_MODAL_CONFIRM_BUTTON } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; export class DeleteRuleModal extends Component { @@ -43,32 +43,30 @@ export class DeleteRuleModal extends Component { if (this.state.isModalVisible) { modal = ( - - - } - onCancel={this.closeModal} - onConfirm={this.deleteRule} - buttonColor="danger" - cancelButtonText={ - - } - confirmButtonText={ - - } - defaultFocusedButton={EUI_MODAL_CONFIRM_BUTTON} - /> - + + } + onCancel={this.closeModal} + onConfirm={this.deleteRule} + buttonColor="danger" + cancelButtonText={ + + } + confirmButtonText={ + + } + defaultFocusedButton={EUI_MODAL_CONFIRM_BUTTON} + /> ); } diff --git a/x-pack/plugins/ml/public/application/components/validate_job/validate_job_view.js b/x-pack/plugins/ml/public/application/components/validate_job/validate_job_view.js index a93264c852dd1..2b7c89db15e2e 100644 --- a/x-pack/plugins/ml/public/application/components/validate_job/validate_job_view.js +++ b/x-pack/plugins/ml/public/application/components/validate_job/validate_job_view.js @@ -19,7 +19,6 @@ import { EuiModalFooter, EuiModalHeader, EuiModalHeaderTitle, - EuiOverlayMask, EuiSpacer, EuiText, EuiFlexGroup, @@ -161,24 +160,19 @@ const LoadingSpinner = () => ( ); const Modal = ({ close, title, children }) => ( - - - - {title} - - - {children} - - - - - - - - + + + {title} + + + {children} + + + + + + + ); Modal.propType = { close: PropTypes.func.isRequired, diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_delete/delete_action_modal.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_delete/delete_action_modal.tsx index bc99330a444ae..e2e1ec852d1a9 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_delete/delete_action_modal.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_delete/delete_action_modal.tsx @@ -9,7 +9,6 @@ import React, { FC } from 'react'; import { i18n } from '@kbn/i18n'; import { EuiConfirmModal, - EuiOverlayMask, EuiSwitch, EuiFlexGroup, EuiFlexItem, @@ -37,67 +36,62 @@ export const DeleteActionModal: FC = ({ const indexName = item.config.dest.index; return ( - - - - - {userCanDeleteIndex && ( - - )} - - - {userCanDeleteIndex && indexPatternExists && ( - - )} - - - - + + + + {userCanDeleteIndex && ( + + )} + + + {userCanDeleteIndex && indexPatternExists && ( + + )} + + + ); }; diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_start/start_action_modal.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_start/start_action_modal.tsx index 2a19068ca6f4e..d63e60e43e909 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_start/start_action_modal.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_start/start_action_modal.tsx @@ -7,7 +7,7 @@ import React, { FC } from 'react'; import { i18n } from '@kbn/i18n'; -import { EuiConfirmModal, EuiOverlayMask, EUI_MODAL_CONFIRM_BUTTON } from '@elastic/eui'; +import { EuiConfirmModal, EUI_MODAL_CONFIRM_BUTTON } from '@elastic/eui'; import { StartAction } from './use_start_action'; @@ -15,37 +15,35 @@ export const StartActionModal: FC = ({ closeModal, item, startAndCl return ( <> {item !== undefined && ( - - +

    + {i18n.translate('xpack.ml.dataframe.analyticsList.startModalBody', { + defaultMessage: + 'A data frame analytics job increases search and indexing load in your cluster. If excessive load occurs, stop the job.', })} - onCancel={closeModal} - onConfirm={startAndCloseModal} - cancelButtonText={i18n.translate( - 'xpack.ml.dataframe.analyticsList.startModalCancelButton', - { - defaultMessage: 'Cancel', - } - )} - confirmButtonText={i18n.translate( - 'xpack.ml.dataframe.analyticsList.startModalStartButton', - { - defaultMessage: 'Start', - } - )} - defaultFocusedButton={EUI_MODAL_CONFIRM_BUTTON} - buttonColor="primary" - > -

    - {i18n.translate('xpack.ml.dataframe.analyticsList.startModalBody', { - defaultMessage: - 'A data frame analytics job increases search and indexing load in your cluster. If excessive load occurs, stop the job.', - })} -

    -
    -
    +

    +
    )} ); diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_stop/stop_action_modal.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_stop/stop_action_modal.tsx index a10c0c59abd97..8ee7350245be4 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_stop/stop_action_modal.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_stop/stop_action_modal.tsx @@ -8,7 +8,7 @@ import React, { FC } from 'react'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; -import { EuiConfirmModal, EuiOverlayMask, EUI_MODAL_CONFIRM_BUTTON } from '@elastic/eui'; +import { EuiConfirmModal, EUI_MODAL_CONFIRM_BUTTON } from '@elastic/eui'; import { StopAction } from './use_stop_action'; @@ -16,37 +16,35 @@ export const StopActionModal: FC = ({ closeModal, item, forceStopAnd return ( <> {item !== undefined && ( - - -

    - -

    -
    -
    + +

    + +

    +
    )} ); diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/delete_models_modal.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/delete_models_modal.tsx index 7ff77f21a8623..d93baee97c533 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/delete_models_modal.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/delete_models_modal.tsx @@ -8,7 +8,6 @@ import React, { FC } from 'react'; import { FormattedMessage } from '@kbn/i18n/react'; import { - EuiOverlayMask, EuiModal, EuiModalHeader, EuiModalHeaderTitle, @@ -31,57 +30,55 @@ export const DeleteModelsModal: FC = ({ models, onClose .map((model) => model.model_id); return ( - - - - + + + + + + + + + {modelsWithPipelines.length > 0 && ( + - - - - - {modelsWithPipelines.length > 0 && ( - - - - )} - + + )} + - - - - + + + + - - - - - - + + + + + ); }; diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/source_selection/source_selection.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/source_selection/source_selection.tsx index c9f78e9b0dab1..40f97690d7790 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/source_selection/source_selection.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/source_selection/source_selection.tsx @@ -9,13 +9,7 @@ import React, { FC } from 'react'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; -import { - EuiModal, - EuiModalBody, - EuiModalHeader, - EuiModalHeaderTitle, - EuiOverlayMask, -} from '@elastic/eui'; +import { EuiModal, EuiModalBody, EuiModalHeader, EuiModalHeaderTitle } from '@elastic/eui'; import { SavedObjectFinderUi } from '../../../../../../../../../../src/plugins/saved_objects/public'; import { useMlKibana, useNavigateToPath } from '../../../../../contexts/kibana'; @@ -41,66 +35,62 @@ export const SourceSelection: FC = ({ onClose }) => { }; return ( - <> - - - - - {' '} - /{' '} - - - - - + + + {' '} + /{' '} + + + + + 'search', + name: i18n.translate( + 'xpack.ml.dataFrame.analytics.create.searchSelection.savedObjectType.search', { - defaultMessage: 'No matching indices or saved searches found.', + defaultMessage: 'Saved search', } - )} - savedObjectMetaData={[ + ), + }, + { + type: 'index-pattern', + getIconForSavedObject: () => 'indexPatternApp', + name: i18n.translate( + 'xpack.ml.dataFrame.analytics.create.searchSelection.savedObjectType.indexPattern', { - type: 'search', - getIconForSavedObject: () => 'search', - name: i18n.translate( - 'xpack.ml.dataFrame.analytics.create.searchSelection.savedObjectType.search', - { - defaultMessage: 'Saved search', - } - ), - }, - { - type: 'index-pattern', - getIconForSavedObject: () => 'indexPatternApp', - name: i18n.translate( - 'xpack.ml.dataFrame.analytics.create.searchSelection.savedObjectType.indexPattern', - { - defaultMessage: 'Index pattern', - } - ), - }, - ]} - fixedPageSize={fixedPageSize} - uiSettings={uiSettings} - savedObjects={savedObjects} - /> - - - - + defaultMessage: 'Index pattern', + } + ), + }, + ]} + fixedPageSize={fixedPageSize} + uiSettings={uiSettings} + savedObjects={savedObjects} + /> + + ); }; diff --git a/x-pack/plugins/ml/public/application/explorer/add_to_dashboard_control.tsx b/x-pack/plugins/ml/public/application/explorer/add_to_dashboard_control.tsx index 3401c72a3b854..2330eafd87825 100644 --- a/x-pack/plugins/ml/public/application/explorer/add_to_dashboard_control.tsx +++ b/x-pack/plugins/ml/public/application/explorer/add_to_dashboard_control.tsx @@ -14,7 +14,6 @@ import { EuiModal, EuiModalHeader, EuiModalHeaderTitle, - EuiOverlayMask, EuiSpacer, EuiButtonEmpty, EuiButton, @@ -215,103 +214,99 @@ export const AddToDashboardControl: FC = ({ const noSwimlaneSelected = Object.values(selectedSwimlanes).every((isSelected) => !isSelected); return ( - - - - + + + + + + + + - - - - - } - > - { - const newSelection = { - ...selectedSwimlanes, - [optionId]: !selectedSwimlanes[optionId as SwimlaneType], - }; - setSelectedSwimlanes(newSelection); - }} - data-test-subj="mlAddToDashboardSwimlaneTypeSelector" - /> - + } + > + { + const newSelection = { + ...selectedSwimlanes, + [optionId]: !selectedSwimlanes[optionId as SwimlaneType], + }; + setSelectedSwimlanes(newSelection); + }} + data-test-subj="mlAddToDashboardSwimlaneTypeSelector" + /> + - + - - } - data-test-subj="mlDashboardSelectionContainer" - > - - - - - - - - { - onClose(async () => { - const selectedDashboardId = selectedItems[0].id; - await addSwimlaneToDashboardCallback(); - await navigateToUrl( - await dashboardService.getDashboardEditUrl(selectedDashboardId) - ); - }); - }} - data-test-subj="mlAddAndEditDashboardButton" - > - - - + - - - - + } + data-test-subj="mlDashboardSelectionContainer" + > + + + + + + + + { + onClose(async () => { + const selectedDashboardId = selectedItems[0].id; + await addSwimlaneToDashboardCallback(); + await navigateToUrl(await dashboardService.getDashboardEditUrl(selectedDashboardId)); + }); + }} + data-test-subj="mlAddAndEditDashboardButton" + > + + + + + + + ); }; diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/delete_job_modal/delete_job_modal.tsx b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/delete_job_modal/delete_job_modal.tsx index 3bec404276ca2..a67863ea5f803 100644 --- a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/delete_job_modal/delete_job_modal.tsx +++ b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/delete_job_modal/delete_job_modal.tsx @@ -10,7 +10,6 @@ import { FormattedMessage } from '@kbn/i18n/react'; import { EuiSpacer, EuiModal, - EuiOverlayMask, EuiModalHeader, EuiModalHeaderTitle, EuiModalBody, @@ -77,74 +76,72 @@ export const DeleteJobModal: FC = ({ setShowFunction, unsetShowFunction, if (canDelete) { return ( - - - - - - - - -

    - {deleting === true ? ( -

    - - -
    - -
    + + + + + + + +

    + {deleting === true ? ( +

    + + +
    +
    - ) : ( - - - - )} -

    - - <> - - - - - + + )} +

    + + <> + + + + + - - - - - - - + + + +
    + + ); } else { return ( diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/edit_job_flyout/edit_job_flyout.js b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/edit_job_flyout/edit_job_flyout.js index 8769c2c3cca20..b23bbedb7413a 100644 --- a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/edit_job_flyout/edit_job_flyout.js +++ b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/edit_job_flyout/edit_job_flyout.js @@ -21,7 +21,6 @@ import { EuiFlexGroup, EuiFlexItem, EuiTabbedContent, - EuiOverlayMask, EuiConfirmModal, } from '@elastic/eui'; @@ -443,38 +442,36 @@ export class EditJobFlyoutUI extends Component { if (this.state.isConfirmationModalVisible) { confirmationModal = ( - - - } - onCancel={() => this.closeFlyout(true)} - onConfirm={() => this.save()} - cancelButtonText={ - - } - confirmButtonText={ - - } - defaultFocusedButton="confirm" - > -

    - -

    -
    -
    + + } + onCancel={() => this.closeFlyout(true)} + onConfirm={() => this.save()} + cancelButtonText={ + + } + confirmButtonText={ + + } + defaultFocusedButton="confirm" + > +

    + +

    +
    ); } diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/edit_job_flyout/tabs/custom_urls.tsx b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/edit_job_flyout/tabs/custom_urls.tsx index a1bac4b6a3597..da4c9b0b0cc00 100644 --- a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/edit_job_flyout/tabs/custom_urls.tsx +++ b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/edit_job_flyout/tabs/custom_urls.tsx @@ -14,7 +14,6 @@ import { EuiFlexItem, EuiPanel, EuiSpacer, - EuiOverlayMask, EuiModal, EuiModalBody, EuiModalHeader, @@ -282,30 +281,28 @@ class CustomUrlsUI extends Component { ) : ( - - - - - - - + + + + + + - {editor} + {editor} - - {testButton} - {addButton} - - - + + {testButton} + {addButton} + + ); } diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/start_datafeed_modal/start_datafeed_modal.js b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/start_datafeed_modal/start_datafeed_modal.js index 5f5759e49208c..361e8956c714e 100644 --- a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/start_datafeed_modal/start_datafeed_modal.js +++ b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/start_datafeed_modal/start_datafeed_modal.js @@ -16,7 +16,6 @@ import { EuiModalFooter, EuiModalHeader, EuiModalHeaderTitle, - EuiOverlayMask, EuiHorizontalRule, EuiCheckbox, } from '@elastic/eui'; @@ -138,78 +137,76 @@ export class StartDatafeedModal extends Component { if (this.state.isModalVisible) { modal = ( - - - - - - - - - - + + + - {this.state.endTime === undefined && ( -
    - - - } - checked={createAlert} - onChange={this.setCreateAlert} - /> -
    - )} -
    - - - - - - - - + + + + + {this.state.endTime === undefined && ( +
    + + + } + checked={createAlert} + onChange={this.setCreateAlert} /> - - - - +
    + )} +
    + + + + + + + + + + +
    ); } return
    {modal}
    ; diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/datafeed_step/components/reset_query/reset_query.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/datafeed_step/components/reset_query/reset_query.tsx index 02f53c77c088c..e42ec414e9641 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/datafeed_step/components/reset_query/reset_query.tsx +++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/datafeed_step/components/reset_query/reset_query.tsx @@ -8,13 +8,7 @@ import React, { FC, useContext, useState } from 'react'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; -import { - EuiButtonEmpty, - EuiConfirmModal, - EuiOverlayMask, - EuiCodeBlock, - EuiSpacer, -} from '@elastic/eui'; +import { EuiButtonEmpty, EuiConfirmModal, EuiCodeBlock, EuiSpacer } from '@elastic/eui'; import { JobCreatorContext } from '../../../job_creator_context'; import { getDefaultDatafeedQuery } from '../../../../../utils/new_job_utils'; @@ -34,35 +28,33 @@ export const ResetQueryButton: FC = () => { return ( <> {confirmModalVisible && ( - - - + + - + - - {defaultQueryString} - - - + + {defaultQueryString} + + )} diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/advanced_detector_modal/modal_wrapper.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/advanced_detector_modal/modal_wrapper.tsx index 3f4a0f6ea6b3d..aaed47cc7a02b 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/advanced_detector_modal/modal_wrapper.tsx +++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/advanced_detector_modal/modal_wrapper.tsx @@ -9,7 +9,6 @@ import React, { FC } from 'react'; import { FormattedMessage } from '@kbn/i18n/react'; import { EuiButton, - EuiOverlayMask, EuiModal, EuiModalHeader, EuiModalHeaderTitle, @@ -28,44 +27,42 @@ interface Props { export const ModalWrapper: FC = ({ onCreateClick, closeModal, saveEnabled, children }) => { return ( - - - - - - - + + + + + + - {children} + {children} - - - - + + + + - - - - - - + + + + + ); }; diff --git a/x-pack/plugins/ml/public/application/settings/calendars/edit/new_calendar.js b/x-pack/plugins/ml/public/application/settings/calendars/edit/new_calendar.js index 7f3c8ce9a1a6e..42d8b32691c20 100644 --- a/x-pack/plugins/ml/public/application/settings/calendars/edit/new_calendar.js +++ b/x-pack/plugins/ml/public/application/settings/calendars/edit/new_calendar.js @@ -10,7 +10,7 @@ import { PropTypes } from 'prop-types'; import { i18n } from '@kbn/i18n'; -import { EuiPage, EuiPageBody, EuiPageContent, EuiOverlayMask } from '@elastic/eui'; +import { EuiPage, EuiPageBody, EuiPageContent } from '@elastic/eui'; import { NavigationMenu } from '../../../components/navigation_menu'; @@ -336,19 +336,13 @@ class NewCalendarUI extends Component { let modal = ''; if (isNewEventModalVisible) { - modal = ( - - - - ); + modal = ; } else if (isImportModalVisible) { modal = ( - - - + ); } diff --git a/x-pack/plugins/ml/public/application/settings/calendars/list/calendars_list.js b/x-pack/plugins/ml/public/application/settings/calendars/list/calendars_list.js index afd1433b7ae69..bba28ab481ea1 100644 --- a/x-pack/plugins/ml/public/application/settings/calendars/list/calendars_list.js +++ b/x-pack/plugins/ml/public/application/settings/calendars/list/calendars_list.js @@ -10,7 +10,6 @@ import { PropTypes } from 'prop-types'; import { EuiConfirmModal, - EuiOverlayMask, EuiPage, EuiPageBody, EuiPageContent, @@ -111,37 +110,35 @@ export class CalendarsListUI extends Component { if (this.state.isDestroyModalVisible) { destroyModal = ( - - c.calendar_id).join(', '), - }} - /> - } - onCancel={this.closeDestroyModal} - onConfirm={this.deleteCalendars} - cancelButtonText={ - - } - confirmButtonText={ - - } - buttonColor="danger" - defaultFocusedButton={EUI_MODAL_CONFIRM_BUTTON} - /> - + c.calendar_id).join(', '), + }} + /> + } + onCancel={this.closeDestroyModal} + onConfirm={this.deleteCalendars} + cancelButtonText={ + + } + confirmButtonText={ + + } + buttonColor="danger" + defaultFocusedButton={EUI_MODAL_CONFIRM_BUTTON} + /> ); } diff --git a/x-pack/plugins/ml/public/application/settings/filter_lists/components/delete_filter_list_modal/__snapshots__/delete_filter_list_modal.test.js.snap b/x-pack/plugins/ml/public/application/settings/filter_lists/components/delete_filter_list_modal/__snapshots__/delete_filter_list_modal.test.js.snap index 93ca044cb0c83..8cadb8270f680 100644 --- a/x-pack/plugins/ml/public/application/settings/filter_lists/components/delete_filter_list_modal/__snapshots__/delete_filter_list_modal.test.js.snap +++ b/x-pack/plugins/ml/public/application/settings/filter_lists/components/delete_filter_list_modal/__snapshots__/delete_filter_list_modal.test.js.snap @@ -92,41 +92,39 @@ exports[`DeleteFilterListModal renders modal after clicking delete button 1`] = values={Object {}} /> - - - } - className="eui-textBreakWord" - confirmButtonText={ - - } - data-test-subj="mlFilterListDeleteConfirmation" - defaultFocusedButton="confirm" - onCancel={[Function]} - onConfirm={[Function]} - title={ - + } + className="eui-textBreakWord" + confirmButtonText={ + + } + data-test-subj="mlFilterListDeleteConfirmation" + defaultFocusedButton="confirm" + onCancel={[Function]} + onConfirm={[Function]} + title={ + - } - /> - + } + /> + } + />
    `; diff --git a/x-pack/plugins/ml/public/application/settings/filter_lists/components/delete_filter_list_modal/delete_filter_list_modal.js b/x-pack/plugins/ml/public/application/settings/filter_lists/components/delete_filter_list_modal/delete_filter_list_modal.js index bed0e7ca281e5..20b716586b97d 100644 --- a/x-pack/plugins/ml/public/application/settings/filter_lists/components/delete_filter_list_modal/delete_filter_list_modal.js +++ b/x-pack/plugins/ml/public/application/settings/filter_lists/components/delete_filter_list_modal/delete_filter_list_modal.js @@ -9,7 +9,7 @@ import PropTypes from 'prop-types'; import React, { Component } from 'react'; import { FormattedMessage } from '@kbn/i18n/react'; -import { EuiButton, EuiConfirmModal, EuiOverlayMask, EUI_MODAL_CONFIRM_BUTTON } from '@elastic/eui'; +import { EuiButton, EuiConfirmModal, EUI_MODAL_CONFIRM_BUTTON } from '@elastic/eui'; import { deleteFilterLists } from './delete_filter_lists'; @@ -67,29 +67,27 @@ export class DeleteFilterListModal extends Component { /> ); modal = ( - - - } - confirmButtonText={ - - } - buttonColor="danger" - defaultFocusedButton={EUI_MODAL_CONFIRM_BUTTON} - data-test-subj={'mlFilterListDeleteConfirmation'} - /> - + + } + confirmButtonText={ + + } + buttonColor="danger" + defaultFocusedButton={EUI_MODAL_CONFIRM_BUTTON} + data-test-subj={'mlFilterListDeleteConfirmation'} + /> ); } diff --git a/x-pack/plugins/ml/public/application/timeseriesexplorer/components/forecasting_modal/modal.js b/x-pack/plugins/ml/public/application/timeseriesexplorer/components/forecasting_modal/modal.js index 613bd51bc16c3..3261846a5fdd5 100644 --- a/x-pack/plugins/ml/public/application/timeseriesexplorer/components/forecasting_modal/modal.js +++ b/x-pack/plugins/ml/public/application/timeseriesexplorer/components/forecasting_modal/modal.js @@ -19,7 +19,6 @@ import { EuiModalFooter, EuiModalHeader, EuiModalHeaderTitle, - EuiOverlayMask, EuiSpacer, } from '@elastic/eui'; @@ -31,48 +30,42 @@ import { FormattedMessage } from '@kbn/i18n/react'; export function Modal(props) { return ( - - - - - - - + + + + + + - - {props.messages.map((message, i) => ( - - - - - ))} + + {props.messages.map((message, i) => ( + + + + + ))} - {props.forecasts.length > 0 && ( - - - - - )} - - + {props.forecasts.length > 0 && ( + + + + + )} + + - - - - - - - + + + + + +
    ); } diff --git a/x-pack/plugins/remote_clusters/public/application/sections/remote_cluster_list/components/remove_cluster_button_provider/remove_cluster_button_provider.js b/x-pack/plugins/remote_clusters/public/application/sections/remote_cluster_list/components/remove_cluster_button_provider/remove_cluster_button_provider.js index fd9f4e3503d10..4db5d1b333b7c 100644 --- a/x-pack/plugins/remote_clusters/public/application/sections/remote_cluster_list/components/remove_cluster_button_provider/remove_cluster_button_provider.js +++ b/x-pack/plugins/remote_clusters/public/application/sections/remote_cluster_list/components/remove_cluster_button_provider/remove_cluster_button_provider.js @@ -10,7 +10,7 @@ import PropTypes from 'prop-types'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; -import { EuiConfirmModal, EuiOverlayMask } from '@elastic/eui'; +import { EuiConfirmModal } from '@elastic/eui'; export class RemoveClusterButtonProvider extends Component { static propTypes = { @@ -84,7 +84,7 @@ export class RemoveClusterButtonProvider extends Component { ); modal = ( - + <> {/* eslint-disable-next-line jsx-a11y/mouse-events-have-key-events */} {!isSingleCluster && content} - + ); } diff --git a/x-pack/plugins/reporting/public/components/buttons/__snapshots__/report_info_button.test.tsx.snap b/x-pack/plugins/reporting/public/components/buttons/__snapshots__/report_info_button.test.tsx.snap index 09e487591c164..692b410bd7e5f 100644 --- a/x-pack/plugins/reporting/public/components/buttons/__snapshots__/report_info_button.test.tsx.snap +++ b/x-pack/plugins/reporting/public/components/buttons/__snapshots__/report_info_button.test.tsx.snap @@ -58,7 +58,7 @@ Array [ >
    ,
    ,
    { }); return ( - - this.hideConfirm()} - onConfirm={() => this.props.performDelete()} - confirmButtonText={confirmButtonText} - cancelButtonText={cancelButtonText} - defaultFocusedButton="confirm" - buttonColor="danger" - > - {message} - - + this.hideConfirm()} + onConfirm={() => this.props.performDelete()} + confirmButtonText={confirmButtonText} + cancelButtonText={cancelButtonText} + defaultFocusedButton="confirm" + buttonColor="danger" + > + {message} + ); } diff --git a/x-pack/plugins/rollup/public/crud_app/sections/components/job_action_menu/confirm_delete_modal/confirm_delete_modal.js b/x-pack/plugins/rollup/public/crud_app/sections/components/job_action_menu/confirm_delete_modal/confirm_delete_modal.js index 650d63f38eeb5..ec7473c69dec1 100644 --- a/x-pack/plugins/rollup/public/crud_app/sections/components/job_action_menu/confirm_delete_modal/confirm_delete_modal.js +++ b/x-pack/plugins/rollup/public/crud_app/sections/components/job_action_menu/confirm_delete_modal/confirm_delete_modal.js @@ -10,7 +10,7 @@ import PropTypes from 'prop-types'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; -import { EuiConfirmModal, EuiOverlayMask } from '@elastic/eui'; +import { EuiConfirmModal } from '@elastic/eui'; export class ConfirmDeleteModal extends Component { static propTypes = { @@ -91,28 +91,26 @@ export class ConfirmDeleteModal extends Component { } return ( - - - {content} - - + + {content} + ); } } diff --git a/x-pack/plugins/security/public/components/confirm_modal.tsx b/x-pack/plugins/security/public/components/confirm_modal.tsx index d0ca1de07314e..3802ee368d735 100644 --- a/x-pack/plugins/security/public/components/confirm_modal.tsx +++ b/x-pack/plugins/security/public/components/confirm_modal.tsx @@ -18,7 +18,6 @@ import { EuiModalHeader, EuiModalHeaderTitle, EuiModalProps, - EuiOverlayMask, } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; @@ -29,13 +28,11 @@ export interface ConfirmModalProps extends Omit = ({ children, @@ -45,51 +42,42 @@ export const ConfirmModal: FunctionComponent = ({ isDisabled, onCancel, onConfirm, - ownFocus = true, title, ...rest -}) => { - const modal = ( - - - {title} - - {children} - - - - - - - - - - {confirmButtonText} - - - - - - ); - - return ownFocus ? ( - {modal} - ) : ( - modal - ); -}; +}) => ( + + + {title} + + {children} + + + + + + + + + + {confirmButtonText} + + + + + +); diff --git a/x-pack/plugins/security/public/management/api_keys/api_keys_grid/invalidate_provider/invalidate_provider.tsx b/x-pack/plugins/security/public/management/api_keys/api_keys_grid/invalidate_provider/invalidate_provider.tsx index ae142e76877ce..232847b63cb1a 100644 --- a/x-pack/plugins/security/public/management/api_keys/api_keys_grid/invalidate_provider/invalidate_provider.tsx +++ b/x-pack/plugins/security/public/management/api_keys/api_keys_grid/invalidate_provider/invalidate_provider.tsx @@ -6,7 +6,7 @@ */ import React, { Fragment, useRef, useState } from 'react'; -import { EuiConfirmModal, EuiOverlayMask } from '@elastic/eui'; +import { EuiConfirmModal } from '@elastic/eui'; import type { PublicMethodsOf } from '@kbn/utility-types'; import { i18n } from '@kbn/i18n'; import { NotificationsStart } from 'src/core/public'; @@ -127,58 +127,56 @@ export const InvalidateProvider: React.FunctionComponent = ({ const isSingle = apiKeys.length === 1; return ( - - - {!isSingle ? ( - -

    - {i18n.translate( - 'xpack.security.management.apiKeys.invalidateApiKey.confirmModal.invalidateMultipleListDescription', - { defaultMessage: 'You are about to invalidate these API keys:' } - )} -

    -
      - {apiKeys.map(({ name, id }) => ( -
    • {name}
    • - ))} -
    -
    - ) : null} -
    -
    + )} + buttonColor="danger" + data-test-subj="invalidateApiKeyConfirmationModal" + > + {!isSingle ? ( + +

    + {i18n.translate( + 'xpack.security.management.apiKeys.invalidateApiKey.confirmModal.invalidateMultipleListDescription', + { defaultMessage: 'You are about to invalidate these API keys:' } + )} +

    +
      + {apiKeys.map(({ name, id }) => ( +
    • {name}
    • + ))} +
    +
    + ) : null} + ); }; diff --git a/x-pack/plugins/security/public/management/role_mappings/components/delete_provider/delete_provider.tsx b/x-pack/plugins/security/public/management/role_mappings/components/delete_provider/delete_provider.tsx index c5e6e3cb9860d..680a4a40a7d9a 100644 --- a/x-pack/plugins/security/public/management/role_mappings/components/delete_provider/delete_provider.tsx +++ b/x-pack/plugins/security/public/management/role_mappings/components/delete_provider/delete_provider.tsx @@ -6,7 +6,7 @@ */ import React, { Fragment, useRef, useState, ReactElement } from 'react'; -import { EuiConfirmModal, EuiOverlayMask } from '@elastic/eui'; +import { EuiConfirmModal } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import type { PublicMethodsOf } from '@kbn/utility-types'; import { NotificationsStart } from 'src/core/public'; @@ -140,59 +140,57 @@ export const DeleteProvider: React.FunctionComponent = ({ const isSingle = roleMappings.length === 1; return ( - - - {!isSingle ? ( - -

    - {i18n.translate( - 'xpack.security.management.roleMappings.deleteRoleMapping.confirmModal.deleteMultipleListDescription', - { defaultMessage: 'You are about to delete these role mappings:' } - )} -

    -
      - {roleMappings.map(({ name }) => ( -
    • {name}
    • - ))} -
    -
    - ) : null} -
    -
    + )} + confirmButtonDisabled={isDeleteInProgress} + buttonColor="danger" + data-test-subj="deleteRoleMappingConfirmationModal" + > + {!isSingle ? ( + +

    + {i18n.translate( + 'xpack.security.management.roleMappings.deleteRoleMapping.confirmModal.deleteMultipleListDescription', + { defaultMessage: 'You are about to delete these role mappings:' } + )} +

    +
      + {roleMappings.map(({ name }) => ( +
    • {name}
    • + ))} +
    +
    + ) : null} + ); }; diff --git a/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/rule_editor_panel/rule_editor_panel.tsx b/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/rule_editor_panel/rule_editor_panel.tsx index b094f78a53e77..d027a1aeb7e1f 100644 --- a/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/rule_editor_panel/rule_editor_panel.tsx +++ b/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/rule_editor_panel/rule_editor_panel.tsx @@ -9,7 +9,6 @@ import React, { Component, Fragment } from 'react'; import { EuiSpacer, EuiConfirmModal, - EuiOverlayMask, EuiCallOut, EuiErrorBoundary, EuiIcon, @@ -228,40 +227,38 @@ export class RuleEditorPanel extends Component { return null; } return ( - - - } - onCancel={() => this.setState({ showConfirmModeChange: false })} - onConfirm={() => { - this.setState({ mode: 'visual', showConfirmModeChange: false }); - this.onValidityChange(true); - }} - cancelButtonText={ - - } - confirmButtonText={ - - } - > -

    - -

    -
    -
    + + } + onCancel={() => this.setState({ showConfirmModeChange: false })} + onConfirm={() => { + this.setState({ mode: 'visual', showConfirmModeChange: false }); + this.onValidityChange(true); + }} + cancelButtonText={ + + } + confirmButtonText={ + + } + > +

    + +

    +
    ); }; diff --git a/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/rule_editor_panel/rule_group_title.tsx b/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/rule_editor_panel/rule_group_title.tsx index 6e94abfb3f4a2..478e8d87abf95 100644 --- a/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/rule_editor_panel/rule_group_title.tsx +++ b/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/rule_editor_panel/rule_group_title.tsx @@ -12,7 +12,6 @@ import { EuiContextMenuItem, EuiLink, EuiIcon, - EuiOverlayMask, EuiConfirmModal, } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; @@ -87,45 +86,43 @@ export const RuleGroupTitle = (props: Props) => { ); const confirmChangeModal = showConfirmChangeModal ? ( - - - } - onCancel={() => { - setShowConfirmChangeModal(false); - setPendingNewRule(null); - }} - onConfirm={() => { - setShowConfirmChangeModal(false); - changeRuleDiscardingSubRules(pendingNewRule!); - setPendingNewRule(null); - }} - cancelButtonText={ - - } - confirmButtonText={ - - } - > -

    - -

    -
    -
    + + } + onCancel={() => { + setShowConfirmChangeModal(false); + setPendingNewRule(null); + }} + onConfirm={() => { + setShowConfirmChangeModal(false); + changeRuleDiscardingSubRules(pendingNewRule!); + setPendingNewRule(null); + }} + cancelButtonText={ + + } + confirmButtonText={ + + } + > +

    + +

    +
    ) : null; return ( diff --git a/x-pack/plugins/security/public/management/roles/edit_role/delete_role_button.tsx b/x-pack/plugins/security/public/management/roles/edit_role/delete_role_button.tsx index bd3c86575c61a..1b3a7fa024dd1 100644 --- a/x-pack/plugins/security/public/management/roles/edit_role/delete_role_button.tsx +++ b/x-pack/plugins/security/public/management/roles/edit_role/delete_role_button.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { EuiButtonEmpty, EuiConfirmModal, EuiOverlayMask } from '@elastic/eui'; +import { EuiButtonEmpty, EuiConfirmModal } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import React, { Component, Fragment } from 'react'; @@ -46,44 +46,42 @@ export class DeleteRoleButton extends Component { return null; } return ( - - - } - onCancel={this.closeModal} - onConfirm={this.onConfirmDelete} - cancelButtonText={ - - } - confirmButtonText={ - - } - buttonColor={'danger'} - > -

    - -

    -

    - -

    -
    -
    + + } + onCancel={this.closeModal} + onConfirm={this.onConfirmDelete} + cancelButtonText={ + + } + confirmButtonText={ + + } + buttonColor={'danger'} + > +

    + +

    +

    + +

    +
    ); }; diff --git a/x-pack/plugins/security/public/management/roles/roles_grid/confirm_delete/confirm_delete.tsx b/x-pack/plugins/security/public/management/roles/roles_grid/confirm_delete/confirm_delete.tsx index dbbb09f1598b6..81302465bb373 100644 --- a/x-pack/plugins/security/public/management/roles/roles_grid/confirm_delete/confirm_delete.tsx +++ b/x-pack/plugins/security/public/management/roles/roles_grid/confirm_delete/confirm_delete.tsx @@ -14,7 +14,6 @@ import { EuiModalFooter, EuiModalHeader, EuiModalHeaderTitle, - EuiOverlayMask, EuiText, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; @@ -55,65 +54,61 @@ export class ConfirmDelete extends Component { // to disable the buttons since this could be a long-running operation return ( - - - - - {title} - - - - - {moreThanOne ? ( - -

    - -

    -
      - {rolesToDelete.map((roleName) => ( -
    • {roleName}
    • - ))} -
    -
    - ) : null} -

    - -

    -
    -
    - - + + + {title} + + + + {moreThanOne ? ( + +

    + +

    +
      + {rolesToDelete.map((roleName) => ( +
    • {roleName}
    • + ))} +
    +
    + ) : null} +

    - +

    +
    +
    + + + + - - - - -
    -
    + + + + + ); } diff --git a/x-pack/plugins/security/public/management/users/components/confirm_delete_users/confirm_delete_users.tsx b/x-pack/plugins/security/public/management/users/components/confirm_delete_users/confirm_delete_users.tsx index c670a9ce99f5b..38adca145dfc5 100644 --- a/x-pack/plugins/security/public/management/users/components/confirm_delete_users/confirm_delete_users.tsx +++ b/x-pack/plugins/security/public/management/users/components/confirm_delete_users/confirm_delete_users.tsx @@ -6,7 +6,7 @@ */ import React, { Component, Fragment } from 'react'; -import { EuiOverlayMask, EuiConfirmModal } from '@elastic/eui'; +import { EuiConfirmModal } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import type { PublicMethodsOf } from '@kbn/utility-types'; @@ -35,46 +35,44 @@ export class ConfirmDeleteUsers extends Component { values: { userLength: usersToDelete[0] }, }); return ( - - -
    - {moreThanOne ? ( - -

    - -

    -
      - {usersToDelete.map((username) => ( -
    • {username}
    • - ))} -
    -
    - ) : null} -

    - -

    -
    -
    -
    + +
    + {moreThanOne ? ( + +

    + +

    +
      + {usersToDelete.map((username) => ( +
    • {username}
    • + ))} +
    +
    + ) : null} +

    + +

    +
    +
    ); } diff --git a/x-pack/plugins/security/public/management/users/edit_user/confirm_delete_users.tsx b/x-pack/plugins/security/public/management/users/edit_user/confirm_delete_users.tsx index 9e8745538e0ed..189f0c3845d63 100644 --- a/x-pack/plugins/security/public/management/users/edit_user/confirm_delete_users.tsx +++ b/x-pack/plugins/security/public/management/users/edit_user/confirm_delete_users.tsx @@ -68,7 +68,6 @@ export const ConfirmDeleteUsers: FunctionComponent = ({ )} confirmButtonColor="danger" isLoading={state.loading} - ownFocus >

    diff --git a/x-pack/plugins/security/public/management/users/edit_user/confirm_disable_users.tsx b/x-pack/plugins/security/public/management/users/edit_user/confirm_disable_users.tsx index 793f0e6c2a420..e0fb4e554ee3c 100644 --- a/x-pack/plugins/security/public/management/users/edit_user/confirm_disable_users.tsx +++ b/x-pack/plugins/security/public/management/users/edit_user/confirm_disable_users.tsx @@ -80,7 +80,6 @@ export const ConfirmDisableUsers: FunctionComponent = } confirmButtonColor={isSystemUser ? 'danger' : undefined} isLoading={state.loading} - ownFocus > {isSystemUser ? ( diff --git a/x-pack/plugins/security/public/management/users/edit_user/confirm_enable_users.tsx b/x-pack/plugins/security/public/management/users/edit_user/confirm_enable_users.tsx index a1aac5bc0a8cb..2cb4cf8b4a9e2 100644 --- a/x-pack/plugins/security/public/management/users/edit_user/confirm_enable_users.tsx +++ b/x-pack/plugins/security/public/management/users/edit_user/confirm_enable_users.tsx @@ -67,7 +67,6 @@ export const ConfirmEnableUsers: FunctionComponent = ({ } )} isLoading={state.loading} - ownFocus >

    diff --git a/x-pack/plugins/security_solution/cypress/tasks/timeline.ts b/x-pack/plugins/security_solution/cypress/tasks/timeline.ts index ca4c869e0f2d3..c001f1fc2bc47 100644 --- a/x-pack/plugins/security_solution/cypress/tasks/timeline.ts +++ b/x-pack/plugins/security_solution/cypress/tasks/timeline.ts @@ -99,7 +99,7 @@ export const goToQueryTab = () => { export const addNotesToTimeline = (notes: string) => { goToNotesTab(); cy.get(NOTES_TEXT_AREA).type(notes); - cy.get(ADD_NOTE_BUTTON).click(); + cy.get(ADD_NOTE_BUTTON).click({ force: true }); cy.get(QUERY_TAB_BUTTON).click(); }; diff --git a/x-pack/plugins/security_solution/public/cases/components/confirm_delete_case/index.tsx b/x-pack/plugins/security_solution/public/cases/components/confirm_delete_case/index.tsx index 1b67aaeb795dd..eb75d896ae778 100644 --- a/x-pack/plugins/security_solution/public/cases/components/confirm_delete_case/index.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/confirm_delete_case/index.tsx @@ -6,7 +6,7 @@ */ import React from 'react'; -import { EuiConfirmModal, EuiOverlayMask } from '@elastic/eui'; +import { EuiConfirmModal } from '@elastic/eui'; import * as i18n from './translations'; interface ConfirmDeleteCaseModalProps { @@ -28,20 +28,18 @@ const ConfirmDeleteCaseModalComp: React.FC = ({ return null; } return ( - - - {isPlural ? i18n.CONFIRM_QUESTION_PLURAL : i18n.CONFIRM_QUESTION} - - + + {isPlural ? i18n.CONFIRM_QUESTION_PLURAL : i18n.CONFIRM_QUESTION} + ); }; diff --git a/x-pack/plugins/security_solution/public/cases/components/use_all_cases_modal/all_cases_modal.tsx b/x-pack/plugins/security_solution/public/cases/components/use_all_cases_modal/all_cases_modal.tsx index 1dfabda8068f1..eda8ed8cdfbcd 100644 --- a/x-pack/plugins/security_solution/public/cases/components/use_all_cases_modal/all_cases_modal.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/use_all_cases_modal/all_cases_modal.tsx @@ -6,13 +6,7 @@ */ import React, { memo } from 'react'; -import { - EuiModal, - EuiModalBody, - EuiModalHeader, - EuiModalHeaderTitle, - EuiOverlayMask, -} from '@elastic/eui'; +import { EuiModal, EuiModalBody, EuiModalHeader, EuiModalHeaderTitle } from '@elastic/eui'; import { useGetUserSavedObjectPermissions } from '../../../common/lib/kibana'; import { Case } from '../../containers/types'; @@ -34,16 +28,14 @@ const AllCasesModalComponent: React.FC = ({ const userCanCrud = userPermissions?.crud ?? false; return isModalOpen ? ( - - - - {i18n.SELECT_CASE_TITLE} - - - - - - + + + {i18n.SELECT_CASE_TITLE} + + + + + ) : null; }; diff --git a/x-pack/plugins/security_solution/public/cases/components/use_create_case_modal/create_case_modal.tsx b/x-pack/plugins/security_solution/public/cases/components/use_create_case_modal/create_case_modal.tsx index 3595f2c916af7..8dd5080666cb3 100644 --- a/x-pack/plugins/security_solution/public/cases/components/use_create_case_modal/create_case_modal.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/use_create_case_modal/create_case_modal.tsx @@ -7,13 +7,7 @@ import React, { memo } from 'react'; import styled from 'styled-components'; -import { - EuiModal, - EuiModalBody, - EuiModalHeader, - EuiModalHeaderTitle, - EuiOverlayMask, -} from '@elastic/eui'; +import { EuiModal, EuiModalBody, EuiModalHeader, EuiModalHeaderTitle } from '@elastic/eui'; import { FormContext } from '../create/form_context'; import { CreateCaseForm } from '../create/form'; @@ -40,21 +34,19 @@ const CreateModalComponent: React.FC = ({ onSuccess, }) => { return isModalOpen ? ( - - - - {i18n.CREATE_TITLE} - - - - - - - - - - - + + + {i18n.CREATE_TITLE} + + + + + + + + + + ) : null; }; diff --git a/x-pack/plugins/security_solution/public/common/components/exceptions/add_exception_modal/index.tsx b/x-pack/plugins/security_solution/public/common/components/exceptions/add_exception_modal/index.tsx index dc7388438c012..5ea11f61f9a7e 100644 --- a/x-pack/plugins/security_solution/public/common/components/exceptions/add_exception_modal/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/exceptions/add_exception_modal/index.tsx @@ -14,7 +14,6 @@ import { EuiModalHeader, EuiModalHeaderTitle, EuiModalFooter, - EuiOverlayMask, EuiButton, EuiButtonEmpty, EuiHorizontalRule, @@ -348,133 +347,129 @@ export const AddExceptionModal = memo(function AddExceptionModal({ }, [maybeRule]); return ( - - - - {addExceptionMessage} - - {ruleName} - - - - {fetchOrCreateListError != null && ( - - - + + + {addExceptionMessage} + + {ruleName} + + + + {fetchOrCreateListError != null && ( + + + + )} + {fetchOrCreateListError == null && + (isLoadingExceptionList || + isIndexPatternLoading || + isSignalIndexLoading || + isSignalIndexPatternLoading) && ( + )} - {fetchOrCreateListError == null && - (isLoadingExceptionList || - isIndexPatternLoading || - isSignalIndexLoading || - isSignalIndexPatternLoading) && ( - - )} - {fetchOrCreateListError == null && - !isSignalIndexLoading && - !isSignalIndexPatternLoading && - !isLoadingExceptionList && - !isIndexPatternLoading && - !isRuleLoading && - !mlJobLoading && - ruleExceptionList && ( - <> - - {isRuleEQLSequenceStatement && ( - <> - - - - )} - {i18n.EXCEPTION_BUILDER_INFO} - - - - - - - - - - {alertData !== undefined && alertStatus !== 'closed' && ( - - - - )} + {fetchOrCreateListError == null && + !isSignalIndexLoading && + !isSignalIndexPatternLoading && + !isLoadingExceptionList && + !isIndexPatternLoading && + !isRuleLoading && + !mlJobLoading && + ruleExceptionList && ( + <> + + {isRuleEQLSequenceStatement && ( + <> + + + + )} + {i18n.EXCEPTION_BUILDER_INFO} + + + + + + + + + + {alertData !== undefined && alertStatus !== 'closed' && ( - {exceptionListType === 'endpoint' && ( - <> - - - {i18n.ENDPOINT_QUARANTINE_TEXT} - - - )} - - - )} - {fetchOrCreateListError == null && ( - - {i18n.CANCEL} - - - {addExceptionMessage} - - + )} + + + + {exceptionListType === 'endpoint' && ( + <> + + + {i18n.ENDPOINT_QUARANTINE_TEXT} + + + )} + + )} - - + {fetchOrCreateListError == null && ( + + {i18n.CANCEL} + + + {addExceptionMessage} + + + )} + ); }); diff --git a/x-pack/plugins/security_solution/public/common/components/exceptions/edit_exception_modal/index.tsx b/x-pack/plugins/security_solution/public/common/components/exceptions/edit_exception_modal/index.tsx index 75b7bf2aabd7f..336732016e936 100644 --- a/x-pack/plugins/security_solution/public/common/components/exceptions/edit_exception_modal/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/exceptions/edit_exception_modal/index.tsx @@ -12,7 +12,6 @@ import { EuiModalHeader, EuiModalHeaderTitle, EuiModalFooter, - EuiOverlayMask, EuiButton, EuiButtonEmpty, EuiHorizontalRule, @@ -281,125 +280,121 @@ export const EditExceptionModal = memo(function EditExceptionModal({ }, [maybeRule]); return ( - - - - - {exceptionListType === 'endpoint' - ? i18n.EDIT_ENDPOINT_EXCEPTION_TITLE - : i18n.EDIT_EXCEPTION_TITLE} - - - {ruleName} - - - {(addExceptionIsLoading || isIndexPatternLoading || isSignalIndexLoading) && ( - - )} - {!isSignalIndexLoading && - !addExceptionIsLoading && - !isIndexPatternLoading && - !isRuleLoading && - !mlJobLoading && ( - <> - - {isRuleEQLSequenceStatement && ( - <> - - - - )} - {i18n.EXCEPTION_BUILDER_INFO} - - - - - - - - - - - + + + {exceptionListType === 'endpoint' + ? i18n.EDIT_ENDPOINT_EXCEPTION_TITLE + : i18n.EDIT_EXCEPTION_TITLE} + + + {ruleName} + + + {(addExceptionIsLoading || isIndexPatternLoading || isSignalIndexLoading) && ( + + )} + {!isSignalIndexLoading && + !addExceptionIsLoading && + !isIndexPatternLoading && + !isRuleLoading && + !mlJobLoading && ( + <> + + {isRuleEQLSequenceStatement && ( + <> + - - {exceptionListType === 'endpoint' && ( - <> - - - {i18n.ENDPOINT_QUARANTINE_TEXT} - - - )} - - - )} - {updateError != null && ( - - - - )} - {hasVersionConflict && ( - - -

    {i18n.VERSION_CONFLICT_ERROR_DESCRIPTION}

    - - - )} - {updateError == null && ( - - {i18n.CANCEL} - - - {i18n.EDIT_EXCEPTION_SAVE_BUTTON} - - + + + )} + {i18n.EXCEPTION_BUILDER_INFO} + + + + + + + + + + + + + {exceptionListType === 'endpoint' && ( + <> + + + {i18n.ENDPOINT_QUARANTINE_TEXT} + + + )} + + )} - - + {updateError != null && ( + + + + )} + {hasVersionConflict && ( + + +

    {i18n.VERSION_CONFLICT_ERROR_DESCRIPTION}

    +
    +
    + )} + {updateError == null && ( + + {i18n.CANCEL} + + + {i18n.EDIT_EXCEPTION_SAVE_BUTTON} + + + )} + ); }); diff --git a/x-pack/plugins/security_solution/public/common/components/import_data_modal/__snapshots__/index.test.tsx.snap b/x-pack/plugins/security_solution/public/common/components/import_data_modal/__snapshots__/index.test.tsx.snap index 6503dd8dfb508..d1a41b1c32c10 100644 --- a/x-pack/plugins/security_solution/public/common/components/import_data_modal/__snapshots__/index.test.tsx.snap +++ b/x-pack/plugins/security_solution/public/common/components/import_data_modal/__snapshots__/index.test.tsx.snap @@ -2,64 +2,62 @@ exports[`ImportDataModal renders correctly against snapshot 1`] = ` - - - - - title - - - - -

    - description -

    -
    - - - - -
    - - - Cancel - - - submitBtnText - - -
    -
    + + + + title + + + + +

    + description +

    +
    + + + + +
    + + + Cancel + + + submitBtnText + + +
    `; diff --git a/x-pack/plugins/security_solution/public/common/components/import_data_modal/index.tsx b/x-pack/plugins/security_solution/public/common/components/import_data_modal/index.tsx index 8a29ce3799321..4c3dc2a249b4f 100644 --- a/x-pack/plugins/security_solution/public/common/components/import_data_modal/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/import_data_modal/index.tsx @@ -15,7 +15,6 @@ import { EuiModalFooter, EuiModalHeader, EuiModalHeaderTitle, - EuiOverlayMask, EuiSpacer, EuiText, } from '@elastic/eui'; @@ -132,51 +131,49 @@ export const ImportDataModalComponent = ({ return ( <> {showModal && ( - - - - {title} - - - - -

    {description}

    -
    - - - { - setSelectedFiles(files && files.length > 0 ? files : null); - }} - display={'large'} - fullWidth={true} - isLoading={isImporting} + + + {title} + + + + +

    {description}

    +
    + + + { + setSelectedFiles(files && files.length > 0 ? files : null); + }} + display={'large'} + fullWidth={true} + isLoading={isImporting} + /> + + {showCheckBox && ( + setOverwrite(!overwrite)} /> - - {showCheckBox && ( - setOverwrite(!overwrite)} - /> - )} -
    - - - {i18n.CANCEL_BUTTON} - - {submitBtnText} - - -
    -
    + )} + + + + {i18n.CANCEL_BUTTON} + + {submitBtnText} + + + )} ); diff --git a/x-pack/plugins/security_solution/public/common/components/inspect/modal.tsx b/x-pack/plugins/security_solution/public/common/components/inspect/modal.tsx index ece29cd360ce7..a5c0144531110 100644 --- a/x-pack/plugins/security_solution/public/common/components/inspect/modal.tsx +++ b/x-pack/plugins/security_solution/public/common/components/inspect/modal.tsx @@ -15,7 +15,6 @@ import { EuiModalHeader, EuiModalHeaderTitle, EuiModalFooter, - EuiOverlayMask, EuiSpacer, EuiTabbedContent, } from '@elastic/eui'; @@ -211,24 +210,22 @@ export const ModalInspectQuery = ({ ]; return ( - - - - - {i18n.INSPECT} {title} - - - - - - - - - - {i18n.CLOSE} - - - - + + + + {i18n.INSPECT} {title} + + + + + + + + + + {i18n.CLOSE} + + + ); }; diff --git a/x-pack/plugins/security_solution/public/common/components/paginated_table/__snapshots__/index.test.tsx.snap b/x-pack/plugins/security_solution/public/common/components/paginated_table/__snapshots__/index.test.tsx.snap index 778916ad2d07a..be5702550a44c 100644 --- a/x-pack/plugins/security_solution/public/common/components/paginated_table/__snapshots__/index.test.tsx.snap +++ b/x-pack/plugins/security_solution/public/common/components/paginated_table/__snapshots__/index.test.tsx.snap @@ -246,6 +246,12 @@ exports[`Paginated Table Component rendering it renders the default load more ta }, "euiFilePickerTallHeight": "128px", "euiFlyoutBorder": "1px solid #343741", + "euiFlyoutPaddingModifiers": Object { + "paddingLarge": "24px", + "paddingMedium": "16px", + "paddingNone": 0, + "paddingSmall": "8px", + }, "euiFocusBackgroundColor": "#08334a", "euiFocusRingAnimStartColor": "rgba(27, 169, 245, 0)", "euiFocusRingAnimStartSize": "6px", @@ -357,6 +363,7 @@ exports[`Paginated Table Component rendering it renders the default load more ta }, "euiMarkdownEditorMinHeight": "150px", "euiPageBackgroundColor": "#1a1b20", + "euiPageDefaultMaxWidth": "1000px", "euiPaletteColorBlind": Object { "euiColorVis0": Object { "behindText": "#6dccb1", @@ -534,6 +541,7 @@ exports[`Paginated Table Component rendering it renders the default load more ta "euiSwitchWidthCompressed": "28px", "euiSwitchWidthMini": "22px", "euiTabFontSize": "16px", + "euiTabFontSizeL": "18px", "euiTabFontSizeS": "14px", "euiTableActionsAreaWidth": "40px", "euiTableActionsBorderColor": "rgba(83, 89, 102, 0.09999999999999998)", diff --git a/x-pack/plugins/security_solution/public/common/components/toasters/__snapshots__/modal_all_errors.test.tsx.snap b/x-pack/plugins/security_solution/public/common/components/toasters/__snapshots__/modal_all_errors.test.tsx.snap index f7924f37d2c17..5e008e28073de 100644 --- a/x-pack/plugins/security_solution/public/common/components/toasters/__snapshots__/modal_all_errors.test.tsx.snap +++ b/x-pack/plugins/security_solution/public/common/components/toasters/__snapshots__/modal_all_errors.test.tsx.snap @@ -1,50 +1,48 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`Modal all errors rendering it renders the default all errors modal when isShowing is positive 1`] = ` - - - - - Your visualization has error(s) - - - - - - - - Error 1, Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. - - - - - - Close - - - - + + + + Your visualization has error(s) + + + + + + + + Error 1, Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. + + + + + + Close + + + `; diff --git a/x-pack/plugins/security_solution/public/common/components/toasters/modal_all_errors.tsx b/x-pack/plugins/security_solution/public/common/components/toasters/modal_all_errors.tsx index 873ebe97317f4..0a78139f5fe3a 100644 --- a/x-pack/plugins/security_solution/public/common/components/toasters/modal_all_errors.tsx +++ b/x-pack/plugins/security_solution/public/common/components/toasters/modal_all_errors.tsx @@ -7,7 +7,6 @@ import { EuiButton, - EuiOverlayMask, EuiModal, EuiModalHeader, EuiModalHeaderTitle, @@ -36,36 +35,34 @@ const ModalAllErrorsComponent: React.FC = ({ isShowing, toast, t if (!isShowing || toast == null) return null; return ( - - - - {i18n.TITLE_ERROR_MODAL} - + + + {i18n.TITLE_ERROR_MODAL} + - - - - {toast.errors != null && - toast.errors.map((error, index) => ( - 100 ? `${error.substring(0, 100)} ...` : error} - data-test-subj="modal-all-errors-accordion" - > - {error} - - ))} - + + + + {toast.errors != null && + toast.errors.map((error, index) => ( + 100 ? `${error.substring(0, 100)} ...` : error} + data-test-subj="modal-all-errors-accordion" + > + {error} + + ))} + - - - {i18n.CLOSE_ERROR_MODAL} - - - - + + + {i18n.CLOSE_ERROR_MODAL} + + + ); }; diff --git a/x-pack/plugins/security_solution/public/detections/components/value_lists_management_modal/modal.tsx b/x-pack/plugins/security_solution/public/detections/components/value_lists_management_modal/modal.tsx index adc46f08272d7..aefa447269f46 100644 --- a/x-pack/plugins/security_solution/public/detections/components/value_lists_management_modal/modal.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/value_lists_management_modal/modal.tsx @@ -15,7 +15,6 @@ import { EuiModalFooter, EuiModalHeader, EuiModalHeaderTitle, - EuiOverlayMask, EuiPanel, EuiSpacer, EuiText, @@ -211,7 +210,7 @@ export const ValueListsModalComponent: React.FC = ({ const columns = buildColumns(handleExport, handleDelete); return ( - + <> {i18n.MODAL_TITLE} @@ -255,7 +254,7 @@ export const ValueListsModalComponent: React.FC = ({ name={exportDownload.name} onDownload={() => setExportDownload({})} /> - + ); }; diff --git a/x-pack/plugins/security_solution/public/detections/components/value_lists_management_modal/reference_error_modal/reference_error_modal.tsx b/x-pack/plugins/security_solution/public/detections/components/value_lists_management_modal/reference_error_modal/reference_error_modal.tsx index 20744c3a22515..e4d8e2cee3268 100644 --- a/x-pack/plugins/security_solution/public/detections/components/value_lists_management_modal/reference_error_modal/reference_error_modal.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/value_lists_management_modal/reference_error_modal/reference_error_modal.tsx @@ -6,7 +6,7 @@ */ import React from 'react'; -import { EuiConfirmModal, EuiListGroup, EuiListGroupItem, EuiOverlayMask } from '@elastic/eui'; +import { EuiConfirmModal, EuiListGroup, EuiListGroupItem } from '@elastic/eui'; import styled from 'styled-components'; import { rgba } from 'polished'; @@ -59,28 +59,26 @@ export const ReferenceErrorModalComponent: React.FC = } return ( - - -

    {contentText}

    - - - {references.map((r, index) => ( - - ))} - - -
    -
    + +

    {contentText}

    + + + {references.map((r, index) => ( + + ))} + + +
    ); }; diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/rules_tables.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/rules_tables.tsx index 89785efbb5047..04bf3c544030a 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/rules_tables.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/rules_tables.tsx @@ -9,7 +9,6 @@ import { EuiBasicTable, EuiLoadingContent, EuiProgress, - EuiOverlayMask, EuiConfirmModal, EuiWindowEvent, } from '@elastic/eui'; @@ -490,18 +489,16 @@ export const RulesTables = React.memo( )} {showIdleModal && ( - - -

    {i18n.REFRESH_PROMPT_BODY}

    -
    -
    + +

    {i18n.REFRESH_PROMPT_BODY}

    +
    )} {shouldShowRulesTable && ( <> diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_details.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_details.tsx index ec0198de58558..e14f56881d673 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_details.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_details.tsx @@ -12,7 +12,6 @@ import { EuiButton, EuiButtonEmpty, EuiSpacer, - EuiOverlayMask, EuiConfirmModal, EuiCallOut, EuiLoadingSpinner, @@ -234,59 +233,54 @@ const ConfirmUpdate = React.memo<{ onCancel: () => void; }>(({ hostCount, onCancel, onConfirm }) => { return ( - - - {hostCount > 0 && ( - <> - - - - - - )} -

    - -

    -
    -
    + + {hostCount > 0 && ( + <> + + + + + + )} +

    + +

    +
    ); }); diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/trusted_app_deletion_dialog.tsx b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/trusted_app_deletion_dialog.tsx index 4e3dc953b539e..bffd980610372 100644 --- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/trusted_app_deletion_dialog.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/trusted_app_deletion_dialog.tsx @@ -19,7 +19,6 @@ import { EuiModalFooter, EuiModalHeader, EuiModalHeaderTitle, - EuiOverlayMask, EuiText, } from '@elastic/eui'; @@ -100,36 +99,34 @@ export const TrustedAppDeletionDialog = memo(() => { if (useTrustedAppsSelector(isDeletionDialogOpen)) { return ( - - - - {translations.title} - + + + {translations.title} + - - -

    {translations.mainMessage}

    -

    {translations.subMessage}

    -
    -
    + + +

    {translations.mainMessage}

    +

    {translations.subMessage}

    +
    +
    - - - {translations.cancelButton} - + + + {translations.cancelButton} + - - {translations.confirmButton} - - -
    -
    + + {translations.confirmButton} + + + ); } else { return <>; diff --git a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/delete_timeline_modal/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/delete_timeline_modal/index.tsx index a87f486a9d5d1..7dde3fbe4cd2a 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/delete_timeline_modal/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/delete_timeline_modal/index.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { EuiModal, EuiOverlayMask } from '@elastic/eui'; +import { EuiModal } from '@elastic/eui'; import React, { useCallback } from 'react'; import { createGlobalStyle } from 'styled-components'; @@ -46,16 +46,14 @@ export const DeleteTimelineModalOverlay = React.memo( <> {isModalOpen && } {isModalOpen ? ( - - - - - + + + ) : null} ); diff --git a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/open_timeline_modal/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/open_timeline_modal/index.tsx index 5b7fbcffd14ad..c23cffa854514 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/open_timeline_modal/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/open_timeline_modal/index.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { EuiModal, EuiOverlayMask } from '@elastic/eui'; +import { EuiModal } from '@elastic/eui'; import React from 'react'; import { TimelineModel } from '../../../../timelines/store/timeline/model'; @@ -26,22 +26,20 @@ const OPEN_TIMELINE_MODAL_WIDTH = 1100; // px export const OpenTimelineModal = React.memo( ({ hideActions = [], modalTitle, onClose, onOpen }) => ( - - - - - + + + ) ); diff --git a/x-pack/plugins/security_solution/public/timelines/components/side_panel/__snapshots__/index.test.tsx.snap b/x-pack/plugins/security_solution/public/timelines/components/side_panel/__snapshots__/index.test.tsx.snap index 124c8012fd533..aece377ee4f2d 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/side_panel/__snapshots__/index.test.tsx.snap +++ b/x-pack/plugins/security_solution/public/timelines/components/side_panel/__snapshots__/index.test.tsx.snap @@ -287,7 +287,7 @@ Array [ data-eui="EuiFocusTrap" >
    - - {isSaving && ( - + + {isSaving && ( + + )} + {modalHeader} + + + {showWarning && ( + + + + )} - {modalHeader} - - - {showWarning && ( - - - - - )} -
    - - - - - - - - - - - - - {closeModalText} - - - - - {saveButtonTitle} - - - - -
    -
    -
    - +
    + + + + + + + + + + + + + {closeModalText} + + + + + {saveButtonTitle} + + + + +
    + +
    ); } ); diff --git a/x-pack/plugins/snapshot_restore/public/application/components/policy_delete_provider.tsx b/x-pack/plugins/snapshot_restore/public/application/components/policy_delete_provider.tsx index 0638d3349206d..792538a730ebe 100644 --- a/x-pack/plugins/snapshot_restore/public/application/components/policy_delete_provider.tsx +++ b/x-pack/plugins/snapshot_restore/public/application/components/policy_delete_provider.tsx @@ -7,7 +7,7 @@ import React, { Fragment, useRef, useState } from 'react'; import { FormattedMessage } from '@kbn/i18n/react'; -import { EuiConfirmModal, EuiOverlayMask } from '@elastic/eui'; +import { EuiConfirmModal } from '@elastic/eui'; import { useServices, useToastNotifications } from '../app_context'; import { deletePolicies } from '../services/http'; @@ -96,58 +96,56 @@ export const PolicyDeleteProvider: React.FunctionComponent = ({ children const isSingle = policyNames.length === 1; return ( - - - ) : ( - - ) - } - onCancel={closeModal} - onConfirm={deletePolicy} - cancelButtonText={ + - } - confirmButtonText={ + ) : ( - } - buttonColor="danger" - data-test-subj="srdeletePolicyConfirmationModal" - > - {!isSingle ? ( - -

    - -

    -
      - {policyNames.map((name) => ( -
    • {name}
    • - ))} -
    -
    - ) : null} -
    -
    + ) + } + onCancel={closeModal} + onConfirm={deletePolicy} + cancelButtonText={ + + } + confirmButtonText={ + + } + buttonColor="danger" + data-test-subj="srdeletePolicyConfirmationModal" + > + {!isSingle ? ( + +

    + +

    +
      + {policyNames.map((name) => ( +
    • {name}
    • + ))} +
    +
    + ) : null} + ); }; diff --git a/x-pack/plugins/snapshot_restore/public/application/components/policy_execute_provider.tsx b/x-pack/plugins/snapshot_restore/public/application/components/policy_execute_provider.tsx index 3fcf5a35b3455..5636ca651b628 100644 --- a/x-pack/plugins/snapshot_restore/public/application/components/policy_execute_provider.tsx +++ b/x-pack/plugins/snapshot_restore/public/application/components/policy_execute_provider.tsx @@ -7,7 +7,7 @@ import React, { Fragment, useRef, useState } from 'react'; import { FormattedMessage } from '@kbn/i18n/react'; -import { EuiConfirmModal, EuiOverlayMask } from '@elastic/eui'; +import { EuiConfirmModal } from '@elastic/eui'; import { useServices, useToastNotifications } from '../app_context'; import { executePolicy as executePolicyRequest } from '../services/http'; @@ -81,32 +81,30 @@ export const PolicyExecuteProvider: React.FunctionComponent = ({ children } return ( - - - } - onCancel={closeModal} - onConfirm={executePolicy} - cancelButtonText={ - - } - confirmButtonText={ - - } - data-test-subj="srExecutePolicyConfirmationModal" - /> - + + } + onCancel={closeModal} + onConfirm={executePolicy} + cancelButtonText={ + + } + confirmButtonText={ + + } + data-test-subj="srExecutePolicyConfirmationModal" + /> ); }; diff --git a/x-pack/plugins/snapshot_restore/public/application/components/repository_delete_provider.tsx b/x-pack/plugins/snapshot_restore/public/application/components/repository_delete_provider.tsx index 3009413541111..f02f160958a20 100644 --- a/x-pack/plugins/snapshot_restore/public/application/components/repository_delete_provider.tsx +++ b/x-pack/plugins/snapshot_restore/public/application/components/repository_delete_provider.tsx @@ -7,7 +7,7 @@ import React, { Fragment, useRef, useState } from 'react'; import { FormattedMessage } from '@kbn/i18n/react'; -import { EuiConfirmModal, EuiOverlayMask } from '@elastic/eui'; +import { EuiConfirmModal } from '@elastic/eui'; import { Repository } from '../../../common/types'; import { useServices, useToastNotifications } from '../app_context'; @@ -109,79 +109,77 @@ export const RepositoryDeleteProvider: React.FunctionComponent = ({ child const isSingle = repositoryNames.length === 1; return ( - - - ) : ( - - ) - } - onCancel={closeModal} - onConfirm={deleteRepository} - cancelButtonText={ + - } - confirmButtonText={ - isSingle ? ( - - ) : ( + ) : ( + + ) + } + onCancel={closeModal} + onConfirm={deleteRepository} + cancelButtonText={ + + } + confirmButtonText={ + isSingle ? ( + + ) : ( + + ) + } + buttonColor="danger" + data-test-subj="deleteRepositoryConfirmation" + > + {isSingle ? ( +

    + +

    + ) : ( + +

    - ) - } - buttonColor="danger" - data-test-subj="deleteRepositoryConfirmation" - > - {isSingle ? ( +

    +
      + {repositoryNames.map((name) => ( +
    • {name}
    • + ))} +

    - ) : ( - -

    - -

    -
      - {repositoryNames.map((name) => ( -
    • {name}
    • - ))} -
    -

    - -

    -
    - )} -
    -
    + + )} +
    ); }; diff --git a/x-pack/plugins/snapshot_restore/public/application/components/retention_execute_modal_provider.tsx b/x-pack/plugins/snapshot_restore/public/application/components/retention_execute_modal_provider.tsx index 9366815a0256e..4ce1d93955952 100644 --- a/x-pack/plugins/snapshot_restore/public/application/components/retention_execute_modal_provider.tsx +++ b/x-pack/plugins/snapshot_restore/public/application/components/retention_execute_modal_provider.tsx @@ -7,7 +7,7 @@ import React, { useState } from 'react'; import { FormattedMessage } from '@kbn/i18n/react'; -import { EuiConfirmModal, EuiOverlayMask } from '@elastic/eui'; +import { EuiConfirmModal } from '@elastic/eui'; import { useServices, useToastNotifications } from '../app_context'; import { executeRetention as executeRetentionRequest } from '../services/http'; @@ -58,31 +58,29 @@ export const RetentionExecuteModalProvider: React.FunctionComponent = ({ } return ( - - - } - onCancel={closeModal} - onConfirm={executeRetention} - cancelButtonText={ - - } - confirmButtonText={ - - } - data-test-subj="executeRetentionModal" - /> - + + } + onCancel={closeModal} + onConfirm={executeRetention} + cancelButtonText={ + + } + confirmButtonText={ + + } + data-test-subj="executeRetentionModal" + /> ); }; diff --git a/x-pack/plugins/snapshot_restore/public/application/components/retention_update_modal_provider.tsx b/x-pack/plugins/snapshot_restore/public/application/components/retention_update_modal_provider.tsx index d8916ce9858f8..73e19eee8bf7a 100644 --- a/x-pack/plugins/snapshot_restore/public/application/components/retention_update_modal_provider.tsx +++ b/x-pack/plugins/snapshot_restore/public/application/components/retention_update_modal_provider.tsx @@ -8,7 +8,6 @@ import React, { Fragment, useRef, useState } from 'react'; import { FormattedMessage } from '@kbn/i18n/react'; import { - EuiOverlayMask, EuiModal, EuiModalHeader, EuiModalHeaderTitle, @@ -129,165 +128,161 @@ export const RetentionSettingsUpdateModalProvider: React.FunctionComponent - - - - {isEditing ? ( - - ) : ( - - )} - - - - - {saveError && ( - - - } - color="danger" - iconType="alert" - > - {saveError.data && saveError.data.message ? ( -

    {saveError.data.message}

    - ) : null} -
    - -
    + + + + {isEditing ? ( + + ) : ( + )} - {isAdvancedCronVisible ? ( - - - } - isInvalid={isInvalid} - error={i18n.translate( - 'xpack.snapshotRestore.policyForm.stepRetention.policyUpdateRetentionScheduleFieldErrorMessage', - { - defaultMessage: 'Retention schedule is required.', - } - )} - helpText={ - - - - ), - }} - /> + + + + + {saveError && ( + + + } + color="danger" + iconType="alert" + > + {saveError.data && saveError.data.message ?

    {saveError.data.message}

    : null} +
    + +
    + )} + {isAdvancedCronVisible ? ( + + + } + isInvalid={isInvalid} + error={i18n.translate( + 'xpack.snapshotRestore.policyForm.stepRetention.policyUpdateRetentionScheduleFieldErrorMessage', + { + defaultMessage: 'Retention schedule is required.', } - fullWidth - > - setRetentionSchedule(e.target.value)} + )} + helpText={ + + + + ), + }} /> - + } + fullWidth + > + setRetentionSchedule(e.target.value)} + /> + - + - - { - setIsAdvancedCronVisible(false); - setRetentionSchedule(simpleCron.expression); - }} - data-test-subj="showBasicCronLink" - > - - - - - ) : ( - - { - setSimpleCron({ - expression, - frequency, - }); - setFieldToPreferredValueMap(newFieldToPreferredValueMap); - setRetentionSchedule(expression); + + { + setIsAdvancedCronVisible(false); + setRetentionSchedule(simpleCron.expression); }} - /> - - + data-test-subj="showBasicCronLink" + > + + + + + ) : ( + + { + setSimpleCron({ + expression, + frequency, + }); + setFieldToPreferredValueMap(newFieldToPreferredValueMap); + setRetentionSchedule(expression); + }} + /> - - { - setIsAdvancedCronVisible(true); - }} - data-test-subj="showAdvancedCronLink" - > - - - - - )} -
    + - - + + { + setIsAdvancedCronVisible(true); + }} + data-test-subj="showAdvancedCronLink" + > + + + + + )} +
    + + + + + + + + {isEditing ? ( - - - - {isEditing ? ( - - ) : ( - - )} - - -
    - + ) : ( + + )} + + + ); }; diff --git a/x-pack/plugins/snapshot_restore/public/application/components/snapshot_delete_provider.tsx b/x-pack/plugins/snapshot_restore/public/application/components/snapshot_delete_provider.tsx index 40af1b07a50bc..74614efb314aa 100644 --- a/x-pack/plugins/snapshot_restore/public/application/components/snapshot_delete_provider.tsx +++ b/x-pack/plugins/snapshot_restore/public/application/components/snapshot_delete_provider.tsx @@ -9,7 +9,6 @@ import React, { Fragment, useRef, useState } from 'react'; import { FormattedMessage } from '@kbn/i18n/react'; import { EuiConfirmModal, - EuiOverlayMask, EuiCallOut, EuiLoadingSpinner, EuiFlexGroup, @@ -118,95 +117,93 @@ export const SnapshotDeleteProvider: React.FunctionComponent = ({ childre const isSingle = snapshotIds.length === 1; return ( - - - ) : ( - - ) - } - onCancel={closeModal} - onConfirm={deleteSnapshot} - cancelButtonText={ + - } - confirmButtonText={ + ) : ( - } - confirmButtonDisabled={isDeleting} - buttonColor="danger" - data-test-subj="srdeleteSnapshotConfirmationModal" - > - {!isSingle ? ( - + ) + } + onCancel={closeModal} + onConfirm={deleteSnapshot} + cancelButtonText={ + + } + confirmButtonText={ + + } + confirmButtonDisabled={isDeleting} + buttonColor="danger" + data-test-subj="srdeleteSnapshotConfirmationModal" + > + {!isSingle ? ( + +

    + +

    +
      + {snapshotIds.map(({ snapshot, repository }) => ( +
    • {snapshot}
    • + ))} +
    +
    + ) : null} +

    + +

    + {!isSingle && isDeleting ? ( + + + + + + + + + + + + } + >

    -
      - {snapshotIds.map(({ snapshot, repository }) => ( -
    • {snapshot}
    • - ))} -
    -
    - ) : null} -

    - -

    - {!isSingle && isDeleting ? ( - - - - - - - - - - - - } - > -

    - -

    - - - ) : null} -
    -
    + + + ) : null} +
    ); }; diff --git a/x-pack/plugins/snapshot_restore/public/application/sections/home/repository_list/repository_details/repository_details.tsx b/x-pack/plugins/snapshot_restore/public/application/sections/home/repository_list/repository_details/repository_details.tsx index e7bdde2984d6f..823ce3a122ef1 100644 --- a/x-pack/plugins/snapshot_restore/public/application/sections/home/repository_list/repository_details/repository_details.tsx +++ b/x-pack/plugins/snapshot_restore/public/application/sections/home/repository_list/repository_details/repository_details.tsx @@ -281,7 +281,7 @@ export const RepositoryDetails: React.FunctionComponent = ({ {verification ? ( - + {JSON.stringify( verification.valid ? verification.response : verification.error, null, @@ -350,7 +350,7 @@ export const RepositoryDetails: React.FunctionComponent = ({ /> - + {JSON.stringify(cleanup.response, null, 2)}
    diff --git a/x-pack/plugins/spaces/public/management/components/confirm_delete_modal/__snapshots__/confirm_delete_modal.test.tsx.snap b/x-pack/plugins/spaces/public/management/components/confirm_delete_modal/__snapshots__/confirm_delete_modal.test.tsx.snap index b0d0933614d12..5bf93a1021c05 100644 --- a/x-pack/plugins/spaces/public/management/components/confirm_delete_modal/__snapshots__/confirm_delete_modal.test.tsx.snap +++ b/x-pack/plugins/spaces/public/management/components/confirm_delete_modal/__snapshots__/confirm_delete_modal.test.tsx.snap @@ -1,95 +1,93 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`ConfirmDeleteModal renders as expected 1`] = ` - - - - + + + + + + + + +

    + + , } } /> - - - - -

    - - - , - } - } - /> -

    - - - -
    -
    - - - - - + - - - -
    -
    + + + + + + + + + + + + `; diff --git a/x-pack/plugins/spaces/public/management/components/confirm_delete_modal/confirm_delete_modal.tsx b/x-pack/plugins/spaces/public/management/components/confirm_delete_modal/confirm_delete_modal.tsx index c57bc1cef8fbe..94a5c082834ad 100644 --- a/x-pack/plugins/spaces/public/management/components/confirm_delete_modal/confirm_delete_modal.tsx +++ b/x-pack/plugins/spaces/public/management/components/confirm_delete_modal/confirm_delete_modal.tsx @@ -20,7 +20,6 @@ import { EuiModalHeader, EuiModalHeaderTitle, EuiModalProps, - EuiOverlayMask, EuiSpacer, EuiText, } from '@elastic/eui'; @@ -97,88 +96,86 @@ class ConfirmDeleteModalUI extends Component { }; return ( - - - - + + + + + + + + +

    + + + ), }} /> - - - - -

    - - - - ), - }} - /> -

    - - - - - - {warning} -
    -
    - - + + - - - - - - - -
    -
    + + + {warning} + + + + + + + + + + + + ); } diff --git a/x-pack/plugins/spaces/public/management/edit_space/confirm_alter_active_space_modal/__snapshots__/confirm_alter_active_space_modal.test.tsx.snap b/x-pack/plugins/spaces/public/management/edit_space/confirm_alter_active_space_modal/__snapshots__/confirm_alter_active_space_modal.test.tsx.snap index 750afcfc44e7e..3eb92de017927 100644 --- a/x-pack/plugins/spaces/public/management/edit_space/confirm_alter_active_space_modal/__snapshots__/confirm_alter_active_space_modal.test.tsx.snap +++ b/x-pack/plugins/spaces/public/management/edit_space/confirm_alter_active_space_modal/__snapshots__/confirm_alter_active_space_modal.test.tsx.snap @@ -1,28 +1,26 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`ConfirmAlterActiveSpaceModal renders as expected 1`] = ` - - - } - > -

    - -

    -
    -
    + + } +> +

    + +

    +
    `; diff --git a/x-pack/plugins/spaces/public/management/edit_space/confirm_alter_active_space_modal/confirm_alter_active_space_modal.tsx b/x-pack/plugins/spaces/public/management/edit_space/confirm_alter_active_space_modal/confirm_alter_active_space_modal.tsx index 1839fbdfdda7d..c95bb7250a23e 100644 --- a/x-pack/plugins/spaces/public/management/edit_space/confirm_alter_active_space_modal/confirm_alter_active_space_modal.tsx +++ b/x-pack/plugins/spaces/public/management/edit_space/confirm_alter_active_space_modal/confirm_alter_active_space_modal.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { EuiConfirmModal, EuiOverlayMask } from '@elastic/eui'; +import { EuiConfirmModal } from '@elastic/eui'; import { FormattedMessage, InjectedIntl, injectI18n } from '@kbn/i18n/react'; import React from 'react'; @@ -16,34 +16,32 @@ interface Props { } const ConfirmAlterActiveSpaceModalUI: React.FC = (props) => ( - - - } - defaultFocusedButton={'confirm'} - cancelButtonText={props.intl.formatMessage({ - id: 'xpack.spaces.management.confirmAlterActiveSpaceModal.cancelButton', - defaultMessage: 'Cancel', - })} - confirmButtonText={props.intl.formatMessage({ - id: 'xpack.spaces.management.confirmAlterActiveSpaceModal.updateSpaceButton', - defaultMessage: 'Update space', - })} - > -

    - -

    -
    -
    + + } + defaultFocusedButton={'confirm'} + cancelButtonText={props.intl.formatMessage({ + id: 'xpack.spaces.management.confirmAlterActiveSpaceModal.cancelButton', + defaultMessage: 'Cancel', + })} + confirmButtonText={props.intl.formatMessage({ + id: 'xpack.spaces.management.confirmAlterActiveSpaceModal.updateSpaceButton', + defaultMessage: 'Update space', + })} + > +

    + +

    +
    ); export const ConfirmAlterActiveSpaceModal = injectI18n(ConfirmAlterActiveSpaceModalUI); diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/switch_modal/switch_modal.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/switch_modal/switch_modal.tsx index b215b2af56c3a..2d2a5b1fcad93 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/switch_modal/switch_modal.tsx +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/switch_modal/switch_modal.tsx @@ -7,7 +7,7 @@ import React, { FC } from 'react'; import { i18n } from '@kbn/i18n'; -import { EuiConfirmModal, EuiOverlayMask } from '@elastic/eui'; +import { EuiConfirmModal } from '@elastic/eui'; interface Props { onCancel: () => void; @@ -59,19 +59,17 @@ const cancelButtonText = i18n.translate( ); export const SwitchModal: FC = ({ onCancel, onConfirm, type }) => ( - - -

    {type === 'pivot' ? pivotModalMessage : sourceModalMessage}

    -
    -
    + +

    {type === 'pivot' ? pivotModalMessage : sourceModalMessage}

    +
    ); diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_delete/delete_action_modal.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_delete/delete_action_modal.tsx index 148e6c1a3bac0..d82f0769c8b74 100644 --- a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_delete/delete_action_modal.tsx +++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_delete/delete_action_modal.tsx @@ -12,7 +12,6 @@ import { EuiConfirmModal, EuiFlexGroup, EuiFlexItem, - EuiOverlayMask, EuiSpacer, EuiSwitch, } from '@elastic/eui'; @@ -123,22 +122,20 @@ export const DeleteActionModal: FC = ({ ); return ( - - - {isBulkAction ? bulkDeleteModalContent : deleteModalContent} - - + + {isBulkAction ? bulkDeleteModalContent : deleteModalContent} + ); }; diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_start/start_action_modal.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_start/start_action_modal.tsx index c3967dd687a63..bb01fe355a33e 100644 --- a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_start/start_action_modal.tsx +++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_start/start_action_modal.tsx @@ -7,7 +7,7 @@ import React, { FC } from 'react'; import { i18n } from '@kbn/i18n'; -import { EuiConfirmModal, EuiOverlayMask, EUI_MODAL_CONFIRM_BUTTON } from '@elastic/eui'; +import { EuiConfirmModal, EUI_MODAL_CONFIRM_BUTTON } from '@elastic/eui'; import { StartAction } from './use_start_action'; @@ -24,27 +24,25 @@ export const StartActionModal: FC = ({ closeModal, items, startAndC }); return ( - - +

    + {i18n.translate('xpack.transform.transformList.startModalBody', { + defaultMessage: + 'A transform increases search and indexing load in your cluster. If excessive load is experienced, stop the transform.', })} - confirmButtonText={i18n.translate('xpack.transform.transformList.startModalStartButton', { - defaultMessage: 'Start', - })} - defaultFocusedButton={EUI_MODAL_CONFIRM_BUTTON} - buttonColor="primary" - > -

    - {i18n.translate('xpack.transform.transformList.startModalBody', { - defaultMessage: - 'A transform increases search and indexing load in your cluster. If excessive load is experienced, stop the transform.', - })} -

    -
    -
    +

    + ); }; diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/transform_management_section.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/transform_management_section.tsx index b84d7fc433dfc..bcb07c8069ab2 100644 --- a/x-pack/plugins/transform/public/app/sections/transform_management/transform_management_section.tsx +++ b/x-pack/plugins/transform/public/app/sections/transform_management/transform_management_section.tsx @@ -14,7 +14,6 @@ import { EuiFlexGroup, EuiFlexItem, EuiModal, - EuiOverlayMask, EuiPageContent, EuiPageContentBody, EuiSpacer, @@ -124,15 +123,13 @@ export const TransformManagement: FC = () => { {isSearchSelectionVisible && ( - - - - - + + + )} ); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/delete_modal_confirmation.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/delete_modal_confirmation.tsx index 952ea07ba05c3..b98db1178f462 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/delete_modal_confirmation.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/delete_modal_confirmation.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { EuiConfirmModal, EuiOverlayMask } from '@elastic/eui'; +import { EuiConfirmModal } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import React, { useEffect, useState } from 'react'; import { HttpSetup } from 'kibana/public'; @@ -73,56 +73,54 @@ export const DeleteModalConfirmation = ({ } ); return ( - - { - setDeleteModalVisibility(false); - onCancel(); - }} - onConfirm={async () => { - setDeleteModalVisibility(false); - setIsLoadingState(true); - const { successes, errors } = await apiDeleteCall({ ids: idsToDelete, http }); - setIsLoadingState(false); + { + setDeleteModalVisibility(false); + onCancel(); + }} + onConfirm={async () => { + setDeleteModalVisibility(false); + setIsLoadingState(true); + const { successes, errors } = await apiDeleteCall({ ids: idsToDelete, http }); + setIsLoadingState(false); - const numSuccesses = successes.length; - const numErrors = errors.length; - if (numSuccesses > 0) { - toasts.addSuccess( - i18n.translate( - 'xpack.triggersActionsUI.components.deleteSelectedIdsSuccessNotification.descriptionText', - { - defaultMessage: - 'Deleted {numSuccesses, number} {numSuccesses, plural, one {{singleTitle}} other {{multipleTitle}}}', - values: { numSuccesses, singleTitle, multipleTitle }, - } - ) - ); - } + const numSuccesses = successes.length; + const numErrors = errors.length; + if (numSuccesses > 0) { + toasts.addSuccess( + i18n.translate( + 'xpack.triggersActionsUI.components.deleteSelectedIdsSuccessNotification.descriptionText', + { + defaultMessage: + 'Deleted {numSuccesses, number} {numSuccesses, plural, one {{singleTitle}} other {{multipleTitle}}}', + values: { numSuccesses, singleTitle, multipleTitle }, + } + ) + ); + } - if (numErrors > 0) { - toasts.addDanger( - i18n.translate( - 'xpack.triggersActionsUI.components.deleteSelectedIdsErrorNotification.descriptionText', - { - defaultMessage: - 'Failed to delete {numErrors, number} {numErrors, plural, one {{singleTitle}} other {{multipleTitle}}}', - values: { numErrors, singleTitle, multipleTitle }, - } - ) - ); - await onErrors(); - } - await onDeleted(successes); - }} - cancelButtonText={cancelButtonText} - confirmButtonText={confirmButtonText} - > - {confirmModalText} - - + if (numErrors > 0) { + toasts.addDanger( + i18n.translate( + 'xpack.triggersActionsUI.components.deleteSelectedIdsErrorNotification.descriptionText', + { + defaultMessage: + 'Failed to delete {numErrors, number} {numErrors, plural, one {{singleTitle}} other {{multipleTitle}}}', + values: { numErrors, singleTitle, multipleTitle }, + } + ) + ); + await onErrors(); + } + await onDeleted(successes); + }} + cancelButtonText={cancelButtonText} + confirmButtonText={confirmButtonText} + > + {confirmModalText} + ); }; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_add_modal.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_add_modal.tsx index b7450d742bc45..8732727b9a77a 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_add_modal.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_add_modal.tsx @@ -7,17 +7,19 @@ import React, { useCallback, useMemo, useReducer, useState } from 'react'; import { FormattedMessage } from '@kbn/i18n/react'; -import { EuiTitle, EuiFlexItem, EuiIcon, EuiFlexGroup } from '@elastic/eui'; import { EuiModal, EuiButton, + EuiButtonEmpty, EuiModalHeader, EuiModalHeaderTitle, EuiModalBody, EuiModalFooter, + EuiTitle, + EuiFlexItem, + EuiIcon, + EuiFlexGroup, } from '@elastic/eui'; -import { EuiButtonEmpty } from '@elastic/eui'; -import { EuiOverlayMask } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { ActionConnectorForm, getConnectorErrors } from './action_connector_form'; import { createConnectorReducer, InitialConnector, ConnectorReducer } from './connector_reducer'; @@ -127,92 +129,90 @@ export const ConnectorAddModal = ({ }); return ( - - - - - - {actionTypeModel && actionTypeModel.iconClass ? ( - - - - ) : null} - - -

    - -

    -
    + + + + + {actionTypeModel && actionTypeModel.iconClass ? ( + + - - - + ) : null} + + +

    + +

    +
    +
    +
    +
    +
    - - - - - - {i18n.translate( - 'xpack.triggersActionsUI.sections.addModalConnectorForm.cancelButtonLabel', - { - defaultMessage: 'Cancel', + + + + + + {i18n.translate( + 'xpack.triggersActionsUI.sections.addModalConnectorForm.cancelButtonLabel', + { + defaultMessage: 'Cancel', + } + )} + + {canSave ? ( + { + if (hasErrors) { + setConnector( + getConnectorWithInvalidatedFields( + connector, + configErrors, + secretsErrors, + connectorBaseErrors + ) + ); + return; } - )} - - {canSave ? ( - { - if (hasErrors) { - setConnector( - getConnectorWithInvalidatedFields( - connector, - configErrors, - secretsErrors, - connectorBaseErrors - ) - ); - return; - } - setIsSaving(true); - const savedAction = await onActionConnectorSave(); - setIsSaving(false); - if (savedAction) { - if (postSaveEventHandler) { - postSaveEventHandler(savedAction); - } - closeModal(); + setIsSaving(true); + const savedAction = await onActionConnectorSave(); + setIsSaving(false); + if (savedAction) { + if (postSaveEventHandler) { + postSaveEventHandler(savedAction); } - }} - > - - - ) : null} - -
    -
    + closeModal(); + } + }} + > + + + ) : null} + + ); }; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/confirm_alert_close.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/confirm_alert_close.tsx index 9ef7e414d505e..6d71fe858f1c1 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/confirm_alert_close.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/confirm_alert_close.tsx @@ -6,7 +6,7 @@ */ import React from 'react'; -import { EuiOverlayMask, EuiConfirmModal } from '@elastic/eui'; +import { EuiConfirmModal } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; @@ -17,38 +17,36 @@ interface Props { export const ConfirmAlertClose: React.FC = ({ onConfirm, onCancel }) => { return ( - - -

    - -

    -
    -
    + +

    + +

    +
    ); }; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/confirm_alert_save.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/confirm_alert_save.tsx index 48d4229bb9b30..c406ec7c80283 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/confirm_alert_save.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/confirm_alert_save.tsx @@ -6,7 +6,7 @@ */ import React from 'react'; -import { EuiOverlayMask, EuiConfirmModal } from '@elastic/eui'; +import { EuiConfirmModal } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; @@ -17,38 +17,36 @@ interface Props { export const ConfirmAlertSave: React.FC = ({ onConfirm, onCancel }) => { return ( - - -

    - -

    -
    -
    + +

    + +

    +
    ); }; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_list/components/manage_license_modal.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_list/components/manage_license_modal.tsx index f13e5fd96d2ad..4a5739c8b4430 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_list/components/manage_license_modal.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_list/components/manage_license_modal.tsx @@ -8,7 +8,7 @@ import React from 'react'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; -import { EuiOverlayMask, EuiConfirmModal } from '@elastic/eui'; +import { EuiConfirmModal } from '@elastic/eui'; import { capitalize } from 'lodash'; interface Props { @@ -26,37 +26,35 @@ export const ManageLicenseModal: React.FC = ({ }) => { const licenseRequired = capitalize(licenseType); return ( - - -

    - -

    -
    -
    + +

    + +

    +
    ); }; diff --git a/x-pack/plugins/uptime/public/components/common/charts/__snapshots__/donut_chart.test.tsx.snap b/x-pack/plugins/uptime/public/components/common/charts/__snapshots__/donut_chart.test.tsx.snap index 31d4322210e2b..b689ca7ff56f0 100644 --- a/x-pack/plugins/uptime/public/components/common/charts/__snapshots__/donut_chart.test.tsx.snap +++ b/x-pack/plugins/uptime/public/components/common/charts/__snapshots__/donut_chart.test.tsx.snap @@ -342,6 +342,14 @@ exports[`DonutChart component passes correct props without errors for valid prop "band": Object { "fill": "rgba(245, 247, 250, 1)", }, + "crossLine": Object { + "dash": Array [ + 4, + 4, + ], + "stroke": "rgba(105, 112, 125, 1)", + "strokeWidth": 1, + }, "line": Object { "dash": Array [ 4, diff --git a/x-pack/plugins/uptime/public/components/monitor/ml/__snapshots__/confirm_delete.test.tsx.snap b/x-pack/plugins/uptime/public/components/monitor/ml/__snapshots__/confirm_delete.test.tsx.snap index d83e45fea1aec..9d670158bc53a 100644 --- a/x-pack/plugins/uptime/public/components/monitor/ml/__snapshots__/confirm_delete.test.tsx.snap +++ b/x-pack/plugins/uptime/public/components/monitor/ml/__snapshots__/confirm_delete.test.tsx.snap @@ -1,58 +1,54 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`ML Confirm Job Delete shallow renders without errors 1`] = ` - - -

    - -

    -

    - -

    -
    -
    + +

    + +

    +

    + +

    +
    `; exports[`ML Confirm Job Delete shallow renders without errors while loading 1`] = ` - - -

    - - ) -

    - +

    + - - + ) +

    + +
    `; diff --git a/x-pack/plugins/uptime/public/components/monitor/ml/__snapshots__/ml_flyout.test.tsx.snap b/x-pack/plugins/uptime/public/components/monitor/ml/__snapshots__/ml_flyout.test.tsx.snap index fd59b14520ce1..23feec1e5181c 100644 --- a/x-pack/plugins/uptime/public/components/monitor/ml/__snapshots__/ml_flyout.test.tsx.snap +++ b/x-pack/plugins/uptime/public/components/monitor/ml/__snapshots__/ml_flyout.test.tsx.snap @@ -84,7 +84,7 @@ exports[`ML Flyout component shows license info if no ml available 1`] = ` data-eui="EuiFocusTrap" >