From c24318ae407376a2528fcf6339e67fcc36f7a8c3 Mon Sep 17 00:00:00 2001
From: Steph Milovic
Date: Tue, 29 Jun 2021 13:53:56 -0600
Subject: [PATCH 001/121] [RAC] [Cases] All cases table column design updates
(#103544)
---
.../public/components/all_cases/actions.tsx | 85 ++----------
.../all_cases/all_cases_generic.test.tsx | 18 +++
.../all_cases/all_cases_generic.tsx | 1 +
.../components/all_cases/columns.test.tsx | 19 +++
.../public/components/all_cases/columns.tsx | 108 ++++++++-------
.../components/all_cases/index.test.tsx | 124 +++++-------------
.../components/all_cases/translations.ts | 13 +-
.../case_action_bar/status_context_menu.tsx | 24 ++--
.../components/edit_connector/index.test.tsx | 5 +
.../components/edit_connector/index.tsx | 14 +-
.../plugins/cases/public/containers/mock.ts | 12 ++
.../server/scripts/mock/case/post_case.json | 3 +-
.../observability_security.ts | 2 +
13 files changed, 194 insertions(+), 234 deletions(-)
diff --git a/x-pack/plugins/cases/public/components/all_cases/actions.tsx b/x-pack/plugins/cases/public/components/all_cases/actions.tsx
index 4820b10308934f..d9e0e0ef025c80 100644
--- a/x-pack/plugins/cases/public/components/all_cases/actions.tsx
+++ b/x-pack/plugins/cases/public/components/all_cases/actions.tsx
@@ -5,87 +5,24 @@
* 2.0.
*/
-import { Dispatch } from 'react';
import { DefaultItemIconButtonAction } from '@elastic/eui/src/components/basic_table/action_types';
-import { CaseStatuses } from '../../../common';
-import { Case, SubCase } from '../../containers/types';
-import { UpdateCase } from '../../containers/use_get_cases';
-import { statuses } from '../status';
+import { Case } from '../../containers/types';
import * as i18n from './translations';
-import { isIndividual } from './helpers';
interface GetActions {
- dispatchUpdate: Dispatch>;
deleteCaseOnClick: (deleteCase: Case) => void;
}
export const getActions = ({
- dispatchUpdate,
deleteCaseOnClick,
-}: GetActions): Array> => {
- const openCaseAction = {
- available: (item: Case | SubCase) => item.status !== CaseStatuses.open,
- enabled: (item: Case | SubCase) => isIndividual(item),
- description: statuses[CaseStatuses.open].actions.single.title,
- icon: statuses[CaseStatuses.open].icon,
- name: statuses[CaseStatuses.open].actions.single.title,
- onClick: (theCase: Case) =>
- dispatchUpdate({
- updateKey: 'status',
- updateValue: CaseStatuses.open,
- caseId: theCase.id,
- version: theCase.version,
- }),
- type: 'icon' as const,
- 'data-test-subj': 'action-open',
- };
-
- const makeInProgressAction = {
- available: (item: Case) => item.status !== CaseStatuses['in-progress'],
- enabled: (item: Case | SubCase) => isIndividual(item),
- description: statuses[CaseStatuses['in-progress']].actions.single.title,
- icon: statuses[CaseStatuses['in-progress']].icon,
- name: statuses[CaseStatuses['in-progress']].actions.single.title,
- onClick: (theCase: Case) =>
- dispatchUpdate({
- updateKey: 'status',
- updateValue: CaseStatuses['in-progress'],
- caseId: theCase.id,
- version: theCase.version,
- }),
- type: 'icon' as const,
- 'data-test-subj': 'action-in-progress',
- };
-
- const closeCaseAction = {
- available: (item: Case | SubCase) => item.status !== CaseStatuses.closed,
- enabled: (item: Case | SubCase) => isIndividual(item),
- description: statuses[CaseStatuses.closed].actions.single.title,
- icon: statuses[CaseStatuses.closed].icon,
- name: statuses[CaseStatuses.closed].actions.single.title,
- onClick: (theCase: Case) =>
- dispatchUpdate({
- updateKey: 'status',
- updateValue: CaseStatuses.closed,
- caseId: theCase.id,
- version: theCase.version,
- }),
- type: 'icon' as const,
- 'data-test-subj': 'action-close',
- };
-
- return [
- openCaseAction,
- makeInProgressAction,
- closeCaseAction,
- {
- description: i18n.DELETE_CASE(),
- icon: 'trash',
- name: i18n.DELETE_CASE(),
- onClick: deleteCaseOnClick,
- type: 'icon',
- 'data-test-subj': 'action-delete',
- },
- ];
-};
+}: GetActions): Array> => [
+ {
+ description: i18n.DELETE_CASE(),
+ icon: 'trash',
+ name: i18n.DELETE_CASE(),
+ onClick: deleteCaseOnClick,
+ type: 'icon',
+ 'data-test-subj': 'action-delete',
+ },
+];
diff --git a/x-pack/plugins/cases/public/components/all_cases/all_cases_generic.test.tsx b/x-pack/plugins/cases/public/components/all_cases/all_cases_generic.test.tsx
index a0a5bb08ef7703..47c683becb244a 100644
--- a/x-pack/plugins/cases/public/components/all_cases/all_cases_generic.test.tsx
+++ b/x-pack/plugins/cases/public/components/all_cases/all_cases_generic.test.tsx
@@ -34,6 +34,24 @@ const alertDataMock = {
alertId: 'alert-id',
owner: SECURITY_SOLUTION_OWNER,
};
+jest.mock('../../common/lib/kibana', () => {
+ const originalModule = jest.requireActual('../../common/lib/kibana');
+ return {
+ ...originalModule,
+ useKibana: () => ({
+ services: {
+ triggersActionsUi: {
+ actionTypeRegistry: {
+ get: jest.fn().mockReturnValue({
+ actionTypeTitle: '.jira',
+ iconClass: 'logoSecurity',
+ }),
+ },
+ },
+ },
+ }),
+ };
+});
describe('AllCasesGeneric ', () => {
beforeEach(() => {
diff --git a/x-pack/plugins/cases/public/components/all_cases/all_cases_generic.tsx b/x-pack/plugins/cases/public/components/all_cases/all_cases_generic.tsx
index 308c2186b52ed2..a6d8afc3b8b230 100644
--- a/x-pack/plugins/cases/public/components/all_cases/all_cases_generic.tsx
+++ b/x-pack/plugins/cases/public/components/all_cases/all_cases_generic.tsx
@@ -199,6 +199,7 @@ export const AllCasesGeneric = React.memo(
isLoadingCases: loading,
refreshCases,
showActions,
+ userCanCrud,
});
const itemIdToExpandedRowMap = useMemo(
diff --git a/x-pack/plugins/cases/public/components/all_cases/columns.test.tsx b/x-pack/plugins/cases/public/components/all_cases/columns.test.tsx
index c7a255da9dda68..0f0189f2d29c27 100644
--- a/x-pack/plugins/cases/public/components/all_cases/columns.test.tsx
+++ b/x-pack/plugins/cases/public/components/all_cases/columns.test.tsx
@@ -13,6 +13,25 @@ import { ExternalServiceColumn } from './columns';
import { useGetCasesMockState } from '../../containers/mock';
+jest.mock('../../common/lib/kibana', () => {
+ const originalModule = jest.requireActual('../../common/lib/kibana');
+ return {
+ ...originalModule,
+ useKibana: () => ({
+ services: {
+ triggersActionsUi: {
+ actionTypeRegistry: {
+ get: jest.fn().mockReturnValue({
+ actionTypeTitle: '.jira',
+ iconClass: 'logoSecurity',
+ }),
+ },
+ },
+ },
+ }),
+ };
+});
+
describe('ExternalServiceColumn ', () => {
it('Not pushed render', () => {
const wrapper = mount(
diff --git a/x-pack/plugins/cases/public/components/all_cases/columns.tsx b/x-pack/plugins/cases/public/components/all_cases/columns.tsx
index a5a299851d975a..ad4447223837c2 100644
--- a/x-pack/plugins/cases/public/components/all_cases/columns.tsx
+++ b/x-pack/plugins/cases/public/components/all_cases/columns.tsx
@@ -16,6 +16,7 @@ import {
EuiTableFieldDataColumnType,
EuiFlexGroup,
EuiFlexItem,
+ EuiIcon,
} from '@elastic/eui';
import { RIGHT_ALIGNMENT } from '@elastic/eui/lib/services';
import styled from 'styled-components';
@@ -25,13 +26,14 @@ import { getEmptyTagValue } from '../empty_value';
import { FormattedRelativePreferenceDate } from '../formatted_date';
import { CaseDetailsHrefSchema, CaseDetailsLink, CasesNavigation } from '../links';
import * as i18n from './translations';
-import { Status } from '../status';
import { getSubCasesStatusCountsBadges, isSubCase } from './helpers';
import { ALERTS } from '../../common/translations';
import { getActions } from './actions';
import { UpdateCase } from '../../containers/use_get_cases';
import { useDeleteCases } from '../../containers/use_delete_cases';
import { ConfirmDeleteCaseModal } from '../confirm_delete_case';
+import { useKibana } from '../../common/lib/kibana';
+import { StatusContextMenu } from '../case_action_bar/status_context_menu';
export type CasesColumns =
| EuiTableActionsColumnType
@@ -62,6 +64,7 @@ export interface GetCasesColumn {
isLoadingCases: string[];
refreshCases?: (a?: boolean) => void;
showActions: boolean;
+ userCanCrud: boolean;
}
export const useCasesColumns = ({
caseDetailsNavigation,
@@ -72,6 +75,7 @@ export const useCasesColumns = ({
isLoadingCases,
refreshCases,
showActions,
+ userCanCrud,
}: GetCasesColumn): CasesColumns[] => {
// Delete case
const {
@@ -113,9 +117,8 @@ export const useCasesColumns = ({
() =>
getActions({
deleteCaseOnClick: toggleDeleteModal,
- dispatchUpdate: handleDispatchUpdate,
}),
- [toggleDeleteModal, handleDispatchUpdate]
+ [toggleDeleteModal]
);
useEffect(() => {
@@ -267,18 +270,6 @@ export const useCasesColumns = ({
return getEmptyTagValue();
},
},
- {
- name: i18n.INCIDENT_MANAGEMENT_SYSTEM,
- render: (theCase: Case) => {
- if (theCase.externalService != null) {
- return renderStringField(
- `${theCase.externalService.connectorName}`,
- `case-table-column-connector`
- );
- }
- return getEmptyTagValue();
- },
- },
{
name: i18n.STATUS,
render: (theCase: Case) => {
@@ -286,7 +277,20 @@ export const useCasesColumns = ({
if (theCase.status == null || theCase.type === CaseType.collection) {
return getEmptyTagValue();
}
- return ;
+ return (
+ 0}
+ onStatusChanged={(status) =>
+ handleDispatchUpdate({
+ updateKey: 'status',
+ updateValue: status,
+ caseId: theCase.id,
+ version: theCase.version,
+ })
+ }
+ />
+ );
}
const badges = getSubCasesStatusCountsBadges(theCase.subCases);
@@ -322,36 +326,48 @@ interface Props {
theCase: Case;
}
+const IconWrapper = styled.span`
+ svg {
+ height: 20px !important;
+ position: relative;
+ top: 3px;
+ width: 20px !important;
+ }
+`;
export const ExternalServiceColumn: React.FC = ({ theCase }) => {
- const handleRenderDataToPush = useCallback(() => {
- const lastCaseUpdate = theCase.updatedAt != null ? new Date(theCase.updatedAt) : null;
- const lastCasePush =
- theCase.externalService?.pushedAt != null
- ? new Date(theCase.externalService?.pushedAt)
- : null;
- const hasDataToPush =
- lastCasePush === null ||
- (lastCasePush != null &&
- lastCaseUpdate != null &&
- lastCasePush.getTime() < lastCaseUpdate?.getTime());
- return (
-
-
- {theCase.externalService?.externalTitle}
-
- {hasDataToPush
- ? renderStringField(i18n.REQUIRES_UPDATE, `case-table-column-external-requiresUpdate`)
- : renderStringField(i18n.UP_TO_DATE, `case-table-column-external-upToDate`)}
-
- );
- }, [theCase]);
- if (theCase.externalService !== null) {
- return handleRenderDataToPush();
+ const { triggersActionsUi } = useKibana().services;
+
+ if (theCase.externalService == null) {
+ return renderStringField(i18n.NOT_PUSHED, `case-table-column-external-notPushed`);
}
- return renderStringField(i18n.NOT_PUSHED, `case-table-column-external-notPushed`);
+
+ const lastCaseUpdate = theCase.updatedAt != null ? new Date(theCase.updatedAt) : null;
+ const lastCasePush =
+ theCase.externalService?.pushedAt != null ? new Date(theCase.externalService?.pushedAt) : null;
+ const hasDataToPush =
+ lastCasePush === null ||
+ (lastCaseUpdate != null && lastCasePush.getTime() < lastCaseUpdate?.getTime());
+ return (
+
+
+
+
+
+ {theCase.externalService?.externalTitle}
+
+ {hasDataToPush
+ ? renderStringField(i18n.REQUIRES_UPDATE, `case-table-column-external-requiresUpdate`)
+ : renderStringField(i18n.UP_TO_DATE, `case-table-column-external-upToDate`)}
+
+ );
};
diff --git a/x-pack/plugins/cases/public/components/all_cases/index.test.tsx b/x-pack/plugins/cases/public/components/all_cases/index.test.tsx
index c3c0f0bf075dd1..4c15550f3ce3c6 100644
--- a/x-pack/plugins/cases/public/components/all_cases/index.test.tsx
+++ b/x-pack/plugins/cases/public/components/all_cases/index.test.tsx
@@ -36,7 +36,24 @@ const useGetCasesStatusMock = useGetCasesStatus as jest.Mock;
const useUpdateCasesMock = useUpdateCases as jest.Mock;
const useGetActionLicenseMock = useGetActionLicense as jest.Mock;
-jest.mock('../../common/lib/kibana');
+jest.mock('../../common/lib/kibana', () => {
+ const originalModule = jest.requireActual('../../common/lib/kibana');
+ return {
+ ...originalModule,
+ useKibana: () => ({
+ services: {
+ triggersActionsUi: {
+ actionTypeRegistry: {
+ get: jest.fn().mockReturnValue({
+ actionTypeTitle: '.jira',
+ iconClass: 'logoSecurity',
+ }),
+ },
+ },
+ },
+ }),
+ };
+});
describe('AllCasesGeneric', () => {
const defaultAllCasesProps: AllCasesProps = {
@@ -119,6 +136,7 @@ describe('AllCasesGeneric', () => {
handleIsLoading: jest.fn(),
isLoadingCases: [],
showActions: true,
+ userCanCrud: true,
};
beforeEach(() => {
@@ -274,7 +292,7 @@ describe('AllCasesGeneric', () => {
});
});
- it('should render correct actions for case (with type individual and filter status open)', async () => {
+ it('should render delete actions for case', async () => {
useGetCasesMock.mockReturnValue({
...defaultGetCases,
filterOptions: { ...defaultGetCases.filterOptions, status: CaseStatuses.open },
@@ -284,18 +302,12 @@ describe('AllCasesGeneric', () => {
);
- wrapper.find('[data-test-subj="euiCollapsedItemActionsButton"]').first().simulate('click');
await waitFor(() => {
- expect(wrapper.find('[data-test-subj="action-open"]').exists()).toBeFalsy();
- expect(
- wrapper.find('[data-test-subj="action-in-progress"]').first().props().disabled
- ).toBeFalsy();
- expect(wrapper.find('[data-test-subj="action-close"]').first().props().disabled).toBeFalsy();
expect(wrapper.find('[data-test-subj="action-delete"]').first().props().disabled).toBeFalsy();
});
});
- it('should enable correct actions for sub cases', async () => {
+ it.skip('should enable correct actions for sub cases', async () => {
useGetCasesMock.mockReturnValue({
...defaultGetCases,
data: {
@@ -327,25 +339,9 @@ describe('AllCasesGeneric', () => {
);
- wrapper
- .find(
- '[data-test-subj="sub-cases-table-my-case-with-subcases"] [data-test-subj="euiCollapsedItemActionsButton"]'
- )
- .last()
- .simulate('click');
-
- await waitFor(() => {
- expect(wrapper.find('[data-test-subj="action-open"]').first().props().disabled).toEqual(true);
- expect(
- wrapper.find('[data-test-subj="action-in-progress"]').first().props().disabled
- ).toEqual(true);
- expect(wrapper.find('[data-test-subj="action-close"]').first().props().disabled).toEqual(
- true
- );
- expect(wrapper.find('[data-test-subj="action-delete"]').first().props().disabled).toEqual(
- false
- );
- });
+ expect(wrapper.find('[data-test-subj="action-delete"]').first().props().disabled).toEqual(
+ false
+ );
});
it('should not render case link when caseDetailsNavigation is not passed or actions on showActions=false', async () => {
@@ -362,6 +358,7 @@ describe('AllCasesGeneric', () => {
filterStatus: CaseStatuses.open,
handleIsLoading: jest.fn(),
showActions: false,
+ userCanCrud: true,
})
);
await waitFor(() => {
@@ -387,14 +384,17 @@ describe('AllCasesGeneric', () => {
});
});
- it('closes case when row action icon clicked', async () => {
+ it('Updates status when status context menu is updated', async () => {
const wrapper = mount(
);
- wrapper.find('[data-test-subj="euiCollapsedItemActionsButton"]').first().simulate('click');
- wrapper.find('[data-test-subj="action-close"]').first().simulate('click');
+ wrapper.find(`[data-test-subj="case-view-status-dropdown"] button`).first().simulate('click');
+ wrapper
+ .find(`[data-test-subj="case-view-status-dropdown-closed"] button`)
+ .first()
+ .simulate('click');
await waitFor(() => {
const firstCase = useGetCasesMockState.data.cases[0];
@@ -409,66 +409,6 @@ describe('AllCasesGeneric', () => {
});
});
- it('opens case when row action icon clicked', async () => {
- useGetCasesMock.mockReturnValue({
- ...defaultGetCases,
- data: {
- ...defaultGetCases.data,
- cases: [
- {
- ...defaultGetCases.data.cases[0],
- status: CaseStatuses.closed,
- },
- ],
- },
- filterOptions: { ...defaultGetCases.filterOptions, status: CaseStatuses.closed },
- });
-
- const wrapper = mount(
-
-
-
- );
-
- wrapper.find('[data-test-subj="euiCollapsedItemActionsButton"]').first().simulate('click');
- wrapper.find('[data-test-subj="action-open"]').first().simulate('click');
-
- await waitFor(() => {
- const firstCase = useGetCasesMockState.data.cases[0];
- expect(dispatchUpdateCaseProperty.mock.calls[0][0]).toEqual(
- expect.objectContaining({
- caseId: firstCase.id,
- updateKey: 'status',
- updateValue: CaseStatuses.open,
- version: firstCase.version,
- })
- );
- });
- });
-
- it('put case in progress when row action icon clicked', async () => {
- const wrapper = mount(
-
-
-
- );
-
- wrapper.find('[data-test-subj="euiCollapsedItemActionsButton"]').first().simulate('click');
- wrapper.find('[data-test-subj="action-in-progress"]').first().simulate('click');
-
- await waitFor(() => {
- const firstCase = useGetCasesMockState.data.cases[0];
- expect(dispatchUpdateCaseProperty.mock.calls[0][0]).toEqual(
- expect.objectContaining({
- caseId: firstCase.id,
- updateKey: 'status',
- updateValue: CaseStatuses['in-progress'],
- version: firstCase.version,
- })
- );
- });
- });
-
it('Bulk delete', async () => {
useGetCasesMock.mockReturnValue({
...defaultGetCases,
@@ -794,7 +734,7 @@ describe('AllCasesGeneric', () => {
closedAt: null,
closedBy: null,
comments: [],
- connector: { fields: null, id: 'none', name: 'My Connector', type: '.none' },
+ connector: { fields: null, id: '123', name: 'My Connector', type: '.jira' },
createdAt: '2020-02-19T23:06:33.798Z',
createdBy: {
email: 'leslie.knope@elastic.co',
diff --git a/x-pack/plugins/cases/public/components/all_cases/translations.ts b/x-pack/plugins/cases/public/components/all_cases/translations.ts
index 8da90f32fabdf2..be1aa256db657a 100644
--- a/x-pack/plugins/cases/public/components/all_cases/translations.ts
+++ b/x-pack/plugins/cases/public/components/all_cases/translations.ts
@@ -90,10 +90,15 @@ export const REFRESH = i18n.translate('xpack.cases.caseTable.refreshTitle', {
defaultMessage: 'Refresh',
});
-export const SERVICENOW_LINK_ARIA = i18n.translate('xpack.cases.caseTable.serviceNowLinkAria', {
- defaultMessage: 'click to view the incident on servicenow',
-});
-
+export const PUSH_LINK_ARIA = (thirdPartyName: string): string =>
+ i18n.translate('xpack.cases.caseTable.pushLinkAria', {
+ values: { thirdPartyName },
+ defaultMessage: 'click to view the incident on { thirdPartyName }.',
+ });
export const STATUS = i18n.translate('xpack.cases.caseTable.status', {
defaultMessage: 'Status',
});
+
+export const CHANGE_STATUS = i18n.translate('xpack.cases.caseTable.changeStatus', {
+ defaultMessage: 'Change status',
+});
diff --git a/x-pack/plugins/cases/public/components/case_action_bar/status_context_menu.tsx b/x-pack/plugins/cases/public/components/case_action_bar/status_context_menu.tsx
index 3ee7ab65902154..603efb253f0517 100644
--- a/x-pack/plugins/cases/public/components/case_action_bar/status_context_menu.tsx
+++ b/x-pack/plugins/cases/public/components/case_action_bar/status_context_menu.tsx
@@ -9,6 +9,7 @@ import React, { memo, useCallback, useMemo, useState } from 'react';
import { EuiPopover, EuiContextMenuPanel, EuiContextMenuItem } from '@elastic/eui';
import { caseStatuses, CaseStatuses } from '../../../common';
import { Status } from '../status';
+import { CHANGE_STATUS } from '../all_cases/translations';
interface Props {
currentStatus: CaseStatuses;
@@ -53,18 +54,17 @@ const StatusContextMenuComponent: React.FC = ({
);
return (
- <>
-
-
-
- >
+
+
+
);
};
diff --git a/x-pack/plugins/cases/public/components/edit_connector/index.test.tsx b/x-pack/plugins/cases/public/components/edit_connector/index.test.tsx
index 29d64afe3284f7..fb45bf6ac3ae0b 100644
--- a/x-pack/plugins/cases/public/components/edit_connector/index.test.tsx
+++ b/x-pack/plugins/cases/public/components/edit_connector/index.test.tsx
@@ -67,6 +67,7 @@ describe('EditConnector ', () => {
);
+ expect(wrapper.find(`[data-test-subj="has-data-to-push-button"]`).exists()).toBeTruthy();
wrapper.find('[data-test-subj="connector-edit"] button').simulate('click');
expect(
@@ -173,6 +174,8 @@ describe('EditConnector ', () => {
await waitFor(() =>
expect(wrapper.find(`[data-test-subj="connector-edit"]`).exists()).toBeFalsy()
);
+
+ expect(wrapper.find(`[data-test-subj="has-data-to-push-button"]`).exists()).toBeFalsy();
});
it('displays the permissions error message when one is provided', async () => {
@@ -191,6 +194,8 @@ describe('EditConnector ', () => {
expect(
wrapper.find(`[data-test-subj="edit-connector-no-connectors-msg"]`).exists()
).toBeFalsy();
+
+ expect(wrapper.find(`[data-test-subj="has-data-to-push-button"]`).exists()).toBeFalsy();
});
});
diff --git a/x-pack/plugins/cases/public/components/edit_connector/index.tsx b/x-pack/plugins/cases/public/components/edit_connector/index.tsx
index fe92bd28ce21c3..0a20d2f5c83037 100644
--- a/x-pack/plugins/cases/public/components/edit_connector/index.tsx
+++ b/x-pack/plugins/cases/public/components/edit_connector/index.tsx
@@ -330,11 +330,15 @@ export const EditConnector = React.memo(
)}
- {pushCallouts == null && !isLoading && !editConnector && (
-
- {pushButton}
-
- )}
+ {pushCallouts == null &&
+ !isLoading &&
+ !editConnector &&
+ userCanCrud &&
+ !permissionsError && (
+
+ {pushButton}
+
+ )}
);
diff --git a/x-pack/plugins/cases/public/containers/mock.ts b/x-pack/plugins/cases/public/containers/mock.ts
index df03311005bdb1..a900010235c9f1 100644
--- a/x-pack/plugins/cases/public/containers/mock.ts
+++ b/x-pack/plugins/cases/public/containers/mock.ts
@@ -176,6 +176,12 @@ export const basicPush = {
export const pushedCase: Case = {
...basicCase,
+ connector: {
+ id: '123',
+ name: 'My Connector',
+ type: ConnectorTypes.jira,
+ fields: null,
+ },
externalService: basicPush,
};
@@ -286,6 +292,12 @@ export const basicPushSnake = {
export const pushedCaseSnake = {
...basicCaseSnake,
+ connector: {
+ id: '123',
+ name: 'My Connector',
+ type: ConnectorTypes.jira,
+ fields: null,
+ },
external_service: basicPushSnake,
};
diff --git a/x-pack/plugins/cases/server/scripts/mock/case/post_case.json b/x-pack/plugins/cases/server/scripts/mock/case/post_case.json
index bed342dd69fe9d..a6e0e750a6683f 100644
--- a/x-pack/plugins/cases/server/scripts/mock/case/post_case.json
+++ b/x-pack/plugins/cases/server/scripts/mock/case/post_case.json
@@ -12,5 +12,6 @@
},
"settings": {
"syncAlerts": true
- }
+ },
+ "owner": "securitySolution"
}
diff --git a/x-pack/test/functional/apps/observability/feature_controls/observability_security.ts b/x-pack/test/functional/apps/observability/feature_controls/observability_security.ts
index f2d78369bafee0..90ccaf7c3df35b 100644
--- a/x-pack/test/functional/apps/observability/feature_controls/observability_security.ts
+++ b/x-pack/test/functional/apps/observability/feature_controls/observability_security.ts
@@ -75,6 +75,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
});
it(`doesn't show read-only badge`, async () => {
+ await PageObjects.common.navigateToActualUrl('observabilityCases');
await PageObjects.observability.expectNoReadOnlyCallout();
});
@@ -142,6 +143,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
});
it(`shows read-only glasses badge`, async () => {
+ await PageObjects.common.navigateToActualUrl('observabilityCases');
await PageObjects.observability.expectReadOnlyGlassesBadge();
});
From 21dad7edb575e3543a3d71d0cd1ce0cc771b8279 Mon Sep 17 00:00:00 2001
From: James Gowdy
Date: Tue, 29 Jun 2021 21:07:07 +0100
Subject: [PATCH 002/121] [ML] Update file data visualizer permissions
(#101169)
* [ML] Update file data visualizer permissions
* adding home bundle
* fixing translations
* removing home from bundles
* switching to current user for analysis
* adding find structure permission check
* clean up
* updating text
* updating maps
* removing has_find_file_structure_permission endpoint
* removing more code
* adding permission error message
* renaming variable
* adding fileUpload:analyzeFile back into ML
* updating error text
* updating snapshots
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
---
.../file_data_visualizer_view.js | 34 ++++++++++++++-----
.../file_error_callouts.tsx | 23 +++++++++++++
.../__snapshots__/oss_features.test.ts.snap | 2 ++
.../plugins/features/server/oss_features.ts | 1 +
x-pack/plugins/file_upload/server/routes.ts | 4 ---
x-pack/plugins/maps/server/plugin.ts | 1 -
.../plugins/ml/common/types/capabilities.ts | 6 +---
7 files changed, 53 insertions(+), 18 deletions(-)
diff --git a/x-pack/plugins/data_visualizer/public/application/file_data_visualizer/components/file_data_visualizer_view/file_data_visualizer_view.js b/x-pack/plugins/data_visualizer/public/application/file_data_visualizer/components/file_data_visualizer_view/file_data_visualizer_view.js
index e36f1040447e71..99b6ef602985f8 100644
--- a/x-pack/plugins/data_visualizer/public/application/file_data_visualizer/components/file_data_visualizer_view/file_data_visualizer_view.js
+++ b/x-pack/plugins/data_visualizer/public/application/file_data_visualizer/components/file_data_visualizer_view/file_data_visualizer_view.js
@@ -15,7 +15,11 @@ import { isEqual } from 'lodash';
import { AboutPanel, LoadingPanel } from '../about_panel';
import { BottomBar } from '../bottom_bar';
import { ResultsView } from '../results_view';
-import { FileCouldNotBeRead, FileTooLarge } from './file_error_callouts';
+import {
+ FileCouldNotBeRead,
+ FileTooLarge,
+ FindFileStructurePermissionDenied,
+} from './file_error_callouts';
import { EditFlyout } from '../edit_flyout';
import { ExplanationFlyout } from '../explanation_flyout';
import { ImportView } from '../import_view';
@@ -50,6 +54,7 @@ export class FileDataVisualizerView extends Component {
isExplanationFlyoutVisible: false,
bottomBarVisible: false,
hasPermissionToImport: false,
+ fileCouldNotBeReadPermissionError: false,
};
this.overrides = {};
@@ -87,6 +92,7 @@ export class FileDataVisualizerView extends Component {
fileSize: 0,
fileTooLarge: false,
fileCouldNotBeRead: false,
+ fileCouldNotBeReadPermissionError: false,
serverError: null,
results: undefined,
explanation: undefined,
@@ -182,17 +188,19 @@ export class FileDataVisualizerView extends Component {
fileCouldNotBeRead: isRetry,
});
} catch (error) {
+ const fileCouldNotBeReadPermissionError = error.body.statusCode === 403;
this.setState({
results: undefined,
explanation: undefined,
loaded: false,
loading: false,
fileCouldNotBeRead: true,
+ fileCouldNotBeReadPermissionError,
serverError: error,
});
// reload the results with the previous overrides
- if (isRetry === false) {
+ if (isRetry === false && fileCouldNotBeReadPermissionError === false) {
this.setState({
loading: true,
loaded: false,
@@ -275,6 +283,7 @@ export class FileDataVisualizerView extends Component {
isExplanationFlyoutVisible,
bottomBarVisible,
hasPermissionToImport,
+ fileCouldNotBeReadPermissionError,
} = this.state;
const fields =
@@ -286,7 +295,12 @@ export class FileDataVisualizerView extends Component {
{mode === MODE.READ && (
<>
- {!loading && !loaded &&
}
+ {!loading && !loaded && (
+
+ )}
{loading &&
}
@@ -296,11 +310,15 @@ export class FileDataVisualizerView extends Component {
{fileCouldNotBeRead && loading === false && (
<>
-
+ {fileCouldNotBeReadPermissionError ? (
+
+ ) : (
+
+ )}
>
)}
diff --git a/x-pack/plugins/data_visualizer/public/application/file_data_visualizer/components/file_data_visualizer_view/file_error_callouts.tsx b/x-pack/plugins/data_visualizer/public/application/file_data_visualizer/components/file_data_visualizer_view/file_error_callouts.tsx
index 7b6378e34e78e8..62d860c1513e85 100644
--- a/x-pack/plugins/data_visualizer/public/application/file_data_visualizer/components/file_data_visualizer_view/file_error_callouts.tsx
+++ b/x-pack/plugins/data_visualizer/public/application/file_data_visualizer/components/file_data_visualizer_view/file_error_callouts.tsx
@@ -146,3 +146,26 @@ export const Explanation: FC<{ error: FindFileStructureErrorResponse }> = ({ err
>
);
};
+
+export const FindFileStructurePermissionDenied: FC = () => {
+ return (
+ <>
+
+ }
+ color="danger"
+ iconType="cross"
+ data-test-subj="dataVisualizerFileStructurePermissionDeniedErrorCallout"
+ >
+
+
+ >
+ );
+};
diff --git a/x-pack/plugins/features/server/__snapshots__/oss_features.test.ts.snap b/x-pack/plugins/features/server/__snapshots__/oss_features.test.ts.snap
index e1e5776d87c756..fe5e4fb4f1e0ed 100644
--- a/x-pack/plugins/features/server/__snapshots__/oss_features.test.ts.snap
+++ b/x-pack/plugins/features/server/__snapshots__/oss_features.test.ts.snap
@@ -671,6 +671,7 @@ Array [
},
},
"api": Array [
+ "fileUpload:analyzeFile",
"store_search_session",
],
"app": Array [
@@ -1199,6 +1200,7 @@ Array [
},
},
"api": Array [
+ "fileUpload:analyzeFile",
"store_search_session",
],
"app": Array [
diff --git a/x-pack/plugins/features/server/oss_features.ts b/x-pack/plugins/features/server/oss_features.ts
index d1e96b5a788ec3..f3fd934b4c3e83 100644
--- a/x-pack/plugins/features/server/oss_features.ts
+++ b/x-pack/plugins/features/server/oss_features.ts
@@ -37,6 +37,7 @@ export const buildOSSFeatures = ({
privileges: {
all: {
app: ['discover', 'kibana'],
+ api: ['fileUpload:analyzeFile'],
catalogue: ['discover'],
savedObject: {
all: ['search', 'query', 'index-pattern'],
diff --git a/x-pack/plugins/file_upload/server/routes.ts b/x-pack/plugins/file_upload/server/routes.ts
index bd3aa2688c735a..c957916e7f3210 100644
--- a/x-pack/plugins/file_upload/server/routes.ts
+++ b/x-pack/plugins/file_upload/server/routes.ts
@@ -141,7 +141,6 @@ export function fileUploadRoutes(coreSetup: CoreSetup
, logge
accepts: ['application/json'],
maxBytes: MAX_FILE_SIZE_BYTES,
},
- tags: ['access:fileUpload:import'],
},
},
async (context, request, response) => {
@@ -185,9 +184,6 @@ export function fileUploadRoutes(coreSetup: CoreSetup, logge
validate: {
body: schema.object({ index: schema.string() }),
},
- options: {
- tags: ['access:fileUpload:import'],
- },
},
async (context, request, response) => {
try {
diff --git a/x-pack/plugins/maps/server/plugin.ts b/x-pack/plugins/maps/server/plugin.ts
index c5fc602864f965..1119be32a04def 100644
--- a/x-pack/plugins/maps/server/plugin.ts
+++ b/x-pack/plugins/maps/server/plugin.ts
@@ -185,7 +185,6 @@ export class MapsPlugin implements Plugin {
catalogue: [APP_ID],
privileges: {
all: {
- api: ['fileUpload:import'],
app: [APP_ID, 'kibana'],
catalogue: [APP_ID],
savedObject: {
diff --git a/x-pack/plugins/ml/common/types/capabilities.ts b/x-pack/plugins/ml/common/types/capabilities.ts
index 3545a85305c178..ef8d35e52a9516 100644
--- a/x-pack/plugins/ml/common/types/capabilities.ts
+++ b/x-pack/plugins/ml/common/types/capabilities.ts
@@ -105,11 +105,7 @@ export function getPluginPrivileges() {
return {
admin: {
...privilege,
- api: [
- 'fileUpload:import',
- 'fileUpload:analyzeFile',
- ...allMlCapabilitiesKeys.map((k) => `ml:${k}`),
- ],
+ api: ['fileUpload:analyzeFile', ...allMlCapabilitiesKeys.map((k) => `ml:${k}`)],
catalogue: [PLUGIN_ID, `${PLUGIN_ID}_file_data_visualizer`],
ui: allMlCapabilitiesKeys,
savedObject: {
From fea63a2d36727838fa6ce335fbbb1c3eb2282505 Mon Sep 17 00:00:00 2001
From: Ashokaditya
Date: Tue, 29 Jun 2021 22:08:15 +0200
Subject: [PATCH 003/121] [Security Solution][Endpoint] Include actions and
responses for endpoints only (#103159)
---
.../data_generators/fleet_action_generator.ts | 2 +-
.../common/endpoint/index_data.ts | 6 +-
.../pages/endpoint_hosts/store/middleware.ts | 6 +-
.../endpoint/routes/actions/audit_log.test.ts | 12 +-
.../server/endpoint/routes/actions/service.ts | 154 +++++++++++-------
5 files changed, 104 insertions(+), 76 deletions(-)
diff --git a/x-pack/plugins/security_solution/common/endpoint/data_generators/fleet_action_generator.ts b/x-pack/plugins/security_solution/common/endpoint/data_generators/fleet_action_generator.ts
index 6cc5ab7f084476..22d81ba4a34580 100644
--- a/x-pack/plugins/security_solution/common/endpoint/data_generators/fleet_action_generator.ts
+++ b/x-pack/plugins/security_solution/common/endpoint/data_generators/fleet_action_generator.ts
@@ -43,7 +43,7 @@ export class FleetActionGenerator extends BaseDataGenerator {
return merge(this.generate({ data: { command: 'unisolate' } }), overrides);
}
- /** Generates an action response */
+ /** Generates an endpoint action response */
generateResponse(overrides: DeepPartial = {}): EndpointActionResponse {
const timeStamp = new Date();
diff --git a/x-pack/plugins/security_solution/common/endpoint/index_data.ts b/x-pack/plugins/security_solution/common/endpoint/index_data.ts
index 959db0d964aaec..66a3efa82347ec 100644
--- a/x-pack/plugins/security_solution/common/endpoint/index_data.ts
+++ b/x-pack/plugins/security_solution/common/endpoint/index_data.ts
@@ -421,7 +421,7 @@ const indexFleetActionsForHost = async (
action.agents = [agentId];
- await esClient.index(
+ esClient.index(
{
index: AGENT_ACTIONS_INDEX,
body: action,
@@ -436,7 +436,7 @@ const indexFleetActionsForHost = async (
action_data: action.data,
});
- await esClient.index(
+ esClient.index(
{
index: AGENT_ACTIONS_RESULTS_INDEX,
body: actionResponse,
@@ -449,7 +449,7 @@ const indexFleetActionsForHost = async (
if (fleetActionGenerator.randomFloat() < 0.3) {
const randomFloat = fleetActionGenerator.randomFloat();
- // 60% of the time just add either an Isoalte -OR- an UnIsolate action
+ // 60% of the time just add either an Isolate -OR- an UnIsolate action
if (randomFloat < 0.6) {
let action: EndpointAction;
diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts
index 53b30aeb02bd53..ffeef3f7bf5e18 100644
--- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts
+++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts
@@ -412,9 +412,11 @@ export const endpointMiddlewareFactory: ImmutableMiddlewareFactory
+ new Date(b.item.data['@timestamp']) > new Date(a.item.data['@timestamp']) ? 1 : -1
+ );
const updatedLogData = {
page: activityLog.page,
diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/actions/audit_log.test.ts b/x-pack/plugins/security_solution/server/endpoint/routes/actions/audit_log.test.ts
index 9b737217753824..c7f07151f87246 100644
--- a/x-pack/plugins/security_solution/server/endpoint/routes/actions/audit_log.test.ts
+++ b/x-pack/plugins/security_solution/server/endpoint/routes/actions/audit_log.test.ts
@@ -157,20 +157,14 @@ describe('Action Log API', () => {
it('should have actions and action responses', async () => {
havingActionsAndResponses(
- [
- aMockAction().withAgent(mockID).withAction('isolate'),
- aMockAction().withAgent(mockID).withAction('unisolate'),
- aMockAction().withAgent(mockID).withAction('isolate'),
- ],
- [aMockResponse(actionID, mockID), aMockResponse(actionID, mockID)]
+ [aMockAction().withAgent(mockID).withAction('isolate').withID(actionID)],
+ [aMockResponse(actionID, mockID)]
);
const response = await getActivityLog();
const responseBody = response.ok.mock.calls[0][0]?.body as ActivityLog;
expect(response.ok).toBeCalled();
- expect(responseBody.data).toHaveLength(5);
- expect(responseBody.data.filter((x: any) => x.type === 'response')).toHaveLength(2);
- expect(responseBody.data.filter((x: any) => x.type === 'action')).toHaveLength(3);
+ expect(responseBody.data).toHaveLength(2);
});
it('should throw errors when no results for some agentID', async () => {
diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/actions/service.ts b/x-pack/plugins/security_solution/server/endpoint/routes/actions/service.ts
index 1a8b17bf19e18f..7a82a56b1f19bc 100644
--- a/x-pack/plugins/security_solution/server/endpoint/routes/actions/service.ts
+++ b/x-pack/plugins/security_solution/server/endpoint/routes/actions/service.ts
@@ -5,43 +5,10 @@
* 2.0.
*/
-import { Logger } from 'kibana/server';
-import type { estypes } from '@elastic/elasticsearch';
+import { ElasticsearchClient, Logger } from 'kibana/server';
import { AGENT_ACTIONS_INDEX, AGENT_ACTIONS_RESULTS_INDEX } from '../../../../../fleet/common';
import { SecuritySolutionRequestHandlerContext } from '../../../types';
-
-export const getAuditLogESQuery = ({
- elasticAgentId,
- from,
- size,
-}: {
- elasticAgentId: string;
- from: number;
- size: number;
-}): estypes.SearchRequest => {
- return {
- index: [AGENT_ACTIONS_INDEX, AGENT_ACTIONS_RESULTS_INDEX],
- size,
- from,
- body: {
- query: {
- bool: {
- should: [
- { terms: { agents: [elasticAgentId] } },
- { terms: { agent_id: [elasticAgentId] } },
- ],
- },
- },
- sort: [
- {
- '@timestamp': {
- order: 'desc',
- },
- },
- ],
- },
- };
-};
+import { ActivityLog, EndpointAction } from '../../../../common/endpoint/types';
export const getAuditLogResponse = async ({
elasticAgentId,
@@ -58,48 +25,113 @@ export const getAuditLogResponse = async ({
}): Promise<{
page: number;
pageSize: number;
- data: Array<{
- type: 'action' | 'response';
- item: {
- id: string;
- data: unknown;
- };
- }>;
+ data: ActivityLog['data'];
}> => {
- const size = pageSize;
- const from = page <= 1 ? 0 : page * pageSize - pageSize + 1;
+ const size = Math.floor(pageSize / 2);
+ const from = page <= 1 ? 0 : page * size - size + 1;
+ const esClient = context.core.elasticsearch.client.asCurrentUser;
+
+ const data = await getActivityLog({ esClient, from, size, elasticAgentId, logger });
+
+ return {
+ page,
+ pageSize,
+ data,
+ };
+};
+const getActivityLog = async ({
+ esClient,
+ size,
+ from,
+ elasticAgentId,
+ logger,
+}: {
+ esClient: ElasticsearchClient;
+ elasticAgentId: string;
+ size: number;
+ from: number;
+ logger: Logger;
+}) => {
const options = {
headers: {
'X-elastic-product-origin': 'fleet',
},
ignore: [404],
};
- const esClient = context.core.elasticsearch.client.asCurrentUser;
- let result;
- const params = getAuditLogESQuery({
- elasticAgentId,
- from,
- size,
- });
+
+ let actionsResult;
+ let responsesResult;
try {
- result = await esClient.search(params, options);
+ actionsResult = await esClient.search(
+ {
+ index: AGENT_ACTIONS_INDEX,
+ size,
+ from,
+ body: {
+ query: {
+ bool: {
+ filter: [
+ { term: { agents: elasticAgentId } },
+ { term: { input_type: 'endpoint' } },
+ { term: { type: 'INPUT_ACTION' } },
+ ],
+ },
+ },
+ sort: [
+ {
+ '@timestamp': {
+ order: 'desc',
+ },
+ },
+ ],
+ },
+ },
+ options
+ );
+ const actionIds = actionsResult?.body?.hits?.hits?.map(
+ (e) => (e._source as EndpointAction).action_id
+ );
+
+ responsesResult = await esClient.search(
+ {
+ index: AGENT_ACTIONS_RESULTS_INDEX,
+ size: 1000,
+ body: {
+ query: {
+ bool: {
+ filter: [{ term: { agent_id: elasticAgentId } }, { terms: { action_id: actionIds } }],
+ },
+ },
+ },
+ },
+ options
+ );
} catch (error) {
logger.error(error);
throw error;
}
- if (result?.statusCode !== 200) {
+ if (actionsResult?.statusCode !== 200) {
logger.error(`Error fetching actions log for agent_id ${elasticAgentId}`);
throw new Error(`Error fetching actions log for agent_id ${elasticAgentId}`);
}
- return {
- page,
- pageSize,
- data: result.body.hits.hits.map((e) => ({
- type: e._index.startsWith('.fleet-actions') ? 'action' : 'response',
- item: { id: e._id, data: e._source },
- })),
- };
+ const responses = responsesResult?.body?.hits?.hits?.length
+ ? responsesResult?.body?.hits?.hits?.map((e) => ({
+ type: 'response',
+ item: { id: e._id, data: e._source },
+ }))
+ : [];
+ const actions = actionsResult?.body?.hits?.hits?.length
+ ? actionsResult?.body?.hits?.hits?.map((e) => ({
+ type: 'action',
+ item: { id: e._id, data: e._source },
+ }))
+ : [];
+ const sortedData = ([...responses, ...actions] as ActivityLog['data']).sort((a, b) =>
+ new Date(b.item.data['@timestamp']) > new Date(a.item.data['@timestamp']) ? 1 : -1
+ );
+
+ return sortedData;
};
From fa92958c74136ad61d9fe9d0d0f8e8571d2f8de6 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Ece=20=C3=96zalp?=
Date: Tue, 29 Jun 2021 16:21:18 -0400
Subject: [PATCH 004/121] adds hasBorder to ThreatIntelPanelView (#103726)
---
.../components/overview_cti_links/threat_intel_panel_view.tsx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/x-pack/plugins/security_solution/public/overview/components/overview_cti_links/threat_intel_panel_view.tsx b/x-pack/plugins/security_solution/public/overview/components/overview_cti_links/threat_intel_panel_view.tsx
index 9e8375e2630885..4565c16bc2bf65 100644
--- a/x-pack/plugins/security_solution/public/overview/components/overview_cti_links/threat_intel_panel_view.tsx
+++ b/x-pack/plugins/security_solution/public/overview/components/overview_cti_links/threat_intel_panel_view.tsx
@@ -133,7 +133,7 @@ export const ThreatIntelPanelView: React.FC = ({
>
-
+
<>{button}>
From 682d969190f70cff3a753cf786a92600ac0c394f Mon Sep 17 00:00:00 2001
From: Tiago Costa
Date: Tue, 29 Jun 2021 21:22:45 +0100
Subject: [PATCH 005/121] chore(NA): remove unused transient dep declaration
from @kbn/ui-shared-deps build file (#103666)
---
packages/kbn-ui-shared-deps/BUILD.bazel | 2 --
1 file changed, 2 deletions(-)
diff --git a/packages/kbn-ui-shared-deps/BUILD.bazel b/packages/kbn-ui-shared-deps/BUILD.bazel
index c04f88a42cce03..9096905a2586be 100644
--- a/packages/kbn-ui-shared-deps/BUILD.bazel
+++ b/packages/kbn-ui-shared-deps/BUILD.bazel
@@ -50,8 +50,6 @@ SRC_DEPS = [
"@npm//fflate",
"@npm//jquery",
"@npm//loader-utils",
- # TODO: we can remove this once EUI patches the dependencies
- "@npm//mdast-util-to-hast",
"@npm//mini-css-extract-plugin",
"@npm//moment",
"@npm//moment-timezone",
From b9bbfa3695cac04544c04dfe4ecf14ea7236b963 Mon Sep 17 00:00:00 2001
From: Michael Marcialis
Date: Tue, 29 Jun 2021 16:41:45 -0400
Subject: [PATCH 006/121] [Lens] Formula Icon, Button, Height Design Update
(#103682)
* apply `fullScreenEdit` icon; fix docs button size
* fix small viewport height flex issue
---
.../definitions/formula/editor/formula.scss | 15 +++++++++------
.../definitions/formula/editor/formula_editor.tsx | 4 +---
2 files changed, 10 insertions(+), 9 deletions(-)
diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/formula/editor/formula.scss b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/formula/editor/formula.scss
index 14b3fc33efb4e6..d66e19bec8a1c0 100644
--- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/formula/editor/formula.scss
+++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/formula/editor/formula.scss
@@ -47,8 +47,16 @@
}
.lnsFormula__editorContent {
+ min-height: 0;
position: relative;
- height: 201px;
+
+ .lnsIndexPatternDimensionEditor:not(.lnsIndexPatternDimensionEditor-isFullscreen) & {
+ height: 200px;
+ }
+
+ .lnsIndexPatternDimensionEditor-isFullscreen & {
+ flex: 1;
+ }
}
.lnsFormula__editorPlaceholder {
@@ -62,11 +70,6 @@
pointer-events: none;
}
-.lnsIndexPatternDimensionEditor-isFullscreen .lnsFormula__editorContent {
- flex: 1;
- min-height: 201px;
-}
-
.lnsFormula__warningText + .lnsFormula__warningText {
margin-top: $euiSizeS;
border-top: $euiBorderThin;
diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/formula/editor/formula_editor.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/formula/editor/formula_editor.tsx
index c83135536343d0..97eaf7604df831 100644
--- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/formula/editor/formula_editor.tsx
+++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/formula/editor/formula_editor.tsx
@@ -622,7 +622,6 @@ export function FormulaEditor({
- {/* TODO: Replace `bolt` with `fullScreenExit` icon (after latest EUI is deployed). */}
{
toggleFullscreen();
@@ -630,7 +629,7 @@ export function FormulaEditor({
setIsHelpOpen(!isFullscreen);
trackUiEvent('toggle_formula_fullscreen');
}}
- iconType={isFullscreen ? 'bolt' : 'fullScreen'}
+ iconType={isFullscreen ? 'fullScreenExit' : 'fullScreen'}
size="xs"
color="text"
flush="right"
@@ -758,7 +757,6 @@ export function FormulaEditor({
}}
iconType="documentation"
color="text"
- size="s"
aria-label={i18n.translate(
'xpack.lens.formula.editorHelpInlineShowToolTip',
{
From ebf9e5df76db6f1e59c9bcf4a6f449abda07a213 Mon Sep 17 00:00:00 2001
From: Lukas Olson
Date: Tue, 29 Jun 2021 14:36:18 -0700
Subject: [PATCH 007/121] Use new terms enum API for autocomplete value
suggestions (#100174)
* Migrate kibana.autocomplete config to data plugin
* Fix CI
* Fix tests
* Use new terms enum API for autocomplete value suggestions
* Add tiers to config
* Re-introduce terms agg and add config/tests for swapping algorithms
* Add data_content and data_cold tiers by default
* Fix types
* Fix maps test
* Update tests
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
---
src/plugins/data/config.ts | 15 +++
.../server/autocomplete/terms_agg.test.ts | 89 ++++++++++++++
.../data/server/autocomplete/terms_agg.ts | 106 +++++++++++++++++
.../server/autocomplete/terms_enum.test.ts | 74 ++++++++++++
.../data/server/autocomplete/terms_enum.ts | 62 ++++++++++
.../autocomplete/value_suggestions_route.ts | 112 ++++--------------
.../functional/apps/maps/vector_styling.js | 2 +-
7 files changed, 372 insertions(+), 88 deletions(-)
create mode 100644 src/plugins/data/server/autocomplete/terms_agg.test.ts
create mode 100644 src/plugins/data/server/autocomplete/terms_agg.ts
create mode 100644 src/plugins/data/server/autocomplete/terms_enum.test.ts
create mode 100644 src/plugins/data/server/autocomplete/terms_enum.ts
diff --git a/src/plugins/data/config.ts b/src/plugins/data/config.ts
index 1b7bfbc09ad162..a2b5a568b70ef3 100644
--- a/src/plugins/data/config.ts
+++ b/src/plugins/data/config.ts
@@ -15,6 +15,21 @@ export const configSchema = schema.object({
}),
valueSuggestions: schema.object({
enabled: schema.boolean({ defaultValue: true }),
+ method: schema.oneOf([schema.literal('terms_enum'), schema.literal('terms_agg')], {
+ defaultValue: 'terms_enum',
+ }),
+ tiers: schema.arrayOf(
+ schema.oneOf([
+ schema.literal('data_content'),
+ schema.literal('data_hot'),
+ schema.literal('data_warm'),
+ schema.literal('data_cold'),
+ schema.literal('data_frozen'),
+ ]),
+ {
+ defaultValue: ['data_hot', 'data_warm', 'data_content', 'data_cold'],
+ }
+ ),
terminateAfter: schema.duration({ defaultValue: 100000 }),
timeout: schema.duration({ defaultValue: 1000 }),
}),
diff --git a/src/plugins/data/server/autocomplete/terms_agg.test.ts b/src/plugins/data/server/autocomplete/terms_agg.test.ts
new file mode 100644
index 00000000000000..e4652c2c422e22
--- /dev/null
+++ b/src/plugins/data/server/autocomplete/terms_agg.test.ts
@@ -0,0 +1,89 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+import { coreMock } from '../../../../core/server/mocks';
+import { ElasticsearchClient, SavedObjectsClientContract } from 'kibana/server';
+import { ConfigSchema } from '../../config';
+import type { DeeplyMockedKeys } from '@kbn/utility-types/jest';
+import type { ApiResponse } from '@elastic/elasticsearch';
+import { termsAggSuggestions } from './terms_agg';
+import { SearchResponse } from 'elasticsearch';
+import { duration } from 'moment';
+
+let savedObjectsClientMock: jest.Mocked;
+let esClientMock: DeeplyMockedKeys;
+const configMock = ({
+ autocomplete: {
+ valueSuggestions: { timeout: duration(4513), terminateAfter: duration(98430) },
+ },
+} as unknown) as ConfigSchema;
+const mockResponse = {
+ body: {
+ aggregations: {
+ suggestions: {
+ buckets: [{ key: 'whoa' }, { key: 'amazing' }],
+ },
+ },
+ },
+} as ApiResponse>;
+
+describe('terms agg suggestions', () => {
+ beforeEach(() => {
+ const requestHandlerContext = coreMock.createRequestHandlerContext();
+ savedObjectsClientMock = requestHandlerContext.savedObjects.client;
+ esClientMock = requestHandlerContext.elasticsearch.client.asCurrentUser;
+ esClientMock.search.mockResolvedValue(mockResponse);
+ });
+
+ it('calls the _search API with a terms agg with the given args', async () => {
+ const result = await termsAggSuggestions(
+ configMock,
+ savedObjectsClientMock,
+ esClientMock,
+ 'index',
+ 'fieldName',
+ 'query',
+ [],
+ { name: 'field_name', type: 'string' }
+ );
+
+ const [[args]] = esClientMock.search.mock.calls;
+
+ expect(args).toMatchInlineSnapshot(`
+ Object {
+ "body": Object {
+ "aggs": Object {
+ "suggestions": Object {
+ "terms": Object {
+ "execution_hint": "map",
+ "field": "field_name",
+ "include": "query.*",
+ "shard_size": 10,
+ },
+ },
+ },
+ "query": Object {
+ "bool": Object {
+ "filter": Array [],
+ },
+ },
+ "size": 0,
+ "terminate_after": 98430,
+ "timeout": "4513ms",
+ },
+ "index": "index",
+ }
+ `);
+ expect(result).toMatchInlineSnapshot(`
+ Array [
+ "whoa",
+ "amazing",
+ ]
+ `);
+ });
+});
diff --git a/src/plugins/data/server/autocomplete/terms_agg.ts b/src/plugins/data/server/autocomplete/terms_agg.ts
new file mode 100644
index 00000000000000..b902bae49898fc
--- /dev/null
+++ b/src/plugins/data/server/autocomplete/terms_agg.ts
@@ -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 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+import { get, map } from 'lodash';
+import { ElasticsearchClient, SavedObjectsClientContract } from 'kibana/server';
+import { estypes } from '@elastic/elasticsearch';
+import { ConfigSchema } from '../../config';
+import { IFieldType } from '../../common';
+import { findIndexPatternById, getFieldByName } from '../index_patterns';
+import { shimAbortSignal } from '../search';
+
+export async function termsAggSuggestions(
+ config: ConfigSchema,
+ savedObjectsClient: SavedObjectsClientContract,
+ esClient: ElasticsearchClient,
+ index: string,
+ fieldName: string,
+ query: string,
+ filters?: estypes.QueryDslQueryContainer[],
+ field?: IFieldType,
+ abortSignal?: AbortSignal
+) {
+ const autocompleteSearchOptions = {
+ timeout: `${config.autocomplete.valueSuggestions.timeout.asMilliseconds()}ms`,
+ terminate_after: config.autocomplete.valueSuggestions.terminateAfter.asMilliseconds(),
+ };
+
+ if (!field?.name && !field?.type) {
+ const indexPattern = await findIndexPatternById(savedObjectsClient, index);
+
+ field = indexPattern && getFieldByName(fieldName, indexPattern);
+ }
+
+ const body = await getBody(autocompleteSearchOptions, field ?? fieldName, query, filters);
+
+ const promise = esClient.search({ index, body });
+ const result = await shimAbortSignal(promise, abortSignal);
+
+ const buckets =
+ get(result.body, 'aggregations.suggestions.buckets') ||
+ get(result.body, 'aggregations.nestedSuggestions.suggestions.buckets');
+
+ return map(buckets ?? [], 'key');
+}
+
+async function getBody(
+ // eslint-disable-next-line @typescript-eslint/naming-convention
+ { timeout, terminate_after }: Record,
+ field: IFieldType | string,
+ query: string,
+ filters: estypes.QueryDslQueryContainer[] = []
+) {
+ const isFieldObject = (f: any): f is IFieldType => Boolean(f && f.name);
+
+ // https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-regexp-query.html#_standard_operators
+ const getEscapedQuery = (q: string = '') =>
+ q.replace(/[.?+*|{}[\]()"\\#@&<>~]/g, (match) => `\\${match}`);
+
+ // Helps ensure that the regex is not evaluated eagerly against the terms dictionary
+ const executionHint = 'map' as const;
+
+ // We don't care about the accuracy of the counts, just the content of the terms, so this reduces
+ // the amount of information that needs to be transmitted to the coordinating node
+ const shardSize = 10;
+ const body = {
+ size: 0,
+ timeout,
+ terminate_after,
+ query: {
+ bool: {
+ filter: filters,
+ },
+ },
+ aggs: {
+ suggestions: {
+ terms: {
+ field: isFieldObject(field) ? field.name : field,
+ include: `${getEscapedQuery(query)}.*`,
+ execution_hint: executionHint,
+ shard_size: shardSize,
+ },
+ },
+ },
+ };
+
+ if (isFieldObject(field) && field.subType && field.subType.nested) {
+ return {
+ ...body,
+ aggs: {
+ nestedSuggestions: {
+ nested: {
+ path: field.subType.nested.path,
+ },
+ aggs: body.aggs,
+ },
+ },
+ };
+ }
+
+ return body;
+}
diff --git a/src/plugins/data/server/autocomplete/terms_enum.test.ts b/src/plugins/data/server/autocomplete/terms_enum.test.ts
new file mode 100644
index 00000000000000..be8f179db29c05
--- /dev/null
+++ b/src/plugins/data/server/autocomplete/terms_enum.test.ts
@@ -0,0 +1,74 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+import { termsEnumSuggestions } from './terms_enum';
+import { coreMock } from '../../../../core/server/mocks';
+import { ElasticsearchClient, SavedObjectsClientContract } from 'kibana/server';
+import { ConfigSchema } from '../../config';
+import type { DeeplyMockedKeys } from '@kbn/utility-types/jest';
+import type { ApiResponse } from '@elastic/elasticsearch';
+
+let savedObjectsClientMock: jest.Mocked;
+let esClientMock: DeeplyMockedKeys;
+const configMock = {
+ autocomplete: { valueSuggestions: { tiers: ['data_hot', 'data_warm', 'data_content'] } },
+} as ConfigSchema;
+const mockResponse = {
+ body: { terms: ['whoa', 'amazing'] },
+};
+
+describe('_terms_enum suggestions', () => {
+ beforeEach(() => {
+ const requestHandlerContext = coreMock.createRequestHandlerContext();
+ savedObjectsClientMock = requestHandlerContext.savedObjects.client;
+ esClientMock = requestHandlerContext.elasticsearch.client.asCurrentUser;
+ esClientMock.transport.request.mockResolvedValue((mockResponse as unknown) as ApiResponse);
+ });
+
+ it('calls the _terms_enum API with the field, query, filters, and config tiers', async () => {
+ const result = await termsEnumSuggestions(
+ configMock,
+ savedObjectsClientMock,
+ esClientMock,
+ 'index',
+ 'fieldName',
+ 'query',
+ [],
+ { name: 'field_name', type: 'string' }
+ );
+
+ const [[args]] = esClientMock.transport.request.mock.calls;
+
+ expect(args).toMatchInlineSnapshot(`
+ Object {
+ "body": Object {
+ "field": "field_name",
+ "index_filter": Object {
+ "bool": Object {
+ "must": Array [
+ Object {
+ "terms": Object {
+ "_tier": Array [
+ "data_hot",
+ "data_warm",
+ "data_content",
+ ],
+ },
+ },
+ ],
+ },
+ },
+ "string": "query",
+ },
+ "method": "POST",
+ "path": "/index/_terms_enum",
+ }
+ `);
+ expect(result).toEqual(mockResponse.body.terms);
+ });
+});
diff --git a/src/plugins/data/server/autocomplete/terms_enum.ts b/src/plugins/data/server/autocomplete/terms_enum.ts
new file mode 100644
index 00000000000000..c2452b0a099d04
--- /dev/null
+++ b/src/plugins/data/server/autocomplete/terms_enum.ts
@@ -0,0 +1,62 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+import { ElasticsearchClient, SavedObjectsClientContract } from 'kibana/server';
+import { estypes } from '@elastic/elasticsearch';
+import { IFieldType } from '../../common';
+import { findIndexPatternById, getFieldByName } from '../index_patterns';
+import { shimAbortSignal } from '../search';
+import { getKbnServerError } from '../../../kibana_utils/server';
+import { ConfigSchema } from '../../config';
+
+export async function termsEnumSuggestions(
+ config: ConfigSchema,
+ savedObjectsClient: SavedObjectsClientContract,
+ esClient: ElasticsearchClient,
+ index: string,
+ fieldName: string,
+ query: string,
+ filters?: estypes.QueryDslQueryContainer[],
+ field?: IFieldType,
+ abortSignal?: AbortSignal
+) {
+ const { tiers } = config.autocomplete.valueSuggestions;
+ if (!field?.name && !field?.type) {
+ const indexPattern = await findIndexPatternById(savedObjectsClient, index);
+ field = indexPattern && getFieldByName(fieldName, indexPattern);
+ }
+
+ try {
+ const promise = esClient.transport.request({
+ method: 'POST',
+ path: encodeURI(`/${index}/_terms_enum`),
+ body: {
+ field: field?.name ?? field,
+ string: query,
+ index_filter: {
+ bool: {
+ must: [
+ ...(filters ?? []),
+ {
+ terms: {
+ _tier: tiers,
+ },
+ },
+ ],
+ },
+ },
+ },
+ });
+
+ const result = await shimAbortSignal(promise, abortSignal);
+
+ return result.body.terms;
+ } catch (e) {
+ throw getKbnServerError(e);
+ }
+}
diff --git a/src/plugins/data/server/autocomplete/value_suggestions_route.ts b/src/plugins/data/server/autocomplete/value_suggestions_route.ts
index 8fa14f8cbbd427..bd622d0151c93e 100644
--- a/src/plugins/data/server/autocomplete/value_suggestions_route.ts
+++ b/src/plugins/data/server/autocomplete/value_suggestions_route.ts
@@ -6,17 +6,15 @@
* Side Public License, v 1.
*/
-import { get, map } from 'lodash';
import { schema } from '@kbn/config-schema';
import { IRouter } from 'kibana/server';
-
import { Observable } from 'rxjs';
import { first } from 'rxjs/operators';
-import type { estypes } from '@elastic/elasticsearch';
-import type { IFieldType } from '../index';
-import { findIndexPatternById, getFieldByName } from '../index_patterns';
import { getRequestAbortedSignal } from '../lib';
-import { ConfigSchema } from '../../config';
+import { getKbnServerError } from '../../../kibana_utils/server';
+import type { ConfigSchema } from '../../config';
+import { termsEnumSuggestions } from './terms_enum';
+import { termsAggSuggestions } from './terms_agg';
export function registerValueSuggestionsRoute(router: IRouter, config$: Observable) {
router.post(
@@ -44,88 +42,28 @@ export function registerValueSuggestionsRoute(router: IRouter, config$: Observab
const config = await config$.pipe(first()).toPromise();
const { field: fieldName, query, filters, fieldMeta } = request.body;
const { index } = request.params;
- const { client } = context.core.elasticsearch.legacy;
- const signal = getRequestAbortedSignal(request.events.aborted$);
-
- const autocompleteSearchOptions = {
- timeout: `${config.autocomplete.valueSuggestions.timeout.asMilliseconds()}ms`,
- terminate_after: config.autocomplete.valueSuggestions.terminateAfter.asMilliseconds(),
- };
-
- let field: IFieldType | undefined = fieldMeta;
-
- if (!field?.name && !field?.type) {
- const indexPattern = await findIndexPatternById(context.core.savedObjects.client, index);
-
- field = indexPattern && getFieldByName(fieldName, indexPattern);
+ const abortSignal = getRequestAbortedSignal(request.events.aborted$);
+
+ try {
+ const fn =
+ config.autocomplete.valueSuggestions.method === 'terms_enum'
+ ? termsEnumSuggestions
+ : termsAggSuggestions;
+ const body = await fn(
+ config,
+ context.core.savedObjects.client,
+ context.core.elasticsearch.client.asCurrentUser,
+ index,
+ fieldName,
+ query,
+ filters,
+ fieldMeta,
+ abortSignal
+ );
+ return response.ok({ body });
+ } catch (e) {
+ throw getKbnServerError(e);
}
-
- const body = await getBody(autocompleteSearchOptions, field || fieldName, query, filters);
-
- const result = await client.callAsCurrentUser('search', { index, body }, { signal });
-
- const buckets: any[] =
- get(result, 'aggregations.suggestions.buckets') ||
- get(result, 'aggregations.nestedSuggestions.suggestions.buckets');
-
- return response.ok({ body: map(buckets || [], 'key') });
}
);
}
-
-async function getBody(
- // eslint-disable-next-line @typescript-eslint/naming-convention
- { timeout, terminate_after }: Record,
- field: IFieldType | string,
- query: string,
- filters: estypes.QueryDslQueryContainer[] = []
-) {
- const isFieldObject = (f: any): f is IFieldType => Boolean(f && f.name);
-
- // https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-regexp-query.html#_standard_operators
- const getEscapedQuery = (q: string = '') =>
- q.replace(/[.?+*|{}[\]()"\\#@&<>~]/g, (match) => `\\${match}`);
-
- // Helps ensure that the regex is not evaluated eagerly against the terms dictionary
- const executionHint = 'map' as const;
-
- // We don't care about the accuracy of the counts, just the content of the terms, so this reduces
- // the amount of information that needs to be transmitted to the coordinating node
- const shardSize = 10;
- const body = {
- size: 0,
- timeout,
- terminate_after,
- query: {
- bool: {
- filter: filters,
- },
- },
- aggs: {
- suggestions: {
- terms: {
- field: isFieldObject(field) ? field.name : field,
- include: `${getEscapedQuery(query)}.*`,
- execution_hint: executionHint,
- shard_size: shardSize,
- },
- },
- },
- };
-
- if (isFieldObject(field) && field.subType && field.subType.nested) {
- return {
- ...body,
- aggs: {
- nestedSuggestions: {
- nested: {
- path: field.subType.nested.path,
- },
- aggs: body.aggs,
- },
- },
- };
- }
-
- return body;
-}
diff --git a/x-pack/test/functional/apps/maps/vector_styling.js b/x-pack/test/functional/apps/maps/vector_styling.js
index f669416530c4e5..8b9e429242ca0e 100644
--- a/x-pack/test/functional/apps/maps/vector_styling.js
+++ b/x-pack/test/functional/apps/maps/vector_styling.js
@@ -34,7 +34,7 @@ export default function ({ getService, getPageObjects }) {
await PageObjects.maps.setStyleByValue('fillColor', 'machine.os.raw');
await PageObjects.maps.selectCustomColorRamp('fillColor');
const suggestions = await PageObjects.maps.getCategorySuggestions();
- expect(suggestions.trim().split('\n').join()).to.equal('win 8,win xp,win 7,ios,osx');
+ expect(suggestions.trim().split('\n').join()).to.equal('ios,osx,win 7,win 8,win xp');
});
});
});
From 7359fabf7c8350eaa40d8b753aec5236c8a2a34e Mon Sep 17 00:00:00 2001
From: Jonathan Budzenski
Date: Tue, 29 Jun 2021 18:17:51 -0500
Subject: [PATCH 008/121] [build] Remove OSS builds (#103685)
---
.../development-package-tests.asciidoc | 2 +-
src/dev/build/README.md | 4 +-
src/dev/build/args.test.ts | 14 ---
src/dev/build/args.ts | 5 -
src/dev/build/build_distributables.ts | 4 -
src/dev/build/cli.ts | 2 -
src/dev/build/lib/build.test.ts | 39 ++-----
src/dev/build/lib/build.ts | 12 +-
src/dev/build/lib/runner.test.ts | 109 +-----------------
src/dev/build/lib/runner.ts | 11 +-
.../tasks/build_kibana_platform_plugins.ts | 1 -
src/dev/build/tasks/build_packages_task.ts | 2 -
src/dev/build/tasks/create_archives_task.ts | 4 +-
src/dev/build/tasks/install_chromium.js | 24 ++--
src/dev/build/tasks/license_file_task.ts | 16 +--
.../os_packages/create_os_package_tasks.ts | 39 +++----
.../tasks/os_packages/docker_generator/run.ts | 12 +-
src/dev/build/tasks/os_packages/run_fpm.ts | 12 +-
test/scripts/jenkins_baseline.sh | 2 +-
test/scripts/jenkins_build_kibana.sh | 2 +-
test/scripts/jenkins_build_load_testing.sh | 2 +-
test/scripts/jenkins_xpack_baseline.sh | 2 +-
test/scripts/jenkins_xpack_package_build.sh | 2 +-
23 files changed, 67 insertions(+), 255 deletions(-)
diff --git a/docs/developer/contributing/development-package-tests.asciidoc b/docs/developer/contributing/development-package-tests.asciidoc
index 10c09d6cae8c07..7883ce2d832096 100644
--- a/docs/developer/contributing/development-package-tests.asciidoc
+++ b/docs/developer/contributing/development-package-tests.asciidoc
@@ -36,7 +36,7 @@ pip3 install --user ansible
```
# Build distributions
-node scripts/build --all-platforms --debug --no-oss
+node scripts/build --all-platforms --debug
cd test/package
diff --git a/src/dev/build/README.md b/src/dev/build/README.md
index f6e11af67da33f..c499ef4a610832 100644
--- a/src/dev/build/README.md
+++ b/src/dev/build/README.md
@@ -12,8 +12,8 @@ node scripts/build --help
# build a release version
node scripts/build --release
-# reuse already downloaded node executables, turn on debug logging, and only build the default distributable
-node scripts/build --skip-node-download --debug --no-oss
+# reuse already downloaded node executables, turn on debug logging
+node scripts/build --skip-node-download --debug
```
# Fixing out of memory issues
diff --git a/src/dev/build/args.test.ts b/src/dev/build/args.test.ts
index e749af73241cf1..555df8981d70fb 100644
--- a/src/dev/build/args.test.ts
+++ b/src/dev/build/args.test.ts
@@ -26,8 +26,6 @@ it('build default and oss dist for current platform, without packages, by defaul
expect(readCliArgs(['node', 'scripts/build'])).toMatchInlineSnapshot(`
Object {
"buildOptions": Object {
- "buildDefaultDist": true,
- "buildOssDist": true,
"createArchives": true,
"createDebPackage": false,
"createDockerCentOS": false,
@@ -53,8 +51,6 @@ it('builds packages if --all-platforms is passed', () => {
expect(readCliArgs(['node', 'scripts/build', '--all-platforms'])).toMatchInlineSnapshot(`
Object {
"buildOptions": Object {
- "buildDefaultDist": true,
- "buildOssDist": true,
"createArchives": true,
"createDebPackage": true,
"createDockerCentOS": true,
@@ -80,8 +76,6 @@ it('limits packages if --rpm passed with --all-platforms', () => {
expect(readCliArgs(['node', 'scripts/build', '--all-platforms', '--rpm'])).toMatchInlineSnapshot(`
Object {
"buildOptions": Object {
- "buildDefaultDist": true,
- "buildOssDist": true,
"createArchives": true,
"createDebPackage": false,
"createDockerCentOS": false,
@@ -107,8 +101,6 @@ it('limits packages if --deb passed with --all-platforms', () => {
expect(readCliArgs(['node', 'scripts/build', '--all-platforms', '--deb'])).toMatchInlineSnapshot(`
Object {
"buildOptions": Object {
- "buildDefaultDist": true,
- "buildOssDist": true,
"createArchives": true,
"createDebPackage": true,
"createDockerCentOS": false,
@@ -135,8 +127,6 @@ it('limits packages if --docker passed with --all-platforms', () => {
.toMatchInlineSnapshot(`
Object {
"buildOptions": Object {
- "buildDefaultDist": true,
- "buildOssDist": true,
"createArchives": true,
"createDebPackage": false,
"createDockerCentOS": true,
@@ -170,8 +160,6 @@ it('limits packages if --docker passed with --skip-docker-ubi and --all-platform
).toMatchInlineSnapshot(`
Object {
"buildOptions": Object {
- "buildDefaultDist": true,
- "buildOssDist": true,
"createArchives": true,
"createDebPackage": false,
"createDockerCentOS": true,
@@ -198,8 +186,6 @@ it('limits packages if --all-platforms passed with --skip-docker-centos', () =>
.toMatchInlineSnapshot(`
Object {
"buildOptions": Object {
- "buildDefaultDist": true,
- "buildOssDist": true,
"createArchives": true,
"createDebPackage": true,
"createDockerCentOS": false,
diff --git a/src/dev/build/args.ts b/src/dev/build/args.ts
index bbfbd3e6f8813a..08a375e8011e69 100644
--- a/src/dev/build/args.ts
+++ b/src/dev/build/args.ts
@@ -15,8 +15,6 @@ export function readCliArgs(argv: string[]) {
const unknownFlags: string[] = [];
const flags = getopts(argv, {
boolean: [
- 'oss',
- 'no-oss',
'skip-archives',
'skip-initialize',
'skip-generic-folders',
@@ -48,7 +46,6 @@ export function readCliArgs(argv: string[]) {
rpm: null,
deb: null,
'docker-images': null,
- oss: null,
'version-qualifier': '',
},
unknown: (flag) => {
@@ -94,8 +91,6 @@ export function readCliArgs(argv: string[]) {
const buildOptions: BuildOptions = {
isRelease: Boolean(flags.release),
versionQualifier: flags['version-qualifier'],
- buildOssDist: flags.oss !== false,
- buildDefaultDist: !flags.oss,
initialize: !Boolean(flags['skip-initialize']),
downloadFreshNode: !Boolean(flags['skip-node-download']),
createGenericFolders: !Boolean(flags['skip-generic-folders']),
diff --git a/src/dev/build/build_distributables.ts b/src/dev/build/build_distributables.ts
index f0403fac1e26b3..159281ed71db0e 100644
--- a/src/dev/build/build_distributables.ts
+++ b/src/dev/build/build_distributables.ts
@@ -13,8 +13,6 @@ import * as Tasks from './tasks';
export interface BuildOptions {
isRelease: boolean;
- buildOssDist: boolean;
- buildDefaultDist: boolean;
downloadFreshNode: boolean;
initialize: boolean;
createGenericFolders: boolean;
@@ -37,8 +35,6 @@ export async function buildDistributables(log: ToolingLog, options: BuildOptions
const run = createRunner({
config,
log,
- buildDefaultDist: options.buildDefaultDist,
- buildOssDist: options.buildOssDist,
});
/**
diff --git a/src/dev/build/cli.ts b/src/dev/build/cli.ts
index 3712e2230296a1..c727c26d7dcd30 100644
--- a/src/dev/build/cli.ts
+++ b/src/dev/build/cli.ts
@@ -33,8 +33,6 @@ if (showHelp) {
build the Kibana distributable
options:
- --oss {dim Only produce the OSS distributable of Kibana}
- --no-oss {dim Only produce the default distributable of Kibana}
--skip-archives {dim Don't produce tar/zip archives}
--skip-os-packages {dim Don't produce rpm/deb/docker packages}
--all-platforms {dim Produce archives for all platforms, not just this one}
diff --git a/src/dev/build/lib/build.test.ts b/src/dev/build/lib/build.test.ts
index fe328329d7df03..f92a91e2b7221e 100644
--- a/src/dev/build/lib/build.test.ts
+++ b/src/dev/build/lib/build.test.ts
@@ -43,40 +43,24 @@ beforeEach(() => {
jest.clearAllMocks();
});
-const ossBuild = new Build(config, true);
-const defaultBuild = new Build(config, false);
-
-describe('#isOss()', () => {
- it('returns true for oss', () => {
- expect(ossBuild.isOss()).toBe(true);
- });
-
- it('returns false for default build', () => {
- expect(defaultBuild.isOss()).toBe(false);
- });
-});
+const defaultBuild = new Build(config);
describe('#getName()', () => {
it('returns kibana for default build', () => {
expect(defaultBuild.getName()).toBe('kibana');
});
-
- it('returns kibana-oss for oss', () => {
- expect(ossBuild.getName()).toBe('kibana-oss');
- });
});
describe('#getLogTag()', () => {
it('returns string with build name in it', () => {
expect(defaultBuild.getLogTag()).toContain(defaultBuild.getName());
- expect(ossBuild.getLogTag()).toContain(ossBuild.getName());
});
});
describe('#resolvePath()', () => {
it('uses passed config to resolve a path relative to the repo', () => {
- expect(ossBuild.resolvePath('bar')).toMatchInlineSnapshot(
- `/build/kibana-oss/bar`
+ expect(defaultBuild.resolvePath('bar')).toMatchInlineSnapshot(
+ `/build/kibana/bar`
);
});
@@ -89,28 +73,27 @@ describe('#resolvePath()', () => {
describe('#resolvePathForPlatform()', () => {
it('uses config.resolveFromRepo(), config.getBuildVersion(), and platform.getBuildName() to create path', () => {
- expect(ossBuild.resolvePathForPlatform(linuxPlatform, 'foo', 'bar')).toMatchInlineSnapshot(
- `/build/oss/kibana-8.0.0-linux-x86_64/foo/bar`
+ expect(defaultBuild.resolvePathForPlatform(linuxPlatform, 'foo', 'bar')).toMatchInlineSnapshot(
+ `/build/default/kibana-8.0.0-linux-x86_64/foo/bar`
);
});
});
describe('#getPlatformArchivePath()', () => {
it('creates correct path for different platforms', () => {
- expect(ossBuild.getPlatformArchivePath(linuxPlatform)).toMatchInlineSnapshot(
- `/target/kibana-oss-8.0.0-linux-x86_64.tar.gz`
+ expect(defaultBuild.getPlatformArchivePath(linuxPlatform)).toMatchInlineSnapshot(
+ `/target/kibana-8.0.0-linux-x86_64.tar.gz`
);
- expect(ossBuild.getPlatformArchivePath(linuxArmPlatform)).toMatchInlineSnapshot(
- `/target/kibana-oss-8.0.0-linux-aarch64.tar.gz`
+ expect(defaultBuild.getPlatformArchivePath(linuxArmPlatform)).toMatchInlineSnapshot(
+ `/target/kibana-8.0.0-linux-aarch64.tar.gz`
);
- expect(ossBuild.getPlatformArchivePath(windowsPlatform)).toMatchInlineSnapshot(
- `/target/kibana-oss-8.0.0-windows-x86_64.zip`
+ expect(defaultBuild.getPlatformArchivePath(windowsPlatform)).toMatchInlineSnapshot(
+ `/target/kibana-8.0.0-windows-x86_64.zip`
);
});
describe('#getRootDirectory()', () => {
it('creates correct root directory name', () => {
- expect(ossBuild.getRootDirectory()).toMatchInlineSnapshot(`"kibana-oss-8.0.0"`);
expect(defaultBuild.getRootDirectory()).toMatchInlineSnapshot(`"kibana-8.0.0"`);
});
});
diff --git a/src/dev/build/lib/build.ts b/src/dev/build/lib/build.ts
index f4ccb30994eef5..c777ad18dc51f0 100644
--- a/src/dev/build/lib/build.ts
+++ b/src/dev/build/lib/build.ts
@@ -12,14 +12,10 @@ import { Config } from './config';
import { Platform } from './platform';
export class Build {
- private name = this.oss ? 'kibana-oss' : 'kibana';
- private logTag = this.oss ? chalk`{magenta [kibana-oss]}` : chalk`{cyan [ kibana ]}`;
+ private name = 'kibana';
+ private logTag = chalk`{cyan [ kibana ]}`;
- constructor(private config: Config, private oss: boolean) {}
-
- isOss() {
- return !!this.oss;
- }
+ constructor(private config: Config) {}
resolvePath(...args: string[]) {
return this.config.resolveFromRepo('build', this.name, ...args);
@@ -28,7 +24,7 @@ export class Build {
resolvePathForPlatform(platform: Platform, ...args: string[]) {
return this.config.resolveFromRepo(
'build',
- this.oss ? 'oss' : 'default',
+ 'default',
`kibana-${this.config.getBuildVersion()}-${platform.getBuildName()}`,
...args
);
diff --git a/src/dev/build/lib/runner.test.ts b/src/dev/build/lib/runner.test.ts
index 0e3c00d220ad9a..2a08da2797a9de 100644
--- a/src/dev/build/lib/runner.test.ts
+++ b/src/dev/build/lib/runner.test.ts
@@ -45,7 +45,7 @@ beforeEach(() => {
jest.clearAllMocks();
});
-const setup = async (opts: { buildDefaultDist: boolean; buildOssDist: boolean }) => {
+const setup = async () => {
const config = await Config.create({
isRelease: true,
targetAllPlatforms: true,
@@ -55,55 +55,14 @@ const setup = async (opts: { buildDefaultDist: boolean; buildOssDist: boolean })
const run = createRunner({
config,
log,
- ...opts,
});
return { config, run };
};
-describe('buildOssDist = true, buildDefaultDist = true', () => {
+describe('default dist', () => {
it('runs global task once, passing config and log', async () => {
- const { config, run } = await setup({
- buildDefaultDist: true,
- buildOssDist: true,
- });
-
- const mock = jest.fn();
-
- await run({
- global: true,
- description: 'foo',
- run: mock,
- });
-
- expect(mock).toHaveBeenCalledTimes(1);
- expect(mock).toHaveBeenLastCalledWith(config, log, [expect.any(Build), expect.any(Build)]);
- });
-
- it('calls local tasks twice, passing each build', async () => {
- const { config, run } = await setup({
- buildDefaultDist: true,
- buildOssDist: true,
- });
-
- const mock = jest.fn();
-
- await run({
- description: 'foo',
- run: mock,
- });
-
- expect(mock).toHaveBeenCalledTimes(2);
- expect(mock).toHaveBeenCalledWith(config, log, expect.any(Build));
- });
-});
-
-describe('just default dist', () => {
- it('runs global task once, passing config and log', async () => {
- const { config, run } = await setup({
- buildDefaultDist: true,
- buildOssDist: false,
- });
+ const { config, run } = await setup();
const mock = jest.fn();
@@ -118,52 +77,7 @@ describe('just default dist', () => {
});
it('calls local tasks once, passing the default build', async () => {
- const { config, run } = await setup({
- buildDefaultDist: true,
- buildOssDist: false,
- });
-
- const mock = jest.fn();
-
- await run({
- description: 'foo',
- run: mock,
- });
-
- expect(mock).toHaveBeenCalledTimes(1);
- expect(mock).toHaveBeenCalledWith(config, log, expect.any(Build));
- const [args] = mock.mock.calls;
- const [, , build] = args;
- if (build.isOss()) {
- throw new Error('expected build to be the default dist, not the oss dist');
- }
- });
-});
-
-describe('just oss dist', () => {
- it('runs global task once, passing config and log', async () => {
- const { config, run } = await setup({
- buildDefaultDist: false,
- buildOssDist: true,
- });
-
- const mock = jest.fn();
-
- await run({
- global: true,
- description: 'foo',
- run: mock,
- });
-
- expect(mock).toHaveBeenCalledTimes(1);
- expect(mock).toHaveBeenLastCalledWith(config, log, [expect.any(Build)]);
- });
-
- it('calls local tasks once, passing the oss build', async () => {
- const { config, run } = await setup({
- buildDefaultDist: false,
- buildOssDist: true,
- });
+ const { config, run } = await setup();
const mock = jest.fn();
@@ -174,20 +88,12 @@ describe('just oss dist', () => {
expect(mock).toHaveBeenCalledTimes(1);
expect(mock).toHaveBeenCalledWith(config, log, expect.any(Build));
- const [args] = mock.mock.calls;
- const [, , build] = args;
- if (!build.isOss()) {
- throw new Error('expected build to be the oss dist, not the default dist');
- }
});
});
describe('task rejection', () => {
it('rejects, logs error, and marks error logged', async () => {
- const { run } = await setup({
- buildDefaultDist: true,
- buildOssDist: false,
- });
+ const { run } = await setup();
const error = new Error('FOO');
expect(isErrorLogged(error)).toBe(false);
@@ -213,10 +119,7 @@ describe('task rejection', () => {
});
it('just rethrows errors that have already been logged', async () => {
- const { run } = await setup({
- buildDefaultDist: true,
- buildOssDist: false,
- });
+ const { run } = await setup();
const error = markErrorLogged(new Error('FOO'));
const promise = run({
diff --git a/src/dev/build/lib/runner.ts b/src/dev/build/lib/runner.ts
index 015de6fe7e9ef7..1fccd884cc4f95 100644
--- a/src/dev/build/lib/runner.ts
+++ b/src/dev/build/lib/runner.ts
@@ -16,8 +16,6 @@ import { Config } from './config';
interface Options {
config: Config;
log: ToolingLog;
- buildOssDist: boolean;
- buildDefaultDist: boolean;
}
export interface GlobalTask {
@@ -32,7 +30,7 @@ export interface Task {
run(config: Config, log: ToolingLog, build: Build): Promise;
}
-export function createRunner({ config, log, buildOssDist, buildDefaultDist }: Options) {
+export function createRunner({ config, log }: Options) {
async function execTask(desc: string, task: Task | GlobalTask, lastArg: any) {
log.info(desc);
log.indent(4);
@@ -63,12 +61,7 @@ export function createRunner({ config, log, buildOssDist, buildDefaultDist }: Op
}
const builds: Build[] = [];
- if (buildDefaultDist) {
- builds.push(new Build(config, false));
- }
- if (buildOssDist) {
- builds.push(new Build(config, true));
- }
+ builds.push(new Build(config));
/**
* Run a task by calling its `run()` method with three arguments:
diff --git a/src/dev/build/tasks/build_kibana_platform_plugins.ts b/src/dev/build/tasks/build_kibana_platform_plugins.ts
index edff77d458f0f8..48d249ca374095 100644
--- a/src/dev/build/tasks/build_kibana_platform_plugins.ts
+++ b/src/dev/build/tasks/build_kibana_platform_plugins.ts
@@ -27,7 +27,6 @@ export const BuildKibanaPlatformPlugins: Task = {
repoRoot: REPO_ROOT,
outputRoot: build.resolvePath(),
cache: false,
- oss: build.isOss(),
examples: false,
watch: false,
dist: true,
diff --git a/src/dev/build/tasks/build_packages_task.ts b/src/dev/build/tasks/build_packages_task.ts
index e6305b3761a4f7..808903661a5950 100644
--- a/src/dev/build/tasks/build_packages_task.ts
+++ b/src/dev/build/tasks/build_packages_task.ts
@@ -63,7 +63,6 @@ export const BuildBazelPackages: Task = {
await buildBazelProductionProjects({
kibanaRoot: config.resolveFromRepo(),
buildRoot: build.resolvePath(),
- onlyOSS: build.isOss(),
});
},
};
@@ -75,7 +74,6 @@ export const BuildPackages: Task = {
await buildNonBazelProductionProjects({
kibanaRoot: config.resolveFromRepo(),
buildRoot: build.resolvePath(),
- onlyOSS: build.isOss(),
});
},
};
diff --git a/src/dev/build/tasks/create_archives_task.ts b/src/dev/build/tasks/create_archives_task.ts
index e2d3ff7149c4c5..37c4becae76a83 100644
--- a/src/dev/build/tasks/create_archives_task.ts
+++ b/src/dev/build/tasks/create_archives_task.ts
@@ -77,14 +77,14 @@ export const CreateArchives: Task = {
const metrics: CiStatsMetric[] = [];
for (const { format, path, fileCount } of archives) {
metrics.push({
- group: `${build.isOss() ? 'oss ' : ''}distributable size`,
+ group: `distributable size`,
id: format,
value: (await asyncStat(path)).size,
});
metrics.push({
group: 'distributable file count',
- id: build.isOss() ? 'oss' : 'default',
+ id: 'default',
value: fileCount,
});
}
diff --git a/src/dev/build/tasks/install_chromium.js b/src/dev/build/tasks/install_chromium.js
index b21a8484fa7104..37abcbad4466eb 100644
--- a/src/dev/build/tasks/install_chromium.js
+++ b/src/dev/build/tasks/install_chromium.js
@@ -15,21 +15,17 @@ export const InstallChromium = {
description: 'Installing Chromium',
async run(config, log, build) {
- if (build.isOss()) {
- return;
- } else {
- for (const platform of config.getNodePlatforms()) {
- log.info(`Installing Chromium for ${platform.getName()}-${platform.getArchitecture()}`);
+ for (const platform of config.getNodePlatforms()) {
+ log.info(`Installing Chromium for ${platform.getName()}-${platform.getArchitecture()}`);
- const { binaryPath$ } = installBrowser(
- // TODO: https://github.com/elastic/kibana/issues/72496
- log,
- build.resolvePathForPlatform(platform, 'x-pack/plugins/reporting/chromium'),
- platform.getName(),
- platform.getArchitecture()
- );
- await binaryPath$.pipe(first()).toPromise();
- }
+ const { binaryPath$ } = installBrowser(
+ // TODO: https://github.com/elastic/kibana/issues/72496
+ log,
+ build.resolvePathForPlatform(platform, 'x-pack/plugins/reporting/chromium'),
+ platform.getName(),
+ platform.getArchitecture()
+ );
+ await binaryPath$.pipe(first()).toPromise();
}
},
};
diff --git a/src/dev/build/tasks/license_file_task.ts b/src/dev/build/tasks/license_file_task.ts
index 7e5ed8da0a27a9..ff33707476576f 100644
--- a/src/dev/build/tasks/license_file_task.ts
+++ b/src/dev/build/tasks/license_file_task.ts
@@ -8,23 +8,13 @@
import { write, read, Task } from '../lib';
-const LICENSE_SEPARATOR = `\n------------------------------------------------------------------------\n\n`;
-
export const UpdateLicenseFile: Task = {
description: 'Updating LICENSE.txt file',
async run(config, log, build) {
const elasticLicense = await read(config.resolveFromRepo('licenses/ELASTIC-LICENSE-2.0.txt'));
- if (build.isOss()) {
- const ssplLicense = await read(config.resolveFromRepo('licenses/SSPL-LICENSE.txt'));
- log.info('Copying dual-license to LICENSE.txt');
- await write(
- build.resolvePath('LICENSE.txt'),
- ssplLicense + LICENSE_SEPARATOR + elasticLicense
- );
- } else {
- log.info('Copying Elastic license to LICENSE.txt');
- await write(build.resolvePath('LICENSE.txt'), elasticLicense);
- }
+
+ log.info('Copying Elastic license to LICENSE.txt');
+ await write(build.resolvePath('LICENSE.txt'), elasticLicense);
},
};
diff --git a/src/dev/build/tasks/os_packages/create_os_package_tasks.ts b/src/dev/build/tasks/os_packages/create_os_package_tasks.ts
index 2ae882000cae00..99d0e1998e78a8 100644
--- a/src/dev/build/tasks/os_packages/create_os_package_tasks.ts
+++ b/src/dev/build/tasks/os_packages/create_os_package_tasks.ts
@@ -73,15 +73,12 @@ export const CreateDockerUBI: Task = {
description: 'Creating Docker UBI image',
async run(config, log, build) {
- if (!build.isOss()) {
- await runDockerGenerator(config, log, build, {
- architecture: 'x64',
- context: false,
- ubi: true,
- image: true,
- dockerBuildDate,
- });
- }
+ await runDockerGenerator(config, log, build, {
+ architecture: 'x64',
+ context: false,
+ ubi: true,
+ image: true,
+ });
},
};
@@ -95,19 +92,15 @@ export const CreateDockerContexts: Task = {
dockerBuildDate,
});
- if (!build.isOss()) {
- await runDockerGenerator(config, log, build, {
- ubi: true,
- context: true,
- image: false,
- dockerBuildDate,
- });
- await runDockerGenerator(config, log, build, {
- ironbank: true,
- context: true,
- image: false,
- dockerBuildDate,
- });
- }
+ await runDockerGenerator(config, log, build, {
+ ubi: true,
+ context: true,
+ image: false,
+ });
+ await runDockerGenerator(config, log, build, {
+ ironbank: true,
+ context: true,
+ image: false,
+ });
},
};
diff --git a/src/dev/build/tasks/os_packages/docker_generator/run.ts b/src/dev/build/tasks/os_packages/docker_generator/run.ts
index c72112b7b6b03d..97fd7404097410 100644
--- a/src/dev/build/tasks/os_packages/docker_generator/run.ts
+++ b/src/dev/build/tasks/os_packages/docker_generator/run.ts
@@ -43,24 +43,18 @@ export async function runDockerGenerator(
let imageFlavor = '';
if (flags.ubi) imageFlavor += `-${ubiVersionTag}`;
if (flags.ironbank) imageFlavor += '-ironbank';
- if (build.isOss()) imageFlavor += '-oss';
// General docker var config
- const license = build.isOss() ? 'ASL 2.0' : 'Elastic License';
+ const license = 'Elastic License';
const imageTag = 'docker.elastic.co/kibana/kibana';
const version = config.getBuildVersion();
const artifactArchitecture = flags.architecture === 'aarch64' ? 'aarch64' : 'x86_64';
- const artifactFlavor = build.isOss() ? '-oss' : '';
- const artifactPrefix = `kibana${artifactFlavor}-${version}-linux`;
+ const artifactPrefix = `kibana-${version}-linux`;
const artifactTarball = `${artifactPrefix}-${artifactArchitecture}.tar.gz`;
const artifactsDir = config.resolveFromTarget('.');
const dockerBuildDate = flags.dockerBuildDate || new Date().toISOString();
// That would produce oss, default and default-ubi7
- const dockerBuildDir = config.resolveFromRepo(
- 'build',
- 'kibana-docker',
- build.isOss() ? `oss` : `default${imageFlavor}`
- );
+ const dockerBuildDir = config.resolveFromRepo('build', 'kibana-docker', `default${imageFlavor}`);
const imageArchitecture = flags.architecture === 'aarch64' ? '-aarch64' : '';
const dockerTargetFilename = config.resolveFromTarget(
`kibana${imageFlavor}-${version}-docker-image${imageArchitecture}.tar.gz`
diff --git a/src/dev/build/tasks/os_packages/run_fpm.ts b/src/dev/build/tasks/os_packages/run_fpm.ts
index 933b3e411b2864..b732e4c80ea370 100644
--- a/src/dev/build/tasks/os_packages/run_fpm.ts
+++ b/src/dev/build/tasks/os_packages/run_fpm.ts
@@ -28,11 +28,7 @@ export async function runFpm(
const fromBuild = (...paths: string[]) => build.resolvePathForPlatform(linux, ...paths);
const pickLicense = () => {
- if (build.isOss()) {
- return type === 'rpm' ? 'ASL 2.0' : 'ASL-2.0';
- } else {
- return type === 'rpm' ? 'Elastic License' : 'Elastic-License';
- }
+ return type === 'rpm' ? 'Elastic License' : 'Elastic-License';
};
const envFolder = type === 'rpm' ? 'sysconfig' : 'default';
@@ -57,7 +53,7 @@ export async function runFpm(
// general info about the package
'--name',
- build.isOss() ? 'kibana-oss' : 'kibana',
+ 'kibana',
'--description',
'Explore and visualize your Elasticsearch data',
'--version',
@@ -71,10 +67,6 @@ export async function runFpm(
'--license',
pickLicense(),
- // prevent installing kibana if installing kibana-oss and vice versa
- '--conflicts',
- build.isOss() ? 'kibana' : 'kibana-oss',
-
// define install/uninstall scripts
'--after-install',
resolve(__dirname, 'package_scripts/post_install.sh'),
diff --git a/test/scripts/jenkins_baseline.sh b/test/scripts/jenkins_baseline.sh
index 40bfc6e83ad1bf..5c0ca5a8e01ca1 100755
--- a/test/scripts/jenkins_baseline.sh
+++ b/test/scripts/jenkins_baseline.sh
@@ -4,7 +4,7 @@ source src/dev/ci_setup/setup_env.sh
source "$KIBANA_DIR/src/dev/ci_setup/setup_percy.sh"
echo " -> building and extracting OSS Kibana distributable for use in functional tests"
-node scripts/build --debug --oss
+node scripts/build --debug
echo " -> shipping metrics from build to ci-stats"
node scripts/ship_ci_stats \
diff --git a/test/scripts/jenkins_build_kibana.sh b/test/scripts/jenkins_build_kibana.sh
index 198723908cf487..a9edd3ed2a701b 100755
--- a/test/scripts/jenkins_build_kibana.sh
+++ b/test/scripts/jenkins_build_kibana.sh
@@ -33,7 +33,7 @@ node x-pack/scripts/functional_tests --assert-none-excluded \
# Do not build kibana for code coverage run
if [[ -z "$CODE_COVERAGE" ]] ; then
echo " -> building and extracting default Kibana distributable for use in functional tests"
- node scripts/build --debug --no-oss
+ node scripts/build --debug
echo " -> shipping metrics from build to ci-stats"
node scripts/ship_ci_stats \
diff --git a/test/scripts/jenkins_build_load_testing.sh b/test/scripts/jenkins_build_load_testing.sh
index 5571eee4f28edb..d7c7bda83c9ef2 100755
--- a/test/scripts/jenkins_build_load_testing.sh
+++ b/test/scripts/jenkins_build_load_testing.sh
@@ -60,7 +60,7 @@ export KBN_NP_PLUGINS_BUILT=true
echo " -> Building and extracting default Kibana distributable for use in functional tests"
cd "$KIBANA_DIR"
-node scripts/build --debug --no-oss
+node scripts/build --debug
linuxBuild="$(find "$KIBANA_DIR/target" -name 'kibana-*-linux-x86_64.tar.gz')"
installDir="$KIBANA_DIR/install/kibana"
mkdir -p "$installDir"
diff --git a/test/scripts/jenkins_xpack_baseline.sh b/test/scripts/jenkins_xpack_baseline.sh
index 8d5624949505a5..c68c0f40902f22 100755
--- a/test/scripts/jenkins_xpack_baseline.sh
+++ b/test/scripts/jenkins_xpack_baseline.sh
@@ -5,7 +5,7 @@ source "$KIBANA_DIR/src/dev/ci_setup/setup_percy.sh"
echo " -> building and extracting default Kibana distributable"
cd "$KIBANA_DIR"
-node scripts/build --debug --no-oss
+node scripts/build --debug
echo " -> shipping metrics from build to ci-stats"
node scripts/ship_ci_stats \
diff --git a/test/scripts/jenkins_xpack_package_build.sh b/test/scripts/jenkins_xpack_package_build.sh
index 698129a2d253bd..86e846f720803f 100755
--- a/test/scripts/jenkins_xpack_package_build.sh
+++ b/test/scripts/jenkins_xpack_package_build.sh
@@ -7,6 +7,6 @@ source src/dev/ci_setup/setup_env.sh
export TMP=/tmp
export TMPDIR=/tmp
-node scripts/build --all-platforms --debug --no-oss
+node scripts/build --all-platforms --debug
gsutil -q -m cp 'target/*' "gs://ci-artifacts.kibana.dev/package-testing/$GIT_COMMIT/"
From 61d23665a9e391c3cfa42021671ef3461f0c9560 Mon Sep 17 00:00:00 2001
From: Elizabet Oliveira
Date: Wed, 30 Jun 2021 00:32:55 +0100
Subject: [PATCH 009/121] [Maps] Add layer disabled button styles (#103775)
---
.../right_side_controls/layer_control/_layer_control.scss | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/_layer_control.scss b/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/_layer_control.scss
index 19f70070ef5b2b..6e31d3f26b4bc4 100644
--- a/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/_layer_control.scss
+++ b/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/_layer_control.scss
@@ -9,6 +9,12 @@
.mapLayerControl__addLayerButton {
flex-shrink: 0;
+
+ // overrides disabled color that is transparent and doesn't work well on top of the map
+ &.euiButton-isDisabled {
+ // sass-lint:disable-block no-important
+ background-color: $euiColorLightShade !important;
+ }
}
.mapLayerControl__addLayerButton,
From f039f8311f342c0b9510fe49f79369f417007c77 Mon Sep 17 00:00:00 2001
From: Nathan L Smith
Date: Tue, 29 Jun 2021 18:41:17 -0500
Subject: [PATCH 010/121] Deprecate APM OSS `fleetMode` setting (#103721)
This has been enabled by default, and there isn't any remaining reason to turn it off.
Mark it as deprecated and remove conditionals that check for it.
---
src/plugins/apm_oss/server/index.ts | 5 ++-
x-pack/plugins/apm/server/index.test.ts | 46 +++----------------------
x-pack/plugins/apm/server/index.ts | 27 +++++++--------
3 files changed, 21 insertions(+), 57 deletions(-)
diff --git a/src/plugins/apm_oss/server/index.ts b/src/plugins/apm_oss/server/index.ts
index 1424cb1c7126fb..7b16c42f4c9b91 100644
--- a/src/plugins/apm_oss/server/index.ts
+++ b/src/plugins/apm_oss/server/index.ts
@@ -7,9 +7,11 @@
*/
import { schema, TypeOf } from '@kbn/config-schema';
-import { PluginInitializerContext } from '../../../core/server';
+import { ConfigDeprecationProvider, PluginInitializerContext } from '../../../core/server';
import { APMOSSPlugin } from './plugin';
+const deprecations: ConfigDeprecationProvider = ({ unused }) => [unused('fleetMode')];
+
export const config = {
schema: schema.object({
enabled: schema.boolean({ defaultValue: true }),
@@ -22,6 +24,7 @@ export const config = {
indexPattern: schema.string({ defaultValue: 'apm-*' }),
fleetMode: schema.boolean({ defaultValue: true }),
}),
+ deprecations,
};
export function plugin(initializerContext: PluginInitializerContext) {
diff --git a/x-pack/plugins/apm/server/index.test.ts b/x-pack/plugins/apm/server/index.test.ts
index 226dfd6e95bd31..6052ec921f9f9c 100644
--- a/x-pack/plugins/apm/server/index.test.ts
+++ b/x-pack/plugins/apm/server/index.test.ts
@@ -28,53 +28,15 @@ describe('mergeConfigs', () => {
agent: { migrations: { enabled: true } },
} as APMXPackConfig;
- expect(mergeConfigs(apmOssConfig, apmConfig)).toEqual({
- 'apm_oss.errorIndices': 'apm-*-error-*',
- 'apm_oss.indexPattern': 'apm-*',
- 'apm_oss.metricsIndices': 'apm-*-metric-*',
- 'apm_oss.spanIndices': 'apm-*-span-*',
- 'apm_oss.transactionIndices': 'apm-*-transaction-*',
- 'xpack.apm.metricsInterval': 2000,
- 'xpack.apm.ui.enabled': false,
- 'xpack.apm.agent.migrations.enabled': true,
- });
- });
-
- it('adds fleet indices', () => {
- const apmOssConfig = {
- transactionIndices: 'apm-*-transaction-*',
- spanIndices: 'apm-*-span-*',
- errorIndices: 'apm-*-error-*',
- metricsIndices: 'apm-*-metric-*',
- fleetMode: true,
- } as APMOSSConfig;
-
- const apmConfig = { ui: {}, agent: { migrations: {} } } as APMXPackConfig;
-
expect(mergeConfigs(apmOssConfig, apmConfig)).toEqual({
'apm_oss.errorIndices': 'logs-apm*,apm-*-error-*',
+ 'apm_oss.indexPattern': 'apm-*',
'apm_oss.metricsIndices': 'metrics-apm*,apm-*-metric-*',
'apm_oss.spanIndices': 'traces-apm*,apm-*-span-*',
'apm_oss.transactionIndices': 'traces-apm*,apm-*-transaction-*',
- });
- });
-
- it('does not add fleet indices', () => {
- const apmOssConfig = {
- transactionIndices: 'apm-*-transaction-*',
- spanIndices: 'apm-*-span-*',
- errorIndices: 'apm-*-error-*',
- metricsIndices: 'apm-*-metric-*',
- fleetMode: false,
- } as APMOSSConfig;
-
- const apmConfig = { ui: {}, agent: { migrations: {} } } as APMXPackConfig;
-
- expect(mergeConfigs(apmOssConfig, apmConfig)).toEqual({
- 'apm_oss.errorIndices': 'apm-*-error-*',
- 'apm_oss.metricsIndices': 'apm-*-metric-*',
- 'apm_oss.spanIndices': 'apm-*-span-*',
- 'apm_oss.transactionIndices': 'apm-*-transaction-*',
+ 'xpack.apm.metricsInterval': 2000,
+ 'xpack.apm.ui.enabled': false,
+ 'xpack.apm.agent.migrations.enabled': true,
});
});
});
diff --git a/x-pack/plugins/apm/server/index.ts b/x-pack/plugins/apm/server/index.ts
index 413efcdb788128..8ec92bfa7a1b58 100644
--- a/x-pack/plugins/apm/server/index.ts
+++ b/x-pack/plugins/apm/server/index.ts
@@ -102,23 +102,22 @@ export function mergeConfigs(
'xpack.apm.agent.migrations.enabled': apmConfig.agent.migrations.enabled,
};
- if (apmOssConfig.fleetMode) {
- mergedConfig[
- 'apm_oss.transactionIndices'
- ] = `traces-apm*,${mergedConfig['apm_oss.transactionIndices']}`;
+ // Add data stream indices to list of configured values
+ mergedConfig[
+ 'apm_oss.transactionIndices'
+ ] = `traces-apm*,${mergedConfig['apm_oss.transactionIndices']}`;
- mergedConfig[
- 'apm_oss.spanIndices'
- ] = `traces-apm*,${mergedConfig['apm_oss.spanIndices']}`;
+ mergedConfig[
+ 'apm_oss.spanIndices'
+ ] = `traces-apm*,${mergedConfig['apm_oss.spanIndices']}`;
- mergedConfig[
- 'apm_oss.errorIndices'
- ] = `logs-apm*,${mergedConfig['apm_oss.errorIndices']}`;
+ mergedConfig[
+ 'apm_oss.errorIndices'
+ ] = `logs-apm*,${mergedConfig['apm_oss.errorIndices']}`;
- mergedConfig[
- 'apm_oss.metricsIndices'
- ] = `metrics-apm*,${mergedConfig['apm_oss.metricsIndices']}`;
- }
+ mergedConfig[
+ 'apm_oss.metricsIndices'
+ ] = `metrics-apm*,${mergedConfig['apm_oss.metricsIndices']}`;
return mergedConfig;
}
From e749fa62fa2e9ed2799630768b82b4492fc57827 Mon Sep 17 00:00:00 2001
From: Candace Park <56409205+parkiino@users.noreply.github.com>
Date: Tue, 29 Jun 2021 19:42:38 -0400
Subject: [PATCH 011/121] [Security Solution][Endpoint][Host Isolation][Cases]
Update Host Isolation comment in Cases UI (#102937)
---
.../plugins/cases/common/api/cases/comment.ts | 43 +-
.../components/case_view/index.test.tsx | 20 +
.../public/components/case_view/index.tsx | 5 +
.../components/user_action_tree/helpers.tsx | 78 ++-
.../user_action_tree/index.test.tsx | 1 +
.../components/user_action_tree/index.tsx | 30 ++
.../user_action_tree/translations.ts | 14 +
...er_action_host_isolation_comment_event.tsx | 56 ++
x-pack/plugins/cases/server/client/utils.ts | 4 +
x-pack/plugins/cases/server/common/utils.ts | 9 +
.../server/saved_object_types/comments.ts | 12 +
.../common/endpoint/schema/actions.ts | 4 +-
.../cases/components/case_view/index.tsx | 23 +
.../common/lib/endpoint_isolation/mocks.ts | 1 -
.../public/management/common/routing.ts | 10 +-
.../endpoint/routes/actions/isolation.test.ts | 507 +++++++++---------
.../endpoint/routes/actions/isolation.ts | 51 +-
.../server/endpoint/services/index.ts | 2 +-
.../services/{lookup_agent.ts => metadata.ts} | 7 +-
19 files changed, 591 insertions(+), 286 deletions(-)
create mode 100644 x-pack/plugins/cases/public/components/user_action_tree/user_action_host_isolation_comment_event.tsx
rename x-pack/plugins/security_solution/server/endpoint/services/{lookup_agent.ts => metadata.ts} (89%)
diff --git a/x-pack/plugins/cases/common/api/cases/comment.ts b/x-pack/plugins/cases/common/api/cases/comment.ts
index 746c28f9942392..378a89f8af8c3c 100644
--- a/x-pack/plugins/cases/common/api/cases/comment.ts
+++ b/x-pack/plugins/cases/common/api/cases/comment.ts
@@ -39,6 +39,7 @@ export enum CommentType {
user = 'user',
alert = 'alert',
generatedAlert = 'generated_alert',
+ actions = 'actions',
}
export const ContextTypeUserRt = rt.type({
@@ -63,11 +64,38 @@ export const AlertCommentRequestRt = rt.type({
owner: rt.string,
});
+export const ActionsCommentRequestRt = rt.type({
+ type: rt.literal(CommentType.actions),
+ comment: rt.string,
+ actions: rt.type({
+ targets: rt.array(
+ rt.type({
+ hostname: rt.string,
+ endpointId: rt.string,
+ })
+ ),
+ type: rt.string,
+ }),
+ owner: rt.string,
+});
+
const AttributesTypeUserRt = rt.intersection([ContextTypeUserRt, CommentAttributesBasicRt]);
const AttributesTypeAlertsRt = rt.intersection([AlertCommentRequestRt, CommentAttributesBasicRt]);
-const CommentAttributesRt = rt.union([AttributesTypeUserRt, AttributesTypeAlertsRt]);
+const AttributesTypeActionsRt = rt.intersection([
+ ActionsCommentRequestRt,
+ CommentAttributesBasicRt,
+]);
+const CommentAttributesRt = rt.union([
+ AttributesTypeUserRt,
+ AttributesTypeAlertsRt,
+ AttributesTypeActionsRt,
+]);
-export const CommentRequestRt = rt.union([ContextTypeUserRt, AlertCommentRequestRt]);
+export const CommentRequestRt = rt.union([
+ ContextTypeUserRt,
+ AlertCommentRequestRt,
+ ActionsCommentRequestRt,
+]);
export const CommentResponseRt = rt.intersection([
CommentAttributesRt,
@@ -85,6 +113,14 @@ export const CommentResponseTypeAlertsRt = rt.intersection([
}),
]);
+export const CommentResponseTypeActionsRt = rt.intersection([
+ AttributesTypeActionsRt,
+ rt.type({
+ id: rt.string,
+ version: rt.string,
+ }),
+]);
+
export const AllCommentsResponseRT = rt.array(CommentResponseRt);
export const CommentPatchRequestRt = rt.intersection([
@@ -125,15 +161,18 @@ export const FindQueryParamsRt = rt.partial({
});
export type FindQueryParams = rt.TypeOf;
+export type AttributesTypeActions = rt.TypeOf;
export type AttributesTypeAlerts = rt.TypeOf;
export type AttributesTypeUser = rt.TypeOf;
export type CommentAttributes = rt.TypeOf;
export type CommentRequest = rt.TypeOf;
export type CommentResponse = rt.TypeOf;
export type CommentResponseAlertsType = rt.TypeOf;
+export type CommentResponseActionsType = rt.TypeOf;
export type AllCommentsResponse = rt.TypeOf;
export type CommentsResponse = rt.TypeOf;
export type CommentPatchRequest = rt.TypeOf;
export type CommentPatchAttributes = rt.TypeOf;
export type CommentRequestUserType = rt.TypeOf;
export type CommentRequestAlertType = rt.TypeOf;
+export type CommentRequestActionsType = rt.TypeOf;
diff --git a/x-pack/plugins/cases/public/components/case_view/index.test.tsx b/x-pack/plugins/cases/public/components/case_view/index.test.tsx
index 1fafbac50c2b9a..e04bbbe54c8374 100644
--- a/x-pack/plugins/cases/public/components/case_view/index.test.tsx
+++ b/x-pack/plugins/cases/public/components/case_view/index.test.tsx
@@ -90,6 +90,10 @@ export const caseProps: CaseComponentProps = {
},
getCaseDetailHrefWithCommentId: jest.fn(),
onComponentInitialized: jest.fn(),
+ actionsNavigation: {
+ href: jest.fn(),
+ onClick: jest.fn(),
+ },
ruleDetailsNavigation: {
href: jest.fn(),
onClick: jest.fn(),
@@ -408,6 +412,10 @@ describe('CaseView ', () => {
},
getCaseDetailHrefWithCommentId: jest.fn(),
onComponentInitialized: jest.fn(),
+ actionsNavigation: {
+ href: jest.fn(),
+ onClick: jest.fn(),
+ },
ruleDetailsNavigation: {
href: jest.fn(),
onClick: jest.fn(),
@@ -448,6 +456,10 @@ describe('CaseView ', () => {
},
getCaseDetailHrefWithCommentId: jest.fn(),
onComponentInitialized: jest.fn(),
+ actionsNavigation: {
+ href: jest.fn(),
+ onClick: jest.fn(),
+ },
ruleDetailsNavigation: {
href: jest.fn(),
onClick: jest.fn(),
@@ -485,6 +497,10 @@ describe('CaseView ', () => {
},
getCaseDetailHrefWithCommentId: jest.fn(),
onComponentInitialized: jest.fn(),
+ actionsNavigation: {
+ href: jest.fn(),
+ onClick: jest.fn(),
+ },
ruleDetailsNavigation: {
href: jest.fn(),
onClick: jest.fn(),
@@ -522,6 +538,10 @@ describe('CaseView ', () => {
},
getCaseDetailHrefWithCommentId: jest.fn(),
onComponentInitialized: jest.fn(),
+ actionsNavigation: {
+ href: jest.fn(),
+ onClick: jest.fn(),
+ },
ruleDetailsNavigation: {
href: jest.fn(),
onClick: jest.fn(),
diff --git a/x-pack/plugins/cases/public/components/case_view/index.tsx b/x-pack/plugins/cases/public/components/case_view/index.tsx
index 2fffd6464dbb1e..ac7c9ebe08b5ab 100644
--- a/x-pack/plugins/cases/public/components/case_view/index.tsx
+++ b/x-pack/plugins/cases/public/components/case_view/index.tsx
@@ -50,6 +50,7 @@ export interface CaseViewComponentProps {
configureCasesNavigation: CasesNavigation;
getCaseDetailHrefWithCommentId: (commentId: string) => string;
onComponentInitialized?: () => void;
+ actionsNavigation?: CasesNavigation;
ruleDetailsNavigation?: CasesNavigation;
showAlertDetails?: (alertId: string, index: string) => void;
subCaseId?: string;
@@ -99,6 +100,7 @@ export const CaseComponent = React.memo(
getCaseDetailHrefWithCommentId,
fetchCase,
onComponentInitialized,
+ actionsNavigation,
ruleDetailsNavigation,
showAlertDetails,
subCaseId,
@@ -418,6 +420,7 @@ export const CaseComponent = React.memo(
caseUserActions={caseUserActions}
connectors={connectors}
data={caseData}
+ actionsNavigation={actionsNavigation}
fetchUserActions={fetchCaseUserActions.bind(
null,
caseId,
@@ -505,6 +508,7 @@ export const CaseView = React.memo(
getCaseDetailHrefWithCommentId,
onCaseDataSuccess,
onComponentInitialized,
+ actionsNavigation,
ruleDetailsNavigation,
showAlertDetails,
subCaseId,
@@ -543,6 +547,7 @@ export const CaseView = React.memo(
getCaseDetailHrefWithCommentId={getCaseDetailHrefWithCommentId}
fetchCase={fetchCase}
onComponentInitialized={onComponentInitialized}
+ actionsNavigation={actionsNavigation}
ruleDetailsNavigation={ruleDetailsNavigation}
showAlertDetails={showAlertDetails}
subCaseId={subCaseId}
diff --git a/x-pack/plugins/cases/public/components/user_action_tree/helpers.tsx b/x-pack/plugins/cases/public/components/user_action_tree/helpers.tsx
index 5424ad8238a2ac..5d234296dd5033 100644
--- a/x-pack/plugins/cases/public/components/user_action_tree/helpers.tsx
+++ b/x-pack/plugins/cases/public/components/user_action_tree/helpers.tsx
@@ -7,12 +7,14 @@
import { EuiFlexGroup, EuiFlexItem, EuiIcon, EuiLink, EuiCommentProps } from '@elastic/eui';
import React from 'react';
-
+import classNames from 'classnames';
import {
CaseFullExternalService,
ActionConnector,
CaseStatuses,
CommentType,
+ Comment,
+ CommentRequestActionsType,
} from '../../../common';
import { CaseUserActions } from '../../containers/types';
import { CaseServices } from '../../containers/use_get_case_user_actions';
@@ -20,13 +22,16 @@ import { parseString } from '../../containers/utils';
import { Tags } from '../tag_list/tags';
import { UserActionUsernameWithAvatar } from './user_action_username_with_avatar';
import { UserActionTimestamp } from './user_action_timestamp';
+import { UserActionContentToolbar } from './user_action_content_toolbar';
import { UserActionCopyLink } from './user_action_copy_link';
+import { UserActionMarkdown } from './user_action_markdown';
import { UserActionMoveToReference } from './user_action_move_to_reference';
import { Status, statuses } from '../status';
import { UserActionShowAlert } from './user_action_show_alert';
import * as i18n from './translations';
import { AlertCommentEvent } from './user_action_alert_comment_event';
import { CasesNavigation } from '../links';
+import { HostIsolationCommentEvent } from './user_action_host_isolation_comment_event';
interface LabelTitle {
action: CaseUserActions;
@@ -34,6 +39,8 @@ interface LabelTitle {
}
export type RuleDetailsNavigation = CasesNavigation;
+export type ActionsNavigation = CasesNavigation;
+
const getStatusTitle = (id: string, status: CaseStatuses) => (
string;
+ actionsNavigation?: ActionsNavigation;
+ manageMarkdownEditIds: string[];
+ handleManageMarkdownEditId: (id: string) => void;
+ handleManageQuote: (id: string) => void;
+ handleSaveComment: ({ id, version }: { id: string; version: string }, content: string) => void;
+ action: CaseUserActions;
+}): EuiCommentProps => ({
+ username: (
+
+ ),
+ className: classNames({
+ isEdit: manageMarkdownEditIds.includes(comment.id),
+ }),
+ event: (
+
+ ),
+ 'data-test-subj': 'endpoint-action',
+ timestamp: ,
+ timelineIcon: comment.actions.type === 'isolate' ? 'lock' : 'lockOpen',
+ actions: (
+
+ ),
+ children: (
+
+ ),
+});
+
interface Signal {
rule: {
id: string;
diff --git a/x-pack/plugins/cases/public/components/user_action_tree/index.test.tsx b/x-pack/plugins/cases/public/components/user_action_tree/index.test.tsx
index faa4f1d1a786fc..610399c31928b7 100644
--- a/x-pack/plugins/cases/public/components/user_action_tree/index.test.tsx
+++ b/x-pack/plugins/cases/public/components/user_action_tree/index.test.tsx
@@ -28,6 +28,7 @@ const defaultProps = {
caseUserActions: [],
connectors: [],
getCaseDetailHrefWithCommentId: jest.fn(),
+ actionsNavigation: { href: jest.fn(), onClick: jest.fn() },
getRuleDetailsHref: jest.fn(),
onRuleDetailsClick: jest.fn(),
data: basicCase,
diff --git a/x-pack/plugins/cases/public/components/user_action_tree/index.tsx b/x-pack/plugins/cases/public/components/user_action_tree/index.tsx
index 5e90c2e6a951fb..eb74eb2bd643a6 100644
--- a/x-pack/plugins/cases/public/components/user_action_tree/index.tsx
+++ b/x-pack/plugins/cases/public/components/user_action_tree/index.tsx
@@ -26,6 +26,7 @@ import { useCurrentUser } from '../../common/lib/kibana';
import { AddComment, AddCommentRefObject } from '../add_comment';
import {
ActionConnector,
+ ActionsCommentRequestRt,
AlertCommentRequestRt,
Case,
CaseUserActions,
@@ -45,6 +46,8 @@ import {
getAlertAttachment,
getGeneratedAlertsAttachment,
RuleDetailsNavigation,
+ ActionsNavigation,
+ getActionAttachment,
} from './helpers';
import { UserActionAvatar } from './user_action_avatar';
import { UserActionMarkdown } from './user_action_markdown';
@@ -61,6 +64,7 @@ export interface UserActionTreeProps {
fetchUserActions: () => void;
getCaseDetailHrefWithCommentId: (commentId: string) => string;
getRuleDetailsHref?: RuleDetailsNavigation['href'];
+ actionsNavigation?: ActionsNavigation;
isLoadingDescription: boolean;
isLoadingUserActions: boolean;
onRuleDetailsClick?: RuleDetailsNavigation['onClick'];
@@ -125,6 +129,7 @@ export const UserActionTree = React.memo(
fetchUserActions,
getCaseDetailHrefWithCommentId,
getRuleDetailsHref,
+ actionsNavigation,
isLoadingDescription,
isLoadingUserActions,
onRuleDetailsClick,
@@ -447,6 +452,30 @@ export const UserActionTree = React.memo(
]
: []),
];
+ } else if (
+ comment != null &&
+ isRight(ActionsCommentRequestRt.decode(comment)) &&
+ comment.type === CommentType.actions
+ ) {
+ return [
+ ...comments,
+ ...(comment.actions !== null
+ ? [
+ getActionAttachment({
+ comment,
+ userCanCrud,
+ isLoadingIds,
+ getCaseDetailHrefWithCommentId,
+ actionsNavigation,
+ manageMarkdownEditIds,
+ handleManageMarkdownEditId,
+ handleManageQuote,
+ handleSaveComment,
+ action,
+ }),
+ ]
+ : []),
+ ];
}
}
@@ -559,6 +588,7 @@ export const UserActionTree = React.memo(
handleManageMarkdownEditId,
handleSaveComment,
getCaseDetailHrefWithCommentId,
+ actionsNavigation,
userCanCrud,
isLoadingIds,
handleManageQuote,
diff --git a/x-pack/plugins/cases/public/components/user_action_tree/translations.ts b/x-pack/plugins/cases/public/components/user_action_tree/translations.ts
index 27d1554ed255b6..54738e29060f3d 100644
--- a/x-pack/plugins/cases/public/components/user_action_tree/translations.ts
+++ b/x-pack/plugins/cases/public/components/user_action_tree/translations.ts
@@ -56,3 +56,17 @@ export const SHOW_ALERT_TOOLTIP = i18n.translate('xpack.cases.caseView.showAlert
export const UNKNOWN_RULE = i18n.translate('xpack.cases.caseView.unknownRule.label', {
defaultMessage: 'Unknown rule',
});
+
+export const ISOLATED_HOST = i18n.translate('xpack.cases.caseView.isolatedHost', {
+ defaultMessage: 'isolated host',
+});
+
+export const RELEASED_HOST = i18n.translate('xpack.cases.caseView.releasedHost', {
+ defaultMessage: 'released host',
+});
+
+export const OTHER_ENDPOINTS = (endpoints: number): string =>
+ i18n.translate('xpack.cases.caseView.otherEndpoints', {
+ values: { endpoints },
+ defaultMessage: ` and {endpoints} {endpoints, plural, =1 {other} other {others}}`,
+ });
diff --git a/x-pack/plugins/cases/public/components/user_action_tree/user_action_host_isolation_comment_event.tsx b/x-pack/plugins/cases/public/components/user_action_tree/user_action_host_isolation_comment_event.tsx
new file mode 100644
index 00000000000000..d363e874a4e0dd
--- /dev/null
+++ b/x-pack/plugins/cases/public/components/user_action_tree/user_action_host_isolation_comment_event.tsx
@@ -0,0 +1,56 @@
+/*
+ * 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, useCallback } from 'react';
+import * as i18n from './translations';
+import { LinkAnchor } from '../links';
+import { ActionsNavigation } from './helpers';
+
+interface EndpointInfo {
+ endpointId: string;
+ hostname: string;
+}
+
+interface Props {
+ type: string;
+ endpoints: EndpointInfo[];
+ href?: ActionsNavigation['href'];
+ onClick?: ActionsNavigation['onClick'];
+}
+
+const HostIsolationCommentEventComponent: React.FC = ({
+ type,
+ endpoints,
+ href,
+ onClick,
+}) => {
+ const endpointDetailsHref = href ? href(endpoints[0].endpointId) : '';
+
+ const onLinkClick = useCallback(
+ (ev) => {
+ ev.preventDefault();
+ if (onClick) onClick(endpoints[0].endpointId, ev);
+ },
+ [onClick, endpoints]
+ );
+
+ return (
+ <>
+ {type === 'isolate' ? `${i18n.ISOLATED_HOST} ` : `${i18n.RELEASED_HOST} `}
+
+ {endpoints[0].hostname}
+
+ {endpoints.length > 1 && i18n.OTHER_ENDPOINTS(endpoints.length - 1)}
+ >
+ );
+};
+
+export const HostIsolationCommentEvent = memo(HostIsolationCommentEventComponent);
diff --git a/x-pack/plugins/cases/server/client/utils.ts b/x-pack/plugins/cases/server/client/utils.ts
index 3240ec06803c77..0e7a21816de4c1 100644
--- a/x-pack/plugins/cases/server/client/utils.ts
+++ b/x-pack/plugins/cases/server/client/utils.ts
@@ -17,6 +17,7 @@ import { nodeBuilder, KueryNode } from '../../../../../src/plugins/data/common';
import { esKuery } from '../../../../../src/plugins/data/server';
import {
AlertCommentRequestRt,
+ ActionsCommentRequestRt,
CASE_SAVED_OBJECT,
CaseConnector,
CaseStatuses,
@@ -35,12 +36,15 @@ import {
getIDsAndIndicesAsArrays,
isCommentRequestTypeAlertOrGenAlert,
isCommentRequestTypeUser,
+ isCommentRequestTypeActions,
SavedObjectFindOptionsKueryNode,
} from '../common';
export const decodeCommentRequest = (comment: CommentRequest) => {
if (isCommentRequestTypeUser(comment)) {
pipe(excess(ContextTypeUserRt).decode(comment), fold(throwErrors(badRequest), identity));
+ } else if (isCommentRequestTypeActions(comment)) {
+ pipe(excess(ActionsCommentRequestRt).decode(comment), fold(throwErrors(badRequest), identity));
} else if (isCommentRequestTypeAlertOrGenAlert(comment)) {
pipe(excess(AlertCommentRequestRt).decode(comment), fold(throwErrors(badRequest), identity));
const { ids, indices } = getIDsAndIndicesAsArrays(comment);
diff --git a/x-pack/plugins/cases/server/common/utils.ts b/x-pack/plugins/cases/server/common/utils.ts
index 70ecc50df0f488..13d3f3768f3919 100644
--- a/x-pack/plugins/cases/server/common/utils.ts
+++ b/x-pack/plugins/cases/server/common/utils.ts
@@ -317,6 +317,15 @@ export const isCommentRequestTypeUser = (
return context.type === CommentType.user;
};
+/**
+ * A type narrowing function for actions comments. Exporting so integration tests can use it.
+ */
+export const isCommentRequestTypeActions = (
+ context: CommentRequest
+): context is CommentRequestUserType => {
+ return context.type === CommentType.actions;
+};
+
/**
* A type narrowing function for alert comments. Exporting so integration tests can use it.
*/
diff --git a/x-pack/plugins/cases/server/saved_object_types/comments.ts b/x-pack/plugins/cases/server/saved_object_types/comments.ts
index cd2dec7260fa4a..876ceb9bc2045a 100644
--- a/x-pack/plugins/cases/server/saved_object_types/comments.ts
+++ b/x-pack/plugins/cases/server/saved_object_types/comments.ts
@@ -27,6 +27,18 @@ export const caseCommentSavedObjectType: SavedObjectsType = {
type: {
type: 'keyword',
},
+ actions: {
+ properties: {
+ targets: {
+ type: 'nested',
+ properties: {
+ hostname: { type: 'keyword' },
+ endpointId: { type: 'keyword' },
+ },
+ },
+ type: { type: 'keyword' },
+ },
+ },
alertId: {
type: 'keyword',
},
diff --git a/x-pack/plugins/security_solution/common/endpoint/schema/actions.ts b/x-pack/plugins/security_solution/common/endpoint/schema/actions.ts
index f58dd1f3370d4a..fd4d89540f0ce9 100644
--- a/x-pack/plugins/security_solution/common/endpoint/schema/actions.ts
+++ b/x-pack/plugins/security_solution/common/endpoint/schema/actions.ts
@@ -9,10 +9,8 @@ import { schema, TypeOf } from '@kbn/config-schema';
export const HostIsolationRequestSchema = {
body: schema.object({
- /** A list of Fleet Agent IDs whose hosts will be isolated */
- agent_ids: schema.maybe(schema.arrayOf(schema.string())),
/** A list of endpoint IDs whose hosts will be isolated (Fleet Agent IDs will be retrieved for these) */
- endpoint_ids: schema.maybe(schema.arrayOf(schema.string())),
+ endpoint_ids: schema.arrayOf(schema.string(), { minSize: 1 }),
/** If defined, any case associated with the given IDs will be updated */
alert_ids: schema.maybe(schema.arrayOf(schema.string())),
/** Case IDs to be updated */
diff --git a/x-pack/plugins/security_solution/public/cases/components/case_view/index.tsx b/x-pack/plugins/security_solution/public/cases/components/case_view/index.tsx
index 5474fcb47d87e1..10a9e27fee1cfc 100644
--- a/x-pack/plugins/security_solution/public/cases/components/case_view/index.tsx
+++ b/x-pack/plugins/security_solution/public/cases/components/case_view/index.tsx
@@ -35,6 +35,7 @@ import { useInsertTimeline } from '../use_insert_timeline';
import { SpyRoute } from '../../../common/utils/route/spy_routes';
import * as timelineMarkdownPlugin from '../../../common/components/markdown_editor/plugins/timeline';
import { CaseDetailsRefreshContext } from '../../../common/components/endpoint/host_isolation/endpoint_host_isolation_cases_context';
+import { getEndpointDetailsPath } from '../../../management/common/routing';
interface Props {
caseId: string;
@@ -162,6 +163,14 @@ export const CaseView = React.memo(({ caseId, subCaseId, userCanCrud }: Props) =
[dispatch]
);
+ const endpointDetailsHref = (endpointId: string) =>
+ formatUrl(
+ getEndpointDetailsPath({
+ name: 'endpointActivityLog',
+ selected_endpoint: endpointId,
+ })
+ );
+
const onComponentInitialized = useCallback(() => {
dispatch(
timelineActions.createTimeline({
@@ -220,6 +229,20 @@ export const CaseView = React.memo(({ caseId, subCaseId, userCanCrud }: Props) =
getCaseDetailHrefWithCommentId,
onCaseDataSuccess,
onComponentInitialized,
+ actionsNavigation: {
+ href: endpointDetailsHref,
+ onClick: (endpointId: string, e) => {
+ if (e) {
+ e.preventDefault();
+ }
+ return navigateToApp(APP_ID, {
+ path: getEndpointDetailsPath({
+ name: 'endpointActivityLog',
+ selected_endpoint: endpointId,
+ }),
+ });
+ },
+ },
ruleDetailsNavigation: {
href: getDetectionsRuleDetailsHref,
onClick: async (ruleId: string | null | undefined, e) => {
diff --git a/x-pack/plugins/security_solution/public/common/lib/endpoint_isolation/mocks.ts b/x-pack/plugins/security_solution/public/common/lib/endpoint_isolation/mocks.ts
index 61989184963163..256359e40b6e27 100644
--- a/x-pack/plugins/security_solution/public/common/lib/endpoint_isolation/mocks.ts
+++ b/x-pack/plugins/security_solution/public/common/lib/endpoint_isolation/mocks.ts
@@ -14,7 +14,6 @@ import { ISOLATE_HOST_ROUTE, UNISOLATE_HOST_ROUTE } from '../../../../common/end
export const hostIsolationRequestBodyMock = (): HostIsolationRequestBody => {
return {
- agent_ids: ['fd8a122b-4c54-4c05-b295-111'],
endpoint_ids: ['88c04a90-b19c-11eb-b838-222'],
alert_ids: ['88c04a90-b19c-11eb-b838-333'],
case_ids: ['88c04a90-b19c-11eb-b838-444'],
diff --git a/x-pack/plugins/security_solution/public/management/common/routing.ts b/x-pack/plugins/security_solution/public/management/common/routing.ts
index 93d0642c6b3b6f..9cb25dd4bb5a57 100644
--- a/x-pack/plugins/security_solution/public/management/common/routing.ts
+++ b/x-pack/plugins/security_solution/public/management/common/routing.ts
@@ -66,7 +66,12 @@ export const getEndpointListPath = (
export const getEndpointDetailsPath = (
props: {
- name: 'endpointDetails' | 'endpointPolicyResponse' | 'endpointIsolate' | 'endpointUnIsolate';
+ name:
+ | 'endpointDetails'
+ | 'endpointPolicyResponse'
+ | 'endpointIsolate'
+ | 'endpointUnIsolate'
+ | 'endpointActivityLog';
} & EndpointIndexUIQueryParams &
EndpointDetailsUrlProps,
search?: string
@@ -85,6 +90,9 @@ export const getEndpointDetailsPath = (
case 'endpointPolicyResponse':
queryParams.show = 'policy_response';
break;
+ case 'endpointActivityLog':
+ queryParams.show = 'activity_log';
+ break;
}
const urlQueryParams = querystringStringify(
diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/actions/isolation.test.ts b/x-pack/plugins/security_solution/server/endpoint/routes/actions/isolation.test.ts
index 5ce27164dc8787..1b1490d3af0727 100644
--- a/x-pack/plugins/security_solution/server/endpoint/routes/actions/isolation.test.ts
+++ b/x-pack/plugins/security_solution/server/endpoint/routes/actions/isolation.test.ts
@@ -22,6 +22,7 @@ import {
createMockPackageService,
createRouteHandlerContext,
} from '../../mocks';
+import { HostIsolationRequestSchema } from '../../../../common/endpoint/schema/actions';
import { registerHostIsolationRoutes } from './isolation';
import { createMockConfig } from '../../../lib/detection_engine/routes/__mocks__';
import { LicenseService } from '../../../../common/license';
@@ -55,282 +56,310 @@ const Platinum = licenseMock.createLicense({ license: { type: 'platinum', mode:
const Gold = licenseMock.createLicense({ license: { type: 'gold', mode: 'gold' } });
describe('Host Isolation', () => {
- let endpointAppContextService: EndpointAppContextService;
- let mockResponse: jest.Mocked;
- let licenseService: LicenseService;
- let licenseEmitter: Subject;
+ describe('schema', () => {
+ it('should require at least 1 Endpoint ID', () => {
+ expect(() => {
+ HostIsolationRequestSchema.body.validate({});
+ }).toThrow();
+ });
- let callRoute: (
- routePrefix: string,
- opts: CallRouteInterface
- ) => Promise>;
- const superUser = {
- username: 'superuser',
- roles: ['superuser'],
- };
+ it('should accept an Endpoint ID as the only required field', () => {
+ expect(() => {
+ HostIsolationRequestSchema.body.validate({
+ endpoint_ids: ['ABC-XYZ-000'],
+ });
+ }).not.toThrow();
+ });
- const docGen = new EndpointDocGenerator();
+ it('should accept a comment', () => {
+ expect(() => {
+ HostIsolationRequestSchema.body.validate({
+ endpoint_ids: ['ABC-XYZ-000'],
+ comment: 'a user comment',
+ });
+ }).not.toThrow();
+ });
- beforeEach(() => {
- // instantiate... everything
- const mockScopedClient = elasticsearchServiceMock.createScopedClusterClient();
- const mockClusterClient = elasticsearchServiceMock.createClusterClient();
- mockClusterClient.asScoped.mockReturnValue(mockScopedClient);
- const routerMock = httpServiceMock.createRouter();
- mockResponse = httpServerMock.createResponseFactory();
- const startContract = createMockEndpointAppContextServiceStartContract();
- endpointAppContextService = new EndpointAppContextService();
- const mockSavedObjectClient = savedObjectsClientMock.create();
- const mockPackageService = createMockPackageService();
- mockPackageService.getInstalledEsAssetReferences.mockReturnValue(
- Promise.resolve([
- {
- id: 'logs-endpoint.events.security',
- type: ElasticsearchAssetType.indexTemplate,
- },
- {
- id: `${metadataTransformPrefix}-0.16.0-dev.0`,
- type: ElasticsearchAssetType.transform,
- },
- ])
- );
- licenseEmitter = new Subject();
- licenseService = new LicenseService();
- licenseService.start(licenseEmitter);
- endpointAppContextService.start({
- ...startContract,
- licenseService,
- packageService: mockPackageService,
+ it('should accept alert IDs', () => {
+ expect(() => {
+ HostIsolationRequestSchema.body.validate({
+ endpoint_ids: ['ABC-XYZ-000'],
+ alert_ids: ['0000000-000-00'],
+ });
+ }).not.toThrow();
});
- // add the host isolation route handlers to routerMock
- registerHostIsolationRoutes(routerMock, {
- logFactory: loggingSystemMock.create(),
- service: endpointAppContextService,
- config: () => Promise.resolve(createMockConfig()),
- experimentalFeatures: parseExperimentalConfigValue(createMockConfig().enableExperimental),
+ it('should accept case IDs', () => {
+ expect(() => {
+ HostIsolationRequestSchema.body.validate({
+ endpoint_ids: ['ABC-XYZ-000'],
+ case_ids: ['000000000-000-000'],
+ });
+ }).not.toThrow();
});
+ });
+
+ describe('handler', () => {
+ let endpointAppContextService: EndpointAppContextService;
+ let mockResponse: jest.Mocked;
+ let licenseService: LicenseService;
+ let licenseEmitter: Subject;
- // define a convenience function to execute an API call for a given route, body, and mocked response from ES
- // it returns the requestContext mock used in the call, to assert internal calls (e.g. the indexed document)
- callRoute = async (
+ let callRoute: (
routePrefix: string,
- { body, idxResponse, searchResponse, mockUser, license }: CallRouteInterface
- ): Promise> => {
- const asUser = mockUser ? mockUser : superUser;
- (startContract.security.authc.getCurrentUser as jest.Mock).mockImplementationOnce(
- () => asUser
- );
- const ctx = createRouteHandlerContext(mockScopedClient, mockSavedObjectClient);
- const withIdxResp = idxResponse ? idxResponse : { statusCode: 201 };
- ctx.core.elasticsearch.client.asCurrentUser.index = jest
- .fn()
- .mockImplementationOnce(() => Promise.resolve(withIdxResp));
- ctx.core.elasticsearch.client.asCurrentUser.search = jest
- .fn()
- .mockImplementationOnce(() =>
- Promise.resolve({ body: createV2SearchResponse(searchResponse) })
- );
- const withLicense = license ? license : Platinum;
- licenseEmitter.next(withLicense);
- const mockRequest = httpServerMock.createKibanaRequest({ body });
- const [, routeHandler]: [
- RouteConfig,
- RequestHandler
- ] = routerMock.post.mock.calls.find(([{ path }]) => path.startsWith(routePrefix))!;
- await routeHandler(ctx, mockRequest, mockResponse);
- return (ctx as unknown) as jest.Mocked;
+ opts: CallRouteInterface
+ ) => Promise>;
+ const superUser = {
+ username: 'superuser',
+ roles: ['superuser'],
};
- });
-
- afterEach(() => {
- endpointAppContextService.stop();
- licenseService.stop();
- licenseEmitter.complete();
- });
- it('errors if no endpoint or agent is provided', async () => {
- await callRoute(ISOLATE_HOST_ROUTE, {});
- expect(mockResponse.badRequest).toBeCalled();
- });
- it('succeeds when an agent ID is provided', async () => {
- await callRoute(ISOLATE_HOST_ROUTE, { body: { agent_ids: ['XYZ'] } });
- expect(mockResponse.ok).toBeCalled();
- });
- it('reports elasticsearch errors creating an action', async () => {
- const ErrMessage = 'something went wrong?';
+ const docGen = new EndpointDocGenerator();
- await callRoute(ISOLATE_HOST_ROUTE, {
- body: { agent_ids: ['XYZ'] },
- idxResponse: {
- statusCode: 500,
- body: {
- result: ErrMessage,
- },
- },
- });
- expect(mockResponse.ok).not.toBeCalled();
- const response = mockResponse.customError.mock.calls[0][0];
- expect(response.statusCode).toEqual(500);
- expect((response.body as Error).message).toEqual(ErrMessage);
- });
- it('accepts a comment field', async () => {
- await callRoute(ISOLATE_HOST_ROUTE, { body: { agent_ids: ['XYZ'], comment: 'XYZ' } });
- expect(mockResponse.ok).toBeCalled();
- });
- it('sends the action to the requested agent', async () => {
- const AgentID = '123-ABC';
- const ctx = await callRoute(ISOLATE_HOST_ROUTE, {
- body: { agent_ids: [AgentID] },
- });
- const actionDoc: EndpointAction = (ctx.core.elasticsearch.client.asCurrentUser
- .index as jest.Mock).mock.calls[0][0].body;
- expect(actionDoc.agents).toContain(AgentID);
- });
- it('records the user who performed the action to the action record', async () => {
- const testU = { username: 'testuser', roles: ['superuser'] };
- const ctx = await callRoute(ISOLATE_HOST_ROUTE, {
- body: { agent_ids: ['XYZ'] },
- mockUser: testU,
- });
- const actionDoc: EndpointAction = (ctx.core.elasticsearch.client.asCurrentUser
- .index as jest.Mock).mock.calls[0][0].body;
- expect(actionDoc.user_id).toEqual(testU.username);
- });
- it('records the comment in the action payload', async () => {
- const CommentText = "I am isolating this because it's Friday";
- const ctx = await callRoute(ISOLATE_HOST_ROUTE, {
- body: { agent_ids: ['XYZ'], comment: CommentText },
- });
- const actionDoc: EndpointAction = (ctx.core.elasticsearch.client.asCurrentUser
- .index as jest.Mock).mock.calls[0][0].body;
- expect(actionDoc.data.comment).toEqual(CommentText);
- });
- it('creates an action and returns its ID', async () => {
- const ctx = await callRoute(ISOLATE_HOST_ROUTE, {
- body: { agent_ids: ['XYZ'], comment: 'XYZ' },
- });
- const actionDoc: EndpointAction = (ctx.core.elasticsearch.client.asCurrentUser
- .index as jest.Mock).mock.calls[0][0].body;
- const actionID = actionDoc.action_id;
- expect(mockResponse.ok).toBeCalled();
- expect((mockResponse.ok.mock.calls[0][0]?.body as HostIsolationResponse).action).toEqual(
- actionID
- );
- });
-
- it('succeeds when just an endpoint ID is provided', async () => {
- await callRoute(ISOLATE_HOST_ROUTE, { body: { endpoint_ids: ['XYZ'] } });
- expect(mockResponse.ok).toBeCalled();
- });
- it('sends the action to the correct agent when endpoint ID is given', async () => {
- const doc = docGen.generateHostMetadata();
- const AgentID = doc.elastic.agent.id;
+ beforeEach(() => {
+ // instantiate... everything
+ const mockScopedClient = elasticsearchServiceMock.createScopedClusterClient();
+ const mockClusterClient = elasticsearchServiceMock.createClusterClient();
+ mockClusterClient.asScoped.mockReturnValue(mockScopedClient);
+ const routerMock = httpServiceMock.createRouter();
+ mockResponse = httpServerMock.createResponseFactory();
+ const startContract = createMockEndpointAppContextServiceStartContract();
+ endpointAppContextService = new EndpointAppContextService();
+ const mockSavedObjectClient = savedObjectsClientMock.create();
+ const mockPackageService = createMockPackageService();
+ mockPackageService.getInstalledEsAssetReferences.mockReturnValue(
+ Promise.resolve([
+ {
+ id: 'logs-endpoint.events.security',
+ type: ElasticsearchAssetType.indexTemplate,
+ },
+ {
+ id: `${metadataTransformPrefix}-0.16.0-dev.0`,
+ type: ElasticsearchAssetType.transform,
+ },
+ ])
+ );
+ licenseEmitter = new Subject();
+ licenseService = new LicenseService();
+ licenseService.start(licenseEmitter);
+ endpointAppContextService.start({
+ ...startContract,
+ licenseService,
+ packageService: mockPackageService,
+ });
- const ctx = await callRoute(ISOLATE_HOST_ROUTE, {
- body: { endpoint_ids: ['XYZ'] },
- searchResponse: doc,
- });
- const actionDoc: EndpointAction = (ctx.core.elasticsearch.client.asCurrentUser
- .index as jest.Mock).mock.calls[0][0].body;
- expect(actionDoc.agents).toContain(AgentID);
- });
- it('combines given agent IDs and endpoint IDs', async () => {
- const doc = docGen.generateHostMetadata();
- const explicitAgentID = 'XYZ';
- const lookupAgentID = doc.elastic.agent.id;
+ // add the host isolation route handlers to routerMock
+ registerHostIsolationRoutes(routerMock, {
+ logFactory: loggingSystemMock.create(),
+ service: endpointAppContextService,
+ config: () => Promise.resolve(createMockConfig()),
+ experimentalFeatures: parseExperimentalConfigValue(createMockConfig().enableExperimental),
+ });
- const ctx = await callRoute(ISOLATE_HOST_ROUTE, {
- body: { agent_ids: [explicitAgentID], endpoint_ids: ['XYZ'] },
- searchResponse: doc,
+ // define a convenience function to execute an API call for a given route, body, and mocked response from ES
+ // it returns the requestContext mock used in the call, to assert internal calls (e.g. the indexed document)
+ callRoute = async (
+ routePrefix: string,
+ { body, idxResponse, searchResponse, mockUser, license }: CallRouteInterface
+ ): Promise> => {
+ const asUser = mockUser ? mockUser : superUser;
+ (startContract.security.authc.getCurrentUser as jest.Mock).mockImplementationOnce(
+ () => asUser
+ );
+ const ctx = createRouteHandlerContext(mockScopedClient, mockSavedObjectClient);
+ const withIdxResp = idxResponse ? idxResponse : { statusCode: 201 };
+ ctx.core.elasticsearch.client.asCurrentUser.index = jest
+ .fn()
+ .mockImplementationOnce(() => Promise.resolve(withIdxResp));
+ ctx.core.elasticsearch.client.asCurrentUser.search = jest
+ .fn()
+ .mockImplementation(() =>
+ Promise.resolve({ body: createV2SearchResponse(searchResponse) })
+ );
+ const withLicense = license ? license : Platinum;
+ licenseEmitter.next(withLicense);
+ const mockRequest = httpServerMock.createKibanaRequest({ body });
+ const [, routeHandler]: [
+ RouteConfig,
+ RequestHandler
+ ] = routerMock.post.mock.calls.find(([{ path }]) => path.startsWith(routePrefix))!;
+ await routeHandler(ctx, mockRequest, mockResponse);
+ return (ctx as unknown) as jest.Mocked;
+ };
});
- const actionDoc: EndpointAction = (ctx.core.elasticsearch.client.asCurrentUser
- .index as jest.Mock).mock.calls[0][0].body;
- expect(actionDoc.agents).toContain(explicitAgentID);
- expect(actionDoc.agents).toContain(lookupAgentID);
- });
- it('sends the isolate command payload from the isolate route', async () => {
- const ctx = await callRoute(ISOLATE_HOST_ROUTE, {
- body: { agent_ids: ['XYZ'] },
+ afterEach(() => {
+ endpointAppContextService.stop();
+ licenseService.stop();
+ licenseEmitter.complete();
});
- const actionDoc: EndpointAction = (ctx.core.elasticsearch.client.asCurrentUser
- .index as jest.Mock).mock.calls[0][0].body;
- expect(actionDoc.data.command).toEqual('isolate');
- });
- it('sends the unisolate command payload from the unisolate route', async () => {
- const ctx = await callRoute(UNISOLATE_HOST_ROUTE, {
- body: { agent_ids: ['XYZ'] },
+ it('succeeds when an endpoint ID is provided', async () => {
+ await callRoute(ISOLATE_HOST_ROUTE, { body: { endpoint_ids: ['XYZ'] } });
+ expect(mockResponse.ok).toBeCalled();
});
- const actionDoc: EndpointAction = (ctx.core.elasticsearch.client.asCurrentUser
- .index as jest.Mock).mock.calls[0][0].body;
- expect(actionDoc.data.command).toEqual('unisolate');
- });
+ it('reports elasticsearch errors creating an action', async () => {
+ const ErrMessage = 'something went wrong?';
- describe('License Level', () => {
- it('allows platinum license levels to isolate hosts', async () => {
await callRoute(ISOLATE_HOST_ROUTE, {
- body: { agent_ids: ['XYZ'] },
- license: Platinum,
+ body: { endpoint_ids: ['XYZ'] },
+ idxResponse: {
+ statusCode: 500,
+ body: {
+ result: ErrMessage,
+ },
+ },
});
+ expect(mockResponse.ok).not.toBeCalled();
+ const response = mockResponse.customError.mock.calls[0][0];
+ expect(response.statusCode).toEqual(500);
+ expect((response.body as Error).message).toEqual(ErrMessage);
+ });
+ it('accepts a comment field', async () => {
+ await callRoute(ISOLATE_HOST_ROUTE, { body: { endpoint_ids: ['XYZ'], comment: 'XYZ' } });
expect(mockResponse.ok).toBeCalled();
});
- it('prohibits license levels less than platinum from isolating hosts', async () => {
- licenseEmitter.next(Gold);
- await callRoute(ISOLATE_HOST_ROUTE, {
- body: { agent_ids: ['XYZ'] },
- license: Gold,
+ it('sends the action to the requested agent', async () => {
+ const metadataResponse = docGen.generateHostMetadata();
+ const AgentID = metadataResponse.elastic.agent.id;
+ const ctx = await callRoute(ISOLATE_HOST_ROUTE, {
+ body: { endpoint_ids: ['ABC-XYZ-000'] },
+ searchResponse: metadataResponse,
});
- expect(mockResponse.forbidden).toBeCalled();
+ const actionDoc: EndpointAction = (ctx.core.elasticsearch.client.asCurrentUser
+ .index as jest.Mock).mock.calls[0][0].body;
+ expect(actionDoc.agents).toContain(AgentID);
});
- it('allows any license level to unisolate', async () => {
- licenseEmitter.next(Gold);
- await callRoute(UNISOLATE_HOST_ROUTE, {
- body: { agent_ids: ['XYZ'] },
- license: Gold,
+ it('records the user who performed the action to the action record', async () => {
+ const testU = { username: 'testuser', roles: ['superuser'] };
+ const ctx = await callRoute(ISOLATE_HOST_ROUTE, {
+ body: { endpoint_ids: ['XYZ'] },
+ mockUser: testU,
});
+ const actionDoc: EndpointAction = (ctx.core.elasticsearch.client.asCurrentUser
+ .index as jest.Mock).mock.calls[0][0].body;
+ expect(actionDoc.user_id).toEqual(testU.username);
+ });
+ it('records the comment in the action payload', async () => {
+ const CommentText = "I am isolating this because it's Friday";
+ const ctx = await callRoute(ISOLATE_HOST_ROUTE, {
+ body: { endpoint_ids: ['XYZ'], comment: CommentText },
+ });
+ const actionDoc: EndpointAction = (ctx.core.elasticsearch.client.asCurrentUser
+ .index as jest.Mock).mock.calls[0][0].body;
+ expect(actionDoc.data.comment).toEqual(CommentText);
+ });
+ it('creates an action and returns its ID', async () => {
+ const ctx = await callRoute(ISOLATE_HOST_ROUTE, {
+ body: { endpoint_ids: ['XYZ'], comment: 'XYZ' },
+ });
+ const actionDoc: EndpointAction = (ctx.core.elasticsearch.client.asCurrentUser
+ .index as jest.Mock).mock.calls[0][0].body;
+ const actionID = actionDoc.action_id;
expect(mockResponse.ok).toBeCalled();
+ expect((mockResponse.ok.mock.calls[0][0]?.body as HostIsolationResponse).action).toEqual(
+ actionID
+ );
});
- });
- describe('User Level', () => {
- it('allows superuser to perform isolation', async () => {
- const superU = { username: 'foo', roles: ['superuser'] };
- await callRoute(ISOLATE_HOST_ROUTE, {
- body: { agent_ids: ['XYZ'] },
- mockUser: superU,
- });
+ it('succeeds when just an endpoint ID is provided', async () => {
+ await callRoute(ISOLATE_HOST_ROUTE, { body: { endpoint_ids: ['XYZ'] } });
expect(mockResponse.ok).toBeCalled();
});
- it('allows superuser to perform unisolation', async () => {
- const superU = { username: 'foo', roles: ['superuser'] };
- await callRoute(UNISOLATE_HOST_ROUTE, {
- body: { agent_ids: ['XYZ'] },
- mockUser: superU,
+ it('sends the action to the correct agent when endpoint ID is given', async () => {
+ const doc = docGen.generateHostMetadata();
+ const AgentID = doc.elastic.agent.id;
+
+ const ctx = await callRoute(ISOLATE_HOST_ROUTE, {
+ body: { endpoint_ids: ['XYZ'] },
+ searchResponse: doc,
});
- expect(mockResponse.ok).toBeCalled();
+ const actionDoc: EndpointAction = (ctx.core.elasticsearch.client.asCurrentUser
+ .index as jest.Mock).mock.calls[0][0].body;
+ expect(actionDoc.agents).toContain(AgentID);
});
- it('prohibits non-admin user from performing isolation', async () => {
- const superU = { username: 'foo', roles: ['user'] };
- await callRoute(ISOLATE_HOST_ROUTE, {
- body: { agent_ids: ['XYZ'] },
- mockUser: superU,
+ it('sends the isolate command payload from the isolate route', async () => {
+ const ctx = await callRoute(ISOLATE_HOST_ROUTE, {
+ body: { endpoint_ids: ['XYZ'] },
});
- expect(mockResponse.forbidden).toBeCalled();
+ const actionDoc: EndpointAction = (ctx.core.elasticsearch.client.asCurrentUser
+ .index as jest.Mock).mock.calls[0][0].body;
+ expect(actionDoc.data.command).toEqual('isolate');
});
- it('prohibits non-admin user from performing unisolation', async () => {
- const superU = { username: 'foo', roles: ['user'] };
- await callRoute(UNISOLATE_HOST_ROUTE, {
- body: { agent_ids: ['XYZ'] },
- mockUser: superU,
+ it('sends the unisolate command payload from the unisolate route', async () => {
+ const ctx = await callRoute(UNISOLATE_HOST_ROUTE, {
+ body: { endpoint_ids: ['XYZ'] },
+ });
+ const actionDoc: EndpointAction = (ctx.core.elasticsearch.client.asCurrentUser
+ .index as jest.Mock).mock.calls[0][0].body;
+ expect(actionDoc.data.command).toEqual('unisolate');
+ });
+
+ describe('License Level', () => {
+ it('allows platinum license levels to isolate hosts', async () => {
+ await callRoute(ISOLATE_HOST_ROUTE, {
+ body: { endpoint_ids: ['XYZ'] },
+ license: Platinum,
+ });
+ expect(mockResponse.ok).toBeCalled();
+ });
+ it('prohibits license levels less than platinum from isolating hosts', async () => {
+ licenseEmitter.next(Gold);
+ await callRoute(ISOLATE_HOST_ROUTE, {
+ body: { endpoint_ids: ['XYZ'] },
+ license: Gold,
+ });
+ expect(mockResponse.forbidden).toBeCalled();
+ });
+ it('allows any license level to unisolate', async () => {
+ licenseEmitter.next(Gold);
+ await callRoute(UNISOLATE_HOST_ROUTE, {
+ body: { endpoint_ids: ['XYZ'] },
+ license: Gold,
+ });
+ expect(mockResponse.ok).toBeCalled();
});
- expect(mockResponse.forbidden).toBeCalled();
});
- });
- describe('Cases', () => {
- it.todo('logs a comment to the provided case');
- it.todo('logs a comment to any cases associated with the given alert');
+ describe('User Level', () => {
+ it('allows superuser to perform isolation', async () => {
+ const superU = { username: 'foo', roles: ['superuser'] };
+ await callRoute(ISOLATE_HOST_ROUTE, {
+ body: { endpoint_ids: ['XYZ'] },
+ mockUser: superU,
+ });
+ expect(mockResponse.ok).toBeCalled();
+ });
+ it('allows superuser to perform unisolation', async () => {
+ const superU = { username: 'foo', roles: ['superuser'] };
+ await callRoute(UNISOLATE_HOST_ROUTE, {
+ body: { endpoint_ids: ['XYZ'] },
+ mockUser: superU,
+ });
+ expect(mockResponse.ok).toBeCalled();
+ });
+
+ it('prohibits non-admin user from performing isolation', async () => {
+ const superU = { username: 'foo', roles: ['user'] };
+ await callRoute(ISOLATE_HOST_ROUTE, {
+ body: { endpoint_ids: ['XYZ'] },
+ mockUser: superU,
+ });
+ expect(mockResponse.forbidden).toBeCalled();
+ });
+ it('prohibits non-admin user from performing unisolation', async () => {
+ const superU = { username: 'foo', roles: ['user'] };
+ await callRoute(UNISOLATE_HOST_ROUTE, {
+ body: { endpoint_ids: ['XYZ'] },
+ mockUser: superU,
+ });
+ expect(mockResponse.forbidden).toBeCalled();
+ });
+ });
+
+ describe('Cases', () => {
+ it.todo('logs a comment to the provided case');
+ it.todo('logs a comment to any cases associated with the given alert');
+ });
});
});
diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/actions/isolation.ts b/x-pack/plugins/security_solution/server/endpoint/routes/actions/isolation.ts
index 785434aa17ec65..45063ca92e2b0a 100644
--- a/x-pack/plugins/security_solution/server/endpoint/routes/actions/isolation.ts
+++ b/x-pack/plugins/security_solution/server/endpoint/routes/actions/isolation.ts
@@ -14,12 +14,12 @@ import { CasesByAlertId } from '../../../../../cases/common/api/cases/case';
import { HostIsolationRequestSchema } from '../../../../common/endpoint/schema/actions';
import { ISOLATE_HOST_ROUTE, UNISOLATE_HOST_ROUTE } from '../../../../common/endpoint/constants';
import { AGENT_ACTIONS_INDEX } from '../../../../../fleet/common';
-import { EndpointAction } from '../../../../common/endpoint/types';
+import { EndpointAction, HostMetadata } from '../../../../common/endpoint/types';
import {
SecuritySolutionPluginRouter,
SecuritySolutionRequestHandlerContext,
} from '../../../types';
-import { getAgentIDsForEndpoints } from '../../services';
+import { getMetadataForEndpoints } from '../../services';
import { EndpointAppContext } from '../../types';
import { APP_ID } from '../../../../common/constants';
import { userCanIsolate } from '../../../../common/endpoint/actions';
@@ -61,19 +61,7 @@ export const isolationRequestHandler = function (
TypeOf,
SecuritySolutionRequestHandlerContext
> {
- // eslint-disable-next-line complexity
return async (context, req, res) => {
- if (
- (!req.body.agent_ids || req.body.agent_ids.length === 0) &&
- (!req.body.endpoint_ids || req.body.endpoint_ids.length === 0)
- ) {
- return res.badRequest({
- body: {
- message: 'At least one agent ID or endpoint ID is required',
- },
- });
- }
-
// only allow admin users
const user = endpointContext.service.security?.authc.getCurrentUser(req);
if (!userCanIsolate(user?.roles)) {
@@ -93,13 +81,9 @@ export const isolationRequestHandler = function (
});
}
- // translate any endpoint_ids into agent_ids
- let agentIDs = req.body.agent_ids?.slice() || [];
- if (req.body.endpoint_ids && req.body.endpoint_ids.length > 0) {
- const newIDs = await getAgentIDsForEndpoints(req.body.endpoint_ids, context, endpointContext);
- agentIDs = agentIDs.concat(newIDs);
- }
- agentIDs = [...new Set(agentIDs)]; // dedupe
+ // fetch the Agent IDs to send the commands to
+ const endpointIDs = [...new Set(req.body.endpoint_ids)]; // dedupe
+ const endpointData = await getMetadataForEndpoints(endpointIDs, context, endpointContext);
const casesClient = await endpointContext.service.getCasesClient(req);
@@ -134,7 +118,7 @@ export const isolationRequestHandler = function (
expiration: moment().add(2, 'weeks').toISOString(),
type: 'INPUT_ACTION',
input_type: 'endpoint',
- agents: agentIDs,
+ agents: endpointData.map((endpt: HostMetadata) => endpt.elastic.agent.id),
user_id: user!.username,
data: {
command: isolate ? 'isolate' : 'unisolate',
@@ -158,25 +142,24 @@ export const isolationRequestHandler = function (
});
}
- const commentLines: string[] = [];
-
- commentLines.push(`${isolate ? 'I' : 'Uni'}solate action was sent to the following Agents:`);
- // lines of markdown links, inside a code block
-
- commentLines.push(`${agentIDs.map((a) => `- [${a}](/app/fleet#/agents/${a})`).join('\n')}`);
- if (req.body.comment) {
- commentLines.push(`\n\nWith Comment:\n> ${req.body.comment}`);
- }
-
// Update all cases with a comment
if (caseIDs.length > 0) {
+ const targets = endpointData.map((endpt: HostMetadata) => ({
+ hostname: endpt.host.hostname,
+ endpointId: endpt.agent.id,
+ }));
+
await Promise.all(
caseIDs.map((caseId) =>
casesClient.attachments.add({
caseId,
comment: {
- comment: commentLines.join('\n'),
- type: CommentType.user,
+ type: CommentType.actions,
+ comment: req.body.comment || '',
+ actions: {
+ targets,
+ type: isolate ? 'isolate' : 'unisolate',
+ },
owner: APP_ID,
},
})
diff --git a/x-pack/plugins/security_solution/server/endpoint/services/index.ts b/x-pack/plugins/security_solution/server/endpoint/services/index.ts
index 9fabd043e2950d..8bf64999c746a1 100644
--- a/x-pack/plugins/security_solution/server/endpoint/services/index.ts
+++ b/x-pack/plugins/security_solution/server/endpoint/services/index.ts
@@ -6,4 +6,4 @@
*/
export * from './artifacts';
-export { getAgentIDsForEndpoints } from './lookup_agent';
+export { getMetadataForEndpoints } from './metadata';
diff --git a/x-pack/plugins/security_solution/server/endpoint/services/lookup_agent.ts b/x-pack/plugins/security_solution/server/endpoint/services/metadata.ts
similarity index 89%
rename from x-pack/plugins/security_solution/server/endpoint/services/lookup_agent.ts
rename to x-pack/plugins/security_solution/server/endpoint/services/metadata.ts
index e82b548641290e..0ca1983aa68d51 100644
--- a/x-pack/plugins/security_solution/server/endpoint/services/lookup_agent.ts
+++ b/x-pack/plugins/security_solution/server/endpoint/services/metadata.ts
@@ -12,11 +12,11 @@ import { SecuritySolutionRequestHandlerContext } from '../../types';
import { getESQueryHostMetadataByIDs } from '../routes/metadata/query_builders';
import { EndpointAppContext } from '../types';
-export async function getAgentIDsForEndpoints(
+export async function getMetadataForEndpoints(
endpointIDs: string[],
requestHandlerContext: SecuritySolutionRequestHandlerContext,
endpointAppContext: EndpointAppContext
-): Promise {
+): Promise {
const queryStrategy = await endpointAppContext.service
?.getMetadataService()
?.queryStrategy(requestHandlerContext.core.savedObjects.client);
@@ -25,6 +25,5 @@ export async function getAgentIDsForEndpoints(
const esClient = requestHandlerContext.core.elasticsearch.client.asCurrentUser;
const { body } = await esClient.search(query as SearchRequest);
const hosts = queryStrategy!.queryResponseToHostListResult(body as SearchResponse);
-
- return hosts.resultList.map((x: HostMetadata): string => x.elastic.agent.id);
+ return hosts.resultList;
}
From 1ff2407b185b71f969f567369062c8a5b9a3e75c Mon Sep 17 00:00:00 2001
From: Kevin Logan <56395104+kevinlog@users.noreply.github.com>
Date: Tue, 29 Jun 2021 19:46:19 -0400
Subject: [PATCH 012/121] [Security Solution] Adds a version and OS check for
Host Isolation (#103026)
---
.../data_generators/base_data_generator.ts | 2 +-
.../common/endpoint/generate_data.ts | 8 +--
.../service/host_isolation/utils.test.ts | 66 +++++++++++++++++++
.../endpoint/service/host_isolation/utils.ts | 46 +++++++++++++
.../details/components/actions_menu.test.tsx | 4 ++
.../view/hooks/use_endpoint_action_items.tsx | 7 +-
.../pages/endpoint_hosts/view/index.test.tsx | 11 ++++
.../side_panel/event_details/index.tsx | 38 ++++++++---
8 files changed, 167 insertions(+), 15 deletions(-)
create mode 100644 x-pack/plugins/security_solution/common/endpoint/service/host_isolation/utils.test.ts
create mode 100644 x-pack/plugins/security_solution/common/endpoint/service/host_isolation/utils.ts
diff --git a/x-pack/plugins/security_solution/common/endpoint/data_generators/base_data_generator.ts b/x-pack/plugins/security_solution/common/endpoint/data_generators/base_data_generator.ts
index 1f3d4307197f8a..1c9adc8f2f9c39 100644
--- a/x-pack/plugins/security_solution/common/endpoint/data_generators/base_data_generator.ts
+++ b/x-pack/plugins/security_solution/common/endpoint/data_generators/base_data_generator.ts
@@ -102,7 +102,7 @@ export class BaseDataGenerator {
}
protected randomVersion(): string {
- return [6, ...this.randomNGenerator(10, 2)].map((x) => x.toString()).join('.');
+ return [7, ...this.randomNGenerator(20, 2)].map((x) => x.toString()).join('.');
}
protected randomChoice(choices: T[]): T {
diff --git a/x-pack/plugins/security_solution/common/endpoint/generate_data.ts b/x-pack/plugins/security_solution/common/endpoint/generate_data.ts
index b08d5649540db7..876cb3866c6147 100644
--- a/x-pack/plugins/security_solution/common/endpoint/generate_data.ts
+++ b/x-pack/plugins/security_solution/common/endpoint/generate_data.ts
@@ -51,7 +51,7 @@ export const ANCESTRY_LIMIT: number = 2;
const Windows: OSFields[] = [
{
- name: 'windows 10.0',
+ name: 'Windows',
full: 'Windows 10',
version: '10.0',
platform: 'Windows',
@@ -61,7 +61,7 @@ const Windows: OSFields[] = [
},
},
{
- name: 'windows 10.0',
+ name: 'Windows',
full: 'Windows Server 2016',
version: '10.0',
platform: 'Windows',
@@ -71,7 +71,7 @@ const Windows: OSFields[] = [
},
},
{
- name: 'windows 6.2',
+ name: 'Windows',
full: 'Windows Server 2012',
version: '6.2',
platform: 'Windows',
@@ -81,7 +81,7 @@ const Windows: OSFields[] = [
},
},
{
- name: 'windows 6.3',
+ name: 'Windows',
full: 'Windows Server 2012R2',
version: '6.3',
platform: 'Windows',
diff --git a/x-pack/plugins/security_solution/common/endpoint/service/host_isolation/utils.test.ts b/x-pack/plugins/security_solution/common/endpoint/service/host_isolation/utils.test.ts
new file mode 100644
index 00000000000000..7d3810bed8f44f
--- /dev/null
+++ b/x-pack/plugins/security_solution/common/endpoint/service/host_isolation/utils.test.ts
@@ -0,0 +1,66 @@
+/*
+ * 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 { isVersionSupported, isOsSupported, isIsolationSupported } from './utils';
+
+describe('Host Isolation utils isVersionSupported', () => {
+ test.each`
+ a | b | expected
+ ${'8.14.0'} | ${'7.13.0'} | ${true}
+ ${'7.14.0'} | ${'7.13.0'} | ${true}
+ ${'7.14.1'} | ${'7.14.0'} | ${true}
+ ${'8.14.0'} | ${'9.14.0'} | ${false}
+ ${'7.13.0'} | ${'7.14.0'} | ${false}
+ ${'7.14.0'} | ${'7.14.1'} | ${false}
+ ${'7.14.0'} | ${'7.14.0'} | ${true}
+ ${'7.14.0-SNAPSHOT'} | ${'7.14.0'} | ${true}
+ ${'7.14.0-SNAPSHOT-beta'} | ${'7.14.0'} | ${true}
+ ${'7.14.0-alpha'} | ${'7.14.0'} | ${true}
+ `('should validate that version $a is compatible($expected) to $b', ({ a, b, expected }) => {
+ expect(
+ isVersionSupported({
+ currentVersion: a,
+ minVersionRequired: b,
+ })
+ ).toEqual(expected);
+ });
+});
+
+describe('Host Isolation utils isOsSupported', () => {
+ test.each`
+ a | b | expected
+ ${'linux'} | ${['macos', 'linux']} | ${true}
+ ${'linux'} | ${['macos', 'windows']} | ${false}
+ `('should validate that os $a is compatible($expected) to $b', ({ a, b, expected }) => {
+ expect(
+ isOsSupported({
+ currentOs: a,
+ supportedOss: b,
+ })
+ ).toEqual(expected);
+ });
+});
+
+describe('Host Isolation utils isIsolationSupported', () => {
+ test.each`
+ a | b | expected
+ ${'windows'} | ${'7.14.0'} | ${true}
+ ${'linux'} | ${'7.13.0'} | ${false}
+ ${'linux'} | ${'7.14.0'} | ${false}
+ ${'macos'} | ${'7.13.0'} | ${false}
+ `(
+ 'should validate that os $a and version $b supports hostIsolation($expected)',
+ ({ a, b, expected }) => {
+ expect(
+ isIsolationSupported({
+ osName: a,
+ version: b,
+ })
+ ).toEqual(expected);
+ }
+ );
+});
diff --git a/x-pack/plugins/security_solution/common/endpoint/service/host_isolation/utils.ts b/x-pack/plugins/security_solution/common/endpoint/service/host_isolation/utils.ts
new file mode 100644
index 00000000000000..c5e57179bcb8d3
--- /dev/null
+++ b/x-pack/plugins/security_solution/common/endpoint/service/host_isolation/utils.ts
@@ -0,0 +1,46 @@
+/*
+ * 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 const isVersionSupported = ({
+ currentVersion,
+ minVersionRequired,
+}: {
+ currentVersion: string;
+ minVersionRequired: string;
+}) => {
+ const parsedCurrentVersion = currentVersion.includes('-SNAPSHOT')
+ ? currentVersion.substring(0, currentVersion.indexOf('-'))
+ : currentVersion;
+ const tokenizedCurrent = parsedCurrentVersion
+ .split('.')
+ .map((token: string) => parseInt(token, 10));
+ const tokenizedMin = minVersionRequired.split('.').map((token: string) => parseInt(token, 10));
+
+ const versionNotSupported = tokenizedCurrent.some((token: number, index: number) => {
+ return token < tokenizedMin[index];
+ });
+
+ return !versionNotSupported;
+};
+
+export const isOsSupported = ({
+ currentOs,
+ supportedOss,
+}: {
+ currentOs: string;
+ supportedOss: string[];
+}) => {
+ return supportedOss.some((os) => currentOs === os);
+};
+
+export const isIsolationSupported = ({ osName, version }: { osName: string; version: string }) => {
+ const normalizedOs = osName.toLowerCase();
+ return (
+ isOsSupported({ currentOs: normalizedOs, supportedOss: ['macos', 'windows'] }) &&
+ isVersionSupported({ currentVersion: version, minVersionRequired: '7.14.0' })
+ );
+};
diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/components/actions_menu.test.tsx b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/components/actions_menu.test.tsx
index 04fd8cd715c879..56ff523e2c887b 100644
--- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/components/actions_menu.test.tsx
+++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/components/actions_menu.test.tsx
@@ -46,6 +46,10 @@ describe('When using the Endpoint Details Actions Menu', () => {
// Safe to mutate this mocked data
// @ts-ignore
endpointHost.metadata.Endpoint.state.isolation = isolation;
+ // @ts-ignore
+ endpointHost.metadata.host.os.name = 'Windows';
+ // @ts-ignore
+ endpointHost.metadata.agent.version = '7.14.0';
httpMocks.responseProvider.metadataDetails.mockReturnValue(endpointHost);
};
diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/hooks/use_endpoint_action_items.tsx b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/hooks/use_endpoint_action_items.tsx
index 0422cbcaa4310b..584e6df1ff7818 100644
--- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/hooks/use_endpoint_action_items.tsx
+++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/hooks/use_endpoint_action_items.tsx
@@ -17,6 +17,7 @@ import { useAppUrl } from '../../../../../common/lib/kibana/hooks';
import { ContextMenuItemNavByRouterProps } from '../components/context_menu_item_nav_by_rotuer';
import { isEndpointHostIsolated } from '../../../../../common/utils/validators';
import { useLicense } from '../../../../../common/hooks/use_license';
+import { isIsolationSupported } from '../../../../../../common/endpoint/service/host_isolation/utils';
/**
* Returns a list (array) of actions for an individual endpoint
@@ -37,6 +38,10 @@ export const useEndpointActionItems = (
const endpointPolicyId = endpointMetadata.Endpoint.policy.applied.id;
const endpointHostName = endpointMetadata.host.hostname;
const fleetAgentId = endpointMetadata.elastic.agent.id;
+ const isolationSupported = isIsolationSupported({
+ osName: endpointMetadata.host.os.name,
+ version: endpointMetadata.agent.version,
+ });
const {
show,
selected_endpoint: _selectedEndpoint,
@@ -73,7 +78,7 @@ export const useEndpointActionItems = (
/>
),
});
- } else if (isPlatinumPlus) {
+ } else if (isPlatinumPlus && isolationSupported) {
// For Platinum++ licenses, users also have ability to isolate
isolationActions.push({
'data-test-subj': 'isolateLink',
diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.test.tsx b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.test.tsx
index 7bfd77e7dd9754..ee5ef52d00f184 100644
--- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.test.tsx
+++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.test.tsx
@@ -1189,6 +1189,17 @@ describe('when on the endpoint list page', () => {
isolation: false,
},
},
+ host: {
+ ...hosts[0].metadata.host,
+ os: {
+ ...hosts[0].metadata.host.os,
+ name: 'Windows',
+ },
+ },
+ agent: {
+ ...hosts[0].metadata.agent,
+ version: '7.14.0',
+ },
},
query_strategy_version: queryStrategyVersion,
};
diff --git a/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/index.tsx
index 58b991dafed67f..509c629dc287c8 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/index.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/index.tsx
@@ -32,6 +32,7 @@ import {
} from '../../../../detections/components/host_isolation/translations';
import { ALERT_DETAILS } from './translations';
import { useIsolationPrivileges } from '../../../../common/hooks/endpoint/use_isolate_privileges';
+import { isIsolationSupported } from '../../../../../common/endpoint/service/host_isolation/utils';
import { endpointAlertCheck } from '../../../../common/utils/endpoint_alert_check';
import { useWithCaseDetailsRefresh } from '../../../../common/components/endpoint/host_isolation/endpoint_host_isolation_cases_context';
@@ -102,6 +103,22 @@ const EventDetailsPanelComponent: React.FC = ({
return findAgentId ? findAgentId[0] : '';
}, [detailsData]);
+ const hostOsFamily = useMemo(() => {
+ const findOsName = find({ category: 'host', field: 'host.os.name' }, detailsData)?.values;
+ return findOsName ? findOsName[0] : '';
+ }, [detailsData]);
+
+ const agentVersion = useMemo(() => {
+ const findAgentVersion = find({ category: 'agent', field: 'agent.version' }, detailsData)
+ ?.values;
+ return findAgentVersion ? findAgentVersion[0] : '';
+ }, [detailsData]);
+
+ const isolationSupported = isIsolationSupported({
+ osName: hostOsFamily,
+ version: agentVersion,
+ });
+
const backToAlertDetailsLink = useMemo(() => {
return (
<>
@@ -164,15 +181,18 @@ const EventDetailsPanelComponent: React.FC = ({
/>
)}
- {isIsolationAllowed && isEndpointAlert && isHostIsolationPanelOpen === false && (
-
-
-
-
-
-
-
- )}
+ {isIsolationAllowed &&
+ isEndpointAlert &&
+ isolationSupported &&
+ isHostIsolationPanelOpen === false && (
+
+
+
+
+
+
+
+ )}
>
) : (
<>
From fee73488066583577dd98138115acb284926a0df Mon Sep 17 00:00:00 2001
From: Davis Plumlee <56367316+dplumlee@users.noreply.github.com>
Date: Tue, 29 Jun 2021 20:02:38 -0400
Subject: [PATCH 013/121] [Security Solution][Exceptions Lists] Adds overflow
container for exceptions list table (#103377)
---
.../rules/all/exceptions/columns.tsx | 27 +---
.../exceptions_overflow_display.tsx | 119 ++++++++++++++++++
2 files changed, 125 insertions(+), 21 deletions(-)
create mode 100644 x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/exceptions/exceptions_overflow_display.tsx
diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/exceptions/columns.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/exceptions/columns.tsx
index c4c938d5bb05ec..582ca0252604c6 100644
--- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/exceptions/columns.tsx
+++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/exceptions/columns.tsx
@@ -11,12 +11,10 @@ import React from 'react';
import { EuiButtonIcon, EuiBasicTableColumn, EuiToolTip } from '@elastic/eui';
import type { NamespaceType } from '@kbn/securitysolution-io-ts-list-types';
-import { Spacer } from '../../../../../../common/components/page';
import { FormatUrl } from '../../../../../../common/components/link_to';
-import { LinkAnchor } from '../../../../../../common/components/links';
import * as i18n from './translations';
import { ExceptionListInfo } from './use_all_exception_lists';
-import { getRuleDetailsUrl } from '../../../../../../common/components/link_to/redirect_to_detection_engine';
+import { ExceptionOverflowDisplay } from './exceptions_overflow_display';
export type AllExceptionListsColumns = EuiBasicTableColumn;
@@ -72,24 +70,11 @@ export const getAllExceptionListsColumns = (
width: '20%',
render: (value: ExceptionListInfo['rules']) => {
return (
- <>
- {value.map(({ id, name }, index) => (
-
- void }) => {
- ev.preventDefault();
- navigateToUrl(formatUrl(getRuleDetailsUrl(id)));
- }}
- href={formatUrl(getRuleDetailsUrl(id))}
- >
- {name}
-
- {index !== value.length - 1 ? ', ' : ''}
-
- ))}
- >
+
);
},
},
diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/exceptions/exceptions_overflow_display.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/exceptions/exceptions_overflow_display.tsx
new file mode 100644
index 00000000000000..d22d338d6f50a2
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/exceptions/exceptions_overflow_display.tsx
@@ -0,0 +1,119 @@
+/*
+ * 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 { EuiPopover, EuiBadgeGroup, EuiButton } from '@elastic/eui';
+import styled from 'styled-components';
+import * as i18n from '../../translations';
+import { Spacer } from '../../../../../../common/components/page';
+import { LinkAnchor } from '../../../../../../common/components/links';
+import { getRuleDetailsUrl } from '../../../../../../common/components/link_to/redirect_to_detection_engine';
+import { Rule } from '../../../../../containers/detection_engine/rules';
+import { FormatUrl } from '../../../../../../common/components/link_to';
+
+interface ExceptionOverflowDisplayProps {
+ rules: Rule[];
+ navigateToUrl: (url: string) => Promise;
+ formatUrl: FormatUrl;
+}
+
+interface OverflowListComponentProps {
+ rule: Rule;
+ index: number;
+}
+
+const ExceptionOverflowWrapper = styled(EuiBadgeGroup)`
+ width: 100%;
+`;
+
+const ExceptionOverflowPopoverWrapper = styled(EuiBadgeGroup)`
+ max-height: 200px;
+ max-width: 600px;
+ overflow: auto;
+`;
+
+const ExceptionOverflowPopoverButton = styled(EuiButton)`
+ font-size: ${({ theme }) => theme.eui.euiFontSizeXS}
+ font-weight: 500;
+ height: 20px;
+`;
+
+/**
+ * @param rules to display for filtering
+ */
+const ExceptionOverflowDisplayComponent = ({
+ rules,
+ navigateToUrl,
+ formatUrl,
+}: ExceptionOverflowDisplayProps) => {
+ const [isExceptionOverflowPopoverOpen, setIsExceptionOverflowPopoverOpen] = useState(false);
+
+ const OverflowListComponent = ({ rule: { id, name }, index }: OverflowListComponentProps) => {
+ return (
+
+ void }) => {
+ ev.preventDefault();
+ navigateToUrl(formatUrl(getRuleDetailsUrl(id)));
+ }}
+ href={formatUrl(getRuleDetailsUrl(id))}
+ >
+ {name}
+
+ {index !== rules.length - 1 ? ', ' : ''}
+
+ );
+ };
+
+ return (
+ <>
+ {rules.length <= 2 ? (
+
+ {rules.map((rule, index: number) => (
+
+ ))}
+
+ ) : (
+
+ {rules.slice(0, 2).map((rule, index: number) => (
+
+ ))}
+
+ setIsExceptionOverflowPopoverOpen(!isExceptionOverflowPopoverOpen)}
+ >
+ {i18n.COLUMN_SEE_ALL_POPOVER}
+
+ }
+ isOpen={isExceptionOverflowPopoverOpen}
+ closePopover={() => setIsExceptionOverflowPopoverOpen(!isExceptionOverflowPopoverOpen)}
+ repositionOnScroll
+ >
+
+ {rules.map((rule, index: number) => (
+
+ ))}
+
+
+
+ )}
+ >
+ );
+};
+
+export const ExceptionOverflowDisplay = React.memo(ExceptionOverflowDisplayComponent);
+
+ExceptionOverflowDisplay.displayName = 'ExceptionOverflowDisplay';
From 25db1df1a3f399f1e7537ff9d186c152eca37223 Mon Sep 17 00:00:00 2001
From: Chris Cowan
Date: Tue, 29 Jun 2021 17:07:56 -0700
Subject: [PATCH 014/121] [Monitoring] Migrated legacy Elasticsearch client for
8.0 (#101850)
---
.../server/es_client/instantiate_client.ts | 6 +-
.../collectors/get_usage_collector.test.ts | 19 +-
.../collectors/get_usage_collector.ts | 16 +-
.../kibana_monitoring/collectors/index.ts | 6 +-
.../collectors/lib/fetch_es_usage.test.ts | 110 ++++++------
.../collectors/lib/fetch_es_usage.ts | 34 +---
.../collectors/lib/fetch_license_type.test.ts | 41 +++--
.../collectors/lib/fetch_license_type.ts | 11 +-
.../lib/fetch_stack_product_usage.test.ts | 99 +++++++----
.../lib/fetch_stack_product_usage.ts | 14 +-
.../lib/get_stack_products_usage.test.ts | 15 +-
.../lib/get_stack_products_usage.ts | 6 +-
.../alerts/disable_watcher_cluster_alerts.ts | 17 +-
.../lib/alerts/fetch_ccr_read_exceptions.ts | 2 +-
.../server/lib/alerts/fetch_cluster_health.ts | 2 +-
.../server/lib/alerts/fetch_clusters.ts | 4 +-
.../alerts/fetch_cpu_usage_node_stats.test.ts | 2 +-
.../lib/alerts/fetch_cpu_usage_node_stats.ts | 3 +-
.../lib/alerts/fetch_disk_usage_node_stats.ts | 2 +-
.../alerts/fetch_elasticsearch_versions.ts | 2 +-
.../lib/alerts/fetch_index_shard_size.ts | 2 +-
.../lib/alerts/fetch_kibana_versions.ts | 2 +-
.../server/lib/alerts/fetch_licenses.ts | 2 +-
.../lib/alerts/fetch_logstash_versions.ts | 2 +-
.../alerts/fetch_memory_usage_node_stats.ts | 2 +-
.../alerts/fetch_missing_monitoring_data.ts | 2 +-
.../alerts/fetch_nodes_from_cluster_stats.ts | 2 +-
.../fetch_thread_pool_rejections_stats.ts | 2 +-
.../server/lib/apm/_get_time_of_last_event.ts | 2 +-
.../monitoring/server/lib/apm/get_apm_info.ts | 4 +-
.../monitoring/server/lib/apm/get_apms.ts | 4 +-
.../server/lib/apm/get_apms_for_clusters.js | 4 +-
.../monitoring/server/lib/apm/get_stats.js | 4 +-
.../server/lib/beats/get_beat_summary.ts | 4 +-
.../monitoring/server/lib/beats/get_beats.ts | 4 +-
.../lib/beats/get_beats_for_clusters.js | 4 +-
.../server/lib/beats/get_latest_stats.js | 4 +-
.../monitoring/server/lib/beats/get_stats.js | 4 +-
.../lib/cluster/flag_supported_clusters.ts | 4 +-
.../server/lib/cluster/get_cluster_license.ts | 4 +-
.../server/lib/cluster/get_clusters_state.ts | 4 +-
.../server/lib/cluster/get_clusters_stats.ts | 4 +-
.../server/lib/details/get_series.js | 2 +-
.../server/lib/elasticsearch/ccr.ts | 4 +-
.../lib/elasticsearch/get_last_recovery.ts | 2 +-
.../server/lib/elasticsearch/get_ml_jobs.ts | 8 +-
.../indices/get_index_summary.ts | 2 +-
.../lib/elasticsearch/indices/get_indices.ts | 4 +-
.../elasticsearch/nodes/get_node_summary.ts | 2 +-
.../nodes/get_nodes/get_node_ids.js | 4 +-
.../nodes/get_nodes/get_nodes.ts | 4 +-
.../get_indices_unassigned_shard_stats.ts | 2 +-
.../shards/get_nodes_shard_count.ts | 2 +-
.../shards/get_shard_allocation.ts | 2 +-
.../elasticsearch/shards/get_shard_stats.ts | 2 +-
.../elasticsearch/verify_monitoring_auth.js | 2 +-
.../lib/elasticsearch_settings/cluster.js | 11 +-
.../server/lib/kibana/get_kibana_info.ts | 4 +-
.../server/lib/kibana/get_kibanas.ts | 2 +-
.../lib/kibana/get_kibanas_for_clusters.js | 2 +-
.../server/lib/logs/get_log_types.ts | 4 +-
.../monitoring/server/lib/logs/get_logs.ts | 4 +-
.../lib/logstash/get_logstash_for_clusters.js | 2 +-
.../server/lib/logstash/get_node_info.ts | 4 +-
.../server/lib/logstash/get_nodes.ts | 2 +-
.../server/lib/logstash/get_pipeline_ids.js | 4 +-
.../logstash/get_pipeline_state_document.ts | 2 +-
.../get_pipeline_stats_aggregation.js | 4 +-
.../lib/logstash/get_pipeline_versions.js | 2 +-
.../get_pipeline_vertex_stats_aggregation.js | 4 +-
.../setup/collection/get_collection_status.js | 8 +-
.../monitoring/server/license_service.ts | 14 +-
x-pack/plugins/monitoring/server/plugin.ts | 162 ++++++++++--------
.../server/routes/api/v1/alerts/enable.ts | 2 +-
.../server/routes/api/v1/elasticsearch/ccr.ts | 2 +-
.../routes/api/v1/elasticsearch/ccr_shard.ts | 2 +-
.../elasticsearch_settings/check/cluster.js | 1 +
.../check/internal_monitoring.ts | 9 +-
.../monitoring/server/static_globals.ts | 71 ++++++--
.../get_all_stats.test.ts | 25 ++-
.../telemetry_collection/get_all_stats.ts | 4 +-
.../get_beats_stats.test.ts | 24 +--
.../telemetry_collection/get_beats_stats.ts | 23 +--
.../get_cluster_uuids.test.ts | 33 ++--
.../telemetry_collection/get_cluster_uuids.ts | 18 +-
.../telemetry_collection/get_es_stats.test.ts | 22 +--
.../telemetry_collection/get_es_stats.ts | 18 +-
.../get_high_level_stats.test.ts | 20 ++-
.../get_high_level_stats.ts | 27 +--
.../telemetry_collection/get_kibana_stats.ts | 4 +-
.../telemetry_collection/get_licenses.test.ts | 20 ++-
.../telemetry_collection/get_licenses.ts | 18 +-
.../get_logstash_stats.test.ts | 45 +++--
.../get_logstash_stats.ts | 48 +++---
...egister_monitoring_telemetry_collection.ts | 10 +-
x-pack/plugins/monitoring/server/types.ts | 7 +-
96 files changed, 685 insertions(+), 526 deletions(-)
diff --git a/x-pack/plugins/monitoring/server/es_client/instantiate_client.ts b/x-pack/plugins/monitoring/server/es_client/instantiate_client.ts
index a32943dc10272a..58da336a5447dc 100644
--- a/x-pack/plugins/monitoring/server/es_client/instantiate_client.ts
+++ b/x-pack/plugins/monitoring/server/es_client/instantiate_client.ts
@@ -6,7 +6,7 @@
*/
import { ConfigOptions } from 'elasticsearch';
-import { Logger, ILegacyCustomClusterClient } from 'kibana/server';
+import { Logger, ICustomClusterClient, ElasticsearchClientConfig } from 'kibana/server';
// @ts-ignore
import { monitoringBulk } from '../kibana_monitoring/lib/monitoring_bulk';
import { monitoringEndpointDisableWatches } from './monitoring_endpoint_disable_watches';
@@ -25,8 +25,8 @@ export function instantiateClient(
log: Logger,
createClient: (
type: string,
- clientConfig?: Partial
- ) => ILegacyCustomClusterClient
+ clientConfig?: Partial | undefined
+ ) => ICustomClusterClient
) {
const isMonitoringCluster = hasMonitoringCluster(elasticsearchConfig);
const cluster = createClient('monitoring', {
diff --git a/x-pack/plugins/monitoring/server/kibana_monitoring/collectors/get_usage_collector.test.ts b/x-pack/plugins/monitoring/server/kibana_monitoring/collectors/get_usage_collector.test.ts
index 03a3659b49ce19..9c72c1f8d3841b 100644
--- a/x-pack/plugins/monitoring/server/kibana_monitoring/collectors/get_usage_collector.test.ts
+++ b/x-pack/plugins/monitoring/server/kibana_monitoring/collectors/get_usage_collector.test.ts
@@ -6,11 +6,11 @@
*/
import { getMonitoringUsageCollector } from './get_usage_collector';
-import { fetchClustersLegacy } from '../../lib/alerts/fetch_clusters';
+import { fetchClusters } from '../../lib/alerts/fetch_clusters';
import { elasticsearchServiceMock } from '../../../../../../src/core/server/mocks';
jest.mock('../../lib/alerts/fetch_clusters', () => ({
- fetchClustersLegacy: jest.fn().mockImplementation(() => {
+ fetchClusters: jest.fn().mockImplementation(() => {
return [
{
clusterUuid: '1abc',
@@ -59,7 +59,8 @@ jest.mock('./lib/fetch_license_type', () => ({
}));
describe('getMonitoringUsageCollector', () => {
- const esClient = elasticsearchServiceMock.createLegacyClusterClient();
+ const esClient = elasticsearchServiceMock.createClusterClient();
+ const getEsClient = () => esClient;
const config: any = {
ui: {
ccs: {
@@ -72,7 +73,7 @@ describe('getMonitoringUsageCollector', () => {
const usageCollection: any = {
makeUsageCollector: jest.fn(),
};
- await getMonitoringUsageCollector(usageCollection, config, esClient);
+ getMonitoringUsageCollector(usageCollection, config, getEsClient);
const mock = (usageCollection.makeUsageCollector as jest.Mock).mock;
@@ -122,7 +123,7 @@ describe('getMonitoringUsageCollector', () => {
makeUsageCollector: jest.fn(),
};
- await getMonitoringUsageCollector(usageCollection, config, esClient);
+ getMonitoringUsageCollector(usageCollection, config, getEsClient);
const mock = (usageCollection.makeUsageCollector as jest.Mock).mock;
const args = mock.calls[0];
@@ -149,11 +150,11 @@ describe('getMonitoringUsageCollector', () => {
makeUsageCollector: jest.fn(),
};
- await getMonitoringUsageCollector(usageCollection, config, esClient);
+ getMonitoringUsageCollector(usageCollection, config, getEsClient);
const mock = (usageCollection.makeUsageCollector as jest.Mock).mock;
const args = mock.calls[0];
- (fetchClustersLegacy as jest.Mock).mockImplementation(() => {
+ (fetchClusters as jest.Mock).mockImplementation(() => {
return [];
});
@@ -169,11 +170,11 @@ describe('getMonitoringUsageCollector', () => {
makeUsageCollector: jest.fn(),
};
- await getMonitoringUsageCollector(usageCollection, config, esClient);
+ getMonitoringUsageCollector(usageCollection, config, getEsClient);
const mock = (usageCollection.makeUsageCollector as jest.Mock).mock;
const args = mock.calls[0];
- (fetchClustersLegacy as jest.Mock).mockImplementation(() => {
+ (fetchClusters as jest.Mock).mockImplementation(() => {
return [];
});
diff --git a/x-pack/plugins/monitoring/server/kibana_monitoring/collectors/get_usage_collector.ts b/x-pack/plugins/monitoring/server/kibana_monitoring/collectors/get_usage_collector.ts
index 6f638b6ff8f0e0..558a79e03dcb7a 100644
--- a/x-pack/plugins/monitoring/server/kibana_monitoring/collectors/get_usage_collector.ts
+++ b/x-pack/plugins/monitoring/server/kibana_monitoring/collectors/get_usage_collector.ts
@@ -6,20 +6,20 @@
*/
import { UsageCollectionSetup } from 'src/plugins/usage_collection/server';
-import { ILegacyClusterClient } from 'src/core/server';
+import { IClusterClient } from 'src/core/server';
import { MonitoringConfig } from '../../config';
-import { fetchAvailableCcsLegacy } from '../../lib/alerts/fetch_available_ccs';
+import { fetchAvailableCcs } from '../../lib/alerts/fetch_available_ccs';
import { getStackProductsUsage } from './lib/get_stack_products_usage';
import { fetchLicenseType } from './lib/fetch_license_type';
import { MonitoringUsage, StackProductUsage, MonitoringClusterStackProductUsage } from './types';
import { INDEX_PATTERN_ELASTICSEARCH } from '../../../common/constants';
import { getCcsIndexPattern } from '../../lib/alerts/get_ccs_index_pattern';
-import { fetchClustersLegacy } from '../../lib/alerts/fetch_clusters';
+import { fetchClusters } from '../../lib/alerts/fetch_clusters';
export function getMonitoringUsageCollector(
usageCollection: UsageCollectionSetup,
config: MonitoringConfig,
- legacyEsClient: ILegacyClusterClient
+ getClient: () => IClusterClient
) {
return usageCollection.makeUsageCollector({
type: 'monitoring',
@@ -103,12 +103,12 @@ export function getMonitoringUsageCollector(
},
fetch: async ({ kibanaRequest }) => {
const callCluster = kibanaRequest
- ? legacyEsClient.asScoped(kibanaRequest).callAsCurrentUser
- : legacyEsClient.callAsInternalUser;
+ ? getClient().asScoped(kibanaRequest).asCurrentUser
+ : getClient().asInternalUser;
const usageClusters: MonitoringClusterStackProductUsage[] = [];
- const availableCcs = config.ui.ccs.enabled ? await fetchAvailableCcsLegacy(callCluster) : [];
+ const availableCcs = config.ui.ccs.enabled ? await fetchAvailableCcs(callCluster) : [];
const elasticsearchIndex = getCcsIndexPattern(INDEX_PATTERN_ELASTICSEARCH, availableCcs);
- const clusters = await fetchClustersLegacy(callCluster, elasticsearchIndex);
+ const clusters = await fetchClusters(callCluster, elasticsearchIndex);
for (const cluster of clusters) {
const license = await fetchLicenseType(callCluster, availableCcs, cluster.clusterUuid);
const stackProducts = await getStackProductsUsage(
diff --git a/x-pack/plugins/monitoring/server/kibana_monitoring/collectors/index.ts b/x-pack/plugins/monitoring/server/kibana_monitoring/collectors/index.ts
index 5549b7aa968d0b..06d47f00447286 100644
--- a/x-pack/plugins/monitoring/server/kibana_monitoring/collectors/index.ts
+++ b/x-pack/plugins/monitoring/server/kibana_monitoring/collectors/index.ts
@@ -5,7 +5,7 @@
* 2.0.
*/
-import { ILegacyClusterClient } from 'src/core/server';
+import { IClusterClient } from 'src/core/server';
import { UsageCollectionSetup } from 'src/plugins/usage_collection/server';
import { getSettingsCollector } from './get_settings_collector';
import { getMonitoringUsageCollector } from './get_usage_collector';
@@ -16,10 +16,10 @@ export { KibanaSettingsCollector, getKibanaSettings } from './get_settings_colle
export function registerCollectors(
usageCollection: UsageCollectionSetup,
config: MonitoringConfig,
- legacyEsClient: ILegacyClusterClient
+ getClient: () => IClusterClient
) {
usageCollection.registerCollector(getSettingsCollector(usageCollection, config));
usageCollection.registerCollector(
- getMonitoringUsageCollector(usageCollection, config, legacyEsClient)
+ getMonitoringUsageCollector(usageCollection, config, getClient)
);
}
diff --git a/x-pack/plugins/monitoring/server/kibana_monitoring/collectors/lib/fetch_es_usage.test.ts b/x-pack/plugins/monitoring/server/kibana_monitoring/collectors/lib/fetch_es_usage.test.ts
index 6d01dbc120be37..b7d616a5a3cc77 100644
--- a/x-pack/plugins/monitoring/server/kibana_monitoring/collectors/lib/fetch_es_usage.test.ts
+++ b/x-pack/plugins/monitoring/server/kibana_monitoring/collectors/lib/fetch_es_usage.test.ts
@@ -5,41 +5,45 @@
* 2.0.
*/
+import { ElasticsearchClient } from 'kibana/server';
import { fetchESUsage } from './fetch_es_usage';
describe('fetchESUsage', () => {
const clusterUuid = '1abcde2';
const index = '.monitoring-es-*';
- const callCluster = jest.fn().mockImplementation(() => ({
- hits: {
- hits: [
- {
- _source: {
- cluster_stats: {
- nodes: {
- count: {
- total: 10,
+ const callCluster = ({
+ search: jest.fn().mockImplementation(() => ({
+ body: {
+ hits: {
+ hits: [
+ {
+ _source: {
+ cluster_stats: {
+ nodes: {
+ count: {
+ total: 10,
+ },
+ },
},
},
},
- },
+ ],
},
- ],
- },
- aggregations: {
- indices: {
- buckets: [
- {
- key: '.monitoring-es-2',
+ aggregations: {
+ indices: {
+ buckets: [
+ {
+ key: '.monitoring-es-2',
+ },
+ ],
},
- ],
+ },
},
- },
- }));
- const config: any = {};
+ })),
+ } as unknown) as ElasticsearchClient;
it('should return usage data for Elasticsearch', async () => {
- const result = await fetchESUsage(config, callCluster, clusterUuid, index);
+ const result = await fetchESUsage(callCluster, clusterUuid, index);
expect(result).toStrictEqual({
count: 10,
enabled: true,
@@ -48,33 +52,37 @@ describe('fetchESUsage', () => {
});
it('should handle some indices coming from Metricbeat', async () => {
- const customCallCluster = jest.fn().mockImplementation(() => ({
- hits: {
- hits: [
- {
- _source: {
- cluster_stats: {
- nodes: {
- count: {
- total: 10,
+ const customCallCluster = ({
+ search: jest.fn().mockImplementation(() => ({
+ body: {
+ hits: {
+ hits: [
+ {
+ _source: {
+ cluster_stats: {
+ nodes: {
+ count: {
+ total: 10,
+ },
+ },
},
},
},
- },
+ ],
},
- ],
- },
- aggregations: {
- indices: {
- buckets: [
- {
- key: '.monitoring-es-mb-2',
+ aggregations: {
+ indices: {
+ buckets: [
+ {
+ key: '.monitoring-es-mb-2',
+ },
+ ],
},
- ],
+ },
},
- },
- }));
- const result = await fetchESUsage(config, customCallCluster, clusterUuid, index);
+ })),
+ } as unknown) as ElasticsearchClient;
+ const result = await fetchESUsage(customCallCluster, clusterUuid, index);
expect(result).toStrictEqual({
count: 10,
enabled: true,
@@ -83,12 +91,16 @@ describe('fetchESUsage', () => {
});
it('should handle no monitoring data', async () => {
- const customCallCluster = jest.fn().mockImplementation(() => ({
- hits: {
- hits: [],
- },
- }));
- const result = await fetchESUsage(config, customCallCluster, clusterUuid, index);
+ const customCallCluster = ({
+ search: jest.fn().mockImplementation(() => ({
+ body: {
+ hits: {
+ hits: [],
+ },
+ },
+ })),
+ } as unknown) as ElasticsearchClient;
+ const result = await fetchESUsage(customCallCluster, clusterUuid, index);
expect(result).toStrictEqual({
count: 0,
enabled: false,
diff --git a/x-pack/plugins/monitoring/server/kibana_monitoring/collectors/lib/fetch_es_usage.ts b/x-pack/plugins/monitoring/server/kibana_monitoring/collectors/lib/fetch_es_usage.ts
index b96c119bf5ab8d..1fb2ba70f2ab11 100644
--- a/x-pack/plugins/monitoring/server/kibana_monitoring/collectors/lib/fetch_es_usage.ts
+++ b/x-pack/plugins/monitoring/server/kibana_monitoring/collectors/lib/fetch_es_usage.ts
@@ -5,30 +5,15 @@
* 2.0.
*/
-import { LegacyAPICaller } from 'src/core/server';
+import { ElasticsearchClient } from 'src/core/server';
import { get } from 'lodash';
-import { MonitoringConfig } from '../../../config';
+import { estypes } from '@elastic/elasticsearch';
import { StackProductUsage } from '../types';
-interface ESResponse {
- hits: {
- hits: ESResponseHits[];
- };
- aggregations: {
- indices: {
- buckets: ESIndicesBucket;
- };
- };
-}
-
interface ESIndicesBucket {
key: string;
}
-interface ESResponseHits {
- _source: ClusterStats;
-}
-
interface ClusterStats {
cluster_stats: {
nodes: {
@@ -41,16 +26,15 @@ interface ClusterStats {
}
export async function fetchESUsage(
- config: MonitoringConfig,
- callCluster: LegacyAPICaller,
+ callCluster: ElasticsearchClient,
clusterUuid: string,
index: string
): Promise {
- const params = {
+ const params: estypes.SearchRequest = {
index,
size: 1,
- ignoreUnavailable: true,
- filterPath: [
+ ignore_unavailable: true,
+ filter_path: [
'hits.hits._source.cluster_stats.nodes.count.total',
'aggregations.indices.buckets',
],
@@ -101,8 +85,8 @@ export async function fetchESUsage(
},
};
- const response = await callCluster('search', params);
- const esResponse = response as ESResponse;
+ const { body: response } = await callCluster.search(params);
+ const esResponse = response as estypes.SearchResponse;
if (esResponse.hits.hits.length === 0) {
return {
count: 0,
@@ -112,7 +96,7 @@ export async function fetchESUsage(
}
const hit = esResponse.hits.hits[0]._source;
- const count = hit.cluster_stats.nodes.count.total;
+ const count = hit?.cluster_stats.nodes.count.total || 0;
const buckets = get(esResponse, 'aggregations.indices.buckets', []) as ESIndicesBucket[];
const metricbeatUsed = Boolean(buckets.find((indexBucket) => indexBucket.key.includes('-mb-')));
diff --git a/x-pack/plugins/monitoring/server/kibana_monitoring/collectors/lib/fetch_license_type.test.ts b/x-pack/plugins/monitoring/server/kibana_monitoring/collectors/lib/fetch_license_type.test.ts
index b5c3293af40209..e4d801cfac0c3a 100644
--- a/x-pack/plugins/monitoring/server/kibana_monitoring/collectors/lib/fetch_license_type.test.ts
+++ b/x-pack/plugins/monitoring/server/kibana_monitoring/collectors/lib/fetch_license_type.test.ts
@@ -5,24 +5,29 @@
* 2.0.
*/
+import { ElasticsearchClient } from 'kibana/server';
import { fetchLicenseType } from './fetch_license_type';
describe('fetchLicenseType', () => {
const clusterUuid = '1abcde2';
const availableCcs: string[] = [];
- const callCluster = jest.fn().mockImplementation(() => ({
- hits: {
- hits: [
- {
- _source: {
- license: {
- type: 'trial',
+ const callCluster = ({
+ search: jest.fn().mockImplementation(() => ({
+ body: {
+ hits: {
+ hits: [
+ {
+ _source: {
+ license: {
+ type: 'trial',
+ },
+ },
},
- },
+ ],
},
- ],
- },
- }));
+ },
+ })),
+ } as unknown) as ElasticsearchClient;
it('should get the license type', async () => {
const result = await fetchLicenseType(callCluster, availableCcs, clusterUuid);
@@ -30,11 +35,15 @@ describe('fetchLicenseType', () => {
});
it('should handle no license data', async () => {
- const customCallCluster = jest.fn().mockImplementation(() => ({
- hits: {
- hits: [],
- },
- }));
+ const customCallCluster = ({
+ search: jest.fn().mockImplementation(() => ({
+ body: {
+ hits: {
+ hits: [],
+ },
+ },
+ })),
+ } as unknown) as ElasticsearchClient;
const result = await fetchLicenseType(customCallCluster, availableCcs, clusterUuid);
expect(result).toStrictEqual(null);
});
diff --git a/x-pack/plugins/monitoring/server/kibana_monitoring/collectors/lib/fetch_license_type.ts b/x-pack/plugins/monitoring/server/kibana_monitoring/collectors/lib/fetch_license_type.ts
index eed177a6647f20..f42623ff851ce7 100644
--- a/x-pack/plugins/monitoring/server/kibana_monitoring/collectors/lib/fetch_license_type.ts
+++ b/x-pack/plugins/monitoring/server/kibana_monitoring/collectors/lib/fetch_license_type.ts
@@ -6,12 +6,13 @@
*/
import { get } from 'lodash';
-import { LegacyAPICaller } from 'src/core/server';
+import { ElasticsearchClient } from 'src/core/server';
+import { estypes } from '@elastic/elasticsearch';
import { INDEX_PATTERN_ELASTICSEARCH } from '../../../../common/constants';
import { getCcsIndexPattern } from '../../../lib/alerts/get_ccs_index_pattern';
export async function fetchLicenseType(
- callCluster: LegacyAPICaller,
+ client: ElasticsearchClient,
availableCcs: string[],
clusterUuid: string
) {
@@ -19,9 +20,9 @@ export async function fetchLicenseType(
if (availableCcs) {
index = getCcsIndexPattern(index, availableCcs);
}
- const params = {
+ const params: estypes.SearchRequest = {
index,
- filterPath: ['hits.hits._source.license'],
+ filter_path: ['hits.hits._source.license'],
body: {
size: 1,
sort: [
@@ -54,6 +55,6 @@ export async function fetchLicenseType(
},
},
};
- const response = await callCluster('search', params);
+ const { body: response } = await client.search(params);
return get(response, 'hits.hits[0]._source.license.type', null);
}
diff --git a/x-pack/plugins/monitoring/server/kibana_monitoring/collectors/lib/fetch_stack_product_usage.test.ts b/x-pack/plugins/monitoring/server/kibana_monitoring/collectors/lib/fetch_stack_product_usage.test.ts
index 4a2f1abdc96ef2..a8650c375a0dfc 100644
--- a/x-pack/plugins/monitoring/server/kibana_monitoring/collectors/lib/fetch_stack_product_usage.test.ts
+++ b/x-pack/plugins/monitoring/server/kibana_monitoring/collectors/lib/fetch_stack_product_usage.test.ts
@@ -5,6 +5,7 @@
* 2.0.
*/
+import { ElasticsearchClient } from 'kibana/server';
import { fetchStackProductUsage } from './fetch_stack_product_usage';
describe('fetchStackProductUsage', () => {
@@ -16,7 +17,27 @@ describe('fetchStackProductUsage', () => {
};
it('should use appropiate query parameters', async () => {
- const callCluster = jest.fn();
+ const searchMock = jest.fn().mockImplementation(() => ({
+ body: {
+ aggregations: {
+ uuids: {
+ buckets: [
+ {
+ key: 'sadfsdf',
+ indices: {
+ buckets: [
+ {
+ key: '.monitoring-kibana-8',
+ },
+ ],
+ },
+ },
+ ],
+ },
+ },
+ },
+ }));
+ const callCluster = ({ search: searchMock } as unknown) as ElasticsearchClient;
await fetchStackProductUsage(
config,
callCluster,
@@ -34,7 +55,7 @@ describe('fetchStackProductUsage', () => {
},
]
);
- const params = callCluster.mock.calls[0][1];
+ const params = searchMock.mock.calls[0][0];
expect(params.body.query.bool.must[0].term.type.value).toBe('kibana_stats');
expect(params.body.query.bool.must[1].term.cluster_uuid.value).toBe(clusterUuid);
expect(params.body.query.bool.must[2].range.timestamp.gte).toBe('now-1h');
@@ -42,24 +63,28 @@ describe('fetchStackProductUsage', () => {
});
it('should get the usage data', async () => {
- const callCluster = jest.fn().mockImplementation(() => ({
- aggregations: {
- uuids: {
- buckets: [
- {
- key: 'sadfsdf',
- indices: {
- buckets: [
- {
- key: '.monitoring-kibana-8',
+ const callCluster = ({
+ search: jest.fn().mockImplementation(() => ({
+ body: {
+ aggregations: {
+ uuids: {
+ buckets: [
+ {
+ key: 'sadfsdf',
+ indices: {
+ buckets: [
+ {
+ key: '.monitoring-kibana-8',
+ },
+ ],
},
- ],
- },
+ },
+ ],
},
- ],
+ },
},
- },
- }));
+ })),
+ } as unknown) as ElasticsearchClient;
const result = await fetchStackProductUsage(
config,
@@ -78,27 +103,31 @@ describe('fetchStackProductUsage', () => {
});
it('should handle both collection types', async () => {
- const callCluster = jest.fn().mockImplementation(() => ({
- aggregations: {
- uuids: {
- buckets: [
- {
- key: 'sadfsdf',
- indices: {
- buckets: [
- {
- key: '.monitoring-kibana-8',
+ const callCluster = ({
+ search: jest.fn().mockImplementation(() => ({
+ body: {
+ aggregations: {
+ uuids: {
+ buckets: [
+ {
+ key: 'sadfsdf',
+ indices: {
+ buckets: [
+ {
+ key: '.monitoring-kibana-8',
+ },
+ {
+ key: '.monitoring-kibana-mb-8',
+ },
+ ],
},
- {
- key: '.monitoring-kibana-mb-8',
- },
- ],
- },
+ },
+ ],
},
- ],
+ },
},
- },
- }));
+ })),
+ } as unknown) as ElasticsearchClient;
const result = await fetchStackProductUsage(
config,
diff --git a/x-pack/plugins/monitoring/server/kibana_monitoring/collectors/lib/fetch_stack_product_usage.ts b/x-pack/plugins/monitoring/server/kibana_monitoring/collectors/lib/fetch_stack_product_usage.ts
index 2160e159ad1008..527ed503c8fafc 100644
--- a/x-pack/plugins/monitoring/server/kibana_monitoring/collectors/lib/fetch_stack_product_usage.ts
+++ b/x-pack/plugins/monitoring/server/kibana_monitoring/collectors/lib/fetch_stack_product_usage.ts
@@ -6,7 +6,8 @@
*/
import { get } from 'lodash';
-import { LegacyAPICaller } from 'src/core/server';
+import { ElasticsearchClient } from 'src/core/server';
+import { estypes } from '@elastic/elasticsearch';
import { MonitoringConfig } from '../../../config';
// @ts-ignore
import { prefixIndexPattern } from '../../../lib/ccs_utils';
@@ -33,7 +34,7 @@ interface KeyBucket {
export async function fetchStackProductUsage(
config: MonitoringConfig,
- callCluster: LegacyAPICaller,
+ callCluster: ElasticsearchClient,
clusterUuid: string,
index: string,
type: string,
@@ -41,11 +42,11 @@ export async function fetchStackProductUsage(
filters: any[] = []
): Promise {
const size = config.ui.max_bucket_size;
- const params = {
+ const params: estypes.SearchRequest = {
index,
size: 0,
- ignoreUnavailable: true,
- filterPath: ['aggregations.uuids.buckets'],
+ ignore_unavailable: true,
+ filter_path: ['aggregations.uuids.buckets'],
body: {
query: {
bool: {
@@ -94,7 +95,8 @@ export async function fetchStackProductUsage(
},
};
- const response = (await callCluster('search', params)) as ESResponse;
+ const { body: responseBody } = await callCluster.search(params);
+ const response = responseBody as estypes.SearchResponse;
const uuidBuckets = get(response, 'aggregations.uuids.buckets', []) as UuidBucket[];
const count = uuidBuckets.length;
const metricbeatUsed = Boolean(
diff --git a/x-pack/plugins/monitoring/server/kibana_monitoring/collectors/lib/get_stack_products_usage.test.ts b/x-pack/plugins/monitoring/server/kibana_monitoring/collectors/lib/get_stack_products_usage.test.ts
index 8a3135ff832a98..928d7efcdd0d1c 100644
--- a/x-pack/plugins/monitoring/server/kibana_monitoring/collectors/lib/get_stack_products_usage.test.ts
+++ b/x-pack/plugins/monitoring/server/kibana_monitoring/collectors/lib/get_stack_products_usage.test.ts
@@ -5,6 +5,7 @@
* 2.0.
*/
+import { ElasticsearchClient } from 'kibana/server';
import { getStackProductsUsage } from './get_stack_products_usage';
describe('getStackProductsUsage', () => {
@@ -15,11 +16,15 @@ describe('getStackProductsUsage', () => {
};
const clusterUuid = '1abcde2';
const availableCcs: string[] = [];
- const callCluster = jest.fn().mockImplementation(() => ({
- hits: {
- hits: [],
- },
- }));
+ const callCluster = ({
+ search: jest.fn().mockImplementation(() => ({
+ body: {
+ hits: {
+ hits: [],
+ },
+ },
+ })),
+ } as unknown) as ElasticsearchClient;
it('should get all stack products', async () => {
const result = await getStackProductsUsage(config, callCluster, availableCcs, clusterUuid);
diff --git a/x-pack/plugins/monitoring/server/kibana_monitoring/collectors/lib/get_stack_products_usage.ts b/x-pack/plugins/monitoring/server/kibana_monitoring/collectors/lib/get_stack_products_usage.ts
index 0fd214f2fa8557..7cce1b392112f9 100644
--- a/x-pack/plugins/monitoring/server/kibana_monitoring/collectors/lib/get_stack_products_usage.ts
+++ b/x-pack/plugins/monitoring/server/kibana_monitoring/collectors/lib/get_stack_products_usage.ts
@@ -5,7 +5,7 @@
* 2.0.
*/
-import { LegacyAPICaller } from 'src/core/server';
+import { ElasticsearchClient } from 'src/core/server';
import { MonitoringClusterStackProductUsage } from '../types';
import { fetchESUsage } from './fetch_es_usage';
import { MonitoringConfig } from '../../../config';
@@ -24,7 +24,7 @@ import { getCcsIndexPattern } from '../../../lib/alerts/get_ccs_index_pattern';
export const getStackProductsUsage = async (
config: MonitoringConfig,
- callCluster: LegacyAPICaller,
+ callCluster: ElasticsearchClient,
availableCcs: string[],
clusterUuid: string
): Promise<
@@ -38,7 +38,7 @@ export const getStackProductsUsage = async (
const logstashIndex = getCcsIndexPattern(INDEX_PATTERN_LOGSTASH, availableCcs);
const beatsIndex = getCcsIndexPattern(INDEX_PATTERN_BEATS, availableCcs);
const [elasticsearch, kibana, logstash, beats, apm] = await Promise.all([
- fetchESUsage(config, callCluster, clusterUuid, elasticsearchIndex),
+ fetchESUsage(callCluster, clusterUuid, elasticsearchIndex),
fetchStackProductUsage(
config,
callCluster,
diff --git a/x-pack/plugins/monitoring/server/lib/alerts/disable_watcher_cluster_alerts.ts b/x-pack/plugins/monitoring/server/lib/alerts/disable_watcher_cluster_alerts.ts
index dff828d05e2b60..e1dfb893869898 100644
--- a/x-pack/plugins/monitoring/server/lib/alerts/disable_watcher_cluster_alerts.ts
+++ b/x-pack/plugins/monitoring/server/lib/alerts/disable_watcher_cluster_alerts.ts
@@ -6,7 +6,7 @@
*/
import { Logger } from 'kibana/server';
-import { LegacyAPICaller } from 'src/core/server';
+import { ElasticsearchClient } from 'src/core/server';
interface DisableWatchesResponse {
exporters: Array<
@@ -22,9 +22,13 @@ interface DisableWatchesResponse {
>;
}
-async function callMigrationApi(callCluster: LegacyAPICaller, logger: Logger) {
+async function callMigrationApi(callCluster: ElasticsearchClient, logger: Logger) {
try {
- return await callCluster('monitoring.disableWatches');
+ const { body: response } = await callCluster.transport.request({
+ method: 'post',
+ path: '/monitoring.disableWatches',
+ });
+ return response as DisableWatchesResponse;
} catch (err) {
logger.warn(
`Unable to call migration api to disable cluster alert watches. Message=${err.message}`
@@ -33,8 +37,11 @@ async function callMigrationApi(callCluster: LegacyAPICaller, logger: Logger) {
}
}
-export async function disableWatcherClusterAlerts(callCluster: LegacyAPICaller, logger: Logger) {
- const response: DisableWatchesResponse = await callMigrationApi(callCluster, logger);
+export async function disableWatcherClusterAlerts(
+ callCluster: ElasticsearchClient,
+ logger: Logger
+) {
+ const response: DisableWatchesResponse | undefined = await callMigrationApi(callCluster, logger);
if (!response || response.exporters.length === 0) {
return true;
}
diff --git a/x-pack/plugins/monitoring/server/lib/alerts/fetch_ccr_read_exceptions.ts b/x-pack/plugins/monitoring/server/lib/alerts/fetch_ccr_read_exceptions.ts
index a96a7454ea744a..560751d1297d53 100644
--- a/x-pack/plugins/monitoring/server/lib/alerts/fetch_ccr_read_exceptions.ts
+++ b/x-pack/plugins/monitoring/server/lib/alerts/fetch_ccr_read_exceptions.ts
@@ -18,7 +18,7 @@ export async function fetchCCRReadExceptions(
): Promise {
const params = {
index,
- filterPath: ['aggregations.remote_clusters.buckets'],
+ filter_path: ['aggregations.remote_clusters.buckets'],
body: {
size: 0,
query: {
diff --git a/x-pack/plugins/monitoring/server/lib/alerts/fetch_cluster_health.ts b/x-pack/plugins/monitoring/server/lib/alerts/fetch_cluster_health.ts
index f622418c77910f..85bfbd9dbd0490 100644
--- a/x-pack/plugins/monitoring/server/lib/alerts/fetch_cluster_health.ts
+++ b/x-pack/plugins/monitoring/server/lib/alerts/fetch_cluster_health.ts
@@ -15,7 +15,7 @@ export async function fetchClusterHealth(
): Promise {
const params = {
index,
- filterPath: [
+ filter_path: [
'hits.hits._source.cluster_state.status',
'hits.hits._source.cluster_uuid',
'hits.hits._index',
diff --git a/x-pack/plugins/monitoring/server/lib/alerts/fetch_clusters.ts b/x-pack/plugins/monitoring/server/lib/alerts/fetch_clusters.ts
index bbaea8d9f206e6..8b5803d588e12e 100644
--- a/x-pack/plugins/monitoring/server/lib/alerts/fetch_clusters.ts
+++ b/x-pack/plugins/monitoring/server/lib/alerts/fetch_clusters.ts
@@ -23,7 +23,7 @@ export async function fetchClusters(
): Promise {
const params = {
index,
- filterPath: [
+ filter_path: [
'hits.hits._source.cluster_settings.cluster.metadata.display_name',
'hits.hits._source.cluster_uuid',
'hits.hits._source.cluster_name',
@@ -70,7 +70,7 @@ export async function fetchClustersLegacy(
): Promise {
const params = {
index,
- filterPath: [
+ filter_path: [
'hits.hits._source.cluster_settings.cluster.metadata.display_name',
'hits.hits._source.cluster_uuid',
'hits.hits._source.cluster_name',
diff --git a/x-pack/plugins/monitoring/server/lib/alerts/fetch_cpu_usage_node_stats.test.ts b/x-pack/plugins/monitoring/server/lib/alerts/fetch_cpu_usage_node_stats.test.ts
index 9cb773c81923b1..90cd456f18037a 100644
--- a/x-pack/plugins/monitoring/server/lib/alerts/fetch_cpu_usage_node_stats.test.ts
+++ b/x-pack/plugins/monitoring/server/lib/alerts/fetch_cpu_usage_node_stats.test.ts
@@ -204,7 +204,7 @@ describe('fetchCpuUsageNodeStats', () => {
await fetchCpuUsageNodeStats(esClient, clusters, index, startMs, endMs, size);
expect(params).toStrictEqual({
index: '.monitoring-es-*',
- filterPath: ['aggregations'],
+ filter_path: ['aggregations'],
body: {
size: 0,
query: {
diff --git a/x-pack/plugins/monitoring/server/lib/alerts/fetch_cpu_usage_node_stats.ts b/x-pack/plugins/monitoring/server/lib/alerts/fetch_cpu_usage_node_stats.ts
index 07ca3572ad6b3f..6f7d27916a7b1b 100644
--- a/x-pack/plugins/monitoring/server/lib/alerts/fetch_cpu_usage_node_stats.ts
+++ b/x-pack/plugins/monitoring/server/lib/alerts/fetch_cpu_usage_node_stats.ts
@@ -34,10 +34,9 @@ export async function fetchCpuUsageNodeStats(
// Using pure MS didn't seem to work well with the date_histogram interval
// but minutes does
const intervalInMinutes = moment.duration(endMs - startMs).asMinutes();
- const filterPath = ['aggregations'];
const params = {
index,
- filterPath,
+ filter_path: ['aggregations'],
body: {
size: 0,
query: {
diff --git a/x-pack/plugins/monitoring/server/lib/alerts/fetch_disk_usage_node_stats.ts b/x-pack/plugins/monitoring/server/lib/alerts/fetch_disk_usage_node_stats.ts
index 2e8b5c7478e152..70f05991d42291 100644
--- a/x-pack/plugins/monitoring/server/lib/alerts/fetch_disk_usage_node_stats.ts
+++ b/x-pack/plugins/monitoring/server/lib/alerts/fetch_disk_usage_node_stats.ts
@@ -19,7 +19,7 @@ export async function fetchDiskUsageNodeStats(
const clustersIds = clusters.map((cluster) => cluster.clusterUuid);
const params = {
index,
- filterPath: ['aggregations'],
+ filter_path: ['aggregations'],
body: {
size: 0,
query: {
diff --git a/x-pack/plugins/monitoring/server/lib/alerts/fetch_elasticsearch_versions.ts b/x-pack/plugins/monitoring/server/lib/alerts/fetch_elasticsearch_versions.ts
index f25f1dbe594db9..f2f311ac870a51 100644
--- a/x-pack/plugins/monitoring/server/lib/alerts/fetch_elasticsearch_versions.ts
+++ b/x-pack/plugins/monitoring/server/lib/alerts/fetch_elasticsearch_versions.ts
@@ -16,7 +16,7 @@ export async function fetchElasticsearchVersions(
): Promise {
const params = {
index,
- filterPath: [
+ filter_path: [
'hits.hits._source.cluster_stats.nodes.versions',
'hits.hits._index',
'hits.hits._source.cluster_uuid',
diff --git a/x-pack/plugins/monitoring/server/lib/alerts/fetch_index_shard_size.ts b/x-pack/plugins/monitoring/server/lib/alerts/fetch_index_shard_size.ts
index 117894c0d823b5..7e7ea5e6bfdd22 100644
--- a/x-pack/plugins/monitoring/server/lib/alerts/fetch_index_shard_size.ts
+++ b/x-pack/plugins/monitoring/server/lib/alerts/fetch_index_shard_size.ts
@@ -39,7 +39,7 @@ export async function fetchIndexShardSize(
): Promise {
const params = {
index,
- filterPath: ['aggregations.clusters.buckets'],
+ filter_path: ['aggregations.clusters.buckets'],
body: {
size: 0,
query: {
diff --git a/x-pack/plugins/monitoring/server/lib/alerts/fetch_kibana_versions.ts b/x-pack/plugins/monitoring/server/lib/alerts/fetch_kibana_versions.ts
index cb2f201e2586ec..e57b45e2570fa1 100644
--- a/x-pack/plugins/monitoring/server/lib/alerts/fetch_kibana_versions.ts
+++ b/x-pack/plugins/monitoring/server/lib/alerts/fetch_kibana_versions.ts
@@ -20,7 +20,7 @@ export async function fetchKibanaVersions(
): Promise {
const params = {
index,
- filterPath: ['aggregations'],
+ filter_path: ['aggregations'],
body: {
size: 0,
query: {
diff --git a/x-pack/plugins/monitoring/server/lib/alerts/fetch_licenses.ts b/x-pack/plugins/monitoring/server/lib/alerts/fetch_licenses.ts
index 5178b6c4c53a71..38ff82cf298329 100644
--- a/x-pack/plugins/monitoring/server/lib/alerts/fetch_licenses.ts
+++ b/x-pack/plugins/monitoring/server/lib/alerts/fetch_licenses.ts
@@ -15,7 +15,7 @@ export async function fetchLicenses(
): Promise {
const params = {
index,
- filterPath: [
+ filter_path: [
'hits.hits._source.license.*',
'hits.hits._source.cluster_uuid',
'hits.hits._index',
diff --git a/x-pack/plugins/monitoring/server/lib/alerts/fetch_logstash_versions.ts b/x-pack/plugins/monitoring/server/lib/alerts/fetch_logstash_versions.ts
index 6fb54857d40e43..774ee2551ec073 100644
--- a/x-pack/plugins/monitoring/server/lib/alerts/fetch_logstash_versions.ts
+++ b/x-pack/plugins/monitoring/server/lib/alerts/fetch_logstash_versions.ts
@@ -20,7 +20,7 @@ export async function fetchLogstashVersions(
): Promise {
const params = {
index,
- filterPath: ['aggregations'],
+ filter_path: ['aggregations'],
body: {
size: 0,
query: {
diff --git a/x-pack/plugins/monitoring/server/lib/alerts/fetch_memory_usage_node_stats.ts b/x-pack/plugins/monitoring/server/lib/alerts/fetch_memory_usage_node_stats.ts
index 46bb9c794a6a69..f34a8dcff1db73 100644
--- a/x-pack/plugins/monitoring/server/lib/alerts/fetch_memory_usage_node_stats.ts
+++ b/x-pack/plugins/monitoring/server/lib/alerts/fetch_memory_usage_node_stats.ts
@@ -20,7 +20,7 @@ export async function fetchMemoryUsageNodeStats(
const clustersIds = clusters.map((cluster) => cluster.clusterUuid);
const params = {
index,
- filterPath: ['aggregations'],
+ filter_path: ['aggregations'],
body: {
size: 0,
query: {
diff --git a/x-pack/plugins/monitoring/server/lib/alerts/fetch_missing_monitoring_data.ts b/x-pack/plugins/monitoring/server/lib/alerts/fetch_missing_monitoring_data.ts
index a7b4a3a0232072..856ca7c9198851 100644
--- a/x-pack/plugins/monitoring/server/lib/alerts/fetch_missing_monitoring_data.ts
+++ b/x-pack/plugins/monitoring/server/lib/alerts/fetch_missing_monitoring_data.ts
@@ -52,7 +52,7 @@ export async function fetchMissingMonitoringData(
const endMs = nowInMs;
const params = {
index,
- filterPath: ['aggregations.clusters.buckets'],
+ filter_path: ['aggregations.clusters.buckets'],
body: {
size: 0,
query: {
diff --git a/x-pack/plugins/monitoring/server/lib/alerts/fetch_nodes_from_cluster_stats.ts b/x-pack/plugins/monitoring/server/lib/alerts/fetch_nodes_from_cluster_stats.ts
index 5f867ca5b6edf5..dcc8e6516c69bd 100644
--- a/x-pack/plugins/monitoring/server/lib/alerts/fetch_nodes_from_cluster_stats.ts
+++ b/x-pack/plugins/monitoring/server/lib/alerts/fetch_nodes_from_cluster_stats.ts
@@ -30,7 +30,7 @@ export async function fetchNodesFromClusterStats(
): Promise {
const params = {
index,
- filterPath: ['aggregations.clusters.buckets'],
+ filter_path: ['aggregations.clusters.buckets'],
body: {
size: 0,
sort: [
diff --git a/x-pack/plugins/monitoring/server/lib/alerts/fetch_thread_pool_rejections_stats.ts b/x-pack/plugins/monitoring/server/lib/alerts/fetch_thread_pool_rejections_stats.ts
index 954ec3877144fe..132f7692a7579a 100644
--- a/x-pack/plugins/monitoring/server/lib/alerts/fetch_thread_pool_rejections_stats.ts
+++ b/x-pack/plugins/monitoring/server/lib/alerts/fetch_thread_pool_rejections_stats.ts
@@ -41,7 +41,7 @@ export async function fetchThreadPoolRejectionStats(
const clustersIds = clusters.map((cluster) => cluster.clusterUuid);
const params = {
index,
- filterPath: ['aggregations'],
+ filter_path: ['aggregations'],
body: {
size: 0,
query: {
diff --git a/x-pack/plugins/monitoring/server/lib/apm/_get_time_of_last_event.ts b/x-pack/plugins/monitoring/server/lib/apm/_get_time_of_last_event.ts
index 93ff966b5def54..398428f89a4baa 100644
--- a/x-pack/plugins/monitoring/server/lib/apm/_get_time_of_last_event.ts
+++ b/x-pack/plugins/monitoring/server/lib/apm/_get_time_of_last_event.ts
@@ -30,7 +30,7 @@ export async function getTimeOfLastEvent({
const params = {
index: apmIndexPattern,
size: 1,
- ignoreUnavailable: true,
+ ignore_unavailable: true,
body: {
_source: ['beats_stats.timestamp', '@timestamp'],
sort: [
diff --git a/x-pack/plugins/monitoring/server/lib/apm/get_apm_info.ts b/x-pack/plugins/monitoring/server/lib/apm/get_apm_info.ts
index 53a4aeb06bcc16..3721bf873a4177 100644
--- a/x-pack/plugins/monitoring/server/lib/apm/get_apm_info.ts
+++ b/x-pack/plugins/monitoring/server/lib/apm/get_apm_info.ts
@@ -97,8 +97,8 @@ export async function getApmInfo(
const params = {
index: apmIndexPattern,
size: 1,
- ignoreUnavailable: true,
- filterPath: [
+ ignore_unavailable: true,
+ filter_path: [
'hits.hits._source.beats_stats.beat.host',
'hits.hits._source.beats_stats.beat.version',
'hits.hits._source.beats_stats.beat.name',
diff --git a/x-pack/plugins/monitoring/server/lib/apm/get_apms.ts b/x-pack/plugins/monitoring/server/lib/apm/get_apms.ts
index b606eaf5fe7939..be3bb6fdfd6615 100644
--- a/x-pack/plugins/monitoring/server/lib/apm/get_apms.ts
+++ b/x-pack/plugins/monitoring/server/lib/apm/get_apms.ts
@@ -109,8 +109,8 @@ export async function getApms(req: LegacyRequest, apmIndexPattern: string, clust
const params = {
index: apmIndexPattern,
size: config.get('monitoring.ui.max_bucket_size'), // FIXME
- ignoreUnavailable: true,
- filterPath: [
+ ignore_unavailable: true,
+ filter_path: [
// only filter path can filter for inner_hits
'hits.hits._source.timestamp',
'hits.hits._source.@timestamp',
diff --git a/x-pack/plugins/monitoring/server/lib/apm/get_apms_for_clusters.js b/x-pack/plugins/monitoring/server/lib/apm/get_apms_for_clusters.js
index 45bbe35ebd59d1..0c96e0e2305850 100644
--- a/x-pack/plugins/monitoring/server/lib/apm/get_apms_for_clusters.js
+++ b/x-pack/plugins/monitoring/server/lib/apm/get_apms_for_clusters.js
@@ -46,8 +46,8 @@ export function getApmsForClusters(req, apmIndexPattern, clusters) {
const params = {
index: apmIndexPattern,
size: 0,
- ignoreUnavailable: true,
- filterPath: apmAggFilterPath,
+ ignore_unavailable: true,
+ filter_path: apmAggFilterPath,
body: {
query: createApmQuery({
start,
diff --git a/x-pack/plugins/monitoring/server/lib/apm/get_stats.js b/x-pack/plugins/monitoring/server/lib/apm/get_stats.js
index 7dd1b652254cb8..2abd81e325f5ec 100644
--- a/x-pack/plugins/monitoring/server/lib/apm/get_stats.js
+++ b/x-pack/plugins/monitoring/server/lib/apm/get_stats.js
@@ -34,9 +34,9 @@ export async function getStats(req, apmIndexPattern, clusterUuid) {
const params = {
index: apmIndexPattern,
- filterPath: apmAggFilterPath,
+ filter_path: apmAggFilterPath,
size: 0,
- ignoreUnavailable: true,
+ ignore_unavailable: true,
body: {
query: createApmQuery({
start,
diff --git a/x-pack/plugins/monitoring/server/lib/beats/get_beat_summary.ts b/x-pack/plugins/monitoring/server/lib/beats/get_beat_summary.ts
index 13b4f0041c99b7..d67f32e64ba71d 100644
--- a/x-pack/plugins/monitoring/server/lib/beats/get_beat_summary.ts
+++ b/x-pack/plugins/monitoring/server/lib/beats/get_beat_summary.ts
@@ -89,8 +89,8 @@ export async function getBeatSummary(
const params = {
index: beatsIndexPattern,
size: 1,
- ignoreUnavailable: true,
- filterPath: [
+ ignore_unavailable: true,
+ filter_path: [
'hits.hits._source.beats_stats.beat.host',
'hits.hits._source.beat.stats.beat.host',
'hits.hits._source.beats_stats.beat.version',
diff --git a/x-pack/plugins/monitoring/server/lib/beats/get_beats.ts b/x-pack/plugins/monitoring/server/lib/beats/get_beats.ts
index 641039331bbcb6..fff2b55cf26163 100644
--- a/x-pack/plugins/monitoring/server/lib/beats/get_beats.ts
+++ b/x-pack/plugins/monitoring/server/lib/beats/get_beats.ts
@@ -121,8 +121,8 @@ export async function getBeats(req: LegacyRequest, beatsIndexPattern: string, cl
const params = {
index: beatsIndexPattern,
size: config.get('monitoring.ui.max_bucket_size'), // FIXME
- ignoreUnavailable: true,
- filterPath: [
+ ignore_unavailable: true,
+ filter_path: [
// only filter path can filter for inner_hits
'hits.hits._source.beats_stats.beat.uuid',
'hits.hits._source.beat.stats.beat.uuid',
diff --git a/x-pack/plugins/monitoring/server/lib/beats/get_beats_for_clusters.js b/x-pack/plugins/monitoring/server/lib/beats/get_beats_for_clusters.js
index 5ceca7a02d89b0..e7c4771dd601cc 100644
--- a/x-pack/plugins/monitoring/server/lib/beats/get_beats_for_clusters.js
+++ b/x-pack/plugins/monitoring/server/lib/beats/get_beats_for_clusters.js
@@ -44,8 +44,8 @@ export function getBeatsForClusters(req, beatsIndexPattern, clusters) {
const params = {
index: beatsIndexPattern,
size: 0,
- ignoreUnavailable: true,
- filterPath: beatsAggFilterPath,
+ ignore_unavailable: true,
+ filter_path: beatsAggFilterPath,
body: {
query: createBeatsQuery({
start,
diff --git a/x-pack/plugins/monitoring/server/lib/beats/get_latest_stats.js b/x-pack/plugins/monitoring/server/lib/beats/get_latest_stats.js
index f3f296fc09a389..fb40df115d19ac 100644
--- a/x-pack/plugins/monitoring/server/lib/beats/get_latest_stats.js
+++ b/x-pack/plugins/monitoring/server/lib/beats/get_latest_stats.js
@@ -81,8 +81,8 @@ export function getLatestStats(req, beatsIndexPattern, clusterUuid) {
const params = {
index: beatsIndexPattern,
size: 0,
- ignoreUnavailable: true,
- filterPath: 'aggregations',
+ ignore_unavailable: true,
+ filter_path: 'aggregations',
body: {
query: createBeatsQuery({
clusterUuid,
diff --git a/x-pack/plugins/monitoring/server/lib/beats/get_stats.js b/x-pack/plugins/monitoring/server/lib/beats/get_stats.js
index aa3d3947adccf5..3af51d909697ff 100644
--- a/x-pack/plugins/monitoring/server/lib/beats/get_stats.js
+++ b/x-pack/plugins/monitoring/server/lib/beats/get_stats.js
@@ -33,9 +33,9 @@ export async function getStats(req, beatsIndexPattern, clusterUuid) {
const params = {
index: beatsIndexPattern,
- filterPath: beatsAggFilterPath,
+ filter_path: beatsAggFilterPath,
size: 0,
- ignoreUnavailable: true,
+ ignore_unavailable: true,
body: {
query: createBeatsQuery({
start,
diff --git a/x-pack/plugins/monitoring/server/lib/cluster/flag_supported_clusters.ts b/x-pack/plugins/monitoring/server/lib/cluster/flag_supported_clusters.ts
index 42041832cfee5d..820b1bf24c6a13 100644
--- a/x-pack/plugins/monitoring/server/lib/cluster/flag_supported_clusters.ts
+++ b/x-pack/plugins/monitoring/server/lib/cluster/flag_supported_clusters.ts
@@ -30,8 +30,8 @@ async function findSupportedBasicLicenseCluster(
const kibanaDataResult: ElasticsearchResponse = (await callWithRequest(req, 'search', {
index: kbnIndexPattern,
size: 1,
- ignoreUnavailable: true,
- filterPath: ['hits.hits._source.cluster_uuid', 'hits.hits._source.cluster.id'],
+ ignore_unavailable: true,
+ filter_path: ['hits.hits._source.cluster_uuid', 'hits.hits._source.cluster.id'],
body: {
sort: { timestamp: { order: 'desc', unmapped_type: 'long' } },
query: {
diff --git a/x-pack/plugins/monitoring/server/lib/cluster/get_cluster_license.ts b/x-pack/plugins/monitoring/server/lib/cluster/get_cluster_license.ts
index d1fa0efcbca2de..8ed5578e574a0c 100644
--- a/x-pack/plugins/monitoring/server/lib/cluster/get_cluster_license.ts
+++ b/x-pack/plugins/monitoring/server/lib/cluster/get_cluster_license.ts
@@ -20,8 +20,8 @@ export function getClusterLicense(req: LegacyRequest, esIndexPattern: string, cl
const params = {
index: esIndexPattern,
size: 1,
- ignoreUnavailable: true,
- filterPath: 'hits.hits._source.license',
+ ignore_unavailable: true,
+ filter_path: ['hits.hits._source.license'],
body: {
sort: { timestamp: { order: 'desc', unmapped_type: 'long' } },
query: createQuery({
diff --git a/x-pack/plugins/monitoring/server/lib/cluster/get_clusters_state.ts b/x-pack/plugins/monitoring/server/lib/cluster/get_clusters_state.ts
index 2cec89b18aecda..d732b43bc203b4 100644
--- a/x-pack/plugins/monitoring/server/lib/cluster/get_clusters_state.ts
+++ b/x-pack/plugins/monitoring/server/lib/cluster/get_clusters_state.ts
@@ -66,8 +66,8 @@ export function getClustersState(
const params = {
index: esIndexPattern,
size: clusterUuids.length,
- ignoreUnavailable: true,
- filterPath: [
+ ignore_unavailable: true,
+ filter_path: [
'hits.hits._source.cluster_uuid',
'hits.hits._source.elasticsearch.cluster.id',
'hits.hits._source.cluster_state',
diff --git a/x-pack/plugins/monitoring/server/lib/cluster/get_clusters_stats.ts b/x-pack/plugins/monitoring/server/lib/cluster/get_clusters_stats.ts
index 4067293e7829da..6eb21165d7256b 100644
--- a/x-pack/plugins/monitoring/server/lib/cluster/get_clusters_stats.ts
+++ b/x-pack/plugins/monitoring/server/lib/cluster/get_clusters_stats.ts
@@ -53,8 +53,8 @@ function fetchClusterStats(req: LegacyRequest, esIndexPattern: string, clusterUu
const params = {
index: esIndexPattern,
size: config.get('monitoring.ui.max_bucket_size'),
- ignoreUnavailable: true,
- filterPath: [
+ ignore_unavailable: true,
+ filter_path: [
'hits.hits._index',
'hits.hits._source.cluster_uuid',
'hits.hits._source.elasticsearch.cluster.id',
diff --git a/x-pack/plugins/monitoring/server/lib/details/get_series.js b/x-pack/plugins/monitoring/server/lib/details/get_series.js
index ca062ad5599fac..d06ff950449dc0 100644
--- a/x-pack/plugins/monitoring/server/lib/details/get_series.js
+++ b/x-pack/plugins/monitoring/server/lib/details/get_series.js
@@ -150,7 +150,7 @@ async function fetchSeries(
const params = {
index: indexPattern,
size: 0,
- ignoreUnavailable: true,
+ ignore_unavailable: true,
body: {
query: createQuery({
start: adjustedMin,
diff --git a/x-pack/plugins/monitoring/server/lib/elasticsearch/ccr.ts b/x-pack/plugins/monitoring/server/lib/elasticsearch/ccr.ts
index d0abed2ad8b8da..482cbd3601993c 100644
--- a/x-pack/plugins/monitoring/server/lib/elasticsearch/ccr.ts
+++ b/x-pack/plugins/monitoring/server/lib/elasticsearch/ccr.ts
@@ -27,7 +27,7 @@ export async function checkCcrEnabled(req: LegacyRequest, esIndexPattern: string
const params = {
index: esIndexPattern,
size: 1,
- ignoreUnavailable: true,
+ ignore_unavailable: true,
body: {
query: createQuery({
type: 'cluster_stats',
@@ -38,7 +38,7 @@ export async function checkCcrEnabled(req: LegacyRequest, esIndexPattern: string
}),
sort: [{ timestamp: { order: 'desc', unmapped_type: 'long' } }],
},
- filterPath: [
+ filter_path: [
'hits.hits._source.stack_stats.xpack.ccr',
'hits.hits._source.elasticsearch.cluster.stats.stack.xpack.ccr',
],
diff --git a/x-pack/plugins/monitoring/server/lib/elasticsearch/get_last_recovery.ts b/x-pack/plugins/monitoring/server/lib/elasticsearch/get_last_recovery.ts
index b6669342649332..43527f875cf729 100644
--- a/x-pack/plugins/monitoring/server/lib/elasticsearch/get_last_recovery.ts
+++ b/x-pack/plugins/monitoring/server/lib/elasticsearch/get_last_recovery.ts
@@ -97,7 +97,7 @@ export async function getLastRecovery(req: LegacyRequest, esIndexPattern: string
const legacyParams = {
index: esIndexPattern,
size: 1,
- ignoreUnavailable: true,
+ ignore_unavailable: true,
body: {
_source: ['index_recovery.shards'],
sort: { timestamp: { order: 'desc', unmapped_type: 'long' } },
diff --git a/x-pack/plugins/monitoring/server/lib/elasticsearch/get_ml_jobs.ts b/x-pack/plugins/monitoring/server/lib/elasticsearch/get_ml_jobs.ts
index 8b89416f14e9fa..df44036cd0cd82 100644
--- a/x-pack/plugins/monitoring/server/lib/elasticsearch/get_ml_jobs.ts
+++ b/x-pack/plugins/monitoring/server/lib/elasticsearch/get_ml_jobs.ts
@@ -47,8 +47,8 @@ export function getMlJobs(req: LegacyRequest, esIndexPattern: string) {
const params = {
index: esIndexPattern,
size: maxBucketSize,
- ignoreUnavailable: true,
- filterPath: [
+ ignore_unavailable: true,
+ filter_path: [
'hits.hits._source.job_stats.job_id',
'hits.hits._source.elasticsearch.ml.job.id',
'hits.hits._source.job_stats.state',
@@ -95,8 +95,8 @@ export function getMlJobsForCluster(
const params = {
index: esIndexPattern,
size: 0,
- ignoreUnavailable: true,
- filterPath: 'aggregations.jobs_count.value',
+ ignore_unavailable: true,
+ filter_path: 'aggregations.jobs_count.value',
body: {
query: createQuery({ types: ['ml_job', 'job_stats'], start, end, clusterUuid, metric }),
aggs: {
diff --git a/x-pack/plugins/monitoring/server/lib/elasticsearch/indices/get_index_summary.ts b/x-pack/plugins/monitoring/server/lib/elasticsearch/indices/get_index_summary.ts
index ee4988d773974f..8a03027a93a565 100644
--- a/x-pack/plugins/monitoring/server/lib/elasticsearch/indices/get_index_summary.ts
+++ b/x-pack/plugins/monitoring/server/lib/elasticsearch/indices/get_index_summary.ts
@@ -89,7 +89,7 @@ export function getIndexSummary(
const params = {
index: esIndexPattern,
size: 1,
- ignoreUnavailable: true,
+ ignore_unavailable: true,
body: {
sort: { timestamp: { order: 'desc', unmapped_type: 'long' } },
query: createQuery({
diff --git a/x-pack/plugins/monitoring/server/lib/elasticsearch/indices/get_indices.ts b/x-pack/plugins/monitoring/server/lib/elasticsearch/indices/get_indices.ts
index 9a61d6c603b973..0a9993769a6e5c 100644
--- a/x-pack/plugins/monitoring/server/lib/elasticsearch/indices/get_indices.ts
+++ b/x-pack/plugins/monitoring/server/lib/elasticsearch/indices/get_indices.ts
@@ -116,8 +116,8 @@ export function buildGetIndicesQuery(
return {
index: esIndexPattern,
size,
- ignoreUnavailable: true,
- filterPath: [
+ ignore_unavailable: true,
+ filter_path: [
// only filter path can filter for inner_hits
'hits.hits._source.index_stats.index',
'hits.hits._source.elasticsearch.index.name',
diff --git a/x-pack/plugins/monitoring/server/lib/elasticsearch/nodes/get_node_summary.ts b/x-pack/plugins/monitoring/server/lib/elasticsearch/nodes/get_node_summary.ts
index c7ca66753c73bf..79c44286717b45 100644
--- a/x-pack/plugins/monitoring/server/lib/elasticsearch/nodes/get_node_summary.ts
+++ b/x-pack/plugins/monitoring/server/lib/elasticsearch/nodes/get_node_summary.ts
@@ -124,7 +124,7 @@ export function getNodeSummary(
const params = {
index: esIndexPattern,
size: 1,
- ignoreUnavailable: true,
+ ignore_unavailable: true,
body: {
sort: { timestamp: { order: 'desc', unmapped_type: 'long' } },
query: createQuery({ type: 'node_stats', start, end, clusterUuid, metric, filters }),
diff --git a/x-pack/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/get_node_ids.js b/x-pack/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/get_node_ids.js
index 96b434b945ad46..87781417a07e5f 100644
--- a/x-pack/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/get_node_ids.js
+++ b/x-pack/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/get_node_ids.js
@@ -17,8 +17,8 @@ export async function getNodeIds(req, indexPattern, { clusterUuid }, size) {
const params = {
index: indexPattern,
size: 0,
- ignoreUnavailable: true,
- filterPath: ['aggregations.composite_data.buckets'],
+ ignore_unavailable: true,
+ filter_path: ['aggregations.composite_data.buckets'],
body: {
query: createQuery({
type: 'node_stats',
diff --git a/x-pack/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/get_nodes.ts b/x-pack/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/get_nodes.ts
index 2235d3d2f3224d..442a2b1b9b9c9b 100644
--- a/x-pack/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/get_nodes.ts
+++ b/x-pack/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/get_nodes.ts
@@ -76,7 +76,7 @@ export async function getNodes(
const params = {
index: esIndexPattern,
size: config.get('monitoring.ui.max_bucket_size'),
- ignoreUnavailable: true,
+ ignore_unavailable: true,
body: {
query: createQuery({
type: 'node_stats',
@@ -110,7 +110,7 @@ export async function getNodes(
},
sort: [{ timestamp: { order: 'desc', unmapped_type: 'long' } }],
},
- filterPath: [
+ filter_path: [
'hits.hits._source.source_node',
'hits.hits._source.service.address',
'hits.hits._source.elasticsearch.node',
diff --git a/x-pack/plugins/monitoring/server/lib/elasticsearch/shards/get_indices_unassigned_shard_stats.ts b/x-pack/plugins/monitoring/server/lib/elasticsearch/shards/get_indices_unassigned_shard_stats.ts
index d3acf8ccaf4437..87f79ff5b9b44d 100644
--- a/x-pack/plugins/monitoring/server/lib/elasticsearch/shards/get_indices_unassigned_shard_stats.ts
+++ b/x-pack/plugins/monitoring/server/lib/elasticsearch/shards/get_indices_unassigned_shard_stats.ts
@@ -41,7 +41,7 @@ async function getUnassignedShardData(
const params = {
index: esIndexPattern,
size: 0,
- ignoreUnavailable: true,
+ ignore_unavailable: true,
body: {
sort: { timestamp: { order: 'desc', unmapped_type: 'long' } },
query: createQuery({
diff --git a/x-pack/plugins/monitoring/server/lib/elasticsearch/shards/get_nodes_shard_count.ts b/x-pack/plugins/monitoring/server/lib/elasticsearch/shards/get_nodes_shard_count.ts
index 7cda87bf09af83..12ce144ebf5c5a 100644
--- a/x-pack/plugins/monitoring/server/lib/elasticsearch/shards/get_nodes_shard_count.ts
+++ b/x-pack/plugins/monitoring/server/lib/elasticsearch/shards/get_nodes_shard_count.ts
@@ -39,7 +39,7 @@ async function getShardCountPerNode(
const params = {
index: esIndexPattern,
size: 0,
- ignoreUnavailable: true,
+ ignore_unavailable: true,
body: {
sort: { timestamp: { order: 'desc', unmapped_type: 'long' } },
query: createQuery({
diff --git a/x-pack/plugins/monitoring/server/lib/elasticsearch/shards/get_shard_allocation.ts b/x-pack/plugins/monitoring/server/lib/elasticsearch/shards/get_shard_allocation.ts
index b739b4a6533db2..d39e38eab21e93 100644
--- a/x-pack/plugins/monitoring/server/lib/elasticsearch/shards/get_shard_allocation.ts
+++ b/x-pack/plugins/monitoring/server/lib/elasticsearch/shards/get_shard_allocation.ts
@@ -103,7 +103,7 @@ export function getShardAllocation(
const params = {
index: esIndexPattern,
size: config.get('monitoring.ui.max_bucket_size'),
- ignoreUnavailable: true,
+ ignore_unavailable: true,
body: {
query: createQuery({ types: ['shard', 'shards'], clusterUuid, metric, filters }),
},
diff --git a/x-pack/plugins/monitoring/server/lib/elasticsearch/shards/get_shard_stats.ts b/x-pack/plugins/monitoring/server/lib/elasticsearch/shards/get_shard_stats.ts
index 5219a2e9286f5b..e0e6854dba05f2 100644
--- a/x-pack/plugins/monitoring/server/lib/elasticsearch/shards/get_shard_stats.ts
+++ b/x-pack/plugins/monitoring/server/lib/elasticsearch/shards/get_shard_stats.ts
@@ -96,7 +96,7 @@ export function getShardStats(
const params = {
index: esIndexPattern,
size: 0,
- ignoreUnavailable: true,
+ ignore_unavailable: true,
body: {
sort: { timestamp: { order: 'desc', unmapped_type: 'long' } },
query: createQuery({
diff --git a/x-pack/plugins/monitoring/server/lib/elasticsearch/verify_monitoring_auth.js b/x-pack/plugins/monitoring/server/lib/elasticsearch/verify_monitoring_auth.js
index 1895c36699e50f..e4cee4d4455cab 100644
--- a/x-pack/plugins/monitoring/server/lib/elasticsearch/verify_monitoring_auth.js
+++ b/x-pack/plugins/monitoring/server/lib/elasticsearch/verify_monitoring_auth.js
@@ -53,7 +53,7 @@ async function verifyHasPrivileges(req) {
},
],
},
- ignoreUnavailable: true, // we allow 404 incase the user shutdown security in-between the check and now
+ ignore_unavailable: true, // we allow 404 incase the user shutdown security in-between the check and now
});
} catch (err) {
if (
diff --git a/x-pack/plugins/monitoring/server/lib/elasticsearch_settings/cluster.js b/x-pack/plugins/monitoring/server/lib/elasticsearch_settings/cluster.js
index a1ece9e302b2a7..1c20634c102204 100644
--- a/x-pack/plugins/monitoring/server/lib/elasticsearch_settings/cluster.js
+++ b/x-pack/plugins/monitoring/server/lib/elasticsearch_settings/cluster.js
@@ -34,15 +34,6 @@ export async function checkClusterSettings(req) {
const { callWithRequest } = req.server.plugins.elasticsearch.getCluster('admin');
const { cloud } = req.server.newPlatform.setup.plugins;
const isCloudEnabled = !!(cloud && cloud.isCloudEnabled);
- const response = await callWithRequest(req, 'transport.request', {
- method: 'GET',
- path: '/_cluster/settings?include_defaults',
- filter_path: [
- 'persistent.xpack.monitoring',
- 'transient.xpack.monitoring',
- 'defaults.xpack.monitoring',
- ],
- });
-
+ const response = await callWithRequest(req, 'cluster.getSettings', { include_defaults: true });
return handleResponse(response, isCloudEnabled);
}
diff --git a/x-pack/plugins/monitoring/server/lib/kibana/get_kibana_info.ts b/x-pack/plugins/monitoring/server/lib/kibana/get_kibana_info.ts
index 3b0af657947e54..15cc9904dd060c 100644
--- a/x-pack/plugins/monitoring/server/lib/kibana/get_kibana_info.ts
+++ b/x-pack/plugins/monitoring/server/lib/kibana/get_kibana_info.ts
@@ -36,8 +36,8 @@ export function getKibanaInfo(
const params = {
index: kbnIndexPattern,
size: 1,
- ignoreUnavailable: true,
- filterPath: [
+ ignore_unavailable: true,
+ filter_path: [
'hits.hits._source.kibana_stats.kibana',
'hits.hits._source.kibana.kibana',
'hits.hits._source.kibana_stats.os.memory.free_in_bytes',
diff --git a/x-pack/plugins/monitoring/server/lib/kibana/get_kibanas.ts b/x-pack/plugins/monitoring/server/lib/kibana/get_kibanas.ts
index 4da4c40b25568e..b89ff7a326d80f 100644
--- a/x-pack/plugins/monitoring/server/lib/kibana/get_kibanas.ts
+++ b/x-pack/plugins/monitoring/server/lib/kibana/get_kibanas.ts
@@ -71,7 +71,7 @@ export async function getKibanas(
const params = {
index: kbnIndexPattern,
size: config.get('monitoring.ui.max_bucket_size'),
- ignoreUnavailable: true,
+ ignore_unavailable: true,
body: {
query: createQuery({
types: ['kibana_stats', 'stats'],
diff --git a/x-pack/plugins/monitoring/server/lib/kibana/get_kibanas_for_clusters.js b/x-pack/plugins/monitoring/server/lib/kibana/get_kibanas_for_clusters.js
index 7d648601229224..141596ffd2f6fb 100644
--- a/x-pack/plugins/monitoring/server/lib/kibana/get_kibanas_for_clusters.js
+++ b/x-pack/plugins/monitoring/server/lib/kibana/get_kibanas_for_clusters.js
@@ -37,7 +37,7 @@ export function getKibanasForClusters(req, kbnIndexPattern, clusters) {
const params = {
index: kbnIndexPattern,
size: 0,
- ignoreUnavailable: true,
+ ignore_unavailable: true,
body: {
query: createQuery({
types: ['stats', 'kibana_stats'],
diff --git a/x-pack/plugins/monitoring/server/lib/logs/get_log_types.ts b/x-pack/plugins/monitoring/server/lib/logs/get_log_types.ts
index 82b73c6b87f060..bbb48c43033da3 100644
--- a/x-pack/plugins/monitoring/server/lib/logs/get_log_types.ts
+++ b/x-pack/plugins/monitoring/server/lib/logs/get_log_types.ts
@@ -84,8 +84,8 @@ export async function getLogTypes(
const params = {
index: filebeatIndexPattern,
size: 0,
- filterPath: ['aggregations.levels.buckets', 'aggregations.types.buckets'],
- ignoreUnavailable: true,
+ filter_path: ['aggregations.levels.buckets', 'aggregations.types.buckets'],
+ ignore_unavailable: true,
body: {
sort: { '@timestamp': { order: 'desc', unmapped_type: 'long' } },
query: {
diff --git a/x-pack/plugins/monitoring/server/lib/logs/get_logs.ts b/x-pack/plugins/monitoring/server/lib/logs/get_logs.ts
index 11e8f6550bcb7d..4c21422a5d0cf7 100644
--- a/x-pack/plugins/monitoring/server/lib/logs/get_logs.ts
+++ b/x-pack/plugins/monitoring/server/lib/logs/get_logs.ts
@@ -100,7 +100,7 @@ export async function getLogs(
const params = {
index: filebeatIndexPattern,
size: Math.min(50, config.get('monitoring.ui.elasticsearch.logFetchCount')),
- filterPath: [
+ filter_path: [
'hits.hits._source.message',
'hits.hits._source.log.level',
'hits.hits._source.@timestamp',
@@ -109,7 +109,7 @@ export async function getLogs(
'hits.hits._source.elasticsearch.index.name',
'hits.hits._source.elasticsearch.node.name',
],
- ignoreUnavailable: true,
+ ignore_unavailable: true,
body: {
sort: { '@timestamp': { order: 'desc', unmapped_type: 'long' } },
query: {
diff --git a/x-pack/plugins/monitoring/server/lib/logstash/get_logstash_for_clusters.js b/x-pack/plugins/monitoring/server/lib/logstash/get_logstash_for_clusters.js
index 58155e35ad52f9..17f76834b333ad 100644
--- a/x-pack/plugins/monitoring/server/lib/logstash/get_logstash_for_clusters.js
+++ b/x-pack/plugins/monitoring/server/lib/logstash/get_logstash_for_clusters.js
@@ -48,7 +48,7 @@ export function getLogstashForClusters(req, lsIndexPattern, clusters) {
const params = {
index: lsIndexPattern,
size: 0,
- ignoreUnavailable: true,
+ ignore_unavailable: true,
body: {
query: createQuery({
types: ['stats', 'logstash_stats'],
diff --git a/x-pack/plugins/monitoring/server/lib/logstash/get_node_info.ts b/x-pack/plugins/monitoring/server/lib/logstash/get_node_info.ts
index 954b78c432374f..d047729a0b3c22 100644
--- a/x-pack/plugins/monitoring/server/lib/logstash/get_node_info.ts
+++ b/x-pack/plugins/monitoring/server/lib/logstash/get_node_info.ts
@@ -45,8 +45,8 @@ export function getNodeInfo(
const params = {
index: lsIndexPattern,
size: 1,
- ignoreUnavailable: true,
- filterPath: [
+ ignore_unavailable: true,
+ filter_path: [
'hits.hits._source.logstash_stats.events',
'hits.hits._source.logstash.node.stats.events',
'hits.hits._source.logstash_stats.jvm.uptime_in_millis',
diff --git a/x-pack/plugins/monitoring/server/lib/logstash/get_nodes.ts b/x-pack/plugins/monitoring/server/lib/logstash/get_nodes.ts
index 9db59fec25530e..42d1b69aee5f37 100644
--- a/x-pack/plugins/monitoring/server/lib/logstash/get_nodes.ts
+++ b/x-pack/plugins/monitoring/server/lib/logstash/get_nodes.ts
@@ -82,7 +82,7 @@ export async function getNodes(
const params = {
index: lsIndexPattern,
size: config.get('monitoring.ui.max_bucket_size'), // FIXME
- ignoreUnavailable: true,
+ ignore_unavailable: true,
body: {
query: createQuery({
start,
diff --git a/x-pack/plugins/monitoring/server/lib/logstash/get_pipeline_ids.js b/x-pack/plugins/monitoring/server/lib/logstash/get_pipeline_ids.js
index 1521c5d3773d0e..2846a968bfed6d 100644
--- a/x-pack/plugins/monitoring/server/lib/logstash/get_pipeline_ids.js
+++ b/x-pack/plugins/monitoring/server/lib/logstash/get_pipeline_ids.js
@@ -27,8 +27,8 @@ export async function getLogstashPipelineIds(
const params = {
index: logstashIndexPattern,
size: 0,
- ignoreUnavailable: true,
- filterPath: ['aggregations.nest.id.buckets', 'aggregations.nest_mb.id.buckets'],
+ ignore_unavailable: true,
+ filter_path: ['aggregations.nest.id.buckets', 'aggregations.nest_mb.id.buckets'],
body: {
query: createQuery({
start,
diff --git a/x-pack/plugins/monitoring/server/lib/logstash/get_pipeline_state_document.ts b/x-pack/plugins/monitoring/server/lib/logstash/get_pipeline_state_document.ts
index e261505e2d1b01..61c99c3a069b32 100644
--- a/x-pack/plugins/monitoring/server/lib/logstash/get_pipeline_state_document.ts
+++ b/x-pack/plugins/monitoring/server/lib/logstash/get_pipeline_state_document.ts
@@ -42,7 +42,7 @@ export async function getPipelineStateDocument(
const params = {
index: logstashIndexPattern,
size: 1,
- ignoreUnavailable: true,
+ ignore_unavailable: true,
body: {
_source: { excludes: 'logstash_state.pipeline.representation.plugins' },
sort: { timestamp: { order: 'desc', unmapped_type: 'long' } },
diff --git a/x-pack/plugins/monitoring/server/lib/logstash/get_pipeline_stats_aggregation.js b/x-pack/plugins/monitoring/server/lib/logstash/get_pipeline_stats_aggregation.js
index 2e35a4639fa5a6..4d9d2a720a1627 100644
--- a/x-pack/plugins/monitoring/server/lib/logstash/get_pipeline_stats_aggregation.js
+++ b/x-pack/plugins/monitoring/server/lib/logstash/get_pipeline_stats_aggregation.js
@@ -106,8 +106,8 @@ function fetchPipelineLatestStats(
const params = {
index: logstashIndexPattern,
size: 0,
- ignoreUnavailable: true,
- filterPath: [
+ ignore_unavailable: true,
+ filter_path: [
'aggregations.pipelines.scoped.vertices.vertex_id.buckets.key',
'aggregations.pipelines.scoped.vertices.vertex_id.buckets.events_in_total',
'aggregations.pipelines.scoped.vertices.vertex_id.buckets.events_out_total',
diff --git a/x-pack/plugins/monitoring/server/lib/logstash/get_pipeline_versions.js b/x-pack/plugins/monitoring/server/lib/logstash/get_pipeline_versions.js
index d1121c78407ffc..c52d41a363055f 100644
--- a/x-pack/plugins/monitoring/server/lib/logstash/get_pipeline_versions.js
+++ b/x-pack/plugins/monitoring/server/lib/logstash/get_pipeline_versions.js
@@ -82,7 +82,7 @@ function fetchPipelineVersions(...args) {
const params = {
index: logstashIndexPattern,
size: 0,
- ignoreUnavailable: true,
+ ignore_unavailable: true,
body: {
sort: { timestamp: { order: 'desc', unmapped_type: 'long' } },
query,
diff --git a/x-pack/plugins/monitoring/server/lib/logstash/get_pipeline_vertex_stats_aggregation.js b/x-pack/plugins/monitoring/server/lib/logstash/get_pipeline_vertex_stats_aggregation.js
index 81d1f2bf572176..97a8c463a2259f 100644
--- a/x-pack/plugins/monitoring/server/lib/logstash/get_pipeline_vertex_stats_aggregation.js
+++ b/x-pack/plugins/monitoring/server/lib/logstash/get_pipeline_vertex_stats_aggregation.js
@@ -155,8 +155,8 @@ function fetchPipelineVertexTimeSeriesStats(
const params = {
index: logstashIndexPattern,
size: 0,
- ignoreUnavailable: true,
- filterPath: [
+ ignore_unavailable: true,
+ filter_path: [
'aggregations.timeseries.buckets.key',
'aggregations.timeseries.buckets.pipelines.scoped.vertices.vertex_id.events_in_total',
'aggregations.timeseries.buckets.pipelines.scoped.vertices.vertex_id.events_out_total',
diff --git a/x-pack/plugins/monitoring/server/lib/setup/collection/get_collection_status.js b/x-pack/plugins/monitoring/server/lib/setup/collection/get_collection_status.js
index 36a48002005b3d..b9ce355b44b625 100644
--- a/x-pack/plugins/monitoring/server/lib/setup/collection/get_collection_status.js
+++ b/x-pack/plugins/monitoring/server/lib/setup/collection/get_collection_status.js
@@ -57,8 +57,8 @@ const getRecentMonitoringDocuments = async (req, indexPatterns, clusterUuid, nod
const params = {
index: Object.values(indexPatterns),
size: 0,
- ignoreUnavailable: true,
- filterPath: ['aggregations.indices.buckets'],
+ ignore_unavailable: true,
+ filter_path: ['aggregations.indices.buckets'],
body: {
query: {
bool: {
@@ -206,8 +206,8 @@ async function doesIndexExist(req, index) {
index,
size: 0,
terminate_after: 1,
- ignoreUnavailable: true,
- filterPath: ['hits.total.value'],
+ ignore_unavailable: true,
+ filter_path: ['hits.total.value'],
};
const { callWithRequest } = req.server.plugins.elasticsearch.getCluster('monitoring');
const response = await callWithRequest(req, 'search', params);
diff --git a/x-pack/plugins/monitoring/server/license_service.ts b/x-pack/plugins/monitoring/server/license_service.ts
index fde22aaefa5dd2..ab10193fc93cbd 100644
--- a/x-pack/plugins/monitoring/server/license_service.ts
+++ b/x-pack/plugins/monitoring/server/license_service.ts
@@ -6,16 +6,17 @@
*/
import { Subscription } from 'rxjs';
-import { ILegacyCustomClusterClient } from 'kibana/server';
+import { IClusterClient, ILegacyClusterClient } from 'kibana/server';
import { ILicense, LicenseFeature } from '../../licensing/common/types';
import { LicensingPluginStart } from '../../licensing/server';
import { MonitoringConfig } from './config';
import { Logger } from '../../../../src/core/server';
import { MonitoringLicenseService } from './types';
+import { EndpointTypes, Globals, ClientParams } from './static_globals';
interface SetupDeps {
licensing: LicensingPluginStart;
- monitoringClient: ILegacyCustomClusterClient;
+ monitoringClient: IClusterClient;
config: MonitoringConfig;
log: Logger;
}
@@ -27,8 +28,15 @@ const defaultLicenseFeature: LicenseFeature = {
export class LicenseService {
public setup({ licensing, monitoringClient, config, log }: SetupDeps): MonitoringLicenseService {
+ // TODO: This needs to be changed to an IClusterClient as when the Licensing server
+ // is upgraded to the new client.
+ const fakeLegacyClusterClient = {
+ callAsInternalUser: (endpoint: EndpointTypes, options: ClientParams) =>
+ Globals.app.getLegacyClusterShim(monitoringClient.asInternalUser, endpoint, options),
+ } as ILegacyClusterClient;
+
const { refresh, license$ } = licensing.createLicensePoller(
- monitoringClient,
+ fakeLegacyClusterClient,
config.licensing.api_polling_frequency.asMilliseconds()
);
diff --git a/x-pack/plugins/monitoring/server/plugin.ts b/x-pack/plugins/monitoring/server/plugin.ts
index 10724594ce576d..ca8d87bd300a08 100644
--- a/x-pack/plugins/monitoring/server/plugin.ts
+++ b/x-pack/plugins/monitoring/server/plugin.ts
@@ -15,11 +15,12 @@ import {
KibanaRequest,
KibanaResponseFactory,
CoreSetup,
- ILegacyCustomClusterClient,
+ ICustomClusterClient,
CoreStart,
CustomHttpResponseOptions,
ResponseError,
Plugin,
+ SharedGlobalConfig,
} from 'kibana/server';
import { DEFAULT_APP_CATEGORIES } from '../../../../src/core/server';
import {
@@ -33,8 +34,6 @@ import { MonitoringConfig, createConfig, configSchema } from './config';
import { requireUIRoutes } from './routes';
import { initBulkUploader } from './kibana_monitoring';
import { initInfraSource } from './lib/logs/init_infra_source';
-import { mbSafeQuery } from './lib/mb_safe_query';
-import { instantiateClient } from './es_client/instantiate_client';
import { registerCollectors } from './kibana_monitoring/collectors';
import { registerMonitoringTelemetryCollection } from './telemetry_collection';
import { LicenseService } from './license_service';
@@ -51,7 +50,8 @@ import {
RequestHandlerContextMonitoringPlugin,
} from './types';
-import { Globals } from './static_globals';
+import { Globals, EndpointTypes } from './static_globals';
+import { instantiateClient } from './es_client/instantiate_client';
// This is used to test the version of kibana
const snapshotRegex = /-snapshot/i;
@@ -71,50 +71,68 @@ export class MonitoringPlugin
private readonly initializerContext: PluginInitializerContext;
private readonly log: Logger;
private readonly getLogger: (...scopes: string[]) => Logger;
- private cluster = {} as ILegacyCustomClusterClient;
+ private cluster = {} as ICustomClusterClient;
private licenseService = {} as MonitoringLicenseService;
private monitoringCore = {} as MonitoringCore;
private legacyShimDependencies = {} as LegacyShimDependencies;
- private bulkUploader: IBulkUploader | undefined;
+ private bulkUploader?: IBulkUploader;
+
+ private readonly config: MonitoringConfig;
+ private readonly legacyConfig: SharedGlobalConfig;
+ private coreSetup?: CoreSetup;
+ private setupPlugins?: PluginsSetup;
constructor(initializerContext: PluginInitializerContext) {
this.initializerContext = initializerContext;
this.log = initializerContext.logger.get(LOGGING_TAG);
this.getLogger = (...scopes: string[]) => initializerContext.logger.get(LOGGING_TAG, ...scopes);
+ this.config = createConfig(this.initializerContext.config.get>());
+ this.legacyConfig = this.initializerContext.config.legacy.get();
}
- setup(core: CoreSetup, plugins: PluginsSetup) {
- const config = createConfig(this.initializerContext.config.get>());
- const legacyConfig = this.initializerContext.config.legacy.get();
+ setup(coreSetup: CoreSetup, plugins: PluginsSetup) {
+ this.coreSetup = coreSetup;
+ this.setupPlugins = plugins;
- const router = core.http.createRouter();
- this.legacyShimDependencies = {
- router,
- instanceUuid: this.initializerContext.env.instanceUuid,
- esDataClient: core.elasticsearch.legacy.client,
- kibanaStatsCollector: plugins.usageCollection?.getCollectorByType(
- KIBANA_STATS_TYPE_MONITORING
- ),
- };
+ const serverInfo = coreSetup.http.getServerInfo();
+ const kibanaMonitoringLog = this.getLogger(KIBANA_MONITORING_LOGGING_TAG);
+ this.bulkUploader = initBulkUploader({
+ config: this.config,
+ log: kibanaMonitoringLog,
+ opsMetrics$: coreSetup.metrics.getOpsMetrics$(),
+ statusGetter$: coreSetup.status.overall$,
+ kibanaStats: {
+ uuid: this.initializerContext.env.instanceUuid,
+ name: serverInfo.name,
+ index: this.legacyConfig.kibana.index,
+ host: serverInfo.hostname,
+ locale: i18n.getLocale(),
+ port: serverInfo.port.toString(),
+ transport_address: `${serverInfo.hostname}:${serverInfo.port}`,
+ version: this.initializerContext.env.packageInfo.version,
+ snapshot: snapshotRegex.test(this.initializerContext.env.packageInfo.version),
+ },
+ });
- // Monitoring creates and maintains a connection to a potentially
- // separate ES cluster - create this first
- const cluster = (this.cluster = instantiateClient(
- config.ui.elasticsearch,
- this.log,
- core.elasticsearch.legacy.createClient
- ));
+ Globals.init({
+ initializerContext: this.initializerContext,
+ config: this.config!,
+ getLogger: this.getLogger,
+ log: this.log,
+ legacyConfig: this.legacyConfig,
+ coreSetup: this.coreSetup!,
+ setupPlugins: this.setupPlugins!,
+ });
- Globals.init(core, plugins.cloud, cluster, config, this.getLogger);
- const serverInfo = core.http.getServerInfo();
const alerts = AlertsFactory.getAll();
for (const alert of alerts) {
plugins.alerting?.registerType(alert.getAlertType());
}
+ const config = createConfig(this.initializerContext.config.get>());
// Register collector objects for stats to show up in the APIs
if (plugins.usageCollection) {
- core.savedObjects.registerType({
+ coreSetup.savedObjects.registerType({
name: SAVED_OBJECT_TELEMETRY,
hidden: true,
namespaceType: 'agnostic',
@@ -127,33 +145,40 @@ export class MonitoringPlugin
},
});
- registerCollectors(plugins.usageCollection, config, cluster);
+ registerCollectors(plugins.usageCollection, config, () => this.cluster);
registerMonitoringTelemetryCollection(
plugins.usageCollection,
- cluster,
+ () => this.cluster,
config.ui.max_bucket_size
);
}
+ if (config.ui.enabled) {
+ this.registerPluginInUI(plugins);
+ }
- // Always create the bulk uploader
- const kibanaMonitoringLog = this.getLogger(KIBANA_MONITORING_LOGGING_TAG);
- const bulkUploader = (this.bulkUploader = initBulkUploader({
- config,
- log: kibanaMonitoringLog,
- opsMetrics$: core.metrics.getOpsMetrics$(),
- statusGetter$: core.status.overall$,
- kibanaStats: {
- uuid: this.initializerContext.env.instanceUuid,
- name: serverInfo.name,
- index: get(legacyConfig, 'kibana.index'),
- host: serverInfo.hostname,
- locale: i18n.getLocale(),
- port: serverInfo.port.toString(),
- transport_address: `${serverInfo.hostname}:${serverInfo.port}`,
- version: this.initializerContext.env.packageInfo.version,
- snapshot: snapshotRegex.test(this.initializerContext.env.packageInfo.version),
- },
- }));
+ return {
+ // OSS stats api needs to call this in order to centralize how
+ // we fetch kibana specific stats
+ getKibanaStats: () => this.bulkUploader?.getKibanaStats() || {},
+ };
+ }
+
+ init(cluster: ICustomClusterClient, coreStart: CoreStart) {
+ const config = createConfig(this.initializerContext.config.get>());
+ const legacyConfig = this.initializerContext.config.legacy.get();
+ const coreSetup = this.coreSetup!;
+ const plugins = this.setupPlugins!;
+
+ const router = coreSetup.http.createRouter();
+ // const [{ elasticsearch }] = await core.getStartServices();
+ this.legacyShimDependencies = {
+ router,
+ instanceUuid: this.initializerContext.env.instanceUuid,
+ esDataClient: coreStart.elasticsearch.client.asInternalUser,
+ kibanaStatsCollector: plugins.usageCollection?.getCollectorByType(
+ KIBANA_STATS_TYPE_MONITORING
+ ),
+ };
// If the UI is enabled, then we want to register it so it shows up
// and start any other UI-related setup tasks
@@ -162,12 +187,11 @@ export class MonitoringPlugin
this.monitoringCore = this.getLegacyShim(
config,
legacyConfig,
- core.getStartServices as () => Promise<[CoreStart, PluginsStart, {}]>,
- this.cluster,
+ coreSetup.getStartServices as () => Promise<[CoreStart, PluginsStart, {}]>,
+ cluster,
plugins
);
- this.registerPluginInUI(plugins);
requireUIRoutes(this.monitoringCore, {
cluster,
router,
@@ -177,16 +201,18 @@ export class MonitoringPlugin
});
initInfraSource(config, plugins.infra);
}
-
- return {
- // OSS stats api needs to call this in order to centralize how
- // we fetch kibana specific stats
- getKibanaStats: () => bulkUploader.getKibanaStats(),
- };
}
- async start(core: CoreStart, { licensing }: PluginsStart) {
- const config = createConfig(this.initializerContext.config.get>());
+ async start(coreStart: CoreStart, { licensing }: PluginsStart) {
+ const config = this.config!;
+ this.cluster = instantiateClient(
+ config.ui.elasticsearch,
+ this.log,
+ coreStart.elasticsearch.createClient
+ );
+
+ this.init(this.cluster, coreStart);
+
// Start our license service which will ensure
// the appropriate licenses are present
this.licenseService = new LicenseService().setup({
@@ -209,7 +235,7 @@ export class MonitoringPlugin
const monitoringBulkEnabled =
mainMonitoring && mainMonitoring.isAvailable && mainMonitoring.isEnabled;
if (monitoringBulkEnabled) {
- this.bulkUploader?.start(core.elasticsearch.client.asInternalUser);
+ this.bulkUploader?.start(coreStart.elasticsearch.client.asInternalUser);
} else {
this.bulkUploader?.handleNotEnabled();
}
@@ -227,7 +253,7 @@ export class MonitoringPlugin
}
stop() {
- if (this.cluster) {
+ if (this.cluster && this.cluster.close) {
this.cluster.close();
}
if (this.licenseService && this.licenseService.stop) {
@@ -281,7 +307,7 @@ export class MonitoringPlugin
config: MonitoringConfig,
legacyConfig: any,
getCoreServices: () => Promise<[CoreStart, PluginsStart, {}]>,
- cluster: ILegacyCustomClusterClient,
+ cluster: ICustomClusterClient,
setupPlugins: PluginsSetup
): MonitoringCore {
const router = this.legacyShimDependencies.router;
@@ -354,12 +380,12 @@ export class MonitoringPlugin
},
elasticsearch: {
getCluster: (name: string) => ({
- callWithRequest: async (_req: any, endpoint: string, params: any) => {
+ callWithRequest: async (_req: any, endpoint: EndpointTypes, params: any) => {
const client =
- name === 'monitoring' ? cluster : this.legacyShimDependencies.esDataClient;
- return mbSafeQuery(() =>
- client.asScoped(req).callAsCurrentUser(endpoint, params)
- );
+ name === 'monitoring'
+ ? cluster.asScoped(req).asCurrentUser
+ : context.core.elasticsearch.client.asCurrentUser;
+ return await Globals.app.getLegacyClusterShim(client, endpoint, params);
},
}),
},
diff --git a/x-pack/plugins/monitoring/server/routes/api/v1/alerts/enable.ts b/x-pack/plugins/monitoring/server/routes/api/v1/alerts/enable.ts
index e48f424a3d8ee4..e21304e8458e37 100644
--- a/x-pack/plugins/monitoring/server/routes/api/v1/alerts/enable.ts
+++ b/x-pack/plugins/monitoring/server/routes/api/v1/alerts/enable.ts
@@ -93,7 +93,7 @@ export function enableAlertsRoute(server: LegacyServer, npRoute: RouteDependenci
let createdAlerts: Array> = [];
const disabledWatcherClusterAlerts = await disableWatcherClusterAlerts(
- npRoute.cluster.asScoped(request).callAsCurrentUser,
+ npRoute.cluster.asScoped(request).asCurrentUser,
npRoute.logger
);
diff --git a/x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch/ccr.ts b/x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch/ccr.ts
index 5cc7046777e265..73b646126ce988 100644
--- a/x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch/ccr.ts
+++ b/x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch/ccr.ts
@@ -99,7 +99,7 @@ function buildRequest(
return {
index: esIndexPattern,
size: maxBucketSize,
- filterPath: [
+ filter_path: [
'hits.hits.inner_hits.by_shard.hits.hits._source.ccr_stats.read_exceptions',
'hits.hits.inner_hits.by_shard.hits.hits._source.elasticsearch.ccr.read_exceptions',
'hits.hits.inner_hits.by_shard.hits.hits._source.ccr_stats.follower_index',
diff --git a/x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch/ccr_shard.ts b/x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch/ccr_shard.ts
index ac5563430fb7ca..5ecb84d97618b0 100644
--- a/x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch/ccr_shard.ts
+++ b/x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch/ccr_shard.ts
@@ -35,7 +35,7 @@ async function getCcrStat(req: LegacyRequest, esIndexPattern: string, filters: u
const params = {
index: esIndexPattern,
size: 1,
- filterPath: [
+ filter_path: [
'hits.hits._source.ccr_stats',
'hits.hits._source.elasticsearch.ccr',
'hits.hits._source.timestamp',
diff --git a/x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch_settings/check/cluster.js b/x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch_settings/check/cluster.js
index b8cdec9ed9b00f..6996c4885d25dc 100644
--- a/x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch_settings/check/cluster.js
+++ b/x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch_settings/check/cluster.js
@@ -23,6 +23,7 @@ export function clusterSettingsCheckRoute(server) {
const response = await checkClusterSettings(req); // needs to be try/catch to handle privilege error
return response;
} catch (err) {
+ console.log(err);
throw handleSettingsError(err);
}
},
diff --git a/x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch_settings/check/internal_monitoring.ts b/x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch_settings/check/internal_monitoring.ts
index 1c4ccdada095f6..c2bad7b905c5b1 100644
--- a/x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch_settings/check/internal_monitoring.ts
+++ b/x-pack/plugins/monitoring/server/routes/api/v1/elasticsearch_settings/check/internal_monitoring.ts
@@ -7,6 +7,7 @@
import { schema } from '@kbn/config-schema';
import { RequestHandlerContext } from 'kibana/server';
+import { estypes } from '@elastic/elasticsearch';
import {
INDEX_PATTERN_ELASTICSEARCH,
INDEX_PATTERN_KIBANA,
@@ -44,11 +45,11 @@ const queryBody = {
};
const checkLatestMonitoringIsLegacy = async (context: RequestHandlerContext, index: string) => {
- const { client: esClient } = context.core.elasticsearch.legacy;
- const result = await esClient.callAsCurrentUser('search', {
+ const client = context.core.elasticsearch.client.asCurrentUser;
+ const { body: result } = await client.search>({
index,
body: queryBody,
- });
+ } as estypes.SearchRequest);
const { aggregations } = result;
const counts = {
@@ -62,7 +63,7 @@ const checkLatestMonitoringIsLegacy = async (context: RequestHandlerContext, ind
const {
types: { buckets },
- } = aggregations;
+ } = aggregations as { types: { buckets: Array<{ key: string }> } };
counts.mbIndicesCount = buckets.filter(({ key }: { key: string }) => key.includes('-mb-')).length;
counts.legacyIndicesCount = buckets.length - counts.mbIndicesCount;
diff --git a/x-pack/plugins/monitoring/server/static_globals.ts b/x-pack/plugins/monitoring/server/static_globals.ts
index 1bb08d6cafc384..fc4ece32314864 100644
--- a/x-pack/plugins/monitoring/server/static_globals.ts
+++ b/x-pack/plugins/monitoring/server/static_globals.ts
@@ -5,20 +5,50 @@
* 2.0.
*/
-import { CoreSetup, ILegacyCustomClusterClient, Logger } from 'kibana/server';
+import {
+ CoreSetup,
+ ElasticsearchClient,
+ Logger,
+ SharedGlobalConfig,
+ PluginInitializerContext,
+} from 'kibana/server';
import url from 'url';
-import { CloudSetup } from '../../cloud/server';
+import { estypes } from '@elastic/elasticsearch';
import { MonitoringConfig } from './config';
-
+import { PluginsSetup } from './types';
+import { mbSafeQuery } from './lib/mb_safe_query';
type GetLogger = (...scopes: string[]) => Logger;
+interface InitSetupOptions {
+ initializerContext: PluginInitializerContext;
+ coreSetup: CoreSetup;
+ config: MonitoringConfig;
+ getLogger: GetLogger;
+ log: Logger;
+ legacyConfig: SharedGlobalConfig;
+ setupPlugins: PluginsSetup;
+}
+
+export type EndpointTypes =
+ | 'search'
+ | 'msearch'
+ | 'transport.request'
+ | 'cluster.putSettings'
+ | 'cluster.getSettings'
+ | string;
+export type ClientParams = estypes.SearchRequest | undefined;
+
interface IAppGlobals {
url: string;
isCloud: boolean;
- monitoringCluster: ILegacyCustomClusterClient;
config: MonitoringConfig;
getLogger: GetLogger;
getKeyStoreValue: (key: string, storeValueMethod?: () => unknown) => unknown;
+ getLegacyClusterShim: (
+ client: ElasticsearchClient,
+ endpoint: EndpointTypes,
+ params: ClientParams
+ ) => any;
}
interface KeyStoreData {
@@ -37,22 +67,35 @@ const getKeyStoreValue = (key: string, storeValueMethod?: () => unknown) => {
export class Globals {
private static _app: IAppGlobals;
- public static init(
- coreSetup: CoreSetup,
- cloud: CloudSetup | undefined,
- monitoringCluster: ILegacyCustomClusterClient,
- config: MonitoringConfig,
- getLogger: GetLogger
- ) {
+ public static init(options: InitSetupOptions) {
+ const { coreSetup, setupPlugins, config, getLogger } = options;
+ const getLegacyClusterShim = async (
+ client: ElasticsearchClient,
+ endpoint: EndpointTypes,
+ params: ClientParams
+ ): Promise =>
+ await mbSafeQuery(async () => {
+ const endpointMap: { [key: string]: (params: any) => any } = {
+ search: (p) => client.search(p),
+ msearch: (p) => client.msearch(p),
+ 'transport.request': (p) => client.transport.request(p),
+ 'cluster.getSettings': (p) => client.cluster.getSettings(p),
+ 'cluster.putSettings': (p) => client.cluster.putSettings(p),
+ };
+ const { body } = await endpointMap[endpoint](params);
+ return body;
+ });
+
const { protocol, hostname, port } = coreSetup.http.getServerInfo();
const pathname = coreSetup.http.basePath.serverBasePath;
+
Globals._app = {
url: url.format({ protocol, hostname, port, pathname }),
- isCloud: cloud?.isCloudEnabled || false,
- monitoringCluster,
+ isCloud: setupPlugins.cloud?.isCloudEnabled || false,
config,
getLogger,
getKeyStoreValue,
+ getLegacyClusterShim,
};
}
@@ -64,4 +107,6 @@ export class Globals {
}
return Globals._app;
}
+
+ public static stop() {}
}
diff --git a/x-pack/plugins/monitoring/server/telemetry_collection/get_all_stats.test.ts b/x-pack/plugins/monitoring/server/telemetry_collection/get_all_stats.test.ts
index 33487ecafd8c52..8933833bb068a2 100644
--- a/x-pack/plugins/monitoring/server/telemetry_collection/get_all_stats.test.ts
+++ b/x-pack/plugins/monitoring/server/telemetry_collection/get_all_stats.test.ts
@@ -5,6 +5,7 @@
* 2.0.
*/
+import { ElasticsearchClient } from 'kibana/server';
import sinon from 'sinon';
import { getStackStats, getAllStats, handleAllStats } from './get_all_stats';
import { ESClusterStats } from './get_es_stats';
@@ -13,7 +14,11 @@ import { LogstashStatsByClusterUuid } from './get_logstash_stats';
describe('get_all_stats', () => {
const timestamp = Date.now();
- const callCluster = sinon.stub();
+ const searchMock = sinon.stub();
+ const callCluster = ({ search: searchMock } as unknown) as ElasticsearchClient;
+ afterEach(() => {
+ searchMock.reset();
+ });
const esClusters = [
{ cluster_uuid: 'a' },
@@ -157,18 +162,20 @@ describe('get_all_stats', () => {
},
];
- callCluster
- .withArgs('search')
+ searchMock
.onCall(0)
- .returns(Promise.resolve(esStatsResponse))
+ .returns(Promise.resolve({ body: esStatsResponse }))
.onCall(1)
- .returns(Promise.resolve(kibanaStatsResponse))
+ .returns(Promise.resolve({ body: kibanaStatsResponse }))
.onCall(2)
- .returns(Promise.resolve(logstashStatsResponse))
+ .returns(Promise.resolve({ body: logstashStatsResponse }))
+ .returns(Promise.resolve({ body: logstashStatsResponse }))
.onCall(3)
- .returns(Promise.resolve({})) // Beats stats
+ .returns(Promise.resolve({ body: {} })) // Beats stats
.onCall(4)
- .returns(Promise.resolve({})); // Beats state
+ .returns(Promise.resolve({ body: {} })) // Beats state
+ .onCall(5)
+ .returns(Promise.resolve({ body: {} })); // Logstash state
expect(await getAllStats(['a'], callCluster, timestamp, 1)).toStrictEqual(allClusters);
});
@@ -178,7 +185,7 @@ describe('get_all_stats', () => {
aggregations: { cluster_uuids: { buckets: [] } },
};
- callCluster.withArgs('search').returns(Promise.resolve(clusterUuidsResponse));
+ searchMock.returns(Promise.resolve({ body: clusterUuidsResponse }));
expect(await getAllStats([], callCluster, timestamp, 1)).toStrictEqual([]);
});
diff --git a/x-pack/plugins/monitoring/server/telemetry_collection/get_all_stats.ts b/x-pack/plugins/monitoring/server/telemetry_collection/get_all_stats.ts
index 60b107cb293426..a0bad277dacf66 100644
--- a/x-pack/plugins/monitoring/server/telemetry_collection/get_all_stats.ts
+++ b/x-pack/plugins/monitoring/server/telemetry_collection/get_all_stats.ts
@@ -9,7 +9,7 @@ import { set } from '@elastic/safer-lodash-set';
import { get, merge } from 'lodash';
import moment from 'moment';
-import { LegacyAPICaller } from 'kibana/server';
+import { ElasticsearchClient } from 'kibana/server';
import {
LOGSTASH_SYSTEM_ID,
KIBANA_SYSTEM_ID,
@@ -28,7 +28,7 @@ import { getLogstashStats, LogstashStatsByClusterUuid } from './get_logstash_sta
*/
export async function getAllStats(
clusterUuids: string[],
- callCluster: LegacyAPICaller, // TODO: To be changed to the new ES client when the plugin migrates
+ callCluster: ElasticsearchClient,
timestamp: number,
maxBucketSize: number
) {
diff --git a/x-pack/plugins/monitoring/server/telemetry_collection/get_beats_stats.test.ts b/x-pack/plugins/monitoring/server/telemetry_collection/get_beats_stats.test.ts
index 3637ace3aeb63e..36477cb48cd891 100644
--- a/x-pack/plugins/monitoring/server/telemetry_collection/get_beats_stats.test.ts
+++ b/x-pack/plugins/monitoring/server/telemetry_collection/get_beats_stats.test.ts
@@ -7,6 +7,7 @@
import { fetchBeatsStats, processResults } from './get_beats_stats';
import sinon from 'sinon';
+import { ElasticsearchClient } from 'kibana/server';
// eslint-disable-next-line @typescript-eslint/no-var-requires
const beatsStatsResultSet = require('./__mocks__/fixtures/beats_stats_results');
@@ -23,40 +24,41 @@ describe('Get Beats Stats', () => {
const clusterUuids = ['aCluster', 'bCluster', 'cCluster'];
const start = new Date().toISOString();
const end = new Date().toISOString();
- let callCluster = sinon.stub();
+ const searchMock = sinon.stub();
+ const callCluster = ({ search: searchMock } as unknown) as ElasticsearchClient;
beforeEach(() => {
const getStub = { get: sinon.stub() };
getStub.get.withArgs('xpack.monitoring.beats.index_pattern').returns('beats-indices-*');
- callCluster = sinon.stub();
+ searchMock.reset();
});
it('should set `from: 0, to: 10000` in the query', async () => {
+ searchMock.returns(Promise.resolve({ body: {} }));
await fetchBeatsStats(callCluster, clusterUuids, start, end, {} as any);
- const { args } = callCluster.firstCall;
- const [api, { body }] = args;
+ const { args } = searchMock.firstCall;
+ const [{ body }] = args;
- expect(api).toEqual('search');
expect(body.from).toEqual(0);
expect(body.size).toEqual(10000);
});
it('should set `from: 10000, from: 10000` in the query', async () => {
+ searchMock.returns(Promise.resolve({ body: {} }));
await fetchBeatsStats(callCluster, clusterUuids, start, end, { page: 1 } as any);
- const { args } = callCluster.firstCall;
- const [api, { body }] = args;
+ const { args } = searchMock.firstCall;
+ const [{ body }] = args;
- expect(api).toEqual('search');
expect(body.from).toEqual(10000);
expect(body.size).toEqual(10000);
});
it('should set `from: 20000, from: 10000` in the query', async () => {
+ searchMock.returns(Promise.resolve({ body: {} }));
await fetchBeatsStats(callCluster, clusterUuids, start, end, { page: 2 } as any);
- const { args } = callCluster.firstCall;
- const [api, { body }] = args;
+ const { args } = searchMock.firstCall;
+ const [{ body }] = args;
- expect(api).toEqual('search');
expect(body.from).toEqual(20000);
expect(body.size).toEqual(10000);
});
diff --git a/x-pack/plugins/monitoring/server/telemetry_collection/get_beats_stats.ts b/x-pack/plugins/monitoring/server/telemetry_collection/get_beats_stats.ts
index ba83584e346545..c62fbf4e8a1cfb 100644
--- a/x-pack/plugins/monitoring/server/telemetry_collection/get_beats_stats.ts
+++ b/x-pack/plugins/monitoring/server/telemetry_collection/get_beats_stats.ts
@@ -7,7 +7,8 @@
import { get } from 'lodash';
import { SearchResponse } from 'elasticsearch';
-import { LegacyAPICaller } from 'kibana/server';
+import { ElasticsearchClient } from 'kibana/server';
+import { estypes } from '@elastic/elasticsearch';
import { createQuery } from './create_query';
import { INDEX_PATTERN_BEATS } from '../../common/constants';
@@ -319,17 +320,17 @@ export function processResults(
* @return {Promise}
*/
async function fetchBeatsByType(
- callCluster: LegacyAPICaller,
+ callCluster: ElasticsearchClient,
clusterUuids: string[],
start: string,
end: string,
{ page = 0, ...options }: { page?: number } & BeatsProcessOptions,
type: string
): Promise {
- const params = {
+ const params: estypes.SearchRequest = {
index: INDEX_PATTERN_BEATS,
- ignoreUnavailable: true,
- filterPath: [
+ ignore_unavailable: true,
+ filter_path: [
'hits.hits._source.cluster_uuid',
'hits.hits._source.type',
'hits.hits._source.beats_stats.beat.version',
@@ -353,7 +354,7 @@ async function fetchBeatsByType(
},
},
],
- }),
+ }) as estypes.QueryDslQueryContainer,
from: page * HITS_SIZE,
collapse: { field: `${type}.beat.uuid` },
sort: [{ [`${type}.timestamp`]: { order: 'desc', unmapped_type: 'long' } }],
@@ -361,11 +362,11 @@ async function fetchBeatsByType(
},
};
- const results = await callCluster>('search', params);
+ const { body: results } = await callCluster.search(params);
const hitsLength = results?.hits?.hits.length || 0;
if (hitsLength > 0) {
// further augment the clusters object with more stats
- processResults(results, options);
+ processResults(results as SearchResponse, options);
if (hitsLength === HITS_SIZE) {
// call recursively
@@ -383,7 +384,7 @@ async function fetchBeatsByType(
}
export async function fetchBeatsStats(
- callCluster: LegacyAPICaller,
+ callCluster: ElasticsearchClient,
clusterUuids: string[],
start: string,
end: string,
@@ -393,7 +394,7 @@ export async function fetchBeatsStats(
}
export async function fetchBeatsStates(
- callCluster: LegacyAPICaller,
+ callCluster: ElasticsearchClient,
clusterUuids: string[],
start: string,
end: string,
@@ -411,7 +412,7 @@ export interface BeatsStatsByClusterUuid {
* @return {Object} - Beats stats in an object keyed by the cluster UUIDs
*/
export async function getBeatsStats(
- callCluster: LegacyAPICaller,
+ callCluster: ElasticsearchClient,
clusterUuids: string[],
start: string,
end: string
diff --git a/x-pack/plugins/monitoring/server/telemetry_collection/get_cluster_uuids.test.ts b/x-pack/plugins/monitoring/server/telemetry_collection/get_cluster_uuids.test.ts
index 07d623867bc1c6..7e7ec23b69daf3 100644
--- a/x-pack/plugins/monitoring/server/telemetry_collection/get_cluster_uuids.test.ts
+++ b/x-pack/plugins/monitoring/server/telemetry_collection/get_cluster_uuids.test.ts
@@ -5,6 +5,7 @@
* 2.0.
*/
+import { ElasticsearchClient } from 'kibana/server';
import sinon from 'sinon';
import {
getClusterUuids,
@@ -13,40 +14,52 @@ import {
} from './get_cluster_uuids';
describe('get_cluster_uuids', () => {
- const callCluster = sinon.stub();
+ const searchMock = sinon.stub();
+ const callCluster = ({ search: searchMock } as unknown) as ElasticsearchClient;
+
+ afterEach(() => {
+ searchMock.reset();
+ });
+
const response = {
- aggregations: {
- cluster_uuids: {
- buckets: [{ key: 'abc' }, { key: 'xyz' }, { key: '123' }],
+ body: {
+ aggregations: {
+ cluster_uuids: {
+ buckets: [{ key: 'abc' }, { key: 'xyz' }, { key: '123' }],
+ },
},
},
};
- const expectedUuids = response.aggregations.cluster_uuids.buckets.map((bucket) => bucket.key);
+
+ const expectedUuids = response.body.aggregations.cluster_uuids.buckets.map(
+ (bucket) => bucket.key
+ );
+
const timestamp = Date.now();
describe('getClusterUuids', () => {
it('returns cluster UUIDs', async () => {
- callCluster.withArgs('search').returns(Promise.resolve(response));
+ searchMock.returns(Promise.resolve(response));
expect(await getClusterUuids(callCluster, timestamp, 1)).toStrictEqual(expectedUuids);
});
});
describe('fetchClusterUuids', () => {
it('searches for clusters', async () => {
- callCluster.returns(Promise.resolve(response));
- expect(await fetchClusterUuids(callCluster, timestamp, 1)).toStrictEqual(response);
+ searchMock.returns(Promise.resolve(response));
+ expect(await fetchClusterUuids(callCluster, timestamp, 1)).toStrictEqual(response.body);
});
});
describe('handleClusterUuidsResponse', () => {
- // filterPath makes it easy to ignore anything unexpected because it will come back empty
+ // filter_path makes it easy to ignore anything unexpected because it will come back empty
it('handles unexpected response', () => {
const clusterUuids = handleClusterUuidsResponse({});
expect(clusterUuids.length).toStrictEqual(0);
});
it('handles valid response', () => {
- const clusterUuids = handleClusterUuidsResponse(response);
+ const clusterUuids = handleClusterUuidsResponse(response.body);
expect(clusterUuids).toStrictEqual(expectedUuids);
});
diff --git a/x-pack/plugins/monitoring/server/telemetry_collection/get_cluster_uuids.ts b/x-pack/plugins/monitoring/server/telemetry_collection/get_cluster_uuids.ts
index db7a031385b4a7..7cf4ca2b94ce01 100644
--- a/x-pack/plugins/monitoring/server/telemetry_collection/get_cluster_uuids.ts
+++ b/x-pack/plugins/monitoring/server/telemetry_collection/get_cluster_uuids.ts
@@ -7,7 +7,8 @@
import { get } from 'lodash';
import moment from 'moment';
-import { LegacyAPICaller } from 'kibana/server';
+import { ElasticsearchClient } from 'kibana/server';
+import { estypes } from '@elastic/elasticsearch';
import { createQuery } from './create_query';
import {
INDEX_PATTERN_ELASTICSEARCH,
@@ -18,7 +19,7 @@ import {
* Get a list of Cluster UUIDs that exist within the specified timespan.
*/
export async function getClusterUuids(
- callCluster: LegacyAPICaller, // TODO: To be changed to the new ES client when the plugin migrates
+ callCluster: ElasticsearchClient,
timestamp: number,
maxBucketSize: number
) {
@@ -30,7 +31,7 @@ export async function getClusterUuids(
* Fetch the aggregated Cluster UUIDs from the monitoring cluster.
*/
export async function fetchClusterUuids(
- callCluster: LegacyAPICaller,
+ callCluster: ElasticsearchClient,
timestamp: number,
maxBucketSize: number
) {
@@ -38,13 +39,13 @@ export async function fetchClusterUuids(
const end = moment(timestamp).toISOString();
- const params = {
+ const params: estypes.SearchRequest = {
index: INDEX_PATTERN_ELASTICSEARCH,
size: 0,
- ignoreUnavailable: true,
- filterPath: 'aggregations.cluster_uuids.buckets.key',
+ ignore_unavailable: true,
+ filter_path: 'aggregations.cluster_uuids.buckets.key',
body: {
- query: createQuery({ type: 'cluster_stats', start, end }),
+ query: createQuery({ type: 'cluster_stats', start, end }) as estypes.QueryDslQueryContainer,
aggs: {
cluster_uuids: {
terms: {
@@ -56,7 +57,8 @@ export async function fetchClusterUuids(
},
};
- return await callCluster('search', params);
+ const { body: response } = await callCluster.search(params);
+ return response;
}
/**
diff --git a/x-pack/plugins/monitoring/server/telemetry_collection/get_es_stats.test.ts b/x-pack/plugins/monitoring/server/telemetry_collection/get_es_stats.test.ts
index 033b3ebffe5f16..3b5f654be42223 100644
--- a/x-pack/plugins/monitoring/server/telemetry_collection/get_es_stats.test.ts
+++ b/x-pack/plugins/monitoring/server/telemetry_collection/get_es_stats.test.ts
@@ -5,6 +5,7 @@
* 2.0.
*/
+import { ElasticsearchClient } from 'kibana/server';
import sinon from 'sinon';
import {
fetchElasticsearchStats,
@@ -13,8 +14,9 @@ import {
} from './get_es_stats';
describe('get_es_stats', () => {
- const callWith = sinon.stub();
- const response = {
+ const searchMock = sinon.stub();
+ const client = ({ search: searchMock } as unknown) as ElasticsearchClient;
+ const body = {
hits: {
hits: [
{ _id: 'abc', _source: { cluster_uuid: 'abc' } },
@@ -23,15 +25,15 @@ describe('get_es_stats', () => {
],
},
};
- const expectedClusters = response.hits.hits.map((hit) => hit._source);
+ const expectedClusters = body.hits.hits.map((hit) => hit._source);
const clusterUuids = expectedClusters.map((cluster) => cluster.cluster_uuid);
const maxBucketSize = 1;
describe('getElasticsearchStats', () => {
it('returns clusters', async () => {
- callWith.withArgs('search').returns(Promise.resolve(response));
+ searchMock.returns(Promise.resolve({ body }));
- expect(await getElasticsearchStats(callWith, clusterUuids, maxBucketSize)).toStrictEqual(
+ expect(await getElasticsearchStats(client, clusterUuids, maxBucketSize)).toStrictEqual(
expectedClusters
);
});
@@ -39,16 +41,16 @@ describe('get_es_stats', () => {
describe('fetchElasticsearchStats', () => {
it('searches for clusters', async () => {
- callWith.returns(response);
+ searchMock.returns({ body });
- expect(await fetchElasticsearchStats(callWith, clusterUuids, maxBucketSize)).toStrictEqual(
- response
+ expect(await fetchElasticsearchStats(client, clusterUuids, maxBucketSize)).toStrictEqual(
+ body
);
});
});
describe('handleElasticsearchStats', () => {
- // filterPath makes it easy to ignore anything unexpected because it will come back empty
+ // filter_path makes it easy to ignore anything unexpected because it will come back empty
it('handles unexpected response', () => {
const clusters = handleElasticsearchStats({} as any);
@@ -56,7 +58,7 @@ describe('get_es_stats', () => {
});
it('handles valid response', () => {
- const clusters = handleElasticsearchStats(response as any);
+ const clusters = handleElasticsearchStats(body as any);
expect(clusters).toStrictEqual(expectedClusters);
});
diff --git a/x-pack/plugins/monitoring/server/telemetry_collection/get_es_stats.ts b/x-pack/plugins/monitoring/server/telemetry_collection/get_es_stats.ts
index f309e01c961187..217ab70ab0ecaa 100644
--- a/x-pack/plugins/monitoring/server/telemetry_collection/get_es_stats.ts
+++ b/x-pack/plugins/monitoring/server/telemetry_collection/get_es_stats.ts
@@ -6,7 +6,8 @@
*/
import { SearchResponse } from 'elasticsearch';
-import { LegacyAPICaller } from 'kibana/server';
+import { ElasticsearchClient } from 'kibana/server';
+import { estypes } from '@elastic/elasticsearch';
import { INDEX_PATTERN_ELASTICSEARCH } from '../../common/constants';
/**
@@ -17,7 +18,7 @@ import { INDEX_PATTERN_ELASTICSEARCH } from '../../common/constants';
* @param {Array} clusterUuids The string Cluster UUIDs to fetch details for
*/
export async function getElasticsearchStats(
- callCluster: LegacyAPICaller,
+ callCluster: ElasticsearchClient,
clusterUuids: string[],
maxBucketSize: number
) {
@@ -34,16 +35,16 @@ export async function getElasticsearchStats(
*
* Returns the response for the aggregations to fetch details for the product.
*/
-export function fetchElasticsearchStats(
- callCluster: LegacyAPICaller,
+export async function fetchElasticsearchStats(
+ callCluster: ElasticsearchClient,
clusterUuids: string[],
maxBucketSize: number
) {
- const params = {
+ const params: estypes.SearchRequest = {
index: INDEX_PATTERN_ELASTICSEARCH,
size: maxBucketSize,
- ignoreUnavailable: true,
- filterPath: [
+ ignore_unavailable: true,
+ filter_path: [
'hits.hits._source.cluster_uuid',
'hits.hits._source.timestamp',
'hits.hits._source.cluster_name',
@@ -69,7 +70,8 @@ export function fetchElasticsearchStats(
},
};
- return callCluster('search', params);
+ const { body: response } = await callCluster.search(params);
+ return response as SearchResponse;
}
export interface ESClusterStats {
diff --git a/x-pack/plugins/monitoring/server/telemetry_collection/get_high_level_stats.test.ts b/x-pack/plugins/monitoring/server/telemetry_collection/get_high_level_stats.test.ts
index e2eed7381f4511..00a14f7b12f7ed 100644
--- a/x-pack/plugins/monitoring/server/telemetry_collection/get_high_level_stats.test.ts
+++ b/x-pack/plugins/monitoring/server/telemetry_collection/get_high_level_stats.test.ts
@@ -5,6 +5,7 @@
* 2.0.
*/
+import { ElasticsearchClient } from 'kibana/server';
import sinon from 'sinon';
import {
fetchHighLevelStats,
@@ -13,12 +14,13 @@ import {
} from './get_high_level_stats';
describe('get_high_level_stats', () => {
- const callWith = sinon.stub();
+ const searchMock = sinon.stub();
+ const callCluster = ({ search: searchMock } as unknown) as ElasticsearchClient;
const product = 'xyz';
const cloudName = 'bare-metal';
const start = new Date().toISOString();
const end = new Date().toISOString();
- const response = {
+ const body = {
hits: {
hits: [
{
@@ -232,26 +234,26 @@ describe('get_high_level_stats', () => {
describe('getHighLevelStats', () => {
it('returns clusters', async () => {
- callWith.withArgs('search').returns(Promise.resolve(response));
+ searchMock.returns(Promise.resolve({ body }));
expect(
- await getHighLevelStats(callWith, clusterUuids, start, end, product, maxBucketSize)
+ await getHighLevelStats(callCluster, clusterUuids, start, end, product, maxBucketSize)
).toStrictEqual(expectedClusters);
});
});
describe('fetchHighLevelStats', () => {
it('searches for clusters', async () => {
- callWith.returns(Promise.resolve(response));
+ searchMock.returns(Promise.resolve({ body }));
expect(
- await fetchHighLevelStats(callWith, clusterUuids, start, end, product, maxBucketSize)
- ).toStrictEqual(response);
+ await fetchHighLevelStats(callCluster, clusterUuids, start, end, product, maxBucketSize)
+ ).toStrictEqual(body);
});
});
describe('handleHighLevelStatsResponse', () => {
- // filterPath makes it easy to ignore anything unexpected because it will come back empty
+ // filter_path makes it easy to ignore anything unexpected because it will come back empty
it('handles unexpected response', () => {
const clusters = handleHighLevelStatsResponse({} as any, product);
@@ -259,7 +261,7 @@ describe('get_high_level_stats', () => {
});
it('handles valid response', () => {
- const clusters = handleHighLevelStatsResponse(response as any, product);
+ const clusters = handleHighLevelStatsResponse(body as any, product);
expect(clusters).toStrictEqual(expectedClusters);
});
diff --git a/x-pack/plugins/monitoring/server/telemetry_collection/get_high_level_stats.ts b/x-pack/plugins/monitoring/server/telemetry_collection/get_high_level_stats.ts
index 63188be142fdd6..2fb3814ac40224 100644
--- a/x-pack/plugins/monitoring/server/telemetry_collection/get_high_level_stats.ts
+++ b/x-pack/plugins/monitoring/server/telemetry_collection/get_high_level_stats.ts
@@ -7,7 +7,8 @@
import { get } from 'lodash';
import { SearchResponse } from 'elasticsearch';
-import { LegacyAPICaller } from 'kibana/server';
+import { ElasticsearchClient } from 'kibana/server';
+import { estypes } from '@elastic/elasticsearch';
import { createQuery } from './create_query';
import {
INDEX_PATTERN_KIBANA,
@@ -248,7 +249,7 @@ function getIndexPatternForStackProduct(product: string) {
* Returns an object keyed by the cluster UUIDs to make grouping easier.
*/
export async function getHighLevelStats(
- callCluster: LegacyAPICaller,
+ callCluster: ElasticsearchClient,
clusterUuids: string[],
start: string,
end: string,
@@ -269,7 +270,7 @@ export async function getHighLevelStats(
export async function fetchHighLevelStats<
T extends { cluster_uuid?: string } = { cluster_uuid?: string }
>(
- callCluster: LegacyAPICaller,
+ callCluster: ElasticsearchClient,
clusterUuids: string[],
start: string,
end: string,
@@ -300,14 +301,11 @@ export async function fetchHighLevelStats<
filters.push(kibanaFilter);
}
- const params = {
- index: getIndexPatternForStackProduct(product),
+ const params: estypes.SearchRequest = {
+ index: getIndexPatternForStackProduct(product) as string,
size: maxBucketSize,
- headers: {
- 'X-QUERY-SOURCE': TELEMETRY_QUERY_SOURCE,
- },
- ignoreUnavailable: true,
- filterPath: [
+ ignore_unavailable: true,
+ filter_path: [
'hits.hits._source.cluster_uuid',
`hits.hits._source.${product}_stats.${product}.version`,
`hits.hits._source.${product}_stats.os`,
@@ -325,7 +323,7 @@ export async function fetchHighLevelStats<
end,
type: `${product}_stats`,
filters,
- }),
+ }) as estypes.QueryDslQueryContainer,
collapse: {
// a more ideal field would be the concatenation of the uuid + transport address for duped UUIDs (copied installations)
field: `${product}_stats.${product}.uuid`,
@@ -334,7 +332,12 @@ export async function fetchHighLevelStats<
},
};
- return callCluster('search', params);
+ const { body: response } = await callCluster.search(params, {
+ headers: {
+ 'X-QUERY-SOURCE': TELEMETRY_QUERY_SOURCE,
+ },
+ });
+ return response as SearchResponse;
}
/**
diff --git a/x-pack/plugins/monitoring/server/telemetry_collection/get_kibana_stats.ts b/x-pack/plugins/monitoring/server/telemetry_collection/get_kibana_stats.ts
index 59a77bd7a6b4c0..4be5e02dff3a66 100644
--- a/x-pack/plugins/monitoring/server/telemetry_collection/get_kibana_stats.ts
+++ b/x-pack/plugins/monitoring/server/telemetry_collection/get_kibana_stats.ts
@@ -8,7 +8,7 @@
import moment from 'moment';
import { isEmpty } from 'lodash';
import { SearchResponse } from 'elasticsearch';
-import { LegacyAPICaller } from 'kibana/server';
+import { ElasticsearchClient } from 'kibana/server';
import { KIBANA_SYSTEM_ID, TELEMETRY_COLLECTION_INTERVAL } from '../../common/constants';
import {
fetchHighLevelStats,
@@ -183,7 +183,7 @@ export function ensureTimeSpan(
* specialized usage data that comes with kibana stats (kibana_stats.usage).
*/
export async function getKibanaStats(
- callCluster: LegacyAPICaller,
+ callCluster: ElasticsearchClient,
clusterUuids: string[],
start: string,
end: string,
diff --git a/x-pack/plugins/monitoring/server/telemetry_collection/get_licenses.test.ts b/x-pack/plugins/monitoring/server/telemetry_collection/get_licenses.test.ts
index ce4bf523b3e1ae..98f1daf57a85a6 100644
--- a/x-pack/plugins/monitoring/server/telemetry_collection/get_licenses.test.ts
+++ b/x-pack/plugins/monitoring/server/telemetry_collection/get_licenses.test.ts
@@ -5,12 +5,14 @@
* 2.0.
*/
+import { ElasticsearchClient } from 'kibana/server';
import sinon from 'sinon';
import { getLicenses, handleLicenses, fetchLicenses } from './get_licenses';
describe('get_licenses', () => {
- const callWith = sinon.stub();
- const response = {
+ const searchMock = sinon.stub();
+ const client = ({ search: searchMock } as unknown) as ElasticsearchClient;
+ const body = {
hits: {
hits: [
{ _id: 'abc', _source: { cluster_uuid: 'abc', license: { type: 'basic' } } },
@@ -19,7 +21,7 @@ describe('get_licenses', () => {
],
},
};
- const expectedClusters = response.hits.hits.map((hit) => hit._source);
+ const expectedClusters = body.hits.hits.map((hit) => hit._source);
const clusterUuids = expectedClusters.map((cluster) => cluster.cluster_uuid);
const expectedLicenses = {
abc: { type: 'basic' },
@@ -29,22 +31,22 @@ describe('get_licenses', () => {
describe('getLicenses', () => {
it('returns clusters', async () => {
- callWith.withArgs('search').returns(Promise.resolve(response));
+ searchMock.returns(Promise.resolve({ body }));
- expect(await getLicenses(clusterUuids, callWith, 1)).toStrictEqual(expectedLicenses);
+ expect(await getLicenses(clusterUuids, client, 1)).toStrictEqual(expectedLicenses);
});
});
describe('fetchLicenses', () => {
it('searches for clusters', async () => {
- callWith.returns(response);
+ searchMock.returns({ body });
- expect(await fetchLicenses(callWith, clusterUuids, 1)).toStrictEqual(response);
+ expect(await fetchLicenses(client, clusterUuids, 1)).toStrictEqual(body);
});
});
describe('handleLicenses', () => {
- // filterPath makes it easy to ignore anything unexpected because it will come back empty
+ // filter_path makes it easy to ignore anything unexpected because it will come back empty
it('handles unexpected response', () => {
const clusters = handleLicenses({} as any);
@@ -52,7 +54,7 @@ describe('get_licenses', () => {
});
it('handles valid response', () => {
- const clusters = handleLicenses(response as any);
+ const clusters = handleLicenses(body as any);
expect(clusters).toStrictEqual(expectedLicenses);
});
diff --git a/x-pack/plugins/monitoring/server/telemetry_collection/get_licenses.ts b/x-pack/plugins/monitoring/server/telemetry_collection/get_licenses.ts
index 3115e2adbdad27..633e37a2049bdd 100644
--- a/x-pack/plugins/monitoring/server/telemetry_collection/get_licenses.ts
+++ b/x-pack/plugins/monitoring/server/telemetry_collection/get_licenses.ts
@@ -6,7 +6,8 @@
*/
import { SearchResponse } from 'elasticsearch';
-import { LegacyAPICaller } from 'kibana/server';
+import { ElasticsearchClient } from 'kibana/server';
+import { estypes } from '@elastic/elasticsearch';
import { ESLicense } from '../../../telemetry_collection_xpack/server';
import { INDEX_PATTERN_ELASTICSEARCH } from '../../common/constants';
@@ -15,7 +16,7 @@ import { INDEX_PATTERN_ELASTICSEARCH } from '../../common/constants';
*/
export async function getLicenses(
clusterUuids: string[],
- callCluster: LegacyAPICaller, // TODO: To be changed to the new ES client when the plugin migrates
+ callCluster: ElasticsearchClient, // TODO: To be changed to the new ES client when the plugin migrates
maxBucketSize: number
): Promise<{ [clusterUuid: string]: ESLicense | undefined }> {
const response = await fetchLicenses(callCluster, clusterUuids, maxBucketSize);
@@ -31,16 +32,16 @@ export async function getLicenses(
*
* Returns the response for the aggregations to fetch details for the product.
*/
-export function fetchLicenses(
- callCluster: LegacyAPICaller,
+export async function fetchLicenses(
+ callCluster: ElasticsearchClient,
clusterUuids: string[],
maxBucketSize: number
) {
- const params = {
+ const params: estypes.SearchRequest = {
index: INDEX_PATTERN_ELASTICSEARCH,
size: maxBucketSize,
- ignoreUnavailable: true,
- filterPath: ['hits.hits._source.cluster_uuid', 'hits.hits._source.license'],
+ ignore_unavailable: true,
+ filter_path: ['hits.hits._source.cluster_uuid', 'hits.hits._source.license'],
body: {
query: {
bool: {
@@ -59,7 +60,8 @@ export function fetchLicenses(
},
};
- return callCluster('search', params);
+ const { body: response } = await callCluster.search(params);
+ return response as SearchResponse;
}
export interface ESClusterStatsWithLicense {
diff --git a/x-pack/plugins/monitoring/server/telemetry_collection/get_logstash_stats.test.ts b/x-pack/plugins/monitoring/server/telemetry_collection/get_logstash_stats.test.ts
index cf1574f8d3f0ee..cd77d28f6c430d 100644
--- a/x-pack/plugins/monitoring/server/telemetry_collection/get_logstash_stats.test.ts
+++ b/x-pack/plugins/monitoring/server/telemetry_collection/get_logstash_stats.test.ts
@@ -12,6 +12,7 @@ import {
processLogstashStateResults,
} from './get_logstash_stats';
import sinon from 'sinon';
+import { ElasticsearchClient } from 'kibana/server';
// eslint-disable-next-line @typescript-eslint/no-var-requires
const logstashStatsResultSet = require('./__mocks__/fixtures/logstash_stats_results');
@@ -33,10 +34,15 @@ const getBaseOptions = () => ({
describe('Get Logstash Stats', () => {
const clusterUuids = ['aCluster', 'bCluster', 'cCluster'];
- let callCluster = sinon.stub();
+ const searchMock = sinon.stub();
+ const callCluster = ({ search: searchMock } as unknown) as ElasticsearchClient;
beforeEach(() => {
- callCluster = sinon.stub();
+ searchMock.returns(Promise.resolve({ body: {} }));
+ });
+
+ afterEach(() => {
+ searchMock.reset();
});
describe('fetchLogstashState', () => {
@@ -63,17 +69,15 @@ describe('Get Logstash Stats', () => {
};
await fetchLogstashState(callCluster, clusterUuid, ephemeralIds, {} as any);
- const { args } = callCluster.firstCall;
- const [api, { body }] = args;
- expect(api).toEqual('search');
+ const { args } = searchMock.firstCall;
+ const [{ body }] = args;
expect(body.query).toEqual(expected);
});
it('should set `from: 0, to: 10000` in the query', async () => {
await fetchLogstashState(callCluster, clusterUuid, ephemeralIds, {} as any);
- const { args } = callCluster.firstCall;
- const [api, { body }] = args;
- expect(api).toEqual('search');
+ const { args } = searchMock.firstCall;
+ const [{ body }] = args;
expect(body.from).toEqual(0);
expect(body.size).toEqual(10000);
});
@@ -82,10 +86,9 @@ describe('Get Logstash Stats', () => {
await fetchLogstashState(callCluster, clusterUuid, ephemeralIds, {
page: 1,
} as any);
- const { args } = callCluster.firstCall;
- const [api, { body }] = args;
+ const { args } = searchMock.firstCall;
+ const [{ body }] = args;
- expect(api).toEqual('search');
expect(body.from).toEqual(10000);
expect(body.size).toEqual(10000);
});
@@ -94,10 +97,9 @@ describe('Get Logstash Stats', () => {
await fetchLogstashState(callCluster, clusterUuid, ephemeralIds, {
page: 2,
} as any);
- const { args } = callCluster.firstCall;
- const [api, { body }] = args;
+ const { args } = searchMock.firstCall;
+ const [{ body }] = args;
- expect(api).toEqual('search');
expect(body.from).toEqual(20000);
expect(body.size).toEqual(10000);
});
@@ -106,30 +108,27 @@ describe('Get Logstash Stats', () => {
describe('fetchLogstashStats', () => {
it('should set `from: 0, to: 10000` in the query', async () => {
await fetchLogstashStats(callCluster, clusterUuids, {} as any);
- const { args } = callCluster.firstCall;
- const [api, { body }] = args;
+ const { args } = searchMock.firstCall;
+ const [{ body }] = args;
- expect(api).toEqual('search');
expect(body.from).toEqual(0);
expect(body.size).toEqual(10000);
});
it('should set `from: 10000, to: 10000` in the query', async () => {
await fetchLogstashStats(callCluster, clusterUuids, { page: 1 } as any);
- const { args } = callCluster.firstCall;
- const [api, { body }] = args;
+ const { args } = searchMock.firstCall;
+ const [{ body }] = args;
- expect(api).toEqual('search');
expect(body.from).toEqual(10000);
expect(body.size).toEqual(10000);
});
it('should set `from: 20000, to: 10000` in the query', async () => {
await fetchLogstashStats(callCluster, clusterUuids, { page: 2 } as any);
- const { args } = callCluster.firstCall;
- const [api, { body }] = args;
+ const { args } = searchMock.firstCall;
+ const [{ body }] = args;
- expect(api).toEqual('search');
expect(body.from).toEqual(20000);
expect(body.size).toEqual(10000);
});
diff --git a/x-pack/plugins/monitoring/server/telemetry_collection/get_logstash_stats.ts b/x-pack/plugins/monitoring/server/telemetry_collection/get_logstash_stats.ts
index fafb52d6862b55..ac3f5fceb47ade 100644
--- a/x-pack/plugins/monitoring/server/telemetry_collection/get_logstash_stats.ts
+++ b/x-pack/plugins/monitoring/server/telemetry_collection/get_logstash_stats.ts
@@ -6,7 +6,8 @@
*/
import { SearchResponse } from 'elasticsearch';
-import { LegacyAPICaller } from 'kibana/server';
+import { ElasticsearchClient } from 'kibana/server';
+import { estypes } from '@elastic/elasticsearch';
import { createQuery } from './create_query';
import { mapToList } from './get_high_level_stats';
import { incrementByKey } from './get_high_level_stats';
@@ -261,17 +262,14 @@ export function processLogstashStateResults(
}
export async function fetchLogstashStats(
- callCluster: LegacyAPICaller,
+ callCluster: ElasticsearchClient,
clusterUuids: string[],
{ page = 0, ...options }: { page?: number } & LogstashProcessOptions
): Promise {
- const params = {
- headers: {
- 'X-QUERY-SOURCE': TELEMETRY_QUERY_SOURCE,
- },
+ const params: estypes.SearchRequest = {
index: INDEX_PATTERN_LOGSTASH,
- ignoreUnavailable: true,
- filterPath: [
+ ignore_unavailable: true,
+ filter_path: [
'hits.hits._source.cluster_uuid',
'hits.hits._source.type',
'hits.hits._source.source_node',
@@ -295,7 +293,7 @@ export async function fetchLogstashStats(
},
},
],
- }),
+ }) as estypes.QueryDslQueryContainer,
from: page * HITS_SIZE,
collapse: { field: 'logstash_stats.logstash.uuid' },
sort: [{ ['logstash_stats.timestamp']: { order: 'desc', unmapped_type: 'long' } }],
@@ -303,12 +301,16 @@ export async function fetchLogstashStats(
},
};
- const results = await callCluster>('search', params);
+ const { body: results } = await callCluster.search(params, {
+ headers: {
+ 'X-QUERY-SOURCE': TELEMETRY_QUERY_SOURCE,
+ },
+ });
const hitsLength = results?.hits?.hits.length || 0;
if (hitsLength > 0) {
// further augment the clusters object with more stats
- processStatsResults(results, options);
+ processStatsResults(results as SearchResponse, options);
if (hitsLength === HITS_SIZE) {
// call recursively
@@ -325,18 +327,15 @@ export async function fetchLogstashStats(
}
export async function fetchLogstashState(
- callCluster: LegacyAPICaller,
+ callCluster: ElasticsearchClient,
clusterUuid: string,
ephemeralIds: string[],
{ page = 0, ...options }: { page?: number } & LogstashProcessOptions
): Promise {
- const params = {
- headers: {
- 'X-QUERY-SOURCE': TELEMETRY_QUERY_SOURCE,
- },
+ const params: estypes.SearchRequest = {
index: INDEX_PATTERN_LOGSTASH,
- ignoreUnavailable: true,
- filterPath: [
+ ignore_unavailable: true,
+ filter_path: [
'hits.hits._source.logstash_state.pipeline.batch_size',
'hits.hits._source.logstash_state.pipeline.workers',
'hits.hits._source.logstash_state.pipeline.representation.graph.vertices.config_name',
@@ -355,7 +354,7 @@ export async function fetchLogstashState(
},
},
],
- }),
+ }) as estypes.QueryDslQueryContainer,
from: page * HITS_SIZE,
collapse: { field: 'logstash_state.pipeline.ephemeral_id' },
sort: [{ ['timestamp']: { order: 'desc', unmapped_type: 'long' } }],
@@ -363,11 +362,16 @@ export async function fetchLogstashState(
},
};
- const results = await callCluster>('search', params);
+ const { body: results } = await callCluster.search>(params, {
+ headers: {
+ 'X-QUERY-SOURCE': TELEMETRY_QUERY_SOURCE,
+ },
+ });
+
const hitsLength = results?.hits?.hits.length || 0;
if (hitsLength > 0) {
// further augment the clusters object with more stats
- processLogstashStateResults(results, clusterUuid, options);
+ processLogstashStateResults(results as SearchResponse, clusterUuid, options);
if (hitsLength === HITS_SIZE) {
// call recursively
@@ -392,7 +396,7 @@ export interface LogstashStatsByClusterUuid {
* @return {Object} - Logstash stats in an object keyed by the cluster UUIDs
*/
export async function getLogstashStats(
- callCluster: LegacyAPICaller,
+ callCluster: ElasticsearchClient,
clusterUuids: string[]
): Promise {
const options: LogstashProcessOptions = {
diff --git a/x-pack/plugins/monitoring/server/telemetry_collection/register_monitoring_telemetry_collection.ts b/x-pack/plugins/monitoring/server/telemetry_collection/register_monitoring_telemetry_collection.ts
index 23c61393899170..bb5d379f3c80c4 100644
--- a/x-pack/plugins/monitoring/server/telemetry_collection/register_monitoring_telemetry_collection.ts
+++ b/x-pack/plugins/monitoring/server/telemetry_collection/register_monitoring_telemetry_collection.ts
@@ -5,7 +5,7 @@
* 2.0.
*/
-import type { ILegacyClusterClient } from 'kibana/server';
+import type { IClusterClient } from 'kibana/server';
import type { UsageCollectionSetup } from 'src/plugins/usage_collection/server';
import type { UsageStatsPayload } from '../../../../../src/plugins/telemetry_collection_manager/server';
import type { LogstashBaseStats } from './get_logstash_stats';
@@ -31,7 +31,7 @@ interface MonitoringTelemetryUsage {
export function registerMonitoringTelemetryCollection(
usageCollection: UsageCollectionSetup,
- legacyEsClient: ILegacyClusterClient,
+ getClient: () => IClusterClient,
maxBucketSize: number
) {
const monitoringStatsCollector = usageCollection.makeStatsCollector<
@@ -137,15 +137,13 @@ export function registerMonitoringTelemetryCollection(
},
},
},
- fetch: async ({ kibanaRequest }) => {
+ fetch: async ({ kibanaRequest, esClient }) => {
const timestamp = Date.now(); // Collect the telemetry from the monitoring indices for this moment.
// NOTE: Usually, the monitoring indices index stats for each product every 10s (by default).
// However, some data may be delayed up-to 24h because monitoring only collects extended Kibana stats in that interval
// to avoid overloading of the system when retrieving data from the collectors (that delay is dealt with in the Kibana Stats getter inside the `getAllStats` method).
// By 8.x, we expect to stop collecting the Kibana extended stats and keep only the monitoring-related metrics.
- const callCluster = kibanaRequest
- ? legacyEsClient.asScoped(kibanaRequest).callAsCurrentUser
- : legacyEsClient.callAsInternalUser;
+ const callCluster = kibanaRequest ? esClient : getClient().asInternalUser;
const clusterDetails = await getClusterUuids(callCluster, timestamp, maxBucketSize);
const [licenses, stats] = await Promise.all([
getLicenses(clusterDetails, callCluster, maxBucketSize),
diff --git a/x-pack/plugins/monitoring/server/types.ts b/x-pack/plugins/monitoring/server/types.ts
index 3dcf6862b72325..c4a0687bef497b 100644
--- a/x-pack/plugins/monitoring/server/types.ts
+++ b/x-pack/plugins/monitoring/server/types.ts
@@ -8,9 +8,8 @@
import { Observable } from 'rxjs';
import type {
IRouter,
- ILegacyClusterClient,
Logger,
- ILegacyCustomClusterClient,
+ ICustomClusterClient,
RequestHandlerContext,
ElasticsearchClient,
} from 'kibana/server';
@@ -71,7 +70,7 @@ export interface MonitoringCoreConfig {
}
export interface RouteDependencies {
- cluster: ILegacyCustomClusterClient;
+ cluster: ICustomClusterClient;
router: IRouter;
licenseService: MonitoringLicenseService;
encryptedSavedObjects?: EncryptedSavedObjectsPluginSetup;
@@ -87,7 +86,7 @@ export interface MonitoringCore {
export interface LegacyShimDependencies {
router: IRouter;
instanceUuid: string;
- esDataClient: ILegacyClusterClient;
+ esDataClient: ElasticsearchClient;
kibanaStatsCollector: any;
}
From 57a91215f3180d8a9d2ce4ae8638461a667c4c01 Mon Sep 17 00:00:00 2001
From: Alison Goryachev
Date: Tue, 29 Jun 2021 20:13:31 -0400
Subject: [PATCH 015/121] fix error handling for repositories API (#103723)
---
.../server/routes/api/repositories.test.ts | 3 +--
.../server/routes/api/repositories.ts | 25 +++++++++++++------
2 files changed, 18 insertions(+), 10 deletions(-)
diff --git a/x-pack/plugins/snapshot_restore/server/routes/api/repositories.test.ts b/x-pack/plugins/snapshot_restore/server/routes/api/repositories.test.ts
index c3389e893407d3..ad180b8db45e04 100644
--- a/x-pack/plugins/snapshot_restore/server/routes/api/repositories.test.ts
+++ b/x-pack/plugins/snapshot_restore/server/routes/api/repositories.test.ts
@@ -188,11 +188,10 @@ describe('[Snapshot and Restore API Routes] Repositories', () => {
const mockEsResponse = {
[name]: { type: '', settings: {} },
};
- const mockEsSnapshotError = jest.fn().mockRejectedValueOnce(new Error('snapshot error'));
getClusterSettingsFn.mockResolvedValue({ body: mockSnapshotGetManagedRepositoryEsResponse });
getRepoFn.mockResolvedValue({ body: mockEsResponse });
- getSnapshotFn.mockResolvedValue({ body: mockEsSnapshotError });
+ getSnapshotFn.mockRejectedValueOnce(new Error('snapshot error'));
const expectedResponse = {
repository: { name, ...mockEsResponse[name] },
diff --git a/x-pack/plugins/snapshot_restore/server/routes/api/repositories.ts b/x-pack/plugins/snapshot_restore/server/routes/api/repositories.ts
index 4898c6e299ad30..a0799f4de8c92f 100644
--- a/x-pack/plugins/snapshot_restore/server/routes/api/repositories.ts
+++ b/x-pack/plugins/snapshot_restore/server/routes/api/repositories.ts
@@ -6,7 +6,10 @@
*/
import { TypeOf } from '@kbn/config-schema';
-import type { SnapshotRepositorySettings } from '@elastic/elasticsearch/api/types';
+import type {
+ SnapshotGetRepositoryResponse,
+ SnapshotRepositorySettings,
+} from '@elastic/elasticsearch/api/types';
import { DEFAULT_REPOSITORY_TYPES, REPOSITORY_PLUGINS_MAP } from '../../../common/constants';
import { Repository, RepositoryType } from '../../../common/types';
@@ -101,7 +104,7 @@ export function registerRepositoriesRoutes({
const managedRepository = await getManagedRepositoryName(clusterClient.asCurrentUser);
- let repositoryByName: any;
+ let repositoryByName: SnapshotGetRepositoryResponse;
try {
({ body: repositoryByName } = await clusterClient.asCurrentUser.snapshot.getRepository({
@@ -111,12 +114,18 @@ export function registerRepositoriesRoutes({
return handleEsError({ error: e, response: res });
}
- const response = await clusterClient.asCurrentUser.snapshot.get({
- repository: name,
- snapshot: '_all',
- });
-
- const { snapshots: snapshotList } = response.body;
+ const {
+ body: { snapshots: snapshotList },
+ } = await clusterClient.asCurrentUser.snapshot
+ .get({
+ repository: name,
+ snapshot: '_all',
+ })
+ .catch((e) => ({
+ body: {
+ snapshots: null,
+ },
+ }));
if (repositoryByName[name]) {
const { type = '', settings = {} } = repositoryByName[name];
From e7e1e3c1f9163f940950b9c41e2e5a0082e66df3 Mon Sep 17 00:00:00 2001
From: Frank Hassanabad
Date: Tue, 29 Jun 2021 18:14:36 -0600
Subject: [PATCH 016/121] Small follow up to PR comments (#103771)
## Summary
Small follow up to:
https://github.com/elastic/kibana/pull/102280
Where I address PR concerns around docs.
---
.../signals/source_fields_merging/README.md | 81 ++++++++++---------
.../utils/recursive_unboxing_fields.ts | 2 +-
2 files changed, 42 insertions(+), 41 deletions(-)
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/source_fields_merging/README.md b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/source_fields_merging/README.md
index eb72fc2b32687c..16672a324f8085 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/source_fields_merging/README.md
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/source_fields_merging/README.md
@@ -1,33 +1,34 @@
Set of utilities for merging between `_source` and `fields` are within this folder as well as strategies for merging between these two.
-See `strategies` for the strategies for merging between `_source` and `fields`. See the `utils` folder for the different utilities
+See the `strategies` folder for the strategies for merging between `_source` and `fields`. See the `utils` folder for the different utilities
which the strategies utilize for help in building their merged documents.
-If we run into problems such as ambiguities, uncertainties, or data type contradictions then we will prefer the value within
-"doc.fields" when we can. If "doc.fields" contradicts its self or is too ambiguous, then we assume that
-there a problem within "doc.fields" due to a malformed runtime field definition and omit the last seen
-contradiction. In some cases we might have to omit the merging of the field altogether and instead utilize
-the value from "doc._source"
-
-Hence, these are labeled as "best effort" since we could run into conditions where we should have taken the value
-from "doc.fields" but instead did not and took the value from "doc._source".
-
-If "doc.fields" does not exist we return "doc._source" untouched as-is. If "doc._source" does not exist but
-"doc.fields" does exist then we will do a "best effort" to merge "doc.fields" into a fully functional object as
-if it was a "doc._source". But again, if we run into contradictions or ambiguities from the
-"doc.fields" we will remove that field or omit one of the contradictions.
-
-If a "doc.field" is found that does not exist in "doc._source" then we merge that "doc.field" into our
-return object.
-
-If we find that a "field" contradicts the "doc._source" object in which we cannot create a regular
+The `strategies/merge_all_fields_with_source` strategy will try to merge as much from `fields` as it can into `_source`. This includes
+ * Data in only `fields` such as `constant_keyword`, `copy_to`, `field alias`, etc...
+ * It will try to merge data from `fields` into `_source` and overwrite `_source` when it can, which can include data coercion, or different overridden values from runtime fields, etc...
+ * It will take non-multifield values such as `host.name` from `fields` instead of `host.name.keyword` and merge that as the truth data even though `_source` might have a different original value that is not preserved correctly in `fields` as its choice of which one to take.
+ * If we run into problems such as ambiguities, uncertainties, or data type contradictions then we will prefer the value within `fields` if we can, but not in all cases.
+ * It compares and unboxes arrays where it can.
+
+The `strategies/merge_missing_fields_with_source` strategy is lighter weight and will only merge from `fields` into `_source` when it these conditions
+ * The value in `_source` is missing but exists in `fields` such as a `constant_keyword`, `copy_to`, `field alias`, etc...
+ * The value in `fields` is a primitive value and not a nested field or an object type such as a geo-point.
+ * If we run into problems such as ambiguities, uncertainties, or data type contradictions, then the `fields` value is skipped altogether.
+
+Hence, these are labeled as "best effort" since we could run into conditions where we should have taken the value from `fields` but instead did not and took the value from
+`_source`. If `fields` does not exist we return `_source` untouched as-is for all strategies. If `_source` does not exist but
+`fields` does exist then we will do a "best effort" to merge `fields` into a fully functional object as
+if it was a `_source` for `strategies/merge_all_fields_with_source` For `strategies/merge_missing_fields_with_source` we will only merge primitive values. In both
+strategies if we run into contradictions or ambiguities from `fields` we will remove that field or omit one of the contradictions.
+
+In all strategies If we find that a `field` contradicts the `_source` object in which we cannot create a regular
JSON such as a keyword trying to override an object or an object trying to override a keyword:
```
"fields": { 'foo': 'value_1', foo.bar': 'value_2' } <-- Foo cannot be both an object and a value
```
-Then you will get an object such as
+Then you will get an object such as
```
{ "foo": "value_1" }
```
@@ -37,33 +38,33 @@ This happens when we have multiFields since multiFields are represented in field
fields tries to add multiple overrides or invalid multiFields.
Invalid field names such as ".", "..", ".foo", "foo.", ".foo." will be skipped as those cause errors if
-we tried to insert them into Elasticsearch as a new field.
+we tried to insert them into Elasticsearch as a new field in all strategies
-If we encounter an array within "doc._source" that contains an object with more than 1 value and a "field"
-tries to add a new element we will not merge that in as we do not know which array to merge that value into.
+For `strategies/merge_all_fields_with_source` if we encounter an array within `_source` that contains
+an object with more than 1 value and a "field" tries to add a new element we will not merge that in
+as we do not know which array to merge that value into.
-If we encounter a flattened array in the fields object which is not a nested fields such as:
+For `strategies/merge_all_fields_with_source` if we encounter a flattened array in the fields object which is not a nested fields such as:
```
"fields": { "object_name.first" : [ "foo", "bar" ], "object_name.second" : [ "mars", "bar" ] }
```
-and no "doc._source" with the name "object_name", the assumption is that we these are not related and we construct the object as this:
-
+and no `_source` with the name `object_name`, the assumption is that we these are not related and we construct the object as this:
```
{ "object.name": { "first": ["foo", "bar" }, "second": ["mars", "bar"] }
```
-If we detect a "doc._source with a single flattened array sub objects we will prefer the "fields" flattened
+For `strategies/merge_all_fields_with_source` if we detect a `_source` with a single flattened array sub objects we will prefer the `fields` flattened
array elements and copy them over as-is, which means we could be subtracting elements, adding elements, or
completely changing the items from the array.
-If we detect an object within the "doc._source" inside of the array, we will not take anything from the
-"fields" flattened array elements even if they exist as it is ambiguous where we would put those elements
+For `strategies/merge_all_fields_with_source` if we detect an object within the `_source` inside of the array, we will not take anything from the
+`fields` flattened array elements even if they exist as it is ambiguous where we would put those elements
within the ""doc._source" as an override.
-It is best to feed this both the "doc._source" and "doc.fields" values to get the best chances of merging the document correctly.
+It is best to feed these strategies both the `_source` and `fields` values to get the best chances of merging the document correctly.
-Using these strategies will get you these value types merged that you would otherwise not get directly on your
+Using both of these strategies will get you these value types merged that you would otherwise not get directly on your
```
"doc._source":
- constant_keyword field
@@ -86,7 +87,7 @@ Ambiguities and issues
Existing bugs and ambiguities
---
-* We currently filter out the geo data points by looking at "type" on the object and filter it out. We could transform it to be valid input at some point.
+* For `strategies/merge_all_fields_with_source` we currently filter out the geo data points by looking at "type" on the object and filter it out. We could transform it to be valid input at some point.
Tests
---
@@ -130,7 +131,7 @@ f_[{}1]
f_[{}1, ...1]
```
-fields arrays can contain the following values:
+`fields` arrays can contain the following values:
```
undefined
f_[]
@@ -203,7 +204,7 @@ undefined | f_[{}2] | {} <-- We have an empty object since we o
undefined | f_[{}2, ...2] | {} <-- We have an empty object since we only merge primitives
```
-When source key is either a primitive key or a flattened object key with a primitive value (p_p1 or f_p1),
+For the `merge_all_fields_with_source` when source key is either a primitive key or a flattened object key with a primitive value (p_p1 or f_p1),
then we overwrite source value with fields value as an unboxed value array if fields value is a
single array element (f_[p2] or f[{}2]), otherwise we overwrite source as an array.
@@ -221,7 +222,7 @@ f_p1 | f_[{}2] | f_{}2 <-- Unboxed from array
f_p1 | f_[{}2, ...2] | f_[{}2, ...2]
```
-For the `merge_missing_fields_with_source` none of these will be merged since the source has values such as
+For both strategies none of these will be merged since the source has values such as
```
source | fields | value after merge
@@ -237,7 +238,7 @@ f_p1 | f_[{}2] | f_p1
f_p1 | f_[{}2, ...2] | f_p1
```
-When source key is a primitive key or a flattened object key and the source value is any
+For the `merge_all_fields_with_source` when source key is a primitive key or a flattened object key and the source value is any
type of array (p_[], p_p[p1], or p_p[p1, ...1]) of primitives then we always copy the
fields value as is and keep the source key as it was originally (primitive or flattened)
@@ -311,10 +312,10 @@ f_[p1, ...1] | f_[{}2] | f_[p1, ...1]
f_[p1, ...1] | f_[{}2, ...2] | f_[p1, ...1]
```
-When source key is a primitive key or flattened key and the source value is an object (p_{}1, f_{}1) or
-an array containing objects ([p_{1}], f_{}1, p_[{}1, ...1], f_[{}1, ...1]), we only copy the
-field value if we detect that field value is also an object meaning that it is a nested field,
-(f_[{}]2 or f[{}2, ...2]). We never allow a field to convert an object back into a value.
+For the `merge_all_fields_with_source` when source key is a primitive key or flattened key and
+the source value is an object (p_{}1, f_{}1) or an array containing objects ([p_{1}], f_{}1, p_[{}1, ...1],
+f_[{}1, ...1]), we only copy the field value if we detect that field value is also an object meaning
+that it is a nested field, (f_[{}]2 or f[{}2, ...2]). We never allow a field to convert an object back into a value.
We never try to merge field values into the array either since they're flattened in the fields and we
will have too many ambiguities and issues between the flattened array values and the source objects.
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/source_fields_merging/utils/recursive_unboxing_fields.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/source_fields_merging/utils/recursive_unboxing_fields.ts
index 9cd0ebcb5a4277..e9967cda363eac 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/source_fields_merging/utils/recursive_unboxing_fields.ts
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/source_fields_merging/utils/recursive_unboxing_fields.ts
@@ -19,7 +19,7 @@ import { FieldsType } from '../types';
*
* @param fieldsValue The fields value that contains the nested field or not.
* @param valueInMergedDocument The document to compare against fields value to see if it is also an array or not
- * @returns
+ * @returns The unboxed fields if any
*/
export const recursiveUnboxingFields = (
fieldsValue: FieldsType | FieldsType[0],
From 26407550dae787558a404b6f507c50b9319ac0f9 Mon Sep 17 00:00:00 2001
From: Justin Ibarra
Date: Tue, 29 Jun 2021 16:24:35 -0800
Subject: [PATCH 017/121] [Detection Rules] Add 7.14 rules (#103730)
---
..._365_brute_force_user_account_attempt.json | 11 ++-
...65_potential_password_spraying_attack.json | 4 +-
...n_shell_execution_via_apple_scripting.json | 4 +-
..._full_network_packet_capture_detected.json | 71 +++++++++++++++++++
.../exfiltration_rds_snapshot_export.json | 48 +++++++++++++
...e_service_principal_credentials_added.json | 55 ++++++++++++++
.../impact_rds_group_deletion.json | 55 ++++++++++++++
.../rules/prepackaged_rules/index.ts | 36 ++++++++--
...file_sharing_activity_to_the_internet.json | 7 +-
...ml_auth_rare_hour_for_a_user_to_logon.json | 29 ++++++++
.../ml_auth_rare_source_ip_for_a_user.json | 29 ++++++++
.../ml_auth_rare_user_logon.json | 29 ++++++++
.../ml_auth_spike_in_failed_logon_events.json | 29 ++++++++
.../ml_auth_spike_in_logon_events.json | 29 ++++++++
...pike_in_logon_events_from_a_source_ip.json | 29 ++++++++
...istence_folder_action_scripts_runtime.json | 4 +-
.../persistence_rds_group_creation.json | 62 ++++++++++++++++
.../persistence_rds_instance_creation.json | 48 +++++++++++++
...oute_53_domain_transfer_lock_disabled.json | 65 +++++++++++++++++
...domain_transferred_to_another_account.json | 64 +++++++++++++++++
20 files changed, 693 insertions(+), 15 deletions(-)
create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/exfiltration_ec2_full_network_packet_capture_detected.json
create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/exfiltration_rds_snapshot_export.json
create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_azure_service_principal_credentials_added.json
create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_rds_group_deletion.json
create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_auth_rare_hour_for_a_user_to_logon.json
create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_auth_rare_source_ip_for_a_user.json
create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_auth_rare_user_logon.json
create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_auth_spike_in_failed_logon_events.json
create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_auth_spike_in_logon_events.json
create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_auth_spike_in_logon_events_from_a_source_ip.json
create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_rds_group_creation.json
create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_rds_instance_creation.json
create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_route_53_domain_transfer_lock_disabled.json
create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_route_53_domain_transferred_to_another_account.json
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_microsoft_365_brute_force_user_account_attempt.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_microsoft_365_brute_force_user_account_attempt.json
index 532b9bf3b17b3a..6bd3606d3b1f99 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_microsoft_365_brute_force_user_account_attempt.json
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_microsoft_365_brute_force_user_account_attempt.json
@@ -1,6 +1,8 @@
{
"author": [
- "Elastic"
+ "Elastic",
+ "Willem D'Haese",
+ "Austin Songer"
],
"description": "Identifies attempts to brute force a Microsoft 365 user account. An adversary may attempt a brute force attack to obtain unauthorized access to user accounts.",
"false_positives": [
@@ -15,7 +17,10 @@
"license": "Elastic License v2",
"name": "Attempts to Brute Force a Microsoft 365 User Account",
"note": "## Config\n\nThe Microsoft 365 Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.",
- "query": "event.dataset:o365.audit and event.provider:(Exchange or AzureActiveDirectory) and event.category:authentication and \nevent.action:(\"UserLoginFailed\" or \"PasswordLogonInitialAuthUsingPassword\") and event.outcome:failure\n",
+ "query": "event.dataset:o365.audit and event.provider:(AzureActiveDirectory or Exchange) and\n event.category:authentication and event.action:(UserLoginFailed or PasswordLogonInitialAuthUsingPassword) and\n not o365.audit.LogonError:(UserAccountNotFound or EntitlementGrantsNotFound or UserStrongAuthEnrollmentRequired or\n UserStrongAuthClientAuthNRequired or InvalidReplyTo) and event.outcome:failure\n",
+ "references": [
+ "https://blueteamblog.com/7-ways-to-monitor-your-office-365-logs-using-siem"
+ ],
"risk_score": 73,
"rule_id": "26f68dba-ce29-497b-8e13-b4fde1db5a2d",
"severity": "high",
@@ -51,5 +56,5 @@
"value": 10
},
"type": "threshold",
- "version": 4
+ "version": 5
}
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_microsoft_365_potential_password_spraying_attack.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_microsoft_365_potential_password_spraying_attack.json
index 536f893236deeb..c5a20b643b6d78 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_microsoft_365_potential_password_spraying_attack.json
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_microsoft_365_potential_password_spraying_attack.json
@@ -15,7 +15,7 @@
"license": "Elastic License v2",
"name": "Potential Password Spraying of Microsoft 365 User Accounts",
"note": "## Config\n\nThe Microsoft 365 Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.",
- "query": "event.dataset:o365.audit and event.provider:AzureActiveDirectory and event.category:authentication and event.action:UserLoginFailed and event.outcome:failure\n",
+ "query": "event.dataset:o365.audit and event.provider:(Exchange or AzureActiveDirectory) and event.category:authentication and \nevent.action:(\"UserLoginFailed\" or \"PasswordLogonInitialAuthUsingPassword\") and event.outcome:failure\n",
"risk_score": 73,
"rule_id": "3efee4f0-182a-40a8-a835-102c68a4175d",
"severity": "high",
@@ -51,5 +51,5 @@
"value": 25
},
"type": "threshold",
- "version": 3
+ "version": 4
}
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_shell_execution_via_apple_scripting.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_shell_execution_via_apple_scripting.json
index b6a285828141c6..6399e3f2031fdb 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_shell_execution_via_apple_scripting.json
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_shell_execution_via_apple_scripting.json
@@ -11,7 +11,7 @@
"language": "eql",
"license": "Elastic License v2",
"name": "Shell Execution via Apple Scripting",
- "query": "sequence by host.id with maxspan=5s\n [process where event.type in (\"start\", \"process_started\", \"info\") and process.name == \"osascript\"] by process.pid\n [process where event.type in (\"start\", \"process_started\") and process.name == \"sh\" and process.args == \"-c\"] by process.ppid\n",
+ "query": "sequence by host.id with maxspan=5s\n [process where event.type in (\"start\", \"process_started\", \"info\") and process.name == \"osascript\"] by process.pid\n [process where event.type in (\"start\", \"process_started\") and process.name == \"sh\" and process.args == \"-c\"] by process.parent.pid\n",
"references": [
"https://developer.apple.com/library/archive/technotes/tn2065/_index.html",
"https://objectivebythesea.com/v2/talks/OBTS_v2_Thomas.pdf"
@@ -44,5 +44,5 @@
}
],
"type": "eql",
- "version": 2
+ "version": 3
}
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/exfiltration_ec2_full_network_packet_capture_detected.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/exfiltration_ec2_full_network_packet_capture_detected.json
new file mode 100644
index 00000000000000..88d7e8f262cb54
--- /dev/null
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/exfiltration_ec2_full_network_packet_capture_detected.json
@@ -0,0 +1,71 @@
+{
+ "author": [
+ "Elastic",
+ "Austin Songer"
+ ],
+ "description": "Identifies potential Traffic Mirroring in an Amazon Elastic Compute Cloud (EC2) instance. Traffic Mirroring is an Amazon VPC feature that you can use to copy network traffic from an elastic network interface. This feature can potentially be abused to exfiltrate sensitive data from unencrypted internal traffic.",
+ "false_positives": [
+ "Traffic Mirroring may be done by a system or network administrator. Verify whether the user identity, user agent, and/or hostname should be making changes in your environment. Traffic Mirroring from unfamiliar users or hosts should be investigated. If known behavior is causing false positives, it can be exempted from the rule."
+ ],
+ "from": "now-60m",
+ "index": [
+ "filebeat-*",
+ "logs-aws*"
+ ],
+ "interval": "10m",
+ "language": "kuery",
+ "license": "Elastic License v2",
+ "name": "AWS EC2 Full Network Packet Capture Detected",
+ "note": "## Config\n\nThe AWS Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.",
+ "query": "event.dataset:aws.cloudtrail and event.provider:ec2.amazonaws.com and \nevent.action:(CreateTrafficMirrorFilter or CreateTrafficMirrorFilterRule or CreateTrafficMirrorSession or CreateTrafficMirrorTarget) and \nevent.outcome:success\n",
+ "references": [
+ "https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_TrafficMirrorFilter.html",
+ "https://github.com/easttimor/aws-incident-response"
+ ],
+ "risk_score": 47,
+ "rule_id": "c1812764-0788-470f-8e74-eb4a14d47573",
+ "severity": "medium",
+ "tags": [
+ "Elastic",
+ "Cloud",
+ "AWS",
+ "Continuous Monitoring",
+ "SecOps",
+ "Network Security"
+ ],
+ "threat": [
+ {
+ "framework": "MITRE ATT&CK",
+ "tactic": {
+ "id": "TA0010",
+ "name": "Exfiltration",
+ "reference": "https://attack.mitre.org/tactics/TA0010/"
+ },
+ "technique": [
+ {
+ "id": "T1020",
+ "name": "Automated Exfiltration",
+ "reference": "https://attack.mitre.org/techniques/T1020/"
+ }
+ ]
+ },
+ {
+ "framework": "MITRE ATT&CK",
+ "tactic": {
+ "id": "TA0009",
+ "name": "Collection",
+ "reference": "https://attack.mitre.org/tactics/TA0009/"
+ },
+ "technique": [
+ {
+ "id": "T1074",
+ "name": "Data Staged",
+ "reference": "https://attack.mitre.org/techniques/T1074/"
+ }
+ ]
+ }
+ ],
+ "timestamp_override": "event.ingested",
+ "type": "query",
+ "version": 1
+}
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/exfiltration_rds_snapshot_export.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/exfiltration_rds_snapshot_export.json
new file mode 100644
index 00000000000000..430d97690b6f4d
--- /dev/null
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/exfiltration_rds_snapshot_export.json
@@ -0,0 +1,48 @@
+{
+ "author": [
+ "Elastic"
+ ],
+ "description": "Identifies the export of an Amazon Relational Database Service (RDS) Aurora database snapshot.",
+ "false_positives": [
+ "Exporting snapshots may be done by a system or network administrator. Verify whether the user identity, user agent, and/or hostname should be making changes in your environment. Snapshot exports from unfamiliar users or hosts should be investigated. If known behavior is causing false positives, it can be exempted from the rule."
+ ],
+ "from": "now-60m",
+ "index": [
+ "filebeat-*",
+ "logs-aws*"
+ ],
+ "interval": "10m",
+ "language": "kuery",
+ "license": "Elastic License v2",
+ "name": "AWS RDS Snapshot Export",
+ "note": "## Config\n\nThe AWS Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.",
+ "query": "event.dataset:aws.cloudtrail and event.provider:rds.amazonaws.com and event.action:StartExportTask and event.outcome:success\n",
+ "references": [
+ "https://docs.aws.amazon.com/AmazonRDS/latest/APIReference/API_StartExportTask.html"
+ ],
+ "risk_score": 21,
+ "rule_id": "119c8877-8613-416d-a98a-96b6664ee73a5",
+ "severity": "low",
+ "tags": [
+ "Elastic",
+ "Cloud",
+ "AWS",
+ "Continuous Monitoring",
+ "SecOps",
+ "Asset Visibility"
+ ],
+ "threat": [
+ {
+ "framework": "MITRE ATT&CK",
+ "tactic": {
+ "id": "TA0010",
+ "name": "Exfiltration",
+ "reference": "https://attack.mitre.org/tactics/TA0010/"
+ },
+ "technique": []
+ }
+ ],
+ "timestamp_override": "event.ingested",
+ "type": "query",
+ "version": 1
+}
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_azure_service_principal_credentials_added.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_azure_service_principal_credentials_added.json
new file mode 100644
index 00000000000000..0036a719250c18
--- /dev/null
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_azure_service_principal_credentials_added.json
@@ -0,0 +1,55 @@
+{
+ "author": [
+ "Elastic",
+ "Austin Songer"
+ ],
+ "description": "Identifies when new Service Principal credentials have been added in Azure. In most organizations, credentials will be added to service principals infrequently. Hijacking an application (by adding a rogue secret or certificate) with granted permissions will allow the attacker to access data that is normally protected by MFA requirements.",
+ "false_positives": [
+ "Service principal credential additions may be done by a system or network administrator. Verify whether the username, hostname, and/or resource name should be making changes in your environment. Credential additions from unfamiliar users or hosts should be investigated. If known behavior is causing false positives, it can be exempted from the rule."
+ ],
+ "from": "now-25m",
+ "index": [
+ "filebeat-*",
+ "logs-azure*"
+ ],
+ "interval": "10m",
+ "language": "kuery",
+ "license": "Elastic License v2",
+ "name": "Azure Service Principal Credentials Added",
+ "note": "## Config\n\nThe Azure Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.",
+ "query": "event.dataset:azure.auditlogs and azure.auditlogs.operation_name:\"Add service principal credentials.\" and event.outcome:(success or Success)\n",
+ "references": [
+ "https://www.fireeye.com/content/dam/collateral/en/wp-m-unc2452.pdf"
+ ],
+ "risk_score": 47,
+ "rule_id": "f766ffaf-9568-4909-b734-75d19b35cbf4",
+ "severity": "medium",
+ "tags": [
+ "Elastic",
+ "Cloud",
+ "Azure",
+ "Continuous Monitoring",
+ "SecOps",
+ "Identity and Access"
+ ],
+ "threat": [
+ {
+ "framework": "MITRE ATT&CK",
+ "tactic": {
+ "id": "TA0040",
+ "name": "Impact",
+ "reference": "https://attack.mitre.org/tactics/TA0040/"
+ },
+ "technique": [
+ {
+ "id": "T1496",
+ "name": "Resource Hijacking",
+ "reference": "https://attack.mitre.org/techniques/T1496/"
+ }
+ ]
+ }
+ ],
+ "timestamp_override": "event.ingested",
+ "type": "query",
+ "version": 1
+}
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_rds_group_deletion.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_rds_group_deletion.json
new file mode 100644
index 00000000000000..bc5d808c10c301
--- /dev/null
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_rds_group_deletion.json
@@ -0,0 +1,55 @@
+{
+ "author": [
+ "Elastic",
+ "Austin Songer"
+ ],
+ "description": "Identifies the deletion of an Amazon Relational Database Service (RDS) Security Group.",
+ "false_positives": [
+ "A RDS security group deletion may be done by a system or network administrator. Verify whether the user identity, user agent, and/or hostname should be making changes in your environment. Security Group deletions from unfamiliar users or hosts should be investigated. If known behavior is causing false positives, it can be exempted from the rule."
+ ],
+ "from": "now-60m",
+ "index": [
+ "filebeat-*",
+ "logs-aws*"
+ ],
+ "interval": "10m",
+ "language": "kuery",
+ "license": "Elastic License v2",
+ "name": "AWS RDS Security Group Deletion",
+ "note": "## Config\n\nThe AWS Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.",
+ "query": "event.dataset:aws.cloudtrail and event.provider:rds.amazonaws.com and event.action:DeleteDBSecurityGroup and event.outcome:success\n",
+ "references": [
+ "https://docs.aws.amazon.com/AmazonRDS/latest/APIReference/API_DeleteDBSecurityGroup.html"
+ ],
+ "risk_score": 21,
+ "rule_id": "863cdf31-7fd3-41cf-a185-681237ea277b",
+ "severity": "low",
+ "tags": [
+ "Elastic",
+ "Cloud",
+ "AWS",
+ "Continuous Monitoring",
+ "SecOps",
+ "Monitoring"
+ ],
+ "threat": [
+ {
+ "framework": "MITRE ATT&CK",
+ "tactic": {
+ "id": "TA0040",
+ "name": "Impact",
+ "reference": "https://attack.mitre.org/tactics/TA0040/"
+ },
+ "technique": [
+ {
+ "id": "T1531",
+ "name": "Account Access Removal",
+ "reference": "https://attack.mitre.org/techniques/T1531/"
+ }
+ ]
+ }
+ ],
+ "timestamp_override": "event.ingested",
+ "type": "query",
+ "version": 1
+}
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/index.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/index.ts
index dc3ca4ceed4c8d..4a6bea85dc6b5d 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/index.ts
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/index.ts
@@ -545,10 +545,24 @@ import rule532 from './command_and_control_tunneling_via_earthworm.json';
import rule533 from './lateral_movement_evasion_rdp_shadowing.json';
import rule534 from './threat_intel_module_match.json';
import rule535 from './exfiltration_ec2_vm_export_failure.json';
-import rule536 from './defense_evasion_suspicious_execution_from_mounted_device.json';
-import rule537 from './defense_evasion_unusual_network_connection_via_dllhost.json';
-import rule538 from './defense_evasion_amsienable_key_mod.json';
-import rule539 from './persistence_via_bits_job_notify_command.json';
+import rule536 from './exfiltration_ec2_full_network_packet_capture_detected.json';
+import rule537 from './impact_azure_service_principal_credentials_added.json';
+import rule538 from './persistence_route_53_domain_transfer_lock_disabled.json';
+import rule539 from './persistence_route_53_domain_transferred_to_another_account.json';
+import rule540 from './defense_evasion_suspicious_execution_from_mounted_device.json';
+import rule541 from './defense_evasion_unusual_network_connection_via_dllhost.json';
+import rule542 from './defense_evasion_amsienable_key_mod.json';
+import rule543 from './impact_rds_group_deletion.json';
+import rule544 from './persistence_rds_group_creation.json';
+import rule545 from './exfiltration_rds_snapshot_export.json';
+import rule546 from './persistence_rds_instance_creation.json';
+import rule547 from './ml_auth_rare_hour_for_a_user_to_logon.json';
+import rule548 from './ml_auth_rare_source_ip_for_a_user.json';
+import rule549 from './ml_auth_rare_user_logon.json';
+import rule550 from './ml_auth_spike_in_failed_logon_events.json';
+import rule551 from './ml_auth_spike_in_logon_events.json';
+import rule552 from './ml_auth_spike_in_logon_events_from_a_source_ip.json';
+import rule553 from './persistence_via_bits_job_notify_command.json';
export const rawRules = [
rule1,
@@ -1090,4 +1104,18 @@ export const rawRules = [
rule537,
rule538,
rule539,
+ rule540,
+ rule541,
+ rule542,
+ rule543,
+ rule544,
+ rule545,
+ rule546,
+ rule547,
+ rule548,
+ rule549,
+ rule550,
+ rule551,
+ rule552,
+ rule553,
];
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_smb_windows_file_sharing_activity_to_the_internet.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_smb_windows_file_sharing_activity_to_the_internet.json
index b119dc0a4f211f..29503e050caa76 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_smb_windows_file_sharing_activity_to_the_internet.json
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_smb_windows_file_sharing_activity_to_the_internet.json
@@ -12,7 +12,10 @@
"language": "kuery",
"license": "Elastic License v2",
"name": "SMB (Windows File Sharing) Activity to the Internet",
- "query": "event.category:(network or network_traffic) and network.transport:tcp and (destination.port:(139 or 445) or event.dataset:zeek.smb) and\n source.ip:(\n 10.0.0.0/8 or\n 172.16.0.0/12 or\n 192.168.0.0/16\n ) and\n not destination.ip:(\n 10.0.0.0/8 or\n 127.0.0.0/8 or\n 169.254.0.0/16 or\n 172.16.0.0/12 or\n 192.168.0.0/16 or\n 224.0.0.0/4 or\n \"::1\" or\n \"FE80::/10\" or\n \"FF00::/8\"\n )\n",
+ "query": "event.category:(network or network_traffic) and network.transport:tcp and (destination.port:(139 or 445) or event.dataset:zeek.smb) and\n source.ip:(\n 10.0.0.0/8 or\n 172.16.0.0/12 or\n 192.168.0.0/16\n ) and\n not destination.ip:(\n 10.0.0.0/8 or\n 127.0.0.0/8 or\n 169.254.0.0/16 or\n 172.16.0.0/12 or\n 192.0.0.0/24 or\n 192.0.0.0/29 or\n 192.0.0.8/32 or\n 192.0.0.9/32 or\n 192.0.0.10/32 or\n 192.0.0.170/32 or\n 192.0.0.171/32 or\n 192.0.2.0/24 or\n 192.31.196.0/24 or\n 192.52.193.0/24 or\n 192.168.0.0/16 or\n 192.88.99.0/24 or\n 224.0.0.0/4 or\n 100.64.0.0/10 or\n 192.175.48.0/24 or\n 198.18.0.0/15 or\n 198.51.100.0/24 or\n 203.0.113.0/24 or\n 240.0.0.0/4 or\n \"::1\" or\n \"FE80::/10\" or\n \"FF00::/8\"\n )\n",
+ "references": [
+ "https://www.iana.org/assignments/iana-ipv4-special-registry/iana-ipv4-special-registry.xhtml"
+ ],
"risk_score": 73,
"rule_id": "c82b2bd8-d701-420c-ba43-f11a155b681a",
"severity": "high",
@@ -57,5 +60,5 @@
],
"timestamp_override": "event.ingested",
"type": "query",
- "version": 8
+ "version": 9
}
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_auth_rare_hour_for_a_user_to_logon.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_auth_rare_hour_for_a_user_to_logon.json
new file mode 100644
index 00000000000000..8848ec3b7b8c6d
--- /dev/null
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_auth_rare_hour_for_a_user_to_logon.json
@@ -0,0 +1,29 @@
+{
+ "anomaly_threshold": 75,
+ "author": [
+ "Elastic"
+ ],
+ "description": "A machine learning job detected a user logging in at a time of day that is unusual for the user. This can be due to credentialed access via a compromised account when the user and the threat actor are in different time zones. In addition, unauthorized user activity often takes place during non-business hours.",
+ "false_positives": [
+ "Users working late, or logging in from unusual time zones while traveling, may trigger this rule."
+ ],
+ "from": "now-30m",
+ "interval": "15m",
+ "license": "Elastic License v2",
+ "machine_learning_job_id": "auth_rare_hour_for_a_user",
+ "name": "Unusual Hour for a User to Logon",
+ "references": [
+ "https://www.elastic.co/guide/en/security/current/prebuilt-ml-jobs.html"
+ ],
+ "risk_score": 21,
+ "rule_id": "745b0119-0560-43ba-860a-7235dd8cee8d",
+ "severity": "low",
+ "tags": [
+ "Elastic",
+ "Authentication",
+ "Threat Detection",
+ "ML"
+ ],
+ "type": "machine_learning",
+ "version": 1
+}
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_auth_rare_source_ip_for_a_user.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_auth_rare_source_ip_for_a_user.json
new file mode 100644
index 00000000000000..638e18b85bc01d
--- /dev/null
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_auth_rare_source_ip_for_a_user.json
@@ -0,0 +1,29 @@
+{
+ "anomaly_threshold": 75,
+ "author": [
+ "Elastic"
+ ],
+ "description": "A machine learning job detected a user logging in from an IP address that is unusual for the user. This can be due to credentialed access via a compromised account when the user and the threat actor are in different locations. An unusual source IP address for a username could also be due to lateral movement when a compromised account is used to pivot between hosts.",
+ "false_positives": [
+ "Business travelers who roam to new locations may trigger this alert."
+ ],
+ "from": "now-30m",
+ "interval": "15m",
+ "license": "Elastic License v2",
+ "machine_learning_job_id": "auth_rare_source_ip_for_a_user",
+ "name": "Unusual Source IP for a User to Logon from",
+ "references": [
+ "https://www.elastic.co/guide/en/security/current/prebuilt-ml-jobs.html"
+ ],
+ "risk_score": 21,
+ "rule_id": "d4b73fa0-9d43-465e-b8bf-50230da6718b",
+ "severity": "low",
+ "tags": [
+ "Elastic",
+ "Authentication",
+ "Threat Detection",
+ "ML"
+ ],
+ "type": "machine_learning",
+ "version": 1
+}
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_auth_rare_user_logon.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_auth_rare_user_logon.json
new file mode 100644
index 00000000000000..f72893a0cf252d
--- /dev/null
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_auth_rare_user_logon.json
@@ -0,0 +1,29 @@
+{
+ "anomaly_threshold": 75,
+ "author": [
+ "Elastic"
+ ],
+ "description": "A machine learning job found an unusual user name in the authentication logs. An unusual user name is one way of detecting credentialed access by means of a new or dormant user account. A user account that is normally inactive, because the user has left the organization, which becomes active, may be due to credentialed access using a compromised account password. Threat actors will sometimes also create new users as a means of persisting in a compromised web application.",
+ "false_positives": [
+ "User accounts that are rarely active, such as an SRE or developer logging into a prod server for troubleshooting, may trigger this alert. Under some conditions, a newly created user account may briefly trigger this alert while the model is learning."
+ ],
+ "from": "now-30m",
+ "interval": "15m",
+ "license": "Elastic License v2",
+ "machine_learning_job_id": "auth_rare_user",
+ "name": "Rare User Logon",
+ "references": [
+ "https://www.elastic.co/guide/en/security/current/prebuilt-ml-jobs.html"
+ ],
+ "risk_score": 21,
+ "rule_id": "138c5dd5-838b-446e-b1ac-c995c7f8108a",
+ "severity": "low",
+ "tags": [
+ "Elastic",
+ "Authentication",
+ "Threat Detection",
+ "ML"
+ ],
+ "type": "machine_learning",
+ "version": 1
+}
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_auth_spike_in_failed_logon_events.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_auth_spike_in_failed_logon_events.json
new file mode 100644
index 00000000000000..39f104da673045
--- /dev/null
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_auth_spike_in_failed_logon_events.json
@@ -0,0 +1,29 @@
+{
+ "anomaly_threshold": 75,
+ "author": [
+ "Elastic"
+ ],
+ "description": "A machine learning job found an unusually large spike in authentication failure events. This can be due to password spraying, user enumeration or brute force activity and may be a precursor to account takeover or credentialed access.",
+ "false_positives": [
+ "A misconfigured service account can trigger this alert. A password change on ana account used by an email client can trigger this alert. Security test cycles that include brute force or password spraying activities may trigger this alert."
+ ],
+ "from": "now-30m",
+ "interval": "15m",
+ "license": "Elastic License v2",
+ "machine_learning_job_id": "auth_high_count_logon_fails",
+ "name": "Spike in Failed Logon Events",
+ "references": [
+ "https://www.elastic.co/guide/en/security/current/prebuilt-ml-jobs.html"
+ ],
+ "risk_score": 21,
+ "rule_id": "99dcf974-6587-4f65-9252-d866a3fdfd9c",
+ "severity": "low",
+ "tags": [
+ "Elastic",
+ "Authentication",
+ "Threat Detection",
+ "ML"
+ ],
+ "type": "machine_learning",
+ "version": 1
+}
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_auth_spike_in_logon_events.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_auth_spike_in_logon_events.json
new file mode 100644
index 00000000000000..d591cc6e0f56d8
--- /dev/null
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_auth_spike_in_logon_events.json
@@ -0,0 +1,29 @@
+{
+ "anomaly_threshold": 75,
+ "author": [
+ "Elastic"
+ ],
+ "description": "A machine learning job found an unusually large spike in successful authentication events. This can be due to password spraying, user enumeration or brute force activity.",
+ "false_positives": [
+ "Build servers and CI systems can sometimes trigger this alert. Security test cycles that include brute force or password spraying activities may trigger this alert."
+ ],
+ "from": "now-30m",
+ "interval": "15m",
+ "license": "Elastic License v2",
+ "machine_learning_job_id": "auth_high_count_logon_events",
+ "name": "Spike in Logon Events",
+ "references": [
+ "https://www.elastic.co/guide/en/security/current/prebuilt-ml-jobs.html"
+ ],
+ "risk_score": 21,
+ "rule_id": "d7d5c059-c19a-4a96-8ae3-41496ef3bcf9",
+ "severity": "low",
+ "tags": [
+ "Elastic",
+ "Authentication",
+ "Threat Detection",
+ "ML"
+ ],
+ "type": "machine_learning",
+ "version": 1
+}
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_auth_spike_in_logon_events_from_a_source_ip.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_auth_spike_in_logon_events_from_a_source_ip.json
new file mode 100644
index 00000000000000..8e007c96c37fbb
--- /dev/null
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_auth_spike_in_logon_events_from_a_source_ip.json
@@ -0,0 +1,29 @@
+{
+ "anomaly_threshold": 75,
+ "author": [
+ "Elastic"
+ ],
+ "description": "A machine learning job found an unusually large spike in successful authentication events events from a particular source IP address. This can be due to password spraying, user enumeration or brute force activity.",
+ "false_positives": [
+ "Build servers and CI systems can sometimes trigger this alert. Security test cycles that include brute force or password spraying activities may trigger this alert."
+ ],
+ "from": "now-30m",
+ "interval": "15m",
+ "license": "Elastic License v2",
+ "machine_learning_job_id": "auth_high_count_logon_events_for_a_source_ip",
+ "name": "Spike in Logon Events from a Source IP",
+ "references": [
+ "https://www.elastic.co/guide/en/security/current/prebuilt-ml-jobs.html"
+ ],
+ "risk_score": 21,
+ "rule_id": "e26aed74-c816-40d3-a810-48d6fbd8b2fd",
+ "severity": "low",
+ "tags": [
+ "Elastic",
+ "Authentication",
+ "Threat Detection",
+ "ML"
+ ],
+ "type": "machine_learning",
+ "version": 1
+}
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_folder_action_scripts_runtime.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_folder_action_scripts_runtime.json
index 369aec91e8f453..fbf9bcc44ed44b 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_folder_action_scripts_runtime.json
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_folder_action_scripts_runtime.json
@@ -11,7 +11,7 @@
"language": "eql",
"license": "Elastic License v2",
"name": "Persistence via Folder Action Script",
- "query": "sequence by host.id with maxspan=5s\n [process where event.type in (\"start\", \"process_started\", \"info\") and process.name == \"com.apple.foundation.UserScriptService\"] by process.pid\n [process where event.type in (\"start\", \"process_started\") and process.name in (\"osascript\", \"sh\")] by process.ppid\n",
+ "query": "sequence by host.id with maxspan=5s\n [process where event.type in (\"start\", \"process_started\", \"info\") and process.name == \"com.apple.foundation.UserScriptService\"] by process.pid\n [process where event.type in (\"start\", \"process_started\") and process.name in (\"osascript\", \"sh\")] by process.parent.pid\n",
"references": [
"https://posts.specterops.io/folder-actions-for-persistence-on-macos-8923f222343d"
],
@@ -59,5 +59,5 @@
}
],
"type": "eql",
- "version": 2
+ "version": 3
}
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_rds_group_creation.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_rds_group_creation.json
new file mode 100644
index 00000000000000..b2b5f06f8792a2
--- /dev/null
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_rds_group_creation.json
@@ -0,0 +1,62 @@
+{
+ "author": [
+ "Elastic",
+ "Austin Songer"
+ ],
+ "description": "Identifies the creation of an Amazon Relational Database Service (RDS) Security Group.",
+ "false_positives": [
+ "A RDS security group may be created by a system or network administrator. Verify whether the user identity, user agent, and/or hostname should be making changes in your environment. Security group creations from unfamiliar users or hosts should be investigated. If known behavior is causing false positives, it can be exempted from the rule."
+ ],
+ "from": "now-60m",
+ "index": [
+ "filebeat-*",
+ "logs-aws*"
+ ],
+ "interval": "10m",
+ "language": "kuery",
+ "license": "Elastic License v2",
+ "name": "AWS RDS Security Group Creation",
+ "note": "## Config\n\nThe AWS Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.",
+ "query": "event.dataset:aws.cloudtrail and event.provider:rds.amazonaws.com and event.action:CreateDBSecurityGroup and event.outcome:success\n",
+ "references": [
+ "https://docs.aws.amazon.com/AmazonRDS/latest/APIReference/API_CreateDBSecurityGroup.html"
+ ],
+ "risk_score": 21,
+ "rule_id": "378f9024-8a0c-46a5-aa08-ce147ac73a4e",
+ "severity": "low",
+ "tags": [
+ "Elastic",
+ "Cloud",
+ "AWS",
+ "Continuous Monitoring",
+ "SecOps",
+ "Monitoring"
+ ],
+ "threat": [
+ {
+ "framework": "MITRE ATT&CK",
+ "tactic": {
+ "id": "TA0003",
+ "name": "Persistence",
+ "reference": "https://attack.mitre.org/tactics/TA0003/"
+ },
+ "technique": [
+ {
+ "id": "T1136",
+ "name": "Create Account",
+ "reference": "https://attack.mitre.org/techniques/T1136/",
+ "subtechnique": [
+ {
+ "id": "T1136.003",
+ "name": "Cloud Account",
+ "reference": "https://attack.mitre.org/techniques/T1136/003/"
+ }
+ ]
+ }
+ ]
+ }
+ ],
+ "timestamp_override": "event.ingested",
+ "type": "query",
+ "version": 1
+}
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_rds_instance_creation.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_rds_instance_creation.json
new file mode 100644
index 00000000000000..aa2c946d3a0013
--- /dev/null
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_rds_instance_creation.json
@@ -0,0 +1,48 @@
+{
+ "author": [
+ "Elastic"
+ ],
+ "description": "Identifies the creation of an Amazon Relational Database Service (RDS) Aurora database instance.",
+ "false_positives": [
+ "A database instance may be created by a system or network administrator. Verify whether the user identity, user agent, and/or hostname should be making changes in your environment. Instances creations from unfamiliar users or hosts should be investigated. If known behavior is causing false positives, it can be exempted from the rule."
+ ],
+ "from": "now-60m",
+ "index": [
+ "filebeat-*",
+ "logs-aws*"
+ ],
+ "interval": "10m",
+ "language": "kuery",
+ "license": "Elastic License v2",
+ "name": "AWS RDS Instance Creation",
+ "note": "## Config\n\nThe AWS Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.",
+ "query": "event.dataset:aws.cloudtrail and event.provider:rds.amazonaws.com and event.action:CreateDBInstance and event.outcome:success\n",
+ "references": [
+ "https://docs.aws.amazon.com/AmazonRDS/latest/APIReference/API_CreateDBInstance.html"
+ ],
+ "risk_score": 21,
+ "rule_id": "f30f3443-4fbb-4c27-ab89-c3ad49d62315",
+ "severity": "low",
+ "tags": [
+ "Elastic",
+ "Cloud",
+ "AWS",
+ "Continuous Monitoring",
+ "SecOps",
+ "Asset Visibility"
+ ],
+ "threat": [
+ {
+ "framework": "MITRE ATT&CK",
+ "tactic": {
+ "id": "TA0003",
+ "name": "Persistence",
+ "reference": "https://attack.mitre.org/tactics/TA0003/"
+ },
+ "technique": []
+ }
+ ],
+ "timestamp_override": "event.ingested",
+ "type": "query",
+ "version": 1
+}
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_route_53_domain_transfer_lock_disabled.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_route_53_domain_transfer_lock_disabled.json
new file mode 100644
index 00000000000000..61ceebb7615f52
--- /dev/null
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_route_53_domain_transfer_lock_disabled.json
@@ -0,0 +1,65 @@
+{
+ "author": [
+ "Elastic",
+ "Austin Songer"
+ ],
+ "description": "Identifies when a transfer lock was removed from a Route 53 domain. It is recommended to refrain from performing this action unless intending to transfer the domain to a different registrar.",
+ "false_positives": [
+ "A domain transfer lock may be disabled by a system or network administrator. Verify whether the user identity, user agent, and/or hostname should be making changes in your environment. Activity from unfamiliar users or hosts should be investigated. If known behavior is causing false positives, it can be exempted from the rule."
+ ],
+ "from": "now-60m",
+ "index": [
+ "filebeat-*",
+ "logs-aws*"
+ ],
+ "interval": "10m",
+ "language": "kuery",
+ "license": "Elastic License v2",
+ "name": "AWS Route 53 Domain Transfer Lock Disabled",
+ "note": "## Config\n\nThe AWS Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.",
+ "query": "event.dataset:aws.cloudtrail and event.provider:route53.amazonaws.com and event.action:DisableDomainTransferLock and event.outcome:success\n",
+ "references": [
+ "https://docs.aws.amazon.com/Route53/latest/APIReference/API_Operations_Amazon_Route_53.html",
+ "https://docs.aws.amazon.com/Route53/latest/APIReference/API_domains_DisableDomainTransferLock.html"
+ ],
+ "risk_score": 21,
+ "rule_id": "12051077-0124-4394-9522-8f4f4db1d674",
+ "severity": "low",
+ "tags": [
+ "Elastic",
+ "Cloud",
+ "AWS",
+ "Continuous Monitoring",
+ "SecOps",
+ "Asset Visibility"
+ ],
+ "threat": [
+ {
+ "framework": "MITRE ATT&CK",
+ "tactic": {
+ "id": "TA0003",
+ "name": "Persistence",
+ "reference": "https://attack.mitre.org/tactics/TA0003/"
+ },
+ "technique": [
+ {
+ "id": "T1098",
+ "name": "Account Manipulation",
+ "reference": "https://attack.mitre.org/techniques/T1098/"
+ }
+ ]
+ },
+ {
+ "framework": "MITRE ATT&CK",
+ "tactic": {
+ "id": "TA0006",
+ "name": "Credential Access",
+ "reference": "https://attack.mitre.org/tactics/TA0006/"
+ },
+ "technique": []
+ }
+ ],
+ "timestamp_override": "event.ingested",
+ "type": "query",
+ "version": 1
+}
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_route_53_domain_transferred_to_another_account.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_route_53_domain_transferred_to_another_account.json
new file mode 100644
index 00000000000000..8485e1a96e685d
--- /dev/null
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_route_53_domain_transferred_to_another_account.json
@@ -0,0 +1,64 @@
+{
+ "author": [
+ "Elastic",
+ "Austin Songer"
+ ],
+ "description": "Identifies when a request has been made to transfer a Route 53 domain to another AWS account.",
+ "false_positives": [
+ "A domain may be transferred to another AWS account by a system or network administrator. Verify whether the user identity, user agent, and/or hostname should be making changes in your environment. Domain transfers from unfamiliar users or hosts should be investigated. If known behavior is causing false positives, it can be exempted from the rule."
+ ],
+ "from": "now-60m",
+ "index": [
+ "filebeat-*",
+ "logs-aws*"
+ ],
+ "interval": "10m",
+ "language": "kuery",
+ "license": "Elastic License v2",
+ "name": "AWS Route 53 Domain Transferred to Another Account",
+ "note": "## Config\n\nThe AWS Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.",
+ "query": "event.dataset:aws.cloudtrail and event.provider:route53.amazonaws.com and event.action:TransferDomainToAnotherAwsAccount and event.outcome:success\n",
+ "references": [
+ "https://docs.aws.amazon.com/Route53/latest/APIReference/API_Operations_Amazon_Route_53.html"
+ ],
+ "risk_score": 21,
+ "rule_id": "2045567e-b0af-444a-8c0b-0b6e2dae9e13",
+ "severity": "low",
+ "tags": [
+ "Elastic",
+ "Cloud",
+ "AWS",
+ "Continuous Monitoring",
+ "SecOps",
+ "Asset Visibility"
+ ],
+ "threat": [
+ {
+ "framework": "MITRE ATT&CK",
+ "tactic": {
+ "id": "TA0003",
+ "name": "Persistence",
+ "reference": "https://attack.mitre.org/tactics/TA0003/"
+ },
+ "technique": [
+ {
+ "id": "T1098",
+ "name": "Account Manipulation",
+ "reference": "https://attack.mitre.org/techniques/T1098/"
+ }
+ ]
+ },
+ {
+ "framework": "MITRE ATT&CK",
+ "tactic": {
+ "id": "TA0006",
+ "name": "Credential Access",
+ "reference": "https://attack.mitre.org/tactics/TA0006/"
+ },
+ "technique": []
+ }
+ ],
+ "timestamp_override": "event.ingested",
+ "type": "query",
+ "version": 1
+}
From a22f08750bbb09b85beeee67513ff83c5328b350 Mon Sep 17 00:00:00 2001
From: debadair
Date: Tue, 29 Jun 2021 17:36:09 -0700
Subject: [PATCH 018/121] [DOCS] Updated xref to ES guide (#103803)
---
docs/settings/reporting-settings.asciidoc | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/settings/reporting-settings.asciidoc b/docs/settings/reporting-settings.asciidoc
index 70f3e272fa5a99..8e870ceec8947b 100644
--- a/docs/settings/reporting-settings.asciidoc
+++ b/docs/settings/reporting-settings.asciidoc
@@ -105,7 +105,7 @@ security is enabled, <
Date: Tue, 29 Jun 2021 20:42:37 -0400
Subject: [PATCH 019/121] [Monitoring] Fix Cluster Listing view (#103718)
* check for number of nodes
* remove console
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
---
.../monitoring/public/components/cluster/listing/listing.js | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/x-pack/plugins/monitoring/public/components/cluster/listing/listing.js b/x-pack/plugins/monitoring/public/components/cluster/listing/listing.js
index 12cfc4f1328634..06a480cd30f189 100644
--- a/x-pack/plugins/monitoring/public/components/cluster/listing/listing.js
+++ b/x-pack/plugins/monitoring/public/components/cluster/listing/listing.js
@@ -132,7 +132,9 @@ const getColumns = (
'data-test-subj': 'nodesCount',
sortable: true,
render: (total, cluster) => (
- {numeral(total).format('0,0')}
+
+ {typeof total === 'number' ? numeral(total).format('0,0') : 0}
+
),
},
{
From 37e2d8a6c5fc53ad7d6cdb1d6ba1ca5b061e9ca7 Mon Sep 17 00:00:00 2001
From: Paul Tavares <56442535+paul-tavares@users.noreply.github.com>
Date: Tue, 29 Jun 2021 20:43:13 -0400
Subject: [PATCH 020/121] [Security Solution][Hosts] Fix Host Events flyout and
remove the Endpoint Host Isolation `Take Action` button (only valid for
Alerts) (#103784)
* Fix bug in `endpointAlertCheck` to ensure events are not looked at
* Fix data/type
---
.../public/common/mock/mock_detail_item.ts | 4 +-
.../common/utils/endpoint_alert_check.test.ts | 50 +++++++++++++------
.../common/utils/endpoint_alert_check.ts | 17 +++++--
.../side_panel/event_details/index.tsx | 2 +-
4 files changed, 54 insertions(+), 19 deletions(-)
diff --git a/x-pack/plugins/security_solution/public/common/mock/mock_detail_item.ts b/x-pack/plugins/security_solution/public/common/mock/mock_detail_item.ts
index 198ab084ae0b87..3712d389edeb10 100644
--- a/x-pack/plugins/security_solution/public/common/mock/mock_detail_item.ts
+++ b/x-pack/plugins/security_solution/public/common/mock/mock_detail_item.ts
@@ -9,7 +9,7 @@ import { TimelineEventsDetailsItem } from '../../../common/search_strategy';
export const mockDetailItemDataId = 'Y-6TfmcB0WOhS6qyMv3s';
-export const mockDetailItemData: TimelineEventsDetailsItem[] = [
+export const generateMockDetailItemData = (): TimelineEventsDetailsItem[] => [
{
field: '_id',
originalValue: 'pEMaMmkBUV60JmNWmWVi',
@@ -137,3 +137,5 @@ export const mockDetailItemData: TimelineEventsDetailsItem[] = [
isObjectArray: false,
},
];
+
+export const mockDetailItemData: TimelineEventsDetailsItem[] = generateMockDetailItemData();
diff --git a/x-pack/plugins/security_solution/public/common/utils/endpoint_alert_check.test.ts b/x-pack/plugins/security_solution/public/common/utils/endpoint_alert_check.test.ts
index b085fe67d3814a..e95f5c15d4ecb9 100644
--- a/x-pack/plugins/security_solution/public/common/utils/endpoint_alert_check.test.ts
+++ b/x-pack/plugins/security_solution/public/common/utils/endpoint_alert_check.test.ts
@@ -6,26 +6,48 @@
*/
import _ from 'lodash';
-import { mockDetailItemData } from '../mock';
+import { generateMockDetailItemData } from '../mock';
import { endpointAlertCheck } from './endpoint_alert_check';
-describe('utils', () => {
- describe('endpointAlertCheck', () => {
- it('should return false if detections data does not come from endpoint rule', () => {
- expect(endpointAlertCheck({ data: mockDetailItemData })).toBeFalsy();
- });
- it('should return true if detections data comes from an endpoint rule', () => {
- _.remove(mockDetailItemData, function (o) {
- return o.field === 'agent.type';
- });
- const mockEndpointDetailItemData = _.concat(mockDetailItemData, {
+describe('Endpoint Alert Check Utility', () => {
+ let mockDetailItemData: ReturnType;
+
+ beforeEach(() => {
+ mockDetailItemData = generateMockDetailItemData();
+
+ // Remove the filebeat agent type from the mock
+ _.remove(mockDetailItemData, { field: 'agent.type' });
+
+ mockDetailItemData.push(
+ // Must be an Alert
+ {
+ field: 'signal.rule.id',
+ category: 'signal',
+ originalValue: 'endpoint',
+ values: ['endpoint'],
+ isObjectArray: false,
+ },
+ // Must be from an endpoint agent
+ {
field: 'agent.type',
originalValue: 'endpoint',
values: ['endpoint'],
isObjectArray: false,
- });
+ }
+ );
+ });
+
+ it('should return true if detections data comes from an endpoint rule', () => {
+ expect(endpointAlertCheck({ data: mockDetailItemData })).toBe(true);
+ });
+
+ it('should return false if it is not an Alert (ex. maybe an event)', () => {
+ _.remove(mockDetailItemData, { field: 'signal.rule.id' });
+ expect(endpointAlertCheck({ data: mockDetailItemData })).toBeFalsy();
+ });
- expect(endpointAlertCheck({ data: mockEndpointDetailItemData })).toBeTruthy();
- });
+ it('should return false if it is not an endpoint agent', () => {
+ _.remove(mockDetailItemData, { field: 'agent.type' });
+ expect(endpointAlertCheck({ data: mockDetailItemData })).toBeFalsy();
});
});
diff --git a/x-pack/plugins/security_solution/public/common/utils/endpoint_alert_check.ts b/x-pack/plugins/security_solution/public/common/utils/endpoint_alert_check.ts
index e399cec0f3bbe8..30c6e3fdeb672d 100644
--- a/x-pack/plugins/security_solution/public/common/utils/endpoint_alert_check.ts
+++ b/x-pack/plugins/security_solution/public/common/utils/endpoint_alert_check.ts
@@ -5,10 +5,21 @@
* 2.0.
*/
-import { find } from 'lodash/fp';
-import { TimelineEventsDetailsItem } from '../../../common/search_strategy';
+import { find, some } from 'lodash/fp';
+import { TimelineEventsDetailsItem } from '../../../../timelines/common';
+
+/**
+ * Checks to see if the given set of Timeline event detail items includes data that indicates its
+ * an endpoint Alert. Note that it will NOT match on Events - only alerts
+ * @param data
+ */
+export const endpointAlertCheck = ({ data }: { data: TimelineEventsDetailsItem[] }): boolean => {
+ const isAlert = some({ category: 'signal', field: 'signal.rule.id' }, data);
+
+ if (!isAlert) {
+ return false;
+ }
-export const endpointAlertCheck = ({ data }: { data: TimelineEventsDetailsItem[] | null }) => {
const findEndpointAlert = find({ field: 'agent.type' }, data)?.values;
return findEndpointAlert ? findEndpointAlert[0] === 'endpoint' : false;
};
diff --git a/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/index.tsx
index 509c629dc287c8..94ef690a1fdc1a 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/index.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/index.tsx
@@ -95,7 +95,7 @@ const EventDetailsPanelComponent: React.FC = ({
const isAlert = some({ category: 'signal', field: 'signal.rule.id' }, detailsData);
const isEndpointAlert = useMemo(() => {
- return endpointAlertCheck({ data: detailsData });
+ return endpointAlertCheck({ data: detailsData || [] });
}, [detailsData]);
const agentId = useMemo(() => {
From 7d45fcf8ee984b27d24076efe3edfedee17fb992 Mon Sep 17 00:00:00 2001
From: Caroline Horn <549577+cchaos@users.noreply.github.com>
Date: Tue, 29 Jun 2021 20:50:15 -0400
Subject: [PATCH 021/121] [Page layouts] Some light fixes (#103197)
* [Solution Toolbar] Fixing button border on non-text color versions
* [Alerts] Removed extra wrappers and use EuiPageHeader
* [Logstash] Basic conversion to template
* [Reporting] Adding bottomBorder to page header
* [ML] Fix display of main navigation tabs
* [Stack Management] Fix side nav not updating when going back to landing page
* [Tags] Add spacing after page header
* [License Management] Full width on file uploader
* [Page Template] Fixed `emptyState` default template for pages with side nav
* [Infra] Removing some page header displays in empty states
* [Enterprise Search] Fix some error layouts
* [Index Patterns] Quick fix for empty state
* snaps
* [Page Template] Remove forced padding when `centeredBody`
* small hack for tab padding for ml
* scroll ML page to fix test
* fix test method type signature
Co-authored-by: Dave Snider
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
Co-authored-by: Michail Yasonik
---
.../__snapshots__/empty_state.test.tsx.snap | 8 +-
.../empty_state/empty_state.tsx | 9 +-
.../__snapshots__/page_template.test.tsx.snap | 5 -
.../public/page_template/page_template.tsx | 7 +-
.../public/components/landing/landing.tsx | 13 +-
.../management_app/management_router.tsx | 1 +
.../solution_toolbar/items/button.scss | 6 +-
.../services/common/test_subjects.ts | 7 +-
.../error_connecting/error_connecting.tsx | 10 +-
.../error_connecting/error_connecting.tsx | 11 +-
.../views/error_state/error_state.tsx | 14 +-
.../log_entry_categories/page_content.tsx | 10 +-
.../logs/log_entry_rate/page_content.tsx | 10 +-
.../upload_license.test.tsx.snap | 795 ++---
.../sections/upload_license/upload_license.js | 28 +-
.../pipeline_editor.test.js.snap | 3080 ++++++++---------
.../pipeline_editor/pipeline_editor.js | 382 +-
.../components/pipeline_list/pipeline_list.js | 65 +-
.../plugins/ml/public/application/_index.scss | 1 -
.../components/navigation_menu/_index.scss | 1 -
.../navigation_menu/_navigation_menu.scss | 5 -
.../components/navigation_menu/main_tabs.scss | 4 +
.../components/navigation_menu/main_tabs.tsx | 49 +-
.../navigation_menu/navigation_menu.tsx | 13 +-
.../public/management/report_listing.tsx | 1 +
.../public/management/tag_management_page.tsx | 2 +
.../public/application/home.tsx | 135 +-
.../components/alert_details.tsx | 388 +--
.../services/ml/data_visualizer_file_based.ts | 6 +-
29 files changed, 2437 insertions(+), 2629 deletions(-)
delete mode 100644 x-pack/plugins/ml/public/application/components/navigation_menu/_index.scss
delete mode 100644 x-pack/plugins/ml/public/application/components/navigation_menu/_navigation_menu.scss
create mode 100644 x-pack/plugins/ml/public/application/components/navigation_menu/main_tabs.scss
diff --git a/src/plugins/index_pattern_management/public/components/index_pattern_table/empty_state/__snapshots__/empty_state.test.tsx.snap b/src/plugins/index_pattern_management/public/components/index_pattern_table/empty_state/__snapshots__/empty_state.test.tsx.snap
index 957c94c80680d9..75b8177d9dac35 100644
--- a/src/plugins/index_pattern_management/public/components/index_pattern_table/empty_state/__snapshots__/empty_state.test.tsx.snap
+++ b/src/plugins/index_pattern_management/public/components/index_pattern_table/empty_state/__snapshots__/empty_state.test.tsx.snap
@@ -120,11 +120,10 @@ exports[`EmptyState should render normally 1`] = `
-
+
-
+
diff --git a/src/plugins/index_pattern_management/public/components/index_pattern_table/empty_state/empty_state.tsx b/src/plugins/index_pattern_management/public/components/index_pattern_table/empty_state/empty_state.tsx
index c05f6a1f193b7a..af49e8c36fe3bd 100644
--- a/src/plugins/index_pattern_management/public/components/index_pattern_table/empty_state/empty_state.tsx
+++ b/src/plugins/index_pattern_management/public/components/index_pattern_table/empty_state/empty_state.tsx
@@ -24,6 +24,7 @@ import {
EuiCard,
EuiLink,
EuiText,
+ EuiFlexGroup,
} from '@elastic/eui';
import { useHistory } from 'react-router-dom';
import { reactRouterNavigate } from '../../../../../../plugins/kibana_react/public';
@@ -143,8 +144,8 @@ export const EmptyState = ({
-
-
+
+
-
+
-
+
diff --git a/src/plugins/kibana_react/public/page_template/__snapshots__/page_template.test.tsx.snap b/src/plugins/kibana_react/public/page_template/__snapshots__/page_template.test.tsx.snap
index 71d034d32bd1be..9ad2bd73674bd8 100644
--- a/src/plugins/kibana_react/public/page_template/__snapshots__/page_template.test.tsx.snap
+++ b/src/plugins/kibana_react/public/page_template/__snapshots__/page_template.test.tsx.snap
@@ -2,7 +2,6 @@
exports[`KibanaPageTemplate render basic template 1`] = `
= ({
);
}
+ const emptyStateDefaultTemplate = pageSideBar ? 'centeredContent' : 'centeredBody';
+
/**
* An easy way to create the right content for empty pages
*/
if (isEmptyState && pageHeader && !children) {
- template = template ?? 'centeredBody';
+ template = template ?? emptyStateDefaultTemplate;
const { iconType, pageTitle, description, rightSideItems } = pageHeader;
pageHeader = undefined;
children = (
@@ -104,14 +106,13 @@ export const KibanaPageTemplate: FunctionComponent = ({
} else if (isEmptyState && pageHeader && children) {
template = template ?? 'centeredContent';
} else if (isEmptyState && !pageHeader) {
- template = template ?? 'centeredBody';
+ template = template ?? emptyStateDefaultTemplate;
}
return (
void;
setBreadcrumbs: () => void;
}
-export const ManagementLandingPage = ({ version, setBreadcrumbs }: ManagementLandingPageProps) => {
+export const ManagementLandingPage = ({
+ version,
+ setBreadcrumbs,
+ onAppMounted,
+}: ManagementLandingPageProps) => {
setBreadcrumbs();
+ useEffect(() => {
+ onAppMounted('');
+ }, [onAppMounted]);
+
return (
)}
/>
diff --git a/src/plugins/presentation_util/public/components/solution_toolbar/items/button.scss b/src/plugins/presentation_util/public/components/solution_toolbar/items/button.scss
index 4fc3651ee9f730..a1e5b4e1417652 100644
--- a/src/plugins/presentation_util/public/components/solution_toolbar/items/button.scss
+++ b/src/plugins/presentation_util/public/components/solution_toolbar/items/button.scss
@@ -6,7 +6,9 @@
border-color: $euiBorderColor !important; // sass-lint:disable-line no-important
@include kbnThemeStyle('v8') {
- border-width: $euiBorderWidthThin;
- border-style: solid;
+ &[class*='--text'] {
+ border-width: $euiBorderWidthThin;
+ border-style: solid;
+ }
}
}
diff --git a/test/functional/services/common/test_subjects.ts b/test/functional/services/common/test_subjects.ts
index ae04fe5d2b9390..3f47c6155f1753 100644
--- a/test/functional/services/common/test_subjects.ts
+++ b/test/functional/services/common/test_subjects.ts
@@ -312,9 +312,12 @@ export class TestSubjects extends FtrService {
return testSubjSelector(selector);
}
- public async scrollIntoView(selector: string) {
+ public async scrollIntoView(
+ selector: string,
+ offset?: number | { topOffset?: number; bottomOffset?: number }
+ ) {
const element = await this.find(selector);
- await element.scrollIntoViewIfNecessary();
+ await element.scrollIntoViewIfNecessary(offset);
}
// isChecked always returns false when run on an euiSwitch, because they use the aria-checked attribute
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/error_connecting/error_connecting.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/error_connecting/error_connecting.tsx
index a53e8a099177c2..84dcb07a074743 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/error_connecting/error_connecting.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/error_connecting/error_connecting.tsx
@@ -7,7 +7,7 @@
import React from 'react';
-import { EuiPage, EuiPageContent } from '@elastic/eui';
+import { KibanaPageTemplate } from '../../../../../../../../src/plugins/kibana_react/public';
import { ErrorStatePrompt } from '../../../shared/error_state';
import { SetAppSearchChrome as SetPageChrome } from '../../../shared/kibana_chrome';
@@ -19,11 +19,9 @@ export const ErrorConnecting: React.FC = () => {
-
-
-
-
-
+
+
+
>
);
};
diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search/components/error_connecting/error_connecting.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search/components/error_connecting/error_connecting.tsx
index afee20df106e89..979847b4cf1c6d 100644
--- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search/components/error_connecting/error_connecting.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search/components/error_connecting/error_connecting.tsx
@@ -7,16 +7,15 @@
import React from 'react';
-import { EuiPage, EuiPageContent } from '@elastic/eui';
+import { KibanaPageTemplate } from '../../../../../../../../src/plugins/kibana_react/public';
import { ErrorStatePrompt } from '../../../shared/error_state';
import { SendEnterpriseSearchTelemetry as SendTelemetry } from '../../../shared/telemetry';
export const ErrorConnecting: React.FC = () => (
-
+
-
-
-
-
+
+
+
);
diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/error_state/error_state.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/error_state/error_state.tsx
index 8116d55542820b..f4914413a80fa1 100644
--- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/error_state/error_state.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/error_state/error_state.tsx
@@ -9,7 +9,7 @@
import React from 'react';
-import { EuiPage, EuiPageBody, EuiPageContent } from '@elastic/eui';
+import { KibanaPageTemplate } from '../../../../../../../../src/plugins/kibana_react/public';
import { WORKPLACE_SEARCH_PLUGIN } from '../../../../../common/constants';
import { ErrorStatePrompt } from '../../../shared/error_state';
@@ -19,16 +19,14 @@ import { ViewContentHeader } from '../../components/shared/view_content_header';
export const ErrorState: React.FC = () => {
return (
-
+ <>
-
+
-
-
-
-
-
+
+
+ >
);
};
diff --git a/x-pack/plugins/infra/public/pages/logs/log_entry_categories/page_content.tsx b/x-pack/plugins/infra/public/pages/logs/log_entry_categories/page_content.tsx
index 462b8b2f9dc3ec..bfdbe035261649 100644
--- a/x-pack/plugins/infra/public/pages/logs/log_entry_categories/page_content.tsx
+++ b/x-pack/plugins/infra/public/pages/logs/log_entry_categories/page_content.tsx
@@ -116,9 +116,13 @@ const CategoriesPageTemplate: React.FC = ({
return (
{children}
diff --git a/x-pack/plugins/infra/public/pages/logs/log_entry_rate/page_content.tsx b/x-pack/plugins/infra/public/pages/logs/log_entry_rate/page_content.tsx
index ea60d073c23114..04909b059d8c33 100644
--- a/x-pack/plugins/infra/public/pages/logs/log_entry_rate/page_content.tsx
+++ b/x-pack/plugins/infra/public/pages/logs/log_entry_rate/page_content.tsx
@@ -158,9 +158,13 @@ const AnomaliesPageTemplate: React.FC = ({
return (
{children}
diff --git a/x-pack/plugins/license_management/__jest__/__snapshots__/upload_license.test.tsx.snap b/x-pack/plugins/license_management/__jest__/__snapshots__/upload_license.test.tsx.snap
index 45e7055f4db2bd..ea973df9aad765 100644
--- a/x-pack/plugins/license_management/__jest__/__snapshots__/upload_license.test.tsx.snap
+++ b/x-pack/plugins/license_management/__jest__/__snapshots__/upload_license.test.tsx.snap
@@ -703,106 +703,85 @@ exports[`UploadLicense should display a modal when license requires acknowledgem
-
+ }
+ onChange={[Function]}
>
-
-
-
+
+
+
+
+
+
+
-
- }
- onChange={[Function]}
+
-
-
-
-
-
-
-
-
-
-
-
-
- Select or drag your license file
-
-
-
-
-
-
-
+ Select or drag your license file
+
-
+
-
-
-
+
+
+
@@ -1445,106 +1424,85 @@ exports[`UploadLicense should display an error when ES says license is expired 1
-
+ }
+ onChange={[Function]}
>
-
-
-
+
+
+
+
+
+
+
-
- }
- onChange={[Function]}
+
-
-
-
-
-
-
-
-
-
-
-
-
- Select or drag your license file
-
-
-
-
-
-
-
+ Select or drag your license file
+
-
+
-
-
-
+
+
+
@@ -2187,106 +2145,85 @@ exports[`UploadLicense should display an error when ES says license is invalid 1
-
+ }
+ onChange={[Function]}
>
-
-
-
+
+
+
+
+
+
+
-
- }
- onChange={[Function]}
+
-
-
-
-
-
-
-
-
-
-
-
-
- Select or drag your license file
-
-
-
-
-
-
-
+ Select or drag your license file
+
-
+
-
-
-
+
+
+
@@ -2929,106 +2866,85 @@ exports[`UploadLicense should display an error when submitting invalid JSON 1`]
-
+ }
+ onChange={[Function]}
>
-
-
-
+
+
+
+
+
+
+
-
- }
- onChange={[Function]}
+
-
-
-
-
-
-
-
-
-
-
-
-
- Select or drag your license file
-
-
-
-
-
-
-
+ Select or drag your license file
+
-
+
-
-
-
+
+
+
@@ -3671,106 +3587,85 @@ exports[`UploadLicense should display error when ES returns error 1`] = `
-
+ }
+ onChange={[Function]}
>
-
-
-
+
+
+
+
+
+
+
-
- }
- onChange={[Function]}
+
-
-
-
-
-
-
-
-
-
-
-
-
- Select or drag your license file
-
-
-
-
-
-
-
+ Select or drag your license file
+
-
+
-
-
-
+
+
+
diff --git a/x-pack/plugins/license_management/public/application/sections/upload_license/upload_license.js b/x-pack/plugins/license_management/public/application/sections/upload_license/upload_license.js
index 8ae20a2e607570..22933c6207a725 100644
--- a/x-pack/plugins/license_management/public/application/sections/upload_license/upload_license.js
+++ b/x-pack/plugins/license_management/public/application/sections/upload_license/upload_license.js
@@ -160,22 +160,18 @@ export class UploadLicense extends React.PureComponent {
-
-
-
-
- }
- onChange={this.handleFile}
- />
-
-
-
+
+ }
+ onChange={this.handleFile}
+ />
+
{shouldShowTelemetryOptIn(telemetry) && (
-
+
+
-
-
- Create Pipeline
-
-
-
-
}
- isInvalid={true}
+ labelType="label"
>
-
- }
- labelType="label"
- >
-
+
+
-
-
+
- }
- labelType="label"
- >
-
+
+
-
-
- }
- labelType="label"
+ }
+ labelType="label"
+ >
+
+
+
- }
- labelType="label"
+ />
+ }
+ labelType="label"
+ >
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
-
-
-
+
+
-
-
-
-
-
-
+
+
+
+
+
-
-
-
-
-
-
+
+
+
+
+
+
+
-
-
-
-
-
-
+
+
+
+
-
-
-
-
-
-
-
+
+
+
+
+
`;
exports[`PipelineEditor component invalidates form for invalid pipeline id input 1`] = `
-
-
+
+
-
-
- Create Pipeline
-
-
-
-
}
- isInvalid={true}
+ labelType="label"
>
-
- }
- labelType="label"
- >
-
+
+
-
-
+
- }
- labelType="label"
- >
-
+
+
-
-
- }
- labelType="label"
+ }
+ labelType="label"
+ >
+
+
+
- }
- labelType="label"
+ />
+ }
+ labelType="label"
+ >
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
-
-
-
+
+
-
-
-
-
-
-
+
+
+
+
+
-
-
-
-
-
-
+
+
+
+
+
+
+
-
-
-
-
-
-
+
+
+
+
-
-
-
-
-
-
-
+
+
+
+
+
`;
exports[`PipelineEditor component invalidates form for pipeline id with spaces 1`] = `
-
-
+
+
-
-
- Create Pipeline
-
-
-
-
}
- isInvalid={true}
+ labelType="label"
>
-
- }
- labelType="label"
- >
-
+
+
-
-
+
- }
- labelType="label"
- >
-
+
+
-
-
- }
- labelType="label"
+ }
+ labelType="label"
+ >
+
+
+
- }
- labelType="label"
+ />
+ }
+ labelType="label"
+ >
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
-
-
-
+
+
-
-
-
-
-
-
+
+
+
+
+
-
-
-
-
-
-
+
+
+
+
+
+
+
-
-
-
-
-
-
+
+
+
+
-
-
-
-
-
-
-
+
+
+
+
+
`;
exports[`PipelineEditor component matches snapshot for clone pipeline 1`] = `
-
-
+
+
+
-
-
- Clone Pipeline "pipelineToClone"
-
-
-
-
+ }
+ labelType="label"
>
-
- }
- labelType="label"
- >
-
+
+
-
-
- }
- labelType="label"
+ }
+ labelType="label"
+ >
+
+
+
- }
- labelType="label"
+ />
+ }
+ labelType="label"
+ >
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
-
-
-
+
+
-
-
-
-
-
-
+
+
+
+
+
-
-
-
-
-
-
+
+
+
+
+
+
+
-
-
-
-
-
-
+
+
+
+
-
-
-
-
-
+
+
+
+
-
-
-
-
-
-
-
+
+
+
+
+
`;
exports[`PipelineEditor component matches snapshot for create pipeline 1`] = `
-
-
+
+
+
-
-
- Create Pipeline
-
-
-
-
+ }
+ labelType="label"
>
-
- }
- labelType="label"
- >
-
+
+
-
-
+
- }
- labelType="label"
- >
-
+
+
-
-
- }
- labelType="label"
+ }
+ labelType="label"
+ >
+
+
+
- }
- labelType="label"
+ />
+ }
+ labelType="label"
+ >
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
-
-
-
+
+
-
-
-
-
-
-
+
+
+
+
+
-
-
-
-
-
-
+
+
+
+
+
+
+
-
-
-
-
-
-
+
+
+
+
-
-
-
-
-
-
-
+
+
+
+
+
`;
exports[`PipelineEditor component matches snapshot for edit pipeline 1`] = `
-
-
+
+
+
-
-
- Edit Pipeline "pipelineId"
-
-
-
-
+ }
+ labelType="label"
>
-
- }
- labelType="label"
- >
-
+
+
-
-
- }
- labelType="label"
+ }
+ labelType="label"
+ >
+
+
+
- }
- labelType="label"
+ />
+ }
+ labelType="label"
+ >
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
-
-
-
+
+
-
-
-
-
-
-
+
+
+
+
+
-
-
-
-
-
-
+
+
+
+
+
+
+
-
-
-
-
-
-
+
+
+
+
-
-
-
-
-
+
+
+
+
-
-
-
-
-
-
-
+
+
+
+
+
`;
diff --git a/x-pack/plugins/logstash/public/application/components/pipeline_editor/pipeline_editor.js b/x-pack/plugins/logstash/public/application/components/pipeline_editor/pipeline_editor.js
index 2ec7caa976cc8b..334c7d0322a849 100644
--- a/x-pack/plugins/logstash/public/application/components/pipeline_editor/pipeline_editor.js
+++ b/x-pack/plugins/logstash/public/application/components/pipeline_editor/pipeline_editor.js
@@ -25,10 +25,10 @@ import {
EuiFieldText,
EuiForm,
EuiFormRow,
- EuiPageContent,
+ EuiPageContentBody,
EuiSelect,
EuiSpacer,
- EuiTitle,
+ EuiPageHeader,
} from '@elastic/eui';
import { ConfirmDeletePipelineModal } from './confirm_delete_pipeline_modal';
import { FlexItemSetting } from './flex_item_setting';
@@ -269,219 +269,211 @@ class PipelineEditorUi extends React.Component {
const { intl } = this.props;
return (
-
-
-
- {this.getPipelineHeadingText()}
-
-
-
- {this.props.isNewPipeline && (
-
- }
- >
-
-
- )}
+
+
+
+
+ {this.props.isNewPipeline && (
}
>
-
+ }
+ >
+
- }
+ name="pipelineDescription"
+ onChange={this.onPipelineDescriptionChange}
+ value={this.state.pipeline.description || ''}
+ />
+
+
+ }
+ >
+
+
+
+
+
+ }
+ >
+ this.handleNumberChange('pipeline.workers', e.target.value)}
+ value={this.state.pipeline.settings['pipeline.workers']}
+ />
+
+
+
+
-
-
-
-
-
- }
+ this.handleNumberChange('pipeline.batch.size', e.target.value)}
+ value={this.state.pipeline.settings['pipeline.batch.size']}
+ />
+
+
this.handleNumberChange('pipeline.workers', e.target.value)}
- value={this.state.pipeline.settings['pipeline.workers']}
+ data-test-subj="inputBatchDelay"
+ onChange={(e) => this.handleNumberChange('pipeline.batch.delay', e.target.value)}
+ value={this.state.pipeline.settings['pipeline.batch.delay']}
/>
-
-
-
-
- this.handleNumberChange('pipeline.batch.size', e.target.value)}
- value={this.state.pipeline.settings['pipeline.batch.size']}
- />
-
-
- this.handleNumberChange('pipeline.batch.delay', e.target.value)}
- value={this.state.pipeline.settings['pipeline.batch.delay']}
- />
-
-
-
-
- this.handleSettingChange('queue.type', e.target.value)}
- options={PIPELINE_EDITOR.QUEUE_TYPES}
- value={this.state.pipeline.settings['queue.type']}
- />
-
-
- this.handleMaxByteNumberChange(e.target.value)}
- value={this.state.maxBytesNumber}
- />
-
-
- this.handleMaxByteUnitChange(e.target.value)}
- options={PIPELINE_EDITOR.UNITS}
- value={this.state.maxBytesUnit}
- />
-
-
-
- this.handleNumberChange('queue.checkpoint.writes', e.target.value)
- }
- value={this.state.pipeline.settings['queue.checkpoint.writes']}
- />
-
-
-
-
-
+
+
+
+
+ this.handleSettingChange('queue.type', e.target.value)}
+ options={PIPELINE_EDITOR.QUEUE_TYPES}
+ value={this.state.pipeline.settings['queue.type']}
+ />
+
+
+ this.handleMaxByteNumberChange(e.target.value)}
+ value={this.state.maxBytesNumber}
+ />
+
+
+ this.handleMaxByteUnitChange(e.target.value)}
+ options={PIPELINE_EDITOR.UNITS}
+ value={this.state.maxBytesUnit}
+ />
+
+
+ this.handleNumberChange('queue.checkpoint.writes', e.target.value)}
+ value={this.state.pipeline.settings['queue.checkpoint.writes']}
+ />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {!this.props.isNewPipeline && (
-
-
-
-
-
-
-
+
- {!this.props.isNewPipeline && (
-
-
-
-
-
- )}
-
-
+ )}
+
{this.state.showConfirmDeleteModal && (
)}
-
+
);
}
}
diff --git a/x-pack/plugins/logstash/public/application/components/pipeline_list/pipeline_list.js b/x-pack/plugins/logstash/public/application/components/pipeline_list/pipeline_list.js
index a8b03d91f996dd..4772669daec62d 100644
--- a/x-pack/plugins/logstash/public/application/components/pipeline_list/pipeline_list.js
+++ b/x-pack/plugins/logstash/public/application/components/pipeline_list/pipeline_list.js
@@ -12,9 +12,8 @@ import {
EuiCallOut,
EuiEmptyPrompt,
EuiLoadingSpinner,
- EuiPageContent,
- EuiTitle,
- EuiText,
+ EuiPageContentBody,
+ EuiPageHeader,
EuiSpacer,
} from '@elastic/eui';
@@ -292,36 +291,34 @@ class PipelineListUi extends React.Component {
const { clonePipeline, createPipeline, isReadOnly, openPipeline } = this.props;
const { isSelectable, message, pipelines, selection, showConfirmDeleteModal } = this.state;
return (
-
-
-
-
-
-
-
-
-
-
-
-
-
- {this.renderNoPermissionCallOut()}
-
-
+
+
+ }
+ description={
+
+ }
+ bottomBorder
+ />
+
+ {this.renderNoPermissionCallOut()}
+
+
-
+
);
}
}
diff --git a/x-pack/plugins/ml/public/application/_index.scss b/x-pack/plugins/ml/public/application/_index.scss
index fb61a2a16c19d5..8fec50ed0e0196 100644
--- a/x-pack/plugins/ml/public/application/_index.scss
+++ b/x-pack/plugins/ml/public/application/_index.scss
@@ -25,7 +25,6 @@
@import 'components/items_grid/index';
@import 'components/job_selector/index';
@import 'components/loading_indicator/index'; // SASSTODO: This component should be replaced with EuiLoadingSpinner
- @import 'components/navigation_menu/index';
@import 'components/rule_editor/index'; // SASSTODO: This file overwrites EUI directly
@import 'components/stats_bar/index';
@import 'components/ml_embedded_map/index';
diff --git a/x-pack/plugins/ml/public/application/components/navigation_menu/_index.scss b/x-pack/plugins/ml/public/application/components/navigation_menu/_index.scss
deleted file mode 100644
index 5135bba535dd9b..00000000000000
--- a/x-pack/plugins/ml/public/application/components/navigation_menu/_index.scss
+++ /dev/null
@@ -1 +0,0 @@
-@import 'navigation_menu'
diff --git a/x-pack/plugins/ml/public/application/components/navigation_menu/_navigation_menu.scss b/x-pack/plugins/ml/public/application/components/navigation_menu/_navigation_menu.scss
deleted file mode 100644
index 0d14bb46e8deb8..00000000000000
--- a/x-pack/plugins/ml/public/application/components/navigation_menu/_navigation_menu.scss
+++ /dev/null
@@ -1,5 +0,0 @@
-.mlNavigationMenu {
- padding: 0 $euiSizeM;
- border-bottom: $euiBorderThin;
- background-color: $euiColorEmptyShade;
-}
diff --git a/x-pack/plugins/ml/public/application/components/navigation_menu/main_tabs.scss b/x-pack/plugins/ml/public/application/components/navigation_menu/main_tabs.scss
new file mode 100644
index 00000000000000..05b85e4d20633a
--- /dev/null
+++ b/x-pack/plugins/ml/public/application/components/navigation_menu/main_tabs.scss
@@ -0,0 +1,4 @@
+.mlMainTabs {
+ // Hack to address https://github.com/elastic/kibana/pull/103197#discussion_r659645946
+ padding-bottom: 0 !important;
+}
diff --git a/x-pack/plugins/ml/public/application/components/navigation_menu/main_tabs.tsx b/x-pack/plugins/ml/public/application/components/navigation_menu/main_tabs.tsx
index 0f381fb7acee91..5073896eba9f80 100644
--- a/x-pack/plugins/ml/public/application/components/navigation_menu/main_tabs.tsx
+++ b/x-pack/plugins/ml/public/application/components/navigation_menu/main_tabs.tsx
@@ -7,13 +7,14 @@
import React, { FC, useState, useEffect } from 'react';
-import { EuiTabs, EuiTab } from '@elastic/eui';
+import { EuiPageHeader } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { TabId } from './navigation_menu';
import { useMlKibana, useMlUrlGenerator, useNavigateToPath } from '../../contexts/kibana';
import { MlUrlGeneratorState } from '../../../../common/types/ml_url_generator';
import { useUrlState } from '../../util/url_state';
import { ML_APP_NAME } from '../../../../common/constants/app';
+import './main_tabs.scss';
export interface Tab {
id: TabId;
@@ -154,40 +155,26 @@ export const MainTabs: FC = ({ tabId, disableLinks }) => {
}, [selectedTabId]);
return (
-
- {tabs.map((tab: Tab) => {
+ {
const { id, disabled } = tab;
const testSubject = TAB_DATA[id].testSubject;
const defaultPathId = (TAB_DATA[id].pathId || id) as MlUrlGeneratorState['page'];
- return disabled ? (
-
-
- {tab.name}
-
-
- ) : (
-
- {
- onSelectedTabChanged(id);
- redirectToTab(defaultPathId);
- }}
- isSelected={id === selectedTabId}
- key={`tab-${id}-key`}
- >
- {tab.name}
-
-
- );
+ return {
+ label: tab.name,
+ disabled,
+ onClick: () => {
+ onSelectedTabChanged(id);
+ redirectToTab(defaultPathId);
+ },
+ 'data-test-subj': testSubject + (id === selectedTabId ? ' selected' : ''),
+ isSelected: id === selectedTabId,
+ };
})}
-
+ />
);
};
diff --git a/x-pack/plugins/ml/public/application/components/navigation_menu/navigation_menu.tsx b/x-pack/plugins/ml/public/application/components/navigation_menu/navigation_menu.tsx
index e836dc3ddced72..986a88d789b369 100644
--- a/x-pack/plugins/ml/public/application/components/navigation_menu/navigation_menu.tsx
+++ b/x-pack/plugins/ml/public/application/components/navigation_menu/navigation_menu.tsx
@@ -5,8 +5,7 @@
* 2.0.
*/
-import React, { Fragment, FC } from 'react';
-import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
+import React, { FC } from 'react';
import { isFullLicense } from '../../license';
@@ -27,13 +26,5 @@ interface Props {
export const NavigationMenu: FC = ({ tabId }) => {
const disableLinks = isFullLicense() === false;
- return (
-
-
-
-
-
-
-
- );
+ return ;
};
diff --git a/x-pack/plugins/reporting/public/management/report_listing.tsx b/x-pack/plugins/reporting/public/management/report_listing.tsx
index fffa952be6cb43..0b6ece4d8bd021 100644
--- a/x-pack/plugins/reporting/public/management/report_listing.tsx
+++ b/x-pack/plugins/reporting/public/management/report_listing.tsx
@@ -135,6 +135,7 @@ class ReportListingUi extends Component {
return (
<>
}
diff --git a/x-pack/plugins/saved_objects_tagging/public/management/tag_management_page.tsx b/x-pack/plugins/saved_objects_tagging/public/management/tag_management_page.tsx
index e671bc587cf1f9..e36d1a8afc08f3 100644
--- a/x-pack/plugins/saved_objects_tagging/public/management/tag_management_page.tsx
+++ b/x-pack/plugins/saved_objects_tagging/public/management/tag_management_page.tsx
@@ -11,6 +11,7 @@ import useMount from 'react-use/lib/useMount';
import { Query } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { ChromeBreadcrumb, CoreStart } from 'src/core/public';
+import { EuiSpacer } from '@elastic/eui';
import { TagWithRelations, TagsCapabilities } from '../../common';
import { getCreateModalOpener } from '../components/edition_modal';
import { ITagInternalClient, ITagAssignmentService, ITagsCache } from '../services';
@@ -194,6 +195,7 @@ export const TagManagementPage: FC = ({
return (
<>
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+ <>
+
-
-
-
-
- {tabs.map((tab) => (
- onSectionChange(tab.id)}
- isSelected={tab.id === section}
- key={tab.id}
- data-test-subj={`${tab.id}Tab`}
- >
- {tab.name}
-
- ))}
-
-
-
-
-
-
-
- {canShowActions && (
-
- )}
+
+ }
+ rightSideItems={[
+
+
+ ,
+ ]}
+ description={
+
+ }
+ tabs={tabs.map((tab) => ({
+ label: tab.name,
+ onClick: () => onSectionChange(tab.id),
+ isSelected: tab.id === section,
+ key: tab.id,
+ 'data-test-subj': `${tab.id}Tab`,
+ }))}
+ />
+
+
+
+
+
+
+ {canShowActions && (
-
-
-
-
-
+ )}
+
+
+
+
+ >
);
};
diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_details.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_details.tsx
index 3e411913520adc..02aa5f9b0b828f 100644
--- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_details.tsx
+++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_details.tsx
@@ -10,8 +10,6 @@ import React, { useState, useEffect, useReducer } from 'react';
import { keyBy } from 'lodash';
import { useHistory } from 'react-router-dom';
import {
- EuiPageBody,
- EuiPageContent,
EuiPageHeader,
EuiText,
EuiFlexGroup,
@@ -149,213 +147,211 @@ export const AlertDetails: React.FunctionComponent = ({
: [];
return (
-
-
-
+
+ }
+ rightSideItems={[
+ ,
+
- }
- rightSideItems={[
- ,
-
-
- ,
- ...rightPageHeaderButtons,
- ]}
- />
-
-
-
-
-
-
-
-
-
-
- {alertType.name}
-
-
- {uniqueActions && uniqueActions.length ? (
- <>
-
-
-
-
-
-
-
- {uniqueActions.map((action, index) => (
-
-
- {actionTypesByTypeId[action].name ?? action}
-
-
- ))}
-
- >
- ) : null}
-
-
-
-
-
- {
- if (isEnabled) {
- setIsEnabled(false);
- await disableAlert(alert);
- // Reset dismiss if previously clicked
- setDissmissAlertErrors(false);
- } else {
- setIsEnabled(true);
- await enableAlert(alert);
- }
- requestRefresh();
- }}
- label={
-
+ ,
+ ...rightPageHeaderButtons,
+ ]}
+ />
+
+
+
+
+
+
+
+
+
+
+ {alertType.name}
+
+
+ {uniqueActions && uniqueActions.length ? (
+ <>
+
+
+
+
+
+
+
+ {uniqueActions.map((action, index) => (
+
+
+ {actionTypesByTypeId[action].name ?? action}
+
+
+ ))}
+
+ >
+ ) : null}
+
+
+
+
+
+ {
+ if (isEnabled) {
+ setIsEnabled(false);
+ await disableAlert(alert);
+ // Reset dismiss if previously clicked
+ setDissmissAlertErrors(false);
+ } else {
+ setIsEnabled(true);
+ await enableAlert(alert);
}
- />
-
-
- {
- if (isMuted) {
- setIsMuted(false);
- await unmuteAlert(alert);
- } else {
- setIsMuted(true);
- await muteAlert(alert);
- }
- requestRefresh();
- }}
- label={
+ requestRefresh();
+ }}
+ label={
+
+ }
+ />
+
+
+ {
+ if (isMuted) {
+ setIsMuted(false);
+ await unmuteAlert(alert);
+ } else {
+ setIsMuted(true);
+ await muteAlert(alert);
+ }
+ requestRefresh();
+ }}
+ label={
+
+ }
+ />
+
+
+
+
+ {alert.enabled && !dissmissAlertErrors && alert.executionStatus.status === 'error' ? (
+
+
+
+
+ {alert.executionStatus.error?.message}
+
+
+
+
+ setDissmissAlertErrors(true)}
+ >
- }
- />
-
-
-
-
- {alert.enabled && !dissmissAlertErrors && alert.executionStatus.status === 'error' ? (
-
-
-
-
- {alert.executionStatus.error?.message}
-
-
-
+
+
+ {alert.executionStatus.error?.reason ===
+ AlertExecutionStatusErrorReasons.License && (
- setDissmissAlertErrors(true)}
+ target="_blank"
>
-
+
- {alert.executionStatus.error?.reason ===
- AlertExecutionStatusErrorReasons.License && (
-
-
-
-
-
- )}
-
-
-
-
- ) : null}
-
-
- {alert.enabled ? (
-
- ) : (
- <>
-
-
-
-
-
-
- >
- )}
+ )}
+
+
-
-
-
+ ) : null}
+
+
+ {alert.enabled ? (
+
+ ) : (
+ <>
+
+
+
+
+
+
+ >
+ )}
+
+
+
+ >
);
};
diff --git a/x-pack/test/functional/services/ml/data_visualizer_file_based.ts b/x-pack/test/functional/services/ml/data_visualizer_file_based.ts
index 5eece4057ac0c7..291e5a8964553f 100644
--- a/x-pack/test/functional/services/ml/data_visualizer_file_based.ts
+++ b/x-pack/test/functional/services/ml/data_visualizer_file_based.ts
@@ -10,6 +10,8 @@ import expect from '@kbn/expect';
import { FtrProviderContext } from '../../ftr_provider_context';
import { MlCommonUI } from './common_ui';
+const fixedFooterHeight = 72; // Size of EuiBottomBar more or less
+
export function MachineLearningDataVisualizerFileBasedProvider(
{ getService, getPageObjects }: FtrProviderContext,
mlCommonUI: MlCommonUI
@@ -131,7 +133,9 @@ export function MachineLearningDataVisualizerFileBasedProvider(
},
async selectCreateFilebeatConfig() {
- await testSubjects.scrollIntoView('fileDataVisFilebeatConfigLink');
+ await testSubjects.scrollIntoView('fileDataVisFilebeatConfigLink', {
+ bottomOffset: fixedFooterHeight,
+ });
await testSubjects.click('fileDataVisFilebeatConfigLink');
await testSubjects.existOrFail('fileDataVisFilebeatConfigPanel');
},
From 71a57454c7f435b9e2d4e4dc26de578370802d19 Mon Sep 17 00:00:00 2001
From: debadair
Date: Tue, 29 Jun 2021 18:03:36 -0700
Subject: [PATCH 022/121] [DOCS] Update xrefs to units sections in the ES guide
(#103809)
---
docs/settings/reporting-settings.asciidoc | 12 ++++++------
1 file changed, 6 insertions(+), 6 deletions(-)
diff --git a/docs/settings/reporting-settings.asciidoc b/docs/settings/reporting-settings.asciidoc
index 8e870ceec8947b..b1948dbf630dda 100644
--- a/docs/settings/reporting-settings.asciidoc
+++ b/docs/settings/reporting-settings.asciidoc
@@ -109,7 +109,7 @@ security is enabled, <
Date: Tue, 29 Jun 2021 22:11:09 -0300
Subject: [PATCH 023/121] Port PR 3746 from ent-search (#103765)
* Port the changes as is with no modifications
* Fix accessibility errors
* Rename variable
* Fix Stylelint issues and remove unused CSS
* Extract getAsLocalDatetimeString as a util function and use it everywhere
* Update backend schema
Also replace schema.maybe with schema.nullable. Previously assigning
"Leave unassigned" value to subtitle and description caused a server error,
because we were receiving null for these values that server did not expect.
* Update exampleResult mock
* Add tests for DisplaySettingsLogic
* Add tests for ExampleSearchResultGroup
* Add tests for ExampleStandoutResult
* Add tests for SearchResults
* Add missed null fallback type
---
.../__mocks__/content_sources.mock.ts | 4 +
.../applications/workplace_search/types.ts | 4 +
.../get_as_local_datetime_string.test.ts | 22 +++++
.../utils/get_as_local_datetime_string.ts | 11 +++
.../workplace_search/utils/index.ts | 1 +
.../display_settings/display_settings.scss | 39 ++++++++
.../display_settings_logic.test.ts | 78 +++++++++++++++
.../display_settings_logic.ts | 65 ++++++++++++-
.../example_result_detail_card.tsx | 6 +-
.../example_search_result_group.test.tsx | 22 +++++
.../example_search_result_group.tsx | 70 +++++++++++++-
.../example_standout_result.test.tsx | 22 +++++
.../example_standout_result.tsx | 65 ++++++++++++-
.../display_settings/search_results.test.tsx | 60 ++++++++++++
.../display_settings/search_results.tsx | 96 ++++++++++++++++++-
.../server/routes/workplace_search/sources.ts | 8 +-
16 files changed, 561 insertions(+), 12 deletions(-)
create mode 100644 x-pack/plugins/enterprise_search/public/applications/workplace_search/utils/get_as_local_datetime_string.test.ts
create mode 100644 x-pack/plugins/enterprise_search/public/applications/workplace_search/utils/get_as_local_datetime_string.ts
diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/__mocks__/content_sources.mock.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/__mocks__/content_sources.mock.ts
index 1d04504cdcbc5c..748dc6a7cbcf88 100644
--- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/__mocks__/content_sources.mock.ts
+++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/__mocks__/content_sources.mock.ts
@@ -305,6 +305,10 @@ export const exampleResult = {
urlField: 'myLink',
color: '#e3e3e3',
descriptionField: 'about',
+ typeField: 'otherType',
+ mediaTypeField: 'otherMediaType',
+ createdByField: 'otherCreatedBy',
+ updatedByField: 'otherUpdatedBy',
detailFields: [
{ fieldName: 'cats', label: 'Felines' },
{ fieldName: 'dogs', label: 'Canines' },
diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/types.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/types.ts
index bce778f90436c8..edc772b369558b 100644
--- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/types.ts
+++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/types.ts
@@ -204,6 +204,10 @@ export interface SearchResultConfig {
titleField: string | null;
subtitleField: string | null;
descriptionField: string | null;
+ typeField: string | null;
+ mediaTypeField: string | null;
+ createdByField: string | null;
+ updatedByField: string | null;
urlField: string | null;
color: string;
detailFields: DetailField[];
diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/utils/get_as_local_datetime_string.test.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/utils/get_as_local_datetime_string.test.ts
new file mode 100644
index 00000000000000..6475df7f4c3996
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/utils/get_as_local_datetime_string.test.ts
@@ -0,0 +1,22 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { getAsLocalDateTimeString } from './';
+
+describe('getAsLocalDateTimeString', () => {
+ it('returns localized date if string can be parsed as date', () => {
+ const date = '2021-06-28';
+
+ expect(getAsLocalDateTimeString(date)).toEqual(new Date(Date.parse(date)).toLocaleString());
+ });
+
+ it('returns null if string cannot be parsed as date', () => {
+ const date = 'foo';
+
+ expect(getAsLocalDateTimeString(date)).toEqual(null);
+ });
+});
diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/utils/get_as_local_datetime_string.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/utils/get_as_local_datetime_string.ts
new file mode 100644
index 00000000000000..d5ceb50d4c9af7
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/utils/get_as_local_datetime_string.ts
@@ -0,0 +1,11 @@
+/*
+ * 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 const getAsLocalDateTimeString = (str: string) => {
+ const dateValue = Date.parse(str);
+ return dateValue ? new Date(dateValue).toLocaleString() : null;
+};
diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/utils/index.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/utils/index.ts
index 37228cf9e7025d..2d15d6ce407b4f 100644
--- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/utils/index.ts
+++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/utils/index.ts
@@ -6,3 +6,4 @@
*/
export { toSentenceSerial } from './to_sentence_serial';
+export { getAsLocalDateTimeString } from './get_as_local_datetime_string';
diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/display_settings.scss b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/display_settings.scss
index 027f36a5c5518a..e5b680c5edec3d 100644
--- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/display_settings.scss
+++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/display_settings.scss
@@ -22,6 +22,23 @@
0 0 20px $euiColorLightestShade;
}
+@mixin searchResultTag {
+ height: 20px;
+ border-radius: 2px;
+ display: inline-flex;
+ align-items: center;
+ padding: 0 .25rem;
+ background: #E9EDF2;
+ color: #647487;
+ font-size: 10px;
+ font-weight: 600;
+ text-transform: uppercase;
+ letter-spacing: .075em;
+ white-space: nowrap;
+ z-index: 1;
+ margin-right: 5px;
+}
+
// Wrapper
.custom-source-display-settings {
font-size: 16px;
@@ -73,6 +90,28 @@
color: $euiColorDarkShade;
}
}
+
+ &__tag {
+ @include searchResultTag;
+ }
+
+ &__tag-content {
+ display: inline-flex;
+ height: 20px;
+ flex-shrink: 0;
+ align-items: center;
+ }
+
+ &__meta {
+ position: relative;
+ z-index: 1;
+ display: flex;
+ flex-flow: row nowrap;
+ align-items: center;
+ margin-top: .5rem;
+ font-size: .8em;
+ overflow: hidden;
+ }
}
.example-result-content-placeholder {
diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/display_settings_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/display_settings_logic.test.ts
index 10c715c80b3d66..3e409fb9f61446 100644
--- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/display_settings_logic.test.ts
+++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/display_settings_logic.test.ts
@@ -52,6 +52,10 @@ describe('DisplaySettingsLogic', () => {
urlFieldHover: false,
subtitleFieldHover: false,
descriptionFieldHover: false,
+ typeFieldHover: false,
+ mediaTypeFieldHover: false,
+ createdByFieldHover: false,
+ updatedByFieldHover: false,
fieldOptions: [],
optionalFieldOptions: [
{
@@ -182,6 +186,50 @@ describe('DisplaySettingsLogic', () => {
});
});
+ it('setTypeField', () => {
+ const TYPE = 'new type';
+ DisplaySettingsLogic.actions.setServerResponseData(serverProps);
+ DisplaySettingsLogic.actions.setTypeField(TYPE);
+
+ expect(DisplaySettingsLogic.values.searchResultConfig).toEqual({
+ ...searchResultConfig,
+ typeField: TYPE,
+ });
+ });
+
+ it('setMediaTypeField', () => {
+ const MEDIA_TYPE = 'new media type';
+ DisplaySettingsLogic.actions.setServerResponseData(serverProps);
+ DisplaySettingsLogic.actions.setMediaTypeField(MEDIA_TYPE);
+
+ expect(DisplaySettingsLogic.values.searchResultConfig).toEqual({
+ ...searchResultConfig,
+ mediaTypeField: MEDIA_TYPE,
+ });
+ });
+
+ it('setCreatedByField', () => {
+ const CREATED_BY = 'new created by';
+ DisplaySettingsLogic.actions.setServerResponseData(serverProps);
+ DisplaySettingsLogic.actions.setCreatedByField(CREATED_BY);
+
+ expect(DisplaySettingsLogic.values.searchResultConfig).toEqual({
+ ...searchResultConfig,
+ createdByField: CREATED_BY,
+ });
+ });
+
+ it('setUpdatedByField', () => {
+ const UPDATED_BY = 'new updated by';
+ DisplaySettingsLogic.actions.setServerResponseData(serverProps);
+ DisplaySettingsLogic.actions.setUpdatedByField(UPDATED_BY);
+
+ expect(DisplaySettingsLogic.values.searchResultConfig).toEqual({
+ ...searchResultConfig,
+ updatedByField: UPDATED_BY,
+ });
+ });
+
it('setDetailFields', () => {
const result = {
destination: {
@@ -286,6 +334,36 @@ describe('DisplaySettingsLogic', () => {
expect(DisplaySettingsLogic.values.urlFieldHover).toEqual(!defaultValues.urlFieldHover);
});
+
+ it('toggleTypeFieldHover', () => {
+ DisplaySettingsLogic.actions.toggleTypeFieldHover();
+
+ expect(DisplaySettingsLogic.values.typeFieldHover).toEqual(!defaultValues.typeFieldHover);
+ });
+
+ it('toggleMediaTypeFieldHover', () => {
+ DisplaySettingsLogic.actions.toggleMediaTypeFieldHover();
+
+ expect(DisplaySettingsLogic.values.mediaTypeFieldHover).toEqual(
+ !defaultValues.mediaTypeFieldHover
+ );
+ });
+
+ it('toggleCreatedByFieldHover', () => {
+ DisplaySettingsLogic.actions.toggleCreatedByFieldHover();
+
+ expect(DisplaySettingsLogic.values.createdByFieldHover).toEqual(
+ !defaultValues.createdByFieldHover
+ );
+ });
+
+ it('toggleUpdatedByFieldHover', () => {
+ DisplaySettingsLogic.actions.toggleUpdatedByFieldHover();
+
+ expect(DisplaySettingsLogic.values.updatedByFieldHover).toEqual(
+ !defaultValues.updatedByFieldHover
+ );
+ });
});
describe('listeners', () => {
diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/display_settings_logic.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/display_settings_logic.ts
index 38424df724bd4e..556507d891dcb7 100644
--- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/display_settings_logic.ts
+++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/display_settings_logic.ts
@@ -55,6 +55,10 @@ interface DisplaySettingsActions {
setUrlField(urlField: string): string;
setSubtitleField(subtitleField: string | null): string | null;
setDescriptionField(descriptionField: string | null): string | null;
+ setTypeField(typeField: string | null): string | null;
+ setMediaTypeField(mediaTypeField: string | null): string | null;
+ setCreatedByField(createdByField: string | null): string | null;
+ setUpdatedByField(updatedByField: string | null): string | null;
setColorField(hex: string): string;
setDetailFields(result: DropResult): { result: DropResult };
openEditDetailField(editFieldIndex: number | null): number | null;
@@ -70,6 +74,10 @@ interface DisplaySettingsActions {
toggleTitleFieldHover(): void;
toggleSubtitleFieldHover(): void;
toggleDescriptionFieldHover(): void;
+ toggleTypeFieldHover(): void;
+ toggleMediaTypeFieldHover(): void;
+ toggleCreatedByFieldHover(): void;
+ toggleUpdatedByFieldHover(): void;
toggleUrlFieldHover(): void;
}
@@ -89,6 +97,10 @@ interface DisplaySettingsValues {
urlFieldHover: boolean;
subtitleFieldHover: boolean;
descriptionFieldHover: boolean;
+ typeFieldHover: boolean;
+ mediaTypeFieldHover: boolean;
+ createdByFieldHover: boolean;
+ updatedByFieldHover: boolean;
fieldOptions: OptionValue[];
optionalFieldOptions: OptionValue[];
availableFieldOptions: OptionValue[];
@@ -100,6 +112,10 @@ export const defaultSearchResultConfig = {
subtitleField: '',
descriptionField: '',
urlField: '',
+ typeField: '',
+ mediaTypeField: '',
+ createdByField: '',
+ updatedByField: '',
color: '#000000',
detailFields: [],
};
@@ -115,7 +131,11 @@ export const DisplaySettingsLogic = kea<
setTitleField: (titleField: string) => titleField,
setUrlField: (urlField: string) => urlField,
setSubtitleField: (subtitleField: string | null) => subtitleField,
- setDescriptionField: (descriptionField: string) => descriptionField,
+ setDescriptionField: (descriptionField: string | null) => descriptionField,
+ setTypeField: (typeField: string | null) => typeField,
+ setMediaTypeField: (mediaTypeField: string | null) => mediaTypeField,
+ setCreatedByField: (createdByField: string | null) => createdByField,
+ setUpdatedByField: (updatedByField: string | null) => updatedByField,
setColorField: (hex: string) => hex,
setDetailFields: (result: DropResult) => ({ result }),
openEditDetailField: (editFieldIndex: number | null) => editFieldIndex,
@@ -128,6 +148,10 @@ export const DisplaySettingsLogic = kea<
toggleTitleFieldHover: () => true,
toggleSubtitleFieldHover: () => true,
toggleDescriptionFieldHover: () => true,
+ toggleTypeFieldHover: () => true,
+ toggleMediaTypeFieldHover: () => true,
+ toggleCreatedByFieldHover: () => true,
+ toggleUpdatedByFieldHover: () => true,
toggleUrlFieldHover: () => true,
initializeDisplaySettings: () => true,
setServerData: () => true,
@@ -181,6 +205,19 @@ export const DisplaySettingsLogic = kea<
...searchResultConfig,
descriptionField,
}),
+ setTypeField: (searchResultConfig, typeField) => ({ ...searchResultConfig, typeField }),
+ setMediaTypeField: (searchResultConfig, mediaTypeField) => ({
+ ...searchResultConfig,
+ mediaTypeField,
+ }),
+ setCreatedByField: (searchResultConfig, createdByField) => ({
+ ...searchResultConfig,
+ createdByField,
+ }),
+ setUpdatedByField: (searchResultConfig, updatedByField) => ({
+ ...searchResultConfig,
+ updatedByField,
+ }),
setColorField: (searchResultConfig, color) => ({ ...searchResultConfig, color }),
setDetailFields: (searchResultConfig, { result: { destination, source } }) => {
const detailFields = cloneDeep(searchResultConfig.detailFields);
@@ -273,7 +310,31 @@ export const DisplaySettingsLogic = kea<
descriptionFieldHover: [
false,
{
- toggleDescriptionFieldHover: (addFieldModalVisible) => !addFieldModalVisible,
+ toggleDescriptionFieldHover: (descriptionFieldHover) => !descriptionFieldHover,
+ },
+ ],
+ typeFieldHover: [
+ false,
+ {
+ toggleTypeFieldHover: (typeFieldHover) => !typeFieldHover,
+ },
+ ],
+ mediaTypeFieldHover: [
+ false,
+ {
+ toggleMediaTypeFieldHover: (mediaTypeFieldHover) => !mediaTypeFieldHover,
+ },
+ ],
+ createdByFieldHover: [
+ false,
+ {
+ toggleCreatedByFieldHover: (createdByFieldHover) => !createdByFieldHover,
+ },
+ ],
+ updatedByFieldHover: [
+ false,
+ {
+ toggleUpdatedByFieldHover: (updatedByFieldHover) => !updatedByFieldHover,
},
],
},
diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/example_result_detail_card.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/example_result_detail_card.tsx
index c3d56949d0fe7d..eef508b2e618f4 100644
--- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/example_result_detail_card.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/example_result_detail_card.tsx
@@ -13,16 +13,12 @@ import { useValues } from 'kea';
import { EuiFlexGroup, EuiFlexItem, EuiSpacer, EuiText, EuiTitle } from '@elastic/eui';
import { URL_LABEL } from '../../../../constants';
+import { getAsLocalDateTimeString } from '../../../../utils';
import { CustomSourceIcon } from './custom_source_icon';
import { DisplaySettingsLogic } from './display_settings_logic';
import { TitleField } from './title_field';
-const getAsLocalDateTimeString = (str: string) => {
- const dateValue = Date.parse(str);
- return dateValue ? new Date(dateValue).toLocaleString() : null;
-};
-
export const ExampleResultDetailCard: React.FC = () => {
const {
sourceName,
diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/example_search_result_group.test.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/example_search_result_group.test.tsx
index 7139ea30be137a..1835d8485664e4 100644
--- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/example_search_result_group.test.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/example_search_result_group.test.tsx
@@ -41,4 +41,26 @@ describe('ExampleSearchResultGroup', () => {
expect(wrapper.find('[data-test-subj="DefaultDescriptionLabel"]')).toHaveLength(1);
});
+
+ it('renders optional fields if they exist in result', () => {
+ setMockValues({
+ ...exampleResult,
+ exampleDocuments: [
+ {
+ myLink: 'http://foo',
+ otherTitle: 'foo',
+ otherType: 'File',
+ otherMediaType: 'PDF',
+ otherCreatedBy: 'bar',
+ otherUpdatedBy: 'baz',
+ },
+ ],
+ });
+ const wrapper = shallow( );
+
+ expect(wrapper.find('[data-test-subj="CreatedByField"]')).toHaveLength(1);
+ expect(wrapper.find('[data-test-subj="UpdatedByField"]')).toHaveLength(1);
+ expect(wrapper.find('[data-test-subj="TypeField"]')).toHaveLength(1);
+ expect(wrapper.find('[data-test-subj="MediaTypeField"]')).toHaveLength(1);
+ });
});
diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/example_search_result_group.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/example_search_result_group.tsx
index df89eed38ae92d..95a62b06515ce7 100644
--- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/example_search_result_group.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/example_search_result_group.tsx
@@ -13,6 +13,7 @@ import { useValues } from 'kea';
import { isColorDark, hexToRgb } from '@elastic/eui';
import { DESCRIPTION_LABEL } from '../../../../constants';
+import { getAsLocalDateTimeString } from '../../../../utils';
import { CustomSourceIcon } from './custom_source_icon';
import { DisplaySettingsLogic } from './display_settings_logic';
@@ -22,10 +23,23 @@ import { TitleField } from './title_field';
export const ExampleSearchResultGroup: React.FC = () => {
const {
sourceName,
- searchResultConfig: { titleField, subtitleField, descriptionField, color },
+ searchResultConfig: {
+ titleField,
+ subtitleField,
+ descriptionField,
+ typeField,
+ mediaTypeField,
+ createdByField,
+ updatedByField,
+ color,
+ },
titleFieldHover,
subtitleFieldHover,
descriptionFieldHover,
+ typeFieldHover,
+ mediaTypeFieldHover,
+ createdByFieldHover,
+ updatedByFieldHover,
exampleDocuments,
} = useValues(DisplaySettingsLogic);
@@ -72,6 +86,60 @@ export const ExampleSearchResultGroup: React.FC = () => {
)}
+ {createdByField && result[createdByField] && (
+
+ Created by {result[createdByField]}
+
+ )}
+
+ {typeField && result[typeField] && (
+
+
+ {result[typeField]}
+
+
+ )}
+ {mediaTypeField && result[mediaTypeField] && (
+
+
+ {result[mediaTypeField]}
+
+
+ )}
+
+
+ Last updated
+ {updatedByField && result[updatedByField] && (
+
+ {' '}
+ by {result[updatedByField]}
+
+ )}
+ {getAsLocalDateTimeString(result.last_updated as string) ||
+ result.last_updated}
+
+
+
))}
diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/example_standout_result.test.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/example_standout_result.test.tsx
index a38e0ce82490d6..0c40db0c5a8ea0 100644
--- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/example_standout_result.test.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/example_standout_result.test.tsx
@@ -41,4 +41,26 @@ describe('ExampleStandoutResult', () => {
expect(wrapper.find('[data-test-subj="DefaultDescriptionLabel"]')).toHaveLength(1);
});
+
+ it('renders optional fields if they exist in result', () => {
+ setMockValues({
+ ...exampleResult,
+ exampleDocuments: [
+ {
+ myLink: 'http://foo',
+ otherTitle: 'foo',
+ otherType: 'File',
+ otherMediaType: 'PDF',
+ otherCreatedBy: 'bar',
+ otherUpdatedBy: 'baz',
+ },
+ ],
+ });
+ const wrapper = shallow( );
+
+ expect(wrapper.find('[data-test-subj="CreatedByField"]')).toHaveLength(1);
+ expect(wrapper.find('[data-test-subj="UpdatedByField"]')).toHaveLength(1);
+ expect(wrapper.find('[data-test-subj="TypeField"]')).toHaveLength(1);
+ expect(wrapper.find('[data-test-subj="MediaTypeField"]')).toHaveLength(1);
+ });
});
diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/example_standout_result.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/example_standout_result.tsx
index 48c3149e622bd9..b6aa180eb65de4 100644
--- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/example_standout_result.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/example_standout_result.tsx
@@ -13,6 +13,7 @@ import { useValues } from 'kea';
import { isColorDark, hexToRgb } from '@elastic/eui';
import { DESCRIPTION_LABEL } from '../../../../constants';
+import { getAsLocalDateTimeString } from '../../../../utils';
import { CustomSourceIcon } from './custom_source_icon';
import { DisplaySettingsLogic } from './display_settings_logic';
@@ -22,10 +23,23 @@ import { TitleField } from './title_field';
export const ExampleStandoutResult: React.FC = () => {
const {
sourceName,
- searchResultConfig: { titleField, subtitleField, descriptionField, color },
+ searchResultConfig: {
+ titleField,
+ subtitleField,
+ descriptionField,
+ typeField,
+ mediaTypeField,
+ createdByField,
+ updatedByField,
+ color,
+ },
titleFieldHover,
subtitleFieldHover,
descriptionFieldHover,
+ typeFieldHover,
+ mediaTypeFieldHover,
+ createdByFieldHover,
+ updatedByFieldHover,
exampleDocuments,
} = useValues(DisplaySettingsLogic);
@@ -66,6 +80,55 @@ export const ExampleStandoutResult: React.FC = () => {
)}
+ {createdByField && result[createdByField] && (
+
+ Created by {result[createdByField]}
+
+ )}
+
+ {typeField && result[typeField] && (
+
+ {result[typeField]}
+
+ )}
+ {mediaTypeField && result[mediaTypeField] && (
+
+ {result[mediaTypeField]}
+
+ )}
+
+
+ Last updated
+ {updatedByField && result[updatedByField] && (
+
+ {' '}
+ by {result[updatedByField]}
+
+ )}
+ {getAsLocalDateTimeString(result.last_updated as string) || result.last_updated}
+
+
+
diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/search_results.test.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/search_results.test.tsx
index cc0378b4b70db0..24ecb94df2aaaa 100644
--- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/search_results.test.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/search_results.test.tsx
@@ -41,6 +41,10 @@ describe('SearchResults', () => {
const setDescriptionField = jest.fn();
const setUrlField = jest.fn();
const setColorField = jest.fn();
+ const setTypeField = jest.fn();
+ const setMediaTypeField = jest.fn();
+ const setCreatedByField = jest.fn();
+ const setUpdatedByField = jest.fn();
beforeEach(() => {
setMockActions({
@@ -52,6 +56,10 @@ describe('SearchResults', () => {
setDescriptionField,
setUrlField,
setColorField,
+ setTypeField,
+ setMediaTypeField,
+ setCreatedByField,
+ setUpdatedByField,
});
setMockValues({
searchResultConfig,
@@ -103,6 +111,42 @@ describe('SearchResults', () => {
expect(setDescriptionField).toHaveBeenCalledWith(searchResultConfig.descriptionField);
});
+ it('calls setTypeField on change', () => {
+ const wrapper = shallow( );
+ wrapper
+ .find('[data-test-subj="TypeFieldSelect"]')
+ .simulate('change', { target: { value: searchResultConfig.typeField } });
+
+ expect(setTypeField).toHaveBeenCalledWith(searchResultConfig.typeField);
+ });
+
+ it('calls setMediaTypeField on change', () => {
+ const wrapper = shallow( );
+ wrapper
+ .find('[data-test-subj="MediaTypeFieldSelect"]')
+ .simulate('change', { target: { value: searchResultConfig.mediaTypeField } });
+
+ expect(setMediaTypeField).toHaveBeenCalledWith(searchResultConfig.mediaTypeField);
+ });
+
+ it('calls setCreatedByField on change', () => {
+ const wrapper = shallow( );
+ wrapper
+ .find('[data-test-subj="CreatedByFieldSelect"]')
+ .simulate('change', { target: { value: searchResultConfig.createdByField } });
+
+ expect(setCreatedByField).toHaveBeenCalledWith(searchResultConfig.createdByField);
+ });
+
+ it('calls setUpdatedByField on change', () => {
+ const wrapper = shallow( );
+ wrapper
+ .find('[data-test-subj="UpdatedByFieldSelect"]')
+ .simulate('change', { target: { value: searchResultConfig.updatedByField } });
+
+ expect(setUpdatedByField).toHaveBeenCalledWith(searchResultConfig.updatedByField);
+ });
+
it('handles blank fallbacks', () => {
setMockValues({
searchResultConfig: { detailFields: [] },
@@ -116,9 +160,25 @@ describe('SearchResults', () => {
wrapper
.find('[data-test-subj="DescriptionFieldSelect"]')
.simulate('change', { target: { value: LEAVE_UNASSIGNED_FIELD } });
+ wrapper
+ .find('[data-test-subj="TypeFieldSelect"]')
+ .simulate('change', { target: { value: LEAVE_UNASSIGNED_FIELD } });
+ wrapper
+ .find('[data-test-subj="MediaTypeFieldSelect"]')
+ .simulate('change', { target: { value: LEAVE_UNASSIGNED_FIELD } });
+ wrapper
+ .find('[data-test-subj="CreatedByFieldSelect"]')
+ .simulate('change', { target: { value: LEAVE_UNASSIGNED_FIELD } });
+ wrapper
+ .find('[data-test-subj="UpdatedByFieldSelect"]')
+ .simulate('change', { target: { value: LEAVE_UNASSIGNED_FIELD } });
expect(wrapper.find('[data-test-subj="UrlFieldSelect"]').prop('value')).toEqual('');
expect(setSubtitleField).toHaveBeenCalledWith(null);
expect(setDescriptionField).toHaveBeenCalledWith(null);
+ expect(setTypeField).toHaveBeenCalledWith(null);
+ expect(setMediaTypeField).toHaveBeenCalledWith(null);
+ expect(setCreatedByField).toHaveBeenCalledWith(null);
+ expect(setUpdatedByField).toHaveBeenCalledWith(null);
});
});
diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/search_results.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/search_results.tsx
index 859fb2d5d2a206..a6a0fcda0dd670 100644
--- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/search_results.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/search_results.tsx
@@ -42,15 +42,33 @@ export const SearchResults: React.FC = () => {
toggleTitleFieldHover,
toggleSubtitleFieldHover,
toggleDescriptionFieldHover,
+ toggleTypeFieldHover,
+ toggleMediaTypeFieldHover,
+ toggleCreatedByFieldHover,
+ toggleUpdatedByFieldHover,
setTitleField,
setSubtitleField,
setDescriptionField,
+ setTypeField,
+ setMediaTypeField,
+ setCreatedByField,
+ setUpdatedByField,
setUrlField,
setColorField,
} = useActions(DisplaySettingsLogic);
const {
- searchResultConfig: { titleField, descriptionField, subtitleField, urlField, color },
+ searchResultConfig: {
+ titleField,
+ descriptionField,
+ subtitleField,
+ typeField,
+ mediaTypeField,
+ createdByField,
+ updatedByField,
+ urlField,
+ color,
+ },
fieldOptions,
optionalFieldOptions,
} = useValues(DisplaySettingsLogic);
@@ -136,6 +154,82 @@ export const SearchResults: React.FC = () => {
}
/>
+
+
+ setTypeField(value === LEAVE_UNASSIGNED_FIELD ? null : value)
+ }
+ />
+
+
+
+ setMediaTypeField(value === LEAVE_UNASSIGNED_FIELD ? null : value)
+ }
+ />
+
+
+
+ setCreatedByField(value === LEAVE_UNASSIGNED_FIELD ? null : value)
+ }
+ />
+
+
+
+ setUpdatedByField(value === LEAVE_UNASSIGNED_FIELD ? null : value)
+ }
+ />
+
diff --git a/x-pack/plugins/enterprise_search/server/routes/workplace_search/sources.ts b/x-pack/plugins/enterprise_search/server/routes/workplace_search/sources.ts
index 044393f65dc59c..b393ab9d1f26a2 100644
--- a/x-pack/plugins/enterprise_search/server/routes/workplace_search/sources.ts
+++ b/x-pack/plugins/enterprise_search/server/routes/workplace_search/sources.ts
@@ -45,9 +45,13 @@ const displayFieldSchema = schema.object({
const displaySettingsSchema = schema.object({
titleField: schema.maybe(schema.string()),
- subtitleField: schema.maybe(schema.string()),
- descriptionField: schema.maybe(schema.string()),
+ subtitleField: schema.nullable(schema.string()),
+ descriptionField: schema.nullable(schema.string()),
urlField: schema.maybe(schema.string()),
+ typeField: schema.nullable(schema.string()),
+ mediaTypeField: schema.nullable(schema.string()),
+ createdByField: schema.nullable(schema.string()),
+ updatedByField: schema.nullable(schema.string()),
color: schema.string(),
urlFieldIsLinkable: schema.boolean(),
detailFields: schema.oneOf([schema.arrayOf(displayFieldSchema), displayFieldSchema]),
From ff475164edf68e7333f41e0acbe53440d304a23f Mon Sep 17 00:00:00 2001
From: Kevin Qualters <56408403+kqualters-elastic@users.noreply.github.com>
Date: Tue, 29 Jun 2021 21:11:58 -0400
Subject: [PATCH 024/121] Skip flaky cypress timeline test (#103779)
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
---
.../cypress/integration/timelines/row_renderers.spec.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/x-pack/plugins/security_solution/cypress/integration/timelines/row_renderers.spec.ts b/x-pack/plugins/security_solution/cypress/integration/timelines/row_renderers.spec.ts
index ed9a7db4702d02..b3103963284b4d 100644
--- a/x-pack/plugins/security_solution/cypress/integration/timelines/row_renderers.spec.ts
+++ b/x-pack/plugins/security_solution/cypress/integration/timelines/row_renderers.spec.ts
@@ -75,7 +75,7 @@ describe('Row renderers', () => {
});
});
- it('Selected renderer can be disabled with one click', () => {
+ it.skip('Selected renderer can be disabled with one click', () => {
cy.get(TIMELINE_ROW_RENDERERS_DISABLE_ALL_BTN).click({ force: true });
cy.intercept('PATCH', '/api/timeline').as('updateTimeline');
From bed5b6d8f2dd23687a17a0c2d3e636ca4ace7d29 Mon Sep 17 00:00:00 2001
From: Jonathan Budzenski
Date: Tue, 29 Jun 2021 20:16:00 -0500
Subject: [PATCH 025/121] [packages] Migrate @kbn/test to Bazel (#103122)
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
---
.../monorepo-packages.asciidoc | 1 +
jest.config.integration.js | 9 +-
package.json | 2 +-
packages/BUILD.bazel | 1 +
packages/kbn-es-archiver/package.json | 5 +-
packages/kbn-test/BUILD.bazel | 153 ++++++++++++++++++
packages/kbn-test/jest-preset.js | 35 ++--
packages/kbn-test/jest/package.json | 4 +-
packages/kbn-test/package.json | 11 +-
packages/kbn-test/scripts/build.js | 80 ---------
packages/kbn-test/src/index.ts | 2 +-
.../__fixtures__/jest.config.js | 2 +-
packages/kbn-test/tsconfig.json | 21 +--
.../actions/snapshot_policy_actions.ts | 2 +-
.../edit_role_mapping_page.test.tsx | 2 +-
.../json_rule_editor.test.tsx | 2 +-
.../rule_editor_panel.test.tsx | 2 +-
yarn.lock | 2 +-
18 files changed, 198 insertions(+), 138 deletions(-)
create mode 100644 packages/kbn-test/BUILD.bazel
delete mode 100644 packages/kbn-test/scripts/build.js
diff --git a/docs/developer/getting-started/monorepo-packages.asciidoc b/docs/developer/getting-started/monorepo-packages.asciidoc
index 7d708e17ae1161..06e87fa1300297 100644
--- a/docs/developer/getting-started/monorepo-packages.asciidoc
+++ b/docs/developer/getting-started/monorepo-packages.asciidoc
@@ -107,6 +107,7 @@ yarn kbn watch-bazel
- @kbn/std
- @kbn/storybook
- @kbn/telemetry-utils
+- @kbn/test
- @kbn/test-subj-selector
- @kbn/tinymath
- @kbn/ui-framework
diff --git a/jest.config.integration.js b/jest.config.integration.js
index b6ecb4569b643a..8ff142714eebf9 100644
--- a/jest.config.integration.js
+++ b/jest.config.integration.js
@@ -16,13 +16,12 @@ module.exports = {
testPathIgnorePatterns: preset.testPathIgnorePatterns.filter(
(pattern) => !pattern.includes('integration_tests')
),
- setupFilesAfterEnv: ['/packages/kbn-test/target/jest/setup/after_env.integration.js'],
+ setupFilesAfterEnv: [
+ '/node_modules/@kbn/test/target_node/jest/setup/after_env.integration.js',
+ ],
reporters: [
'default',
- [
- '/packages/kbn-test/target/jest/junit_reporter',
- { reportName: 'Jest Integration Tests' },
- ],
+ ['@kbn/test/target_node/jest/junit_reporter', { reportName: 'Jest Integration Tests' }],
],
coverageReporters: !!process.env.CI
? [['json', { file: 'jest-integration.json' }]]
diff --git a/package.json b/package.json
index 1111179fc816b6..99dad59044bf40 100644
--- a/package.json
+++ b/package.json
@@ -473,7 +473,7 @@
"@kbn/spec-to-console": "link:bazel-bin/packages/kbn-spec-to-console",
"@kbn/storybook": "link:bazel-bin/packages/kbn-storybook",
"@kbn/telemetry-tools": "link:bazel-bin/packages/kbn-telemetry-tools",
- "@kbn/test": "link:packages/kbn-test",
+ "@kbn/test": "link:bazel-bin/packages/kbn-test",
"@kbn/test-subj-selector": "link:bazel-bin/packages/kbn-test-subj-selector",
"@loaders.gl/polyfills": "^2.3.5",
"@microsoft/api-documenter": "7.7.2",
diff --git a/packages/BUILD.bazel b/packages/BUILD.bazel
index 38d3f28ec866b7..6ffd5ff151ac3b 100644
--- a/packages/BUILD.bazel
+++ b/packages/BUILD.bazel
@@ -52,6 +52,7 @@ filegroup(
"//packages/kbn-std:build",
"//packages/kbn-storybook:build",
"//packages/kbn-telemetry-tools:build",
+ "//packages/kbn-test:build",
"//packages/kbn-test-subj-selector:build",
"//packages/kbn-tinymath:build",
"//packages/kbn-ui-framework:build",
diff --git a/packages/kbn-es-archiver/package.json b/packages/kbn-es-archiver/package.json
index c86d94c70d7394..b55e67b82694f5 100644
--- a/packages/kbn-es-archiver/package.json
+++ b/packages/kbn-es-archiver/package.json
@@ -11,8 +11,5 @@
"scripts": {
"kbn:bootstrap": "rm -rf target && ../../node_modules/.bin/tsc",
"kbn:watch": "rm -rf target && ../../node_modules/.bin/tsc --watch"
- },
- "dependencies": {
- "@kbn/test": "link:../kbn-test"
}
-}
\ No newline at end of file
+}
diff --git a/packages/kbn-test/BUILD.bazel b/packages/kbn-test/BUILD.bazel
new file mode 100644
index 00000000000000..28c42a4c476846
--- /dev/null
+++ b/packages/kbn-test/BUILD.bazel
@@ -0,0 +1,153 @@
+load("@npm//@bazel/typescript:index.bzl", "ts_config", "ts_project")
+load("@npm//@babel/cli:index.bzl", "babel")
+load("@build_bazel_rules_nodejs//:index.bzl", "js_library", "pkg_npm")
+
+PKG_BASE_NAME = "kbn-test"
+PKG_REQUIRE_NAME = "@kbn/test"
+
+SOURCE_FILES = glob(
+ [
+ "src/**/*"
+ ],
+ exclude = [
+ "**/*.test.*",
+ "**/*.snap",
+ "**/__fixture__/**",
+ "**/__fixtures__/**",
+ "**/__snapshots__/**",
+ ]
+)
+
+SRCS = SOURCE_FILES
+
+filegroup(
+ name = "srcs",
+ srcs = SRCS,
+)
+
+NPM_MODULE_EXTRA_FILES = [
+ "jest/package.json",
+ "jest-preset.js",
+ "jest.config.js",
+ "README.md",
+ "package.json",
+]
+
+SRC_DEPS = [
+ "//packages/kbn-dev-utils",
+ "//packages/kbn-i18n",
+ "//packages/kbn-std",
+ "//packages/kbn-utils",
+ "@npm//@elastic/elasticsearch",
+ "@npm//axios",
+ "@npm//@babel/traverse",
+ "@npm//chance",
+ "@npm//del",
+ "@npm//enzyme",
+ "@npm//execa",
+ "@npm//exit-hook",
+ "@npm//form-data",
+ "@npm//globby",
+ "@npm//history",
+ "@npm//jest",
+ "@npm//jest-cli",
+ "@npm//jest-snapshot",
+ "@npm//@jest/reporters",
+ "@npm//joi",
+ "@npm//mustache",
+ "@npm//parse-link-header",
+ "@npm//prettier",
+ "@npm//react-dom",
+ "@npm//react-redux",
+ "@npm//react-router-dom",
+ "@npm//redux",
+ "@npm//rxjs",
+ "@npm//strip-ansi",
+ "@npm//xmlbuilder",
+ "@npm//xml2js",
+]
+
+TYPES_DEPS = [
+ "@npm//@types/chance",
+ "@npm//@types/enzyme",
+ "@npm//@types/history",
+ "@npm//@types/jest",
+ "@npm//@types/joi",
+ "@npm//@types/lodash",
+ "@npm//@types/mustache",
+ "@npm//@types/node",
+ "@npm//@types/parse-link-header",
+ "@npm//@types/prettier",
+ "@npm//@types/react-dom",
+ "@npm//@types/react-redux",
+ "@npm//@types/react-router-dom",
+ "@npm//@types/xml2js",
+]
+
+DEPS = SRC_DEPS + TYPES_DEPS
+
+babel(
+ name = "target_node",
+ data = DEPS + [
+ ":srcs",
+ "babel.config.js",
+ ],
+ output_dir = True,
+ # the following arg paths includes $(execpath) as babel runs on the sandbox root
+ args = [
+ "./%s/src" % package_name(),
+ "--config-file",
+ "./%s/babel.config.js" % package_name(),
+ "--out-dir",
+ "$(@D)",
+ "--extensions",
+ ".ts,.js,.tsx",
+ "--quiet"
+ ],
+)
+
+ts_config(
+ name = "tsconfig",
+ src = "tsconfig.json",
+ deps = [
+ "//:tsconfig.base.json",
+ ],
+)
+
+ts_project(
+ name = "tsc",
+ args = ['--pretty'],
+ srcs = SRCS,
+ deps = DEPS,
+ declaration = True,
+ declaration_map = True,
+ declaration_dir = "target_types",
+ emit_declaration_only = True,
+ incremental = True,
+ source_map = True,
+ root_dir = "src",
+ tsconfig = ":tsconfig",
+)
+
+js_library(
+ name = PKG_BASE_NAME,
+ srcs = NPM_MODULE_EXTRA_FILES,
+ deps = DEPS + [":target_node", ":tsc"],
+ package_name = PKG_REQUIRE_NAME,
+ visibility = ["//visibility:public"],
+)
+
+pkg_npm(
+ name = "npm_module",
+ deps = [
+ ":%s" % PKG_BASE_NAME,
+ ]
+)
+
+filegroup(
+ name = "build",
+ srcs = [
+ ":npm_module",
+ ],
+ visibility = ["//visibility:public"],
+)
diff --git a/packages/kbn-test/jest-preset.js b/packages/kbn-test/jest-preset.js
index 5baff607704c78..c84fe3f7a55b05 100644
--- a/packages/kbn-test/jest-preset.js
+++ b/packages/kbn-test/jest-preset.js
@@ -9,8 +9,6 @@
// For a detailed explanation regarding each configuration property, visit:
// https://jestjs.io/docs/en/configuration.html
-const { resolve } = require('path');
-
module.exports = {
// The directory where Jest should output its coverage files
coverageDirectory: '/target/kibana-coverage/jest',
@@ -30,13 +28,16 @@ module.exports = {
moduleNameMapper: {
'@elastic/eui/lib/(.*)?': '/node_modules/@elastic/eui/test-env/$1',
'@elastic/eui$': '/node_modules/@elastic/eui/test-env',
- '\\.module.(css|scss)$': '/packages/kbn-test/target/jest/mocks/css_module_mock.js',
- '\\.(css|less|scss)$': '/packages/kbn-test/target/jest/mocks/style_mock.js',
+ '\\.module.(css|scss)$':
+ '/node_modules/@kbn/test/target_node/jest/mocks/css_module_mock.js',
+ '\\.(css|less|scss)$': '/node_modules/@kbn/test/target_node/jest/mocks/style_mock.js',
'\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$':
- '/packages/kbn-test/target/jest/mocks/file_mock.js',
- '\\.ace\\.worker.js$': '/packages/kbn-test/target/jest/mocks/worker_module_mock.js',
- '\\.editor\\.worker.js$': '/packages/kbn-test/target/jest/mocks/worker_module_mock.js',
- '^(!!)?file-loader!': '/packages/kbn-test/target/jest/mocks/file_mock.js',
+ '/node_modules/@kbn/test/target_node/jest/mocks/file_mock.js',
+ '\\.ace\\.worker.js$':
+ '/node_modules/@kbn/test/target_node/jest/mocks/worker_module_mock.js',
+ '\\.editor\\.worker.js$':
+ '/node_modules/@kbn/test/target_node/jest/mocks/worker_module_mock.js',
+ '^(!!)?file-loader!': '/node_modules/@kbn/test/target_node/jest/mocks/file_mock.js',
'^src/core/(.*)': '/src/core/$1',
'^src/plugins/(.*)': '/src/plugins/$1',
},
@@ -45,20 +46,20 @@ module.exports = {
modulePathIgnorePatterns: ['__fixtures__/', 'target/'],
// Use this configuration option to add custom reporters to Jest
- reporters: ['default', resolve(__dirname, './target/jest/junit_reporter')],
+ reporters: ['default', '@kbn/test/target_node/jest/junit_reporter'],
// The paths to modules that run some code to configure or set up the testing environment before each test
setupFiles: [
- '/packages/kbn-test/target/jest/setup/babel_polyfill.js',
- '/packages/kbn-test/target/jest/setup/polyfills.js',
- '/packages/kbn-test/target/jest/setup/enzyme.js',
+ '/node_modules/@kbn/test/target_node/jest/setup/babel_polyfill.js',
+ '/node_modules/@kbn/test/target_node/jest/setup/polyfills.js',
+ '/node_modules/@kbn/test/target_node/jest/setup/enzyme.js',
],
// A list of paths to modules that run some code to configure or set up the testing framework before each test
setupFilesAfterEnv: [
- '/packages/kbn-test/target/jest/setup/setup_test.js',
- '/packages/kbn-test/target/jest/setup/mocks.js',
- '/packages/kbn-test/target/jest/setup/react_testing_library.js',
+ '/node_modules/@kbn/test/target_node/jest/setup/setup_test.js',
+ '/node_modules/@kbn/test/target_node/jest/setup/mocks.js',
+ '/node_modules/@kbn/test/target_node/jest/setup/react_testing_library.js',
],
// A list of paths to snapshot serializer modules Jest should use for snapshot testing
@@ -85,7 +86,7 @@ module.exports = {
// A map from regular expressions to paths to transformers
transform: {
- '^.+\\.(js|tsx?)$': '/packages/kbn-test/target/jest/babel_transform.js',
+ '^.+\\.(js|tsx?)$': '/node_modules/@kbn/test/target_node/jest/babel_transform.js',
'^.+\\.txt?$': 'jest-raw-loader',
'^.+\\.html?$': 'jest-raw-loader',
},
@@ -109,5 +110,5 @@ module.exports = {
],
// A custom resolver to preserve symlinks by default
- resolver: '/packages/kbn-test/target/jest/setup/preserve_symlinks_resolver.js',
+ resolver: '/node_modules/@kbn/test/target_node/jest/setup/preserve_symlinks_resolver.js',
};
diff --git a/packages/kbn-test/jest/package.json b/packages/kbn-test/jest/package.json
index c8b50f7b1b5ba6..aa0ba838736845 100644
--- a/packages/kbn-test/jest/package.json
+++ b/packages/kbn-test/jest/package.json
@@ -1,4 +1,4 @@
{
- "main": "../target/jest",
- "types": "../target/types/jest/index.d.ts"
+ "main": "../target_node/jest",
+ "types": "../target_types/jest/index.d.ts"
}
diff --git a/packages/kbn-test/package.json b/packages/kbn-test/package.json
index aaff513f1591f2..c937d1e0be85c1 100644
--- a/packages/kbn-test/package.json
+++ b/packages/kbn-test/package.json
@@ -3,14 +3,9 @@
"version": "1.0.0",
"private": true,
"license": "SSPL-1.0 OR Elastic License 2.0",
- "main": "./target",
- "types": "./target/types",
- "scripts": {
- "build": "node scripts/build",
- "kbn:bootstrap": "node scripts/build --source-maps",
- "kbn:watch": "node scripts/build --watch --source-maps"
- },
+ "main": "./target_node",
+ "types": "./target_types",
"kibana": {
"devOnly": true
}
-}
\ No newline at end of file
+}
diff --git a/packages/kbn-test/scripts/build.js b/packages/kbn-test/scripts/build.js
deleted file mode 100644
index 0be9d96ad6d58f..00000000000000
--- a/packages/kbn-test/scripts/build.js
+++ /dev/null
@@ -1,80 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0 and the Server Side Public License, v 1; you may not use this file except
- * in compliance with, at your election, the Elastic License 2.0 or the Server
- * Side Public License, v 1.
- */
-
-const { resolve } = require('path');
-
-const del = require('del');
-const supportsColor = require('supports-color');
-const { run, withProcRunner } = require('@kbn/dev-utils');
-
-const ROOT_DIR = resolve(__dirname, '..');
-const BUILD_DIR = resolve(ROOT_DIR, 'target');
-
-const padRight = (width, str) =>
- str.length >= width ? str : `${str}${' '.repeat(width - str.length)}`;
-
-run(
- async ({ log, flags }) => {
- await withProcRunner(log, async (proc) => {
- log.info('Deleting old output');
- await del(BUILD_DIR);
-
- const cwd = ROOT_DIR;
- const env = { ...process.env };
- if (supportsColor.stdout) {
- env.FORCE_COLOR = 'true';
- }
-
- log.info(`Starting babel and typescript${flags.watch ? ' in watch mode' : ''}`);
- await Promise.all([
- proc.run(padRight(10, `babel`), {
- cmd: 'babel',
- args: [
- 'src',
- '--config-file',
- require.resolve('../babel.config.js'),
- '--out-dir',
- BUILD_DIR,
- '--extensions',
- '.ts,.js,.tsx',
- ...(flags.watch ? ['--watch'] : ['--quiet']),
- ...(!flags['source-maps'] || !!process.env.CODE_COVERAGE
- ? []
- : ['--source-maps', 'inline']),
- ],
- wait: true,
- env,
- cwd,
- }),
-
- proc.run(padRight(10, 'tsc'), {
- cmd: 'tsc',
- args: [
- ...(flags.watch ? ['--watch', '--preserveWatchOutput', 'true'] : []),
- ...(flags['source-maps'] ? ['--declarationMap', 'true'] : []),
- ],
- wait: true,
- env,
- cwd,
- }),
- ]);
-
- log.success('Complete');
- });
- },
- {
- description: 'Simple build tool for @kbn/i18n package',
- flags: {
- boolean: ['watch', 'source-maps'],
- help: `
- --watch Run in watch mode
- --source-maps Include sourcemaps
- `,
- },
- }
-);
diff --git a/packages/kbn-test/src/index.ts b/packages/kbn-test/src/index.ts
index 86cbc121703ecf..dea2ec9d1035e3 100644
--- a/packages/kbn-test/src/index.ts
+++ b/packages/kbn-test/src/index.ts
@@ -18,7 +18,7 @@ import {
// @internal
export { runTestsCli, processRunTestsCliOptions, startServersCli, processStartServersCliOptions };
-// @ts-expect-error not typed yet
+// @ts-ignore not typed yet
// @internal
export { runTests, startServers } from './functional_tests/tasks';
diff --git a/packages/kbn-test/src/jest/integration_tests/__fixtures__/jest.config.js b/packages/kbn-test/src/jest/integration_tests/__fixtures__/jest.config.js
index 90d19de897ad4f..b90cc413d3f0d4 100644
--- a/packages/kbn-test/src/jest/integration_tests/__fixtures__/jest.config.js
+++ b/packages/kbn-test/src/jest/integration_tests/__fixtures__/jest.config.js
@@ -13,7 +13,7 @@ module.exports = {
reporters: [
'default',
[
- `${REPO_ROOT}/packages/kbn-test/target/jest/junit_reporter`,
+ `${REPO_ROOT}/node_modules/@kbn/test/target_node/jest/junit_reporter`,
{
reportName: 'JUnit Reporter Integration Test',
rootDirectory: resolve(
diff --git a/packages/kbn-test/tsconfig.json b/packages/kbn-test/tsconfig.json
index 3cb68029d74cfd..22c502f53c03c9 100644
--- a/packages/kbn-test/tsconfig.json
+++ b/packages/kbn-test/tsconfig.json
@@ -1,24 +1,17 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
- "incremental": false,
- "outDir": "./target/types",
+ "incremental": true,
+ "outDir": "./target_types",
"stripInternal": true,
- "emitDeclarationOnly": true,
"declaration": true,
"declarationMap": true,
+ "emitDeclarationOnly": true,
+ "rootDir": "src",
"sourceMap": true,
"sourceRoot": "../../../../../../packages/kbn-test/src",
- "types": [
- "jest",
- "node"
- ],
+ "types": ["jest", "node"]
},
- "include": [
- "src/**/*",
- "index.d.ts"
- ],
- "exclude": [
- "**/__fixtures__/**/*"
- ]
+ "include": ["src/**/*", "index.d.ts"],
+ "exclude": ["**/__fixtures__/**/*"]
}
diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/actions/snapshot_policy_actions.ts b/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/actions/snapshot_policy_actions.ts
index 0a49c3cf295bd6..37e0ef17d22544 100644
--- a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/actions/snapshot_policy_actions.ts
+++ b/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/actions/snapshot_policy_actions.ts
@@ -5,7 +5,7 @@
* 2.0.
*/
-import { TestBed } from '@kbn/test/target/types/jest';
+import { TestBed } from '@kbn/test/target_types/jest';
import { act } from 'react-dom/test-utils';
const createSetWaitForSnapshotAction = (testBed: TestBed) => async (snapshotPolicyName: string) => {
diff --git a/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/edit_role_mapping_page.test.tsx b/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/edit_role_mapping_page.test.tsx
index c0486ee7d0d827..b624da2cd88b41 100644
--- a/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/edit_role_mapping_page.test.tsx
+++ b/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/edit_role_mapping_page.test.tsx
@@ -8,7 +8,7 @@
// brace/ace uses the Worker class, which is not currently provided by JSDOM.
// This is not required for the tests to pass, but it rather suppresses lengthy
// warnings in the console which adds unnecessary noise to the test output.
-import '@kbn/test/target/jest/utils/stub_web_worker';
+import '@kbn/test/target_node/jest/utils/stub_web_worker';
import React from 'react';
diff --git a/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/rule_editor_panel/json_rule_editor.test.tsx b/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/rule_editor_panel/json_rule_editor.test.tsx
index a4c000d3246364..1cfc57323da1d9 100644
--- a/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/rule_editor_panel/json_rule_editor.test.tsx
+++ b/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/rule_editor_panel/json_rule_editor.test.tsx
@@ -10,7 +10,7 @@ import 'brace/mode/json';
// brace/ace uses the Worker class, which is not currently provided by JSDOM.
// This is not required for the tests to pass, but it rather suppresses lengthy
// warnings in the console which adds unnecessary noise to the test output.
-import '@kbn/test/target/jest/utils/stub_web_worker';
+import '@kbn/test/target_node/jest/utils/stub_web_worker';
import { EuiCodeEditor } from '@elastic/eui';
import React from 'react';
diff --git a/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/rule_editor_panel/rule_editor_panel.test.tsx b/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/rule_editor_panel/rule_editor_panel.test.tsx
index 01b4c0a0eab6d5..c0da0ae42a7263 100644
--- a/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/rule_editor_panel/rule_editor_panel.test.tsx
+++ b/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/rule_editor_panel/rule_editor_panel.test.tsx
@@ -8,7 +8,7 @@
// brace/ace uses the Worker class, which is not currently provided by JSDOM.
// This is not required for the tests to pass, but it rather suppresses lengthy
// warnings in the console which adds unnecessary noise to the test output.
-import '@kbn/test/target/jest/utils/stub_web_worker';
+import '@kbn/test/target_node/jest/utils/stub_web_worker';
import { EuiErrorBoundary } from '@elastic/eui';
import React from 'react';
diff --git a/yarn.lock b/yarn.lock
index 21a7b445d43712..589f07e7a35d83 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2789,7 +2789,7 @@
version "0.0.0"
uid ""
-"@kbn/test@link:packages/kbn-test":
+"@kbn/test@link:bazel-bin/packages/kbn-test":
version "0.0.0"
uid ""
From ba5d5cf441008d9fa1b973f435bd36ef0cc4965c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Ece=20=C3=96zalp?=
Date: Tue, 29 Jun 2021 21:28:44 -0400
Subject: [PATCH 026/121] fixes diffing on AlertsUtilityBar (#103746)
---
.../components/alerts_table/alerts_utility_bar/index.tsx | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/alerts_utility_bar/index.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/alerts_utility_bar/index.tsx
index bda8c85ddb315b..1ef79a64f831e6 100644
--- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/alerts_utility_bar/index.tsx
+++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/alerts_utility_bar/index.tsx
@@ -258,6 +258,5 @@ export const AlertsUtilityBar = React.memo(
prevProps.totalCount === nextProps.totalCount &&
prevProps.showClearSelection === nextProps.showClearSelection &&
prevProps.showBuildingBlockAlerts === nextProps.showBuildingBlockAlerts &&
- prevProps.onShowOnlyThreatIndicatorAlertsChanged ===
- nextProps.onShowOnlyThreatIndicatorAlertsChanged
+ prevProps.showOnlyThreatIndicatorAlerts === nextProps.showOnlyThreatIndicatorAlerts
);
From b7ad0c9004cac3399c73dd4d7a38285fce238d0e Mon Sep 17 00:00:00 2001
From: Clint Andrew Hall
Date: Tue, 29 Jun 2021 22:34:50 -0400
Subject: [PATCH 027/121] [storybook] Ignore TS-related HMR warnings (#103605)
* [storybook] Ignore TS-related HMR warnings
* Fix casing
* Remove warnings filter
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
---
.../ignore_not_found_export_plugin.ts | 36 +++++++++++++++++++
packages/kbn-storybook/webpack.config.ts | 3 +-
2 files changed, 38 insertions(+), 1 deletion(-)
create mode 100644 packages/kbn-storybook/ignore_not_found_export_plugin.ts
diff --git a/packages/kbn-storybook/ignore_not_found_export_plugin.ts b/packages/kbn-storybook/ignore_not_found_export_plugin.ts
new file mode 100644
index 00000000000000..18769416f43c1a
--- /dev/null
+++ b/packages/kbn-storybook/ignore_not_found_export_plugin.ts
@@ -0,0 +1,36 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+// derived from https://github.com/TypeStrong/ts-loader/issues/653#issuecomment-390889335
+//
+// This plugin suppresses the irritating TS-related warnings in Storybook HMR.
+
+import { Compiler, Stats } from 'webpack';
+// @ts-expect-error
+import ModuleDependencyWarning from 'webpack/lib/ModuleDependencyWarning';
+
+export class IgnoreNotFoundExportPlugin {
+ apply(compiler: Compiler) {
+ const messageRegExp = /export '.*'( \(reexported as '.*'\))? was not found in/;
+
+ function doneHook(stats: Stats) {
+ stats.compilation.warnings = stats.compilation.warnings.filter(function (warn) {
+ if (warn instanceof ModuleDependencyWarning && messageRegExp.test(warn.message)) {
+ return false;
+ }
+ return true;
+ });
+ }
+
+ if (compiler.hooks) {
+ compiler.hooks.done.tap('IgnoreNotFoundExportPlugin', doneHook);
+ } else {
+ compiler.plugin('done', doneHook);
+ }
+ }
+}
diff --git a/packages/kbn-storybook/webpack.config.ts b/packages/kbn-storybook/webpack.config.ts
index 41d3ee1f7ee5c3..97fbf40468429d 100644
--- a/packages/kbn-storybook/webpack.config.ts
+++ b/packages/kbn-storybook/webpack.config.ts
@@ -12,6 +12,7 @@ import { resolve } from 'path';
import { Configuration, Stats } from 'webpack';
import webpackMerge from 'webpack-merge';
import { REPO_ROOT } from './lib/constants';
+import { IgnoreNotFoundExportPlugin } from './ignore_not_found_export_plugin';
const stats = {
...Stats.presetToOptions('minimal'),
@@ -19,7 +20,6 @@ const stats = {
errorDetails: true,
errors: true,
moduleTrace: true,
- warningsFilter: /(export .* was not found in)|(entrypoint size limit)/,
};
// Extend the Storybook Webpack config with some customizations
@@ -70,6 +70,7 @@ export default function ({ config: storybookConfig }: { config: Configuration })
},
],
},
+ plugins: [new IgnoreNotFoundExportPlugin()],
resolve: {
extensions: ['.js', '.ts', '.tsx', '.json'],
mainFields: ['browser', 'main'],
From bf54eec22bdcf6dc7535c046eeaafa2e1f15a2a8 Mon Sep 17 00:00:00 2001
From: Clint Andrew Hall
Date: Tue, 29 Jun 2021 22:36:13 -0400
Subject: [PATCH 028/121] [labs] Update Labs Status (#103603)
* [labs] Update Labs Status
* Fix translations
* Supply IntersectionObserver mock
* Set defer fold project to not enabled by default
* Update copy for labs flyout
---
.../public/application/test_helpers/index.ts | 1 +
.../intersection_observer_mock.ts | 47 +++++++++++++++++++
src/plugins/dashboard/server/ui_settings.ts | 2 +-
src/plugins/presentation_util/common/labs.ts | 20 ++------
.../public/components/labs/project_list.tsx | 17 ++++++-
.../presentation_util/public/i18n/labs.tsx | 25 +++++-----
x-pack/plugins/canvas/server/ui_settings.ts | 2 +-
.../translations/translations/ja-JP.json | 1 -
.../translations/translations/zh-CN.json | 1 -
9 files changed, 82 insertions(+), 34 deletions(-)
create mode 100644 src/plugins/dashboard/public/application/test_helpers/intersection_observer_mock.ts
diff --git a/src/plugins/dashboard/public/application/test_helpers/index.ts b/src/plugins/dashboard/public/application/test_helpers/index.ts
index d26eadec8f1c9c..7c8ae86074a46d 100644
--- a/src/plugins/dashboard/public/application/test_helpers/index.ts
+++ b/src/plugins/dashboard/public/application/test_helpers/index.ts
@@ -9,3 +9,4 @@
export { getSampleDashboardInput, getSampleDashboardPanel } from './get_sample_dashboard_input';
export { getSavedDashboardMock } from './get_saved_dashboard_mock';
export { makeDefaultServices } from './make_default_services';
+export { setupIntersectionObserverMock } from './intersection_observer_mock';
diff --git a/src/plugins/dashboard/public/application/test_helpers/intersection_observer_mock.ts b/src/plugins/dashboard/public/application/test_helpers/intersection_observer_mock.ts
new file mode 100644
index 00000000000000..401ec5acdee4ee
--- /dev/null
+++ b/src/plugins/dashboard/public/application/test_helpers/intersection_observer_mock.ts
@@ -0,0 +1,47 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+/**
+ * Utility function that mocks the `IntersectionObserver` API. Necessary for components that rely
+ * on it, otherwise the tests will crash. Recommended to execute inside `beforeEach`.
+ *
+ * @param intersectionObserverMock - Parameter that is sent to the `Object.defineProperty`
+ * overwrite method. `jest.fn()` mock functions can be passed here if the goal is to not only
+ * mock the intersection observer, but its methods.
+ */
+export function setupIntersectionObserverMock({
+ root = null,
+ rootMargin = '',
+ thresholds = [],
+ disconnect = () => null,
+ observe = () => null,
+ takeRecords = () => [],
+ unobserve = () => null,
+} = {}): void {
+ class MockIntersectionObserver implements IntersectionObserver {
+ readonly root: Element | null = root;
+ readonly rootMargin: string = rootMargin;
+ readonly thresholds: readonly number[] = thresholds;
+ disconnect: () => void = disconnect;
+ observe: (target: Element) => void = observe;
+ takeRecords: () => IntersectionObserverEntry[] = takeRecords;
+ unobserve: (target: Element) => void = unobserve;
+ }
+
+ Object.defineProperty(window, 'IntersectionObserver', {
+ writable: true,
+ configurable: true,
+ value: MockIntersectionObserver,
+ });
+
+ Object.defineProperty(global, 'IntersectionObserver', {
+ writable: true,
+ configurable: true,
+ value: MockIntersectionObserver,
+ });
+}
diff --git a/src/plugins/dashboard/server/ui_settings.ts b/src/plugins/dashboard/server/ui_settings.ts
index 34cfff0e4ef473..99eb29a27deaa8 100644
--- a/src/plugins/dashboard/server/ui_settings.ts
+++ b/src/plugins/dashboard/server/ui_settings.ts
@@ -20,7 +20,7 @@ export const getUISettings = (): Record> => ({
name: i18n.translate('dashboard.labs.enableUI', {
defaultMessage: 'Enable labs button in Dashboard',
}),
- description: i18n.translate('dashboard.labs.enableUnifiedToolbarProjectDescription', {
+ description: i18n.translate('dashboard.labs.enableLabsDescription', {
defaultMessage:
'This flag determines if the viewer has access to the Labs button, a quick way to enable and disable experimental features in Dashboard.',
}),
diff --git a/src/plugins/presentation_util/common/labs.ts b/src/plugins/presentation_util/common/labs.ts
index d80624fe0bb99f..b958f3de0814f9 100644
--- a/src/plugins/presentation_util/common/labs.ts
+++ b/src/plugins/presentation_util/common/labs.ts
@@ -9,10 +9,9 @@
import { i18n } from '@kbn/i18n';
export const LABS_PROJECT_PREFIX = 'labs:';
-export const TIME_TO_PRESENT = `${LABS_PROJECT_PREFIX}presentation:timeToPresent` as const;
export const DEFER_BELOW_FOLD = `${LABS_PROJECT_PREFIX}dashboard:deferBelowFold` as const;
-export const projectIDs = [TIME_TO_PRESENT, DEFER_BELOW_FOLD] as const;
+export const projectIDs = [DEFER_BELOW_FOLD] as const;
export const environmentNames = ['kibana', 'browser', 'session'] as const;
export const solutionNames = ['canvas', 'dashboard', 'presentation'] as const;
@@ -21,30 +20,17 @@ export const solutionNames = ['canvas', 'dashboard', 'presentation'] as const;
* provided to users of our solutions in Kibana.
*/
export const projects: { [ID in ProjectID]: ProjectConfig & { id: ID } } = {
- [TIME_TO_PRESENT]: {
- id: TIME_TO_PRESENT,
- isActive: false,
- isDisplayed: false,
- environments: ['kibana', 'browser', 'session'],
- name: i18n.translate('presentationUtil.labs.enableTimeToPresentProjectName', {
- defaultMessage: 'Canvas Presentation UI',
- }),
- description: i18n.translate('presentationUtil.labs.enableUnifiedToolbarProjectDescription', {
- defaultMessage: 'Enable the new presentation-oriented UI for Canvas.',
- }),
- solutions: ['canvas'],
- },
[DEFER_BELOW_FOLD]: {
id: DEFER_BELOW_FOLD,
isActive: false,
isDisplayed: true,
environments: ['kibana', 'browser', 'session'],
name: i18n.translate('presentationUtil.labs.enableDeferBelowFoldProjectName', {
- defaultMessage: 'Defer loading below "the fold"',
+ defaultMessage: 'Defer loading panels below "the fold"',
}),
description: i18n.translate('presentationUtil.labs.enableDeferBelowFoldProjectDescription', {
defaultMessage:
- 'Any Dashboard panels below the fold-- the area hidden beyond the bottom of the window, accessed by scrolling-- will not be loaded immediately, but only when they enter the viewport',
+ 'Any panels below "the fold"-- the area hidden beyond the bottom of the window, accessed by scrolling-- will not be loaded immediately, but only when they enter the viewport',
}),
solutions: ['dashboard'],
},
diff --git a/src/plugins/presentation_util/public/components/labs/project_list.tsx b/src/plugins/presentation_util/public/components/labs/project_list.tsx
index 301fd1aa6414f5..ee1997b5ca7d8e 100644
--- a/src/plugins/presentation_util/public/components/labs/project_list.tsx
+++ b/src/plugins/presentation_util/public/components/labs/project_list.tsx
@@ -22,7 +22,20 @@ export interface Props {
onStatusChange: ProjectListItemProps['onStatusChange'];
}
-const EmptyList = () => ;
+const EmptyList = ({ solutions }: { solutions?: SolutionName[] }) => {
+ let title = strings.getNoProjectsMessage();
+
+ if (solutions?.length === 1) {
+ const solution = solutions[0];
+ switch (solution) {
+ case 'dashboard':
+ title = strings.getNoProjectsInSolutionMessage('Dashboard');
+ case 'canvas':
+ title = strings.getNoProjectsInSolutionMessage('Canvas');
+ }
+ }
+ return ;
+};
export const ProjectList = (props: Props) => {
const { solutions, projects, onStatusChange } = props;
@@ -48,7 +61,7 @@ export const ProjectList = (props: Props) => {
return (
- {items.length > 0 ? : }
+ {items.length > 0 ? : }
);
};
diff --git a/src/plugins/presentation_util/public/i18n/labs.tsx b/src/plugins/presentation_util/public/i18n/labs.tsx
index d9e34fa43ebb78..487c6fa6641e4d 100644
--- a/src/plugins/presentation_util/public/i18n/labs.tsx
+++ b/src/plugins/presentation_util/public/i18n/labs.tsx
@@ -19,8 +19,7 @@ export const LabsStrings = {
defaultMessage: 'Kibana',
}),
help: i18n.translate('presentationUtil.labs.components.kibanaSwitchHelp', {
- defaultMessage:
- 'Sets the corresponding Advanced Setting for this lab project; affects all Kibana users',
+ defaultMessage: 'Enables this lab for all Kibana users.',
}),
}),
getBrowserSwitchText: () => ({
@@ -28,8 +27,7 @@ export const LabsStrings = {
defaultMessage: 'Browser',
}),
help: i18n.translate('presentationUtil.labs.components.browserSwitchHelp', {
- defaultMessage:
- 'Enables or disables the lab project for the browser; persists between browser instances',
+ defaultMessage: 'Enables the lab for this browser and persists after it closes.',
}),
}),
getSessionSwitchText: () => ({
@@ -37,21 +35,27 @@ export const LabsStrings = {
defaultMessage: 'Session',
}),
help: i18n.translate('presentationUtil.labs.components.sessionSwitchHelp', {
- defaultMessage:
- 'Enables or disables the lab project for this tab; resets when the browser tab is closed',
+ defaultMessage: 'Enables the lab for this browser session, so it resets when it closes.',
}),
}),
},
List: {
getNoProjectsMessage: () =>
i18n.translate('presentationUtil.labs.components.noProjectsMessage', {
- defaultMessage: 'No available lab projects',
+ defaultMessage: 'No labs currently available.',
+ }),
+ getNoProjectsInSolutionMessage: (solutionName: string) =>
+ i18n.translate('presentationUtil.labs.components.noProjectsinSolutionMessage', {
+ defaultMessage: 'No labs currently in {solutionName}.',
+ values: {
+ solutionName,
+ },
}),
},
ListItem: {
getOverrideLegend: () =>
i18n.translate('presentationUtil.labs.components.overrideFlagsLabel', {
- defaultMessage: 'Override flags',
+ defaultMessage: 'Overrides',
}),
getOverriddenIconTipLabel: () =>
i18n.translate('presentationUtil.labs.components.overridenIconTipLabel', {
@@ -81,12 +85,11 @@ export const LabsStrings = {
Flyout: {
getTitleLabel: () =>
i18n.translate('presentationUtil.labs.components.titleLabel', {
- defaultMessage: 'Lab projects',
+ defaultMessage: 'Labs',
}),
getDescriptionMessage: () =>
i18n.translate('presentationUtil.labs.components.descriptionMessage', {
- defaultMessage:
- 'Lab projects are features and functionality that are in-progress or experimental in nature. They can be enabled and disabled locally for your browser or tab, or in Kibana.',
+ defaultMessage: 'Try out our features that are in progress or experimental.',
}),
getResetToDefaultLabel: () =>
i18n.translate('presentationUtil.labs.components.resetToDefaultLabel', {
diff --git a/x-pack/plugins/canvas/server/ui_settings.ts b/x-pack/plugins/canvas/server/ui_settings.ts
index 75c4cc082c5576..8c7dc9a0958728 100644
--- a/x-pack/plugins/canvas/server/ui_settings.ts
+++ b/x-pack/plugins/canvas/server/ui_settings.ts
@@ -19,7 +19,7 @@ export const getUISettings = (): Record> => ({
name: i18n.translate('xpack.canvas.labs.enableUI', {
defaultMessage: 'Enable labs button in Canvas',
}),
- description: i18n.translate('xpack.canvas.labs.enableUnifiedToolbarProjectDescription', {
+ description: i18n.translate('xpack.canvas.labs.enableLabsDescription', {
defaultMessage:
'This flag determines if the viewer has access to the Labs button, a quick way to enable and disable experimental features in Canvas.',
}),
diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json
index 7ef74136d1c182..fb1a5026b7e01c 100644
--- a/x-pack/plugins/translations/translations/ja-JP.json
+++ b/x-pack/plugins/translations/translations/ja-JP.json
@@ -3320,7 +3320,6 @@
"presentationUtil.labs.components.sessionSwitchHelp": "このタブのラボプロジェクトを有効または無効にします。ブラウザータブが閉じたときにリセットします",
"presentationUtil.labs.components.sessionSwitchName": "セッション",
"presentationUtil.labs.components.titleLabel": "ラボプロジェクト",
- "presentationUtil.labs.enableUnifiedToolbarProjectDescription": "プレゼンテーションソリューションの新しい統合ツールバー設計を有効にする",
"presentationUtil.saveModalDashboard.addToDashboardLabel": "ダッシュボードに追加",
"presentationUtil.saveModalDashboard.dashboardInfoTooltip": "Visualizeライブラリに追加された項目はすべてのダッシュボードで使用できます。ライブラリ項目の編集は、使用されるすべての場所に表示されます。",
"presentationUtil.saveModalDashboard.existingDashboardOptionLabel": "既存",
diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json
index 0d5ecf17159e53..d33212d8a2696a 100644
--- a/x-pack/plugins/translations/translations/zh-CN.json
+++ b/x-pack/plugins/translations/translations/zh-CN.json
@@ -3342,7 +3342,6 @@
"presentationUtil.labs.components.sessionSwitchHelp": "启用或禁用此选项卡的实验室项目;在关闭浏览器选项卡时重置",
"presentationUtil.labs.components.sessionSwitchName": "会话",
"presentationUtil.labs.components.titleLabel": "实验室项目",
- "presentationUtil.labs.enableUnifiedToolbarProjectDescription": "启用演示解决方案的新统一工具栏设计",
"presentationUtil.saveModalDashboard.addToDashboardLabel": "添加到仪表板",
"presentationUtil.saveModalDashboard.dashboardInfoTooltip": "添加到 Visualize 库的项目可用于所有仪表板。对库项目的编辑将显示在使用位置。",
"presentationUtil.saveModalDashboard.existingDashboardOptionLabel": "现有",
From 428eba425d38fc62a7129318a153cfb67e20b232 Mon Sep 17 00:00:00 2001
From: Catherine Liu
Date: Tue, 29 Jun 2021 19:52:02 -0700
Subject: [PATCH 029/121] [Canvas] Removes link from workpad breadcrumb
(#103793)
* Removes link from workpad title breadcrumb
* Fixed ts error
---
x-pack/plugins/canvas/public/lib/breadcrumbs.ts | 13 ++++---------
.../routes/workpad/workpad_presentation_helper.tsx | 2 +-
2 files changed, 5 insertions(+), 10 deletions(-)
diff --git a/x-pack/plugins/canvas/public/lib/breadcrumbs.ts b/x-pack/plugins/canvas/public/lib/breadcrumbs.ts
index 35a17eda8c165f..b926c306742982 100644
--- a/x-pack/plugins/canvas/public/lib/breadcrumbs.ts
+++ b/x-pack/plugins/canvas/public/lib/breadcrumbs.ts
@@ -7,18 +7,13 @@
import { ChromeBreadcrumb } from '../../../../../src/core/public';
-export const getBaseBreadcrumb = () => ({
+export const getBaseBreadcrumb = (): ChromeBreadcrumb => ({
text: 'Canvas',
href: '#/',
});
export const getWorkpadBreadcrumb = ({
name = 'Workpad',
- id,
-}: { name?: string; id?: string } = {}) => {
- const output: ChromeBreadcrumb = { text: name };
- if (id != null) {
- output.href = `#/workpad/${id}`;
- }
- return output;
-};
+}: { name?: string } = {}): ChromeBreadcrumb => ({
+ text: name,
+});
diff --git a/x-pack/plugins/canvas/public/routes/workpad/workpad_presentation_helper.tsx b/x-pack/plugins/canvas/public/routes/workpad/workpad_presentation_helper.tsx
index cecb8a376c2424..ccb38cd1a1e0f2 100644
--- a/x-pack/plugins/canvas/public/routes/workpad/workpad_presentation_helper.tsx
+++ b/x-pack/plugins/canvas/public/routes/workpad/workpad_presentation_helper.tsx
@@ -26,7 +26,7 @@ export const WorkpadPresentationHelper: FC = ({ children }) => {
useEffect(() => {
services.platform.setBreadcrumbs([
getBaseBreadcrumb(),
- getWorkpadBreadcrumb({ name: workpad.name, id: workpad.id }),
+ getWorkpadBreadcrumb({ name: workpad.name }),
]);
}, [workpad.name, workpad.id, services.platform]);
From 699731f25eb05c12461ce6ba893cd89ef55f825d Mon Sep 17 00:00:00 2001
From: "Joey F. Poon"
Date: Tue, 29 Jun 2021 22:05:30 -0500
Subject: [PATCH 030/121] [Security Solution] throttle package calls on initial
security page load (#103570)
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
---
.../pages/endpoint_hosts/store/action.ts | 10 +++-----
.../pages/endpoint_hosts/store/builders.ts | 2 +-
.../pages/endpoint_hosts/store/index.test.ts | 4 ++-
.../pages/endpoint_hosts/store/middleware.ts | 25 ++++++++++++++-----
.../pages/endpoint_hosts/store/reducer.ts | 23 ++++++++++++-----
.../pages/endpoint_hosts/store/selectors.ts | 15 ++++++++---
.../management/pages/endpoint_hosts/types.ts | 2 +-
.../view/trusted_apps_page.test.tsx | 6 ++++-
8 files changed, 62 insertions(+), 25 deletions(-)
diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/action.ts b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/action.ts
index 949feb29643173..42c16e151c45d5 100644
--- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/action.ts
+++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/action.ts
@@ -15,7 +15,6 @@ import {
} from '../../../../../common/endpoint/types';
import { ServerApiError } from '../../../../common/types';
import { GetPolicyListResponse } from '../../policy/types';
-import { GetPackagesResponse } from '../../../../../../fleet/common';
import { EndpointIndexUIQueryParams, EndpointState } from '../types';
import { IIndexPattern } from '../../../../../../../../src/plugins/data/public';
@@ -75,10 +74,9 @@ export interface ServerCancelledPolicyItemsLoading {
type: 'serverCancelledPolicyItemsLoading';
}
-export interface ServerReturnedEndpointPackageInfo {
- type: 'serverReturnedEndpointPackageInfo';
- payload: GetPackagesResponse['response'][0];
-}
+export type EndpointPackageInfoStateChanged = Action<'endpointPackageInfoStateChanged'> & {
+ payload: EndpointState['endpointPackageInfo'];
+};
export interface ServerReturnedEndpointNonExistingPolicies {
type: 'serverReturnedEndpointNonExistingPolicies';
@@ -195,7 +193,7 @@ export type EndpointAction =
| ServerCancelledEndpointListLoading
| ServerReturnedEndpointExistValue
| ServerCancelledPolicyItemsLoading
- | ServerReturnedEndpointPackageInfo
+ | EndpointPackageInfoStateChanged
| ServerReturnedMetadataPatterns
| ServerFailedToReturnMetadataPatterns
| AppRequestedEndpointList
diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/builders.ts b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/builders.ts
index 317b735e1169e5..5db861d18cd693 100644
--- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/builders.ts
+++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/builders.ts
@@ -41,7 +41,7 @@ export const initialEndpointPageState = (): Immutable => {
policyItems: [],
selectedPolicyId: undefined,
policyItemsLoading: false,
- endpointPackageInfo: undefined,
+ endpointPackageInfo: createUninitialisedResourceState(),
nonExistingPolicies: {},
agentPolicies: {},
endpointsExist: true,
diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/index.test.ts b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/index.test.ts
index 68dd47362bc383..3bf625d726e5f3 100644
--- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/index.test.ts
+++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/index.test.ts
@@ -64,7 +64,9 @@ describe('EndpointList store concerns', () => {
policyItems: [],
selectedPolicyId: undefined,
policyItemsLoading: false,
- endpointPackageInfo: undefined,
+ endpointPackageInfo: {
+ type: 'UninitialisedResourceState',
+ },
nonExistingPolicies: {},
agentPolicies: {},
endpointsExist: true,
diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts
index ffeef3f7bf5e18..1a431ea88ad6a0 100644
--- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts
+++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts
@@ -36,6 +36,8 @@ import {
getLastLoadedActivityLogData,
detailsData,
getEndpointDetailsFlyoutView,
+ getIsEndpointPackageInfoPending,
+ getIsEndpointPackageInfoSuccessful,
} from './selectors';
import { AgentIdsPendingActions, EndpointState, PolicyIds } from '../types';
import {
@@ -44,7 +46,7 @@ import {
sendGetAgentPolicyList,
sendGetFleetAgentsWithEndpoint,
} from '../../policy/store/services/ingest';
-import { AGENT_POLICY_SAVED_OBJECT_TYPE } from '../../../../../../fleet/common';
+import { AGENT_POLICY_SAVED_OBJECT_TYPE, PackageListItem } from '../../../../../../fleet/common';
import {
ENDPOINT_ACTION_LOG_ROUTE,
HOST_METADATA_GET_ROUTE,
@@ -61,7 +63,7 @@ import {
import { isolateHost, unIsolateHost } from '../../../../common/lib/endpoint_isolation';
import { AppAction } from '../../../../common/store/actions';
import { resolvePathVariables } from '../../../../common/utils/resolve_path_variables';
-import { ServerReturnedEndpointPackageInfo } from './action';
+import { EndpointPackageInfoStateChanged } from './action';
import { fetchPendingActionsByAgentId } from '../../../../common/lib/endpoint_pending_actions';
import { EndpointDetailsTabsTypes } from '../view/details/components/endpoint_details_tabs';
@@ -593,20 +595,31 @@ const handleIsolateEndpointHost = async (
async function getEndpointPackageInfo(
state: ImmutableObject,
- dispatch: Dispatch,
+ dispatch: Dispatch,
coreStart: CoreStart
) {
- if (endpointPackageInfo(state)) return;
+ if (getIsEndpointPackageInfoPending(state) || getIsEndpointPackageInfoSuccessful(state)) return;
+
+ dispatch({
+ type: 'endpointPackageInfoStateChanged',
+ // Ignore will be fixed with when AsyncResourceState is refactored (#830)
+ // @ts-ignore
+ payload: createLoadingResourceState(endpointPackageInfo(state)),
+ });
try {
const packageInfo = await sendGetEndpointSecurityPackage(coreStart.http);
dispatch({
- type: 'serverReturnedEndpointPackageInfo',
- payload: packageInfo,
+ type: 'endpointPackageInfoStateChanged',
+ payload: createLoadedResourceState(packageInfo),
});
} catch (error) {
// Ignore Errors, since this should not hinder the user's ability to use the UI
logError(error);
+ dispatch({
+ type: 'endpointPackageInfoStateChanged',
+ payload: createFailedResourceState(error),
+ });
}
}
diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/reducer.ts b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/reducer.ts
index 44c63edd8e95c5..0981d621f26f32 100644
--- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/reducer.ts
+++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/reducer.ts
@@ -5,7 +5,11 @@
* 2.0.
*/
-import { EndpointDetailsActivityLogChanged, EndpointPendingActionsStateChanged } from './action';
+import {
+ EndpointDetailsActivityLogChanged,
+ EndpointPackageInfoStateChanged,
+ EndpointPendingActionsStateChanged,
+} from './action';
import {
isOnEndpointPage,
hasSelectedEndpoint,
@@ -65,6 +69,16 @@ const handleEndpointPendingActionsStateChanged: CaseReducer = (
+ state,
+ action
+) => {
+ return {
+ ...state,
+ endpointPackageInfo: action.payload,
+ };
+};
+
/* eslint-disable-next-line complexity */
export const endpointListReducer: StateReducer = (state = initialEndpointPageState(), action) => {
if (action.type === 'serverReturnedEndpointList') {
@@ -231,11 +245,8 @@ export const endpointListReducer: StateReducer = (state = initialEndpointPageSta
...state,
policyItemsLoading: false,
};
- } else if (action.type === 'serverReturnedEndpointPackageInfo') {
- return {
- ...state,
- endpointPackageInfo: action.payload,
- };
+ } else if (action.type === 'endpointPackageInfoStateChanged') {
+ return handleEndpointPackageInfoStateChanged(state, action);
} else if (action.type === 'serverReturnedEndpointExistValue') {
return {
...state,
diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/selectors.ts b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/selectors.ts
index eeb54379e8e7df..c09e4032d6222f 100644
--- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/selectors.ts
+++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/selectors.ts
@@ -69,6 +69,16 @@ export const policyItemsLoading = (state: Immutable) => state.pol
export const selectedPolicyId = (state: Immutable) => state.selectedPolicyId;
export const endpointPackageInfo = (state: Immutable) => state.endpointPackageInfo;
+export const getIsEndpointPackageInfoPending: (
+ state: Immutable
+) => boolean = createSelector(endpointPackageInfo, (packageInfo) =>
+ isLoadingResourceState(packageInfo)
+);
+export const getIsEndpointPackageInfoSuccessful: (
+ state: Immutable
+) => boolean = createSelector(endpointPackageInfo, (packageInfo) =>
+ isLoadedResourceState(packageInfo)
+);
export const isAutoRefreshEnabled = (state: Immutable) => state.isAutoRefreshEnabled;
@@ -86,9 +96,8 @@ export const agentsWithEndpointsTotalError = (state: Immutable) =
export const endpointsTotalError = (state: Immutable) => state.endpointsTotalError;
const queryStrategyVersion = (state: Immutable) => state.queryStrategyVersion;
-export const endpointPackageVersion = createSelector(
- endpointPackageInfo,
- (info) => info?.version ?? undefined
+export const endpointPackageVersion = createSelector(endpointPackageInfo, (info) =>
+ isLoadedResourceState(info) ? info.data.version : undefined
);
export const isTransformEnabled = createSelector(
diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/types.ts b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/types.ts
index c985259588cb05..de213b3dbccc34 100644
--- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/types.ts
+++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/types.ts
@@ -70,7 +70,7 @@ export interface EndpointState {
/** the selected policy ID in the onboarding flow */
selectedPolicyId?: string;
/** Endpoint package info */
- endpointPackageInfo?: GetPackagesResponse['response'][0];
+ endpointPackageInfo: AsyncResourceState;
/** Tracks the list of policies IDs used in Host metadata that may no longer exist */
nonExistingPolicies: PolicyIds['packagePolicy'];
/** List of Package Policy Ids mapped to an associated Fleet Parent Agent Policy Id*/
diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/trusted_apps_page.test.tsx b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/trusted_apps_page.test.tsx
index 970ade80bd8db7..5c627d1d7a8373 100644
--- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/trusted_apps_page.test.tsx
+++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/trusted_apps_page.test.tsx
@@ -762,8 +762,9 @@ describe('When on the Trusted Apps Page', () => {
});
beforeEach(() => {
+ const priorMockImplementation = coreStart.http.get.getMockImplementation();
// @ts-ignore
- coreStart.http.get.mockImplementation(async (path, options) => {
+ coreStart.http.get.mockImplementation((path, options) => {
if (path === TRUSTED_APPS_LIST_API) {
const { page, per_page: perPage } = options.query as { page: number; per_page: number };
@@ -773,6 +774,9 @@ describe('When on the Trusted Apps Page', () => {
return releaseListResponse();
}
}
+ if (priorMockImplementation) {
+ return priorMockImplementation(path);
+ }
});
});
From d809f48c602c67582def13867affd1389e3caa0c Mon Sep 17 00:00:00 2001
From: Melissa Alvarez
Date: Tue, 29 Jun 2021 23:17:12 -0400
Subject: [PATCH 031/121] [ML] Anomaly Detection: add ability to clear warning
notification from jobs list (#103608)
* wip: adds clear messages endpoint
* wip: clear messages and index new message for clearing
* remove icon from jobs list on clear
* remove unnecessary comments and fix typo
* ensure clear messages has correct permissions
* use cleaner ml context and add type
* only show clear button with canCreateJob and if warning icon in table
* fix types for job message pane
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
---
.../ml/common/constants/index_patterns.ts | 1 +
.../anomaly_detection_jobs/summary_job.ts | 1 +
.../plugins/ml/common/types/audit_message.ts | 2 +
.../components/job_messages/job_messages.tsx | 2 +-
.../components/job_details/job_details.js | 10 +-
.../job_details/job_messages_pane.tsx | 100 ++++++++++++++++--
.../jobs_list_view/jobs_list_view.js | 6 ++
.../services/ml_api_service/jobs.ts | 9 ++
.../job_audit_messages.d.ts | 1 +
.../job_audit_messages/job_audit_messages.js | 74 ++++++++++++-
x-pack/plugins/ml/server/routes/apidoc.json | 2 +-
.../ml/server/routes/job_audit_messages.ts | 37 +++++++
.../schemas/job_audit_messages_schema.ts | 4 +
13 files changed, 234 insertions(+), 15 deletions(-)
diff --git a/x-pack/plugins/ml/common/constants/index_patterns.ts b/x-pack/plugins/ml/common/constants/index_patterns.ts
index d7d6c343e282bc..cec692217546d6 100644
--- a/x-pack/plugins/ml/common/constants/index_patterns.ts
+++ b/x-pack/plugins/ml/common/constants/index_patterns.ts
@@ -11,3 +11,4 @@ export const ML_ANNOTATIONS_INDEX_PATTERN = '.ml-annotations-6';
export const ML_RESULTS_INDEX_PATTERN = '.ml-anomalies-*';
export const ML_NOTIFICATION_INDEX_PATTERN = '.ml-notifications*';
+export const ML_NOTIFICATION_INDEX_02 = '.ml-notifications-000002';
diff --git a/x-pack/plugins/ml/common/types/anomaly_detection_jobs/summary_job.ts b/x-pack/plugins/ml/common/types/anomaly_detection_jobs/summary_job.ts
index e9e89a3c99771c..28071f88da9d93 100644
--- a/x-pack/plugins/ml/common/types/anomaly_detection_jobs/summary_job.ts
+++ b/x-pack/plugins/ml/common/types/anomaly_detection_jobs/summary_job.ts
@@ -46,6 +46,7 @@ export interface AuditMessage {
highestLevel: string;
highestLevelText: string;
text: string;
+ cleared?: boolean;
}
export type MlSummaryJobs = MlSummaryJob[];
diff --git a/x-pack/plugins/ml/common/types/audit_message.ts b/x-pack/plugins/ml/common/types/audit_message.ts
index bb7e3a53e51d75..ea50a8993634b2 100644
--- a/x-pack/plugins/ml/common/types/audit_message.ts
+++ b/x-pack/plugins/ml/common/types/audit_message.ts
@@ -11,8 +11,10 @@ export interface AuditMessageBase {
timestamp: number;
node_name: string;
text?: string;
+ cleared?: boolean;
}
export interface JobMessage extends AuditMessageBase {
job_id: string;
+ clearable?: boolean;
}
diff --git a/x-pack/plugins/ml/public/application/components/job_messages/job_messages.tsx b/x-pack/plugins/ml/public/application/components/job_messages/job_messages.tsx
index d0666ac2c660c3..2311807b6bbe6a 100644
--- a/x-pack/plugins/ml/public/application/components/job_messages/job_messages.tsx
+++ b/x-pack/plugins/ml/public/application/components/job_messages/job_messages.tsx
@@ -126,7 +126,7 @@ export const JobMessages: FC = ({
const defaultSorting = {
sort: {
field: 'timestamp' as const,
- direction: 'asc' as const,
+ direction: 'desc' as const,
},
};
diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_details/job_details.js b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_details/job_details.js
index d030fe08eef3ed..9ec1d6cc6b606a 100644
--- a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_details/job_details.js
+++ b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_details/job_details.js
@@ -58,7 +58,7 @@ export class JobDetailsUI extends Component {
);
} else {
- const { showFullDetails, refreshJobList } = this.props;
+ const { showFullDetails, refreshJobList, showClearButton } = this.props;
const {
general,
@@ -185,7 +185,13 @@ export class JobDetailsUI extends Component {
name: i18n.translate('xpack.ml.jobsList.jobDetails.tabs.jobMessagesLabel', {
defaultMessage: 'Job messages',
}),
- content: ,
+ content: (
+
+ ),
},
];
diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_details/job_messages_pane.tsx b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_details/job_messages_pane.tsx
index f808d512e1dfa7..a78a832fdb6e92 100644
--- a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_details/job_messages_pane.tsx
+++ b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_details/job_messages_pane.tsx
@@ -6,25 +6,38 @@
*/
import React, { FC, useCallback, useEffect, useState } from 'react';
+import { EuiButton, EuiFlexGroup, EuiFlexItem, EuiSpacer, EuiToolTip } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
+import { FormattedMessage } from '@kbn/i18n/react';
import { ml } from '../../../../services/ml_api_service';
import { JobMessages } from '../../../../components/job_messages';
import { JobMessage } from '../../../../../../common/types/audit_message';
import { extractErrorMessage } from '../../../../../../common/util/errors';
import { useToastNotificationService } from '../../../../services/toast_notification_service';
+import { useMlApiContext } from '../../../../contexts/kibana';
+import { checkPermission } from '../../../../capabilities/check_capabilities';
interface JobMessagesPaneProps {
jobId: string;
+ showClearButton?: boolean;
start?: string;
end?: string;
actionHandler?: (message: JobMessage) => void;
+ refreshJobList?: () => void;
}
export const JobMessagesPane: FC = React.memo(
- ({ jobId, start, end, actionHandler }) => {
+ ({ jobId, start, end, actionHandler, refreshJobList, showClearButton }) => {
+ const canCreateJob = checkPermission('canCreateJob');
+
const [messages, setMessages] = useState([]);
const [isLoading, setIsLoading] = useState(false);
const [errorMessage, setErrorMessage] = useState('');
+ const [isClearing, setIsClearing] = useState(false);
+
const toastNotificationService = useToastNotificationService();
+ const {
+ jobs: { clearJobAuditMessages },
+ } = useMlApiContext();
const fetchMessages = async () => {
setIsLoading(true);
@@ -46,18 +59,89 @@ export const JobMessagesPane: FC = React.memo(
const refreshMessage = useCallback(fetchMessages, [jobId]);
+ // Clear messages for last 24hrs and refresh jobs list
+ const clearMessages = useCallback(async () => {
+ setIsClearing(true);
+ try {
+ await clearJobAuditMessages(jobId);
+ setIsClearing(false);
+ if (typeof refreshJobList === 'function') {
+ refreshJobList();
+ }
+ } catch (e) {
+ setIsClearing(false);
+ toastNotificationService.displayErrorToast(
+ e,
+ i18n.translate('xpack.ml.jobMessages.clearJobAuditMessagesErrorTitle', {
+ defaultMessage: 'Error clearing job message warnings and errors',
+ })
+ );
+ }
+ }, [jobId]);
+
useEffect(() => {
fetchMessages();
}, []);
+ const disabled = messages.length > 0 && messages[0].clearable === false;
+
+ const clearButton = (
+
+
+
+ );
+
return (
-
+ <>
+
+
+ {canCreateJob && showClearButton ? (
+
+
+ {disabled === true ? (
+
+ {clearButton}
+
+ ) : (
+
+ {clearButton}
+
+ )}
+
+
+ ) : null}
+
+
+
+
+ >
);
}
);
diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/jobs_list_view/jobs_list_view.js b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/jobs_list_view/jobs_list_view.js
index bf8db538bc8ae6..4fdf1c4d3ab110 100644
--- a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/jobs_list_view/jobs_list_view.js
+++ b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/jobs_list_view/jobs_list_view.js
@@ -123,6 +123,9 @@ export class JobsListView extends Component {
delete itemIdToExpandedRowMap[jobId];
this.setState({ itemIdToExpandedRowMap });
} else {
+ // Only show clear notifications button if job has warning icon due to auditMessage
+ const expandedJob = this.state.jobsSummaryList.filter((job) => job.id === jobId);
+ const showClearButton = expandedJob.length > 0 && expandedJob[0].auditMessage !== undefined;
let itemIdToExpandedRowMap = { ...this.state.itemIdToExpandedRowMap };
if (this.state.fullJobsList[jobId] !== undefined) {
@@ -134,6 +137,7 @@ export class JobsListView extends Component {
removeYourself={this.removeUpdateFunction}
showFullDetails={this.props.isManagementTable !== true}
refreshJobList={this.onRefreshClick}
+ showClearButton={showClearButton}
/>
);
} else {
@@ -144,6 +148,7 @@ export class JobsListView extends Component {
removeYourself={this.removeUpdateFunction}
showFullDetails={this.props.isManagementTable !== true}
refreshJobList={this.onRefreshClick}
+ showClearButton={showClearButton}
/>
);
}
@@ -167,6 +172,7 @@ export class JobsListView extends Component {
removeYourself={this.removeUpdateFunction}
showFullDetails={this.props.isManagementTable !== true}
refreshJobList={this.onRefreshClick}
+ showClearButton={showClearButton}
/>
);
}
diff --git a/x-pack/plugins/ml/public/application/services/ml_api_service/jobs.ts b/x-pack/plugins/ml/public/application/services/ml_api_service/jobs.ts
index 1de7cb455cc2c3..5695e3d8308902 100644
--- a/x-pack/plugins/ml/public/application/services/ml_api_service/jobs.ts
+++ b/x-pack/plugins/ml/public/application/services/ml_api_service/jobs.ts
@@ -160,6 +160,15 @@ export const jobsApiProvider = (httpService: HttpService) => ({
});
},
+ clearJobAuditMessages(jobId: string) {
+ const body = JSON.stringify({ jobId });
+ return httpService.http<{ success: boolean; latest_cleared: number }>({
+ path: `${ML_BASE_PATH}/job_audit_messages/clear_messages`,
+ method: 'PUT',
+ body,
+ });
+ },
+
deletingJobTasks() {
return httpService.http({
path: `${ML_BASE_PATH}/jobs/deleting_jobs_tasks`,
diff --git a/x-pack/plugins/ml/server/models/job_audit_messages/job_audit_messages.d.ts b/x-pack/plugins/ml/server/models/job_audit_messages/job_audit_messages.d.ts
index 28287bfc8f1b48..60ea866978f1ac 100644
--- a/x-pack/plugins/ml/server/models/job_audit_messages/job_audit_messages.d.ts
+++ b/x-pack/plugins/ml/server/models/job_audit_messages/job_audit_messages.d.ts
@@ -23,4 +23,5 @@ export function jobAuditMessagesProvider(
}
) => any;
getAuditMessagesSummary: (jobIds?: string[]) => any;
+ clearJobAuditMessages: (jobId: string) => any;
};
diff --git a/x-pack/plugins/ml/server/models/job_audit_messages/job_audit_messages.js b/x-pack/plugins/ml/server/models/job_audit_messages/job_audit_messages.js
index e349462d8421d2..318c103b39636e 100644
--- a/x-pack/plugins/ml/server/models/job_audit_messages/job_audit_messages.js
+++ b/x-pack/plugins/ml/server/models/job_audit_messages/job_audit_messages.js
@@ -5,7 +5,11 @@
* 2.0.
*/
-import { ML_NOTIFICATION_INDEX_PATTERN } from '../../../common/constants/index_patterns';
+import {
+ ML_NOTIFICATION_INDEX_PATTERN,
+ ML_NOTIFICATION_INDEX_02,
+} from '../../../common/constants/index_patterns';
+import { MESSAGE_LEVEL } from '../../../common/constants/message_levels';
import moment from 'moment';
const SIZE = 1000;
@@ -35,7 +39,7 @@ const anomalyDetectorTypeFilter = {
},
};
-export function jobAuditMessagesProvider({ asInternalUser }, mlClient) {
+export function jobAuditMessagesProvider({ asInternalUser, asCurrentUser }, mlClient) {
// search for audit messages,
// jobId is optional. without it, all jobs will be listed.
// from is optional and should be a string formatted in ES time units. e.g. 12h, 1d, 7d
@@ -123,7 +127,10 @@ export function jobAuditMessagesProvider({ asInternalUser }, mlClient) {
let messages = [];
if (body.hits.total.value > 0) {
- messages = body.hits.hits.map((hit) => hit._source);
+ messages = body.hits.hits.map((hit) => ({
+ clearable: hit._index === ML_NOTIFICATION_INDEX_02,
+ ...hit._source,
+ }));
}
messages = await jobSavedObjectService.filterJobsForSpace(
'anomaly-detector',
@@ -152,6 +159,11 @@ export function jobAuditMessagesProvider({ asInternalUser }, mlClient) {
},
anomalyDetectorTypeFilter,
],
+ must_not: {
+ term: {
+ cleared: true,
+ },
+ },
},
};
@@ -266,6 +278,61 @@ export function jobAuditMessagesProvider({ asInternalUser }, mlClient) {
return jobMessages;
}
+ const clearedTime = new Date().getTime();
+
+ // Sets 'cleared' to true for messages in the last 24hrs and index new message for clear action
+ async function clearJobAuditMessages(jobId) {
+ const newClearedMessage = {
+ job_id: jobId,
+ job_type: 'anomaly_detection',
+ level: MESSAGE_LEVEL.INFO,
+ message: 'Cleared set to true for messages in the last 24hrs.',
+ timestamp: clearedTime,
+ };
+
+ const query = {
+ bool: {
+ filter: [
+ {
+ range: {
+ timestamp: {
+ gte: 'now-24h',
+ },
+ },
+ },
+ {
+ term: {
+ job_id: jobId,
+ },
+ },
+ ],
+ },
+ };
+
+ await Promise.all([
+ asCurrentUser.updateByQuery({
+ index: ML_NOTIFICATION_INDEX_02,
+ ignore_unavailable: true,
+ refresh: true,
+ conflicts: 'proceed',
+ body: {
+ query,
+ script: {
+ source: 'ctx._source.cleared = true',
+ lang: 'painless',
+ },
+ },
+ }),
+ asCurrentUser.index({
+ index: ML_NOTIFICATION_INDEX_02,
+ body: newClearedMessage,
+ refresh: 'wait_for',
+ }),
+ ]);
+
+ return { success: true, last_cleared: clearedTime };
+ }
+
function levelToText(level) {
return Object.keys(LEVEL)[Object.values(LEVEL).indexOf(level)];
}
@@ -273,5 +340,6 @@ export function jobAuditMessagesProvider({ asInternalUser }, mlClient) {
return {
getJobAuditMessages,
getAuditMessagesSummary,
+ clearJobAuditMessages,
};
}
diff --git a/x-pack/plugins/ml/server/routes/apidoc.json b/x-pack/plugins/ml/server/routes/apidoc.json
index ba712583f1b61e..27944b542b93fa 100644
--- a/x-pack/plugins/ml/server/routes/apidoc.json
+++ b/x-pack/plugins/ml/server/routes/apidoc.json
@@ -121,7 +121,7 @@
"JobAuditMessages",
"GetJobAuditMessages",
"GetAllJobAuditMessages",
-
+ "ClearJobAuditMessages",
"JobValidation",
"EstimateBucketSpan",
"CalculateModelMemoryLimit",
diff --git a/x-pack/plugins/ml/server/routes/job_audit_messages.ts b/x-pack/plugins/ml/server/routes/job_audit_messages.ts
index 93d981aaa52afa..1548427797e168 100644
--- a/x-pack/plugins/ml/server/routes/job_audit_messages.ts
+++ b/x-pack/plugins/ml/server/routes/job_audit_messages.ts
@@ -11,6 +11,7 @@ import { jobAuditMessagesProvider } from '../models/job_audit_messages';
import {
jobAuditMessagesQuerySchema,
jobAuditMessagesJobIdSchema,
+ clearJobAuditMessagesBodySchema,
} from './schemas/job_audit_messages_schema';
/**
@@ -96,4 +97,40 @@ export function jobAuditMessagesRoutes({ router, routeGuard }: RouteInitializati
}
)
);
+
+ /**
+ * @apiGroup JobAuditMessages
+ *
+ * @api {put} /api/ml/job_audit_messages/clear_messages/{jobId} Index annotation
+ * @apiName ClearJobAuditMessages
+ * @apiDescription Clear the job audit messages.
+ *
+ * @apiSchema (body) clearJobAuditMessagesSchema
+ */
+ router.put(
+ {
+ path: '/api/ml/job_audit_messages/clear_messages',
+ validate: {
+ body: clearJobAuditMessagesBodySchema,
+ },
+ options: {
+ tags: ['access:ml:canCreateJob'],
+ },
+ },
+ routeGuard.fullLicenseAPIGuard(
+ async ({ client, mlClient, request, response, jobSavedObjectService }) => {
+ try {
+ const { clearJobAuditMessages } = jobAuditMessagesProvider(client, mlClient);
+ const { jobId } = request.body;
+ const resp = await clearJobAuditMessages(jobId);
+
+ return response.ok({
+ body: resp,
+ });
+ } catch (e) {
+ return response.customError(wrapError(e));
+ }
+ }
+ )
+ );
}
diff --git a/x-pack/plugins/ml/server/routes/schemas/job_audit_messages_schema.ts b/x-pack/plugins/ml/server/routes/schemas/job_audit_messages_schema.ts
index ef7e3afa47e9c2..525ac73fde1207 100644
--- a/x-pack/plugins/ml/server/routes/schemas/job_audit_messages_schema.ts
+++ b/x-pack/plugins/ml/server/routes/schemas/job_audit_messages_schema.ts
@@ -17,3 +17,7 @@ export const jobAuditMessagesQuerySchema = schema.object({
start: schema.maybe(schema.string()),
end: schema.maybe(schema.string()),
});
+
+export const clearJobAuditMessagesBodySchema = schema.object({
+ jobId: schema.string(),
+});
From 9a3a359c69a29612820ecc5b83cc3e306654171a Mon Sep 17 00:00:00 2001
From: Clint Andrew Hall
Date: Tue, 29 Jun 2021 23:28:08 -0400
Subject: [PATCH 032/121] [canvas] Restore Workpad Title/button to Home; fix
mounting behavior (#103601)
---
.../public/components/home/hooks/index.ts | 4 +-
.../home/hooks/use_find_templates.ts | 25 +------
.../components/home/hooks/use_find_workpad.ts | 25 +------
.../home/{my_workpads => }/loading.tsx | 0
.../home/my_workpads/my_workpads.tsx | 18 +++--
.../workpad_templates/workpad_templates.tsx | 30 +++++---
.../components/toolbar/toolbar.component.tsx | 75 ++++++++++---------
.../public/components/toolbar/toolbar.scss | 5 ++
.../empty_prompt.stories.storyshot | 65 ----------------
9 files changed, 80 insertions(+), 167 deletions(-)
rename x-pack/plugins/canvas/public/components/home/{my_workpads => }/loading.tsx (100%)
delete mode 100644 x-pack/plugins/canvas/storybook/public/components/home/my_workpads/__snapshots__/empty_prompt.stories.storyshot
diff --git a/x-pack/plugins/canvas/public/components/home/hooks/index.ts b/x-pack/plugins/canvas/public/components/home/hooks/index.ts
index 91e52948a7ba6b..c4267a98574906 100644
--- a/x-pack/plugins/canvas/public/components/home/hooks/index.ts
+++ b/x-pack/plugins/canvas/public/components/home/hooks/index.ts
@@ -9,7 +9,7 @@ export { useCloneWorkpad } from './use_clone_workpad';
export { useCreateWorkpad } from './use_create_workpad';
export { useDeleteWorkpads } from './use_delete_workpad';
export { useDownloadWorkpad } from './use_download_workpad';
-export { useFindTemplates, useFindTemplatesOnMount } from './use_find_templates';
-export { useFindWorkpads, useFindWorkpadsOnMount } from './use_find_workpad';
+export { useFindTemplates } from './use_find_templates';
+export { useFindWorkpads } from './use_find_workpad';
export { useImportWorkpad } from './use_upload_workpad';
export { useCreateFromTemplate } from './use_create_from_template';
diff --git a/x-pack/plugins/canvas/public/components/home/hooks/use_find_templates.ts b/x-pack/plugins/canvas/public/components/home/hooks/use_find_templates.ts
index 13ee289fe98676..9364a79987908d 100644
--- a/x-pack/plugins/canvas/public/components/home/hooks/use_find_templates.ts
+++ b/x-pack/plugins/canvas/public/components/home/hooks/use_find_templates.ts
@@ -5,34 +5,11 @@
* 2.0.
*/
-import { useState, useCallback } from 'react';
-import useMount from 'react-use/lib/useMount';
+import { useCallback } from 'react';
import { useWorkpadService } from '../../../services';
-import { TemplateFindResponse } from '../../../services/workpad';
-
-const emptyResponse = { templates: [] };
export const useFindTemplates = () => {
const workpadService = useWorkpadService();
return useCallback(async () => await workpadService.findTemplates(), [workpadService]);
};
-
-export const useFindTemplatesOnMount = (): [boolean, TemplateFindResponse] => {
- const [isMounted, setIsMounted] = useState(false);
- const findTemplates = useFindTemplates();
- const [templateResponse, setTemplateResponse] = useState(emptyResponse);
-
- const fetchTemplates = useCallback(async () => {
- const foundTemplates = await findTemplates();
- setTemplateResponse(foundTemplates || emptyResponse);
- setIsMounted(true);
- }, [findTemplates]);
-
- useMount(() => {
- fetchTemplates();
- return () => setIsMounted(false);
- });
-
- return [isMounted, templateResponse];
-};
diff --git a/x-pack/plugins/canvas/public/components/home/hooks/use_find_workpad.ts b/x-pack/plugins/canvas/public/components/home/hooks/use_find_workpad.ts
index 3f8b0e6f630f5a..10352d0472e8c3 100644
--- a/x-pack/plugins/canvas/public/components/home/hooks/use_find_workpad.ts
+++ b/x-pack/plugins/canvas/public/components/home/hooks/use_find_workpad.ts
@@ -5,14 +5,10 @@
* 2.0.
*/
-import { useState, useCallback } from 'react';
-import useMount from 'react-use/lib/useMount';
+import { useCallback } from 'react';
import { i18n } from '@kbn/i18n';
-import { WorkpadFindResponse } from '../../../services/workpad';
-
import { useNotifyService, useWorkpadService } from '../../../services';
-const emptyResponse = { total: 0, workpads: [] };
export const useFindWorkpads = () => {
const workpadService = useWorkpadService();
@@ -30,25 +26,6 @@ export const useFindWorkpads = () => {
);
};
-export const useFindWorkpadsOnMount = (): [boolean, WorkpadFindResponse] => {
- const [isMounted, setIsMounted] = useState(false);
- const findWorkpads = useFindWorkpads();
- const [workpadResponse, setWorkpadResponse] = useState(emptyResponse);
-
- const fetchWorkpads = useCallback(async () => {
- const foundWorkpads = await findWorkpads();
- setWorkpadResponse(foundWorkpads || emptyResponse);
- setIsMounted(true);
- }, [findWorkpads]);
-
- useMount(() => {
- fetchWorkpads();
- return () => setIsMounted(false);
- });
-
- return [isMounted, workpadResponse];
-};
-
const errors = {
getFindFailureErrorMessage: () =>
i18n.translate('xpack.canvas.error.useFindWorkpads.findFailureErrorMessage', {
diff --git a/x-pack/plugins/canvas/public/components/home/my_workpads/loading.tsx b/x-pack/plugins/canvas/public/components/home/loading.tsx
similarity index 100%
rename from x-pack/plugins/canvas/public/components/home/my_workpads/loading.tsx
rename to x-pack/plugins/canvas/public/components/home/loading.tsx
diff --git a/x-pack/plugins/canvas/public/components/home/my_workpads/my_workpads.tsx b/x-pack/plugins/canvas/public/components/home/my_workpads/my_workpads.tsx
index 4242e2e9d130f7..f5d41313ff5712 100644
--- a/x-pack/plugins/canvas/public/components/home/my_workpads/my_workpads.tsx
+++ b/x-pack/plugins/canvas/public/components/home/my_workpads/my_workpads.tsx
@@ -6,9 +6,9 @@
*/
import React, { useState, useEffect, createContext, Dispatch, SetStateAction } from 'react';
-import { useFindWorkpadsOnMount } from './../hooks';
+import { useFindWorkpads } from './../hooks';
import { FoundWorkpad } from '../../../services/workpad';
-import { Loading } from './loading';
+import { Loading } from '../loading';
import { MyWorkpads as Component } from './my_workpads.component';
interface Context {
@@ -19,12 +19,18 @@ interface Context {
export const WorkpadsContext = createContext(null);
export const MyWorkpads = () => {
- const [isMounted, workpadResponse] = useFindWorkpadsOnMount();
- const [workpads, setWorkpads] = useState(workpadResponse.workpads);
+ const findWorkpads = useFindWorkpads();
+ const [isMounted, setIsMounted] = useState(false);
+ const [workpads, setWorkpads] = useState([]);
useEffect(() => {
- setWorkpads(workpadResponse.workpads);
- }, [workpadResponse]);
+ const mount = async () => {
+ const response = await findWorkpads('');
+ setIsMounted(true);
+ setWorkpads(response?.workpads || []);
+ };
+ mount();
+ }, [setIsMounted, findWorkpads]);
if (!isMounted) {
return ;
diff --git a/x-pack/plugins/canvas/public/components/home/workpad_templates/workpad_templates.tsx b/x-pack/plugins/canvas/public/components/home/workpad_templates/workpad_templates.tsx
index 352285e66424b7..6171c05e11c8e8 100644
--- a/x-pack/plugins/canvas/public/components/home/workpad_templates/workpad_templates.tsx
+++ b/x-pack/plugins/canvas/public/components/home/workpad_templates/workpad_templates.tsx
@@ -5,27 +5,33 @@
* 2.0.
*/
-import React from 'react';
-import { EuiFlexGroup, EuiFlexItem, EuiLoadingSpinner } from '@elastic/eui';
+import React, { useState, useEffect } from 'react';
-import { useCreateFromTemplate, useFindTemplatesOnMount } from '../hooks';
+import { useCreateFromTemplate, useFindTemplates } from '../hooks';
import { WorkpadTemplates as Component } from './workpad_templates.component';
+import { CanvasTemplate } from '../../../../types';
+import { Loading } from '../loading';
export const WorkpadTemplates = () => {
- const [isMounted, templateResponse] = useFindTemplatesOnMount();
+ const findTemplates = useFindTemplates();
+ const [isMounted, setIsMounted] = useState(false);
+ const [templates, setTemplates] = useState([]);
+
+ useEffect(() => {
+ const mount = async () => {
+ const response = await findTemplates();
+ setIsMounted(true);
+ setTemplates(response?.templates || []);
+ };
+ mount();
+ }, [setIsMounted, findTemplates]);
+
const onCreateWorkpad = useCreateFromTemplate();
if (!isMounted) {
- return (
-
-
-
-
-
- );
+ return ;
}
- const { templates } = templateResponse;
return ;
};
diff --git a/x-pack/plugins/canvas/public/components/toolbar/toolbar.component.tsx b/x-pack/plugins/canvas/public/components/toolbar/toolbar.component.tsx
index 13cc4db7c6217c..dca549b6b38edb 100644
--- a/x-pack/plugins/canvas/public/components/toolbar/toolbar.component.tsx
+++ b/x-pack/plugins/canvas/public/components/toolbar/toolbar.component.tsx
@@ -93,43 +93,50 @@ export const Toolbar: FC = ({
{activeTray !== null &&
setActiveTray(null)}>{trays[activeTray]} }
-
-
-
-
-
- toggleTray('pageManager')}>
- {strings.getPageButtonLabel(selectedPageNumber, totalPages)}
-
+
+
+ {workpadName}
- = totalPages}
- aria-label={strings.getNextPageAriaLabel()}
- />
+
+
+
+
+
+ toggleTray('pageManager')}>
+ {strings.getPageButtonLabel(selectedPageNumber, totalPages)}
+
+
+
+ = totalPages}
+ aria-label={strings.getNextPageAriaLabel()}
+ />
+
+
+ {elementIsSelected && isWriteable && (
+
+ toggleTray('expression')}
+ data-test-subj="canvasExpressionEditorButton"
+ >
+ {strings.getEditorButtonLabel()}
+
+
+ )}
+
-
- {elementIsSelected && isWriteable && (
-
- toggleTray('expression')}
- data-test-subj="canvasExpressionEditorButton"
- >
- {strings.getEditorButtonLabel()}
-
-
- )}
diff --git a/x-pack/plugins/canvas/public/components/toolbar/toolbar.scss b/x-pack/plugins/canvas/public/components/toolbar/toolbar.scss
index c46a2ec7a1e220..9433fdddbc8bb2 100644
--- a/x-pack/plugins/canvas/public/components/toolbar/toolbar.scss
+++ b/x-pack/plugins/canvas/public/components/toolbar/toolbar.scss
@@ -20,6 +20,11 @@
}
}
+.canvasToolbar__home {
+ padding: $euiSizeM 0 $euiSizeM $euiSizeL;
+ height: 100%;
+}
+
.canvasToolbar__controls {
padding: $euiSizeM;
height: 100%;
diff --git a/x-pack/plugins/canvas/storybook/public/components/home/my_workpads/__snapshots__/empty_prompt.stories.storyshot b/x-pack/plugins/canvas/storybook/public/components/home/my_workpads/__snapshots__/empty_prompt.stories.storyshot
deleted file mode 100644
index 39ec1e234ead54..00000000000000
--- a/x-pack/plugins/canvas/storybook/public/components/home/my_workpads/__snapshots__/empty_prompt.stories.storyshot
+++ /dev/null
@@ -1,65 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`Storyshots Home/Empty Prompt Empty Prompt 1`] = `
-
-
-
-
-
-
-
-
- Add your first workpad
-
-
-
-
- Create a new workpad, start from a template, or import a workpad JSON file by dropping it here.
-
-
- New to Canvas?
-
-
- Add your first workpad
-
- .
-
-
-
-
-
-
-
-`;
From 5f35d23416bd8fc99c4c5dfc72f3b922f78ed7be Mon Sep 17 00:00:00 2001
From: Tiago Costa
Date: Wed, 30 Jun 2021 04:33:02 +0100
Subject: [PATCH 033/121] chore(NA): moving @kbn/es-archiver into bazel
(#103770)
---
.../monorepo-packages.asciidoc | 1 +
package.json | 2 +-
packages/BUILD.bazel | 1 +
packages/kbn-es-archiver/BUILD.bazel | 100 ++++++++++++++++++
packages/kbn-es-archiver/package.json | 4 -
packages/kbn-es-archiver/tsconfig.json | 2 +-
yarn.lock | 2 +-
7 files changed, 105 insertions(+), 7 deletions(-)
create mode 100644 packages/kbn-es-archiver/BUILD.bazel
diff --git a/docs/developer/getting-started/monorepo-packages.asciidoc b/docs/developer/getting-started/monorepo-packages.asciidoc
index 06e87fa1300297..b656405b173d82 100644
--- a/docs/developer/getting-started/monorepo-packages.asciidoc
+++ b/docs/developer/getting-started/monorepo-packages.asciidoc
@@ -77,6 +77,7 @@ yarn kbn watch-bazel
- @kbn/dev-utils
- @kbn/docs-utils
- @kbn/es
+- @kbn/es-archiver
- @kbn/eslint-import-resolver-kibana
- @kbn/eslint-plugin-eslint
- @kbn/expect
diff --git a/package.json b/package.json
index 99dad59044bf40..1cdeed4189cd32 100644
--- a/package.json
+++ b/package.json
@@ -462,7 +462,7 @@
"@kbn/dev-utils": "link:bazel-bin/packages/kbn-dev-utils",
"@kbn/docs-utils": "link:bazel-bin/packages/kbn-docs-utils",
"@kbn/es": "link:bazel-bin/packages/kbn-es",
- "@kbn/es-archiver": "link:packages/kbn-es-archiver",
+ "@kbn/es-archiver": "link:bazel-bin/packages/kbn-es-archiver",
"@kbn/eslint-import-resolver-kibana": "link:bazel-bin/packages/kbn-eslint-import-resolver-kibana",
"@kbn/eslint-plugin-eslint": "link:bazel-bin/packages/kbn-eslint-plugin-eslint",
"@kbn/expect": "link:bazel-bin/packages/kbn-expect",
diff --git a/packages/BUILD.bazel b/packages/BUILD.bazel
index 6ffd5ff151ac3b..de7a27fd512769 100644
--- a/packages/BUILD.bazel
+++ b/packages/BUILD.bazel
@@ -20,6 +20,7 @@ filegroup(
"//packages/kbn-dev-utils:build",
"//packages/kbn-docs-utils:build",
"//packages/kbn-es:build",
+ "//packages/kbn-es-archiver:build",
"//packages/kbn-eslint-import-resolver-kibana:build",
"//packages/kbn-eslint-plugin-eslint:build",
"//packages/kbn-expect:build",
diff --git a/packages/kbn-es-archiver/BUILD.bazel b/packages/kbn-es-archiver/BUILD.bazel
new file mode 100644
index 00000000000000..9b3db311afa248
--- /dev/null
+++ b/packages/kbn-es-archiver/BUILD.bazel
@@ -0,0 +1,100 @@
+load("@npm//@bazel/typescript:index.bzl", "ts_config", "ts_project")
+load("@build_bazel_rules_nodejs//:index.bzl", "js_library", "pkg_npm")
+
+PKG_BASE_NAME = "kbn-es-archiver"
+PKG_REQUIRE_NAME = "@kbn/es-archiver"
+
+SOURCE_FILES = glob(
+ [
+ "src/**/*.ts",
+ ],
+ exclude = [
+ "**/*.test.*",
+ "**/__fixtures__",
+ "**/__mocks__",
+ "**/__snapshots__"
+ ],
+)
+
+SRCS = SOURCE_FILES
+
+filegroup(
+ name = "srcs",
+ srcs = SRCS,
+)
+
+NPM_MODULE_EXTRA_FILES = [
+ "package.json",
+]
+
+SRC_DEPS = [
+ "//packages/kbn-dev-utils",
+ "//packages/kbn-test",
+ "//packages/kbn-utils",
+ "@npm//@elastic/elasticsearch",
+ "@npm//aggregate-error",
+ "@npm//bluebird",
+ "@npm//chance",
+ "@npm//globby",
+ "@npm//json-stable-stringify",
+ "@npm//lodash",
+ "@npm//sinon",
+ "@npm//zlib",
+]
+
+TYPES_DEPS = [
+ "@npm//@types/bluebird",
+ "@npm//@types/chance",
+ "@npm//@types/jest",
+ "@npm//@types/json-stable-stringify",
+ "@npm//@types/lodash",
+ "@npm//@types/node",
+ "@npm//@types/sinon",
+]
+
+DEPS = SRC_DEPS + TYPES_DEPS
+
+ts_config(
+ name = "tsconfig",
+ src = "tsconfig.json",
+ deps = [
+ "//:tsconfig.base.json",
+ ],
+)
+
+ts_project(
+ name = "tsc",
+ args = ['--pretty'],
+ srcs = SRCS,
+ deps = DEPS,
+ declaration = True,
+ declaration_map = True,
+ incremental = True,
+ out_dir = "target",
+ source_map = True,
+ root_dir = "src",
+ tsconfig = ":tsconfig",
+)
+
+js_library(
+ name = PKG_BASE_NAME,
+ srcs = NPM_MODULE_EXTRA_FILES,
+ deps = DEPS + [":tsc"],
+ package_name = PKG_REQUIRE_NAME,
+ visibility = ["//visibility:public"],
+)
+
+pkg_npm(
+ name = "npm_module",
+ deps = [
+ ":%s" % PKG_BASE_NAME,
+ ]
+)
+
+filegroup(
+ name = "build",
+ srcs = [
+ ":npm_module",
+ ],
+ visibility = ["//visibility:public"],
+)
diff --git a/packages/kbn-es-archiver/package.json b/packages/kbn-es-archiver/package.json
index b55e67b82694f5..e8eb7b5f8f1c98 100644
--- a/packages/kbn-es-archiver/package.json
+++ b/packages/kbn-es-archiver/package.json
@@ -7,9 +7,5 @@
"types": "target/index.d.ts",
"kibana": {
"devOnly": true
- },
- "scripts": {
- "kbn:bootstrap": "rm -rf target && ../../node_modules/.bin/tsc",
- "kbn:watch": "rm -rf target && ../../node_modules/.bin/tsc --watch"
}
}
diff --git a/packages/kbn-es-archiver/tsconfig.json b/packages/kbn-es-archiver/tsconfig.json
index 0950cd39d0bee4..1bc93908a993e4 100644
--- a/packages/kbn-es-archiver/tsconfig.json
+++ b/packages/kbn-es-archiver/tsconfig.json
@@ -1,7 +1,7 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
- "incremental": false,
+ "incremental": true,
"outDir": "./target",
"target": "ES2019",
"declaration": true,
diff --git a/yarn.lock b/yarn.lock
index 589f07e7a35d83..10cbb9789ba984 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2645,7 +2645,7 @@
version "0.0.0"
uid ""
-"@kbn/es-archiver@link:packages/kbn-es-archiver":
+"@kbn/es-archiver@link:bazel-bin/packages/kbn-es-archiver":
version "0.0.0"
uid ""
From 0324a06bd92777834e760c924e73afa764d708d8 Mon Sep 17 00:00:00 2001
From: Mat Schaffer
Date: Wed, 30 Jun 2021 13:16:13 +0900
Subject: [PATCH 034/121] Update format_number time test for APJ timezones
(#102691)
* Update format_number test for APJ timezones
* Switch asertion to optional leading 1
* Allow leading 1 or 2
In EMEA timezones H:mm:ss can return 20:42:17
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
---
x-pack/plugins/monitoring/public/lib/format_number.test.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/x-pack/plugins/monitoring/public/lib/format_number.test.js b/x-pack/plugins/monitoring/public/lib/format_number.test.js
index 551ec1e6a40962..b93f9101a4df2f 100644
--- a/x-pack/plugins/monitoring/public/lib/format_number.test.js
+++ b/x-pack/plugins/monitoring/public/lib/format_number.test.js
@@ -32,7 +32,7 @@ describe('format_number', () => {
});
it('should format time in H:mm:ss', () => {
- expect(formatNumber(1461868937000, 'time')).to.match(/\d\d:\d\d:\d\d/);
+ expect(formatNumber(1461868937000, 'time')).to.match(/[12]?\d:\d\d:\d\d/);
});
it('should format integers with commas', () => {
From 0fb21c49b9bde076d653d730ac6e98c7b28da7da Mon Sep 17 00:00:00 2001
From: Lisa Cawley
Date: Tue, 29 Jun 2021 21:47:38 -0700
Subject: [PATCH 035/121] Adds ECS guide to doc links service (#102246)
---
.../public/kibana-plugin-core-public.doclinksstart.links.md | 3 +++
.../core/public/kibana-plugin-core-public.doclinksstart.md | 2 +-
src/core/public/doc_links/doc_links_service.ts | 6 ++++++
src/core/public/public.api.md | 3 +++
4 files changed, 13 insertions(+), 1 deletion(-)
diff --git a/docs/development/core/public/kibana-plugin-core-public.doclinksstart.links.md b/docs/development/core/public/kibana-plugin-core-public.doclinksstart.links.md
index 63d791db452d04..8754d19e2fc138 100644
--- a/docs/development/core/public/kibana-plugin-core-public.doclinksstart.links.md
+++ b/docs/development/core/public/kibana-plugin-core-public.doclinksstart.links.md
@@ -201,5 +201,8 @@ readonly links: {
upgradeElasticAgent: string;
upgradeElasticAgent712lower: string;
}>;
+ readonly ecs: {
+ readonly guide: string;
+ };
};
```
diff --git a/docs/development/core/public/kibana-plugin-core-public.doclinksstart.md b/docs/development/core/public/kibana-plugin-core-public.doclinksstart.md
index 947eece498130e..7dbb8a58694853 100644
--- a/docs/development/core/public/kibana-plugin-core-public.doclinksstart.md
+++ b/docs/development/core/public/kibana-plugin-core-public.doclinksstart.md
@@ -17,5 +17,5 @@ export interface DocLinksStart
| --- | --- | --- |
| [DOC\_LINK\_VERSION](./kibana-plugin-core-public.doclinksstart.doc_link_version.md) | string
| |
| [ELASTIC\_WEBSITE\_URL](./kibana-plugin-core-public.doclinksstart.elastic_website_url.md) | string
| |
-| [links](./kibana-plugin-core-public.doclinksstart.links.md) | {
readonly settings: string;
readonly canvas: {
readonly guide: string;
};
readonly dashboard: {
readonly guide: string;
readonly drilldowns: string;
readonly drilldownsTriggerPicker: string;
readonly urlDrilldownTemplateSyntax: string;
readonly urlDrilldownVariables: string;
};
readonly discover: Record<string, string>;
readonly filebeat: {
readonly base: string;
readonly installation: string;
readonly configuration: string;
readonly elasticsearchOutput: string;
readonly elasticsearchModule: string;
readonly startup: string;
readonly exportedFields: string;
};
readonly auditbeat: {
readonly base: string;
};
readonly metricbeat: {
readonly base: string;
readonly configure: string;
readonly httpEndpoint: string;
readonly install: string;
readonly start: string;
};
readonly enterpriseSearch: {
readonly base: string;
readonly appSearchBase: string;
readonly workplaceSearchBase: string;
};
readonly heartbeat: {
readonly base: string;
};
readonly logstash: {
readonly base: string;
};
readonly functionbeat: {
readonly base: string;
};
readonly winlogbeat: {
readonly base: string;
};
readonly aggs: {
readonly composite: string;
readonly composite_missing_bucket: string;
readonly date_histogram: string;
readonly date_range: string;
readonly date_format_pattern: string;
readonly filter: string;
readonly filters: string;
readonly geohash_grid: string;
readonly histogram: string;
readonly ip_range: string;
readonly range: string;
readonly significant_terms: string;
readonly terms: string;
readonly avg: string;
readonly avg_bucket: string;
readonly max_bucket: string;
readonly min_bucket: string;
readonly sum_bucket: string;
readonly cardinality: string;
readonly count: string;
readonly cumulative_sum: string;
readonly derivative: string;
readonly geo_bounds: string;
readonly geo_centroid: string;
readonly max: string;
readonly median: string;
readonly min: string;
readonly moving_avg: string;
readonly percentile_ranks: string;
readonly serial_diff: string;
readonly std_dev: string;
readonly sum: string;
readonly top_hits: string;
};
readonly runtimeFields: {
readonly overview: string;
readonly mapping: string;
};
readonly scriptedFields: {
readonly scriptFields: string;
readonly scriptAggs: string;
readonly painless: string;
readonly painlessApi: string;
readonly painlessLangSpec: string;
readonly painlessSyntax: string;
readonly painlessWalkthrough: string;
readonly luceneExpressions: string;
};
readonly search: {
readonly sessions: string;
readonly sessionLimits: string;
};
readonly indexPatterns: {
readonly introduction: string;
readonly fieldFormattersNumber: string;
readonly fieldFormattersString: string;
readonly runtimeFields: string;
};
readonly addData: string;
readonly kibana: string;
readonly upgradeAssistant: string;
readonly rollupJobs: string;
readonly elasticsearch: Record<string, string>;
readonly siem: {
readonly guide: string;
readonly gettingStarted: string;
};
readonly query: {
readonly eql: string;
readonly kueryQuerySyntax: string;
readonly luceneQuerySyntax: string;
readonly percolate: string;
readonly queryDsl: string;
};
readonly date: {
readonly dateMath: string;
readonly dateMathIndexNames: string;
};
readonly management: Record<string, string>;
readonly ml: Record<string, string>;
readonly transforms: Record<string, string>;
readonly visualize: Record<string, string>;
readonly apis: Readonly<{
bulkIndexAlias: string;
byteSizeUnits: string;
createAutoFollowPattern: string;
createFollower: string;
createIndex: string;
createSnapshotLifecyclePolicy: string;
createRoleMapping: string;
createRoleMappingTemplates: string;
createRollupJobsRequest: string;
createApiKey: string;
createPipeline: string;
createTransformRequest: string;
cronExpressions: string;
executeWatchActionModes: string;
indexExists: string;
openIndex: string;
putComponentTemplate: string;
painlessExecute: string;
painlessExecuteAPIContexts: string;
putComponentTemplateMetadata: string;
putSnapshotLifecyclePolicy: string;
putIndexTemplateV1: string;
putWatch: string;
simulatePipeline: string;
timeUnits: string;
updateTransform: string;
}>;
readonly observability: Record<string, string>;
readonly alerting: Record<string, string>;
readonly maps: Record<string, string>;
readonly monitoring: Record<string, string>;
readonly security: Readonly<{
apiKeyServiceSettings: string;
clusterPrivileges: string;
elasticsearchSettings: string;
elasticsearchEnableSecurity: string;
indicesPrivileges: string;
kibanaTLS: string;
kibanaPrivileges: string;
mappingRoles: string;
mappingRolesFieldRules: string;
runAsPrivilege: string;
}>;
readonly watcher: Record<string, string>;
readonly ccs: Record<string, string>;
readonly plugins: Record<string, string>;
readonly snapshotRestore: Record<string, string>;
readonly ingest: Record<string, string>;
readonly fleet: Readonly<{
guide: string;
fleetServer: string;
fleetServerAddFleetServer: string;
settings: string;
settingsFleetServerHostSettings: string;
troubleshooting: string;
elasticAgent: string;
datastreams: string;
datastreamsNamingScheme: string;
upgradeElasticAgent: string;
upgradeElasticAgent712lower: string;
}>;
}
| |
+| [links](./kibana-plugin-core-public.doclinksstart.links.md) | {
readonly settings: string;
readonly canvas: {
readonly guide: string;
};
readonly dashboard: {
readonly guide: string;
readonly drilldowns: string;
readonly drilldownsTriggerPicker: string;
readonly urlDrilldownTemplateSyntax: string;
readonly urlDrilldownVariables: string;
};
readonly discover: Record<string, string>;
readonly filebeat: {
readonly base: string;
readonly installation: string;
readonly configuration: string;
readonly elasticsearchOutput: string;
readonly elasticsearchModule: string;
readonly startup: string;
readonly exportedFields: string;
};
readonly auditbeat: {
readonly base: string;
};
readonly metricbeat: {
readonly base: string;
readonly configure: string;
readonly httpEndpoint: string;
readonly install: string;
readonly start: string;
};
readonly enterpriseSearch: {
readonly base: string;
readonly appSearchBase: string;
readonly workplaceSearchBase: string;
};
readonly heartbeat: {
readonly base: string;
};
readonly logstash: {
readonly base: string;
};
readonly functionbeat: {
readonly base: string;
};
readonly winlogbeat: {
readonly base: string;
};
readonly aggs: {
readonly composite: string;
readonly composite_missing_bucket: string;
readonly date_histogram: string;
readonly date_range: string;
readonly date_format_pattern: string;
readonly filter: string;
readonly filters: string;
readonly geohash_grid: string;
readonly histogram: string;
readonly ip_range: string;
readonly range: string;
readonly significant_terms: string;
readonly terms: string;
readonly avg: string;
readonly avg_bucket: string;
readonly max_bucket: string;
readonly min_bucket: string;
readonly sum_bucket: string;
readonly cardinality: string;
readonly count: string;
readonly cumulative_sum: string;
readonly derivative: string;
readonly geo_bounds: string;
readonly geo_centroid: string;
readonly max: string;
readonly median: string;
readonly min: string;
readonly moving_avg: string;
readonly percentile_ranks: string;
readonly serial_diff: string;
readonly std_dev: string;
readonly sum: string;
readonly top_hits: string;
};
readonly runtimeFields: {
readonly overview: string;
readonly mapping: string;
};
readonly scriptedFields: {
readonly scriptFields: string;
readonly scriptAggs: string;
readonly painless: string;
readonly painlessApi: string;
readonly painlessLangSpec: string;
readonly painlessSyntax: string;
readonly painlessWalkthrough: string;
readonly luceneExpressions: string;
};
readonly search: {
readonly sessions: string;
readonly sessionLimits: string;
};
readonly indexPatterns: {
readonly introduction: string;
readonly fieldFormattersNumber: string;
readonly fieldFormattersString: string;
readonly runtimeFields: string;
};
readonly addData: string;
readonly kibana: string;
readonly upgradeAssistant: string;
readonly rollupJobs: string;
readonly elasticsearch: Record<string, string>;
readonly siem: {
readonly guide: string;
readonly gettingStarted: string;
};
readonly query: {
readonly eql: string;
readonly kueryQuerySyntax: string;
readonly luceneQuerySyntax: string;
readonly percolate: string;
readonly queryDsl: string;
};
readonly date: {
readonly dateMath: string;
readonly dateMathIndexNames: string;
};
readonly management: Record<string, string>;
readonly ml: Record<string, string>;
readonly transforms: Record<string, string>;
readonly visualize: Record<string, string>;
readonly apis: Readonly<{
bulkIndexAlias: string;
byteSizeUnits: string;
createAutoFollowPattern: string;
createFollower: string;
createIndex: string;
createSnapshotLifecyclePolicy: string;
createRoleMapping: string;
createRoleMappingTemplates: string;
createRollupJobsRequest: string;
createApiKey: string;
createPipeline: string;
createTransformRequest: string;
cronExpressions: string;
executeWatchActionModes: string;
indexExists: string;
openIndex: string;
putComponentTemplate: string;
painlessExecute: string;
painlessExecuteAPIContexts: string;
putComponentTemplateMetadata: string;
putSnapshotLifecyclePolicy: string;
putIndexTemplateV1: string;
putWatch: string;
simulatePipeline: string;
timeUnits: string;
updateTransform: string;
}>;
readonly observability: Record<string, string>;
readonly alerting: Record<string, string>;
readonly maps: Record<string, string>;
readonly monitoring: Record<string, string>;
readonly security: Readonly<{
apiKeyServiceSettings: string;
clusterPrivileges: string;
elasticsearchSettings: string;
elasticsearchEnableSecurity: string;
indicesPrivileges: string;
kibanaTLS: string;
kibanaPrivileges: string;
mappingRoles: string;
mappingRolesFieldRules: string;
runAsPrivilege: string;
}>;
readonly watcher: Record<string, string>;
readonly ccs: Record<string, string>;
readonly plugins: Record<string, string>;
readonly snapshotRestore: Record<string, string>;
readonly ingest: Record<string, string>;
readonly fleet: Readonly<{
guide: string;
fleetServer: string;
fleetServerAddFleetServer: string;
settings: string;
settingsFleetServerHostSettings: string;
troubleshooting: string;
elasticAgent: string;
datastreams: string;
datastreamsNamingScheme: string;
upgradeElasticAgent: string;
upgradeElasticAgent712lower: string;
}>;
readonly ecs: {
readonly guide: string;
};
}
| |
diff --git a/src/core/public/doc_links/doc_links_service.ts b/src/core/public/doc_links/doc_links_service.ts
index 9206a4d1b99f1e..fcc12f43ec5311 100644
--- a/src/core/public/doc_links/doc_links_service.ts
+++ b/src/core/public/doc_links/doc_links_service.ts
@@ -418,6 +418,9 @@ export class DocLinksService {
upgradeElasticAgent: `${FLEET_DOCS}upgrade-elastic-agent.html`,
upgradeElasticAgent712lower: `${FLEET_DOCS}upgrade-elastic-agent.html#upgrade-7.12-lower`,
},
+ ecs: {
+ guide: `${ELASTIC_WEBSITE_URL}guide/en/ecs/current/index.html`,
+ },
},
});
}
@@ -621,5 +624,8 @@ export interface DocLinksStart {
upgradeElasticAgent: string;
upgradeElasticAgent712lower: string;
}>;
+ readonly ecs: {
+ readonly guide: string;
+ };
};
}
diff --git a/src/core/public/public.api.md b/src/core/public/public.api.md
index 8b87c21e22fa47..3f8184bea97fee 100644
--- a/src/core/public/public.api.md
+++ b/src/core/public/public.api.md
@@ -680,6 +680,9 @@ export interface DocLinksStart {
upgradeElasticAgent: string;
upgradeElasticAgent712lower: string;
}>;
+ readonly ecs: {
+ readonly guide: string;
+ };
};
}
From 569c209f2c4ceca903d0c7e68b9611dc151601ee Mon Sep 17 00:00:00 2001
From: Ryland Herrick
Date: Wed, 30 Jun 2021 00:16:24 -0500
Subject: [PATCH 036/121] [Security Solution][CTI] Investigation time
enrichment UI (#103383)
* Add pure fn and consuming hook to fetch event enrichment
It's not being invoked yet, but I've added a placeholder where it's
going.
* Move existing enrichment tests to new spec file
This is a rough copy/paste, I'll clean up as I flesh out the new tests.
* Move test constants into tests that use them
* style: declare FC function as an FC
* Extract some inline parsing logic into a helper function
And test it!
* Solidifying enrichment types on the backend
* Declares an enum for our types
* Sets type during indicator match rule enrichment
* Sets type during investigation-time enrichment
* WIP: Enrichment rows are rendered on the alerts summary
There are lots of TODOs here, but this implements the following:
* Fetching investigation-time enrichments from the backend
* Parsing existing enrichments from timeline data
* Merging the two enrichment types together, and rendering them in rows
as specified
Much of the data-fetching is hardcoded, and this broke the existing
pattern with SummaryView/SummaryRow so that got a little messy; I may
end up just using my own EuiTable but we'll see.
Threat Intel tab is currently broken; that's up next.
* Updates ThreatDetailsView to accept an array of enrichments
The investigation-time enrichments are a little messy because they
contain all the non-ECS fields that indicators contain; other than that,
this is looking good.
Still need to add the new header, and potentially sort the fields.
* Sort our details fields
This promotes sanity for the user.
* Add "view threat intel data" button
This simply opens the threat intel tab.
* Implement header for threat details sections
* Add a basic jest "unit" test around ThreatSummaryView
* Fix remaining tests for components we modified
This also addresses a bug where we were not properly sorting new
enrichments by first_seen; this is covered under the tests that were
fixed.
* Filter out duplicate investigation-time enrichments
Because the enrichment endpoint is dumb and doesn't know about the
existing event or its enrichments, we need to merge these together on
the client to reduce noise and redundant data.
* Add inspect button to investigation enrichments
* Massages the response into the format that the inspect component uses
* Moves stateful fetching of query and persisting in redux to new, more
specialized hook
* Moves existing enrichment hook to a more suitable location in
containers/
* Fix failing unit tests
* indicator match rule now specifies `matched.type` as coming from the
rule
* Inspecting the enrichment query requires use of the redux store, which
was not previously mocked
* Fix existing CTI cypress tests
This covers the basics of the Alert Summary and Threat Intel tabs; the
investigation-time enrichment functionality is up next.
* Adds a cypress test exercising investigation time enrichment
* Loads more indicators (filebeat data, `threat_indicator2` archive)
AFTER the rule has executed
* Asserts that those indicators are also found on the alert summary.
* Populate event enrichment call with actual alert fields
This was previously hardcoded during development.
* Add a new field to our suspicious event to trigger enrichment
The existing myhash field will generate an alert due to the way the rule
is written, but the alert had no other fields that would match the
investigation time enrichment. This gives it a source.ip, and updates
the indicator to match.
* Only fetch enrichments data if there are valid event fields
If none of the alert's fields would be relevant to the enrichment query,
then we don't make the request at all.
* Update enrichments matched.typed in integration tests
This field was updated to reflect the source of the match, in this case:
indicator match rules.
* Ensure draggable fields are unique in a multi-match scenario
If a given field matched multiple indicators, then the previous
contextId was not unique as it was based on field/value that matched.
Adding provider to the mix would fix it, except that we're not
guaranteed to have a provider.
I've added both provider (if present) and an index value to the key to
ensure that it's unique.
* Simplify types
This field can never be null, as we always set it in our response.
* Move helper functioons out of shared location and into consuming component
These are unlikely to be used elsewhere.
* Clean up data parsing logic using reduce
This obviates the need for our filter/guard function and the extra loop
that it entails. We have to specify the return value of our reduce fn,
however, but that's mostly equivalent to our type guard.
* Move our general function into a general location
* Extract the concept of "enrichment identifiers"
This was already partially codified with 'buildEnrichmentId,' which is
used to dedup enrichments; this extends the idea to all fields that
could uniquely identify a given indicator.
* Use existing constant as the source of our enrichments query
This is now used by both the overview card and the enrichment query.
* Codify our default enrichment lookback as constants
* Remove unnecessary flexbox
The generic SummaryView component previously had to deal with
multi-valued CTI fields, representing the multiple values coming from
the multiple nested objects with that field.
However, with the new UI we no longer have that constraint, and so the
default columnar style, and the corresponding overriding styles, are no
longer necessary.
* Filter out partial responses in the event enrichment observable
The UI does not currently handle these. We need to test the behavior of
long-running queries with this filter, but this should simplify the
behavior to complete/error until we handle partial responses.
* Display placeholders while event enrichment is loading
Displays a loading spinner in the Threat Intel tab title, and some
loading lines where the enrichments summary is.
* Update our indicator data to be within the last 30 days
This fixes our cypress test, but it's going to start failing again in 30
days. However, by that time I'll have implemented the absolute data
picker, which will allow for a more comprehensive test in addition to us
sidestepping this issue.
* Fix type error with our details tabs
The name prop on a Tab will be rendered as a node, so both strings and
elements are acceptable. This relaxes the types to inherit from the
component itself.
* Fix failing jest tests
The addition of our filtering of the search observable broke this test,
since we now need to implement the search observable.
Rather than do that, we'll instead mock our local hook as that's more
likely to change.
---
.../security_solution/common/cti/constants.ts | 18 +-
.../security_solution/cti/index.mock.ts | 55 +-
.../security_solution/cti/index.ts | 18 +-
.../common/utils/data_retrieval.test.ts | 26 +
.../common/utils/data_retrieval.ts | 15 +
.../detection_alerts/cti_enrichments.spec.ts | 193 ++++
.../indicator_match_rule.spec.ts | 176 +---
.../cypress/screens/alerts_details.ts | 2 +-
.../empty_threat_details_view.test.tsx | 10 +-
.../empty_threat_details_view.tsx | 3 +-
.../cti_details/enrichment_icon.tsx | 30 +
.../cti_details/helpers.test.tsx | 477 ++++++++++
.../event_details/cti_details/helpers.tsx | 123 +++
.../cti_details/threat_details_view.test.tsx | 88 ++
.../cti_details/threat_details_view.tsx | 163 ++++
.../cti_details/threat_summary_view.test.tsx | 41 +
.../cti_details/threat_summary_view.tsx | 141 +++
.../event_details/cti_details/translations.ts | 71 ++
.../event_details/event_details.test.tsx | 3 +
.../event_details/event_details.tsx | 114 ++-
.../components/event_details/helpers.tsx | 13 +-
.../components/event_details/summary_view.tsx | 8 +-
.../threat_details_view.test.tsx | 114 ---
.../event_details/threat_details_view.tsx | 122 ---
.../threat_summary_view.test.tsx | 43 -
.../event_details/threat_summary_view.tsx | 78 --
.../components/event_details/translations.ts | 19 +-
.../containers/cti/event_enrichment/api.ts | 54 ++
.../containers/cti/event_enrichment/index.ts | 9 +
.../cti/event_enrichment/translations.ts | 15 +
.../event_enrichment/use_event_enrichment.ts | 19 +
.../use_investigation_enrichment.ts | 83 ++
.../enrich_signal_threat_matches.test.ts | 27 +-
.../enrich_signal_threat_matches.ts | 10 +-
.../factory/cti/event_enrichment/helpers.ts | 19 +-
.../tests/create_threat_matching.ts | 23 +-
.../suspicious_source_event/data.json | 3 +
.../es_archives/threat_indicator/data.json | 1 -
.../es_archives/threat_indicator2/data.json | 63 ++
.../threat_indicator2/mappings.json | 822 ++++++++++++++++++
40 files changed, 2662 insertions(+), 650 deletions(-)
create mode 100644 x-pack/plugins/security_solution/common/utils/data_retrieval.test.ts
create mode 100644 x-pack/plugins/security_solution/common/utils/data_retrieval.ts
create mode 100644 x-pack/plugins/security_solution/cypress/integration/detection_alerts/cti_enrichments.spec.ts
rename x-pack/plugins/security_solution/public/common/components/event_details/{ => cti_details}/empty_threat_details_view.test.tsx (84%)
rename x-pack/plugins/security_solution/public/common/components/event_details/{ => cti_details}/empty_threat_details_view.tsx (96%)
create mode 100644 x-pack/plugins/security_solution/public/common/components/event_details/cti_details/enrichment_icon.tsx
create mode 100644 x-pack/plugins/security_solution/public/common/components/event_details/cti_details/helpers.test.tsx
create mode 100644 x-pack/plugins/security_solution/public/common/components/event_details/cti_details/helpers.tsx
create mode 100644 x-pack/plugins/security_solution/public/common/components/event_details/cti_details/threat_details_view.test.tsx
create mode 100644 x-pack/plugins/security_solution/public/common/components/event_details/cti_details/threat_details_view.tsx
create mode 100644 x-pack/plugins/security_solution/public/common/components/event_details/cti_details/threat_summary_view.test.tsx
create mode 100644 x-pack/plugins/security_solution/public/common/components/event_details/cti_details/threat_summary_view.tsx
create mode 100644 x-pack/plugins/security_solution/public/common/components/event_details/cti_details/translations.ts
delete mode 100644 x-pack/plugins/security_solution/public/common/components/event_details/threat_details_view.test.tsx
delete mode 100644 x-pack/plugins/security_solution/public/common/components/event_details/threat_details_view.tsx
delete mode 100644 x-pack/plugins/security_solution/public/common/components/event_details/threat_summary_view.test.tsx
delete mode 100644 x-pack/plugins/security_solution/public/common/components/event_details/threat_summary_view.tsx
create mode 100644 x-pack/plugins/security_solution/public/common/containers/cti/event_enrichment/api.ts
create mode 100644 x-pack/plugins/security_solution/public/common/containers/cti/event_enrichment/index.ts
create mode 100644 x-pack/plugins/security_solution/public/common/containers/cti/event_enrichment/translations.ts
create mode 100644 x-pack/plugins/security_solution/public/common/containers/cti/event_enrichment/use_event_enrichment.ts
create mode 100644 x-pack/plugins/security_solution/public/common/containers/cti/event_enrichment/use_investigation_enrichment.ts
create mode 100644 x-pack/test/security_solution_cypress/es_archives/threat_indicator2/data.json
create mode 100644 x-pack/test/security_solution_cypress/es_archives/threat_indicator2/mappings.json
diff --git a/x-pack/plugins/security_solution/common/cti/constants.ts b/x-pack/plugins/security_solution/common/cti/constants.ts
index 4e935f3e497f43..631a13df1ecb1f 100644
--- a/x-pack/plugins/security_solution/common/cti/constants.ts
+++ b/x-pack/plugins/security_solution/common/cti/constants.ts
@@ -9,6 +9,7 @@ import { INDICATOR_DESTINATION_PATH } from '../constants';
export const MATCHED_ATOMIC = 'matched.atomic';
export const MATCHED_FIELD = 'matched.field';
+export const MATCHED_ID = 'matched.id';
export const MATCHED_TYPE = 'matched.type';
export const INDICATOR_MATCH_SUBFIELDS = [MATCHED_ATOMIC, MATCHED_FIELD, MATCHED_TYPE];
@@ -18,11 +19,12 @@ export const INDICATOR_MATCHED_TYPE = `${INDICATOR_DESTINATION_PATH}.${MATCHED_T
export const EVENT_DATASET = 'event.dataset';
export const EVENT_REFERENCE = 'event.reference';
+export const EVENT_URL = 'event.url';
export const PROVIDER = 'provider';
export const FIRSTSEEN = 'first_seen';
export const INDICATOR_DATASET = `${INDICATOR_DESTINATION_PATH}.${EVENT_DATASET}`;
-export const INDICATOR_EVENT_URL = `${INDICATOR_DESTINATION_PATH}.event.url`;
+export const INDICATOR_EVENT_URL = `${INDICATOR_DESTINATION_PATH}.${EVENT_URL}`;
export const INDICATOR_FIRSTSEEN = `${INDICATOR_DESTINATION_PATH}.${FIRSTSEEN}`;
export const INDICATOR_LASTSEEN = `${INDICATOR_DESTINATION_PATH}.last_seen`;
export const INDICATOR_PROVIDER = `${INDICATOR_DESTINATION_PATH}.${PROVIDER}`;
@@ -37,13 +39,10 @@ export const CTI_ROW_RENDERER_FIELDS = [
INDICATOR_PROVIDER,
];
-export const SORTED_THREAT_SUMMARY_FIELDS = [
- INDICATOR_MATCHED_FIELD,
- INDICATOR_MATCHED_TYPE,
- INDICATOR_PROVIDER,
- INDICATOR_FIRSTSEEN,
- INDICATOR_LASTSEEN,
-];
+export enum ENRICHMENT_TYPES {
+ InvestigationTime = 'investigation_time',
+ IndicatorMatchRule = 'indicator_match_rule',
+}
export const EVENT_ENRICHMENT_INDICATOR_FIELD_MAP = {
'file.hash.md5': 'threatintel.indicator.file.hash.md5',
@@ -58,6 +57,9 @@ export const EVENT_ENRICHMENT_INDICATOR_FIELD_MAP = {
'registry.path': 'threatintel.indicator.registry.path',
};
+export const DEFAULT_EVENT_ENRICHMENT_FROM = 'now-30d';
+export const DEFAULT_EVENT_ENRICHMENT_TO = 'now';
+
export const CTI_DEFAULT_SOURCES = [
'Abuse URL',
'Abuse Malware',
diff --git a/x-pack/plugins/security_solution/common/search_strategy/security_solution/cti/index.mock.ts b/x-pack/plugins/security_solution/common/search_strategy/security_solution/cti/index.mock.ts
index f3dee5a21e4c98..7898962b1a72d8 100644
--- a/x-pack/plugins/security_solution/common/search_strategy/security_solution/cti/index.mock.ts
+++ b/x-pack/plugins/security_solution/common/search_strategy/security_solution/cti/index.mock.ts
@@ -8,6 +8,7 @@
import { IEsSearchResponse } from 'src/plugins/data/public';
import {
+ CtiEnrichment,
CtiEventEnrichmentRequestOptions,
CtiEventEnrichmentStrategyResponse,
CtiQueries,
@@ -99,11 +100,63 @@ export const buildEventEnrichmentRawResponseMock = (): IEsSearchResponse => ({
},
});
+export const buildEventEnrichmentMock = (
+ overrides: Partial = {}
+): CtiEnrichment => ({
+ '@timestamp': ['2021-05-28T18:33:52.993Z'],
+ 'agent.ephemeral_id': ['d6b14f65-5bf3-430d-8315-7b5613685979'],
+ 'agent.hostname': ['rylastic.local'],
+ 'agent.id': ['ff93aee5-86a1-4a61-b0e6-0cdc313d01b5'],
+ 'agent.name': ['rylastic.local'],
+ 'agent.type': ['filebeat'],
+ 'agent.version': ['8.0.0'],
+ 'ecs.version': ['1.6.0'],
+ 'event.category': ['threat'],
+ 'event.created': ['2021-05-28T18:33:52.993Z'],
+ 'event.dataset': ['threatintel.abusemalware'],
+ 'event.ingested': ['2021-05-28T18:33:55.086Z'],
+ 'event.kind': ['enrichment'],
+ 'event.module': ['threatintel'],
+ 'event.reference': [
+ 'https://urlhaus-api.abuse.ch/v1/download/15b012e6f626d0f88c2926d2bf4ca394d7b8ee07cc06d2ec05ea76bed3e8a05e/',
+ ],
+ 'event.type': ['indicator'],
+ 'fileset.name': ['abusemalware'],
+ 'input.type': ['httpjson'],
+ 'matched.atomic': ['5529de7b60601aeb36f57824ed0e1ae8'],
+ 'matched.field': ['file.hash.md5'],
+ 'matched.id': ['31408415b6d5601a92d29b86c2519658f210c194057588ae396d55cc20b3f03d'],
+ 'matched.index': ['filebeat-8.0.0-2021.05.28-000001'],
+ 'matched.type': ['investigation_time'],
+ 'related.hash': [
+ '5529de7b60601aeb36f57824ed0e1ae8',
+ '15b012e6f626d0f88c2926d2bf4ca394d7b8ee07cc06d2ec05ea76bed3e8a05e',
+ '768:NXSFGJ/ooP6FawrB7Bo1MWnF/jRmhJImp:1SFXIqBo1Mwj2p',
+ ],
+ 'service.type': ['threatintel'],
+ tags: ['threatintel-abusemalware', 'forwarded'],
+ 'threatintel.indicator.file.hash.md5': ['5529de7b60601aeb36f57824ed0e1ae8'],
+ 'threatintel.indicator.file.hash.sha256': [
+ '15b012e6f626d0f88c2926d2bf4ca394d7b8ee07cc06d2ec05ea76bed3e8a05e',
+ ],
+ 'threatintel.indicator.file.hash.ssdeep': [
+ '768:NXSFGJ/ooP6FawrB7Bo1MWnF/jRmhJImp:1SFXIqBo1Mwj2p',
+ ],
+ 'threatintel.indicator.file.hash.tlsh': [
+ 'FFB20B82F6617061C32784E2712F7A46B179B04FD1EA54A0F28CD8E9CFE4CAA1617F1C',
+ ],
+ 'threatintel.indicator.file.size': [24738],
+ 'threatintel.indicator.file.type': ['html'],
+ 'threatintel.indicator.first_seen': ['2021-05-28T18:33:29.000Z'],
+ 'threatintel.indicator.type': ['file'],
+ ...overrides,
+});
+
export const buildEventEnrichmentResponseMock = (
overrides: Partial = {}
): CtiEventEnrichmentStrategyResponse => ({
...buildEventEnrichmentRawResponseMock(),
- enrichments: [],
+ enrichments: [buildEventEnrichmentMock()],
inspect: { dsl: ['{"mocked": "json"}'] },
totalCount: 0,
...overrides,
diff --git a/x-pack/plugins/security_solution/common/search_strategy/security_solution/cti/index.ts b/x-pack/plugins/security_solution/common/search_strategy/security_solution/cti/index.ts
index 788a44bc5b9f7f..69a6841c7c14f8 100644
--- a/x-pack/plugins/security_solution/common/search_strategy/security_solution/cti/index.ts
+++ b/x-pack/plugins/security_solution/common/search_strategy/security_solution/cti/index.ts
@@ -6,6 +6,7 @@
*/
import { IEsSearchResponse } from 'src/plugins/data/public';
+import { EVENT_ENRICHMENT_INDICATOR_FIELD_MAP } from '../../../cti/constants';
import { Inspect } from '../../common';
import { RequestBasicOptions } from '..';
@@ -18,9 +19,24 @@ export interface CtiEventEnrichmentRequestOptions extends RequestBasicOptions {
}
export type CtiEnrichment = Record;
+export type EventFields = Record;
+
+export interface CtiEnrichmentIdentifiers {
+ id: string | undefined;
+ field: string | undefined;
+ value: string | undefined;
+ type: string | undefined;
+ provider: string | undefined;
+}
export interface CtiEventEnrichmentStrategyResponse extends IEsSearchResponse {
enrichments: CtiEnrichment[];
- inspect?: Inspect;
+ inspect: Inspect;
totalCount: number;
}
+
+export type EventField = keyof typeof EVENT_ENRICHMENT_INDICATOR_FIELD_MAP;
+export const validEventFields = Object.keys(EVENT_ENRICHMENT_INDICATOR_FIELD_MAP) as EventField[];
+
+export const isValidEventField = (field: string): field is EventField =>
+ validEventFields.includes(field as EventField);
diff --git a/x-pack/plugins/security_solution/common/utils/data_retrieval.test.ts b/x-pack/plugins/security_solution/common/utils/data_retrieval.test.ts
new file mode 100644
index 00000000000000..d7ab3986a14f98
--- /dev/null
+++ b/x-pack/plugins/security_solution/common/utils/data_retrieval.test.ts
@@ -0,0 +1,26 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { getFirstElement } from './data_retrieval';
+
+describe('getFirstElement', () => {
+ it('returns undefined if array is undefined', () => {
+ expect(getFirstElement(undefined)).toEqual(undefined);
+ });
+
+ it('returns undefined if array is empty', () => {
+ expect(getFirstElement([])).toEqual(undefined);
+ });
+
+ it('returns the first element if present', () => {
+ expect(getFirstElement(['hi mom'])).toEqual('hi mom');
+ });
+
+ it('returns the first element of multiple', () => {
+ expect(getFirstElement(['hi mom', 'hello world'])).toEqual('hi mom');
+ });
+});
diff --git a/x-pack/plugins/security_solution/common/utils/data_retrieval.ts b/x-pack/plugins/security_solution/common/utils/data_retrieval.ts
new file mode 100644
index 00000000000000..04b6839b854b42
--- /dev/null
+++ b/x-pack/plugins/security_solution/common/utils/data_retrieval.ts
@@ -0,0 +1,15 @@
+/*
+ * 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.
+ */
+
+/**
+ * Retrieves the first element of the given array.
+ *
+ * @param array the array to retrieve a value from
+ * @returns the first element of the array, or undefined if the array is undefined
+ */
+export const getFirstElement: (array: T[] | undefined) => T | undefined = (array) =>
+ array ? array[0] : undefined;
diff --git a/x-pack/plugins/security_solution/cypress/integration/detection_alerts/cti_enrichments.spec.ts b/x-pack/plugins/security_solution/cypress/integration/detection_alerts/cti_enrichments.spec.ts
new file mode 100644
index 00000000000000..b03daf74ce247a
--- /dev/null
+++ b/x-pack/plugins/security_solution/cypress/integration/detection_alerts/cti_enrichments.spec.ts
@@ -0,0 +1,193 @@
+/*
+ * 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 { newThreatIndicatorRule } from '../../objects/rule';
+import { cleanKibana, reload } from '../../tasks/common';
+import { esArchiverLoad, esArchiverUnload } from '../../tasks/es_archiver';
+import { loginAndWaitForPageWithoutDateRange } from '../../tasks/login';
+import {
+ JSON_LINES,
+ TABLE_CELL,
+ TABLE_ROWS,
+ THREAT_CONTENT,
+ THREAT_DETAILS_VIEW,
+ THREAT_INTEL_TAB,
+ THREAT_SUMMARY_VIEW,
+ TITLE,
+} from '../../screens/alerts_details';
+import { TIMELINE_FIELD } from '../../screens/rule_details';
+import { goToRuleDetails } from '../../tasks/alerts_detection_rules';
+import { expandFirstAlert, goToManageAlertsDetectionRules } from '../../tasks/alerts';
+import { createCustomIndicatorRule } from '../../tasks/api_calls/rules';
+import {
+ openJsonView,
+ openThreatIndicatorDetails,
+ scrollJsonViewToBottom,
+} from '../../tasks/alerts_details';
+
+import { ALERTS_URL } from '../../urls/navigation';
+import { addsFieldsToTimeline } from '../../tasks/rule_details';
+
+describe('CTI Enrichment', () => {
+ before(() => {
+ cleanKibana();
+ esArchiverLoad('threat_indicator');
+ esArchiverLoad('suspicious_source_event');
+ loginAndWaitForPageWithoutDateRange(ALERTS_URL);
+ goToManageAlertsDetectionRules();
+ createCustomIndicatorRule(newThreatIndicatorRule);
+ reload();
+ });
+
+ after(() => {
+ esArchiverUnload('threat_indicator');
+ esArchiverUnload('suspicious_source_event');
+ });
+
+ beforeEach(() => {
+ loginAndWaitForPageWithoutDateRange(ALERTS_URL);
+ goToManageAlertsDetectionRules();
+ goToRuleDetails();
+ });
+
+ it('Displays enrichment matched.* fields on the timeline', () => {
+ const expectedFields = {
+ 'threat.indicator.matched.atomic': newThreatIndicatorRule.atomic,
+ 'threat.indicator.matched.type': 'indicator_match_rule',
+ 'threat.indicator.matched.field': newThreatIndicatorRule.indicatorMappingField,
+ };
+ const fields = Object.keys(expectedFields) as Array;
+
+ addsFieldsToTimeline('threat.indicator.matched', fields);
+
+ fields.forEach((field) => {
+ cy.get(TIMELINE_FIELD(field)).should('have.text', expectedFields[field]);
+ });
+ });
+
+ it('Displays persisted enrichments on the JSON view', () => {
+ const expectedEnrichment = [
+ { line: 4, text: ' "threat": {' },
+ {
+ line: 3,
+ text:
+ ' "indicator": "{\\"first_seen\\":\\"2021-03-10T08:02:14.000Z\\",\\"file\\":{\\"size\\":80280,\\"pe\\":{},\\"type\\":\\"elf\\",\\"hash\\":{\\"sha256\\":\\"a04ac6d98ad989312783d4fe3456c53730b212c79a426fb215708b6c6daa3de3\\",\\"tlsh\\":\\"6D7312E017B517CC1371A8353BED205E9128223972AE35302E97528DF957703BAB2DBE\\",\\"ssdeep\\":\\"1536:87vbq1lGAXSEYQjbChaAU2yU23M51DjZgSQAvcYkFtZTjzBht5:8D+CAXFYQChaAUk5ljnQssL\\",\\"md5\\":\\"9b6c3518a91d23ed77504b5416bfb5b3\\"}},\\"type\\":\\"file\\",\\"event\\":{\\"reference\\":\\"https://urlhaus-api.abuse.ch/v1/download/a04ac6d98ad989312783d4fe3456c53730b212c79a426fb215708b6c6daa3de3/\\",\\"ingested\\":\\"2021-03-10T14:51:09.809069Z\\",\\"created\\":\\"2021-03-10T14:51:07.663Z\\",\\"kind\\":\\"enrichment\\",\\"module\\":\\"threatintel\\",\\"category\\":\\"threat\\",\\"type\\":\\"indicator\\",\\"dataset\\":\\"threatintel.abusemalware\\"},\\"matched\\":{\\"atomic\\":\\"a04ac6d98ad989312783d4fe3456c53730b212c79a426fb215708b6c6daa3de3\\",\\"field\\":\\"myhash.mysha256\\",\\"id\\":\\"84cf452c1e0375c3d4412cb550bd1783358468a3b3b777da4829d72c7d6fb74f\\",\\"index\\":\\"filebeat-7.12.0-2021.03.10-000001\\",\\"type\\":\\"indicator_match_rule\\"}}"',
+ },
+ { line: 2, text: ' }' },
+ ];
+
+ expandFirstAlert();
+ openJsonView();
+ scrollJsonViewToBottom();
+
+ cy.get(JSON_LINES).then((elements) => {
+ const length = elements.length;
+ expectedEnrichment.forEach((enrichment) => {
+ cy.wrap(elements)
+ .eq(length - enrichment.line)
+ .should('have.text', enrichment.text);
+ });
+ });
+ });
+
+ it('Displays threat indicator details on the threat intel tab', () => {
+ const expectedThreatIndicatorData = [
+ { field: 'event.category', value: 'threat' },
+ { field: 'event.created', value: '2021-03-10T14:51:07.663Z' },
+ { field: 'event.dataset', value: 'threatintel.abusemalware' },
+ { field: 'event.ingested', value: '2021-03-10T14:51:09.809069Z' },
+ { field: 'event.kind', value: 'enrichment' },
+ { field: 'event.module', value: 'threatintel' },
+ {
+ field: 'event.reference',
+ value:
+ 'https://urlhaus-api.abuse.ch/v1/download/a04ac6d98ad989312783d4fe3456c53730b212c79a426fb215708b6c6daa3de3/(opens in a new tab or window)',
+ },
+ { field: 'event.type', value: 'indicator' },
+ { field: 'file.hash.md5', value: '9b6c3518a91d23ed77504b5416bfb5b3' },
+ {
+ field: 'file.hash.sha256',
+ value: 'a04ac6d98ad989312783d4fe3456c53730b212c79a426fb215708b6c6daa3de3',
+ },
+ {
+ field: 'file.hash.ssdeep',
+ value: '1536:87vbq1lGAXSEYQjbChaAU2yU23M51DjZgSQAvcYkFtZTjzBht5:8D+CAXFYQChaAUk5ljnQssL',
+ },
+ {
+ field: 'file.hash.tlsh',
+ value: '6D7312E017B517CC1371A8353BED205E9128223972AE35302E97528DF957703BAB2DBE',
+ },
+ { field: 'file.size', value: '80280' },
+ { field: 'file.type', value: 'elf' },
+ { field: 'first_seen', value: '2021-03-10T08:02:14.000Z' },
+ {
+ field: 'matched.atomic',
+ value: 'a04ac6d98ad989312783d4fe3456c53730b212c79a426fb215708b6c6daa3de3',
+ },
+ { field: 'matched.field', value: 'myhash.mysha256' },
+ {
+ field: 'matched.id',
+ value: '84cf452c1e0375c3d4412cb550bd1783358468a3b3b777da4829d72c7d6fb74f',
+ },
+ { field: 'matched.index', value: 'filebeat-7.12.0-2021.03.10-000001' },
+ { field: 'matched.type', value: 'indicator_match_rule' },
+ { field: 'type', value: 'file' },
+ ];
+
+ expandFirstAlert();
+ openThreatIndicatorDetails();
+
+ cy.get(THREAT_INTEL_TAB).should('have.text', 'Threat Intel (1)');
+ cy.get(THREAT_DETAILS_VIEW).within(() => {
+ cy.get(TABLE_ROWS).should('have.length', expectedThreatIndicatorData.length);
+ expectedThreatIndicatorData.forEach((row, index) => {
+ cy.get(TABLE_ROWS)
+ .eq(index)
+ .within(() => {
+ cy.get(TABLE_CELL).eq(0).should('have.text', row.field);
+ cy.get(TABLE_CELL).eq(1).should('have.text', row.value);
+ });
+ });
+ });
+ });
+
+ describe('with additional indicators', () => {
+ before(() => {
+ esArchiverLoad('threat_indicator2');
+ });
+
+ after(() => {
+ esArchiverUnload('threat_indicator2');
+ });
+
+ it('Displays matched fields from both indicator match rules and investigation time enrichments on Alerts Summary tab', () => {
+ const indicatorMatchRuleEnrichment = {
+ field: 'myhash.mysha256',
+ value: 'a04ac6d98ad989312783d4fe3456c53730b212c79a426fb215708b6c6daa3de3',
+ };
+ const investigationTimeEnrichment = {
+ field: 'source.ip',
+ value: '192.168.1.1',
+ };
+ const expectedMatches = [indicatorMatchRuleEnrichment, investigationTimeEnrichment];
+
+ expandFirstAlert();
+
+ cy.get(THREAT_SUMMARY_VIEW).within(() => {
+ cy.get(TABLE_ROWS).should('have.length', expectedMatches.length);
+ expectedMatches.forEach((row, index) => {
+ cy.get(TABLE_ROWS)
+ .eq(index)
+ .within(() => {
+ cy.get(TITLE).should('have.text', row.field);
+ cy.get(THREAT_CONTENT).should('have.text', row.value);
+ });
+ });
+ });
+ });
+ });
+});
diff --git a/x-pack/plugins/security_solution/cypress/integration/detection_rules/indicator_match_rule.spec.ts b/x-pack/plugins/security_solution/cypress/integration/detection_rules/indicator_match_rule.spec.ts
index c2e8a92474814b..e1268c52f75d4d 100644
--- a/x-pack/plugins/security_solution/cypress/integration/detection_rules/indicator_match_rule.spec.ts
+++ b/x-pack/plugins/security_solution/cypress/integration/detection_rules/indicator_match_rule.spec.ts
@@ -16,16 +16,6 @@ import {
ALERT_RULE_VERSION,
NUMBER_OF_ALERTS,
} from '../../screens/alerts';
-import {
- JSON_LINES,
- TABLE_CELL,
- TABLE_ROWS,
- THREAT_CONTENT,
- THREAT_DETAILS_VIEW,
- THREAT_INTEL_TAB,
- THREAT_SUMMARY_VIEW,
- TITLE,
-} from '../../screens/alerts_details';
import {
CUSTOM_RULES_BTN,
RISK_SCORE,
@@ -60,23 +50,15 @@ import {
SCHEDULE_DETAILS,
SEVERITY_DETAILS,
TAGS_DETAILS,
- TIMELINE_FIELD,
TIMELINE_TEMPLATE_DETAILS,
} from '../../screens/rule_details';
import { INDICATOR_MATCH_ROW_RENDER, PROVIDER_BADGE } from '../../screens/timeline';
-
import {
- expandFirstAlert,
goToManageAlertsDetectionRules,
investigateFirstAlertInTimeline,
waitForAlertsIndexToBeCreated,
waitForAlertsPanelToBeLoaded,
} from '../../tasks/alerts';
-import {
- openJsonView,
- openThreatIndicatorDetails,
- scrollJsonViewToBottom,
-} from '../../tasks/alerts_details';
import {
changeRowsPerPageTo100,
duplicateFirstRule,
@@ -121,7 +103,7 @@ import {
import { goBackToRuleDetails, waitForKibana } from '../../tasks/edit_rule';
import { esArchiverLoad, esArchiverUnload } from '../../tasks/es_archiver';
import { loginAndWaitForPageWithoutDateRange } from '../../tasks/login';
-import { addsFieldsToTimeline, goBackToAllRulesTable } from '../../tasks/rule_details';
+import { goBackToAllRulesTable } from '../../tasks/rule_details';
import { ALERTS_URL, RULE_CREATION } from '../../urls/navigation';
@@ -520,170 +502,18 @@ describe('indicator match', () => {
cy.get(PROVIDER_BADGE).should('have.length', 3);
cy.get(PROVIDER_BADGE).should(
'have.text',
- `threat.indicator.matched.atomic: "${newThreatIndicatorRule.atomic}"threat.indicator.matched.type: "${newThreatIndicatorRule.type}"threat.indicator.matched.field: "${newThreatIndicatorRule.indicatorMappingField}"`
+ `threat.indicator.matched.atomic: "${newThreatIndicatorRule.atomic}"threat.indicator.matched.type: "indicator_match_rule"threat.indicator.matched.field: "${newThreatIndicatorRule.indicatorMappingField}"`
);
cy.readFile(threatIndicatorPath).then((threatIndicator) => {
cy.get(INDICATOR_MATCH_ROW_RENDER).should(
'have.text',
- `threat.indicator.matched.field${newThreatIndicatorRule.indicatorMappingField}${accessibilityText}matched${newThreatIndicatorRule.indicatorMappingField}${newThreatIndicatorRule.atomic}${accessibilityText}threat.indicator.matched.type${newThreatIndicatorRule.type}${accessibilityText}fromthreat.indicator.event.dataset${threatIndicator.value.source.event.dataset}${accessibilityText}:threat.indicator.event.reference${threatIndicator.value.source.event.reference}(opens in a new tab or window)${accessibilityText}`
+ `threat.indicator.matched.field${newThreatIndicatorRule.indicatorMappingField}${accessibilityText}matched${newThreatIndicatorRule.indicatorMappingField}${newThreatIndicatorRule.atomic}${accessibilityText}threat.indicator.matched.typeindicator_match_rule${accessibilityText}fromthreat.indicator.event.dataset${threatIndicator.value.source.event.dataset}${accessibilityText}:threat.indicator.event.reference${threatIndicator.value.source.event.reference}(opens in a new tab or window)${accessibilityText}`
);
});
});
});
- describe('Enrichment', () => {
- const fieldSearch = 'threat.indicator.matched';
- const fields = [
- 'threat.indicator.matched.atomic',
- 'threat.indicator.matched.type',
- 'threat.indicator.matched.field',
- ];
- const expectedFieldsText = [
- newThreatIndicatorRule.atomic,
- newThreatIndicatorRule.type,
- newThreatIndicatorRule.indicatorMappingField,
- ];
-
- const expectedEnrichment = [
- { line: 4, text: ' "threat": {' },
- {
- line: 3,
- text:
- ' "indicator": "{\\"first_seen\\":\\"2021-03-10T08:02:14.000Z\\",\\"file\\":{\\"size\\":80280,\\"pe\\":{},\\"type\\":\\"elf\\",\\"hash\\":{\\"sha256\\":\\"a04ac6d98ad989312783d4fe3456c53730b212c79a426fb215708b6c6daa3de3\\",\\"tlsh\\":\\"6D7312E017B517CC1371A8353BED205E9128223972AE35302E97528DF957703BAB2DBE\\",\\"ssdeep\\":\\"1536:87vbq1lGAXSEYQjbChaAU2yU23M51DjZgSQAvcYkFtZTjzBht5:8D+CAXFYQChaAUk5ljnQssL\\",\\"md5\\":\\"9b6c3518a91d23ed77504b5416bfb5b3\\"}},\\"type\\":\\"file\\",\\"event\\":{\\"reference\\":\\"https://urlhaus-api.abuse.ch/v1/download/a04ac6d98ad989312783d4fe3456c53730b212c79a426fb215708b6c6daa3de3/\\",\\"ingested\\":\\"2021-03-10T14:51:09.809069Z\\",\\"created\\":\\"2021-03-10T14:51:07.663Z\\",\\"kind\\":\\"enrichment\\",\\"module\\":\\"threatintel\\",\\"category\\":\\"threat\\",\\"type\\":\\"indicator\\",\\"dataset\\":\\"threatintel.abusemalware\\"},\\"matched\\":{\\"atomic\\":\\"a04ac6d98ad989312783d4fe3456c53730b212c79a426fb215708b6c6daa3de3\\",\\"field\\":\\"myhash.mysha256\\",\\"id\\":\\"84cf452c1e0375c3d4412cb550bd1783358468a3b3b777da4829d72c7d6fb74f\\",\\"index\\":\\"filebeat-7.12.0-2021.03.10-000001\\",\\"type\\":\\"file\\"}}"',
- },
- { line: 2, text: ' }' },
- ];
-
- before(() => {
- cleanKibana();
- esArchiverLoad('threat_indicator');
- esArchiverLoad('suspicious_source_event');
- loginAndWaitForPageWithoutDateRange(ALERTS_URL);
- goToManageAlertsDetectionRules();
- createCustomIndicatorRule(newThreatIndicatorRule);
- reload();
- });
-
- after(() => {
- esArchiverUnload('threat_indicator');
- esArchiverUnload('suspicious_source_event');
- });
-
- beforeEach(() => {
- loginAndWaitForPageWithoutDateRange(ALERTS_URL);
- goToManageAlertsDetectionRules();
- goToRuleDetails();
- });
-
- it('Displays matches on the timeline', () => {
- addsFieldsToTimeline(fieldSearch, fields);
-
- fields.forEach((field, index) => {
- cy.get(TIMELINE_FIELD(field)).should('have.text', expectedFieldsText[index]);
- });
- });
-
- it('Displays enrichment on the JSON view', () => {
- expandFirstAlert();
- openJsonView();
- scrollJsonViewToBottom();
-
- cy.get(JSON_LINES).then((elements) => {
- const length = elements.length;
- expectedEnrichment.forEach((enrichment) => {
- cy.wrap(elements)
- .eq(length - enrichment.line)
- .should('have.text', enrichment.text);
- });
- });
- });
-
- it('Displays threat summary data on alerts details', () => {
- const expectedThreatSummary = [
- { field: 'matched.field', value: 'myhash.mysha256' },
- { field: 'matched.type', value: 'file' },
- { field: 'first_seen', value: '2021-03-10T08:02:14.000Z' },
- ];
-
- expandFirstAlert();
-
- cy.get(THREAT_SUMMARY_VIEW).within(() => {
- cy.get(TABLE_ROWS).should('have.length', expectedThreatSummary.length);
- expectedThreatSummary.forEach((row, index) => {
- cy.get(TABLE_ROWS)
- .eq(index)
- .within(() => {
- cy.get(TITLE).should('have.text', row.field);
- cy.get(THREAT_CONTENT).should('have.text', row.value);
- });
- });
- });
- });
-
- it('Displays threat indicator data on the threat intel tab', () => {
- const expectedThreatIndicatorData = [
- { field: 'first_seen', value: '2021-03-10T08:02:14.000Z' },
- { field: 'file.size', value: '80280' },
- { field: 'file.type', value: 'elf' },
- {
- field: 'file.hash.sha256',
- value: 'a04ac6d98ad989312783d4fe3456c53730b212c79a426fb215708b6c6daa3de3',
- },
- {
- field: 'file.hash.tlsh',
- value: '6D7312E017B517CC1371A8353BED205E9128223972AE35302E97528DF957703BAB2DBE',
- },
- {
- field: 'file.hash.ssdeep',
- value:
- '1536:87vbq1lGAXSEYQjbChaAU2yU23M51DjZgSQAvcYkFtZTjzBht5:8D+CAXFYQChaAUk5ljnQssL',
- },
- { field: 'file.hash.md5', value: '9b6c3518a91d23ed77504b5416bfb5b3' },
- { field: 'type', value: 'file' },
- {
- field: 'event.reference',
- value:
- 'https://urlhaus-api.abuse.ch/v1/download/a04ac6d98ad989312783d4fe3456c53730b212c79a426fb215708b6c6daa3de3/(opens in a new tab or window)',
- },
- { field: 'event.ingested', value: '2021-03-10T14:51:09.809069Z' },
- { field: 'event.created', value: '2021-03-10T14:51:07.663Z' },
- { field: 'event.kind', value: 'enrichment' },
- { field: 'event.module', value: 'threatintel' },
- { field: 'event.category', value: 'threat' },
- { field: 'event.type', value: 'indicator' },
- { field: 'event.dataset', value: 'threatintel.abusemalware' },
- {
- field: 'matched.atomic',
- value: 'a04ac6d98ad989312783d4fe3456c53730b212c79a426fb215708b6c6daa3de3',
- },
- { field: 'matched.field', value: 'myhash.mysha256' },
- {
- field: 'matched.id',
- value: '84cf452c1e0375c3d4412cb550bd1783358468a3b3b777da4829d72c7d6fb74f',
- },
- { field: 'matched.index', value: 'filebeat-7.12.0-2021.03.10-000001' },
- { field: 'matched.type', value: 'file' },
- ];
-
- expandFirstAlert();
- openThreatIndicatorDetails();
-
- cy.get(THREAT_INTEL_TAB).should('have.text', 'Threat Intel (1)');
- cy.get(THREAT_DETAILS_VIEW).within(() => {
- cy.get(TABLE_ROWS).should('have.length', expectedThreatIndicatorData.length);
- expectedThreatIndicatorData.forEach((row, index) => {
- cy.get(TABLE_ROWS)
- .eq(index)
- .within(() => {
- cy.get(TABLE_CELL).eq(0).should('have.text', row.field);
- cy.get(TABLE_CELL).eq(1).should('have.text', row.value);
- });
- });
- });
- });
- });
-
describe('Duplicates the indicator rule', () => {
beforeEach(() => {
cleanKibana();
diff --git a/x-pack/plugins/security_solution/cypress/screens/alerts_details.ts b/x-pack/plugins/security_solution/cypress/screens/alerts_details.ts
index 12bef09b8356d5..460652cf6f2daf 100644
--- a/x-pack/plugins/security_solution/cypress/screens/alerts_details.ts
+++ b/x-pack/plugins/security_solution/cypress/screens/alerts_details.ts
@@ -19,7 +19,7 @@ export const TABLE_TAB = '[data-test-subj="tableTab"]';
export const TABLE_ROWS = '.euiTableRow';
-export const THREAT_CONTENT = '[data-test-subj^=draggable-content-threat]';
+export const THREAT_CONTENT = '[data-test-subj^=draggable-content-]';
export const THREAT_DETAILS_VIEW = '[data-test-subj="threat-details-view-0"]';
diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/empty_threat_details_view.test.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/empty_threat_details_view.test.tsx
similarity index 84%
rename from x-pack/plugins/security_solution/public/common/components/event_details/empty_threat_details_view.test.tsx
rename to x-pack/plugins/security_solution/public/common/components/event_details/cti_details/empty_threat_details_view.test.tsx
index b3e70fd17c0e12..76c6b077236f0d 100644
--- a/x-pack/plugins/security_solution/public/common/components/event_details/empty_threat_details_view.test.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/empty_threat_details_view.test.tsx
@@ -8,11 +8,11 @@
import React from 'react';
import { ThemeProvider } from 'styled-components';
-import { useMountAppended } from '../../utils/use_mount_appended';
-import { getMockTheme } from '../../lib/kibana/kibana_react.mock';
+import { useMountAppended } from '../../../utils/use_mount_appended';
+import { getMockTheme } from '../../../lib/kibana/kibana_react.mock';
import { EmptyThreatDetailsView } from './empty_threat_details_view';
-jest.mock('../../lib/kibana');
+jest.mock('../../../lib/kibana');
describe('EmptyThreatDetailsView', () => {
const mount = useMountAppended();
@@ -28,10 +28,6 @@ describe('EmptyThreatDetailsView', () => {
},
});
- beforeEach(() => {
- jest.clearAllMocks();
- });
-
test('renders correct items', () => {
const wrapper = mount(
diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/empty_threat_details_view.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/empty_threat_details_view.tsx
similarity index 96%
rename from x-pack/plugins/security_solution/public/common/components/event_details/empty_threat_details_view.tsx
rename to x-pack/plugins/security_solution/public/common/components/event_details/cti_details/empty_threat_details_view.tsx
index c78df92dceb3c1..d7e1c4d7754ecd 100644
--- a/x-pack/plugins/security_solution/public/common/components/event_details/empty_threat_details_view.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/empty_threat_details_view.tsx
@@ -8,8 +8,9 @@
import { EuiLink, EuiSpacer, EuiTitle } from '@elastic/eui';
import React from 'react';
import styled from 'styled-components';
+
+import { useKibana } from '../../../lib/kibana';
import * as i18n from './translations';
-import { useKibana } from '../../lib/kibana';
const EmptyThreatDetailsViewContainer = styled.div`
display: flex;
diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/enrichment_icon.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/enrichment_icon.tsx
new file mode 100644
index 00000000000000..042940a1cf036c
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/enrichment_icon.tsx
@@ -0,0 +1,30 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import React from 'react';
+import { EuiIcon, EuiToolTip } from '@elastic/eui';
+
+import * as i18n from './translations';
+import { isInvestigationTimeEnrichment } from './helpers';
+
+export const getTooltipTitle = (type: string | undefined) =>
+ isInvestigationTimeEnrichment(type)
+ ? i18n.INVESTIGATION_TOOLTIP_TITLE
+ : i18n.INDICATOR_TOOLTIP_TITLE;
+
+export const getTooltipContent = (type: string | undefined) =>
+ isInvestigationTimeEnrichment(type)
+ ? i18n.INVESTIGATION_TOOLTIP_CONTENT
+ : i18n.INDICATOR_TOOLTIP_CONTENT;
+
+export const EnrichmentIcon: React.FC<{ type: string | undefined }> = ({ type }) => {
+ return (
+
+
+
+ );
+};
diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/helpers.test.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/helpers.test.tsx
new file mode 100644
index 00000000000000..858962efa9e83f
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/helpers.test.tsx
@@ -0,0 +1,477 @@
+/*
+ * 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 { ENRICHMENT_TYPES } from '../../../../../common/cti/constants';
+import { buildEventEnrichmentMock } from '../../../../../common/search_strategy/security_solution/cti/index.mock';
+import {
+ filterDuplicateEnrichments,
+ getEnrichmentFields,
+ parseExistingEnrichments,
+} from './helpers';
+
+describe('parseExistingEnrichments', () => {
+ it('returns an empty array if data is empty', () => {
+ expect(parseExistingEnrichments([])).toEqual([]);
+ });
+
+ it('returns an empty array if data contains no enrichment field', () => {
+ const data = [
+ {
+ category: 'host',
+ field: 'host.os.name.text',
+ isObjectArray: false,
+ originalValue: ['Mac OS X'],
+ values: ['Mac OS X'],
+ },
+ ];
+ expect(parseExistingEnrichments(data)).toEqual([]);
+ });
+
+ it('returns an empty array if enrichment field contains invalid JSON', () => {
+ const data = [
+ {
+ category: 'threat',
+ field: 'threat.indicator',
+ isObjectArray: true,
+ originalValue: ['whoops'],
+ values: ['whoops'],
+ },
+ ];
+ expect(parseExistingEnrichments(data)).toEqual([]);
+ });
+
+ it('returns an array if enrichment field contains valid JSON', () => {
+ const data = [
+ {
+ category: 'threat',
+ field: 'threat.indicator',
+ isObjectArray: true,
+ originalValue: [
+ `{"first_seen":"2021-03-21T19:40:19.000Z","provider":"geenensp","ip":"192.168.1.19","type":"url","event":{"reference":"https://urlhaus.abuse.ch/url/1055419/","ingested":"2021-03-08T19:40:44.213673Z","created":"2021-03-08T19:40:43.160Z","kind":"other","module":"threatintel","category":"threat","type":"indicator","dataset":"threatintel.abuseurl"},"matched":{"atomic":"192.168.1.19","field":"host.ip","id":"0SIZMnoB_Blp1Ib9ZYHU","index":"filebeat-8.0.0-2021.05.28-000001","type":"url"}}`,
+ ],
+ values: [
+ `{"first_seen":"2021-03-21T19:40:19.000Z","provider":"geenensp","ip":"192.168.1.19","type":"url","event":{"reference":"https://urlhaus.abuse.ch/url/1055419/","ingested":"2021-03-08T19:40:44.213673Z","created":"2021-03-08T19:40:43.160Z","kind":"other","module":"threatintel","category":"threat","type":"indicator","dataset":"threatintel.abuseurl"},"matched":{"atomic":"192.168.1.19","field":"host.ip","id":"0SIZMnoB_Blp1Ib9ZYHU","index":"filebeat-8.0.0-2021.05.28-000001","type":"url"}}`,
+ ],
+ },
+ ];
+
+ expect(parseExistingEnrichments(data)).toEqual([
+ [
+ {
+ category: 'first_seen',
+ field: 'first_seen',
+ isObjectArray: false,
+ originalValue: ['2021-03-21T19:40:19.000Z'],
+ values: ['2021-03-21T19:40:19.000Z'],
+ },
+ {
+ category: 'provider',
+ field: 'provider',
+ isObjectArray: false,
+ originalValue: ['geenensp'],
+ values: ['geenensp'],
+ },
+ {
+ category: 'ip',
+ field: 'ip',
+ isObjectArray: false,
+ originalValue: ['192.168.1.19'],
+ values: ['192.168.1.19'],
+ },
+ {
+ category: 'type',
+ field: 'type',
+ isObjectArray: false,
+ originalValue: ['url'],
+ values: ['url'],
+ },
+ {
+ category: 'event',
+ field: 'event.reference',
+ isObjectArray: false,
+ originalValue: ['https://urlhaus.abuse.ch/url/1055419/'],
+ values: ['https://urlhaus.abuse.ch/url/1055419/'],
+ },
+ {
+ category: 'event',
+ field: 'event.ingested',
+ isObjectArray: false,
+ originalValue: ['2021-03-08T19:40:44.213673Z'],
+ values: ['2021-03-08T19:40:44.213673Z'],
+ },
+ {
+ category: 'event',
+ field: 'event.created',
+ isObjectArray: false,
+ originalValue: ['2021-03-08T19:40:43.160Z'],
+ values: ['2021-03-08T19:40:43.160Z'],
+ },
+ {
+ category: 'event',
+ field: 'event.kind',
+ isObjectArray: false,
+ originalValue: ['other'],
+ values: ['other'],
+ },
+ {
+ category: 'event',
+ field: 'event.module',
+ isObjectArray: false,
+ originalValue: ['threatintel'],
+ values: ['threatintel'],
+ },
+ {
+ category: 'event',
+ field: 'event.category',
+ isObjectArray: false,
+ originalValue: ['threat'],
+ values: ['threat'],
+ },
+ {
+ category: 'event',
+ field: 'event.type',
+ isObjectArray: false,
+ originalValue: ['indicator'],
+ values: ['indicator'],
+ },
+ {
+ category: 'event',
+ field: 'event.dataset',
+ isObjectArray: false,
+ originalValue: ['threatintel.abuseurl'],
+ values: ['threatintel.abuseurl'],
+ },
+ {
+ category: 'matched',
+ field: 'matched.atomic',
+ isObjectArray: false,
+ originalValue: ['192.168.1.19'],
+ values: ['192.168.1.19'],
+ },
+ {
+ category: 'matched',
+ field: 'matched.field',
+ isObjectArray: false,
+ originalValue: ['host.ip'],
+ values: ['host.ip'],
+ },
+ {
+ category: 'matched',
+ field: 'matched.id',
+ isObjectArray: false,
+ originalValue: ['0SIZMnoB_Blp1Ib9ZYHU'],
+ values: ['0SIZMnoB_Blp1Ib9ZYHU'],
+ },
+ {
+ category: 'matched',
+ field: 'matched.index',
+ isObjectArray: false,
+ originalValue: ['filebeat-8.0.0-2021.05.28-000001'],
+ values: ['filebeat-8.0.0-2021.05.28-000001'],
+ },
+ {
+ category: 'matched',
+ field: 'matched.type',
+ isObjectArray: false,
+ originalValue: ['url'],
+ values: ['url'],
+ },
+ ],
+ ]);
+ });
+
+ it('returns multiple arrays for multiple enrichments', () => {
+ const data = [
+ {
+ category: 'threat',
+ field: 'threat.indicator',
+ isObjectArray: true,
+ originalValue: [
+ `{"first_seen":"2021-03-21T19:40:19.000Z","provider":"other","ip":"192.168.1.19","type":"url","event":{"reference":"https://urlhaus.abuse.ch/url/1055419/","ingested":"2021-03-08T19:40:44.213673Z","created":"2021-03-08T19:40:43.160Z","kind":"other","module":"threatintel","category":"threat","type":"indicator","dataset":"threatintel.abuseurl"},"matched":{"atomic":"192.168.1.19","field":"host.ip","id":"iiL9NHoB_Blp1Ib9yoJo","index":"filebeat-8.0.0-2021.05.28-000001","type":"url"}}`,
+ `{"first_seen":"2021-03-21T19:40:19.000Z","provider":"geenensp","ip":"192.168.1.19","type":"url","event":{"reference":"https://urlhaus.abuse.ch/url/1055419/","ingested":"2021-03-08T19:40:44.213673Z","created":"2021-03-08T19:40:43.160Z","kind":"other","module":"threatintel","category":"threat","type":"indicator","dataset":"threatintel.abuseurl"},"matched":{"atomic":"192.168.1.19","field":"host.ip","id":"0SIZMnoB_Blp1Ib9ZYHU","index":"filebeat-8.0.0-2021.05.28-000001","type":"url"}}`,
+ ],
+ values: [
+ `{"first_seen":"2021-03-21T19:40:19.000Z","provider":"other","ip":"192.168.1.19","type":"url","event":{"reference":"https://urlhaus.abuse.ch/url/1055419/","ingested":"2021-03-08T19:40:44.213673Z","created":"2021-03-08T19:40:43.160Z","kind":"other","module":"threatintel","category":"threat","type":"indicator","dataset":"threatintel.abuseurl"},"matched":{"atomic":"192.168.1.19","field":"host.ip","id":"iiL9NHoB_Blp1Ib9yoJo","index":"filebeat-8.0.0-2021.05.28-000001","type":"url"}}`,
+ `{"first_seen":"2021-03-21T19:40:19.000Z","provider":"geenensp","ip":"192.168.1.19","type":"url","event":{"reference":"https://urlhaus.abuse.ch/url/1055419/","ingested":"2021-03-08T19:40:44.213673Z","created":"2021-03-08T19:40:43.160Z","kind":"other","module":"threatintel","category":"threat","type":"indicator","dataset":"threatintel.abuseurl"},"matched":{"atomic":"192.168.1.19","field":"host.ip","id":"0SIZMnoB_Blp1Ib9ZYHU","index":"filebeat-8.0.0-2021.05.28-000001","type":"url"}}`,
+ ],
+ },
+ ];
+
+ expect(parseExistingEnrichments(data)).toEqual([
+ expect.arrayContaining([
+ {
+ category: 'first_seen',
+ field: 'first_seen',
+ isObjectArray: false,
+ originalValue: ['2021-03-21T19:40:19.000Z'],
+ values: ['2021-03-21T19:40:19.000Z'],
+ },
+ {
+ category: 'provider',
+ field: 'provider',
+ isObjectArray: false,
+ originalValue: ['other'],
+ values: ['other'],
+ },
+ {
+ category: 'ip',
+ field: 'ip',
+ isObjectArray: false,
+ originalValue: ['192.168.1.19'],
+ values: ['192.168.1.19'],
+ },
+ {
+ category: 'type',
+ field: 'type',
+ isObjectArray: false,
+ originalValue: ['url'],
+ values: ['url'],
+ },
+ {
+ category: 'event',
+ field: 'event.reference',
+ isObjectArray: false,
+ originalValue: ['https://urlhaus.abuse.ch/url/1055419/'],
+ values: ['https://urlhaus.abuse.ch/url/1055419/'],
+ },
+ {
+ category: 'event',
+ field: 'event.ingested',
+ isObjectArray: false,
+ originalValue: ['2021-03-08T19:40:44.213673Z'],
+ values: ['2021-03-08T19:40:44.213673Z'],
+ },
+ {
+ category: 'event',
+ field: 'event.module',
+ isObjectArray: false,
+ originalValue: ['threatintel'],
+ values: ['threatintel'],
+ },
+ {
+ category: 'event',
+ field: 'event.category',
+ isObjectArray: false,
+ originalValue: ['threat'],
+ values: ['threat'],
+ },
+ {
+ category: 'event',
+ field: 'event.type',
+ isObjectArray: false,
+ originalValue: ['indicator'],
+ values: ['indicator'],
+ },
+ {
+ category: 'event',
+ field: 'event.dataset',
+ isObjectArray: false,
+ originalValue: ['threatintel.abuseurl'],
+ values: ['threatintel.abuseurl'],
+ },
+ {
+ category: 'matched',
+ field: 'matched.atomic',
+ isObjectArray: false,
+ originalValue: ['192.168.1.19'],
+ values: ['192.168.1.19'],
+ },
+ {
+ category: 'matched',
+ field: 'matched.field',
+ isObjectArray: false,
+ originalValue: ['host.ip'],
+ values: ['host.ip'],
+ },
+ {
+ category: 'matched',
+ field: 'matched.id',
+ isObjectArray: false,
+ originalValue: ['iiL9NHoB_Blp1Ib9yoJo'],
+ values: ['iiL9NHoB_Blp1Ib9yoJo'],
+ },
+ {
+ category: 'matched',
+ field: 'matched.index',
+ isObjectArray: false,
+ originalValue: ['filebeat-8.0.0-2021.05.28-000001'],
+ values: ['filebeat-8.0.0-2021.05.28-000001'],
+ },
+ {
+ category: 'matched',
+ field: 'matched.type',
+ isObjectArray: false,
+ originalValue: ['url'],
+ values: ['url'],
+ },
+ ]),
+ expect.arrayContaining([
+ {
+ category: 'first_seen',
+ field: 'first_seen',
+ isObjectArray: false,
+ originalValue: ['2021-03-21T19:40:19.000Z'],
+ values: ['2021-03-21T19:40:19.000Z'],
+ },
+ {
+ category: 'provider',
+ field: 'provider',
+ isObjectArray: false,
+ originalValue: ['geenensp'],
+ values: ['geenensp'],
+ },
+ {
+ category: 'ip',
+ field: 'ip',
+ isObjectArray: false,
+ originalValue: ['192.168.1.19'],
+ values: ['192.168.1.19'],
+ },
+ {
+ category: 'type',
+ field: 'type',
+ isObjectArray: false,
+ originalValue: ['url'],
+ values: ['url'],
+ },
+ {
+ category: 'event',
+ field: 'event.reference',
+ isObjectArray: false,
+ originalValue: ['https://urlhaus.abuse.ch/url/1055419/'],
+ values: ['https://urlhaus.abuse.ch/url/1055419/'],
+ },
+ {
+ category: 'event',
+ field: 'event.ingested',
+ isObjectArray: false,
+ originalValue: ['2021-03-08T19:40:44.213673Z'],
+ values: ['2021-03-08T19:40:44.213673Z'],
+ },
+ {
+ category: 'event',
+ field: 'event.module',
+ isObjectArray: false,
+ originalValue: ['threatintel'],
+ values: ['threatintel'],
+ },
+ {
+ category: 'event',
+ field: 'event.category',
+ isObjectArray: false,
+ originalValue: ['threat'],
+ values: ['threat'],
+ },
+ {
+ category: 'event',
+ field: 'event.type',
+ isObjectArray: false,
+ originalValue: ['indicator'],
+ values: ['indicator'],
+ },
+ {
+ category: 'event',
+ field: 'event.dataset',
+ isObjectArray: false,
+ originalValue: ['threatintel.abuseurl'],
+ values: ['threatintel.abuseurl'],
+ },
+ {
+ category: 'matched',
+ field: 'matched.atomic',
+ isObjectArray: false,
+ originalValue: ['192.168.1.19'],
+ values: ['192.168.1.19'],
+ },
+ {
+ category: 'matched',
+ field: 'matched.field',
+ isObjectArray: false,
+ originalValue: ['host.ip'],
+ values: ['host.ip'],
+ },
+ {
+ category: 'matched',
+ field: 'matched.id',
+ isObjectArray: false,
+ originalValue: ['0SIZMnoB_Blp1Ib9ZYHU'],
+ values: ['0SIZMnoB_Blp1Ib9ZYHU'],
+ },
+ {
+ category: 'matched',
+ field: 'matched.index',
+ isObjectArray: false,
+ originalValue: ['filebeat-8.0.0-2021.05.28-000001'],
+ values: ['filebeat-8.0.0-2021.05.28-000001'],
+ },
+ {
+ category: 'matched',
+ field: 'matched.type',
+ isObjectArray: false,
+ originalValue: ['url'],
+ values: ['url'],
+ },
+ ]),
+ ]);
+ });
+});
+
+describe('filterDuplicateEnrichments', () => {
+ it('returns an empty array if given one', () => {
+ expect(filterDuplicateEnrichments([])).toEqual([]);
+ });
+
+ it('returns the existing enrichment if given both that and an investigation-time enrichment for the same indicator and field', () => {
+ const existingEnrichment = buildEventEnrichmentMock({
+ 'matched.type': [ENRICHMENT_TYPES.IndicatorMatchRule],
+ });
+ const investigationEnrichment = buildEventEnrichmentMock({
+ 'matched.type': [ENRICHMENT_TYPES.InvestigationTime],
+ });
+ expect(filterDuplicateEnrichments([existingEnrichment, investigationEnrichment])).toEqual([
+ existingEnrichment,
+ ]);
+ });
+
+ it('includes two enrichments from the same indicator if it matched different fields', () => {
+ const enrichments = [
+ buildEventEnrichmentMock(),
+ buildEventEnrichmentMock({
+ 'matched.field': ['other.field'],
+ }),
+ ];
+ expect(filterDuplicateEnrichments(enrichments)).toEqual(enrichments);
+ });
+});
+
+describe('getEnrichmentFields', () => {
+ it('returns an empty object if items is empty', () => {
+ expect(getEnrichmentFields([])).toEqual({});
+ });
+
+ it('returns an object of event fields and values', () => {
+ const data = [
+ {
+ category: 'source',
+ field: 'source.ip',
+ isObjectArray: false,
+ originalValue: ['192.168.1.1'],
+ values: ['192.168.1.1'],
+ },
+ {
+ category: 'event',
+ field: 'event.reference',
+ isObjectArray: false,
+ originalValue: ['https://urlhaus.abuse.ch/url/1055419/'],
+ values: ['https://urlhaus.abuse.ch/url/1055419/'],
+ },
+ ];
+ expect(getEnrichmentFields(data)).toEqual({
+ 'source.ip': '192.168.1.1',
+ });
+ });
+});
diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/helpers.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/helpers.tsx
new file mode 100644
index 00000000000000..b048bb076e2d37
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/helpers.tsx
@@ -0,0 +1,123 @@
+/*
+ * 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 { groupBy } from 'lodash';
+import {
+ DEFAULT_INDICATOR_SOURCE_PATH,
+ INDICATOR_DESTINATION_PATH,
+} from '../../../../../common/constants';
+import {
+ ENRICHMENT_TYPES,
+ MATCHED_ATOMIC,
+ MATCHED_FIELD,
+ MATCHED_ID,
+ MATCHED_TYPE,
+ PROVIDER,
+} from '../../../../../common/cti/constants';
+import { TimelineEventsDetailsItem } from '../../../../../common/search_strategy';
+import {
+ CtiEnrichment,
+ CtiEnrichmentIdentifiers,
+ EventFields,
+ isValidEventField,
+} from '../../../../../common/search_strategy/security_solution/cti';
+import { getFirstElement } from '../../../../../common/utils/data_retrieval';
+import { getDataFromSourceHits } from '../../../../../common/utils/field_formatters';
+
+export const isInvestigationTimeEnrichment = (type: string | undefined) =>
+ type === ENRICHMENT_TYPES.InvestigationTime;
+
+export const parseExistingEnrichments = (
+ data: TimelineEventsDetailsItem[]
+): TimelineEventsDetailsItem[][] => {
+ const threatIndicatorField = data.find(
+ ({ field, originalValue }) => field === INDICATOR_DESTINATION_PATH && originalValue
+ );
+ if (!threatIndicatorField) {
+ return [];
+ }
+
+ const { originalValue } = threatIndicatorField;
+ const enrichmentStrings = Array.isArray(originalValue) ? originalValue : [originalValue];
+
+ return enrichmentStrings.reduce(
+ (enrichments, enrichmentString) => {
+ try {
+ const enrichment = getDataFromSourceHits(JSON.parse(enrichmentString));
+ enrichments.push(enrichment);
+ } catch (e) {
+ // omit failed parse
+ }
+ return enrichments;
+ },
+ []
+ );
+};
+
+export const timelineDataToEnrichment = (data: TimelineEventsDetailsItem[]): CtiEnrichment =>
+ data.reduce((acc, item) => {
+ acc[item.field] = item.originalValue;
+ return acc;
+ }, {});
+
+export const getEnrichmentValue = (enrichment: CtiEnrichment, field: string) =>
+ getFirstElement(enrichment[field]) as string | undefined;
+
+/**
+ * These fields (e.g. 'x') may be in one of two keys depending on whether it's
+ * a new enrichment ('threatintel.indicator.x') or an old indicator alert
+ * (simply 'x'). Once enrichment has been normalized and we support the new ECS
+ * fields, this value should always be 'indicator.x';
+ */
+export const getShimmedIndicatorValue = (enrichment: CtiEnrichment, field: string) =>
+ getEnrichmentValue(enrichment, field) ||
+ getEnrichmentValue(enrichment, `${DEFAULT_INDICATOR_SOURCE_PATH}.${field}`);
+
+export const getEnrichmentIdentifiers = (enrichment: CtiEnrichment): CtiEnrichmentIdentifiers => ({
+ id: getEnrichmentValue(enrichment, MATCHED_ID),
+ field: getEnrichmentValue(enrichment, MATCHED_FIELD),
+ value: getEnrichmentValue(enrichment, MATCHED_ATOMIC),
+ type: getEnrichmentValue(enrichment, MATCHED_TYPE),
+ provider: getShimmedIndicatorValue(enrichment, PROVIDER),
+});
+
+const buildEnrichmentId = (enrichment: CtiEnrichment): string => {
+ const { id, field } = getEnrichmentIdentifiers(enrichment);
+ return `${id}${field}`;
+};
+
+/**
+ * This function receives an array of enrichments and removes
+ * investigation-time enrichments if that exact indicator already exists
+ * elsewhere in the list.
+ *
+ * @param enrichments {@type CtiEnrichment[]}
+ */
+export const filterDuplicateEnrichments = (enrichments: CtiEnrichment[]): CtiEnrichment[] => {
+ if (enrichments.length < 2) {
+ return enrichments;
+ }
+ const enrichmentsById = groupBy(enrichments, buildEnrichmentId);
+
+ return Object.values(enrichmentsById).map(
+ (enrichmentGroup) =>
+ enrichmentGroup.find(
+ (enrichment) => !isInvestigationTimeEnrichment(getEnrichmentValue(enrichment, MATCHED_TYPE))
+ ) ?? enrichmentGroup[0]
+ );
+};
+
+export const getEnrichmentFields = (items: TimelineEventsDetailsItem[]): EventFields =>
+ items.reduce((fields, item) => {
+ if (isValidEventField(item.field)) {
+ const value = getFirstElement(item.originalValue);
+ if (value) {
+ return { ...fields, [item.field]: value };
+ }
+ }
+ return fields;
+ }, {});
diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/threat_details_view.test.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/threat_details_view.test.tsx
new file mode 100644
index 00000000000000..0113dde96a4b6c
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/threat_details_view.test.tsx
@@ -0,0 +1,88 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import React from 'react';
+import { mount } from 'enzyme';
+
+import { TestProviders } from '../../../mock';
+import { buildEventEnrichmentMock } from '../../../../../common/search_strategy/security_solution/cti/index.mock';
+import { FIRSTSEEN } from '../../../../../common/cti/constants';
+import { ThreatDetailsView } from './threat_details_view';
+
+describe('ThreatDetailsView', () => {
+ it('renders a detail view for each enrichment', () => {
+ const enrichments = [
+ buildEventEnrichmentMock(),
+ buildEventEnrichmentMock({ 'matched.id': ['other.id'], 'matched.field': ['other.field'] }),
+ ];
+
+ const wrapper = mount(
+
+
+
+ );
+
+ expect(wrapper.find('[data-test-subj^="threat-details-view"]').hostNodes()).toHaveLength(
+ enrichments.length
+ );
+ });
+
+ it('renders an empty view if there are no enrichments', () => {
+ const wrapper = mount(
+
+
+
+ );
+ expect(wrapper.find('[data-test-subj="empty-threat-details-view"]').exists()).toEqual(true);
+ });
+
+ it('renders anchor links for event.url and event.reference', () => {
+ const enrichments = [
+ buildEventEnrichmentMock({
+ 'event.url': ['http://foo.bar'],
+ 'event.reference': ['http://foo.baz'],
+ }),
+ ];
+ const wrapper = mount(
+
+
+
+ );
+ expect(wrapper.find('a').length).toEqual(2);
+ });
+
+ it('orders enrichments by first_seen descending', () => {
+ const mostRecentDate = '2021-04-25T18:17:00.000Z';
+ const olderDate = '2021-03-25T18:17:00.000Z';
+ // this simulates a legacy enrichment from the old indicator match rule,
+ // where first_seen is available at the top level
+ const existingEnrichment = buildEventEnrichmentMock({
+ first_seen: [mostRecentDate],
+ });
+ delete existingEnrichment['threatintel.indicator.first_seen'];
+ const newEnrichment = buildEventEnrichmentMock({
+ 'matched.id': ['other.id'],
+ 'threatintel.indicator.first_seen': [olderDate],
+ });
+ const enrichments = [existingEnrichment, newEnrichment];
+
+ const wrapper = mount(
+
+
+
+ );
+
+ const firstSeenRows = wrapper
+ .find('.euiTableRow')
+ .hostNodes()
+ .filterWhere((node) => node.text().includes(FIRSTSEEN));
+ expect(firstSeenRows.map((node) => node.text())).toEqual([
+ `first_seen${mostRecentDate}`,
+ `first_seen${olderDate}`,
+ ]);
+ });
+});
diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/threat_details_view.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/threat_details_view.tsx
new file mode 100644
index 00000000000000..d5e985c5757a62
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/threat_details_view.tsx
@@ -0,0 +1,163 @@
+/*
+ * 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 {
+ EuiBasicTableColumn,
+ EuiFlexGroup,
+ EuiFlexItem,
+ EuiHorizontalRule,
+ EuiSpacer,
+ EuiToolTip,
+ EuiLink,
+ EuiText,
+ EuiTextColor,
+} from '@elastic/eui';
+import React, { Fragment } from 'react';
+
+import { StyledEuiInMemoryTable } from '../summary_view';
+import { getSummaryColumns, SummaryRow, ThreatDetailsRow } from '../helpers';
+import { EmptyThreatDetailsView } from './empty_threat_details_view';
+import { FIRSTSEEN, EVENT_URL, EVENT_REFERENCE } from '../../../../../common/cti/constants';
+import { DEFAULT_INDICATOR_SOURCE_PATH } from '../../../../../common/constants';
+import { getFirstElement } from '../../../../../common/utils/data_retrieval';
+import { CtiEnrichment } from '../../../../../common/search_strategy/security_solution/cti';
+import {
+ getShimmedIndicatorValue,
+ isInvestigationTimeEnrichment,
+ getEnrichmentIdentifiers,
+} from './helpers';
+import * as i18n from './translations';
+import { EnrichmentIcon } from './enrichment_icon';
+import { QUERY_ID } from '../../../containers/cti/event_enrichment/use_investigation_enrichment';
+import { InspectButton } from '../../inspect';
+
+const getFirstSeen = (enrichment: CtiEnrichment): number => {
+ const firstSeenValue = getShimmedIndicatorValue(enrichment, FIRSTSEEN);
+ const firstSeenDate = Date.parse(firstSeenValue ?? 'no date');
+ return Number.isInteger(firstSeenDate) ? firstSeenDate : new Date(-1).valueOf();
+};
+
+const ThreatDetailsHeader: React.FC<{
+ field: string | undefined;
+ value: string | undefined;
+ provider: string | undefined;
+ type: string | undefined;
+}> = ({ field, value, provider, type }) => (
+ <>
+
+
+
+
+
+
+
+ {field} {value}
+
+
+ {provider && (
+ <>
+
+
+ {i18n.PROVIDER_PREPOSITION} {provider}
+
+
+ >
+ )}
+
+
+
+
+
+ {isInvestigationTimeEnrichment(type) && (
+
+
+
+
+
+ )}
+ >
+);
+
+const ThreatDetailsDescription: React.FC = ({
+ fieldName,
+ value,
+}) => {
+ const tooltipChild = [EVENT_URL, EVENT_REFERENCE].includes(fieldName) ? (
+
+ {value}
+
+ ) : (
+ {value}
+ );
+ return (
+
+
+ {fieldName}
+
+
+ }
+ >
+ {tooltipChild}
+
+ );
+};
+
+const columns: Array> = getSummaryColumns(ThreatDetailsDescription);
+
+const buildThreatDetailsItems = (enrichment: CtiEnrichment) =>
+ Object.keys(enrichment)
+ .sort()
+ .map((field) => {
+ const displayField = field.startsWith(DEFAULT_INDICATOR_SOURCE_PATH)
+ ? field.replace(`${DEFAULT_INDICATOR_SOURCE_PATH}.`, '')
+ : field;
+
+ return {
+ title: displayField,
+ description: {
+ fieldName: field,
+ value: getFirstElement(enrichment[field]),
+ },
+ };
+ });
+
+const ThreatDetailsViewComponent: React.FC<{
+ enrichments: CtiEnrichment[];
+}> = ({ enrichments }) => {
+ if (enrichments.length < 1) {
+ return ;
+ }
+
+ const sortedEnrichments = enrichments.sort((a, b) => getFirstSeen(b) - getFirstSeen(a));
+
+ return (
+ <>
+
+ {sortedEnrichments.map((enrichment, index) => {
+ const { id, field, provider, type, value } = getEnrichmentIdentifiers(enrichment);
+
+ return (
+
+
+
+ {index < sortedEnrichments.length - 1 && }
+
+ );
+ })}
+ >
+ );
+};
+
+export const ThreatDetailsView = React.memo(ThreatDetailsViewComponent);
diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/threat_summary_view.test.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/threat_summary_view.test.tsx
new file mode 100644
index 00000000000000..bf6c4b9594344a
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/threat_summary_view.test.tsx
@@ -0,0 +1,41 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import React from 'react';
+
+import { ThreatSummaryView } from './threat_summary_view';
+import { TestProviders } from '../../../mock';
+import { useMountAppended } from '../../../utils/use_mount_appended';
+import { buildEventEnrichmentMock } from '../../../../../common/search_strategy/security_solution/cti/index.mock';
+
+jest.mock('../../../../timelines/components/timeline/body/renderers/formatted_field');
+
+describe('ThreatSummaryView', () => {
+ const mount = useMountAppended();
+ const eventId = '5d1d53da502f56aacc14c3cb5c669363d102b31f99822e5d369d4804ed370a31';
+ const timelineId = 'detections-page';
+
+ beforeEach(() => {
+ jest.clearAllMocks();
+ });
+
+ it('renders a row for each enrichment', () => {
+ const enrichments = [
+ buildEventEnrichmentMock(),
+ buildEventEnrichmentMock({ 'matched.id': ['other.id'], 'matched.field': ['other.field'] }),
+ ];
+ const wrapper = mount(
+
+
+
+ );
+
+ expect(wrapper.find('[data-test-subj="threat-summary-view"] .euiTableRow')).toHaveLength(
+ enrichments.length
+ );
+ });
+});
diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/threat_summary_view.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/threat_summary_view.tsx
new file mode 100644
index 00000000000000..4a6c9ec48bcbc7
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/threat_summary_view.tsx
@@ -0,0 +1,141 @@
+/*
+ * 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 styled from 'styled-components';
+import React from 'react';
+import { EuiBasicTableColumn, EuiText, EuiTitle } from '@elastic/eui';
+
+import * as i18n from './translations';
+import { StyledEuiInMemoryTable } from '../summary_view';
+import { FormattedFieldValue } from '../../../../timelines/components/timeline/body/renderers/formatted_field';
+import { CtiEnrichment } from '../../../../../common/search_strategy/security_solution/cti';
+import { getEnrichmentIdentifiers } from './helpers';
+import { EnrichmentIcon } from './enrichment_icon';
+
+export interface ThreatSummaryItem {
+ title: {
+ title: string | undefined;
+ type: string | undefined;
+ };
+ description: {
+ timelineId: string;
+ eventId: string;
+ fieldName: string | undefined;
+ index: number;
+ value: string | undefined;
+ provider: string | undefined;
+ };
+}
+
+const RightMargin = styled.span`
+ margin-right: ${({ theme }) => theme.eui.paddingSizes.s};
+`;
+
+const EnrichmentTitle: React.FC = ({ title, type }) => (
+ <>
+
+
+
+
+ {title}
+
+ >
+);
+
+const EnrichmentDescription: React.FC = ({
+ timelineId,
+ eventId,
+ fieldName,
+ index,
+ value,
+ provider,
+}) => {
+ const key = `alert-details-value-formatted-field-value-${timelineId}-${eventId}-${fieldName}-${value}-${index}-${provider}`;
+ return (
+ <>
+
+
+
+ {provider && (
+ <>
+
+
+ {i18n.PROVIDER_PREPOSITION}
+
+
+
+
+ {provider}
+
+
+ >
+ )}
+ >
+ );
+};
+
+const buildThreatSummaryItems = (
+ enrichments: CtiEnrichment[],
+ timelineId: string,
+ eventId: string
+) => {
+ return enrichments.map((enrichment, index) => {
+ const { field, type, value, provider } = getEnrichmentIdentifiers(enrichment);
+
+ return {
+ title: {
+ title: field,
+ type,
+ },
+ description: {
+ eventId,
+ fieldName: field,
+ index,
+ provider,
+ timelineId,
+ value,
+ },
+ };
+ });
+};
+
+const columns: Array> = [
+ {
+ field: 'title',
+ truncateText: false,
+ render: EnrichmentTitle,
+ width: '160px',
+ name: '',
+ },
+ {
+ field: 'description',
+ truncateText: false,
+ render: EnrichmentDescription,
+ name: '',
+ },
+];
+
+const ThreatSummaryViewComponent: React.FC<{
+ enrichments: CtiEnrichment[];
+ timelineId: string;
+ eventId: string;
+}> = ({ enrichments, timelineId, eventId }) => (
+
+);
+
+export const ThreatSummaryView = React.memo(ThreatSummaryViewComponent);
diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/translations.ts b/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/translations.ts
new file mode 100644
index 00000000000000..a0c247db927ce6
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/translations.ts
@@ -0,0 +1,71 @@
+/*
+ * 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 { i18n } from '@kbn/i18n';
+
+export const PROVIDER_PREPOSITION = i18n.translate(
+ 'xpack.securitySolution.eventDetails.ctiSummary.providerPreposition',
+ {
+ defaultMessage: 'from',
+ }
+);
+
+export const INDICATOR_TOOLTIP_TITLE = i18n.translate(
+ 'xpack.securitySolution.eventDetails.ctiSummary.indicatorEnrichmentTooltipTitle',
+ {
+ defaultMessage: 'Indicator rule enrichment',
+ }
+);
+
+export const INVESTIGATION_TOOLTIP_TITLE = i18n.translate(
+ 'xpack.securitySolution.eventDetails.ctiSummary.investigationEnrichmentTooltipTitle',
+ {
+ defaultMessage: 'Investigation time enrichment',
+ }
+);
+
+export const INDICATOR_TOOLTIP_CONTENT = i18n.translate(
+ 'xpack.securitySolution.eventDetails.ctiSummary.indicatorEnrichmentTooltipContent',
+ {
+ defaultMessage:
+ 'This field matched a known indicator, and was enriched by an indicator match rule. See more details on the Threat Intel tab.',
+ }
+);
+
+export const INVESTIGATION_TOOLTIP_CONTENT = i18n.translate(
+ 'xpack.securitySolution.eventDetails.ctiSummary.investigationEnrichmentTooltipContent',
+ {
+ defaultMessage:
+ 'This field matched a known indicator; see more details on the Threat Intel tab.',
+ }
+);
+
+export const NO_ENRICHMENT_FOUND = i18n.translate(
+ 'xpack.securitySolution.alertDetails.noEnrichmentFound',
+ {
+ defaultMessage: 'No Threat Intel Enrichment Found',
+ }
+);
+
+export const IF_CTI_NOT_ENABLED = i18n.translate(
+ 'xpack.securitySolution.alertDetails.ifCtiNotEnabled',
+ {
+ defaultMessage:
+ "If you haven't enabled any threat intelligence sources and want to learn more about this capability, ",
+ }
+);
+
+export const CHECK_DOCS = i18n.translate('xpack.securitySolution.alertDetails.checkDocs', {
+ defaultMessage: 'please check out our documentation.',
+});
+
+export const INVESTIGATION_QUERY_TITLE = i18n.translate(
+ 'xpack.securitySolution.alertDetails.investigationTimeQueryTitle',
+ {
+ defaultMessage: 'Investigation time enrichment',
+ }
+);
diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/event_details.test.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/event_details.test.tsx
index 6aff259d8220e2..f599cfa242dea6 100644
--- a/x-pack/plugins/security_solution/public/common/components/event_details/event_details.test.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/event_details/event_details.test.tsx
@@ -19,8 +19,10 @@ import { useMountAppended } from '../../utils/use_mount_appended';
import { mockAlertDetailsData } from './__mocks__';
import { TimelineEventsDetailsItem } from '../../../../common/search_strategy';
import { TimelineTabs } from '../../../../common/types/timeline';
+import { useInvestigationTimeEnrichment } from '../../containers/cti/event_enrichment';
jest.mock('../../../common/lib/kibana');
+jest.mock('../../containers/cti/event_enrichment');
jest.mock('../link_to');
describe('EventDetails', () => {
@@ -46,6 +48,7 @@ describe('EventDetails', () => {
let wrapper: ReactWrapper;
let alertsWrapper: ReactWrapper;
beforeAll(async () => {
+ (useInvestigationTimeEnrichment as jest.Mock).mockReturnValue({});
wrapper = mount(
diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/event_details.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/event_details.tsx
index c4092214633e5f..d07cdd81aa5f45 100644
--- a/x-pack/plugins/security_solution/public/common/components/event_details/event_details.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/event_details/event_details.tsx
@@ -5,27 +5,37 @@
* 2.0.
*/
-import { EuiTabbedContent, EuiTabbedContentTab, EuiSpacer } from '@elastic/eui';
+import {
+ EuiTabbedContent,
+ EuiTabbedContentTab,
+ EuiSpacer,
+ EuiButton,
+ EuiFlexGroup,
+ EuiFlexItem,
+ EuiLoadingContent,
+ EuiLoadingSpinner,
+} from '@elastic/eui';
import React, { useCallback, useMemo, useState } from 'react';
import styled from 'styled-components';
import { EventFieldsBrowser } from './event_fields_browser';
import { JsonView } from './json_view';
-import { ThreatSummaryView } from './threat_summary_view';
-import { ThreatDetailsView } from './threat_details_view';
+import { ThreatSummaryView } from './cti_details/threat_summary_view';
+import { ThreatDetailsView } from './cti_details/threat_details_view';
import * as i18n from './translations';
import { AlertSummaryView } from './alert_summary_view';
import { BrowserFields } from '../../containers/source';
+import { useInvestigationTimeEnrichment } from '../../containers/cti/event_enrichment';
import { TimelineEventsDetailsItem } from '../../../../common/search_strategy/timeline';
import { TimelineTabs } from '../../../../common/types/timeline';
-import { INDICATOR_DESTINATION_PATH } from '../../../../common/constants';
-import { getDataFromSourceHits } from '../../../../common/utils/field_formatters';
+import {
+ filterDuplicateEnrichments,
+ getEnrichmentFields,
+ parseExistingEnrichments,
+ timelineDataToEnrichment,
+} from './cti_details/helpers';
-interface EventViewTab {
- id: EventViewId;
- name: string;
- content: JSX.Element;
-}
+type EventViewTab = EuiTabbedContentTab;
export type EventViewId =
| EventsViewType.tableView
@@ -90,23 +100,33 @@ const EventDetailsComponent: React.FC = ({
(tab: EuiTabbedContentTab) => setSelectedTabId(tab.id as EventViewId),
[setSelectedTabId]
);
+ const viewThreatIntelTab = useCallback(() => setSelectedTabId(EventsViewType.threatIntelView), [
+ setSelectedTabId,
+ ]);
- const threatData = useMemo(() => {
- if (isAlert && data) {
- const threatIndicator = data.find(
- ({ field, originalValue }) => field === INDICATOR_DESTINATION_PATH && originalValue
- );
- if (!threatIndicator) return [];
- const { originalValue } = threatIndicator;
- const values = Array.isArray(originalValue) ? originalValue : [originalValue];
- return values.map((value) => getDataFromSourceHits(JSON.parse(value)));
+ const eventFields = useMemo(() => getEnrichmentFields(data ?? []), [data]);
+ const existingEnrichments = useMemo(
+ () =>
+ isAlert
+ ? parseExistingEnrichments(data).map((enrichmentData) =>
+ timelineDataToEnrichment(enrichmentData)
+ )
+ : [],
+ [data, isAlert]
+ );
+ const {
+ loading: enrichmentsLoading,
+ result: enrichmentsResponse,
+ } = useInvestigationTimeEnrichment(eventFields);
+ const allEnrichments = useMemo(() => {
+ if (enrichmentsLoading || !enrichmentsResponse?.enrichments) {
+ return existingEnrichments;
}
- return [];
- }, [data, isAlert]);
-
- const threatCount = useMemo(() => threatData.length, [threatData.length]);
+ return filterDuplicateEnrichments([...existingEnrichments, ...enrichmentsResponse.enrichments]);
+ }, [enrichmentsLoading, enrichmentsResponse, existingEnrichments]);
+ const enrichmentCount = allEnrichments.length;
- const summaryTab = useMemo(
+ const summaryTab: EventViewTab | undefined = useMemo(
() =>
isAlert
? {
@@ -120,15 +140,44 @@ const EventDetailsComponent: React.FC = ({
eventId: id,
browserFields,
timelineId,
- title: threatCount ? i18n.ALERT_SUMMARY : undefined,
+ title: i18n.ALERT_SUMMARY,
}}
/>
- {threatCount > 0 && }
+ {enrichmentsLoading && (
+ <>
+
+ >
+ )}
+ {enrichmentCount > 0 && (
+ <>
+
+
+
+
+ {i18n.VIEW_CTI_DATA}
+
+
+ >
+ )}
>
),
}
: undefined,
- [browserFields, data, id, isAlert, timelineId, threatCount]
+ [
+ isAlert,
+ data,
+ id,
+ browserFields,
+ timelineId,
+ enrichmentsLoading,
+ enrichmentCount,
+ allEnrichments,
+ viewThreatIntelTab,
+ ]
);
const threatIntelTab = useMemo(
@@ -137,11 +186,16 @@ const EventDetailsComponent: React.FC = ({
? {
id: EventsViewType.threatIntelView,
'data-test-subj': 'threatIntelTab',
- name: `${i18n.THREAT_INTEL} (${threatCount})`,
- content: ,
+ name: (
+
+ {`${i18n.THREAT_INTEL} `}
+ {enrichmentsLoading ? : `(${enrichmentCount})`}
+
+ ),
+ content: ,
}
: undefined,
- [isAlert, threatCount, threatData]
+ [allEnrichments, enrichmentCount, enrichmentsLoading, isAlert]
);
const tableTab = useMemo(
diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/helpers.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/helpers.tsx
index 8392be420a2c53..6002f66da43092 100644
--- a/x-pack/plugins/security_solution/public/common/components/event_details/helpers.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/event_details/helpers.tsx
@@ -64,16 +64,6 @@ export interface AlertSummaryRow {
};
}
-export interface ThreatSummaryRow {
- title: string;
- description: {
- contextId: string;
- eventId: string;
- fieldName: string;
- values: string[];
- };
-}
-
export interface ThreatDetailsRow {
title: string;
description: {
@@ -82,7 +72,7 @@ export interface ThreatDetailsRow {
};
}
-export type SummaryRow = AlertSummaryRow | ThreatSummaryRow | ThreatDetailsRow;
+export type SummaryRow = AlertSummaryRow | ThreatDetailsRow;
export const getColumnHeaderFromBrowserField = ({
browserField,
@@ -215,7 +205,6 @@ getTitle.displayName = 'getTitle';
export const getSummaryColumns = (
DescriptionComponent:
- | React.FC
| React.FC
| React.FC
): Array> => {
diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/summary_view.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/summary_view.tsx
index 1dda40ae4b19d7..0e846f3f6f6998 100644
--- a/x-pack/plugins/security_solution/public/common/components/event_details/summary_view.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/event_details/summary_view.tsx
@@ -12,19 +12,13 @@ import styled from 'styled-components';
import { SummaryRow } from './helpers';
// eslint-disable-next-line @typescript-eslint/no-explicit-any
-const StyledEuiInMemoryTable = styled(EuiInMemoryTable as any)`
+export const StyledEuiInMemoryTable = styled(EuiInMemoryTable as any)`
.euiTableHeaderCell {
border: none;
}
.euiTableRowCell {
border: none;
}
-
- .euiTableCellContent {
- display: flex;
- flex-direction: column;
- align-items: flex-start;
- }
`;
const StyledEuiTitle = styled(EuiTitle)`
diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/threat_details_view.test.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/threat_details_view.test.tsx
deleted file mode 100644
index 4b2f56a2050421..00000000000000
--- a/x-pack/plugins/security_solution/public/common/components/event_details/threat_details_view.test.tsx
+++ /dev/null
@@ -1,114 +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 from 'react';
-
-import { ThreatDetailsView } from './threat_details_view';
-
-import { TestProviders } from '../../mock';
-import { useMountAppended } from '../../utils/use_mount_appended';
-
-jest.mock('../../../detections/containers/detection_engine/rules/use_rule_async', () => {
- return {
- useRuleAsync: jest.fn(),
- };
-});
-
-const mostRecentDate = '2021-04-25T18:17:00.000Z';
-
-const threatData = [
- [
- {
- category: 'matched',
- field: 'matched.field',
- isObjectArray: false,
- originalValue: ['test_field_2'],
- values: ['test_field_2'],
- },
- {
- category: 'first_seen',
- field: 'first_seen',
- isObjectArray: false,
- originalValue: ['2019-04-25T18:17:00.000Z'],
- values: ['2019-04-25T18:17:00.000Z'],
- },
- {
- category: 'event',
- field: 'event.reference',
- isObjectArray: false,
- originalValue: ['https://test.com/'],
- values: ['https://test.com/'],
- },
- {
- category: 'event',
- field: 'event.url',
- isObjectArray: false,
- originalValue: ['https://test2.com/'],
- values: ['https://test2.com/'],
- },
- ],
- [
- {
- category: 'first_seen',
- field: 'first_seen',
- isObjectArray: false,
- originalValue: [mostRecentDate],
- values: [mostRecentDate],
- },
- {
- category: 'matched',
- field: 'matched.field',
- isObjectArray: false,
- originalValue: ['test_field'],
- values: ['test_field'],
- },
- ],
-];
-
-describe('ThreatDetailsView', () => {
- const mount = useMountAppended();
-
- beforeEach(() => {
- jest.clearAllMocks();
- });
-
- test('render correct items', () => {
- const wrapper = mount(
-
-
-
- );
- expect(wrapper.find('[data-test-subj="threat-details-view-0"]').exists()).toEqual(true);
- });
-
- test('renders empty view if there are no items', () => {
- const wrapper = mount(
-
-
-
- );
- expect(wrapper.find('[data-test-subj="empty-threat-details-view"]').exists()).toEqual(true);
- });
-
- test('renders link for event.url and event.reference', () => {
- const wrapper = mount(
-
-
-
- );
- expect(wrapper.find('a').length).toEqual(2);
- });
-
- test('orders items by first_seen', () => {
- const wrapper = mount(
-
-
-
- );
- expect(wrapper.find('.euiToolTipAnchor span').at(0).text()).toEqual(mostRecentDate);
- });
-});
diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/threat_details_view.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/threat_details_view.tsx
deleted file mode 100644
index 0f577200b7b47b..00000000000000
--- a/x-pack/plugins/security_solution/public/common/components/event_details/threat_details_view.tsx
+++ /dev/null
@@ -1,122 +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 {
- EuiBasicTableColumn,
- EuiFlexGroup,
- EuiFlexItem,
- EuiHorizontalRule,
- EuiSpacer,
- EuiToolTip,
- EuiLink,
-} from '@elastic/eui';
-import React from 'react';
-
-import { isEmpty } from 'fp-ts/Array';
-import { SummaryView } from './summary_view';
-import { getSummaryColumns, SummaryRow, ThreatDetailsRow } from './helpers';
-import { TimelineEventsDetailsItem } from '../../../../common/search_strategy';
-import { INDICATOR_DESTINATION_PATH } from '../../../../common/constants';
-import {
- FIRSTSEEN,
- INDICATOR_EVENT_URL,
- INDICATOR_REFERENCE,
-} from '../../../../common/cti/constants';
-import { EmptyThreatDetailsView } from './empty_threat_details_view';
-
-const ThreatDetailsDescription: React.FC = ({
- fieldName,
- value,
-}) => {
- const tooltipChild = [INDICATOR_EVENT_URL, INDICATOR_REFERENCE].some(
- (field) => field === fieldName
- ) ? (
-
- {value}
-
- ) : (
- {value}
- );
- return (
-
-
- {fieldName}
-
-
- }
- >
- {tooltipChild}
-
- );
-};
-
-const summaryColumns: Array> = getSummaryColumns(
- ThreatDetailsDescription
-);
-
-const getISOStringFromThreatDataItem = (threatDataItem: TimelineEventsDetailsItem[]) => {
- const firstSeen = threatDataItem.find(
- (item: TimelineEventsDetailsItem) => item.field === FIRSTSEEN
- );
- if (firstSeen) {
- const { originalValue } = firstSeen;
- const firstSeenValue = Array.isArray(originalValue) ? originalValue[0] : originalValue;
- if (!Number.isNaN(Date.parse(firstSeenValue))) {
- return firstSeenValue;
- }
- }
- return new Date(-1).toString();
-};
-
-const getThreatDetailsRowsArray = (threatData: TimelineEventsDetailsItem[][]) =>
- threatData
- .sort(
- (a, b) =>
- Date.parse(getISOStringFromThreatDataItem(b)) -
- Date.parse(getISOStringFromThreatDataItem(a))
- )
- .map((items) =>
- items.map(({ field, originalValue }) => ({
- title: field,
- description: {
- fieldName: `${INDICATOR_DESTINATION_PATH}.${field}`,
- value: Array.isArray(originalValue) ? originalValue[0] : originalValue,
- },
- }))
- );
-
-const ThreatDetailsViewComponent: React.FC<{
- threatData: TimelineEventsDetailsItem[][];
-}> = ({ threatData }) => {
- const threatDetailsRowsArray = getThreatDetailsRowsArray(threatData);
- return isEmpty(threatDetailsRowsArray) || isEmpty(threatDetailsRowsArray[0]) ? (
-
- ) : (
- <>
- {threatDetailsRowsArray.map((summaryRows, index, arr) => {
- const key = summaryRows.find((threat) => threat.title === 'matched.id')?.description
- .value[0];
- return (
-
- {index === 0 && }
-
- {index < arr.length - 1 && }
-
- );
- })}
- >
- );
-};
-
-export const ThreatDetailsView = React.memo(ThreatDetailsViewComponent);
diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/threat_summary_view.test.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/threat_summary_view.test.tsx
deleted file mode 100644
index fa12ff3db7895c..00000000000000
--- a/x-pack/plugins/security_solution/public/common/components/event_details/threat_summary_view.test.tsx
+++ /dev/null
@@ -1,43 +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 from 'react';
-
-import { ThreatSummaryView } from './threat_summary_view';
-import { TestProviders } from '../../mock';
-import { useMountAppended } from '../../utils/use_mount_appended';
-import { mockAlertDetailsData } from './__mocks__';
-import { TimelineEventsDetailsItem } from '../../../../common/search_strategy';
-
-jest.mock('../../../detections/containers/detection_engine/rules/use_rule_async', () => {
- return {
- useRuleAsync: jest.fn(),
- };
-});
-
-const props = {
- data: mockAlertDetailsData as TimelineEventsDetailsItem[],
- eventId: '5d1d53da502f56aacc14c3cb5c669363d102b31f99822e5d369d4804ed370a31',
- timelineId: 'detections-page',
-};
-
-describe('ThreatSummaryView', () => {
- const mount = useMountAppended();
-
- beforeEach(() => {
- jest.clearAllMocks();
- });
-
- test('render correct items', () => {
- const wrapper = mount(
-
-
-
- );
- expect(wrapper.find('[data-test-subj="threat-summary-view"]').exists()).toEqual(true);
- });
-});
diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/threat_summary_view.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/threat_summary_view.tsx
deleted file mode 100644
index 67b09e8e596990..00000000000000
--- a/x-pack/plugins/security_solution/public/common/components/event_details/threat_summary_view.tsx
+++ /dev/null
@@ -1,78 +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 { EuiBasicTableColumn, EuiSpacer } from '@elastic/eui';
-import React from 'react';
-
-import * as i18n from './translations';
-import { SummaryView } from './summary_view';
-import { getSummaryColumns, SummaryRow, ThreatSummaryRow } from './helpers';
-import { FormattedFieldValue } from '../../../timelines/components/timeline/body/renderers/formatted_field';
-import { TimelineEventsDetailsItem } from '../../../../common/search_strategy/timeline';
-import { SORTED_THREAT_SUMMARY_FIELDS } from '../../../../common/cti/constants';
-import { INDICATOR_DESTINATION_PATH } from '../../../../common/constants';
-
-const getThreatSummaryRows = (
- data: TimelineEventsDetailsItem[],
- timelineId: string,
- eventId: string
-) =>
- SORTED_THREAT_SUMMARY_FIELDS.map((threatSummaryField) => {
- const item = data.find(({ field }) => field === threatSummaryField);
- if (item) {
- const { field, originalValue } = item;
- return {
- title: field.replace(`${INDICATOR_DESTINATION_PATH}.`, ''),
- description: {
- values: Array.isArray(originalValue) ? originalValue : [originalValue],
- contextId: timelineId,
- eventId,
- fieldName: field,
- },
- };
- }
- return null;
- }).filter((item: ThreatSummaryRow | null): item is ThreatSummaryRow => !!item);
-
-const getDescription = ({
- contextId,
- eventId,
- fieldName,
- values,
-}: ThreatSummaryRow['description']): JSX.Element => (
- <>
- {values.map((value: string) => (
-
- ))}
- >
-);
-
-const summaryColumns: Array> = getSummaryColumns(getDescription);
-
-const ThreatSummaryViewComponent: React.FC<{
- data: TimelineEventsDetailsItem[];
- timelineId: string;
- eventId: string;
-}> = ({ data, timelineId, eventId }) => (
- <>
-
-
- >
-);
-
-export const ThreatSummaryView = React.memo(ThreatSummaryViewComponent);
diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/translations.ts b/x-pack/plugins/security_solution/public/common/components/event_details/translations.ts
index a28d1976ca9400..a17ca5e434ace7 100644
--- a/x-pack/plugins/security_solution/public/common/components/event_details/translations.ts
+++ b/x-pack/plugins/security_solution/public/common/components/event_details/translations.ts
@@ -23,23 +23,8 @@ export const THREAT_SUMMARY = i18n.translate('xpack.securitySolution.alertDetail
defaultMessage: 'Threat Summary',
});
-export const NO_ENRICHMENT_FOUND = i18n.translate(
- 'xpack.securitySolution.alertDetails.noEnrichmentFound',
- {
- defaultMessage: 'No Threat Intel Enrichment Found',
- }
-);
-
-export const IF_CTI_NOT_ENABLED = i18n.translate(
- 'xpack.securitySolution.alertDetails.ifCtiNotEnabled',
- {
- defaultMessage:
- "If you haven't enabled any threat intelligence sources and want to learn more about this capability, ",
- }
-);
-
-export const CHECK_DOCS = i18n.translate('xpack.securitySolution.alertDetails.checkDocs', {
- defaultMessage: 'please check out our documentation.',
+export const VIEW_CTI_DATA = i18n.translate('xpack.securitySolution.alertDetails.threatIntelCta', {
+ defaultMessage: 'View threat intel data',
});
export const INVESTIGATION_GUIDE = i18n.translate(
diff --git a/x-pack/plugins/security_solution/public/common/containers/cti/event_enrichment/api.ts b/x-pack/plugins/security_solution/public/common/containers/cti/event_enrichment/api.ts
new file mode 100644
index 00000000000000..179b4a53e3676c
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/common/containers/cti/event_enrichment/api.ts
@@ -0,0 +1,54 @@
+/*
+ * 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 { Observable } from 'rxjs';
+import { filter } from 'rxjs/operators';
+
+import { DataPublicPluginStart } from 'src/plugins/data/public';
+import {
+ isErrorResponse,
+ isCompleteResponse,
+} from '../../../../../../../../src/plugins/data/common';
+import {
+ CtiEventEnrichmentRequestOptions,
+ CtiEventEnrichmentStrategyResponse,
+ CtiQueries,
+} from '../../../../../common/search_strategy/security_solution/cti';
+
+type GetEventEnrichmentProps = CtiEventEnrichmentRequestOptions & {
+ data: DataPublicPluginStart;
+ signal: AbortSignal;
+};
+
+export const getEventEnrichment = ({
+ data,
+ defaultIndex,
+ eventFields,
+ filterQuery,
+ timerange,
+ signal,
+}: GetEventEnrichmentProps): Observable =>
+ data.search.search(
+ {
+ defaultIndex,
+ eventFields,
+ factoryQueryType: CtiQueries.eventEnrichment,
+ filterQuery,
+ timerange,
+ },
+ {
+ strategy: 'securitySolutionSearchStrategy',
+ abortSignal: signal,
+ }
+ );
+
+export const getEventEnrichmentComplete = (
+ props: GetEventEnrichmentProps
+): Observable =>
+ getEventEnrichment(props).pipe(
+ filter((response) => isErrorResponse(response) || isCompleteResponse(response))
+ );
diff --git a/x-pack/plugins/security_solution/public/common/containers/cti/event_enrichment/index.ts b/x-pack/plugins/security_solution/public/common/containers/cti/event_enrichment/index.ts
new file mode 100644
index 00000000000000..e8fb1a03045d9e
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/common/containers/cti/event_enrichment/index.ts
@@ -0,0 +1,9 @@
+/*
+ * 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 * from './use_event_enrichment';
+export * from './use_investigation_enrichment';
diff --git a/x-pack/plugins/security_solution/public/common/containers/cti/event_enrichment/translations.ts b/x-pack/plugins/security_solution/public/common/containers/cti/event_enrichment/translations.ts
new file mode 100644
index 00000000000000..ff9130b288fa89
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/common/containers/cti/event_enrichment/translations.ts
@@ -0,0 +1,15 @@
+/*
+ * 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 { i18n } from '@kbn/i18n';
+
+export const INVESTIGATION_ENRICHMENT_REQUEST_ERROR = i18n.translate(
+ 'xpack.securitySolution.investigationEnrichment.requestError',
+ {
+ defaultMessage: `An error occurred while requesting threat intelligence`,
+ }
+);
diff --git a/x-pack/plugins/security_solution/public/common/containers/cti/event_enrichment/use_event_enrichment.ts b/x-pack/plugins/security_solution/public/common/containers/cti/event_enrichment/use_event_enrichment.ts
new file mode 100644
index 00000000000000..939566d6e59c32
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/common/containers/cti/event_enrichment/use_event_enrichment.ts
@@ -0,0 +1,19 @@
+/*
+ * 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 { useObservable, withOptionalSignal } from '@kbn/securitysolution-hook-utils';
+
+import { getEventEnrichment, getEventEnrichmentComplete } from './api';
+
+const getEventEnrichmentOptionalSignal = withOptionalSignal(getEventEnrichment);
+
+export const useEventEnrichment = () => useObservable(getEventEnrichmentOptionalSignal);
+
+const getEventEnrichmentCompleteWithOptionalSignal = withOptionalSignal(getEventEnrichmentComplete);
+
+export const useEventEnrichmentComplete = () =>
+ useObservable(getEventEnrichmentCompleteWithOptionalSignal);
diff --git a/x-pack/plugins/security_solution/public/common/containers/cti/event_enrichment/use_investigation_enrichment.ts b/x-pack/plugins/security_solution/public/common/containers/cti/event_enrichment/use_investigation_enrichment.ts
new file mode 100644
index 00000000000000..c15b49fe5c41e3
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/common/containers/cti/event_enrichment/use_investigation_enrichment.ts
@@ -0,0 +1,83 @@
+/*
+ * 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 { useCallback, useEffect, useState } from 'react';
+import { useDispatch } from 'react-redux';
+import { isEmpty } from 'lodash';
+
+import { EventFields } from '../../../../../common/search_strategy/security_solution/cti';
+import {
+ DEFAULT_CTI_SOURCE_INDEX,
+ DEFAULT_EVENT_ENRICHMENT_FROM,
+ DEFAULT_EVENT_ENRICHMENT_TO,
+} from '../../../../../common/cti/constants';
+import { useAppToasts } from '../../../hooks/use_app_toasts';
+import { useKibana } from '../../../lib/kibana';
+import { inputsActions } from '../../../store/actions';
+import * as i18n from './translations';
+import { useEventEnrichmentComplete } from '.';
+
+export const QUERY_ID = 'investigation_time_enrichment';
+const noop = () => {};
+
+export const useInvestigationTimeEnrichment = (eventFields: EventFields) => {
+ const { addError } = useAppToasts();
+ const kibana = useKibana();
+ const dispatch = useDispatch();
+ const [{ from, to }, setRange] = useState({
+ from: DEFAULT_EVENT_ENRICHMENT_FROM,
+ to: DEFAULT_EVENT_ENRICHMENT_TO,
+ });
+ const { error, loading, result, start } = useEventEnrichmentComplete();
+
+ const deleteQuery = useCallback(() => {
+ dispatch(inputsActions.deleteOneQuery({ inputId: 'global', id: QUERY_ID }));
+ }, [dispatch]);
+
+ useEffect(() => {
+ if (!loading && result) {
+ dispatch(
+ inputsActions.setQuery({
+ inputId: 'global',
+ id: QUERY_ID,
+ inspect: {
+ dsl: result.inspect.dsl,
+ response: [JSON.stringify(result.rawResponse, null, 2)],
+ },
+ loading,
+ refetch: noop,
+ })
+ );
+ }
+
+ return deleteQuery;
+ }, [deleteQuery, dispatch, loading, result]);
+
+ useEffect(() => {
+ if (error) {
+ addError(error, { title: i18n.INVESTIGATION_ENRICHMENT_REQUEST_ERROR });
+ }
+ }, [addError, error]);
+
+ useEffect(() => {
+ if (!isEmpty(eventFields)) {
+ start({
+ data: kibana.services.data,
+ timerange: { from, to, interval: '' },
+ defaultIndex: DEFAULT_CTI_SOURCE_INDEX,
+ eventFields,
+ filterQuery: '',
+ });
+ }
+ }, [from, start, kibana.services.data, to, eventFields]);
+
+ return {
+ loading,
+ result,
+ setRange,
+ };
+};
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/enrich_signal_threat_matches.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/enrich_signal_threat_matches.test.ts
index 7c80572f6b1ee7..4a51880e0f227d 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/enrich_signal_threat_matches.test.ts
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/enrich_signal_threat_matches.test.ts
@@ -7,6 +7,7 @@
import { get } from 'lodash';
import { INDICATOR_DESTINATION_PATH } from '../../../../../common/constants';
+import { ENRICHMENT_TYPES } from '../../../../../common/cti/constants';
import { getThreatListItemMock } from './build_threat_mapping_filter.mock';
import {
@@ -158,14 +159,14 @@ describe('buildMatchedIndicator', () => {
expect(get(indicator, 'matched.field')).toEqual('event.field');
});
- it('returns the type of the matched indicator as matched.type', () => {
+ it('returns the type of the enrichment as an indicator match type', () => {
const [indicator] = buildMatchedIndicator({
queries,
threats,
indicatorPath,
});
- expect(get(indicator, 'matched.type')).toEqual('type_1');
+ expect(get(indicator, 'matched.type')).toEqual(ENRICHMENT_TYPES.IndicatorMatchRule);
});
it('returns indicators for each provided query', () => {
@@ -216,7 +217,7 @@ describe('buildMatchedIndicator', () => {
id: '123',
index: 'threat-index',
field: 'event.field',
- type: 'type_1',
+ type: ENRICHMENT_TYPES.IndicatorMatchRule,
},
other: 'other_1',
type: 'type_1',
@@ -263,7 +264,7 @@ describe('buildMatchedIndicator', () => {
id: '123',
index: 'threat-index',
field: 'event.field',
- type: 'indicator_type',
+ type: ENRICHMENT_TYPES.IndicatorMatchRule,
},
type: 'indicator_type',
event: {
@@ -294,7 +295,7 @@ describe('buildMatchedIndicator', () => {
id: '123',
index: 'threat-index',
field: 'event.field',
- type: undefined,
+ type: ENRICHMENT_TYPES.IndicatorMatchRule,
},
},
]);
@@ -321,7 +322,7 @@ describe('buildMatchedIndicator', () => {
id: '123',
index: 'threat-index',
field: 'event.field',
- type: undefined,
+ type: ENRICHMENT_TYPES.IndicatorMatchRule,
},
},
]);
@@ -359,7 +360,7 @@ describe('buildMatchedIndicator', () => {
id: '123',
index: 'threat-index',
field: 'event.field',
- type: 'first',
+ type: ENRICHMENT_TYPES.IndicatorMatchRule,
},
type: 'first',
event: {
@@ -478,7 +479,7 @@ describe('enrichSignalThreatMatches', () => {
id: '123',
index: 'indicator_index',
field: 'event.field',
- type: 'type_1',
+ type: ENRICHMENT_TYPES.IndicatorMatchRule,
},
other: 'other_1',
type: 'type_1',
@@ -510,7 +511,7 @@ describe('enrichSignalThreatMatches', () => {
id: '123',
index: 'indicator_index',
field: 'event.field',
- type: undefined,
+ type: ENRICHMENT_TYPES.IndicatorMatchRule,
},
},
]);
@@ -543,7 +544,7 @@ describe('enrichSignalThreatMatches', () => {
id: '123',
index: 'indicator_index',
field: 'event.field',
- type: 'type_1',
+ type: ENRICHMENT_TYPES.IndicatorMatchRule,
},
other: 'other_1',
type: 'type_1',
@@ -608,7 +609,7 @@ describe('enrichSignalThreatMatches', () => {
id: '123',
index: 'custom_index',
field: 'event.field',
- type: 'custom_type',
+ type: ENRICHMENT_TYPES.IndicatorMatchRule,
},
other: 'custom_other',
type: 'custom_type',
@@ -670,7 +671,7 @@ describe('enrichSignalThreatMatches', () => {
id: '123',
index: 'indicator_index',
field: 'event.field',
- type: 'type_1',
+ type: ENRICHMENT_TYPES.IndicatorMatchRule,
},
event: {
category: 'threat',
@@ -685,7 +686,7 @@ describe('enrichSignalThreatMatches', () => {
id: '456',
index: 'other_custom_index',
field: 'event.other',
- type: 'type_2',
+ type: ENRICHMENT_TYPES.IndicatorMatchRule,
},
event: {
category: 'bad',
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/enrich_signal_threat_matches.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/enrich_signal_threat_matches.ts
index c26f03d1dd480b..3423cc1a8744f4 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/enrich_signal_threat_matches.ts
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/enrich_signal_threat_matches.ts
@@ -6,6 +6,7 @@
*/
import { get, isObject } from 'lodash';
+import { ENRICHMENT_TYPES } from '../../../../../common/cti/constants';
import type { SignalSearchResponse, SignalSourceHit } from '../types';
import type {
@@ -56,13 +57,18 @@ export const buildMatchedIndicator = ({
throw new Error(`Expected indicator field to be an object, but found: ${indicator}`);
}
const atomic = get(matchedThreat?._source, query.value) as unknown;
- const type = get(indicator, 'type') as unknown;
const event = get(matchedThreat?._source, 'event') as unknown;
return {
...indicator,
event,
- matched: { atomic, field: query.field, id: query.id, index: query.index, type },
+ matched: {
+ atomic,
+ field: query.field,
+ id: query.id,
+ index: query.index,
+ type: ENRICHMENT_TYPES.IndicatorMatchRule,
+ },
};
});
diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/cti/event_enrichment/helpers.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/cti/event_enrichment/helpers.ts
index e4ed05baeed778..f24bfc08b39e0f 100644
--- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/cti/event_enrichment/helpers.ts
+++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/cti/event_enrichment/helpers.ts
@@ -8,14 +8,16 @@
import { get, isEmpty } from 'lodash';
import { estypes } from '@elastic/elasticsearch';
-import { EVENT_ENRICHMENT_INDICATOR_FIELD_MAP } from '../../../../../../common/cti/constants';
-import { CtiEnrichment } from '../../../../../../common/search_strategy/security_solution/cti';
-
-type EventField = keyof typeof EVENT_ENRICHMENT_INDICATOR_FIELD_MAP;
-const validEventFields = Object.keys(EVENT_ENRICHMENT_INDICATOR_FIELD_MAP) as EventField[];
-
-const isValidEventField = (field: string): field is EventField =>
- validEventFields.includes(field as EventField);
+import {
+ ENRICHMENT_TYPES,
+ EVENT_ENRICHMENT_INDICATOR_FIELD_MAP,
+} from '../../../../../../common/cti/constants';
+import {
+ CtiEnrichment,
+ EventField,
+ isValidEventField,
+ validEventFields,
+} from '../../../../../../common/search_strategy/security_solution/cti';
export const buildIndicatorShouldClauses = (
eventFields: Record
@@ -67,6 +69,7 @@ const buildIndicatorMatchedFields = (
'matched.field': [eventField],
'matched.id': [hit._id],
'matched.index': [hit._index],
+ 'matched.type': [ENRICHMENT_TYPES.InvestigationTime],
};
};
diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/create_threat_matching.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/create_threat_matching.ts
index e6a835462619c1..c64713575c1303 100644
--- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/create_threat_matching.ts
+++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/create_threat_matching.ts
@@ -28,6 +28,7 @@ import {
import { getCreateThreatMatchRulesSchemaMock } from '../../../../plugins/security_solution/common/detection_engine/schemas/request/rule_schemas.mock';
import { getThreatMatchingSchemaPartialMock } from '../../../../plugins/security_solution/common/detection_engine/schemas/response/rules_schema.mocks';
import { SIGNALS_TEMPLATE_VERSION } from '../../../../plugins/security_solution/server/lib/detection_engine/routes/index/get_signals_template';
+import { ENRICHMENT_TYPES } from '../../../../plugins/security_solution/common/cti/constants';
const format = (value: unknown): string => JSON.stringify(value, null, 2);
@@ -425,7 +426,7 @@ export default ({ getService }: FtrProviderContext) => {
id: '978783',
index: 'filebeat-8.0.0-2021.01.26-000001',
field: 'destination.ip',
- type: 'url',
+ type: ENRICHMENT_TYPES.IndicatorMatchRule,
},
provider: 'geenensp',
type: 'url',
@@ -457,7 +458,7 @@ export default ({ getService }: FtrProviderContext) => {
id: '978783',
index: 'filebeat-8.0.0-2021.01.26-000001',
field: 'destination.ip',
- type: 'url',
+ type: ENRICHMENT_TYPES.IndicatorMatchRule,
},
provider: 'geenensp',
type: 'url',
@@ -519,7 +520,7 @@ export default ({ getService }: FtrProviderContext) => {
id: '978785',
index: 'filebeat-8.0.0-2021.01.26-000001',
field: 'source.ip',
- type: 'url',
+ type: ENRICHMENT_TYPES.IndicatorMatchRule,
},
port: 57324,
provider: 'geenensp',
@@ -544,7 +545,7 @@ export default ({ getService }: FtrProviderContext) => {
id: '978787',
index: 'filebeat-8.0.0-2021.01.26-000001',
field: 'source.ip',
- type: 'ip',
+ type: ENRICHMENT_TYPES.IndicatorMatchRule,
},
provider: 'other_provider',
type: 'ip',
@@ -619,7 +620,7 @@ export default ({ getService }: FtrProviderContext) => {
id: '978785',
index: 'filebeat-8.0.0-2021.01.26-000001',
field: 'source.ip',
- type: 'url',
+ type: ENRICHMENT_TYPES.IndicatorMatchRule,
},
port: 57324,
provider: 'geenensp',
@@ -649,7 +650,7 @@ export default ({ getService }: FtrProviderContext) => {
id: '978785',
index: 'filebeat-8.0.0-2021.01.26-000001',
field: 'source.port',
- type: 'url',
+ type: ENRICHMENT_TYPES.IndicatorMatchRule,
},
port: 57324,
provider: 'geenensp',
@@ -674,7 +675,7 @@ export default ({ getService }: FtrProviderContext) => {
id: '978787',
index: 'filebeat-8.0.0-2021.01.26-000001',
field: 'source.ip',
- type: 'ip',
+ type: ENRICHMENT_TYPES.IndicatorMatchRule,
},
provider: 'other_provider',
type: 'ip',
@@ -754,7 +755,7 @@ export default ({ getService }: FtrProviderContext) => {
id: '978783',
index: 'filebeat-8.0.0-2021.01.26-000001',
field: 'destination.ip',
- type: 'url',
+ type: ENRICHMENT_TYPES.IndicatorMatchRule,
},
provider: 'geenensp',
type: 'url',
@@ -785,7 +786,7 @@ export default ({ getService }: FtrProviderContext) => {
id: '978783',
index: 'filebeat-8.0.0-2021.01.26-000001',
field: 'destination.ip',
- type: 'url',
+ type: ENRICHMENT_TYPES.IndicatorMatchRule,
},
provider: 'geenensp',
type: 'url',
@@ -813,7 +814,7 @@ export default ({ getService }: FtrProviderContext) => {
id: '978785',
index: 'filebeat-8.0.0-2021.01.26-000001',
field: 'source.ip',
- type: 'url',
+ type: ENRICHMENT_TYPES.IndicatorMatchRule,
},
port: 57324,
provider: 'geenensp',
@@ -838,7 +839,7 @@ export default ({ getService }: FtrProviderContext) => {
id: '978785',
index: 'filebeat-8.0.0-2021.01.26-000001',
field: 'source.port',
- type: 'url',
+ type: ENRICHMENT_TYPES.IndicatorMatchRule,
},
port: 57324,
provider: 'geenensp',
diff --git a/x-pack/test/security_solution_cypress/es_archives/suspicious_source_event/data.json b/x-pack/test/security_solution_cypress/es_archives/suspicious_source_event/data.json
index 11b5e9bd0828b5..543250ba174994 100644
--- a/x-pack/test/security_solution_cypress/es_archives/suspicious_source_event/data.json
+++ b/x-pack/test/security_solution_cypress/es_archives/suspicious_source_event/data.json
@@ -7,6 +7,9 @@
"@timestamp": "2021-02-22T21:00:49.337Z",
"myhash": {
"mysha256": "a04ac6d98ad989312783d4fe3456c53730b212c79a426fb215708b6c6daa3de3"
+ },
+ "source": {
+ "ip": "192.168.1.1"
}
}
}
diff --git a/x-pack/test/security_solution_cypress/es_archives/threat_indicator/data.json b/x-pack/test/security_solution_cypress/es_archives/threat_indicator/data.json
index 9573372d02e9c5..c5d382194027fa 100644
--- a/x-pack/test/security_solution_cypress/es_archives/threat_indicator/data.json
+++ b/x-pack/test/security_solution_cypress/es_archives/threat_indicator/data.json
@@ -47,7 +47,6 @@
"input": {
"type": "httpjson"
},
- "@timestamp": "2021-03-10T14:51:07.663Z",
"ecs": {
"version": "1.6.0"
},
diff --git a/x-pack/test/security_solution_cypress/es_archives/threat_indicator2/data.json b/x-pack/test/security_solution_cypress/es_archives/threat_indicator2/data.json
new file mode 100644
index 00000000000000..0598fd7ba7c866
--- /dev/null
+++ b/x-pack/test/security_solution_cypress/es_archives/threat_indicator2/data.json
@@ -0,0 +1,63 @@
+{
+ "type": "doc",
+ "value": {
+ "id": "a04ac6d98ad989312783d4fe3456c53730b212c79a426fb215708b6c6daa3de3",
+ "index": "filebeat-7.12.0-2021.03.11-000001",
+ "source": {
+ "@timestamp": "2021-06-27T14:51:05.766Z",
+ "agent": {
+ "ephemeral_id": "34c78500-8df5-4a07-ba87-1cc738b98431",
+ "hostname": "test",
+ "id": "08a3d064-8f23-41f3-84b2-f917f6ff9588",
+ "name": "test",
+ "type": "filebeat",
+ "version": "7.12.0"
+ },
+ "fileset": {
+ "name": "abusemalware"
+ },
+ "threatintel": {
+ "indicator": {
+ "first_seen": "2021-03-11T08:02:14.000Z",
+ "ip": "192.168.1.1",
+ "provider": "another_provider",
+ "type": "ip"
+ },
+ "abusemalware": {
+ "virustotal": {
+ "result": "38 / 61",
+ "link": "https://www.virustotal.com/gui/file/a04ac6d98ad989312783d4fe3456c53730b212c79a426fb215708b6c6daa3de3/detection/f-a04ac6d",
+ "percent": "62.30"
+ }
+ }
+ },
+ "tags": ["threatintel-abusemalware", "forwarded"],
+ "input": {
+ "type": "httpjson"
+ },
+ "ecs": {
+ "version": "1.6.0"
+ },
+ "related": {
+ "hash": [
+ "9b6c3518a91d23ed77504b5416bfb5b3",
+ "a04ac6d98ad989312783d4fe3456c53730b212c79a426fb215708b6c6daa3de3",
+ "1536:87vbq1lGAXSEYQjbChaAU2yU23M51DjZgSQAvcYkFtZTjzBht5:8D+CAXFYQChaAUk5ljnQssL"
+ ]
+ },
+ "service": {
+ "type": "threatintel"
+ },
+ "event": {
+ "reference": "https://urlhaus-api.abuse.ch/v1/download/a04ac6d98ad989312783d4fe3456c53730b212c79a426fb215708b6c6daa3de3/",
+ "ingested": "2021-03-11T14:51:09.809069Z",
+ "created": "2021-03-11T14:51:07.663Z",
+ "kind": "enrichment",
+ "module": "threatintel",
+ "category": "threat",
+ "type": "indicator",
+ "dataset": "threatintel.abusemalware"
+ }
+ }
+ }
+}
diff --git a/x-pack/test/security_solution_cypress/es_archives/threat_indicator2/mappings.json b/x-pack/test/security_solution_cypress/es_archives/threat_indicator2/mappings.json
new file mode 100644
index 00000000000000..072318f7f4fc49
--- /dev/null
+++ b/x-pack/test/security_solution_cypress/es_archives/threat_indicator2/mappings.json
@@ -0,0 +1,822 @@
+{
+ "type": "index",
+ "value": {
+ "aliases": {
+ "filebeat-7.12.0": {
+ "is_write_index": false
+ }
+ },
+ "index": "filebeat-7.12.0-2021.03.11-000001",
+ "mappings": {
+ "_meta": {
+ "beat": "filebeat",
+ "version": "7.12.0"
+ },
+ "date_detection": false,
+ "dynamic_templates": [
+ {
+ "labels": {
+ "mapping": {
+ "type": "keyword"
+ },
+ "match_mapping_type": "string",
+ "path_match": "labels.*"
+ }
+ },
+ {
+ "container.labels": {
+ "mapping": {
+ "type": "keyword"
+ },
+ "match_mapping_type": "string",
+ "path_match": "container.labels.*"
+ }
+ },
+ {
+ "fields": {
+ "mapping": {
+ "type": "keyword"
+ },
+ "match_mapping_type": "string",
+ "path_match": "fields.*"
+ }
+ },
+ {
+ "docker.container.labels": {
+ "mapping": {
+ "type": "keyword"
+ },
+ "match_mapping_type": "string",
+ "path_match": "docker.container.labels.*"
+ }
+ },
+ {
+ "kubernetes.labels.*": {
+ "mapping": {
+ "type": "keyword"
+ },
+ "path_match": "kubernetes.labels.*"
+ }
+ },
+ {
+ "kubernetes.annotations.*": {
+ "mapping": {
+ "type": "keyword"
+ },
+ "path_match": "kubernetes.annotations.*"
+ }
+ },
+ {
+ "kubernetes.service.selectors.*": {
+ "mapping": {
+ "type": "keyword"
+ },
+ "path_match": "kubernetes.service.selectors.*"
+ }
+ },
+ {
+ "docker.attrs": {
+ "mapping": {
+ "type": "keyword"
+ },
+ "match_mapping_type": "string",
+ "path_match": "docker.attrs.*"
+ }
+ },
+ {
+ "azure.activitylogs.identity.claims.*": {
+ "mapping": {
+ "type": "keyword"
+ },
+ "path_match": "azure.activitylogs.identity.claims.*"
+ }
+ },
+ {
+ "azure.platformlogs.properties.*": {
+ "mapping": {
+ "type": "keyword"
+ },
+ "path_match": "azure.platformlogs.properties.*"
+ }
+ },
+ {
+ "kibana.log.meta": {
+ "mapping": {
+ "type": "keyword"
+ },
+ "match_mapping_type": "string",
+ "path_match": "kibana.log.meta.*"
+ }
+ },
+ {
+ "strings_as_keyword": {
+ "mapping": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "match_mapping_type": "string"
+ }
+ }
+ ],
+ "properties": {
+ "@timestamp": {
+ "type": "date"
+ },
+ "agent": {
+ "properties": {
+ "build": {
+ "properties": {
+ "original": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "ephemeral_id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "hostname": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "type": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "version": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "apache": {
+ "properties": {
+ "access": {
+ "properties": {
+ "ssl": {
+ "properties": {
+ "cipher": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "protocol": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ }
+ }
+ },
+ "error": {
+ "properties": {
+ "module": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ }
+ }
+ },
+ "fileset": {
+ "properties": {
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "threatintel": {
+ "properties": {
+ "abusemalware": {
+ "properties": {
+ "file_type": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "signature": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "urlhaus_download": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "virustotal": {
+ "properties": {
+ "link": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "percent": {
+ "type": "float"
+ },
+ "result": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ }
+ }
+ },
+ "abuseurl": {
+ "properties": {
+ "blacklists": {
+ "properties": {
+ "spamhaus_dbl": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "surbl": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "larted": {
+ "type": "boolean"
+ },
+ "reporter": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "tags": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "threat": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "url_status": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "urlhaus_reference": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "anomali": {
+ "properties": {
+ "content": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "description": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "indicator": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "labels": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "modified": {
+ "type": "date"
+ },
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "object_marking_refs": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "pattern": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "title": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "type": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "valid_from": {
+ "type": "date"
+ }
+ }
+ },
+ "indicator": {
+ "properties": {
+ "as": {
+ "properties": {
+ "number": {
+ "type": "long"
+ },
+ "organization": {
+ "properties": {
+ "name": {
+ "fields": {
+ "text": {
+ "norms": false,
+ "type": "text"
+ }
+ },
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ }
+ }
+ },
+ "confidence": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "dataset": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "description": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "domain": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "email": {
+ "properties": {
+ "address": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "file": {
+ "properties": {
+ "hash": {
+ "properties": {
+ "md5": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "sha1": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "sha256": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "sha512": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "ssdeep": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "tlsh": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "pe": {
+ "properties": {
+ "imphash": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "size": {
+ "type": "long"
+ },
+ "type": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "first_seen": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "geo": {
+ "properties": {
+ "geo": {
+ "properties": {
+ "city_name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "country_iso_code": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "country_name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "location": {
+ "type": "geo_point"
+ },
+ "region_iso_code": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "region_name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ }
+ }
+ },
+ "ip": {
+ "type": "ip"
+ },
+ "last_seen": {
+ "type": "date"
+ },
+ "marking": {
+ "properties": {
+ "tlp": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "matched": {
+ "properties": {
+ "atomic": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "field": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "type": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "module": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "port": {
+ "type": "long"
+ },
+ "provider": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "registry": {
+ "properties": {
+ "data": {
+ "properties": {
+ "strings": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "key": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "path": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "value": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "scanner_stats": {
+ "type": "long"
+ },
+ "sightings": {
+ "type": "long"
+ },
+ "type": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "url": {
+ "properties": {
+ "domain": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "extension": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "fragment": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "full": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "original": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "password": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "path": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "port": {
+ "type": "long"
+ },
+ "query": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "registered_domain": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "scheme": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "subdomain": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "top_level_domain": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "username": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "x509": {
+ "properties": {
+ "alternative_names": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "issuer": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "serial_number": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "subject": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ }
+ }
+ },
+ "misp": {
+ "properties": {
+ "attribute": {
+ "properties": {
+ "category": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "comment": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "deleted": {
+ "type": "boolean"
+ },
+ "disable_correlation": {
+ "type": "boolean"
+ },
+ "distribution": {
+ "type": "long"
+ },
+ "event_id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "object_id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "object_relation": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "sharing_group_id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "timestamp": {
+ "type": "date"
+ },
+ "to_ids": {
+ "type": "boolean"
+ },
+ "type": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "uuid": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "value": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "attribute_count": {
+ "type": "long"
+ },
+ "date": {
+ "type": "date"
+ },
+ "disable_correlation": {
+ "type": "boolean"
+ },
+ "distribution": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "extends_uuid": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "info": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "locked": {
+ "type": "boolean"
+ },
+ "org": {
+ "properties": {
+ "id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "local": {
+ "type": "boolean"
+ },
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "uuid": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "org_id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "orgc": {
+ "properties": {
+ "id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "local": {
+ "type": "boolean"
+ },
+ "name": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "uuid": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "orgc_id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "proposal_email_lock": {
+ "type": "boolean"
+ },
+ "publish_timestamp": {
+ "type": "date"
+ },
+ "published": {
+ "type": "boolean"
+ },
+ "sharing_group_id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "threat_level_id": {
+ "type": "long"
+ },
+ "timestamp": {
+ "type": "date"
+ },
+ "uuid": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ },
+ "otx": {
+ "properties": {
+ "content": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "description": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "id": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "indicator": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "title": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ },
+ "type": {
+ "ignore_above": 1024,
+ "type": "keyword"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "settings": {
+ "index": {
+ "lifecycle": {
+ "name": "filebeat",
+ "rollover_alias": "filebeat-7.12.0"
+ },
+ "mapping": {
+ "total_fields": {
+ "limit": "10000"
+ }
+ },
+ "max_docvalue_fields_search": "200",
+ "number_of_replicas": "1",
+ "number_of_shards": "1",
+ "refresh_interval": "5s"
+ }
+ }
+ }
+}
From 345b9382b171f3a3de2af2f78148dd1837781d12 Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Wed, 30 Jun 2021 09:11:31 +0200
Subject: [PATCH 037/121] Update dependency @elastic/charts to v31 (#102078)
Co-authored-by: Renovate Bot
---
package.json | 2 +-
yarn.lock | 18 +++++++++---------
2 files changed, 10 insertions(+), 10 deletions(-)
diff --git a/package.json b/package.json
index 1cdeed4189cd32..23a3e823b2e3c8 100644
--- a/package.json
+++ b/package.json
@@ -99,7 +99,7 @@
"dependencies": {
"@elastic/apm-rum": "^5.8.0",
"@elastic/apm-rum-react": "^1.2.11",
- "@elastic/charts": "30.1.0",
+ "@elastic/charts": "31.0.0",
"@elastic/datemath": "link:bazel-bin/packages/elastic-datemath",
"@elastic/elasticsearch": "npm:@elastic/elasticsearch-canary@^8.0.0-canary.13",
"@elastic/ems-client": "7.14.0",
diff --git a/yarn.lock b/yarn.lock
index 10cbb9789ba984..a86eb52398d214 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1359,10 +1359,10 @@
dependencies:
object-hash "^1.3.0"
-"@elastic/charts@30.1.0":
- version "30.1.0"
- resolved "https://registry.yarnpkg.com/@elastic/charts/-/charts-30.1.0.tgz#eb9b3348c149ce13f74876738a9d2899b6b10067"
- integrity sha512-aUfXRQYQopm+6O48tEO0v/w6fETYORGiSPBRtqlq5xPncZGhGnQbgXVNQsPngYqapnKpOupXAqzjopF+RJ4QWg==
+"@elastic/charts@31.0.0":
+ version "31.0.0"
+ resolved "https://registry.yarnpkg.com/@elastic/charts/-/charts-31.0.0.tgz#c0b177313192bf5999835b8e01dc20471a8e7ef8"
+ integrity sha512-k8IJEwUBzzAjjkw1GDbt3laUfBoyc2t46+i1hs0Ni3ehb9jVntJSbYMBDReUDjWQSrUvrgHM6Xht1urBsdLS8Q==
dependencies:
"@popperjs/core" "^2.4.0"
chroma-js "^2.1.0"
@@ -1381,7 +1381,7 @@
redux "^4.0.4"
reselect "^4.0.0"
resize-observer-polyfill "^1.5.1"
- ts-debounce "^1.0.0"
+ ts-debounce "^3.0.0"
utility-types "^3.10.0"
uuid "^3.3.2"
@@ -27119,10 +27119,10 @@ trough@^1.0.0:
dependencies:
glob "^6.0.4"
-ts-debounce@^1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/ts-debounce/-/ts-debounce-1.0.0.tgz#e433301744ba75fe25466f7f23e1382c646aae6a"
- integrity sha512-V+IzWj418IoqqxVJD6I0zjPtgIyvAJ8VyViqzcxZ0JRiJXsi5mCmy1yUKkWd2gUygT28a8JsVFCgqdrf2pLUHQ==
+ts-debounce@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/ts-debounce/-/ts-debounce-3.0.0.tgz#9beedf59c04de3b5bef8ff28bd6885624df357be"
+ integrity sha512-7jiRWgN4/8IdvCxbIwnwg2W0bbYFBH6BxFqBjMKk442t7+liF2Z1H6AUCcl8e/pD93GjPru+axeiJwFmRww1WQ==
ts-dedent@^2.0.0:
version "2.0.0"
From b92d955b5648bc24916e2d9f7e5f65d568daa734 Mon Sep 17 00:00:00 2001
From: Lisa Cawley
Date: Wed, 30 Jun 2021 00:27:58 -0700
Subject: [PATCH 038/121] [SECURITY] Adds security links to doc link service
(#102676)
---
...-plugin-core-public.doclinksstart.links.md | 11 ++++++++++
...kibana-plugin-core-public.doclinksstart.md | 2 +-
.../public/doc_links/doc_links_service.ts | 22 +++++++++++++++++++
src/core/public/public.api.md | 11 ++++++++++
4 files changed, 45 insertions(+), 1 deletion(-)
diff --git a/docs/development/core/public/kibana-plugin-core-public.doclinksstart.links.md b/docs/development/core/public/kibana-plugin-core-public.doclinksstart.links.md
index 8754d19e2fc138..3650fe970d8fcf 100644
--- a/docs/development/core/public/kibana-plugin-core-public.doclinksstart.links.md
+++ b/docs/development/core/public/kibana-plugin-core-public.doclinksstart.links.md
@@ -28,9 +28,13 @@ readonly links: {
readonly elasticsearchModule: string;
readonly startup: string;
readonly exportedFields: string;
+ readonly suricataModule: string;
+ readonly zeekModule: string;
};
readonly auditbeat: {
readonly base: string;
+ readonly auditdModule: string;
+ readonly systemModule: string;
};
readonly metricbeat: {
readonly base: string;
@@ -47,6 +51,9 @@ readonly links: {
readonly heartbeat: {
readonly base: string;
};
+ readonly libbeat: {
+ readonly getStarted: string;
+ };
readonly logstash: {
readonly base: string;
};
@@ -123,6 +130,10 @@ readonly links: {
readonly siem: {
readonly guide: string;
readonly gettingStarted: string;
+ readonly ml: string;
+ readonly ruleChangeLog: string;
+ readonly detectionsReq: string;
+ readonly networkMap: string;
};
readonly query: {
readonly eql: string;
diff --git a/docs/development/core/public/kibana-plugin-core-public.doclinksstart.md b/docs/development/core/public/kibana-plugin-core-public.doclinksstart.md
index 7dbb8a58694853..4f66cc9a2c10f9 100644
--- a/docs/development/core/public/kibana-plugin-core-public.doclinksstart.md
+++ b/docs/development/core/public/kibana-plugin-core-public.doclinksstart.md
@@ -17,5 +17,5 @@ export interface DocLinksStart
| --- | --- | --- |
| [DOC\_LINK\_VERSION](./kibana-plugin-core-public.doclinksstart.doc_link_version.md) | string
| |
| [ELASTIC\_WEBSITE\_URL](./kibana-plugin-core-public.doclinksstart.elastic_website_url.md) | string
| |
-| [links](./kibana-plugin-core-public.doclinksstart.links.md) | {
readonly settings: string;
readonly canvas: {
readonly guide: string;
};
readonly dashboard: {
readonly guide: string;
readonly drilldowns: string;
readonly drilldownsTriggerPicker: string;
readonly urlDrilldownTemplateSyntax: string;
readonly urlDrilldownVariables: string;
};
readonly discover: Record<string, string>;
readonly filebeat: {
readonly base: string;
readonly installation: string;
readonly configuration: string;
readonly elasticsearchOutput: string;
readonly elasticsearchModule: string;
readonly startup: string;
readonly exportedFields: string;
};
readonly auditbeat: {
readonly base: string;
};
readonly metricbeat: {
readonly base: string;
readonly configure: string;
readonly httpEndpoint: string;
readonly install: string;
readonly start: string;
};
readonly enterpriseSearch: {
readonly base: string;
readonly appSearchBase: string;
readonly workplaceSearchBase: string;
};
readonly heartbeat: {
readonly base: string;
};
readonly logstash: {
readonly base: string;
};
readonly functionbeat: {
readonly base: string;
};
readonly winlogbeat: {
readonly base: string;
};
readonly aggs: {
readonly composite: string;
readonly composite_missing_bucket: string;
readonly date_histogram: string;
readonly date_range: string;
readonly date_format_pattern: string;
readonly filter: string;
readonly filters: string;
readonly geohash_grid: string;
readonly histogram: string;
readonly ip_range: string;
readonly range: string;
readonly significant_terms: string;
readonly terms: string;
readonly avg: string;
readonly avg_bucket: string;
readonly max_bucket: string;
readonly min_bucket: string;
readonly sum_bucket: string;
readonly cardinality: string;
readonly count: string;
readonly cumulative_sum: string;
readonly derivative: string;
readonly geo_bounds: string;
readonly geo_centroid: string;
readonly max: string;
readonly median: string;
readonly min: string;
readonly moving_avg: string;
readonly percentile_ranks: string;
readonly serial_diff: string;
readonly std_dev: string;
readonly sum: string;
readonly top_hits: string;
};
readonly runtimeFields: {
readonly overview: string;
readonly mapping: string;
};
readonly scriptedFields: {
readonly scriptFields: string;
readonly scriptAggs: string;
readonly painless: string;
readonly painlessApi: string;
readonly painlessLangSpec: string;
readonly painlessSyntax: string;
readonly painlessWalkthrough: string;
readonly luceneExpressions: string;
};
readonly search: {
readonly sessions: string;
readonly sessionLimits: string;
};
readonly indexPatterns: {
readonly introduction: string;
readonly fieldFormattersNumber: string;
readonly fieldFormattersString: string;
readonly runtimeFields: string;
};
readonly addData: string;
readonly kibana: string;
readonly upgradeAssistant: string;
readonly rollupJobs: string;
readonly elasticsearch: Record<string, string>;
readonly siem: {
readonly guide: string;
readonly gettingStarted: string;
};
readonly query: {
readonly eql: string;
readonly kueryQuerySyntax: string;
readonly luceneQuerySyntax: string;
readonly percolate: string;
readonly queryDsl: string;
};
readonly date: {
readonly dateMath: string;
readonly dateMathIndexNames: string;
};
readonly management: Record<string, string>;
readonly ml: Record<string, string>;
readonly transforms: Record<string, string>;
readonly visualize: Record<string, string>;
readonly apis: Readonly<{
bulkIndexAlias: string;
byteSizeUnits: string;
createAutoFollowPattern: string;
createFollower: string;
createIndex: string;
createSnapshotLifecyclePolicy: string;
createRoleMapping: string;
createRoleMappingTemplates: string;
createRollupJobsRequest: string;
createApiKey: string;
createPipeline: string;
createTransformRequest: string;
cronExpressions: string;
executeWatchActionModes: string;
indexExists: string;
openIndex: string;
putComponentTemplate: string;
painlessExecute: string;
painlessExecuteAPIContexts: string;
putComponentTemplateMetadata: string;
putSnapshotLifecyclePolicy: string;
putIndexTemplateV1: string;
putWatch: string;
simulatePipeline: string;
timeUnits: string;
updateTransform: string;
}>;
readonly observability: Record<string, string>;
readonly alerting: Record<string, string>;
readonly maps: Record<string, string>;
readonly monitoring: Record<string, string>;
readonly security: Readonly<{
apiKeyServiceSettings: string;
clusterPrivileges: string;
elasticsearchSettings: string;
elasticsearchEnableSecurity: string;
indicesPrivileges: string;
kibanaTLS: string;
kibanaPrivileges: string;
mappingRoles: string;
mappingRolesFieldRules: string;
runAsPrivilege: string;
}>;
readonly watcher: Record<string, string>;
readonly ccs: Record<string, string>;
readonly plugins: Record<string, string>;
readonly snapshotRestore: Record<string, string>;
readonly ingest: Record<string, string>;
readonly fleet: Readonly<{
guide: string;
fleetServer: string;
fleetServerAddFleetServer: string;
settings: string;
settingsFleetServerHostSettings: string;
troubleshooting: string;
elasticAgent: string;
datastreams: string;
datastreamsNamingScheme: string;
upgradeElasticAgent: string;
upgradeElasticAgent712lower: string;
}>;
readonly ecs: {
readonly guide: string;
};
}
| |
+| [links](./kibana-plugin-core-public.doclinksstart.links.md) | {
readonly settings: string;
readonly canvas: {
readonly guide: string;
};
readonly dashboard: {
readonly guide: string;
readonly drilldowns: string;
readonly drilldownsTriggerPicker: string;
readonly urlDrilldownTemplateSyntax: string;
readonly urlDrilldownVariables: string;
};
readonly discover: Record<string, string>;
readonly filebeat: {
readonly base: string;
readonly installation: string;
readonly configuration: string;
readonly elasticsearchOutput: string;
readonly elasticsearchModule: string;
readonly startup: string;
readonly exportedFields: string;
readonly suricataModule: string;
readonly zeekModule: string;
};
readonly auditbeat: {
readonly base: string;
readonly auditdModule: string;
readonly systemModule: string;
};
readonly metricbeat: {
readonly base: string;
readonly configure: string;
readonly httpEndpoint: string;
readonly install: string;
readonly start: string;
};
readonly enterpriseSearch: {
readonly base: string;
readonly appSearchBase: string;
readonly workplaceSearchBase: string;
};
readonly heartbeat: {
readonly base: string;
};
readonly libbeat: {
readonly getStarted: string;
};
readonly logstash: {
readonly base: string;
};
readonly functionbeat: {
readonly base: string;
};
readonly winlogbeat: {
readonly base: string;
};
readonly aggs: {
readonly composite: string;
readonly composite_missing_bucket: string;
readonly date_histogram: string;
readonly date_range: string;
readonly date_format_pattern: string;
readonly filter: string;
readonly filters: string;
readonly geohash_grid: string;
readonly histogram: string;
readonly ip_range: string;
readonly range: string;
readonly significant_terms: string;
readonly terms: string;
readonly avg: string;
readonly avg_bucket: string;
readonly max_bucket: string;
readonly min_bucket: string;
readonly sum_bucket: string;
readonly cardinality: string;
readonly count: string;
readonly cumulative_sum: string;
readonly derivative: string;
readonly geo_bounds: string;
readonly geo_centroid: string;
readonly max: string;
readonly median: string;
readonly min: string;
readonly moving_avg: string;
readonly percentile_ranks: string;
readonly serial_diff: string;
readonly std_dev: string;
readonly sum: string;
readonly top_hits: string;
};
readonly runtimeFields: {
readonly overview: string;
readonly mapping: string;
};
readonly scriptedFields: {
readonly scriptFields: string;
readonly scriptAggs: string;
readonly painless: string;
readonly painlessApi: string;
readonly painlessLangSpec: string;
readonly painlessSyntax: string;
readonly painlessWalkthrough: string;
readonly luceneExpressions: string;
};
readonly search: {
readonly sessions: string;
readonly sessionLimits: string;
};
readonly indexPatterns: {
readonly introduction: string;
readonly fieldFormattersNumber: string;
readonly fieldFormattersString: string;
readonly runtimeFields: string;
};
readonly addData: string;
readonly kibana: string;
readonly upgradeAssistant: string;
readonly rollupJobs: string;
readonly elasticsearch: Record<string, string>;
readonly siem: {
readonly guide: string;
readonly gettingStarted: string;
readonly ml: string;
readonly ruleChangeLog: string;
readonly detectionsReq: string;
readonly networkMap: string;
};
readonly query: {
readonly eql: string;
readonly kueryQuerySyntax: string;
readonly luceneQuerySyntax: string;
readonly percolate: string;
readonly queryDsl: string;
};
readonly date: {
readonly dateMath: string;
readonly dateMathIndexNames: string;
};
readonly management: Record<string, string>;
readonly ml: Record<string, string>;
readonly transforms: Record<string, string>;
readonly visualize: Record<string, string>;
readonly apis: Readonly<{
bulkIndexAlias: string;
byteSizeUnits: string;
createAutoFollowPattern: string;
createFollower: string;
createIndex: string;
createSnapshotLifecyclePolicy: string;
createRoleMapping: string;
createRoleMappingTemplates: string;
createRollupJobsRequest: string;
createApiKey: string;
createPipeline: string;
createTransformRequest: string;
cronExpressions: string;
executeWatchActionModes: string;
indexExists: string;
openIndex: string;
putComponentTemplate: string;
painlessExecute: string;
painlessExecuteAPIContexts: string;
putComponentTemplateMetadata: string;
putSnapshotLifecyclePolicy: string;
putIndexTemplateV1: string;
putWatch: string;
simulatePipeline: string;
timeUnits: string;
updateTransform: string;
}>;
readonly observability: Record<string, string>;
readonly alerting: Record<string, string>;
readonly maps: Record<string, string>;
readonly monitoring: Record<string, string>;
readonly security: Readonly<{
apiKeyServiceSettings: string;
clusterPrivileges: string;
elasticsearchSettings: string;
elasticsearchEnableSecurity: string;
indicesPrivileges: string;
kibanaTLS: string;
kibanaPrivileges: string;
mappingRoles: string;
mappingRolesFieldRules: string;
runAsPrivilege: string;
}>;
readonly watcher: Record<string, string>;
readonly ccs: Record<string, string>;
readonly plugins: Record<string, string>;
readonly snapshotRestore: Record<string, string>;
readonly ingest: Record<string, string>;
readonly fleet: Readonly<{
guide: string;
fleetServer: string;
fleetServerAddFleetServer: string;
settings: string;
settingsFleetServerHostSettings: string;
troubleshooting: string;
elasticAgent: string;
datastreams: string;
datastreamsNamingScheme: string;
upgradeElasticAgent: string;
upgradeElasticAgent712lower: string;
}>;
readonly ecs: {
readonly guide: string;
};
}
| |
diff --git a/src/core/public/doc_links/doc_links_service.ts b/src/core/public/doc_links/doc_links_service.ts
index fcc12f43ec5311..1efe1e560bce10 100644
--- a/src/core/public/doc_links/doc_links_service.ts
+++ b/src/core/public/doc_links/doc_links_service.ts
@@ -51,9 +51,13 @@ export class DocLinksService {
elasticsearchOutput: `${ELASTIC_WEBSITE_URL}guide/en/beats/filebeat/${DOC_LINK_VERSION}/elasticsearch-output.html`,
startup: `${ELASTIC_WEBSITE_URL}guide/en/beats/filebeat/${DOC_LINK_VERSION}/filebeat-starting.html`,
exportedFields: `${ELASTIC_WEBSITE_URL}guide/en/beats/filebeat/${DOC_LINK_VERSION}/exported-fields.html`,
+ suricataModule: `${ELASTIC_WEBSITE_URL}guide/en/beats/filebeat/${DOC_LINK_VERSION}/filebeat-module-suricata.html`,
+ zeekModule: `${ELASTIC_WEBSITE_URL}guide/en/beats/filebeat/${DOC_LINK_VERSION}/filebeat-module-zeek.html`,
},
auditbeat: {
base: `${ELASTIC_WEBSITE_URL}guide/en/beats/auditbeat/${DOC_LINK_VERSION}`,
+ auditdModule: `${ELASTIC_WEBSITE_URL}guide/en/beats/auditbeat/${DOC_LINK_VERSION}/auditbeat-module-auditd.html`,
+ systemModule: `${ELASTIC_WEBSITE_URL}guide/en/beats/auditbeat/${DOC_LINK_VERSION}/auditbeat-module-system.html`,
},
enterpriseSearch: {
base: `${ELASTIC_WEBSITE_URL}guide/en/enterprise-search/${DOC_LINK_VERSION}`,
@@ -70,6 +74,9 @@ export class DocLinksService {
heartbeat: {
base: `${ELASTIC_WEBSITE_URL}guide/en/beats/heartbeat/${DOC_LINK_VERSION}`,
},
+ libbeat: {
+ getStarted: `${ELASTIC_WEBSITE_URL}guide/en/beats/libbeat/${DOC_LINK_VERSION}/getting-started.html`,
+ },
logstash: {
base: `${ELASTIC_WEBSITE_URL}guide/en/logstash/${DOC_LINK_VERSION}`,
},
@@ -195,6 +202,10 @@ export class DocLinksService {
siem: {
guide: `${ELASTIC_WEBSITE_URL}guide/en/security/${DOC_LINK_VERSION}/index.html`,
gettingStarted: `${ELASTIC_WEBSITE_URL}guide/en/security/${DOC_LINK_VERSION}/index.html`,
+ ml: `${ELASTIC_WEBSITE_URL}guide/en/security/${DOC_LINK_VERSION}/machine-learning.html`,
+ ruleChangeLog: `${ELASTIC_WEBSITE_URL}guide/en/security/${DOC_LINK_VERSION}/prebuilt-rules-changelog.html`,
+ detectionsReq: `${ELASTIC_WEBSITE_URL}guide/en/security/${DOC_LINK_VERSION}/detections-permissions-section.html`,
+ networkMap: `${ELASTIC_WEBSITE_URL}guide/en/security/${DOC_LINK_VERSION}/conf-map-ui.html`,
},
query: {
eql: `${ELASTICSEARCH_DOCS}eql.html`,
@@ -451,9 +462,13 @@ export interface DocLinksStart {
readonly elasticsearchModule: string;
readonly startup: string;
readonly exportedFields: string;
+ readonly suricataModule: string;
+ readonly zeekModule: string;
};
readonly auditbeat: {
readonly base: string;
+ readonly auditdModule: string;
+ readonly systemModule: string;
};
readonly metricbeat: {
readonly base: string;
@@ -470,6 +485,9 @@ export interface DocLinksStart {
readonly heartbeat: {
readonly base: string;
};
+ readonly libbeat: {
+ readonly getStarted: string;
+ };
readonly logstash: {
readonly base: string;
};
@@ -546,6 +564,10 @@ export interface DocLinksStart {
readonly siem: {
readonly guide: string;
readonly gettingStarted: string;
+ readonly ml: string;
+ readonly ruleChangeLog: string;
+ readonly detectionsReq: string;
+ readonly networkMap: string;
};
readonly query: {
readonly eql: string;
diff --git a/src/core/public/public.api.md b/src/core/public/public.api.md
index 3f8184bea97fee..f18dfb02fd41da 100644
--- a/src/core/public/public.api.md
+++ b/src/core/public/public.api.md
@@ -507,9 +507,13 @@ export interface DocLinksStart {
readonly elasticsearchModule: string;
readonly startup: string;
readonly exportedFields: string;
+ readonly suricataModule: string;
+ readonly zeekModule: string;
};
readonly auditbeat: {
readonly base: string;
+ readonly auditdModule: string;
+ readonly systemModule: string;
};
readonly metricbeat: {
readonly base: string;
@@ -526,6 +530,9 @@ export interface DocLinksStart {
readonly heartbeat: {
readonly base: string;
};
+ readonly libbeat: {
+ readonly getStarted: string;
+ };
readonly logstash: {
readonly base: string;
};
@@ -602,6 +609,10 @@ export interface DocLinksStart {
readonly siem: {
readonly guide: string;
readonly gettingStarted: string;
+ readonly ml: string;
+ readonly ruleChangeLog: string;
+ readonly detectionsReq: string;
+ readonly networkMap: string;
};
readonly query: {
readonly eql: string;
From 790bd35ea7716e6de03db35c382abfb83c39543a Mon Sep 17 00:00:00 2001
From: Alexey Antonov
Date: Wed, 30 Jun 2021 10:54:06 +0300
Subject: [PATCH 039/121] [TSVB] Fix TSVB is not reporting all categories of
Elasticsearch error (#102926)
* [TSVB] Fix TSVB is not reporting all categories of Elasticsearch error
Closes: #94182
* move validation to server side
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
---
.../common/fields_utils.ts | 2 +-
.../lib => common}/validate_interval.ts | 41 ++++----
.../components/timeseries_visualization.tsx | 12 ---
.../application/components/vis_editor.tsx | 4 +-
.../public/request_handler.ts | 13 +--
.../server/lib/get_vis_data.ts | 36 ++++++-
.../get_interval_and_timefield.test.ts | 9 +-
.../vis_data/get_interval_and_timefield.ts | 25 ++++-
.../server/lib/vis_data/get_series_data.ts | 17 ++--
.../server/lib/vis_data/get_table_data.ts | 32 ++----
.../lib/vis_data/handle_error_response.js | 37 -------
.../vis_data/handle_error_response.test.ts | 99 +++++++++++++++++++
.../lib/vis_data/handle_error_response.ts | 57 +++++++++++
.../series/date_histogram.test.js | 11 ++-
.../lib/vis_data/series/get_request_params.ts | 16 +--
.../vis_type_timeseries/server/types.ts | 16 ++-
16 files changed, 285 insertions(+), 142 deletions(-)
rename src/plugins/vis_type_timeseries/{public/application/lib => common}/validate_interval.ts (52%)
delete mode 100644 src/plugins/vis_type_timeseries/server/lib/vis_data/handle_error_response.js
create mode 100644 src/plugins/vis_type_timeseries/server/lib/vis_data/handle_error_response.test.ts
create mode 100644 src/plugins/vis_type_timeseries/server/lib/vis_data/handle_error_response.ts
diff --git a/src/plugins/vis_type_timeseries/common/fields_utils.ts b/src/plugins/vis_type_timeseries/common/fields_utils.ts
index 6a83dd323b3fdb..b64fcc383a1bb9 100644
--- a/src/plugins/vis_type_timeseries/common/fields_utils.ts
+++ b/src/plugins/vis_type_timeseries/common/fields_utils.ts
@@ -25,7 +25,7 @@ export class FieldNotFoundError extends Error {
return this.constructor.name;
}
- public get body() {
+ public get errBody() {
return this.message;
}
}
diff --git a/src/plugins/vis_type_timeseries/public/application/lib/validate_interval.ts b/src/plugins/vis_type_timeseries/common/validate_interval.ts
similarity index 52%
rename from src/plugins/vis_type_timeseries/public/application/lib/validate_interval.ts
rename to src/plugins/vis_type_timeseries/common/validate_interval.ts
index a602b34d999867..7f9ccf20c0eb1d 100644
--- a/src/plugins/vis_type_timeseries/public/application/lib/validate_interval.ts
+++ b/src/plugins/vis_type_timeseries/common/validate_interval.ts
@@ -7,20 +7,29 @@
*/
import { i18n } from '@kbn/i18n';
-import { GTE_INTERVAL_RE } from '../../../common/interval_regexp';
-import { search } from '../../../../../plugins/data/public';
+import { GTE_INTERVAL_RE } from './interval_regexp';
+import { parseInterval, TimeRangeBounds } from '../../data/common';
-import type { TimeRangeBounds } from '../../../../data/common';
-import type { TimeseriesVisParams } from '../../types';
+export class ValidateIntervalError extends Error {
+ constructor() {
+ super(
+ i18n.translate('visTypeTimeseries.validateInterval.notifier.maxBucketsExceededErrorMessage', {
+ defaultMessage:
+ 'Your query attempted to fetch too much data. Reducing the time range or changing the interval used usually fixes the issue.',
+ })
+ );
+ }
+
+ public get name() {
+ return this.constructor.name;
+ }
-const { parseInterval } = search.aggs;
+ public get errBody() {
+ return this.message;
+ }
+}
-export function validateInterval(
- bounds: TimeRangeBounds,
- panel: TimeseriesVisParams,
- maxBuckets: number
-) {
- const { interval } = panel;
+export function validateInterval(bounds: TimeRangeBounds, interval: string, maxBuckets: number) {
const { min, max } = bounds;
// No need to check auto it will return around 100
if (!interval) return;
@@ -33,15 +42,7 @@ export function validateInterval(
const span = max!.valueOf() - min!.valueOf();
const buckets = Math.floor(span / duration.asMilliseconds());
if (buckets > maxBuckets) {
- throw new Error(
- i18n.translate(
- 'visTypeTimeseries.validateInterval.notifier.maxBucketsExceededErrorMessage',
- {
- defaultMessage:
- 'Your query attempted to fetch too much data. Reducing the time range or changing the interval used usually fixes the issue.',
- }
- )
- );
+ throw new ValidateIntervalError();
}
}
}
diff --git a/src/plugins/vis_type_timeseries/public/application/components/timeseries_visualization.tsx b/src/plugins/vis_type_timeseries/public/application/components/timeseries_visualization.tsx
index 5391bf319ee573..d97100a0cfaaf1 100644
--- a/src/plugins/vis_type_timeseries/public/application/components/timeseries_visualization.tsx
+++ b/src/plugins/vis_type_timeseries/public/application/components/timeseries_visualization.tsx
@@ -18,8 +18,6 @@ import { IInterpreterRenderHandlers } from 'src/plugins/expressions';
import { PersistedState } from 'src/plugins/visualizations/public';
import { PaletteRegistry } from 'src/plugins/charts/public';
-// @ts-expect-error
-import { ErrorComponent } from './error';
import { TimeseriesVisTypes } from './vis_types';
import type { TimeseriesVisData, PanelData } from '../../../common/types';
import { isVisSeriesData } from '../../../common/vis_data_utils';
@@ -147,16 +145,6 @@ function TimeseriesVisualization({
handlers.done();
});
- // Show the error panel
- const error = isVisSeriesData(visData) && visData[model.id]?.error;
- if (error) {
- return (
-
-
-
- );
- }
-
const VisComponent = TimeseriesVisTypes[model.type];
const isLastValueMode =
diff --git a/src/plugins/vis_type_timeseries/public/application/components/vis_editor.tsx b/src/plugins/vis_type_timeseries/public/application/components/vis_editor.tsx
index 99f25643284025..d11b5a60b31b7d 100644
--- a/src/plugins/vis_type_timeseries/public/application/components/vis_editor.tsx
+++ b/src/plugins/vis_type_timeseries/public/application/components/vis_editor.tsx
@@ -159,8 +159,8 @@ export class VisEditor extends Component {
- this.visDataSubject.next(visData);
+ onDataChange = (data: { visData?: TimeseriesVisData }) => {
+ this.visDataSubject.next(data?.visData);
};
render() {
diff --git a/src/plugins/vis_type_timeseries/public/request_handler.ts b/src/plugins/vis_type_timeseries/public/request_handler.ts
index 89b3cb3b6c5831..4cd297a597dfce 100644
--- a/src/plugins/vis_type_timeseries/public/request_handler.ts
+++ b/src/plugins/vis_type_timeseries/public/request_handler.ts
@@ -6,14 +6,13 @@
* Side Public License, v 1.
*/
-import { KibanaContext } from '../../data/public';
-
import { getTimezone } from './application/lib/get_timezone';
-import { validateInterval } from './application/lib/validate_interval';
import { getUISettings, getDataStart, getCoreStart } from './services';
-import { MAX_BUCKETS_SETTING, ROUTES } from '../common/constants';
-import { TimeseriesVisParams } from './types';
+import { ROUTES } from '../common/constants';
+
+import type { TimeseriesVisParams } from './types';
import type { TimeseriesVisData } from '../common/types';
+import type { KibanaContext } from '../../data/public';
interface MetricsRequestHandlerParams {
input: KibanaContext | null;
@@ -37,10 +36,6 @@ export const metricsRequestHandler = async ({
const parsedTimeRange = data.query.timefilter.timefilter.calculateBounds(input?.timeRange!);
if (visParams && visParams.id && !visParams.isModelInvalid) {
- const maxBuckets = config.get(MAX_BUCKETS_SETTING);
-
- validateInterval(parsedTimeRange, visParams, maxBuckets);
-
const untrackSearch =
dataSearch.session.isCurrentSession(searchSessionId) &&
dataSearch.session.trackSearch({
diff --git a/src/plugins/vis_type_timeseries/server/lib/get_vis_data.ts b/src/plugins/vis_type_timeseries/server/lib/get_vis_data.ts
index dd45812f4ebfce..817812a88ca987 100644
--- a/src/plugins/vis_type_timeseries/server/lib/get_vis_data.ts
+++ b/src/plugins/vis_type_timeseries/server/lib/get_vis_data.ts
@@ -9,7 +9,7 @@
import _ from 'lodash';
import { Framework } from '../plugin';
-import type { TimeseriesVisData } from '../../common/types';
+import type { TimeseriesVisData, FetchedIndexPattern, Series } from '../../common/types';
import { PANEL_TYPES } from '../../common/enums';
import type {
VisTypeTimeseriesVisDataRequest,
@@ -20,6 +20,8 @@ import { getSeriesData } from './vis_data/get_series_data';
import { getTableData } from './vis_data/get_table_data';
import { getEsQueryConfig } from './vis_data/helpers/get_es_query_uisettings';
import { getCachedIndexPatternFetcher } from './search_strategies/lib/cached_index_pattern_fetcher';
+import { MAX_BUCKETS_SETTING } from '../../common/constants';
+import { getIntervalAndTimefield } from './vis_data/get_interval_and_timefield';
export async function getVisData(
requestContext: VisTypeTimeseriesRequestHandlerContext,
@@ -32,15 +34,41 @@ export async function getVisData(
const esQueryConfig = await getEsQueryConfig(uiSettings);
const promises = request.body.panels.map((panel) => {
+ const cachedIndexPatternFetcher = getCachedIndexPatternFetcher(indexPatternsService, {
+ fetchKibanaIndexForStringIndexes: Boolean(panel.use_kibana_indexes),
+ });
const services: VisTypeTimeseriesRequestServices = {
esQueryConfig,
esShardTimeout,
indexPatternsService,
uiSettings,
+ cachedIndexPatternFetcher,
searchStrategyRegistry: framework.searchStrategyRegistry,
- cachedIndexPatternFetcher: getCachedIndexPatternFetcher(indexPatternsService, {
- fetchKibanaIndexForStringIndexes: Boolean(panel.use_kibana_indexes),
- }),
+ buildSeriesMetaParams: async (
+ index: FetchedIndexPattern,
+ useKibanaIndexes: boolean,
+ series?: Series
+ ) => {
+ /** This part of code is required to try to get the default timefield for string indices.
+ * The rest of the functionality available for Kibana indexes should not be active **/
+ if (!useKibanaIndexes && index.indexPatternString) {
+ index = await cachedIndexPatternFetcher(index.indexPatternString, true);
+ }
+
+ const maxBuckets = await uiSettings.get(MAX_BUCKETS_SETTING);
+ const { min, max } = request.body.timerange;
+
+ return getIntervalAndTimefield(
+ panel,
+ index,
+ {
+ min,
+ max,
+ maxBuckets,
+ },
+ series
+ );
+ },
};
return panel.type === PANEL_TYPES.TABLE
diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/get_interval_and_timefield.test.ts b/src/plugins/vis_type_timeseries/server/lib/vis_data/get_interval_and_timefield.test.ts
index 0d1ca9cba022a7..62220e08b7e104 100644
--- a/src/plugins/vis_type_timeseries/server/lib/vis_data/get_interval_and_timefield.test.ts
+++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/get_interval_and_timefield.test.ts
@@ -11,12 +11,17 @@ import { FetchedIndexPattern, Panel, Series } from '../../../common/types';
describe('getIntervalAndTimefield(panel, series)', () => {
const index: FetchedIndexPattern = {} as FetchedIndexPattern;
+ const params = {
+ min: '2017-01-01T00:00:00Z',
+ max: '2017-01-01T01:00:00Z',
+ maxBuckets: 1000,
+ };
test('returns the panel interval and timefield', () => {
const panel = { time_field: '@timestamp', interval: 'auto' } as Panel;
const series = {} as Series;
- expect(getIntervalAndTimefield(panel, index, series)).toEqual({
+ expect(getIntervalAndTimefield(panel, index, params, series)).toEqual({
timeField: '@timestamp',
interval: 'auto',
});
@@ -30,7 +35,7 @@ describe('getIntervalAndTimefield(panel, series)', () => {
series_time_field: 'time',
} as unknown) as Series;
- expect(getIntervalAndTimefield(panel, index, series)).toEqual({
+ expect(getIntervalAndTimefield(panel, index, params, series)).toEqual({
timeField: 'time',
interval: '1m',
});
diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/get_interval_and_timefield.ts b/src/plugins/vis_type_timeseries/server/lib/vis_data/get_interval_and_timefield.ts
index 0e90dfe77e8145..b7a22abd825e05 100644
--- a/src/plugins/vis_type_timeseries/server/lib/vis_data/get_interval_and_timefield.ts
+++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/get_interval_and_timefield.ts
@@ -5,13 +5,25 @@
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
-
+import moment from 'moment';
import { AUTO_INTERVAL } from '../../../common/constants';
import { validateField } from '../../../common/fields_utils';
+import { validateInterval } from '../../../common/validate_interval';
import type { FetchedIndexPattern, Panel, Series } from '../../../common/types';
-export function getIntervalAndTimefield(panel: Panel, index: FetchedIndexPattern, series?: Series) {
+interface IntervalParams {
+ min: string;
+ max: string;
+ maxBuckets: number;
+}
+
+export function getIntervalAndTimefield(
+ panel: Panel,
+ index: FetchedIndexPattern,
+ { min, max, maxBuckets }: IntervalParams,
+ series?: Series
+) {
const timeField =
(series?.override_index_pattern ? series.series_time_field : panel.time_field) ||
index.indexPattern?.timeFieldName;
@@ -28,6 +40,15 @@ export function getIntervalAndTimefield(panel: Panel, index: FetchedIndexPattern
maxBars = series.series_max_bars;
}
+ validateInterval(
+ {
+ min: moment.utc(min),
+ max: moment.utc(max),
+ },
+ interval,
+ maxBuckets
+ );
+
return {
maxBars,
timeField,
diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/get_series_data.ts b/src/plugins/vis_type_timeseries/server/lib/vis_data/get_series_data.ts
index 822331e0ca0d07..8d495d68eb6257 100644
--- a/src/plugins/vis_type_timeseries/server/lib/vis_data/get_series_data.ts
+++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/get_series_data.ts
@@ -8,8 +8,6 @@
import { i18n } from '@kbn/i18n';
-// not typed yet
-// @ts-expect-error
import { handleErrorResponse } from './handle_error_response';
import { getAnnotations } from './get_annotations';
import { handleResponseBody } from './series/handle_response_body';
@@ -51,6 +49,8 @@ export async function getSeriesData(
uiRestrictions: capabilities.uiRestrictions,
};
+ const handleError = handleErrorResponse(panel);
+
try {
const bodiesPromises = getActiveSeries(panel).map((series) =>
getSeriesRequestParams(req, panel, panelIndex, series, capabilities, services)
@@ -97,14 +97,9 @@ export async function getSeriesData(
},
};
} catch (err) {
- if (err.body) {
- err.response = err.body;
-
- return {
- ...meta,
- ...handleErrorResponse(panel)(err),
- };
- }
- return meta;
+ return {
+ ...meta,
+ ...handleError(err),
+ };
}
}
diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/get_table_data.ts b/src/plugins/vis_type_timeseries/server/lib/vis_data/get_table_data.ts
index db2e027f7815c3..3f8d30f0ed8339 100644
--- a/src/plugins/vis_type_timeseries/server/lib/vis_data/get_table_data.ts
+++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/get_table_data.ts
@@ -12,20 +12,19 @@ import { get } from 'lodash';
// not typed yet
// @ts-expect-error
import { buildRequestBody } from './table/build_request_body';
-// @ts-expect-error
import { handleErrorResponse } from './handle_error_response';
// @ts-expect-error
import { processBucket } from './table/process_bucket';
import { createFieldsFetcher } from '../search_strategies/lib/fields_fetcher';
import { extractFieldLabel } from '../../../common/fields_utils';
+
import type {
VisTypeTimeseriesRequestHandlerContext,
VisTypeTimeseriesRequestServices,
VisTypeTimeseriesVisDataRequest,
} from '../../types';
import type { Panel } from '../../../common/types';
-import { getIntervalAndTimefield } from './get_interval_and_timefield';
export async function getTableData(
requestContext: VisTypeTimeseriesRequestHandlerContext,
@@ -67,23 +66,13 @@ export async function getTableData(
return panel.pivot_id;
};
- const buildSeriesMetaParams = async () => {
- let index = panelIndex;
-
- /** This part of code is required to try to get the default timefield for string indices.
- * The rest of the functionality available for Kibana indexes should not be active **/
- if (!panel.use_kibana_indexes && index.indexPatternString) {
- index = await services.cachedIndexPatternFetcher(index.indexPatternString, true);
- }
-
- return getIntervalAndTimefield(panel, index);
- };
-
const meta = {
type: panel.type,
uiRestrictions: capabilities.uiRestrictions,
};
+ const handleError = handleErrorResponse(panel);
+
try {
const body = await buildRequestBody(
req,
@@ -92,7 +81,7 @@ export async function getTableData(
panelIndex,
capabilities,
services.uiSettings,
- buildSeriesMetaParams
+ () => services.buildSeriesMetaParams(panelIndex, Boolean(panel.use_kibana_indexes))
);
const [resp] = await searchStrategy.search(requestContext, req, [
@@ -121,14 +110,9 @@ export async function getTableData(
series,
};
} catch (err) {
- if (err.body) {
- err.response = err.body;
-
- return {
- ...meta,
- ...handleErrorResponse(panel)(err),
- };
- }
- return meta;
+ return {
+ ...meta,
+ ...handleError(err),
+ };
}
}
diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/handle_error_response.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/handle_error_response.js
deleted file mode 100644
index b5583ce20d68ae..00000000000000
--- a/src/plugins/vis_type_timeseries/server/lib/vis_data/handle_error_response.js
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0 and the Server Side Public License, v 1; you may not use this file except
- * in compliance with, at your election, the Elastic License 2.0 or the Server
- * Side Public License, v 1.
- */
-
-export const handleErrorResponse = (panel) => (error) => {
- if (error.isBoom && error.status === 401) throw error;
- const result = {};
- let errorResponse;
- try {
- errorResponse = JSON.parse(error.response);
- } catch (e) {
- errorResponse = error.response;
- }
- if (!errorResponse && !(error.name === 'KQLSyntaxError')) {
- errorResponse = {
- message: error.message,
- stack: error.stack,
- };
- }
- if (error.name === 'KQLSyntaxError') {
- errorResponse = {
- message: error.shortMessage,
- stack: error.stack,
- };
- }
- result[panel.id] = {
- id: panel.id,
- statusCode: error.statusCode,
- error: errorResponse,
- series: [],
- };
- return result;
-};
diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/handle_error_response.test.ts b/src/plugins/vis_type_timeseries/server/lib/vis_data/handle_error_response.test.ts
new file mode 100644
index 00000000000000..eeb22a3dc32cff
--- /dev/null
+++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/handle_error_response.test.ts
@@ -0,0 +1,99 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+import { Panel } from '../../../common/types';
+import { handleErrorResponse, ErrorResponse } from './handle_error_response';
+
+describe('handleErrorResponse', () => {
+ const handleError = handleErrorResponse(({
+ id: 'test_panel',
+ } as unknown) as Panel);
+
+ test('should only handle errors that contain errBody', () => {
+ expect(handleError(new Error('Test Error'))).toMatchInlineSnapshot(`Object {}`);
+
+ expect(handleError({ errBody: 'test' } as ErrorResponse)).toMatchInlineSnapshot(`
+ Object {
+ "test_panel": Object {
+ "error": "test",
+ "id": "test_panel",
+ "series": Array [],
+ },
+ }
+ `);
+ });
+
+ test('should set as error the last value of caused_by', () => {
+ expect(
+ handleError({
+ errBody: {
+ error: {
+ reason: 'wrong 0',
+ caused_by: {
+ reason: 'wrong 1',
+ caused_by: {
+ caused_by: 'ok',
+ },
+ },
+ },
+ },
+ } as ErrorResponse)
+ ).toMatchInlineSnapshot(`
+ Object {
+ "test_panel": Object {
+ "error": "ok",
+ "id": "test_panel",
+ "series": Array [],
+ },
+ }
+ `);
+ });
+
+ test('should use the previous error message if the actual value is empty', () => {
+ expect(
+ handleError({
+ errBody: {
+ error: {
+ reason: 'ok',
+ caused_by: {
+ reason: '',
+ },
+ },
+ },
+ } as ErrorResponse)
+ ).toMatchInlineSnapshot(`
+ Object {
+ "test_panel": Object {
+ "error": "ok",
+ "id": "test_panel",
+ "series": Array [],
+ },
+ }
+ `);
+ });
+
+ test('shouldn not return empty error message', () => {
+ expect(
+ handleError({
+ errBody: {
+ error: {
+ reason: '',
+ },
+ },
+ } as ErrorResponse)
+ ).toMatchInlineSnapshot(`
+ Object {
+ "test_panel": Object {
+ "error": "Unexpected error",
+ "id": "test_panel",
+ "series": Array [],
+ },
+ }
+ `);
+ });
+});
diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/handle_error_response.ts b/src/plugins/vis_type_timeseries/server/lib/vis_data/handle_error_response.ts
new file mode 100644
index 00000000000000..d9327a0fbb786e
--- /dev/null
+++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/handle_error_response.ts
@@ -0,0 +1,57 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+import { i18n } from '@kbn/i18n';
+import type { Panel } from '../../../common/types';
+
+type ErrorType =
+ | {
+ reason: string;
+ caused_by?: ErrorType;
+ }
+ | string;
+
+export type ErrorResponse = Error &
+ Partial<{
+ errBody:
+ | {
+ error: ErrorType;
+ }
+ | string;
+ }>;
+
+const getErrorMessage = (errBody: ErrorType, defaultMessage?: string): string | undefined => {
+ if (typeof errBody === 'string') {
+ return errBody;
+ } else {
+ if (errBody.caused_by) {
+ return getErrorMessage(errBody.caused_by, errBody.reason);
+ }
+ return errBody.reason || defaultMessage;
+ }
+};
+
+export const handleErrorResponse = (panel: Panel) => (error: ErrorResponse) => {
+ const result: Record = {};
+
+ if (error.errBody) {
+ const errorResponse =
+ typeof error.errBody === 'string' ? error.errBody : getErrorMessage(error.errBody.error);
+
+ result[panel.id] = {
+ id: panel.id,
+ error:
+ errorResponse ??
+ i18n.translate('visTypeTimeseries.handleErrorResponse.unexpectedError', {
+ defaultMessage: 'Unexpected error',
+ }),
+ series: [],
+ };
+ }
+
+ return result;
+};
diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/date_histogram.test.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/date_histogram.test.js
index 08b9801254c2e2..022718ece435d9 100644
--- a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/date_histogram.test.js
+++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/date_histogram.test.js
@@ -47,7 +47,16 @@ describe('dateHistogram(req, panel, series)', () => {
get: async (key) => (key === UI_SETTINGS.HISTOGRAM_MAX_BARS ? 100 : 50),
};
buildSeriesMetaParams = jest.fn(async () => {
- return getIntervalAndTimefield(panel, indexPattern, series);
+ return getIntervalAndTimefield(
+ panel,
+ indexPattern,
+ {
+ min: '2017-01-01T00:00:00Z',
+ max: '2017-01-01T01:00:00Z',
+ maxBuckets: 1000,
+ },
+ series
+ );
});
});
diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/series/get_request_params.ts b/src/plugins/vis_type_timeseries/server/lib/vis_data/series/get_request_params.ts
index a2248308dc5711..aedc4ee7c8a155 100644
--- a/src/plugins/vis_type_timeseries/server/lib/vis_data/series/get_request_params.ts
+++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/series/get_request_params.ts
@@ -7,7 +7,6 @@
*/
import { buildRequestBody } from './build_request_body';
-import { getIntervalAndTimefield } from '../get_interval_and_timefield';
import type { FetchedIndexPattern, Panel, Series } from '../../../../common/types';
import type {
@@ -27,6 +26,7 @@ export async function getSeriesRequestParams(
esShardTimeout,
uiSettings,
cachedIndexPatternFetcher,
+ buildSeriesMetaParams,
}: VisTypeTimeseriesRequestServices
) {
let seriesIndex = panelIndex;
@@ -35,18 +35,6 @@ export async function getSeriesRequestParams(
seriesIndex = await cachedIndexPatternFetcher(series.series_index_pattern ?? '');
}
- const buildSeriesMetaParams = async () => {
- let index = seriesIndex;
-
- /** This part of code is required to try to get the default timefield for string indices.
- * The rest of the functionality available for Kibana indexes should not be active **/
- if (!panel.use_kibana_indexes && index.indexPatternString) {
- index = await cachedIndexPatternFetcher(index.indexPatternString, true);
- }
-
- return getIntervalAndTimefield(panel, index, series);
- };
-
const request = await buildRequestBody(
req,
panel,
@@ -55,7 +43,7 @@ export async function getSeriesRequestParams(
seriesIndex,
capabilities,
uiSettings,
- buildSeriesMetaParams
+ () => buildSeriesMetaParams(seriesIndex, Boolean(panel.use_kibana_indexes), series)
);
return {
diff --git a/src/plugins/vis_type_timeseries/server/types.ts b/src/plugins/vis_type_timeseries/server/types.ts
index 2fc46b7cd1f11c..a2657f99d222dc 100644
--- a/src/plugins/vis_type_timeseries/server/types.ts
+++ b/src/plugins/vis_type_timeseries/server/types.ts
@@ -6,17 +6,18 @@
* Side Public License, v 1.
*/
-import { Observable } from 'rxjs';
-import { SharedGlobalConfig } from 'kibana/server';
+import type { Observable } from 'rxjs';
+import type { SharedGlobalConfig } from 'kibana/server';
import type { IRouter, IUiSettingsClient, KibanaRequest } from 'src/core/server';
import type {
DataRequestHandlerContext,
EsQueryConfig,
IndexPatternsService,
} from '../../data/server';
-import type { VisPayload } from '../common/types';
+import type { Series, VisPayload } from '../common/types';
import type { SearchStrategyRegistry } from './lib/search_strategies';
import type { CachedIndexPatternFetcher } from './lib/search_strategies/lib/cached_index_pattern_fetcher';
+import type { FetchedIndexPattern } from '../common/types';
export type ConfigObservable = Observable;
@@ -35,4 +36,13 @@ export interface VisTypeTimeseriesRequestServices {
indexPatternsService: IndexPatternsService;
searchStrategyRegistry: SearchStrategyRegistry;
cachedIndexPatternFetcher: CachedIndexPatternFetcher;
+ buildSeriesMetaParams: (
+ index: FetchedIndexPattern,
+ useKibanaIndexes: boolean,
+ series?: Series
+ ) => Promise<{
+ maxBars: number;
+ timeField?: string;
+ interval: string;
+ }>;
}
From 21858a570da998ca10dec976788210a3c8d9680c Mon Sep 17 00:00:00 2001
From: Marco Liberati
Date: Wed, 30 Jun 2021 10:06:38 +0200
Subject: [PATCH 040/121] [Lens] Fix value popover spacing (#103081)
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
---
.../line_curve_option.tsx | 47 +++++++++----------
.../missing_values_option.tsx | 10 +---
2 files changed, 23 insertions(+), 34 deletions(-)
diff --git a/x-pack/plugins/lens/public/xy_visualization/visual_options_popover/line_curve_option.tsx b/x-pack/plugins/lens/public/xy_visualization/visual_options_popover/line_curve_option.tsx
index ea0a1553ba5e51..1df7744524779b 100644
--- a/x-pack/plugins/lens/public/xy_visualization/visual_options_popover/line_curve_option.tsx
+++ b/x-pack/plugins/lens/public/xy_visualization/visual_options_popover/line_curve_option.tsx
@@ -7,7 +7,7 @@
import React from 'react';
import { i18n } from '@kbn/i18n';
-import { EuiFormRow, EuiSpacer, EuiSwitch } from '@elastic/eui';
+import { EuiFormRow, EuiSwitch } from '@elastic/eui';
import { XYCurveType } from '../types';
export interface LineCurveOptionProps {
@@ -28,29 +28,26 @@ export const LineCurveOption: React.FC = ({
isCurveTypeEnabled = true,
}) => {
return isCurveTypeEnabled ? (
- <>
-
- {
- if (e.target.checked) {
- onChange('CURVE_MONOTONE_X');
- } else {
- onChange('LINEAR');
- }
- }}
- data-test-subj="lnsCurveStyleToggle"
- />
-
-
- >
+
+ {
+ if (e.target.checked) {
+ onChange('CURVE_MONOTONE_X');
+ } else {
+ onChange('LINEAR');
+ }
+ }}
+ data-test-subj="lnsCurveStyleToggle"
+ />
+
) : null;
};
diff --git a/x-pack/plugins/lens/public/xy_visualization/visual_options_popover/missing_values_option.tsx b/x-pack/plugins/lens/public/xy_visualization/visual_options_popover/missing_values_option.tsx
index a683d4fbf514c3..fb6ecec4d28013 100644
--- a/x-pack/plugins/lens/public/xy_visualization/visual_options_popover/missing_values_option.tsx
+++ b/x-pack/plugins/lens/public/xy_visualization/visual_options_popover/missing_values_option.tsx
@@ -7,14 +7,7 @@
import React from 'react';
import { i18n } from '@kbn/i18n';
-import {
- EuiButtonGroup,
- EuiFormRow,
- EuiIconTip,
- EuiSuperSelect,
- EuiText,
- EuiSpacer,
-} from '@elastic/eui';
+import { EuiButtonGroup, EuiFormRow, EuiIconTip, EuiSuperSelect, EuiText } from '@elastic/eui';
import { FittingFunction, fittingFunctionDefinitions } from '../fitting_functions';
import { ValueLabelConfig } from '../types';
@@ -140,7 +133,6 @@ export const MissingValuesOptions: React.FC = ({
/>
)}
-
>
);
};
From 4aca0b7b61b8b5db51f1fc9d0e656f8f02d5ccfd Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?S=C3=B8ren=20Louv-Jansen?=
Date: Wed, 30 Jun 2021 10:20:31 +0200
Subject: [PATCH 041/121] =?UTF-8?q?[APM]=20Add=20=E2=80=9CAnalyze=20Data?=
=?UTF-8?q?=E2=80=9D=20button=20(#103485)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Co-authored-by: Shahzad
---
.../__snapshots__/apm_telemetry.test.ts.snap | 60 +++++++++++++
x-pack/plugins/apm/common/agent_name.test.ts | 34 ++++++-
x-pack/plugins/apm/common/agent_name.ts | 15 +++-
.../components/app/correlations/index.tsx | 1 +
.../components/app/service_overview/index.tsx | 5 +-
.../templates/apm_service_template.tsx | 78 +++++++++++++++-
.../shared/agent_icon/get_agent_icon.ts | 5 ++
.../apm/server/lib/apm_telemetry/schema.ts | 3 +
.../apm/typings/es_schemas/ui/fields/agent.ts | 2 +
.../schema/xpack_plugins.json | 90 +++++++++++++++++++
10 files changed, 282 insertions(+), 11 deletions(-)
diff --git a/x-pack/plugins/apm/common/__snapshots__/apm_telemetry.test.ts.snap b/x-pack/plugins/apm/common/__snapshots__/apm_telemetry.test.ts.snap
index 71b0929164705b..a2baee60749897 100644
--- a/x-pack/plugins/apm/common/__snapshots__/apm_telemetry.test.ts.snap
+++ b/x-pack/plugins/apm/common/__snapshots__/apm_telemetry.test.ts.snap
@@ -16,6 +16,9 @@ exports[`APM telemetry helpers getApmTelemetry generates a JSON object with the
"dotnet": {
"type": "long"
},
+ "iOS/swift": {
+ "type": "long"
+ },
"go": {
"type": "long"
},
@@ -70,6 +73,9 @@ exports[`APM telemetry helpers getApmTelemetry generates a JSON object with the
"opentelemetry/ruby": {
"type": "long"
},
+ "opentelemetry/swift": {
+ "type": "long"
+ },
"opentelemetry/webjs": {
"type": "long"
}
@@ -131,6 +137,60 @@ exports[`APM telemetry helpers getApmTelemetry generates a JSON object with the
}
}
},
+ "iOS/swift": {
+ "properties": {
+ "agent": {
+ "properties": {
+ "version": {
+ "type": "keyword"
+ }
+ }
+ },
+ "service": {
+ "properties": {
+ "framework": {
+ "properties": {
+ "name": {
+ "type": "keyword"
+ },
+ "version": {
+ "type": "keyword"
+ },
+ "composite": {
+ "type": "keyword"
+ }
+ }
+ },
+ "language": {
+ "properties": {
+ "name": {
+ "type": "keyword"
+ },
+ "version": {
+ "type": "keyword"
+ },
+ "composite": {
+ "type": "keyword"
+ }
+ }
+ },
+ "runtime": {
+ "properties": {
+ "name": {
+ "type": "keyword"
+ },
+ "version": {
+ "type": "keyword"
+ },
+ "composite": {
+ "type": "keyword"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
"go": {
"properties": {
"agent": {
diff --git a/x-pack/plugins/apm/common/agent_name.test.ts b/x-pack/plugins/apm/common/agent_name.test.ts
index 9f74136efe829c..162a5716d6c7b1 100644
--- a/x-pack/plugins/apm/common/agent_name.test.ts
+++ b/x-pack/plugins/apm/common/agent_name.test.ts
@@ -5,7 +5,7 @@
* 2.0.
*/
-import { isJavaAgentName, isRumAgentName } from './agent_name';
+import { isJavaAgentName, isRumAgentName, isIosAgentName } from './agent_name';
describe('agent name helpers', () => {
describe('isJavaAgentName', () => {
@@ -22,7 +22,7 @@ describe('agent name helpers', () => {
});
describe('when the agent name is not java', () => {
- it('returns true', () => {
+ it('returns false', () => {
expect(isJavaAgentName('not java')).toEqual(false);
});
});
@@ -47,9 +47,35 @@ describe('agent name helpers', () => {
});
});
- describe('when the agent name something else', () => {
+ describe('when the agent name is something else', () => {
+ it('returns false', () => {
+ expect(isRumAgentName('not rum')).toEqual(false);
+ });
+ });
+ });
+
+ describe('isIosAgentName', () => {
+ describe('when the agent name is js-base', () => {
+ it('returns true', () => {
+ expect(isIosAgentName('iOS/swift')).toEqual(true);
+ });
+ });
+
+ describe('when the agent name is rum-js', () => {
it('returns true', () => {
- expect(isRumAgentName('java')).toEqual(false);
+ expect(isIosAgentName('ios/swift')).toEqual(true);
+ });
+ });
+
+ describe('when the agent name is opentelemetry/swift', () => {
+ it('returns true', () => {
+ expect(isIosAgentName('opentelemetry/swift')).toEqual(true);
+ });
+ });
+
+ describe('when the agent name is something else', () => {
+ it('returns false', () => {
+ expect(isIosAgentName('not ios')).toEqual(false);
});
});
});
diff --git a/x-pack/plugins/apm/common/agent_name.ts b/x-pack/plugins/apm/common/agent_name.ts
index 36bfbabf7797d0..650e72751749e5 100644
--- a/x-pack/plugins/apm/common/agent_name.ts
+++ b/x-pack/plugins/apm/common/agent_name.ts
@@ -26,12 +26,14 @@ export const OPEN_TELEMETRY_AGENT_NAMES: AgentName[] = [
'opentelemetry/php',
'opentelemetry/python',
'opentelemetry/ruby',
+ 'opentelemetry/swift',
'opentelemetry/webjs',
];
export const AGENT_NAMES: AgentName[] = [
'dotnet',
'go',
+ 'iOS/swift',
'java',
'js-base',
'nodejs',
@@ -62,7 +64,9 @@ export function isRumAgentName(
return RUM_AGENT_NAMES.includes(agentName! as AgentName);
}
-export function normalizeAgentName(agentName: string | undefined) {
+export function normalizeAgentName(
+ agentName: T
+): T | string {
if (isRumAgentName(agentName)) {
return 'rum-js';
}
@@ -71,5 +75,14 @@ export function normalizeAgentName(agentName: string | undefined) {
return 'java';
}
+ if (isIosAgentName(agentName)) {
+ return 'ios';
+ }
+
return agentName;
}
+
+export function isIosAgentName(agentName?: string) {
+ const lowercased = agentName && agentName.toLowerCase();
+ return lowercased === 'ios/swift' || lowercased === 'opentelemetry/swift';
+}
diff --git a/x-pack/plugins/apm/public/components/app/correlations/index.tsx b/x-pack/plugins/apm/public/components/app/correlations/index.tsx
index 7b6328916d445e..36b298af834ac1 100644
--- a/x-pack/plugins/apm/public/components/app/correlations/index.tsx
+++ b/x-pack/plugins/apm/public/components/app/correlations/index.tsx
@@ -131,6 +131,7 @@ export function Correlations() {
return (
<>
{
setIsFlyoutVisible(true);
}}
diff --git a/x-pack/plugins/apm/public/components/app/service_overview/index.tsx b/x-pack/plugins/apm/public/components/app/service_overview/index.tsx
index 5b202e208a52d9..fce543b05c6c3b 100644
--- a/x-pack/plugins/apm/public/components/app/service_overview/index.tsx
+++ b/x-pack/plugins/apm/public/components/app/service_overview/index.tsx
@@ -8,7 +8,7 @@
import { EuiFlexGroup, EuiFlexItem, EuiPanel } from '@elastic/eui';
import React from 'react';
import { useTrackPageview } from '../../../../../observability/public';
-import { isRumAgentName } from '../../../../common/agent_name';
+import { isRumAgentName, isIosAgentName } from '../../../../common/agent_name';
import { AnnotationsContextProvider } from '../../../context/annotations/annotations_context';
import { useApmServiceContext } from '../../../context/apm_service/use_apm_service_context';
import { ChartPointerEventContextProvider } from '../../../context/chart_pointer_event/chart_pointer_event_context';
@@ -43,6 +43,7 @@ export function ServiceOverview({ serviceName }: ServiceOverviewProps) {
const { isMedium } = useBreakPoints();
const rowDirection = isMedium ? 'column' : 'row';
const isRumAgent = isRumAgentName(agentName);
+ const isIosAgent = isIosAgentName(agentName);
return (
@@ -110,7 +111,7 @@ export function ServiceOverview({ serviceName }: ServiceOverviewProps) {
)}
- {!isRumAgent && (
+ {!isRumAgent && !isIosAgent && (
[0] & {
key:
@@ -54,12 +70,12 @@ interface Props {
export function ApmServiceTemplate(props: Props) {
return (
-
+
);
}
-function Template({
+function TemplateWithContext({
children,
serviceName,
selectedTab,
@@ -86,6 +102,10 @@ function Template({
+
+
+
+
@@ -100,6 +120,53 @@ function Template({
);
}
+function AnalyzeDataButton({ serviceName }: { serviceName: string }) {
+ const { agentName } = useApmServiceContext();
+ const { services } = useKibana();
+ const { urlParams } = useUrlParams();
+ const { rangeTo, rangeFrom, environment } = urlParams;
+ const basepath = services.http?.basePath.get();
+
+ if (isRumAgentName(agentName) || isIosAgentName(agentName)) {
+ const href = createExploratoryViewUrl(
+ {
+ 'apm-series': {
+ dataType: isRumAgentName(agentName) ? 'ux' : 'mobile',
+ time: { from: rangeFrom, to: rangeTo },
+ reportType: 'kpi-over-time',
+ reportDefinitions: {
+ [SERVICE_NAME]: [serviceName],
+ ...(!!environment && ENVIRONMENT_NOT_DEFINED.value !== environment
+ ? { [SERVICE_ENVIRONMENT]: [environment] }
+ : {}),
+ },
+ operationType: 'average',
+ isNew: true,
+ } as SeriesUrl,
+ },
+ basepath
+ );
+
+ return (
+
+
+ {i18n.translate('xpack.apm.analyzeDataButton.label', {
+ defaultMessage: 'Analyze data',
+ })}
+
+
+ );
+ }
+
+ return null;
+}
+
function useTabs({
serviceName,
selectedTab,
@@ -153,7 +220,10 @@ function useTabs({
defaultMessage: 'Metrics',
}),
hidden:
- !agentName || isRumAgentName(agentName) || isJavaAgentName(agentName),
+ !agentName ||
+ isRumAgentName(agentName) ||
+ isJavaAgentName(agentName) ||
+ isIosAgentName(agentName),
},
{
key: 'service-map',
diff --git a/x-pack/plugins/apm/public/components/shared/agent_icon/get_agent_icon.ts b/x-pack/plugins/apm/public/components/shared/agent_icon/get_agent_icon.ts
index 7d24d8ffa79e26..b3551e7ccebcb4 100644
--- a/x-pack/plugins/apm/public/components/shared/agent_icon/get_agent_icon.ts
+++ b/x-pack/plugins/apm/public/components/shared/agent_icon/get_agent_icon.ts
@@ -6,6 +6,7 @@
*/
import {
+ isIosAgentName,
isRumAgentName,
isJavaAgentName,
OPEN_TELEMETRY_AGENT_NAMES,
@@ -70,6 +71,10 @@ export function getAgentIconKey(agentName: string) {
return 'java';
}
+ if (isIosAgentName(lowercasedAgentName)) {
+ return 'ios';
+ }
+
// Remove "opentelemetry/" prefix
const agentNameWithoutPrefix = lowercasedAgentName.replace(
/^opentelemetry\//,
diff --git a/x-pack/plugins/apm/server/lib/apm_telemetry/schema.ts b/x-pack/plugins/apm/server/lib/apm_telemetry/schema.ts
index b04f64c6bccff6..80be1ecbe13a51 100644
--- a/x-pack/plugins/apm/server/lib/apm_telemetry/schema.ts
+++ b/x-pack/plugins/apm/server/lib/apm_telemetry/schema.ts
@@ -74,6 +74,7 @@ const apmPerAgentSchema: Pick<
// In the meanwhile, we'll have to maintain these lists up to date (TS will remind us to update)
services_per_agent: {
dotnet: long,
+ 'iOS/swift': long,
go: long,
java: long,
'js-base': long,
@@ -92,10 +93,12 @@ const apmPerAgentSchema: Pick<
'opentelemetry/php': long,
'opentelemetry/python': long,
'opentelemetry/ruby': long,
+ 'opentelemetry/swift': long,
'opentelemetry/webjs': long,
},
agents: {
dotnet: agentSchema,
+ 'iOS/swift': agentSchema,
go: agentSchema,
java: agentSchema,
'js-base': agentSchema,
diff --git a/x-pack/plugins/apm/typings/es_schemas/ui/fields/agent.ts b/x-pack/plugins/apm/typings/es_schemas/ui/fields/agent.ts
index 6bc18ed8b15750..6376d047baf39b 100644
--- a/x-pack/plugins/apm/typings/es_schemas/ui/fields/agent.ts
+++ b/x-pack/plugins/apm/typings/es_schemas/ui/fields/agent.ts
@@ -9,6 +9,7 @@ export type ElasticAgentName =
| 'go'
| 'java'
| 'js-base'
+ | 'iOS/swift'
| 'rum-js'
| 'nodejs'
| 'python'
@@ -27,6 +28,7 @@ export type OpenTelemetryAgentName =
| 'opentelemetry/php'
| 'opentelemetry/python'
| 'opentelemetry/ruby'
+ | 'opentelemetry/swift'
| 'opentelemetry/webjs';
/*
diff --git a/x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json b/x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json
index 34f191f58f1d34..ffe86213ceaf67 100644
--- a/x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json
+++ b/x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json
@@ -319,6 +319,9 @@
"dotnet": {
"type": "long"
},
+ "iOS/swift": {
+ "type": "long"
+ },
"go": {
"type": "long"
},
@@ -373,6 +376,9 @@
"opentelemetry/ruby": {
"type": "long"
},
+ "opentelemetry/swift": {
+ "type": "long"
+ },
"opentelemetry/webjs": {
"type": "long"
}
@@ -464,6 +470,90 @@
}
}
},
+ "iOS/swift": {
+ "properties": {
+ "agent": {
+ "properties": {
+ "version": {
+ "type": "array",
+ "items": {
+ "type": "keyword"
+ }
+ }
+ }
+ },
+ "service": {
+ "properties": {
+ "framework": {
+ "properties": {
+ "name": {
+ "type": "array",
+ "items": {
+ "type": "keyword"
+ }
+ },
+ "version": {
+ "type": "array",
+ "items": {
+ "type": "keyword"
+ }
+ },
+ "composite": {
+ "type": "array",
+ "items": {
+ "type": "keyword"
+ }
+ }
+ }
+ },
+ "language": {
+ "properties": {
+ "name": {
+ "type": "array",
+ "items": {
+ "type": "keyword"
+ }
+ },
+ "version": {
+ "type": "array",
+ "items": {
+ "type": "keyword"
+ }
+ },
+ "composite": {
+ "type": "array",
+ "items": {
+ "type": "keyword"
+ }
+ }
+ }
+ },
+ "runtime": {
+ "properties": {
+ "name": {
+ "type": "array",
+ "items": {
+ "type": "keyword"
+ }
+ },
+ "version": {
+ "type": "array",
+ "items": {
+ "type": "keyword"
+ }
+ },
+ "composite": {
+ "type": "array",
+ "items": {
+ "type": "keyword"
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ },
"go": {
"properties": {
"agent": {
From 1a6cb4634d5197de411fea5874980f0eff7f57c6 Mon Sep 17 00:00:00 2001
From: Shahzad
Date: Wed, 30 Jun 2021 11:05:48 +0200
Subject: [PATCH 042/121] fix too many rernders (#103672)
---
.../exploratory_view/exploratory_view.tsx | 57 +++-------------
.../hooks/use_series_storage.tsx | 4 +-
.../exploratory_view/lens_embeddable.tsx | 68 +++++++++++++++++++
3 files changed, 80 insertions(+), 49 deletions(-)
create mode 100644 x-pack/plugins/observability/public/components/shared/exploratory_view/lens_embeddable.tsx
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/exploratory_view.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/exploratory_view.tsx
index ad85ecab968b21..af04108c56790a 100644
--- a/x-pack/plugins/observability/public/components/shared/exploratory_view/exploratory_view.tsx
+++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/exploratory_view.tsx
@@ -5,7 +5,7 @@
* 2.0.
*/
import { i18n } from '@kbn/i18n';
-import React, { useEffect, useRef, useState, useCallback } from 'react';
+import React, { useEffect, useRef, useState } from 'react';
import { EuiPanel, EuiTitle } from '@elastic/eui';
import styled from 'styled-components';
import { isEmpty } from 'lodash';
@@ -14,11 +14,12 @@ import { ObservabilityPublicPluginsStart } from '../../../plugin';
import { ExploratoryViewHeader } from './header/header';
import { useSeriesStorage } from './hooks/use_series_storage';
import { useLensAttributes } from './hooks/use_lens_attributes';
-import { EmptyView } from './components/empty_view';
import { TypedLensByValueInput } from '../../../../../lens/public';
import { useAppIndexPatternContext } from './hooks/use_app_index_pattern';
import { SeriesBuilder } from './series_builder/series_builder';
import { SeriesUrl } from './types';
+import { LensEmbeddable } from './lens_embeddable';
+import { EmptyView } from './components/empty_view';
export const combineTimeRanges = (
allSeries: Record,
@@ -52,14 +53,13 @@ export function ExploratoryView({
saveAttributes?: (attr: TypedLensByValueInput['attributes'] | null) => void;
}) {
const {
- services: { lens, notifications },
+ services: { lens },
} = useKibana();
const seriesBuilderRef = useRef(null);
const wrapperRef = useRef(null);
const [height, setHeight] = useState('100vh');
- const [seriesId, setSeriesId] = useState('');
const [lastUpdated, setLastUpdated] = useState();
@@ -69,13 +69,7 @@ export function ExploratoryView({
const { loadIndexPattern, loading } = useAppIndexPatternContext();
- const LensComponent = lens?.EmbeddableComponent;
-
- const { firstSeriesId, firstSeries: series, setSeries, allSeries } = useSeriesStorage();
-
- useEffect(() => {
- setSeriesId(firstSeriesId);
- }, [allSeries, firstSeriesId]);
+ const { firstSeries, firstSeriesId, allSeries } = useSeriesStorage();
const lensAttributesT = useLensAttributes();
@@ -108,49 +102,16 @@ export function ExploratoryView({
setHeightOffset();
});
- const timeRange = combineTimeRanges(allSeries, series);
-
- const onLensLoad = useCallback(() => {
- setLastUpdated(Date.now());
- }, []);
-
- const onBrushEnd = useCallback(
- ({ range }: { range: number[] }) => {
- if (series?.reportType !== 'data-distribution') {
- setSeries(seriesId, {
- ...series,
- time: {
- from: new Date(range[0]).toISOString(),
- to: new Date(range[1]).toISOString(),
- },
- });
- } else {
- notifications?.toasts.add(
- i18n.translate('xpack.observability.exploratoryView.noBrusing', {
- defaultMessage: 'Zoom by brush selection is only available on time series charts.',
- })
- );
- }
- },
- [notifications?.toasts, series, seriesId, setSeries]
- );
-
return (
{lens ? (
<>
-
+
- {lensAttributes && timeRange.to && timeRange.from ? (
-
+ {lensAttributes ? (
+
) : (
-
+
)}
();
useEffect(() => {
const allSeriesIds = Object.keys(allShortSeries);
@@ -66,6 +67,7 @@ export function UrlStorageContextProvider({
setAllSeries(allSeriesN);
setFirstSeriesId(allSeriesIds?.[0]);
+ setFirstSeries(allSeriesN?.[0]);
(storage as IKbnUrlStateStorage).set(allSeriesKey, allShortSeries);
}, [allShortSeries, storage]);
@@ -100,7 +102,7 @@ export function UrlStorageContextProvider({
firstSeriesId,
allSeries,
allSeriesIds,
- firstSeries: allSeries?.[firstSeriesId],
+ firstSeries: firstSeries!,
};
return {children} ;
}
diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/lens_embeddable.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/lens_embeddable.tsx
new file mode 100644
index 00000000000000..4cb586fe94ceb0
--- /dev/null
+++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/lens_embeddable.tsx
@@ -0,0 +1,68 @@
+/*
+ * 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 { i18n } from '@kbn/i18n';
+import React, { Dispatch, SetStateAction, useCallback } from 'react';
+import { combineTimeRanges } from './exploratory_view';
+import { TypedLensByValueInput } from '../../../../../lens/public';
+import { useSeriesStorage } from './hooks/use_series_storage';
+import { ObservabilityPublicPluginsStart } from '../../../plugin';
+import { useKibana } from '../../../../../../../src/plugins/kibana_react/public';
+
+interface Props {
+ lensAttributes: TypedLensByValueInput['attributes'];
+ setLastUpdated: Dispatch>;
+}
+
+export function LensEmbeddable(props: Props) {
+ const { lensAttributes, setLastUpdated } = props;
+
+ const {
+ services: { lens, notifications },
+ } = useKibana();
+
+ const LensComponent = lens?.EmbeddableComponent;
+
+ const { firstSeriesId, firstSeries: series, setSeries, allSeries } = useSeriesStorage();
+
+ const timeRange = combineTimeRanges(allSeries, series);
+
+ const onLensLoad = useCallback(() => {
+ setLastUpdated(Date.now());
+ }, [setLastUpdated]);
+
+ const onBrushEnd = useCallback(
+ ({ range }: { range: number[] }) => {
+ if (series?.reportType !== 'data-distribution') {
+ setSeries(firstSeriesId, {
+ ...series,
+ time: {
+ from: new Date(range[0]).toISOString(),
+ to: new Date(range[1]).toISOString(),
+ },
+ });
+ } else {
+ notifications?.toasts.add(
+ i18n.translate('xpack.observability.exploratoryView.noBrusing', {
+ defaultMessage: 'Zoom by brush selection is only available on time series charts.',
+ })
+ );
+ }
+ },
+ [notifications?.toasts, series, firstSeriesId, setSeries]
+ );
+
+ return (
+
+ );
+}
From 7b8645e0fc6774b631acdc1ed01e217a16313e40 Mon Sep 17 00:00:00 2001
From: Yuliia Naumenko
Date: Wed, 30 Jun 2021 12:17:07 +0300
Subject: [PATCH 043/121] [Alerting] Fixed search results are not updated when
search term is removed on Rules and Connectors page (#103663)
---
.../alerts_list/components/alerts_list.tsx | 7 +++-
.../apps/triggers_actions_ui/alerts_list.ts | 37 +++++++++++++++++++
2 files changed, 43 insertions(+), 1 deletion(-)
diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_list/components/alerts_list.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_list/components/alerts_list.tsx
index 1c1633ff4a72f4..b48f6f45ce4b84 100644
--- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_list/components/alerts_list.tsx
+++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_list/components/alerts_list.tsx
@@ -539,7 +539,12 @@ export const AlertsList: React.FunctionComponent = () => {
fullWidth
isClearable
data-test-subj="alertSearchField"
- onChange={(e) => setInputText(e.target.value)}
+ onChange={(e) => {
+ setInputText(e.target.value);
+ if (e.target.value === '') {
+ setSearchText(e.target.value);
+ }
+ }}
onKeyUp={(e) => {
if (e.keyCode === ENTER_KEY) {
setSearchText(inputText);
diff --git a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alerts_list.ts b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alerts_list.ts
index cbb1d2729e74c1..d4d556e06b70c9 100644
--- a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alerts_list.ts
+++ b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alerts_list.ts
@@ -109,6 +109,43 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
]);
});
+ it('should update alert list on the search clear button click', async () => {
+ await createAlert({ name: 'b' });
+ await createAlert({ name: 'c' });
+ await refreshAlertsList();
+ await pageObjects.triggersActionsUI.searchAlerts('b');
+
+ const searchResults = await pageObjects.triggersActionsUI.getAlertsList();
+ expect(searchResults).to.eql([
+ {
+ name: 'b',
+ tagsText: 'foo, bar',
+ alertType: 'Test: Noop',
+ interval: '1m',
+ },
+ ]);
+ const searchClearButton = await find.byCssSelector('.euiFormControlLayoutClearButton');
+ await searchClearButton.click();
+ await find.byCssSelector(
+ '.euiBasicTable[data-test-subj="alertsList"]:not(.euiBasicTable-loading)'
+ );
+ const searchResultsAfterClear = await pageObjects.triggersActionsUI.getAlertsList();
+ expect(searchResultsAfterClear).to.eql([
+ {
+ name: 'b',
+ tagsText: 'foo, bar',
+ alertType: 'Test: Noop',
+ interval: '1m',
+ },
+ {
+ name: 'c',
+ tagsText: 'foo, bar',
+ alertType: 'Test: Noop',
+ interval: '1m',
+ },
+ ]);
+ });
+
it('should search for tags', async () => {
const createdAlert = await createAlert();
await refreshAlertsList();
From eca1460f62b95077aa0fdf2b2b0709690c2da6a3 Mon Sep 17 00:00:00 2001
From: Pete Hampton
Date: Wed, 30 Jun 2021 10:33:50 +0100
Subject: [PATCH 044/121] Endpoint Telemetry: Agents Metrics + Policy Config /
Response (#102171)
* [PH] Initial setup for endpoint task telemetry.
* Refactor / Add daily task for collecting fleet detail / policy resp / EP metrics
* [PH CD] Code walkthrough. Start fetching fleet policy configs.
* [PH] pass in fleet agent service rather than homebrew kuerys.
* [PH] prepare to move away from legacy es client. Get fleet ep agents.
* Fetch agent policy configs.
* Stub ep policy responses.
* Fix CI + Types. Fix dep injection. Reimagine SO client creation.
* Create SO client properly
* Fetch EP Policy responses.
* Fetch EP Policy responses.
* Remove unused import
* Fetch failed policy responses from EP data stream.
* Remove unused imports.
* Combine failed policy responses with policy configs.
* Attach fleet agent + ep agent ids
* Add dedicated channel sender. Temp disable with feature flag.
* Remove ublock from the failed policy response.
* Fetch endpoint metrics.
* Fix bad merge commit.
* Get EP telemetry.
* Record last execution time of endpoint task
* Remove send on demand feature flag.
* Simplify cache conditional.
* Refactor into Promise.allSettled
* Fix type error.
* Bail if there is no endpoint metrics
* Bump interval to 24h.
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
---
.../{task.test.ts => diagnostic_task.test.ts} | 57 +----
.../telemetry/{task.ts => diagnostic_task.ts} | 17 +-
.../lib/telemetry/endpoint_task.test.ts | 51 ++++
.../server/lib/telemetry/endpoint_task.ts | 220 ++++++++++++++++++
.../server/lib/telemetry/helpers.task.ts | 37 +++
.../server/lib/telemetry/helpers.ts | 23 ++
.../server/lib/telemetry/mocks.ts | 11 +-
.../server/lib/telemetry/sender.ts | 179 ++++++++++++--
.../server/lib/telemetry/types.ts | 130 +++++++++++
.../security_solution/server/plugin.ts | 7 +-
10 files changed, 649 insertions(+), 83 deletions(-)
rename x-pack/plugins/security_solution/server/lib/telemetry/{task.test.ts => diagnostic_task.test.ts} (63%)
rename x-pack/plugins/security_solution/server/lib/telemetry/{task.ts => diagnostic_task.ts} (85%)
create mode 100644 x-pack/plugins/security_solution/server/lib/telemetry/endpoint_task.test.ts
create mode 100644 x-pack/plugins/security_solution/server/lib/telemetry/endpoint_task.ts
create mode 100644 x-pack/plugins/security_solution/server/lib/telemetry/helpers.task.ts
create mode 100644 x-pack/plugins/security_solution/server/lib/telemetry/helpers.ts
create mode 100644 x-pack/plugins/security_solution/server/lib/telemetry/types.ts
diff --git a/x-pack/plugins/security_solution/server/lib/telemetry/task.test.ts b/x-pack/plugins/security_solution/server/lib/telemetry/diagnostic_task.test.ts
similarity index 63%
rename from x-pack/plugins/security_solution/server/lib/telemetry/task.test.ts
rename to x-pack/plugins/security_solution/server/lib/telemetry/diagnostic_task.test.ts
index 9b29df711663bc..3008919fd2ceda 100644
--- a/x-pack/plugins/security_solution/server/lib/telemetry/task.test.ts
+++ b/x-pack/plugins/security_solution/server/lib/telemetry/diagnostic_task.test.ts
@@ -5,13 +5,10 @@
* 2.0.
*/
-import moment from 'moment';
import { loggingSystemMock } from 'src/core/server/mocks';
-
import { taskManagerMock } from '../../../../task_manager/server/mocks';
import { TaskStatus } from '../../../../task_manager/server';
-
-import { TelemetryDiagTask, TelemetryDiagTaskConstants } from './task';
+import { TelemetryDiagTask, TelemetryDiagTaskConstants } from './diagnostic_task';
import { createMockTelemetryEventsSender, MockTelemetryDiagnosticTask } from './mocks';
describe('test', () => {
@@ -22,7 +19,7 @@ describe('test', () => {
});
describe('basic diagnostic alert telemetry sanity checks', () => {
- test('task can register', () => {
+ test('diagnostic task can register', () => {
const telemetryDiagTask = new TelemetryDiagTask(
logger,
taskManagerMock.createSetup(),
@@ -40,7 +37,7 @@ describe('test', () => {
expect(mockTaskManager.registerTaskDefinitions).toHaveBeenCalled();
});
- test('task should be scheduled', async () => {
+ test('diagnostic task should be scheduled', async () => {
const mockTaskManagerSetup = taskManagerMock.createSetup();
const telemetryDiagTask = new TelemetryDiagTask(
logger,
@@ -53,7 +50,7 @@ describe('test', () => {
expect(mockTaskManagerStart.ensureScheduled).toHaveBeenCalled();
});
- test('task should run', async () => {
+ test('diagnostic task should run', async () => {
const mockContext = createMockTelemetryEventsSender(true);
const mockTaskManager = taskManagerMock.createSetup();
const telemetryDiagTask = new MockTelemetryDiagnosticTask(logger, mockTaskManager, mockContext);
@@ -79,7 +76,7 @@ describe('test', () => {
expect(telemetryDiagTask.runTask).toHaveBeenCalled();
});
- test('task should not query elastic if telemetry is not opted in', async () => {
+ test('diagnostic task should not query elastic if telemetry is not opted in', async () => {
const mockSender = createMockTelemetryEventsSender(false);
const mockTaskManager = taskManagerMock.createSetup();
new MockTelemetryDiagnosticTask(logger, mockTaskManager, mockSender);
@@ -104,48 +101,4 @@ describe('test', () => {
await taskRunner.run();
expect(mockSender.fetchDiagnosticAlerts).not.toHaveBeenCalled();
});
-
- test('test -5 mins is returned when there is no previous task run', async () => {
- const telemetryDiagTask = new TelemetryDiagTask(
- logger,
- taskManagerMock.createSetup(),
- createMockTelemetryEventsSender(true)
- );
-
- const executeTo = moment().utc().toISOString();
- const executeFrom = undefined;
- const newExecuteFrom = telemetryDiagTask.getLastExecutionTimestamp(executeTo, executeFrom);
-
- expect(newExecuteFrom).toEqual(moment(executeTo).subtract(5, 'minutes').toISOString());
- });
-
- test('test -6 mins is returned when there was a previous task run', async () => {
- const telemetryDiagTask = new TelemetryDiagTask(
- logger,
- taskManagerMock.createSetup(),
- createMockTelemetryEventsSender(true)
- );
-
- const executeTo = moment().utc().toISOString();
- const executeFrom = moment(executeTo).subtract(6, 'minutes').toISOString();
- const newExecuteFrom = telemetryDiagTask.getLastExecutionTimestamp(executeTo, executeFrom);
-
- expect(newExecuteFrom).toEqual(executeFrom);
- });
-
- // it's possible if Kibana is down for a prolonged period the stored lastRun would have drifted
- // if that is the case we will just roll it back to a 10 min search window
- test('test 10 mins is returned when previous task run took longer than 10 minutes', async () => {
- const telemetryDiagTask = new TelemetryDiagTask(
- logger,
- taskManagerMock.createSetup(),
- createMockTelemetryEventsSender(true)
- );
-
- const executeTo = moment().utc().toISOString();
- const executeFrom = moment(executeTo).subtract(142, 'minutes').toISOString();
- const newExecuteFrom = telemetryDiagTask.getLastExecutionTimestamp(executeTo, executeFrom);
-
- expect(newExecuteFrom).toEqual(moment(executeTo).subtract(10, 'minutes').toISOString());
- });
});
diff --git a/x-pack/plugins/security_solution/server/lib/telemetry/task.ts b/x-pack/plugins/security_solution/server/lib/telemetry/diagnostic_task.ts
similarity index 85%
rename from x-pack/plugins/security_solution/server/lib/telemetry/task.ts
rename to x-pack/plugins/security_solution/server/lib/telemetry/diagnostic_task.ts
index 766a8f007aefff..05d7396031a5f9 100644
--- a/x-pack/plugins/security_solution/server/lib/telemetry/task.ts
+++ b/x-pack/plugins/security_solution/server/lib/telemetry/diagnostic_task.ts
@@ -12,6 +12,7 @@ import {
TaskManagerSetupContract,
TaskManagerStartContract,
} from '../../../../task_manager/server';
+import { getLastTaskExecutionTimestamp } from './helpers';
import { TelemetryEventsSender, TelemetryEvent } from './sender';
export const TelemetryDiagTaskConstants = {
@@ -43,7 +44,7 @@ export class TelemetryDiagTask {
return {
run: async () => {
const executeTo = moment().utc().toISOString();
- const executeFrom = this.getLastExecutionTimestamp(
+ const executeFrom = getLastTaskExecutionTimestamp(
executeTo,
taskInstance.state?.lastExecutionTimestamp
);
@@ -64,20 +65,6 @@ export class TelemetryDiagTask {
});
}
- public getLastExecutionTimestamp(executeTo: string, lastExecutionTimestamp?: string) {
- if (lastExecutionTimestamp === undefined) {
- this.logger.debug(`No last execution timestamp defined`);
- return moment(executeTo).subtract(5, 'minutes').toISOString();
- }
-
- if (moment(executeTo).diff(lastExecutionTimestamp, 'minutes') >= 10) {
- this.logger.debug(`last execution timestamp was greater than 10 minutes`);
- return moment(executeTo).subtract(10, 'minutes').toISOString();
- }
-
- return lastExecutionTimestamp;
- }
-
public start = async (taskManager: TaskManagerStartContract) => {
try {
await taskManager.ensureScheduled({
diff --git a/x-pack/plugins/security_solution/server/lib/telemetry/endpoint_task.test.ts b/x-pack/plugins/security_solution/server/lib/telemetry/endpoint_task.test.ts
new file mode 100644
index 00000000000000..a056ef783f6cf6
--- /dev/null
+++ b/x-pack/plugins/security_solution/server/lib/telemetry/endpoint_task.test.ts
@@ -0,0 +1,51 @@
+/*
+ * 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 { loggingSystemMock } from 'src/core/server/mocks';
+import { taskManagerMock } from '../../../../task_manager/server/mocks';
+import { TelemetryEndpointTask } from './endpoint_task';
+import { createMockTelemetryEventsSender } from './mocks';
+
+describe('test', () => {
+ let logger: ReturnType;
+
+ beforeEach(() => {
+ logger = loggingSystemMock.createLogger();
+ });
+
+ describe('endpoint alert telemetry checks', () => {
+ test('the task can register', () => {
+ const telemetryEndpointTask = new TelemetryEndpointTask(
+ logger,
+ taskManagerMock.createSetup(),
+ createMockTelemetryEventsSender(true)
+ );
+
+ expect(telemetryEndpointTask).toBeInstanceOf(TelemetryEndpointTask);
+ });
+ });
+
+ test('the endpoint task should be registered', () => {
+ const mockTaskManager = taskManagerMock.createSetup();
+ new TelemetryEndpointTask(logger, mockTaskManager, createMockTelemetryEventsSender(true));
+
+ expect(mockTaskManager.registerTaskDefinitions).toHaveBeenCalled();
+ });
+
+ test('the endpoint task should be scheduled', async () => {
+ const mockTaskManagerSetup = taskManagerMock.createSetup();
+ const telemetryEndpointTask = new TelemetryEndpointTask(
+ logger,
+ mockTaskManagerSetup,
+ createMockTelemetryEventsSender(true)
+ );
+
+ const mockTaskManagerStart = taskManagerMock.createStart();
+ await telemetryEndpointTask.start(mockTaskManagerStart);
+ expect(mockTaskManagerStart.ensureScheduled).toHaveBeenCalled();
+ });
+});
diff --git a/x-pack/plugins/security_solution/server/lib/telemetry/endpoint_task.ts b/x-pack/plugins/security_solution/server/lib/telemetry/endpoint_task.ts
new file mode 100644
index 00000000000000..cac92983b38783
--- /dev/null
+++ b/x-pack/plugins/security_solution/server/lib/telemetry/endpoint_task.ts
@@ -0,0 +1,220 @@
+/*
+ * 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 moment from 'moment';
+import { Logger } from 'src/core/server';
+import {
+ ConcreteTaskInstance,
+ TaskManagerSetupContract,
+ TaskManagerStartContract,
+} from '../../../../task_manager/server';
+import { getLastTaskExecutionTimestamp } from './helpers';
+import { TelemetryEventsSender } from './sender';
+import { FullAgentPolicyInput } from '../../../../fleet/common/types/models/agent_policy';
+import {
+ EndpointMetricsAggregation,
+ EndpointPolicyResponseAggregation,
+ EndpointPolicyResponseDocument,
+ FleetAgentCacheItem,
+} from './types';
+
+export const TelemetryEndpointTaskConstants = {
+ TIMEOUT: '5m',
+ TYPE: 'security:endpoint-meta-telemetry',
+ INTERVAL: '24m',
+ VERSION: '1.0.0',
+};
+
+export class TelemetryEndpointTask {
+ private readonly logger: Logger;
+ private readonly sender: TelemetryEventsSender;
+
+ constructor(
+ logger: Logger,
+ taskManager: TaskManagerSetupContract,
+ sender: TelemetryEventsSender
+ ) {
+ this.logger = logger;
+ this.sender = sender;
+
+ taskManager.registerTaskDefinitions({
+ [TelemetryEndpointTaskConstants.TYPE]: {
+ title: 'Security Solution Telemetry Endpoint Metrics and Info task',
+ timeout: TelemetryEndpointTaskConstants.TIMEOUT,
+ createTaskRunner: ({ taskInstance }: { taskInstance: ConcreteTaskInstance }) => {
+ const { state } = taskInstance;
+
+ return {
+ run: async () => {
+ const executeTo = moment().utc().toISOString();
+ const lastExecutionTimestamp = getLastTaskExecutionTimestamp(
+ executeTo,
+ taskInstance.state?.lastExecutionTimestamp
+ );
+
+ const hits = await this.runTask(taskInstance.id);
+
+ return {
+ state: {
+ lastExecutionTimestamp,
+ runs: (state.runs || 0) + 1,
+ hits,
+ },
+ };
+ },
+ cancel: async () => {},
+ };
+ },
+ },
+ });
+ }
+
+ public start = async (taskManager: TaskManagerStartContract) => {
+ try {
+ await taskManager.ensureScheduled({
+ id: this.getTaskId(),
+ taskType: TelemetryEndpointTaskConstants.TYPE,
+ scope: ['securitySolution'],
+ schedule: {
+ interval: TelemetryEndpointTaskConstants.INTERVAL,
+ },
+ state: { runs: 0 },
+ params: { version: TelemetryEndpointTaskConstants.VERSION },
+ });
+ } catch (e) {
+ this.logger.error(`Error scheduling task, received ${e.message}`);
+ }
+ };
+
+ private getTaskId = (): string => {
+ return `${TelemetryEndpointTaskConstants.TYPE}:${TelemetryEndpointTaskConstants.VERSION}`;
+ };
+
+ private async fetchEndpointData() {
+ const [epMetricsResponse, fleetAgentsResponse, policyResponse] = await Promise.allSettled([
+ this.sender.fetchEndpointMetrics(),
+ this.sender.fetchFleetAgents(),
+ this.sender.fetchFailedEndpointPolicyResponses(),
+ ]);
+
+ return {
+ endpointMetrics:
+ epMetricsResponse.status === 'fulfilled' ? epMetricsResponse.value : undefined,
+ fleetAgentsResponse:
+ fleetAgentsResponse.status === 'fulfilled' ? fleetAgentsResponse.value : undefined,
+ epPolicyResponse: policyResponse.status === 'fulfilled' ? policyResponse.value : undefined,
+ };
+ }
+
+ public runTask = async (taskId: string) => {
+ if (taskId !== this.getTaskId()) {
+ this.logger.debug(`Outdated task running: ${taskId}`);
+ return 0;
+ }
+
+ const isOptedIn = await this.sender.isTelemetryOptedIn();
+ if (!isOptedIn) {
+ this.logger.debug(`Telemetry is not opted-in.`);
+ return 0;
+ }
+
+ const endpointData = await this.fetchEndpointData();
+
+ const { body: endpointMetricsResponse } = (endpointData.endpointMetrics as unknown) as {
+ body: EndpointMetricsAggregation;
+ };
+ if (endpointMetricsResponse.aggregations === undefined) {
+ this.logger.debug(`No endpoint metrics`);
+ return 0;
+ }
+
+ const endpointMetrics = endpointMetricsResponse.aggregations.endpoint_agents.buckets.map(
+ (epMetrics) => {
+ return {
+ endpoint_agent: epMetrics.latest_metrics.hits.hits[0]._source.agent.id,
+ endpoint_metrics: epMetrics.latest_metrics.hits.hits[0]._source,
+ };
+ }
+ );
+
+ if (endpointMetrics.length === 0) {
+ this.logger.debug('no reported endpoint metrics');
+ return 0;
+ }
+
+ const agentsResponse = endpointData.fleetAgentsResponse;
+ if (agentsResponse === undefined) {
+ this.logger.debug('no agents to report');
+ return 0;
+ }
+
+ const fleetAgents = agentsResponse?.agents.reduce((cache, agent) => {
+ cache.set(agent.id, { policy_id: agent.policy_id, policy_version: agent.policy_revision });
+ return cache;
+ }, new Map());
+
+ const endpointPolicyCache = new Map();
+ for (const policyInfo of fleetAgents.values()) {
+ if (
+ policyInfo.policy_id !== null &&
+ policyInfo.policy_id !== undefined &&
+ !endpointPolicyCache.has(policyInfo.policy_id)
+ ) {
+ const packagePolicies = await this.sender.fetchEndpointPolicyConfigs(policyInfo.policy_id);
+ packagePolicies?.inputs.forEach((input) => {
+ if (input.type === 'endpoint' && policyInfo.policy_id !== undefined) {
+ endpointPolicyCache.set(policyInfo.policy_id, input);
+ }
+ });
+ }
+ }
+
+ const { body: failedPolicyResponses } = (endpointData.epPolicyResponse as unknown) as {
+ body: EndpointPolicyResponseAggregation;
+ };
+ const policyResponses = failedPolicyResponses.aggregations.policy_responses.buckets.reduce(
+ (cache, bucket) => {
+ const doc = bucket.latest_response.hits.hits[0];
+ cache.set(bucket.key, doc);
+ return cache;
+ },
+ new Map()
+ );
+
+ const telemetryPayloads = endpointMetrics.map((endpoint) => {
+ let policyConfig = null;
+ let failedPolicy = null;
+
+ const fleetAgentId = endpoint.endpoint_metrics.elastic.agent.id;
+ const endpointAgentId = endpoint.endpoint_agent;
+
+ const policyInformation = fleetAgents.get(fleetAgentId);
+ if (policyInformation?.policy_id) {
+ policyConfig = endpointPolicyCache.get(policyInformation?.policy_id);
+ if (policyConfig) {
+ failedPolicy = policyResponses.get(policyConfig?.id);
+ }
+ }
+
+ return {
+ agent_id: fleetAgentId,
+ endpoint_id: endpointAgentId,
+ endpoint_metrics: {
+ os: endpoint.endpoint_metrics.host.os,
+ cpu: endpoint.endpoint_metrics.Endpoint.metrics.cpu,
+ memory: endpoint.endpoint_metrics.Endpoint.metrics.memory,
+ uptime: endpoint.endpoint_metrics.Endpoint.metrics.uptime,
+ },
+ policy_config: policyConfig,
+ policy_failure: failedPolicy,
+ };
+ });
+
+ this.sender.sendOnDemand('endpoint-metadata', telemetryPayloads);
+ return telemetryPayloads.length;
+ };
+}
diff --git a/x-pack/plugins/security_solution/server/lib/telemetry/helpers.task.ts b/x-pack/plugins/security_solution/server/lib/telemetry/helpers.task.ts
new file mode 100644
index 00000000000000..ec81f3d0a5fa4b
--- /dev/null
+++ b/x-pack/plugins/security_solution/server/lib/telemetry/helpers.task.ts
@@ -0,0 +1,37 @@
+/*
+ * 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 moment from 'moment';
+import { getLastTaskExecutionTimestamp } from './helpers';
+
+describe('test scheduled task helpers', () => {
+ test('test -5 mins is returned when there is no previous task run', async () => {
+ const executeTo = moment().utc().toISOString();
+ const executeFrom = undefined;
+ const newExecuteFrom = getLastTaskExecutionTimestamp(executeTo, executeFrom);
+
+ expect(newExecuteFrom).toEqual(moment(executeTo).subtract(5, 'minutes').toISOString());
+ });
+
+ test('test -6 mins is returned when there was a previous task run', async () => {
+ const executeTo = moment().utc().toISOString();
+ const executeFrom = moment(executeTo).subtract(6, 'minutes').toISOString();
+ const newExecuteFrom = getLastTaskExecutionTimestamp(executeTo, executeFrom);
+
+ expect(newExecuteFrom).toEqual(executeFrom);
+ });
+
+ // it's possible if Kibana is down for a prolonged period the stored lastRun would have drifted
+ // if that is the case we will just roll it back to a 10 min search window
+ test('test 10 mins is returned when previous task run took longer than 10 minutes', async () => {
+ const executeTo = moment().utc().toISOString();
+ const executeFrom = moment(executeTo).subtract(142, 'minutes').toISOString();
+ const newExecuteFrom = getLastTaskExecutionTimestamp(executeTo, executeFrom);
+
+ expect(newExecuteFrom).toEqual(moment(executeTo).subtract(10, 'minutes').toISOString());
+ });
+});
diff --git a/x-pack/plugins/security_solution/server/lib/telemetry/helpers.ts b/x-pack/plugins/security_solution/server/lib/telemetry/helpers.ts
new file mode 100644
index 00000000000000..e820116462fa20
--- /dev/null
+++ b/x-pack/plugins/security_solution/server/lib/telemetry/helpers.ts
@@ -0,0 +1,23 @@
+/*
+ * 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 moment from 'moment';
+
+export const getLastTaskExecutionTimestamp = (
+ executeTo: string,
+ lastExecutionTimestamp?: string
+) => {
+ if (lastExecutionTimestamp === undefined) {
+ return moment(executeTo).subtract(5, 'minutes').toISOString();
+ }
+
+ if (moment(executeTo).diff(lastExecutionTimestamp, 'minutes') >= 10) {
+ return moment(executeTo).subtract(10, 'minutes').toISOString();
+ }
+
+ return lastExecutionTimestamp;
+};
diff --git a/x-pack/plugins/security_solution/server/lib/telemetry/mocks.ts b/x-pack/plugins/security_solution/server/lib/telemetry/mocks.ts
index e76fd606c2054e..6738113da103d5 100644
--- a/x-pack/plugins/security_solution/server/lib/telemetry/mocks.ts
+++ b/x-pack/plugins/security_solution/server/lib/telemetry/mocks.ts
@@ -5,8 +5,10 @@
* 2.0.
*/
+// eslint-disable-next-line max-classes-per-file
import { TelemetryEventsSender } from './sender';
-import { TelemetryDiagTask } from './task';
+import { TelemetryDiagTask } from './diagnostic_task';
+import { TelemetryEndpointTask } from './endpoint_task';
/**
* Creates a mocked Telemetry Events Sender
@@ -37,3 +39,10 @@ export const createMockTelemetryEventsSender = (
export class MockTelemetryDiagnosticTask extends TelemetryDiagTask {
public runTask = jest.fn();
}
+
+/**
+ * Creates a mocked Telemetry Endpoint Task
+ */
+export class MockTelemetryEndpointTask extends TelemetryEndpointTask {
+ public runTask = jest.fn();
+}
diff --git a/x-pack/plugins/security_solution/server/lib/telemetry/sender.ts b/x-pack/plugins/security_solution/server/lib/telemetry/sender.ts
index 4f552b3edcda4a..302f56802a5a49 100644
--- a/x-pack/plugins/security_solution/server/lib/telemetry/sender.ts
+++ b/x-pack/plugins/security_solution/server/lib/telemetry/sender.ts
@@ -7,19 +7,19 @@
import { cloneDeep } from 'lodash';
import axios from 'axios';
-import { LegacyAPICaller } from 'kibana/server';
+import { LegacyAPICaller, SavedObjectsClientContract } from 'kibana/server';
import { URL } from 'url';
-import { Logger, CoreStart } from '../../../../../../src/core/server';
+import { CoreStart, ElasticsearchClient, Logger } from 'src/core/server';
+import { TelemetryPluginStart, TelemetryPluginSetup } from 'src/plugins/telemetry/server';
import { transformDataToNdjson } from '../../utils/read_stream/create_stream_from_ndjson';
-import {
- TelemetryPluginStart,
- TelemetryPluginSetup,
-} from '../../../../../../src/plugins/telemetry/server';
import {
TaskManagerSetupContract,
TaskManagerStartContract,
} from '../../../../task_manager/server';
-import { TelemetryDiagTask } from './task';
+import { TelemetryDiagTask } from './diagnostic_task';
+import { TelemetryEndpointTask } from './endpoint_task';
+import { EndpointAppContextService } from '../../endpoint/endpoint_app_context_services';
+import { AgentService, AgentPolicyServiceInterface } from '../../../../fleet/server';
type BaseSearchTypes = string | number | boolean | object;
export type SearchTypes = BaseSearchTypes | BaseSearchTypes[] | undefined;
@@ -45,6 +45,7 @@ export interface TelemetryEvent {
export class TelemetryEventsSender {
private readonly initialCheckDelayMs = 10 * 1000;
private readonly checkIntervalMs = 60 * 1000;
+ private readonly max_records = 10_000;
private readonly logger: Logger;
private core?: CoreStart;
private maxQueueSize = 100;
@@ -55,6 +56,11 @@ export class TelemetryEventsSender {
private queue: TelemetryEvent[] = [];
private isOptedIn?: boolean = true; // Assume true until the first check
private diagTask?: TelemetryDiagTask;
+ private epMetricsTask?: TelemetryEndpointTask;
+ private agentService?: AgentService;
+ private agentPolicyService?: AgentPolicyServiceInterface;
+ private esClient?: ElasticsearchClient;
+ private savedObjectClient?: SavedObjectsClientContract;
constructor(logger: Logger) {
this.logger = logger.get('telemetry_events');
@@ -65,20 +71,27 @@ export class TelemetryEventsSender {
if (taskManager) {
this.diagTask = new TelemetryDiagTask(this.logger, taskManager, this);
+ this.epMetricsTask = new TelemetryEndpointTask(this.logger, taskManager, this);
}
}
public start(
core?: CoreStart,
telemetryStart?: TelemetryPluginStart,
- taskManager?: TaskManagerStartContract
+ taskManager?: TaskManagerStartContract,
+ endpointContextService?: EndpointAppContextService
) {
this.telemetryStart = telemetryStart;
this.core = core;
+ this.esClient = core?.elasticsearch.client.asInternalUser;
+ this.agentService = endpointContextService?.getAgentService();
+ this.agentPolicyService = endpointContextService?.getAgentPolicyService();
+ this.savedObjectClient = (core?.savedObjects.createInternalRepository() as unknown) as SavedObjectsClientContract;
- if (taskManager && this.diagTask) {
- this.logger.debug(`Starting diag task`);
+ if (taskManager && this.diagTask && this.epMetricsTask) {
+ this.logger.debug(`Starting diagnostic and endpoint telemetry tasks`);
this.diagTask.start(taskManager);
+ this.epMetricsTask.start(taskManager);
}
this.logger.debug(`Starting local task`);
@@ -126,6 +139,112 @@ export class TelemetryEventsSender {
return callCluster('search', query);
}
+ public async fetchEndpointMetrics() {
+ if (this.esClient === undefined) {
+ throw Error('could not fetch policy responses. es client is not available');
+ }
+
+ const query = {
+ expand_wildcards: 'open,hidden',
+ index: `.ds-metrics-endpoint.metrics*`,
+ ignore_unavailable: false,
+ size: 0, // no query results required - only aggregation quantity
+ body: {
+ aggs: {
+ endpoint_agents: {
+ terms: {
+ size: this.max_records,
+ field: 'agent.id.keyword',
+ },
+ aggs: {
+ latest_metrics: {
+ top_hits: {
+ size: 1,
+ sort: [
+ {
+ '@timestamp': {
+ order: 'desc',
+ },
+ },
+ ],
+ },
+ },
+ },
+ },
+ },
+ },
+ };
+
+ // @ts-expect-error The types of 'body.aggs' are incompatible between these types.
+ return this.esClient.search(query);
+ }
+
+ public async fetchFleetAgents() {
+ if (this.esClient === undefined) {
+ throw Error('could not fetch policy responses. es client is not available');
+ }
+
+ return this.agentService?.listAgents(this.esClient, {
+ perPage: this.max_records,
+ showInactive: true,
+ sortField: 'enrolled_at',
+ sortOrder: 'desc',
+ });
+ }
+
+ public async fetchEndpointPolicyConfigs(id: string) {
+ if (this.savedObjectClient === undefined) {
+ throw Error('could not fetch endpoint policy configs. saved object client is not available');
+ }
+
+ return this.agentPolicyService?.getFullAgentPolicy(this.savedObjectClient, id);
+ }
+
+ public async fetchFailedEndpointPolicyResponses() {
+ if (this.esClient === undefined) {
+ throw Error('could not fetch policy responses. es client is not available');
+ }
+
+ const query = {
+ expand_wildcards: 'open,hidden',
+ index: `.ds-metrics-endpoint.policy*`,
+ ignore_unavailable: false,
+ size: 0, // no query results required - only aggregation quantity
+ body: {
+ query: {
+ match: {
+ 'Endpoint.policy.applied.status': 'failure',
+ },
+ },
+ aggs: {
+ policy_responses: {
+ terms: {
+ size: this.max_records,
+ field: 'Endpoint.policy.applied.id.keyword',
+ },
+ aggs: {
+ latest_response: {
+ top_hits: {
+ size: 1,
+ sort: [
+ {
+ '@timestamp': {
+ order: 'desc',
+ },
+ },
+ ],
+ },
+ },
+ },
+ },
+ },
+ },
+ };
+
+ // @ts-expect-error The types of 'body.aggs' are incompatible between these types.
+ return this.esClient.search(query);
+ }
+
public queueTelemetryEvents(events: TelemetryEvent[]) {
const qlength = this.queue.length;
@@ -179,7 +298,7 @@ export class TelemetryEventsSender {
}
const [telemetryUrl, clusterInfo, licenseInfo] = await Promise.all([
- this.fetchTelemetryUrl(),
+ this.fetchTelemetryUrl('alerts-endpoint'),
this.fetchClusterInfo(),
this.fetchLicenseInfo(),
]);
@@ -211,6 +330,39 @@ export class TelemetryEventsSender {
this.isSending = false;
}
+ /**
+ * This function sends events to the elastic telemetry channel. Caution is required
+ * because it does no allowlist filtering. The caller is responsible for making sure
+ * that there is no sensitive material or PII in the records that are sent upstream.
+ *
+ * @param channel the elastic telemetry channel
+ * @param toSend telemetry events
+ */
+ public async sendOnDemand(channel: string, toSend: unknown[]) {
+ try {
+ const [telemetryUrl, clusterInfo, licenseInfo] = await Promise.all([
+ this.fetchTelemetryUrl(channel),
+ this.fetchClusterInfo(),
+ this.fetchLicenseInfo(),
+ ]);
+
+ this.logger.debug(`Telemetry URL: ${telemetryUrl}`);
+ this.logger.debug(
+ `cluster_uuid: ${clusterInfo?.cluster_uuid} cluster_name: ${clusterInfo?.cluster_name}`
+ );
+
+ await this.sendEvents(
+ toSend,
+ telemetryUrl,
+ clusterInfo.cluster_uuid,
+ clusterInfo.version?.number,
+ licenseInfo?.uid
+ );
+ } catch (err) {
+ this.logger.warn(`Error sending telemetry events data: ${err}`);
+ }
+ }
+
private async fetchClusterInfo(): Promise {
if (!this.core) {
throw Error("Couldn't fetch cluster info because core is not available");
@@ -219,12 +371,12 @@ export class TelemetryEventsSender {
return getClusterInfo(callCluster);
}
- private async fetchTelemetryUrl(): Promise {
+ private async fetchTelemetryUrl(channel: string): Promise {
const telemetryUrl = await this.telemetrySetup?.getTelemetryUrl();
if (!telemetryUrl) {
throw Error("Couldn't get telemetry URL");
}
- return getV3UrlFromV2(telemetryUrl.toString(), 'alerts-endpoint');
+ return getV3UrlFromV2(telemetryUrl.toString(), channel);
}
private async fetchLicenseInfo(): Promise {
@@ -258,7 +410,6 @@ export class TelemetryEventsSender {
clusterVersionNumber: string | undefined,
licenseId: string | undefined
) {
- // this.logger.debug(`Sending events: ${JSON.stringify(events, null, 2)}`);
const ndjson = transformDataToNdjson(events);
// this.logger.debug(`NDJSON: ${ndjson}`);
diff --git a/x-pack/plugins/security_solution/server/lib/telemetry/types.ts b/x-pack/plugins/security_solution/server/lib/telemetry/types.ts
new file mode 100644
index 00000000000000..435f3cf49d1f1b
--- /dev/null
+++ b/x-pack/plugins/security_solution/server/lib/telemetry/types.ts
@@ -0,0 +1,130 @@
+/*
+ * 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.
+ */
+
+// Sec Sol Kbn telemetry instrumentation specific
+
+export interface FleetAgentCacheItem {
+ policy_id: string | undefined;
+ policy_version: number | undefined | null;
+}
+
+// EP Policy Response
+
+export interface EndpointPolicyResponseAggregation {
+ hits: {
+ total: { value: number };
+ };
+ aggregations: {
+ policy_responses: {
+ buckets: Array<{
+ key: string;
+ doc_count: number;
+ latest_response: EndpointPolicyResponseHits;
+ }>;
+ };
+ };
+}
+
+interface EndpointPolicyResponseHits {
+ hits: {
+ total: { value: number };
+ hits: EndpointPolicyResponseDocument[];
+ };
+}
+
+export interface EndpointPolicyResponseDocument {
+ _source: {
+ '@timestamp': string;
+ agent: {
+ id: string;
+ };
+ event: {
+ agent_id_status: string;
+ };
+ Endpoint: {};
+ };
+}
+
+// EP Metrics
+
+export interface EndpointMetricsAggregation {
+ hits: {
+ total: { value: number };
+ };
+ aggregations: {
+ endpoint_agents: {
+ buckets: Array<{ key: string; doc_count: number; latest_metrics: EndpointMetricHits }>;
+ };
+ };
+}
+
+interface EndpointMetricHits {
+ hits: {
+ total: { value: number };
+ hits: EndpointMetricDocument[];
+ };
+}
+
+interface EndpointMetricDocument {
+ _source: {
+ '@timestamp': string;
+ agent: {
+ id: string;
+ };
+ Endpoint: {
+ metrics: EndpointMetrics;
+ };
+ elastic: {
+ agent: {
+ id: string;
+ };
+ };
+ host: {
+ os: EndpointMetricOS;
+ };
+ event: {
+ agent_id_status: string;
+ };
+ };
+}
+
+export interface EndpointMetrics {
+ memory: {
+ endpoint: {
+ private: {
+ mean: number;
+ latest: number;
+ };
+ };
+ };
+ cpu: {
+ endpoint: {
+ histogram: {
+ counts: number[];
+ values: number[];
+ };
+ mean: number;
+ latest: number;
+ };
+ };
+ uptime: {
+ endpoint: number;
+ system: number;
+ };
+}
+
+interface EndpointMetricOS {
+ Ext: {
+ variant: string;
+ };
+ kernel: string;
+ name: string;
+ family: string;
+ version: string;
+ platform: string;
+ full: string;
+}
diff --git a/x-pack/plugins/security_solution/server/plugin.ts b/x-pack/plugins/security_solution/server/plugin.ts
index 453e98b020cbea..cd923a4b0619f2 100644
--- a/x-pack/plugins/security_solution/server/plugin.ts
+++ b/x-pack/plugins/security_solution/server/plugin.ts
@@ -506,7 +506,12 @@ export class Plugin implements IPlugin
Date: Wed, 30 Jun 2021 12:30:35 +0200
Subject: [PATCH 045/121] [APM] use conventional error rate color for
correlations (#103500)
---
.../public/components/app/correlations/error_correlations.tsx | 1 +
1 file changed, 1 insertion(+)
diff --git a/x-pack/plugins/apm/public/components/app/correlations/error_correlations.tsx b/x-pack/plugins/apm/public/components/app/correlations/error_correlations.tsx
index 7fb7444a52f848..526aad56e743ec 100644
--- a/x-pack/plugins/apm/public/components/app/correlations/error_correlations.tsx
+++ b/x-pack/plugins/apm/public/components/app/correlations/error_correlations.tsx
@@ -248,6 +248,7 @@ function ErrorTimeseriesChart({
yAccessors={['y']}
data={overallData?.overall?.timeseries ?? []}
curve={CurveType.CURVE_MONOTONE_X}
+ color={theme.eui.euiColorVis7}
/>
{correlationsData && selectedSignificantTerm ? (
From a63084b8de1e32170a61dc4f35dce14d77b8d3ec Mon Sep 17 00:00:00 2001
From: Dima Arnautov
Date: Wed, 30 Jun 2021 12:52:47 +0200
Subject: [PATCH 046/121] [Transform] Fix default naming and sorting fields
suggestion for `top_metrics` agg (#103690)
* [ML] remove advanced settings
* [ML] fix getUpdatedItem for switching to single field agg
* [ML] incremental naming for top aggs
* [ML] set default sorting field based on date type
* [ML] set desc order by default
* [ML] fix TS
* [ML] change sorting direction init
---
.../transform/public/app/common/pivot_aggs.ts | 7 +-
.../aggregation_list/popover_form.tsx | 17 +++-
.../components/top_metrics_agg_form.tsx | 80 +------------------
.../common/top_metrics_agg/config.ts | 3 +
.../step_define/hooks/use_pivot_config.ts | 56 ++++++++++++-
5 files changed, 76 insertions(+), 87 deletions(-)
diff --git a/x-pack/plugins/transform/public/app/common/pivot_aggs.ts b/x-pack/plugins/transform/public/app/common/pivot_aggs.ts
index 97685096a5d223..6f3d3de79c3915 100644
--- a/x-pack/plugins/transform/public/app/common/pivot_aggs.ts
+++ b/x-pack/plugins/transform/public/app/common/pivot_aggs.ts
@@ -18,6 +18,7 @@ import { isPopulatedObject } from '../../../common/shared_imports';
import { getAggFormConfig } from '../sections/create_transform/components/step_define/common/get_agg_form_config';
import { PivotAggsConfigFilter } from '../sections/create_transform/components/step_define/common/filter_agg/types';
+import { PivotAggsConfigTopMetrics } from '../sections/create_transform/components/step_define/common/top_metrics_agg/types';
export function isPivotSupportedAggs(arg: unknown): arg is PivotSupportedAggs {
return (
@@ -240,12 +241,16 @@ export function isPivotAggsConfigWithUiSupport(arg: unknown): arg is PivotAggsCo
/**
* Union type for agg configs with extended forms
*/
-type PivotAggsConfigWithExtendedForm = PivotAggsConfigFilter;
+type PivotAggsConfigWithExtendedForm = PivotAggsConfigFilter | PivotAggsConfigTopMetrics;
export function isPivotAggsWithExtendedForm(arg: unknown): arg is PivotAggsConfigWithExtendedForm {
return isPopulatedObject(arg, ['AggFormComponent']);
}
+export function isPivotAggConfigTopMetric(arg: unknown): arg is PivotAggsConfigTopMetrics {
+ return isPivotAggsWithExtendedForm(arg) && arg.agg === PIVOT_SUPPORTED_AGGS.TOP_METRICS;
+}
+
export function isPivotAggsConfigPercentiles(arg: unknown): arg is PivotAggsConfigPercentiles {
return (
isPopulatedObject(arg, ['agg', 'field', 'percents']) &&
diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/aggregation_list/popover_form.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/aggregation_list/popover_form.tsx
index fd11255374a517..831ee17371910f 100644
--- a/x-pack/plugins/transform/public/app/sections/create_transform/components/aggregation_list/popover_form.tsx
+++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/aggregation_list/popover_form.tsx
@@ -126,19 +126,30 @@ export const PopoverForm: React.FC = ({ defaultData, otherAggNames, onCha
function getUpdatedItem(): PivotAggsConfig {
let updatedItem: PivotAggsConfig;
+
+ let resultField = field;
+ if (
+ isPivotAggsConfigWithUiSupport(aggConfigDef) &&
+ !aggConfigDef.isMultiField &&
+ Array.isArray(field)
+ ) {
+ // reset to a single field in case agg doesn't support multiple fields
+ resultField = field[0];
+ }
+
if (agg !== PIVOT_SUPPORTED_AGGS.PERCENTILES) {
updatedItem = {
...aggConfigDef,
agg,
aggName,
- field,
+ field: resultField,
dropDownName: defaultData.dropDownName,
};
} else {
updatedItem = {
agg,
aggName,
- field,
+ field: resultField,
dropDownName: defaultData.dropDownName,
percents,
};
@@ -286,7 +297,7 @@ export const PopoverForm: React.FC = ({ defaultData, otherAggNames, onCha
{
+ onChange={(update: typeof aggConfigDef.aggConfig) => {
setAggConfigDef({
...aggConfigDef,
aggConfig: update,
diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/top_metrics_agg/components/top_metrics_agg_form.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/top_metrics_agg/components/top_metrics_agg_form.tsx
index 0ec66a3d59a113..6af319274a5c20 100644
--- a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/top_metrics_agg/components/top_metrics_agg_form.tsx
+++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/top_metrics_agg/components/top_metrics_agg_form.tsx
@@ -8,18 +8,13 @@
import React, { useCallback, useContext } from 'react';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
-import { EuiFormRow, EuiSelect, EuiButtonGroup, EuiAccordion, EuiSpacer } from '@elastic/eui';
+import { EuiFormRow, EuiSelect, EuiButtonGroup, EuiSpacer } from '@elastic/eui';
import { PivotAggsConfigTopMetrics, TopMetricsAggConfig } from '../types';
import { PivotConfigurationContext } from '../../../../pivot_configuration/pivot_configuration';
import {
isSpecialSortField,
- KbnNumericType,
- NUMERIC_TYPES_OPTIONS,
SORT_DIRECTION,
- SORT_MODE,
SortDirection,
- SortMode,
- SortNumericFieldType,
TOP_METRICS_SORT_FIELD_TYPES,
TOP_METRICS_SPECIAL_SORT_FIELDS,
} from '../../../../../../../common/pivot_aggs';
@@ -48,13 +43,6 @@ export const TopMetricsAggForm: PivotAggsConfigTopMetrics['AggFormComponent'] =
label: v,
}));
- const sortModeOptions = Object.values(SORT_MODE).map((v) => ({
- id: v,
- label: v,
- }));
-
- const sortFieldType = fields.find((f) => f.name === aggConfig.sortField)?.type;
-
const sortSettings = aggConfig.sortSettings ?? {};
const updateSortSettings = useCallback(
@@ -120,72 +108,6 @@ export const TopMetricsAggForm: PivotAggsConfigTopMetrics['AggFormComponent'] =
-
-
- }
- >
-
- }
- helpText={
-
- }
- >
- {
- updateSortSettings({ mode: id as SortMode });
- }}
- color="text"
- />
-
-
- {sortFieldType && NUMERIC_TYPES_OPTIONS.hasOwnProperty(sortFieldType) ? (
-
- }
- >
- ({
- text: v,
- name: v,
- }))}
- value={sortSettings.numericType}
- onChange={(e) => {
- updateSortSettings({
- numericType: e.target.value as SortNumericFieldType,
- });
- }}
- data-test-subj="transformSortNumericTypeTopMetricsLabel"
- />
-
- ) : null}
-
>
)}
>
diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/top_metrics_agg/config.ts b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/top_metrics_agg/config.ts
index 56d17e7973e160..354a326f38659b 100644
--- a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/top_metrics_agg/config.ts
+++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/top_metrics_agg/config.ts
@@ -17,6 +17,7 @@ import {
import { PivotAggsConfigTopMetrics } from './types';
import { TopMetricsAggForm } from './components/top_metrics_agg_form';
import { isPopulatedObject } from '../../../../../../../../common/shared_imports';
+import { PIVOT_SUPPORTED_AGGS } from '../../../../../../../../common/types/pivot_aggs';
/**
* Gets initial basic configuration of the top_metrics aggregation.
@@ -30,6 +31,8 @@ export function getTopMetricsAggConfig(
isMultiField: true,
field: isPivotAggsConfigWithUiSupport(commonConfig) ? commonConfig.field : '',
AggFormComponent: TopMetricsAggForm,
+ /** Default name */
+ aggName: PIVOT_SUPPORTED_AGGS.TOP_METRICS,
aggConfig: {},
getEsAggConfig() {
// ensure the configuration has been completed
diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/hooks/use_pivot_config.ts b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/hooks/use_pivot_config.ts
index 0c31b4fe2da819..a291f648a835e5 100644
--- a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/hooks/use_pivot_config.ts
+++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/hooks/use_pivot_config.ts
@@ -13,8 +13,9 @@ import { dictionaryToArray } from '../../../../../../../common/types/common';
import { useToastNotifications } from '../../../../../app_dependencies';
import {
- getRequestPayload,
DropDownLabel,
+ getRequestPayload,
+ isPivotGroupByConfigWithUiSupport,
PivotAggsConfig,
PivotAggsConfigDict,
PivotGroupByConfig,
@@ -26,8 +27,14 @@ import {
StepDefineExposedState,
} from '../common';
import { StepDefineFormProps } from '../step_define_form';
-import { isPivotAggsWithExtendedForm } from '../../../../../common/pivot_aggs';
+import {
+ isPivotAggConfigTopMetric,
+ isPivotAggsWithExtendedForm,
+} from '../../../../../common/pivot_aggs';
import { TransformPivotConfig } from '../../../../../../../common/types/transform';
+import { PIVOT_SUPPORTED_AGGS } from '../../../../../../../common/types/pivot_aggs';
+import { KBN_FIELD_TYPES } from '../../../../../../../../../../src/plugins/data/common';
+import { isPivotAggConfigWithUiSupport } from '../../../../../common/pivot_group_by';
/**
* Clones aggregation configuration and updates parent references
@@ -165,7 +172,48 @@ export const usePivotConfig = (
(d: DropDownLabel[]) => {
const label: AggName = d[0].label;
const config: PivotAggsConfig = aggOptionsData[label];
- const aggName: AggName = config.aggName;
+
+ let aggName: AggName = config.aggName;
+
+ if (isPivotAggConfigTopMetric(config)) {
+ let suggestedSortField = [
+ ...new Set(
+ Object.values(groupByList).map((v) =>
+ isPivotGroupByConfigWithUiSupport(v) ? v.field : undefined
+ )
+ ),
+ ].find((v) => fields.find((x) => x.name === v)?.type === KBN_FIELD_TYPES.DATE);
+
+ if (!suggestedSortField) {
+ suggestedSortField = [
+ ...new Set(
+ Object.values(aggList)
+ .map((v) => (isPivotAggConfigWithUiSupport(v) ? v.field : undefined))
+ .flat()
+ ),
+ ].find((v) => fields.find((x) => x.name === v)?.type === KBN_FIELD_TYPES.DATE);
+ }
+
+ if (suggestedSortField) {
+ config.aggConfig.sortField = suggestedSortField;
+ config.aggConfig.sortSettings = {};
+ config.aggConfig.sortSettings.order = 'desc';
+ }
+ }
+
+ if (aggList[aggName] && aggName === PIVOT_SUPPORTED_AGGS.TOP_METRICS) {
+ // handle special case for naming top_metric aggs
+ const regExp = new RegExp(`^${PIVOT_SUPPORTED_AGGS.TOP_METRICS}(\\d)*$`);
+ const increment: number = Object.keys(aggList).reduce((acc, curr) => {
+ const match = curr.match(regExp);
+ if (!match || !match[1]) return acc;
+ const n = Number(match[1]);
+ return n > acc ? n : acc;
+ }, 0 as number);
+
+ aggName = `${PIVOT_SUPPORTED_AGGS.TOP_METRICS}${increment + 1}`;
+ config.aggName = aggName;
+ }
const aggNameConflictMessages = getAggNameConflictToastMessages(
aggName,
@@ -180,7 +228,7 @@ export const usePivotConfig = (
aggList[aggName] = config;
setAggList({ ...aggList });
},
- [aggList, aggOptionsData, groupByList, toastNotifications]
+ [aggList, aggOptionsData, groupByList, toastNotifications, fields]
);
/**
From 1d02a0d6c1f237320917bd0e3eefec0102269d64 Mon Sep 17 00:00:00 2001
From: Tim Roes
Date: Wed, 30 Jun 2021 13:37:16 +0200
Subject: [PATCH 047/121] Fix telemetry advanced setting style (#103838)
---
...telemetry_management_section.test.tsx.snap | 246 +++++++++---------
.../telemetry_management_section.tsx | 85 +++---
2 files changed, 160 insertions(+), 171 deletions(-)
diff --git a/src/plugins/telemetry_management_section/public/components/__snapshots__/telemetry_management_section.test.tsx.snap b/src/plugins/telemetry_management_section/public/components/__snapshots__/telemetry_management_section.test.tsx.snap
index c4cee25a33b7ff..014142a2a3d068 100644
--- a/src/plugins/telemetry_management_section/public/components/__snapshots__/telemetry_management_section.test.tsx.snap
+++ b/src/plugins/telemetry_management_section/public/components/__snapshots__/telemetry_management_section.test.tsx.snap
@@ -47,139 +47,137 @@ exports[`TelemetryManagementSectionComponent does not show the endpoint link whe
exports[`TelemetryManagementSectionComponent renders as expected 1`] = `
-
-
-
-
-
-
-
-
-
-
-
+ <_EuiSplitPanelInner
+ color="subdued"
+ >
+
+
-
- ,
- }
- }
+ defaultMessage="Usage Data"
+ id="telemetry.usageDataTitle"
+ values={Object {}}
/>
-
- }
- />
-
-
-
-
-
- ,
- }
+
+
+
+ <_EuiSplitPanelInner>
+
+
+
+ ,
}
- />
-
-
-
-
- ,
- "endpointSecurityData":
-
- ,
+ }
+ />
+
+ }
+ />
+
+
+
+
+
+ ,
+ }
}
- }
- />
-
- ,
- "displayName": "Provide usage statistics",
- "isCustom": true,
- "isOverridden": false,
- "name": "telemetry:enabled",
- "requiresPageReload": false,
- "type": "boolean",
- "value": true,
+ />
+
+
+
+
+ ,
+ "endpointSecurityData":
+
+ ,
+ }
+ }
+ />
+
+ ,
+ "displayName": "Provide usage statistics",
+ "isCustom": true,
+ "isOverridden": false,
+ "name": "telemetry:enabled",
+ "requiresPageReload": false,
+ "type": "boolean",
+ "value": true,
+ }
}
- }
- toasts={
- Object {
- "add": [MockFunction],
- "addDanger": [MockFunction],
- "addError": [MockFunction],
- "addInfo": [MockFunction],
- "addSuccess": [MockFunction],
- "addWarning": [MockFunction],
- "get$": [MockFunction],
- "remove": [MockFunction],
+ toasts={
+ Object {
+ "add": [MockFunction],
+ "addDanger": [MockFunction],
+ "addError": [MockFunction],
+ "addInfo": [MockFunction],
+ "addSuccess": [MockFunction],
+ "addWarning": [MockFunction],
+ "get$": [MockFunction],
+ "remove": [MockFunction],
+ }
}
- }
- />
+ />
+
-
+
`;
diff --git a/src/plugins/telemetry_management_section/public/components/telemetry_management_section.tsx b/src/plugins/telemetry_management_section/public/components/telemetry_management_section.tsx
index e9ddc4cf82dfc7..b0d1b42a9b8927 100644
--- a/src/plugins/telemetry_management_section/public/components/telemetry_management_section.tsx
+++ b/src/plugins/telemetry_management_section/public/components/telemetry_management_section.tsx
@@ -7,16 +7,7 @@
*/
import React, { Component, Fragment } from 'react';
-import {
- EuiCallOut,
- EuiPanel,
- EuiForm,
- EuiFlexGroup,
- EuiFlexItem,
- EuiLink,
- EuiSpacer,
- EuiText,
-} from '@elastic/eui';
+import { EuiCallOut, EuiForm, EuiLink, EuiSpacer, EuiSplitPanel, EuiTitle } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
import { i18n } from '@kbn/i18n';
@@ -116,46 +107,46 @@ export class TelemetryManagementSection extends Component {
)}
-
+
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
- {this.maybeGetAppliesSettingMessage()}
-
-
+
+ {this.maybeGetAppliesSettingMessage()}
+
+
+
-
+
);
}
From 0ef542041b175afb173b31e4c2c70a2fb27ff21d Mon Sep 17 00:00:00 2001
From: Dave Snider
Date: Wed, 30 Jun 2021 07:47:53 -0400
Subject: [PATCH 048/121] Remove add data button from nav (#103810)
* Remove add data button from nav
* remove unused imports
Co-authored-by: Michail Yasonik
---
.../collapsible_nav.test.tsx.snap | 89 -------------------
.../chrome/ui/header/collapsible_nav.tsx | 12 ---
2 files changed, 101 deletions(-)
diff --git a/src/core/public/chrome/ui/header/__snapshots__/collapsible_nav.test.tsx.snap b/src/core/public/chrome/ui/header/__snapshots__/collapsible_nav.test.tsx.snap
index a653fbc5e40bda..9e890f5bce4083 100644
--- a/src/core/public/chrome/ui/header/__snapshots__/collapsible_nav.test.tsx.snap
+++ b/src/core/public/chrome/ui/header/__snapshots__/collapsible_nav.test.tsx.snap
@@ -1917,95 +1917,6 @@ exports[`CollapsibleNav renders links grouped by category 1`] = `
/>
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Add Data
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/src/core/public/chrome/ui/header/collapsible_nav.tsx b/src/core/public/chrome/ui/header/collapsible_nav.tsx
index 2ce5cf2ee49336..eaddc6b94bd469 100644
--- a/src/core/public/chrome/ui/header/collapsible_nav.tsx
+++ b/src/core/public/chrome/ui/header/collapsible_nav.tsx
@@ -16,10 +16,8 @@ import {
EuiListGroupItem,
EuiShowFor,
EuiCollapsibleNavProps,
- EuiButton,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
-import { FormattedMessage } from '@kbn/i18n/react';
import { groupBy, sortBy } from 'lodash';
import React, { Fragment, useRef } from 'react';
import useObservable from 'react-use/lib/useObservable';
@@ -325,16 +323,6 @@ export function CollapsibleNav({
- {/* Quick addition of that "ADD DATA" button everyone wants :) Feel free to remove though. */}
-
- {/* Span fakes the nav group into not being the first item and therefore adding a top border */}
-
-
-
-
-
-
-
);
}
From 2bc801c8ef26c357a4be437852bd1fd6745724a4 Mon Sep 17 00:00:00 2001
From: Liza Katz
Date: Wed, 30 Jun 2021 13:07:27 +0100
Subject: [PATCH 049/121] [Query] Es query/field base (#103177)
* remove es query dependency on format.convert
* FieldBase
* fix types
* types
* Fix type imports
* test types
* fix jest
* rename
* docs
* comment
---
...na-plugin-plugins-data-public.esfilters.md | 8 ++--
...gin-plugins-data-public.ifieldtype.lang.md | 11 -----
...a-plugin-plugins-data-public.ifieldtype.md | 8 +---
...gin-plugins-data-public.ifieldtype.name.md | 11 -----
...n-plugins-data-public.ifieldtype.script.md | 11 -----
...plugins-data-public.ifieldtype.scripted.md | 11 -----
...-plugins-data-public.ifieldtype.subtype.md | 11 -----
...gin-plugins-data-public.ifieldtype.type.md | 11 -----
...lugins-data-public.iindexpattern.fields.md | 11 +++++
...lugin-plugins-data-public.iindexpattern.md | 1 +
...indexpattern.getaggregationrestrictions.md | 8 +++-
...n-plugins-data-public.indexpatternfield.md | 2 +-
...s-data-public.indexpatternfield.subtype.md | 2 +-
...ns-data-public.indexpatternfield.tojson.md | 4 +-
...na-plugin-plugins-data-server.esfilters.md | 8 ++--
...gin-plugins-data-server.ifieldtype.lang.md | 11 -----
...a-plugin-plugins-data-server.ifieldtype.md | 8 +---
...gin-plugins-data-server.ifieldtype.name.md | 11 -----
...n-plugins-data-server.ifieldtype.script.md | 11 -----
...plugins-data-server.ifieldtype.scripted.md | 11 -----
...-plugins-data-server.ifieldtype.subtype.md | 11 -----
...gin-plugins-data-server.ifieldtype.type.md | 11 -----
...indexpattern.getaggregationrestrictions.md | 8 +++-
.../es_query/es_query/filter_matches_index.ts | 3 +-
.../es_query/handle_nested_filter.test.ts | 9 ++--
.../data/common/es_query/es_query/index.ts | 2 +-
.../data/common/es_query/es_query/types.ts | 27 +++++++++++-
.../common/es_query/filters/build_filters.ts | 7 ++--
.../common/es_query/filters/exists_filter.ts | 5 +--
.../common/es_query/filters/phrase_filter.ts | 9 ++--
.../common/es_query/filters/phrases_filter.ts | 5 +--
.../es_query/filters/range_filter.test.ts | 14 +++----
.../common/es_query/filters/range_filter.ts | 11 +++--
.../common/es_query/kuery/functions/exists.ts | 7 ++--
.../kuery/functions/geo_bounding_box.ts | 6 +--
.../es_query/kuery/functions/geo_polygon.ts | 6 +--
.../common/es_query/kuery/functions/is.ts | 4 +-
.../common/es_query/kuery/functions/range.ts | 4 +-
.../kuery/functions/utils/get_fields.test.ts | 14 +++----
.../utils/get_full_field_name_node.ts | 4 +-
.../common/index_patterns/fields/types.ts | 11 +----
.../data/common/index_patterns/types.ts | 24 ++---------
src/plugins/data/public/public.api.md | 41 ++++++++-----------
src/plugins/data/server/server.api.md | 27 ++++--------
44 files changed, 148 insertions(+), 292 deletions(-)
delete mode 100644 docs/development/plugins/data/public/kibana-plugin-plugins-data-public.ifieldtype.lang.md
delete mode 100644 docs/development/plugins/data/public/kibana-plugin-plugins-data-public.ifieldtype.name.md
delete mode 100644 docs/development/plugins/data/public/kibana-plugin-plugins-data-public.ifieldtype.script.md
delete mode 100644 docs/development/plugins/data/public/kibana-plugin-plugins-data-public.ifieldtype.scripted.md
delete mode 100644 docs/development/plugins/data/public/kibana-plugin-plugins-data-public.ifieldtype.subtype.md
delete mode 100644 docs/development/plugins/data/public/kibana-plugin-plugins-data-public.ifieldtype.type.md
create mode 100644 docs/development/plugins/data/public/kibana-plugin-plugins-data-public.iindexpattern.fields.md
delete mode 100644 docs/development/plugins/data/server/kibana-plugin-plugins-data-server.ifieldtype.lang.md
delete mode 100644 docs/development/plugins/data/server/kibana-plugin-plugins-data-server.ifieldtype.name.md
delete mode 100644 docs/development/plugins/data/server/kibana-plugin-plugins-data-server.ifieldtype.script.md
delete mode 100644 docs/development/plugins/data/server/kibana-plugin-plugins-data-server.ifieldtype.scripted.md
delete mode 100644 docs/development/plugins/data/server/kibana-plugin-plugins-data-server.ifieldtype.subtype.md
delete mode 100644 docs/development/plugins/data/server/kibana-plugin-plugins-data-server.ifieldtype.type.md
diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.esfilters.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.esfilters.md
index 80c321ce6b3209..d06ce1b2ef2bc0 100644
--- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.esfilters.md
+++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.esfilters.md
@@ -13,11 +13,11 @@ esFilters: {
FILTERS: typeof FILTERS;
FilterStateStore: typeof FilterStateStore;
buildEmptyFilter: (isPinned: boolean, index?: string | undefined) => import("../common").Filter;
- buildPhrasesFilter: (field: import("../common").IFieldType, params: any[], indexPattern: import("../common").IndexPatternBase) => import("../common").PhrasesFilter;
- buildExistsFilter: (field: import("../common").IFieldType, indexPattern: import("../common").IndexPatternBase) => import("../common").ExistsFilter;
- buildPhraseFilter: (field: import("../common").IFieldType, value: any, indexPattern: import("../common").IndexPatternBase) => import("../common").PhraseFilter;
+ buildPhrasesFilter: (field: import("../common").IndexPatternFieldBase, params: any[], indexPattern: import("../common").IndexPatternBase) => import("../common").PhrasesFilter;
+ buildExistsFilter: (field: import("../common").IndexPatternFieldBase, indexPattern: import("../common").IndexPatternBase) => import("../common").ExistsFilter;
+ buildPhraseFilter: (field: import("../common").IndexPatternFieldBase, value: any, indexPattern: import("../common").IndexPatternBase) => import("../common").PhraseFilter;
buildQueryFilter: (query: any, index: string, alias: string) => import("../common").QueryStringFilter;
- buildRangeFilter: (field: import("../common").IFieldType, params: import("../common").RangeFilterParams, indexPattern: import("../common").IndexPatternBase, formattedValue?: string | undefined) => import("../common").RangeFilter;
+ buildRangeFilter: (field: import("../common").IndexPatternFieldBase, params: import("../common").RangeFilterParams, indexPattern: import("../common").IndexPatternBase, formattedValue?: string | undefined) => import("../common").RangeFilter;
isPhraseFilter: (filter: any) => filter is import("../common").PhraseFilter;
isExistsFilter: (filter: any) => filter is import("../common").ExistsFilter;
isPhrasesFilter: (filter: any) => filter is import("../common").PhrasesFilter;
diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.ifieldtype.lang.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.ifieldtype.lang.md
deleted file mode 100644
index f99e7ba8b967ec..00000000000000
--- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.ifieldtype.lang.md
+++ /dev/null
@@ -1,11 +0,0 @@
-
-
-[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [IFieldType](./kibana-plugin-plugins-data-public.ifieldtype.md) > [lang](./kibana-plugin-plugins-data-public.ifieldtype.lang.md)
-
-## IFieldType.lang property
-
-Signature:
-
-```typescript
-lang?: estypes.ScriptLanguage;
-```
diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.ifieldtype.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.ifieldtype.md
index 29377ff8fd392a..e1acea53ea5e01 100644
--- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.ifieldtype.md
+++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.ifieldtype.md
@@ -12,7 +12,7 @@
Signature:
```typescript
-export interface IFieldType
+export interface IFieldType extends IndexPatternFieldBase
```
## Properties
@@ -26,15 +26,9 @@ export interface IFieldType
| [esTypes](./kibana-plugin-plugins-data-public.ifieldtype.estypes.md) | string[]
| |
| [filterable](./kibana-plugin-plugins-data-public.ifieldtype.filterable.md) | boolean
| |
| [format](./kibana-plugin-plugins-data-public.ifieldtype.format.md) | any
| |
-| [lang](./kibana-plugin-plugins-data-public.ifieldtype.lang.md) | estypes.ScriptLanguage
| |
-| [name](./kibana-plugin-plugins-data-public.ifieldtype.name.md) | string
| |
| [readFromDocValues](./kibana-plugin-plugins-data-public.ifieldtype.readfromdocvalues.md) | boolean
| |
-| [script](./kibana-plugin-plugins-data-public.ifieldtype.script.md) | string
| |
-| [scripted](./kibana-plugin-plugins-data-public.ifieldtype.scripted.md) | boolean
| |
| [searchable](./kibana-plugin-plugins-data-public.ifieldtype.searchable.md) | boolean
| |
| [sortable](./kibana-plugin-plugins-data-public.ifieldtype.sortable.md) | boolean
| |
-| [subType](./kibana-plugin-plugins-data-public.ifieldtype.subtype.md) | IFieldSubType
| |
| [toSpec](./kibana-plugin-plugins-data-public.ifieldtype.tospec.md) | (options?: {
getFormatterForField?: IndexPattern['getFormatterForField'];
}) => FieldSpec
| |
-| [type](./kibana-plugin-plugins-data-public.ifieldtype.type.md) | string
| |
| [visualizable](./kibana-plugin-plugins-data-public.ifieldtype.visualizable.md) | boolean
| |
diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.ifieldtype.name.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.ifieldtype.name.md
deleted file mode 100644
index 1c01484372fd37..00000000000000
--- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.ifieldtype.name.md
+++ /dev/null
@@ -1,11 +0,0 @@
-
-
-[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [IFieldType](./kibana-plugin-plugins-data-public.ifieldtype.md) > [name](./kibana-plugin-plugins-data-public.ifieldtype.name.md)
-
-## IFieldType.name property
-
-Signature:
-
-```typescript
-name: string;
-```
diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.ifieldtype.script.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.ifieldtype.script.md
deleted file mode 100644
index 252c2c38220465..00000000000000
--- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.ifieldtype.script.md
+++ /dev/null
@@ -1,11 +0,0 @@
-
-
-[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [IFieldType](./kibana-plugin-plugins-data-public.ifieldtype.md) > [script](./kibana-plugin-plugins-data-public.ifieldtype.script.md)
-
-## IFieldType.script property
-
-Signature:
-
-```typescript
-script?: string;
-```
diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.ifieldtype.scripted.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.ifieldtype.scripted.md
deleted file mode 100644
index 33bbd0c2c20cb8..00000000000000
--- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.ifieldtype.scripted.md
+++ /dev/null
@@ -1,11 +0,0 @@
-
-
-[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [IFieldType](./kibana-plugin-plugins-data-public.ifieldtype.md) > [scripted](./kibana-plugin-plugins-data-public.ifieldtype.scripted.md)
-
-## IFieldType.scripted property
-
-Signature:
-
-```typescript
-scripted?: boolean;
-```
diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.ifieldtype.subtype.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.ifieldtype.subtype.md
deleted file mode 100644
index d0c26186da0857..00000000000000
--- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.ifieldtype.subtype.md
+++ /dev/null
@@ -1,11 +0,0 @@
-
-
-[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [IFieldType](./kibana-plugin-plugins-data-public.ifieldtype.md) > [subType](./kibana-plugin-plugins-data-public.ifieldtype.subtype.md)
-
-## IFieldType.subType property
-
-Signature:
-
-```typescript
-subType?: IFieldSubType;
-```
diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.ifieldtype.type.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.ifieldtype.type.md
deleted file mode 100644
index 26228cbe4bfdb1..00000000000000
--- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.ifieldtype.type.md
+++ /dev/null
@@ -1,11 +0,0 @@
-
-
-[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [IFieldType](./kibana-plugin-plugins-data-public.ifieldtype.md) > [type](./kibana-plugin-plugins-data-public.ifieldtype.type.md)
-
-## IFieldType.type property
-
-Signature:
-
-```typescript
-type: string;
-```
diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.iindexpattern.fields.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.iindexpattern.fields.md
new file mode 100644
index 00000000000000..792bee44f96a85
--- /dev/null
+++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.iindexpattern.fields.md
@@ -0,0 +1,11 @@
+
+
+[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [IIndexPattern](./kibana-plugin-plugins-data-public.iindexpattern.md) > [fields](./kibana-plugin-plugins-data-public.iindexpattern.fields.md)
+
+## IIndexPattern.fields property
+
+Signature:
+
+```typescript
+fields: IFieldType[];
+```
diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.iindexpattern.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.iindexpattern.md
index ec29ef81a6e69b..c4410737811697 100644
--- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.iindexpattern.md
+++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.iindexpattern.md
@@ -20,6 +20,7 @@ export interface IIndexPattern extends IndexPatternBase
| Property | Type | Description |
| --- | --- | --- |
| [fieldFormatMap](./kibana-plugin-plugins-data-public.iindexpattern.fieldformatmap.md) | Record<string, SerializedFieldFormat<unknown> | undefined>
| |
+| [fields](./kibana-plugin-plugins-data-public.iindexpattern.fields.md) | IFieldType[]
| |
| [getFormatterForField](./kibana-plugin-plugins-data-public.iindexpattern.getformatterforfield.md) | (field: IndexPatternField | IndexPatternField['spec'] | IFieldType) => FieldFormat
| Look up a formatter for a given field |
| [timeFieldName](./kibana-plugin-plugins-data-public.iindexpattern.timefieldname.md) | string
| |
| [title](./kibana-plugin-plugins-data-public.iindexpattern.title.md) | string
| |
diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.getaggregationrestrictions.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.getaggregationrestrictions.md
index e42980bb53af48..1bbe0b594ecf01 100644
--- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.getaggregationrestrictions.md
+++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.getaggregationrestrictions.md
@@ -9,7 +9,9 @@
```typescript
getAggregationRestrictions(): Recordboolean | |
| [sortable](./kibana-plugin-plugins-data-public.indexpatternfield.sortable.md) | | boolean
| |
| [spec](./kibana-plugin-plugins-data-public.indexpatternfield.spec.md) | | FieldSpec
| |
-| [subType](./kibana-plugin-plugins-data-public.indexpatternfield.subtype.md) | | import("../types").IFieldSubType | undefined
| |
+| [subType](./kibana-plugin-plugins-data-public.indexpatternfield.subtype.md) | | import("../..").IFieldSubType | undefined
| |
| [type](./kibana-plugin-plugins-data-public.indexpatternfield.type.md) | | string
| |
| [visualizable](./kibana-plugin-plugins-data-public.indexpatternfield.visualizable.md) | | boolean
| |
diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfield.subtype.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfield.subtype.md
index 5c3c4d54ad0997..6cd5247291602d 100644
--- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfield.subtype.md
+++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfield.subtype.md
@@ -7,5 +7,5 @@
Signature:
```typescript
-get subType(): import("../types").IFieldSubType | undefined;
+get subType(): import("../..").IFieldSubType | undefined;
```
diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfield.tojson.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfield.tojson.md
index 8882fa05ce0c25..b77f3d1f374fbf 100644
--- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfield.tojson.md
+++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfield.tojson.md
@@ -19,7 +19,7 @@ toJSON(): {
searchable: boolean;
aggregatable: boolean;
readFromDocValues: boolean;
- subType: import("../types").IFieldSubType | undefined;
+ subType: import("../..").IFieldSubType | undefined;
customLabel: string | undefined;
};
```
@@ -37,7 +37,7 @@ toJSON(): {
searchable: boolean;
aggregatable: boolean;
readFromDocValues: boolean;
- subType: import("../types").IFieldSubType | undefined;
+ subType: import("../..").IFieldSubType | undefined;
customLabel: string | undefined;
}`
diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.esfilters.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.esfilters.md
index d009cad9ec601c..594afcf9ee0ddd 100644
--- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.esfilters.md
+++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.esfilters.md
@@ -11,11 +11,11 @@ esFilters: {
buildQueryFilter: (query: any, index: string, alias: string) => import("../common").QueryStringFilter;
buildCustomFilter: typeof buildCustomFilter;
buildEmptyFilter: (isPinned: boolean, index?: string | undefined) => import("../common").Filter;
- buildExistsFilter: (field: import("../common").IFieldType, indexPattern: import("../common").IndexPatternBase) => import("../common").ExistsFilter;
+ buildExistsFilter: (field: import("../common").IndexPatternFieldBase, indexPattern: import("../common").IndexPatternBase) => import("../common").ExistsFilter;
buildFilter: typeof buildFilter;
- buildPhraseFilter: (field: import("../common").IFieldType, value: any, indexPattern: import("../common").IndexPatternBase) => import("../common").PhraseFilter;
- buildPhrasesFilter: (field: import("../common").IFieldType, params: any[], indexPattern: import("../common").IndexPatternBase) => import("../common").PhrasesFilter;
- buildRangeFilter: (field: import("../common").IFieldType, params: import("../common").RangeFilterParams, indexPattern: import("../common").IndexPatternBase, formattedValue?: string | undefined) => import("../common").RangeFilter;
+ buildPhraseFilter: (field: import("../common").IndexPatternFieldBase, value: any, indexPattern: import("../common").IndexPatternBase) => import("../common").PhraseFilter;
+ buildPhrasesFilter: (field: import("../common").IndexPatternFieldBase, params: any[], indexPattern: import("../common").IndexPatternBase) => import("../common").PhrasesFilter;
+ buildRangeFilter: (field: import("../common").IndexPatternFieldBase, params: import("../common").RangeFilterParams, indexPattern: import("../common").IndexPatternBase, formattedValue?: string | undefined) => import("../common").RangeFilter;
isFilterDisabled: (filter: import("../common").Filter) => boolean;
}
```
diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.ifieldtype.lang.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.ifieldtype.lang.md
deleted file mode 100644
index 3d5a757cb8f187..00000000000000
--- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.ifieldtype.lang.md
+++ /dev/null
@@ -1,11 +0,0 @@
-
-
-[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IFieldType](./kibana-plugin-plugins-data-server.ifieldtype.md) > [lang](./kibana-plugin-plugins-data-server.ifieldtype.lang.md)
-
-## IFieldType.lang property
-
-Signature:
-
-```typescript
-lang?: estypes.ScriptLanguage;
-```
diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.ifieldtype.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.ifieldtype.md
index bbc4cc2135d406..9f14bedf920087 100644
--- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.ifieldtype.md
+++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.ifieldtype.md
@@ -12,7 +12,7 @@
Signature:
```typescript
-export interface IFieldType
+export interface IFieldType extends IndexPatternFieldBase
```
## Properties
@@ -26,15 +26,9 @@ export interface IFieldType
| [esTypes](./kibana-plugin-plugins-data-server.ifieldtype.estypes.md) | string[]
| |
| [filterable](./kibana-plugin-plugins-data-server.ifieldtype.filterable.md) | boolean
| |
| [format](./kibana-plugin-plugins-data-server.ifieldtype.format.md) | any
| |
-| [lang](./kibana-plugin-plugins-data-server.ifieldtype.lang.md) | estypes.ScriptLanguage
| |
-| [name](./kibana-plugin-plugins-data-server.ifieldtype.name.md) | string
| |
| [readFromDocValues](./kibana-plugin-plugins-data-server.ifieldtype.readfromdocvalues.md) | boolean
| |
-| [script](./kibana-plugin-plugins-data-server.ifieldtype.script.md) | string
| |
-| [scripted](./kibana-plugin-plugins-data-server.ifieldtype.scripted.md) | boolean
| |
| [searchable](./kibana-plugin-plugins-data-server.ifieldtype.searchable.md) | boolean
| |
| [sortable](./kibana-plugin-plugins-data-server.ifieldtype.sortable.md) | boolean
| |
-| [subType](./kibana-plugin-plugins-data-server.ifieldtype.subtype.md) | IFieldSubType
| |
| [toSpec](./kibana-plugin-plugins-data-server.ifieldtype.tospec.md) | (options?: {
getFormatterForField?: IndexPattern['getFormatterForField'];
}) => FieldSpec
| |
-| [type](./kibana-plugin-plugins-data-server.ifieldtype.type.md) | string
| |
| [visualizable](./kibana-plugin-plugins-data-server.ifieldtype.visualizable.md) | boolean
| |
diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.ifieldtype.name.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.ifieldtype.name.md
deleted file mode 100644
index 8be33a3f56d976..00000000000000
--- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.ifieldtype.name.md
+++ /dev/null
@@ -1,11 +0,0 @@
-
-
-[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IFieldType](./kibana-plugin-plugins-data-server.ifieldtype.md) > [name](./kibana-plugin-plugins-data-server.ifieldtype.name.md)
-
-## IFieldType.name property
-
-Signature:
-
-```typescript
-name: string;
-```
diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.ifieldtype.script.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.ifieldtype.script.md
deleted file mode 100644
index b54a952a112531..00000000000000
--- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.ifieldtype.script.md
+++ /dev/null
@@ -1,11 +0,0 @@
-
-
-[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IFieldType](./kibana-plugin-plugins-data-server.ifieldtype.md) > [script](./kibana-plugin-plugins-data-server.ifieldtype.script.md)
-
-## IFieldType.script property
-
-Signature:
-
-```typescript
-script?: string;
-```
diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.ifieldtype.scripted.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.ifieldtype.scripted.md
deleted file mode 100644
index f7a8ed9aee0df4..00000000000000
--- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.ifieldtype.scripted.md
+++ /dev/null
@@ -1,11 +0,0 @@
-
-
-[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IFieldType](./kibana-plugin-plugins-data-server.ifieldtype.md) > [scripted](./kibana-plugin-plugins-data-server.ifieldtype.scripted.md)
-
-## IFieldType.scripted property
-
-Signature:
-
-```typescript
-scripted?: boolean;
-```
diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.ifieldtype.subtype.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.ifieldtype.subtype.md
deleted file mode 100644
index fa78b23a2b558c..00000000000000
--- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.ifieldtype.subtype.md
+++ /dev/null
@@ -1,11 +0,0 @@
-
-
-[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IFieldType](./kibana-plugin-plugins-data-server.ifieldtype.md) > [subType](./kibana-plugin-plugins-data-server.ifieldtype.subtype.md)
-
-## IFieldType.subType property
-
-Signature:
-
-```typescript
-subType?: IFieldSubType;
-```
diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.ifieldtype.type.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.ifieldtype.type.md
deleted file mode 100644
index ef6a4dcc167c53..00000000000000
--- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.ifieldtype.type.md
+++ /dev/null
@@ -1,11 +0,0 @@
-
-
-[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IFieldType](./kibana-plugin-plugins-data-server.ifieldtype.md) > [type](./kibana-plugin-plugins-data-server.ifieldtype.type.md)
-
-## IFieldType.type property
-
-Signature:
-
-```typescript
-type: string;
-```
diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.getaggregationrestrictions.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.getaggregationrestrictions.md
index b655e779e4fa4d..70a3da86e9fbd3 100644
--- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.getaggregationrestrictions.md
+++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.getaggregationrestrictions.md
@@ -9,7 +9,9 @@
```typescript
getAggregationRestrictions(): Record field.name === filter.meta.key);
+ return indexPattern.fields.some((field) => field.name === filter.meta.key);
}
diff --git a/src/plugins/data/common/es_query/es_query/handle_nested_filter.test.ts b/src/plugins/data/common/es_query/es_query/handle_nested_filter.test.ts
index d312d034df5641..24852ebf33bda3 100644
--- a/src/plugins/data/common/es_query/es_query/handle_nested_filter.test.ts
+++ b/src/plugins/data/common/es_query/es_query/handle_nested_filter.test.ts
@@ -10,7 +10,6 @@ import { handleNestedFilter } from './handle_nested_filter';
import { fields } from '../../index_patterns/mocks';
import { buildPhraseFilter, buildQueryFilter } from '../filters';
import { IndexPatternBase } from './types';
-import { IFieldType } from '../../index_patterns';
describe('handleNestedFilter', function () {
const indexPattern: IndexPatternBase = {
@@ -45,8 +44,12 @@ describe('handleNestedFilter', function () {
});
it('should return filter untouched if it does not target a field from the given index pattern', () => {
- const field = { ...getField('extension'), name: 'notarealfield' };
- const filter = buildPhraseFilter(field as IFieldType, 'jpg', indexPattern);
+ const field = getField('extension');
+ const unrealField = {
+ ...field!,
+ name: 'notarealfield',
+ };
+ const filter = buildPhraseFilter(unrealField, 'jpg', indexPattern);
const result = handleNestedFilter(filter, indexPattern);
expect(result).toBe(filter);
});
diff --git a/src/plugins/data/common/es_query/es_query/index.ts b/src/plugins/data/common/es_query/es_query/index.ts
index c10ea5846ae3fd..ecc7c8ba5a9f5a 100644
--- a/src/plugins/data/common/es_query/es_query/index.ts
+++ b/src/plugins/data/common/es_query/es_query/index.ts
@@ -11,4 +11,4 @@ export { buildQueryFromFilters } from './from_filters';
export { luceneStringToDsl } from './lucene_string_to_dsl';
export { decorateQuery } from './decorate_query';
export { getEsQueryConfig } from './get_es_query_config';
-export { IndexPatternBase } from './types';
+export { IndexPatternBase, IndexPatternFieldBase, IFieldSubType } from './types';
diff --git a/src/plugins/data/common/es_query/es_query/types.ts b/src/plugins/data/common/es_query/es_query/types.ts
index 21337365160491..9282072cd444d4 100644
--- a/src/plugins/data/common/es_query/es_query/types.ts
+++ b/src/plugins/data/common/es_query/es_query/types.ts
@@ -6,9 +6,32 @@
* Side Public License, v 1.
*/
-import { IFieldType } from '../../index_patterns';
+import type { estypes } from '@elastic/elasticsearch';
+
+export interface IFieldSubType {
+ multi?: { parent: string };
+ nested?: { path: string };
+}
+export interface IndexPatternFieldBase {
+ name: string;
+ /**
+ * Kibana field type
+ */
+ type: string;
+ subType?: IFieldSubType;
+ /**
+ * Scripted field painless script
+ */
+ script?: string;
+ /**
+ * Scripted field langauge
+ * Painless is the only valid scripted field language
+ */
+ lang?: estypes.ScriptLanguage;
+ scripted?: boolean;
+}
export interface IndexPatternBase {
- fields: IFieldType[];
+ fields: IndexPatternFieldBase[];
id?: string;
}
diff --git a/src/plugins/data/common/es_query/filters/build_filters.ts b/src/plugins/data/common/es_query/filters/build_filters.ts
index 369f9530fb92b2..1d8d67b6e937fa 100644
--- a/src/plugins/data/common/es_query/filters/build_filters.ts
+++ b/src/plugins/data/common/es_query/filters/build_filters.ts
@@ -6,7 +6,8 @@
* Side Public License, v 1.
*/
-import { IFieldType, IndexPatternBase } from '../..';
+import { IndexPatternFieldBase, IndexPatternBase } from '../..';
+
import {
Filter,
FILTERS,
@@ -20,7 +21,7 @@ import {
export function buildFilter(
indexPattern: IndexPatternBase,
- field: IFieldType,
+ field: IndexPatternFieldBase,
type: FILTERS,
negate: boolean,
disabled: boolean,
@@ -60,7 +61,7 @@ export function buildCustomFilter(
function buildBaseFilter(
indexPattern: IndexPatternBase,
- field: IFieldType,
+ field: IndexPatternFieldBase,
type: FILTERS,
params: any
): Filter {
diff --git a/src/plugins/data/common/es_query/filters/exists_filter.ts b/src/plugins/data/common/es_query/filters/exists_filter.ts
index 4836950c3bb277..7a09adb7d9ed6f 100644
--- a/src/plugins/data/common/es_query/filters/exists_filter.ts
+++ b/src/plugins/data/common/es_query/filters/exists_filter.ts
@@ -7,8 +7,7 @@
*/
import { Filter, FilterMeta } from './meta_filter';
-import { IFieldType } from '../../index_patterns';
-import { IndexPatternBase } from '..';
+import { IndexPatternFieldBase, IndexPatternBase } from '..';
export type ExistsFilterMeta = FilterMeta;
@@ -27,7 +26,7 @@ export const getExistsFilterField = (filter: ExistsFilter) => {
return filter.exists && filter.exists.field;
};
-export const buildExistsFilter = (field: IFieldType, indexPattern: IndexPatternBase) => {
+export const buildExistsFilter = (field: IndexPatternFieldBase, indexPattern: IndexPatternBase) => {
return {
meta: {
index: indexPattern.id,
diff --git a/src/plugins/data/common/es_query/filters/phrase_filter.ts b/src/plugins/data/common/es_query/filters/phrase_filter.ts
index 27c1e85562097c..68ad16cb31d429 100644
--- a/src/plugins/data/common/es_query/filters/phrase_filter.ts
+++ b/src/plugins/data/common/es_query/filters/phrase_filter.ts
@@ -8,8 +8,7 @@
import type { estypes } from '@elastic/elasticsearch';
import { get, isPlainObject } from 'lodash';
import { Filter, FilterMeta } from './meta_filter';
-import { IFieldType } from '../../index_patterns';
-import { IndexPatternBase } from '..';
+import { IndexPatternFieldBase, IndexPatternBase } from '..';
export type PhraseFilterMeta = FilterMeta & {
params?: {
@@ -59,7 +58,7 @@ export const getPhraseFilterValue = (filter: PhraseFilter): PhraseFilterValue =>
};
export const buildPhraseFilter = (
- field: IFieldType,
+ field: IndexPatternFieldBase,
value: any,
indexPattern: IndexPatternBase
): PhraseFilter => {
@@ -82,7 +81,7 @@ export const buildPhraseFilter = (
}
};
-export const getPhraseScript = (field: IFieldType, value: string) => {
+export const getPhraseScript = (field: IndexPatternFieldBase, value: string) => {
const convertedValue = getConvertedValueForField(field, value);
const script = buildInlineScriptForPhraseFilter(field);
@@ -106,7 +105,7 @@ export const getPhraseScript = (field: IFieldType, value: string) => {
* https://github.com/elastic/elasticsearch/issues/20941
* https://github.com/elastic/elasticsearch/pull/22201
**/
-export const getConvertedValueForField = (field: IFieldType, value: any) => {
+export const getConvertedValueForField = (field: IndexPatternFieldBase, value: any) => {
if (typeof value !== 'boolean' && field.type === 'boolean') {
if ([1, 'true'].includes(value)) {
return true;
diff --git a/src/plugins/data/common/es_query/filters/phrases_filter.ts b/src/plugins/data/common/es_query/filters/phrases_filter.ts
index 2694461fc19300..7f7831e1c7978e 100644
--- a/src/plugins/data/common/es_query/filters/phrases_filter.ts
+++ b/src/plugins/data/common/es_query/filters/phrases_filter.ts
@@ -9,8 +9,7 @@
import { Filter, FilterMeta } from './meta_filter';
import { getPhraseScript } from './phrase_filter';
import { FILTERS } from './index';
-import { IFieldType } from '../../index_patterns';
-import { IndexPatternBase } from '../es_query';
+import { IndexPatternFieldBase, IndexPatternBase } from '../es_query';
export type PhrasesFilterMeta = FilterMeta & {
params: string[]; // The unformatted values
@@ -33,7 +32,7 @@ export const getPhrasesFilterField = (filter: PhrasesFilter) => {
// Creates a filter where the given field matches one or more of the given values
// params should be an array of values
export const buildPhrasesFilter = (
- field: IFieldType,
+ field: IndexPatternFieldBase,
params: any[],
indexPattern: IndexPatternBase
) => {
diff --git a/src/plugins/data/common/es_query/filters/range_filter.test.ts b/src/plugins/data/common/es_query/filters/range_filter.test.ts
index bb7ecc09ebc349..30e52b21d52b75 100644
--- a/src/plugins/data/common/es_query/filters/range_filter.test.ts
+++ b/src/plugins/data/common/es_query/filters/range_filter.test.ts
@@ -9,15 +9,15 @@
import { each } from 'lodash';
import { buildRangeFilter, getRangeFilterField, RangeFilter } from './range_filter';
import { fields, getField } from '../../index_patterns/mocks';
-import { IIndexPattern, IFieldType } from '../../index_patterns';
+import { IndexPatternBase, IndexPatternFieldBase } from '../es_query';
describe('Range filter builder', () => {
- let indexPattern: IIndexPattern;
+ let indexPattern: IndexPatternBase;
beforeEach(() => {
indexPattern = {
id: 'id',
- } as IIndexPattern;
+ } as IndexPatternBase;
});
it('should be a function', () => {
@@ -130,7 +130,7 @@ describe('Range filter builder', () => {
});
describe('when given params where one side is infinite', () => {
- let field: IFieldType;
+ let field: IndexPatternFieldBase;
let filter: RangeFilter;
beforeEach(() => {
@@ -160,7 +160,7 @@ describe('Range filter builder', () => {
});
describe('when given params where both sides are infinite', () => {
- let field: IFieldType;
+ let field: IndexPatternFieldBase;
let filter: RangeFilter;
beforeEach(() => {
@@ -186,9 +186,9 @@ describe('Range filter builder', () => {
});
describe('getRangeFilterField', function () {
- const indexPattern: IIndexPattern = ({
+ const indexPattern: IndexPatternBase = ({
fields,
- } as unknown) as IIndexPattern;
+ } as unknown) as IndexPatternBase;
test('should return the name of the field a range query is targeting', () => {
const field = indexPattern.fields.find((patternField) => patternField.name === 'bytes');
diff --git a/src/plugins/data/common/es_query/filters/range_filter.ts b/src/plugins/data/common/es_query/filters/range_filter.ts
index 9f1d9a5d089264..e44e23f64936e1 100644
--- a/src/plugins/data/common/es_query/filters/range_filter.ts
+++ b/src/plugins/data/common/es_query/filters/range_filter.ts
@@ -8,8 +8,7 @@
import type { estypes } from '@elastic/elasticsearch';
import { map, reduce, mapValues, get, keys, pickBy } from 'lodash';
import { Filter, FilterMeta } from './meta_filter';
-import { IFieldType } from '../../index_patterns';
-import { IndexPatternBase } from '..';
+import { IndexPatternBase, IndexPatternFieldBase } from '..';
const OPERANDS_IN_RANGE = 2;
@@ -83,13 +82,13 @@ export const getRangeFilterField = (filter: RangeFilter) => {
return filter.range && Object.keys(filter.range)[0];
};
-const formatValue = (field: IFieldType, params: any[]) =>
+const formatValue = (params: any[]) =>
map(params, (val: any, key: string) => get(operators, key) + val).join(' ');
// Creates a filter where the value for the given field is in the given range
// params should be an object containing `lt`, `lte`, `gt`, and/or `gte`
export const buildRangeFilter = (
- field: IFieldType,
+ field: IndexPatternFieldBase,
params: RangeFilterParams,
indexPattern: IndexPatternBase,
formattedValue?: string
@@ -124,7 +123,7 @@ export const buildRangeFilter = (
filter.meta.field = field.name;
} else if (field.scripted) {
filter.script = getRangeScript(field, params);
- filter.script.script.params.value = formatValue(field, filter.script.script.params);
+ filter.script.script.params.value = formatValue(filter.script.script.params);
filter.meta.field = field.name;
} else {
@@ -135,7 +134,7 @@ export const buildRangeFilter = (
return filter as RangeFilter;
};
-export const getRangeScript = (field: IFieldType, params: RangeFilterParams) => {
+export const getRangeScript = (field: IndexPatternFieldBase, params: RangeFilterParams) => {
const knownParams = mapValues(
pickBy(params, (val, key: any) => key in operators),
(value) => (field.type === 'number' && typeof value === 'string' ? parseFloat(value) : value)
diff --git a/src/plugins/data/common/es_query/kuery/functions/exists.ts b/src/plugins/data/common/es_query/kuery/functions/exists.ts
index fa6c37e6ba18f7..4df566d874d8b5 100644
--- a/src/plugins/data/common/es_query/kuery/functions/exists.ts
+++ b/src/plugins/data/common/es_query/kuery/functions/exists.ts
@@ -6,9 +6,8 @@
* Side Public License, v 1.
*/
-import { get } from 'lodash';
import * as literal from '../node_types/literal';
-import { KueryNode, IFieldType, IndexPatternBase } from '../../..';
+import { KueryNode, IndexPatternFieldBase, IndexPatternBase } from '../../..';
export function buildNodeParams(fieldName: string) {
return {
@@ -30,9 +29,9 @@ export function toElasticsearchQuery(
value: context?.nested ? `${context.nested.path}.${fieldNameArg.value}` : fieldNameArg.value,
};
const fieldName = literal.toElasticsearchQuery(fullFieldNameArg);
- const field = get(indexPattern, 'fields', []).find((fld: IFieldType) => fld.name === fieldName);
+ const field = indexPattern?.fields?.find((fld: IndexPatternFieldBase) => fld.name === fieldName);
- if (field && (field as IFieldType).scripted) {
+ if (field?.scripted) {
throw new Error(`Exists query does not support scripted fields`);
}
return {
diff --git a/src/plugins/data/common/es_query/kuery/functions/geo_bounding_box.ts b/src/plugins/data/common/es_query/kuery/functions/geo_bounding_box.ts
index 38a433b1b80ab0..79bef10b14f71f 100644
--- a/src/plugins/data/common/es_query/kuery/functions/geo_bounding_box.ts
+++ b/src/plugins/data/common/es_query/kuery/functions/geo_bounding_box.ts
@@ -9,7 +9,7 @@
import _ from 'lodash';
import { nodeTypes } from '../node_types';
import * as ast from '../ast';
-import { IndexPatternBase, KueryNode, IFieldType, LatLon } from '../../..';
+import { IndexPatternBase, KueryNode, LatLon } from '../../..';
export function buildNodeParams(fieldName: string, params: any) {
params = _.pick(params, 'topLeft', 'bottomRight');
@@ -36,8 +36,8 @@ export function toElasticsearchQuery(
value: context?.nested ? `${context.nested.path}.${fieldNameArg.value}` : fieldNameArg.value,
};
const fieldName = nodeTypes.literal.toElasticsearchQuery(fullFieldNameArg) as string;
- const fieldList: IFieldType[] = indexPattern?.fields ?? [];
- const field = fieldList.find((fld: IFieldType) => fld.name === fieldName);
+ const fieldList = indexPattern?.fields ?? [];
+ const field = fieldList.find((fld) => fld.name === fieldName);
const queryParams = args.reduce((acc: any, arg: any) => {
const snakeArgName = _.snakeCase(arg.name);
diff --git a/src/plugins/data/common/es_query/kuery/functions/geo_polygon.ts b/src/plugins/data/common/es_query/kuery/functions/geo_polygon.ts
index 69de7248a7b380..2e3280138502a7 100644
--- a/src/plugins/data/common/es_query/kuery/functions/geo_polygon.ts
+++ b/src/plugins/data/common/es_query/kuery/functions/geo_polygon.ts
@@ -8,7 +8,7 @@
import { nodeTypes } from '../node_types';
import * as ast from '../ast';
-import { IndexPatternBase, KueryNode, IFieldType, LatLon } from '../../..';
+import { IndexPatternBase, KueryNode, LatLon } from '../../..';
import { LiteralTypeBuildNode } from '../node_types/types';
export function buildNodeParams(fieldName: string, points: LatLon[]) {
@@ -35,8 +35,8 @@ export function toElasticsearchQuery(
value: context?.nested ? `${context.nested.path}.${fieldNameArg.value}` : fieldNameArg.value,
};
const fieldName = nodeTypes.literal.toElasticsearchQuery(fullFieldNameArg) as string;
- const fieldList: IFieldType[] = indexPattern?.fields ?? [];
- const field = fieldList.find((fld: IFieldType) => fld.name === fieldName);
+ const fieldList = indexPattern?.fields ?? [];
+ const field = fieldList.find((fld) => fld.name === fieldName);
const queryParams = {
points: points.map((point: LiteralTypeBuildNode) => {
return ast.toElasticsearchQuery(point, indexPattern, config, context);
diff --git a/src/plugins/data/common/es_query/kuery/functions/is.ts b/src/plugins/data/common/es_query/kuery/functions/is.ts
index 55d036c2156f9b..381913670c26aa 100644
--- a/src/plugins/data/common/es_query/kuery/functions/is.ts
+++ b/src/plugins/data/common/es_query/kuery/functions/is.ts
@@ -11,7 +11,7 @@ import { getPhraseScript } from '../../filters';
import { getFields } from './utils/get_fields';
import { getTimeZoneFromSettings } from '../../utils';
import { getFullFieldNameNode } from './utils/get_full_field_name_node';
-import { IndexPatternBase, KueryNode, IFieldType } from '../../..';
+import { IndexPatternBase, KueryNode, IndexPatternFieldBase } from '../../..';
import * as ast from '../ast';
@@ -100,7 +100,7 @@ export function toElasticsearchQuery(
return { match_all: {} };
}
- const queries = fields!.reduce((accumulator: any, field: IFieldType) => {
+ const queries = fields!.reduce((accumulator: any, field: IndexPatternFieldBase) => {
const wrapWithNestedQuery = (query: any) => {
// Wildcards can easily include nested and non-nested fields. There isn't a good way to let
// users handle this themselves so we automatically add nested queries in this scenario.
diff --git a/src/plugins/data/common/es_query/kuery/functions/range.ts b/src/plugins/data/common/es_query/kuery/functions/range.ts
index caefa7e5373cac..b134434dc182b6 100644
--- a/src/plugins/data/common/es_query/kuery/functions/range.ts
+++ b/src/plugins/data/common/es_query/kuery/functions/range.ts
@@ -13,7 +13,7 @@ import { getRangeScript, RangeFilterParams } from '../../filters';
import { getFields } from './utils/get_fields';
import { getTimeZoneFromSettings } from '../../utils';
import { getFullFieldNameNode } from './utils/get_full_field_name_node';
-import { IndexPatternBase, KueryNode, IFieldType } from '../../..';
+import { IndexPatternBase, KueryNode } from '../../..';
export function buildNodeParams(fieldName: string, params: RangeFilterParams) {
const paramsToMap = _.pick(params, 'gt', 'lt', 'gte', 'lte', 'format');
@@ -62,7 +62,7 @@ export function toElasticsearchQuery(
});
}
- const queries = fields!.map((field: IFieldType) => {
+ const queries = fields!.map((field) => {
const wrapWithNestedQuery = (query: any) => {
// Wildcards can easily include nested and non-nested fields. There isn't a good way to let
// users handle this themselves so we automatically add nested queries in this scenario.
diff --git a/src/plugins/data/common/es_query/kuery/functions/utils/get_fields.test.ts b/src/plugins/data/common/es_query/kuery/functions/utils/get_fields.test.ts
index 47fe677454cbfc..949f94d0435539 100644
--- a/src/plugins/data/common/es_query/kuery/functions/utils/get_fields.test.ts
+++ b/src/plugins/data/common/es_query/kuery/functions/utils/get_fields.test.ts
@@ -6,21 +6,21 @@
* Side Public License, v 1.
*/
+import { IndexPatternBase } from '../../..';
import { fields } from '../../../../index_patterns/mocks';
import { nodeTypes } from '../../index';
-import { IIndexPattern, IFieldType } from '../../../../index_patterns';
// @ts-ignore
import { getFields } from './get_fields';
describe('getFields', () => {
- let indexPattern: IIndexPattern;
+ let indexPattern: IndexPatternBase;
beforeEach(() => {
indexPattern = ({
fields,
- } as unknown) as IIndexPattern;
+ } as unknown) as IndexPatternBase;
});
describe('field names without a wildcard', () => {
@@ -41,14 +41,14 @@ describe('getFields', () => {
});
test('should not match a wildcard in a literal node', () => {
- const indexPatternWithWildField = {
+ const indexPatternWithWildField: IndexPatternBase = ({
title: 'wildIndex',
fields: [
{
name: 'foo*',
},
],
- } as IIndexPattern;
+ } as unknown) as IndexPatternBase;
const fieldNameNode = nodeTypes.literal.buildNode('foo*');
const results = getFields(fieldNameNode, indexPatternWithWildField);
@@ -76,8 +76,8 @@ describe('getFields', () => {
expect(Array.isArray(results)).toBeTruthy();
expect(results).toHaveLength(2);
- expect(results!.find((field: IFieldType) => field.name === 'machine.os')).toBeDefined();
- expect(results!.find((field: IFieldType) => field.name === 'machine.os.raw')).toBeDefined();
+ expect(results!.find((field) => field.name === 'machine.os')).toBeDefined();
+ expect(results!.find((field) => field.name === 'machine.os.raw')).toBeDefined();
});
});
});
diff --git a/src/plugins/data/common/es_query/kuery/functions/utils/get_full_field_name_node.ts b/src/plugins/data/common/es_query/kuery/functions/utils/get_full_field_name_node.ts
index 644791637aa709..2a31ebeee2fab7 100644
--- a/src/plugins/data/common/es_query/kuery/functions/utils/get_full_field_name_node.ts
+++ b/src/plugins/data/common/es_query/kuery/functions/utils/get_full_field_name_node.ts
@@ -7,7 +7,7 @@
*/
import { getFields } from './get_fields';
-import { IndexPatternBase, IFieldType, KueryNode } from '../../../..';
+import { IndexPatternBase, IndexPatternFieldBase, KueryNode } from '../../../..';
export function getFullFieldNameNode(
rootNameNode: any,
@@ -27,7 +27,7 @@ export function getFullFieldNameNode(
}
const fields = getFields(fullFieldNameNode, indexPattern);
- const errors = fields!.reduce((acc: any, field: IFieldType) => {
+ const errors = fields!.reduce((acc: any, field: IndexPatternFieldBase) => {
const nestedPathFromField =
field.subType && field.subType.nested ? field.subType.nested.path : undefined;
diff --git a/src/plugins/data/common/index_patterns/fields/types.ts b/src/plugins/data/common/index_patterns/fields/types.ts
index 0fb7a46c2cf73d..3b2e25d3d80a6c 100644
--- a/src/plugins/data/common/index_patterns/fields/types.ts
+++ b/src/plugins/data/common/index_patterns/fields/types.ts
@@ -5,18 +5,13 @@
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
-import type { estypes } from '@elastic/elasticsearch';
-import { FieldSpec, IFieldSubType, IndexPattern } from '../..';
+import { IndexPatternFieldBase, FieldSpec, IndexPattern } from '../..';
/**
* @deprecated
* Use IndexPatternField or FieldSpec instead
*/
-export interface IFieldType {
- name: string;
- type: string;
- script?: string;
- lang?: estypes.ScriptLanguage;
+export interface IFieldType extends IndexPatternFieldBase {
count?: number;
// esTypes might be undefined on old index patterns that have not been refreshed since we added
// this prop. It is also undefined on scripted fields.
@@ -27,8 +22,6 @@ export interface IFieldType {
sortable?: boolean;
visualizable?: boolean;
readFromDocValues?: boolean;
- scripted?: boolean;
- subType?: IFieldSubType;
displayName?: string;
customLabel?: string;
format?: any;
diff --git a/src/plugins/data/common/index_patterns/types.ts b/src/plugins/data/common/index_patterns/types.ts
index a88f029c0c7cd9..b03e745df74a6a 100644
--- a/src/plugins/data/common/index_patterns/types.ts
+++ b/src/plugins/data/common/index_patterns/types.ts
@@ -9,7 +9,7 @@ import type { estypes } from '@elastic/elasticsearch';
import { ToastInputFields, ErrorToastOptions } from 'src/core/public/notifications';
// eslint-disable-next-line
import type { SavedObject } from 'src/core/server';
-import type { IndexPatternBase } from '../es_query';
+import type { IndexPatternFieldBase, IFieldSubType, IndexPatternBase } from '../es_query';
import { IFieldType } from './fields';
import { RUNTIME_FIELD_TYPES } from './constants';
import { SerializedFieldFormat } from '../../../expressions/common';
@@ -32,6 +32,7 @@ export interface RuntimeField {
*/
export interface IIndexPattern extends IndexPatternBase {
title: string;
+ fields: IFieldType[];
/**
* Type is used for identifying rollup indices, otherwise left undefined
*/
@@ -149,12 +150,6 @@ export type AggregationRestrictions = Record<
time_zone?: string;
}
>;
-
-export interface IFieldSubType {
- multi?: { parent: string };
- nested?: { path: string };
-}
-
export interface TypeMeta {
aggs?: Record;
[key: string]: any;
@@ -183,30 +178,17 @@ export interface FieldSpecExportFmt {
/**
* Serialized version of IndexPatternField
*/
-export interface FieldSpec {
+export interface FieldSpec extends IndexPatternFieldBase {
/**
* Popularity count is used by discover
*/
count?: number;
- /**
- * Scripted field painless script
- */
- script?: string;
- /**
- * Scripted field langauge
- * Painless is the only valid scripted field language
- */
- lang?: estypes.ScriptLanguage;
conflictDescriptions?: Record;
format?: SerializedFieldFormat;
- name: string;
- type: string;
esTypes?: string[];
- scripted?: boolean;
searchable: boolean;
aggregatable: boolean;
readFromDocValues?: boolean;
- subType?: IFieldSubType;
indexed?: boolean;
customLabel?: string;
runtimeField?: RuntimeField;
diff --git a/src/plugins/data/public/public.api.md b/src/plugins/data/public/public.api.md
index 6a49fab0e33ff4..35094fac1cc0f1 100644
--- a/src/plugins/data/public/public.api.md
+++ b/src/plugins/data/public/public.api.md
@@ -808,11 +808,11 @@ export const esFilters: {
FILTERS: typeof FILTERS;
FilterStateStore: typeof FilterStateStore;
buildEmptyFilter: (isPinned: boolean, index?: string | undefined) => import("../common").Filter;
- buildPhrasesFilter: (field: import("../common").IFieldType, params: any[], indexPattern: import("../common").IndexPatternBase) => import("../common").PhrasesFilter;
- buildExistsFilter: (field: import("../common").IFieldType, indexPattern: import("../common").IndexPatternBase) => import("../common").ExistsFilter;
- buildPhraseFilter: (field: import("../common").IFieldType, value: any, indexPattern: import("../common").IndexPatternBase) => import("../common").PhraseFilter;
+ buildPhrasesFilter: (field: import("../common").IndexPatternFieldBase, params: any[], indexPattern: import("../common").IndexPatternBase) => import("../common").PhrasesFilter;
+ buildExistsFilter: (field: import("../common").IndexPatternFieldBase, indexPattern: import("../common").IndexPatternBase) => import("../common").ExistsFilter;
+ buildPhraseFilter: (field: import("../common").IndexPatternFieldBase, value: any, indexPattern: import("../common").IndexPatternBase) => import("../common").PhraseFilter;
buildQueryFilter: (query: any, index: string, alias: string) => import("../common").QueryStringFilter;
- buildRangeFilter: (field: import("../common").IFieldType, params: import("../common").RangeFilterParams, indexPattern: import("../common").IndexPatternBase, formattedValue?: string | undefined) => import("../common").RangeFilter;
+ buildRangeFilter: (field: import("../common").IndexPatternFieldBase, params: import("../common").RangeFilterParams, indexPattern: import("../common").IndexPatternBase, formattedValue?: string | undefined) => import("../common").RangeFilter;
isPhraseFilter: (filter: any) => filter is import("../common").PhraseFilter;
isExistsFilter: (filter: any) => filter is import("../common").ExistsFilter;
isPhrasesFilter: (filter: any) => filter is import("../common").PhrasesFilter;
@@ -1242,10 +1242,11 @@ export interface IFieldSubType {
};
}
+// Warning: (ae-forgotten-export) The symbol "IndexPatternFieldBase" needs to be exported by the entry point index.d.ts
// Warning: (ae-missing-release-tag) "IFieldType" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
// @public @deprecated (undocumented)
-export interface IFieldType {
+export interface IFieldType extends IndexPatternFieldBase {
// (undocumented)
aggregatable?: boolean;
// (undocumented)
@@ -1261,28 +1262,16 @@ export interface IFieldType {
// (undocumented)
format?: any;
// (undocumented)
- lang?: estypes.ScriptLanguage;
- // (undocumented)
- name: string;
- // (undocumented)
readFromDocValues?: boolean;
// (undocumented)
- script?: string;
- // (undocumented)
- scripted?: boolean;
- // (undocumented)
searchable?: boolean;
// (undocumented)
sortable?: boolean;
// (undocumented)
- subType?: IFieldSubType;
- // (undocumented)
toSpec?: (options?: {
getFormatterForField?: IndexPattern['getFormatterForField'];
}) => FieldSpec;
// (undocumented)
- type: string;
- // (undocumented)
visualizable?: boolean;
}
@@ -1295,6 +1284,8 @@ export interface IIndexPattern extends IndexPatternBase {
//
// (undocumented)
fieldFormatMap?: Record | undefined>;
+ // (undocumented)
+ fields: IFieldType[];
getFormatterForField?: (field: IndexPatternField | IndexPatternField['spec'] | IFieldType) => FieldFormat;
// (undocumented)
getTimeField?(): IFieldType | undefined;
@@ -1394,7 +1385,9 @@ export class IndexPattern implements IIndexPattern {
// (undocumented)
getAggregationRestrictions(): Record import("../common").QueryStringFilter;
buildCustomFilter: typeof buildCustomFilter;
buildEmptyFilter: (isPinned: boolean, index?: string | undefined) => import("../common").Filter;
- buildExistsFilter: (field: import("../common").IFieldType, indexPattern: import("../common").IndexPatternBase) => import("../common").ExistsFilter;
+ buildExistsFilter: (field: import("../common").IndexPatternFieldBase, indexPattern: import("../common").IndexPatternBase) => import("../common").ExistsFilter;
buildFilter: typeof buildFilter;
- buildPhraseFilter: (field: import("../common").IFieldType, value: any, indexPattern: import("../common").IndexPatternBase) => import("../common").PhraseFilter;
- buildPhrasesFilter: (field: import("../common").IFieldType, params: any[], indexPattern: import("../common").IndexPatternBase) => import("../common").PhrasesFilter;
- buildRangeFilter: (field: import("../common").IFieldType, params: import("../common").RangeFilterParams, indexPattern: import("../common").IndexPatternBase, formattedValue?: string | undefined) => import("../common").RangeFilter;
+ buildPhraseFilter: (field: import("../common").IndexPatternFieldBase, value: any, indexPattern: import("../common").IndexPatternBase) => import("../common").PhraseFilter;
+ buildPhrasesFilter: (field: import("../common").IndexPatternFieldBase, params: any[], indexPattern: import("../common").IndexPatternBase) => import("../common").PhrasesFilter;
+ buildRangeFilter: (field: import("../common").IndexPatternFieldBase, params: import("../common").RangeFilterParams, indexPattern: import("../common").IndexPatternBase, formattedValue?: string | undefined) => import("../common").RangeFilter;
isFilterDisabled: (filter: import("../common").Filter) => boolean;
};
@@ -693,10 +693,11 @@ export interface IFieldSubType {
};
}
+// Warning: (ae-forgotten-export) The symbol "IndexPatternFieldBase" needs to be exported by the entry point index.d.ts
// Warning: (ae-missing-release-tag) "IFieldType" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
// @public @deprecated (undocumented)
-export interface IFieldType {
+export interface IFieldType extends IndexPatternFieldBase {
// (undocumented)
aggregatable?: boolean;
// (undocumented)
@@ -712,21 +713,11 @@ export interface IFieldType {
// (undocumented)
format?: any;
// (undocumented)
- lang?: estypes.ScriptLanguage;
- // (undocumented)
- name: string;
- // (undocumented)
readFromDocValues?: boolean;
// (undocumented)
- script?: string;
- // (undocumented)
- scripted?: boolean;
- // (undocumented)
searchable?: boolean;
// (undocumented)
sortable?: boolean;
- // (undocumented)
- subType?: IFieldSubType;
// Warning: (ae-forgotten-export) The symbol "FieldSpec" needs to be exported by the entry point index.d.ts
//
// (undocumented)
@@ -734,8 +725,6 @@ export interface IFieldType {
getFormatterForField?: IndexPattern['getFormatterForField'];
}) => FieldSpec;
// (undocumented)
- type: string;
- // (undocumented)
visualizable?: boolean;
}
@@ -780,7 +769,9 @@ export class IndexPattern implements IIndexPattern {
// (undocumented)
getAggregationRestrictions(): Record
Date: Wed, 30 Jun 2021 15:43:18 +0300
Subject: [PATCH 050/121] [TSVB] Metric count is depicted as `-` instead of 0
(#103717)
* [TSVB] Metric count is depicted as - instead of 0
* Rename extractData to mapEmptyToZero and remove unnecessary intervalString undefined assignment in get_bucket_size.js
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
---
.../lib/vis_data/helpers/get_bucket_size.js | 2 -
.../server/lib/vis_data/helpers/index.js | 2 +-
.../server/lib/vis_data/helpers/map_bucket.js | 13 -----
...cket.test.js => map_empty_to_zero.test.ts} | 52 +++++++++++--------
.../lib/vis_data/helpers/map_empty_to_zero.ts | 25 +++++++++
.../response_processors/series/math.js | 7 +--
.../response_processors/series/std_metric.js | 7 +--
.../response_processors/table/std_metric.js | 6 +--
8 files changed, 62 insertions(+), 52 deletions(-)
delete mode 100644 src/plugins/vis_type_timeseries/server/lib/vis_data/helpers/map_bucket.js
rename src/plugins/vis_type_timeseries/server/lib/vis_data/helpers/{map_bucket.test.js => map_empty_to_zero.test.ts} (53%)
create mode 100644 src/plugins/vis_type_timeseries/server/lib/vis_data/helpers/map_empty_to_zero.ts
diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/helpers/get_bucket_size.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/helpers/get_bucket_size.js
index b9ce76f7176b45..ad20f434bedf54 100644
--- a/src/plugins/vis_type_timeseries/server/lib/vis_data/helpers/get_bucket_size.js
+++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/helpers/get_bucket_size.js
@@ -45,8 +45,6 @@ const calculateBucketData = (timeInterval, capabilities) => {
if (converted) {
intervalString = converted.value + converted.unit;
}
-
- intervalString = undefined;
} else {
intervalString = '1ms';
}
diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/helpers/index.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/helpers/index.js
index de93ff22fa5980..e2211e4843bd68 100644
--- a/src/plugins/vis_type_timeseries/server/lib/vis_data/helpers/index.js
+++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/helpers/index.js
@@ -15,6 +15,6 @@ export { getLastMetric } from './get_last_metric';
export { getSiblingAggValue } from './get_sibling_agg_value';
export { getSplits } from './get_splits';
export { getTimerange } from './get_timerange';
-export { mapBucket } from './map_bucket';
export { parseSettings } from './parse_settings';
+export { mapEmptyToZero } from './map_empty_to_zero';
export { overwrite } from './overwrite';
diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/helpers/map_bucket.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/helpers/map_bucket.js
deleted file mode 100644
index dd48aa3705d41c..00000000000000
--- a/src/plugins/vis_type_timeseries/server/lib/vis_data/helpers/map_bucket.js
+++ /dev/null
@@ -1,13 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0 and the Server Side Public License, v 1; you may not use this file except
- * in compliance with, at your election, the Elastic License 2.0 or the Server
- * Side Public License, v 1.
- */
-
-import { getAggValue } from './get_agg_value';
-
-export function mapBucket(metric) {
- return (bucket) => [bucket.key, getAggValue(bucket, metric)];
-}
diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/helpers/map_bucket.test.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/helpers/map_empty_to_zero.test.ts
similarity index 53%
rename from src/plugins/vis_type_timeseries/server/lib/vis_data/helpers/map_bucket.test.js
rename to src/plugins/vis_type_timeseries/server/lib/vis_data/helpers/map_empty_to_zero.test.ts
index 63ba294b7dc46a..b5f1adc3f02022 100644
--- a/src/plugins/vis_type_timeseries/server/lib/vis_data/helpers/map_bucket.test.js
+++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/helpers/map_empty_to_zero.test.ts
@@ -6,39 +6,47 @@
* Side Public License, v 1.
*/
-import { mapBucket } from './map_bucket';
+import { mapEmptyToZero } from './map_empty_to_zero';
-describe('mapBucket(metric)', () => {
+describe('mapEmptyToZero(metric, buckets)', () => {
test('returns bucket key and value for basic metric', () => {
const metric = { id: 'AVG', type: 'avg' };
- const bucket = {
- key: 1234,
- AVG: { value: 1 },
- };
- expect(mapBucket(metric)(bucket)).toEqual([1234, 1]);
+ const buckets = [
+ {
+ key: 1234,
+ AVG: { value: 1 },
+ },
+ ];
+ expect(mapEmptyToZero(metric, buckets)).toEqual([[1234, 1]]);
});
test('returns bucket key and value for std_deviation', () => {
const metric = { id: 'STDDEV', type: 'std_deviation' };
- const bucket = {
- key: 1234,
- STDDEV: { std_deviation: 1 },
- };
- expect(mapBucket(metric)(bucket)).toEqual([1234, 1]);
+ const buckets = [
+ {
+ key: 1234,
+ STDDEV: { std_deviation: 1 },
+ },
+ ];
+ expect(mapEmptyToZero(metric, buckets)).toEqual([[1234, 1]]);
});
test('returns bucket key and value for percentiles', () => {
const metric = { id: 'PCT', type: 'percentile', percent: 50 };
- const bucket = {
- key: 1234,
- PCT: { values: { '50.0': 1 } },
- };
- expect(mapBucket(metric)(bucket)).toEqual([1234, 1]);
+ const buckets = [
+ {
+ key: 1234,
+ PCT: { values: { '50.0': 1 } },
+ },
+ ];
+ expect(mapEmptyToZero(metric, buckets)).toEqual([[1234, 1]]);
});
test('returns bucket key and value for derivative', () => {
const metric = { id: 'DERV', type: 'derivative', field: 'io', unit: '1s' };
- const bucket = {
- key: 1234,
- DERV: { value: 100, normalized_value: 1 },
- };
- expect(mapBucket(metric)(bucket)).toEqual([1234, 1]);
+ const buckets = [
+ {
+ key: 1234,
+ DERV: { value: 100, normalized_value: 1 },
+ },
+ ];
+ expect(mapEmptyToZero(metric, buckets)).toEqual([[1234, 1]]);
});
});
diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/helpers/map_empty_to_zero.ts b/src/plugins/vis_type_timeseries/server/lib/vis_data/helpers/map_empty_to_zero.ts
new file mode 100644
index 00000000000000..0490193a76e81a
--- /dev/null
+++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/helpers/map_empty_to_zero.ts
@@ -0,0 +1,25 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+// @ts-expect-error not typed yet
+import { getAggValue } from './get_agg_value';
+import { METRIC_TYPES } from '../../../../../data/common';
+import type { Metric } from '../../../../common/types';
+
+export const mapEmptyToZero = (metric: Metric, buckets: any[]) => {
+ // Metric types where an empty set equals `zero`
+ const isSettableToZero = [
+ METRIC_TYPES.COUNT,
+ METRIC_TYPES.CARDINALITY,
+ METRIC_TYPES.SUM,
+ ].includes(metric.type as METRIC_TYPES);
+
+ return isSettableToZero && !buckets.length
+ ? [[undefined, 0]]
+ : buckets.map((bucket) => [bucket.key, getAggValue(bucket, metric)]);
+};
diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/response_processors/series/math.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/response_processors/series/math.js
index d3cff76524ee36..a6addc8ba0e53b 100644
--- a/src/plugins/vis_type_timeseries/server/lib/vis_data/response_processors/series/math.js
+++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/response_processors/series/math.js
@@ -10,10 +10,7 @@ import { convertIntervalToUnit } from '../../helpers/unit_to_seconds';
const percentileValueMatch = /\[([0-9\.]+)\]$/;
import { startsWith, flatten, values, first, last } from 'lodash';
-import { getDefaultDecoration } from '../../helpers/get_default_decoration';
-import { getSiblingAggValue } from '../../helpers/get_sibling_agg_value';
-import { getSplits } from '../../helpers/get_splits';
-import { mapBucket } from '../../helpers/map_bucket';
+import { getDefaultDecoration, getSiblingAggValue, getSplits, mapEmptyToZero } from '../../helpers';
import { evaluate } from '@kbn/tinymath';
export function mathAgg(resp, panel, series, meta, extractFields) {
@@ -44,7 +41,7 @@ export function mathAgg(resp, panel, series, meta, extractFields) {
} else {
const percentileMatch = v.field.match(percentileValueMatch);
const m = percentileMatch ? { ...metric, percent: percentileMatch[1] } : { ...metric };
- acc[v.name] = split.timeseries.buckets.map(mapBucket(m));
+ acc[v.name] = mapEmptyToZero(m, split.timeseries.buckets);
}
return acc;
}, {});
diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/response_processors/series/std_metric.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/response_processors/series/std_metric.js
index b9a4139a723138..cc406041ad8742 100644
--- a/src/plugins/vis_type_timeseries/server/lib/vis_data/response_processors/series/std_metric.js
+++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/response_processors/series/std_metric.js
@@ -6,10 +6,7 @@
* Side Public License, v 1.
*/
-import { getDefaultDecoration } from '../../helpers/get_default_decoration';
-import { getSplits } from '../../helpers/get_splits';
-import { getLastMetric } from '../../helpers/get_last_metric';
-import { mapBucket } from '../../helpers/map_bucket';
+import { getDefaultDecoration, getSplits, getLastMetric, mapEmptyToZero } from '../../helpers';
import { METRIC_TYPES } from '../../../../../common/enums';
export function stdMetric(resp, panel, series, meta, extractFields) {
@@ -26,7 +23,7 @@ export function stdMetric(resp, panel, series, meta, extractFields) {
const decoration = getDefaultDecoration(series);
(await getSplits(resp, panel, series, meta, extractFields)).forEach((split) => {
- const data = split.timeseries.buckets.map(mapBucket(metric));
+ const data = mapEmptyToZero(metric, split.timeseries.buckets);
results.push({
id: `${split.id}`,
label: split.label,
diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/response_processors/table/std_metric.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/response_processors/table/std_metric.js
index a648258745a15e..140212c2ec9070 100644
--- a/src/plugins/vis_type_timeseries/server/lib/vis_data/response_processors/table/std_metric.js
+++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/response_processors/table/std_metric.js
@@ -6,9 +6,7 @@
* Side Public License, v 1.
*/
-import { getSplits } from '../../helpers/get_splits';
-import { getLastMetric } from '../../helpers/get_last_metric';
-import { mapBucket } from '../../helpers/map_bucket';
+import { getSplits, getLastMetric, mapEmptyToZero } from '../../helpers';
import { METRIC_TYPES } from '../../../../../common/enums';
export function stdMetric(bucket, panel, series, meta, extractFields) {
@@ -32,7 +30,7 @@ export function stdMetric(bucket, panel, series, meta, extractFields) {
};
(await getSplits(fakeResp, panel, series, meta, extractFields)).forEach((split) => {
- const data = split.timeseries.buckets.map(mapBucket(metric));
+ const data = mapEmptyToZero(metric, split.timeseries.buckets);
results.push({
id: split.id,
label: split.label,
From fd7dc18f35afae1292470794614a6c46180190c4 Mon Sep 17 00:00:00 2001
From: Jen Huang
Date: Wed, 30 Jun 2021 05:44:00 -0700
Subject: [PATCH 051/121] Allow zero (0) to unset unenroll_timeout field
(#103790)
---
.../components/agent_policy_form.tsx | 23 ++--
.../components/settings/index.tsx | 113 ++++++++++--------
.../fleet/server/services/agent_policy.ts | 5 +-
.../fleet/server/types/models/agent_policy.ts | 2 +-
4 files changed, 81 insertions(+), 62 deletions(-)
diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_form.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_form.tsx
index e9094be5344525..475d5a47e3e9a1 100644
--- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_form.tsx
+++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_form.tsx
@@ -159,10 +159,6 @@ export const AgentPolicyForm: React.FunctionComponent = ({
);
});
- const unenrollmentTimeoutText = i18n.translate(
- 'xpack.fleet.agentPolicyForm.unenrollmentTimeoutLabel',
- { defaultMessage: 'Unenrollment timeout' }
- );
const advancedOptionsContent = (
<>
@@ -303,7 +299,14 @@ export const AgentPolicyForm: React.FunctionComponent = ({
/>
{unenrollmentTimeoutText}}
+ title={
+
+
+
+ }
description={
= ({
updateAgentPolicy({ unenroll_timeout: Number(e.target.value) })}
+ value={agentPolicy.unenroll_timeout || ''}
+ onChange={(e) => {
+ updateAgentPolicy({
+ unenroll_timeout: e.target.value ? Number(e.target.value) : 0,
+ });
+ }}
isInvalid={Boolean(touchedFields.unenroll_timeout && validation.unenroll_timeout)}
onBlur={() => setTouchedFields({ ...touchedFields, unenroll_timeout: true })}
- placeholder={unenrollmentTimeoutText}
/>
diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/details_page/components/settings/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/details_page/components/settings/index.tsx
index 0c6451e3f34a22..ff704bba70ff42 100644
--- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/details_page/components/settings/index.tsx
+++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/details_page/components/settings/index.tsx
@@ -8,7 +8,14 @@
import React, { memo, useState } from 'react';
import { useHistory } from 'react-router-dom';
import styled from 'styled-components';
-import { EuiBottomBar, EuiFlexGroup, EuiFlexItem, EuiButtonEmpty, EuiButton } from '@elastic/eui';
+import {
+ EuiBottomBar,
+ EuiFlexGroup,
+ EuiFlexItem,
+ EuiButtonEmpty,
+ EuiButton,
+ EuiSpacer,
+} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
@@ -145,58 +152,62 @@ export const SettingsView = memo<{ agentPolicy: AgentPolicy }>(
/>
{hasChanges ? (
-
-
-
-
-
-
-
-
- {
- setAgentPolicy({ ...originalAgentPolicy });
- setHasChanges(false);
- }}
- >
-
-
-
-
- 0
- }
- iconType="save"
- color="primary"
- fill
- >
- {isLoading ? (
-
- ) : (
+ <>
+
+
+
+
+
+
+
+
+
+
+ {
+ setAgentPolicy({ ...originalAgentPolicy });
+ setHasChanges(false);
+ }}
+ >
- )}
-
-
-
-
-
-
+
+
+
+ 0
+ }
+ iconType="save"
+ color="primary"
+ fill
+ >
+ {isLoading ? (
+
+ ) : (
+
+ )}
+
+
+
+
+
+
+ >
) : null}
);
diff --git a/x-pack/plugins/fleet/server/services/agent_policy.ts b/x-pack/plugins/fleet/server/services/agent_policy.ts
index 465075cca7a0b5..cff70737be6ee1 100644
--- a/x-pack/plugins/fleet/server/services/agent_policy.ts
+++ b/x-pack/plugins/fleet/server/services/agent_policy.ts
@@ -642,9 +642,12 @@ class AgentPolicyService {
data: (fullPolicy as unknown) as FleetServerPolicy['data'],
policy_id: fullPolicy.id,
default_fleet_server: policy.is_default_fleet_server === true,
- unenroll_timeout: policy.unenroll_timeout,
};
+ if (policy.unenroll_timeout) {
+ fleetServerPolicy.unenroll_timeout = policy.unenroll_timeout;
+ }
+
await esClient.create({
index: AGENT_POLICY_INDEX,
body: fleetServerPolicy,
diff --git a/x-pack/plugins/fleet/server/types/models/agent_policy.ts b/x-pack/plugins/fleet/server/types/models/agent_policy.ts
index 48aea1b5cbcc4c..2a47c002736b58 100644
--- a/x-pack/plugins/fleet/server/types/models/agent_policy.ts
+++ b/x-pack/plugins/fleet/server/types/models/agent_policy.ts
@@ -16,7 +16,7 @@ export const AgentPolicyBaseSchema = {
namespace: NamespaceSchema,
description: schema.maybe(schema.string()),
is_managed: schema.maybe(schema.boolean()),
- unenroll_timeout: schema.maybe(schema.number({ min: 1 })),
+ unenroll_timeout: schema.maybe(schema.number()),
monitoring_enabled: schema.maybe(
schema.arrayOf(
schema.oneOf([schema.literal(dataTypes.Logs), schema.literal(dataTypes.Metrics)])
From 0636c9bc82c3017f7f846544564bf33d1c830b48 Mon Sep 17 00:00:00 2001
From: Ashokaditya
Date: Wed, 30 Jun 2021 14:54:14 +0200
Subject: [PATCH 052/121] [Security Solution][Endpoint] Allow activity log
scrolling on small screens (#103852)
---
.../endpoint_hosts/view/details/endpoint_activity_log.tsx | 7 +++++--
1 file changed, 5 insertions(+), 2 deletions(-)
diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/endpoint_activity_log.tsx b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/endpoint_activity_log.tsx
index f1701054c4d5f4..360d6e38428161 100644
--- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/endpoint_activity_log.tsx
+++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/endpoint_activity_log.tsx
@@ -31,6 +31,9 @@ import {
getActivityLogRequestLoading,
} from '../../store/selectors';
+const StyledEuiFlexGroup = styled(EuiFlexGroup)`
+ height: 85vh;
+`;
const LoadMoreTrigger = styled.div`
height: 6px;
width: 100%;
@@ -79,7 +82,7 @@ export const EndpointActivityLog = memo(
return (
<>
-
+
{(activityLogLoaded && !activityLogSize) || activityLogError ? (
>
)}
-
+
>
);
}
From 398d3fdd5caa955e6229304c29fb52b9e733c56b Mon Sep 17 00:00:00 2001
From: Jean-Louis Leysens
Date: Wed, 30 Jun 2021 15:24:41 +0200
Subject: [PATCH 053/121] [Reporting] Reintroduce "ILM policy for managing
reporting indices" (#103850)
* revert revert
* fix import of old type from lib
---
.../server/lib/store/report_ilm_policy.ts | 18 +++++
.../reporting/server/lib/store/store.test.ts | 39 +++++++++++
.../reporting/server/lib/store/store.ts | 67 +++++++++++++++----
x-pack/plugins/reporting/server/plugin.ts | 3 +
4 files changed, 115 insertions(+), 12 deletions(-)
create mode 100644 x-pack/plugins/reporting/server/lib/store/report_ilm_policy.ts
diff --git a/x-pack/plugins/reporting/server/lib/store/report_ilm_policy.ts b/x-pack/plugins/reporting/server/lib/store/report_ilm_policy.ts
new file mode 100644
index 00000000000000..90636e3c523a32
--- /dev/null
+++ b/x-pack/plugins/reporting/server/lib/store/report_ilm_policy.ts
@@ -0,0 +1,18 @@
+/*
+ * 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 { IlmPutLifecycleRequest } from '@elastic/elasticsearch/api/types';
+
+export const reportingIlmPolicy: IlmPutLifecycleRequest['body'] = {
+ policy: {
+ phases: {
+ hot: {
+ actions: {},
+ },
+ },
+ },
+};
diff --git a/x-pack/plugins/reporting/server/lib/store/store.test.ts b/x-pack/plugins/reporting/server/lib/store/store.test.ts
index 8bb5c7fb8bbf91..f46e55c9cc41b2 100644
--- a/x-pack/plugins/reporting/server/lib/store/store.test.ts
+++ b/x-pack/plugins/reporting/server/lib/store/store.test.ts
@@ -7,6 +7,7 @@
import type { DeeplyMockedKeys } from '@kbn/utility-types/jest';
import { ElasticsearchClient } from 'src/core/server';
+import { elasticsearchServiceMock } from 'src/core/server/mocks';
import { ReportingCore } from '../../';
import {
createMockConfigSchema,
@@ -16,6 +17,8 @@ import {
import { Report, ReportDocument } from './report';
import { ReportingStore } from './store';
+const { createApiResponse } = elasticsearchServiceMock;
+
describe('ReportingStore', () => {
const mockLogger = createMockLevelLogger();
let mockCore: ReportingCore;
@@ -401,4 +404,40 @@ describe('ReportingStore', () => {
expect(updateCall.if_seq_no).toBe(46);
expect(updateCall.if_primary_term).toBe(10002);
});
+
+ describe('start', () => {
+ it('creates an ILM policy for managing reporting indices if there is not already one', async () => {
+ mockEsClient.ilm.getLifecycle.mockRejectedValueOnce(createApiResponse({ statusCode: 404 }));
+ mockEsClient.ilm.putLifecycle.mockResolvedValueOnce(createApiResponse());
+
+ const store = new ReportingStore(mockCore, mockLogger);
+ await store.start();
+
+ expect(mockEsClient.ilm.getLifecycle).toHaveBeenCalledWith({ policy: 'kibana-reporting' });
+ expect(mockEsClient.ilm.putLifecycle.mock.calls[0][0]).toMatchInlineSnapshot(`
+ Object {
+ "body": Object {
+ "policy": Object {
+ "phases": Object {
+ "hot": Object {
+ "actions": Object {},
+ },
+ },
+ },
+ },
+ "policy": "kibana-reporting",
+ }
+ `);
+ });
+
+ it('does not create an ILM policy for managing reporting indices if one already exists', async () => {
+ mockEsClient.ilm.getLifecycle.mockResolvedValueOnce(createApiResponse());
+
+ const store = new ReportingStore(mockCore, mockLogger);
+ await store.start();
+
+ expect(mockEsClient.ilm.getLifecycle).toHaveBeenCalledWith({ policy: 'kibana-reporting' });
+ expect(mockEsClient.ilm.putLifecycle).not.toHaveBeenCalled();
+ });
+ });
});
diff --git a/x-pack/plugins/reporting/server/lib/store/store.ts b/x-pack/plugins/reporting/server/lib/store/store.ts
index 8f1e6c315a2d1e..17c067a255b38d 100644
--- a/x-pack/plugins/reporting/server/lib/store/store.ts
+++ b/x-pack/plugins/reporting/server/lib/store/store.ts
@@ -15,6 +15,8 @@ import { indexTimestamp } from './index_timestamp';
import { mapping } from './mapping';
import { MIGRATION_VERSION, Report, ReportDocument, ReportSource } from './report';
+import { reportingIlmPolicy } from './report_ilm_policy';
+
/*
* When an instance of Kibana claims a report job, this information tells us about that instance
*/
@@ -115,19 +117,22 @@ export class ReportingStore {
return exists;
}
- const indexSettings = {
- number_of_shards: 1,
- auto_expand_replicas: '0-1',
- };
- const body = {
- settings: indexSettings,
- mappings: {
- properties: mapping,
- },
- };
-
try {
- await client.indices.create({ index: indexName, body });
+ await client.indices.create({
+ index: indexName,
+ body: {
+ settings: {
+ number_of_shards: 1,
+ auto_expand_replicas: '0-1',
+ lifecycle: {
+ name: this.ilmPolicyName,
+ },
+ },
+ mappings: {
+ properties: mapping,
+ },
+ },
+ });
return true;
} catch (error) {
@@ -176,6 +181,44 @@ export class ReportingStore {
return client.indices.refresh({ index });
}
+ private readonly ilmPolicyName = 'kibana-reporting';
+
+ private async doesIlmPolicyExist(): Promise {
+ const client = await this.getClient();
+ try {
+ await client.ilm.getLifecycle({ policy: this.ilmPolicyName });
+ return true;
+ } catch (e) {
+ if (e.statusCode === 404) {
+ return false;
+ }
+ throw e;
+ }
+ }
+
+ /**
+ * Function to be called during plugin start phase. This ensures the environment is correctly
+ * configured for storage of reports.
+ */
+ public async start() {
+ const client = await this.getClient();
+ try {
+ if (await this.doesIlmPolicyExist()) {
+ this.logger.debug(`Found ILM policy ${this.ilmPolicyName}; skipping creation.`);
+ return;
+ }
+ this.logger.info(`Creating ILM policy for managing reporting indices: ${this.ilmPolicyName}`);
+ await client.ilm.putLifecycle({
+ policy: this.ilmPolicyName,
+ body: reportingIlmPolicy,
+ });
+ } catch (e) {
+ this.logger.error('Error in start phase');
+ this.logger.error(e.body.error);
+ throw e;
+ }
+ }
+
public async addReport(report: Report): Promise {
let index = report._index;
if (!index) {
diff --git a/x-pack/plugins/reporting/server/plugin.ts b/x-pack/plugins/reporting/server/plugin.ts
index 4e7328cf180039..dc0ddf27a53b36 100644
--- a/x-pack/plugins/reporting/server/plugin.ts
+++ b/x-pack/plugins/reporting/server/plugin.ts
@@ -108,6 +108,9 @@ export class ReportingPlugin
logger: this.logger,
});
+ // Note: this must be called after ReportingCore.pluginStart
+ await store.start();
+
this.logger.debug('Start complete');
})().catch((e) => {
this.logger.error(`Error in Reporting start, reporting may not function properly`);
From 4cb979b6888cfeeff25af349fa88318c3a4139b7 Mon Sep 17 00:00:00 2001
From: Rashmi Kulkarni
Date: Wed, 30 Jun 2021 06:36:53 -0700
Subject: [PATCH 054/121] [test] Migrating to kbn_archiver from es_archiver -
for the Maps app (#103028)
* converting Maps es_archiver to kbn_archiver
* delete the esArchiver .kibana reference directory
* fix the path of the json file
* use the delete API to delete the missing references populated in the data.json
* fix the path
* kbn_archiver_maps.json
* added the missing ref
* restoring it to use esArchiver
* replace esArchiver to use kbnArchiver
* moved the data.json directly under kbnArchiver
Please enter the commit message for your changes. Lines starting
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
---
.../test/api_integration/apis/maps/index.js | 4 +
.../api_integration/apis/maps/migrations.js | 10 +-
x-pack/test/functional/apps/maps/index.js | 30 +-
.../es_archives/maps/kibana/data.json | 1249 -----------------
.../es_archives/maps/kibana/mappings.json | 445 ------
.../fixtures/kbn_archiver/maps.json | 912 ++++++++++++
6 files changed, 951 insertions(+), 1699 deletions(-)
delete mode 100644 x-pack/test/functional/es_archives/maps/kibana/data.json
delete mode 100644 x-pack/test/functional/es_archives/maps/kibana/mappings.json
create mode 100644 x-pack/test/functional/fixtures/kbn_archiver/maps.json
diff --git a/x-pack/test/api_integration/apis/maps/index.js b/x-pack/test/api_integration/apis/maps/index.js
index b3f1cf1a89e3b8..46d791b18f0334 100644
--- a/x-pack/test/api_integration/apis/maps/index.js
+++ b/x-pack/test/api_integration/apis/maps/index.js
@@ -14,6 +14,10 @@ export default function ({ loadTestFile, getService }) {
await esArchiver.load('x-pack/test/functional/es_archives/maps/data');
});
+ after(async () => {
+ await esArchiver.unload('x-pack/test/functional/es_archives/logstash_functional');
+ });
+
describe('', () => {
loadTestFile(require.resolve('./get_indexes_matching_pattern'));
loadTestFile(require.resolve('./create_doc_source'));
diff --git a/x-pack/test/api_integration/apis/maps/migrations.js b/x-pack/test/api_integration/apis/maps/migrations.js
index 728f26636f970d..88e6f0c8425989 100644
--- a/x-pack/test/api_integration/apis/maps/migrations.js
+++ b/x-pack/test/api_integration/apis/maps/migrations.js
@@ -9,7 +9,7 @@ import expect from '@kbn/expect';
export default function ({ getService }) {
const supertest = getService('supertest');
- const esArchiver = getService('esArchiver');
+ const kibanaServer = getService('kibanaServer');
describe('migrations', () => {
describe('saved object migrations', () => {
@@ -51,11 +51,15 @@ export default function ({ getService }) {
describe('embeddable migrations', () => {
before(async () => {
- await esArchiver.loadIfNeeded('x-pack/test/functional/es_archives/maps/kibana');
+ await kibanaServer.importExport.load(
+ 'x-pack/test/functional/fixtures/kbn_archiver/maps.json'
+ );
});
after(async () => {
- await esArchiver.unload('x-pack/test/functional/es_archives/maps/kibana');
+ await kibanaServer.importExport.unload(
+ 'x-pack/test/functional/fixtures/kbn_archiver/maps.json'
+ );
});
it('should apply embeddable migrations', async () => {
diff --git a/x-pack/test/functional/apps/maps/index.js b/x-pack/test/functional/apps/maps/index.js
index 4a9262f97ee364..273a8baa0ae4c4 100644
--- a/x-pack/test/functional/apps/maps/index.js
+++ b/x-pack/test/functional/apps/maps/index.js
@@ -9,14 +9,38 @@ export default function ({ loadTestFile, getService }) {
const kibanaServer = getService('kibanaServer');
const esArchiver = getService('esArchiver');
const browser = getService('browser');
+ const log = getService('log');
+ const supertest = getService('supertest');
describe('maps app', function () {
this.tags(['skipFirefox']);
before(async () => {
await esArchiver.loadIfNeeded('x-pack/test/functional/es_archives/logstash_functional');
+ await kibanaServer.importExport.load(
+ 'x-pack/test/functional/fixtures/kbn_archiver/maps.json'
+ );
+ //Find the missing references manually, use the below API to delete it after successful import.
+ log.info('Delete index pattern');
+ log.debug('id: ' + 'idThatDoesNotExitForESGeoGridSource');
+ log.debug('id: ' + 'idThatDoesNotExitForESSearchSource');
+ log.debug('id: ' + 'idThatDoesNotExitForESJoinSource');
+ await supertest
+ .delete('/api/index_patterns/index_pattern/' + 'idThatDoesNotExitForESGeoGridSource')
+ .set('kbn-xsrf', 'true')
+ .expect(200);
+
+ await supertest
+ .delete('/api/index_patterns/index_pattern/' + 'idThatDoesNotExitForESSearchSource')
+ .set('kbn-xsrf', 'true')
+ .expect(200);
+
+ await supertest
+ .delete('/api/index_patterns/index_pattern/' + 'idThatDoesNotExitForESJoinSource')
+ .set('kbn-xsrf', 'true')
+ .expect(200);
+
await esArchiver.load('x-pack/test/functional/es_archives/maps/data');
- await esArchiver.load('x-pack/test/functional/es_archives/maps/kibana');
await kibanaServer.uiSettings.replace({
defaultIndex: 'c698b940-e149-11e8-a35a-370a8516603a',
});
@@ -25,7 +49,9 @@ export default function ({ loadTestFile, getService }) {
after(async () => {
await esArchiver.unload('x-pack/test/functional/es_archives/maps/data');
- await esArchiver.unload('x-pack/test/functional/es_archives/maps/kibana');
+ await kibanaServer.importExport.unload(
+ 'x-pack/test/functional/fixtures/kbn_archiver/maps.json'
+ );
});
describe('', async function () {
diff --git a/x-pack/test/functional/es_archives/maps/kibana/data.json b/x-pack/test/functional/es_archives/maps/kibana/data.json
deleted file mode 100644
index d0c4559d0a0a9f..00000000000000
--- a/x-pack/test/functional/es_archives/maps/kibana/data.json
+++ /dev/null
@@ -1,1249 +0,0 @@
-{
- "type": "doc",
- "value": {
- "id": "space:default",
- "index": ".kibana",
- "source": {
- "space": {
- "description": "This is the default space!",
- "name": "Default"
- },
- "type": "space"
- }
- }
-}
-
-{
- "type": "doc",
- "value": {
- "id": "index-pattern:c698b940-e149-11e8-a35a-370a8516603a",
- "index": ".kibana",
- "source": {
- "index-pattern": {
- "fields" : "[{\"name\":\"hour_of_day\",\"type\":\"number\",\"count\":0,\"scripted\":true,\"script\":\"doc['@timestamp'].value.getHour()\",\"lang\":\"painless\",\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false}]",
- "timeFieldName": "@timestamp",
- "title": "logstash-*"
- },
- "type": "index-pattern"
- }
- }
-}
-
-{
- "type": "doc",
- "value": {
- "id": "index-pattern:561253e0-f731-11e8-8487-11b9dd924f96",
- "index": ".kibana",
- "source": {
- "index-pattern": {
- "fields" : "[]",
- "title": "geo_shapes*"
- },
- "type": "index-pattern"
- }
- }
-}
-
-{
- "type": "doc",
- "value": {
- "id": "index-pattern:e20b2a30-f735-11e8-8ce0-9723965e01e3",
- "index": ".kibana",
- "source": {
- "index-pattern": {
- "runtimeFieldMap" : "{\"runtime_shape_name\":{\"type\":\"keyword\",\"script\":{\"source\":\"emit(doc['shape_name'].value)\"}}}",
- "fields" : "[]",
- "title": "meta_for_geo_shapes*"
- },
- "type": "index-pattern"
- }
- }
-}
-
-{
- "type": "doc",
- "value": {
- "id": "index-pattern:1035e930-1811-11e9-b78a-23d706cd2507",
- "index": ".kibana",
- "source": {
- "index-pattern": {
- "fields" : "[]",
- "title": "antimeridian_points"
- },
- "type": "index-pattern"
- }
- }
-}
-
-{
- "type": "doc",
- "value": {
- "id": "index-pattern:502886a0-18f8-11e9-97c8-5da5e037299c",
- "index": ".kibana",
- "source": {
- "index-pattern": {
- "fields" : "[]",
- "title": "antimeridian_shapes"
- },
- "type": "index-pattern"
- }
- }
-}
-
-{
- "type": "doc",
- "value": {
- "id": "index-pattern:f7fc94a0-936b-11e9-a43f-eb6ee3467b2b",
- "index": ".kibana",
- "source": {
- "index-pattern" : {
- "fields" : "[]",
- "title" : "flights"
- },
- "type" : "index-pattern",
- "references" : [ ],
- "migrationVersion" : {
- "index-pattern" : "6.5.0"
- },
- "updated_at" : "2019-06-20T14:59:15.348Z"
- }
- }
-}
-
-{
- "type": "doc",
- "value": {
- "id": "index-pattern:dedd3180-c8d8-11e9-b36c-81f9f9da524f",
- "index": ".kibana",
- "source": {
- "index-pattern" : {
- "fields" : "[]",
- "title" : "connections"
- },
- "type" : "index-pattern",
- "references" : [ ],
- "migrationVersion" : {
- "index-pattern" : "6.5.0"
- },
- "updated_at" : "2019-08-27T14:42:20.061Z"
- }
- }
-}
-
-{
- "type": "doc",
- "value": {
- "id": "map:d2e73f40-e14a-11e8-a35a-370a8516603a",
- "index": ".kibana",
- "source": {
- "map": {
- "bounds": {
- "coordinates": [
- [
- -130.50168,
- 45.55574
- ],
- [
- -70.72014,
- 18.91275
- ]
- ],
- "type": "envelope"
- },
- "description": "",
- "layerListJSON": "[{\"id\":\"z52lq\",\"label\":\"logstash\",\"minZoom\":0,\"maxZoom\":24,\"sourceDescriptor\":{\"id\":\"e1a5e1a6-676c-4a89-8ea9-0d91d64b73c6\",\"type\":\"ES_SEARCH\",\"indexPatternId\":\"c698b940-e149-11e8-a35a-370a8516603a\",\"geoField\":\"geo.coordinates\",\"limit\":2048,\"filterByMapBounds\":true,\"showTooltip\":true,\"tooltipProperties\":[]},\"visible\":true,\"temporary\":false,\"style\":{\"type\":\"VECTOR\",\"properties\":{\"fillColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#e6194b\"}},\"lineColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#FFFFFF\"}},\"lineWidth\":{\"type\":\"STATIC\",\"options\":{\"size\":1}},\"iconSize\":{\"type\":\"STATIC\",\"options\":{\"size\":10}}},\"previousStyle\":null},\"type\":\"VECTOR\"}]",
- "mapStateJSON": "{\"zoom\":4.1,\"center\":{\"lon\":-100.61091,\"lat\":33.23887},\"timeFilters\":{\"from\":\"2015-09-20T00:00:00.000Z\",\"to\":\"2015-09-20T01:00:00.000Z\"},\"refreshConfig\":{\"isPaused\":true,\"interval\":1000}}",
- "title": "document example",
- "uiStateJSON": "{\"isDarkMode\":false}"
- },
- "type": "map"
- }
- }
-}
-
-{
- "type": "doc",
- "value": {
- "id": "map:8eabdab0-144f-11e9-809f-ad25bb78262c",
- "index": ".kibana",
- "source": {
- "map": {
- "bounds": {
- "coordinates": [
- [
- -130.50168,
- 45.55574
- ],
- [
- -70.72014,
- 18.91275
- ]
- ],
- "type": "envelope"
- },
- "description": "",
- "layerListJSON": "[{\"id\":\"z52lq\",\"label\":null,\"minZoom\":0,\"maxZoom\":24,\"sourceDescriptor\":{\"id\":\"e1a5e1a6-676c-4a89-8ea9-0d91d64b73c6\",\"type\":\"ES_SEARCH\",\"indexPatternId\":\"c698b940-e149-11e8-a35a-370a8516603a\",\"geoField\":\"geo.coordinates\",\"limit\":2048,\"filterByMapBounds\":true,\"showTooltip\":true,\"tooltipProperties\":[\"machine.os\"]},\"visible\":true,\"temporary\":false,\"style\":{\"type\":\"VECTOR\",\"properties\":{\"fillColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#e6194b\"}},\"lineColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#FFFFFF\"}},\"lineWidth\":{\"type\":\"STATIC\",\"options\":{\"size\":1}},\"iconSize\":{\"type\":\"STATIC\",\"options\":{\"size\":10}}},\"previousStyle\":null},\"type\":\"VECTOR\"}]",
- "mapStateJSON": "{\"zoom\":4.1,\"center\":{\"lon\":-100.61091,\"lat\":33.23887},\"timeFilters\":{\"from\":\"2015-09-20T00:00:00.000Z\",\"to\":\"2015-09-20T01:00:00.000Z\"},\"refreshConfig\":{\"isPaused\":true,\"interval\":1000},\"query\":{\"query\":\"machine.os.raw : \\\"ios\\\"\",\"language\":\"kuery\"}}",
- "title": "document example with query",
- "uiStateJSON": "{\"isDarkMode\":false}"
- },
- "type": "map"
- }
- }
-}
-
-{
- "type": "doc",
- "value": {
- "id": "map:7f047b80-b93b-11e9-b146-7d043e1b0586",
- "index": ".kibana",
- "source": {
- "map": {
- "bounds": {
- "coordinates": [
- [
- -130.50168,
- 45.55574
- ],
- [
- -70.72014,
- 18.91275
- ]
- ],
- "type": "envelope"
- },
- "description": "",
- "layerListJSON": "[{\"id\":\"z52lq\",\"label\":null,\"minZoom\":0,\"maxZoom\":24,\"sourceDescriptor\":{\"id\":\"e1a5e1a6-676c-4a89-8ea9-0d91d64b73c6\",\"type\":\"ES_SEARCH\",\"indexPatternId\":\"c698b940-e149-11e8-a35a-370a8516603a\",\"geoField\":\"geo.coordinates\",\"limit\":2048,\"filterByMapBounds\":true,\"showTooltip\":true,\"tooltipProperties\":[\"machine.os\"]},\"visible\":true,\"temporary\":false,\"style\":{\"type\":\"VECTOR\",\"properties\":{\"fillColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#e6194b\"}},\"lineColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#FFFFFF\"}},\"lineWidth\":{\"type\":\"STATIC\",\"options\":{\"size\":1}},\"iconSize\":{\"type\":\"STATIC\",\"options\":{\"size\":10}}},\"previousStyle\":null},\"type\":\"VECTOR\"}]",
- "description": "",
- "mapStateJSON": "{\"zoom\":4.09,\"center\":{\"lon\":-100.58836,\"lat\":33.21778},\"timeFilters\":{\"from\":\"2015-09-20T00:00:00.000Z\",\"to\":\"2015-09-20T01:00:00.000Z\"},\"refreshConfig\":{\"isPaused\":true,\"interval\":1000},\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filters\":[{\"meta\":{\"index\":\"c698b940-e149-11e8-a35a-370a8516603a\",\"alias\":null,\"negate\":false,\"disabled\":false,\"type\":\"phrase\",\"key\":\"machine.os.raw\",\"value\":\"ios\",\"params\":{\"query\":\"ios\"}},\"query\":{\"match\":{\"machine.os.raw\":{\"query\":\"ios\",\"type\":\"phrase\"}}},\"$state\":{\"store\":\"appState\"}}]}",
- "title": "document example with filter",
- "uiStateJSON": "{\"isDarkMode\":false}"
- },
- "type": "map"
- }
- }
-}
-
-{
- "type": "doc",
- "value": {
- "id": "map:68305470-87bc-11e9-a991-3b492a7c3e09",
- "index": ".kibana",
- "source": {
- "map": {
- "title" : "document example top hits",
- "description" : "",
- "mapStateJSON" : "{\"zoom\":4.1,\"center\":{\"lon\":-100.61091,\"lat\":33.23887},\"timeFilters\":{\"from\":\"2015-09-20T00:00:00.000Z\",\"to\":\"2015-09-24T01:00:00.000Z\"},\"refreshConfig\":{\"isPaused\":true,\"interval\":1000},\"query\":{\"query\":\"\",\"language\":\"kuery\"}}",
- "layerListJSON" : "[{\"id\":\"z52lq\",\"label\":\"logstash\",\"minZoom\":0,\"maxZoom\":24,\"sourceDescriptor\":{\"id\":\"e1a5e1a6-676c-4a89-8ea9-0d91d64b73c6\",\"type\":\"ES_SEARCH\",\"geoField\":\"geo.coordinates\",\"limit\":2048,\"filterByMapBounds\":true,\"showTooltip\":true,\"tooltipProperties\":[],\"topHitsTimeField\":\"@timestamp\",\"useTopHits\":true,\"topHitsSplitField\":\"machine.os.raw\",\"topHitsSize\":2,\"indexPatternRefName\":\"layer_1_source_index_pattern\"},\"visible\":true,\"temporary\":false,\"style\":{\"type\":\"VECTOR\",\"properties\":{\"fillColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#e6194b\"}},\"lineColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#FFFFFF\"}},\"lineWidth\":{\"type\":\"STATIC\",\"options\":{\"size\":1}},\"iconSize\":{\"type\":\"STATIC\",\"options\":{\"size\":10}}},\"previousStyle\":null},\"type\":\"VECTOR\"}]",
- "uiStateJSON" : "{\"isLayerTOCOpen\":true,\"openTOCDetails\":[]}",
- "bounds" : {
- "type" : "polygon",
- "coordinates" : [
- [
- [
- -137.80011,
- 46.32557
- ],
- [
- -137.80011,
- 17.86223
- ],
- [
- -63.42171,
- 17.86223
- ],
- [
- -63.42171,
- 46.32557
- ],
- [
- -137.80011,
- 46.32557
- ]
- ]
- ]
- }
- },
- "type": "map",
- "references" : [
- {
- "name" : "layer_1_source_index_pattern",
- "type" : "index-pattern",
- "id" : "c698b940-e149-11e8-a35a-370a8516603a"
- }
- ],
- "migrationVersion" : {
- "map" : "7.2.0"
- }
- }
- }
-}
-
-{
- "type": "doc",
- "value": {
- "id": "map:4ea1e4f0-4dba-11ea-b554-4ba0def79f86",
- "index": ".kibana",
- "source": {
- "map": {
- "title" : "document example top hits split with scripted field",
- "description" : "",
- "mapStateJSON" : "{\"zoom\":4.1,\"center\":{\"lon\":-100.61091,\"lat\":33.23887},\"timeFilters\":{\"from\":\"2015-09-20T00:00:00.000Z\",\"to\":\"2015-09-24T01:00:00.000Z\"},\"refreshConfig\":{\"isPaused\":true,\"interval\":1000},\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filters\":[]}",
- "layerListJSON" : "[{\"id\":\"z52lq\",\"label\":\"logstash\",\"minZoom\":0,\"maxZoom\":24,\"sourceDescriptor\":{\"id\":\"e1a5e1a6-676c-4a89-8ea9-0d91d64b73c6\",\"type\":\"ES_SEARCH\",\"geoField\":\"geo.coordinates\",\"limit\":2048,\"filterByMapBounds\":true,\"showTooltip\":true,\"tooltipProperties\":[],\"scalingType\":\"TOP_HITS\",\"topHitsSplitField\":\"hour_of_day\",\"topHitsSize\":1,\"sortField\":\"@timestamp\",\"sortOrder\":\"desc\",\"applyGlobalQuery\":true,\"indexPatternRefName\":\"layer_1_source_index_pattern\"},\"visible\":true,\"temporary\":false,\"style\":{\"type\":\"VECTOR\",\"properties\":{\"fillColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#e6194b\"}},\"lineColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#FFFFFF\"}},\"lineWidth\":{\"type\":\"STATIC\",\"options\":{\"size\":1}},\"iconSize\":{\"type\":\"STATIC\",\"options\":{\"size\":10}},\"symbolizeAs\":{\"options\":{\"value\":\"circle\"}},\"icon\":{\"type\":\"STATIC\",\"options\":{\"value\":\"airfield\"}}},\"previousStyle\":null},\"type\":\"VECTOR\"}]",
- "uiStateJSON" : "{\"isLayerTOCOpen\":true,\"openTOCDetails\":[]}",
- "bounds" : {
- "type" : "Polygon",
- "coordinates" : [
- [
- [
- -141.61334,
- 47.30762
- ],
- [
- -141.61334,
- 16.49119
- ],
- [
- -59.60848,
- 16.49119
- ],
- [
- -59.60848,
- 47.30762
- ],
- [
- -141.61334,
- 47.30762
- ]
- ]
- ]
- }
- },
- "type" : "map",
- "references" : [
- {
- "name" : "layer_1_source_index_pattern",
- "type" : "index-pattern",
- "id" : "c698b940-e149-11e8-a35a-370a8516603a"
- }
- ],
- "migrationVersion" : {
- "map" : "7.7.0"
- },
- "updated_at" : "2020-02-12T17:08:36.671Z"
- }
- }
-}
-
-{
- "type": "doc",
- "value": {
- "id": "map:c9734720-eb7f-11e9-8f42-fb14e91ef4b8",
- "index": ".kibana",
- "source": {
- "map": {
- "title" : "document example with data driven styles",
- "description" : "",
- "mapStateJSON" : "{\"zoom\":4.1,\"center\":{\"lon\":-100.61091,\"lat\":33.23887},\"timeFilters\":{\"from\":\"2015-09-20T00:00:00.000Z\",\"to\":\"2015-09-20T01:00:00.000Z\"},\"refreshConfig\":{\"isPaused\":true,\"interval\":1000},\"query\":{\"language\":\"kuery\",\"query\":\"\"},\"filters\":[]}",
- "layerListJSON" : "[{\"id\":\"z52lq\",\"label\":\"logstash\",\"minZoom\":0,\"maxZoom\":24,\"sourceDescriptor\":{\"id\":\"e1a5e1a6-676c-4a89-8ea9-0d91d64b73c6\",\"type\":\"ES_SEARCH\",\"geoField\":\"geo.coordinates\",\"limit\":2048,\"filterByMapBounds\":true,\"showTooltip\":true,\"tooltipProperties\":[],\"indexPatternRefName\":\"layer_1_source_index_pattern\"},\"visible\":true,\"temporary\":false,\"style\":{\"type\":\"VECTOR\",\"properties\":{\"fillColor\":{\"type\":\"DYNAMIC\",\"options\":{\"color\":\"Blues\",\"field\":{\"label\":\"hour_of_day\",\"name\":\"hour_of_day\",\"origin\":\"source\"}}},\"lineColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#FFFFFF\"}},\"lineWidth\":{\"type\":\"STATIC\",\"options\":{\"size\":1}},\"iconSize\":{\"type\":\"DYNAMIC\",\"options\":{\"minSize\":4,\"maxSize\":24,\"field\":{\"label\":\"bytes\",\"name\":\"bytes\",\"origin\":\"source\"}}},\"iconOrientation\":{\"type\":\"STATIC\",\"options\":{\"orientation\":0}},\"symbol\":{\"options\":{\"symbolizeAs\":\"circle\",\"symbolId\":\"airfield\"}}}},\"type\":\"VECTOR\"}]",
- "uiStateJSON" : "{\"isLayerTOCOpen\":true,\"openTOCDetails\":[]}",
- "bounds" : {
- "type" : "Polygon",
- "coordinates" : [
- [
- [
- -137.80011,
- 45.99899
- ],
- [
- -137.80011,
- 18.31046
- ],
- [
- -63.42171,
- 18.31046
- ],
- [
- -63.42171,
- 45.99899
- ],
- [
- -137.80011,
- 45.99899
- ]
- ]
- ]
- }
- },
- "type" : "map",
- "references" : [
- {
- "name" : "layer_1_source_index_pattern",
- "type" : "index-pattern",
- "id" : "c698b940-e149-11e8-a35a-370a8516603a"
- }
- ],
- "migrationVersion" : {
- "map" : "7.5.0"
- },
- "updated_at" : "2019-10-10T17:02:48.465Z"
- }
- }
-}
-
-{
- "type": "doc",
- "value": {
- "id": "map:c9277dd0-eb8f-11e9-ae47-693d6a50fb9e",
- "index": ".kibana",
- "source": {
- "map": {
- "title" : "document example with data driven styles on date field",
- "description" : "",
- "mapStateJSON": "{\"zoom\":4.1,\"center\":{\"lon\":-100.61091,\"lat\":33.23887},\"timeFilters\":{\"from\":\"2015-09-20T00:00:00.000Z\",\"to\":\"2015-09-20T01:00:00.000Z\"},\"refreshConfig\":{\"isPaused\":true,\"interval\":1000},\"query\":{\"language\":\"kuery\",\"query\":\"\"},\"filters\":[]}",
- "layerListJSON": "[{\"id\":\"z52lq\",\"label\":\"logstash\",\"minZoom\":0,\"maxZoom\":24,\"sourceDescriptor\":{\"id\":\"e1a5e1a6-676c-4a89-8ea9-0d91d64b73c6\",\"type\":\"ES_SEARCH\",\"geoField\":\"geo.coordinates\",\"limit\":2048,\"filterByMapBounds\":true,\"showTooltip\":true,\"tooltipProperties\":[],\"indexPatternRefName\":\"layer_1_source_index_pattern\"},\"visible\":true,\"temporary\":false,\"style\":{\"type\":\"VECTOR\",\"properties\":{\"fillColor\":{\"type\":\"DYNAMIC\",\"options\":{\"color\":\"Blues\",\"field\":{\"label\":\"@timestamp\",\"name\":\"@timestamp\",\"origin\":\"source\"}}},\"lineColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#FFFFFF\"}},\"lineWidth\":{\"type\":\"STATIC\",\"options\":{\"size\":1}},\"iconSize\":{\"type\":\"DYNAMIC\",\"options\":{\"minSize\":4,\"maxSize\":24,\"field\":{\"label\":\"bytes\",\"name\":\"bytes\",\"origin\":\"source\"}}},\"iconOrientation\":{\"type\":\"STATIC\",\"options\":{\"orientation\":0}},\"symbol\":{\"options\":{\"symbolizeAs\":\"circle\",\"symbolId\":\"airfield\"}}}},\"type\":\"VECTOR\"}]",
- "uiStateJSON": "{\"isLayerTOCOpen\":true,\"openTOCDetails\":[]}",
- "bounds" : {
- "type" : "Polygon",
- "coordinates" : [
- [
- [
- -132.42879,
- 44.5711
- ],
- [
- -132.42879,
- 20.22627
- ],
- [
- -68.79303,
- 20.22627
- ],
- [
- -68.79303,
- 44.5711
- ],
- [
- -132.42879,
- 44.5711
- ]
- ]
- ]
- }
- },
- "type" : "map",
- "references" : [
- {
- "name" : "layer_1_source_index_pattern",
- "type" : "index-pattern",
- "id" : "c698b940-e149-11e8-a35a-370a8516603a"
- }
- ],
- "migrationVersion" : {
- "map" : "7.5.0"
- },
- "updated_at" : "2019-10-10T18:57:19.916Z"
- }
- }
-}
-
-{
- "type": "doc",
- "value": {
- "id": "map:2de4de10-cc82-11ea-9b0a-eb2886fc84af",
- "index": ".kibana",
- "source": {
- "map": {
- "title" : "document example hidden",
- "description" : "",
- "mapStateJSON" : "{\"zoom\":4.1,\"center\":{\"lon\":-100.61091,\"lat\":33.23887},\"timeFilters\":{\"from\":\"2015-09-20T00:00:00.000Z\",\"to\":\"2015-09-20T01:00:00.000Z\"},\"refreshConfig\":{\"isPaused\":true,\"interval\":1000},\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filters\":[],\"settings\":{\"autoFitToDataBounds\":false,\"initialLocation\":\"LAST_SAVED_LOCATION\",\"fixedLocation\":{\"lat\":0,\"lon\":0,\"zoom\":2},\"browserLocation\":{\"zoom\":2},\"maxZoom\":24,\"minZoom\":0,\"showSpatialFilters\":true,\"spatialFiltersAlpa\":0.3,\"spatialFiltersFillColor\":\"#DA8B45\",\"spatialFiltersLineColor\":\"#DA8B45\"}}",
- "layerListJSON" : "[{\"id\":\"z52lq\",\"label\":\"logstash\",\"minZoom\":0,\"maxZoom\":24,\"sourceDescriptor\":{\"id\":\"e1a5e1a6-676c-4a89-8ea9-0d91d64b73c6\",\"type\":\"ES_SEARCH\",\"geoField\":\"geo.coordinates\",\"limit\":2048,\"filterByMapBounds\":true,\"showTooltip\":true,\"tooltipProperties\":[],\"applyGlobalQuery\":true,\"scalingType\":\"LIMIT\",\"indexPatternRefName\":\"layer_1_source_index_pattern\"},\"visible\":false,\"temporary\":false,\"style\":{\"type\":\"VECTOR\",\"properties\":{\"fillColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#e6194b\"}},\"lineColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#FFFFFF\"}},\"lineWidth\":{\"type\":\"STATIC\",\"options\":{\"size\":1}},\"iconSize\":{\"type\":\"STATIC\",\"options\":{\"size\":10}},\"symbolizeAs\":{\"options\":{\"value\":\"circle\"}},\"icon\":{\"type\":\"STATIC\",\"options\":{\"value\":\"marker\"}}},\"previousStyle\":null},\"type\":\"VECTOR\"}]",
- "uiStateJSON" : "{\"isLayerTOCOpen\":true,\"openTOCDetails\":[]}"
- },
- "type" : "map",
- "references" : [
- {
- "name" : "layer_1_source_index_pattern",
- "type" : "index-pattern",
- "id" : "c698b940-e149-11e8-a35a-370a8516603a"
- }
- ],
- "migrationVersion" : {
- "map" : "7.9.0"
- },
- "updated_at" : "2020-07-23T01:16:47.600Z"
- }
- }
-}
-
-
-
-{
- "type": "doc",
- "value": {
- "id": "map:1649cc70-f736-11e8-8ce0-9723965e01e3",
- "index": ".kibana",
- "source": {
- "map": {
- "bounds": {
- "coordinates": [
- [
- 14.1441,
- 31.99653
- ],
- [
- 140.52442,
- -32.07532
- ]
- ],
- "type": "envelope"
- },
- "description": "",
- "layerListJSON" : "[{\"id\":\"n1t6f\",\"label\":null,\"minZoom\":0,\"maxZoom\":24,\"sourceDescriptor\":{\"id\":\"62eca1fc-fe42-11e8-8eb2-f2801f1b9fd1\",\"type\":\"ES_SEARCH\",\"geoField\":\"geometry\",\"limit\":2048,\"filterByMapBounds\":false,\"showTooltip\":true,\"tooltipProperties\":[\"name\"],\"applyGlobalQuery\":false,\"indexPatternRefName\":\"layer_1_source_index_pattern\"},\"visible\":true,\"temporary\":false,\"style\":{\"type\":\"VECTOR\",\"properties\":{\"fillColor\":{\"type\":\"DYNAMIC\",\"options\":{\"fieldMetaOptions\":{\"isEnabled\":false,\"sigma\":3},\"field\":{\"label\":\"max(prop1) group by meta_for_geo_shapes*.runtime_shape_name\",\"name\":\"__kbnjoin__max_of_prop1_groupby_meta_for_geo_shapes*.runtime_shape_name\",\"origin\":\"join\"},\"color\":\"Blues\"}},\"iconSize\":{\"type\":\"STATIC\",\"options\":{\"size\":10}}},\"temporary\":true,\"previousStyle\":null},\"type\":\"VECTOR\",\"joins\":[{\"leftField\":\"name\",\"right\":{\"id\":\"855ccb86-fe42-11e8-8eb2-f2801f1b9fd1\",\"indexPatternTitle\":\"meta_for_geo_shapes*\",\"term\":\"runtime_shape_name\",\"metrics\":[{\"type\":\"max\",\"field\":\"prop1\"}],\"applyGlobalQuery\":true,\"indexPatternRefName\":\"layer_1_join_0_index_pattern\"}}]}]",
- "mapStateJSON": "{\"zoom\":3.02,\"center\":{\"lon\":77.33426,\"lat\":-0.04647},\"timeFilters\":{\"from\":\"now-17m\",\"to\":\"now\",\"mode\":\"quick\"},\"refreshConfig\":{\"isPaused\":true,\"interval\":1000}}",
- "title": "join example",
- "uiStateJSON": "{\"isLayerTOCOpen\":true,\"openTOCDetails\":[\"n1t6f\"]}"
- },
- "type": "map",
- "references" : [
- {
- "name" : "layer_1_source_index_pattern",
- "type" : "index-pattern",
- "id" : "561253e0-f731-11e8-8487-11b9dd924f96"
- },
- {
- "name" : "layer_1_join_0_index_pattern",
- "type" : "index-pattern",
- "id" : "e20b2a30-f735-11e8-8ce0-9723965e01e3"
- }
- ],
- "migrationVersion" : {
- "map" : "7.6.0"
- }
- }
- }
-}
-
-{
- "type": "doc",
- "value": {
- "id": "map:60a3c850-03ae-11e9-83d4-5b39b6575325",
- "index": ".kibana",
- "source": {
- "map": {
- "bounds": {
- "coordinates": [
- [
- -140.62361,
- 54.11832
- ],
- [
- -55.49169,
- 18.17193
- ]
- ],
- "type": "envelope"
- },
- "layerListJSON": "[{\"id\":\"3xlvm\",\"label\":null,\"minZoom\":0,\"maxZoom\":24,\"sourceDescriptor\":{\"resolution\": \"COARSE\",\"type\":\"ES_GEO_GRID\",\"id\":\"427aa49d-a552-4e7d-a629-67c47db27128\",\"indexPatternId\":\"c698b940-e149-11e8-a35a-370a8516603a\",\"geoField\":\"geo.coordinates\",\"requestType\":\"heatmap\"},\"visible\":true,\"temporary\":false,\"style\":{\"type\":\"HEATMAP\",\"refinement\":\"coarse\",\"properties\":{},\"previousStyle\":null},\"type\":\"HEATMAP\"}]",
- "mapStateJSON": "{\"zoom\":3.59,\"center\":{\"lon\":-98.05765,\"lat\":38.32288},\"timeFilters\":{\"from\":\"2015-09-20T00:00:00.000Z\",\"to\":\"2015-09-20T01:00:00.000Z\"},\"refreshConfig\":{\"isPaused\":true,\"interval\":1000}}",
- "title": "geo grid heatmap example",
- "uiStateJSON": "{\"isDarkMode\":false}"
- },
- "type": "map"
- }
- }
-}
-
-{
- "type": "doc",
- "value": {
- "id": "map:88845ec0-03ae-11e9-83d4-5b39b6575325",
- "index": ".kibana",
- "source": {
- "map": {
- "bounds": {
- "coordinates": [
- [
- -140.62361,
- 54.11832
- ],
- [
- -55.49169,
- 18.17193
- ]
- ],
- "type": "envelope"
- },
- "description": "",
- "layerListJSON": "[{\"id\":\"g1xkv\",\"label\":null,\"minZoom\":0,\"maxZoom\":24,\"sourceDescriptor\":{\"resolution\": \"COARSE\",\"type\":\"ES_GEO_GRID\",\"id\":\"9305f6ea-4518-4c06-95b9-33321aa38d6a\",\"indexPatternId\":\"c698b940-e149-11e8-a35a-370a8516603a\",\"geoField\":\"geo.coordinates\",\"requestType\":\"grid\",\"metrics\":[{\"type\":\"count\"},{\"type\":\"max\",\"field\":\"bytes\"}]},\"visible\":true,\"temporary\":false,\"style\":{\"type\":\"VECTOR\",\"properties\":{\"fillColor\":{\"type\":\"DYNAMIC\",\"options\":{\"field\":{\"label\":\"max of bytes\",\"name\":\"max_of_bytes\",\"origin\":\"source\"},\"color\":\"Blues\"}},\"lineColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#cccccc\"}},\"lineWidth\":{\"type\":\"STATIC\",\"options\":{\"size\":1}},\"iconSize\":{\"type\":\"DYNAMIC\",\"options\":{\"field\":{\"label\":\"Count\",\"name\":\"doc_count\",\"origin\":\"source\"},\"minSize\":4,\"maxSize\":32}}},\"temporary\":true,\"previousStyle\":null},\"type\":\"VECTOR\"}]",
- "mapStateJSON": "{\"zoom\":3.59,\"center\":{\"lon\":-98.05765,\"lat\":38.32288},\"timeFilters\":{\"from\":\"2015-09-20T00:00:00.000Z\",\"to\":\"2015-09-20T01:00:00.000Z\"},\"refreshConfig\":{\"isPaused\":true,\"interval\":1000}}",
- "title": "geo grid vector grid example",
- "uiStateJSON": "{\"isDarkMode\":false}"
- },
- "type": "map"
- }
- }
-}
-
-{
- "type": "doc",
- "value": {
- "id": "map:0c86d024-a767-11ea-bb37-0242ac130002",
- "index": ".kibana",
- "source": {
- "map": {
- "bounds": {
- "coordinates": [
- [
- -160,
- 60
- ],
- [
- 160,
- -60
- ]
- ],
- "type": "envelope"
- },
- "description": "",
- "layerListJSON": "[{\"id\":\"g1xkv\",\"label\":null,\"minZoom\":0,\"maxZoom\":24,\"sourceDescriptor\":{\"resolution\": \"COARSE\",\"type\":\"ES_GEO_GRID\",\"id\":\"64ddd934-a767-11ea-bb37-0242ac130002\",\"indexPatternId\":\"561253e0-f731-11e8-8487-11b9dd924f96\",\"geoField\":\"geometry\",\"requestType\":\"grid\",\"metrics\":[{\"type\":\"count\"}]},\"visible\":true,\"temporary\":false,\"style\":{\"type\":\"VECTOR\",\"properties\":{\"fillColor\":{\"type\":\"DYNAMIC\",\"options\":{\"field\":{\"label\":\"Count\",\"name\":\"doc_count\",\"origin\":\"source\"},\"color\":\"Blues\"}},\"lineColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#cccccc\"}},\"lineWidth\":{\"type\":\"STATIC\",\"options\":{\"size\":1}},\"iconSize\":{\"type\":\"DYNAMIC\",\"options\":{\"field\":{\"label\":\"Count\",\"name\":\"doc_count\",\"origin\":\"source\"},\"minSize\":4,\"maxSize\":32}}},\"temporary\":true,\"previousStyle\":null},\"type\":\"VECTOR\"}]",
- "mapStateJSON": "{\"zoom\":3,\"center\":{\"lon\":76,\"lat\":4},\"timeFilters\":{\"from\":\"2015-09-20T00:00:00.000Z\",\"to\":\"2015-09-20T01:00:00.000Z\"},\"refreshConfig\":{\"isPaused\":true,\"interval\":1000}}",
- "title": "geo grid vector grid example with shape",
- "uiStateJSON": "{\"isDarkMode\":false}"
- },
- "type": "map"
- }
- }
-}
-
-{
- "type": "doc",
- "value": {
- "id": "map:78116c8c-fd2a-11ea-adc1-0242ac120002",
- "index": ".kibana",
- "source": {
- "map": {
- "bounds": {
- "coordinates": [
- [
- -140.62361,
- 54.11832
- ],
- [
- -55.49169,
- 18.17193
- ]
- ],
- "type": "envelope"
- },
- "description": "",
- "layerListJSON": "[{\"id\":\"g1xkv\",\"label\":null,\"minZoom\":0,\"maxZoom\":24,\"sourceDescriptor\":{\"resolution\": \"SUPER_FINE\",\"type\":\"ES_GEO_GRID\",\"id\":\"9305f6ea-4518-4c06-95b9-33321aa38d6a\",\"indexPatternId\":\"c698b940-e149-11e8-a35a-370a8516603a\",\"geoField\":\"geo.coordinates\",\"requestType\":\"grid\",\"metrics\":[{\"type\":\"count\"},{\"type\":\"max\",\"field\":\"bytes\"}]},\"visible\":true,\"temporary\":false,\"style\":{\"type\":\"VECTOR\",\"properties\":{\"fillColor\":{\"type\":\"DYNAMIC\",\"options\":{\"field\":{\"label\":\"max of bytes\",\"name\":\"max_of_bytes\",\"origin\":\"source\"},\"color\":\"Blues\"}},\"lineColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#cccccc\"}},\"lineWidth\":{\"type\":\"STATIC\",\"options\":{\"size\":1}},\"iconSize\":{\"type\":\"DYNAMIC\",\"options\":{\"field\":{\"label\":\"Count\",\"name\":\"doc_count\",\"origin\":\"source\"},\"minSize\":4,\"maxSize\":32}}},\"temporary\":true,\"previousStyle\":null},\"type\":\"TILED_VECTOR\"}]",
- "mapStateJSON": "{\"zoom\":3.59,\"center\":{\"lon\":-98.05765,\"lat\":38.32288},\"timeFilters\":{\"from\":\"2015-09-20T00:00:00.000Z\",\"to\":\"2015-09-20T01:00:00.000Z\"},\"refreshConfig\":{\"isPaused\":true,\"interval\":1000}}",
- "title": "geo grid vector grid example SUPER_FINE resolution",
- "uiStateJSON": "{\"isDarkMode\":false}"
- },
- "type": "map"
- }
- }
-}
-
-{
- "type": "doc",
- "value": {
- "id": "map:346fbec0-1811-11e9-b78a-23d706cd2507",
- "index": ".kibana",
- "source": {
- "map": {
- "bounds": {
- "coordinates": [
- [
- [
- 172.235,
- 6.35533
- ],
- [
- 172.235,
- -6.16462
- ],
- [
- -174.17114,
- -6.16462
- ],
- [
- -174.17114,
- 6.35533
- ],
- [
- 172.235,
- 6.35533
- ]
- ]
- ],
- "type": "polygon"
- },
- "description": "",
- "layerListJSON": "[{\"id\":\"frk92\",\"label\":null,\"minZoom\":0,\"maxZoom\":24,\"sourceDescriptor\":{\"id\":\"7d807c75-088a-44b7-920a-e7e47f4fc038\",\"type\":\"ES_SEARCH\",\"indexPatternId\":\"1035e930-1811-11e9-b78a-23d706cd2507\",\"geoField\":\"location\",\"limit\":2048,\"filterByMapBounds\":true,\"tooltipProperties\":[]},\"visible\":true,\"temporary\":false,\"style\":{\"type\":\"VECTOR\",\"properties\":{\"fillColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#e6194b\"}},\"lineColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#FFFFFF\"}},\"lineWidth\":{\"type\":\"STATIC\",\"options\":{\"size\":1}},\"iconSize\":{\"type\":\"STATIC\",\"options\":{\"size\":10}}},\"previousStyle\":null},\"type\":\"VECTOR\"}]",
- "mapStateJSON": "{\"zoom\":8.8,\"center\":{\"lon\":-179.98743,\"lat\":-0.09561},\"timeFilters\":{\"from\":\"now-15m\",\"to\":\"now\",\"mode\":\"quick\"},\"refreshConfig\":{\"isPaused\":true,\"interval\":0},\"query\":{\"query\":\"\",\"language\":\"kuery\"}}",
- "title": "antimeridian points example",
- "uiStateJSON": "{\"isDarkMode\":false}"
- },
- "type": "map"
- }
- }
-}
-
-{
- "type": "doc",
- "value": {
- "id": "map:6a6ff930-18f8-11e9-97c8-5da5e037299c",
- "index": ".kibana",
- "source": {
- "map": {
- "bounds": {
- "coordinates": [
- [
- [
- 172.235,
- 6.35533
- ],
- [
- 172.235,
- -6.16462
- ],
- [
- -174.17114,
- -6.16462
- ],
- [
- -174.17114,
- 6.35533
- ],
- [
- 172.235,
- 6.35533
- ]
- ]
- ],
- "type": "polygon"
- },
- "description": "",
- "layerListJSON": "[{\"id\":\"ad9fj\",\"label\":null,\"minZoom\":0,\"maxZoom\":24,\"sourceDescriptor\":{\"id\":\"4e4b5628-dbdc-40bb-93f0-8a7a48be1141\",\"type\":\"ES_SEARCH\",\"indexPatternId\":\"502886a0-18f8-11e9-97c8-5da5e037299c\",\"geoField\":\"location\",\"limit\":2048,\"filterByMapBounds\":true,\"tooltipProperties\":[]},\"visible\":true,\"temporary\":true,\"style\":{\"type\":\"VECTOR\",\"properties\":{\"fillColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#e6194b\"}},\"lineColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#FFFFFF\"}},\"lineWidth\":{\"type\":\"STATIC\",\"options\":{\"size\":1}},\"iconSize\":{\"type\":\"STATIC\",\"options\":{\"size\":10}}}},\"type\":\"VECTOR\"}]",
- "mapStateJSON": "{\"zoom\":5.65,\"center\":{\"lon\":179.03193,\"lat\":0.09593},\"timeFilters\":{\"from\":\"now-15m\",\"to\":\"now\"},\"refreshConfig\":{\"isPaused\":true,\"interval\":0},\"query\":{\"language\":\"kuery\",\"query\":\"\"}}",
- "title": "antimeridian shapes example",
- "uiStateJSON": "{\"isDarkMode\":false}"
- },
- "type": "map"
- }
- }
-}
-
-{
- "type": "doc",
- "value": {
- "id": "map:745c98b0-23e1-11e9-a048-6fef5a3e0d1e",
- "index": ".kibana",
- "source": {
- "map": {
- "bounds": {
- "coordinates": [
- [
- [
- -180,
- 85.05113
- ],
- [
- -180,
- -85.05113
- ],
- [
- 180,
- -85.05113
- ],
- [
- 180,
- 85.05113
- ],
- [
- -180,
- 85.05113
- ]
- ]
- ],
- "type": "polygon"
- },
- "description": "",
- "layerListJSON" : "[{\"sourceDescriptor\":{\"type\":\"KIBANA_TILEMAP\"},\"id\":\"ap0ys\",\"label\":\"Custom_TMS\",\"minZoom\":0,\"maxZoom\":24,\"alpha\":1,\"visible\":true,\"temporary\":false,\"style\":{\"type\":\"TILE\",\"properties\":{},\"previousStyle\":null},\"type\":\"TILE\"},{\"sourceDescriptor\":{\"type\":\"REGIONMAP_FILE\",\"name\":\"nameThatDoesNotExitForKibanaRegionmapSource\"},\"temporary\":false,\"id\":\"0sabv\",\"label\":\"Custom_vector_shapes\",\"minZoom\":0,\"maxZoom\":24,\"alpha\":0.75,\"visible\":true,\"style\":{\"type\":\"VECTOR\",\"properties\":{\"fillColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#3cb44b\"}},\"lineColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#FFFFFF\"}},\"lineWidth\":{\"type\":\"STATIC\",\"options\":{\"size\":1}},\"iconSize\":{\"type\":\"STATIC\",\"options\":{\"size\":10}}},\"previousStyle\":null},\"type\":\"VECTOR\"},{\"sourceDescriptor\":{\"type\":\"EMS_TMS\",\"id\":\"idThatDoesNotExitForEMSTile\"},\"temporary\":false,\"id\":\"plw9l\",\"label\":\"EMS_tiles\",\"minZoom\":0,\"maxZoom\":24,\"alpha\":1,\"visible\":true,\"style\":{\"type\":\"TILE\",\"properties\":{},\"previousStyle\":null},\"type\":\"TILE\"},{\"sourceDescriptor\":{\"type\":\"EMS_FILE\",\"id\":\"idThatDoesNotExitForEMSFileSource\"},\"temporary\":false,\"id\":\"2gro0\",\"label\":\"EMS_vector_shapes\",\"minZoom\":0,\"maxZoom\":24,\"alpha\":0.75,\"visible\":true,\"style\":{\"type\":\"VECTOR\",\"properties\":{\"fillColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#e6194b\"}},\"lineColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#FFFFFF\"}},\"lineWidth\":{\"type\":\"STATIC\",\"options\":{\"size\":1}},\"iconSize\":{\"type\":\"STATIC\",\"options\":{\"size\":10}}},\"previousStyle\":null},\"type\":\"VECTOR\"},{\"sourceDescriptor\":{\"type\":\"ES_GEO_GRID\",\"id\":\"f67fe707-95dd-46d6-89b8-82617b251b61\",\"indexPatternId\":\"idThatDoesNotExitForESGeoGridSource\",\"geoField\":\"geo.coordinates\",\"requestType\":\"grid\",\"resolution\":\"COARSE\"},\"temporary\":false,\"id\":\"pl5qd\",\"label\":\"\",\"minZoom\":0,\"maxZoom\":24,\"alpha\":0.75,\"visible\":true,\"style\":{\"type\":\"VECTOR\",\"properties\":{\"fillColor\":{\"type\":\"DYNAMIC\",\"options\":{\"field\":{\"label\":\"Count\",\"name\":\"doc_count\",\"origin\":\"source\"},\"color\":\"Blues\"}},\"lineColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#FFFFFF\"}},\"lineWidth\":{\"type\":\"STATIC\",\"options\":{\"size\":1}},\"iconSize\":{\"type\":\"DYNAMIC\",\"options\":{\"field\":{\"label\":\"Count\",\"name\":\"doc_count\",\"origin\":\"source\"},\"minSize\":4,\"maxSize\":32}}},\"previousStyle\":null},\"type\":\"VECTOR\"},{\"sourceDescriptor\":{\"id\":\"a07072bb-3a92-4320-bd37-250ef6d04db7\",\"type\":\"ES_SEARCH\",\"indexPatternId\":\"idThatDoesNotExitForESSearchSource\",\"geoField\":\"geo.coordinates\",\"limit\":2048,\"filterByMapBounds\":true,\"tooltipProperties\":[]},\"temporary\":false,\"id\":\"9bw8h\",\"label\":\"\",\"minZoom\":0,\"maxZoom\":24,\"alpha\":0.75,\"visible\":true,\"style\":{\"type\":\"VECTOR\",\"properties\":{\"fillColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#e6194b\"}},\"lineColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#FFFFFF\"}},\"lineWidth\":{\"type\":\"STATIC\",\"options\":{\"size\":1}},\"iconSize\":{\"type\":\"STATIC\",\"options\":{\"size\":10}}},\"previousStyle\":null},\"type\":\"VECTOR\"},{\"id\":\"n1t6f\",\"label\":null,\"minZoom\":0,\"maxZoom\":24,\"sourceDescriptor\":{\"id\":\"62eca1fc-fe42-11e8-8eb2-f2801f1b9fd1\",\"type\":\"ES_SEARCH\",\"indexPatternId\":\"561253e0-f731-11e8-8487-11b9dd924f96\",\"geoField\":\"geometry\",\"limit\":2048,\"filterByMapBounds\":false,\"showTooltip\":true,\"tooltipProperties\":[]},\"visible\":true,\"temporary\":false,\"style\":{\"type\":\"VECTOR\",\"properties\":{\"fillColor\":{\"type\":\"DYNAMIC\",\"options\":{\"field\":{\"label\":\"max(prop1) group by meta_for_geo_shapes*.shape_name\",\"name\":\"__kbnjoin__max_of_prop1_groupby_meta_for_geo_shapes*.shape_name\",\"origin\":\"join\"},\"color\":\"Blues\"}},\"iconSize\":{\"type\":\"STATIC\",\"options\":{\"size\":10}}},\"temporary\":true,\"previousStyle\":null},\"type\":\"VECTOR\",\"joins\":[{\"leftField\":\"name\",\"right\":{\"id\":\"855ccb86-fe42-11e8-8eb2-f2801f1b9fd1\",\"indexPatternId\":\"idThatDoesNotExitForESJoinSource\",\"indexPatternTitle\":\"meta_for_geo_shapes*\",\"term\":\"shape_name\",\"metrics\":[{\"type\":\"max\",\"field\":\"prop1\"}]}}]}]",
- "mapStateJSON": "{\"zoom\":0.71,\"center\":{\"lon\":0.10268,\"lat\":0},\"timeFilters\":{\"from\":\"now-7d\",\"to\":\"now\"},\"refreshConfig\":{\"isPaused\":true,\"interval\":0},\"query\":{\"query\":\"\",\"language\":\"kuery\"}}",
- "title": "layer with errors",
- "uiStateJSON": "{}"
- },
- "type": "map"
- }
- }
-}
-
-{
- "type": "doc",
- "value": {
- "id": "map:62914770-936c-11e9-a43f-eb6ee3467b2b",
- "index": ".kibana",
- "source": {
- "map": {
- "title" : "vector styling icon demo",
- "description" : "",
- "mapStateJSON" : "{\"zoom\":4.13,\"center\":{\"lon\":-3.57807,\"lat\":2.04204},\"timeFilters\":{\"from\":\"now-15m\",\"to\":\"now\"},\"refreshConfig\":{\"isPaused\":false,\"interval\":0},\"query\":{\"language\":\"kuery\",\"query\":\"\"}}",
- "layerListJSON" : "[{\"sourceDescriptor\":{\"id\":\"5b46884e-062f-4ff4-9dd2-bd410d9e8754\",\"type\":\"ES_SEARCH\",\"geoField\":\"location\",\"filterByMapBounds\":false,\"tooltipProperties\":[],\"useTopHits\":false,\"topHitsSize\":1,\"indexPatternRefName\":\"layer_0_source_index_pattern\"},\"id\":\"8fcb7c4b-7d6a-4b69-82c2-74f53ec0df24\",\"label\":null,\"minZoom\":0,\"maxZoom\":24,\"alpha\":0.75,\"visible\":true,\"applyGlobalQuery\":true,\"style\":{\"type\":\"VECTOR\",\"properties\":{\"fillColor\":{\"type\":\"DYNAMIC\",\"options\":{\"color\":\"Reds\",\"field\":{\"label\":\"altitude\",\"name\":\"altitude\",\"origin\":\"source\"}}},\"lineColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#FFFFFF\"}},\"lineWidth\":{\"type\":\"STATIC\",\"options\":{\"size\":1}},\"iconSize\":{\"type\":\"DYNAMIC\",\"options\":{\"minSize\":30,\"maxSize\":64,\"field\":{\"label\":\"altitude\",\"name\":\"altitude\",\"origin\":\"source\"}}},\"iconOrientation\":{\"type\":\"DYNAMIC\",\"options\":{\"field\":{\"label\":\"heading\",\"name\":\"heading\",\"origin\":\"source\"}}},\"symbol\":{\"options\":{\"symbolizeAs\":\"icon\",\"symbolId\":\"airport\"}}}},\"type\":\"VECTOR\"}]",
- "uiStateJSON" : "{\"isLayerTOCOpen\":true,\"openTOCDetails\":[\"8fcb7c4b-7d6a-4b69-82c2-74f53ec0df24\"]}",
- "bounds" : {
- "type" : "polygon",
- "coordinates" : [
- [
- [
- -40.00193,
- 19.58063
- ],
- [
- -40.00193,
- -15.68855
- ],
- [
- 32.84579,
- -15.68855
- ],
- [
- 32.84579,
- 19.58063
- ],
- [
- -40.00193,
- 19.58063
- ]
- ]
- ]
- }
- },
- "type": "map",
- "references" : [
- {
- "name" : "layer_0_source_index_pattern",
- "type" : "index-pattern",
- "id" : "f7fc94a0-936b-11e9-a43f-eb6ee3467b2b"
- }
- ],
- "migrationVersion" : {
- "map" : "7.2.0"
- },
- "updated_at" : "2019-06-20T15:02:13.095Z"
- }
- }
-}
-
-{
- "type": "doc",
- "value": {
- "id": "map:4cd3e220-bf64-11e9-bbcc-7db09a1519e9",
- "index": ".kibana",
- "source": {
- "map": {
- "title" : "join and dynamic coloring demo",
- "description" : "",
- "mapStateJSON" : "{\"zoom\":3.42,\"center\":{\"lon\":81.67747,\"lat\":1.80586},\"timeFilters\":{\"from\":\"now-17m\",\"to\":\"now\",\"mode\":\"quick\"},\"refreshConfig\":{\"isPaused\":true,\"interval\":1000},\"query\":{\"language\":\"kuery\",\"query\":\"\"},\"filters\":[]}",
- "layerListJSON" : "[{\"id\":\"n1t6f\",\"label\":null,\"minZoom\":0,\"maxZoom\":24,\"sourceDescriptor\":{\"id\":\"62eca1fc-fe42-11e8-8eb2-f2801f1b9fd1\",\"type\":\"ES_SEARCH\",\"geoField\":\"geometry\",\"limit\":2048,\"filterByMapBounds\":false,\"showTooltip\":true,\"tooltipProperties\":[\"name\"],\"indexPatternRefName\":\"layer_0_source_index_pattern\"},\"visible\":true,\"temporary\":false,\"style\":{\"type\":\"VECTOR\",\"properties\":{\"fillColor\":{\"type\":\"DYNAMIC\",\"options\":{\"field\":{\"label\":\"max(prop1) group by meta_for_geo_shapes*.shape_name\",\"name\":\"__kbnjoin__max_of_prop1_groupby_meta_for_geo_shapes*.shape_name\",\"origin\":\"join\"},\"color\":null,\"useCustomColorRamp\":true,\"customColorRamp\":[{\"stop\":0,\"color\":\"#E6C220\"},{\"stop\":5,\"color\":\"#F98510\"},{\"stop\":11,\"color\":\"#D36086\"}]}},\"lineColor\":{\"type\":\"DYNAMIC\",\"options\":{\"color\":\"Blues\",\"field\":{\"label\":\"prop1\",\"name\":\"prop1\",\"origin\":\"source\"},\"useCustomColorRamp\":false}},\"lineWidth\":{\"type\":\"STATIC\",\"options\":{\"size\":10}},\"iconSize\":{\"type\":\"STATIC\",\"options\":{\"size\":10}},\"iconOrientation\":{\"type\":\"STATIC\",\"options\":{\"orientation\":0}},\"symbol\":{\"options\":{\"symbolizeAs\":\"circle\",\"symbolId\":\"airfield\"}}}},\"type\":\"VECTOR\",\"joins\":[{\"leftField\":\"name\",\"right\":{\"id\":\"855ccb86-fe42-11e8-8eb2-f2801f1b9fd1\",\"indexPatternTitle\":\"meta_for_geo_shapes*\",\"term\":\"shape_name\",\"metrics\":[{\"type\":\"max\",\"field\":\"prop1\"}],\"indexPatternRefName\":\"layer_0_join_0_index_pattern\"}}]}]",
- "uiStateJSON" : "{\"isLayerTOCOpen\":true,\"openTOCDetails\":[]}",
- "bounds" : {
- "type" : "Polygon",
- "coordinates" : [
- [
- [
- 29.45263,
- 23.75812
- ],
- [
- 29.45263,
- -20.41148
- ],
- [
- 133.90231,
- -20.41148
- ],
- [
- 133.90231,
- 23.75812
- ],
- [
- 29.45263,
- 23.75812
- ]
- ]
- ]
- }
- },
- "type": "map",
- "references" : [
- {
- "name" : "layer_0_source_index_pattern",
- "type" : "index-pattern",
- "id" : "561253e0-f731-11e8-8487-11b9dd924f96"
- },
- {
- "name" : "layer_0_join_0_index_pattern",
- "type" : "index-pattern",
- "id" : "e20b2a30-f735-11e8-8ce0-9723965e01e3"
- }
- ],
- "migrationVersion" : {
- "map" : "7.2.0"
- },
- "updated_at" : "2019-08-15T13:56:15.793Z"
- }
- }
-}
-
-{
- "type": "doc",
- "value": {
- "id": "map:3c9949f0-c8dc-11e9-9ea1-8b2710d4a86b",
- "index": ".kibana",
- "source": {
- "map" : {
- "title" : "pew pew demo",
- "description" : "",
- "mapStateJSON" : "{\"zoom\":5.5,\"center\":{\"lon\":-71.34293,\"lat\":40.33097},\"timeFilters\":{\"from\":\"now-15m\",\"to\":\"now\"},\"refreshConfig\":{\"isPaused\":false,\"interval\":0},\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filters\":[]}",
- "layerListJSON" : "[{\"style\":{\"type\":\"VECTOR\",\"properties\":{\"fillColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#1EA593\"}},\"lineColor\":{\"type\":\"DYNAMIC\",\"options\":{\"field\":{\"label\":\"count\",\"name\":\"doc_count\",\"origin\":\"source\"},\"color\":\"Blues\"}},\"lineWidth\":{\"type\":\"DYNAMIC\",\"options\":{\"field\":{\"label\":\"count\",\"name\":\"doc_count\",\"origin\":\"source\"},\"minSize\":4,\"maxSize\":32}},\"iconSize\":{\"type\":\"STATIC\",\"options\":{\"size\":10}},\"iconOrientation\":{\"type\":\"STATIC\",\"options\":{\"orientation\":0}},\"symbol\":{\"options\":{\"symbolizeAs\":\"circle\",\"symbolId\":\"airfield\"}}}},\"sourceDescriptor\":{\"type\":\"ES_PEW_PEW\",\"id\":\"d7ca27a8-dba2-4f5b-857c-8f529511ad81\",\"sourceGeoField\":\"source\",\"destGeoField\":\"destination\",\"indexPatternRefName\":\"layer_0_source_index_pattern\"},\"id\":\"67c1de2c-2fc5-4425-8983-094b589afe61\",\"label\":null,\"minZoom\":0,\"maxZoom\":24,\"alpha\":0.75,\"visible\":true,\"applyGlobalQuery\":true,\"type\":\"VECTOR\"}]",
- "uiStateJSON" : "{\"isLayerTOCOpen\":true,\"openTOCDetails\":[]}",
- "bounds" : {
- "type" : "Polygon",
- "coordinates" : [
- [
- [
- -83.72374,
- 44.30172
- ],
- [
- -83.72374,
- 36.11211
- ],
- [
- -58.96211,
- 36.11211
- ],
- [
- -58.96211,
- 44.30172
- ],
- [
- -83.72374,
- 44.30172
- ]
- ]
- ]
- }
- },
- "type" : "map",
- "references" : [
- {
- "name" : "layer_0_source_index_pattern",
- "type" : "index-pattern",
- "id" : "dedd3180-c8d8-11e9-b36c-81f9f9da524f"
- }
- ],
- "migrationVersion" : {
- "map" : "7.4.0"
- },
- "updated_at" : "2019-08-27T15:06:24.654Z"
- }
- }
-}
-
-{
- "type": "doc",
- "value": {
- "id": "map:279e1f20-6883-11ea-952a-b102add99cf8",
- "index": ".kibana",
- "source": {
- "map" : {
- "title" : "blended document example",
- "description" : "",
- "mapStateJSON" : "{\"zoom\":10.27,\"center\":{\"lon\":-83.70716,\"lat\":32.73679},\"timeFilters\":{\"from\":\"2015-09-20T00:00:00.000Z\",\"to\":\"2015-09-23T00:00:00.000Z\"},\"refreshConfig\":{\"isPaused\":true,\"interval\":0},\"query\":{\"language\":\"kuery\",\"query\":\"\"},\"filters\":[]}",
- "layerListJSON" : "[{\"id\":\"307c8495-89f7-431b-83d8-78724d9a8f72\",\"label\":\"logstash-*\",\"sourceDescriptor\":{\"geoField\":\"geo.coordinates\",\"id\":\"20fc58c3-3c0a-4c7b-9cdc-37552cafdc21\",\"tooltipProperties\":[],\"type\":\"ES_SEARCH\",\"scalingType\":\"CLUSTERS\",\"indexPatternRefName\":\"layer_1_source_index_pattern\"},\"type\":\"BLENDED_VECTOR\",\"visible\":true,\"style\":{\"type\":\"VECTOR\",\"properties\":{\"icon\":{\"type\":\"STATIC\",\"options\":{\"value\":\"airfield\"}},\"fillColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#54B399\"}},\"lineColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#41937c\"}},\"lineWidth\":{\"type\":\"STATIC\",\"options\":{\"size\":1}},\"iconSize\":{\"type\":\"STATIC\",\"options\":{\"size\":6}},\"iconOrientation\":{\"type\":\"STATIC\",\"options\":{\"orientation\":0}},\"labelText\":{\"type\":\"STATIC\",\"options\":{\"value\":\"\"}},\"labelColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#000000\"}},\"labelSize\":{\"type\":\"STATIC\",\"options\":{\"size\":14}},\"labelBorderColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#FFFFFF\"}},\"symbolizeAs\":{\"options\":{\"value\":\"circle\"}},\"labelBorderSize\":{\"options\":{\"size\":\"SMALL\"}}},\"isTimeAware\":true}}]",
- "uiStateJSON" : "{\"isLayerTOCOpen\":true,\"openTOCDetails\":[]}",
- "bounds" : {
- "type" : "Polygon",
- "coordinates" : [
- [
- [
- -84.07816,
- 32.95327
- ],
- [
- -84.07816,
- 32.51978
- ],
- [
- -83.33616,
- 32.51978
- ],
- [
- -83.33616,
- 32.95327
- ],
- [
- -84.07816,
- 32.95327
- ]
- ]
- ]
- }
- },
- "type" : "map",
- "references" : [
- {
- "name" : "layer_1_source_index_pattern",
- "type" : "index-pattern",
- "id" : "c698b940-e149-11e8-a35a-370a8516603a"
- }
- ],
- "migrationVersion" : {
- "map" : "7.7.0"
- },
- "updated_at" : "2020-03-17T19:11:50.290Z"
- }
- }
-}
-
-{
- "type": "doc",
- "value": {
- "id": "map:13776f20-db37-11ea-8fbb-3da39bb9bff2",
- "index": ".kibana",
- "source": {
- "map" : {
- "title" : "document example - auto fit to bounds for initial location",
- "description" : "",
- "mapStateJSON" : "{\"zoom\":5.2,\"center\":{\"lon\":-67.80052,\"lat\":-55.25331},\"timeFilters\":{\"from\":\"2015-09-20T00:00:00.000Z\",\"to\":\"2015-09-20T01:00:00.000Z\"},\"refreshConfig\":{\"isPaused\":true,\"interval\":1000},\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filters\":[],\"settings\":{\"autoFitToDataBounds\":false,\"initialLocation\":\"AUTO_FIT_TO_BOUNDS\",\"fixedLocation\":{\"lat\":0,\"lon\":0,\"zoom\":2},\"browserLocation\":{\"zoom\":2},\"maxZoom\":24,\"minZoom\":0,\"showSpatialFilters\":true,\"spatialFiltersAlpa\":0.3,\"spatialFiltersFillColor\":\"#DA8B45\",\"spatialFiltersLineColor\":\"#DA8B45\"}}",
- "layerListJSON" : "[{\"id\":\"z52lq\",\"label\":\"logstash\",\"minZoom\":0,\"maxZoom\":24,\"sourceDescriptor\":{\"id\":\"e1a5e1a6-676c-4a89-8ea9-0d91d64b73c6\",\"type\":\"ES_SEARCH\",\"geoField\":\"geo.coordinates\",\"limit\":2048,\"filterByMapBounds\":true,\"showTooltip\":true,\"tooltipProperties\":[],\"applyGlobalQuery\":true,\"scalingType\":\"LIMIT\",\"indexPatternRefName\":\"layer_1_source_index_pattern\"},\"visible\":true,\"temporary\":false,\"style\":{\"type\":\"VECTOR\",\"properties\":{\"fillColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#e6194b\"}},\"lineColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#FFFFFF\"}},\"lineWidth\":{\"type\":\"STATIC\",\"options\":{\"size\":1}},\"iconSize\":{\"type\":\"STATIC\",\"options\":{\"size\":10}},\"symbolizeAs\":{\"options\":{\"value\":\"circle\"}},\"icon\":{\"type\":\"STATIC\",\"options\":{\"value\":\"marker\"}}},\"previousStyle\":null},\"type\":\"VECTOR\"}]",
- "uiStateJSON" : "{\"isLayerTOCOpen\":true,\"openTOCDetails\":[]}"
- },
- "type" : "map",
- "references" : [
- {
- "name" : "layer_1_source_index_pattern",
- "type" : "index-pattern",
- "id" : "c698b940-e149-11e8-a35a-370a8516603a"
- }
- ],
- "migrationVersion" : {
- "map" : "7.9.0"
- },
- "updated_at" : "2020-08-10T18:27:39.805Z"
- }
- }
-}
-
-{
- "type": "doc",
- "value": {
- "id": "map:bff99716-e3dc-11ea-87d0-0242ac130003",
- "index": ".kibana",
- "source": {
- "map" : {
- "description":"shapes with mvt scaling",
- "layerListJSON":"[{\"sourceDescriptor\":{\"geoField\":\"geometry\",\"filterByMapBounds\":true,\"scalingType\":\"MVT\",\"topHitsSize\":1,\"id\":\"97f8555e-8db0-4bd8-8b18-22e32f468667\",\"type\":\"ES_SEARCH\",\"tooltipProperties\":[],\"sortField\":\"\",\"sortOrder\":\"desc\",\"indexPatternRefName\":\"layer_1_source_index_pattern\"},\"id\":\"caffa63a-ebfb-466d-8ff6-d797975b88ab\",\"label\":null,\"minZoom\":0,\"maxZoom\":24,\"alpha\":1,\"visible\":true,\"style\":{\"type\":\"VECTOR\",\"properties\":{\"icon\":{\"type\":\"STATIC\",\"options\":{\"value\":\"marker\"}},\"fillColor\":{\"type\":\"DYNAMIC\",\"options\":{\"color\":\"Blues\",\"colorCategory\":\"palette_0\",\"field\":{\"name\":\"prop1\",\"origin\":\"source\"},\"fieldMetaOptions\":{\"isEnabled\":true,\"sigma\":1},\"type\":\"ORDINAL\"}},\"lineColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#41937c\"}},\"lineWidth\":{\"type\":\"STATIC\",\"options\":{\"size\":1}},\"iconSize\":{\"type\":\"STATIC\",\"options\":{\"size\":6}},\"iconOrientation\":{\"type\":\"STATIC\",\"options\":{\"orientation\":0}},\"labelText\":{\"type\":\"STATIC\",\"options\":{\"value\":\"\"}},\"labelColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#000000\"}},\"labelSize\":{\"type\":\"STATIC\",\"options\":{\"size\":14}},\"labelBorderColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#FFFFFF\"}},\"symbolizeAs\":{\"options\":{\"value\":\"circle\"}},\"labelBorderSize\":{\"options\":{\"size\":\"SMALL\"}}},\"isTimeAware\":true},\"type\":\"TILED_VECTOR\",\"joins\":[]}]",
- "mapStateJSON":"{\"zoom\":3.75,\"center\":{\"lon\":80.01106,\"lat\":3.65009},\"timeFilters\":{\"from\":\"now-15m\",\"to\":\"now\"},\"refreshConfig\":{\"isPaused\":true,\"interval\":0},\"query\":{\"language\":\"kuery\",\"query\":\"\"},\"filters\":[],\"settings\":{\"autoFitToDataBounds\":false,\"initialLocation\":\"LAST_SAVED_LOCATION\",\"fixedLocation\":{\"lat\":0,\"lon\":0,\"zoom\":2},\"browserLocation\":{\"zoom\":2},\"maxZoom\":24,\"minZoom\":0,\"showSpatialFilters\":true,\"spatialFiltersAlpa\":0.3,\"spatialFiltersFillColor\":\"#DA8B45\",\"spatialFiltersLineColor\":\"#DA8B45\"}}",
- "title":"geo_shape_mvt",
- "uiStateJSON":"{\"isLayerTOCOpen\":true,\"openTOCDetails\":[]}"
- },
- "type" : "map",
- "references" : [
- {
- "id":"561253e0-f731-11e8-8487-11b9dd924f96",
- "name":"layer_1_source_index_pattern",
- "type":"index-pattern"
- }
- ],
- "migrationVersion" : {
- "map" : "7.9.0"
- },
- "updated_at" : "2020-08-10T18:27:39.805Z"
- }
- }
-}
-
-
-
-
-
-
-{
- "type": "doc",
- "value": {
- "id": "dashboard:19906970-2e40-11e9-85cb-6965aae20f13",
- "index": ".kibana",
- "source": {
- "dashboard": {
- "description": "",
- "hits": 0,
- "kibanaSavedObjectMeta": {
- "searchSourceJSON": "{\"query\":{\"query\":\"\",\"language\":\"lucene\"}}"
- },
- "optionsJSON": "{\"darkTheme\":false,\"useMargins\":true,\"hidePanelTitles\":false}",
- "panelsJSON": "[{\"embeddableConfig\":{},\"gridData\":{\"x\":0,\"y\":0,\"w\":24,\"h\":20,\"i\":\"1\"},\"id\":\"1649cc70-f736-11e8-8ce0-9723965e01e3\",\"panelIndex\":\"1\",\"type\":\"map\",\"version\":\"7.0.0-alpha1\"},{\"embeddableConfig\":{},\"gridData\":{\"x\":24,\"y\":0,\"w\":24,\"h\":20,\"i\":\"2\"},\"id\":\"88845ec0-03ae-11e9-83d4-5b39b6575325\",\"panelIndex\":\"2\",\"type\":\"map\",\"version\":\"7.0.0-alpha1\"}]",
- "refreshInterval": {
- "pause": true,
- "value": 1000
- },
- "timeFrom": "2015-09-20T00:00:00.000Z",
- "timeRestore": true,
- "timeTo": "2015-09-20T01:00:00.000Z",
- "title": "map embeddable example",
- "version": 1
- },
- "type": "dashboard",
- "updated_at": "2018-04-11T21:57:52.253Z"
- }
- }
-}
-
-{
- "type": "doc",
- "value": {
- "id": "dashboard:03c7cbf0-8eae-11e9-b674-69d1999628e4",
- "index": ".kibana",
- "source": {
- "dashboard": {
- "title" : "dash for tooltip filter action test",
- "hits" : 0,
- "description" : "Zoomed in so entire screen is covered by filter so click to open tooltip can not miss.",
- "panelsJSON" : "[{\"version\":\"8.0.0\",\"gridData\":{\"x\":0,\"y\":0,\"w\":48,\"h\":26,\"i\":\"1\"},\"panelIndex\":\"1\",\"embeddableConfig\":{\"mapCenter\":{\"lat\":-1.31919,\"lon\":59.53306,\"zoom\":9.67},\"isLayerTOCOpen\":false,\"openTOCDetails\":[\"n1t6f\"],\"hiddenLayers\":[],\"enhancements\":{\"dynamicActions\":{\"events\":[{\"eventId\":\"669a3521-1215-4228-9ced-77e2edf5ad17\",\"triggers\":[\"FILTER_TRIGGER\"],\"action\":{\"name\":\"drilldown1\",\"config\":{\"useCurrentFilters\":true,\"useCurrentDateRange\":true},\"factoryId\":\"DASHBOARD_TO_DASHBOARD_DRILLDOWN\"}},{\"eventId\":\"b9c20d96-03ce-4dcc-8823-e3503311172e\",\"triggers\":[\"VALUE_CLICK_TRIGGER\"],\"action\":{\"name\":\"urlDrilldownToDiscover\",\"config\":{\"url\":{\"template\":\"{{kibanaUrl}}/app/discover#/?_a=(columns:!(_source),filters:!(('$state':(store:appState),meta:(alias:!n,disabled:!f,index:'561253e0-f731-11e8-8487-11b9dd924f96',key:{{event.key}},negate:!f,params:(query:{{event.value}}),type:phrase),query:(match_phrase:({{event.key}}:{{event.value}})))),index:'561253e0-f731-11e8-8487-11b9dd924f96',interval:auto,query:(language:kuery,query:''),sort:!())\"},\"openInNewTab\":false},\"factoryId\":\"URL_DRILLDOWN\"}}]}}},\"panelRefName\":\"panel_0\"}]",
- "optionsJSON" : "{\"useMargins\":true,\"hidePanelTitles\":false}",
- "version" : 1,
- "timeRestore" : true,
- "timeTo" : "2015-09-20T01:00:00.000Z",
- "timeFrom" : "2015-09-20T00:00:00.000Z",
- "refreshInterval" : {
- "pause" : true,
- "value" : 1000
- },
- "kibanaSavedObjectMeta" : {
- "searchSourceJSON" : "{\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filter\":[]}"
- }
- },
- "type" : "dashboard",
- "references" : [
- {
- "name" : "drilldown:DASHBOARD_TO_DASHBOARD_DRILLDOWN:669a3521-1215-4228-9ced-77e2edf5ad17:dashboardId",
- "type" : "dashboard",
- "id" : "19906970-2e40-11e9-85cb-6965aae20f13"
- },
- {
- "name" : "panel_0",
- "type" : "map",
- "id" : "1649cc70-f736-11e8-8ce0-9723965e01e3"
- }
- ],
- "migrationVersion" : {
- "dashboard" : "7.11.0"
- },
- "updated_at" : "2020-11-19T15:12:25.703Z"
- }
- }
-}
-
-{
- "type": "doc",
- "value": {
- "id": "dashboard:42f6f040-b34f-11eb-8c95-dd19591c63df",
- "index": ".kibana",
- "source": {
- "dashboard": {
- "title" : "filter by map extent dashboard",
- "hits" : 0,
- "description" : "",
- "panelsJSON" : "[{\"version\":\"8.0.0\",\"type\":\"map\",\"gridData\":{\"x\":0,\"y\":0,\"w\":29,\"h\":21,\"i\":\"24ade730-afe4-42b6-919a-c4e0a98c94f2\"},\"panelIndex\":\"24ade730-afe4-42b6-919a-c4e0a98c94f2\",\"embeddableConfig\":{\"mapCenter\":{\"lat\":38.64679,\"lon\":-120.96481,\"zoom\":7.06},\"mapBuffer\":{\"minLon\":-125.44180499999999,\"minLat\":36.364824999999996,\"maxLon\":-116.603825,\"maxLat\":40.943405},\"isLayerTOCOpen\":true,\"openTOCDetails\":[],\"hiddenLayers\":[],\"enhancements\":{}},\"panelRefName\":\"panel_24ade730-afe4-42b6-919a-c4e0a98c94f2\"},{\"version\":\"8.0.0\",\"type\":\"lens\",\"gridData\":{\"x\":29,\"y\":0,\"w\":10,\"h\":21,\"i\":\"44eb3c47-f6ad-4da8-993b-13c10997d585\"},\"panelIndex\":\"44eb3c47-f6ad-4da8-993b-13c10997d585\",\"embeddableConfig\":{\"attributes\":{\"title\":\"\",\"type\":\"lens\",\"visualizationType\":\"lnsMetric\",\"state\":{\"datasourceStates\":{\"indexpattern\":{\"layers\":{\"3cda3519-055a-4b9c-8759-caa28388298c\":{\"columns\":{\"26acba84-22ca-4625-b2ac-5309945e9b30\":{\"label\":\"Count of records\",\"dataType\":\"number\",\"operationType\":\"count\",\"isBucketed\":false,\"scale\":\"ratio\",\"sourceField\":\"Records\"}},\"columnOrder\":[\"26acba84-22ca-4625-b2ac-5309945e9b30\"],\"incompleteColumns\":{}}}}},\"visualization\":{\"layerId\":\"3cda3519-055a-4b9c-8759-caa28388298c\",\"accessor\":\"26acba84-22ca-4625-b2ac-5309945e9b30\"},\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filters\":[]},\"references\":[{\"type\":\"index-pattern\",\"id\":\"c698b940-e149-11e8-a35a-370a8516603a\",\"name\":\"indexpattern-datasource-current-indexpattern\"},{\"type\":\"index-pattern\",\"id\":\"c698b940-e149-11e8-a35a-370a8516603a\",\"name\":\"indexpattern-datasource-layer-3cda3519-055a-4b9c-8759-caa28388298c\"}]},\"enhancements\":{},\"hidePanelTitles\":false},\"title\":\"Count panel\"}]",
- "optionsJSON" : "{\"hidePanelTitles\":false,\"useMargins\":true}",
- "version" : 1,
- "timeRestore" : true,
- "timeTo" : "2015-09-20T01:00:00.000Z",
- "timeFrom" : "2015-09-20T00:00:00.000Z",
- "refreshInterval" : {
- "pause" : true,
- "value" : 1000
- },
- "kibanaSavedObjectMeta" : {
- "searchSourceJSON" : "{\"query\":{\"language\":\"kuery\",\"query\":\"\"},\"filter\":[]}"
- }
- },
- "type" : "dashboard",
- "references" : [
- {
- "name" : "24ade730-afe4-42b6-919a-c4e0a98c94f2:panel_24ade730-afe4-42b6-919a-c4e0a98c94f2",
- "type" : "map",
- "id" : "d2e73f40-e14a-11e8-a35a-370a8516603a"
- },
- {
- "type" : "index-pattern",
- "id" : "c698b940-e149-11e8-a35a-370a8516603a",
- "name" : "44eb3c47-f6ad-4da8-993b-13c10997d585:indexpattern-datasource-current-indexpattern"
- },
- {
- "type" : "index-pattern",
- "id" : "c698b940-e149-11e8-a35a-370a8516603a",
- "name" : "44eb3c47-f6ad-4da8-993b-13c10997d585:indexpattern-datasource-layer-3cda3519-055a-4b9c-8759-caa28388298c"
- }
- ],
- "migrationVersion" : {
- "dashboard" : "7.11.0"
- },
- "updated_at" : "2021-05-12T18:24:17.228Z"
- }
- }
-}
-
-{
- "type": "doc",
- "value": {
- "id": "dashboard:4beb0d80-c2ef-11eb-b0cb-bd162d969e6b",
- "index": ".kibana",
- "source": {
- "dashboard": {
- "title" : "by value map",
- "hits" : 0,
- "description" : "",
- "panelsJSON" : "[{\"version\":\"7.12.1-SNAPSHOT\",\"type\":\"map\",\"gridData\":{\"x\":0,\"y\":0,\"w\":24,\"h\":15,\"i\":\"cb82a9e3-2eb0-487f-9ade-0ffb921eb536\"},\"panelIndex\":\"cb82a9e3-2eb0-487f-9ade-0ffb921eb536\",\"embeddableConfig\":{\"attributes\":{\"title\":\"\",\"description\":\"\",\"layerListJSON\":\"[]\",\"mapStateJSON\":\"{\\\"zoom\\\":1.75,\\\"center\\\":{\\\"lon\\\":0,\\\"lat\\\":19.94277},\\\"timeFilters\\\":{\\\"from\\\":\\\"now-15m\\\",\\\"to\\\":\\\"now\\\"},\\\"refreshConfig\\\":{\\\"isPaused\\\":true,\\\"interval\\\":0},\\\"query\\\":{\\\"query\\\":\\\"\\\",\\\"language\\\":\\\"kuery\\\"},\\\"filters\\\":[],\\\"settings\\\":{\\\"autoFitToDataBounds\\\":false,\\\"backgroundColor\\\":\\\"#ffffff\\\",\\\"disableInteractive\\\":false,\\\"disableTooltipControl\\\":false,\\\"hideToolbarOverlay\\\":false,\\\"hideLayerControl\\\":false,\\\"hideViewControl\\\":false,\\\"initialLocation\\\":\\\"LAST_SAVED_LOCATION\\\",\\\"fixedLocation\\\":{\\\"lat\\\":0,\\\"lon\\\":0,\\\"zoom\\\":2},\\\"browserLocation\\\":{\\\"zoom\\\":2},\\\"maxZoom\\\":24,\\\"minZoom\\\":0,\\\"showScaleControl\\\":false,\\\"showSpatialFilters\\\":true,\\\"spatialFiltersAlpa\\\":0.3,\\\"spatialFiltersFillColor\\\":\\\"#DA8B45\\\",\\\"spatialFiltersLineColor\\\":\\\"#DA8B45\\\"}}\",\"uiStateJSON\":\"{\\\"isLayerTOCOpen\\\":true,\\\"openTOCDetails\\\":[]}\"},\"mapCenter\":{\"lat\":19.94277,\"lon\":0,\"zoom\":1.75},\"mapBuffer\":{\"minLon\":-211.13072,\"minLat\":-55.27145,\"maxLon\":211.13072,\"maxLat\":87.44135},\"isLayerTOCOpen\":true,\"openTOCDetails\":[],\"hiddenLayers\":[],\"enhancements\":{}}}]",
- "optionsJSON" : "{\"hidePanelTitles\":false,\"useMargins\":true}",
- "version" : 1,
- "timeRestore" : false,
- "kibanaSavedObjectMeta" : {
- "searchSourceJSON" : "{\"query\":{\"language\":\"kuery\",\"query\":\"\"},\"filter\":[]}"
- }
- },
- "type" : "dashboard",
- "references" : [ ],
- "migrationVersion" : {
- "dashboard" : "7.11.0"
- },
- "updated_at" : "2021-06-01T15:37:39.198Z"
- }
- }
-}
-
-{
- "type": "doc",
- "value": {
- "index": ".kibana",
- "id": "query:OKJpgs",
- "source": {
- "query": {
- "title": "OKJpgs",
- "description": "Ok responses for jpg files",
- "query": {
- "query": "response:200",
- "language": "kuery"
- },
- "filters": [{"meta":{"index":"b15b1d40-a8bb-11e9-98cf-2bb06ef63e0b","alias":null,"negate":false,"type":"phrase","key":"extension.raw","value":"jpg","params":{"query":"jpg"},"disabled":false},"query":{"match":{"extension.raw":{"query":"jpg","type":"phrase"}}},"$state":{"store":"appState"}}]
- },
- "type": "query",
- "updated_at": "2019-07-17T17:54:26.378Z"
- }
- }
-}
diff --git a/x-pack/test/functional/es_archives/maps/kibana/mappings.json b/x-pack/test/functional/es_archives/maps/kibana/mappings.json
deleted file mode 100644
index d9a4ed16b51bd0..00000000000000
--- a/x-pack/test/functional/es_archives/maps/kibana/mappings.json
+++ /dev/null
@@ -1,445 +0,0 @@
-{
- "type": "index",
- "value": {
- "aliases": {
- ".kibana": {}
- },
- "index": ".kibana_1",
- "mappings": {
- "dynamic": "strict",
- "properties": {
- "canvas-workpad": {
- "dynamic": "false",
- "properties": {
- "@created": {
- "type": "date"
- },
- "@timestamp": {
- "type": "date"
- },
- "id": {
- "index": false,
- "type": "text"
- },
- "name": {
- "fields": {
- "keyword": {
- "type": "keyword"
- }
- },
- "type": "text"
- }
- }
- },
- "config": {
- "dynamic": "true",
- "properties": {
- "buildNum": {
- "type": "keyword"
- },
- "defaultIndex": {
- "fields": {
- "keyword": {
- "ignore_above": 256,
- "type": "keyword"
- }
- },
- "type": "text"
- }
- }
- },
- "dashboard": {
- "properties": {
- "description": {
- "type": "text"
- },
- "hits": {
- "type": "integer"
- },
- "kibanaSavedObjectMeta": {
- "properties": {
- "searchSourceJSON": {
- "type": "text"
- }
- }
- },
- "optionsJSON": {
- "type": "text"
- },
- "panelsJSON": {
- "type": "text"
- },
- "refreshInterval": {
- "properties": {
- "display": {
- "type": "keyword"
- },
- "pause": {
- "type": "boolean"
- },
- "section": {
- "type": "integer"
- },
- "value": {
- "type": "integer"
- }
- }
- },
- "timeFrom": {
- "type": "keyword"
- },
- "timeRestore": {
- "type": "boolean"
- },
- "timeTo": {
- "type": "keyword"
- },
- "title": {
- "type": "text"
- },
- "uiStateJSON": {
- "type": "text"
- },
- "version": {
- "type": "integer"
- }
- }
- },
- "graph-workspace": {
- "properties": {
- "description": {
- "type": "text"
- },
- "kibanaSavedObjectMeta": {
- "properties": {
- "searchSourceJSON": {
- "type": "text"
- }
- }
- },
- "numLinks": {
- "type": "integer"
- },
- "numVertices": {
- "type": "integer"
- },
- "title": {
- "type": "text"
- },
- "version": {
- "type": "integer"
- },
- "wsState": {
- "type": "text"
- }
- }
- },
- "index-pattern": {
- "properties": {
- "fieldFormatMap": {
- "type": "text"
- },
- "runtimeFieldMap": {
- "type": "text"
- },
- "fields": {
- "type": "text"
- },
- "intervalName": {
- "type": "keyword"
- },
- "notExpandable": {
- "type": "boolean"
- },
- "sourceFilters": {
- "type": "text"
- },
- "timeFieldName": {
- "type": "keyword"
- },
- "title": {
- "type": "text"
- },
- "type": {
- "type": "keyword"
- },
- "typeMeta": {
- "type": "keyword"
- }
- }
- },
- "kql-telemetry": {
- "properties": {
- "optInCount": {
- "type": "long"
- },
- "optOutCount": {
- "type": "long"
- }
- }
- },
- "map": {
- "properties": {
- "bounds": {
- "dynamic": false,
- "properties": {}
- },
- "description": {
- "type": "text"
- },
- "layerListJSON": {
- "type": "text"
- },
- "mapStateJSON": {
- "type": "text"
- },
- "title": {
- "type": "text"
- },
- "uiStateJSON": {
- "type": "text"
- },
- "version": {
- "type": "integer"
- }
- }
- },
- "migrationVersion": {
- "dynamic": "true",
- "properties": {
- "index-pattern": {
- "fields": {
- "keyword": {
- "ignore_above": 256,
- "type": "keyword"
- }
- },
- "type": "text"
- }
- }
- },
- "namespace": {
- "type": "keyword"
- },
- "references": {
- "type": "nested",
- "properties": {
- "name": {
- "type": "keyword"
- },
- "type": {
- "type": "keyword"
- },
- "id": {
- "type": "keyword"
- }
- }
- },
- "search": {
- "properties": {
- "columns": {
- "type": "keyword"
- },
- "description": {
- "type": "text"
- },
- "hits": {
- "type": "integer"
- },
- "kibanaSavedObjectMeta": {
- "properties": {
- "searchSourceJSON": {
- "type": "text"
- }
- }
- },
- "sort": {
- "type": "keyword"
- },
- "title": {
- "type": "text"
- },
- "version": {
- "type": "integer"
- }
- }
- },
- "server": {
- "properties": {
- "uuid": {
- "type": "keyword"
- }
- }
- },
- "space": {
- "properties": {
- "_reserved": {
- "type": "boolean"
- },
- "color": {
- "type": "keyword"
- },
- "description": {
- "type": "text"
- },
- "initials": {
- "type": "keyword"
- },
- "disabledFeatures": {
- "type": "keyword"
- },
- "name": {
- "fields": {
- "keyword": {
- "ignore_above": 2048,
- "type": "keyword"
- }
- },
- "type": "text"
- }
- }
- },
- "telemetry": {
- "properties": {
- "enabled": {
- "type": "boolean"
- }
- }
- },
- "timelion-sheet": {
- "properties": {
- "description": {
- "type": "text"
- },
- "hits": {
- "type": "integer"
- },
- "kibanaSavedObjectMeta": {
- "properties": {
- "searchSourceJSON": {
- "type": "text"
- }
- }
- },
- "timelion_chart_height": {
- "type": "integer"
- },
- "timelion_columns": {
- "type": "integer"
- },
- "timelion_interval": {
- "type": "keyword"
- },
- "timelion_other_interval": {
- "type": "keyword"
- },
- "timelion_rows": {
- "type": "integer"
- },
- "timelion_sheet": {
- "type": "text"
- },
- "title": {
- "type": "text"
- },
- "version": {
- "type": "integer"
- }
- }
- },
- "type": {
- "type": "keyword"
- },
- "updated_at": {
- "type": "date"
- },
- "url": {
- "properties": {
- "accessCount": {
- "type": "long"
- },
- "accessDate": {
- "type": "date"
- },
- "createDate": {
- "type": "date"
- },
- "url": {
- "fields": {
- "keyword": {
- "ignore_above": 2048,
- "type": "keyword"
- }
- },
- "type": "text"
- }
- }
- },
- "visualization": {
- "properties": {
- "description": {
- "type": "text"
- },
- "kibanaSavedObjectMeta": {
- "properties": {
- "searchSourceJSON": {
- "type": "text"
- }
- }
- },
- "savedSearchId": {
- "type": "keyword"
- },
- "title": {
- "type": "text"
- },
- "uiStateJSON": {
- "type": "text"
- },
- "version": {
- "type": "integer"
- },
- "visState": {
- "type": "text"
- }
- }
- },
- "query": {
- "properties": {
- "title": {
- "type": "text"
- },
- "description": {
- "type": "text"
- },
- "query": {
- "properties": {
- "language": {
- "type": "keyword"
- },
- "query": {
- "type": "keyword",
- "index": false
- }
- }
- },
- "filters": {
- "type": "object",
- "enabled": false
- },
- "timefilter": {
- "type": "object",
- "enabled": false
- }
- }
- }
- }
- },
- "settings": {
- "index": {
- "number_of_replicas": "0",
- "number_of_shards": "1"
- }
- }
- }
-}
diff --git a/x-pack/test/functional/fixtures/kbn_archiver/maps.json b/x-pack/test/functional/fixtures/kbn_archiver/maps.json
new file mode 100644
index 00000000000000..b17b4a64f34853
--- /dev/null
+++ b/x-pack/test/functional/fixtures/kbn_archiver/maps.json
@@ -0,0 +1,912 @@
+{
+ "attributes": {
+ "fields": "[]",
+ "title": "delete*"
+ },
+ "coreMigrationVersion": "8.0.0",
+ "id": "idThatDoesNotExitForESGeoGridSource",
+ "migrationVersion": {
+ "index-pattern": "7.11.0"
+ },
+ "references": [],
+ "type": "index-pattern",
+ "version": "WzM3LDJd"
+}
+
+{
+ "attributes": {
+ "fields": "[]",
+ "title": "delete*"
+ },
+ "coreMigrationVersion": "8.0.0",
+ "id": "idThatDoesNotExitForESSearchSource",
+ "migrationVersion": {
+ "index-pattern": "7.11.0"
+ },
+ "references": [],
+ "type": "index-pattern",
+ "version": "WzM3LDJd"
+}
+
+{
+ "attributes": {
+ "fields": "[]",
+ "title": "delete*"
+ },
+ "coreMigrationVersion": "8.0.0",
+ "id": "idThatDoesNotExitForESJoinSource",
+ "migrationVersion": {
+ "index-pattern": "7.11.0"
+ },
+ "references": [],
+ "type": "index-pattern",
+ "version": "WzM3LDJd"
+}
+
+
+{
+ "attributes": {
+ "fields": "[]",
+ "title": "geo_shapes*"
+ },
+ "coreMigrationVersion": "8.0.0",
+ "id": "561253e0-f731-11e8-8487-11b9dd924f96",
+ "migrationVersion": {
+ "index-pattern": "7.11.0"
+ },
+ "references": [],
+ "type": "index-pattern",
+ "version": "WzM3LDJd"
+}
+
+{
+ "attributes": {
+ "fields": "[]",
+ "runtimeFieldMap": "{\"runtime_shape_name\":{\"type\":\"keyword\",\"script\":{\"source\":\"emit(doc['shape_name'].value)\"}}}",
+ "title": "meta_for_geo_shapes*"
+ },
+ "coreMigrationVersion": "8.0.0",
+ "id": "e20b2a30-f735-11e8-8ce0-9723965e01e3",
+ "migrationVersion": {
+ "index-pattern": "7.11.0"
+ },
+ "references": [],
+ "type": "index-pattern",
+ "version": "WzM4LDJd"
+}
+
+{
+ "attributes": {
+ "description": "",
+ "layerListJSON": "[{\"id\":\"n1t6f\",\"label\":null,\"minZoom\":0,\"maxZoom\":24,\"sourceDescriptor\":{\"id\":\"62eca1fc-fe42-11e8-8eb2-f2801f1b9fd1\",\"type\":\"ES_SEARCH\",\"geoField\":\"geometry\",\"limit\":2048,\"filterByMapBounds\":false,\"showTooltip\":true,\"tooltipProperties\":[\"name\"],\"applyGlobalQuery\":false,\"indexPatternRefName\":\"layer_1_source_index_pattern\",\"scalingType\":\"LIMIT\"},\"visible\":true,\"temporary\":false,\"style\":{\"type\":\"VECTOR\",\"properties\":{\"fillColor\":{\"type\":\"DYNAMIC\",\"options\":{\"fieldMetaOptions\":{\"isEnabled\":false,\"sigma\":3},\"field\":{\"label\":\"max(prop1) group by meta_for_geo_shapes*.runtime_shape_name\",\"name\":\"__kbnjoin__max_of_prop1__855ccb86-fe42-11e8-8eb2-f2801f1b9fd1\",\"origin\":\"join\"},\"color\":\"Blues\"}},\"iconSize\":{\"type\":\"STATIC\",\"options\":{\"size\":10}},\"symbolizeAs\":{\"options\":{\"value\":\"circle\"}},\"icon\":{\"type\":\"STATIC\",\"options\":{\"value\":\"marker\"}}},\"temporary\":true,\"previousStyle\":null},\"type\":\"VECTOR\",\"joins\":[{\"leftField\":\"name\",\"right\":{\"id\":\"855ccb86-fe42-11e8-8eb2-f2801f1b9fd1\",\"indexPatternTitle\":\"meta_for_geo_shapes*\",\"term\":\"runtime_shape_name\",\"metrics\":[{\"type\":\"max\",\"field\":\"prop1\"}],\"applyGlobalQuery\":true,\"indexPatternRefName\":\"layer_1_join_0_index_pattern\",\"type\":\"ES_TERM_SOURCE\"}}]}]",
+ "mapStateJSON": "{\"zoom\":3.02,\"center\":{\"lon\":77.33426,\"lat\":-0.04647},\"timeFilters\":{\"from\":\"now-17m\",\"to\":\"now\",\"mode\":\"quick\"},\"refreshConfig\":{\"isPaused\":true,\"interval\":1000},\"settings\":{\"autoFitToDataBounds\":false}}",
+ "title": "join example",
+ "uiStateJSON": "{\"isLayerTOCOpen\":true,\"openTOCDetails\":[\"n1t6f\"]}"
+ },
+ "coreMigrationVersion": "8.0.0",
+ "id": "1649cc70-f736-11e8-8ce0-9723965e01e3",
+ "migrationVersion": {
+ "map": "7.14.0"
+ },
+ "references": [
+ {
+ "id": "561253e0-f731-11e8-8487-11b9dd924f96",
+ "name": "layer_1_source_index_pattern",
+ "type": "index-pattern"
+ },
+ {
+ "id": "e20b2a30-f735-11e8-8ce0-9723965e01e3",
+ "name": "layer_1_join_0_index_pattern",
+ "type": "index-pattern"
+ }
+ ],
+ "type": "map",
+ "version": "WzUxLDJd"
+}
+
+{
+ "attributes": {
+ "fields": "[{\"name\":\"hour_of_day\",\"type\":\"number\",\"count\":0,\"scripted\":true,\"script\":\"doc['@timestamp'].value.getHour()\",\"lang\":\"painless\",\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false}]",
+ "timeFieldName": "@timestamp",
+ "title": "logstash-*"
+ },
+ "coreMigrationVersion": "8.0.0",
+ "id": "c698b940-e149-11e8-a35a-370a8516603a",
+ "migrationVersion": {
+ "index-pattern": "7.11.0"
+ },
+ "references": [],
+ "type": "index-pattern",
+ "version": "WzM2LDJd"
+}
+
+{
+ "attributes": {
+ "description": "",
+ "layerListJSON": "[{\"id\":\"g1xkv\",\"label\":null,\"minZoom\":0,\"maxZoom\":24,\"sourceDescriptor\":{\"resolution\":\"COARSE\",\"type\":\"ES_GEO_GRID\",\"id\":\"9305f6ea-4518-4c06-95b9-33321aa38d6a\",\"geoField\":\"geo.coordinates\",\"requestType\":\"grid\",\"metrics\":[{\"type\":\"count\"},{\"type\":\"max\",\"field\":\"bytes\"}],\"indexPatternRefName\":\"layer_0_source_index_pattern\",\"applyGlobalQuery\":true},\"visible\":true,\"temporary\":false,\"style\":{\"type\":\"VECTOR\",\"properties\":{\"fillColor\":{\"type\":\"DYNAMIC\",\"options\":{\"field\":{\"label\":\"max of bytes\",\"name\":\"max_of_bytes\",\"origin\":\"source\"},\"color\":\"Blues\",\"fieldMetaOptions\":{\"isEnabled\":false,\"sigma\":3}}},\"lineColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#cccccc\"}},\"lineWidth\":{\"type\":\"STATIC\",\"options\":{\"size\":1}},\"iconSize\":{\"type\":\"DYNAMIC\",\"options\":{\"field\":{\"label\":\"Count\",\"name\":\"doc_count\",\"origin\":\"source\"},\"minSize\":4,\"maxSize\":32,\"fieldMetaOptions\":{\"isEnabled\":false,\"sigma\":3}}},\"symbolizeAs\":{\"options\":{\"value\":\"circle\"}},\"icon\":{\"type\":\"STATIC\",\"options\":{\"value\":\"marker\"}}},\"temporary\":true,\"previousStyle\":null},\"type\":\"VECTOR\"}]",
+ "mapStateJSON": "{\"zoom\":3.59,\"center\":{\"lon\":-98.05765,\"lat\":38.32288},\"timeFilters\":{\"from\":\"2015-09-20T00:00:00.000Z\",\"to\":\"2015-09-20T01:00:00.000Z\"},\"refreshConfig\":{\"isPaused\":true,\"interval\":1000},\"settings\":{\"autoFitToDataBounds\":false}}",
+ "title": "geo grid vector grid example",
+ "uiStateJSON": "{\"isDarkMode\":false}"
+ },
+ "coreMigrationVersion": "8.0.0",
+ "id": "88845ec0-03ae-11e9-83d4-5b39b6575325",
+ "migrationVersion": {
+ "map": "7.14.0"
+ },
+ "references": [
+ {
+ "id": "c698b940-e149-11e8-a35a-370a8516603a",
+ "name": "layer_0_source_index_pattern",
+ "type": "index-pattern"
+ }
+ ],
+ "type": "map",
+ "version": "WzUzLDJd"
+}
+
+{
+ "attributes": {
+ "description": "",
+ "hits": 0,
+ "kibanaSavedObjectMeta": {
+ "searchSourceJSON": "{\"query\":{\"query\":\"\",\"language\":\"lucene\"},\"filter\":[]}"
+ },
+ "optionsJSON": "{\"darkTheme\":false,\"useMargins\":true,\"hidePanelTitles\":false}",
+ "panelsJSON": "[{\"version\":\"7.0.0-alpha1\",\"gridData\":{\"x\":0,\"y\":0,\"w\":24,\"h\":20,\"i\":\"1\"},\"panelIndex\":\"1\",\"embeddableConfig\":{\"enhancements\":{}},\"panelRefName\":\"panel_0\"},{\"version\":\"7.0.0-alpha1\",\"gridData\":{\"x\":24,\"y\":0,\"w\":24,\"h\":20,\"i\":\"2\"},\"panelIndex\":\"2\",\"embeddableConfig\":{\"enhancements\":{}},\"panelRefName\":\"panel_1\"}]",
+ "refreshInterval": {
+ "pause": true,
+ "value": 1000
+ },
+ "timeFrom": "2015-09-20T00:00:00.000Z",
+ "timeRestore": true,
+ "timeTo": "2015-09-20T01:00:00.000Z",
+ "title": "map embeddable example",
+ "version": 1
+ },
+ "coreMigrationVersion": "8.0.0",
+ "id": "19906970-2e40-11e9-85cb-6965aae20f13",
+ "migrationVersion": {
+ "dashboard": "7.14.0"
+ },
+ "references": [
+ {
+ "id": "1649cc70-f736-11e8-8ce0-9723965e01e3",
+ "name": "panel_0",
+ "type": "map"
+ },
+ {
+ "id": "88845ec0-03ae-11e9-83d4-5b39b6575325",
+ "name": "panel_1",
+ "type": "map"
+ }
+ ],
+ "type": "dashboard",
+ "updated_at": "2018-04-11T21:57:52.253Z",
+ "version": "WzY1LDJd"
+}
+
+{
+ "attributes": {
+ "description": "Zoomed in so entire screen is covered by filter so click to open tooltip can not miss.",
+ "hits": 0,
+ "kibanaSavedObjectMeta": {
+ "searchSourceJSON": "{\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filter\":[]}"
+ },
+ "optionsJSON": "{\"useMargins\":true,\"hidePanelTitles\":false}",
+ "panelsJSON": "[{\"version\":\"8.0.0\",\"gridData\":{\"x\":0,\"y\":0,\"w\":48,\"h\":26,\"i\":\"1\"},\"panelIndex\":\"1\",\"embeddableConfig\":{\"mapCenter\":{\"lat\":-1.31919,\"lon\":59.53306,\"zoom\":9.67},\"isLayerTOCOpen\":false,\"openTOCDetails\":[\"n1t6f\"],\"hiddenLayers\":[],\"enhancements\":{\"dynamicActions\":{\"events\":[{\"eventId\":\"669a3521-1215-4228-9ced-77e2edf5ad17\",\"triggers\":[\"FILTER_TRIGGER\"],\"action\":{\"name\":\"drilldown1\",\"config\":{\"useCurrentFilters\":true,\"useCurrentDateRange\":true},\"factoryId\":\"DASHBOARD_TO_DASHBOARD_DRILLDOWN\"}},{\"eventId\":\"b9c20d96-03ce-4dcc-8823-e3503311172e\",\"triggers\":[\"VALUE_CLICK_TRIGGER\"],\"action\":{\"name\":\"urlDrilldownToDiscover\",\"config\":{\"url\":{\"template\":\"{{kibanaUrl}}/app/discover#/?_a=(columns:!(_source),filters:!(('$state':(store:appState),meta:(alias:!n,disabled:!f,index:'561253e0-f731-11e8-8487-11b9dd924f96',key:{{event.key}},negate:!f,params:(query:{{event.value}}),type:phrase),query:(match_phrase:({{event.key}}:{{event.value}})))),index:'561253e0-f731-11e8-8487-11b9dd924f96',interval:auto,query:(language:kuery,query:''),sort:!())\"},\"openInNewTab\":false},\"factoryId\":\"URL_DRILLDOWN\"}}]}}},\"panelRefName\":\"panel_0\"}]",
+ "refreshInterval": {
+ "pause": true,
+ "value": 1000
+ },
+ "timeFrom": "2015-09-20T00:00:00.000Z",
+ "timeRestore": true,
+ "timeTo": "2015-09-20T01:00:00.000Z",
+ "title": "dash for tooltip filter action test",
+ "version": 1
+ },
+ "coreMigrationVersion": "8.0.0",
+ "id": "03c7cbf0-8eae-11e9-b674-69d1999628e4",
+ "migrationVersion": {
+ "dashboard": "7.14.0"
+ },
+ "references": [
+ {
+ "id": "19906970-2e40-11e9-85cb-6965aae20f13",
+ "name": "drilldown:DASHBOARD_TO_DASHBOARD_DRILLDOWN:669a3521-1215-4228-9ced-77e2edf5ad17:dashboardId",
+ "type": "dashboard"
+ },
+ {
+ "id": "1649cc70-f736-11e8-8ce0-9723965e01e3",
+ "name": "panel_0",
+ "type": "map"
+ }
+ ],
+ "type": "dashboard",
+ "updated_at": "2020-11-19T15:12:25.703Z",
+ "version": "WzY2LDJd"
+}
+
+{
+ "attributes": {
+ "description": "",
+ "layerListJSON": "[{\"id\":\"g1xkv\",\"label\":null,\"minZoom\":0,\"maxZoom\":24,\"sourceDescriptor\":{\"resolution\":\"COARSE\",\"type\":\"ES_GEO_GRID\",\"id\":\"64ddd934-a767-11ea-bb37-0242ac130002\",\"geoField\":\"geometry\",\"requestType\":\"grid\",\"metrics\":[{\"type\":\"count\"}],\"indexPatternRefName\":\"layer_0_source_index_pattern\",\"applyGlobalQuery\":true},\"visible\":true,\"temporary\":false,\"style\":{\"type\":\"VECTOR\",\"properties\":{\"fillColor\":{\"type\":\"DYNAMIC\",\"options\":{\"field\":{\"label\":\"Count\",\"name\":\"doc_count\",\"origin\":\"source\"},\"color\":\"Blues\",\"fieldMetaOptions\":{\"isEnabled\":false,\"sigma\":3}}},\"lineColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#cccccc\"}},\"lineWidth\":{\"type\":\"STATIC\",\"options\":{\"size\":1}},\"iconSize\":{\"type\":\"DYNAMIC\",\"options\":{\"field\":{\"label\":\"Count\",\"name\":\"doc_count\",\"origin\":\"source\"},\"minSize\":4,\"maxSize\":32,\"fieldMetaOptions\":{\"isEnabled\":false,\"sigma\":3}}},\"symbolizeAs\":{\"options\":{\"value\":\"circle\"}},\"icon\":{\"type\":\"STATIC\",\"options\":{\"value\":\"marker\"}}},\"temporary\":true,\"previousStyle\":null},\"type\":\"VECTOR\"}]",
+ "mapStateJSON": "{\"zoom\":3,\"center\":{\"lon\":76,\"lat\":4},\"timeFilters\":{\"from\":\"2015-09-20T00:00:00.000Z\",\"to\":\"2015-09-20T01:00:00.000Z\"},\"refreshConfig\":{\"isPaused\":true,\"interval\":1000},\"settings\":{\"autoFitToDataBounds\":false}}",
+ "title": "geo grid vector grid example with shape",
+ "uiStateJSON": "{\"isDarkMode\":false}"
+ },
+ "coreMigrationVersion": "8.0.0",
+ "id": "0c86d024-a767-11ea-bb37-0242ac130002",
+ "migrationVersion": {
+ "map": "7.14.0"
+ },
+ "references": [
+ {
+ "id": "561253e0-f731-11e8-8487-11b9dd924f96",
+ "name": "layer_0_source_index_pattern",
+ "type": "index-pattern"
+ }
+ ],
+ "type": "map",
+ "version": "WzU0LDJd"
+}
+
+{
+ "attributes": {
+ "fields": "[]",
+ "title": "antimeridian_points"
+ },
+ "coreMigrationVersion": "8.0.0",
+ "id": "1035e930-1811-11e9-b78a-23d706cd2507",
+ "migrationVersion": {
+ "index-pattern": "7.11.0"
+ },
+ "references": [],
+ "type": "index-pattern",
+ "version": "WzM5LDJd"
+}
+
+{
+ "attributes": {
+ "description": "",
+ "layerListJSON": "[{\"id\":\"z52lq\",\"label\":\"logstash\",\"minZoom\":0,\"maxZoom\":24,\"sourceDescriptor\":{\"id\":\"e1a5e1a6-676c-4a89-8ea9-0d91d64b73c6\",\"type\":\"ES_SEARCH\",\"geoField\":\"geo.coordinates\",\"limit\":2048,\"filterByMapBounds\":true,\"showTooltip\":true,\"tooltipProperties\":[],\"applyGlobalQuery\":true,\"scalingType\":\"LIMIT\",\"indexPatternRefName\":\"layer_1_source_index_pattern\"},\"visible\":true,\"temporary\":false,\"style\":{\"type\":\"VECTOR\",\"properties\":{\"fillColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#e6194b\"}},\"lineColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#FFFFFF\"}},\"lineWidth\":{\"type\":\"STATIC\",\"options\":{\"size\":1}},\"iconSize\":{\"type\":\"STATIC\",\"options\":{\"size\":10}},\"symbolizeAs\":{\"options\":{\"value\":\"circle\"}},\"icon\":{\"type\":\"STATIC\",\"options\":{\"value\":\"marker\"}}},\"previousStyle\":null},\"type\":\"VECTOR\"}]",
+ "mapStateJSON": "{\"zoom\":5.2,\"center\":{\"lon\":-67.80052,\"lat\":-55.25331},\"timeFilters\":{\"from\":\"2015-09-20T00:00:00.000Z\",\"to\":\"2015-09-20T01:00:00.000Z\"},\"refreshConfig\":{\"isPaused\":true,\"interval\":1000},\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filters\":[],\"settings\":{\"autoFitToDataBounds\":false,\"initialLocation\":\"AUTO_FIT_TO_BOUNDS\",\"fixedLocation\":{\"lat\":0,\"lon\":0,\"zoom\":2},\"browserLocation\":{\"zoom\":2},\"maxZoom\":24,\"minZoom\":0,\"showSpatialFilters\":true,\"spatialFiltersAlpa\":0.3,\"spatialFiltersFillColor\":\"#DA8B45\",\"spatialFiltersLineColor\":\"#DA8B45\"}}",
+ "title": "document example - auto fit to bounds for initial location",
+ "uiStateJSON": "{\"isLayerTOCOpen\":true,\"openTOCDetails\":[]}"
+ },
+ "coreMigrationVersion": "8.0.0",
+ "id": "13776f20-db37-11ea-8fbb-3da39bb9bff2",
+ "migrationVersion": {
+ "map": "7.14.0"
+ },
+ "references": [
+ {
+ "id": "c698b940-e149-11e8-a35a-370a8516603a",
+ "name": "layer_1_source_index_pattern",
+ "type": "index-pattern"
+ }
+ ],
+ "type": "map",
+ "updated_at": "2020-08-10T18:27:39.805Z",
+ "version": "WzYzLDJd"
+}
+
+{
+ "attributes": {
+ "description": "",
+ "layerListJSON": "[{\"id\":\"307c8495-89f7-431b-83d8-78724d9a8f72\",\"label\":\"logstash-*\",\"sourceDescriptor\":{\"geoField\":\"geo.coordinates\",\"id\":\"20fc58c3-3c0a-4c7b-9cdc-37552cafdc21\",\"tooltipProperties\":[],\"type\":\"ES_SEARCH\",\"scalingType\":\"CLUSTERS\",\"indexPatternRefName\":\"layer_1_source_index_pattern\"},\"type\":\"BLENDED_VECTOR\",\"visible\":true,\"style\":{\"type\":\"VECTOR\",\"properties\":{\"icon\":{\"type\":\"STATIC\",\"options\":{\"value\":\"airfield\"}},\"fillColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#54B399\"}},\"lineColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#41937c\"}},\"lineWidth\":{\"type\":\"STATIC\",\"options\":{\"size\":1}},\"iconSize\":{\"type\":\"STATIC\",\"options\":{\"size\":6}},\"iconOrientation\":{\"type\":\"STATIC\",\"options\":{\"orientation\":0}},\"labelText\":{\"type\":\"STATIC\",\"options\":{\"value\":\"\"}},\"labelColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#000000\"}},\"labelSize\":{\"type\":\"STATIC\",\"options\":{\"size\":14}},\"labelBorderColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#FFFFFF\"}},\"symbolizeAs\":{\"options\":{\"value\":\"circle\"}},\"labelBorderSize\":{\"options\":{\"size\":\"SMALL\"}}},\"isTimeAware\":true}}]",
+ "mapStateJSON": "{\"zoom\":10.27,\"center\":{\"lon\":-83.70716,\"lat\":32.73679},\"timeFilters\":{\"from\":\"2015-09-20T00:00:00.000Z\",\"to\":\"2015-09-23T00:00:00.000Z\"},\"refreshConfig\":{\"isPaused\":true,\"interval\":0},\"query\":{\"language\":\"kuery\",\"query\":\"\"},\"filters\":[],\"settings\":{\"autoFitToDataBounds\":false}}",
+ "title": "blended document example",
+ "uiStateJSON": "{\"isLayerTOCOpen\":true,\"openTOCDetails\":[]}"
+ },
+ "coreMigrationVersion": "8.0.0",
+ "id": "279e1f20-6883-11ea-952a-b102add99cf8",
+ "migrationVersion": {
+ "map": "7.14.0"
+ },
+ "references": [
+ {
+ "id": "c698b940-e149-11e8-a35a-370a8516603a",
+ "name": "layer_1_source_index_pattern",
+ "type": "index-pattern"
+ }
+ ],
+ "type": "map",
+ "updated_at": "2020-03-17T19:11:50.290Z",
+ "version": "WzYyLDJd"
+}
+
+{
+ "attributes": {
+ "description": "",
+ "layerListJSON": "[{\"id\":\"z52lq\",\"label\":\"logstash\",\"minZoom\":0,\"maxZoom\":24,\"sourceDescriptor\":{\"id\":\"e1a5e1a6-676c-4a89-8ea9-0d91d64b73c6\",\"type\":\"ES_SEARCH\",\"geoField\":\"geo.coordinates\",\"limit\":2048,\"filterByMapBounds\":true,\"showTooltip\":true,\"tooltipProperties\":[],\"applyGlobalQuery\":true,\"scalingType\":\"LIMIT\",\"indexPatternRefName\":\"layer_1_source_index_pattern\"},\"visible\":false,\"temporary\":false,\"style\":{\"type\":\"VECTOR\",\"properties\":{\"fillColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#e6194b\"}},\"lineColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#FFFFFF\"}},\"lineWidth\":{\"type\":\"STATIC\",\"options\":{\"size\":1}},\"iconSize\":{\"type\":\"STATIC\",\"options\":{\"size\":10}},\"symbolizeAs\":{\"options\":{\"value\":\"circle\"}},\"icon\":{\"type\":\"STATIC\",\"options\":{\"value\":\"marker\"}}},\"previousStyle\":null},\"type\":\"VECTOR\"}]",
+ "mapStateJSON": "{\"zoom\":4.1,\"center\":{\"lon\":-100.61091,\"lat\":33.23887},\"timeFilters\":{\"from\":\"2015-09-20T00:00:00.000Z\",\"to\":\"2015-09-20T01:00:00.000Z\"},\"refreshConfig\":{\"isPaused\":true,\"interval\":1000},\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filters\":[],\"settings\":{\"autoFitToDataBounds\":false,\"initialLocation\":\"LAST_SAVED_LOCATION\",\"fixedLocation\":{\"lat\":0,\"lon\":0,\"zoom\":2},\"browserLocation\":{\"zoom\":2},\"maxZoom\":24,\"minZoom\":0,\"showSpatialFilters\":true,\"spatialFiltersAlpa\":0.3,\"spatialFiltersFillColor\":\"#DA8B45\",\"spatialFiltersLineColor\":\"#DA8B45\"}}",
+ "title": "document example hidden",
+ "uiStateJSON": "{\"isLayerTOCOpen\":true,\"openTOCDetails\":[]}"
+ },
+ "coreMigrationVersion": "8.0.0",
+ "id": "2de4de10-cc82-11ea-9b0a-eb2886fc84af",
+ "migrationVersion": {
+ "map": "7.14.0"
+ },
+ "references": [
+ {
+ "id": "c698b940-e149-11e8-a35a-370a8516603a",
+ "name": "layer_1_source_index_pattern",
+ "type": "index-pattern"
+ }
+ ],
+ "type": "map",
+ "updated_at": "2020-07-23T01:16:47.600Z",
+ "version": "WzUwLDJd"
+}
+
+{
+ "attributes": {
+ "description": "",
+ "layerListJSON": "[{\"id\":\"frk92\",\"label\":null,\"minZoom\":0,\"maxZoom\":24,\"sourceDescriptor\":{\"id\":\"7d807c75-088a-44b7-920a-e7e47f4fc038\",\"type\":\"ES_SEARCH\",\"geoField\":\"location\",\"limit\":2048,\"filterByMapBounds\":true,\"tooltipProperties\":[],\"indexPatternRefName\":\"layer_0_source_index_pattern\",\"applyGlobalQuery\":true,\"scalingType\":\"LIMIT\"},\"visible\":true,\"temporary\":false,\"style\":{\"type\":\"VECTOR\",\"properties\":{\"fillColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#e6194b\"}},\"lineColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#FFFFFF\"}},\"lineWidth\":{\"type\":\"STATIC\",\"options\":{\"size\":1}},\"iconSize\":{\"type\":\"STATIC\",\"options\":{\"size\":10}},\"symbolizeAs\":{\"options\":{\"value\":\"circle\"}},\"icon\":{\"type\":\"STATIC\",\"options\":{\"value\":\"marker\"}}},\"previousStyle\":null},\"type\":\"VECTOR\"}]",
+ "mapStateJSON": "{\"zoom\":8.8,\"center\":{\"lon\":-179.98743,\"lat\":-0.09561},\"timeFilters\":{\"from\":\"now-15m\",\"to\":\"now\",\"mode\":\"quick\"},\"refreshConfig\":{\"isPaused\":true,\"interval\":0},\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"settings\":{\"autoFitToDataBounds\":false}}",
+ "title": "antimeridian points example",
+ "uiStateJSON": "{\"isDarkMode\":false}"
+ },
+ "coreMigrationVersion": "8.0.0",
+ "id": "346fbec0-1811-11e9-b78a-23d706cd2507",
+ "migrationVersion": {
+ "map": "7.14.0"
+ },
+ "references": [
+ {
+ "id": "1035e930-1811-11e9-b78a-23d706cd2507",
+ "name": "layer_0_source_index_pattern",
+ "type": "index-pattern"
+ }
+ ],
+ "type": "map",
+ "version": "WzU2LDJd"
+}
+
+{
+ "attributes": {
+ "fields": "[]",
+ "title": "connections"
+ },
+ "coreMigrationVersion": "8.0.0",
+ "id": "dedd3180-c8d8-11e9-b36c-81f9f9da524f",
+ "migrationVersion": {
+ "index-pattern": "7.11.0"
+ },
+ "references": [],
+ "type": "index-pattern",
+ "updated_at": "2019-08-27T14:42:20.061Z",
+ "version": "WzQyLDJd"
+}
+
+{
+ "attributes": {
+ "description": "",
+ "layerListJSON": "[{\"style\":{\"type\":\"VECTOR\",\"properties\":{\"fillColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#1EA593\"}},\"lineColor\":{\"type\":\"DYNAMIC\",\"options\":{\"field\":{\"label\":\"count\",\"name\":\"doc_count\",\"origin\":\"source\"},\"color\":\"Blues\",\"fieldMetaOptions\":{\"isEnabled\":false,\"sigma\":3}}},\"lineWidth\":{\"type\":\"DYNAMIC\",\"options\":{\"field\":{\"label\":\"count\",\"name\":\"doc_count\",\"origin\":\"source\"},\"minSize\":4,\"maxSize\":32,\"fieldMetaOptions\":{\"isEnabled\":false,\"sigma\":3}}},\"iconSize\":{\"type\":\"STATIC\",\"options\":{\"size\":10}},\"iconOrientation\":{\"type\":\"STATIC\",\"options\":{\"orientation\":0}},\"symbolizeAs\":{\"options\":{\"value\":\"circle\"}},\"icon\":{\"type\":\"STATIC\",\"options\":{\"value\":\"airfield\"}}}},\"sourceDescriptor\":{\"type\":\"ES_PEW_PEW\",\"id\":\"d7ca27a8-dba2-4f5b-857c-8f529511ad81\",\"sourceGeoField\":\"source\",\"destGeoField\":\"destination\",\"indexPatternRefName\":\"layer_0_source_index_pattern\",\"applyGlobalQuery\":true},\"id\":\"67c1de2c-2fc5-4425-8983-094b589afe61\",\"label\":null,\"minZoom\":0,\"maxZoom\":24,\"alpha\":0.75,\"visible\":true,\"type\":\"VECTOR\"}]",
+ "mapStateJSON": "{\"zoom\":5.5,\"center\":{\"lon\":-71.34293,\"lat\":40.33097},\"timeFilters\":{\"from\":\"now-15m\",\"to\":\"now\"},\"refreshConfig\":{\"isPaused\":false,\"interval\":0},\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filters\":[],\"settings\":{\"autoFitToDataBounds\":false}}",
+ "title": "pew pew demo",
+ "uiStateJSON": "{\"isLayerTOCOpen\":true,\"openTOCDetails\":[]}"
+ },
+ "coreMigrationVersion": "8.0.0",
+ "id": "3c9949f0-c8dc-11e9-9ea1-8b2710d4a86b",
+ "migrationVersion": {
+ "map": "7.14.0"
+ },
+ "references": [
+ {
+ "id": "dedd3180-c8d8-11e9-b36c-81f9f9da524f",
+ "name": "layer_0_source_index_pattern",
+ "type": "index-pattern"
+ }
+ ],
+ "type": "map",
+ "updated_at": "2019-08-27T15:06:24.654Z",
+ "version": "WzYxLDJd"
+}
+
+{
+ "attributes": {
+ "description": "",
+ "layerListJSON": "[{\"id\":\"z52lq\",\"label\":\"logstash\",\"minZoom\":0,\"maxZoom\":24,\"sourceDescriptor\":{\"id\":\"e1a5e1a6-676c-4a89-8ea9-0d91d64b73c6\",\"type\":\"ES_SEARCH\",\"geoField\":\"geo.coordinates\",\"limit\":2048,\"filterByMapBounds\":true,\"showTooltip\":true,\"tooltipProperties\":[],\"indexPatternRefName\":\"layer_0_source_index_pattern\",\"applyGlobalQuery\":true,\"scalingType\":\"LIMIT\"},\"visible\":true,\"temporary\":false,\"style\":{\"type\":\"VECTOR\",\"properties\":{\"fillColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#e6194b\"}},\"lineColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#FFFFFF\"}},\"lineWidth\":{\"type\":\"STATIC\",\"options\":{\"size\":1}},\"iconSize\":{\"type\":\"STATIC\",\"options\":{\"size\":10}},\"symbolizeAs\":{\"options\":{\"value\":\"circle\"}},\"icon\":{\"type\":\"STATIC\",\"options\":{\"value\":\"marker\"}}},\"previousStyle\":null},\"type\":\"VECTOR\"}]",
+ "mapStateJSON": "{\"zoom\":4.1,\"center\":{\"lon\":-100.61091,\"lat\":33.23887},\"timeFilters\":{\"from\":\"2015-09-20T00:00:00.000Z\",\"to\":\"2015-09-20T01:00:00.000Z\"},\"refreshConfig\":{\"isPaused\":true,\"interval\":1000},\"settings\":{\"autoFitToDataBounds\":false}}",
+ "title": "document example",
+ "uiStateJSON": "{\"isDarkMode\":false}"
+ },
+ "coreMigrationVersion": "8.0.0",
+ "id": "d2e73f40-e14a-11e8-a35a-370a8516603a",
+ "migrationVersion": {
+ "map": "7.14.0"
+ },
+ "references": [
+ {
+ "id": "c698b940-e149-11e8-a35a-370a8516603a",
+ "name": "layer_0_source_index_pattern",
+ "type": "index-pattern"
+ }
+ ],
+ "type": "map",
+ "version": "WzQzLDJd"
+}
+
+{
+ "attributes": {
+ "description": "",
+ "hits": 0,
+ "kibanaSavedObjectMeta": {
+ "searchSourceJSON": "{\"query\":{\"language\":\"kuery\",\"query\":\"\"},\"filter\":[]}"
+ },
+ "optionsJSON": "{\"hidePanelTitles\":false,\"useMargins\":true}",
+ "panelsJSON": "[{\"version\":\"8.0.0\",\"type\":\"map\",\"gridData\":{\"x\":0,\"y\":0,\"w\":29,\"h\":21,\"i\":\"24ade730-afe4-42b6-919a-c4e0a98c94f2\"},\"panelIndex\":\"24ade730-afe4-42b6-919a-c4e0a98c94f2\",\"embeddableConfig\":{\"mapCenter\":{\"lat\":38.64679,\"lon\":-120.96481,\"zoom\":7.06},\"mapBuffer\":{\"minLon\":-125.44180499999999,\"minLat\":36.364824999999996,\"maxLon\":-116.603825,\"maxLat\":40.943405},\"isLayerTOCOpen\":true,\"openTOCDetails\":[],\"hiddenLayers\":[],\"enhancements\":{}},\"panelRefName\":\"panel_24ade730-afe4-42b6-919a-c4e0a98c94f2\"},{\"version\":\"7.14.0\",\"type\":\"lens\",\"gridData\":{\"x\":29,\"y\":0,\"w\":10,\"h\":21,\"i\":\"44eb3c47-f6ad-4da8-993b-13c10997d585\"},\"panelIndex\":\"44eb3c47-f6ad-4da8-993b-13c10997d585\",\"embeddableConfig\":{\"attributes\":{\"title\":\"\",\"type\":\"lens\",\"visualizationType\":\"lnsMetric\",\"state\":{\"datasourceStates\":{\"indexpattern\":{\"layers\":{\"3cda3519-055a-4b9c-8759-caa28388298c\":{\"columns\":{\"26acba84-22ca-4625-b2ac-5309945e9b30\":{\"label\":\"Count of records\",\"dataType\":\"number\",\"operationType\":\"count\",\"isBucketed\":false,\"scale\":\"ratio\",\"sourceField\":\"Records\"}},\"columnOrder\":[\"26acba84-22ca-4625-b2ac-5309945e9b30\"],\"incompleteColumns\":{}}}}},\"visualization\":{\"layerId\":\"3cda3519-055a-4b9c-8759-caa28388298c\",\"accessor\":\"26acba84-22ca-4625-b2ac-5309945e9b30\"},\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filters\":[]},\"references\":[{\"type\":\"index-pattern\",\"id\":\"c698b940-e149-11e8-a35a-370a8516603a\",\"name\":\"indexpattern-datasource-current-indexpattern\"},{\"type\":\"index-pattern\",\"id\":\"c698b940-e149-11e8-a35a-370a8516603a\",\"name\":\"indexpattern-datasource-layer-3cda3519-055a-4b9c-8759-caa28388298c\"}]},\"enhancements\":{},\"hidePanelTitles\":false,\"type\":\"lens\"},\"title\":\"Count panel\"}]",
+ "refreshInterval": {
+ "pause": true,
+ "value": 1000
+ },
+ "timeFrom": "2015-09-20T00:00:00.000Z",
+ "timeRestore": true,
+ "timeTo": "2015-09-20T01:00:00.000Z",
+ "title": "filter by map extent dashboard",
+ "version": 1
+ },
+ "coreMigrationVersion": "8.0.0",
+ "id": "42f6f040-b34f-11eb-8c95-dd19591c63df",
+ "migrationVersion": {
+ "dashboard": "7.14.0"
+ },
+ "references": [
+ {
+ "id": "d2e73f40-e14a-11e8-a35a-370a8516603a",
+ "name": "24ade730-afe4-42b6-919a-c4e0a98c94f2:panel_24ade730-afe4-42b6-919a-c4e0a98c94f2",
+ "type": "map"
+ },
+ {
+ "id": "c698b940-e149-11e8-a35a-370a8516603a",
+ "name": "44eb3c47-f6ad-4da8-993b-13c10997d585:indexpattern-datasource-current-indexpattern",
+ "type": "index-pattern"
+ },
+ {
+ "id": "c698b940-e149-11e8-a35a-370a8516603a",
+ "name": "44eb3c47-f6ad-4da8-993b-13c10997d585:indexpattern-datasource-layer-3cda3519-055a-4b9c-8759-caa28388298c",
+ "type": "index-pattern"
+ }
+ ],
+ "type": "dashboard",
+ "updated_at": "2021-05-12T18:24:17.228Z",
+ "version": "WzY3LDJd"
+}
+
+{
+ "attributes": {
+ "description": "",
+ "hits": 0,
+ "kibanaSavedObjectMeta": {
+ "searchSourceJSON": "{\"query\":{\"language\":\"kuery\",\"query\":\"\"},\"filter\":[]}"
+ },
+ "optionsJSON": "{\"hidePanelTitles\":false,\"useMargins\":true}",
+ "panelsJSON": "[{\"version\":\"7.14.0\",\"type\":\"map\",\"gridData\":{\"x\":0,\"y\":0,\"w\":24,\"h\":15,\"i\":\"cb82a9e3-2eb0-487f-9ade-0ffb921eb536\"},\"panelIndex\":\"cb82a9e3-2eb0-487f-9ade-0ffb921eb536\",\"embeddableConfig\":{\"attributes\":{\"title\":\"\",\"description\":\"\",\"layerListJSON\":\"[]\",\"mapStateJSON\":\"{\\\"zoom\\\":1.75,\\\"center\\\":{\\\"lon\\\":0,\\\"lat\\\":19.94277},\\\"timeFilters\\\":{\\\"from\\\":\\\"now-15m\\\",\\\"to\\\":\\\"now\\\"},\\\"refreshConfig\\\":{\\\"isPaused\\\":true,\\\"interval\\\":0},\\\"query\\\":{\\\"query\\\":\\\"\\\",\\\"language\\\":\\\"kuery\\\"},\\\"filters\\\":[],\\\"settings\\\":{\\\"autoFitToDataBounds\\\":false,\\\"backgroundColor\\\":\\\"#ffffff\\\",\\\"disableInteractive\\\":false,\\\"disableTooltipControl\\\":false,\\\"hideToolbarOverlay\\\":false,\\\"hideLayerControl\\\":false,\\\"hideViewControl\\\":false,\\\"initialLocation\\\":\\\"LAST_SAVED_LOCATION\\\",\\\"fixedLocation\\\":{\\\"lat\\\":0,\\\"lon\\\":0,\\\"zoom\\\":2},\\\"browserLocation\\\":{\\\"zoom\\\":2},\\\"maxZoom\\\":24,\\\"minZoom\\\":0,\\\"showScaleControl\\\":false,\\\"showSpatialFilters\\\":true,\\\"spatialFiltersAlpa\\\":0.3,\\\"spatialFiltersFillColor\\\":\\\"#DA8B45\\\",\\\"spatialFiltersLineColor\\\":\\\"#DA8B45\\\"}}\",\"uiStateJSON\":\"{\\\"isLayerTOCOpen\\\":true,\\\"openTOCDetails\\\":[]}\"},\"mapCenter\":{\"lat\":19.94277,\"lon\":0,\"zoom\":1.75},\"mapBuffer\":{\"minLon\":-211.13072,\"minLat\":-55.27145,\"maxLon\":211.13072,\"maxLat\":87.44135},\"isLayerTOCOpen\":true,\"openTOCDetails\":[],\"hiddenLayers\":[],\"enhancements\":{},\"type\":\"map\"}}]",
+ "timeRestore": false,
+ "title": "by value map",
+ "version": 1
+ },
+ "coreMigrationVersion": "8.0.0",
+ "id": "4beb0d80-c2ef-11eb-b0cb-bd162d969e6b",
+ "migrationVersion": {
+ "dashboard": "7.14.0"
+ },
+ "references": [],
+ "type": "dashboard",
+ "updated_at": "2021-06-01T15:37:39.198Z",
+ "version": "WzY4LDJd"
+}
+
+{
+ "attributes": {
+ "description": "",
+ "layerListJSON": "[{\"id\":\"n1t6f\",\"label\":null,\"minZoom\":0,\"maxZoom\":24,\"sourceDescriptor\":{\"id\":\"62eca1fc-fe42-11e8-8eb2-f2801f1b9fd1\",\"type\":\"ES_SEARCH\",\"geoField\":\"geometry\",\"limit\":2048,\"filterByMapBounds\":false,\"showTooltip\":true,\"tooltipProperties\":[\"name\"],\"indexPatternRefName\":\"layer_0_source_index_pattern\",\"applyGlobalQuery\":true,\"scalingType\":\"LIMIT\"},\"visible\":true,\"temporary\":false,\"style\":{\"type\":\"VECTOR\",\"properties\":{\"fillColor\":{\"type\":\"DYNAMIC\",\"options\":{\"field\":{\"label\":\"max(prop1) group by meta_for_geo_shapes*.shape_name\",\"name\":\"__kbnjoin__max_of_prop1__855ccb86-fe42-11e8-8eb2-f2801f1b9fd1\",\"origin\":\"join\"},\"color\":null,\"useCustomColorRamp\":true,\"customColorRamp\":[{\"stop\":0,\"color\":\"#E6C220\"},{\"stop\":5,\"color\":\"#F98510\"},{\"stop\":11,\"color\":\"#D36086\"}],\"fieldMetaOptions\":{\"isEnabled\":false,\"sigma\":3}}},\"lineColor\":{\"type\":\"DYNAMIC\",\"options\":{\"color\":\"Blues\",\"field\":{\"label\":\"prop1\",\"name\":\"prop1\",\"origin\":\"source\"},\"useCustomColorRamp\":false,\"fieldMetaOptions\":{\"isEnabled\":false,\"sigma\":3}}},\"lineWidth\":{\"type\":\"STATIC\",\"options\":{\"size\":10}},\"iconSize\":{\"type\":\"STATIC\",\"options\":{\"size\":10}},\"iconOrientation\":{\"type\":\"STATIC\",\"options\":{\"orientation\":0}},\"symbolizeAs\":{\"options\":{\"value\":\"circle\"}},\"icon\":{\"type\":\"STATIC\",\"options\":{\"value\":\"airfield\"}}}},\"type\":\"VECTOR\",\"joins\":[{\"leftField\":\"name\",\"right\":{\"id\":\"855ccb86-fe42-11e8-8eb2-f2801f1b9fd1\",\"indexPatternTitle\":\"meta_for_geo_shapes*\",\"term\":\"shape_name\",\"metrics\":[{\"type\":\"max\",\"field\":\"prop1\"}],\"indexPatternRefName\":\"layer_0_join_0_index_pattern\",\"applyGlobalQuery\":true,\"type\":\"ES_TERM_SOURCE\"}}]}]",
+ "mapStateJSON": "{\"zoom\":3.42,\"center\":{\"lon\":81.67747,\"lat\":1.80586},\"timeFilters\":{\"from\":\"now-17m\",\"to\":\"now\",\"mode\":\"quick\"},\"refreshConfig\":{\"isPaused\":true,\"interval\":1000},\"query\":{\"language\":\"kuery\",\"query\":\"\"},\"filters\":[],\"settings\":{\"autoFitToDataBounds\":false}}",
+ "title": "join and dynamic coloring demo",
+ "uiStateJSON": "{\"isLayerTOCOpen\":true,\"openTOCDetails\":[]}"
+ },
+ "coreMigrationVersion": "8.0.0",
+ "id": "4cd3e220-bf64-11e9-bbcc-7db09a1519e9",
+ "migrationVersion": {
+ "map": "7.14.0"
+ },
+ "references": [
+ {
+ "id": "561253e0-f731-11e8-8487-11b9dd924f96",
+ "name": "layer_0_source_index_pattern",
+ "type": "index-pattern"
+ },
+ {
+ "id": "e20b2a30-f735-11e8-8ce0-9723965e01e3",
+ "name": "layer_0_join_0_index_pattern",
+ "type": "index-pattern"
+ }
+ ],
+ "type": "map",
+ "updated_at": "2019-08-15T13:56:15.793Z",
+ "version": "WzYwLDJd"
+}
+
+{
+ "attributes": {
+ "description": "",
+ "layerListJSON": "[{\"id\":\"z52lq\",\"label\":\"logstash\",\"minZoom\":0,\"maxZoom\":24,\"sourceDescriptor\":{\"id\":\"e1a5e1a6-676c-4a89-8ea9-0d91d64b73c6\",\"type\":\"ES_SEARCH\",\"geoField\":\"geo.coordinates\",\"limit\":2048,\"filterByMapBounds\":true,\"showTooltip\":true,\"tooltipProperties\":[],\"scalingType\":\"TOP_HITS\",\"topHitsSplitField\":\"hour_of_day\",\"topHitsSize\":1,\"sortField\":\"@timestamp\",\"sortOrder\":\"desc\",\"applyGlobalQuery\":true,\"indexPatternRefName\":\"layer_1_source_index_pattern\"},\"visible\":true,\"temporary\":false,\"style\":{\"type\":\"VECTOR\",\"properties\":{\"fillColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#e6194b\"}},\"lineColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#FFFFFF\"}},\"lineWidth\":{\"type\":\"STATIC\",\"options\":{\"size\":1}},\"iconSize\":{\"type\":\"STATIC\",\"options\":{\"size\":10}},\"symbolizeAs\":{\"options\":{\"value\":\"circle\"}},\"icon\":{\"type\":\"STATIC\",\"options\":{\"value\":\"airfield\"}}},\"previousStyle\":null},\"type\":\"VECTOR\"}]",
+ "mapStateJSON": "{\"zoom\":4.1,\"center\":{\"lon\":-100.61091,\"lat\":33.23887},\"timeFilters\":{\"from\":\"2015-09-20T00:00:00.000Z\",\"to\":\"2015-09-24T01:00:00.000Z\"},\"refreshConfig\":{\"isPaused\":true,\"interval\":1000},\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filters\":[],\"settings\":{\"autoFitToDataBounds\":false}}",
+ "title": "document example top hits split with scripted field",
+ "uiStateJSON": "{\"isLayerTOCOpen\":true,\"openTOCDetails\":[]}"
+ },
+ "coreMigrationVersion": "8.0.0",
+ "id": "4ea1e4f0-4dba-11ea-b554-4ba0def79f86",
+ "migrationVersion": {
+ "map": "7.14.0"
+ },
+ "references": [
+ {
+ "id": "c698b940-e149-11e8-a35a-370a8516603a",
+ "name": "layer_1_source_index_pattern",
+ "type": "index-pattern"
+ }
+ ],
+ "type": "map",
+ "updated_at": "2020-02-12T17:08:36.671Z",
+ "version": "WzQ3LDJd"
+}
+
+{
+ "attributes": {
+ "fields": "[]",
+ "title": "antimeridian_shapes"
+ },
+ "coreMigrationVersion": "8.0.0",
+ "id": "502886a0-18f8-11e9-97c8-5da5e037299c",
+ "migrationVersion": {
+ "index-pattern": "7.11.0"
+ },
+ "references": [],
+ "type": "index-pattern",
+ "version": "WzQwLDJd"
+}
+
+{
+ "attributes": {
+ "layerListJSON": "[{\"id\":\"3xlvm\",\"label\":null,\"minZoom\":0,\"maxZoom\":24,\"sourceDescriptor\":{\"resolution\":\"COARSE\",\"type\":\"ES_GEO_GRID\",\"id\":\"427aa49d-a552-4e7d-a629-67c47db27128\",\"geoField\":\"geo.coordinates\",\"requestType\":\"heatmap\",\"indexPatternRefName\":\"layer_0_source_index_pattern\",\"applyGlobalQuery\":true},\"visible\":true,\"temporary\":false,\"style\":{\"type\":\"HEATMAP\",\"refinement\":\"coarse\",\"properties\":{},\"previousStyle\":null},\"type\":\"HEATMAP\"}]",
+ "mapStateJSON": "{\"zoom\":3.59,\"center\":{\"lon\":-98.05765,\"lat\":38.32288},\"timeFilters\":{\"from\":\"2015-09-20T00:00:00.000Z\",\"to\":\"2015-09-20T01:00:00.000Z\"},\"refreshConfig\":{\"isPaused\":true,\"interval\":1000},\"settings\":{\"autoFitToDataBounds\":false}}",
+ "title": "geo grid heatmap example",
+ "uiStateJSON": "{\"isDarkMode\":false}"
+ },
+ "coreMigrationVersion": "8.0.0",
+ "id": "60a3c850-03ae-11e9-83d4-5b39b6575325",
+ "migrationVersion": {
+ "map": "7.14.0"
+ },
+ "references": [
+ {
+ "id": "c698b940-e149-11e8-a35a-370a8516603a",
+ "name": "layer_0_source_index_pattern",
+ "type": "index-pattern"
+ }
+ ],
+ "type": "map",
+ "version": "WzUyLDJd"
+}
+
+{
+ "attributes": {
+ "fields": "[]",
+ "title": "flights"
+ },
+ "coreMigrationVersion": "8.0.0",
+ "id": "f7fc94a0-936b-11e9-a43f-eb6ee3467b2b",
+ "migrationVersion": {
+ "index-pattern": "7.11.0"
+ },
+ "references": [],
+ "type": "index-pattern",
+ "updated_at": "2019-06-20T14:59:15.348Z",
+ "version": "WzQxLDJd"
+}
+
+{
+ "attributes": {
+ "description": "",
+ "layerListJSON": "[{\"sourceDescriptor\":{\"id\":\"5b46884e-062f-4ff4-9dd2-bd410d9e8754\",\"type\":\"ES_SEARCH\",\"geoField\":\"location\",\"filterByMapBounds\":false,\"tooltipProperties\":[],\"topHitsSize\":1,\"indexPatternRefName\":\"layer_0_source_index_pattern\",\"applyGlobalQuery\":true,\"scalingType\":\"LIMIT\"},\"id\":\"8fcb7c4b-7d6a-4b69-82c2-74f53ec0df24\",\"label\":null,\"minZoom\":0,\"maxZoom\":24,\"alpha\":0.75,\"visible\":true,\"style\":{\"type\":\"VECTOR\",\"properties\":{\"fillColor\":{\"type\":\"DYNAMIC\",\"options\":{\"color\":\"Reds\",\"field\":{\"label\":\"altitude\",\"name\":\"altitude\",\"origin\":\"source\"},\"fieldMetaOptions\":{\"isEnabled\":false,\"sigma\":3}}},\"lineColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#FFFFFF\"}},\"lineWidth\":{\"type\":\"STATIC\",\"options\":{\"size\":1}},\"iconSize\":{\"type\":\"DYNAMIC\",\"options\":{\"minSize\":30,\"maxSize\":64,\"field\":{\"label\":\"altitude\",\"name\":\"altitude\",\"origin\":\"source\"},\"fieldMetaOptions\":{\"isEnabled\":false,\"sigma\":3}}},\"iconOrientation\":{\"type\":\"DYNAMIC\",\"options\":{\"field\":{\"label\":\"heading\",\"name\":\"heading\",\"origin\":\"source\"},\"fieldMetaOptions\":{\"isEnabled\":false,\"sigma\":3}}},\"symbolizeAs\":{\"options\":{\"value\":\"icon\"}},\"icon\":{\"type\":\"STATIC\",\"options\":{\"value\":\"airport\"}}}},\"type\":\"VECTOR\"}]",
+ "mapStateJSON": "{\"zoom\":4.13,\"center\":{\"lon\":-3.57807,\"lat\":2.04204},\"timeFilters\":{\"from\":\"now-15m\",\"to\":\"now\"},\"refreshConfig\":{\"isPaused\":false,\"interval\":0},\"query\":{\"language\":\"kuery\",\"query\":\"\"},\"settings\":{\"autoFitToDataBounds\":false}}",
+ "title": "vector styling icon demo",
+ "uiStateJSON": "{\"isLayerTOCOpen\":true,\"openTOCDetails\":[\"8fcb7c4b-7d6a-4b69-82c2-74f53ec0df24\"]}"
+ },
+ "coreMigrationVersion": "8.0.0",
+ "id": "62914770-936c-11e9-a43f-eb6ee3467b2b",
+ "migrationVersion": {
+ "map": "7.14.0"
+ },
+ "references": [
+ {
+ "id": "f7fc94a0-936b-11e9-a43f-eb6ee3467b2b",
+ "name": "layer_0_source_index_pattern",
+ "type": "index-pattern"
+ }
+ ],
+ "type": "map",
+ "updated_at": "2019-06-20T15:02:13.095Z",
+ "version": "WzU5LDJd"
+}
+
+{
+ "attributes": {
+ "description": "",
+ "layerListJSON": "[{\"id\":\"z52lq\",\"label\":\"logstash\",\"minZoom\":0,\"maxZoom\":24,\"sourceDescriptor\":{\"id\":\"e1a5e1a6-676c-4a89-8ea9-0d91d64b73c6\",\"type\":\"ES_SEARCH\",\"geoField\":\"geo.coordinates\",\"limit\":2048,\"filterByMapBounds\":true,\"showTooltip\":true,\"tooltipProperties\":[],\"topHitsSplitField\":\"machine.os.raw\",\"topHitsSize\":2,\"indexPatternRefName\":\"layer_1_source_index_pattern\",\"sortField\":\"@timestamp\",\"sortOrder\":\"desc\",\"applyGlobalQuery\":true,\"scalingType\":\"TOP_HITS\"},\"visible\":true,\"temporary\":false,\"style\":{\"type\":\"VECTOR\",\"properties\":{\"fillColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#e6194b\"}},\"lineColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#FFFFFF\"}},\"lineWidth\":{\"type\":\"STATIC\",\"options\":{\"size\":1}},\"iconSize\":{\"type\":\"STATIC\",\"options\":{\"size\":10}},\"symbolizeAs\":{\"options\":{\"value\":\"circle\"}},\"icon\":{\"type\":\"STATIC\",\"options\":{\"value\":\"marker\"}}},\"previousStyle\":null},\"type\":\"VECTOR\"}]",
+ "mapStateJSON": "{\"zoom\":4.1,\"center\":{\"lon\":-100.61091,\"lat\":33.23887},\"timeFilters\":{\"from\":\"2015-09-20T00:00:00.000Z\",\"to\":\"2015-09-24T01:00:00.000Z\"},\"refreshConfig\":{\"isPaused\":true,\"interval\":1000},\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"settings\":{\"autoFitToDataBounds\":false}}",
+ "title": "document example top hits",
+ "uiStateJSON": "{\"isLayerTOCOpen\":true,\"openTOCDetails\":[]}"
+ },
+ "coreMigrationVersion": "8.0.0",
+ "id": "68305470-87bc-11e9-a991-3b492a7c3e09",
+ "migrationVersion": {
+ "map": "7.14.0"
+ },
+ "references": [
+ {
+ "id": "c698b940-e149-11e8-a35a-370a8516603a",
+ "name": "layer_1_source_index_pattern",
+ "type": "index-pattern"
+ }
+ ],
+ "type": "map",
+ "version": "WzQ2LDJd"
+}
+
+{
+ "attributes": {
+ "description": "",
+ "layerListJSON": "[{\"id\":\"ad9fj\",\"label\":null,\"minZoom\":0,\"maxZoom\":24,\"sourceDescriptor\":{\"id\":\"4e4b5628-dbdc-40bb-93f0-8a7a48be1141\",\"type\":\"ES_SEARCH\",\"geoField\":\"location\",\"limit\":2048,\"filterByMapBounds\":true,\"tooltipProperties\":[],\"indexPatternRefName\":\"layer_0_source_index_pattern\",\"applyGlobalQuery\":true,\"scalingType\":\"LIMIT\"},\"visible\":true,\"temporary\":true,\"style\":{\"type\":\"VECTOR\",\"properties\":{\"fillColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#e6194b\"}},\"lineColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#FFFFFF\"}},\"lineWidth\":{\"type\":\"STATIC\",\"options\":{\"size\":1}},\"iconSize\":{\"type\":\"STATIC\",\"options\":{\"size\":10}},\"symbolizeAs\":{\"options\":{\"value\":\"circle\"}},\"icon\":{\"type\":\"STATIC\",\"options\":{\"value\":\"marker\"}}}},\"type\":\"VECTOR\"}]",
+ "mapStateJSON": "{\"zoom\":5.65,\"center\":{\"lon\":179.03193,\"lat\":0.09593},\"timeFilters\":{\"from\":\"now-15m\",\"to\":\"now\"},\"refreshConfig\":{\"isPaused\":true,\"interval\":0},\"query\":{\"language\":\"kuery\",\"query\":\"\"},\"settings\":{\"autoFitToDataBounds\":false}}",
+ "title": "antimeridian shapes example",
+ "uiStateJSON": "{\"isDarkMode\":false}"
+ },
+ "coreMigrationVersion": "8.0.0",
+ "id": "6a6ff930-18f8-11e9-97c8-5da5e037299c",
+ "migrationVersion": {
+ "map": "7.14.0"
+ },
+ "references": [
+ {
+ "id": "502886a0-18f8-11e9-97c8-5da5e037299c",
+ "name": "layer_0_source_index_pattern",
+ "type": "index-pattern"
+ }
+ ],
+ "type": "map",
+ "version": "WzU3LDJd"
+}
+
+{
+ "attributes": {
+ "description": "",
+ "layerListJSON": "[{\"sourceDescriptor\":{\"type\":\"KIBANA_TILEMAP\"},\"id\":\"ap0ys\",\"label\":\"Custom_TMS\",\"minZoom\":0,\"maxZoom\":24,\"alpha\":1,\"visible\":true,\"temporary\":false,\"style\":{\"type\":\"TILE\",\"properties\":{},\"previousStyle\":null},\"type\":\"TILE\"},{\"sourceDescriptor\":{\"type\":\"REGIONMAP_FILE\",\"name\":\"nameThatDoesNotExitForKibanaRegionmapSource\"},\"temporary\":false,\"id\":\"0sabv\",\"label\":\"Custom_vector_shapes\",\"minZoom\":0,\"maxZoom\":24,\"alpha\":0.75,\"visible\":true,\"style\":{\"type\":\"VECTOR\",\"properties\":{\"fillColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#3cb44b\"}},\"lineColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#FFFFFF\"}},\"lineWidth\":{\"type\":\"STATIC\",\"options\":{\"size\":1}},\"iconSize\":{\"type\":\"STATIC\",\"options\":{\"size\":10}},\"symbolizeAs\":{\"options\":{\"value\":\"circle\"}},\"icon\":{\"type\":\"STATIC\",\"options\":{\"value\":\"marker\"}}},\"previousStyle\":null},\"type\":\"VECTOR\"},{\"sourceDescriptor\":{\"type\":\"EMS_TMS\",\"id\":\"idThatDoesNotExitForEMSTile\"},\"temporary\":false,\"id\":\"plw9l\",\"label\":\"EMS_tiles\",\"minZoom\":0,\"maxZoom\":24,\"alpha\":1,\"visible\":true,\"style\":{\"type\":\"TILE\",\"properties\":{},\"previousStyle\":null},\"type\":\"VECTOR_TILE\"},{\"sourceDescriptor\":{\"type\":\"EMS_FILE\",\"id\":\"idThatDoesNotExitForEMSFileSource\"},\"temporary\":false,\"id\":\"2gro0\",\"label\":\"EMS_vector_shapes\",\"minZoom\":0,\"maxZoom\":24,\"alpha\":0.75,\"visible\":true,\"style\":{\"type\":\"VECTOR\",\"properties\":{\"fillColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#e6194b\"}},\"lineColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#FFFFFF\"}},\"lineWidth\":{\"type\":\"STATIC\",\"options\":{\"size\":1}},\"iconSize\":{\"type\":\"STATIC\",\"options\":{\"size\":10}},\"symbolizeAs\":{\"options\":{\"value\":\"circle\"}},\"icon\":{\"type\":\"STATIC\",\"options\":{\"value\":\"marker\"}}},\"previousStyle\":null},\"type\":\"VECTOR\"},{\"sourceDescriptor\":{\"type\":\"ES_GEO_GRID\",\"id\":\"f67fe707-95dd-46d6-89b8-82617b251b61\",\"geoField\":\"geo.coordinates\",\"requestType\":\"grid\",\"resolution\":\"COARSE\",\"indexPatternRefName\":\"layer_4_source_index_pattern\",\"applyGlobalQuery\":true},\"temporary\":false,\"id\":\"pl5qd\",\"label\":\"\",\"minZoom\":0,\"maxZoom\":24,\"alpha\":0.75,\"visible\":true,\"style\":{\"type\":\"VECTOR\",\"properties\":{\"fillColor\":{\"type\":\"DYNAMIC\",\"options\":{\"field\":{\"label\":\"Count\",\"name\":\"doc_count\",\"origin\":\"source\"},\"color\":\"Blues\",\"fieldMetaOptions\":{\"isEnabled\":false,\"sigma\":3}}},\"lineColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#FFFFFF\"}},\"lineWidth\":{\"type\":\"STATIC\",\"options\":{\"size\":1}},\"iconSize\":{\"type\":\"DYNAMIC\",\"options\":{\"field\":{\"label\":\"Count\",\"name\":\"doc_count\",\"origin\":\"source\"},\"minSize\":4,\"maxSize\":32,\"fieldMetaOptions\":{\"isEnabled\":false,\"sigma\":3}}},\"symbolizeAs\":{\"options\":{\"value\":\"circle\"}},\"icon\":{\"type\":\"STATIC\",\"options\":{\"value\":\"marker\"}}},\"previousStyle\":null},\"type\":\"VECTOR\"},{\"sourceDescriptor\":{\"id\":\"a07072bb-3a92-4320-bd37-250ef6d04db7\",\"type\":\"ES_SEARCH\",\"geoField\":\"geo.coordinates\",\"limit\":2048,\"filterByMapBounds\":true,\"tooltipProperties\":[],\"indexPatternRefName\":\"layer_5_source_index_pattern\",\"applyGlobalQuery\":true,\"scalingType\":\"LIMIT\"},\"temporary\":false,\"id\":\"9bw8h\",\"label\":\"\",\"minZoom\":0,\"maxZoom\":24,\"alpha\":0.75,\"visible\":true,\"style\":{\"type\":\"VECTOR\",\"properties\":{\"fillColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#e6194b\"}},\"lineColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#FFFFFF\"}},\"lineWidth\":{\"type\":\"STATIC\",\"options\":{\"size\":1}},\"iconSize\":{\"type\":\"STATIC\",\"options\":{\"size\":10}},\"symbolizeAs\":{\"options\":{\"value\":\"circle\"}},\"icon\":{\"type\":\"STATIC\",\"options\":{\"value\":\"marker\"}}},\"previousStyle\":null},\"type\":\"VECTOR\"},{\"id\":\"n1t6f\",\"label\":null,\"minZoom\":0,\"maxZoom\":24,\"sourceDescriptor\":{\"id\":\"62eca1fc-fe42-11e8-8eb2-f2801f1b9fd1\",\"type\":\"ES_SEARCH\",\"geoField\":\"geometry\",\"limit\":2048,\"filterByMapBounds\":false,\"showTooltip\":true,\"tooltipProperties\":[],\"indexPatternRefName\":\"layer_6_source_index_pattern\",\"applyGlobalQuery\":true,\"scalingType\":\"LIMIT\"},\"visible\":true,\"temporary\":false,\"style\":{\"type\":\"VECTOR\",\"properties\":{\"fillColor\":{\"type\":\"DYNAMIC\",\"options\":{\"field\":{\"label\":\"max(prop1) group by meta_for_geo_shapes*.shape_name\",\"name\":\"__kbnjoin__max_of_prop1__855ccb86-fe42-11e8-8eb2-f2801f1b9fd1\",\"origin\":\"join\"},\"color\":\"Blues\",\"fieldMetaOptions\":{\"isEnabled\":false,\"sigma\":3}}},\"iconSize\":{\"type\":\"STATIC\",\"options\":{\"size\":10}},\"symbolizeAs\":{\"options\":{\"value\":\"circle\"}},\"icon\":{\"type\":\"STATIC\",\"options\":{\"value\":\"marker\"}}},\"temporary\":true,\"previousStyle\":null},\"type\":\"VECTOR\",\"joins\":[{\"leftField\":\"name\",\"right\":{\"id\":\"855ccb86-fe42-11e8-8eb2-f2801f1b9fd1\",\"indexPatternTitle\":\"meta_for_geo_shapes*\",\"term\":\"shape_name\",\"metrics\":[{\"type\":\"max\",\"field\":\"prop1\"}],\"indexPatternRefName\":\"layer_6_join_0_index_pattern\",\"applyGlobalQuery\":true,\"type\":\"ES_TERM_SOURCE\"}}]}]",
+ "mapStateJSON": "{\"zoom\":0.71,\"center\":{\"lon\":0.10268,\"lat\":0},\"timeFilters\":{\"from\":\"now-7d\",\"to\":\"now\"},\"refreshConfig\":{\"isPaused\":true,\"interval\":0},\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"settings\":{\"autoFitToDataBounds\":false}}",
+ "title": "layer with errors",
+ "uiStateJSON": "{}"
+ },
+ "coreMigrationVersion": "8.0.0",
+ "id": "745c98b0-23e1-11e9-a048-6fef5a3e0d1e",
+ "migrationVersion": {
+ "map": "7.14.0"
+ },
+ "references": [
+ {
+ "id": "idThatDoesNotExitForESGeoGridSource",
+ "name": "layer_4_source_index_pattern",
+ "type": "index-pattern"
+ },
+ {
+ "id": "idThatDoesNotExitForESSearchSource",
+ "name": "layer_5_source_index_pattern",
+ "type": "index-pattern"
+ },
+ {
+ "id": "561253e0-f731-11e8-8487-11b9dd924f96",
+ "name": "layer_6_source_index_pattern",
+ "type": "index-pattern"
+ },
+ {
+ "id": "idThatDoesNotExitForESJoinSource",
+ "name": "layer_6_join_0_index_pattern",
+ "type": "index-pattern"
+ }
+ ],
+ "type": "map",
+ "version": "WzU4LDJd"
+}
+
+{
+ "attributes": {
+ "description": "",
+ "layerListJSON": "[{\"id\":\"g1xkv\",\"label\":null,\"minZoom\":0,\"maxZoom\":24,\"sourceDescriptor\":{\"resolution\":\"SUPER_FINE\",\"type\":\"ES_GEO_GRID\",\"id\":\"9305f6ea-4518-4c06-95b9-33321aa38d6a\",\"geoField\":\"geo.coordinates\",\"requestType\":\"grid\",\"metrics\":[{\"type\":\"count\"},{\"type\":\"max\",\"field\":\"bytes\"}],\"indexPatternRefName\":\"layer_0_source_index_pattern\",\"applyGlobalQuery\":true},\"visible\":true,\"temporary\":false,\"style\":{\"type\":\"VECTOR\",\"properties\":{\"fillColor\":{\"type\":\"DYNAMIC\",\"options\":{\"field\":{\"label\":\"max of bytes\",\"name\":\"max_of_bytes\",\"origin\":\"source\"},\"color\":\"Blues\"}},\"lineColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#cccccc\"}},\"lineWidth\":{\"type\":\"STATIC\",\"options\":{\"size\":1}},\"iconSize\":{\"type\":\"DYNAMIC\",\"options\":{\"field\":{\"label\":\"Count\",\"name\":\"doc_count\",\"origin\":\"source\"},\"minSize\":4,\"maxSize\":32}}},\"temporary\":true,\"previousStyle\":null},\"type\":\"TILED_VECTOR\"}]",
+ "mapStateJSON": "{\"zoom\":3.59,\"center\":{\"lon\":-98.05765,\"lat\":38.32288},\"timeFilters\":{\"from\":\"2015-09-20T00:00:00.000Z\",\"to\":\"2015-09-20T01:00:00.000Z\"},\"refreshConfig\":{\"isPaused\":true,\"interval\":1000},\"settings\":{\"autoFitToDataBounds\":false}}",
+ "title": "geo grid vector grid example SUPER_FINE resolution",
+ "uiStateJSON": "{\"isDarkMode\":false}"
+ },
+ "coreMigrationVersion": "8.0.0",
+ "id": "78116c8c-fd2a-11ea-adc1-0242ac120002",
+ "migrationVersion": {
+ "map": "7.14.0"
+ },
+ "references": [
+ {
+ "id": "c698b940-e149-11e8-a35a-370a8516603a",
+ "name": "layer_0_source_index_pattern",
+ "type": "index-pattern"
+ }
+ ],
+ "type": "map",
+ "version": "WzU1LDJd"
+}
+
+{
+ "attributes": {
+ "description": "",
+ "layerListJSON": "[{\"id\":\"z52lq\",\"label\":null,\"minZoom\":0,\"maxZoom\":24,\"sourceDescriptor\":{\"id\":\"e1a5e1a6-676c-4a89-8ea9-0d91d64b73c6\",\"type\":\"ES_SEARCH\",\"geoField\":\"geo.coordinates\",\"limit\":2048,\"filterByMapBounds\":true,\"showTooltip\":true,\"tooltipProperties\":[\"machine.os\"],\"indexPatternRefName\":\"layer_0_source_index_pattern\",\"applyGlobalQuery\":true,\"scalingType\":\"LIMIT\"},\"visible\":true,\"temporary\":false,\"style\":{\"type\":\"VECTOR\",\"properties\":{\"fillColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#e6194b\"}},\"lineColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#FFFFFF\"}},\"lineWidth\":{\"type\":\"STATIC\",\"options\":{\"size\":1}},\"iconSize\":{\"type\":\"STATIC\",\"options\":{\"size\":10}},\"symbolizeAs\":{\"options\":{\"value\":\"circle\"}},\"icon\":{\"type\":\"STATIC\",\"options\":{\"value\":\"marker\"}}},\"previousStyle\":null},\"type\":\"VECTOR\"}]",
+ "mapStateJSON": "{\"zoom\":4.09,\"center\":{\"lon\":-100.58836,\"lat\":33.21778},\"timeFilters\":{\"from\":\"2015-09-20T00:00:00.000Z\",\"to\":\"2015-09-20T01:00:00.000Z\"},\"refreshConfig\":{\"isPaused\":true,\"interval\":1000},\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filters\":[{\"meta\":{\"index\":\"c698b940-e149-11e8-a35a-370a8516603a\",\"alias\":null,\"negate\":false,\"disabled\":false,\"type\":\"phrase\",\"key\":\"machine.os.raw\",\"value\":\"ios\",\"params\":{\"query\":\"ios\"}},\"query\":{\"match\":{\"machine.os.raw\":{\"query\":\"ios\",\"type\":\"phrase\"}}},\"$state\":{\"store\":\"appState\"}}],\"settings\":{\"autoFitToDataBounds\":false}}",
+ "title": "document example with filter",
+ "uiStateJSON": "{\"isDarkMode\":false}"
+ },
+ "coreMigrationVersion": "8.0.0",
+ "id": "7f047b80-b93b-11e9-b146-7d043e1b0586",
+ "migrationVersion": {
+ "map": "7.14.0"
+ },
+ "references": [
+ {
+ "id": "c698b940-e149-11e8-a35a-370a8516603a",
+ "name": "layer_0_source_index_pattern",
+ "type": "index-pattern"
+ }
+ ],
+ "type": "map",
+ "version": "WzQ1LDJd"
+}
+
+{
+ "attributes": {
+ "description": "",
+ "layerListJSON": "[{\"id\":\"z52lq\",\"label\":null,\"minZoom\":0,\"maxZoom\":24,\"sourceDescriptor\":{\"id\":\"e1a5e1a6-676c-4a89-8ea9-0d91d64b73c6\",\"type\":\"ES_SEARCH\",\"geoField\":\"geo.coordinates\",\"limit\":2048,\"filterByMapBounds\":true,\"showTooltip\":true,\"tooltipProperties\":[\"machine.os\"],\"indexPatternRefName\":\"layer_0_source_index_pattern\",\"applyGlobalQuery\":true,\"scalingType\":\"LIMIT\"},\"visible\":true,\"temporary\":false,\"style\":{\"type\":\"VECTOR\",\"properties\":{\"fillColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#e6194b\"}},\"lineColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#FFFFFF\"}},\"lineWidth\":{\"type\":\"STATIC\",\"options\":{\"size\":1}},\"iconSize\":{\"type\":\"STATIC\",\"options\":{\"size\":10}},\"symbolizeAs\":{\"options\":{\"value\":\"circle\"}},\"icon\":{\"type\":\"STATIC\",\"options\":{\"value\":\"marker\"}}},\"previousStyle\":null},\"type\":\"VECTOR\"}]",
+ "mapStateJSON": "{\"zoom\":4.1,\"center\":{\"lon\":-100.61091,\"lat\":33.23887},\"timeFilters\":{\"from\":\"2015-09-20T00:00:00.000Z\",\"to\":\"2015-09-20T01:00:00.000Z\"},\"refreshConfig\":{\"isPaused\":true,\"interval\":1000},\"query\":{\"query\":\"machine.os.raw : \\\"ios\\\"\",\"language\":\"kuery\"},\"settings\":{\"autoFitToDataBounds\":false}}",
+ "title": "document example with query",
+ "uiStateJSON": "{\"isDarkMode\":false}"
+ },
+ "coreMigrationVersion": "8.0.0",
+ "id": "8eabdab0-144f-11e9-809f-ad25bb78262c",
+ "migrationVersion": {
+ "map": "7.14.0"
+ },
+ "references": [
+ {
+ "id": "c698b940-e149-11e8-a35a-370a8516603a",
+ "name": "layer_0_source_index_pattern",
+ "type": "index-pattern"
+ }
+ ],
+ "type": "map",
+ "version": "WzQ0LDJd"
+}
+
+{
+ "attributes": {
+ "description": "Ok responses for jpg files",
+ "filters": [
+ {
+ "$state": {
+ "store": "appState"
+ },
+ "meta": {
+ "alias": null,
+ "disabled": false,
+ "index": "b15b1d40-a8bb-11e9-98cf-2bb06ef63e0b",
+ "key": "extension.raw",
+ "negate": false,
+ "params": {
+ "query": "jpg"
+ },
+ "type": "phrase",
+ "value": "jpg"
+ },
+ "query": {
+ "match": {
+ "extension.raw": {
+ "query": "jpg",
+ "type": "phrase"
+ }
+ }
+ }
+ }
+ ],
+ "query": {
+ "language": "kuery",
+ "query": "response:200"
+ },
+ "title": "OKJpgs"
+ },
+ "coreMigrationVersion": "8.0.0",
+ "id": "OKJpgs",
+ "references": [],
+ "type": "query",
+ "updated_at": "2019-07-17T17:54:26.378Z",
+ "version": "WzY5LDJd"
+}
+
+{
+ "attributes": {
+ "description": "shapes with mvt scaling",
+ "layerListJSON": "[{\"sourceDescriptor\":{\"geoField\":\"geometry\",\"filterByMapBounds\":true,\"scalingType\":\"MVT\",\"topHitsSize\":1,\"id\":\"97f8555e-8db0-4bd8-8b18-22e32f468667\",\"type\":\"ES_SEARCH\",\"tooltipProperties\":[],\"sortField\":\"\",\"sortOrder\":\"desc\",\"indexPatternRefName\":\"layer_1_source_index_pattern\"},\"id\":\"caffa63a-ebfb-466d-8ff6-d797975b88ab\",\"label\":null,\"minZoom\":0,\"maxZoom\":24,\"alpha\":1,\"visible\":true,\"style\":{\"type\":\"VECTOR\",\"properties\":{\"icon\":{\"type\":\"STATIC\",\"options\":{\"value\":\"marker\"}},\"fillColor\":{\"type\":\"DYNAMIC\",\"options\":{\"color\":\"Blues\",\"colorCategory\":\"palette_0\",\"field\":{\"name\":\"prop1\",\"origin\":\"source\"},\"fieldMetaOptions\":{\"isEnabled\":true,\"sigma\":1},\"type\":\"ORDINAL\"}},\"lineColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#41937c\"}},\"lineWidth\":{\"type\":\"STATIC\",\"options\":{\"size\":1}},\"iconSize\":{\"type\":\"STATIC\",\"options\":{\"size\":6}},\"iconOrientation\":{\"type\":\"STATIC\",\"options\":{\"orientation\":0}},\"labelText\":{\"type\":\"STATIC\",\"options\":{\"value\":\"\"}},\"labelColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#000000\"}},\"labelSize\":{\"type\":\"STATIC\",\"options\":{\"size\":14}},\"labelBorderColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#FFFFFF\"}},\"symbolizeAs\":{\"options\":{\"value\":\"circle\"}},\"labelBorderSize\":{\"options\":{\"size\":\"SMALL\"}}},\"isTimeAware\":true},\"type\":\"TILED_VECTOR\",\"joins\":[]}]",
+ "mapStateJSON": "{\"zoom\":3.75,\"center\":{\"lon\":80.01106,\"lat\":3.65009},\"timeFilters\":{\"from\":\"now-15m\",\"to\":\"now\"},\"refreshConfig\":{\"isPaused\":true,\"interval\":0},\"query\":{\"language\":\"kuery\",\"query\":\"\"},\"filters\":[],\"settings\":{\"autoFitToDataBounds\":false,\"initialLocation\":\"LAST_SAVED_LOCATION\",\"fixedLocation\":{\"lat\":0,\"lon\":0,\"zoom\":2},\"browserLocation\":{\"zoom\":2},\"maxZoom\":24,\"minZoom\":0,\"showSpatialFilters\":true,\"spatialFiltersAlpa\":0.3,\"spatialFiltersFillColor\":\"#DA8B45\",\"spatialFiltersLineColor\":\"#DA8B45\"}}",
+ "title": "geo_shape_mvt",
+ "uiStateJSON": "{\"isLayerTOCOpen\":true,\"openTOCDetails\":[]}"
+ },
+ "coreMigrationVersion": "8.0.0",
+ "id": "bff99716-e3dc-11ea-87d0-0242ac130003",
+ "migrationVersion": {
+ "map": "7.14.0"
+ },
+ "references": [
+ {
+ "id": "561253e0-f731-11e8-8487-11b9dd924f96",
+ "name": "layer_1_source_index_pattern",
+ "type": "index-pattern"
+ }
+ ],
+ "type": "map",
+ "updated_at": "2020-08-10T18:27:39.805Z",
+ "version": "WzY0LDJd"
+}
+
+{
+ "attributes": {
+ "description": "",
+ "layerListJSON": "[{\"id\":\"z52lq\",\"label\":\"logstash\",\"minZoom\":0,\"maxZoom\":24,\"sourceDescriptor\":{\"id\":\"e1a5e1a6-676c-4a89-8ea9-0d91d64b73c6\",\"type\":\"ES_SEARCH\",\"geoField\":\"geo.coordinates\",\"limit\":2048,\"filterByMapBounds\":true,\"showTooltip\":true,\"tooltipProperties\":[],\"indexPatternRefName\":\"layer_1_source_index_pattern\",\"applyGlobalQuery\":true,\"scalingType\":\"LIMIT\"},\"visible\":true,\"temporary\":false,\"style\":{\"type\":\"VECTOR\",\"properties\":{\"fillColor\":{\"type\":\"DYNAMIC\",\"options\":{\"color\":\"Blues\",\"field\":{\"label\":\"@timestamp\",\"name\":\"@timestamp\",\"origin\":\"source\"},\"fieldMetaOptions\":{\"isEnabled\":false,\"sigma\":3}}},\"lineColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#FFFFFF\"}},\"lineWidth\":{\"type\":\"STATIC\",\"options\":{\"size\":1}},\"iconSize\":{\"type\":\"DYNAMIC\",\"options\":{\"minSize\":4,\"maxSize\":24,\"field\":{\"label\":\"bytes\",\"name\":\"bytes\",\"origin\":\"source\"},\"fieldMetaOptions\":{\"isEnabled\":false,\"sigma\":3}}},\"iconOrientation\":{\"type\":\"STATIC\",\"options\":{\"orientation\":0}},\"symbolizeAs\":{\"options\":{\"value\":\"circle\"}},\"icon\":{\"type\":\"STATIC\",\"options\":{\"value\":\"airfield\"}}}},\"type\":\"VECTOR\"}]",
+ "mapStateJSON": "{\"zoom\":4.1,\"center\":{\"lon\":-100.61091,\"lat\":33.23887},\"timeFilters\":{\"from\":\"2015-09-20T00:00:00.000Z\",\"to\":\"2015-09-20T01:00:00.000Z\"},\"refreshConfig\":{\"isPaused\":true,\"interval\":1000},\"query\":{\"language\":\"kuery\",\"query\":\"\"},\"filters\":[],\"settings\":{\"autoFitToDataBounds\":false}}",
+ "title": "document example with data driven styles on date field",
+ "uiStateJSON": "{\"isLayerTOCOpen\":true,\"openTOCDetails\":[]}"
+ },
+ "coreMigrationVersion": "8.0.0",
+ "id": "c9277dd0-eb8f-11e9-ae47-693d6a50fb9e",
+ "migrationVersion": {
+ "map": "7.14.0"
+ },
+ "references": [
+ {
+ "id": "c698b940-e149-11e8-a35a-370a8516603a",
+ "name": "layer_1_source_index_pattern",
+ "type": "index-pattern"
+ }
+ ],
+ "type": "map",
+ "updated_at": "2019-10-10T18:57:19.916Z",
+ "version": "WzQ5LDJd"
+}
+
+{
+ "attributes": {
+ "description": "",
+ "layerListJSON": "[{\"id\":\"z52lq\",\"label\":\"logstash\",\"minZoom\":0,\"maxZoom\":24,\"sourceDescriptor\":{\"id\":\"e1a5e1a6-676c-4a89-8ea9-0d91d64b73c6\",\"type\":\"ES_SEARCH\",\"geoField\":\"geo.coordinates\",\"limit\":2048,\"filterByMapBounds\":true,\"showTooltip\":true,\"tooltipProperties\":[],\"indexPatternRefName\":\"layer_1_source_index_pattern\",\"applyGlobalQuery\":true,\"scalingType\":\"LIMIT\"},\"visible\":true,\"temporary\":false,\"style\":{\"type\":\"VECTOR\",\"properties\":{\"fillColor\":{\"type\":\"DYNAMIC\",\"options\":{\"color\":\"Blues\",\"field\":{\"label\":\"hour_of_day\",\"name\":\"hour_of_day\",\"origin\":\"source\"},\"fieldMetaOptions\":{\"isEnabled\":false,\"sigma\":3}}},\"lineColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#FFFFFF\"}},\"lineWidth\":{\"type\":\"STATIC\",\"options\":{\"size\":1}},\"iconSize\":{\"type\":\"DYNAMIC\",\"options\":{\"minSize\":4,\"maxSize\":24,\"field\":{\"label\":\"bytes\",\"name\":\"bytes\",\"origin\":\"source\"},\"fieldMetaOptions\":{\"isEnabled\":false,\"sigma\":3}}},\"iconOrientation\":{\"type\":\"STATIC\",\"options\":{\"orientation\":0}},\"symbolizeAs\":{\"options\":{\"value\":\"circle\"}},\"icon\":{\"type\":\"STATIC\",\"options\":{\"value\":\"airfield\"}}}},\"type\":\"VECTOR\"}]",
+ "mapStateJSON": "{\"zoom\":4.1,\"center\":{\"lon\":-100.61091,\"lat\":33.23887},\"timeFilters\":{\"from\":\"2015-09-20T00:00:00.000Z\",\"to\":\"2015-09-20T01:00:00.000Z\"},\"refreshConfig\":{\"isPaused\":true,\"interval\":1000},\"query\":{\"language\":\"kuery\",\"query\":\"\"},\"filters\":[],\"settings\":{\"autoFitToDataBounds\":false}}",
+ "title": "document example with data driven styles",
+ "uiStateJSON": "{\"isLayerTOCOpen\":true,\"openTOCDetails\":[]}"
+ },
+ "coreMigrationVersion": "8.0.0",
+ "id": "c9734720-eb7f-11e9-8f42-fb14e91ef4b8",
+ "migrationVersion": {
+ "map": "7.14.0"
+ },
+ "references": [
+ {
+ "id": "c698b940-e149-11e8-a35a-370a8516603a",
+ "name": "layer_1_source_index_pattern",
+ "type": "index-pattern"
+ }
+ ],
+ "type": "map",
+ "updated_at": "2019-10-10T17:02:48.465Z",
+ "version": "WzQ4LDJd"
+}
From 39b6597219d4a3cc8527c16cc731135933fbef62 Mon Sep 17 00:00:00 2001
From: Wylie Conlon
Date: Wed, 30 Jun 2021 09:47:03 -0400
Subject: [PATCH 055/121] [Lens] Formula can be colored by value (#103572)
* [Lens] Formula can be colored by value
* Fix mapColumn test
* Fix tests
---
.../expression_functions/specs/map_column.ts | 13 +++-
.../expression_functions/specs/math_column.ts | 13 +++-
.../specs/tests/map_column.test.ts | 34 +++++++--
.../specs/tests/math_column.test.ts | 20 +++++-
.../expression_functions/specs/tests/utils.ts | 70 ++++++++++++++++++-
x-pack/test/functional/apps/lens/formula.ts | 12 +++-
6 files changed, 149 insertions(+), 13 deletions(-)
diff --git a/src/plugins/expressions/common/expression_functions/specs/map_column.ts b/src/plugins/expressions/common/expression_functions/specs/map_column.ts
index 7ea96ee7fdde82..d897e24aaad83c 100644
--- a/src/plugins/expressions/common/expression_functions/specs/map_column.ts
+++ b/src/plugins/expressions/common/expression_functions/specs/map_column.ts
@@ -10,7 +10,7 @@ import { Observable, defer, of, zip } from 'rxjs';
import { map } from 'rxjs/operators';
import { i18n } from '@kbn/i18n';
import { ExpressionFunctionDefinition } from '../types';
-import { Datatable, DatatableColumn, getType } from '../../expression_types';
+import { Datatable, DatatableColumn, DatatableColumnType, getType } from '../../expression_types';
export interface MapColumnArguments {
id?: string | null;
@@ -103,7 +103,16 @@ export const mapColumn: ExpressionFunctionDefinition<
return rows$.pipe(
map((rows) => {
- const type = getType(rows[0]?.[id]);
+ let type: DatatableColumnType = 'null';
+ if (rows.length) {
+ for (const row of rows) {
+ const rowType = getType(row[id]);
+ if (rowType !== 'null') {
+ type = rowType;
+ break;
+ }
+ }
+ }
const newColumn: DatatableColumn = {
id,
name: args.name,
diff --git a/src/plugins/expressions/common/expression_functions/specs/math_column.ts b/src/plugins/expressions/common/expression_functions/specs/math_column.ts
index 633d912c29502f..c59016cd260aba 100644
--- a/src/plugins/expressions/common/expression_functions/specs/math_column.ts
+++ b/src/plugins/expressions/common/expression_functions/specs/math_column.ts
@@ -9,7 +9,7 @@
import { i18n } from '@kbn/i18n';
import { ExpressionFunctionDefinition } from '../types';
import { math, MathArguments } from './math';
-import { Datatable, DatatableColumn, getType } from '../../expression_types';
+import { Datatable, DatatableColumn, DatatableColumnType, getType } from '../../expression_types';
export type MathColumnArguments = MathArguments & {
id: string;
@@ -104,7 +104,16 @@ export const mathColumn: ExpressionFunctionDefinition<
return { ...row, [args.id]: result };
});
- const type = newRows.length ? getType(newRows[0][args.id]) : 'null';
+ let type: DatatableColumnType = 'null';
+ if (newRows.length) {
+ for (const row of newRows) {
+ const rowType = getType(row[args.id]);
+ if (rowType !== 'null') {
+ type = rowType;
+ break;
+ }
+ }
+ }
const newColumn: DatatableColumn = {
id: args.id,
name: args.name ?? args.id,
diff --git a/src/plugins/expressions/common/expression_functions/specs/tests/map_column.test.ts b/src/plugins/expressions/common/expression_functions/specs/tests/map_column.test.ts
index bd934745fed723..64a42958ae8a26 100644
--- a/src/plugins/expressions/common/expression_functions/specs/tests/map_column.test.ts
+++ b/src/plugins/expressions/common/expression_functions/specs/tests/map_column.test.ts
@@ -10,9 +10,10 @@ import { of, Observable } from 'rxjs';
import { TestScheduler } from 'rxjs/testing';
import { Datatable } from '../../../expression_types';
import { mapColumn, MapColumnArguments } from '../map_column';
-import { emptyTable, functionWrapper, testTable } from './utils';
+import { emptyTable, functionWrapper, testTable, tableWithNulls } from './utils';
-const pricePlusTwo = (datatable: Datatable) => of(datatable.rows[0].price + 2);
+const pricePlusTwo = (datatable: Datatable) =>
+ of(typeof datatable.rows[0].price === 'number' ? datatable.rows[0].price + 2 : null);
describe('mapColumn', () => {
const fn = functionWrapper(mapColumn);
@@ -219,11 +220,11 @@ describe('mapColumn', () => {
});
});
- it('should correctly infer the type fromt he first row if the references column for meta information does not exists', () => {
+ it('should correctly infer the type from the first row if the references column for meta information does not exists', () => {
testScheduler.run(({ expectObservable }) => {
expectObservable(
runFn(
- { ...emptyTable, rows: [...emptyTable.rows, { value: 5 }] },
+ { ...emptyTable, rows: [...emptyTable.rows, { price: 5 }] },
{ name: 'value', copyMetaFrom: 'time', expression: pricePlusTwo }
)
).toBe('(0|)', [
@@ -236,6 +237,31 @@ describe('mapColumn', () => {
meta: expect.objectContaining({ type: 'number' }),
}),
],
+ rows: [{ price: 5, value: 7 }],
+ }),
+ ]);
+ });
+ });
+
+ it('should correctly infer the type from the first non-null row', () => {
+ testScheduler.run(({ expectObservable }) => {
+ expectObservable(
+ runFn(tableWithNulls, {
+ id: 'value',
+ name: 'value',
+ expression: pricePlusTwo,
+ })
+ ).toBe('(0|)', [
+ expect.objectContaining({
+ type: 'datatable',
+ columns: [
+ ...tableWithNulls.columns,
+ expect.objectContaining({
+ id: 'value',
+ name: 'value',
+ meta: expect.objectContaining({ type: 'number' }),
+ }),
+ ],
}),
]);
});
diff --git a/src/plugins/expressions/common/expression_functions/specs/tests/math_column.test.ts b/src/plugins/expressions/common/expression_functions/specs/tests/math_column.test.ts
index e0fb0a3a9f23d5..3464736fe0ad35 100644
--- a/src/plugins/expressions/common/expression_functions/specs/tests/math_column.test.ts
+++ b/src/plugins/expressions/common/expression_functions/specs/tests/math_column.test.ts
@@ -7,7 +7,7 @@
*/
import { mathColumn } from '../math_column';
-import { functionWrapper, testTable } from './utils';
+import { functionWrapper, testTable, tableWithNulls } from './utils';
describe('mathColumn', () => {
const fn = functionWrapper(mathColumn);
@@ -95,4 +95,22 @@ describe('mathColumn', () => {
meta: { type: 'date', params: { id: 'number', params: { digits: 2 } } },
});
});
+
+ it('should correctly infer the type from the first non-null row', () => {
+ expect(
+ fn(tableWithNulls, { id: 'value', name: 'value', expression: 'price + 2', onError: 'null' })
+ ).toEqual(
+ expect.objectContaining({
+ type: 'datatable',
+ columns: [
+ ...tableWithNulls.columns,
+ expect.objectContaining({
+ id: 'value',
+ name: 'value',
+ meta: expect.objectContaining({ type: 'number' }),
+ }),
+ ],
+ })
+ );
+ });
});
diff --git a/src/plugins/expressions/common/expression_functions/specs/tests/utils.ts b/src/plugins/expressions/common/expression_functions/specs/tests/utils.ts
index 60d22d2b8575cb..ca41b427a28f7a 100644
--- a/src/plugins/expressions/common/expression_functions/specs/tests/utils.ts
+++ b/src/plugins/expressions/common/expression_functions/specs/tests/utils.ts
@@ -224,4 +224,72 @@ const stringTable: Datatable = {
],
};
-export { emptyTable, testTable, stringTable };
+const tableWithNulls: Datatable = {
+ type: 'datatable',
+ columns: [
+ {
+ id: 'name',
+ name: 'name label',
+ meta: { type: 'string' },
+ },
+ {
+ id: 'time',
+ name: 'time label',
+ meta: { type: 'date' },
+ },
+ {
+ id: 'price',
+ name: 'price label',
+ meta: { type: 'number' },
+ },
+ ],
+ rows: [
+ {
+ name: 'product1',
+ time: 1517842800950, // 05 Feb 2018 15:00:00 GMT
+ price: null,
+ },
+ {
+ name: 'product1',
+ time: 1517929200950, // 06 Feb 2018 15:00:00 GMT
+ price: null,
+ },
+ {
+ name: 'product1',
+ time: 1518015600950, // 07 Feb 2018 15:00:00 GMT
+ price: 420,
+ },
+ {
+ name: 'product2',
+ time: 1517842800950, // 05 Feb 2018 15:00:00 GMT
+ price: 216,
+ },
+ {
+ name: 'product2',
+ time: 1517929200950, // 06 Feb 2018 15:00:00 GMT
+ price: 200,
+ },
+ {
+ name: 'product2',
+ time: 1518015600950, // 07 Feb 2018 15:00:00 GMT
+ price: 190,
+ },
+ {
+ name: 'product3',
+ time: 1517842800950, // 05 Feb 2018 15:00:00 GMT
+ price: null,
+ },
+ {
+ name: 'product4',
+ time: 1517842800950, // 05 Feb 2018 15:00:00 GMT
+ price: 311,
+ },
+ {
+ name: 'product5',
+ time: 1517842800950, // 05 Feb 2018 15:00:00 GMT
+ price: 288,
+ },
+ ],
+};
+
+export { emptyTable, testTable, stringTable, tableWithNulls };
diff --git a/x-pack/test/functional/apps/lens/formula.ts b/x-pack/test/functional/apps/lens/formula.ts
index 38d1f63e946d41..17061f6e37170e 100644
--- a/x-pack/test/functional/apps/lens/formula.ts
+++ b/x-pack/test/functional/apps/lens/formula.ts
@@ -180,7 +180,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
expect(await PageObjects.lens.getErrorCount()).to.eql(0);
});
- it('should duplicate a moving average formula and be a valid table', async () => {
+ it('should duplicate a moving average formula and be a valid table with conditional coloring', async () => {
await PageObjects.visualize.navigateToNewVisualization();
await PageObjects.visualize.clickVisType('lens');
await PageObjects.lens.goToTimeRange();
@@ -198,14 +198,20 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
formula: `moving_average(sum(bytes), window=5`,
keepOpen: true,
});
+ await PageObjects.lens.setTableDynamicColoring('text');
+ await PageObjects.header.waitUntilLoadingHasFinished();
+ const styleObj = await PageObjects.lens.getDatatableCellStyle(1, 1);
+ expect(styleObj['background-color']).to.be(undefined);
+ expect(styleObj.color).not.to.be(undefined);
+
await PageObjects.lens.closeDimensionEditor();
await PageObjects.lens.dragDimensionToDimension(
'lnsDatatable_metrics > lns-dimensionTrigger',
'lnsDatatable_metrics > lns-empty-dimension'
);
- expect(await PageObjects.lens.getDatatableCellText(1, 1)).to.eql('222420');
- expect(await PageObjects.lens.getDatatableCellText(1, 2)).to.eql('222420');
+ expect(await PageObjects.lens.getDatatableCellText(1, 1)).to.eql('222,420');
+ expect(await PageObjects.lens.getDatatableCellText(1, 2)).to.eql('222,420');
});
it('should keep the formula if the user does not fully transition to a quick function', async () => {
From 4b54ccecd356c513a2a1ccfe6de2e9e654aa6700 Mon Sep 17 00:00:00 2001
From: Dario Gieselaar
Date: Wed, 30 Jun 2021 15:49:50 +0200
Subject: [PATCH 056/121] [APM] Return operationName as part of _inspect
response (#103616)
Closes #103448.
---
.../lib/helpers/create_es_client/call_async_with_debug.ts | 3 +++
.../helpers/create_es_client/create_apm_event_client/index.ts | 1 +
.../create_es_client/create_internal_es_client/index.ts | 1 +
x-pack/plugins/apm/server/routes/typings.ts | 1 +
x-pack/test/apm_api_integration/tests/inspect/inspect.ts | 1 +
5 files changed, 7 insertions(+)
diff --git a/x-pack/plugins/apm/server/lib/helpers/create_es_client/call_async_with_debug.ts b/x-pack/plugins/apm/server/lib/helpers/create_es_client/call_async_with_debug.ts
index 39018e26f371ce..644416e41b1a6d 100644
--- a/x-pack/plugins/apm/server/lib/helpers/create_es_client/call_async_with_debug.ts
+++ b/x-pack/plugins/apm/server/lib/helpers/create_es_client/call_async_with_debug.ts
@@ -23,6 +23,7 @@ export async function callAsyncWithDebug({
request,
requestType,
requestParams,
+ operationName,
isCalledWithInternalUser,
}: {
cb: () => Promise;
@@ -31,6 +32,7 @@ export async function callAsyncWithDebug({
request: KibanaRequest;
requestType: string;
requestParams: Record;
+ operationName: string;
isCalledWithInternalUser: boolean; // only allow inspection of queries that were retrieved with credentials of the end user
}) {
if (!debug) {
@@ -65,6 +67,7 @@ export async function callAsyncWithDebug({
const inspectableEsQueries = inspectableEsQueriesMap.get(request);
if (!isCalledWithInternalUser && inspectableEsQueries) {
inspectableEsQueries.push({
+ operationName,
response: res,
duration,
requestType,
diff --git a/x-pack/plugins/apm/server/lib/helpers/create_es_client/create_apm_event_client/index.ts b/x-pack/plugins/apm/server/lib/helpers/create_es_client/create_apm_event_client/index.ts
index 0a464982b6e813..7b5425321d239a 100644
--- a/x-pack/plugins/apm/server/lib/helpers/create_es_client/create_apm_event_client/index.ts
+++ b/x-pack/plugins/apm/server/lib/helpers/create_es_client/create_apm_event_client/index.ts
@@ -120,6 +120,7 @@ export function createApmEventClient({
debug,
request,
requestType,
+ operationName,
requestParams: searchParams,
});
},
diff --git a/x-pack/plugins/apm/server/lib/helpers/create_es_client/create_internal_es_client/index.ts b/x-pack/plugins/apm/server/lib/helpers/create_es_client/create_internal_es_client/index.ts
index eb3deb28893607..34801cdf947506 100644
--- a/x-pack/plugins/apm/server/lib/helpers/create_es_client/create_internal_es_client/index.ts
+++ b/x-pack/plugins/apm/server/lib/helpers/create_es_client/create_internal_es_client/index.ts
@@ -54,6 +54,7 @@ export function createInternalESClient({
request,
requestType,
requestParams: params,
+ operationName,
});
}
diff --git a/x-pack/plugins/apm/server/routes/typings.ts b/x-pack/plugins/apm/server/routes/typings.ts
index 474464dec1f99b..c9f425473ada6c 100644
--- a/x-pack/plugins/apm/server/routes/typings.ts
+++ b/x-pack/plugins/apm/server/routes/typings.ts
@@ -29,6 +29,7 @@ export type InspectResponse = Array<{
requestType: string;
requestParams: Record;
esError: Error;
+ operationName: string;
}>;
export interface APMRouteCreateOptions {
diff --git a/x-pack/test/apm_api_integration/tests/inspect/inspect.ts b/x-pack/test/apm_api_integration/tests/inspect/inspect.ts
index 4f65808de820ec..22623cb2662039 100644
--- a/x-pack/test/apm_api_integration/tests/inspect/inspect.ts
+++ b/x-pack/test/apm_api_integration/tests/inspect/inspect.ts
@@ -53,6 +53,7 @@ export default function customLinksTests({ getService }: FtrProviderContext) {
// @ts-expect-error
expect(Object.keys(body._inspect[0])).to.eql([
+ 'operationName',
'response',
'duration',
'requestType',
From 23621e663c67ed0b5bae2a8c6a5ecf0db47abb34 Mon Sep 17 00:00:00 2001
From: Ahmad Bamieh
Date: Wed, 30 Jun 2021 16:51:47 +0300
Subject: [PATCH 057/121] [Deprecation service] Using i18n for deprecation
messages (#103422)
---
src/core/server/deprecations/README.mdx | 210 ++++++++++++++++++++++++
1 file changed, 210 insertions(+)
create mode 100644 src/core/server/deprecations/README.mdx
diff --git a/src/core/server/deprecations/README.mdx b/src/core/server/deprecations/README.mdx
new file mode 100644
index 00000000000000..9a2ad689974e65
--- /dev/null
+++ b/src/core/server/deprecations/README.mdx
@@ -0,0 +1,210 @@
+---
+id: kibCoreDeprecationsService
+slug: /kibana-dev-docs/services/deprecations-service
+title: Core Deprecations service
+summary: The Deprecations service helps surface deprecated configs and features for plugins to our users
+date: 2021-06-27
+tags: ['kibana','dev', 'contributor', 'api docs']
+---
+
+# Core Deprecations service
+This guide is written in the format of a FAQ to help you get started using the deprecations service.
+For more details on the service contract we have documented all the service apis and properties under the
+deprecation service docs generated by in core.
+
+## What is the deprecations service?
+The deprecations service provides a way for the Kibana platform to communicate deprecated features
+and configs with its users. These deprecations are only communicated to the user if the deployment is using
+these features, allowing for a user tailored experience for upgrading the stack version.
+
+The Upgrade Assistant (UA) is the main consumer of these deprecations. The UA displays them to users and signals to
+Cloud that the Stack is ready for a major upgrade.
+
+
+## Where are deprecations displayed to users?
+The Upgrade Assistant (UA) in Kibana is consuming the core deprecations service in order to surface plugin
+deprecations. Each deprecation is required to provide manual steps that we display in the UI. Additionally
+each deprecation can provide an optional API to automatically resolve the deprecation.
+
+To check your plugin deprecations, go to the UA interface via **Stack Management > Stack > Upgrade Assistant**
+and click on `View deprecations` under the Kibana section.
+
+To display deprecations in the UA set `xpack.upgrade_assistant.readonly: false` in the kibana configurations.
+The UA is in read-only mode and will be enabled up until the last minor before the next major release.
+
+## How do I use this service for deprecated plugin configurations?
+The deprecations service automatically hooks deprecated configs with the deprecations service.
+
+All the config deprecation functions (`unused`, `unusedFromRoot`, `rename`, `renameFromRoot`) accept an
+optional parameter to customize the deprecation details.
+
+To read more about the deprecation functions check `/kibana-dev-docs/corePluginApi` in the new doc
+system under `core.ConfigDeprecationFactory`. You can also check the `ConfigDeprecationFactory`
+[interface docs](../../../../../packages/kbn-config/src/deprecation/types.ts).
+
+### Example
+```ts
+export const config: PluginConfigDescriptor = {
+ schema: configSchema,
+ deprecations: ({ renameFromRoot }) => [
+ renameFromRoot('ui_metric.debug', 'usageCollection.uiCounters.debug', {
+ documentationUrl: 'elastic.co/some-url',
+ }),
+ ],
+};
+```
+
+The service will show the following deprecation details when the users set the above deprecated kibana
+config `ui_metric.debug`.
+
+```ts
+{
+ deprecationsInfo:[{
+ level: 'critical',
+ message: `"ui_metric.debug" is deprecated and has been replaced by "usageCollection.uiCounters.debug"`,
+ documentationUrl: 'elastic.co/some-url',
+ correctiveActions:{
+ manualSteps: [
+ `Replace "ui_metric.debug" with "usageCollection.uiCounters.debug" in the Kibana config file, CLI flag, or environment variable (in Docker only).`,
+ ]
+ },
+ domainId: 'usageCollection',
+ }],
+}
+```
+
+#### Custom deprecations for plugin configs
+Custom config deprecation handling allows specifying the deprecation details via the `addDeprecation`.
+
+##### Example
+
+```ts
+export const config: PluginConfigDescriptor = {
+ exposeToBrowser: {
+ defaultAppId: true,
+ },
+ schema: configSchema,
+ deprecations: () => [
+ (
+ completeConfig: Record,
+ rootPath: string,
+ addDeprecation: AddConfigDeprecation
+ ) => {
+ if (
+ get(completeConfig, 'kibana.defaultAppId') === undefined &&
+ get(completeConfig, 'kibana_legacy.defaultAppId') === undefined
+ ) {
+ return completeConfig;
+ }
+ addDeprecation({
+ message: `kibana.defaultAppId is deprecated and will be removed in 8.0. Please use the "defaultRoute" advanced setting instead`,
+ correctiveActions: {
+ manualSteps: [
+ 'Go to Stack Management > Advanced Settings',
+ 'Update the "defaultRoute" setting under the General section',
+ 'Remove "kibana.defaultAppId" from the kibana.yml config file',
+ ],
+ },
+ });
+ return completeConfig;
+ },
+ ],
+};
+```
+
+## How do I add deprecations for non-config plugin deprecations?
+Plugins are responsible for registering any deprecations during the `setup` lifecycle by using
+the deprecations service.
+
+Examples of non-config deprecations include things like
+- timelion sheets
+- kibana_user security roles
+
+This service is not intended to be used for non-user facing deprecations or cases where the deprecation
+cannot be 'detected' by this mechanism.
+
+### Usage
+```ts
+coreSetup.deprecations.registerDeprecations({
+ getDeprecations: ({ esClient, savedObjectsClient }) => [{ ...`` }],
+});
+```
+
+The `getDeprecations` function is invoked when the user requests to see the deprecations affecting their deployment.
+The function provides a context object which contains a scoped Elasticsearch client and a saved objects client.
+
+To check the full TS types of the service please check the [generated core docs](../../../../docs/development/core/server/kibana-plugin-core-server.deprecationsservicesetup.md).
+
+### Example
+```ts
+import { DeprecationsDetails, GetDeprecationsContext } from 'src/core/server';
+
+async function getDeprecations({ esClient, savedObjectsClient }: GetDeprecationsContext): Promise {
+ const deprecations: DeprecationsDetails[] = [];
+ const testDashboardUser = await getTestDashboardUser(savedObjectsClient);
+
+ if (testDashboardUser) {
+ deprecations.push({
+ message: 'User "test_dashboard_user" is using a deprecated role: "kibana_user"',
+ documentationUrl: 'https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-put-user.html',
+ level: 'critical',
+ correctiveActions: {
+ api: {
+ path: '/internal/security/users/test_dashboard_user',
+ method: 'POST',
+ body: {
+ username: 'test_dashboard_user',
+ roles: [
+ 'machine_learning_user',
+ 'enrich_user',
+ 'kibana_admin'
+ ],
+ full_name: 'Alison Goryachev',
+ email: 'alisongoryachev@gmail.com',
+ metadata: {},
+ enabled: true
+ }
+ },
+ manualSteps: [
+ 'Using Kibana user management, change all users using the kibana_user role to the kibana_admin role.',
+ 'Using Kibana role-mapping management, change all role-mappings which assing the kibana_user role to the kibana_admin role.'
+ ]
+ },
+ });
+ }
+
+ return deprecations;
+}
+ ```
+
+## How do I implement the API corrective action?
+The deprecations API allows plugins to provide an API call that can be used to automatically fix specific deprecations.
+To do so create a `PUT` or `POST` route in your plugin and specify data you want to be passed in the payload for the deprecation.
+
+In the example above, `/internal/security/users/test_dashboard_user` will be called when users click on `Quick Resolve` in the UA. The service will automatically pass the body provided in the api corrective action to provide context to the route for fixing the deprecation.
+
+The deprecations service expects a `200` status code to recognize the corrective action as a success.
+
+## How do I test my deprecations?
+We recommend testing the route for your api corrective actions via unit and plugin functional tests.
+To check the deprecation in the UI please check the UA page.
+
+To test that your logic registering the deprecations with the deprecations service during `setup` is valid we recommend adding
+unit tests to check that the `getDeprecations` is returning the expected array of deprecations at different scenarios.
+
+Core provides mocks for the service which should help you focus on testing only your specific requirements.
+
+You can also use the deprecations service API; The deprecations service exposes an api `GET /api/deprecations/` which provides
+a list of deprecations and possible corrective actions required to resolve these entries. The context is scoped to the requesting
+user, hence a user with limited access might not be able to see all the deprecations affecting the deployment.
+
+Currently we do not have test objects to run functional tests against the Upgrade Assistant directly.
+
+## Should I use the service for all the deprecations before 8.0?
+Yes. Using this service should help users find and resolve any issues specific to their deployment before upgrading.
+We recommend adding a `documentationUrl` for every deprecation you expose to further assist our users if they need extra help.
+
+## Note on i18n
+We have decided to support i18n to the exposed deprecations for a better user experience when using the UA.
+We will inject `i18n` into the deprecation function to enable teams to use it before fully documenting its usage.
+For context follow [this issue](https://github.com/elastic/kibana/issues/99072).
From 5bb9691b556d4ed8a632ed0cf3826cfdd0eceabd Mon Sep 17 00:00:00 2001
From: Pierre Gayvallet
Date: Wed, 30 Jun 2021 15:59:00 +0200
Subject: [PATCH 058/121] Use new ES client for licensing plugin (#92143)
* use new client for licensing API
* add logs
* adapt unit tests
* Revert "add logs"
This reverts commit 4a61b646
* fix some type errors
* fix test types
* adapt monitoring usage of `createLicensePoller`
* remove test comment
* fix unit test
* remove createLicensePoller from setup contract
* fix unit tests
---
x-pack/plugins/licensing/server/mocks.ts | 5 -
.../plugins/licensing/server/plugin.test.ts | 128 ++++++++++--------
x-pack/plugins/licensing/server/plugin.ts | 85 ++++++------
x-pack/plugins/licensing/server/types.ts | 41 ++----
.../monitoring/server/license_service.ts | 14 +-
.../plugins/monitoring/server/plugin.test.ts | 3 +
6 files changed, 123 insertions(+), 153 deletions(-)
diff --git a/x-pack/plugins/licensing/server/mocks.ts b/x-pack/plugins/licensing/server/mocks.ts
index 186d35ec47dd34..724a5aa2d057e1 100644
--- a/x-pack/plugins/licensing/server/mocks.ts
+++ b/x-pack/plugins/licensing/server/mocks.ts
@@ -19,14 +19,9 @@ const createSetupMock = (): jest.Mocked => {
const mock = {
license$: new BehaviorSubject(license),
refresh: jest.fn(),
- createLicensePoller: jest.fn(),
featureUsage: featureUsageMock.createSetup(),
};
mock.refresh.mockResolvedValue(license);
- mock.createLicensePoller.mockReturnValue({
- license$: mock.license$,
- refresh: mock.refresh,
- });
return mock;
};
diff --git a/x-pack/plugins/licensing/server/plugin.test.ts b/x-pack/plugins/licensing/server/plugin.test.ts
index 446cd2baa1cc0a..1fe4bbf238e197 100644
--- a/x-pack/plugins/licensing/server/plugin.test.ts
+++ b/x-pack/plugins/licensing/server/plugin.test.ts
@@ -6,31 +6,34 @@
*/
import { take, toArray } from 'rxjs/operators';
+import { estypes } from '@elastic/elasticsearch';
import moment from 'moment';
import { LicenseType } from '../common/types';
-import { ElasticsearchError, RawLicense } from './types';
+import { ElasticsearchError } from './types';
import { LicensingPlugin } from './plugin';
import {
coreMock,
elasticsearchServiceMock,
loggingSystemMock,
} from '../../../../src/core/server/mocks';
-import { ILegacyClusterClient } from '../../../../src/core/server/';
+import { IClusterClient } from '../../../../src/core/server';
-function buildRawLicense(options: Partial = {}): RawLicense {
- const defaultRawLicense: RawLicense = {
+function buildRawLicense(
+ options: Partial = {}
+): estypes.XpackInfoMinimalLicenseInformation {
+ return {
uid: 'uid-000000001234',
status: 'active',
type: 'basic',
mode: 'basic',
expiry_date_in_millis: 1000,
+ ...options,
};
- return Object.assign(defaultRawLicense, options);
}
const flushPromises = (ms = 50) => new Promise((res) => setTimeout(res, ms));
-function createCoreSetupWith(esClient: ILegacyClusterClient) {
+function createCoreSetupWith(esClient: IClusterClient) {
const coreSetup = coreMock.createSetup();
const coreStart = coreMock.createStart();
coreSetup.getStartServices.mockResolvedValue([
@@ -38,11 +41,7 @@ function createCoreSetupWith(esClient: ILegacyClusterClient) {
...coreStart,
elasticsearch: {
...coreStart.elasticsearch,
- legacy: {
- ...coreStart.elasticsearch.legacy,
- client: esClient,
- createClient: jest.fn(),
- },
+ client: esClient,
},
},
{},
@@ -52,6 +51,16 @@ function createCoreSetupWith(esClient: ILegacyClusterClient) {
}
describe('licensing plugin', () => {
+ const createEsClient = (response?: Record) => {
+ const client = elasticsearchServiceMock.createClusterClient();
+ if (response) {
+ client.asInternalUser.xpack.info.mockReturnValue(
+ elasticsearchServiceMock.createSuccessTransportRequestPromise(response as any)
+ );
+ }
+ return client;
+ };
+
describe('#start', () => {
describe('#license$', () => {
let plugin: LicensingPlugin;
@@ -69,8 +78,7 @@ describe('licensing plugin', () => {
});
it('returns license', async () => {
- const esClient = elasticsearchServiceMock.createLegacyClusterClient();
- esClient.callAsInternalUser.mockResolvedValue({
+ const esClient = createEsClient({
license: buildRawLicense(),
features: {},
});
@@ -83,8 +91,7 @@ describe('licensing plugin', () => {
});
it('calls `callAsInternalUser` with the correct parameters', async () => {
- const esClient = elasticsearchServiceMock.createLegacyClusterClient();
- esClient.callAsInternalUser.mockResolvedValue({
+ const esClient = createEsClient({
license: buildRawLicense(),
features: {},
});
@@ -94,23 +101,22 @@ describe('licensing plugin', () => {
const { license$ } = await plugin.start();
await license$.pipe(take(1)).toPromise();
- expect(esClient.callAsInternalUser).toHaveBeenCalledTimes(1);
- expect(esClient.callAsInternalUser).toHaveBeenCalledWith('transport.request', {
- method: 'GET',
- path: '/_xpack?accept_enterprise=true',
+ expect(esClient.asInternalUser.xpack.info).toHaveBeenCalledTimes(1);
+ expect(esClient.asInternalUser.xpack.info).toHaveBeenCalledWith({
+ accept_enterprise: true,
});
});
it('observable receives updated licenses', async () => {
const types: LicenseType[] = ['basic', 'gold', 'platinum'];
- const esClient = elasticsearchServiceMock.createLegacyClusterClient();
- esClient.callAsInternalUser.mockImplementation(() =>
- Promise.resolve({
+ const esClient = createEsClient();
+ esClient.asInternalUser.xpack.info.mockImplementation(() => {
+ return elasticsearchServiceMock.createSuccessTransportRequestPromise({
license: buildRawLicense({ type: types.shift() }),
features: {},
- })
- );
+ } as estypes.XpackInfoResponse);
+ });
const coreSetup = createCoreSetupWith(esClient);
await plugin.setup(coreSetup);
@@ -123,8 +129,8 @@ describe('licensing plugin', () => {
});
it('returns a license with error when request fails', async () => {
- const esClient = elasticsearchServiceMock.createLegacyClusterClient();
- esClient.callAsInternalUser.mockRejectedValue(new Error('test'));
+ const esClient = createEsClient();
+ esClient.asInternalUser.xpack.info.mockRejectedValue(new Error('test'));
const coreSetup = createCoreSetupWith(esClient);
await plugin.setup(coreSetup);
@@ -136,10 +142,10 @@ describe('licensing plugin', () => {
});
it('generate error message when x-pack plugin was not installed', async () => {
- const esClient = elasticsearchServiceMock.createLegacyClusterClient();
+ const esClient = createEsClient();
const error: ElasticsearchError = new Error('reason');
error.status = 400;
- esClient.callAsInternalUser.mockRejectedValue(error);
+ esClient.asInternalUser.xpack.info.mockRejectedValue(error);
const coreSetup = createCoreSetupWith(esClient);
await plugin.setup(coreSetup);
@@ -154,26 +160,35 @@ describe('licensing plugin', () => {
const error1 = new Error('reason-1');
const error2 = new Error('reason-2');
- const esClient = elasticsearchServiceMock.createLegacyClusterClient();
-
- esClient.callAsInternalUser
- .mockRejectedValueOnce(error1)
- .mockRejectedValueOnce(error2)
- .mockResolvedValue({ license: buildRawLicense(), features: {} });
+ const esClient = createEsClient();
+ let i = 0;
+ esClient.asInternalUser.xpack.info.mockImplementation(() => {
+ i++;
+ if (i === 1) {
+ return elasticsearchServiceMock.createErrorTransportRequestPromise(error1);
+ }
+ if (i === 2) {
+ return elasticsearchServiceMock.createErrorTransportRequestPromise(error2);
+ }
+ return elasticsearchServiceMock.createSuccessTransportRequestPromise({
+ license: buildRawLicense(),
+ features: {},
+ } as estypes.XpackInfoResponse);
+ });
const coreSetup = createCoreSetupWith(esClient);
await plugin.setup(coreSetup);
const { license$ } = await plugin.start();
const [first, second, third] = await license$.pipe(take(3), toArray()).toPromise();
+
expect(first.error).toBe(error1.message);
expect(second.error).toBe(error2.message);
expect(third.type).toBe('basic');
});
it('fetch license immediately without subscriptions', async () => {
- const esClient = elasticsearchServiceMock.createLegacyClusterClient();
- esClient.callAsInternalUser.mockResolvedValue({
+ const esClient = createEsClient({
license: buildRawLicense(),
features: {},
});
@@ -184,12 +199,11 @@ describe('licensing plugin', () => {
await flushPromises();
- expect(esClient.callAsInternalUser).toHaveBeenCalledTimes(1);
+ expect(esClient.asInternalUser.xpack.info).toHaveBeenCalledTimes(1);
});
it('logs license details without subscriptions', async () => {
- const esClient = elasticsearchServiceMock.createLegacyClusterClient();
- esClient.callAsInternalUser.mockResolvedValue({
+ const esClient = createEsClient({
license: buildRawLicense(),
features: {},
});
@@ -214,13 +228,13 @@ describe('licensing plugin', () => {
it('generates signature based on fetched license content', async () => {
const types: LicenseType[] = ['basic', 'gold', 'basic'];
- const esClient = elasticsearchServiceMock.createLegacyClusterClient();
- esClient.callAsInternalUser.mockImplementation(() =>
- Promise.resolve({
+ const esClient = createEsClient();
+ esClient.asInternalUser.xpack.info.mockImplementation(() => {
+ return elasticsearchServiceMock.createSuccessTransportRequestPromise({
license: buildRawLicense({ type: types.shift() }),
features: {},
- })
- );
+ } as estypes.XpackInfoResponse);
+ });
const coreSetup = createCoreSetupWith(esClient);
await plugin.setup(coreSetup);
@@ -245,8 +259,7 @@ describe('licensing plugin', () => {
api_polling_frequency: moment.duration(50000),
})
);
- const esClient = elasticsearchServiceMock.createLegacyClusterClient();
- esClient.callAsInternalUser.mockResolvedValue({
+ const esClient = createEsClient({
license: buildRawLicense(),
features: {},
});
@@ -255,14 +268,14 @@ describe('licensing plugin', () => {
await plugin.setup(coreSetup);
const { refresh, license$ } = await plugin.start();
- expect(esClient.callAsInternalUser).toHaveBeenCalledTimes(0);
+ expect(esClient.asInternalUser.xpack.info).toHaveBeenCalledTimes(0);
await license$.pipe(take(1)).toPromise();
- expect(esClient.callAsInternalUser).toHaveBeenCalledTimes(1);
+ expect(esClient.asInternalUser.xpack.info).toHaveBeenCalledTimes(1);
refresh();
await flushPromises();
- expect(esClient.callAsInternalUser).toHaveBeenCalledTimes(2);
+ expect(esClient.asInternalUser.xpack.info).toHaveBeenCalledTimes(2);
});
});
@@ -280,8 +293,7 @@ describe('licensing plugin', () => {
})
);
- const esClient = elasticsearchServiceMock.createLegacyClusterClient();
- esClient.callAsInternalUser.mockResolvedValue({
+ const esClient = createEsClient({
license: buildRawLicense(),
features: {},
});
@@ -289,8 +301,7 @@ describe('licensing plugin', () => {
await plugin.setup(coreSetup);
const { createLicensePoller, license$ } = await plugin.start();
- const customClient = elasticsearchServiceMock.createLegacyClusterClient();
- customClient.callAsInternalUser.mockResolvedValue({
+ const customClient = createEsClient({
license: buildRawLicense({ type: 'gold' }),
features: {},
});
@@ -300,10 +311,10 @@ describe('licensing plugin', () => {
customClient,
customPollingFrequency
);
- expect(customClient.callAsInternalUser).toHaveBeenCalledTimes(0);
+ expect(customClient.asInternalUser.xpack.info).toHaveBeenCalledTimes(0);
const customLicense = await customLicense$.pipe(take(1)).toPromise();
- expect(customClient.callAsInternalUser).toHaveBeenCalledTimes(1);
+ expect(customClient.asInternalUser.xpack.info).toHaveBeenCalledTimes(1);
await flushPromises(customPollingFrequency * 1.5);
@@ -324,18 +335,17 @@ describe('licensing plugin', () => {
await plugin.setup(coreSetup);
const { createLicensePoller } = await plugin.start();
- const customClient = elasticsearchServiceMock.createLegacyClusterClient();
- customClient.callAsInternalUser.mockResolvedValue({
+ const customClient = createEsClient({
license: buildRawLicense({ type: 'gold' }),
features: {},
});
const { license$, refresh } = createLicensePoller(customClient, 10000);
- expect(customClient.callAsInternalUser).toHaveBeenCalledTimes(0);
+ expect(customClient.asInternalUser.xpack.info).toHaveBeenCalledTimes(0);
await refresh();
- expect(customClient.callAsInternalUser).toHaveBeenCalledTimes(1);
+ expect(customClient.asInternalUser.xpack.info).toHaveBeenCalledTimes(1);
const license = await license$.pipe(take(1)).toPromise();
expect(license.type).toBe('gold');
});
diff --git a/x-pack/plugins/licensing/server/plugin.ts b/x-pack/plugins/licensing/server/plugin.ts
index 7c0f0a119d2590..00d2ae602fcae5 100644
--- a/x-pack/plugins/licensing/server/plugin.ts
+++ b/x-pack/plugins/licensing/server/plugin.ts
@@ -10,22 +10,29 @@ import moment from 'moment';
import { createHash } from 'crypto';
import stringify from 'json-stable-stringify';
+import { estypes } from '@elastic/elasticsearch';
+import { MaybePromise } from '@kbn/utility-types';
+import { isPromise } from '@kbn/std';
import {
CoreSetup,
Logger,
Plugin,
PluginInitializerContext,
- ILegacyClusterClient,
- ILegacyScopedClusterClient,
- ScopeableRequest,
+ IClusterClient,
} from 'src/core/server';
-import { ILicense, PublicLicense, PublicFeatures } from '../common/types';
+import {
+ ILicense,
+ PublicLicense,
+ PublicFeatures,
+ LicenseType,
+ LicenseStatus,
+} from '../common/types';
import { LicensingPluginSetup, LicensingPluginStart } from './types';
import { License } from '../common/license';
import { createLicenseUpdate } from '../common/license_update';
-import { ElasticsearchError, RawLicense, RawFeatures } from './types';
+import { ElasticsearchError } from './types';
import { registerRoutes } from './routes';
import { FeatureUsageService } from './services';
@@ -34,17 +41,22 @@ import { createRouteHandlerContext } from './licensing_route_handler_context';
import { createOnPreResponseHandler } from './on_pre_response_handler';
import { getPluginStatus$ } from './plugin_status';
-function normalizeServerLicense(license: RawLicense): PublicLicense {
+function normalizeServerLicense(
+ license: estypes.XpackInfoMinimalLicenseInformation
+): PublicLicense {
return {
uid: license.uid,
- type: license.type,
- mode: license.mode,
- expiryDateInMillis: license.expiry_date_in_millis,
- status: license.status,
+ type: license.type as LicenseType,
+ mode: license.mode as LicenseType,
+ expiryDateInMillis:
+ typeof license.expiry_date_in_millis === 'string'
+ ? parseInt(license.expiry_date_in_millis, 10)
+ : license.expiry_date_in_millis,
+ status: license.status as LicenseStatus,
};
}
-function normalizeFeatures(rawFeatures: RawFeatures) {
+function normalizeFeatures(rawFeatures: estypes.XpackInfoFeatures) {
const features: PublicFeatures = {};
for (const [name, feature] of Object.entries(rawFeatures)) {
features[name] = {
@@ -99,32 +111,12 @@ export class LicensingPlugin implements Plugin
- ): ReturnType {
- const [coreStart] = await core.getStartServices();
- const client = coreStart.elasticsearch.legacy.client;
- return await client.callAsInternalUser(...args);
- }
-
- const client: ILegacyClusterClient = {
- callAsInternalUser,
- asScoped(request?: ScopeableRequest): ILegacyScopedClusterClient {
- return {
- async callAsCurrentUser(
- ...args: Parameters
- ): ReturnType {
- const [coreStart] = await core.getStartServices();
- const _client = coreStart.elasticsearch.legacy.client;
- return await _client.asScoped(request).callAsCurrentUser(...args);
- },
- callAsInternalUser,
- };
- },
- };
+ const clientPromise = core.getStartServices().then(([{ elasticsearch }]) => {
+ return elasticsearch.client;
+ });
const { refresh, license$ } = this.createLicensePoller(
- client,
+ clientPromise,
pollingFrequency.asMilliseconds()
);
@@ -146,12 +138,14 @@ export class LicensingPlugin implements Plugin,
+ pollingFrequency: number
+ ) {
this.logger.debug(`Polling Elasticsearch License API with frequency ${pollingFrequency}ms.`);
const intervalRefresh$ = timer(0, pollingFrequency);
@@ -180,16 +174,17 @@ export class LicensingPlugin implements Plugin => {
+ private fetchLicense = async (clusterClient: MaybePromise): Promise => {
+ const client = isPromise(clusterClient) ? await clusterClient : clusterClient;
try {
- const response = await clusterClient.callAsInternalUser('transport.request', {
- method: 'GET',
- path: '/_xpack?accept_enterprise=true',
+ const { body: response } = await client.asInternalUser.xpack.info({
+ // @ts-expect-error `accept_enterprise` is not present in the client definition
+ accept_enterprise: true,
});
-
- const normalizedLicense = response.license
- ? normalizeServerLicense(response.license)
- : undefined;
+ const normalizedLicense =
+ response.license && response.license.type !== 'missing'
+ ? normalizeServerLicense(response.license)
+ : undefined;
const normalizedFeatures = response.features
? normalizeFeatures(response.features)
: undefined;
diff --git a/x-pack/plugins/licensing/server/types.ts b/x-pack/plugins/licensing/server/types.ts
index 8b2d9599e0ffe2..ff13ee882be605 100644
--- a/x-pack/plugins/licensing/server/types.ts
+++ b/x-pack/plugins/licensing/server/types.ts
@@ -6,13 +6,14 @@
*/
import { Observable } from 'rxjs';
-import type { ILegacyClusterClient, IRouter, RequestHandlerContext } from 'src/core/server';
-import { ILicense, LicenseStatus, LicenseType } from '../common/types';
+import type { IClusterClient, IRouter, RequestHandlerContext } from 'src/core/server';
+import { ILicense } from '../common/types';
import { FeatureUsageServiceSetup, FeatureUsageServiceStart } from './services';
export interface ElasticsearchError extends Error {
status?: number;
}
+
/**
* Result from remote request fetching raw feature set.
* @internal
@@ -22,26 +23,6 @@ export interface RawFeature {
enabled: boolean;
}
-/**
- * Results from remote request fetching raw feature sets.
- * @internal
- */
-export interface RawFeatures {
- [key: string]: RawFeature;
-}
-
-/**
- * Results from remote request fetching a raw license.
- * @internal
- */
-export interface RawLicense {
- uid: string;
- status: LicenseStatus;
- expiry_date_in_millis: number;
- type: LicenseType;
- mode: LicenseType;
-}
-
/**
* The APIs exposed on the `licensing` key of {@link RequestHandlerContext} for plugins that depend on licensing.
* @public
@@ -70,21 +51,13 @@ export interface LicensingPluginSetup {
* @deprecated in favour of the counterpart provided from start contract
*/
license$: Observable;
+
/**
* Triggers licensing information re-fetch.
* @deprecated in favour of the counterpart provided from start contract
*/
refresh(): Promise;
- /**
- * Creates a license poller to retrieve a license data with.
- * Allows a plugin to configure a cluster to retrieve data from at
- * given polling frequency.
- * @deprecated in favour of the counterpart provided from start contract
- */
- createLicensePoller: (
- clusterClient: ILegacyClusterClient,
- pollingFrequency: number
- ) => { license$: Observable; refresh(): Promise };
+
/**
* APIs to register licensed feature usage.
*/
@@ -97,17 +70,19 @@ export interface LicensingPluginStart {
* Steam of licensing information {@link ILicense}.
*/
license$: Observable;
+
/**
* Triggers licensing information re-fetch.
*/
refresh(): Promise;
+
/**
* Creates a license poller to retrieve a license data with.
* Allows a plugin to configure a cluster to retrieve data from at
* given polling frequency.
*/
createLicensePoller: (
- clusterClient: ILegacyClusterClient,
+ clusterClient: IClusterClient,
pollingFrequency: number
) => { license$: Observable; refresh(): Promise };
/**
diff --git a/x-pack/plugins/monitoring/server/license_service.ts b/x-pack/plugins/monitoring/server/license_service.ts
index ab10193fc93cbd..0ac1c9d3498b17 100644
--- a/x-pack/plugins/monitoring/server/license_service.ts
+++ b/x-pack/plugins/monitoring/server/license_service.ts
@@ -6,17 +6,16 @@
*/
import { Subscription } from 'rxjs';
-import { IClusterClient, ILegacyClusterClient } from 'kibana/server';
+import { ICustomClusterClient } from 'kibana/server';
import { ILicense, LicenseFeature } from '../../licensing/common/types';
import { LicensingPluginStart } from '../../licensing/server';
import { MonitoringConfig } from './config';
import { Logger } from '../../../../src/core/server';
import { MonitoringLicenseService } from './types';
-import { EndpointTypes, Globals, ClientParams } from './static_globals';
interface SetupDeps {
licensing: LicensingPluginStart;
- monitoringClient: IClusterClient;
+ monitoringClient: ICustomClusterClient;
config: MonitoringConfig;
log: Logger;
}
@@ -28,15 +27,8 @@ const defaultLicenseFeature: LicenseFeature = {
export class LicenseService {
public setup({ licensing, monitoringClient, config, log }: SetupDeps): MonitoringLicenseService {
- // TODO: This needs to be changed to an IClusterClient as when the Licensing server
- // is upgraded to the new client.
- const fakeLegacyClusterClient = {
- callAsInternalUser: (endpoint: EndpointTypes, options: ClientParams) =>
- Globals.app.getLegacyClusterShim(monitoringClient.asInternalUser, endpoint, options),
- } as ILegacyClusterClient;
-
const { refresh, license$ } = licensing.createLicensePoller(
- fakeLegacyClusterClient,
+ monitoringClient,
config.licensing.api_polling_frequency.asMilliseconds()
);
diff --git a/x-pack/plugins/monitoring/server/plugin.test.ts b/x-pack/plugins/monitoring/server/plugin.test.ts
index 1bdef23dd0c369..68453a849a15b2 100644
--- a/x-pack/plugins/monitoring/server/plugin.test.ts
+++ b/x-pack/plugins/monitoring/server/plugin.test.ts
@@ -13,6 +13,9 @@ jest.mock('./es_client/instantiate_client', () => ({
instantiateClient: jest.fn().mockImplementation(() => ({
cluster: {},
})),
+ instantiateLegacyClient: jest.fn().mockImplementation(() => ({
+ cluster: {},
+ })),
}));
jest.mock('./license_service', () => ({
From 2f457da6402825e9f126ed778ad7d84c4edc09b4 Mon Sep 17 00:00:00 2001
From: Nathan Reese
Date: Wed, 30 Jun 2021 08:30:44 -0600
Subject: [PATCH 059/121] [Maps] Create drawing layer copy updates (#103791)
* [Maps] Create drawing layer copy updates
* tslint
---
.../layers/new_vector_layer_wizard/config.tsx | 9 ++--
.../layers/new_vector_layer_wizard/wizard.tsx | 41 ++++---------------
2 files changed, 13 insertions(+), 37 deletions(-)
diff --git a/x-pack/plugins/maps/public/classes/layers/new_vector_layer_wizard/config.tsx b/x-pack/plugins/maps/public/classes/layers/new_vector_layer_wizard/config.tsx
index 5df11407b83bda..5a82cf881e34d4 100644
--- a/x-pack/plugins/maps/public/classes/layers/new_vector_layer_wizard/config.tsx
+++ b/x-pack/plugins/maps/public/classes/layers/new_vector_layer_wizard/config.tsx
@@ -18,12 +18,11 @@ const ADD_VECTOR_DRAWING_LAYER = 'ADD_VECTOR_DRAWING_LAYER';
export const newVectorLayerWizardConfig: LayerWizard = {
categories: [LAYER_WIZARD_CATEGORY.ELASTICSEARCH],
description: i18n.translate('xpack.maps.newVectorLayerWizard.description', {
- defaultMessage:
- 'Create an empty layer. Use this to create documents by drawing shapes on the map',
+ defaultMessage: 'Draw shapes on the map and index in Elasticsearch',
}),
disabledReason: i18n.translate('xpack.maps.newVectorLayerWizard.disabledDesc', {
defaultMessage:
- 'Unable to draw vector shapes, you are missing the Kibana privilege "Index Pattern Management".',
+ 'Unable to create index, you are missing the Kibana privilege "Index Pattern Management".',
}),
getIsDisabled: async () => {
const hasImportPermission = await getFileUpload().hasImportPermission({
@@ -38,7 +37,7 @@ export const newVectorLayerWizardConfig: LayerWizard = {
{
id: ADD_VECTOR_DRAWING_LAYER,
label: i18n.translate('xpack.maps.newVectorLayerWizard.indexNewLayer', {
- defaultMessage: 'Index new layer',
+ defaultMessage: 'Create index',
}),
},
],
@@ -47,6 +46,6 @@ export const newVectorLayerWizardConfig: LayerWizard = {
},
showFeatureEditTools: true,
title: i18n.translate('xpack.maps.newVectorLayerWizard.title', {
- defaultMessage: 'Create new layer',
+ defaultMessage: 'Create index',
}),
};
diff --git a/x-pack/plugins/maps/public/classes/layers/new_vector_layer_wizard/wizard.tsx b/x-pack/plugins/maps/public/classes/layers/new_vector_layer_wizard/wizard.tsx
index 0d39c1c720bf22..4f9e9c39ce466a 100644
--- a/x-pack/plugins/maps/public/classes/layers/new_vector_layer_wizard/wizard.tsx
+++ b/x-pack/plugins/maps/public/classes/layers/new_vector_layer_wizard/wizard.tsx
@@ -5,8 +5,8 @@
* 2.0.
*/
-import React, { Component, Fragment } from 'react';
-import { EuiEmptyPrompt, EuiPanel, EuiCallOut } from '@elastic/eui';
+import React, { Component } from 'react';
+import { EuiPanel, EuiCallOut } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { createNewIndexAndPattern } from './create_new_index_pattern';
import { RenderWizardArguments } from '../layer_wizard_registry';
@@ -125,36 +125,13 @@ export class NewVectorLayerEditor extends Component
- <>
-
- {i18n.translate('xpack.maps.layers.newVectorLayerWizard.createNewLayer', {
- defaultMessage: 'Create new layer',
- })}
-
- }
- body={
-
-
- {i18n.translate(
- 'xpack.maps.layers.newVectorLayerWizard.vectorEditorDescription',
- {
- defaultMessage: `Creates a new vector layer. This can be used to draw and store new shapes.`,
- }
- )}
-
-
- }
- />
- {}}
- onIndexNameValidationEnd={() => {}}
- />
- >
+ {}}
+ onIndexNameValidationEnd={() => {}}
+ />
);
}
From 1b1e29c7569db74f8d111fa43c1640e717163642 Mon Sep 17 00:00:00 2001
From: Paul Tavares <56442535+paul-tavares@users.noreply.github.com>
Date: Wed, 30 Jun 2021 10:51:36 -0400
Subject: [PATCH 060/121] [Security Solution][Endpoint] Unit Test cases to
cover CaseView action/comment refresh and Endpoint isolation api (#103560)
* Tests for `` `refreshRef` prop
* Tests for Isolation API update of cases
---
.../components/case_view/index.test.tsx | 65 +++++++++++++++++-
x-pack/plugins/cases/server/index.ts | 1 -
.../server/endpoint/mocks.ts | 11 +++-
.../endpoint/routes/actions/isolation.test.ts | 66 +++++++++++++++++--
4 files changed, 136 insertions(+), 7 deletions(-)
diff --git a/x-pack/plugins/cases/public/components/case_view/index.test.tsx b/x-pack/plugins/cases/public/components/case_view/index.test.tsx
index e04bbbe54c8374..f12c8ba098d437 100644
--- a/x-pack/plugins/cases/public/components/case_view/index.test.tsx
+++ b/x-pack/plugins/cases/public/components/case_view/index.test.tsx
@@ -9,7 +9,7 @@ import React from 'react';
import { mount } from 'enzyme';
import '../../common/mock/match_media';
-import { CaseComponent, CaseComponentProps, CaseView } from '.';
+import { CaseComponent, CaseComponentProps, CaseView, CaseViewProps } from '.';
import {
basicCase,
basicCaseClosed,
@@ -789,6 +789,69 @@ describe('CaseView ', () => {
});
});
+ describe('when a `refreshRef` prop is provided', () => {
+ let refreshRef: CaseViewProps['refreshRef'];
+
+ beforeEach(() => {
+ (useGetCase as jest.Mock).mockImplementation(() => defaultGetCase);
+ refreshRef = React.createRef();
+
+ mount(
+
+
+
+ );
+ });
+
+ it('should set it with expected refresh interface', async () => {
+ expect(refreshRef!.current).toEqual({
+ refreshUserActionsAndComments: expect.any(Function),
+ refreshCase: expect.any(Function),
+ });
+ });
+
+ it('should refresh actions and comments', async () => {
+ await waitFor(() => {
+ refreshRef!.current!.refreshUserActionsAndComments();
+ expect(fetchCaseUserActions).toBeCalledWith('1234', 'resilient-2', undefined);
+ expect(fetchCase).toBeCalledWith(true);
+ });
+ });
+
+ it('should refresh case', async () => {
+ await waitFor(() => {
+ refreshRef!.current!.refreshCase();
+ expect(fetchCase).toBeCalledWith(); // No args given to `fetchCase()`
+ });
+ });
+ });
+
describe('Callouts', () => {
it('it shows the danger callout when a connector has been deleted', async () => {
useConnectorsMock.mockImplementation(() => ({ connectors: [], loading: false }));
diff --git a/x-pack/plugins/cases/server/index.ts b/x-pack/plugins/cases/server/index.ts
index 0e4554572aad94..4526ecce284602 100644
--- a/x-pack/plugins/cases/server/index.ts
+++ b/x-pack/plugins/cases/server/index.ts
@@ -10,7 +10,6 @@ export { CasesClient } from './client';
import { ConfigType, ConfigSchema } from './config';
import { CasePlugin } from './plugin';
-export { CaseRequestContext } from './types';
export const config: PluginConfigDescriptor = {
schema: ConfigSchema,
deprecations: ({ renameFromRoot }) => [
diff --git a/x-pack/plugins/security_solution/server/endpoint/mocks.ts b/x-pack/plugins/security_solution/server/endpoint/mocks.ts
index b2439efc63567f..08a09b427feb2e 100644
--- a/x-pack/plugins/security_solution/server/endpoint/mocks.ts
+++ b/x-pack/plugins/security_solution/server/endpoint/mocks.ts
@@ -31,6 +31,12 @@ import { MetadataRequestContext } from './routes/metadata/handlers';
import { LicenseService } from '../../common/license';
import { SecuritySolutionRequestHandlerContext } from '../types';
import { parseExperimentalConfigValue } from '../../common/experimental_features';
+// A TS error (TS2403) is thrown when attempting to export the mock function below from Cases
+// plugin server `index.ts`. Its unclear what is actually causing the error. Since this is a Mock
+// file and not bundled with the application, adding a eslint disable below and using import from
+// a restricted path.
+// eslint-disable-next-line @kbn/eslint/no-restricted-paths
+import { createCasesClientMock } from '../../../cases/server/client/mocks';
/**
* Creates a mocked EndpointAppContext.
@@ -69,7 +75,10 @@ export const createMockEndpointAppContextService = (
export const createMockEndpointAppContextServiceStartContract = (): jest.Mocked => {
const factory = new AppClientFactory();
const config = createMockConfig();
+ const casesClientMock = createCasesClientMock();
+
factory.setup({ getSpaceId: () => 'mockSpace', config });
+
return {
agentService: createMockAgentService(),
packageService: createMockPackageService(),
@@ -88,7 +97,7 @@ export const createMockEndpointAppContextServiceStartContract = (): jest.Mocked<
exceptionListsClient: listMock.getExceptionListClient(),
packagePolicyService: createPackagePolicyServiceMock(),
cases: {
- getCasesClientWithRequest: jest.fn(),
+ getCasesClientWithRequest: jest.fn(async () => casesClientMock),
},
};
};
diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/actions/isolation.test.ts b/x-pack/plugins/security_solution/server/endpoint/routes/actions/isolation.test.ts
index 1b1490d3af0727..3b88aac8d93f74 100644
--- a/x-pack/plugins/security_solution/server/endpoint/routes/actions/isolation.test.ts
+++ b/x-pack/plugins/security_solution/server/endpoint/routes/actions/isolation.test.ts
@@ -6,7 +6,7 @@
*/
/* eslint-disable @typescript-eslint/no-explicit-any */
-import { KibanaResponseFactory, RequestHandler, RouteConfig } from 'kibana/server';
+import { KibanaRequest, KibanaResponseFactory, RequestHandler, RouteConfig } from 'kibana/server';
import {
elasticsearchServiceMock,
httpServerMock,
@@ -37,15 +37,17 @@ import {
} from '../../../../common/endpoint/constants';
import {
EndpointAction,
+ HostIsolationRequestBody,
HostIsolationResponse,
HostMetadata,
} from '../../../../common/endpoint/types';
import { EndpointDocGenerator } from '../../../../common/endpoint/generate_data';
import { createV2SearchResponse } from '../metadata/support/test_support';
import { ElasticsearchAssetType } from '../../../../../fleet/common';
+import { CasesClientMock } from '../../../../../cases/server/client/mocks';
interface CallRouteInterface {
- body?: any;
+ body?: HostIsolationRequestBody;
idxResponse?: any;
searchResponse?: HostMetadata;
mockUser?: any;
@@ -358,8 +360,64 @@ describe('Host Isolation', () => {
});
describe('Cases', () => {
- it.todo('logs a comment to the provided case');
- it.todo('logs a comment to any cases associated with the given alert');
+ let casesClient: CasesClientMock;
+
+ const getCaseIdsFromAttachmentAddService = () => {
+ return casesClient.attachments.add.mock.calls.map(([addArgs]) => addArgs.caseId);
+ };
+
+ beforeEach(async () => {
+ casesClient = (await endpointAppContextService.getCasesClient(
+ {} as KibanaRequest
+ )) as CasesClientMock;
+
+ let counter = 1;
+ casesClient.cases.getCasesByAlertID.mockImplementation(async () => {
+ return [
+ {
+ id: `case-${counter++}`,
+ title: 'case',
+ },
+ ];
+ });
+ });
+
+ it('logs a comment to the provided cases', async () => {
+ await callRoute(ISOLATE_HOST_ROUTE, {
+ body: { endpoint_ids: ['XYZ'], case_ids: ['one', 'two'] },
+ });
+
+ expect(casesClient.attachments.add).toHaveBeenCalledTimes(2);
+ expect(getCaseIdsFromAttachmentAddService()).toEqual(
+ expect.arrayContaining(['one', 'two'])
+ );
+ });
+
+ it('logs a comment to any cases associated with the given alerts', async () => {
+ await callRoute(ISOLATE_HOST_ROUTE, {
+ body: { endpoint_ids: ['XYZ'], alert_ids: ['one', 'two'] },
+ });
+
+ expect(getCaseIdsFromAttachmentAddService()).toEqual(
+ expect.arrayContaining(['case-1', 'case-2'])
+ );
+ });
+
+ it('logs a comment to any cases provided on input along with cases associated with the given alerts', async () => {
+ await callRoute(ISOLATE_HOST_ROUTE, {
+ // 'case-1` provided on `case_ids` should be dedupped
+ body: {
+ endpoint_ids: ['XYZ'],
+ case_ids: ['ONE', 'TWO', 'case-1'],
+ alert_ids: ['one', 'two'],
+ },
+ });
+
+ expect(casesClient.attachments.add).toHaveBeenCalledTimes(4);
+ expect(getCaseIdsFromAttachmentAddService()).toEqual(
+ expect.arrayContaining(['ONE', 'TWO', 'case-1', 'case-2'])
+ );
+ });
});
});
});
From e04f99541a08aac2ddaad75118c160fd0f9e610a Mon Sep 17 00:00:00 2001
From: Joe Reuter
Date: Wed, 30 Jun 2021 16:59:30 +0200
Subject: [PATCH 061/121] [Lens] Fix help popovers (#103314)
---
.../lens/public/indexpattern_datasource/help_popover.tsx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/help_popover.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/help_popover.tsx
index 0e4c1897743b49..88fb96b58cd211 100644
--- a/x-pack/plugins/lens/public/indexpattern_datasource/help_popover.tsx
+++ b/x-pack/plugins/lens/public/indexpattern_datasource/help_popover.tsx
@@ -67,7 +67,7 @@ export const HelpPopover = ({
panelClassName="lnsHelpPopover__panel"
panelPaddingSize="none"
>
- {title && {title} }
+ {title && {title} }
{children}
From affe82b73c6161530391322cf451905c5eab07ca Mon Sep 17 00:00:00 2001
From: Joe Reuter
Date: Wed, 30 Jun 2021 17:02:22 +0200
Subject: [PATCH 062/121] [Lens] Lazy loading refactoring (#103277)
---
.../lens/public/app_plugin/app.test.tsx | 2 +-
x-pack/plugins/lens/public/app_plugin/app.tsx | 2 +-
.../lens/public/app_plugin/mounter.test.tsx | 2 +-
.../lens/public/app_plugin/mounter.tsx | 2 +-
.../app_plugin/save_modal_container.tsx | 2 +-
.../plugins/lens/public/app_plugin/types.ts | 2 +-
x-pack/plugins/lens/public/async_services.ts | 9 +-
.../public/editor_frame_service/service.tsx | 26 +--
.../embeddable/embeddable.test.tsx | 18 +-
.../embeddable/embeddable.tsx | 22 +--
.../embeddable/embeddable_component.tsx | 14 +-
.../embeddable/embeddable_factory.ts | 21 +--
.../embeddable/expression_wrapper.tsx | 4 +-
.../embeddable/index.ts | 0
x-pack/plugins/lens/public/index.ts | 4 +-
.../lens/public/lens_attribute_service.ts | 2 +-
x-pack/plugins/lens/public/mocks.tsx | 2 +-
x-pack/plugins/lens/public/plugin.ts | 165 +++++++++++-------
x-pack/plugins/lens/public/search_provider.ts | 10 --
19 files changed, 164 insertions(+), 145 deletions(-)
rename x-pack/plugins/lens/public/{editor_frame_service => }/embeddable/embeddable.test.tsx (97%)
rename x-pack/plugins/lens/public/{editor_frame_service => }/embeddable/embeddable.tsx (95%)
rename x-pack/plugins/lens/public/{editor_frame_service => }/embeddable/embeddable_component.tsx (87%)
rename x-pack/plugins/lens/public/{editor_frame_service => }/embeddable/embeddable_factory.ts (84%)
rename x-pack/plugins/lens/public/{editor_frame_service => }/embeddable/expression_wrapper.tsx (97%)
rename x-pack/plugins/lens/public/{editor_frame_service => }/embeddable/index.ts (100%)
diff --git a/x-pack/plugins/lens/public/app_plugin/app.test.tsx b/x-pack/plugins/lens/public/app_plugin/app.test.tsx
index 30b4e2d954d2b8..bced8bf7c04fe1 100644
--- a/x-pack/plugins/lens/public/app_plugin/app.test.tsx
+++ b/x-pack/plugins/lens/public/app_plugin/app.test.tsx
@@ -29,7 +29,7 @@ import {
Query,
} from '../../../../../src/plugins/data/public';
import { TopNavMenuData } from '../../../../../src/plugins/navigation/public';
-import { LensByValueInput } from '../editor_frame_service/embeddable/embeddable';
+import { LensByValueInput } from '../embeddable/embeddable';
import { SavedObjectReference } from '../../../../../src/core/types';
import { KibanaContextProvider } from '../../../../../src/plugins/kibana_react/public';
import moment from 'moment';
diff --git a/x-pack/plugins/lens/public/app_plugin/app.tsx b/x-pack/plugins/lens/public/app_plugin/app.tsx
index 95068204d5eb41..fee64a532553df 100644
--- a/x-pack/plugins/lens/public/app_plugin/app.tsx
+++ b/x-pack/plugins/lens/public/app_plugin/app.tsx
@@ -22,7 +22,7 @@ import { OnSaveProps } from '../../../../../src/plugins/saved_objects/public';
import { syncQueryStateWithUrl } from '../../../../../src/plugins/data/public';
import { LensAppProps, LensAppServices } from './types';
import { LensTopNavMenu } from './lens_top_nav';
-import { LensByReferenceInput } from '../editor_frame_service/embeddable';
+import { LensByReferenceInput } from '../embeddable';
import { EditorFrameInstance } from '../types';
import {
setState as setAppState,
diff --git a/x-pack/plugins/lens/public/app_plugin/mounter.test.tsx b/x-pack/plugins/lens/public/app_plugin/mounter.test.tsx
index e84f6fd43418be..4f890a51f9b6a8 100644
--- a/x-pack/plugins/lens/public/app_plugin/mounter.test.tsx
+++ b/x-pack/plugins/lens/public/app_plugin/mounter.test.tsx
@@ -7,7 +7,7 @@
import { makeDefaultServices, mockLensStore } from '../mocks';
import { act } from 'react-dom/test-utils';
import { loadDocument } from './mounter';
-import { LensEmbeddableInput } from '../editor_frame_service/embeddable/embeddable';
+import { LensEmbeddableInput } from '../embeddable/embeddable';
const defaultSavedObjectId = '1234';
diff --git a/x-pack/plugins/lens/public/app_plugin/mounter.tsx b/x-pack/plugins/lens/public/app_plugin/mounter.tsx
index 8e59f90c958f91..7f27b06c51ba40 100644
--- a/x-pack/plugins/lens/public/app_plugin/mounter.tsx
+++ b/x-pack/plugins/lens/public/app_plugin/mounter.tsx
@@ -31,7 +31,7 @@ import {
LensEmbeddableInput,
LensByReferenceInput,
LensByValueInput,
-} from '../editor_frame_service/embeddable/embeddable';
+} from '../embeddable/embeddable';
import { ACTION_VISUALIZE_LENS_FIELD } from '../../../../../src/plugins/ui_actions/public';
import { LensAttributeService } from '../lens_attribute_service';
import { LensAppServices, RedirectToOriginProps, HistoryLocationState } from './types';
diff --git a/x-pack/plugins/lens/public/app_plugin/save_modal_container.tsx b/x-pack/plugins/lens/public/app_plugin/save_modal_container.tsx
index a65c8e6732e448..facf85d45bcbb4 100644
--- a/x-pack/plugins/lens/public/app_plugin/save_modal_container.tsx
+++ b/x-pack/plugins/lens/public/app_plugin/save_modal_container.tsx
@@ -14,7 +14,7 @@ import { SaveModal } from './save_modal';
import { LensAppProps, LensAppServices } from './types';
import type { SaveProps } from './app';
import { Document, injectFilterReferences } from '../persistence';
-import { LensByReferenceInput, LensEmbeddableInput } from '../editor_frame_service/embeddable';
+import { LensByReferenceInput, LensEmbeddableInput } from '../embeddable';
import { LensAttributeService } from '../lens_attribute_service';
import {
DataPublicPluginStart,
diff --git a/x-pack/plugins/lens/public/app_plugin/types.ts b/x-pack/plugins/lens/public/app_plugin/types.ts
index b253e76aa14071..b4e7f18ccfeb83 100644
--- a/x-pack/plugins/lens/public/app_plugin/types.ts
+++ b/x-pack/plugins/lens/public/app_plugin/types.ts
@@ -20,7 +20,7 @@ import {
import { DataPublicPluginStart } from '../../../../../src/plugins/data/public';
import { UsageCollectionStart } from '../../../../../src/plugins/usage_collection/public';
import { DashboardStart } from '../../../../../src/plugins/dashboard/public';
-import { LensEmbeddableInput } from '../editor_frame_service/embeddable/embeddable';
+import { LensEmbeddableInput } from '../embeddable/embeddable';
import { NavigationPublicPluginStart } from '../../../../../src/plugins/navigation/public';
import { LensAttributeService } from '../lens_attribute_service';
import { IStorageWrapper } from '../../../../../src/plugins/kibana_utils/public';
diff --git a/x-pack/plugins/lens/public/async_services.ts b/x-pack/plugins/lens/public/async_services.ts
index e7be2959556154..9fac9a143c3b33 100644
--- a/x-pack/plugins/lens/public/async_services.ts
+++ b/x-pack/plugins/lens/public/async_services.ts
@@ -15,15 +15,22 @@
*/
export * from './datatable_visualization/datatable_visualization';
+export * from './datatable_visualization';
export * from './metric_visualization/metric_visualization';
+export * from './metric_visualization';
export * from './pie_visualization/pie_visualization';
+export * from './pie_visualization';
export * from './xy_visualization/xy_visualization';
+export * from './xy_visualization';
export * from './heatmap_visualization/heatmap_visualization';
+export * from './heatmap_visualization';
export * from './indexpattern_datasource/indexpattern';
+export * from './indexpattern_datasource';
export * from './editor_frame_service/editor_frame';
-export * from './editor_frame_service/embeddable';
+export * from './editor_frame_service';
+export * from './embeddable';
export * from './app_plugin/mounter';
export * from './lens_attribute_service';
export * from './lens_ui_telemetry';
diff --git a/x-pack/plugins/lens/public/editor_frame_service/service.tsx b/x-pack/plugins/lens/public/editor_frame_service/service.tsx
index 62274df23e837b..6a26f85a64acc6 100644
--- a/x-pack/plugins/lens/public/editor_frame_service/service.tsx
+++ b/x-pack/plugins/lens/public/editor_frame_service/service.tsx
@@ -23,11 +23,9 @@ import {
} from '../types';
import { Document } from '../persistence/saved_object_store';
import { mergeTables } from './merge_tables';
-import { EmbeddableFactory, LensEmbeddableStartServices } from './embeddable/embeddable_factory';
import { UiActionsStart } from '../../../../../src/plugins/ui_actions/public';
import { ChartsPluginSetup } from '../../../../../src/plugins/charts/public';
import { DashboardStart } from '../../../../../src/plugins/dashboard/public';
-import { LensAttributeService } from '../lens_attribute_service';
export interface EditorFrameSetupPlugins {
data: DataPublicPluginSetup;
@@ -72,7 +70,7 @@ export class EditorFrameService {
* This is an asynchronous process and should only be triggered once for a saved object.
* @param doc parsed Lens saved object
*/
- private documentToExpression = async (doc: Document) => {
+ public documentToExpression = async (doc: Document) => {
const [resolvedDatasources, resolvedVisualizations] = await Promise.all([
collectAsyncDefinitions(this.datasources),
collectAsyncDefinitions(this.visualizations),
@@ -85,30 +83,10 @@ export class EditorFrameService {
public setup(
core: CoreSetup,
- plugins: EditorFrameSetupPlugins,
- getAttributeService: () => Promise
+ plugins: EditorFrameSetupPlugins
): EditorFrameSetup {
plugins.expressions.registerFunction(() => mergeTables);
- const getStartServices = async (): Promise => {
- const [coreStart, deps] = await core.getStartServices();
- return {
- attributeService: await getAttributeService(),
- capabilities: coreStart.application.capabilities,
- coreHttp: coreStart.http,
- timefilter: deps.data.query.timefilter.timefilter,
- expressionRenderer: deps.expressions.ReactExpressionRenderer,
- documentToExpression: this.documentToExpression,
- indexPatternService: deps.data.indexPatterns,
- uiActions: deps.uiActions,
- usageCollection: plugins.usageCollection,
- };
- };
-
- if (plugins.embeddable) {
- plugins.embeddable.registerEmbeddableFactory('lens', new EmbeddableFactory(getStartServices));
- }
-
return {
registerDatasource: (datasource) => {
this.datasources.push(datasource as Datasource);
diff --git a/x-pack/plugins/lens/public/editor_frame_service/embeddable/embeddable.test.tsx b/x-pack/plugins/lens/public/embeddable/embeddable.test.tsx
similarity index 97%
rename from x-pack/plugins/lens/public/editor_frame_service/embeddable/embeddable.test.tsx
rename to x-pack/plugins/lens/public/embeddable/embeddable.test.tsx
index 00eaadeaf82996..56abf499aac88c 100644
--- a/x-pack/plugins/lens/public/editor_frame_service/embeddable/embeddable.test.tsx
+++ b/x-pack/plugins/lens/public/embeddable/embeddable.test.tsx
@@ -14,17 +14,17 @@ import {
} from './embeddable';
import { ReactExpressionRendererProps } from 'src/plugins/expressions/public';
import { Query, TimeRange, Filter, IndexPatternsContract } from 'src/plugins/data/public';
-import { Document } from '../../persistence';
-import { dataPluginMock } from '../../../../../../src/plugins/data/public/mocks';
-import { VIS_EVENT_TO_TRIGGER } from '../../../../../../src/plugins/visualizations/public/embeddable';
-import { coreMock, httpServiceMock } from '../../../../../../src/core/public/mocks';
-import { IBasePath } from '../../../../../../src/core/public';
-import { AttributeService, ViewMode } from '../../../../../../src/plugins/embeddable/public';
-import { LensAttributeService } from '../../lens_attribute_service';
-import { OnSaveProps } from '../../../../../../src/plugins/saved_objects/public/save_modal';
+import { Document } from '../persistence';
+import { dataPluginMock } from '../../../../../src/plugins/data/public/mocks';
+import { VIS_EVENT_TO_TRIGGER } from '../../../../../src/plugins/visualizations/public/embeddable';
+import { coreMock, httpServiceMock } from '../../../../../src/core/public/mocks';
+import { IBasePath } from '../../../../../src/core/public';
+import { AttributeService, ViewMode } from '../../../../../src/plugins/embeddable/public';
+import { LensAttributeService } from '../lens_attribute_service';
+import { OnSaveProps } from '../../../../../src/plugins/saved_objects/public/save_modal';
import { act } from 'react-dom/test-utils';
-jest.mock('../../../../../../src/plugins/inspector/public/', () => ({
+jest.mock('../../../../../src/plugins/inspector/public/', () => ({
isAvailable: false,
open: false,
}));
diff --git a/x-pack/plugins/lens/public/editor_frame_service/embeddable/embeddable.tsx b/x-pack/plugins/lens/public/embeddable/embeddable.tsx
similarity index 95%
rename from x-pack/plugins/lens/public/editor_frame_service/embeddable/embeddable.tsx
rename to x-pack/plugins/lens/public/embeddable/embeddable.tsx
index 89a04f38201697..c114aca95578a1 100644
--- a/x-pack/plugins/lens/public/editor_frame_service/embeddable/embeddable.tsx
+++ b/x-pack/plugins/lens/public/embeddable/embeddable.tsx
@@ -29,8 +29,8 @@ import { METRIC_TYPE } from '@kbn/analytics';
import {
ExpressionRendererEvent,
ReactExpressionRendererType,
-} from '../../../../../../src/plugins/expressions/public';
-import { VIS_EVENT_TO_TRIGGER } from '../../../../../../src/plugins/visualizations/public';
+} from '../../../../../src/plugins/expressions/public';
+import { VIS_EVENT_TO_TRIGGER } from '../../../../../src/plugins/visualizations/public';
import {
Embeddable as AbstractEmbeddable,
@@ -39,10 +39,10 @@ import {
IContainer,
SavedObjectEmbeddableInput,
ReferenceOrValueEmbeddable,
-} from '../../../../../../src/plugins/embeddable/public';
-import { Document, injectFilterReferences } from '../../persistence';
+} from '../../../../../src/plugins/embeddable/public';
+import { Document, injectFilterReferences } from '../persistence';
import { ExpressionWrapper } from './expression_wrapper';
-import { UiActionsStart } from '../../../../../../src/plugins/ui_actions/public';
+import { UiActionsStart } from '../../../../../src/plugins/ui_actions/public';
import {
isLensBrushEvent,
isLensFilterEvent,
@@ -50,13 +50,13 @@ import {
LensBrushEvent,
LensFilterEvent,
LensTableRowContextMenuEvent,
-} from '../../types';
+} from '../types';
-import { IndexPatternsContract } from '../../../../../../src/plugins/data/public';
-import { getEditPath, DOC_TYPE, PLUGIN_ID } from '../../../common';
-import { IBasePath } from '../../../../../../src/core/public';
-import { LensAttributeService } from '../../lens_attribute_service';
-import type { ErrorMessage } from '../types';
+import { IndexPatternsContract } from '../../../../../src/plugins/data/public';
+import { getEditPath, DOC_TYPE, PLUGIN_ID } from '../../common';
+import { IBasePath } from '../../../../../src/core/public';
+import { LensAttributeService } from '../lens_attribute_service';
+import type { ErrorMessage } from '../editor_frame_service/types';
export type LensSavedObjectAttributes = Omit;
diff --git a/x-pack/plugins/lens/public/editor_frame_service/embeddable/embeddable_component.tsx b/x-pack/plugins/lens/public/embeddable/embeddable_component.tsx
similarity index 87%
rename from x-pack/plugins/lens/public/editor_frame_service/embeddable/embeddable_component.tsx
rename to x-pack/plugins/lens/public/embeddable/embeddable_component.tsx
index 82812ee3556636..688d612b8533e8 100644
--- a/x-pack/plugins/lens/public/editor_frame_service/embeddable/embeddable_component.tsx
+++ b/x-pack/plugins/lens/public/embeddable/embeddable_component.tsx
@@ -17,14 +17,14 @@ import {
EmbeddableStart,
IEmbeddable,
useEmbeddableFactory,
-} from '../../../../../../src/plugins/embeddable/public';
+} from '../../../../../src/plugins/embeddable/public';
import type { LensByReferenceInput, LensByValueInput } from './embeddable';
-import type { Document } from '../../persistence';
-import type { IndexPatternPersistedState } from '../../indexpattern_datasource/types';
-import type { XYState } from '../../xy_visualization/types';
-import type { PieVisualizationState } from '../../pie_visualization/types';
-import type { DatatableVisualizationState } from '../../datatable_visualization/visualization';
-import type { MetricState } from '../../metric_visualization/types';
+import type { Document } from '../persistence';
+import type { IndexPatternPersistedState } from '../indexpattern_datasource/types';
+import type { XYState } from '../xy_visualization/types';
+import type { PieVisualizationState } from '../pie_visualization/types';
+import type { DatatableVisualizationState } from '../datatable_visualization/visualization';
+import type { MetricState } from '../metric_visualization/types';
type LensAttributes = Omit<
Document,
diff --git a/x-pack/plugins/lens/public/editor_frame_service/embeddable/embeddable_factory.ts b/x-pack/plugins/lens/public/embeddable/embeddable_factory.ts
similarity index 84%
rename from x-pack/plugins/lens/public/editor_frame_service/embeddable/embeddable_factory.ts
rename to x-pack/plugins/lens/public/embeddable/embeddable_factory.ts
index 095e18e3fb5eb1..894e0dda3b674c 100644
--- a/x-pack/plugins/lens/public/editor_frame_service/embeddable/embeddable_factory.ts
+++ b/x-pack/plugins/lens/public/embeddable/embeddable_factory.ts
@@ -11,21 +11,18 @@ import { RecursiveReadonly } from '@kbn/utility-types';
import { Ast } from '@kbn/interpreter/target/common';
import { EmbeddableStateWithType } from 'src/plugins/embeddable/common';
import { UsageCollectionSetup } from 'src/plugins/usage_collection/public';
-import {
- IndexPatternsContract,
- TimefilterContract,
-} from '../../../../../../src/plugins/data/public';
-import { ReactExpressionRendererType } from '../../../../../../src/plugins/expressions/public';
+import { IndexPatternsContract, TimefilterContract } from '../../../../../src/plugins/data/public';
+import { ReactExpressionRendererType } from '../../../../../src/plugins/expressions/public';
import {
EmbeddableFactoryDefinition,
IContainer,
-} from '../../../../../../src/plugins/embeddable/public';
+} from '../../../../../src/plugins/embeddable/public';
import { LensByReferenceInput, LensEmbeddableInput } from './embeddable';
-import { UiActionsStart } from '../../../../../../src/plugins/ui_actions/public';
-import { Document } from '../../persistence/saved_object_store';
-import { LensAttributeService } from '../../lens_attribute_service';
-import { DOC_TYPE } from '../../../common';
-import { ErrorMessage } from '../types';
+import { UiActionsStart } from '../../../../../src/plugins/ui_actions/public';
+import { Document } from '../persistence/saved_object_store';
+import { LensAttributeService } from '../lens_attribute_service';
+import { DOC_TYPE } from '../../common';
+import { ErrorMessage } from '../editor_frame_service/types';
export interface LensEmbeddableStartServices {
timefilter: TimefilterContract;
@@ -92,7 +89,7 @@ export class EmbeddableFactory implements EmbeddableFactoryDefinition {
usageCollection,
} = await this.getStartServices();
- const { Embeddable } = await import('../../async_services');
+ const { Embeddable } = await import('../async_services');
return new Embeddable(
{
diff --git a/x-pack/plugins/lens/public/editor_frame_service/embeddable/expression_wrapper.tsx b/x-pack/plugins/lens/public/embeddable/expression_wrapper.tsx
similarity index 97%
rename from x-pack/plugins/lens/public/editor_frame_service/embeddable/expression_wrapper.tsx
rename to x-pack/plugins/lens/public/embeddable/expression_wrapper.tsx
index 15d168465ec71a..34b373b87465d2 100644
--- a/x-pack/plugins/lens/public/editor_frame_service/embeddable/expression_wrapper.tsx
+++ b/x-pack/plugins/lens/public/embeddable/expression_wrapper.tsx
@@ -17,8 +17,8 @@ import {
import { ExecutionContextSearch } from 'src/plugins/data/public';
import { DefaultInspectorAdapters, RenderMode } from 'src/plugins/expressions';
import classNames from 'classnames';
-import { getOriginalRequestErrorMessages } from '../error_helper';
-import { ErrorMessage } from '../types';
+import { getOriginalRequestErrorMessages } from '../editor_frame_service/error_helper';
+import { ErrorMessage } from '../editor_frame_service/types';
export interface ExpressionWrapperProps {
ExpressionRenderer: ReactExpressionRendererType;
diff --git a/x-pack/plugins/lens/public/editor_frame_service/embeddable/index.ts b/x-pack/plugins/lens/public/embeddable/index.ts
similarity index 100%
rename from x-pack/plugins/lens/public/editor_frame_service/embeddable/index.ts
rename to x-pack/plugins/lens/public/embeddable/index.ts
diff --git a/x-pack/plugins/lens/public/index.ts b/x-pack/plugins/lens/public/index.ts
index 3054f3787a24c6..76afe7260a35a6 100644
--- a/x-pack/plugins/lens/public/index.ts
+++ b/x-pack/plugins/lens/public/index.ts
@@ -10,7 +10,7 @@ import { LensPlugin } from './plugin';
export type {
EmbeddableComponentProps,
TypedLensByValueInput,
-} from './editor_frame_service/embeddable/embeddable_component';
+} from './embeddable/embeddable_component';
export type {
XYState,
AxesSettingsConfig,
@@ -58,7 +58,7 @@ export type {
MathIndexPatternColumn,
OverallSumIndexPatternColumn,
} from './indexpattern_datasource/types';
-export type { LensEmbeddableInput } from './editor_frame_service/embeddable';
+export type { LensEmbeddableInput } from './embeddable';
export { LensPublicStart } from './plugin';
diff --git a/x-pack/plugins/lens/public/lens_attribute_service.ts b/x-pack/plugins/lens/public/lens_attribute_service.ts
index 94a443f6e75f3e..39a1903c6d0c45 100644
--- a/x-pack/plugins/lens/public/lens_attribute_service.ts
+++ b/x-pack/plugins/lens/public/lens_attribute_service.ts
@@ -12,7 +12,7 @@ import {
LensSavedObjectAttributes,
LensByValueInput,
LensByReferenceInput,
-} from './editor_frame_service/embeddable/embeddable';
+} from './embeddable/embeddable';
import { SavedObjectIndexStore, Document } from './persistence';
import { checkForDuplicateTitle, OnSaveProps } from '../../../../src/plugins/saved_objects/public';
import { DOC_TYPE } from '../common';
diff --git a/x-pack/plugins/lens/public/mocks.tsx b/x-pack/plugins/lens/public/mocks.tsx
index b35353b98a5859..dcdabac36db3a4 100644
--- a/x-pack/plugins/lens/public/mocks.tsx
+++ b/x-pack/plugins/lens/public/mocks.tsx
@@ -26,7 +26,7 @@ import {
LensByValueInput,
LensSavedObjectAttributes,
LensByReferenceInput,
-} from './editor_frame_service/embeddable/embeddable';
+} from './embeddable/embeddable';
import {
mockAttributeService,
createEmbeddableStateTransferMock,
diff --git a/x-pack/plugins/lens/public/plugin.ts b/x-pack/plugins/lens/public/plugin.ts
index ad81413e213450..26608f9cc78bea 100644
--- a/x-pack/plugins/lens/public/plugin.ts
+++ b/x-pack/plugins/lens/public/plugin.ts
@@ -11,7 +11,11 @@ import { UsageCollectionSetup, UsageCollectionStart } from 'src/plugins/usage_co
import { DataPublicPluginSetup, DataPublicPluginStart } from '../../../../src/plugins/data/public';
import { EmbeddableSetup, EmbeddableStart } from '../../../../src/plugins/embeddable/public';
import { DashboardStart } from '../../../../src/plugins/dashboard/public';
-import { ExpressionsSetup, ExpressionsStart } from '../../../../src/plugins/expressions/public';
+import {
+ ExpressionsServiceSetup,
+ ExpressionsSetup,
+ ExpressionsStart,
+} from '../../../../src/plugins/expressions/public';
import {
VisualizationsSetup,
VisualizationsStart,
@@ -22,19 +26,29 @@ import { GlobalSearchPluginSetup } from '../../global_search/public';
import { ChartsPluginSetup, ChartsPluginStart } from '../../../../src/plugins/charts/public';
import { PresentationUtilPluginStart } from '../../../../src/plugins/presentation_util/public';
import { EmbeddableStateTransfer } from '../../../../src/plugins/embeddable/public';
-import { EditorFrameService } from './editor_frame_service';
+import type { EditorFrameService as EditorFrameServiceType } from './editor_frame_service';
import { IndexPatternFieldEditorStart } from '../../../../src/plugins/index_pattern_field_editor/public';
-import {
- IndexPatternDatasource,
+import type {
+ IndexPatternDatasource as IndexPatternDatasourceType,
IndexPatternDatasourceSetupPlugins,
} from './indexpattern_datasource';
-import { XyVisualization, XyVisualizationPluginSetupPlugins } from './xy_visualization';
-import { MetricVisualization, MetricVisualizationPluginSetupPlugins } from './metric_visualization';
-import {
- DatatableVisualization,
+import type {
+ XyVisualization as XyVisualizationType,
+ XyVisualizationPluginSetupPlugins,
+} from './xy_visualization';
+import type {
+ MetricVisualization as MetricVisualizationType,
+ MetricVisualizationPluginSetupPlugins,
+} from './metric_visualization';
+import type {
+ DatatableVisualization as DatatableVisualizationType,
DatatableVisualizationPluginSetupPlugins,
} from './datatable_visualization';
-import { PieVisualization, PieVisualizationPluginSetupPlugins } from './pie_visualization';
+import type {
+ PieVisualization as PieVisualizationType,
+ PieVisualizationPluginSetupPlugins,
+} from './pie_visualization';
+import type { HeatmapVisualization as HeatmapVisualizationType } from './heatmap_visualization';
import { AppNavLinkStatus } from '../../../../src/core/public';
import type { SavedObjectTaggingPluginStart } from '../../saved_objects_tagging/public';
@@ -50,12 +64,12 @@ import { visualizeFieldAction } from './trigger_actions/visualize_field_actions'
import { getSearchProvider } from './search_provider';
import { LensAttributeService } from './lens_attribute_service';
-import { LensEmbeddableInput } from './editor_frame_service/embeddable';
+import { LensEmbeddableInput } from './embeddable';
+import { EmbeddableFactory, LensEmbeddableStartServices } from './embeddable/embeddable_factory';
import {
EmbeddableComponentProps,
getEmbeddableComponent,
-} from './editor_frame_service/embeddable/embeddable_component';
-import { HeatmapVisualization } from './heatmap_visualization';
+} from './embeddable/embeddable_component';
import { getSaveModalComponent } from './app_plugin/shared/saved_modal_lazy';
import { SaveModalContainerProps } from './app_plugin/save_modal_container';
@@ -126,28 +140,18 @@ export interface LensPublicStart {
}
export class LensPlugin {
- private datatableVisualization: DatatableVisualization;
- private editorFrameService: EditorFrameService;
+ private datatableVisualization: DatatableVisualizationType | undefined;
+ private editorFrameService: EditorFrameServiceType | undefined;
private createEditorFrame: EditorFrameStart['createInstance'] | null = null;
private attributeService: (() => Promise) | null = null;
- private indexpatternDatasource: IndexPatternDatasource;
- private xyVisualization: XyVisualization;
- private metricVisualization: MetricVisualization;
- private pieVisualization: PieVisualization;
- private heatmapVisualization: HeatmapVisualization;
+ private indexpatternDatasource: IndexPatternDatasourceType | undefined;
+ private xyVisualization: XyVisualizationType | undefined;
+ private metricVisualization: MetricVisualizationType | undefined;
+ private pieVisualization: PieVisualizationType | undefined;
+ private heatmapVisualization: HeatmapVisualizationType | undefined;
private stopReportManager?: () => void;
- constructor() {
- this.datatableVisualization = new DatatableVisualization();
- this.editorFrameService = new EditorFrameService();
- this.indexpatternDatasource = new IndexPatternDatasource();
- this.xyVisualization = new XyVisualization();
- this.metricVisualization = new MetricVisualization();
- this.pieVisualization = new PieVisualization();
- this.heatmapVisualization = new HeatmapVisualization();
- }
-
setup(
core: CoreSetup,
{
@@ -166,36 +170,25 @@ export class LensPlugin {
const [coreStart, startDependencies] = await core.getStartServices();
return getLensAttributeService(coreStart, startDependencies);
};
- const editorFrameSetupInterface = this.editorFrameService.setup(
- core,
- {
- data,
- embeddable,
- charts,
- expressions,
+ const getStartServices = async (): Promise => {
+ const [coreStart, deps] = await core.getStartServices();
+ this.initParts(core, data, embeddable, charts, expressions, usageCollection);
+ return {
+ attributeService: await this.attributeService!(),
+ capabilities: coreStart.application.capabilities,
+ coreHttp: coreStart.http,
+ timefilter: deps.data.query.timefilter.timefilter,
+ expressionRenderer: deps.expressions.ReactExpressionRenderer,
+ documentToExpression: this.editorFrameService!.documentToExpression,
+ indexPatternService: deps.data.indexPatterns,
+ uiActions: deps.uiActions,
usageCollection,
- },
- this.attributeService
- );
- const dependencies: IndexPatternDatasourceSetupPlugins &
- XyVisualizationPluginSetupPlugins &
- DatatableVisualizationPluginSetupPlugins &
- MetricVisualizationPluginSetupPlugins &
- PieVisualizationPluginSetupPlugins = {
- expressions,
- data,
- charts,
- editorFrame: editorFrameSetupInterface,
- formatFactory: core
- .getStartServices()
- .then(([_, { data: dataStart }]) => dataStart.fieldFormats.deserialize),
+ };
};
- this.indexpatternDatasource.setup(core, dependencies);
- this.xyVisualization.setup(core, dependencies);
- this.datatableVisualization.setup(core, dependencies);
- this.metricVisualization.setup(core, dependencies);
- this.pieVisualization.setup(core, dependencies);
- this.heatmapVisualization.setup(core, dependencies);
+
+ if (embeddable) {
+ embeddable.registerEmbeddableFactory('lens', new EmbeddableFactory(getStartServices));
+ }
visualizations.registerAlias(getLensAliasConfig());
@@ -217,6 +210,7 @@ export class LensPlugin {
title: NOT_INTERNATIONALIZED_PRODUCT_NAME,
navLinkStatus: AppNavLinkStatus.hidden,
mount: async (params: AppMountParameters) => {
+ await this.initParts(core, data, embeddable, charts, expressions, usageCollection);
const { mountApp, stopReportManager } = await import('./async_services');
this.stopReportManager = stopReportManager;
await ensureDefaultIndexPattern();
@@ -245,9 +239,62 @@ export class LensPlugin {
urlForwarding.forwardApp('lens', 'lens');
}
- start(core: CoreStart, startDependencies: LensPluginStartDependencies): LensPublicStart {
- const frameStart = this.editorFrameService.start(core, startDependencies);
+ private async initParts(
+ core: CoreSetup,
+ data: DataPublicPluginSetup,
+ embeddable: EmbeddableSetup | undefined,
+ charts: ChartsPluginSetup,
+ expressions: ExpressionsServiceSetup,
+ usageCollection: UsageCollectionSetup | undefined
+ ) {
+ const {
+ DatatableVisualization,
+ EditorFrameService,
+ IndexPatternDatasource,
+ XyVisualization,
+ MetricVisualization,
+ PieVisualization,
+ HeatmapVisualization,
+ } = await import('./async_services');
+ this.datatableVisualization = new DatatableVisualization();
+ this.editorFrameService = new EditorFrameService();
+ this.indexpatternDatasource = new IndexPatternDatasource();
+ this.xyVisualization = new XyVisualization();
+ this.metricVisualization = new MetricVisualization();
+ this.pieVisualization = new PieVisualization();
+ this.heatmapVisualization = new HeatmapVisualization();
+ const editorFrameSetupInterface = this.editorFrameService.setup(core, {
+ data,
+ embeddable,
+ charts,
+ expressions,
+ usageCollection,
+ });
+ const dependencies: IndexPatternDatasourceSetupPlugins &
+ XyVisualizationPluginSetupPlugins &
+ DatatableVisualizationPluginSetupPlugins &
+ MetricVisualizationPluginSetupPlugins &
+ PieVisualizationPluginSetupPlugins = {
+ expressions,
+ data,
+ charts,
+ editorFrame: editorFrameSetupInterface,
+ formatFactory: core
+ .getStartServices()
+ .then(([_, { data: dataStart }]) => dataStart.fieldFormats.deserialize),
+ };
+ this.indexpatternDatasource.setup(core, dependencies);
+ this.xyVisualization.setup(core, dependencies);
+ this.datatableVisualization.setup(core, dependencies);
+ this.metricVisualization.setup(core, dependencies);
+ this.pieVisualization.setup(core, dependencies);
+ this.heatmapVisualization.setup(core, dependencies);
+ const [coreStart, startDependencies] = await core.getStartServices();
+ const frameStart = this.editorFrameService.start(coreStart, startDependencies);
this.createEditorFrame = frameStart.createInstance;
+ }
+
+ start(core: CoreStart, startDependencies: LensPluginStartDependencies): LensPublicStart {
// unregisters the Visualize action and registers the lens one
if (startDependencies.uiActions.hasAction(ACTION_VISUALIZE_FIELD)) {
startDependencies.uiActions.unregisterAction(ACTION_VISUALIZE_FIELD);
diff --git a/x-pack/plugins/lens/public/search_provider.ts b/x-pack/plugins/lens/public/search_provider.ts
index 6baa3bcdb71635..4bc18f2653a0b1 100644
--- a/x-pack/plugins/lens/public/search_provider.ts
+++ b/x-pack/plugins/lens/public/search_provider.ts
@@ -5,7 +5,6 @@
* 2.0.
*/
-import levenshtein from 'js-levenshtein';
import { ApplicationStart } from 'kibana/public';
import { from, of } from 'rxjs';
import { i18n } from '@kbn/i18n';
@@ -52,15 +51,6 @@ export const getSearchProvider: (
score = 90;
} else if (searchableTitle.includes(term)) {
score = 75;
- } else {
- const length = Math.max(term.length, searchableTitle.length);
- const distance = levenshtein(term, searchableTitle);
-
- // maximum lev distance is length, we compute the match ratio (lower distance is better)
- const ratio = Math.floor((1 - distance / length) * 100);
- if (ratio >= 60) {
- score = ratio;
- }
}
if (score === 0) return [];
return [
From 3ac067fc914e2dbdae4e7c2af90e538ed54a5a51 Mon Sep 17 00:00:00 2001
From: Christos Nasikas
Date: Wed, 30 Jun 2021 18:08:04 +0300
Subject: [PATCH 063/121] [Cases] Swimlane: Fix bug when creating the connector
with empty mapping. (#103446)
---
.../swimlane/steps/swimlane_connection.tsx | 46 +----
.../swimlane/steps/swimlane_fields.tsx | 21 +--
.../swimlane/swimlane.tsx | 37 +++-
.../swimlane/swimlane_connectors.tsx | 175 ++++++++++++------
.../swimlane/translations.ts | 43 ++---
5 files changed, 167 insertions(+), 155 deletions(-)
diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/swimlane/steps/swimlane_connection.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/swimlane/steps/swimlane_connection.tsx
index 05b6d8d63f1cfb..2bf99ec9d62b68 100644
--- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/swimlane/steps/swimlane_connection.tsx
+++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/swimlane/steps/swimlane_connection.tsx
@@ -4,21 +4,12 @@
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
-import {
- EuiButton,
- EuiCallOut,
- EuiFieldText,
- EuiFormRow,
- EuiLink,
- EuiSpacer,
- EuiText,
-} from '@elastic/eui';
+import { EuiCallOut, EuiFieldText, EuiFormRow, EuiLink, EuiSpacer, EuiText } from '@elastic/eui';
import React, { useCallback } from 'react';
import { FormattedMessage } from '@kbn/i18n/react';
import * as i18n from '../translations';
import { useKibana } from '../../../../../common/lib/kibana';
-import { useGetApplication } from '../use_get_application';
-import { SwimlaneActionConnector, SwimlaneFieldMappingConfig } from '../types';
+import { SwimlaneActionConnector } from '../types';
import { IErrorObject } from '../../../../../types';
interface Props {
@@ -27,8 +18,6 @@ interface Props {
editActionSecrets: (property: string, value: any) => void;
errors: IErrorObject;
readOnly: boolean;
- updateCurrentStep: (step: number) => void;
- updateFields: (items: SwimlaneFieldMappingConfig[]) => void;
}
const SwimlaneConnectionComponent: React.FunctionComponent = ({
@@ -37,33 +26,10 @@ const SwimlaneConnectionComponent: React.FunctionComponent = ({
editActionSecrets,
errors,
readOnly,
- updateCurrentStep,
- updateFields,
}) => {
- const {
- notifications: { toasts },
- } = useKibana().services;
const { apiUrl, appId } = action.config;
const { apiToken } = action.secrets;
const { docLinks } = useKibana().services;
- const { getApplication } = useGetApplication({
- toastNotifications: toasts,
- apiToken,
- appId,
- apiUrl,
- });
- const isValid = apiUrl && apiToken && appId;
-
- const connectSwimlane = useCallback(async () => {
- // fetch swimlane application configuration
- const application = await getApplication();
-
- if (application?.fields) {
- const allFields = application.fields;
- updateFields(allFields);
- updateCurrentStep(2);
- }
- }, [getApplication, updateCurrentStep, updateFields]);
const onChangeConfig = useCallback(
(e: React.ChangeEvent, key: 'apiUrl' | 'appId') => {
@@ -186,14 +152,6 @@ const SwimlaneConnectionComponent: React.FunctionComponent = ({
/>
>
-
-
- {i18n.SW_RETRIEVE_CONFIGURATION_LABEL}
-
>
);
};
diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/swimlane/steps/swimlane_fields.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/swimlane/steps/swimlane_fields.tsx
index 87d0964322e140..fd3150c703b74d 100644
--- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/swimlane/steps/swimlane_fields.tsx
+++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/swimlane/steps/swimlane_fields.tsx
@@ -6,13 +6,7 @@
*/
import React, { useMemo, useCallback, useEffect, useRef } from 'react';
-import {
- EuiButton,
- EuiFormRow,
- EuiComboBox,
- EuiComboBoxOptionOption,
- EuiButtonGroup,
-} from '@elastic/eui';
+import { EuiFormRow, EuiComboBox, EuiComboBoxOptionOption, EuiButtonGroup } from '@elastic/eui';
import * as i18n from '../translations';
import {
SwimlaneActionConnector,
@@ -102,10 +96,6 @@ const SwimlaneFieldsComponent: React.FC = ({
[errors]
);
- const resetConnection = useCallback(() => {
- updateCurrentStep(1);
- }, [updateCurrentStep]);
-
const editMappings = useCallback(
(key: keyof SwimlaneMappingConfig, e: Array>) => {
if (e.length === 0) {
@@ -132,14 +122,6 @@ const SwimlaneFieldsComponent: React.FC = ({
[editActionConfig, fieldIdMap, mappings]
);
- /**
- * Connector type needs to be updated on mount to All.
- * Otherwise it is undefined and this will cause an error
- * if the user saves the connector without any mapping
- */
- // eslint-disable-next-line react-hooks/exhaustive-deps
- useEffect(() => editActionConfig('connectorType', connectorType), []);
-
useEffect(() => {
if (connectorType !== prevConnectorType.current) {
prevConnectorType.current = connectorType;
@@ -305,7 +287,6 @@ const SwimlaneFieldsComponent: React.FC = ({
>
)}
- {i18n.SW_CONFIGURE_API_LABEL}
>
);
};
diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/swimlane/swimlane.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/swimlane/swimlane.tsx
index 5e06e3935eebdd..5797017de7baff 100644
--- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/swimlane/swimlane.tsx
+++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/swimlane/swimlane.tsx
@@ -7,6 +7,7 @@
import { isEmpty } from 'lodash';
import { lazy } from 'react';
+import { i18n } from '@kbn/i18n';
import {
ActionTypeModel,
ConnectorValidationResult,
@@ -18,10 +19,23 @@ import {
SwimlaneSecrets,
SwimlaneActionParams,
} from './types';
-import * as i18n from './translations';
import { isValidUrl } from '../../../lib/value_validators';
import { validateMappingForConnector } from './helpers';
+export const SW_SELECT_MESSAGE_TEXT = i18n.translate(
+ 'xpack.triggersActionsUI.components.builtinActionTypes.swimlaneAction.selectMessageText',
+ {
+ defaultMessage: 'Create record in Swimlane',
+ }
+);
+
+export const SW_ACTION_TYPE_TITLE = i18n.translate(
+ 'xpack.triggersActionsUI.components.builtinActionTypes.swimlaneAction.actionTypeTitle',
+ {
+ defaultMessage: 'Create Swimlane Record',
+ }
+);
+
export function getActionType(): ActionTypeModel<
SwimlaneConfig,
SwimlaneSecrets,
@@ -30,11 +44,12 @@ export function getActionType(): ActionTypeModel<
return {
id: '.swimlane',
iconClass: lazy(() => import('./logo')),
- selectMessage: i18n.SW_SELECT_MESSAGE_TEXT,
- actionTypeTitle: i18n.SW_ACTION_TYPE_TITLE,
+ selectMessage: SW_SELECT_MESSAGE_TEXT,
+ actionTypeTitle: SW_ACTION_TYPE_TITLE,
validateConnector: async (
action: SwimlaneActionConnector
): Promise> => {
+ const translations = await import('./translations');
const configErrors = {
apiUrl: new Array(),
appId: new Array(),
@@ -51,19 +66,22 @@ export function getActionType(): ActionTypeModel<
};
if (!action.config.apiUrl) {
- configErrors.apiUrl = [...configErrors.apiUrl, i18n.SW_API_URL_REQUIRED];
+ configErrors.apiUrl = [...configErrors.apiUrl, translations.SW_API_URL_REQUIRED];
} else if (action.config.apiUrl) {
if (!isValidUrl(action.config.apiUrl)) {
- configErrors.apiUrl = [...configErrors.apiUrl, i18n.SW_API_URL_INVALID];
+ configErrors.apiUrl = [...configErrors.apiUrl, translations.SW_API_URL_INVALID];
}
}
if (!action.secrets.apiToken) {
- secretsErrors.apiToken = [...secretsErrors.apiToken, i18n.SW_REQUIRED_API_TOKEN_TEXT];
+ secretsErrors.apiToken = [
+ ...secretsErrors.apiToken,
+ translations.SW_REQUIRED_API_TOKEN_TEXT,
+ ];
}
if (!action.config.appId) {
- configErrors.appId = [...configErrors.appId, i18n.SW_REQUIRED_APP_ID_TEXT];
+ configErrors.appId = [...configErrors.appId, translations.SW_REQUIRED_APP_ID_TEXT];
}
const mappingErrors = validateMappingForConnector(
@@ -80,6 +98,7 @@ export function getActionType(): ActionTypeModel<
validateParams: async (
actionParams: SwimlaneActionParams
): Promise> => {
+ const translations = await import('./translations');
const errors = {
'subActionParams.incident.ruleName': new Array(),
'subActionParams.incident.alertId': new Array(),
@@ -91,11 +110,11 @@ export function getActionType(): ActionTypeModel<
const hasIncident = actionParams.subActionParams && actionParams.subActionParams.incident;
if (hasIncident && !actionParams.subActionParams.incident.ruleName?.length) {
- errors['subActionParams.incident.ruleName'].push(i18n.SW_REQUIRED_RULE_NAME);
+ errors['subActionParams.incident.ruleName'].push(translations.SW_REQUIRED_RULE_NAME);
}
if (hasIncident && !actionParams.subActionParams.incident.alertId?.length) {
- errors['subActionParams.incident.alertId'].push(i18n.SW_REQUIRED_ALERT_ID);
+ errors['subActionParams.incident.alertId'].push(translations.SW_REQUIRED_ALERT_ID);
}
return validationResult;
diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/swimlane/swimlane_connectors.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/swimlane/swimlane_connectors.tsx
index acf9f38e9ba48b..9fe94906d5ef8c 100644
--- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/swimlane/swimlane_connectors.tsx
+++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/swimlane/swimlane_connectors.tsx
@@ -5,94 +5,153 @@
* 2.0.
*/
-import React, { Fragment, useCallback, useMemo, useState } from 'react';
-import { EuiForm, EuiSpacer, EuiStepsHorizontal, EuiStepStatus } from '@elastic/eui';
-import * as i18n from './translations';
+import React, { Fragment, useCallback, useMemo, useState, useEffect } from 'react';
+import {
+ EuiForm,
+ EuiSpacer,
+ EuiStepsHorizontal,
+ EuiStepStatus,
+ EuiButton,
+ EuiFormRow,
+} from '@elastic/eui';
+import { useKibana } from '../../../../common/lib/kibana';
import { ActionConnectorFieldsProps } from '../../../../types';
-import { SwimlaneActionConnector, SwimlaneFieldMappingConfig } from './types';
+import {
+ SwimlaneActionConnector,
+ SwimlaneConnectorType,
+ SwimlaneFieldMappingConfig,
+} from './types';
import { SwimlaneConnection, SwimlaneFields } from './steps';
+import { useGetApplication } from './use_get_application';
+import * as i18n from './translations';
const SwimlaneActionConnectorFields: React.FunctionComponent<
ActionConnectorFieldsProps
> = ({ errors, action, editActionConfig, editActionSecrets, readOnly }) => {
+ const {
+ notifications: { toasts },
+ } = useKibana().services;
+
+ const { apiUrl, appId, mappings, connectorType } = action.config;
+ const { apiToken } = action.secrets;
+
+ const { getApplication, isLoading: isLoadingApplication } = useGetApplication({
+ toastNotifications: toasts,
+ apiToken,
+ appId,
+ apiUrl,
+ });
+
+ const hasConfigurationErrors =
+ errors.apiUrl?.length > 0 || errors.appId?.length > 0 || errors.apiToken?.length > 0;
+
const [currentStep, setCurrentStep] = useState(1);
- const [stepsStatuses, setStepsStatuses] = useState<{
- connection: EuiStepStatus;
- fields: EuiStepStatus;
- }>({ connection: 'incomplete', fields: 'incomplete' });
const [fields, setFields] = useState([]);
- const updateCurrentStep = useCallback(
- (step: number) => {
- setCurrentStep(step);
- if (step === 2) {
- setStepsStatuses((statuses) => ({ ...statuses, connection: 'complete' }));
- } else if (step === 1) {
- setStepsStatuses({
- fields: 'incomplete',
- connection: 'incomplete',
- });
- editActionConfig('mappings', action.config.mappings);
- }
- },
- [action.config.mappings, editActionConfig]
+ const updateCurrentStep = useCallback((step: number) => {
+ setCurrentStep(step);
+ }, []);
+
+ const onNextStep = useCallback(async () => {
+ // fetch swimlane application configuration
+ const application = await getApplication();
+
+ if (application?.fields) {
+ const allFields = application.fields;
+ setFields(allFields);
+ setCurrentStep(2);
+ }
+ }, [getApplication]);
+
+ const resetConnection = useCallback(() => {
+ setCurrentStep(1);
+ }, []);
+
+ const hasMappingErrors = useMemo(
+ () => Object.values(errors?.mappings ?? {}).some((mappingError) => mappingError.length !== 0),
+ [errors?.mappings]
);
- const setupSteps = useMemo(
+ const steps = useMemo(
() => [
{
title: i18n.SW_CONFIGURE_CONNECTION_LABEL,
- status: stepsStatuses.connection,
+ isSelected: currentStep === 1,
+ isComplete: currentStep === 2,
onClick: () => updateCurrentStep(1),
},
{
title: i18n.SW_MAPPING_TITLE_TEXT_FIELD_LABEL,
- disabled: stepsStatuses.connection !== 'complete',
- status: stepsStatuses.fields,
- onClick: () => updateCurrentStep(2),
+ disabled: hasConfigurationErrors || isLoadingApplication,
+ isSelected: currentStep === 2,
+ onClick: onNextStep,
+ status: hasMappingErrors ? ('danger' as EuiStepStatus) : undefined,
},
],
- [stepsStatuses.connection, stepsStatuses.fields, updateCurrentStep]
+ [
+ currentStep,
+ hasConfigurationErrors,
+ hasMappingErrors,
+ isLoadingApplication,
+ onNextStep,
+ updateCurrentStep,
+ ]
);
- const editActionConfigCb = useCallback(
- (k: string, v: string) => {
- editActionConfig(k, v);
- if (
- Object.values(errors?.mappings ?? {}).every((mappingError) => mappingError.length === 0)
- ) {
- setStepsStatuses((statuses) => ({ ...statuses, fields: 'complete' }));
- } else {
- setStepsStatuses((statuses) => ({ ...statuses, fields: 'incomplete' }));
- }
- },
- [editActionConfig, errors?.mappings]
- );
+ /**
+ * Connector type needs to be updated on mount to All.
+ * Otherwise it is undefined and this will cause an error
+ * if the user saves the connector without going to the
+ * second step. Same for mapping.
+ */
+ useEffect(() => {
+ editActionConfig('connectorType', connectorType ?? SwimlaneConnectorType.All);
+ editActionConfig('mappings', mappings ?? {});
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, []);
return (
-
+
{currentStep === 1 && (
-
+ <>
+
+
+
+
+ {i18n.SW_NEXT}
+
+
+ >
)}
{currentStep === 2 && (
-
+ <>
+
+
+ {i18n.SW_BACK}
+
+ >
)}
diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/swimlane/translations.ts b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/swimlane/translations.ts
index 726997cb4456a3..a1dbf88f929946 100644
--- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/swimlane/translations.ts
+++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/swimlane/translations.ts
@@ -7,20 +7,6 @@
import { i18n } from '@kbn/i18n';
-export const SW_SELECT_MESSAGE_TEXT = i18n.translate(
- 'xpack.triggersActionsUI.components.builtinActionTypes.swimlaneAction.selectMessageText',
- {
- defaultMessage: 'Create record in Swimlane',
- }
-);
-
-export const SW_ACTION_TYPE_TITLE = i18n.translate(
- 'xpack.triggersActionsUI.components.builtinActionTypes.swimlaneAction.actionTypeTitle',
- {
- defaultMessage: 'Create Swimlane Record',
- }
-);
-
export const SW_REQUIRED_RULE_NAME = i18n.translate(
'xpack.triggersActionsUI.components.builtinActionTypes.swimlaneAction.error.requiredRuleName',
{
@@ -190,11 +176,6 @@ export const SW_RETRIEVE_CONFIGURATION_LABEL = i18n.translate(
{ defaultMessage: 'Configure Fields' }
);
-export const SW_CONFIGURE_API_LABEL = i18n.translate(
- 'xpack.triggersActionsUI.components.builtinActionTypes.swimlaneAction.configureAPILabel',
- { defaultMessage: 'Configure API' }
-);
-
export const SW_CONNECTOR_TYPE_LABEL = i18n.translate(
'xpack.triggersActionsUI.components.builtinActionTypes.swimlaneAction.connectorType',
{
@@ -220,7 +201,7 @@ export const EMPTY_MAPPING_WARNING_DESC = i18n.translate(
'xpack.triggersActionsUI.components.builtinActionTypes.swimlaneAction.emptyMappingWarningDesc',
{
defaultMessage:
- 'This connector cannot be selected because it is missing the required case field mappings. You can edit this connector to add required field mappings or select a connector of type Alerts.',
+ 'This connector cannot be selected because it is missing the required alert field mappings. You can edit this connector to add required field mappings or select a connector of type Alerts.',
}
);
@@ -273,10 +254,24 @@ export const SW_REQUIRED_ALERT_ID = i18n.translate(
}
);
-export const SW_ALERT_SOURCE_TOOLTIP = i18n.translate(
- 'xpack.triggersActionsUI.components.builtinActionTypes.swimlaneAction.alertSourceTooltip',
+export const SW_BACK = i18n.translate(
+ 'xpack.triggersActionsUI.components.builtinActionTypes.swimlaneAction.prevStep',
{
- defaultMessage: 'The index of the alert. Use {index} in Detections.',
- values: { index: '{{context.rule.output_index}}' },
+ defaultMessage: 'Back',
+ }
+);
+
+export const SW_NEXT = i18n.translate(
+ 'xpack.triggersActionsUI.components.builtinActionTypes.swimlaneAction.nextStep',
+ {
+ defaultMessage: 'Next',
+ }
+);
+
+export const SW_FIELDS_BUTTON_HELP_TEXT = i18n.translate(
+ 'xpack.triggersActionsUI.components.builtinActionTypes.swimlaneAction.nextStepHelpText',
+ {
+ defaultMessage:
+ 'If field mappings are not configured, Swimlane connector type will be set to all.',
}
);
From 36b21b400706551b518401293409706fdb274d7c Mon Sep 17 00:00:00 2001
From: Davis Plumlee <56367316+dplumlee@users.noreply.github.com>
Date: Wed, 30 Jun 2021 11:17:52 -0400
Subject: [PATCH 064/121] [Security Solution] Invalid KQL Query Bug (#99442)
## Summary
Addresses #98283
Currently, our method of converting KQL to Elasticsearch queries silently suppresses errors bubbled up by ES and returns an empty query string. This makes it so the entire query, including filters, etc. gets wiped out and potentially incorrect data is displayed.
This PR addresses that by bubbling up the errors and putting them in a toast component as well as cancelling any request that was made with the invalid query so that incorrect data is never fetched.
![Screen Shot 2021-05-11 at 5 05 24 PM](https://user-images.githubusercontent.com/56367316/117895214-e8bf9500-b28b-11eb-83a6-522deebecbe2.png)
### Checklist
Delete any items that are not applicable to this PR.
- [x] [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
### 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)
---
.../draggable_wrapper_hover_content.test.tsx | 1 +
.../events_viewer/events_viewer.tsx | 5 +-
.../components/header_section/index.tsx | 9 ++-
.../components/matrix_histogram/index.tsx | 3 +
.../ml/tables/anomalies_host_table.tsx | 1 +
.../ml/tables/anomalies_network_table.tsx | 1 +
.../containers/matrix_histogram/index.ts | 2 +-
.../common/hooks/use_invalid_filter_query.tsx | 68 +++++++++++++++++++
.../public/common/lib/keury/index.ts | 31 +++++----
.../public/common/store/app/actions.ts | 7 ++
.../public/common/store/app/model.ts | 2 +
.../public/common/store/app/reducer.ts | 24 ++++++-
.../alerts_histogram_panel/index.tsx | 5 +-
.../components/alerts_table/index.tsx | 10 +++
.../hosts/components/kpi_hosts/types.ts | 2 +-
.../public/hosts/containers/hosts/index.tsx | 2 +-
.../hosts/pages/details/details_tabs.tsx | 2 +-
.../public/hosts/pages/details/index.tsx | 7 +-
.../public/hosts/pages/details/types.ts | 2 +-
.../public/hosts/pages/hosts.tsx | 12 ++--
.../public/hosts/pages/hosts_tabs.tsx | 2 +-
.../public/hosts/pages/navigation/types.ts | 2 +-
.../network/components/kpi_network/types.ts | 2 +-
.../network/containers/details/index.tsx | 2 +-
.../public/network/pages/details/index.tsx | 29 +++++---
.../public/network/pages/details/types.ts | 2 +-
.../public/network/pages/network.tsx | 12 ++--
.../components/alerts_by_category/index.tsx | 5 +-
.../components/event_counts/index.tsx | 15 +++-
.../components/events_by_dataset/index.tsx | 34 ++++++----
.../components/overview_host/index.tsx | 8 ++-
.../components/overview_network/index.tsx | 8 ++-
.../use_request_event_counts.ts | 38 ++++++-----
.../containers/overview_network/index.tsx | 4 +-
.../components/flyout/header/index.tsx | 45 ++++++++++--
.../network_details/expandable_network.tsx | 7 +-
.../components/timeline/helpers.test.tsx | 44 +++++++++---
.../timelines/components/timeline/helpers.tsx | 48 ++++++++++---
.../timeline/query_tab_content/index.tsx | 16 ++++-
39 files changed, 403 insertions(+), 116 deletions(-)
create mode 100644 x-pack/plugins/security_solution/public/common/hooks/use_invalid_filter_query.tsx
diff --git a/x-pack/plugins/security_solution/public/common/components/drag_and_drop/draggable_wrapper_hover_content.test.tsx b/x-pack/plugins/security_solution/public/common/components/drag_and_drop/draggable_wrapper_hover_content.test.tsx
index 400b178c167f68..2531780ec4bd5d 100644
--- a/x-pack/plugins/security_solution/public/common/components/drag_and_drop/draggable_wrapper_hover_content.test.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/drag_and_drop/draggable_wrapper_hover_content.test.tsx
@@ -50,6 +50,7 @@ jest.mock('../../../common/hooks/use_selector', () => ({
useShallowEqualSelector: jest.fn(),
useDeepEqualSelector: jest.fn(),
}));
+jest.mock('../../../common/hooks/use_invalid_filter_query.tsx');
const mockUiSettingsForFilterManager = coreMock.createStart().uiSettings;
const timelineId = TimelineId.active;
diff --git a/x-pack/plugins/security_solution/public/common/components/events_viewer/events_viewer.tsx b/x-pack/plugins/security_solution/public/common/components/events_viewer/events_viewer.tsx
index 5dadd740ae3bca..c2f170c58043d0 100644
--- a/x-pack/plugins/security_solution/public/common/components/events_viewer/events_viewer.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/events_viewer/events_viewer.tsx
@@ -49,9 +49,9 @@ import {
import { GraphOverlay } from '../../../timelines/components/graph_overlay';
import { CellValueElementProps } from '../../../timelines/components/timeline/cell_rendering';
import { SELECTOR_TIMELINE_GLOBAL_CONTAINER } from '../../../timelines/components/timeline/styles';
-import { defaultControlColumn } from '../../../timelines/components/timeline/body/control_columns';
import { timelineSelectors, timelineActions } from '../../../timelines/store/timeline';
import { useDeepEqualSelector } from '../../hooks/use_selector';
+import { defaultControlColumn } from '../../../timelines/components/timeline/body/control_columns';
export const EVENTS_VIEWER_HEADER_HEIGHT = 90; // px
const UTILITY_BAR_HEIGHT = 19; // px
@@ -243,7 +243,7 @@ const EventsViewerComponent: React.FC = ({
sort: sortField,
startDate: start,
endDate: end,
- skip: !canQueryTimeline,
+ skip: !canQueryTimeline || combinedQueries?.filterQuery === undefined, // When the filterQuery comes back as undefined, it means an error has been thrown and the request should be skipped
});
const totalCountMinusDeleted = useMemo(
@@ -296,6 +296,7 @@ const EventsViewerComponent: React.FC = ({
height={headerFilterGroup ? COMPACT_HEADER_HEIGHT : EVENTS_VIEWER_HEADER_HEIGHT}
subtitle={utilityBar ? undefined : subtitle}
title={globalFullScreen ? titleWithExitFullScreen : justTitle}
+ isInspectDisabled={combinedQueries!.filterQuery === undefined}
>
{HeaderSectionContent}
diff --git a/x-pack/plugins/security_solution/public/common/components/header_section/index.tsx b/x-pack/plugins/security_solution/public/common/components/header_section/index.tsx
index fb8022292d3296..fe32b7addd25ef 100644
--- a/x-pack/plugins/security_solution/public/common/components/header_section/index.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/header_section/index.tsx
@@ -41,6 +41,7 @@ export interface HeaderSectionProps extends HeaderProps {
children?: React.ReactNode;
height?: number;
id?: string;
+ isInspectDisabled?: boolean;
split?: boolean;
subtitle?: string | React.ReactNode;
title: string | React.ReactNode;
@@ -55,6 +56,7 @@ const HeaderSectionComponent: React.FC = ({
children,
height,
id,
+ isInspectDisabled,
split,
subtitle,
title,
@@ -85,7 +87,12 @@ const HeaderSectionComponent: React.FC = ({
{id && (
-
+
)}
diff --git a/x-pack/plugins/security_solution/public/common/components/matrix_histogram/index.tsx b/x-pack/plugins/security_solution/public/common/components/matrix_histogram/index.tsx
index 0a80ff0045cd10..68a1ff14f2f0be 100644
--- a/x-pack/plugins/security_solution/public/common/components/matrix_histogram/index.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/matrix_histogram/index.tsx
@@ -91,6 +91,7 @@ export const MatrixHistogramComponent: React.FC =
title,
titleSize,
yTickFormatter,
+ skip,
}) => {
const dispatch = useDispatch();
const handleBrushEnd = useCallback(
@@ -146,6 +147,7 @@ export const MatrixHistogramComponent: React.FC =
stackByField: selectedStackByOption.value,
isPtrIncluded,
docValueFields,
+ skip,
};
const [loading, { data, inspect, totalCount, refetch }] = useMatrixHistogramCombined(
@@ -216,6 +218,7 @@ export const MatrixHistogramComponent: React.FC =
titleSize={titleSize}
subtitle={subtitleWithCounts}
inspectMultiple
+ isInspectDisabled={filterQuery === undefined}
>
diff --git a/x-pack/plugins/security_solution/public/common/components/ml/tables/anomalies_host_table.tsx b/x-pack/plugins/security_solution/public/common/components/ml/tables/anomalies_host_table.tsx
index dd2f84221f5f09..49a4f22ef0a75e 100644
--- a/x-pack/plugins/security_solution/public/common/components/ml/tables/anomalies_host_table.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/ml/tables/anomalies_host_table.tsx
@@ -75,6 +75,7 @@ const AnomaliesHostTableComponent: React.FC = ({
)}`}
title={i18n.ANOMALIES}
tooltip={i18n.TOOLTIP}
+ isInspectDisabled={skip}
/>
= ({
)}`}
title={i18n.ANOMALIES}
tooltip={i18n.TOOLTIP}
+ isInspectDisabled={skip}
/>
(() => mainLoading || missingDataLoading, [
diff --git a/x-pack/plugins/security_solution/public/common/hooks/use_invalid_filter_query.tsx b/x-pack/plugins/security_solution/public/common/hooks/use_invalid_filter_query.tsx
new file mode 100644
index 00000000000000..e03efcac4bbf68
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/common/hooks/use_invalid_filter_query.tsx
@@ -0,0 +1,68 @@
+/*
+ * 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 { useEffect, useMemo } from 'react';
+import { useDispatch } from 'react-redux';
+import { Query } from 'src/plugins/data/public';
+import { appSelectors } from '../store';
+import { appActions } from '../store/app';
+import { useAppToasts } from './use_app_toasts';
+import { useDeepEqualSelector } from './use_selector';
+
+/**
+ * Adds a toast error message whenever invalid KQL is submitted through the search bar
+ */
+export const useInvalidFilterQuery = ({
+ id,
+ filterQuery,
+ kqlError,
+ query,
+ startDate,
+ endDate,
+}: {
+ id: string;
+ filterQuery?: string;
+ kqlError?: Error;
+ query: Query;
+ startDate: string;
+ endDate: string;
+}) => {
+ const { addError } = useAppToasts();
+ const dispatch = useDispatch();
+ const getErrorsSelector = useMemo(() => appSelectors.errorsSelector(), []);
+ const errors = useDeepEqualSelector(getErrorsSelector);
+
+ useEffect(() => {
+ if (filterQuery === undefined && kqlError != null) {
+ // Local util for creating an replicatable error hash
+ const hashCode = kqlError.message
+ .split('')
+ // eslint-disable-next-line no-bitwise
+ .reduce((a, b) => ((a << 5) - a + b.charCodeAt(0)) | 0, 0)
+ .toString();
+ dispatch(
+ appActions.addErrorHash({
+ id,
+ hash: hashCode,
+ title: kqlError.name,
+ message: [kqlError.message],
+ })
+ );
+ }
+ // This disable is required to only trigger the toast once per render
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [id, filterQuery, addError, query, startDate, endDate]);
+
+ useEffect(() => {
+ const myError = errors.find((e) => e.id === id);
+ if (myError != null && myError.displayError && kqlError != null) {
+ // Removes error stack from user view
+ delete kqlError.stack; // Mutates the error object and can possibly lead to side effects, only going this route for type issues. Change when we add a stackless toast error
+ addError(kqlError, { title: kqlError.name });
+ }
+ }, [addError, errors, id, kqlError]);
+};
diff --git a/x-pack/plugins/security_solution/public/common/lib/keury/index.ts b/x-pack/plugins/security_solution/public/common/lib/keury/index.ts
index a71524f9e02a86..13db6e94d2eeae 100644
--- a/x-pack/plugins/security_solution/public/common/lib/keury/index.ts
+++ b/x-pack/plugins/security_solution/public/common/lib/keury/index.ts
@@ -80,20 +80,23 @@ export const convertToBuildEsQuery = ({
indexPattern: IIndexPattern;
queries: Query[];
filters: Filter[];
-}) => {
+}): [string, undefined] | [undefined, Error] => {
try {
- return JSON.stringify(
- esQuery.buildEsQuery(
- indexPattern,
- queries,
- filters.filter((f) => f.meta.disabled === false),
- {
- ...config,
- dateFormatTZ: undefined,
- }
- )
- );
- } catch (exp) {
- return '';
+ return [
+ JSON.stringify(
+ esQuery.buildEsQuery(
+ indexPattern,
+ queries,
+ filters.filter((f) => f.meta.disabled === false),
+ {
+ ...config,
+ dateFormatTZ: undefined,
+ }
+ )
+ ),
+ undefined,
+ ];
+ } catch (error) {
+ return [undefined, error];
}
};
diff --git a/x-pack/plugins/security_solution/public/common/store/app/actions.ts b/x-pack/plugins/security_solution/public/common/store/app/actions.ts
index 316134c5b66722..a262b053d706cf 100644
--- a/x-pack/plugins/security_solution/public/common/store/app/actions.ts
+++ b/x-pack/plugins/security_solution/public/common/store/app/actions.ts
@@ -20,3 +20,10 @@ export const addError = actionCreator<{ id: string; title: string; message: stri
);
export const removeError = actionCreator<{ id: string }>('REMOVE_ERRORS');
+
+export const addErrorHash = actionCreator<{
+ id: string;
+ hash: string;
+ title: string;
+ message: string[];
+}>('ADD_ERROR_HASH');
diff --git a/x-pack/plugins/security_solution/public/common/store/app/model.ts b/x-pack/plugins/security_solution/public/common/store/app/model.ts
index 5a252e4aa48f20..2888867167c144 100644
--- a/x-pack/plugins/security_solution/public/common/store/app/model.ts
+++ b/x-pack/plugins/security_solution/public/common/store/app/model.ts
@@ -18,6 +18,8 @@ export interface Error {
id: string;
title: string;
message: string[];
+ hash?: string;
+ displayError?: boolean;
}
export type ErrorModel = Error[];
diff --git a/x-pack/plugins/security_solution/public/common/store/app/reducer.ts b/x-pack/plugins/security_solution/public/common/store/app/reducer.ts
index c55c83b5e5f010..20c9b0e14dbd91 100644
--- a/x-pack/plugins/security_solution/public/common/store/app/reducer.ts
+++ b/x-pack/plugins/security_solution/public/common/store/app/reducer.ts
@@ -9,7 +9,7 @@ import { reducerWithInitialState } from 'typescript-fsa-reducers';
import { Note } from '../../lib/note';
-import { addError, addNotes, removeError, updateNote } from './actions';
+import { addError, addErrorHash, addNotes, removeError, updateNote } from './actions';
import { AppModel, NotesById } from './model';
export type AppState = AppModel;
@@ -46,4 +46,26 @@ export const appReducer = reducerWithInitialState(initialAppState)
...state,
errors: state.errors.filter((error) => error.id !== id),
}))
+ .case(addErrorHash, (state, { id, hash, title, message }) => {
+ const errorIdx = state.errors.findIndex((e) => e.id === id);
+ const errorObj = state.errors.find((e) => e.id === id) || { id, title, message };
+ if (errorIdx === -1) {
+ return {
+ ...state,
+ errors: state.errors.concat({
+ ...errorObj,
+ hash,
+ displayError: !state.errors.some((e) => e.hash === hash),
+ }),
+ };
+ }
+ return {
+ ...state,
+ errors: [
+ ...state.errors.slice(0, errorIdx),
+ { ...errorObj, hash, displayError: !state.errors.some((e) => e.hash === hash) },
+ ...state.errors.slice(errorIdx + 1),
+ ],
+ };
+ })
.build();
diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_histogram_panel/index.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_histogram_panel/index.tsx
index 8a328c14726c93..482032e6b4cbf7 100644
--- a/x-pack/plugins/security_solution/public/detections/components/alerts_histogram_panel/index.tsx
+++ b/x-pack/plugins/security_solution/public/detections/components/alerts_histogram_panel/index.tsx
@@ -129,6 +129,7 @@ export const AlertsHistogramPanel = memo(
// create a unique, but stable (across re-renders) query id
const uniqueQueryId = useMemo(() => `${DETECTIONS_HISTOGRAM_ID}-${uuid.v4()}`, []);
const [isInitialLoading, setIsInitialLoading] = useState(true);
+ const [isInspectDisabled, setIsInspectDisabled] = useState(false);
const [defaultNumberFormat] = useUiSetting$(DEFAULT_NUMBER_FORMAT);
const [totalAlertsObj, setTotalAlertsObj] = useState(defaultTotalAlertsObj);
const [selectedStackByOption, setSelectedStackByOption] = useState(
@@ -261,7 +262,7 @@ export const AlertsHistogramPanel = memo(
}
);
}
-
+ setIsInspectDisabled(false);
setAlertsQuery(
getAlertsHistogramQuery(
selectedStackByOption.value,
@@ -271,6 +272,7 @@ export const AlertsHistogramPanel = memo(
)
);
} catch (e) {
+ setIsInspectDisabled(true);
setAlertsQuery(getAlertsHistogramQuery(selectedStackByOption.value, from, to, []));
}
// eslint-disable-next-line react-hooks/exhaustive-deps
@@ -305,6 +307,7 @@ export const AlertsHistogramPanel = memo(
title={titleText}
titleSize={onlyField == null ? 'm' : 's'}
subtitle={!isInitialLoading && showTotalAlertsCount && totalAlerts}
+ isInspectDisabled={isInspectDisabled}
>
diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.tsx
index 7980160fea76cd..0e32df851592d3 100644
--- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.tsx
+++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.tsx
@@ -51,6 +51,7 @@ import { useSourcererScope } from '../../../common/containers/sourcerer';
import { buildTimeRangeFilter } from './helpers';
import { defaultRowRenderers } from '../../../timelines/components/timeline/body/renderers';
import { columns, RenderCellValue } from '../../configurations/security_solution_detections';
+import { useInvalidFilterQuery } from '../../../common/hooks/use_invalid_filter_query';
interface OwnProps {
defaultFilters?: Filter[];
@@ -132,6 +133,15 @@ export const AlertsTableComponent: React.FC = ({
[browserFields, defaultFilters, globalFilters, globalQuery, indexPatterns, kibana, to, from]
);
+ useInvalidFilterQuery({
+ id: timelineId,
+ filterQuery: getGlobalQuery([])?.filterQuery,
+ kqlError: getGlobalQuery([])?.kqlError,
+ query: globalQuery,
+ startDate: from,
+ endDate: to,
+ });
+
const setEventsLoadingCallback = useCallback(
({ eventIds, isLoading }: SetEventsLoadingProps) => {
setEventsLoading!({ id: timelineId, eventIds, isLoading });
diff --git a/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/types.ts b/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/types.ts
index 71b4c2322c1fb4..031e2c905fda20 100644
--- a/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/types.ts
+++ b/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/types.ts
@@ -9,7 +9,7 @@ import { UpdateDateRange } from '../../../common/components/charts/common';
import { GlobalTimeArgs } from '../../../common/containers/use_global_time';
export interface HostsKpiProps {
- filterQuery: string;
+ filterQuery?: string;
from: string;
to: string;
indexNames: string[];
diff --git a/x-pack/plugins/security_solution/public/hosts/containers/hosts/index.tsx b/x-pack/plugins/security_solution/public/hosts/containers/hosts/index.tsx
index 6244427b45d11b..1a9e86755cf7dc 100644
--- a/x-pack/plugins/security_solution/public/hosts/containers/hosts/index.tsx
+++ b/x-pack/plugins/security_solution/public/hosts/containers/hosts/index.tsx
@@ -33,7 +33,7 @@ import { InspectResponse } from '../../../types';
import { useTransforms } from '../../../transforms/containers/use_transforms';
import { useAppToasts } from '../../../common/hooks/use_app_toasts';
-const ID = 'hostsAllQuery';
+export const ID = 'hostsAllQuery';
type LoadPage = (newActivePage: number) => void;
export interface HostsArgs {
diff --git a/x-pack/plugins/security_solution/public/hosts/pages/details/details_tabs.tsx b/x-pack/plugins/security_solution/public/hosts/pages/details/details_tabs.tsx
index bb51a353f4706e..dc537f2f6ffe35 100644
--- a/x-pack/plugins/security_solution/public/hosts/pages/details/details_tabs.tsx
+++ b/x-pack/plugins/security_solution/public/hosts/pages/details/details_tabs.tsx
@@ -70,7 +70,7 @@ export const HostDetailsTabs = React.memo(
deleteQuery,
endDate: to,
filterQuery,
- skip: isInitializing,
+ skip: isInitializing || filterQuery === undefined,
setQuery,
startDate: from,
type,
diff --git a/x-pack/plugins/security_solution/public/hosts/pages/details/index.tsx b/x-pack/plugins/security_solution/public/hosts/pages/details/index.tsx
index 22edd2c19d6bd2..6e371bbf610e1e 100644
--- a/x-pack/plugins/security_solution/public/hosts/pages/details/index.tsx
+++ b/x-pack/plugins/security_solution/public/hosts/pages/details/index.tsx
@@ -49,8 +49,9 @@ import { TimelineId } from '../../../../common/types/timeline';
import { timelineDefaults } from '../../../timelines/store/timeline/defaults';
import { useSourcererScope } from '../../../common/containers/sourcerer';
import { useDeepEqualSelector, useShallowEqualSelector } from '../../../common/hooks/use_selector';
-import { useHostDetails } from '../../containers/hosts/details';
+import { ID, useHostDetails } from '../../containers/hosts/details';
import { manageQuery } from '../../../common/components/page/manage_query';
+import { useInvalidFilterQuery } from '../../../common/hooks/use_invalid_filter_query';
const HostOverviewManage = manageQuery(HostOverview);
@@ -103,13 +104,15 @@ const HostDetailsComponent: React.FC = ({ detailName, hostDeta
indexNames: selectedPatterns,
skip: selectedPatterns.length === 0,
});
- const filterQuery = convertToBuildEsQuery({
+ const [filterQuery, kqlError] = convertToBuildEsQuery({
config: esQuery.getEsQueryConfig(kibana.services.uiSettings),
indexPattern,
queries: [query],
filters: getFilters(),
});
+ useInvalidFilterQuery({ id: ID, filterQuery, kqlError, query, startDate: from, endDate: to });
+
useEffect(() => {
dispatch(setHostDetailsTablesActivePageToZero());
}, [dispatch, detailName]);
diff --git a/x-pack/plugins/security_solution/public/hosts/pages/details/types.ts b/x-pack/plugins/security_solution/public/hosts/pages/details/types.ts
index 447fba4cd182ae..21924fe9293207 100644
--- a/x-pack/plugins/security_solution/public/hosts/pages/details/types.ts
+++ b/x-pack/plugins/security_solution/public/hosts/pages/details/types.ts
@@ -61,7 +61,7 @@ export type HostDetailsTabsProps = HostBodyComponentDispatchProps &
docValueFields?: DocValueFields[];
indexNames: string[];
pageFilters?: Filter[];
- filterQuery: string;
+ filterQuery?: string;
indexPattern: IIndexPattern;
type: hostsModel.HostsType;
};
diff --git a/x-pack/plugins/security_solution/public/hosts/pages/hosts.tsx b/x-pack/plugins/security_solution/public/hosts/pages/hosts.tsx
index 7d31d291e75f17..f647819798ab77 100644
--- a/x-pack/plugins/security_solution/public/hosts/pages/hosts.tsx
+++ b/x-pack/plugins/security_solution/public/hosts/pages/hosts.tsx
@@ -52,6 +52,8 @@ import { timelineSelectors } from '../../timelines/store/timeline';
import { timelineDefaults } from '../../timelines/store/timeline/defaults';
import { useSourcererScope } from '../../common/containers/sourcerer';
import { useDeepEqualSelector, useShallowEqualSelector } from '../../common/hooks/use_selector';
+import { useInvalidFilterQuery } from '../../common/hooks/use_invalid_filter_query';
+import { ID } from '../containers/hosts';
/**
* Need a 100% height here to account for the graph/analyze tool, which sets no explicit height parameters, but fills the available space.
@@ -110,7 +112,7 @@ const HostsComponent = () => {
[dispatch]
);
const { docValueFields, indicesExist, indexPattern, selectedPatterns } = useSourcererScope();
- const filterQuery = useMemo(
+ const [filterQuery, kqlError] = useMemo(
() =>
convertToBuildEsQuery({
config: esQuery.getEsQueryConfig(uiSettings),
@@ -120,7 +122,7 @@ const HostsComponent = () => {
}),
[filters, indexPattern, uiSettings, query]
);
- const tabsFilterQuery = useMemo(
+ const [tabsFilterQuery] = useMemo(
() =>
convertToBuildEsQuery({
config: esQuery.getEsQueryConfig(uiSettings),
@@ -131,6 +133,8 @@ const HostsComponent = () => {
[indexPattern, query, tabsFilters, uiSettings]
);
+ useInvalidFilterQuery({ id: ID, filterQuery, kqlError, query, startDate: from, endDate: to });
+
const onSkipFocusBeforeEventsTable = useCallback(() => {
containerElement.current
?.querySelector('.inspectButtonComponent:last-of-type')
@@ -183,7 +187,7 @@ const HostsComponent = () => {
from={from}
setQuery={setQuery}
to={to}
- skip={isInitializing}
+ skip={isInitializing || !filterQuery}
narrowDateRange={narrowDateRange}
/>
@@ -200,7 +204,7 @@ const HostsComponent = () => {
deleteQuery={deleteQuery}
docValueFields={docValueFields}
to={to}
- filterQuery={tabsFilterQuery}
+ filterQuery={tabsFilterQuery || ''}
isInitializing={isInitializing}
indexNames={selectedPatterns}
setAbsoluteRangeDatePicker={setAbsoluteRangeDatePicker}
diff --git a/x-pack/plugins/security_solution/public/hosts/pages/hosts_tabs.tsx b/x-pack/plugins/security_solution/public/hosts/pages/hosts_tabs.tsx
index 876730a8f66c49..7d7288c8783699 100644
--- a/x-pack/plugins/security_solution/public/hosts/pages/hosts_tabs.tsx
+++ b/x-pack/plugins/security_solution/public/hosts/pages/hosts_tabs.tsx
@@ -69,7 +69,7 @@ export const HostsTabs = memo(
endDate: to,
filterQuery,
indexNames,
- skip: isInitializing,
+ skip: isInitializing || filterQuery === undefined,
setQuery,
startDate: from,
type,
diff --git a/x-pack/plugins/security_solution/public/hosts/pages/navigation/types.ts b/x-pack/plugins/security_solution/public/hosts/pages/navigation/types.ts
index a35e93812cbcac..c051d85f055632 100644
--- a/x-pack/plugins/security_solution/public/hosts/pages/navigation/types.ts
+++ b/x-pack/plugins/security_solution/public/hosts/pages/navigation/types.ts
@@ -44,7 +44,7 @@ export type HostsComponentsQueryProps = QueryTabBodyProps & {
};
export type AlertsComponentQueryProps = HostsComponentsQueryProps & {
- filterQuery: string;
+ filterQuery?: string;
pageFilters?: Filter[];
};
diff --git a/x-pack/plugins/security_solution/public/network/components/kpi_network/types.ts b/x-pack/plugins/security_solution/public/network/components/kpi_network/types.ts
index ff6f40b8c3a67e..3be0177557712d 100644
--- a/x-pack/plugins/security_solution/public/network/components/kpi_network/types.ts
+++ b/x-pack/plugins/security_solution/public/network/components/kpi_network/types.ts
@@ -9,7 +9,7 @@ import { UpdateDateRange } from '../../../common/components/charts/common';
import { GlobalTimeArgs } from '../../../common/containers/use_global_time';
export interface NetworkKpiProps {
- filterQuery: string;
+ filterQuery?: string;
from: string;
indexNames: string[];
to: string;
diff --git a/x-pack/plugins/security_solution/public/network/containers/details/index.tsx b/x-pack/plugins/security_solution/public/network/containers/details/index.tsx
index cf7d8e05858d5f..2565b9d8d54485 100644
--- a/x-pack/plugins/security_solution/public/network/containers/details/index.tsx
+++ b/x-pack/plugins/security_solution/public/network/containers/details/index.tsx
@@ -26,7 +26,7 @@ import { getInspectResponse } from '../../../helpers';
import { InspectResponse } from '../../../types';
import { useAppToasts } from '../../../common/hooks/use_app_toasts';
-const ID = 'networkDetailsQuery';
+export const ID = 'networkDetailsQuery';
export interface NetworkDetailsArgs {
id: string;
diff --git a/x-pack/plugins/security_solution/public/network/pages/details/index.tsx b/x-pack/plugins/security_solution/public/network/pages/details/index.tsx
index 02be5f78261c1d..10588b449473ae 100644
--- a/x-pack/plugins/security_solution/public/network/pages/details/index.tsx
+++ b/x-pack/plugins/security_solution/public/network/pages/details/index.tsx
@@ -29,7 +29,7 @@ import { FlowTargetSelectConnected } from '../../components/flow_target_select_c
import { IpOverview } from '../../components/details';
import { SiemSearchBar } from '../../../common/components/search_bar';
import { SecuritySolutionPageWrapper } from '../../../common/components/page_wrapper';
-import { useNetworkDetails } from '../../containers/details';
+import { useNetworkDetails, ID } from '../../containers/details';
import { useKibana } from '../../../common/lib/kibana';
import { decodeIpv6 } from '../../../common/lib/helpers';
import { convertToBuildEsQuery } from '../../../common/lib/keury';
@@ -49,6 +49,7 @@ import { esQuery } from '../../../../../../../src/plugins/data/public';
import { networkModel } from '../../store';
import { SecurityPageName } from '../../../app/types';
import { useSourcererScope } from '../../../common/containers/sourcerer';
+import { useInvalidFilterQuery } from '../../../common/hooks/use_invalid_filter_query';
export { getBreadcrumbs } from './utils';
const NetworkDetailsManage = manageQuery(IpOverview);
@@ -93,13 +94,15 @@ const NetworkDetailsComponent: React.FC = () => {
const { docValueFields, indicesExist, indexPattern, selectedPatterns } = useSourcererScope();
const ip = decodeIpv6(detailName);
- const filterQuery = convertToBuildEsQuery({
+ const [filterQuery, kqlError] = convertToBuildEsQuery({
config: esQuery.getEsQueryConfig(uiSettings),
indexPattern,
queries: [query],
filters,
});
+ useInvalidFilterQuery({ id: ID, filterQuery, kqlError, query, startDate: from, endDate: to });
+
const [loading, { id, inspect, networkDetails, refetch }] = useNetworkDetails({
docValueFields,
skip: isInitializing,
@@ -120,6 +123,12 @@ const NetworkDetailsComponent: React.FC = () => {
ip,
]);
+ // When the filterQuery comes back as undefined, it means an error has been thrown and the request should be skipped
+ const shouldSkip = useMemo(() => isInitializing || filterQuery === undefined, [
+ isInitializing,
+ filterQuery,
+ ]);
+
return (
{indicesExist ? (
@@ -174,7 +183,7 @@ const NetworkDetailsComponent: React.FC = () => {
flowTarget={FlowTargetSourceDest.source}
indexNames={selectedPatterns}
ip={ip}
- skip={isInitializing}
+ skip={shouldSkip}
startDate={from}
type={type}
setQuery={setQuery}
@@ -189,7 +198,7 @@ const NetworkDetailsComponent: React.FC = () => {
filterQuery={filterQuery}
indexNames={selectedPatterns}
ip={ip}
- skip={isInitializing}
+ skip={shouldSkip}
startDate={from}
type={type}
setQuery={setQuery}
@@ -208,7 +217,7 @@ const NetworkDetailsComponent: React.FC = () => {
flowTarget={FlowTargetSourceDest.source}
indexNames={selectedPatterns}
ip={ip}
- skip={isInitializing}
+ skip={shouldSkip}
startDate={from}
type={type}
setQuery={setQuery}
@@ -223,7 +232,7 @@ const NetworkDetailsComponent: React.FC = () => {
filterQuery={filterQuery}
indexNames={selectedPatterns}
ip={ip}
- skip={isInitializing}
+ skip={shouldSkip}
startDate={from}
type={type}
setQuery={setQuery}
@@ -240,7 +249,7 @@ const NetworkDetailsComponent: React.FC = () => {
flowTarget={flowTarget}
indexNames={selectedPatterns}
ip={ip}
- skip={isInitializing}
+ skip={shouldSkip}
startDate={from}
type={type}
setQuery={setQuery}
@@ -253,7 +262,7 @@ const NetworkDetailsComponent: React.FC = () => {
filterQuery={filterQuery}
indexNames={selectedPatterns}
ip={ip}
- skip={isInitializing}
+ skip={shouldSkip}
startDate={from}
type={type}
setQuery={setQuery}
@@ -268,7 +277,7 @@ const NetworkDetailsComponent: React.FC = () => {
indexNames={selectedPatterns}
ip={ip}
setQuery={setQuery}
- skip={isInitializing}
+ skip={shouldSkip}
startDate={from}
type={type}
/>
@@ -280,7 +289,7 @@ const NetworkDetailsComponent: React.FC = () => {
setQuery={setQuery}
startDate={from}
endDate={to}
- skip={isInitializing}
+ skip={shouldSkip}
indexNames={selectedPatterns}
ip={ip}
type={type}
diff --git a/x-pack/plugins/security_solution/public/network/pages/details/types.ts b/x-pack/plugins/security_solution/public/network/pages/details/types.ts
index 759373d495f53b..b91a22cbd5fc32 100644
--- a/x-pack/plugins/security_solution/public/network/pages/details/types.ts
+++ b/x-pack/plugins/security_solution/public/network/pages/details/types.ts
@@ -21,7 +21,7 @@ export interface OwnProps {
type: NetworkType;
startDate: string;
endDate: string;
- filterQuery: string | ESTermQuery;
+ filterQuery?: string | ESTermQuery;
ip: string;
indexNames: string[];
skip: boolean;
diff --git a/x-pack/plugins/security_solution/public/network/pages/network.tsx b/x-pack/plugins/security_solution/public/network/pages/network.tsx
index b08a75215a4088..928570417c524f 100644
--- a/x-pack/plugins/security_solution/public/network/pages/network.tsx
+++ b/x-pack/plugins/security_solution/public/network/pages/network.tsx
@@ -51,7 +51,7 @@ import { TimelineId } from '../../../common/types/timeline';
import { timelineDefaults } from '../../timelines/store/timeline/defaults';
import { useSourcererScope } from '../../common/containers/sourcerer';
import { useDeepEqualSelector, useShallowEqualSelector } from '../../common/hooks/use_selector';
-
+import { useInvalidFilterQuery } from '../../common/hooks/use_invalid_filter_query';
/**
* Need a 100% height here to account for the graph/analyze tool, which sets no explicit height parameters, but fills the available space.
*/
@@ -61,6 +61,8 @@ const StyledFullHeightContainer = styled.div`
flex: 1 1 auto;
`;
+const ID = 'NetworkQueryId';
+
const NetworkComponent = React.memo
(
({ hasMlUserPermissions, capabilitiesFetched }) => {
const dispatch = useDispatch();
@@ -133,19 +135,21 @@ const NetworkComponent = React.memo(
[containerElement, onSkipFocusBeforeEventsTable, onSkipFocusAfterEventsTable]
);
- const filterQuery = convertToBuildEsQuery({
+ const [filterQuery, kqlError] = convertToBuildEsQuery({
config: esQuery.getEsQueryConfig(kibana.services.uiSettings),
indexPattern,
queries: [query],
filters,
});
- const tabsFilterQuery = convertToBuildEsQuery({
+ const [tabsFilterQuery] = convertToBuildEsQuery({
config: esQuery.getEsQueryConfig(kibana.services.uiSettings),
indexPattern,
queries: [query],
filters: tabsFilters,
});
+ useInvalidFilterQuery({ id: ID, filterQuery, kqlError, query, startDate: from, endDate: to });
+
return (
<>
{indicesExist ? (
@@ -184,7 +188,7 @@ const NetworkComponent = React.memo(
indexNames={selectedPatterns}
narrowDateRange={narrowDateRange}
setQuery={setQuery}
- skip={isInitializing}
+ skip={isInitializing || filterQuery === undefined}
to={to}
/>
diff --git a/x-pack/plugins/security_solution/public/overview/components/alerts_by_category/index.tsx b/x-pack/plugins/security_solution/public/overview/components/alerts_by_category/index.tsx
index 98874c25e0ef8c..11270fe377733e 100644
--- a/x-pack/plugins/security_solution/public/overview/components/alerts_by_category/index.tsx
+++ b/x-pack/plugins/security_solution/public/overview/components/alerts_by_category/index.tsx
@@ -33,6 +33,7 @@ import { GlobalTimeArgs } from '../../../common/containers/use_global_time';
import { SecurityPageName } from '../../../app/types';
import { useFormatUrl } from '../../../common/components/link_to';
import { LinkButton } from '../../../common/components/links';
+import { useInvalidFilterQuery } from '../../../common/hooks/use_invalid_filter_query';
const ID = 'alertsByCategoryOverview';
@@ -101,7 +102,7 @@ const AlertsByCategoryComponent: React.FC = ({
[]
);
- const filterQuery = useMemo(
+ const [filterQuery, kqlError] = useMemo(
() =>
convertToBuildEsQuery({
config: esQuery.getEsQueryConfig(uiSettings),
@@ -112,6 +113,8 @@ const AlertsByCategoryComponent: React.FC = ({
[filters, indexPattern, uiSettings, query]
);
+ useInvalidFilterQuery({ id: ID, filterQuery, kqlError, query, startDate: from, endDate: to });
+
useEffect(() => {
return () => {
if (deleteQuery) {
diff --git a/x-pack/plugins/security_solution/public/overview/components/event_counts/index.tsx b/x-pack/plugins/security_solution/public/overview/components/event_counts/index.tsx
index 24c290edd4527f..919057d6b5eab4 100644
--- a/x-pack/plugins/security_solution/public/overview/components/event_counts/index.tsx
+++ b/x-pack/plugins/security_solution/public/overview/components/event_counts/index.tsx
@@ -9,6 +9,7 @@ import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
import React, { useMemo } from 'react';
import styled from 'styled-components';
+import { ID as OverviewHostQueryId } from '../../containers/overview_host';
import { OverviewHost } from '../overview_host';
import { OverviewNetwork } from '../overview_network';
import { filterHostData } from '../../../hosts/pages/navigation/alerts_query_tab_body';
@@ -22,6 +23,7 @@ import {
Query,
} from '../../../../../../../src/plugins/data/public';
import { GlobalTimeArgs } from '../../../common/containers/use_global_time';
+import { useInvalidFilterQuery } from '../../../common/hooks/use_invalid_filter_query';
const HorizontalSpacer = styled(EuiFlexItem)`
width: 24px;
@@ -45,7 +47,7 @@ const EventCountsComponent: React.FC = ({
}) => {
const { uiSettings } = useKibana().services;
- const hostFilterQuery = useMemo(
+ const [hostFilterQuery, hostKqlError] = useMemo(
() =>
convertToBuildEsQuery({
config: esQuery.getEsQueryConfig(uiSettings),
@@ -56,7 +58,7 @@ const EventCountsComponent: React.FC = ({
[filters, indexPattern, query, uiSettings]
);
- const networkFilterQuery = useMemo(
+ const [networkFilterQuery] = useMemo(
() =>
convertToBuildEsQuery({
config: esQuery.getEsQueryConfig(uiSettings),
@@ -67,6 +69,15 @@ const EventCountsComponent: React.FC = ({
[filters, indexPattern, uiSettings, query]
);
+ useInvalidFilterQuery({
+ id: OverviewHostQueryId,
+ filterQuery: hostFilterQuery || networkFilterQuery,
+ kqlError: hostKqlError,
+ query,
+ startDate: from,
+ endDate: to,
+ });
+
return (
diff --git a/x-pack/plugins/security_solution/public/overview/components/events_by_dataset/index.tsx b/x-pack/plugins/security_solution/public/overview/components/events_by_dataset/index.tsx
index a6ebfd2bbe0606..5cea3fa98eeb7e 100644
--- a/x-pack/plugins/security_solution/public/overview/components/events_by_dataset/index.tsx
+++ b/x-pack/plugins/security_solution/public/overview/components/events_by_dataset/index.tsx
@@ -36,6 +36,7 @@ import * as i18n from '../../pages/translations';
import { SecurityPageName } from '../../../app/types';
import { useFormatUrl } from '../../../common/components/link_to';
import { LinkButton } from '../../../common/components/links';
+import { useInvalidFilterQuery } from '../../../common/hooks/use_invalid_filter_query';
const DEFAULT_STACK_BY = 'event.dataset';
@@ -116,18 +117,26 @@ const EventsByDatasetComponent: React.FC = ({
[goToHostEvents, formatUrl]
);
- const filterQuery = useMemo(
- () =>
- combinedQueries == null
- ? convertToBuildEsQuery({
- config: esQuery.getEsQueryConfig(kibana.services.uiSettings),
- indexPattern,
- queries: [query],
- filters,
- })
- : combinedQueries,
- [combinedQueries, kibana, indexPattern, query, filters]
- );
+ const [filterQuery, kqlError] = useMemo(() => {
+ if (combinedQueries == null) {
+ return convertToBuildEsQuery({
+ config: esQuery.getEsQueryConfig(kibana.services.uiSettings),
+ indexPattern,
+ queries: [query],
+ filters,
+ });
+ }
+ return [combinedQueries];
+ }, [combinedQueries, kibana, indexPattern, query, filters]);
+
+ useInvalidFilterQuery({
+ id: uniqueQueryId,
+ filterQuery,
+ kqlError,
+ query,
+ startDate: from,
+ endDate: to,
+ });
const eventsByDatasetHistogramConfigs: MatrixHistogramConfigs = useMemo(
() => ({
@@ -171,6 +180,7 @@ const EventsByDatasetComponent: React.FC = ({
setAbsoluteRangeDatePickerTarget={setAbsoluteRangeDatePickerTarget}
setQuery={setQuery}
showSpacer={showSpacer}
+ skip={filterQuery === undefined}
startDate={from}
timelineId={timelineId}
{...eventsByDatasetHistogramConfigs}
diff --git a/x-pack/plugins/security_solution/public/overview/components/overview_host/index.tsx b/x-pack/plugins/security_solution/public/overview/components/overview_host/index.tsx
index 0a8e817a3bfc46..a0307380ce8025 100644
--- a/x-pack/plugins/security_solution/public/overview/components/overview_host/index.tsx
+++ b/x-pack/plugins/security_solution/public/overview/components/overview_host/index.tsx
@@ -51,6 +51,7 @@ const OverviewHostComponent: React.FC = ({
filterQuery,
indexNames,
startDate,
+ skip: filterQuery === undefined,
});
const goToHost = useCallback(
@@ -117,7 +118,12 @@ const OverviewHostComponent: React.FC = ({
-
+
<>{hostPageButton}>
diff --git a/x-pack/plugins/security_solution/public/overview/components/overview_network/index.tsx b/x-pack/plugins/security_solution/public/overview/components/overview_network/index.tsx
index eb5231d4ce5e06..214cd7b3f055b5 100644
--- a/x-pack/plugins/security_solution/public/overview/components/overview_network/index.tsx
+++ b/x-pack/plugins/security_solution/public/overview/components/overview_network/index.tsx
@@ -53,6 +53,7 @@ const OverviewNetworkComponent: React.FC = ({
filterQuery,
indexNames,
startDate,
+ skip: filterQuery === undefined,
});
const goToNetwork = useCallback(
@@ -123,7 +124,12 @@ const OverviewNetworkComponent: React.FC = ({
<>
-
+
{networkPageButton}
diff --git a/x-pack/plugins/security_solution/public/overview/containers/overview_cti_links/use_request_event_counts.ts b/x-pack/plugins/security_solution/public/overview/containers/overview_cti_links/use_request_event_counts.ts
index a6990c726dcf26..31c6593282f8c7 100644
--- a/x-pack/plugins/security_solution/public/overview/containers/overview_cti_links/use_request_event_counts.ts
+++ b/x-pack/plugins/security_solution/public/overview/containers/overview_cti_links/use_request_event_counts.ts
@@ -16,36 +16,38 @@ import { useKibana } from '../../../common/lib/kibana';
export const useRequestEventCounts = (to: string, from: string) => {
const { uiSettings } = useKibana().services;
+ const [filterQuery] = convertToBuildEsQuery({
+ config: esQuery.getEsQueryConfig(uiSettings),
+ indexPattern: {
+ fields: [
+ {
+ name: 'event.kind',
+ aggregatable: true,
+ searchable: true,
+ type: 'string',
+ esTypes: ['keyword'],
+ },
+ ],
+ title: 'filebeat-*',
+ },
+ queries: [{ query: 'event.type:indicator', language: 'kuery' }],
+ filters: [],
+ });
+
const matrixHistogramRequest = useMemo(() => {
return {
endDate: to,
errorMessage: i18n.translate('xpack.securitySolution.overview.errorFetchingEvents', {
defaultMessage: 'Error fetching events',
}),
- filterQuery: convertToBuildEsQuery({
- config: esQuery.getEsQueryConfig(uiSettings),
- indexPattern: {
- fields: [
- {
- name: 'event.kind',
- aggregatable: true,
- searchable: true,
- type: 'string',
- esTypes: ['keyword'],
- },
- ],
- title: 'filebeat-*',
- },
- queries: [{ query: 'event.type:indicator', language: 'kuery' }],
- filters: [],
- }),
+ filterQuery,
histogramType: MatrixHistogramType.events,
indexNames: DEFAULT_CTI_SOURCE_INDEX,
stackByField: EVENT_DATASET,
startDate: from,
size: 0,
};
- }, [to, from, uiSettings]);
+ }, [to, from, filterQuery]);
const results = useMatrixHistogram(matrixHistogramRequest);
diff --git a/x-pack/plugins/security_solution/public/overview/containers/overview_network/index.tsx b/x-pack/plugins/security_solution/public/overview/containers/overview_network/index.tsx
index 846c40994aac21..2b3adc36ae7465 100644
--- a/x-pack/plugins/security_solution/public/overview/containers/overview_network/index.tsx
+++ b/x-pack/plugins/security_solution/public/overview/containers/overview_network/index.tsx
@@ -74,7 +74,7 @@ export const useNetworkOverview = ({
const overviewNetworkSearch = useCallback(
(request: NetworkOverviewRequestOptions | null) => {
- if (request == null) {
+ if (request == null || skip) {
return;
}
@@ -118,7 +118,7 @@ export const useNetworkOverview = ({
asyncSearch();
refetch.current = asyncSearch;
},
- [data.search, addError, addWarning]
+ [data.search, addError, addWarning, skip]
);
useEffect(() => {
diff --git a/x-pack/plugins/security_solution/public/timelines/components/flyout/header/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/flyout/header/index.tsx
index 479b32c2d642e9..e54da13ea6056f 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/flyout/header/index.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/flyout/header/index.tsx
@@ -69,6 +69,9 @@ interface FlyoutHeaderPanelProps {
const FlyoutHeaderPanelComponent: React.FC = ({ timelineId }) => {
const dispatch = useDispatch();
+ const { indexPattern, browserFields } = useSourcererScope(SourcererScopeName.timeline);
+ const { uiSettings } = useKibana().services;
+ const esQueryConfig = useMemo(() => esQuery.getEsQueryConfig(uiSettings), [uiSettings]);
const getTimeline = useMemo(() => timelineSelectors.getTimelineByIdSelector(), []);
const {
activeTab,
@@ -79,6 +82,8 @@ const FlyoutHeaderPanelComponent: React.FC = ({ timeline
status: timelineStatus,
updated,
show,
+ filters,
+ kqlMode,
} = useDeepEqualSelector((state) =>
pick(
[
@@ -90,6 +95,8 @@ const FlyoutHeaderPanelComponent: React.FC = ({ timeline
'timelineType',
'updated',
'show',
+ 'filters',
+ 'kqlMode',
],
getTimeline(state, timelineId) ?? timelineDefaults
)
@@ -98,6 +105,30 @@ const FlyoutHeaderPanelComponent: React.FC = ({ timeline
() => !isEmpty(dataProviders) || !isEmpty(get('filterQuery.kuery.expression', kqlQuery)),
[dataProviders, kqlQuery]
);
+ const getKqlQueryTimeline = useMemo(() => timelineSelectors.getKqlFilterQuerySelector(), []);
+ const kqlQueryTimeline = useSelector((state: State) => getKqlQueryTimeline(state, timelineId)!);
+
+ const kqlQueryExpression =
+ isEmpty(dataProviders) && isEmpty(kqlQueryTimeline) && timelineType === 'template'
+ ? ' '
+ : kqlQueryTimeline;
+ const kqlQueryTest = useMemo(() => ({ query: kqlQueryExpression, language: 'kuery' }), [
+ kqlQueryExpression,
+ ]);
+
+ const combinedQueries = useMemo(
+ () =>
+ combineQueries({
+ config: esQueryConfig,
+ dataProviders,
+ indexPattern,
+ browserFields,
+ filters: filters ? filters : [],
+ kqlQuery: kqlQueryTest,
+ kqlMode,
+ }),
+ [browserFields, dataProviders, esQueryConfig, filters, indexPattern, kqlMode, kqlQueryTest]
+ );
const handleClose = useCallback(() => {
dispatch(timelineActions.showTimeline({ id: timelineId, show: false }));
@@ -134,7 +165,7 @@ const FlyoutHeaderPanelComponent: React.FC = ({ timeline
queryId={`${timelineId}-${activeTab}`}
inputId="timeline"
inspectIndex={0}
- isDisabled={!isDataInTimeline}
+ isDisabled={!isDataInTimeline || combinedQueries?.filterQuery === undefined}
title={i18n.INSPECT_TIMELINE_TITLE}
/>
@@ -295,10 +326,6 @@ const FlyoutHeaderComponent: React.FC = ({ timelineId }) => {
kqlQueryExpression,
]);
- const isBlankTimeline: boolean = useMemo(
- () => isEmpty(dataProviders) && isEmpty(filters) && isEmpty(kqlQuery.query),
- [dataProviders, filters, kqlQuery]
- );
const combinedQueries = useMemo(
() =>
combineQueries({
@@ -312,6 +339,14 @@ const FlyoutHeaderComponent: React.FC = ({ timelineId }) => {
}),
[browserFields, dataProviders, esQueryConfig, filters, indexPattern, kqlMode, kqlQuery]
);
+
+ const isBlankTimeline: boolean = useMemo(
+ () =>
+ (isEmpty(dataProviders) && isEmpty(filters) && isEmpty(kqlQuery.query)) ||
+ combinedQueries?.filterQuery === undefined,
+ [dataProviders, filters, kqlQuery, combinedQueries]
+ );
+
const [loading, kpis] = useTimelineKpis({
defaultIndex: selectedPatterns,
docValueFields,
diff --git a/x-pack/plugins/security_solution/public/timelines/components/side_panel/network_details/expandable_network.tsx b/x-pack/plugins/security_solution/public/timelines/components/side_panel/network_details/expandable_network.tsx
index 19f6e2c9652f92..e53e835cfd8820 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/side_panel/network_details/expandable_network.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/side_panel/network_details/expandable_network.tsx
@@ -10,6 +10,7 @@ import { i18n } from '@kbn/i18n';
import styled from 'styled-components';
import React, { useCallback, useMemo } from 'react';
import { useDispatch } from 'react-redux';
+import { useInvalidFilterQuery } from '../../../../common/hooks/use_invalid_filter_query';
import { FlowTarget } from '../../../../../common/search_strategy';
import { NetworkDetailsLink } from '../../../../common/components/links';
import { IpOverview } from '../../../../network/components/details';
@@ -97,7 +98,7 @@ export const ExpandableNetworkDetails = ({
} = useKibana();
const { docValueFields, indicesExist, indexPattern, selectedPatterns } = useSourcererScope();
- const filterQuery = convertToBuildEsQuery({
+ const [filterQuery, kqlError] = convertToBuildEsQuery({
config: esQuery.getEsQueryConfig(uiSettings),
indexPattern,
queries: [query],
@@ -106,12 +107,14 @@ export const ExpandableNetworkDetails = ({
const [loading, { id, networkDetails }] = useNetworkDetails({
docValueFields,
- skip: isInitializing,
+ skip: isInitializing || filterQuery === undefined,
filterQuery,
indexNames: selectedPatterns,
ip,
});
+ useInvalidFilterQuery({ id, filterQuery, kqlError, query, startDate: from, endDate: to });
+
const [isLoadingAnomaliesData, anomaliesData] = useAnomaliesTableData({
criteriaFields: networkToCriteria(ip, flowTarget),
startDate: from,
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/helpers.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/helpers.test.tsx
index 7e6dbe80dc7b09..678fd9d7a3cf50 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/helpers.test.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/helpers.test.tsx
@@ -305,7 +305,7 @@ describe('Combined Queries', () => {
test('Only Data Provider', () => {
const dataProviders = cloneDeep(mockDataProviders.slice(0, 1));
- const { filterQuery } = combineQueries({
+ const { filterQuery, kqlError } = combineQueries({
config,
dataProviders,
indexPattern: mockIndexPattern,
@@ -317,13 +317,14 @@ describe('Combined Queries', () => {
expect(filterQuery).toEqual(
'{"bool":{"must":[],"filter":[{"bool":{"should":[{"match_phrase":{"name":"Provider 1"}}],"minimum_should_match":1}}],"should":[],"must_not":[]}}'
);
+ expect(kqlError).toBeUndefined();
});
test('Only Data Provider with timestamp (string input)', () => {
const dataProviders = cloneDeep(mockDataProviders.slice(0, 1));
dataProviders[0].queryMatch.field = '@timestamp';
dataProviders[0].queryMatch.value = '2018-03-23T23:36:23.232Z';
- const { filterQuery } = combineQueries({
+ const { filterQuery, kqlError } = combineQueries({
config,
dataProviders,
indexPattern: mockIndexPattern,
@@ -335,13 +336,14 @@ describe('Combined Queries', () => {
expect(filterQuery).toMatchInlineSnapshot(
`"{\\"bool\\":{\\"must\\":[],\\"filter\\":[{\\"bool\\":{\\"should\\":[{\\"range\\":{\\"@timestamp\\":{\\"gte\\":\\"1521848183232\\",\\"lte\\":\\"1521848183232\\"}}}],\\"minimum_should_match\\":1}}],\\"should\\":[],\\"must_not\\":[]}}"`
);
+ expect(kqlError).toBeUndefined();
});
test('Only Data Provider with timestamp (numeric input)', () => {
const dataProviders = cloneDeep(mockDataProviders.slice(0, 1));
dataProviders[0].queryMatch.field = '@timestamp';
dataProviders[0].queryMatch.value = 1521848183232;
- const { filterQuery } = combineQueries({
+ const { filterQuery, kqlError } = combineQueries({
config,
dataProviders,
indexPattern: mockIndexPattern,
@@ -353,13 +355,14 @@ describe('Combined Queries', () => {
expect(filterQuery).toMatchInlineSnapshot(
`"{\\"bool\\":{\\"must\\":[],\\"filter\\":[{\\"bool\\":{\\"should\\":[{\\"range\\":{\\"@timestamp\\":{\\"gte\\":\\"1521848183232\\",\\"lte\\":\\"1521848183232\\"}}}],\\"minimum_should_match\\":1}}],\\"should\\":[],\\"must_not\\":[]}}"`
);
+ expect(kqlError).toBeUndefined();
});
test('Only Data Provider with a date type (string input)', () => {
const dataProviders = cloneDeep(mockDataProviders.slice(0, 1));
dataProviders[0].queryMatch.field = 'event.end';
dataProviders[0].queryMatch.value = '2018-03-23T23:36:23.232Z';
- const { filterQuery } = combineQueries({
+ const { filterQuery, kqlError } = combineQueries({
config,
dataProviders,
indexPattern: mockIndexPattern,
@@ -371,13 +374,14 @@ describe('Combined Queries', () => {
expect(filterQuery).toMatchInlineSnapshot(
`"{\\"bool\\":{\\"must\\":[],\\"filter\\":[{\\"bool\\":{\\"should\\":[{\\"match\\":{\\"event.end\\":\\"1521848183232\\"}}],\\"minimum_should_match\\":1}}],\\"should\\":[],\\"must_not\\":[]}}"`
);
+ expect(kqlError).toBeUndefined();
});
test('Only Data Provider with date type (numeric input)', () => {
const dataProviders = cloneDeep(mockDataProviders.slice(0, 1));
dataProviders[0].queryMatch.field = 'event.end';
dataProviders[0].queryMatch.value = 1521848183232;
- const { filterQuery } = combineQueries({
+ const { filterQuery, kqlError } = combineQueries({
config,
dataProviders,
indexPattern: mockIndexPattern,
@@ -389,10 +393,11 @@ describe('Combined Queries', () => {
expect(filterQuery).toMatchInlineSnapshot(
`"{\\"bool\\":{\\"must\\":[],\\"filter\\":[{\\"bool\\":{\\"should\\":[{\\"match\\":{\\"event.end\\":\\"1521848183232\\"}}],\\"minimum_should_match\\":1}}],\\"should\\":[],\\"must_not\\":[]}}"`
);
+ expect(kqlError).toBeUndefined();
});
test('Only KQL search/filter query', () => {
- const { filterQuery } = combineQueries({
+ const { filterQuery, kqlError } = combineQueries({
config,
dataProviders: [],
indexPattern: mockIndexPattern,
@@ -404,11 +409,26 @@ describe('Combined Queries', () => {
expect(filterQuery).toEqual(
'{"bool":{"must":[],"filter":[{"bool":{"should":[{"match_phrase":{"host.name":"host-1"}}],"minimum_should_match":1}}],"should":[],"must_not":[]}}'
);
+ expect(kqlError).toBeUndefined();
+ });
+
+ test('Invalid KQL search/filter query', () => {
+ const { filterQuery, kqlError } = combineQueries({
+ config,
+ dataProviders: [],
+ indexPattern: mockIndexPattern,
+ browserFields: mockBrowserFields,
+ filters: [],
+ kqlQuery: { query: 'host.name: "host-1', language: 'kuery' },
+ kqlMode: 'search',
+ })!;
+ expect(filterQuery).toBeUndefined();
+ expect(kqlError).toBeDefined(); // Not testing on the error message since we don't control changes to them
});
test('Data Provider & KQL search query', () => {
const dataProviders = cloneDeep(mockDataProviders.slice(0, 1));
- const { filterQuery } = combineQueries({
+ const { filterQuery, kqlError } = combineQueries({
config,
dataProviders,
indexPattern: mockIndexPattern,
@@ -420,11 +440,12 @@ describe('Combined Queries', () => {
expect(filterQuery).toEqual(
'{"bool":{"must":[],"filter":[{"bool":{"should":[{"bool":{"should":[{"match_phrase":{"name":"Provider 1"}}],"minimum_should_match":1}},{"bool":{"should":[{"match_phrase":{"host.name":"host-1"}}],"minimum_should_match":1}}],"minimum_should_match":1}}],"should":[],"must_not":[]}}'
);
+ expect(kqlError).toBeUndefined();
});
test('Data Provider & KQL filter query', () => {
const dataProviders = cloneDeep(mockDataProviders.slice(0, 1));
- const { filterQuery } = combineQueries({
+ const { filterQuery, kqlError } = combineQueries({
config,
dataProviders,
indexPattern: mockIndexPattern,
@@ -436,13 +457,14 @@ describe('Combined Queries', () => {
expect(filterQuery).toEqual(
'{"bool":{"must":[],"filter":[{"bool":{"filter":[{"bool":{"should":[{"match_phrase":{"name":"Provider 1"}}],"minimum_should_match":1}},{"bool":{"should":[{"match_phrase":{"host.name":"host-1"}}],"minimum_should_match":1}}]}}],"should":[],"must_not":[]}}'
);
+ expect(kqlError).toBeUndefined();
});
test('Data Provider & KQL search query multiple', () => {
const dataProviders = cloneDeep(mockDataProviders.slice(0, 2));
dataProviders[0].and = cloneDeep(mockDataProviders.slice(2, 4));
dataProviders[1].and = cloneDeep(mockDataProviders.slice(4, 5));
- const { filterQuery } = combineQueries({
+ const { filterQuery, kqlError } = combineQueries({
config,
dataProviders,
indexPattern: mockIndexPattern,
@@ -454,13 +476,14 @@ describe('Combined Queries', () => {
expect(filterQuery).toMatchInlineSnapshot(
`"{\\"bool\\":{\\"must\\":[],\\"filter\\":[{\\"bool\\":{\\"should\\":[{\\"bool\\":{\\"should\\":[{\\"bool\\":{\\"filter\\":[{\\"bool\\":{\\"should\\":[{\\"match_phrase\\":{\\"name\\":\\"Provider 1\\"}}],\\"minimum_should_match\\":1}},{\\"bool\\":{\\"should\\":[{\\"match_phrase\\":{\\"name\\":\\"Provider 3\\"}}],\\"minimum_should_match\\":1}},{\\"bool\\":{\\"should\\":[{\\"match_phrase\\":{\\"name\\":\\"Provider 4\\"}}],\\"minimum_should_match\\":1}}]}},{\\"bool\\":{\\"filter\\":[{\\"bool\\":{\\"should\\":[{\\"match_phrase\\":{\\"name\\":\\"Provider 2\\"}}],\\"minimum_should_match\\":1}},{\\"bool\\":{\\"should\\":[{\\"match_phrase\\":{\\"name\\":\\"Provider 5\\"}}],\\"minimum_should_match\\":1}}]}}],\\"minimum_should_match\\":1}},{\\"bool\\":{\\"should\\":[{\\"match_phrase\\":{\\"host.name\\":\\"host-1\\"}}],\\"minimum_should_match\\":1}}],\\"minimum_should_match\\":1}}],\\"should\\":[],\\"must_not\\":[]}}"`
);
+ expect(kqlError).toBeUndefined();
});
test('Data Provider & KQL filter query multiple', () => {
const dataProviders = cloneDeep(mockDataProviders.slice(0, 2));
dataProviders[0].and = cloneDeep(mockDataProviders.slice(2, 4));
dataProviders[1].and = cloneDeep(mockDataProviders.slice(4, 5));
- const { filterQuery } = combineQueries({
+ const { filterQuery, kqlError } = combineQueries({
config,
dataProviders,
indexPattern: mockIndexPattern,
@@ -472,6 +495,7 @@ describe('Combined Queries', () => {
expect(filterQuery).toMatchInlineSnapshot(
`"{\\"bool\\":{\\"must\\":[],\\"filter\\":[{\\"bool\\":{\\"filter\\":[{\\"bool\\":{\\"should\\":[{\\"bool\\":{\\"filter\\":[{\\"bool\\":{\\"should\\":[{\\"match_phrase\\":{\\"name\\":\\"Provider 1\\"}}],\\"minimum_should_match\\":1}},{\\"bool\\":{\\"should\\":[{\\"match_phrase\\":{\\"name\\":\\"Provider 3\\"}}],\\"minimum_should_match\\":1}},{\\"bool\\":{\\"should\\":[{\\"match_phrase\\":{\\"name\\":\\"Provider 4\\"}}],\\"minimum_should_match\\":1}}]}},{\\"bool\\":{\\"filter\\":[{\\"bool\\":{\\"should\\":[{\\"match_phrase\\":{\\"name\\":\\"Provider 2\\"}}],\\"minimum_should_match\\":1}},{\\"bool\\":{\\"should\\":[{\\"match_phrase\\":{\\"name\\":\\"Provider 5\\"}}],\\"minimum_should_match\\":1}}]}}],\\"minimum_should_match\\":1}},{\\"bool\\":{\\"should\\":[{\\"match_phrase\\":{\\"host.name\\":\\"host-1\\"}}],\\"minimum_should_match\\":1}}]}}],\\"should\\":[],\\"must_not\\":[]}}"`
);
+ expect(kqlError).toBeUndefined();
});
test('Data Provider & kql filter query with nested field that exists', () => {
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/helpers.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/helpers.tsx
index f2a40711116027..7c38474d39dba5 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/helpers.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/helpers.tsx
@@ -161,27 +161,48 @@ export const combineQueries = ({
kqlQuery: Query;
kqlMode: string;
isEventViewer?: boolean;
-}): { filterQuery: string } | null => {
+}): { filterQuery?: string; kqlError?: Error } | null => {
const kuery: Query = { query: '', language: kqlQuery.language };
if (isEmpty(dataProviders) && isEmpty(kqlQuery.query) && isEmpty(filters) && !isEventViewer) {
return null;
- } else if (isEmpty(dataProviders) && isEmpty(kqlQuery.query) && isEventViewer) {
- return {
- filterQuery: convertToBuildEsQuery({ config, queries: [kuery], indexPattern, filters }),
- };
- } else if (isEmpty(dataProviders) && isEmpty(kqlQuery.query) && !isEmpty(filters)) {
+ } else if (
+ isEmpty(dataProviders) &&
+ isEmpty(kqlQuery.query) &&
+ (isEventViewer || !isEmpty(filters))
+ ) {
+ const [filterQuery, kqlError] = convertToBuildEsQuery({
+ config,
+ queries: [kuery],
+ indexPattern,
+ filters,
+ });
return {
- filterQuery: convertToBuildEsQuery({ config, queries: [kuery], indexPattern, filters }),
+ filterQuery,
+ kqlError,
};
} else if (isEmpty(dataProviders) && !isEmpty(kqlQuery.query)) {
kuery.query = `(${kqlQuery.query})`;
+ const [filterQuery, kqlError] = convertToBuildEsQuery({
+ config,
+ queries: [kuery],
+ indexPattern,
+ filters,
+ });
return {
- filterQuery: convertToBuildEsQuery({ config, queries: [kuery], indexPattern, filters }),
+ filterQuery,
+ kqlError,
};
} else if (!isEmpty(dataProviders) && isEmpty(kqlQuery)) {
kuery.query = `(${buildGlobalQuery(dataProviders, browserFields)})`;
+ const [filterQuery, kqlError] = convertToBuildEsQuery({
+ config,
+ queries: [kuery],
+ indexPattern,
+ filters,
+ });
return {
- filterQuery: convertToBuildEsQuery({ config, queries: [kuery], indexPattern, filters }),
+ filterQuery,
+ kqlError,
};
}
const operatorKqlQuery = kqlMode === 'filter' ? 'and' : 'or';
@@ -189,8 +210,15 @@ export const combineQueries = ({
kuery.query = `((${buildGlobalQuery(dataProviders, browserFields)})${postpend(
kqlQuery.query as string
)})`;
+ const [filterQuery, kqlError] = convertToBuildEsQuery({
+ config,
+ queries: [kuery],
+ indexPattern,
+ filters,
+ });
return {
- filterQuery: convertToBuildEsQuery({ config, queries: [kuery], indexPattern, filters }),
+ filterQuery,
+ kqlError,
};
};
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/query_tab_content/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/query_tab_content/index.tsx
index 6f0bbd026cd7bf..c2e47edeae2029 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/query_tab_content/index.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/query_tab_content/index.tsx
@@ -21,6 +21,7 @@ import { connect, ConnectedProps, useDispatch } from 'react-redux';
import deepEqual from 'fast-deep-equal';
import { InPortal } from 'react-reverse-portal';
+import { useInvalidFilterQuery } from '../../../../common/hooks/use_invalid_filter_query';
import { timelineActions, timelineSelectors } from '../../../store/timeline';
import { CellValueElementProps } from '../cell_rendering';
import { Direction, TimelineItem } from '../../../../../common/search_strategy';
@@ -197,7 +198,7 @@ export const QueryTabContentComponent: React.FC = ({
const kqlQuery: {
query: string;
language: KueryFilterQueryKind;
- } = { query: kqlQueryExpression, language: 'kuery' };
+ } = useMemo(() => ({ query: kqlQueryExpression, language: 'kuery' }), [kqlQueryExpression]);
const combinedQueries = combineQueries({
config: esQueryConfig,
@@ -209,6 +210,15 @@ export const QueryTabContentComponent: React.FC = ({
kqlMode,
});
+ useInvalidFilterQuery({
+ id: timelineId,
+ filterQuery: combinedQueries?.filterQuery,
+ kqlError: combinedQueries?.kqlError,
+ query: kqlQuery,
+ startDate: start,
+ endDate: end,
+ });
+
const isBlankTimeline: boolean =
isEmpty(dataProviders) && isEmpty(filters) && isEmpty(kqlQuery.query);
@@ -252,9 +262,9 @@ export const QueryTabContentComponent: React.FC = ({
fields: getTimelineQueryFields(),
language: kqlQuery.language,
limit: itemsPerPage,
- filterQuery: combinedQueries?.filterQuery ?? '',
+ filterQuery: combinedQueries?.filterQuery,
startDate: start,
- skip: !canQueryTimeline(),
+ skip: !canQueryTimeline() || combinedQueries?.filterQuery === undefined,
sort: timelineQuerySortField,
timerangeKind,
});
From 9fdd5698387bc1ef156fee2b29cc40031a7e8220 Mon Sep 17 00:00:00 2001
From: Nicolas Chaulet
Date: Wed, 30 Jun 2021 11:32:17 -0400
Subject: [PATCH 065/121] [Fleet] Do not show settings banner while its loading
in the add agent flyout (#103883)
---
.../fleet/public/components/agent_enrollment_flyout/index.tsx | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/index.tsx b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/index.tsx
index 9e4bdf386ef478..08d78154941e9a 100644
--- a/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/index.tsx
+++ b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/index.tsx
@@ -63,6 +63,8 @@ export const AgentEnrollmentFlyout: React.FunctionComponent = ({
}
}, [modal, lastModal, settings]);
+ const isLoadingInitialRequest = settings.isLoading && settings.isInitialRequest;
+
return (
@@ -108,7 +110,7 @@ export const AgentEnrollmentFlyout: React.FunctionComponent = ({
) : undefined
}
From d046adaa0927403617eb788d4517e33c5d7725da Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Cau=C3=AA=20Marcondes?=
<55978943+cauemarcondes@users.noreply.github.com>
Date: Wed, 30 Jun 2021 11:41:17 -0400
Subject: [PATCH 066/121] [APM] Adding telemetry to APM APIs (#103543)
* adding telemetry to apm apis
* addressing PR comment
* adding space
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
---
x-pack/plugins/apm/server/plugin.ts | 5 +++++
.../apm/server/routes/register_routes/index.ts | 18 ++++++++++++++++++
x-pack/plugins/apm/server/routes/typings.ts | 1 +
3 files changed, 24 insertions(+)
diff --git a/x-pack/plugins/apm/server/plugin.ts b/x-pack/plugins/apm/server/plugin.ts
index 2d3638272508e0..638880c9f3e4af 100644
--- a/x-pack/plugins/apm/server/plugin.ts
+++ b/x-pack/plugins/apm/server/plugin.ts
@@ -202,6 +202,10 @@ export class APMPlugin
};
}) as APMRouteHandlerResources['plugins'];
+ const telemetryUsageCounter = resourcePlugins.usageCollection?.setup.createUsageCounter(
+ 'apm'
+ );
+
registerRoutes({
core: {
setup: core,
@@ -212,6 +216,7 @@ export class APMPlugin
repository: getGlobalApmServerRouteRepository(),
ruleDataClient,
plugins: resourcePlugins,
+ telemetryUsageCounter,
});
const boundGetApmIndices = async () =>
diff --git a/x-pack/plugins/apm/server/routes/register_routes/index.ts b/x-pack/plugins/apm/server/routes/register_routes/index.ts
index c9df12fd582084..136f3c73d80469 100644
--- a/x-pack/plugins/apm/server/routes/register_routes/index.ts
+++ b/x-pack/plugins/apm/server/routes/register_routes/index.ts
@@ -18,6 +18,7 @@ import {
routeValidationObject,
} from '@kbn/server-route-repository';
import { mergeRt, jsonRt } from '@kbn/io-ts-utils';
+import { UsageCollectionSetup } from '../../../../../../src/plugins/usage_collection/server';
import { pickKeys } from '../../../common/utils/pick_keys';
import { APMRouteHandlerResources, InspectResponse } from '../typings';
import type { ApmPluginRequestHandlerContext } from '../typings';
@@ -40,6 +41,7 @@ export function registerRoutes({
logger,
config,
ruleDataClient,
+ telemetryUsageCounter,
}: {
core: APMRouteHandlerResources['core'];
plugins: APMRouteHandlerResources['plugins'];
@@ -47,6 +49,9 @@ export function registerRoutes({
repository: ServerRouteRepository;
config: APMRouteHandlerResources['config'];
ruleDataClient: APMRouteHandlerResources['ruleDataClient'];
+ telemetryUsageCounter?: ReturnType<
+ UsageCollectionSetup['createUsageCounter']
+ >;
}) {
const routes = repository.getRoutes();
@@ -116,9 +121,22 @@ export function registerRoutes({
// cleanup
inspectableEsQueriesMap.delete(request);
+ if (!options.disableTelemetry && telemetryUsageCounter) {
+ telemetryUsageCounter.incrementCounter({
+ counterName: `${method.toUpperCase()} ${pathname}`,
+ counterType: 'success',
+ });
+ }
+
return response.ok({ body });
} catch (error) {
logger.error(error);
+ if (!options.disableTelemetry && telemetryUsageCounter) {
+ telemetryUsageCounter.incrementCounter({
+ counterName: `${method.toUpperCase()} ${pathname}`,
+ counterType: 'error',
+ });
+ }
const opts = {
statusCode: 500,
body: {
diff --git a/x-pack/plugins/apm/server/routes/typings.ts b/x-pack/plugins/apm/server/routes/typings.ts
index c9f425473ada6c..98c2ee47b5633b 100644
--- a/x-pack/plugins/apm/server/routes/typings.ts
+++ b/x-pack/plugins/apm/server/routes/typings.ts
@@ -41,6 +41,7 @@ export interface APMRouteCreateOptions {
| 'access:ml:canCreateJob'
>;
body?: { accepts: Array<'application/json' | 'multipart/form-data'> };
+ disableTelemetry?: boolean;
};
}
From 0f4319c43e72abb7dc8b0a578a42258ccc8299a3 Mon Sep 17 00:00:00 2001
From: debadair
Date: Wed, 30 Jun 2021 08:43:15 -0700
Subject: [PATCH 067/121] [DOCS] Fixed another units xref (#103826)
---
docs/settings/reporting-settings.asciidoc | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/settings/reporting-settings.asciidoc b/docs/settings/reporting-settings.asciidoc
index b1948dbf630dda..32d80d7e0c766d 100644
--- a/docs/settings/reporting-settings.asciidoc
+++ b/docs/settings/reporting-settings.asciidoc
@@ -127,7 +127,7 @@ control the capturing process.
|===
a| `xpack.reporting.capture.timeouts`
`.openUrl` {ess-icon}
- | Specify the {ref}/common-options.html#time-units[time] to allow the Reporting browser to wait for the "Loading..." screen
+ | Specify the {time-units}[time] to allow the Reporting browser to wait for the "Loading..." screen
to dismiss and find the initial data for the page. If the time is exceeded, a screenshot is captured showing the current
page, and the download link shows a warning message. Can be specified as number of milliseconds.
Defaults to `1m`.
From c63e74f0242b9e0d1efdbef417c086fbc1321604 Mon Sep 17 00:00:00 2001
From: Jen Huang
Date: Wed, 30 Jun 2021 09:22:14 -0700
Subject: [PATCH 068/121] [Integrations UI] Restore post-save integration
policy redirect (#103780)
* Revert "Remove post-installation redirect for integrations (#103179)"
This reverts commit 96c4350289adace86b877e6836be0029a062f662.
* Restore post-save redirects but only when user hasn't navigated away
---
.../create_package_policy_page/index.tsx | 57 ++++++++------
.../sections/epm/screens/detail/index.tsx | 76 +++++++++++++++++--
2 files changed, 105 insertions(+), 28 deletions(-)
diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/index.tsx
index 3fbaea67d8973e..1363af573b86d6 100644
--- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/index.tsx
+++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/index.tsx
@@ -6,7 +6,7 @@
*/
import type { ReactEventHandler } from 'react';
-import React, { useState, useEffect, useMemo, useCallback } from 'react';
+import React, { useState, useEffect, useMemo, useCallback, useRef } from 'react';
import { useRouteMatch, useHistory, useLocation } from 'react-router-dom';
import styled from 'styled-components';
import { i18n } from '@kbn/i18n';
@@ -271,6 +271,14 @@ export const CreatePackagePolicyPage: React.FunctionComponent = () => {
setFormState('SUBMITTED');
return result;
}, [packagePolicy]);
+ const doOnSaveNavigation = useRef(true);
+
+ // Detect if user left page
+ useEffect(() => {
+ return () => {
+ doOnSaveNavigation.current = false;
+ };
+ }, []);
const onSubmit = useCallback(async () => {
if (formState === 'VALID' && hasErrors) {
@@ -283,18 +291,20 @@ export const CreatePackagePolicyPage: React.FunctionComponent = () => {
}
const { error, data } = await savePackagePolicy();
if (!error) {
- if (routeState && routeState.onSaveNavigateTo) {
- handleNavigateTo(
- typeof routeState.onSaveNavigateTo === 'function'
- ? routeState.onSaveNavigateTo(data!.item)
- : routeState.onSaveNavigateTo
- );
- } else {
- history.push(
- getPath('policy_details', {
- policyId: agentPolicy?.id || (params as AddFromPolicyParams).policyId,
- })
- );
+ if (doOnSaveNavigation.current) {
+ if (routeState && routeState.onSaveNavigateTo) {
+ handleNavigateTo(
+ typeof routeState.onSaveNavigateTo === 'function'
+ ? routeState.onSaveNavigateTo(data!.item)
+ : routeState.onSaveNavigateTo
+ );
+ } else {
+ history.push(
+ getPath('policy_details', {
+ policyId: agentPolicy?.id || (params as AddFromPolicyParams).policyId,
+ })
+ );
+ }
}
const fromPolicyWithoutAgentsAssigned = from === 'policy' && agentPolicy && agentCount === 0;
@@ -361,21 +371,22 @@ export const CreatePackagePolicyPage: React.FunctionComponent = () => {
setFormState('VALID');
}
}, [
- getHref,
- from,
- packageInfo,
- agentCount,
- agentPolicy,
formState,
- getPath,
- handleNavigateTo,
hasErrors,
- history,
+ agentCount,
+ savePackagePolicy,
+ doOnSaveNavigation,
+ from,
+ agentPolicy,
+ packageInfo,
notifications.toasts,
packagePolicy.name,
- params,
+ getHref,
routeState,
- savePackagePolicy,
+ handleNavigateTo,
+ history,
+ getPath,
+ params,
]);
const integrationInfo = useMemo(
diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/index.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/index.tsx
index e840da142cfbf8..2102c5055503b9 100644
--- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/index.tsx
+++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/index.tsx
@@ -6,7 +6,7 @@
*/
import type { ReactEventHandler } from 'react';
import React, { useCallback, useEffect, useMemo, useState } from 'react';
-import { Redirect, Route, Switch, useLocation, useParams } from 'react-router-dom';
+import { Redirect, Route, Switch, useLocation, useParams, useHistory } from 'react-router-dom';
import styled from 'styled-components';
import {
EuiBetaBadge,
@@ -31,7 +31,12 @@ import {
useBreadcrumbs,
useStartServices,
} from '../../../../hooks';
-import { PLUGIN_ID, INTEGRATIONS_ROUTING_PATHS, pagePathGetters } from '../../../../constants';
+import {
+ PLUGIN_ID,
+ INTEGRATIONS_PLUGIN_ID,
+ INTEGRATIONS_ROUTING_PATHS,
+ pagePathGetters,
+} from '../../../../constants';
import {
useCapabilities,
useGetPackageInfoByKey,
@@ -39,7 +44,11 @@ import {
useAgentPolicyContext,
} from '../../../../hooks';
import { pkgKeyFromPackageInfo } from '../../../../services';
-import type { DetailViewPanelName, PackageInfo } from '../../../../types';
+import type {
+ CreatePackagePolicyRouteState,
+ DetailViewPanelName,
+ PackageInfo,
+} from '../../../../types';
import { InstallStatus } from '../../../../types';
import { Error, Loading } from '../../../../components';
import type { WithHeaderLayoutProps } from '../../../../layouts';
@@ -80,7 +89,8 @@ export function Detail() {
const { pkgkey, panel } = useParams();
const { getHref } = useLink();
const hasWriteCapabilites = useCapabilities().write;
- const { search } = useLocation();
+ const history = useHistory();
+ const { pathname, search, hash } = useLocation();
const queryParams = useMemo(() => new URLSearchParams(search), [search]);
const integration = useMemo(() => queryParams.get('integration'), [queryParams]);
const services = useStartServices();
@@ -202,19 +212,75 @@ export function Detail() {
(ev) => {
ev.preventDefault();
+ // The object below, given to `createHref` is explicitly accessing keys of `location` in order
+ // to ensure that dependencies to this `useCallback` is set correctly (because `location` is mutable)
+ const currentPath = history.createHref({
+ pathname,
+ search,
+ hash,
+ });
+
const path = pagePathGetters.add_integration_to_policy({
pkgkey,
...(integration ? { integration } : {}),
...(agentPolicyIdFromContext ? { agentPolicyId: agentPolicyIdFromContext } : {}),
})[1];
+ let redirectToPath: CreatePackagePolicyRouteState['onSaveNavigateTo'] &
+ CreatePackagePolicyRouteState['onCancelNavigateTo'];
+
+ if (agentPolicyIdFromContext) {
+ redirectToPath = [
+ PLUGIN_ID,
+ {
+ path: `#${
+ pagePathGetters.policy_details({
+ policyId: agentPolicyIdFromContext,
+ })[1]
+ }`,
+ },
+ ];
+ } else {
+ redirectToPath = [
+ INTEGRATIONS_PLUGIN_ID,
+ {
+ path: `#${
+ pagePathGetters.integration_details_policies({
+ pkgkey,
+ })[1]
+ }`,
+ },
+ ];
+ }
+
+ const redirectBackRouteState: CreatePackagePolicyRouteState = {
+ onSaveNavigateTo: redirectToPath,
+ onCancelNavigateTo: [
+ INTEGRATIONS_PLUGIN_ID,
+ {
+ path: currentPath,
+ },
+ ],
+ onCancelUrl: currentPath,
+ };
+
services.application.navigateToApp(PLUGIN_ID, {
// Necessary because of Fleet's HashRouter. Can be changed when
// https://github.com/elastic/kibana/issues/96134 is resolved
path: `#${path}`,
+ state: redirectBackRouteState,
});
},
- [pkgkey, integration, services.application, agentPolicyIdFromContext]
+ [
+ history,
+ hash,
+ pathname,
+ search,
+ pkgkey,
+ integration,
+ services.application,
+ agentPolicyIdFromContext,
+ ]
);
const headerRightContent = useMemo(
From 4b5ceb39959326ba5abe51b8619a08be89776d9c Mon Sep 17 00:00:00 2001
From: Rashmi Kulkarni
Date: Wed, 30 Jun 2021 09:24:52 -0700
Subject: [PATCH 069/121] addressed the nits from PR 103028 (#103892)
---
x-pack/test/api_integration/apis/maps/index.js | 1 +
x-pack/test/functional/apps/maps/index.js | 6 +++++-
2 files changed, 6 insertions(+), 1 deletion(-)
diff --git a/x-pack/test/api_integration/apis/maps/index.js b/x-pack/test/api_integration/apis/maps/index.js
index 46d791b18f0334..6d4122163d66ac 100644
--- a/x-pack/test/api_integration/apis/maps/index.js
+++ b/x-pack/test/api_integration/apis/maps/index.js
@@ -16,6 +16,7 @@ export default function ({ loadTestFile, getService }) {
after(async () => {
await esArchiver.unload('x-pack/test/functional/es_archives/logstash_functional');
+ await esArchiver.unload('x-pack/test/functional/es_archives/maps/data');
});
describe('', () => {
diff --git a/x-pack/test/functional/apps/maps/index.js b/x-pack/test/functional/apps/maps/index.js
index 273a8baa0ae4c4..33184f2d352131 100644
--- a/x-pack/test/functional/apps/maps/index.js
+++ b/x-pack/test/functional/apps/maps/index.js
@@ -20,7 +20,11 @@ export default function ({ loadTestFile, getService }) {
await kibanaServer.importExport.load(
'x-pack/test/functional/fixtures/kbn_archiver/maps.json'
);
- //Find the missing references manually, use the below API to delete it after successful import.
+ // Functional tests verify behavior when referenced index pattern saved objects can not be found.
+ // However, saved object import fails when reference saved objects can not be found.
+ // To prevent import errors, index pattern saved object references exist during import
+ // but are then deleted afterwards to enable testing of missing reference index pattern saved objects.
+
log.info('Delete index pattern');
log.debug('id: ' + 'idThatDoesNotExitForESGeoGridSource');
log.debug('id: ' + 'idThatDoesNotExitForESSearchSource');
From 2576d61fef6aa93e7f721f47c8de388c13126a76 Mon Sep 17 00:00:00 2001
From: Greg Thompson
Date: Wed, 30 Jun 2021 11:33:25 -0500
Subject: [PATCH 070/121] eui to 34.5.2 (#103896)
---
package.json | 2 +-
yarn.lock | 8 ++++----
2 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/package.json b/package.json
index 23a3e823b2e3c8..b1d57d54838bc8 100644
--- a/package.json
+++ b/package.json
@@ -103,7 +103,7 @@
"@elastic/datemath": "link:bazel-bin/packages/elastic-datemath",
"@elastic/elasticsearch": "npm:@elastic/elasticsearch-canary@^8.0.0-canary.13",
"@elastic/ems-client": "7.14.0",
- "@elastic/eui": "34.5.1",
+ "@elastic/eui": "34.5.2",
"@elastic/filesaver": "1.1.2",
"@elastic/good": "^9.0.1-kibana3",
"@elastic/maki": "6.3.0",
diff --git a/yarn.lock b/yarn.lock
index a86eb52398d214..b95056a78ea8be 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1436,10 +1436,10 @@
resolved "https://registry.yarnpkg.com/@elastic/eslint-plugin-eui/-/eslint-plugin-eui-0.0.2.tgz#56b9ef03984a05cc213772ae3713ea8ef47b0314"
integrity sha512-IoxURM5zraoQ7C8f+mJb9HYSENiZGgRVcG4tLQxE61yHNNRDXtGDWTZh8N1KIHcsqN1CEPETjuzBXkJYF/fDiQ==
-"@elastic/eui@34.5.1":
- version "34.5.1"
- resolved "https://registry.yarnpkg.com/@elastic/eui/-/eui-34.5.1.tgz#280faff755aa8dc4d683ec9d7eab09465e2f9628"
- integrity sha512-+7viKbdu3nbUHKfFeNm8f7VFqyn68gp33vIOdwvZ2iXV93mqK4AWaUitW5j0QrpcodmbyaQma8Csggce9K9wYg==
+"@elastic/eui@34.5.2":
+ version "34.5.2"
+ resolved "https://registry.yarnpkg.com/@elastic/eui/-/eui-34.5.2.tgz#6aad49945a894fb77785a48c281cd0bd5e9e87ba"
+ integrity sha512-+ColXEaZ8Oa8lJ/ixayiLuWlYUggoTTW0Q5sWXOolR94PlekxsdSpu5f0kVyxlz7ECdkHz3ttOu9RYM7Z6ARyA==
dependencies:
"@types/chroma-js" "^2.0.0"
"@types/lodash" "^4.14.160"
From ffc207281f9f30f4ba895ff0f535fab96f5f3c40 Mon Sep 17 00:00:00 2001
From: Jen Huang
Date: Wed, 30 Jun 2021 09:36:21 -0700
Subject: [PATCH 071/121] Add back min zero value, add validation (#103898)
---
.../components/agent_policy_form.tsx | 20 ++++++++++++++++++-
.../fleet/server/types/models/agent_policy.ts | 2 +-
2 files changed, 20 insertions(+), 2 deletions(-)
diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_form.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_form.tsx
index 475d5a47e3e9a1..f89bd5ef48d729 100644
--- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_form.tsx
+++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_form.tsx
@@ -63,6 +63,15 @@ export const agentPolicyFormValidation = (
errors.namespace = [namespaceValidation.error];
}
+ if (agentPolicy.unenroll_timeout && agentPolicy.unenroll_timeout < 0) {
+ errors.unenroll_timeout = [
+ ,
+ ];
+ }
+
return errors;
};
@@ -314,11 +323,20 @@ export const AgentPolicyForm: React.FunctionComponent = ({
/>
}
>
-
+
{
updateAgentPolicy({
unenroll_timeout: e.target.value ? Number(e.target.value) : 0,
diff --git a/x-pack/plugins/fleet/server/types/models/agent_policy.ts b/x-pack/plugins/fleet/server/types/models/agent_policy.ts
index 2a47c002736b58..840dbd0ccb6074 100644
--- a/x-pack/plugins/fleet/server/types/models/agent_policy.ts
+++ b/x-pack/plugins/fleet/server/types/models/agent_policy.ts
@@ -16,7 +16,7 @@ export const AgentPolicyBaseSchema = {
namespace: NamespaceSchema,
description: schema.maybe(schema.string()),
is_managed: schema.maybe(schema.boolean()),
- unenroll_timeout: schema.maybe(schema.number()),
+ unenroll_timeout: schema.maybe(schema.number({ min: 0 })),
monitoring_enabled: schema.maybe(
schema.arrayOf(
schema.oneOf([schema.literal(dataTypes.Logs), schema.literal(dataTypes.Metrics)])
From 8c34a88d2675cb32f905ac10518cf8b6582f0507 Mon Sep 17 00:00:00 2001
From: Jason Rhodes
Date: Wed, 30 Jun 2021 12:45:59 -0400
Subject: [PATCH 072/121] Fix inaccurate Kibana status message when Kibana is
in a yellow "degraded" state (#103816)
* Correctly orders imports via ESLint
* Accounts for "yellow" status
We should do much better than this.
a) We shouldn't be converting the statuses to colors in the first place
b) We shouldn't always show the same message for all non-green statuses
c) We shouldn't link to kibana status when we are the kibana monitoring product
---
.../components/cluster/overview/helpers.js | 28 +++++++++----------
1 file changed, 14 insertions(+), 14 deletions(-)
diff --git a/x-pack/plugins/monitoring/public/components/cluster/overview/helpers.js b/x-pack/plugins/monitoring/public/components/cluster/overview/helpers.js
index 9fe22a6a4f85b7..5c766a23558de1 100644
--- a/x-pack/plugins/monitoring/public/components/cluster/overview/helpers.js
+++ b/x-pack/plugins/monitoring/public/components/cluster/overview/helpers.js
@@ -5,20 +5,21 @@
* 2.0.
*/
-import React from 'react';
-import { i18n } from '@kbn/i18n';
-import { get } from 'lodash';
-import { formatBytesUsage, formatPercentageUsage, formatNumber } from '../../../lib/format_number';
import {
- EuiSpacer,
- EuiFlexItem,
EuiFlexGroup,
- EuiTitle,
- EuiIcon,
+ EuiFlexItem,
EuiHealth,
- EuiText,
+ EuiIcon,
EuiLink,
+ EuiSpacer,
+ EuiText,
+ EuiTitle,
} from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
+import { get } from 'lodash';
+import React from 'react';
+import { Legacy } from '../../../legacy_shims';
+import { formatBytesUsage, formatNumber, formatPercentageUsage } from '../../../lib/format_number';
export function HealthLabel(props) {
if (props.status === 'green') {
@@ -42,15 +43,14 @@ export function HealthLabel(props) {
}
}
- if (product === 'kb' && status === 'red') {
+ // TODO: Use the actual service level statuses instead of converting them to colors
+ if (product === 'kb' && (status === 'yellow' || status === 'red')) {
return (
{i18n.translate('xpack.monitoring.cluster.health.pluginIssues', {
- defaultMessage: 'Some plugins are experiencing issues. Check ',
+ defaultMessage: 'Some plugins may be experiencing issues. Please check ',
})}
-
- status
-
+ the Kibana status page .
);
}
From c5c68749b4040b6eb249f190830bfc7b769ff035 Mon Sep 17 00:00:00 2001
From: Domenico Andreoli
Date: Wed, 30 Jun 2021 18:49:43 +0200
Subject: [PATCH 073/121] Refactor security_solution's Cypress package
(#103261)
* Define reporter configuration in one place: 'yarn cypress:run:reporter'
* Define junit generation in one place: 'yarn junit:merge'
---
x-pack/plugins/security_solution/package.json | 11 +++++++----
1 file changed, 7 insertions(+), 4 deletions(-)
diff --git a/x-pack/plugins/security_solution/package.json b/x-pack/plugins/security_solution/package.json
index f35974d84164e5..104c6120ecb39b 100644
--- a/x-pack/plugins/security_solution/package.json
+++ b/x-pack/plugins/security_solution/package.json
@@ -7,12 +7,15 @@
"scripts": {
"extract-mitre-attacks": "node scripts/extract_tactics_techniques_mitre.js && node ../../../scripts/eslint ./public/detections/mitre/mitre_tactics_techniques.ts --fix",
"build-beat-doc": "node scripts/beat_docs/build.js && node ../../../scripts/eslint ./server/utils/beat_schema/fields.ts --fix",
- "cypress:open": "../../../node_modules/.bin/cypress open --config-file ./cypress/cypress.json",
+ "cypress": "../../../node_modules/.bin/cypress",
+ "cypress:open": "yarn cypress open --config-file ./cypress/cypress.json",
"cypress:open-as-ci": "node ../../../scripts/functional_tests --config ../../test/security_solution_cypress/visual_config.ts",
- "cypress:run": "../../../node_modules/.bin/cypress run --browser chrome --headless --spec ./cypress/integration/**/*.spec.ts --config-file ./cypress/cypress.json --reporter ../../../node_modules/cypress-multi-reporters --reporter-options configFile=./cypress/reporter_config.json; status=$?; ../../../node_modules/.bin/mochawesome-merge ../../../target/kibana-security-solution/cypress/results/mochawesome*.json > ../../../target/kibana-security-solution/cypress/results/output.json; ../../../node_modules/.bin/marge ../../../target/kibana-security-solution/cypress/results/output.json --reportDir ../../../target/kibana-security-solution/cypress/results; mkdir -p ../../../target/junit && cp ../../../target/kibana-security-solution/cypress/results/*.xml ../../../target/junit/ && exit $status;",
+ "cypress:run": "yarn cypress:run:reporter --browser chrome --headless --spec './cypress/integration/**/*.spec.ts'; status=$?; yarn junit:merge && exit $status",
+ "cypress:run:firefox": "yarn cypress:run:reporter --browser firefox --headless --spec './cypress/integration/**/*.spec.ts'; status=$?; yarn junit:merge && exit $status",
+ "cypress:run:reporter": "yarn cypress run --config-file ./cypress/cypress.json --reporter ../../../node_modules/cypress-multi-reporters --reporter-options configFile=./cypress/reporter_config.json",
"cypress:run-as-ci": "node --max-old-space-size=2048 ../../../scripts/functional_tests --config ../../test/security_solution_cypress/cli_config.ts",
- "cypress:run:firefox": "../../../node_modules/.bin/cypress run --browser firefox --headless --spec ./cypress/integration/**/*.spec.ts --config-file ./cypress/cypress.json --reporter ../../../node_modules/cypress-multi-reporters --reporter-options configFile=./cypress/reporter_config.json; status=$?; ../../../node_modules/.bin/mochawesome-merge ../../../target/kibana-security-solution/cypress/results/mochawesome*.json > ../../../target/kibana-security-solution/cypress/results/output.json; ../../../node_modules/.bin/marge ../../../target/kibana-security-solution/cypress/results/output.json --reportDir ../../../target/kibana-security-solution/cypress/results; mkdir -p ../../../target/junit && cp ../../../target/kibana-security-solution/cypress/results/*.xml ../../../target/junit/ && exit $status;",
"cypress:run-as-ci:firefox": "node --max-old-space-size=2048 ../../../scripts/functional_tests --config ../../test/security_solution_cypress/config.firefox.ts",
+ "junit:merge": "../../../node_modules/.bin/mochawesome-merge ../../../target/kibana-security-solution/cypress/results/mochawesome*.json > ../../../target/kibana-security-solution/cypress/results/output.json && ../../../node_modules/.bin/marge ../../../target/kibana-security-solution/cypress/results/output.json --reportDir ../../../target/kibana-security-solution/cypress/results && mkdir -p ../../../target/junit && cp ../../../target/kibana-security-solution/cypress/results/*.xml ../../../target/junit/",
"test:generate": "node scripts/endpoint/resolver_generator"
}
-}
\ No newline at end of file
+}
From c5741e8b77dadbba46bf3261a63796de505abd63 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?S=C3=A9bastien=20Loix?=
Date: Wed, 30 Jun 2021 17:59:14 +0100
Subject: [PATCH 074/121] [Amsterdam theme] Fix issues Index management
(#103465)
Co-authored-by: Elizabet Oliveira
---
.../component_templates.scss | 13 ++++++
.../component_templates_list_item.scss | 2 +-
.../components/filter_list_button.tsx | 46 ++++++++++---------
3 files changed, 38 insertions(+), 23 deletions(-)
diff --git a/x-pack/plugins/index_management/public/application/components/component_templates/component_template_selector/component_templates.scss b/x-pack/plugins/index_management/public/application/components/component_templates/component_template_selector/component_templates.scss
index c603224394919b..53636603fbe361 100644
--- a/x-pack/plugins/index_management/public/application/components/component_templates/component_template_selector/component_templates.scss
+++ b/x-pack/plugins/index_management/public/application/components/component_templates/component_template_selector/component_templates.scss
@@ -1,6 +1,7 @@
/**
* [1] Will center vertically the empty search result
+ * [2] Align the height with the search input height
*/
$heightHeader: $euiSizeL * 2;
@@ -22,10 +23,22 @@ $heightHeader: $euiSizeL * 2;
&__searchBox {
border-bottom: $euiBorderThin;
border-top: $euiBorderThin;
+ border-top-right-radius: 0;
+ border-bottom-right-radius: 0;
box-shadow: none;
max-width: initial;
}
+ &__filterListButton {
+ border-bottom: $euiBorderThin;
+ border-top: $euiBorderThin;
+ border-left: $euiBorderThin;
+ border-top-left-radius: 0;
+ border-bottom-left-radius: 0;
+ box-shadow: none;
+ height: $euiSizeXXL; /* [2] */
+ }
+
&__listWrapper {
height: calc(100% - #{$heightHeader});
diff --git a/x-pack/plugins/index_management/public/application/components/component_templates/component_template_selector/component_templates_list_item.scss b/x-pack/plugins/index_management/public/application/components/component_templates/component_template_selector/component_templates_list_item.scss
index 44ba20eed44aee..cff38f115258d7 100644
--- a/x-pack/plugins/index_management/public/application/components/component_templates/component_template_selector/component_templates_list_item.scss
+++ b/x-pack/plugins/index_management/public/application/components/component_templates/component_template_selector/component_templates_list_item.scss
@@ -1,5 +1,5 @@
.componentTemplatesListItem {
- background-color: $euiColorGhost;
+ background-color: $euiPageBackgroundColor;
padding: $euiSizeM;
border-bottom: $euiBorderThin;
position: relative;
diff --git a/x-pack/plugins/index_management/public/application/components/component_templates/component_template_selector/components/filter_list_button.tsx b/x-pack/plugins/index_management/public/application/components/component_templates/component_template_selector/components/filter_list_button.tsx
index 522725ac679e26..ce7b164eb79950 100644
--- a/x-pack/plugins/index_management/public/application/components/component_templates/component_template_selector/components/filter_list_button.tsx
+++ b/x-pack/plugins/index_management/public/application/components/component_templates/component_template_selector/components/filter_list_button.tsx
@@ -7,7 +7,7 @@
import React, { useState } from 'react';
import { FormattedMessage } from '@kbn/i18n/react';
-import { EuiFilterButton, EuiPopover, EuiFilterSelectItem } from '@elastic/eui';
+import { EuiFilterButton, EuiPopover, EuiFilterSelectItem, EuiFilterGroup } from '@elastic/eui';
interface Filter {
name: string;
@@ -67,26 +67,28 @@ export function FilterListButton({ onChange, filters }: Props) {
);
return (
-
-
- {Object.entries(filters).map(([filter, item], index) => (
- toggleFilter(filter)}
- data-test-subj="filterItem"
- >
- {(item as Filter).name}
-
- ))}
-
-
+
+
+
+ {Object.entries(filters).map(([filter, item], index) => (
+ toggleFilter(filter)}
+ data-test-subj="filterItem"
+ >
+ {(item as Filter).name}
+
+ ))}
+
+
+
);
}
From 5cf0fead026c8036458f6f9c6ecac90af6914994 Mon Sep 17 00:00:00 2001
From: Nathan Reese
Date: Wed, 30 Jun 2021 10:59:27 -0600
Subject: [PATCH 075/121] [Maps] deprecate map.proxyElasticMapsServiceInMaps
(#103740)
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
---
docs/maps/connect-to-ems.asciidoc | 11 -----------
docs/setup/settings.asciidoc | 3 ++-
x-pack/plugins/maps/server/index.ts | 29 +++++++++++++++++++++++++++++
3 files changed, 31 insertions(+), 12 deletions(-)
diff --git a/docs/maps/connect-to-ems.asciidoc b/docs/maps/connect-to-ems.asciidoc
index 1db9dd5ee1123c..6ea879e64d14d3 100644
--- a/docs/maps/connect-to-ems.asciidoc
+++ b/docs/maps/connect-to-ems.asciidoc
@@ -13,17 +13,6 @@ EMS requests are made to the following domains:
Maps makes requests directly from the browser to EMS.
-[float]
-=== Connect to Elastic Maps Service from an internal network
-
-To connect to EMS when your Kibana server and browser are in an internal network:
-
-. Set `map.proxyElasticMapsServiceInMaps` to `true` in your <> file to proxy EMS requests through the Kibana server.
-. Update your firewall rules to allow connections from your Kibana server to the EMS domains.
-
-NOTE: Coordinate map and region map visualizations do not support `map.proxyElasticMapsServiceInMaps` and will not proxy EMS requests through the Kibana server.
-
-
[float]
=== Disable Elastic Maps Service
diff --git a/docs/setup/settings.asciidoc b/docs/setup/settings.asciidoc
index a3f815e5ea5044..d9a48835553cfe 100644
--- a/docs/setup/settings.asciidoc
+++ b/docs/setup/settings.asciidoc
@@ -374,7 +374,8 @@ When `includeElasticMapsService` is turned off, only tile layer configured by <<
| Specifies the URL of a self hosted <>
| `map.proxyElasticMapsServiceInMaps:`
- | Set to `true` to proxy all <> Elastic Maps Service
+ | deprecated:[7.14.0,"In 8.0 and later, this setting will no longer be supported."]
+ Set to `true` to proxy all <> Elastic Maps Service
requests through the {kib} server. *Default: `false`*
| [[regionmap-settings]] `map.regionmap:` {ess-icon}
diff --git a/x-pack/plugins/maps/server/index.ts b/x-pack/plugins/maps/server/index.ts
index ebae257b03f4ae..331e9ac70afdd4 100644
--- a/x-pack/plugins/maps/server/index.ts
+++ b/x-pack/plugins/maps/server/index.ts
@@ -24,6 +24,35 @@ export const config: PluginConfigDescriptor = {
},
schema: configSchema,
deprecations: () => [
+ (
+ completeConfig: Record,
+ rootPath: string,
+ addDeprecation: AddConfigDeprecation
+ ) => {
+ if (_.get(completeConfig, 'map.proxyElasticMapsServiceInMaps') === undefined) {
+ return completeConfig;
+ }
+ addDeprecation({
+ documentationUrl:
+ 'https://www.elastic.co/guide/en/kibana/current/maps-connect-to-ems.html#elastic-maps-server',
+ message: i18n.translate('xpack.maps.deprecation.proxyEMS.message', {
+ defaultMessage:
+ 'map.proxyElasticMapsServiceInMaps is deprecated and will be removed in 8.0.',
+ }),
+ correctiveActions: {
+ manualSteps: [
+ i18n.translate('xpack.maps.deprecation.proxyEMS.step1', {
+ defaultMessage:
+ 'Remove "map.proxyElasticMapsServiceInMaps" in the Kibana config file, CLI flag, or environment variable (in Docker only).',
+ }),
+ i18n.translate('xpack.maps.deprecation.proxyEMS.step2', {
+ defaultMessage: 'Host Elastic Maps Service locally.',
+ }),
+ ],
+ },
+ });
+ return completeConfig;
+ },
(
completeConfig: Record,
rootPath: string,
From 524fe6dfe23aac77d2b18a52168372c46ca9fee1 Mon Sep 17 00:00:00 2001
From: Kaarina Tungseth
Date: Wed, 30 Jun 2021 12:10:04 -0500
Subject: [PATCH 076/121] [DOCS] Updates to the Reporting docs (#101326)
* [DOCS] Updates to thee Reporting docs
* Adds the main sharing page
* Final changes
* Changed configuring-reporting link to secure-reporting
* Updates from meeting with Tim and Larry
* Moves reporting and sharing content above ML
* Update docs/setup/configuring-reporting.asciidoc
Co-authored-by: Larry Gregory
* Review comments from Tim and Larry
* Fixes broken links
* Fixes redirect
* Fixes broken link from ES docs
* Adds metadata to changed pages
* Review comments
Co-authored-by: Larry Gregory
---
docs/canvas/canvas-share-workpad.asciidoc | 42 --
.../development/csv-integration.asciidoc | 0
.../architecture}/development/index.asciidoc | 0
.../development/pdf-integration.asciidoc | 0
docs/developer/architecture/index.asciidoc | 3 +
docs/redirects.asciidoc | 9 +
docs/settings/reporting-settings.asciidoc | 386 +++++++++---------
docs/setup/configuring-reporting.asciidoc | 235 +++++++++++
docs/setup/embedding.asciidoc | 63 ---
docs/user/canvas.asciidoc | 14 +-
docs/user/dashboard/dashboard.asciidoc | 2 +-
docs/user/discover.asciidoc | 6 +
docs/user/index.asciidoc | 11 +-
.../production-considerations/index.asciidoc | 1 +
...porting-production-considerations.asciidoc | 36 ++
.../automating-report-generation.asciidoc | 49 +--
docs/user/reporting/chromium-sandbox.asciidoc | 26 --
.../reporting/configuring-reporting.asciidoc | 78 ----
.../reporting/generating-reports.asciidoc | 1 -
docs/user/reporting/gs-index.asciidoc | 28 --
.../images/embed-code-public-url.png | Bin 0 -> 37509 bytes
.../reporting/images/permalink-public-url.png | Bin 0 -> 24855 bytes
docs/user/reporting/index.asciidoc | 189 +++++----
docs/user/reporting/network-policy.asciidoc | 71 ----
docs/user/reporting/report-intervals.asciidoc | 12 -
.../reporting-troubleshooting.asciidoc | 37 +-
docs/user/reporting/script-example.asciidoc | 28 +-
docs/user/reporting/watch-example.asciidoc | 36 +-
.../security/authentication/index.asciidoc | 55 ++-
docs/user/security/reporting.asciidoc | 213 ----------
docs/user/setup.asciidoc | 9 +-
31 files changed, 706 insertions(+), 934 deletions(-)
delete mode 100644 docs/canvas/canvas-share-workpad.asciidoc
rename docs/{user/reporting => developer/architecture}/development/csv-integration.asciidoc (100%)
rename docs/{user/reporting => developer/architecture}/development/index.asciidoc (100%)
rename docs/{user/reporting => developer/architecture}/development/pdf-integration.asciidoc (100%)
create mode 100644 docs/setup/configuring-reporting.asciidoc
delete mode 100644 docs/setup/embedding.asciidoc
create mode 100644 docs/user/production-considerations/reporting-production-considerations.asciidoc
delete mode 100644 docs/user/reporting/chromium-sandbox.asciidoc
delete mode 100644 docs/user/reporting/configuring-reporting.asciidoc
delete mode 100644 docs/user/reporting/generating-reports.asciidoc
delete mode 100644 docs/user/reporting/gs-index.asciidoc
create mode 100644 docs/user/reporting/images/embed-code-public-url.png
create mode 100644 docs/user/reporting/images/permalink-public-url.png
delete mode 100644 docs/user/reporting/network-policy.asciidoc
delete mode 100644 docs/user/reporting/report-intervals.asciidoc
delete mode 100644 docs/user/security/reporting.asciidoc
diff --git a/docs/canvas/canvas-share-workpad.asciidoc b/docs/canvas/canvas-share-workpad.asciidoc
deleted file mode 100644
index 348d15f39ad76e..00000000000000
--- a/docs/canvas/canvas-share-workpad.asciidoc
+++ /dev/null
@@ -1,42 +0,0 @@
-[role="xpack"]
-[[workpad-share-options]]
-== Share your workpad
-
-When you've finished your workpad, you can share it outside of {kib}.
-
-For information on how to create PDFs and POST URLs, refer to <>.
-
-[float]
-[[export-single-workpad]]
-=== Export workpads
-
-Create a JSON file of your workpad that you can export outside of {kib}.
-
-To begin, click *Share > Download as JSON*.
-
-[role="screenshot"]
-image::images/canvas-export-workpad.png[Export single workpad through JSON, from Share dropdown]
-
-Want to export multiple workpads? Go to the *Canvas* home page, select the workpads you want to export, then click *Export*.
-
-[float]
-[[add-workpad-website]]
-=== Share the workpad on a website
-
-beta[] *Canvas* allows you to create _shareables_, which are workpads that you download and securely share on any website.
-To customize the behavior of the workpad on your website, you can choose to autoplay the pages or hide the workpad toolbar.
-
-. Click *Share > Share on a website*.
-
-. Follow the *Share on a website* instructions.
-
-. To customize the workpad behavior to autoplay the pages or hide the toolbar, use the inline parameters.
-+
-To make sure that your data remains secure, the data in the JSON file is not connected to {kib}. *Canvas* does not display elements that manipulate the data on the workpad.
-+
-[role="screenshot"]
-image::canvas/images/canvas-embed_workpad.gif[Image showing how to share the workpad on a website]
-+
-NOTE: Shareable workpads encode the current state of the workpad in a JSON file. When you make changes to the workpad, the changes do not appear in the shareable workpad on your website.
-
-. To change the settings, click the settings icon, then choose the settings you want to use.
diff --git a/docs/user/reporting/development/csv-integration.asciidoc b/docs/developer/architecture/development/csv-integration.asciidoc
similarity index 100%
rename from docs/user/reporting/development/csv-integration.asciidoc
rename to docs/developer/architecture/development/csv-integration.asciidoc
diff --git a/docs/user/reporting/development/index.asciidoc b/docs/developer/architecture/development/index.asciidoc
similarity index 100%
rename from docs/user/reporting/development/index.asciidoc
rename to docs/developer/architecture/development/index.asciidoc
diff --git a/docs/user/reporting/development/pdf-integration.asciidoc b/docs/developer/architecture/development/pdf-integration.asciidoc
similarity index 100%
rename from docs/user/reporting/development/pdf-integration.asciidoc
rename to docs/developer/architecture/development/pdf-integration.asciidoc
diff --git a/docs/developer/architecture/index.asciidoc b/docs/developer/architecture/index.asciidoc
index 1a0e7bab2f8f8f..90a0972d65f2fc 100644
--- a/docs/developer/architecture/index.asciidoc
+++ b/docs/developer/architecture/index.asciidoc
@@ -24,6 +24,7 @@ A few notable services are called out below.
* <>
* <>
* <>
+* <>
include::kibana-platform-plugin-api.asciidoc[leveloffset=+1]
@@ -52,3 +53,5 @@ include::security/index.asciidoc[leveloffset=+1]
include::add-data-tutorials.asciidoc[leveloffset=+1]
include::development-visualize-index.asciidoc[leveloffset=+1]
+
+include::development/index.asciidoc[leveloffset=+1]
diff --git a/docs/redirects.asciidoc b/docs/redirects.asciidoc
index a14bda2bf5a986..eb3130ba6fdb5f 100644
--- a/docs/redirects.asciidoc
+++ b/docs/redirects.asciidoc
@@ -308,3 +308,12 @@ This content has moved. Refer to <>.
This content has moved. Refer to <>.
+[role="exclude",id="embedding"]
+== Embed {kib} content in a web page
+
+This content has moved. Refer to <> and <>.
+
+[role="exclude",id="reporting-troubleshooting-system-dependencies"]
+== System dependencies
+
+This content has moved. Refer to <>.
\ No newline at end of file
diff --git a/docs/settings/reporting-settings.asciidoc b/docs/settings/reporting-settings.asciidoc
index 32d80d7e0c766d..b339daf3d36f76 100644
--- a/docs/settings/reporting-settings.asciidoc
+++ b/docs/settings/reporting-settings.asciidoc
@@ -4,29 +4,75 @@
++++
Reporting settings
++++
+:keywords: administrator, reference, setup, reporting
+:description: A reference of the reporting settings administrators configure in kibana.yml.
You can configure `xpack.reporting` settings in your `kibana.yml` to:
+* <>
+* <>
+* <>
* <>
* <>
* <>
+* <>
* <>
[float]
[[general-reporting-settings]]
-==== General reporting settings
+==== Enable reporting
-[cols="2*<"]
-|===
-| [[xpack-enable-reporting]]`xpack.reporting.enabled` {ess-icon}
- | Set to `false` to disable the {report-features}.
+[[xpack-enable-reporting]]`xpack.reporting.enabled` {ess-icon}::
+When `true`, enables the {report-features}. The {report-features} are automatically enabled in {kib}. The default is `true`.
-|[[xpack-reporting-encryptionKey]] `xpack.reporting.encryptionKey` {ess-icon}
- | Set to an alphanumeric, at least 32 characters long text string. By default, {kib} will generate a random key when it
- starts, which will cause pending reports to fail after restart. Configure this
- setting to preserve the same key across multiple restarts and multiple instances of {kib}.
+[float]
+[[encryption-keys]]
+==== Encryption key setting
+
+By default, an encryption key is generated for the {report-features} each
+time you start {kib}. If a static encryption key is not persisted in
+the {kib} configuration, any pending reports fail when you restart {kib}.
+
+If you are load balancing across multiple {kib} instances, each instance needs to have
+the same reporting encryption key. Otherwise, report generation fails if a
+report is queued through one instance, and another instance picks up the job
+from the report queue. The instance that picks up the job is unable to decrypt the
+reporting job metadata.
+
+[[xpack-reporting-encryptionKey]] `xpack.reporting.encryptionKey` {ess-icon}::
+The static encryption key for reporting. Use an alphanumeric text string that is at least 32 characters. By default, {kib} generates a random key when it starts, which causes pending reports to fail after restart. Configure `xpack.reporting.encryptionKey` to preserve the same key across multiple restarts and multiple {kib} instances.
-|===
+[source,yaml]
+--------------------------------------------------------------------------------
+xpack.reporting.encryptionKey: "something_secret"
+--------------------------------------------------------------------------------
+
+[float]
+[[report-indices]]
+==== Reporting index setting
+
+
+
+`xpack.reporting.index`::
+deprecated:[7.11.0,This setting will be removed in 8.0.0.] Multitenancy by changing `kibana.index` is unsupported starting in 8.0.0. For more details, refer to https://ela.st/kbn-remove-legacy-multitenancy[8.0 Breaking Changes]. When you divide workspaces in an Elastic cluster using multiple {kib} instances with a different `kibana.index` setting per instance, you must set a unique `xpack.reporting.index` setting per `kibana.index`. Otherwise, report generation periodically fails if a report is queued through an instance with one `kibana.index` setting, and an instance with a different `kibana.index` attempts to claim the job. Reporting uses a weekly index in {es} to store the reporting job and the report content. The index is automatically created if it does not already exist. Configure a unique value for `xpack.reporting.index`, beginning with `.reporting-`, for every {kib} instance that has a unique <> setting. Defaults to `.reporting`.
+
+{kib} instance A:
+[source,yaml]
+--------------------------------------------------------------------------------
+kibana.index: ".kibana-a"
+xpack.reporting.index: ".reporting-a"
+xpack.reporting.encryptionKey: "something_secret"
+--------------------------------------------------------------------------------
+
+{kib} instance B:
+[source,yaml]
+--------------------------------------------------------------------------------
+kibana.index: ".kibana-b"
+xpack.reporting.index: ".reporting-b"
+xpack.reporting.encryptionKey: "something_secret"
+--------------------------------------------------------------------------------
+
+NOTE: If security is enabled, the `xpack.reporting.index` setting should begin with `.reporting-` for the `kibana_system` role to have the necessary privileges over the index.
[float]
[[reporting-kibana-server-settings]]
@@ -34,44 +80,37 @@ You can configure `xpack.reporting` settings in your `kibana.yml` to:
Reporting opens the {kib} web interface in a server process to generate
screenshots of {kib} visualizations. In most cases, the default settings
-will work and you don't need to configure Reporting to communicate with {kib}.
-However, if your client connections must go through a reverse-proxy
-to access {kib}, Reporting configuration must have the proxy port, protocol,
-and hostname set in the `xpack.reporting.kibanaServer.*` settings.
+work and you don't need to configure the {report-features} to communicate with {kib}.
+
+If your {kib} instance requires a reverse proxy (such as NGINX, Apache, etc.) for
+access, because of rewrite rules or special headers being added by the proxy,
+you must configure the `xpack.reporting.kibanaServer` settings to make
+the headless browser process connect to the proxy.
[NOTE]
-====
-If a reverse-proxy carries encrypted traffic from end-user
+============
+If a reverse proxy carries encrypted traffic from user
clients back to a {kib} server, the proxy port, protocol, and hostname
-in Reporting settings must be valid for the encryption that the Reporting
-browser will receive. Encrypted communications will fail if there are
+in `xpack.reporting.kibanaServer` must be valid for the encryption that the Reporting
+browser receives. Encrypted communications fail if there are
mismatches in the host information between the request and the certificate on the server.
Configuring the `xpack.reporting.kibanaServer` settings to point to a
proxy host requires that the {kib} server has network access to the proxy.
-====
-
-[cols="2*<"]
-|===
-| `xpack.reporting.kibanaServer.port`
- | The port for accessing {kib}, if different from the <> value.
+============
-| `xpack.reporting.kibanaServer.protocol`
- | The protocol for accessing {kib}, typically `http` or `https`.
+`xpack.reporting.kibanaServer.port`:: The port for accessing {kib}, if different from the <> value.
-|[[xpack-kibanaServer-hostname]] `xpack.reporting.kibanaServer.hostname`
- | The hostname for accessing {kib}, if different from the <> value.
+`xpack.reporting.kibanaServer.protocol`::
+The protocol for accessing {kib}, typically `http` or `https`.
-|===
+[[xpack-kibanaServer-hostname]] `xpack.reporting.kibanaServer.hostname`::
+The hostname for accessing {kib}, if different from the <> value.
-[NOTE]
-============
-Reporting authenticates requests on the {kib} page only when the hostname matches the
-<> setting. Therefore Reporting would fail if the
+NOTE: Reporting authenticates requests on the {kib} page only when the hostname matches the
+<> setting. Therefore Reporting fails if the
set value redirects to another server. For that reason, `"0"` is an invalid setting
because, in the Reporting browser, it becomes an automatic redirect to `"0.0.0.0"`.
-============
-
[float]
[[reporting-job-queue-settings]]
@@ -81,98 +120,50 @@ Reporting generates reports in the background and jobs are coordinated using doc
in {es}. Depending on how often you generate reports and the overall number of
reports, you might need to change the following settings.
-[cols="2*<"]
-|===
-| `xpack.reporting.queue.indexInterval`
- | How often the index that stores reporting jobs rolls over to a new index.
- Valid values are `year`, `month`, `week`, `day`, and `hour`. Defaults to `week`.
-
-| `xpack.reporting.queue.pollEnabled` {ess-icon}
- | Set to `true` (default) to enable the {kib} instance to poll the index for
- pending jobs and claim them for execution. Setting this to `false` allows the
- {kib} instance to only add new jobs to the reporting queue, list jobs, and
- provide the downloads to completed report through the UI.
+`xpack.reporting.queue.indexInterval`::
+How often the index that stores reporting jobs rolls over to a new index. Valid values are `year`, `month`, `week`, `day`, and `hour`. Defaults to `week`.
-|===
+`xpack.reporting.queue.pollEnabled` {ess-icon}::
+Set to `true` (default) to enable the {kib} instance to poll the index for pending jobs and claim them for execution. Setting this to `false` allows the {kib} instance to only add new jobs to the reporting queue, list jobs, and provide the downloads to completed report through the UI.
-[NOTE]
-============
-Running multiple instances of {kib} in a cluster for load balancing of
+NOTE: Running multiple instances of {kib} in a cluster for load balancing of
reporting requires identical values for <> and, if
security is enabled, <>.
-============
-[cols="2*<"]
-|===
-| `xpack.reporting.queue.pollInterval`
- | Specify the {time-units}[time] that the reporting poller waits between polling the index for any
- pending Reporting jobs. Can be specified as number of milliseconds. Defaults to `3s`.
+`xpack.reporting.queue.pollInterval`::
+Specifies the {time-units}[time] that the reporting poller waits between polling the index for any pending Reporting jobs. Can be specified as number of milliseconds. Defaults to `3s`.
-| [[xpack-reporting-q-timeout]] `xpack.reporting.queue.timeout` {ess-icon}
- | {time-units}[How long] each worker has to produce a report. If your machine is slow or under heavy
- load, you might need to increase this timeout. If a Reporting job execution goes over this time limit, the job is marked as a
- failure and no download will be available. Can be specified as number of milliseconds.
- Defaults to `2m`.
-
-|===
+[[xpack-reporting-q-timeout]] `xpack.reporting.queue.timeout` {ess-icon}::
+{time-units}[How long] each worker has to produce a report. If your machine is slow or under heavy load, you might need to increase this timeout. If a Reporting job execution goes over this time limit, the job is marked as a failure and no download will be available. Can be specified as number of milliseconds. Defaults to `2m`.
[float]
[[reporting-capture-settings]]
==== Capture settings
-Reporting works by capturing screenshots from {kib}. The following settings
-control the capturing process.
-
-[cols="2*<"]
-|===
-a| `xpack.reporting.capture.timeouts`
-`.openUrl` {ess-icon}
- | Specify the {time-units}[time] to allow the Reporting browser to wait for the "Loading..." screen
- to dismiss and find the initial data for the page. If the time is exceeded, a screenshot is captured showing the current
- page, and the download link shows a warning message. Can be specified as number of milliseconds.
- Defaults to `1m`.
-
-a| `xpack.reporting.capture.timeouts`
-`.waitForElements` {ess-icon}
- | Specify the {time-units}[time] to allow the Reporting browser to wait for all visualization panels
- to load on the page. If the time is exceeded, a screenshot is captured showing the current page, and the download link shows
- a warning message. Can be specified as number of milliseconds.
- Defaults to `30s`.
-
-a| `xpack.reporting.capture.timeouts`
-`.renderComplete` {ess-icon}
- | Specify the {time-units}[time] to allow the Reporting browser to wait for all visualizations to
- fetch and render the data. If the time is exceeded, a screenshot is captured showing the current page, and the download link shows a
- warning message. Can be specified as number of milliseconds.
- Defaults to `30s`.
-
-|===
+Reporting works by capturing screenshots from {kib}. The following settings control the capturing process.
-[NOTE]
-============
-If any timeouts from `xpack.reporting.capture.timeouts.*` settings occur when
+`xpack.reporting.capture.timeouts.openUrl` {ess-icon}::
+Specify the {time-units}[time] to allow the Reporting browser to wait for the "Loading..." screen to dismiss and find the initial data for the page. If the time is exceeded, a screenshot is captured showing the current page, and the download link shows a warning message. Can be specified as number of milliseconds. Defaults to `1m`.
+
+`xpack.reporting.capture.timeouts.waitForElements` {ess-icon}::
+ Specify the {time-units}[time] to allow the Reporting browser to wait for all visualization panels to load on the page. If the time is exceeded, a screenshot is captured showing the current page, and the download link shows a warning message. Can be specified as number of milliseconds. Defaults to `30s`.
+
+`xpack.reporting.capture.timeouts.renderComplete` {ess-icon}::
+ Specify the {time-units}[time] to allow the Reporting browser to wait for all visualizations to fetch and render the data. If the time is exceeded, a screenshot is captured showing the current page, and the download link shows a warning message. Can be specified as number of milliseconds. Defaults to `30s`.
+
+NOTE: If any timeouts from `xpack.reporting.capture.timeouts.*` settings occur when
running a report job, Reporting will log the error and try to continue
capturing the page with a screenshot. As a result, a download will be
available, but there will likely be errors in the visualizations in the report.
-============
-[cols="2*<"]
-|===
-| `xpack.reporting.capture.maxAttempts` {ess-icon}
- | If capturing a report fails for any reason, {kib} will re-attempt other reporting
- job, as many times as this setting. Defaults to `3`.
+`xpack.reporting.capture.maxAttempts` {ess-icon}::
+If capturing a report fails for any reason, {kib} will re-attempt other reporting job, as many times as this setting. Defaults to `3`.
-| `xpack.reporting.capture.loadDelay`
- | Specify the {time-units}[amount of time] before taking a screenshot when visualizations are not evented.
- All visualizations that ship with {kib} are evented, so this setting should not have much effect. If you are seeing empty images
- instead of visualizations, try increasing this value.
- Defaults to `3s`.
+`xpack.reporting.capture.loadDelay`::
+Specify the {time-units}[amount of time] before taking a screenshot when visualizations are not evented. All visualizations that ship with {kib} are evented, so this setting should not have much effect. If you are seeing empty images instead of visualizations, try increasing this value. Defaults to `3s`.
-| [[xpack-reporting-browser]] `xpack.reporting.capture.browser.type` {ess-icon}
- | Specifies the browser to use to capture screenshots. This setting exists for
- backward compatibility. The only valid option is `chromium`.
-
-|===
+[[xpack-reporting-browser]] `xpack.reporting.capture.browser.type` {ess-icon}::
+Specifies the browser to use to capture screenshots. This setting exists for backward compatibility. The only valid option is `chromium`.
[float]
[[reporting-chromium-settings]]
@@ -180,42 +171,85 @@ available, but there will likely be errors in the visualizations in the report.
When <> is set to `chromium` (default) you can also specify the following settings.
-[cols="2*<"]
-|===
-a| `xpack.reporting.capture.browser`
-`.chromium.disableSandbox`
- | It is recommended that you research the feasibility of enabling unprivileged user namespaces.
- See Chromium Sandbox for additional information. Defaults to false for all operating systems except Debian,
- Red Hat Linux, and CentOS which use true.
+`xpack.reporting.capture.browser.chromium.disableSandbox`::
+It is recommended that you research the feasibility of enabling unprivileged user namespaces. An exception is if you are running {kib} in Docker because the container runs in a user namespace with the built-in seccomp/bpf filters. For more information, refer to <>. Defaults to `false` for all operating systems except Debian, Red Hat Linux, and CentOS, which use `true`.
+
+`xpack.reporting.capture.browser.chromium.proxy.enabled`::
+Enables the proxy for Chromium to use. When set to `true`, you must also specify the `xpack.reporting.capture.browser.chromium.proxy.server` setting. Defaults to `false`.
+
+`xpack.reporting.capture.browser.chromium.proxy.server`::
+The uri for the proxy server. Providing the username and password for the proxy server via the uri is not supported.
+
+`xpack.reporting.capture.browser.chromium.proxy.bypass`::
+An array of hosts that should not go through the proxy server and should use a direct connection instead. Examples of valid entries are "elastic.co", "*.elastic.co", ".elastic.co", ".elastic.co:5601".
+
+[float]
+[[reporting-network-policy]]
+=== Network policy settings
+
+To generate PDF reports, *Reporting* uses the Chromium browser to fully load the {kib} page on the server. This potentially involves sending requests to external hosts. For example, a request might go to an external image server to show a field formatted as an image, or to show an image in a Markdown visualization.
+
+If the Chromium browser is asked to send a request that violates the network policy, *Reporting* stops processing the page before the request goes out, and the report is marked as a failure. Additional information about the event is in the {kib} server logs.
+
+NOTE: {kib} installations are not designed to be publicly accessible over the internet. The Reporting network policy and other capabilities of the Elastic Stack security features do not change this condition.
+
+`xpack.reporting.capture.networkPolicy`::
+Capturing a screenshot from a {kib} page involves sending out requests for all the linked web assets. For example, a Markdown visualization can show an image from a remote server.
+
+`xpack.reporting.capture.networkPolicy.enabled`::
+When `false`, disables the *Reporting* network policy. Defaults to `true`.
+
+`xpack.reporting.capture.networkPolicy.rules`::
+A policy is specified as an array of objects that describe what to allow or deny based on a host or protocol. If a host or protocol is not specified, the rule matches any host or protocol.
-a| `xpack.reporting.capture.browser`
-`.chromium.proxy.enabled`
- | Enables the proxy for Chromium to use. When set to `true`, you must also specify the
- `xpack.reporting.capture.browser.chromium.proxy.server` setting.
- Defaults to `false`.
+The rule objects are evaluated sequentially from the beginning to the end of the array, and continue until there is a matching rule. If no rules allow a request, the request is denied.
-a| `xpack.reporting.capture.browser`
-`.chromium.proxy.server`
- | The uri for the proxy server. Providing the username and password for the proxy server via the uri is not supported.
+[source,yaml]
+-------------------------------------------------------
+# Only allow requests to placeholder.com
+xpack.reporting.capture.networkPolicy:
+ rules: [ { allow: true, host: "placeholder.com" } ]
+-------------------------------------------------------
-a| `xpack.reporting.capture.browser`
-`.chromium.proxy.bypass`
- | An array of hosts that should not go through the proxy server and should use a direct connection instead.
- Examples of valid entries are "elastic.co", "*.elastic.co", ".elastic.co", ".elastic.co:5601".
+[source,yaml]
+-------------------------------------------------------
+# Only allow requests to https://placeholder.com
+xpack.reporting.capture.networkPolicy:
+ rules: [ { allow: true, host: "placeholder.com", protocol: "https:" } ]
+-------------------------------------------------------
-|===
+A final `allow` rule with no host or protocol allows all requests that are not explicitly denied:
+
+[source,yaml]
+-------------------------------------------------------
+# Denies requests from http://placeholder.com, but anything else is allowed.
+xpack.reporting.capture.networkPolicy:
+ rules: [{ allow: false, host: "placeholder.com", protocol: "http:" }, { allow: true }];
+-------------------------------------------------------
+
+A network policy can be composed of multiple rules:
+
+[source,yaml]
+-------------------------------------------------------
+# Allow any request to http://placeholder.com but for any other host, https is required
+xpack.reporting.capture.networkPolicy
+ rules: [
+ { allow: true, host: "placeholder.com", protocol: "http:" },
+ { allow: true, protocol: "https:" },
+ ]
+-------------------------------------------------------
+
+[NOTE]
+============
+The `file:` protocol is always denied, even if no network policy is configured.
+============
[float]
[[reporting-csv-settings]]
==== CSV settings
-[cols="2*<"]
-|===
-| [[xpack-reporting-csv]] `xpack.reporting.csv.maxSizeBytes` {ess-icon}
- | The maximum {byte-units}[byte size] of a CSV file before being truncated. This setting exists to
- prevent large exports from causing performance and storage issues. Can be specified as number of bytes.
- Defaults to `10mb`.
-|===
+[[xpack-reporting-csv]] `xpack.reporting.csv.maxSizeBytes` {ess-icon}::
+The maximum {byte-units}[byte size] of a CSV file before being truncated. This setting exists to prevent large exports from causing performance and storage issues. Can be specified as number of bytes. Defaults to `10mb`.
[NOTE]
============
@@ -230,69 +264,33 @@ on multiple factors:
For information about {kib} memory limits, see <>.
============
-[cols="2*<"]
-|===
+`xpack.reporting.csv.scroll.size`::
+Number of documents retrieved from {es} for each scroll iteration during a CSV export. Defaults to `500`.
-| `xpack.reporting.csv.scroll.size`
- | Number of documents retrieved from {es} for each scroll iteration during a CSV
- export.
- Defaults to `500`.
+`xpack.reporting.csv.scroll.duration`::
+ Amount of {time-units}[time] allowed before {kib} cleans the scroll context during a CSV export. Defaults to `30s`.
-| `xpack.reporting.csv.scroll.duration`
- | Amount of {time-units}[time] allowed before {kib} cleans the scroll context during a CSV export.
- Defaults to `30s`.
+`xpack.reporting.csv.checkForFormulas`::
+Enables a check that warns you when there's a potential formula involved in the output (=, -, +, and @ chars). See OWASP: https://www.owasp.org/index.php/CSV_Injection. Defaults to `true`.
-| `xpack.reporting.csv.checkForFormulas`
- | Enables a check that warns you when there's a potential formula involved in the output (=, -, +, and @ chars).
- See OWASP: https://www.owasp.org/index.php/CSV_Injection
- Defaults to `true`.
-
-| `xpack.reporting.csv` `.enablePanelActionDownload`
- | Enables CSV export from a saved search on a dashboard. This action is available in the dashboard panel menu for the saved search.
- *Note:* This setting exists for backwards compatibility, but is unused and hardcoded to `true`. CSV export from a saved search on a dashboard
- is enabled when Reporting is enabled.
-
-|===
+`xpack.reporting.csv` `.enablePanelActionDownload`::
+Enables CSV export from a saved search on a dashboard. This action is available in the dashboard panel menu for the saved search.
+NOTE: This setting exists for backwards compatibility, but is unused and hardcoded to `true`. CSV export from a saved search on a dashboard is enabled when Reporting is enabled.
[float]
[[reporting-advanced-settings]]
-==== Advanced settings
-
-[cols="2*<"]
-|===
-| `xpack.reporting.capture.networkPolicy`
- | Capturing a screenshot from a {kib} page involves sending out requests for all the linked web assets. For example, a Markdown
- visualization can show an image from a remote server. You can configure what type of requests to allow or filter by setting a
- <> for Reporting.
-
-| `xpack.reporting.index`
- | deprecated:[7.11.0,This setting will be removed in 8.0.] Multitenancy by
- changing `kibana.index` will not be supported starting in 8.0. See
- https://ela.st/kbn-remove-legacy-multitenancy[8.0 Breaking Changes] for more
- details. Reporting uses a weekly index in {es} to store the reporting job and
- the report content. The index is automatically created if it does not already
- exist. Configure this to a unique value, beginning with `.reporting-`, for
- every {kib} instance that has a unique <>
- setting. Defaults to `.reporting`.
-
-| [[xpack-reporting-roles-enabled]] `xpack.reporting.roles.enabled`
- | deprecated:[7.14.0,This setting must be set to `false` in 8.0.] When `true`, grants users
- access to the {report-features} by assigning reporting roles, specified by `xpack.reporting.roles.allow`.
- Granting access to users this way is deprecated. Set to `false` and use
- {kibana-ref}/kibana-privileges.html[{kib} privileges] instead.
- Defaults to `true`.
-
-| `xpack.reporting.roles.allow`
- | deprecated:[7.14.0,This setting will be removed in 8.0.] Specifies the roles,
- in addition to superusers, that can generate reports, using the {ref}/security-api.html#security-role-apis[{es} role management APIs].
- Requires `xpack.reporting.roles.enabled` to be `true`.
- Granting access to users this way is deprecated. Use
- {kibana-ref}/kibana-privileges.html[{kib} privileges] instead.
- Defaults to `[ "reporting_user" ]`.
-
-|===
+==== Security settings
+
+[[xpack-reporting-roles-enabled]] `xpack.reporting.roles.enabled`::
+deprecated:[7.14.0,This setting must be set to `false` in 8.0.] When `true`, grants users access to the {report-features} by assigning reporting roles, specified by `xpack.reporting.roles.allow`. Granting access to users this way is deprecated. Set to `false` and use {kibana-ref}/kibana-privileges.html[{kib} privileges] instead. Defaults to `true`.
[NOTE]
-============
-Each user has access to only their own reports.
-============
+============================================================================
+In 7.x, the default value of `xpack.reporting.roles.enabled` is `true`. To migrate users to the
+new method of securing access to *Reporting*, you must set `xpack.reporting.roles.enabled: false`. In the next major version of {kib}, `false` will be the only valid configuration.
+============================================================================
+
+`xpack.reporting.roles.allow`::
+deprecated:[7.14.0,This setting will be removed in 8.0.] Specifies the roles, in addition to superusers, that can generate reports, using the {ref}/security-api.html#security-role-apis[{es} role management APIs]. Requires `xpack.reporting.roles.enabled` to be `true`. Granting access to users this way is deprecated. Use {kibana-ref}/kibana-privileges.html[{kib} privileges] instead. Defaults to `[ "reporting_user" ]`.
+
+NOTE: Each user has access to only their own reports.
diff --git a/docs/setup/configuring-reporting.asciidoc b/docs/setup/configuring-reporting.asciidoc
new file mode 100644
index 00000000000000..af4fc14448ac57
--- /dev/null
+++ b/docs/setup/configuring-reporting.asciidoc
@@ -0,0 +1,235 @@
+[role="xpack"]
+[[secure-reporting]]
+== Configure reporting in {kib}
+
+++++
+Configure reporting
+++++
+
+To enable users to manually and automatically generate reports, install the reporting packages, grant users access to the {report-features}, and secure the reporting endpoints.
+
+[float]
+[[install-reporting-packages]]
+=== Install the reporting packages
+
+Make sure the {kib} server operating system has the appropriate packages installed for the distribution.
+
+If you are using CentOS/RHEL systems, install the following packages:
+
+* `ipa-gothic-fonts`
+* `xorg-x11-fonts-100dpi`
+* `xorg-x11-fonts-75dpi`
+* `xorg-x11-utils`
+* `xorg-x11-fonts-cyrillic`
+* `xorg-x11-fonts-Type1`
+* `xorg-x11-fonts-misc`
+* `fontconfig`
+* `freetype`
+
+If you are using Ubuntu/Debian systems, install the following packages:
+
+* `fonts-liberation`
+* `libfontconfig1`
+
+If the system is missing dependencies, *Reporting* fails in a non-deterministic way. {kib} runs a self-test at server startup, and
+if it encounters errors, logs them in the Console. The error message does not include
+information about why Chromium failed to run. The most common error message is `Error: connect ECONNREFUSED`, which indicates
+that {kib} could not connect to the Chromium process.
+
+To troubleshoot the problem, start the {kib} server with environment variables that tell Chromium to print verbose logs. For more information, refer to <>.
+
+[float]
+[[grant-user-access]]
+=== Grant users access to reporting
+
+When security is enabled, access to the {report-features} is controlled by roles and privileges.
+
+[[reporting-app-users]]
+In 7.12.0 and earlier, you grant access to the {report-features} by assigning users the `reporting_user` role in {es}.
+
+In 7.14.0 and later, you configure *Reporting* to use <>. By using {kib} privileges, you can define custom roles that grant *Reporting* privileges as sub-features of {kib} applications.
+
+To grant users permission to generate reports and view their reports in *Reporting*, create and assign the reporting role.
+
+. Create the reporting role.
+
+.. Open the main menu, then click *Stack Management*.
+
+.. Click *Roles > Create role*.
+
+. Specify the role settings.
+
+.. Enter the *Role name*. For example, `custom_reporting_user`.
+
+.. Specify the *Indices* and *Privileges*.
++
+Access to data is an index-level privilege. For each index that contains the data you want to include in reports, add a line, then give each index `read` and `view_index_metadata` privileges.
++
+For more information, refer to {ref}/security-privileges.html[Security privileges].
++
+[role="screenshot"]
+image::user/security/images/reporting-privileges-example.png["Reporting privileges"]
+
+. Add the {kib} privileges.
+
+.. Click *Add Kibana privilege*.
+
+.. Select one or more *Spaces* that you want to grant reporting privileges to.
+
+.. Click *Customize*, then click *Analytics*.
+
+.. Next to each application you want to grant reporting privileges to, click *All*.
++
+[role="screenshot"]
+image::user/security/images/reporting-custom-role.png["Reporting custom role"]
+
+.. Click *Add {kib} privilege*.
+
+. Click *Create role*.
+
+. Assign the reporting role to a user.
+
+.. Open the main menu, then click *Stack Management*.
+
+.. Click *Users*, then click the user you want to assign the reporting role to.
+
+.. From the *Roles* dropdown, select *custom_reporting_user*.
+
+.. Click *Update user*.
+
+[float]
+[[reporting-roles-user-api]]
+==== Grant access with the role API
+You can also use the {ref}/security-api-put-role.html[role API] to grant access to the reporting features. Grant the reporting role to users in combination with other roles that grant read access to the data in {es}, and at least read access in the applications where users can generate reports.
+
+[source, sh]
+---------------------------------------------------------------
+POST /_security/role/custom_reporting_user
+{
+ metadata: {},
+ elasticsearch: { cluster: [], indices: [], run_as: [] },
+ kibana: [
+ {
+ base: [],
+ feature: {
+ dashboard: [
+ 'generate_report', <1>
+ 'download_csv_report' <2>
+ ],
+ discover: ['generate_report'], <3>
+ canvas: ['generate_report'], <4>
+ visualize: ['generate_report'], <5>
+ },
+ spaces: ['*'],
+ }
+ ]
+}
+---------------------------------------------------------------
+// CONSOLE
+
+<1> Grants access to generate PNG and PDF reports in *Dashboard*.
+<2> Grants access to download CSV files from saved search panels in *Dashboard*.
+<3> Grants access to generate CSV reports from saved searches in *Discover*.
+<4> Grants access to generate PDF reports in *Canvas*.
+<5> Grants access to generate PNG and PDF reports in *Visualize Library*.
+
+[float]
+==== Grant access using an external provider
+
+If you are using an external identity provider, such as LDAP or Active Directory, you can assign roles to individual users or groups of users. Role mappings are configured in {ref}/mapping-roles.html[`config/role_mapping.yml`].
+
+For example, assign the `kibana_admin` and `reporting_user` roles to the Bill Murray user:
+
+[source,yaml]
+--------------------------------------------------------------------------------
+kibana_admin:
+ - "cn=Bill Murray,dc=example,dc=com"
+reporting_user:
+ - "cn=Bill Murray,dc=example,dc=com"
+--------------------------------------------------------------------------------
+
+[float]
+==== Grant access with a custom index
+
+If you are using a custom index, the `xpack.reporting.index` setting must begin with `.reporting-*`. The default {kib} system user has `all` privileges against the `.reporting-*` pattern of indices.
+
+If you use a different pattern for the `xpack.reporting.index` setting, you must create a custom `kibana_system` user with appropriate access to the index.
+
+NOTE: In the next major version of Kibana, granting access with a custom index is unsupported.
+
+. Create the reporting role.
+
+.. Open the main menu, then click *Stack Management*.
+
+.. Click *Roles > Create role*.
+
+. Specify the role settings.
+
+.. Enter the *Role name*. For example, `custom-reporting-user`.
+
+.. From the *Indices* dropdown, select the custom index.
+
+.. From the *Privileges* dropdown, select *all*.
+
+.. Click *Add Kibana privilege*.
+
+.. Select one or more *Spaces* that you want to grant reporting privileges to.
+
+.. Click *Customize*, then click *Analytics*.
+
+.. Next to each application you want to grant reporting privileges to, click *All*.
+
+.. Click *Add {kib} privilege*, then click *Create role*.
+
+. Assign the reporting role to a user.
+
+.. Open the main menu, then click *Stack Management*.
+
+.. Click *Users*, then click the user you want to assign the reporting role to.
+
+.. From the *Roles* dropdown, select *kibana_system* and *custom-reporting-user*.
+
+.. Click *Update user*.
+
+. Configure {kib} to use the new account.
++
+[source,js]
+--------------------------------------------------------------------------------
+elasticsearch.username: 'custom_kibana_system'
+--------------------------------------------------------------------------------
+
+[float]
+[[securing-reporting]]
+=== Secure the reporting endpoints
+
+To automatically generate reports with {watcher}, you must configure {watcher} to trust the {kib} server certificate.
+
+. Enable {stack-security-features} on your {es} cluster. For more information, see {ref}/security-getting-started.html[Getting started with security].
+
+. Configure TLS/SSL encryption for the {kib} server. For more information, see <>.
+
+. Specify the {kib} server CA certificate chain in `elasticsearch.yml`:
++
+--
+If you are using your own CA to sign the {kib} server certificate, then you need to specify the CA certificate chain in {es} to properly establish trust in TLS connections between {watcher} and {kib}. If your CA certificate chain is contained in a PKCS #12 trust store, specify it like so:
+
+[source,yaml]
+--------------------------------------------------------------------------------
+xpack.http.ssl.truststore.path: "/path/to/your/truststore.p12"
+xpack.http.ssl.truststore.type: "PKCS12"
+xpack.http.ssl.truststore.password: "optional decryption password"
+--------------------------------------------------------------------------------
+
+Otherwise, if your CA certificate chain is in PEM format, specify it like so:
+
+[source,yaml]
+--------------------------------------------------------------------------------
+xpack.http.ssl.certificate_authorities: ["/path/to/your/cacert1.pem", "/path/to/your/cacert2.pem"]
+--------------------------------------------------------------------------------
+
+For more information, see {ref}/notification-settings.html#ssl-notification-settings[the {watcher} HTTP TLS/SSL Settings].
+--
+
+. Add one or more users who have access to the {report-features}.
++
+Once you've enabled SSL for {kib}, all requests to the reporting endpoints must include valid credentials.
diff --git a/docs/setup/embedding.asciidoc b/docs/setup/embedding.asciidoc
deleted file mode 100644
index 0c48aeefc95577..00000000000000
--- a/docs/setup/embedding.asciidoc
+++ /dev/null
@@ -1,63 +0,0 @@
-[[embedding]]
-== Embed {kib} content in a web page
-
-Once you create a dashboard or a visualization, you might want to share it with your colleagues or friends. The easiest way to do this is to share a direct link to your dashboard or visualization. However, some users might not have access to your {kib}.
-
-With the {kib} embedding functionality, you can display the content you created in {kib} to an internal company website or a personal web page.
-
-. Open the main menu, then click *Dashboard* or *Visualize Library*.
-
-. Open the dashboard or visualization you want to embed.
-
-. To generate the HTML code snippet, open the *Share* menu, then click *Embed code > Copy iFrame code*.
-+
-You can embed this snippet in your web page, and then add analysis, images, and links to give more context to the object you're sharing.
-+
-image::images/embed-kibana.png[Generate an HTML snippet to embed {kib}, align=center]
-+
-NOTE: Embedding of any other part of {kib} is also generally possible, but you might need to craft the proper HTML code manually.
-
-[float]
-[[embedding-security]]
-=== Configure security
-
-Embedding content through iframes requires careful consideration to minimize security risks. By default, modern web browsers enforce the
-https://developer.mozilla.org/en-US/docs/Web/Security/Same-origin_policy[same-origin policy] to restrict the behavior of framed pages. When
-{stack-security-features} are enabled on your cluster, you must relax this constraint for cookies as described in <> for {kib} to function
-in an iframe. Refer to https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe[iframe] and
-https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite[SameSite cookies] for more information.
-
-[float]
-==== Authentication
-If you're embedding {kib} in a website that supports Single Sign-On with SAML, OpenID Connect, Kerberos, or PKI, it's highly advisable to configure {kib} as a part of the Single Sign-On setup. Operating in a single and properly configured security domain provides you with the most secure and seamless user experience. You can read more at <>.
-
-If you want users to access embedded {kib} by skipping the login step, and Single Sign-On isn't an option for you, consider configuring <>. It is already natively integrated into the workflow for embedding dashboards and visualizations.
-
-If you have multiple authentication providers enabled, and you want to automatically log in anonymous users when embedding anything other than dashboards and visualizations, then you will need to add the `auth_provider_hint=` query string parameter to the {kib} URL that you're embedding.
-
-For example, if you craft the iframe code to embed {kib}, it might look like this:
-
-```html
-
-```
-
-To make this iframe leverage anonymous access automatically, you will need to modify a link to {kib} in the `src` iframe attribute to look like this:
-
-```html
-
-```
-
-Note that the `auth_provider_hint` query string parameter goes *before* the hash URL fragment.
-
-[float]
-[[embedding-cookies]]
-==== Cookies
-
-Regardless of the authentication type that you're using for the embedded {kib}, you must make sure that the browsers can transmit session cookies to a {kib} server. The setting you need to be aware of is <>. To support modern browsers, you must set it to `None`:
-
-[source,yaml]
---
-xpack.security.sameSiteCookies: "None"
---
-
-For more information about possible values and implications, go to <>.
\ No newline at end of file
diff --git a/docs/user/canvas.asciidoc b/docs/user/canvas.asciidoc
index 1face015f1f76b..08462e0f0ed241 100644
--- a/docs/user/canvas.asciidoc
+++ b/docs/user/canvas.asciidoc
@@ -194,14 +194,24 @@ Organize and separate your ideas by adding more pages.
[role="screenshot"]
image::images/canvas-add-pages.gif[Add pages]
+[float]
+[[workpad-share-options]]
+== Share your workpad
+
+To share workpads with a larger audience, click *Share* in the toolbar. For detailed information about the sharing options, refer to <>.
+
+[float]
+[[export-single-workpad]]
+== Export workpads
+
+Want to export multiple workpads? Go to the *Canvas* home page, select the workpads you want to export, then click *Export*.
+
--
include::{kib-repo-dir}/canvas/canvas-edit-workpads.asciidoc[]
include::{kib-repo-dir}/canvas/canvas-present-workpad.asciidoc[]
-include::{kib-repo-dir}/canvas/canvas-share-workpad.asciidoc[]
-
include::{kib-repo-dir}/canvas/canvas-tutorial.asciidoc[]
include::{kib-repo-dir}/canvas/canvas-expression-lifecycle.asciidoc[]
diff --git a/docs/user/dashboard/dashboard.asciidoc b/docs/user/dashboard/dashboard.asciidoc
index 8226e9c6ed073f..af67b3016454b0 100644
--- a/docs/user/dashboard/dashboard.asciidoc
+++ b/docs/user/dashboard/dashboard.asciidoc
@@ -301,7 +301,7 @@ image:images/Dashboard_inspect.png[Inspect in dashboard]
[[share-the-dashboard]]
== Share dashboards
-To share the dashboard with a larger audience, click *Share* in the toolbar. For detailed information, refer to <>.
+To share the dashboard with a larger audience, click *Share* in the toolbar. For detailed information about the sharing options, refer to <>.
[float]
[[import-dashboards]]
diff --git a/docs/user/discover.asciidoc b/docs/user/discover.asciidoc
index 82a7dd300f028b..f0029f8925b3c4 100644
--- a/docs/user/discover.asciidoc
+++ b/docs/user/discover.asciidoc
@@ -272,6 +272,12 @@ your data appears in a map.
[role="screenshot"]
image:images/discover-maps.png[Map containing documents]
+[float]
+[[share-your-findings]]
+=== Share your findings
+
+To share your findings with a larger audience, click *Share* in the toolbar. For detailed information about the sharing options, refer to <>.
+
[float]
=== What’s next?
diff --git a/docs/user/index.asciidoc b/docs/user/index.asciidoc
index 47d86004fdc664..29dd5e49a668b3 100644
--- a/docs/user/index.asciidoc
+++ b/docs/user/index.asciidoc
@@ -8,13 +8,6 @@ include::{kib-repo-dir}/getting-started/quick-start-guide.asciidoc[]
include::setup.asciidoc[]
-include::monitoring/configuring-monitoring.asciidoc[leveloffset=+1]
-include::monitoring/monitoring-metricbeat.asciidoc[leveloffset=+2]
-include::monitoring/viewing-metrics.asciidoc[leveloffset=+2]
-include::monitoring/monitoring-kibana.asciidoc[leveloffset=+2]
-
-include::security/securing-kibana.asciidoc[]
-
include::production-considerations/index.asciidoc[]
include::discover.asciidoc[]
@@ -25,6 +18,8 @@ include::canvas.asciidoc[]
include::{kib-repo-dir}/maps/index.asciidoc[]
+include::reporting/index.asciidoc[]
+
include::ml/index.asciidoc[]
include::graph/index.asciidoc[]
@@ -45,8 +40,6 @@ include::management.asciidoc[]
include::{kib-repo-dir}/fleet/fleet.asciidoc[]
-include::reporting/index.asciidoc[]
-
include::api.asciidoc[]
include::plugins.asciidoc[]
diff --git a/docs/user/production-considerations/index.asciidoc b/docs/user/production-considerations/index.asciidoc
index c52aade575968f..198e8324af3e6c 100644
--- a/docs/user/production-considerations/index.asciidoc
+++ b/docs/user/production-considerations/index.asciidoc
@@ -1,5 +1,6 @@
include::production.asciidoc[]
include::alerting-production-considerations.asciidoc[]
+include::reporting-production-considerations.asciidoc[]
include::task-manager-production-considerations.asciidoc[]
include::task-manager-health-monitoring.asciidoc[]
include::task-manager-troubleshooting.asciidoc[]
diff --git a/docs/user/production-considerations/reporting-production-considerations.asciidoc b/docs/user/production-considerations/reporting-production-considerations.asciidoc
new file mode 100644
index 00000000000000..32752cbe69ab8c
--- /dev/null
+++ b/docs/user/production-considerations/reporting-production-considerations.asciidoc
@@ -0,0 +1,36 @@
+[role="xpack"]
+[[reporting-production-considerations]]
+== Reporting production considerations
+
+++++
+Reporting
+++++
+:keywords: administrator, analyst, concept, setup, reporting
+:description: Consider the production components that are used to generate reports.
+
+To generate reports, {kib} uses a custom build of the Chromium web browser, which runs on the {kib} server in headless mode to load {kib} and capture the rendered {kib} visualizations as images. Chromium is an open-source project not related to Elastic, but the Chromium binary for {kib} has been custom-built by Elastic to make sure it works with minimal setup. The operating system that the {kib} server uses can require additional dependencies for Chromium.
+
+[float]
+[[reporting-chromium-sandbox]]
+=== Chromium sandbox
+For an additional layer of security, use the sandbox. The Chromium sandbox uses operating system-provided mechanisms to ensure that code execution cannot make persistent changes to the computer or access confidential information. The specific sandboxing techniques differ for each operating system.
+
+[float]
+[[reporting-linux-sandbox]]
+==== Linux sandbox
+The Linux sandbox depends on user namespaces, which were introduced with the 3.8 Linux kernel. However, many
+distributions don't have user namespaces enabled by default, or they require the CAP_SYS_ADMIN capability. The {report-features}
+automatically disable the sandbox when it is running on Debian and CentOS, as additional steps are required to enable
+unprivileged usernamespaces. In these situations, you'll see the following message in your {kib} startup logs:
+`Chromium sandbox provides an additional layer of protection, but is not supported for your OS.
+Automatically setting 'xpack.reporting.capture.browser.chromium.disableSandbox: true'.`
+
+Reporting automatically enables the Chromium sandbox at startup when a supported OS is detected. However, if your kernel is 3.8 or newer, it's
+recommended to set `xpack.reporting.capture.browser.chromium.disableSandbox: false` in your `kibana.yml` to explicitly enable usernamespaces.
+
+[float]
+[[reporting-docker-sandbox]]
+==== Docker
+When running {kib} in a Docker container, all container processes are run within a usernamespace with seccomp-bpf and
+AppArmor profiles that prevent the Chromium sandbox from being used. In these situations, disabling the sandbox is recommended,
+as the container implements similar security mechanisms.
\ No newline at end of file
diff --git a/docs/user/reporting/automating-report-generation.asciidoc b/docs/user/reporting/automating-report-generation.asciidoc
index 6c86da8e214c16..3ddb4a36949940 100644
--- a/docs/user/reporting/automating-report-generation.asciidoc
+++ b/docs/user/reporting/automating-report-generation.asciidoc
@@ -1,69 +1,62 @@
[role="xpack"]
[[automating-report-generation]]
-== Automating report generation
-Automatically generate PDF and CSV reports by submitting HTTP `POST` requests using {watcher} or a script.
+== Automatically generate reports
-include::report-intervals.asciidoc[]
+To automatically generate PDF and CSV reports, generate a POST URL, then submit the HTTP `POST` request using {watcher} or a script.
[float]
+[[create-a-post-url]]
=== Create a POST URL
Create the POST URL that triggers a report to generate PDF and CSV reports.
To create the POST URL for PDF reports:
-. Open the dashboard, visualization, or **Canvas** workpad.
+. Open the main menu, then click *Dashboard, *Visualize Library*, or *Canvas*.
-. From the {kib} toolbar, click *Share*, then select *PDF Reports*.
+. Open the dashboard, visualization, or **Canvas** workpad you want to view as a report.
-. If you are using **Canvas**, click *Advanced options*.
+. From the toolbar, click *Share > PDF Reports*, then choose an option:
-. Click *Copy POST URL*.
-+
-[role="screenshot"]
-image::images/report-automate-pdf.png[Automatically generate *Dashboard* and *Visualize Library* reports]
+* If you are using *Dashboard* or *Visulize Library*, click *Copy POST URL*.
+* If you are using *Canvas*, click *Advanced options > Copy POST URL*.
To create the POST URL for CSV reports:
-. In *Discover*, open the saved search.
+. Open the main menu, then click *Discover*.
-. From the {kib} toolbar, click *Share*, then select *CSV Reports*.
+. Open the saved search you want to share.
-. Click *Copy POST URL*.
-+
-[role="screenshot"]
-image::images/report-automate-csv.png[Generate Discover reports]
+. In the toolbar, click *Share > CSV Reports > Copy POST URL*.
[float]
+[[use-watcher]]
=== Use Watcher
include::watch-example.asciidoc[]
[float]
+[[use-a-script]]
=== Use a script
include::script-example.asciidoc[]
[float]
+[[reporting-response-codes]]
=== HTTP response codes
include::response-codes.asciidoc[]
[float]
+[[deprecated-report-urls]]
=== Deprecated report URLs
-The following POST URL paths are deprecated. If there are
-any problems with using these paths after you upgrade {kib}, use
-{kib} to regenerate the POST URL for a particular report.
+If you experience issues with the deprecated report URLs after you upgrade {kib}, regenerate the POST URL for your reports.
-* Dashboard reports: `/api/reporting/generate/dashboard/`
-* Visualize reports: `/api/reporting/generate/visualization/`
-* Saved Search reports: `/api/reporting/generate/search/`
+* *Dashboard* reports: `/api/reporting/generate/dashboard/`
+* *Visualize Library* reports: `/api/reporting/generate/visualization/`
+* *Discover* saved search reports: `/api/reporting/generate/search/`
-[IMPORTANT]
-===================
-Previously there was a `&sync` parameter appended to generation URLs which would hold
-the request open until the document was fully generated. This feature has been removed.
-Existing use of the `&sync` parameter, in Watcher for example, will need to be updated.
-===================
+IMPORTANT:
+In earlier {kib} versions, you could use the `&sync` parameter to append to report URLs that held the request open until the document was fully generated. The `&sync` parameter is now unsupported. If you use the `&sync` parameter in Watcher, you must update the parameter.
diff --git a/docs/user/reporting/chromium-sandbox.asciidoc b/docs/user/reporting/chromium-sandbox.asciidoc
deleted file mode 100644
index dcb421261c0678..00000000000000
--- a/docs/user/reporting/chromium-sandbox.asciidoc
+++ /dev/null
@@ -1,26 +0,0 @@
-[role="xpack"]
-[[reporting-chromium-sandbox]]
-=== Chromium sandbox
-
-When {report-features} uses the Chromium browser for generating PDF reports,
-it's recommended to use the sandbox for an additional layer of security. The
-Chromium sandbox uses operating system provided mechanisms to ensure that
-code execution cannot make persistent changes to the computer or access
-confidential information. The specific sandboxing techniques differ for each
-operating system.
-
-==== Linux sandbox
-The Linux sandbox depends on user namespaces, which were introduced with the 3.8 Linux kernel. However, many
-distributions don't have user namespaces enabled by default, or they require the CAP_SYS_ADMIN capability. The {report-features}
-will automatically disable the sandbox when it is running on Debian and CentOS as additional steps are required to enable
-unprivileged usernamespaces. In these situations, you'll see the following message in your {kib} startup logs:
-`Chromium sandbox provides an additional layer of protection, but is not supported for your OS.
-Automatically setting 'xpack.reporting.capture.browser.chromium.disableSandbox: true'.`
-
-Reporting will automatically enable the Chromium sandbox at startup when a supported OS is detected. However, if your kernel is 3.8 or newer, it's
-recommended to set `xpack.reporting.capture.browser.chromium.disableSandbox: false` in your `kibana.yml` to explicitly enable usernamespaces.
-
-==== Docker
-When running {kib} in a Docker container, all container processes are run within a usernamespace with seccomp-bpf and
-AppArmor profiles that prevent the Chromium sandbox from being used. In these situations, disabling the sandbox is recommended,
-as the container implements similar security mechanisms.
\ No newline at end of file
diff --git a/docs/user/reporting/configuring-reporting.asciidoc b/docs/user/reporting/configuring-reporting.asciidoc
deleted file mode 100644
index a8b76f36b9a844..00000000000000
--- a/docs/user/reporting/configuring-reporting.asciidoc
+++ /dev/null
@@ -1,78 +0,0 @@
-[role="xpack"]
-[[configuring-reporting]]
-== Reporting configuration
-
-You can configure settings in `kibana.yml` to control how the {report-features}
-communicate with the {kib} server, manages background jobs, and captures
-screenshots. See <> for the complete
-list of settings.
-
-[float]
-[[encryption-keys]]
-=== Encryption keys for multiple {kib} instances
-
-By default, a new encryption key is generated for the {report-features} each
-time you start {kib}. This means if a static encryption key is not persisted in
-the {kib} configuration, any pending reports will fail when you restart {kib}.
-
-If you are load balancing across multiple {kib} instances, they need to have
-the same reporting encryption key. Otherwise, report generation will fail if a
-report is queued through one instance and another instance picks up the job
-from the report queue. The other instance will not be able to decrypt the
-reporting job metadata.
-
-To set a static encryption key for reporting, set the
-`xpack.reporting.encryptionKey` property in the `kibana.yml`
-configuration file. You can use any alphanumeric, at least 32 characters long text string as the encryption key.
-
-[source,yaml]
---------------------------------------------------------------------------------
-xpack.reporting.encryptionKey: "something_secret"
---------------------------------------------------------------------------------
-
-[float]
-[[report-indices]]
-=== Report indices for multiple {kib} workspaces
-
-If you divide workspaces in an Elastic cluster using multiple {kib} instances
-with a different `kibana.index` setting per instance, you must set a unique `xpack.reporting.index`
-setting per `kibana.index`. Otherwise, report generation will periodically fail
-if a report is queued through an instance with one `kibana.index` setting, and
-an instance with a different `kibana.index` attempts to claim the job.
-
-Kibana instance A:
-[source,yaml]
---------------------------------------------------------------------------------
-kibana.index: ".kibana-a"
-xpack.reporting.index: ".reporting-a"
-xpack.reporting.encryptionKey: "something_secret"
---------------------------------------------------------------------------------
-
-Kibana instance B:
-[source,yaml]
---------------------------------------------------------------------------------
-kibana.index: ".kibana-b"
-xpack.reporting.index: ".reporting-b"
-xpack.reporting.encryptionKey: "something_secret"
---------------------------------------------------------------------------------
-
-NOTE: If security is enabled, the `xpack.reporting.index` setting should begin
-with `.reporting-` in order for the `kibana_system` role to have the necessary
-privileges over the index.
-
-[float]
-[[using-reverse-proxies]]
-=== Use reverse proxies
-
-If your {kib} instance requires a reverse proxy (NGINX, Apache, etc.) for
-access, because of rewrite rules or special headers being added by the proxy,
-then you need to configure the `xpack.reporting.kibanaServer` settings to make
-the headless browser process connect to the proxy in <>.
-
-NOTE: A headless browser runs on the Kibana server to open a Kibana page for
-capturing screenshots. Configuring the `xpack.reporting.kibanaServer` settings
-to point to a proxy host requires that the Kibana server has network access to
-the proxy.
-
-include::{kib-repo-dir}/user/security/reporting.asciidoc[]
-include::network-policy.asciidoc[]
diff --git a/docs/user/reporting/generating-reports.asciidoc b/docs/user/reporting/generating-reports.asciidoc
deleted file mode 100644
index 6503838cb44143..00000000000000
--- a/docs/user/reporting/generating-reports.asciidoc
+++ /dev/null
@@ -1 +0,0 @@
-[role="xpack"]
diff --git a/docs/user/reporting/gs-index.asciidoc b/docs/user/reporting/gs-index.asciidoc
deleted file mode 100644
index 46c1fd38b7d69b..00000000000000
--- a/docs/user/reporting/gs-index.asciidoc
+++ /dev/null
@@ -1,28 +0,0 @@
-[role="xpack"]
-[[xpack-reporting]]
-= Reporting from Kibana
-
-[partintro]
---
-You can generate reports that contain {kib} dashboards,
-visualizations, and saved searches. The reports are exported as
-print-optimized PDF documents.
-
-NOTE: On Linux, the `libfontconfig` and `libfreetype6` packages and system
-fonts are required to generate reports. If no system fonts are available,
-labels are not rendered correctly in the reports.
-
-The following Reporting button appears in the {kib} toolbar:
-
-image:images/reporting.jpg["Reporting",link="reporting.jpg"]
-
-You can also <>.
-
-IMPORTANT: Reports are stored in the `.reporting-*` indices. Any user with
-access to these indices has access to every report generated by all users.
-
-To use {report-features} in a production environment,
-<>.
---
-
-include::getting-started.asciidoc[]
diff --git a/docs/user/reporting/images/embed-code-public-url.png b/docs/user/reporting/images/embed-code-public-url.png
new file mode 100644
index 0000000000000000000000000000000000000000..4ecd6c47122d106bde3d13d6f407efe6107f0d8d
GIT binary patch
literal 37509
zcmagF1ymf*@-M!?;u;7cxVyW%E^fhs26qqc9^BnMxCD0y?(VL^-QjJ%_rCkP=l}li
zyxB8ndZxRorn-B2tE)a0rYJ9ojDUjx0059dQew*B>lOe25d{YWE*Y2@34?Dy=AxpC
zAW=~wMF(3Gb1P#2fGXBdUmqPrM>S|*psznTMoW$0;G!HF8lkN3Gu+irG}zUxKS>
zqOHC88Eq3vwFi)|)ZJ)_6hZV<%P>ec^C;s_PH->3deX67t-1NbLH3r)mzMk&7q-+p
z#CF84e;zsvO#ersugz@*fW9+8t@sb$D9G9a-^C-eI~X)x_#OdBH4OA!B%=*LEp!|!
zx}OPC2cXtDE~alLc2Uto*q4#6XK=MkgLsjBaE3j_Hvj}pUW!Rhw9MU^Q%l9lpD)(^
z;j?rpLYCW*I}d^cj6^UJQVvr{$r~OJMMk2=3$Esc90}>u#^=u|lq7v|MNCXqwubsK
zO+jcXsRLbI8$Dgsr;5Vglt@+~A~rOB%tS;ISrK@^z|6P4zx&MV>+5F^c3rq^zrS~4
zy}!S^Fi*a$AR)ohK%{*!u<}JcIs^A@lCcKJL{1Js11`e>pdru!P~Z{-_y>T%0YLv#
z1^}cXaQ{@MnvMVfBF5hn0+5#R832IfH&@kg
z(vXwoF|@U2Ffg+HX3XGfZTFWCfX|f&T(mZJG9Yrbwz6^LapfoZrv(qV{P!^<3DG}I
zoGkfCG~^VCL~R|6i8vUT7??-|5QvC~_#BK(c$CE?{!I?P<0mn5a9>~GD#$o|pmU*h=w
zGUHJ+cQv-s6f?I5cQyFc1X#Jb`2G>|f7JZXi~fu9i=(lFsI4`a&`IEb#_He1|6Te2
zCHzOE#{Y<9VPXGok^fcmUzC4U;8Ace2TL>f+lK00;p_u#&-Vw$9kz2q6}9LyHErTh
z+T&5mb6Bfzth^b;(+`<&9^Y_)MhMF@}SE8C9_oS4^yAX;q_?hsS5oNV=ybU#?2{ygRll5vhXbNB>Wu6_-#;
zd|JYg#KgkNIy&^p(~RL**Wlv|ptgIxQaJmB!@FbU)}=KwJ6uRsP9~W_6{V5&!RCN)
zO5p22lCNYk!(^~}federFmD5!^=SogH3m?@iep-_`DB;xl-?)ZC7mMyNiZ46p#iS}
zzsdryhzl9`r?$yBu^Rd-ypsw1q$Mia@@3ulD%?rkhC955-A)-r@2F@IfhI~)bw5O;SgD=U)}78Y(F
zAJe|Qy`?eY5{&G&_vDdW-d**OISH1QmnYQLI?NU-Uw|o?fh%_bF={Bhcxgya`VBKEcUjA+_F50SNiT?BF=dv*Q
zKH+Dy5@)Lt@3hp{nKSaqZ)j=3DT#^nww_^?HciX>ZuI_lxX(r)t
z^2JH>N%l%9vtx`$)CRjnLkc9L&sdy?KZ2wVi|mI-MvPydB5RG0)xshp6H8SnM=p0L
zjo#m0dpt4%HxG^};ltL99;yw}yf->tYdwEUThB(p`0=m*R(ZNVrermdjd$6276SLf
zZIB2+Ok5H)k?;|l&ow46D2N74QxK*@NLpI@R~tU>_u(bSQiK%mLCe)9=4t+#R&Ppx
zW|j4d%U~D=WvB`W1WNiq8a(3r_R2P~)_Om@?0jrtv`I}&+#Q#aLInkLBPtMvFuzBu
zHsl8dTW=#Fp>50UJgkZ>;90Vtk6Gu#lL6EMiui}(TqwJ@yqwxrl@+-?h3g3gn5A`w
zAU|#w%3RHVJvS3rYjF;M>8hn^+iLyo5-)i$7XPJeVQ{)|kNWGTH0x#@9s^wfb8kmzTG(gP~Gpi;zPDrcXDeSM8neQ74PdGP*vm
za_|T!(=UrD?KxW|ZWIbxWQ5)~HQG`#@m)DWmzQ@_ERax8M(b@Mdol_>O8&9qzSXt0
z(rT4jS{Jz|e%E}ir`tr8&
z@$K$J3(oWS?3{N{LRj!uY&Lxte|M=-7|cE3}a5JCVDP
z2N0<{?zjg?vX|=4Fcby{F`SEwzO#E#tKjk^0axb(wPD5e+g^stmW!W;le7GieoRTM
zX@3A%E^o+4NyV6@_?|>V=LpFeop@oc`Sa3GTeg)Hi7mZIX4P>3VQ
zGEJXqX>zB~8sob{RB&xL6leT|7Tu*C9u+kYb3U%3B#O^vr`U1dP6*I{8ypbbA8jTq
zT0CIm_5Wd_At4cbvfeJ4N%S?ByvHt6pPhn)l$2Hz8RPpxrOr=&KK^$7c>%f)A3nI(
zAw)%5uQZIPXFt0~QrsjFzV+T4-=rx0xv=xAn=jPc=s@TR$71}MqoCNdi&_PG
z^F~qfW49by6h5m~Ffx9gv$UMtmzlV07Am5XO29Px&mq
zLKJKsJF4PnLCu_3$vflTONYk;pQqeAM!EId>)MT#PT(pGF<~SM8Y7!S$J$qPI6QX}
z(hIfw5cQbI7+ncn)0|KFVW5HjJO!+>REbk&nAcJ^GkAA4Iek%MABVodEen=u^DCOC
zvfg0iFFlkNc!aZ@2*yg`?j9bc63jt0viri?syE$PE9mUSj&twT#%Yk
zyVc<$y4^FDk)Fl)Yu#QD)*Bt+=({3;$IS<8Jhnn@TuYU{-UleyyX5z$2_N;y;d3C#
zvpWU&Ly{V+G#*K^d^^FK&VSthYO9ACZ4QfyO||hh&*>z!`JVNjNT=7KjDpXjJ#*1&
z_I^?<_{e^rf=|FBnXFjNRSn^kElw`+Ivwh7J#Yw^a1>
z@uVTqgQ<0<X1ndTtWOBE@mNG3v#gk!>e6J5pWNXtly%%PCjb
zQ}gXYdeTu
zUZG~guEu%KhbR%Tjx?x31lGOIy-x3=0vpak41S0L@b<(RkAGm6G0Db}%Oo)j;0haU
z_rU`!ApampA_*K!Nv;KUFlWzUEH@}*uP^(MeYQSKLAS#V^wj3)KU>4tYd+{8VeEK_
zYgr=nGRpGLr;hYLI@&XY;`4fncsV)foFs;7xlBDTt7
zCi|$4N%?j@pLwKV4ICd9xNZ71|6EGxbhBCCait28F}r%(#(FP%{B1CL^vxmB>MTRHNWn8)ghEtsE+SXd9nmDp;rT1gKjLmv$m`Dro$bx`{z
za_Qc%{n5b3<@E_!3)TNBed8s#T0tILDT~9258gF{JN-L3GqIYFkc*EPK5bh9gKgVo
zQs6hm=aC7%ipXt=?VQvBVQ-F(+A9{JXb7K6K`tr{*iDySL=Yoow~#vjFJpoF{aJIB
z`pyQ_w4dlE$G#A+&YSOavRvamxu0^A5Ru^%*S*|xl?H}NXNq?>{X{Kvr%#N82@aE)
z_0pn`C!4q%1`l$Rayj-V;X^1RmQ0NbUL|JVcS}{6oTE$bc202RDL%6p!QlySJ&EY^
z$xAJw4yf?RDtZ^Im~3`I4Du>vKlX*i^AP8XP-5r!L_QZ-tX%VnAxk
zIn5hSZu(7^nNBP}F;^nvi$dODTCF*2Xh8>h8gVeBDP{OexOl0IO+^t`9xudA*mMb3
znE3ulLpTpyHaExHaMb0hWIk~-jrWBx2p?5al?IM4sSu6^)!^KRf+Ce!yO2VK^Z%
zxke>#XnuOT@)fcIM&w6WWKA8!#hneo-H}Z&Fm1o54V%5jSOeZk8D0bJ`!wOY!1w1^
zUuc8pM|vwco>Cwk9PbgTEn(}cRF{AC>&vGG3b3XBDKPUUq%nHUn@nGL(c2sGYrH%S
z1Y(c;Q}4=vjUak8k(FR)Pz8uG5zL74b?elykr2$iA$y}|z93#2%qaft;;v>T8rar^
z6oN;c;t|;e&H1kl2NJw0giBwxy0=nk23Pu4xp#H=s!i~THMSZg4FqhU#`Y)lLsXRb
zlr&>0HJ-ma2TmDC;Femo#G$)LTHg4;&z|&Ks6~fzARk}#7mmX(n<+J~LMwRXK${?7
z$yJ;1JT#itsSB+);A>GLpymK1cjzi!Ayf_oH)sHc@dQs8gByC1|C?T}~%cms=;D
zBc2w6T!m9nTKfm#&akS#M$0LezQ?1&M
ziUg&pLCUIXxsPlKnBBt;L@bo@bqIB_wv_Nq74XOpcUc{DWp^3Lh??T)xF3k}pPUkT
zp9Vs+QC-TN#ePc+s+gXOa0miln8z|ME$-R-#P`Q?7BX4;$eC`!^Vwr0I(O2jLl}MI
zldpacl!ZHmBlS(G%+wH3%<<^Y^F$*>WUDArBs<6ycsS)$o@)I%@ZbHEI5euXM$R`V
zNx6t)-G60q9DK9Sb4^2C73ciSD9v+)6Ng7#MaNIK1KU4DiV>fArQw(6OCzOZUif1D
zn;G;6hwMJ0uVJaF|M-dSp_j)3{o@Y{i%tIa08%~lr