`;
-
-exports[`should render a Welcome screen with the telemetry disclaimer 1`] = `
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-`;
-
-exports[`should render a Welcome screen with the telemetry disclaimer when optIn is false 1`] = `
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-`;
-
-exports[`should render a Welcome screen with the telemetry disclaimer when optIn is true 1`] = `
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-`;
-
-exports[`should render a Welcome screen without the opt in/out link when user cannot change optIn status 1`] = `
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-`;
diff --git a/src/plugins/home/public/application/components/home.test.tsx b/src/plugins/home/public/application/components/home.test.tsx
index 9983afa3d4d611..f27a286488c2b1 100644
--- a/src/plugins/home/public/application/components/home.test.tsx
+++ b/src/plugins/home/public/application/components/home.test.tsx
@@ -12,7 +12,6 @@ import type { HomeProps } from './home';
import { Home } from './home';
import { FeatureCatalogueCategory } from '../../services';
-import { telemetryPluginMock } from '../../../../telemetry/public/mocks';
import { Welcome } from './welcome';
let mockHasIntegrationsPermission = true;
@@ -57,7 +56,6 @@ describe('home', () => {
setItem: jest.fn(),
},
urlBasePath: 'goober',
- telemetry: telemetryPluginMock.createStartContract(),
addBasePath(url) {
return `base_path/${url}`;
},
diff --git a/src/plugins/home/public/application/components/home.tsx b/src/plugins/home/public/application/components/home.tsx
index fdf04ea5806538..1fb0b3c790ab7e 100644
--- a/src/plugins/home/public/application/components/home.tsx
+++ b/src/plugins/home/public/application/components/home.tsx
@@ -10,7 +10,6 @@ import React, { Component } from 'react';
import { FormattedMessage } from '@kbn/i18n-react';
import { METRIC_TYPE } from '@kbn/analytics';
import { i18n } from '@kbn/i18n';
-import type { TelemetryPluginStart } from 'src/plugins/telemetry/public';
import { KibanaPageTemplate, OverviewPageFooter } from '../../../../kibana_react/public';
import { HOME_APP_BASE_PATH } from '../../../common/constants';
import type { FeatureCatalogueEntry, FeatureCatalogueSolution } from '../../services';
@@ -29,7 +28,6 @@ export interface HomeProps {
solutions: FeatureCatalogueSolution[];
localStorage: Storage;
urlBasePath: string;
- telemetry: TelemetryPluginStart;
hasUserDataView: () => Promise
;
}
@@ -175,13 +173,7 @@ export class Home extends Component {
}
private renderWelcome() {
- return (
- this.skipWelcome()}
- urlBasePath={this.props.urlBasePath}
- telemetry={this.props.telemetry}
- />
- );
+ return this.skipWelcome()} urlBasePath={this.props.urlBasePath} />;
}
public render() {
diff --git a/src/plugins/home/public/application/components/home_app.js b/src/plugins/home/public/application/components/home_app.js
index 62df479ecbfdf6..a634573aaf21ec 100644
--- a/src/plugins/home/public/application/components/home_app.js
+++ b/src/plugins/home/public/application/components/home_app.js
@@ -26,7 +26,6 @@ export function HomeApp({ directories, solutions }) {
getBasePath,
addBasePath,
environmentService,
- telemetry,
dataViewsService,
} = getServices();
const environment = environmentService.getEnvironment();
@@ -75,7 +74,6 @@ export function HomeApp({ directories, solutions }) {
solutions={solutions}
localStorage={localStorage}
urlBasePath={getBasePath()}
- telemetry={telemetry}
hasUserDataView={() => dataViewsService.hasUserDataView()}
/>
diff --git a/src/plugins/home/public/application/components/welcome.test.mocks.ts b/src/plugins/home/public/application/components/welcome.test.mocks.ts
new file mode 100644
index 00000000000000..fc9854bae31990
--- /dev/null
+++ b/src/plugins/home/public/application/components/welcome.test.mocks.ts
@@ -0,0 +1,17 @@
+/*
+ * 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 { welcomeServiceMock } from '../../services/welcome/welcome_service.mocks';
+
+jest.doMock('../kibana_services', () => ({
+ getServices: () => ({
+ addBasePath: (path: string) => `root${path}`,
+ trackUiMetric: () => {},
+ welcomeService: welcomeServiceMock.create(),
+ }),
+}));
diff --git a/src/plugins/home/public/application/components/welcome.test.tsx b/src/plugins/home/public/application/components/welcome.test.tsx
index b042a91e58c9d2..3400b4bfcdb75f 100644
--- a/src/plugins/home/public/application/components/welcome.test.tsx
+++ b/src/plugins/home/public/application/components/welcome.test.tsx
@@ -8,58 +8,11 @@
import React from 'react';
import { shallow } from 'enzyme';
+import './welcome.test.mocks';
import { Welcome } from './welcome';
-import { telemetryPluginMock } from '../../../../telemetry/public/mocks';
-jest.mock('../kibana_services', () => ({
- getServices: () => ({
- addBasePath: (path: string) => `root${path}`,
- trackUiMetric: () => {},
- }),
-}));
-
-test('should render a Welcome screen with the telemetry disclaimer', () => {
- const telemetry = telemetryPluginMock.createStartContract();
- const component = shallow( {}} telemetry={telemetry} />);
-
- expect(component).toMatchSnapshot();
-});
-
-test('should render a Welcome screen with the telemetry disclaimer when optIn is true', () => {
- const telemetry = telemetryPluginMock.createStartContract();
- telemetry.telemetryService.getIsOptedIn = jest.fn().mockReturnValue(true);
- const component = shallow( {}} telemetry={telemetry} />);
-
- expect(component).toMatchSnapshot();
-});
-
-test('should render a Welcome screen with the telemetry disclaimer when optIn is false', () => {
- const telemetry = telemetryPluginMock.createStartContract();
- telemetry.telemetryService.getIsOptedIn = jest.fn().mockReturnValue(false);
- const component = shallow( {}} telemetry={telemetry} />);
-
- expect(component).toMatchSnapshot();
-});
-
-test('should render a Welcome screen with no telemetry disclaimer', () => {
+test('should render a Welcome screen', () => {
const component = shallow( {}} />);
expect(component).toMatchSnapshot();
});
-
-test('should render a Welcome screen without the opt in/out link when user cannot change optIn status', () => {
- const telemetry = telemetryPluginMock.createStartContract();
- telemetry.telemetryService.getCanChangeOptInStatus = jest.fn().mockReturnValue(false);
- const component = shallow( {}} telemetry={telemetry} />);
-
- expect(component).toMatchSnapshot();
-});
-
-test('fires opt-in seen when mounted', () => {
- const telemetry = telemetryPluginMock.createStartContract();
- const mockSetOptedInNoticeSeen = jest.fn();
- telemetry.telemetryNotifications.setOptedInNoticeSeen = mockSetOptedInNoticeSeen;
- shallow( {}} telemetry={telemetry} />);
-
- expect(mockSetOptedInNoticeSeen).toHaveBeenCalled();
-});
diff --git a/src/plugins/home/public/application/components/welcome.tsx b/src/plugins/home/public/application/components/welcome.tsx
index 1a6251ebdca118..9efa6d356d9716 100644
--- a/src/plugins/home/public/application/components/welcome.tsx
+++ b/src/plugins/home/public/application/components/welcome.tsx
@@ -12,27 +12,17 @@
* in Elasticsearch.
*/
-import React, { Fragment } from 'react';
-import {
- EuiLink,
- EuiTextColor,
- EuiTitle,
- EuiSpacer,
- EuiFlexGroup,
- EuiFlexItem,
- EuiIcon,
- EuiPortal,
-} from '@elastic/eui';
+import React from 'react';
+import { EuiTitle, EuiSpacer, EuiFlexGroup, EuiFlexItem, EuiIcon, EuiPortal } from '@elastic/eui';
import { METRIC_TYPE } from '@kbn/analytics';
import { FormattedMessage } from '@kbn/i18n-react';
import { getServices } from '../kibana_services';
-import { TelemetryPluginStart } from '../../../../telemetry/public';
import { SampleDataCard } from './sample_data';
+
interface Props {
urlBasePath: string;
onSkip: () => void;
- telemetry?: TelemetryPluginStart;
}
/**
@@ -47,7 +37,7 @@ export class Welcome extends React.Component {
}
};
- private redirecToAddData() {
+ private redirectToAddData() {
this.services.application.navigateToApp('integrations', { path: '/browse' });
}
@@ -58,68 +48,23 @@ export class Welcome extends React.Component {
private onSampleDataConfirm = () => {
this.services.trackUiMetric(METRIC_TYPE.CLICK, 'sampleDataConfirm');
- this.redirecToAddData();
+ this.redirectToAddData();
};
componentDidMount() {
- const { telemetry } = this.props;
+ const { welcomeService } = this.services;
this.services.trackUiMetric(METRIC_TYPE.LOADED, 'welcomeScreenMount');
- if (telemetry?.telemetryService.userCanChangeSettings) {
- telemetry.telemetryNotifications.setOptedInNoticeSeen();
- }
document.addEventListener('keydown', this.hideOnEsc);
+ welcomeService.onRendered();
}
componentWillUnmount() {
document.removeEventListener('keydown', this.hideOnEsc);
}
- private renderTelemetryEnabledOrDisabledText = () => {
- const { telemetry } = this.props;
- if (
- !telemetry ||
- !telemetry.telemetryService.userCanChangeSettings ||
- !telemetry.telemetryService.getCanChangeOptInStatus()
- ) {
- return null;
- }
-
- const isOptedIn = telemetry.telemetryService.getIsOptedIn();
- if (isOptedIn) {
- return (
-
-
-
-
-
-
- );
- } else {
- return (
-
-
-
-
-
-
- );
- }
- };
-
render() {
- const { urlBasePath, telemetry } = this.props;
+ const { urlBasePath } = this.props;
+ const { welcomeService } = this.services;
return (
@@ -146,28 +91,7 @@ export class Welcome extends React.Component
{
onDecline={this.onSampleDataDecline}
/>
- {!!telemetry && (
-
-
-
-
-
-
- {this.renderTelemetryEnabledOrDisabledText()}
-
-
-
- )}
+ {welcomeService.renderTelemetryNotice()}
diff --git a/src/plugins/home/public/application/kibana_services.ts b/src/plugins/home/public/application/kibana_services.ts
index fdd325df96ac57..3ccfd9413a88ad 100644
--- a/src/plugins/home/public/application/kibana_services.ts
+++ b/src/plugins/home/public/application/kibana_services.ts
@@ -17,7 +17,6 @@ import {
ApplicationStart,
} from 'kibana/public';
import { UiCounterMetricType } from '@kbn/analytics';
-import { TelemetryPluginStart } from '../../../telemetry/public';
import { UrlForwardingStart } from '../../../url_forwarding/public';
import { DataViewsContract } from '../../../data_views/public';
import { TutorialService } from '../services/tutorials';
@@ -26,6 +25,7 @@ import { FeatureCatalogueRegistry } from '../services/feature_catalogue';
import { EnvironmentService } from '../services/environment';
import { ConfigSchema } from '../../config';
import { SharePluginSetup } from '../../../share/public';
+import type { WelcomeService } from '../services/welcome';
export interface HomeKibanaServices {
dataViewsService: DataViewsContract;
@@ -46,9 +46,9 @@ export interface HomeKibanaServices {
docLinks: DocLinksStart;
addBasePath: (url: string) => string;
environmentService: EnvironmentService;
- telemetry?: TelemetryPluginStart;
tutorialService: TutorialService;
addDataService: AddDataService;
+ welcomeService: WelcomeService;
}
let services: HomeKibanaServices | null = null;
diff --git a/src/plugins/home/public/index.ts b/src/plugins/home/public/index.ts
index 009382eee0009a..3450f4f9d2caf9 100644
--- a/src/plugins/home/public/index.ts
+++ b/src/plugins/home/public/index.ts
@@ -27,6 +27,8 @@ export type {
TutorialVariables,
TutorialDirectoryHeaderLinkComponent,
TutorialModuleNoticeComponent,
+ WelcomeRenderTelemetryNotice,
+ WelcomeServiceSetup,
} from './services';
export { INSTRUCTION_VARIANT, getDisplayText } from '../common/instruction_variant';
diff --git a/src/plugins/home/public/mocks.ts b/src/plugins/home/public/mocks.ts
index 10c186ee3f4e30..42e489dea9d2a3 100644
--- a/src/plugins/home/public/mocks.ts
+++ b/src/plugins/home/public/mocks.ts
@@ -8,16 +8,17 @@
import { featureCatalogueRegistryMock } from './services/feature_catalogue/feature_catalogue_registry.mock';
import { environmentServiceMock } from './services/environment/environment.mock';
-import { configSchema } from '../config';
import { tutorialServiceMock } from './services/tutorials/tutorial_service.mock';
import { addDataServiceMock } from './services/add_data/add_data_service.mock';
+import { HomePublicPluginSetup } from './plugin';
+import { welcomeServiceMock } from './services/welcome/welcome_service.mocks';
-const createSetupContract = () => ({
+const createSetupContract = (): jest.Mocked => ({
featureCatalogue: featureCatalogueRegistryMock.createSetup(),
environment: environmentServiceMock.createSetup(),
tutorials: tutorialServiceMock.createSetup(),
addData: addDataServiceMock.createSetup(),
- config: configSchema.validate({}),
+ welcomeScreen: welcomeServiceMock.createSetup(),
});
export const homePluginMock = {
diff --git a/src/plugins/home/public/plugin.test.mocks.ts b/src/plugins/home/public/plugin.test.mocks.ts
index c3e3c50a2fe0f3..22d314cbd6d068 100644
--- a/src/plugins/home/public/plugin.test.mocks.ts
+++ b/src/plugins/home/public/plugin.test.mocks.ts
@@ -10,14 +10,17 @@ import { featureCatalogueRegistryMock } from './services/feature_catalogue/featu
import { environmentServiceMock } from './services/environment/environment.mock';
import { tutorialServiceMock } from './services/tutorials/tutorial_service.mock';
import { addDataServiceMock } from './services/add_data/add_data_service.mock';
+import { welcomeServiceMock } from './services/welcome/welcome_service.mocks';
export const registryMock = featureCatalogueRegistryMock.create();
export const environmentMock = environmentServiceMock.create();
export const tutorialMock = tutorialServiceMock.create();
export const addDataMock = addDataServiceMock.create();
+export const welcomeMock = welcomeServiceMock.create();
jest.doMock('./services', () => ({
FeatureCatalogueRegistry: jest.fn(() => registryMock),
EnvironmentService: jest.fn(() => environmentMock),
TutorialService: jest.fn(() => tutorialMock),
AddDataService: jest.fn(() => addDataMock),
+ WelcomeService: jest.fn(() => welcomeMock),
}));
diff --git a/src/plugins/home/public/plugin.test.ts b/src/plugins/home/public/plugin.test.ts
index 990f0dce54a05f..57a1f5ec112aaf 100644
--- a/src/plugins/home/public/plugin.test.ts
+++ b/src/plugins/home/public/plugin.test.ts
@@ -79,5 +79,18 @@ describe('HomePublicPlugin', () => {
expect(setup).toHaveProperty('tutorials');
expect(setup.tutorials).toHaveProperty('setVariable');
});
+
+ test('wires up and returns welcome service', async () => {
+ const setup = await new HomePublicPlugin(mockInitializerContext).setup(
+ coreMock.createSetup() as any,
+ {
+ share: mockShare,
+ urlForwarding: urlForwardingPluginMock.createSetupContract(),
+ }
+ );
+ expect(setup).toHaveProperty('welcomeScreen');
+ expect(setup.welcomeScreen).toHaveProperty('registerOnRendered');
+ expect(setup.welcomeScreen).toHaveProperty('registerTelemetryNoticeRenderer');
+ });
});
});
diff --git a/src/plugins/home/public/plugin.ts b/src/plugins/home/public/plugin.ts
index 1ece73e71f393f..af43e56a1d75d3 100644
--- a/src/plugins/home/public/plugin.ts
+++ b/src/plugins/home/public/plugin.ts
@@ -25,11 +25,12 @@ import {
TutorialServiceSetup,
AddDataService,
AddDataServiceSetup,
+ WelcomeService,
+ WelcomeServiceSetup,
} from './services';
import { ConfigSchema } from '../config';
import { setServices } from './application/kibana_services';
import { DataViewsPublicPluginStart } from '../../data_views/public';
-import { TelemetryPluginStart } from '../../telemetry/public';
import { UsageCollectionSetup } from '../../usage_collection/public';
import { UrlForwardingSetup, UrlForwardingStart } from '../../url_forwarding/public';
import { AppNavLinkStatus } from '../../../core/public';
@@ -38,7 +39,6 @@ import { SharePluginSetup } from '../../share/public';
export interface HomePluginStartDependencies {
dataViews: DataViewsPublicPluginStart;
- telemetry?: TelemetryPluginStart;
urlForwarding: UrlForwardingStart;
}
@@ -61,6 +61,7 @@ export class HomePublicPlugin
private readonly environmentService = new EnvironmentService();
private readonly tutorialService = new TutorialService();
private readonly addDataService = new AddDataService();
+ private readonly welcomeService = new WelcomeService();
constructor(private readonly initializerContext: PluginInitializerContext) {}
@@ -76,7 +77,7 @@ export class HomePublicPlugin
const trackUiMetric = usageCollection
? usageCollection.reportUiCounter.bind(usageCollection, 'Kibana_home')
: () => {};
- const [coreStart, { telemetry, dataViews, urlForwarding: urlForwardingStart }] =
+ const [coreStart, { dataViews, urlForwarding: urlForwardingStart }] =
await core.getStartServices();
setServices({
share,
@@ -89,7 +90,6 @@ export class HomePublicPlugin
savedObjectsClient: coreStart.savedObjects.client,
chrome: coreStart.chrome,
application: coreStart.application,
- telemetry,
uiSettings: core.uiSettings,
addBasePath: core.http.basePath.prepend,
getBasePath: core.http.basePath.get,
@@ -100,6 +100,7 @@ export class HomePublicPlugin
tutorialService: this.tutorialService,
addDataService: this.addDataService,
featureCatalogue: this.featuresCatalogueRegistry,
+ welcomeService: this.welcomeService,
});
coreStart.chrome.docTitle.change(
i18n.translate('home.pageTitle', { defaultMessage: 'Home' })
@@ -132,6 +133,7 @@ export class HomePublicPlugin
environment: { ...this.environmentService.setup() },
tutorials: { ...this.tutorialService.setup() },
addData: { ...this.addDataService.setup() },
+ welcomeScreen: { ...this.welcomeService.setup() },
};
}
@@ -159,12 +161,12 @@ export interface HomePublicPluginSetup {
tutorials: TutorialServiceSetup;
addData: AddDataServiceSetup;
featureCatalogue: FeatureCatalogueSetup;
+ welcomeScreen: WelcomeServiceSetup;
/**
* The environment service is only available for a transition period and will
* be replaced by display specific extension points.
* @deprecated
*/
-
environment: EnvironmentSetup;
}
diff --git a/src/plugins/home/public/services/index.ts b/src/plugins/home/public/services/index.ts
index 2ee68a9eef0c29..41bc9ee258cebb 100644
--- a/src/plugins/home/public/services/index.ts
+++ b/src/plugins/home/public/services/index.ts
@@ -28,3 +28,6 @@ export type {
export { AddDataService } from './add_data';
export type { AddDataServiceSetup, AddDataTab } from './add_data';
+
+export { WelcomeService } from './welcome';
+export type { WelcomeServiceSetup, WelcomeRenderTelemetryNotice } from './welcome';
diff --git a/src/plugins/home/public/services/welcome/index.ts b/src/plugins/home/public/services/welcome/index.ts
new file mode 100644
index 00000000000000..371c6044c5dc5c
--- /dev/null
+++ b/src/plugins/home/public/services/welcome/index.ts
@@ -0,0 +1,10 @@
+/*
+ * 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 type { WelcomeServiceSetup, WelcomeRenderTelemetryNotice } from './welcome_service';
+export { WelcomeService } from './welcome_service';
diff --git a/src/plugins/home/public/services/welcome/welcome_service.mocks.ts b/src/plugins/home/public/services/welcome/welcome_service.mocks.ts
new file mode 100644
index 00000000000000..921cb990663276
--- /dev/null
+++ b/src/plugins/home/public/services/welcome/welcome_service.mocks.ts
@@ -0,0 +1,36 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+import { PublicMethodsOf } from '@kbn/utility-types';
+import { WelcomeService, WelcomeServiceSetup } from './welcome_service';
+
+const createSetupMock = (): jest.Mocked => {
+ const welcomeService = new WelcomeService();
+ const welcomeServiceSetup = welcomeService.setup();
+ return {
+ registerTelemetryNoticeRenderer: jest
+ .fn()
+ .mockImplementation(welcomeServiceSetup.registerTelemetryNoticeRenderer),
+ registerOnRendered: jest.fn().mockImplementation(welcomeServiceSetup.registerOnRendered),
+ };
+};
+
+const createMock = (): jest.Mocked> => {
+ const welcomeService = new WelcomeService();
+
+ return {
+ setup: jest.fn().mockImplementation(welcomeService.setup),
+ onRendered: jest.fn().mockImplementation(welcomeService.onRendered),
+ renderTelemetryNotice: jest.fn().mockImplementation(welcomeService.renderTelemetryNotice),
+ };
+};
+
+export const welcomeServiceMock = {
+ createSetup: createSetupMock,
+ create: createMock,
+};
diff --git a/src/plugins/home/public/services/welcome/welcome_service.test.ts b/src/plugins/home/public/services/welcome/welcome_service.test.ts
new file mode 100644
index 00000000000000..df2f95718c78b5
--- /dev/null
+++ b/src/plugins/home/public/services/welcome/welcome_service.test.ts
@@ -0,0 +1,86 @@
+/*
+ * 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 { WelcomeService, WelcomeServiceSetup } from './welcome_service';
+
+describe('WelcomeService', () => {
+ let welcomeService: WelcomeService;
+ let welcomeServiceSetup: WelcomeServiceSetup;
+
+ beforeEach(() => {
+ welcomeService = new WelcomeService();
+ welcomeServiceSetup = welcomeService.setup();
+ });
+ describe('onRendered', () => {
+ test('it should register an onRendered listener', () => {
+ const onRendered = jest.fn();
+ welcomeServiceSetup.registerOnRendered(onRendered);
+
+ welcomeService.onRendered();
+ expect(onRendered).toHaveBeenCalledTimes(1);
+ });
+
+ test('it should handle onRendered errors', () => {
+ const onRendered = jest.fn().mockImplementation(() => {
+ throw new Error('Something went terribly wrong');
+ });
+ welcomeServiceSetup.registerOnRendered(onRendered);
+
+ expect(() => welcomeService.onRendered()).not.toThrow();
+ expect(onRendered).toHaveBeenCalledTimes(1);
+ });
+
+ test('it should allow registering multiple onRendered listeners', () => {
+ const onRendered = jest.fn();
+ const onRendered2 = jest.fn();
+ welcomeServiceSetup.registerOnRendered(onRendered);
+ welcomeServiceSetup.registerOnRendered(onRendered2);
+
+ welcomeService.onRendered();
+ expect(onRendered).toHaveBeenCalledTimes(1);
+ expect(onRendered2).toHaveBeenCalledTimes(1);
+ });
+
+ test('if the same handler is registered twice, it is called twice', () => {
+ const onRendered = jest.fn();
+ welcomeServiceSetup.registerOnRendered(onRendered);
+ welcomeServiceSetup.registerOnRendered(onRendered);
+
+ welcomeService.onRendered();
+ expect(onRendered).toHaveBeenCalledTimes(2);
+ });
+ });
+ describe('renderTelemetryNotice', () => {
+ test('it should register a renderer', () => {
+ const renderer = jest.fn().mockReturnValue('rendered text');
+ welcomeServiceSetup.registerTelemetryNoticeRenderer(renderer);
+
+ expect(welcomeService.renderTelemetryNotice()).toEqual('rendered text');
+ });
+
+ test('it should fail to register a 2nd renderer and still use the first registered renderer', () => {
+ const renderer = jest.fn().mockReturnValue('rendered text');
+ const renderer2 = jest.fn().mockReturnValue('other text');
+ welcomeServiceSetup.registerTelemetryNoticeRenderer(renderer);
+ expect(() => welcomeServiceSetup.registerTelemetryNoticeRenderer(renderer2)).toThrowError(
+ 'Only one renderTelemetryNotice handler can be registered'
+ );
+
+ expect(welcomeService.renderTelemetryNotice()).toEqual('rendered text');
+ });
+
+ test('it should handle errors in the renderer', () => {
+ const renderer = jest.fn().mockImplementation(() => {
+ throw new Error('Something went terribly wrong');
+ });
+ welcomeServiceSetup.registerTelemetryNoticeRenderer(renderer);
+
+ expect(welcomeService.renderTelemetryNotice()).toEqual(null);
+ });
+ });
+});
diff --git a/src/plugins/home/public/services/welcome/welcome_service.ts b/src/plugins/home/public/services/welcome/welcome_service.ts
new file mode 100644
index 00000000000000..46cf139adb36a3
--- /dev/null
+++ b/src/plugins/home/public/services/welcome/welcome_service.ts
@@ -0,0 +1,63 @@
+/*
+ * 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 type WelcomeRenderTelemetryNotice = () => null | JSX.Element;
+
+export interface WelcomeServiceSetup {
+ /**
+ * Register listeners to be called when the Welcome component is mounted.
+ * It can be called multiple times to register multiple listeners.
+ */
+ registerOnRendered: (onRendered: () => void) => void;
+ /**
+ * Register a renderer of the telemetry notice to be shown below the Welcome page.
+ */
+ registerTelemetryNoticeRenderer: (renderTelemetryNotice: WelcomeRenderTelemetryNotice) => void;
+}
+
+export class WelcomeService {
+ private readonly onRenderedHandlers: Array<() => void> = [];
+ private renderTelemetryNoticeHandler?: WelcomeRenderTelemetryNotice;
+
+ public setup = (): WelcomeServiceSetup => {
+ return {
+ registerOnRendered: (onRendered) => {
+ this.onRenderedHandlers.push(onRendered);
+ },
+ registerTelemetryNoticeRenderer: (renderTelemetryNotice) => {
+ if (this.renderTelemetryNoticeHandler) {
+ throw new Error('Only one renderTelemetryNotice handler can be registered');
+ }
+ this.renderTelemetryNoticeHandler = renderTelemetryNotice;
+ },
+ };
+ };
+
+ public onRendered = () => {
+ this.onRenderedHandlers.forEach((onRendered) => {
+ try {
+ onRendered();
+ } catch (err) {
+ // eslint-disable-next-line no-console
+ console.error(err);
+ }
+ });
+ };
+
+ public renderTelemetryNotice = () => {
+ if (this.renderTelemetryNoticeHandler) {
+ try {
+ return this.renderTelemetryNoticeHandler();
+ } catch (err) {
+ // eslint-disable-next-line no-console
+ console.error(err);
+ }
+ }
+ return null;
+ };
+}
diff --git a/src/plugins/home/tsconfig.json b/src/plugins/home/tsconfig.json
index fa98b98ff8e1c3..17d0fc7bd91acf 100644
--- a/src/plugins/home/tsconfig.json
+++ b/src/plugins/home/tsconfig.json
@@ -15,7 +15,6 @@
{ "path": "../kibana_react/tsconfig.json" },
{ "path": "../share/tsconfig.json" },
{ "path": "../url_forwarding/tsconfig.json" },
- { "path": "../usage_collection/tsconfig.json" },
- { "path": "../telemetry/tsconfig.json" }
+ { "path": "../usage_collection/tsconfig.json" }
]
}
diff --git a/src/plugins/presentation_util/public/services/create/dependency_manager.test.ts b/src/plugins/presentation_util/public/services/create/dependency_manager.test.ts
index 29702c33568655..8e67dee3f8b6b4 100644
--- a/src/plugins/presentation_util/public/services/create/dependency_manager.test.ts
+++ b/src/plugins/presentation_util/public/services/create/dependency_manager.test.ts
@@ -24,6 +24,16 @@ describe('DependencyManager', () => {
expect(DependencyManager.orderDependencies(graph)).toEqual(sortedTopology);
});
+ it('should include final vertex if it has dependencies', () => {
+ const graph = {
+ A: [],
+ B: [],
+ C: ['A', 'B'],
+ };
+ const sortedTopology = ['A', 'B', 'C'];
+ expect(DependencyManager.orderDependencies(graph)).toEqual(sortedTopology);
+ });
+
it('orderDependencies. Should return base topology if no depended vertices', () => {
const graph = {
N: [],
@@ -34,22 +44,34 @@ describe('DependencyManager', () => {
expect(DependencyManager.orderDependencies(graph)).toEqual(sortedTopology);
});
- it('orderDependencies. Should detect circular dependencies and throw error with path', () => {
- const graph = {
- N: ['R'],
- R: ['A'],
- A: ['B'],
- B: ['C'],
- C: ['D'],
- D: ['E'],
- E: ['F'],
- F: ['L'],
- L: ['G'],
- G: ['N'],
- };
- const circularPath = ['N', 'R', 'A', 'B', 'C', 'D', 'E', 'F', 'L', 'G', 'N'].join(' -> ');
- const errorMessage = `Circular dependency detected while setting up services: ${circularPath}`;
+ describe('circular dependencies', () => {
+ it('should detect circular dependencies and throw error with path', () => {
+ const graph = {
+ N: ['R'],
+ R: ['A'],
+ A: ['B'],
+ B: ['C'],
+ C: ['D'],
+ D: ['E'],
+ E: ['F'],
+ F: ['L'],
+ L: ['G'],
+ G: ['N'],
+ };
+ const circularPath = ['G', 'L', 'F', 'E', 'D', 'C', 'B', 'A', 'R', 'N'].join(' -> ');
+ const errorMessage = `Circular dependency detected while setting up services: ${circularPath}`;
+
+ expect(() => DependencyManager.orderDependencies(graph)).toThrowError(errorMessage);
+ });
+
+ it('should detect circular dependency if circular reference is the first dependency for a vertex', () => {
+ const graph = {
+ A: ['B'],
+ B: ['A', 'C'],
+ C: [],
+ };
- expect(() => DependencyManager.orderDependencies(graph)).toThrowError(errorMessage);
+ expect(() => DependencyManager.orderDependencies(graph)).toThrow();
+ });
});
});
diff --git a/src/plugins/presentation_util/public/services/create/dependency_manager.ts b/src/plugins/presentation_util/public/services/create/dependency_manager.ts
index de30b180607fe4..3925f3e9d9c4fe 100644
--- a/src/plugins/presentation_util/public/services/create/dependency_manager.ts
+++ b/src/plugins/presentation_util/public/services/create/dependency_manager.ts
@@ -41,7 +41,14 @@ export class DependencyManager {
return cycleInfo;
}
- return DependencyManager.sortVerticesFrom(srcVertex, graph, sortedVertices, {}, {});
+ return DependencyManager.sortVerticesFrom(
+ srcVertex,
+ graph,
+ sortedVertices,
+ {},
+ {},
+ cycleInfo
+ );
}, DependencyManager.createCycleInfo());
}
@@ -58,24 +65,30 @@ export class DependencyManager {
graph: Graph,
sortedVertices: Set,
visited: BreadCrumbs = {},
- inpath: BreadCrumbs = {}
+ inpath: BreadCrumbs = {},
+ cycle: CycleDetectionResult
): CycleDetectionResult {
visited[srcVertex] = true;
inpath[srcVertex] = true;
- const cycleInfo = graph[srcVertex]?.reduce | undefined>(
- (info, vertex) => {
- if (inpath[vertex]) {
- const path = (Object.keys(inpath) as T[]).filter(
- (visitedVertex) => inpath[visitedVertex]
- );
- return DependencyManager.createCycleInfo([...path, vertex], true);
- } else if (!visited[vertex]) {
- return DependencyManager.sortVerticesFrom(vertex, graph, sortedVertices, visited, inpath);
- }
- return info;
- },
- undefined
- );
+
+ const vertexEdges =
+ graph[srcVertex] === undefined || graph[srcVertex] === null ? [] : graph[srcVertex];
+
+ cycle = vertexEdges!.reduce>((info, vertex) => {
+ if (inpath[vertex]) {
+ return { ...info, hasCycle: true };
+ } else if (!visited[vertex]) {
+ return DependencyManager.sortVerticesFrom(
+ vertex,
+ graph,
+ sortedVertices,
+ visited,
+ inpath,
+ info
+ );
+ }
+ return info;
+ }, cycle);
inpath[srcVertex] = false;
@@ -83,7 +96,10 @@ export class DependencyManager {
sortedVertices.add(srcVertex);
}
- return cycleInfo ?? DependencyManager.createCycleInfo([...sortedVertices]);
+ return {
+ ...cycle,
+ path: [...sortedVertices],
+ };
}
private static createCycleInfo(
diff --git a/src/plugins/telemetry/kibana.json b/src/plugins/telemetry/kibana.json
index 09cc6accb68f4b..a6796e42f92282 100644
--- a/src/plugins/telemetry/kibana.json
+++ b/src/plugins/telemetry/kibana.json
@@ -8,6 +8,7 @@
"server": true,
"ui": true,
"requiredPlugins": ["telemetryCollectionManager", "usageCollection", "screenshotMode"],
+ "optionalPlugins": ["home", "security"],
"extraPublicDirs": ["common/constants"],
"requiredBundles": ["kibanaUtils", "kibanaReact"]
}
diff --git a/src/plugins/telemetry/public/plugin.ts b/src/plugins/telemetry/public/plugin.ts
index 3072ff67703d78..794183cb8a8f5d 100644
--- a/src/plugins/telemetry/public/plugin.ts
+++ b/src/plugins/telemetry/public/plugin.ts
@@ -31,6 +31,8 @@ import {
} from '../common/telemetry_config';
import { getNotifyUserAboutOptInDefault } from '../common/telemetry_config/get_telemetry_notify_user_about_optin_default';
import { PRIVACY_STATEMENT_URL } from '../common/constants';
+import { HomePublicPluginSetup } from '../../home/public';
+import { renderWelcomeTelemetryNotice } from './render_welcome_telemetry_notice';
/**
* Publicly exposed APIs from the Telemetry Service
@@ -82,6 +84,7 @@ export interface TelemetryPluginStart {
interface TelemetryPluginSetupDependencies {
screenshotMode: ScreenshotModePluginSetup;
+ home?: HomePublicPluginSetup;
}
/**
@@ -121,7 +124,7 @@ export class TelemetryPlugin implements Plugin {
+ if (this.telemetryService?.userCanChangeSettings) {
+ this.telemetryNotifications?.setOptedInNoticeSeen();
+ }
+ });
+
+ home.welcomeScreen.registerTelemetryNoticeRenderer(() =>
+ renderWelcomeTelemetryNotice(this.telemetryService!, http.basePath.prepend)
+ );
+ }
+
return {
telemetryService: this.getTelemetryServicePublicApis(),
};
diff --git a/src/plugins/telemetry/public/render_welcome_telemetry_notice.test.ts b/src/plugins/telemetry/public/render_welcome_telemetry_notice.test.ts
new file mode 100644
index 00000000000000..6da76db915656d
--- /dev/null
+++ b/src/plugins/telemetry/public/render_welcome_telemetry_notice.test.ts
@@ -0,0 +1,32 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 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 { mountWithIntl } from '@kbn/test-jest-helpers';
+import { renderWelcomeTelemetryNotice } from './render_welcome_telemetry_notice';
+import { mockTelemetryService } from './mocks';
+
+describe('renderWelcomeTelemetryNotice', () => {
+ test('it should show the opt-out message', () => {
+ const telemetryService = mockTelemetryService();
+ const component = mountWithIntl(renderWelcomeTelemetryNotice(telemetryService, (url) => url));
+ expect(component.exists('[id="telemetry.dataManagementDisableCollectionLink"]')).toBe(true);
+ });
+
+ test('it should show the opt-in message', () => {
+ const telemetryService = mockTelemetryService({ config: { optIn: false } });
+ const component = mountWithIntl(renderWelcomeTelemetryNotice(telemetryService, (url) => url));
+ expect(component.exists('[id="telemetry.dataManagementEnableCollectionLink"]')).toBe(true);
+ });
+
+ test('it should not show opt-in/out options if user cannot change the settings', () => {
+ const telemetryService = mockTelemetryService({ config: { allowChangingOptInStatus: false } });
+ const component = mountWithIntl(renderWelcomeTelemetryNotice(telemetryService, (url) => url));
+ expect(component.exists('[id="telemetry.dataManagementDisableCollectionLink"]')).toBe(false);
+ expect(component.exists('[id="telemetry.dataManagementEnableCollectionLink"]')).toBe(false);
+ });
+});
diff --git a/src/plugins/telemetry/public/render_welcome_telemetry_notice.tsx b/src/plugins/telemetry/public/render_welcome_telemetry_notice.tsx
new file mode 100644
index 00000000000000..8ef26fb797d532
--- /dev/null
+++ b/src/plugins/telemetry/public/render_welcome_telemetry_notice.tsx
@@ -0,0 +1,80 @@
+/*
+ * 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 React from 'react';
+import { EuiLink, EuiSpacer, EuiTextColor } from '@elastic/eui';
+import { FormattedMessage } from '@kbn/i18n-react';
+import type { TelemetryService } from './services';
+import { PRIVACY_STATEMENT_URL } from '../common/constants';
+
+export function renderWelcomeTelemetryNotice(
+ telemetryService: TelemetryService,
+ addBasePath: (url: string) => string
+) {
+ return (
+ <>
+
+
+
+
+
+ {renderTelemetryEnabledOrDisabledText(telemetryService, addBasePath)}
+
+
+ >
+ );
+}
+
+function renderTelemetryEnabledOrDisabledText(
+ telemetryService: TelemetryService,
+ addBasePath: (url: string) => string
+) {
+ if (!telemetryService.userCanChangeSettings || !telemetryService.getCanChangeOptInStatus()) {
+ return null;
+ }
+
+ const isOptedIn = telemetryService.getIsOptedIn();
+
+ if (isOptedIn) {
+ return (
+ <>
+
+
+
+
+ >
+ );
+ } else {
+ return (
+ <>
+
+
+
+
+ >
+ );
+ }
+}
diff --git a/src/plugins/telemetry/server/plugin.ts b/src/plugins/telemetry/server/plugin.ts
index 73c61ea1c50386..681a871ba105b6 100644
--- a/src/plugins/telemetry/server/plugin.ts
+++ b/src/plugins/telemetry/server/plugin.ts
@@ -23,6 +23,7 @@ import type {
Plugin,
Logger,
} from 'src/core/server';
+import type { SecurityPluginStart } from '../../../../x-pack/plugins/security/server';
import { SavedObjectsClient } from '../../../core/server';
import { registerRoutes } from './routes';
import { registerCollection } from './telemetry_collection';
@@ -42,6 +43,7 @@ interface TelemetryPluginsDepsSetup {
interface TelemetryPluginsDepsStart {
telemetryCollectionManager: TelemetryCollectionManagerPluginStart;
+ security?: SecurityPluginStart;
}
/**
@@ -90,6 +92,8 @@ export class TelemetryPlugin implements Plugin(1);
+ private security?: SecurityPluginStart;
+
constructor(initializerContext: PluginInitializerContext) {
this.logger = initializerContext.logger.get();
this.isDev = initializerContext.env.mode.dev;
@@ -119,6 +123,7 @@ export class TelemetryPlugin implements Plugin this.security,
});
this.registerMappings((opts) => savedObjects.registerType(opts));
@@ -137,11 +142,17 @@ export class TelemetryPlugin implements Plugin;
+ getSecurity: SecurityGetter;
}
export function registerRoutes(options: RegisterRoutesParams) {
- const { isDev, telemetryCollectionManager, router, savedObjectsInternalClient$ } = options;
+ const { isDev, telemetryCollectionManager, router, savedObjectsInternalClient$, getSecurity } =
+ options;
registerTelemetryOptInRoutes(options);
- registerTelemetryUsageStatsRoutes(router, telemetryCollectionManager, isDev);
+ registerTelemetryUsageStatsRoutes(router, telemetryCollectionManager, isDev, getSecurity);
registerTelemetryOptInStatsRoutes(router, telemetryCollectionManager);
registerTelemetryUserHasSeenNotice(router);
registerTelemetryLastReported(router, savedObjectsInternalClient$);
diff --git a/src/plugins/telemetry/server/routes/telemetry_opt_in_stats.ts b/src/plugins/telemetry/server/routes/telemetry_opt_in_stats.ts
index 2a956656621944..6139eee3e10ca6 100644
--- a/src/plugins/telemetry/server/routes/telemetry_opt_in_stats.ts
+++ b/src/plugins/telemetry/server/routes/telemetry_opt_in_stats.ts
@@ -75,7 +75,6 @@ export function registerTelemetryOptInStatsRoutes(
const statsGetterConfig: StatsGetterConfig = {
unencrypted,
- request: req,
};
const optInStatus = await telemetryCollectionManager.getOptInStats(
diff --git a/src/plugins/telemetry/server/routes/telemetry_usage_stats.test.ts b/src/plugins/telemetry/server/routes/telemetry_usage_stats.test.ts
index 736367446d3c05..bc7569585c127b 100644
--- a/src/plugins/telemetry/server/routes/telemetry_usage_stats.test.ts
+++ b/src/plugins/telemetry/server/routes/telemetry_usage_stats.test.ts
@@ -8,7 +8,8 @@
import { registerTelemetryUsageStatsRoutes } from './telemetry_usage_stats';
import { coreMock, httpServerMock } from 'src/core/server/mocks';
-import type { RequestHandlerContext, IRouter } from 'kibana/server';
+import type { RequestHandlerContext, IRouter } from 'src/core/server';
+import { securityMock } from '../../../../../x-pack/plugins/security/server/mocks';
import { telemetryCollectionManagerPluginMock } from '../../../telemetry_collection_manager/server/mocks';
async function runRequest(
@@ -35,13 +36,18 @@ describe('registerTelemetryUsageStatsRoutes', () => {
};
const telemetryCollectionManager = telemetryCollectionManagerPluginMock.createSetupContract();
const mockCoreSetup = coreMock.createSetup();
- const mockRouter = mockCoreSetup.http.createRouter();
const mockStats = [{ clusterUuid: 'text', stats: 'enc_str' }];
telemetryCollectionManager.getStats.mockResolvedValue(mockStats);
+ const getSecurity = jest.fn();
+
+ let mockRouter: IRouter;
+ beforeEach(() => {
+ mockRouter = mockCoreSetup.http.createRouter();
+ });
describe('clusters/_stats POST route', () => {
it('registers _stats POST route and accepts body configs', () => {
- registerTelemetryUsageStatsRoutes(mockRouter, telemetryCollectionManager, true);
+ registerTelemetryUsageStatsRoutes(mockRouter, telemetryCollectionManager, true, getSecurity);
expect(mockRouter.post).toBeCalledTimes(1);
const [routeConfig, handler] = (mockRouter.post as jest.Mock).mock.calls[0];
expect(routeConfig.path).toMatchInlineSnapshot(`"/api/telemetry/v2/clusters/_stats"`);
@@ -50,11 +56,10 @@ describe('registerTelemetryUsageStatsRoutes', () => {
});
it('responds with encrypted stats with no cache refresh by default', async () => {
- registerTelemetryUsageStatsRoutes(mockRouter, telemetryCollectionManager, true);
+ registerTelemetryUsageStatsRoutes(mockRouter, telemetryCollectionManager, true, getSecurity);
- const { mockRequest, mockResponse } = await runRequest(mockRouter);
+ const { mockResponse } = await runRequest(mockRouter);
expect(telemetryCollectionManager.getStats).toBeCalledWith({
- request: mockRequest,
unencrypted: undefined,
refreshCache: undefined,
});
@@ -63,39 +68,99 @@ describe('registerTelemetryUsageStatsRoutes', () => {
});
it('when unencrypted is set getStats is called with unencrypted and refreshCache', async () => {
- registerTelemetryUsageStatsRoutes(mockRouter, telemetryCollectionManager, true);
+ registerTelemetryUsageStatsRoutes(mockRouter, telemetryCollectionManager, true, getSecurity);
- const { mockRequest } = await runRequest(mockRouter, { unencrypted: true });
+ await runRequest(mockRouter, { unencrypted: true });
expect(telemetryCollectionManager.getStats).toBeCalledWith({
- request: mockRequest,
unencrypted: true,
refreshCache: true,
});
});
it('calls getStats with refreshCache when set in body', async () => {
- registerTelemetryUsageStatsRoutes(mockRouter, telemetryCollectionManager, true);
- const { mockRequest } = await runRequest(mockRouter, { refreshCache: true });
+ registerTelemetryUsageStatsRoutes(mockRouter, telemetryCollectionManager, true, getSecurity);
+ await runRequest(mockRouter, { refreshCache: true });
expect(telemetryCollectionManager.getStats).toBeCalledWith({
- request: mockRequest,
unencrypted: undefined,
refreshCache: true,
});
});
it('calls getStats with refreshCache:true even if set to false in body when unencrypted is set to true', async () => {
- registerTelemetryUsageStatsRoutes(mockRouter, telemetryCollectionManager, true);
- const { mockRequest } = await runRequest(mockRouter, {
+ registerTelemetryUsageStatsRoutes(mockRouter, telemetryCollectionManager, true, getSecurity);
+ await runRequest(mockRouter, {
refreshCache: false,
unencrypted: true,
});
expect(telemetryCollectionManager.getStats).toBeCalledWith({
- request: mockRequest,
unencrypted: true,
refreshCache: true,
});
});
+ it('returns 403 when the user does not have enough permissions to request unencrypted telemetry', async () => {
+ const getSecurityMock = jest.fn().mockImplementation(() => {
+ const securityStartMock = securityMock.createStart();
+ securityStartMock.authz.checkPrivilegesWithRequest.mockReturnValue({
+ globally: () => ({ hasAllRequested: false }),
+ });
+ return securityStartMock;
+ });
+ registerTelemetryUsageStatsRoutes(
+ mockRouter,
+ telemetryCollectionManager,
+ true,
+ getSecurityMock
+ );
+ const { mockResponse } = await runRequest(mockRouter, {
+ refreshCache: false,
+ unencrypted: true,
+ });
+ expect(mockResponse.forbidden).toBeCalled();
+ });
+
+ it('returns 200 when the user has enough permissions to request unencrypted telemetry', async () => {
+ const getSecurityMock = jest.fn().mockImplementation(() => {
+ const securityStartMock = securityMock.createStart();
+ securityStartMock.authz.checkPrivilegesWithRequest.mockReturnValue({
+ globally: () => ({ hasAllRequested: true }),
+ });
+ return securityStartMock;
+ });
+ registerTelemetryUsageStatsRoutes(
+ mockRouter,
+ telemetryCollectionManager,
+ true,
+ getSecurityMock
+ );
+ const { mockResponse } = await runRequest(mockRouter, {
+ refreshCache: false,
+ unencrypted: true,
+ });
+ expect(mockResponse.ok).toBeCalled();
+ });
+
+ it('returns 200 when the user does not have enough permissions to request unencrypted telemetry but it requests encrypted', async () => {
+ const getSecurityMock = jest.fn().mockImplementation(() => {
+ const securityStartMock = securityMock.createStart();
+ securityStartMock.authz.checkPrivilegesWithRequest.mockReturnValue({
+ globally: () => ({ hasAllRequested: false }),
+ });
+ return securityStartMock;
+ });
+ registerTelemetryUsageStatsRoutes(
+ mockRouter,
+ telemetryCollectionManager,
+ true,
+ getSecurityMock
+ );
+ const { mockResponse } = await runRequest(mockRouter, {
+ refreshCache: false,
+ unencrypted: false,
+ });
+ expect(mockResponse.ok).toBeCalled();
+ });
+
it.todo('always returns an empty array on errors on encrypted payload');
it.todo('returns the actual request error object when in development mode');
it.todo('returns forbidden on unencrypted and ES returns 403 in getStats');
diff --git a/src/plugins/telemetry/server/routes/telemetry_usage_stats.ts b/src/plugins/telemetry/server/routes/telemetry_usage_stats.ts
index 2f72ae818f1121..4647f5afe0760b 100644
--- a/src/plugins/telemetry/server/routes/telemetry_usage_stats.ts
+++ b/src/plugins/telemetry/server/routes/telemetry_usage_stats.ts
@@ -12,11 +12,15 @@ import {
TelemetryCollectionManagerPluginSetup,
StatsGetterConfig,
} from 'src/plugins/telemetry_collection_manager/server';
+import type { SecurityPluginStart } from '../../../../../x-pack/plugins/security/server';
+
+export type SecurityGetter = () => SecurityPluginStart | undefined;
export function registerTelemetryUsageStatsRoutes(
router: IRouter,
telemetryCollectionManager: TelemetryCollectionManagerPluginSetup,
- isDev: boolean
+ isDev: boolean,
+ getSecurity: SecurityGetter
) {
router.post(
{
@@ -31,9 +35,22 @@ export function registerTelemetryUsageStatsRoutes(
async (context, req, res) => {
const { unencrypted, refreshCache } = req.body;
+ const security = getSecurity();
+ if (security && unencrypted) {
+ // Normally we would use `options: { tags: ['access:decryptedTelemetry'] }` in the route definition to check authorization for an
+ // API action, however, we want to check this conditionally based on the `unencrypted` parameter. In this case we need to use the
+ // security API directly to check privileges for this action. Note that the 'decryptedTelemetry' API privilege string is only
+ // granted to users that have "Global All" or "Global Read" privileges in Kibana.
+ const { checkPrivilegesWithRequest, actions } = security.authz;
+ const privileges = { kibana: actions.api.get('decryptedTelemetry') };
+ const { hasAllRequested } = await checkPrivilegesWithRequest(req).globally(privileges);
+ if (!hasAllRequested) {
+ return res.forbidden();
+ }
+ }
+
try {
const statsConfig: StatsGetterConfig = {
- request: req,
unencrypted,
refreshCache: unencrypted || refreshCache,
};
diff --git a/src/plugins/telemetry/server/telemetry_collection/get_kibana.ts b/src/plugins/telemetry/server/telemetry_collection/get_kibana.ts
index 83f33a894b9032..4340eaafd2d8ff 100644
--- a/src/plugins/telemetry/server/telemetry_collection/get_kibana.ts
+++ b/src/plugins/telemetry/server/telemetry_collection/get_kibana.ts
@@ -7,10 +7,9 @@
*/
import { omit } from 'lodash';
-import { UsageCollectionSetup } from 'src/plugins/usage_collection/server';
-import { KibanaRequest, SavedObjectsClientContract } from 'kibana/server';
-import { StatsCollectionContext } from 'src/plugins/telemetry_collection_manager/server';
-import { ElasticsearchClient } from 'src/core/server';
+import type { ElasticsearchClient, SavedObjectsClientContract } from 'src/core/server';
+import type { UsageCollectionSetup } from 'src/plugins/usage_collection/server';
+import type { StatsCollectionContext } from 'src/plugins/telemetry_collection_manager/server';
export interface KibanaUsageStats {
kibana: {
@@ -71,9 +70,8 @@ export function handleKibanaStats(
export async function getKibana(
usageCollection: UsageCollectionSetup,
asInternalUser: ElasticsearchClient,
- soClient: SavedObjectsClientContract,
- kibanaRequest: KibanaRequest | undefined // intentionally `| undefined` to enforce providing the parameter
+ soClient: SavedObjectsClientContract
): Promise {
- const usage = await usageCollection.bulkFetch(asInternalUser, soClient, kibanaRequest);
+ const usage = await usageCollection.bulkFetch(asInternalUser, soClient);
return usageCollection.toObject(usage);
}
diff --git a/src/plugins/telemetry/server/telemetry_collection/get_local_stats.test.ts b/src/plugins/telemetry/server/telemetry_collection/get_local_stats.test.ts
index 2392ac570ecbc1..fa45438e00fbe3 100644
--- a/src/plugins/telemetry/server/telemetry_collection/get_local_stats.test.ts
+++ b/src/plugins/telemetry/server/telemetry_collection/get_local_stats.test.ts
@@ -14,7 +14,7 @@ import {
usageCollectionPluginMock,
createCollectorFetchContextMock,
} from '../../../usage_collection/server/mocks';
-import { elasticsearchServiceMock, httpServerMock } from '../../../../../src/core/server/mocks';
+import { elasticsearchServiceMock } from '../../../../../src/core/server/mocks';
import { StatsCollectionConfig } from '../../../telemetry_collection_manager/server';
function mockUsageCollection(kibanaUsage = {}) {
@@ -74,7 +74,6 @@ function mockStatsCollectionConfig(
...createCollectorFetchContextMock(),
esClient: mockGetLocalStats(clusterInfo, clusterStats),
usageCollection: mockUsageCollection(kibana),
- kibanaRequest: httpServerMock.createKibanaRequest(),
refreshCache: false,
};
}
diff --git a/src/plugins/telemetry/server/telemetry_collection/get_local_stats.ts b/src/plugins/telemetry/server/telemetry_collection/get_local_stats.ts
index ae2a849ccfa19a..73de59ae8156aa 100644
--- a/src/plugins/telemetry/server/telemetry_collection/get_local_stats.ts
+++ b/src/plugins/telemetry/server/telemetry_collection/get_local_stats.ts
@@ -65,7 +65,7 @@ export const getLocalStats: StatsGetter = async (
config,
context
) => {
- const { usageCollection, esClient, soClient, kibanaRequest } = config;
+ const { usageCollection, esClient, soClient } = config;
return await Promise.all(
clustersDetails.map(async (clustersDetail) => {
@@ -73,7 +73,7 @@ export const getLocalStats: StatsGetter = async (
getClusterInfo(esClient), // cluster info
getClusterStats(esClient), // cluster stats (not to be confused with cluster _state_)
getNodesUsage(esClient), // nodes_usage info
- getKibana(usageCollection, esClient, soClient, kibanaRequest),
+ getKibana(usageCollection, esClient, soClient),
getDataTelemetry(esClient),
]);
return handleLocalStats(
diff --git a/src/plugins/telemetry/tsconfig.json b/src/plugins/telemetry/tsconfig.json
index d50ccd563fe5ac..052d484447e428 100644
--- a/src/plugins/telemetry/tsconfig.json
+++ b/src/plugins/telemetry/tsconfig.json
@@ -17,10 +17,12 @@
],
"references": [
{ "path": "../../core/tsconfig.json" },
+ { "path": "../../plugins/home/tsconfig.json" },
{ "path": "../../plugins/kibana_react/tsconfig.json" },
{ "path": "../../plugins/kibana_utils/tsconfig.json" },
{ "path": "../../plugins/screenshot_mode/tsconfig.json" },
{ "path": "../../plugins/telemetry_collection_manager/tsconfig.json" },
- { "path": "../../plugins/usage_collection/tsconfig.json" }
+ { "path": "../../plugins/usage_collection/tsconfig.json" },
+ { "path": "../../../x-pack/plugins/security/tsconfig.json" }
]
}
diff --git a/src/plugins/telemetry_collection_manager/server/plugin.test.ts b/src/plugins/telemetry_collection_manager/server/plugin.test.ts
index ca932e92d98bdb..990e237b6b2724 100644
--- a/src/plugins/telemetry_collection_manager/server/plugin.test.ts
+++ b/src/plugins/telemetry_collection_manager/server/plugin.test.ts
@@ -6,7 +6,7 @@
* Side Public License, v 1.
*/
-import { coreMock, httpServerMock } from '../../../core/server/mocks';
+import { coreMock } from '../../../core/server/mocks';
import { usageCollectionPluginMock } from '../../usage_collection/server/mocks';
import { TelemetryCollectionManagerPlugin } from './plugin';
import type { BasicStatsPayload, CollectionStrategyConfig, StatsGetterConfig } from './types';
@@ -217,19 +217,17 @@ describe('Telemetry Collection Manager', () => {
});
});
describe('unencrypted: true', () => {
- const mockRequest = httpServerMock.createKibanaRequest();
const config: StatsGetterConfig = {
unencrypted: true,
- request: mockRequest,
};
describe('getStats', () => {
- test('getStats returns empty because clusterDetails returns empty, and the soClient is not an instance of the TelemetrySavedObjectsClient', async () => {
+ test('getStats returns empty because clusterDetails returns empty, and the soClient is an instance of the TelemetrySavedObjectsClient', async () => {
collectionStrategy.clusterDetailsGetter.mockResolvedValue([]);
await expect(setupApi.getStats(config)).resolves.toStrictEqual([]);
expect(
collectionStrategy.clusterDetailsGetter.mock.calls[0][0].soClient
- ).not.toBeInstanceOf(TelemetrySavedObjectsClient);
+ ).toBeInstanceOf(TelemetrySavedObjectsClient);
});
test('returns encrypted payload (assumes opted-in when no explicitly opted-out)', async () => {
collectionStrategy.clusterDetailsGetter.mockResolvedValue([
@@ -249,7 +247,7 @@ describe('Telemetry Collection Manager', () => {
expect(
collectionStrategy.clusterDetailsGetter.mock.calls[0][0].soClient
- ).not.toBeInstanceOf(TelemetrySavedObjectsClient);
+ ).toBeInstanceOf(TelemetrySavedObjectsClient);
});
it('calls getStats with config { refreshCache: true } even if set to false', async () => {
@@ -267,7 +265,6 @@ describe('Telemetry Collection Manager', () => {
expect(getStatsCollectionConfig).toReturnWith(
expect.objectContaining({
refreshCache: true,
- kibanaRequest: mockRequest,
})
);
@@ -281,7 +278,7 @@ describe('Telemetry Collection Manager', () => {
await expect(setupApi.getOptInStats(true, config)).resolves.toStrictEqual([]);
expect(
collectionStrategy.clusterDetailsGetter.mock.calls[0][0].soClient
- ).not.toBeInstanceOf(TelemetrySavedObjectsClient);
+ ).toBeInstanceOf(TelemetrySavedObjectsClient);
});
test('returns results for opt-in true', async () => {
@@ -296,7 +293,7 @@ describe('Telemetry Collection Manager', () => {
]);
expect(
collectionStrategy.clusterDetailsGetter.mock.calls[0][0].soClient
- ).not.toBeInstanceOf(TelemetrySavedObjectsClient);
+ ).toBeInstanceOf(TelemetrySavedObjectsClient);
});
test('returns results for opt-in false', async () => {
@@ -311,7 +308,7 @@ describe('Telemetry Collection Manager', () => {
]);
expect(
collectionStrategy.clusterDetailsGetter.mock.calls[0][0].soClient
- ).not.toBeInstanceOf(TelemetrySavedObjectsClient);
+ ).toBeInstanceOf(TelemetrySavedObjectsClient);
});
});
});
diff --git a/src/plugins/telemetry_collection_manager/server/plugin.ts b/src/plugins/telemetry_collection_manager/server/plugin.ts
index fad51ca1dbfde8..cffe736f8eeaf5 100644
--- a/src/plugins/telemetry_collection_manager/server/plugin.ts
+++ b/src/plugins/telemetry_collection_manager/server/plugin.ts
@@ -126,11 +126,10 @@ export class TelemetryCollectionManagerPlugin
const esClient = this.getElasticsearchClient(config);
const soClient = this.getSavedObjectsClient(config);
// Provide the kibanaRequest so opted-in plugins can scope their custom clients only if the request is not encrypted
- const kibanaRequest = config.unencrypted ? config.request : void 0;
const refreshCache = config.unencrypted ? true : !!config.refreshCache;
if (esClient && soClient) {
- return { usageCollection, esClient, soClient, kibanaRequest, refreshCache };
+ return { usageCollection, esClient, soClient, refreshCache };
}
}
@@ -142,9 +141,7 @@ export class TelemetryCollectionManagerPlugin
* @private
*/
private getElasticsearchClient(config: StatsGetterConfig): ElasticsearchClient | undefined {
- return config.unencrypted
- ? this.elasticsearchClient?.asScoped(config.request).asCurrentUser
- : this.elasticsearchClient?.asInternalUser;
+ return this.elasticsearchClient?.asInternalUser;
}
/**
@@ -155,11 +152,7 @@ export class TelemetryCollectionManagerPlugin
* @private
*/
private getSavedObjectsClient(config: StatsGetterConfig): SavedObjectsClientContract | undefined {
- if (config.unencrypted) {
- // Intentionally using the scoped client here to make use of all the security wrappers.
- // It also returns spaces-scoped telemetry.
- return this.savedObjectsService?.getScopedClient(config.request);
- } else if (this.savedObjectsService) {
+ if (this.savedObjectsService) {
// Wrapping the internalRepository with the `TelemetrySavedObjectsClient`
// to ensure some best practices when collecting "all the telemetry"
// (i.e.: `.find` requests should query all spaces)
diff --git a/src/plugins/telemetry_collection_manager/server/types.ts b/src/plugins/telemetry_collection_manager/server/types.ts
index 7ea32844a858cb..9658c0d68d05db 100644
--- a/src/plugins/telemetry_collection_manager/server/types.ts
+++ b/src/plugins/telemetry_collection_manager/server/types.ts
@@ -6,14 +6,9 @@
* Side Public License, v 1.
*/
-import {
- ElasticsearchClient,
- Logger,
- KibanaRequest,
- SavedObjectsClientContract,
-} from 'src/core/server';
-import { UsageCollectionSetup } from 'src/plugins/usage_collection/server';
-import { TelemetryCollectionManagerPlugin } from './plugin';
+import type { ElasticsearchClient, Logger, SavedObjectsClientContract } from 'src/core/server';
+import type { UsageCollectionSetup } from 'src/plugins/usage_collection/server';
+import type { TelemetryCollectionManagerPlugin } from './plugin';
export interface TelemetryCollectionManagerPluginSetup {
setCollectionStrategy: (
@@ -36,7 +31,6 @@ export interface TelemetryOptInStats {
export interface BaseStatsGetterConfig {
unencrypted: boolean;
refreshCache?: boolean;
- request?: KibanaRequest;
}
export interface EncryptedStatsGetterConfig extends BaseStatsGetterConfig {
@@ -45,7 +39,6 @@ export interface EncryptedStatsGetterConfig extends BaseStatsGetterConfig {
export interface UnencryptedStatsGetterConfig extends BaseStatsGetterConfig {
unencrypted: true;
- request: KibanaRequest;
}
export interface ClusterDetails {
@@ -56,7 +49,6 @@ export interface StatsCollectionConfig {
usageCollection: UsageCollectionSetup;
esClient: ElasticsearchClient;
soClient: SavedObjectsClientContract;
- kibanaRequest: KibanaRequest | undefined; // intentionally `| undefined` to enforce providing the parameter
refreshCache: boolean;
}
diff --git a/src/plugins/usage_collection/README.mdx b/src/plugins/usage_collection/README.mdx
index a58f197818bf4e..03d8f7badb8c2a 100644
--- a/src/plugins/usage_collection/README.mdx
+++ b/src/plugins/usage_collection/README.mdx
@@ -297,8 +297,7 @@ Some background:
- `isReady` (added in v7.2.0 and v6.8.4) is a way for a usage collector to announce that some async process must finish first before it can return data in the `fetch` method (e.g. a client needs to ne initialized, or the task manager needs to run a task first). If any collector reports that it is not ready when we call its `fetch` method, we reset a flag to try again and, after a set amount of time, collect data from those collectors that are ready and skip any that are not. This means that if a collector returns `true` for `isReady` and it actually isn't ready to return data, there won't be telemetry data from that collector in that telemetry report (usually once per day). You should consider what it means if your collector doesn't return data in the first few documents when Kibana starts or, if we should wait for any other reason (e.g. the task manager needs to run your task first). If you need to tell telemetry collection to wait, you should implement this function with custom logic. If your `fetch` method can run without the need of any previous dependencies, then you can return true for `isReady` as shown in the example below.
-- The `fetch` method needs to support multiple contexts in which it is called. For example, when a user requests the example of what we collect in the **Kibana>Advanced Settings>Usage data** section, the clients provided in the context of the function (`CollectorFetchContext`) are scoped to that user's privileges. The reason is to avoid exposing via telemetry any data that user should not have access to (i.e.: if the user does not have access to certain indices, they shouldn't be allowed to see the number of documents that exists in it). In this case, the `fetch` method receives the clients `esClient` and `soClient` scoped to the user who performed the HTTP API request. Alternatively, when requesting the usage data to be reported to the Remote Telemetry Service, the clients are scoped to the internal Kibana user (`kibana_system`). Please, mind it might have lower-level access than the default super-admin `elastic` test user.
-In some scenarios, your collector might need to maintain its own client. An example of that is the `monitoring` plugin, that maintains a connection to the Remote Monitoring Cluster to push its monitoring data. If that's the case, your plugin can opt-in to receive the additional `kibanaRequest` parameter by adding `extendFetchContext.kibanaRequest: true` to the collector's config: it will be appended to the context of the `fetch` method only if the request needs to be scoped to a user other than Kibana Internal, so beware that your collector will need to work for both scenarios (especially for the scenario when `kibanaRequest` is missing).
+- The clients provided to the `fetch` method are scoped to the internal Kibana user (`kibana_system`).
Note: there will be many cases where you won't need to use the `esClient` or `soClient` function that gets passed in to your `fetch` method at all. Your feature might have an accumulating value in server memory, or read something from the OS.
diff --git a/src/plugins/usage_collection/server/collector/collector.ts b/src/plugins/usage_collection/server/collector/collector.ts
index 74373d44a359b6..1ff04cf3650c0b 100644
--- a/src/plugins/usage_collection/server/collector/collector.ts
+++ b/src/plugins/usage_collection/server/collector/collector.ts
@@ -7,20 +7,14 @@
*/
import type { Logger } from 'src/core/server';
-import type {
- CollectorFetchMethod,
- CollectorOptions,
- CollectorOptionsFetchExtendedContext,
- ICollector,
-} from './types';
+import type { CollectorFetchMethod, CollectorOptions, ICollector } from './types';
export class Collector
implements ICollector
{
- public readonly extendFetchContext: CollectorOptionsFetchExtendedContext;
- public readonly type: CollectorOptions['type'];
- public readonly fetch: CollectorFetchMethod;
- public readonly isReady: CollectorOptions['isReady'];
+ public readonly type: CollectorOptions['type'];
+ public readonly fetch: CollectorFetchMethod;
+ public readonly isReady: CollectorOptions['isReady'];
/**
* @private Constructor of a Collector. It should be called via the CollectorSet factory methods: `makeStatsCollector` and `makeUsageCollector`
* @param log {@link Logger}
@@ -28,15 +22,7 @@ export class Collector
*/
constructor(
public readonly log: Logger,
- {
- type,
- fetch,
- isReady,
- extendFetchContext = {},
- ...options
- }: // Any does not affect here, but needs to be set so it doesn't affect anything else down the line
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- CollectorOptions
+ { type, fetch, isReady, ...options }: CollectorOptions
) {
if (type === undefined) {
throw new Error('Collector must be instantiated with a options.type string property');
@@ -50,6 +36,5 @@ export class Collector
this.type = type;
this.fetch = fetch;
this.isReady = typeof isReady === 'function' ? isReady : () => true;
- this.extendFetchContext = extendFetchContext;
}
}
diff --git a/src/plugins/usage_collection/server/collector/collector_set.test.ts b/src/plugins/usage_collection/server/collector/collector_set.test.ts
index 5e0698b286f79b..87e841f3de4c54 100644
--- a/src/plugins/usage_collection/server/collector/collector_set.test.ts
+++ b/src/plugins/usage_collection/server/collector/collector_set.test.ts
@@ -15,7 +15,6 @@ import {
elasticsearchServiceMock,
loggingSystemMock,
savedObjectsClientMock,
- httpServerMock,
executionContextServiceMock,
} from '../../../../core/server/mocks';
import type { ExecutionContextSetup, Logger } from 'src/core/server';
@@ -39,7 +38,6 @@ describe('CollectorSet', () => {
});
const mockEsClient = elasticsearchServiceMock.createClusterClient().asInternalUser;
const mockSoClient = savedObjectsClientMock.create();
- const req = void 0; // No need to instantiate any KibanaRequest in these tests
it('should throw an error if non-Collector type of object is registered', () => {
const collectors = new CollectorSet(collectorSetConfig);
@@ -88,7 +86,7 @@ describe('CollectorSet', () => {
})
);
- const result = await collectors.bulkFetch(mockEsClient, mockSoClient, req);
+ const result = await collectors.bulkFetch(mockEsClient, mockSoClient);
expect(logger.debug).toHaveBeenCalledTimes(2);
expect(logger.debug).toHaveBeenCalledWith('Getting ready collectors');
expect(logger.debug).toHaveBeenCalledWith('Fetching data from MY_TEST_COLLECTOR collector');
@@ -121,7 +119,7 @@ describe('CollectorSet', () => {
let result;
try {
- result = await collectors.bulkFetch(mockEsClient, mockSoClient, req);
+ result = await collectors.bulkFetch(mockEsClient, mockSoClient);
} catch (err) {
// Do nothing
}
@@ -150,7 +148,7 @@ describe('CollectorSet', () => {
})
);
- const result = await collectors.bulkFetch(mockEsClient, mockSoClient, req);
+ const result = await collectors.bulkFetch(mockEsClient, mockSoClient);
expect(result).toStrictEqual([
{
type: 'MY_TEST_COLLECTOR',
@@ -178,7 +176,7 @@ describe('CollectorSet', () => {
})
);
- const result = await collectors.bulkFetch(mockEsClient, mockSoClient, req);
+ const result = await collectors.bulkFetch(mockEsClient, mockSoClient);
expect(result).toStrictEqual([
{
type: 'MY_TEST_COLLECTOR',
@@ -269,50 +267,6 @@ describe('CollectorSet', () => {
collectorSet = new CollectorSet(collectorSetConfig);
});
- test('TS should hide kibanaRequest when not opted-in', () => {
- collectorSet.makeStatsCollector({
- type: 'MY_TEST_COLLECTOR',
- isReady: () => true,
- schema: { test: { type: 'long' } },
- fetch: (ctx) => {
- // @ts-expect-error
- const { kibanaRequest } = ctx;
- return { test: kibanaRequest ? 1 : 0 };
- },
- });
- });
-
- test('TS should hide kibanaRequest when not opted-in (explicit false)', () => {
- collectorSet.makeStatsCollector({
- type: 'MY_TEST_COLLECTOR',
- isReady: () => true,
- schema: { test: { type: 'long' } },
- fetch: (ctx) => {
- // @ts-expect-error
- const { kibanaRequest } = ctx;
- return { test: kibanaRequest ? 1 : 0 };
- },
- extendFetchContext: {
- kibanaRequest: false,
- },
- });
- });
-
- test('TS should allow using kibanaRequest when opted-in (explicit true)', () => {
- collectorSet.makeStatsCollector({
- type: 'MY_TEST_COLLECTOR',
- isReady: () => true,
- schema: { test: { type: 'long' } },
- fetch: (ctx) => {
- const { kibanaRequest } = ctx;
- return { test: kibanaRequest ? 1 : 0 };
- },
- extendFetchContext: {
- kibanaRequest: true,
- },
- });
- });
-
test('fetch can use the logger (TS allows it)', () => {
const collector = collectorSet.makeStatsCollector({
type: 'MY_TEST_COLLECTOR',
@@ -339,188 +293,6 @@ describe('CollectorSet', () => {
collectorSet = new CollectorSet(collectorSetConfig);
});
- describe('TS validations', () => {
- describe('when types are inferred', () => {
- test('TS should hide kibanaRequest when not opted-in', () => {
- collectorSet.makeUsageCollector({
- type: 'MY_TEST_COLLECTOR',
- isReady: () => true,
- schema: { test: { type: 'long' } },
- fetch: (ctx) => {
- // @ts-expect-error
- const { kibanaRequest } = ctx;
- return { test: kibanaRequest ? 1 : 0 };
- },
- });
- });
-
- test('TS should hide kibanaRequest when not opted-in (explicit false)', () => {
- collectorSet.makeUsageCollector({
- type: 'MY_TEST_COLLECTOR',
- isReady: () => true,
- schema: { test: { type: 'long' } },
- fetch: (ctx) => {
- // @ts-expect-error
- const { kibanaRequest } = ctx;
- return { test: kibanaRequest ? 1 : 0 };
- },
- extendFetchContext: {
- kibanaRequest: false,
- },
- });
- });
-
- test('TS should allow using kibanaRequest when opted-in (explicit true)', () => {
- collectorSet.makeUsageCollector({
- type: 'MY_TEST_COLLECTOR',
- isReady: () => true,
- schema: { test: { type: 'long' } },
- fetch: (ctx) => {
- const { kibanaRequest } = ctx;
- return { test: kibanaRequest ? 1 : 0 };
- },
- extendFetchContext: {
- kibanaRequest: true,
- },
- });
- });
- });
-
- describe('when types are explicit', () => {
- test('TS should hide `kibanaRequest` from ctx when undefined or false', () => {
- collectorSet.makeUsageCollector<{ test: number }>({
- type: 'MY_TEST_COLLECTOR',
- isReady: () => true,
- schema: { test: { type: 'long' } },
- fetch: (ctx) => {
- // @ts-expect-error
- const { kibanaRequest } = ctx;
- return { test: kibanaRequest ? 1 : 0 };
- },
- });
- collectorSet.makeUsageCollector<{ test: number }, false>({
- type: 'MY_TEST_COLLECTOR',
- isReady: () => true,
- schema: { test: { type: 'long' } },
- fetch: (ctx) => {
- // @ts-expect-error
- const { kibanaRequest } = ctx;
- return { test: kibanaRequest ? 1 : 0 };
- },
- extendFetchContext: {
- kibanaRequest: false,
- },
- });
- collectorSet.makeUsageCollector<{ test: number }, false>({
- type: 'MY_TEST_COLLECTOR',
- isReady: () => true,
- schema: { test: { type: 'long' } },
- fetch: (ctx) => {
- // @ts-expect-error
- const { kibanaRequest } = ctx;
- return { test: kibanaRequest ? 1 : 0 };
- },
- });
- });
- test('TS should not allow `true` when types declare false', () => {
- // false is the default when at least 1 type is specified
- collectorSet.makeUsageCollector<{ test: number }>({
- type: 'MY_TEST_COLLECTOR',
- isReady: () => true,
- schema: { test: { type: 'long' } },
- fetch: (ctx) => {
- // @ts-expect-error
- const { kibanaRequest } = ctx;
- return { test: kibanaRequest ? 1 : 0 };
- },
- extendFetchContext: {
- // @ts-expect-error
- kibanaRequest: true,
- },
- });
- collectorSet.makeUsageCollector<{ test: number }, false>({
- type: 'MY_TEST_COLLECTOR',
- isReady: () => true,
- schema: { test: { type: 'long' } },
- fetch: (ctx) => {
- // @ts-expect-error
- const { kibanaRequest } = ctx;
- return { test: kibanaRequest ? 1 : 0 };
- },
- extendFetchContext: {
- // @ts-expect-error
- kibanaRequest: true,
- },
- });
- });
-
- test('TS should allow `true` when types explicitly declare `true` and do not allow `false` or undefined', () => {
- // false is the default when at least 1 type is specified
- collectorSet.makeUsageCollector<{ test: number }, true>({
- type: 'MY_TEST_COLLECTOR',
- isReady: () => true,
- schema: { test: { type: 'long' } },
- fetch: (ctx) => {
- const { kibanaRequest } = ctx;
- return { test: kibanaRequest ? 1 : 0 };
- },
- extendFetchContext: {
- kibanaRequest: true,
- },
- });
- collectorSet.makeUsageCollector<{ test: number }, true>({
- type: 'MY_TEST_COLLECTOR',
- isReady: () => true,
- schema: { test: { type: 'long' } },
- fetch: (ctx) => {
- const { kibanaRequest } = ctx;
- return { test: kibanaRequest ? 1 : 0 };
- },
- extendFetchContext: {
- // @ts-expect-error
- kibanaRequest: false,
- },
- });
- collectorSet.makeUsageCollector<{ test: number }, true>({
- type: 'MY_TEST_COLLECTOR',
- isReady: () => true,
- schema: { test: { type: 'long' } },
- fetch: (ctx) => {
- const { kibanaRequest } = ctx;
- return { test: kibanaRequest ? 1 : 0 };
- },
- extendFetchContext: {
- // @ts-expect-error
- kibanaRequest: undefined,
- },
- });
- collectorSet.makeUsageCollector<{ test: number }, true>({
- type: 'MY_TEST_COLLECTOR',
- isReady: () => true,
- schema: { test: { type: 'long' } },
- fetch: (ctx) => {
- const { kibanaRequest } = ctx;
- return { test: kibanaRequest ? 1 : 0 };
- },
- // @ts-expect-error
- extendFetchContext: {},
- });
- collectorSet.makeUsageCollector<{ test: number }, true>(
- // @ts-expect-error
- {
- type: 'MY_TEST_COLLECTOR',
- isReady: () => true,
- schema: { test: { type: 'long' } },
- fetch: (ctx) => {
- const { kibanaRequest } = ctx;
- return { test: kibanaRequest ? 1 : 0 };
- },
- }
- );
- });
- });
- });
-
test('fetch can use the logger (TS allows it)', () => {
const collector = collectorSet.makeUsageCollector({
type: 'MY_TEST_COLLECTOR',
@@ -777,31 +549,5 @@ describe('CollectorSet', () => {
expect.any(Function)
);
});
-
- it('adds extra context to collectors with extendFetchContext config', async () => {
- const mockReadyFetch = jest.fn().mockResolvedValue({});
- collectorSet.registerCollector(
- collectorSet.makeUsageCollector({
- type: 'ready_col',
- isReady: () => true,
- schema: {},
- fetch: mockReadyFetch,
- extendFetchContext: { kibanaRequest: true },
- })
- );
-
- const mockEsClient = elasticsearchServiceMock.createClusterClient().asInternalUser;
- const mockSoClient = savedObjectsClientMock.create();
- const request = httpServerMock.createKibanaRequest();
- const results = await collectorSet.bulkFetch(mockEsClient, mockSoClient, request);
-
- expect(mockReadyFetch).toBeCalledTimes(1);
- expect(mockReadyFetch).toBeCalledWith({
- esClient: mockEsClient,
- soClient: mockSoClient,
- kibanaRequest: request,
- });
- expect(results).toHaveLength(2);
- });
});
});
diff --git a/src/plugins/usage_collection/server/collector/collector_set.ts b/src/plugins/usage_collection/server/collector/collector_set.ts
index 49332b0a1826fc..3a7c0a66ac60d1 100644
--- a/src/plugins/usage_collection/server/collector/collector_set.ts
+++ b/src/plugins/usage_collection/server/collector/collector_set.ts
@@ -11,7 +11,6 @@ import type {
Logger,
ElasticsearchClient,
SavedObjectsClientContract,
- KibanaRequest,
KibanaExecutionContext,
ExecutionContextSetup,
} from 'src/core/server';
@@ -64,12 +63,8 @@ export class CollectorSet {
* Instantiates a stats collector with the definition provided in the options
* @param options Definition of the collector {@link CollectorOptions}
*/
- public makeStatsCollector = <
- TFetchReturn,
- WithKibanaRequest extends boolean,
- ExtraOptions extends object = {}
- >(
- options: CollectorOptions
+ public makeStatsCollector = (
+ options: CollectorOptions
) => {
return new Collector(this.logger, options);
};
@@ -78,15 +73,8 @@ export class CollectorSet {
* Instantiates an usage collector with the definition provided in the options
* @param options Definition of the collector {@link CollectorOptions}
*/
- public makeUsageCollector = <
- TFetchReturn,
- // TODO: Right now, users will need to explicitly claim `true` for TS to allow `kibanaRequest` usage.
- // If we improve `telemetry-check-tools` so plugins do not need to specify TFetchReturn,
- // we'll be able to remove the type defaults and TS will successfully infer the config value as provided in JS.
- WithKibanaRequest extends boolean = false,
- ExtraOptions extends object = {}
- >(
- options: UsageCollectorOptions
+ public makeUsageCollector = (
+ options: UsageCollectorOptions
) => {
return new UsageCollector(this.logger, options);
};
@@ -191,7 +179,6 @@ export class CollectorSet {
public bulkFetch = async (
esClient: ElasticsearchClient,
soClient: SavedObjectsClientContract,
- kibanaRequest: KibanaRequest | undefined, // intentionally `| undefined` to enforce providing the parameter
collectors: Map = this.collectors
) => {
this.logger.debug(`Getting ready collectors`);
@@ -209,11 +196,7 @@ export class CollectorSet {
readyCollectors.map(async (collector) => {
this.logger.debug(`Fetching data from ${collector.type} collector`);
try {
- const context = {
- esClient,
- soClient,
- ...(collector.extendFetchContext.kibanaRequest && { kibanaRequest }),
- };
+ const context = { esClient, soClient };
const executionContext: KibanaExecutionContext = {
type: 'usage_collection',
name: 'collector.fetch',
@@ -254,16 +237,10 @@ export class CollectorSet {
public bulkFetchUsage = async (
esClient: ElasticsearchClient,
- savedObjectsClient: SavedObjectsClientContract,
- kibanaRequest: KibanaRequest | undefined // intentionally `| undefined` to enforce providing the parameter
+ savedObjectsClient: SavedObjectsClientContract
) => {
const usageCollectors = this.getFilteredCollectorSet((c) => c instanceof UsageCollector);
- return await this.bulkFetch(
- esClient,
- savedObjectsClient,
- kibanaRequest,
- usageCollectors.collectors
- );
+ return await this.bulkFetch(esClient, savedObjectsClient, usageCollectors.collectors);
};
/**
diff --git a/src/plugins/usage_collection/server/collector/index.ts b/src/plugins/usage_collection/server/collector/index.ts
index ca240a520ee24a..e284844b34c344 100644
--- a/src/plugins/usage_collection/server/collector/index.ts
+++ b/src/plugins/usage_collection/server/collector/index.ts
@@ -17,7 +17,6 @@ export type {
CollectorOptions,
CollectorFetchContext,
CollectorFetchMethod,
- CollectorOptionsFetchExtendedContext,
ICollector as Collector,
} from './types';
export type { UsageCollectorOptions } from './usage_collector';
diff --git a/src/plugins/usage_collection/server/collector/types.ts b/src/plugins/usage_collection/server/collector/types.ts
index bf1e9f4644b1b7..8d427d211a191b 100644
--- a/src/plugins/usage_collection/server/collector/types.ts
+++ b/src/plugins/usage_collection/server/collector/types.ts
@@ -6,12 +6,7 @@
* Side Public License, v 1.
*/
-import type {
- ElasticsearchClient,
- KibanaRequest,
- SavedObjectsClientContract,
- Logger,
-} from 'src/core/server';
+import type { ElasticsearchClient, SavedObjectsClientContract, Logger } from 'src/core/server';
/** Types matching number values **/
export type AllowedSchemaNumberTypes =
@@ -73,7 +68,7 @@ export type MakeSchemaFrom = {
*
* @remark Bear in mind when testing your collector that your user has the same privileges as the Kibana Internal user to ensure the expected data is sent to the remote cluster.
*/
-export type CollectorFetchContext = {
+export interface CollectorFetchContext {
/**
* Request-scoped Elasticsearch client
* @remark Bear in mind when testing your collector that your user has the same privileges as the Kibana Internal user to ensure the expected data is sent to the remote cluster (more info: {@link CollectorFetchContext})
@@ -84,58 +79,22 @@ export type CollectorFetchContext = (
+export type CollectorFetchMethod = (
this: ICollector & ExtraOptions, // Specify the context of `this` for this.log and others to become available
- context: CollectorFetchContext
+ context: CollectorFetchContext
) => Promise | TReturn;
-export interface ICollectorOptionsFetchExtendedContext {
- /**
- * Set to `true` if your `fetch` method requires the `KibanaRequest` object to be added in its context {@link CollectorFetchContextWithRequest}.
- * @remark You should fully acknowledge that by using the `KibanaRequest` in your collector, you need to ensure it should specially work without it because it won't be provided when building the telemetry payload actually sent to the remote telemetry service.
- */
- kibanaRequest?: WithKibanaRequest;
-}
-
-/**
- * The options to extend the context provided to the `fetch` method.
- * @remark Only to be used in very rare scenarios when this is really needed.
- */
-export type CollectorOptionsFetchExtendedContext =
- ICollectorOptionsFetchExtendedContext &
- (WithKibanaRequest extends true // If enforced to true via Types, the config must be expected
- ? Required, 'kibanaRequest'>>
- : {});
-
/**
* Options to instantiate a collector
*/
-export type CollectorOptions<
- TFetchReturn = unknown,
- WithKibanaRequest extends boolean = boolean,
- ExtraOptions extends object = {}
-> = {
+export type CollectorOptions = {
/**
* Unique string identifier for the collector
*/
@@ -152,17 +111,8 @@ export type CollectorOptions<
* The method that will collect and return the data in the final format.
* @param collectorFetchContext {@link CollectorFetchContext}
*/
- fetch: CollectorFetchMethod;
-} & ExtraOptions &
- (WithKibanaRequest extends true // If enforced to true via Types, the config must be enforced
- ? {
- /** {@link CollectorOptionsFetchExtendedContext} **/
- extendFetchContext: CollectorOptionsFetchExtendedContext;
- }
- : {
- /** {@link CollectorOptionsFetchExtendedContext} **/
- extendFetchContext?: CollectorOptionsFetchExtendedContext;
- });
+ fetch: CollectorFetchMethod;
+} & ExtraOptions;
/**
* Common interface for Usage and Stats Collectors
@@ -170,13 +120,8 @@ export type CollectorOptions<
export interface ICollector {
/** Logger **/
readonly log: Logger;
- /**
- * The options to extend the context provided to the `fetch` method: {@link CollectorOptionsFetchExtendedContext}.
- * @remark Only to be used in very rare scenarios when this is really needed.
- */
- readonly extendFetchContext: CollectorOptionsFetchExtendedContext;
/** The registered type (aka name) of the collector **/
- readonly type: CollectorOptions['type'];
+ readonly type: CollectorOptions['type'];
/**
* The actual logic that reports the Usage collection.
* It will be called on every collection request.
@@ -188,9 +133,9 @@ export interface ICollector {
* [type]: await fetch(context)
* }
*/
- readonly fetch: CollectorFetchMethod;
+ readonly fetch: CollectorFetchMethod;
/**
* Should return `true` when it's safe to call the `fetch` method.
*/
- readonly isReady: CollectorOptions['isReady'];
+ readonly isReady: CollectorOptions['isReady'];
}
diff --git a/src/plugins/usage_collection/server/collector/usage_collector.ts b/src/plugins/usage_collection/server/collector/usage_collector.ts
index 15f7cd9c627fcb..2ed8c2a50dbafd 100644
--- a/src/plugins/usage_collection/server/collector/usage_collector.ts
+++ b/src/plugins/usage_collection/server/collector/usage_collector.ts
@@ -15,10 +15,9 @@ import { Collector } from './collector';
*/
export type UsageCollectorOptions<
TFetchReturn = unknown,
- WithKibanaRequest extends boolean = false,
ExtraOptions extends object = {}
-> = CollectorOptions &
- Required, 'schema'>>;
+> = CollectorOptions &
+ Required, 'schema'>>;
/**
* @private Only used in fixtures as a type
@@ -27,12 +26,7 @@ export class UsageCollector exte
TFetchReturn,
ExtraOptions
> {
- constructor(
- log: Logger,
- // Needed because it doesn't affect on anything here but being explicit creates a lot of pain down the line
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- collectorOptions: UsageCollectorOptions
- ) {
+ constructor(log: Logger, collectorOptions: UsageCollectorOptions) {
super(log, collectorOptions);
}
}
diff --git a/src/plugins/usage_collection/server/index.ts b/src/plugins/usage_collection/server/index.ts
index 74fa77be9843cb..907a61a752052a 100644
--- a/src/plugins/usage_collection/server/index.ts
+++ b/src/plugins/usage_collection/server/index.ts
@@ -17,7 +17,6 @@ export type {
UsageCollectorOptions,
CollectorFetchContext,
CollectorFetchMethod,
- CollectorOptionsFetchExtendedContext,
} from './collector';
export type {
diff --git a/src/plugins/usage_collection/server/mocks.ts b/src/plugins/usage_collection/server/mocks.ts
index 6f7d4f19cbaf12..ac7ad69ed4bce7 100644
--- a/src/plugins/usage_collection/server/mocks.ts
+++ b/src/plugins/usage_collection/server/mocks.ts
@@ -9,7 +9,6 @@
import {
elasticsearchServiceMock,
executionContextServiceMock,
- httpServerMock,
loggingSystemMock,
savedObjectsClientMock,
} from '../../../../src/core/server/mocks';
@@ -45,25 +44,14 @@ export const createUsageCollectionSetupMock = () => {
return usageCollectionSetupMock;
};
-export function createCollectorFetchContextMock(): jest.Mocked> {
- const collectorFetchClientsMock: jest.Mocked> = {
+export function createCollectorFetchContextMock(): jest.Mocked {
+ const collectorFetchClientsMock: jest.Mocked = {
esClient: elasticsearchServiceMock.createClusterClient().asInternalUser,
soClient: savedObjectsClientMock.create(),
};
return collectorFetchClientsMock;
}
-export function createCollectorFetchContextWithKibanaMock(): jest.Mocked<
- CollectorFetchContext
-> {
- const collectorFetchClientsMock: jest.Mocked> = {
- esClient: elasticsearchServiceMock.createClusterClient().asInternalUser,
- soClient: savedObjectsClientMock.create(),
- kibanaRequest: httpServerMock.createKibanaRequest(),
- };
- return collectorFetchClientsMock;
-}
-
export const usageCollectionPluginMock = {
createSetupContract: createUsageCollectionSetupMock,
};
diff --git a/src/plugins/usage_collection/server/plugin.ts b/src/plugins/usage_collection/server/plugin.ts
index f415dd768dc226..7cde8bad706dd1 100644
--- a/src/plugins/usage_collection/server/plugin.ts
+++ b/src/plugins/usage_collection/server/plugin.ts
@@ -15,7 +15,6 @@ import type {
Plugin,
ElasticsearchClient,
SavedObjectsClientContract,
- KibanaRequest,
} from 'src/core/server';
import type { ConfigType } from './config';
import { CollectorSet } from './collector';
@@ -39,12 +38,8 @@ export interface UsageCollectionSetup {
* Creates a usage collector to collect plugin telemetry data.
* registerCollector must be called to connect the created collector with the service.
*/
- makeUsageCollector: <
- TFetchReturn,
- WithKibanaRequest extends boolean = false,
- ExtraOptions extends object = {}
- >(
- options: UsageCollectorOptions
+ makeUsageCollector: (
+ options: UsageCollectorOptions
) => Collector;
/**
* Register a usage collector or a stats collector.
@@ -66,7 +61,6 @@ export interface UsageCollectionSetup {
bulkFetch: (
esClient: ElasticsearchClient,
soClient: SavedObjectsClientContract,
- kibanaRequest: KibanaRequest | undefined, // intentionally `| undefined` to enforce providing the parameter
collectors?: Map>
) => Promise>;
/**
@@ -88,12 +82,8 @@ export interface UsageCollectionSetup {
* registerCollector must be called to connect the created collector with the service.
* @internal: telemetry and monitoring use
*/
- makeStatsCollector: <
- TFetchReturn,
- WithKibanaRequest extends boolean,
- ExtraOptions extends object = {}
- >(
- options: CollectorOptions
+ makeStatsCollector: (
+ options: CollectorOptions
) => Collector;
}
diff --git a/src/plugins/usage_collection/server/routes/stats/stats.ts b/src/plugins/usage_collection/server/routes/stats/stats.ts
index 8e5382d1631721..72cbd2e5899ff5 100644
--- a/src/plugins/usage_collection/server/routes/stats/stats.ts
+++ b/src/plugins/usage_collection/server/routes/stats/stats.ts
@@ -15,7 +15,6 @@ import { first } from 'rxjs/operators';
import {
ElasticsearchClient,
IRouter,
- KibanaRequest,
MetricsServiceSetup,
SavedObjectsClientContract,
ServiceStatus,
@@ -55,10 +54,9 @@ export function registerStatsRoute({
}) {
const getUsage = async (
esClient: ElasticsearchClient,
- savedObjectsClient: SavedObjectsClientContract,
- kibanaRequest: KibanaRequest
+ savedObjectsClient: SavedObjectsClientContract
): Promise => {
- const usage = await collectorSet.bulkFetchUsage(esClient, savedObjectsClient, kibanaRequest);
+ const usage = await collectorSet.bulkFetchUsage(esClient, savedObjectsClient);
return collectorSet.toObject(usage);
};
@@ -97,7 +95,7 @@ export function registerStatsRoute({
const [usage, clusterUuid] = await Promise.all([
shouldGetUsage
- ? getUsage(asCurrentUser, savedObjectsClient, req)
+ ? getUsage(asCurrentUser, savedObjectsClient)
: Promise.resolve({}),
getClusterUuid(asCurrentUser),
]);
diff --git a/src/plugins/vis_default_editor/public/components/agg_params_map.ts b/src/plugins/vis_default_editor/public/components/agg_params_map.ts
index a61df61f2316c6..283e1d7511b750 100644
--- a/src/plugins/vis_default_editor/public/components/agg_params_map.ts
+++ b/src/plugins/vis_default_editor/public/components/agg_params_map.ts
@@ -67,6 +67,11 @@ const metrics = {
sortField: controls.TopSortFieldParamEditor,
sortOrder: controls.OrderParamEditor,
},
+ [METRIC_TYPES.TOP_METRICS]: {
+ field: controls.FieldParamEditor,
+ sortField: controls.TopSortFieldParamEditor,
+ sortOrder: controls.OrderParamEditor,
+ },
[METRIC_TYPES.PERCENTILES]: {
percents: controls.PercentilesEditor,
},
diff --git a/src/plugins/vis_default_editor/public/components/controls/metric_agg.tsx b/src/plugins/vis_default_editor/public/components/controls/metric_agg.tsx
index 1f844b50424746..2888d399bc0148 100644
--- a/src/plugins/vis_default_editor/public/components/controls/metric_agg.tsx
+++ b/src/plugins/vis_default_editor/public/components/controls/metric_agg.tsx
@@ -13,7 +13,14 @@ import { i18n } from '@kbn/i18n';
import { useAvailableOptions, useFallbackMetric, useValidation } from './utils';
import { AggParamEditorProps } from '../agg_param_props';
-const aggFilter = ['!top_hits', '!percentiles', '!percentile_ranks', '!median', '!std_dev'];
+const aggFilter = [
+ '!top_hits',
+ '!top_metrics',
+ '!percentiles',
+ '!percentile_ranks',
+ '!median',
+ '!std_dev',
+];
const EMPTY_VALUE = 'EMPTY_VALUE';
const DEFAULT_OPTIONS = [{ text: '', value: EMPTY_VALUE, hidden: true }];
diff --git a/src/plugins/vis_types/xy/public/config/get_config.ts b/src/plugins/vis_types/xy/public/config/get_config.ts
index d7cf22625e10ed..7aad30c5b743e6 100644
--- a/src/plugins/vis_types/xy/public/config/get_config.ts
+++ b/src/plugins/vis_types/xy/public/config/get_config.ts
@@ -6,7 +6,7 @@
* Side Public License, v 1.
*/
-import { ScaleContinuousType } from '@elastic/charts';
+import { Fit, ScaleContinuousType } from '@elastic/charts';
import { Datatable } from '../../../../expressions/public';
import { BUCKET_TYPES } from '../../../../data/public';
@@ -92,7 +92,7 @@ export function getConfig(
return {
// NOTE: downscale ratio to match current vislib implementation
markSizeRatio: radiusRatio * 0.6,
- fittingFunction,
+ fittingFunction: fittingFunction ?? Fit.Linear,
fillOpacity,
detailedTooltip,
orderBucketsBySum,
diff --git a/src/plugins/vis_types/xy/public/editor/components/options/point_series/elastic_charts_options.tsx b/src/plugins/vis_types/xy/public/editor/components/options/point_series/elastic_charts_options.tsx
index 105cd667990416..1c93fe92b79af7 100644
--- a/src/plugins/vis_types/xy/public/editor/components/options/point_series/elastic_charts_options.tsx
+++ b/src/plugins/vis_types/xy/public/editor/components/options/point_series/elastic_charts_options.tsx
@@ -78,7 +78,7 @@ export function ElasticChartsOptions(props: ValidationVisOptionsProps
})}
options={fittingFunctions}
paramName="fittingFunction"
- value={stateParams.fittingFunction}
+ value={stateParams.fittingFunction ?? fittingFunctions[2].value}
setValue={(paramName, value) => {
if (trackUiMetric) {
trackUiMetric(METRIC_TYPE.CLICK, 'fitting_function_selected');
diff --git a/src/plugins/visualizations/tsconfig.json b/src/plugins/visualizations/tsconfig.json
index 57d21d8719ede3..2bc25cfb3c3463 100644
--- a/src/plugins/visualizations/tsconfig.json
+++ b/src/plugins/visualizations/tsconfig.json
@@ -30,6 +30,7 @@
{ "path": "../home/tsconfig.json" },
{ "path": "../share/tsconfig.json" },
{ "path": "../presentation_util/tsconfig.json" },
+ { "path": "../screenshot_mode/tsconfig.json" },
{ "path": "../../../x-pack/plugins/spaces/tsconfig.json" }
]
}
diff --git a/test/common/services/es_archiver.ts b/test/common/services/es_archiver.ts
index 2ea4b6ce3a4342..3212e809286860 100644
--- a/test/common/services/es_archiver.ts
+++ b/test/common/services/es_archiver.ts
@@ -8,11 +8,12 @@
import { EsArchiver } from '@kbn/es-archiver';
import { FtrProviderContext } from '../ftr_provider_context';
-import * as KibanaServer from './kibana_server';
+import * as KibanaServer from '../../common/services/kibana_server';
export function EsArchiverProvider({ getService }: FtrProviderContext): EsArchiver {
const config = getService('config');
const client = getService('es');
+
const log = getService('log');
const kibanaServer = getService('kibanaServer');
const retry = getService('retry');
diff --git a/test/common/services/security/system_indices_user.ts b/test/common/services/security/system_indices_user.ts
index 2546fbeafffa71..091621207a6710 100644
--- a/test/common/services/security/system_indices_user.ts
+++ b/test/common/services/security/system_indices_user.ts
@@ -6,25 +6,18 @@
* Side Public License, v 1.
*/
-import { systemIndicesSuperuser, createEsClientForFtrConfig } from '@kbn/test';
+import { Client } from '@elastic/elasticsearch';
+import { ToolingLog } from '@kbn/dev-utils';
+import {
+ systemIndicesSuperuser,
+ createEsClientForFtrConfig,
+ createRemoteEsClientForFtrConfig,
+} from '@kbn/test';
import { FtrProviderContext } from '../../ftr_provider_context';
const SYSTEM_INDICES_SUPERUSER_ROLE = 'system_indices_superuser';
-export async function createSystemIndicesUser(ctx: FtrProviderContext) {
- const log = ctx.getService('log');
- const config = ctx.getService('config');
-
- const enabled = !config
- .get('esTestCluster.serverArgs')
- .some((arg: string) => arg === 'xpack.security.enabled=false');
-
- if (!enabled) {
- return;
- }
-
- const es = createEsClientForFtrConfig(config);
-
+async function ensureSystemIndicesUser(es: Client, log: ToolingLog) {
// There are cases where the test config file doesn't have security disabled
// but tests are still executed on ES without security. Checking this case
// by trying to fetch the users list.
@@ -67,3 +60,24 @@ export async function createSystemIndicesUser(ctx: FtrProviderContext) {
await es.close();
}
+
+export async function createSystemIndicesUser(ctx: FtrProviderContext) {
+ const log = ctx.getService('log');
+ const config = ctx.getService('config');
+
+ const enabled = !config
+ .get('esTestCluster.serverArgs')
+ .some((arg: string) => arg === 'xpack.security.enabled=false');
+
+ if (!enabled) {
+ return;
+ }
+
+ const localEs = createEsClientForFtrConfig(config);
+ await ensureSystemIndicesUser(localEs, log);
+
+ if (config.get('esTestCluster.ccs')) {
+ const remoteEs = createRemoteEsClientForFtrConfig(config);
+ await ensureSystemIndicesUser(remoteEs, log);
+ }
+}
diff --git a/test/common/services/security/test_user.ts b/test/common/services/security/test_user.ts
index 7c4751220fa1f1..bc5dbf68698bc0 100644
--- a/test/common/services/security/test_user.ts
+++ b/test/common/services/security/test_user.ts
@@ -90,6 +90,28 @@ export async function createTestUserService(ctx: FtrProviderContext, role: Role,
await role.create(name, definition);
}
+ // when configured to setup remote roles, load the remote es service and set them up directly via es
+ const remoteEsRoles: undefined | Record = config.get('security.remoteEsRoles');
+ if (remoteEsRoles) {
+ let remoteEs;
+ try {
+ remoteEs = ctx.getService('remoteEs' as 'es');
+ } catch (error) {
+ throw new Error(
+ 'unable to load `remoteEs` cluster, which should provide an ES client configured to talk to the remote cluster. Include that service from another FTR config or fix the error it is throwing on creation: ' +
+ error.message
+ );
+ }
+
+ for (const [name, body] of Object.entries(remoteEsRoles)) {
+ log.info(`creating ${name} role on remote cluster`);
+ await remoteEs.security.putRole({
+ name,
+ ...body,
+ });
+ }
+ }
+
// delete the test_user if present (will it error if the user doesn't exist?)
try {
await user.delete(TEST_USER_NAME);
diff --git a/test/functional/apps/management/_create_index_pattern_wizard.js b/test/functional/apps/management/_create_index_pattern_wizard.ts
similarity index 93%
rename from test/functional/apps/management/_create_index_pattern_wizard.js
rename to test/functional/apps/management/_create_index_pattern_wizard.ts
index b2f24e530cb120..cf732e178aa74c 100644
--- a/test/functional/apps/management/_create_index_pattern_wizard.js
+++ b/test/functional/apps/management/_create_index_pattern_wizard.ts
@@ -6,7 +6,9 @@
* Side Public License, v 1.
*/
-export default function ({ getService, getPageObjects }) {
+import { FtrProviderContext } from '../../ftr_provider_context';
+
+export default function ({ getService, getPageObjects }: FtrProviderContext) {
const kibanaServer = getService('kibanaServer');
const testSubjects = getService('testSubjects');
const es = getService('es');
@@ -38,7 +40,7 @@ export default function ({ getService, getPageObjects }) {
body: { actions: [{ add: { index: 'blogs', alias: 'alias1' } }] },
});
- await PageObjects.settings.createIndexPattern('alias1', false);
+ await PageObjects.settings.createIndexPattern('alias1', null);
});
it('can delete an index pattern', async () => {
diff --git a/test/functional/apps/management/_exclude_index_pattern.js b/test/functional/apps/management/_exclude_index_pattern.ts
similarity index 89%
rename from test/functional/apps/management/_exclude_index_pattern.js
rename to test/functional/apps/management/_exclude_index_pattern.ts
index b71222c1ec44d6..8c20acdc21f926 100644
--- a/test/functional/apps/management/_exclude_index_pattern.js
+++ b/test/functional/apps/management/_exclude_index_pattern.ts
@@ -7,8 +7,9 @@
*/
import expect from '@kbn/expect';
+import { FtrProviderContext } from '../../ftr_provider_context';
-export default function ({ getService, getPageObjects }) {
+export default function ({ getService, getPageObjects }: FtrProviderContext) {
const PageObjects = getPageObjects(['settings']);
const es = getService('es');
diff --git a/test/functional/apps/management/_handle_alias.js b/test/functional/apps/management/_handle_alias.ts
similarity index 95%
rename from test/functional/apps/management/_handle_alias.js
rename to test/functional/apps/management/_handle_alias.ts
index 891e59d84a04bc..04496bf9ed7583 100644
--- a/test/functional/apps/management/_handle_alias.js
+++ b/test/functional/apps/management/_handle_alias.ts
@@ -7,8 +7,9 @@
*/
import expect from '@kbn/expect';
+import { FtrProviderContext } from '../../ftr_provider_context';
-export default function ({ getService, getPageObjects }) {
+export default function ({ getService, getPageObjects }: FtrProviderContext) {
const esArchiver = getService('esArchiver');
const es = getService('es');
const retry = getService('retry');
diff --git a/test/functional/apps/management/_handle_version_conflict.js b/test/functional/apps/management/_handle_version_conflict.ts
similarity index 96%
rename from test/functional/apps/management/_handle_version_conflict.js
rename to test/functional/apps/management/_handle_version_conflict.ts
index a04c5d34b2d351..2f65f966c55967 100644
--- a/test/functional/apps/management/_handle_version_conflict.js
+++ b/test/functional/apps/management/_handle_version_conflict.ts
@@ -16,8 +16,9 @@
*/
import expect from '@kbn/expect';
+import { FtrProviderContext } from '../../ftr_provider_context';
-export default function ({ getService, getPageObjects }) {
+export default function ({ getService, getPageObjects }: FtrProviderContext) {
const testSubjects = getService('testSubjects');
const kibanaServer = getService('kibanaServer');
const browser = getService('browser');
@@ -93,7 +94,6 @@ export default function ({ getService, getPageObjects }) {
expect(response.body.result).to.be('updated');
await PageObjects.settings.controlChangeSave();
await retry.try(async function () {
- //await PageObjects.common.sleep(2000);
const message = await PageObjects.common.closeToast();
expect(message).to.contain('Unable');
});
diff --git a/test/functional/apps/management/_index_pattern_create_delete.js b/test/functional/apps/management/_index_pattern_create_delete.ts
similarity index 91%
rename from test/functional/apps/management/_index_pattern_create_delete.js
rename to test/functional/apps/management/_index_pattern_create_delete.ts
index 4c9f5a5210ac68..6b2036499a1edd 100644
--- a/test/functional/apps/management/_index_pattern_create_delete.js
+++ b/test/functional/apps/management/_index_pattern_create_delete.ts
@@ -7,8 +7,9 @@
*/
import expect from '@kbn/expect';
+import { FtrProviderContext } from '../../ftr_provider_context';
-export default function ({ getService, getPageObjects }) {
+export default function ({ getService, getPageObjects }: FtrProviderContext) {
const esArchiver = getService('esArchiver');
const kibanaServer = getService('kibanaServer');
const browser = getService('browser');
@@ -35,8 +36,7 @@ export default function ({ getService, getPageObjects }) {
});
});
- // FLAKY: https://github.com/elastic/kibana/issues/124663
- describe.skip('validation', function () {
+ describe('validation', function () {
it('can display errors', async function () {
await PageObjects.settings.clickAddNewIndexPatternButton();
await PageObjects.settings.setIndexPatternField('log-fake*');
@@ -46,7 +46,7 @@ export default function ({ getService, getPageObjects }) {
it('can resolve errors and submit', async function () {
await PageObjects.settings.setIndexPatternField('log*');
- await (await PageObjects.settings.getSaveIndexPatternButton()).click();
+ await (await PageObjects.settings.getSaveDataViewButtonActive()).click();
await PageObjects.settings.removeIndexPattern();
});
});
@@ -72,10 +72,12 @@ export default function ({ getService, getPageObjects }) {
});
describe('index pattern creation', function indexPatternCreation() {
- let indexPatternId;
+ let indexPatternId: string;
before(function () {
- return PageObjects.settings.createIndexPattern().then((id) => (indexPatternId = id));
+ return PageObjects.settings
+ .createIndexPattern('logstash-*')
+ .then((id) => (indexPatternId = id));
});
it('should have index pattern in page header', async function () {
diff --git a/test/functional/apps/management/_index_pattern_filter.js b/test/functional/apps/management/_index_pattern_filter.ts
similarity index 90%
rename from test/functional/apps/management/_index_pattern_filter.js
rename to test/functional/apps/management/_index_pattern_filter.ts
index 3e9d316b59c618..afa64c474d39d4 100644
--- a/test/functional/apps/management/_index_pattern_filter.js
+++ b/test/functional/apps/management/_index_pattern_filter.ts
@@ -7,8 +7,9 @@
*/
import expect from '@kbn/expect';
+import { FtrProviderContext } from '../../ftr_provider_context';
-export default function ({ getService, getPageObjects }) {
+export default function ({ getService, getPageObjects }: FtrProviderContext) {
const kibanaServer = getService('kibanaServer');
const retry = getService('retry');
const PageObjects = getPageObjects(['settings']);
@@ -23,7 +24,7 @@ export default function ({ getService, getPageObjects }) {
});
beforeEach(async function () {
- await PageObjects.settings.createIndexPattern();
+ await PageObjects.settings.createIndexPattern('logstash-*');
});
afterEach(async function () {
diff --git a/test/functional/apps/management/_index_pattern_popularity.js b/test/functional/apps/management/_index_pattern_popularity.ts
similarity index 92%
rename from test/functional/apps/management/_index_pattern_popularity.js
rename to test/functional/apps/management/_index_pattern_popularity.ts
index 1a71e4c5fbc68b..bff6cdce0f7a69 100644
--- a/test/functional/apps/management/_index_pattern_popularity.js
+++ b/test/functional/apps/management/_index_pattern_popularity.ts
@@ -7,8 +7,9 @@
*/
import expect from '@kbn/expect';
+import { FtrProviderContext } from '../../ftr_provider_context';
-export default function ({ getService, getPageObjects }) {
+export default function ({ getService, getPageObjects }: FtrProviderContext) {
const kibanaServer = getService('kibanaServer');
const testSubjects = getService('testSubjects');
const log = getService('log');
@@ -23,7 +24,7 @@ export default function ({ getService, getPageObjects }) {
});
beforeEach(async () => {
- await PageObjects.settings.createIndexPattern();
+ await PageObjects.settings.createIndexPattern('logstash-*');
// increase Popularity of geo.coordinates
log.debug('Starting openControlsByName (' + fieldName + ')');
await PageObjects.settings.openControlsByName(fieldName);
diff --git a/test/functional/apps/management/_index_pattern_results_sort.js b/test/functional/apps/management/_index_pattern_results_sort.ts
similarity index 90%
rename from test/functional/apps/management/_index_pattern_results_sort.js
rename to test/functional/apps/management/_index_pattern_results_sort.ts
index cedf5ee355b36a..305a72889e95ab 100644
--- a/test/functional/apps/management/_index_pattern_results_sort.js
+++ b/test/functional/apps/management/_index_pattern_results_sort.ts
@@ -7,8 +7,9 @@
*/
import expect from '@kbn/expect';
+import { FtrProviderContext } from '../../ftr_provider_context';
-export default function ({ getService, getPageObjects }) {
+export default function ({ getService, getPageObjects }: FtrProviderContext) {
const kibanaServer = getService('kibanaServer');
const retry = getService('retry');
const PageObjects = getPageObjects(['settings', 'common']);
@@ -18,7 +19,7 @@ export default function ({ getService, getPageObjects }) {
// delete .kibana index and then wait for Kibana to re-create it
await kibanaServer.uiSettings.replace({});
await PageObjects.settings.navigateTo();
- await PageObjects.settings.createIndexPattern();
+ await PageObjects.settings.createIndexPattern('logstash-*');
});
after(async function () {
@@ -30,7 +31,7 @@ export default function ({ getService, getPageObjects }) {
heading: 'Name',
first: '@message',
last: 'xss.raw',
- selector: async function () {
+ async selector() {
const tableRow = await PageObjects.settings.getTableRow(0, 0);
return await tableRow.getVisibleText();
},
@@ -39,7 +40,7 @@ export default function ({ getService, getPageObjects }) {
heading: 'Type',
first: '',
last: 'text',
- selector: async function () {
+ async selector() {
const tableRow = await PageObjects.settings.getTableRow(0, 1);
return await tableRow.getVisibleText();
},
@@ -49,7 +50,6 @@ export default function ({ getService, getPageObjects }) {
columns.forEach(function (col) {
describe('sort by heading - ' + col.heading, function indexPatternCreation() {
it('should sort ascending', async function () {
- console.log('col.heading', col.heading);
if (col.heading !== 'Name') {
await PageObjects.settings.sortBy(col.heading);
}
diff --git a/test/functional/apps/management/_kibana_settings.js b/test/functional/apps/management/_kibana_settings.ts
similarity index 96%
rename from test/functional/apps/management/_kibana_settings.js
rename to test/functional/apps/management/_kibana_settings.ts
index cfe4e88cda21de..d459643849fbc0 100644
--- a/test/functional/apps/management/_kibana_settings.js
+++ b/test/functional/apps/management/_kibana_settings.ts
@@ -7,8 +7,9 @@
*/
import expect from '@kbn/expect';
+import { FtrProviderContext } from '../../ftr_provider_context';
-export default function ({ getService, getPageObjects }) {
+export default function ({ getService, getPageObjects }: FtrProviderContext) {
const kibanaServer = getService('kibanaServer');
const browser = getService('browser');
const PageObjects = getPageObjects(['settings', 'common', 'dashboard', 'timePicker', 'header']);
diff --git a/test/functional/apps/management/_mgmt_import_saved_objects.js b/test/functional/apps/management/_mgmt_import_saved_objects.ts
similarity index 80%
rename from test/functional/apps/management/_mgmt_import_saved_objects.js
rename to test/functional/apps/management/_mgmt_import_saved_objects.ts
index 95b0bbb7ed03b3..04a1bb59383223 100644
--- a/test/functional/apps/management/_mgmt_import_saved_objects.js
+++ b/test/functional/apps/management/_mgmt_import_saved_objects.ts
@@ -8,13 +8,14 @@
import expect from '@kbn/expect';
import path from 'path';
+import { FtrProviderContext } from '../../ftr_provider_context';
-export default function ({ getService, getPageObjects }) {
+export default function ({ getService, getPageObjects }: FtrProviderContext) {
const kibanaServer = getService('kibanaServer');
const PageObjects = getPageObjects(['common', 'settings', 'header', 'savedObjects']);
- //in 6.4.0 bug the Saved Search conflict would be resolved and get imported but the visualization
- //that referenced the saved search was not imported.( https://github.com/elastic/kibana/issues/22238)
+ // in 6.4.0 bug the Saved Search conflict would be resolved and get imported but the visualization
+ // that referenced the saved search was not imported.( https://github.com/elastic/kibana/issues/22238)
describe('mgmt saved objects', function describeIndexTests() {
before(async () => {
@@ -41,7 +42,7 @@ export default function ({ getService, getPageObjects }) {
await PageObjects.savedObjects.waitTableIsLoaded();
await PageObjects.savedObjects.searchForObject('mysaved');
- //instead of asserting on count- am asserting on the titles- which is more accurate than count.
+ // instead of asserting on count- am asserting on the titles- which is more accurate than count.
const objects = await PageObjects.savedObjects.getRowTitles();
expect(objects.includes('mysavedsearch')).to.be(true);
expect(objects.includes('mysavedviz')).to.be(true);
diff --git a/test/functional/apps/management/_runtime_fields.js b/test/functional/apps/management/_runtime_fields.ts
similarity index 91%
rename from test/functional/apps/management/_runtime_fields.js
rename to test/functional/apps/management/_runtime_fields.ts
index 3a70df81b55d96..8ec9fb92c58eae 100644
--- a/test/functional/apps/management/_runtime_fields.js
+++ b/test/functional/apps/management/_runtime_fields.ts
@@ -7,8 +7,9 @@
*/
import expect from '@kbn/expect';
+import { FtrProviderContext } from '../../ftr_provider_context';
-export default function ({ getService, getPageObjects }) {
+export default function ({ getService, getPageObjects }: FtrProviderContext) {
const kibanaServer = getService('kibanaServer');
const log = getService('log');
const browser = getService('browser');
@@ -36,7 +37,7 @@ export default function ({ getService, getPageObjects }) {
await PageObjects.settings.navigateTo();
await PageObjects.settings.clickKibanaIndexPatterns();
await PageObjects.settings.clickIndexPatternLogstash();
- const startingCount = parseInt(await PageObjects.settings.getFieldsTabCount());
+ const startingCount = parseInt(await PageObjects.settings.getFieldsTabCount(), 10);
await log.debug('add runtime field');
await PageObjects.settings.addRuntimeField(
fieldName,
@@ -51,7 +52,9 @@ export default function ({ getService, getPageObjects }) {
await PageObjects.settings.clickSaveField();
await retry.try(async function () {
- expect(parseInt(await PageObjects.settings.getFieldsTabCount())).to.be(startingCount + 1);
+ expect(parseInt(await PageObjects.settings.getFieldsTabCount(), 10)).to.be(
+ startingCount + 1
+ );
});
});
diff --git a/test/functional/apps/management/_scripted_fields.js b/test/functional/apps/management/_scripted_fields.ts
similarity index 96%
rename from test/functional/apps/management/_scripted_fields.js
rename to test/functional/apps/management/_scripted_fields.ts
index 72f45e1fedb4db..c8c605ec7ed19e 100644
--- a/test/functional/apps/management/_scripted_fields.js
+++ b/test/functional/apps/management/_scripted_fields.ts
@@ -23,8 +23,9 @@
// it will automatically insert a a closing square brace ], etc.
import expect from '@kbn/expect';
+import { FtrProviderContext } from '../../ftr_provider_context';
-export default function ({ getService, getPageObjects }) {
+export default function ({ getService, getPageObjects }: FtrProviderContext) {
const kibanaServer = getService('kibanaServer');
const log = getService('log');
const browser = getService('browser');
@@ -77,7 +78,7 @@ export default function ({ getService, getPageObjects }) {
await PageObjects.settings.navigateTo();
await PageObjects.settings.clickKibanaIndexPatterns();
await PageObjects.settings.clickIndexPatternLogstash();
- const startingCount = parseInt(await PageObjects.settings.getScriptedFieldsTabCount());
+ const startingCount = parseInt(await PageObjects.settings.getScriptedFieldsTabCount(), 10);
await PageObjects.settings.clickScriptedFieldsTab();
await log.debug('add scripted field');
const script = `1`;
@@ -90,7 +91,7 @@ export default function ({ getService, getPageObjects }) {
script
);
await retry.try(async function () {
- expect(parseInt(await PageObjects.settings.getScriptedFieldsTabCount())).to.be(
+ expect(parseInt(await PageObjects.settings.getScriptedFieldsTabCount(), 10)).to.be(
startingCount + 1
);
});
@@ -111,7 +112,7 @@ export default function ({ getService, getPageObjects }) {
await PageObjects.settings.navigateTo();
await PageObjects.settings.clickKibanaIndexPatterns();
await PageObjects.settings.clickIndexPatternLogstash();
- const startingCount = parseInt(await PageObjects.settings.getScriptedFieldsTabCount());
+ const startingCount = parseInt(await PageObjects.settings.getScriptedFieldsTabCount(), 10);
await PageObjects.settings.clickScriptedFieldsTab();
await log.debug('add scripted field');
const script = `if (doc['machine.ram'].size() == 0) return -1;
@@ -126,7 +127,7 @@ export default function ({ getService, getPageObjects }) {
script
);
await retry.try(async function () {
- expect(parseInt(await PageObjects.settings.getScriptedFieldsTabCount())).to.be(
+ expect(parseInt(await PageObjects.settings.getScriptedFieldsTabCount(), 10)).to.be(
startingCount + 1
);
});
@@ -150,7 +151,7 @@ export default function ({ getService, getPageObjects }) {
});
});
- //add a test to sort numeric scripted field
+ // add a test to sort numeric scripted field
it('should sort scripted field value in Discover', async function () {
await testSubjects.click(`docTableHeaderFieldSort_${scriptedPainlessFieldName}`);
// after the first click on the scripted field, it becomes secondary sort after time.
@@ -201,7 +202,7 @@ export default function ({ getService, getPageObjects }) {
await PageObjects.settings.navigateTo();
await PageObjects.settings.clickKibanaIndexPatterns();
await PageObjects.settings.clickIndexPatternLogstash();
- const startingCount = parseInt(await PageObjects.settings.getScriptedFieldsTabCount());
+ const startingCount = parseInt(await PageObjects.settings.getScriptedFieldsTabCount(), 10);
await PageObjects.settings.clickScriptedFieldsTab();
await log.debug('add scripted field');
await PageObjects.settings.addScriptedField(
@@ -213,7 +214,7 @@ export default function ({ getService, getPageObjects }) {
"if (doc['response.raw'].value == '200') { return 'good'} else { return 'bad'}"
);
await retry.try(async function () {
- expect(parseInt(await PageObjects.settings.getScriptedFieldsTabCount())).to.be(
+ expect(parseInt(await PageObjects.settings.getScriptedFieldsTabCount(), 10)).to.be(
startingCount + 1
);
});
@@ -237,7 +238,7 @@ export default function ({ getService, getPageObjects }) {
});
});
- //add a test to sort string scripted field
+ // add a test to sort string scripted field
it('should sort scripted field value in Discover', async function () {
await testSubjects.click(`docTableHeaderFieldSort_${scriptedPainlessFieldName2}`);
// after the first click on the scripted field, it becomes secondary sort after time.
@@ -287,7 +288,7 @@ export default function ({ getService, getPageObjects }) {
await PageObjects.settings.navigateTo();
await PageObjects.settings.clickKibanaIndexPatterns();
await PageObjects.settings.clickIndexPatternLogstash();
- const startingCount = parseInt(await PageObjects.settings.getScriptedFieldsTabCount());
+ const startingCount = parseInt(await PageObjects.settings.getScriptedFieldsTabCount(), 10);
await PageObjects.settings.clickScriptedFieldsTab();
await log.debug('add scripted field');
await PageObjects.settings.addScriptedField(
@@ -299,7 +300,7 @@ export default function ({ getService, getPageObjects }) {
"doc['response.raw'].value == '200'"
);
await retry.try(async function () {
- expect(parseInt(await PageObjects.settings.getScriptedFieldsTabCount())).to.be(
+ expect(parseInt(await PageObjects.settings.getScriptedFieldsTabCount(), 10)).to.be(
startingCount + 1
);
});
@@ -335,8 +336,8 @@ export default function ({ getService, getPageObjects }) {
await filterBar.removeAllFilters();
});
- //add a test to sort boolean
- //existing bug: https://github.com/elastic/kibana/issues/75519 hence the issue is skipped.
+ // add a test to sort boolean
+ // existing bug: https://github.com/elastic/kibana/issues/75519 hence the issue is skipped.
it.skip('should sort scripted field value in Discover', async function () {
await testSubjects.click(`docTableHeaderFieldSort_${scriptedPainlessFieldName2}`);
// after the first click on the scripted field, it becomes secondary sort after time.
@@ -374,7 +375,7 @@ export default function ({ getService, getPageObjects }) {
await PageObjects.settings.navigateTo();
await PageObjects.settings.clickKibanaIndexPatterns();
await PageObjects.settings.clickIndexPatternLogstash();
- const startingCount = parseInt(await PageObjects.settings.getScriptedFieldsTabCount());
+ const startingCount = parseInt(await PageObjects.settings.getScriptedFieldsTabCount(), 10);
await PageObjects.settings.clickScriptedFieldsTab();
await log.debug('add scripted field');
await PageObjects.settings.addScriptedField(
@@ -386,7 +387,7 @@ export default function ({ getService, getPageObjects }) {
"doc['utc_time'].value.toEpochMilli() + (1000) * 60 * 60"
);
await retry.try(async function () {
- expect(parseInt(await PageObjects.settings.getScriptedFieldsTabCount())).to.be(
+ expect(parseInt(await PageObjects.settings.getScriptedFieldsTabCount(), 10)).to.be(
startingCount + 1
);
});
@@ -410,8 +411,8 @@ export default function ({ getService, getPageObjects }) {
});
});
- //add a test to sort date scripted field
- //https://github.com/elastic/kibana/issues/75711
+ // add a test to sort date scripted field
+ // https://github.com/elastic/kibana/issues/75711
it.skip('should sort scripted field value in Discover', async function () {
await testSubjects.click(`docTableHeaderFieldSort_${scriptedPainlessFieldName2}`);
// after the first click on the scripted field, it becomes secondary sort after time.
diff --git a/test/functional/apps/management/_scripted_fields_filter.js b/test/functional/apps/management/_scripted_fields_filter.ts
similarity index 92%
rename from test/functional/apps/management/_scripted_fields_filter.js
rename to test/functional/apps/management/_scripted_fields_filter.ts
index abae9a300994dc..4f6d1a41d05237 100644
--- a/test/functional/apps/management/_scripted_fields_filter.js
+++ b/test/functional/apps/management/_scripted_fields_filter.ts
@@ -7,8 +7,9 @@
*/
import expect from '@kbn/expect';
+import { FtrProviderContext } from '../../ftr_provider_context';
-export default function ({ getService, getPageObjects }) {
+export default function ({ getService, getPageObjects }: FtrProviderContext) {
const kibanaServer = getService('kibanaServer');
const retry = getService('retry');
const log = getService('log');
@@ -16,7 +17,8 @@ export default function ({ getService, getPageObjects }) {
const esArchiver = getService('esArchiver');
const PageObjects = getPageObjects(['settings']);
- describe('filter scripted fields', function describeIndexTests() {
+ // FLAKY: https://github.com/elastic/kibana/issues/126027
+ describe.skip('filter scripted fields', function describeIndexTests() {
before(async function () {
// delete .kibana index and then wait for Kibana to re-create it
await browser.setWindowSize(1200, 800);
diff --git a/test/functional/apps/management/_scripted_fields_preview.js b/test/functional/apps/management/_scripted_fields_preview.ts
similarity index 90%
rename from test/functional/apps/management/_scripted_fields_preview.js
rename to test/functional/apps/management/_scripted_fields_preview.ts
index b6c941fe21d0ac..380b4659c0f38f 100644
--- a/test/functional/apps/management/_scripted_fields_preview.js
+++ b/test/functional/apps/management/_scripted_fields_preview.ts
@@ -7,13 +7,14 @@
*/
import expect from '@kbn/expect';
+import { FtrProviderContext } from '../../ftr_provider_context';
-export default function ({ getService, getPageObjects }) {
+export default function ({ getService, getPageObjects }: FtrProviderContext) {
const browser = getService('browser');
const PageObjects = getPageObjects(['settings']);
const SCRIPTED_FIELD_NAME = 'myScriptedField';
- const scriptResultToJson = (scriptResult) => {
+ const scriptResultToJson = (scriptResult: string) => {
try {
return JSON.parse(scriptResult);
} catch (e) {
@@ -26,7 +27,7 @@ export default function ({ getService, getPageObjects }) {
await browser.setWindowSize(1200, 800);
await PageObjects.settings.navigateTo();
await PageObjects.settings.clickKibanaIndexPatterns();
- await PageObjects.settings.createIndexPattern();
+ await PageObjects.settings.createIndexPattern('logstash-*');
await PageObjects.settings.navigateTo();
await PageObjects.settings.clickKibanaIndexPatterns();
@@ -67,7 +68,7 @@ export default function ({ getService, getPageObjects }) {
it('should display additional fields', async function () {
const scriptResults = await PageObjects.settings.executeScriptedField(
`doc['bytes'].value * 2`,
- ['bytes']
+ 'bytes'
);
const [{ _id, bytes }] = scriptResultToJson(scriptResults);
expect(_id).to.be.a('string');
diff --git a/test/functional/apps/management/_test_huge_fields.js b/test/functional/apps/management/_test_huge_fields.ts
similarity index 90%
rename from test/functional/apps/management/_test_huge_fields.js
rename to test/functional/apps/management/_test_huge_fields.ts
index 7b756839409286..abc338cb8abc82 100644
--- a/test/functional/apps/management/_test_huge_fields.js
+++ b/test/functional/apps/management/_test_huge_fields.ts
@@ -7,8 +7,9 @@
*/
import expect from '@kbn/expect';
+import { FtrProviderContext } from '../../ftr_provider_context';
-export default function ({ getService, getPageObjects }) {
+export default function ({ getService, getPageObjects }: FtrProviderContext) {
const esArchiver = getService('esArchiver');
const security = getService('security');
const PageObjects = getPageObjects(['common', 'home', 'settings']);
@@ -19,7 +20,7 @@ export default function ({ getService, getPageObjects }) {
const EXPECTED_FIELD_COUNT = '10006';
before(async function () {
- await security.testUser.setRoles(['kibana_admin', 'test_testhuge_reader'], false);
+ await security.testUser.setRoles(['kibana_admin', 'test_testhuge_reader']);
await esArchiver.emptyKibanaIndex();
await esArchiver.loadIfNeeded('test/functional/fixtures/es_archiver/large_fields');
await PageObjects.settings.navigateTo();
diff --git a/test/functional/fixtures/kbn_archiver/date_nested_ccs.json b/test/functional/fixtures/kbn_archiver/date_nested_ccs.json
index 933b21d920c00b..9a411ba96705a9 100644
--- a/test/functional/fixtures/kbn_archiver/date_nested_ccs.json
+++ b/test/functional/fixtures/kbn_archiver/date_nested_ccs.json
@@ -2,10 +2,10 @@
"attributes": {
"fields": "[]",
"timeFieldName": "nested.timestamp",
- "title": "remote:date-nested"
+ "title": "ftr-remote:date-nested"
},
"coreMigrationVersion": "8.2.0",
- "id": "remote:date-nested",
+ "id": "ftr-remote:date-nested",
"migrationVersion": {
"index-pattern": "8.0.0"
},
diff --git a/test/functional/fixtures/kbn_archiver/discover_ccs.json b/test/functional/fixtures/kbn_archiver/discover_ccs.json
index d53aa1bc759afb..4c1143ed4e798f 100644
--- a/test/functional/fixtures/kbn_archiver/discover_ccs.json
+++ b/test/functional/fixtures/kbn_archiver/discover_ccs.json
@@ -3,10 +3,10 @@
"fieldAttrs": "{\"referer\":{\"customLabel\":\"Referer custom\"}}",
"fields": "[{\"name\":\"@message\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"@message.raw\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"@message\"}}},{\"name\":\"@tags\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"@tags.raw\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"@tags\"}}},{\"name\":\"@timestamp\",\"type\":\"date\",\"esTypes\":[\"date\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"_id\",\"type\":\"string\",\"esTypes\":[\"_id\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"_index\",\"type\":\"string\",\"esTypes\":[\"_index\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"_score\",\"type\":\"number\",\"searchable\":false,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"_source\",\"type\":\"_source\",\"esTypes\":[\"_source\"],\"searchable\":false,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"_type\",\"type\":\"string\",\"searchable\":false,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"agent\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"agent.raw\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"agent\"}}},{\"name\":\"bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"clientip\",\"type\":\"ip\",\"esTypes\":[\"ip\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"extension\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"extension.raw\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"extension\"}}},{\"name\":\"geo.coordinates\",\"type\":\"geo_point\",\"esTypes\":[\"geo_point\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"geo.dest\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"geo.src\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"geo.srcdest\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"headings\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"headings.raw\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"headings\"}}},{\"name\":\"host\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"host.raw\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"host\"}}},{\"name\":\"id\",\"type\":\"number\",\"esTypes\":[\"integer\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"index\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"index.raw\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"index\"}}},{\"name\":\"ip\",\"type\":\"ip\",\"esTypes\":[\"ip\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"links\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"links.raw\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"links\"}}},{\"name\":\"machine.os\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"machine.os.raw\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"machine.os\"}}},{\"name\":\"machine.ram\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"memory\",\"type\":\"number\",\"esTypes\":[\"double\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"meta.char\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"meta.related\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"meta.user.firstname\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"meta.user.lastname\",\"type\":\"number\",\"esTypes\":[\"integer\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"nestedField.child\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"nested\":{\"path\":\"nestedField\"}}},{\"name\":\"phpmemory\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"referer\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.article:modified_time\",\"type\":\"date\",\"esTypes\":[\"date\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.article:published_time\",\"type\":\"date\",\"esTypes\":[\"date\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.article:section\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.article:section.raw\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"relatedContent.article:section\"}}},{\"name\":\"relatedContent.article:tag\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.article:tag.raw\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"relatedContent.article:tag\"}}},{\"name\":\"relatedContent.og:description\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:description.raw\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"relatedContent.og:description\"}}},{\"name\":\"relatedContent.og:image\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:image.raw\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"relatedContent.og:image\"}}},{\"name\":\"relatedContent.og:image:height\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:image:height.raw\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"relatedContent.og:image:height\"}}},{\"name\":\"relatedContent.og:image:width\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:image:width.raw\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"relatedContent.og:image:width\"}}},{\"name\":\"relatedContent.og:site_name\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:site_name.raw\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"relatedContent.og:site_name\"}}},{\"name\":\"relatedContent.og:title\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:title.raw\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"relatedContent.og:title\"}}},{\"name\":\"relatedContent.og:type\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:type.raw\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"relatedContent.og:type\"}}},{\"name\":\"relatedContent.og:url\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:url.raw\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"relatedContent.og:url\"}}},{\"name\":\"relatedContent.twitter:card\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.twitter:card.raw\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"relatedContent.twitter:card\"}}},{\"name\":\"relatedContent.twitter:description\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.twitter:description.raw\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"relatedContent.twitter:description\"}}},{\"name\":\"relatedContent.twitter:image\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.twitter:image.raw\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"relatedContent.twitter:image\"}}},{\"name\":\"relatedContent.twitter:site\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.twitter:site.raw\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"relatedContent.twitter:site\"}}},{\"name\":\"relatedContent.twitter:title\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.twitter:title.raw\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"relatedContent.twitter:title\"}}},{\"name\":\"relatedContent.url\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.url.raw\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"relatedContent.url\"}}},{\"name\":\"request\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"request.raw\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"request\"}}},{\"name\":\"response\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"response.raw\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"response\"}}},{\"name\":\"spaces\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"spaces.raw\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"spaces\"}}},{\"name\":\"type\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"url\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"url.raw\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"url\"}}},{\"name\":\"utc_time\",\"type\":\"date\",\"esTypes\":[\"date\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"xss\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"xss.raw\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"xss\"}}}]",
"timeFieldName": "@timestamp",
- "title": "remote:logstash-*"
+ "title": "ftr-remote:logstash-*"
},
"coreMigrationVersion": "8.0.0",
- "id": "remote:logstash-*",
+ "id": "ftr-remote:logstash-*",
"migrationVersion": {
"index-pattern": "7.11.0"
},
@@ -41,7 +41,7 @@
},
"references": [
{
- "id": "remote:logstash-*",
+ "id": "ftr-remote:logstash-*",
"name": "kibanaSavedObjectMeta.searchSourceJSON.index",
"type": "index-pattern"
}
diff --git a/test/functional/page_objects/settings_page.ts b/test/functional/page_objects/settings_page.ts
index 70cdbea7fa8970..98fdff82e13c55 100644
--- a/test/functional/page_objects/settings_page.ts
+++ b/test/functional/page_objects/settings_page.ts
@@ -164,6 +164,19 @@ export class SettingsPageObject extends FtrService {
return await this.testSubjects.find('saveIndexPatternButton');
}
+ async getSaveDataViewButtonActive() {
+ await this.retry.try(async () => {
+ expect(
+ (
+ await this.find.allByCssSelector(
+ '[data-test-subj="saveIndexPatternButton"]:not(.euiButton-isDisabled)'
+ )
+ ).length
+ ).to.be(1);
+ });
+ return await this.testSubjects.find('saveIndexPatternButton');
+ }
+
async getCreateButton() {
return await this.find.displayedByCssSelector('[type="submit"]');
}
@@ -550,7 +563,7 @@ export class SettingsPageObject extends FtrService {
name: string,
language: string,
type: string,
- format: Record,
+ format: Record | null,
popularity: string,
script: string
) {
@@ -790,7 +803,7 @@ export class SettingsPageObject extends FtrService {
await this.flyout.ensureClosed('scriptedFieldsHelpFlyout');
}
- async executeScriptedField(script: string, additionalField: string) {
+ async executeScriptedField(script: string, additionalField?: string) {
this.log.debug('execute Scripted Fields help');
await this.closeScriptedFieldHelp(); // ensure script help is closed so script input is not blocked
await this.setScriptedFieldScript(script);
@@ -801,7 +814,7 @@ export class SettingsPageObject extends FtrService {
await this.testSubjects.click('runScriptButton');
await this.testSubjects.waitForDeleted('.euiLoadingSpinner');
}
- let scriptResults;
+ let scriptResults: string = '';
await this.retry.try(async () => {
scriptResults = await this.testSubjects.getVisibleText('scriptedFieldPreview');
});
diff --git a/test/functional_ccs/apps/discover/_data_view_ccs.ts b/test/functional_ccs/apps/discover/data_view_ccs.ts
similarity index 80%
rename from test/functional_ccs/apps/discover/_data_view_ccs.ts
rename to test/functional_ccs/apps/discover/data_view_ccs.ts
index 91d9cb2faf6816..44258b9cbadd63 100644
--- a/test/functional_ccs/apps/discover/_data_view_ccs.ts
+++ b/test/functional_ccs/apps/discover/data_view_ccs.ts
@@ -6,13 +6,13 @@
* Side Public License, v 1.
*/
-import { FtrProviderContext } from './ftr_provider_context';
+import { FtrProviderContext } from '../../ftr_provider_context';
export default function ({ getService, getPageObjects }: FtrProviderContext) {
const retry = getService('retry');
const testSubjects = getService('testSubjects');
const kibanaServer = getService('kibanaServer');
- const esArchiver = getService('esArchiver');
+ const remoteEsArchiver = getService('remoteEsArchiver');
const security = getService('security');
const PageObjects = getPageObjects(['common', 'discover', 'header', 'timePicker']);
@@ -27,10 +27,17 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
await testSubjects.click('saveIndexPatternButton');
};
- describe('discover integration with data view editor', function describeIndexTests() {
+ // FLAKY: https://github.com/elastic/kibana/issues/126658
+ describe.skip('discover integration with data view editor', function describeIndexTests() {
before(async function () {
- await security.testUser.setRoles(['kibana_admin', 'test_logstash_reader']);
- await esArchiver.loadIfNeeded('test/functional/fixtures/es_archiver/logstash_functional');
+ await security.testUser.setRoles([
+ 'kibana_admin',
+ 'test_logstash_reader',
+ 'ccs_remote_search',
+ ]);
+ await remoteEsArchiver.loadIfNeeded(
+ 'test/functional/fixtures/es_archiver/logstash_functional'
+ );
await kibanaServer.savedObjects.clean({ types: ['saved-search', 'index-pattern'] });
await kibanaServer.importExport.load('test/functional/fixtures/kbn_archiver/discover');
await PageObjects.timePicker.setDefaultAbsoluteRangeViaUiSettings();
@@ -44,7 +51,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
});
it('use ccs to create a new data view', async function () {
- const dataViewToCreate = 'remote:logstash';
+ const dataViewToCreate = 'ftr-remote:logstash';
await createDataView(dataViewToCreate);
await PageObjects.header.waitUntilLoadingHasFinished();
await retry.waitForWithTimeout(
diff --git a/test/functional_ccs/apps/discover/index.ts b/test/functional_ccs/apps/discover/index.ts
index 629423b1b75aa4..2e9d428f44c601 100644
--- a/test/functional_ccs/apps/discover/index.ts
+++ b/test/functional_ccs/apps/discover/index.ts
@@ -6,38 +6,24 @@
* Side Public License, v 1.
*/
-import { FtrProviderContext } from './ftr_provider_context';
+import { FtrProviderContext } from '../../ftr_provider_context';
export default function ({ getService, loadTestFile }: FtrProviderContext) {
const esArchiver = getService('esArchiver');
const browser = getService('browser');
- const esClient = getService('es');
describe('discover app css', function () {
this.tags('ciGroup6');
- before(async function () {
- await esClient.cluster.putSettings({
- persistent: {
- cluster: {
- remote: {
- remote: {
- skip_unavailable: 'true',
- seeds: ['localhost:9300'],
- },
- },
- },
- },
- });
- return browser.setWindowSize(1300, 800);
+ before(async () => {
+ await browser.setWindowSize(1300, 800);
});
- after(function unloadMakelogs() {
- // Make sure to clean up the cluster setting from the before above.
- return esArchiver.unload('test/functional/fixtures/es_archiver/logstash_functional');
- });
+ loadTestFile(require.resolve('./data_view_ccs'));
+ loadTestFile(require.resolve('./saved_queries_ccs'));
- loadTestFile(require.resolve('./_data_view_ccs'));
- loadTestFile(require.resolve('./_saved_queries_ccs'));
+ after(async () => {
+ await esArchiver.unload('test/functional/fixtures/es_archiver/logstash_functional');
+ });
});
}
diff --git a/test/functional_ccs/apps/discover/_saved_queries_ccs.ts b/test/functional_ccs/apps/discover/saved_queries_ccs.ts
similarity index 93%
rename from test/functional_ccs/apps/discover/_saved_queries_ccs.ts
rename to test/functional_ccs/apps/discover/saved_queries_ccs.ts
index 325f279ff28ab7..08b6d61368f5d4 100644
--- a/test/functional_ccs/apps/discover/_saved_queries_ccs.ts
+++ b/test/functional_ccs/apps/discover/saved_queries_ccs.ts
@@ -8,12 +8,12 @@
import expect from '@kbn/expect';
-import { FtrProviderContext } from './ftr_provider_context';
+import { FtrProviderContext } from '../../ftr_provider_context';
export default function ({ getService, getPageObjects }: FtrProviderContext) {
const retry = getService('retry');
const log = getService('log');
- const esArchiver = getService('esArchiver');
+ const remoteEsArchiver = getService('remoteEsArchiver');
const kibanaServer = getService('kibanaServer');
const PageObjects = getPageObjects(['common', 'discover', 'timePicker']);
const browser = getService('browser');
@@ -47,8 +47,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
await kibanaServer.importExport.load(
'test/functional/fixtures/kbn_archiver/date_nested_ccs.json'
);
- await esArchiver.load('test/functional/fixtures/es_archiver/date_nested');
- await esArchiver.load('test/functional/fixtures/es_archiver/logstash_functional');
+ await remoteEsArchiver.load('test/functional/fixtures/es_archiver/date_nested');
+ await remoteEsArchiver.load('test/functional/fixtures/es_archiver/logstash_functional');
await kibanaServer.uiSettings.replace(defaultSettings);
log.debug('discover');
@@ -61,8 +61,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
await kibanaServer.importExport.unload(
'test/functional/fixtures/kbn_archiver/date_nested_ccs'
);
- await esArchiver.unload('test/functional/fixtures/es_archiver/date_nested');
- await esArchiver.unload('test/functional/fixtures/es_archiver/logstash_functional');
+ await remoteEsArchiver.unload('test/functional/fixtures/es_archiver/date_nested');
+ await remoteEsArchiver.unload('test/functional/fixtures/es_archiver/logstash_functional');
await PageObjects.common.unsetTime();
});
@@ -87,12 +87,12 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
expect(await filterBar.hasFilter('extension.raw', 'jpg')).to.be(false);
expect(await queryBar.getQueryString()).to.eql('');
- await PageObjects.discover.selectIndexPattern('remote:date-nested');
+ await PageObjects.discover.selectIndexPattern('ftr-remote:date-nested');
expect(await filterBar.hasFilter('extension.raw', 'jpg')).to.be(false);
expect(await queryBar.getQueryString()).to.eql('');
- await PageObjects.discover.selectIndexPattern('remote:logstash-*');
+ await PageObjects.discover.selectIndexPattern('ftr-remote:logstash-*');
expect(await filterBar.hasFilter('extension.raw', 'jpg')).to.be(false);
expect(await queryBar.getQueryString()).to.eql('');
diff --git a/test/functional_ccs/config.js b/test/functional_ccs/config.js
deleted file mode 100644
index 4cd88757983720..00000000000000
--- a/test/functional_ccs/config.js
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0 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 { services } from '../functional/services';
-
-export default async function ({ readConfigFile }) {
- const functionalConfig = await readConfigFile(require.resolve('../functional/config'));
-
- return {
- ...functionalConfig.getAll(),
-
- testFiles: [require.resolve('./apps/discover')],
-
- services,
-
- junit: {
- reportName: 'Kibana CCS Tests',
- },
- };
-}
diff --git a/test/functional_ccs/config.ts b/test/functional_ccs/config.ts
new file mode 100644
index 00000000000000..e99a5310453d93
--- /dev/null
+++ b/test/functional_ccs/config.ts
@@ -0,0 +1,52 @@
+/*
+ * 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 { FtrConfigProviderContext } from '@kbn/test';
+import { services } from './services';
+
+export default async function ({ readConfigFile }: FtrConfigProviderContext) {
+ const functionalConfig = await readConfigFile(require.resolve('../functional/config'));
+
+ return {
+ ...functionalConfig.getAll(),
+
+ testFiles: [require.resolve('./apps/discover')],
+
+ services,
+
+ junit: {
+ reportName: 'Kibana CCS Tests',
+ },
+
+ security: {
+ ...functionalConfig.get('security'),
+ remoteEsRoles: {
+ ccs_remote_search: {
+ indices: [
+ {
+ names: ['*'],
+ privileges: ['read', 'view_index_metadata', 'read_cross_cluster'],
+ },
+ ],
+ },
+ },
+ defaultRoles: [...(functionalConfig.get('security.defaultRoles') ?? []), 'ccs_remote_search'],
+ },
+
+ esTestCluster: {
+ ...functionalConfig.get('esTestCluster'),
+ ccs: {
+ remoteClusterUrl:
+ process.env.REMOTE_CLUSTER_URL ??
+ `http://elastic:changeme@localhost:${
+ functionalConfig.get('servers.elasticsearch.port') + 1
+ }`,
+ },
+ },
+ };
+}
diff --git a/test/functional_ccs/apps/discover/ftr_provider_context.d.ts b/test/functional_ccs/ftr_provider_context.ts
similarity index 80%
rename from test/functional_ccs/apps/discover/ftr_provider_context.d.ts
rename to test/functional_ccs/ftr_provider_context.ts
index ea232d23463e65..8fa82b46ac4063 100644
--- a/test/functional_ccs/apps/discover/ftr_provider_context.d.ts
+++ b/test/functional_ccs/ftr_provider_context.ts
@@ -7,7 +7,7 @@
*/
import { GenericFtrProviderContext } from '@kbn/test';
-import { services } from '../../../functional/services';
-import { pageObjects } from '../../../functional/page_objects';
+import { services } from './services';
+import { pageObjects } from '../functional/page_objects';
export type FtrProviderContext = GenericFtrProviderContext;
diff --git a/test/functional_ccs/services/index.ts b/test/functional_ccs/services/index.ts
new file mode 100644
index 00000000000000..dcdffa077fe083
--- /dev/null
+++ b/test/functional_ccs/services/index.ts
@@ -0,0 +1,17 @@
+/*
+ * 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 { services as functionalServices } from '../../functional/services';
+import { RemoteEsProvider } from './remote_es';
+import { RemoteEsArchiverProvider } from './remote_es_archiver';
+
+export const services = {
+ ...functionalServices,
+ remoteEs: RemoteEsProvider,
+ remoteEsArchiver: RemoteEsArchiverProvider,
+};
diff --git a/test/functional_ccs/services/remote_es.ts b/test/functional_ccs/services/remote_es.ts
new file mode 100644
index 00000000000000..05a10d9e068f03
--- /dev/null
+++ b/test/functional_ccs/services/remote_es.ts
@@ -0,0 +1,24 @@
+/*
+ * 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 { Client } from '@elastic/elasticsearch';
+
+import { systemIndicesSuperuser, createRemoteEsClientForFtrConfig } from '@kbn/test';
+import { FtrProviderContext } from '../ftr_provider_context';
+
+/**
+ * Kibana-specific @elastic/elasticsearch client instance.
+ */
+export function RemoteEsProvider({ getService }: FtrProviderContext): Client {
+ const config = getService('config');
+
+ return createRemoteEsClientForFtrConfig(config, {
+ // Use system indices user so tests can write to system indices
+ authOverride: systemIndicesSuperuser,
+ });
+}
diff --git a/test/functional_ccs/services/remote_es_archiver.ts b/test/functional_ccs/services/remote_es_archiver.ts
new file mode 100644
index 00000000000000..569792d050a4d5
--- /dev/null
+++ b/test/functional_ccs/services/remote_es_archiver.ts
@@ -0,0 +1,22 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 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 { EsArchiver } from '@kbn/es-archiver';
+import { FtrProviderContext } from '../ftr_provider_context';
+
+export function RemoteEsArchiverProvider({ getService }: FtrProviderContext): EsArchiver {
+ const remoteEs = getService('remoteEs');
+ const log = getService('log');
+ const kibanaServer = getService('kibanaServer');
+
+ return new EsArchiver({
+ client: remoteEs,
+ log,
+ kbnClient: kibanaServer,
+ });
+}
diff --git a/test/interpreter_functional/test_suites/run_pipeline/esaggs_topmetrics.ts b/test/interpreter_functional/test_suites/run_pipeline/esaggs_topmetrics.ts
new file mode 100644
index 00000000000000..4f43709ba4a7e0
--- /dev/null
+++ b/test/interpreter_functional/test_suites/run_pipeline/esaggs_topmetrics.ts
@@ -0,0 +1,112 @@
+/*
+ * 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 expect from '@kbn/expect';
+import { ExpectExpression, expectExpressionProvider } from './helpers';
+import { FtrProviderContext } from '../../../functional/ftr_provider_context';
+
+export default function ({
+ getService,
+ updateBaselines,
+}: FtrProviderContext & { updateBaselines: boolean }) {
+ let expectExpression: ExpectExpression;
+
+ describe('esaggs_topmetrics', () => {
+ before(() => {
+ expectExpression = expectExpressionProvider({ getService, updateBaselines });
+ });
+
+ const timeRange = {
+ from: '2015-09-21T00:00:00Z',
+ to: '2015-09-22T00:00:00Z',
+ };
+
+ describe('aggTopMetrics', () => {
+ it('can execute aggTopMetrics', async () => {
+ const expression = `
+ kibana_context timeRange={timerange from='${timeRange.from}' to='${timeRange.to}'}
+ | esaggs index={indexPatternLoad id='logstash-*'}
+ aggs={aggTerms id="1" enabled=true schema="bucket" field="extension.raw"}
+ aggs={aggTopMetrics id="2" enabled=true schema="metric" field="bytes" sortField="@timestamp" sortOrder="desc" size=3 }
+ `;
+ const result = await expectExpression('aggTopMetrics', expression).getResponse();
+
+ expect(result.rows.map((r: { 'col-0-1': string }) => r['col-0-1'])).to.eql([
+ 'jpg',
+ 'css',
+ 'png',
+ 'gif',
+ 'php',
+ ]);
+
+ result.rows.forEach((r: { 'col-1-2': number[] }) => {
+ expect(r['col-1-2'].length).to.be(3);
+ expect(
+ r['col-1-2'].forEach((metric) => {
+ expect(typeof metric).to.be('number');
+ })
+ );
+ });
+ });
+
+ it('can execute aggTopMetrics with different sortOrder and size', async () => {
+ const expression = `
+ kibana_context timeRange={timerange from='${timeRange.from}' to='${timeRange.to}'}
+ | esaggs index={indexPatternLoad id='logstash-*'}
+ aggs={aggTerms id="1" enabled=true schema="bucket" field="extension.raw"}
+ aggs={aggTopMetrics id="2" enabled=true schema="metric" field="bytes" sortField="@timestamp" sortOrder="asc" size=1 }
+ `;
+ const result = await expectExpression('aggTopMetrics', expression).getResponse();
+
+ expect(result.rows.map((r: { 'col-0-1': string }) => r['col-0-1'])).to.eql([
+ 'jpg',
+ 'css',
+ 'png',
+ 'gif',
+ 'php',
+ ]);
+
+ result.rows.forEach((r: { 'col-1-2': number[] }) => {
+ expect(typeof r['col-1-2']).to.be('number');
+ });
+ });
+
+ it('can use aggTopMetrics as an orderAgg of aggTerms', async () => {
+ const expressionSortBytesAsc = `
+ kibana_context timeRange={timerange from='${timeRange.from}' to='${timeRange.to}'}
+ | esaggs index={indexPatternLoad id='logstash-*'}
+ aggs={aggTerms id="1" enabled=true schema="bucket" field="extension.raw" size=1 orderAgg={aggTopMetrics id="order" enabled=true schema="metric" field="bytes" sortField="@timestamp" sortOrder="asc" size=1}}
+ aggs={aggCount id="2" enabled=true schema="metric"}
+ `;
+
+ const resultSortBytesAsc = await expectExpression(
+ 'sortBytesAsc',
+ expressionSortBytesAsc
+ ).getResponse();
+
+ const expressionSortBytesDesc = `
+ kibana_context timeRange={timerange from='${timeRange.from}' to='${timeRange.to}'}
+ | esaggs index={indexPatternLoad id='logstash-*'}
+ aggs={aggTerms id="1" enabled=true schema="bucket" field="extension.raw" size=1 orderAgg={aggTopMetrics id="order" enabled=true schema="metric" field="bytes" sortField="@timestamp" sortOrder="desc" size=1}}
+ aggs={aggCount id="2" enabled=true schema="metric"}
+ `;
+
+ const resultSortBytesDesc = await expectExpression(
+ 'sortBytesDesc',
+ expressionSortBytesDesc
+ ).getResponse();
+
+ expect(resultSortBytesAsc.rows.length).to.be(1);
+ expect(resultSortBytesAsc.rows[0]['col-0-1']).to.be('jpg');
+
+ expect(resultSortBytesDesc.rows.length).to.be(1);
+ expect(resultSortBytesDesc.rows[0]['col-0-1']).to.be('php');
+ });
+ });
+ });
+}
diff --git a/test/interpreter_functional/test_suites/run_pipeline/index.ts b/test/interpreter_functional/test_suites/run_pipeline/index.ts
index 97387fc0a965fc..e24563a5918eb8 100644
--- a/test/interpreter_functional/test_suites/run_pipeline/index.ts
+++ b/test/interpreter_functional/test_suites/run_pipeline/index.ts
@@ -25,6 +25,7 @@ export default function ({ getService, getPageObjects, loadTestFile }: FtrProvid
await kibanaServer.uiSettings.replace({
'dateFormat:tz': 'Australia/North',
defaultIndex: 'logstash-*',
+ 'bfetch:disableCompression': true, // makes it easier to debug while developing tests
});
await browser.setWindowSize(1300, 900);
await PageObjects.common.navigateToApp('settings');
@@ -47,5 +48,6 @@ export default function ({ getService, getPageObjects, loadTestFile }: FtrProvid
loadTestFile(require.resolve('./esaggs_sampler'));
loadTestFile(require.resolve('./esaggs_significanttext'));
loadTestFile(require.resolve('./esaggs_rareterms'));
+ loadTestFile(require.resolve('./esaggs_topmetrics'));
});
}
diff --git a/x-pack/plugins/alerting/server/lib/wrap_scoped_cluster_client.test.ts b/x-pack/plugins/alerting/server/lib/wrap_scoped_cluster_client.test.ts
index 15f5a37edb910e..d6101c9c6cec4f 100644
--- a/x-pack/plugins/alerting/server/lib/wrap_scoped_cluster_client.test.ts
+++ b/x-pack/plugins/alerting/server/lib/wrap_scoped_cluster_client.test.ts
@@ -9,7 +9,6 @@ import { Client } from '@elastic/elasticsearch';
import { loggingSystemMock } from 'src/core/server/mocks';
import { elasticsearchServiceMock } from '../../../../../src/core/server/mocks';
import { createWrappedScopedClusterClientFactory } from './wrap_scoped_cluster_client';
-import { ElasticsearchClientWithChild } from '../types';
const esQuery = {
body: { query: { bool: { filter: { range: { '@timestamp': { gte: 0 } } } } } },
@@ -41,9 +40,7 @@ describe('wrapScopedClusterClient', () => {
const scopedClusterClient = elasticsearchServiceMock.createScopedClusterClient();
const childClient = elasticsearchServiceMock.createElasticsearchClient();
- (
- scopedClusterClient.asInternalUser as unknown as jest.Mocked
- ).child.mockReturnValue(childClient as unknown as Client);
+ scopedClusterClient.asInternalUser.child.mockReturnValue(childClient as unknown as Client);
const asInternalUserWrappedSearchFn = childClient.search;
const wrappedSearchClient = createWrappedScopedClusterClientFactory({
@@ -62,9 +59,7 @@ describe('wrapScopedClusterClient', () => {
const scopedClusterClient = elasticsearchServiceMock.createScopedClusterClient();
const childClient = elasticsearchServiceMock.createElasticsearchClient();
- (
- scopedClusterClient.asCurrentUser as unknown as jest.Mocked
- ).child.mockReturnValue(childClient as unknown as Client);
+ scopedClusterClient.asCurrentUser.child.mockReturnValue(childClient as unknown as Client);
const asCurrentUserWrappedSearchFn = childClient.search;
const wrappedSearchClient = createWrappedScopedClusterClientFactory({
@@ -83,9 +78,7 @@ describe('wrapScopedClusterClient', () => {
const scopedClusterClient = elasticsearchServiceMock.createScopedClusterClient();
const childClient = elasticsearchServiceMock.createElasticsearchClient();
- (
- scopedClusterClient.asInternalUser as unknown as jest.Mocked
- ).child.mockReturnValue(childClient as unknown as Client);
+ scopedClusterClient.asInternalUser.child.mockReturnValue(childClient as unknown as Client);
const asInternalUserWrappedSearchFn = childClient.search;
const wrappedSearchClient = createWrappedScopedClusterClientFactory({
@@ -106,9 +99,7 @@ describe('wrapScopedClusterClient', () => {
const scopedClusterClient = elasticsearchServiceMock.createScopedClusterClient();
const childClient = elasticsearchServiceMock.createElasticsearchClient();
- (
- scopedClusterClient.asInternalUser as unknown as jest.Mocked
- ).child.mockReturnValue(childClient as unknown as Client);
+ scopedClusterClient.asInternalUser.child.mockReturnValue(childClient as unknown as Client);
const asInternalUserWrappedSearchFn = childClient.search;
asInternalUserWrappedSearchFn.mockRejectedValueOnce(new Error('something went wrong!'));
@@ -127,9 +118,7 @@ describe('wrapScopedClusterClient', () => {
const scopedClusterClient = elasticsearchServiceMock.createScopedClusterClient();
const childClient = elasticsearchServiceMock.createElasticsearchClient();
- (
- scopedClusterClient.asInternalUser as unknown as jest.Mocked
- ).child.mockReturnValue(childClient as unknown as Client);
+ scopedClusterClient.asInternalUser.child.mockReturnValue(childClient as unknown as Client);
const asInternalUserWrappedSearchFn = childClient.search;
// @ts-ignore incomplete return type
asInternalUserWrappedSearchFn.mockResolvedValue({});
@@ -156,9 +145,7 @@ describe('wrapScopedClusterClient', () => {
const scopedClusterClient = elasticsearchServiceMock.createScopedClusterClient();
const childClient = elasticsearchServiceMock.createElasticsearchClient();
- (
- scopedClusterClient.asInternalUser as unknown as jest.Mocked
- ).child.mockReturnValue(childClient as unknown as Client);
+ scopedClusterClient.asInternalUser.child.mockReturnValue(childClient as unknown as Client);
const asInternalUserWrappedSearchFn = childClient.search;
// @ts-ignore incomplete return type
asInternalUserWrappedSearchFn.mockResolvedValue({ took: 333 });
diff --git a/x-pack/plugins/alerting/server/lib/wrap_scoped_cluster_client.ts b/x-pack/plugins/alerting/server/lib/wrap_scoped_cluster_client.ts
index dfe32a48ce4384..2b71f95cd9f1c6 100644
--- a/x-pack/plugins/alerting/server/lib/wrap_scoped_cluster_client.ts
+++ b/x-pack/plugins/alerting/server/lib/wrap_scoped_cluster_client.ts
@@ -21,7 +21,7 @@ import type {
AggregationsAggregate,
} from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
import { IScopedClusterClient, ElasticsearchClient, Logger } from 'src/core/server';
-import { ElasticsearchClientWithChild, RuleExecutionMetrics } from '../types';
+import { RuleExecutionMetrics } from '../types';
import { Alert as Rule } from '../types';
type RuleInfo = Pick & { spaceId: string };
@@ -87,8 +87,7 @@ function wrapScopedClusterClient(opts: WrapScopedClusterClientOpts): IScopedClus
function wrapEsClient(opts: WrapEsClientOpts): ElasticsearchClient {
const { esClient, ...rest } = opts;
- // Core hides access to .child via TS
- const wrappedClient = (esClient as ElasticsearchClientWithChild).child({});
+ const wrappedClient = esClient.child({});
// Mutating the functions we want to wrap
wrappedClient.search = getWrappedSearchFn({ esClient: wrappedClient, ...rest });
diff --git a/x-pack/plugins/alerting/server/task_runner/task_runner.ts b/x-pack/plugins/alerting/server/task_runner/task_runner.ts
index c05bdc3cf7bd94..ccf9659a8e67d1 100644
--- a/x-pack/plugins/alerting/server/task_runner/task_runner.ts
+++ b/x-pack/plugins/alerting/server/task_runner/task_runner.ts
@@ -142,7 +142,7 @@ export class TaskRunner<
this.executionId = uuid.v4();
}
- async getDecryptedAttributes(
+ private async getDecryptedAttributes(
ruleId: string,
spaceId: string
): Promise<{ apiKey: string | null; enabled: boolean }> {
@@ -267,7 +267,7 @@ export class TaskRunner<
}
}
- async executeAlert(
+ private async executeAlert(
alertId: string,
alert: CreatedAlert,
executionHandler: ExecutionHandler
@@ -283,7 +283,7 @@ export class TaskRunner<
return executionHandler({ actionGroup, actionSubgroup, context, state, alertId });
}
- async executeAlerts(
+ private async executeAlerts(
fakeRequest: KibanaRequest,
rule: SanitizedAlert,
params: Params,
@@ -548,7 +548,7 @@ export class TaskRunner<
};
}
- async validateAndExecuteRule(
+ private async validateAndExecuteRule(
fakeRequest: KibanaRequest,
apiKey: RawRule['apiKey'],
rule: SanitizedAlert,
@@ -574,7 +574,9 @@ export class TaskRunner<
return this.executeAlerts(fakeRequest, rule, validatedParams, executionHandler, spaceId, event);
}
- async loadRuleAttributesAndRun(event: Event): Promise> {
+ private async loadRuleAttributesAndRun(
+ event: Event
+ ): Promise> {
const {
params: { alertId: ruleId, spaceId },
} = this.taskInstance;
diff --git a/x-pack/plugins/alerting/server/types.ts b/x-pack/plugins/alerting/server/types.ts
index 6b06f7efe30660..5499ba0c76caf6 100644
--- a/x-pack/plugins/alerting/server/types.ts
+++ b/x-pack/plugins/alerting/server/types.ts
@@ -5,12 +5,10 @@
* 2.0.
*/
-import { Client } from '@elastic/elasticsearch';
import type {
IRouter,
RequestHandlerContext,
SavedObjectReference,
- ElasticsearchClient,
IUiSettingsClient,
} from 'src/core/server';
import type { PublicMethodsOf } from '@kbn/utility-types';
@@ -48,10 +46,6 @@ import { IAbortableClusterClient } from './lib/create_abortable_es_client_factor
export type WithoutQueryAndParams = Pick>;
export type SpaceIdToNamespaceFunction = (spaceId?: string) => string | undefined;
-export interface ElasticsearchClientWithChild extends ElasticsearchClient {
- child: Client['child'];
-}
-
/**
* @public
*/
diff --git a/x-pack/plugins/cloud/public/fullstory.ts b/x-pack/plugins/cloud/public/fullstory.ts
index 4f76abf540cae3..602b1c4cc63d37 100644
--- a/x-pack/plugins/cloud/public/fullstory.ts
+++ b/x-pack/plugins/cloud/public/fullstory.ts
@@ -15,9 +15,11 @@ export interface FullStoryDeps {
}
export type FullstoryUserVars = Record;
+export type FullstoryVars = Record;
export interface FullStoryApi {
identify(userId: string, userVars?: FullstoryUserVars): void;
+ setVars(pageName: string, vars?: FullstoryVars): void;
setUserVars(userVars?: FullstoryUserVars): void;
event(eventName: string, eventProperties: Record): void;
}
diff --git a/x-pack/plugins/cloud/public/plugin.test.mocks.ts b/x-pack/plugins/cloud/public/plugin.test.mocks.ts
index b79fb1bc651307..1c185d0194912a 100644
--- a/x-pack/plugins/cloud/public/plugin.test.mocks.ts
+++ b/x-pack/plugins/cloud/public/plugin.test.mocks.ts
@@ -11,6 +11,7 @@ import type { FullStoryDeps, FullStoryApi, FullStoryService } from './fullstory'
export const fullStoryApiMock: jest.Mocked = {
event: jest.fn(),
setUserVars: jest.fn(),
+ setVars: jest.fn(),
identify: jest.fn(),
};
export const initializeFullStoryMock = jest.fn(() => ({
diff --git a/x-pack/plugins/cloud/public/plugin.test.ts b/x-pack/plugins/cloud/public/plugin.test.ts
index 1eef581610f004..edbf724e25390e 100644
--- a/x-pack/plugins/cloud/public/plugin.test.ts
+++ b/x-pack/plugins/cloud/public/plugin.test.ts
@@ -12,6 +12,7 @@ import { securityMock } from '../../security/public/mocks';
import { fullStoryApiMock, initializeFullStoryMock } from './plugin.test.mocks';
import { CloudPlugin, CloudConfigType, loadFullStoryUserId } from './plugin';
import { Observable, Subject } from 'rxjs';
+import { KibanaExecutionContext } from 'kibana/public';
describe('Cloud Plugin', () => {
describe('#setup', () => {
@@ -24,12 +25,12 @@ describe('Cloud Plugin', () => {
config = {},
securityEnabled = true,
currentUserProps = {},
- currentAppId$ = undefined,
+ currentContext$ = undefined,
}: {
config?: Partial;
securityEnabled?: boolean;
currentUserProps?: Record;
- currentAppId$?: Observable;
+ currentContext$?: Observable;
}) => {
const initContext = coreMock.createPluginInitializerContext({
id: 'cloudId',
@@ -51,8 +52,8 @@ describe('Cloud Plugin', () => {
const coreSetup = coreMock.createSetup();
const coreStart = coreMock.createStart();
- if (currentAppId$) {
- coreStart.application.currentAppId$ = currentAppId$;
+ if (currentContext$) {
+ coreStart.executionContext.context$ = currentContext$;
}
coreSetup.getStartServices.mockResolvedValue([coreStart, {}, undefined]);
@@ -94,44 +95,98 @@ describe('Cloud Plugin', () => {
});
expect(fullStoryApiMock.identify).toHaveBeenCalledWith(
- '03ac674216f3e15c761ee1a5e255f067953623c8b388b4459e13f978d7c846f4',
+ '5ef112cfdae3dea57097bc276e275b2816e73ef2a398dc0ffaf5b6b4e3af2041',
{
version_str: 'version',
version_major_int: -1,
version_minor_int: -1,
version_patch_int: -1,
+ org_id_str: 'cloudId',
}
);
});
- it('calls FS.setUserVars everytime an app changes', async () => {
- const currentAppId$ = new Subject();
+ it('user hash includes org id', async () => {
+ await setupPlugin({
+ config: { full_story: { enabled: true, org_id: 'foo' }, id: 'esOrg1' },
+ currentUserProps: {
+ username: '1234',
+ },
+ });
+
+ const hashId1 = fullStoryApiMock.identify.mock.calls[0][0];
+
+ await setupPlugin({
+ config: { full_story: { enabled: true, org_id: 'foo' }, id: 'esOrg2' },
+ currentUserProps: {
+ username: '1234',
+ },
+ });
+
+ const hashId2 = fullStoryApiMock.identify.mock.calls[1][0];
+
+ expect(hashId1).not.toEqual(hashId2);
+ });
+
+ it('calls FS.setVars everytime an app changes', async () => {
+ const currentContext$ = new Subject();
const { plugin } = await setupPlugin({
config: { full_story: { enabled: true, org_id: 'foo' } },
currentUserProps: {
username: '1234',
},
- currentAppId$,
+ currentContext$,
});
- expect(fullStoryApiMock.setUserVars).not.toHaveBeenCalled();
- currentAppId$.next('App1');
- expect(fullStoryApiMock.setUserVars).toHaveBeenCalledWith({
+ // takes the app name
+ expect(fullStoryApiMock.setVars).not.toHaveBeenCalled();
+ currentContext$.next({
+ name: 'App1',
+ description: '123',
+ });
+
+ expect(fullStoryApiMock.setVars).toHaveBeenCalledWith('page', {
+ pageName: 'App1',
app_id_str: 'App1',
});
- currentAppId$.next();
- expect(fullStoryApiMock.setUserVars).toHaveBeenCalledWith({
- app_id_str: 'unknown',
+
+ // context clear
+ currentContext$.next({});
+ expect(fullStoryApiMock.setVars).toHaveBeenCalledWith('page', {
+ pageName: 'App1',
+ app_id_str: 'App1',
});
- currentAppId$.next('App2');
- expect(fullStoryApiMock.setUserVars).toHaveBeenCalledWith({
+ // different app
+ currentContext$.next({
+ name: 'App2',
+ page: 'page2',
+ id: '123',
+ });
+ expect(fullStoryApiMock.setVars).toHaveBeenCalledWith('page', {
+ pageName: 'App2:page2',
app_id_str: 'App2',
+ page_str: 'page2',
+ ent_id_str: '123',
+ });
+
+ // Back to first app
+ currentContext$.next({
+ name: 'App1',
+ page: 'page3',
+ id: '123',
+ });
+
+ expect(fullStoryApiMock.setVars).toHaveBeenCalledWith('page', {
+ pageName: 'App1:page3',
+ app_id_str: 'App1',
+ page_str: 'page3',
+ ent_id_str: '123',
});
- expect(currentAppId$.observers.length).toBe(1);
+ expect(currentContext$.observers.length).toBe(1);
plugin.stop();
- expect(currentAppId$.observers.length).toBe(0);
+ expect(currentContext$.observers.length).toBe(0);
});
it('does not call FS.identify when security is not available', async () => {
diff --git a/x-pack/plugins/cloud/public/plugin.tsx b/x-pack/plugins/cloud/public/plugin.tsx
index 991a7c1f8b565c..89f24971de25c1 100644
--- a/x-pack/plugins/cloud/public/plugin.tsx
+++ b/x-pack/plugins/cloud/public/plugin.tsx
@@ -13,11 +13,12 @@ import {
PluginInitializerContext,
HttpStart,
IBasePath,
- ApplicationStart,
+ ExecutionContextStart,
} from 'src/core/public';
import { i18n } from '@kbn/i18n';
import useObservable from 'react-use/lib/useObservable';
import { BehaviorSubject, Subscription } from 'rxjs';
+import { compact, isUndefined, omitBy } from 'lodash';
import type {
AuthenticatedUser,
SecurityPluginSetup,
@@ -83,8 +84,9 @@ export interface CloudSetup {
}
interface SetupFullstoryDeps extends CloudSetupDependencies {
- application?: Promise;
+ executionContextPromise?: Promise;
basePath: IBasePath;
+ esOrgId?: string;
}
interface SetupChatDeps extends Pick {
@@ -103,11 +105,16 @@ export class CloudPlugin implements Plugin {
}
public setup(core: CoreSetup, { home, security }: CloudSetupDependencies) {
- const application = core.getStartServices().then(([coreStart]) => {
- return coreStart.application;
+ const executionContextPromise = core.getStartServices().then(([coreStart]) => {
+ return coreStart.executionContext;
});
- this.setupFullstory({ basePath: core.http.basePath, security, application }).catch((e) =>
+ this.setupFullstory({
+ basePath: core.http.basePath,
+ security,
+ executionContextPromise,
+ esOrgId: this.config.id,
+ }).catch((e) =>
// eslint-disable-next-line no-console
console.debug(`Error setting up FullStory: ${e.toString()}`)
);
@@ -223,9 +230,14 @@ export class CloudPlugin implements Plugin {
return user?.roles.includes('superuser') ?? true;
}
- private async setupFullstory({ basePath, security, application }: SetupFullstoryDeps) {
- const { enabled, org_id: orgId } = this.config.full_story;
- if (!enabled || !orgId) {
+ private async setupFullstory({
+ basePath,
+ security,
+ executionContextPromise,
+ esOrgId,
+ }: SetupFullstoryDeps) {
+ const { enabled, org_id: fsOrgId } = this.config.full_story;
+ if (!enabled || !fsOrgId) {
return; // do not load any fullstory code in the browser if not enabled
}
@@ -243,7 +255,7 @@ export class CloudPlugin implements Plugin {
const { fullStory, sha256 } = initializeFullStory({
basePath,
- orgId,
+ orgId: fsOrgId,
packageInfo: this.initializerContext.env.packageInfo,
});
@@ -252,16 +264,29 @@ export class CloudPlugin implements Plugin {
// This needs to be called syncronously to be sure that we populate the user ID soon enough to make sessions merging
// across domains work
if (userId) {
- // Do the hashing here to keep it at clear as possible in our source code that we do not send literal user IDs
- const hashedId = sha256(userId.toString());
- application
- ?.then(async () => {
- const appStart = await application;
- this.appSubscription = appStart.currentAppId$.subscribe((appId) => {
- // Update the current application every time it changes
- fullStory.setUserVars({
- app_id_str: appId ?? 'unknown',
- });
+ // Join the cloud org id and the user to create a truly unique user id.
+ // The hashing here is to keep it at clear as possible in our source code that we do not send literal user IDs
+ const hashedId = sha256(esOrgId ? `${esOrgId}:${userId}` : `${userId}`);
+
+ executionContextPromise
+ ?.then(async (executionContext) => {
+ this.appSubscription = executionContext.context$.subscribe((context) => {
+ const { name, page, id } = context;
+ // Update the current context every time it changes
+ fullStory.setVars(
+ 'page',
+ omitBy(
+ {
+ // Read about the special pageName property
+ // https://help.fullstory.com/hc/en-us/articles/1500004101581-FS-setVars-API-Sending-custom-page-data-to-FullStory
+ pageName: `${compact([name, page]).join(':')}`,
+ app_id_str: name ?? 'unknown',
+ page_str: page,
+ ent_id_str: id,
+ },
+ isUndefined
+ )
+ );
});
})
.catch((e) => {
@@ -282,6 +307,7 @@ export class CloudPlugin implements Plugin {
version_major_int: parsedVer[0] ?? -1,
version_minor_int: parsedVer[1] ?? -1,
version_patch_int: parsedVer[2] ?? -1,
+ org_id_str: esOrgId,
});
}
} catch (e) {
diff --git a/x-pack/plugins/cloud_security_posture/server/routes/benchmarks/benchmarks.test.ts b/x-pack/plugins/cloud_security_posture/server/routes/benchmarks/benchmarks.test.ts
index b728948cf2a056..8c9d04dc207f3e 100644
--- a/x-pack/plugins/cloud_security_posture/server/routes/benchmarks/benchmarks.test.ts
+++ b/x-pack/plugins/cloud_security_posture/server/routes/benchmarks/benchmarks.test.ts
@@ -76,6 +76,18 @@ describe('benchmarks API', () => {
});
});
+ it('expect to find benchmark_name', async () => {
+ const validatedQuery = benchmarksInputSchema.validate({
+ benchmark_name: 'my_cis_benchmark',
+ });
+
+ expect(validatedQuery).toMatchObject({
+ page: 1,
+ per_page: DEFAULT_BENCHMARKS_PER_PAGE,
+ benchmark_name: 'my_cis_benchmark',
+ });
+ });
+
it('should throw when page field is not a positive integer', async () => {
expect(() => {
benchmarksInputSchema.validate({ page: -2 });
@@ -125,6 +137,24 @@ describe('benchmarks API', () => {
});
});
+ it('should format request by benchmark_name', async () => {
+ const mockAgentPolicyService = createPackagePolicyServiceMock();
+
+ await getPackagePolicies(mockSoClient, mockAgentPolicyService, 'myPackage', {
+ page: 1,
+ per_page: 100,
+ benchmark_name: 'my_cis_benchmark',
+ });
+
+ expect(mockAgentPolicyService.list.mock.calls[0][1]).toMatchObject(
+ expect.objectContaining({
+ kuery: `${PACKAGE_POLICY_SAVED_OBJECT_TYPE}.package.name:myPackage AND ${PACKAGE_POLICY_SAVED_OBJECT_TYPE}.name: *my_cis_benchmark*`,
+ page: 1,
+ perPage: 100,
+ })
+ );
+ });
+
describe('test getAgentPolicies', () => {
it('should return one agent policy id when there is duplication', async () => {
const agentPolicyService = createMockAgentPolicyService();
diff --git a/x-pack/plugins/cloud_security_posture/server/routes/benchmarks/benchmarks.ts b/x-pack/plugins/cloud_security_posture/server/routes/benchmarks/benchmarks.ts
index 80c526c248c0ff..c52aeead6cd4da 100644
--- a/x-pack/plugins/cloud_security_posture/server/routes/benchmarks/benchmarks.ts
+++ b/x-pack/plugins/cloud_security_posture/server/routes/benchmarks/benchmarks.ts
@@ -43,8 +43,13 @@ export interface Benchmark {
export const DEFAULT_BENCHMARKS_PER_PAGE = 20;
export const PACKAGE_POLICY_SAVED_OBJECT_TYPE = 'ingest-package-policies';
-const getPackageNameQuery = (packageName: string): string => {
- return `${PACKAGE_POLICY_SAVED_OBJECT_TYPE}.package.name:${packageName}`;
+const getPackageNameQuery = (packageName: string, benchmarkFilter?: string): string => {
+ const integrationNameQuery = `${PACKAGE_POLICY_SAVED_OBJECT_TYPE}.package.name:${packageName}`;
+ const kquery = benchmarkFilter
+ ? `${PACKAGE_POLICY_SAVED_OBJECT_TYPE}.package.name:${packageName} AND ${PACKAGE_POLICY_SAVED_OBJECT_TYPE}.name: *${benchmarkFilter}*`
+ : integrationNameQuery;
+
+ return kquery;
};
export const getPackagePolicies = async (
@@ -57,7 +62,7 @@ export const getPackagePolicies = async (
throw new Error('packagePolicyService is undefined');
}
- const packageNameQuery = getPackageNameQuery(packageName);
+ const packageNameQuery = getPackageNameQuery(packageName, queryParams.benchmark_name);
const { items: packagePolicies } = (await packagePolicyService?.list(soClient, {
kuery: packageNameQuery,
@@ -193,4 +198,8 @@ export const benchmarksInputSchema = rt.object({
* The number of objects to include in each page
*/
per_page: rt.number({ defaultValue: DEFAULT_BENCHMARKS_PER_PAGE, min: 0 }),
+ /**
+ * Benchmark filter
+ */
+ benchmark_name: rt.maybe(rt.string()),
});
diff --git a/x-pack/plugins/enterprise_search/common/constants.ts b/x-pack/plugins/enterprise_search/common/constants.ts
index 7a6203c994f4d9..456a76d914f7d2 100644
--- a/x-pack/plugins/enterprise_search/common/constants.ts
+++ b/x-pack/plugins/enterprise_search/common/constants.ts
@@ -89,3 +89,4 @@ export const READ_ONLY_MODE_HEADER = 'x-ent-search-read-only-mode';
export const ENTERPRISE_SEARCH_KIBANA_COOKIE = '_enterprise_search';
export const LOGS_SOURCE_ID = 'ent-search-logs';
+export const ENTERPRISE_SEARCH_AUDIT_LOGS_SOURCE_ID = 'ent-search-audit-logs';
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/crawl_custom_settings_flyout/crawl_custom_settings_flyout_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/crawl_custom_settings_flyout/crawl_custom_settings_flyout_logic.test.ts
index ddfc23b5aa6285..bb20e0e639aa2a 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/crawl_custom_settings_flyout/crawl_custom_settings_flyout_logic.test.ts
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/crawl_custom_settings_flyout/crawl_custom_settings_flyout_logic.test.ts
@@ -297,7 +297,25 @@ describe('CrawlCustomSettingsFlyoutLogic', () => {
});
describe('startCustomCrawl', () => {
- it('starts a custom crawl with the user set values', async () => {
+ it('can start a custom crawl for selected domains', async () => {
+ mount({
+ includeSitemapsInRobotsTxt: true,
+ maxCrawlDepth: 5,
+ selectedDomainUrls: ['https://www.elastic.co', 'https://swiftype.com'],
+ });
+ jest.spyOn(CrawlerLogic.actions, 'startCrawl');
+
+ CrawlCustomSettingsFlyoutLogic.actions.startCustomCrawl();
+ await nextTick();
+
+ expect(CrawlerLogic.actions.startCrawl).toHaveBeenCalledWith({
+ domain_allowlist: ['https://www.elastic.co', 'https://swiftype.com'],
+ max_crawl_depth: 5,
+ sitemap_discovery_disabled: false,
+ });
+ });
+
+ it('can start a custom crawl selected domains, sitemaps, and seed urls', async () => {
mount({
includeSitemapsInRobotsTxt: true,
maxCrawlDepth: 5,
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/crawl_custom_settings_flyout/crawl_custom_settings_flyout_logic.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/crawl_custom_settings_flyout/crawl_custom_settings_flyout_logic.ts
index f22dcc7487af3d..3b04e1b28c17eb 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/crawl_custom_settings_flyout/crawl_custom_settings_flyout_logic.ts
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/crawl_custom_settings_flyout/crawl_custom_settings_flyout_logic.ts
@@ -11,7 +11,7 @@ import { flashAPIErrors } from '../../../../../shared/flash_messages';
import { HttpLogic } from '../../../../../shared/http';
import { EngineLogic } from '../../../engine';
-import { CrawlerLogic } from '../../crawler_logic';
+import { CrawlerLogic, CrawlRequestOverrides } from '../../crawler_logic';
import { DomainConfig, DomainConfigFromServer } from '../../types';
import { domainConfigServerToClient } from '../../utils';
import { extractDomainAndEntryPointFromUrl } from '../add_domain/utils';
@@ -213,13 +213,23 @@ export const CrawlCustomSettingsFlyoutLogic = kea<
actions.fetchDomainConfigData();
},
startCustomCrawl: () => {
- CrawlerLogic.actions.startCrawl({
- domain_allowlist: values.selectedDomainUrls,
- max_crawl_depth: values.maxCrawlDepth,
- seed_urls: [...values.selectedEntryPointUrls, ...values.customEntryPointUrls],
- sitemap_urls: [...values.selectedSitemapUrls, ...values.customSitemapUrls],
+ const overrides: CrawlRequestOverrides = {
sitemap_discovery_disabled: !values.includeSitemapsInRobotsTxt,
- });
+ max_crawl_depth: values.maxCrawlDepth,
+ domain_allowlist: values.selectedDomainUrls,
+ };
+
+ const seedUrls = [...values.selectedEntryPointUrls, ...values.customEntryPointUrls];
+ if (seedUrls.length > 0) {
+ overrides.seed_urls = seedUrls;
+ }
+
+ const sitemapUrls = [...values.selectedSitemapUrls, ...values.customSitemapUrls];
+ if (sitemapUrls.length > 0) {
+ overrides.sitemap_urls = sitemapUrls;
+ }
+
+ CrawlerLogic.actions.startCrawl(overrides);
},
}),
});
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/crawler_logic.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/crawler_logic.ts
index 68b1cb6ec9b267..2d1b8a9e7aa27e 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/crawler_logic.ts
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/crawler_logic.ts
@@ -33,7 +33,7 @@ const ACTIVE_STATUSES = [
CrawlerStatus.Canceling,
];
-interface CrawlRequestOverrides {
+export interface CrawlRequestOverrides {
domain_allowlist?: string[];
max_crawl_depth?: number;
seed_urls?: string[];
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/audit_logs_modal/audit_logs_modal.scss b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/audit_logs_modal/audit_logs_modal.scss
new file mode 100644
index 00000000000000..11a008a3cc51fc
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/audit_logs_modal/audit_logs_modal.scss
@@ -0,0 +1,9 @@
+.auditLogsModal {
+ width: 75vw;
+}
+
+@media (max-width: 1200px) {
+ .auditLogsModal {
+ width: 100vw;
+ }
+}
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/audit_logs_modal/audit_logs_modal.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/audit_logs_modal/audit_logs_modal.test.tsx
new file mode 100644
index 00000000000000..f6687e431e9836
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/audit_logs_modal/audit_logs_modal.test.tsx
@@ -0,0 +1,52 @@
+/*
+ * 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 { LogicMounter, setMockValues, setMockActions } from '../../../../../__mocks__/kea_logic';
+
+import React from 'react';
+
+import { shallow } from 'enzyme';
+
+import { EuiText, EuiModal } from '@elastic/eui';
+
+import { EntSearchLogStream } from '../../../../../shared/log_stream';
+
+import { AuditLogsModal } from './audit_logs_modal';
+
+import { AuditLogsModalLogic } from './audit_logs_modal_logic';
+
+describe('AuditLogsModal', () => {
+ const { mount } = new LogicMounter(AuditLogsModalLogic);
+ beforeEach(() => {
+ jest.clearAllMocks();
+ mount({ isModalVisible: true });
+ });
+
+ it('renders nothing by default', () => {
+ const wrapper = shallow();
+ expect(wrapper.isEmptyRender()).toBe(true);
+ });
+
+ it('renders the modal when modal visible', () => {
+ const testEngineName = 'test-engine-123';
+ const mockClose = jest.fn();
+ setMockValues({
+ isModalVisible: true,
+ engineName: testEngineName,
+ });
+ setMockActions({
+ hideModal: mockClose,
+ });
+
+ const wrapper = shallow();
+ expect(wrapper.find(EntSearchLogStream).prop('query')).toBe(
+ `event.kind: event and event.action: audit and enterprisesearch.data_repository.name: ${testEngineName}`
+ );
+ expect(wrapper.find(EuiText).children().text()).toBe('Showing events from last 24 hours');
+ expect(wrapper.find(EuiModal).prop('onClose')).toBe(mockClose);
+ });
+});
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/audit_logs_modal/audit_logs_modal.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/audit_logs_modal/audit_logs_modal.tsx
new file mode 100644
index 00000000000000..3807234fd5c118
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/audit_logs_modal/audit_logs_modal.tsx
@@ -0,0 +1,121 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import React from 'react';
+
+import { useValues, useActions } from 'kea';
+
+import {
+ EuiButton,
+ EuiModal,
+ EuiModalBody,
+ EuiModalFooter,
+ EuiModalHeader,
+ EuiModalHeaderTitle,
+ EuiSpacer,
+ EuiText,
+} from '@elastic/eui';
+
+import { i18n } from '@kbn/i18n';
+
+import { ENTERPRISE_SEARCH_AUDIT_LOGS_SOURCE_ID } from '../../../../../../../common/constants';
+import { EntSearchLogStream } from '../../../../../shared/log_stream';
+
+import { AuditLogsModalLogic } from './audit_logs_modal_logic';
+
+import './audit_logs_modal.scss';
+
+export const AuditLogsModal: React.FC = () => {
+ const auditLogsModalLogic = AuditLogsModalLogic();
+ const { isModalVisible, engineName } = useValues(auditLogsModalLogic);
+ const { hideModal } = useActions(auditLogsModalLogic);
+
+ const filters = [
+ 'event.kind: event',
+ 'event.action: audit',
+ `enterprisesearch.data_repository.name: ${engineName}`,
+ ].join(' and ');
+
+ return !isModalVisible ? null : (
+
+
+
+ {engineName}
+
+
+
+
+ {i18n.translate('xpack.enterpriseSearch.appSearch.engines.auditLogsModal.eventTip', {
+ defaultMessage: 'Showing events from last 24 hours',
+ })}
+
+
+
+
+
+
+
+ {i18n.translate('xpack.enterpriseSearch.appSearch.engines.auditLogsModal.closeButton', {
+ defaultMessage: 'Close',
+ })}
+
+
+
+ );
+};
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/audit_logs_modal/audit_logs_modal_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/audit_logs_modal/audit_logs_modal_logic.test.ts
new file mode 100644
index 00000000000000..f869dd145087d3
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/audit_logs_modal/audit_logs_modal_logic.test.ts
@@ -0,0 +1,53 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { LogicMounter } from '../../../../../__mocks__/kea_logic';
+
+import { AuditLogsModalLogic } from './audit_logs_modal_logic';
+
+describe('AuditLogsModalLogic', () => {
+ const { mount } = new LogicMounter(AuditLogsModalLogic);
+
+ beforeEach(() => {
+ jest.clearAllMocks();
+ mount();
+ });
+
+ it('has excepted default values', () => {
+ expect(AuditLogsModalLogic.values).toEqual({
+ isModalVisible: false,
+ engineName: '',
+ });
+ });
+
+ describe('actions', () => {
+ describe('hideModal', () => {
+ it('hides the modal', () => {
+ mount({
+ isModalVisible: true,
+ engineName: 'test_engine',
+ });
+
+ AuditLogsModalLogic.actions.hideModal();
+ expect(AuditLogsModalLogic.values).toEqual({
+ isModalVisible: false,
+ engineName: '',
+ });
+ });
+ });
+
+ describe('showModal', () => {
+ it('show the modal with correct engine name', () => {
+ AuditLogsModalLogic.actions.showModal('test-engine-123');
+ expect(AuditLogsModalLogic.values).toEqual({
+ isModalVisible: true,
+ engineName: 'test-engine-123',
+ });
+ });
+ });
+ });
+});
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/audit_logs_modal/audit_logs_modal_logic.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/audit_logs_modal/audit_logs_modal_logic.ts
new file mode 100644
index 00000000000000..afa70b4f3dee0e
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/audit_logs_modal/audit_logs_modal_logic.ts
@@ -0,0 +1,32 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { kea } from 'kea';
+
+export const AuditLogsModalLogic = kea({
+ path: ['enterprise_search', 'app_search', 'engines_overview', 'audit_logs_modal'],
+ actions: () => ({
+ hideModal: true,
+ showModal: (engineName: string) => ({ engineName }),
+ }),
+ reducers: () => ({
+ isModalVisible: [
+ false,
+ {
+ showModal: () => true,
+ hideModal: () => false,
+ },
+ ],
+ engineName: [
+ '',
+ {
+ showModal: (_, { engineName }) => engineName,
+ hideModal: () => '',
+ },
+ ],
+ }),
+});
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/tables/engine_link_helpers.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/tables/engine_link_helpers.tsx
index a3350d1ef9939c..229e0def4700e9 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/tables/engine_link_helpers.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/tables/engine_link_helpers.tsx
@@ -7,11 +7,14 @@
import React from 'react';
+import { EuiLink } from '@elastic/eui';
+
import { KibanaLogic } from '../../../../../shared/kibana';
import { EuiLinkTo } from '../../../../../shared/react_router_helpers';
import { TelemetryLogic } from '../../../../../shared/telemetry';
import { ENGINE_PATH } from '../../../../routes';
import { generateEncodedPath } from '../../../../utils/encode_path_params';
+import { FormattedDateTime } from '../../../../utils/formatted_date_time';
const sendEngineTableLinkClickTelemetry = () => {
TelemetryLogic.actions.sendAppSearchTelemetry({
@@ -34,3 +37,9 @@ export const renderEngineLink = (engineName: string) => (
{engineName}
);
+
+export const renderLastChangeLink = (dateString: string, onClick = () => {}) => (
+
+ {!dateString ? '-' : }
+
+);
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/tables/engines_table.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/tables/engines_table.tsx
index 563e272a4a7303..5e6ece1003e7fc 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/tables/engines_table.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/tables/engines_table.tsx
@@ -7,7 +7,7 @@
import React from 'react';
-import { useValues } from 'kea';
+import { useActions, useValues } from 'kea';
import { EuiBasicTable, EuiBasicTableColumn, EuiTableFieldDataColumnType } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
@@ -16,10 +16,13 @@ import { AppLogic } from '../../../../app_logic';
import { UNIVERSAL_LANGUAGE } from '../../../../constants';
import { EngineDetails } from '../../../engine/types';
-import { renderEngineLink } from './engine_link_helpers';
+import { AuditLogsModalLogic } from '../audit_logs_modal/audit_logs_modal_logic';
+
+import { renderEngineLink, renderLastChangeLink } from './engine_link_helpers';
import {
ACTIONS_COLUMN,
CREATED_AT_COLUMN,
+ LAST_UPDATED_COLUMN,
DOCUMENT_COUNT_COLUMN,
FIELD_COUNT_COLUMN,
NAME_COLUMN,
@@ -46,12 +49,22 @@ export const EnginesTable: React.FC = ({
myRole: { canManageEngines },
} = useValues(AppLogic);
+ const { showModal: showAuditLogModal } = useActions(AuditLogsModalLogic);
+
const columns: Array> = [
{
...NAME_COLUMN,
render: (name: string) => renderEngineLink(name),
},
CREATED_AT_COLUMN,
+ {
+ ...LAST_UPDATED_COLUMN,
+ render: (dateString: string, engineDetails) => {
+ return renderLastChangeLink(dateString, () => {
+ showAuditLogModal(engineDetails.name);
+ });
+ },
+ },
LANGUAGE_COLUMN,
DOCUMENT_COUNT_COLUMN,
FIELD_COUNT_COLUMN,
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/tables/meta_engines_table.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/tables/meta_engines_table.tsx
index f99dc7e15eaec5..24eb8cc8a6b812 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/tables/meta_engines_table.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/tables/meta_engines_table.tsx
@@ -14,6 +14,9 @@ import { EuiBasicTable, EuiBasicTableColumn } from '@elastic/eui';
import { AppLogic } from '../../../../app_logic';
import { EngineDetails } from '../../../engine/types';
+import { AuditLogsModalLogic } from '../audit_logs_modal/audit_logs_modal_logic';
+
+import { renderLastChangeLink } from './engine_link_helpers';
import { MetaEnginesTableExpandedRow } from './meta_engines_table_expanded_row';
import { MetaEnginesTableLogic } from './meta_engines_table_logic';
import { MetaEnginesTableNameColumnContent } from './meta_engines_table_name_column_content';
@@ -21,6 +24,7 @@ import {
ACTIONS_COLUMN,
BLANK_COLUMN,
CREATED_AT_COLUMN,
+ LAST_UPDATED_COLUMN,
DOCUMENT_COUNT_COLUMN,
FIELD_COUNT_COLUMN,
NAME_COLUMN,
@@ -49,6 +53,8 @@ export const MetaEnginesTable: React.FC = ({
myRole: { canManageMetaEngines },
} = useValues(AppLogic);
+ const { showModal: showAuditLogModal } = useActions(AuditLogsModalLogic);
+
const conflictingEnginesSets: ConflictingEnginesSets = useMemo(
() =>
items.reduce((accumulator, metaEngine) => {
@@ -89,6 +95,14 @@ export const MetaEnginesTable: React.FC = ({
),
},
CREATED_AT_COLUMN,
+ {
+ ...LAST_UPDATED_COLUMN,
+ render: (dateString: string, engineDetails) => {
+ return renderLastChangeLink(dateString, () => {
+ showAuditLogModal(engineDetails.name);
+ });
+ },
+ },
BLANK_COLUMN,
DOCUMENT_COUNT_COLUMN,
FIELD_COUNT_COLUMN,
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/tables/shared_columns.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/tables/shared_columns.tsx
index 325760b641efdc..b0ca36a7778389 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/tables/shared_columns.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/tables/shared_columns.tsx
@@ -50,6 +50,17 @@ export const CREATED_AT_COLUMN: EuiTableFieldDataColumnType = {
render: (dateString: string) => ,
};
+export const LAST_UPDATED_COLUMN: EuiTableFieldDataColumnType = {
+ field: 'updated_at',
+ name: i18n.translate(
+ 'xpack.enterpriseSearch.appSearch.enginesOverview.table.column.lastUpdated',
+ {
+ defaultMessage: 'Last updated',
+ }
+ ),
+ dataType: 'string',
+};
+
export const DOCUMENT_COUNT_COLUMN: EuiTableFieldDataColumnType = {
field: 'document_count',
name: i18n.translate(
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/engines_overview.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/engines_overview.tsx
index f8df9f5abfaa54..27cdff5d69812e 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/engines_overview.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/engines_overview.tsx
@@ -21,6 +21,7 @@ import { DataPanel } from '../data_panel';
import { AppSearchPageTemplate } from '../layout';
import { EmptyState, EmptyMetaEnginesState } from './components';
+import { AuditLogsModal } from './components/audit_logs_modal/audit_logs_modal';
import { EnginesTable } from './components/tables/engines_table';
import { MetaEnginesTable } from './components/tables/meta_engines_table';
import {
@@ -144,6 +145,7 @@ export const EnginesOverview: React.FC = () => {
data-test-subj="metaEnginesLicenseCTA"
/>
)}
+
);
};
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/utils/role/get_role_abilities.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/utils/role/get_role_abilities.test.ts
index 60d0dcc0c5911e..0d5e4e9824f170 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/utils/role/get_role_abilities.test.ts
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/utils/role/get_role_abilities.test.ts
@@ -23,6 +23,7 @@ describe('getRoleAbilities', () => {
// Has access
canViewAccountCredentials: true,
canManageEngines: true,
+ canManageMetaEngines: true,
// Does not have access
canViewMetaEngines: false,
canViewEngineAnalytics: false,
@@ -35,7 +36,6 @@ describe('getRoleAbilities', () => {
canViewMetaEngineSourceEngines: false,
canViewSettings: false,
canViewRoleMappings: false,
- canManageMetaEngines: false,
canManageLogSettings: false,
canManageSettings: false,
canManageEngineCrawler: false,
@@ -81,10 +81,10 @@ describe('getRoleAbilities', () => {
expect(myRole.canManageMetaEngines).toEqual(true);
});
- it('returns false when the user can manage any engines but the account does not have a platinum license', () => {
+ it('returns true when the user can manage any engines but the account does not have a platinum license', () => {
const myRole = getRoleAbilities({ ...mockRole, ...canManageEngines }, false);
- expect(myRole.canManageMetaEngines).toEqual(false);
+ expect(myRole.canManageMetaEngines).toEqual(true);
});
it('returns false when has a platinum license but the user cannot manage any engines', () => {
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/utils/role/get_role_abilities.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/utils/role/get_role_abilities.ts
index ef3e22d851f387..15cba16ab04345 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/utils/role/get_role_abilities.ts
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/utils/role/get_role_abilities.ts
@@ -49,7 +49,7 @@ export const getRoleAbilities = (role: Account['role'], hasPlatinumLicense = fal
canViewSettings: myRole.can('view', 'account_settings'),
canViewRoleMappings: myRole.can('view', 'role_mappings'),
canManageEngines: myRole.can('manage', 'account_engines'),
- canManageMetaEngines: hasPlatinumLicense && myRole.can('manage', 'account_engines'),
+ canManageMetaEngines: myRole.can('manage', 'account_engines'),
canManageLogSettings: myRole.can('manage', 'account_log_settings'),
canManageSettings: myRole.can('manage', 'account_settings'),
canManageEngineCrawler: myRole.can('manage', 'engine_crawler'),
diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source.test.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source.test.tsx
index 4598ca337f4e2c..76c6c3cfa9d592 100644
--- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source.test.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source.test.tsx
@@ -27,6 +27,7 @@ import { staticSourceData } from '../../source_data';
import { AddSource } from './add_source';
import { AddSourceSteps } from './add_source_logic';
import { ConfigCompleted } from './config_completed';
+import { ConfigurationChoice } from './configuration_choice';
import { ConfigurationIntro } from './configuration_intro';
import { ConfigureOauth } from './configure_oauth';
import { ConnectInstance } from './connect_instance';
@@ -71,6 +72,22 @@ describe('AddSourceList', () => {
expect(setAddSourceStep).toHaveBeenCalledWith(AddSourceSteps.SaveConfigStep);
});
+ it('renders default state correctly when there are multiple connector options', () => {
+ const wrapper = shallow(
+
+ );
+ wrapper.find(ConfigurationIntro).prop('advanceStep')();
+
+ expect(setAddSourceStep).toHaveBeenCalledWith(AddSourceSteps.ChoiceStep);
+ });
+
describe('layout', () => {
it('renders the default workplace search layout when on an organization view', () => {
setMockValues({ ...mockValues, isOrganization: true });
@@ -153,4 +170,19 @@ describe('AddSourceList', () => {
expect(wrapper.find(Reauthenticate)).toHaveLength(1);
});
+
+ it('renders Config Choice step', () => {
+ setMockValues({
+ ...mockValues,
+ addSourceCurrentStep: AddSourceSteps.ChoiceStep,
+ });
+ const wrapper = shallow();
+ const advance = wrapper.find(ConfigurationChoice).prop('goToInternalStep');
+ expect(advance).toBeDefined();
+ if (advance) {
+ advance();
+ }
+
+ expect(setAddSourceStep).toHaveBeenCalledWith(AddSourceSteps.SaveConfigStep);
+ });
});
diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source.tsx
index 1e9be74224c5ed..f03c77290f22dd 100644
--- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source.tsx
@@ -21,9 +21,12 @@ import {
import { NAV } from '../../../../constants';
import { SOURCES_PATH, getSourcesPath, getAddPath } from '../../../../routes';
+import { hasMultipleConnectorOptions } from '../../../../utils';
+
import { AddSourceHeader } from './add_source_header';
import { AddSourceLogic, AddSourceProps, AddSourceSteps } from './add_source_logic';
import { ConfigCompleted } from './config_completed';
+import { ConfigurationChoice } from './configuration_choice';
import { ConfigurationIntro } from './configuration_intro';
import { ConfigureOauth } from './configure_oauth';
import { ConnectInstance } from './connect_instance';
@@ -51,6 +54,7 @@ export const AddSource: React.FC = (props) => {
const goToSaveConfig = () => setAddSourceStep(AddSourceSteps.SaveConfigStep);
const setConfigCompletedStep = () => setAddSourceStep(AddSourceSteps.ConfigCompletedStep);
const goToConfigCompleted = () => saveSourceConfig(false, setConfigCompletedStep);
+ const goToChoice = () => setAddSourceStep(AddSourceSteps.ChoiceStep);
const FORM_SOURCE_ADDED_SUCCESS_MESSAGE = i18n.translate(
'xpack.enterpriseSearch.workplaceSearch.contentSource.formSourceAddedSuccessMessage',
{
@@ -75,7 +79,11 @@ export const AddSource: React.FC = (props) => {
return (
{addSourceCurrentStep === AddSourceSteps.ConfigIntroStep && (
-
+
)}
{addSourceCurrentStep === AddSourceSteps.SaveConfigStep && (
= (props) => {
{addSourceCurrentStep === AddSourceSteps.ReauthenticateStep && (
)}
+ {addSourceCurrentStep === AddSourceSteps.ChoiceStep && (
+
+ )}
);
};
diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source_logic.test.ts
index 80f8a2fc18218d..a633beac3a1c23 100644
--- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source_logic.test.ts
+++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source_logic.test.ts
@@ -664,11 +664,13 @@ describe('AddSourceLogic', () => {
});
it('handles error', async () => {
+ const setButtonNotLoadingSpy = jest.spyOn(AddSourceLogic.actions, 'setButtonNotLoading');
http.post.mockReturnValue(Promise.reject('this is an error'));
AddSourceLogic.actions.createContentSource(serviceType, successCallback, errorCallback);
await nextTick();
+ expect(setButtonNotLoadingSpy).toHaveBeenCalled();
expect(errorCallback).toHaveBeenCalled();
expect(flashAPIErrors).toHaveBeenCalledWith('this is an error');
});
diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source_logic.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source_logic.ts
index db0c5b97372636..92fab713a3fa09 100644
--- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source_logic.ts
+++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source_logic.ts
@@ -41,6 +41,7 @@ export enum AddSourceSteps {
ConnectInstanceStep = 'Connect Instance',
ConfigureOauthStep = 'Configure Oauth',
ReauthenticateStep = 'Reauthenticate',
+ ChoiceStep = 'Choice',
}
export interface OauthParams {
diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/configuration_choice.test.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/configuration_choice.test.tsx
index bfb916847d865e..392ce175d271db 100644
--- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/configuration_choice.test.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/configuration_choice.test.tsx
@@ -13,10 +13,6 @@ import { shallow } from 'enzyme';
import { EuiText, EuiButton } from '@elastic/eui';
-import {
- PersonalDashboardLayout,
- WorkplaceSearchPageTemplate,
-} from '../../../../components/layout';
import { staticSourceData } from '../../source_data';
import { ConfigurationChoice } from './configuration_choice';
@@ -35,22 +31,6 @@ describe('ConfigurationChoice', () => {
jest.clearAllMocks();
});
- describe('layout', () => {
- it('renders the default workplace search layout when on an organization view', () => {
- setMockValues({ ...mockValues, isOrganization: true });
- const wrapper = shallow();
-
- expect(wrapper.type()).toEqual(WorkplaceSearchPageTemplate);
- });
-
- it('renders the personal dashboard layout when not in an organization', () => {
- setMockValues({ ...mockValues, isOrganization: false });
- const wrapper = shallow();
-
- expect(wrapper.type()).toEqual(PersonalDashboardLayout);
- });
- });
-
it('renders internal connector if available', () => {
const wrapper = shallow();
@@ -64,6 +44,16 @@ describe('ConfigurationChoice', () => {
button.simulate('click');
expect(navigateToUrl).toHaveBeenCalledWith('/sources/add/box/internal/');
});
+ it('should call prop function when provided on internal connector click', () => {
+ const advanceSpy = jest.fn();
+ const wrapper = shallow(
+
+ );
+ const button = wrapper.find(EuiButton);
+ button.simulate('click');
+ expect(navigateToUrl).not.toHaveBeenCalled();
+ expect(advanceSpy).toHaveBeenCalled();
+ });
it('renders external connector if available', () => {
const wrapper = shallow(
diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/configuration_choice.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/configuration_choice.tsx
index 46a8998c9dd10a..f5d6d51651dd4d 100644
--- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/configuration_choice.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/configuration_choice.tsx
@@ -9,16 +9,11 @@ import React from 'react';
import { useValues } from 'kea';
-import { EuiButton, EuiFlexGroup, EuiFlexItem, EuiPanel, EuiSpacer, EuiText } from '@elastic/eui';
+import { EuiButton, EuiFlexGroup, EuiFlexItem, EuiPanel, EuiText } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { KibanaLogic } from '../../../../../shared/kibana';
import { AppLogic } from '../../../../app_logic';
-import {
- WorkplaceSearchPageTemplate,
- PersonalDashboardLayout,
-} from '../../../../components/layout';
-import { NAV } from '../../../../constants';
import { getAddPath, getSourcesPath } from '../../../../routes';
import { SourceDataItem } from '../../../../types';
@@ -26,6 +21,7 @@ import { AddSourceHeader } from './add_source_header';
interface ConfigurationIntroProps {
sourceData: SourceDataItem;
+ goToInternalStep?: () => void;
}
export const ConfigurationChoice: React.FC = ({
@@ -36,15 +32,18 @@ export const ConfigurationChoice: React.FC = ({
internalConnectorAvailable,
customConnectorAvailable,
},
+ goToInternalStep,
}) => {
const { isOrganization } = useValues(AppLogic);
- const goToInternal = () =>
- KibanaLogic.values.navigateToUrl(
- `${getSourcesPath(
- `${getSourcesPath(getAddPath(serviceType), isOrganization)}/internal`,
- isOrganization
- )}/`
- );
+ const goToInternal = goToInternalStep
+ ? goToInternalStep
+ : () =>
+ KibanaLogic.values.navigateToUrl(
+ `${getSourcesPath(
+ `${getSourcesPath(getAddPath(serviceType), isOrganization)}/internal`,
+ isOrganization
+ )}/`
+ );
const goToExternal = () =>
KibanaLogic.values.navigateToUrl(
`${getSourcesPath(
@@ -59,12 +58,10 @@ export const ConfigurationChoice: React.FC = ({
isOrganization
)}/`
);
- const Layout = isOrganization ? WorkplaceSearchPageTemplate : PersonalDashboardLayout;
return (
-
+ <>
-
= ({
)}
-
+ >
);
};
diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/connect_instance.test.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/connect_instance.test.tsx
index 0ee80019ea720e..0ae176dbef019f 100644
--- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/connect_instance.test.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/connect_instance.test.tsx
@@ -36,9 +36,8 @@ describe('ConnectInstance', () => {
const getSourceConnectData = jest.fn((_, redirectOauth) => {
redirectOauth();
});
- const createContentSource = jest.fn((_, redirectFormCreated, handleFormSubmitError) => {
+ const createContentSource = jest.fn((_, redirectFormCreated) => {
redirectFormCreated();
- handleFormSubmitError();
});
const credentialsSourceData = staticSourceData[13];
diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/connect_instance.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/connect_instance.tsx
index a9e24c7b944aba..352addd8176d84 100644
--- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/connect_instance.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/connect_instance.tsx
@@ -5,7 +5,7 @@
* 2.0.
*/
-import React, { useState, useEffect, FormEvent } from 'react';
+import React, { useEffect, FormEvent } from 'react';
import { useActions, useValues } from 'kea';
@@ -51,8 +51,6 @@ export const ConnectInstance: React.FC = ({
onFormCreated,
header,
}) => {
- const [formLoading, setFormLoading] = useState(false);
-
const { hasPlatinumLicense } = useValues(LicensingLogic);
const {
@@ -64,7 +62,7 @@ export const ConnectInstance: React.FC = ({
setSourceIndexPermissionsValue,
} = useActions(AddSourceLogic);
- const { loginValue, passwordValue, indexPermissionsValue, subdomainValue } =
+ const { buttonLoading, loginValue, passwordValue, indexPermissionsValue, subdomainValue } =
useValues(AddSourceLogic);
const { isOrganization } = useValues(AppLogic);
@@ -77,12 +75,9 @@ export const ConnectInstance: React.FC = ({
const redirectOauth = (oauthUrl: string) => window.location.replace(oauthUrl);
const redirectFormCreated = () => onFormCreated(name);
const onOauthFormSubmit = () => getSourceConnectData(serviceType, redirectOauth);
- const handleFormSubmitError = () => setFormLoading(false);
- const onCredentialsFormSubmit = () =>
- createContentSource(serviceType, redirectFormCreated, handleFormSubmitError);
+ const onCredentialsFormSubmit = () => createContentSource(serviceType, redirectFormCreated);
const handleFormSubmit = (e: FormEvent) => {
- setFormLoading(true);
e.preventDefault();
const onSubmit = hasOauthRedirect ? onOauthFormSubmit : onCredentialsFormSubmit;
onSubmit();
@@ -145,7 +140,7 @@ export const ConnectInstance: React.FC = ({
{permissionsExcluded && !hasPlatinumLicense && }
-
+
{i18n.translate('xpack.enterpriseSearch.workplaceSearch.contentSource.connect.button', {
defaultMessage: 'Connect {name}',
values: { name },
diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/sources_router.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/sources_router.tsx
index c2cd58a90f209d..e735119f687cc1 100644
--- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/sources_router.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/sources_router.tsx
@@ -90,9 +90,10 @@ export const SourcesRouter: React.FC = () => {
: externalConnectorAvailable
? 'external'
: 'custom';
+ const showChoice = defaultOption !== 'internal' && hasMultipleConnectorOptions(sourceData);
return (
- {hasMultipleConnectorOptions(sourceData) ? (
+ {showChoice ? (
) : (
diff --git a/x-pack/plugins/enterprise_search/server/plugin.ts b/x-pack/plugins/enterprise_search/server/plugin.ts
index ef9a0cea9da60f..f393ca59a44118 100644
--- a/x-pack/plugins/enterprise_search/server/plugin.ts
+++ b/x-pack/plugins/enterprise_search/server/plugin.ts
@@ -27,6 +27,7 @@ import {
APP_SEARCH_PLUGIN,
WORKPLACE_SEARCH_PLUGIN,
LOGS_SOURCE_ID,
+ ENTERPRISE_SEARCH_AUDIT_LOGS_SOURCE_ID,
} from '../common/constants';
import { registerTelemetryUsageCollector as registerASTelemetryUsageCollector } from './collectors/app_search/telemetry';
@@ -185,6 +186,14 @@ export class EnterpriseSearchPlugin implements Plugin {
indexName: '.ent-search-*',
},
});
+
+ infra.defineInternalSourceConfiguration(ENTERPRISE_SEARCH_AUDIT_LOGS_SOURCE_ID, {
+ name: 'Enterprise Search Audit Logs',
+ logIndices: {
+ type: 'index_name',
+ indexName: 'logs-enterprise_search*',
+ },
+ });
}
public start() {}
diff --git a/x-pack/plugins/fleet/server/services/epm/kibana/assets/install.test.ts b/x-pack/plugins/fleet/server/services/epm/kibana/assets/install.test.ts
new file mode 100644
index 00000000000000..51aee45c83cf3d
--- /dev/null
+++ b/x-pack/plugins/fleet/server/services/epm/kibana/assets/install.test.ts
@@ -0,0 +1,124 @@
+/*
+ * 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 type {
+ ISavedObjectsImporter,
+ SavedObjectsImportFailure,
+ SavedObjectsImportSuccess,
+ SavedObjectsImportResponse,
+} from 'src/core/server';
+
+import { loggingSystemMock } from '../../../../../../../../src/core/server/mocks';
+
+import type { ArchiveAsset } from './install';
+
+jest.mock('timers/promises', () => ({
+ async setTimeout() {},
+}));
+
+import { installKibanaSavedObjects } from './install';
+
+const mockLogger = loggingSystemMock.createLogger();
+
+const mockImporter: jest.Mocked = {
+ import: jest.fn(),
+ resolveImportErrors: jest.fn(),
+};
+
+const createImportError = (so: ArchiveAsset, type: string) =>
+ ({ id: so.id, error: { type } } as SavedObjectsImportFailure);
+const createImportSuccess = (so: ArchiveAsset) =>
+ ({ id: so.id, type: so.type, meta: {} } as SavedObjectsImportSuccess);
+const createAsset = (asset: Partial) =>
+ ({ id: 1234, type: 'dashboard', attributes: {}, ...asset } as ArchiveAsset);
+
+const createImportResponse = (
+ errors: SavedObjectsImportFailure[] = [],
+ successResults: SavedObjectsImportSuccess[] = []
+) =>
+ ({
+ success: !!successResults.length,
+ errors,
+ successResults,
+ warnings: [],
+ successCount: successResults.length,
+ } as SavedObjectsImportResponse);
+
+describe('installKibanaSavedObjects', () => {
+ beforeEach(() => {
+ mockImporter.import.mockReset();
+ mockImporter.resolveImportErrors.mockReset();
+ });
+
+ it('should retry on conflict error', async () => {
+ const asset = createAsset({ attributes: { hello: 'world' } });
+ const conflictResponse = createImportResponse([createImportError(asset, 'conflict')]);
+ const successResponse = createImportResponse([], [createImportSuccess(asset)]);
+
+ mockImporter.import
+ .mockResolvedValueOnce(conflictResponse)
+ .mockResolvedValueOnce(successResponse);
+
+ await installKibanaSavedObjects({
+ savedObjectsImporter: mockImporter,
+ logger: mockLogger,
+ kibanaAssets: [asset],
+ });
+
+ expect(mockImporter.import).toHaveBeenCalledTimes(2);
+ });
+
+ it('should give up after 50 retries on conflict errors', async () => {
+ const asset = createAsset({ attributes: { hello: 'world' } });
+ const conflictResponse = createImportResponse([createImportError(asset, 'conflict')]);
+
+ mockImporter.import.mockImplementation(() => Promise.resolve(conflictResponse));
+
+ await expect(
+ installKibanaSavedObjects({
+ savedObjectsImporter: mockImporter,
+ logger: mockLogger,
+ kibanaAssets: [asset],
+ })
+ ).rejects.toEqual(expect.any(Error));
+ expect(mockImporter.import).toHaveBeenCalledTimes(51);
+ });
+ it('should not retry errors that arent conflict errors', async () => {
+ const asset = createAsset({ attributes: { hello: 'world' } });
+ const errorResponse = createImportResponse([createImportError(asset, 'something_bad')]);
+ const successResponse = createImportResponse([], [createImportSuccess(asset)]);
+
+ mockImporter.import.mockResolvedValueOnce(errorResponse).mockResolvedValueOnce(successResponse);
+
+ expect(
+ installKibanaSavedObjects({
+ savedObjectsImporter: mockImporter,
+ logger: mockLogger,
+ kibanaAssets: [asset],
+ })
+ ).rejects.toEqual(expect.any(Error));
+ });
+
+ it('should resolve reference errors', async () => {
+ const asset = createAsset({ attributes: { hello: 'world' } });
+ const referenceErrorResponse = createImportResponse([
+ createImportError(asset, 'missing_references'),
+ ]);
+ const successResponse = createImportResponse([], [createImportSuccess(asset)]);
+
+ mockImporter.import.mockResolvedValueOnce(referenceErrorResponse);
+ mockImporter.resolveImportErrors.mockResolvedValueOnce(successResponse);
+
+ await installKibanaSavedObjects({
+ savedObjectsImporter: mockImporter,
+ logger: mockLogger,
+ kibanaAssets: [asset],
+ });
+
+ expect(mockImporter.import).toHaveBeenCalledTimes(1);
+ expect(mockImporter.resolveImportErrors).toHaveBeenCalledTimes(1);
+ });
+});
diff --git a/x-pack/plugins/fleet/server/services/epm/kibana/assets/install.ts b/x-pack/plugins/fleet/server/services/epm/kibana/assets/install.ts
index 5ab15a1f52e755..d654fab427f198 100644
--- a/x-pack/plugins/fleet/server/services/epm/kibana/assets/install.ts
+++ b/x-pack/plugins/fleet/server/services/epm/kibana/assets/install.ts
@@ -5,6 +5,8 @@
* 2.0.
*/
+import { setTimeout } from 'timers/promises';
+
import type {
SavedObject,
SavedObjectsBulkCreateObject,
@@ -13,7 +15,6 @@ import type {
Logger,
} from 'src/core/server';
import type { SavedObjectsImportSuccess, SavedObjectsImportFailure } from 'src/core/server/types';
-
import { createListStream } from '@kbn/utils';
import { partition } from 'lodash';
@@ -166,7 +167,40 @@ export async function getKibanaAssets(
return result;
}
-async function installKibanaSavedObjects({
+const isImportConflictError = (e: SavedObjectsImportFailure) => e?.error?.type === 'conflict';
+/**
+ * retry saved object import if only conflict errors are encountered
+ */
+async function retryImportOnConflictError(
+ importCall: () => ReturnType,
+ {
+ logger,
+ maxAttempts = 50,
+ _attempt = 0,
+ }: { logger?: Logger; _attempt?: number; maxAttempts?: number } = {}
+): ReturnType {
+ const result = await importCall();
+
+ const errors = result.errors ?? [];
+ if (_attempt < maxAttempts && errors.length && errors.every(isImportConflictError)) {
+ const retryCount = _attempt + 1;
+ const retryDelayMs = 1000 + Math.floor(Math.random() * 3000); // 1s + 0-3s of jitter
+
+ logger?.debug(
+ `Retrying import operation after [${
+ retryDelayMs * 1000
+ }s] due to conflict errors: ${JSON.stringify(errors)}`
+ );
+
+ await setTimeout(retryDelayMs);
+ return retryImportOnConflictError(importCall, { logger, _attempt: retryCount });
+ }
+
+ return result;
+}
+
+// only exported for testing
+export async function installKibanaSavedObjects({
savedObjectsImporter,
kibanaAssets,
logger,
@@ -185,18 +219,19 @@ async function installKibanaSavedObjects({
return [];
} else {
const { successResults: importSuccessResults = [], errors: importErrors = [] } =
- await savedObjectsImporter.import({
- overwrite: true,
- readStream: createListStream(toBeSavedObjects),
- createNewCopies: false,
- });
+ await retryImportOnConflictError(() =>
+ savedObjectsImporter.import({
+ overwrite: true,
+ readStream: createListStream(toBeSavedObjects),
+ createNewCopies: false,
+ })
+ );
allSuccessResults = importSuccessResults;
const [referenceErrors, otherErrors] = partition(
importErrors,
(e) => e?.error?.type === 'missing_references'
);
-
if (otherErrors?.length) {
throw new Error(
`Encountered ${
diff --git a/x-pack/plugins/lens/public/embeddable/embeddable_component.tsx b/x-pack/plugins/lens/public/embeddable/embeddable_component.tsx
index 482a5b931ed78a..f44aef76ab83d4 100644
--- a/x-pack/plugins/lens/public/embeddable/embeddable_component.tsx
+++ b/x-pack/plugins/lens/public/embeddable/embeddable_component.tsx
@@ -11,6 +11,7 @@ import type { Action, UiActionsStart } from 'src/plugins/ui_actions/public';
import type { Start as InspectorStartContract } from 'src/plugins/inspector/public';
import { EuiLoadingChart } from '@elastic/eui';
import {
+ EmbeddableFactory,
EmbeddableInput,
EmbeddableOutput,
EmbeddablePanel,
@@ -69,41 +70,48 @@ interface PluginsStartDependencies {
}
export function getEmbeddableComponent(core: CoreStart, plugins: PluginsStartDependencies) {
+ const { embeddable: embeddableStart, uiActions, inspector } = plugins;
+ const factory = embeddableStart.getEmbeddableFactory('lens')!;
+ const theme = core.theme;
return (props: EmbeddableComponentProps) => {
- const { embeddable: embeddableStart, uiActions, inspector } = plugins;
- const factory = embeddableStart.getEmbeddableFactory('lens')!;
const input = { ...props };
- const [embeddable, loading, error] = useEmbeddableFactory({ factory, input });
const hasActions =
- Boolean(props.withDefaultActions) || (props.extraActions && props.extraActions?.length > 0);
+ Boolean(input.withDefaultActions) || (input.extraActions && input.extraActions?.length > 0);
- const theme = core.theme;
-
- if (loading) {
- return ;
- }
-
- if (embeddable && hasActions) {
+ if (hasActions) {
return (
}
+ factory={factory}
uiActions={uiActions}
inspector={inspector}
actionPredicate={() => hasActions}
input={input}
theme={theme}
- extraActions={props.extraActions}
- withDefaultActions={props.withDefaultActions}
+ extraActions={input.extraActions}
+ withDefaultActions={input.withDefaultActions}
/>
);
}
-
- return ;
+ return ;
};
}
+function EmbeddableRootWrapper({
+ factory,
+ input,
+}: {
+ factory: EmbeddableFactory;
+ input: EmbeddableComponentProps;
+}) {
+ const [embeddable, loading, error] = useEmbeddableFactory({ factory, input });
+ if (loading) {
+ return ;
+ }
+ return ;
+}
+
interface EmbeddablePanelWrapperProps {
- embeddable: IEmbeddable;
+ factory: EmbeddableFactory;
uiActions: PluginsStartDependencies['uiActions'];
inspector: PluginsStartDependencies['inspector'];
actionPredicate: (id: string) => boolean;
@@ -114,7 +122,7 @@ interface EmbeddablePanelWrapperProps {
}
const EmbeddablePanelWrapper: FC = ({
- embeddable,
+ factory,
uiActions,
actionPredicate,
inspector,
@@ -123,10 +131,17 @@ const EmbeddablePanelWrapper: FC = ({
extraActions,
withDefaultActions,
}) => {
+ const [embeddable, loading] = useEmbeddableFactory({ factory, input });
useEffect(() => {
- embeddable.updateInput(input);
+ if (embeddable) {
+ embeddable.updateInput(input);
+ }
}, [embeddable, input]);
+ if (loading || !embeddable) {
+ return ;
+ }
+
return (
) {
return async (
dispatch: ThunkDispatch,
getState: () => MapStoreState
@@ -369,7 +375,10 @@ export function addNewFeatureToIndex(geometry: Geometry | Position[]) {
}
try {
- await (layer as IVectorLayer).addFeature(geometry);
+ dispatch(updateEditShape(DRAW_SHAPE.WAIT));
+ await asyncForEach(geometries, async (geometry) => {
+ await (layer as IVectorLayer).addFeature(geometry);
+ });
await dispatch(syncDataForLayerDueToDrawing(layer));
} catch (e) {
getToasts().addError(e, {
@@ -378,6 +387,7 @@ export function addNewFeatureToIndex(geometry: Geometry | Position[]) {
}),
});
}
+ dispatch(updateEditShape(DRAW_SHAPE.SIMPLE_SELECT));
};
}
@@ -386,6 +396,12 @@ export function deleteFeatureFromIndex(featureId: string) {
dispatch: ThunkDispatch,
getState: () => MapStoreState
) => {
+ // There is a race condition where users can click on a previously deleted feature before layer has re-rendered after feature delete.
+ // Check ensures delete requests for previously deleted features are aborted.
+ if (getDeletedFeatureIds(getState()).includes(featureId)) {
+ return;
+ }
+
const editState = getEditState(getState());
const layerId = editState ? editState.layerId : undefined;
if (!layerId) {
@@ -395,8 +411,11 @@ export function deleteFeatureFromIndex(featureId: string) {
if (!layer || !isVectorLayer(layer)) {
return;
}
+
try {
+ dispatch(updateEditShape(DRAW_SHAPE.WAIT));
await (layer as IVectorLayer).deleteFeature(featureId);
+ dispatch(pushDeletedFeatureId(featureId));
await dispatch(syncDataForLayerDueToDrawing(layer));
} catch (e) {
getToasts().addError(e, {
@@ -405,5 +424,6 @@ export function deleteFeatureFromIndex(featureId: string) {
}),
});
}
+ dispatch(updateEditShape(DRAW_SHAPE.DELETE));
};
}
diff --git a/x-pack/plugins/maps/public/actions/ui_actions.ts b/x-pack/plugins/maps/public/actions/ui_actions.ts
index 70e24283ef48f7..1ffcf416f6f8fa 100644
--- a/x-pack/plugins/maps/public/actions/ui_actions.ts
+++ b/x-pack/plugins/maps/public/actions/ui_actions.ts
@@ -24,6 +24,8 @@ export const SET_OPEN_TOC_DETAILS = 'SET_OPEN_TOC_DETAILS';
export const SHOW_TOC_DETAILS = 'SHOW_TOC_DETAILS';
export const HIDE_TOC_DETAILS = 'HIDE_TOC_DETAILS';
export const SET_DRAW_MODE = 'SET_DRAW_MODE';
+export const PUSH_DELETED_FEATURE_ID = 'PUSH_DELETED_FEATURE_ID';
+export const CLEAR_DELETED_FEATURE_IDS = 'CLEAR_DELETED_FEATURE_IDS';
export function exitFullScreen() {
return {
@@ -123,3 +125,16 @@ export function closeTimeslider() {
dispatch(setQuery({ clearTimeslice: true }));
};
}
+
+export function pushDeletedFeatureId(featureId: string) {
+ return {
+ type: PUSH_DELETED_FEATURE_ID,
+ featureId,
+ };
+}
+
+export function clearDeletedFeatureIds() {
+ return {
+ type: CLEAR_DELETED_FEATURE_IDS,
+ };
+}
diff --git a/x-pack/plugins/maps/public/connected_components/mb_map/draw_control/draw_control.tsx b/x-pack/plugins/maps/public/connected_components/mb_map/draw_control/draw_control.tsx
index a404db91a942ef..8cbfcd3a41e80e 100644
--- a/x-pack/plugins/maps/public/connected_components/mb_map/draw_control/draw_control.tsx
+++ b/x-pack/plugins/maps/public/connected_components/mb_map/draw_control/draw_control.tsx
@@ -20,15 +20,6 @@ import { DRAW_SHAPE } from '../../../../common/constants';
import { DrawCircle, DRAW_CIRCLE_RADIUS_LABEL_STYLE } from './draw_circle';
import { DrawTooltip } from './draw_tooltip';
-const mbModeEquivalencies = new Map([
- ['simple_select', DRAW_SHAPE.SIMPLE_SELECT],
- ['draw_rectangle', DRAW_SHAPE.BOUNDS],
- ['draw_circle', DRAW_SHAPE.DISTANCE],
- ['draw_polygon', DRAW_SHAPE.POLYGON],
- ['draw_line_string', DRAW_SHAPE.LINE],
- ['draw_point', DRAW_SHAPE.POINT],
-]);
-
const DRAW_RECTANGLE = 'draw_rectangle';
const DRAW_CIRCLE = 'draw_circle';
const mbDrawModes = MapboxDraw.modes;
@@ -41,7 +32,6 @@ export interface Props {
onClick?: (event: MapMouseEvent, drawControl?: MapboxDraw) => void;
mbMap: MbMap;
enable: boolean;
- updateEditShape: (shapeToDraw: DRAW_SHAPE) => void;
}
export class DrawControl extends Component {
@@ -91,12 +81,6 @@ export class DrawControl extends Component {
}
}, 0);
- _onModeChange = ({ mode }: { mode: string }) => {
- if (mbModeEquivalencies.has(mode)) {
- this.props.updateEditShape(mbModeEquivalencies.get(mode)!);
- }
- };
-
_removeDrawControl() {
// Do not remove draw control after mbMap.remove is called, causes execeptions and mbMap.remove cleans up all map resources.
const isMapRemoved = !this.props.mbMap.loaded();
@@ -105,7 +89,6 @@ export class DrawControl extends Component {
}
this.props.mbMap.getCanvas().style.cursor = '';
- this.props.mbMap.off('draw.modechange', this._onModeChange);
this.props.mbMap.off('draw.create', this._onDraw);
if (this.props.onClick) {
this.props.mbMap.off('click', this._onClick);
@@ -118,7 +101,6 @@ export class DrawControl extends Component {
if (!this._mbDrawControlAdded) {
this.props.mbMap.addControl(this._mbDrawControl);
this._mbDrawControlAdded = true;
- this.props.mbMap.on('draw.modechange', this._onModeChange);
this.props.mbMap.on('draw.create', this._onDraw);
if (this.props.onClick) {
@@ -144,6 +126,9 @@ export class DrawControl extends Component {
this._mbDrawControl.changeMode(DRAW_POINT);
} else if (this.props.drawShape === DRAW_SHAPE.DELETE) {
this._mbDrawControl.changeMode(SIMPLE_SELECT);
+ } else if (this.props.drawShape === DRAW_SHAPE.WAIT) {
+ this.props.mbMap.getCanvas().style.cursor = 'wait';
+ this._mbDrawControl.changeMode(SIMPLE_SELECT);
} else {
this._mbDrawControl.changeMode(SIMPLE_SELECT);
}
diff --git a/x-pack/plugins/maps/public/connected_components/mb_map/draw_control/draw_feature_control/draw_feature_control.tsx b/x-pack/plugins/maps/public/connected_components/mb_map/draw_control/draw_feature_control/draw_feature_control.tsx
index 6c7fe9f0ad213a..b6ffacc491030e 100644
--- a/x-pack/plugins/maps/public/connected_components/mb_map/draw_control/draw_feature_control/draw_feature_control.tsx
+++ b/x-pack/plugins/maps/public/connected_components/mb_map/draw_control/draw_feature_control/draw_feature_control.tsx
@@ -15,7 +15,7 @@ import { i18n } from '@kbn/i18n';
import * as jsts from 'jsts';
import { MapMouseEvent } from '@kbn/mapbox-gl';
import { getToasts } from '../../../../kibana_services';
-import { DrawControl } from '../';
+import { DrawControl } from '../draw_control';
import { DRAW_MODE, DRAW_SHAPE } from '../../../../../common/constants';
import { ILayer } from '../../../../classes/layers/layer';
import { EXCLUDE_CENTROID_FEATURES } from '../../../../classes/util/mb_filter_expressions';
@@ -29,9 +29,8 @@ export interface ReduxStateProps {
}
export interface ReduxDispatchProps {
- addNewFeatureToIndex: (geometry: Geometry | Position[]) => void;
+ addNewFeatureToIndex: (geometries: Array) => void;
deleteFeatureFromIndex: (featureId: string) => void;
- disableDrawState: () => void;
}
export interface OwnProps {
@@ -43,6 +42,7 @@ type Props = ReduxStateProps & ReduxDispatchProps & OwnProps;
export class DrawFeatureControl extends Component {
_onDraw = async (e: { features: Feature[] }, mbDrawControl: MapboxDraw) => {
try {
+ const geometries: Array = [];
e.features.forEach((feature: Feature) => {
const { geometry } = geoJSONReader.read(feature);
if (!geometry.isSimple() || !geometry.isValid()) {
@@ -58,9 +58,13 @@ export class DrawFeatureControl extends Component {
this.props.drawMode === DRAW_MODE.DRAW_POINTS
? feature.geometry.coordinates
: feature.geometry;
- this.props.addNewFeatureToIndex(featureGeom);
+ geometries.push(featureGeom);
}
});
+
+ if (geometries.length) {
+ this.props.addNewFeatureToIndex(geometries);
+ }
} catch (error) {
getToasts().addWarning(
i18n.translate('xpack.maps.drawFeatureControl.unableToCreateFeature', {
@@ -71,7 +75,6 @@ export class DrawFeatureControl extends Component {
})
);
} finally {
- this.props.disableDrawState();
try {
mbDrawControl.deleteAll();
} catch (_e) {
@@ -86,6 +89,7 @@ export class DrawFeatureControl extends Component {
if (!this.props.editLayer || this.props.drawShape !== DRAW_SHAPE.DELETE) {
return;
}
+
const mbEditLayerIds = this.props.editLayer
.getMbLayerIds()
.filter((mbLayerId) => !!this.props.mbMap.getLayer(mbLayerId));
diff --git a/x-pack/plugins/maps/public/connected_components/mb_map/draw_control/draw_feature_control/index.ts b/x-pack/plugins/maps/public/connected_components/mb_map/draw_control/draw_feature_control/index.ts
index e1d703173fc2da..d2c369b4bd50a2 100644
--- a/x-pack/plugins/maps/public/connected_components/mb_map/draw_control/draw_feature_control/index.ts
+++ b/x-pack/plugins/maps/public/connected_components/mb_map/draw_control/draw_feature_control/index.ts
@@ -15,7 +15,7 @@ import {
ReduxStateProps,
OwnProps,
} from './draw_feature_control';
-import { addNewFeatureToIndex, deleteFeatureFromIndex, updateEditShape } from '../../../../actions';
+import { addNewFeatureToIndex, deleteFeatureFromIndex } from '../../../../actions';
import { MapStoreState } from '../../../../reducers/store';
import { getEditState, getLayerById } from '../../../../selectors/map_selectors';
import { getDrawMode } from '../../../../selectors/ui_selectors';
@@ -34,15 +34,12 @@ function mapDispatchToProps(
dispatch: ThunkDispatch
): ReduxDispatchProps {
return {
- addNewFeatureToIndex(geometry: Geometry | Position[]) {
- dispatch(addNewFeatureToIndex(geometry));
+ addNewFeatureToIndex(geometries: Array) {
+ dispatch(addNewFeatureToIndex(geometries));
},
deleteFeatureFromIndex(featureId: string) {
dispatch(deleteFeatureFromIndex(featureId));
},
- disableDrawState() {
- dispatch(updateEditShape(null));
- },
};
}
diff --git a/x-pack/plugins/maps/public/connected_components/mb_map/draw_control/draw_filter_control/draw_filter_control.tsx b/x-pack/plugins/maps/public/connected_components/mb_map/draw_control/draw_filter_control/draw_filter_control.tsx
index 2f652506857d2a..98d88d43fc65f9 100644
--- a/x-pack/plugins/maps/public/connected_components/mb_map/draw_control/draw_filter_control/draw_filter_control.tsx
+++ b/x-pack/plugins/maps/public/connected_components/mb_map/draw_control/draw_filter_control/draw_filter_control.tsx
@@ -20,7 +20,7 @@ import {
roundCoordinates,
} from '../../../../../common/elasticsearch_util';
import { getToasts } from '../../../../kibana_services';
-import { DrawControl } from '../';
+import { DrawControl } from '../draw_control';
import { DrawCircleProperties } from '../draw_circle';
export interface Props {
diff --git a/x-pack/plugins/maps/public/connected_components/mb_map/draw_control/index.ts b/x-pack/plugins/maps/public/connected_components/mb_map/draw_control/index.ts
deleted file mode 100644
index b0f1941caec08c..00000000000000
--- a/x-pack/plugins/maps/public/connected_components/mb_map/draw_control/index.ts
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import { ThunkDispatch } from 'redux-thunk';
-import { AnyAction } from 'redux';
-import { connect } from 'react-redux';
-import { updateEditShape } from '../../../actions';
-import { MapStoreState } from '../../../reducers/store';
-import { DrawControl } from './draw_control';
-import { DRAW_SHAPE } from '../../../../common/constants';
-
-function mapDispatchToProps(dispatch: ThunkDispatch) {
- return {
- updateEditShape(shapeToDraw: DRAW_SHAPE) {
- dispatch(updateEditShape(shapeToDraw));
- },
- };
-}
-
-const connected = connect(null, mapDispatchToProps)(DrawControl);
-export { connected as DrawControl };
diff --git a/x-pack/plugins/maps/public/reducers/ui.ts b/x-pack/plugins/maps/public/reducers/ui.ts
index f3f948bb96508a..f0f22c5a8c4a93 100644
--- a/x-pack/plugins/maps/public/reducers/ui.ts
+++ b/x-pack/plugins/maps/public/reducers/ui.ts
@@ -19,6 +19,8 @@ import {
SHOW_TOC_DETAILS,
HIDE_TOC_DETAILS,
SET_DRAW_MODE,
+ PUSH_DELETED_FEATURE_ID,
+ CLEAR_DELETED_FEATURE_IDS,
} from '../actions';
import { DRAW_MODE } from '../../common/constants';
@@ -37,6 +39,7 @@ export type MapUiState = {
isLayerTOCOpen: boolean;
isTimesliderOpen: boolean;
openTOCDetails: string[];
+ deletedFeatureIds: string[];
};
export const DEFAULT_IS_LAYER_TOC_OPEN = true;
@@ -51,6 +54,7 @@ export const DEFAULT_MAP_UI_STATE = {
// storing TOC detail visibility outside of map.layerList because its UI state and not map rendering state.
// This also makes for easy read/write access for embeddables.
openTOCDetails: [],
+ deletedFeatureIds: [],
};
// Reducer
@@ -82,6 +86,16 @@ export function ui(state: MapUiState = DEFAULT_MAP_UI_STATE, action: any) {
return layerId !== action.layerId;
}),
};
+ case PUSH_DELETED_FEATURE_ID:
+ return {
+ ...state,
+ deletedFeatureIds: [...state.deletedFeatureIds, action.featureId],
+ };
+ case CLEAR_DELETED_FEATURE_IDS:
+ return {
+ ...state,
+ deletedFeatureIds: [],
+ };
default:
return state;
}
diff --git a/x-pack/plugins/maps/public/selectors/ui_selectors.ts b/x-pack/plugins/maps/public/selectors/ui_selectors.ts
index 942a5190691a15..6bdf5a35679a73 100644
--- a/x-pack/plugins/maps/public/selectors/ui_selectors.ts
+++ b/x-pack/plugins/maps/public/selectors/ui_selectors.ts
@@ -17,3 +17,4 @@ export const getIsTimesliderOpen = ({ ui }: MapStoreState): boolean => ui.isTime
export const getOpenTOCDetails = ({ ui }: MapStoreState): string[] => ui.openTOCDetails;
export const getIsFullScreen = ({ ui }: MapStoreState): boolean => ui.isFullScreen;
export const getIsReadOnly = ({ ui }: MapStoreState): boolean => ui.isReadOnly;
+export const getDeletedFeatureIds = ({ ui }: MapStoreState): string[] => ui.deletedFeatureIds;
diff --git a/x-pack/plugins/ml/server/models/data_recognizer/data_recognizer.ts b/x-pack/plugins/ml/server/models/data_recognizer/data_recognizer.ts
index 3df5016f560c07..4a621bc5f608b5 100644
--- a/x-pack/plugins/ml/server/models/data_recognizer/data_recognizer.ts
+++ b/x-pack/plugins/ml/server/models/data_recognizer/data_recognizer.ts
@@ -124,6 +124,17 @@ export class DataRecognizer {
private _resultsService: ReturnType;
private _calculateModelMemoryLimit: ReturnType;
+ /**
+ * A temporary cache of configs loaded from disk and from save object service.
+ * The configs from disk will not change while kibana is running.
+ * The configs from saved objects could potentially change while an instance of
+ * DataRecognizer exists, if a fleet package containing modules is installed.
+ * However the chance of this happening is very low and so the benefit of using
+ * this cache outweighs the risk of the cache being out of date during the short
+ * existence of a DataRecognizer instance.
+ */
+ private _configCache: Config[] | null = null;
+
/**
* List of the module jobs that require model memory estimation
*/
@@ -181,6 +192,10 @@ export class DataRecognizer {
}
private async _loadConfigs(): Promise {
+ if (this._configCache !== null) {
+ return this._configCache;
+ }
+
const configs: Config[] = [];
const dirs = await this._listDirs(this._modulesDir);
await Promise.all(
@@ -211,7 +226,9 @@ export class DataRecognizer {
isSavedObject: true,
}));
- return [...configs, ...savedObjectConfigs];
+ this._configCache = [...configs, ...savedObjectConfigs];
+
+ return this._configCache;
}
private async _loadSavedObjectModules() {
diff --git a/x-pack/plugins/ml/server/routes/data_frame_analytics.ts b/x-pack/plugins/ml/server/routes/data_frame_analytics.ts
index 2ab10bda361909..1fa7217e7d252d 100644
--- a/x-pack/plugins/ml/server/routes/data_frame_analytics.ts
+++ b/x-pack/plugins/ml/server/routes/data_frame_analytics.ts
@@ -609,12 +609,12 @@ export function dataFrameAnalyticsRoutes({ router, mlLicense, routeGuard }: Rout
/**
* @apiGroup DataFrameAnalytics
*
- * @api {post} /api/ml/data_frame/analytics/job_exists Check whether jobs exists in current or any space
- * @apiName JobExists
- * @apiDescription Checks if each of the jobs in the specified list of IDs exist.
+ * @api {post} /api/ml/data_frame/analytics/jobs_exist Check whether jobs exist in current or any space
+ * @apiName JobsExist
+ * @apiDescription Checks if each of the jobs in the specified list of IDs exists.
* If allSpaces is true, the check will look across all spaces.
*
- * @apiSchema (params) analyticsIdSchema
+ * @apiSchema (params) jobsExistSchema
*/
router.post(
{
@@ -707,7 +707,7 @@ export function dataFrameAnalyticsRoutes({ router, mlLicense, routeGuard }: Rout
/**
* @apiGroup DataFrameAnalytics
*
- * @api {get} api/data_frame/analytics/fields/:indexPattern Get fields for a pattern of indices used for analytics
+ * @api {get} /api/ml/data_frame/analytics/new_job_caps/:indexPattern Get fields for a pattern of indices used for analytics
* @apiName AnalyticsNewJobCaps
* @apiDescription Retrieve the index fields for analytics
*/
diff --git a/x-pack/plugins/monitoring/public/components/cluster/overview/enterprise_search_panel.js b/x-pack/plugins/monitoring/public/components/cluster/overview/enterprise_search_panel.js
index 1459ccb2ecac63..b8ff05213c1c03 100644
--- a/x-pack/plugins/monitoring/public/components/cluster/overview/enterprise_search_panel.js
+++ b/x-pack/plugins/monitoring/public/components/cluster/overview/enterprise_search_panel.js
@@ -31,6 +31,12 @@ import { getSafeForExternalLink } from '../../../lib/get_safe_for_external_link'
export function EnterpriseSearchPanel(props) {
const { setupMode } = props;
const setupModeData = get(setupMode.data, 'enterprise_search');
+ const nodesCount = props.stats.totalInstances || 0;
+
+ // Do not show if we are not in setup mode
+ if (!nodesCount && !setupMode.enabled) {
+ return null;
+ }
return (
diff --git a/x-pack/plugins/monitoring/server/kibana_monitoring/collectors/get_settings_collector.ts b/x-pack/plugins/monitoring/server/kibana_monitoring/collectors/get_settings_collector.ts
index 7096647854c157..76cc9adeb43ecd 100644
--- a/x-pack/plugins/monitoring/server/kibana_monitoring/collectors/get_settings_collector.ts
+++ b/x-pack/plugins/monitoring/server/kibana_monitoring/collectors/get_settings_collector.ts
@@ -90,7 +90,6 @@ export function getSettingsCollector(
) {
return usageCollection.makeStatsCollector<
EmailSettingData | undefined,
- false,
KibanaSettingsCollectorExtraOptions
>({
type: 'kibana_settings',
diff --git a/x-pack/plugins/monitoring/server/kibana_monitoring/collectors/get_usage_collector.ts b/x-pack/plugins/monitoring/server/kibana_monitoring/collectors/get_usage_collector.ts
index cbbfe64f5e3e22..0c952949c56b49 100644
--- a/x-pack/plugins/monitoring/server/kibana_monitoring/collectors/get_usage_collector.ts
+++ b/x-pack/plugins/monitoring/server/kibana_monitoring/collectors/get_usage_collector.ts
@@ -18,7 +18,7 @@ export function getMonitoringUsageCollector(
config: MonitoringConfig,
getClient: () => IClusterClient
) {
- return usageCollection.makeUsageCollector({
+ return usageCollection.makeUsageCollector({
type: 'monitoring',
isReady: () => true,
schema: {
@@ -95,13 +95,8 @@ export function getMonitoringUsageCollector(
},
},
},
- extendFetchContext: {
- kibanaRequest: true,
- },
- fetch: async ({ kibanaRequest }) => {
- const callCluster = kibanaRequest
- ? getClient().asScoped(kibanaRequest).asCurrentUser
- : getClient().asInternalUser;
+ fetch: async () => {
+ const callCluster = getClient().asInternalUser;
const usageClusters: MonitoringClusterStackProductUsage[] = [];
const availableCcs = config.ui.ccs.enabled;
const clusters = await fetchClusters(callCluster);
diff --git a/x-pack/plugins/monitoring/server/telemetry_collection/register_monitoring_telemetry_collection.ts b/x-pack/plugins/monitoring/server/telemetry_collection/register_monitoring_telemetry_collection.ts
index bce6f57d6f950a..344b04fb4780d4 100644
--- a/x-pack/plugins/monitoring/server/telemetry_collection/register_monitoring_telemetry_collection.ts
+++ b/x-pack/plugins/monitoring/server/telemetry_collection/register_monitoring_telemetry_collection.ts
@@ -34,13 +34,9 @@ export function registerMonitoringTelemetryCollection(
getClient: () => IClusterClient,
maxBucketSize: number
) {
- const monitoringStatsCollector = usageCollection.makeStatsCollector<
- MonitoringTelemetryUsage,
- true
- >({
+ const monitoringStatsCollector = usageCollection.makeStatsCollector({
type: 'monitoringTelemetry',
isReady: () => true,
- extendFetchContext: { kibanaRequest: true },
schema: {
stats: {
type: 'array',
@@ -137,13 +133,13 @@ export function registerMonitoringTelemetryCollection(
},
},
},
- fetch: async ({ kibanaRequest, esClient }) => {
+ fetch: async () => {
const timestamp = Date.now(); // Collect the telemetry from the monitoring indices for this moment.
// NOTE: Usually, the monitoring indices index stats for each product every 10s (by default).
// However, some data may be delayed up-to 24h because monitoring only collects extended Kibana stats in that interval
// to avoid overloading of the system when retrieving data from the collectors (that delay is dealt with in the Kibana Stats getter inside the `getAllStats` method).
// By 8.x, we expect to stop collecting the Kibana extended stats and keep only the monitoring-related metrics.
- const callCluster = kibanaRequest ? esClient : getClient().asInternalUser;
+ const callCluster = getClient().asInternalUser;
const clusterDetails = await getClusterUuids(callCluster, timestamp, maxBucketSize);
const [licenses, stats] = await Promise.all([
getLicenses(clusterDetails, callCluster, maxBucketSize),
diff --git a/x-pack/plugins/reporting/server/config/config.ts b/x-pack/plugins/reporting/server/config/config.ts
index 00c57053653f75..269a66503a741f 100644
--- a/x-pack/plugins/reporting/server/config/config.ts
+++ b/x-pack/plugins/reporting/server/config/config.ts
@@ -7,8 +7,7 @@
import { get } from 'lodash';
import { first } from 'rxjs/operators';
-import { CoreSetup, PluginInitializerContext } from 'src/core/server';
-import { LevelLogger } from '../lib';
+import type { CoreSetup, Logger, PluginInitializerContext } from 'kibana/server';
import { createConfig$ } from './create_config';
import { ReportingConfigType } from './schema';
@@ -63,13 +62,13 @@ export interface ReportingConfig extends Config {
* @internal
* @param {PluginInitializerContext} initContext
* @param {CoreSetup} core
- * @param {LevelLogger} logger
+ * @param {Logger} logger
* @returns {Promise}
*/
export const buildConfig = async (
initContext: PluginInitializerContext,
core: CoreSetup,
- logger: LevelLogger
+ logger: Logger
): Promise => {
const config$ = initContext.config.create();
const { http } = core;
diff --git a/x-pack/plugins/reporting/server/config/create_config.test.ts b/x-pack/plugins/reporting/server/config/create_config.test.ts
index fd8180bd46a05d..f839d72e1a45df 100644
--- a/x-pack/plugins/reporting/server/config/create_config.test.ts
+++ b/x-pack/plugins/reporting/server/config/create_config.test.ts
@@ -6,11 +6,10 @@
*/
import * as Rx from 'rxjs';
-import { CoreSetup, HttpServerInfo, PluginInitializerContext } from 'src/core/server';
-import { coreMock } from 'src/core/server/mocks';
-import { LevelLogger } from '../lib/level_logger';
-import { createMockConfigSchema, createMockLevelLogger } from '../test_helpers';
-import { ReportingConfigType } from './';
+import type { CoreSetup, HttpServerInfo, Logger, PluginInitializerContext } from 'kibana/server';
+import { coreMock, loggingSystemMock } from 'src/core/server/mocks';
+import { createMockConfigSchema } from '../test_helpers';
+import type { ReportingConfigType } from './';
import { createConfig$ } from './create_config';
const createMockConfig = (
@@ -20,14 +19,14 @@ const createMockConfig = (
describe('Reporting server createConfig$', () => {
let mockCoreSetup: CoreSetup;
let mockInitContext: PluginInitializerContext;
- let mockLogger: jest.Mocked;
+ let mockLogger: jest.Mocked;
beforeEach(() => {
mockCoreSetup = coreMock.createSetup();
mockInitContext = coreMock.createPluginInitializerContext(
createMockConfigSchema({ kibanaServer: {} })
);
- mockLogger = createMockLevelLogger();
+ mockLogger = loggingSystemMock.createLogger();
});
afterEach(() => {
diff --git a/x-pack/plugins/reporting/server/config/create_config.ts b/x-pack/plugins/reporting/server/config/create_config.ts
index 2ac225ec4576a8..ff8d00c30d4f83 100644
--- a/x-pack/plugins/reporting/server/config/create_config.ts
+++ b/x-pack/plugins/reporting/server/config/create_config.ts
@@ -7,11 +7,10 @@
import crypto from 'crypto';
import ipaddr from 'ipaddr.js';
+import type { CoreSetup, Logger } from 'kibana/server';
import { sum } from 'lodash';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
-import { CoreSetup } from 'src/core/server';
-import { LevelLogger } from '../lib';
import { ReportingConfigType } from './schema';
/*
@@ -22,9 +21,9 @@ import { ReportingConfigType } from './schema';
export function createConfig$(
core: CoreSetup,
config$: Observable,
- parentLogger: LevelLogger
+ parentLogger: Logger
) {
- const logger = parentLogger.clone(['config']);
+ const logger = parentLogger.get('config');
return config$.pipe(
map((config) => {
// encryption key
diff --git a/x-pack/plugins/reporting/server/core.ts b/x-pack/plugins/reporting/server/core.ts
index 745542c358a69b..a4e4f43f90e1ee 100644
--- a/x-pack/plugins/reporting/server/core.ts
+++ b/x-pack/plugins/reporting/server/core.ts
@@ -11,6 +11,7 @@ import { filter, first, map, switchMap, take } from 'rxjs/operators';
import type {
BasePath,
IClusterClient,
+ Logger,
PackageInfo,
PluginInitializerContext,
SavedObjectsClientContract,
@@ -32,7 +33,7 @@ import { REPORTING_REDIRECT_LOCATOR_STORE_KEY } from '../common/constants';
import { durationToNumber } from '../common/schema_utils';
import type { ReportingConfig, ReportingSetup } from './';
import { ReportingConfigType } from './config';
-import { checkLicense, getExportTypesRegistry, LevelLogger } from './lib';
+import { checkLicense, getExportTypesRegistry } from './lib';
import { reportingEventLoggerFactory } from './lib/event_logger/logger';
import type { IReport, ReportingStore } from './lib/store';
import { ExecuteReportTask, MonitorReportsTask, ReportTaskParams } from './lib/tasks';
@@ -45,7 +46,7 @@ export interface ReportingInternalSetup {
security?: SecurityPluginSetup;
spaces?: SpacesPluginSetup;
taskManager: TaskManagerSetupContract;
- logger: LevelLogger;
+ logger: Logger;
status: StatusServiceSetup;
}
@@ -57,7 +58,7 @@ export interface ReportingInternalStart {
data: DataPluginStart;
fieldFormats: FieldFormatsStart;
licensing: LicensingPluginStart;
- logger: LevelLogger;
+ logger: Logger;
screenshotting: ScreenshottingStart;
security?: SecurityPluginStart;
taskManager: TaskManagerStartContract;
@@ -81,7 +82,7 @@ export class ReportingCore {
public getContract: () => ReportingSetup;
- constructor(private logger: LevelLogger, context: PluginInitializerContext) {
+ constructor(private logger: Logger, context: PluginInitializerContext) {
this.packageInfo = context.env.packageInfo;
const syncConfig = context.config.get();
this.deprecatedAllowedRoles = syncConfig.roles.enabled ? syncConfig.roles.allow : false;
diff --git a/x-pack/plugins/reporting/server/export_types/common/decrypt_job_headers.test.ts b/x-pack/plugins/reporting/server/export_types/common/decrypt_job_headers.test.ts
index b5258d91485f70..56a1c39e75aa46 100644
--- a/x-pack/plugins/reporting/server/export_types/common/decrypt_job_headers.test.ts
+++ b/x-pack/plugins/reporting/server/export_types/common/decrypt_job_headers.test.ts
@@ -5,11 +5,11 @@
* 2.0.
*/
+import { loggingSystemMock } from 'src/core/server/mocks';
import { cryptoFactory } from '../../lib';
-import { createMockLevelLogger } from '../../test_helpers';
import { decryptJobHeaders } from './';
-const logger = createMockLevelLogger();
+const logger = loggingSystemMock.createLogger();
const encryptHeaders = async (encryptionKey: string, headers: Record) => {
const crypto = cryptoFactory(encryptionKey);
diff --git a/x-pack/plugins/reporting/server/export_types/common/decrypt_job_headers.ts b/x-pack/plugins/reporting/server/export_types/common/decrypt_job_headers.ts
index f126d1edbfce3a..3dfcfe362abd49 100644
--- a/x-pack/plugins/reporting/server/export_types/common/decrypt_job_headers.ts
+++ b/x-pack/plugins/reporting/server/export_types/common/decrypt_job_headers.ts
@@ -6,12 +6,13 @@
*/
import { i18n } from '@kbn/i18n';
-import { cryptoFactory, LevelLogger } from '../../lib';
+import type { Logger } from 'kibana/server';
+import { cryptoFactory } from '../../lib';
export const decryptJobHeaders = async (
encryptionKey: string | undefined,
headers: string,
- logger: LevelLogger
+ logger: Logger
): Promise> => {
try {
if (typeof headers !== 'string') {
diff --git a/x-pack/plugins/reporting/server/export_types/common/generate_png.ts b/x-pack/plugins/reporting/server/export_types/common/generate_png.ts
index caa0b7fb91b3f2..272d1c287178ad 100644
--- a/x-pack/plugins/reporting/server/export_types/common/generate_png.ts
+++ b/x-pack/plugins/reporting/server/export_types/common/generate_png.ts
@@ -6,14 +6,14 @@
*/
import apm from 'elastic-apm-node';
+import type { Logger } from 'kibana/server';
import * as Rx from 'rxjs';
import { finalize, map, tap } from 'rxjs/operators';
+import type { ReportingCore } from '../../';
import { LayoutTypes } from '../../../../screenshotting/common';
import { REPORTING_TRANSACTION_TYPE } from '../../../common/constants';
import type { PngMetrics } from '../../../common/types';
-import { ReportingCore } from '../../';
-import { ScreenshotOptions } from '../../types';
-import { LevelLogger } from '../../lib';
+import type { ScreenshotOptions } from '../../types';
interface PngResult {
buffer: Buffer;
@@ -23,7 +23,7 @@ interface PngResult {
export function generatePngObservable(
reporting: ReportingCore,
- logger: LevelLogger,
+ logger: Logger,
options: ScreenshotOptions
): Rx.Observable {
const apmTrans = apm.startTransaction('generate-png', REPORTING_TRANSACTION_TYPE);
diff --git a/x-pack/plugins/reporting/server/export_types/common/get_custom_logo.test.ts b/x-pack/plugins/reporting/server/export_types/common/get_custom_logo.test.ts
index f5675b50cfddd4..850d0ae507e126 100644
--- a/x-pack/plugins/reporting/server/export_types/common/get_custom_logo.test.ts
+++ b/x-pack/plugins/reporting/server/export_types/common/get_custom_logo.test.ts
@@ -5,17 +5,14 @@
* 2.0.
*/
-import { ReportingCore } from '../..';
-import {
- createMockConfigSchema,
- createMockLevelLogger,
- createMockReportingCore,
-} from '../../test_helpers';
+import { loggingSystemMock } from 'src/core/server/mocks';
+import { ReportingCore } from '../../';
+import { createMockConfigSchema, createMockReportingCore } from '../../test_helpers';
import { getCustomLogo } from './get_custom_logo';
let mockReportingPlugin: ReportingCore;
-const logger = createMockLevelLogger();
+const logger = loggingSystemMock.createLogger();
beforeEach(async () => {
mockReportingPlugin = await createMockReportingCore(createMockConfigSchema());
diff --git a/x-pack/plugins/reporting/server/export_types/common/get_custom_logo.ts b/x-pack/plugins/reporting/server/export_types/common/get_custom_logo.ts
index fcabd34a642c8c..10873155039885 100644
--- a/x-pack/plugins/reporting/server/export_types/common/get_custom_logo.ts
+++ b/x-pack/plugins/reporting/server/export_types/common/get_custom_logo.ts
@@ -5,16 +5,15 @@
* 2.0.
*/
-import type { Headers } from 'src/core/server';
+import type { Headers, Logger } from 'kibana/server';
import { ReportingCore } from '../../';
import { UI_SETTINGS_CUSTOM_PDF_LOGO } from '../../../common/constants';
-import { LevelLogger } from '../../lib';
export const getCustomLogo = async (
reporting: ReportingCore,
headers: Headers,
spaceId: string | undefined,
- logger: LevelLogger
+ logger: Logger
) => {
const fakeRequest = reporting.getFakeRequest({ headers }, spaceId, logger);
const uiSettingsClient = await reporting.getUiSettingsClient(fakeRequest, logger);
diff --git a/x-pack/plugins/reporting/server/export_types/csv_searchsource/execute_job.test.ts b/x-pack/plugins/reporting/server/export_types/csv_searchsource/execute_job.test.ts
index ee6d6daab88e06..5a8c4f1fd760c0 100644
--- a/x-pack/plugins/reporting/server/export_types/csv_searchsource/execute_job.test.ts
+++ b/x-pack/plugins/reporting/server/export_types/csv_searchsource/execute_job.test.ts
@@ -16,18 +16,15 @@ jest.mock('./generate_csv/generate_csv', () => ({
},
}));
-import { Writable } from 'stream';
import nodeCrypto from '@elastic/node-crypto';
+import { loggingSystemMock } from 'src/core/server/mocks';
+import { Writable } from 'stream';
import { ReportingCore } from '../../';
import { CancellationToken } from '../../../common/cancellation_token';
-import {
- createMockConfigSchema,
- createMockLevelLogger,
- createMockReportingCore,
-} from '../../test_helpers';
+import { createMockConfigSchema, createMockReportingCore } from '../../test_helpers';
import { runTaskFnFactory } from './execute_job';
-const logger = createMockLevelLogger();
+const logger = loggingSystemMock.createLogger();
const encryptionKey = 'tetkey';
const headers = { sid: 'cooltestheaders' };
let encryptedHeaders: string;
diff --git a/x-pack/plugins/reporting/server/export_types/csv_searchsource/execute_job.ts b/x-pack/plugins/reporting/server/export_types/csv_searchsource/execute_job.ts
index 97f0aa65e3d68e..8b5f0e5395827b 100644
--- a/x-pack/plugins/reporting/server/export_types/csv_searchsource/execute_job.ts
+++ b/x-pack/plugins/reporting/server/export_types/csv_searchsource/execute_job.ts
@@ -5,7 +5,6 @@
* 2.0.
*/
-import { CSV_JOB_TYPE } from '../../../common/constants';
import { getFieldFormats } from '../../services';
import { RunTaskFn, RunTaskFnFactory } from '../../types';
import { decryptJobHeaders } from '../common';
@@ -19,7 +18,7 @@ export const runTaskFnFactory: RunTaskFnFactory> = (
const config = reporting.getConfig();
return async function runTask(jobId, job, cancellationToken, stream) {
- const logger = parentLogger.clone([CSV_JOB_TYPE, 'execute-job', jobId]);
+ const logger = parentLogger.get(`execute-job:${jobId}`);
const encryptionKey = config.get('encryptionKey');
const headers = await decryptJobHeaders(encryptionKey, job.headers, logger);
diff --git a/x-pack/plugins/reporting/server/export_types/csv_searchsource/generate_csv/generate_csv.test.ts b/x-pack/plugins/reporting/server/export_types/csv_searchsource/generate_csv/generate_csv.test.ts
index c525cb7c0def2d..4755d153666e40 100644
--- a/x-pack/plugins/reporting/server/export_types/csv_searchsource/generate_csv/generate_csv.test.ts
+++ b/x-pack/plugins/reporting/server/export_types/csv_searchsource/generate_csv/generate_csv.test.ts
@@ -5,21 +5,22 @@
* 2.0.
*/
-import { Writable } from 'stream';
-import * as Rx from 'rxjs';
import { errors as esErrors } from '@elastic/elasticsearch';
+import type { IScopedClusterClient, IUiSettingsClient, SearchResponse } from 'kibana/server';
import { identity, range } from 'lodash';
-import { IScopedClusterClient, IUiSettingsClient, SearchResponse } from 'src/core/server';
+import * as Rx from 'rxjs';
import {
elasticsearchServiceMock,
+ loggingSystemMock,
savedObjectsClientMock,
uiSettingsServiceMock,
} from 'src/core/server/mocks';
import { ISearchStartSearchSource } from 'src/plugins/data/common';
-import { FieldFormatsRegistry } from 'src/plugins/field_formats/common';
import { searchSourceInstanceMock } from 'src/plugins/data/common/search/search_source/mocks';
import { IScopedSearchClient } from 'src/plugins/data/server';
import { dataPluginMock } from 'src/plugins/data/server/mocks';
+import { FieldFormatsRegistry } from 'src/plugins/field_formats/common';
+import { Writable } from 'stream';
import { ReportingConfig } from '../../../';
import { CancellationToken } from '../../../../common/cancellation_token';
import {
@@ -28,11 +29,7 @@ import {
UI_SETTINGS_DATEFORMAT_TZ,
} from '../../../../common/constants';
import { UnknownError } from '../../../../common/errors';
-import {
- createMockConfig,
- createMockConfigSchema,
- createMockLevelLogger,
-} from '../../../test_helpers';
+import { createMockConfig, createMockConfigSchema } from '../../../test_helpers';
import { JobParamsCSV } from '../types';
import { CsvGenerator } from './generate_csv';
@@ -125,7 +122,7 @@ beforeEach(async () => {
});
});
-const logger = createMockLevelLogger();
+const logger = loggingSystemMock.createLogger();
it('formats an empty search result to CSV content', async () => {
const generateCsv = new CsvGenerator(
diff --git a/x-pack/plugins/reporting/server/export_types/csv_searchsource/generate_csv/generate_csv.ts b/x-pack/plugins/reporting/server/export_types/csv_searchsource/generate_csv/generate_csv.ts
index 201484af9d7d0a..c913706f585624 100644
--- a/x-pack/plugins/reporting/server/export_types/csv_searchsource/generate_csv/generate_csv.ts
+++ b/x-pack/plugins/reporting/server/export_types/csv_searchsource/generate_csv/generate_csv.ts
@@ -5,9 +5,9 @@
* 2.0.
*/
-import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
import { errors as esErrors } from '@elastic/elasticsearch';
-import type { IScopedClusterClient, IUiSettingsClient } from 'src/core/server';
+import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
+import type { IScopedClusterClient, IUiSettingsClient, Logger } from 'kibana/server';
import type { IScopedSearchClient } from 'src/plugins/data/server';
import type { Datatable } from 'src/plugins/expressions/server';
import type { Writable } from 'stream';
@@ -32,16 +32,15 @@ import type { CancellationToken } from '../../../../common/cancellation_token';
import { CONTENT_TYPE_CSV } from '../../../../common/constants';
import {
AuthenticationExpiredError,
- UnknownError,
ReportingError,
+ UnknownError,
} from '../../../../common/errors';
import { byteSizeValueToNumber } from '../../../../common/schema_utils';
-import type { LevelLogger } from '../../../lib';
import type { TaskRunResult } from '../../../lib/tasks';
import type { JobParamsCSV } from '../types';
import { CsvExportSettings, getExportSettings } from './get_export_settings';
-import { MaxSizeStringBuilder } from './max_size_string_builder';
import { i18nTexts } from './i18n_texts';
+import { MaxSizeStringBuilder } from './max_size_string_builder';
interface Clients {
es: IScopedClusterClient;
@@ -65,7 +64,7 @@ export class CsvGenerator {
private clients: Clients,
private dependencies: Dependencies,
private cancellationToken: CancellationToken,
- private logger: LevelLogger,
+ private logger: Logger,
private stream: Writable
) {}
@@ -316,7 +315,7 @@ export class CsvGenerator {
}
if (!results) {
- this.logger.warning(`Search results are undefined!`);
+ this.logger.warn(`Search results are undefined!`);
break;
}
@@ -396,7 +395,7 @@ export class CsvGenerator {
this.logger.debug(`Finished generating. Row count: ${this.csvRowCount}.`);
if (!this.maxSizeReached && this.csvRowCount !== totalRecords) {
- this.logger.warning(
+ this.logger.warn(
`ES scroll returned fewer total hits than expected! ` +
`Search result total hits: ${totalRecords}. Row count: ${this.csvRowCount}.`
);
diff --git a/x-pack/plugins/reporting/server/export_types/csv_searchsource/generate_csv/get_export_settings.test.ts b/x-pack/plugins/reporting/server/export_types/csv_searchsource/generate_csv/get_export_settings.test.ts
index 2ae3e5e712d313..ef0f0062bf19b6 100644
--- a/x-pack/plugins/reporting/server/export_types/csv_searchsource/generate_csv/get_export_settings.test.ts
+++ b/x-pack/plugins/reporting/server/export_types/csv_searchsource/generate_csv/get_export_settings.test.ts
@@ -12,18 +12,18 @@ import {
UI_SETTINGS_SEARCH_INCLUDE_FROZEN,
} from '../../../../common/constants';
import { IUiSettingsClient } from 'kibana/server';
-import { savedObjectsClientMock, uiSettingsServiceMock } from 'src/core/server/mocks';
import {
- createMockConfig,
- createMockConfigSchema,
- createMockLevelLogger,
-} from '../../../test_helpers';
+ loggingSystemMock,
+ savedObjectsClientMock,
+ uiSettingsServiceMock,
+} from 'src/core/server/mocks';
+import { createMockConfig, createMockConfigSchema } from '../../../test_helpers';
import { getExportSettings } from './get_export_settings';
describe('getExportSettings', () => {
let uiSettingsClient: IUiSettingsClient;
const config = createMockConfig(createMockConfigSchema({}));
- const logger = createMockLevelLogger();
+ const logger = loggingSystemMock.createLogger();
beforeEach(() => {
uiSettingsClient = uiSettingsServiceMock
diff --git a/x-pack/plugins/reporting/server/export_types/csv_searchsource/generate_csv/get_export_settings.ts b/x-pack/plugins/reporting/server/export_types/csv_searchsource/generate_csv/get_export_settings.ts
index 5b69e33624c5c2..6a07e3184eb48f 100644
--- a/x-pack/plugins/reporting/server/export_types/csv_searchsource/generate_csv/get_export_settings.ts
+++ b/x-pack/plugins/reporting/server/export_types/csv_searchsource/generate_csv/get_export_settings.ts
@@ -6,7 +6,7 @@
*/
import { ByteSizeValue } from '@kbn/config-schema';
-import { IUiSettingsClient } from 'kibana/server';
+import type { IUiSettingsClient, Logger } from 'kibana/server';
import { createEscapeValue } from '../../../../../../../src/plugins/data/common';
import { ReportingConfig } from '../../../';
import {
@@ -16,7 +16,6 @@ import {
UI_SETTINGS_DATEFORMAT_TZ,
UI_SETTINGS_SEARCH_INCLUDE_FROZEN,
} from '../../../../common/constants';
-import { LevelLogger } from '../../../lib';
export interface CsvExportSettings {
timezone: string;
@@ -37,7 +36,7 @@ export const getExportSettings = async (
client: IUiSettingsClient,
config: ReportingConfig,
timezone: string | undefined,
- logger: LevelLogger
+ logger: Logger
): Promise => {
let setTimezone: string;
if (timezone) {
diff --git a/x-pack/plugins/reporting/server/export_types/csv_searchsource_immediate/execute_job.ts b/x-pack/plugins/reporting/server/export_types/csv_searchsource_immediate/execute_job.ts
index 53e1f6ba3c95bf..50ae2ab10f6e75 100644
--- a/x-pack/plugins/reporting/server/export_types/csv_searchsource_immediate/execute_job.ts
+++ b/x-pack/plugins/reporting/server/export_types/csv_searchsource_immediate/execute_job.ts
@@ -8,7 +8,6 @@
import { KibanaRequest } from 'src/core/server';
import { Writable } from 'stream';
import { CancellationToken } from '../../../common/cancellation_token';
-import { CSV_SEARCHSOURCE_IMMEDIATE_TYPE } from '../../../common/constants';
import { TaskRunResult } from '../../lib/tasks';
import { getFieldFormats } from '../../services';
import { ReportingRequestHandlerContext, RunTaskFnFactory } from '../../types';
@@ -32,7 +31,7 @@ export const runTaskFnFactory: RunTaskFnFactory = function e
parentLogger
) {
const config = reporting.getConfig();
- const logger = parentLogger.clone([CSV_SEARCHSOURCE_IMMEDIATE_TYPE, 'execute-job']);
+ const logger = parentLogger.get('execute-job');
return async function runTask(_jobId, immediateJobParams, context, stream, req) {
const job = {
@@ -82,7 +81,7 @@ export const runTaskFnFactory: RunTaskFnFactory = function e
const { warnings } = result;
if (warnings) {
warnings.forEach((warning) => {
- logger.warning(warning);
+ logger.warn(warning);
});
}
diff --git a/x-pack/plugins/reporting/server/export_types/png/execute_job/index.test.ts b/x-pack/plugins/reporting/server/export_types/png/execute_job/index.test.ts
index 9069ec63a88250..bc37978372ba63 100644
--- a/x-pack/plugins/reporting/server/export_types/png/execute_job/index.test.ts
+++ b/x-pack/plugins/reporting/server/export_types/png/execute_job/index.test.ts
@@ -5,11 +5,12 @@
* 2.0.
*/
-import { Writable } from 'stream';
import * as Rx from 'rxjs';
+import { loggingSystemMock } from 'src/core/server/mocks';
+import { Writable } from 'stream';
import { ReportingCore } from '../../../';
import { CancellationToken } from '../../../../common/cancellation_token';
-import { cryptoFactory, LevelLogger } from '../../../lib';
+import { cryptoFactory } from '../../../lib';
import {
createMockConfig,
createMockConfigSchema,
@@ -29,14 +30,7 @@ const cancellationToken = {
on: jest.fn(),
} as unknown as CancellationToken;
-const mockLoggerFactory = {
- get: jest.fn().mockImplementation(() => ({
- error: jest.fn(),
- debug: jest.fn(),
- warn: jest.fn(),
- })),
-};
-const getMockLogger = () => new LevelLogger(mockLoggerFactory);
+const getMockLogger = () => loggingSystemMock.createLogger();
const mockEncryptionKey = 'abcabcsecuresecret';
const encryptHeaders = async (headers: Record) => {
diff --git a/x-pack/plugins/reporting/server/export_types/png/execute_job/index.ts b/x-pack/plugins/reporting/server/export_types/png/execute_job/index.ts
index 67d013740bedd3..52023e53b80b56 100644
--- a/x-pack/plugins/reporting/server/export_types/png/execute_job/index.ts
+++ b/x-pack/plugins/reporting/server/export_types/png/execute_job/index.ts
@@ -8,7 +8,7 @@
import apm from 'elastic-apm-node';
import * as Rx from 'rxjs';
import { finalize, map, mergeMap, takeUntil, tap } from 'rxjs/operators';
-import { PNG_JOB_TYPE, REPORTING_TRANSACTION_TYPE } from '../../../../common/constants';
+import { REPORTING_TRANSACTION_TYPE } from '../../../../common/constants';
import { TaskRunResult } from '../../../lib/tasks';
import { RunTaskFn, RunTaskFnFactory } from '../../../types';
import { decryptJobHeaders, getFullUrls, generatePngObservable } from '../../common';
@@ -24,7 +24,7 @@ export const runTaskFnFactory: RunTaskFnFactory> =
const apmGetAssets = apmTrans?.startSpan('get-assets', 'setup');
let apmGeneratePng: { end: () => void } | null | undefined;
- const jobLogger = parentLogger.clone([PNG_JOB_TYPE, 'execute', jobId]);
+ const jobLogger = parentLogger.get(`execute:${jobId}`);
const process$: Rx.Observable = Rx.of(1).pipe(
mergeMap(() => decryptJobHeaders(encryptionKey, job.headers, jobLogger)),
mergeMap((headers) => {
diff --git a/x-pack/plugins/reporting/server/export_types/png_v2/execute_job.test.ts b/x-pack/plugins/reporting/server/export_types/png_v2/execute_job.test.ts
index 1b1ad6878d78fb..1403873e8da4b8 100644
--- a/x-pack/plugins/reporting/server/export_types/png_v2/execute_job.test.ts
+++ b/x-pack/plugins/reporting/server/export_types/png_v2/execute_job.test.ts
@@ -6,11 +6,12 @@
*/
import * as Rx from 'rxjs';
+import { loggingSystemMock } from 'src/core/server/mocks';
import { Writable } from 'stream';
import { ReportingCore } from '../../';
import { CancellationToken } from '../../../common/cancellation_token';
import { LocatorParams } from '../../../common/types';
-import { cryptoFactory, LevelLogger } from '../../lib';
+import { cryptoFactory } from '../../lib';
import {
createMockConfig,
createMockConfigSchema,
@@ -30,14 +31,7 @@ const cancellationToken = {
on: jest.fn(),
} as unknown as CancellationToken;
-const mockLoggerFactory = {
- get: jest.fn().mockImplementation(() => ({
- error: jest.fn(),
- debug: jest.fn(),
- warn: jest.fn(),
- })),
-};
-const getMockLogger = () => new LevelLogger(mockLoggerFactory);
+const getMockLogger = () => loggingSystemMock.createLogger();
const mockEncryptionKey = 'abcabcsecuresecret';
const encryptHeaders = async (headers: Record) => {
diff --git a/x-pack/plugins/reporting/server/export_types/png_v2/execute_job.ts b/x-pack/plugins/reporting/server/export_types/png_v2/execute_job.ts
index 51044aa324a1aa..5df7a497adf6c7 100644
--- a/x-pack/plugins/reporting/server/export_types/png_v2/execute_job.ts
+++ b/x-pack/plugins/reporting/server/export_types/png_v2/execute_job.ts
@@ -8,7 +8,7 @@
import apm from 'elastic-apm-node';
import * as Rx from 'rxjs';
import { finalize, map, mergeMap, takeUntil, tap } from 'rxjs/operators';
-import { PNG_JOB_TYPE_V2, REPORTING_TRANSACTION_TYPE } from '../../../common/constants';
+import { REPORTING_TRANSACTION_TYPE } from '../../../common/constants';
import { TaskRunResult } from '../../lib/tasks';
import { RunTaskFn, RunTaskFnFactory } from '../../types';
import { decryptJobHeaders, generatePngObservable } from '../common';
@@ -25,7 +25,7 @@ export const runTaskFnFactory: RunTaskFnFactory> =
const apmGetAssets = apmTrans?.startSpan('get-assets', 'setup');
let apmGeneratePng: { end: () => void } | null | undefined;
- const jobLogger = parentLogger.clone([PNG_JOB_TYPE_V2, 'execute', jobId]);
+ const jobLogger = parentLogger.get(`execute:${jobId}`);
const process$: Rx.Observable = Rx.of(1).pipe(
mergeMap(() => decryptJobHeaders(encryptionKey, job.headers, jobLogger)),
mergeMap((headers) => {
diff --git a/x-pack/plugins/reporting/server/export_types/printable_pdf/execute_job/index.test.ts b/x-pack/plugins/reporting/server/export_types/printable_pdf/execute_job/index.test.ts
index a8d2027f2ba120..7faa13486b5a18 100644
--- a/x-pack/plugins/reporting/server/export_types/printable_pdf/execute_job/index.test.ts
+++ b/x-pack/plugins/reporting/server/export_types/printable_pdf/execute_job/index.test.ts
@@ -6,10 +6,11 @@
*/
import * as Rx from 'rxjs';
+import { loggingSystemMock } from 'src/core/server/mocks';
import { Writable } from 'stream';
import { ReportingCore } from '../../../';
import { CancellationToken } from '../../../../common/cancellation_token';
-import { cryptoFactory, LevelLogger } from '../../../lib';
+import { cryptoFactory } from '../../../lib';
import { createMockConfigSchema, createMockReportingCore } from '../../../test_helpers';
import { generatePdfObservable } from '../lib/generate_pdf';
import { TaskPayloadPDF } from '../types';
@@ -25,14 +26,7 @@ const cancellationToken = {
on: jest.fn(),
} as unknown as CancellationToken;
-const mockLoggerFactory = {
- get: jest.fn().mockImplementation(() => ({
- error: jest.fn(),
- debug: jest.fn(),
- warn: jest.fn(),
- })),
-};
-const getMockLogger = () => new LevelLogger(mockLoggerFactory);
+const getMockLogger = () => loggingSystemMock.createLogger();
const mockEncryptionKey = 'testencryptionkey';
const encryptHeaders = async (headers: Record) => {
diff --git a/x-pack/plugins/reporting/server/export_types/printable_pdf/execute_job/index.ts b/x-pack/plugins/reporting/server/export_types/printable_pdf/execute_job/index.ts
index ab3793935e1d86..9b4db48ed66970 100644
--- a/x-pack/plugins/reporting/server/export_types/printable_pdf/execute_job/index.ts
+++ b/x-pack/plugins/reporting/server/export_types/printable_pdf/execute_job/index.ts
@@ -8,7 +8,7 @@
import apm from 'elastic-apm-node';
import * as Rx from 'rxjs';
import { catchError, map, mergeMap, takeUntil, tap } from 'rxjs/operators';
-import { PDF_JOB_TYPE, REPORTING_TRANSACTION_TYPE } from '../../../../common/constants';
+import { REPORTING_TRANSACTION_TYPE } from '../../../../common/constants';
import { TaskRunResult } from '../../../lib/tasks';
import { RunTaskFn, RunTaskFnFactory } from '../../../types';
import { decryptJobHeaders, getFullUrls, getCustomLogo } from '../../common';
@@ -21,7 +21,7 @@ export const runTaskFnFactory: RunTaskFnFactory> =
const encryptionKey = config.get('encryptionKey');
return async function runTask(jobId, job, cancellationToken, stream) {
- const jobLogger = parentLogger.clone([PDF_JOB_TYPE, 'execute-job', jobId]);
+ const jobLogger = parentLogger.get(`execute-job:${jobId}`);
const apmTrans = apm.startTransaction('execute-job-pdf', REPORTING_TRANSACTION_TYPE);
const apmGetAssets = apmTrans?.startSpan('get-assets', 'setup');
let apmGeneratePdf: { end: () => void } | null | undefined;
diff --git a/x-pack/plugins/reporting/server/export_types/printable_pdf/lib/generate_pdf.ts b/x-pack/plugins/reporting/server/export_types/printable_pdf/lib/generate_pdf.ts
index a401f59b8f4bf0..ff0ef2cf39af4b 100644
--- a/x-pack/plugins/reporting/server/export_types/printable_pdf/lib/generate_pdf.ts
+++ b/x-pack/plugins/reporting/server/export_types/printable_pdf/lib/generate_pdf.ts
@@ -5,13 +5,13 @@
* 2.0.
*/
+import type { Logger } from 'kibana/server';
import { groupBy } from 'lodash';
import * as Rx from 'rxjs';
import { mergeMap, tap } from 'rxjs/operators';
+import { ReportingCore } from '../../../';
import { ScreenshotResult } from '../../../../../screenshotting/server';
import type { PdfMetrics } from '../../../../common/types';
-import { ReportingCore } from '../../../';
-import { LevelLogger } from '../../../lib';
import { ScreenshotOptions } from '../../../types';
import { PdfMaker } from '../../common/pdf';
import { getTracker } from './tracker';
@@ -34,7 +34,7 @@ interface PdfResult {
export function generatePdfObservable(
reporting: ReportingCore,
- logger: LevelLogger,
+ logger: Logger,
title: string,
options: ScreenshotOptions,
logo?: string
diff --git a/x-pack/plugins/reporting/server/export_types/printable_pdf_v2/execute_job.test.ts b/x-pack/plugins/reporting/server/export_types/printable_pdf_v2/execute_job.test.ts
index 3cf7f82058563a..efad71a64a81d6 100644
--- a/x-pack/plugins/reporting/server/export_types/printable_pdf_v2/execute_job.test.ts
+++ b/x-pack/plugins/reporting/server/export_types/printable_pdf_v2/execute_job.test.ts
@@ -8,11 +8,12 @@
jest.mock('./lib/generate_pdf');
import * as Rx from 'rxjs';
+import { loggingSystemMock } from 'src/core/server/mocks';
import { Writable } from 'stream';
import { ReportingCore } from '../../';
import { CancellationToken } from '../../../common/cancellation_token';
import { LocatorParams } from '../../../common/types';
-import { cryptoFactory, LevelLogger } from '../../lib';
+import { cryptoFactory } from '../../lib';
import { createMockConfigSchema, createMockReportingCore } from '../../test_helpers';
import { runTaskFnFactory } from './execute_job';
import { generatePdfObservable } from './lib/generate_pdf';
@@ -26,14 +27,7 @@ const cancellationToken = {
on: jest.fn(),
} as unknown as CancellationToken;
-const mockLoggerFactory = {
- get: jest.fn().mockImplementation(() => ({
- error: jest.fn(),
- debug: jest.fn(),
- warn: jest.fn(),
- })),
-};
-const getMockLogger = () => new LevelLogger(mockLoggerFactory);
+const getMockLogger = () => loggingSystemMock.createLogger();
const mockEncryptionKey = 'testencryptionkey';
const encryptHeaders = async (headers: Record) => {
diff --git a/x-pack/plugins/reporting/server/export_types/printable_pdf_v2/execute_job.ts b/x-pack/plugins/reporting/server/export_types/printable_pdf_v2/execute_job.ts
index 85684bca66b869..7f887707829cb1 100644
--- a/x-pack/plugins/reporting/server/export_types/printable_pdf_v2/execute_job.ts
+++ b/x-pack/plugins/reporting/server/export_types/printable_pdf_v2/execute_job.ts
@@ -8,7 +8,7 @@
import apm from 'elastic-apm-node';
import * as Rx from 'rxjs';
import { catchError, map, mergeMap, takeUntil, tap } from 'rxjs/operators';
-import { PDF_JOB_TYPE_V2, REPORTING_TRANSACTION_TYPE } from '../../../common/constants';
+import { REPORTING_TRANSACTION_TYPE } from '../../../common/constants';
import { TaskRunResult } from '../../lib/tasks';
import { RunTaskFn, RunTaskFnFactory } from '../../types';
import { decryptJobHeaders, getCustomLogo } from '../common';
@@ -21,7 +21,7 @@ export const runTaskFnFactory: RunTaskFnFactory> =
const encryptionKey = config.get('encryptionKey');
return async function runTask(jobId, job, cancellationToken, stream) {
- const jobLogger = parentLogger.clone([PDF_JOB_TYPE_V2, 'execute-job', jobId]);
+ const jobLogger = parentLogger.get(`execute-job:${jobId}`);
const apmTrans = apm.startTransaction('execute-job-pdf-v2', REPORTING_TRANSACTION_TYPE);
const apmGetAssets = apmTrans?.startSpan('get-assets', 'setup');
let apmGeneratePdf: { end: () => void } | null | undefined;
diff --git a/x-pack/plugins/reporting/server/export_types/printable_pdf_v2/lib/generate_pdf.ts b/x-pack/plugins/reporting/server/export_types/printable_pdf_v2/lib/generate_pdf.ts
index ac922c07574b3c..8bec3cac28f430 100644
--- a/x-pack/plugins/reporting/server/export_types/printable_pdf_v2/lib/generate_pdf.ts
+++ b/x-pack/plugins/reporting/server/export_types/printable_pdf_v2/lib/generate_pdf.ts
@@ -5,14 +5,14 @@
* 2.0.
*/
+import type { Logger } from 'kibana/server';
import { groupBy } from 'lodash';
import * as Rx from 'rxjs';
import { mergeMap, tap } from 'rxjs/operators';
-import { ReportingCore } from '../../../';
-import { ScreenshotResult } from '../../../../../screenshotting/server';
-import { LocatorParams, PdfMetrics, UrlOrUrlLocatorTuple } from '../../../../common/types';
-import { LevelLogger } from '../../../lib';
-import { ScreenshotOptions } from '../../../types';
+import type { ReportingCore } from '../../../';
+import type { ScreenshotResult } from '../../../../../screenshotting/server';
+import type { LocatorParams, PdfMetrics, UrlOrUrlLocatorTuple } from '../../../../common/types';
+import type { ScreenshotOptions } from '../../../types';
import { PdfMaker } from '../../common/pdf';
import { getFullRedirectAppUrl } from '../../common/v2/get_full_redirect_app_url';
import type { TaskPayloadPDFV2 } from '../types';
@@ -36,7 +36,7 @@ interface PdfResult {
export function generatePdfObservable(
reporting: ReportingCore,
- logger: LevelLogger,
+ logger: Logger,
job: TaskPayloadPDFV2,
title: string,
locatorParams: LocatorParams[],
diff --git a/x-pack/plugins/reporting/server/lib/check_params_version.ts b/x-pack/plugins/reporting/server/lib/check_params_version.ts
index 7298384b875715..79237ba56677a5 100644
--- a/x-pack/plugins/reporting/server/lib/check_params_version.ts
+++ b/x-pack/plugins/reporting/server/lib/check_params_version.ts
@@ -5,16 +5,16 @@
* 2.0.
*/
+import type { Logger } from 'kibana/server';
import { UNVERSIONED_VERSION } from '../../common/constants';
import type { BaseParams } from '../../common/types';
-import type { LevelLogger } from './';
-export function checkParamsVersion(jobParams: BaseParams, logger: LevelLogger) {
+export function checkParamsVersion(jobParams: BaseParams, logger: Logger) {
if (jobParams.version) {
logger.debug(`Using reporting job params v${jobParams.version}`);
return jobParams.version;
}
- logger.warning(`No version provided in report job params. Assuming ${UNVERSIONED_VERSION}`);
+ logger.warn(`No version provided in report job params. Assuming ${UNVERSIONED_VERSION}`);
return UNVERSIONED_VERSION;
}
diff --git a/x-pack/plugins/reporting/server/lib/content_stream.test.ts b/x-pack/plugins/reporting/server/lib/content_stream.test.ts
index 0c45ef2d5f5ce8..069ac22258ad10 100644
--- a/x-pack/plugins/reporting/server/lib/content_stream.test.ts
+++ b/x-pack/plugins/reporting/server/lib/content_stream.test.ts
@@ -5,20 +5,20 @@
* 2.0.
*/
+import type { Logger } from 'kibana/server';
import { set } from 'lodash';
-import { elasticsearchServiceMock } from 'src/core/server/mocks';
-import { createMockLevelLogger } from '../test_helpers';
+import { elasticsearchServiceMock, loggingSystemMock } from 'src/core/server/mocks';
import { ContentStream } from './content_stream';
describe('ContentStream', () => {
let client: ReturnType