diff --git a/api_docs/deprecations_by_plugin.mdx b/api_docs/deprecations_by_plugin.mdx
index d5cba68bc1777..d0ba182370c29 100644
--- a/api_docs/deprecations_by_plugin.mdx
+++ b/api_docs/deprecations_by_plugin.mdx
@@ -1211,7 +1211,7 @@ migrates to using the Kibana Privilege model: https://github.com/elastic/kibana/
| Deprecated API | Reference location(s) | Remove By |
| ---------------|-----------|-----------|
-| | [common.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/server/legacy_uptime/lib/alerts/common.ts#:~:text=alertFactory), [status_check.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/server/legacy_uptime/lib/alerts/status_check.ts#:~:text=alertFactory), [tls.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/server/legacy_uptime/lib/alerts/tls.ts#:~:text=alertFactory), [tls_legacy.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/server/legacy_uptime/lib/alerts/tls_legacy.ts#:~:text=alertFactory), [duration_anomaly.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/server/legacy_uptime/lib/alerts/duration_anomaly.ts#:~:text=alertFactory), [common.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/server/alert_rules/common.ts#:~:text=alertFactory), [monitor_status_rule.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/server/alert_rules/status_rule/monitor_status_rule.ts#:~:text=alertFactory) | - |
+| | [common.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/server/legacy_uptime/lib/alerts/common.ts#:~:text=alertFactory), [status_check.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/server/legacy_uptime/lib/alerts/status_check.ts#:~:text=alertFactory), [tls.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/server/legacy_uptime/lib/alerts/tls.ts#:~:text=alertFactory), [tls_legacy.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/server/legacy_uptime/lib/alerts/tls_legacy.ts#:~:text=alertFactory), [duration_anomaly.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/server/legacy_uptime/lib/alerts/duration_anomaly.ts#:~:text=alertFactory), [common.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/server/alert_rules/common.ts#:~:text=alertFactory), [tls_rule.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/server/alert_rules/status_rule/tls_rule.ts#:~:text=alertFactory) | - |
| | [filter_group.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/legacy_uptime/components/overview/filter_group/filter_group.tsx#:~:text=title), [filters_expression_select.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/legacy_uptime/components/overview/alerts/monitor_expressions/filters_expression_select.tsx#:~:text=title), [filter_group.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/legacy_uptime/components/overview/filter_group/filter_group.tsx#:~:text=title), [filters_expression_select.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/legacy_uptime/components/overview/alerts/monitor_expressions/filters_expression_select.tsx#:~:text=title) | - |
| | [filter_group.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/legacy_uptime/components/overview/filter_group/filter_group.tsx#:~:text=title), [filters_expression_select.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/legacy_uptime/components/overview/alerts/monitor_expressions/filters_expression_select.tsx#:~:text=title), [filter_group.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/legacy_uptime/components/overview/filter_group/filter_group.tsx#:~:text=title), [filters_expression_select.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/legacy_uptime/components/overview/alerts/monitor_expressions/filters_expression_select.tsx#:~:text=title) | - |
| | [filter_group.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/legacy_uptime/components/overview/filter_group/filter_group.tsx#:~:text=title), [filters_expression_select.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/legacy_uptime/components/overview/alerts/monitor_expressions/filters_expression_select.tsx#:~:text=title) | - |
diff --git a/packages/core/application/core-application-browser-internal/integration_tests/application_service.test.tsx b/packages/core/application/core-application-browser-internal/integration_tests/application_service.test.tsx
index a4f117d5c883b..95dc6d8f8b181 100644
--- a/packages/core/application/core-application-browser-internal/integration_tests/application_service.test.tsx
+++ b/packages/core/application/core-application-browser-internal/integration_tests/application_service.test.tsx
@@ -12,6 +12,7 @@ import { act } from 'react-dom/test-utils';
import { createMemoryHistory, MemoryHistory } from 'history';
import { httpServiceMock } from '@kbn/core-http-browser-mocks';
+import { analyticsServiceMock } from '@kbn/core-analytics-browser-mocks';
import { themeServiceMock } from '@kbn/core-theme-browser-mocks';
import type { AppMountParameters, AppUpdater } from '@kbn/core-application-browser';
import { overlayServiceMock } from '@kbn/core-overlays-browser-mocks';
@@ -38,11 +39,13 @@ describe('ApplicationService', () => {
beforeEach(() => {
history = createMemoryHistory();
const http = httpServiceMock.createSetupContract({ basePath: '/test' });
+ const analytics = analyticsServiceMock.createAnalyticsServiceSetup();
http.post.mockResolvedValue({ navLinks: {} });
setupDeps = {
http,
+ analytics,
history: history as any,
};
startDeps = {
@@ -87,6 +90,45 @@ describe('ApplicationService', () => {
expect(await currentAppId$.pipe(take(1)).toPromise()).toEqual('app1');
});
+
+ it('updates the page_url analytics context', async () => {
+ const { register } = service.setup(setupDeps);
+
+ const context$ = setupDeps.analytics.registerContextProvider.mock.calls[0][0]
+ .context$ as Observable<{
+ page_url: string;
+ }>;
+ const locations: string[] = [];
+ context$.subscribe((context) => locations.push(context.page_url));
+
+ register(Symbol(), {
+ id: 'app1',
+ title: 'App1',
+ mount: async () => () => undefined,
+ });
+ register(Symbol(), {
+ id: 'app2',
+ title: 'App2',
+ mount: async () => () => undefined,
+ });
+
+ const { getComponent } = await service.start(startDeps);
+ update = createRenderer(getComponent());
+
+ await navigate('/app/app1/bar?hello=dolly');
+ await flushPromises();
+ await navigate('/app/app2#/foo');
+ await flushPromises();
+ await navigate('/app/app2#/another-path');
+ await flushPromises();
+
+ expect(locations).toEqual([
+ '/',
+ '/app/app1/bar',
+ '/app/app2#/foo',
+ '/app/app2#/another-path',
+ ]);
+ });
});
describe('using navigateToApp', () => {
@@ -127,6 +169,46 @@ describe('ApplicationService', () => {
expect(currentAppIds).toEqual(['app1']);
});
+ it('updates the page_url analytics context', async () => {
+ const { register } = service.setup(setupDeps);
+
+ const context$ = setupDeps.analytics.registerContextProvider.mock.calls[0][0]
+ .context$ as Observable<{
+ page_url: string;
+ }>;
+ const locations: string[] = [];
+ context$.subscribe((context) => locations.push(context.page_url));
+
+ register(Symbol(), {
+ id: 'app1',
+ title: 'App1',
+ mount: async () => () => undefined,
+ });
+ register(Symbol(), {
+ id: 'app2',
+ title: 'App2',
+ mount: async () => () => undefined,
+ });
+
+ const { navigateToApp, getComponent } = await service.start(startDeps);
+ update = createRenderer(getComponent());
+
+ await act(async () => {
+ await navigateToApp('app1');
+ update();
+ });
+ await act(async () => {
+ await navigateToApp('app2', { path: '/nested' });
+ update();
+ });
+ await act(async () => {
+ await navigateToApp('app2', { path: '/another-path' });
+ update();
+ });
+
+ expect(locations).toEqual(['/', '/app/app1', '/app/app2/nested', '/app/app2/another-path']);
+ });
+
it('replaces the current history entry when the `replace` option is true', async () => {
const { register } = service.setup(setupDeps);
diff --git a/packages/core/application/core-application-browser-internal/src/application_service.test.mocks.ts b/packages/core/application/core-application-browser-internal/src/application_service.test.mocks.ts
index 7197e7308def6..a41c27f348f3a 100644
--- a/packages/core/application/core-application-browser-internal/src/application_service.test.mocks.ts
+++ b/packages/core/application/core-application-browser-internal/src/application_service.test.mocks.ts
@@ -26,11 +26,23 @@ jest.doMock('history', () => ({
}));
export const parseAppUrlMock = jest.fn();
+export const getLocationObservableMock = jest.fn();
jest.doMock('./utils', () => {
const original = jest.requireActual('./utils');
return {
...original,
parseAppUrl: parseAppUrlMock,
+ getLocationObservable: getLocationObservableMock,
+ };
+});
+
+export const registerAnalyticsContextProviderMock = jest.fn();
+jest.doMock('./register_analytics_context_provider', () => {
+ const original = jest.requireActual('./register_analytics_context_provider');
+
+ return {
+ ...original,
+ registerAnalyticsContextProvider: registerAnalyticsContextProviderMock,
};
});
diff --git a/packages/core/application/core-application-browser-internal/src/application_service.test.ts b/packages/core/application/core-application-browser-internal/src/application_service.test.ts
index 65e5867db83a0..09e7ed5a385a4 100644
--- a/packages/core/application/core-application-browser-internal/src/application_service.test.ts
+++ b/packages/core/application/core-application-browser-internal/src/application_service.test.ts
@@ -10,17 +10,21 @@ import {
MockCapabilitiesService,
MockHistory,
parseAppUrlMock,
+ getLocationObservableMock,
+ registerAnalyticsContextProviderMock,
} from './application_service.test.mocks';
import { createElement } from 'react';
import { BehaviorSubject, firstValueFrom, Subject } from 'rxjs';
import { bufferCount, takeUntil } from 'rxjs/operators';
import { mount, shallow } from 'enzyme';
+import { createBrowserHistory } from 'history';
import { httpServiceMock } from '@kbn/core-http-browser-mocks';
import { themeServiceMock } from '@kbn/core-theme-browser-mocks';
import { overlayServiceMock } from '@kbn/core-overlays-browser-mocks';
import { customBrandingServiceMock } from '@kbn/core-custom-branding-browser-mocks';
+import { analyticsServiceMock } from '@kbn/core-analytics-browser-mocks';
import { MockLifecycle } from './test_helpers/test_types';
import { ApplicationService } from './application_service';
import {
@@ -48,9 +52,12 @@ let service: ApplicationService;
describe('#setup()', () => {
beforeEach(() => {
+ jest.clearAllMocks();
const http = httpServiceMock.createSetupContract({ basePath: '/base-path' });
+ const analytics = analyticsServiceMock.createAnalyticsServiceSetup();
setupDeps = {
http,
+ analytics,
redirectTo: jest.fn(),
};
startDeps = {
@@ -469,13 +476,38 @@ describe('#setup()', () => {
]);
});
});
+
+ describe('analytics context provider', () => {
+ it('calls getLocationObservable with the correct parameters', () => {
+ const history = createBrowserHistory();
+ service.setup({ ...setupDeps, history });
+
+ expect(getLocationObservableMock).toHaveBeenCalledTimes(1);
+ expect(getLocationObservableMock).toHaveBeenCalledWith(window.location, history);
+ });
+
+ it('calls registerAnalyticsContextProvider with the correct parameters', () => {
+ const location$ = new Subject();
+ getLocationObservableMock.mockReturnValue(location$);
+
+ service.setup(setupDeps);
+
+ expect(registerAnalyticsContextProviderMock).toHaveBeenCalledTimes(1);
+ expect(registerAnalyticsContextProviderMock).toHaveBeenCalledWith({
+ analytics: setupDeps.analytics,
+ location$,
+ });
+ });
+ });
});
describe('#start()', () => {
beforeEach(() => {
const http = httpServiceMock.createSetupContract({ basePath: '/base-path' });
+ const analytics = analyticsServiceMock.createAnalyticsServiceSetup();
setupDeps = {
http,
+ analytics,
redirectTo: jest.fn(),
};
startDeps = {
@@ -1185,8 +1217,10 @@ describe('#stop()', () => {
MockHistory.push.mockReset();
const http = httpServiceMock.createSetupContract({ basePath: '/test' });
+ const analytics = analyticsServiceMock.createAnalyticsServiceSetup();
setupDeps = {
http,
+ analytics,
};
startDeps = {
http,
diff --git a/packages/core/application/core-application-browser-internal/src/application_service.tsx b/packages/core/application/core-application-browser-internal/src/application_service.tsx
index 5808410b07fe2..0c8207ca1d2f6 100644
--- a/packages/core/application/core-application-browser-internal/src/application_service.tsx
+++ b/packages/core/application/core-application-browser-internal/src/application_service.tsx
@@ -17,6 +17,7 @@ import type { HttpSetup, HttpStart } from '@kbn/core-http-browser';
import type { Capabilities } from '@kbn/core-capabilities-common';
import type { MountPoint } from '@kbn/core-mount-utils-browser';
import type { OverlayStart } from '@kbn/core-overlays-browser';
+import type { AnalyticsServiceSetup } from '@kbn/core-analytics-browser';
import type {
App,
AppDeepLink,
@@ -35,10 +36,18 @@ import type { InternalApplicationSetup, InternalApplicationStart, Mounter } from
import { getLeaveAction, isConfirmAction } from './application_leave';
import { getUserConfirmationHandler } from './navigation_confirm';
-import { appendAppPath, parseAppUrl, relativeToAbsolute, getAppInfo } from './utils';
+import {
+ appendAppPath,
+ parseAppUrl,
+ relativeToAbsolute,
+ getAppInfo,
+ getLocationObservable,
+} from './utils';
+import { registerAnalyticsContextProvider } from './register_analytics_context_provider';
export interface SetupDeps {
http: HttpSetup;
+ analytics: AnalyticsServiceSetup;
history?: History;
/** Used to redirect to external urls */
redirectTo?: (path: string) => void;
@@ -111,6 +120,7 @@ export class ApplicationService {
public setup({
http: { basePath },
+ analytics,
redirectTo = (path: string) => {
window.location.assign(path);
},
@@ -126,6 +136,12 @@ export class ApplicationService {
}),
});
+ const location$ = getLocationObservable(window.location, this.history);
+ registerAnalyticsContextProvider({
+ analytics,
+ location$,
+ });
+
this.navigate = (url, state, replace) => {
// basePath not needed here because `history` is configured with basename
return replace ? this.history!.replace(url, state) : this.history!.push(url, state);
diff --git a/packages/core/application/core-application-browser-internal/src/register_analytics_context_provider.test.ts b/packages/core/application/core-application-browser-internal/src/register_analytics_context_provider.test.ts
new file mode 100644
index 0000000000000..075095db9cfa8
--- /dev/null
+++ b/packages/core/application/core-application-browser-internal/src/register_analytics_context_provider.test.ts
@@ -0,0 +1,38 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+import { firstValueFrom, ReplaySubject, Subject } from 'rxjs';
+import { registerAnalyticsContextProvider } from './register_analytics_context_provider';
+import { analyticsServiceMock } from '@kbn/core-analytics-browser-mocks';
+
+describe('registerAnalyticsContextProvider', () => {
+ let analytics: ReturnType;
+ let location$: Subject;
+
+ beforeEach(() => {
+ analytics = analyticsServiceMock.createAnalyticsServiceSetup();
+ location$ = new ReplaySubject(1);
+ registerAnalyticsContextProvider({ analytics, location$ });
+ });
+
+ test('should register the analytics context provider', () => {
+ expect(analytics.registerContextProvider).toHaveBeenCalledTimes(1);
+ expect(analytics.registerContextProvider).toHaveBeenCalledWith(
+ expect.objectContaining({
+ name: 'page url',
+ })
+ );
+ });
+
+ test('emits a context value when location$ emits', async () => {
+ location$.next('/some_url');
+ await expect(
+ firstValueFrom(analytics.registerContextProvider.mock.calls[0][0].context$)
+ ).resolves.toEqual({ page_url: '/some_url' });
+ });
+});
diff --git a/packages/core/application/core-application-browser-internal/src/register_analytics_context_provider.ts b/packages/core/application/core-application-browser-internal/src/register_analytics_context_provider.ts
new file mode 100644
index 0000000000000..9c79b0e15f070
--- /dev/null
+++ b/packages/core/application/core-application-browser-internal/src/register_analytics_context_provider.ts
@@ -0,0 +1,26 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+import type { AnalyticsServiceSetup } from '@kbn/core-analytics-browser';
+import { type Observable, map } from 'rxjs';
+
+export function registerAnalyticsContextProvider({
+ analytics,
+ location$,
+}: {
+ analytics: AnalyticsServiceSetup;
+ location$: Observable;
+}) {
+ analytics.registerContextProvider({
+ name: 'page url',
+ context$: location$.pipe(map((location) => ({ page_url: location }))),
+ schema: {
+ page_url: { type: 'text', _meta: { description: 'The page url' } },
+ },
+ });
+}
diff --git a/packages/core/application/core-application-browser-internal/src/utils/get_location_observable.test.ts b/packages/core/application/core-application-browser-internal/src/utils/get_location_observable.test.ts
new file mode 100644
index 0000000000000..3567929e6b688
--- /dev/null
+++ b/packages/core/application/core-application-browser-internal/src/utils/get_location_observable.test.ts
@@ -0,0 +1,65 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+import { createBrowserHistory, type History } from 'history';
+import { firstValueFrom } from 'rxjs';
+import { getLocationObservable } from './get_location_observable';
+
+const nextTick = () => new Promise((resolve) => window.setTimeout(resolve, 1));
+
+describe('getLocationObservable', () => {
+ let history: History;
+
+ beforeEach(() => {
+ history = createBrowserHistory();
+ });
+
+ it('emits with the initial location', async () => {
+ const location$ = getLocationObservable({ pathname: '/foo', hash: '' }, history);
+ expect(await firstValueFrom(location$)).toEqual('/foo');
+ });
+
+ it('emits when the location changes', async () => {
+ const location$ = getLocationObservable({ pathname: '/foo', hash: '' }, history);
+ const locations: string[] = [];
+ location$.subscribe((location) => locations.push(location));
+
+ history.push({ pathname: '/bar' });
+ history.push({ pathname: '/dolly' });
+
+ await nextTick();
+
+ expect(locations).toEqual(['/foo', '/bar', '/dolly']);
+ });
+
+ it('emits only once for a given url', async () => {
+ const location$ = getLocationObservable({ pathname: '/foo', hash: '' }, history);
+ const locations: string[] = [];
+ location$.subscribe((location) => locations.push(location));
+
+ history.push({ pathname: '/bar' });
+ history.push({ pathname: '/bar' });
+ history.push({ pathname: '/foo' });
+
+ await nextTick();
+
+ expect(locations).toEqual(['/foo', '/bar', '/foo']);
+ });
+
+ it('includes the hash when present', async () => {
+ const location$ = getLocationObservable({ pathname: '/foo', hash: '#/index' }, history);
+ const locations: string[] = [];
+ location$.subscribe((location) => locations.push(location));
+
+ history.push({ pathname: '/bar', hash: '#/home' });
+
+ await nextTick();
+
+ expect(locations).toEqual(['/foo#/index', '/bar#/home']);
+ });
+});
diff --git a/packages/core/application/core-application-browser-internal/src/utils/get_location_observable.ts b/packages/core/application/core-application-browser-internal/src/utils/get_location_observable.ts
new file mode 100644
index 0000000000000..3e1957de38b63
--- /dev/null
+++ b/packages/core/application/core-application-browser-internal/src/utils/get_location_observable.ts
@@ -0,0 +1,35 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+import { Observable, Subject, startWith, shareReplay, distinctUntilChanged } from 'rxjs';
+import type { History } from 'history';
+
+// interface compatible for both window.location and history.location...
+export interface Location {
+ pathname: string;
+ hash: string;
+}
+
+export const getLocationObservable = (
+ initialLocation: Location,
+ history: History
+): Observable => {
+ const subject = new Subject();
+ history.listen((location) => {
+ subject.next(locationToUrl(location));
+ });
+ return subject.pipe(
+ startWith(locationToUrl(initialLocation)),
+ distinctUntilChanged(),
+ shareReplay(1)
+ );
+};
+
+const locationToUrl = (location: Location) => {
+ return `${location.pathname}${location.hash}`;
+};
diff --git a/packages/core/application/core-application-browser-internal/src/utils/index.ts b/packages/core/application/core-application-browser-internal/src/utils/index.ts
index e88b1f7a8a6fc..f22146584f70a 100644
--- a/packages/core/application/core-application-browser-internal/src/utils/index.ts
+++ b/packages/core/application/core-application-browser-internal/src/utils/index.ts
@@ -11,3 +11,4 @@ export { getAppInfo } from './get_app_info';
export { parseAppUrl } from './parse_app_url';
export { relativeToAbsolute } from './relative_to_absolute';
export { removeSlashes } from './remove_slashes';
+export { getLocationObservable } from './get_location_observable';
diff --git a/packages/core/application/core-application-browser-internal/tsconfig.json b/packages/core/application/core-application-browser-internal/tsconfig.json
index 8f54fa8aa6ae1..cc07927f15ed2 100644
--- a/packages/core/application/core-application-browser-internal/tsconfig.json
+++ b/packages/core/application/core-application-browser-internal/tsconfig.json
@@ -33,6 +33,8 @@
"@kbn/test-jest-helpers",
"@kbn/core-custom-branding-browser",
"@kbn/core-custom-branding-browser-mocks",
+ "@kbn/core-analytics-browser-mocks",
+ "@kbn/core-analytics-browser",
],
"exclude": [
"target/**/*",
diff --git a/packages/core/chrome/core-chrome-browser-internal/src/chrome_service.test.mocks.ts b/packages/core/chrome/core-chrome-browser-internal/src/chrome_service.test.mocks.ts
new file mode 100644
index 0000000000000..6ac3e812413c4
--- /dev/null
+++ b/packages/core/chrome/core-chrome-browser-internal/src/chrome_service.test.mocks.ts
@@ -0,0 +1,14 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+export const registerAnalyticsContextProviderMock = jest.fn();
+jest.doMock('./register_analytics_context_provider', () => {
+ return {
+ registerAnalyticsContextProvider: registerAnalyticsContextProviderMock,
+ };
+});
diff --git a/packages/core/chrome/core-chrome-browser-internal/src/chrome_service.test.tsx b/packages/core/chrome/core-chrome-browser-internal/src/chrome_service.test.tsx
index 0e18b5b72c367..f680a1b5d6ba9 100644
--- a/packages/core/chrome/core-chrome-browser-internal/src/chrome_service.test.tsx
+++ b/packages/core/chrome/core-chrome-browser-internal/src/chrome_service.test.tsx
@@ -6,6 +6,7 @@
* Side Public License, v 1.
*/
+import { registerAnalyticsContextProviderMock } from './chrome_service.test.mocks';
import { shallow, mount } from 'enzyme';
import React from 'react';
import * as Rx from 'rxjs';
@@ -18,6 +19,7 @@ import { applicationServiceMock } from '@kbn/core-application-browser-mocks';
import { notificationServiceMock } from '@kbn/core-notifications-browser-mocks';
import { uiSettingsServiceMock } from '@kbn/core-ui-settings-browser-mocks';
import { customBrandingServiceMock } from '@kbn/core-custom-branding-browser-mocks';
+import { analyticsServiceMock } from '@kbn/core-analytics-browser-mocks';
import { getAppInfo } from '@kbn/core-application-browser-internal';
import { findTestSubject } from '@kbn/test-jest-helpers';
import { ChromeService } from './chrome_service';
@@ -84,15 +86,19 @@ async function start({
startDeps.injectedMetadata.getCspConfig.mockReturnValue(cspConfigMock);
}
+ await service.setup({ analytics: analyticsServiceMock.createAnalyticsServiceSetup() });
+ const chromeStart = await service.start(startDeps);
+
return {
service,
startDeps,
- chrome: await service.start(startDeps),
+ chrome: chromeStart,
};
}
beforeEach(() => {
store.clear();
+ registerAnalyticsContextProviderMock.mockReset();
window.history.pushState(undefined, '', '#/home?a=b');
});
@@ -100,6 +106,20 @@ afterAll(() => {
(window as any).localStorage = originalLocalStorage;
});
+describe('setup', () => {
+ it('calls registerAnalyticsContextProvider with the correct parameters', async () => {
+ const service = new ChromeService(defaultStartTestOptions({}));
+ const analytics = analyticsServiceMock.createAnalyticsServiceSetup();
+ await service.setup({ analytics });
+
+ expect(registerAnalyticsContextProviderMock).toHaveBeenCalledTimes(1);
+ expect(registerAnalyticsContextProviderMock).toHaveBeenCalledWith(
+ analytics,
+ expect.any(Object)
+ );
+ });
+});
+
describe('start', () => {
it('adds legacy browser warning if browserSupportsCsp is disabled and warnLegacyBrowsers is enabled', async () => {
const { startDeps } = await start({
diff --git a/packages/core/chrome/core-chrome-browser-internal/src/chrome_service.tsx b/packages/core/chrome/core-chrome-browser-internal/src/chrome_service.tsx
index 9a37afdb78e16..ed619f50ec63b 100644
--- a/packages/core/chrome/core-chrome-browser-internal/src/chrome_service.tsx
+++ b/packages/core/chrome/core-chrome-browser-internal/src/chrome_service.tsx
@@ -14,6 +14,7 @@ import { parse } from 'url';
import { EuiLink } from '@elastic/eui';
import useObservable from 'react-use/lib/useObservable';
import type { InternalInjectedMetadataStart } from '@kbn/core-injected-metadata-browser-internal';
+import type { AnalyticsServiceSetup } from '@kbn/core-analytics-browser';
import type { DocLinksStart } from '@kbn/core-doc-links-browser';
import type { HttpStart } from '@kbn/core-http-browser';
import { mountReactNode } from '@kbn/core-mount-utils-browser-internal';
@@ -40,6 +41,7 @@ import { NavLinksService } from './nav_links';
import { ProjectNavigationService } from './project_navigation';
import { RecentlyAccessedService } from './recently_accessed';
import { Header, ProjectHeader, ProjectSideNavigation } from './ui';
+import { registerAnalyticsContextProvider } from './register_analytics_context_provider';
import type { InternalChromeStart } from './types';
const IS_LOCKED_KEY = 'core.chrome.isLocked';
@@ -50,6 +52,10 @@ interface ConstructorParams {
kibanaVersion: string;
}
+export interface SetupDeps {
+ analytics: AnalyticsServiceSetup;
+}
+
export interface StartDeps {
application: InternalApplicationStart;
docLinks: DocLinksStart;
@@ -104,6 +110,11 @@ export class ChromeService {
);
}
+ public setup({ analytics }: SetupDeps) {
+ const docTitle = this.docTitle.setup({ document: window.document });
+ registerAnalyticsContextProvider(analytics, docTitle.title$);
+ }
+
public async start({
application,
docLinks,
@@ -155,7 +166,7 @@ export class ChromeService {
const navLinks = this.navLinks.start({ application, http });
const projectNavigation = this.projectNavigation.start({ application, navLinks, http });
const recentlyAccessed = await this.recentlyAccessed.start({ http });
- const docTitle = this.docTitle.start({ document: window.document });
+ const docTitle = this.docTitle.start();
const { customBranding$ } = customBranding;
// erase chrome fields from a previous app while switching to a next app
diff --git a/packages/core/chrome/core-chrome-browser-internal/src/doc_title/doc_title_service.test.ts b/packages/core/chrome/core-chrome-browser-internal/src/doc_title/doc_title_service.test.ts
index f728321fa6dcf..145f0f1a59627 100644
--- a/packages/core/chrome/core-chrome-browser-internal/src/doc_title/doc_title_service.test.ts
+++ b/packages/core/chrome/core-chrome-browser-internal/src/doc_title/doc_title_service.test.ts
@@ -10,47 +10,128 @@ import { DocTitleService } from './doc_title_service';
describe('DocTitleService', () => {
const defaultTitle = 'KibanaTest';
- const document = { title: '' };
+ let document: { title: string };
const getStart = (title: string = defaultTitle) => {
document.title = title;
- return new DocTitleService().start({ document });
+ const docTitle = new DocTitleService();
+ docTitle.setup({ document });
+ return docTitle.start();
};
beforeEach(() => {
- document.title = defaultTitle;
+ document = { title: defaultTitle };
});
- describe('#change()', () => {
- it('changes the title of the document', async () => {
- getStart().change('TitleA');
- expect(document.title).toEqual('TitleA - KibanaTest');
+ describe('#setup', () => {
+ describe('title$', () => {
+ it('emits with the initial title', () => {
+ document.title = 'Kibana';
+ const docTitle = new DocTitleService();
+ const { title$ } = docTitle.setup({ document });
+ docTitle.start();
+
+ const titles: string[] = [];
+ title$.subscribe((title) => {
+ titles.push(title);
+ });
+
+ expect(titles).toEqual(['Kibana']);
+ });
+
+ it('emits when the title changes', () => {
+ document.title = 'Kibana';
+ const docTitle = new DocTitleService();
+ const { title$ } = docTitle.setup({ document });
+ const { change } = docTitle.start();
+
+ const titles: string[] = [];
+ title$.subscribe((title) => {
+ titles.push(title);
+ });
+
+ change('title 2');
+ change('title 3');
+
+ expect(titles).toEqual(['Kibana', 'title 2 - Kibana', 'title 3 - Kibana']);
+ });
+
+ it('emits when the title is reset', () => {
+ document.title = 'Kibana';
+ const docTitle = new DocTitleService();
+ const { title$ } = docTitle.setup({ document });
+ const { change, reset } = docTitle.start();
+
+ const titles: string[] = [];
+ title$.subscribe((title) => {
+ titles.push(title);
+ });
+
+ change('title 2');
+ reset();
+
+ expect(titles).toEqual(['Kibana', 'title 2 - Kibana', 'Kibana']);
+ });
+
+ it('only emits on unique titles', () => {
+ document.title = 'Kibana';
+ const docTitle = new DocTitleService();
+ const { title$ } = docTitle.setup({ document });
+ const { change } = docTitle.start();
+
+ const titles: string[] = [];
+ title$.subscribe((title) => {
+ titles.push(title);
+ });
+
+ change('title 2');
+ change('title 2');
+ change('title 3');
+
+ expect(titles).toEqual(['Kibana', 'title 2 - Kibana', 'title 3 - Kibana']);
+ });
});
+ });
- it('appends the baseTitle to the title', async () => {
- const start = getStart('BaseTitle');
- start.change('TitleA');
- expect(document.title).toEqual('TitleA - BaseTitle');
- start.change('TitleB');
- expect(document.title).toEqual('TitleB - BaseTitle');
+ describe('#start', () => {
+ it('throws if called before #setup', () => {
+ const docTitle = new DocTitleService();
+ expect(() => docTitle.start()).toThrowErrorMatchingInlineSnapshot(
+ `"DocTitleService#setup must be called before DocTitleService#start"`
+ );
});
- it('accepts string arrays as input', async () => {
- const start = getStart();
- start.change(['partA', 'partB']);
- expect(document.title).toEqual(`partA - partB - ${defaultTitle}`);
- start.change(['partA', 'partB', 'partC']);
- expect(document.title).toEqual(`partA - partB - partC - ${defaultTitle}`);
+ describe('#change()', () => {
+ it('changes the title of the document', async () => {
+ getStart().change('TitleA');
+ expect(document.title).toEqual('TitleA - KibanaTest');
+ });
+
+ it('appends the baseTitle to the title', async () => {
+ const start = getStart('BaseTitle');
+ start.change('TitleA');
+ expect(document.title).toEqual('TitleA - BaseTitle');
+ start.change('TitleB');
+ expect(document.title).toEqual('TitleB - BaseTitle');
+ });
+
+ it('accepts string arrays as input', async () => {
+ const start = getStart();
+ start.change(['partA', 'partB']);
+ expect(document.title).toEqual(`partA - partB - ${defaultTitle}`);
+ start.change(['partA', 'partB', 'partC']);
+ expect(document.title).toEqual(`partA - partB - partC - ${defaultTitle}`);
+ });
});
- });
- describe('#reset()', () => {
- it('resets the title to the initial value', async () => {
- const start = getStart('InitialTitle');
- start.change('TitleA');
- expect(document.title).toEqual('TitleA - InitialTitle');
- start.reset();
- expect(document.title).toEqual('InitialTitle');
+ describe('#reset()', () => {
+ it('resets the title to the initial value', async () => {
+ const start = getStart('InitialTitle');
+ start.change('TitleA');
+ expect(document.title).toEqual('TitleA - InitialTitle');
+ start.reset();
+ expect(document.title).toEqual('InitialTitle');
+ });
});
});
});
diff --git a/packages/core/chrome/core-chrome-browser-internal/src/doc_title/doc_title_service.ts b/packages/core/chrome/core-chrome-browser-internal/src/doc_title/doc_title_service.ts
index ee884b59d8122..04a118a842d54 100644
--- a/packages/core/chrome/core-chrome-browser-internal/src/doc_title/doc_title_service.ts
+++ b/packages/core/chrome/core-chrome-browser-internal/src/doc_title/doc_title_service.ts
@@ -7,9 +7,14 @@
*/
import { compact, flattenDeep, isString } from 'lodash';
+import { Observable, ReplaySubject, distinctUntilChanged } from 'rxjs';
import type { ChromeDocTitle } from '@kbn/core-chrome-browser';
-interface StartDeps {
+export interface InternalChromeDocTitleSetup {
+ title$: Observable;
+}
+
+interface SetupDeps {
document: { title: string };
}
@@ -18,12 +23,24 @@ const titleSeparator = ' - ';
/** @internal */
export class DocTitleService {
- private document = { title: '' };
- private baseTitle = '';
+ private document?: { title: string };
+ private baseTitle?: string;
+ private titleSubject = new ReplaySubject(1);
- public start({ document }: StartDeps): ChromeDocTitle {
+ public setup({ document }: SetupDeps): InternalChromeDocTitleSetup {
this.document = document;
this.baseTitle = document.title;
+ this.titleSubject.next(this.baseTitle);
+
+ return {
+ title$: this.titleSubject.asObservable().pipe(distinctUntilChanged()),
+ };
+ }
+
+ public start(): ChromeDocTitle {
+ if (this.document === undefined || this.baseTitle === undefined) {
+ throw new Error('DocTitleService#setup must be called before DocTitleService#start');
+ }
return {
change: (title: string | string[]) => {
@@ -36,7 +53,9 @@ export class DocTitleService {
}
private applyTitle(title: string | string[]) {
- this.document.title = this.render(title);
+ const rendered = this.render(title);
+ this.document!.title = rendered;
+ this.titleSubject.next(rendered);
}
private render(title: string | string[]) {
diff --git a/packages/core/chrome/core-chrome-browser-internal/src/register_analytics_context_provider.test.ts b/packages/core/chrome/core-chrome-browser-internal/src/register_analytics_context_provider.test.ts
new file mode 100644
index 0000000000000..85caf49f12f0a
--- /dev/null
+++ b/packages/core/chrome/core-chrome-browser-internal/src/register_analytics_context_provider.test.ts
@@ -0,0 +1,39 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+import { firstValueFrom, of, ReplaySubject } from 'rxjs';
+import { analyticsServiceMock } from '@kbn/core-analytics-browser-mocks';
+import { registerAnalyticsContextProvider } from './register_analytics_context_provider';
+
+describe('registerAnalyticsContextProvider', () => {
+ let analytics: ReturnType;
+
+ beforeEach(() => {
+ analytics = analyticsServiceMock.createAnalyticsServiceSetup();
+ });
+
+ test('it registers the context provider', async () => {
+ registerAnalyticsContextProvider(analytics, of('some title'));
+ expect(analytics.registerContextProvider).toHaveBeenCalledTimes(1);
+ expect(analytics.registerContextProvider).toHaveBeenCalledWith(
+ expect.objectContaining({
+ name: 'page title',
+ })
+ );
+ });
+
+ test('emits a context value when location$ emits', async () => {
+ const title$ = new ReplaySubject(1);
+ registerAnalyticsContextProvider(analytics, title$);
+ title$.next('kibana title');
+
+ await expect(
+ firstValueFrom(analytics.registerContextProvider.mock.calls[0][0].context$)
+ ).resolves.toEqual({ page_title: 'kibana title' });
+ });
+});
diff --git a/packages/core/chrome/core-chrome-browser-internal/src/register_analytics_context_provider.ts b/packages/core/chrome/core-chrome-browser-internal/src/register_analytics_context_provider.ts
new file mode 100644
index 0000000000000..be811577d3977
--- /dev/null
+++ b/packages/core/chrome/core-chrome-browser-internal/src/register_analytics_context_provider.ts
@@ -0,0 +1,33 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+import { type Observable, map } from 'rxjs';
+import type { AnalyticsServiceSetup } from '@kbn/core-analytics-browser';
+
+/**
+ * Registers the Analytics context provider to enrich events with the page title.
+ * @param analytics Analytics service.
+ * @param pageTitle$ Observable emitting the page title.
+ * @private
+ */
+export function registerAnalyticsContextProvider(
+ analytics: AnalyticsServiceSetup,
+ pageTitle$: Observable
+) {
+ analytics.registerContextProvider({
+ name: 'page title',
+ context$: pageTitle$.pipe(
+ map((pageTitle) => ({
+ page_title: pageTitle,
+ }))
+ ),
+ schema: {
+ page_title: { type: 'text', _meta: { description: 'The page title' } },
+ },
+ });
+}
diff --git a/packages/core/chrome/core-chrome-browser-internal/tsconfig.json b/packages/core/chrome/core-chrome-browser-internal/tsconfig.json
index cd27209bef12c..687c2a2d9eace 100644
--- a/packages/core/chrome/core-chrome-browser-internal/tsconfig.json
+++ b/packages/core/chrome/core-chrome-browser-internal/tsconfig.json
@@ -39,6 +39,8 @@
"@kbn/core-custom-branding-browser-mocks",
"@kbn/core-custom-branding-browser",
"@kbn/core-custom-branding-common",
+ "@kbn/core-analytics-browser-mocks",
+ "@kbn/core-analytics-browser",
],
"exclude": [
"target/**/*",
diff --git a/packages/core/chrome/core-chrome-browser-mocks/src/chrome_service.mock.ts b/packages/core/chrome/core-chrome-browser-mocks/src/chrome_service.mock.ts
index 845c3aa3e8eb6..76192aff162c7 100644
--- a/packages/core/chrome/core-chrome-browser-mocks/src/chrome_service.mock.ts
+++ b/packages/core/chrome/core-chrome-browser-mocks/src/chrome_service.mock.ts
@@ -88,6 +88,7 @@ const createStartContractMock = () => {
type ChromeServiceContract = PublicMethodsOf;
const createMock = () => {
const mocked: jest.Mocked = {
+ setup: jest.fn(),
start: jest.fn(),
stop: jest.fn(),
};
diff --git a/packages/core/root/core-root-browser-internal/src/core_system.test.ts b/packages/core/root/core-root-browser-internal/src/core_system.test.ts
index 0e4c0eb1340d6..d77ccaedb5279 100644
--- a/packages/core/root/core-root-browser-internal/src/core_system.test.ts
+++ b/packages/core/root/core-root-browser-internal/src/core_system.test.ts
@@ -311,6 +311,11 @@ describe('#setup()', () => {
await setupCore();
expect(MockThemeService.setup).toHaveBeenCalledTimes(1);
});
+
+ it('calls chrome#setup()', async () => {
+ await setupCore();
+ expect(MockChromeService.setup).toHaveBeenCalledTimes(1);
+ });
});
describe('#start()', () => {
diff --git a/packages/core/root/core-root-browser-internal/src/core_system.ts b/packages/core/root/core-root-browser-internal/src/core_system.ts
index c4213e5efdb58..cf9a172479b31 100644
--- a/packages/core/root/core-root-browser-internal/src/core_system.ts
+++ b/packages/core/root/core-root-browser-internal/src/core_system.ts
@@ -236,12 +236,13 @@ export class CoreSystem {
fatalErrors: this.fatalErrorsSetup,
executionContext,
});
+ this.chrome.setup({ analytics });
const uiSettings = this.uiSettings.setup({ http, injectedMetadata });
const settings = this.settings.setup({ http, injectedMetadata });
const notifications = this.notifications.setup({ uiSettings });
const customBranding = this.customBranding.setup({ injectedMetadata });
- const application = this.application.setup({ http });
+ const application = this.application.setup({ http, analytics });
this.coreApp.setup({ application, http, injectedMetadata, notifications });
const core: InternalCoreSetup = {
diff --git a/packages/kbn-check-mappings-update-cli/current_mappings.json b/packages/kbn-check-mappings-update-cli/current_mappings.json
index e0a24046196d6..525cbba463d2d 100644
--- a/packages/kbn-check-mappings-update-cli/current_mappings.json
+++ b/packages/kbn-check-mappings-update-cli/current_mappings.json
@@ -931,17 +931,6 @@
}
}
},
- "event-annotation-group": {
- "dynamic": false,
- "properties": {
- "title": {
- "type": "text"
- },
- "description": {
- "type": "text"
- }
- }
- },
"visualization": {
"dynamic": false,
"properties": {
@@ -959,6 +948,17 @@
}
}
},
+ "event-annotation-group": {
+ "dynamic": false,
+ "properties": {
+ "title": {
+ "type": "text"
+ },
+ "description": {
+ "type": "text"
+ }
+ }
+ },
"dashboard": {
"properties": {
"description": {
@@ -1898,9 +1898,6 @@
"fleet-uninstall-tokens": {
"dynamic": false,
"properties": {
- "created_at": {
- "type": "date"
- },
"policy_id": {
"type": "keyword"
},
@@ -2377,6 +2374,13 @@
"type": "boolean"
}
}
+ },
+ "tls": {
+ "properties": {
+ "enabled": {
+ "type": "boolean"
+ }
+ }
}
}
},
diff --git a/src/core/server/integration_tests/saved_objects/migrations/group2/check_registered_types.test.ts b/src/core/server/integration_tests/saved_objects/migrations/group2/check_registered_types.test.ts
index 58ec8e1597cba..9859886cfd6d5 100644
--- a/src/core/server/integration_tests/saved_objects/migrations/group2/check_registered_types.test.ts
+++ b/src/core/server/integration_tests/saved_objects/migrations/group2/check_registered_types.test.ts
@@ -96,7 +96,7 @@ describe('checking migration metadata changes on all registered SO types', () =>
"fleet-message-signing-keys": "93421f43fed2526b59092a4e3c65d64bc2266c0f",
"fleet-preconfiguration-deletion-record": "c52ea1e13c919afe8a5e8e3adbb7080980ecc08e",
"fleet-proxy": "6cb688f0d2dd856400c1dbc998b28704ff70363d",
- "fleet-uninstall-tokens": "d25a8aedb522d2b839ab0950160777528122070f",
+ "fleet-uninstall-tokens": "ed8aa37e3cdd69e4360709e64944bb81cae0c025",
"graph-workspace": "5cc6bb1455b078fd848c37324672163f09b5e376",
"guided-onboarding-guide-state": "d338972ed887ac480c09a1a7fbf582d6a3827c91",
"guided-onboarding-plugin-state": "bc109e5ef46ca594fdc179eda15f3095ca0a37a4",
@@ -140,7 +140,7 @@ describe('checking migration metadata changes on all registered SO types', () =>
"slo": "2048ab6791df2e1ae0936f29c20765cb8d2fcfaa",
"space": "8de4ec513e9bbc6b2f1d635161d850be7747d38e",
"spaces-usage-stats": "3abca98713c52af8b30300e386c7779b3025a20e",
- "synthetics-monitor": "ca7c0710c0607e44b2c52e5a41086b8b4a214f63",
+ "synthetics-monitor": "33ddc4b8979f378edf58bcc7ba13c5c5b572f42d",
"synthetics-param": "3ebb744e5571de678b1312d5c418c8188002cf5e",
"synthetics-privates-locations": "9cfbd6d1d2e2c559bf96dd6fbc27ff0c47719dd3",
"tag": "e2544392fe6563e215bb677abc8b01c2601ef2dc",
diff --git a/src/plugins/chart_expressions/common/index.ts b/src/plugins/chart_expressions/common/index.ts
index 4373260657909..0983b1ed28d4d 100644
--- a/src/plugins/chart_expressions/common/index.ts
+++ b/src/plugins/chart_expressions/common/index.ts
@@ -6,5 +6,10 @@
* Side Public License, v 1.
*/
-export { extractContainerType, extractVisualizationType, getOverridesFor } from './utils';
+export {
+ extractContainerType,
+ extractVisualizationType,
+ getOverridesFor,
+ isOnAggBasedEditor,
+} from './utils';
export type { Simplify, MakeOverridesSerializable } from './types';
diff --git a/src/plugins/chart_expressions/common/utils.test.ts b/src/plugins/chart_expressions/common/utils.test.ts
index 2ed71e9a17b92..48519c9f6f1a9 100644
--- a/src/plugins/chart_expressions/common/utils.test.ts
+++ b/src/plugins/chart_expressions/common/utils.test.ts
@@ -6,7 +6,7 @@
* Side Public License, v 1.
*/
-import { getOverridesFor } from './utils';
+import { getOverridesFor, isOnAggBasedEditor } from './utils';
describe('Overrides utilities', () => {
describe('getOverridesFor', () => {
@@ -31,3 +31,77 @@ describe('Overrides utilities', () => {
});
});
});
+
+describe('isOnAggBasedEditor', () => {
+ it('should return false if is on dashboard', () => {
+ const context = {
+ type: 'dashboard',
+ description: 'test',
+ child: {
+ type: 'lens',
+ name: 'lnsPie',
+ id: 'd8bb29a7-13a4-43fa-a162-d7705050bb6c',
+ description: 'test',
+ url: '/gdu/app/lens#/edit_by_value',
+ },
+ };
+ expect(isOnAggBasedEditor(context)).toEqual(false);
+ });
+
+ it('should return false if is on editor but lens', () => {
+ const context = {
+ type: 'application',
+ description: 'test',
+ child: {
+ type: 'lens',
+ name: 'lnsPie',
+ id: 'd8bb29a7-13a4-43fa-a162-d7705050bb6c',
+ description: 'test',
+ url: '/gdu/app/lens#/edit_by_value',
+ },
+ };
+ expect(isOnAggBasedEditor(context)).toEqual(false);
+ });
+
+ it('should return false if is on dashboard but agg_based', () => {
+ const context = {
+ type: 'dashboard',
+ description: 'test',
+ child: {
+ type: 'agg_based',
+ name: 'pie',
+ id: 'd8bb29a7-13a4-43fa-a162-d7705050bb6c',
+ description: 'test',
+ url: '',
+ },
+ };
+ expect(isOnAggBasedEditor(context)).toEqual(false);
+ });
+
+ it('should return true if is on editor but agg_based', () => {
+ const context = {
+ type: 'application',
+ description: 'test',
+ child: {
+ type: 'agg_based',
+ name: 'pie',
+ id: 'd8bb29a7-13a4-43fa-a162-d7705050bb6c',
+ description: 'test',
+ url: '',
+ },
+ };
+ expect(isOnAggBasedEditor(context)).toEqual(true);
+ });
+
+ it('should return false if child is missing', () => {
+ const context = {
+ type: 'application',
+ description: 'test',
+ };
+ expect(isOnAggBasedEditor(context)).toEqual(false);
+ });
+
+ it('should return false if context is missing', () => {
+ expect(isOnAggBasedEditor()).toEqual(false);
+ });
+});
diff --git a/src/plugins/chart_expressions/common/utils.ts b/src/plugins/chart_expressions/common/utils.ts
index 2966532c44117..db2e564efc4b3 100644
--- a/src/plugins/chart_expressions/common/utils.ts
+++ b/src/plugins/chart_expressions/common/utils.ts
@@ -20,6 +20,31 @@ export const extractContainerType = (context?: KibanaExecutionContext): string |
}
};
+/* Function to identify if the pie is rendered inside the aggBased editor
+ Context comes with this format
+ {
+ type: 'dashboard', // application for lens, agg based charts
+ description: 'test',
+ child: {
+ type: 'lens', // agg_based for legacy editor
+ name: 'pie',
+ id: 'id',
+ description: 'test',
+ url: '',
+ },
+ }; */
+export const isOnAggBasedEditor = (context?: KibanaExecutionContext): boolean => {
+ if (context) {
+ return Boolean(
+ context.type &&
+ context.type === 'application' &&
+ context.child &&
+ context.child.type === 'agg_based'
+ );
+ }
+ return false;
+};
+
export const extractVisualizationType = (context?: KibanaExecutionContext): string | undefined => {
if (context) {
const recursiveGet = (item: KibanaExecutionContext): KibanaExecutionContext | undefined => {
diff --git a/src/plugins/chart_expressions/expression_partition_vis/public/components/partition_vis_component.test.tsx b/src/plugins/chart_expressions/expression_partition_vis/public/components/partition_vis_component.test.tsx
index 5eb48cfab6cd5..c935ce847e40e 100644
--- a/src/plugins/chart_expressions/expression_partition_vis/public/components/partition_vis_component.test.tsx
+++ b/src/plugins/chart_expressions/expression_partition_vis/public/components/partition_vis_component.test.tsx
@@ -83,6 +83,7 @@ describe('PartitionVisComponent', function () {
data: dataPluginMock.createStartContract(),
fieldFormats: fieldFormatsServiceMock.createStartContract(),
},
+ hasOpenedOnAggBasedEditor: false,
};
});
diff --git a/src/plugins/chart_expressions/expression_partition_vis/public/components/partition_vis_component.tsx b/src/plugins/chart_expressions/expression_partition_vis/public/components/partition_vis_component.tsx
index 94590ff164555..4ce300a7d9bb9 100644
--- a/src/plugins/chart_expressions/expression_partition_vis/public/components/partition_vis_component.tsx
+++ b/src/plugins/chart_expressions/expression_partition_vis/public/components/partition_vis_component.tsx
@@ -93,6 +93,7 @@ export type PartitionVisComponentProps = Omit<
palettesRegistry: PaletteRegistry;
services: Pick;
columnCellValueActions: ColumnCellValueActions;
+ hasOpenedOnAggBasedEditor: boolean;
};
const PartitionVisComponent = (props: PartitionVisComponentProps) => {
@@ -105,6 +106,7 @@ const PartitionVisComponent = (props: PartitionVisComponentProps) => {
syncColors,
interactive,
overrides,
+ hasOpenedOnAggBasedEditor,
} = props;
const visParams = useMemo(() => filterOutConfig(visType, preVisParams), [preVisParams, visType]);
const chartTheme = props.chartsThemeService.useChartsTheme();
@@ -148,7 +150,7 @@ const PartitionVisComponent = (props: PartitionVisComponentProps) => {
const [showLegend, setShowLegend] = useState(() => showLegendDefault());
const showToggleLegendElement = props.uiState !== undefined;
-
+ const [chartIsLoaded, setChartIsLoaded] = useState(false);
const [containerDimensions, setContainerDimensions] = useState<
undefined | PieContainerDimensions
>();
@@ -156,12 +158,14 @@ const PartitionVisComponent = (props: PartitionVisComponentProps) => {
const parentRef = useRef(null);
useEffect(() => {
- if (parentRef && parentRef.current) {
+ // chart should be loaded to compute the dimensions
+ // otherwise the height is set to 0
+ if (parentRef && parentRef.current && chartIsLoaded) {
const parentHeight = parentRef.current!.getBoundingClientRect().height;
const parentWidth = parentRef.current!.getBoundingClientRect().width;
setContainerDimensions({ width: parentWidth, height: parentHeight });
}
- }, [parentRef]);
+ }, [chartIsLoaded, parentRef]);
useEffect(() => {
const legendShow = showLegendDefault();
@@ -172,6 +176,7 @@ const PartitionVisComponent = (props: PartitionVisComponentProps) => {
(isRendered: boolean = true) => {
if (isRendered) {
props.renderComplete();
+ setChartIsLoaded(true);
}
},
[props]
@@ -363,8 +368,16 @@ const PartitionVisComponent = (props: PartitionVisComponentProps) => {
) as Partial;
const themeOverrides = useMemo(
- () => getPartitionTheme(visType, visParams, chartTheme, containerDimensions, rescaleFactor),
- [visType, visParams, chartTheme, containerDimensions, rescaleFactor]
+ () =>
+ getPartitionTheme(
+ visType,
+ visParams,
+ chartTheme,
+ containerDimensions,
+ rescaleFactor,
+ hasOpenedOnAggBasedEditor
+ ),
+ [visType, visParams, chartTheme, containerDimensions, rescaleFactor, hasOpenedOnAggBasedEditor]
);
const fixedViewPort = document.getElementById('app-fixed-viewport');
diff --git a/src/plugins/chart_expressions/expression_partition_vis/public/expression_renderers/partition_vis_renderer.tsx b/src/plugins/chart_expressions/expression_partition_vis/public/expression_renderers/partition_vis_renderer.tsx
index 056ba6b7136ce..2379096796639 100644
--- a/src/plugins/chart_expressions/expression_partition_vis/public/expression_renderers/partition_vis_renderer.tsx
+++ b/src/plugins/chart_expressions/expression_partition_vis/public/expression_renderers/partition_vis_renderer.tsx
@@ -21,7 +21,11 @@ import { withSuspense } from '@kbn/presentation-util-plugin/public';
import { KibanaThemeProvider } from '@kbn/kibana-react-plugin/public';
import { METRIC_TYPE } from '@kbn/analytics';
import { getColumnByAccessor } from '@kbn/visualizations-plugin/common/utils';
-import { extractContainerType, extractVisualizationType } from '@kbn/chart-expressions-common';
+import {
+ extractContainerType,
+ extractVisualizationType,
+ isOnAggBasedEditor,
+} from '@kbn/chart-expressions-common';
import { VisTypePieDependencies } from '../plugin';
import { PARTITION_VIS_RENDERER_NAME } from '../../common/constants';
import { CellValueAction, GetCompatibleCellValueActions } from '../types';
@@ -110,6 +114,8 @@ export const getPartitionVisRenderer: (
plugins.charts.palettes.getPalettes(),
]);
+ const hasOpenedOnAggBasedEditor = isOnAggBasedEditor(handlers.getExecutionContext());
+
render(
@@ -128,6 +134,7 @@ export const getPartitionVisRenderer: (
syncColors={syncColors}
columnCellValueActions={columnCellValueActions}
overrides={overrides}
+ hasOpenedOnAggBasedEditor={hasOpenedOnAggBasedEditor}
/>
diff --git a/src/plugins/chart_expressions/expression_partition_vis/public/utils/get_partition_theme.test.ts b/src/plugins/chart_expressions/expression_partition_vis/public/utils/get_partition_theme.test.ts
index 345d6ce068d0c..31d025ac0310f 100644
--- a/src/plugins/chart_expressions/expression_partition_vis/public/utils/get_partition_theme.test.ts
+++ b/src/plugins/chart_expressions/expression_partition_vis/public/utils/get_partition_theme.test.ts
@@ -144,9 +144,16 @@ const runPieDonutWaffleTestSuites = (chartType: ChartTypes, visParams: Partition
});
});
- it('should return adjusted padding settings if dimensions are specified', () => {
+ it('should return adjusted padding settings if dimensions are specified and is on aggBased editor', () => {
const specifiedDimensions = { width: 2000, height: 2000 };
- const theme = getPartitionTheme(chartType, visParams, chartTheme, specifiedDimensions);
+ const theme = getPartitionTheme(
+ chartType,
+ visParams,
+ chartTheme,
+ specifiedDimensions,
+ undefined,
+ true
+ );
expect(theme).toEqual({
...getStaticThemeOptions(chartTheme, visParams),
@@ -233,7 +240,6 @@ const runPieDonutWaffleTestSuites = (chartType: ChartTypes, visParams: Partition
expect(theme).toEqual({
...getStaticThemeOptions(chartTheme, visParams),
- chartPaddings: { top: 500, bottom: 500, left: 500, right: 500 },
partition: {
...getStaticThemePartition(chartTheme, visParams),
outerSizeRatio: rescaleFactor,
@@ -263,7 +269,6 @@ const runPieDonutWaffleTestSuites = (chartType: ChartTypes, visParams: Partition
expect(theme).toEqual({
...getStaticThemeOptions(chartTheme, vParams),
- chartPaddings: { top: 500, bottom: 500, left: 500, right: 500 },
partition: {
...getStaticThemePartition(chartTheme, vParams),
outerSizeRatio: 0.5,
@@ -285,7 +290,6 @@ const runPieDonutWaffleTestSuites = (chartType: ChartTypes, visParams: Partition
expect(theme).toEqual({
...getStaticThemeOptions(chartTheme, vParams),
- chartPaddings: { top: 500, bottom: 500, left: 500, right: 500 },
partition: {
...getStaticThemePartition(chartTheme, vParams),
linkLabel: linkLabelWithEnoughSpace(vParams),
@@ -420,7 +424,14 @@ const runTreemapMosaicTestSuites = (chartType: ChartTypes, visParams: PartitionV
it('should return fullfilled padding settings if dimensions are specified', () => {
const specifiedDimensions = { width: 2000, height: 2000 };
- const theme = getPartitionTheme(chartType, visParams, chartTheme, specifiedDimensions);
+ const theme = getPartitionTheme(
+ chartType,
+ visParams,
+ chartTheme,
+ specifiedDimensions,
+ undefined,
+ true
+ );
expect(theme).toEqual({
...getStaticThemeOptions(chartTheme, visParams),
diff --git a/src/plugins/chart_expressions/expression_partition_vis/public/utils/get_partition_theme.ts b/src/plugins/chart_expressions/expression_partition_vis/public/utils/get_partition_theme.ts
index edb1aaea64aad..3714cac911829 100644
--- a/src/plugins/chart_expressions/expression_partition_vis/public/utils/get_partition_theme.ts
+++ b/src/plugins/chart_expressions/expression_partition_vis/public/utils/get_partition_theme.ts
@@ -26,7 +26,8 @@ type GetThemeFn = (
visParams: PartitionVisParams,
chartTheme: RecursivePartial,
dimensions?: PieContainerDimensions,
- rescaleFactor?: number
+ rescaleFactor?: number,
+ hasOpenedOnAggBasedEditor?: boolean
) => PartialTheme;
type GetPieDonutWaffleThemeFn = (
@@ -118,12 +119,13 @@ export const getPartitionTheme: GetThemeFn = (
visParams,
chartTheme,
dimensions,
- rescaleFactor = 1
+ rescaleFactor = 1,
+ hasOpenedOnAggBasedEditor
) => {
// On small multiples we want the labels to only appear inside
const isSplitChart = Boolean(visParams.dimensions.splitColumn || visParams.dimensions.splitRow);
const paddingProps: PartialTheme | null =
- dimensions && !isSplitChart
+ dimensions && !isSplitChart && hasOpenedOnAggBasedEditor
? {
chartPaddings: {
top: ((1 - Math.min(1, MAX_SIZE / dimensions?.height)) / 2) * dimensions?.height,
diff --git a/test/analytics/tests/instrumented_events/from_the_browser/core_context_providers.ts b/test/analytics/tests/instrumented_events/from_the_browser/core_context_providers.ts
index 79e43f0df086f..19a0bff55153d 100644
--- a/test/analytics/tests/instrumented_events/from_the_browser/core_context_providers.ts
+++ b/test/analytics/tests/instrumented_events/from_the_browser/core_context_providers.ts
@@ -98,5 +98,15 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
expect(event.context).to.have.property('viewport_height');
expect(event.context.viewport_height).to.be.a('number');
});
+
+ it('should have the properties provided by the "page title" context provider', async () => {
+ expect(event.context).to.have.property('page_title');
+ expect(event.context.page_title).to.be.a('string');
+ });
+
+ it('should have the properties provided by the "page url" context provider', () => {
+ expect(event.context).to.have.property('page_url');
+ expect(event.context.page_url).to.be.a('string');
+ });
});
}
diff --git a/x-pack/packages/kbn-slo-schema/src/rest_specs/slo.ts b/x-pack/packages/kbn-slo-schema/src/rest_specs/slo.ts
index 9d37c36c94534..cbab659f07c82 100644
--- a/x-pack/packages/kbn-slo-schema/src/rest_specs/slo.ts
+++ b/x-pack/packages/kbn-slo-schema/src/rest_specs/slo.ts
@@ -22,6 +22,7 @@ import {
summarySchema,
tagsSchema,
timeWindowSchema,
+ timeWindowTypeSchema,
} from '../schema';
const createSLOParamsSchema = t.type({
@@ -166,6 +167,7 @@ type GetPreviewDataParams = t.TypeOf;
type BudgetingMethod = t.TypeOf;
+type TimeWindow = t.TypeOf;
type Indicator = t.OutputOf;
type MetricCustomIndicator = t.OutputOf;
@@ -211,4 +213,5 @@ export type {
Indicator,
MetricCustomIndicator,
KQLCustomIndicator,
+ TimeWindow,
};
diff --git a/x-pack/packages/kbn-slo-schema/src/schema/time_window.ts b/x-pack/packages/kbn-slo-schema/src/schema/time_window.ts
index ebbb957d723b6..b495f7e545314 100644
--- a/x-pack/packages/kbn-slo-schema/src/schema/time_window.ts
+++ b/x-pack/packages/kbn-slo-schema/src/schema/time_window.ts
@@ -20,6 +20,10 @@ const calendarAlignedTimeWindowSchema = t.type({
type: calendarAlignedTimeWindowTypeSchema,
});
+const timeWindowTypeSchema = t.union([
+ rollingTimeWindowTypeSchema,
+ calendarAlignedTimeWindowTypeSchema,
+]);
const timeWindowSchema = t.union([rollingTimeWindowSchema, calendarAlignedTimeWindowSchema]);
export {
@@ -28,4 +32,5 @@ export {
calendarAlignedTimeWindowSchema,
calendarAlignedTimeWindowTypeSchema,
timeWindowSchema,
+ timeWindowTypeSchema,
};
diff --git a/x-pack/plugins/enterprise_search/server/index.ts b/x-pack/plugins/enterprise_search/server/index.ts
index 692d17d9952bc..bb4e1e2e0bf0e 100644
--- a/x-pack/plugins/enterprise_search/server/index.ts
+++ b/x-pack/plugins/enterprise_search/server/index.ts
@@ -52,3 +52,4 @@ export const CURRENT_CONNECTORS_INDEX = '.elastic-connectors-v1';
export const CONNECTORS_JOBS_INDEX = '.elastic-connectors-sync-jobs';
export const CONNECTORS_VERSION = 1;
export const CRAWLERS_INDEX = '.ent-search-actastic-crawler2_configurations_v2';
+export const CONNECTORS_ACCESS_CONTROL_INDEX_PREFIX = 'search-acl-filter-';
diff --git a/x-pack/plugins/enterprise_search/server/lib/connectors/start_sync.test.ts b/x-pack/plugins/enterprise_search/server/lib/connectors/start_sync.test.ts
index 6c69273e2e240..ca7ae9fc76a66 100644
--- a/x-pack/plugins/enterprise_search/server/lib/connectors/start_sync.test.ts
+++ b/x-pack/plugins/enterprise_search/server/lib/connectors/start_sync.test.ts
@@ -7,7 +7,11 @@
import { IScopedClusterClient } from '@kbn/core/server';
-import { CONNECTORS_INDEX, CONNECTORS_JOBS_INDEX } from '../..';
+import {
+ CONNECTORS_ACCESS_CONTROL_INDEX_PREFIX,
+ CONNECTORS_INDEX,
+ CONNECTORS_JOBS_INDEX,
+} from '../..';
import { SyncJobType, SyncStatus, TriggerMethod } from '../../../common/types/connectors';
import { ErrorCode } from '../../../common/types/error_codes';
@@ -311,7 +315,7 @@ describe('startSync lib function', () => {
created_at: null,
custom_scheduling: {},
error: null,
- index_name: 'index_name',
+ index_name: 'search-index_name',
language: null,
last_access_control_sync_status: null,
last_seen: null,
@@ -345,7 +349,7 @@ describe('startSync lib function', () => {
configuration: {},
filtering: null,
id: 'connectorId',
- index_name: 'index_name',
+ index_name: `${CONNECTORS_ACCESS_CONTROL_INDEX_PREFIX}index_name`,
language: null,
pipeline: null,
service_type: null,
diff --git a/x-pack/plugins/enterprise_search/server/lib/connectors/start_sync.ts b/x-pack/plugins/enterprise_search/server/lib/connectors/start_sync.ts
index ff9e0419e69b0..8d2ac4715e8df 100644
--- a/x-pack/plugins/enterprise_search/server/lib/connectors/start_sync.ts
+++ b/x-pack/plugins/enterprise_search/server/lib/connectors/start_sync.ts
@@ -7,18 +7,22 @@
import { IScopedClusterClient } from '@kbn/core/server';
-import { CONNECTORS_INDEX, CONNECTORS_JOBS_INDEX } from '../..';
+import {
+ CONNECTORS_ACCESS_CONTROL_INDEX_PREFIX,
+ CONNECTORS_INDEX,
+ CONNECTORS_JOBS_INDEX,
+} from '../..';
import { isConfigEntry } from '../../../common/connectors/is_category_entry';
import { ENTERPRISE_SEARCH_CONNECTOR_CRAWLER_SERVICE_TYPE } from '../../../common/constants';
import {
- ConnectorSyncConfiguration,
ConnectorDocument,
+ ConnectorSyncConfiguration,
ConnectorSyncJobDocument,
+ SyncJobType,
SyncStatus,
TriggerMethod,
- SyncJobType,
} from '../../../common/types/connectors';
import { ErrorCode } from '../../../common/types/error_codes';
@@ -63,6 +67,12 @@ export const startConnectorSync = async (
});
}
+ const indexNameWithoutSearchPrefix = index_name.replace('search-', '');
+ const targetIndexName =
+ jobType === SyncJobType.ACCESS_CONTROL
+ ? `${CONNECTORS_ACCESS_CONTROL_INDEX_PREFIX}${indexNameWithoutSearchPrefix}`
+ : index_name;
+
return await client.asCurrentUser.index({
document: {
cancelation_requested_at: null,
@@ -72,7 +82,7 @@ export const startConnectorSync = async (
configuration,
filtering: filtering ? filtering[0]?.active ?? null : null,
id: connectorId,
- index_name,
+ index_name: targetIndexName,
language,
pipeline: pipeline ?? null,
service_type,
diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/hooks/navigation.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/hooks/navigation.tsx
index b908cb4908ade..097323962bc0c 100644
--- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/hooks/navigation.tsx
+++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/hooks/navigation.tsx
@@ -4,9 +4,11 @@
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
+
import { useCallback, useMemo, useEffect, useRef } from 'react';
-import { useHistory } from 'react-router-dom';
+import type { ApplicationStart } from '@kbn/core-application-browser';
+import { PLUGIN_ID } from '../../../../constants';
import { useStartServices, useLink, useIntraAppState } from '../../../../hooks';
import type {
CreatePackagePolicyRouteState,
@@ -67,7 +69,6 @@ export const useOnSaveNavigate = (params: UseOnSaveNavigateParams) => {
const routeState = useIntraAppState();
const doOnSaveNavigation = useRef(true);
const { getPath } = useLink();
- const history = useHistory();
const {
application: { navigateToApp },
@@ -81,32 +82,46 @@ export const useOnSaveNavigate = (params: UseOnSaveNavigateParams) => {
}, []);
const onSaveNavigate = useCallback(
- (policy?: PackagePolicy, paramsToApply: OnSaveQueryParamKeys[] = []) => {
+ (policy: PackagePolicy, paramsToApply: OnSaveQueryParamKeys[] = []) => {
if (!doOnSaveNavigation.current) {
return;
}
-
const packagePolicyPath = getPath('policy_details', { policyId: packagePolicy.policy_id });
- if (routeState?.onSaveNavigateTo && policy) {
- const [appId, options] = routeState.onSaveNavigateTo;
- if (options?.path) {
- const pathWithQueryString = appendOnSaveQueryParamsToPath({
- // In cases where we want to navigate back to a new/existing policy, we need to override the initial `path`
- // value and navigate to the actual agent policy instead
- path: queryParamsPolicyId ? packagePolicyPath : options.path,
- policy,
- mappingOptions: routeState.onSaveQueryParams,
- paramsToApply,
- });
- navigateToApp(appId, { ...options, path: pathWithQueryString });
- } else {
- navigateToApp(...routeState.onSaveNavigateTo);
- }
+
+ const [onSaveNavigateTo, onSaveQueryParams]: [
+ Parameters,
+ CreatePackagePolicyRouteState['onSaveQueryParams']
+ ] = routeState?.onSaveNavigateTo
+ ? [routeState.onSaveNavigateTo, routeState?.onSaveQueryParams]
+ : [
+ [
+ PLUGIN_ID,
+ {
+ path: packagePolicyPath,
+ },
+ ],
+ {
+ showAddAgentHelp: true,
+ openEnrollmentFlyout: true,
+ },
+ ];
+
+ const [appId, options] = onSaveNavigateTo;
+ if (options?.path) {
+ const pathWithQueryString = appendOnSaveQueryParamsToPath({
+ // In cases where we want to navigate back to a new/existing policy, we need to override the initial `path`
+ // value and navigate to the actual agent policy instead
+ path: queryParamsPolicyId ? packagePolicyPath : options.path,
+ policy,
+ mappingOptions: onSaveQueryParams,
+ paramsToApply,
+ });
+ navigateToApp(appId, { ...options, path: pathWithQueryString });
} else {
- history.push(packagePolicyPath);
+ navigateToApp(...onSaveNavigateTo);
}
},
- [packagePolicy.policy_id, getPath, navigateToApp, history, routeState, queryParamsPolicyId]
+ [packagePolicy.policy_id, getPath, navigateToApp, routeState, queryParamsPolicyId]
);
return onSaveNavigate;
diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/hooks/form.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/hooks/form.tsx
index cc01cadccb788..83b476b774913 100644
--- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/hooks/form.tsx
+++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/hooks/form.tsx
@@ -233,10 +233,10 @@ export function useOnSubmit({
queryParamsPolicyId,
});
- const navigateAddAgent = (policy?: PackagePolicy) =>
+ const navigateAddAgent = (policy: PackagePolicy) =>
onSaveNavigate(policy, ['openEnrollmentFlyout']);
- const navigateAddAgentHelp = (policy?: PackagePolicy) =>
+ const navigateAddAgentHelp = (policy: PackagePolicy) =>
onSaveNavigate(policy, ['showAddAgentHelp']);
const onSubmit = useCallback(
diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/index.test.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/index.test.tsx
index e2f2ec5e908f7..458f4fab53384 100644
--- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/index.test.tsx
+++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/index.test.tsx
@@ -5,7 +5,6 @@
* 2.0.
*/
-import { useHistory } from 'react-router-dom';
import { Route } from '@kbn/shared-ux-router';
import React from 'react';
import { fireEvent, act, waitFor } from '@testing-library/react';
@@ -127,6 +126,8 @@ describe('when on the package policy create page', () => {
mockApiCalls(testRenderer.startServices.http);
testRenderer.mountHistory.push(createPageUrlPath);
+ jest.mocked(useStartServices().application.navigateToApp).mockReset();
+
mockPackageInfo = {
data: {
item: {
@@ -339,12 +340,15 @@ describe('when on the package policy create page', () => {
test('should navigate to save navigate path with query param if set', async () => {
const routeState = {
onSaveNavigateTo: [PLUGIN_ID, { path: '/save/url/here' }],
+ onSaveQueryParams: {
+ openEnrollmentFlyout: true,
+ },
};
const queryParamsPolicyId = 'agent-policy-1';
await setupSaveNavigate(routeState, queryParamsPolicyId);
expect(useStartServices().application.navigateToApp).toHaveBeenCalledWith(PLUGIN_ID, {
- path: '/policies/agent-policy-1',
+ path: '/policies/agent-policy-1?openEnrollmentFlyout=true',
});
});
@@ -357,10 +361,12 @@ describe('when on the package policy create page', () => {
expect(useStartServices().application.navigateToApp).toHaveBeenCalledWith(PLUGIN_ID);
});
- test('should set history if no routeState', async () => {
+ test('should navigate to agent policy if no route state is set', async () => {
await setupSaveNavigate({});
- expect(useHistory().push).toHaveBeenCalledWith('/policies/agent-policy-1');
+ expect(useStartServices().application.navigateToApp).toHaveBeenCalledWith(PLUGIN_ID, {
+ path: '/policies/agent-policy-1?openEnrollmentFlyout=true',
+ });
});
});
diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/index.tsx
index 81c1c518ccd4f..fd62addd08391 100644
--- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/index.tsx
+++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/index.tsx
@@ -402,13 +402,16 @@ export const CreatePackagePolicySinglePage: CreatePackagePolicyParams = ({
onCancel={() => setFormState('VALID')}
/>
)}
- {formState === 'SUBMITTED_NO_AGENTS' && agentPolicy && packageInfo && (
- navigateAddAgent(savedPackagePolicy)}
- onCancel={() => navigateAddAgentHelp(savedPackagePolicy)}
- />
- )}
+ {formState === 'SUBMITTED_NO_AGENTS' &&
+ agentPolicy &&
+ packageInfo &&
+ savedPackagePolicy && (
+ navigateAddAgent(savedPackagePolicy)}
+ onCancel={() => navigateAddAgentHelp(savedPackagePolicy)}
+ />
+ )}
{packageInfo && (
({
mappings: {
dynamic: false,
properties: {
- created_at: { type: 'date' },
policy_id: { type: 'keyword' },
token_plain: { type: 'keyword' },
},
diff --git a/x-pack/plugins/fleet/server/services/security/uninstall_token_service/index.ts b/x-pack/plugins/fleet/server/services/security/uninstall_token_service/index.ts
index 013f77f3005f3..40f0768161368 100644
--- a/x-pack/plugins/fleet/server/services/security/uninstall_token_service/index.ts
+++ b/x-pack/plugins/fleet/server/services/security/uninstall_token_service/index.ts
@@ -152,7 +152,7 @@ export class UninstallTokenService implements UninstallTokenServiceInterface {
latest: {
top_hits: {
size: 1,
- sort: [{ [`${UNINSTALL_TOKENS_SAVED_OBJECT_TYPE}.created_at`]: { order: 'desc' } }],
+ sort: [{ created_at: { order: 'desc' } }],
},
},
},
diff --git a/x-pack/plugins/fleet/tsconfig.json b/x-pack/plugins/fleet/tsconfig.json
index 499af010d076f..dc858ff5029a8 100644
--- a/x-pack/plugins/fleet/tsconfig.json
+++ b/x-pack/plugins/fleet/tsconfig.json
@@ -98,5 +98,6 @@
"@kbn/safer-lodash-set",
"@kbn/shared-ux-file-types",
"@kbn/core-http-router-server-mocks",
+ "@kbn/core-application-browser",
]
}
diff --git a/x-pack/plugins/infra/public/pages/metrics/hosts/components/tabs/logs/logs_link_to_stream.tsx b/x-pack/plugins/infra/public/pages/metrics/hosts/components/tabs/logs/logs_link_to_stream.tsx
index f5c2101317f01..ac3981026ea8e 100644
--- a/x-pack/plugins/infra/public/pages/metrics/hosts/components/tabs/logs/logs_link_to_stream.tsx
+++ b/x-pack/plugins/infra/public/pages/metrics/hosts/components/tabs/logs/logs_link_to_stream.tsx
@@ -8,15 +8,17 @@ import React from 'react';
import { RedirectAppLinks } from '@kbn/shared-ux-link-redirect-app';
import { EuiButtonEmpty } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n-react';
+import { LogViewReference } from '../../../../../../../common/log_views';
import { useKibanaContextForPlugin } from '../../../../../../hooks/use_kibana';
interface LogsLinkToStreamProps {
startTime: number;
endTime: number;
query: string;
+ logView: LogViewReference;
}
-export const LogsLinkToStream = ({ startTime, endTime, query }: LogsLinkToStreamProps) => {
+export const LogsLinkToStream = ({ startTime, endTime, query, logView }: LogsLinkToStreamProps) => {
const { services } = useKibanaContextForPlugin();
const { locators } = services;
@@ -30,6 +32,7 @@ export const LogsLinkToStream = ({ startTime, endTime, query }: LogsLinkToStream
endTime,
},
filter: query,
+ logView,
})}
data-test-subj="hostsView-logs-link-to-stream-button"
iconType="popout"
diff --git a/x-pack/plugins/infra/public/pages/metrics/hosts/components/tabs/logs/logs_tab_content.tsx b/x-pack/plugins/infra/public/pages/metrics/hosts/components/tabs/logs/logs_tab_content.tsx
index db93a9a4617cc..55a8410618b28 100644
--- a/x-pack/plugins/infra/public/pages/metrics/hosts/components/tabs/logs/logs_tab_content.tsx
+++ b/x-pack/plugins/infra/public/pages/metrics/hosts/components/tabs/logs/logs_tab_content.tsx
@@ -5,9 +5,15 @@
* 2.0.
*/
-import React, { useMemo } from 'react';
+import React, { useEffect, useMemo, useState } from 'react';
import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n-react';
+import { DEFAULT_LOG_VIEW } from '../../../../../../../common/log_views';
+import type {
+ LogIndexReference,
+ LogViewReference,
+} from '../../../../../../../common/log_views/types';
+import { useKibanaContextForPlugin } from '../../../../../../hooks/use_kibana';
import { InfraLoadingPanel } from '../../../../../../components/loading';
import { LogStream } from '../../../../../../components/log_stream';
import { useHostsViewContext } from '../../../hooks/use_hosts_view';
@@ -23,11 +29,57 @@ export const LogsTabContent = () => {
const { from, to } = useMemo(() => getDateRangeAsTimestamp(), [getDateRangeAsTimestamp]);
const { hostNodes, loading } = useHostsViewContext();
+ const [logViewIndices, setLogViewIndices] = useState();
+
+ const {
+ services: {
+ logViews: { client },
+ },
+ } = useKibanaContextForPlugin();
+
+ useEffect(() => {
+ const getLogView = async () => {
+ const { attributes } = await client.getLogView(DEFAULT_LOG_VIEW);
+ setLogViewIndices(attributes.logIndices);
+ };
+ getLogView();
+ }, [client, setLogViewIndices]);
+
const hostsFilterQuery = useMemo(
() => createHostsFilter(hostNodes.map((p) => p.name)),
[hostNodes]
);
+ const logView: LogViewReference = useMemo(() => {
+ return {
+ type: 'log-view-inline',
+ id: 'hosts-logs-view',
+ attributes: {
+ name: 'Hosts Logs View',
+ description: 'Default view for hosts logs tab',
+ logIndices: logViewIndices!,
+ logColumns: [
+ {
+ timestampColumn: {
+ id: '5e7f964a-be8a-40d8-88d2-fbcfbdca0e2f',
+ },
+ },
+ {
+ fieldColumn: {
+ id: 'eb9777a8-fcd3-420e-ba7d-172fff6da7a2',
+ field: 'host.name',
+ },
+ },
+ {
+ messageColumn: {
+ id: 'b645d6da-824b-4723-9a2a-e8cece1645c0',
+ },
+ },
+ ],
+ },
+ };
+ }, [logViewIndices]);
+
const logsLinkToStreamQuery = useMemo(() => {
const hostsFilterQueryParam = createHostsFilterQueryParam(hostNodes.map((p) => p.name));
@@ -38,7 +90,7 @@ export const LogsTabContent = () => {
return filterQuery.query || hostsFilterQueryParam;
}, [filterQuery.query, hostNodes]);
- if (loading) {
+ if (loading || !logViewIndices) {
return (
@@ -64,14 +116,19 @@ export const LogsTabContent = () => {
-
+
{
+ const url = new URL(HOSTS_FEEDBACK_LINK);
+ if (kibanaVersion) {
+ url.searchParams.append(KIBANA_VERSION_QUERY_PARAM, kibanaVersion);
+ }
+
+ return url.href;
+};
export const HostsPage = () => {
const { isLoading, loadSourceFailureMessage, loadSource, source } = useSourceContext();
+ const {
+ services: { kibanaVersion },
+ } = useKibanaContextForPlugin();
useTrackPageview({ app: 'infra_metrics', path: 'hosts' });
useTrackPageview({ app: 'infra_metrics', path: 'hosts', delay: 15000 });
@@ -83,7 +98,7 @@ export const HostsPage = () => {
rightSideItems: [
(() => ({}));
constructor(context: PluginInitializerContext) {
@@ -74,6 +75,7 @@ export class Plugin implements InfraClientPluginClass {
this.metricsExplorerViews = new MetricsExplorerViewsService();
this.telemetry = new TelemetryService();
this.appTarget = this.config.logs.app_target;
+ this.kibanaVersion = context.env.packageInfo.version;
}
setup(core: InfraClientCoreSetup, pluginsSetup: InfraClientSetupDeps) {
@@ -286,10 +288,15 @@ export class Plugin implements InfraClientPluginClass {
deepLinks: infraDeepLinks,
mount: async (params: AppMountParameters) => {
// mount callback should not use setup dependencies, get start dependencies instead
- const [coreStart, pluginsStart, pluginStart] = await core.getStartServices();
+ const [coreStart, plugins, pluginStart] = await core.getStartServices();
const { renderApp } = await import('./apps/metrics_app');
- return renderApp(coreStart, pluginsStart, pluginStart, params);
+ return renderApp(
+ coreStart,
+ { ...plugins, kibanaVersion: this.kibanaVersion },
+ pluginStart,
+ params
+ );
},
});
diff --git a/x-pack/plugins/infra/public/types.ts b/x-pack/plugins/infra/public/types.ts
index fb1d2d4ab2a91..15d4f4ea5fb6e 100644
--- a/x-pack/plugins/infra/public/types.ts
+++ b/x-pack/plugins/infra/public/types.ts
@@ -93,6 +93,7 @@ export interface InfraClientStartDeps {
dataViews: DataViewsPublicPluginStart;
discover: DiscoverStart;
embeddable?: EmbeddableStart;
+ kibanaVersion?: string;
lens: LensPublicStart;
ml: MlPluginStart;
observability: ObservabilityPublicStart;
diff --git a/x-pack/plugins/ml/common/types/storage.ts b/x-pack/plugins/ml/common/types/storage.ts
index cb80b17bda583..a74bbea0e3aff 100644
--- a/x-pack/plugins/ml/common/types/storage.ts
+++ b/x-pack/plugins/ml/common/types/storage.ts
@@ -14,6 +14,7 @@ export const ML_GETTING_STARTED_CALLOUT_DISMISSED = 'ml.gettingStarted.isDismiss
export const ML_FROZEN_TIER_PREFERENCE = 'ml.frozenDataTierPreference';
export const ML_ANOMALY_EXPLORER_PANELS = 'ml.anomalyExplorerPanels';
export const ML_NOTIFICATIONS_LAST_CHECKED_AT = 'ml.notificationsLastCheckedAt';
+export const ML_OVERVIEW_PANELS = 'ml.overviewPanels';
export type PartitionFieldConfig =
| {
@@ -52,6 +53,12 @@ export interface AnomalyExplorerPanelsState {
mainPage: { size: number };
}
+export interface OverviewPanelsState {
+ nodes: boolean;
+ adJobs: boolean;
+ dfaJobs: boolean;
+}
+
export interface MlStorageRecord {
[key: string]: unknown;
[ML_ENTITY_FIELDS_CONFIG]: PartitionFieldsConfig;
@@ -60,6 +67,7 @@ export interface MlStorageRecord {
[ML_FROZEN_TIER_PREFERENCE]: FrozenTierPreference;
[ML_ANOMALY_EXPLORER_PANELS]: AnomalyExplorerPanelsState | undefined;
[ML_NOTIFICATIONS_LAST_CHECKED_AT]: number | undefined;
+ [ML_OVERVIEW_PANELS]: OverviewPanelsState;
}
export type MlStorage = Partial | null;
@@ -78,6 +86,8 @@ export type TMlStorageMapped = T extends typeof ML_ENTIT
? AnomalyExplorerPanelsState | undefined
: T extends typeof ML_NOTIFICATIONS_LAST_CHECKED_AT
? number | undefined
+ : T extends typeof ML_OVERVIEW_PANELS
+ ? OverviewPanelsState | undefined
: null;
export const ML_STORAGE_KEYS = [
@@ -87,4 +97,5 @@ export const ML_STORAGE_KEYS = [
ML_FROZEN_TIER_PREFERENCE,
ML_ANOMALY_EXPLORER_PANELS,
ML_NOTIFICATIONS_LAST_CHECKED_AT,
+ ML_OVERVIEW_PANELS,
] as const;
diff --git a/x-pack/plugins/ml/public/application/components/collapsible_panel/collapsible_panel.tsx b/x-pack/plugins/ml/public/application/components/collapsible_panel/collapsible_panel.tsx
new file mode 100644
index 0000000000000..53ed046423c2f
--- /dev/null
+++ b/x-pack/plugins/ml/public/application/components/collapsible_panel/collapsible_panel.tsx
@@ -0,0 +1,137 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import {
+ EuiBadge,
+ EuiButtonIcon,
+ EuiFlexGroup,
+ EuiFlexItem,
+ EuiSplitPanel,
+ EuiText,
+ EuiTitle,
+} from '@elastic/eui';
+import React, { type FC } from 'react';
+import { css } from '@emotion/react/dist/emotion-react.cjs';
+import { useCurrentThemeVars } from '../../contexts/kibana';
+
+export interface CollapsiblePanelProps {
+ isOpen: boolean;
+ onToggle: (isOpen: boolean) => void;
+
+ header: React.ReactElement;
+ headerItems?: React.ReactElement[];
+}
+
+export const CollapsiblePanel: FC = ({
+ isOpen,
+ onToggle,
+ children,
+ header,
+ headerItems,
+}) => {
+ const { euiTheme } = useCurrentThemeVars();
+
+ return (
+
+
+
+
+
+
+ {
+ onToggle(!isOpen);
+ }}
+ />
+
+
+
+ {header}
+
+
+
+
+ {headerItems ? (
+
+
+ {headerItems.map((item, i) => {
+ return (
+
+
+ {item}
+
+
+ );
+ })}
+
+
+ ) : null}
+
+
+ {isOpen ? (
+
+ {children}
+
+ ) : null}
+
+ );
+};
+
+export interface StatEntry {
+ label: string;
+ value: number;
+ 'data-test-subj'?: string;
+}
+
+export interface OverviewStatsBarProps {
+ inputStats: StatEntry[];
+ dataTestSub?: string;
+}
+
+export const OverviewStatsBar: FC = ({ inputStats, dataTestSub }) => {
+ return (
+
+ {inputStats.map(({ value, label, 'data-test-subj': dataTestSubjValue }) => {
+ return (
+
+
+
+ {label}:
+
+
+ {value}
+
+
+
+ );
+ })}
+
+ );
+};
diff --git a/x-pack/plugins/ml/public/application/components/collapsible_panel/index.ts b/x-pack/plugins/ml/public/application/components/collapsible_panel/index.ts
new file mode 100644
index 0000000000000..d45a251f69ca9
--- /dev/null
+++ b/x-pack/plugins/ml/public/application/components/collapsible_panel/index.ts
@@ -0,0 +1,8 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+export { CollapsiblePanel } from './collapsible_panel';
diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/empty_prompt/empty_prompt.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/empty_prompt/empty_prompt.tsx
index 23ce92dce1b91..14745812e3045 100644
--- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/empty_prompt/empty_prompt.tsx
+++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/empty_prompt/empty_prompt.tsx
@@ -6,16 +6,7 @@
*/
import React, { FC } from 'react';
-import {
- EuiButton,
- EuiCallOut,
- EuiEmptyPrompt,
- EuiFlexGroup,
- EuiFlexItem,
- EuiImage,
- EuiLink,
- EuiTitle,
-} from '@elastic/eui';
+import { EuiButton, EuiEmptyPrompt, EuiImage, EuiLink } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n-react';
import dfaImage from './data_frame_analytics_kibana.png';
@@ -26,10 +17,7 @@ import { usePermissionCheck } from '../../../../../capabilities/check_capabiliti
export const AnalyticsEmptyPrompt: FC = () => {
const {
- services: {
- docLinks,
- http: { basePath },
- },
+ services: { docLinks },
} = useMlKibana();
const [canCreateDataFrameAnalytics, canStartStopDataFrameAnalytics] = usePermissionCheck([
@@ -40,7 +28,6 @@ export const AnalyticsEmptyPrompt: FC = () => {
const disabled =
!mlNodesAvailable() || !canCreateDataFrameAnalytics || !canStartStopDataFrameAnalytics;
- const transformsLink = `${basePath.get()}/app/management/data/transform`;
const navigateToPath = useNavigateToPath();
const navigateToSourceSelection = async () => {
@@ -57,16 +44,15 @@ export const AnalyticsEmptyPrompt: FC = () => {
size="fullWidth"
src={dfaImage}
alt={i18n.translate('xpack.ml.dataFrame.analyticsList.emptyPromptTitle', {
- defaultMessage: 'Create your first data frame analytics job',
+ defaultMessage: 'Analyze your data with data frame analytics',
})}
/>
}
- color="subdued"
title={
}
@@ -78,39 +64,6 @@ export const AnalyticsEmptyPrompt: FC = () => {
defaultMessage="Train outlier detection, regression, or classification machine learning models using data frame analytics."
/>
-
-
-
- ),
- sourcedata: (
-
-
-
- ),
- }}
- />
- }
- iconType="iInCircle"
- />
>
}
actions={[
@@ -118,37 +71,19 @@ export const AnalyticsEmptyPrompt: FC = () => {
onClick={navigateToSourceSelection}
isDisabled={disabled}
color="primary"
- iconType="plusInCircle"
- fill
data-test-subj="mlAnalyticsCreateFirstButton"
>
{i18n.translate('xpack.ml.dataFrame.analyticsList.emptyPromptButtonText', {
- defaultMessage: 'Create job',
+ defaultMessage: 'Create data frame analytics job',
})}
,
+
+
+ ,
]}
- footer={
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- }
data-test-subj="mlNoDataFrameAnalyticsFound"
/>
);
diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/services/analytics_service/get_analytics.test.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/services/analytics_service/get_analytics.test.ts
index 613f9034e2ff4..949c8a47deb32 100644
--- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/services/analytics_service/get_analytics.test.ts
+++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/services/analytics_service/get_analytics.test.ts
@@ -67,7 +67,7 @@ describe('get_analytics', () => {
// act and assert
expect(getAnalyticsJobsStats(mockResponse)).toEqual({
total: {
- label: 'Total analytics jobs',
+ label: 'Total',
value: 2,
show: true,
},
diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/services/analytics_service/get_analytics.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/services/analytics_service/get_analytics.ts
index 170a90b1fcba0..9a3ea0c9bef90 100644
--- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/services/analytics_service/get_analytics.ts
+++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/services/analytics_service/get_analytics.ts
@@ -47,7 +47,7 @@ export function getInitialAnalyticsStats(): AnalyticStatsBarStats {
return {
total: {
label: i18n.translate('xpack.ml.overview.statsBar.totalAnalyticsLabel', {
- defaultMessage: 'Total analytics jobs',
+ defaultMessage: 'Total',
}),
value: 0,
show: true,
@@ -97,12 +97,18 @@ export function getAnalyticsJobsStats(
);
resultStats.failed.show = resultStats.failed.value > 0;
resultStats.total.value = analyticsStats.count;
+
+ if (resultStats.total.value === 0) {
+ resultStats.started.show = false;
+ resultStats.stopped.show = false;
+ }
+
return resultStats;
}
export const getAnalyticsFactory = (
setAnalytics: React.Dispatch>,
- setAnalyticsStats: React.Dispatch>,
+ setAnalyticsStats: (update: AnalyticStatsBarStats | undefined) => void,
setErrorMessage: React.Dispatch<
React.SetStateAction
>,
diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/anomaly_detection_empty_state/anomaly_detection_empty_state.tsx b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/anomaly_detection_empty_state/anomaly_detection_empty_state.tsx
index b8dee1a7e6f60..171320dd7b781 100644
--- a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/anomaly_detection_empty_state/anomaly_detection_empty_state.tsx
+++ b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/anomaly_detection_empty_state/anomaly_detection_empty_state.tsx
@@ -7,15 +7,7 @@
import React, { FC } from 'react';
import { FormattedMessage } from '@kbn/i18n-react';
-import {
- EuiButton,
- EuiEmptyPrompt,
- EuiFlexGroup,
- EuiFlexItem,
- EuiImage,
- EuiLink,
- EuiTitle,
-} from '@elastic/eui';
+import { EuiButton, EuiEmptyPrompt, EuiImage, EuiLink } from '@elastic/eui';
import adImage from './anomaly_detection_kibana.png';
import { ML_PAGES } from '../../../../../../common/constants/locator';
import { useMlKibana, useMlLocator, useNavigateToPath } from '../../../../contexts/kibana';
@@ -47,12 +39,11 @@ export const AnomalyDetectionEmptyState: FC = () => {
hasBorder={false}
hasShadow={false}
icon={}
- color="subdued"
title={
}
@@ -66,43 +57,25 @@ export const AnomalyDetectionEmptyState: FC = () => {
>
}
- actions={
+ actions={[
-
- }
- footer={
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- }
+ ,
+
+
+ ,
+ ]}
data-test-subj="mlAnomalyDetectionEmptyState"
/>
);
diff --git a/x-pack/plugins/ml/public/application/memory_usage/nodes_overview/nodes_list.tsx b/x-pack/plugins/ml/public/application/memory_usage/nodes_overview/nodes_list.tsx
index a615e40c9e3ea..a8e5848aaef1a 100644
--- a/x-pack/plugins/ml/public/application/memory_usage/nodes_overview/nodes_list.tsx
+++ b/x-pack/plugins/ml/public/application/memory_usage/nodes_overview/nodes_list.tsx
@@ -200,15 +200,20 @@ export const NodesList: FC = ({ compactView = false }) => {
return (
-
-
- {nodesStats && (
-
-
-
- )}
-
-
+ {nodesStats && !compactView ? (
+ <>
+
+
+ {nodesStats && (
+
+
+
+ )}
+
+
+ >
+ ) : null}
+
allowNeutralSort={false}
diff --git a/x-pack/plugins/ml/public/application/ml_nodes_check/check_ml_nodes.ts b/x-pack/plugins/ml/public/application/ml_nodes_check/check_ml_nodes.ts
index ea981a25d7ecb..c44c391a2fcfd 100644
--- a/x-pack/plugins/ml/public/application/ml_nodes_check/check_ml_nodes.ts
+++ b/x-pack/plugins/ml/public/application/ml_nodes_check/check_ml_nodes.ts
@@ -43,3 +43,7 @@ export function lazyMlNodesAvailable() {
export function permissionToViewMlNodeCount() {
return userHasPermissionToViewMlNodeCount;
}
+
+export function getMlNodesCount(): number {
+ return mlNodeCount;
+}
diff --git a/x-pack/plugins/ml/public/application/overview/components/analytics_panel/analytics_panel.tsx b/x-pack/plugins/ml/public/application/overview/components/analytics_panel/analytics_panel.tsx
index b53860d9a3be6..41e9732461bb3 100644
--- a/x-pack/plugins/ml/public/application/overview/components/analytics_panel/analytics_panel.tsx
+++ b/x-pack/plugins/ml/public/application/overview/components/analytics_panel/analytics_panel.tsx
@@ -5,28 +5,32 @@
* 2.0.
*/
-import React, { FC, useEffect, useState } from 'react';
-import {
- EuiButton,
- EuiCallOut,
- EuiFlexGroup,
- EuiFlexItem,
- EuiLoadingSpinner,
- EuiPanel,
- EuiSpacer,
- EuiText,
-} from '@elastic/eui';
+import React, { FC, useCallback, useEffect, useState } from 'react';
+import { EuiCallOut, EuiLink, EuiLoadingSpinner } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
+import { useStorage } from '@kbn/ml-local-storage';
+import { FormattedMessage } from '@kbn/i18n-react';
+import { type AnalyticStatsBarStats } from '../../../components/stats_bar';
+import {
+ OverviewStatsBar,
+ type StatEntry,
+} from '../../../components/collapsible_panel/collapsible_panel';
+import {
+ ML_OVERVIEW_PANELS,
+ MlStorageKey,
+ TMlStorageMapped,
+} from '../../../../../common/types/storage';
import { AnalyticsTable } from './table';
import { getAnalyticsFactory } from '../../../data_frame_analytics/pages/analytics_management/services/analytics_service';
import { DataFrameAnalyticsListRow } from '../../../data_frame_analytics/pages/analytics_management/components/analytics_list/common';
-import { AnalyticStatsBarStats, StatsBar } from '../../../components/stats_bar';
import { useMlLink } from '../../../contexts/kibana';
import { ML_PAGES } from '../../../../../common/constants/locator';
import { useRefresh } from '../../../routing/use_refresh';
import type { GetDataFrameAnalyticsStatsResponseError } from '../../../services/ml_api_service/data_frame_analytics';
import { AnalyticsEmptyPrompt } from '../../../data_frame_analytics/pages/analytics_management/components/empty_prompt';
+import { overviewPanelDefaultState } from '../../overview_page';
+import { CollapsiblePanel } from '../../../components/collapsible_panel';
interface Props {
setLazyJobCount: React.Dispatch>;
@@ -35,9 +39,7 @@ export const AnalyticsPanel: FC = ({ setLazyJobCount }) => {
const refresh = useRefresh();
const [analytics, setAnalytics] = useState([]);
- const [analyticsStats, setAnalyticsStats] = useState(
- undefined
- );
+ const [analyticsStats, setAnalyticsStats] = useState(undefined);
const [errorMessage, setErrorMessage] = useState();
const [isInitialized, setIsInitialized] = useState(false);
@@ -45,9 +47,24 @@ export const AnalyticsPanel: FC = ({ setLazyJobCount }) => {
page: ML_PAGES.DATA_FRAME_ANALYTICS_JOBS_MANAGE,
});
+ const [panelsState, setPanelsState] = useStorage<
+ MlStorageKey,
+ TMlStorageMapped
+ >(ML_OVERVIEW_PANELS, overviewPanelDefaultState);
+
+ const setAnalyticsStatsCustom = useCallback((stats: AnalyticStatsBarStats | undefined) => {
+ if (!stats) return;
+
+ const result = Object.entries(stats)
+ .filter(([k, v]) => v.show)
+ .map(([k, v]) => v);
+
+ setAnalyticsStats(result);
+ }, []);
+
const getAnalytics = getAnalyticsFactory(
setAnalytics,
- setAnalyticsStats,
+ setAnalyticsStatsCustom,
setErrorMessage,
setIsInitialized,
setLazyJobCount,
@@ -78,58 +95,40 @@ export const AnalyticsPanel: FC = ({ setLazyJobCount }) => {
const noDFAJobs = errorMessage === undefined && isInitialized === true && analytics.length === 0;
return (
- <>
- {noDFAJobs ? (
-
- ) : (
-
- {typeof errorMessage !== 'undefined' ? errorDisplay : null}
- {isInitialized === false && (
-
- )}
-
- {isInitialized === true && analytics.length > 0 && (
- <>
-
-
-
-
- {i18n.translate('xpack.ml.overview.analyticsList.PanelTitle', {
- defaultMessage: 'Analytics',
- })}
-
-
-
-
-
- {analyticsStats !== undefined ? (
-
-
-
- ) : null}
-
-
- {i18n.translate('xpack.ml.overview.analyticsList.manageJobsButtonText', {
- defaultMessage: 'Manage jobs',
- })}
-
-
-
-
-
-
-
- >
- )}
-
- )}
- >
+ {
+ setPanelsState({ ...panelsState, dfaJobs: update });
+ }}
+ header={
+
+ }
+ headerItems={[
+ ...(analyticsStats
+ ? [
+ ,
+ ]
+ : []),
+
+ {i18n.translate('xpack.ml.overview.analyticsList.manageJobsButtonText', {
+ defaultMessage: 'Manage jobs',
+ })}
+ ,
+ ]}
+ >
+ {noDFAJobs ? : null}
+
+ {typeof errorMessage !== 'undefined' ? errorDisplay : null}
+
+ {isInitialized === false && }
+
+ {isInitialized === true && analytics.length > 0 ? : null}
+
);
};
diff --git a/x-pack/plugins/ml/public/application/overview/components/anomaly_detection_panel/anomaly_detection_panel.tsx b/x-pack/plugins/ml/public/application/overview/components/anomaly_detection_panel/anomaly_detection_panel.tsx
index 1773531cb9aa2..30aa0f6d22dfb 100644
--- a/x-pack/plugins/ml/public/application/overview/components/anomaly_detection_panel/anomaly_detection_panel.tsx
+++ b/x-pack/plugins/ml/public/application/overview/components/anomaly_detection_panel/anomaly_detection_panel.tsx
@@ -6,10 +6,20 @@
*/
import React, { FC, Fragment, useEffect, useState } from 'react';
-import { EuiCallOut, EuiLoadingSpinner, EuiPanel } from '@elastic/eui';
+import { EuiCallOut, EuiLink, EuiLoadingSpinner } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
-import { zipObject } from 'lodash';
-import { useMlKibana } from '../../../contexts/kibana';
+import { zipObject, groupBy } from 'lodash';
+import { FormattedMessage } from '@kbn/i18n-react';
+import { useStorage } from '@kbn/ml-local-storage';
+import {
+ ML_OVERVIEW_PANELS,
+ MlStorageKey,
+ TMlStorageMapped,
+} from '../../../../../common/types/storage';
+import { ML_PAGES } from '../../../../../common/constants/locator';
+import { OverviewStatsBar } from '../../../components/collapsible_panel/collapsible_panel';
+import { CollapsiblePanel } from '../../../components/collapsible_panel';
+import { useMlKibana, useMlLink } from '../../../contexts/kibana';
import { AnomalyDetectionTable } from './table';
import { ml } from '../../../services/ml_api_service';
import { getGroupsFromJobs, getStatsBarData } from './utils';
@@ -19,8 +29,8 @@ import { useRefresh } from '../../../routing/use_refresh';
import { useToastNotificationService } from '../../../services/toast_notification_service';
import { AnomalyTimelineService } from '../../../services/anomaly_timeline_service';
import type { OverallSwimlaneData } from '../../../explorer/explorer_utils';
-import { JobStatsBarStats } from '../../../components/stats_bar';
import { AnomalyDetectionEmptyState } from '../../../jobs/jobs_list/components/anomaly_detection_empty_state';
+import { overviewPanelDefaultState } from '../../overview_page';
export type GroupsDictionary = Dictionary;
@@ -50,10 +60,21 @@ export const AnomalyDetectionPanel: FC = ({ anomalyTimelineService, setLa
const refresh = useRefresh();
+ const [panelsState, setPanelsState] = useStorage<
+ MlStorageKey,
+ TMlStorageMapped
+ >(ML_OVERVIEW_PANELS, overviewPanelDefaultState);
+
+ const manageJobsLink = useMlLink({
+ page: ML_PAGES.ANOMALY_DETECTION_JOBS_MANAGE,
+ });
+
const [isLoading, setIsLoading] = useState(false);
const [groups, setGroups] = useState({});
const [groupsCount, setGroupsCount] = useState(0);
- const [statsBarData, setStatsBarData] = useState();
+ const [statsBarData, setStatsBarData] = useState>();
+ const [restStatsBarData, setRestStatsBarData] =
+ useState>();
const [errorMessage, setErrorMessage] = useState();
const loadJobs = async () => {
@@ -71,9 +92,20 @@ export const AnomalyDetectionPanel: FC = ({ anomalyTimelineService, setLa
});
const { groups: jobsGroups, count } = getGroupsFromJobs(jobsSummaryList);
const stats = getStatsBarData(jobsSummaryList);
+
+ const statGroups = groupBy(
+ Object.entries(stats)
+ .filter(([k, v]) => v.show)
+ .map(([k, v]) => v),
+ 'group'
+ );
+
setIsLoading(false);
setErrorMessage(undefined);
- setStatsBarData(stats);
+
+ setStatsBarData(statGroups[0]);
+ setRestStatsBarData(statGroups[1]);
+
setGroupsCount(count);
setGroups(jobsGroups);
loadOverallSwimLanes(jobsGroups);
@@ -138,30 +170,52 @@ export const AnomalyDetectionPanel: FC = ({ anomalyTimelineService, setLa
);
- const panelClass = isLoading ? 'mlOverviewPanel__isLoading' : 'mlOverviewPanel';
-
const noAdJobs =
!errorMessage &&
isLoading === false &&
typeof errorMessage === 'undefined' &&
groupsCount === 0;
- if (noAdJobs) {
- return ;
- }
-
return (
-
+ {
+ setPanelsState({ ...panelsState, adJobs: update });
+ }}
+ header={
+
+ }
+ headerItems={[
+ ...(statsBarData
+ ? []
+ : []),
+ ...(restStatsBarData
+ ? [
+ ,
+ ]
+ : []),
+
+ {i18n.translate('xpack.ml.overview.anomalyDetection.manageJobsButtonText', {
+ defaultMessage: 'Manage jobs',
+ })}
+ ,
+ ]}
+ >
+ {noAdJobs ? : null}
+
{typeof errorMessage !== 'undefined' && errorDisplay}
- {isLoading && }
+
+ {isLoading ? : null}
{isLoading === false && typeof errorMessage === 'undefined' && groupsCount > 0 ? (
-
+
) : null}
-
+
);
};
diff --git a/x-pack/plugins/ml/public/application/overview/components/anomaly_detection_panel/table.tsx b/x-pack/plugins/ml/public/application/overview/components/anomaly_detection_panel/table.tsx
index 016261be7997e..de4a05c638ce2 100644
--- a/x-pack/plugins/ml/public/application/overview/components/anomaly_detection_panel/table.tsx
+++ b/x-pack/plugins/ml/public/application/overview/components/anomaly_detection_panel/table.tsx
@@ -9,13 +9,8 @@ import React, { FC, useState } from 'react';
import {
Direction,
EuiBasicTableColumn,
- EuiButton,
- EuiFlexGroup,
- EuiFlexItem,
EuiIcon,
EuiInMemoryTable,
- EuiSpacer,
- EuiText,
EuiToolTip,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
@@ -24,13 +19,10 @@ import { ChartsPluginStart } from '@kbn/charts-plugin/public';
import { formatHumanReadableDateTime } from '@kbn/ml-date-utils';
import { useGroupActions } from './actions';
import { Group, GroupsDictionary } from './anomaly_detection_panel';
-import { JobStatsBarStats, StatsBar } from '../../../components/stats_bar';
import { JobSelectorBadge } from '../../../components/job_selector/job_selector_badge';
import { toLocaleString } from '../../../util/string_utils';
import { SwimlaneContainer } from '../../../explorer/swimlane_container';
import { useTimeBuckets } from '../../../components/custom_hooks/use_time_buckets';
-import { ML_PAGES } from '../../../../../common/constants/locator';
-import { useMlLink } from '../../../contexts/kibana';
export enum AnomalyDetectionListColumns {
id = 'id',
@@ -44,11 +36,10 @@ export enum AnomalyDetectionListColumns {
interface Props {
items: GroupsDictionary;
- statsBarData: JobStatsBarStats;
chartsService: ChartsPluginStart;
}
-export const AnomalyDetectionTable: FC = ({ items, statsBarData, chartsService }) => {
+export const AnomalyDetectionTable: FC = ({ items, chartsService }) => {
const groupsList = Object.values(items);
const [pageIndex, setPageIndex] = useState(0);
const [pageSize, setPageSize] = useState(10);
@@ -58,10 +49,6 @@ export const AnomalyDetectionTable: FC = ({ items, statsBarData, chartsSe
const timeBuckets = useTimeBuckets();
- const manageJobsLink = useMlLink({
- page: ML_PAGES.ANOMALY_DETECTION_JOBS_MANAGE,
- });
-
const columns: Array> = [
{
field: AnomalyDetectionListColumns.id,
@@ -195,47 +182,19 @@ export const AnomalyDetectionTable: FC = ({ items, statsBarData, chartsSe
};
return (
- <>
-
-
-
-
- {i18n.translate('xpack.ml.overview.anomalyDetection.panelTitle', {
- defaultMessage: 'Anomaly Detection',
- })}
-
-
-
-
-
-
-
-
-
-
- {i18n.translate('xpack.ml.overview.anomalyDetection.manageJobsButtonText', {
- defaultMessage: 'Manage jobs',
- })}
-
-
-
-
-
-
-
- allowNeutralSort={false}
- className="mlAnomalyDetectionTable"
- columns={columns}
- hasActions={true}
- isExpandable={false}
- isSelectable={false}
- items={groupsList}
- itemId={AnomalyDetectionListColumns.id}
- onTableChange={onTableChange}
- pagination={pagination}
- sorting={sorting}
- data-test-subj="mlOverviewTableAnomalyDetection"
- />
- >
+
+ allowNeutralSort={false}
+ className="mlAnomalyDetectionTable"
+ columns={columns}
+ hasActions={true}
+ isExpandable={false}
+ isSelectable={false}
+ items={groupsList}
+ itemId={AnomalyDetectionListColumns.id}
+ onTableChange={onTableChange}
+ pagination={pagination}
+ sorting={sorting}
+ data-test-subj="mlOverviewTableAnomalyDetection"
+ />
);
};
diff --git a/x-pack/plugins/ml/public/application/overview/components/anomaly_detection_panel/utils.ts b/x-pack/plugins/ml/public/application/overview/components/anomaly_detection_panel/utils.ts
index fa2e83151b80f..a5a864e139eae 100644
--- a/x-pack/plugins/ml/public/application/overview/components/anomaly_detection_panel/utils.ts
+++ b/x-pack/plugins/ml/public/application/overview/components/anomaly_detection_panel/utils.ts
@@ -76,40 +76,45 @@ export function getGroupsFromJobs(jobs: MlSummaryJobs): {
export function getStatsBarData(jobsList: any) {
const jobStats = {
- activeNodes: {
- label: i18n.translate('xpack.ml.overviewJobsList.statsBar.activeMLNodesLabel', {
- defaultMessage: 'Active ML nodes',
- }),
- value: 0,
- show: true,
- },
total: {
label: i18n.translate('xpack.ml.overviewJobsList.statsBar.totalJobsLabel', {
- defaultMessage: 'Total jobs',
+ defaultMessage: 'Total',
}),
value: 0,
show: true,
+ group: 0,
},
open: {
label: i18n.translate('xpack.ml.overviewJobsList.statsBar.openJobsLabel', {
- defaultMessage: 'Open jobs',
+ defaultMessage: 'Open',
}),
value: 0,
show: true,
+ group: 0,
},
closed: {
label: i18n.translate('xpack.ml.overviewJobsList.statsBar.closedJobsLabel', {
- defaultMessage: 'Closed jobs',
+ defaultMessage: 'Closed',
}),
value: 0,
show: true,
+ group: 0,
},
failed: {
label: i18n.translate('xpack.ml.overviewJobsList.statsBar.failedJobsLabel', {
- defaultMessage: 'Failed jobs',
+ defaultMessage: 'Failed',
}),
value: 0,
show: false,
+ group: 0,
+ },
+ activeNodes: {
+ label: i18n.translate('xpack.ml.overviewJobsList.statsBar.activeMLNodesLabel', {
+ defaultMessage: 'Active ML nodes',
+ }),
+ value: 0,
+ show: true,
+ group: 1,
},
activeDatafeeds: {
label: i18n.translate('xpack.ml.jobsList.statsBar.activeDatafeedsLabel', {
@@ -117,6 +122,7 @@ export function getStatsBarData(jobsList: any) {
}),
value: 0,
show: true,
+ group: 1,
},
};
@@ -158,5 +164,13 @@ export function getStatsBarData(jobsList: any) {
jobStats.activeNodes.value = Object.keys(mlNodes).length;
+ if (jobStats.total.value === 0) {
+ for (const [statKey, val] of Object.entries(jobStats)) {
+ if (statKey !== 'total') {
+ val.show = false;
+ }
+ }
+ }
+
return jobStats;
}
diff --git a/x-pack/plugins/ml/public/application/overview/overview_page.tsx b/x-pack/plugins/ml/public/application/overview/overview_page.tsx
index 4f7244c37f298..6772125bb9532 100644
--- a/x-pack/plugins/ml/public/application/overview/overview_page.tsx
+++ b/x-pack/plugins/ml/public/application/overview/overview_page.tsx
@@ -6,9 +6,15 @@
*/
import React, { FC, useState } from 'react';
-import { EuiPanel, EuiSpacer } from '@elastic/eui';
+import { FormattedMessage } from '@kbn/i18n-react';
+import { EuiLink, EuiSpacer } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { mlTimefilterRefresh$, useTimefilter } from '@kbn/ml-date-picker';
+import { useStorage } from '@kbn/ml-local-storage';
+import { OverviewStatsBar } from '../components/collapsible_panel/collapsible_panel';
+import { ML_PAGES } from '../../../common/constants/locator';
+import { ML_OVERVIEW_PANELS, MlStorageKey, TMlStorageMapped } from '../../../common/types/storage';
+import { CollapsiblePanel } from '../components/collapsible_panel';
import { usePermissionCheck } from '../capabilities/check_capabilities';
import { mlNodesAvailable } from '../ml_nodes_check';
import { OverviewContent } from './components/content';
@@ -17,11 +23,18 @@ import { JobsAwaitingNodeWarning } from '../components/jobs_awaiting_node_warnin
import { SavedObjectsWarning } from '../components/saved_objects_warning';
import { UpgradeWarning } from '../components/upgrade';
import { HelpMenu } from '../components/help_menu';
-import { useMlKibana } from '../contexts/kibana';
+import { useMlKibana, useMlLink } from '../contexts/kibana';
import { NodesList } from '../memory_usage/nodes_overview';
import { MlPageHeader } from '../components/page_header';
import { PageTitle } from '../components/page_title';
import { useIsServerless } from '../contexts/kibana/use_is_serverless';
+import { getMlNodesCount } from '../ml_nodes_check/check_ml_nodes';
+
+export const overviewPanelDefaultState = Object.freeze({
+ nodes: true,
+ adJobs: true,
+ dfaJobs: true,
+});
export const OverviewPage: FC = () => {
const serverless = useIsServerless();
@@ -33,11 +46,20 @@ export const OverviewPage: FC = () => {
} = useMlKibana();
const helpLink = docLinks.links.ml.guide;
+ const viewNodesLink = useMlLink({
+ page: ML_PAGES.MEMORY_USAGE,
+ });
+
const timefilter = useTimefilter({ timeRangeSelector: true, autoRefreshSelector: true });
const [adLazyJobCount, setAdLazyJobCount] = useState(0);
const [dfaLazyJobCount, setDfaLazyJobCount] = useState(0);
+ const [panelsState, setPanelsState] = useStorage<
+ MlStorageKey,
+ TMlStorageMapped
+ >(ML_OVERVIEW_PANELS, overviewPanelDefaultState);
+
return (
@@ -63,9 +85,36 @@ export const OverviewPage: FC = () => {
{canViewMlNodes && serverless === false ? (
<>
-
+ {
+ setPanelsState({ ...panelsState, nodes: update });
+ }}
+ header={
+
+ }
+ headerItems={[
+ ,
+
+ {i18n.translate('xpack.ml.overview.nodesPanel.viewNodeLink', {
+ defaultMessage: 'View nodes',
+ })}
+ ,
+ ]}
+ >
-
+
>
) : null}
diff --git a/x-pack/plugins/ml/public/application/routing/use_resolver.tsx b/x-pack/plugins/ml/public/application/routing/use_resolver.tsx
index eb3586c15c05f..5c67ed9769426 100644
--- a/x-pack/plugins/ml/public/application/routing/use_resolver.tsx
+++ b/x-pack/plugins/ml/public/application/routing/use_resolver.tsx
@@ -35,7 +35,6 @@ export const useRouteResolver = (
): {
context: RouteResolverContext;
results: ResolverResults;
- component?: React.Component;
} => {
const requiredCapabilitiesRef = useRef(requiredCapabilities);
const customResolversRef = useRef(customResolvers);
diff --git a/x-pack/plugins/observability/kibana.jsonc b/x-pack/plugins/observability/kibana.jsonc
index a69ffbd0234f6..5c08133f15a95 100644
--- a/x-pack/plugins/observability/kibana.jsonc
+++ b/x-pack/plugins/observability/kibana.jsonc
@@ -25,12 +25,11 @@
"triggersActionsUi",
"security",
"share",
- "spaces",
"unifiedSearch",
"visualizations"
],
- "optionalPlugins": ["discover", "home", "licensing", "usageCollection", "cloud"],
- "requiredBundles": ["data", "kibanaReact", "kibanaUtils", "unifiedSearch", "cloudChat"],
+ "optionalPlugins": ["discover", "home", "licensing", "usageCollection", "cloud", "spaces"],
+ "requiredBundles": ["data", "kibanaReact", "kibanaUtils", "unifiedSearch", "cloudChat", "spaces"],
"extraPublicDirs": ["common"]
}
}
diff --git a/x-pack/plugins/observability/public/hooks/__storybook_mocks__/use_fetch_indices.ts b/x-pack/plugins/observability/public/hooks/__storybook_mocks__/use_fetch_indices.ts
index fdedecfef7243..a709dea0600f8 100644
--- a/x-pack/plugins/observability/public/hooks/__storybook_mocks__/use_fetch_indices.ts
+++ b/x-pack/plugins/observability/public/hooks/__storybook_mocks__/use_fetch_indices.ts
@@ -12,18 +12,13 @@ export const useFetchIndices = (): UseFetchIndicesResponse => {
isLoading: false,
isError: false,
isSuccess: true,
- indices: [
+ data: [
...Array(10)
.fill(0)
- .map((_, i) => ({
- name: `.index-${i}`,
- })),
+ .map((_, i) => `.index-${i}`),
...Array(10)
.fill(0)
- .map((_, i) => ({
- name: `.some-other-index-${i}`,
- })),
+ .map((_, i) => `.some-other-index-${i}`),
] as Index[],
- refetch: function () {} as UseFetchIndicesResponse['refetch'],
};
};
diff --git a/x-pack/plugins/observability/public/hooks/use_fetch_data_views.ts b/x-pack/plugins/observability/public/hooks/use_fetch_data_views.ts
index 976d6426d4ce3..1da32948e2e50 100644
--- a/x-pack/plugins/observability/public/hooks/use_fetch_data_views.ts
+++ b/x-pack/plugins/observability/public/hooks/use_fetch_data_views.ts
@@ -18,34 +18,31 @@ export interface UseFetchDataViewsResponse {
isLoading: boolean;
isSuccess: boolean;
isError: boolean;
- dataViews: DataView[] | undefined;
+ data: DataView[] | undefined;
refetch: (
options?: (RefetchOptions & RefetchQueryFilters) | undefined
) => Promise>;
}
-interface FetchDataViewParams {
+interface Params {
name?: string;
size?: number;
}
-export function useFetchDataViews({
- name = '',
- size = 10,
-}: FetchDataViewParams): UseFetchDataViewsResponse {
+export function useFetchDataViews({ name = '', size = 10 }: Params): UseFetchDataViewsResponse {
const { dataViews } = useKibana().services;
+ const search = name.endsWith('*') ? name : `${name}*`;
const { isLoading, isError, isSuccess, data, refetch } = useQuery({
- queryKey: ['fetchDataViews', name],
+ queryKey: ['fetchDataViews', search],
queryFn: async () => {
try {
- const response = await dataViews.find(`${name}*`, size);
- return response;
+ return await dataViews.find(search, size);
} catch (error) {
throw new Error(`Something went wrong. Error: ${error}`);
}
},
});
- return { isLoading, isError, isSuccess, dataViews: data, refetch };
+ return { isLoading, isError, isSuccess, data, refetch };
}
diff --git a/x-pack/plugins/observability/public/hooks/use_fetch_indices.ts b/x-pack/plugins/observability/public/hooks/use_fetch_indices.ts
index 88c7b1d5561c1..2d622a702a839 100644
--- a/x-pack/plugins/observability/public/hooks/use_fetch_indices.ts
+++ b/x-pack/plugins/observability/public/hooks/use_fetch_indices.ts
@@ -5,41 +5,46 @@
* 2.0.
*/
-import {
- QueryObserverResult,
- RefetchOptions,
- RefetchQueryFilters,
- useQuery,
-} from '@tanstack/react-query';
+import { useQuery } from '@tanstack/react-query';
import { useKibana } from '../utils/kibana_react';
+export type Index = string;
+
export interface UseFetchIndicesResponse {
isLoading: boolean;
isSuccess: boolean;
isError: boolean;
- indices: Index[] | undefined;
- refetch: (
- options?: (RefetchOptions & RefetchQueryFilters) | undefined
- ) => Promise>;
+ data: Index[] | undefined;
+}
+
+interface Params {
+ search?: string;
}
-export interface Index {
- name: string;
+
+interface ResolveIndexReponse {
+ indices: Array<{ name: string }>;
}
-export function useFetchIndices(): UseFetchIndicesResponse {
+export function useFetchIndices({ search }: Params): UseFetchIndicesResponse {
const { http } = useKibana().services;
- const { isLoading, isError, isSuccess, data, refetch } = useQuery({
- queryKey: ['fetchIndices'],
- queryFn: async ({ signal }) => {
+ const { isLoading, isError, isSuccess, data } = useQuery({
+ queryKey: ['fetchIndices', search],
+ queryFn: async () => {
+ const searchPattern = search?.endsWith('*') ? search : `${search}*`;
try {
- const response = await http.get(`/api/index_management/indices`, { signal });
- return response;
+ const response = await http.get(
+ `/internal/index-pattern-management/resolve_index/${searchPattern}`
+ );
+ return response.indices.map((index) => index.name);
} catch (error) {
throw new Error(`Something went wrong. Error: ${error}`);
}
},
+ retry: false,
+ enabled: Boolean(search),
+ refetchOnWindowFocus: false,
});
- return { isLoading, isError, isSuccess, indices: data, refetch };
+ return { isLoading, isError, isSuccess, data };
}
diff --git a/x-pack/plugins/observability/public/pages/slo_edit/components/custom_common/index_selection.tsx b/x-pack/plugins/observability/public/pages/slo_edit/components/custom_common/index_selection.tsx
index 691761bd8a94b..8d914991bf926 100644
--- a/x-pack/plugins/observability/public/pages/slo_edit/components/custom_common/index_selection.tsx
+++ b/x-pack/plugins/observability/public/pages/slo_edit/components/custom_common/index_selection.tsx
@@ -5,16 +5,15 @@
* 2.0.
*/
-import React, { useEffect, useMemo, useState } from 'react';
-import { Controller, useFormContext } from 'react-hook-form';
import { EuiComboBox, EuiComboBoxOptionOption, EuiFormRow } from '@elastic/eui';
+import { DataView } from '@kbn/data-views-plugin/public';
import { i18n } from '@kbn/i18n';
import { CreateSLOInput } from '@kbn/slo-schema';
-import { DataView } from '@kbn/data-views-plugin/public';
import { debounce } from 'lodash';
-
+import React, { useEffect, useMemo, useState } from 'react';
+import { Controller, useFormContext } from 'react-hook-form';
import { useFetchDataViews } from '../../../../hooks/use_fetch_data_views';
-import { useFetchIndices, Index } from '../../../../hooks/use_fetch_indices';
+import { useFetchIndices } from '../../../../hooks/use_fetch_indices';
interface Option {
label: string;
@@ -23,55 +22,62 @@ interface Option {
export function IndexSelection() {
const { control, getFieldState } = useFormContext();
- const { isLoading: isIndicesLoading, indices = [] } = useFetchIndices();
+
const [searchValue, setSearchValue] = useState('');
- const { isLoading: isDataViewsLoading, dataViews = [] } = useFetchDataViews({
+ const [dataViewOptions, setDataViewOptions] = useState