From 3be456009223cdbc9efa8ac7cec19d394d569633 Mon Sep 17 00:00:00 2001 From: Liza Katz Date: Mon, 9 Mar 2020 08:53:51 +0000 Subject: [PATCH 01/20] explicit exports (#59620) --- .../lib/get_indices.test.ts | 3 +- src/plugins/data/public/index.ts | 35 +++++++++++++++++-- src/plugins/data/public/search/fetch/types.ts | 2 +- 3 files changed, 36 insertions(+), 4 deletions(-) diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/create_index_pattern_wizard/lib/get_indices.test.ts b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/create_index_pattern_wizard/lib/get_indices.test.ts index cd7c8278adcc7c..5a8460fcb51bae 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/create_index_pattern_wizard/lib/get_indices.test.ts +++ b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/create_index_pattern_wizard/lib/get_indices.test.ts @@ -19,7 +19,8 @@ import { getIndices } from './get_indices'; import { IndexPatternCreationConfig } from './../../../../../../../management/public'; -import { LegacyApiCaller } from '../../../../../../../../../plugins/data/public'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { LegacyApiCaller } from '../../../../../../../../../plugins/data/public/search'; export const successfulResponse = { hits: { diff --git a/src/plugins/data/public/index.ts b/src/plugins/data/public/index.ts index a5f4ce2ce3c581..86cc0cca85e0b1 100644 --- a/src/plugins/data/public/index.ts +++ b/src/plugins/data/public/index.ts @@ -283,8 +283,39 @@ export { * Search: */ -export { IRequestTypesMap, IResponseTypesMap } from './search'; -export * from './search'; +export { + ES_SEARCH_STRATEGY, + SYNC_SEARCH_STRATEGY, + defaultSearchStrategy, + esSearchStrategyProvider, + getEsPreference, + addSearchStrategy, + hasSearchStategyForIndexPattern, + getSearchErrorType, + ISearchContext, + TSearchStrategyProvider, + ISearchStrategy, + ISearch, + ISearchOptions, + IRequestTypesMap, + IResponseTypesMap, + ISearchGeneric, + IEsSearchResponse, + IEsSearchRequest, + ISyncSearchRequest, + IKibanaSearchResponse, + IKibanaSearchRequest, + SearchRequest, + SearchResponse, + SearchError, + SearchStrategyProvider, + ISearchSource, + SearchSource, + SearchSourceFields, + EsQuerySortValue, + SortDirection, + FetchOptions, +} from './search'; /* * UI components diff --git a/src/plugins/data/public/search/fetch/types.ts b/src/plugins/data/public/search/fetch/types.ts index 62eb965703c3a4..e8de0576b8a72f 100644 --- a/src/plugins/data/public/search/fetch/types.ts +++ b/src/plugins/data/public/search/fetch/types.ts @@ -17,8 +17,8 @@ * under the License. */ -import { ISearchStart } from 'src/plugins/data/public'; import { IUiSettingsClient } from '../../../../../core/public'; +import { ISearchStart } from '../types'; export interface FetchOptions { abortSignal?: AbortSignal; From 1f8da9dd7726596fd80658d48cbdf2b89e7c7855 Mon Sep 17 00:00:00 2001 From: Pierre Gayvallet Date: Mon, 9 Mar 2020 11:03:46 +0100 Subject: [PATCH 02/20] Reset the metrics after each emission (#59551) * reset the metrics after each emission * add test comment --- src/core/server/metrics/collectors/mocks.ts | 35 +++++++++ src/core/server/metrics/collectors/os.ts | 2 + src/core/server/metrics/collectors/process.ts | 2 + src/core/server/metrics/collectors/server.ts | 17 ++++- src/core/server/metrics/collectors/types.ts | 3 + .../server_collector.test.ts | 76 +++++++++++++++++++ .../metrics/metrics_service.test.mocks.ts | 7 +- .../server/metrics/metrics_service.test.ts | 39 ++++++---- src/core/server/metrics/metrics_service.ts | 9 ++- .../ops_metrics_collector.test.mocks.ts | 14 ++-- .../metrics/ops_metrics_collector.test.ts | 48 ++++++++---- .../server/metrics/ops_metrics_collector.ts | 6 ++ 12 files changed, 212 insertions(+), 46 deletions(-) create mode 100644 src/core/server/metrics/collectors/mocks.ts diff --git a/src/core/server/metrics/collectors/mocks.ts b/src/core/server/metrics/collectors/mocks.ts new file mode 100644 index 00000000000000..d1eb15637779af --- /dev/null +++ b/src/core/server/metrics/collectors/mocks.ts @@ -0,0 +1,35 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { MetricsCollector } from './types'; + +const createMock = () => { + const mocked: jest.Mocked> = { + collect: jest.fn(), + reset: jest.fn(), + }; + + mocked.collect.mockResolvedValue({}); + + return mocked; +}; + +export const collectorMock = { + create: createMock, +}; diff --git a/src/core/server/metrics/collectors/os.ts b/src/core/server/metrics/collectors/os.ts index d3d9bb0be86fa2..59bef9d8ddd2b1 100644 --- a/src/core/server/metrics/collectors/os.ts +++ b/src/core/server/metrics/collectors/os.ts @@ -57,4 +57,6 @@ export class OsMetricsCollector implements MetricsCollector { return metrics; } + + public reset() {} } diff --git a/src/core/server/metrics/collectors/process.ts b/src/core/server/metrics/collectors/process.ts index aa68abaf74e41a..a3b59a7cc8b7c2 100644 --- a/src/core/server/metrics/collectors/process.ts +++ b/src/core/server/metrics/collectors/process.ts @@ -40,6 +40,8 @@ export class ProcessMetricsCollector implements MetricsCollector => { diff --git a/src/core/server/metrics/collectors/server.ts b/src/core/server/metrics/collectors/server.ts index e46ac2f653df66..84204d0466ff34 100644 --- a/src/core/server/metrics/collectors/server.ts +++ b/src/core/server/metrics/collectors/server.ts @@ -26,12 +26,12 @@ interface ServerResponseTime { } export class ServerMetricsCollector implements MetricsCollector { - private readonly requests: OpsServerMetrics['requests'] = { + private requests: OpsServerMetrics['requests'] = { disconnects: 0, total: 0, statusCodes: {}, }; - private readonly responseTimes: ServerResponseTime = { + private responseTimes: ServerResponseTime = { count: 0, total: 0, max: 0, @@ -77,4 +77,17 @@ export class ServerMetricsCollector implements MetricsCollector { + /** collect the data currently gathered by the collector */ collect(): Promise; + /** reset the internal state of the collector */ + reset(): void; } /** diff --git a/src/core/server/metrics/integration_tests/server_collector.test.ts b/src/core/server/metrics/integration_tests/server_collector.test.ts index 6baf95894b9b42..dd5c256cf1600d 100644 --- a/src/core/server/metrics/integration_tests/server_collector.test.ts +++ b/src/core/server/metrics/integration_tests/server_collector.test.ts @@ -200,4 +200,80 @@ describe('ServerMetricsCollector', () => { metrics = await collector.collect(); expect(metrics.concurrent_connections).toEqual(0); }); + + describe('#reset', () => { + it('reset the requests state', async () => { + router.get({ path: '/', validate: false }, async (ctx, req, res) => { + return res.ok({ body: '' }); + }); + await server.start(); + + await sendGet('/'); + await sendGet('/'); + await sendGet('/not-found'); + + let metrics = await collector.collect(); + + expect(metrics.requests).toEqual({ + total: 3, + disconnects: 0, + statusCodes: { + '200': 2, + '404': 1, + }, + }); + + collector.reset(); + metrics = await collector.collect(); + + expect(metrics.requests).toEqual({ + total: 0, + disconnects: 0, + statusCodes: {}, + }); + + await sendGet('/'); + await sendGet('/not-found'); + + metrics = await collector.collect(); + + expect(metrics.requests).toEqual({ + total: 2, + disconnects: 0, + statusCodes: { + '200': 1, + '404': 1, + }, + }); + }); + + it('resets the response times', async () => { + router.get({ path: '/no-delay', validate: false }, async (ctx, req, res) => { + return res.ok({ body: '' }); + }); + router.get({ path: '/500-ms', validate: false }, async (ctx, req, res) => { + await delay(500); + return res.ok({ body: '' }); + }); + + await server.start(); + + await Promise.all([sendGet('/no-delay'), sendGet('/500-ms')]); + let metrics = await collector.collect(); + + expect(metrics.response_times.avg_in_millis).toBeGreaterThanOrEqual(250); + expect(metrics.response_times.max_in_millis).toBeGreaterThanOrEqual(500); + + collector.reset(); + metrics = await collector.collect(); + expect(metrics.response_times.avg_in_millis).toBe(0); + expect(metrics.response_times.max_in_millis).toBeGreaterThanOrEqual(0); + + await Promise.all([sendGet('/500-ms'), sendGet('/500-ms')]); + metrics = await collector.collect(); + + expect(metrics.response_times.avg_in_millis).toBeGreaterThanOrEqual(500); + expect(metrics.response_times.max_in_millis).toBeGreaterThanOrEqual(500); + }); + }); }); diff --git a/src/core/server/metrics/metrics_service.test.mocks.ts b/src/core/server/metrics/metrics_service.test.mocks.ts index 8e91775283042b..fe46e5693bf458 100644 --- a/src/core/server/metrics/metrics_service.test.mocks.ts +++ b/src/core/server/metrics/metrics_service.test.mocks.ts @@ -17,9 +17,10 @@ * under the License. */ -export const mockOpsCollector = { - collect: jest.fn(), -}; +import { collectorMock } from './collectors/mocks'; + +export const mockOpsCollector = collectorMock.create(); + jest.doMock('./ops_metrics_collector', () => ({ OpsMetricsCollector: jest.fn().mockImplementation(() => mockOpsCollector), })); diff --git a/src/core/server/metrics/metrics_service.test.ts b/src/core/server/metrics/metrics_service.test.ts index 10d6761adbe7d5..f6334cc5d3c0f7 100644 --- a/src/core/server/metrics/metrics_service.test.ts +++ b/src/core/server/metrics/metrics_service.test.ts @@ -57,37 +57,50 @@ describe('MetricsService', () => { expect(setInterval).toHaveBeenCalledWith(expect.any(Function), testInterval); }); - it('emits the metrics at start', async () => { + it('collects the metrics at every interval', async () => { mockOpsCollector.collect.mockResolvedValue(dummyMetrics); - const { getOpsMetrics$ } = await metricsService.setup({ - http: httpMock, - }); - + await metricsService.setup({ http: httpMock }); await metricsService.start(); expect(mockOpsCollector.collect).toHaveBeenCalledTimes(1); - expect( - await getOpsMetrics$() - .pipe(take(1)) - .toPromise() - ).toEqual(dummyMetrics); + + jest.advanceTimersByTime(testInterval); + expect(mockOpsCollector.collect).toHaveBeenCalledTimes(2); + + jest.advanceTimersByTime(testInterval); + expect(mockOpsCollector.collect).toHaveBeenCalledTimes(3); }); - it('collects the metrics at every interval', async () => { + it('resets the collector after each collection', async () => { mockOpsCollector.collect.mockResolvedValue(dummyMetrics); - await metricsService.setup({ http: httpMock }); - + const { getOpsMetrics$ } = await metricsService.setup({ http: httpMock }); await metricsService.start(); + // `advanceTimersByTime` only ensure the interval handler is executed + // however the `reset` call is executed after the async call to `collect` + // meaning that we are going to miss the call if we don't wait for the + // actual observable emission that is performed after + const waitForNextEmission = () => + getOpsMetrics$() + .pipe(take(1)) + .toPromise(); + expect(mockOpsCollector.collect).toHaveBeenCalledTimes(1); + expect(mockOpsCollector.reset).toHaveBeenCalledTimes(1); + let nextEmission = waitForNextEmission(); jest.advanceTimersByTime(testInterval); + await nextEmission; expect(mockOpsCollector.collect).toHaveBeenCalledTimes(2); + expect(mockOpsCollector.reset).toHaveBeenCalledTimes(2); + nextEmission = waitForNextEmission(); jest.advanceTimersByTime(testInterval); + await nextEmission; expect(mockOpsCollector.collect).toHaveBeenCalledTimes(3); + expect(mockOpsCollector.reset).toHaveBeenCalledTimes(3); }); it('throws when called before setup', async () => { diff --git a/src/core/server/metrics/metrics_service.ts b/src/core/server/metrics/metrics_service.ts index 1aed89a4aad600..0ea9d007926003 100644 --- a/src/core/server/metrics/metrics_service.ts +++ b/src/core/server/metrics/metrics_service.ts @@ -17,8 +17,8 @@ * under the License. */ -import { ReplaySubject } from 'rxjs'; -import { first, shareReplay } from 'rxjs/operators'; +import { Subject } from 'rxjs'; +import { first } from 'rxjs/operators'; import { CoreService } from '../../types'; import { CoreContext } from '../core_context'; import { Logger } from '../logging'; @@ -37,7 +37,7 @@ export class MetricsService private readonly logger: Logger; private metricsCollector?: OpsMetricsCollector; private collectInterval?: NodeJS.Timeout; - private metrics$ = new ReplaySubject(1); + private metrics$ = new Subject(); constructor(private readonly coreContext: CoreContext) { this.logger = coreContext.logger.get('metrics'); @@ -46,7 +46,7 @@ export class MetricsService public async setup({ http }: MetricsServiceSetupDeps): Promise { this.metricsCollector = new OpsMetricsCollector(http.server); - const metricsObservable = this.metrics$.pipe(shareReplay(1)); + const metricsObservable = this.metrics$.asObservable(); return { getOpsMetrics$: () => metricsObservable, @@ -74,6 +74,7 @@ export class MetricsService private async refreshMetrics() { this.logger.debug('Refreshing metrics'); const metrics = await this.metricsCollector!.collect(); + this.metricsCollector!.reset(); this.metrics$.next(metrics); } diff --git a/src/core/server/metrics/ops_metrics_collector.test.mocks.ts b/src/core/server/metrics/ops_metrics_collector.test.mocks.ts index 8265796d57970e..cf51f8a753729f 100644 --- a/src/core/server/metrics/ops_metrics_collector.test.mocks.ts +++ b/src/core/server/metrics/ops_metrics_collector.test.mocks.ts @@ -17,23 +17,19 @@ * under the License. */ -export const mockOsCollector = { - collect: jest.fn(), -}; +import { collectorMock } from './collectors/mocks'; + +export const mockOsCollector = collectorMock.create(); jest.doMock('./collectors/os', () => ({ OsMetricsCollector: jest.fn().mockImplementation(() => mockOsCollector), })); -export const mockProcessCollector = { - collect: jest.fn(), -}; +export const mockProcessCollector = collectorMock.create(); jest.doMock('./collectors/process', () => ({ ProcessMetricsCollector: jest.fn().mockImplementation(() => mockProcessCollector), })); -export const mockServerCollector = { - collect: jest.fn(), -}; +export const mockServerCollector = collectorMock.create(); jest.doMock('./collectors/server', () => ({ ServerMetricsCollector: jest.fn().mockImplementation(() => mockServerCollector), })); diff --git a/src/core/server/metrics/ops_metrics_collector.test.ts b/src/core/server/metrics/ops_metrics_collector.test.ts index 04302a195fb6cd..559588db60a425 100644 --- a/src/core/server/metrics/ops_metrics_collector.test.ts +++ b/src/core/server/metrics/ops_metrics_collector.test.ts @@ -35,25 +35,43 @@ describe('OpsMetricsCollector', () => { mockOsCollector.collect.mockResolvedValue('osMetrics'); }); - it('gathers metrics from the underlying collectors', async () => { - mockOsCollector.collect.mockResolvedValue('osMetrics'); - mockProcessCollector.collect.mockResolvedValue('processMetrics'); - mockServerCollector.collect.mockResolvedValue({ - requests: 'serverRequestsMetrics', - response_times: 'serverTimingMetrics', + describe('#collect', () => { + it('gathers metrics from the underlying collectors', async () => { + mockOsCollector.collect.mockResolvedValue('osMetrics'); + mockProcessCollector.collect.mockResolvedValue('processMetrics'); + mockServerCollector.collect.mockResolvedValue({ + requests: 'serverRequestsMetrics', + response_times: 'serverTimingMetrics', + }); + + const metrics = await collector.collect(); + + expect(mockOsCollector.collect).toHaveBeenCalledTimes(1); + expect(mockProcessCollector.collect).toHaveBeenCalledTimes(1); + expect(mockServerCollector.collect).toHaveBeenCalledTimes(1); + + expect(metrics).toEqual({ + process: 'processMetrics', + os: 'osMetrics', + requests: 'serverRequestsMetrics', + response_times: 'serverTimingMetrics', + }); }); + }); + + describe('#reset', () => { + it('call reset on the underlying collectors', () => { + collector.reset(); - const metrics = await collector.collect(); + expect(mockOsCollector.reset).toHaveBeenCalledTimes(1); + expect(mockProcessCollector.reset).toHaveBeenCalledTimes(1); + expect(mockServerCollector.reset).toHaveBeenCalledTimes(1); - expect(mockOsCollector.collect).toHaveBeenCalledTimes(1); - expect(mockProcessCollector.collect).toHaveBeenCalledTimes(1); - expect(mockServerCollector.collect).toHaveBeenCalledTimes(1); + collector.reset(); - expect(metrics).toEqual({ - process: 'processMetrics', - os: 'osMetrics', - requests: 'serverRequestsMetrics', - response_times: 'serverTimingMetrics', + expect(mockOsCollector.reset).toHaveBeenCalledTimes(2); + expect(mockProcessCollector.reset).toHaveBeenCalledTimes(2); + expect(mockServerCollector.reset).toHaveBeenCalledTimes(2); }); }); }); diff --git a/src/core/server/metrics/ops_metrics_collector.ts b/src/core/server/metrics/ops_metrics_collector.ts index 04344f21f57f72..525515dba14577 100644 --- a/src/core/server/metrics/ops_metrics_collector.ts +++ b/src/core/server/metrics/ops_metrics_collector.ts @@ -49,4 +49,10 @@ export class OpsMetricsCollector implements MetricsCollector { ...server, }; } + + public reset() { + this.processCollector.reset(); + this.osCollector.reset(); + this.serverCollector.reset(); + } } From 1d0697a1cca33048f61d35422562a9b089c88bf8 Mon Sep 17 00:00:00 2001 From: Daniil Suleiman <31325372+sulemanof@users.noreply.github.com> Date: Mon, 9 Mar 2020 13:45:25 +0300 Subject: [PATCH 03/20] [Visualize] Remove global state in visualize (#58352) * Remove global state in visualize * Fix saved query * Update saved query handling * Resolve merge conflicts * Use new state syncing helpers * Fix state behavior * Prevent loosing the global state * Update state syncing with url Co-authored-by: Elastic Machine --- .../kibana/public/visualize/legacy_imports.ts | 9 -- .../public/visualize/np_ready/application.ts | 34 +---- .../visualize/np_ready/editor/editor.html | 4 +- .../visualize/np_ready/editor/editor.js | 125 ++++++++++-------- .../editor/lib/visualize_app_state.ts | 17 ++- .../visualize/np_ready/global_state_sync.ts | 67 ---------- .../public/visualize/np_ready/legacy_app.js | 27 ++-- .../np_ready/listing/visualize_listing.js | 22 ++- .../public/visualize/np_ready/types.d.ts | 9 +- .../query/state_sync/sync_state_with_url.ts | 5 +- 10 files changed, 118 insertions(+), 201 deletions(-) delete mode 100644 src/legacy/core_plugins/kibana/public/visualize/np_ready/global_state_sync.ts diff --git a/src/legacy/core_plugins/kibana/public/visualize/legacy_imports.ts b/src/legacy/core_plugins/kibana/public/visualize/legacy_imports.ts index b8ee7cd378750c..66a7bd6f333737 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/legacy_imports.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/legacy_imports.ts @@ -24,17 +24,8 @@ * directly where they are needed. */ -export { State } from 'ui/state_management/state'; -// @ts-ignore -export { GlobalStateProvider } from 'ui/state_management/global_state'; -// @ts-ignore -export { StateManagementConfigProvider } from 'ui/state_management/config_provider'; - export { subscribeWithScope } from 'ui/utils/subscribe_with_scope'; // @ts-ignore -export { EventsProvider } from 'ui/events'; -export { registerTimefilterWithGlobalStateFactory } from 'ui/timefilter/setup_router'; -// @ts-ignore export { KbnUrlProvider, RedirectWhenMissingProvider } from 'ui/url'; export { absoluteToParsedUrl } from 'ui/url/absolute_to_parsed_url'; export { KibanaParsedUrl } from 'ui/url/kibana_parsed_url'; diff --git a/src/legacy/core_plugins/kibana/public/visualize/np_ready/application.ts b/src/legacy/core_plugins/kibana/public/visualize/np_ready/application.ts index b15d89275eba79..8ef63ec5778e2d 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/np_ready/application.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/np_ready/application.ts @@ -23,13 +23,11 @@ import { i18nDirective, i18nFilter, I18nProvider } from '@kbn/i18n/angular'; import { AppMountContext } from 'kibana/public'; import { configureAppAngularModule, - GlobalStateProvider, KbnUrlProvider, RedirectWhenMissingProvider, IPrivate, PrivateProvider, PromiseServiceCreator, - StateManagementConfigProvider, } from '../legacy_imports'; import { NavigationPublicPluginStart as NavigationStart } from '../../../../../../plugins/navigation/public'; import { @@ -87,35 +85,20 @@ function createLocalAngularModule(core: AppMountContext['core'], navigation: Nav createLocalI18nModule(); createLocalPrivateModule(); createLocalPromiseModule(); - createLocalConfigModule(core); createLocalKbnUrlModule(); - createLocalStateModule(); createLocalTopNavModule(navigation); const visualizeAngularModule: IModule = angular.module(moduleName, [ ...thirdPartyAngularDependencies, - 'app/visualize/Config', 'app/visualize/I18n', 'app/visualize/Private', 'app/visualize/TopNav', - 'app/visualize/State', + 'app/visualize/KbnUrl', + 'app/visualize/Promise', ]); return visualizeAngularModule; } -function createLocalStateModule() { - angular - .module('app/visualize/State', [ - 'app/visualize/Private', - 'app/visualize/Config', - 'app/visualize/KbnUrl', - 'app/visualize/Promise', - ]) - .service('globalState', function(Private: IPrivate) { - return Private(GlobalStateProvider); - }); -} - function createLocalKbnUrlModule() { angular .module('app/visualize/KbnUrl', ['app/visualize/Private', 'ngRoute']) @@ -123,19 +106,6 @@ function createLocalKbnUrlModule() { .service('redirectWhenMissing', (Private: IPrivate) => Private(RedirectWhenMissingProvider)); } -function createLocalConfigModule(core: AppMountContext['core']) { - angular - .module('app/visualize/Config', ['app/visualize/Private']) - .provider('stateManagementConfig', StateManagementConfigProvider) - .provider('config', () => { - return { - $get: () => ({ - get: core.uiSettings.get.bind(core.uiSettings), - }), - }; - }); -} - function createLocalPromiseModule() { angular.module('app/visualize/Promise', []).service('Promise', PromiseServiceCreator); } diff --git a/src/legacy/core_plugins/kibana/public/visualize/np_ready/editor/editor.html b/src/legacy/core_plugins/kibana/public/visualize/np_ready/editor/editor.html index 9dbb05ea95b487..28baf21925cbea 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/np_ready/editor/editor.html +++ b/src/legacy/core_plugins/kibana/public/visualize/np_ready/editor/editor.html @@ -31,8 +31,8 @@ refresh-interval="refreshInterval.value" on-refresh-change="onRefreshChange" show-save-query="showSaveQuery" - on-saved="onQuerySaved" - on-saved-query-updated="onSavedQueryUpdated" + on-saved="updateSavedQuery" + on-saved-query-updated="updateSavedQuery" on-clear-saved-query="onClearSavedQuery" > diff --git a/src/legacy/core_plugins/kibana/public/visualize/np_ready/editor/editor.js b/src/legacy/core_plugins/kibana/public/visualize/np_ready/editor/editor.js index 2d2552b5e2f30c..e1a20e33813310 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/np_ready/editor/editor.js +++ b/src/legacy/core_plugins/kibana/public/visualize/np_ready/editor/editor.js @@ -20,6 +20,7 @@ import angular from 'angular'; import _ from 'lodash'; import { Subscription } from 'rxjs'; +import { map } from 'rxjs/operators'; import { i18n } from '@kbn/i18n'; import React from 'react'; @@ -29,13 +30,17 @@ import { VisualizeConstants } from '../visualize_constants'; import { getEditBreadcrumbs } from '../breadcrumbs'; import { addHelpMenuToAppChrome } from '../help_menu/help_menu_util'; -import { FilterStateManager } from '../../../../../data/public'; import { unhashUrl } from '../../../../../../../plugins/kibana_utils/public'; import { kbnBaseUrl } from '../../../../../../../plugins/kibana_legacy/public'; import { SavedObjectSaveModal, showSaveModal, } from '../../../../../../../plugins/saved_objects/public'; +import { + esFilters, + connectToQueryState, + syncQueryStateWithUrl, +} from '../../../../../../../plugins/data/public'; import { initVisEditorDirective } from './visualization_editor'; import { initVisualizationDirective } from './visualization'; @@ -65,28 +70,21 @@ export function initEditorDirective(app, deps) { function VisualizeAppController( $scope, - $element, $route, $window, $injector, $timeout, kbnUrl, redirectWhenMissing, - Promise, - globalState, - config + kbnUrlStateStorage, + history ) { const { indexPatterns, localStorage, visualizeCapabilities, share, - data: { - query: { - filterManager, - timefilter: { timefilter }, - }, - }, + data: { query: queryService }, toastNotifications, chrome, getBasePath, @@ -97,6 +95,17 @@ function VisualizeAppController( setActiveUrl, } = getServices(); + const { + filterManager, + timefilter: { timefilter }, + } = queryService; + + // starts syncing `_g` portion of url with query services + const { stop: stopSyncingQueryServiceStateWithUrl } = syncQueryStateWithUrl( + queryService, + kbnUrlStateStorage + ); + // Retrieve the resolved SavedVis instance. const savedVis = $route.current.locals.savedVis; const _applyVis = () => { @@ -284,26 +293,24 @@ function VisualizeAppController( linked: !!savedVis.savedSearchId, }; - const useHash = config.get('state:storeInSessionStorage'); const { stateContainer, stopStateSync } = useVisualizeAppState({ - useHash, stateDefaults, + kbnUrlStateStorage, }); - const filterStateManager = new FilterStateManager( - globalState, - () => { - // Temporary AppState replacement - return { - set filters(_filters) { - stateContainer.transitions.set('filters', _filters); - }, - get filters() { - return stateContainer.getState().filters; - }, - }; + // sync initial app filters from state to filterManager + filterManager.setAppFilters(_.cloneDeep(stateContainer.getState().filters)); + // setup syncing of app filters between appState and filterManager + const stopSyncingAppFilters = connectToQueryState( + queryService, + { + set: ({ filters }) => stateContainer.transitions.set('filters', filters), + get: () => ({ filters: stateContainer.getState().filters }), + state$: stateContainer.state$.pipe(map(state => ({ filters: state.filters }))), }, - filterManager + { + filters: esFilters.FilterStateStore.APP_STATE, + } ); // The savedVis is pulled from elasticsearch, but the appState is pulled from the url, with the @@ -335,6 +342,24 @@ function VisualizeAppController( } ); + const updateSavedQueryFromUrl = savedQueryId => { + if (!savedQueryId) { + delete $scope.savedQuery; + + return; + } + + if ($scope.savedQuery && $scope.savedQuery.id === savedQueryId) { + return; + } + + savedQueryService.getSavedQuery(savedQueryId).then(savedQuery => { + $scope.$evalAsync(() => { + $scope.updateSavedQuery(savedQuery); + }); + }); + }; + function init() { if (vis.indexPattern) { $scope.indexPattern = vis.indexPattern; @@ -388,7 +413,6 @@ function VisualizeAppController( }; $scope.timeRange = timefilter.getTime(); - $scope.opts = _.pick($scope, 'savedVis', 'isAddToDashMode'); const unsubscribeStateUpdates = stateContainer.subscribe(state => { const newQuery = migrateLegacyQuery(state.query); @@ -396,6 +420,7 @@ function VisualizeAppController( stateContainer.transitions.set('query', newQuery); } persistOnChange(state); + updateSavedQueryFromUrl(state.savedQuery); // if the browser history was changed manually we need to reflect changes in the editor if (!_.isEqual(vis.getState(), state.vis)) { @@ -413,6 +438,9 @@ function VisualizeAppController( $scope.$broadcast('render'); }; + // update the query if savedQuery is stored + updateSavedQueryFromUrl(initialState.savedQuery); + const subscriptions = new Subscription(); subscriptions.add( @@ -438,7 +466,7 @@ function VisualizeAppController( // update the searchSource when query updates $scope.fetch = function() { - const { query, filters, linked } = stateContainer.getState(); + const { query, linked, filters } = stateContainer.getState(); $scope.query = query; $scope.linked = linked; savedVis.searchSource.setField('query', query); @@ -451,7 +479,6 @@ function VisualizeAppController( subscribeWithScope($scope, filterManager.getUpdates$(), { next: () => { $scope.filters = filterManager.getFilters(); - $scope.globalFilters = filterManager.getGlobalFilters(); }, }) ); @@ -466,13 +493,14 @@ function VisualizeAppController( $scope._handler.destroy(); } savedVis.destroy(); - filterStateManager.destroy(); subscriptions.unsubscribe(); $scope.vis.off('apply', _applyVis); unsubscribePersisted(); unsubscribeStateUpdates(); stopStateSync(); + stopSyncingQueryServiceStateWithUrl(); + stopSyncingAppFilters(); }); $timeout(() => { @@ -501,23 +529,14 @@ function VisualizeAppController( }); }; - $scope.onQuerySaved = savedQuery => { - $scope.savedQuery = savedQuery; - }; - - $scope.onSavedQueryUpdated = savedQuery => { - $scope.savedQuery = { ...savedQuery }; - }; - $scope.onClearSavedQuery = () => { delete $scope.savedQuery; stateContainer.transitions.removeSavedQuery(defaultQuery); filterManager.setFilters(filterManager.getGlobalFilters()); - $scope.fetch(); }; const updateStateFromSavedQuery = savedQuery => { - stateContainer.transitions.set('query', savedQuery.attributes.query); + stateContainer.transitions.updateFromSavedQuery(savedQuery); const savedQueryFilters = savedQuery.attributes.filters || []; const globalFilters = filterManager.getGlobalFilters(); @@ -532,25 +551,12 @@ function VisualizeAppController( timefilter.setRefreshInterval(savedQuery.attributes.timefilter.refreshInterval); } } - - $scope.fetch(); }; - // update the query if savedQuery is stored - if (stateContainer.getState().savedQuery) { - savedQueryService.getSavedQuery(stateContainer.getState().savedQuery).then(savedQuery => { - $scope.$evalAsync(() => { - $scope.savedQuery = savedQuery; - }); - }); - } - - $scope.$watch('savedQuery', newSavedQuery => { - if (!newSavedQuery) return; - stateContainer.transitions.set('savedQuery', newSavedQuery.id); - - updateStateFromSavedQuery(newSavedQuery); - }); + $scope.updateSavedQuery = savedQuery => { + $scope.savedQuery = savedQuery; + updateStateFromSavedQuery(savedQuery); + }; $scope.$watch('linked', linked => { if (linked && !savedVis.savedSearchId) { @@ -626,7 +632,10 @@ function VisualizeAppController( savedVis.vis.title = savedVis.title; savedVis.vis.description = savedVis.description; } else { - kbnUrl.change(`${VisualizeConstants.EDIT_PATH}/{{id}}`, { id: savedVis.id }); + history.replace({ + ...history.location, + pathname: `${VisualizeConstants.EDIT_PATH}/${savedVis.id}`, + }); } } }); diff --git a/src/legacy/core_plugins/kibana/public/visualize/np_ready/editor/lib/visualize_app_state.ts b/src/legacy/core_plugins/kibana/public/visualize/np_ready/editor/lib/visualize_app_state.ts index d8de81193d857f..d3fae3d457b632 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/np_ready/editor/lib/visualize_app_state.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/np_ready/editor/lib/visualize_app_state.ts @@ -17,21 +17,20 @@ * under the License. */ -import { createHashHistory } from 'history'; import { isFunction, omit } from 'lodash'; import { migrateAppState } from './migrate_app_state'; import { - createKbnUrlStateStorage, createStateContainer, syncState, + IKbnUrlStateStorage, } from '../../../../../../../../plugins/kibana_utils/public'; import { PureVisState, VisualizeAppState, VisualizeAppStateTransitions } from '../../types'; const STATE_STORAGE_KEY = '_a'; interface Arguments { - useHash: boolean; + kbnUrlStateStorage: IKbnUrlStateStorage; stateDefaults: VisualizeAppState; } @@ -41,12 +40,7 @@ function toObject(state: PureVisState): PureVisState { }); } -export function useVisualizeAppState({ useHash, stateDefaults }: Arguments) { - const history = createHashHistory(); - const kbnUrlStateStorage = createKbnUrlStateStorage({ - useHash, - history, - }); +export function useVisualizeAppState({ stateDefaults, kbnUrlStateStorage }: Arguments) { const urlState = kbnUrlStateStorage.get(STATE_STORAGE_KEY); const initialState = migrateAppState({ ...stateDefaults, @@ -88,6 +82,11 @@ export function useVisualizeAppState({ useHash, stateDefaults }: Arguments) { linked: false, }), updateVisState: state => newVisState => ({ ...state, vis: toObject(newVisState) }), + updateFromSavedQuery: state => savedQuery => ({ + ...state, + savedQuery: savedQuery.id, + query: savedQuery.attributes.query, + }), } ); diff --git a/src/legacy/core_plugins/kibana/public/visualize/np_ready/global_state_sync.ts b/src/legacy/core_plugins/kibana/public/visualize/np_ready/global_state_sync.ts deleted file mode 100644 index f29fb72a9fbc5a..00000000000000 --- a/src/legacy/core_plugins/kibana/public/visualize/np_ready/global_state_sync.ts +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { State } from '../legacy_imports'; -import { DataPublicPluginStart as DataStart } from '../../../../../../plugins/data/public'; - -/** - * Helper function to sync the global state with the various state providers - * when a local angular application mounts. There are three different ways - * global state can be passed into the application: - * * parameter in the URL hash - e.g. shared link - * * in-memory state in the data plugin exports (timefilter and filterManager) - e.g. default values - * - * This function looks up the three sources (earlier in the list means it takes precedence), - * puts it into the globalState object and syncs it with the url. - * - * Currently the legacy chrome takes care of restoring the global state when navigating from - * one app to another - to migrate away from that it will become necessary to also write the current - * state to local storage - */ -export function syncOnMount( - globalState: State, - { - query: { - filterManager, - timefilter: { timefilter }, - }, - }: DataStart -) { - // pull in global state information from the URL - globalState.fetch(); - // remember whether there were info in the URL - const hasGlobalURLState = Boolean(Object.keys(globalState.toObject()).length); - - // sync kibana platform state with the angular global state - if (!globalState.time) { - globalState.time = timefilter.getTime(); - } - if (!globalState.refreshInterval) { - globalState.refreshInterval = timefilter.getRefreshInterval(); - } - if (!globalState.filters && filterManager.getGlobalFilters().length > 0) { - globalState.filters = filterManager.getGlobalFilters(); - } - // only inject cross app global state if there is none in the url itself (that takes precedence) - if (hasGlobalURLState) { - // set flag the global state is set from the URL - globalState.$inheritedGlobalState = true; - } - globalState.save(); -} diff --git a/src/legacy/core_plugins/kibana/public/visualize/np_ready/legacy_app.js b/src/legacy/core_plugins/kibana/public/visualize/np_ready/legacy_app.js index 24055b9a2d9ed9..7079023e5bfa38 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/np_ready/legacy_app.js +++ b/src/legacy/core_plugins/kibana/public/visualize/np_ready/legacy_app.js @@ -19,6 +19,9 @@ import { find } from 'lodash'; import { i18n } from '@kbn/i18n'; +import { createHashHistory } from 'history'; + +import { createKbnUrlStateStorage } from '../../../../../../plugins/kibana_utils/public'; import editorTemplate from './editor/editor.html'; import visualizeListingTemplate from './listing/visualize_listing.html'; @@ -26,11 +29,7 @@ import visualizeListingTemplate from './listing/visualize_listing.html'; import { initVisualizeAppDirective } from './visualize_app'; import { VisualizeConstants } from './visualize_constants'; import { VisualizeListingController } from './listing/visualize_listing'; -import { - ensureDefaultIndexPattern, - registerTimefilterWithGlobalStateFactory, -} from '../legacy_imports'; -import { syncOnMount } from './global_state_sync'; +import { ensureDefaultIndexPattern } from '../legacy_imports'; import { getLandingBreadcrumbs, @@ -42,17 +41,13 @@ import { export function initVisualizeApp(app, deps) { initVisualizeAppDirective(app, deps); - app.run(globalState => { - syncOnMount(globalState, deps.data); - }); - - app.run((globalState, $rootScope) => { - registerTimefilterWithGlobalStateFactory( - deps.data.query.timefilter.timefilter, - globalState, - $rootScope - ); - }); + app.factory('history', () => createHashHistory()); + app.factory('kbnUrlStateStorage', history => + createKbnUrlStateStorage({ + history, + useHash: deps.uiSettings.get('state:storeInSessionStorage'), + }) + ); app.config(function($routeProvider) { const defaults = { diff --git a/src/legacy/core_plugins/kibana/public/visualize/np_ready/listing/visualize_listing.js b/src/legacy/core_plugins/kibana/public/visualize/np_ready/listing/visualize_listing.js index c0cc499b598f02..5a479a491395ae 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/np_ready/listing/visualize_listing.js +++ b/src/legacy/core_plugins/kibana/public/visualize/np_ready/listing/visualize_listing.js @@ -26,23 +26,21 @@ import { i18n } from '@kbn/i18n'; import { getServices } from '../../kibana_services'; import { wrapInI18nContext } from '../../legacy_imports'; +import { syncQueryStateWithUrl } from '../../../../../../../plugins/data/public'; + export function initListingDirective(app) { app.directive('visualizeListingTable', reactDirective => reactDirective(wrapInI18nContext(VisualizeListingTable)) ); } -export function VisualizeListingController($injector, $scope, createNewVis) { +export function VisualizeListingController($injector, $scope, createNewVis, kbnUrlStateStorage) { const { addBasePath, chrome, savedObjectsClient, savedVisualizations, - data: { - query: { - timefilter: { timefilter }, - }, - }, + data: { query }, toastNotifications, uiSettings, visualizations, @@ -50,6 +48,16 @@ export function VisualizeListingController($injector, $scope, createNewVis) { } = getServices(); const kbnUrl = $injector.get('kbnUrl'); + // syncs `_g` portion of url with query services + const { stop: stopSyncingQueryServiceStateWithUrl } = syncQueryStateWithUrl( + query, + kbnUrlStateStorage + ); + + const { + timefilter: { timefilter }, + } = query; + timefilter.disableAutoRefreshSelector(); timefilter.disableTimeRangeSelector(); @@ -124,5 +132,7 @@ export function VisualizeListingController($injector, $scope, createNewVis) { if (this.closeNewVisModal) { this.closeNewVisModal(); } + + stopSyncingQueryServiceStateWithUrl(); }); } diff --git a/src/legacy/core_plugins/kibana/public/visualize/np_ready/types.d.ts b/src/legacy/core_plugins/kibana/public/visualize/np_ready/types.d.ts index 8ca603eb114590..55fccd75361a04 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/np_ready/types.d.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/np_ready/types.d.ts @@ -17,7 +17,13 @@ * under the License. */ -import { TimeRange, Query, Filter, DataPublicPluginStart } from 'src/plugins/data/public'; +import { + TimeRange, + Query, + Filter, + DataPublicPluginStart, + SavedQuery, +} from 'src/plugins/data/public'; import { IEmbeddableStart } from 'src/plugins/embeddable/public'; import { PersistedState } from 'src/plugins/visualizations/public'; import { LegacyCoreStart } from 'kibana/public'; @@ -48,6 +54,7 @@ export interface VisualizeAppStateTransitions { state: VisualizeAppState ) => (query: Query, filters: Filter[]) => VisualizeAppState; updateVisState: (state: VisualizeAppState) => (vis: PureVisState) => VisualizeAppState; + updateFromSavedQuery: (state: VisualizeAppState) => (savedQuery: SavedQuery) => VisualizeAppState; } export interface EditorRenderProps { diff --git a/src/plugins/data/public/query/state_sync/sync_state_with_url.ts b/src/plugins/data/public/query/state_sync/sync_state_with_url.ts index cd7058b9f8f1c1..77e5b0ab02dc13 100644 --- a/src/plugins/data/public/query/state_sync/sync_state_with_url.ts +++ b/src/plugins/data/public/query/state_sync/sync_state_with_url.ts @@ -85,7 +85,10 @@ export const syncQueryStateWithUrl = ( stateContainer: { ...globalQueryStateContainer, set: state => { - globalQueryStateContainer.set(state || defaultState); + if (state) { + // syncState utils requires to handle incoming "null" value + globalQueryStateContainer.set(state); + } }, }, storageKey: GLOBAL_STATE_STORAGE_KEY, From f0e063c9f50bad3e856714927ba19c1ffe9e1d2d Mon Sep 17 00:00:00 2001 From: Alexey Antonov Date: Mon, 9 Mar 2020 14:47:53 +0300 Subject: [PATCH 04/20] In scripted fields, unable to switch the `Type` - getting a console error which says - Class constructor DecoratedFieldFormat cannot be invoked without 'new' (#59285) Closes: #58763 Co-authored-by: Elastic Machine --- .../ui/public/field_editor/field_editor.js | 56 +++++++++++-------- .../lib/__tests__/get_default_format.test.js | 40 ------------- .../field_editor/lib/get_default_format.js | 32 ----------- .../ui/public/field_editor/lib/index.js | 1 - 4 files changed, 33 insertions(+), 96 deletions(-) delete mode 100644 src/legacy/ui/public/field_editor/lib/__tests__/get_default_format.test.js delete mode 100644 src/legacy/ui/public/field_editor/lib/get_default_format.js diff --git a/src/legacy/ui/public/field_editor/field_editor.js b/src/legacy/ui/public/field_editor/field_editor.js index ee88ad95eeff02..43461c4c689be6 100644 --- a/src/legacy/ui/public/field_editor/field_editor.js +++ b/src/legacy/ui/public/field_editor/field_editor.js @@ -66,7 +66,7 @@ import { ScriptingHelpFlyout } from './components/scripting_help'; import { FieldFormatEditor } from './components/field_format_editor'; import { FIELD_TYPES_BY_LANG, DEFAULT_FIELD_TYPES } from './constants'; -import { copyField, getDefaultFormat, executeScript, isScriptValid } from './lib'; +import { copyField, executeScript, isScriptValid } from './lib'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; @@ -76,6 +76,25 @@ import 'brace/mode/groovy'; const getFieldFormats = () => npStart.plugins.data.fieldFormats; +const getFieldTypeFormatsList = (field, defaultFieldFormat) => { + const fieldFormats = getFieldFormats(); + const formatsByType = fieldFormats.getByFieldType(field.type).map(({ id, title }) => ({ + id, + title, + })); + + return [ + { + id: '', + defaultFieldFormat, + title: i18n.translate('common.ui.fieldEditor.defaultFormatDropDown', { + defaultMessage: '- Default -', + }), + }, + ...formatsByType, + ]; +}; + export class FieldEditor extends PureComponent { static propTypes = { indexPattern: PropTypes.object.isRequired, @@ -137,11 +156,7 @@ export class FieldEditor extends PureComponent { field.type = fieldTypes.includes(field.type) ? field.type : fieldTypes[0]; const fieldFormats = getFieldFormats(); - - const fieldTypeFormats = [ - getDefaultFormat(fieldFormats.getDefaultType(field.type, field.esTypes)), - ...fieldFormats.getByFieldType(field.type), - ]; + const DefaultFieldFormat = fieldFormats.getDefaultType(field.type, field.esTypes); this.setState({ isReady: true, @@ -150,14 +165,14 @@ export class FieldEditor extends PureComponent { errors: [], scriptingLangs, fieldTypes, - fieldTypeFormats, + fieldTypeFormats: getFieldTypeFormatsList(field, DefaultFieldFormat), fieldFormatId: get(indexPattern, ['fieldFormatMap', field.name, 'type', 'id']), fieldFormatParams: field.format.params(), }); } onFieldChange = (fieldName, value) => { - const field = this.state.field; + const { field } = this.state; field[fieldName] = value; this.forceUpdate(); }; @@ -169,18 +184,11 @@ export class FieldEditor extends PureComponent { const DefaultFieldFormat = fieldFormats.getDefaultType(type); field.type = type; - - const fieldTypeFormats = [ - getDefaultFormat(DefaultFieldFormat), - ...getFieldFormats().getByFieldType(field.type), - ]; - - const FieldFormat = fieldTypeFormats[0]; - field.format = new FieldFormat(null, getConfig); + field.format = new DefaultFieldFormat(null, getConfig); this.setState({ - fieldTypeFormats, - fieldFormatId: FieldFormat.id, + fieldTypeFormats: getFieldTypeFormatsList(field, DefaultFieldFormat), + fieldFormatId: DefaultFieldFormat.id, fieldFormatParams: field.format.params(), }); }; @@ -197,12 +205,13 @@ export class FieldEditor extends PureComponent { }; onFormatChange = (formatId, params) => { - const { getConfig } = this.props.helpers; + const fieldFormats = getFieldFormats(); const { field, fieldTypeFormats } = this.state; - const FieldFormat = - fieldTypeFormats.find(format => format.id === formatId) || fieldTypeFormats[0]; + const FieldFormat = fieldFormats.getType( + formatId || fieldTypeFormats[0]?.defaultFieldFormat.id + ); - field.format = new FieldFormat(params, getConfig); + field.format = new FieldFormat(params, this.props.helpers.getConfig); this.setState({ fieldFormatId: FieldFormat.id, @@ -416,7 +425,8 @@ export class FieldEditor extends PureComponent { renderFormat() { const { field, fieldTypeFormats, fieldFormatId, fieldFormatParams } = this.state; const { fieldFormatEditors } = this.props.helpers; - const defaultFormat = fieldTypeFormats[0] && fieldTypeFormats[0].resolvedTitle; + const defaultFormat = fieldTypeFormats[0]?.defaultFieldFormat.title; + const label = defaultFormat ? ( { - return '0,0.[000]'; -}; - -describe('getDefaultFormat', () => { - it('should create default format', () => { - const DefaultFormat = getDefaultFormat(fieldFormats.NumberFormat); - const defaultFormatObject = new DefaultFormat(null, getConfig); - const formatObject = new fieldFormats.NumberFormat(null, getConfig); - - expect(DefaultFormat.id).toEqual(''); - expect(DefaultFormat.resolvedTitle).toEqual(fieldFormats.NumberFormat.title); - expect(DefaultFormat.title).toEqual('- Default -'); - expect(JSON.stringify(defaultFormatObject.params())).toEqual( - JSON.stringify(formatObject.params()) - ); - }); -}); diff --git a/src/legacy/ui/public/field_editor/lib/get_default_format.js b/src/legacy/ui/public/field_editor/lib/get_default_format.js deleted file mode 100644 index acb7ab9c6afa53..00000000000000 --- a/src/legacy/ui/public/field_editor/lib/get_default_format.js +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { i18n } from '@kbn/i18n'; - -export const getDefaultFormat = Format => { - class DefaultFormat extends Format { - static id = ''; - static resolvedTitle = Format.title; - static title = i18n.translate('common.ui.fieldEditor.defaultFormatDropDown', { - defaultMessage: '- Default -', - }); - } - - return DefaultFormat; -}; diff --git a/src/legacy/ui/public/field_editor/lib/index.js b/src/legacy/ui/public/field_editor/lib/index.js index 5e12d51763a189..c74bb0cc2ef8ab 100644 --- a/src/legacy/ui/public/field_editor/lib/index.js +++ b/src/legacy/ui/public/field_editor/lib/index.js @@ -18,5 +18,4 @@ */ export { copyField } from './copy_field'; -export { getDefaultFormat } from './get_default_format'; export { executeScript, isScriptValid } from './validate_script'; From a6489aa70efac15ed0185e4ca03d9e44e281852c Mon Sep 17 00:00:00 2001 From: Peter Pisljar Date: Mon, 9 Mar 2020 13:11:05 +0100 Subject: [PATCH 05/20] additional visualizations plugin cleanup before moving to NP (#59318) --- src/core/MIGRATION.md | 6 +-- .../data/public/search/expressions/esaggs.ts | 2 +- .../public/input_control_vis_type.ts | 3 +- .../input_control_vis/public/plugin.ts | 2 +- .../kibana/public/discover/kibana_services.ts | 1 - .../discover/np_ready/angular/discover.js | 46 ++++++++++++++----- .../field_chooser/lib/visualize_url_utils.ts | 4 +- .../management/saved_object_registry.ts | 7 +-- .../public/visualize/np_ready/legacy_app.js | 2 +- .../__tests__/region_map_visualization.js | 6 +-- .../core_plugins/region_map/public/plugin.ts | 2 +- .../coordinate_maps_visualization.js | 6 +-- .../core_plugins/tile_map/public/plugin.ts | 4 +- .../core_plugins/timelion/public/app.js | 10 +--- .../vis_type_markdown/public/plugin.ts | 2 +- .../public/metric_vis_type.test.ts | 6 +-- .../vis_type_metric/public/plugin.ts | 2 +- .../public/agg_table/__tests__/agg_table.js | 8 ++-- .../agg_table/__tests__/agg_table_group.js | 4 +- .../vis_type_table/public/plugin.ts | 2 +- .../__tests__/tag_cloud_visualization.js | 2 +- .../vis_type_tagcloud/public/plugin.ts | 2 +- .../vis_type_timelion/public/plugin.ts | 2 +- .../public/metrics_type.ts | 2 +- .../vis_type_timeseries/public/plugin.ts | 2 +- .../public/__tests__/vega_visualization.js | 4 +- .../vis_type_vega/public/plugin.ts | 4 +- .../vis_type_vega/public/vega_type.ts | 3 +- .../vis_type_vislib/public/plugin.ts | 4 +- .../__tests__/visualizations/pie_chart.js | 4 +- .../core_plugins/vis_type_xy/public/plugin.ts | 2 +- .../public/np_ready/public/index.ts | 17 ++----- .../np_ready/public/legacy/__tests__/_vis.js | 8 ++-- .../public/legacy/calculate_object_hash.d.ts | 20 -------- .../np_ready/public/legacy/update_status.ts | 2 +- .../public/np_ready/public/mocks.ts | 20 ++++---- .../public/np_ready/public/plugin.ts | 26 +++++++---- .../public/saved_visualizations/_saved_vis.ts | 3 +- .../public/vis_types/types_service.ts | 27 +++++++++++ .../np_ready/public/wizard/show_new_vis.tsx | 5 ++ .../common/calculate_object_hash.ts} | 21 +++++---- .../common}/default_feedback_message.test.ts | 0 .../common}/default_feedback_message.ts | 2 +- src/plugins/kibana_utils/common/index.ts | 2 + .../self_changing_vis/self_changing_vis.js | 2 +- x-pack/legacy/plugins/lens/public/plugin.tsx | 2 +- .../maps/public/register_vis_type_alias.js | 4 +- .../translations/translations/ja-JP.json | 1 - .../translations/translations/zh-CN.json | 1 - 49 files changed, 168 insertions(+), 151 deletions(-) delete mode 100644 src/legacy/core_plugins/visualizations/public/np_ready/public/legacy/calculate_object_hash.d.ts rename src/{legacy/core_plugins/visualizations/public/np_ready/public/legacy/calculate_object_hash.js => plugins/kibana_utils/common/calculate_object_hash.ts} (80%) rename src/{legacy/core_plugins/visualizations/public/np_ready/public/misc => plugins/kibana_utils/common}/default_feedback_message.test.ts (100%) rename src/{legacy/core_plugins/visualizations/public/np_ready/public/misc => plugins/kibana_utils/common}/default_feedback_message.ts (91%) diff --git a/src/core/MIGRATION.md b/src/core/MIGRATION.md index 4dd6bedfa4f0ce..c5e649f7d9d5c8 100644 --- a/src/core/MIGRATION.md +++ b/src/core/MIGRATION.md @@ -1178,10 +1178,10 @@ import { setup, start } from '../core_plugins/visualizations/public/legacy'; | `ui/index_patterns` | `data.indexPatterns` | still in progress | | `ui/registry/field_formats` | `data.fieldFormats` | | | `ui/registry/feature_catalogue` | `home.featureCatalogue.register` | Must add `home` as a dependency in your kibana.json. | -| `ui/registry/vis_types` | `visualizations.types` | -- | -| `ui/vis` | `visualizations.types` | -- | +| `ui/registry/vis_types` | `visualizations` | -- | +| `ui/vis` | `visualizations` | -- | | `ui/share` | `share` | `showShareContextMenu` is now called `toggleShareContextMenu`, `ShareContextMenuExtensionsRegistryProvider` is now called `register` | -| `ui/vis/vis_factory` | `visualizations.types` | -- | +| `ui/vis/vis_factory` | `visualizations` | -- | | `ui/vis/vis_filters` | `visualizations.filters` | -- | | `ui/utils/parse_es_interval` | `import { parseEsInterval } from '../data/public'` | `parseEsInterval`, `ParsedInterval`, `InvalidEsCalendarIntervalError`, `InvalidEsIntervalFormatError` items were moved to the `Data Plugin` as a static code | diff --git a/src/legacy/core_plugins/data/public/search/expressions/esaggs.ts b/src/legacy/core_plugins/data/public/search/expressions/esaggs.ts index 24dd1c4944bfba..bb954cb887ef3f 100644 --- a/src/legacy/core_plugins/data/public/search/expressions/esaggs.ts +++ b/src/legacy/core_plugins/data/public/search/expressions/esaggs.ts @@ -38,7 +38,7 @@ import { } from '../../../../../../plugins/data/public'; import { buildTabularInspectorData } from './build_tabular_inspector_data'; -import { calculateObjectHash } from '../../../../visualizations/public'; +import { calculateObjectHash } from '../../../../../../plugins/kibana_utils/common'; import { tabifyAggResponse } from '../../../../../core_plugins/data/public'; import { PersistedState } from '../../../../../../plugins/visualizations/public'; import { Adapters } from '../../../../../../plugins/inspector/public'; diff --git a/src/legacy/core_plugins/input_control_vis/public/input_control_vis_type.ts b/src/legacy/core_plugins/input_control_vis/public/input_control_vis_type.ts index 1bdff06b3a59f9..dae6c9abb625ef 100644 --- a/src/legacy/core_plugins/input_control_vis/public/input_control_vis_type.ts +++ b/src/legacy/core_plugins/input_control_vis/public/input_control_vis_type.ts @@ -22,8 +22,9 @@ import { i18n } from '@kbn/i18n'; import { createInputControlVisController } from './vis_controller'; import { getControlsTab } from './components/editor/controls_tab'; import { OptionsTab } from './components/editor/options_tab'; -import { Status, defaultFeedbackMessage } from '../../visualizations/public'; +import { Status } from '../../visualizations/public'; import { InputControlVisDependencies } from './plugin'; +import { defaultFeedbackMessage } from '../../../../plugins/kibana_utils/common'; export function createInputControlVisTypeDefinition(deps: InputControlVisDependencies) { const InputControlVisController = createInputControlVisController(deps); diff --git a/src/legacy/core_plugins/input_control_vis/public/plugin.ts b/src/legacy/core_plugins/input_control_vis/public/plugin.ts index e9ffad8b35f213..e85ccd94f9e6a7 100644 --- a/src/legacy/core_plugins/input_control_vis/public/plugin.ts +++ b/src/legacy/core_plugins/input_control_vis/public/plugin.ts @@ -59,7 +59,7 @@ export class InputControlVisPlugin implements Plugin, void> { }; expressions.registerFunction(createInputControlVisFn); - visualizations.types.createBaseVisualization( + visualizations.createBaseVisualization( createInputControlVisTypeDefinition(visualizationDependencies) ); } diff --git a/src/legacy/core_plugins/kibana/public/discover/kibana_services.ts b/src/legacy/core_plugins/kibana/public/discover/kibana_services.ts index 91b5c7f13dc954..7fa5183a4f54bb 100644 --- a/src/legacy/core_plugins/kibana/public/discover/kibana_services.ts +++ b/src/legacy/core_plugins/kibana/public/discover/kibana_services.ts @@ -50,7 +50,6 @@ export function setServices(newServices: any) { // EXPORT legacy static dependencies, should be migrated when available in a new version; export { angular }; export { wrapInI18nContext } from 'ui/i18n'; -export { buildVislibDimensions } from '../../../visualizations/public'; export { getRequestInspectorStats, getResponseInspectorStats } from '../../../data/public'; // @ts-ignore export { intervalOptions } from 'ui/agg_types'; diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/discover.js b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/discover.js index fb4158a6e3e038..81c10798936f56 100644 --- a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/discover.js +++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/discover.js @@ -45,7 +45,6 @@ import { getPainlessError } from './get_painless_error'; import { discoverResponseHandler } from './response_handler'; import { angular, - buildVislibDimensions, getRequestInspectorStats, getResponseInspectorStats, getServices, @@ -76,6 +75,7 @@ const { import { getRootBreadcrumbs, getSavedSearchBreadcrumbs } from '../helpers/breadcrumbs'; import { esFilters, + fieldFormats, indexPatterns as indexPatternsUtils, } from '../../../../../../../plugins/data/public'; import { getIndexPatternId } from '../helpers/get_index_pattern_id'; @@ -812,21 +812,45 @@ function discoverController( $fetchObservable.next(); }; + function getDimensions(aggs, timeRange) { + const [metric, agg] = aggs; + agg.params.timeRange = timeRange; + const bounds = agg.params.timeRange ? timefilter.calculateBounds(agg.params.timeRange) : null; + agg.buckets.setBounds(bounds); + + const { esUnit, esValue } = agg.buckets.getInterval(); + return { + x: { + accessor: 0, + label: agg.makeLabel(), + format: fieldFormats.serialize(agg), + params: { + date: true, + interval: moment.duration(esValue, esUnit), + intervalESValue: esValue, + intervalESUnit: esUnit, + format: agg.buckets.getScaledDateFormat(), + bounds: agg.buckets.getBounds(), + }, + }, + y: { + accessor: 1, + format: fieldFormats.serialize(metric), + label: metric.makeLabel(), + }, + }; + } + function onResults(resp) { logInspectorResponse(resp); if ($scope.opts.timefield) { const tabifiedData = tabifyAggResponse($scope.vis.aggs, resp); $scope.searchSource.rawResponse = resp; - Promise.resolve( - buildVislibDimensions($scope.vis, { - timefilter, - timeRange: $scope.timeRange, - searchSource: $scope.searchSource, - }) - ).then(resp => { - $scope.histogramData = discoverResponseHandler(tabifiedData, resp); - }); + $scope.histogramData = discoverResponseHandler( + tabifiedData, + getDimensions($scope.vis.aggs.aggs, $scope.timeRange) + ); } $scope.hits = resp.hits.total; @@ -993,7 +1017,7 @@ function discoverController( }, }; - $scope.vis = new visualizations.Vis( + $scope.vis = visualizations.createVis( $scope.searchSource.getField('index'), visSavedObject.visState ); diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/components/field_chooser/lib/visualize_url_utils.ts b/src/legacy/core_plugins/kibana/public/discover/np_ready/components/field_chooser/lib/visualize_url_utils.ts index 8dbf3cd79ccb16..7ea1863693e0d6 100644 --- a/src/legacy/core_plugins/kibana/public/discover/np_ready/components/field_chooser/lib/visualize_url_utils.ts +++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/components/field_chooser/lib/visualize_url_utils.ts @@ -29,7 +29,7 @@ import { getServices } from '../../../../kibana_services'; function getMapsAppBaseUrl() { const mapsAppVisAlias = getServices() - .visualizations.types.getAliases() + .visualizations.getAliases() .find(({ name }) => { return name === 'maps'; }); @@ -38,7 +38,7 @@ function getMapsAppBaseUrl() { export function isMapsAppRegistered() { return getServices() - .visualizations.types.getAliases() + .visualizations.getAliases() .some(({ name }) => { return name === 'maps'; }); diff --git a/src/legacy/core_plugins/kibana/public/management/saved_object_registry.ts b/src/legacy/core_plugins/kibana/public/management/saved_object_registry.ts index 604575a6e6220e..8e73a09480c41b 100644 --- a/src/legacy/core_plugins/kibana/public/management/saved_object_registry.ts +++ b/src/legacy/core_plugins/kibana/public/management/saved_object_registry.ts @@ -22,7 +22,7 @@ import { i18n } from '@kbn/i18n'; import { npStart } from 'ui/new_platform'; import { SavedObjectLoader } from '../../../../../plugins/saved_objects/public'; import { createSavedDashboardLoader } from '../dashboard'; -import { TypesService, createSavedVisLoader } from '../../../visualizations/public'; +import { start as visualizations } from '../../../visualizations/public/np_ready/public/legacy'; import { createSavedSearchesLoader } from '../../../../../plugins/discover/public'; /** @@ -58,10 +58,7 @@ const services = { savedObjectManagementRegistry.register({ id: 'savedVisualizations', - service: createSavedVisLoader({ - ...services, - ...{ visualizationTypes: new TypesService().start() }, - }), + service: visualizations.savedVisualizationsLoader, title: 'visualizations', }); diff --git a/src/legacy/core_plugins/kibana/public/visualize/np_ready/legacy_app.js b/src/legacy/core_plugins/kibana/public/visualize/np_ready/legacy_app.js index 7079023e5bfa38..b9409445166bc4 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/np_ready/legacy_app.js +++ b/src/legacy/core_plugins/kibana/public/visualize/np_ready/legacy_app.js @@ -102,7 +102,7 @@ export function initVisualizeApp(app, deps) { resolve: { savedVis: function(redirectWhenMissing, $route, $rootScope, kbnUrl) { const { core, data, savedVisualizations, visualizations } = deps; - const visTypes = visualizations.types.all(); + const visTypes = visualizations.all(); const visType = find(visTypes, { name: $route.current.params.type }); const shouldHaveIndex = visType.requiresSearch && visType.options.showIndexSelection; const hasIndex = diff --git a/src/legacy/core_plugins/region_map/public/__tests__/region_map_visualization.js b/src/legacy/core_plugins/region_map/public/__tests__/region_map_visualization.js index f11aab9b9db882..6bdb5d00e67d8f 100644 --- a/src/legacy/core_plugins/region_map/public/__tests__/region_map_visualization.js +++ b/src/legacy/core_plugins/region_map/public/__tests__/region_map_visualization.js @@ -111,9 +111,7 @@ describe('RegionMapsVisualizationTests', function() { if (!visRegComplete) { visRegComplete = true; - visualizationsSetup.types.createBaseVisualization( - createRegionMapTypeDefinition(dependencies) - ); + visualizationsSetup.createBaseVisualization(createRegionMapTypeDefinition(dependencies)); } RegionMapsVisualization = createRegionMapVisualization(dependencies); @@ -160,7 +158,7 @@ describe('RegionMapsVisualizationTests', function() { imageComparator = new ImageComparator(); - vis = new visualizationsStart.Vis(indexPattern, { + vis = visualizationsStart.createVis(indexPattern, { type: 'region_map', }); diff --git a/src/legacy/core_plugins/region_map/public/plugin.ts b/src/legacy/core_plugins/region_map/public/plugin.ts index aaf0a8a308aeae..98fb5604c3d658 100644 --- a/src/legacy/core_plugins/region_map/public/plugin.ts +++ b/src/legacy/core_plugins/region_map/public/plugin.ts @@ -70,7 +70,7 @@ export class RegionMapPlugin implements Plugin, void> { expressions.registerFunction(createRegionMapFn); - visualizations.types.createBaseVisualization( + visualizations.createBaseVisualization( createRegionMapTypeDefinition(visualizationDependencies) ); } diff --git a/src/legacy/core_plugins/tile_map/public/__tests__/coordinate_maps_visualization.js b/src/legacy/core_plugins/tile_map/public/__tests__/coordinate_maps_visualization.js index 27e9459c7e06c1..6a08405b5b6a55 100644 --- a/src/legacy/core_plugins/tile_map/public/__tests__/coordinate_maps_visualization.js +++ b/src/legacy/core_plugins/tile_map/public/__tests__/coordinate_maps_visualization.js @@ -88,9 +88,7 @@ describe('CoordinateMapsVisualizationTest', function() { if (!visRegComplete) { visRegComplete = true; - visualizationsSetup.types.createBaseVisualization( - createTileMapTypeDefinition(dependencies) - ); + visualizationsSetup.createBaseVisualization(createTileMapTypeDefinition(dependencies)); } CoordinateMapsVisualization = createTileMapVisualization(dependencies); @@ -126,7 +124,7 @@ describe('CoordinateMapsVisualizationTest', function() { setupDOM('512px', '512px'); imageComparator = new ImageComparator(); - vis = new visualizationsStart.Vis(indexPattern, { + vis = visualizationsStart.createVis(indexPattern, { type: 'tile_map', }); vis.params = { diff --git a/src/legacy/core_plugins/tile_map/public/plugin.ts b/src/legacy/core_plugins/tile_map/public/plugin.ts index 52acaf51b39b1a..a12c2753cc5251 100644 --- a/src/legacy/core_plugins/tile_map/public/plugin.ts +++ b/src/legacy/core_plugins/tile_map/public/plugin.ts @@ -64,9 +64,7 @@ export class TileMapPlugin implements Plugin, void> { expressions.registerFunction(() => createTileMapFn(visualizationDependencies)); - visualizations.types.createBaseVisualization( - createTileMapTypeDefinition(visualizationDependencies) - ); + visualizations.createBaseVisualization(createTileMapTypeDefinition(visualizationDependencies)); } public start(core: CoreStart) { diff --git a/src/legacy/core_plugins/timelion/public/app.js b/src/legacy/core_plugins/timelion/public/app.js index e4a48c09db8327..a9d678cfea79cf 100644 --- a/src/legacy/core_plugins/timelion/public/app.js +++ b/src/legacy/core_plugins/timelion/public/app.js @@ -42,7 +42,7 @@ import '../../data/public/legacy'; import './services/saved_sheet_register'; import rootTemplate from 'plugins/timelion/index.html'; -import { createSavedVisLoader, TypesService } from '../../visualizations/public'; +import { start as visualizations } from '../../visualizations/public/np_ready/public/legacy'; import { loadKbnTopNavDirectives } from '../../../../plugins/kibana_legacy/public'; loadKbnTopNavDirectives(npStart.plugins.navigation.ui); @@ -127,13 +127,7 @@ app.controller('timelion', function( timefilter.enableAutoRefreshSelector(); timefilter.enableTimeRangeSelector(); - const savedVisualizations = createSavedVisLoader({ - savedObjectsClient: npStart.core.savedObjects.client, - indexPatterns: npStart.plugins.data.indexPatterns, - chrome: npStart.core.chrome, - overlays: npStart.core.overlays, - visualizationTypes: new TypesService().start(), - }); + const savedVisualizations = visualizations.savedVisualizationsLoader; const timezone = Private(timezoneProvider)(); const defaultExpression = '.es(*)'; diff --git a/src/legacy/core_plugins/vis_type_markdown/public/plugin.ts b/src/legacy/core_plugins/vis_type_markdown/public/plugin.ts index f1316647562020..71d6c1c69ef2d9 100644 --- a/src/legacy/core_plugins/vis_type_markdown/public/plugin.ts +++ b/src/legacy/core_plugins/vis_type_markdown/public/plugin.ts @@ -39,7 +39,7 @@ export class MarkdownPlugin implements Plugin { } public setup(core: CoreSetup, { expressions, visualizations }: MarkdownPluginSetupDependencies) { - visualizations.types.createReactVisualization(markdownVisDefinition); + visualizations.createReactVisualization(markdownVisDefinition); expressions.registerFunction(createMarkdownVisFn); } diff --git a/src/legacy/core_plugins/vis_type_metric/public/metric_vis_type.test.ts b/src/legacy/core_plugins/vis_type_metric/public/metric_vis_type.test.ts index 67b5d018f46388..5dbd59f3f1709f 100644 --- a/src/legacy/core_plugins/vis_type_metric/public/metric_vis_type.test.ts +++ b/src/legacy/core_plugins/vis_type_metric/public/metric_vis_type.test.ts @@ -40,7 +40,7 @@ describe('metric_vis - createMetricVisTypeDefinition', () => { let vis: Vis; beforeAll(() => { - visualizationsSetup.types.createReactVisualization(createMetricVisTypeDefinition()); + visualizationsSetup.createReactVisualization(createMetricVisTypeDefinition()); (npStart.plugins.data.fieldFormats.getType as jest.Mock).mockImplementation(() => { return fieldFormats.UrlFormat; }); @@ -59,7 +59,7 @@ describe('metric_vis - createMetricVisTypeDefinition', () => { // TODO: remove when Vis is converted to typescript. Only importing Vis as type // @ts-ignore - vis = new visualizationsStart.Vis(stubIndexPattern, { + vis = visualizationsStart.createVis(stubIndexPattern, { type: 'metric', aggs: [{ id: '1', type: 'top_hits', schema: 'metric', params: { field: 'ip' } }], }); @@ -80,7 +80,7 @@ describe('metric_vis - createMetricVisTypeDefinition', () => { }; const el = document.createElement('div'); - const metricVisType = visualizationsStart.types.get('metric'); + const metricVisType = visualizationsStart.get('metric'); const Controller = metricVisType.visualization; const controller = new Controller(el, vis); const render = (esResponse: any) => { diff --git a/src/legacy/core_plugins/vis_type_metric/public/plugin.ts b/src/legacy/core_plugins/vis_type_metric/public/plugin.ts index 082fab47e573c6..28b435cbc79807 100644 --- a/src/legacy/core_plugins/vis_type_metric/public/plugin.ts +++ b/src/legacy/core_plugins/vis_type_metric/public/plugin.ts @@ -45,7 +45,7 @@ export class MetricVisPlugin implements Plugin { { expressions, visualizations, charts }: MetricVisPluginSetupDependencies ) { expressions.registerFunction(createMetricVisFn); - visualizations.types.createReactVisualization(createMetricVisTypeDefinition()); + visualizations.createReactVisualization(createMetricVisTypeDefinition()); } public start(core: CoreStart) { diff --git a/src/legacy/core_plugins/vis_type_table/public/agg_table/__tests__/agg_table.js b/src/legacy/core_plugins/vis_type_table/public/agg_table/__tests__/agg_table.js index 9fe7920588cd24..91581923b05cb2 100644 --- a/src/legacy/core_plugins/vis_type_table/public/agg_table/__tests__/agg_table.js +++ b/src/legacy/core_plugins/vis_type_table/public/agg_table/__tests__/agg_table.js @@ -47,10 +47,10 @@ describe('Table Vis - AggTable Directive', function() { const tabifiedData = {}; const init = () => { - const vis1 = new visualizationsStart.Vis(indexPattern, 'table'); + const vis1 = visualizationsStart.createVis(indexPattern, 'table'); tabifiedData.metricOnly = tabifyAggResponse(vis1.aggs, metricOnly); - const vis2 = new visualizationsStart.Vis(indexPattern, { + const vis2 = visualizationsStart.createVis(indexPattern, { type: 'table', params: { showMetricsAtAllLevels: true, @@ -69,7 +69,7 @@ describe('Table Vis - AggTable Directive', function() { metricsAtAllLevels: true, }); - const vis3 = new visualizationsStart.Vis(indexPattern, { + const vis3 = visualizationsStart.createVis(indexPattern, { type: 'table', aggs: [ { type: 'avg', schema: 'metric', params: { field: 'bytes' } }, @@ -110,7 +110,7 @@ describe('Table Vis - AggTable Directive', function() { beforeEach(initLocalAngular); ngMock.inject(function() { - visualizationsSetup.types.createBaseVisualization(tableVisTypeDefinition); + visualizationsSetup.createBaseVisualization(tableVisTypeDefinition); }); beforeEach(ngMock.module('kibana/table_vis')); diff --git a/src/legacy/core_plugins/vis_type_table/public/agg_table/__tests__/agg_table_group.js b/src/legacy/core_plugins/vis_type_table/public/agg_table/__tests__/agg_table_group.js index 79d4d7c40d3559..4d62551dcf3961 100644 --- a/src/legacy/core_plugins/vis_type_table/public/agg_table/__tests__/agg_table_group.js +++ b/src/legacy/core_plugins/vis_type_table/public/agg_table/__tests__/agg_table_group.js @@ -35,10 +35,10 @@ describe('Table Vis - AggTableGroup Directive', function() { const tabifiedData = {}; const init = () => { - const vis1 = new visualizationsStart.Vis(indexPattern, 'table'); + const vis1 = visualizationsStart.createVis(indexPattern, 'table'); tabifiedData.metricOnly = tabifyAggResponse(vis1.aggs, metricOnly); - const vis2 = new visualizationsStart.Vis(indexPattern, { + const vis2 = visualizationsStart.createVis(indexPattern, { type: 'pie', aggs: [ { type: 'avg', schema: 'metric', params: { field: 'bytes' } }, diff --git a/src/legacy/core_plugins/vis_type_table/public/plugin.ts b/src/legacy/core_plugins/vis_type_table/public/plugin.ts index 17c50b0567b67d..519a56da23ac9b 100644 --- a/src/legacy/core_plugins/vis_type_table/public/plugin.ts +++ b/src/legacy/core_plugins/vis_type_table/public/plugin.ts @@ -44,7 +44,7 @@ export class TableVisPlugin implements Plugin, void> { ) { expressions.registerFunction(createTableVisFn); - visualizations.types.createBaseVisualization(tableVisTypeDefinition); + visualizations.createBaseVisualization(tableVisTypeDefinition); } public start(core: CoreStart) { diff --git a/src/legacy/core_plugins/vis_type_tagcloud/public/components/__tests__/tag_cloud_visualization.js b/src/legacy/core_plugins/vis_type_tagcloud/public/components/__tests__/tag_cloud_visualization.js index 55ecf98f994d24..3091b3340cd6d2 100644 --- a/src/legacy/core_plugins/vis_type_tagcloud/public/components/__tests__/tag_cloud_visualization.js +++ b/src/legacy/core_plugins/vis_type_tagcloud/public/components/__tests__/tag_cloud_visualization.js @@ -76,7 +76,7 @@ describe('TagCloudVisualizationTest', function() { beforeEach(async function() { setupDOM('512px', '512px'); imageComparator = new ImageComparator(); - vis = new visualizationsStart.Vis(indexPattern, { + vis = visualizationsStart.createVis(indexPattern, { type: 'tagcloud', params: { bucket: { accessor: 0, format: {} }, diff --git a/src/legacy/core_plugins/vis_type_tagcloud/public/plugin.ts b/src/legacy/core_plugins/vis_type_tagcloud/public/plugin.ts index 9e5940eca15983..8244cba38edc32 100644 --- a/src/legacy/core_plugins/vis_type_tagcloud/public/plugin.ts +++ b/src/legacy/core_plugins/vis_type_tagcloud/public/plugin.ts @@ -53,7 +53,7 @@ export class TagCloudPlugin implements Plugin { colors: charts.colors, }; expressions.registerFunction(createTagCloudFn); - visualizations.types.createBaseVisualization( + visualizations.createBaseVisualization( createTagCloudVisTypeDefinition(visualizationDependencies) ); } diff --git a/src/legacy/core_plugins/vis_type_timelion/public/plugin.ts b/src/legacy/core_plugins/vis_type_timelion/public/plugin.ts index 69a2ad3c1351ae..9d69c312b48f44 100644 --- a/src/legacy/core_plugins/vis_type_timelion/public/plugin.ts +++ b/src/legacy/core_plugins/vis_type_timelion/public/plugin.ts @@ -66,7 +66,7 @@ export class TimelionVisPlugin implements Plugin { }; expressions.registerFunction(() => getTimelionVisualizationConfig(dependencies)); - visualizations.types.createReactVisualization(getTimelionVisDefinition(dependencies)); + visualizations.createReactVisualization(getTimelionVisDefinition(dependencies)); } public start(core: CoreStart, plugins: PluginsStart) { diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/metrics_type.ts b/src/legacy/core_plugins/vis_type_timeseries/public/metrics_type.ts index 135cc1e1814320..30c62d778933bf 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/metrics_type.ts +++ b/src/legacy/core_plugins/vis_type_timeseries/public/metrics_type.ts @@ -25,7 +25,7 @@ import { metricsRequestHandler } from './request_handler'; import { EditorController } from './editor_controller'; // @ts-ignore import { PANEL_TYPES } from '../../../../plugins/vis_type_timeseries/common/panel_types'; -import { defaultFeedbackMessage } from '../../visualizations/public'; +import { defaultFeedbackMessage } from '../../../../plugins/kibana_utils/common'; export const metricsVisDefinition = { name: 'metrics', diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/plugin.ts b/src/legacy/core_plugins/vis_type_timeseries/public/plugin.ts index 38a9c68487854e..441b1f05ea78cd 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/plugin.ts +++ b/src/legacy/core_plugins/vis_type_timeseries/public/plugin.ts @@ -57,7 +57,7 @@ export class MetricsPlugin implements Plugin, void> { ) { expressions.registerFunction(createMetricsFn); setUISettings(core.uiSettings); - visualizations.types.createReactVisualization(metricsVisDefinition); + visualizations.createReactVisualization(metricsVisDefinition); } public start(core: CoreStart, { data }: MetricsPluginStartDependencies) { diff --git a/src/legacy/core_plugins/vis_type_vega/public/__tests__/vega_visualization.js b/src/legacy/core_plugins/vis_type_vega/public/__tests__/vega_visualization.js index 378590af29d3a8..5befc09b245448 100644 --- a/src/legacy/core_plugins/vis_type_vega/public/__tests__/vega_visualization.js +++ b/src/legacy/core_plugins/vis_type_vega/public/__tests__/vega_visualization.js @@ -93,7 +93,7 @@ describe('VegaVisualizations', () => { if (!visRegComplete) { visRegComplete = true; - visualizationsSetup.types.createBaseVisualization( + visualizationsSetup.createBaseVisualization( createVegaTypeDefinition(vegaVisualizationDependencies) ); } @@ -108,7 +108,7 @@ describe('VegaVisualizations', () => { setupDOM('512px', '512px'); imageComparator = new ImageComparator(); - vis = new visualizationsStart.Vis(indexPattern, { type: 'vega' }); + vis = visualizationsStart.createVis(indexPattern, { type: 'vega' }); }); afterEach(function() { diff --git a/src/legacy/core_plugins/vis_type_vega/public/plugin.ts b/src/legacy/core_plugins/vis_type_vega/public/plugin.ts index b354433330caf8..3b01d9ceca5a6b 100644 --- a/src/legacy/core_plugins/vis_type_vega/public/plugin.ts +++ b/src/legacy/core_plugins/vis_type_vega/public/plugin.ts @@ -84,9 +84,7 @@ export class VegaPlugin implements Plugin, void> { expressions.registerFunction(() => createVegaFn(visualizationDependencies)); - visualizations.types.createBaseVisualization( - createVegaTypeDefinition(visualizationDependencies) - ); + visualizations.createBaseVisualization(createVegaTypeDefinition(visualizationDependencies)); } public start(core: CoreStart, { data }: VegaPluginStartDependencies) { diff --git a/src/legacy/core_plugins/vis_type_vega/public/vega_type.ts b/src/legacy/core_plugins/vis_type_vega/public/vega_type.ts index a84948f725e0ab..78f9c170ab62d7 100644 --- a/src/legacy/core_plugins/vis_type_vega/public/vega_type.ts +++ b/src/legacy/core_plugins/vis_type_vega/public/vega_type.ts @@ -19,10 +19,11 @@ import { i18n } from '@kbn/i18n'; // @ts-ignore -import { Status, defaultFeedbackMessage } from '../../visualizations/public'; +import { Status } from '../../visualizations/public'; import { DefaultEditorSize } from '../../vis_default_editor/public'; import { VegaVisualizationDependencies } from './plugin'; import { VegaVisEditor } from './components'; +import { defaultFeedbackMessage } from '../../../../plugins/kibana_utils/common'; import { createVegaRequestHandler } from './vega_request_handler'; // @ts-ignore diff --git a/src/legacy/core_plugins/vis_type_vislib/public/plugin.ts b/src/legacy/core_plugins/vis_type_vislib/public/plugin.ts index 8a7196a61ecec2..a71892cc47b05a 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/plugin.ts +++ b/src/legacy/core_plugins/vis_type_vislib/public/plugin.ts @@ -97,14 +97,14 @@ export class VisTypeVislibPlugin implements Plugin, void> { // Register legacy vislib types that have been converted convertedFns.forEach(expressions.registerFunction); convertedTypes.forEach(vis => - visualizations.types.createBaseVisualization(vis(visualizationDependencies)) + visualizations.createBaseVisualization(vis(visualizationDependencies)) ); } // Register non-converted types vislibFns.forEach(expressions.registerFunction); vislibTypes.forEach(vis => - visualizations.types.createBaseVisualization(vis(visualizationDependencies)) + visualizations.createBaseVisualization(vis(visualizationDependencies)) ); } diff --git a/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/visualizations/pie_chart.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/visualizations/pie_chart.js index 9c9c5a84f046cc..43e3b987f19628 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/visualizations/pie_chart.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/visualizations/pie_chart.js @@ -133,7 +133,7 @@ describe('No global chart settings', function() { responseHandler = vislibSlicesResponseHandler; let id1 = 1; - stubVis1 = new visualizationsStart.Vis(indexPattern, { + stubVis1 = visualizationsStart.createVis(indexPattern, { type: 'pie', aggs: rowAgg, }); @@ -222,7 +222,7 @@ describe('Vislib PieChart Class Test Suite', function() { responseHandler = vislibSlicesResponseHandler; let id = 1; - stubVis = new visualizationsStart.Vis(indexPattern, { + stubVis = visualizationsStart.createVis(indexPattern, { type: 'pie', aggs: dataAgg, }); diff --git a/src/legacy/core_plugins/vis_type_xy/public/plugin.ts b/src/legacy/core_plugins/vis_type_xy/public/plugin.ts index 59bb64b337256c..35abb04fd87324 100644 --- a/src/legacy/core_plugins/vis_type_xy/public/plugin.ts +++ b/src/legacy/core_plugins/vis_type_xy/public/plugin.ts @@ -72,7 +72,7 @@ export class VisTypeXyPlugin implements Plugin, void> { visFunctions.forEach((fn: any) => expressions.registerFunction(fn)); visTypeDefinitions.forEach((vis: any) => - visualizations.types.createBaseVisualization(vis(visualizationDependencies)) + visualizations.createBaseVisualization(vis(visualizationDependencies)) ); } diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/index.ts b/src/legacy/core_plugins/visualizations/public/np_ready/public/index.ts index 7688a7769cf795..b59eb2277411c5 100644 --- a/src/legacy/core_plugins/visualizations/public/np_ready/public/index.ts +++ b/src/legacy/core_plugins/visualizations/public/np_ready/public/index.ts @@ -43,20 +43,11 @@ export { Vis, VisParams, VisState } from './vis'; import { VisualizeEmbeddableFactory, VisualizeEmbeddable } from './embeddable'; export type VisualizeEmbeddableFactoryContract = PublicContract; export type VisualizeEmbeddableContract = PublicContract; +export { TypesService } from './vis_types/types_service'; +export { Status } from './legacy/update_status'; // should remove +export { VISUALIZE_EMBEDDABLE_TYPE, VisualizeInput } from './embeddable'; +export { SchemaConfig } from './legacy/build_pipeline'; export function plugin(initializerContext: PluginInitializerContext) { return new VisualizationsPlugin(initializerContext); } - -/** @public static code */ -export { TypesService } from './vis_types/types_service'; -export { VISUALIZE_EMBEDDABLE_TYPE, VisualizeInput } from './embeddable'; - -export { Status } from './legacy/update_status'; -export { buildPipeline, buildVislibDimensions, SchemaConfig } from './legacy/build_pipeline'; - -// @ts-ignore -export { updateOldState } from './legacy/vis_update_state'; -export { calculateObjectHash } from './legacy/calculate_object_hash'; -export { createSavedVisLoader } from './saved_visualizations/saved_visualizations'; -export { defaultFeedbackMessage } from './misc/default_feedback_message'; diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/legacy/__tests__/_vis.js b/src/legacy/core_plugins/visualizations/public/np_ready/public/legacy/__tests__/_vis.js index 8c75ba24051b00..deb345a77cdb6e 100644 --- a/src/legacy/core_plugins/visualizations/public/np_ready/public/legacy/__tests__/_vis.js +++ b/src/legacy/core_plugins/visualizations/public/np_ready/public/legacy/__tests__/_vis.js @@ -43,12 +43,12 @@ describe('Vis Class', function() { beforeEach( ngMock.inject(function(Private) { indexPattern = Private(FixturesStubbedLogstashIndexPatternProvider); - visTypes = visualizations.types; + visTypes = visualizations; }) ); beforeEach(function() { - vis = new visualizations.Vis(indexPattern, stateFixture); + vis = visualizations.createVis(indexPattern, stateFixture); }); const verifyVis = function(vis) { @@ -84,7 +84,7 @@ describe('Vis Class', function() { describe('setState()', function() { it('should set the state to defaults', function() { - const vis = new visualizations.Vis(indexPattern); + const vis = visualizations.createVis(indexPattern); expect(vis).to.have.property('type'); expect(vis.type).to.eql(visTypes.get('histogram')); expect(vis).to.have.property('aggs'); @@ -100,7 +100,7 @@ describe('Vis Class', function() { expect(vis.isHierarchical()).to.be(true); }); it('should return false for non-hierarchical vis (like histogram)', function() { - const vis = new visualizations.Vis(indexPattern); + const vis = visualizations.createVis(indexPattern); expect(vis.isHierarchical()).to.be(false); }); }); diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/legacy/calculate_object_hash.d.ts b/src/legacy/core_plugins/visualizations/public/np_ready/public/legacy/calculate_object_hash.d.ts deleted file mode 100644 index d2d11c14a3e5fb..00000000000000 --- a/src/legacy/core_plugins/visualizations/public/np_ready/public/legacy/calculate_object_hash.d.ts +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -export function calculateObjectHash(obj: object): string; diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/legacy/update_status.ts b/src/legacy/core_plugins/visualizations/public/np_ready/public/legacy/update_status.ts index d9af5122eadec6..92a9ce8366f4fe 100644 --- a/src/legacy/core_plugins/visualizations/public/np_ready/public/legacy/update_status.ts +++ b/src/legacy/core_plugins/visualizations/public/np_ready/public/legacy/update_status.ts @@ -18,7 +18,7 @@ */ import { PersistedState } from '../../../../../../../plugins/visualizations/public'; -import { calculateObjectHash } from './calculate_object_hash'; +import { calculateObjectHash } from '../../../../../../../plugins/kibana_utils/common'; import { Vis } from '../vis'; enum Status { diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/mocks.ts b/src/legacy/core_plugins/visualizations/public/np_ready/public/mocks.ts index 8d7407b6191d63..9e8eac08c33eac 100644 --- a/src/legacy/core_plugins/visualizations/public/np_ready/public/mocks.ts +++ b/src/legacy/core_plugins/visualizations/public/np_ready/public/mocks.ts @@ -28,23 +28,19 @@ import { usageCollectionPluginMock } from '../../../../../../plugins/usage_colle import { uiActionsPluginMock } from '../../../../../../plugins/ui_actions/public/mocks'; const createSetupContract = (): VisualizationsSetup => ({ - types: { - createBaseVisualization: jest.fn(), - createReactVisualization: jest.fn(), - registerAlias: jest.fn(), - hideTypes: jest.fn(), - }, + createBaseVisualization: jest.fn(), + createReactVisualization: jest.fn(), + registerAlias: jest.fn(), + hideTypes: jest.fn(), }); const createStartContract = (): VisualizationsStart => ({ - types: { - get: jest.fn(), - all: jest.fn(), - getAliases: jest.fn(), - }, + get: jest.fn(), + all: jest.fn(), + getAliases: jest.fn(), savedVisualizationsLoader: {} as any, showNewVisModal: jest.fn(), - Vis: jest.fn(), + createVis: jest.fn(), }); const createInstance = async () => { diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/plugin.ts b/src/legacy/core_plugins/visualizations/public/np_ready/public/plugin.ts index 10797a1a04df49..b8db611f30815f 100644 --- a/src/legacy/core_plugins/visualizations/public/np_ready/public/plugin.ts +++ b/src/legacy/core_plugins/visualizations/public/np_ready/public/plugin.ts @@ -48,27 +48,27 @@ import { visualization as visualizationRenderer } from './expressions/visualizat import { DataPublicPluginSetup, DataPublicPluginStart, + IIndexPattern, } from '../../../../../../plugins/data/public'; import { UsageCollectionSetup } from '../../../../../../plugins/usage_collection/public'; import { createSavedVisLoader, SavedVisualizationsLoader } from './saved_visualizations'; -import { VisImpl, VisImplConstructor } from './vis_impl'; +import { VisImpl } from './vis_impl'; import { showNewVisModal } from './wizard'; import { UiActionsStart } from '../../../../../../plugins/ui_actions/public'; import { DataStart as LegacyDataStart } from '../../../../data/public'; +import { VisState } from './types'; /** * Interface for this plugin's returned setup/start contracts. * * @public */ -export interface VisualizationsSetup { - types: TypesSetup; -} -export interface VisualizationsStart { - types: TypesStart; +export type VisualizationsSetup = TypesSetup; + +export interface VisualizationsStart extends TypesStart { savedVisualizationsLoader: SavedVisualizationsLoader; - Vis: VisImplConstructor; + createVis: (indexPattern: IIndexPattern, visState?: VisState) => VisImpl; showNewVisModal: typeof showNewVisModal; } @@ -122,7 +122,7 @@ export class VisualizationsPlugin embeddable.registerEmbeddableFactory(VISUALIZE_EMBEDDABLE_TYPE, embeddableFactory); return { - types: this.types.setup(), + ...this.types.setup(), }; } @@ -152,9 +152,15 @@ export class VisualizationsPlugin setSavedVisualizationsLoader(savedVisualizationsLoader); return { - types, + ...types, showNewVisModal, - Vis: VisImpl, + /** + * creates new instance of Vis + * @param {IIndexPattern} indexPattern - index pattern to use + * @param {VisState} visState - visualization configuration + */ + createVis: (indexPattern: IIndexPattern, visState?: VisState) => + new VisImpl(indexPattern, visState), savedVisualizationsLoader, }; } diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/saved_visualizations/_saved_vis.ts b/src/legacy/core_plugins/visualizations/public/np_ready/public/saved_visualizations/_saved_vis.ts index 2458ed5008ddda..e381a01edef8ba 100644 --- a/src/legacy/core_plugins/visualizations/public/np_ready/public/saved_visualizations/_saved_vis.ts +++ b/src/legacy/core_plugins/visualizations/public/np_ready/public/saved_visualizations/_saved_vis.ts @@ -29,7 +29,8 @@ import { SavedObject, SavedObjectKibanaServices, } from '../../../../../../../plugins/saved_objects/public'; -import { updateOldState } from '../../../index'; +// @ts-ignore +import { updateOldState } from '../legacy/vis_update_state'; import { extractReferences, injectReferences } from './saved_visualization_references'; import { IIndexPattern } from '../../../../../../../plugins/data/public'; import { VisSavedObject } from '../types'; diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/vis_types/types_service.ts b/src/legacy/core_plugins/visualizations/public/np_ready/public/vis_types/types_service.ts index 0cae83afb78610..6bcaa9a3e1dac7 100644 --- a/src/legacy/core_plugins/visualizations/public/np_ready/public/vis_types/types_service.ts +++ b/src/legacy/core_plugins/visualizations/public/np_ready/public/vis_types/types_service.ts @@ -67,15 +67,32 @@ export class TypesService { this.types[visDefinition.name] = visDefinition; }; return { + /** + * registers a visualization type + * @param {VisType} config - visualization type definition + */ createBaseVisualization: (config: any) => { const vis = new BaseVisType(config); registerVisualization(() => vis); }, + /** + * registers a visualization which uses react for rendering + * @param {VisType} config - visualization type definition + */ createReactVisualization: (config: any) => { const vis = new ReactVisType(config); registerVisualization(() => vis); }, + /** + * registers a visualization alias + * alias is a visualization type without implementation, it just redirects somewhere in kibana + * @param {VisTypeAlias} config - visualization alias definition + */ registerAlias: visTypeAliasRegistry.add, + /** + * allows to hide specific visualization types from create visualization dialog + * @param {string[]} typeNames - list of type ids to hide + */ hideTypes: (typeNames: string[]) => { typeNames.forEach((name: string) => { if (this.types[name]) { @@ -90,12 +107,22 @@ export class TypesService { public start() { return { + /** + * returns specific visualization or undefined if not found + * @param {string} visualization - id of visualization to return + */ get: (visualization: string) => { return this.types[visualization]; }, + /** + * returns all registered visualization types + */ all: () => { return [...Object.values(this.types)]; }, + /** + * returns all registered aliases + */ getAliases: visTypeAliasRegistry.get, }; } diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/wizard/show_new_vis.tsx b/src/legacy/core_plugins/visualizations/public/np_ready/public/wizard/show_new_vis.tsx index a79c6ad98edf6f..6b37845f03db12 100644 --- a/src/legacy/core_plugins/visualizations/public/np_ready/public/wizard/show_new_vis.tsx +++ b/src/legacy/core_plugins/visualizations/public/np_ready/public/wizard/show_new_vis.tsx @@ -29,6 +29,11 @@ export interface ShowNewVisModalParams { onClose?: () => void; } +/** + * shows modal dialog that allows you to create new visualization + * @param {string[]} editorParams + * @param {function} onClose - function that will be called when dialog is closed + */ export function showNewVisModal({ editorParams = [], onClose }: ShowNewVisModalParams = {}) { const container = document.createElement('div'); let isClosed = false; diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/legacy/calculate_object_hash.js b/src/plugins/kibana_utils/common/calculate_object_hash.ts similarity index 80% rename from src/legacy/core_plugins/visualizations/public/np_ready/public/legacy/calculate_object_hash.js rename to src/plugins/kibana_utils/common/calculate_object_hash.ts index 0b6f3dc4e08265..26062636d624c6 100644 --- a/src/legacy/core_plugins/visualizations/public/np_ready/public/legacy/calculate_object_hash.js +++ b/src/plugins/kibana_utils/common/calculate_object_hash.ts @@ -19,14 +19,14 @@ // adopted form https://github.com/bevacqua/hash-sum -function pad(hash, len) { +function pad(hash: string, len: number): string { while (hash.length < len) { hash = '0' + hash; } return hash; } -function fold(hash, text) { +function fold(hash: number, text: string): number { let i; let chr; let len; @@ -35,22 +35,25 @@ function fold(hash, text) { } for (i = 0, len = text.length; i < len; i++) { chr = text.charCodeAt(i); + // eslint-disable-next-line no-bitwise hash = (hash << 5) - hash + chr; + // eslint-disable-next-line no-bitwise hash |= 0; } return hash < 0 ? hash * -2 : hash; } -function foldObject(hash, o, seen) { +function foldObject(hash: number, o: any, seen: any[]) { + function foldKey(h: number, key: string): number { + return foldValue(h, o[key], key, seen); + } + return Object.keys(o) .sort() .reduce(foldKey, hash); - function foldKey(hash, key) { - return foldValue(hash, o[key], key, seen); - } } -function foldValue(input, value, key, seen) { +function foldValue(input: number, value: any, key: string, seen: any[]) { const hash = fold(fold(fold(input, key), toString(value)), typeof value); if (value === null) { return fold(hash, 'null'); @@ -72,11 +75,11 @@ function foldValue(input, value, key, seen) { return fold(hash, value.toString()); } -function toString(o) { +function toString(o: object): string { return Object.prototype.toString.call(o); } -function sum(o) { +function sum(o: object): string { return pad(foldValue(0, o, '', []).toString(16), 8); } diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/misc/default_feedback_message.test.ts b/src/plugins/kibana_utils/common/default_feedback_message.test.ts similarity index 100% rename from src/legacy/core_plugins/visualizations/public/np_ready/public/misc/default_feedback_message.test.ts rename to src/plugins/kibana_utils/common/default_feedback_message.test.ts diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/misc/default_feedback_message.ts b/src/plugins/kibana_utils/common/default_feedback_message.ts similarity index 91% rename from src/legacy/core_plugins/visualizations/public/np_ready/public/misc/default_feedback_message.ts rename to src/plugins/kibana_utils/common/default_feedback_message.ts index 2871437614231a..f61f36bc8810cc 100644 --- a/src/legacy/core_plugins/visualizations/public/np_ready/public/misc/default_feedback_message.ts +++ b/src/plugins/kibana_utils/common/default_feedback_message.ts @@ -19,7 +19,7 @@ import { i18n } from '@kbn/i18n'; -export const defaultFeedbackMessage = i18n.translate('visualizations.defaultFeedbackMessage', { +export const defaultFeedbackMessage = i18n.translate('kibana_utils.defaultFeedbackMessage', { defaultMessage: 'Have feedback? Please create an issue in {link}.', values: { link: diff --git a/src/plugins/kibana_utils/common/index.ts b/src/plugins/kibana_utils/common/index.ts index 50120edc0c0561..87b625ef9a64f5 100644 --- a/src/plugins/kibana_utils/common/index.ts +++ b/src/plugins/kibana_utils/common/index.ts @@ -26,3 +26,5 @@ export { createGetterSetter, Get, Set } from './create_getter_setter'; export { distinctUntilChangedWithInitialValue } from './distinct_until_changed_with_initial_value'; export { url } from './url'; export { now } from './now'; +export { calculateObjectHash } from './calculate_object_hash'; +export { defaultFeedbackMessage } from './default_feedback_message'; diff --git a/test/plugin_functional/plugins/kbn_tp_custom_visualizations/public/self_changing_vis/self_changing_vis.js b/test/plugin_functional/plugins/kbn_tp_custom_visualizations/public/self_changing_vis/self_changing_vis.js index 2976a6cd98e30a..643d15c9827929 100644 --- a/test/plugin_functional/plugins/kbn_tp_custom_visualizations/public/self_changing_vis/self_changing_vis.js +++ b/test/plugin_functional/plugins/kbn_tp_custom_visualizations/public/self_changing_vis/self_changing_vis.js @@ -22,7 +22,7 @@ import { SelfChangingComponent } from './self_changing_components'; import { setup as visualizations } from '../../../../../../src/legacy/core_plugins/visualizations/public/np_ready/public/legacy'; -visualizations.types.createReactVisualization({ +visualizations.createReactVisualization({ name: 'self_changing_vis', title: 'Self Changing Vis', icon: 'controlsHorizontal', diff --git a/x-pack/legacy/plugins/lens/public/plugin.tsx b/x-pack/legacy/plugins/lens/public/plugin.tsx index 7f96268fc2e8c6..7afe6d7abedc0e 100644 --- a/x-pack/legacy/plugins/lens/public/plugin.tsx +++ b/x-pack/legacy/plugins/lens/public/plugin.tsx @@ -103,7 +103,7 @@ export class LensPlugin { this.datatableVisualization.setup(core, dependencies); this.metricVisualization.setup(core, dependencies); - visualizations.types.registerAlias(getLensAliasConfig()); + visualizations.registerAlias(getLensAliasConfig()); kibanaLegacy.registerLegacyApp({ id: 'lens', diff --git a/x-pack/legacy/plugins/maps/public/register_vis_type_alias.js b/x-pack/legacy/plugins/maps/public/register_vis_type_alias.js index b0e62d37fbf1cf..4d87b6a0558020 100644 --- a/x-pack/legacy/plugins/maps/public/register_vis_type_alias.js +++ b/x-pack/legacy/plugins/maps/public/register_vis_type_alias.js @@ -23,7 +23,7 @@ The Maps app offers more functionality and is easier to use.`, } ); -visualizationsSetup.types.registerAlias({ +visualizationsSetup.registerAlias({ aliasUrl: MAP_BASE_URL, name: APP_ID, title: i18n.translate('xpack.maps.visTypeAlias.title', { @@ -37,5 +37,5 @@ visualizationsSetup.types.registerAlias({ }); if (!showMapVisualizationTypes) { - visualizationsSetup.types.hideTypes(['region_map', 'tile_map']); + visualizationsSetup.hideTypes(['region_map', 'tile_map']); } diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 568108aff75031..5635bb19b7e836 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -2849,7 +2849,6 @@ "timelion.vis.intervalLabel": "間隔", "uiActions.actionPanel.title": "オプション", "uiActions.errors.incompatibleAction": "操作に互換性がありません", - "visualizations.defaultFeedbackMessage": "フィードバックがありますか?{link} で問題を報告してください。", "visualizations.newVisWizard.betaDescription": "このビジュアライゼーションはベータ段階で、変更される可能性があります。デザインとコードはオフィシャル GA 機能よりも完成度が低く、現状のまま保証なしで提供されています。ベータ機能にはオフィシャル GA 機能の SLA が適用されません", "visualizations.newVisWizard.betaTitle": "ベータ", "visualizations.newVisWizard.chooseSourceTitle": "ソースの選択", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index a91f55960e34fd..05230210461674 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -2850,7 +2850,6 @@ "timelion.vis.intervalLabel": "时间间隔", "uiActions.actionPanel.title": "选项", "uiActions.errors.incompatibleAction": "操作不兼容", - "visualizations.defaultFeedbackMessage": "想反馈?请在“{link}中创建问题。", "visualizations.newVisWizard.betaDescription": "此可视化为公测版,可能会进行更改。设计和代码相对于正式发行版功能还不够成熟,将按原样提供,且不提供任何保证。公测版功能不受正式发行版功能支持 SLA 的约束", "visualizations.newVisWizard.betaTitle": "公测版", "visualizations.newVisWizard.chooseSourceTitle": "选择源", From 492a97e288527d57a02ccaa6ec9106c9bcb3b4de Mon Sep 17 00:00:00 2001 From: Jean-Louis Leysens Date: Mon, 9 Mar 2020 14:11:31 +0100 Subject: [PATCH 06/20] [Upgrade Assistant] Better handling of closed indices (#58890) * Exclude disallowed, private setting at index creation * Remove intl from tabs component * Added logic for checking the current index status * Added ES contract integration test Using _cluster/state is considered internal. This adds an integration test for checking the contract in CI. * Add the client side notification for closed indices * First version of end-to-end functionality working * Clean up unused, incorrect type information * Fix type issues and added a comment about the new reindex options * Fixed server side tests, added comments and logic updates Updated the handling of reindexOptions to make it more backwards compatible (treat it as if it could be undefined). Also update the logic for checking for open or closed indices. No optional chaining! It should break if the response does not exactly match. * Clean up unused code * Improved idempotency of test and check explicitly for "close". Rather check for the specific value we want, as this is what is also gauranteed by the tests. In this way, the information we send back to the client is also more accurate regarding the index status. If, in future, more index states are introduced this will need to be revisited if it affects the ability for an index to be re-indexed. * Update client-side tests * Fix types * Handle a case where the index name provided may be an alias * Fix types * merge-conflict: finish merge conflict resolution * Update x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/reindex/closed_warning_icon.tsx Co-Authored-By: Alison Goryachev * merge-conflict: Remove duplicate import VSCode does not auto-save as expected :sigh: * ui: Revisit the UI Moved the warning icon to inside of the button and tooltip to on the button. Added a callout to the reindex flyout for when an index is closed. * logic: slight update to when the index closed callout is shown We only show the index closed callout in the flyout when the reindex operation is not considered "completed" * tests: fix jest tests * refactor: remove "openAndClose" from reindex endpoints "openAndClose" should just happen automatically. The user should not have to pass the flag in, that would be a weird API. We just need to warn the user about that reindexing a closed index will take more resources * test: update upgrade assistant integration test * fix: types * copy: use sentence case * refactor: use the in scope declaration of reindex op * test: Clean up tests Reindexing test was generating index name, could just get it from server response. Also removed openAndClose from all integration tests Co-authored-by: Elastic Machine Co-authored-by: Alison Goryachev --- ...get_index_state_from_cluster_state.test.ts | 53 +++++++ .../get_index_state_from_cluster_state.ts | 28 ++++ .../plugins/upgrade_assistant/common/types.ts | 55 ++++++- .../public/application/app_context.tsx | 3 +- .../public/application/components/tabs.tsx | 27 ++-- .../tabs/checkup/deprecations/cell.tsx | 12 +- .../tabs/checkup/deprecations/index_table.tsx | 22 ++- .../tabs/checkup/deprecations/list.test.tsx | 148 +++++++++--------- .../tabs/checkup/deprecations/list.tsx | 10 +- .../checkup/deprecations/reindex/button.tsx | 49 +++++- .../reindex/flyout/checklist_step.test.tsx | 1 + .../reindex/flyout/checklist_step.tsx | 4 +- .../deprecations/reindex/flyout/container.tsx | 78 ++++++++- .../reindex/flyout/warning_step.test.tsx | 1 + .../reindex/flyout/warnings_step.tsx | 4 +- .../deprecations/reindex/polling_service.ts | 1 + .../upgrade_assistant/public/plugin.ts | 4 +- .../es_migration_apis.test.ts.snap | 6 + .../server/lib/es_indices_state_check.ts | 36 +++++ .../server/lib/es_migration_apis.test.ts | 17 +- .../server/lib/es_migration_apis.ts | 15 ++ .../server/lib/reindexing/index_settings.ts | 1 + .../lib/reindexing/reindex_actions.test.ts | 4 + .../server/lib/reindexing/reindex_actions.ts | 2 +- .../lib/reindexing/reindex_service.test.ts | 12 +- .../server/lib/reindexing/reindex_service.ts | 17 +- .../routes/reindex_indices/reindex_handler.ts | 14 +- .../reindex_indices/reindex_indices.test.ts | 14 +- .../routes/reindex_indices/reindex_indices.ts | 9 +- .../upgrade_assistant/index.js | 1 + .../upgrade_assistant/reindexing.js | 17 ++ .../upgrade_assistant/status.ts | 57 +++++++ 32 files changed, 596 insertions(+), 126 deletions(-) create mode 100644 x-pack/plugins/upgrade_assistant/common/get_index_state_from_cluster_state.test.ts create mode 100644 x-pack/plugins/upgrade_assistant/common/get_index_state_from_cluster_state.ts create mode 100644 x-pack/plugins/upgrade_assistant/server/lib/es_indices_state_check.ts create mode 100644 x-pack/test/upgrade_assistant_integration/upgrade_assistant/status.ts diff --git a/x-pack/plugins/upgrade_assistant/common/get_index_state_from_cluster_state.test.ts b/x-pack/plugins/upgrade_assistant/common/get_index_state_from_cluster_state.test.ts new file mode 100644 index 00000000000000..1098594a68f8a2 --- /dev/null +++ b/x-pack/plugins/upgrade_assistant/common/get_index_state_from_cluster_state.test.ts @@ -0,0 +1,53 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { getIndexStateFromClusterState } from './get_index_state_from_cluster_state'; +import { ClusterStateAPIResponse } from './types'; + +describe('getIndexStateFromClusterState', () => { + const indexName = 'indexName'; + const clusterState: ClusterStateAPIResponse = { + metadata: { + indices: {}, + cluster_coordination: {} as any, + cluster_uuid: 'test', + templates: {} as any, + }, + cluster_name: 'test', + cluster_uuid: 'test', + }; + + afterEach(() => { + clusterState.metadata.indices = {}; + }); + + it('correctly extracts state from cluster state', () => { + clusterState.metadata.indices[indexName] = { state: 'open' } as any; + clusterState.metadata.indices.aTotallyDifferentIndex = { state: 'close' } as any; + expect(getIndexStateFromClusterState(indexName, clusterState)).toBe('open'); + }); + + it('correctly extracts state from aliased index in cluster state', () => { + clusterState.metadata.indices.aTotallyDifferentName = { + state: 'close', + aliases: [indexName, 'test'], + } as any; + clusterState.metadata.indices.aTotallyDifferentName1 = { + state: 'open', + aliases: ['another', 'test'], + } as any; + + expect(getIndexStateFromClusterState(indexName, clusterState)).toBe('close'); + }); + + it('throws if the index name cannot be found in the cluster state', () => { + expect(() => getIndexStateFromClusterState(indexName, clusterState)).toThrow('not found'); + clusterState.metadata.indices.aTotallyDifferentName1 = { + state: 'open', + aliases: ['another', 'test'], + } as any; + expect(() => getIndexStateFromClusterState(indexName, clusterState)).toThrow('not found'); + }); +}); diff --git a/x-pack/plugins/upgrade_assistant/common/get_index_state_from_cluster_state.ts b/x-pack/plugins/upgrade_assistant/common/get_index_state_from_cluster_state.ts new file mode 100644 index 00000000000000..75b71fee000d4a --- /dev/null +++ b/x-pack/plugins/upgrade_assistant/common/get_index_state_from_cluster_state.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; + * you may not use this file except in compliance with the Elastic License. + */ + +import { ClusterStateAPIResponse } from './types'; + +const checkAllAliases = ( + indexName: string, + clusterState: ClusterStateAPIResponse +): 'open' | 'close' => { + for (const index of Object.values(clusterState.metadata.indices)) { + if (index.aliases?.some(alias => alias === indexName)) { + return index.state; + } + } + + throw new Error(`${indexName} not found in cluster state!`); +}; + +export const getIndexStateFromClusterState = ( + indexName: string, + clusterState: ClusterStateAPIResponse +): 'open' | 'close' => + clusterState.metadata.indices[indexName] + ? clusterState.metadata.indices[indexName].state + : checkAllAliases(indexName, clusterState); diff --git a/x-pack/plugins/upgrade_assistant/common/types.ts b/x-pack/plugins/upgrade_assistant/common/types.ts index ceb3a6dd60166f..1114e889882c26 100644 --- a/x-pack/plugins/upgrade_assistant/common/types.ts +++ b/x-pack/plugins/upgrade_assistant/common/types.ts @@ -34,6 +34,13 @@ export interface QueueSettings extends SavedObjectAttributes { } export interface ReindexOptions extends SavedObjectAttributes { + /** + * Whether to treat the index as if it were closed. This instructs the + * reindex strategy to first open the index, perform reindexing and + * then close the index again. + */ + openAndClose?: boolean; + /** * Set this key to configure a reindex operation as part of a * batch to be run in series. @@ -50,7 +57,6 @@ export interface ReindexOperation extends SavedObjectAttributes { reindexTaskId: string | null; reindexTaskPercComplete: number | null; errorMessage: string | null; - // This field is only used for the singleton IndexConsumerType documents. runningReindexCount: number | null; @@ -142,6 +148,14 @@ export interface EnrichedDeprecationInfo extends DeprecationInfo { index?: string; node?: string; reindex?: boolean; + /** + * Indicate what blockers have been detected for calling reindex + * against this index. + * + * @remark + * In future this could be an array of blockers. + */ + blockerForReindexing?: 'index-closed'; // 'index-closed' can be handled automatically, but requires more resources, user should be warned } export interface UpgradeAssistantStatus { @@ -149,3 +163,42 @@ export interface UpgradeAssistantStatus { cluster: EnrichedDeprecationInfo[]; indices: EnrichedDeprecationInfo[]; } + +export interface ClusterStateIndexAPIResponse { + state: 'open' | 'close'; + settings: { + index: { + verified_before_close: string; + search: { + throttled: string; + }; + number_of_shards: string; + provided_name: string; + frozen: string; + creation_date: string; + number_of_replicas: string; + uuid: string; + version: { + created: string; + }; + }; + }; + mappings: any; + aliases: string[]; +} + +export interface ClusterStateAPIResponse { + cluster_name: string; + cluster_uuid: string; + metadata: { + cluster_uuid: string; + cluster_coordination: { + term: number; + last_committed_config: string[]; + last_accepted_config: string[]; + voting_config_exclusions: []; + }; + templates: any; + indices: { [indexName: string]: ClusterStateIndexAPIResponse }; + }; +} diff --git a/x-pack/plugins/upgrade_assistant/public/application/app_context.tsx b/x-pack/plugins/upgrade_assistant/public/application/app_context.tsx index 1ae9dabd69481b..11c88a52ea24e0 100644 --- a/x-pack/plugins/upgrade_assistant/public/application/app_context.tsx +++ b/x-pack/plugins/upgrade_assistant/public/application/app_context.tsx @@ -3,12 +3,13 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { HttpSetup } from 'src/core/public'; +import { DocLinksStart, HttpSetup } from 'src/core/public'; import React, { createContext, useContext } from 'react'; export interface ContextValue { http: HttpSetup; isCloudEnabled: boolean; + docLinks: DocLinksStart; } export const AppContext = createContext({} as any); diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/tabs.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/tabs.tsx index 43ec5554aaaeef..77ee3448cd06de 100644 --- a/x-pack/plugins/upgrade_assistant/public/application/components/tabs.tsx +++ b/x-pack/plugins/upgrade_assistant/public/application/components/tabs.tsx @@ -14,7 +14,8 @@ import { EuiTabbedContent, EuiTabbedContentTab, } from '@elastic/eui'; -import { FormattedMessage, injectI18n } from '@kbn/i18n/react'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n/react'; import { HttpSetup } from 'src/core/public'; import { UpgradeAssistantStatus } from '../../../common/types'; @@ -38,9 +39,11 @@ interface TabsState { clusterUpgradeState: ClusterUpgradeState; } -type Props = ReactIntl.InjectedIntlProps & { http: HttpSetup }; +interface Props { + http: HttpSetup; +} -export class UpgradeAssistantTabsUI extends React.Component { +export class UpgradeAssistantTabs extends React.Component { constructor(props: Props) { super(props); this.state = { @@ -172,7 +175,6 @@ export class UpgradeAssistantTabsUI extends React.Component { }; private get tabs() { - const { intl } = this.props; const { loadingError, loadingState, checkupData } = this.state; const commonProps: UpgradeAssistantTabProps = { loadingError, @@ -186,24 +188,21 @@ export class UpgradeAssistantTabsUI extends React.Component { return [ { id: 'overview', - name: intl.formatMessage({ - id: 'xpack.upgradeAssistant.overviewTab.overviewTabTitle', + name: i18n.translate('xpack.upgradeAssistant.overviewTab.overviewTabTitle', { defaultMessage: 'Overview', }), content: , }, { id: 'cluster', - name: intl.formatMessage({ - id: 'xpack.upgradeAssistant.checkupTab.clusterTabLabel', + name: i18n.translate('xpack.upgradeAssistant.checkupTab.clusterTabLabel', { defaultMessage: 'Cluster', }), content: ( { }, { id: 'indices', - name: intl.formatMessage({ - id: 'xpack.upgradeAssistant.checkupTab.indicesTabLabel', + name: i18n.translate('xpack.upgradeAssistant.checkupTab.indicesTabLabel', { defaultMessage: 'Indices', }), content: ( { this.setState({ telemetryState: TelemetryState.Complete }); } } - -export const UpgradeAssistantTabs = injectI18n(UpgradeAssistantTabsUI); diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/cell.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/cell.tsx index 879bb695ca60af..6eaa0de530673f 100644 --- a/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/cell.tsx +++ b/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/cell.tsx @@ -18,6 +18,7 @@ import { import { FormattedMessage } from '@kbn/i18n/react'; import { ReindexButton } from './reindex'; import { AppContext } from '../../../../app_context'; +import { EnrichedDeprecationInfo } from '../../../../../../common/types'; interface DeprecationCellProps { items?: Array<{ title?: string; body: string }>; @@ -26,6 +27,7 @@ interface DeprecationCellProps { headline?: string; healthColor?: string; children?: ReactNode; + reindexBlocker?: EnrichedDeprecationInfo['blockerForReindexing']; } /** @@ -38,6 +40,7 @@ export const DeprecationCell: FunctionComponent = ({ docUrl, items = [], children, + reindexBlocker, }) => (
@@ -79,7 +82,14 @@ export const DeprecationCell: FunctionComponent = ({ {reindexIndexName && ( - {({ http }) => } + {({ http, docLinks }) => ( + + )} )} diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/index_table.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/index_table.tsx index 5506528a3ded09..19767c34a1b064 100644 --- a/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/index_table.tsx +++ b/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/index_table.tsx @@ -11,12 +11,14 @@ import { EuiBasicTable } from '@elastic/eui'; import { injectI18n } from '@kbn/i18n/react'; import { ReindexButton } from './reindex'; import { AppContext } from '../../../../app_context'; +import { EnrichedDeprecationInfo } from '../../../../../../common/types'; const PAGE_SIZES = [10, 25, 50, 100, 250, 500, 1000]; export interface IndexDeprecationDetails { index: string; reindex: boolean; + blockerForReindexing?: EnrichedDeprecationInfo['blockerForReindexing']; details?: string; } @@ -68,9 +70,10 @@ export class IndexDeprecationTableUI extends React.Component< }, ]; - if (this.actionsColumn) { - // @ts-ignore - columns.push(this.actionsColumn); + const actionsColumn = this.generateActionsColumn(); + + if (actionsColumn) { + columns.push(actionsColumn as any); } const sorting = { @@ -134,7 +137,7 @@ export class IndexDeprecationTableUI extends React.Component< return { totalItemCount, pageSizeOptions, hidePerPageOptions: false }; } - private get actionsColumn() { + private generateActionsColumn() { // NOTE: this naive implementation assumes all indices in the table are // should show the reindex button. This should work for known usecases. const { indices } = this.props; @@ -148,7 +151,16 @@ export class IndexDeprecationTableUI extends React.Component< render(indexDep: IndexDeprecationDetails) { return ( - {({ http }) => } + {({ http, docLinks }) => { + return ( + + ); + }} ); }, diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/list.test.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/list.test.tsx index a1e173737bab06..606faf52b8e2bd 100644 --- a/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/list.test.tsx +++ b/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/list.test.tsx @@ -23,29 +23,29 @@ describe('DeprecationList', () => { test('shows simple messages when index field is not present', () => { expect(shallow()).toMatchInlineSnapshot(` -
- - -
-`); +
+ + +
+ `); }); test('shows index deprecation when index field is present', () => { @@ -59,31 +59,33 @@ describe('DeprecationList', () => { }; const wrapper = shallow(); expect(wrapper).toMatchInlineSnapshot(` - -`); + + `); }); }); @@ -98,31 +100,31 @@ describe('DeprecationList', () => { test('shows detailed messages', () => { expect(shallow()).toMatchInlineSnapshot(` -
- - -
-`); +
+ + +
+ `); }); }); }); diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/list.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/list.tsx index a46bc0d12fad41..ab71972f361ea2 100644 --- a/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/list.tsx +++ b/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/list.tsx @@ -32,6 +32,7 @@ const MessageDeprecation: FunctionComponent<{ deprecation: EnrichedDeprecationIn return ( ; + return ( + + ); }; interface IndexDeprecationProps { @@ -89,6 +96,7 @@ export const DeprecationList: FunctionComponent<{ index: dep.index!, details: dep.details, reindex: dep.reindex === true, + blockerForReindexing: dep.blockerForReindexing, })); return ; } else if (currentGroupBy === GroupByOption.index) { diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/reindex/button.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/reindex/button.tsx index 30b46e0c15213e..3738e265515a0d 100644 --- a/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/reindex/button.tsx +++ b/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/reindex/button.tsx @@ -6,12 +6,17 @@ import { set } from 'lodash'; import React, { Fragment, ReactNode } from 'react'; +import { i18n } from '@kbn/i18n'; import { Subscription } from 'rxjs'; -import { EuiButton, EuiLoadingSpinner } from '@elastic/eui'; +import { EuiButton, EuiLoadingSpinner, EuiText, EuiToolTip } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; -import { HttpSetup } from 'src/core/public'; -import { ReindexStatus, UIReindexOption } from '../../../../../../../common/types'; +import { DocLinksStart, HttpSetup } from 'src/core/public'; +import { + EnrichedDeprecationInfo, + ReindexStatus, + UIReindexOption, +} from '../../../../../../../common/types'; import { LoadingState } from '../../../../types'; import { ReindexFlyout } from './flyout'; import { ReindexPollingService, ReindexState } from './polling_service'; @@ -19,6 +24,8 @@ import { ReindexPollingService, ReindexState } from './polling_service'; interface ReindexButtonProps { indexName: string; http: HttpSetup; + docLinks: DocLinksStart; + reindexBlocker?: EnrichedDeprecationInfo['blockerForReindexing']; } interface ReindexButtonState { @@ -61,7 +68,7 @@ export class ReindexButton extends React.Component{buttonContent}; + return ( - {buttonContent} + {showIndexedClosedWarning ? ( + + {i18n.translate( + 'xpack.upgradeAssistant.checkupTab.reindexing.reindexButton.indexClosedToolTipDetails', + { + defaultMessage: + '"{indexName}" needs to be reindexed, but it is currently closed. The Upgrade Assistant will open, reindex and then close the index. Reindexing may take longer than usual.', + values: { indexName }, + } + )} + + } + > + {button} + + ) : ( + button + )} {flyoutVisible && ( { onConfirmInputChange: jest.fn(), startReindex: jest.fn(), cancelReindex: jest.fn(), + renderGlobalCallouts: jest.fn(), reindexState: { loadingState: LoadingState.Success, lastCompletedStep: undefined, diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/reindex/flyout/checklist_step.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/reindex/flyout/checklist_step.tsx index e1b8f297570781..31ddaba99a896c 100644 --- a/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/reindex/flyout/checklist_step.tsx +++ b/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/reindex/flyout/checklist_step.tsx @@ -68,17 +68,19 @@ const buttonLabel = (status?: ReindexStatus) => { * Displays a flyout that shows the current reindexing status for a given index. */ export const ChecklistFlyoutStep: React.FunctionComponent<{ + renderGlobalCallouts: () => React.ReactNode; closeFlyout: () => void; reindexState: ReindexState; startReindex: () => void; cancelReindex: () => void; -}> = ({ closeFlyout, reindexState, startReindex, cancelReindex }) => { +}> = ({ closeFlyout, reindexState, startReindex, cancelReindex, renderGlobalCallouts }) => { const { loadingState, status, hasRequiredPrivileges } = reindexState; const loading = loadingState === LoadingState.Loading || status === ReindexStatus.inProgress; return ( + {renderGlobalCallouts()} void; cancelReindex: () => void; + docLinks: DocLinksStart; + reindexBlocker?: EnrichedDeprecationInfo['blockerForReindexing']; } interface ReindexFlyoutState { currentFlyoutStep: ReindexFlyoutStep; } +const getOpenAndCloseIndexDocLink = ({ ELASTIC_WEBSITE_URL, DOC_LINK_VERSION }: DocLinksStart) => ( + + {i18n.translate( + 'xpack.upgradeAssistant.checkupTab.reindexing.flyout.openAndCloseDocumentation', + { defaultMessage: 'documentation' } + )} + +); + +const getIndexClosedCallout = (docLinks: DocLinksStart) => ( + <> + +

+ + {i18n.translate( + 'xpack.upgradeAssistant.checkupTab.reindexing.flyout.indexClosedCallout.calloutDetails.reindexingTakesLongerEmphasis', + { defaultMessage: 'Reindexing may take longer than usual' } + )} + + ), + }} + /> +

+
+ + +); + /** * Wrapper for the contents of the flyout that manages which step of the flyout to show. */ @@ -48,14 +105,28 @@ export class ReindexFlyout extends React.Component globalCallout} closeFlyout={closeFlyout} warnings={reindexState.reindexWarnings!} advanceNextStep={this.advanceNextStep} @@ -65,6 +136,7 @@ export class ReindexFlyout extends React.Component globalCallout} closeFlyout={closeFlyout} reindexState={reindexState} startReindex={startReindex} diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/reindex/flyout/warning_step.test.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/reindex/flyout/warning_step.test.tsx index fddbe84e284616..318d2bc7baffef 100644 --- a/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/reindex/flyout/warning_step.test.tsx +++ b/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/reindex/flyout/warning_step.test.tsx @@ -16,6 +16,7 @@ describe('WarningsFlyoutStep', () => { advanceNextStep: jest.fn(), warnings: [ReindexWarning.allField, ReindexWarning.booleanFields], closeFlyout: jest.fn(), + renderGlobalCallouts: jest.fn(), }; it('renders', () => { diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/reindex/flyout/warnings_step.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/reindex/flyout/warnings_step.tsx index 643dd2e9b6efc6..4e296aca3d0b70 100644 --- a/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/reindex/flyout/warnings_step.tsx +++ b/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/reindex/flyout/warnings_step.tsx @@ -62,6 +62,7 @@ const WarningCheckbox: React.FunctionComponent<{ ); interface WarningsConfirmationFlyoutProps { + renderGlobalCallouts: () => React.ReactNode; closeFlyout: () => void; warnings: ReindexWarning[]; advanceNextStep: () => void; @@ -91,7 +92,7 @@ export class WarningsFlyoutStep extends React.Component< } public render() { - const { warnings, closeFlyout, advanceNextStep } = this.props; + const { warnings, closeFlyout, advanceNextStep, renderGlobalCallouts } = this.props; const { checkedIds } = this.state; // Do not allow to proceed until all checkboxes are checked. @@ -100,6 +101,7 @@ export class WarningsFlyoutStep extends React.Component< return ( + {renderGlobalCallouts()} ( `/api/upgrade_assistant/reindex/${this.indexName}` ); diff --git a/x-pack/plugins/upgrade_assistant/public/plugin.ts b/x-pack/plugins/upgrade_assistant/public/plugin.ts index 614221272dd5c4..300da4eccae156 100644 --- a/x-pack/plugins/upgrade_assistant/public/plugin.ts +++ b/x-pack/plugins/upgrade_assistant/public/plugin.ts @@ -37,8 +37,8 @@ export class UpgradeAssistantUIPlugin implements Plugin { }), order: 1000, async mount({ element }) { - const [{ i18n: i18nDep }] = await getStartServices(); - return renderApp({ element, isCloudEnabled, http, i18n: i18nDep }); + const [{ i18n: i18nDep, docLinks }] = await getStartServices(); + return renderApp({ element, isCloudEnabled, http, i18n: i18nDep, docLinks }); }, }); } diff --git a/x-pack/plugins/upgrade_assistant/server/lib/__snapshots__/es_migration_apis.test.ts.snap b/x-pack/plugins/upgrade_assistant/server/lib/__snapshots__/es_migration_apis.test.ts.snap index 10f66fd1fc01ac..244fc96acd1943 100644 --- a/x-pack/plugins/upgrade_assistant/server/lib/__snapshots__/es_migration_apis.test.ts.snap +++ b/x-pack/plugins/upgrade_assistant/server/lib/__snapshots__/es_migration_apis.test.ts.snap @@ -30,6 +30,7 @@ Object { ], "indices": Array [ Object { + "blockerForReindexing": undefined, "details": "[[type: doc, field: spins], [type: doc, field: mlockall], [type: doc, field: node_master], [type: doc, field: primary]]", "index": ".monitoring-es-6-2018.11.07", "level": "warning", @@ -38,6 +39,7 @@ Object { "url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_mappings_changes.html#_coercion_of_boolean_fields", }, Object { + "blockerForReindexing": undefined, "details": "[[type: tweet, field: liked]]", "index": "twitter", "level": "warning", @@ -46,6 +48,7 @@ Object { "url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_mappings_changes.html#_coercion_of_boolean_fields", }, Object { + "blockerForReindexing": undefined, "details": "[[type: index-pattern, field: notExpandable], [type: config, field: xPackMonitoring:allowReport], [type: config, field: xPackMonitoring:showBanner], [type: dashboard, field: pause], [type: dashboard, field: timeRestore]]", "index": ".kibana", "level": "warning", @@ -54,6 +57,7 @@ Object { "url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_mappings_changes.html#_coercion_of_boolean_fields", }, Object { + "blockerForReindexing": undefined, "details": "[[type: doc, field: notify], [type: doc, field: created], [type: doc, field: attach_payload], [type: doc, field: met]]", "index": ".watcher-history-6-2018.11.07", "level": "warning", @@ -62,6 +66,7 @@ Object { "url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_mappings_changes.html#_coercion_of_boolean_fields", }, Object { + "blockerForReindexing": undefined, "details": "[[type: doc, field: snapshot]]", "index": ".monitoring-kibana-6-2018.11.07", "level": "warning", @@ -70,6 +75,7 @@ Object { "url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_mappings_changes.html#_coercion_of_boolean_fields", }, Object { + "blockerForReindexing": undefined, "details": "[[type: tweet, field: liked]]", "index": "twitter2", "level": "warning", diff --git a/x-pack/plugins/upgrade_assistant/server/lib/es_indices_state_check.ts b/x-pack/plugins/upgrade_assistant/server/lib/es_indices_state_check.ts new file mode 100644 index 00000000000000..9931abf7f416c4 --- /dev/null +++ b/x-pack/plugins/upgrade_assistant/server/lib/es_indices_state_check.ts @@ -0,0 +1,36 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { IScopedClusterClient } from 'kibana/server'; +import { getIndexStateFromClusterState } from '../../common/get_index_state_from_cluster_state'; +import { ClusterStateAPIResponse } from '../../common/types'; + +type StatusCheckResult = Record; + +export const esIndicesStateCheck = async ( + dataClient: IScopedClusterClient, + indices: string[] +): Promise => { + // According to https://www.elastic.co/guide/en/elasticsearch/reference/7.6/cluster-state.html + // The response from this call is considered internal and subject to change. We have an API + // integration test for asserting that the current ES version still returns what we expect. + // This lives in x-pack/test/upgrade_assistant_integration + const clusterState: ClusterStateAPIResponse = await dataClient.callAsCurrentUser( + 'cluster.state', + { + index: indices, + metric: 'metadata', + } + ); + + const result: StatusCheckResult = {}; + + indices.forEach(index => { + result[index] = getIndexStateFromClusterState(index, clusterState); + }); + + return result; +}; diff --git a/x-pack/plugins/upgrade_assistant/server/lib/es_migration_apis.test.ts b/x-pack/plugins/upgrade_assistant/server/lib/es_migration_apis.test.ts index 4ab4227ba3e919..89571a4a18231a 100644 --- a/x-pack/plugins/upgrade_assistant/server/lib/es_migration_apis.test.ts +++ b/x-pack/plugins/upgrade_assistant/server/lib/es_migration_apis.test.ts @@ -15,9 +15,24 @@ describe('getUpgradeAssistantStatus', () => { let deprecationsResponse: DeprecationAPIResponse; const dataClient = elasticsearchServiceMock.createScopedClusterClient(); - (dataClient.callAsCurrentUser as jest.Mock).mockImplementation(async (api, { path }) => { + (dataClient.callAsCurrentUser as jest.Mock).mockImplementation(async (api, { path, index }) => { if (path === '/_migration/deprecations') { return deprecationsResponse; + } else if (api === 'cluster.state') { + return { + metadata: { + indices: { + ...index.reduce((acc: any, i: any) => { + return { + ...acc, + [i]: { + state: 'open', + }, + }; + }, {}), + }, + }, + }; } else if (api === 'indices.getMapping') { return {}; } else { diff --git a/x-pack/plugins/upgrade_assistant/server/lib/es_migration_apis.ts b/x-pack/plugins/upgrade_assistant/server/lib/es_migration_apis.ts index 68f21c1fd93b56..3381e5506f39a6 100644 --- a/x-pack/plugins/upgrade_assistant/server/lib/es_migration_apis.ts +++ b/x-pack/plugins/upgrade_assistant/server/lib/es_migration_apis.ts @@ -8,6 +8,8 @@ import { IScopedClusterClient } from 'src/core/server'; import { DeprecationAPIResponse } from 'src/legacy/core_plugins/elasticsearch'; import { EnrichedDeprecationInfo, UpgradeAssistantStatus } from '../../common/types'; +import { esIndicesStateCheck } from './es_indices_state_check'; + export async function getUpgradeAssistantStatus( dataClient: IScopedClusterClient, isCloudEnabled: boolean @@ -20,6 +22,19 @@ export async function getUpgradeAssistantStatus( const cluster = getClusterDeprecations(deprecations, isCloudEnabled); const indices = getCombinedIndexInfos(deprecations); + const indexNames = indices.map(({ index }) => index!); + + // If we have found deprecation information for index/indices check whether the index is + // open or closed. + if (indexNames.length) { + const indexStates = await esIndicesStateCheck(dataClient, indexNames); + + indices.forEach(indexData => { + indexData.blockerForReindexing = + indexStates[indexData.index!] === 'close' ? 'index-closed' : undefined; + }); + } + const criticalWarnings = cluster.concat(indices).filter(d => d.level === 'critical'); return { diff --git a/x-pack/plugins/upgrade_assistant/server/lib/reindexing/index_settings.ts b/x-pack/plugins/upgrade_assistant/server/lib/reindexing/index_settings.ts index f6dc471d0945d3..5722a6c29b68ff 100644 --- a/x-pack/plugins/upgrade_assistant/server/lib/reindexing/index_settings.ts +++ b/x-pack/plugins/upgrade_assistant/server/lib/reindexing/index_settings.ts @@ -88,6 +88,7 @@ const removeUnsettableSettings = (settings: FlatSettings['settings']) => 'index.routing.allocation.initial_recovery._id', 'index.version.created', 'index.version.upgraded', + 'index.verified_before_close', ]); // Use `flow` to pipe the settings through each function. diff --git a/x-pack/plugins/upgrade_assistant/server/lib/reindexing/reindex_actions.test.ts b/x-pack/plugins/upgrade_assistant/server/lib/reindexing/reindex_actions.test.ts index 4569fdfa33a83b..0a8887083c27e9 100644 --- a/x-pack/plugins/upgrade_assistant/server/lib/reindexing/reindex_actions.test.ts +++ b/x-pack/plugins/upgrade_assistant/server/lib/reindexing/reindex_actions.test.ts @@ -51,6 +51,7 @@ describe('ReindexActions', () => { expect(client.create).toHaveBeenCalledWith(REINDEX_OP_TYPE, { indexName: 'myIndex', newIndexName: `reindexed-v${CURRENT_MAJOR_VERSION}-myIndex`, + reindexOptions: undefined, status: ReindexStatus.inProgress, lastCompletedStep: ReindexStep.created, locked: null, @@ -66,6 +67,7 @@ describe('ReindexActions', () => { expect(client.create).toHaveBeenCalledWith(REINDEX_OP_TYPE, { indexName: '.internalIndex', newIndexName: `.reindexed-v${CURRENT_MAJOR_VERSION}-internalIndex`, + reindexOptions: undefined, status: ReindexStatus.inProgress, lastCompletedStep: ReindexStep.created, locked: null, @@ -83,6 +85,7 @@ describe('ReindexActions', () => { expect(client.create).toHaveBeenCalledWith(REINDEX_OP_TYPE, { indexName, newIndexName: `reindexed-v${CURRENT_MAJOR_VERSION}-myIndex`, + reindexOptions: undefined, status: ReindexStatus.inProgress, lastCompletedStep: ReindexStep.created, locked: null, @@ -98,6 +101,7 @@ describe('ReindexActions', () => { expect(client.create).toHaveBeenCalledWith(REINDEX_OP_TYPE, { indexName: `reindexed-v${PREV_MAJOR_VERSION}-myIndex`, newIndexName: `reindexed-v${CURRENT_MAJOR_VERSION}-myIndex`, + reindexOptions: undefined, status: ReindexStatus.inProgress, lastCompletedStep: ReindexStep.created, locked: null, diff --git a/x-pack/plugins/upgrade_assistant/server/lib/reindexing/reindex_actions.ts b/x-pack/plugins/upgrade_assistant/server/lib/reindexing/reindex_actions.ts index 422e78c2f12ad9..81c8f2563a66d9 100644 --- a/x-pack/plugins/upgrade_assistant/server/lib/reindexing/reindex_actions.ts +++ b/x-pack/plugins/upgrade_assistant/server/lib/reindexing/reindex_actions.ts @@ -35,7 +35,7 @@ export interface ReindexActions { /** * Creates a new reindexOp, does not perform any pre-flight checks. * @param indexName - * @param opts Options for the reindex operation + * @param opts Additional options when creating the reindex operation */ createReindexOp(indexName: string, opts?: ReindexOptions): Promise; diff --git a/x-pack/plugins/upgrade_assistant/server/lib/reindexing/reindex_service.test.ts b/x-pack/plugins/upgrade_assistant/server/lib/reindexing/reindex_service.test.ts index 886ea6761e3b73..beb7b28e05e97b 100644 --- a/x-pack/plugins/upgrade_assistant/server/lib/reindexing/reindex_service.test.ts +++ b/x-pack/plugins/upgrade_assistant/server/lib/reindexing/reindex_service.test.ts @@ -841,7 +841,11 @@ describe('reindexService', () => { describe('newIndexCreated', () => { const reindexOp = { id: '1', - attributes: { ...defaultAttributes, lastCompletedStep: ReindexStep.newIndexCreated }, + attributes: { + ...defaultAttributes, + lastCompletedStep: ReindexStep.newIndexCreated, + reindexOptions: { openAndClose: false }, + }, } as ReindexSavedObject; beforeEach(() => { @@ -957,7 +961,11 @@ describe('reindexService', () => { describe('reindexCompleted', () => { const reindexOp = { id: '1', - attributes: { ...defaultAttributes, lastCompletedStep: ReindexStep.reindexCompleted }, + attributes: { + ...defaultAttributes, + lastCompletedStep: ReindexStep.reindexCompleted, + reindexOptions: { openAndClose: false }, + }, } as ReindexSavedObject; it('switches aliases, sets as complete, and updates lastCompletedStep', async () => { diff --git a/x-pack/plugins/upgrade_assistant/server/lib/reindexing/reindex_service.ts b/x-pack/plugins/upgrade_assistant/server/lib/reindexing/reindex_service.ts index aa91b925b744b2..4cc465e1f10b9c 100644 --- a/x-pack/plugins/upgrade_assistant/server/lib/reindexing/reindex_service.ts +++ b/x-pack/plugins/upgrade_assistant/server/lib/reindexing/reindex_service.ts @@ -14,6 +14,7 @@ import { ReindexStep, ReindexWarning, } from '../../../common/types'; + import { generateNewIndexName, getReindexWarnings, @@ -52,7 +53,7 @@ export interface ReindexService { /** * Creates a new reindex operation for a given index. * @param indexName - * @param opts + * @param opts Additional options when creating a new reindex operation */ createReindexOperation(indexName: string, opts?: ReindexOptions): Promise; @@ -314,7 +315,11 @@ export const reindexServiceFactory = ( * @param reindexOp */ const startReindexing = async (reindexOp: ReindexSavedObject) => { - const { indexName } = reindexOp.attributes; + const { indexName, reindexOptions } = reindexOp.attributes; + + if (reindexOptions?.openAndClose === true) { + await callAsUser('indices.open', { index: indexName }); + } const startReindex = (await callAsUser('reindex', { refresh: true, @@ -394,7 +399,7 @@ export const reindexServiceFactory = ( * @param reindexOp */ const switchAlias = async (reindexOp: ReindexSavedObject) => { - const { indexName, newIndexName } = reindexOp.attributes; + const { indexName, newIndexName, reindexOptions } = reindexOp.attributes; const existingAliases = ( await callAsUser('indices.getAlias', { @@ -420,6 +425,10 @@ export const reindexServiceFactory = ( throw error.cannotCreateIndex(`Index aliases could not be created.`); } + if (reindexOptions?.openAndClose === true) { + await callAsUser('indices.close', { index: indexName }); + } + return actions.updateReindexOp(reindexOp, { lastCompletedStep: ReindexStep.aliasCreated, }); @@ -647,7 +656,7 @@ export const reindexServiceFactory = ( return actions.updateReindexOp(op, { status: ReindexStatus.inProgress, - reindexOptions: opts, + reindexOptions: opts ?? op.attributes.reindexOptions, }); }); }, diff --git a/x-pack/plugins/upgrade_assistant/server/routes/reindex_indices/reindex_handler.ts b/x-pack/plugins/upgrade_assistant/server/routes/reindex_indices/reindex_handler.ts index 944b4a225d4425..b7569d86795909 100644 --- a/x-pack/plugins/upgrade_assistant/server/routes/reindex_indices/reindex_handler.ts +++ b/x-pack/plugins/upgrade_assistant/server/routes/reindex_indices/reindex_handler.ts @@ -23,7 +23,10 @@ interface ReindexHandlerArgs { licensing: LicensingPluginSetup; headers: Record; credentialStore: CredentialStore; - enqueue?: boolean; + reindexOptions?: { + openAndClose?: boolean; + enqueue?: boolean; + }; } export const reindexHandler = async ({ @@ -34,7 +37,7 @@ export const reindexHandler = async ({ licensing, log, savedObjects, - enqueue, + reindexOptions, }: ReindexHandlerArgs): Promise => { const callAsCurrentUser = dataClient.callAsCurrentUser.bind(dataClient); const reindexActions = reindexActionsFactory(savedObjects, callAsCurrentUser); @@ -51,8 +54,11 @@ export const reindexHandler = async ({ const existingOp = await reindexService.findReindexOperation(indexName); - const opts: ReindexOptions | undefined = enqueue - ? { queueSettings: { queuedAt: Date.now() } } + const opts: ReindexOptions | undefined = reindexOptions + ? { + openAndClose: reindexOptions.openAndClose, + queueSettings: reindexOptions.enqueue ? { queuedAt: Date.now() } : undefined, + } : undefined; // If the reindexOp already exists and it's paused, resume it. Otherwise create a new one. diff --git a/x-pack/plugins/upgrade_assistant/server/routes/reindex_indices/reindex_indices.test.ts b/x-pack/plugins/upgrade_assistant/server/routes/reindex_indices/reindex_indices.test.ts index af4f7f436ec811..dc1516ad765609 100644 --- a/x-pack/plugins/upgrade_assistant/server/routes/reindex_indices/reindex_indices.test.ts +++ b/x-pack/plugins/upgrade_assistant/server/routes/reindex_indices/reindex_indices.test.ts @@ -20,7 +20,7 @@ const mockReindexService = { resumeReindexOperation: jest.fn(), cancelReindexing: jest.fn(), }; - +jest.mock('../../lib/es_indices_state_check', () => ({ esIndicesStateCheck: jest.fn() })); jest.mock('../../lib/es_version_precheck', () => ({ versionCheckHandlerWrapper: (a: any) => a, })); @@ -39,6 +39,7 @@ import { } from '../../../common/types'; import { credentialStoreFactory } from '../../lib/reindexing/credential_store'; import { registerReindexIndicesRoutes } from './reindex_indices'; +import { esIndicesStateCheck } from '../../lib/es_indices_state_check'; /** * Since these route callbacks are so thin, these serve simply as integration tests @@ -56,6 +57,7 @@ describe('reindex API', () => { } as any; beforeEach(() => { + (esIndicesStateCheck as jest.Mock).mockResolvedValue({}); mockRouter = createMockRouter(); routeDependencies = { credentialStore, @@ -166,7 +168,9 @@ describe('reindex API', () => { ); // It called create correctly - expect(mockReindexService.createReindexOperation).toHaveBeenCalledWith('theIndex', undefined); + expect(mockReindexService.createReindexOperation).toHaveBeenCalledWith('theIndex', { + openAndClose: false, + }); // It returned the right results expect(resp.status).toEqual(200); @@ -233,7 +237,10 @@ describe('reindex API', () => { kibanaResponseFactory ); // It called resume correctly - expect(mockReindexService.resumeReindexOperation).toHaveBeenCalledWith('theIndex', undefined); + expect(mockReindexService.resumeReindexOperation).toHaveBeenCalledWith('theIndex', { + openAndClose: false, + queueSettings: undefined, + }); expect(mockReindexService.createReindexOperation).not.toHaveBeenCalled(); // It returned the right results @@ -262,6 +269,7 @@ describe('reindex API', () => { describe('POST /api/upgrade_assistant/reindex/batch', () => { const queueSettingsArg = { + openAndClose: false, queueSettings: { queuedAt: expect.any(Number) }, }; it('creates a collection of index operations', async () => { diff --git a/x-pack/plugins/upgrade_assistant/server/routes/reindex_indices/reindex_indices.ts b/x-pack/plugins/upgrade_assistant/server/routes/reindex_indices/reindex_indices.ts index 697b73d8e10f68..0846e6c0d31d3b 100644 --- a/x-pack/plugins/upgrade_assistant/server/routes/reindex_indices/reindex_indices.ts +++ b/x-pack/plugins/upgrade_assistant/server/routes/reindex_indices/reindex_indices.ts @@ -16,6 +16,7 @@ import { LicensingPluginSetup } from '../../../../licensing/server'; import { ReindexStatus } from '../../../common/types'; import { versionCheckHandlerWrapper } from '../../lib/es_version_precheck'; +import { esIndicesStateCheck } from '../../lib/es_indices_state_check'; import { reindexServiceFactory, ReindexWorker } from '../../lib/reindexing'; import { CredentialStore } from '../../lib/reindexing/credential_store'; import { reindexActionsFactory } from '../../lib/reindexing/reindex_actions'; @@ -107,6 +108,7 @@ export function registerReindexIndicesRoutes( response ) => { const { indexName } = request.params; + const indexStates = await esIndicesStateCheck(dataClient, [indexName]); try { const result = await reindexHandler({ savedObjects: savedObjectsClient, @@ -116,6 +118,7 @@ export function registerReindexIndicesRoutes( licensing, headers: request.headers, credentialStore, + reindexOptions: { openAndClose: indexStates[indexName] === 'close' }, }); // Kick the worker on this node to immediately pickup the new reindex operation. @@ -187,6 +190,7 @@ export function registerReindexIndicesRoutes( response ) => { const { indexNames } = request.body; + const indexStates = await esIndicesStateCheck(dataClient, indexNames); const results: PostBatchResponse = { enqueued: [], errors: [], @@ -201,7 +205,10 @@ export function registerReindexIndicesRoutes( licensing, headers: request.headers, credentialStore, - enqueue: true, + reindexOptions: { + openAndClose: indexStates[indexName] === 'close', + enqueue: true, + }, }); results.enqueued.push(result); } catch (e) { diff --git a/x-pack/test/upgrade_assistant_integration/upgrade_assistant/index.js b/x-pack/test/upgrade_assistant_integration/upgrade_assistant/index.js index 196e06a61833b9..fa8c3fd99f71d6 100644 --- a/x-pack/test/upgrade_assistant_integration/upgrade_assistant/index.js +++ b/x-pack/test/upgrade_assistant_integration/upgrade_assistant/index.js @@ -9,5 +9,6 @@ export default function({ loadTestFile }) { this.tags('ciGroup7'); loadTestFile(require.resolve('./reindexing')); + loadTestFile(require.resolve('./status')); }); } diff --git a/x-pack/test/upgrade_assistant_integration/upgrade_assistant/reindexing.js b/x-pack/test/upgrade_assistant_integration/upgrade_assistant/reindexing.js index a99c02ffef23e7..d2cae9830d31ad 100644 --- a/x-pack/test/upgrade_assistant_integration/upgrade_assistant/reindexing.js +++ b/x-pack/test/upgrade_assistant_integration/upgrade_assistant/reindexing.js @@ -8,6 +8,7 @@ import expect from '@kbn/expect'; import { ReindexStatus, REINDEX_OP_TYPE } from '../../../plugins/upgrade_assistant/common/types'; import { generateNewIndexName } from '../../../plugins/upgrade_assistant/server/lib/reindexing/index_settings'; +import { getIndexStateFromClusterState } from '../../../plugins/upgrade_assistant/common/get_index_state_from_cluster_state'; export default function({ getService }) { const supertest = getService('supertest'); @@ -178,6 +179,8 @@ export default function({ getService }) { await es.indices.create({ index: test2 }); await es.indices.create({ index: test3 }); + await es.indices.close({ index: test1 }); + const result = await supertest .post(`/api/upgrade_assistant/reindex/batch`) .set('kbn-xsrf', 'xxx') @@ -187,6 +190,8 @@ export default function({ getService }) { expect(result.body.enqueued.length).to.equal(3); expect(result.body.errors.length).to.equal(0); + const [{ newIndexName: newTest1Name }] = result.body.enqueued; + await assertQueueState(test1, 3); await waitForReindexToComplete(test1); @@ -197,6 +202,18 @@ export default function({ getService }) { await waitForReindexToComplete(test3); await assertQueueState(undefined, 0); + + // Check that the closed index is still closed after reindexing + const clusterStateResponse = await es.cluster.state({ + index: newTest1Name, + metric: 'metadata', + }); + + const test1ReindexedState = getIndexStateFromClusterState( + newTest1Name, + clusterStateResponse + ); + expect(test1ReindexedState).to.be('close'); } finally { await cleanupReindex(test1); await cleanupReindex(test2); diff --git a/x-pack/test/upgrade_assistant_integration/upgrade_assistant/status.ts b/x-pack/test/upgrade_assistant_integration/upgrade_assistant/status.ts new file mode 100644 index 00000000000000..f38130aa594c19 --- /dev/null +++ b/x-pack/test/upgrade_assistant_integration/upgrade_assistant/status.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; + * you may not use this file except in compliance with the Elastic License. + */ + +import expect from '@kbn/expect'; +import { FtrProviderContext } from '../../api_integration/ftr_provider_context'; +import { getIndexStateFromClusterState } from '../../../plugins/upgrade_assistant/common/get_index_state_from_cluster_state'; + +// eslint-disable-next-line import/no-default-export +export default function({ getService }: FtrProviderContext) { + const esArchiver = getService('esArchiver'); + const es = getService('es'); + + describe('status and _cluster/state contract', () => { + beforeEach(async () => { + await es.indices.open({ index: '7.0-data' }); + }); + + afterEach(async () => { + await es.indices.open({ index: '7.0-data' }); + }); + + // According to https://www.elastic.co/guide/en/elasticsearch/reference/7.6/cluster-state.html + // The response from this call is considered internal and subject to change. We check that + // the contract has not changed in this integration test. + it('the _cluster/state endpoint is still what we expect', async () => { + await esArchiver.load('upgrade_assistant/reindex'); + await es.indices.close({ index: '7.0-data' }); + const result = await es.cluster.state({ + index: '7.0-data', + metric: 'metadata', + }); + + try { + if (getIndexStateFromClusterState('7.0-data', result.body) === 'close') { + return; + } + } catch (e) { + expect().fail( + `Can no longer access index open/closed state. Please update Upgrade Assistant checkup. (${e.message})` + ); + return; + } + expect().fail( + `The response contract for _cluster/state metadata has changed. Please update Upgrade Assistant checkup. Received ${JSON.stringify( + result, + null, + 2 + )}. + +Expected body.metadata.indices['7.0-data'].state to be "close".` + ); + }); + }); +} From 92cc04c7df0683e03173267c51b5440120cc343e Mon Sep 17 00:00:00 2001 From: James Gowdy Date: Mon, 9 Mar 2020 13:51:07 +0000 Subject: [PATCH 07/20] [ML] Refactoring anomaly detector job types (#59556) * [ML] Refactoring anomaly detector job types * removing calendar type * update validateCardinality * fixing test * updating types in functional tests * using state constants --- .../combined_job.test.ts} | 8 +- .../anomaly_detection_jobs}/combined_job.ts | 15 ++ .../types/anomaly_detection_jobs}/datafeed.ts | 9 +- .../anomaly_detection_jobs/datafeed_stats.ts | 25 ++ .../types/anomaly_detection_jobs}/index.ts | 3 + .../types/anomaly_detection_jobs}/job.ts | 12 +- .../types/anomaly_detection_jobs/job_stats.ts | 95 +++++++ .../anomaly_detection_jobs/summary_job.ts | 57 ++++ x-pack/legacy/plugins/ml/common/types/jobs.ts | 97 ------- .../legacy/plugins/ml/common/types/modules.ts | 2 +- .../plugins/ml/common/util/job_utils.d.ts | 2 +- .../job_selector/job_select_service_utils.ts | 2 +- .../components/job_selector/job_selector.tsx | 2 +- .../job_selector/use_job_selection.ts | 2 +- .../application/explorer/explorer_utils.d.ts | 2 +- .../custom_url_editor/list.test.tsx | 2 +- .../components/custom_url_editor/list.tsx | 2 +- .../components/custom_url_editor/utils.d.ts | 2 +- .../edit_job_flyout/tabs/custom_urls.tsx | 2 +- .../job_creator/advanced_job_creator.ts | 7 +- .../job_creator/categorization_job_creator.ts | 2 +- .../new_job/common/job_creator/job_creator.ts | 10 +- .../job_creator/multi_metric_job_creator.ts | 2 +- .../job_creator/population_job_creator.ts | 2 +- .../job_creator/single_metric_job_creator.ts | 7 +- .../job_creator/util/default_configs.ts | 4 +- .../common/job_creator/util/general.ts | 2 +- .../new_job/common/job_runner/job_runner.ts | 2 +- .../jobs/new_job/common/job_validator/util.ts | 2 +- .../datafeed_preview_flyout.tsx | 2 +- .../json_editor_flyout/json_editor_flyout.tsx | 2 +- .../custom_urls/custom_urls_selection.tsx | 2 +- .../advanced_view/detector_list.tsx | 2 +- .../preconfigured_job_redirect.ts | 2 +- .../jobs/new_job/pages/new_job/page.tsx | 2 +- .../jobs/new_job/recognize/page.tsx | 2 +- .../anomaly_detection_panel/actions.tsx | 2 +- .../anomaly_detection_panel.tsx | 2 +- .../anomaly_detection_panel/table.tsx | 2 +- .../anomaly_detection_panel/utils.ts | 6 +- .../application/routing/routes/explorer.tsx | 2 +- .../routing/routes/timeseriesexplorer.tsx | 2 +- .../application/services/calendar_service.ts | 2 +- .../services/forecast_service.d.ts | 2 +- .../application/services/job_service.d.ts | 2 +- .../services/ml_api_service/index.d.ts | 8 +- .../results_service/result_service_rx.ts | 2 +- .../timeseries_chart/timeseries_chart.d.ts | 4 +- .../timeseries_search_service.ts | 2 +- .../get_focus_data.ts | 2 +- .../validate_job_selection.ts | 2 +- .../models/data_recognizer/data_recognizer.ts | 5 +- .../{datafeeds.js => datafeeds.ts} | 77 ++++-- .../{error_utils.js => error_utils.ts} | 31 ++- .../job_service/{groups.js => groups.ts} | 32 ++- .../models/job_service/{index.js => index.ts} | 3 +- .../models/job_service/{jobs.js => jobs.ts} | 249 ++++++++++-------- .../job_validation/validate_cardinality.d.ts | 14 +- .../job_validation/validate_time_range.ts | 2 +- .../plugins/ml/server/routes/job_service.ts | 10 +- .../anomaly_detection/anomaly_explorer.ts | 2 +- .../anomaly_detection/single_metric_viewer.ts | 2 +- .../services/machine_learning/api.ts | 3 +- 63 files changed, 536 insertions(+), 331 deletions(-) rename x-pack/legacy/plugins/ml/common/types/{jobs.test.js => anomaly_detection_jobs/combined_job.test.ts} (59%) rename x-pack/legacy/plugins/ml/{public/application/jobs/new_job/common/job_creator/configs => common/types/anomaly_detection_jobs}/combined_job.ts (63%) rename x-pack/legacy/plugins/ml/{public/application/jobs/new_job/common/job_creator/configs => common/types/anomaly_detection_jobs}/datafeed.ts (85%) create mode 100644 x-pack/legacy/plugins/ml/common/types/anomaly_detection_jobs/datafeed_stats.ts rename x-pack/legacy/plugins/ml/{public/application/jobs/new_job/common/job_creator/configs => common/types/anomaly_detection_jobs}/index.ts (77%) rename x-pack/legacy/plugins/ml/{public/application/jobs/new_job/common/job_creator/configs => common/types/anomaly_detection_jobs}/job.ts (85%) create mode 100644 x-pack/legacy/plugins/ml/common/types/anomaly_detection_jobs/job_stats.ts create mode 100644 x-pack/legacy/plugins/ml/common/types/anomaly_detection_jobs/summary_job.ts delete mode 100644 x-pack/legacy/plugins/ml/common/types/jobs.ts rename x-pack/plugins/ml/server/models/job_service/{datafeeds.js => datafeeds.ts} (57%) rename x-pack/plugins/ml/server/models/job_service/{error_utils.js => error_utils.ts} (77%) rename x-pack/plugins/ml/server/models/job_service/{groups.js => groups.ts} (70%) rename x-pack/plugins/ml/server/models/job_service/{index.js => index.ts} (87%) rename x-pack/plugins/ml/server/models/job_service/{jobs.js => jobs.ts} (62%) diff --git a/x-pack/legacy/plugins/ml/common/types/jobs.test.js b/x-pack/legacy/plugins/ml/common/types/anomaly_detection_jobs/combined_job.test.ts similarity index 59% rename from x-pack/legacy/plugins/ml/common/types/jobs.test.js rename to x-pack/legacy/plugins/ml/common/types/anomaly_detection_jobs/combined_job.test.ts index 02a6500403cf38..dce107a4923184 100644 --- a/x-pack/legacy/plugins/ml/common/types/jobs.test.js +++ b/x-pack/legacy/plugins/ml/common/types/anomaly_detection_jobs/combined_job.test.ts @@ -4,12 +4,12 @@ * you may not use this file except in compliance with the Elastic License. */ -import jobConfigFarequote from './__mocks__/job_config_farequote'; -import { isMlJob, isMlJobs } from './jobs'; +// @ts-ignore importing JSON file +import jobConfigFarequote from '../__mocks__/job_config_farequote'; +import { isCombinedJobWithStats } from './combined_job'; describe('Types: Jobs', () => { test('Minimal integrity check.', () => { - expect(isMlJob(jobConfigFarequote)).toBe(true); - expect(isMlJobs([jobConfigFarequote])).toBe(true); + expect(isCombinedJobWithStats(jobConfigFarequote)).toBe(true); }); }); diff --git a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/common/job_creator/configs/combined_job.ts b/x-pack/legacy/plugins/ml/common/types/anomaly_detection_jobs/combined_job.ts similarity index 63% rename from x-pack/legacy/plugins/ml/public/application/jobs/new_job/common/job_creator/configs/combined_job.ts rename to x-pack/legacy/plugins/ml/common/types/anomaly_detection_jobs/combined_job.ts index 435b7696af398d..6df7643c5221fb 100644 --- a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/common/job_creator/configs/combined_job.ts +++ b/x-pack/legacy/plugins/ml/common/types/anomaly_detection_jobs/combined_job.ts @@ -6,14 +6,25 @@ import { cloneDeep } from 'lodash'; import { Datafeed } from './datafeed'; +import { DatafeedStats } from './datafeed_stats'; import { Job } from './job'; +import { JobStats } from './job_stats'; + +export type JobWithStats = Job & JobStats; +export type DatafeedWithStats = Datafeed & DatafeedStats; // in older implementations of the job config, the datafeed was placed inside the job // for convenience. export interface CombinedJob extends Job { + calendars?: string[]; datafeed_config: Datafeed; } +export interface CombinedJobWithStats extends JobWithStats { + calendars?: string[]; + datafeed_config: DatafeedWithStats; +} + export function expandCombinedJobConfig(combinedJob: CombinedJob) { const combinedJobClone = cloneDeep(combinedJob); const job = combinedJobClone; @@ -22,3 +33,7 @@ export function expandCombinedJobConfig(combinedJob: CombinedJob) { return { job, datafeed }; } + +export function isCombinedJobWithStats(arg: any): arg is CombinedJobWithStats { + return typeof arg.job_id === 'string'; +} diff --git a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/common/job_creator/configs/datafeed.ts b/x-pack/legacy/plugins/ml/common/types/anomaly_detection_jobs/datafeed.ts similarity index 85% rename from x-pack/legacy/plugins/ml/public/application/jobs/new_job/common/job_creator/configs/datafeed.ts rename to x-pack/legacy/plugins/ml/common/types/anomaly_detection_jobs/datafeed.ts index 538b225926f652..47ff618ffa77f9 100644 --- a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/common/job_creator/configs/datafeed.ts +++ b/x-pack/legacy/plugins/ml/common/types/anomaly_detection_jobs/datafeed.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { IndexPatternTitle } from '../../../../../../../common/types/kibana'; +import { IndexPatternTitle } from '../kibana'; import { JobId } from './job'; export type DatafeedId = string; @@ -15,11 +15,8 @@ export interface Datafeed { chunking_config?: ChunkingConfig; frequency?: string; indices: IndexPatternTitle[]; - /** - * The datafeed can contain indexes and indices - */ - indexes?: IndexPatternTitle[]; - job_id?: JobId; + indexes?: IndexPatternTitle[]; // The datafeed can contain indexes and indices + job_id: JobId; query: object; query_delay?: string; script_fields?: object; diff --git a/x-pack/legacy/plugins/ml/common/types/anomaly_detection_jobs/datafeed_stats.ts b/x-pack/legacy/plugins/ml/common/types/anomaly_detection_jobs/datafeed_stats.ts new file mode 100644 index 00000000000000..f1bb56b5337ab8 --- /dev/null +++ b/x-pack/legacy/plugins/ml/common/types/anomaly_detection_jobs/datafeed_stats.ts @@ -0,0 +1,25 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { Node } from './job_stats'; +import { DATAFEED_STATE } from '../../constants/states'; + +export interface DatafeedStats { + datafeed_id: string; + state: DATAFEED_STATE; + node: Node; + assignment_explanation: string; + timing_stats: TimingStats; +} + +interface TimingStats { + job_id: string; + search_count: number; + bucket_count: number; + total_search_time_ms: number; + average_search_time_per_bucket_ms: number; + exponential_average_search_time_per_hour_ms: number; +} diff --git a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/common/job_creator/configs/index.ts b/x-pack/legacy/plugins/ml/common/types/anomaly_detection_jobs/index.ts similarity index 77% rename from x-pack/legacy/plugins/ml/public/application/jobs/new_job/common/job_creator/configs/index.ts rename to x-pack/legacy/plugins/ml/common/types/anomaly_detection_jobs/index.ts index c8b71ead4b6fb4..9c299c628426a5 100644 --- a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/common/job_creator/configs/index.ts +++ b/x-pack/legacy/plugins/ml/common/types/anomaly_detection_jobs/index.ts @@ -5,5 +5,8 @@ */ export * from './job'; +export * from './job_stats'; export * from './datafeed'; +export * from './datafeed_stats'; export * from './combined_job'; +export * from './summary_job'; diff --git a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/common/job_creator/configs/job.ts b/x-pack/legacy/plugins/ml/common/types/anomaly_detection_jobs/job.ts similarity index 85% rename from x-pack/legacy/plugins/ml/public/application/jobs/new_job/common/job_creator/configs/job.ts rename to x-pack/legacy/plugins/ml/common/types/anomaly_detection_jobs/job.ts index 3246f8ae4b31ac..823d27e4617b26 100644 --- a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/common/job_creator/configs/job.ts +++ b/x-pack/legacy/plugins/ml/common/types/anomaly_detection_jobs/job.ts @@ -4,8 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -import { UrlConfig } from '../../../../../../../common/types/custom_urls'; -import { CREATED_BY_LABEL } from '../../../../../../../common/constants/new_job'; +import { UrlConfig } from '../custom_urls'; +import { CREATED_BY_LABEL } from '../../constants/new_job'; export type JobId = string; export type BucketSpan = string; @@ -29,6 +29,14 @@ export interface Job { renormalization_window_days?: number; results_index_name?: string; results_retention_days?: number; + + // optional properties added when the job has been created + create_time?: number; + finished_time?: number; + job_type?: 'anomaly_detector'; + job_version?: string; + model_snapshot_id?: string; + deleting?: boolean; } export interface AnalysisConfig { diff --git a/x-pack/legacy/plugins/ml/common/types/anomaly_detection_jobs/job_stats.ts b/x-pack/legacy/plugins/ml/common/types/anomaly_detection_jobs/job_stats.ts new file mode 100644 index 00000000000000..2d64e70bb1f782 --- /dev/null +++ b/x-pack/legacy/plugins/ml/common/types/anomaly_detection_jobs/job_stats.ts @@ -0,0 +1,95 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { JOB_STATE } from '../../constants/states'; + +export interface JobStats { + job_id: string; + data_counts: DataCounts; + model_size_stats: ModelSizeStats; + forecasts_stats: ForecastsStats; + state: JOB_STATE; + node: Node; + assignment_explanation: string; + open_time: string; + timing_stats: TimingStats; +} + +export interface DataCounts { + job_id: string; + processed_record_count: number; + processed_field_count: number; + input_bytes: number; + input_field_count: number; + invalid_date_count: number; + missing_field_count: number; + out_of_order_timestamp_count: number; + empty_bucket_count: number; + sparse_bucket_count: number; + bucket_count: number; + earliest_record_timestamp: number; + latest_record_timestamp: number; + last_data_time: number; + input_record_count: number; + latest_empty_bucket_timestamp: number; + latest_sparse_bucket_timestamp: number; + latest_bucket_timestamp?: number; // stat added by the UI +} + +export interface ModelSizeStats { + job_id: string; + result_type: string; + model_bytes: number; + model_bytes_exceeded: number; + model_bytes_memory_limit: number; + total_by_field_count: number; + total_over_field_count: number; + total_partition_field_count: number; + bucket_allocation_failures_count: number; + memory_status: 'ok' | 'soft_limit' | 'hard_limit'; + categorized_doc_count: number; + total_category_count: number; + frequent_category_count: number; + rare_category_count: number; + dead_category_count: number; + categorization_status: 'ok' | 'warn'; + log_time: number; + timestamp: number; +} + +export interface ForecastsStats { + total: number; + forecasted_jobs: number; + memory_bytes?: any; + records?: any; + processing_time_ms?: any; + status?: any; +} + +export interface Node { + id: string; + name: string; + ephemeral_id: string; + transport_address: string; + attributes: { + 'transform.remote_connect'?: boolean; + 'ml.machine_memory'?: number; + 'xpack.installed'?: boolean; + 'transform.node'?: boolean; + 'ml.max_open_jobs'?: number; + }; +} + +interface TimingStats { + job_id: string; + bucket_count: number; + total_bucket_processing_time_ms: number; + minimum_bucket_processing_time_ms: number; + maximum_bucket_processing_time_ms: number; + average_bucket_processing_time_ms: number; + exponential_average_bucket_processing_time_ms: number; + exponential_average_bucket_processing_time_per_hour_ms: number; +} diff --git a/x-pack/legacy/plugins/ml/common/types/anomaly_detection_jobs/summary_job.ts b/x-pack/legacy/plugins/ml/common/types/anomaly_detection_jobs/summary_job.ts new file mode 100644 index 00000000000000..6cf109dc553ae5 --- /dev/null +++ b/x-pack/legacy/plugins/ml/common/types/anomaly_detection_jobs/summary_job.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; + * you may not use this file except in compliance with the Elastic License. + */ + +import { Moment } from 'moment'; + +import { CombinedJob, CombinedJobWithStats } from './combined_job'; +export { Datafeed } from './datafeed'; +export { DatafeedStats } from './datafeed_stats'; + +export interface MlSummaryJob { + id: string; + description: string; + groups: string[]; + processed_record_count?: number; + memory_status?: string; + jobState: string; + datafeedIndices: string[]; + hasDatafeed: boolean; + datafeedId: string; + datafeedState: string; + latestTimestampMs?: number; + earliestTimestampMs?: number; + latestResultsTimestampMs?: number; + fullJob?: CombinedJob; + nodeName?: string; + auditMessage?: Partial; + isSingleMetricViewerJob: boolean; + deleting?: boolean; + latestTimestampSortValue?: number; +} + +export interface AuditMessage { + job_id: string; + msgTime: number; + level: number; + highestLevel: number; + highestLevelText: string; + text: string; +} + +export type MlSummaryJobs = MlSummaryJob[]; + +export interface MlJobWithTimeRange extends CombinedJobWithStats { + timeRange: { + from: number; + to: number; + fromPx: number; + toPx: number; + fromMoment: Moment; + toMoment: Moment; + widthPx: number; + label: string; + }; +} diff --git a/x-pack/legacy/plugins/ml/common/types/jobs.ts b/x-pack/legacy/plugins/ml/common/types/jobs.ts deleted file mode 100644 index a9885048550bbf..00000000000000 --- a/x-pack/legacy/plugins/ml/common/types/jobs.ts +++ /dev/null @@ -1,97 +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; - * you may not use this file except in compliance with the Elastic License. - */ - -import { Moment } from 'moment'; - -// TS TODO: This is not yet a fully fledged representation of the job data structure, -// but it fulfills some basic TypeScript related needs. -export interface MlJob { - analysis_config: { - bucket_span: string; - detectors: object[]; - influencers: string[]; - }; - analysis_limits: { - categorization_examples_limit: number; - model_memory_limit: string; - }; - create_time: number; - custom_settings: object; - data_counts: { - earliest_record_timestamp: number; - latest_record_timestamp: number; - }; - data_description: { - time_field: string; - time_format: string; - }; - datafeed_config: object; - description: string; - established_model_memory: number; - finished_time: number; - job_id: string; - job_type: string; - job_version: string; - model_plot_config: object; - model_size_stats: object; - model_snapshot_id: string; - model_snapshot_min_version: string; - model_snapshot_retention_days: number; - results_index_name: string; - state: string; -} - -export interface MlSummaryJob { - id: string; - description: string; - groups: string[]; - processed_record_count: number; - memory_status?: string; - jobState: string; - hasDatafeed: boolean; - datafeedId?: string; - datafeedIndices: any[]; - datafeedState?: string; - latestTimestampMs: number; - earliestTimestampMs?: number; - latestResultsTimestampMs: number; - isSingleMetricViewerJob: boolean; - nodeName?: string; - deleting?: boolean; - fullJob?: any; - auditMessage?: any; - latestTimestampSortValue?: number; -} - -export type MlSummaryJobs = MlSummaryJob[]; - -export interface MlJobWithTimeRange extends MlJob { - groups: string[]; - timeRange: { - from: number; - to: number; - fromPx: number; - toPx: number; - fromMoment: Moment; - toMoment: Moment; - widthPx: number; - label: string; - }; -} - -export function isMlJob(arg: any): arg is MlJob { - return typeof arg.job_id === 'string'; -} - -// eslint-disable-next-line @typescript-eslint/no-empty-interface -export interface MlJobs extends Array {} - -export function isMlJobs(arg: any): arg is MlJobs { - if (Array.isArray(arg) === false) { - return false; - } - return arg.every((d: MlJob) => isMlJob(d)); -} diff --git a/x-pack/legacy/plugins/ml/common/types/modules.ts b/x-pack/legacy/plugins/ml/common/types/modules.ts index 3e1a2cf9ab2e62..87e19d09da30c4 100644 --- a/x-pack/legacy/plugins/ml/common/types/modules.ts +++ b/x-pack/legacy/plugins/ml/common/types/modules.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ import { SavedObjectAttributes } from 'src/core/public'; -import { Datafeed, Job } from '../../public/application/jobs/new_job/common/job_creator/configs'; +import { Datafeed, Job } from '../types/anomaly_detection_jobs'; export interface ModuleJob { id: string; diff --git a/x-pack/legacy/plugins/ml/common/util/job_utils.d.ts b/x-pack/legacy/plugins/ml/common/util/job_utils.d.ts index 7dcd4b20fe0bf0..bfad422e0ab48d 100644 --- a/x-pack/legacy/plugins/ml/common/util/job_utils.d.ts +++ b/x-pack/legacy/plugins/ml/common/util/job_utils.d.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Job } from '../../public/application/jobs/new_job/common/job_creator/configs'; +import { Job } from '../types/anomaly_detection_jobs'; export interface ValidationMessage { id: string; diff --git a/x-pack/legacy/plugins/ml/public/application/components/job_selector/job_select_service_utils.ts b/x-pack/legacy/plugins/ml/public/application/components/job_selector/job_select_service_utils.ts index 1484f0a391b671..3a215f8cfb46d3 100644 --- a/x-pack/legacy/plugins/ml/public/application/components/job_selector/job_select_service_utils.ts +++ b/x-pack/legacy/plugins/ml/public/application/components/job_selector/job_select_service_utils.ts @@ -9,7 +9,7 @@ import moment from 'moment'; import d3 from 'd3'; import { Dictionary } from '../../../../common/types/common'; -import { MlJobWithTimeRange } from '../../../../common/types/jobs'; +import { MlJobWithTimeRange } from '../../../../common/types/anomaly_detection_jobs'; export function getGroupsFromJobs(jobs: MlJobWithTimeRange[]) { const groups: Dictionary = {}; diff --git a/x-pack/legacy/plugins/ml/public/application/components/job_selector/job_selector.tsx b/x-pack/legacy/plugins/ml/public/application/components/job_selector/job_selector.tsx index bd2ec2d1511a30..bd75b7be4d5ef7 100644 --- a/x-pack/legacy/plugins/ml/public/application/components/job_selector/job_selector.tsx +++ b/x-pack/legacy/plugins/ml/public/application/components/job_selector/job_selector.tsx @@ -24,7 +24,7 @@ import { i18n } from '@kbn/i18n'; import { useMlKibana } from '../../contexts/kibana'; import { Dictionary } from '../../../../common/types/common'; -import { MlJobWithTimeRange } from '../../../../common/types/jobs'; +import { MlJobWithTimeRange } from '../../../../common/types/anomaly_detection_jobs'; import { ml } from '../../services/ml_api_service'; import { useUrlState } from '../../util/url_state'; // @ts-ignore diff --git a/x-pack/legacy/plugins/ml/public/application/components/job_selector/use_job_selection.ts b/x-pack/legacy/plugins/ml/public/application/components/job_selector/use_job_selection.ts index 214bb909173026..d3fad9ae6bc2c8 100644 --- a/x-pack/legacy/plugins/ml/public/application/components/job_selector/use_job_selection.ts +++ b/x-pack/legacy/plugins/ml/public/application/components/job_selector/use_job_selection.ts @@ -10,7 +10,7 @@ import { useEffect } from 'react'; import { i18n } from '@kbn/i18n'; import { getToastNotifications } from '../../util/dependency_cache'; -import { MlJobWithTimeRange } from '../../../../common/types/jobs'; +import { MlJobWithTimeRange } from '../../../../common/types/anomaly_detection_jobs'; import { useUrlState } from '../../util/url_state'; diff --git a/x-pack/legacy/plugins/ml/public/application/explorer/explorer_utils.d.ts b/x-pack/legacy/plugins/ml/public/application/explorer/explorer_utils.d.ts index c60b2d55d86862..2d3c9a389110a8 100644 --- a/x-pack/legacy/plugins/ml/public/application/explorer/explorer_utils.d.ts +++ b/x-pack/legacy/plugins/ml/public/application/explorer/explorer_utils.d.ts @@ -6,7 +6,7 @@ import { Moment } from 'moment'; -import { CombinedJob } from '../jobs/new_job/common/job_creator/configs'; +import { CombinedJob } from '../../../common/types/anomaly_detection_jobs'; import { TimeBucketsInterval } from '../util/time_buckets'; diff --git a/x-pack/legacy/plugins/ml/public/application/jobs/components/custom_url_editor/list.test.tsx b/x-pack/legacy/plugins/ml/public/application/jobs/components/custom_url_editor/list.test.tsx index 42ee18bafd8706..923d3bf5ce59f9 100644 --- a/x-pack/legacy/plugins/ml/public/application/jobs/components/custom_url_editor/list.test.tsx +++ b/x-pack/legacy/plugins/ml/public/application/jobs/components/custom_url_editor/list.test.tsx @@ -6,7 +6,7 @@ import React from 'react'; import { shallow } from 'enzyme'; -import { Job } from '../../new_job/common/job_creator/configs'; +import { Job } from '../../../../../common/types/anomaly_detection_jobs'; import { CustomUrlList, CustomUrlListProps } from './list'; diff --git a/x-pack/legacy/plugins/ml/public/application/jobs/components/custom_url_editor/list.tsx b/x-pack/legacy/plugins/ml/public/application/jobs/components/custom_url_editor/list.tsx index 4c0956a46d669f..1b18afaf2569fa 100644 --- a/x-pack/legacy/plugins/ml/public/application/jobs/components/custom_url_editor/list.tsx +++ b/x-pack/legacy/plugins/ml/public/application/jobs/components/custom_url_editor/list.tsx @@ -25,7 +25,7 @@ import { getTestUrl } from './utils'; import { parseInterval } from '../../../../../common/util/parse_interval'; import { TIME_RANGE_TYPE } from './constants'; import { UrlConfig, KibanaUrlConfig } from '../../../../../common/types/custom_urls'; -import { Job } from '../../new_job/common/job_creator/configs'; +import { Job } from '../../../../../common/types/anomaly_detection_jobs'; function isValidTimeRange(timeRange: KibanaUrlConfig['time_range']): boolean { // Allow empty timeRange string, which gives the 'auto' behaviour. diff --git a/x-pack/legacy/plugins/ml/public/application/jobs/components/custom_url_editor/utils.d.ts b/x-pack/legacy/plugins/ml/public/application/jobs/components/custom_url_editor/utils.d.ts index cce0c7c29912c9..8ae61a01d5e424 100644 --- a/x-pack/legacy/plugins/ml/public/application/jobs/components/custom_url_editor/utils.d.ts +++ b/x-pack/legacy/plugins/ml/public/application/jobs/components/custom_url_editor/utils.d.ts @@ -6,7 +6,7 @@ import { IIndexPattern } from 'src/plugins/data/common'; import { UrlConfig } from '../../../../../common/types/custom_urls'; -import { Job } from '../../new_job/common/job_creator/configs'; +import { Job } from '../../../../../common/types/anomaly_detection_jobs'; import { TimeRangeType } from './constants'; export interface TimeRange { diff --git a/x-pack/legacy/plugins/ml/public/application/jobs/jobs_list/components/edit_job_flyout/tabs/custom_urls.tsx b/x-pack/legacy/plugins/ml/public/application/jobs/jobs_list/components/edit_job_flyout/tabs/custom_urls.tsx index fe6f72fd10279c..dd85934d11150b 100644 --- a/x-pack/legacy/plugins/ml/public/application/jobs/jobs_list/components/edit_job_flyout/tabs/custom_urls.tsx +++ b/x-pack/legacy/plugins/ml/public/application/jobs/jobs_list/components/edit_job_flyout/tabs/custom_urls.tsx @@ -35,7 +35,7 @@ import { import { withKibana } from '../../../../../../../../../../../src/plugins/kibana_react/public'; import { loadSavedDashboards, loadIndexPatterns } from '../edit_utils'; import { openCustomUrlWindow } from '../../../../../util/custom_url_utils'; -import { Job } from '../../../../new_job/common/job_creator/configs'; +import { Job } from '../../../../../../../common/types/anomaly_detection_jobs'; import { UrlConfig } from '../../../../../../../common/types/custom_urls'; import { IIndexPattern } from '../../../../../../../../../../../src/plugins/data/common/index_patterns'; import { MlKibanaReactContextValue } from '../../../../../contexts/kibana'; diff --git a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/common/job_creator/advanced_job_creator.ts b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/common/job_creator/advanced_job_creator.ts index 4530c00c105355..099f587caa0514 100644 --- a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/common/job_creator/advanced_job_creator.ts +++ b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/common/job_creator/advanced_job_creator.ts @@ -8,7 +8,12 @@ import { SavedSearchSavedObject } from '../../../../../../common/types/kibana'; import { JobCreator } from './job_creator'; import { Field, Aggregation, SplitField } from '../../../../../../common/types/fields'; -import { Job, Datafeed, Detector, CustomRule } from './configs'; +import { + Job, + Datafeed, + Detector, + CustomRule, +} from '../../../../../../common/types/anomaly_detection_jobs'; import { createBasicDetector } from './util/default_configs'; import { JOB_TYPE } from '../../../../../../common/constants/new_job'; import { getRichDetectors } from './util/general'; diff --git a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/common/job_creator/categorization_job_creator.ts b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/common/job_creator/categorization_job_creator.ts index eb2f0f4e368a34..25ec8becd9a054 100644 --- a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/common/job_creator/categorization_job_creator.ts +++ b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/common/job_creator/categorization_job_creator.ts @@ -9,7 +9,7 @@ import { IndexPattern } from '../../../../../../../../../../src/plugins/data/pub import { SavedSearchSavedObject } from '../../../../../../common/types/kibana'; import { JobCreator } from './job_creator'; import { Field, Aggregation, mlCategory } from '../../../../../../common/types/fields'; -import { Job, Datafeed, Detector } from './configs'; +import { Job, Datafeed, Detector } from '../../../../../../common/types/anomaly_detection_jobs'; import { createBasicDetector } from './util/default_configs'; import { JOB_TYPE, diff --git a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/common/job_creator/job_creator.ts b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/common/job_creator/job_creator.ts index 5b33aa35569809..98b8a7904eda97 100644 --- a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/common/job_creator/job_creator.ts +++ b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/common/job_creator/job_creator.ts @@ -9,7 +9,15 @@ import { UrlConfig } from '../../../../../../common/types/custom_urls'; import { IndexPatternTitle } from '../../../../../../common/types/kibana'; import { ML_JOB_AGGREGATION } from '../../../../../../common/constants/aggregation_types'; import { ES_FIELD_TYPES } from '../../../../../../../../../../src/plugins/data/public'; -import { Job, Datafeed, Detector, JobId, DatafeedId, BucketSpan, CustomSettings } from './configs'; +import { + Job, + Datafeed, + Detector, + JobId, + DatafeedId, + BucketSpan, + CustomSettings, +} from '../../../../../../common/types/anomaly_detection_jobs'; import { Aggregation, Field } from '../../../../../../common/types/fields'; import { createEmptyJob, createEmptyDatafeed } from './util/default_configs'; import { mlJobService } from '../../../../services/job_service'; diff --git a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/common/job_creator/multi_metric_job_creator.ts b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/common/job_creator/multi_metric_job_creator.ts index 7c5fba028d9e8f..120eee984a10b1 100644 --- a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/common/job_creator/multi_metric_job_creator.ts +++ b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/common/job_creator/multi_metric_job_creator.ts @@ -12,7 +12,7 @@ import { SplitField, AggFieldPair, } from '../../../../../../common/types/fields'; -import { Job, Datafeed, Detector } from './configs'; +import { Job, Datafeed, Detector } from '../../../../../../common/types/anomaly_detection_jobs'; import { createBasicDetector } from './util/default_configs'; import { JOB_TYPE, diff --git a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/common/job_creator/population_job_creator.ts b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/common/job_creator/population_job_creator.ts index 3009d68ca67caa..032ebc202142d4 100644 --- a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/common/job_creator/population_job_creator.ts +++ b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/common/job_creator/population_job_creator.ts @@ -12,7 +12,7 @@ import { SplitField, AggFieldPair, } from '../../../../../../common/types/fields'; -import { Job, Datafeed, Detector } from './configs'; +import { Job, Datafeed, Detector } from '../../../../../../common/types/anomaly_detection_jobs'; import { createBasicDetector } from './util/default_configs'; import { JOB_TYPE, CREATED_BY_LABEL } from '../../../../../../common/constants/new_job'; import { getRichDetectors } from './util/general'; diff --git a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/common/job_creator/single_metric_job_creator.ts b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/common/job_creator/single_metric_job_creator.ts index 9f3500185c2bfa..b16d624cff463d 100644 --- a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/common/job_creator/single_metric_job_creator.ts +++ b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/common/job_creator/single_metric_job_creator.ts @@ -8,7 +8,12 @@ import { SavedSearchSavedObject } from '../../../../../../common/types/kibana'; import { parseInterval } from '../../../../../../common/util/parse_interval'; import { JobCreator } from './job_creator'; import { Field, Aggregation, AggFieldPair } from '../../../../../../common/types/fields'; -import { Job, Datafeed, Detector, BucketSpan } from './configs'; +import { + Job, + Datafeed, + Detector, + BucketSpan, +} from '../../../../../../common/types/anomaly_detection_jobs'; import { createBasicDetector } from './util/default_configs'; import { ML_JOB_AGGREGATION, diff --git a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/common/job_creator/util/default_configs.ts b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/common/job_creator/util/default_configs.ts index 1160401478ab7e..306fd82dc87582 100644 --- a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/common/job_creator/util/default_configs.ts +++ b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/common/job_creator/util/default_configs.ts @@ -4,10 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Job, Datafeed } from '../configs'; import { IndexPatternTitle } from '../../../../../../../common/types/kibana'; import { Field, Aggregation, EVENT_RATE_FIELD_ID } from '../../../../../../../common/types/fields'; -import { Detector } from '../configs'; +import { Job, Datafeed, Detector } from '../../../../../../../common/types/anomaly_detection_jobs'; export function createEmptyJob(): Job { return { @@ -28,6 +27,7 @@ export function createEmptyJob(): Job { export function createEmptyDatafeed(indexPatternTitle: IndexPatternTitle): Datafeed { return { datafeed_id: '', + job_id: '', indices: [indexPatternTitle], query: {}, }; diff --git a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/common/job_creator/util/general.ts b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/common/job_creator/util/general.ts index 0764e276d635ea..b07d55c5b85165 100644 --- a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/common/job_creator/util/general.ts +++ b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/common/job_creator/util/general.ts @@ -5,7 +5,7 @@ */ import { i18n } from '@kbn/i18n'; -import { Job, Datafeed, Detector } from '../configs'; +import { Job, Datafeed, Detector } from '../../../../../../../common/types/anomaly_detection_jobs'; import { newJobCapsService } from '../../../../../services/new_job_capabilities_service'; import { ML_JOB_AGGREGATION, diff --git a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/common/job_runner/job_runner.ts b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/common/job_runner/job_runner.ts index 9627d2e4775281..2571fe70f4a830 100644 --- a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/common/job_runner/job_runner.ts +++ b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/common/job_runner/job_runner.ts @@ -8,7 +8,7 @@ import { BehaviorSubject } from 'rxjs'; import { ml } from '../../../../services/ml_api_service'; import { mlJobService } from '../../../../services/job_service'; import { JobCreator } from '../job_creator'; -import { DatafeedId, JobId } from '../job_creator/configs'; +import { DatafeedId, JobId } from '../../../../../../common/types/anomaly_detection_jobs'; import { DATAFEED_STATE } from '../../../../../../common/constants/states'; const REFRESH_INTERVAL_MS = 100; diff --git a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/common/job_validator/util.ts b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/common/job_validator/util.ts index ab33afb23ef514..39e3ef2ae0007b 100644 --- a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/common/job_validator/util.ts +++ b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/common/job_validator/util.ts @@ -6,7 +6,7 @@ import { i18n } from '@kbn/i18n'; import { BasicValidations } from './job_validator'; -import { Job, Datafeed } from '../job_creator/configs'; +import { Job, Datafeed } from '../../../../../../common/types/anomaly_detection_jobs'; import { ALLOWED_DATA_UNITS, JOB_ID_MAX_LENGTH, diff --git a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/common/datafeed_preview_flyout/datafeed_preview_flyout.tsx b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/common/datafeed_preview_flyout/datafeed_preview_flyout.tsx index 7f5d2bfbe0e90c..03be38adfbbe0c 100644 --- a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/common/datafeed_preview_flyout/datafeed_preview_flyout.tsx +++ b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/common/datafeed_preview_flyout/datafeed_preview_flyout.tsx @@ -18,7 +18,7 @@ import { EuiSpacer, EuiLoadingSpinner, } from '@elastic/eui'; -import { CombinedJob } from '../../../../common/job_creator/configs'; +import { CombinedJob } from '../../../../../../../../common/types/anomaly_detection_jobs'; import { MLJobEditor } from '../../../../../jobs_list/components/ml_job_editor'; import { JobCreatorContext } from '../../job_creator_context'; import { mlJobService } from '../../../../../../services/job_service'; diff --git a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/common/json_editor_flyout/json_editor_flyout.tsx b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/common/json_editor_flyout/json_editor_flyout.tsx index 061d92b54472c7..dd5c8aa3e280a1 100644 --- a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/common/json_editor_flyout/json_editor_flyout.tsx +++ b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/common/json_editor_flyout/json_editor_flyout.tsx @@ -19,7 +19,7 @@ import { EuiSpacer, } from '@elastic/eui'; import { collapseLiteralStrings } from '../../../../../../../../shared_imports'; -import { Datafeed } from '../../../../common/job_creator/configs'; +import { Datafeed } from '../../../../../../../../common/types/anomaly_detection_jobs'; import { ML_EDITOR_MODE, MLJobEditor } from '../../../../../jobs_list/components/ml_job_editor'; import { isValidJson } from '../../../../../../../../common/util/validation_utils'; import { JobCreatorContext } from '../../job_creator_context'; diff --git a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/job_details_step/components/additional_section/components/custom_urls/custom_urls_selection.tsx b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/job_details_step/components/additional_section/components/custom_urls/custom_urls_selection.tsx index cd580e60c08438..2e5135d4d8840e 100644 --- a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/job_details_step/components/additional_section/components/custom_urls/custom_urls_selection.tsx +++ b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/job_details_step/components/additional_section/components/custom_urls/custom_urls_selection.tsx @@ -9,7 +9,7 @@ import { CustomUrls } from '../../../../../../../../jobs_list/components/edit_jo import { UrlConfig } from '../../../../../../../../../../../common/types/custom_urls'; import { JobCreatorContext } from '../../../../../job_creator_context'; import { Description } from './description'; -import { CombinedJob } from '../../../../../../../common/job_creator/configs'; +import { CombinedJob } from '../../../../../../../../../../../common/types/anomaly_detection_jobs'; export const CustomUrlsSelection: FC = () => { const { jobCreator, jobCreatorUpdate } = useContext(JobCreatorContext); diff --git a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/advanced_view/detector_list.tsx b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/advanced_view/detector_list.tsx index f996a0e9728bae..38903dd4845a67 100644 --- a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/advanced_view/detector_list.tsx +++ b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/advanced_view/detector_list.tsx @@ -25,7 +25,7 @@ import { JobCreatorContext } from '../../../job_creator_context'; import { AdvancedJobCreator } from '../../../../../common/job_creator'; import { Validation } from '../../../../../common/job_validator'; import { detectorToString } from '../../../../../../../util/string_utils'; -import { Detector } from '../../../../../common/job_creator/configs'; +import { Detector } from '../../../../../../../../../common/types/anomaly_detection_jobs'; interface Props { isActive: boolean; diff --git a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/index_or_search/preconfigured_job_redirect.ts b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/index_or_search/preconfigured_job_redirect.ts index 51fc226751ae2a..d1a6ca7c19a240 100644 --- a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/index_or_search/preconfigured_job_redirect.ts +++ b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/index_or_search/preconfigured_job_redirect.ts @@ -7,7 +7,7 @@ import { IndexPatternsContract } from '../../../../../../../../../../src/plugins/data/public'; import { mlJobService } from '../../../../services/job_service'; import { loadIndexPatterns, getIndexPatternIdFromName } from '../../../../util/index_utils'; -import { CombinedJob } from '../../common/job_creator/configs'; +import { CombinedJob } from '../../../../../../common/types/anomaly_detection_jobs'; import { CREATED_BY_LABEL, JOB_TYPE } from '../../../../../../common/constants/new_job'; export async function preConfiguredJobRedirect(indexPatterns: IndexPatternsContract) { diff --git a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/new_job/page.tsx b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/new_job/page.tsx index b2383b6c08a587..d851559815de8e 100644 --- a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/new_job/page.tsx +++ b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/new_job/page.tsx @@ -37,7 +37,7 @@ import { useMlContext } from '../../../../contexts/ml'; import { getTimeFilterRange } from '../../../../components/full_time_range_selector'; import { TimeBuckets } from '../../../../util/time_buckets'; import { ExistingJobsAndGroups, mlJobService } from '../../../../services/job_service'; -import { expandCombinedJobConfig } from '../../common/job_creator/configs'; +import { expandCombinedJobConfig } from '../../../../../../common/types/anomaly_detection_jobs'; import { newJobCapsService } from '../../../../services/new_job_capabilities_service'; import { EVENT_RATE_FIELD_ID } from '../../../../../../common/types/fields'; import { getNewJobDefaults } from '../../../../services/ml_server_info'; diff --git a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/recognize/page.tsx b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/recognize/page.tsx index 8571ae43da587f..ac7a2093d1f81e 100644 --- a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/recognize/page.tsx +++ b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/recognize/page.tsx @@ -41,7 +41,7 @@ import { ModuleJobs } from './components/module_jobs'; import { checkForSavedObjects } from './resolvers'; import { JobSettingsForm, JobSettingsFormValues } from './components/job_settings_form'; import { TimeRange } from '../common/components'; -import { JobId } from '../common/job_creator/configs'; +import { JobId } from '../../../../../common/types/anomaly_detection_jobs'; export interface ModuleJobUI extends ModuleJob { datafeedResult?: DatafeedResponse; diff --git a/x-pack/legacy/plugins/ml/public/application/overview/components/anomaly_detection_panel/actions.tsx b/x-pack/legacy/plugins/ml/public/application/overview/components/anomaly_detection_panel/actions.tsx index f638094cfb4349..3a64c623dd1298 100644 --- a/x-pack/legacy/plugins/ml/public/application/overview/components/anomaly_detection_panel/actions.tsx +++ b/x-pack/legacy/plugins/ml/public/application/overview/components/anomaly_detection_panel/actions.tsx @@ -9,7 +9,7 @@ import { EuiToolTip, EuiButtonEmpty } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; // @ts-ignore no module file import { getLink } from '../../../jobs/jobs_list/components/job_actions/results'; -import { MlSummaryJobs } from '../../../../../common/types/jobs'; +import { MlSummaryJobs } from '../../../../../common/types/anomaly_detection_jobs'; interface Props { jobsList: MlSummaryJobs; diff --git a/x-pack/legacy/plugins/ml/public/application/overview/components/anomaly_detection_panel/anomaly_detection_panel.tsx b/x-pack/legacy/plugins/ml/public/application/overview/components/anomaly_detection_panel/anomaly_detection_panel.tsx index cda03b21b0d656..5f5c3f7c28670a 100644 --- a/x-pack/legacy/plugins/ml/public/application/overview/components/anomaly_detection_panel/anomaly_detection_panel.tsx +++ b/x-pack/legacy/plugins/ml/public/application/overview/components/anomaly_detection_panel/anomaly_detection_panel.tsx @@ -21,7 +21,7 @@ import { AnomalyDetectionTable } from './table'; import { ml } from '../../../services/ml_api_service'; import { getGroupsFromJobs, getStatsBarData, getJobsWithTimerange } from './utils'; import { Dictionary } from '../../../../../common/types/common'; -import { MlSummaryJobs, MlSummaryJob } from '../../../../../common/types/jobs'; +import { MlSummaryJobs, MlSummaryJob } from '../../../../../common/types/anomaly_detection_jobs'; export type GroupsDictionary = Dictionary; diff --git a/x-pack/legacy/plugins/ml/public/application/overview/components/anomaly_detection_panel/table.tsx b/x-pack/legacy/plugins/ml/public/application/overview/components/anomaly_detection_panel/table.tsx index cca86e2fc0e3ed..cd45721d54a684 100644 --- a/x-pack/legacy/plugins/ml/public/application/overview/components/anomaly_detection_panel/table.tsx +++ b/x-pack/legacy/plugins/ml/public/application/overview/components/anomaly_detection_panel/table.tsx @@ -27,7 +27,7 @@ import { formatHumanReadableDateTimeSeconds } from '../../../util/date_utils'; import { ExplorerLink } from './actions'; import { getJobsFromGroup } from './utils'; import { GroupsDictionary, Group } from './anomaly_detection_panel'; -import { MlSummaryJobs } from '../../../../../common/types/jobs'; +import { MlSummaryJobs } from '../../../../../common/types/anomaly_detection_jobs'; import { StatsBar, JobStatsBarStats } from '../../../components/stats_bar'; // @ts-ignore import { JobSelectorBadge } from '../../../components/job_selector/job_selector_badge/index'; diff --git a/x-pack/legacy/plugins/ml/public/application/overview/components/anomaly_detection_panel/utils.ts b/x-pack/legacy/plugins/ml/public/application/overview/components/anomaly_detection_panel/utils.ts index 01848bad2670ef..eab40c0f577f86 100644 --- a/x-pack/legacy/plugins/ml/public/application/overview/components/anomaly_detection_panel/utils.ts +++ b/x-pack/legacy/plugins/ml/public/application/overview/components/anomaly_detection_panel/utils.ts @@ -7,7 +7,7 @@ import { i18n } from '@kbn/i18n'; import { JOB_STATE, DATAFEED_STATE } from '../../../../../common/constants/states'; import { Group, GroupsDictionary } from './anomaly_detection_panel'; -import { MlSummaryJobs, MlSummaryJob } from '../../../../../common/types/jobs'; +import { MlSummaryJobs, MlSummaryJob } from '../../../../../common/types/anomaly_detection_jobs'; export function getGroupsFromJobs( jobs: MlSummaryJobs @@ -43,7 +43,7 @@ export function getGroupsFromJobs( // if incoming job latest timestamp is greater than the last saved one, replace it if (groups[g].latest_timestamp === undefined) { groups[g].latest_timestamp = job.latestTimestampMs; - } else if (job.latestTimestampMs > groups[g].latest_timestamp) { + } else if (job.latestTimestampMs && job.latestTimestampMs > groups[g].latest_timestamp) { groups[g].latest_timestamp = job.latestTimestampMs; } } @@ -53,7 +53,7 @@ export function getGroupsFromJobs( groups.ungrouped.docs_processed += job.processed_record_count; groups.ungrouped.jobs_in_group++; // if incoming job latest timestamp is greater than the last saved one, replace it - if (job.latestTimestampMs > groups.ungrouped.latest_timestamp) { + if (job.latestTimestampMs && job.latestTimestampMs > groups.ungrouped.latest_timestamp) { groups.ungrouped.latest_timestamp = job.latestTimestampMs; } } diff --git a/x-pack/legacy/plugins/ml/public/application/routing/routes/explorer.tsx b/x-pack/legacy/plugins/ml/public/application/routing/routes/explorer.tsx index 2c6726338d2f14..2e355c6073abd9 100644 --- a/x-pack/legacy/plugins/ml/public/application/routing/routes/explorer.tsx +++ b/x-pack/legacy/plugins/ml/public/application/routing/routes/explorer.tsx @@ -9,7 +9,7 @@ import useObservable from 'react-use/lib/useObservable'; import { i18n } from '@kbn/i18n'; -import { MlJobWithTimeRange } from '../../../../common/types/jobs'; +import { MlJobWithTimeRange } from '../../../../common/types/anomaly_detection_jobs'; import { MlRoute, PageLoader, PageProps } from '../router'; import { useRefresh } from '../use_refresh'; diff --git a/x-pack/legacy/plugins/ml/public/application/routing/routes/timeseriesexplorer.tsx b/x-pack/legacy/plugins/ml/public/application/routing/routes/timeseriesexplorer.tsx index f8a6f6c454fc06..a41a6c83615d39 100644 --- a/x-pack/legacy/plugins/ml/public/application/routing/routes/timeseriesexplorer.tsx +++ b/x-pack/legacy/plugins/ml/public/application/routing/routes/timeseriesexplorer.tsx @@ -11,7 +11,7 @@ import moment from 'moment'; import { i18n } from '@kbn/i18n'; -import { MlJobWithTimeRange } from '../../../../common/types/jobs'; +import { MlJobWithTimeRange } from '../../../../common/types/anomaly_detection_jobs'; import { TimeSeriesExplorer } from '../../timeseriesexplorer'; import { getDateFormatTz, TimeRangeBounds } from '../../explorer/explorer_utils'; diff --git a/x-pack/legacy/plugins/ml/public/application/services/calendar_service.ts b/x-pack/legacy/plugins/ml/public/application/services/calendar_service.ts index f17c9f359c7b57..98001fce1cf71d 100644 --- a/x-pack/legacy/plugins/ml/public/application/services/calendar_service.ts +++ b/x-pack/legacy/plugins/ml/public/application/services/calendar_service.ts @@ -7,7 +7,7 @@ import { i18n } from '@kbn/i18n'; import { ml } from './ml_api_service'; import { Calendar, CalendarId } from '../../../common/types/calendars'; -import { JobId } from '../jobs/new_job/common/job_creator/configs'; +import { JobId } from '../../../common/types/anomaly_detection_jobs'; class CalendarService { /** * Assigns a job id to the calendar. diff --git a/x-pack/legacy/plugins/ml/public/application/services/forecast_service.d.ts b/x-pack/legacy/plugins/ml/public/application/services/forecast_service.d.ts index 8de903a422f340..9eff86c753da92 100644 --- a/x-pack/legacy/plugins/ml/public/application/services/forecast_service.d.ts +++ b/x-pack/legacy/plugins/ml/public/application/services/forecast_service.d.ts @@ -5,7 +5,7 @@ */ import { Observable } from 'rxjs'; -import { Job } from '../jobs/new_job/common/job_creator/configs'; +import { Job } from '../../../common/types/anomaly_detection_jobs'; export interface ForecastData { success: boolean; diff --git a/x-pack/legacy/plugins/ml/public/application/services/job_service.d.ts b/x-pack/legacy/plugins/ml/public/application/services/job_service.d.ts index b9ed83eeffba16..2134d157e1baae 100644 --- a/x-pack/legacy/plugins/ml/public/application/services/job_service.d.ts +++ b/x-pack/legacy/plugins/ml/public/application/services/job_service.d.ts @@ -5,7 +5,7 @@ */ import { SearchResponse } from 'elasticsearch'; -import { CombinedJob } from '../jobs/new_job/common/job_creator/configs'; +import { CombinedJob } from '../../../common/types/anomaly_detection_jobs'; import { Calendar } from '../../../common/types/calendars'; export interface ExistingJobsAndGroups { diff --git a/x-pack/legacy/plugins/ml/public/application/services/ml_api_service/index.d.ts b/x-pack/legacy/plugins/ml/public/application/services/ml_api_service/index.d.ts index 6cb8eccafe1511..97e001389c5f1a 100644 --- a/x-pack/legacy/plugins/ml/public/application/services/ml_api_service/index.d.ts +++ b/x-pack/legacy/plugins/ml/public/application/services/ml_api_service/index.d.ts @@ -11,7 +11,6 @@ import { AggFieldNamePair } from '../../../../common/types/fields'; import { Category } from '../../../../common/types/categories'; import { ExistingJobsAndGroups } from '../job_service'; import { PrivilegesResponse } from '../../../../common/types/privileges'; -import { MlJobWithTimeRange, MlSummaryJobs } from '../../../../common/types/jobs'; import { MlServerDefaults, MlServerLimits } from '../ml_server_info'; import { ES_AGGREGATION } from '../../../../common/constants/aggregation_types'; import { DataFrameAnalyticsStats } from '../../data_frame_analytics/pages/analytics_management/components/analytics_list/common'; @@ -21,7 +20,12 @@ import { DeepPartial } from '../../../../common/types/common'; import { PartitionFieldsDefinition } from '../results_service/result_service_rx'; import { annotations } from './annotations'; import { Calendar, CalendarId, UpdateCalendar } from '../../../../common/types/calendars'; -import { CombinedJob, JobId } from '../../jobs/new_job/common/job_creator/configs'; +import { + MlJobWithTimeRange, + MlSummaryJobs, + CombinedJob, + JobId, +} from '../../../../common/types/anomaly_detection_jobs'; import { CategorizationAnalyzer, CategoryFieldExample, diff --git a/x-pack/legacy/plugins/ml/public/application/services/results_service/result_service_rx.ts b/x-pack/legacy/plugins/ml/public/application/services/results_service/result_service_rx.ts index 299dfe01676947..8f701a9ebe057c 100644 --- a/x-pack/legacy/plugins/ml/public/application/services/results_service/result_service_rx.ts +++ b/x-pack/legacy/plugins/ml/public/application/services/results_service/result_service_rx.ts @@ -16,7 +16,7 @@ import { map } from 'rxjs/operators'; import _ from 'lodash'; import { Dictionary } from '../../../../common/types/common'; import { ML_MEDIAN_PERCENTS } from '../../../../common/util/job_utils'; -import { JobId } from '../../jobs/new_job/common/job_creator/configs'; +import { JobId } from '../../../../common/types/anomaly_detection_jobs'; import { ml } from '../ml_api_service'; import { ML_RESULTS_INDEX_PATTERN } from '../../../../common/constants/index_patterns'; import { CriteriaField } from './index'; diff --git a/x-pack/legacy/plugins/ml/public/application/timeseriesexplorer/components/timeseries_chart/timeseries_chart.d.ts b/x-pack/legacy/plugins/ml/public/application/timeseriesexplorer/components/timeseries_chart/timeseries_chart.d.ts index 1f49ec1826422a..de294ffc214216 100644 --- a/x-pack/legacy/plugins/ml/public/application/timeseriesexplorer/components/timeseries_chart/timeseries_chart.d.ts +++ b/x-pack/legacy/plugins/ml/public/application/timeseriesexplorer/components/timeseries_chart/timeseries_chart.d.ts @@ -7,10 +7,10 @@ import d3 from 'd3'; import { Annotation } from '../../../../../common/types/annotations'; -import { MlJob } from '../../../../../common/types/jobs'; +import { CombinedJob } from '../../../../../common/types/anomaly_detection_jobs'; interface Props { - selectedJob: MlJob; + selectedJob: CombinedJob; } interface State { diff --git a/x-pack/legacy/plugins/ml/public/application/timeseriesexplorer/timeseries_search_service.ts b/x-pack/legacy/plugins/ml/public/application/timeseriesexplorer/timeseries_search_service.ts index 65bcc9d355fd69..978f5f68d9d8d8 100644 --- a/x-pack/legacy/plugins/ml/public/application/timeseriesexplorer/timeseries_search_service.ts +++ b/x-pack/legacy/plugins/ml/public/application/timeseriesexplorer/timeseries_search_service.ts @@ -14,7 +14,7 @@ import { isModelPlotEnabled } from '../../../common/util/job_utils'; import { buildConfigFromDetector } from '../util/chart_config_builder'; import { mlResultsService } from '../services/results_service'; import { ModelPlotOutput } from '../services/results_service/result_service_rx'; -import { Job } from '../jobs/new_job/common/job_creator/configs'; +import { Job } from '../../../common/types/anomaly_detection_jobs'; function getMetricData( job: Job, diff --git a/x-pack/legacy/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer_utils/get_focus_data.ts b/x-pack/legacy/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer_utils/get_focus_data.ts index 2a4eaf1a545a1c..534c8c567359e6 100644 --- a/x-pack/legacy/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer_utils/get_focus_data.ts +++ b/x-pack/legacy/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer_utils/get_focus_data.ts @@ -13,7 +13,7 @@ import { } from '../../../../common/constants/search'; import { mlTimeSeriesSearchService } from '../timeseries_search_service'; import { mlResultsService, CriteriaField } from '../../services/results_service'; -import { Job } from '../../jobs/new_job/common/job_creator/configs'; +import { Job } from '../../../../common/types/anomaly_detection_jobs'; import { MAX_SCHEDULED_EVENTS, TIME_FIELD_NAME } from '../timeseriesexplorer_constants'; import { processDataForFocusAnomalies, diff --git a/x-pack/legacy/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer_utils/validate_job_selection.ts b/x-pack/legacy/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer_utils/validate_job_selection.ts index bd8f98e0428a18..d31ae710b025ee 100644 --- a/x-pack/legacy/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer_utils/validate_job_selection.ts +++ b/x-pack/legacy/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer_utils/validate_job_selection.ts @@ -10,7 +10,7 @@ import { i18n } from '@kbn/i18n'; import { getToastNotifications } from '../../util/dependency_cache'; -import { MlJobWithTimeRange } from '../../../../common/types/jobs'; +import { MlJobWithTimeRange } from '../../../../common/types/anomaly_detection_jobs'; import { getTimeRangeFromSelection } from '../../components/job_selector/job_select_service_utils'; import { mlJobService } from '../../services/job_service'; diff --git a/x-pack/plugins/ml/server/models/data_recognizer/data_recognizer.ts b/x-pack/plugins/ml/server/models/data_recognizer/data_recognizer.ts index 8d2a6c9955da36..020a77baa4e358 100644 --- a/x-pack/plugins/ml/server/models/data_recognizer/data_recognizer.ts +++ b/x-pack/plugins/ml/server/models/data_recognizer/data_recognizer.ts @@ -10,7 +10,7 @@ import numeral from '@elastic/numeral'; import { CallAPIOptions, RequestHandlerContext, SavedObjectsClientContract } from 'kibana/server'; import { IndexPatternAttributes } from 'src/plugins/data/server'; import { merge } from 'lodash'; -import { MlJob } from '../../../../../legacy/plugins/ml/common/types/jobs'; +import { CombinedJobWithStats } from '../../../../../legacy/plugins/ml/common/types/anomaly_detection_jobs'; import { KibanaObjects, ModuleDataFeed, @@ -29,7 +29,6 @@ import { prefixDatafeedId, } from '../../../../../legacy/plugins/ml/common/util/job_utils'; import { mlLog } from '../../client/log'; -// @ts-ignore import { jobServiceProvider } from '../job_service'; import { resultsServiceProvider } from '../results_service'; @@ -61,7 +60,7 @@ interface RawModuleConfig { } interface MlJobStats { - jobs: MlJob[]; + jobs: CombinedJobWithStats[]; } interface Config { diff --git a/x-pack/plugins/ml/server/models/job_service/datafeeds.js b/x-pack/plugins/ml/server/models/job_service/datafeeds.ts similarity index 57% rename from x-pack/plugins/ml/server/models/job_service/datafeeds.js rename to x-pack/plugins/ml/server/models/job_service/datafeeds.ts index 961b712610512d..47a0d8f2546595 100644 --- a/x-pack/plugins/ml/server/models/job_service/datafeeds.js +++ b/x-pack/plugins/ml/server/models/job_service/datafeeds.ts @@ -4,24 +4,45 @@ * you may not use this file except in compliance with the Elastic License. */ +import { APICaller } from 'src/core/server'; import { i18n } from '@kbn/i18n'; import { JOB_STATE, DATAFEED_STATE, } from '../../../../../legacy/plugins/ml/common/constants/states'; import { fillResultsWithTimeouts, isRequestTimeout } from './error_utils'; +import { + Datafeed, + DatafeedStats, +} from '../../../../../legacy/plugins/ml/common/types/anomaly_detection_jobs'; + +export interface MlDatafeedsResponse { + datafeeds: Datafeed[]; + count: number; +} +export interface MlDatafeedsStatsResponse { + datafeeds: DatafeedStats[]; + count: number; +} -export function datafeedsProvider(callWithRequest) { - async function forceStartDatafeeds(datafeedIds, start, end) { +interface Results { + [id: string]: { + started: boolean; + error?: any; + }; +} + +export function datafeedsProvider(callAsCurrentUser: APICaller) { + async function forceStartDatafeeds(datafeedIds: string[], start: number, end: number) { const jobIds = await getJobIdsByDatafeedId(); - const doStartsCalled = datafeedIds.reduce((p, c) => { - p[c] = false; - return p; - }, {}); + const doStartsCalled = datafeedIds.reduce((acc, cur) => { + acc[cur] = false; + return acc; + }, {} as { [id: string]: boolean }); - const results = {}; + const results: Results = {}; - async function doStart(datafeedId) { + async function doStart(datafeedId: string): Promise<{ started: boolean; error?: string }> { if (doStartsCalled[datafeedId] === false) { doStartsCalled[datafeedId] = true; try { @@ -30,6 +51,8 @@ export function datafeedsProvider(callWithRequest) { } catch (error) { return { started: false, error }; } + } else { + return { started: true }; } } @@ -64,10 +87,10 @@ export function datafeedsProvider(callWithRequest) { return results; } - async function openJob(jobId) { + async function openJob(jobId: string) { let opened = false; try { - const resp = await callWithRequest('ml.openJob', { jobId }); + const resp = await callAsCurrentUser('ml.openJob', { jobId }); opened = resp.opened; } catch (error) { if (error.statusCode === 409) { @@ -79,16 +102,16 @@ export function datafeedsProvider(callWithRequest) { return opened; } - async function startDatafeed(datafeedId, start, end) { - return callWithRequest('ml.startDatafeed', { datafeedId, start, end }); + async function startDatafeed(datafeedId: string, start: number, end: number) { + return callAsCurrentUser('ml.startDatafeed', { datafeedId, start, end }); } - async function stopDatafeeds(datafeedIds) { - const results = {}; + async function stopDatafeeds(datafeedIds: string[]) { + const results: Results = {}; for (const datafeedId of datafeedIds) { try { - results[datafeedId] = await callWithRequest('ml.stopDatafeed', { datafeedId }); + results[datafeedId] = await callAsCurrentUser('ml.stopDatafeed', { datafeedId }); } catch (error) { if (isRequestTimeout(error)) { return fillResultsWithTimeouts(results, datafeedId, datafeedIds, DATAFEED_STATE.STOPPED); @@ -99,24 +122,24 @@ export function datafeedsProvider(callWithRequest) { return results; } - async function forceDeleteDatafeed(datafeedId) { - return callWithRequest('ml.deleteDatafeed', { datafeedId, force: true }); + async function forceDeleteDatafeed(datafeedId: string) { + return callAsCurrentUser('ml.deleteDatafeed', { datafeedId, force: true }); } async function getDatafeedIdsByJobId() { - const datafeeds = await callWithRequest('ml.datafeeds'); - return datafeeds.datafeeds.reduce((p, c) => { - p[c.job_id] = c.datafeed_id; - return p; - }, {}); + const { datafeeds } = await callAsCurrentUser('ml.datafeeds'); + return datafeeds.reduce((acc, cur) => { + acc[cur.job_id] = cur.datafeed_id; + return acc; + }, {} as { [id: string]: string }); } async function getJobIdsByDatafeedId() { - const datafeeds = await callWithRequest('ml.datafeeds'); - return datafeeds.datafeeds.reduce((p, c) => { - p[c.datafeed_id] = c.job_id; - return p; - }, {}); + const { datafeeds } = await callAsCurrentUser('ml.datafeeds'); + return datafeeds.reduce((acc, cur) => { + acc[cur.datafeed_id] = cur.job_id; + return acc; + }, {} as { [id: string]: string }); } return { diff --git a/x-pack/plugins/ml/server/models/job_service/error_utils.js b/x-pack/plugins/ml/server/models/job_service/error_utils.ts similarity index 77% rename from x-pack/plugins/ml/server/models/job_service/error_utils.js rename to x-pack/plugins/ml/server/models/job_service/error_utils.ts index 21e45110e70930..a5c98f8dbdda6b 100644 --- a/x-pack/plugins/ml/server/models/job_service/error_utils.js +++ b/x-pack/plugins/ml/server/models/job_service/error_utils.ts @@ -11,14 +11,27 @@ import { } from '../../../../../legacy/plugins/ml/common/constants/states'; const REQUEST_TIMEOUT = 'RequestTimeout'; +type ACTION_STATE = DATAFEED_STATE | JOB_STATE; -export function isRequestTimeout(error) { +export function isRequestTimeout(error: { displayName: string }) { return error.displayName === REQUEST_TIMEOUT; } +interface Results { + [id: string]: { + [status: string]: any; + error?: any; + }; +} + // populate a results object with timeout errors // for the ids which haven't already been set -export function fillResultsWithTimeouts(results, id, ids, status) { +export function fillResultsWithTimeouts( + results: Results, + id: string, + ids: string[], + status: ACTION_STATE +) { const action = getAction(status); const extra = ids.length - Object.keys(results).length > 1 @@ -49,20 +62,20 @@ export function fillResultsWithTimeouts(results, id, ids, status) { }, }; - return ids.reduce((p, c) => { - if (results[c] === undefined) { - p[c] = { + return ids.reduce((acc, cur) => { + if (results[cur] === undefined) { + acc[cur] = { [status]: false, error, }; } else { - p[c] = results[c]; + acc[cur] = results[cur]; } - return p; - }, {}); + return acc; + }, {} as Results); } -function getAction(status) { +function getAction(status: ACTION_STATE) { let action = ''; if (status === DATAFEED_STATE.STARTED) { action = 'start'; diff --git a/x-pack/plugins/ml/server/models/job_service/groups.js b/x-pack/plugins/ml/server/models/job_service/groups.ts similarity index 70% rename from x-pack/plugins/ml/server/models/job_service/groups.js rename to x-pack/plugins/ml/server/models/job_service/groups.ts index b30e9cdc6048b7..58d71aa331ea9d 100644 --- a/x-pack/plugins/ml/server/models/job_service/groups.js +++ b/x-pack/plugins/ml/server/models/job_service/groups.ts @@ -4,17 +4,33 @@ * you may not use this file except in compliance with the Elastic License. */ +import { APICaller } from 'src/core/server'; import { CalendarManager } from '../calendar'; import { GLOBAL_CALENDAR } from '../../../../../legacy/plugins/ml/common/constants/calendars'; +import { Job } from '../../../../../legacy/plugins/ml/common/types/anomaly_detection_jobs'; +import { MlJobsResponse } from './jobs'; -export function groupsProvider(callWithRequest) { - const calMngr = new CalendarManager(callWithRequest); +interface Group { + id: string; + jobIds: string[]; + calendarIds: string[]; +} + +interface Results { + [id: string]: { + success: boolean; + error?: any; + }; +} + +export function groupsProvider(callAsCurrentUser: APICaller) { + const calMngr = new CalendarManager(callAsCurrentUser); async function getAllGroups() { - const groups = {}; - const jobIds = {}; + const groups: { [id: string]: Group } = {}; + const jobIds: { [id: string]: undefined | null } = {}; const [{ jobs }, calendars] = await Promise.all([ - callWithRequest('ml.jobs'), + callAsCurrentUser('ml.jobs'), calMngr.getAllCalendars(), ]); @@ -58,12 +74,12 @@ export function groupsProvider(callWithRequest) { return Object.keys(groups).map(g => groups[g]); } - async function updateGroups(jobs) { - const results = {}; + async function updateGroups(jobs: Job[]) { + const results: Results = {}; for (const job of jobs) { const { job_id: jobId, groups } = job; try { - await callWithRequest('ml.updateJob', { jobId, body: { groups } }); + await callAsCurrentUser('ml.updateJob', { jobId, body: { groups } }); results[jobId] = { success: true }; } catch (error) { results[jobId] = { success: false, error }; diff --git a/x-pack/plugins/ml/server/models/job_service/index.js b/x-pack/plugins/ml/server/models/job_service/index.ts similarity index 87% rename from x-pack/plugins/ml/server/models/job_service/index.js rename to x-pack/plugins/ml/server/models/job_service/index.ts index 70b855e80a770c..37a1daa20cab2d 100644 --- a/x-pack/plugins/ml/server/models/job_service/index.js +++ b/x-pack/plugins/ml/server/models/job_service/index.ts @@ -4,13 +4,14 @@ * you may not use this file except in compliance with the Elastic License. */ +import { APICaller } from 'src/core/server'; import { datafeedsProvider } from './datafeeds'; import { jobsProvider } from './jobs'; import { groupsProvider } from './groups'; import { newJobCapsProvider } from './new_job_caps'; import { newJobChartsProvider, topCategoriesProvider } from './new_job'; -export function jobServiceProvider(callAsCurrentUser) { +export function jobServiceProvider(callAsCurrentUser: APICaller) { return { ...datafeedsProvider(callAsCurrentUser), ...jobsProvider(callAsCurrentUser), diff --git a/x-pack/plugins/ml/server/models/job_service/jobs.js b/x-pack/plugins/ml/server/models/job_service/jobs.ts similarity index 62% rename from x-pack/plugins/ml/server/models/job_service/jobs.js rename to x-pack/plugins/ml/server/models/job_service/jobs.ts index 16d3c30bb0a280..6ced623273f742 100644 --- a/x-pack/plugins/ml/server/models/job_service/jobs.js +++ b/x-pack/plugins/ml/server/models/job_service/jobs.ts @@ -5,34 +5,60 @@ */ import { i18n } from '@kbn/i18n'; +import { uniq } from 'lodash'; +import { APICaller } from 'src/core/server'; import { JOB_STATE, DATAFEED_STATE, } from '../../../../../legacy/plugins/ml/common/constants/states'; -import { datafeedsProvider } from './datafeeds'; +import { + MlSummaryJob, + AuditMessage, + Job, + JobStats, + DatafeedWithStats, + CombinedJobWithStats, +} from '../../../../../legacy/plugins/ml/common/types/anomaly_detection_jobs'; +import { datafeedsProvider, MlDatafeedsResponse, MlDatafeedsStatsResponse } from './datafeeds'; import { jobAuditMessagesProvider } from '../job_audit_messages'; import { resultsServiceProvider } from '../results_service'; -import { CalendarManager } from '../calendar'; +import { CalendarManager, Calendar } from '../calendar'; import { fillResultsWithTimeouts, isRequestTimeout } from './error_utils'; import { getLatestDataOrBucketTimestamp, isTimeSeriesViewJob, } from '../../../../../legacy/plugins/ml/common/util/job_utils'; import { groupsProvider } from './groups'; -import { uniq } from 'lodash'; -export function jobsProvider(callWithRequest) { - const { forceDeleteDatafeed, getDatafeedIdsByJobId } = datafeedsProvider(callWithRequest); - const { getAuditMessagesSummary } = jobAuditMessagesProvider(callWithRequest); - const { getLatestBucketTimestampByJob } = resultsServiceProvider(callWithRequest); - const calMngr = new CalendarManager(callWithRequest); +export interface MlJobsResponse { + jobs: Job[]; + count: number; +} + +export interface MlJobsStatsResponse { + jobs: JobStats[]; + count: number; +} + +interface Results { + [id: string]: { + [status: string]: boolean; + error?: any; + }; +} + +export function jobsProvider(callAsCurrentUser: APICaller) { + const { forceDeleteDatafeed, getDatafeedIdsByJobId } = datafeedsProvider(callAsCurrentUser); + const { getAuditMessagesSummary } = jobAuditMessagesProvider(callAsCurrentUser); + const { getLatestBucketTimestampByJob } = resultsServiceProvider(callAsCurrentUser); + const calMngr = new CalendarManager(callAsCurrentUser); - async function forceDeleteJob(jobId) { - return callWithRequest('ml.deleteJob', { jobId, force: true }); + async function forceDeleteJob(jobId: string) { + return callAsCurrentUser('ml.deleteJob', { jobId, force: true }); } - async function deleteJobs(jobIds) { - const results = {}; + async function deleteJobs(jobIds: string[]) { + const results: Results = {}; const datafeedIds = await getDatafeedIdsByJobId(); for (const jobId of jobIds) { @@ -68,11 +94,11 @@ export function jobsProvider(callWithRequest) { return results; } - async function closeJobs(jobIds) { - const results = {}; + async function closeJobs(jobIds: string[]) { + const results: Results = {}; for (const jobId of jobIds) { try { - await callWithRequest('ml.closeJob', { jobId }); + await callAsCurrentUser('ml.closeJob', { jobId }); results[jobId] = { closed: true }; } catch (error) { if (isRequestTimeout(error)) { @@ -88,7 +114,7 @@ export function jobsProvider(callWithRequest) { // if the job has failed we want to attempt a force close. // however, if we received a 409 due to the datafeed being started we should not attempt a force close. try { - await callWithRequest('ml.closeJob', { jobId, force: true }); + await callAsCurrentUser('ml.closeJob', { jobId, force: true }); results[jobId] = { closed: true }; } catch (error2) { if (isRequestTimeout(error)) { @@ -104,14 +130,14 @@ export function jobsProvider(callWithRequest) { return results; } - async function jobsSummary(jobIds = []) { - const fullJobsList = await createFullJobsList(); + async function jobsSummary(jobIds: string[] = []) { + const fullJobsList: CombinedJobWithStats[] = await createFullJobsList(); const fullJobsIds = fullJobsList.map(job => job.job_id); - const auditMessages = await getAuditMessagesSummary(fullJobsIds); - const auditMessagesByJob = auditMessages.reduce((p, c) => { - p[c.job_id] = c; - return p; - }, {}); + const auditMessages: AuditMessage[] = await getAuditMessagesSummary(fullJobsIds); + const auditMessagesByJob = auditMessages.reduce((acc, cur) => { + acc[cur.job_id] = cur; + return acc; + }, {} as { [id: string]: AuditMessage }); const deletingStr = i18n.translate('xpack.ml.models.jobService.deletingJob', { defaultMessage: 'deleting', @@ -122,11 +148,11 @@ export function jobsProvider(callWithRequest) { typeof job.datafeed_config === 'object' && Object.keys(job.datafeed_config).length > 0; const dataCounts = job.data_counts; - const tempJob = { + const tempJob: MlSummaryJob = { id: job.job_id, description: job.description || '', groups: Array.isArray(job.groups) ? job.groups.sort() : [], - processed_record_count: job.data_counts.processed_record_count, + processed_record_count: job.data_counts?.processed_record_count, memory_status: job.model_size_stats ? job.model_size_stats.memory_status : '', jobState: job.deleting === true ? deletingStr : job.state, hasDatafeed, @@ -135,11 +161,11 @@ export function jobsProvider(callWithRequest) { datafeedIndices: hasDatafeed && job.datafeed_config.indices ? job.datafeed_config.indices : [], datafeedState: hasDatafeed && job.datafeed_config.state ? job.datafeed_config.state : '', - latestTimestampMs: dataCounts.latest_record_timestamp, - earliestTimestampMs: dataCounts.earliest_record_timestamp, + latestTimestampMs: dataCounts?.latest_record_timestamp, + earliestTimestampMs: dataCounts?.earliest_record_timestamp, latestResultsTimestampMs: getLatestDataOrBucketTimestamp( - dataCounts.latest_record_timestamp, - dataCounts.latest_bucket_timestamp + dataCounts?.latest_record_timestamp as number, + dataCounts?.latest_bucket_timestamp as number ), isSingleMetricViewerJob: isTimeSeriesViewJob(job), nodeName: job.node ? job.node.name : undefined, @@ -149,7 +175,11 @@ export function jobsProvider(callWithRequest) { tempJob.fullJob = job; } const auditMessage = auditMessagesByJob[tempJob.id]; - if (auditMessage !== undefined && job.create_time <= auditMessage.msgTime) { + if ( + auditMessage !== undefined && + job.create_time !== undefined && + job.create_time <= auditMessage.msgTime + ) { tempJob.auditMessage = { level: auditMessage.highestLevel, text: auditMessage.highestLevelText, @@ -163,19 +193,19 @@ export function jobsProvider(callWithRequest) { async function jobsWithTimerange() { const fullJobsList = await createFullJobsList(); - const jobsMap = {}; + const jobsMap: { [id: string]: string[] } = {}; const jobs = fullJobsList.map(job => { jobsMap[job.job_id] = job.groups || []; const hasDatafeed = typeof job.datafeed_config === 'object' && Object.keys(job.datafeed_config).length > 0; - const timeRange = {}; + const timeRange: { to?: number; from?: number } = {}; const dataCounts = job.data_counts; if (dataCounts !== undefined) { timeRange.to = getLatestDataOrBucketTimestamp( - dataCounts.latest_record_timestamp, - dataCounts.latest_bucket_timestamp + dataCounts.latest_record_timestamp as number, + dataCounts.latest_bucket_timestamp as number ); timeRange.from = dataCounts.earliest_record_timestamp; } @@ -195,56 +225,65 @@ export function jobsProvider(callWithRequest) { return { jobs, jobsMap }; } - async function createFullJobsList(jobIds = []) { - const [JOBS, JOB_STATS, DATAFEEDS, DATAFEED_STATS, CALENDARS, BUCKET_TIMESTAMPS] = [ - 0, - 1, - 2, - 3, - 4, - 5, - ]; - - const jobs = []; - const groups = {}; - const datafeeds = {}; - const calendarsByJobId = {}; - const requests = + async function createFullJobsList(jobIds: string[] = []) { + const jobs: CombinedJobWithStats[] = []; + const groups: { [jobId: string]: string[] } = {}; + const datafeeds: { [id: string]: DatafeedWithStats } = {}; + const calendarsByJobId: { [jobId: string]: string[] } = {}; + const requests: [ + Promise, + Promise, + Promise, + Promise, + Promise, + Promise<{ [id: string]: number | undefined }> + ] = [ + jobIds.length > 0 + ? callAsCurrentUser('ml.jobs', { jobId: jobIds }) // move length check in side call + : callAsCurrentUser('ml.jobs'), jobIds.length > 0 - ? [ - callWithRequest('ml.jobs', { jobId: jobIds }), - callWithRequest('ml.jobStats', { jobId: jobIds }), - ] - : [callWithRequest('ml.jobs'), callWithRequest('ml.jobStats')]; - requests.push( - callWithRequest('ml.datafeeds'), - callWithRequest('ml.datafeedStats'), + ? callAsCurrentUser('ml.jobStats', { jobId: jobIds }) + : callAsCurrentUser('ml.jobStats'), + callAsCurrentUser('ml.datafeeds'), + callAsCurrentUser('ml.datafeedStats'), calMngr.getAllCalendars(), - getLatestBucketTimestampByJob() - ); - - const results = await Promise.all(requests); + getLatestBucketTimestampByJob(), + ]; - if (results[DATAFEEDS] && results[DATAFEEDS].datafeeds) { - results[DATAFEEDS].datafeeds.forEach(datafeed => { - if (results[DATAFEED_STATS] && results[DATAFEED_STATS].datafeeds) { - const datafeedStats = results[DATAFEED_STATS].datafeeds.find( + const [ + jobResults, + jobStatsResults, + datafeedResults, + datafeedStatsResults, + calendarResults, + latestBucketTimestampByJob, + ] = await Promise.all< + MlJobsResponse, + MlJobsStatsResponse, + MlDatafeedsResponse, + MlDatafeedsStatsResponse, + Calendar[], + { [id: string]: number | undefined } + >(requests); + + if (datafeedResults && datafeedResults.datafeeds) { + datafeedResults.datafeeds.forEach(datafeed => { + if (datafeedStatsResults && datafeedStatsResults.datafeeds) { + const datafeedStats = datafeedStatsResults.datafeeds.find( ds => ds.datafeed_id === datafeed.datafeed_id ); if (datafeedStats) { - datafeed.state = datafeedStats.state; - datafeed.timing_stats = datafeedStats.timing_stats; + datafeeds[datafeed.job_id] = { ...datafeed, ...datafeedStats }; } } - datafeeds[datafeed.job_id] = datafeed; }); } // create list of jobs per group. // used for assigning calendars to jobs when a calendar has // only been attached to a group - if (results[JOBS] && results[JOBS].jobs) { - results[JOBS].jobs.forEach(job => { + if (jobResults && jobResults.jobs) { + jobResults.jobs.forEach(job => { calendarsByJobId[job.job_id] = []; if (job.groups !== undefined) { @@ -259,8 +298,8 @@ export function jobsProvider(callWithRequest) { } // assign calendars to jobs - if (results[CALENDARS]) { - results[CALENDARS].forEach(cal => { + if (calendarResults) { + calendarResults.forEach(cal => { cal.job_ids.forEach(id => { if (groups[id]) { groups[id].forEach(jId => { @@ -285,42 +324,42 @@ export function jobsProvider(callWithRequest) { } // create jobs objects containing job stats, datafeeds, datafeed stats and calendars - if (results[JOBS] && results[JOBS].jobs) { - results[JOBS].jobs.forEach(job => { - job.data_counts = {}; - job.model_size_stats = {}; - job.datafeed_config = {}; - - if (calendarsByJobId[job.job_id].length) { - job.calendars = calendarsByJobId[job.job_id]; + if (jobResults && jobResults.jobs) { + jobResults.jobs.forEach(job => { + const tempJob = job as CombinedJobWithStats; + + if (calendarsByJobId[tempJob.job_id].length) { + tempJob.calendars = calendarsByJobId[tempJob.job_id]; } - if (results[JOB_STATS] && results[JOB_STATS].jobs) { - const jobStats = results[JOB_STATS].jobs.find(js => js.job_id === job.job_id); + if (jobStatsResults && jobStatsResults.jobs) { + const jobStats = jobStatsResults.jobs.find(js => js.job_id === tempJob.job_id); if (jobStats !== undefined) { - job.state = jobStats.state; - job.data_counts = jobStats.data_counts; - job.model_size_stats = jobStats.model_size_stats; + tempJob.state = jobStats.state; + tempJob.data_counts = jobStats.data_counts; + tempJob.model_size_stats = jobStats.model_size_stats; if (jobStats.node) { - job.node = jobStats.node; + tempJob.node = jobStats.node; } if (jobStats.open_time) { - job.open_time = jobStats.open_time; + tempJob.open_time = jobStats.open_time; } // Add in the timestamp of the last bucket processed for each job if available. - if (results[BUCKET_TIMESTAMPS] && results[BUCKET_TIMESTAMPS][job.job_id]) { - job.data_counts.latest_bucket_timestamp = results[BUCKET_TIMESTAMPS][job.job_id]; + const latestBucketTimestamp = + latestBucketTimestampByJob && latestBucketTimestampByJob[tempJob.job_id]; + if (latestBucketTimestamp) { + tempJob.data_counts.latest_bucket_timestamp = latestBucketTimestamp; } } } - const datafeed = datafeeds[job.job_id]; + const datafeed = datafeeds[tempJob.job_id]; if (datafeed !== undefined) { - job.datafeed_config = datafeed; + tempJob.datafeed_config = datafeed; } - jobs.push(job); + jobs.push(tempJob); }); } return jobs; @@ -331,7 +370,7 @@ export function jobsProvider(callWithRequest) { const detailed = true; const jobIds = []; try { - const tasksList = await callWithRequest('tasks.list', { actions, detailed }); + const tasksList = await callAsCurrentUser('tasks.list', { actions, detailed }); Object.keys(tasksList.nodes).forEach(nodeId => { const tasks = tasksList.nodes[nodeId].tasks; Object.keys(tasks).forEach(taskId => { @@ -341,7 +380,7 @@ export function jobsProvider(callWithRequest) { } catch (e) { // if the user doesn't have permission to load the task list, // use the jobs list to get the ids of deleting jobs - const { jobs } = await callWithRequest('ml.jobs'); + const { jobs } = await callAsCurrentUser('ml.jobs'); jobIds.push(...jobs.filter(j => j.deleting === true).map(j => j.job_id)); } return { jobIds }; @@ -350,11 +389,13 @@ export function jobsProvider(callWithRequest) { // Checks if each of the jobs in the specified list of IDs exist. // Job IDs in supplied array may contain wildcard '*' characters // e.g. *_low_request_rate_ecs - async function jobsExist(jobIds = []) { + async function jobsExist(jobIds: string[] = []) { // Get the list of job IDs. - const jobsInfo = await callWithRequest('ml.jobs', { jobId: jobIds }); + const jobsInfo = await callAsCurrentUser('ml.jobs', { + jobId: jobIds, + }); - const results = {}; + const results: { [id: string]: boolean } = {}; if (jobsInfo.count > 0) { const allJobIds = jobsInfo.jobs.map(job => job.job_id); @@ -375,8 +416,8 @@ export function jobsProvider(callWithRequest) { } async function getAllJobAndGroupIds() { - const { getAllGroups } = groupsProvider(callWithRequest); - const jobs = await callWithRequest('ml.jobs'); + const { getAllGroups } = groupsProvider(callAsCurrentUser); + const jobs = await callAsCurrentUser('ml.jobs'); const jobIds = jobs.jobs.map(job => job.job_id); const groups = await getAllGroups(); const groupIds = groups.map(group => group.id); @@ -387,10 +428,10 @@ export function jobsProvider(callWithRequest) { }; } - async function getLookBackProgress(jobId, start, end) { + async function getLookBackProgress(jobId: string, start: number, end: number) { const datafeedId = `datafeed-${jobId}`; const [jobStats, isRunning] = await Promise.all([ - callWithRequest('ml.jobStats', { jobId: [jobId] }), + callAsCurrentUser('ml.jobStats', { jobId: [jobId] }), isDatafeedRunning(datafeedId), ]); @@ -408,8 +449,10 @@ export function jobsProvider(callWithRequest) { return { progress: 0, isRunning: false, isJobClosed: true }; } - async function isDatafeedRunning(datafeedId) { - const stats = await callWithRequest('ml.datafeedStats', { datafeedId: [datafeedId] }); + async function isDatafeedRunning(datafeedId: string) { + const stats = await callAsCurrentUser('ml.datafeedStats', { + datafeedId: [datafeedId], + }); if (stats.datafeeds.length) { const state = stats.datafeeds[0].state; return ( diff --git a/x-pack/plugins/ml/server/models/job_validation/validate_cardinality.d.ts b/x-pack/plugins/ml/server/models/job_validation/validate_cardinality.d.ts index d3930ecf44c8d5..ada2a1222281db 100644 --- a/x-pack/plugins/ml/server/models/job_validation/validate_cardinality.d.ts +++ b/x-pack/plugins/ml/server/models/job_validation/validate_cardinality.d.ts @@ -5,16 +5,6 @@ */ import { APICaller } from 'src/core/server'; -import { - Job, - Datafeed, -} from '../../../../../legacy/plugins/ml/public/application/jobs/new_job/common/job_creator/configs'; +import { CombinedJob } from '../../../../../legacy/plugins/ml/common/types/anomaly_detection_jobs'; -interface ValidateCardinalityConfig extends Job { - datafeed_config?: Datafeed; -} - -export function validateCardinality( - callAsCurrentUser: APICaller, - job: ValidateCardinalityConfig -): any[]; +export function validateCardinality(callAsCurrentUser: APICaller, job: CombinedJob): any[]; diff --git a/x-pack/plugins/ml/server/models/job_validation/validate_time_range.ts b/x-pack/plugins/ml/server/models/job_validation/validate_time_range.ts index 551b5ab9173a41..e2f6e0dca1a6f4 100644 --- a/x-pack/plugins/ml/server/models/job_validation/validate_time_range.ts +++ b/x-pack/plugins/ml/server/models/job_validation/validate_time_range.ts @@ -7,7 +7,7 @@ import { APICaller } from 'src/core/server'; import { ES_FIELD_TYPES } from '../../../../../../src/plugins/data/server'; import { parseInterval } from '../../../../../legacy/plugins/ml/common/util/parse_interval'; -import { CombinedJob } from '../../../../../legacy/plugins/ml/public/application/jobs/new_job/common/job_creator/configs'; +import { CombinedJob } from '../../../../../legacy/plugins/ml/common/types/anomaly_detection_jobs'; // @ts-ignore import { validateJobObject } from './validate_job_object'; diff --git a/x-pack/plugins/ml/server/routes/job_service.ts b/x-pack/plugins/ml/server/routes/job_service.ts index 9ad2f80a1e66b4..bb4530bfb1c6c5 100644 --- a/x-pack/plugins/ml/server/routes/job_service.ts +++ b/x-pack/plugins/ml/server/routes/job_service.ts @@ -20,7 +20,7 @@ import { topCategoriesSchema, updateGroupsSchema, } from './schemas/job_service_schema'; -// @ts-ignore no declaration module + import { jobServiceProvider } from '../models/job_service'; import { categorizationExamplesProvider } from '../models/job_service/new_job'; @@ -209,8 +209,7 @@ export function jobServiceRoutes({ router, mlLicense }: RouteInitialization) { mlLicense.fullLicenseAPIGuard(async (context, request, response) => { try { const { jobsWithTimerange } = jobServiceProvider(context.ml!.mlClient.callAsCurrentUser); - const { dateFormatTz } = request.body; - const resp = await jobsWithTimerange(dateFormatTz); + const resp = await jobsWithTimerange(); return response.ok({ body: resp, @@ -420,10 +419,7 @@ export function jobServiceRoutes({ router, mlLicense }: RouteInitialization) { splitFieldValue, } = request.body; - const { newJobLineChart } = jobServiceProvider( - context.ml!.mlClient.callAsCurrentUser, - request - ); + const { newJobLineChart } = jobServiceProvider(context.ml!.mlClient.callAsCurrentUser); const resp = await newJobLineChart( indexPatternTitle, timeField, diff --git a/x-pack/test/functional/apps/machine_learning/anomaly_detection/anomaly_explorer.ts b/x-pack/test/functional/apps/machine_learning/anomaly_detection/anomaly_explorer.ts index 8a1ef5f196eba0..47c69d3f6d7ade 100644 --- a/x-pack/test/functional/apps/machine_learning/anomaly_detection/anomaly_explorer.ts +++ b/x-pack/test/functional/apps/machine_learning/anomaly_detection/anomaly_explorer.ts @@ -9,7 +9,7 @@ import { FtrProviderContext } from '../../../ftr_provider_context'; import { Job, Datafeed, -} from '../../../../..//legacy/plugins/ml/public/application/jobs/new_job/common/job_creator/configs'; +} from '../../../../..//legacy/plugins/ml/common/types/anomaly_detection_jobs'; const JOB_CONFIG: Job = { job_id: `fq_multi_1_ae`, diff --git a/x-pack/test/functional/apps/machine_learning/anomaly_detection/single_metric_viewer.ts b/x-pack/test/functional/apps/machine_learning/anomaly_detection/single_metric_viewer.ts index 407e002f11f99e..f2d12b7d515cda 100644 --- a/x-pack/test/functional/apps/machine_learning/anomaly_detection/single_metric_viewer.ts +++ b/x-pack/test/functional/apps/machine_learning/anomaly_detection/single_metric_viewer.ts @@ -9,7 +9,7 @@ import { FtrProviderContext } from '../../../ftr_provider_context'; import { Job, Datafeed, -} from '../../../../..//legacy/plugins/ml/public/application/jobs/new_job/common/job_creator/configs'; +} from '../../../../..//legacy/plugins/ml/common/types/anomaly_detection_jobs'; const JOB_CONFIG: Job = { job_id: `fq_single_1_smv`, diff --git a/x-pack/test/functional/services/machine_learning/api.ts b/x-pack/test/functional/services/machine_learning/api.ts index 7330a7b9812d58..976eb51318915b 100644 --- a/x-pack/test/functional/services/machine_learning/api.ts +++ b/x-pack/test/functional/services/machine_learning/api.ts @@ -10,8 +10,7 @@ import { FtrProviderContext } from '../../ftr_provider_context'; import { JOB_STATE, DATAFEED_STATE } from '../../../../legacy/plugins/ml/common/constants/states'; import { DATA_FRAME_TASK_STATE } from '../../../../legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/common'; -import { Job } from '../../../..//legacy/plugins/ml/public/application/jobs/new_job/common/job_creator/configs/job'; -import { Datafeed } from '../../../..//legacy/plugins/ml/public/application/jobs/new_job/common/job_creator/configs/datafeed'; +import { Job, Datafeed } from '../../../..//legacy/plugins/ml/common/types/anomaly_detection_jobs'; export type MlApi = ProvidedType; From 8a4bb61f0507c206443295e91428e2facab52449 Mon Sep 17 00:00:00 2001 From: James Gowdy Date: Mon, 9 Mar 2020 14:43:23 +0000 Subject: [PATCH 08/20] [ML] Fixes bucket span estimators loading of max_buckets setting (#59639) Co-authored-by: Elastic Machine --- .../bucket_span_estimator/bucket_span_estimator.js | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) 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 53b9d75304963b..d2e4311bf6f225 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 @@ -343,11 +343,21 @@ export function estimateBucketSpanFactory( filterPath: '*.*max_buckets', }) .then(settings => { - if (typeof settings !== 'object' || typeof settings.defaults !== 'object') { + if (typeof settings !== 'object') { + reject('Unable to retrieve cluster settings'); + } + + // search.max_buckets could exist in default, persistent or transient cluster settings + const maxBucketsSetting = (settings.defaults || + settings.persistent || + settings.transient || + {})['search.max_buckets']; + + if (maxBucketsSetting === undefined) { reject('Unable to retrieve cluster setting search.max_buckets'); } - const maxBuckets = parseInt(settings.defaults['search.max_buckets']); + const maxBuckets = parseInt(maxBucketsSetting); const runEstimator = (splitFieldValues = []) => { const bucketSpanEstimator = new BucketSpanEstimator( From da3c9d968c184163ad4aaa163ceb7bafa773b948 Mon Sep 17 00:00:00 2001 From: Joe Reuter Date: Mon, 9 Mar 2020 16:22:52 +0100 Subject: [PATCH 09/20] fix outdated docs (#58729) --- CONTRIBUTING.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index bd7868adb511ec..aec6d44ad4abfb 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -391,9 +391,9 @@ Note that for VSCode, to enable "live" linting of TypeScript (and other) file ty All user-facing labels and info texts in Kibana should be internationalized. Please take a look at the [readme](packages/kbn-i18n/README.md) and the [guideline](packages/kbn-i18n/GUIDELINE.md) of the i18n package on how to do so. -In order to enable translations in the React parts of the application, the top most component of every `ReactDOM.render` call should be an `I18nContext`: +In order to enable translations in the React parts of the application, the top most component of every `ReactDOM.render` call should be the `Context` component from the `i18n` core service: ```jsx -import { I18nContext } from 'ui/i18n'; +const I18nContext = coreStart.i18n.Context; ReactDOM.render( From 84f6885c36b010bb67e0a39ccf37c0740caa610b Mon Sep 17 00:00:00 2001 From: Kaarina Tungseth Date: Mon, 9 Mar 2020 10:29:25 -0500 Subject: [PATCH 10/20] [DOCS] Removed spatial references (#59595) * [DOCS] Removed ss above spatial references * Removed above from n numeral formatting page --- docs/apm/advanced-queries.asciidoc | 4 +- .../canvas/canvas-function-reference.asciidoc | 34 +++++++------- docs/dev-tools/console/console.asciidoc | 42 ++++++++--------- .../searchprofiler/more-complicated.asciidoc | 6 +-- .../core/development-dependencies.asciidoc | 4 +- .../core/development-modules.asciidoc | 4 +- ...pment-plugin-feature-registration.asciidoc | 2 +- .../development-plugin-localization.asciidoc | 8 ++-- docs/developer/pr-review.asciidoc | 4 +- docs/infrastructure/metrics-explorer.asciidoc | 2 +- docs/logs/using.asciidoc | 4 +- docs/management/numeral.asciidoc | 4 +- .../create_and_manage_rollups.asciidoc | 2 +- docs/maps/connect-to-ems.asciidoc | 2 +- docs/setup/docker.asciidoc | 18 ++++---- docs/setup/settings.asciidoc | 5 +- docs/user/introduction.asciidoc | 2 +- .../development/pdf-integration.asciidoc | 5 +- .../elasticsearch-mutual-tls.asciidoc | 4 +- .../securing-communications/index.asciidoc | 4 +- docs/user/security/securing-kibana.asciidoc | 10 ++-- docs/visualize/vega.asciidoc | 46 +++++++++---------- 22 files changed, 105 insertions(+), 111 deletions(-) diff --git a/docs/apm/advanced-queries.asciidoc b/docs/apm/advanced-queries.asciidoc index 942882f8c4dfb3..971d543bbb4452 100644 --- a/docs/apm/advanced-queries.asciidoc +++ b/docs/apm/advanced-queries.asciidoc @@ -5,7 +5,7 @@ When querying in the APM app, you're simply searching and selecting data from fi Queries entered into the query bar are also added as parameters to the URL, so it's easy to share a specific query or view with others. -In the screenshot below, you can begin to see some of the transaction fields available for filtering on: +In the screenshot below, you can begin to see some of the transaction fields available for filtering on: [role="screenshot"] image::apm/images/apm-query-bar.png[Example of the Kibana Query bar in APM app in Kibana] @@ -25,7 +25,7 @@ TIP: Read the {kibana-ref}/kuery-query.html[Kibana Query Language Enhancements] It may also be helpful to view your APM data in the {kibana-ref}/discover.html[Discover app]. Querying documents in Discover works the same way as querying in the APM app, -and all of the example queries listed above can also be used in the Discover app. +and all of the example APM app queries can also be used in the Discover app. [float] ==== Example Discover app query diff --git a/docs/canvas/canvas-function-reference.asciidoc b/docs/canvas/canvas-function-reference.asciidoc index 85e9d224904971..16aaf55802b170 100644 --- a/docs/canvas/canvas-function-reference.asciidoc +++ b/docs/canvas/canvas-function-reference.asciidoc @@ -3,13 +3,13 @@ == Canvas function reference Behind the scenes, Canvas is driven by a powerful expression language, -with dozens of functions and other capabilities, including table transforms, +with dozens of functions and other capabilities, including table transforms, type casting, and sub-expressions. The Canvas expression language also supports <>, which perform complex math calculations. -A *** denotes a required argument. +A *** denotes a required argument. A † denotes an argument can be passed multiple times. @@ -184,7 +184,7 @@ filters ---- `as` casts any primitive value (`string`, `number`, `date`, `null`) into a `datatable` with a single row and a single column with the given name (or defaults to `"value"` if no name is provided). This is useful when piping a primitive value into a function that only takes `datatable` as an input. -In the example above, `ply` expects each `fn` subexpression to return a `datatable` in order to merge the results of each `fn` back into a `datatable`, but using a `math` aggregation in the subexpressions returns a single `math` value, which is then cast into a `datatable` using `as`. +In the example, `ply` expects each `fn` subexpression to return a `datatable` in order to merge the results of each `fn` back into a `datatable`, but using a `math` aggregation in the subexpressions returns a single `math` value, which is then cast into a `datatable` using `as`. *Accepts:* `string`, `boolean`, `number`, `null` @@ -496,14 +496,14 @@ containerStyle backgroundImage={asset id=asset-f40d2292-cf9e-4f2c-8c6f-a504a25e9 *Code example* [source,text] ---- -shape "star" fill="#E61D35" maintainAspect=true -| render containerStyle={ - containerStyle backgroundColor="#F8D546" - borderRadius="200px" - border="4px solid #05509F" - padding="0px" - opacity="0.9" - overflow="hidden" +shape "star" fill="#E61D35" maintainAspect=true +| render containerStyle={ + containerStyle backgroundColor="#F8D546" + borderRadius="200px" + border="4px solid #05509F" + padding="0px" + opacity="0.9" + overflow="hidden" } ---- @@ -1437,8 +1437,8 @@ Aliases: `dataurl`, `url` |`string`, `null` |The HTTP(S) URL or `base64` data URL of an image. -Example value for the _Unnamed_ argument, formatted as a `base64` data URL: -[source, url] +Example value for the _Unnamed_ argument, formatted as a `base64` data URL: +[source, url] ------------ data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjxzdmcKICAgeG1sbnM6ZGM9Imh0dHA6Ly9wdXJsLm9yZy9kYy9lbGVtZW50cy8xLjEvIgogICB4bWxuczpjYz0iaHR0cDovL2NyZWF0aXZlY29tbW9ucy5vcmcvbnMjIgogICB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiCiAgIHhtbG5zOnN2Zz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciCiAgIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIKICAgdmlld0JveD0iMCAwIDI3MC42MDAwMSAyNjkuNTQ2NjYiCiAgIGhlaWdodD0iMjY5LjU0NjY2IgogICB3aWR0aD0iMjcwLjYwMDAxIgogICB4bWw6c3BhY2U9InByZXNlcnZlIgogICBpZD0ic3ZnMiIKICAgdmVyc2lvbj0iMS4xIj48bWV0YWRhdGEKICAgICBpZD0ibWV0YWRhdGE4Ij48cmRmOlJERj48Y2M6V29yawogICAgICAgICByZGY6YWJvdXQ9IiI+PGRjOmZvcm1hdD5pbWFnZS9zdmcreG1sPC9kYzpmb3JtYXQ+PGRjOnR5cGUKICAgICAgICAgICByZGY6cmVzb3VyY2U9Imh0dHA6Ly9wdXJsLm9yZy9kYy9kY21pdHlwZS9TdGlsbEltYWdlIiAvPjwvY2M6V29yaz48L3JkZjpSREY+PC9tZXRhZGF0YT48ZGVmcwogICAgIGlkPSJkZWZzNiIgLz48ZwogICAgIHRyYW5zZm9ybT0ibWF0cml4KDEuMzMzMzMzMywwLDAsLTEuMzMzMzMzMywwLDI2OS41NDY2NykiCiAgICAgaWQ9ImcxMCI+PGcKICAgICAgIHRyYW5zZm9ybT0ic2NhbGUoMC4xKSIKICAgICAgIGlkPSJnMTIiPjxwYXRoCiAgICAgICAgIGlkPSJwYXRoMTQiCiAgICAgICAgIHN0eWxlPSJmaWxsOiNmZmZmZmY7ZmlsbC1vcGFjaXR5OjE7ZmlsbC1ydWxlOm5vbnplcm87c3Ryb2tlOm5vbmUiCiAgICAgICAgIGQ9Im0gMjAyOS40OCw5NjIuNDQxIGMgMCwxNzAuMDk5IC0xMDUuNDYsMzE4Ljc5OSAtMjY0LjE3LDM3Ni42NTkgNi45OCwzNS44NiAxMC42Miw3MS43MSAxMC42MiwxMDkuMDUgMCwzMTYuMTkgLTI1Ny4yNCw1NzMuNDMgLTU3My40Nyw1NzMuNDMgLTE4NC43MiwwIC0zNTYuNTU4LC04OC41OSAtNDY0LjUzLC0yMzcuODUgLTUzLjA5LDQxLjE4IC0xMTguMjg1LDYzLjc1IC0xODYuMzA1LDYzLjc1IC0xNjcuODM2LDAgLTMwNC4zODMsLTEzNi41NCAtMzA0LjM4MywtMzA0LjM4IDAsLTM3LjA4IDYuNjE3LC03Mi41OCAxOS4wMzEsLTEwNi4wOCBDIDEwOC40ODgsMTM4MC4wOSAwLDEyMjcuODkgMCwxMDU4Ljg4IDAsODg3LjkxIDEwNS45NzcsNzM4LjUzOSAyNjUuMzk4LDY4MS4wOSBjIC02Ljc2OSwtMzUuNDQyIC0xMC40NiwtNzIuMDIgLTEwLjQ2LC0xMDkgQyAyNTQuOTM4LDI1Ni42MjEgNTExLjU2NiwwIDgyNy4wMjcsMCAxMDEyLjIsMCAxMTgzLjk0LDg4Ljk0MTQgMTI5MS4zLDIzOC44MzIgYyA1My40NSwtNDEuOTYxIDExOC44LC02NC45OTIgMTg2LjU2LC02NC45OTIgMTY3LjgzLDAgMzA0LjM4LDEzNi40OTIgMzA0LjM4LDMwNC4zMzIgMCwzNy4wNzggLTYuNjIsNzIuNjI5IC0xOS4wMywxMDYuMTI5IDE1Ny43OCw1Ni44NzkgMjY2LjI3LDIwOS4xMjkgMjY2LjI3LDM3OC4xNCIgLz48cGF0aAogICAgICAgICBpZD0icGF0aDE2IgogICAgICAgICBzdHlsZT0iZmlsbDojZmFjZjA5O2ZpbGwtb3BhY2l0eToxO2ZpbGwtcnVsZTpub256ZXJvO3N0cm9rZTpub25lIgogICAgICAgICBkPSJtIDc5Ny44OTgsMTE1MC45MyA0NDQuMDcyLC0yMDIuNDUgNDQ4LjA1LDM5Mi41OCBjIDYuNDksMzIuMzkgOS42Niw2NC42NyA5LjY2LDk4LjQ2IDAsMjc2LjIzIC0yMjQuNjgsNTAwLjk1IC01MDAuOSw1MDAuOTUgLTE2NS4yNCwwIC0zMTkuMzcsLTgxLjM2IC00MTMuMDUzLC0yMTcuNzkgbCAtNzQuNTI0LC0zODYuNjQgODYuNjk1LC0xODUuMTEiIC8+PHBhdGgKICAgICAgICAgaWQ9InBhdGgxOCIKICAgICAgICAgc3R5bGU9ImZpbGw6IzQ5YzFhZTtmaWxsLW9wYWNpdHk6MTtmaWxsLXJ1bGU6bm9uemVybztzdHJva2U6bm9uZSIKICAgICAgICAgZD0ibSAzMzguMjIzLDY4MC42NzIgYyAtNi40ODksLTMyLjM4MyAtOS44MDksLTY1Ljk4MSAtOS44MDksLTk5Ljk3MyAwLC0yNzYuOTI5IDIyNS4zMzYsLTUwMi4yNTc2IDUwMi4zMTMsLTUwMi4yNTc2IDE2Ni41OTMsMCAzMjEuNDczLDgyLjExNzYgNDE1LjAxMywyMTkuOTQ5NiBsIDczLjk3LDM4NS4zNDcgLTk4LjcyLDE4OC42MjEgTCA3NzUuMTU2LDEwNzUuNTcgMzM4LjIyMyw2ODAuNjcyIiAvPjxwYXRoCiAgICAgICAgIGlkPSJwYXRoMjAiCiAgICAgICAgIHN0eWxlPSJmaWxsOiNlZjI5OWI7ZmlsbC1vcGFjaXR5OjE7ZmlsbC1ydWxlOm5vbnplcm87c3Ryb2tlOm5vbmUiCiAgICAgICAgIGQ9Im0gMzM1LjQxLDE0NDkuMTggMzA0LjMzMiwtNzEuODYgNjYuNjgsMzQ2LjAyIGMgLTQxLjU4NiwzMS43OCAtOTIuOTMsNDkuMTggLTE0NS43MzEsNDkuMTggLTEzMi4yNSwwIC0yMzkuODEyLC0xMDcuNjEgLTIzOS44MTIsLTIzOS44NyAwLC0yOS4yMSA0Ljg3OSwtNTcuMjIgMTQuNTMxLC04My40NyIgLz48cGF0aAogICAgICAgICBpZD0icGF0aDIyIgogICAgICAgICBzdHlsZT0iZmlsbDojNGNhYmU0O2ZpbGwtb3BhY2l0eToxO2ZpbGwtcnVsZTpub256ZXJvO3N0cm9rZTpub25lIgogICAgICAgICBkPSJNIDMwOC45OTIsMTM3Ni43IEMgMTczLjAyLDEzMzEuNjQgNzguNDgwNSwxMjAxLjMgNzguNDgwNSwxMDU3LjkzIDc4LjQ4MDUsOTE4LjM0IDE2NC44Miw3OTMuNjggMjk0LjQwNiw3NDQuMzUyIGwgNDI2Ljk4MSwzODUuOTM4IC03OC4zOTUsMTY3LjUxIC0zMzQsNzguOSIgLz48cGF0aAogICAgICAgICBpZD0icGF0aDI0IgogICAgICAgICBzdHlsZT0iZmlsbDojODVjZTI2O2ZpbGwtb3BhY2l0eToxO2ZpbGwtcnVsZTpub256ZXJvO3N0cm9rZTpub25lIgogICAgICAgICBkPSJtIDEzMjMuOCwyOTguNDEgYyA0MS43NCwtMzIuMDkgOTIuODMsLTQ5LjU5IDE0NC45OCwtNDkuNTkgMTMyLjI1LDAgMjM5LjgxLDEwNy41NTkgMjM5LjgxLDIzOS44MjEgMCwyOS4xNiAtNC44OCw1Ny4xNjggLTE0LjUzLDgzLjQxOCBsIC0zMDQuMDgsNzEuMTYgLTY2LjE4LC0zNDQuODA5IiAvPjxwYXRoCiAgICAgICAgIGlkPSJwYXRoMjYiCiAgICAgICAgIHN0eWxlPSJmaWxsOiMzMTc3YTc7ZmlsbC1vcGFjaXR5OjE7ZmlsbC1ydWxlOm5vbnplcm87c3Ryb2tlOm5vbmUiCiAgICAgICAgIGQ9Im0gMTM4NS42Nyw3MjIuOTMgMzM0Ljc2LC03OC4zMDEgYyAxMzYuMDIsNDQuOTYxIDIzMC41NiwxNzUuMzUxIDIzMC41NiwzMTguNzYyIDAsMTM5LjMzOSAtODYuNTQsMjYzLjg1OSAtMjE2LjM4LDMxMy4wMzkgbCAtNDM3Ljg0LC0zODMuNTkgODguOSwtMTY5LjkxIiAvPjwvZz48L2c+PC9zdmc+ ------------ @@ -2052,8 +2052,8 @@ Default: `null` |`string`, `null` |The image to repeat. Provide an image asset as a `base64` data URL, or pass in a sub-expression. -Example value for the `image` argument, formatted as a `base64` data URL: -[source, url] +Example value for the `image` argument, formatted as a `base64` data URL: +[source, url] ------------ data:image/svg+xml,%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22utf-8%22%3F%3E%0A%3Csvg%20viewBox%3D%22-3.948730230331421%20-1.7549896240234375%20245.25946044921875%20241.40370178222656%22%20width%3D%22245.25946044921875%22%20height%3D%22241.40370178222656%22%20style%3D%22enable-background%3Anew%200%200%20686.2%20235.7%3B%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%0A%20%20%3Cdefs%3E%0A%20%20%20%20%3Cstyle%20type%3D%22text%2Fcss%22%3E%0A%09.st0%7Bfill%3A%232D2D2D%3B%7D%0A%3C%2Fstyle%3E%0A%20%20%3C%2Fdefs%3E%0A%20%20%3Cg%20transform%3D%22matrix%281%2C%200%2C%200%2C%201%2C%200%2C%200%29%22%3E%0A%20%20%20%20%3Cg%3E%0A%20%20%20%20%20%20%3Cpath%20class%3D%22st0%22%20d%3D%22M329.4%2C160.3l4.7-0.5l0.3%2C9.6c-12.4%2C1.7-23%2C2.6-31.8%2C2.6c-11.7%2C0-20-3.4-24.9-10.2%26%2310%3B%26%239%3B%26%239%3B%26%239%3Bc-4.9-6.8-7.3-17.4-7.3-31.7c0-28.6%2C11.4-42.9%2C34.1-42.9c11%2C0%2C19.2%2C3.1%2C24.6%2C9.2c5.4%2C6.1%2C8.1%2C15.8%2C8.1%2C28.9l-0.7%2C9.3h-53.8%26%2310%3B%26%239%3B%26%239%3B%26%239%3Bc0%2C9%2C1.6%2C15.7%2C4.9%2C20c3.3%2C4.3%2C8.9%2C6.5%2C17%2C6.5C312.8%2C161.2%2C321.1%2C160.9%2C329.4%2C160.3z%20M325%2C124.9c0-10-1.6-17.1-4.8-21.2%26%2310%3B%26%239%3B%26%239%3B%26%239%3Bc-3.2-4.1-8.4-6.2-15.6-6.2c-7.2%2C0-12.7%2C2.2-16.3%2C6.5c-3.6%2C4.3-5.5%2C11.3-5.6%2C20.9H325z%22%2F%3E%0A%20%20%20%20%20%20%3Cpath%20class%3D%22st0%22%20d%3D%22M354.3%2C171.4V64h12.2v107.4H354.3z%22%2F%3E%0A%20%20%20%20%20%20%3Cpath%20class%3D%22st0%22%20d%3D%22M443.5%2C113.5v41.1c0%2C4.1%2C10.1%2C3.9%2C10.1%2C3.9l-0.6%2C10.8c-8.6%2C0-15.7%2C0.7-20-3.4c-9.8%2C4.3-19.5%2C6.1-29.3%2C6.1%26%2310%3B%26%239%3B%26%239%3B%26%239%3Bc-7.5%2C0-13.2-2.1-17.1-6.4c-3.9-4.2-5.9-10.3-5.9-18.3c0-7.9%2C2-13.8%2C6-17.5c4-3.7%2C10.3-6.1%2C18.9-6.9l25.6-2.4v-7%26%2310%3B%26%239%3B%26%239%3B%26%239%3Bc0-5.5-1.2-9.5-3.6-11.9c-2.4-2.4-5.7-3.6-9.8-3.6l-32.1%2C0V87.2h31.3c9.2%2C0%2C15.9%2C2.1%2C20.1%2C6.4C441.4%2C97.8%2C443.5%2C104.5%2C443.5%2C113.5%26%2310%3B%26%239%3B%26%239%3B%26%239%3Bz%20M393.3%2C146.7c0%2C10%2C4.1%2C15%2C12.4%2C15c7.4%2C0%2C14.7-1.2%2C21.8-3.7l3.7-1.3v-26.9l-24.1%2C2.3c-4.9%2C0.4-8.4%2C1.8-10.6%2C4.2%26%2310%3B%26%239%3B%26%239%3B%26%239%3BC394.4%2C138.7%2C393.3%2C142.2%2C393.3%2C146.7z%22%2F%3E%0A%20%20%20%20%20%20%3Cpath%20class%3D%22st0%22%20d%3D%22M491.2%2C98.2c-11.8%2C0-17.8%2C4.1-17.8%2C12.4c0%2C3.8%2C1.4%2C6.5%2C4.1%2C8.1c2.7%2C1.6%2C8.9%2C3.2%2C18.6%2C4.9%26%2310%3B%26%239%3B%26%239%3B%26%239%3Bc9.7%2C1.7%2C16.5%2C4%2C20.5%2C7.1c4%2C3%2C6%2C8.7%2C6%2C17.1c0%2C8.4-2.7%2C14.5-8.1%2C18.4c-5.4%2C3.9-13.2%2C5.9-23.6%2C5.9c-6.7%2C0-29.2-2.5-29.2-2.5%26%2310%3B%26%239%3B%26%239%3B%26%239%3Bl0.7-10.6c12.9%2C1.2%2C22.3%2C2.2%2C28.6%2C2.2c6.3%2C0%2C11.1-1%2C14.4-3c3.3-2%2C5-5.4%2C5-10.1c0-4.7-1.4-7.9-4.2-9.6c-2.8-1.7-9-3.3-18.6-4.8%26%2310%3B%26%239%3B%26%239%3B%26%239%3Bc-9.6-1.5-16.4-3.7-20.4-6.7c-4-2.9-6-8.4-6-16.3c0-7.9%2C2.8-13.8%2C8.4-17.6c5.6-3.8%2C12.6-5.7%2C20.9-5.7c6.6%2C0%2C29.6%2C1.7%2C29.6%2C1.7%26%2310%3B%26%239%3B%26%239%3B%26%239%3Bv10.7C508.1%2C99%2C498.2%2C98.2%2C491.2%2C98.2z%22%2F%3E%0A%20%20%20%20%20%20%3Cpath%20class%3D%22st0%22%20d%3D%22M581.7%2C99.5h-25.9v39c0%2C9.3%2C0.7%2C15.5%2C2%2C18.4c1.4%2C2.9%2C4.6%2C4.4%2C9.7%2C4.4l14.5-1l0.8%2C10.1%26%2310%3B%26%239%3B%26%239%3B%26%239%3Bc-7.3%2C1.2-12.8%2C1.8-16.6%2C1.8c-8.5%2C0-14.3-2.1-17.6-6.2c-3.3-4.1-4.9-12-4.9-23.6V99.5h-11.6V88.9h11.6V63.9h12.1v24.9h25.9V99.5z%22%2F%3E%0A%20%20%20%20%20%20%3Cpath%20class%3D%22st0%22%20d%3D%22M598.7%2C78.4V64.3h12.2v14.2H598.7z%20M598.7%2C171.4V88.9h12.2v82.5H598.7z%22%2F%3E%0A%20%20%20%20%20%20%3Cpath%20class%3D%22st0%22%20d%3D%22M663.8%2C87.2c3.6%2C0%2C9.7%2C0.7%2C18.3%2C2l3.9%2C0.5l-0.5%2C9.9c-8.7-1-15.1-1.5-19.2-1.5c-9.2%2C0-15.5%2C2.2-18.8%2C6.6%26%2310%3B%26%239%3B%26%239%3B%26%239%3Bc-3.3%2C4.4-5%2C12.6-5%2C24.5c0%2C11.9%2C1.5%2C20.2%2C4.6%2C24.9c3.1%2C4.7%2C9.5%2C7%2C19.3%2C7l19.2-1.5l0.5%2C10.1c-10.1%2C1.5-17.7%2C2.3-22.7%2C2.3%26%2310%3B%26%239%3B%26%239%3B%26%239%3Bc-12.7%2C0-21.5-3.3-26.3-9.8c-4.8-6.5-7.3-17.5-7.3-33c0-15.5%2C2.6-26.4%2C7.8-32.6C643%2C90.4%2C651.7%2C87.2%2C663.8%2C87.2z%22%2F%3E%0A%20%20%20%20%3C%2Fg%3E%0A%20%20%20%20%3Cpath%20class%3D%22st0%22%20d%3D%22M236.6%2C123.5c0-19.8-12.3-37.2-30.8-43.9c0.8-4.2%2C1.2-8.4%2C1.2-12.7C207%2C30%2C177%2C0%2C140.2%2C0%26%2310%3B%26%239%3B%26%239%3BC118.6%2C0%2C98.6%2C10.3%2C86%2C27.7c-6.2-4.8-13.8-7.4-21.7-7.4c-19.6%2C0-35.5%2C15.9-35.5%2C35.5c0%2C4.3%2C0.8%2C8.5%2C2.2%2C12.4%26%2310%3B%26%239%3B%26%239%3BC12.6%2C74.8%2C0%2C92.5%2C0%2C112.2c0%2C19.9%2C12.4%2C37.3%2C30.9%2C44c-0.8%2C4.1-1.2%2C8.4-1.2%2C12.7c0%2C36.8%2C29.9%2C66.7%2C66.7%2C66.7%26%2310%3B%26%239%3B%26%239%3Bc21.6%2C0%2C41.6-10.4%2C54.1-27.8c6.2%2C4.9%2C13.8%2C7.6%2C21.7%2C7.6c19.6%2C0%2C35.5-15.9%2C35.5-35.5c0-4.3-0.8-8.5-2.2-12.4%26%2310%3B%26%239%3B%26%239%3BC223.9%2C160.9%2C236.6%2C143.2%2C236.6%2C123.5z%20M91.6%2C34.8c10.9-15.9%2C28.9-25.4%2C48.1-25.4c32.2%2C0%2C58.4%2C26.2%2C58.4%2C58.4%26%2310%3B%26%239%3B%26%239%3Bc0%2C3.9-0.4%2C7.7-1.1%2C11.5l-52.2%2C45.8L93%2C101.5L82.9%2C79.9L91.6%2C34.8z%20M65.4%2C29c6.2%2C0%2C12.1%2C2%2C17%2C5.7l-7.8%2C40.3l-35.5-8.4%26%2310%3B%26%239%3B%26%239%3Bc-1.1-3.1-1.7-6.3-1.7-9.7C37.4%2C41.6%2C49.9%2C29%2C65.4%2C29z%20M9.1%2C112.3c0-16.7%2C11-31.9%2C26.9-37.2L75%2C84.4l9.1%2C19.5l-49.8%2C45%26%2310%3B%26%239%3B%26%239%3BC19.2%2C143.1%2C9.1%2C128.6%2C9.1%2C112.3z%20M145.2%2C200.9c-10.9%2C16.1-29%2C25.6-48.4%2C25.6c-32.3%2C0-58.6-26.3-58.6-58.5c0-4%2C0.4-7.9%2C1.1-11.7%26%2310%3B%26%239%3B%26%239%3Bl50.9-46l52%2C23.7l11.5%2C22L145.2%2C200.9z%20M171.2%2C206.6c-6.1%2C0-12-2-16.9-5.8l7.7-40.2l35.4%2C8.3c1.1%2C3.1%2C1.7%2C6.3%2C1.7%2C9.7%26%2310%3B%26%239%3B%26%239%3BC199.2%2C194.1%2C186.6%2C206.6%2C171.2%2C206.6z%20M200.5%2C160.5l-39-9.1l-10.4-19.8l51-44.7c15.1%2C5.7%2C25.2%2C20.2%2C25.2%2C36.5%26%2310%3B%26%239%3B%26%239%3BC227.4%2C140.1%2C216.4%2C155.3%2C200.5%2C160.5z%22%2F%3E%0A%20%20%3C%2Fg%3E%0A%3C%2Fsvg%3E ------------ @@ -2132,8 +2132,8 @@ Default: `null` |`string`, `null` |The image to reveal. Provide an image asset as a `base64` data URL, or pass in a sub-expression. -Example value for the `image` argument, formatted as a `base64` data URL: -[source, url] +Example value for the `image` argument, formatted as a `base64` data URL: +[source, url] ------------ data:image/svg+xml,%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22utf-8%22%3F%3E%0A%3Csvg%20viewBox%3D%22-3.948730230331421%20-1.7549896240234375%20245.25946044921875%20241.40370178222656%22%20width%3D%22245.25946044921875%22%20height%3D%22241.40370178222656%22%20style%3D%22enable-background%3Anew%200%200%20686.2%20235.7%3B%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%0A%20%20%3Cdefs%3E%0A%20%20%20%20%3Cstyle%20type%3D%22text%2Fcss%22%3E%0A%09.st0%7Bfill%3A%232D2D2D%3B%7D%0A%3C%2Fstyle%3E%0A%20%20%3C%2Fdefs%3E%0A%20%20%3Cg%20transform%3D%22matrix%281%2C%200%2C%200%2C%201%2C%200%2C%200%29%22%3E%0A%20%20%20%20%3Cg%3E%0A%20%20%20%20%20%20%3Cpath%20class%3D%22st0%22%20d%3D%22M329.4%2C160.3l4.7-0.5l0.3%2C9.6c-12.4%2C1.7-23%2C2.6-31.8%2C2.6c-11.7%2C0-20-3.4-24.9-10.2%26%2310%3B%26%239%3B%26%239%3B%26%239%3Bc-4.9-6.8-7.3-17.4-7.3-31.7c0-28.6%2C11.4-42.9%2C34.1-42.9c11%2C0%2C19.2%2C3.1%2C24.6%2C9.2c5.4%2C6.1%2C8.1%2C15.8%2C8.1%2C28.9l-0.7%2C9.3h-53.8%26%2310%3B%26%239%3B%26%239%3B%26%239%3Bc0%2C9%2C1.6%2C15.7%2C4.9%2C20c3.3%2C4.3%2C8.9%2C6.5%2C17%2C6.5C312.8%2C161.2%2C321.1%2C160.9%2C329.4%2C160.3z%20M325%2C124.9c0-10-1.6-17.1-4.8-21.2%26%2310%3B%26%239%3B%26%239%3B%26%239%3Bc-3.2-4.1-8.4-6.2-15.6-6.2c-7.2%2C0-12.7%2C2.2-16.3%2C6.5c-3.6%2C4.3-5.5%2C11.3-5.6%2C20.9H325z%22%2F%3E%0A%20%20%20%20%20%20%3Cpath%20class%3D%22st0%22%20d%3D%22M354.3%2C171.4V64h12.2v107.4H354.3z%22%2F%3E%0A%20%20%20%20%20%20%3Cpath%20class%3D%22st0%22%20d%3D%22M443.5%2C113.5v41.1c0%2C4.1%2C10.1%2C3.9%2C10.1%2C3.9l-0.6%2C10.8c-8.6%2C0-15.7%2C0.7-20-3.4c-9.8%2C4.3-19.5%2C6.1-29.3%2C6.1%26%2310%3B%26%239%3B%26%239%3B%26%239%3Bc-7.5%2C0-13.2-2.1-17.1-6.4c-3.9-4.2-5.9-10.3-5.9-18.3c0-7.9%2C2-13.8%2C6-17.5c4-3.7%2C10.3-6.1%2C18.9-6.9l25.6-2.4v-7%26%2310%3B%26%239%3B%26%239%3B%26%239%3Bc0-5.5-1.2-9.5-3.6-11.9c-2.4-2.4-5.7-3.6-9.8-3.6l-32.1%2C0V87.2h31.3c9.2%2C0%2C15.9%2C2.1%2C20.1%2C6.4C441.4%2C97.8%2C443.5%2C104.5%2C443.5%2C113.5%26%2310%3B%26%239%3B%26%239%3B%26%239%3Bz%20M393.3%2C146.7c0%2C10%2C4.1%2C15%2C12.4%2C15c7.4%2C0%2C14.7-1.2%2C21.8-3.7l3.7-1.3v-26.9l-24.1%2C2.3c-4.9%2C0.4-8.4%2C1.8-10.6%2C4.2%26%2310%3B%26%239%3B%26%239%3B%26%239%3BC394.4%2C138.7%2C393.3%2C142.2%2C393.3%2C146.7z%22%2F%3E%0A%20%20%20%20%20%20%3Cpath%20class%3D%22st0%22%20d%3D%22M491.2%2C98.2c-11.8%2C0-17.8%2C4.1-17.8%2C12.4c0%2C3.8%2C1.4%2C6.5%2C4.1%2C8.1c2.7%2C1.6%2C8.9%2C3.2%2C18.6%2C4.9%26%2310%3B%26%239%3B%26%239%3B%26%239%3Bc9.7%2C1.7%2C16.5%2C4%2C20.5%2C7.1c4%2C3%2C6%2C8.7%2C6%2C17.1c0%2C8.4-2.7%2C14.5-8.1%2C18.4c-5.4%2C3.9-13.2%2C5.9-23.6%2C5.9c-6.7%2C0-29.2-2.5-29.2-2.5%26%2310%3B%26%239%3B%26%239%3B%26%239%3Bl0.7-10.6c12.9%2C1.2%2C22.3%2C2.2%2C28.6%2C2.2c6.3%2C0%2C11.1-1%2C14.4-3c3.3-2%2C5-5.4%2C5-10.1c0-4.7-1.4-7.9-4.2-9.6c-2.8-1.7-9-3.3-18.6-4.8%26%2310%3B%26%239%3B%26%239%3B%26%239%3Bc-9.6-1.5-16.4-3.7-20.4-6.7c-4-2.9-6-8.4-6-16.3c0-7.9%2C2.8-13.8%2C8.4-17.6c5.6-3.8%2C12.6-5.7%2C20.9-5.7c6.6%2C0%2C29.6%2C1.7%2C29.6%2C1.7%26%2310%3B%26%239%3B%26%239%3B%26%239%3Bv10.7C508.1%2C99%2C498.2%2C98.2%2C491.2%2C98.2z%22%2F%3E%0A%20%20%20%20%20%20%3Cpath%20class%3D%22st0%22%20d%3D%22M581.7%2C99.5h-25.9v39c0%2C9.3%2C0.7%2C15.5%2C2%2C18.4c1.4%2C2.9%2C4.6%2C4.4%2C9.7%2C4.4l14.5-1l0.8%2C10.1%26%2310%3B%26%239%3B%26%239%3B%26%239%3Bc-7.3%2C1.2-12.8%2C1.8-16.6%2C1.8c-8.5%2C0-14.3-2.1-17.6-6.2c-3.3-4.1-4.9-12-4.9-23.6V99.5h-11.6V88.9h11.6V63.9h12.1v24.9h25.9V99.5z%22%2F%3E%0A%20%20%20%20%20%20%3Cpath%20class%3D%22st0%22%20d%3D%22M598.7%2C78.4V64.3h12.2v14.2H598.7z%20M598.7%2C171.4V88.9h12.2v82.5H598.7z%22%2F%3E%0A%20%20%20%20%20%20%3Cpath%20class%3D%22st0%22%20d%3D%22M663.8%2C87.2c3.6%2C0%2C9.7%2C0.7%2C18.3%2C2l3.9%2C0.5l-0.5%2C9.9c-8.7-1-15.1-1.5-19.2-1.5c-9.2%2C0-15.5%2C2.2-18.8%2C6.6%26%2310%3B%26%239%3B%26%239%3B%26%239%3Bc-3.3%2C4.4-5%2C12.6-5%2C24.5c0%2C11.9%2C1.5%2C20.2%2C4.6%2C24.9c3.1%2C4.7%2C9.5%2C7%2C19.3%2C7l19.2-1.5l0.5%2C10.1c-10.1%2C1.5-17.7%2C2.3-22.7%2C2.3%26%2310%3B%26%239%3B%26%239%3B%26%239%3Bc-12.7%2C0-21.5-3.3-26.3-9.8c-4.8-6.5-7.3-17.5-7.3-33c0-15.5%2C2.6-26.4%2C7.8-32.6C643%2C90.4%2C651.7%2C87.2%2C663.8%2C87.2z%22%2F%3E%0A%20%20%20%20%3C%2Fg%3E%0A%20%20%20%20%3Cpath%20class%3D%22st0%22%20d%3D%22M236.6%2C123.5c0-19.8-12.3-37.2-30.8-43.9c0.8-4.2%2C1.2-8.4%2C1.2-12.7C207%2C30%2C177%2C0%2C140.2%2C0%26%2310%3B%26%239%3B%26%239%3BC118.6%2C0%2C98.6%2C10.3%2C86%2C27.7c-6.2-4.8-13.8-7.4-21.7-7.4c-19.6%2C0-35.5%2C15.9-35.5%2C35.5c0%2C4.3%2C0.8%2C8.5%2C2.2%2C12.4%26%2310%3B%26%239%3B%26%239%3BC12.6%2C74.8%2C0%2C92.5%2C0%2C112.2c0%2C19.9%2C12.4%2C37.3%2C30.9%2C44c-0.8%2C4.1-1.2%2C8.4-1.2%2C12.7c0%2C36.8%2C29.9%2C66.7%2C66.7%2C66.7%26%2310%3B%26%239%3B%26%239%3Bc21.6%2C0%2C41.6-10.4%2C54.1-27.8c6.2%2C4.9%2C13.8%2C7.6%2C21.7%2C7.6c19.6%2C0%2C35.5-15.9%2C35.5-35.5c0-4.3-0.8-8.5-2.2-12.4%26%2310%3B%26%239%3B%26%239%3BC223.9%2C160.9%2C236.6%2C143.2%2C236.6%2C123.5z%20M91.6%2C34.8c10.9-15.9%2C28.9-25.4%2C48.1-25.4c32.2%2C0%2C58.4%2C26.2%2C58.4%2C58.4%26%2310%3B%26%239%3B%26%239%3Bc0%2C3.9-0.4%2C7.7-1.1%2C11.5l-52.2%2C45.8L93%2C101.5L82.9%2C79.9L91.6%2C34.8z%20M65.4%2C29c6.2%2C0%2C12.1%2C2%2C17%2C5.7l-7.8%2C40.3l-35.5-8.4%26%2310%3B%26%239%3B%26%239%3Bc-1.1-3.1-1.7-6.3-1.7-9.7C37.4%2C41.6%2C49.9%2C29%2C65.4%2C29z%20M9.1%2C112.3c0-16.7%2C11-31.9%2C26.9-37.2L75%2C84.4l9.1%2C19.5l-49.8%2C45%26%2310%3B%26%239%3B%26%239%3BC19.2%2C143.1%2C9.1%2C128.6%2C9.1%2C112.3z%20M145.2%2C200.9c-10.9%2C16.1-29%2C25.6-48.4%2C25.6c-32.3%2C0-58.6-26.3-58.6-58.5c0-4%2C0.4-7.9%2C1.1-11.7%26%2310%3B%26%239%3B%26%239%3Bl50.9-46l52%2C23.7l11.5%2C22L145.2%2C200.9z%20M171.2%2C206.6c-6.1%2C0-12-2-16.9-5.8l7.7-40.2l35.4%2C8.3c1.1%2C3.1%2C1.7%2C6.3%2C1.7%2C9.7%26%2310%3B%26%239%3B%26%239%3BC199.2%2C194.1%2C186.6%2C206.6%2C171.2%2C206.6z%20M200.5%2C160.5l-39-9.1l-10.4-19.8l51-44.7c15.1%2C5.7%2C25.2%2C20.2%2C25.2%2C36.5%26%2310%3B%26%239%3B%26%239%3BC227.4%2C140.1%2C216.4%2C155.3%2C200.5%2C160.5z%22%2F%3E%0A%20%20%3C%2Fg%3E%0A%3C%2Fsvg%3E ------------ diff --git a/docs/dev-tools/console/console.asciidoc b/docs/dev-tools/console/console.asciidoc index 26620688499af6..caffef7995fbfc 100644 --- a/docs/dev-tools/console/console.asciidoc +++ b/docs/dev-tools/console/console.asciidoc @@ -18,8 +18,8 @@ NOTE: You are unable to interact with the REST API of {kib} with the Console. [[console-api]] === Write requests -Console understands commands in a cURL-like syntax. -For example, the following is a `GET` request to the {es} `_search` API. +Console understands commands in a cURL-like syntax. +For example, the following is a `GET` request to the {es} `_search` API. [source,js] ---------------------------------- @@ -43,23 +43,23 @@ curl -XGET "http://localhost:9200/_search" -d' }' ---------------------------------- -If you paste the above command into Console, {kib} automatically converts it +When you paste the command into Console, {kib} automatically converts it to Console syntax. Alternatively, if you want to want to see Console syntax in cURL, -click the action icon (image:dev-tools/console/images/wrench.png[]) and select *Copy as cURL*. +click the action icon (image:dev-tools/console/images/wrench.png[]) and select *Copy as cURL*. [float] [[console-autocomplete]] ==== Autocomplete When you're typing a command, Console makes context-sensitive suggestions. -These suggestions show you the parameters for each API and speed up your typing. -To configure your preferences for autocomplete, go to -<>. +These suggestions show you the parameters for each API and speed up your typing. +To configure your preferences for autocomplete, go to +<>. [float] [[auto-formatting]] ==== Auto-formatting -The auto-formatting +The auto-formatting capability can help you format requests. Select one or more requests that you want to format, click the action icon (image:dev-tools/console/images/wrench.png[]), and then select *Auto indent*. @@ -69,27 +69,27 @@ For example, you might have a request formatted like this: [role="screenshot"] image::dev-tools/console/images/copy-curl.png["Console close-up"] -Console adjusts the JSON body of the request to apply the indents. +Console adjusts the JSON body of the request to apply the indents. [role="screenshot"] image::dev-tools/console/images/request.png["Console close-up"] -If you select *Auto indent* on a request that is already well formatted, -Console collapses the request body to a single line per document. +If you select *Auto indent* on a request that is already well formatted, +Console collapses the request body to a single line per document. This is helpful when working with the {es} {ref}/docs-bulk.html[bulk APIs]. [float] [[console-request]] -=== Submit requests +=== Submit requests -When you're ready to submit the request to {es}, click the +When you're ready to submit the request to {es}, click the green triangle. You can select multiple requests and submit them together. -Console sends the requests to {es} one by one and shows the output -in the response pane. Submitting multiple request is helpful when you're debugging an issue or trying query +Console sends the requests to {es} one by one and shows the output +in the response pane. Submitting multiple request is helpful when you're debugging an issue or trying query combinations in multiple scenarios. @@ -105,7 +105,7 @@ the action icon (image:dev-tools/console/images/wrench.png[]) and select [[console-history]] === Get your request history -Console maintains a list of the last 500 requests that {es} successfully executed. +Console maintains a list of the last 500 requests that {es} successfully executed. To view your most recent requests, click *History*. If you select a request and click *Apply*, {kib} adds it to the editor at the current cursor position. @@ -113,7 +113,7 @@ and click *Apply*, {kib} adds it to the editor at the current cursor position. [[configuring-console]] === Configure Console settings -You can configure the Console font size, JSON syntax, +You can configure the Console font size, JSON syntax, and autocomplete suggestions in *Settings*. [role="screenshot"] @@ -130,9 +130,7 @@ shortcuts, click *Help*. [[console-settings]] === Disable Console -If you don’t want to use Console, you can disable it by setting `console.enabled` -to `false` in your `kibana.yml` configuration file. Changing this setting -causes the server to regenerate assets on the next startup, +If you don’t want to use Console, you can disable it by setting `console.enabled` +to `false` in your `kibana.yml` configuration file. Changing this setting +causes the server to regenerate assets on the next startup, which might cause a delay before pages start being served. - - diff --git a/docs/dev-tools/searchprofiler/more-complicated.asciidoc b/docs/dev-tools/searchprofiler/more-complicated.asciidoc index bd74a1095083f6..a0771f4a0f240d 100644 --- a/docs/dev-tools/searchprofiler/more-complicated.asciidoc +++ b/docs/dev-tools/searchprofiler/more-complicated.asciidoc @@ -25,7 +25,7 @@ POST test/_bulk // CONSOLE -- -. From the {searchprofiler}, enter "test" in the Index field above the query editor to restrict profiled +. From the {searchprofiler}, enter "test" in the *Index* field to restrict profiled queries to the `test` index. . Replace the default `match_all` query in the query editor with a query that has two sub-query @@ -66,7 +66,7 @@ components and includes a simple aggregation, like the example below. // NOTCONSOLE -- -. Click *Profile* to profile the query and visualize the results. +. Click *Profile* to profile the query and visualize the results. . Select the shard to view the query details. + [role="screenshot"] @@ -100,5 +100,5 @@ Select the name of the shard to view the aggregation details and timing breakdow image::dev-tools/searchprofiler/images/gs10.png["Drilling into the first shard's details"] For more information about how the {searchprofiler} works, how timings are calculated, and -how to interpret various results, see +how to interpret various results, see {ref}/search-profile.html#profiling-queries[Profiling queries]. diff --git a/docs/developer/core/development-dependencies.asciidoc b/docs/developer/core/development-dependencies.asciidoc index d430667449afa5..285d338a23a0dc 100644 --- a/docs/developer/core/development-dependencies.asciidoc +++ b/docs/developer/core/development-dependencies.asciidoc @@ -96,8 +96,8 @@ module.exports = window.angular; What this shim does is fairly simple if you go line by line: -. makes sure that jQuery is loaded before angular (which actually runs the shim above) +. makes sure that jQuery is loaded before angular (which actually runs the shim) . load the angular.js file from the node_modules directory . load the angular-elastic plugin, a plugin we want to always be included whenever we import angular . use the `ui/modules` module to add the module exported by angular-elastic as a dependency to the `kibana` angular module -. finally, export the window.angular variable. This means that writing `import angular from 'angular';` will properly set the angular variable to the angular library, rather than undefined which is the default behavior. \ No newline at end of file +. finally, export the window.angular variable. This means that writing `import angular from 'angular';` will properly set the angular variable to the angular library, rather than undefined which is the default behavior. diff --git a/docs/developer/core/development-modules.asciidoc b/docs/developer/core/development-modules.asciidoc index b36be6bbb5d25b..cc5cd69ed8cb98 100644 --- a/docs/developer/core/development-modules.asciidoc +++ b/docs/developer/core/development-modules.asciidoc @@ -20,7 +20,7 @@ certain components. Here is a breakdown of those modules: the required modules and import them were they are actually necessary. - *`import 'ui/autoload/all'`* - Imports all of the above modules + Imports all of the modules [float] ==== Resolving Require Paths @@ -60,4 +60,4 @@ Here is how import/require statements are resolved to a file: ** path/index + '.js' ** path/index + '.json' ** path/index - * if none of the above paths matches then an error is thrown \ No newline at end of file + * if none of the paths matches then an error is thrown diff --git a/docs/developer/plugin/development-plugin-feature-registration.asciidoc b/docs/developer/plugin/development-plugin-feature-registration.asciidoc index f9078440cff2b7..2c686964d369aa 100644 --- a/docs/developer/plugin/development-plugin-feature-registration.asciidoc +++ b/docs/developer/plugin/development-plugin-feature-registration.asciidoc @@ -175,7 +175,7 @@ init(server) { } ----------- -Unlike the Canvas example above, Dev Tools does not require access to any saved objects to function. Dev Tools does specify an API endpoint, however. When this is configured, the Security plugin will automatically authorize access to any server API route that is tagged with `access:console`, similar to the following: +Unlike the Canvas example, Dev Tools does not require access to any saved objects to function. Dev Tools does specify an API endpoint, however. When this is configured, the Security plugin will automatically authorize access to any server API route that is tagged with `access:console`, similar to the following: ["source","javascript"] ----------- diff --git a/docs/developer/plugin/development-plugin-localization.asciidoc b/docs/developer/plugin/development-plugin-localization.asciidoc index ff497ec40e30eb..78ee933f681f48 100644 --- a/docs/developer/plugin/development-plugin-localization.asciidoc +++ b/docs/developer/plugin/development-plugin-localization.asciidoc @@ -68,7 +68,7 @@ This outputs a `en.json` file inside the `translations` directory. To localize o Checking i18n does the following: * Checks all existing labels for violations. -* Takes translations from `.i18nrc.json` and compares them to the messages extracted and validated at the step above and: +* Takes translations from `.i18nrc.json` and compares them to the messages extracted and validated. ** Checks for unused translations. If you remove a label that has a corresponding translation, you must also remove the label from the translations file. ** Checks for incompatible translations. If you add or remove a new parameter from an existing string, you must also remove the label from the translations file. @@ -86,7 +86,7 @@ node scripts/i18n_check --fix --include-config ../kibana-extra/myPlugin/.i18nrc. Kibana relies on several UI frameworks (ReactJS and AngularJS) and requires localization in different environments (browser and NodeJS). The internationalization engine is framework agnostic and consumable in -all parts of Kibana (ReactJS, AngularJS and NodeJS). +all parts of Kibana (ReactJS, AngularJS and NodeJS). To simplify internationalization in UI frameworks, additional abstractions are @@ -112,7 +112,7 @@ export const HELLO_WORLD = i18n.translate('hello.wonderful.world', { Full details are {repo}tree/master/packages/kbn-i18n#vanilla-js[here]. [float] -===== i18n for React +===== i18n for React To localize strings in React, use either `FormattedMessage` or `i18n.translate`. @@ -138,7 +138,7 @@ Full details are {repo}tree/master/packages/kbn-i18n#react[here]. [float] -===== i18n for Angular +===== i18n for Angular You are encouraged to use `i18n.translate()` by statically importing `i18n` from `@kbn/i18n` wherever possible in your Angular code. Angular wrappers use the translation `service` with the i18n engine under the hood. diff --git a/docs/developer/pr-review.asciidoc b/docs/developer/pr-review.asciidoc index dee40f51186725..304718e437dc57 100644 --- a/docs/developer/pr-review.asciidoc +++ b/docs/developer/pr-review.asciidoc @@ -67,7 +67,7 @@ Enhancements are pretty much always going to have extensive unit tests as a base [float] === Product level review -Reviewers are not simply evaluating the code itself, they are also evaluating the quality of the user-facing change in the product. This generally means they need to check out the branch locally and "play around" with it. In addition to the "do we want this change in the product" details from above, the reviewer should be looking for bugs and evaluating how approachable and useful the feature is as implemented. Special attention should be given to error scenarios and edge cases to ensure they are all handled well within the product. +Reviewers are not simply evaluating the code itself, they are also evaluating the quality of the user-facing change in the product. This generally means they need to check out the branch locally and "play around" with it. In addition to the "do we want this change in the product" details, the reviewer should be looking for bugs and evaluating how approachable and useful the feature is as implemented. Special attention should be given to error scenarios and edge cases to ensure they are all handled well within the product. [float] @@ -107,7 +107,7 @@ Conflicting opinions between reviewers and authors happen, and sometimes it is h Whether or not a bit of feedback is appropriate for a pull request is often dependent on the motivation for giving the feedback in the first place. -_Demanding_ an author make changes based primarily on the mindset of "how would I write this code?" isn't appropriate. The reviewer didn't write the code, and their critical purpose in the review process is not to craft the contribution into a form that is simply whatever they would have written if they had. If a reviewer wants to provide this type of feedback, they should qualify it as a "nit" as mentioned in the nitpicking section above to make it clear that the author can take it or leave it. +_Demanding_ an author make changes based primarily on the mindset of "how would I write this code?" isn't appropriate. The reviewer didn't write the code, and their critical purpose in the review process is not to craft the contribution into a form that is simply whatever they would have written if they had. If a reviewer wants to provide this type of feedback, they should qualify it as a "nit" as mentioned in the nitpicking section to make it clear that the author can take it or leave it. Inflammatory feedback such as "this is crap" isn't feedback at all. It's both mean and unhelpful, and it is never appropriate. diff --git a/docs/infrastructure/metrics-explorer.asciidoc b/docs/infrastructure/metrics-explorer.asciidoc index c20718dac1c7ac..d47581ffe720ac 100644 --- a/docs/infrastructure/metrics-explorer.asciidoc +++ b/docs/infrastructure/metrics-explorer.asciidoc @@ -44,7 +44,7 @@ In this step we'll leave the aggregation dropdown set to *Average* but you can t 4. In the *graph per* dropdown, enter `host.name` and select this field. You will see a separate graph for each host you are monitoring. -If you are collecting metrics for multiple hosts, you will see something like the screenshot above. +If you are collecting metrics for multiple hosts, multiple graphics are displayed. If you only have metrics for a single host, you will see a single graph. Congratulations! Either way, you've explored your first metric. diff --git a/docs/logs/using.asciidoc b/docs/logs/using.asciidoc index d84a9260521c7e..8074cc4a8026d4 100644 --- a/docs/logs/using.asciidoc +++ b/docs/logs/using.asciidoc @@ -31,9 +31,7 @@ If so, <> to change the Click image:images/time-filter-calendar.png[time filter calendar], then choose the time range for the logs. -Log entries for the specified time appear in the middle of the page, with the earlier entries above and the later entries below. - -To quickly jump to a nearby point in time, click the minimap timeline to the right. +Log entries for the specified time appear in the middle of the page. To quickly jump to a nearby point in time, click the minimap timeline to the right. // ++ what's this thing called? It's minimap in the UI. Would timeline be better? [float] diff --git a/docs/management/numeral.asciidoc b/docs/management/numeral.asciidoc index 861277fd184783..65dfdab3abd3c5 100644 --- a/docs/management/numeral.asciidoc +++ b/docs/management/numeral.asciidoc @@ -145,7 +145,7 @@ with multiple forms, such as German. [float] === Complete number pattern reference -These number formats, combined with the patterns described above, +These number formats, combined with the previously described patterns, produce the complete set of options for numeral formatting. The output here is all for the `en` locale. @@ -180,5 +180,3 @@ The output here is all for the `en` locale. | 1e-27 | 000 | 1e-27 | -1e-27 | 000 | -1e-27 |=== - - diff --git a/docs/management/rollups/create_and_manage_rollups.asciidoc b/docs/management/rollups/create_and_manage_rollups.asciidoc index b07f075f880322..83e1b7c16f8b47 100644 --- a/docs/management/rollups/create_and_manage_rollups.asciidoc +++ b/docs/management/rollups/create_and_manage_rollups.asciidoc @@ -128,7 +128,7 @@ rollup index, or you can remove or archive it using <> file to proxy EMS requests through the Kibana server. -. Update your firewall rules to whitelist connections from your Kibana server to the EMS domains listed above. +. Update your firewall rules to whitelist connections from your Kibana server to the EMS domains. NOTE: Coordinate map and region map visualizations do not support `map.proxyElasticMapsServiceInMaps` and will not proxy EMS requests through the Kibana server. diff --git a/docs/setup/docker.asciidoc b/docs/setup/docker.asciidoc index 8fd7b0490e194a..ddabce3d5b8425 100644 --- a/docs/setup/docker.asciidoc +++ b/docs/setup/docker.asciidoc @@ -7,11 +7,11 @@ A list of all published Docker images and tags is available at https://www.docker.elastic.co[www.docker.elastic.co]. The source code is in https://github.com/elastic/dockerfiles/tree/{branch}/kibana[GitHub]. -These images are free to use under the Elastic license. They contain open source -and free commercial features and access to paid commercial features. -{stack-ov}/license-management.html[Start a 30-day trial] to try out all of the -paid commercial features. See the -https://www.elastic.co/subscriptions[Subscriptions] page for information about +These images are free to use under the Elastic license. They contain open source +and free commercial features and access to paid commercial features. +{stack-ov}/license-management.html[Start a 30-day trial] to try out all of the +paid commercial features. See the +https://www.elastic.co/subscriptions[Subscriptions] page for information about Elastic license levels. [float] @@ -35,8 +35,8 @@ ifeval::["{release-state}"!="unreleased"] docker pull {docker-repo}:{version} -------------------------------------------- -Alternatively, you can download other Docker images that contain only features -available under the Apache 2.0 license. To download the images, go to +Alternatively, you can download other Docker images that contain only features +available under the Apache 2.0 license. To download the images, go to https://www.docker.elastic.co[www.docker.elastic.co]. [float] @@ -96,7 +96,7 @@ Some example translations are shown here: `KIBANA_DEFAULTAPPID`:: `kibana.defaultAppId` `XPACK_MONITORING_ENABLED`:: `xpack.monitoring.enabled` -In general, any setting listed in <> can be +In general, any setting listed in <> can be configured with this technique. These variables can be set with +docker-compose+ like this: @@ -135,5 +135,5 @@ with a <> or via <>. IMPORTANT: If replacing `kibana.yml` with a custom version, be sure to copy the -above defaults to the custom file if you want to retain them. If not, they will +defaults to the custom file if you want to retain them. If not, they will be "masked" by the new file. diff --git a/docs/setup/settings.asciidoc b/docs/setup/settings.asciidoc index 3d99e7298755f6..80d04c260e25f3 100644 --- a/docs/setup/settings.asciidoc +++ b/docs/setup/settings.asciidoc @@ -234,7 +234,8 @@ FeatureCollection. The file must use the https://en.wikipedia.org/wiki/World_Geodetic_System[WGS84 coordinate reference system (ESPG:4326)] and only include polygons. If the file is hosted on a separate domain from Kibana, the server needs to be CORS-enabled so Kibana can download the file. -The following example shows a valid regionmap configuration. +[[region-map-configuration-example]] +The following example shows a valid region map configuration. + -- map @@ -262,7 +263,7 @@ References the originating source of the geojson file. Supported on {ece}. [[regionmap-fields]]`map.regionmap.layers[].fields[]:`:: Mandatory. Each layer can contain multiple fields to indicate what properties from the geojson -features you wish to expose. The example above shows how to define multiple +features you wish to expose. This <> shows how to define multiple properties. Supported on {ece}. [[regionmap-field-description]]`map.regionmap.layers[].fields[].description:`:: diff --git a/docs/user/introduction.asciidoc b/docs/user/introduction.asciidoc index bbaf22b4978682..8b987f81779e37 100644 --- a/docs/user/introduction.asciidoc +++ b/docs/user/introduction.asciidoc @@ -85,7 +85,7 @@ image::images/intro-dashboard.png[] * <> allows you to display your data in line charts, bar graphs, pie charts, histograms, and tables -(just to name a few). It's also home to *Lens*, mentioned above. +(just to name a few). It's also home to *Lens*, the drag-and-drop interface. *Visualize* supports the ability to add interactive controls to your dashboard, and filter dashboard content in real time. diff --git a/docs/user/reporting/development/pdf-integration.asciidoc b/docs/user/reporting/development/pdf-integration.asciidoc index dc9e63f34b25eb..af5ba5be1636e3 100644 --- a/docs/user/reporting/development/pdf-integration.asciidoc +++ b/docs/user/reporting/development/pdf-integration.asciidoc @@ -51,8 +51,7 @@ should have their screenshot taken and when the Visualizations are done renderin The print layout takes a screenshot of every element with the `data-shared-item` attribute and includes the individual screenshots in the PDF. The print layout also uses the `data-title` and `data-description` -attributes on the same HTMLElement as the `data-shared-item` to specify the title and description -that appears right above the individual screenshots. +attributes on the same HTMLElement as the `data-shared-item` to specify the title and description. The preserve layout takes a screenshot of the element with the `data-shared-items-container` attribute. Additionally, reporting will resize the element with the `data-shared-items-container` to be the size specified in the layout dimensions. @@ -63,4 +62,4 @@ Reporting needs to determine when all of the visualizations have completed rende If there are multiple visualizations, the `data-shared-items-count` attribute should be specified to let Reporting know how many Visualizations to look for. Reporting will look at every element with the `data-shared-item` attribute and use the corresponding `data-render-complete` attribute and `renderComplete` events to listen for rendering to complete. When rendering is complete for a visualization -the `data-render-complete` attribute should be set to "true" and it should dispatch a custom DOM `renderComplete` event. \ No newline at end of file +the `data-render-complete` attribute should be set to "true" and it should dispatch a custom DOM `renderComplete` event. diff --git a/docs/user/security/securing-communications/elasticsearch-mutual-tls.asciidoc b/docs/user/security/securing-communications/elasticsearch-mutual-tls.asciidoc index 8d64a0e6e0c113..f5192f4641d4df 100644 --- a/docs/user/security/securing-communications/elasticsearch-mutual-tls.asciidoc +++ b/docs/user/security/securing-communications/elasticsearch-mutual-tls.asciidoc @@ -63,7 +63,7 @@ you have hostname verification enabled on {es}. -- {es} needs the appropriate CA certificate chain to properly establish trust when receiving connections from {kib}. -If you followed the instructions above to generate a client certificate, then you will have a PKCS#12 file for {kib}. You can extract the CA +If you followed the instructions to generate a client certificate, then you will have a PKCS#12 file for {kib}. You can extract the CA certificate chain from this file. For example: [source,sh] @@ -165,6 +165,6 @@ attempt to use them to authenticate to {es} via the native realm. . Restart {kib}. -NOTE: The steps above enable {kib} to authenticate to {es} using a certificate. However, end users will only be able to authenticate to +These steps enable {kib} to authenticate to {es} using a certificate. However, end users will only be able to authenticate to {kib} with a username and password. To allow end users to authenticate to {kib} using a client certificate, see <>. diff --git a/docs/user/security/securing-communications/index.asciidoc b/docs/user/security/securing-communications/index.asciidoc index 2ac08a4fab5ee5..97313c19f44cb9 100644 --- a/docs/user/security/securing-communications/index.asciidoc +++ b/docs/user/security/securing-communications/index.asciidoc @@ -150,7 +150,7 @@ elasticsearch.ssl.certificateAuthorities: ["/path/to/elasticsearch-ca.pem"] + -- WARNING: You should not use a PKCS#12 file that contains a private key. This is an unnecessary security risk. If you only have a PKCS#12 -file that contains a private key, a safer approach is to extract the CA certificate chain in PEM format as described above. +file that contains a private key, a safer approach is to extract the CA certificate chain in PEM format. Specify your PKCS#12 file in `kibana.yml`: @@ -188,5 +188,5 @@ verification. For more information about this setting, see <>. +. Optional: <>. . Optional: <>. @@ -103,8 +103,8 @@ You can manage privileges on the *Management / Security / Roles* page in {kib}. If you're using the native realm with Basic Authentication, you can assign roles using the *Management / Security / Users* page in {kib} or the -{ref}/security-api.html#security-user-apis[user management APIs]. For example, -the following creates a user named `jacknich` and assigns it the `kibana_admin` +{ref}/security-api.html#security-user-apis[user management APIs]. For example, +the following creates a user named `jacknich` and assigns it the `kibana_admin` role: [source,js] @@ -131,8 +131,8 @@ on specific index patterns. For more information, see . Verify that you can log in as a user. If you are running {kib} locally, go to `https://localhost:5601` and enter the credentials for a -user you've assigned a {kib} user role. For example, you could log in as the -`jacknich` user created above. +user you've assigned a {kib} user role. For example, you could log in as the user +`jacknich`. + -- diff --git a/docs/visualize/vega.asciidoc b/docs/visualize/vega.asciidoc index d5b7ccb12f48c3..c9cf1e7aeb8205 100644 --- a/docs/visualize/vega.asciidoc +++ b/docs/visualize/vega.asciidoc @@ -18,47 +18,47 @@ NOTE: In Vega it is possible to load data dynamically, e.g. by setting signals a * To experiment using sample data, first click the {kib} logo in the upper left hand corner and then click the link next to *Sample Data*. -* Once you have data loaded, go to *Visualize*, click *+*, and select *Vega* to see an example graph. -*Note*: The default graph is written in Vega-Lite, but you can build visualizations -in either language. See <> for more information. +* Once you have data loaded, go to *Visualize*, click *+*, and select *Vega* to see an example graph. +*Note*: The default graph is written in Vega-Lite, but you can build visualizations +in either language. See <> for more information. * Try changing `mark` from `line` to `point`, `area`, `bar`, `circle`, -or `square`. Check out the +or `square`. Check out the https://vega.github.io/vega-lite/docs/mark.html#mark-def[Vega-Lite docs] for more information. * Explore other available https://vega.github.io/vega/examples/[Vega] or -https://vega.github.io/vega-lite/examples/[Vega-Lite] visualizations. +https://vega.github.io/vega-lite/examples/[Vega-Lite] visualizations. *Note*: You might need to make URLs absolute, for example, replace `"url": "data/world-110m.json"` with -`"url": "https://vega.github.io/editor/data/world-110m.json"`. +`"url": "https://vega.github.io/editor/data/world-110m.json"`. See <>. -* For more information on getting started, check out this https://www.elastic.co/blog/getting-started-with-vega-visualizations-in-kibana[blog post]. +* For more information on getting started, check out this https://www.elastic.co/blog/getting-started-with-vega-visualizations-in-kibana[blog post]. [[vega-vs-vegalite]] === Vega vs Vega-Lite -The Vega visualization in {kib} supports both Vega and Vega-Lite. You can use the -`schema` value to define which language you would like to use and its minimum +The Vega visualization in {kib} supports both Vega and Vega-Lite. You can use the +`schema` value to define which language you would like to use and its minimum required version. - -For example: + +For example: * Vega-Lite v2: `$schema: https://vega.github.io/schema/vega-lite/v2.json` * Vega v4: `$schema: https://vega.github.io/schema/vega/v4.json` - + The `schema` URL is only used for identification, and does not need to be accessible by {kib}. -Vega-Lite is a simplified version of Vega; it automates some constructions and has -much shorter specifications than Vega. Vega-Lite is automatically converted into +Vega-Lite is a simplified version of Vega; it automates some constructions and has +much shorter specifications than Vega. Vega-Lite is automatically converted into Vega before rendering, but it has some limitations, and there are some visualizations that can be expressed in Vega that cannot be expressed in Vega-Lite. You can learn more in the https://vega.github.io/vega-lite/[Vega-Lite documentation]. You can use https://vega.github.io/editor/[this editor] to convert Vega-Lite into -Vega. +Vega. -When you create a Vega visualization in {kib}, you can edit the `schema` -value in the dev tools to the left of the graph to define which of the two expression -languages you would like to use (see examples above). +When you create a Vega visualization in {kib}, you can edit the `schema` +value in the dev tools to the left of the graph to define which of the two expression +languages you would like to use. [[vega-querying-elasticsearch]] @@ -176,7 +176,7 @@ except that the timerange is shifted back by 10 minutes: ---- The `"%timefilter%"` can also be used to specify a single min or max -value. As shown above, the date_histogram's `extended_bounds` can be set +value. The date_histogram's `extended_bounds` can be set with two values - min and max. Instead of hardcoding a value, you may use `"min": {"%timefilter%": "min"}`, which will be replaced with the beginning of the current time range. The `shift` and `unit` values are @@ -234,7 +234,7 @@ the graph must specify `type=map` in the host configuration: // defaults to true, shows +/- buttons to zoom in/out "zoomControl": false, - // Defaults to 'false', disables mouse wheel zoom. If set to + // Defaults to 'false', disables mouse wheel zoom. If set to // 'true', map may zoom unexpectedly while scrolling dashboard "scrollWheelZoom": false, @@ -295,7 +295,7 @@ to your kibana.yml file. === Useful Links ==== Vega Editor -The https://vega.github.io/editor/[Vega Editor] includes examples for Vega & Vega-Lite, but does not support any +The https://vega.github.io/editor/[Vega Editor] includes examples for Vega & Vega-Lite, but does not support any {kib}-specific features like {es} requests and interactive base maps. ==== Vega-Lite resources @@ -303,14 +303,14 @@ The https://vega.github.io/editor/[Vega Editor] includes examples for Vega & Veg * https://vega.github.io/vega-lite/docs/[Docs] * https://vega.github.io/vega-lite/examples/[Examples] -==== Vega resources +==== Vega resources * https://vega.github.io/vega/tutorials/[Tutorials] * https://vega.github.io/vega/docs/[Docs] * https://vega.github.io/vega/examples/[Examples] ==== Elastic blog posts * https://www.elastic.co/blog/getting-started-with-vega-visualizations-in-kibana[Getting Started with Vega Visualizations in Kibana] -* https://www.elastic.co/blog/custom-vega-visualizations-in-kibana[Custom Vega Visualizations in Kibana] +* https://www.elastic.co/blog/custom-vega-visualizations-in-kibana[Custom Vega Visualizations in Kibana] * https://www.elastic.co/blog/sankey-visualization-with-vega-in-kibana[Sankey Visualization with Vega in Kibana] From 51fb32bbf3e6d037105c53e8dc40637c54ad677c Mon Sep 17 00:00:00 2001 From: Ben Skelker <54019610+benskelker@users.noreply.github.com> Date: Mon, 9 Mar 2020 17:58:13 +0200 Subject: [PATCH 11/20] removes beta tag (#59618) --- docs/siem/index.asciidoc | 1 - docs/siem/siem-ui.asciidoc | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/siem/index.asciidoc b/docs/siem/index.asciidoc index a15d860d76775b..9d17b5209304f0 100644 --- a/docs/siem/index.asciidoc +++ b/docs/siem/index.asciidoc @@ -4,7 +4,6 @@ [partintro] -- -beta[] The SIEM app in Kibana provides an interactive workspace for security teams to triage events and perform initial investigations. It enables analysis of diff --git a/docs/siem/siem-ui.asciidoc b/docs/siem/siem-ui.asciidoc index f01575a21b9f67..85253daaf29330 100644 --- a/docs/siem/siem-ui.asciidoc +++ b/docs/siem/siem-ui.asciidoc @@ -35,7 +35,7 @@ image::siem/images/network-ui.png[] [float] [[detections-ui]] -=== Detections +=== Detections (Beta) The Detections feature automatically searches for threats and creates signals when they are detected. Signal detection rules define the conditions From 239ca74fc9e24eb141daa79095a01acc00fb60ee Mon Sep 17 00:00:00 2001 From: CJ Cenizal Date: Mon, 9 Mar 2020 09:55:59 -0700 Subject: [PATCH 12/20] Fix slm_ui setting by changing camel case back to snake case. (#59663) --- .../__jest__/client_integration/helpers/setup_environment.tsx | 2 +- x-pack/plugins/snapshot_restore/public/application/app.tsx | 2 +- .../snapshot_restore/public/application/sections/home/home.tsx | 2 +- x-pack/plugins/snapshot_restore/public/types.ts | 2 +- x-pack/plugins/snapshot_restore/server/config.ts | 2 +- x-pack/plugins/snapshot_restore/server/index.ts | 2 +- x-pack/plugins/snapshot_restore/server/plugin.ts | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/x-pack/plugins/snapshot_restore/__jest__/client_integration/helpers/setup_environment.tsx b/x-pack/plugins/snapshot_restore/__jest__/client_integration/helpers/setup_environment.tsx index 741ad40f7d1cbf..827fea7021eb46 100644 --- a/x-pack/plugins/snapshot_restore/__jest__/client_integration/helpers/setup_environment.tsx +++ b/x-pack/plugins/snapshot_restore/__jest__/client_integration/helpers/setup_environment.tsx @@ -35,7 +35,7 @@ const appDependencies = { core: coreMock.createSetup(), services, config: { - slmUi: { enabled: true }, + slm_ui: { enabled: true }, }, plugins: {}, }; diff --git a/x-pack/plugins/snapshot_restore/public/application/app.tsx b/x-pack/plugins/snapshot_restore/public/application/app.tsx index 5f240a7335ecc7..77ef697814b2c4 100644 --- a/x-pack/plugins/snapshot_restore/public/application/app.tsx +++ b/x-pack/plugins/snapshot_restore/public/application/app.tsx @@ -24,7 +24,7 @@ import { useConfig } from './app_context'; import { AuthorizationContext, WithPrivileges, NotAuthorizedSection } from './lib/authorization'; export const App: React.FunctionComponent = () => { - const { slmUi } = useConfig(); + const { slm_ui: slmUi } = useConfig(); const { apiError } = useContext(AuthorizationContext); const sections: Section[] = ['repositories', 'snapshots', 'restore_status']; diff --git a/x-pack/plugins/snapshot_restore/public/application/sections/home/home.tsx b/x-pack/plugins/snapshot_restore/public/application/sections/home/home.tsx index 81e7cb895297e5..1d9f2ca5e9e4f5 100644 --- a/x-pack/plugins/snapshot_restore/public/application/sections/home/home.tsx +++ b/x-pack/plugins/snapshot_restore/public/application/sections/home/home.tsx @@ -41,7 +41,7 @@ export const SnapshotRestoreHome: React.FunctionComponent { - const { slmUi } = useConfig(); + const { slm_ui: slmUi } = useConfig(); const tabs: Array<{ id: Section; diff --git a/x-pack/plugins/snapshot_restore/public/types.ts b/x-pack/plugins/snapshot_restore/public/types.ts index 82fecd8c40ecb2..f7003aa6a8e2ca 100644 --- a/x-pack/plugins/snapshot_restore/public/types.ts +++ b/x-pack/plugins/snapshot_restore/public/types.ts @@ -5,5 +5,5 @@ */ export interface ClientConfigType { - slmUi: { enabled: boolean }; + slm_ui: { enabled: boolean }; } diff --git a/x-pack/plugins/snapshot_restore/server/config.ts b/x-pack/plugins/snapshot_restore/server/config.ts index db8c0735ae2d55..0ed3392b553347 100644 --- a/x-pack/plugins/snapshot_restore/server/config.ts +++ b/x-pack/plugins/snapshot_restore/server/config.ts @@ -8,7 +8,7 @@ import { schema, TypeOf } from '@kbn/config-schema'; export const configSchema = schema.object({ enabled: schema.boolean({ defaultValue: true }), - slmUi: schema.object({ + slm_ui: schema.object({ enabled: schema.boolean({ defaultValue: true }), }), }); diff --git a/x-pack/plugins/snapshot_restore/server/index.ts b/x-pack/plugins/snapshot_restore/server/index.ts index cc77aa13163a5f..1bbf482b4d4965 100644 --- a/x-pack/plugins/snapshot_restore/server/index.ts +++ b/x-pack/plugins/snapshot_restore/server/index.ts @@ -12,6 +12,6 @@ export const plugin = (ctx: PluginInitializerContext) => new SnapshotRestoreServ export const config: PluginConfigDescriptor = { schema: configSchema, exposeToBrowser: { - slmUi: true, + slm_ui: true, }, }; diff --git a/x-pack/plugins/snapshot_restore/server/plugin.ts b/x-pack/plugins/snapshot_restore/server/plugin.ts index a6daa12767c7cf..00ff3db976d661 100644 --- a/x-pack/plugins/snapshot_restore/server/plugin.ts +++ b/x-pack/plugins/snapshot_restore/server/plugin.ts @@ -86,7 +86,7 @@ export class SnapshotRestoreServerPlugin implements Plugin config: { isSecurityEnabled: security !== undefined, isCloudEnabled: cloud !== undefined && cloud.isCloudEnabled, - isSlmEnabled: pluginConfig.slmUi.enabled, + isSlmEnabled: pluginConfig.slm_ui.enabled, }, lib: { isEsError, From 4bd7b364313a09b0422d900956152650d2c94f82 Mon Sep 17 00:00:00 2001 From: Robert Oskamp Date: Mon, 9 Mar 2020 18:03:47 +0100 Subject: [PATCH 13/20] [ML] Functional API tests - bucket span estimation with custom search.max_buckets (#59665) This PR adds functional API tests for the bucket span estimation endpoint with a transient or persistent `search.max_buckets` setting. --- .../apis/ml/bucket_span_estimator.ts | 67 ++++++++++++++++++- 1 file changed, 64 insertions(+), 3 deletions(-) diff --git a/x-pack/test/api_integration/apis/ml/bucket_span_estimator.ts b/x-pack/test/api_integration/apis/ml/bucket_span_estimator.ts index 1c7245234b089f..a50d65a48c2bb3 100644 --- a/x-pack/test/api_integration/apis/ml/bucket_span_estimator.ts +++ b/x-pack/test/api_integration/apis/ml/bucket_span_estimator.ts @@ -16,6 +16,7 @@ const COMMON_HEADERS = { // eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext) => { const esArchiver = getService('esArchiver'); + const esSupertest = getService('esSupertest'); const supertest = getService('supertestWithoutAuth'); const mlSecurity = getService('mlSecurity'); @@ -97,8 +98,39 @@ export default ({ getService }: FtrProviderContext) => { await esArchiver.unload('ml/ecommerce'); }); - for (const testData of testDataList) { - it(`estimates the bucket span ${testData.testTitleSuffix}`, async () => { + describe('with default settings', function() { + for (const testData of testDataList) { + it(`estimates the bucket span ${testData.testTitleSuffix}`, async () => { + const { body } = await supertest + .post('/api/ml/validate/estimate_bucket_span') + .auth(testData.user, mlSecurity.getPasswordForUser(testData.user)) + .set(COMMON_HEADERS) + .send(testData.requestBody) + .expect(testData.expected.responseCode); + + expect(body).to.eql(testData.expected.responseBody); + }); + } + }); + + describe('with transient search.max_buckets setting', function() { + before(async () => { + await esSupertest + .put('/_cluster/settings') + .send({ transient: { 'search.max_buckets': 9000 } }) + .expect(200); + }); + + after(async () => { + await esSupertest + .put('/_cluster/settings') + .send({ transient: { 'search.max_buckets': null } }) + .expect(200); + }); + + const testData = testDataList[0]; + + it(`estimates the bucket span`, async () => { const { body } = await supertest .post('/api/ml/validate/estimate_bucket_span') .auth(testData.user, mlSecurity.getPasswordForUser(testData.user)) @@ -108,6 +140,35 @@ export default ({ getService }: FtrProviderContext) => { expect(body).to.eql(testData.expected.responseBody); }); - } + }); + + describe('with persistent search.max_buckets setting', function() { + before(async () => { + await esSupertest + .put('/_cluster/settings') + .send({ persistent: { 'search.max_buckets': 9000 } }) + .expect(200); + }); + + after(async () => { + await esSupertest + .put('/_cluster/settings') + .send({ persistent: { 'search.max_buckets': null } }) + .expect(200); + }); + + const testData = testDataList[0]; + + it(`estimates the bucket span`, async () => { + const { body } = await supertest + .post('/api/ml/validate/estimate_bucket_span') + .auth(testData.user, mlSecurity.getPasswordForUser(testData.user)) + .set(COMMON_HEADERS) + .send(testData.requestBody) + .expect(testData.expected.responseCode); + + expect(body).to.eql(testData.expected.responseBody); + }); + }); }); }; From 6e5e8c815e0eab44e78af025a8ddada46b6244e2 Mon Sep 17 00:00:00 2001 From: Thomas Neirynck Date: Mon, 9 Mar 2020 13:22:30 -0400 Subject: [PATCH 14/20] [Maps] Support categorical styling for numbers and dates (#57908) --- .../legacy/plugins/maps/common/constants.ts | 1 + .../plugins/maps/common/descriptor_types.d.ts | 8 +- .../maps/public/layers/fields/es_doc_field.js | 5 - .../plugins/maps/public/layers/layer.js | 12 --- .../public/layers/sources/es_agg_source.d.ts | 6 ++ .../public/layers/sources/es_agg_source.js | 8 +- .../layers/sources/es_agg_source.test.ts | 82 ++++++++++++++ .../es_geo_grid_source.d.ts | 3 + .../es_geo_grid_source/es_geo_grid_source.js | 2 +- .../es_geo_grid_source.test.ts | 41 +++++++ .../es_search_source/es_search_source.js | 44 -------- .../layers/sources/es_term_source.test.js | 13 +-- .../public/layers/sources/vector_source.js | 12 --- .../components/color/color_map_select.js | 100 ++++++++++++++---- .../components/color/dynamic_color_form.js | 67 ++++++++---- .../vector/components/vector_style_editor.js | 56 ++++------ .../properties/dynamic_color_property.test.js | 30 ++++++ .../maps/public/layers/vector_layer.js | 13 --- 18 files changed, 320 insertions(+), 183 deletions(-) create mode 100644 x-pack/legacy/plugins/maps/public/layers/sources/es_agg_source.test.ts create mode 100644 x-pack/legacy/plugins/maps/public/layers/sources/es_geo_grid_source/es_geo_grid_source.test.ts diff --git a/x-pack/legacy/plugins/maps/common/constants.ts b/x-pack/legacy/plugins/maps/common/constants.ts index 53289fbbc9005a..a4afae0b9e077d 100644 --- a/x-pack/legacy/plugins/maps/common/constants.ts +++ b/x-pack/legacy/plugins/maps/common/constants.ts @@ -165,6 +165,7 @@ export const COLOR_MAP_TYPE = { export const COLOR_PALETTE_MAX_SIZE = 10; export const CATEGORICAL_DATA_TYPES = ['string', 'ip', 'boolean']; +export const ORDINAL_DATA_TYPES = ['number', 'date']; export const SYMBOLIZE_AS_TYPES = { CIRCLE: 'circle', diff --git a/x-pack/legacy/plugins/maps/common/descriptor_types.d.ts b/x-pack/legacy/plugins/maps/common/descriptor_types.d.ts index f03f828200bbd9..ce0743ba2baedf 100644 --- a/x-pack/legacy/plugins/maps/common/descriptor_types.d.ts +++ b/x-pack/legacy/plugins/maps/common/descriptor_types.d.ts @@ -35,11 +35,11 @@ export type AggDescriptor = { type: AGG_TYPE; }; -export type AbstractESAggDescriptor = AbstractESSourceDescriptor & { +export type AbstractESAggSourceDescriptor = AbstractESSourceDescriptor & { metrics: AggDescriptor[]; }; -export type ESGeoGridSourceDescriptor = AbstractESAggDescriptor & { +export type ESGeoGridSourceDescriptor = AbstractESAggSourceDescriptor & { requestType?: RENDER_AS; resolution?: GRID_RESOLUTION; }; @@ -54,12 +54,12 @@ export type ESSearchSourceDescriptor = AbstractESSourceDescriptor & { topHitsSize?: number; }; -export type ESPewPewSourceDescriptor = AbstractESAggDescriptor & { +export type ESPewPewSourceDescriptor = AbstractESAggSourceDescriptor & { sourceGeoField: string; destGeoField: string; }; -export type ESTermSourceDescriptor = AbstractESAggDescriptor & { +export type ESTermSourceDescriptor = AbstractESAggSourceDescriptor & { indexPatternTitle: string; term: string; // term field name }; diff --git a/x-pack/legacy/plugins/maps/public/layers/fields/es_doc_field.js b/x-pack/legacy/plugins/maps/public/layers/fields/es_doc_field.js index 0b90dbe47c6e9f..ea7641ed5e8dd6 100644 --- a/x-pack/legacy/plugins/maps/public/layers/fields/es_doc_field.js +++ b/x-pack/legacy/plugins/maps/public/layers/fields/es_doc_field.js @@ -57,11 +57,6 @@ export class ESDocField extends AbstractField { async getCategoricalFieldMetaRequest() { const field = await this._getField(); - if (field.type !== 'string') { - //UX does not support categorical styling for number/date fields - return null; - } - const topTerms = { size: COLOR_PALETTE_MAX_SIZE - 1, //need additional color for the "other"-value }; diff --git a/x-pack/legacy/plugins/maps/public/layers/layer.js b/x-pack/legacy/plugins/maps/public/layers/layer.js index b76f1ebce15d21..71e5d7b95e44fd 100644 --- a/x-pack/legacy/plugins/maps/public/layers/layer.js +++ b/x-pack/legacy/plugins/maps/public/layers/layer.js @@ -332,18 +332,6 @@ export class AbstractLayer { return []; } - async getDateFields() { - return []; - } - - async getNumberFields() { - return []; - } - - async getCategoricalFields() { - return []; - } - async getFields() { return []; } diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_agg_source.d.ts b/x-pack/legacy/plugins/maps/public/layers/sources/es_agg_source.d.ts index a91bb4a8bb1a7b..99ee1ec652b542 100644 --- a/x-pack/legacy/plugins/maps/public/layers/sources/es_agg_source.d.ts +++ b/x-pack/legacy/plugins/maps/public/layers/sources/es_agg_source.d.ts @@ -7,13 +7,19 @@ import { IESSource } from './es_source'; import { AbstractESSource } from './es_source'; import { AGG_TYPE } from '../../../common/constants'; +import { IESAggField } from '../fields/es_agg_field'; +import { AbstractESAggSourceDescriptor } from '../../../common/descriptor_types'; export interface IESAggSource extends IESSource { getAggKey(aggType: AGG_TYPE, fieldName: string): string; getAggLabel(aggType: AGG_TYPE, fieldName: string): string; + getMetricFields(): IESAggField[]; } export class AbstractESAggSource extends AbstractESSource implements IESAggSource { + constructor(sourceDescriptor: AbstractESAggSourceDescriptor, inspectorAdapters: object); + getAggKey(aggType: AGG_TYPE, fieldName: string): string; getAggLabel(aggType: AGG_TYPE, fieldName: string): string; + getMetricFields(): IESAggField[]; } diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_agg_source.js b/x-pack/legacy/plugins/maps/public/layers/sources/es_agg_source.js index 62f3369ceb3a36..9f4b89cadc7773 100644 --- a/x-pack/legacy/plugins/maps/public/layers/sources/es_agg_source.js +++ b/x-pack/legacy/plugins/maps/public/layers/sources/es_agg_source.js @@ -78,6 +78,10 @@ export class AbstractESAggSource extends AbstractESSource { } } + async getFields() { + return this.getMetricFields(); + } + getValueAggsDsl(indexPattern) { const valueAggsDsl = {}; this.getMetricFields().forEach(esAggMetric => { @@ -89,10 +93,6 @@ export class AbstractESAggSource extends AbstractESSource { return valueAggsDsl; } - async getNumberFields() { - return this.getMetricFields(); - } - async filterAndFormatPropertiesToHtmlForMetricFields(properties) { const metricFields = this.getMetricFields(); const tooltipPropertiesPromises = []; diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_agg_source.test.ts b/x-pack/legacy/plugins/maps/public/layers/sources/es_agg_source.test.ts new file mode 100644 index 00000000000000..848091586eb9cb --- /dev/null +++ b/x-pack/legacy/plugins/maps/public/layers/sources/es_agg_source.test.ts @@ -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; + * you may not use this file except in compliance with the Elastic License. + */ + +import { AbstractESAggSource } from './es_agg_source'; +import { IField } from '../fields/field'; +import { IESAggField } from '../fields/es_agg_field'; +import _ from 'lodash'; +import { AGG_TYPE } from '../../../common/constants'; +import { AggDescriptor } from '../../../common/descriptor_types'; + +jest.mock('ui/new_platform'); + +const sumFieldName = 'myFieldGettingSummed'; +const metricExamples = [ + { + type: AGG_TYPE.SUM, + field: sumFieldName, + label: 'my custom label', + }, + { + // metric config is invalid beause field is missing + type: AGG_TYPE.MAX, + }, + { + // metric config is valid because "count" metric does not need to provide field + type: AGG_TYPE.COUNT, + label: '', // should ignore empty label fields + }, +]; + +class TestESAggSource extends AbstractESAggSource { + constructor(metrics: AggDescriptor[]) { + super({ type: 'test', id: 'foobar', indexPatternId: 'foobarid', metrics }, []); + } +} + +describe('getMetricFields', () => { + it('should add default "count" metric when no metrics are provided', async () => { + const source = new TestESAggSource([]); + const metrics = source.getMetricFields(); + expect(metrics.length).toBe(1); + + expect(metrics[0].getName()).toEqual('doc_count'); + expect(await metrics[0].getLabel()).toEqual('count'); + }); + + it('should remove incomplete metric configurations', async () => { + const source = new TestESAggSource(metricExamples); + const metrics = source.getMetricFields(); + expect(metrics.length).toBe(2); + + expect(metrics[0].getRootName()).toEqual(sumFieldName); + expect(metrics[0].getName()).toEqual('sum_of_myFieldGettingSummed'); + expect(await metrics[0].getLabel()).toEqual('my custom label'); + + expect(metrics[1].getName()).toEqual('doc_count'); + expect(await metrics[1].getLabel()).toEqual('count'); + }); + + it('getMetrics should be identical to getFields', async () => { + const source = new TestESAggSource(metricExamples); + const metrics = source.getMetricFields(); + const fields = await source.getFields(); + + const getFieldMeta = async (field: IField) => { + const esAggField = field as IESAggField; // this ensures we can downcast correctly. + return { + name: esAggField.getName(), + label: await esAggField.getLabel(), + esDoc: esAggField.getRootName(), + }; + }; + + const metricsFieldMeta = await Promise.all(metrics.map(getFieldMeta)); + const fieldsFieldMeta = await Promise.all(fields.map(getFieldMeta)); + + expect(_.isEqual(metricsFieldMeta, fieldsFieldMeta)).toEqual(true); + }); +}); diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_geo_grid_source/es_geo_grid_source.d.ts b/x-pack/legacy/plugins/maps/public/layers/sources/es_geo_grid_source/es_geo_grid_source.d.ts index 652409b61fd722..48e90b6c41d51a 100644 --- a/x-pack/legacy/plugins/maps/public/layers/sources/es_geo_grid_source/es_geo_grid_source.d.ts +++ b/x-pack/legacy/plugins/maps/public/layers/sources/es_geo_grid_source/es_geo_grid_source.d.ts @@ -6,7 +6,10 @@ import { AbstractESAggSource } from '../es_agg_source'; import { ESGeoGridSourceDescriptor } from '../../../../common/descriptor_types'; +import { GRID_RESOLUTION } from '../../../../common/constants'; export class ESGeoGridSource extends AbstractESAggSource { constructor(sourceDescriptor: ESGeoGridSourceDescriptor, inspectorAdapters: unknown); + getGridResolution(): GRID_RESOLUTION; + getGeoGridPrecision(zoom: number): number; } diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_geo_grid_source/es_geo_grid_source.js b/x-pack/legacy/plugins/maps/public/layers/sources/es_geo_grid_source/es_geo_grid_source.js index 4987d052b8ab70..3b3e8004ded053 100644 --- a/x-pack/legacy/plugins/maps/public/layers/sources/es_geo_grid_source/es_geo_grid_source.js +++ b/x-pack/legacy/plugins/maps/public/layers/sources/es_geo_grid_source/es_geo_grid_source.js @@ -35,7 +35,7 @@ import { DynamicStyleProperty } from '../../styles/vector/properties/dynamic_sty import { StaticStyleProperty } from '../../styles/vector/properties/static_style_property'; import { DataRequestAbortError } from '../../util/data_request'; -const MAX_GEOTILE_LEVEL = 29; +export const MAX_GEOTILE_LEVEL = 29; export class ESGeoGridSource extends AbstractESAggSource { static type = ES_GEO_GRID; diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_geo_grid_source/es_geo_grid_source.test.ts b/x-pack/legacy/plugins/maps/public/layers/sources/es_geo_grid_source/es_geo_grid_source.test.ts new file mode 100644 index 00000000000000..727435c3cbfef1 --- /dev/null +++ b/x-pack/legacy/plugins/maps/public/layers/sources/es_geo_grid_source/es_geo_grid_source.test.ts @@ -0,0 +1,41 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +jest.mock('../../../kibana_services', () => {}); +jest.mock('ui/new_platform'); + +import { ESGeoGridSource } from './es_geo_grid_source'; +import { ES_GEO_GRID, GRID_RESOLUTION, RENDER_AS } from '../../../../common/constants'; + +describe('ESGeoGridSource', () => { + const geogridSource = new ESGeoGridSource( + { + id: 'foobar', + indexPatternId: 'fooIp', + geoField: 'bar', + metrics: [], + resolution: GRID_RESOLUTION.COARSE, + type: ES_GEO_GRID, + requestType: RENDER_AS.HEATMAP, + }, + {} + ); + + describe('getGridResolution', () => { + it('should echo gridResoltuion', () => { + expect(geogridSource.getGridResolution()).toBe(GRID_RESOLUTION.COARSE); + }); + }); + + describe('getGeoGridPrecision', () => { + it('should clamp geo-grid derived zoom to max geotile level supported by ES', () => { + expect(geogridSource.getGeoGridPrecision(29)).toBe(29); + }); + + it('should use heuristic to derive precision', () => { + expect(geogridSource.getGeoGridPrecision(10)).toBe(12); + }); + }); +}); diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_search_source/es_search_source.js b/x-pack/legacy/plugins/maps/public/layers/sources/es_search_source/es_search_source.js index 35332824361393..7f0e8707605120 100644 --- a/x-pack/legacy/plugins/maps/public/layers/sources/es_search_source/es_search_source.js +++ b/x-pack/legacy/plugins/maps/public/layers/sources/es_search_source/es_search_source.js @@ -19,7 +19,6 @@ import { ES_GEO_FIELD_TYPE, DEFAULT_MAX_BUCKETS_LIMIT, SORT_ORDER, - CATEGORICAL_DATA_TYPES, } from '../../../../common/constants'; import { i18n } from '@kbn/i18n'; import { getDataSourceLabel } from '../../../../common/i18n_getters'; @@ -135,49 +134,6 @@ export class ESSearchSource extends AbstractESSource { ); } - async getNumberFields() { - try { - const indexPattern = await this.getIndexPattern(); - return indexPattern.fields.getByType('number').map(field => { - return this.createField({ fieldName: field.name }); - }); - } catch (error) { - return []; - } - } - - async getDateFields() { - try { - const indexPattern = await this.getIndexPattern(); - return indexPattern.fields.getByType('date').map(field => { - return this.createField({ fieldName: field.name }); - }); - } catch (error) { - return []; - } - } - - async getCategoricalFields() { - try { - const indexPattern = await this.getIndexPattern(); - - const aggFields = []; - CATEGORICAL_DATA_TYPES.forEach(dataType => { - indexPattern.fields.getByType(dataType).forEach(field => { - if (field.aggregatable) { - aggFields.push(field); - } - }); - }); - return aggFields.map(field => { - return this.createField({ fieldName: field.name }); - }); - } catch (error) { - //error surfaces in the LayerTOC UI - return []; - } - } - async getFields() { try { const indexPattern = await this.getIndexPattern(); diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_term_source.test.js b/x-pack/legacy/plugins/maps/public/layers/sources/es_term_source.test.js index d6f9f6d2911e91..890b1e3aaac1f0 100644 --- a/x-pack/legacy/plugins/maps/public/layers/sources/es_term_source.test.js +++ b/x-pack/legacy/plugins/maps/public/layers/sources/es_term_source.test.js @@ -31,36 +31,27 @@ const metricExamples = [ ]; describe('getMetricFields', () => { - it('should add default "count" metric when no metrics are provided', async () => { + it('should override name and label of count metric', async () => { const source = new ESTermSource({ indexPatternTitle: indexPatternTitle, term: termFieldName, }); const metrics = source.getMetricFields(); - expect(metrics.length).toBe(1); - - expect(metrics[0].getAggType()).toEqual('count'); expect(metrics[0].getName()).toEqual('__kbnjoin__count_groupby_myIndex.myTermField'); expect(await metrics[0].getLabel()).toEqual('Count of myIndex'); }); - it('should remove incomplete metric configurations', async () => { + it('should override name and label of sum metric', async () => { const source = new ESTermSource({ indexPatternTitle: indexPatternTitle, term: termFieldName, metrics: metricExamples, }); const metrics = source.getMetricFields(); - expect(metrics.length).toBe(2); - - expect(metrics[0].getAggType()).toEqual('sum'); - expect(metrics[0].getRootName()).toEqual(sumFieldName); expect(metrics[0].getName()).toEqual( '__kbnjoin__sum_of_myFieldGettingSummed_groupby_myIndex.myTermField' ); expect(await metrics[0].getLabel()).toEqual('my custom label'); - - expect(metrics[1].getAggType()).toEqual('count'); expect(metrics[1].getName()).toEqual('__kbnjoin__count_groupby_myIndex.myTermField'); expect(await metrics[1].getLabel()).toEqual('Count of myIndex'); }); diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/vector_source.js b/x-pack/legacy/plugins/maps/public/layers/sources/vector_source.js index 8369ca562e14b3..0f74dd605c8f1b 100644 --- a/x-pack/legacy/plugins/maps/public/layers/sources/vector_source.js +++ b/x-pack/legacy/plugins/maps/public/layers/sources/vector_source.js @@ -111,19 +111,7 @@ export class AbstractVectorSource extends AbstractSource { return null; } - async getDateFields() { - return []; - } - - async getNumberFields() { - return []; - } - async getFields() { - return [...(await this.getDateFields()), ...(await this.getNumberFields())]; - } - - async getCategoricalFields() { return []; } diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/color_map_select.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/color_map_select.js index 436a92b6199090..0d4cf322d2a406 100644 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/color_map_select.js +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/color_map_select.js @@ -6,10 +6,11 @@ import React, { Component, Fragment } from 'react'; -import { EuiSuperSelect, EuiSpacer } from '@elastic/eui'; +import { EuiSpacer, EuiSelect, EuiSuperSelect, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { ColorStopsOrdinal } from './color_stops_ordinal'; import { COLOR_MAP_TYPE } from '../../../../../../common/constants'; import { ColorStopsCategorical } from './color_stops_categorical'; +import { i18n } from '@kbn/i18n'; const CUSTOM_COLOR_MAP = 'CUSTOM_COLOR_MAP'; @@ -27,6 +28,43 @@ export class ColorMapSelect extends Component { }; } + _renderColorMapToggle() { + const options = [ + { + value: COLOR_MAP_TYPE.ORDINAL, + text: i18n.translate('xpack.maps.styles.dynamicColorSelect.quantitativeLabel', { + defaultMessage: 'As number', + }), + }, + { + value: COLOR_MAP_TYPE.CATEGORICAL, + text: i18n.translate('xpack.maps.styles.dynamicColorSelect.qualitativeLabel', { + defaultMessage: 'As category', + }), + }, + ]; + + const selectedValue = this.props.styleProperty.isOrdinal() + ? COLOR_MAP_TYPE.ORDINAL + : COLOR_MAP_TYPE.CATEGORICAL; + + return ( + + ); + } + _onColorMapSelect = selectedValue => { const useCustomColorMap = selectedValue === CUSTOM_COLOR_MAP; this.props.onChange({ @@ -55,32 +93,32 @@ export class ColorMapSelect extends Component { return null; } + let colorStopEditor; if (this.props.colorMapType === COLOR_MAP_TYPE.ORDINAL) { - return ( - - - - + colorStopEditor = ( + ); - } - - return ( - - + } else + colorStopEditor = ( - + ); + + return ( + + {colorStopEditor} + ); } - render() { + _renderColorMapSelections() { const colorMapOptionsWithCustom = [ { value: CUSTOM_COLOR_MAP, @@ -98,15 +136,31 @@ export class ColorMapSelect extends Component { : ''; } + const toggle = this.props.showColorMapTypeToggle ? ( + {this._renderColorMapToggle()} + ) : null; + + return ( + + {toggle} + + + + + ); + } + + render() { return ( - + {this._renderColorMapSelections()} + {this._renderColorStopsInput()} ); diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/dynamic_color_form.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/dynamic_color_form.js index 3dc356c31cf30b..c6b68b7e944099 100644 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/dynamic_color_form.js +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/dynamic_color_form.js @@ -40,21 +40,44 @@ export function DynamicColorForm({ }; const onFieldChange = async ({ field }) => { - const { name, origin, type } = field; + const { name, origin, type: fieldType } = field; + const defaultColorMapType = CATEGORICAL_DATA_TYPES.includes(fieldType) + ? COLOR_MAP_TYPE.CATEGORICAL + : COLOR_MAP_TYPE.ORDINAL; onDynamicStyleChange(styleProperty.getStyleName(), { ...styleOptions, field: { name, origin }, - type: CATEGORICAL_DATA_TYPES.includes(type) - ? COLOR_MAP_TYPE.CATEGORICAL - : COLOR_MAP_TYPE.ORDINAL, + type: defaultColorMapType, + }); + }; + + const onColorMapTypeChange = async e => { + const colorMapType = e.target.value; + onDynamicStyleChange(styleProperty.getStyleName(), { + ...styleOptions, + type: colorMapType, + }); + }; + + const getField = () => { + const fieldName = styleProperty.getFieldName(); + if (!fieldName) { + return null; + } + + return fields.find(field => { + return field.name === fieldName; }); }; const renderColorMapSelect = () => { - if (!styleOptions.field || !styleOptions.field.name) { + const field = getField(); + if (!field) { return null; } + const showColorMapTypeToggle = !CATEGORICAL_DATA_TYPES.includes(field.type); + if (styleProperty.isOrdinal()) { return ( + ); + } else if (styleProperty.isCategorical()) { + return ( + ); } - - return ( - - ); }; return ( diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/vector_style_editor.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/vector_style_editor.js index 7daf85b68dd8e1..7ad36bd2ae33d1 100644 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/vector_style_editor.js +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/vector_style_editor.js @@ -26,13 +26,14 @@ import { VECTOR_SHAPE_TYPES } from '../../../sources/vector_feature_types'; import { i18n } from '@kbn/i18n'; import { EuiSpacer, EuiButtonGroup, EuiFormRow, EuiSwitch } from '@elastic/eui'; +import { CATEGORICAL_DATA_TYPES, ORDINAL_DATA_TYPES } from '../../../../../common/constants'; export class VectorStyleEditor extends Component { state = { dateFields: [], numberFields: [], - categoricalFields: [], fields: [], + ordinalAndCategoricalFields: [], defaultDynamicProperties: getDefaultDynamicProperties(), defaultStaticProperties: getDefaultStaticProperties(), supportedFeatures: undefined, @@ -64,33 +65,24 @@ export class VectorStyleEditor extends Component { }; }; - const dateFields = await this.props.layer.getDateFields(); - const dateFieldPromises = dateFields.map(getFieldMeta); - const dateFieldsArray = await Promise.all(dateFieldPromises); - if (this._isMounted && !_.isEqual(dateFieldsArray, this.state.dateFields)) { - this.setState({ dateFields: dateFieldsArray }); - } - - const numberFields = await this.props.layer.getNumberFields(); - const numberFieldPromises = numberFields.map(getFieldMeta); - const numberFieldsArray = await Promise.all(numberFieldPromises); - if (this._isMounted && !_.isEqual(numberFieldsArray, this.state.numberFields)) { - this.setState({ numberFields: numberFieldsArray }); - } - - const categoricalFields = await this.props.layer.getCategoricalFields(); - const categoricalFieldMeta = categoricalFields.map(getFieldMeta); - const categoricalFieldsArray = await Promise.all(categoricalFieldMeta); - if (this._isMounted && !_.isEqual(categoricalFieldsArray, this.state.categoricalFields)) { - this.setState({ categoricalFields: categoricalFieldsArray }); - } - + //These are all fields (only used for text labeling) const fields = await this.props.layer.getFields(); const fieldPromises = fields.map(getFieldMeta); - const fieldsArray = await Promise.all(fieldPromises); - if (this._isMounted && !_.isEqual(fieldsArray, this.state.fields)) { - this.setState({ fields: fieldsArray }); + const fieldsArrayAll = await Promise.all(fieldPromises); + if (!this._isMounted || _.isEqual(fieldsArrayAll, this.state.fields)) { + return; } + + this.setState({ + fields: fieldsArrayAll, + ordinalAndCategoricalFields: fieldsArrayAll.filter(field => { + return ( + CATEGORICAL_DATA_TYPES.includes(field.type) || ORDINAL_DATA_TYPES.includes(field.type) + ); + }), + dateFields: fieldsArrayAll.filter(field => field.type === 'date'), + numberFields: fieldsArrayAll.filter(field => field.type === 'number'), + }); } async _loadSupportedFeatures() { @@ -118,10 +110,6 @@ export class VectorStyleEditor extends Component { return [...this.state.dateFields, ...this.state.numberFields]; } - _getOrdinalAndCategoricalFields() { - return [...this.state.dateFields, ...this.state.numberFields, ...this.state.categoricalFields]; - } - _handleSelectedFeatureChange = selectedFeature => { this.setState({ selectedFeature }); }; @@ -172,7 +160,7 @@ export class VectorStyleEditor extends Component { onStaticStyleChange={this._onStaticStyleChange} onDynamicStyleChange={this._onDynamicStyleChange} styleProperty={this.props.styleProperties[VECTOR_STYLES.FILL_COLOR]} - fields={this._getOrdinalAndCategoricalFields()} + fields={this.state.ordinalAndCategoricalFields} defaultStaticStyleOptions={ this.state.defaultStaticProperties[VECTOR_STYLES.FILL_COLOR].options } @@ -193,7 +181,7 @@ export class VectorStyleEditor extends Component { onStaticStyleChange={this._onStaticStyleChange} onDynamicStyleChange={this._onDynamicStyleChange} styleProperty={this.props.styleProperties[VECTOR_STYLES.LINE_COLOR]} - fields={this._getOrdinalAndCategoricalFields()} + fields={this.state.ordinalAndCategoricalFields} defaultStaticStyleOptions={ this.state.defaultStaticProperties[VECTOR_STYLES.LINE_COLOR].options } @@ -249,7 +237,7 @@ export class VectorStyleEditor extends Component { onStaticStyleChange={this._onStaticStyleChange} onDynamicStyleChange={this._onDynamicStyleChange} styleProperty={this.props.styleProperties[VECTOR_STYLES.LABEL_COLOR]} - fields={this._getOrdinalAndCategoricalFields()} + fields={this.state.ordinalAndCategoricalFields} defaultStaticStyleOptions={ this.state.defaultStaticProperties[VECTOR_STYLES.LABEL_COLOR].options } @@ -282,7 +270,7 @@ export class VectorStyleEditor extends Component { onStaticStyleChange={this._onStaticStyleChange} onDynamicStyleChange={this._onDynamicStyleChange} styleProperty={this.props.styleProperties[VECTOR_STYLES.LABEL_BORDER_COLOR]} - fields={this._getOrdinalAndCategoricalFields()} + fields={this.state.ordinalAndCategoricalFields} defaultStaticStyleOptions={ this.state.defaultStaticProperties[VECTOR_STYLES.LABEL_BORDER_COLOR].options } @@ -335,7 +323,7 @@ export class VectorStyleEditor extends Component { onStaticStyleChange={this._onStaticStyleChange} onDynamicStyleChange={this._onDynamicStyleChange} styleProperty={this.props.styleProperties[VECTOR_STYLES.ICON]} - fields={this.state.categoricalFields} + fields={this.state.ordinalAndCategoricalFields} defaultStaticStyleOptions={ this.state.defaultStaticProperties[VECTOR_STYLES.ICON].options } diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/dynamic_color_property.test.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/dynamic_color_property.test.js index c2f7a1313d02ae..5b286e4ba120ec 100644 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/dynamic_color_property.test.js +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/dynamic_color_property.test.js @@ -236,3 +236,33 @@ test('Should pluck the categorical style-meta from fieldmeta', async () => { ], }); }); + +test('isCategorical should return true when type is categorical', async () => { + const categoricalColorStyle = makeProperty({ + type: COLOR_MAP_TYPE.CATEGORICAL, + colorCategory: 'palette_0', + }); + + expect(categoricalColorStyle.isOrdinal()).toEqual(false); + expect(categoricalColorStyle.isCategorical()).toEqual(true); +}); + +test('isOrdinal should return true when type is ordinal', async () => { + const ordinalColorStyle = makeProperty({ + type: undefined, + color: 'Blues', + }); + + expect(ordinalColorStyle.isOrdinal()).toEqual(true); + expect(ordinalColorStyle.isCategorical()).toEqual(false); +}); + +test('Should read out ordinal type correctly', async () => { + const ordinalColorStyle2 = makeProperty({ + type: COLOR_MAP_TYPE.ORDINAL, + colorCategory: 'palette_0', + }); + + expect(ordinalColorStyle2.isOrdinal()).toEqual(true); + expect(ordinalColorStyle2.isCategorical()).toEqual(false); +}); diff --git a/x-pack/legacy/plugins/maps/public/layers/vector_layer.js b/x-pack/legacy/plugins/maps/public/layers/vector_layer.js index 32fdbcf9654147..70bba3d91c7237 100644 --- a/x-pack/legacy/plugins/maps/public/layers/vector_layer.js +++ b/x-pack/legacy/plugins/maps/public/layers/vector_layer.js @@ -197,19 +197,6 @@ export class VectorLayer extends AbstractLayer { return joinFields; } - async getDateFields() { - return await this._source.getDateFields(); - } - - async getNumberFields() { - const numberFieldOptions = await this._source.getNumberFields(); - return [...numberFieldOptions, ...this._getJoinFields()]; - } - - async getCategoricalFields() { - return await this._source.getCategoricalFields(); - } - async getFields() { const sourceFields = await this._source.getFields(); return [...sourceFields, ...this._getJoinFields()]; From 12a3ccf5658fa729952ba313d574f081e43204bd Mon Sep 17 00:00:00 2001 From: Robert Austin Date: Mon, 9 Mar 2020 13:23:22 -0400 Subject: [PATCH 15/20] Use HTTP request schemas to create types, use those types in the client (#59340) * wip * wip * wip * will this work? * wip but it works * pedro * remove thing * remove TODOs * fix type issue * add tests to check that alert index api works * Revert "add tests to check that alert index api works" This reverts commit 5d40ca18337cf8deb63a0291150780ec094db016. * Moved schema * undoing my evils * fix comments. fix incorrect import Co-authored-by: Elastic Machine --- src/core/public/http/types.ts | 7 +- src/core/public/public.api.md | 2 +- .../plugins/endpoint/common/schema/README.md | 6 ++ .../schema/alert_index.ts} | 32 ++++---- x-pack/plugins/endpoint/common/types.ts | 78 +++++++++++++++++-- .../endpoint/store/alerts/middleware.ts | 3 +- .../endpoint/store/alerts/selectors.ts | 16 ++-- .../public/applications/endpoint/types.ts | 16 +--- .../common/clone_http_fetch_query.test.ts | 32 ++++++++ .../public/common/clone_http_fetch_query.ts | 22 ++++++ .../server/routes/alerts/alerts.test.ts | 14 ++-- .../routes/alerts/details/lib/pagination.ts | 10 +-- .../endpoint/server/routes/alerts/index.ts | 5 +- .../server/routes/alerts/lib/index.ts | 6 +- .../server/routes/alerts/lib/pagination.ts | 2 +- .../server/routes/alerts/list/handlers.ts | 10 +-- .../server/routes/alerts/list/index.ts | 1 - .../server/routes/alerts/list/lib/index.ts | 7 +- 18 files changed, 192 insertions(+), 77 deletions(-) create mode 100644 x-pack/plugins/endpoint/common/schema/README.md rename x-pack/plugins/endpoint/{server/routes/alerts/list/schemas.ts => common/schema/alert_index.ts} (81%) create mode 100644 x-pack/plugins/endpoint/public/common/clone_http_fetch_query.test.ts create mode 100644 x-pack/plugins/endpoint/public/common/clone_http_fetch_query.ts diff --git a/src/core/public/http/types.ts b/src/core/public/http/types.ts index 6370ae165282b0..c40ad74893ead7 100644 --- a/src/core/public/http/types.ts +++ b/src/core/public/http/types.ts @@ -205,7 +205,12 @@ export interface HttpRequestInit { /** @public */ export interface HttpFetchQuery { - [key: string]: string | number | boolean | undefined; + [key: string]: + | string + | number + | boolean + | undefined + | Array; } /** diff --git a/src/core/public/public.api.md b/src/core/public/public.api.md index cd956eb17531a9..f71a50e2927d88 100644 --- a/src/core/public/public.api.md +++ b/src/core/public/public.api.md @@ -610,7 +610,7 @@ export interface HttpFetchOptionsWithPath extends HttpFetchOptions { // @public (undocumented) export interface HttpFetchQuery { // (undocumented) - [key: string]: string | number | boolean | undefined; + [key: string]: string | number | boolean | undefined | Array; } // @public diff --git a/x-pack/plugins/endpoint/common/schema/README.md b/x-pack/plugins/endpoint/common/schema/README.md new file mode 100644 index 00000000000000..42abedd647e6b0 --- /dev/null +++ b/x-pack/plugins/endpoint/common/schema/README.md @@ -0,0 +1,6 @@ +# Schemas + +These schemas are used to validate, coerce, and provide types for the comms between the client, server, and ES. + +# Future work +In the future, we may be able to locate these under 'server'. diff --git a/x-pack/plugins/endpoint/server/routes/alerts/list/schemas.ts b/x-pack/plugins/endpoint/common/schema/alert_index.ts similarity index 81% rename from x-pack/plugins/endpoint/server/routes/alerts/list/schemas.ts rename to x-pack/plugins/endpoint/common/schema/alert_index.ts index ce2163ac96dd1c..e8e2e1af102d63 100644 --- a/x-pack/plugins/endpoint/server/routes/alerts/list/schemas.ts +++ b/x-pack/plugins/endpoint/common/schema/alert_index.ts @@ -3,13 +3,17 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { decode } from 'rison-node'; + +import { schema, Type } from '@kbn/config-schema'; import { i18n } from '@kbn/i18n'; -import { schema } from '@kbn/config-schema'; -import { esKuery } from '../../../../../../../src/plugins/data/server'; -import { EndpointAppConstants } from '../../../../common/types'; +import { decode } from 'rison-node'; +import { fromKueryExpression } from '../../../../../src/plugins/data/common'; +import { EndpointAppConstants } from '../types'; -export const alertListReqSchema = schema.object( +/** + * Used to validate GET requests against the index of the alerting APIs. + */ +export const alertingIndexGetQuerySchema = schema.object( { page_size: schema.maybe( schema.number({ @@ -26,31 +30,21 @@ export const alertListReqSchema = schema.object( schema.arrayOf(schema.string(), { minSize: 2, maxSize: 2, - }) + }) as Type<[string, string]> // Cast this to a string tuple. `@kbn/config-schema` doesn't do this automatically ), before: schema.maybe( schema.arrayOf(schema.string(), { minSize: 2, maxSize: 2, - }) + }) as Type<[string, string]> // Cast this to a string tuple. `@kbn/config-schema` doesn't do this automatically ), sort: schema.maybe(schema.string()), - order: schema.maybe( - schema.string({ - validate(value) { - if (value !== 'asc' && value !== 'desc') { - return i18n.translate('xpack.endpoint.alerts.errors.bad_sort_direction', { - defaultMessage: 'must be `asc` or `desc`', - }); - } - }, - }) - ), + order: schema.maybe(schema.oneOf([schema.literal('asc'), schema.literal('desc')])), query: schema.maybe( schema.string({ validate(value) { try { - esKuery.fromKueryExpression(value); + fromKueryExpression(value); } catch (err) { return i18n.translate('xpack.endpoint.alerts.errors.bad_kql', { defaultMessage: 'must be valid KQL', diff --git a/x-pack/plugins/endpoint/common/types.ts b/x-pack/plugins/endpoint/common/types.ts index b1e5ab015aa5fd..edcd2d7841b120 100644 --- a/x-pack/plugins/endpoint/common/types.ts +++ b/x-pack/plugins/endpoint/common/types.ts @@ -5,6 +5,9 @@ */ import { SearchResponse } from 'elasticsearch'; +import { TypeOf } from '@kbn/config-schema'; +import * as kbnConfigSchemaTypes from '@kbn/config-schema/target/types/types'; +import { alertingIndexGetQuerySchema } from './schema/alert_index'; /** * A deep readonly type that will make all children of a given object readonly recursively @@ -24,10 +27,7 @@ export type ImmutableMap = ReadonlyMap, Immutable>; export type ImmutableSet = ReadonlySet>; export type ImmutableObject = { readonly [K in keyof T]: Immutable }; -export enum Direction { - asc = 'asc', - desc = 'desc', -} +export type Direction = 'asc' | 'desc'; export class EndpointAppConstants { static BASE_API_URL = '/api/endpoint'; @@ -45,7 +45,6 @@ export class EndpointAppConstants { **/ static ALERT_LIST_DEFAULT_PAGE_SIZE = 10; static ALERT_LIST_DEFAULT_SORT = '@timestamp'; - static ALERT_LIST_DEFAULT_ORDER = Direction.desc; } export interface AlertResultList { @@ -336,3 +335,72 @@ export type ResolverEvent = EndpointEvent | LegacyEndpointEvent; * The PageId type is used for the payload when firing userNavigatedToPage actions */ export type PageId = 'alertsPage' | 'managementPage' | 'policyListPage'; + +/** + * Takes a @kbn/config-schema 'schema' type and returns a type that represents valid inputs. + * Similar to `TypeOf`, but allows strings as input for `schema.number()` (which is inline + * with the behavior of the validator.) Also, for `schema.object`, when a value is a `schema.maybe` + * the key will be marked optional (via `?`) so that you can omit keys for optional values. + * + * Use this when creating a value that will be passed to the schema. + * e.g. + * ```ts + * const input: KbnConfigSchemaInputTypeOf = value + * schema.validate(input) // should be valid + * ``` + */ +type KbnConfigSchemaInputTypeOf< + T extends kbnConfigSchemaTypes.Type +> = T extends kbnConfigSchemaTypes.ObjectType + ? KbnConfigSchemaInputObjectTypeOf< + T + > /** `schema.number()` accepts strings, so this type should accept them as well. */ + : kbnConfigSchemaTypes.Type extends T + ? TypeOf | string + : TypeOf; + +/** + * Works like ObjectResultType, except that 'maybe' schema will create an optional key. + * This allows us to avoid passing 'maybeKey: undefined' when constructing such an object. + * + * Instead of using this directly, use `InputTypeOf`. + */ +type KbnConfigSchemaInputObjectTypeOf< + T extends kbnConfigSchemaTypes.ObjectType +> = T extends kbnConfigSchemaTypes.ObjectType + ? { + /** Use ? to make the field optional if the prop accepts undefined. + * This allows us to avoid writing `field: undefined` for optional fields. + */ + [K in Exclude< + keyof P, + keyof KbnConfigSchemaNonOptionalProps

+ >]?: KbnConfigSchemaInputTypeOf; + } & + { [K in keyof KbnConfigSchemaNonOptionalProps

]: KbnConfigSchemaInputTypeOf } + : never; + +/** + * Takes the props of a schema.object type, and returns a version that excludes + * optional values. Used by `InputObjectTypeOf`. + * + * Instead of using this directly, use `InputTypeOf`. + */ +type KbnConfigSchemaNonOptionalProps = Pick< + Props, + { + [Key in keyof Props]: undefined extends TypeOf ? never : Key; + }[keyof Props] +>; + +/** + * Query params to pass to the alert API when fetching new data. + */ +export type AlertingIndexGetQueryInput = KbnConfigSchemaInputTypeOf< + typeof alertingIndexGetQuerySchema +>; + +/** + * Result of the validated query params when handling alert index requests. + */ +export type AlertingIndexGetQueryResult = TypeOf; diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/store/alerts/middleware.ts b/x-pack/plugins/endpoint/public/applications/endpoint/store/alerts/middleware.ts index 2cb381e901b4ed..339be7a4ec7f10 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/store/alerts/middleware.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/store/alerts/middleware.ts @@ -8,6 +8,7 @@ import { AlertResultList, AlertData } from '../../../../../common/types'; import { AppAction } from '../action'; import { MiddlewareFactory, AlertListState } from '../../types'; import { isOnAlertPage, apiQueryParams, hasSelectedAlert, uiQueryParams } from './selectors'; +import { cloneHttpFetchQuery } from '../../../../common/clone_http_fetch_query'; export const alertMiddlewareFactory: MiddlewareFactory = coreStart => { return api => next => async (action: AppAction) => { @@ -15,7 +16,7 @@ export const alertMiddlewareFactory: MiddlewareFactory = coreSta const state = api.getState(); if (action.type === 'userChangedUrl' && isOnAlertPage(state)) { const response: AlertResultList = await coreStart.http.get(`/api/endpoint/alerts`, { - query: apiQueryParams(state), + query: cloneHttpFetchQuery(apiQueryParams(state)), }); api.dispatch({ type: 'serverReturnedAlertsData', payload: response }); } diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/store/alerts/selectors.ts b/x-pack/plugins/endpoint/public/applications/endpoint/store/alerts/selectors.ts index 7ce7c2d08691ee..ca836f8b62bd20 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/store/alerts/selectors.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/store/alerts/selectors.ts @@ -9,15 +9,11 @@ import { createSelector, createStructuredSelector as createStructuredSelectorWithBadType, } from 'reselect'; -import { - AlertListState, - AlertingIndexUIQueryParams, - AlertsAPIQueryParams, - CreateStructuredSelector, -} from '../../types'; -import { Immutable } from '../../../../../common/types'; +import { AlertListState, AlertingIndexUIQueryParams, CreateStructuredSelector } from '../../types'; +import { Immutable, AlertingIndexGetQueryInput } from '../../../../../common/types'; const createStructuredSelector: CreateStructuredSelector = createStructuredSelectorWithBadType; + /** * Returns the Alert Data array from state */ @@ -82,7 +78,7 @@ export const uiQueryParams: ( */ export const apiQueryParams: ( state: AlertListState -) => Immutable = createSelector( +) => Immutable = createSelector( uiQueryParams, ({ page_size, page_index }) => ({ page_size, @@ -90,6 +86,10 @@ export const apiQueryParams: ( }) ); +/** + * True if the user has selected an alert to see details about. + * Populated via the browsers query params. + */ export const hasSelectedAlert: (state: AlertListState) => boolean = createSelector( uiQueryParams, ({ selected_alert: selectedAlert }) => selectedAlert !== undefined diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/types.ts b/x-pack/plugins/endpoint/public/applications/endpoint/types.ts index 6adb3d6adc2603..4ceb4cec23d0b8 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/types.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/types.ts @@ -5,7 +5,6 @@ */ import { Dispatch, MiddlewareAPI } from 'redux'; -import { CoreStart } from 'kibana/public'; import { EndpointMetadata, AlertData, @@ -14,6 +13,7 @@ import { ImmutableArray, } from '../../../common/types'; import { AppAction } from './store/action'; +import { CoreStart } from '../../../../../../src/core/public'; export { AppAction }; export type MiddlewareFactory = ( @@ -140,17 +140,3 @@ export interface AlertingIndexUIQueryParams { */ selected_alert?: string; } - -/** - * Query params to pass to the alert API when fetching new data. - */ -export interface AlertsAPIQueryParams { - /** - * Number of results to return. - */ - page_size?: string; - /** - * 0-based index of 'page' to return. - */ - page_index?: string; -} diff --git a/x-pack/plugins/endpoint/public/common/clone_http_fetch_query.test.ts b/x-pack/plugins/endpoint/public/common/clone_http_fetch_query.test.ts new file mode 100644 index 00000000000000..9ac6b8b29f462b --- /dev/null +++ b/x-pack/plugins/endpoint/public/common/clone_http_fetch_query.test.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; + * you may not use this file except in compliance with the Elastic License. + */ + +import { cloneHttpFetchQuery } from './clone_http_fetch_query'; +import { Immutable } from '../../common/types'; +import { HttpFetchQuery } from '../../../../../src/core/public'; + +describe('cloneHttpFetchQuery', () => { + it('can clone complex queries', () => { + const query: Immutable = { + a: 'a', + '1': 1, + undefined, + array: [1, 2, undefined], + }; + expect(cloneHttpFetchQuery(query)).toMatchInlineSnapshot(` + Object { + "1": 1, + "a": "a", + "array": Array [ + 1, + 2, + undefined, + ], + "undefined": undefined, + } + `); + }); +}); diff --git a/x-pack/plugins/endpoint/public/common/clone_http_fetch_query.ts b/x-pack/plugins/endpoint/public/common/clone_http_fetch_query.ts new file mode 100644 index 00000000000000..fdf1d6603830a8 --- /dev/null +++ b/x-pack/plugins/endpoint/public/common/clone_http_fetch_query.ts @@ -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; + * you may not use this file except in compliance with the Elastic License. + */ + +import { Immutable } from '../../common/types'; + +import { HttpFetchQuery } from '../../../../../src/core/public'; + +export function cloneHttpFetchQuery(query: Immutable): HttpFetchQuery { + const clone: HttpFetchQuery = {}; + for (const [key, value] of Object.entries(query)) { + if (Array.isArray(value)) { + clone[key] = [...value]; + } else { + // Array.isArray is not removing ImmutableArray from the union. + clone[key] = value as string | number | boolean; + } + } + return clone; +} diff --git a/x-pack/plugins/endpoint/server/routes/alerts/alerts.test.ts b/x-pack/plugins/endpoint/server/routes/alerts/alerts.test.ts index adf686381f9677..5f5e4be4ecd0a1 100644 --- a/x-pack/plugins/endpoint/server/routes/alerts/alerts.test.ts +++ b/x-pack/plugins/endpoint/server/routes/alerts/alerts.test.ts @@ -9,9 +9,9 @@ import { httpServiceMock, loggingServiceMock, } from '../../../../../../src/core/server/mocks'; -import { alertListReqSchema } from './list/schemas'; import { registerAlertRoutes } from './index'; import { EndpointConfigSchema } from '../../config'; +import { alertingIndexGetQuerySchema } from '../../../common/schema/alert_index'; describe('test alerts route', () => { let routerMock: jest.Mocked; @@ -31,7 +31,7 @@ describe('test alerts route', () => { it('should fail to validate when `page_size` is not a number', async () => { const validate = () => { - alertListReqSchema.validate({ + alertingIndexGetQuerySchema.validate({ page_size: 'abc', }); }; @@ -40,7 +40,7 @@ describe('test alerts route', () => { it('should validate when `page_size` is a number', async () => { const validate = () => { - alertListReqSchema.validate({ + alertingIndexGetQuerySchema.validate({ page_size: 25, }); }; @@ -49,7 +49,7 @@ describe('test alerts route', () => { it('should validate when `page_size` can be converted to a number', async () => { const validate = () => { - alertListReqSchema.validate({ + alertingIndexGetQuerySchema.validate({ page_size: '50', }); }; @@ -58,7 +58,7 @@ describe('test alerts route', () => { it('should allow either `page_index` or `after`, but not both', async () => { const validate = () => { - alertListReqSchema.validate({ + alertingIndexGetQuerySchema.validate({ page_index: 1, after: [123, 345], }); @@ -68,7 +68,7 @@ describe('test alerts route', () => { it('should allow either `page_index` or `before`, but not both', async () => { const validate = () => { - alertListReqSchema.validate({ + alertingIndexGetQuerySchema.validate({ page_index: 1, before: 'abc', }); @@ -78,7 +78,7 @@ describe('test alerts route', () => { it('should allow either `before` or `after`, but not both', async () => { const validate = () => { - alertListReqSchema.validate({ + alertingIndexGetQuerySchema.validate({ before: ['abc', 'def'], after: [123, 345], }); diff --git a/x-pack/plugins/endpoint/server/routes/alerts/details/lib/pagination.ts b/x-pack/plugins/endpoint/server/routes/alerts/details/lib/pagination.ts index 94aff64e75f017..16328587597f24 100644 --- a/x-pack/plugins/endpoint/server/routes/alerts/details/lib/pagination.ts +++ b/x-pack/plugins/endpoint/server/routes/alerts/details/lib/pagination.ts @@ -5,7 +5,6 @@ */ import { GetResponse, SearchResponse } from 'elasticsearch'; -import { RequestHandlerContext } from 'src/core/server'; import { AlertEvent, AlertHits, @@ -16,6 +15,7 @@ import { EndpointConfigType } from '../../../../config'; import { searchESForAlerts, Pagination } from '../../lib'; import { AlertSearchQuery, SearchCursor, AlertDetailsRequestParams } from '../../types'; import { BASE_ALERTS_ROUTE } from '../..'; +import { RequestHandlerContext } from '../../../../../../../../src/core/server'; /** * Pagination class for alert details. @@ -40,10 +40,10 @@ export class AlertDetailsPagination extends Pagination< const reqData: AlertSearchQuery = { pageSize: 1, sort: EndpointAppConstants.ALERT_LIST_DEFAULT_SORT, - order: EndpointAppConstants.ALERT_LIST_DEFAULT_ORDER, + order: 'desc', }; - if (direction === Direction.asc) { + if (direction === 'asc') { reqData.searchAfter = cursor; } else { reqData.searchBefore = cursor; @@ -67,7 +67,7 @@ export class AlertDetailsPagination extends Pagination< * Gets the next alert after this one. */ async getNextUrl(): Promise { - const response = await this.doSearch(Direction.asc, [ + const response = await this.doSearch('asc', [ this.data._source['@timestamp'].toString(), this.data._source.event.id, ]); @@ -78,7 +78,7 @@ export class AlertDetailsPagination extends Pagination< * Gets the alert before this one. */ async getPrevUrl(): Promise { - const response = await this.doSearch(Direction.desc, [ + const response = await this.doSearch('desc', [ this.data._source['@timestamp'].toString(), this.data._source.event.id, ]); diff --git a/x-pack/plugins/endpoint/server/routes/alerts/index.ts b/x-pack/plugins/endpoint/server/routes/alerts/index.ts index 2a4a127fb41bc2..09de11897813b5 100644 --- a/x-pack/plugins/endpoint/server/routes/alerts/index.ts +++ b/x-pack/plugins/endpoint/server/routes/alerts/index.ts @@ -6,8 +6,9 @@ import { IRouter } from 'kibana/server'; import { EndpointAppContext } from '../../types'; import { EndpointAppConstants } from '../../../common/types'; -import { alertListHandlerWrapper, alertListReqSchema } from './list'; +import { alertListHandlerWrapper } from './list'; import { alertDetailsHandlerWrapper, alertDetailsReqSchema } from './details'; +import { alertingIndexGetQuerySchema } from '../../../common/schema/alert_index'; export const BASE_ALERTS_ROUTE = `${EndpointAppConstants.BASE_API_URL}/alerts`; @@ -16,7 +17,7 @@ export function registerAlertRoutes(router: IRouter, endpointAppContext: Endpoin { path: BASE_ALERTS_ROUTE, validate: { - query: alertListReqSchema, + query: alertingIndexGetQuerySchema, }, options: { authRequired: true }, }, diff --git a/x-pack/plugins/endpoint/server/routes/alerts/lib/index.ts b/x-pack/plugins/endpoint/server/routes/alerts/lib/index.ts index ff5b33a978b2cb..39067e9c27709a 100644 --- a/x-pack/plugins/endpoint/server/routes/alerts/lib/index.ts +++ b/x-pack/plugins/endpoint/server/routes/alerts/lib/index.ts @@ -18,10 +18,10 @@ import { export { Pagination } from './pagination'; function reverseSortDirection(order: Direction): Direction { - if (order === Direction.asc) { - return Direction.desc; + if (order === 'asc') { + return 'desc'; } - return Direction.asc; + return 'asc'; } function buildQuery(query: AlertSearchQuery): JsonObject { diff --git a/x-pack/plugins/endpoint/server/routes/alerts/lib/pagination.ts b/x-pack/plugins/endpoint/server/routes/alerts/lib/pagination.ts index e8218fcfb0ed2d..fc408878f8956c 100644 --- a/x-pack/plugins/endpoint/server/routes/alerts/lib/pagination.ts +++ b/x-pack/plugins/endpoint/server/routes/alerts/lib/pagination.ts @@ -4,8 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -import { RequestHandlerContext } from 'src/core/server'; import { EndpointConfigType } from '../../../config'; +import { RequestHandlerContext } from '../../../../../../../src/core/server'; /** * Abstract Pagination class for determining next/prev urls, diff --git a/x-pack/plugins/endpoint/server/routes/alerts/list/handlers.ts b/x-pack/plugins/endpoint/server/routes/alerts/list/handlers.ts index 4fece65a65bfe0..93ec8d7aa3e679 100644 --- a/x-pack/plugins/endpoint/server/routes/alerts/list/handlers.ts +++ b/x-pack/plugins/endpoint/server/routes/alerts/list/handlers.ts @@ -3,18 +3,18 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { KibanaRequest, RequestHandler } from 'kibana/server'; +import { RequestHandler } from 'kibana/server'; import { EndpointAppContext } from '../../../types'; import { searchESForAlerts } from '../lib'; import { getRequestData, mapToAlertResultList } from './lib'; -import { AlertListRequestQuery } from '../types'; +import { AlertingIndexGetQueryResult } from '../../../../common/types'; export const alertListHandlerWrapper = function( endpointAppContext: EndpointAppContext -): RequestHandler { - const alertListHandler: RequestHandler = async ( +): RequestHandler { + const alertListHandler: RequestHandler = async ( ctx, - req: KibanaRequest, + req, res ) => { try { diff --git a/x-pack/plugins/endpoint/server/routes/alerts/list/index.ts b/x-pack/plugins/endpoint/server/routes/alerts/list/index.ts index 43a0745365c9b0..cf72ea4d199de1 100644 --- a/x-pack/plugins/endpoint/server/routes/alerts/list/index.ts +++ b/x-pack/plugins/endpoint/server/routes/alerts/list/index.ts @@ -4,5 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -export { alertListReqSchema } from './schemas'; export { alertListHandlerWrapper } from './handlers'; diff --git a/x-pack/plugins/endpoint/server/routes/alerts/list/lib/index.ts b/x-pack/plugins/endpoint/server/routes/alerts/list/lib/index.ts index fa083229100913..1c7a157a988ae3 100644 --- a/x-pack/plugins/endpoint/server/routes/alerts/list/lib/index.ts +++ b/x-pack/plugins/endpoint/server/routes/alerts/list/lib/index.ts @@ -15,13 +15,14 @@ import { AlertHits, EndpointAppConstants, ESTotal, + AlertingIndexGetQueryResult, } from '../../../../../common/types'; import { EndpointAppContext } from '../../../../types'; -import { AlertSearchQuery, AlertListRequestQuery } from '../../types'; +import { AlertSearchQuery } from '../../types'; import { AlertListPagination } from './pagination'; export const getRequestData = async ( - request: KibanaRequest, + request: KibanaRequest, endpointAppContext: EndpointAppContext ): Promise => { const config = await endpointAppContext.config(); @@ -29,7 +30,7 @@ export const getRequestData = async ( // Defaults not enforced by schema pageSize: request.query.page_size || EndpointAppConstants.ALERT_LIST_DEFAULT_PAGE_SIZE, sort: request.query.sort || EndpointAppConstants.ALERT_LIST_DEFAULT_SORT, - order: request.query.order || EndpointAppConstants.ALERT_LIST_DEFAULT_ORDER, + order: request.query.order || 'desc', dateRange: ((request.query.date_range !== undefined ? decode(request.query.date_range) : config.alertResultListDefaultDateRange) as unknown) as TimeRange, From e630950a3d814597a1ed1bc70abb063343a6a273 Mon Sep 17 00:00:00 2001 From: Matthias Wilhelm Date: Mon, 9 Mar 2020 18:45:14 +0100 Subject: [PATCH 16/20] [Discover] Migrate AppState/GlobalState to new app state helpers (#57175) * Replace AppState * Replace GlobalState * Adapt functional test * Sync initial app state to URL * Add jest tests * Refactoring to use use-default-behaviors="true" in kbn-top-nav * Cleanup code remove unnecessary imports and variables * Refactor to use syncQueryStateWithUrl & stopSyncingQueryAppStateWithStateContainer * Remove discoverPersistedState * Allow indexPattern switch without $route.reload() * Remove timeRangeObj because it's not needed * Create new getDefaultQuery in data plugin * Simplify check for null in sync_state_with_url.ts * Remove unused stateMonitorFactory Co-authored-by: Alexey Antonov --- .../public/discover/get_inner_angular.ts | 46 +-- .../kibana/public/discover/kibana_services.ts | 3 - .../discover/np_ready/angular/discover.html | 16 +- .../discover/np_ready/angular/discover.js | 382 +++++++++--------- .../np_ready/angular/discover_state.test.ts | 78 ++++ .../np_ready/angular/discover_state.ts | 223 ++++++++++ .../angular/doc_table/actions/columns.ts | 43 +- .../angular/doc_table/components/table_row.ts | 10 +- .../np_ready/angular/doc_table/doc_table.html | 2 - .../np_ready/angular/doc_table/doc_table.ts | 25 -- .../np_ready/angular/doc_table/index.ts | 4 +- .../public/discover/np_ready/application.ts | 2 - .../discover_index_pattern.test.tsx | 8 +- .../field_chooser/discover_index_pattern.tsx | 14 +- .../field_chooser/field_chooser.html | 4 +- .../components/field_chooser/field_chooser.js | 15 +- .../np_ready/embeddable/search_embeddable.ts | 12 +- src/plugins/data/public/index.ts | 1 + .../public/query/lib/get_default_query.ts | 27 ++ src/plugins/data/public/query/lib/index.ts | 1 + .../ui/search_bar/lib/use_saved_query.ts | 1 + test/functional/apps/discover/_discover.js | 1 + .../functional/apps/discover/_shared_links.js | 6 +- test/functional/apps/home/_navigation.ts | 4 +- 24 files changed, 606 insertions(+), 322 deletions(-) create mode 100644 src/legacy/core_plugins/kibana/public/discover/np_ready/angular/discover_state.test.ts create mode 100644 src/legacy/core_plugins/kibana/public/discover/np_ready/angular/discover_state.ts create mode 100644 src/plugins/data/public/query/lib/get_default_query.ts diff --git a/src/legacy/core_plugins/kibana/public/discover/get_inner_angular.ts b/src/legacy/core_plugins/kibana/public/discover/get_inner_angular.ts index 9c29e182c55d61..76d475c4f7f969 100644 --- a/src/legacy/core_plugins/kibana/public/discover/get_inner_angular.ts +++ b/src/legacy/core_plugins/kibana/public/discover/get_inner_angular.ts @@ -22,15 +22,9 @@ // They can stay even after NP cutover import angular from 'angular'; import { EuiIcon } from '@elastic/eui'; -// @ts-ignore -import { StateProvider } from 'ui/state_management/state'; import { i18nDirective, i18nFilter, I18nProvider } from '@kbn/i18n/angular'; import { CoreStart, LegacyCoreStart, IUiSettingsClient } from 'kibana/public'; // @ts-ignore -import { AppStateProvider } from 'ui/state_management/app_state'; -// @ts-ignore -import { GlobalStateProvider } from 'ui/state_management/global_state'; -// @ts-ignore import { StateManagementConfigProvider } from 'ui/state_management/config_provider'; // @ts-ignore import { KbnUrlProvider, RedirectWhenMissingProvider } from 'ui/url'; @@ -117,8 +111,6 @@ export function initializeInnerAngularModule( createLocalConfigModule(core.uiSettings); createLocalKbnUrlModule(); createLocalTopNavModule(navigation); - createLocalGlobalStateModule(); - createLocalAppStateModule(); createLocalStorageModule(); createElasticSearchModule(data); createPagerFactoryModule(); @@ -136,6 +128,7 @@ export function initializeInnerAngularModule( 'discoverPrivate', 'discoverDocTable', 'discoverPagerFactory', + 'discoverPromise', ]) .config(watchMultiDecorator) .directive('icon', reactDirective => reactDirective(EuiIcon)) @@ -153,9 +146,8 @@ export function initializeInnerAngularModule( 'discoverConfig', 'discoverI18n', 'discoverPrivate', + 'discoverPromise', 'discoverTopNav', - 'discoverGlobalState', - 'discoverAppState', 'discoverLocalStorageProvider', 'discoverEs', 'discoverDocTable', @@ -178,19 +170,6 @@ export function initializeInnerAngularModule( .service('debounce', ['$timeout', DebounceProviderTimeout]); } -export function createLocalGlobalStateModule() { - angular - .module('discoverGlobalState', [ - 'discoverPrivate', - 'discoverConfig', - 'discoverKbnUrl', - 'discoverPromise', - ]) - .service('globalState', function(Private: IPrivate) { - return Private(GlobalStateProvider); - }); -} - function createLocalKbnUrlModule() { angular .module('discoverKbnUrl', ['discoverPrivate', 'ngRoute']) @@ -236,26 +215,6 @@ function createLocalI18nModule() { .directive('i18nId', i18nDirective); } -function createLocalAppStateModule() { - angular - .module('discoverAppState', [ - 'discoverGlobalState', - 'discoverPrivate', - 'discoverConfig', - 'discoverKbnUrl', - 'discoverPromise', - ]) - .service('AppState', function(Private: IPrivate) { - return Private(AppStateProvider); - }) - .service('getAppState', function(Private: any) { - return Private(AppStateProvider).getAppState; - }) - .service('State', function(Private: any) { - return Private(StateProvider); - }); -} - function createLocalStorageModule() { angular .module('discoverLocalStorageProvider', ['discoverPrivate']) @@ -287,7 +246,6 @@ function createDocTableModule() { .module('discoverDocTable', [ 'discoverKbnUrl', 'discoverConfig', - 'discoverAppState', 'discoverPagerFactory', 'react', ]) diff --git a/src/legacy/core_plugins/kibana/public/discover/kibana_services.ts b/src/legacy/core_plugins/kibana/public/discover/kibana_services.ts index 7fa5183a4f54bb..6947d985be4363 100644 --- a/src/legacy/core_plugins/kibana/public/discover/kibana_services.ts +++ b/src/legacy/core_plugins/kibana/public/discover/kibana_services.ts @@ -53,14 +53,12 @@ export { wrapInI18nContext } from 'ui/i18n'; export { getRequestInspectorStats, getResponseInspectorStats } from '../../../data/public'; // @ts-ignore export { intervalOptions } from 'ui/agg_types'; -export { stateMonitorFactory } from 'ui/state_management/state_monitor_factory'; export { subscribeWithScope } from 'ui/utils/subscribe_with_scope'; // @ts-ignore export { timezoneProvider } from 'ui/vis/lib/timezone'; export { tabifyAggResponse } from '../../../data/public'; export { unhashUrl } from '../../../../../plugins/kibana_utils/public'; export { - migrateLegacyQuery, ensureDefaultIndexPattern, formatMsg, formatStack, @@ -80,7 +78,6 @@ export { SortDirection, } from '../../../../../plugins/data/public'; export { ElasticSearchHit } from './np_ready/doc_views/doc_views_types'; -export { registerTimefilterWithGlobalStateFactory } from 'ui/timefilter/setup_router'; export { getFormat } from 'ui/visualize/loader/pipeline_helpers/utilities'; // @ts-ignore export { buildPointSeriesData } from 'ui/agg_response/point_series/point_series'; diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/discover.html b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/discover.html index 18254aeca5094e..2d44b12989228a 100644 --- a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/discover.html +++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/discover.html @@ -5,18 +5,15 @@

{{screenTitle}}

@@ -186,7 +183,6 @@

{{screenTitle}}

columns="state.columns" infinite-scroll="true" filter="filterQuery" - filters="state.filters" data-shared-item data-title="{{opts.savedSearch.lastSavedTitle}}" data-description="{{opts.savedSearch.description}}" diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/discover.js b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/discover.js index 81c10798936f56..cf237d8d79cc2b 100644 --- a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/discover.js +++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/discover.js @@ -24,17 +24,14 @@ import { debounceTime } from 'rxjs/operators'; import moment from 'moment'; import dateMath from '@elastic/datemath'; import { i18n } from '@kbn/i18n'; -import '../components/field_chooser/field_chooser'; +import { getState, splitState } from './discover_state'; import { RequestAdapter } from '../../../../../../../plugins/inspector/public'; import { SavedObjectSaveModal, showSaveModal, } from '../../../../../../../plugins/saved_objects/public'; -// doc table -import './doc_table'; -import { getSortArray } from './doc_table/lib/get_sort'; -import { getSortForSearchSource } from './doc_table/lib/get_sort_for_search_source'; +import { getSortArray, getSortForSearchSource } from './doc_table'; import * as columnActions from './doc_table/actions/columns'; import indexTemplate from './discover.html'; @@ -44,26 +41,24 @@ import '../components/fetch_error'; import { getPainlessError } from './get_painless_error'; import { discoverResponseHandler } from './response_handler'; import { - angular, getRequestInspectorStats, getResponseInspectorStats, getServices, hasSearchStategyForIndexPattern, intervalOptions, - migrateLegacyQuery, unhashUrl, - stateMonitorFactory, subscribeWithScope, tabifyAggResponse, getAngularModule, ensureDefaultIndexPattern, - registerTimefilterWithGlobalStateFactory, } from '../../kibana_services'; const { core, chrome, + data, docTitle, + indexPatterns, filterManager, share, timefilter, @@ -77,9 +72,11 @@ import { esFilters, fieldFormats, indexPatterns as indexPatternsUtils, + connectToQueryState, + syncQueryStateWithUrl, + getDefaultQuery, } from '../../../../../../../plugins/data/public'; import { getIndexPatternId } from '../helpers/get_index_pattern_id'; -import { FilterStateManager } from '../../../../../data/public'; const fetchStatuses = { UNINITIALIZED: 'uninitialized', @@ -88,9 +85,6 @@ const fetchStatuses = { }; const app = getAngularModule(); -app.run((globalState, $rootScope) => { - registerTimefilterWithGlobalStateFactory(timefilter, globalState, $rootScope); -}); app.config($routeProvider => { const defaults = { @@ -119,10 +113,11 @@ app.config($routeProvider => { template: indexTemplate, reloadOnSearch: false, resolve: { - savedObjects: function(redirectWhenMissing, $route, kbnUrl, Promise, $rootScope, State) { - const indexPatterns = getServices().indexPatterns; + savedObjects: function(redirectWhenMissing, $route, kbnUrl, Promise, $rootScope) { const savedSearchId = $route.current.params.id; - return ensureDefaultIndexPattern(core, getServices().data, $rootScope, kbnUrl).then(() => { + return ensureDefaultIndexPattern(core, data, $rootScope, kbnUrl).then(() => { + const { appStateContainer } = getState({}); + const { index } = appStateContainer.getState(); return Promise.props({ ip: indexPatterns.getCache().then(indexPatternList => { /** @@ -134,22 +129,16 @@ app.config($routeProvider => { * * @type {State} */ - const state = new State('_a', {}); - const id = getIndexPatternId( - state.index, - indexPatternList, - uiSettings.get('defaultIndex') - ); - state.destroy(); + const id = getIndexPatternId(index, indexPatternList, uiSettings.get('defaultIndex')); return Promise.props({ list: indexPatternList, loaded: indexPatterns.get(id), - stateVal: state.index, - stateValFound: !!state.index && id === state.index, + stateVal: index, + stateValFound: !!index && id === index, }); }), savedSearch: getServices() - .getSavedSearchById(savedSearchId, kbnUrl) + .getSavedSearchById(savedSearchId) .then(savedSearch => { if (savedSearchId) { chrome.recentlyAccessed.add( @@ -188,23 +177,114 @@ function discoverController( $scope, $timeout, $window, - AppState, Promise, config, kbnUrl, localStorage, - uiCapabilities, - getAppState, - globalState + uiCapabilities ) { - const filterStateManager = new FilterStateManager(globalState, getAppState, filterManager); + const { isDefault: isDefaultType } = indexPatternsUtils; + const subscriptions = new Subscription(); + const $fetchObservable = new Subject(); + let inspectorRequest; + const savedSearch = $route.current.locals.savedObjects.savedSearch; + $scope.searchSource = savedSearch.searchSource; + $scope.indexPattern = resolveIndexPatternLoading(); + + const getTimeField = () => { + return isDefaultType($scope.indexPattern) ? $scope.indexPattern.timeFieldName : undefined; + }; + + const { + appStateContainer, + startSync: startStateSync, + stopSync: stopStateSync, + setAppState, + replaceUrlAppState, + isAppStateDirty, + kbnUrlStateStorage, + getPreviousAppState, + } = getState({ + defaultAppState: getStateDefaults(), + storeInSessionStorage: config.get('state:storeInSessionStorage'), + }); + if (appStateContainer.getState().index !== $scope.indexPattern.id) { + //used index pattern is different than the given by url/state which is invalid + setAppState({ index: $scope.indexPattern.id }); + } + $scope.state = { ...appStateContainer.getState() }; + + // syncs `_g` portion of url with query services + const { stop: stopSyncingGlobalStateWithUrl } = syncQueryStateWithUrl( + data.query, + kbnUrlStateStorage + ); + + // sync initial app filters from state to filterManager + filterManager.setAppFilters(_.cloneDeep(appStateContainer.getState().filters)); + + const stopSyncingQueryAppStateWithStateContainer = connectToQueryState( + data.query, + appStateContainer, + { filters: esFilters.FilterStateStore.APP_STATE } + ); + + const appStateUnsubscribe = appStateContainer.subscribe(async newState => { + const { state: newStatePartial } = splitState(newState); + const { state: oldStatePartial } = splitState(getPreviousAppState()); + + if (!_.isEqual(newStatePartial, oldStatePartial)) { + $scope.$evalAsync(async () => { + $scope.state = { ...newState }; + + // detect changes that should trigger fetching of new data + const changes = ['interval', 'sort', 'index', 'query'].filter( + prop => !_.isEqual(newStatePartial[prop], oldStatePartial[prop]) + ); + if (changes.indexOf('index') !== -1) { + try { + $scope.indexPattern = await indexPatterns.get(newStatePartial.index); + $scope.opts.timefield = getTimeField(); + $scope.enableTimeRangeSelector = !!$scope.opts.timefield; + // is needed to rerender the histogram + $scope.vis = undefined; + + // Taking care of sort when switching index pattern: + // Old indexPattern: sort by A + // If A is not available in the new index pattern, sort has to be adapted and propagated to URL + const sort = getSortArray(newStatePartial.sort, $scope.indexPattern); + if (newStatePartial.sort && !_.isEqual(sort, newStatePartial.sort)) { + return await replaceUrlAppState({ sort }); + } + } catch (e) { + toastNotifications.addWarning({ text: getIndexPatternWarning(newStatePartial.index) }); + } + } + + if (changes.length) { + $fetchObservable.next(); + } + }); + } + }); + + $scope.setIndexPattern = id => { + setAppState({ index: id }); + }; + + // update data source when filters update + subscriptions.add( + subscribeWithScope($scope, filterManager.getUpdates$(), { + next: () => { + $scope.updateDataSource(); + }, + }) + ); const inspectorAdapters = { requests: new RequestAdapter(), }; - const subscriptions = new Subscription(); - $scope.timefilterUpdateHandler = ranges => { timefilter.setTime({ from: moment(ranges.from).toISOString(), @@ -213,7 +293,6 @@ function discoverController( }); }; $scope.intervalOptions = intervalOptions; - $scope.showInterval = false; $scope.minimumVisibleRows = 50; $scope.fetchStatus = fetchStatuses.UNINITIALIZED; $scope.showSaveQuery = uiCapabilities.discover.saveQuery; @@ -229,19 +308,15 @@ function discoverController( return interval.val !== 'custom'; }; - // the saved savedSearch - const savedSearch = $route.current.locals.savedObjects.savedSearch; - let abortController; $scope.$on('$destroy', () => { if (abortController) abortController.abort(); savedSearch.destroy(); subscriptions.unsubscribe(); - filterStateManager.destroy(); - }); - - const $appStatus = ($scope.appStatus = this.appStatus = { - dirty: !savedSearch.id, + appStateUnsubscribe(); + stopStateSync(); + stopSyncingGlobalStateWithUrl(); + stopSyncingQueryAppStateWithStateContainer(); }); const getTopNavLinks = () => { @@ -299,7 +374,7 @@ function discoverController( onSave={onSave} onClose={() => {}} title={savedSearch.title} - showCopyOnSave={savedSearch.id ? true : false} + showCopyOnSave={!!savedSearch.id} objectType="search" description={i18n.translate('kbn.discover.localMenu.saveSaveSearchDescription', { defaultMessage: @@ -353,7 +428,7 @@ function discoverController( ...sharingData, title: savedSearch.title, }, - isDirty: $appStatus.dirty, + isDirty: !savedSearch.id || isAppStateDirty(), }); }, }; @@ -382,13 +457,8 @@ function discoverController( inspectSearch, ]; }; - $scope.topNavMenu = getTopNavLinks(); - // the actual courier.SearchSource - $scope.searchSource = savedSearch.searchSource; - $scope.indexPattern = resolveIndexPatternLoading(); - $scope.searchSource .setField('index', $scope.indexPattern) .setField('highlightAll', true) @@ -401,7 +471,7 @@ function discoverController( // searchSource which applies time range const timeRangeSearchSource = savedSearch.searchSource.create(); - if (indexPatternsUtils.isDefault($scope.indexPattern)) { + if (isDefaultType($scope.indexPattern)) { timeRangeSearchSource.setField('filter', () => { return timefilter.createFilter($scope.indexPattern); }); @@ -431,11 +501,6 @@ function discoverController( ]); } - let stateMonitor; - - const $state = ($scope.state = new AppState(getStateDefaults())); - const $fetchObservable = new Subject(); - $scope.screenTitle = savedSearch.title; const getFieldCounts = async () => { @@ -455,8 +520,7 @@ function discoverController( }); }; - const getSharingDataFields = async () => { - const selectedFields = $state.columns; + const getSharingDataFields = async (selectedFields, timeFieldName, hideTimeColumn) => { if (selectedFields.length === 1 && selectedFields[0] === '_source') { const fieldCounts = await getFieldCounts(); return { @@ -465,8 +529,6 @@ function discoverController( }; } - const timeFieldName = $scope.indexPattern.timeFieldName; - const hideTimeColumn = config.get('doc_table:hideTimeColumn'); const fields = timeFieldName && !hideTimeColumn ? [timeFieldName, ...selectedFields] : selectedFields; return { @@ -478,12 +540,16 @@ function discoverController( this.getSharingData = async () => { const searchSource = $scope.searchSource.createCopy(); - const { searchFields, selectFields } = await getSharingDataFields(); + const { searchFields, selectFields } = await getSharingDataFields( + $scope.state.columns, + $scope.indexPattern.timeFieldName, + config.get('doc_table:hideTimeColumn') + ); searchSource.setField('fields', searchFields); searchSource.setField( 'sort', getSortForSearchSource( - $state.sort, + $scope.state.sort, $scope.indexPattern, config.get('discover:sort:defaultOrder') ) @@ -508,29 +574,25 @@ function discoverController( }; }; - $scope.uiState = $state.makeStateful('uiState'); - function getStateDefaults() { + const query = + $scope.searchSource.getField('query') || + getDefaultQuery( + localStorage.get('kibana.userQueryLanguage') || config.get('search:queryLanguage') + ); return { - query: ($scope.savedQuery && $scope.savedQuery.attributes.query) || - $scope.searchSource.getField('query') || { - query: '', - language: - localStorage.get('kibana.userQueryLanguage') || config.get('search:queryLanguage'), - }, + query, sort: getSortArray(savedSearch.sort, $scope.indexPattern), columns: savedSearch.columns.length > 0 ? savedSearch.columns : config.get('defaultColumns').slice(), index: $scope.indexPattern.id, interval: 'auto', - filters: - ($scope.savedQuery && $scope.savedQuery.attributes.filters) || - _.cloneDeep($scope.searchSource.getOwnField('filter')), + filters: _.cloneDeep($scope.searchSource.getOwnField('filter')), }; } - $state.index = $scope.indexPattern.id; - $state.sort = getSortArray($state.sort, $scope.indexPattern); + $scope.state.index = $scope.indexPattern.id; + $scope.state.sort = getSortArray($scope.state.sort, $scope.indexPattern); $scope.getBucketIntervalToolTipText = () => { return i18n.translate('kbn.discover.bucketIntervalTooltip', { @@ -550,16 +612,10 @@ function discoverController( }); }; - $scope.$watchCollection('state.columns', function() { - $state.save(); - }); - $scope.opts = { // number of records to fetch, then paginate through sampleSize: config.get('discover:sampleSize'), - timefield: indexPatternsUtils.isDefault($scope.indexPattern) - ? $scope.indexPattern.timeFieldName - : undefined, + timefield: getTimeField(), savedSearch: savedSearch, indexPatternList: $route.current.locals.savedObjects.ip.list, }; @@ -574,14 +630,8 @@ function discoverController( ); }; - const init = _.once(function() { - stateMonitor = stateMonitorFactory.create($state, getStateDefaults()); - stateMonitor.onChange(status => { - $appStatus.dirty = status.dirty || !savedSearch.id; - }); - $scope.$on('$destroy', () => stateMonitor.destroy()); - - $scope.updateDataSource().then(function() { + const init = _.once(() => { + $scope.updateDataSource().then(async () => { const searchBarChanges = merge( timefilter.getAutoRefreshFetch$(), timefilter.getFetch$(), @@ -601,47 +651,16 @@ function discoverController( }, }) ); - - $scope.$watchCollection('state.sort', function(sort) { - if (!sort) return; - - // get the current sort from searchSource as array of arrays - const currentSort = getSortArray($scope.searchSource.getField('sort'), $scope.indexPattern); - - // if the searchSource doesn't know, tell it so - if (!angular.equals(sort, currentSort)) $fetchObservable.next(); - }); - - // update data source when filters update - - subscriptions.add( - subscribeWithScope($scope, filterManager.getUpdates$(), { - next: () => { - $scope.updateDataSource().then(function() { - $state.save(); - }); - }, - }) - ); - - // update data source when hitting forward/back and the query changes - $scope.$listen($state, 'fetch_with_changes', function(diff) { - if (diff.indexOf('query') >= 0) $fetchObservable.next(); - }); - - $scope.$watch('opts.timefield', function(timefield) { - $scope.enableTimeRangeSelector = !!timefield; - }); - + //Handling change oft the histogram interval $scope.$watch('state.interval', function(newInterval, oldInterval) { if (newInterval !== oldInterval) { - $fetchObservable.next(); + setAppState({ interval: newInterval }); } }); $scope.$watch('vis.aggs', function() { // no timefield, no vis, nothing to update - if (!$scope.opts.timefield) return; + if (!getTimeField() || !$scope.vis) return; const buckets = $scope.vis.getAggConfig().byTypeName('buckets'); @@ -650,15 +669,6 @@ function discoverController( } }); - $scope.$watch('state.query', (newQuery, oldQuery) => { - if (!_.isEqual(newQuery, oldQuery)) { - const query = migrateLegacyQuery(newQuery); - if (!_.isEqual(query, newQuery)) { - $scope.updateQuery({ query }); - } - } - }); - $scope.$watchMulti( ['rows', 'fetchStatus'], (function updateResultState() { @@ -705,14 +715,12 @@ function discoverController( })() ); - if ($scope.opts.timefield) { + if (getTimeField()) { setupVisualization(); $scope.updateTime(); } init.complete = true; - $state.replace(); - if (shouldSearchOnPageLoad()) { $fetchObservable.next(); } @@ -728,7 +736,6 @@ function discoverController( try { const id = await savedSearch.save(saveOptions); $scope.$evalAsync(() => { - stateMonitor.setInitialState($state.toJSON()); if (id) { toastNotifications.addSuccess({ title: i18n.translate('kbn.discover.notifications.savedSearchTitle', { @@ -744,7 +751,7 @@ function discoverController( kbnUrl.change('/discover/{{id}}', { id: savedSearch.id }); } else { // Update defaults so that "reload saved query" functions correctly - $state.setDefaults(getStateDefaults()); + setAppState(getStateDefaults()); docTitle.change(savedSearch.lastSavedTitle); } } @@ -770,8 +777,6 @@ function discoverController( $scope.fetchError = undefined; - $scope.updateTime(); - // Abort any in-progress requests before fetching again if (abortController) abortController.abort(); abortController = new AbortController(); @@ -780,7 +785,6 @@ function discoverController( .updateDataSource() .then(setupVisualization) .then(function() { - $state.save(); $scope.fetchStatus = fetchStatuses.LOADING; logInspectorRequest(); return $scope.searchSource.fetch({ @@ -808,10 +812,27 @@ function discoverController( }; $scope.updateQuery = function({ query }) { - $state.query = query; + setAppState({ query }); $fetchObservable.next(); }; + $scope.updateSavedQueryId = newSavedQueryId => { + if (newSavedQueryId) { + setAppState({ savedQuery: newSavedQueryId }); + } else { + //reset filters and query string, remove savedQuery from state + const state = { + ...appStateContainer.getState(), + query: getDefaultQuery( + localStorage.get('kibana.userQueryLanguage') || config.get('search:queryLanguage') + ), + filters: [], + }; + delete state.savedQuery; + appStateContainer.set(state); + } + }; + function getDimensions(aggs, timeRange) { const [metric, agg] = aggs; agg.params.timeRange = timeRange; @@ -842,9 +863,9 @@ function discoverController( } function onResults(resp) { - logInspectorResponse(resp); + inspectorRequest.stats(getResponseInspectorStats($scope.searchSource, resp)).ok({ json: resp }); - if ($scope.opts.timefield) { + if (getTimeField()) { const tabifiedData = tabifyAggResponse($scope.vis.aggs, resp); $scope.searchSource.rawResponse = resp; $scope.histogramData = discoverResponseHandler( @@ -869,8 +890,6 @@ function discoverController( $scope.fetchStatus = fetchStatuses.COMPLETE; } - let inspectorRequest; - function logInspectorRequest() { inspectorAdapters.requests.reset(); const title = i18n.translate('kbn.discover.inspectorRequestDataTitle', { @@ -886,11 +905,8 @@ function discoverController( }); } - function logInspectorResponse(resp) { - inspectorRequest.stats(getResponseInspectorStats($scope.searchSource, resp)).ok({ json: resp }); - } - $scope.updateTime = function() { + //this is the timerange for the histogram, should be refactored $scope.timeRange = { from: dateMath.parse(timefilter.getTime().from), to: dateMath.parse(timefilter.getTime().to, { roundUp: true }), @@ -909,20 +925,26 @@ function discoverController( kbnUrl.change('/discover'); }; - $scope.updateDataSource = Promise.method(function updateDataSource() { + $scope.updateDataSource = () => { const { indexPattern, searchSource } = $scope; searchSource + .setField('index', $scope.indexPattern) .setField('size', $scope.opts.sampleSize) .setField( 'sort', - getSortForSearchSource($state.sort, indexPattern, config.get('discover:sort:defaultOrder')) + getSortForSearchSource( + $scope.state.sort, + indexPattern, + config.get('discover:sort:defaultOrder') + ) ) - .setField('query', !$state.query ? null : $state.query) + .setField('query', $scope.state.query || null) .setField('filter', filterManager.getFilters()); - }); + return Promise.resolve(); + }; - $scope.setSortOrder = function setSortOrder(sortPair) { - $scope.state.sort = sortPair; + $scope.setSortOrder = function setSortOrder(sort) { + setAppState({ sort }); }; // TODO: On array fields, negating does not negate the combination, rather all terms @@ -940,16 +962,19 @@ function discoverController( $scope.addColumn = function addColumn(columnName) { $scope.indexPattern.popularizeField(columnName, 1); - columnActions.addColumn($scope.state.columns, columnName); + const columns = columnActions.addColumn($scope.state.columns, columnName); + setAppState({ columns }); }; $scope.removeColumn = function removeColumn(columnName) { $scope.indexPattern.popularizeField(columnName, 1); - columnActions.removeColumn($scope.state.columns, columnName); + const columns = columnActions.removeColumn($scope.state.columns, columnName); + setAppState({ columns }); }; $scope.moveColumn = function moveColumn(columnName, newIndex) { - columnActions.moveColumn($scope.state.columns, columnName, newIndex); + const columns = columnActions.moveColumn($scope.state.columns, columnName, newIndex); + setAppState({ columns }); }; $scope.scrollToTop = function() { @@ -967,18 +992,10 @@ function discoverController( $scope.minimumVisibleRows = $scope.hits; }; - $scope.updateSavedQueryId = newSavedQueryId => { - if (newSavedQueryId) { - $state.savedQuery = newSavedQueryId; - } else { - delete $state.savedQuery; - } - $state.save(); - }; - async function setupVisualization() { // If no timefield has been specified we don't create a histogram of messages - if (!$scope.opts.timefield) return; + if (!getTimeField()) return; + const { interval: histogramInterval } = $scope.state; const visStateAggs = [ { @@ -989,8 +1006,8 @@ function discoverController( type: 'date_histogram', schema: 'segment', params: { - field: $scope.opts.timefield, - interval: $state.interval, + field: getTimeField(), + interval: histogramInterval, timeRange: timefilter.getTime(), }, }, @@ -1024,14 +1041,25 @@ function discoverController( visSavedObject.vis = $scope.vis; $scope.searchSource.onRequestStart((searchSource, options) => { + if (!$scope.vis) return; return $scope.vis.getAggConfig().onSearchRequestStart(searchSource, options); }); $scope.searchSource.setField('aggs', function() { + if (!$scope.vis) return; return $scope.vis.getAggConfig().toDsl(); }); } + function getIndexPatternWarning(index) { + return i18n.translate('kbn.discover.valueIsNotConfiguredIndexPatternIDWarningTitle', { + defaultMessage: '{stateVal} is not a configured index pattern ID', + values: { + stateVal: `"${index}"`, + }, + }); + } + function resolveIndexPatternLoading() { const { loaded: loadedIndexPattern, @@ -1046,15 +1074,7 @@ function discoverController( } if (stateVal && !stateValFound) { - const warningTitle = i18n.translate( - 'kbn.discover.valueIsNotConfiguredIndexPatternIDWarningTitle', - { - defaultMessage: '{stateVal} is not a configured index pattern ID', - values: { - stateVal: `"${stateVal}"`, - }, - } - ); + const warningTitle = getIndexPatternWarning(); if (ownIndexPattern) { toastNotifications.addWarning({ @@ -1090,7 +1110,7 @@ function discoverController( // Block the UI from loading if the user has loaded a rollup index pattern but it isn't // supported. $scope.isUnsupportedIndexPattern = - !indexPatternsUtils.isDefault($route.current.locals.savedObjects.ip.loaded) && + !isDefaultType($route.current.locals.savedObjects.ip.loaded) && !hasSearchStategyForIndexPattern($route.current.locals.savedObjects.ip.loaded); if ($scope.isUnsupportedIndexPattern) { @@ -1101,4 +1121,6 @@ function discoverController( addHelpMenuToAppChrome(chrome); init(); + // Propagate current app state to url, then start syncing + replaceUrlAppState().then(() => startStateSync()); } diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/discover_state.test.ts b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/discover_state.test.ts new file mode 100644 index 00000000000000..af772cb5c76f18 --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/discover_state.test.ts @@ -0,0 +1,78 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { getState, GetStateReturn } from './discover_state'; +import { createBrowserHistory, History } from 'history'; + +let history: History; +let state: GetStateReturn; +const getCurrentUrl = () => history.createHref(history.location); + +describe('Test discover state', () => { + beforeEach(async () => { + history = createBrowserHistory(); + history.push('/'); + state = getState({ + defaultAppState: { index: 'test' }, + hashHistory: history, + }); + await state.replaceUrlAppState({}); + await state.startSync(); + }); + afterEach(() => { + state.stopSync(); + }); + test('setting app state and syncing to URL', async () => { + state.setAppState({ index: 'modified' }); + state.flushToUrl(); + expect(getCurrentUrl()).toMatchInlineSnapshot(`"/#?_a=(index:modified)"`); + }); + + test('changing URL to be propagated to appState', async () => { + history.push('/#?_a=(index:modified)'); + expect(state.appStateContainer.getState()).toMatchInlineSnapshot(` + Object { + "index": "modified", + } + `); + }); + test('URL navigation to url without _a, state should not change', async () => { + history.push('/#?_a=(index:modified)'); + history.push('/'); + expect(state.appStateContainer.getState()).toMatchInlineSnapshot(` + Object { + "index": "modified", + } + `); + }); + + test('isAppStateDirty returns whether the current state has changed', async () => { + state.setAppState({ index: 'modified' }); + expect(state.isAppStateDirty()).toBeTruthy(); + state.resetInitialAppState(); + expect(state.isAppStateDirty()).toBeFalsy(); + }); + + test('getPreviousAppState returns the state before the current', async () => { + state.setAppState({ index: 'first' }); + const stateA = state.appStateContainer.getState(); + state.setAppState({ index: 'second' }); + expect(state.getPreviousAppState()).toEqual(stateA); + }); +}); diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/discover_state.ts b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/discover_state.ts new file mode 100644 index 00000000000000..10e7cd1d0c49d4 --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/discover_state.ts @@ -0,0 +1,223 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import { isEqual } from 'lodash'; +import { createHashHistory, History } from 'history'; +import { + createStateContainer, + createKbnUrlStateStorage, + syncState, + ReduxLikeStateContainer, + IKbnUrlStateStorage, +} from '../../../../../../../plugins/kibana_utils/public'; +import { esFilters, Filter, Query } from '../../../../../../../plugins/data/public'; +import { migrateLegacyQuery } from '../../../../../../../plugins/kibana_legacy/public'; + +interface AppState { + /** + * Columns displayed in the table + */ + columns?: string[]; + /** + * Array of applied filters + */ + filters?: Filter[]; + /** + * id of the used index pattern + */ + index?: string; + /** + * Used interval of the histogram + */ + interval?: string; + /** + * Lucence or KQL query + */ + query?: Query; + /** + * Array of the used sorting [[field,direction],...] + */ + sort?: string[][]; +} + +interface GetStateParams { + /** + * Default state used for merging with with URL state to get the initial state + */ + defaultAppState?: AppState; + /** + * Determins the use of long vs. short/hashed urls + */ + storeInSessionStorage?: boolean; + /** + * Browser history used for testing + */ + hashHistory?: History; +} + +export interface GetStateReturn { + /** + * kbnUrlStateStorage + */ + kbnUrlStateStorage: IKbnUrlStateStorage; + /** + * App state, the _a part of the URL + */ + appStateContainer: ReduxLikeStateContainer; + /** + * Start sync between state and URL + */ + startSync: () => void; + /** + * Stop sync between state and URL + */ + stopSync: () => void; + /** + * Set app state to with a partial new app state + */ + setAppState: (newState: Partial) => void; + /** + * Set state in Url using history.replace + */ + replaceUrlAppState: (newState: Partial) => Promise; + /** + * Sync state to URL, used for testing + */ + flushToUrl: () => void; + /** + * Reset initial state to the current app state + */ + resetInitialAppState: () => void; + /** + * Return the Appstate before the current app state, useful for diffing changes + */ + getPreviousAppState: () => AppState; + /** + * Returns whether the current app state is different to the initial state + */ + isAppStateDirty: () => void; +} +const APP_STATE_URL_KEY = '_a'; + +/** + * Builds and returns appState and globalState containers and helper functions + * Used to sync URL with UI state + */ +export function getState({ + defaultAppState = {}, + storeInSessionStorage = false, + hashHistory, +}: GetStateParams): GetStateReturn { + const stateStorage = createKbnUrlStateStorage({ + useHash: storeInSessionStorage, + history: hashHistory ? hashHistory : createHashHistory(), + }); + + const appStateFromUrl = stateStorage.get(APP_STATE_URL_KEY) as AppState; + let initialAppState = { + ...defaultAppState, + ...appStateFromUrl, + }; + let previousAppState: AppState; + const appStateContainer = createStateContainer(initialAppState); + + const appStateContainerModified = { + ...appStateContainer, + set: (value: AppState | null) => { + if (value) { + previousAppState = appStateContainer.getState(); + appStateContainer.set(value); + } + }, + }; + + const { start, stop } = syncState({ + storageKey: APP_STATE_URL_KEY, + stateContainer: appStateContainerModified, + stateStorage, + }); + + return { + kbnUrlStateStorage: stateStorage, + appStateContainer: appStateContainerModified, + startSync: start, + stopSync: stop, + setAppState: (newPartial: AppState) => setState(appStateContainerModified, newPartial), + replaceUrlAppState: async (newPartial: AppState = {}) => { + const state = { ...appStateContainer.getState(), ...newPartial }; + await stateStorage.set(APP_STATE_URL_KEY, state, { replace: true }); + }, + resetInitialAppState: () => { + initialAppState = appStateContainer.getState(); + }, + getPreviousAppState: () => previousAppState, + flushToUrl: () => stateStorage.flush(), + isAppStateDirty: () => !isEqualState(initialAppState, appStateContainer.getState()), + }; +} + +/** + * Helper function to merge a given new state with the existing state and to set the given state + * container + */ +export function setState(stateContainer: ReduxLikeStateContainer, newState: AppState) { + const oldState = stateContainer.getState(); + const mergedState = { ...oldState, ...newState }; + if (!isEqualState(oldState, mergedState)) { + if (mergedState.query) { + mergedState.query = migrateLegacyQuery(mergedState.query); + } + stateContainer.set(mergedState); + } +} + +/** + * Helper function to compare 2 different filter states + */ +export function isEqualFilters(filtersA: Filter[], filtersB: Filter[]) { + if (!filtersA && !filtersB) { + return true; + } else if (!filtersA || !filtersB) { + return false; + } + return esFilters.compareFilters(filtersA, filtersB, esFilters.COMPARE_ALL_OPTIONS); +} + +/** + * helper function to extract filters of the given state + * returns a state object without filters and an array of filters + */ +export function splitState(state: AppState = {}) { + const { filters = [], ...statePartial } = state; + return { filters, state: statePartial }; +} + +/** + * Helper function to compare 2 different state, is needed since comparing filters + * works differently + */ +export function isEqualState(stateA: AppState, stateB: AppState) { + if (!stateA && !stateB) { + return true; + } else if (!stateA || !stateB) { + return false; + } + const { filters: stateAFilters = [], ...stateAPartial } = stateA; + const { filters: stateBFilters = [], ...stateBPartial } = stateB; + return isEqual(stateAPartial, stateBPartial) && isEqualFilters(stateAFilters, stateBFilters); +} diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/actions/columns.ts b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/actions/columns.ts index ec4fe8025a7c74..cec1a097da5bff 100644 --- a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/actions/columns.ts +++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/actions/columns.ts @@ -17,35 +17,40 @@ * under the License. */ +/** + * Helper function to provide a fallback to a single _source column if the given array of columns + * is empty, and removes _source if there are more than 1 columns given + * @param columns + */ +function buildColumns(columns: string[]) { + if (columns.length > 1 && columns.indexOf('_source') !== -1) { + return columns.filter(col => col !== '_source'); + } else if (columns.length !== 0) { + return columns; + } + return ['_source']; +} + export function addColumn(columns: string[], columnName: string) { if (columns.includes(columnName)) { - return; + return columns; } - - columns.push(columnName); + return buildColumns([...columns, columnName]); } export function removeColumn(columns: string[], columnName: string) { if (!columns.includes(columnName)) { - return; + return columns; } - - columns.splice(columns.indexOf(columnName), 1); + return buildColumns(columns.filter(col => col !== columnName)); } export function moveColumn(columns: string[], columnName: string, newIndex: number) { - if (newIndex < 0) { - return; + if (newIndex < 0 || newIndex >= columns.length || !columns.includes(columnName)) { + return columns; } - - if (newIndex >= columns.length) { - return; - } - - if (!columns.includes(columnName)) { - return; - } - - columns.splice(columns.indexOf(columnName), 1); // remove at old index - columns.splice(newIndex, 0, columnName); // insert before new index + const modifiedColumns = [...columns]; + modifiedColumns.splice(modifiedColumns.indexOf(columnName), 1); // remove at old index + modifiedColumns.splice(newIndex, 0, columnName); // insert before new index + return modifiedColumns; } diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/components/table_row.ts b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/components/table_row.ts index 8df035d0984694..7a090d6b7820c4 100644 --- a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/components/table_row.ts +++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/components/table_row.ts @@ -33,6 +33,7 @@ import { dispatchRenderComplete } from '../../../../../../../../../plugins/kiban import cellTemplateHtml from '../components/table_row/cell.html'; import truncateByHeightTemplateHtml from '../components/table_row/truncate_by_height.html'; import { esFilters } from '../../../../../../../../../plugins/data/public'; +import { getServices } from '../../../../kibana_services'; // guesstimate at the minimum number of chars wide cells in the table should be const MIN_LINE_LENGTH = 20; @@ -55,7 +56,6 @@ export function createTableRowDirective( scope: { columns: '=', filter: '=', - filters: '=?', indexPattern: '=', row: '=kbnTableRow', onAddColumn: '=?', @@ -116,12 +116,18 @@ export function createTableRowDirective( anchorId: $scope.row._id, indexPattern: $scope.indexPattern.id, }); + const globalFilters: any = getServices().filterManager.getGlobalFilters(); + const appFilters: any = getServices().filterManager.getAppFilters(); const hash = $httpParamSerializer({ + _g: rison.encode({ + filters: globalFilters || [], + }), _a: rison.encode({ columns: $scope.columns, - filters: ($scope.filters || []).map(esFilters.disableFilter), + filters: (appFilters || []).map(esFilters.disableFilter), }), }); + return `${path}?${hash}`; }; diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/doc_table.html b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/doc_table.html index 61bb5cbf39cbea..3ce43426caf445 100644 --- a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/doc_table.html +++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/doc_table.html @@ -43,7 +43,6 @@ sorting="sorting" index-pattern="indexPattern" filter="filter" - filters="filters" class="kbnDocTable__row" on-add-column="onAddColumn" on-remove-column="onRemoveColumn" @@ -93,7 +92,6 @@ sorting="sorting" index-pattern="indexPattern" filter="filter" - filters="filters" class="kbnDocTable__row" ng-class="{'kbnDocTable__row--highlight': row['$$_isAnchor']}" data-test-subj="docTableRow{{ row['$$_isAnchor'] ? ' docTableAnchorRow' : ''}}" diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/doc_table.ts b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/doc_table.ts index 3329ffc7cd102a..0ca8286c17081f 100644 --- a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/doc_table.ts +++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/doc_table.ts @@ -17,15 +17,9 @@ * under the License. */ -import _ from 'lodash'; import { IUiSettingsClient } from 'kibana/public'; import html from './doc_table.html'; -import './infinite_scroll'; -import './components/table_header'; -import './components/table_row'; import { dispatchRenderComplete } from '../../../../../../../../plugins/kibana_utils/public'; -import './components/pager'; -import './lib/pager'; // @ts-ignore import { getLimitedSearchResultsMessage } from './doc_table_strings'; @@ -35,7 +29,6 @@ interface LazyScope extends ng.IScope { export function createDocTableDirective( config: IUiSettingsClient, - getAppState: any, pagerFactory: any, $filter: any ) { @@ -51,7 +44,6 @@ export function createDocTableDirective( isLoading: '=?', infiniteScroll: '=?', filter: '=?', - filters: '=?', minimumVisibleRows: '=?', onAddColumn: '=?', onChangeSortOrder: '=?', @@ -83,23 +75,6 @@ export function createDocTableDirective( $scope.limit += 50; }; - // This exists to fix the problem of an empty initial column list not playing nice with watchCollection. - $scope.$watch('columns', function(columns: string[]) { - if (columns.length !== 0) return; - - const $state = getAppState(); - $scope.columns.push('_source'); - if ($state) $state.replace(); - }); - - $scope.$watchCollection('columns', function(columns: string[], oldColumns: string[]) { - if (oldColumns.length === 1 && oldColumns[0] === '_source' && $scope.columns.length > 1) { - _.pull($scope.columns, '_source'); - } - - if ($scope.columns.length === 0) $scope.columns.push('_source'); - }); - $scope.$watch('hits', (hits: any) => { if (!hits) return; diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/index.ts b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/index.ts index 1eb1f10114d532..a7e3bdfd57f3bf 100644 --- a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/index.ts +++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/index.ts @@ -17,4 +17,6 @@ * under the License. */ -import './doc_table'; +export { createDocTableDirective } from './doc_table'; +export { getSort, getSortArray } from './lib/get_sort'; +export { getSortForSearchSource } from './lib/get_sort_for_search_source'; diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/application.ts b/src/legacy/core_plugins/kibana/public/discover/np_ready/application.ts index 83f4a5962e3cdf..7a4819bb0f2d17 100644 --- a/src/legacy/core_plugins/kibana/public/discover/np_ready/application.ts +++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/application.ts @@ -36,8 +36,6 @@ function mountDiscoverApp(moduleName: string, element: HTMLElement) { // bootstrap angular into detached element and attach it later to // make angular-within-angular possible const $injector = angular.bootstrap(mountpoint, [moduleName]); - // initialize global state handler - $injector.get('globalState'); element.appendChild(mountpoint); return $injector; } diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/components/field_chooser/discover_index_pattern.test.tsx b/src/legacy/core_plugins/kibana/public/discover/np_ready/components/field_chooser/discover_index_pattern.test.tsx index b6fd5ee60b8e2c..79417c07501a3e 100644 --- a/src/legacy/core_plugins/kibana/public/discover/np_ready/components/field_chooser/discover_index_pattern.test.tsx +++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/components/field_chooser/discover_index_pattern.test.tsx @@ -25,6 +25,12 @@ import { ChangeIndexPattern } from './change_indexpattern'; import { SavedObject } from 'kibana/server'; import { DiscoverIndexPattern } from './discover_index_pattern'; import { EuiSelectable, EuiSelectableList } from '@elastic/eui'; +import { IIndexPattern } from 'src/plugins/data/public'; + +const indexPattern = { + id: 'test1', + title: 'test1 title', +} as IIndexPattern; const indexPattern1 = { id: 'test1', @@ -42,7 +48,7 @@ const indexPattern2 = { const defaultProps = { indexPatternList: [indexPattern1, indexPattern2], - selectedIndexPattern: indexPattern1, + selectedIndexPattern: indexPattern, setIndexPattern: jest.fn(async () => {}), }; diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/components/field_chooser/discover_index_pattern.tsx b/src/legacy/core_plugins/kibana/public/discover/np_ready/components/field_chooser/discover_index_pattern.tsx index cca523ee2c1bda..fd2f96ca83a2f8 100644 --- a/src/legacy/core_plugins/kibana/public/discover/np_ready/components/field_chooser/discover_index_pattern.tsx +++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/components/field_chooser/discover_index_pattern.tsx @@ -16,9 +16,9 @@ * specific language governing permissions and limitations * under the License. */ -import React, { useState } from 'react'; +import React, { useState, useEffect } from 'react'; import { SavedObject } from 'kibana/server'; -import { IndexPatternAttributes } from 'src/plugins/data/public'; +import { IIndexPattern, IndexPatternAttributes } from 'src/plugins/data/public'; import { I18nProvider } from '@kbn/i18n/react'; import { IndexPatternRef } from './types'; @@ -31,7 +31,7 @@ export interface DiscoverIndexPatternProps { /** * currently selected index pattern, due to angular issues it's undefined at first rendering */ - selectedIndexPattern: SavedObject; + selectedIndexPattern: IIndexPattern; /** * triggered when user selects a new index pattern */ @@ -50,12 +50,16 @@ export function DiscoverIndexPattern({ id: entity.id, title: entity.attributes!.title, })); - const { id: selectedId, attributes } = selectedIndexPattern || {}; + const { id: selectedId, title: selectedTitle } = selectedIndexPattern || {}; const [selected, setSelected] = useState({ id: selectedId, - title: attributes?.title || '', + title: selectedTitle || '', }); + useEffect(() => { + const { id, title } = selectedIndexPattern; + setSelected({ id, title }); + }, [selectedIndexPattern]); if (!selectedId) { return null; } diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/components/field_chooser/field_chooser.html b/src/legacy/core_plugins/kibana/public/discover/np_ready/components/field_chooser/field_chooser.html index 1587c2af797521..fd63c26aa2bb3d 100644 --- a/src/legacy/core_plugins/kibana/public/discover/np_ready/components/field_chooser/field_chooser.html +++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/components/field_chooser/field_chooser.html @@ -1,7 +1,7 @@