From e6f7af346863fc80900c357cb4267603301e7351 Mon Sep 17 00:00:00 2001
From: Pablo Machado
Date: Fri, 21 Apr 2023 11:53:14 +0200
Subject: [PATCH 01/15] [Security Solutions] Add telemetry to Security
Solutions Cell Actions + Anomalies count (#154830)
EPIC issue: https://github.com/elastic/kibana/issues/145276
## Summary
[Dashboard with both
events](https://telemetry-v2-staging.elastic.dev/s/securitysolution/app/dashboards#/view/d47a90f0-a6e4-11ed-a6e6-d32d2209b7b7?_g=(filters%3A!()%2CrefreshInterval%3A(pause%3A!t%2Cvalue%3A0)%2Ctime%3A(from%3Anow-90d%2Fd%2Cto%3Anow)))
Add 2 telemetry events.
1. Cell Action clicked
![Screenshot 2023-04-17 at 13 29
53](https://user-images.githubusercontent.com/1490444/232471951-6c0ca1a1-a81e-48d7-9a96-b750f0248e55.png)
Event:
```json
{
"timestamp": "2023-03-20T14:23:53.452Z",
"event_type": "Cell Action Clicked",
"context": {
...
},
"properties": {
"metadata": {
"telemetry": {
"component": "RiskScoreTable"
}
},
"fieldName": "host.name",
"displayName": "Show top host.name",
"actionId": "security-default-cellActions-showTopN"
}
}
```
2. Anomalies count click (EA page)
![Screenshot 2023-04-17 at 13 30
19](https://user-images.githubusercontent.com/1490444/232471971-4b16b28f-497b-4de8-a8ff-d98a4fbf15cf.png)
Event:
```json
{
"timestamp": "2023-03-20T14:23:53.452Z",
"event_type": "Anomalies Count Clicked",
"context": {
...
},
"properties": {
"jobId": "suspicious_login_activity",
"count": 134
}
}
```
### Checklist
[x] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or added to match the most common scenarios
---
.../public/actions/register.ts | 73 +++++++++--------
.../public/actions/telemetry.test.ts | 45 +++++++++++
.../public/actions/telemetry.ts | 33 ++++++++
.../security_solution/public/actions/types.ts | 9 +++
.../lib/telemetry/telemetry_client.mock.ts | 2 +
.../common/lib/telemetry/telemetry_client.ts | 10 +++
.../common/lib/telemetry/telemetry_events.ts | 56 +++++++++++++
.../public/common/lib/telemetry/types.ts | 32 +++++++-
.../components/risk_score/constants.ts | 4 +
.../host_risk_score_table/columns.tsx | 4 +
.../user_risk_score_table/columns.tsx | 4 +
.../anomalies/anomalies_count_link.test.tsx | 42 ++++++++++
.../anomalies/anomalies_count_link.tsx | 80 +++++++++++++++++++
.../entity_analytics/anomalies/columns.tsx | 71 +---------------
.../entity_analytics/risk_score/columns.tsx | 4 +
.../security_solution/public/plugin.tsx | 5 +-
16 files changed, 370 insertions(+), 104 deletions(-)
create mode 100644 x-pack/plugins/security_solution/public/actions/telemetry.test.ts
create mode 100644 x-pack/plugins/security_solution/public/actions/telemetry.ts
create mode 100644 x-pack/plugins/security_solution/public/overview/components/entity_analytics/anomalies/anomalies_count_link.test.tsx
create mode 100644 x-pack/plugins/security_solution/public/overview/components/entity_analytics/anomalies/anomalies_count_link.tsx
diff --git a/x-pack/plugins/security_solution/public/actions/register.ts b/x-pack/plugins/security_solution/public/actions/register.ts
index 0fafb561d45a1..2df581342f614 100644
--- a/x-pack/plugins/security_solution/public/actions/register.ts
+++ b/x-pack/plugins/security_solution/public/actions/register.ts
@@ -6,10 +6,9 @@
*/
import { CELL_VALUE_TRIGGER } from '@kbn/embeddable-plugin/public';
-import type { UiActionsStart } from '@kbn/ui-actions-plugin/public';
import type * as H from 'history';
import type { SecurityAppStore } from '../common/store/types';
-import type { StartPlugins, StartServices } from '../types';
+import type { StartServices } from '../types';
import { createFilterInCellActionFactory, createFilterOutCellActionFactory } from './filter';
import {
createAddToTimelineLensAction,
@@ -23,18 +22,20 @@ import {
import { createToggleColumnCellActionFactory } from './toggle_column';
import { SecurityCellActionsTrigger } from './constants';
import type { SecurityCellActionName, SecurityCellActions } from './types';
+import { enhanceActionWithTelemetry } from './telemetry';
export const registerUIActions = (
- { uiActions }: StartPlugins,
store: SecurityAppStore,
history: H.History,
services: StartServices
) => {
- registerLensActions(uiActions, store);
- registerCellActions(uiActions, store, history, services);
+ registerLensActions(store, services);
+ registerCellActions(store, history, services);
};
-const registerLensActions = (uiActions: UiActionsStart, store: SecurityAppStore) => {
+const registerLensActions = (store: SecurityAppStore, services: StartServices) => {
+ const { uiActions } = services;
+
const addToTimelineAction = createAddToTimelineLensAction({ store, order: 1 });
uiActions.addTriggerAction(CELL_VALUE_TRIGGER, addToTimelineAction);
@@ -43,7 +44,6 @@ const registerLensActions = (uiActions: UiActionsStart, store: SecurityAppStore)
};
const registerCellActions = (
- uiActions: UiActionsStart,
store: SecurityAppStore,
history: H.History,
services: StartServices
@@ -57,37 +57,46 @@ const registerCellActions = (
toggleColumn: createToggleColumnCellActionFactory({ store }),
};
- registerCellActionsTrigger(uiActions, SecurityCellActionsTrigger.DEFAULT, cellActions, [
- 'filterIn',
- 'filterOut',
- 'addToTimeline',
- 'showTopN',
- 'copyToClipboard',
- ]);
+ registerCellActionsTrigger({
+ triggerId: SecurityCellActionsTrigger.DEFAULT,
+ cellActions,
+ actionsOrder: ['filterIn', 'filterOut', 'addToTimeline', 'showTopN', 'copyToClipboard'],
+ services,
+ });
- registerCellActionsTrigger(uiActions, SecurityCellActionsTrigger.DETAILS_FLYOUT, cellActions, [
- 'filterIn',
- 'filterOut',
- 'addToTimeline',
- 'toggleColumn',
- 'showTopN',
- 'copyToClipboard',
- ]);
+ registerCellActionsTrigger({
+ triggerId: SecurityCellActionsTrigger.DETAILS_FLYOUT,
+ cellActions,
+ actionsOrder: [
+ 'filterIn',
+ 'filterOut',
+ 'addToTimeline',
+ 'toggleColumn',
+ 'showTopN',
+ 'copyToClipboard',
+ ],
+ services,
+ });
};
-const registerCellActionsTrigger = (
- uiActions: UiActionsStart,
- triggerId: SecurityCellActionsTrigger,
- cellActions: SecurityCellActions,
- actionsOrder: SecurityCellActionName[]
-) => {
+const registerCellActionsTrigger = ({
+ triggerId,
+ cellActions,
+ actionsOrder,
+ services,
+}: {
+ triggerId: SecurityCellActionsTrigger;
+ cellActions: SecurityCellActions;
+ actionsOrder: SecurityCellActionName[];
+ services: StartServices;
+}) => {
+ const { uiActions } = services;
uiActions.registerTrigger({ id: triggerId });
actionsOrder.forEach((actionName, order) => {
const actionFactory = cellActions[actionName];
- uiActions.addTriggerAction(
- triggerId,
- actionFactory({ id: `${triggerId}-${actionName}`, order })
- );
+ const action = actionFactory({ id: `${triggerId}-${actionName}`, order });
+
+ uiActions.addTriggerAction(triggerId, enhanceActionWithTelemetry(action, services));
});
};
diff --git a/x-pack/plugins/security_solution/public/actions/telemetry.test.ts b/x-pack/plugins/security_solution/public/actions/telemetry.test.ts
new file mode 100644
index 0000000000000..82a691348a206
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/actions/telemetry.test.ts
@@ -0,0 +1,45 @@
+/*
+ * 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 { StartServices } from '../types';
+import { enhanceActionWithTelemetry } from './telemetry';
+import { createAction } from '@kbn/ui-actions-plugin/public';
+import type { CellActionExecutionContext } from '@kbn/cell-actions';
+
+const actionId = 'test_action_id';
+const displayName = 'test-actions';
+const fieldName = 'test.user.name';
+const fieldValue = 'test value';
+const metadata = undefined;
+
+const action = createAction({
+ id: actionId,
+ execute: async () => {},
+ getIconType: () => 'test-icon',
+ getDisplayName: () => displayName,
+});
+const context = {
+ field: { name: fieldName, value: fieldValue, type: 'text' },
+ metadata,
+} as CellActionExecutionContext;
+
+describe('enhanceActionWithTelemetry', () => {
+ it('calls telemetry report when the action is executed', () => {
+ const telemetry = { reportCellActionClicked: jest.fn() };
+ const services = { telemetry } as unknown as StartServices;
+
+ const enhancedAction = enhanceActionWithTelemetry(action, services);
+ enhancedAction.execute(context);
+
+ expect(telemetry.reportCellActionClicked).toHaveBeenCalledWith({
+ displayName,
+ actionId,
+ fieldName,
+ metadata,
+ });
+ });
+});
diff --git a/x-pack/plugins/security_solution/public/actions/telemetry.ts b/x-pack/plugins/security_solution/public/actions/telemetry.ts
new file mode 100644
index 0000000000000..c051f11a81271
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/actions/telemetry.ts
@@ -0,0 +1,33 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import type { CellAction, CellActionExecutionContext } from '@kbn/cell-actions';
+import type { ActionExecutionContext } from '@kbn/ui-actions-plugin/public';
+import type { StartServices } from '../types';
+import type { SecurityCellActionExecutionContext } from './types';
+
+export const enhanceActionWithTelemetry = (
+ action: CellAction,
+ services: StartServices
+): CellAction => {
+ const { telemetry } = services;
+ const { execute, ...rest } = action;
+ const enhancedExecute = (
+ context: ActionExecutionContext
+ ): Promise => {
+ telemetry.reportCellActionClicked({
+ actionId: rest.id,
+ displayName: rest.getDisplayName(context),
+ fieldName: context.field.name,
+ metadata: context.metadata,
+ });
+
+ return execute(context);
+ };
+
+ return { ...rest, execute: enhancedExecute };
+};
diff --git a/x-pack/plugins/security_solution/public/actions/types.ts b/x-pack/plugins/security_solution/public/actions/types.ts
index da6b2cc3919a9..1d15896ae6b35 100644
--- a/x-pack/plugins/security_solution/public/actions/types.ts
+++ b/x-pack/plugins/security_solution/public/actions/types.ts
@@ -26,6 +26,15 @@ export interface SecurityMetadata extends Record {
* and we need all the filtering actions to perform the opposite (negate) operation.
*/
negateFilters?: boolean;
+ /**
+ * `metadata.telemetry` is used by the telemetry service to add context to the event.
+ */
+ telemetry?: {
+ /**
+ * It defines which UI component renders the CellActions.
+ */
+ component: string;
+ };
}
export interface SecurityCellActionExecutionContext extends CellActionExecutionContext {
diff --git a/x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_client.mock.ts b/x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_client.mock.ts
index 4f2b2bdafe96f..9cd22c469160d 100644
--- a/x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_client.mock.ts
+++ b/x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_client.mock.ts
@@ -15,4 +15,6 @@ export const createTelemetryClientMock = (): jest.Mocked =
reportEntityAlertsClicked: jest.fn(),
reportEntityRiskFiltered: jest.fn(),
reportMLJobUpdate: jest.fn(),
+ reportCellActionClicked: jest.fn(),
+ reportAnomaliesCountClicked: jest.fn(),
});
diff --git a/x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_client.ts b/x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_client.ts
index c95c8153261e1..edd50340e873e 100644
--- a/x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_client.ts
+++ b/x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_client.ts
@@ -15,6 +15,8 @@ import type {
ReportEntityAlertsClickedParams,
ReportEntityRiskFilteredParams,
ReportMLJobUpdateParams,
+ ReportCellActionClickedParams,
+ ReportAnomaliesCountClickedParams,
} from './types';
import { TelemetryEventTypes } from './types';
@@ -88,4 +90,12 @@ export class TelemetryClient implements TelemetryClientStart {
public reportMLJobUpdate = (params: ReportMLJobUpdateParams) => {
this.analytics.reportEvent(TelemetryEventTypes.MLJobUpdate, params);
};
+
+ public reportCellActionClicked = (params: ReportCellActionClickedParams) => {
+ this.analytics.reportEvent(TelemetryEventTypes.CellActionClicked, params);
+ };
+
+ public reportAnomaliesCountClicked = (params: ReportAnomaliesCountClickedParams) => {
+ this.analytics.reportEvent(TelemetryEventTypes.AnomaliesCountClicked, params);
+ };
}
diff --git a/x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_events.ts b/x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_events.ts
index cb82b16658224..e69f90b57a1f0 100644
--- a/x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_events.ts
+++ b/x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_events.ts
@@ -182,6 +182,60 @@ const mlJobUpdateEvent: TelemetryEvent = {
},
};
+const cellActionClickedEvent: TelemetryEvent = {
+ eventType: TelemetryEventTypes.CellActionClicked,
+ schema: {
+ fieldName: {
+ type: 'keyword',
+ _meta: {
+ description: 'Field Name',
+ optional: false,
+ },
+ },
+ actionId: {
+ type: 'keyword',
+ _meta: {
+ description: 'Action id',
+ optional: false,
+ },
+ },
+ displayName: {
+ type: 'keyword',
+ _meta: {
+ description: 'User friendly action name',
+ optional: false,
+ },
+ },
+ metadata: {
+ type: 'pass_through',
+ _meta: {
+ description: 'Action metadata',
+ optional: true,
+ },
+ },
+ },
+};
+
+const anomaliesCountClickedEvent: TelemetryEvent = {
+ eventType: TelemetryEventTypes.AnomaliesCountClicked,
+ schema: {
+ jobId: {
+ type: 'keyword',
+ _meta: {
+ description: 'Job id',
+ optional: false,
+ },
+ },
+ count: {
+ type: 'integer',
+ _meta: {
+ description: 'Number of anomalies',
+ optional: false,
+ },
+ },
+ },
+};
+
export const telemetryEvents = [
alertsGroupingToggledEvent,
alertsGroupingChangedEvent,
@@ -190,4 +244,6 @@ export const telemetryEvents = [
entityAlertsClickedEvent,
entityRiskFilteredEvent,
mlJobUpdateEvent,
+ cellActionClickedEvent,
+ anomaliesCountClickedEvent,
];
diff --git a/x-pack/plugins/security_solution/public/common/lib/telemetry/types.ts b/x-pack/plugins/security_solution/public/common/lib/telemetry/types.ts
index ba084c49cc033..f9f7c84c488ed 100644
--- a/x-pack/plugins/security_solution/public/common/lib/telemetry/types.ts
+++ b/x-pack/plugins/security_solution/public/common/lib/telemetry/types.ts
@@ -8,6 +8,7 @@
import type { RootSchema } from '@kbn/analytics-client';
import type { AnalyticsServiceSetup } from '@kbn/core/public';
import type { RiskSeverity } from '../../../../common/search_strategy';
+import type { SecurityMetadata } from '../../../actions/types';
export interface TelemetryServiceSetupParams {
analytics: AnalyticsServiceSetup;
@@ -21,6 +22,8 @@ export enum TelemetryEventTypes {
EntityAlertsClicked = 'Entity Alerts Clicked',
EntityRiskFiltered = 'Entity Risk Filtered',
MLJobUpdate = 'ML Job Update',
+ CellActionClicked = 'Cell Action Clicked',
+ AnomaliesCountClicked = 'Anomalies Count Clicked',
}
export interface ReportAlertsGroupingChangedParams {
@@ -69,6 +72,18 @@ export interface ReportMLJobUpdateParams {
errorMessage?: string;
}
+export interface ReportCellActionClickedParams {
+ metadata: SecurityMetadata | undefined;
+ displayName: string;
+ actionId: string;
+ fieldName: string;
+}
+
+export interface ReportAnomaliesCountClickedParams {
+ jobId: string;
+ count: number;
+}
+
export type TelemetryEventParams =
| ReportAlertsGroupingChangedParams
| ReportAlertsGroupingToggledParams
@@ -76,7 +91,10 @@ export type TelemetryEventParams =
| ReportEntityDetailsClickedParams
| ReportEntityAlertsClickedParams
| ReportEntityRiskFilteredParams
- | ReportMLJobUpdateParams;
+ | ReportMLJobUpdateParams
+ | ReportCellActionClickedParams
+ | ReportCellActionClickedParams
+ | ReportAnomaliesCountClickedParams;
export interface TelemetryClientStart {
reportAlertsGroupingChanged(params: ReportAlertsGroupingChangedParams): void;
@@ -87,6 +105,10 @@ export interface TelemetryClientStart {
reportEntityAlertsClicked(params: ReportEntityAlertsClickedParams): void;
reportEntityRiskFiltered(params: ReportEntityRiskFilteredParams): void;
reportMLJobUpdate(params: ReportMLJobUpdateParams): void;
+
+ reportCellActionClicked(params: ReportCellActionClickedParams): void;
+
+ reportAnomaliesCountClicked(params: ReportAnomaliesCountClickedParams): void;
}
export type TelemetryEvent =
@@ -117,4 +139,12 @@ export type TelemetryEvent =
| {
eventType: TelemetryEventTypes.MLJobUpdate;
schema: RootSchema;
+ }
+ | {
+ eventType: TelemetryEventTypes.CellActionClicked;
+ schema: RootSchema;
+ }
+ | {
+ eventType: TelemetryEventTypes.AnomaliesCountClicked;
+ schema: RootSchema;
};
diff --git a/x-pack/plugins/security_solution/public/explore/components/risk_score/constants.ts b/x-pack/plugins/security_solution/public/explore/components/risk_score/constants.ts
index 7f2a8e37b94b0..d6147328d6be2 100644
--- a/x-pack/plugins/security_solution/public/explore/components/risk_score/constants.ts
+++ b/x-pack/plugins/security_solution/public/explore/components/risk_score/constants.ts
@@ -7,3 +7,7 @@
export const RISKY_HOSTS_DASHBOARD_TITLE = 'Current Risk Score for Hosts';
export const RISKY_USERS_DASHBOARD_TITLE = 'Current Risk Score for Users';
+
+export const CELL_ACTIONS_TELEMETRY = {
+ component: 'RiskScoreTable',
+};
diff --git a/x-pack/plugins/security_solution/public/explore/hosts/components/host_risk_score_table/columns.tsx b/x-pack/plugins/security_solution/public/explore/hosts/components/host_risk_score_table/columns.tsx
index f1a489ed83b73..1c95938c106ad 100644
--- a/x-pack/plugins/security_solution/public/explore/hosts/components/host_risk_score_table/columns.tsx
+++ b/x-pack/plugins/security_solution/public/explore/hosts/components/host_risk_score_table/columns.tsx
@@ -21,6 +21,7 @@ import type { RiskSeverity } from '../../../../../common/search_strategy';
import { RiskScoreFields, RiskScoreEntity } from '../../../../../common/search_strategy';
import { RiskScore } from '../../../components/risk_score/severity/common';
import { ENTITY_RISK_CLASSIFICATION } from '../../../components/risk_score/translations';
+import { CELL_ACTIONS_TELEMETRY } from '../../../components/risk_score/constants';
export const getHostRiskScoreColumns = ({
dispatchSeverityUpdate,
@@ -47,6 +48,9 @@ export const getHostRiskScoreColumns = ({
type: 'keyword',
aggregatable: true,
}}
+ metadata={{
+ telemetry: CELL_ACTIONS_TELEMETRY,
+ }}
>
diff --git a/x-pack/plugins/security_solution/public/explore/users/components/user_risk_score_table/columns.tsx b/x-pack/plugins/security_solution/public/explore/users/components/user_risk_score_table/columns.tsx
index cf122adc0c397..d8d5abb4981c0 100644
--- a/x-pack/plugins/security_solution/public/explore/users/components/user_risk_score_table/columns.tsx
+++ b/x-pack/plugins/security_solution/public/explore/users/components/user_risk_score_table/columns.tsx
@@ -22,6 +22,7 @@ import { RiskScoreEntity, RiskScoreFields } from '../../../../../common/search_s
import { UserDetailsLink } from '../../../../common/components/links';
import { UsersTableType } from '../../store/model';
import { ENTITY_RISK_CLASSIFICATION } from '../../../components/risk_score/translations';
+import { CELL_ACTIONS_TELEMETRY } from '../../../components/risk_score/constants';
export const getUserRiskScoreColumns = ({
dispatchSeverityUpdate,
@@ -50,6 +51,9 @@ export const getUserRiskScoreColumns = ({
type: 'keyword',
aggregatable: true,
}}
+ metadata={{
+ telemetry: CELL_ACTIONS_TELEMETRY,
+ }}
>
diff --git a/x-pack/plugins/security_solution/public/overview/components/entity_analytics/anomalies/anomalies_count_link.test.tsx b/x-pack/plugins/security_solution/public/overview/components/entity_analytics/anomalies/anomalies_count_link.test.tsx
new file mode 100644
index 0000000000000..4ce4b6810ec98
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/overview/components/entity_analytics/anomalies/anomalies_count_link.test.tsx
@@ -0,0 +1,42 @@
+/*
+ * 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 { fireEvent, render } from '@testing-library/react';
+import React from 'react';
+import { AnomalyEntity } from '../../../../common/components/ml/anomaly/use_anomalies_search';
+import { createTelemetryServiceMock } from '../../../../common/lib/telemetry/telemetry_service.mock';
+import { TestProviders } from '../../../../common/mock';
+import { AnomaliesCountLink } from './anomalies_count_link';
+
+const mockedTelemetry = createTelemetryServiceMock();
+jest.mock('../../../../common/lib/kibana', () => {
+ const original = jest.requireActual('../../../../common/lib/kibana');
+
+ return {
+ ...original,
+ useKibana: () => ({
+ services: {
+ telemetry: mockedTelemetry,
+ },
+ }),
+ };
+});
+
+describe('AnomaliesCountLink', () => {
+ it('reports telemetry when clicked', () => {
+ const count = 10;
+ const jobId = 'test-job-id';
+
+ const { getByRole } = render(
+ ,
+ { wrapper: TestProviders }
+ );
+
+ fireEvent.click(getByRole('button'));
+
+ expect(mockedTelemetry.reportAnomaliesCountClicked).toHaveBeenLastCalledWith({ jobId, count });
+ });
+});
diff --git a/x-pack/plugins/security_solution/public/overview/components/entity_analytics/anomalies/anomalies_count_link.tsx b/x-pack/plugins/security_solution/public/overview/components/entity_analytics/anomalies/anomalies_count_link.tsx
new file mode 100644
index 0000000000000..529f197c62e44
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/overview/components/entity_analytics/anomalies/anomalies_count_link.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; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+import { useDispatch } from 'react-redux';
+import React, { useCallback } from 'react';
+import { AnomalyEntity } from '../../../../common/components/ml/anomaly/use_anomalies_search';
+import { SecuritySolutionLinkAnchor } from '../../../../common/components/links';
+import { SecurityPageName } from '../../../../app/types';
+import { usersActions } from '../../../../explore/users/store';
+import { hostsActions } from '../../../../explore/hosts/store';
+import { HostsType } from '../../../../explore/hosts/store/model';
+import { UsersType } from '../../../../explore/users/store/model';
+
+import { useKibana } from '../../../../common/lib/kibana';
+
+export const AnomaliesCountLink = ({
+ count,
+ jobId,
+ entity,
+}: {
+ count: number;
+ jobId?: string;
+ entity: AnomalyEntity;
+}) => {
+ const dispatch = useDispatch();
+ const { telemetry } = useKibana().services;
+
+ const deepLinkId =
+ entity === AnomalyEntity.User
+ ? SecurityPageName.usersAnomalies
+ : SecurityPageName.hostsAnomalies;
+
+ const onClick = useCallback(() => {
+ if (!jobId) return;
+
+ telemetry.reportAnomaliesCountClicked({
+ jobId,
+ count,
+ });
+
+ if (entity === AnomalyEntity.User) {
+ dispatch(
+ usersActions.updateUsersAnomaliesJobIdFilter({
+ jobIds: [jobId],
+ usersType: UsersType.page,
+ })
+ );
+
+ dispatch(
+ usersActions.updateUsersAnomaliesInterval({
+ interval: 'second',
+ usersType: UsersType.page,
+ })
+ );
+ } else {
+ dispatch(
+ hostsActions.updateHostsAnomaliesJobIdFilter({
+ jobIds: [jobId],
+ hostsType: HostsType.page,
+ })
+ );
+
+ dispatch(
+ hostsActions.updateHostsAnomaliesInterval({
+ interval: 'second',
+ hostsType: HostsType.page,
+ })
+ );
+ }
+ }, [jobId, telemetry, count, entity, dispatch]);
+
+ return (
+
+ {count}
+
+ );
+};
diff --git a/x-pack/plugins/security_solution/public/overview/components/entity_analytics/anomalies/columns.tsx b/x-pack/plugins/security_solution/public/overview/components/entity_analytics/anomalies/columns.tsx
index 92f8751782fa6..1f8556dd27a98 100644
--- a/x-pack/plugins/security_solution/public/overview/components/entity_analytics/anomalies/columns.tsx
+++ b/x-pack/plugins/security_solution/public/overview/components/entity_analytics/anomalies/columns.tsx
@@ -8,24 +8,16 @@ import React, { useCallback, useMemo } from 'react';
import styled from 'styled-components';
import type { EuiBasicTableColumn } from '@elastic/eui';
import { EuiIcon, EuiLoadingSpinner } from '@elastic/eui';
-import { useDispatch } from 'react-redux';
-
import * as i18n from './translations';
import type { AnomaliesCount } from '../../../../common/components/ml/anomaly/use_anomalies_search';
-import { AnomalyEntity } from '../../../../common/components/ml/anomaly/use_anomalies_search';
-
-import { LinkAnchor, SecuritySolutionLinkAnchor } from '../../../../common/components/links';
-import { SecurityPageName } from '../../../../app/types';
-import { usersActions } from '../../../../explore/users/store';
-import { hostsActions } from '../../../../explore/hosts/store';
-import { HostsType } from '../../../../explore/hosts/store/model';
-import { UsersType } from '../../../../explore/users/store/model';
+import { LinkAnchor } from '../../../../common/components/links';
import type { SecurityJob } from '../../../../common/components/ml_popover/types';
import {
isJobFailed,
isJobStarted,
isJobLoading,
} from '../../../../../common/machine_learning/helpers';
+import { AnomaliesCountLink } from './anomalies_count_link';
type AnomaliesColumns = Array>;
@@ -76,7 +68,7 @@ export const useAnomaliesColumns = (
if (!job) return '';
if (count > 0 || isJobStarted(job.jobState, job.datafeedState)) {
- return ;
+ return ;
} else if (isJobFailed(job.jobState, job.datafeedState)) {
return i18n.JOB_STATUS_FAILED;
} else if (job.isCompatible) {
@@ -111,60 +103,3 @@ const EnableJob = ({
);
};
-
-const AnomaliesTabLink = ({
- count,
- jobId,
- entity,
-}: {
- count: number;
- jobId?: string;
- entity: AnomalyEntity;
-}) => {
- const dispatch = useDispatch();
-
- const deepLinkId =
- entity === AnomalyEntity.User
- ? SecurityPageName.usersAnomalies
- : SecurityPageName.hostsAnomalies;
-
- const onClick = useCallback(() => {
- if (!jobId) return;
-
- if (entity === AnomalyEntity.User) {
- dispatch(
- usersActions.updateUsersAnomaliesJobIdFilter({
- jobIds: [jobId],
- usersType: UsersType.page,
- })
- );
-
- dispatch(
- usersActions.updateUsersAnomaliesInterval({
- interval: 'second',
- usersType: UsersType.page,
- })
- );
- } else {
- dispatch(
- hostsActions.updateHostsAnomaliesJobIdFilter({
- jobIds: [jobId],
- hostsType: HostsType.page,
- })
- );
-
- dispatch(
- hostsActions.updateHostsAnomaliesInterval({
- interval: 'second',
- hostsType: HostsType.page,
- })
- );
- }
- }, [jobId, dispatch, entity]);
-
- return (
-
- {count}
-
- );
-};
diff --git a/x-pack/plugins/security_solution/public/overview/components/entity_analytics/risk_score/columns.tsx b/x-pack/plugins/security_solution/public/overview/components/entity_analytics/risk_score/columns.tsx
index 7cd355e967974..eee34e79d99c6 100644
--- a/x-pack/plugins/security_solution/public/overview/components/entity_analytics/risk_score/columns.tsx
+++ b/x-pack/plugins/security_solution/public/overview/components/entity_analytics/risk_score/columns.tsx
@@ -14,6 +14,7 @@ import { getEmptyTagValue } from '../../../../common/components/empty_value';
import { HostDetailsLink, UserDetailsLink } from '../../../../common/components/links';
import { HostsTableType } from '../../../../explore/hosts/store/model';
import { RiskScore } from '../../../../explore/components/risk_score/severity/common';
+import { CELL_ACTIONS_TELEMETRY } from '../../../../explore/components/risk_score/constants';
import type {
HostRiskScore,
RiskSeverity,
@@ -64,6 +65,9 @@ export const getRiskScoreColumns = (
SecurityCellActionType.FILTER,
SecurityCellActionType.SHOW_TOP_N,
]}
+ metadata={{
+ telemetry: CELL_ACTIONS_TELEMETRY,
+ }}
/>
>
) : (
diff --git a/x-pack/plugins/security_solution/public/plugin.tsx b/x-pack/plugins/security_solution/public/plugin.tsx
index 9553b2695f95c..61ec29d956414 100644
--- a/x-pack/plugins/security_solution/public/plugin.tsx
+++ b/x-pack/plugins/security_solution/public/plugin.tsx
@@ -194,7 +194,7 @@ export class Plugin implements IPlugin
Date: Fri, 21 Apr 2023 14:02:03 +0300
Subject: [PATCH 02/15] [Lens] Tests trendline data from inspector (#155479)
## Summary
Part of https://github.com/elastic/kibana/issues/142708
Adds more tests for trendlines depending on the inspector table. The
issue above mentions retrieving the information from EC debug data but I
think fetching the data from the inspector is a good alternative and is
used in general in our FTs.
Test runner 50 times
https://buildkite.com/elastic/kibana-flaky-test-suite-runner/builds/2152
### Checklist
- [ ] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or added to match the most common scenarios
---
test/functional/services/inspector.ts | 13 +++
.../functional/apps/lens/group3/metric.ts | 81 ++++++++++++++++++-
2 files changed, 91 insertions(+), 3 deletions(-)
diff --git a/test/functional/services/inspector.ts b/test/functional/services/inspector.ts
index c3f357ea3875b..cee65bf1c4b52 100644
--- a/test/functional/services/inspector.ts
+++ b/test/functional/services/inspector.ts
@@ -238,6 +238,19 @@ export class InspectorService extends FtrService {
});
}
+ public async getTableDataWithId(tableTestSubj: string): Promise {
+ const chooserDataTestId = 'inspectorTableChooser';
+ if (!(await this.testSubjects.exists(chooserDataTestId))) {
+ return [];
+ }
+
+ return await this.retry.try(async () => {
+ await this.testSubjects.click(chooserDataTestId);
+ await this.testSubjects.click(tableTestSubj);
+ return this.getTableData();
+ });
+ }
+
/**
* Returns the selected option value from combobox
*/
diff --git a/x-pack/test/functional/apps/lens/group3/metric.ts b/x-pack/test/functional/apps/lens/group3/metric.ts
index 843422e87c51f..f5ef312c39d2d 100644
--- a/x-pack/test/functional/apps/lens/group3/metric.ts
+++ b/x-pack/test/functional/apps/lens/group3/metric.ts
@@ -15,6 +15,52 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
const retry = getService('retry');
const inspector = getService('inspector');
+ const inspectorTrendlineData = [
+ ['2015-09-19 06:00', 'null'],
+ ['2015-09-19 09:00', 'null'],
+ ['2015-09-19 12:00', 'null'],
+ ['2015-09-19 15:00', 'null'],
+ ['2015-09-19 18:00', 'null'],
+ ['2015-09-19 21:00', 'null'],
+ ['2015-09-20 00:00', '6,011.351'],
+ ['2015-09-20 03:00', '5,849.901'],
+ ['2015-09-20 06:00', '5,722.622'],
+ ['2015-09-20 09:00', '5,769.092'],
+ ['2015-09-20 12:00', '5,740.875'],
+ ['2015-09-20 15:00', '5,520.429'],
+ ['2015-09-20 18:00', '5,153.053'],
+ ['2015-09-20 21:00', '6,656.581'],
+ ['2015-09-21 00:00', '5,139.357'],
+ ['2015-09-21 03:00', '5,884.891'],
+ ['2015-09-21 06:00', '5,683.283'],
+ ['2015-09-21 09:00', '5,863.661'],
+ ['2015-09-21 12:00', '5,657.531'],
+ ['2015-09-21 15:00', '5,841.935'],
+ ];
+
+ const inspectorExpectedTrenlineDataWithBreakdown = [
+ ['97.220.3.248', '2015-09-19 06:00', 'null'],
+ ['97.220.3.248', '2015-09-19 09:00', 'null'],
+ ['97.220.3.248', '2015-09-19 12:00', 'null'],
+ ['97.220.3.248', '2015-09-19 15:00', 'null'],
+ ['97.220.3.248', '2015-09-19 18:00', 'null'],
+ ['97.220.3.248', '2015-09-19 21:00', 'null'],
+ ['97.220.3.248', '2015-09-20 00:00', 'null'],
+ ['97.220.3.248', '2015-09-20 03:00', 'null'],
+ ['97.220.3.248', '2015-09-20 06:00', 'null'],
+ ['97.220.3.248', '2015-09-20 09:00', 'null'],
+ ['97.220.3.248', '2015-09-20 12:00', 'null'],
+ ['97.220.3.248', '2015-09-20 15:00', 'null'],
+ ['97.220.3.248', '2015-09-20 18:00', 'null'],
+ ['97.220.3.248', '2015-09-20 21:00', 'null'],
+ ['97.220.3.248', '2015-09-21 00:00', 'null'],
+ ['97.220.3.248', '2015-09-21 03:00', 'null'],
+ ['97.220.3.248', '2015-09-21 06:00', 'null'],
+ ['97.220.3.248', '2015-09-21 09:00', '19,755'],
+ ['97.220.3.248', '2015-09-21 12:00', 'null'],
+ ['97.220.3.248', '2015-09-21 15:00', 'null'],
+ ];
+
const clickMetric = async (title: string) => {
const tiles = await PageObjects.lens.getMetricTiles();
for (const tile of tiles) {
@@ -47,7 +93,33 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
});
expect((await PageObjects.lens.getMetricVisualizationData()).length).to.be.equal(1);
+ });
+
+ it('should enable trendlines', async () => {
+ // trendline data without the breakdown
+ await PageObjects.lens.openDimensionEditor(
+ 'lnsMetric_primaryMetricDimensionPanel > lns-dimensionTrigger'
+ );
+ await testSubjects.click('lnsMetric_supporting_visualization_trendline');
+ await PageObjects.lens.closeDimensionEditor();
+
+ await inspector.open('lnsApp_inspectButton');
+
+ const trendLineData = await inspector.getTableDataWithId('inspectorTableChooser1');
+ expect(trendLineData).to.eql(inspectorTrendlineData);
+ await inspector.close();
+ await PageObjects.lens.openDimensionEditor(
+ 'lnsMetric_primaryMetricDimensionPanel > lns-dimensionTrigger'
+ );
+ await testSubjects.click('lnsMetric_supporting_visualization_none');
+ await PageObjects.lens.closeDimensionEditor();
+
+ await PageObjects.lens.waitForVisualization('mtrVis');
+ });
+
+ it('should enable metric with breakdown', async () => {
+ // trendline data without the breakdown
await PageObjects.lens.configureDimension({
dimension: 'lnsMetric_breakdownByDimensionPanel > lns-empty-dimension',
operation: 'terms',
@@ -112,7 +184,9 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
showingBar: false,
},
]);
+ });
+ it('should enable bar with max dimension', async () => {
await PageObjects.lens.openDimensionEditor(
'lnsMetric_maxDimensionPanel > lns-empty-dimension'
);
@@ -125,7 +199,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
await PageObjects.lens.removeDimension('lnsMetric_maxDimensionPanel');
});
- it('should enable trendlines', async () => {
+ it('should enable trendlines with breakdown', async () => {
await PageObjects.lens.openDimensionEditor(
'lnsMetric_primaryMetricDimensionPanel > lns-dimensionTrigger'
);
@@ -139,11 +213,12 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
(datum) => datum.showingTrendline
)
).to.be(true);
+ await PageObjects.lens.closeDimensionEditor();
await inspector.open('lnsApp_inspectButton');
- expect(await inspector.getNumberOfTables()).to.equal(2);
-
+ const trendLineData = await inspector.getTableDataWithId('inspectorTableChooser1');
+ expect(trendLineData).to.eql(inspectorExpectedTrenlineDataWithBreakdown);
await inspector.close();
await PageObjects.lens.openDimensionEditor(
From 134af87114a9186ce453e5414e32e89946bf6d82 Mon Sep 17 00:00:00 2001
From: Sander Philipse <94373878+sphilipse@users.noreply.github.com>
Date: Fri, 21 Apr 2023 13:56:21 +0200
Subject: [PATCH 03/15] [Enterprise Search] Add empty prompt for hidden indices
(#155391)
## Summary
This adds an empty prompt to the index overview page for hidden indices.
### Checklist
Delete any items that are not applicable to this PR.
- [ ] Any text added follows [EUI's writing
guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses
sentence case text and includes [i18n
support](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md)
- [ ]
[Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html)
was added for features that require explanation or tutorials
- [ ] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or added to match the most common scenarios
- [ ] Any UI touched in this PR is usable by keyboard only (learn more
about [keyboard accessibility](https://webaim.org/techniques/keyboard/))
- [ ] Any UI touched in this PR does not create any new axe failures
(run axe in browser:
[FF](https://addons.mozilla.org/en-US/firefox/addon/axe-devtools/),
[Chrome](https://chrome.google.com/webstore/detail/axe-web-accessibility-tes/lhdoppojpmngadmnindnejefpokejbdd?hl=en-US))
- [ ] If a plugin configuration key changed, check if it needs to be
allowlisted in the cloud and added to the [docker
list](https://github.com/elastic/kibana/blob/main/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker)
- [ ] This renders correctly on smaller devices using a responsive
layout. (You can test this [in your
browser](https://www.browserstack.com/guide/responsive-testing-on-local-server))
- [ ] This was checked for [cross-browser
compatibility](https://www.elastic.co/support/matrix#matrix_browsers)
### Risk Matrix
Delete this section if it is not applicable to this PR.
Before closing this PR, invite QA, stakeholders, and other developers to
identify risks that should be tested prior to the change/feature
release.
When forming the risk matrix, consider some of the following examples
and how they may potentially impact the change:
| Risk | Probability | Severity | Mitigation/Notes |
|---------------------------|-------------|----------|-------------------------|
| Multiple Spaces—unexpected behavior in non-default Kibana Space.
| Low | High | Integration tests will verify that all features are still
supported in non-default Kibana Space and when user switches between
spaces. |
| Multiple nodes—Elasticsearch polling might have race conditions
when multiple Kibana nodes are polling for the same tasks. | High | Low
| Tasks are idempotent, so executing them multiple times will not result
in logical error, but will degrade performance. To test for this case we
add plenty of unit tests around this logic and document manual testing
procedure. |
| Code should gracefully handle cases when feature X or plugin Y are
disabled. | Medium | High | Unit tests will verify that any feature flag
or plugin combination still results in our service operational. |
| [See more potential risk
examples](https://github.com/elastic/kibana/blob/main/RISK_MATRIX.mdx) |
### For maintainers
- [ ] This was checked for breaking API changes and was [labeled
appropriately](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)
---------
Co-authored-by: Liam Thompson <32779855+leemthompo@users.noreply.github.com>
---
.../search_index/generate_api_key_panel.tsx | 115 +++++++++++-------
.../search_index/index_view_logic.test.ts | 1 +
.../search_index/index_view_logic.ts | 6 +
3 files changed, 76 insertions(+), 46 deletions(-)
diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/generate_api_key_panel.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/generate_api_key_panel.tsx
index 40cdda3aaae32..365593c4699c3 100644
--- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/generate_api_key_panel.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/generate_api_key_panel.tsx
@@ -9,7 +9,15 @@ import React, { useState } from 'react';
import { useActions, useValues } from 'kea';
-import { EuiFlexGroup, EuiFlexItem, EuiPanel, EuiSpacer, EuiSwitch, EuiTitle } from '@elastic/eui';
+import {
+ EuiEmptyPrompt,
+ EuiFlexGroup,
+ EuiFlexItem,
+ EuiPanel,
+ EuiSpacer,
+ EuiSwitch,
+ EuiTitle,
+} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
@@ -27,7 +35,7 @@ import { OverviewLogic } from './overview.logic';
export const GenerateApiKeyPanel: React.FC = () => {
const { apiKey, isGenerateModalOpen } = useValues(OverviewLogic);
- const { indexName, ingestionMethod } = useValues(IndexViewLogic);
+ const { indexName, ingestionMethod, isHiddenIndex } = useValues(IndexViewLogic);
const { closeGenerateModal } = useActions(OverviewLogic);
const { defaultPipeline } = useValues(SettingsLogic);
@@ -41,11 +49,29 @@ export const GenerateApiKeyPanel: React.FC = () => {
-
-
-
-
- {indexName[0] !== '.' && (
+ {isHiddenIndex ? (
+
+ {i18n.translate('xpack.enterpriseSearch.content.overview.emptyPrompt.body', {
+ defaultMessage:
+ 'We do not recommend adding documents to an externally managed index.',
+ })}
+
+ }
+ title={
+
+ {i18n.translate('xpack.enterpriseSearch.content.overview.emptyPrompt.title', {
+ defaultMessage: 'Index managed externally',
+ })}
+
+ }
+ />
+ ) : (
+
+
+
+
{i18n.translate(
@@ -54,45 +80,42 @@ export const GenerateApiKeyPanel: React.FC = () => {
)}
- )}
-
-
- setOptimizedRequest(event.target.checked)}
- label={i18n.translate(
- 'xpack.enterpriseSearch.content.overview.optimizedRequest.label',
- { defaultMessage: 'View Enterprise Search optimized request' }
- )}
- checked={optimizedRequest}
- />
-
-
-
-
-
-
-
-
-
-
-
-
-
- {indexName[0] !== '.' && (
- <>
-
-
-
-
- >
- )}
-
+
+
+ setOptimizedRequest(event.target.checked)}
+ label={i18n.translate(
+ 'xpack.enterpriseSearch.content.overview.optimizedRequest.label',
+ { defaultMessage: 'View Enterprise Search optimized request' }
+ )}
+ checked={optimizedRequest}
+ />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ )}
diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/index_view_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/index_view_logic.test.ts
index 5e4256f92a97b..e440947cdfb5c 100644
--- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/index_view_logic.test.ts
+++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/index_view_logic.test.ts
@@ -51,6 +51,7 @@ const DEFAULT_VALUES = {
ingestionStatus: IngestionStatus.CONNECTED,
isCanceling: false,
isConnectorIndex: false,
+ isHiddenIndex: false,
isInitialLoading: false,
isSyncing: false,
isWaitingForSync: false,
diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/index_view_logic.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/index_view_logic.ts
index b89e122f03c7f..cd33656062fdb 100644
--- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/index_view_logic.ts
+++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/index_view_logic.ts
@@ -77,6 +77,7 @@ export interface IndexViewValues {
ingestionMethod: IngestionMethod;
ingestionStatus: IngestionStatus;
isCanceling: boolean;
+ isHiddenIndex: boolean;
isInitialLoading: typeof CachedFetchIndexApiLogic.values.isInitialLoading;
isSyncing: boolean;
isWaitingForSync: boolean;
@@ -234,6 +235,11 @@ export const IndexViewLogic = kea [selectors.indexData],
(data: FetchIndexApiResponse | undefined) => isConnectorIndex(data),
],
+ isHiddenIndex: [
+ () => [selectors.indexData],
+ (data: FetchIndexApiResponse | undefined) =>
+ data?.hidden || (data?.name ?? '').startsWith('.'),
+ ],
isSyncing: [
() => [selectors.indexData, selectors.syncStatus],
(indexData: FetchIndexApiResponse | null, syncStatus: SyncStatus) =>
From 9e712bb6fec3e2fe882cfa2342efbf0ee28d2441 Mon Sep 17 00:00:00 2001
From: Sander Philipse <94373878+sphilipse@users.noreply.github.com>
Date: Fri, 21 Apr 2023 13:57:34 +0200
Subject: [PATCH 04/15] [Enterprise Search] Replace connectors ruby references
with python (#155374)
## Summary
This replaces all references to the Ruby repository with references to
the Python repository for connectors.
---------
Co-authored-by: Navarone Feekery <13634519+navarone-feekery@users.noreply.github.com>
Co-authored-by: Liam Thompson <32779855+leemthompo@users.noreply.github.com>
---
.../search_index/connector/connector_configuration.tsx | 8 ++++----
.../connector/custom_connector_configuration_config.tsx | 8 ++++----
2 files changed, 8 insertions(+), 8 deletions(-)
diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/connector_configuration.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/connector_configuration.tsx
index 90972bcfc3c51..0abd1ba5b4afe 100644
--- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/connector_configuration.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/connector_configuration.tsx
@@ -124,7 +124,7 @@ export const ConnectorConfiguration: React.FC = () => {
values={{
link: (
@@ -145,7 +145,7 @@ export const ConnectorConfiguration: React.FC = () => {
values={{
link: (
@@ -356,7 +356,7 @@ export const ConnectorConfiguration: React.FC = () => {
{i18n.translate(
@@ -382,7 +382,7 @@ export const ConnectorConfiguration: React.FC = () => {
{i18n.translate(
diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/custom_connector_configuration_config.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/custom_connector_configuration_config.tsx
index 58be80f2caa15..6eb991df1cdf9 100644
--- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/custom_connector_configuration_config.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/custom_connector_configuration_config.tsx
@@ -18,16 +18,16 @@ export const ConnectorConfigurationConfig: React.FC = () => {
{i18n.translate(
'xpack.enterpriseSearch.content.indices.configurationConnector.config.connectorClientLink',
- { defaultMessage: 'example connector client' }
+ { defaultMessage: 'connectors' }
)}
),
@@ -56,7 +56,7 @@ export const ConnectorConfigurationConfig: React.FC = () => {
),
issuesLink: (
-
+
{i18n.translate(
'xpack.enterpriseSearch.content.indices.configurationConnector.config.issuesLink',
{ defaultMessage: 'issue' }
From 60c8b8fecd45bdcf833d17d1f7a2e1cab75f5e85 Mon Sep 17 00:00:00 2001
From: Achyut Jhunjhunwala
Date: Fri, 21 Apr 2023 14:16:19 +0200
Subject: [PATCH 05/15] [APM] Implement Unified Search for APM (#153842)
## Summary
This PR brings Unified Search to APM
https://github.com/elastic/kibana/issues/152147
## Scope of Implementation
1. We are only adding the search capability for now.
2. Filters and Saved queries are not part of this scope
### Pending Items
- [x] Add Unit tests
- [x] Fix existing broken Unit tests
- [x] Fix existing broken Cy tests -
https://github.com/elastic/kibana/pull/154059
- [x] Replace the search bar for mobile
- [x] Work on feedback after deploying this branch
- [x] Add validation for Free Text. Awaiting -
https://github.com/elastic/kibana/issues/154239
- [x] Add logic to pass custom filters to Unified Search. Awaiting -
https://github.com/elastic/kibana/issues/154437
### Pages using Unified Search
- [x] Service Inventory
- [x] Service Map
- [x] Service Overview
- [x] Transactions Overview
- [x] Errors
- [x] Trace Overview
- [x] Dependencies Inventory
- [x] Agent Explorer
- [x] Storage Explorer
### Pages still using old Custom Implementation
- [ ] Trace Explorer - Out of scope for this PR
- [ ] Service Group - Changing this logic could take some additional
time as this would mean we allowing our SearchBar component to accept a
custom submit function which does not updates the URL, as in every other
implementation, we update the URL. I would mark this as a follow up
ticket/stretch goal - https://github.com/elastic/kibana/issues/154320
- [x] Alerts - ~~It uses a Custom Search bar built by Actionable Obs
team. Not sure if it's in this scope.~~ Seems they are already using US
## Things to consider
- [x] ~~What do we do with the old components - `KueryBar` and
`ApmDatePicker`. Should we delete them ?~~ The existing component will
stay as its still used in Certain places, see `Pages still using old
Custom Implementation` of this PR
- [x] Other implementation are supporting Free Text Search, hence this
one is too and is not checking for valid KQL. I hope my understanding is
correct here - If needed, then awaiting -
https://github.com/elastic/kibana/issues/154239
[Update] - We will add validations for free text to replicate the
previous behaviour which we had with our own custom implementation
- [ ] The UX of the search bar is a bit off when it comes to long
placeholders. May be we need a shorter text for placeholder -
@boriskirov ?
- [x] ~~When navigating from Service Inventory page to Traces or
Dependencies page, we are only persisting URL state for the selected
time range. Shouldn't KQL query be also part of it. If yes, that would a
stretch goal but good thing to consider.~~ @gbamparop @sqren - As
discussed during the demo, we will keep this functionality as it is,
which means while navigating to a different page, the search bar will be
reset, but the when navigating between tabs on the same page, search bar
will persist.
- [x] ~~On the Initial page load, the Unified Search Bar Input box does
not loads immediately. You only see the DateTimePicker and the button.
Once the component has completely loaded, only then the text box appear.
I see other pages like Log Streams brings up a Full Page loader unless
the component has loaded and then once certain things have loaded, they
remove the full page loader and start showing the search bar. Opinion
?~~ @boriskirov @gbamparop @sqren - Added a EUI Skeleton to handle this
issue.
https://user-images.githubusercontent.com/7416358/228291762-0ca55e9a-7de9-4312-aa58-f484441430ce.mov
---------
Co-authored-by: Katerina Patticha
---
x-pack/plugins/apm/common/dependencies.ts | 6 +-
.../storage_explorer/storage_explorer.cy.ts | 9 -
.../e2e/read_only_user/deep_links.cy.ts | 12 +
.../read_only_user/errors/errors_page.cy.ts | 6 +-
.../header_filters/header_filters.cy.ts | 2 +-
.../service_inventory/service_inventory.cy.ts | 9 -
.../service_overview/errors_table.cy.ts | 4 +-
.../service_overview/header_filters.cy.ts | 24 +-
.../service_overview/time_comparison.cy.ts | 2 +-
.../transaction_details/span_links.cy.ts | 12 +-
.../app/dependencies_inventory/index.tsx | 13 +-
.../components/app/mobile/search_bar.tsx | 24 +-
.../components/app/service_map/index.tsx | 2 +-
.../app/settings/agent_explorer/index.tsx | 9 +-
.../routing/mobile_service_detail/index.tsx | 2 +-
.../routing/service_detail/index.tsx | 6 +-
.../templates/dependency_detail_template.tsx | 15 +-
.../shared/{kuery_bar => }/get_bool_filter.ts | 10 +-
.../components/shared/kuery_bar/index.tsx | 4 +-
.../shared/search_bar/search_bar.test.tsx | 14 +
.../shared/search_bar/search_bar.tsx | 29 +-
.../shared/unified_search_bar/index.tsx | 248 ++++++++++++++++++
.../unified_search_bar.test.tsx | 138 ++++++++++
.../apm_plugin/mock_apm_plugin_context.tsx | 7 +
.../use_processor_event.ts | 2 +-
.../translations/translations/fr-FR.json | 1 -
.../translations/translations/ja-JP.json | 1 -
.../translations/translations/zh-CN.json | 1 -
28 files changed, 500 insertions(+), 112 deletions(-)
rename x-pack/plugins/apm/public/components/shared/{kuery_bar => }/get_bool_filter.ts (85%)
create mode 100644 x-pack/plugins/apm/public/components/shared/unified_search_bar/index.tsx
create mode 100644 x-pack/plugins/apm/public/components/shared/unified_search_bar/unified_search_bar.test.tsx
rename x-pack/plugins/apm/public/{components/shared/kuery_bar => hooks}/use_processor_event.ts (94%)
diff --git a/x-pack/plugins/apm/common/dependencies.ts b/x-pack/plugins/apm/common/dependencies.ts
index 6228d4b9d279d..812bb33342ba5 100644
--- a/x-pack/plugins/apm/common/dependencies.ts
+++ b/x-pack/plugins/apm/common/dependencies.ts
@@ -13,14 +13,14 @@ import {
} from './es_fields/apm';
import { environmentQuery } from './utils/environment_query';
-export const kueryBarPlaceholder = i18n.translate(
- 'xpack.apm.dependencies.kueryBarPlaceholder',
+export const unifiedSearchBarPlaceholder = i18n.translate(
+ 'xpack.apm.dependencies.unifiedSearchBarPlaceholder',
{
defaultMessage: `Search dependency metrics (e.g. span.destination.service.resource:elasticsearch)`,
}
);
-export const getKueryBarBoolFilter = ({
+export const getSearchBarBoolFilter = ({
dependencyName,
environment,
}: {
diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/power_user/storage_explorer/storage_explorer.cy.ts b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/power_user/storage_explorer/storage_explorer.cy.ts
index 3fdeff2e406a8..88d4408d33152 100644
--- a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/power_user/storage_explorer/storage_explorer.cy.ts
+++ b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/power_user/storage_explorer/storage_explorer.cy.ts
@@ -140,12 +140,6 @@ describe('Storage Explorer', () => {
});
});
- it('when clicking the refresh button', () => {
- cy.wait(mainAliasNames);
- cy.contains('Refresh').click();
- cy.wait(mainAliasNames);
- });
-
it('when selecting a different time range and clicking the update button', () => {
cy.wait(mainAliasNames);
@@ -155,9 +149,6 @@ describe('Storage Explorer', () => {
);
cy.contains('Update').click();
cy.wait(mainAliasNames);
-
- cy.contains('Refresh').click();
- cy.wait(mainAliasNames);
});
it('with the correct lifecycle phase when changing the lifecycle phase', () => {
diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/deep_links.cy.ts b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/deep_links.cy.ts
index 2ae901be32fbf..5451a0df2d845 100644
--- a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/deep_links.cy.ts
+++ b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/deep_links.cy.ts
@@ -17,6 +17,8 @@ describe('APM deep links', () => {
cy.contains('APM / Service groups');
cy.contains('APM / Traces');
cy.contains('APM / Service Map');
+ cy.contains('APM / Dependencies');
+ cy.contains('APM / Settings');
// navigates to home page
// Force click because welcome screen changes
@@ -43,5 +45,15 @@ describe('APM deep links', () => {
// navigates to service maps
cy.contains('APM / Service Map').click({ force: true });
cy.url().should('include', '/apm/service-map');
+
+ cy.getByTestSubj('nav-search-input').type('APM');
+ // navigates to dependencies page
+ cy.contains('APM / Dependencies').click({ force: true });
+ cy.url().should('include', '/apm/dependencies/inventory');
+
+ cy.getByTestSubj('nav-search-input').type('APM');
+ // navigates to settings page
+ cy.contains('APM / Settings').click({ force: true });
+ cy.url().should('include', '/apm/settings/general-settings');
});
});
diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/errors/errors_page.cy.ts b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/errors/errors_page.cy.ts
index bc6f872d76fcc..a64dc157d4037 100644
--- a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/errors/errors_page.cy.ts
+++ b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/errors/errors_page.cy.ts
@@ -81,16 +81,16 @@ describe('Errors page', () => {
cy.contains('div', 'Error 1');
});
- it('clicking on type adds a filter in the kuerybar', () => {
+ it('clicking on type adds a filter in the searchbar', () => {
cy.visitKibana(javaServiceErrorsPageHref);
- cy.getByTestSubj('headerFilterKuerybar')
+ cy.getByTestSubj('apmUnifiedSearchBar')
.invoke('val')
.should('be.empty');
// `force: true` because Cypress says the element is 0x0
cy.contains('exception 0').click({
force: true,
});
- cy.getByTestSubj('headerFilterKuerybar')
+ cy.getByTestSubj('apmUnifiedSearchBar')
.its('length')
.should('be.gt', 0);
cy.get('table')
diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/service_inventory/header_filters/header_filters.cy.ts b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/service_inventory/header_filters/header_filters.cy.ts
index e689e126d4bfd..8e30f2784eb34 100644
--- a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/service_inventory/header_filters/header_filters.cy.ts
+++ b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/service_inventory/header_filters/header_filters.cy.ts
@@ -45,7 +45,7 @@ describe('Service inventory - header filters', () => {
cy.contains('Services');
cy.contains('opbeans-node');
cy.contains('service 1');
- cy.getByTestSubj('headerFilterKuerybar')
+ cy.getByTestSubj('apmUnifiedSearchBar')
.type(`service.name: "${specialServiceName}"`)
.type('{enter}');
cy.contains('service 1');
diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/service_inventory/service_inventory.cy.ts b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/service_inventory/service_inventory.cy.ts
index 2d40c690a8c92..032409ec56d40 100644
--- a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/service_inventory/service_inventory.cy.ts
+++ b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/service_inventory/service_inventory.cy.ts
@@ -103,12 +103,6 @@ describe('Service inventory', () => {
});
});
- it('when clicking the refresh button', () => {
- cy.wait(mainAliasNames);
- cy.contains('Refresh').click();
- cy.wait(mainAliasNames);
- });
-
it('when selecting a different time range and clicking the update button', () => {
cy.wait(mainAliasNames);
@@ -118,9 +112,6 @@ describe('Service inventory', () => {
);
cy.contains('Update').click();
cy.wait(mainAliasNames);
-
- cy.contains('Refresh').click();
- cy.wait(mainAliasNames);
});
});
diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/service_overview/errors_table.cy.ts b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/service_overview/errors_table.cy.ts
index d693148010c7e..2970aa3887e95 100644
--- a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/service_overview/errors_table.cy.ts
+++ b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/service_overview/errors_table.cy.ts
@@ -50,12 +50,12 @@ describe('Errors table', () => {
it('clicking on type adds a filter in the kuerybar and navigates to errors page', () => {
cy.visitKibana(serviceOverviewHref);
- cy.getByTestSubj('headerFilterKuerybar').invoke('val').should('be.empty');
+ cy.getByTestSubj('apmUnifiedSearchBar').invoke('val').should('be.empty');
// `force: true` because Cypress says the element is 0x0
cy.contains('Exception').click({
force: true,
});
- cy.getByTestSubj('headerFilterKuerybar').its('length').should('be.gt', 0);
+ cy.getByTestSubj('apmUnifiedSearchBar').its('length').should('be.gt', 0);
cy.get('table').find('td:contains("Exception")').should('have.length', 1);
});
diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/service_overview/header_filters.cy.ts b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/service_overview/header_filters.cy.ts
index 8a25024506696..05fe508092ff4 100644
--- a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/service_overview/header_filters.cy.ts
+++ b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/service_overview/header_filters.cy.ts
@@ -117,7 +117,7 @@ describe('Service overview - header filters', () => {
});
});
- describe('Filtering by kuerybar', () => {
+ describe('Filtering by searchbar', () => {
beforeEach(() => {
cy.loginAsViewerUser();
});
@@ -129,13 +129,23 @@ describe('Service overview - header filters', () => {
})
);
cy.contains('opbeans-java');
- cy.getByTestSubj('headerFilterKuerybar').type('transaction.n');
+ cy.getByTestSubj('apmUnifiedSearchBar').type('transaction.n');
cy.contains('transaction.name');
- cy.getByTestSubj('suggestionContainer').find('li').first().click();
- cy.getByTestSubj('headerFilterKuerybar').type(':');
- cy.getByTestSubj('suggestionContainer').find('li').first().click();
- cy.getByTestSubj('headerFilterKuerybar').type('{enter}');
- cy.url().should('include', '&kuery=transaction.name');
+ cy.getByTestSubj(
+ 'autocompleteSuggestion-field-transaction.name-'
+ ).click();
+ cy.getByTestSubj('apmUnifiedSearchBar').type(':');
+ cy.getByTestSubj('autoCompleteSuggestionText').should('have.length', 1);
+ cy.getByTestSubj(
+ Cypress.$.escapeSelector(
+ 'autocompleteSuggestion-value-"GET-/api/product"-'
+ )
+ ).click();
+ cy.getByTestSubj('apmUnifiedSearchBar').type('{enter}');
+ cy.url().should(
+ 'include',
+ '&kuery=transaction.name%20:%22GET%20%2Fapi%2Fproduct%22%20'
+ );
});
});
});
diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/service_overview/time_comparison.cy.ts b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/service_overview/time_comparison.cy.ts
index bce3da42d5a3f..ec511a0725aac 100644
--- a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/service_overview/time_comparison.cy.ts
+++ b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/service_overview/time_comparison.cy.ts
@@ -121,7 +121,7 @@ describe('Service overview: Time Comparison', () => {
'2021-10-20T00:00:00.000Z'
);
- cy.getByTestSubj('superDatePickerApplyTimeButton').click();
+ cy.getByTestSubj('querySubmitButton').click();
cy.getByTestSubj('comparisonSelect').should('have.value', '864000000ms');
cy.getByTestSubj('comparisonSelect').should(
diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/transaction_details/span_links.cy.ts b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/transaction_details/span_links.cy.ts
index 60b36b10ee4a3..0ec3c16e8658b 100644
--- a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/transaction_details/span_links.cy.ts
+++ b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/transaction_details/span_links.cy.ts
@@ -52,7 +52,7 @@ describe('Span links', () => {
cy.contains('2 Span links');
cy.getByTestSubj(
`spanLinksBadge_${ids.producerInternalOnlyIds.spanAId}`
- ).realHover();
+ ).trigger('mouseover');
cy.contains('2 Span links found');
cy.contains('2 incoming');
cy.contains('0 outgoing');
@@ -66,7 +66,7 @@ describe('Span links', () => {
cy.contains('2 Span links');
cy.getByTestSubj(
`spanLinksBadge_${ids.producerExternalOnlyIds.spanBId}`
- ).realHover();
+ ).trigger('mouseover');
cy.contains('2 Span links found');
cy.contains('1 incoming');
cy.contains('1 outgoing');
@@ -80,7 +80,7 @@ describe('Span links', () => {
cy.contains('2 Span links');
cy.getByTestSubj(
`spanLinksBadge_${ids.producerConsumerIds.transactionCId}`
- ).realHover();
+ ).trigger('mouseover');
cy.contains('2 Span links found');
cy.contains('1 incoming');
cy.contains('1 outgoing');
@@ -94,7 +94,7 @@ describe('Span links', () => {
cy.contains('1 Span link');
cy.getByTestSubj(
`spanLinksBadge_${ids.producerConsumerIds.spanCId}`
- ).realHover();
+ ).trigger('mouseover');
cy.contains('1 Span link found');
cy.contains('1 incoming');
cy.contains('0 outgoing');
@@ -108,7 +108,7 @@ describe('Span links', () => {
cy.contains('2 Span links');
cy.getByTestSubj(
`spanLinksBadge_${ids.producerMultipleIds.transactionDId}`
- ).realHover();
+ ).trigger('mouseover');
cy.contains('2 Span links found');
cy.contains('0 incoming');
cy.contains('2 outgoing');
@@ -122,7 +122,7 @@ describe('Span links', () => {
cy.contains('2 Span links');
cy.getByTestSubj(
`spanLinksBadge_${ids.producerMultipleIds.spanEId}`
- ).realHover();
+ ).trigger('mouseover');
cy.contains('2 Span links found');
cy.contains('0 incoming');
cy.contains('2 outgoing');
diff --git a/x-pack/plugins/apm/public/components/app/dependencies_inventory/index.tsx b/x-pack/plugins/apm/public/components/app/dependencies_inventory/index.tsx
index b4ae13619abb0..ff569f369ba4e 100644
--- a/x-pack/plugins/apm/public/components/app/dependencies_inventory/index.tsx
+++ b/x-pack/plugins/apm/public/components/app/dependencies_inventory/index.tsx
@@ -8,27 +8,26 @@
import { EuiSpacer } from '@elastic/eui';
import React from 'react';
import {
- getKueryBarBoolFilter,
- kueryBarPlaceholder,
+ unifiedSearchBarPlaceholder,
+ getSearchBarBoolFilter,
} from '../../../../common/dependencies';
-import { useApmParams } from '../../../hooks/use_apm_params';
import { SearchBar } from '../../shared/search_bar/search_bar';
import { DependenciesInventoryTable } from './dependencies_inventory_table';
+import { useApmParams } from '../../../hooks/use_apm_params';
export function DependenciesInventory() {
const {
query: { environment },
} = useApmParams('/dependencies/inventory');
- const kueryBarBoolFilter = getKueryBarBoolFilter({
+ const searchBarBoolFilter = getSearchBarBoolFilter({
environment,
});
-
return (
<>
diff --git a/x-pack/plugins/apm/public/components/app/mobile/search_bar.tsx b/x-pack/plugins/apm/public/components/app/mobile/search_bar.tsx
index 3039b69d094a9..7d41b8cf3ee01 100644
--- a/x-pack/plugins/apm/public/components/app/mobile/search_bar.tsx
+++ b/x-pack/plugins/apm/public/components/app/mobile/search_bar.tsx
@@ -5,7 +5,6 @@
* 2.0.
*/
-import { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
import {
EuiFlexGroup,
EuiFlexGroupProps,
@@ -14,30 +13,27 @@ import {
} from '@elastic/eui';
import React from 'react';
import { useBreakpoints } from '../../../hooks/use_breakpoints';
-import { ApmDatePicker } from '../../shared/date_picker/apm_date_picker';
-import { KueryBar } from '../../shared/kuery_bar';
import { TimeComparison } from '../../shared/time_comparison';
import { TransactionTypeSelect } from '../../shared/transaction_type_select';
import { MobileFilters } from './service_overview/filters';
+import { UnifiedSearchBar } from '../../shared/unified_search_bar';
interface Props {
hidden?: boolean;
- showKueryBar?: boolean;
+ showUnifiedSearchBar?: boolean;
showTimeComparison?: boolean;
showTransactionTypeSelector?: boolean;
showMobileFilters?: boolean;
- kueryBarPlaceholder?: string;
- kueryBarBoolFilter?: QueryDslQueryContainer[];
+ searchBarPlaceholder?: string;
}
export function MobileSearchBar({
hidden = false,
- showKueryBar = true,
+ showUnifiedSearchBar = true,
showTimeComparison = false,
showTransactionTypeSelector = false,
showMobileFilters = false,
- kueryBarBoolFilter,
- kueryBarPlaceholder,
+ searchBarPlaceholder,
}: Props) {
const { isSmall, isMedium, isLarge, isXl, isXXXL } = useBreakpoints();
@@ -66,17 +62,11 @@ export function MobileSearchBar({
)}
- {showKueryBar && (
+ {showUnifiedSearchBar && (
-
+
)}
-
-
-
diff --git a/x-pack/plugins/apm/public/components/app/service_map/index.tsx b/x-pack/plugins/apm/public/components/app/service_map/index.tsx
index 2b3d8c8eaf15c..f9b848b816d4c 100644
--- a/x-pack/plugins/apm/public/components/app/service_map/index.tsx
+++ b/x-pack/plugins/apm/public/components/app/service_map/index.tsx
@@ -40,7 +40,7 @@ import { DisabledPrompt } from './disabled_prompt';
function PromptContainer({ children }: { children: ReactNode }) {
return (
<>
-
+
-
+
diff --git a/x-pack/plugins/apm/public/components/routing/mobile_service_detail/index.tsx b/x-pack/plugins/apm/public/components/routing/mobile_service_detail/index.tsx
index 0d7a6ade36140..52e127c63f805 100644
--- a/x-pack/plugins/apm/public/components/routing/mobile_service_detail/index.tsx
+++ b/x-pack/plugins/apm/public/components/routing/mobile_service_detail/index.tsx
@@ -33,7 +33,7 @@ export function page({
tabKey: React.ComponentProps['selectedTabKey'];
element: React.ReactElement;
searchBarOptions?: {
- showKueryBar?: boolean;
+ showUnifiedSearchBar?: boolean;
showTransactionTypeSelector?: boolean;
showTimeComparison?: boolean;
showMobileFilters?: boolean;
diff --git a/x-pack/plugins/apm/public/components/routing/service_detail/index.tsx b/x-pack/plugins/apm/public/components/routing/service_detail/index.tsx
index 6158cf85d0a84..ed2b3f477ced0 100644
--- a/x-pack/plugins/apm/public/components/routing/service_detail/index.tsx
+++ b/x-pack/plugins/apm/public/components/routing/service_detail/index.tsx
@@ -48,7 +48,7 @@ function page({
tab: React.ComponentProps['selectedTab'];
element: React.ReactElement;
searchBarOptions?: {
- showKueryBar?: boolean;
+ showUnifiedSearchBar?: boolean;
showTransactionTypeSelector?: boolean;
showTimeComparison?: boolean;
hidden?: boolean;
@@ -320,7 +320,7 @@ export const serviceDetail = {
}),
element: ,
searchBarOptions: {
- showKueryBar: false,
+ showUnifiedSearchBar: false,
},
}),
'/services/{serviceName}/infrastructure': {
@@ -331,7 +331,7 @@ export const serviceDetail = {
}),
element: ,
searchBarOptions: {
- showKueryBar: false,
+ showUnifiedSearchBar: false,
},
}),
params: t.partial({
diff --git a/x-pack/plugins/apm/public/components/routing/templates/dependency_detail_template.tsx b/x-pack/plugins/apm/public/components/routing/templates/dependency_detail_template.tsx
index 7b57dbade2216..ecc4435f092ed 100644
--- a/x-pack/plugins/apm/public/components/routing/templates/dependency_detail_template.tsx
+++ b/x-pack/plugins/apm/public/components/routing/templates/dependency_detail_template.tsx
@@ -8,10 +8,7 @@
import { EuiFlexGroup, EuiFlexItem, EuiTitle } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import React from 'react';
-import {
- getKueryBarBoolFilter,
- kueryBarPlaceholder,
-} from '../../../../common/dependencies';
+import { unifiedSearchBarPlaceholder } from '../../../../common/dependencies';
import { useApmParams } from '../../../hooks/use_apm_params';
import { useApmRouter } from '../../../hooks/use_apm_router';
import { useApmRoutePath } from '../../../hooks/use_apm_route_path';
@@ -29,7 +26,7 @@ interface Props {
export function DependencyDetailTemplate({ children }: Props) {
const {
query,
- query: { dependencyName, rangeFrom, rangeTo, environment },
+ query: { dependencyName, rangeFrom, rangeTo },
} = useApmParams('/dependencies');
const router = useApmRouter();
@@ -38,11 +35,6 @@ export function DependencyDetailTemplate({ children }: Props) {
const path = useApmRoutePath();
- const kueryBarBoolFilter = getKueryBarBoolFilter({
- environment,
- dependencyName,
- });
-
const dependencyMetadataFetch = useFetcher(
(callApmApi) => {
if (!start || !end) {
@@ -113,8 +105,7 @@ export function DependencyDetailTemplate({ children }: Props) {
>
{children}
diff --git a/x-pack/plugins/apm/public/components/shared/kuery_bar/get_bool_filter.ts b/x-pack/plugins/apm/public/components/shared/get_bool_filter.ts
similarity index 85%
rename from x-pack/plugins/apm/public/components/shared/kuery_bar/get_bool_filter.ts
rename to x-pack/plugins/apm/public/components/shared/get_bool_filter.ts
index 92a8a9172ff9a..42575126aaab5 100644
--- a/x-pack/plugins/apm/public/components/shared/kuery_bar/get_bool_filter.ts
+++ b/x-pack/plugins/apm/public/components/shared/get_bool_filter.ts
@@ -12,11 +12,11 @@ import {
SERVICE_NAME,
TRANSACTION_NAME,
TRANSACTION_TYPE,
-} from '../../../../common/es_fields/apm';
-import { ENVIRONMENT_ALL } from '../../../../common/environment_filter_values';
-import { UIProcessorEvent } from '../../../../common/processor_event';
-import { environmentQuery } from '../../../../common/utils/environment_query';
-import { ApmUrlParams } from '../../../context/url_params_context/types';
+} from '../../../common/es_fields/apm';
+import { ENVIRONMENT_ALL } from '../../../common/environment_filter_values';
+import { UIProcessorEvent } from '../../../common/processor_event';
+import { environmentQuery } from '../../../common/utils/environment_query';
+import { ApmUrlParams } from '../../context/url_params_context/types';
export function getBoolFilter({
groupId,
diff --git a/x-pack/plugins/apm/public/components/shared/kuery_bar/index.tsx b/x-pack/plugins/apm/public/components/shared/kuery_bar/index.tsx
index 825fa8e3ef7f4..d166c7d283eda 100644
--- a/x-pack/plugins/apm/public/components/shared/kuery_bar/index.tsx
+++ b/x-pack/plugins/apm/public/components/shared/kuery_bar/index.tsx
@@ -18,9 +18,9 @@ import { useLegacyUrlParams } from '../../../context/url_params_context/use_url_
import { useApmParams } from '../../../hooks/use_apm_params';
import { useApmDataView } from '../../../hooks/use_apm_data_view';
import { fromQuery, toQuery } from '../links/url_helpers';
-import { getBoolFilter } from './get_bool_filter';
+import { getBoolFilter } from '../get_bool_filter';
import { Typeahead } from './typeahead';
-import { useProcessorEvent } from './use_processor_event';
+import { useProcessorEvent } from '../../../hooks/use_processor_event';
interface State {
suggestions: QuerySuggestion[];
diff --git a/x-pack/plugins/apm/public/components/shared/search_bar/search_bar.test.tsx b/x-pack/plugins/apm/public/components/shared/search_bar/search_bar.test.tsx
index 27eecaf573bf6..d9efbe0403b41 100644
--- a/x-pack/plugins/apm/public/components/shared/search_bar/search_bar.test.tsx
+++ b/x-pack/plugins/apm/public/components/shared/search_bar/search_bar.test.tsx
@@ -39,6 +39,20 @@ function setup({
const KibanaReactContext = createKibanaReactContext({
usageCollection: { reportUiCounter: () => {} },
dataViews: { get: async () => {} },
+ data: {
+ query: {
+ queryString: {
+ setQuery: () => {},
+ getQuery: () => {},
+ clearQuery: () => {},
+ },
+ timefilter: {
+ timefilter: {
+ setTime: () => {},
+ },
+ },
+ },
+ },
} as Partial);
// mock transaction types
diff --git a/x-pack/plugins/apm/public/components/shared/search_bar/search_bar.tsx b/x-pack/plugins/apm/public/components/shared/search_bar/search_bar.tsx
index edff90281b9ec..d2f25880ade33 100644
--- a/x-pack/plugins/apm/public/components/shared/search_bar/search_bar.tsx
+++ b/x-pack/plugins/apm/public/components/shared/search_bar/search_bar.tsx
@@ -4,8 +4,6 @@
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
-
-import { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
import {
EuiFlexGroup,
EuiFlexGroupProps,
@@ -13,30 +11,30 @@ import {
EuiSpacer,
} from '@elastic/eui';
import React from 'react';
+import { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
import { isMobileAgentName } from '../../../../common/agent_name';
import { useApmServiceContext } from '../../../context/apm_service/use_apm_service_context';
import { useBreakpoints } from '../../../hooks/use_breakpoints';
-import { ApmDatePicker } from '../date_picker/apm_date_picker';
-import { KueryBar } from '../kuery_bar';
import { TimeComparison } from '../time_comparison';
import { TransactionTypeSelect } from '../transaction_type_select';
+import { UnifiedSearchBar } from '../unified_search_bar';
interface Props {
hidden?: boolean;
- showKueryBar?: boolean;
+ showUnifiedSearchBar?: boolean;
showTimeComparison?: boolean;
showTransactionTypeSelector?: boolean;
- kueryBarPlaceholder?: string;
- kueryBarBoolFilter?: QueryDslQueryContainer[];
+ searchBarPlaceholder?: string;
+ searchBarBoolFilter?: QueryDslQueryContainer[];
}
export function SearchBar({
hidden = false,
- showKueryBar = true,
+ showUnifiedSearchBar = true,
showTimeComparison = false,
showTransactionTypeSelector = false,
- kueryBarBoolFilter,
- kueryBarPlaceholder,
+ searchBarPlaceholder,
+ searchBarBoolFilter,
}: Props) {
const { agentName } = useApmServiceContext();
const isMobileAgent = isMobileAgentName(agentName);
@@ -69,11 +67,11 @@ export function SearchBar({
)}
- {showKueryBar && (
+ {showUnifiedSearchBar && (
-
)}
@@ -91,9 +89,6 @@ export function SearchBar({
)}
-
-
-
diff --git a/x-pack/plugins/apm/public/components/shared/unified_search_bar/index.tsx b/x-pack/plugins/apm/public/components/shared/unified_search_bar/index.tsx
new file mode 100644
index 0000000000000..83e080c8c0df3
--- /dev/null
+++ b/x-pack/plugins/apm/public/components/shared/unified_search_bar/index.tsx
@@ -0,0 +1,248 @@
+/*
+ * 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, { useCallback, useEffect, useState } from 'react';
+import { i18n } from '@kbn/i18n';
+import {
+ Filter,
+ fromKueryExpression,
+ Query,
+ TimeRange,
+ toElasticsearchQuery,
+} from '@kbn/es-query';
+import { useHistory, useLocation } from 'react-router-dom';
+import deepEqual from 'fast-deep-equal';
+import { useKibana } from '@kbn/kibana-react-plugin/public';
+import { EuiSkeletonRectangle } from '@elastic/eui';
+import qs from 'query-string';
+import { DataView, UI_SETTINGS } from '@kbn/data-plugin/common';
+import { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
+import { UIProcessorEvent } from '../../../../common/processor_event';
+import { TimePickerTimeDefaults } from '../date_picker/typings';
+import { ApmPluginStartDeps } from '../../../plugin';
+import { useApmPluginContext } from '../../../context/apm_plugin/use_apm_plugin_context';
+import { useApmDataView } from '../../../hooks/use_apm_data_view';
+import { useProcessorEvent } from '../../../hooks/use_processor_event';
+import { fromQuery, toQuery } from '../links/url_helpers';
+import { useApmParams } from '../../../hooks/use_apm_params';
+import { getBoolFilter } from '../get_bool_filter';
+import { useLegacyUrlParams } from '../../../context/url_params_context/use_url_params';
+
+function useSearchBarParams(defaultKuery?: string) {
+ const { path, query } = useApmParams('/*');
+ const urlKuery = 'kuery' in query ? query.kuery : undefined;
+ const serviceName = 'serviceName' in path ? path.serviceName : undefined;
+ const groupId = 'groupId' in path ? path.groupId : undefined;
+ const environment = 'environment' in query ? query.environment : undefined;
+
+ return {
+ kuery: urlKuery
+ ? {
+ query: defaultKuery || urlKuery,
+ language: 'kuery',
+ }
+ : undefined,
+ serviceName,
+ groupId,
+ environment,
+ };
+}
+
+function useUrlTimeRange(defaultTimeRange: TimeRange) {
+ const location = useLocation();
+ const query = qs.parse(location.search);
+
+ const isDateRangeSet = 'rangeFrom' in query && 'rangeTo' in query;
+
+ if (isDateRangeSet) {
+ return {
+ from: query.rangeFrom,
+ to: query.rangeTo,
+ };
+ }
+ return defaultTimeRange;
+}
+
+function getSearchBarPlaceholder(
+ searchbarPlaceholder?: string,
+ processorEvent?: UIProcessorEvent
+) {
+ const examples = {
+ transaction: 'transaction.duration.us > 300000',
+ error: 'http.response.status_code >= 400',
+ metric: 'process.pid = "1234"',
+ defaults:
+ 'transaction.duration.us > 300000 AND http.response.status_code >= 400',
+ };
+ const example = examples[processorEvent || 'defaults'];
+
+ return (
+ searchbarPlaceholder ??
+ i18n.translate('xpack.apm.unifiedSearchBar.placeholder', {
+ defaultMessage: `Search {event, select,
+ transaction {transactions}
+ metric {metrics}
+ error {errors}
+ other {transactions, errors and metrics}
+ } (E.g. {queryExample})`,
+ values: {
+ queryExample: example,
+ event: processorEvent,
+ },
+ })
+ );
+}
+
+function convertKueryToEsQuery(kuery: string, dataView: DataView) {
+ const ast = fromKueryExpression(kuery);
+ return toElasticsearchQuery(ast, dataView);
+}
+
+export function UnifiedSearchBar({
+ placeholder,
+ value,
+ showDatePicker = true,
+ showSubmitButton = true,
+ isClearable = true,
+ boolFilter,
+}: {
+ placeholder?: string;
+ value?: string;
+ showDatePicker?: boolean;
+ showSubmitButton?: boolean;
+ isClearable?: boolean;
+ boolFilter?: QueryDslQueryContainer[];
+}) {
+ const {
+ unifiedSearch: {
+ ui: { SearchBar },
+ },
+ core,
+ } = useApmPluginContext();
+ const { services } = useKibana();
+ const {
+ data: {
+ query: { queryString: queryStringService, timefilter: timeFilterService },
+ },
+ } = services;
+
+ const { kuery, serviceName, environment, groupId } =
+ useSearchBarParams(value);
+ const timePickerTimeDefaults = core.uiSettings.get(
+ UI_SETTINGS.TIMEPICKER_TIME_DEFAULTS
+ );
+ const urlTimeRange = useUrlTimeRange(timePickerTimeDefaults);
+ const [displaySearchBar, setDisplaySearchBar] = useState(false);
+
+ const syncSearchBarWithUrl = useCallback(() => {
+ // Sync Kuery params with Search Bar
+ if (kuery && !deepEqual(queryStringService.getQuery(), kuery)) {
+ queryStringService.setQuery(kuery);
+ }
+ // On page navigation the search bar persists the state where as the url is cleared, hence we need to clear the search bar
+ if (!kuery) {
+ queryStringService.clearQuery();
+ }
+ // Sync Time Range with Search Bar
+ timeFilterService.timefilter.setTime(urlTimeRange as TimeRange);
+ }, [kuery, queryStringService, timeFilterService, urlTimeRange]);
+
+ useEffect(() => {
+ syncSearchBarWithUrl();
+ }, [syncSearchBarWithUrl]);
+
+ const location = useLocation();
+ const history = useHistory();
+ const { dataView } = useApmDataView();
+ const { urlParams } = useLegacyUrlParams();
+ const processorEvent = useProcessorEvent();
+ const searchbarPlaceholder = getSearchBarPlaceholder(
+ placeholder,
+ processorEvent
+ );
+
+ useEffect(() => {
+ if (dataView) setDisplaySearchBar(true);
+ }, [dataView]);
+
+ const customFilters =
+ boolFilter ??
+ getBoolFilter({
+ groupId,
+ processorEvent,
+ serviceName,
+ environment,
+ urlParams,
+ });
+
+ const filtersForSearchBarSuggestions = customFilters.map((filter) => {
+ return {
+ query: filter,
+ } as Filter;
+ });
+ const handleSubmit = (payload: { dateRange: TimeRange; query?: Query }) => {
+ if (dataView == null) {
+ return;
+ }
+
+ const { dateRange, query } = payload;
+ const { from: rangeFrom, to: rangeTo } = dateRange;
+
+ try {
+ const res = convertKueryToEsQuery(
+ query?.query as string,
+ dataView as DataView
+ );
+ if (!res) {
+ return;
+ }
+
+ const existingQueryParams = toQuery(location.search);
+ const updatedQueryWithTime = {
+ ...existingQueryParams,
+ rangeFrom,
+ rangeTo,
+ };
+ history.push({
+ ...location,
+ search: fromQuery({
+ ...updatedQueryWithTime,
+ kuery: query?.query,
+ }),
+ });
+ } catch (e) {
+ console.log('Invalid kuery syntax'); // eslint-disable-line no-console
+ }
+ };
+
+ return (
+
+
+
+ );
+}
diff --git a/x-pack/plugins/apm/public/components/shared/unified_search_bar/unified_search_bar.test.tsx b/x-pack/plugins/apm/public/components/shared/unified_search_bar/unified_search_bar.test.tsx
new file mode 100644
index 0000000000000..25d3ecbc41b0f
--- /dev/null
+++ b/x-pack/plugins/apm/public/components/shared/unified_search_bar/unified_search_bar.test.tsx
@@ -0,0 +1,138 @@
+/*
+ * 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 { createMemoryHistory, MemoryHistory } from 'history';
+import React from 'react';
+import { Router, useLocation } from 'react-router-dom';
+import { createKibanaReactContext } from '@kbn/kibana-react-plugin/public';
+import { MockApmPluginContextWrapper } from '../../../context/apm_plugin/mock_apm_plugin_context';
+import * as useFetcherHook from '../../../hooks/use_fetcher';
+import * as useApmDataViewHook from '../../../hooks/use_apm_data_view';
+import * as useApmParamsHook from '../../../hooks/use_apm_params';
+import * as useProcessorEventHook from '../../../hooks/use_processor_event';
+import { fromQuery } from '../links/url_helpers';
+import { CoreStart } from '@kbn/core/public';
+import { UnifiedSearchBar } from '.';
+import { UrlParams } from '../../../context/url_params_context/types';
+import { mount } from 'enzyme';
+
+jest.mock('react-router-dom', () => ({
+ ...jest.requireActual('react-router-dom'),
+ useLocation: jest.fn(),
+}));
+
+function setup({
+ urlParams,
+ history,
+}: {
+ urlParams: UrlParams;
+ history: MemoryHistory;
+}) {
+ history.replace({
+ pathname: '/services',
+ search: fromQuery(urlParams),
+ });
+
+ const setQuerySpy = jest.fn();
+ const getQuerySpy = jest.fn();
+ const clearQuerySpy = jest.fn();
+ const setTimeSpy = jest.fn();
+
+ const KibanaReactContext = createKibanaReactContext({
+ usageCollection: {
+ reportUiCounter: () => {},
+ },
+ dataViews: {
+ get: async () => {},
+ },
+ data: {
+ query: {
+ queryString: {
+ setQuery: setQuerySpy,
+ getQuery: getQuerySpy,
+ clearQuery: clearQuerySpy,
+ },
+ timefilter: {
+ timefilter: {
+ setTime: setTimeSpy,
+ },
+ },
+ },
+ },
+ } as Partial);
+
+ // mock transaction types
+ jest
+ .spyOn(useApmDataViewHook, 'useApmDataView')
+ .mockReturnValue({ dataView: undefined });
+
+ jest.spyOn(useFetcherHook, 'useFetcher').mockReturnValue({} as any);
+
+ const wrapper = mount(
+
+
+
+
+
+
+
+ );
+
+ return { wrapper, setQuerySpy, getQuerySpy, clearQuerySpy, setTimeSpy };
+}
+
+describe('when kuery is already present in the url, the search bar must reflect the same', () => {
+ let history: MemoryHistory;
+ beforeEach(() => {
+ history = createMemoryHistory();
+ jest.spyOn(history, 'push');
+ jest.spyOn(history, 'replace');
+ });
+ jest
+ .spyOn(useProcessorEventHook, 'useProcessorEvent')
+ .mockReturnValue(undefined);
+
+ const search = '?method=json';
+ const pathname = '/services';
+ (useLocation as jest.Mock).mockImplementationOnce(() => ({
+ search,
+ pathname,
+ }));
+
+ it('sets the searchbar value based on URL', () => {
+ const expectedQuery = {
+ query: 'service.name:"opbeans-android"',
+ language: 'kuery',
+ };
+
+ const expectedTimeRange = {
+ from: 'now-15m',
+ to: 'now',
+ };
+
+ const urlParams = {
+ kuery: expectedQuery.query,
+ rangeFrom: expectedTimeRange.from,
+ rangeTo: expectedTimeRange.to,
+ environment: 'ENVIRONMENT_ALL',
+ comparisonEnabled: true,
+ serviceGroup: '',
+ offset: '1d',
+ };
+ jest
+ .spyOn(useApmParamsHook, 'useApmParams')
+ .mockReturnValue({ query: urlParams, path: {} });
+
+ const { setQuerySpy, setTimeSpy } = setup({
+ history,
+ urlParams,
+ });
+
+ expect(setQuerySpy).toBeCalledWith(expectedQuery);
+ expect(setTimeSpy).toBeCalledWith(expectedTimeRange);
+ });
+});
diff --git a/x-pack/plugins/apm/public/context/apm_plugin/mock_apm_plugin_context.tsx b/x-pack/plugins/apm/public/context/apm_plugin/mock_apm_plugin_context.tsx
index 6924277f229c0..9ba943b79b12c 100644
--- a/x-pack/plugins/apm/public/context/apm_plugin/mock_apm_plugin_context.tsx
+++ b/x-pack/plugins/apm/public/context/apm_plugin/mock_apm_plugin_context.tsx
@@ -98,6 +98,12 @@ const mockCorePlugins = {
data: {},
};
+const mockUnifiedSearch = {
+ ui: {
+ SearchBar: () => ,
+ },
+};
+
export const mockApmPluginContextValue = {
appMountParameters: coreMock.createAppMountParameters('/basepath'),
config: mockConfig,
@@ -106,6 +112,7 @@ export const mockApmPluginContextValue = {
observabilityRuleTypeRegistry: createObservabilityRuleTypeRegistryMock(),
corePlugins: mockCorePlugins,
deps: {},
+ unifiedSearch: mockUnifiedSearch,
};
export function MockApmPluginContextWrapper({
diff --git a/x-pack/plugins/apm/public/components/shared/kuery_bar/use_processor_event.ts b/x-pack/plugins/apm/public/hooks/use_processor_event.ts
similarity index 94%
rename from x-pack/plugins/apm/public/components/shared/kuery_bar/use_processor_event.ts
rename to x-pack/plugins/apm/public/hooks/use_processor_event.ts
index d8abb49e42277..deffd7eae1517 100644
--- a/x-pack/plugins/apm/public/components/shared/kuery_bar/use_processor_event.ts
+++ b/x-pack/plugins/apm/public/hooks/use_processor_event.ts
@@ -7,7 +7,7 @@
import { ProcessorEvent } from '@kbn/observability-plugin/common';
import { useLocation } from 'react-router-dom';
-import { UIProcessorEvent } from '../../../../common/processor_event';
+import { UIProcessorEvent } from '../../common/processor_event';
/**
* Infer the processor.event to used based on the route path
diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json
index 7a86a2d49d1ba..3e5b85ffcf0b4 100644
--- a/x-pack/plugins/translations/translations/fr-FR.json
+++ b/x-pack/plugins/translations/translations/fr-FR.json
@@ -7546,7 +7546,6 @@
"xpack.apm.dataView.autoCreateDisabled": "La création automatique des vues de données a été désactivée via l'option de configuration \"autoCreateApmDataView\"",
"xpack.apm.dataView.noApmData": "Aucune donnée APM",
"xpack.apm.dependecyOperationDetailView.header.backLinkLabel": "Toutes les opérations",
- "xpack.apm.dependencies.kueryBarPlaceholder": "Rechercher dans les indicateurs de dépendance (par ex., span.destination.service.resource:elasticsearch)",
"xpack.apm.dependenciesInventory.dependencyTableColumn": "Dépendance",
"xpack.apm.dependenciesTable.columnErrorRate": "Taux de transactions ayant échoué",
"xpack.apm.dependenciesTable.columnErrorRateTip": "Le pourcentage de transactions ayant échoué pour le service sélectionné. Les transactions du serveur HTTP avec un code du statut 4xx (erreur du client) ne sont pas considérées comme des échecs, car l'appelant, et non le serveur, a provoqué l'échec.",
diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json
index a8c59ef19152c..ef49ea6ad1039 100644
--- a/x-pack/plugins/translations/translations/ja-JP.json
+++ b/x-pack/plugins/translations/translations/ja-JP.json
@@ -7547,7 +7547,6 @@
"xpack.apm.dataView.autoCreateDisabled": "データビューの自動作成は、「autoCreateApmDataView」構成オプションによって無効化されています",
"xpack.apm.dataView.noApmData": "APMデータがありません",
"xpack.apm.dependecyOperationDetailView.header.backLinkLabel": "すべての演算",
- "xpack.apm.dependencies.kueryBarPlaceholder": "依存関係メトリックを検索(例:span.destination.service.resource:elasticsearch)",
"xpack.apm.dependenciesInventory.dependencyTableColumn": "依存関係",
"xpack.apm.dependenciesTable.columnErrorRate": "失敗したトランザクション率",
"xpack.apm.dependenciesTable.columnErrorRateTip": "選択したサービスの失敗したトランザクションの割合。4xxステータスコード(クライアントエラー)のHTTPサーバートランザクションは、サーバーではなく呼び出し側が失敗の原因であるため、失敗と見なされません。",
diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json
index 9ad3258100c35..589d923ae8ce1 100644
--- a/x-pack/plugins/translations/translations/zh-CN.json
+++ b/x-pack/plugins/translations/translations/zh-CN.json
@@ -7546,7 +7546,6 @@
"xpack.apm.dataView.autoCreateDisabled": "已通过“autoCreateApmDataView”配置选项禁止自动创建数据视图",
"xpack.apm.dataView.noApmData": "无 APM 数据",
"xpack.apm.dependecyOperationDetailView.header.backLinkLabel": "所有操作",
- "xpack.apm.dependencies.kueryBarPlaceholder": "搜索依赖项指标(例如,span.destination.service.resource:elasticsearch)",
"xpack.apm.dependenciesInventory.dependencyTableColumn": "依赖项",
"xpack.apm.dependenciesTable.columnErrorRate": "失败事务率",
"xpack.apm.dependenciesTable.columnErrorRateTip": "选定服务的失败事务百分比。状态代码为 4xx 的 HTTP 服务器事务(客户端错误)不会视为失败,因为是调用方而不是服务器造成了失败。",
From 656f824a614e8331fae89877caa733f9a6a2eb5d Mon Sep 17 00:00:00 2001
From: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
Date: Fri, 21 Apr 2023 08:23:48 -0400
Subject: [PATCH 06/15] skip failing test suite (#155447)
---
.../test/functional/apps/security/doc_level_security_roles.ts | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/x-pack/test/functional/apps/security/doc_level_security_roles.ts b/x-pack/test/functional/apps/security/doc_level_security_roles.ts
index 56feb76394b61..b1af9ec3350b7 100644
--- a/x-pack/test/functional/apps/security/doc_level_security_roles.ts
+++ b/x-pack/test/functional/apps/security/doc_level_security_roles.ts
@@ -19,7 +19,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
const PageObjects = getPageObjects(['security', 'common', 'header', 'discover', 'settings']);
const kibanaServer = getService('kibanaServer');
- describe('dls', function () {
+ // Failing: See https://github.com/elastic/kibana/issues/155447
+ describe.skip('dls', function () {
before('initialize tests', async () => {
await kibanaServer.savedObjects.cleanStandardList();
await esArchiver.loadIfNeeded('x-pack/test/functional/es_archives/security/dlstest');
From d401b4428851414a18af81b78869d8d94b5eb528 Mon Sep 17 00:00:00 2001
From: Katerina Patticha
Date: Fri, 21 Apr 2023 14:41:37 +0200
Subject: [PATCH 07/15] [APM] Add error grouping key filter in error count rule
type (#155410)
part of https://github.com/elastic/kibana/issues/152329
Introduces the error grouping key filter in the error count rule type
https://user-images.githubusercontent.com/3369346/233397481-899e32e0-f26d-4335-84fe-e18c5264f3d2.mov
---
x-pack/plugins/apm/common/rules/schema.ts | 1 +
.../error_count_rule_type/index.tsx | 11 +++++
.../components/alerting/utils/fields.tsx | 40 +++++++++++++++++++
.../server/routes/alerts/action_variables.ts | 9 +++++
.../plugins/apm/server/routes/alerts/route.ts | 1 +
.../get_error_count_chart_preview.ts | 9 ++++-
.../register_error_count_rule_type.ts | 12 +++++-
.../tests/alerts/chart_preview.spec.ts | 27 +++++++++++++
8 files changed, 107 insertions(+), 3 deletions(-)
diff --git a/x-pack/plugins/apm/common/rules/schema.ts b/x-pack/plugins/apm/common/rules/schema.ts
index 7e48ad989b606..698b4507c5b3f 100644
--- a/x-pack/plugins/apm/common/rules/schema.ts
+++ b/x-pack/plugins/apm/common/rules/schema.ts
@@ -15,6 +15,7 @@ export const errorCountParamsSchema = schema.object({
threshold: schema.number(),
serviceName: schema.maybe(schema.string()),
environment: schema.string(),
+ errorGroupingKey: schema.maybe(schema.string()),
});
export const transactionDurationParamsSchema = schema.object({
diff --git a/x-pack/plugins/apm/public/components/alerting/rule_types/error_count_rule_type/index.tsx b/x-pack/plugins/apm/public/components/alerting/rule_types/error_count_rule_type/index.tsx
index 09ca441cd26e9..f23dc6f4fb362 100644
--- a/x-pack/plugins/apm/public/components/alerting/rule_types/error_count_rule_type/index.tsx
+++ b/x-pack/plugins/apm/public/components/alerting/rule_types/error_count_rule_type/index.tsx
@@ -21,6 +21,7 @@ import { createCallApmApi } from '../../../../services/rest/create_call_apm_api'
import { ChartPreview } from '../../ui_components/chart_preview';
import {
EnvironmentField,
+ ErrorGroupingKeyField,
IsAboveField,
ServiceField,
} from '../../utils/fields';
@@ -33,6 +34,7 @@ export interface RuleParams {
threshold?: number;
serviceName?: string;
environment?: string;
+ errorGroupingKey?: string;
}
interface Props {
@@ -74,6 +76,7 @@ export function ErrorCountRuleType(props: Props) {
query: {
environment: params.environment,
serviceName: params.serviceName,
+ errorGroupingKey: params.errorGroupingKey,
interval,
start,
end,
@@ -88,6 +91,7 @@ export function ErrorCountRuleType(props: Props) {
params.windowUnit,
params.environment,
params.serviceName,
+ params.errorGroupingKey,
]
);
@@ -98,6 +102,7 @@ export function ErrorCountRuleType(props: Props) {
if (value !== params.serviceName) {
setRuleParams('serviceName', value);
setRuleParams('environment', ENVIRONMENT_ALL.value);
+ setRuleParams('errorGroupingKey', undefined);
}
}}
/>,
@@ -106,6 +111,12 @@ export function ErrorCountRuleType(props: Props) {
onChange={(value) => setRuleParams('environment', value)}
serviceName={params.serviceName}
/>,
+ setRuleParams('errorGroupingKey', value)}
+ serviceName={params.serviceName}
+ />,
+
void;
+ serviceName?: string;
+}) {
+ const label = i18n.translate('xpack.apm.alerting.fields.error.group.id', {
+ defaultMessage: 'Error grouping key',
+ });
+ return (
+
+
+
+ );
+}
+
export function IsAboveField({
value,
unit,
diff --git a/x-pack/plugins/apm/server/routes/alerts/action_variables.ts b/x-pack/plugins/apm/server/routes/alerts/action_variables.ts
index 471d2ff3141b0..1bb9c41546cf8 100644
--- a/x-pack/plugins/apm/server/routes/alerts/action_variables.ts
+++ b/x-pack/plugins/apm/server/routes/alerts/action_variables.ts
@@ -94,4 +94,13 @@ export const apmActionVariables = {
name: 'viewInAppUrl' as const,
usesPublicBaseUrl: true,
},
+ errorGroupingKey: {
+ description: i18n.translate(
+ 'xpack.apm.alerts.action_variables.errorGroupingKey',
+ {
+ defaultMessage: 'The error grouping key the alert is created for',
+ }
+ ),
+ name: 'errorGroupingKey' as const,
+ },
};
diff --git a/x-pack/plugins/apm/server/routes/alerts/route.ts b/x-pack/plugins/apm/server/routes/alerts/route.ts
index 314e05fed539a..13b790155520f 100644
--- a/x-pack/plugins/apm/server/routes/alerts/route.ts
+++ b/x-pack/plugins/apm/server/routes/alerts/route.ts
@@ -28,6 +28,7 @@ const alertParamsRt = t.intersection([
t.literal(AggregationType.P99),
]),
serviceName: t.string,
+ errorGroupingKey: t.string,
transactionType: t.string,
transactionName: t.string,
}),
diff --git a/x-pack/plugins/apm/server/routes/alerts/rule_types/error_count/get_error_count_chart_preview.ts b/x-pack/plugins/apm/server/routes/alerts/rule_types/error_count/get_error_count_chart_preview.ts
index 6175b2578a236..59ae52eab6a5a 100644
--- a/x-pack/plugins/apm/server/routes/alerts/rule_types/error_count/get_error_count_chart_preview.ts
+++ b/x-pack/plugins/apm/server/routes/alerts/rule_types/error_count/get_error_count_chart_preview.ts
@@ -7,7 +7,10 @@
import { rangeQuery, termQuery } from '@kbn/observability-plugin/server';
import { ProcessorEvent } from '@kbn/observability-plugin/common';
-import { SERVICE_NAME } from '../../../../../common/es_fields/apm';
+import {
+ ERROR_GROUP_ID,
+ SERVICE_NAME,
+} from '../../../../../common/es_fields/apm';
import { AlertParams } from '../../route';
import { environmentQuery } from '../../../../../common/utils/environment_query';
import { APMEventClient } from '../../../../lib/helpers/create_es_client/create_apm_event_client';
@@ -24,12 +27,14 @@ export async function getTransactionErrorCountChartPreview({
apmEventClient: APMEventClient;
alertParams: AlertParams;
}): Promise {
- const { serviceName, environment, interval, start, end } = alertParams;
+ const { serviceName, environment, errorGroupingKey, interval, start, end } =
+ alertParams;
const query = {
bool: {
filter: [
...termQuery(SERVICE_NAME, serviceName),
+ ...termQuery(ERROR_GROUP_ID, errorGroupingKey),
...rangeQuery(start, end),
...environmentQuery(environment),
],
diff --git a/x-pack/plugins/apm/server/routes/alerts/rule_types/error_count/register_error_count_rule_type.ts b/x-pack/plugins/apm/server/routes/alerts/rule_types/error_count/register_error_count_rule_type.ts
index a008dd0d23cb3..2527a11e3f001 100644
--- a/x-pack/plugins/apm/server/routes/alerts/rule_types/error_count/register_error_count_rule_type.ts
+++ b/x-pack/plugins/apm/server/routes/alerts/rule_types/error_count/register_error_count_rule_type.ts
@@ -25,6 +25,7 @@ import {
getEnvironmentLabel,
} from '../../../../../common/environment_filter_values';
import {
+ ERROR_GROUP_ID,
PROCESSOR_EVENT,
SERVICE_ENVIRONMENT,
SERVICE_NAME,
@@ -77,6 +78,7 @@ export function registerErrorCountRuleType({
apmActionVariables.interval,
apmActionVariables.reason,
apmActionVariables.serviceName,
+ apmActionVariables.errorGroupingKey,
apmActionVariables.threshold,
apmActionVariables.triggerValue,
apmActionVariables.viewInAppUrl,
@@ -112,6 +114,7 @@ export function registerErrorCountRuleType({
},
{ term: { [PROCESSOR_EVENT]: ProcessorEvent.error } },
...termQuery(SERVICE_NAME, ruleParams.serviceName),
+ ...termQuery(ERROR_GROUP_ID, ruleParams.errorGroupingKey),
...environmentQuery(ruleParams.environment),
],
},
@@ -164,7 +167,12 @@ export function registerErrorCountRuleType({
windowUnit: ruleParams.windowUnit,
});
- const id = [ApmRuleType.ErrorCount, serviceName, environment]
+ const id = [
+ ApmRuleType.ErrorCount,
+ serviceName,
+ environment,
+ ruleParams.errorGroupingKey,
+ ]
.filter((name) => name)
.join('_');
@@ -188,6 +196,7 @@ export function registerErrorCountRuleType({
[PROCESSOR_EVENT]: ProcessorEvent.error,
[ALERT_EVALUATION_VALUE]: errorCount,
[ALERT_EVALUATION_THRESHOLD]: ruleParams.threshold,
+ [ERROR_GROUP_ID]: ruleParams.errorGroupingKey,
[ALERT_REASON]: alertReason,
...sourceFields,
},
@@ -201,6 +210,7 @@ export function registerErrorCountRuleType({
reason: alertReason,
serviceName,
threshold: ruleParams.threshold,
+ errorGroupingKey: ruleParams.errorGroupingKey,
triggerValue: errorCount,
viewInAppUrl,
});
diff --git a/x-pack/test/apm_api_integration/tests/alerts/chart_preview.spec.ts b/x-pack/test/apm_api_integration/tests/alerts/chart_preview.spec.ts
index f070098a66661..7ec09849b7ff2 100644
--- a/x-pack/test/apm_api_integration/tests/alerts/chart_preview.spec.ts
+++ b/x-pack/test/apm_api_integration/tests/alerts/chart_preview.spec.ts
@@ -100,6 +100,33 @@ export default function ApiTest({ getService }: FtrProviderContext) {
).to.equal(true);
});
+ it('error_count with error grouping key', async () => {
+ const options = {
+ params: {
+ query: {
+ start,
+ end,
+ serviceName: 'opbeans-java',
+ errorGroupingKey: 'd16d39e7fa133b8943cea035430a7b4e',
+ environment: 'ENVIRONMENT_ALL',
+ interval: '5m',
+ },
+ },
+ };
+
+ const response = await apmApiClient.readUser({
+ endpoint: 'GET /internal/apm/rule_types/error_count/chart_preview',
+ ...options,
+ });
+
+ expect(response.status).to.be(200);
+ expect(response.body.errorCountChartPreview).to.eql([
+ { x: 1627974600000, y: 4 },
+ { x: 1627974900000, y: 2 },
+ { x: 1627975200000, y: 0 },
+ ]);
+ });
+
it('transaction_duration (with data)', async () => {
const options = getOptions();
const response = await apmApiClient.readUser({
From e41936662e89c3050c8988c909792333bb58fa11 Mon Sep 17 00:00:00 2001
From: Dima Arnautov
Date: Fri, 21 Apr 2023 14:46:47 +0200
Subject: [PATCH 08/15] [ML] Fix indentation in the API content page template
(#155498)
## Summary
Fixes indentation in the API content page template
---
.../content_page/content_page.ts | 29 +++++++++----------
1 file changed, 14 insertions(+), 15 deletions(-)
diff --git a/x-pack/plugins/ml/scripts/apidoc_scripts/content_page/content_page.ts b/x-pack/plugins/ml/scripts/apidoc_scripts/content_page/content_page.ts
index 402a68c07e2d5..8b992817c843c 100644
--- a/x-pack/plugins/ml/scripts/apidoc_scripts/content_page/content_page.ts
+++ b/x-pack/plugins/ml/scripts/apidoc_scripts/content_page/content_page.ts
@@ -24,27 +24,26 @@ const getContent = (groups: Group[]) => {
.join('\n');
return `---
- id: uiMlKibanaRestApi
- slug: /ml-team/docs/ui/rest-api/ml-kibana-rest-api
- title: Machine Learning Kibana REST API
- image: https://source.unsplash.com/400x175/?Nature
- description: This page contains documentation for the ML Kibana REST API.
- date: ${moment().format('YYYY-MM-DD')}
- tags: ['machine learning','internal docs', 'UI']
- ---
+id: uiMlKibanaRestApi
+slug: /ml-team/docs/ui/rest-api/ml-kibana-rest-api
+title: Machine Learning Kibana REST API
+image: https://source.unsplash.com/400x175/?Nature
+description: This page contains documentation for the ML Kibana REST API.
+date: ${moment().format('YYYY-MM-DD')}
+tags: ['machine learning','internal docs', 'UI']
+---
- _Updated for ${kibanaPackageJson.version}_
+_Updated for ${kibanaPackageJson.version}_
- Some of the features of the Machine Learning (ML) Kibana plugin are provided via a REST API, which is ideal for creating an integration with the ML plugin.
+Some of the features of the Machine Learning (ML) Kibana plugin are provided via a REST API, which is ideal for creating an integration with the ML plugin.
- Each API is experimental and can include breaking changes in any version of the ML plugin, or might have been entirely removed from the plugin.
+Each API is experimental and can include breaking changes in any version of the ML plugin, or might have been entirely removed from the plugin.
- -
+-
- The following APIs are available:
+The following APIs are available:
-${groupsStr}
- `;
+${groupsStr}`;
};
export const generateContentPage = () => {
From cd90b11e97a2b12c8a4e81ecfadee83933cde1d4 Mon Sep 17 00:00:00 2001
From: Coen Warmer
Date: Fri, 21 Apr 2023 15:08:42 +0200
Subject: [PATCH 09/15] [Observability:RulesPage] [TriggerActions:RulesList]
Allow filtering of Rules via Rule Param (#154258)
Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
---
.../public/pages/rules/rules.tsx | 21 ++-
.../hooks/use_load_rule_aggregations.test.tsx | 3 +
.../application/hooks/use_load_rules.test.tsx | 7 +
.../application/hooks/use_load_rules_query.ts | 2 +
.../lib/rule_api/map_filters_to_kuery_node.ts | 15 +++
.../application/lib/rule_api/rules_helpers.ts | 1 +
.../lib/rule_api/rules_kuery_filter.ts | 2 +
.../rules_list/components/rules_list.tsx | 121 +++++++++++-------
.../rules_list_clear_rule_filter_banner.tsx | 39 ++++++
.../components/rules_list_filters_bar.tsx | 73 ++++++-----
.../triggers_actions_ui/public/types.ts | 14 +-
.../plugins/triggers_actions_ui/tsconfig.json | 3 +-
12 files changed, 210 insertions(+), 91 deletions(-)
create mode 100644 x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list_clear_rule_filter_banner.tsx
diff --git a/x-pack/plugins/observability/public/pages/rules/rules.tsx b/x-pack/plugins/observability/public/pages/rules/rules.tsx
index e7a63cfbdb81d..40e4c2192a0c0 100644
--- a/x-pack/plugins/observability/public/pages/rules/rules.tsx
+++ b/x-pack/plugins/observability/public/pages/rules/rules.tsx
@@ -62,14 +62,16 @@ export function RulesPage() {
useHashQuery: false,
});
- const { lastResponse, search, status, type } = urlStateStorage.get<{
+ const { lastResponse, params, search, status, type } = urlStateStorage.get<{
lastResponse: string[];
+ params: Record;
search: string;
status: RuleStatus[];
type: string[];
- }>('_a') || { lastResponse: [], search: '', status: [], type: [] };
+ }>('_a') || { lastResponse: [], params: {}, search: '', status: [], type: [] };
const [stateLastResponse, setLastResponse] = useState(lastResponse);
+ const [stateParams, setParams] = useState>(params);
const [stateSearch, setSearch] = useState(search);
const [stateStatus, setStatus] = useState(status);
const [stateType, setType] = useState(type);
@@ -80,23 +82,28 @@ export function RulesPage() {
const handleStatusFilterChange = (newStatus: RuleStatus[]) => {
setStatus(newStatus);
- urlStateStorage.set('_a', { lastResponse, search, status: newStatus, type });
+ urlStateStorage.set('_a', { lastResponse, params, search, status: newStatus, type });
};
const handleLastRunOutcomeFilterChange = (newLastResponse: string[]) => {
setRefresh(new Date());
setLastResponse(newLastResponse);
- urlStateStorage.set('_a', { lastResponse: newLastResponse, search, status, type });
+ urlStateStorage.set('_a', { lastResponse: newLastResponse, params, search, status, type });
};
const handleTypeFilterChange = (newType: string[]) => {
setType(newType);
- urlStateStorage.set('_a', { lastResponse, search, status, type: newType });
+ urlStateStorage.set('_a', { lastResponse, params, search, status, type: newType });
};
const handleSearchFilterChange = (newSearch: string) => {
setSearch(newSearch);
- urlStateStorage.set('_a', { lastResponse, search: newSearch, status, type });
+ urlStateStorage.set('_a', { lastResponse, params, search: newSearch, status, type });
+ };
+
+ const handleRuleParamFilterChange = (newParams: Record) => {
+ setParams(newParams);
+ urlStateStorage.set('_a', { lastResponse, params: newParams, search, status, type });
};
return (
@@ -143,6 +150,7 @@ export function RulesPage() {
refresh={stateRefresh}
ruleDetailsRoute="alerts/rules/:ruleId"
rulesListKey="observability_rulesListColumns"
+ ruleParamFilter={stateParams}
showActionFilter={false}
statusFilter={stateStatus}
searchFilter={stateSearch}
@@ -155,6 +163,7 @@ export function RulesPage() {
'ruleExecutionState',
]}
onLastRunOutcomeFilterChange={handleLastRunOutcomeFilterChange}
+ onRuleParamFilterChange={handleRuleParamFilterChange}
onSearchFilterChange={handleSearchFilterChange}
onStatusFilterChange={handleStatusFilterChange}
onTypeFilterChange={handleTypeFilterChange}
diff --git a/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_load_rule_aggregations.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_load_rule_aggregations.test.tsx
index df067b43c32c7..b9f33ab40c6fc 100644
--- a/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_load_rule_aggregations.test.tsx
+++ b/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_load_rule_aggregations.test.tsx
@@ -68,6 +68,7 @@ describe('useLoadRuleAggregations', () => {
ruleExecutionStatuses: [],
ruleLastRunOutcomes: [],
ruleStatuses: [],
+ ruleParams: {},
tags: [],
},
enabled: true,
@@ -105,6 +106,7 @@ describe('useLoadRuleAggregations', () => {
types: ['type1', 'type2'],
actionTypes: ['action1', 'action2'],
ruleExecutionStatuses: ['status1', 'status2'],
+ ruleParams: {},
ruleStatuses: ['enabled', 'snoozed'] as RuleStatus[],
tags: ['tag1', 'tag2'],
ruleLastRunOutcomes: ['outcome1', 'outcome2'],
@@ -145,6 +147,7 @@ describe('useLoadRuleAggregations', () => {
types: [],
actionTypes: [],
ruleExecutionStatuses: [],
+ ruleParams: {},
ruleLastRunOutcomes: [],
ruleStatuses: [],
tags: [],
diff --git a/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_load_rules.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_load_rules.test.tsx
index ff674f677783e..1342746223889 100644
--- a/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_load_rules.test.tsx
+++ b/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_load_rules.test.tsx
@@ -272,6 +272,7 @@ describe('useLoadRules', () => {
types: [],
actionTypes: [],
ruleExecutionStatuses: [],
+ ruleParams: {},
ruleLastRunOutcomes: [],
ruleStatuses: [],
tags: [],
@@ -328,6 +329,7 @@ describe('useLoadRules', () => {
types: ['type1', 'type2'],
actionTypes: ['action1', 'action2'],
ruleExecutionStatuses: ['status1', 'status2'],
+ ruleParams: {},
ruleLastRunOutcomes: ['outcome1', 'outcome2'],
ruleStatuses: ['enabled', 'snoozed'] as RuleStatus[],
tags: ['tag1', 'tag2'],
@@ -355,6 +357,7 @@ describe('useLoadRules', () => {
actionTypesFilter: ['action1', 'action2'],
ruleExecutionStatusesFilter: ['status1', 'status2'],
ruleLastRunOutcomesFilter: ['outcome1', 'outcome2'],
+ ruleParamsFilter: {},
ruleStatusesFilter: ['enabled', 'snoozed'],
tagsFilter: ['tag1', 'tag2'],
sort: { field: 'name', direction: 'asc' },
@@ -378,6 +381,7 @@ describe('useLoadRules', () => {
types: [],
actionTypes: [],
ruleExecutionStatuses: [],
+ ruleParams: {},
ruleLastRunOutcomes: [],
ruleStatuses: [],
tags: [],
@@ -415,6 +419,7 @@ describe('useLoadRules', () => {
types: [],
actionTypes: [],
ruleExecutionStatuses: [],
+ ruleParams: {},
ruleLastRunOutcomes: [],
ruleStatuses: [],
tags: [],
@@ -444,6 +449,7 @@ describe('useLoadRules', () => {
types: [],
actionTypes: [],
ruleExecutionStatuses: [],
+ ruleParams: {},
ruleLastRunOutcomes: [],
ruleStatuses: [],
tags: [],
@@ -477,6 +483,7 @@ describe('useLoadRules', () => {
types: ['some-kind-of-filter'],
actionTypes: [],
ruleExecutionStatuses: [],
+ ruleParams: {},
ruleLastRunOutcomes: [],
ruleStatuses: [],
tags: [],
diff --git a/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_load_rules_query.ts b/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_load_rules_query.ts
index 8ffb59a7806ad..7a30b00b94c94 100644
--- a/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_load_rules_query.ts
+++ b/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_load_rules_query.ts
@@ -44,6 +44,7 @@ export const useLoadRulesQuery = (props: UseLoadRulesQueryProps) => {
filters.actionTypes,
filters.ruleStatuses,
filters.ruleLastRunOutcomes,
+ filters.ruleParams,
page,
sort,
{
@@ -59,6 +60,7 @@ export const useLoadRulesQuery = (props: UseLoadRulesQueryProps) => {
actionTypesFilter: filters.actionTypes,
ruleExecutionStatusesFilter: filters.ruleExecutionStatuses,
ruleLastRunOutcomesFilter: filters.ruleLastRunOutcomes,
+ ruleParamsFilter: filters.ruleParams,
ruleStatusesFilter: filters.ruleStatuses,
tagsFilter: filters.tags,
sort,
diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/map_filters_to_kuery_node.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/map_filters_to_kuery_node.ts
index 8159501b3d4b4..ea704891523b8 100644
--- a/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/map_filters_to_kuery_node.ts
+++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/map_filters_to_kuery_node.ts
@@ -13,6 +13,7 @@ export const mapFiltersToKueryNode = ({
actionTypesFilter,
ruleExecutionStatusesFilter,
ruleLastRunOutcomesFilter,
+ ruleParamsFilter,
ruleStatusesFilter,
tagsFilter,
searchText,
@@ -22,6 +23,7 @@ export const mapFiltersToKueryNode = ({
tagsFilter?: string[];
ruleExecutionStatusesFilter?: string[];
ruleLastRunOutcomesFilter?: string[];
+ ruleParamsFilter?: Record;
ruleStatusesFilter?: RuleStatus[];
searchText?: string;
}): KueryNode | null => {
@@ -63,6 +65,19 @@ export const mapFiltersToKueryNode = ({
);
}
+ if (ruleParamsFilter && Object.keys(ruleParamsFilter).length) {
+ filterKueryNode.push(
+ nodeBuilder.and(
+ Object.keys(ruleParamsFilter).map((ruleParam) =>
+ nodeBuilder.is(
+ `alert.attributes.params.${ruleParam}`,
+ String(ruleParamsFilter[ruleParam])
+ )
+ )
+ )
+ );
+ }
+
if (ruleStatusesFilter && ruleStatusesFilter.length) {
const snoozedFilter = nodeBuilder.or([
fromKueryExpression('alert.attributes.muteAll: true'),
diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/rules_helpers.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/rules_helpers.ts
index cce9fd3e5ac97..989bd49289dde 100644
--- a/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/rules_helpers.ts
+++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/rules_helpers.ts
@@ -19,6 +19,7 @@ export interface LoadRulesProps {
tagsFilter?: string[];
ruleExecutionStatusesFilter?: string[];
ruleLastRunOutcomesFilter?: string[];
+ ruleParamsFilter?: Record;
ruleStatusesFilter?: RuleStatus[];
sort?: Sorting;
}
diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/rules_kuery_filter.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/rules_kuery_filter.ts
index d7c56388b4e9e..f7e2bd6a8177e 100644
--- a/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/rules_kuery_filter.ts
+++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/rules_kuery_filter.ts
@@ -19,6 +19,7 @@ export async function loadRulesWithKueryFilter({
actionTypesFilter,
ruleExecutionStatusesFilter,
ruleLastRunOutcomesFilter,
+ ruleParamsFilter,
ruleStatusesFilter,
tagsFilter,
sort = { field: 'name', direction: 'asc' },
@@ -34,6 +35,7 @@ export async function loadRulesWithKueryFilter({
tagsFilter,
ruleExecutionStatusesFilter,
ruleLastRunOutcomesFilter,
+ ruleParamsFilter,
ruleStatusesFilter,
searchText,
});
diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list.tsx
index 0bf80a0eb83cd..0fbb367e1b9ed 100644
--- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list.tsx
+++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list.tsx
@@ -8,7 +8,7 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { i18n } from '@kbn/i18n';
-import { capitalize, isEmpty, sortBy } from 'lodash';
+import { capitalize, isEmpty, isEqual, sortBy } from 'lodash';
import { KueryNode } from '@kbn/es-query';
import { FormattedMessage } from '@kbn/i18n-react';
import React, {
@@ -75,6 +75,7 @@ import './rules_list.scss';
import { CreateRuleButton } from './create_rule_button';
import { ManageLicenseModal } from './manage_license_modal';
import { getIsExperimentalFeatureEnabled } from '../../../../common/get_experimental_features';
+import { RulesListClearRuleFilterBanner } from './rules_list_clear_rule_filter_banner';
import { RulesListTable, convertRulesToTableItems } from './rules_list_table';
import { RulesListDocLink } from './rules_list_doc_link';
import { UpdateApiKeyModalConfirmation } from '../../../components/update_api_key_modal_confirmation';
@@ -107,23 +108,26 @@ const RuleEdit = lazy(() => import('../../rule_form/rule_edit'));
export interface RulesListProps {
filteredRuleTypes?: string[];
- showActionFilter?: boolean;
+ lastResponseFilter?: string[];
+ lastRunOutcomeFilter?: string[];
+ refresh?: Date;
ruleDetailsRoute?: string;
+ ruleParamFilter?: Record;
+ rulesListKey?: string;
+ searchFilter?: string;
+ showActionFilter?: boolean;
showCreateRuleButtonInPrompt?: boolean;
- setHeaderActions?: (components?: React.ReactNode[]) => void;
+ showSearchBar?: boolean;
statusFilter?: RuleStatus[];
- onStatusFilterChange?: (status: RuleStatus[]) => void;
- lastResponseFilter?: string[];
+ typeFilter?: string[];
+ visibleColumns?: string[];
onLastResponseFilterChange?: (lastResponse: string[]) => void;
- lastRunOutcomeFilter?: string[];
onLastRunOutcomeFilterChange?: (lastRunOutcome: string[]) => void;
- typeFilter?: string[];
- onTypeFilterChange?: (type: string[]) => void;
- searchFilter?: string;
+ onRuleParamFilterChange?: (ruleParams: Record) => void;
onSearchFilterChange?: (search: string) => void;
- refresh?: Date;
- rulesListKey?: string;
- visibleColumns?: string[];
+ onStatusFilterChange?: (status: RuleStatus[]) => void;
+ onTypeFilterChange?: (type: string[]) => void;
+ setHeaderActions?: (components?: React.ReactNode[]) => void;
}
export const percentileFields = {
@@ -142,47 +146,51 @@ const EMPTY_ARRAY: string[] = [];
export const RulesList = ({
filteredRuleTypes = EMPTY_ARRAY,
- showActionFilter = true,
+ lastResponseFilter,
+ lastRunOutcomeFilter,
+ refresh,
ruleDetailsRoute,
+ ruleParamFilter,
+ rulesListKey,
+ searchFilter = '',
+ showActionFilter = true,
showCreateRuleButtonInPrompt = false,
+ showSearchBar = true,
statusFilter,
- onStatusFilterChange,
- lastResponseFilter,
+ typeFilter,
+ visibleColumns,
onLastResponseFilterChange,
- lastRunOutcomeFilter,
onLastRunOutcomeFilterChange,
- searchFilter = '',
+ onRuleParamFilterChange,
onSearchFilterChange,
- typeFilter,
+ onStatusFilterChange,
onTypeFilterChange,
setHeaderActions,
- refresh,
- rulesListKey,
- visibleColumns,
}: RulesListProps) => {
const history = useHistory();
const {
+ actionTypeRegistry,
+ application: { capabilities },
http,
+ kibanaFeatures,
notifications: { toasts },
- application: { capabilities },
ruleTypeRegistry,
- actionTypeRegistry,
- kibanaFeatures,
} = useKibana().services;
const canExecuteActions = hasExecuteActionsCapability(capabilities);
const [isPerformingAction, setIsPerformingAction] = useState(false);
const [page, setPage] = useState({ index: 0, size: DEFAULT_SEARCH_PAGE_SIZE });
const [inputText, setInputText] = useState(searchFilter);
- const [filters, setFilters] = useState(() => ({
- searchText: searchFilter || '',
- types: typeFilter || [],
+ const [filters, setFilters] = useState({
actionTypes: [],
ruleExecutionStatuses: lastResponseFilter || [],
ruleLastRunOutcomes: lastRunOutcomeFilter || [],
+ ruleParams: ruleParamFilter || {},
ruleStatuses: statusFilter || [],
+ searchText: searchFilter || '',
tags: [],
- }));
+ types: typeFilter || [],
+ });
const [ruleFlyoutVisible, setRuleFlyoutVisibility] = useState(false);
const [editFlyoutVisible, setEditFlyoutVisibility] = useState(false);
@@ -357,6 +365,9 @@ export const RulesList = ({
case 'ruleLastRunOutcomes':
onLastRunOutcomeFilterChange?.(value as string[]);
break;
+ case 'ruleParams':
+ onRuleParamFilterChange?.(value as Record);
+ break;
case 'searchText':
onSearchFilterChange?.(value as string);
break;
@@ -389,6 +400,8 @@ export const RulesList = ({
[setFilters, handleUpdateFiltersEffect]
);
+ const handleClearRuleParamFilter = () => updateFilters({ filter: 'ruleParams', value: {} });
+
useEffect(() => {
if (statusFilter) {
updateFilters({ filter: 'ruleStatuses', value: statusFilter });
@@ -407,6 +420,12 @@ export const RulesList = ({
}
}, [lastRunOutcomeFilter]);
+ useEffect(() => {
+ if (ruleParamFilter && !isEqual(ruleParamFilter, filters.ruleParams)) {
+ updateFilters({ filter: 'ruleParams', value: ruleParamFilter });
+ }
+ }, [ruleParamFilter]);
+
useEffect(() => {
if (typeof searchFilter === 'string') {
updateFilters({ filter: 'searchText', value: searchFilter });
@@ -783,26 +802,36 @@ export const RulesList = ({
/>
)}
+
+ {showSearchBar && !isEmpty(filters.ruleParams) ? (
+
+ ) : null}
+
{showRulesList && (
<>
-
-
+ {showSearchBar ? (
+ <>
+
+
+ >
+ ) : null}
+
void;
+}
+
+export const RulesListClearRuleFilterBanner = ({
+ onClickClearFilter,
+}: RulesListClearRuleFilterProps) => {
+ return (
+ <>
+
+
+ {' '}
+ {' '}
+
+
+
+
+
+
+ >
+ );
+};
diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list_filters_bar.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list_filters_bar.tsx
index 7945f8b328623..a429f3abc06c6 100644
--- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list_filters_bar.tsx
+++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list_filters_bar.tsx
@@ -6,16 +6,16 @@
*/
import React from 'react';
-import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n-react';
+import { i18n } from '@kbn/i18n';
import {
EuiFlexGroup,
EuiFlexItem,
EuiButton,
EuiFilterGroup,
- EuiFieldSearch,
EuiSpacer,
EuiLink,
+ EuiFieldSearch,
} from '@elastic/eui';
import { ActionType, RulesListFilters, UpdateFiltersProps } from '../../../../types';
import { getIsExperimentalFeatureEnabled } from '../../../../common/get_experimental_features';
@@ -29,43 +29,42 @@ import { ActionTypeFilter } from './action_type_filter';
import { RuleTagFilter } from './rule_tag_filter';
import { RuleStatusFilter } from './rule_status_filter';
-const ENTER_KEY = 13;
-
interface RulesListFiltersBarProps {
- inputText: string;
- filters: RulesListFilters;
- showActionFilter: boolean;
- rulesStatusesTotal: Record;
- rulesLastRunOutcomesTotal: Record;
- tags: string[];
- filterOptions: TypeFilterProps['options'];
actionTypes: ActionType[];
+ filterOptions: TypeFilterProps['options'];
+ filters: RulesListFilters;
+ inputText: string;
lastUpdate: string;
+ rulesLastRunOutcomesTotal: Record;
+ rulesStatusesTotal: Record;
+ showActionFilter: boolean;
showErrors: boolean;
- updateFilters: (updateFiltersProps: UpdateFiltersProps) => void;
- setInputText: (text: string) => void;
+ tags: string[];
onClearSelection: () => void;
onRefreshRules: () => void;
onToggleRuleErrors: () => void;
+ setInputText: (text: string) => void;
+ updateFilters: (updateFiltersProps: UpdateFiltersProps) => void;
}
+const ENTER_KEY = 13;
export const RulesListFiltersBar = React.memo((props: RulesListFiltersBarProps) => {
const {
- filters,
- inputText,
- showActionFilter = true,
- rulesStatusesTotal,
- rulesLastRunOutcomesTotal,
- tags,
actionTypes,
filterOptions,
+ filters,
+ inputText,
lastUpdate,
- showErrors,
- updateFilters,
- setInputText,
onClearSelection,
onRefreshRules,
onToggleRuleErrors,
+ rulesLastRunOutcomesTotal,
+ rulesStatusesTotal,
+ setInputText,
+ showActionFilter = true,
+ showErrors,
+ tags,
+ updateFilters,
} = props;
const isRuleTagFilterEnabled = getIsExperimentalFeatureEnabled('ruleTagFilter');
@@ -136,6 +135,19 @@ export const RulesListFiltersBar = React.memo((props: RulesListFiltersBarProps)
...getRuleTagFilter(),
];
+ const handleChange = (e: React.ChangeEvent) => {
+ setInputText(e.target.value);
+ if (e.target.value === '') {
+ updateFilters({ filter: 'searchText', value: e.target.value });
+ }
+ };
+
+ const handleKeyup = (e: React.KeyboardEvent) => {
+ if (e.keyCode === ENTER_KEY) {
+ updateFilters({ filter: 'searchText', value: inputText });
+ }
+ };
+
return (
<>
{
- setInputText(e.target.value);
- if (e.target.value === '') {
- updateFilters({ filter: 'searchText', value: e.target.value });
- }
- }}
- onKeyUp={(e) => {
- if (e.keyCode === ENTER_KEY) {
- updateFilters({ filter: 'searchText', value: inputText });
- }
- }}
placeholder={i18n.translate(
'xpack.triggersActionsUI.sections.rulesList.searchPlaceholderTitle',
{ defaultMessage: 'Search' }
)}
+ value={inputText}
+ onChange={handleChange}
+ onKeyUp={handleKeyup}
/>
{renderRuleStatusFilter()}
diff --git a/x-pack/plugins/triggers_actions_ui/public/types.ts b/x-pack/plugins/triggers_actions_ui/public/types.ts
index 83f13dcbe18e5..b5278bde4e175 100644
--- a/x-pack/plugins/triggers_actions_ui/public/types.ts
+++ b/x-pack/plugins/triggers_actions_ui/public/types.ts
@@ -374,6 +374,11 @@ export interface RuleTypeParamsExpressionProps<
unifiedSearch: UnifiedSearchPublicPluginStart;
}
+export type RuleParamsForRules = Record<
+ string,
+ Array<{ label: string; value: string | number | object }>
+>;
+
export interface RuleTypeModel {
id: string;
description: string;
@@ -688,13 +693,14 @@ export interface ConnectorServices {
}
export interface RulesListFilters {
- searchText: string;
- types: string[];
actionTypes: string[];
ruleExecutionStatuses: string[];
ruleLastRunOutcomes: string[];
+ ruleParams: Record;
ruleStatuses: RuleStatus[];
+ searchText: string;
tags: string[];
+ types: string[];
}
export type UpdateFiltersProps =
@@ -709,6 +715,10 @@ export type UpdateFiltersProps =
| {
filter: 'types' | 'actionTypes' | 'ruleExecutionStatuses' | 'ruleLastRunOutcomes' | 'tags';
value: string[];
+ }
+ | {
+ filter: 'ruleParams';
+ value: Record;
};
export interface RulesPageContainerState {
diff --git a/x-pack/plugins/triggers_actions_ui/tsconfig.json b/x-pack/plugins/triggers_actions_ui/tsconfig.json
index d2ff5b039022e..3222559bf2b9f 100644
--- a/x-pack/plugins/triggers_actions_ui/tsconfig.json
+++ b/x-pack/plugins/triggers_actions_ui/tsconfig.json
@@ -44,7 +44,6 @@
"@kbn/ui-theme",
"@kbn/datemath",
"@kbn/core-capabilities-common",
- "@kbn/safer-lodash-set",
"@kbn/shared-ux-router",
"@kbn/alerts-ui-shared",
"@kbn/safer-lodash-set",
@@ -52,7 +51,7 @@
"@kbn/field-types",
"@kbn/ecs",
"@kbn/alerts-as-data-utils",
- "@kbn/core-ui-settings-common"
+ "@kbn/core-ui-settings-common",
],
"exclude": ["target/**/*"]
}
From b97b18eecd321cf8a96a505c0bc35edaa2b89692 Mon Sep 17 00:00:00 2001
From: Jill Guyonnet
Date: Fri, 21 Apr 2023 15:19:38 +0200
Subject: [PATCH 10/15] [Fleet] Add raw status to Agent details UI (#154826)
## Summary
Make raw agent status discoverable in Fleet UI, under `Agent details`
tab.
Closes https://github.com/elastic/kibana/issues/154067
### Screenshots
### UX checklist
- [ ] Action link title (`View agent JSON`)
- [ ] Flyout title (`{agentName} agent details`)
- [ ] Download button
- [ ] Download button label (`Download JSON`)
- [ ] Downloaded file name (`{agentName}-agent-details.json`)
### Testing steps
1. Run Kibana in dev on this branch.
2. In Fleet, click on an agent to get to the agent details page.
3. There should be a new `View agent JSON` item in the `Actions` menu.
Click it.
4. A new flyout should open with the agent details in JSON format.
Clicking outside of the flyout or on the `Close` button should close the
flyout.
5. The `Download JSON` button should download the JSON correctly.
### Checklist
- [x] Any text added follows [EUI's writing
guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses
sentence case text and includes [i18n
support](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md)
- [ ]
[Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html)
was added for features that require explanation or tutorials
- [x] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or added to match the most common scenarios
- [ ] Any UI touched in this PR is usable by keyboard only (learn more
about [keyboard accessibility](https://webaim.org/techniques/keyboard/))
- [ ] Any UI touched in this PR does not create any new axe failures
(run axe in browser:
[FF](https://addons.mozilla.org/en-US/firefox/addon/axe-devtools/),
[Chrome](https://chrome.google.com/webstore/detail/axe-web-accessibility-tes/lhdoppojpmngadmnindnejefpokejbdd?hl=en-US))
- [ ] This renders correctly on smaller devices using a responsive
layout. (You can test this [in your
browser](https://www.browserstack.com/guide/responsive-testing-on-local-server))
- [ ] This was checked for [cross-browser
compatibility](https://www.elastic.co/support/matrix#matrix_browsers)
---------
Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
---
.../components/actions_menu.tsx | 39 ++++++-
.../agent_details_json_flyout.test.tsx | 74 ++++++++++++
.../components/agent_details_json_flyout.tsx | 106 ++++++++++++++++++
3 files changed, 218 insertions(+), 1 deletion(-)
create mode 100644 x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_details_json_flyout.test.tsx
create mode 100644 x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_details_json_flyout.tsx
diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/actions_menu.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/actions_menu.tsx
index 2a3d29226371c..d4710e71de9c2 100644
--- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/actions_menu.tsx
+++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/actions_menu.tsx
@@ -5,7 +5,7 @@
* 2.0.
*/
-import React, { memo, useState, useMemo } from 'react';
+import React, { memo, useState, useMemo, useCallback } from 'react';
import { EuiPortal, EuiContextMenuItem } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n-react';
@@ -24,6 +24,8 @@ import { isAgentUpgradeable, policyHasFleetServer } from '../../../../services';
import { AgentRequestDiagnosticsModal } from '../../components/agent_request_diagnostics_modal';
import { ExperimentalFeaturesService } from '../../../../services';
+import { AgentDetailsJsonFlyout } from './agent_details_json_flyout';
+
export const AgentDetailsActionMenu: React.FunctionComponent<{
agent: Agent;
agentPolicy?: AgentPolicy;
@@ -37,8 +39,17 @@ export const AgentDetailsActionMenu: React.FunctionComponent<{
const [isUnenrollModalOpen, setIsUnenrollModalOpen] = useState(false);
const [isUpgradeModalOpen, setIsUpgradeModalOpen] = useState(false);
const [isRequestDiagnosticsModalOpen, setIsRequestDiagnosticsModalOpen] = useState(false);
+ const [isAgentDetailsJsonFlyoutOpen, setIsAgentDetailsJsonFlyoutOpen] = useState(false);
const isUnenrolling = agent.status === 'unenrolling';
+ const [isContextMenuOpen, setIsContextMenuOpen] = useState(false);
+ const onContextMenuChange = useCallback(
+ (open: boolean) => {
+ setIsContextMenuOpen(open);
+ },
+ [setIsContextMenuOpen]
+ );
+
const hasFleetServer = agentPolicy && policyHasFleetServer(agentPolicy);
const { diagnosticFileUploadEnabled } = ExperimentalFeaturesService.get();
@@ -70,6 +81,7 @@ export const AgentDetailsActionMenu: React.FunctionComponent<{
onClick={() => {
setIsUnenrollModalOpen(true);
}}
+ key="unenrollAgent"
>
{isUnenrolling ? (
{
setIsUpgradeModalOpen(true);
}}
+ key="upgradeAgent"
>
,
+ {
+ setIsContextMenuOpen(false);
+ setIsAgentDetailsJsonFlyoutOpen(!isAgentDetailsJsonFlyoutOpen);
+ }}
+ key="agentDetailsJson"
+ >
+
+ ,
];
if (diagnosticFileUploadEnabled) {
@@ -105,6 +131,7 @@ export const AgentDetailsActionMenu: React.FunctionComponent<{
onClick={() => {
setIsRequestDiagnosticsModalOpen(true);
}}
+ key="requestDiagnostics"
>
)}
+ {isAgentDetailsJsonFlyoutOpen && (
+
+ setIsAgentDetailsJsonFlyoutOpen(false)}
+ />
+
+ )}
{
+ const agent: Agent = {
+ id: '123',
+ packages: [],
+ type: 'PERMANENT',
+ active: true,
+ enrolled_at: `${Date.now()}`,
+ user_provided_metadata: {},
+ local_metadata: {},
+ };
+
+ beforeEach(() => {
+ mockUseStartServices.mockReturnValue({
+ docLinks: { links: { fleet: { troubleshooting: 'https://elastic.co' } } },
+ });
+ });
+
+ const renderComponent = () => {
+ return render();
+ };
+
+ it('renders a title with the agent id if host name is not defined', () => {
+ const result = renderComponent();
+ expect(result.getByText("'123' agent details")).toBeInTheDocument();
+ });
+
+ it('renders a title with the agent host name if defined', () => {
+ agent.local_metadata = {
+ host: {
+ hostname: '456',
+ },
+ };
+ const result = renderComponent();
+ expect(result.getByText("'456' agent details")).toBeInTheDocument();
+ });
+
+ it('does not add a link to the page after clicking Download', () => {
+ const result = renderComponent();
+ const downloadButton = result.getByRole('button', { name: 'Download JSON' });
+ const anchorMocked = {
+ href: '',
+ click: jest.fn(),
+ download: '',
+ setAttribute: jest.fn(),
+ } as any;
+ const createElementSpyOn = jest
+ .spyOn(document, 'createElement')
+ .mockReturnValueOnce(anchorMocked);
+
+ downloadButton.click();
+ expect(createElementSpyOn).toBeCalledWith('a');
+ expect(result.queryAllByRole('link')).toHaveLength(1); // The only link is the one from the flyout's description.
+ expect(result.getByRole('link')).toHaveAttribute('href', 'https://elastic.co');
+ });
+});
diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_details_json_flyout.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_details_json_flyout.tsx
new file mode 100644
index 0000000000000..093823f108ac6
--- /dev/null
+++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_details_json_flyout.tsx
@@ -0,0 +1,106 @@
+/*
+ * 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, { memo } from 'react';
+import { FormattedMessage } from '@kbn/i18n-react';
+import {
+ EuiButton,
+ EuiButtonEmpty,
+ EuiCodeBlock,
+ EuiFlexGroup,
+ EuiFlexItem,
+ EuiFlyout,
+ EuiFlyoutBody,
+ EuiFlyoutFooter,
+ EuiFlyoutHeader,
+ EuiLink,
+ EuiSpacer,
+ EuiText,
+ EuiTitle,
+} from '@elastic/eui';
+
+import type { Agent } from '../../../../types';
+import { useStartServices } from '../../../../hooks';
+
+export const AgentDetailsJsonFlyout = memo<{ agent: Agent; onClose: () => void }>(
+ ({ agent, onClose }) => {
+ const agentToJson = JSON.stringify(agent, null, 2);
+ const agentName =
+ typeof agent.local_metadata?.host?.hostname === 'string'
+ ? agent.local_metadata.host.hostname
+ : agent.id;
+
+ const downloadJson = () => {
+ const link = document.createElement('a');
+ link.href = `data:text/json;charset=utf-8,${encodeURIComponent(agentToJson)}`;
+ link.download = `${agentName}-agent-details.json`;
+ link.click();
+ };
+
+ const { docLinks } = useStartServices();
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ),
+ }}
+ />
+
+
+
+ {agentToJson}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+ }
+);
From d32c8674178e9d31280e03599638861407a75241 Mon Sep 17 00:00:00 2001
From: Saarika Bhasi <55930906+saarikabhasi@users.noreply.github.com>
Date: Fri, 21 Apr 2023 09:24:02 -0400
Subject: [PATCH 11/15] [Enterprise Search][Search Application] Update engines
to be search application (#155299)
## Summary
* Update 'engines' to be 'Search Application' in UI
* Navigate to Search application from indices page rather than app search engine
* Pre-select index and open Create Search Application Flyout when navigated from Index page
https://user-images.githubusercontent.com/55930906/233170965-318cdc63-2953-45f8-89d3-b3f15f11ab11.mov
### Checklist
Delete any items that are not applicable to this PR.
- [x] Any text added follows [EUI's writing
guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses
sentence case text and includes [i18n
support](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md)
- [x] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or added to match the most common scenarios
---
.../enterprise_search/common/constants.ts | 4 +-
.../components/engine/add_indices_flyout.tsx | 7 +-
.../engine/add_indices_logic.test.ts | 41 ++-------
.../components/engine/add_indices_logic.ts | 12 ++-
.../engine_connect/engine_api_integration.tsx | 2 +-
.../generate_engine_api_key_modal.tsx | 2 +-
.../engine_connect/search_application_api.tsx | 66 ++++++++------
.../components/engine/engine_error.test.tsx | 2 +-
.../components/engine/engine_error.tsx | 9 +-
.../components/engine/engine_indices.tsx | 8 +-
.../engine/engine_view_header_actions.tsx | 87 -------------------
.../components/engine/header_docs_action.tsx | 2 +-
.../components/indices_select_combobox.tsx | 7 +-
.../components/tables/engines_table.tsx | 8 +-
.../engines/create_engine_flyout.tsx | 41 +++++----
.../engines/create_engine_logic.test.ts | 17 ++--
.../components/engines/create_engine_logic.ts | 18 ++--
.../engines/delete_engine_modal.tsx | 6 +-
.../components/engines/engines_list.tsx | 73 +++++++++-------
.../engines/engines_list_logic.test.ts | 20 +----
.../components/engines/engines_list_logic.ts | 13 +--
.../components/engines/engines_router.tsx | 5 +-
.../new_index/new_index_created_toast.tsx | 29 -------
.../create_engine_menu_item.tsx | 30 ++++---
.../header_actions/search_engines_popover.tsx | 19 ++--
.../search_indices/delete_index_modal.tsx | 2 +-
.../translations/translations/fr-FR.json | 27 ------
.../translations/translations/ja-JP.json | 27 ------
.../translations/translations/zh-CN.json | 27 ------
29 files changed, 205 insertions(+), 406 deletions(-)
delete mode 100644 x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_view_header_actions.tsx
diff --git a/x-pack/plugins/enterprise_search/common/constants.ts b/x-pack/plugins/enterprise_search/common/constants.ts
index bdaf569d27143..34a48c7c76ccf 100644
--- a/x-pack/plugins/enterprise_search/common/constants.ts
+++ b/x-pack/plugins/enterprise_search/common/constants.ts
@@ -119,8 +119,8 @@ export const SEARCH_EXPERIENCES_PLUGIN = {
};
export const ENGINES_PLUGIN = {
- NAV_TITLE: i18n.translate('xpack.enterpriseSearch.engines.navTitle', {
- defaultMessage: 'Engines',
+ NAV_TITLE: i18n.translate('xpack.enterpriseSearch.applications.navTitle', {
+ defaultMessage: 'Applications',
}),
};
diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/add_indices_flyout.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/add_indices_flyout.tsx
index 22ab72ba3a405..7d504c65bb7ac 100644
--- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/add_indices_flyout.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/add_indices_flyout.tsx
@@ -45,10 +45,13 @@ export const AddIndicesFlyout: React.FC = ({ onClose }) =
const { selectedIndices, updateEngineStatus, updateEngineError } = useValues(AddIndicesLogic);
const { setSelectedIndices, submitSelectedIndices } = useActions(AddIndicesLogic);
- const selectedOptions = useMemo(() => selectedIndices.map(indexToOption), [selectedIndices]);
+ const selectedOptions = useMemo(
+ () => selectedIndices.map((index) => indexToOption(index)),
+ [selectedIndices]
+ );
const onIndicesChange = useCallback(
(options: IndicesSelectComboBoxOption[]) => {
- setSelectedIndices(options.map(({ value }) => value).filter(isNotNullish));
+ setSelectedIndices(options.map(({ label }) => label).filter(isNotNullish));
},
[setSelectedIndices]
);
diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/add_indices_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/add_indices_logic.test.ts
index a60c6e9df756a..7b3e61940f001 100644
--- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/add_indices_logic.test.ts
+++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/add_indices_logic.test.ts
@@ -8,7 +8,6 @@
import { LogicMounter } from '../../../__mocks__/kea_logic';
import { Status } from '../../../../../common/types/api';
-import { ElasticsearchIndexWithIngestion } from '../../../../../common/types/indices';
import { AddIndicesLogic, AddIndicesLogicValues } from './add_indices_logic';
@@ -18,16 +17,6 @@ const DEFAULT_VALUES: AddIndicesLogicValues = {
updateEngineStatus: Status.IDLE,
};
-const makeIndexData = (name: string): ElasticsearchIndexWithIngestion => ({
- count: 0,
- hidden: false,
- name,
- total: {
- docs: { count: 0, deleted: 0 },
- store: { size_in_bytes: 'n/a' },
- },
-});
-
describe('AddIndicesLogic', () => {
const { mount: mountAddIndicesLogic } = new LogicMounter(AddIndicesLogic);
const { mount: mountEngineIndicesLogic } = new LogicMounter(AddIndicesLogic);
@@ -47,31 +36,16 @@ describe('AddIndicesLogic', () => {
describe('actions', () => {
describe('setSelectedIndices', () => {
it('adds the indices to selectedIndices', () => {
- AddIndicesLogic.actions.setSelectedIndices([
- makeIndexData('index-001'),
- makeIndexData('index-002'),
- ]);
+ AddIndicesLogic.actions.setSelectedIndices(['index-001', 'index-002']);
- expect(AddIndicesLogic.values.selectedIndices).toEqual([
- makeIndexData('index-001'),
- makeIndexData('index-002'),
- ]);
+ expect(AddIndicesLogic.values.selectedIndices).toEqual(['index-001', 'index-002']);
});
it('replaces any existing indices', () => {
- AddIndicesLogic.actions.setSelectedIndices([
- makeIndexData('index-001'),
- makeIndexData('index-002'),
- ]);
- AddIndicesLogic.actions.setSelectedIndices([
- makeIndexData('index-003'),
- makeIndexData('index-004'),
- ]);
+ AddIndicesLogic.actions.setSelectedIndices(['index-001', 'index-002']);
+ AddIndicesLogic.actions.setSelectedIndices(['index-003', 'index-004']);
- expect(AddIndicesLogic.values.selectedIndices).toEqual([
- makeIndexData('index-003'),
- makeIndexData('index-004'),
- ]);
+ expect(AddIndicesLogic.values.selectedIndices).toEqual(['index-003', 'index-004']);
});
});
});
@@ -103,10 +77,7 @@ describe('AddIndicesLogic', () => {
it('calls addIndicesToEngine when there are selectedIndices', () => {
jest.spyOn(AddIndicesLogic.actions, 'addIndicesToEngine');
- AddIndicesLogic.actions.setSelectedIndices([
- makeIndexData('index-001'),
- makeIndexData('index-002'),
- ]);
+ AddIndicesLogic.actions.setSelectedIndices(['index-001', 'index-002']);
AddIndicesLogic.actions.submitSelectedIndices();
expect(AddIndicesLogic.actions.addIndicesToEngine).toHaveBeenCalledTimes(1);
diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/add_indices_logic.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/add_indices_logic.ts
index 37e5cf43ebd00..add950937b30a 100644
--- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/add_indices_logic.ts
+++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/add_indices_logic.ts
@@ -7,8 +7,6 @@
import { kea, MakeLogicType } from 'kea';
-import { ElasticsearchIndexWithIngestion } from '../../../../../common/types/indices';
-
import { UpdateEngineApiLogic } from '../../api/engines/update_engine_api_logic';
import { EngineIndicesLogic, EngineIndicesLogicActions } from './engine_indices_logic';
@@ -17,21 +15,21 @@ export interface AddIndicesLogicActions {
addIndicesToEngine: EngineIndicesLogicActions['addIndicesToEngine'];
closeAddIndicesFlyout: EngineIndicesLogicActions['closeAddIndicesFlyout'];
engineUpdated: EngineIndicesLogicActions['engineUpdated'];
- setSelectedIndices: (indices: ElasticsearchIndexWithIngestion[]) => {
- indices: ElasticsearchIndexWithIngestion[];
+ setSelectedIndices: (indices: string[]) => {
+ indices: string[];
};
submitSelectedIndices: () => void;
}
export interface AddIndicesLogicValues {
- selectedIndices: ElasticsearchIndexWithIngestion[];
+ selectedIndices: string[];
updateEngineError: typeof UpdateEngineApiLogic.values.error | undefined;
updateEngineStatus: typeof UpdateEngineApiLogic.values.status;
}
export const AddIndicesLogic = kea>({
actions: {
- setSelectedIndices: (indices: ElasticsearchIndexWithIngestion[]) => ({ indices }),
+ setSelectedIndices: (indices: string[]) => ({ indices }),
submitSelectedIndices: () => true,
},
connect: {
@@ -46,7 +44,7 @@ export const AddIndicesLogic = kea name));
+ actions.addIndicesToEngine(selectedIndices);
},
}),
path: ['enterprise_search', 'content', 'add_indices_logic'],
diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_connect/engine_api_integration.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_connect/engine_api_integration.tsx
index 778d69a5ed56b..b61614838d7a1 100644
--- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_connect/engine_api_integration.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_connect/engine_api_integration.tsx
@@ -73,7 +73,7 @@ export const EngineApiIntegrationStage: React.FC = () => {
{i18n.translate('xpack.enterpriseSearch.content.engine.api.step3.intro', {
defaultMessage:
- 'Learn how to integrate with your engine with the language clients maintained by Elastic to help build your search experience.',
+ 'Learn how to integrate with your search application with the language clients maintained by Elastic to help build your search experience.',
})}
diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_connect/generate_engine_api_key_modal/generate_engine_api_key_modal.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_connect/generate_engine_api_key_modal/generate_engine_api_key_modal.tsx
index c0cbcea37e93a..b4d03456ff5ec 100644
--- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_connect/generate_engine_api_key_modal/generate_engine_api_key_modal.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_connect/generate_engine_api_key_modal/generate_engine_api_key_modal.tsx
@@ -59,7 +59,7 @@ export const GenerateEngineApiKeyModal: React.FC
{i18n.translate(
'xpack.enterpriseSearch.content.engine.api.generateEngineApiKeyModal.title',
{
- defaultMessage: 'Create Engine read-only API Key',
+ defaultMessage: 'Create Search application read-only API Key',
}
)}
diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_connect/search_application_api.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_connect/search_application_api.tsx
index c51654f0c557d..9d3c27895657f 100644
--- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_connect/search_application_api.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_connect/search_application_api.tsx
@@ -43,26 +43,32 @@ export const SearchApplicationAPI = () => {
const steps = [
{
- title: i18n.translate('xpack.enterpriseSearch.content.engine.api.step1.title', {
+ title: i18n.translate('xpack.enterpriseSearch.content.searchApplication.api.step1.title', {
defaultMessage: 'Generate and save API key',
}),
children: (
<>
- {i18n.translate('xpack.enterpriseSearch.content.engine.api.step1.apiKeyWarning', {
- defaultMessage:
- "Elastic does not store API keys. Once generated, you'll only be able to view the key one time. Make sure you save it somewhere secure. If you lose access to it you'll need to generate a new API key from this screen.",
- })}{' '}
+ {i18n.translate(
+ 'xpack.enterpriseSearch.content.searchApplication.api.step1.apiKeyWarning',
+ {
+ defaultMessage:
+ "Elastic does not store API keys. Once generated, you'll only be able to view the key one time. Make sure you save it somewhere secure. If you lose access to it you'll need to generate a new API key from this screen.",
+ }
+ )}{' '}
- {i18n.translate('xpack.enterpriseSearch.content.engine.api.step1.learnMoreLink', {
- defaultMessage: 'Learn more about API keys.',
- })}
+ {i18n.translate(
+ 'xpack.enterpriseSearch.content.searchApplication.api.step1.learnMoreLink',
+ {
+ defaultMessage: 'Learn more about API keys.',
+ }
+ )}
@@ -73,10 +79,10 @@ export const SearchApplicationAPI = () => {
iconSide="left"
iconType="plusInCircleFilled"
onClick={openGenerateModal}
- data-telemetry-id="entSearchContent-engines-api-step1-createApiKeyButton"
+ data-telemetry-id="entSearchContent-searchApplications-api-step1-createApiKeyButton"
>
{i18n.translate(
- 'xpack.enterpriseSearch.content.engine.api.step1.createAPIKeyButton',
+ 'xpack.enterpriseSearch.content.searchApplication.api.step1.createAPIKeyButton',
{
defaultMessage: 'Create API Key',
}
@@ -87,16 +93,19 @@ export const SearchApplicationAPI = () => {
KibanaLogic.values.navigateToUrl('/app/management/security/api_keys', {
shouldNotCreateHref: true,
})
}
>
- {i18n.translate('xpack.enterpriseSearch.content.engine.api.step1.viewKeysButton', {
- defaultMessage: 'View Keys',
- })}
+ {i18n.translate(
+ 'xpack.enterpriseSearch.content.searchApplication.api.step1.viewKeysButton',
+ {
+ defaultMessage: 'View Keys',
+ }
+ )}
@@ -104,17 +113,17 @@ export const SearchApplicationAPI = () => {
),
},
{
- title: i18n.translate('xpack.enterpriseSearch.content.engine.api.step2.title', {
- defaultMessage: "Copy your engine's endpoint",
+ title: i18n.translate('xpack.enterpriseSearch.content.searchApplication.api.step2.title', {
+ defaultMessage: "Copy your search application's endpoint",
}),
children: (
<>
{i18n.translate(
- 'xpack.enterpriseSearch.content.engine.api.step2.copyEndpointDescription',
+ 'xpack.enterpriseSearch.content.searchApplication.api.step2.copyEndpointDescription',
{
- defaultMessage: "Use this URL to access your engine's API endpoints.",
+ defaultMessage: "Use this URL to access your search application's API endpoints.",
}
)}
@@ -131,22 +140,22 @@ export const SearchApplicationAPI = () => {
),
},
{
- title: i18n.translate('xpack.enterpriseSearch.content.engine.api.step3.title', {
+ title: i18n.translate('xpack.enterpriseSearch.content.searchApplication.api.step3.title', {
defaultMessage: 'Learn how to call your endpoints',
}),
children: ,
},
{
- title: i18n.translate('xpack.enterpriseSearch.content.engine.api.step4.title', {
+ title: i18n.translate('xpack.enterpriseSearch.content.searchApplication.api.step4.title', {
defaultMessage: '(Optional) Power up your analytics',
}),
children: (
<>
- {i18n.translate('xpack.enterpriseSearch.content.engine.api.step4.copy', {
+ {i18n.translate('xpack.enterpriseSearch.content.searchApplication.api.step4.copy', {
defaultMessage:
- 'Your engine provides basic analytics data as part of this installation. To receive more granular and custom metrics, integrate our Behavioral Analytics script on your platform.',
+ 'Your search application provides basic analytics data as part of this installation. To receive more granular and custom metrics, integrate our Behavioral Analytics script on your platform.',
})}
@@ -154,7 +163,7 @@ export const SearchApplicationAPI = () => {
navigateToUrl(
generateEncodedPath(`${ANALYTICS_PLUGIN.URL}${COLLECTION_INTEGRATE_PATH}`, {
@@ -166,9 +175,12 @@ export const SearchApplicationAPI = () => {
iconSide="left"
iconType="popout"
>
- {i18n.translate('xpack.enterpriseSearch.content.engine.api.step4.learnHowLink', {
- defaultMessage: 'Learn how',
- })}
+ {i18n.translate(
+ 'xpack.enterpriseSearch.content.searchApplication.api.step4.learnHowLink',
+ {
+ defaultMessage: 'Learn how',
+ }
+ )}
diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_error.test.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_error.test.tsx
index 9c71c401fbaf0..0f47d2437e3dc 100644
--- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_error.test.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_error.test.tsx
@@ -40,7 +40,7 @@ describe('EngineError', () => {
const notFound = wrapper.find(NotFoundPrompt);
expect(notFound.prop('backToLink')).toEqual('/engines');
- expect(notFound.prop('backToContent')).toEqual('Back to Engines');
+ expect(notFound.prop('backToContent')).toEqual('Back to Search Applications');
const telemetry = wrapper.find(SendEnterpriseSearchTelemetry);
expect(telemetry.prop('action')).toEqual('error');
diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_error.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_error.tsx
index b8be4c7652e1b..d36ff3e14e7a7 100644
--- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_error.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_error.tsx
@@ -27,9 +27,12 @@ export const EngineError: React.FC<{ error?: HttpError; notFound?: boolean }> =
<>
diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_indices.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_indices.tsx
index 116430e4d0017..4bcd3fcf4f215 100644
--- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_indices.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_indices.tsx
@@ -64,7 +64,7 @@ export const EngineIndices: React.FC = () => {
description: i18n.translate(
'xpack.enterpriseSearch.content.engine.indices.actions.removeIndex.title',
{
- defaultMessage: 'Remove this index from engine',
+ defaultMessage: 'Remove this index from search application',
}
),
icon: 'minusInCircle',
@@ -215,7 +215,7 @@ export const EngineIndices: React.FC = () => {
'xpack.enterpriseSearch.content.engine.indices.unknownIndicesCallout.description',
{
defaultMessage:
- 'Some data might be unreachable from this engine. Check for any pending operations or errors on affected indices, or remove those that should no longer be used by this engine.',
+ 'Some data might be unreachable from this search application. Check for any pending operations or errors on affected indices, or remove those that should no longer be used by this search application.',
}
)}
@@ -259,7 +259,7 @@ export const EngineIndices: React.FC = () => {
}}
title={i18n.translate(
'xpack.enterpriseSearch.content.engine.indices.removeIndexConfirm.title',
- { defaultMessage: 'Remove this index from the engine' }
+ { defaultMessage: 'Remove this index from the Search Application' }
)}
buttonColor="danger"
cancelButtonText={CANCEL_BUTTON_LABEL}
@@ -278,7 +278,7 @@ export const EngineIndices: React.FC = () => {
'xpack.enterpriseSearch.content.engine.indices.removeIndexConfirm.description',
{
defaultMessage:
- "This won't delete the index. You may add it back to this engine at a later time.",
+ "This won't delete the index. You may add it back to this search application at a later time.",
}
)}
diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_view_header_actions.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_view_header_actions.tsx
deleted file mode 100644
index bd48ee8d6d294..0000000000000
--- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_view_header_actions.tsx
+++ /dev/null
@@ -1,87 +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 React, { useState } from 'react';
-
-import { useValues, useActions } from 'kea';
-
-import { EuiPopover, EuiButtonIcon, EuiText, EuiContextMenu, EuiIcon } from '@elastic/eui';
-
-import { i18n } from '@kbn/i18n';
-
-import { TelemetryLogic } from '../../../shared/telemetry/telemetry_logic';
-
-import { EngineViewLogic } from './engine_view_logic';
-
-export const EngineViewHeaderActions: React.FC = () => {
- const { engineData } = useValues(EngineViewLogic);
-
- const { openDeleteEngineModal } = useActions(EngineViewLogic);
- const { sendEnterpriseSearchTelemetry } = useActions(TelemetryLogic);
-
- const [isActionsPopoverOpen, setIsActionsPopoverOpen] = useState(false);
- const toggleActionsPopover = () => setIsActionsPopoverOpen((isPopoverOpen) => !isPopoverOpen);
- const closePopover = () => setIsActionsPopoverOpen(false);
- return (
- <>
-
- }
- isOpen={isActionsPopoverOpen}
- panelPaddingSize="xs"
- closePopover={closePopover}
- display="block"
- >
- ,
- name: (
-
- {i18n.translate(
- 'xpack.enterpriseSearch.content.engine.headerActions.delete',
- { defaultMessage: 'Delete this engine' }
- )}
-
- ),
- onClick: () => {
- if (engineData) {
- openDeleteEngineModal();
- sendEnterpriseSearchTelemetry({
- action: 'clicked',
- metric: 'entSearchContent-engines-engineView-deleteEngine',
- });
- }
- },
- size: 's',
- },
- ],
- },
- ]}
- />
-
- >
- );
-};
diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/header_docs_action.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/header_docs_action.tsx
index e20fbc81689a4..4e925b50c63b2 100644
--- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/header_docs_action.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/header_docs_action.tsx
@@ -21,7 +21,7 @@ export const EngineHeaderDocsAction: React.FC = () => (
target="_blank"
iconType="documents"
>
- Engine Docs
+ Search Application Docs
diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engines/components/indices_select_combobox.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engines/components/indices_select_combobox.tsx
index 5e6f7cebd89fc..200192d1e20a4 100644
--- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engines/components/indices_select_combobox.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engines/components/indices_select_combobox.tsx
@@ -47,7 +47,7 @@ export const IndicesSelectComboBox = (props: IndicesSelectComboBoxProps) => {
}, [searchQuery]);
const options: Array> =
- data?.indices?.map(indexToOption) ?? [];
+ data?.indices?.map((index) => indexToOption(index.name, index)) ?? [];
const renderOption = (option: EuiComboBoxOptionOption) => (
@@ -84,8 +84,9 @@ export const IndicesSelectComboBox = (props: IndicesSelectComboBoxProps) => {
};
export const indexToOption = (
- index: ElasticsearchIndexWithIngestion
+ indexName: string,
+ index?: ElasticsearchIndexWithIngestion
): IndicesSelectComboBoxOption => ({
- label: index.name,
+ label: indexName,
value: index,
});
diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engines/components/tables/engines_table.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engines/components/tables/engines_table.tsx
index f07516313f6e8..de291752639fa 100644
--- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engines/components/tables/engines_table.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engines/components/tables/engines_table.tsx
@@ -56,7 +56,7 @@ export const EnginesListTable: React.FC = ({
{
field: 'name',
name: i18n.translate('xpack.enterpriseSearch.content.enginesList.table.column.name', {
- defaultMessage: 'Engine Name',
+ defaultMessage: 'Search Application Name',
}),
mobileOptions: {
header: true,
@@ -117,7 +117,7 @@ export const EnginesListTable: React.FC = ({
description: i18n.translate(
'xpack.enterpriseSearch.content.enginesList.table.column.actions.view.buttonDescription',
{
- defaultMessage: 'View this engine',
+ defaultMessage: 'View this search application',
}
),
type: 'icon',
@@ -134,7 +134,7 @@ export const EnginesListTable: React.FC = ({
description: i18n.translate(
'xpack.enterpriseSearch.content.enginesList.table.column.action.delete.buttonDescription',
{
- defaultMessage: 'Delete this engine',
+ defaultMessage: 'Delete this search application',
}
),
type: 'icon',
@@ -144,7 +144,7 @@ export const EnginesListTable: React.FC = ({
i18n.translate(
'xpack.enterpriseSearch.content.engineList.table.column.actions.deleteEngineLabel',
{
- defaultMessage: 'Delete this engine',
+ defaultMessage: 'Delete this search application',
}
),
onClick: (engine) => {
diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engines/create_engine_flyout.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engines/create_engine_flyout.tsx
index ae7f7423be7ce..2df12a2b9bd8f 100644
--- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engines/create_engine_flyout.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engines/create_engine_flyout.tsx
@@ -5,13 +5,13 @@
* 2.0.
*/
-import React from 'react';
+import React, { useEffect } from 'react';
+
+import { useLocation } from 'react-router-dom';
import { useActions, useValues } from 'kea';
import {
- EuiButton,
- EuiButtonEmpty,
EuiFlexGroup,
EuiFlexItem,
EuiFieldText,
@@ -26,6 +26,8 @@ import {
EuiTitle,
EuiComboBoxOptionOption,
EuiCallOut,
+ EuiButton,
+ EuiButtonEmpty,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
@@ -33,14 +35,14 @@ import { FormattedMessage } from '@kbn/i18n-react';
import { Status } from '../../../../../common/types/api';
import { ElasticsearchIndexWithIngestion } from '../../../../../common/types/indices';
-import { isNotNullish } from '../../../../../common/utils/is_not_nullish';
-import { CANCEL_BUTTON_LABEL } from '../../../shared/constants';
+import { CANCEL_BUTTON_LABEL, ESINDEX_QUERY_PARAMETER } from '../../../shared/constants';
import { docLinks } from '../../../shared/doc_links';
import { getErrorsFromHttpResponse } from '../../../shared/flash_messages/handle_api_errors';
-import { indexToOption, IndicesSelectComboBox } from './components/indices_select_combobox';
+import { parseQueryParams } from '../../../shared/query_params';
+import { indexToOption, IndicesSelectComboBox } from './components/indices_select_combobox';
import { CreateEngineLogic } from './create_engine_logic';
export interface CreateEngineFlyoutProps {
@@ -60,11 +62,18 @@ export const CreateEngineFlyout = ({ onClose }: CreateEngineFlyoutProps) => {
selectedIndices,
} = useValues(CreateEngineLogic);
+ const { search } = useLocation() as unknown as Location;
+ const { ...params } = parseQueryParams(search);
+ const indexName = params[ESINDEX_QUERY_PARAMETER];
+
const onIndicesChange = (
selectedOptions: Array>
) => {
- setSelectedIndices(selectedOptions.map((option) => option.value).filter(isNotNullish));
+ setSelectedIndices(selectedOptions.map((option) => option.label));
};
+ useEffect(() => {
+ if (indexName && typeof indexName === 'string') setSelectedIndices([indexName]);
+ }, []);
return (
@@ -72,7 +81,7 @@ export const CreateEngineFlyout = ({ onClose }: CreateEngineFlyoutProps) => {
{i18n.translate('xpack.enterpriseSearch.content.engines.createEngine.headerTitle', {
- defaultMessage: 'Create an engine',
+ defaultMessage: 'Create a Search Application',
})}
@@ -81,7 +90,7 @@ export const CreateEngineFlyout = ({ onClose }: CreateEngineFlyoutProps) => {
),
@@ -107,7 +116,7 @@ export const CreateEngineFlyout = ({ onClose }: CreateEngineFlyoutProps) => {
color="danger"
title={i18n.translate(
'xpack.enterpriseSearch.content.engines.createEngine.header.createError.title',
- { defaultMessage: 'Error creating engine' }
+ { defaultMessage: 'Error creating search application' }
)}
>
{getErrorsFromHttpResponse(createEngineError).map((errMessage, i) => (
@@ -126,7 +135,7 @@ export const CreateEngineFlyout = ({ onClose }: CreateEngineFlyoutProps) => {
fullWidth
isDisabled={formDisabled}
onChange={onIndicesChange}
- selectedOptions={selectedIndices.map(indexToOption)}
+ selectedOptions={selectedIndices.map((index: string) => indexToOption(index))}
/>
),
status: indicesStatus,
@@ -142,7 +151,7 @@ export const CreateEngineFlyout = ({ onClose }: CreateEngineFlyoutProps) => {
disabled={formDisabled}
placeholder={i18n.translate(
'xpack.enterpriseSearch.content.engines.createEngine.nameEngine.placeholder',
- { defaultMessage: 'Engine name' }
+ { defaultMessage: 'Search Application name' }
)}
value={engineName}
onChange={(e) => setEngineName(e.target.value)}
@@ -151,7 +160,7 @@ export const CreateEngineFlyout = ({ onClose }: CreateEngineFlyoutProps) => {
status: engineNameStatus,
title: i18n.translate(
'xpack.enterpriseSearch.content.engines.createEngine.nameEngine.title',
- { defaultMessage: 'Name your engine' }
+ { defaultMessage: 'Name your Search Application' }
),
},
]}
@@ -171,7 +180,7 @@ export const CreateEngineFlyout = ({ onClose }: CreateEngineFlyoutProps) => {
{
}}
>
{i18n.translate('xpack.enterpriseSearch.content.engines.createEngine.submit', {
- defaultMessage: 'Create this engine',
+ defaultMessage: 'Create this Search Application',
})}
diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engines/create_engine_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engines/create_engine_logic.test.ts
index 0d88ca44ff87d..93bdda9c4a2d0 100644
--- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engines/create_engine_logic.test.ts
+++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engines/create_engine_logic.test.ts
@@ -8,10 +8,12 @@
import { LogicMounter } from '../../../__mocks__/kea_logic';
import { Status } from '../../../../../common/types/api';
-import { ElasticsearchIndexWithIngestion } from '../../../../../common/types/indices';
+import { KibanaLogic } from '../../../shared/kibana';
import { CreateEngineApiLogic } from '../../api/engines/create_engine_api_logic';
+import { ENGINES_PATH } from '../../routes';
+
import { CreateEngineLogic, CreateEngineLogicValues } from './create_engine_logic';
const DEFAULT_VALUES: CreateEngineLogicValues = {
@@ -27,7 +29,7 @@ const DEFAULT_VALUES: CreateEngineLogicValues = {
const VALID_ENGINE_NAME = 'unit-test-001';
const INVALID_ENGINE_NAME = 'TEST';
-const VALID_INDICES_DATA = [{ name: 'search-index-01' }] as ElasticsearchIndexWithIngestion[];
+const VALID_INDICES_DATA = ['search-index-01'];
describe('CreateEngineLogic', () => {
const { mount: apiLogicMount } = new LogicMounter(CreateEngineApiLogic);
@@ -59,16 +61,17 @@ describe('CreateEngineLogic', () => {
indices: ['search-index-01'],
});
});
- it('engineCreated is handled', () => {
+ it('engineCreated is handled and is navigated to Search application list page', () => {
jest.spyOn(CreateEngineLogic.actions, 'fetchEngines');
- jest.spyOn(CreateEngineLogic.actions, 'closeEngineCreate');
-
+ jest
+ .spyOn(KibanaLogic.values, 'navigateToUrl')
+ .mockImplementationOnce(() => Promise.resolve());
CreateEngineApiLogic.actions.apiSuccess({
result: 'created',
});
+ expect(KibanaLogic.values.navigateToUrl).toHaveBeenCalledWith(ENGINES_PATH);
expect(CreateEngineLogic.actions.fetchEngines).toHaveBeenCalledTimes(1);
- expect(CreateEngineLogic.actions.closeEngineCreate).toHaveBeenCalledTimes(1);
});
});
describe('selectors', () => {
@@ -114,7 +117,7 @@ describe('CreateEngineLogic', () => {
it('returns true while create request in progress', () => {
CreateEngineApiLogic.actions.makeRequest({
engineName: VALID_ENGINE_NAME,
- indices: [VALID_INDICES_DATA[0].name],
+ indices: [VALID_INDICES_DATA[0]],
});
expect(CreateEngineLogic.values.formDisabled).toEqual(true);
diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engines/create_engine_logic.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engines/create_engine_logic.ts
index 266dab05c5441..c939dda096446 100644
--- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engines/create_engine_logic.ts
+++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engines/create_engine_logic.ts
@@ -8,27 +8,27 @@
import { kea, MakeLogicType } from 'kea';
import { Status } from '../../../../../common/types/api';
-import { ElasticsearchIndexWithIngestion } from '../../../../../common/types/indices';
+import { KibanaLogic } from '../../../shared/kibana';
import {
CreateEngineApiLogic,
CreateEngineApiLogicActions,
} from '../../api/engines/create_engine_api_logic';
+import { ENGINES_PATH } from '../../routes';
import { EnginesListLogic } from './engines_list_logic';
const NAME_VALIDATION = new RegExp(/^[a-z0-9\-]+$/);
export interface CreateEngineLogicActions {
- closeEngineCreate: () => void;
createEngine: () => void;
createEngineRequest: CreateEngineApiLogicActions['makeRequest'];
engineCreateError: CreateEngineApiLogicActions['apiError'];
engineCreated: CreateEngineApiLogicActions['apiSuccess'];
fetchEngines: () => void;
setEngineName: (engineName: string) => { engineName: string };
- setSelectedIndices: (indices: ElasticsearchIndexWithIngestion[]) => {
- indices: ElasticsearchIndexWithIngestion[];
+ setSelectedIndices: (indices: string[]) => {
+ indices: string[];
};
}
@@ -40,7 +40,7 @@ export interface CreateEngineLogicValues {
engineNameStatus: 'complete' | 'incomplete' | 'warning';
formDisabled: boolean;
indicesStatus: 'complete' | 'incomplete';
- selectedIndices: ElasticsearchIndexWithIngestion[];
+ selectedIndices: string[];
}
export const CreateEngineLogic = kea<
@@ -49,12 +49,12 @@ export const CreateEngineLogic = kea<
actions: {
createEngine: true,
setEngineName: (engineName: string) => ({ engineName }),
- setSelectedIndices: (indices: ElasticsearchIndexWithIngestion[]) => ({ indices }),
+ setSelectedIndices: (indices: string[]) => ({ indices }),
},
connect: {
actions: [
EnginesListLogic,
- ['closeEngineCreate', 'fetchEngines'],
+ ['fetchEngines'],
CreateEngineApiLogic,
[
'makeRequest as createEngineRequest',
@@ -68,12 +68,12 @@ export const CreateEngineLogic = kea<
createEngine: () => {
actions.createEngineRequest({
engineName: values.engineName,
- indices: values.selectedIndices.map((index) => index.name),
+ indices: values.selectedIndices,
});
},
engineCreated: () => {
actions.fetchEngines();
- actions.closeEngineCreate();
+ KibanaLogic.values.navigateToUrl(ENGINES_PATH);
},
}),
path: ['enterprise_search', 'content', 'create_engine_logic'],
diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engines/delete_engine_modal.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engines/delete_engine_modal.tsx
index 4203969bb74a2..46d7c8586ed80 100644
--- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engines/delete_engine_modal.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engines/delete_engine_modal.tsx
@@ -28,7 +28,7 @@ export const DeleteEngineModal: React.FC = ({ engineName
return (
{
@@ -42,7 +42,7 @@ export const DeleteEngineModal: React.FC = ({ engineName
confirmButtonText={i18n.translate(
'xpack.enterpriseSearch.content.engineList.deleteEngineModal.confirmButton.title',
{
- defaultMessage: 'Yes, delete this engine ',
+ defaultMessage: 'Yes, delete this search application',
}
)}
buttonColor="danger"
@@ -53,7 +53,7 @@ export const DeleteEngineModal: React.FC = ({ engineName
'xpack.enterpriseSearch.content.engineList.deleteEngineModal.delete.description',
{
defaultMessage:
- 'Deleting your engine is not a reversible action. Your indices will not be affected. ',
+ 'Deleting your search application is not a reversible action. Your indices will not be affected. ',
}
)}
diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engines/engines_list.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engines/engines_list.tsx
index 7617467bba64f..48dce9f524164 100644
--- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engines/engines_list.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engines/engines_list.tsx
@@ -31,6 +31,7 @@ import { docLinks } from '../../../shared/doc_links';
import { KibanaLogic } from '../../../shared/kibana';
import { LicensingLogic } from '../../../shared/licensing';
import { EXPLORE_PLATINUM_FEATURES_LINK } from '../../../workplace_search/constants';
+import { ENGINES_PATH, ENGINE_CREATION_PATH } from '../../routes';
import { EnterpriseSearchEnginesPageTemplate } from '../layout/engines_page_template';
import { LicensingCallout, LICENSING_FEATURE } from '../shared/licensing_callout/licensing_callout';
@@ -43,9 +44,12 @@ import { EngineListIndicesFlyout } from './engines_list_flyout';
import { EnginesListFlyoutLogic } from './engines_list_flyout_logic';
import { EnginesListLogic } from './engines_list_logic';
-export const CreateEngineButton: React.FC<{ disabled: boolean }> = ({ disabled }) => {
+interface CreateEngineButtonProps {
+ disabled: boolean;
+}
+export const CreateEngineButton: React.FC = ({ disabled }) => {
const [showPopover, setShowPopover] = useState(false);
- const { openEngineCreate } = useActions(EnginesListLogic);
+
return (
= ({ disabled }
iconType="plusInCircle"
data-test-subj="enterprise-search-content-engines-creation-button"
data-telemetry-id="entSearchContent-engines-list-createEngine"
- disabled={disabled}
- onClick={openEngineCreate}
+ isDisabled={disabled}
+ onClick={() => KibanaLogic.values.navigateToUrl(ENGINE_CREATION_PATH)}
>
- {i18n.translate('xpack.enterpriseSearch.content.engines.createEngineButtonLabel', {
- defaultMessage: 'Create Search Application',
- })}
+ {i18n.translate(
+ 'xpack.enterpriseSearch.content.searchApplications.createEngineButtonLabel',
+ {
+ defaultMessage: 'Create Search Application',
+ }
+ )}
}
>
@@ -82,7 +89,7 @@ export const CreateEngineButton: React.FC<{ disabled: boolean }> = ({ disabled }
@@ -94,27 +101,27 @@ export const CreateEngineButton: React.FC<{ disabled: boolean }> = ({ disabled }
);
};
+interface ListProps {
+ createEngineFlyoutOpen?: boolean;
+}
-export const EnginesList: React.FC = () => {
+export const EnginesList: React.FC = ({ createEngineFlyoutOpen }) => {
const {
closeDeleteEngineModal,
- closeEngineCreate,
fetchEngines,
onPaginate,
openDeleteEngineModal,
setSearchQuery,
setIsFirstRequest,
} = useActions(EnginesListLogic);
-
const { openFetchEngineFlyout } = useActions(EnginesListFlyoutLogic);
- const { isCloud } = useValues(KibanaLogic);
+ const { isCloud, navigateToUrl } = useValues(KibanaLogic);
const { hasPlatinumLicense } = useValues(LicensingLogic);
const isGated = !isCloud && !hasPlatinumLicense;
const {
- createEngineFlyoutOpen,
deleteModalEngineName,
hasNoEngines,
isDeleteModalVisible,
@@ -127,7 +134,7 @@ export const EnginesList: React.FC = () => {
const throttledSearchQuery = useThrottle(searchQuery, INPUT_THROTTLE_DELAY_MS);
useEffect(() => {
- // Don't fetch engines if we don't have a valid license
+ // Don't fetch search applications if we don't have a valid license
if (!isGated) {
fetchEngines();
}
@@ -148,18 +155,18 @@ export const EnginesList: React.FC = () => {
) : null}
- {createEngineFlyoutOpen && }
+ {createEngineFlyoutOpen && navigateToUrl(ENGINES_PATH)} />}
{
target="_blank"
data-telemetry-id="entSearchContent-engines-documentation-viewDocumentaion"
>
- {i18n.translate('xpack.enterpriseSearch.content.engines.documentation', {
- defaultMessage: 'explore our Search Applications documentation',
- })}
+ {i18n.translate(
+ 'xpack.enterpriseSearch.content.searchApplications.documentation',
+ {
+ defaultMessage: 'explore our Search Applications documentation',
+ }
+ )}
),
}}
/>
),
- pageTitle: i18n.translate('xpack.enterpriseSearch.content.engines.title', {
+ pageTitle: i18n.translate('xpack.enterpriseSearch.content.searchApplications.title', {
defaultMessage: 'Search Applications',
}),
rightSideItems: isLoading
@@ -185,7 +195,7 @@ export const EnginesList: React.FC = () => {
? []
: [],
}}
- pageViewTelemetry="Engines"
+ pageViewTelemetry="Search Applications"
isLoading={isLoading && !isGated}
>
{isGated && (
@@ -200,15 +210,15 @@ export const EnginesList: React.FC = () => {
{
{i18n.translate(
- 'xpack.enterpriseSearch.content.engines.searchPlaceholder.description',
+ 'xpack.enterpriseSearch.content.searchApplications.searchPlaceholder.description',
{
- defaultMessage: 'Locate an engine via name or by its included indices.',
+ defaultMessage:
+ 'Locate a search application via name or by its included indices.',
}
)}
@@ -230,7 +241,7 @@ export const EnginesList: React.FC = () => {
{
});
});
});
- describe('openEngineCreate', () => {
- it('set createEngineFlyoutOpen to true', () => {
- EnginesListLogic.actions.openEngineCreate();
- expect(EnginesListLogic.values).toEqual({
- ...DEFAULT_VALUES,
- createEngineFlyoutOpen: true,
- });
- });
- });
- describe('closeEngineCreate', () => {
- it('set createEngineFlyoutOpen to false', () => {
- EnginesListLogic.actions.closeEngineCreate();
- expect(EnginesListLogic.values).toEqual({
- ...DEFAULT_VALUES,
- createEngineFlyoutOpen: false,
- });
- });
- });
+
describe('setSearchQuery', () => {
it('set setSearchQuery to search value', () => {
EnginesListLogic.actions.setSearchQuery('my-search-query');
diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engines/engines_list_logic.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engines/engines_list_logic.ts
index 082747612698a..02ac44ae16c8d 100644
--- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engines/engines_list_logic.ts
+++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engines/engines_list_logic.ts
@@ -39,7 +39,7 @@ export type EnginesListActions = Pick<
'apiError' | 'apiSuccess' | 'makeRequest'
> & {
closeDeleteEngineModal(): void;
- closeEngineCreate(): void;
+
deleteEngine: DeleteEnginesApiLogicActions['makeRequest'];
deleteError: DeleteEnginesApiLogicActions['apiError'];
deleteSuccess: DeleteEnginesApiLogicActions['apiSuccess'];
@@ -50,13 +50,11 @@ export type EnginesListActions = Pick<
openDeleteEngineModal: (engine: EnterpriseSearchEngine | EnterpriseSearchEngineDetails) => {
engine: EnterpriseSearchEngine;
};
- openEngineCreate(): void;
setIsFirstRequest(): void;
setSearchQuery(searchQuery: string): { searchQuery: string };
};
interface EngineListValues {
- createEngineFlyoutOpen: boolean;
data: typeof FetchEnginesAPILogic.values.data;
deleteModalEngine: EnterpriseSearchEngine | null;
deleteModalEngineName: string;
@@ -76,11 +74,9 @@ interface EngineListValues {
export const EnginesListLogic = kea>({
actions: {
closeDeleteEngineModal: true,
- closeEngineCreate: true,
fetchEngines: true,
onPaginate: (args: EuiBasicTableOnChange) => ({ pageNumber: args.page.index }),
openDeleteEngineModal: (engine) => ({ engine }),
- openEngineCreate: true,
setIsFirstRequest: true,
setSearchQuery: (searchQuery: string) => ({ searchQuery }),
},
@@ -111,13 +107,6 @@ export const EnginesListLogic = kea ({
- createEngineFlyoutOpen: [
- false,
- {
- closeEngineCreate: () => false,
- openEngineCreate: () => true,
- },
- ],
deleteModalEngine: [
null,
{
diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engines/engines_router.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engines/engines_router.tsx
index 662e1d7c21417..bcd5e5ec3bcd2 100644
--- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engines/engines_router.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engines/engines_router.tsx
@@ -10,7 +10,7 @@ import { Switch } from 'react-router-dom';
import { Route } from '@kbn/shared-ux-router';
-import { ENGINES_PATH, ENGINE_PATH } from '../../routes';
+import { ENGINES_PATH, ENGINE_CREATION_PATH, ENGINE_PATH } from '../../routes';
import { EngineRouter } from '../engine/engine_router';
import { NotFound } from '../not_found';
@@ -23,6 +23,9 @@ export const EnginesRouter: React.FC = () => {
+
+
+
diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/new_index/new_index_created_toast.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/new_index/new_index_created_toast.tsx
index f1d120dee4e46..ac5bb69706146 100644
--- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/new_index/new_index_created_toast.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/new_index/new_index_created_toast.tsx
@@ -5,37 +5,9 @@
* 2.0.
*/
-import React from 'react';
-
-import { EuiFlexGroup, EuiFlexItem, EuiSpacer, EuiText } from '@elastic/eui';
-
import { i18n } from '@kbn/i18n';
-import { APP_SEARCH_URL } from '../../../../../common/constants';
-
import { flashSuccessToast } from '../../../shared/flash_messages';
-import { EuiButtonTo } from '../../../shared/react_router_helpers';
-
-const SuccessToast = (
- <>
-
- {i18n.translate('xpack.enterpriseSearch.content.new_index.successToast.description', {
- defaultMessage:
- 'You can use App Search engines to build a search experience for your new Elasticsearch index.',
- })}
-
-
-
-
-
- {i18n.translate('xpack.enterpriseSearch.content.new_index.successToast.button.label', {
- defaultMessage: 'Create an engine',
- })}
-
-
-
- >
-);
export function flashIndexCreatedToast(): void {
flashSuccessToast(
@@ -44,7 +16,6 @@ export function flashIndexCreatedToast(): void {
}),
{
iconType: 'cheer',
- text: SuccessToast,
}
);
}
diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/components/header_actions/create_engine_menu_item.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/components/header_actions/create_engine_menu_item.tsx
index 61f090f8e20ad..059fa91c8ac0b 100644
--- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/components/header_actions/create_engine_menu_item.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/components/header_actions/create_engine_menu_item.tsx
@@ -11,11 +11,11 @@ import { EuiContextMenuItem, EuiText, EuiFlexGroup, EuiFlexItem } from '@elastic
import { i18n } from '@kbn/i18n';
-import { APP_SEARCH_PLUGIN } from '../../../../../../../common/constants';
-import { ENGINE_CREATION_PATH } from '../../../../../app_search/routes';
+import { ENTERPRISE_SEARCH_CONTENT_PLUGIN } from '../../../../../../../common/constants';
import { ESINDEX_QUERY_PARAMETER } from '../../../../../shared/constants';
import { generateEncodedPath } from '../../../../../shared/encode_path_params';
import { KibanaLogic } from '../../../../../shared/kibana';
+import { ENGINE_CREATION_PATH } from '../../../../routes';
export interface CreateEngineMenuItemProps {
indexName?: string;
@@ -28,12 +28,15 @@ export const CreateEngineMenuItem: React.FC = ({
ingestionMethod,
isHiddenIndex,
}) => {
- const engineCreationPath = !indexName
- ? `${APP_SEARCH_PLUGIN.URL}${ENGINE_CREATION_PATH}`
- : generateEncodedPath(`${APP_SEARCH_PLUGIN.URL}${ENGINE_CREATION_PATH}?:indexKey=:indexName`, {
- indexKey: ESINDEX_QUERY_PARAMETER,
- indexName,
- });
+ const searchApplicationCreationPath = !indexName
+ ? `${ENTERPRISE_SEARCH_CONTENT_PLUGIN.URL}${ENGINE_CREATION_PATH}`
+ : generateEncodedPath(
+ `${ENTERPRISE_SEARCH_CONTENT_PLUGIN.URL}${ENGINE_CREATION_PATH}?:indexKey=:indexName`,
+ {
+ indexKey: ESINDEX_QUERY_PARAMETER,
+ indexName,
+ }
+ );
return (
@@ -43,7 +46,7 @@ export const CreateEngineMenuItem: React.FC = ({
size="s"
icon="plusInCircle"
onClick={() => {
- KibanaLogic.values.navigateToUrl(engineCreationPath, {
+ KibanaLogic.values.navigateToUrl(searchApplicationCreationPath, {
shouldNotCreateHref: true,
});
}}
@@ -51,9 +54,12 @@ export const CreateEngineMenuItem: React.FC = ({
>
- {i18n.translate('xpack.enterpriseSearch.content.index.searchEngines.createEngine', {
- defaultMessage: 'Create an App Search engine',
- })}
+ {i18n.translate(
+ 'xpack.enterpriseSearch.content.index.searchApplication.createSearchApplication',
+ {
+ defaultMessage: 'Create a Search Application',
+ }
+ )}
diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/components/header_actions/search_engines_popover.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/components/header_actions/search_engines_popover.tsx
index 47584d3021ada..611c63c276750 100644
--- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/components/header_actions/search_engines_popover.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/components/header_actions/search_engines_popover.tsx
@@ -20,9 +20,11 @@ import {
import { i18n } from '@kbn/i18n';
-import { APP_SEARCH_PLUGIN } from '../../../../../../../common/constants';
+import { ENTERPRISE_SEARCH_CONTENT_PLUGIN } from '../../../../../../../common/constants';
import { KibanaLogic } from '../../../../../shared/kibana';
+import { ENGINES_PATH } from '../../../../routes';
+
import { CreateEngineMenuItem } from './create_engine_menu_item';
import { SearchEnginesPopoverLogic } from './search_engines_popover_logic';
@@ -52,7 +54,7 @@ export const SearchEnginesPopover: React.FC = ({
onClick={toggleSearchEnginesPopover}
>
{i18n.translate('xpack.enterpriseSearch.content.index.searchEngines.label', {
- defaultMessage: 'Search engines',
+ defaultMessage: 'Search Applications',
})}
}
@@ -64,15 +66,18 @@ export const SearchEnginesPopover: React.FC = ({
data-telemetry-id={`entSearchContent-${ingestionMethod}-header-searchEngines-viewEngines`}
icon="eye"
onClick={() => {
- KibanaLogic.values.navigateToUrl(APP_SEARCH_PLUGIN.URL, {
- shouldNotCreateHref: true,
- });
+ KibanaLogic.values.navigateToUrl(
+ ENTERPRISE_SEARCH_CONTENT_PLUGIN.URL + ENGINES_PATH,
+ {
+ shouldNotCreateHref: true,
+ }
+ );
}}
>
{i18n.translate('xpack.enterpriseSearch.content.index.searchEngines.viewEngines', {
- defaultMessage: 'View App Search engines',
+ defaultMessage: 'View Search Applications',
})}
@@ -82,7 +87,7 @@ export const SearchEnginesPopover: React.FC = ({
content={i18n.translate(
'xpack.enterpriseSearch.content.index.searchEngines.createEngineDisabledTooltip',
{
- defaultMessage: 'You cannot create engines from hidden indices.',
+ defaultMessage: 'You cannot create search applications from hidden indices.',
}
)}
>
diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_indices/delete_index_modal.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_indices/delete_index_modal.tsx
index 90e1da7c26eb2..38875821aeccf 100644
--- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_indices/delete_index_modal.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_indices/delete_index_modal.tsx
@@ -80,7 +80,7 @@ export const DeleteIndexModal: React.FC = () => {
'xpack.enterpriseSearch.content.searchIndices.deleteModal.delete.description',
{
defaultMessage:
- 'Deleting this index will also delete all of its data and its {ingestionMethod} configuration. Any associated search engines will no longer be able to access any data stored in this index.',
+ 'Deleting this index will also delete all of its data and its {ingestionMethod} configuration. Any associated search applications will no longer be able to access any data stored in this index.',
values: {
ingestionMethod: ingestionMethodToText(ingestionMethod),
},
diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json
index 3e5b85ffcf0b4..cdfb1e09c6ae9 100644
--- a/x-pack/plugins/translations/translations/fr-FR.json
+++ b/x-pack/plugins/translations/translations/fr-FR.json
@@ -11131,8 +11131,6 @@
"xpack.enterpriseSearch.content.engine.indices.actions.viewIndex.caption": "Afficher l'index {indexName}",
"xpack.enterpriseSearch.content.engineList.deleteEngine.successToast.title": "{engineName} a été supprimé",
"xpack.enterpriseSearch.content.engines.createEngine.headerSubTitle": "Un moteur permet à vos utilisateurs d'interroger les données de vos index. Pour en savoir plus, explorez notre {enginesDocsLink} !",
- "xpack.enterpriseSearch.content.engines.description": "Les moteurs vous permettent d'interroger les données indexées avec un ensemble complet d'outils de pertinence, d'analyse et de personnalisation. Pour en savoir plus sur le fonctionnement des moteurs dans Enterprise Search, {documentationUrl}",
- "xpack.enterpriseSearch.content.engines.enginesList.description": "Affichage de {from}-{to} sur {total}",
"xpack.enterpriseSearch.content.enginesList.indicesFlyout.subTitle": "Afficher les index associés à {engineName}",
"xpack.enterpriseSearch.content.enginesList.table.column.view.indices": "{indicesCount, number} {indicesCount, plural, other {index}}",
"xpack.enterpriseSearch.content.index.connector.syncRules.description": "Ajoutez une règle de synchronisation pour personnaliser les données qui sont synchronisées à partir de {indexName}. Tout est inclus par défaut, et les documents sont validés par rapport à l'ensemble des règles d'indexation configurées, en commençant par le haut de la liste.",
@@ -12225,22 +12223,9 @@
"xpack.enterpriseSearch.content.engine.api.generateEngineApiKeyModal.done": "Terminé",
"xpack.enterpriseSearch.content.engine.api.generateEngineApiKeyModal.generateButton": "Générer une clé en lecture seule",
"xpack.enterpriseSearch.content.engine.api.generateEngineApiKeyModal.title": "Créer une clé d'API en lecture seule pour le moteur",
- "xpack.enterpriseSearch.content.engine.api.step1.apiKeyWarning": "Elastic ne stocke pas les clés d’API. Une fois la clé générée, vous ne pourrez la visualiser qu'une seule fois. Veillez à l'enregistrer dans un endroit sûr. Si vous n'y avez plus accès, vous devrez générer une nouvelle clé d’API à partir de cet écran.",
- "xpack.enterpriseSearch.content.engine.api.step1.createAPIKeyButton": "Créer une clé d'API",
- "xpack.enterpriseSearch.content.engine.api.step1.learnMoreLink": "Découvrez plus d'informations sur les clés d'API.",
- "xpack.enterpriseSearch.content.engine.api.step1.title": "Générer et enregistrer la clé d'API",
- "xpack.enterpriseSearch.content.engine.api.step1.viewKeysButton": "Afficher les clés",
- "xpack.enterpriseSearch.content.engine.api.step2.copyEndpointDescription": "Utilisez cette URL pour accéder aux points de terminaison d'API de votre moteur.",
- "xpack.enterpriseSearch.content.engine.api.step2.title": "Copier le point de terminaison de votre moteur",
"xpack.enterpriseSearch.content.engine.api.step3.curlTitle": "cURL",
"xpack.enterpriseSearch.content.engine.api.step3.intro": "Découvrez comment effectuer l'intégration avec votre moteur à l'aide des clients de langage gérés par Elastic pour vous aider à créer votre expérience de recherche.",
"xpack.enterpriseSearch.content.engine.api.step3.searchUITitle": "Search UI",
- "xpack.enterpriseSearch.content.engine.api.step3.title": "Découvrir comment appeler vos points de terminaison",
- "xpack.enterpriseSearch.content.engine.api.step4.copy": "Votre moteur fournit des données d'analyse de base dans le cadre de cette installation. Pour recevoir des indicateurs plus granulaires et personnalisés, intégrez notre script Behavioral Analytics dans votre plateforme.",
- "xpack.enterpriseSearch.content.engine.api.step4.learnHowLink": "Découvrez comment faire",
- "xpack.enterpriseSearch.content.engine.api.step4.title": "(Facultatif) Booster vos analyses",
- "xpack.enterpriseSearch.content.engine.headerActions.actionsButton.ariaLabel": "Bouton de menu d'actions du moteur",
- "xpack.enterpriseSearch.content.engine.headerActions.delete": "Supprimer ce moteur",
"xpack.enterpriseSearch.content.engine.indices.actions.columnTitle": "Actions",
"xpack.enterpriseSearch.content.engine.indices.actions.removeIndex.title": "Retirer cet index du moteur",
"xpack.enterpriseSearch.content.engine.indices.actions.viewIndex.title": "Afficher cet index",
@@ -12265,7 +12250,6 @@
"xpack.enterpriseSearch.content.engineList.deleteEngineModal.delete.description": "La suppression de votre moteur ne pourra pas être annulée. Vos index ne seront pas affectés. ",
"xpack.enterpriseSearch.content.engineList.deleteEngineModal.title": "Supprimer définitivement ce moteur ?",
"xpack.enterpriseSearch.content.engineList.table.column.actions.deleteEngineLabel": "Supprimer ce moteur",
- "xpack.enterpriseSearch.content.engines.breadcrumb": "Moteurs",
"xpack.enterpriseSearch.content.engines.createEngine.header.createError.title": "Erreur lors de la création du moteur",
"xpack.enterpriseSearch.content.engines.createEngine.header.docsLink": "Documentation sur les moteurs",
"xpack.enterpriseSearch.content.engines.createEngine.headerTitle": "Créer un moteur",
@@ -12273,15 +12257,9 @@
"xpack.enterpriseSearch.content.engines.createEngine.nameEngine.title": "Nommer votre moteur",
"xpack.enterpriseSearch.content.engines.createEngine.selectIndices.title": "Sélectionner les index",
"xpack.enterpriseSearch.content.engines.createEngine.submit": "Créer ce moteur",
- "xpack.enterpriseSearch.content.engines.createEngineButtonLabel": "Créer un moteur",
- "xpack.enterpriseSearch.content.engines.documentation": "explorer notre documentation sur les moteurs",
"xpack.enterpriseSearch.content.engines.enginesList.empty.description": "Lançons-nous dans la création de votre premier moteur.",
"xpack.enterpriseSearch.content.engines.enginesList.empty.title": "Créer votre premier moteur",
"xpack.enterpriseSearch.content.engines.indices.addIndicesFlyout.updateError.title": "Erreur lors de la mise à jour du moteur",
- "xpack.enterpriseSearch.content.engines.searchBar.ariaLabel": "Moteurs de recherche",
- "xpack.enterpriseSearch.content.engines.searchPlaceholder": "Moteurs de recherche",
- "xpack.enterpriseSearch.content.engines.searchPlaceholder.description": "Localisez un moteur en fonction de son nom ou de ses index inclus.",
- "xpack.enterpriseSearch.content.engines.title": "Moteurs",
"xpack.enterpriseSearch.content.enginesList.indicesFlyout.table.docsCount.columnTitle": "Nombre de documents",
"xpack.enterpriseSearch.content.enginesList.indicesFlyout.table.health.columnTitle": "Intégrité des index",
"xpack.enterpriseSearch.content.enginesList.indicesFlyout.table.name.columnTitle": "Nom de l'index",
@@ -12338,7 +12316,6 @@
"xpack.enterpriseSearch.content.index.pipelines.settings.reduceWhitespaceDescription": "Supprimer automatiquement l'espace supplémentaire de vos documents",
"xpack.enterpriseSearch.content.index.pipelines.settings.reduceWhitespaceLabel": "Réduire l'espace",
"xpack.enterpriseSearch.content.index.pipelines.settings.runMlInferenceDescrition": "Améliorer vos données à l'aide de modèles de ML entraînés compatibles",
- "xpack.enterpriseSearch.content.index.searchEngines.createEngine": "Créer un moteur App Search",
"xpack.enterpriseSearch.content.index.searchEngines.createEngineDisabledTooltip": "Vous ne pouvez pas créer de moteurs à partir d'index masqués.",
"xpack.enterpriseSearch.content.index.searchEngines.label": "Moteurs de recherche",
"xpack.enterpriseSearch.content.index.searchEngines.viewEngines": "Afficher les moteurs App Search",
@@ -12617,8 +12594,6 @@
"xpack.enterpriseSearch.content.nativeConnectors.mongodb.name": "MongoDB",
"xpack.enterpriseSearch.content.nativeConnectors.mysql.name": "MySQL",
"xpack.enterpriseSearch.content.navTitle": "Contenu",
- "xpack.enterpriseSearch.content.new_index.successToast.button.label": "Créer un moteur",
- "xpack.enterpriseSearch.content.new_index.successToast.description": "Vous pouvez utiliser des moteurs App Search pour créer une expérience de recherche pour votre nouvel index Elasticsearch.",
"xpack.enterpriseSearch.content.new_index.successToast.title": "Index créé avec succès",
"xpack.enterpriseSearch.content.newIndex.breadcrumb": "Nouvel index de recherche",
"xpack.enterpriseSearch.content.newIndex.emptyState.description": "Les données que vous ajoutez dans Enterprise Search sont appelées \"index de recherche\", et vous pouvez effectuer des recherches à l'intérieur à la fois dans App Search et dans Workplace Search. Maintenant, vous pouvez utiliser vos connecteurs dans App Search et vos robots d'indexation dans Workplace Search.",
@@ -13039,8 +13014,6 @@
"xpack.enterpriseSearch.emailLabel": "E-mail",
"xpack.enterpriseSearch.emptyState.description": "Votre contenu est stocké dans un index Elasticsearch. Commencez par créer un index Elasticsearch et sélectionnez une méthode d'ingestion. Les options comprennent le robot d'indexation Elastic, les intégrations de données tierces ou l'utilisation des points de terminaison d'API Elasticsearch.",
"xpack.enterpriseSearch.emptyState.description.line2": "Qu’il s’agisse de créer une expérience de recherche avec App Search ou Elasticsearch, vous pouvez commencer ici.",
- "xpack.enterpriseSearch.engines.engine.notFound.action1": "Retour aux moteurs",
- "xpack.enterpriseSearch.engines.navTitle": "Moteurs",
"xpack.enterpriseSearch.enterpriseSearch.setupGuide.description": "Effectuez des recherches sur tout, partout. Offrez à vos équipes débordées une expérience de recherche innovante et puissante facilement mise en œuvre. Intégrez rapidement une fonction de recherche préréglée à votre site web, à votre application ou à votre lieu de travail. Effectuez des recherches simples sur tout.",
"xpack.enterpriseSearch.enterpriseSearch.setupGuide.notConfigured": "Enterprise Search n'est pas encore configuré dans votre instance Kibana.",
"xpack.enterpriseSearch.enterpriseSearch.setupGuide.videoAlt": "Premiers pas avec Enterprise Search",
diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json
index ef49ea6ad1039..c3aae5c75beb8 100644
--- a/x-pack/plugins/translations/translations/ja-JP.json
+++ b/x-pack/plugins/translations/translations/ja-JP.json
@@ -11130,8 +11130,6 @@
"xpack.enterpriseSearch.content.engine.indices.actions.viewIndex.caption": "インデックス{indexName}を表示",
"xpack.enterpriseSearch.content.engineList.deleteEngine.successToast.title": "{engineName}が削除されました。",
"xpack.enterpriseSearch.content.engines.createEngine.headerSubTitle": "エンジンは、ユーザーがインデックス内のデータを照会することを可能にします。詳細については、{enginesDocsLink}を探索してください。",
- "xpack.enterpriseSearch.content.engines.description": "エンジンは、関連性、分析、パーソナライゼーションツールの完全なセットで、インデックスされたデータを照会することを可能にします。エンタープライズサーチにおけるエンジンの仕組みについて詳しく知るには{documentationUrl}",
- "xpack.enterpriseSearch.content.engines.enginesList.description": "{total}件中{from}-{to}件を表示中",
"xpack.enterpriseSearch.content.enginesList.indicesFlyout.subTitle": "{engineName}に関連付けられたインデックスを表示",
"xpack.enterpriseSearch.content.enginesList.table.column.view.indices": "{indicesCount, number} {indicesCount, plural, other {インデックス}}",
"xpack.enterpriseSearch.content.index.connector.syncRules.description": "同期ルールを追加して、{indexName}から同期されるデータをカスタマイズします。デフォルトではすべてが含まれます。ドキュメントは、リストの最上位から下に向かって、構成されたインデックスルールのセットに対して検証されます。",
@@ -12224,22 +12222,9 @@
"xpack.enterpriseSearch.content.engine.api.generateEngineApiKeyModal.done": "完了",
"xpack.enterpriseSearch.content.engine.api.generateEngineApiKeyModal.generateButton": "読み取り専用キーを生成",
"xpack.enterpriseSearch.content.engine.api.generateEngineApiKeyModal.title": "エンジン読み取り専用APIキーを作成",
- "xpack.enterpriseSearch.content.engine.api.step1.apiKeyWarning": "ElasticはAPIキーを保存しません。生成後は、1回だけキーを表示できます。必ず安全に保管してください。アクセスできなくなった場合は、この画面から新しいAPIキーを生成する必要があります。",
- "xpack.enterpriseSearch.content.engine.api.step1.createAPIKeyButton": "APIキーを作成",
- "xpack.enterpriseSearch.content.engine.api.step1.learnMoreLink": "APIキーの詳細をご覧ください。",
- "xpack.enterpriseSearch.content.engine.api.step1.title": "APIキーを生成して保存",
- "xpack.enterpriseSearch.content.engine.api.step1.viewKeysButton": "キーを表示",
- "xpack.enterpriseSearch.content.engine.api.step2.copyEndpointDescription": "このURLを使用して、エンジンのAPIエンドポイントにアクセスします。",
- "xpack.enterpriseSearch.content.engine.api.step2.title": "エンジンのエンドポイントをコピー",
"xpack.enterpriseSearch.content.engine.api.step3.curlTitle": "cURL",
"xpack.enterpriseSearch.content.engine.api.step3.intro": "Elasticで管理されている言語クライアントが使用されたエンジンと統合し、検索エクスペリエンスを構築する方法をご覧ください。",
"xpack.enterpriseSearch.content.engine.api.step3.searchUITitle": "Search UI",
- "xpack.enterpriseSearch.content.engine.api.step3.title": "エンドポイントを呼び出す方法をご覧ください",
- "xpack.enterpriseSearch.content.engine.api.step4.copy": "エンジンは、このインストールの一部として、基本分析データを提供します。さらに粒度の高いカスタムメトリックを利用するには、ご使用のプラットフォームで当社の行動分析を統合してください。",
- "xpack.enterpriseSearch.content.engine.api.step4.learnHowLink": "方法を学習",
- "xpack.enterpriseSearch.content.engine.api.step4.title": "(任意)分析を強化",
- "xpack.enterpriseSearch.content.engine.headerActions.actionsButton.ariaLabel": "エンジンアクションメニューボタン",
- "xpack.enterpriseSearch.content.engine.headerActions.delete": "このエンジンを削除",
"xpack.enterpriseSearch.content.engine.indices.actions.columnTitle": "アクション",
"xpack.enterpriseSearch.content.engine.indices.actions.removeIndex.title": "このインデックスをエンジンから削除",
"xpack.enterpriseSearch.content.engine.indices.actions.viewIndex.title": "このインデックスを表示",
@@ -12264,7 +12249,6 @@
"xpack.enterpriseSearch.content.engineList.deleteEngineModal.delete.description": "エンジンを削除すると、元に戻せません。インデックスには影響しません。",
"xpack.enterpriseSearch.content.engineList.deleteEngineModal.title": "このエンジンを完全に削除しますか?",
"xpack.enterpriseSearch.content.engineList.table.column.actions.deleteEngineLabel": "このエンジンを削除",
- "xpack.enterpriseSearch.content.engines.breadcrumb": "エンジン",
"xpack.enterpriseSearch.content.engines.createEngine.header.createError.title": "エンジンの作成エラー",
"xpack.enterpriseSearch.content.engines.createEngine.header.docsLink": "エンジンドキュメント",
"xpack.enterpriseSearch.content.engines.createEngine.headerTitle": "エンジンを作成",
@@ -12272,15 +12256,9 @@
"xpack.enterpriseSearch.content.engines.createEngine.nameEngine.title": "エンジン名を指定",
"xpack.enterpriseSearch.content.engines.createEngine.selectIndices.title": "インデックスを選択",
"xpack.enterpriseSearch.content.engines.createEngine.submit": "このエンジンを削除",
- "xpack.enterpriseSearch.content.engines.createEngineButtonLabel": "エンジンを作成",
- "xpack.enterpriseSearch.content.engines.documentation": "エンジンドキュメントを読む",
"xpack.enterpriseSearch.content.engines.enginesList.empty.description": "最初のエンジンの作成を説明します。",
"xpack.enterpriseSearch.content.engines.enginesList.empty.title": "初めてのエンジンの作成",
"xpack.enterpriseSearch.content.engines.indices.addIndicesFlyout.updateError.title": "エンジンの更新エラー",
- "xpack.enterpriseSearch.content.engines.searchBar.ariaLabel": "検索エンジン",
- "xpack.enterpriseSearch.content.engines.searchPlaceholder": "検索エンジン",
- "xpack.enterpriseSearch.content.engines.searchPlaceholder.description": "名前または含まれているインデックスでエンジンを検索します。",
- "xpack.enterpriseSearch.content.engines.title": "エンジン",
"xpack.enterpriseSearch.content.enginesList.indicesFlyout.table.docsCount.columnTitle": "ドキュメント数",
"xpack.enterpriseSearch.content.enginesList.indicesFlyout.table.health.columnTitle": "インデックス正常性",
"xpack.enterpriseSearch.content.enginesList.indicesFlyout.table.name.columnTitle": "インデックス名",
@@ -12337,7 +12315,6 @@
"xpack.enterpriseSearch.content.index.pipelines.settings.reduceWhitespaceDescription": "ドキュメントの不要な空白を自動的に削除",
"xpack.enterpriseSearch.content.index.pipelines.settings.reduceWhitespaceLabel": "空白の削除",
"xpack.enterpriseSearch.content.index.pipelines.settings.runMlInferenceDescrition": "互換性がある学習済みMLモデルを使用してデータを強化",
- "xpack.enterpriseSearch.content.index.searchEngines.createEngine": "App Searchエンジンの作成",
"xpack.enterpriseSearch.content.index.searchEngines.createEngineDisabledTooltip": "非表示のインデックスからエンジンを作成することはできません。",
"xpack.enterpriseSearch.content.index.searchEngines.label": "検索エンジン",
"xpack.enterpriseSearch.content.index.searchEngines.viewEngines": "App Searchエンジンを表示",
@@ -12616,8 +12593,6 @@
"xpack.enterpriseSearch.content.nativeConnectors.mongodb.name": "MongoDB",
"xpack.enterpriseSearch.content.nativeConnectors.mysql.name": "MySQL",
"xpack.enterpriseSearch.content.navTitle": "コンテンツ",
- "xpack.enterpriseSearch.content.new_index.successToast.button.label": "エンジンを作成",
- "xpack.enterpriseSearch.content.new_index.successToast.description": "App Searchエンジンを使用して、新しいElasticsearchインデックスの検索エクスペリエンスを構築できます。",
"xpack.enterpriseSearch.content.new_index.successToast.title": "インデックスが正常に作成されました",
"xpack.enterpriseSearch.content.newIndex.breadcrumb": "新しい検索インデックス",
"xpack.enterpriseSearch.content.newIndex.emptyState.description": "エンタープライズ サーチで追加したデータは検索インデックスと呼ばれ、App SearchとWorkplace Searchの両方で検索可能です。App SearchのコネクターとWorkplace SearchのWebクローラーを使用できます。",
@@ -13038,8 +13013,6 @@
"xpack.enterpriseSearch.emailLabel": "メール",
"xpack.enterpriseSearch.emptyState.description": "コンテンツはElasticsearchインデックスに保存されます。まず、Elasticsearchインデックスを作成し、インジェスチョン方法を選択します。オプションには、Elastic Webクローラー、サードパーティデータ統合、Elasticsearch APIエンドポイントの使用があります。",
"xpack.enterpriseSearch.emptyState.description.line2": "App SearchまたはElasticsearchのどちらで検索エクスペリエンスを構築しても、これが最初のステップです。",
- "xpack.enterpriseSearch.engines.engine.notFound.action1": "エンジンに戻る",
- "xpack.enterpriseSearch.engines.navTitle": "エンジン",
"xpack.enterpriseSearch.enterpriseSearch.setupGuide.description": "場所を問わず、何でも検索。組織を支える多忙なチームのために、パワフルでモダンな検索エクスペリエンスを簡単に導入できます。Webサイトやアプリ、ワークプレイスに事前調整済みの検索をすばやく追加しましょう。何でもシンプルに検索できます。",
"xpack.enterpriseSearch.enterpriseSearch.setupGuide.notConfigured": "エンタープライズサーチはまだKibanaインスタンスで構成されていません。",
"xpack.enterpriseSearch.enterpriseSearch.setupGuide.videoAlt": "エンタープライズ サーチの基本操作",
diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json
index 589d923ae8ce1..84bdf7528d7ec 100644
--- a/x-pack/plugins/translations/translations/zh-CN.json
+++ b/x-pack/plugins/translations/translations/zh-CN.json
@@ -11131,8 +11131,6 @@
"xpack.enterpriseSearch.content.engine.indices.actions.viewIndex.caption": "查看索引 {indexName}",
"xpack.enterpriseSearch.content.engineList.deleteEngine.successToast.title": "{engineName} 已删除",
"xpack.enterpriseSearch.content.engines.createEngine.headerSubTitle": "引擎允许您的用户在索引中查询数据。请浏览我们的 {enginesDocsLink} 了解详情!",
- "xpack.enterpriseSearch.content.engines.description": "引擎允许您通过一整套相关性、分析和个性化工具查询索引数据。有关引擎在 Enterprise Search 中的工作机制的详情,{documentationUrl}",
- "xpack.enterpriseSearch.content.engines.enginesList.description": "正在显示第 {from}-{to} 个(共 {total} 个)",
"xpack.enterpriseSearch.content.enginesList.indicesFlyout.subTitle": "查看与 {engineName} 关联的索引",
"xpack.enterpriseSearch.content.enginesList.table.column.view.indices": "{indicesCount, number} 个{indicesCount, plural, other {索引}}",
"xpack.enterpriseSearch.content.index.connector.syncRules.description": "添加同步规则以定制要从 {indexName} 同步哪些数据。默认包括所有内容,并根据从上到下列出的已配置索引规则集验证文档。",
@@ -12225,22 +12223,9 @@
"xpack.enterpriseSearch.content.engine.api.generateEngineApiKeyModal.done": "完成",
"xpack.enterpriseSearch.content.engine.api.generateEngineApiKeyModal.generateButton": "生成只读密钥",
"xpack.enterpriseSearch.content.engine.api.generateEngineApiKeyModal.title": "创建引擎只读 API 密钥",
- "xpack.enterpriseSearch.content.engine.api.step1.apiKeyWarning": "Elastic 不会存储 API 密钥。一旦生成,您只能查看密钥一次。请确保将其保存在某个安全位置。如果失去它的访问权限,您需要从此屏幕生成新的 API 密钥。",
- "xpack.enterpriseSearch.content.engine.api.step1.createAPIKeyButton": "创建 API 密钥",
- "xpack.enterpriseSearch.content.engine.api.step1.learnMoreLink": "详细了解 API 密钥。",
- "xpack.enterpriseSearch.content.engine.api.step1.title": "生成并保存 API 密钥",
- "xpack.enterpriseSearch.content.engine.api.step1.viewKeysButton": "查看密钥",
- "xpack.enterpriseSearch.content.engine.api.step2.copyEndpointDescription": "使用此 URL 访问您引擎的 API 终端。",
- "xpack.enterpriseSearch.content.engine.api.step2.title": "复制您引擎的终端",
"xpack.enterpriseSearch.content.engine.api.step3.curlTitle": "cURL",
"xpack.enterpriseSearch.content.engine.api.step3.intro": "了解如何将您的引擎与由 Elastic 维护的语言客户端进行集成,以帮助构建搜索体验。",
"xpack.enterpriseSearch.content.engine.api.step3.searchUITitle": "搜索 UI",
- "xpack.enterpriseSearch.content.engine.api.step3.title": "了解如何调用终端",
- "xpack.enterpriseSearch.content.engine.api.step4.copy": "您的引擎作为此安装的一部分提供了基本分析数据。要接收更多粒度化的定制指标,请在您的平台上集成我们的行为分析脚本。",
- "xpack.enterpriseSearch.content.engine.api.step4.learnHowLink": "了解操作方法",
- "xpack.enterpriseSearch.content.engine.api.step4.title": "(可选)强化分析",
- "xpack.enterpriseSearch.content.engine.headerActions.actionsButton.ariaLabel": "引擎操作菜单按钮",
- "xpack.enterpriseSearch.content.engine.headerActions.delete": "删除此引擎",
"xpack.enterpriseSearch.content.engine.indices.actions.columnTitle": "操作",
"xpack.enterpriseSearch.content.engine.indices.actions.removeIndex.title": "从引擎中移除此索引",
"xpack.enterpriseSearch.content.engine.indices.actions.viewIndex.title": "查看此索引",
@@ -12265,7 +12250,6 @@
"xpack.enterpriseSearch.content.engineList.deleteEngineModal.delete.description": "删除引擎是不可逆操作。您的索引不会受到影响。",
"xpack.enterpriseSearch.content.engineList.deleteEngineModal.title": "永久删除此引擎?",
"xpack.enterpriseSearch.content.engineList.table.column.actions.deleteEngineLabel": "删除此引擎",
- "xpack.enterpriseSearch.content.engines.breadcrumb": "引擎",
"xpack.enterpriseSearch.content.engines.createEngine.header.createError.title": "创建引擎时出错",
"xpack.enterpriseSearch.content.engines.createEngine.header.docsLink": "引擎文档",
"xpack.enterpriseSearch.content.engines.createEngine.headerTitle": "创建引擎",
@@ -12273,15 +12257,9 @@
"xpack.enterpriseSearch.content.engines.createEngine.nameEngine.title": "命名您的引擎",
"xpack.enterpriseSearch.content.engines.createEngine.selectIndices.title": "选择索引",
"xpack.enterpriseSearch.content.engines.createEngine.submit": "创建此引擎",
- "xpack.enterpriseSearch.content.engines.createEngineButtonLabel": "创建引擎",
- "xpack.enterpriseSearch.content.engines.documentation": "浏览我们的引擎文档",
"xpack.enterpriseSearch.content.engines.enginesList.empty.description": "下面我们指导您创建首个引擎。",
"xpack.enterpriseSearch.content.engines.enginesList.empty.title": "创建您的首个引擎",
"xpack.enterpriseSearch.content.engines.indices.addIndicesFlyout.updateError.title": "更新引擎时出错",
- "xpack.enterpriseSearch.content.engines.searchBar.ariaLabel": "搜索引擎",
- "xpack.enterpriseSearch.content.engines.searchPlaceholder": "搜索引擎",
- "xpack.enterpriseSearch.content.engines.searchPlaceholder.description": "通过名称或根据其包含的索引查找引擎。",
- "xpack.enterpriseSearch.content.engines.title": "引擎",
"xpack.enterpriseSearch.content.enginesList.indicesFlyout.table.docsCount.columnTitle": "文档计数",
"xpack.enterpriseSearch.content.enginesList.indicesFlyout.table.health.columnTitle": "索引运行状况",
"xpack.enterpriseSearch.content.enginesList.indicesFlyout.table.name.columnTitle": "索引名称",
@@ -12338,7 +12316,6 @@
"xpack.enterpriseSearch.content.index.pipelines.settings.reduceWhitespaceDescription": "自动剪裁文档中的额外空白",
"xpack.enterpriseSearch.content.index.pipelines.settings.reduceWhitespaceLabel": "减少空白",
"xpack.enterpriseSearch.content.index.pipelines.settings.runMlInferenceDescrition": "使用兼容的已训练 ML 模型增强您的数据",
- "xpack.enterpriseSearch.content.index.searchEngines.createEngine": "创建 App Search 引擎",
"xpack.enterpriseSearch.content.index.searchEngines.createEngineDisabledTooltip": "无法从隐藏的索引中创建引擎。",
"xpack.enterpriseSearch.content.index.searchEngines.label": "搜索引擎",
"xpack.enterpriseSearch.content.index.searchEngines.viewEngines": "查看 App Search 引擎",
@@ -12617,8 +12594,6 @@
"xpack.enterpriseSearch.content.nativeConnectors.mongodb.name": "MongoDB",
"xpack.enterpriseSearch.content.nativeConnectors.mysql.name": "MySQL",
"xpack.enterpriseSearch.content.navTitle": "内容",
- "xpack.enterpriseSearch.content.new_index.successToast.button.label": "创建引擎",
- "xpack.enterpriseSearch.content.new_index.successToast.description": "您可以使用 App Search 引擎为您的新 Elasticsearch 索引构建搜索体验。",
"xpack.enterpriseSearch.content.new_index.successToast.title": "已成功创建索引",
"xpack.enterpriseSearch.content.newIndex.breadcrumb": "新搜索索引",
"xpack.enterpriseSearch.content.newIndex.emptyState.description": "您在 Enterprise Search 中添加的数据称为搜索索引,您可在 App Search 和 Workplace Search 中搜索这些数据。现在,您可以在 App Search 中使用连接器,在 Workplace Search 中使用网络爬虫。",
@@ -13039,8 +13014,6 @@
"xpack.enterpriseSearch.emailLabel": "电子邮件",
"xpack.enterpriseSearch.emptyState.description": "您的内容存储在 Elasticsearch 索引中。通过创建 Elasticsearch 索引并选择采集方法开始使用。选项包括 Elastic 网络爬虫、第三方数据集成或使用 Elasticsearch API 终端。",
"xpack.enterpriseSearch.emptyState.description.line2": "无论是使用 App Search 还是 Elasticsearch 构建搜索体验,您都可以从此处立即开始。",
- "xpack.enterpriseSearch.engines.engine.notFound.action1": "返回到引擎",
- "xpack.enterpriseSearch.engines.navTitle": "引擎",
"xpack.enterpriseSearch.enterpriseSearch.setupGuide.description": "随时随地进行全面搜索。为工作繁忙的团队轻松实现强大的现代搜索体验。将预先调整的搜索功能快速添加到您的网站、应用或工作区。全面搜索就是这么简单。",
"xpack.enterpriseSearch.enterpriseSearch.setupGuide.notConfigured": "企业搜索尚未在您的 Kibana 实例中配置。",
"xpack.enterpriseSearch.enterpriseSearch.setupGuide.videoAlt": "企业搜索入门",
From 3c21b451b3d68d725980211e962b3d88aea16e97 Mon Sep 17 00:00:00 2001
From: Ignacio Rivas
Date: Fri, 21 Apr 2023 15:47:28 +0200
Subject: [PATCH 12/15] [Ingest Pipelines] Add attachment processor (#155226)
---
.../__jest__/processors/attachment.test.tsx | 122 +++++++++++
.../__jest__/processors/processor.helpers.tsx | 5 +
.../processor_form/processors/attachment.tsx | 202 ++++++++++++++++++
.../processor_form/processors/index.ts | 1 +
.../shared/map_processor_type_to_form.tsx | 18 ++
5 files changed, 348 insertions(+)
create mode 100644 x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/attachment.test.tsx
create mode 100644 x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/processors/attachment.tsx
diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/attachment.test.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/attachment.test.tsx
new file mode 100644
index 0000000000000..b4cfbf3046db0
--- /dev/null
+++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/attachment.test.tsx
@@ -0,0 +1,122 @@
+/*
+ * 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 { act } from 'react-dom/test-utils';
+import { setup, SetupResult, getProcessorValue, setupEnvironment } from './processor.helpers';
+
+const ATTACHMENT_TYPE = 'attachment';
+
+describe('Processor: Attachment', () => {
+ let onUpdate: jest.Mock;
+ let testBed: SetupResult;
+ const { httpSetup } = setupEnvironment();
+
+ beforeAll(() => {
+ jest.useFakeTimers({ legacyFakeTimers: true });
+ });
+
+ afterAll(() => {
+ jest.useRealTimers();
+ });
+
+ beforeEach(async () => {
+ onUpdate = jest.fn();
+
+ await act(async () => {
+ testBed = await setup(httpSetup, {
+ value: {
+ processors: [],
+ },
+ onFlyoutOpen: jest.fn(),
+ onUpdate,
+ });
+ });
+
+ const { component, actions } = testBed;
+
+ component.update();
+
+ // Open flyout to add new processor
+ actions.addProcessor();
+ // Add type (the other fields are not visible until a type is selected)
+ await actions.addProcessorType(ATTACHMENT_TYPE);
+ });
+
+ test('prevents form submission if required fields are not provided', async () => {
+ const {
+ actions: { saveNewProcessor },
+ form,
+ } = testBed;
+
+ // Click submit button with only the type defined
+ await saveNewProcessor();
+
+ // Expect form error as "field" is a required parameter
+ expect(form.getErrorsMessages()).toEqual([
+ 'A field value is required.', // "Field" input
+ ]);
+ });
+
+ test('saves with default parameter values', async () => {
+ const {
+ actions: { saveNewProcessor },
+ form,
+ } = testBed;
+
+ // Add "field" value
+ form.setInputValue('fieldNameField.input', 'test_attachment_processor');
+
+ // Save the field
+ await saveNewProcessor();
+
+ const processors = getProcessorValue(onUpdate, ATTACHMENT_TYPE);
+
+ expect(processors[0][ATTACHMENT_TYPE]).toEqual({
+ field: 'test_attachment_processor',
+ });
+ });
+
+ test('saves with optional parameter values', async () => {
+ const {
+ actions: { saveNewProcessor },
+ form,
+ find,
+ component,
+ } = testBed;
+
+ // Add required fields
+ form.setInputValue('fieldNameField.input', 'test_attachment_processor');
+
+ // Add optional fields
+ form.setInputValue('targetField.input', 'test_target');
+ form.setInputValue('indexedCharsField.input', '123456');
+ form.setInputValue('indexedCharsFieldField.input', 'indexed_chars_field');
+ form.toggleEuiSwitch('removeBinaryField.input');
+ form.setInputValue('resourceNameField.input', 'resource_name_field');
+
+ // Add "networkDirectionField" value (required)
+ await act(async () => {
+ find('propertiesField').simulate('change', [{ label: 'content' }]);
+ });
+ component.update();
+
+ // Save the field
+ await saveNewProcessor();
+
+ const processors = getProcessorValue(onUpdate, ATTACHMENT_TYPE);
+
+ expect(processors[0][ATTACHMENT_TYPE]).toEqual({
+ field: 'test_attachment_processor',
+ target_field: 'test_target',
+ properties: ['content'],
+ indexed_chars: '123456',
+ indexed_chars_field: 'indexed_chars_field',
+ remove_binary: true,
+ resource_name: 'resource_name_field',
+ });
+ });
+});
diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/processor.helpers.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/processor.helpers.tsx
index 43ebf84f6ef30..125d8758ca0d0 100644
--- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/processor.helpers.tsx
+++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/processor.helpers.tsx
@@ -190,6 +190,11 @@ type TestSubject =
| 'droppableList.input-2'
| 'prefixField.input'
| 'suffixField.input'
+ | 'indexedCharsField.input'
+ | 'indexedCharsFieldField.input'
+ | 'removeBinaryField.input'
+ | 'resourceNameField.input'
+ | 'propertiesField'
| 'tileTypeField'
| 'targetFormatField'
| 'parentField.input'
diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/processors/attachment.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/processors/attachment.tsx
new file mode 100644
index 0000000000000..57f26bdaf204f
--- /dev/null
+++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/processors/attachment.tsx
@@ -0,0 +1,202 @@
+/*
+ * 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, { FunctionComponent } from 'react';
+import { i18n } from '@kbn/i18n';
+import { FormattedMessage } from '@kbn/i18n-react';
+import { EuiCode, EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui';
+
+import {
+ ComboBoxField,
+ FIELD_TYPES,
+ UseField,
+ ToggleField,
+ Field,
+} from '../../../../../../shared_imports';
+
+import { FieldNameField } from './common_fields/field_name_field';
+import { TargetField } from './common_fields/target_field';
+import { IgnoreMissingField } from './common_fields/ignore_missing_field';
+import { FieldsConfig, to, from } from './shared';
+
+const propertyValues: string[] = [
+ 'content',
+ 'title',
+ 'author',
+ 'keywords',
+ 'date',
+ 'content_type',
+ 'content_length',
+ 'language',
+];
+
+const fieldsConfig: FieldsConfig = {
+ /* Optional field configs */
+ indexed_chars: {
+ type: FIELD_TYPES.NUMBER,
+ serializer: from.emptyStringToUndefined,
+ label: i18n.translate(
+ 'xpack.ingestPipelines.pipelineEditor.attachment.indexedCharsFieldLabel',
+ {
+ defaultMessage: 'Indexed chars (optional)',
+ }
+ ),
+ helpText: (
+ {'100000'} }}
+ />
+ ),
+ },
+ indexed_chars_field: {
+ type: FIELD_TYPES.TEXT,
+ serializer: from.emptyStringToUndefined,
+ label: i18n.translate(
+ 'xpack.ingestPipelines.pipelineEditor.attachment.indexedCharsFieldFieldLabel',
+ {
+ defaultMessage: 'Indexed chars field (optional)',
+ }
+ ),
+ helpText: (
+ {'null'} }}
+ />
+ ),
+ },
+ properties: {
+ type: FIELD_TYPES.COMBO_BOX,
+ deserializer: to.arrayOfStrings,
+ serializer: from.optionalArrayOfStrings,
+ label: i18n.translate('xpack.ingestPipelines.pipelineEditor.attachment.propertiesFieldLabel', {
+ defaultMessage: 'Properties (optional)',
+ }),
+ helpText: (
+ {'all'} }}
+ />
+ ),
+ },
+ remove_binary: {
+ type: FIELD_TYPES.TOGGLE,
+ defaultValue: false,
+ deserializer: to.booleanOrUndef,
+ serializer: from.undefinedIfValue(false),
+ label: i18n.translate(
+ 'xpack.ingestPipelines.pipelineEditor.attachment.removeBinaryFieldLabel',
+ {
+ defaultMessage: 'Remove binary',
+ }
+ ),
+ helpText: i18n.translate(
+ 'xpack.ingestPipelines.pipelineEditor.attachment.removeBinaryFieldHelpText',
+ {
+ defaultMessage: 'If enabled, the binary field will be removed from the document.',
+ }
+ ),
+ },
+ resource_name: {
+ type: FIELD_TYPES.TEXT,
+ serializer: from.emptyStringToUndefined,
+ label: i18n.translate(
+ 'xpack.ingestPipelines.pipelineEditor.attachment.resourceNameFieldLabel',
+ {
+ defaultMessage: 'Resource name (optional)',
+ }
+ ),
+ helpText: (
+
+ ),
+ },
+};
+
+export const Attachment: FunctionComponent = () => {
+ return (
+ <>
+
+
+
+ }
+ />
+
+
+ {'attachment'} }}
+ />
+ }
+ />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ({ label })),
+ }}
+ path="fields.properties"
+ />
+
+
+
+
+
+
+ >
+ );
+};
diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/processors/index.ts b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/processors/index.ts
index 3512dafdd2854..210d113bd2aba 100644
--- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/processors/index.ts
+++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/processors/index.ts
@@ -8,6 +8,7 @@
// please try to keep this list sorted by module name (e.g. './bar' before './foo')
export { Append } from './append';
+export { Attachment } from './attachment';
export { Bytes } from './bytes';
export { Circle } from './circle';
export { CommunityId } from './community_id';
diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/shared/map_processor_type_to_form.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/shared/map_processor_type_to_form.tsx
index 75bbd764097ad..60f66dbb415f3 100644
--- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/shared/map_processor_type_to_form.tsx
+++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/shared/map_processor_type_to_form.tsx
@@ -14,6 +14,7 @@ import { LicenseType } from '../../../../../types';
import {
Append,
+ Attachment,
Bytes,
Circle,
CommunityId,
@@ -100,6 +101,23 @@ export const mapProcessorTypeToDescriptor: MapProcessorTypeToDescriptor = {
},
}),
},
+ attachment: {
+ FieldsComponent: Attachment,
+ docLinkPath: '/attachment.html',
+ label: i18n.translate('xpack.ingestPipelines.processors.label.attachment', {
+ defaultMessage: 'Attachment',
+ }),
+ typeDescription: i18n.translate('xpack.ingestPipelines.processors.description.attachment', {
+ defaultMessage: 'Extract file attachments in common formats (such as PPT, XLS, and PDF).',
+ }),
+ getDefaultDescription: ({ field }) =>
+ i18n.translate('xpack.ingestPipelines.processors.defaultDescription.attachment', {
+ defaultMessage: 'Extracts attachment from "{field}"',
+ values: {
+ field,
+ },
+ }),
+ },
bytes: {
FieldsComponent: Bytes,
docLinkPath: '/bytes-processor.html',
From b494716bdb5b5ef3171f65fd227543df2de517bf Mon Sep 17 00:00:00 2001
From: Dominique Clarke
Date: Fri, 21 Apr 2023 09:55:38 -0400
Subject: [PATCH 13/15] [Fleet] Create Synthetics migration for 8.8.0 (#154952)
## Summary
Resolves https://github.com/elastic/kibana/issues/155215
Resolves https://github.com/elastic/kibana/issues/142653
Handles two primary migrations for Synthetics integration policies.
1. Rounds deprecated schedules to supported schedules
2. Transforms old deprecated throttling schema `5u/3u/20l` to support
JSON schema `{ download: 5, upload: 3, latency: 20 }`
Schedule migration
---
Before 16m schedule
After
Before 4m schedule
After
Before 8m schedule
After
Before 2m schedule
After
Throttling migration
---
Before throttling: false
After
Before custom throttling
After
Before default throttling
After
### Testing
1. Check out the 8.7.0 branch
2. Create a Synthetics private location at
`app/synthetics/settings/private-locations`.
3. Create a monitor, configured at that private location, with invalid
schedules, ideally 1 or each type (http, icmp, browser, tcp) with an
invalid schedule (for example, 2, 8, 11, 16, 333, etc) at
`app/uptime/add-monitor`. Note: you must use Uptime to create monitors
with arbitrary schedules. The Synthetics app will not let you.
4. Create a browser monitor, configured with your private location, with
throttling turned off.
5. Create a browser monitor, configured for your private location, with
a custom throttling profile.
6. Check out this PR and wait for saved object migration to run
7. Navigate to the agent policy for your monitor. Confirm the schedules
were updated to supported schedules. Confirm the throttling configs now
appear in yaml. Confirm that `throttling: false` remains for the
throttling config that was turned off.
---
.../fleet/server/saved_objects/index.ts | 6 +-
.../migrations/synthetics/fixtures/8.7.0.ts | 1100 +++++++++++++++++
.../migrations/synthetics/index.ts | 8 +
.../migrations/synthetics/to_v8_8_0.test.ts | 167 +++
.../migrations/synthetics/to_v8_8_0.ts | 107 ++
.../saved_objects/migrations/to_v8_8_0.ts | 35 +
.../server/types/models/package_policy.ts | 1 +
.../common/constants/monitor_defaults.ts | 4 +-
.../formatters/browser/formatters.test.ts | 70 ++
.../common/formatters/browser/formatters.ts | 7 +-
.../format_synthetics_policy.test.ts | 2 +-
.../synthetics/services/add_monitor.ts | 14 +-
.../synthetics/e2e/tasks/import_monitors.ts | 14 +-
.../status_rule/status_rule_executor.test.ts | 14 +-
.../synthetics_private_location.test.ts | 2 +-
.../sample_data/test_browser_policy.ts | 7 +-
.../test_project_monitor_policy.ts | 7 +-
17 files changed, 1538 insertions(+), 27 deletions(-)
create mode 100644 x-pack/plugins/fleet/server/saved_objects/migrations/synthetics/fixtures/8.7.0.ts
create mode 100644 x-pack/plugins/fleet/server/saved_objects/migrations/synthetics/index.ts
create mode 100644 x-pack/plugins/fleet/server/saved_objects/migrations/synthetics/to_v8_8_0.test.ts
create mode 100644 x-pack/plugins/fleet/server/saved_objects/migrations/synthetics/to_v8_8_0.ts
create mode 100644 x-pack/plugins/fleet/server/saved_objects/migrations/to_v8_8_0.ts
create mode 100644 x-pack/plugins/synthetics/common/formatters/browser/formatters.test.ts
diff --git a/x-pack/plugins/fleet/server/saved_objects/index.ts b/x-pack/plugins/fleet/server/saved_objects/index.ts
index 763c6d6c69136..01d7bc1040368 100644
--- a/x-pack/plugins/fleet/server/saved_objects/index.ts
+++ b/x-pack/plugins/fleet/server/saved_objects/index.ts
@@ -54,10 +54,8 @@ import {
migrateInstallationToV860,
migratePackagePolicyToV860,
} from './migrations/to_v8_6_0';
-import {
- migratePackagePolicyToV870,
- migratePackagePolicyToV880,
-} from './migrations/security_solution';
+import { migratePackagePolicyToV870 } from './migrations/security_solution';
+import { migratePackagePolicyToV880 } from './migrations/to_v8_8_0';
/*
* Saved object types and mappings
diff --git a/x-pack/plugins/fleet/server/saved_objects/migrations/synthetics/fixtures/8.7.0.ts b/x-pack/plugins/fleet/server/saved_objects/migrations/synthetics/fixtures/8.7.0.ts
new file mode 100644
index 0000000000000..2c24678184094
--- /dev/null
+++ b/x-pack/plugins/fleet/server/saved_objects/migrations/synthetics/fixtures/8.7.0.ts
@@ -0,0 +1,1100 @@
+/*
+ * 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 { SavedObjectUnsanitizedDoc } from '@kbn/core/server';
+
+import type { PackagePolicy } from '../../../../../common';
+
+export const httpPolicy = {
+ type: 'ingest-package-policies',
+ id: 'ce2f0cc6-b082-4080-9ed3-82f38743a3ed-fa2e69b0-dec6-11ed-8746-c5b1a1a12ec1-default',
+ attributes: {
+ name: 'Invalid http monitor with 4 minute schedule-A private location-default',
+ namespace: 'default',
+ package: { name: 'synthetics', title: 'Elastic Synthetics', version: '0.11.8' },
+ enabled: true,
+ policy_id: 'fa2e69b0-dec6-11ed-8746-c5b1a1a12ec1',
+ inputs: [
+ {
+ type: 'synthetics/http',
+ policy_template: 'synthetics',
+ enabled: true,
+ streams: [
+ {
+ enabled: true,
+ data_stream: {
+ type: 'synthetics',
+ dataset: 'http',
+ elasticsearch: { privileges: { indices: ['auto_configure', 'create_doc', 'read'] } },
+ },
+ vars: {
+ __ui: {
+ value: '{"is_tls_enabled":false,"is_zip_url_tls_enabled":false}',
+ type: 'yaml',
+ },
+ enabled: { value: true, type: 'bool' },
+ type: { value: 'http', type: 'text' },
+ name: { value: 'Invalid http monitor with 4 minute schedule', type: 'text' },
+ schedule: { value: '"@every 4m"', type: 'text' },
+ urls: { value: 'https://elastic.co', type: 'text' },
+ 'service.name': { value: '', type: 'text' },
+ timeout: { value: '16s', type: 'text' },
+ max_redirects: { value: '0', type: 'integer' },
+ proxy_url: { value: '', type: 'text' },
+ tags: { value: null, type: 'yaml' },
+ username: { value: '', type: 'text' },
+ password: { value: '', type: 'password' },
+ 'response.include_headers': { value: true, type: 'bool' },
+ 'response.include_body': { value: 'on_error', type: 'text' },
+ 'check.request.method': { value: 'GET', type: 'text' },
+ 'check.request.headers': { value: null, type: 'yaml' },
+ 'check.request.body': { value: null, type: 'yaml' },
+ 'check.response.status': { value: null, type: 'yaml' },
+ 'check.response.headers': { value: null, type: 'yaml' },
+ 'check.response.body.positive': { value: null, type: 'yaml' },
+ 'check.response.body.negative': { value: null, type: 'yaml' },
+ 'ssl.certificate_authorities': { value: null, type: 'yaml' },
+ 'ssl.certificate': { value: null, type: 'yaml' },
+ 'ssl.key': { value: null, type: 'yaml' },
+ 'ssl.key_passphrase': { value: null, type: 'text' },
+ 'ssl.verification_mode': { value: null, type: 'text' },
+ 'ssl.supported_protocols': { value: null, type: 'yaml' },
+ location_name: { value: 'A private location', type: 'text' },
+ id: { value: 'ce2f0cc6-b082-4080-9ed3-82f38743a3ed', type: 'text' },
+ config_id: { value: 'ce2f0cc6-b082-4080-9ed3-82f38743a3ed', type: 'text' },
+ run_once: { value: false, type: 'bool' },
+ origin: { value: 'ui', type: 'text' },
+ 'monitor.project.id': { value: null, type: 'text' },
+ 'monitor.project.name': { value: null, type: 'text' },
+ },
+ id: 'synthetics/http-http-ce2f0cc6-b082-4080-9ed3-82f38743a3ed-fa2e69b0-dec6-11ed-8746-c5b1a1a12ec1-default',
+ compiled_stream: {
+ __ui: { is_tls_enabled: false, is_zip_url_tls_enabled: false },
+ type: 'http',
+ name: 'Invalid http monitor with 4 minute schedule',
+ id: 'ce2f0cc6-b082-4080-9ed3-82f38743a3ed',
+ origin: 'ui',
+ 'run_from.id': 'A private location',
+ 'run_from.geo.name': 'A private location',
+ enabled: true,
+ urls: 'https://elastic.co',
+ schedule: '@every 4m',
+ timeout: '16s',
+ max_redirects: 0,
+ 'response.include_headers': true,
+ 'response.include_body': 'on_error',
+ 'check.request.method': 'GET',
+ processors: [
+ {
+ add_fields: {
+ target: '',
+ fields: {
+ 'monitor.fleet_managed': true,
+ config_id: 'ce2f0cc6-b082-4080-9ed3-82f38743a3ed',
+ },
+ },
+ },
+ ],
+ },
+ },
+ ],
+ },
+ {
+ type: 'synthetics/tcp',
+ policy_template: 'synthetics',
+ enabled: false,
+ streams: [
+ {
+ enabled: false,
+ data_stream: { type: 'synthetics', dataset: 'tcp' },
+ vars: {
+ __ui: { type: 'yaml' },
+ enabled: { value: true, type: 'bool' },
+ type: { value: 'tcp', type: 'text' },
+ name: { type: 'text' },
+ schedule: { value: '"@every 3m"', type: 'text' },
+ hosts: { type: 'text' },
+ 'service.name': { type: 'text' },
+ timeout: { type: 'text' },
+ proxy_url: { type: 'text' },
+ proxy_use_local_resolver: { value: false, type: 'bool' },
+ tags: { type: 'yaml' },
+ 'check.send': { type: 'text' },
+ 'check.receive': { type: 'text' },
+ 'ssl.certificate_authorities': { type: 'yaml' },
+ 'ssl.certificate': { type: 'yaml' },
+ 'ssl.key': { type: 'yaml' },
+ 'ssl.key_passphrase': { type: 'text' },
+ 'ssl.verification_mode': { type: 'text' },
+ 'ssl.supported_protocols': { type: 'yaml' },
+ location_name: { value: 'Fleet managed', type: 'text' },
+ id: { type: 'text' },
+ config_id: { type: 'text' },
+ run_once: { value: false, type: 'bool' },
+ origin: { type: 'text' },
+ 'monitor.project.id': { type: 'text' },
+ 'monitor.project.name': { type: 'text' },
+ },
+ id: 'synthetics/tcp-tcp-ce2f0cc6-b082-4080-9ed3-82f38743a3ed-fa2e69b0-dec6-11ed-8746-c5b1a1a12ec1-default',
+ },
+ ],
+ },
+ {
+ type: 'synthetics/icmp',
+ policy_template: 'synthetics',
+ enabled: false,
+ streams: [
+ {
+ enabled: false,
+ data_stream: { type: 'synthetics', dataset: 'icmp' },
+ vars: {
+ __ui: { type: 'yaml' },
+ enabled: { value: true, type: 'bool' },
+ type: { value: 'icmp', type: 'text' },
+ name: { type: 'text' },
+ schedule: { value: '"@every 3m"', type: 'text' },
+ wait: { value: '1s', type: 'text' },
+ hosts: { type: 'text' },
+ 'service.name': { type: 'text' },
+ timeout: { type: 'text' },
+ tags: { type: 'yaml' },
+ location_name: { value: 'Fleet managed', type: 'text' },
+ id: { type: 'text' },
+ config_id: { type: 'text' },
+ run_once: { value: false, type: 'bool' },
+ origin: { type: 'text' },
+ 'monitor.project.id': { type: 'text' },
+ 'monitor.project.name': { type: 'text' },
+ },
+ id: 'synthetics/icmp-icmp-ce2f0cc6-b082-4080-9ed3-82f38743a3ed-fa2e69b0-dec6-11ed-8746-c5b1a1a12ec1-default',
+ },
+ ],
+ },
+ {
+ type: 'synthetics/browser',
+ policy_template: 'synthetics',
+ enabled: false,
+ streams: [
+ {
+ enabled: true,
+ data_stream: {
+ type: 'synthetics',
+ dataset: 'browser',
+ elasticsearch: { privileges: { indices: ['auto_configure', 'create_doc', 'read'] } },
+ },
+ vars: {
+ __ui: { type: 'yaml' },
+ enabled: { value: true, type: 'bool' },
+ type: { value: 'browser', type: 'text' },
+ name: { type: 'text' },
+ schedule: { value: '"@every 3m"', type: 'text' },
+ 'service.name': { type: 'text' },
+ timeout: { type: 'text' },
+ tags: { type: 'yaml' },
+ 'source.zip_url.url': { type: 'text' },
+ 'source.zip_url.username': { type: 'text' },
+ 'source.zip_url.folder': { type: 'text' },
+ 'source.zip_url.password': { type: 'password' },
+ 'source.inline.script': { type: 'yaml' },
+ 'source.project.content': { type: 'text' },
+ params: { type: 'yaml' },
+ playwright_options: { type: 'yaml' },
+ screenshots: { type: 'text' },
+ synthetics_args: { type: 'text' },
+ ignore_https_errors: { type: 'bool' },
+ 'throttling.config': { type: 'text' },
+ 'filter_journeys.tags': { type: 'yaml' },
+ 'filter_journeys.match': { type: 'text' },
+ 'source.zip_url.ssl.certificate_authorities': { type: 'yaml' },
+ 'source.zip_url.ssl.certificate': { type: 'yaml' },
+ 'source.zip_url.ssl.key': { type: 'yaml' },
+ 'source.zip_url.ssl.key_passphrase': { type: 'text' },
+ 'source.zip_url.ssl.verification_mode': { type: 'text' },
+ 'source.zip_url.ssl.supported_protocols': { type: 'yaml' },
+ 'source.zip_url.proxy_url': { type: 'text' },
+ location_name: { value: 'Fleet managed', type: 'text' },
+ id: { type: 'text' },
+ config_id: { type: 'text' },
+ run_once: { value: false, type: 'bool' },
+ origin: { type: 'text' },
+ 'monitor.project.id': { type: 'text' },
+ 'monitor.project.name': { type: 'text' },
+ },
+ id: 'synthetics/browser-browser-ce2f0cc6-b082-4080-9ed3-82f38743a3ed-fa2e69b0-dec6-11ed-8746-c5b1a1a12ec1-default',
+ compiled_stream: {
+ __ui: null,
+ type: 'browser',
+ name: null,
+ 'run_from.id': 'Fleet managed',
+ 'run_from.geo.name': 'Fleet managed',
+ enabled: true,
+ schedule: '@every 3m',
+ timeout: null,
+ throttling: null,
+ processors: [
+ { add_fields: { target: '', fields: { 'monitor.fleet_managed': true } } },
+ ],
+ },
+ },
+ {
+ enabled: true,
+ data_stream: {
+ type: 'synthetics',
+ dataset: 'browser.network',
+ elasticsearch: { privileges: { indices: ['auto_configure', 'create_doc', 'read'] } },
+ },
+ id: 'synthetics/browser-browser.network-ce2f0cc6-b082-4080-9ed3-82f38743a3ed-fa2e69b0-dec6-11ed-8746-c5b1a1a12ec1-default',
+ compiled_stream: {
+ processors: [
+ { add_fields: { target: '', fields: { 'monitor.fleet_managed': true } } },
+ ],
+ },
+ },
+ {
+ enabled: true,
+ data_stream: {
+ type: 'synthetics',
+ dataset: 'browser.screenshot',
+ elasticsearch: { privileges: { indices: ['auto_configure', 'create_doc', 'read'] } },
+ },
+ id: 'synthetics/browser-browser.screenshot-ce2f0cc6-b082-4080-9ed3-82f38743a3ed-fa2e69b0-dec6-11ed-8746-c5b1a1a12ec1-default',
+ compiled_stream: {
+ processors: [
+ { add_fields: { target: '', fields: { 'monitor.fleet_managed': true } } },
+ ],
+ },
+ },
+ ],
+ },
+ ],
+ is_managed: true,
+ revision: 1,
+ created_at: '2023-04-19T15:34:51.655Z',
+ created_by: 'system',
+ updated_at: '2023-04-19T15:34:51.655Z',
+ updated_by: 'system',
+ },
+ references: [],
+ coreMigrationVersion: '8.8.0',
+ updated_at: '2023-04-19T15:34:51.714Z',
+ created_at: '2023-04-19T15:34:51.714Z',
+ typeMigrationVersion: '8.7.0',
+} as unknown as SavedObjectUnsanitizedDoc;
+export const tcpPolicy = {
+ type: 'ingest-package-policies',
+ id: '77f25200-7cf3-450d-abfa-4f95faae1907-fa2e69b0-dec6-11ed-8746-c5b1a1a12ec1-default',
+ attributes: {
+ name: 'Invalid tcp monitor with 8 minute schedule-A private location-default',
+ namespace: 'default',
+ package: { name: 'synthetics', title: 'Elastic Synthetics', version: '0.11.8' },
+ enabled: true,
+ policy_id: 'fa2e69b0-dec6-11ed-8746-c5b1a1a12ec1',
+ inputs: [
+ {
+ type: 'synthetics/http',
+ policy_template: 'synthetics',
+ enabled: false,
+ streams: [
+ {
+ enabled: false,
+ data_stream: { type: 'synthetics', dataset: 'http' },
+ vars: {
+ __ui: { type: 'yaml' },
+ enabled: { value: true, type: 'bool' },
+ type: { value: 'http', type: 'text' },
+ name: { type: 'text' },
+ schedule: { value: '"@every 3m"', type: 'text' },
+ urls: { type: 'text' },
+ 'service.name': { type: 'text' },
+ timeout: { type: 'text' },
+ max_redirects: { type: 'integer' },
+ proxy_url: { type: 'text' },
+ tags: { type: 'yaml' },
+ username: { type: 'text' },
+ password: { type: 'password' },
+ 'response.include_headers': { type: 'bool' },
+ 'response.include_body': { type: 'text' },
+ 'check.request.method': { type: 'text' },
+ 'check.request.headers': { type: 'yaml' },
+ 'check.request.body': { type: 'yaml' },
+ 'check.response.status': { type: 'yaml' },
+ 'check.response.headers': { type: 'yaml' },
+ 'check.response.body.positive': { type: 'yaml' },
+ 'check.response.body.negative': { type: 'yaml' },
+ 'ssl.certificate_authorities': { type: 'yaml' },
+ 'ssl.certificate': { type: 'yaml' },
+ 'ssl.key': { type: 'yaml' },
+ 'ssl.key_passphrase': { type: 'text' },
+ 'ssl.verification_mode': { type: 'text' },
+ 'ssl.supported_protocols': { type: 'yaml' },
+ location_name: { value: 'Fleet managed', type: 'text' },
+ id: { type: 'text' },
+ config_id: { type: 'text' },
+ run_once: { value: false, type: 'bool' },
+ origin: { type: 'text' },
+ 'monitor.project.id': { type: 'text' },
+ 'monitor.project.name': { type: 'text' },
+ },
+ id: 'synthetics/http-http-77f25200-7cf3-450d-abfa-4f95faae1907-fa2e69b0-dec6-11ed-8746-c5b1a1a12ec1-default',
+ },
+ ],
+ },
+ {
+ type: 'synthetics/tcp',
+ policy_template: 'synthetics',
+ enabled: true,
+ streams: [
+ {
+ enabled: true,
+ data_stream: {
+ type: 'synthetics',
+ dataset: 'tcp',
+ elasticsearch: { privileges: { indices: ['auto_configure', 'create_doc', 'read'] } },
+ },
+ vars: {
+ __ui: {
+ value: '{"is_tls_enabled":false,"is_zip_url_tls_enabled":false}',
+ type: 'yaml',
+ },
+ enabled: { value: true, type: 'bool' },
+ type: { value: 'tcp', type: 'text' },
+ name: { value: 'Invalid tcp monitor with 8 minute schedule', type: 'text' },
+ schedule: { value: '"@every 8m"', type: 'text' },
+ hosts: { value: 'localhost:5601', type: 'text' },
+ 'service.name': { value: '', type: 'text' },
+ timeout: { value: '16s', type: 'text' },
+ proxy_url: { value: '', type: 'text' },
+ proxy_use_local_resolver: { value: false, type: 'bool' },
+ tags: { value: null, type: 'yaml' },
+ 'check.send': { value: '', type: 'text' },
+ 'check.receive': { value: '', type: 'text' },
+ 'ssl.certificate_authorities': { value: null, type: 'yaml' },
+ 'ssl.certificate': { value: null, type: 'yaml' },
+ 'ssl.key': { value: null, type: 'yaml' },
+ 'ssl.key_passphrase': { value: null, type: 'text' },
+ 'ssl.verification_mode': { value: null, type: 'text' },
+ 'ssl.supported_protocols': { value: null, type: 'yaml' },
+ location_name: { value: 'A private location', type: 'text' },
+ id: { value: '77f25200-7cf3-450d-abfa-4f95faae1907', type: 'text' },
+ config_id: { value: '77f25200-7cf3-450d-abfa-4f95faae1907', type: 'text' },
+ run_once: { value: false, type: 'bool' },
+ origin: { value: 'ui', type: 'text' },
+ 'monitor.project.id': { value: null, type: 'text' },
+ 'monitor.project.name': { value: null, type: 'text' },
+ },
+ id: 'synthetics/tcp-tcp-77f25200-7cf3-450d-abfa-4f95faae1907-fa2e69b0-dec6-11ed-8746-c5b1a1a12ec1-default',
+ compiled_stream: {
+ __ui: { is_tls_enabled: false, is_zip_url_tls_enabled: false },
+ type: 'tcp',
+ name: 'Invalid tcp monitor with 8 minute schedule',
+ id: '77f25200-7cf3-450d-abfa-4f95faae1907',
+ origin: 'ui',
+ 'run_from.id': 'A private location',
+ 'run_from.geo.name': 'A private location',
+ enabled: true,
+ hosts: 'localhost:5601',
+ schedule: '@every 8m',
+ timeout: '16s',
+ proxy_use_local_resolver: false,
+ processors: [
+ {
+ add_fields: {
+ target: '',
+ fields: {
+ 'monitor.fleet_managed': true,
+ config_id: '77f25200-7cf3-450d-abfa-4f95faae1907',
+ },
+ },
+ },
+ ],
+ },
+ },
+ ],
+ },
+ {
+ type: 'synthetics/icmp',
+ policy_template: 'synthetics',
+ enabled: false,
+ streams: [
+ {
+ enabled: false,
+ data_stream: { type: 'synthetics', dataset: 'icmp' },
+ vars: {
+ __ui: { type: 'yaml' },
+ enabled: { value: true, type: 'bool' },
+ type: { value: 'icmp', type: 'text' },
+ name: { type: 'text' },
+ schedule: { value: '"@every 3m"', type: 'text' },
+ wait: { value: '1s', type: 'text' },
+ hosts: { type: 'text' },
+ 'service.name': { type: 'text' },
+ timeout: { type: 'text' },
+ tags: { type: 'yaml' },
+ location_name: { value: 'Fleet managed', type: 'text' },
+ id: { type: 'text' },
+ config_id: { type: 'text' },
+ run_once: { value: false, type: 'bool' },
+ origin: { type: 'text' },
+ 'monitor.project.id': { type: 'text' },
+ 'monitor.project.name': { type: 'text' },
+ },
+ id: 'synthetics/icmp-icmp-77f25200-7cf3-450d-abfa-4f95faae1907-fa2e69b0-dec6-11ed-8746-c5b1a1a12ec1-default',
+ },
+ ],
+ },
+ {
+ type: 'synthetics/browser',
+ policy_template: 'synthetics',
+ enabled: false,
+ streams: [
+ {
+ enabled: true,
+ data_stream: {
+ type: 'synthetics',
+ dataset: 'browser',
+ elasticsearch: { privileges: { indices: ['auto_configure', 'create_doc', 'read'] } },
+ },
+ vars: {
+ __ui: { type: 'yaml' },
+ enabled: { value: true, type: 'bool' },
+ type: { value: 'browser', type: 'text' },
+ name: { type: 'text' },
+ schedule: { value: '"@every 3m"', type: 'text' },
+ 'service.name': { type: 'text' },
+ timeout: { type: 'text' },
+ tags: { type: 'yaml' },
+ 'source.zip_url.url': { type: 'text' },
+ 'source.zip_url.username': { type: 'text' },
+ 'source.zip_url.folder': { type: 'text' },
+ 'source.zip_url.password': { type: 'password' },
+ 'source.inline.script': { type: 'yaml' },
+ 'source.project.content': { type: 'text' },
+ params: { type: 'yaml' },
+ playwright_options: { type: 'yaml' },
+ screenshots: { type: 'text' },
+ synthetics_args: { type: 'text' },
+ ignore_https_errors: { type: 'bool' },
+ 'throttling.config': { type: 'text' },
+ 'filter_journeys.tags': { type: 'yaml' },
+ 'filter_journeys.match': { type: 'text' },
+ 'source.zip_url.ssl.certificate_authorities': { type: 'yaml' },
+ 'source.zip_url.ssl.certificate': { type: 'yaml' },
+ 'source.zip_url.ssl.key': { type: 'yaml' },
+ 'source.zip_url.ssl.key_passphrase': { type: 'text' },
+ 'source.zip_url.ssl.verification_mode': { type: 'text' },
+ 'source.zip_url.ssl.supported_protocols': { type: 'yaml' },
+ 'source.zip_url.proxy_url': { type: 'text' },
+ location_name: { value: 'Fleet managed', type: 'text' },
+ id: { type: 'text' },
+ config_id: { type: 'text' },
+ run_once: { value: false, type: 'bool' },
+ origin: { type: 'text' },
+ 'monitor.project.id': { type: 'text' },
+ 'monitor.project.name': { type: 'text' },
+ },
+ id: 'synthetics/browser-browser-77f25200-7cf3-450d-abfa-4f95faae1907-fa2e69b0-dec6-11ed-8746-c5b1a1a12ec1-default',
+ compiled_stream: {
+ __ui: null,
+ type: 'browser',
+ name: null,
+ 'run_from.id': 'Fleet managed',
+ 'run_from.geo.name': 'Fleet managed',
+ enabled: true,
+ schedule: '@every 3m',
+ timeout: null,
+ throttling: null,
+ processors: [
+ { add_fields: { target: '', fields: { 'monitor.fleet_managed': true } } },
+ ],
+ },
+ },
+ {
+ enabled: true,
+ data_stream: {
+ type: 'synthetics',
+ dataset: 'browser.network',
+ elasticsearch: { privileges: { indices: ['auto_configure', 'create_doc', 'read'] } },
+ },
+ id: 'synthetics/browser-browser.network-77f25200-7cf3-450d-abfa-4f95faae1907-fa2e69b0-dec6-11ed-8746-c5b1a1a12ec1-default',
+ compiled_stream: {
+ processors: [
+ { add_fields: { target: '', fields: { 'monitor.fleet_managed': true } } },
+ ],
+ },
+ },
+ {
+ enabled: true,
+ data_stream: {
+ type: 'synthetics',
+ dataset: 'browser.screenshot',
+ elasticsearch: { privileges: { indices: ['auto_configure', 'create_doc', 'read'] } },
+ },
+ id: 'synthetics/browser-browser.screenshot-77f25200-7cf3-450d-abfa-4f95faae1907-fa2e69b0-dec6-11ed-8746-c5b1a1a12ec1-default',
+ compiled_stream: {
+ processors: [
+ { add_fields: { target: '', fields: { 'monitor.fleet_managed': true } } },
+ ],
+ },
+ },
+ ],
+ },
+ ],
+ is_managed: true,
+ revision: 1,
+ created_at: '2023-04-19T15:35:26.839Z',
+ created_by: 'system',
+ updated_at: '2023-04-19T15:35:26.839Z',
+ updated_by: 'system',
+ },
+ references: [],
+ coreMigrationVersion: '8.8.0',
+ updated_at: '2023-04-19T15:35:26.884Z',
+ created_at: '2023-04-19T15:35:26.884Z',
+ typeMigrationVersion: '8.7.0',
+} as unknown as SavedObjectUnsanitizedDoc;
+export const icmpPolicy = {
+ type: 'ingest-package-policies',
+ id: 'e437c11c-5cb0-42d1-966f-d2ba231550fd-fa2e69b0-dec6-11ed-8746-c5b1a1a12ec1-default',
+ attributes: {
+ name: 'Invalid ICMP monitor with 11 minute schedule-A private location-default',
+ namespace: 'default',
+ package: { name: 'synthetics', title: 'Elastic Synthetics', version: '0.11.8' },
+ enabled: true,
+ policy_id: 'fa2e69b0-dec6-11ed-8746-c5b1a1a12ec1',
+ inputs: [
+ {
+ type: 'synthetics/http',
+ policy_template: 'synthetics',
+ enabled: false,
+ streams: [
+ {
+ enabled: false,
+ data_stream: { type: 'synthetics', dataset: 'http' },
+ vars: {
+ __ui: { type: 'yaml' },
+ enabled: { value: true, type: 'bool' },
+ type: { value: 'http', type: 'text' },
+ name: { type: 'text' },
+ schedule: { value: '"@every 3m"', type: 'text' },
+ urls: { type: 'text' },
+ 'service.name': { type: 'text' },
+ timeout: { type: 'text' },
+ max_redirects: { type: 'integer' },
+ proxy_url: { type: 'text' },
+ tags: { type: 'yaml' },
+ username: { type: 'text' },
+ password: { type: 'password' },
+ 'response.include_headers': { type: 'bool' },
+ 'response.include_body': { type: 'text' },
+ 'check.request.method': { type: 'text' },
+ 'check.request.headers': { type: 'yaml' },
+ 'check.request.body': { type: 'yaml' },
+ 'check.response.status': { type: 'yaml' },
+ 'check.response.headers': { type: 'yaml' },
+ 'check.response.body.positive': { type: 'yaml' },
+ 'check.response.body.negative': { type: 'yaml' },
+ 'ssl.certificate_authorities': { type: 'yaml' },
+ 'ssl.certificate': { type: 'yaml' },
+ 'ssl.key': { type: 'yaml' },
+ 'ssl.key_passphrase': { type: 'text' },
+ 'ssl.verification_mode': { type: 'text' },
+ 'ssl.supported_protocols': { type: 'yaml' },
+ location_name: { value: 'Fleet managed', type: 'text' },
+ id: { type: 'text' },
+ config_id: { type: 'text' },
+ run_once: { value: false, type: 'bool' },
+ origin: { type: 'text' },
+ 'monitor.project.id': { type: 'text' },
+ 'monitor.project.name': { type: 'text' },
+ },
+ id: 'synthetics/http-http-e437c11c-5cb0-42d1-966f-d2ba231550fd-fa2e69b0-dec6-11ed-8746-c5b1a1a12ec1-default',
+ },
+ ],
+ },
+ {
+ type: 'synthetics/tcp',
+ policy_template: 'synthetics',
+ enabled: false,
+ streams: [
+ {
+ enabled: false,
+ data_stream: { type: 'synthetics', dataset: 'tcp' },
+ vars: {
+ __ui: { type: 'yaml' },
+ enabled: { value: true, type: 'bool' },
+ type: { value: 'tcp', type: 'text' },
+ name: { type: 'text' },
+ schedule: { value: '"@every 3m"', type: 'text' },
+ hosts: { type: 'text' },
+ 'service.name': { type: 'text' },
+ timeout: { type: 'text' },
+ proxy_url: { type: 'text' },
+ proxy_use_local_resolver: { value: false, type: 'bool' },
+ tags: { type: 'yaml' },
+ 'check.send': { type: 'text' },
+ 'check.receive': { type: 'text' },
+ 'ssl.certificate_authorities': { type: 'yaml' },
+ 'ssl.certificate': { type: 'yaml' },
+ 'ssl.key': { type: 'yaml' },
+ 'ssl.key_passphrase': { type: 'text' },
+ 'ssl.verification_mode': { type: 'text' },
+ 'ssl.supported_protocols': { type: 'yaml' },
+ location_name: { value: 'Fleet managed', type: 'text' },
+ id: { type: 'text' },
+ config_id: { type: 'text' },
+ run_once: { value: false, type: 'bool' },
+ origin: { type: 'text' },
+ 'monitor.project.id': { type: 'text' },
+ 'monitor.project.name': { type: 'text' },
+ },
+ id: 'synthetics/tcp-tcp-e437c11c-5cb0-42d1-966f-d2ba231550fd-fa2e69b0-dec6-11ed-8746-c5b1a1a12ec1-default',
+ },
+ ],
+ },
+ {
+ type: 'synthetics/icmp',
+ policy_template: 'synthetics',
+ enabled: true,
+ streams: [
+ {
+ enabled: true,
+ data_stream: {
+ type: 'synthetics',
+ dataset: 'icmp',
+ elasticsearch: { privileges: { indices: ['auto_configure', 'create_doc', 'read'] } },
+ },
+ vars: {
+ __ui: { type: 'yaml' },
+ enabled: { value: true, type: 'bool' },
+ type: { value: 'icmp', type: 'text' },
+ name: { value: 'Invalid ICMP monitor with 11 minute schedule', type: 'text' },
+ schedule: { value: '"@every 16m"', type: 'text' },
+ wait: { value: '1s', type: 'text' },
+ hosts: { value: '1.1.1.1', type: 'text' },
+ 'service.name': { value: '', type: 'text' },
+ timeout: { value: '16s', type: 'text' },
+ tags: { value: null, type: 'yaml' },
+ location_name: { value: 'A private location', type: 'text' },
+ id: { value: 'e437c11c-5cb0-42d1-966f-d2ba231550fd', type: 'text' },
+ config_id: { value: 'e437c11c-5cb0-42d1-966f-d2ba231550fd', type: 'text' },
+ run_once: { value: false, type: 'bool' },
+ origin: { value: 'ui', type: 'text' },
+ 'monitor.project.id': { value: null, type: 'text' },
+ 'monitor.project.name': { value: null, type: 'text' },
+ },
+ id: 'synthetics/icmp-icmp-e437c11c-5cb0-42d1-966f-d2ba231550fd-fa2e69b0-dec6-11ed-8746-c5b1a1a12ec1-default',
+ compiled_stream: {
+ __ui: null,
+ type: 'icmp',
+ name: 'Invalid ICMP monitor with 11 minute schedule',
+ id: 'e437c11c-5cb0-42d1-966f-d2ba231550fd',
+ origin: 'ui',
+ 'run_from.id': 'A private location',
+ 'run_from.geo.name': 'A private location',
+ enabled: true,
+ hosts: '1.1.1.1',
+ schedule: '@every 16m',
+ wait: '1s',
+ timeout: '16s',
+ processors: [
+ {
+ add_fields: {
+ target: '',
+ fields: {
+ 'monitor.fleet_managed': true,
+ config_id: 'e437c11c-5cb0-42d1-966f-d2ba231550fd',
+ },
+ },
+ },
+ ],
+ },
+ },
+ ],
+ },
+ {
+ type: 'synthetics/browser',
+ policy_template: 'synthetics',
+ enabled: false,
+ streams: [
+ {
+ enabled: true,
+ data_stream: {
+ type: 'synthetics',
+ dataset: 'browser',
+ elasticsearch: { privileges: { indices: ['auto_configure', 'create_doc', 'read'] } },
+ },
+ vars: {
+ __ui: { type: 'yaml' },
+ enabled: { value: true, type: 'bool' },
+ type: { value: 'browser', type: 'text' },
+ name: { type: 'text' },
+ schedule: { value: '"@every 3m"', type: 'text' },
+ 'service.name': { type: 'text' },
+ timeout: { type: 'text' },
+ tags: { type: 'yaml' },
+ 'source.zip_url.url': { type: 'text' },
+ 'source.zip_url.username': { type: 'text' },
+ 'source.zip_url.folder': { type: 'text' },
+ 'source.zip_url.password': { type: 'password' },
+ 'source.inline.script': { type: 'yaml' },
+ 'source.project.content': { type: 'text' },
+ params: { type: 'yaml' },
+ playwright_options: { type: 'yaml' },
+ screenshots: { type: 'text' },
+ synthetics_args: { type: 'text' },
+ ignore_https_errors: { type: 'bool' },
+ 'throttling.config': { type: 'text' },
+ 'filter_journeys.tags': { type: 'yaml' },
+ 'filter_journeys.match': { type: 'text' },
+ 'source.zip_url.ssl.certificate_authorities': { type: 'yaml' },
+ 'source.zip_url.ssl.certificate': { type: 'yaml' },
+ 'source.zip_url.ssl.key': { type: 'yaml' },
+ 'source.zip_url.ssl.key_passphrase': { type: 'text' },
+ 'source.zip_url.ssl.verification_mode': { type: 'text' },
+ 'source.zip_url.ssl.supported_protocols': { type: 'yaml' },
+ 'source.zip_url.proxy_url': { type: 'text' },
+ location_name: { value: 'Fleet managed', type: 'text' },
+ id: { type: 'text' },
+ config_id: { type: 'text' },
+ run_once: { value: false, type: 'bool' },
+ origin: { type: 'text' },
+ 'monitor.project.id': { type: 'text' },
+ 'monitor.project.name': { type: 'text' },
+ },
+ id: 'synthetics/browser-browser-e437c11c-5cb0-42d1-966f-d2ba231550fd-fa2e69b0-dec6-11ed-8746-c5b1a1a12ec1-default',
+ compiled_stream: {
+ __ui: null,
+ type: 'browser',
+ name: null,
+ 'run_from.id': 'Fleet managed',
+ 'run_from.geo.name': 'Fleet managed',
+ enabled: true,
+ schedule: '@every 3m',
+ timeout: null,
+ throttling: null,
+ processors: [
+ { add_fields: { target: '', fields: { 'monitor.fleet_managed': true } } },
+ ],
+ },
+ },
+ {
+ enabled: true,
+ data_stream: {
+ type: 'synthetics',
+ dataset: 'browser.network',
+ elasticsearch: { privileges: { indices: ['auto_configure', 'create_doc', 'read'] } },
+ },
+ id: 'synthetics/browser-browser.network-e437c11c-5cb0-42d1-966f-d2ba231550fd-fa2e69b0-dec6-11ed-8746-c5b1a1a12ec1-default',
+ compiled_stream: {
+ processors: [
+ { add_fields: { target: '', fields: { 'monitor.fleet_managed': true } } },
+ ],
+ },
+ },
+ {
+ enabled: true,
+ data_stream: {
+ type: 'synthetics',
+ dataset: 'browser.screenshot',
+ elasticsearch: { privileges: { indices: ['auto_configure', 'create_doc', 'read'] } },
+ },
+ id: 'synthetics/browser-browser.screenshot-e437c11c-5cb0-42d1-966f-d2ba231550fd-fa2e69b0-dec6-11ed-8746-c5b1a1a12ec1-default',
+ compiled_stream: {
+ processors: [
+ { add_fields: { target: '', fields: { 'monitor.fleet_managed': true } } },
+ ],
+ },
+ },
+ ],
+ },
+ ],
+ is_managed: true,
+ revision: 1,
+ created_at: '2023-04-19T15:35:53.763Z',
+ created_by: 'system',
+ updated_at: '2023-04-19T15:35:53.763Z',
+ updated_by: 'system',
+ },
+ references: [],
+ coreMigrationVersion: '8.8.0',
+ updated_at: '2023-04-19T15:35:53.793Z',
+ created_at: '2023-04-19T15:35:53.793Z',
+ typeMigrationVersion: '8.7.0',
+} as unknown as SavedObjectUnsanitizedDoc;
+
+export const getBrowserPolicy = (throttling = '5d/3u/20l') =>
+ ({
+ type: 'ingest-package-policies',
+ id: '420754e9-40f2-486c-bc2e-265bafd735c5-fe200580-dee2-11ed-933e-0f85f8c5dd40-default',
+ attributes: {
+ name: 'https://elastic.co-A private location-default',
+ namespace: 'default',
+ package: { name: 'synthetics', title: 'Elastic Synthetics', version: '0.11.8' },
+ enabled: true,
+ policy_id: 'fe200580-dee2-11ed-933e-0f85f8c5dd40',
+ inputs: [
+ {
+ type: 'synthetics/http',
+ policy_template: 'synthetics',
+ enabled: false,
+ streams: [
+ {
+ enabled: false,
+ data_stream: { type: 'synthetics', dataset: 'http' },
+ vars: {
+ __ui: { type: 'yaml' },
+ enabled: { value: true, type: 'bool' },
+ type: { value: 'http', type: 'text' },
+ name: { type: 'text' },
+ schedule: { value: '"@every 3m"', type: 'text' },
+ urls: { type: 'text' },
+ 'service.name': { type: 'text' },
+ timeout: { type: 'text' },
+ max_redirects: { type: 'integer' },
+ proxy_url: { type: 'text' },
+ tags: { type: 'yaml' },
+ username: { type: 'text' },
+ password: { type: 'password' },
+ 'response.include_headers': { type: 'bool' },
+ 'response.include_body': { type: 'text' },
+ 'check.request.method': { type: 'text' },
+ 'check.request.headers': { type: 'yaml' },
+ 'check.request.body': { type: 'yaml' },
+ 'check.response.status': { type: 'yaml' },
+ 'check.response.headers': { type: 'yaml' },
+ 'check.response.body.positive': { type: 'yaml' },
+ 'check.response.body.negative': { type: 'yaml' },
+ 'ssl.certificate_authorities': { type: 'yaml' },
+ 'ssl.certificate': { type: 'yaml' },
+ 'ssl.key': { type: 'yaml' },
+ 'ssl.key_passphrase': { type: 'text' },
+ 'ssl.verification_mode': { type: 'text' },
+ 'ssl.supported_protocols': { type: 'yaml' },
+ location_name: { value: 'Fleet managed', type: 'text' },
+ id: { type: 'text' },
+ config_id: { type: 'text' },
+ run_once: { value: false, type: 'bool' },
+ origin: { type: 'text' },
+ 'monitor.project.id': { type: 'text' },
+ 'monitor.project.name': { type: 'text' },
+ },
+ id: 'synthetics/http-http-420754e9-40f2-486c-bc2e-265bafd735c5-fe200580-dee2-11ed-933e-0f85f8c5dd40-default',
+ },
+ ],
+ },
+ {
+ type: 'synthetics/tcp',
+ policy_template: 'synthetics',
+ enabled: false,
+ streams: [
+ {
+ enabled: false,
+ data_stream: { type: 'synthetics', dataset: 'tcp' },
+ vars: {
+ __ui: { type: 'yaml' },
+ enabled: { value: true, type: 'bool' },
+ type: { value: 'tcp', type: 'text' },
+ name: { type: 'text' },
+ schedule: { value: '"@every 3m"', type: 'text' },
+ hosts: { type: 'text' },
+ 'service.name': { type: 'text' },
+ timeout: { type: 'text' },
+ proxy_url: { type: 'text' },
+ proxy_use_local_resolver: { value: false, type: 'bool' },
+ tags: { type: 'yaml' },
+ 'check.send': { type: 'text' },
+ 'check.receive': { type: 'text' },
+ 'ssl.certificate_authorities': { type: 'yaml' },
+ 'ssl.certificate': { type: 'yaml' },
+ 'ssl.key': { type: 'yaml' },
+ 'ssl.key_passphrase': { type: 'text' },
+ 'ssl.verification_mode': { type: 'text' },
+ 'ssl.supported_protocols': { type: 'yaml' },
+ location_name: { value: 'Fleet managed', type: 'text' },
+ id: { type: 'text' },
+ config_id: { type: 'text' },
+ run_once: { value: false, type: 'bool' },
+ origin: { type: 'text' },
+ 'monitor.project.id': { type: 'text' },
+ 'monitor.project.name': { type: 'text' },
+ },
+ id: 'synthetics/tcp-tcp-420754e9-40f2-486c-bc2e-265bafd735c5-fe200580-dee2-11ed-933e-0f85f8c5dd40-default',
+ },
+ ],
+ },
+ {
+ type: 'synthetics/icmp',
+ policy_template: 'synthetics',
+ enabled: false,
+ streams: [
+ {
+ enabled: false,
+ data_stream: { type: 'synthetics', dataset: 'icmp' },
+ vars: {
+ __ui: { type: 'yaml' },
+ enabled: { value: true, type: 'bool' },
+ type: { value: 'icmp', type: 'text' },
+ name: { type: 'text' },
+ schedule: { value: '"@every 3m"', type: 'text' },
+ wait: { value: '1s', type: 'text' },
+ hosts: { type: 'text' },
+ 'service.name': { type: 'text' },
+ timeout: { type: 'text' },
+ tags: { type: 'yaml' },
+ location_name: { value: 'Fleet managed', type: 'text' },
+ id: { type: 'text' },
+ config_id: { type: 'text' },
+ run_once: { value: false, type: 'bool' },
+ origin: { type: 'text' },
+ 'monitor.project.id': { type: 'text' },
+ 'monitor.project.name': { type: 'text' },
+ },
+ id: 'synthetics/icmp-icmp-420754e9-40f2-486c-bc2e-265bafd735c5-fe200580-dee2-11ed-933e-0f85f8c5dd40-default',
+ },
+ ],
+ },
+ {
+ type: 'synthetics/browser',
+ policy_template: 'synthetics',
+ enabled: true,
+ streams: [
+ {
+ enabled: true,
+ data_stream: {
+ type: 'synthetics',
+ dataset: 'browser',
+ elasticsearch: {
+ privileges: { indices: ['auto_configure', 'create_doc', 'read'] },
+ },
+ },
+ vars: {
+ __ui: {
+ value:
+ '{"script_source":{"is_generated_script":false,"file_name":""},"is_zip_url_tls_enabled":false,"is_tls_enabled":false}',
+ type: 'yaml',
+ },
+ enabled: { value: true, type: 'bool' },
+ type: { value: 'browser', type: 'text' },
+ name: { value: 'https://elastic.co', type: 'text' },
+ schedule: { value: '"@every 2m"', type: 'text' },
+ 'service.name': { value: '', type: 'text' },
+ timeout: { value: null, type: 'text' },
+ tags: { value: null, type: 'yaml' },
+ 'source.zip_url.url': { value: '', type: 'text' },
+ 'source.zip_url.username': { value: '', type: 'text' },
+ 'source.zip_url.folder': { value: '', type: 'text' },
+ 'source.zip_url.password': { value: '', type: 'password' },
+ 'source.inline.script': {
+ value:
+ "\"step('Go to https://elastic.co', async () => {\\n await page.goto('https://elastic.co');\\n});\"",
+ type: 'yaml',
+ },
+ 'source.project.content': { value: '', type: 'text' },
+ params: { value: '', type: 'yaml' },
+ playwright_options: { value: '', type: 'yaml' },
+ screenshots: { value: 'on', type: 'text' },
+ synthetics_args: { value: null, type: 'text' },
+ ignore_https_errors: { value: false, type: 'bool' },
+ 'throttling.config': { value: throttling, type: 'text' },
+ 'filter_journeys.tags': { value: null, type: 'yaml' },
+ 'filter_journeys.match': { value: null, type: 'text' },
+ 'source.zip_url.ssl.certificate_authorities': { value: null, type: 'yaml' },
+ 'source.zip_url.ssl.certificate': { value: null, type: 'yaml' },
+ 'source.zip_url.ssl.key': { value: null, type: 'yaml' },
+ 'source.zip_url.ssl.key_passphrase': { value: null, type: 'text' },
+ 'source.zip_url.ssl.verification_mode': { value: null, type: 'text' },
+ 'source.zip_url.ssl.supported_protocols': { value: null, type: 'yaml' },
+ 'source.zip_url.proxy_url': { value: '', type: 'text' },
+ location_name: { value: 'A private location', type: 'text' },
+ id: { value: '420754e9-40f2-486c-bc2e-265bafd735c5', type: 'text' },
+ config_id: { value: '420754e9-40f2-486c-bc2e-265bafd735c5', type: 'text' },
+ run_once: { value: false, type: 'bool' },
+ origin: { value: 'ui', type: 'text' },
+ 'monitor.project.id': { value: null, type: 'text' },
+ 'monitor.project.name': { value: null, type: 'text' },
+ },
+ id: 'synthetics/browser-browser-420754e9-40f2-486c-bc2e-265bafd735c5-fe200580-dee2-11ed-933e-0f85f8c5dd40-default',
+ compiled_stream: {
+ __ui: {
+ script_source: { is_generated_script: false, file_name: '' },
+ is_zip_url_tls_enabled: false,
+ is_tls_enabled: false,
+ },
+ type: 'browser',
+ name: 'https://elastic.co',
+ id: '420754e9-40f2-486c-bc2e-265bafd735c5',
+ origin: 'ui',
+ 'run_from.id': 'A private location',
+ 'run_from.geo.name': 'A private location',
+ enabled: true,
+ schedule: '@every 2m',
+ timeout: null,
+ throttling: throttling === 'false' ? false : throttling,
+ 'source.inline.script':
+ "step('Go to https://elastic.co', async () => {\n await page.goto('https://elastic.co');\n});",
+ screenshots: 'on',
+ processors: [
+ {
+ add_fields: {
+ target: '',
+ fields: {
+ 'monitor.fleet_managed': true,
+ config_id: '420754e9-40f2-486c-bc2e-265bafd735c5',
+ },
+ },
+ },
+ ],
+ },
+ },
+ {
+ enabled: true,
+ data_stream: {
+ type: 'synthetics',
+ dataset: 'browser.network',
+ elasticsearch: {
+ privileges: { indices: ['auto_configure', 'create_doc', 'read'] },
+ },
+ },
+ id: 'synthetics/browser-browser.network-420754e9-40f2-486c-bc2e-265bafd735c5-fe200580-dee2-11ed-933e-0f85f8c5dd40-default',
+ compiled_stream: {
+ processors: [
+ { add_fields: { target: '', fields: { 'monitor.fleet_managed': true } } },
+ ],
+ },
+ },
+ {
+ enabled: true,
+ data_stream: {
+ type: 'synthetics',
+ dataset: 'browser.screenshot',
+ elasticsearch: {
+ privileges: { indices: ['auto_configure', 'create_doc', 'read'] },
+ },
+ },
+ id: 'synthetics/browser-browser.screenshot-420754e9-40f2-486c-bc2e-265bafd735c5-fe200580-dee2-11ed-933e-0f85f8c5dd40-default',
+ compiled_stream: {
+ processors: [
+ { add_fields: { target: '', fields: { 'monitor.fleet_managed': true } } },
+ ],
+ },
+ },
+ ],
+ },
+ ],
+ is_managed: true,
+ revision: 1,
+ created_at: '2023-04-19T18:55:35.126Z',
+ created_by: 'system',
+ updated_at: '2023-04-19T18:55:35.126Z',
+ updated_by: 'system',
+ },
+ references: [],
+ coreMigrationVersion: '8.8.0',
+ updated_at: '2023-04-19T18:55:35.250Z',
+ created_at: '2023-04-19T18:55:35.250Z',
+ typeMigrationVersion: '8.7.0',
+ } as unknown as SavedObjectUnsanitizedDoc);
diff --git a/x-pack/plugins/fleet/server/saved_objects/migrations/synthetics/index.ts b/x-pack/plugins/fleet/server/saved_objects/migrations/synthetics/index.ts
new file mode 100644
index 0000000000000..b38d03e10a104
--- /dev/null
+++ b/x-pack/plugins/fleet/server/saved_objects/migrations/synthetics/index.ts
@@ -0,0 +1,8 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+export { migratePackagePolicyToV880 } from './to_v8_8_0';
diff --git a/x-pack/plugins/fleet/server/saved_objects/migrations/synthetics/to_v8_8_0.test.ts b/x-pack/plugins/fleet/server/saved_objects/migrations/synthetics/to_v8_8_0.test.ts
new file mode 100644
index 0000000000000..23d7462872c39
--- /dev/null
+++ b/x-pack/plugins/fleet/server/saved_objects/migrations/synthetics/to_v8_8_0.test.ts
@@ -0,0 +1,167 @@
+/*
+ * 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 { SavedObjectMigrationContext } from '@kbn/core/server';
+
+import { getBrowserPolicy, httpPolicy, icmpPolicy, tcpPolicy } from './fixtures/8.7.0';
+
+import { migratePackagePolicyToV880 as migration } from './to_v8_8_0';
+
+describe('8.8.0 Synthetics Package Policy migration', () => {
+ describe('schedule migration', () => {
+ const testSchedules = [
+ ['4', '3'],
+ ['4.5', '5'],
+ ['7', '5'],
+ ['8', '10'],
+ ['9.5', '10'],
+ ['12', '10'],
+ ['13', '15'],
+ ['16', '15'],
+ ['18', '20'],
+ ['21', '20'],
+ ['25', '20'],
+ ['26', '30'],
+ ['31', '30'],
+ ['45', '30'],
+ ['46', '60'],
+ ['61', '60'],
+ ['90', '60'],
+ ['91', '120'],
+ ['121', '120'],
+ ['195', '240'],
+ ['600', '240'],
+ ];
+
+ it.each(testSchedules)('handles a variety of schedules', (invalidSchedule, validSchedule) => {
+ const actual = migration(
+ {
+ ...httpPolicy,
+ attributes: {
+ ...httpPolicy.attributes,
+ inputs: [
+ {
+ ...httpPolicy.attributes.inputs[0],
+ streams: [
+ {
+ ...httpPolicy.attributes.inputs[0].streams[0],
+ vars: {
+ ...httpPolicy.attributes.inputs[0].streams[0].vars,
+ schedule: {
+ value: `"@every ${invalidSchedule}m"`,
+ type: 'text',
+ },
+ },
+ },
+ ],
+ },
+ ],
+ },
+ },
+ {} as SavedObjectMigrationContext
+ );
+ expect(actual.attributes?.inputs[0]?.streams[0]?.vars?.schedule?.value).toEqual(
+ `"@every ${validSchedule}m"`
+ );
+ expect(actual.attributes?.inputs[0]?.streams[0]?.compiled_stream?.schedule).toEqual(
+ `@every ${validSchedule}m`
+ );
+ });
+
+ it('handles browserPolicy with 2 minute', () => {
+ const actual = migration(getBrowserPolicy(), {} as SavedObjectMigrationContext);
+ expect(actual.attributes?.inputs[3]?.streams[0]?.vars?.schedule?.value).toEqual(
+ '"@every 1m"'
+ );
+ expect(actual.attributes?.inputs[3]?.streams[0]?.compiled_stream?.schedule).toEqual(
+ `@every 1m`
+ );
+ });
+
+ it('handles httpPolicy with 4 minute schedule', () => {
+ const actual = migration(httpPolicy, {} as SavedObjectMigrationContext);
+ expect(actual.attributes?.inputs[0]?.streams[0]?.vars?.schedule?.value).toEqual(
+ '"@every 3m"'
+ );
+ expect(actual.attributes?.inputs[0]?.streams[0]?.compiled_stream?.schedule).toEqual(
+ `@every 3m`
+ );
+ });
+
+ it('handles tcp with 8 minute schedule', () => {
+ const actual = migration(tcpPolicy, {} as SavedObjectMigrationContext);
+ expect(actual.attributes?.inputs[1]?.streams[0]?.vars?.schedule?.value).toEqual(
+ '"@every 10m"'
+ );
+ expect(actual.attributes?.inputs[1]?.streams[0]?.compiled_stream?.schedule).toEqual(
+ `@every 10m`
+ );
+ });
+
+ it('handles icmpPolicy with 16 minute schedule', () => {
+ const actual = migration(icmpPolicy, {} as SavedObjectMigrationContext);
+ expect(actual.attributes?.inputs[2]?.streams[0]?.vars?.schedule?.value).toEqual(
+ '"@every 15m"'
+ );
+ expect(actual.attributes?.inputs[2]?.streams[0]?.compiled_stream?.schedule).toEqual(
+ `@every 15m`
+ );
+ });
+ });
+
+ describe('throttling migration', () => {
+ it('handles throtling config for throttling: false', () => {
+ const actual = migration(getBrowserPolicy('false'), {} as SavedObjectMigrationContext);
+ expect(actual.attributes?.inputs[3]?.streams[0]?.vars?.['throttling.config']?.value).toEqual(
+ 'false'
+ );
+ expect(actual.attributes?.inputs[3]?.streams[0]?.compiled_stream?.throttling).toEqual(false);
+ });
+
+ it('handles throttling config for default throttling', () => {
+ const actual = migration(getBrowserPolicy(), {} as SavedObjectMigrationContext);
+ expect(actual.attributes?.inputs[3]?.streams[0]?.vars?.['throttling.config']?.value).toEqual(
+ JSON.stringify({ download: 5, upload: 3, latency: 20 })
+ );
+ expect(actual.attributes?.inputs[3]?.streams[0]?.compiled_stream.throttling).toEqual({
+ download: 5,
+ upload: 3,
+ latency: 20,
+ });
+ });
+
+ it('handles throttling config for custom throttling', () => {
+ const actual = migration(
+ getBrowserPolicy('1.6d/0.75u/150l'),
+ {} as SavedObjectMigrationContext
+ );
+ expect(actual.attributes?.inputs[3]?.streams[0]?.vars?.['throttling.config']?.value).toEqual(
+ JSON.stringify({ download: 1.6, upload: 0.75, latency: 150 })
+ );
+ expect(actual.attributes?.inputs[3]?.streams[0]?.compiled_stream.throttling).toEqual({
+ download: 1.6,
+ upload: 0.75,
+ latency: 150,
+ });
+ });
+
+ it('handles edge cases', () => {
+ const actual = migration(
+ getBrowserPolicy('not a valid value'),
+ {} as SavedObjectMigrationContext
+ );
+ expect(actual.attributes?.inputs[3]?.streams[0]?.vars?.['throttling.config']?.value).toEqual(
+ JSON.stringify({ download: 5, upload: 3, latency: 20 })
+ );
+ expect(actual.attributes?.inputs[3]?.streams[0]?.compiled_stream.throttling).toEqual({
+ download: 5,
+ upload: 3,
+ latency: 20,
+ });
+ });
+ });
+});
diff --git a/x-pack/plugins/fleet/server/saved_objects/migrations/synthetics/to_v8_8_0.ts b/x-pack/plugins/fleet/server/saved_objects/migrations/synthetics/to_v8_8_0.ts
new file mode 100644
index 0000000000000..872313471ae9b
--- /dev/null
+++ b/x-pack/plugins/fleet/server/saved_objects/migrations/synthetics/to_v8_8_0.ts
@@ -0,0 +1,107 @@
+/*
+ * 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 { SavedObjectMigrationFn, SavedObjectUnsanitizedDoc } from '@kbn/core/server';
+
+import type { PackagePolicy } from '../../../../common';
+
+export const ALLOWED_SCHEDULES_IN_MINUTES = [
+ '1',
+ '3',
+ '5',
+ '10',
+ '15',
+ '20',
+ '30',
+ '60',
+ '120',
+ '240',
+];
+
+export const migratePackagePolicyToV880: SavedObjectMigrationFn = (
+ packagePolicyDoc
+) => {
+ if (packagePolicyDoc.attributes.package?.name !== 'synthetics') {
+ return packagePolicyDoc;
+ }
+
+ const updatedPackagePolicyDoc: SavedObjectUnsanitizedDoc = packagePolicyDoc;
+
+ const enabledInput = updatedPackagePolicyDoc.attributes.inputs.find(
+ (input) => input.enabled === true
+ );
+ const enabledStream = enabledInput?.streams.find((stream) => {
+ return ['browser', 'http', 'icmp', 'tcp'].includes(stream.data_stream.dataset);
+ });
+ if (!enabledStream) {
+ return updatedPackagePolicyDoc;
+ }
+
+ if (
+ enabledStream.vars &&
+ enabledStream.vars.schedule?.value &&
+ enabledStream.compiled_stream?.schedule
+ ) {
+ const schedule = enabledStream.vars.schedule.value.match(/\d+\.?\d*/g)?.[0];
+ const updatedSchedule = getNearestSupportedSchedule(schedule);
+ const formattedUpdatedSchedule = `@every ${updatedSchedule}m`;
+ enabledStream.vars.schedule.value = `"${formattedUpdatedSchedule}"`;
+ enabledStream.compiled_stream.schedule = formattedUpdatedSchedule;
+ }
+
+ if (
+ enabledStream.data_stream.dataset === 'browser' &&
+ enabledStream.vars?.['throttling.config'] &&
+ enabledStream.compiled_stream?.throttling
+ ) {
+ const throttling = enabledStream.vars['throttling.config'].value;
+ if (throttling) {
+ const formattedThrottling = handleThrottling(throttling);
+ enabledStream.vars['throttling.config'].value = JSON.stringify(formattedThrottling);
+ enabledStream.compiled_stream.throttling = formattedThrottling;
+ }
+ }
+
+ return updatedPackagePolicyDoc;
+};
+
+const handleThrottling = (
+ throttling: string
+): { download: number; upload: number; latency: number } => {
+ try {
+ const [download = 5, upload = 3, latency = 20] = throttling.match(/\d+\.?\d*/g) || [];
+ return {
+ download: Number(download),
+ upload: Number(upload),
+ latency: Number(latency),
+ };
+ } catch {
+ return {
+ download: 5,
+ upload: 3,
+ latency: 20,
+ };
+ }
+};
+
+const getNearestSupportedSchedule = (currentSchedule: string): string => {
+ try {
+ const closest = ALLOWED_SCHEDULES_IN_MINUTES.reduce(function (prev, curr) {
+ const supportedSchedule = parseFloat(curr);
+ const currSchedule = parseFloat(currentSchedule);
+ const prevSupportedSchedule = parseFloat(prev);
+ return Math.abs(supportedSchedule - currSchedule) <
+ Math.abs(prevSupportedSchedule - currSchedule)
+ ? curr
+ : prev;
+ });
+
+ return closest;
+ } catch {
+ return '10';
+ }
+};
diff --git a/x-pack/plugins/fleet/server/saved_objects/migrations/to_v8_8_0.ts b/x-pack/plugins/fleet/server/saved_objects/migrations/to_v8_8_0.ts
new file mode 100644
index 0000000000000..ab68e4e1c1429
--- /dev/null
+++ b/x-pack/plugins/fleet/server/saved_objects/migrations/to_v8_8_0.ts
@@ -0,0 +1,35 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import type { SavedObjectMigrationFn } from '@kbn/core/server';
+
+import type { PackagePolicy } from '../../../common';
+
+import { migratePackagePolicyToV880 as SecSolMigratePackagePolicyToV880 } from './security_solution';
+import { migratePackagePolicyToV880 as SyntheticsMigratePackagePolicyToV880 } from './synthetics';
+
+export const migratePackagePolicyToV880: SavedObjectMigrationFn = (
+ packagePolicyDoc,
+ migrationContext
+) => {
+ let updatedPackagePolicyDoc = packagePolicyDoc;
+
+ // Endpoint specific migrations
+ if (packagePolicyDoc.attributes.package?.name === 'endpoint') {
+ updatedPackagePolicyDoc = SecSolMigratePackagePolicyToV880(packagePolicyDoc, migrationContext);
+ }
+
+ // Synthetics specific migrations
+ if (packagePolicyDoc.attributes.package?.name === 'synthetics') {
+ updatedPackagePolicyDoc = SyntheticsMigratePackagePolicyToV880(
+ packagePolicyDoc,
+ migrationContext
+ );
+ }
+
+ return updatedPackagePolicyDoc;
+};
diff --git a/x-pack/plugins/fleet/server/types/models/package_policy.ts b/x-pack/plugins/fleet/server/types/models/package_policy.ts
index b94becf1fe449..8e60621102164 100644
--- a/x-pack/plugins/fleet/server/types/models/package_policy.ts
+++ b/x-pack/plugins/fleet/server/types/models/package_policy.ts
@@ -99,6 +99,7 @@ const PackagePolicyBaseSchema = {
namespace: NamespaceSchema,
policy_id: schema.string(),
enabled: schema.boolean(),
+ is_managed: schema.maybe(schema.boolean()),
package: schema.maybe(
schema.object({
name: schema.string(),
diff --git a/x-pack/plugins/synthetics/common/constants/monitor_defaults.ts b/x-pack/plugins/synthetics/common/constants/monitor_defaults.ts
index 6b9cc7913019a..adfa002e8e20a 100644
--- a/x-pack/plugins/synthetics/common/constants/monitor_defaults.ts
+++ b/x-pack/plugins/synthetics/common/constants/monitor_defaults.ts
@@ -48,9 +48,11 @@ export const CUSTOM_LABEL = i18n.translate('xpack.synthetics.connectionProfile.c
defaultMessage: 'Custom',
});
+export const DEFAULT_THROTTLING_VALUE = { download: '5', upload: '3', latency: '20' };
+
export const PROFILE_VALUES: ThrottlingConfig[] = [
{
- value: { download: '5', upload: '3', latency: '20' },
+ value: DEFAULT_THROTTLING_VALUE,
id: PROFILE_VALUES_ENUM.DEFAULT,
label: i18n.translate('xpack.synthetics.connectionProfile.default', {
defaultMessage: 'Default',
diff --git a/x-pack/plugins/synthetics/common/formatters/browser/formatters.test.ts b/x-pack/plugins/synthetics/common/formatters/browser/formatters.test.ts
new file mode 100644
index 0000000000000..7883e0aa09bca
--- /dev/null
+++ b/x-pack/plugins/synthetics/common/formatters/browser/formatters.test.ts
@@ -0,0 +1,70 @@
+/*
+ * 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 { ConfigKey } from '../../runtime_types';
+import { throttlingFormatter } from './formatters';
+
+describe('formatters', () => {
+ describe('throttling formatter', () => {
+ it('formats for no throttling', () => {
+ expect(
+ throttlingFormatter!(
+ {
+ [ConfigKey.THROTTLING_CONFIG]: {
+ value: {
+ download: '0',
+ upload: '0',
+ latency: '0',
+ },
+ label: 'No throttling',
+ id: 'no-throttling',
+ },
+ },
+ ConfigKey.THROTTLING_CONFIG
+ )
+ ).toEqual('false');
+ });
+
+ it('formats for default throttling', () => {
+ expect(
+ throttlingFormatter!(
+ {
+ [ConfigKey.THROTTLING_CONFIG]: {
+ value: {
+ download: '5',
+ upload: '3',
+ latency: '20',
+ },
+ label: 'Default',
+ id: 'default',
+ },
+ },
+ ConfigKey.THROTTLING_CONFIG
+ )
+ ).toEqual(JSON.stringify({ download: 5, upload: 3, latency: 20 }));
+ });
+
+ it('formats for custom throttling', () => {
+ expect(
+ throttlingFormatter!(
+ {
+ [ConfigKey.THROTTLING_CONFIG]: {
+ value: {
+ download: '1.25',
+ upload: '0.75',
+ latency: '150',
+ },
+ label: 'Custom',
+ id: 'custom',
+ },
+ },
+ ConfigKey.THROTTLING_CONFIG
+ )
+ ).toEqual(JSON.stringify({ download: 1.25, upload: 0.75, latency: 150 }));
+ });
+ });
+});
diff --git a/x-pack/plugins/synthetics/common/formatters/browser/formatters.ts b/x-pack/plugins/synthetics/common/formatters/browser/formatters.ts
index 508464c48a5f2..9dfa027767851 100644
--- a/x-pack/plugins/synthetics/common/formatters/browser/formatters.ts
+++ b/x-pack/plugins/synthetics/common/formatters/browser/formatters.ts
@@ -12,6 +12,7 @@ import {
objectToJsonFormatter,
stringToJsonFormatter,
} from '../formatting_utils';
+import { DEFAULT_THROTTLING_VALUE } from '../../constants/monitor_defaults';
import { tlsFormatters } from '../tls/formatters';
@@ -24,7 +25,11 @@ export const throttlingFormatter: Formatter = (fields) => {
return 'false';
}
- return `${throttling.value.download}d/${throttling.value.upload}u/${throttling.value.latency}l`;
+ return JSON.stringify({
+ download: Number(throttling?.value?.download || DEFAULT_THROTTLING_VALUE.download),
+ upload: Number(throttling?.value?.upload || DEFAULT_THROTTLING_VALUE.upload),
+ latency: Number(throttling?.value?.latency || DEFAULT_THROTTLING_VALUE),
+ });
};
export const browserFormatters: BrowserFormatMap = {
diff --git a/x-pack/plugins/synthetics/common/formatters/format_synthetics_policy.test.ts b/x-pack/plugins/synthetics/common/formatters/format_synthetics_policy.test.ts
index 6be823ee0a208..2706972456acd 100644
--- a/x-pack/plugins/synthetics/common/formatters/format_synthetics_policy.test.ts
+++ b/x-pack/plugins/synthetics/common/formatters/format_synthetics_policy.test.ts
@@ -422,7 +422,7 @@ describe('formatSyntheticsPolicy', () => {
},
'throttling.config': {
type: 'text',
- value: '5d/3u/20l',
+ value: JSON.stringify({ download: 5, upload: 3, latency: 20 }),
},
timeout: {
type: 'text',
diff --git a/x-pack/plugins/synthetics/e2e/journeys/synthetics/services/add_monitor.ts b/x-pack/plugins/synthetics/e2e/journeys/synthetics/services/add_monitor.ts
index 44053aa29ed26..fb79e0ec94ff2 100644
--- a/x-pack/plugins/synthetics/e2e/journeys/synthetics/services/add_monitor.ts
+++ b/x-pack/plugins/synthetics/e2e/journeys/synthetics/services/add_monitor.ts
@@ -130,11 +130,15 @@ export const testDataMonitor = {
'filter_journeys.match': '',
'filter_journeys.tags': [],
ignore_https_errors: false,
- 'throttling.is_enabled': true,
- 'throttling.download_speed': '5',
- 'throttling.upload_speed': '3',
- 'throttling.latency': '20',
- 'throttling.config': '5d/3u/20l',
+ throttling: {
+ id: 'custom',
+ label: 'Custom',
+ value: {
+ download: '5',
+ upload: '3',
+ latency: '20',
+ },
+ },
'ssl.certificate_authorities': '',
'ssl.certificate': '',
'ssl.key': '',
diff --git a/x-pack/plugins/synthetics/e2e/tasks/import_monitors.ts b/x-pack/plugins/synthetics/e2e/tasks/import_monitors.ts
index f65d82a9933f4..f7143ee5b89e8 100644
--- a/x-pack/plugins/synthetics/e2e/tasks/import_monitors.ts
+++ b/x-pack/plugins/synthetics/e2e/tasks/import_monitors.ts
@@ -48,11 +48,15 @@ export const importMonitors = async ({
'filter_journeys.match': '',
'filter_journeys.tags': [],
ignore_https_errors: false,
- 'throttling.is_enabled': true,
- 'throttling.download_speed': '5',
- 'throttling.upload_speed': '3',
- 'throttling.latency': '20',
- 'throttling.config': '5d/3u/20l',
+ throttling: {
+ id: 'custom',
+ label: 'Custom',
+ value: {
+ download: '5',
+ upload: '3',
+ latency: '20',
+ },
+ },
};
const id = '1c215bd0-f580-11ec-89e5-694db461b7a5';
diff --git a/x-pack/plugins/synthetics/server/alert_rules/status_rule/status_rule_executor.test.ts b/x-pack/plugins/synthetics/server/alert_rules/status_rule/status_rule_executor.test.ts
index 367c27c4bca08..28d7018448374 100644
--- a/x-pack/plugins/synthetics/server/alert_rules/status_rule/status_rule_executor.test.ts
+++ b/x-pack/plugins/synthetics/server/alert_rules/status_rule/status_rule_executor.test.ts
@@ -169,11 +169,15 @@ const testMonitors = [
'filter_journeys.match': '',
'filter_journeys.tags': [],
ignore_https_errors: false,
- 'throttling.is_enabled': true,
- 'throttling.download_speed': '5',
- 'throttling.upload_speed': '3',
- 'throttling.latency': '20',
- 'throttling.config': '5d/3u/20l',
+ throttling: {
+ id: 'custom',
+ label: 'Custom',
+ value: {
+ download: '5',
+ upload: '3',
+ latency: '20',
+ },
+ },
'ssl.certificate_authorities': '',
'ssl.certificate': '',
'ssl.verification_mode': 'full',
diff --git a/x-pack/plugins/synthetics/server/synthetics_service/private_location/synthetics_private_location.test.ts b/x-pack/plugins/synthetics/server/synthetics_service/private_location/synthetics_private_location.test.ts
index a334b6b406b3b..f1ebab30182ce 100644
--- a/x-pack/plugins/synthetics/server/synthetics_service/private_location/synthetics_private_location.test.ts
+++ b/x-pack/plugins/synthetics/server/synthetics_service/private_location/synthetics_private_location.test.ts
@@ -263,7 +263,7 @@ describe('SyntheticsPrivateLocation', () => {
},
'throttling.config': {
type: 'text',
- value: '5d/3u/20l',
+ value: JSON.stringify({ download: 5, upload: 3, latency: 20 }),
},
timeout: {
type: 'text',
diff --git a/x-pack/test/api_integration/apis/synthetics/sample_data/test_browser_policy.ts b/x-pack/test/api_integration/apis/synthetics/sample_data/test_browser_policy.ts
index 0cebf231cf787..eff1026cb4486 100644
--- a/x-pack/test/api_integration/apis/synthetics/sample_data/test_browser_policy.ts
+++ b/x-pack/test/api_integration/apis/synthetics/sample_data/test_browser_policy.ts
@@ -185,7 +185,10 @@ export const getTestBrowserSyntheticsPolicy = ({
screenshots: { value: 'on', type: 'text' },
synthetics_args: { value: null, type: 'text' },
ignore_https_errors: { value: false, type: 'bool' },
- 'throttling.config': { value: '5d/3u/20l', type: 'text' },
+ 'throttling.config': {
+ value: JSON.stringify({ download: 5, upload: 3, latency: 20 }),
+ type: 'text',
+ },
'filter_journeys.tags': { value: null, type: 'yaml' },
'filter_journeys.match': { value: null, type: 'text' },
'source.zip_url.ssl.certificate_authorities': { type: 'yaml' },
@@ -218,7 +221,7 @@ export const getTestBrowserSyntheticsPolicy = ({
enabled: true,
schedule: '@every 3m',
timeout: '16s',
- throttling: '5d/3u/20l',
+ throttling: { download: 5, upload: 3, latency: 20 },
tags: ['cookie-test', 'browser'],
'source.inline.script':
'step("Visit /users api route", async () => {\\n const response = await page.goto(\'https://nextjs-test-synthetics.vercel.app/api/users\');\\n expect(response.status()).toEqual(200);\\n});',
diff --git a/x-pack/test/api_integration/apis/synthetics/sample_data/test_project_monitor_policy.ts b/x-pack/test/api_integration/apis/synthetics/sample_data/test_project_monitor_policy.ts
index cd5ea22451b92..ba48a26ff50fe 100644
--- a/x-pack/test/api_integration/apis/synthetics/sample_data/test_project_monitor_policy.ts
+++ b/x-pack/test/api_integration/apis/synthetics/sample_data/test_project_monitor_policy.ts
@@ -213,7 +213,10 @@ export const getTestProjectSyntheticsPolicy = (
screenshots: { value: 'on', type: 'text' },
synthetics_args: { value: null, type: 'text' },
ignore_https_errors: { value: false, type: 'bool' },
- 'throttling.config': { value: '5d/3u/20l', type: 'text' },
+ 'throttling.config': {
+ value: JSON.stringify({ download: 5, upload: 3, latency: 20 }),
+ type: 'text',
+ },
'filter_journeys.tags': { value: null, type: 'yaml' },
'filter_journeys.match': { value: '"check if title is present"', type: 'text' },
'source.zip_url.ssl.certificate_authorities': { type: 'yaml' },
@@ -246,7 +249,7 @@ export const getTestProjectSyntheticsPolicy = (
'run_from.geo.name': locationName,
'run_from.id': locationName,
timeout: null,
- throttling: '5d/3u/20l',
+ throttling: { download: 5, upload: 3, latency: 20 },
'source.project.content':
'UEsDBBQACAAIAON5qVQAAAAAAAAAAAAAAAAfAAAAZXhhbXBsZXMvdG9kb3MvYmFzaWMuam91cm5leS50c22Q0WrDMAxF3/sVF7MHB0LMXlc6RvcN+wDPVWNviW0sdUsp/fe5SSiD7UFCWFfHujIGlpnkybwxFTZfoY/E3hsaLEtwhs9RPNWKDU12zAOxkXRIbN4tB9d9pFOJdO6EN2HMqQguWN9asFBuQVMmJ7jiWNII9fIXrbabdUYr58l9IhwhQQZCYORCTFFUC31Btj21NRc7Mq4Nds+4bDD/pNVgT9F52Jyr2Fa+g75LAPttg8yErk+S9ELpTmVotlVwnfNCuh2lepl3+JflUmSBJ3uggt1v9INW/lHNLKze9dJe1J3QJK8pSvWkm6aTtCet5puq+x63+AFQSwcIAPQ3VfcAAACcAQAAUEsBAi0DFAAIAAgA43mpVAD0N1X3AAAAnAEAAB8AAAAAAAAAAAAgAKSBAAAAAGV4YW1wbGVzL3RvZG9zL2Jhc2ljLmpvdXJuZXkudHNQSwUGAAAAAAEAAQBNAAAARAEAAAAA',
playwright_options: { headless: true, chromiumSandbox: false },
From f95ebdfb31fe3d154797c2617fff534f63325370 Mon Sep 17 00:00:00 2001
From: GitStart <1501599+gitstart@users.noreply.github.com>
Date: Fri, 21 Apr 2023 17:02:58 +0300
Subject: [PATCH 14/15] [Synthetics] Filtering - location filter counts are off
when filter is applied (#155437)
Co-authored-by: Anjola Adeuyi <57623705+anjola-adeuyi@users.noreply.github.com>
Co-authored-by: gitstart_bot
Co-authored-by: shahzad31
---
.../common/monitor_filters/filter_group.tsx | 13 ++++++++++++-
.../common/monitor_filters/use_filters.ts | 2 +-
2 files changed, 13 insertions(+), 2 deletions(-)
diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/common/monitor_filters/filter_group.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/common/monitor_filters/filter_group.tsx
index 7e19abbf758ce..a59a26613a427 100644
--- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/common/monitor_filters/filter_group.tsx
+++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/common/monitor_filters/filter_group.tsx
@@ -61,7 +61,18 @@ export const FilterGroup = ({
label: LOCATION_LABEL,
field: 'locations',
values: getSyntheticsFilterDisplayValues(
- mixUrlValues(data.locations, urlParams.locations),
+ mixUrlValues(
+ data.locations.map((locationData) => {
+ const matchingLocation = locations.find(
+ (location) => location.id === locationData.label
+ );
+ return {
+ label: matchingLocation ? matchingLocation.label : locationData.label,
+ count: locationData.count,
+ };
+ }),
+ urlParams.locations
+ ),
'locations',
locations
),
diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/common/monitor_filters/use_filters.ts b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/common/monitor_filters/use_filters.ts
index 014966f31bbc0..0e3fe38566f34 100644
--- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/common/monitor_filters/use_filters.ts
+++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/common/monitor_filters/use_filters.ts
@@ -132,7 +132,7 @@ export const useFilters = (): FiltersList => {
})) ?? [],
schedules:
schedules?.buckets?.map(({ key, doc_count: count }) => ({
- label: key,
+ label: String(key),
count,
})) ?? [],
};
From cfc01d5444610b6d3d01d4178c0a5e5a1f04c5e3 Mon Sep 17 00:00:00 2001
From: Thom Heymann <190132+thomheymann@users.noreply.github.com>
Date: Fri, 21 Apr 2023 15:19:25 +0100
Subject: [PATCH 15/15] Hide remote indices role configuration when not
supported (#155490)
Resolves #155389
## Summary
Adds feature flag to automatically hide remote index privileges section
when not supported by cluster.
---
.../role_mappings/role_mappings_api_client.ts | 3 +-
.../roles/edit_role/edit_role_page.test.tsx | 36 +++++++-
.../roles/edit_role/edit_role_page.tsx | 20 ++++-
.../elasticsearch_privileges.test.tsx.snap | 84 -------------------
.../es/elasticsearch_privileges.test.tsx | 7 +-
.../es/elasticsearch_privileges.tsx | 65 +++++++-------
.../routes/role_mapping/feature_check.test.ts | 30 +++++++
.../routes/role_mapping/feature_check.ts | 4 +
8 files changed, 130 insertions(+), 119 deletions(-)
diff --git a/x-pack/plugins/security/public/management/role_mappings/role_mappings_api_client.ts b/x-pack/plugins/security/public/management/role_mappings/role_mappings_api_client.ts
index 79e2335919b1a..5465bc24b7e31 100644
--- a/x-pack/plugins/security/public/management/role_mappings/role_mappings_api_client.ts
+++ b/x-pack/plugins/security/public/management/role_mappings/role_mappings_api_client.ts
@@ -9,11 +9,12 @@ import type { HttpStart } from '@kbn/core/public';
import type { RoleMapping } from '../../../common/model';
-interface CheckRoleMappingFeaturesResponse {
+export interface CheckRoleMappingFeaturesResponse {
canManageRoleMappings: boolean;
canUseInlineScripts: boolean;
canUseStoredScripts: boolean;
hasCompatibleRealms: boolean;
+ canUseRemoteIndices: boolean;
}
type DeleteRoleMappingsResponse = Array<{
diff --git a/x-pack/plugins/security/public/management/roles/edit_role/edit_role_page.test.tsx b/x-pack/plugins/security/public/management/roles/edit_role/edit_role_page.test.tsx
index 090c7e6854aeb..52e3d768ef07e 100644
--- a/x-pack/plugins/security/public/management/roles/edit_role/edit_role_page.test.tsx
+++ b/x-pack/plugins/security/public/management/roles/edit_role/edit_role_page.test.tsx
@@ -140,11 +140,13 @@ function getProps({
role,
canManageSpaces = true,
spacesEnabled = true,
+ canUseRemoteIndices = true,
}: {
action: 'edit' | 'clone';
role?: Role;
canManageSpaces?: boolean;
spacesEnabled?: boolean;
+ canUseRemoteIndices?: boolean;
}) {
const rolesAPIClient = rolesAPIClientMock.create();
rolesAPIClient.getRole.mockResolvedValue(role);
@@ -171,12 +173,15 @@ function getProps({
const { fatalErrors } = coreMock.createSetup();
const { http, docLinks, notifications } = coreMock.createStart();
http.get.mockImplementation(async (path: any) => {
- if (!spacesEnabled) {
- throw { response: { status: 404 } }; // eslint-disable-line no-throw-literal
- }
if (path === '/api/spaces/space') {
+ if (!spacesEnabled) {
+ throw { response: { status: 404 } }; // eslint-disable-line no-throw-literal
+ }
return buildSpaces();
}
+ if (path === '/internal/security/_check_role_mapping_features') {
+ return { canUseRemoteIndices };
+ }
});
return {
@@ -265,6 +270,8 @@ describe('', () => {
expect(wrapper.find(SpaceAwarePrivilegeSection)).toHaveLength(1);
expect(wrapper.find('[data-test-subj="userCannotManageSpacesCallout"]')).toHaveLength(0);
expect(wrapper.find('input[data-test-subj="roleFormNameInput"]').prop('disabled')).toBe(true);
+ expect(wrapper.find('IndexPrivileges[indexType="indices"]')).toHaveLength(1);
+ expect(wrapper.find('IndexPrivileges[indexType="remote_indices"]')).toHaveLength(1);
expectReadOnlyFormButtons(wrapper);
});
@@ -291,6 +298,8 @@ describe('', () => {
expect(wrapper.find(SpaceAwarePrivilegeSection)).toHaveLength(1);
expect(wrapper.find('[data-test-subj="userCannotManageSpacesCallout"]')).toHaveLength(0);
expect(wrapper.find('input[data-test-subj="roleFormNameInput"]').prop('disabled')).toBe(true);
+ expect(wrapper.find('IndexPrivileges[indexType="indices"]')).toHaveLength(1);
+ expect(wrapper.find('IndexPrivileges[indexType="remote_indices"]')).toHaveLength(1);
expectSaveFormButtons(wrapper);
});
@@ -308,6 +317,8 @@ describe('', () => {
expect(wrapper.find('input[data-test-subj="roleFormNameInput"]').prop('disabled')).toBe(
false
);
+ expect(wrapper.find('IndexPrivileges[indexType="indices"]')).toHaveLength(1);
+ expect(wrapper.find('IndexPrivileges[indexType="remote_indices"]')).toHaveLength(1);
expectSaveFormButtons(wrapper);
});
@@ -480,6 +491,8 @@ describe('', () => {
expect(wrapper.find(SimplePrivilegeSection)).toHaveLength(1);
expect(wrapper.find('[data-test-subj="userCannotManageSpacesCallout"]')).toHaveLength(0);
expect(wrapper.find('input[data-test-subj="roleFormNameInput"]').prop('disabled')).toBe(true);
+ expect(wrapper.find('IndexPrivileges[indexType="indices"]')).toHaveLength(1);
+ expect(wrapper.find('IndexPrivileges[indexType="remote_indices"]')).toHaveLength(1);
expectReadOnlyFormButtons(wrapper);
});
@@ -507,6 +520,8 @@ describe('', () => {
expect(wrapper.find(SimplePrivilegeSection)).toHaveLength(1);
expect(wrapper.find('[data-test-subj="userCannotManageSpacesCallout"]')).toHaveLength(0);
expect(wrapper.find('input[data-test-subj="roleFormNameInput"]').prop('disabled')).toBe(true);
+ expect(wrapper.find('IndexPrivileges[indexType="indices"]')).toHaveLength(1);
+ expect(wrapper.find('IndexPrivileges[indexType="remote_indices"]')).toHaveLength(1);
expectSaveFormButtons(wrapper);
});
@@ -524,6 +539,8 @@ describe('', () => {
expect(wrapper.find('input[data-test-subj="roleFormNameInput"]').prop('disabled')).toBe(
false
);
+ expect(wrapper.find('IndexPrivileges[indexType="indices"]')).toHaveLength(1);
+ expect(wrapper.find('IndexPrivileges[indexType="remote_indices"]')).toHaveLength(1);
expectSaveFormButtons(wrapper);
});
@@ -612,6 +629,19 @@ describe('', () => {
});
});
+ it('hides remote index privileges section when not supported', async () => {
+ const wrapper = mountWithIntl(
+
+
+
+ );
+
+ await waitForRender(wrapper);
+
+ expect(wrapper.find('IndexPrivileges[indexType="indices"]')).toHaveLength(1);
+ expect(wrapper.find('IndexPrivileges[indexType="remote_indices"]')).toHaveLength(0);
+ });
+
it('registers fatal error if features endpoint fails unexpectedly', async () => {
const error = { response: { status: 500 } };
const getFeatures = jest.fn().mockRejectedValue(error);
diff --git a/x-pack/plugins/security/public/management/roles/edit_role/edit_role_page.tsx b/x-pack/plugins/security/public/management/roles/edit_role/edit_role_page.tsx
index e5e5736ca5833..9388ab92a0a76 100644
--- a/x-pack/plugins/security/public/management/roles/edit_role/edit_role_page.tsx
+++ b/x-pack/plugins/security/public/management/roles/edit_role/edit_role_page.tsx
@@ -21,6 +21,7 @@ import {
} from '@elastic/eui';
import type { ChangeEvent, FocusEvent, FunctionComponent, HTMLProps } from 'react';
import React, { Fragment, useCallback, useEffect, useRef, useState } from 'react';
+import useAsync from 'react-use/lib/useAsync';
import type { IHttpFetchError } from '@kbn/core-http-browser';
import type {
@@ -56,6 +57,7 @@ import {
prepareRoleClone,
} from '../../../../common/model';
import { useCapabilities } from '../../../components/use_capabilities';
+import type { CheckRoleMappingFeaturesResponse } from '../../role_mappings/role_mappings_api_client';
import type { UserAPIClient } from '../../users';
import type { IndicesAPIClient } from '../indices_api_client';
import { KibanaPrivileges } from '../model';
@@ -86,6 +88,12 @@ interface Props {
spacesApiUi?: SpacesApiUi;
}
+function useFeatureCheck(http: HttpStart) {
+ return useAsync(() =>
+ http.get('/internal/security/_check_role_mapping_features')
+ );
+}
+
function useRunAsUsers(
userAPIClient: PublicMethodsOf,
fatalErrors: FatalErrorsSetup
@@ -311,6 +319,7 @@ export const EditRolePage: FunctionComponent = ({
const privileges = usePrivileges(privilegesAPIClient, fatalErrors);
const spaces = useSpaces(http, fatalErrors);
const features = useFeatures(getFeatures, fatalErrors);
+ const featureCheckState = useFeatureCheck(http);
const [role, setRole] = useRole(
rolesAPIClient,
fatalErrors,
@@ -329,7 +338,15 @@ export const EditRolePage: FunctionComponent = ({
}
}, [hasReadOnlyPrivileges, isEditingExistingRole]); // eslint-disable-line react-hooks/exhaustive-deps
- if (!role || !runAsUsers || !indexPatternsTitles || !privileges || !spaces || !features) {
+ if (
+ !role ||
+ !runAsUsers ||
+ !indexPatternsTitles ||
+ !privileges ||
+ !spaces ||
+ !features ||
+ !featureCheckState.value
+ ) {
return null;
}
@@ -457,6 +474,7 @@ export const EditRolePage: FunctionComponent = ({
builtinESPrivileges={builtInESPrivileges}
license={license}
docLinks={docLinks}
+ canUseRemoteIndices={featureCheckState.value?.canUseRemoteIndices}
/>
);
diff --git a/x-pack/plugins/security/public/management/roles/edit_role/privileges/es/__snapshots__/elasticsearch_privileges.test.tsx.snap b/x-pack/plugins/security/public/management/roles/edit_role/privileges/es/__snapshots__/elasticsearch_privileges.test.tsx.snap
index 988f463f49fd1..8f160c6e57abc 100644
--- a/x-pack/plugins/security/public/management/roles/edit_role/privileges/es/__snapshots__/elasticsearch_privileges.test.tsx.snap
+++ b/x-pack/plugins/security/public/management/roles/edit_role/privileges/es/__snapshots__/elasticsearch_privileges.test.tsx.snap
@@ -200,89 +200,5 @@ exports[`it renders without crashing 1`] = `
}
}
/>
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
`;
diff --git a/x-pack/plugins/security/public/management/roles/edit_role/privileges/es/elasticsearch_privileges.test.tsx b/x-pack/plugins/security/public/management/roles/edit_role/privileges/es/elasticsearch_privileges.test.tsx
index 13a4143890a86..1a1486f6d82e3 100644
--- a/x-pack/plugins/security/public/management/roles/edit_role/privileges/es/elasticsearch_privileges.test.tsx
+++ b/x-pack/plugins/security/public/management/roles/edit_role/privileges/es/elasticsearch_privileges.test.tsx
@@ -63,8 +63,13 @@ test('it renders index privileges section', () => {
expect(wrapper.find('IndexPrivileges[indexType="indices"]')).toHaveLength(1);
});
-test('it renders remote index privileges section', () => {
+test('it does not render remote index privileges section by default', () => {
const wrapper = shallowWithIntl();
+ expect(wrapper.find('IndexPrivileges[indexType="remote_indices"]')).toHaveLength(0);
+});
+
+test('it renders remote index privileges section when `canUseRemoteIndices` is enabled', () => {
+ const wrapper = shallowWithIntl();
expect(wrapper.find('IndexPrivileges[indexType="remote_indices"]')).toHaveLength(1);
});
diff --git a/x-pack/plugins/security/public/management/roles/edit_role/privileges/es/elasticsearch_privileges.tsx b/x-pack/plugins/security/public/management/roles/edit_role/privileges/es/elasticsearch_privileges.tsx
index 88e0c953711fa..e963c4eda6d92 100644
--- a/x-pack/plugins/security/public/management/roles/edit_role/privileges/es/elasticsearch_privileges.tsx
+++ b/x-pack/plugins/security/public/management/roles/edit_role/privileges/es/elasticsearch_privileges.tsx
@@ -40,6 +40,7 @@ interface Props {
validator: RoleValidator;
builtinESPrivileges: BuiltinESPrivileges;
indexPatterns: string[];
+ canUseRemoteIndices?: boolean;
}
export class ElasticsearchPrivileges extends Component {
@@ -62,6 +63,7 @@ export class ElasticsearchPrivileges extends Component {
indexPatterns,
license,
builtinESPrivileges,
+ canUseRemoteIndices,
} = this.props;
return (
@@ -170,37 +172,42 @@ export class ElasticsearchPrivileges extends Component {
availableIndexPrivileges={builtinESPrivileges.index}
editable={editable}
/>
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+ {this.learnMore(docLinks.links.security.indicesPrivileges)}
+
+
+
- {this.learnMore(docLinks.links.security.indicesPrivileges)}
-
-
-
+ >
+ )}
);
};
diff --git a/x-pack/plugins/security/server/routes/role_mapping/feature_check.test.ts b/x-pack/plugins/security/server/routes/role_mapping/feature_check.test.ts
index 0efe93d21c1b2..ce0a38ef73039 100644
--- a/x-pack/plugins/security/server/routes/role_mapping/feature_check.test.ts
+++ b/x-pack/plugins/security/server/routes/role_mapping/feature_check.test.ts
@@ -21,6 +21,9 @@ interface TestOptions {
}
const defaultXpackUsageResponse = {
+ remote_clusters: {
+ size: 0,
+ },
security: {
realms: {
native: {
@@ -94,6 +97,7 @@ describe('GET role mappings feature check', () => {
canUseInlineScripts: true,
canUseStoredScripts: true,
hasCompatibleRealms: true,
+ canUseRemoteIndices: true,
},
},
});
@@ -117,10 +121,31 @@ describe('GET role mappings feature check', () => {
canUseInlineScripts: true,
canUseStoredScripts: true,
hasCompatibleRealms: true,
+ canUseRemoteIndices: true,
},
},
});
+ getFeatureCheckTest(
+ 'indicates canUseRemoteIndices=false when cluster does not support remote indices',
+ {
+ xpackUsageResponse: () => ({
+ ...defaultXpackUsageResponse,
+ remote_clusters: undefined,
+ }),
+ asserts: {
+ statusCode: 200,
+ result: {
+ canManageRoleMappings: true,
+ canUseInlineScripts: true,
+ canUseStoredScripts: true,
+ hasCompatibleRealms: true,
+ canUseRemoteIndices: false,
+ },
+ },
+ }
+ );
+
getFeatureCheckTest('disallows stored scripts when disabled', {
nodeSettingsResponse: () => ({
nodes: {
@@ -140,6 +165,7 @@ describe('GET role mappings feature check', () => {
canUseInlineScripts: true,
canUseStoredScripts: false,
hasCompatibleRealms: true,
+ canUseRemoteIndices: true,
},
},
});
@@ -163,12 +189,14 @@ describe('GET role mappings feature check', () => {
canUseInlineScripts: false,
canUseStoredScripts: true,
hasCompatibleRealms: true,
+ canUseRemoteIndices: true,
},
},
});
getFeatureCheckTest('indicates incompatible realms when only native and file are enabled', {
xpackUsageResponse: () => ({
+ ...defaultXpackUsageResponse,
security: {
realms: {
native: {
@@ -189,6 +217,7 @@ describe('GET role mappings feature check', () => {
canUseInlineScripts: true,
canUseStoredScripts: true,
hasCompatibleRealms: false,
+ canUseRemoteIndices: true,
},
},
});
@@ -219,6 +248,7 @@ describe('GET role mappings feature check', () => {
canUseInlineScripts: true,
canUseStoredScripts: true,
hasCompatibleRealms: false,
+ canUseRemoteIndices: false,
},
},
}
diff --git a/x-pack/plugins/security/server/routes/role_mapping/feature_check.ts b/x-pack/plugins/security/server/routes/role_mapping/feature_check.ts
index 9715f92cb5a37..309cdfbeab456 100644
--- a/x-pack/plugins/security/server/routes/role_mapping/feature_check.ts
+++ b/x-pack/plugins/security/server/routes/role_mapping/feature_check.ts
@@ -24,6 +24,9 @@ interface NodeSettingsResponse {
}
interface XPackUsageResponse {
+ remote_clusters?: {
+ size: number;
+ };
security: {
realms: {
[realmName: string]: {
@@ -128,6 +131,7 @@ async function getEnabledRoleMappingsFeatures(esClient: ElasticsearchClient, log
hasCompatibleRealms,
canUseStoredScripts,
canUseInlineScripts,
+ canUseRemoteIndices: !!xpackUsage.remote_clusters,
};
}